RE: [Algorithms] action changing on character
Brought to you by:
vexxed72
From: Chris K. <ck...@ir...> - 2002-04-23 17:08:57
|
jason zhang writes: > There is usually actions being done when you want the character > to do another one. What is your method to change from one action > to another smoothly? It sounds like what you are trying to do is to have an automatic way of blending between any number of existing animations and a new animation. For example, if the character activates a waving animation and then activates a idle animation, the waving animation will smoothly fade itself in to the idle animation over a period of time. I've worked on this problem before and managed to get a pretty decent general solution working, which I'll describe below. A few things about the system in which this was implemented: - The code was used in a CG puppeteering application that allowed puppeteers to load in animation clips and trigger them in real-time. The triggers were stored in tracks (like a MIDI sequencer), and the blending code allowed the puppeteer to iteratively layer new tracks on top of old ones without generating large discontinuities in the character's motion. - The world contained characters, and characters were updated once per system 'tick' - Each property on the character was accessible through what we called a DOF (degree of freedom). DOFs are used as a generic interface to an underlying resource. For example, you could have a generic TransformDOF which provides an interface to the transformation matrix on a particular node in a 3d character, or a PositionDOF to control just the translational component of the root node in the character. DOFs can be modified or read repeatedly in an update cycle, but only modify the underlying resource (e.g., push the xform down the graphics pipe) when they are updated at the end of a character update. - Each character had one or more skill layers, which were updated in order when the character was updated. - When a skill layer was updated, the skills in the layer were updated in the order in which they were added to the layer. - A skill is basically a generic interface to a block of code. Skills can be activated, updated, deactivated, suspended, or resumed. Examples of skills could include playing animations or performing inverse kinematics. Skills modify the state of a character by changing the value of various DOFs (degrees of freedom). - Skills had no knowledge of which DOFs they modified. This meant that I had to keep around a lot more state that I would otherwise, and the system did not allow skills to put priorities or locks on certain DOFs. To do the blending I made a special kind of skill layer called a blending skill layer. This skill layer was a little different from the generic skill layer in a few ways: 1) Instead of activating a skill immediately when the skill has activate() called on it, the skill is instead queued for activation and then activated at the beginning of the blending skill layer's next update cycle. 2) Whenever a skill is activated it is moved to be the last skill in the layer. 3) After each skill in the layer is updated, computation is performed on the DOFs in the character to blend the values set by the updated skill with the values set by the previous skill (over time, the previous skill's values are faded out and the new skills values are faded in). The way that skills were blended depended on two factors. First, the length of time over which the blending occurs could be set individually for each skill. Second, a fade-in function (curve) could be set for each skill that defines the percentage of that skill's value that is used at each parametric time during the fade-in period. For example, assume that the fade-in period was set to 2 seconds and the fade-in function was set to a linear function with a value of 0 at parametric time 0 and a value of 1 at parametric time 1. In this case the skill would have no effect when first activated, 50% faded in after an elapsed time of 1 second, and fully faded in after 2 seconds have elapsed. These fade-in functions didn't have to be linear; they could be sigmoid (ease-in, ease-out) curves, square waves, piecewise linear, etc. The only restriction was that the input domain and output range were both [0,1]. The blending skill layer contained the following state: exempt skill: A pointer to the one skill (if any) in the layer that is exempt from blending because it was the first skill to activate after a period during which there were no active skills in the layer. If null, no skill is exempt. activation queue: Queue of skills that need to be activated when the skill layer is next updated oldInitialValues: An array of DOF values that the first active skill in the layer will blend into. This is necessary because, for example, the second skill might be blending into the first skill but the first skill deactivates before the second skill finishes blending. In this case the first skill will no longer be setting values in the DOFs but we still need the last values that it set so that the second skill can continue to blend into those values. The values in this array are supposed to be a one-to-one match for the values of the DOFs in the character. Sometimes skills add or remove DOFs from a character when they are updated, so when and when you code up this solution you should check for this and update the array appropriately. dofValsBeforeUpdate: Array containing value of the character's DOFs before each skill in the layer is updated. fadeInPeriod, fadeOutPeriod: The amount of time each skill takes to completely fade-in/fade-out its changes to DOFs after being activated/deactivated. One value each per skill in the layer. fadeOutPeriod: Holds the amount of time each skill takes to completely fade OUT its changes to DOFs after being deactivated. One value per skill in the layer. activation time, deactivation time: The time at which each skill was activated and deactivated. One value of each per skill in the layer. blendStatusAtActivation: Indicated whether or not a skill was blending its values during the update cycle in which it was activated. Generally, a skill needs to fade in after activation only if there are other skills before it in the layer that are already running. If it's the only skill in the layer, then it doesn't need to fade in. One value per skill in the layer. fadeInFunction, fadeOutFunction: The functions that control how a skill's values are blended over time. The functions must have a domain of [0, 1] and a range within [0, 1]. For example, if the skill should blend in a linearly increasing fashion, the function for that skill should have a value of 0 at input 0 and a value of 1 at input 1. One value for each per skill in the layer. dofValsAfterSkillUpdated: Hash table containing one array for each skill. The array contains the value of the character's dofs after its associated skill updated. The reason we need this is to maintain smooth fading-in in the event that skill #1 deactivates before skill #2 finishes fading in (skill #2 needs to remember the "destination" of its fading operations). One array per skill in the layer. In very rough pseudo-code, the main update loop for the blending skill layer did the following: void update() { // ------------------------------------------------------------------- // Activate any skills that are queued for activation // ------------------------------------------------------------------- for (each queued skills) { tell the skill that is it has been activated } // ------------------------------------------------------------------- // Update skills in order. After updating each skill, blend (if necessary) // the pre-update value of the dofs with the post-update value. // ------------------------------------------------------------------- // Don't loop through layers in which none of the skills are active. if (no active skills in the layer) { return; } // Get the initial value of the dofs, before updating any skills COPY current DOF values INTO dofValsBeforeUpdate // Make sure our internal data structures are consistent with each other if (oldInitialValues.size() != dofValsBeforeUpdate.size()) { // Someone added or removed a dof outside the skill layer update // cycle, or it's the first time this layer has been updated. To // compensate: copy dofValsBeforeUpdate into oldInitialValues copy dofValsBeforeUpdate into each entry in dofValsAfterSkillUpdated[N] // Any blending that was in progress will not be smooth for // this update, but the skill layer might be able to keep // functioning. } // If no skills were active after the last skill layer update, then no // skills are blending. if (no skills were active after last update) { copy dofValsBeforeUpdate into oldInitialValues } // Update the skills in order. for (each of the N skills in the layer) { // First check if the skill has deactivated since the last layer // update. // // We need to check this here instead of after updating the skill // because it is possible that the skill was deactivated in between // updates of the skill layer (e.g., by some external process rather // than the skill deactivating itself). if (skill deactivated between last layer update and now) { set the skill's deactivation time entry to current time } // Update the skill and possibly perform blending if (skills[N] is updating) { //---------------------------------------------------------- // POSSIBLY FADE IN //---------------------------------------------------------- // update the skill and let it modify DOFs on the character UPDATE skills[N] // Get the percentage (based on the fade-in time and the fade-in // function) by which to weight the current DOF values (the values // of the DOFs after the skill was updated) versus before the // skill was updated dtSinceActivation = currentTime-activationTime; percentOfCurrentDofVals = getWeighting(dtSinceActivation, fadeInFunction[N]); // handle special case if (this is not the first skill in the layer that is updating) { if (there were no active skills after the last layer update) { // this is the first skill that is active after a // period in which no skills were updating in the // layer. Therefore this skill is exempt from blending // for the duration of the period that it is the first // active skill in the layer exemptSkill = skills[N]; if (activation time of skills[N] == currentTime) { // skill is NOT blending at the time of its activation blendStatusAtActivation[N] = false; } } // There are two cases in which the skill should NOT blend // its changes to DOF values. These cases are: // // 1. The skill is exempt from blending because it was the // first skill to activate after a period during which // the layer had no active skills // // 2. The skill is the first one in the layer AND it was // not blending when it first became active (this often // happens when a skill gets moved to the first // position because the skills that were previously // first have activated and therefore moved to the last // position in the layer) if ((this skill is the exempt skill) OR (this is the first skill in the layer AND blendStatusAtActivation[N] == false)) { COPY current DOF values INTO dofValsBeforeUpdate if (activation time of skills[N] == currentTime) { // skill is NOT blending at the time of its activation blendStatusAtActivation[N] = false; } } else // special-case blend { // We blend the current dof values with the last values // set by the skill that used to be the first updating // skill. The reason for this is to make sure that blends // continue smoothly even if skills are re-ordered during // the blend. BLEND oldInitialValues INTO current DOF values with the current dof values WEIGHTED BY percentOfCurrentDofVals if (activation time of skills[N] == currentTime) { // skill IS blending at the time of its activation blendStatusAtActivation[N] = true; } } } else // blend with values set by previous skills in layer { // -------------------------------------------------------- // Blend the DOF values that were set when skill N updated // with the DOF values that were in place before it // updated. The blend weighting is based on whether or not // the skill is fading in or out // -------------------------------------------------------- BLEND dofValsBeforeUpdate INTO current DOF values with the current dof values WEIGHTED BY percentOfCurrentDofVals // Cache the blended values so that we can re-use them when // blending the next skill without having to re-query the dofs COPY current DOF values INTO dofValsBeforeUpdate if (activation time of skills[N] == currentTime) { // skill IS blending at the time of its activation blendStatusAtActivation[N] = true; } // ------------------------------------------------------------------- // End of dof blending // ------------------------------------------------------------------- } // end blending operations // Check if skill deactivated itself during the update if (skill[N] deactivated itself during the update) { if (this skill is first skill in layer that is updating) { // Since this was the first updating skill in the // layer and it deactivated itself during the update, // then update the cache of oldInitialValues COPY current DOF values INTO oldInitialValues } } // Cache the values of each DOF after this skill ran, because // we'll need them if we have to do fade-out operations. COPY current DOF values INTO dofsAtTimeOfSkillDeactivation[N] } // end if (skill is updating) else // skill not updating; check if we need to fade it out { //---------------------------------------------------------- // POSSIBLY FADE OUT //---------------------------------------------------------- dtSinceDeactivation = currentTime-deactivationTime; // Make sure the skill is still fading out if (dtSinceDeactivation <= fadeOutPeriod[N]) { // Get the percentage (based on the fade-out time and the fade-out // function) by which to weight the current DOF values (the values // of the DOFs after the skill was updated) versus before the // skill was updated percentOfCurrentDofVals = getWeighting(deactivateDt, fadeOutFunction[N]); // Blend the state of the DOFs at the time the skill deactivated // with the current dof values, so it fades out nicely BLEND dofsAtTimeOfSkillDeactivation[N] INTO current DOF values with the current dof values WEIGHTED BY percentOfCurrentDofVals } } } // end loop over all N skills } // end update Of course, there's a few more state-management details involved to get everything working smoothly. The code isn't as simple as it might have been if skills could have been queried for the set of DOFs that they modified, or if I hadn't had to retrofit it into the existing character-layer-skill architecture, but in the end it worked out nicely and was very robust. Hope that helps! -chris -----Original Message----- From: gda...@li... [mailto:gda...@li...]On Behalf Of jason zhang Sent: Friday, April 19, 2002 10:56 PM To: gda...@li... Subject: [Algorithms] action changing on character There is usually actions being done when you want the character to do another one. What is your method to change from one action to another smoothly? I don't know which I should choose from the following two ways. 1. Each action has an affecting value. The character's pose is computed from blending the poses from all current actions modulated by affecting value. A new action's value will increase from 0 to 1 after it is triggered and the value will decrease to 0 for other current actions. Then we'll get the smooth transition during new action starting. 2. The current pose is computed when a new action is started. Afterwards, the character's pose is interpolated between the computed pose and the pose from the new action. Sorry for my weak English. But any advice? Jason Z. 二逗~z~z亥? |