#include "common.h" #include "hybrid.h" hqrEntryStruct* HQ_Anims = nullptr; std::vector BufferAnim; int SetAnimObjet(int frame, sAnimation* pAnimation, sBody* body) { if(frame >= pAnimation->m_numFrames) { return(0); } int numGroupsInAnimation = pAnimation->m_numGroups; sFrame& keyframe = pAnimation->m_frames[frame]; animCurrentTime = keyframe.m_timestamp; animKeyframeLength = animCurrentTime; if(!(body->m_flags & INFO_ANIM)) { return(0); } body->startAnim = &keyframe; *(u16*)(body->m_scratchBuffer.data() + 4) = timer; if(numGroupsInAnimation > body->m_groupOrder.size()) numGroupsInAnimation = body->m_groupOrder.size(); animStepX = keyframe.m_animStep.x; animStepY = keyframe.m_animStep.y; animStepZ = keyframe.m_animStep.z; for(int i=0;i< numGroupsInAnimation;i++) { body->m_groups[i].m_state.m_type = keyframe.m_groups[i].m_type; body->m_groups[i].m_state.m_delta = keyframe.m_groups[i].m_delta; if(body->m_flags & INFO_OPTIMISE) { body->m_groups[i].m_state.m_rotateDelta = keyframe.m_groups[i].m_rotateDelta; } } return(1); } int InitAnim(int animNum,int animType, int animInfo) { if(animNum == currentProcessedActorPtr->ANIM) { if(!(currentProcessedActorPtr->objectType & AF_ANIMATED)) { if(currentProcessedActorPtr->objectType & AF_BOXIFY) { removeFromBGIncrust(currentProcessedActorIdx); } currentProcessedActorPtr->objectType |= AF_ANIMATED; SetAnimObjet(currentProcessedActorPtr->frame, HQR_Get(HQ_Anims,animNum), HQR_Get(HQ_Bodys, currentProcessedActorPtr->bodyNum)); currentProcessedActorPtr->animType = animType; currentProcessedActorPtr->animInfo = animInfo; if(g_gameId > AITD1) { currentProcessedActorPtr->frame = 0; } return 1; } else { currentProcessedActorPtr->animType = animType; currentProcessedActorPtr->animInfo = animInfo; return 0; } } if(animNum == -1) { currentProcessedActorPtr->newAnim = -2; return(1); } if(!(currentProcessedActorPtr->objectType & AF_ANIMATED)) { currentProcessedActorPtr->objectType |= AF_ANIMATED; if(currentProcessedActorPtr->objectType & AF_BOXIFY) { removeFromBGIncrust(currentProcessedActorIdx); } SetAnimObjet(0, HQR_Get(HQ_Anims,animNum), HQR_Get(HQ_Bodys, currentProcessedActorPtr->bodyNum)); currentProcessedActorPtr->newAnim = animNum; currentProcessedActorPtr->newAnimType = animType; currentProcessedActorPtr->newAnimInfo = animInfo; if(g_gameId > AITD1) { currentProcessedActorPtr->frame = 0; } return 1; } if(g_gameId == AITD1) { if(currentProcessedActorPtr->animType & ANIM_UNINTERRUPTABLE) return(0); if(currentProcessedActorPtr->newAnimType & ANIM_UNINTERRUPTABLE) return(0); } else { if(currentProcessedActorPtr->animType & ANIM_UNINTERRUPTABLE) { if(currentProcessedActorPtr->newAnimType & ANIM_UNINTERRUPTABLE) { return 0; } else { currentProcessedActorPtr->animInfo = animNum; return 1; } } } currentProcessedActorPtr->newAnim = animNum; currentProcessedActorPtr->newAnimType = animType; currentProcessedActorPtr->newAnimInfo = animInfo; if(g_gameId != AITD1) { currentProcessedActorPtr->frame = 0; } return(1); } int evaluateReal(RealValue* data) { if(!data->numSteps) return data->endValue; if(timer - data->memoTicks> (unsigned int)data->numSteps) { data->numSteps = 0; return data->endValue; } s32 valueDiff = data->endValue - data->startValue; s32 currentTime = timer - data->memoTicks; s32 interpolatedValue = data->startValue + ((valueDiff * currentTime) / data->numSteps); return interpolatedValue; } int manageFall(int actorIdx, ZVStruct* zvPtr) { int fallResult = 0; int i; int room = ListObjets[actorIdx].room; for(i=0;iindexInWorld != -1 && i != actorIdx) { ZVStruct* testedZv = ¤tTestedActorPtr->zv; if(currentTestedActorPtr->room != room) { ZVStruct localZv; CopyZV(zvPtr, &localZv); AdjustZV(&localZv, room, currentTestedActorPtr->room); if(CubeIntersect(&localZv, testedZv)) { ListObjets[i].COL_BY = actorIdx; fallResult++; } } else { if(CubeIntersect(zvPtr, testedZv)) { ListObjets[i].COL_BY = actorIdx; fallResult++; } } } } return(fallResult); } void GereAnim(void) { if (currentProcessedActorPtr->objectType & AF_OBJ_2D) { if ((currentProcessedActorPtr->ANIM != -1) && (currentProcessedActorPtr->bodyNum != -1)) { sHybrid* pHybrid = HQR_Get(HQ_Hybrides, currentProcessedActorPtr->ANIM); } } int oldStepZ=0; int oldStepY=0; int oldStepX=0; int stepZ=0; int stepY=0; int stepX=0; s16 localTable[3]; ZVStruct zvLocal; ZVStruct* zvPtr; int newAnim = currentProcessedActorPtr->newAnim; if (newAnim != -1) // next anim ? { if (newAnim == -2) // completely stop anim and add actor to background { addActorToBgInscrust(currentProcessedActorIdx); currentProcessedActorPtr->newAnim = -1; currentProcessedActorPtr->newAnimType = 0; currentProcessedActorPtr->newAnimInfo = -1; currentProcessedActorPtr->flagEndAnim = 1; return; } else { if (!(currentProcessedActorPtr->newAnimType & ANIM_RESET)) // Reset { if (currentProcessedActorPtr->END_FRAME == 0) { currentProcessedActorPtr->worldX += currentProcessedActorPtr->stepX; currentProcessedActorPtr->roomX += currentProcessedActorPtr->stepX; currentProcessedActorPtr->worldZ += currentProcessedActorPtr->stepZ; currentProcessedActorPtr->roomZ += currentProcessedActorPtr->stepZ; currentProcessedActorPtr->stepX = 0; currentProcessedActorPtr->stepZ = 0; currentProcessedActorPtr->animNegX = 0; currentProcessedActorPtr->animNegY = 0; currentProcessedActorPtr->animNegZ = 0; } // TODO: AITD3 has some extra code here to handle bufferAnimCounter StockInterAnim(BufferAnim[bufferAnimCounter], HQR_Get(HQ_Bodys, currentProcessedActorPtr->bodyNum)); bufferAnimCounter++; if (bufferAnimCounter == NB_BUFFER_ANIM) bufferAnimCounter = 0; } else { ResetStartAnim(HQR_Get(HQ_Bodys, currentProcessedActorPtr->bodyNum)); currentProcessedActorPtr->newAnimType &= ~ANIM_RESET; } currentProcessedActorPtr->ANIM = newAnim; currentProcessedActorPtr->animType = currentProcessedActorPtr->newAnimType; currentProcessedActorPtr->animInfo = currentProcessedActorPtr->newAnimInfo; currentProcessedActorPtr->newAnim = -1; currentProcessedActorPtr->newAnimType = 0; currentProcessedActorPtr->newAnimInfo = -1; currentProcessedActorPtr->flagEndAnim = 0; currentProcessedActorPtr->frame = 0; currentProcessedActorPtr->numOfFrames = GetNbFramesAnim(HQR_Get(HQ_Anims, newAnim)); } } if(currentProcessedActorPtr->ANIM == -1) // no animation { currentProcessedActorPtr->END_FRAME = 0; if(currentProcessedActorPtr->speed == 0) { int numObjectCollisions = CheckObjectCol(currentProcessedActorIdx, ¤tProcessedActorPtr->zv); for(int i = 0; iCOL[i]].COL_BY = currentProcessedActorIdx; // collision with current actor } oldStepY = 0; oldStepZ = 0; stepX = 0; stepZ = 0; stepY = 0; } else { oldStepX = currentProcessedActorPtr->stepX; oldStepY = currentProcessedActorPtr->stepY; oldStepZ = currentProcessedActorPtr->stepZ; animStepY = 0; animStepX = 0; animStepZ = evaluateReal(¤tProcessedActorPtr->speedChange); walkStep(0,animStepZ,currentProcessedActorPtr->beta); stepX = animMoveX - oldStepX; stepZ = animMoveZ - oldStepZ; stepY = 0; } } else // animation { oldStepX = currentProcessedActorPtr->stepX; oldStepY = currentProcessedActorPtr->stepY; oldStepZ = currentProcessedActorPtr->stepZ; currentProcessedActorPtr->END_FRAME = SetInterAnimObjet(currentProcessedActorPtr->frame, HQR_Get(HQ_Anims, currentProcessedActorPtr->ANIM), HQR_Get(HQ_Bodys, currentProcessedActorPtr->bodyNum)); walkStep(animStepX,animStepZ,currentProcessedActorPtr->beta); stepX = animMoveX+currentProcessedActorPtr->animNegX - oldStepX; stepZ = animMoveZ+currentProcessedActorPtr->animNegZ - oldStepZ; } if(currentProcessedActorPtr->YHandler.numSteps) // currently falling ? { if(currentProcessedActorPtr->YHandler.numSteps != -1) { stepY = evaluateReal(¤tProcessedActorPtr->YHandler) - oldStepY; } else // stop falling { stepY = currentProcessedActorPtr->YHandler.endValue - oldStepY; currentProcessedActorPtr->YHandler.numSteps = 0; currentProcessedActorPtr->YHandler.endValue = 0; currentProcessedActorPtr->YHandler.startValue = 0; } } else { stepY = 0; } memcpy(localTable,currentProcessedActorPtr->COL,6); if(stepX || stepY || stepZ) // start of movement management { zvPtr = ¤tProcessedActorPtr->zv; CopyZV(¤tProcessedActorPtr->zv,&zvLocal); zvLocal.ZVX1 += stepX; zvLocal.ZVX2 += stepX; zvLocal.ZVY1 += stepY; zvLocal.ZVY2 += stepY; zvLocal.ZVZ1 += stepZ; zvLocal.ZVZ2 += stepZ; // check for wall collisions if(currentProcessedActorPtr->dynFlags & 1) // hard collision enabled for actor ? { int numCol = AsmCheckListCol(&zvLocal, &roomDataTable[currentProcessedActorPtr->room]); for(int i=0;itype == 9) { currentProcessedActorPtr->HARD_COL = (short)pHardCol->parameter; } // climbable wall if(pHardCol->type == 3) { currentProcessedActorPtr->HARD_COL = 255; } if (g_gameId >= JACK) { // monster collision for floor changes if ((pHardCol->type == 10) && (currentProcessedActorIdx == currentCameraTargetActor)) continue; } if(stepX || stepZ) // move on the X or Z axis ? update to avoid entering the hard col { //ZVStruct tempZv; hardColStepX = stepX; hardColStepZ = stepZ; GereCollision(zvPtr, &zvLocal, &pHardCol->zv); currentProcessedActorPtr->animNegX += hardColStepX - stepX; currentProcessedActorPtr->animNegZ += hardColStepZ - stepZ; zvLocal.ZVX1 += hardColStepX - stepX; zvLocal.ZVX2 += hardColStepX - stepX; zvLocal.ZVZ1 += hardColStepZ - stepZ; zvLocal.ZVZ2 += hardColStepZ - stepZ; stepX = hardColStepX; stepZ = hardColStepZ; } if(stepY) { //assert(0); //not implemented } } } else // no hard collision -> just update the flag without performing the position update { if(AsmCheckListCol(&zvLocal, &roomDataTable[currentProcessedActorPtr->room])) { currentProcessedActorPtr->HARD_COL = 1; } else { currentProcessedActorPtr->HARD_COL = 0; } } // check for object collisions // TODO: AITD2 checks flag collision here int numCol = CheckObjectCol(currentProcessedActorIdx,&zvLocal); // get the number of actor/actor collision for(int j=0;jCOL[j]; tObject* actorTouchedPtr = &ListObjets[collisionIndex]; actorTouchedPtr->COL_BY = currentProcessedActorIdx; ZVStruct* touchedZv = &actorTouchedPtr->zv; if(actorTouchedPtr->objectType & AF_FOUNDABLE) // takable { if(currentProcessedActorPtr->trackMode == 1 /*&& ((gameId == AITD1 && defines.field_1E == 0) || (gameId >= JACK && defines.field_6 == 0))*/) // TODO: check if character isn't dead... { FoundObjet(actorTouchedPtr->indexInWorld, 0); } continue; } { if(actorTouchedPtr->objectType & AF_MOVABLE) // can be pushed ? { ZVStruct localZv2; bool isPushPossible = true; CopyZV(touchedZv, &localZv2); localZv2.ZVX1 += stepX; localZv2.ZVX2 += stepX; localZv2.ZVZ1 += stepZ; localZv2.ZVZ2 += stepZ; // check pushed object against walls if(AsmCheckListCol(&localZv2, &roomDataTable[currentProcessedActorPtr->room])) { isPushPossible = false; } else if (CheckObjectCol(collisionIndex, &localZv2)) { // if no wall preventing to move object, check is there is a another object in the way isPushPossible = false; } if(!isPushPossible) { if(stepX || stepZ) //if we're trying to move { if(actorTouchedPtr->room != currentProcessedActorPtr->room) { ZVStruct localZv3; CopyZV(touchedZv, &localZv3); AdjustZV(&localZv3, actorTouchedPtr->room, currentProcessedActorPtr->room); hardColStepX = stepX; hardColStepZ = stepZ; GereCollision(zvPtr, &zvLocal, &localZv3); stepX = hardColStepX; stepZ = hardColStepZ; } else { hardColStepX = stepX; hardColStepZ = stepZ; GereCollision(zvPtr, &zvLocal, touchedZv); // manage as hard collision stepX = hardColStepX; stepZ = hardColStepZ; } } } else // push succeed { if(actorTouchedPtr->objectType & AF_BOXIFY) { removeFromBGIncrust(collisionIndex); } actorTouchedPtr->objectType |= AF_ANIMATED; actorTouchedPtr->worldX += stepX; // apply push to object actorTouchedPtr->worldZ += stepZ; actorTouchedPtr->roomX += stepX; actorTouchedPtr->roomZ += stepZ; CopyZV(&localZv2,touchedZv); } } else { // can't be pushed if(currentProcessedActorPtr->dynFlags & 1) { if(stepX || stepZ) // if moving { if(actorTouchedPtr->room == currentProcessedActorPtr->room) // same room -> easy case { hardColStepX = stepX; hardColStepZ = stepZ; GereCollision(zvPtr, &zvLocal, touchedZv); // manage as hard collision stepX = hardColStepX; stepZ = hardColStepZ; } else // different room { ZVStruct localZv3; CopyZV(touchedZv, &localZv3); AdjustZV(&localZv3, actorTouchedPtr->room, currentProcessedActorPtr->room); hardColStepX = stepX; hardColStepZ = stepZ; GereCollision(zvPtr, &zvLocal, &localZv3); // manage as hard collision stepX = hardColStepX; stepZ = hardColStepZ; } } } } } } // end of actor/actor collision currentProcessedActorPtr->stepX = stepX + oldStepX; currentProcessedActorPtr->stepY = stepY + oldStepY; currentProcessedActorPtr->stepZ = stepZ + oldStepZ; currentProcessedActorPtr->zv.ZVX1 += stepX; currentProcessedActorPtr->zv.ZVX2 += stepX; currentProcessedActorPtr->zv.ZVY1 += stepY; currentProcessedActorPtr->zv.ZVY2 += stepY; currentProcessedActorPtr->zv.ZVZ1 += stepZ; currentProcessedActorPtr->zv.ZVZ2 += stepZ; } // end of movement management if(!currentProcessedActorPtr->YHandler.numSteps) { // fall management ? currentProcessedActorPtr->worldY += currentProcessedActorPtr->stepY; currentProcessedActorPtr->roomY += currentProcessedActorPtr->stepY; currentProcessedActorPtr->stepY = 0; if(currentProcessedActorPtr->objectType & AF_FALLABLE) { zvPtr = ¤tProcessedActorPtr->zv; CopyZV(zvPtr, &zvLocal); zvLocal.ZVY2 += 100; if(currentProcessedActorPtr->roomY < -10 && !AsmCheckListCol(&zvLocal,&roomDataTable[currentProcessedActorPtr->room]) && !manageFall(currentProcessedActorIdx,&zvLocal)) { InitRealValue(0, 2000, 40, ¤tProcessedActorPtr->YHandler); } else { currentProcessedActorPtr->falling = 0; } } } else { if((currentProcessedActorPtr->YHandler.numSteps != -1) && (currentProcessedActorPtr->objectType & AF_FALLABLE)) { currentProcessedActorPtr->falling = 1; } } for(int i=0; i<3; i++) { int collisionIndex = localTable[i]; if(collisionIndex != -1) { tObject* actorTouchedPtr = &ListObjets[collisionIndex]; if(actorTouchedPtr->objectType & AF_MOVABLE) { int i; for(i=0;i<3;i++) { if(currentProcessedActorPtr->COL[i] == collisionIndex) break; } if(i == 3) { actorTouchedPtr->objectType &= ~AF_ANIMATED; addActorToBgInscrust(collisionIndex); } } } } if(currentProcessedActorPtr->END_FRAME) // key frame change { currentProcessedActorPtr->frame++; if(currentProcessedActorPtr->frame >= currentProcessedActorPtr->numOfFrames) // end of anim ? { currentProcessedActorPtr->flagEndAnim = 1; // end of anim currentProcessedActorPtr->frame = 0; // restart anim if(!(currentProcessedActorPtr->animType & 1) && (currentProcessedActorPtr->newAnim == -1)) // is another anim waiting ? { currentProcessedActorPtr->animType &= 0xFFFD; InitAnim(currentProcessedActorPtr->animInfo, 1, -1); } } currentProcessedActorPtr->worldX += currentProcessedActorPtr->stepX; currentProcessedActorPtr->roomX += currentProcessedActorPtr->stepX; currentProcessedActorPtr->worldZ += currentProcessedActorPtr->stepZ; currentProcessedActorPtr->roomZ += currentProcessedActorPtr->stepZ; currentProcessedActorPtr->stepX = 0; currentProcessedActorPtr->stepZ = 0; currentProcessedActorPtr->animNegX = 0; currentProcessedActorPtr->animNegY = 0; currentProcessedActorPtr->animNegZ = 0; } else // not the end of anim { if((currentProcessedActorPtr->ANIM == -1) && (currentProcessedActorPtr->speed != 0) && (currentProcessedActorPtr->speedChange.numSteps == 0)) { currentProcessedActorPtr->worldX += currentProcessedActorPtr->stepX; currentProcessedActorPtr->roomX += currentProcessedActorPtr->stepX; currentProcessedActorPtr->worldZ += currentProcessedActorPtr->stepZ; currentProcessedActorPtr->roomZ += currentProcessedActorPtr->stepZ; currentProcessedActorPtr->stepX = 0; currentProcessedActorPtr->stepZ = 0; InitRealValue(0,currentProcessedActorPtr->speed,60,¤tProcessedActorPtr->speedChange); } currentProcessedActorPtr->flagEndAnim = 0; } } void StockInterAnim(sFrame& buffer, sBody* bodyPtr) { if(bodyPtr->m_flags & INFO_ANIM) { *(u16*)(bodyPtr->m_scratchBuffer.data()+4) = (u16)timer; bodyPtr->startAnim = &buffer; buffer.m_groups.resize(bodyPtr->m_groups.size()); for (int i = 0; i < bodyPtr->m_groups.size(); i++) { buffer.m_groups[i] = bodyPtr->m_groups[i].m_state; } } } void ResetStartAnim(sBody* bodyPtr) { *(u16*)(bodyPtr->m_scratchBuffer.data() + 4) = (u16)timer; // reset timer bodyPtr->startAnim = nullptr; } s16 GetNbFramesAnim(sAnimation* animPtr) { return animPtr->m_numFrames; } s16 PatchType(sGroupState* bodyPtr, u16 type) // local { bodyPtr->m_type = type; return(bodyPtr->m_type); } void PatchInterAngle(s16* value, s16 previousValue, s16 nextValue, int bp, int bx) // local { s16 diff = nextValue - previousValue; if(diff == 0) { *value = nextValue; } else { if(diff <= 0x200) { if(diff >= -0x200) { *value = ((diff*bp)/bx) + previousValue; } else { nextValue += 0x400; nextValue -= previousValue; *value = ((nextValue *bp)/bx) + previousValue; } } else { previousValue += 0x400; nextValue -= previousValue; *value = ((nextValue *bp)/bx) + previousValue; } } } void PatchInterStep(s16* value, s16 previousValue, s16 nextValue, int bp, int bx) // local { s16 cx = previousValue; s16 ax = nextValue; if(ax == cx) { *value = ax; } else { *value = (((ax - cx)*bp)/bx) + cx; } } s16 SetInterAnimObjet(int frame, sAnimation* pAnim, sBody* pBody) { int numOfBonesInAnim = pAnim->m_numGroups; u16 keyframeLength; u16 timeOfKeyframeStart; int ax; u16 bx; u16 time; int bp; int flag; flag = pBody->m_flags; sFrame* pKeyframe = &pAnim->m_frames[frame]; keyframeLength = pKeyframe->m_timestamp; // keyframe length if(!(pBody->m_flags & INFO_ANIM)) // do not anim if the model can't be animated { return(0); } timeOfKeyframeStart = *(u16*)(pBody->m_scratchBuffer.data() + 4); // time of start of keyframe sFrame* pPreviousKeyframe = pBody->startAnim; if(!pPreviousKeyframe) { pPreviousKeyframe = pKeyframe; } if(numOfBonesInAnim > pBody->m_groupOrder.size()) { numOfBonesInAnim = pBody->m_groupOrder.size(); } time = (u16)timer - timeOfKeyframeStart; bx = keyframeLength; bp = time; if(timem_groups[i].m_state.m_delta; point3dStruct& previousState = pPreviousKeyframe->m_groups[i].m_delta; point3dStruct& nextState = pKeyframe->m_groups[i].m_delta; switch(PatchType(&pBody->m_groups[i].m_state, pKeyframe->m_groups[i].m_type)) { case 0: // rotate PatchInterAngle(&state.x, previousState.x, nextState.x, bp, bx); PatchInterAngle(&state.y, previousState.y, nextState.y, bp, bx); PatchInterAngle(&state.z, previousState.z, nextState.z, bp, bx); break; case 1: // translate case 2: // zoom PatchInterStep(&state.x, previousState.x, nextState.x, bp, bx); PatchInterStep(&state.y, previousState.y, nextState.y, bp, bx); PatchInterStep(&state.z, previousState.z, nextState.z, bp, bx); break; } } } else { for (int i = 0; i < numOfBonesInAnim; i++) { point3dStruct& state = pBody->m_groups[i].m_state.m_delta; point3dStruct& previousState = pPreviousKeyframe->m_groups[i].m_delta; point3dStruct& nextState = pKeyframe->m_groups[i].m_delta; switch (PatchType(&pBody->m_groups[i].m_state, pKeyframe->m_groups[i].m_type)) { case 0: break; case 1: case 2: PatchInterStep(&state.x, previousState.x, nextState.x, bp, bx); PatchInterStep(&state.y, previousState.y, nextState.y, bp, bx); PatchInterStep(&state.z, previousState.z, nextState.z, bp, bx); break; } { point3dStruct& state = pBody->m_groups[i].m_state.m_rotateDelta.value(); point3dStruct& previousState = pPreviousKeyframe->m_groups[i].m_rotateDelta.value(); point3dStruct& nextState = pKeyframe->m_groups[i].m_rotateDelta.value(); PatchInterAngle(&state.x, previousState.x, nextState.x, bp, bx); PatchInterAngle(&state.y, previousState.y, nextState.y, bp, bx); PatchInterAngle(&state.z, previousState.z, nextState.z, bp, bx); } } } animStepX = (pKeyframe->m_animStep.x * bp) / bx; animStepY = (pKeyframe->m_animStep.y * bp) / bx; animStepZ = (pKeyframe->m_animStep.z * bp) / bx; animCurrentTime = bx; animKeyframeLength = bp; return(0); } else // change keyframe { for (int i = 0; i < numOfBonesInAnim; i++) { sGroupState& nextState = pKeyframe->m_groups[i]; pBody->m_groups[i].m_state = nextState; }; pBody->startAnim = pKeyframe; *(u16*)(pBody->m_scratchBuffer.data()+4) = (u16)timer; animCurrentTime = bx; animKeyframeLength = bx; animStepX = pKeyframe->m_animStep.x; animStepY = pKeyframe->m_animStep.y; animStepZ = pKeyframe->m_animStep.z; return(1); } }