/** * @file llmotioncontroller.cpp * @brief Implementation of LLMotionController class. * * Copyright (c) 2001-2007, Linden Research, Inc. * * The source code in this file ("Source Code") is provided by Linden Lab * to you under the terms of the GNU General Public License, version 2.0 * ("GPL"), unless you have obtained a separate licensing agreement * ("Other License"), formally executed by you and Linden Lab. Terms of * the GPL can be found in doc/GPL-license.txt in this distribution, or * online at http://secondlife.com/developers/opensource/gplv2 * * There are special exceptions to the terms and conditions of the GPL as * it is applied to this Source Code. View the full text of the exception * in the file doc/FLOSS-exception.txt in this software distribution, or * online at http://secondlife.com/developers/opensource/flossexception * * By copying, modifying or distributing this software, you acknowledge * that you have read and understood your obligations described above, * and agree to abide by those obligations. * * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, * COMPLETENESS OR PERFORMANCE. */ //----------------------------------------------------------------------------- // Header Files //----------------------------------------------------------------------------- #include "linden_common.h" #include "llmotioncontroller.h" #include "llkeyframemotion.h" #include "llmath.h" #include "lltimer.h" #include "llanimationstates.h" #include "llstl.h" const S32 NUM_JOINT_SIGNATURE_STRIDES = LL_CHARACTER_MAX_JOINTS / 4; const U32 MAX_MOTION_INSTANCES = 32; //----------------------------------------------------------------------------- // Constants and statics //----------------------------------------------------------------------------- LLMotionRegistry LLMotionController::sRegistry; //----------------------------------------------------------------------------- // LLMotionTableEntry() //----------------------------------------------------------------------------- LLMotionTableEntry::LLMotionTableEntry() { mConstructor = NULL; mID.setNull(); } LLMotionTableEntry::LLMotionTableEntry(LLMotionConstructor constructor, const LLUUID& id) : mConstructor(constructor), mID(id) { } //----------------------------------------------------------------------------- // create() //----------------------------------------------------------------------------- LLMotion* LLMotionTableEntry::create(const LLUUID &id) { LLMotion* motionp = mConstructor(id); return motionp; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // LLMotionRegistry class //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // LLMotionRegistry() // Class Constructor //----------------------------------------------------------------------------- LLMotionRegistry::LLMotionRegistry() : mMotionTable(LLMotionTableEntry::uuidEq, LLMotionTableEntry()) { } //----------------------------------------------------------------------------- // ~LLMotionRegistry() // Class Destructor //----------------------------------------------------------------------------- LLMotionRegistry::~LLMotionRegistry() { mMotionTable.removeAll(); } //----------------------------------------------------------------------------- // addMotion() //----------------------------------------------------------------------------- BOOL LLMotionRegistry::addMotion( const LLUUID& id, LLMotionConstructor constructor ) { // llinfos << "Registering motion: " << name << llendl; if (!mMotionTable.check(id)) { mMotionTable.set(id, LLMotionTableEntry(constructor, id)); return TRUE; } return FALSE; } //----------------------------------------------------------------------------- // markBad() //----------------------------------------------------------------------------- void LLMotionRegistry::markBad( const LLUUID& id ) { mMotionTable.set(id, LLMotionTableEntry()); } //----------------------------------------------------------------------------- // createMotion() //----------------------------------------------------------------------------- LLMotion *LLMotionRegistry::createMotion( const LLUUID &id ) { LLMotionTableEntry motion_entry = mMotionTable.get(id); LLMotion* motion = NULL; if ( motion_entry.getID().isNull() ) { // *FIX: need to replace with a better default scheme. RN motion = LLKeyframeMotion::create(id); } else { motion = motion_entry.create(id); } return motion; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // LLMotionController class //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // LLMotionController() // Class Constructor //----------------------------------------------------------------------------- LLMotionController::LLMotionController( ) { mTime = 0.f; mTimeOffset = 0.f; mLastTime = 0.0f; mHasRunOnce = FALSE; mPaused = FALSE; mPauseTime = 0.f; mTimeStep = 0.f; mTimeStepCount = 0; mLastInterp = 0.f; mTimeFactor = 1.f; } //----------------------------------------------------------------------------- // ~LLMotionController() // Class Destructor //----------------------------------------------------------------------------- LLMotionController::~LLMotionController() { deleteAllMotions(); } //----------------------------------------------------------------------------- // deleteAllMotions() //----------------------------------------------------------------------------- void LLMotionController::deleteAllMotions() { mLoadingMotions.removeAllNodes(); mLoadedMotions.clear(); mActiveMotions.removeAllNodes(); for_each(mAllMotions.begin(), mAllMotions.end(), DeletePairedPointer()); mAllMotions.clear(); } //----------------------------------------------------------------------------- // addLoadedMotion() //----------------------------------------------------------------------------- void LLMotionController::addLoadedMotion(LLMotion* motionp) { if (mLoadedMotions.size() > MAX_MOTION_INSTANCES) { // too many motions active this frame, kill all blenders mPoseBlender.clearBlenders(); for (U32 i = 0; i < mLoadedMotions.size(); i++) { LLMotion* cur_motionp = mLoadedMotions.front(); mLoadedMotions.pop_front(); // motion isn't playing, delete it if (!isMotionActive(cur_motionp)) { mCharacter->requestStopMotion(cur_motionp); mAllMotions.erase(cur_motionp->getID()); delete cur_motionp; if (mLoadedMotions.size() <= MAX_MOTION_INSTANCES) { break; } } else { // put active motion on back mLoadedMotions.push_back(cur_motionp); } } } mLoadedMotions.push_back(motionp); } //----------------------------------------------------------------------------- // setTimeStep() //----------------------------------------------------------------------------- void LLMotionController::setTimeStep(F32 step) { mTimeStep = step; if (step != 0.f) { // make sure timestamps conform to new quantum for( LLMotion* motionp = mActiveMotions.getFirstData(); motionp != NULL; motionp = mActiveMotions.getNextData() ) { motionp->mActivationTimestamp = (F32)llfloor(motionp->mActivationTimestamp / step) * step; BOOL stopped = motionp->isStopped(); motionp->setStopTime((F32)llfloor(motionp->getStopTime() / step) * step); motionp->setStopped(stopped); motionp->mSendStopTimestamp = (F32)llfloor(motionp->mSendStopTimestamp / step) * step; } } } //----------------------------------------------------------------------------- // setTimeFactor() //----------------------------------------------------------------------------- void LLMotionController::setTimeFactor(F32 time_factor) { mTimeOffset += mTimer.getElapsedTimeAndResetF32() * mTimeFactor; mTimeFactor = time_factor; } //----------------------------------------------------------------------------- // getFirstActiveMotion() //----------------------------------------------------------------------------- LLMotion* LLMotionController::getFirstActiveMotion() { return mActiveMotions.getFirstData(); } //----------------------------------------------------------------------------- // getNextActiveMotion() //----------------------------------------------------------------------------- LLMotion* LLMotionController::getNextActiveMotion() { return mActiveMotions.getNextData(); } //----------------------------------------------------------------------------- // setCharacter() //----------------------------------------------------------------------------- void LLMotionController::setCharacter(LLCharacter *character) { mCharacter = character; } //----------------------------------------------------------------------------- // addMotion() //----------------------------------------------------------------------------- BOOL LLMotionController::addMotion( const LLUUID& id, LLMotionConstructor constructor ) { return sRegistry.addMotion(id, constructor); } //----------------------------------------------------------------------------- // removeMotion() //----------------------------------------------------------------------------- void LLMotionController::removeMotion( const LLUUID& id) { LLMotion* motionp = findMotion(id); if (motionp) { stopMotionLocally(id, TRUE); mLoadingMotions.deleteData(motionp); std::deque::iterator motion_it; for (motion_it = mLoadedMotions.begin(); motion_it != mLoadedMotions.end(); ++motion_it) { if(*motion_it == motionp) { mLoadedMotions.erase(motion_it); break; } } mActiveMotions.deleteData(motionp); mAllMotions.erase(id); delete motionp; } } //----------------------------------------------------------------------------- // createMotion() //----------------------------------------------------------------------------- LLMotion* LLMotionController::createMotion( const LLUUID &id ) { // do we have an instance of this motion for this character? LLMotion *motion = findMotion(id); // if not, we need to create one if (!motion) { // look up constructor and create it motion = sRegistry.createMotion(id); if (!motion) { return NULL; } // look up name for default motions const char* motion_name = gAnimLibrary.animStateToString(id); if (motion_name) { motion->setName(motion_name); } // initialize the new instance LLMotion::LLMotionInitStatus stat = motion->onInitialize(mCharacter); switch(stat) { case LLMotion::STATUS_FAILURE: llinfos << "Motion " << id << " init failed." << llendl; sRegistry.markBad(id); delete motion; return NULL; case LLMotion::STATUS_HOLD: mLoadingMotions.addData(motion); break; case LLMotion::STATUS_SUCCESS: // add motion to our list addLoadedMotion(motion); break; default: llerrs << "Invalid initialization status" << llendl; break; } mAllMotions[id] = motion; } return motion; } //----------------------------------------------------------------------------- // startMotion() //----------------------------------------------------------------------------- BOOL LLMotionController::startMotion(const LLUUID &id, F32 start_offset) { // look for motion in our list of created motions LLMotion *motion = createMotion(id); if (!motion) { return FALSE; } //if the motion is already active, then we're done else if (isMotionActive(motion)) // motion is playing and... { if (motion->isStopped()) // motion has been stopped { deactivateMotion(motion); } else if (mTime < motion->mSendStopTimestamp) // motion is still active { return TRUE; } } // llinfos << "Starting motion " << name << llendl; return activateMotion(motion, mTime - start_offset); } //----------------------------------------------------------------------------- // stopMotionLocally() //----------------------------------------------------------------------------- BOOL LLMotionController::stopMotionLocally(const LLUUID &id, BOOL stop_immediate) { // if already inactive, return false LLMotion *motion = findMotion(id); if (!motion) { return FALSE; } // If on active list, stop it if (isMotionActive(motion) && !motion->isStopped()) { // when using timesteps, set stop time to last frame's time, otherwise grab current timer value // *FIX: should investigate this inconsistency...hints of obscure bugs F32 stop_time = (mTimeStep != 0.f || mPaused) ? (mTime) : mTimeOffset + (mTimer.getElapsedTimeF32() * mTimeFactor); motion->setStopTime(stop_time); if (stop_immediate) { deactivateMotion(motion); } return TRUE; } else if (isMotionLoading(motion)) { motion->setStopped(TRUE); return TRUE; } return FALSE; } //----------------------------------------------------------------------------- // updateRegularMotions() //----------------------------------------------------------------------------- void LLMotionController::updateRegularMotions() { updateMotionsByType(LLMotion::NORMAL_BLEND); } //----------------------------------------------------------------------------- // updateAdditiveMotions() //----------------------------------------------------------------------------- void LLMotionController::updateAdditiveMotions() { updateMotionsByType(LLMotion::ADDITIVE_BLEND); } //----------------------------------------------------------------------------- // resetJointSignatures() //----------------------------------------------------------------------------- void LLMotionController::resetJointSignatures() { memset(&mJointSignature[0][0], 0, sizeof(U8) * LL_CHARACTER_MAX_JOINTS); memset(&mJointSignature[1][0], 0, sizeof(U8) * LL_CHARACTER_MAX_JOINTS); } //----------------------------------------------------------------------------- // updateMotionsByType() //----------------------------------------------------------------------------- void LLMotionController::updateMotionsByType(LLMotion::LLMotionBlendType anim_type) { BOOL update_result = TRUE; U8 last_joint_signature[LL_CHARACTER_MAX_JOINTS]; memset(&last_joint_signature, 0, sizeof(U8) * LL_CHARACTER_MAX_JOINTS); // iterate through active motions in chronological order for(LLMotion* motionp = mActiveMotions.getFirstData(); motionp != NULL; motionp = mActiveMotions.getNextData()) { if (motionp->getBlendType() != anim_type) { continue; } BOOL update_motion = FALSE; if (motionp->getPose()->getWeight() < 1.f) { update_motion = TRUE; } else { S32 i; // NUM_JOINT_SIGNATURE_STRIDES should be multiple of 4 for (i = 0; i < NUM_JOINT_SIGNATURE_STRIDES; i++) { U32 *current_signature = (U32*)&(mJointSignature[0][i * 4]); U32 test_signature = *(U32*)&(motionp->mJointSignature[0][i * 4]); if ((*current_signature | test_signature) > (*current_signature)) { *current_signature |= test_signature; update_motion = TRUE; } *((U32*)&last_joint_signature[i * 4]) = *(U32*)&(mJointSignature[1][i * 4]); current_signature = (U32*)&(mJointSignature[1][i * 4]); test_signature = *(U32*)&(motionp->mJointSignature[1][i * 4]); if ((*current_signature | test_signature) > (*current_signature)) { *current_signature |= test_signature; update_motion = TRUE; } } } if (!update_motion) { if (motionp->isStopped() && mTime > motionp->getStopTime() + motionp->getEaseOutDuration()) { deactivateMotion(motionp); } else if (motionp->isStopped() && mTime > motionp->getStopTime()) { // is this the first iteration in the ease out phase? if (mLastTime <= motionp->getStopTime()) { // store residual weight for this motion motionp->mResidualWeight = motionp->getPose()->getWeight(); } } else if (mTime > motionp->mSendStopTimestamp) { // notify character of timed stop event on first iteration past sendstoptimestamp // this will only be called when an animation stops itself (runs out of time) if (mLastTime <= motionp->mSendStopTimestamp) { mCharacter->requestStopMotion( motionp ); stopMotionLocally(motionp->getID(), FALSE); } } else if (mTime >= motionp->mActivationTimestamp) { if (mLastTime < motionp->mActivationTimestamp) { motionp->mResidualWeight = motionp->getPose()->getWeight(); } } continue; } LLPose *posep = motionp->getPose(); // only filter by LOD after running every animation at least once (to prime the avatar state) if (mHasRunOnce && motionp->getMinPixelArea() > mCharacter->getPixelArea()) { motionp->fadeOut(); //should we notify the simulator that this motion should be stopped (check even if skipped by LOD logic) if (mTime > motionp->mSendStopTimestamp) { // notify character of timed stop event on first iteration past sendstoptimestamp // this will only be called when an animation stops itself (runs out of time) if (mLastTime <= motionp->mSendStopTimestamp) { mCharacter->requestStopMotion( motionp ); stopMotionLocally(motionp->getID(), FALSE); } } if (motionp->getFadeWeight() < 0.01f) { if (motionp->isStopped() && mTime > motionp->getStopTime() + motionp->getEaseOutDuration()) { posep->setWeight(0.f); deactivateMotion(motionp); } continue; } } else { motionp->fadeIn(); } //********************** // MOTION INACTIVE //********************** if (motionp->isStopped() && mTime > motionp->getStopTime() + motionp->getEaseOutDuration()) { // this motion has gone on too long, deactivate it // did we have a chance to stop it? if (mLastTime <= motionp->getStopTime()) { // if not, let's stop it this time through and deactivate it the next posep->setWeight(motionp->getFadeWeight()); motionp->onUpdate(motionp->getStopTime() - motionp->mActivationTimestamp, last_joint_signature); } else { posep->setWeight(0.f); deactivateMotion(motionp); continue; } } //********************** // MOTION EASE OUT //********************** else if (motionp->isStopped() && mTime > motionp->getStopTime()) { // is this the first iteration in the ease out phase? if (mLastTime <= motionp->getStopTime()) { // store residual weight for this motion motionp->mResidualWeight = motionp->getPose()->getWeight(); } if (motionp->getEaseOutDuration() == 0.f) { posep->setWeight(0.f); } else { posep->setWeight(motionp->getFadeWeight() * motionp->mResidualWeight * cubic_step(1.f - ((mTime - motionp->getStopTime()) / motionp->getEaseOutDuration()))); } // perform motion update update_result = motionp->onUpdate(mTime - motionp->mActivationTimestamp, last_joint_signature); } //********************** // MOTION ACTIVE //********************** else if (mTime > motionp->mActivationTimestamp + motionp->getEaseInDuration()) { posep->setWeight(motionp->getFadeWeight()); //should we notify the simulator that this motion should be stopped? if (mTime > motionp->mSendStopTimestamp) { // notify character of timed stop event on first iteration past sendstoptimestamp // this will only be called when an animation stops itself (runs out of time) if (mLastTime <= motionp->mSendStopTimestamp) { mCharacter->requestStopMotion( motionp ); stopMotionLocally(motionp->getID(), FALSE); } } // perform motion update update_result = motionp->onUpdate(mTime - motionp->mActivationTimestamp, last_joint_signature); } //********************** // MOTION EASE IN //********************** else if (mTime >= motionp->mActivationTimestamp) { if (mLastTime < motionp->mActivationTimestamp) { motionp->mResidualWeight = motionp->getPose()->getWeight(); } if (motionp->getEaseInDuration() == 0.f) { posep->setWeight(motionp->getFadeWeight()); } else { // perform motion update posep->setWeight(motionp->getFadeWeight() * motionp->mResidualWeight + (1.f - motionp->mResidualWeight) * cubic_step((mTime - motionp->mActivationTimestamp) / motionp->getEaseInDuration())); } // perform motion update update_result = motionp->onUpdate(mTime - motionp->mActivationTimestamp, last_joint_signature); } else { posep->setWeight(0.f); update_result = motionp->onUpdate(0.f, last_joint_signature); } // allow motions to deactivate themselves if (!update_result) { if (!motionp->isStopped() || motionp->getStopTime() > mTime) { // animation has stopped itself due to internal logic // propagate this to the network // as not all viewers are guaranteed to have access to the same logic mCharacter->requestStopMotion( motionp ); stopMotionLocally(motionp->getID(), FALSE); } } // even if onupdate returns FALSE, add this motion in to the blend one last time mPoseBlender.addMotion(motionp); } } //----------------------------------------------------------------------------- // updateMotion() //----------------------------------------------------------------------------- void LLMotionController::updateMotion() { BOOL use_quantum = (mTimeStep != 0.f); // Update timing info for this time step. if (!mPaused) { F32 update_time = mTimeOffset + (mTimer.getElapsedTimeF32() * mTimeFactor); if (use_quantum) { F32 time_interval = fmodf(update_time, mTimeStep); // always animate *ahead* of actual time S32 quantum_count = llmax(0, llfloor((update_time - time_interval) / mTimeStep)) + 1; if (quantum_count == mTimeStepCount) { // we're still in same time quantum as before, so just interpolate and exit if (!mPaused) { F32 interp = time_interval / mTimeStep; mPoseBlender.interpolate(interp - mLastInterp); mLastInterp = interp; } return; } // is calculating a new keyframe pose, make sure the last one gets applied mPoseBlender.interpolate(1.f); mPoseBlender.clearBlenders(); mTimeStepCount = quantum_count; mLastTime = mTime; mTime = (F32)quantum_count * mTimeStep; mLastInterp = 0.f; } else { mLastTime = mTime; mTime = update_time; } } // query pending motions for completion LLMotion* motionp; for ( motionp = mLoadingMotions.getFirstData(); motionp != NULL; motionp = mLoadingMotions.getNextData() ) { LLMotion::LLMotionInitStatus status = motionp->onInitialize(mCharacter); if (status == LLMotion::STATUS_SUCCESS) { mLoadingMotions.removeCurrentData(); // add motion to our loaded motion list addLoadedMotion(motionp); // this motion should be playing if (!motionp->isStopped()) { activateMotion(motionp, mTime); } } else if (status == LLMotion::STATUS_FAILURE) { llinfos << "Motion " << motionp->getID() << " init failed." << llendl; sRegistry.markBad(motionp->getID()); mLoadingMotions.removeCurrentData(); mAllMotions.erase(motionp->getID()); delete motionp; } } resetJointSignatures(); if (!mPaused) { // update additive motions updateAdditiveMotions(); resetJointSignatures(); // update all regular motions updateRegularMotions(); if (use_quantum) { mPoseBlender.blendAndCache(TRUE); } else { mPoseBlender.blendAndApply(); } } mHasRunOnce = TRUE; // llinfos << "Motion controller time " << motionTimer.getElapsedTimeF32() << llendl; } //----------------------------------------------------------------------------- // activateMotion() //----------------------------------------------------------------------------- BOOL LLMotionController::activateMotion(LLMotion *motion, F32 time) { if (mLoadingMotions.checkData(motion)) { // we want to start this motion, but we can't yet, so flag it as started motion->setStopped(FALSE); // report pending animations as activated return TRUE; } motion->mResidualWeight = motion->getPose()->getWeight(); motion->mActivationTimestamp = time; // set stop time based on given duration and ease out time if (motion->getDuration() != 0.f && !motion->getLoop()) { F32 ease_out_time; F32 motion_duration; // should we stop at the end of motion duration, or a bit earlier // to allow it to ease out while moving? ease_out_time = motion->getEaseOutDuration(); // is the clock running when the motion is easing in? // if not (POSTURE_EASE) then we need to wait that much longer before triggering the stop motion_duration = llmax(motion->getDuration() - ease_out_time, 0.f); motion->mSendStopTimestamp = time + motion_duration; } else { motion->mSendStopTimestamp = F32_MAX; } mActiveMotions.addData(motion); motion->activate(); motion->onUpdate(0.f, mJointSignature[1]); return TRUE; } //----------------------------------------------------------------------------- // deactivateMotion() //----------------------------------------------------------------------------- BOOL LLMotionController::deactivateMotion(LLMotion *motion) { motion->deactivate(); mActiveMotions.removeData(motion); return TRUE; } //----------------------------------------------------------------------------- // isMotionActive() //----------------------------------------------------------------------------- BOOL LLMotionController::isMotionActive(LLMotion *motion) { if (motion && motion->isActive()) { return TRUE; } return FALSE; } //----------------------------------------------------------------------------- // isMotionLoading() //----------------------------------------------------------------------------- BOOL LLMotionController::isMotionLoading(LLMotion* motion) { return mLoadingMotions.checkData(motion); } //----------------------------------------------------------------------------- // findMotion() //----------------------------------------------------------------------------- LLMotion *LLMotionController::findMotion(const LLUUID& id) { return mAllMotions[id]; } //----------------------------------------------------------------------------- // flushAllMotions() //----------------------------------------------------------------------------- void LLMotionController::flushAllMotions() { LLDynamicArray active_motions; LLDynamicArray active_motion_times; for (LLMotion* motionp = mActiveMotions.getFirstData(); motionp; motionp = mActiveMotions.getNextData()) { active_motions.put(motionp->getID()); active_motion_times.put(mTime - motionp->mActivationTimestamp); motionp->deactivate(); } // delete all motion instances deleteAllMotions(); // kill current hand pose that was previously called out by // keyframe motion mCharacter->removeAnimationData("Hand Pose"); // restart motions for (S32 i = 0; i < active_motions.count(); i++) { startMotion(active_motions[i], active_motion_times[i]); } } //----------------------------------------------------------------------------- // pause() //----------------------------------------------------------------------------- void LLMotionController::pause() { if (!mPaused) { //llinfos << "Pausing animations..." << llendl; mPauseTime = mTimer.getElapsedTimeF32(); mPaused = TRUE; } } //----------------------------------------------------------------------------- // unpause() //----------------------------------------------------------------------------- void LLMotionController::unpause() { if (mPaused) { //llinfos << "Unpausing animations..." << llendl; mTimer.reset(); mTimer.setAge(mPauseTime); mPaused = FALSE; } } // End