diff options
Diffstat (limited to 'indra/llcharacter')
43 files changed, 14437 insertions, 0 deletions
diff --git a/indra/llcharacter/llanimationstates.cpp b/indra/llcharacter/llanimationstates.cpp new file mode 100644 index 0000000000..58ba252e04 --- /dev/null +++ b/indra/llcharacter/llanimationstates.cpp @@ -0,0 +1,319 @@ +/** + * @file llanimationstates.cpp + * @brief Implementation of animation state related functions. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +//----------------------------------------------------------------------------- +// Agent Animation State +//----------------------------------------------------------------------------- + +#include "linden_common.h" + +#include <string.h> + +#include "llanimationstates.h" +#include "llstring.h" + +LLUUID AGENT_WALK_ANIMS[] = {ANIM_AGENT_WALK, ANIM_AGENT_RUN, ANIM_AGENT_CROUCHWALK, ANIM_AGENT_TURNLEFT, ANIM_AGENT_TURNRIGHT}; +S32 NUM_AGENT_WALK_ANIMS = sizeof(AGENT_WALK_ANIMS) / sizeof(LLUUID); + +LLUUID AGENT_GUN_HOLD_ANIMS[] = {ANIM_AGENT_HOLD_RIFLE_R, ANIM_AGENT_HOLD_HANDGUN_R, ANIM_AGENT_HOLD_BAZOOKA_R, ANIM_AGENT_HOLD_BOW_L}; +S32 NUM_AGENT_GUN_HOLD_ANIMS = sizeof(AGENT_GUN_HOLD_ANIMS) / sizeof(LLUUID); + +LLUUID AGENT_GUN_AIM_ANIMS[] = {ANIM_AGENT_AIM_RIFLE_R, ANIM_AGENT_AIM_HANDGUN_R, ANIM_AGENT_AIM_BAZOOKA_R, ANIM_AGENT_AIM_BOW_L}; +S32 NUM_AGENT_GUN_AIM_ANIMS = sizeof(AGENT_GUN_AIM_ANIMS) / sizeof(LLUUID); + +LLUUID AGENT_NO_ROTATE_ANIMS[] = {ANIM_AGENT_SIT_GROUND, ANIM_AGENT_SIT_GROUND_CONSTRAINED, ANIM_AGENT_STANDUP}; +S32 NUM_AGENT_NO_ROTATE_ANIMS = sizeof(AGENT_NO_ROTATE_ANIMS) / sizeof(LLUUID); + +LLUUID AGENT_STAND_ANIMS[] = {ANIM_AGENT_STAND, ANIM_AGENT_STAND_1, ANIM_AGENT_STAND_2, ANIM_AGENT_STAND_3, ANIM_AGENT_STAND_4}; +S32 NUM_AGENT_STAND_ANIMS = sizeof(AGENT_STAND_ANIMS) / sizeof(LLUUID); + + +LLAnimationLibrary gAnimLibrary; + +//----------------------------------------------------------------------------- +// LLAnimationLibrary() +//----------------------------------------------------------------------------- +LLAnimationLibrary::LLAnimationLibrary() : + mAnimStringTable(16384) +{ + //add animation names to animmap + mAnimMap[ANIM_AGENT_AFRAID]= mAnimStringTable.addString("express_afraid"); + mAnimMap[ANIM_AGENT_AIM_BAZOOKA_R]= mAnimStringTable.addString("aim_r_bazooka"); + mAnimMap[ANIM_AGENT_AIM_BOW_L]= mAnimStringTable.addString("aim_l_bow"); + mAnimMap[ANIM_AGENT_AIM_HANDGUN_R]= mAnimStringTable.addString("aim_r_handgun"); + mAnimMap[ANIM_AGENT_AIM_RIFLE_R]= mAnimStringTable.addString("aim_r_rifle"); + mAnimMap[ANIM_AGENT_ANGRY]= mAnimStringTable.addString("express_anger"); + mAnimMap[ANIM_AGENT_AWAY]= mAnimStringTable.addString("away"); + mAnimMap[ANIM_AGENT_BACKFLIP]= mAnimStringTable.addString("backflip"); + mAnimMap[ANIM_AGENT_BELLY_LAUGH]= mAnimStringTable.addString("express_laugh"); + mAnimMap[ANIM_AGENT_BLOW_KISS]= mAnimStringTable.addString("blowkiss"); + mAnimMap[ANIM_AGENT_BORED]= mAnimStringTable.addString("express_bored"); + mAnimMap[ANIM_AGENT_BOW]= mAnimStringTable.addString("bow"); + mAnimMap[ANIM_AGENT_BRUSH]= mAnimStringTable.addString("brush"); + mAnimMap[ANIM_AGENT_BUSY]= mAnimStringTable.addString("busy"); + mAnimMap[ANIM_AGENT_CLAP]= mAnimStringTable.addString("clap"); + mAnimMap[ANIM_AGENT_COURTBOW]= mAnimStringTable.addString("courtbow"); + mAnimMap[ANIM_AGENT_CROUCH]= mAnimStringTable.addString("crouch"); + mAnimMap[ANIM_AGENT_CROUCHWALK]= mAnimStringTable.addString("crouchwalk"); + mAnimMap[ANIM_AGENT_CRY]= mAnimStringTable.addString("express_cry"); + mAnimMap[ANIM_AGENT_CUSTOMIZE]= mAnimStringTable.addString("turn_180"); + mAnimMap[ANIM_AGENT_CUSTOMIZE_DONE]= mAnimStringTable.addString("turnback_180"); + mAnimMap[ANIM_AGENT_DANCE1]= mAnimStringTable.addString("dance1"); + mAnimMap[ANIM_AGENT_DANCE2]= mAnimStringTable.addString("dance2"); + mAnimMap[ANIM_AGENT_DANCE3]= mAnimStringTable.addString("dance3"); + mAnimMap[ANIM_AGENT_DANCE4]= mAnimStringTable.addString("dance4"); + mAnimMap[ANIM_AGENT_DANCE5]= mAnimStringTable.addString("dance5"); + mAnimMap[ANIM_AGENT_DANCE6]= mAnimStringTable.addString("dance6"); + mAnimMap[ANIM_AGENT_DANCE7]= mAnimStringTable.addString("dance7"); + mAnimMap[ANIM_AGENT_DANCE8]= mAnimStringTable.addString("dance8"); + mAnimMap[ANIM_AGENT_DEAD]= mAnimStringTable.addString("dead"); + mAnimMap[ANIM_AGENT_DRINK]= mAnimStringTable.addString("drink"); + mAnimMap[ANIM_AGENT_EMBARRASSED]= mAnimStringTable.addString("express_embarrased"); + mAnimMap[ANIM_AGENT_EXPRESS_AFRAID]= mAnimStringTable.addString("express_afraid_emote"); + mAnimMap[ANIM_AGENT_EXPRESS_ANGER]= mAnimStringTable.addString("express_anger_emote"); + mAnimMap[ANIM_AGENT_EXPRESS_BORED]= mAnimStringTable.addString("express_bored_emote"); + mAnimMap[ANIM_AGENT_EXPRESS_CRY]= mAnimStringTable.addString("express_cry_emote"); + mAnimMap[ANIM_AGENT_EXPRESS_DISDAIN]= mAnimStringTable.addString("express_disdain"); + mAnimMap[ANIM_AGENT_EXPRESS_EMBARRASSED]= mAnimStringTable.addString("express_embarrassed_emote"); + mAnimMap[ANIM_AGENT_EXPRESS_FROWN]= mAnimStringTable.addString("express_frown"); + mAnimMap[ANIM_AGENT_EXPRESS_KISS]= mAnimStringTable.addString("express_kiss"); + mAnimMap[ANIM_AGENT_EXPRESS_LAUGH]= mAnimStringTable.addString("express_laugh_emote"); + mAnimMap[ANIM_AGENT_EXPRESS_OPEN_MOUTH]= mAnimStringTable.addString("express_open_mouth"); + mAnimMap[ANIM_AGENT_EXPRESS_REPULSED]= mAnimStringTable.addString("express_repulsed_emote"); + mAnimMap[ANIM_AGENT_EXPRESS_SAD]= mAnimStringTable.addString("express_sad_emote"); + mAnimMap[ANIM_AGENT_EXPRESS_SHRUG]= mAnimStringTable.addString("express_shrug_emote"); + mAnimMap[ANIM_AGENT_EXPRESS_SMILE]= mAnimStringTable.addString("express_smile"); + mAnimMap[ANIM_AGENT_EXPRESS_SURPRISE]= mAnimStringTable.addString("express_surprise_emote"); + mAnimMap[ANIM_AGENT_EXPRESS_TONGUE_OUT]= mAnimStringTable.addString("express_tongue_out"); + mAnimMap[ANIM_AGENT_EXPRESS_TOOTHSMILE]= mAnimStringTable.addString("express_toothsmile"); + mAnimMap[ANIM_AGENT_EXPRESS_WINK]= mAnimStringTable.addString("express_wink_emote"); + mAnimMap[ANIM_AGENT_EXPRESS_WORRY]= mAnimStringTable.addString("express_worry_emote"); + mAnimMap[ANIM_AGENT_FALLDOWN]= mAnimStringTable.addString("falldown"); + mAnimMap[ANIM_AGENT_FEMALE_WALK]= mAnimStringTable.addString("female_walk"); + mAnimMap[ANIM_AGENT_FINGER_WAG]= mAnimStringTable.addString("angry_fingerwag"); + mAnimMap[ANIM_AGENT_FIST_PUMP]= mAnimStringTable.addString("fist_pump"); + mAnimMap[ANIM_AGENT_FLY]= mAnimStringTable.addString("fly"); + mAnimMap[ANIM_AGENT_FLYSLOW]= mAnimStringTable.addString("flyslow"); + mAnimMap[ANIM_AGENT_HELLO]= mAnimStringTable.addString("hello"); + mAnimMap[ANIM_AGENT_HOLD_BAZOOKA_R]= mAnimStringTable.addString("hold_r_bazooka"); + mAnimMap[ANIM_AGENT_HOLD_BOW_L]= mAnimStringTable.addString("hold_l_bow"); + mAnimMap[ANIM_AGENT_HOLD_HANDGUN_R]= mAnimStringTable.addString("hold_r_handgun"); + mAnimMap[ANIM_AGENT_HOLD_RIFLE_R]= mAnimStringTable.addString("hold_r_rifle"); + mAnimMap[ANIM_AGENT_HOLD_THROW_R]= mAnimStringTable.addString("hold_throw_r"); + mAnimMap[ANIM_AGENT_HOVER]= mAnimStringTable.addString("hover"); + mAnimMap[ANIM_AGENT_HOVER_DOWN]= mAnimStringTable.addString("hover_down"); + mAnimMap[ANIM_AGENT_HOVER_UP]= mAnimStringTable.addString("hover_up"); + mAnimMap[ANIM_AGENT_IMPATIENT]= mAnimStringTable.addString("impatient"); + mAnimMap[ANIM_AGENT_JUMP]= mAnimStringTable.addString("jump"); + mAnimMap[ANIM_AGENT_JUMP_FOR_JOY]= mAnimStringTable.addString("jumpforjoy"); + mAnimMap[ANIM_AGENT_KISS_MY_BUTT]= mAnimStringTable.addString("kissmybutt"); + mAnimMap[ANIM_AGENT_LAND]= mAnimStringTable.addString("land"); + mAnimMap[ANIM_AGENT_LAUGH_SHORT]= mAnimStringTable.addString("laugh_short"); + mAnimMap[ANIM_AGENT_MEDIUM_LAND]= mAnimStringTable.addString("soft_land"); + mAnimMap[ANIM_AGENT_MOTORCYCLE_SIT]= mAnimStringTable.addString("motorcycle_sit"); + mAnimMap[ANIM_AGENT_MUSCLE_BEACH]= mAnimStringTable.addString("musclebeach"); + mAnimMap[ANIM_AGENT_NO]= mAnimStringTable.addString("no_head"); + mAnimMap[ANIM_AGENT_NO_UNHAPPY]= mAnimStringTable.addString("no_unhappy"); + mAnimMap[ANIM_AGENT_NYAH_NYAH]= mAnimStringTable.addString("nyanya"); + mAnimMap[ANIM_AGENT_ONETWO_PUNCH]= mAnimStringTable.addString("punch_onetwo"); + mAnimMap[ANIM_AGENT_PEACE]= mAnimStringTable.addString("peace"); + mAnimMap[ANIM_AGENT_POINT_ME]= mAnimStringTable.addString("point_me"); + mAnimMap[ANIM_AGENT_POINT_YOU]= mAnimStringTable.addString("point_you"); + mAnimMap[ANIM_AGENT_PRE_JUMP]= mAnimStringTable.addString("prejump"); + mAnimMap[ANIM_AGENT_PUNCH_LEFT]= mAnimStringTable.addString("punch_l"); + mAnimMap[ANIM_AGENT_PUNCH_RIGHT]= mAnimStringTable.addString("punch_r"); + mAnimMap[ANIM_AGENT_REPULSED]= mAnimStringTable.addString("express_repulsed"); + mAnimMap[ANIM_AGENT_ROUNDHOUSE_KICK]= mAnimStringTable.addString("kick_roundhouse_r"); + mAnimMap[ANIM_AGENT_RPS_COUNTDOWN]= mAnimStringTable.addString("rps_countdown"); + mAnimMap[ANIM_AGENT_RPS_PAPER]= mAnimStringTable.addString("rps_paper"); + mAnimMap[ANIM_AGENT_RPS_ROCK]= mAnimStringTable.addString("rps_rock"); + mAnimMap[ANIM_AGENT_RPS_SCISSORS]= mAnimStringTable.addString("rps_scissors"); + mAnimMap[ANIM_AGENT_RUN]= mAnimStringTable.addString("run"); + mAnimMap[ANIM_AGENT_SAD]= mAnimStringTable.addString("express_sad"); + mAnimMap[ANIM_AGENT_SALUTE]= mAnimStringTable.addString("salute"); + mAnimMap[ANIM_AGENT_SHOOT_BOW_L]= mAnimStringTable.addString("shoot_l_bow"); + mAnimMap[ANIM_AGENT_SHOUT]= mAnimStringTable.addString("shout"); + mAnimMap[ANIM_AGENT_SHRUG]= mAnimStringTable.addString("express_shrug"); + mAnimMap[ANIM_AGENT_SIT]= mAnimStringTable.addString("sit"); + mAnimMap[ANIM_AGENT_SIT_FEMALE]= mAnimStringTable.addString("sit_female"); + mAnimMap[ANIM_AGENT_SIT_GROUND]= mAnimStringTable.addString("sit_ground"); + mAnimMap[ANIM_AGENT_SIT_GROUND_CONSTRAINED]= mAnimStringTable.addString("sit_ground_constrained"); + mAnimMap[ANIM_AGENT_SIT_GENERIC]= mAnimStringTable.addString("sit_generic"); + mAnimMap[ANIM_AGENT_SIT_TO_STAND]= mAnimStringTable.addString("sit_to_stand"); + mAnimMap[ANIM_AGENT_SLEEP]= mAnimStringTable.addString("sleep"); + mAnimMap[ANIM_AGENT_SMOKE_IDLE]= mAnimStringTable.addString("smoke_idle"); + mAnimMap[ANIM_AGENT_SMOKE_INHALE]= mAnimStringTable.addString("smoke_inhale"); + mAnimMap[ANIM_AGENT_SMOKE_THROW_DOWN]= mAnimStringTable.addString("smoke_throw_down"); + mAnimMap[ANIM_AGENT_SNAPSHOT]= mAnimStringTable.addString("snapshot"); + mAnimMap[ANIM_AGENT_STAND]= mAnimStringTable.addString("stand"); + mAnimMap[ANIM_AGENT_STANDUP]= mAnimStringTable.addString("standup"); + mAnimMap[ANIM_AGENT_STAND_1]= mAnimStringTable.addString("stand_1"); + mAnimMap[ANIM_AGENT_STAND_2]= mAnimStringTable.addString("stand_2"); + mAnimMap[ANIM_AGENT_STAND_3]= mAnimStringTable.addString("stand_3"); + mAnimMap[ANIM_AGENT_STAND_4]= mAnimStringTable.addString("stand_4"); + mAnimMap[ANIM_AGENT_STRETCH]= mAnimStringTable.addString("stretch"); + mAnimMap[ANIM_AGENT_STRIDE]= mAnimStringTable.addString("stride"); + mAnimMap[ANIM_AGENT_SURF]= mAnimStringTable.addString("surf"); + mAnimMap[ANIM_AGENT_SURPRISE]= mAnimStringTable.addString("express_surprise"); + mAnimMap[ANIM_AGENT_SWORD_STRIKE]= mAnimStringTable.addString("sword_strike_r"); + mAnimMap[ANIM_AGENT_TALK]= mAnimStringTable.addString("talk"); + mAnimMap[ANIM_AGENT_TANTRUM]= mAnimStringTable.addString("angry_tantrum"); + mAnimMap[ANIM_AGENT_THROW_R]= mAnimStringTable.addString("throw_r"); + mAnimMap[ANIM_AGENT_TRYON_SHIRT]= mAnimStringTable.addString("tryon_shirt"); + mAnimMap[ANIM_AGENT_TURNLEFT]= mAnimStringTable.addString("turnleft"); + mAnimMap[ANIM_AGENT_TURNRIGHT]= mAnimStringTable.addString("turnright"); + mAnimMap[ANIM_AGENT_TYPE]= mAnimStringTable.addString("type"); + mAnimMap[ANIM_AGENT_WALK]= mAnimStringTable.addString("walk"); + mAnimMap[ANIM_AGENT_WHISPER]= mAnimStringTable.addString("whisper"); + mAnimMap[ANIM_AGENT_WHISTLE]= mAnimStringTable.addString("whistle"); + mAnimMap[ANIM_AGENT_WINK]= mAnimStringTable.addString("express_wink"); + mAnimMap[ANIM_AGENT_WINK_HOLLYWOOD]= mAnimStringTable.addString("wink_hollywood"); + mAnimMap[ANIM_AGENT_WORRY]= mAnimStringTable.addString("express_worry"); + mAnimMap[ANIM_AGENT_YES]= mAnimStringTable.addString("yes_head"); + mAnimMap[ANIM_AGENT_YES_HAPPY]= mAnimStringTable.addString("yes_happy"); + mAnimMap[ANIM_AGENT_YOGA_FLOAT]= mAnimStringTable.addString("yoga_float"); +} + +//----------------------------------------------------------------------------- +// ~LLAnimationLibrary() +//----------------------------------------------------------------------------- +LLAnimationLibrary::~LLAnimationLibrary() +{ +} + +//----------------------------------------------------------------------------- +// Return the text name of an animation state +//----------------------------------------------------------------------------- +const char *LLAnimationLibrary::animStateToString( const LLUUID& state ) +{ + if (state.isNull()) + { + return NULL; + } + if (mAnimMap.count(state)) + { + return mAnimMap[state]; + } + + return NULL; +} + + +//----------------------------------------------------------------------------- +// Return the animation state for a given name +//----------------------------------------------------------------------------- +LLUUID LLAnimationLibrary::stringToAnimState( const char *name, BOOL allow_ids ) +{ + LLString lower_case_name(name); + LLString::toLower(lower_case_name); + + char *true_name = mAnimStringTable.checkString(lower_case_name.c_str()); + + LLUUID id; + id.setNull(); + + if (true_name) + { + for (anim_map_t::iterator iter = mAnimMap.begin(); + iter != mAnimMap.end(); iter++) + { + if (iter->second == true_name) + { + id = iter->first; + break; + } + } + } + else if (allow_ids) + { + // try to convert string to LLUUID + id.set(name, FALSE); + } + + return id; +} + +// Animation states that the user can trigger as part of a gesture +const LLAnimStateEntry gUserAnimStates[] = { + LLAnimStateEntry("Afraid", "express_afraid", ANIM_AGENT_AFRAID), + LLAnimStateEntry("Angry", "express_anger", ANIM_AGENT_ANGRY), + LLAnimStateEntry("Away", "away", ANIM_AGENT_AWAY), + LLAnimStateEntry("Backflip", "backflip", ANIM_AGENT_BACKFLIP), + LLAnimStateEntry("Belly Laugh", "express_laugh", ANIM_AGENT_BELLY_LAUGH), + LLAnimStateEntry("BigSmile", "express_toothsmile", ANIM_AGENT_EXPRESS_TOOTHSMILE), + LLAnimStateEntry("Blow Kiss", "blowkiss", ANIM_AGENT_BLOW_KISS), + LLAnimStateEntry("Bored", "express_bored", ANIM_AGENT_BORED), + LLAnimStateEntry("Bow", "bow", ANIM_AGENT_BOW), + LLAnimStateEntry("Clap", "clap", ANIM_AGENT_CLAP), + LLAnimStateEntry("Court Bow", "courtbow", ANIM_AGENT_COURTBOW), + LLAnimStateEntry("Cry", "express_cry", ANIM_AGENT_CRY), + LLAnimStateEntry("Dance 1", "dance1", ANIM_AGENT_DANCE1), + LLAnimStateEntry("Dance 2", "dance2", ANIM_AGENT_DANCE2), + LLAnimStateEntry("Dance 3", "dance3", ANIM_AGENT_DANCE3), + LLAnimStateEntry("Dance 4", "dance4", ANIM_AGENT_DANCE4), + LLAnimStateEntry("Dance 5", "dance5", ANIM_AGENT_DANCE5), + LLAnimStateEntry("Dance 6", "dance6", ANIM_AGENT_DANCE6), + LLAnimStateEntry("Dance 7", "dance7", ANIM_AGENT_DANCE7), + LLAnimStateEntry("Dance 8", "dance8", ANIM_AGENT_DANCE8), + LLAnimStateEntry("Disdain", "express_disdain", ANIM_AGENT_EXPRESS_DISDAIN), + LLAnimStateEntry("Drink", "drink", ANIM_AGENT_DRINK), + LLAnimStateEntry("Embarrassed", "express_embarrased", ANIM_AGENT_EMBARRASSED), + LLAnimStateEntry("Finger Wag", "angry_fingerwag", ANIM_AGENT_FINGER_WAG), + LLAnimStateEntry("Fist Pump", "fist_pump", ANIM_AGENT_FIST_PUMP), + LLAnimStateEntry("Floating Yoga", "yoga_float", ANIM_AGENT_YOGA_FLOAT), + LLAnimStateEntry("Frown", "express_frown", ANIM_AGENT_EXPRESS_FROWN), + LLAnimStateEntry("Impatient", "impatient", ANIM_AGENT_IMPATIENT), + LLAnimStateEntry("Jump For Joy", "jumpforjoy", ANIM_AGENT_JUMP_FOR_JOY), + LLAnimStateEntry("Kiss My Butt", "kissmybutt", ANIM_AGENT_KISS_MY_BUTT), + LLAnimStateEntry("Kiss", "express_kiss", ANIM_AGENT_EXPRESS_KISS), + LLAnimStateEntry("Laugh", "laugh_short", ANIM_AGENT_LAUGH_SHORT), + LLAnimStateEntry("Muscle Beach", "musclebeach", ANIM_AGENT_MUSCLE_BEACH), + LLAnimStateEntry("No (Unhappy)", "no_unhappy", ANIM_AGENT_NO_UNHAPPY), + LLAnimStateEntry("No", "no_head", ANIM_AGENT_NO), + LLAnimStateEntry("Nya-nya-nya", "nyanya", ANIM_AGENT_NYAH_NYAH), + LLAnimStateEntry("One-Two Punch", "punch_onetwo", ANIM_AGENT_ONETWO_PUNCH), + LLAnimStateEntry("Open Mouth", "express_open_mouth", ANIM_AGENT_EXPRESS_OPEN_MOUTH), + LLAnimStateEntry("Peace", "peace", ANIM_AGENT_PEACE), + LLAnimStateEntry("Point at Other", "point_you", ANIM_AGENT_POINT_YOU), + LLAnimStateEntry("Point at Self", "point_me", ANIM_AGENT_POINT_ME), + LLAnimStateEntry("Punch Left", "punch_l", ANIM_AGENT_PUNCH_LEFT), + LLAnimStateEntry("Punch Right", "punch_r", ANIM_AGENT_PUNCH_RIGHT), + LLAnimStateEntry("RPS count", "rps_countdown", ANIM_AGENT_RPS_COUNTDOWN), + LLAnimStateEntry("RPS paper", "rps_paper", ANIM_AGENT_RPS_PAPER), + LLAnimStateEntry("RPS rock", "rps_rock", ANIM_AGENT_RPS_ROCK), + LLAnimStateEntry("RPS scissors", "rps_scissors", ANIM_AGENT_RPS_SCISSORS), + LLAnimStateEntry("Repulsed", "express_repulsed", ANIM_AGENT_EXPRESS_REPULSED), + LLAnimStateEntry("Roundhouse Kick", "kick_roundhouse_r", ANIM_AGENT_ROUNDHOUSE_KICK), + LLAnimStateEntry("Sad", "express_sad", ANIM_AGENT_SAD), + LLAnimStateEntry("Salute", "salute", ANIM_AGENT_SALUTE), + LLAnimStateEntry("Shout", "shout", ANIM_AGENT_SHOUT), + LLAnimStateEntry("Shrug", "express_shrug", ANIM_AGENT_SHRUG), + LLAnimStateEntry("Smile", "express_smile", ANIM_AGENT_EXPRESS_SMILE), + LLAnimStateEntry("Smoke Idle", "smoke_idle", ANIM_AGENT_SMOKE_IDLE), + LLAnimStateEntry("Smoke Inhale", "smoke_inhale", ANIM_AGENT_SMOKE_INHALE), + LLAnimStateEntry("Smoke Throw Down","smoke_throw_down", ANIM_AGENT_SMOKE_THROW_DOWN), + LLAnimStateEntry("Surprise", "express_surprise", ANIM_AGENT_SURPRISE), + LLAnimStateEntry("Sword Strike", "sword_strike_r", ANIM_AGENT_SWORD_STRIKE), + LLAnimStateEntry("Tantrum", "angry_tantrum", ANIM_AGENT_TANTRUM), + LLAnimStateEntry("TongueOut", "express_tongue_out", ANIM_AGENT_EXPRESS_TONGUE_OUT), + LLAnimStateEntry("Wave", "hello", ANIM_AGENT_HELLO), + LLAnimStateEntry("Whisper", "whisper", ANIM_AGENT_WHISPER), + LLAnimStateEntry("Whistle", "whistle", ANIM_AGENT_WHISTLE), + LLAnimStateEntry("Wink", "express_wink", ANIM_AGENT_WINK), + LLAnimStateEntry("Wink (Hollywood)","wink_hollywood", ANIM_AGENT_WINK_HOLLYWOOD), + LLAnimStateEntry("Worry", "express_worry", ANIM_AGENT_EXPRESS_WORRY), + LLAnimStateEntry("Yes (Happy)", "yes_happy", ANIM_AGENT_YES_HAPPY), + LLAnimStateEntry("Yes", "yes_head", ANIM_AGENT_YES), +}; + +const S32 gUserAnimStatesCount = sizeof(gUserAnimStates) / sizeof(gUserAnimStates[0]); + + + +// End + diff --git a/indra/llcharacter/llanimationstates.h b/indra/llcharacter/llanimationstates.h new file mode 100644 index 0000000000..31fe0dfc11 --- /dev/null +++ b/indra/llcharacter/llanimationstates.h @@ -0,0 +1,227 @@ +/** + * @file llanimationstates.h + * @brief Implementation of animation state support. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLANIMATIONSTATES_H +#define LL_LLANIMATIONSTATES_H + +#include <map> + +#include "string_table.h" +#include "lluuid.h" + +//----------------------------------------------------------------------------- +// These bit flags are generally used to track the animation state +// of characters. The simulator and viewer share these flags to interpret +// the Animation name/value attribute on agents. +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Agent Animation State +//----------------------------------------------------------------------------- +const S32 MAX_CONCURRENT_ANIMS = 16; + + +const LLUUID ANIM_AGENT_AFRAID = LLUUID("6b61c8e8-4747-0d75-12d7-e49ff207a4ca"); +const LLUUID ANIM_AGENT_AIM_BAZOOKA_R = LLUUID("b5b4a67d-0aee-30d2-72cd-77b333e932ef"); +const LLUUID ANIM_AGENT_AIM_BOW_L = LLUUID("46bb4359-de38-4ed8-6a22-f1f52fe8f506"); +const LLUUID ANIM_AGENT_AIM_HANDGUN_R = LLUUID("3147d815-6338-b932-f011-16b56d9ac18b"); +const LLUUID ANIM_AGENT_AIM_RIFLE_R = LLUUID("ea633413-8006-180a-c3ba-96dd1d756720"); +const LLUUID ANIM_AGENT_ANGRY = LLUUID("5747a48e-073e-c331-f6f3-7c2149613d3e"); +const LLUUID ANIM_AGENT_AWAY = LLUUID("fd037134-85d4-f241-72c6-4f42164fedee"); +const LLUUID ANIM_AGENT_BACKFLIP = LLUUID("c4ca6188-9127-4f31-0158-23c4e2f93304"); +const LLUUID ANIM_AGENT_BELLY_LAUGH = LLUUID("18b3a4b5-b463-bd48-e4b6-71eaac76c515"); +const LLUUID ANIM_AGENT_BLOW_KISS = LLUUID("db84829b-462c-ee83-1e27-9bbee66bd624"); +const LLUUID ANIM_AGENT_BORED = LLUUID("b906c4ba-703b-1940-32a3-0c7f7d791510"); +const LLUUID ANIM_AGENT_BOW = LLUUID("82e99230-c906-1403-4d9c-3889dd98daba"); +const LLUUID ANIM_AGENT_BRUSH = LLUUID("349a3801-54f9-bf2c-3bd0-1ac89772af01"); +const LLUUID ANIM_AGENT_BUSY = LLUUID("efcf670c-2d18-8128-973a-034ebc806b67"); +const LLUUID ANIM_AGENT_CLAP = LLUUID("9b0c1c4e-8ac7-7969-1494-28c874c4f668"); +const LLUUID ANIM_AGENT_COURTBOW = LLUUID("9ba1c942-08be-e43a-fb29-16ad440efc50"); +const LLUUID ANIM_AGENT_CROUCH = LLUUID("201f3fdf-cb1f-dbec-201f-7333e328ae7c"); +const LLUUID ANIM_AGENT_CROUCHWALK = LLUUID("47f5f6fb-22e5-ae44-f871-73aaaf4a6022"); +const LLUUID ANIM_AGENT_CRY = LLUUID("92624d3e-1068-f1aa-a5ec-8244585193ed"); +const LLUUID ANIM_AGENT_CUSTOMIZE = LLUUID("038fcec9-5ebd-8a8e-0e2e-6e71a0a1ac53"); +const LLUUID ANIM_AGENT_CUSTOMIZE_DONE = LLUUID("6883a61a-b27b-5914-a61e-dda118a9ee2c"); +const LLUUID ANIM_AGENT_DANCE1 = LLUUID("b68a3d7c-de9e-fc87-eec8-543d787e5b0d"); +const LLUUID ANIM_AGENT_DANCE2 = LLUUID("928cae18-e31d-76fd-9cc9-2f55160ff818"); +const LLUUID ANIM_AGENT_DANCE3 = LLUUID("30047778-10ea-1af7-6881-4db7a3a5a114"); +const LLUUID ANIM_AGENT_DANCE4 = LLUUID("951469f4-c7b2-c818-9dee-ad7eea8c30b7"); +const LLUUID ANIM_AGENT_DANCE5 = LLUUID("4bd69a1d-1114-a0b4-625f-84e0a5237155"); +const LLUUID ANIM_AGENT_DANCE6 = LLUUID("cd28b69b-9c95-bb78-3f94-8d605ff1bb12"); +const LLUUID ANIM_AGENT_DANCE7 = LLUUID("a54d8ee2-28bb-80a9-7f0c-7afbbe24a5d6"); +const LLUUID ANIM_AGENT_DANCE8 = LLUUID("b0dc417c-1f11-af36-2e80-7e7489fa7cdc"); +const LLUUID ANIM_AGENT_DEAD = LLUUID("57abaae6-1d17-7b1b-5f98-6d11a6411276"); +const LLUUID ANIM_AGENT_DRINK = LLUUID("0f86e355-dd31-a61c-fdb0-3a96b9aad05f"); +const LLUUID ANIM_AGENT_EMBARRASSED = LLUUID("514af488-9051-044a-b3fc-d4dbf76377c6"); +const LLUUID ANIM_AGENT_EXPRESS_AFRAID = LLUUID("aa2df84d-cf8f-7218-527b-424a52de766e"); +const LLUUID ANIM_AGENT_EXPRESS_ANGER = LLUUID("1a03b575-9634-b62a-5767-3a679e81f4de"); +const LLUUID ANIM_AGENT_EXPRESS_BORED = LLUUID("214aa6c1-ba6a-4578-f27c-ce7688f61d0d"); +const LLUUID ANIM_AGENT_EXPRESS_CRY = LLUUID("d535471b-85bf-3b4d-a542-93bea4f59d33"); +const LLUUID ANIM_AGENT_EXPRESS_DISDAIN = LLUUID("d4416ff1-09d3-300f-4183-1b68a19b9fc1"); +const LLUUID ANIM_AGENT_EXPRESS_EMBARRASSED = LLUUID("0b8c8211-d78c-33e8-fa28-c51a9594e424"); +const LLUUID ANIM_AGENT_EXPRESS_FROWN = LLUUID("fee3df48-fa3d-1015-1e26-a205810e3001"); +const LLUUID ANIM_AGENT_EXPRESS_KISS = LLUUID("1e8d90cc-a84e-e135-884c-7c82c8b03a14"); +const LLUUID ANIM_AGENT_EXPRESS_LAUGH = LLUUID("62570842-0950-96f8-341c-809e65110823"); +const LLUUID ANIM_AGENT_EXPRESS_OPEN_MOUTH = LLUUID("d63bc1f9-fc81-9625-a0c6-007176d82eb7"); +const LLUUID ANIM_AGENT_EXPRESS_REPULSED = LLUUID("f76cda94-41d4-a229-2872-e0296e58afe1"); +const LLUUID ANIM_AGENT_EXPRESS_SAD = LLUUID("eb6ebfb2-a4b3-a19c-d388-4dd5c03823f7"); +const LLUUID ANIM_AGENT_EXPRESS_SHRUG = LLUUID("a351b1bc-cc94-aac2-7bea-a7e6ebad15ef"); +const LLUUID ANIM_AGENT_EXPRESS_SMILE = LLUUID("b7c7c833-e3d3-c4e3-9fc0-131237446312"); +const LLUUID ANIM_AGENT_EXPRESS_SURPRISE = LLUUID("728646d9-cc79-08b2-32d6-937f0a835c24"); +const LLUUID ANIM_AGENT_EXPRESS_TONGUE_OUT = LLUUID("835965c6-7f2f-bda2-5deb-2478737f91bf"); +const LLUUID ANIM_AGENT_EXPRESS_TOOTHSMILE = LLUUID("b92ec1a5-e7ce-a76b-2b05-bcdb9311417e"); +const LLUUID ANIM_AGENT_EXPRESS_WINK = LLUUID("da020525-4d94-59d6-23d7-81fdebf33148"); +const LLUUID ANIM_AGENT_EXPRESS_WORRY = LLUUID("9c05e5c7-6f07-6ca4-ed5a-b230390c3950"); +const LLUUID ANIM_AGENT_FALLDOWN = LLUUID("666307d9-a860-572d-6fd4-c3ab8865c094"); +const LLUUID ANIM_AGENT_FEMALE_WALK = LLUUID("f5fc7433-043d-e819-8298-f519a119b688"); +const LLUUID ANIM_AGENT_FINGER_WAG = LLUUID("c1bc7f36-3ba0-d844-f93c-93be945d644f"); +const LLUUID ANIM_AGENT_FIST_PUMP = LLUUID("7db00ccd-f380-f3ee-439d-61968ec69c8a"); +const LLUUID ANIM_AGENT_FLY = LLUUID("aec4610c-757f-bc4e-c092-c6e9caf18daf"); +const LLUUID ANIM_AGENT_FLYSLOW = LLUUID("2b5a38b2-5e00-3a97-a495-4c826bc443e6"); +const LLUUID ANIM_AGENT_HELLO = LLUUID("9b29cd61-c45b-5689-ded2-91756b8d76a9"); +const LLUUID ANIM_AGENT_HOLD_BAZOOKA_R = LLUUID("ef62d355-c815-4816-2474-b1acc21094a6"); +const LLUUID ANIM_AGENT_HOLD_BOW_L = LLUUID("8b102617-bcba-037b-86c1-b76219f90c88"); +const LLUUID ANIM_AGENT_HOLD_HANDGUN_R = LLUUID("efdc1727-8b8a-c800-4077-975fc27ee2f2"); +const LLUUID ANIM_AGENT_HOLD_RIFLE_R = LLUUID("3d94bad0-c55b-7dcc-8763-033c59405d33"); +const LLUUID ANIM_AGENT_HOLD_THROW_R = LLUUID("7570c7b5-1f22-56dd-56ef-a9168241bbb6"); +const LLUUID ANIM_AGENT_HOVER = LLUUID("4ae8016b-31b9-03bb-c401-b1ea941db41d"); +const LLUUID ANIM_AGENT_HOVER_DOWN = LLUUID("20f063ea-8306-2562-0b07-5c853b37b31e"); +const LLUUID ANIM_AGENT_HOVER_UP = LLUUID("62c5de58-cb33-5743-3d07-9e4cd4352864"); +const LLUUID ANIM_AGENT_IMPATIENT = LLUUID("5ea3991f-c293-392e-6860-91dfa01278a3"); +const LLUUID ANIM_AGENT_JUMP = LLUUID("2305bd75-1ca9-b03b-1faa-b176b8a8c49e"); +const LLUUID ANIM_AGENT_JUMP_FOR_JOY = LLUUID("709ea28e-1573-c023-8bf8-520c8bc637fa"); +const LLUUID ANIM_AGENT_KISS_MY_BUTT = LLUUID("19999406-3a3a-d58c-a2ac-d72e555dcf51"); +const LLUUID ANIM_AGENT_LAND = LLUUID("7a17b059-12b2-41b1-570a-186368b6aa6f"); +const LLUUID ANIM_AGENT_LAUGH_SHORT = LLUUID("ca5b3f14-3194-7a2b-c894-aa699b718d1f"); +const LLUUID ANIM_AGENT_MEDIUM_LAND = LLUUID("f4f00d6e-b9fe-9292-f4cb-0ae06ea58d57"); +const LLUUID ANIM_AGENT_MOTORCYCLE_SIT = LLUUID("08464f78-3a8e-2944-cba5-0c94aff3af29"); +const LLUUID ANIM_AGENT_MUSCLE_BEACH = LLUUID("315c3a41-a5f3-0ba4-27da-f893f769e69b"); +const LLUUID ANIM_AGENT_NO = LLUUID("5a977ed9-7f72-44e9-4c4c-6e913df8ae74"); +const LLUUID ANIM_AGENT_NO_UNHAPPY = LLUUID("d83fa0e5-97ed-7eb2-e798-7bd006215cb4"); +const LLUUID ANIM_AGENT_NYAH_NYAH = LLUUID("f061723d-0a18-754f-66ee-29a44795a32f"); +const LLUUID ANIM_AGENT_ONETWO_PUNCH = LLUUID("eefc79be-daae-a239-8c04-890f5d23654a"); +const LLUUID ANIM_AGENT_PEACE = LLUUID("b312b10e-65ab-a0a4-8b3c-1326ea8e3ed9"); +const LLUUID ANIM_AGENT_POINT_ME = LLUUID("17c024cc-eef2-f6a0-3527-9869876d7752"); +const LLUUID ANIM_AGENT_POINT_YOU = LLUUID("ec952cca-61ef-aa3b-2789-4d1344f016de"); +const LLUUID ANIM_AGENT_PRE_JUMP = LLUUID("7a4e87fe-de39-6fcb-6223-024b00893244"); +const LLUUID ANIM_AGENT_PUNCH_LEFT = LLUUID("f3300ad9-3462-1d07-2044-0fef80062da0"); +const LLUUID ANIM_AGENT_PUNCH_RIGHT = LLUUID("c8e42d32-7310-6906-c903-cab5d4a34656"); +const LLUUID ANIM_AGENT_REPULSED = LLUUID("36f81a92-f076-5893-dc4b-7c3795e487cf"); +const LLUUID ANIM_AGENT_ROUNDHOUSE_KICK = LLUUID("49aea43b-5ac3-8a44-b595-96100af0beda"); +const LLUUID ANIM_AGENT_RPS_COUNTDOWN = LLUUID("35db4f7e-28c2-6679-cea9-3ee108f7fc7f"); +const LLUUID ANIM_AGENT_RPS_PAPER = LLUUID("0836b67f-7f7b-f37b-c00a-460dc1521f5a"); +const LLUUID ANIM_AGENT_RPS_ROCK = LLUUID("42dd95d5-0bc6-6392-f650-777304946c0f"); +const LLUUID ANIM_AGENT_RPS_SCISSORS = LLUUID("16803a9f-5140-e042-4d7b-d28ba247c325"); +const LLUUID ANIM_AGENT_RUN = LLUUID("05ddbff8-aaa9-92a1-2b74-8fe77a29b445"); +const LLUUID ANIM_AGENT_SAD = LLUUID("0eb702e2-cc5a-9a88-56a5-661a55c0676a"); +const LLUUID ANIM_AGENT_SALUTE = LLUUID("cd7668a6-7011-d7e2-ead8-fc69eff1a104"); +const LLUUID ANIM_AGENT_SHOOT_BOW_L = LLUUID("e04d450d-fdb5-0432-fd68-818aaf5935f8"); +const LLUUID ANIM_AGENT_SHOUT = LLUUID("6bd01860-4ebd-127a-bb3d-d1427e8e0c42"); +const LLUUID ANIM_AGENT_SHRUG = LLUUID("70ea714f-3a97-d742-1b01-590a8fcd1db5"); +const LLUUID ANIM_AGENT_SIT = LLUUID("1a5fe8ac-a804-8a5d-7cbd-56bd83184568"); +const LLUUID ANIM_AGENT_SIT_FEMALE = LLUUID("b1709c8d-ecd3-54a1-4f28-d55ac0840782"); +const LLUUID ANIM_AGENT_SIT_GENERIC = LLUUID("245f3c54-f1c0-bf2e-811f-46d8eeb386e7"); +const LLUUID ANIM_AGENT_SIT_GROUND = LLUUID("1c7600d6-661f-b87b-efe2-d7421eb93c86"); +const LLUUID ANIM_AGENT_SIT_GROUND_CONSTRAINED = LLUUID("1a2bd58e-87ff-0df8-0b4c-53e047b0bb6e"); +const LLUUID ANIM_AGENT_SIT_TO_STAND = LLUUID("a8dee56f-2eae-9e7a-05a2-6fb92b97e21e"); +const LLUUID ANIM_AGENT_SLEEP = LLUUID("f2bed5f9-9d44-39af-b0cd-257b2a17fe40"); +const LLUUID ANIM_AGENT_SMOKE_IDLE = LLUUID("d2f2ee58-8ad1-06c9-d8d3-3827ba31567a"); +const LLUUID ANIM_AGENT_SMOKE_INHALE = LLUUID("6802d553-49da-0778-9f85-1599a2266526"); +const LLUUID ANIM_AGENT_SMOKE_THROW_DOWN = LLUUID("0a9fb970-8b44-9114-d3a9-bf69cfe804d6"); +const LLUUID ANIM_AGENT_SNAPSHOT = LLUUID("eae8905b-271a-99e2-4c0e-31106afd100c"); +const LLUUID ANIM_AGENT_STAND = LLUUID("2408fe9e-df1d-1d7d-f4ff-1384fa7b350f"); +const LLUUID ANIM_AGENT_STANDUP = LLUUID("3da1d753-028a-5446-24f3-9c9b856d9422"); +const LLUUID ANIM_AGENT_STAND_1 = LLUUID("15468e00-3400-bb66-cecc-646d7c14458e"); +const LLUUID ANIM_AGENT_STAND_2 = LLUUID("370f3a20-6ca6-9971-848c-9a01bc42ae3c"); +const LLUUID ANIM_AGENT_STAND_3 = LLUUID("42b46214-4b44-79ae-deb8-0df61424ff4b"); +const LLUUID ANIM_AGENT_STAND_4 = LLUUID("f22fed8b-a5ed-2c93-64d5-bdd8b93c889f"); +const LLUUID ANIM_AGENT_STRETCH = LLUUID("80700431-74ec-a008-14f8-77575e73693f"); +const LLUUID ANIM_AGENT_STRIDE = LLUUID("1cb562b0-ba21-2202-efb3-30f82cdf9595"); +const LLUUID ANIM_AGENT_SURF = LLUUID("41426836-7437-7e89-025d-0aa4d10f1d69"); +const LLUUID ANIM_AGENT_SURPRISE = LLUUID("313b9881-4302-73c0-c7d0-0e7a36b6c224"); +const LLUUID ANIM_AGENT_SWORD_STRIKE = LLUUID("85428680-6bf9-3e64-b489-6f81087c24bd"); +const LLUUID ANIM_AGENT_TALK = LLUUID("5c682a95-6da4-a463-0bf6-0f5b7be129d1"); +const LLUUID ANIM_AGENT_TANTRUM = LLUUID("11000694-3f41-adc2-606b-eee1d66f3724"); +const LLUUID ANIM_AGENT_THROW_R = LLUUID("aa134404-7dac-7aca-2cba-435f9db875ca"); +const LLUUID ANIM_AGENT_TRYON_SHIRT = LLUUID("83ff59fe-2346-f236-9009-4e3608af64c1"); +const LLUUID ANIM_AGENT_TURNLEFT = LLUUID("56e0ba0d-4a9f-7f27-6117-32f2ebbf6135"); +const LLUUID ANIM_AGENT_TURNRIGHT = LLUUID("2d6daa51-3192-6794-8e2e-a15f8338ec30"); +const LLUUID ANIM_AGENT_TYPE = LLUUID("c541c47f-e0c0-058b-ad1a-d6ae3a4584d9"); +const LLUUID ANIM_AGENT_WALK = LLUUID("6ed24bd8-91aa-4b12-ccc7-c97c857ab4e0"); +const LLUUID ANIM_AGENT_WHISPER = LLUUID("7693f268-06c7-ea71-fa21-2b30d6533f8f"); +const LLUUID ANIM_AGENT_WHISTLE = LLUUID("b1ed7982-c68e-a982-7561-52a88a5298c0"); +const LLUUID ANIM_AGENT_WINK = LLUUID("869ecdad-a44b-671e-3266-56aef2e3ac2e"); +const LLUUID ANIM_AGENT_WINK_HOLLYWOOD = LLUUID("c0c4030f-c02b-49de-24ba-2331f43fe41c"); +const LLUUID ANIM_AGENT_WORRY = LLUUID("9f496bd2-589a-709f-16cc-69bf7df1d36c"); +const LLUUID ANIM_AGENT_YES = LLUUID("15dd911d-be82-2856-26db-27659b142875"); +const LLUUID ANIM_AGENT_YES_HAPPY = LLUUID("b8c8b2a3-9008-1771-3bfc-90924955ab2d"); +const LLUUID ANIM_AGENT_YOGA_FLOAT = LLUUID("42ecd00b-9947-a97c-400a-bbc9174c7aeb"); + +extern LLUUID AGENT_WALK_ANIMS[]; +extern S32 NUM_AGENT_WALK_ANIMS; + +extern LLUUID AGENT_GUN_HOLD_ANIMS[]; +extern S32 NUM_AGENT_GUN_HOLD_ANIMS; + +extern LLUUID AGENT_GUN_AIM_ANIMS[]; +extern S32 NUM_AGENT_GUN_AIM_ANIMS; + +extern LLUUID AGENT_NO_ROTATE_ANIMS[]; +extern S32 NUM_AGENT_NO_ROTATE_ANIMS; + +extern LLUUID AGENT_STAND_ANIMS[]; +extern S32 NUM_AGENT_STAND_ANIMS; + +class LLAnimationLibrary +{ +private: + LLStringTable mAnimStringTable; + + typedef std::map<LLUUID, char *> anim_map_t; + anim_map_t mAnimMap; + +public: + LLAnimationLibrary(); + ~LLAnimationLibrary(); + + //----------------------------------------------------------------------------- + // Return the text name of a single animation state, + // Return NULL if the state is invalid + //----------------------------------------------------------------------------- + const char *animStateToString( const LLUUID& state ); + + //----------------------------------------------------------------------------- + // Return the animation state for the given name. + // Retun NULL if the name is invalid. + //----------------------------------------------------------------------------- + LLUUID stringToAnimState( const char *name, BOOL allow_ids = TRUE ); +}; + +struct LLAnimStateEntry +{ + LLAnimStateEntry(const char* label, const char* name, const LLUUID& id) + : mLabel(label), + mName(name), + mID(id) + { } + + const char* mLabel; + const char* mName; + const LLUUID mID; +}; + +// Animation states that the user can trigger +extern const LLAnimStateEntry gUserAnimStates[]; +extern const S32 gUserAnimStatesCount; +extern LLAnimationLibrary gAnimLibrary; + + +#endif // LL_LLANIMATIONSTATES_H + + + diff --git a/indra/llcharacter/llbvhloader.cpp b/indra/llcharacter/llbvhloader.cpp new file mode 100644 index 0000000000..4d4ad39080 --- /dev/null +++ b/indra/llcharacter/llbvhloader.cpp @@ -0,0 +1,1489 @@ +/** + * @file llbvhloader.cpp + * @brief Translates a BVH files to LindenLabAnimation format. + * + * Copyright (c) 2004-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llbvhloader.h" +#include "lldatapacker.h" +#include "lldir.h" +#include "llkeyframemotion.h" +#include "llquantize.h" +#include "llstl.h" +#include "llapr.h" + +#include <stdio.h> +#include <string.h> +#include <stdarg.h> +#include <llquaternion.h> +#include <boost/tokenizer.hpp> + +using namespace std; + +#define INCHES_TO_METERS 0.02540005f + +const F32 POSITION_KEYFRAME_THRESHOLD = 0.03f; +const F32 ROTATION_KEYFRAME_THRESHOLD = 0.01f; + +const F32 POSITION_MOTION_THRESHOLD = 0.001f; +const F32 ROTATION_MOTION_THRESHOLD = 0.001f; + +char gInFile[1024]; /* Flawfinder: ignore */ +char gOutFile[1024]; /* Flawfinder: ignore */ + +//------------------------------------------------------------------------ +// Status Codes +//------------------------------------------------------------------------ +char *LLBVHLoader::ST_OK = "Ok"; +char *LLBVHLoader::ST_EOF = "Premature end of file."; +char *LLBVHLoader::ST_NO_CONSTRAINT = "Can't read constraint definition."; +char *LLBVHLoader::ST_NO_FILE = "Can't open BVH file."; +char *LLBVHLoader::ST_NO_HIER = "Invalid HIERARCHY header."; +char *LLBVHLoader::ST_NO_JOINT = "Can't find ROOT or JOINT."; +char *LLBVHLoader::ST_NO_NAME = "Can't get JOINT name."; +char *LLBVHLoader::ST_NO_OFFSET = "Can't find OFFSET."; +char *LLBVHLoader::ST_NO_CHANNELS = "Can't find CHANNELS."; +char *LLBVHLoader::ST_NO_ROTATION = "Can't get rotation order."; +char *LLBVHLoader::ST_NO_AXIS = "Can't get rotation axis."; +char *LLBVHLoader::ST_NO_MOTION = "Can't find MOTION."; +char *LLBVHLoader::ST_NO_FRAMES = "Can't get number of frames."; +char *LLBVHLoader::ST_NO_FRAME_TIME = "Can't get frame time."; +char *LLBVHLoader::ST_NO_POS = "Can't get position values."; +char *LLBVHLoader::ST_NO_ROT = "Can't get rotation values."; +char *LLBVHLoader::ST_NO_XLT_FILE = "Can't open translation file."; +char *LLBVHLoader::ST_NO_XLT_HEADER = "Can't read translation header."; +char *LLBVHLoader::ST_NO_XLT_NAME = "Can't read translation names."; +char *LLBVHLoader::ST_NO_XLT_IGNORE = "Can't read translation ignore value."; +char *LLBVHLoader::ST_NO_XLT_RELATIVE = "Can't read translation relative value."; +char *LLBVHLoader::ST_NO_XLT_OUTNAME = "Can't read translation outname value."; +char *LLBVHLoader::ST_NO_XLT_MATRIX = "Can't read translation matrix."; +char *LLBVHLoader::ST_NO_XLT_MERGECHILD = "Can't get mergechild name."; +char *LLBVHLoader::ST_NO_XLT_MERGEPARENT = "Can't get mergeparent name."; +char *LLBVHLoader::ST_NO_XLT_PRIORITY = "Can't get priority value."; +char *LLBVHLoader::ST_NO_XLT_LOOP = "Can't get loop value."; +char *LLBVHLoader::ST_NO_XLT_EASEIN = "Can't get easeIn values."; +char *LLBVHLoader::ST_NO_XLT_EASEOUT = "Can't get easeOut values."; +char *LLBVHLoader::ST_NO_XLT_HAND = "Can't get hand morph value."; +char *LLBVHLoader::ST_NO_XLT_EMOTE = "Can't read emote name."; + +//------------------------------------------------------------------------ +// find_next_whitespace() +//------------------------------------------------------------------------ +const char *find_next_whitespace(const char *p) +{ + while(*p && isspace(*p)) p++; + while(*p && !isspace(*p)) p++; + return p; +} + + +//------------------------------------------------------------------------ +// bvhStringToOrder() +// +// XYZ order in BVH files must be passed to mayaQ() as ZYX. +// This function reverses the input string before passing it on +// to StringToOrder(). +//------------------------------------------------------------------------ +LLQuaternion::Order bvhStringToOrder( char *str ) +{ + char order[4]; /* Flawfinder: ignore */ + order[0] = str[2]; + order[1] = str[1]; + order[2] = str[0]; + order[3] = 0; + LLQuaternion::Order retVal = StringToOrder( order ); + return retVal; +} + +//----------------------------------------------------------------------------- +// LLBVHLoader() +//----------------------------------------------------------------------------- +LLBVHLoader::LLBVHLoader(const char* buffer) +{ + reset(); + + mStatus = loadTranslationTable("anim.ini"); + + if (mStatus == LLBVHLoader::ST_NO_XLT_FILE) + { + llwarns << "NOTE: No translation table found." << llendl; + return; + } + else + { + if (mStatus != LLBVHLoader::ST_OK) + { + llwarns << "ERROR: [line: " << getLineNumber() << "] " << mStatus << llendl; + return; + } + } + + char error_text[128]; /* Flawfinder: ignore */ + S32 error_line; + mStatus = loadBVHFile(buffer, error_text, error_line); + if (mStatus != LLBVHLoader::ST_OK) + { + llwarns << "ERROR: [line: " << getLineNumber() << "] " << mStatus << llendl; + return; + } + + applyTranslations(); + optimize(); + + mInitialized = TRUE; +} + +LLBVHLoader::~LLBVHLoader() +{ + std::for_each(mJoints.begin(),mJoints.end(),DeletePointer()); +} + +//------------------------------------------------------------------------ +// LLBVHLoader::loadTranslationTable() +//------------------------------------------------------------------------ +LLBVHLoader::Status LLBVHLoader::loadTranslationTable(const char *fileName) +{ + mLineNumber = 0; + mTranslations.clear(); + mConstraints.clear(); + + //-------------------------------------------------------------------- + // open file + //-------------------------------------------------------------------- + char path[LL_MAX_PATH]; /* Flawfinder: ignore */ + + snprintf( path, sizeof(path), "%s", + gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS,fileName).c_str()); /* Flawfinder: ignore */ + + + apr_file_t *fp = ll_apr_file_open(path, LL_APR_R); + if (!fp) + return ST_NO_XLT_FILE; + + llinfos << "NOTE: Loading translation table: " << fileName << llendl; + + //-------------------------------------------------------------------- + // register file to be closed on function exit + //-------------------------------------------------------------------- + FileCloser fileCloser(fp); + + //-------------------------------------------------------------------- + // load header + //-------------------------------------------------------------------- + if ( ! getLine(fp) ) + return ST_EOF; + if ( strncmp(mLine, "Translations 1.0", 16) ) + return ST_NO_XLT_HEADER; + + //-------------------------------------------------------------------- + // load data one line at a time + //-------------------------------------------------------------------- + BOOL loadingGlobals = FALSE; + Translation *trans = NULL; + while ( getLine(fp) ) + { + //---------------------------------------------------------------- + // check the 1st token on the line to determine if it's empty or a comment + //---------------------------------------------------------------- + char token[128]; /* Flawfinder: ignore */ + if ( sscanf(mLine, " %127s", token) != 1 ) + continue; + + if (token[0] == '#') + continue; + + //---------------------------------------------------------------- + // check if a [jointName] or [GLOBALS] was specified. + //---------------------------------------------------------------- + if (token[0] == '[') + { + char name[128]; /* Flawfinder: ignore */ + if ( sscanf(mLine, " [%127[^]]", name) != 1 ) + return ST_NO_XLT_NAME; + + if (strcmp(name, "GLOBALS")==0) + { + loadingGlobals = TRUE; + continue; + } + else + { + loadingGlobals = FALSE; + Translation &newTrans = mTranslations[ name ]; + trans = &newTrans; + continue; + } + } + + //---------------------------------------------------------------- + // check for optional emote + //---------------------------------------------------------------- + if (loadingGlobals && LLString::compareInsensitive(token, "emote")==0) + { + char emote_str[1024]; /* Flawfinder: ignore */ + if ( sscanf(mLine, " %*s = %1023s", emote_str) != 1 ) + return ST_NO_XLT_EMOTE; + + mEmoteName.assign( emote_str ); +// llinfos << "NOTE: Emote: " << mEmoteName.c_str() << llendl; + continue; + } + + + //---------------------------------------------------------------- + // check for global priority setting + //---------------------------------------------------------------- + if (loadingGlobals && LLString::compareInsensitive(token, "priority")==0) + { + S32 priority; + if ( sscanf(mLine, " %*s = %d", &priority) != 1 ) + return ST_NO_XLT_PRIORITY; + + mPriority = priority; +// llinfos << "NOTE: Priority: " << mPriority << llendl; + continue; + } + + //---------------------------------------------------------------- + // check for global loop setting + //---------------------------------------------------------------- + if (loadingGlobals && LLString::compareInsensitive(token, "loop")==0) + { + char trueFalse[128]; /* Flawfinder: ignore */ + trueFalse[0] = '\0'; + + F32 loop_in = 0.f; + F32 loop_out = 1.f; + + if ( sscanf(mLine, " %*s = %f %f", &loop_in, &loop_out) == 2 ) + { + mLoop = TRUE; + } + else if ( sscanf(mLine, " %*s = %127s", trueFalse) == 1 ) + { + mLoop = (LLString::compareInsensitive(trueFalse, "true")==0); + } + else + { + return ST_NO_XLT_LOOP; + } + + mLoopInPoint = loop_in * mDuration; + mLoopOutPoint = loop_out * mDuration; + + continue; + } + + //---------------------------------------------------------------- + // check for global easeIn setting + //---------------------------------------------------------------- + if (loadingGlobals && LLString::compareInsensitive(token, "easein")==0) + { + F32 duration; + char type[128]; /* Flawfinder: ignore */ + if ( sscanf(mLine, " %*s = %f %127s", &duration, type) != 2 ) + return ST_NO_XLT_EASEIN; + + mEaseIn = duration; + continue; + } + + //---------------------------------------------------------------- + // check for global easeOut setting + //---------------------------------------------------------------- + if (loadingGlobals && LLString::compareInsensitive(token, "easeout")==0) + { + F32 duration; + char type[128]; + if ( sscanf(mLine, " %*s = %f %127s", &duration, type) != 2 ) + return ST_NO_XLT_EASEOUT; + + mEaseOut = duration; + continue; + } + + //---------------------------------------------------------------- + // check for global handMorph setting + //---------------------------------------------------------------- + if (loadingGlobals && LLString::compareInsensitive(token, "hand")==0) + { + S32 handMorph; + if (sscanf(mLine, " %*s = %d", &handMorph) != 1) + return ST_NO_XLT_HAND; + + mHand = handMorph; + continue; + } + + if (loadingGlobals && LLString::compareInsensitive(token, "constraint")==0) + { + Constraint constraint; + + // try reading optional target direction + if(sscanf( + mLine, + " %*s = %d %f %f %f %f %15s %f %f %f %15s %f %f %f %f %f %f", + &constraint.mChainLength, + &constraint.mEaseInStart, + &constraint.mEaseInStop, + &constraint.mEaseOutStart, + &constraint.mEaseOutStop, + constraint.mSourceJointName, + &constraint.mSourceOffset.mV[VX], + &constraint.mSourceOffset.mV[VY], + &constraint.mSourceOffset.mV[VZ], + constraint.mTargetJointName, + &constraint.mTargetOffset.mV[VX], + &constraint.mTargetOffset.mV[VY], + &constraint.mTargetOffset.mV[VZ], + &constraint.mTargetDir.mV[VX], + &constraint.mTargetDir.mV[VY], + &constraint.mTargetDir.mV[VZ]) != 16) + { + if(sscanf( + mLine, + " %*s = %d %f %f %f %f %15s %f %f %f %15s %f %f %f", + &constraint.mChainLength, + &constraint.mEaseInStart, + &constraint.mEaseInStop, + &constraint.mEaseOutStart, + &constraint.mEaseOutStop, + constraint.mSourceJointName, + &constraint.mSourceOffset.mV[VX], + &constraint.mSourceOffset.mV[VY], + &constraint.mSourceOffset.mV[VZ], + constraint.mTargetJointName, + &constraint.mTargetOffset.mV[VX], + &constraint.mTargetOffset.mV[VY], + &constraint.mTargetOffset.mV[VZ]) != 13) + { + return ST_NO_CONSTRAINT; + } + } + else + { + // normalize direction + if (!constraint.mTargetDir.isExactlyZero()) + { + constraint.mTargetDir.normVec(); + } + + } + + constraint.mConstraintType = CONSTRAINT_TYPE_POINT; + mConstraints.push_back(constraint); + continue; + } + + if (loadingGlobals && LLString::compareInsensitive(token, "planar_constraint")==0) + { + Constraint constraint; + + // try reading optional target direction + if(sscanf( + mLine, + " %*s = %d %f %f %f %f %15s %f %f %f %15s %f %f %f %f %f %f", + &constraint.mChainLength, + &constraint.mEaseInStart, + &constraint.mEaseInStop, + &constraint.mEaseOutStart, + &constraint.mEaseOutStop, + constraint.mSourceJointName, + &constraint.mSourceOffset.mV[VX], + &constraint.mSourceOffset.mV[VY], + &constraint.mSourceOffset.mV[VZ], + constraint.mTargetJointName, + &constraint.mTargetOffset.mV[VX], + &constraint.mTargetOffset.mV[VY], + &constraint.mTargetOffset.mV[VZ], + &constraint.mTargetDir.mV[VX], + &constraint.mTargetDir.mV[VY], + &constraint.mTargetDir.mV[VZ]) != 16) + { + if(sscanf( + mLine, + " %*s = %d %f %f %f %f %15s %f %f %f %15s %f %f %f", + &constraint.mChainLength, + &constraint.mEaseInStart, + &constraint.mEaseInStop, + &constraint.mEaseOutStart, + &constraint.mEaseOutStop, + constraint.mSourceJointName, + &constraint.mSourceOffset.mV[VX], + &constraint.mSourceOffset.mV[VY], + &constraint.mSourceOffset.mV[VZ], + constraint.mTargetJointName, + &constraint.mTargetOffset.mV[VX], + &constraint.mTargetOffset.mV[VY], + &constraint.mTargetOffset.mV[VZ]) != 13) + { + return ST_NO_CONSTRAINT; + } + } + else + { + // normalize direction + if (!constraint.mTargetDir.isExactlyZero()) + { + constraint.mTargetDir.normVec(); + } + + } + + constraint.mConstraintType = CONSTRAINT_TYPE_PLANE; + mConstraints.push_back(constraint); + continue; + } + + + //---------------------------------------------------------------- + // at this point there must be a valid trans pointer + //---------------------------------------------------------------- + if ( ! trans ) + return ST_NO_XLT_NAME; + + //---------------------------------------------------------------- + // check for ignore flag + //---------------------------------------------------------------- + if ( LLString::compareInsensitive(token, "ignore")==0 ) + { + char trueFalse[128]; /* Flawfinder: ignore */ + if ( sscanf(mLine, " %*s = %127s", trueFalse) != 1 ) + return ST_NO_XLT_IGNORE; + + trans->mIgnore = (LLString::compareInsensitive(trueFalse, "true")==0); + continue; + } + + //---------------------------------------------------------------- + // check for relativepos flag + //---------------------------------------------------------------- + if ( LLString::compareInsensitive(token, "relativepos")==0 ) + { + F32 x, y, z; + char relpos[128]; /* Flawfinder: ignore */ + if ( sscanf(mLine, " %*s = %f %f %f", &x, &y, &z) == 3 ) + { + trans->mRelativePosition.setVec( x, y, z ); + } + else if ( sscanf(mLine, " %*s = %127s", relpos) == 1 ) + { + if ( LLString::compareInsensitive(relpos, "firstkey")==0 ) + { + trans->mRelativePositionKey = TRUE; + } + else + { + return ST_NO_XLT_RELATIVE; + } + } + else + { + return ST_NO_XLT_RELATIVE; + } + + continue; + } + + //---------------------------------------------------------------- + // check for relativerot flag + //---------------------------------------------------------------- + if ( LLString::compareInsensitive(token, "relativerot")==0 ) + { + //F32 x, y, z; + char relpos[128]; /* Flawfinder: ignore */ + if ( sscanf(mLine, " %*s = %127s", relpos) == 1 ) + { + if ( LLString::compareInsensitive(relpos, "firstkey")==0 ) + { + trans->mRelativeRotationKey = TRUE; + } + else + { + return ST_NO_XLT_RELATIVE; + } + } + else + { + return ST_NO_XLT_RELATIVE; + } + + continue; + } + + //---------------------------------------------------------------- + // check for outname value + //---------------------------------------------------------------- + if ( LLString::compareInsensitive(token, "outname")==0 ) + { + char outName[128]; /* Flawfinder: ignore */ + if ( sscanf(mLine, " %*s = %127s", outName) != 1 ) + return ST_NO_XLT_OUTNAME; + + trans->mOutName = outName; + continue; + } + + //---------------------------------------------------------------- + // check for frame matrix value + //---------------------------------------------------------------- + if ( LLString::compareInsensitive(token, "frame")==0 ) + { + LLMatrix3 fm; + if ( sscanf(mLine, " %*s = %f %f %f, %f %f %f, %f %f %f", + &fm.mMatrix[0][0], &fm.mMatrix[0][1], &fm.mMatrix[0][2], + &fm.mMatrix[1][0], &fm.mMatrix[1][1], &fm.mMatrix[1][2], + &fm.mMatrix[2][0], &fm.mMatrix[2][1], &fm.mMatrix[2][2] ) != 9 ) + return ST_NO_XLT_MATRIX; + + trans->mFrameMatrix = fm; + continue; + } + + //---------------------------------------------------------------- + // check for offset matrix value + //---------------------------------------------------------------- + if ( LLString::compareInsensitive(token, "offset")==0 ) + { + LLMatrix3 om; + if ( sscanf(mLine, " %*s = %f %f %f, %f %f %f, %f %f %f", + &om.mMatrix[0][0], &om.mMatrix[0][1], &om.mMatrix[0][2], + &om.mMatrix[1][0], &om.mMatrix[1][1], &om.mMatrix[1][2], + &om.mMatrix[2][0], &om.mMatrix[2][1], &om.mMatrix[2][2] ) != 9 ) + return ST_NO_XLT_MATRIX; + + trans->mOffsetMatrix = om; + continue; + } + + //---------------------------------------------------------------- + // check for mergeparent value + //---------------------------------------------------------------- + if ( LLString::compareInsensitive(token, "mergeparent")==0 ) + { + char mergeParentName[128]; /* Flawfinder: ignore */ + if ( sscanf(mLine, " %*s = %127s", mergeParentName) != 1 ) + return ST_NO_XLT_MERGEPARENT; + + trans->mMergeParentName = mergeParentName; + continue; + } + + //---------------------------------------------------------------- + // check for mergechild value + //---------------------------------------------------------------- + if ( LLString::compareInsensitive(token, "mergechild")==0 ) + { + char mergeChildName[128]; /* Flawfinder: ignore */ + if ( sscanf(mLine, " %*s = %127s", mergeChildName) != 1 ) + return ST_NO_XLT_MERGECHILD; + + trans->mMergeChildName = mergeChildName; + continue; + } + + //---------------------------------------------------------------- + // check for per-joint priority + //---------------------------------------------------------------- + if ( LLString::compareInsensitive(token, "priority")==0 ) + { + S32 priority; + if ( sscanf(mLine, " %*s = %d", &priority) != 1 ) + return ST_NO_XLT_PRIORITY; + + trans->mPriorityModifier = priority; + continue; + } + + } + return ST_OK; +} + + +//------------------------------------------------------------------------ +// LLBVHLoader::loadBVHFile() +//------------------------------------------------------------------------ +LLBVHLoader::Status LLBVHLoader::loadBVHFile(const char *buffer, char* error_text, S32 &err_line) +{ + std::string line; + + err_line = 0; + error_text[127] = '\0'; + + std::string str(buffer); + typedef boost::tokenizer<boost::char_separator<char> > tokenizer; + boost::char_separator<char> sep("\r\n"); + tokenizer tokens(str, sep); + tokenizer::iterator iter = tokens.begin(); + + mLineNumber = 0; + mJoints.clear(); + + std::vector<S32> parent_joints; + + //-------------------------------------------------------------------- + // consume hierarchy + //-------------------------------------------------------------------- + if (iter == tokens.end()) + return ST_EOF; + line = (*(iter++)); + err_line++; + + if ( !strstr(line.c_str(), "HIERARCHY") ) + { +// llinfos << line << llendl; + return ST_NO_HIER; + } + + //-------------------------------------------------------------------- + // consume joints + //-------------------------------------------------------------------- + while (TRUE) + { + //---------------------------------------------------------------- + // get next line + //---------------------------------------------------------------- + if (iter == tokens.end()) + return ST_EOF; + line = (*(iter++)); + err_line++; + + //---------------------------------------------------------------- + // consume } + //---------------------------------------------------------------- + if ( strstr(line.c_str(), "}") ) + { + if (parent_joints.size() > 0) + { + parent_joints.pop_back(); + } + continue; + } + + //---------------------------------------------------------------- + // if MOTION, break out + //---------------------------------------------------------------- + if ( strstr(line.c_str(), "MOTION") ) + break; + + //---------------------------------------------------------------- + // it must be either ROOT or JOINT or EndSite + //---------------------------------------------------------------- + if ( strstr(line.c_str(), "ROOT") ) + { + } + else if ( strstr(line.c_str(), "JOINT") ) + { + } + else if ( strstr(line.c_str(), "End Site") ) + { + iter++; // { + iter++; // OFFSET + S32 depth = 0; + for (S32 j = (S32)parent_joints.size() - 1; j >= 0; j--) + { + Joint *joint = mJoints[parent_joints[j]]; + if (depth > joint->mChildTreeMaxDepth) + { + joint->mChildTreeMaxDepth = depth; + } + depth++; + } + continue; + } + else + { + strncpy(error_text, line.c_str(), 127); /* Flawfinder: ignore */ + return ST_NO_JOINT; + } + + //---------------------------------------------------------------- + // get the joint name + //---------------------------------------------------------------- + char jointName[80]; /* Flawfinder: ignore */ + if ( sscanf(line.c_str(), "%*s %79s", jointName) != 1 ) + { + strncpy(error_text, line.c_str(), 127); /* Flawfinder: ignore */ + return ST_NO_NAME; + } + + //---------------------------------------------------------------- + // add a set of keyframes for this joint + //---------------------------------------------------------------- + mJoints.push_back( new Joint( jointName ) ); + Joint *joint = mJoints.back(); + + S32 depth = 1; + for (S32 j = (S32)parent_joints.size() - 1; j >= 0; j--) + { + Joint *pjoint = mJoints[parent_joints[j]]; + if (depth > pjoint->mChildTreeMaxDepth) + { + pjoint->mChildTreeMaxDepth = depth; + } + depth++; + } + + //---------------------------------------------------------------- + // get next line + //---------------------------------------------------------------- + if (iter == tokens.end()) + { + return ST_EOF; + } + line = (*(iter++)); + err_line++; + + //---------------------------------------------------------------- + // it must be { + //---------------------------------------------------------------- + if ( !strstr(line.c_str(), "{") ) + { + strncpy(error_text, line.c_str(), 127); /*Flawfinder: ignore*/ + return ST_NO_OFFSET; + } + else + { + parent_joints.push_back((S32)mJoints.size() - 1); + } + + //---------------------------------------------------------------- + // get next line + //---------------------------------------------------------------- + if (iter == tokens.end()) + { + return ST_EOF; + } + line = (*(iter++)); + err_line++; + + //---------------------------------------------------------------- + // it must be OFFSET + //---------------------------------------------------------------- + if ( !strstr(line.c_str(), "OFFSET") ) + { + strncpy(error_text, line.c_str(), 127); /*Flawfinder: ignore*/ + return ST_NO_OFFSET; + } + + //---------------------------------------------------------------- + // get next line + //---------------------------------------------------------------- + if (iter == tokens.end()) + { + return ST_EOF; + } + line = (*(iter++)); + err_line++; + + //---------------------------------------------------------------- + // it must be CHANNELS + //---------------------------------------------------------------- + if ( !strstr(line.c_str(), "CHANNELS") ) + { + strncpy(error_text, line.c_str(), 127); /*Flawfinder: ignore*/ + return ST_NO_CHANNELS; + } + + //---------------------------------------------------------------- + // get rotation order + //---------------------------------------------------------------- + const char *p = line.c_str(); + for (S32 i=0; i<3; i++) + { + p = strstr(p, "rotation"); + if (!p) + { + strncpy(error_text, line.c_str(), 127); /*Flawfinder: ignore*/ + return ST_NO_ROTATION; + } + + const char axis = *(p - 1); + if ((axis != 'X') && (axis != 'Y') && (axis != 'Z')) + { + strncpy(error_text, line.c_str(), 127); /*Flawfinder: ignore*/ + return ST_NO_AXIS; + } + + joint->mOrder[i] = axis; + + p++; + } + } + + //-------------------------------------------------------------------- + // consume motion + //-------------------------------------------------------------------- + if ( !strstr(line.c_str(), "MOTION") ) + { + strncpy(error_text, line.c_str(), 127); /*Flawfinder: ignore*/ + return ST_NO_MOTION; + } + + //-------------------------------------------------------------------- + // get number of frames + //-------------------------------------------------------------------- + if (iter == tokens.end()) + { + return ST_EOF; + } + line = (*(iter++)); + err_line++; + + if ( !strstr(line.c_str(), "Frames:") ) + { + strncpy(error_text, line.c_str(), 127); /*Flawfinder: ignore*/ + return ST_NO_FRAMES; + } + + if ( sscanf(line.c_str(), "Frames: %d", &mNumFrames) != 1 ) + { + strncpy(error_text, line.c_str(), 127); /*Flawfinder: ignore*/ + return ST_NO_FRAMES; + } + + //-------------------------------------------------------------------- + // get frame time + //-------------------------------------------------------------------- + if (iter == tokens.end()) + { + return ST_EOF; + } + line = (*(iter++)); + err_line++; + + if ( !strstr(line.c_str(), "Frame Time:") ) + { + strncpy(error_text, line.c_str(), 127); /*Flawfinder: ignore*/ + return ST_NO_FRAME_TIME; + } + + if ( sscanf(line.c_str(), "Frame Time: %f", &mFrameTime) != 1 ) + { + strncpy(error_text, line.c_str(), 127); /*Flawfinder: ignore*/ + return ST_NO_FRAME_TIME; + } + + mDuration = (F32)mNumFrames * mFrameTime; + if (!mLoop) + { + mLoopOutPoint = mDuration; + } + + //-------------------------------------------------------------------- + // load frames + //-------------------------------------------------------------------- + for (S32 i=0; i<mNumFrames; i++) + { + // get next line + if (iter == tokens.end()) + { + return ST_EOF; + } + line = (*(iter++)); + err_line++; + + // read and store values + const char *p = line.c_str(); + for (U32 j=0; j<mJoints.size(); j++) + { + Joint *joint = mJoints[j]; + joint->mKeys.push_back( Key() ); + Key &key = joint->mKeys.back(); + + // get 3 pos values for root joint only + if (j==0) + { + if ( sscanf(p, "%f %f %f", key.mPos, key.mPos+1, key.mPos+2) != 3 ) + { + strncpy(error_text, line.c_str(), 127); /*Flawfinder: ignore*/ + return ST_NO_POS; + } + } + + // skip to next 3 values in the line + p = find_next_whitespace(p); + if (!p) + { + strncpy(error_text, line.c_str(), 127); /*Flawfinder: ignore*/ + return ST_NO_ROT; + } + p = find_next_whitespace(++p); + if (!p) + { + strncpy(error_text, line.c_str(), 127); /*Flawfinder: ignore*/ + return ST_NO_ROT; + } + p = find_next_whitespace(++p); + if (!p) + { + strncpy(error_text, line.c_str(), 127); /*Flawfinder: ignore*/ + return ST_NO_ROT; + } + + // get 3 rot values for joint + F32 rot[3]; + if ( sscanf(p, " %f %f %f", rot, rot+1, rot+2) != 3 ) + { + strncpy(error_text, line.c_str(), 127); /*Flawfinder: ignore*/ + return ST_NO_ROT; + } + + p++; + + key.mRot[ joint->mOrder[0]-'X' ] = rot[0]; + key.mRot[ joint->mOrder[1]-'X' ] = rot[1]; + key.mRot[ joint->mOrder[2]-'X' ] = rot[2]; + } + } + + return ST_OK; +} + + +//------------------------------------------------------------------------ +// LLBVHLoader::applyTranslation() +//------------------------------------------------------------------------ +void LLBVHLoader::applyTranslations() +{ + JointVector::iterator ji; + for (ji = mJoints.begin(); ji != mJoints.end(); ++ji ) + { + Joint *joint = *ji; + //---------------------------------------------------------------- + // Look for a translation for this joint. + // If none, skip to next joint + //---------------------------------------------------------------- + TranslationMap::iterator ti = mTranslations.find( joint->mName ); + if ( ti == mTranslations.end() ) + { + continue; + } + + Translation &trans = ti->second; + + //---------------------------------------------------------------- + // Set the ignore flag if necessary + //---------------------------------------------------------------- + if ( trans.mIgnore ) + { + //llinfos << "NOTE: Ignoring " << joint->mName.c_str() << llendl; + joint->mIgnore = TRUE; + continue; + } + + //---------------------------------------------------------------- + // Set the output name + //---------------------------------------------------------------- + if ( ! trans.mOutName.empty() ) + { + //llinfos << "NOTE: Changing " << joint->mName.c_str() << " to " << trans.mOutName.c_str() << llendl; + joint->mOutName = trans.mOutName; + } + + //---------------------------------------------------------------- + // Set the ignorepos flag if necessary + //---------------------------------------------------------------- + if ( joint->mOutName == std::string("mPelvis") ) + { + joint->mIgnorePositions = FALSE; + } + + //---------------------------------------------------------------- + // Set the relativepos flags if necessary + //---------------------------------------------------------------- + if ( trans.mRelativePositionKey ) + { +// llinfos << "NOTE: Removing 1st position offset from all keys for " << joint->mOutName.c_str() << llendl; + joint->mRelativePositionKey = TRUE; + } + + if ( trans.mRelativeRotationKey ) + { +// llinfos << "NOTE: Removing 1st rotation from all keys for " << joint->mOutName.c_str() << llendl; + joint->mRelativeRotationKey = TRUE; + } + + if ( trans.mRelativePosition.magVec() > 0.0f ) + { + joint->mRelativePosition = trans.mRelativePosition; +// llinfos << "NOTE: Removing " << +// joint->mRelativePosition.mV[0] << " " << +// joint->mRelativePosition.mV[1] << " " << +// joint->mRelativePosition.mV[2] << +// " from all position keys in " << +// joint->mOutName.c_str() << llendl; + } + + //---------------------------------------------------------------- + // Set change of coordinate frame + //---------------------------------------------------------------- + joint->mFrameMatrix = trans.mFrameMatrix; + joint->mOffsetMatrix = trans.mOffsetMatrix; + + //---------------------------------------------------------------- + // Set mergeparent name + //---------------------------------------------------------------- + if ( ! trans.mMergeParentName.empty() ) + { +// llinfos << "NOTE: Merging " << joint->mOutName.c_str() << +// " with parent " << +// trans.mMergeParentName.c_str() << llendl; + joint->mMergeParentName = trans.mMergeParentName; + } + + //---------------------------------------------------------------- + // Set mergechild name + //---------------------------------------------------------------- + if ( ! trans.mMergeChildName.empty() ) + { +// llinfos << "NOTE: Merging " << joint->mName.c_str() << +// " with child " << trans.mMergeChildName.c_str() << llendl; + joint->mMergeChildName = trans.mMergeChildName; + } + + //---------------------------------------------------------------- + // Set joint priority + //---------------------------------------------------------------- + joint->mPriority = mPriority + trans.mPriorityModifier; + + } +} + +//----------------------------------------------------------------------------- +// LLBVHLoader::optimize() +//----------------------------------------------------------------------------- +void LLBVHLoader::optimize() +{ + //RN: assumes motion blend, which is the default now + if (!mLoop && mEaseIn + mEaseOut > mDuration && mDuration != 0.f) + { + F32 factor = mDuration / (mEaseIn + mEaseOut); + mEaseIn *= factor; + mEaseOut *= factor; + } + + JointVector::iterator ji; + for (ji = mJoints.begin(); ji != mJoints.end(); ++ji) + { + Joint *joint = *ji; + BOOL pos_changed = FALSE; + BOOL rot_changed = FALSE; + + if ( ! joint->mIgnore ) + { + joint->mNumPosKeys = 0; + joint->mNumRotKeys = 0; + LLQuaternion::Order order = bvhStringToOrder( joint->mOrder ); + + KeyVector::iterator first_key = joint->mKeys.begin(); + + // no keys? + if (first_key == joint->mKeys.end()) + { + joint->mIgnore = TRUE; + continue; + } + + LLVector3 first_frame_pos(first_key->mPos); + LLQuaternion first_frame_rot = mayaQ( first_key->mRot[0], first_key->mRot[1], first_key->mRot[2], order); + + // skip first key + KeyVector::iterator ki = joint->mKeys.begin(); + if (joint->mKeys.size() == 1) + { + //FIXME: use single frame to move pelvis + // if only one keyframe force output for this joint + rot_changed = TRUE; + } + else + { + // if more than one keyframe, use first frame as reference and skip to second + first_key->mIgnorePos = TRUE; + first_key->mIgnoreRot = TRUE; + ++ki; + } + + KeyVector::iterator ki_prev = ki; + KeyVector::iterator ki_last_good_pos = ki; + KeyVector::iterator ki_last_good_rot = ki; + S32 numPosFramesConsidered = 2; + S32 numRotFramesConsidered = 2; + + F32 rot_threshold = ROTATION_KEYFRAME_THRESHOLD / llmax((F32)joint->mChildTreeMaxDepth * 0.33f, 1.f); + + for (; ki != joint->mKeys.end(); ++ki) + { + if (ki_prev == ki_last_good_pos) + { + joint->mNumPosKeys++; + if (dist_vec(LLVector3(ki_prev->mPos), first_frame_pos) > POSITION_MOTION_THRESHOLD) + { + pos_changed = TRUE; + } + } + else + { + //check position for noticeable effect + LLVector3 test_pos(ki_prev->mPos); + LLVector3 last_good_pos(ki_last_good_pos->mPos); + LLVector3 current_pos(ki->mPos); + LLVector3 interp_pos = lerp(current_pos, last_good_pos, 1.f / (F32)numPosFramesConsidered); + + if (dist_vec(current_pos, first_frame_pos) > POSITION_MOTION_THRESHOLD) + { + pos_changed = TRUE; + } + + if (dist_vec(interp_pos, test_pos) < POSITION_KEYFRAME_THRESHOLD) + { + ki_prev->mIgnorePos = TRUE; + numPosFramesConsidered++; + } + else + { + numPosFramesConsidered = 2; + ki_last_good_pos = ki_prev; + joint->mNumPosKeys++; + } + } + + if (ki_prev == ki_last_good_rot) + { + joint->mNumRotKeys++; + LLQuaternion test_rot = mayaQ( ki_prev->mRot[0], ki_prev->mRot[1], ki_prev->mRot[2], order); + F32 x_delta = dist_vec(LLVector3::x_axis * first_frame_rot, LLVector3::x_axis * test_rot); + F32 y_delta = dist_vec(LLVector3::y_axis * first_frame_rot, LLVector3::y_axis * test_rot); + F32 rot_test = x_delta + y_delta; + + if (rot_test > ROTATION_MOTION_THRESHOLD) + { + rot_changed = TRUE; + } + } + else + { + //check rotation for noticeable effect + LLQuaternion test_rot = mayaQ( ki_prev->mRot[0], ki_prev->mRot[1], ki_prev->mRot[2], order); + LLQuaternion last_good_rot = mayaQ( ki_last_good_rot->mRot[0], ki_last_good_rot->mRot[1], ki_last_good_rot->mRot[2], order); + LLQuaternion current_rot = mayaQ( ki->mRot[0], ki->mRot[1], ki->mRot[2], order); + LLQuaternion interp_rot = lerp(1.f / (F32)numRotFramesConsidered, current_rot, last_good_rot); + + F32 x_delta; + F32 y_delta; + F32 rot_test; + + x_delta = dist_vec(LLVector3::x_axis * first_frame_rot, LLVector3::x_axis * test_rot); + y_delta = dist_vec(LLVector3::y_axis * first_frame_rot, LLVector3::y_axis * test_rot); + rot_test = x_delta + y_delta; + + if (rot_test > ROTATION_MOTION_THRESHOLD) + { + rot_changed = TRUE; + } + + x_delta = dist_vec(LLVector3::x_axis * interp_rot, LLVector3::x_axis * test_rot); + y_delta = dist_vec(LLVector3::y_axis * interp_rot, LLVector3::y_axis * test_rot); + rot_test = x_delta + y_delta; + + if (rot_test < rot_threshold) + { + ki_prev->mIgnoreRot = TRUE; + numRotFramesConsidered++; + } + else + { + numRotFramesConsidered = 2; + ki_last_good_rot = ki_prev; + joint->mNumRotKeys++; + } + } + + ki_prev = ki; + } + } + + // don't output joints with no motion + if (!(pos_changed || rot_changed)) + { + //llinfos << "Ignoring joint " << joint->mName << llendl; + joint->mIgnore = TRUE; + } + } +} + +void LLBVHLoader::reset() +{ + mLineNumber = 0; + mNumFrames = 0; + mFrameTime = 0.0f; + mDuration = 0.0f; + + mPriority = 2; + mLoop = FALSE; + mLoopInPoint = 0.f; + mLoopOutPoint = 0.f; + mEaseIn = 0.3f; + mEaseOut = 0.3f; + mHand = 1; + mInitialized = FALSE; + + mEmoteName = ""; +} + +//------------------------------------------------------------------------ +// LLBVHLoader::getLine() +//------------------------------------------------------------------------ +BOOL LLBVHLoader::getLine(apr_file_t* fp) +{ + if (apr_file_eof(fp) == APR_EOF) + { + return FALSE; + } + if ( apr_file_gets(mLine, BVH_PARSER_LINE_SIZE, fp) == APR_SUCCESS) + { + mLineNumber++; + return TRUE; + } + + return FALSE; +} + +// returns required size of output buffer +U32 LLBVHLoader::getOutputSize() +{ + LLDataPackerBinaryBuffer dp; + serialize(dp); + + return dp.getCurrentSize(); +} + +// writes contents to datapacker +BOOL LLBVHLoader::serialize(LLDataPacker& dp) +{ + JointVector::iterator ji; + KeyVector::iterator ki; + F32 time; + + // count number of non-ignored joints + S32 numJoints = 0; + for (ji=mJoints.begin(); ji!=mJoints.end(); ++ji) + { + Joint *joint = *ji; + if ( ! joint->mIgnore ) + numJoints++; + } + + // print header + dp.packU16(KEYFRAME_MOTION_VERSION, "version"); + dp.packU16(KEYFRAME_MOTION_SUBVERSION, "sub_version"); + dp.packS32(mPriority, "base_priority"); + dp.packF32(mDuration, "duration"); + dp.packString(mEmoteName.c_str(), "emote_name"); + dp.packF32(mLoopInPoint, "loop_in_point"); + dp.packF32(mLoopOutPoint, "loop_out_point"); + dp.packS32(mLoop, "loop"); + dp.packF32(mEaseIn, "ease_in_duration"); + dp.packF32(mEaseOut, "ease_out_duration"); + dp.packU32(mHand, "hand_pose"); + dp.packU32(numJoints, "num_joints"); + + for ( ji = mJoints.begin(); + ji != mJoints.end(); + ++ji ) + { + Joint *joint = *ji; + // if ignored, skip it + if ( joint->mIgnore ) + continue; + + LLQuaternion first_frame_rot; + LLQuaternion fixup_rot; + + dp.packString(joint->mOutName.c_str(), "joint_name"); + dp.packS32(joint->mPriority, "joint_priority"); + + // compute coordinate frame rotation + LLQuaternion frameRot( joint->mFrameMatrix ); + LLQuaternion frameRotInv = ~frameRot; + + LLQuaternion offsetRot( joint->mOffsetMatrix ); + + // find mergechild and mergeparent joints, if specified + LLQuaternion mergeParentRot; + LLQuaternion mergeChildRot; + Joint *mergeParent = NULL; + Joint *mergeChild = NULL; + + JointVector::iterator mji; + for (mji=mJoints.begin(); mji!=mJoints.end(); ++mji) + { + Joint *mjoint = *mji; + if ( !joint->mMergeParentName.empty() && (mjoint->mName == joint->mMergeParentName) ) + { + mergeParent = *mji; + } + if ( !joint->mMergeChildName.empty() && (mjoint->mName == joint->mMergeChildName) ) + { + mergeChild = *mji; + } + } + + dp.packS32(joint->mNumRotKeys, "num_rot_keys"); + + LLQuaternion::Order order = bvhStringToOrder( joint->mOrder ); + S32 outcount = 0; + S32 frame = 1; + for ( ki = joint->mKeys.begin(); + ki != joint->mKeys.end(); + ++ki ) + { + if ((frame == 1) && joint->mRelativeRotationKey) + { + first_frame_rot = mayaQ( ki->mRot[0], ki->mRot[1], ki->mRot[2], order); + + fixup_rot.shortestArc(LLVector3::z_axis * first_frame_rot * frameRot, LLVector3::z_axis); + } + + if (ki->mIgnoreRot) + { + frame++; + continue; + } + + time = (F32)frame * mFrameTime; + + if (mergeParent) + { + mergeParentRot = mayaQ( mergeParent->mKeys[frame-1].mRot[0], + mergeParent->mKeys[frame-1].mRot[1], + mergeParent->mKeys[frame-1].mRot[2], + bvhStringToOrder(mergeParent->mOrder) ); + LLQuaternion parentFrameRot( mergeParent->mFrameMatrix ); + LLQuaternion parentOffsetRot( mergeParent->mOffsetMatrix ); + mergeParentRot = ~parentFrameRot * mergeParentRot * parentFrameRot * parentOffsetRot; + } + else + { + mergeParentRot.loadIdentity(); + } + + if (mergeChild) + { + mergeChildRot = mayaQ( mergeChild->mKeys[frame-1].mRot[0], + mergeChild->mKeys[frame-1].mRot[1], + mergeChild->mKeys[frame-1].mRot[2], + bvhStringToOrder(mergeChild->mOrder) ); + LLQuaternion childFrameRot( mergeChild->mFrameMatrix ); + LLQuaternion childOffsetRot( mergeChild->mOffsetMatrix ); + mergeChildRot = ~childFrameRot * mergeChildRot * childFrameRot * childOffsetRot; + + } + else + { + mergeChildRot.loadIdentity(); + } + + LLQuaternion inRot = mayaQ( ki->mRot[0], ki->mRot[1], ki->mRot[2], order); + + LLQuaternion outRot = frameRotInv* mergeChildRot * inRot * mergeParentRot * ~first_frame_rot * frameRot * offsetRot; + + U16 time_short = F32_to_U16(time, 0.f, mDuration); + dp.packU16(time_short, "time"); + U16 x, y, z; + LLVector3 rot_vec = outRot.packToVector3(); + rot_vec.quantize16(-1.f, 1.f, -1.f, 1.f); + x = F32_to_U16(rot_vec.mV[VX], -1.f, 1.f); + y = F32_to_U16(rot_vec.mV[VY], -1.f, 1.f); + z = F32_to_U16(rot_vec.mV[VZ], -1.f, 1.f); + dp.packU16(x, "rot_angle_x"); + dp.packU16(y, "rot_angle_y"); + dp.packU16(z, "rot_angle_z"); + outcount++; + frame++; + } + + // output position keys (only for 1st joint) + if ( ji == mJoints.begin() && !joint->mIgnorePositions ) + { + dp.packS32(joint->mNumPosKeys, "num_pos_keys"); + + LLVector3 relPos = joint->mRelativePosition; + LLVector3 relKey; + + frame = 1; + for ( ki = joint->mKeys.begin(); + ki != joint->mKeys.end(); + ++ki ) + { + if ((frame == 1) && joint->mRelativePositionKey) + { + relKey.setVec(ki->mPos); + } + + if (ki->mIgnorePos) + { + frame++; + continue; + } + + time = (F32)frame * mFrameTime; + + LLVector3 inPos = (LLVector3(ki->mPos) - relKey) * ~first_frame_rot;// * fixup_rot; + LLVector3 outPos = inPos * frameRot * offsetRot; + + outPos *= INCHES_TO_METERS; + + outPos -= relPos; + outPos.clamp(-LL_MAX_PELVIS_OFFSET, LL_MAX_PELVIS_OFFSET); + + U16 time_short = F32_to_U16(time, 0.f, mDuration); + dp.packU16(time_short, "time"); + + U16 x, y, z; + outPos.quantize16(-LL_MAX_PELVIS_OFFSET, LL_MAX_PELVIS_OFFSET, -LL_MAX_PELVIS_OFFSET, LL_MAX_PELVIS_OFFSET); + x = F32_to_U16(outPos.mV[VX], -LL_MAX_PELVIS_OFFSET, LL_MAX_PELVIS_OFFSET); + y = F32_to_U16(outPos.mV[VY], -LL_MAX_PELVIS_OFFSET, LL_MAX_PELVIS_OFFSET); + z = F32_to_U16(outPos.mV[VZ], -LL_MAX_PELVIS_OFFSET, LL_MAX_PELVIS_OFFSET); + dp.packU16(x, "pos_x"); + dp.packU16(y, "pos_y"); + dp.packU16(z, "pos_z"); + + frame++; + } + } + else + { + dp.packS32(0, "num_pos_keys"); + } + } + + S32 num_constraints = (S32)mConstraints.size(); + dp.packS32(num_constraints, "num_constraints"); + + for (ConstraintVector::iterator constraint_it = mConstraints.begin(); + constraint_it != mConstraints.end(); + constraint_it++) + { + U8 byte = constraint_it->mChainLength; + dp.packU8(byte, "chain_lenght"); + + byte = constraint_it->mConstraintType; + dp.packU8(byte, "constraint_type"); + dp.packBinaryDataFixed((U8*)constraint_it->mSourceJointName, 16, "source_volume"); + dp.packVector3(constraint_it->mSourceOffset, "source_offset"); + dp.packBinaryDataFixed((U8*)constraint_it->mTargetJointName, 16, "target_volume"); + dp.packVector3(constraint_it->mTargetOffset, "target_offset"); + dp.packVector3(constraint_it->mTargetDir, "target_dir"); + dp.packF32(constraint_it->mEaseInStart, "ease_in_start"); + dp.packF32(constraint_it->mEaseInStop, "ease_in_stop"); + dp.packF32(constraint_it->mEaseOutStart, "ease_out_start"); + dp.packF32(constraint_it->mEaseOutStop, "ease_out_stop"); + } + + return TRUE; +} diff --git a/indra/llcharacter/llbvhloader.h b/indra/llcharacter/llbvhloader.h new file mode 100644 index 0000000000..7e00e1d5f4 --- /dev/null +++ b/indra/llcharacter/llbvhloader.h @@ -0,0 +1,282 @@ +/** + * @file llbvhloader.h + * @brief Translates a BVH files to LindenLabAnimation format. + * + * Copyright (c) 2004-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLBVHLOADER_H +#define LL_LLBVHLOADER_H + +#include <string> +#include <vector> +#include <map> +#include <stdtypes.h> +#include <stdio.h> +#include "v3math.h" +#include "m3math.h" +#include "llmath.h" +#include "llapr.h" + +const S32 BVH_PARSER_LINE_SIZE = 2048; +const F32 MAX_ANIM_DURATION = 30.f; +class LLDataPacker; + +//------------------------------------------------------------------------ +// FileCloser +//------------------------------------------------------------------------ +class FileCloser +{ +public: + FileCloser( apr_file_t *file ) + { + mFile = file; + } + + ~FileCloser() + { + apr_file_close(mFile); + } +protected: + apr_file_t* mFile; +}; + + +//------------------------------------------------------------------------ +// Key +//------------------------------------------------------------------------ +struct Key +{ + Key() + { + mPos[0] = mPos[1] = mPos[2] = 0.0f; + mRot[0] = mRot[1] = mRot[2] = 0.0f; + mIgnorePos = false; + mIgnoreRot = false; + } + + F32 mPos[3]; + F32 mRot[3]; + BOOL mIgnorePos; + BOOL mIgnoreRot; +}; + + +//------------------------------------------------------------------------ +// KeyVector +//------------------------------------------------------------------------ +typedef std::vector<Key> KeyVector; + +//------------------------------------------------------------------------ +// Joint +//------------------------------------------------------------------------ +struct Joint +{ + Joint(const char *name) + { + mName = name; + mIgnore = FALSE; + mIgnorePositions = FALSE; + mRelativePositionKey = FALSE; + mRelativeRotationKey = FALSE; + mOutName = name; + mOrder[0] = 'X'; + mOrder[1] = 'Y'; + mOrder[2] = 'Z'; + mOrder[3] = 0; + mNumPosKeys = 0; + mNumRotKeys = 0; + mChildTreeMaxDepth = 0; + mPriority = 0; + } + + // Include aligned members first + LLMatrix3 mFrameMatrix; + LLMatrix3 mOffsetMatrix; + LLVector3 mRelativePosition; + // + std::string mName; + BOOL mIgnore; + BOOL mIgnorePositions; + BOOL mRelativePositionKey; + BOOL mRelativeRotationKey; + std::string mOutName; + std::string mMergeParentName; + std::string mMergeChildName; + char mOrder[4]; /* Flawfinder: ignore */ + KeyVector mKeys; + S32 mNumPosKeys; + S32 mNumRotKeys; + S32 mChildTreeMaxDepth; + S32 mPriority; +}; + + +typedef enum e_constraint_type +{ + CONSTRAINT_TYPE_POINT, + CONSTRAINT_TYPE_PLANE +} EConstraintType; + +struct Constraint +{ + char mSourceJointName[16]; /* Flawfinder: ignore */ + char mTargetJointName[16]; /* Flawfinder: ignore */ + S32 mChainLength; + LLVector3 mSourceOffset; + LLVector3 mTargetOffset; + LLVector3 mTargetDir; + F32 mEaseInStart; + F32 mEaseInStop; + F32 mEaseOutStart; + F32 mEaseOutStop; + EConstraintType mConstraintType; +}; + +//------------------------------------------------------------------------ +// JointVector +//------------------------------------------------------------------------ +typedef std::vector<Joint*> JointVector; + +//------------------------------------------------------------------------ +// ConstraintVector +//------------------------------------------------------------------------ +typedef std::vector<Constraint> ConstraintVector; + +//------------------------------------------------------------------------ +// Translation +//------------------------------------------------------------------------ +class Translation +{ +public: + Translation() + { + mIgnore = FALSE; + mRelativePositionKey = FALSE; + mRelativeRotationKey = FALSE; + mPriorityModifier = 0; + } + + std::string mOutName; + BOOL mIgnore; + BOOL mIgnorePositions; + BOOL mRelativePositionKey; + BOOL mRelativeRotationKey; + LLMatrix3 mFrameMatrix; + LLMatrix3 mOffsetMatrix; + LLVector3 mRelativePosition; + std::string mMergeParentName; + std::string mMergeChildName; + S32 mPriorityModifier; +}; + +//------------------------------------------------------------------------ +// TranslationMap +//------------------------------------------------------------------------ +typedef std::map<std::string, Translation> TranslationMap; + +class LLBVHLoader +{ + friend class LLKeyframeMotion; +public: + // Constructor + LLBVHLoader(const char* buffer); + ~LLBVHLoader(); + + // Status Codes + typedef char *Status; + static char *ST_OK; + static char *ST_EOF; + static char *ST_NO_CONSTRAINT; + static char *ST_NO_FILE; + static char *ST_NO_HIER; + static char *ST_NO_JOINT; + static char *ST_NO_NAME; + static char *ST_NO_OFFSET; + static char *ST_NO_CHANNELS; + static char *ST_NO_ROTATION; + static char *ST_NO_AXIS; + static char *ST_NO_MOTION; + static char *ST_NO_FRAMES; + static char *ST_NO_FRAME_TIME; + static char *ST_NO_POS; + static char *ST_NO_ROT; + static char *ST_NO_XLT_FILE; + static char *ST_NO_XLT_HEADER; + static char *ST_NO_XLT_NAME; + static char *ST_NO_XLT_IGNORE; + static char *ST_NO_XLT_RELATIVE; + static char *ST_NO_XLT_OUTNAME; + static char *ST_NO_XLT_MATRIX; + static char *ST_NO_XLT_MERGECHILD; + static char *ST_NO_XLT_MERGEPARENT; + static char *ST_NO_XLT_PRIORITY; + static char *ST_NO_XLT_LOOP; + static char *ST_NO_XLT_EASEIN; + static char *ST_NO_XLT_EASEOUT; + static char *ST_NO_XLT_HAND; + static char *ST_NO_XLT_EMOTE; + + // Loads the specified translation table. + Status loadTranslationTable(const char *fileName); + + // Load the specified BVH file. + // Returns status code. + Status loadBVHFile(const char *buffer, char *error_text, S32 &error_line); + + // Applies translations to BVH data loaded. + void applyTranslations(); + + // Returns the number of lines scanned. + // Useful for error reporting. + S32 getLineNumber() { return mLineNumber; } + + // returns required size of output buffer + U32 getOutputSize(); + + // writes contents to datapacker + BOOL serialize(LLDataPacker& dp); + + // flags redundant keyframe data + void optimize(); + + void reset(); + + F32 getDuration() { return mDuration; } + + BOOL isInitialized() { return mInitialized; } + + Status getStatus() { return mStatus; } + +protected: + // Consumes one line of input from file. + BOOL getLine(apr_file_t *fp); + + // parser state + char mLine[BVH_PARSER_LINE_SIZE]; /* Flawfinder: ignore */ + S32 mLineNumber; + + // parsed values + S32 mNumFrames; + F32 mFrameTime; + JointVector mJoints; + ConstraintVector mConstraints; + TranslationMap mTranslations; + + S32 mPriority; + BOOL mLoop; + F32 mLoopInPoint; + F32 mLoopOutPoint; + F32 mEaseIn; + F32 mEaseOut; + S32 mHand; + std::string mEmoteName; + + BOOL mInitialized; + Status mStatus; + // computed values + F32 mDuration; +}; + +#endif // LL_LLBVHLOADER_H diff --git a/indra/llcharacter/llcharacter.cpp b/indra/llcharacter/llcharacter.cpp new file mode 100644 index 0000000000..ecca9a2526 --- /dev/null +++ b/indra/llcharacter/llcharacter.cpp @@ -0,0 +1,472 @@ +/** + * @file llcharacter.cpp + * @brief Implementation of LLCharacter class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +//----------------------------------------------------------------------------- +// Header Files +//----------------------------------------------------------------------------- + +#include "linden_common.h" + +#include "llcharacter.h" +#include "llstring.h" + +#define SKEL_HEADER "Linden Skeleton 1.0" + +LLStringTable LLCharacter::sVisualParamNames(1024); + +// helper functions +BOOL larger_screen_area( LLCharacter* data_new, LLCharacter* data_tested ) +{ + return data_new->getPixelArea() > data_tested->getPixelArea(); +} + +LLLinkedList< LLCharacter > LLCharacter::sInstances( larger_screen_area ); + + +//----------------------------------------------------------------------------- +// LLCharacter() +// Class Constructor +//----------------------------------------------------------------------------- +LLCharacter::LLCharacter() + : + mPreferredPelvisHeight( 0.f ), + mSex( SEX_FEMALE ), + mAppearanceSerialNum( 0 ), + mSkeletonSerialNum( 0 ) +{ + mMotionController.setCharacter( this ); + sInstances.addData(this); + mPauseRequest = new LLPauseRequestHandle(); +} + + +//----------------------------------------------------------------------------- +// ~LLCharacter() +// Class Destructor +//----------------------------------------------------------------------------- +LLCharacter::~LLCharacter() +{ + for (LLVisualParam *param = getFirstVisualParam(); + param; + param = getNextVisualParam()) + { + delete param; + } + sInstances.removeData(this); +} + + +//----------------------------------------------------------------------------- +// getJoint() +//----------------------------------------------------------------------------- +LLJoint *LLCharacter::getJoint( const std::string &name ) +{ + LLJoint *joint = NULL; + + LLJoint *root = getRootJoint(); + if (root) + { + joint = root->findJoint(name); + } + + if (!joint) + { + llwarns << "Failed to find joint." << llendl; + } + return joint; +} + +//----------------------------------------------------------------------------- +// addMotion() +//----------------------------------------------------------------------------- +BOOL LLCharacter::addMotion( const LLUUID& id, LLMotionConstructor create ) +{ + return mMotionController.addMotion(id, create); +} + +//----------------------------------------------------------------------------- +// removeMotion() +//----------------------------------------------------------------------------- +void LLCharacter::removeMotion( const LLUUID& id ) +{ + mMotionController.removeMotion(id); +} + +//----------------------------------------------------------------------------- +// getMotion() +//----------------------------------------------------------------------------- +LLMotion* LLCharacter::createMotion( const LLUUID &id ) +{ + return mMotionController.createMotion( id ); +} + +//----------------------------------------------------------------------------- +// startMotion() +//----------------------------------------------------------------------------- +BOOL LLCharacter::startMotion(const LLUUID &id, F32 start_offset) +{ + return mMotionController.startMotion(id, start_offset); +} + + +//----------------------------------------------------------------------------- +// stopMotion() +//----------------------------------------------------------------------------- +BOOL LLCharacter::stopMotion(const LLUUID& id, BOOL stop_immediate) +{ + return mMotionController.stopMotionLocally(id, stop_immediate); +} + +//----------------------------------------------------------------------------- +// isMotionActive() +//----------------------------------------------------------------------------- +BOOL LLCharacter::isMotionActive(const LLUUID& id) +{ + LLMotion *motionp = mMotionController.findMotion(id); + if (motionp) + { + return mMotionController.isMotionActive(motionp); + } + + return FALSE; +} + + +//----------------------------------------------------------------------------- +// onDeactivateMotion() +//----------------------------------------------------------------------------- +void LLCharacter::requestStopMotion( LLMotion* motion) +{ +// llinfos << "DEBUG: Char::onDeactivateMotion( " << motionName << " )" << llendl; +} + + +//----------------------------------------------------------------------------- +// updateMotion() +//----------------------------------------------------------------------------- +void LLCharacter::updateMotion(BOOL force_update) +{ + // unpause if we're forcing an update or + // number of outstanding pause requests has dropped + // to the initial one + if (mMotionController.isPaused() && + (force_update || mPauseRequest->getNumRefs() == 1)) + { + mMotionController.unpause(); + } + + mMotionController.updateMotion(); + + // pause once again, after forced update, if there are outstanding + // pause requests + if (force_update && mPauseRequest->getNumRefs() > 1) + { + mMotionController.pause(); + } +} + + +//----------------------------------------------------------------------------- +// flushAllMotions() +//----------------------------------------------------------------------------- +void LLCharacter::flushAllMotions() +{ + mMotionController.flushAllMotions(); +} + + +//----------------------------------------------------------------------------- +// dumpCharacter() +//----------------------------------------------------------------------------- +void LLCharacter::dumpCharacter( LLJoint *joint ) +{ + // handle top level entry into recursion + if (joint == NULL) + { + llinfos << "DEBUG: Dumping Character @" << this << llendl; + dumpCharacter( getRootJoint() ); + llinfos << "DEBUG: Done." << llendl; + return; + } + + // print joint info + llinfos << "DEBUG: " << joint->getName() << " (" << (joint->getParent()?joint->getParent()->getName():std::string("ROOT")) << ")" << llendl; + + // recurse + for ( LLJoint *j = joint->mChildren.getFirstData(); + j != NULL; + j = joint->mChildren.getNextData() ) + { + dumpCharacter(j); + } +} + +//----------------------------------------------------------------------------- +// setAnimationData() +//----------------------------------------------------------------------------- +void LLCharacter::setAnimationData(std::string name, void *data) +{ + if(mAnimationData.getValue(name)) + { + *mAnimationData[name] = data; + } + else + { + mAnimationData.addToHead(name, data); + } +} + +//----------------------------------------------------------------------------- +// getAnimationData() +//----------------------------------------------------------------------------- +void * LLCharacter::getAnimationData(std::string name) +{ + void **result = mAnimationData.getValue(name); + void *return_value; // Necessary to suppress VC6 warning. JC + if (!result) + { + return_value = NULL; + } + else + { + return_value = *result; + } + + return return_value; +} + +//----------------------------------------------------------------------------- +// removeAnimationData() +//----------------------------------------------------------------------------- +void LLCharacter::removeAnimationData(std::string name) +{ + mAnimationData.remove(name); +} + +//----------------------------------------------------------------------------- +// setVisualParamWeight() +//----------------------------------------------------------------------------- +BOOL LLCharacter::setVisualParamWeight(LLVisualParam* which_param, F32 weight, BOOL set_by_user) +{ + S32 index = which_param->getID(); + VisualParamIndexMap_t::iterator index_iter = mVisualParamIndexMap.find(index); + if (index_iter != mVisualParamIndexMap.end()) + { + index_iter->second->setWeight(weight, set_by_user); + return TRUE; + } + return FALSE; +} + +//----------------------------------------------------------------------------- +// setVisualParamWeight() +//----------------------------------------------------------------------------- +BOOL LLCharacter::setVisualParamWeight(const char* param_name, F32 weight, BOOL set_by_user) +{ + LLString tname(param_name); + LLString::toLower(tname); + char *tableptr = sVisualParamNames.checkString(tname); + VisualParamNameMap_t::iterator name_iter = mVisualParamNameMap.find(tableptr); + if (name_iter != mVisualParamNameMap.end()) + { + name_iter->second->setWeight(weight, set_by_user); + return TRUE; + } + llwarns << "LLCharacter::setVisualParamWeight() Invalid visual parameter: " << param_name << llendl; + return FALSE; +} + +//----------------------------------------------------------------------------- +// setVisualParamWeight() +//----------------------------------------------------------------------------- +BOOL LLCharacter::setVisualParamWeight(S32 index, F32 weight, BOOL set_by_user) +{ + VisualParamIndexMap_t::iterator index_iter = mVisualParamIndexMap.find(index); + if (index_iter != mVisualParamIndexMap.end()) + { + index_iter->second->setWeight(weight, set_by_user); + return TRUE; + } + llwarns << "LLCharacter::setVisualParamWeight() Invalid visual parameter index: " << index << llendl; + return FALSE; +} + +//----------------------------------------------------------------------------- +// getVisualParamWeight() +//----------------------------------------------------------------------------- +F32 LLCharacter::getVisualParamWeight(LLVisualParam *which_param) +{ + S32 index = which_param->getID(); + VisualParamIndexMap_t::iterator index_iter = mVisualParamIndexMap.find(index); + if (index_iter != mVisualParamIndexMap.end()) + { + return index_iter->second->getWeight(); + } + else + { + llwarns << "LLCharacter::getVisualParamWeight() Invalid visual parameter*, index= " << index << llendl; + return 0.f; + } +} + +//----------------------------------------------------------------------------- +// getVisualParamWeight() +//----------------------------------------------------------------------------- +F32 LLCharacter::getVisualParamWeight(const char* param_name) +{ + LLString tname(param_name); + LLString::toLower(tname); + char *tableptr = sVisualParamNames.checkString(tname); + VisualParamNameMap_t::iterator name_iter = mVisualParamNameMap.find(tableptr); + if (name_iter != mVisualParamNameMap.end()) + { + return name_iter->second->getWeight(); + } + llwarns << "LLCharacter::getVisualParamWeight() Invalid visual parameter: " << param_name << llendl; + return 0.f; +} + +//----------------------------------------------------------------------------- +// getVisualParamWeight() +//----------------------------------------------------------------------------- +F32 LLCharacter::getVisualParamWeight(S32 index) +{ + VisualParamIndexMap_t::iterator index_iter = mVisualParamIndexMap.find(index); + if (index_iter != mVisualParamIndexMap.end()) + { + return index_iter->second->getWeight(); + } + else + { + llwarns << "LLCharacter::getVisualParamWeight() Invalid visual parameter index: " << index << llendl; + return 0.f; + } +} + +//----------------------------------------------------------------------------- +// clearVisualParamWeights() +//----------------------------------------------------------------------------- +void LLCharacter::clearVisualParamWeights() +{ + for (LLVisualParam *param = getFirstVisualParam(); + param; + param = getNextVisualParam()) + { + if (param->getGroup() == VISUAL_PARAM_GROUP_TWEAKABLE) + { + param->setWeight( param->getDefaultWeight(), FALSE ); + } + } +} + +//----------------------------------------------------------------------------- +// getVisualParam() +//----------------------------------------------------------------------------- +LLVisualParam* LLCharacter::getVisualParam(const char *param_name) +{ + LLString tname(param_name); + LLString::toLower(tname); + char *tableptr = sVisualParamNames.checkString(tname); + VisualParamNameMap_t::iterator name_iter = mVisualParamNameMap.find(tableptr); + if (name_iter != mVisualParamNameMap.end()) + { + return name_iter->second; + } + llwarns << "LLCharacter::getVisualParam() Invalid visual parameter: " << param_name << llendl; + return NULL; +} + +//----------------------------------------------------------------------------- +// addSharedVisualParam() +//----------------------------------------------------------------------------- +void LLCharacter::addSharedVisualParam(LLVisualParam *param) +{ + S32 index = param->getID(); + VisualParamIndexMap_t::iterator index_iter = mVisualParamIndexMap.find(index); + LLVisualParam* current_param = 0; + if (index_iter != mVisualParamIndexMap.end()) + current_param = index_iter->second; + if( current_param ) + { + LLVisualParam* next_param = current_param; + while(next_param->getNextParam()) + { + next_param = next_param->getNextParam(); + } + next_param->setNextParam(param); + } + else + { + llwarns << "Shared visual parameter " << param->getName() << " does not already exist with ID " << + param->getID() << llendl; + } +} + +//----------------------------------------------------------------------------- +// addVisualParam() +//----------------------------------------------------------------------------- +void LLCharacter::addVisualParam(LLVisualParam *param) +{ + S32 index = param->getID(); + // Add Index map + std::pair<VisualParamIndexMap_t::iterator, bool> idxres; + idxres = mVisualParamIndexMap.insert(VisualParamIndexMap_t::value_type(index, param)); + if (!idxres.second) + { + llwarns << "Visual parameter " << param->getName() << " already exists with same ID as " << + param->getName() << llendl; + VisualParamIndexMap_t::iterator index_iter = idxres.first; + index_iter->second = param; + } + + if (param->getInfo()) + { + // Add name map + LLString tname(param->getName()); + LLString::toLower(tname); + char *tableptr = sVisualParamNames.addString(tname); + std::pair<VisualParamNameMap_t::iterator, bool> nameres; + nameres = mVisualParamNameMap.insert(VisualParamNameMap_t::value_type(tableptr, param)); + if (!nameres.second) + { + // Already exists, copy param + VisualParamNameMap_t::iterator name_iter = nameres.first; + name_iter->second = param; + } + } + //llinfos << "Adding Visual Param '" << param->getName() << "' ( " << index << " )" << llendl; +} + +//----------------------------------------------------------------------------- +// updateVisualParams() +//----------------------------------------------------------------------------- +void LLCharacter::updateVisualParams() +{ + for (LLVisualParam *param = getFirstVisualParam(); + param; + param = getNextVisualParam()) + { + if (param->isAnimating()) + { + continue; + } + // only apply parameters whose effective weight has changed + F32 effective_weight = ( param->getSex() & mSex ) ? param->getWeight() : param->getDefaultWeight(); + if (effective_weight != param->getLastWeight()) + { + param->apply( mSex ); + } + } +} + +LLAnimPauseRequest LLCharacter::requestPause() +{ + mMotionController.pause(); + return mPauseRequest; +} + diff --git a/indra/llcharacter/llcharacter.h b/indra/llcharacter/llcharacter.h new file mode 100644 index 0000000000..f2f106f360 --- /dev/null +++ b/indra/llcharacter/llcharacter.h @@ -0,0 +1,252 @@ +/** + * @file llcharacter.h + * @brief Implementation of LLCharacter class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLCHARACTER_H +#define LL_LLCHARACTER_H + +//----------------------------------------------------------------------------- +// Header Files +//----------------------------------------------------------------------------- +#include <string> + +#include "lljoint.h" +#include "llmotioncontroller.h" +#include "llassoclist.h" +#include "llvisualparam.h" +#include "linked_lists.h" +#include "string_table.h" +#include "llmemory.h" + +class LLPolyMesh; + +class LLPauseRequestHandle : public LLThreadSafeRefCount +{ +public: + LLPauseRequestHandle() {}; +}; + +typedef LLPointer<LLPauseRequestHandle> LLAnimPauseRequest; + +//----------------------------------------------------------------------------- +// class LLCharacter +//----------------------------------------------------------------------------- +class LLCharacter +{ +public: + // Constructor + LLCharacter(); + + // Destructor + virtual ~LLCharacter(); + + //------------------------------------------------------------------------- + // LLCharacter Interface + // These functions must be implemented by subclasses. + //------------------------------------------------------------------------- + + // get the prefix to be used to lookup motion data files + // from the viewer data directory + virtual const char *getAnimationPrefix() = 0; + + // get the root joint of the character + virtual LLJoint *getRootJoint() = 0; + + // get the specified joint + // default implementation does recursive search, + // subclasses may optimize/cache results. + virtual LLJoint *getJoint( const std::string &name ); + + // get the position of the character + virtual LLVector3 getCharacterPosition() = 0; + + // get the rotation of the character + virtual LLQuaternion getCharacterRotation() = 0; + + // get the velocity of the character + virtual LLVector3 getCharacterVelocity() = 0; + + // get the angular velocity of the character + virtual LLVector3 getCharacterAngularVelocity() = 0; + + // get the height & normal of the ground under a point + virtual void getGround(const LLVector3 &inPos, LLVector3 &outPos, LLVector3 &outNorm) = 0; + + // allocate an array of joints for the character skeleton + // this must be overloaded to support joint subclasses, + // and is called implicitly from buildSkeleton(). + // Note this must handle reallocation as it will be called + // each time buildSkeleton() is called. + virtual BOOL allocateCharacterJoints( U32 num ) = 0; + + // skeleton joint accessor to support joint subclasses + virtual LLJoint *getCharacterJoint( U32 i ) = 0; + + // get the physics time dilation for the simulator + virtual F32 getTimeDilation() = 0; + + // gets current pixel area of this character + virtual F32 getPixelArea() = 0; + + // gets the head mesh of the character + virtual LLPolyMesh* getHeadMesh() = 0; + + // gets the upper body mesh of the character + virtual LLPolyMesh* getUpperBodyMesh() = 0; + + // gets global coordinates from agent local coordinates + virtual LLVector3d getPosGlobalFromAgent(const LLVector3 &position) = 0; + + // gets agent local coordinates from global coordinates + virtual LLVector3 getPosAgentFromGlobal(const LLVector3d &position) = 0; + + // updates all visual parameters for this character + virtual void updateVisualParams(); + + virtual void addDebugText( const char* text ) = 0; + + virtual const LLUUID& getID() = 0; + //------------------------------------------------------------------------- + // End Interface + //------------------------------------------------------------------------- + // registers a motion with the character + // returns true if successfull + BOOL addMotion( const LLUUID& id, LLMotionConstructor create ); + + void removeMotion( const LLUUID& id ); + + // returns an instance of a registered motion + LLMotion* createMotion( const LLUUID &id ); + + // start a motion + // returns true if successful, false if an error occurred + virtual BOOL startMotion( const LLUUID& id, F32 start_offset = 0.f); + + // stop a motion + virtual BOOL stopMotion( const LLUUID& id, BOOL stop_immediate = FALSE ); + + // is this motion active? + BOOL isMotionActive( const LLUUID& id ); + + // Event handler for motion deactivation. + // Called when a motion has completely stopped and has been deactivated. + // Subclasses may optionally override this. + // The default implementation does nothing. + virtual void requestStopMotion( LLMotion* motion ); + + // periodic update function, steps the motion controller + void updateMotion(BOOL force_update = FALSE); + + LLAnimPauseRequest requestPause(); + BOOL areAnimationsPaused() { return mMotionController.isPaused(); } + void setAnimTimeFactor(F32 factor) { mMotionController.setTimeFactor(factor); } + void setTimeStep(F32 time_step) { mMotionController.setTimeStep(time_step); } + // Releases all motion instances which should result in + // no cached references to character joint data. This is + // useful if a character wants to rebuild it's skeleton. + virtual void flushAllMotions(); + + // dumps information for debugging + virtual void dumpCharacter( LLJoint *joint = NULL ); + + virtual F32 getPreferredPelvisHeight() { return mPreferredPelvisHeight; } + + virtual LLVector3 getVolumePos(S32 joint_index, LLVector3& volume_offset) { return LLVector3::zero; } + + virtual LLJoint* findCollisionVolume(U32 volume_id) { return NULL; } + + virtual S32 getCollisionVolumeID(std::string &name) { return -1; } + + void setAnimationData(std::string name, void *data); + + void *getAnimationData(std::string name); + + void removeAnimationData(std::string name); + + void addVisualParam(LLVisualParam *param); + void addSharedVisualParam(LLVisualParam *param); + + BOOL setVisualParamWeight(LLVisualParam *which_param, F32 weight, BOOL set_by_user = FALSE ); + BOOL setVisualParamWeight(const char* param_name, F32 weight, BOOL set_by_user = FALSE ); + BOOL setVisualParamWeight(S32 index, F32 weight, BOOL set_by_user = FALSE ); + + // get visual param weight by param or name + F32 getVisualParamWeight(LLVisualParam *distortion); + F32 getVisualParamWeight(const char* param_name); + F32 getVisualParamWeight(S32 index); + + // set all morph weights to 0 + void clearVisualParamWeights(); + + // visual parameter accessors + LLVisualParam* getFirstVisualParam() + { + mCurIterator = mVisualParamIndexMap.begin(); + return getNextVisualParam(); + } + LLVisualParam* getNextVisualParam() + { + if (mCurIterator == mVisualParamIndexMap.end()) + return 0; + return (mCurIterator++)->second; + } + + LLVisualParam* getVisualParam(S32 id) + { + VisualParamIndexMap_t::iterator iter = mVisualParamIndexMap.find(id); + return (iter == mVisualParamIndexMap.end()) ? 0 : iter->second; + } + S32 getVisualParamID(LLVisualParam *id) + { + VisualParamIndexMap_t::iterator iter; + for (iter = mVisualParamIndexMap.begin(); iter != mVisualParamIndexMap.end(); iter++) + { + if (iter->second == id) + return iter->first; + } + return 0; + } + S32 getVisualParamCount() { return (S32)mVisualParamIndexMap.size(); } + LLVisualParam* getVisualParam(const char *name); + + + ESex getSex() { return mSex; } + void setSex( ESex sex ) { mSex = sex; } + + U32 getAppearanceSerialNum() const { return mAppearanceSerialNum; } + void setAppearanceSerialNum( U32 num ) { mAppearanceSerialNum = num; } + + U32 getSkeletonSerialNum() const { return mSkeletonSerialNum; } + void setSkeletonSerialNum( U32 num ) { mSkeletonSerialNum = num; } + + static LLLinkedList< LLCharacter > sInstances; + +protected: + LLMotionController mMotionController; + + LLAssocList<std::string, void *> mAnimationData; + + F32 mPreferredPelvisHeight; + ESex mSex; + U32 mAppearanceSerialNum; + U32 mSkeletonSerialNum; + LLAnimPauseRequest mPauseRequest; + + +private: + // visual parameter stuff + typedef std::map<S32, LLVisualParam *> VisualParamIndexMap_t; + VisualParamIndexMap_t mVisualParamIndexMap; + VisualParamIndexMap_t::iterator mCurIterator; + typedef std::map<char *, LLVisualParam *> VisualParamNameMap_t; + VisualParamNameMap_t mVisualParamNameMap; + + static LLStringTable sVisualParamNames; +}; + +#endif // LL_LLCHARACTER_H + diff --git a/indra/llcharacter/lleditingmotion.cpp b/indra/llcharacter/lleditingmotion.cpp new file mode 100644 index 0000000000..f6cfc0ab80 --- /dev/null +++ b/indra/llcharacter/lleditingmotion.cpp @@ -0,0 +1,234 @@ +/** + * @file lleditingmotion.cpp + * @brief Implementation of LLEditingMotion class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +//----------------------------------------------------------------------------- +// Header Files +//----------------------------------------------------------------------------- +#include "linden_common.h" + +#include "lleditingmotion.h" +#include "llcharacter.h" +#include "llhandmotion.h" +#include "llcriticaldamp.h" + +//----------------------------------------------------------------------------- +// Constants +//----------------------------------------------------------------------------- +const LLQuaternion EDIT_MOTION_WRIST_ROTATION(F_PI_BY_TWO * 0.7f, LLVector3(1.0f, 0.0f, 0.0f)); +const F32 TARGET_LAG_HALF_LIFE = 0.1f; // half-life of IK targeting +const F32 TORSO_LAG_HALF_LIFE = 0.2f; +const F32 MAX_TIME_DELTA = 2.f; //max two seconds a frame for calculating interpolation + +S32 LLEditingMotion::sHandPose = LLHandMotion::HAND_POSE_RELAXED_R; +S32 LLEditingMotion::sHandPosePriority = 3; + +//----------------------------------------------------------------------------- +// LLEditingMotion() +// Class Constructor +//----------------------------------------------------------------------------- +LLEditingMotion::LLEditingMotion( const LLUUID &id) : LLMotion(id) +{ + mCharacter = NULL; + + // create kinematic chain + mParentJoint.addChild( &mShoulderJoint ); + mShoulderJoint.addChild( &mElbowJoint ); + mElbowJoint.addChild( &mWristJoint ); + + mName = "editing"; +} + + +//----------------------------------------------------------------------------- +// ~LLEditingMotion() +// Class Destructor +//----------------------------------------------------------------------------- +LLEditingMotion::~LLEditingMotion() +{ +} + +//----------------------------------------------------------------------------- +// LLEditingMotion::onInitialize(LLCharacter *character) +//----------------------------------------------------------------------------- +LLMotion::LLMotionInitStatus LLEditingMotion::onInitialize(LLCharacter *character) +{ + // save character for future use + mCharacter = character; + + // make sure character skeleton is copacetic + if (!mCharacter->getJoint("mShoulderLeft") || + !mCharacter->getJoint("mElbowLeft") || + !mCharacter->getJoint("mWristLeft")) + { + llwarns << "Invalid skeleton for editing motion!" << llendl; + return STATUS_FAILURE; + } + + // get the shoulder, elbow, wrist joints from the character + mParentState.setJoint( mCharacter->getJoint("mShoulderLeft")->getParent() ); + mShoulderState.setJoint( mCharacter->getJoint("mShoulderLeft") ); + mElbowState.setJoint( mCharacter->getJoint("mElbowLeft") ); + mWristState.setJoint( mCharacter->getJoint("mWristLeft") ); + mTorsoState.setJoint( mCharacter->getJoint("mTorso")); + + if ( ! mParentState.getJoint() ) + { + llinfos << getName() << ": Can't get parent joint." << llendl; + return STATUS_FAILURE; + } + + mWristOffset = LLVector3(0.0f, 0.2f, 0.0f); + + // add joint states to the pose + mShoulderState.setUsage(LLJointState::ROT); + mElbowState.setUsage(LLJointState::ROT); + mTorsoState.setUsage(LLJointState::ROT); + mWristState.setUsage(LLJointState::ROT); + addJointState( &mShoulderState ); + addJointState( &mElbowState ); + addJointState( &mTorsoState ); + addJointState( &mWristState ); + + // propagate joint positions to kinematic chain + mParentJoint.setPosition( mParentState.getJoint()->getWorldPosition() ); + mShoulderJoint.setPosition( mShoulderState.getJoint()->getPosition() ); + mElbowJoint.setPosition( mElbowState.getJoint()->getPosition() ); + mWristJoint.setPosition( mWristState.getJoint()->getPosition() + mWristOffset ); + + // propagate current joint rotations to kinematic chain + mParentJoint.setRotation( mParentState.getJoint()->getWorldRotation() ); + mShoulderJoint.setRotation( mShoulderState.getJoint()->getRotation() ); + mElbowJoint.setRotation( mElbowState.getJoint()->getRotation() ); + + // connect the ikSolver to the chain + mIKSolver.setPoleVector( LLVector3( -1.0f, 1.0f, 0.0f ) ); + // specifying the elbow's axis will prevent bad IK for the more + // singular configurations, but the axis is limb-specific -- Leviathan + mIKSolver.setBAxis( LLVector3( -0.682683f, 0.0f, -0.730714f ) ); + mIKSolver.setupJoints( &mShoulderJoint, &mElbowJoint, &mWristJoint, &mTarget ); + + return STATUS_SUCCESS; +} + +//----------------------------------------------------------------------------- +// LLEditingMotion::onActivate() +//----------------------------------------------------------------------------- +BOOL LLEditingMotion::onActivate() +{ + // propagate joint positions to kinematic chain + mParentJoint.setPosition( mParentState.getJoint()->getWorldPosition() ); + mShoulderJoint.setPosition( mShoulderState.getJoint()->getPosition() ); + mElbowJoint.setPosition( mElbowState.getJoint()->getPosition() ); + mWristJoint.setPosition( mWristState.getJoint()->getPosition() + mWristOffset ); + + // propagate current joint rotations to kinematic chain + mParentJoint.setRotation( mParentState.getJoint()->getWorldRotation() ); + mShoulderJoint.setRotation( mShoulderState.getJoint()->getRotation() ); + mElbowJoint.setRotation( mElbowState.getJoint()->getRotation() ); + + return TRUE; +} + +//----------------------------------------------------------------------------- +// LLEditingMotion::onUpdate() +//----------------------------------------------------------------------------- +BOOL LLEditingMotion::onUpdate(F32 time, U8* joint_mask) +{ + LLVector3 focus_pt; + LLVector3* pointAtPt = (LLVector3*)mCharacter->getAnimationData("PointAtPoint"); + + + BOOL result = TRUE; + + if (!pointAtPt) + { + focus_pt = mLastSelectPt; + result = FALSE; + } + else + { + focus_pt = *pointAtPt; + mLastSelectPt = focus_pt; + } + + focus_pt += mCharacter->getCharacterPosition(); + + // propagate joint positions to kinematic chain + mParentJoint.setPosition( mParentState.getJoint()->getWorldPosition() ); + mShoulderJoint.setPosition( mShoulderState.getJoint()->getPosition() ); + mElbowJoint.setPosition( mElbowState.getJoint()->getPosition() ); + mWristJoint.setPosition( mWristState.getJoint()->getPosition() + mWristOffset ); + + // propagate current joint rotations to kinematic chain + mParentJoint.setRotation( mParentState.getJoint()->getWorldRotation() ); + mShoulderJoint.setRotation( mShoulderState.getJoint()->getRotation() ); + mElbowJoint.setRotation( mElbowState.getJoint()->getRotation() ); + + // update target position from character + LLVector3 target = focus_pt - mParentJoint.getPosition(); + F32 target_dist = target.normVec(); + + LLVector3 edit_plane_normal(1.f / F_SQRT2, 1.f / F_SQRT2, 0.f); + edit_plane_normal.normVec(); + + edit_plane_normal.rotVec(mTorsoState.getJoint()->getWorldRotation()); + + F32 dot = edit_plane_normal * target; + + if (dot < 0.f) + { + target = target + (edit_plane_normal * (dot * 2.f)); + target.mV[VZ] += clamp_rescale(dot, 0.f, -1.f, 0.f, 5.f); + target.normVec(); + } + + target = target * target_dist; + if (!target.isFinite()) + { + llerrs << "Non finite target in editing motion with target distance of " << target_dist << + " and focus point " << focus_pt << llendl; + } + + mTarget.setPosition( target + mParentJoint.getPosition()); + +// llinfos << "Point At: " << mTarget.getPosition() << llendl; + + // update the ikSolver + if (!mTarget.getPosition().isExactlyZero()) + { + LLQuaternion shoulderRot = mShoulderJoint.getRotation(); + LLQuaternion elbowRot = mElbowJoint.getRotation(); + mIKSolver.solve(); + + // use blending... + F32 slerp_amt = LLCriticalDamp::getInterpolant(TARGET_LAG_HALF_LIFE); + shoulderRot = slerp(slerp_amt, mShoulderJoint.getRotation(), shoulderRot); + elbowRot = slerp(slerp_amt, mElbowJoint.getRotation(), elbowRot); + + // now put blended values back into joints + llassert(shoulderRot.isFinite()); + llassert(elbowRot.isFinite()); + mShoulderState.setRotation(shoulderRot); + mElbowState.setRotation(elbowRot); + mWristState.setRotation(LLQuaternion::DEFAULT); + } + + mCharacter->setAnimationData("Hand Pose", &sHandPose); + mCharacter->setAnimationData("Hand Pose Priority", &sHandPosePriority); + return result; +} + +//----------------------------------------------------------------------------- +// LLEditingMotion::onDeactivate() +//----------------------------------------------------------------------------- +void LLEditingMotion::onDeactivate() +{ +} + + +// End diff --git a/indra/llcharacter/lleditingmotion.h b/indra/llcharacter/lleditingmotion.h new file mode 100644 index 0000000000..b817a765b2 --- /dev/null +++ b/indra/llcharacter/lleditingmotion.h @@ -0,0 +1,115 @@ +/** + * @file lleditingmotion.h + * @brief Implementation of LLEditingMotion class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLEDITINGMOTION_H +#define LL_LLEDITINGMOTION_H + +//----------------------------------------------------------------------------- +// Header files +//----------------------------------------------------------------------------- +#include "llmotion.h" +#include "lljointsolverrp3.h" +#include "v3dmath.h" + +#define EDITING_EASEIN_DURATION 0.0f +#define EDITING_EASEOUT_DURATION 0.5f +#define EDITING_PRIORITY LLJoint::HIGH_PRIORITY +#define MIN_REQUIRED_PIXEL_AREA_EDITING 500.f + +//----------------------------------------------------------------------------- +// class LLEditingMotion +//----------------------------------------------------------------------------- +class LLEditingMotion : + public LLMotion +{ +public: + // Constructor + LLEditingMotion(const LLUUID &id); + + // Destructor + virtual ~LLEditingMotion(); + +public: + //------------------------------------------------------------------------- + // functions to support MotionController and MotionRegistry + //------------------------------------------------------------------------- + + // static constructor + // all subclasses must implement such a function and register it + static LLMotion *create(const LLUUID &id) { return new LLEditingMotion(id); } + +public: + //------------------------------------------------------------------------- + // animation callbacks to be implemented by subclasses + //------------------------------------------------------------------------- + + // motions must specify whether or not they loop + virtual BOOL getLoop() { return TRUE; } + + // motions must report their total duration + virtual F32 getDuration() { return 0.0; } + + // motions must report their "ease in" duration + virtual F32 getEaseInDuration() { return EDITING_EASEIN_DURATION; } + + // motions must report their "ease out" duration. + virtual F32 getEaseOutDuration() { return EDITING_EASEOUT_DURATION; } + + // motions must report their priority + virtual LLJoint::JointPriority getPriority() { return EDITING_PRIORITY; } + + virtual LLMotionBlendType getBlendType() { return NORMAL_BLEND; } + + // called to determine when a motion should be activated/deactivated based on avatar pixel coverage + virtual F32 getMinPixelArea() { return MIN_REQUIRED_PIXEL_AREA_EDITING; } + + // run-time (post constructor) initialization, + // called after parameters have been set + // must return true to indicate success and be available for activation + virtual LLMotionInitStatus onInitialize(LLCharacter *character); + + // called when a motion is activated + // must return TRUE to indicate success, or else + // it will be deactivated + virtual BOOL onActivate(); + + // called per time step + // must return TRUE while it is active, and + // must return FALSE when the motion is completed. + virtual BOOL onUpdate(F32 time, U8* joint_mask); + + // called when a motion is deactivated + virtual void onDeactivate(); + +public: + //------------------------------------------------------------------------- + // joint states to be animated + //------------------------------------------------------------------------- + LLCharacter *mCharacter; + LLVector3 mWristOffset; + + LLJointState mParentState; + LLJointState mShoulderState; + LLJointState mElbowState; + LLJointState mWristState; + LLJointState mTorsoState; + + LLJoint mParentJoint; + LLJoint mShoulderJoint; + LLJoint mElbowJoint; + LLJoint mWristJoint; + LLJoint mTarget; + LLJointSolverRP3 mIKSolver; + + static S32 sHandPose; + static S32 sHandPosePriority; + LLVector3 mLastSelectPt; +}; + +#endif // LL_LLKEYFRAMEMOTION_H + diff --git a/indra/llcharacter/llgesture.cpp b/indra/llcharacter/llgesture.cpp new file mode 100644 index 0000000000..028c2f7fdf --- /dev/null +++ b/indra/llcharacter/llgesture.cpp @@ -0,0 +1,356 @@ +/** + * @file llgesture.cpp + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "indra_constants.h" + +#include "llgesture.h" +#include "llendianswizzle.h" +#include "message.h" +#include <boost/tokenizer.hpp> + +// for allocating serialization buffers - these need to be updated when members change +const S32 LLGestureList::SERIAL_HEADER_SIZE = sizeof(S32); +const S32 LLGesture::MAX_SERIAL_SIZE = sizeof(KEY) + sizeof(MASK) + 16 + 26 + 41 + 41; + +LLGesture::LLGesture() +: mKey(KEY_NONE), + mMask(MASK_NONE), + mTrigger(), + mTriggerLower(), + mSoundItemID(), + mAnimation(), + mOutputString() +{ } + +LLGesture::LLGesture(KEY key, MASK mask, const std::string &trigger, + const LLUUID &sound_item_id, + const std::string &animation, + const std::string &output_string) +: + mKey(key), + mMask(mask), + mTrigger(trigger), + mTriggerLower(trigger), + mSoundItemID(sound_item_id), + mAnimation(animation), + mOutputString(output_string) +{ + mTriggerLower = utf8str_tolower(mTriggerLower); +} + +LLGesture::LLGesture(U8 **buffer, S32 max_size) +{ + *buffer = deserialize(*buffer, max_size); +} + +LLGesture::LLGesture(const LLGesture &rhs) +{ + mKey = rhs.mKey; + mMask = rhs.mMask; + mTrigger = rhs.mTrigger; + mTriggerLower = rhs.mTriggerLower; + mSoundItemID = rhs.mSoundItemID; + mAnimation = rhs.mAnimation; + mOutputString = rhs.mOutputString; +} + +const LLGesture &LLGesture::operator =(const LLGesture &rhs) +{ + mKey = rhs.mKey; + mMask = rhs.mMask; + mTrigger = rhs.mTrigger; + mTriggerLower = rhs.mTriggerLower; + mSoundItemID = rhs.mSoundItemID; + mAnimation = rhs.mAnimation; + mOutputString = rhs.mOutputString; + return (*this); +} + + +BOOL LLGesture::trigger(KEY key, MASK mask) +{ + llwarns << "Parent class trigger called: you probably didn't mean this." << llendl; + return FALSE; +} + + +BOOL LLGesture::trigger(const LLString &trigger_string) +{ + llwarns << "Parent class trigger called: you probably didn't mean this." << llendl; + return FALSE; +} + +// NOT endian-neutral +U8 *LLGesture::serialize(U8 *buffer) const +{ + htonmemcpy(buffer, &mKey, MVT_S8, 1); + buffer += sizeof(mKey); + htonmemcpy(buffer, &mMask, MVT_U32, 4); + buffer += sizeof(mMask); + htonmemcpy(buffer, mSoundItemID.mData, MVT_LLUUID, 16); + buffer += 16; + + memcpy(buffer, mTrigger.c_str(), mTrigger.length() + 1); /* Flawfinder: ignore */ + buffer += mTrigger.length() + 1; + memcpy(buffer, mAnimation.c_str(), mAnimation.length() + 1); /* Flawfinder: ignore */ + buffer += mAnimation.length() + 1; + memcpy(buffer, mOutputString.c_str(), mOutputString.length() + 1); /* Flawfinder: ignore */ + buffer += mOutputString.length() + 1; + + return buffer; +} + +U8 *LLGesture::deserialize(U8 *buffer, S32 max_size) +{ + U8 *tmp = buffer; + + if (tmp + sizeof(mKey) + sizeof(mMask) + 16 > buffer + max_size) + { + llwarns << "Attempt to read past end of buffer, bad data!!!!" << llendl; + return buffer; + } + + htonmemcpy(&mKey, tmp, MVT_S8, 1); + tmp += sizeof(mKey); + htonmemcpy(&mMask, tmp, MVT_U32, 4); + tmp += sizeof(mMask); + htonmemcpy(mSoundItemID.mData, tmp, MVT_LLUUID, 16); + tmp += 16; + + mTrigger.assign((char *)tmp); + mTriggerLower = mTrigger; + mTriggerLower = utf8str_tolower(mTriggerLower); + tmp += mTrigger.length() + 1; + mAnimation.assign((char *)tmp); + //RN: force animation names to lower case + // must do this for backwards compatibility + mAnimation = utf8str_tolower(mAnimation); + tmp += mAnimation.length() + 1; + mOutputString.assign((char *)tmp); + tmp += mOutputString.length() + 1; + + if (tmp > buffer + max_size) + { + llwarns << "Read past end of buffer, bad data!!!!" << llendl; + return tmp; + } + + return tmp; +} + +S32 LLGesture::getMaxSerialSize() +{ + return MAX_SERIAL_SIZE; +} + +//--------------------------------------------------------------------- +// LLGestureList +//--------------------------------------------------------------------- + +LLGestureList::LLGestureList() +: mList(0) +{ + // add some gestures for debugging +// LLGesture *gesture = NULL; +/* + gesture = new LLGesture(KEY_F2, MASK_NONE, ":-)", + SND_CHIRP, "dance2", ":-)" ); + mList.put(gesture); + + gesture = new LLGesture(KEY_F3, MASK_NONE, "/dance", + SND_OBJECT_CREATE, "dance3", "(dances)" ); + mList.put(gesture); + + gesture = new LLGesture(KEY_F4, MASK_NONE, "/boogie", + LLUUID::null, "dance4", LLString::null ); + mList.put(gesture); + + gesture = new LLGesture(KEY_F5, MASK_SHIFT, "/tongue", + LLUUID::null, "Express_Tongue_Out", LLString::null ); + mList.put(gesture); + */ +} + +LLGestureList::~LLGestureList() +{ + deleteAll(); +} + + +void LLGestureList::deleteAll() +{ + S32 count = mList.count(); + for (S32 i = 0; i < count; i++) + { + delete mList.get(i); + } + mList.reset(); +} + +// Iterates through space delimited tokens in string, triggering any gestures found. +// Generates a revised string that has the found tokens replaced by their replacement strings +// and (as a minor side effect) has multiple spaces in a row replaced by single spaces. +BOOL LLGestureList::triggerAndReviseString(const LLString &string, LLString* revised_string) +{ + LLString tokenized = string; + + BOOL found_gestures = FALSE; + BOOL first_token = TRUE; + + typedef boost::tokenizer<boost::char_separator<char> > tokenizer; + boost::char_separator<char> sep(" "); + tokenizer tokens(string, sep); + tokenizer::iterator token_iter; + + for( token_iter = tokens.begin(); token_iter != tokens.end(); ++token_iter) + { + LLGesture* gesture = NULL; + + if( !found_gestures ) // Only pay attention to the first gesture in the string. + { + LLString cur_token_lower = *token_iter; + LLString::toLower(cur_token_lower); + + for (S32 i = 0; i < mList.count(); i++) + { + gesture = mList.get(i); + if (gesture->trigger(cur_token_lower)) + { + if( !gesture->getOutputString().empty() ) + { + if( !first_token ) + { + revised_string->append( " " ); + } + + // Don't muck with the user's capitalization if we don't have to. + const std::string& output = gesture->getOutputString(); + LLString output_lower = LLString(output.c_str()); + LLString::toLower(output_lower); + if( cur_token_lower == output_lower ) + { + revised_string->append(*token_iter); + } + else + { + revised_string->append(output.c_str()); + } + + } + found_gestures = TRUE; + break; + } + gesture = NULL; + } + } + + if( !gesture ) + { + if( !first_token ) + { + revised_string->append( " " ); + } + revised_string->append( *token_iter ); + } + + first_token = FALSE; + } + return found_gestures; +} + + + +BOOL LLGestureList::trigger(KEY key, MASK mask) +{ + for (S32 i = 0; i < mList.count(); i++) + { + LLGesture* gesture = mList.get(i); + if( gesture ) + { + if (gesture->trigger(key, mask)) + { + return TRUE; + } + } + else + { + llwarns << "NULL gesture in gesture list (" << i << ")" << llendl + } + } + return FALSE; +} + +// NOT endian-neutral +U8 *LLGestureList::serialize(U8 *buffer) const +{ + // a single S32 serves as the header that tells us how many to read + S32 count = mList.count(); + htonmemcpy(buffer, &count, MVT_S32, 4); + buffer += sizeof(count); + + for (S32 i = 0; i < count; i++) + { + buffer = mList[i]->serialize(buffer); + } + + return buffer; +} + +const S32 MAX_GESTURES = 4096; + +U8 *LLGestureList::deserialize(U8 *buffer, S32 max_size) +{ + deleteAll(); + + S32 count; + U8 *tmp = buffer; + + if (tmp + sizeof(count) > buffer + max_size) + { + llwarns << "Invalid max_size" << llendl; + return buffer; + } + + htonmemcpy(&count, tmp, MVT_S32, 4); + + if (count > MAX_GESTURES) + { + llwarns << "Unreasonably large gesture list count in deserialize: " << count << llendl; + return tmp; + } + + tmp += sizeof(count); + + mList.reserve_block(count); + + for (S32 i = 0; i < count; i++) + { + mList[i] = create_gesture(&tmp, max_size - (S32)(tmp - buffer)); + if (tmp - buffer > max_size) + { + llwarns << "Deserialization read past end of buffer, bad data!!!!" << llendl; + return tmp; + } + } + + return tmp; +} + +// this is a helper for deserialize +// it gets overridden by LLViewerGestureList to create LLViewerGestures +// overridden by child class to use local LLGesture implementation +LLGesture *LLGestureList::create_gesture(U8 **buffer, S32 max_size) +{ + return new LLGesture(buffer, max_size); +} + +S32 LLGestureList::getMaxSerialSize() +{ + return SERIAL_HEADER_SIZE + (count() * LLGesture::getMaxSerialSize()); +} diff --git a/indra/llcharacter/llgesture.h b/indra/llcharacter/llgesture.h new file mode 100644 index 0000000000..64c752769b --- /dev/null +++ b/indra/llcharacter/llgesture.h @@ -0,0 +1,96 @@ +/** + * @file llgesture.h + * @brief A gesture is a combination of a triggering chat phrase or + * key, a sound, an animation, and a chat string. + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLGESTURE_H +#define LL_LLGESTURE_H + +#include "llanimationstates.h" +#include "lluuid.h" +#include "llstring.h" +#include "lldarray.h" + +class LLGesture +{ +public: + LLGesture(); + LLGesture(KEY key, MASK mask, const std::string &trigger, + const LLUUID &sound_item_id, const std::string &animation, + const std::string &output_string); + + LLGesture(U8 **buffer, S32 max_size); // deserializes, advances buffer + LLGesture(const LLGesture &gesture); + const LLGesture &operator=(const LLGesture &rhs); + + virtual ~LLGesture() {}; + + // Accessors + KEY getKey() const { return mKey; } + MASK getMask() const { return mMask; } + const std::string& getTrigger() const { return mTrigger; } + const LLUUID& getSound() const { return mSoundItemID; } + const std::string& getAnimation() const { return mAnimation; } + const std::string& getOutputString() const { return mOutputString; } + + // Triggers if a key/mask matches it + virtual BOOL trigger(KEY key, MASK mask); + + // Triggers if case-insensitive substring matches (assumes string is lowercase) + virtual BOOL trigger(const LLString &string); + + // non-endian-neutral serialization + U8 *serialize(U8 *buffer) const; + U8 *deserialize(U8 *buffer, S32 max_size); + static S32 getMaxSerialSize(); + +protected: + KEY mKey; // usually a function key + MASK mMask; // usually MASK_NONE, or MASK_SHIFT + std::string mTrigger; // string, no whitespace allowed + std::string mTriggerLower; // lowercase version of mTrigger + LLUUID mSoundItemID; // ItemID of sound to play, LLUUID::null if none + std::string mAnimation; // canonical name of animation or face animation + std::string mOutputString; // string to say + + static const S32 MAX_SERIAL_SIZE; +}; + +class LLGestureList +{ +public: + LLGestureList(); + virtual ~LLGestureList(); + + // Triggers if a key/mask matches one in the list + BOOL trigger(KEY key, MASK mask); + + // Triggers if substring matches and generates revised string. + BOOL triggerAndReviseString(const LLString &string, LLString* revised_string); + + // Used for construction from UI + S32 count() const { return mList.count(); } + virtual LLGesture* get(S32 i) const { return mList.get(i); } + virtual void put(LLGesture* gesture) { mList.put( gesture ); } + void deleteAll(); + + // non-endian-neutral serialization + U8 *serialize(U8 *buffer) const; + U8 *deserialize(U8 *buffer, S32 max_size); + S32 getMaxSerialSize(); + +protected: + // overridden by child class to use local LLGesture implementation + virtual LLGesture *create_gesture(U8 **buffer, S32 max_size); + +protected: + LLDynamicArray<LLGesture*> mList; + + static const S32 SERIAL_HEADER_SIZE; +}; + +#endif diff --git a/indra/llcharacter/llhandmotion.cpp b/indra/llcharacter/llhandmotion.cpp new file mode 100644 index 0000000000..d574363f83 --- /dev/null +++ b/indra/llcharacter/llhandmotion.cpp @@ -0,0 +1,202 @@ +/** + * @file llhandmotion.cpp + * @brief Implementation of LLHandMotion class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +//----------------------------------------------------------------------------- +// Header Files +//----------------------------------------------------------------------------- +#include "linden_common.h" + +#include "llhandmotion.h" +#include "llcharacter.h" +#include "m3math.h" + +//----------------------------------------------------------------------------- +// Constants +//----------------------------------------------------------------------------- + +const char *gHandPoseNames[LLHandMotion::NUM_HAND_POSES] = /* Flawfinder: ignore */ +{ + "", + "Hands_Relaxed", + "Hands_Point", + "Hands_Fist", + "Hands_Relaxed_L", + "Hands_Point_L", + "Hands_Fist_L", + "Hands_Relaxed_R", + "Hands_Point_R", + "Hands_Fist_R", + "Hands_Salute_R", + "Hands_Typing", + "Hands_Peace_R", + "Hands_Spread_R" +}; + +const F32 HAND_MORPH_BLEND_TIME = 0.2f; + +//----------------------------------------------------------------------------- +// LLHandMotion() +// Class Constructor +//----------------------------------------------------------------------------- +LLHandMotion::LLHandMotion(const LLUUID &id) : LLMotion(id) +{ + mCharacter = NULL; + mLastTime = 0.f; + mCurrentPose = HAND_POSE_RELAXED; + mNewPose = HAND_POSE_RELAXED; + mName = "hand_motion"; + + //RN: flag hand joint as highest priority for now, until we implement a proper animation track + mJointSignature[0][LL_HAND_JOINT_NUM] = 0xff; + mJointSignature[1][LL_HAND_JOINT_NUM] = 0xff; + mJointSignature[2][LL_HAND_JOINT_NUM] = 0xff; +} + + +//----------------------------------------------------------------------------- +// ~LLHandMotion() +// Class Destructor +//----------------------------------------------------------------------------- +LLHandMotion::~LLHandMotion() +{ +} + +//----------------------------------------------------------------------------- +// LLHandMotion::onInitialize(LLCharacter *character) +//----------------------------------------------------------------------------- +LLMotion::LLMotionInitStatus LLHandMotion::onInitialize(LLCharacter *character) +{ + mCharacter = character; + + return STATUS_SUCCESS; +} + + +//----------------------------------------------------------------------------- +// LLHandMotion::onActivate() +//----------------------------------------------------------------------------- +BOOL LLHandMotion::onActivate() +{ + LLPolyMesh *upperBodyMesh = mCharacter->getUpperBodyMesh(); + + if (upperBodyMesh) + { + // Note: 0 is the default + for (S32 i = 1; i < LLHandMotion::NUM_HAND_POSES; i++) + { + mCharacter->setVisualParamWeight(gHandPoseNames[i], 0.f); + } + mCharacter->setVisualParamWeight(gHandPoseNames[mCurrentPose], 1.f); + mCharacter->updateVisualParams(); + } + return TRUE; +} + + +//----------------------------------------------------------------------------- +// LLHandMotion::onUpdate() +//----------------------------------------------------------------------------- +BOOL LLHandMotion::onUpdate(F32 time, U8* joint_mask) +{ + eHandPose *requestedHandPose; + + F32 timeDelta = time - mLastTime; + mLastTime = time; + + requestedHandPose = (eHandPose *)mCharacter->getAnimationData("Hand Pose"); + // check to see if requested pose has changed + if (!requestedHandPose) + { + if (mNewPose != HAND_POSE_RELAXED && mNewPose != mCurrentPose) + { + mCharacter->setVisualParamWeight(gHandPoseNames[mNewPose], 0.f); + } + mNewPose = HAND_POSE_RELAXED; + } + else + { + // this is a new morph we didn't know about before + if (*requestedHandPose != mNewPose && mNewPose != mCurrentPose && mNewPose != HAND_POSE_SPREAD) + { + mCharacter->setVisualParamWeight(gHandPoseNames[mNewPose], 0.f); + } + mNewPose = *requestedHandPose; + } + + mCharacter->removeAnimationData("Hand Pose"); + mCharacter->removeAnimationData("Hand Pose Priority"); + +// if (requestedHandPose) +// llinfos << "Hand Pose " << *requestedHandPose << llendl; + + // if we are still blending... + if (mCurrentPose != mNewPose) + { + F32 incomingWeight = 1.f; + F32 outgoingWeight = 0.f; + + if (mNewPose != HAND_POSE_SPREAD) + { + incomingWeight = mCharacter->getVisualParamWeight(gHandPoseNames[mNewPose]); + incomingWeight += (timeDelta / HAND_MORPH_BLEND_TIME); + incomingWeight = llclamp(incomingWeight, 0.f, 1.f); + mCharacter->setVisualParamWeight(gHandPoseNames[mNewPose], incomingWeight); + } + + if (mCurrentPose != HAND_POSE_SPREAD) + { + outgoingWeight = mCharacter->getVisualParamWeight(gHandPoseNames[mCurrentPose]); + outgoingWeight -= (timeDelta / HAND_MORPH_BLEND_TIME); + outgoingWeight = llclamp(outgoingWeight, 0.f, 1.f); + mCharacter->setVisualParamWeight(gHandPoseNames[mCurrentPose], outgoingWeight); + } + + mCharacter->updateVisualParams(); + + if (incomingWeight == 1.f && outgoingWeight == 0.f) + { + mCurrentPose = mNewPose; + } + } + + return TRUE; +} + + +//----------------------------------------------------------------------------- +// LLHandMotion::onDeactivate() +//----------------------------------------------------------------------------- +void LLHandMotion::onDeactivate() +{ +} + +//----------------------------------------------------------------------------- +// LLHandMotion::getHandPoseName() +//----------------------------------------------------------------------------- +LLString LLHandMotion::getHandPoseName(eHandPose pose) +{ + if ((S32)pose < LLHandMotion::NUM_HAND_POSES && (S32)pose >= 0) + { + return gHandPoseNames[pose]; + } + return ""; +} + +LLHandMotion::eHandPose LLHandMotion::getHandPose(LLString posename) +{ + for (S32 pose = 0; pose < LLHandMotion::NUM_HAND_POSES; ++pose) + { + if (gHandPoseNames[pose] == posename) + { + return (eHandPose)pose; + } + } + return (eHandPose)0; +} + +// End diff --git a/indra/llcharacter/llhandmotion.h b/indra/llcharacter/llhandmotion.h new file mode 100644 index 0000000000..cef7361633 --- /dev/null +++ b/indra/llcharacter/llhandmotion.h @@ -0,0 +1,119 @@ +/** + * @file llhandmotion.h + * @brief Implementation of LLHandMotion class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLHANDMOTION_H +#define LL_LLHANDMOTION_H + +//----------------------------------------------------------------------------- +// Header files +//----------------------------------------------------------------------------- +#include "llmotion.h" +#include "lltimer.h" + +#define MIN_REQUIRED_PIXEL_AREA_HAND 10000.f; + +//----------------------------------------------------------------------------- +// class LLHandMotion +//----------------------------------------------------------------------------- +class LLHandMotion : + public LLMotion +{ +public: + typedef enum e_hand_pose + { + HAND_POSE_SPREAD, + HAND_POSE_RELAXED, + HAND_POSE_POINT, + HAND_POSE_FIST, + HAND_POSE_RELAXED_L, + HAND_POSE_POINT_L, + HAND_POSE_FIST_L, + HAND_POSE_RELAXED_R, + HAND_POSE_POINT_R, + HAND_POSE_FIST_R, + HAND_POSE_SALUTE_R, + HAND_POSE_TYPING, + HAND_POSE_PEACE_R, + HAND_POSE_PALM_R, + NUM_HAND_POSES + } eHandPose; + + // Constructor + LLHandMotion(const LLUUID &id); + + // Destructor + virtual ~LLHandMotion(); + +public: + //------------------------------------------------------------------------- + // functions to support MotionController and MotionRegistry + //------------------------------------------------------------------------- + + // static constructor + // all subclasses must implement such a function and register it + static LLMotion *create(const LLUUID &id) { return new LLHandMotion(id); } + +public: + //------------------------------------------------------------------------- + // animation callbacks to be implemented by subclasses + //------------------------------------------------------------------------- + + // motions must specify whether or not they loop + virtual BOOL getLoop() { return TRUE; } + + // motions must report their total duration + virtual F32 getDuration() { return 0.0; } + + // motions must report their "ease in" duration + virtual F32 getEaseInDuration() { return 0.0; } + + // motions must report their "ease out" duration. + virtual F32 getEaseOutDuration() { return 0.0; } + + // called to determine when a motion should be activated/deactivated based on avatar pixel coverage + virtual F32 getMinPixelArea() { return MIN_REQUIRED_PIXEL_AREA_HAND; } + + // motions must report their priority + virtual LLJoint::JointPriority getPriority() { return LLJoint::MEDIUM_PRIORITY; } + + virtual LLMotionBlendType getBlendType() { return NORMAL_BLEND; } + + // run-time (post constructor) initialization, + // called after parameters have been set + // must return true to indicate success and be available for activation + virtual LLMotionInitStatus onInitialize(LLCharacter *character); + + // called when a motion is activated + // must return TRUE to indicate success, or else + // it will be deactivated + virtual BOOL onActivate(); + + // called per time step + // must return TRUE while it is active, and + // must return FALSE when the motion is completed. + virtual BOOL onUpdate(F32 time, U8* joint_mask); + + // called when a motion is deactivated + virtual void onDeactivate(); + + static LLString getHandPoseName(eHandPose pose); + static eHandPose getHandPose(LLString posename); + +public: + //------------------------------------------------------------------------- + // joint states to be animated + //------------------------------------------------------------------------- + + LLCharacter *mCharacter; + + F32 mLastTime; + eHandPose mCurrentPose; + eHandPose mNewPose; +}; +#endif // LL_LLHANDMOTION_H + diff --git a/indra/llcharacter/llheadrotmotion.cpp b/indra/llcharacter/llheadrotmotion.cpp new file mode 100644 index 0000000000..4ff7e6b582 --- /dev/null +++ b/indra/llcharacter/llheadrotmotion.cpp @@ -0,0 +1,505 @@ +/** + * @file llheadrotmotion.cpp + * @brief Implementation of LLHeadRotMotion class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +//----------------------------------------------------------------------------- +// Header Files +//----------------------------------------------------------------------------- +#include "linden_common.h" + +#include "llheadrotmotion.h" +#include "llcharacter.h" +#include "llrand.h" +#include "m3math.h" +#include "v3dmath.h" +#include "llcriticaldamp.h" + +//----------------------------------------------------------------------------- +// Constants +//----------------------------------------------------------------------------- +const F32 TORSO_LAG = 0.35f; // torso rotation factor +const F32 NECK_LAG = 0.5f; // neck rotation factor +const F32 HEAD_LOOKAT_LAG_HALF_LIFE = 0.15f; // half-life of lookat targeting for head +const F32 TORSO_LOOKAT_LAG_HALF_LIFE = 0.27f; // half-life of lookat targeting for torso +const F32 EYE_LOOKAT_LAG_HALF_LIFE = 0.06f; // half-life of lookat targeting for eye +const F32 HEAD_ROTATION_CONSTRAINT = F_PI_BY_TWO * 0.8f; // limit angle for head rotation + +const F32 MIN_HEAD_LOOKAT_DISTANCE = 0.3f; // minimum distance from head before we turn to look at it +const F32 MAX_TIME_DELTA = 2.f; //max two seconds a frame for calculating interpolation +const F32 EYE_JITTER_MIN_TIME = 0.3f; // min amount of time between eye "jitter" motions +const F32 EYE_JITTER_MAX_TIME = 2.5f; // max amount of time between eye "jitter" motions +const F32 EYE_JITTER_MAX_YAW = 0.08f; // max yaw of eye jitter motion +const F32 EYE_JITTER_MAX_PITCH = 0.015f; // max pitch of eye jitter motion +const F32 EYE_LOOK_AWAY_MIN_TIME = 5.f; // min amount of time between eye "look away" motions +const F32 EYE_LOOK_AWAY_MAX_TIME = 15.f; // max amount of time between eye "look away" motions +const F32 EYE_LOOK_BACK_MIN_TIME = 1.f; // min amount of time before looking back after looking away +const F32 EYE_LOOK_BACK_MAX_TIME = 5.f; // max amount of time before looking back after looking away +const F32 EYE_LOOK_AWAY_MAX_YAW = 0.15f; // max yaw of eye look away motion +const F32 EYE_LOOK_AWAY_MAX_PITCH = 0.12f; // max pitch of look away motion +const F32 EYE_ROT_LIMIT_ANGLE = F_PI_BY_TWO * 0.3f; //max angle in radians for eye rotation + +const F32 EYE_BLINK_MIN_TIME = 0.5f; // minimum amount of time between blinks +const F32 EYE_BLINK_MAX_TIME = 8.f; // maximum amount of time between blinks +const F32 EYE_BLINK_CLOSE_TIME = 0.03f; // how long the eye stays closed in a blink +const F32 EYE_BLINK_SPEED = 0.015f; // seconds it takes for a eye open/close movement +const F32 EYE_BLINK_TIME_DELTA = 0.005f; // time between one eye starting a blink and the other following + +//----------------------------------------------------------------------------- +// LLHeadRotMotion() +// Class Constructor +//----------------------------------------------------------------------------- +LLHeadRotMotion::LLHeadRotMotion(const LLUUID &id) : + LLMotion(id), + mCharacter(NULL), + mTorsoJoint(NULL), + mHeadJoint(NULL) +{ + mName = "head_rot"; +} + + +//----------------------------------------------------------------------------- +// ~LLHeadRotMotion() +// Class Destructor +//----------------------------------------------------------------------------- +LLHeadRotMotion::~LLHeadRotMotion() +{ +} + +//----------------------------------------------------------------------------- +// LLHeadRotMotion::onInitialize(LLCharacter *character) +//----------------------------------------------------------------------------- +LLMotion::LLMotionInitStatus LLHeadRotMotion::onInitialize(LLCharacter *character) +{ + if (!character) + return STATUS_FAILURE; + mCharacter = character; + + mPelvisJoint = character->getJoint("mPelvis"); + if ( ! mPelvisJoint ) + { + llinfos << getName() << ": Can't get pelvis joint." << llendl; + return STATUS_FAILURE; + } + + mRootJoint = character->getJoint("mRoot"); + if ( ! mRootJoint ) + { + llinfos << getName() << ": Can't get root joint." << llendl; + return STATUS_FAILURE; + } + + mTorsoJoint = character->getJoint("mTorso"); + if ( ! mTorsoJoint ) + { + llinfos << getName() << ": Can't get torso joint." << llendl; + return STATUS_FAILURE; + } + + mHeadJoint = character->getJoint("mHead"); + if ( ! mHeadJoint ) + { + llinfos << getName() << ": Can't get head joint." << llendl; + return STATUS_FAILURE; + } + + mTorsoState.setJoint( character->getJoint("mTorso") ); + if ( ! mTorsoState.getJoint() ) + { + llinfos << getName() << ": Can't get torso joint." << llendl; + return STATUS_FAILURE; + } + + mNeckState.setJoint( character->getJoint("mNeck") ); + if ( ! mNeckState.getJoint() ) + { + llinfos << getName() << ": Can't get neck joint." << llendl; + return STATUS_FAILURE; + } + + mHeadState.setJoint( character->getJoint("mHead") ); + if ( ! mHeadState.getJoint() ) + { + llinfos << getName() << ": Can't get head joint." << llendl; + return STATUS_FAILURE; + } + + mTorsoState.setUsage(LLJointState::ROT); + mNeckState.setUsage(LLJointState::ROT); + mHeadState.setUsage(LLJointState::ROT); + + addJointState( &mTorsoState ); + addJointState( &mNeckState ); + addJointState( &mHeadState ); + + mLastHeadRot.loadIdentity(); + + return STATUS_SUCCESS; +} + + +//----------------------------------------------------------------------------- +// LLHeadRotMotion::onActivate() +//----------------------------------------------------------------------------- +BOOL LLHeadRotMotion::onActivate() +{ + return TRUE; +} + + +//----------------------------------------------------------------------------- +// LLHeadRotMotion::onUpdate() +//----------------------------------------------------------------------------- +BOOL LLHeadRotMotion::onUpdate(F32 time, U8* joint_mask) +{ + LLQuaternion targetHeadRotWorld; + LLQuaternion currentRootRotWorld = mRootJoint->getWorldRotation(); + LLQuaternion currentInvRootRotWorld = ~currentRootRotWorld; + + F32 head_slerp_amt = LLCriticalDamp::getInterpolant(HEAD_LOOKAT_LAG_HALF_LIFE); + F32 torso_slerp_amt = LLCriticalDamp::getInterpolant(TORSO_LOOKAT_LAG_HALF_LIFE); + + LLVector3* targetPos = (LLVector3*)mCharacter->getAnimationData("LookAtPoint"); + + if (targetPos) + { + LLVector3 headLookAt = *targetPos; + +// llinfos << "Look At: " << headLookAt + mHeadJoint->getWorldPosition() << llendl; + + F32 lookatDistance = headLookAt.normVec(); + + if (lookatDistance < MIN_HEAD_LOOKAT_DISTANCE) + { + targetHeadRotWorld = mPelvisJoint->getWorldRotation(); + } + else + { + LLVector3 root_up = LLVector3(0.f, 0.f, 1.f) * currentRootRotWorld; + LLVector3 left(root_up % headLookAt); + // if look_at has zero length, fail + // if look_at and skyward are parallel, fail + // + // Test both of these conditions with a cross product. + + if (left.magVecSquared() < 0.15f) + { + LLVector3 root_at = LLVector3(1.f, 0.f, 0.f) * currentRootRotWorld; + root_at.mV[VZ] = 0.f; + root_at.normVec(); + + headLookAt = lerp(headLookAt, root_at, 0.4f); + headLookAt.normVec(); + + left = root_up % headLookAt; + } + + // Make sure look_at and skyward and not parallel + // and neither are zero length + LLVector3 up(headLookAt % left); + + targetHeadRotWorld = LLQuaternion(headLookAt, left, up); + } + } + else + { + targetHeadRotWorld = currentRootRotWorld; + } + + LLQuaternion head_rot_local = targetHeadRotWorld * currentInvRootRotWorld; + head_rot_local.constrain(HEAD_ROTATION_CONSTRAINT); + + // set final torso rotation + // Set torso target rotation such that it lags behind the head rotation + // by a fixed amount. + LLQuaternion torso_rot_local = nlerp(TORSO_LAG, LLQuaternion::DEFAULT, head_rot_local ); + mTorsoState.setRotation( nlerp(torso_slerp_amt, mTorsoState.getRotation(), torso_rot_local) ); + + head_rot_local = nlerp(head_slerp_amt, mLastHeadRot, head_rot_local); + mLastHeadRot = head_rot_local; + + // Set the head rotation. + LLQuaternion torsoRotLocal = mNeckState.getJoint()->getParent()->getWorldRotation() * currentInvRootRotWorld; + head_rot_local = head_rot_local * ~torsoRotLocal; + mNeckState.setRotation( nlerp(NECK_LAG, LLQuaternion::DEFAULT, head_rot_local) ); + mHeadState.setRotation( nlerp(1.f - NECK_LAG, LLQuaternion::DEFAULT, head_rot_local)); + + return TRUE; +} + + +//----------------------------------------------------------------------------- +// LLHeadRotMotion::onDeactivate() +//----------------------------------------------------------------------------- +void LLHeadRotMotion::onDeactivate() +{ +} + + +//----------------------------------------------------------------------------- +// LLEyeMotion() +// Class Constructor +//----------------------------------------------------------------------------- +LLEyeMotion::LLEyeMotion(const LLUUID &id) : LLMotion(id) +{ + mCharacter = NULL; + mEyeJitterTime = 0.f; + mEyeJitterYaw = 0.f; + mEyeJitterPitch = 0.f; + + mEyeLookAwayTime = 0.f; + mEyeLookAwayYaw = 0.f; + mEyeLookAwayPitch = 0.f; + + mEyeBlinkTime = 0.f; + mEyesClosed = FALSE; + + mHeadJoint = NULL; + + mName = "eye_rot"; +} + + +//----------------------------------------------------------------------------- +// ~LLEyeMotion() +// Class Destructor +//----------------------------------------------------------------------------- +LLEyeMotion::~LLEyeMotion() +{ +} + +//----------------------------------------------------------------------------- +// LLEyeMotion::onInitialize(LLCharacter *character) +//----------------------------------------------------------------------------- +LLMotion::LLMotionInitStatus LLEyeMotion::onInitialize(LLCharacter *character) +{ + mCharacter = character; + + mHeadJoint = character->getJoint("mHead"); + if ( ! mHeadJoint ) + { + llinfos << getName() << ": Can't get head joint." << llendl; + return STATUS_FAILURE; + } + + mLeftEyeState.setJoint( character->getJoint("mEyeLeft") ); + if ( ! mLeftEyeState.getJoint() ) + { + llinfos << getName() << ": Can't get left eyeball joint." << llendl; + return STATUS_FAILURE; + } + + mRightEyeState.setJoint( character->getJoint("mEyeRight") ); + if ( ! mRightEyeState.getJoint() ) + { + llinfos << getName() << ": Can't get Right eyeball joint." << llendl; + return STATUS_FAILURE; + } + + mLeftEyeState.setUsage(LLJointState::ROT); + mRightEyeState.setUsage(LLJointState::ROT); + + addJointState( &mLeftEyeState ); + addJointState( &mRightEyeState ); + + return STATUS_SUCCESS; +} + + +//----------------------------------------------------------------------------- +// LLEyeMotion::onActivate() +//----------------------------------------------------------------------------- +BOOL LLEyeMotion::onActivate() +{ + return TRUE; +} + + +//----------------------------------------------------------------------------- +// LLEyeMotion::onUpdate() +//----------------------------------------------------------------------------- +BOOL LLEyeMotion::onUpdate(F32 time, U8* joint_mask) +{ + // Compute eye rotation. + LLQuaternion target_eye_rot; + LLVector3 eye_look_at; + F32 vergence; + + //calculate jitter + if (mEyeJitterTimer.getElapsedTimeF32() > mEyeJitterTime) + { + mEyeJitterTime = EYE_JITTER_MIN_TIME + frand(EYE_JITTER_MAX_TIME - EYE_JITTER_MIN_TIME); + mEyeJitterYaw = (frand(2.f) - 1.f) * EYE_JITTER_MAX_YAW; + mEyeJitterPitch = (frand(2.f) - 1.f) * EYE_JITTER_MAX_PITCH; + // make sure lookaway time count gets updated, because we're resetting the timer + mEyeLookAwayTime -= llmax(0.f, mEyeJitterTimer.getElapsedTimeF32()); + mEyeJitterTimer.reset(); + } + else if (mEyeJitterTimer.getElapsedTimeF32() > mEyeLookAwayTime) + { + if (frand(1.f) > 0.1f) + { + // blink while moving eyes some percentage of the time + mEyeBlinkTime = mEyeBlinkTimer.getElapsedTimeF32(); + } + if (mEyeLookAwayYaw == 0.f && mEyeLookAwayPitch == 0.f) + { + mEyeLookAwayYaw = (frand(2.f) - 1.f) * EYE_LOOK_AWAY_MAX_YAW; + mEyeLookAwayPitch = (frand(2.f) - 1.f) * EYE_LOOK_AWAY_MAX_PITCH; + mEyeLookAwayTime = EYE_LOOK_BACK_MIN_TIME + frand(EYE_LOOK_BACK_MAX_TIME - EYE_LOOK_BACK_MIN_TIME); + } + else + { + mEyeLookAwayYaw = 0.f; + mEyeLookAwayPitch = 0.f; + mEyeLookAwayTime = EYE_LOOK_AWAY_MIN_TIME + frand(EYE_LOOK_AWAY_MAX_TIME - EYE_LOOK_AWAY_MIN_TIME); + } + } + + // do blinking + if (!mEyesClosed && mEyeBlinkTimer.getElapsedTimeF32() >= mEyeBlinkTime) + { + F32 leftEyeBlinkMorph = mEyeBlinkTimer.getElapsedTimeF32() - mEyeBlinkTime; + F32 rightEyeBlinkMorph = leftEyeBlinkMorph - EYE_BLINK_TIME_DELTA; + + leftEyeBlinkMorph = llclamp(leftEyeBlinkMorph / EYE_BLINK_SPEED, 0.f, 1.f); + rightEyeBlinkMorph = llclamp(rightEyeBlinkMorph / EYE_BLINK_SPEED, 0.f, 1.f); + mCharacter->setVisualParamWeight("Blink_Left", leftEyeBlinkMorph); + mCharacter->setVisualParamWeight("Blink_Right", rightEyeBlinkMorph); + mCharacter->updateVisualParams(); + + if (rightEyeBlinkMorph == 1.f) + { + mEyesClosed = TRUE; + mEyeBlinkTime = EYE_BLINK_CLOSE_TIME; + mEyeBlinkTimer.reset(); + } + } + else if (mEyesClosed) + { + if (mEyeBlinkTimer.getElapsedTimeF32() >= mEyeBlinkTime) + { + F32 leftEyeBlinkMorph = mEyeBlinkTimer.getElapsedTimeF32() - mEyeBlinkTime; + F32 rightEyeBlinkMorph = leftEyeBlinkMorph - EYE_BLINK_TIME_DELTA; + + leftEyeBlinkMorph = 1.f - llclamp(leftEyeBlinkMorph / EYE_BLINK_SPEED, 0.f, 1.f); + rightEyeBlinkMorph = 1.f - llclamp(rightEyeBlinkMorph / EYE_BLINK_SPEED, 0.f, 1.f); + mCharacter->setVisualParamWeight("Blink_Left", leftEyeBlinkMorph); + mCharacter->setVisualParamWeight("Blink_Right", rightEyeBlinkMorph); + mCharacter->updateVisualParams(); + + if (rightEyeBlinkMorph == 0.f) + { + mEyesClosed = FALSE; + mEyeBlinkTime = EYE_BLINK_MIN_TIME + frand(EYE_BLINK_MAX_TIME - EYE_BLINK_MIN_TIME); + mEyeBlinkTimer.reset(); + } + } + } + + BOOL has_eye_target = FALSE; + LLVector3* targetPos = (LLVector3*)mCharacter->getAnimationData("LookAtPoint"); + + if (targetPos) + { + LLVector3 skyward(0.f, 0.f, 1.f); + LLVector3 left; + LLVector3 up; + + eye_look_at = *targetPos; + F32 lookAtDistance = eye_look_at.normVec(); + + left.setVec(skyward % eye_look_at); + up.setVec(eye_look_at % left); + + target_eye_rot = LLQuaternion(eye_look_at, left, up); + + // calculate vergence + F32 interocular_dist = (mLeftEyeState.getJoint()->getWorldPosition() - mRightEyeState.getJoint()->getWorldPosition()).magVec(); + vergence = -atan2((interocular_dist / 2.f), lookAtDistance); + llclamp(vergence, -F_PI_BY_TWO, 0.f); + } + else + { + target_eye_rot = mHeadJoint->getWorldRotation(); + vergence = 0.f; + } + + //RN: subtract 4 degrees to account for foveal angular offset relative to pupil + vergence += 4.f * DEG_TO_RAD; + + // calculate eye jitter + LLQuaternion eye_jitter_rot; + + // vergence not too high... + if (vergence > -0.05f) + { + //...go ahead and jitter + eye_jitter_rot.setQuat(0.f, mEyeJitterPitch + mEyeLookAwayPitch, mEyeJitterYaw + mEyeLookAwayYaw); + } + else + { + //...or don't + eye_jitter_rot.loadIdentity(); + } + + // calculate vergence of eyes as an object gets closer to the avatar's head + LLQuaternion vergence_quat; + + if (has_eye_target) + { + vergence_quat.setQuat(vergence, LLVector3(0.f, 0.f, 1.f)); + } + else + { + vergence_quat.loadIdentity(); + } + + // calculate eye rotations + LLQuaternion left_eye_rot = target_eye_rot; + left_eye_rot = vergence_quat * eye_jitter_rot * left_eye_rot; + + LLQuaternion right_eye_rot = target_eye_rot; + vergence_quat.transQuat(); + right_eye_rot = vergence_quat * eye_jitter_rot * right_eye_rot; + + //set final eye rotations + // start with left + LLQuaternion tQw = mLeftEyeState.getJoint()->getParent()->getWorldRotation(); + LLQuaternion tQh = left_eye_rot * ~tQw; + tQh.constrain(EYE_ROT_LIMIT_ANGLE); + mLeftEyeState.setRotation( tQh ); + + // now do right eye + tQw = mRightEyeState.getJoint()->getParent()->getWorldRotation(); + tQh = right_eye_rot * ~tQw; + tQh.constrain(EYE_ROT_LIMIT_ANGLE); + mRightEyeState.setRotation( tQh ); + + return TRUE; +} + + +//----------------------------------------------------------------------------- +// LLEyeMotion::onDeactivate() +//----------------------------------------------------------------------------- +void LLEyeMotion::onDeactivate() +{ + LLJoint* joint = mLeftEyeState.getJoint(); + if (joint) + { + joint->setRotation(LLQuaternion::DEFAULT); + } + + joint = mRightEyeState.getJoint(); + if (joint) + { + joint->setRotation(LLQuaternion::DEFAULT); + } +} + +// End diff --git a/indra/llcharacter/llheadrotmotion.h b/indra/llcharacter/llheadrotmotion.h new file mode 100644 index 0000000000..dc327cf44b --- /dev/null +++ b/indra/llcharacter/llheadrotmotion.h @@ -0,0 +1,194 @@ +/** + * @file llheadrotmotion.h + * @brief Implementation of LLHeadRotMotion class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLHEADROTMOTION_H +#define LL_LLHEADROTMOTION_H + +//----------------------------------------------------------------------------- +// Header files +//----------------------------------------------------------------------------- +#include "llmotion.h" +#include "llframetimer.h" + +#define MIN_REQUIRED_PIXEL_AREA_HEAD_ROT 500.f; +#define MIN_REQUIRED_PIXEL_AREA_EYE 25000.f; + +//----------------------------------------------------------------------------- +// class LLHeadRotMotion +//----------------------------------------------------------------------------- +class LLHeadRotMotion : + public LLMotion +{ +public: + // Constructor + LLHeadRotMotion(const LLUUID &id); + + // Destructor + virtual ~LLHeadRotMotion(); + +public: + //------------------------------------------------------------------------- + // functions to support MotionController and MotionRegistry + //------------------------------------------------------------------------- + + // static constructor + // all subclasses must implement such a function and register it + static LLMotion *create(const LLUUID &id) { return new LLHeadRotMotion(id); } + +public: + //------------------------------------------------------------------------- + // animation callbacks to be implemented by subclasses + //------------------------------------------------------------------------- + + // motions must specify whether or not they loop + virtual BOOL getLoop() { return TRUE; } + + // motions must report their total duration + virtual F32 getDuration() { return 0.0; } + + // motions must report their "ease in" duration + virtual F32 getEaseInDuration() { return 1.f; } + + // motions must report their "ease out" duration. + virtual F32 getEaseOutDuration() { return 1.f; } + + // called to determine when a motion should be activated/deactivated based on avatar pixel coverage + virtual F32 getMinPixelArea() { return MIN_REQUIRED_PIXEL_AREA_HEAD_ROT; } + + // motions must report their priority + virtual LLJoint::JointPriority getPriority() { return LLJoint::MEDIUM_PRIORITY; } + + virtual LLMotionBlendType getBlendType() { return NORMAL_BLEND; } + + // run-time (post constructor) initialization, + // called after parameters have been set + // must return true to indicate success and be available for activation + virtual LLMotionInitStatus onInitialize(LLCharacter *character); + + // called when a motion is activated + // must return TRUE to indicate success, or else + // it will be deactivated + virtual BOOL onActivate(); + + // called per time step + // must return TRUE while it is active, and + // must return FALSE when the motion is completed. + virtual BOOL onUpdate(F32 time, U8* joint_mask); + + // called when a motion is deactivated + virtual void onDeactivate(); + +public: + //------------------------------------------------------------------------- + // joint states to be animated + //------------------------------------------------------------------------- + LLCharacter *mCharacter; + + LLJoint *mTorsoJoint; + LLJoint *mHeadJoint; + LLJoint *mRootJoint; + LLJoint *mPelvisJoint; + + LLJointState mTorsoState; + LLJointState mNeckState; + LLJointState mHeadState; + + LLQuaternion mLastHeadRot; +}; + +//----------------------------------------------------------------------------- +// class LLEyeMotion +//----------------------------------------------------------------------------- +class LLEyeMotion : + public LLMotion +{ +public: + // Constructor + LLEyeMotion(const LLUUID &id); + + // Destructor + virtual ~LLEyeMotion(); + +public: + //------------------------------------------------------------------------- + // functions to support MotionController and MotionRegistry + //------------------------------------------------------------------------- + + // static constructor + // all subclasses must implement such a function and register it + static LLMotion *create( const LLUUID &id) { return new LLEyeMotion(id); } + +public: + //------------------------------------------------------------------------- + // animation callbacks to be implemented by subclasses + //------------------------------------------------------------------------- + + // motions must specify whether or not they loop + virtual BOOL getLoop() { return TRUE; } + + // motions must report their total duration + virtual F32 getDuration() { return 0.0; } + + // motions must report their "ease in" duration + virtual F32 getEaseInDuration() { return 0.5f; } + + // motions must report their "ease out" duration. + virtual F32 getEaseOutDuration() { return 0.5f; } + + // called to determine when a motion should be activated/deactivated based on avatar pixel coverage + virtual F32 getMinPixelArea() { return MIN_REQUIRED_PIXEL_AREA_EYE; } + + // motions must report their priority + virtual LLJoint::JointPriority getPriority() { return LLJoint::MEDIUM_PRIORITY; } + + virtual LLMotionBlendType getBlendType() { return NORMAL_BLEND; } + + // run-time (post constructor) initialization, + // called after parameters have been set + // must return true to indicate success and be available for activation + virtual LLMotionInitStatus onInitialize(LLCharacter *character); + + // called when a motion is activated + // must return TRUE to indicate success, or else + // it will be deactivated + virtual BOOL onActivate(); + + // called per time step + // must return TRUE while it is active, and + // must return FALSE when the motion is completed. + virtual BOOL onUpdate(F32 time, U8* joint_mask); + + // called when a motion is deactivated + virtual void onDeactivate(); + +public: + //------------------------------------------------------------------------- + // joint states to be animated + //------------------------------------------------------------------------- + LLCharacter *mCharacter; + + LLJoint *mHeadJoint; + LLJointState mLeftEyeState; + LLJointState mRightEyeState; + + LLFrameTimer mEyeJitterTimer; + F32 mEyeJitterTime; + F32 mEyeJitterYaw; + F32 mEyeJitterPitch; + F32 mEyeLookAwayTime; + F32 mEyeLookAwayYaw; + F32 mEyeLookAwayPitch; + + // eye blinking + LLFrameTimer mEyeBlinkTimer; + F32 mEyeBlinkTime; + BOOL mEyesClosed; +}; + +#endif // LL_LLHEADROTMOTION_H + diff --git a/indra/llcharacter/lljoint.cpp b/indra/llcharacter/lljoint.cpp new file mode 100644 index 0000000000..3924c06adc --- /dev/null +++ b/indra/llcharacter/lljoint.cpp @@ -0,0 +1,504 @@ +/** + * @file lljoint.cpp + * @brief Implementation of LLJoint class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +//----------------------------------------------------------------------------- +// Header Files +//----------------------------------------------------------------------------- +#include "linden_common.h" + +#include "lljoint.h" + +#include "llmath.h" + +S32 LLJoint::sNumUpdates = 0; +S32 LLJoint::sNumTouches = 0; + +//----------------------------------------------------------------------------- +// LLJoint() +// Class Constructor +//----------------------------------------------------------------------------- +LLJoint::LLJoint() +{ + mName = "unnamed"; + mParent = NULL; + mXform.setScaleChildOffset(TRUE); + mXform.setScale(LLVector3(1.0f, 1.0f, 1.0f)); + mDirtyFlags = MATRIX_DIRTY | ROTATION_DIRTY | POSITION_DIRTY; + mUpdateXform = TRUE; + mJointNum = -1; + touch(); +} + + +//----------------------------------------------------------------------------- +// LLJoint() +// Class Constructor +//----------------------------------------------------------------------------- +LLJoint::LLJoint(const std::string &name, LLJoint *parent) +{ + mName = "unnamed"; + mParent = NULL; + mXform.setScaleChildOffset(TRUE); + mXform.setScale(LLVector3(1.0f, 1.0f, 1.0f)); + mDirtyFlags = MATRIX_DIRTY | ROTATION_DIRTY | POSITION_DIRTY; + mJointNum = 0; + + setName(name); + if (parent) + parent->addChild( this ); + + touch(); +} + +//----------------------------------------------------------------------------- +// ~LLJoint() +// Class Destructor +//----------------------------------------------------------------------------- +LLJoint::~LLJoint() +{ + removeAllChildren(); +} + + +//----------------------------------------------------------------------------- +// setup() +//----------------------------------------------------------------------------- +void LLJoint::setup(const std::string &name, LLJoint *parent) +{ + setName(name); + if (parent) + parent->addChild( this ); +} + +//----------------------------------------------------------------------------- +// touch() +// Sets all dirty flags for all children, recursively. +//----------------------------------------------------------------------------- +void LLJoint::touch(U32 flags) +{ + if ((flags | mDirtyFlags) != mDirtyFlags) + { + sNumTouches++; + mDirtyFlags |= flags; + U32 child_flags = flags; + if (flags & ROTATION_DIRTY) + { + child_flags |= POSITION_DIRTY; + } + + for ( LLJoint *joint = mChildren.getFirstData(); + joint != NULL; + joint = mChildren.getNextData() ) + { + joint->touch(child_flags); + } + } +} + +//----------------------------------------------------------------------------- +// getRoot() +//----------------------------------------------------------------------------- +LLJoint *LLJoint::getRoot() +{ + if ( getParent() == NULL ) + { + return this; + } + return getParent()->getRoot(); +} + + +//----------------------------------------------------------------------------- +// findJoint() +//----------------------------------------------------------------------------- +LLJoint *LLJoint::findJoint( const std::string &name ) +{ + if (name == getName()) + return this; + + for ( LLJoint *j = mChildren.getFirstData(); + j != NULL; + j = mChildren.getNextData() ) + { + LLJoint *found = j->findJoint(name); + if (found) + return found; + } + + return NULL; +} + + +//-------------------------------------------------------------------- +// addChild() +//-------------------------------------------------------------------- +void LLJoint::addChild(LLJoint *joint) +{ + if (joint->mParent) + joint->mParent->removeChild(joint); + + mChildren.addDataAtEnd(joint); + joint->mXform.setParent(&mXform); + joint->mParent = this; + joint->touch(); +} + + +//-------------------------------------------------------------------- +// removeChild() +//-------------------------------------------------------------------- +void LLJoint::removeChild(LLJoint *joint) +{ + this->mChildren.removeData(joint); + joint->mXform.setParent(NULL); + joint->mParent = NULL; + joint->touch(); +} + + +//-------------------------------------------------------------------- +// removeAllChildren() +//-------------------------------------------------------------------- +void LLJoint::removeAllChildren() +{ + for ( LLJoint *joint = mChildren.getFirstData(); + joint != NULL; + joint = mChildren.getNextData() ) + { + removeChild(joint); + } +} + + +//-------------------------------------------------------------------- +// getPosition() +//-------------------------------------------------------------------- +const LLVector3& LLJoint::getPosition() +{ + return mXform.getPosition(); +} + + +//-------------------------------------------------------------------- +// setPosition() +//-------------------------------------------------------------------- +void LLJoint::setPosition( const LLVector3& pos ) +{ + mXform.setPosition(pos); + touch(MATRIX_DIRTY | POSITION_DIRTY); +} + + +//-------------------------------------------------------------------- +// getWorldPosition() +//-------------------------------------------------------------------- +LLVector3 LLJoint::getWorldPosition() +{ + updateWorldPRSParent(); + return mXform.getWorldPosition(); +} + +//----------------------------------------------------------------------------- +// getLastWorldPosition() +//----------------------------------------------------------------------------- +LLVector3 LLJoint::getLastWorldPosition() +{ + return mXform.getWorldPosition(); +} + + +//-------------------------------------------------------------------- +// setWorldPosition() +//-------------------------------------------------------------------- +void LLJoint::setWorldPosition( const LLVector3& pos ) +{ + if (mParent == NULL) + { + this->setPosition( pos ); + return; + } + + LLMatrix4 temp_matrix = getWorldMatrix(); + temp_matrix.mMatrix[VW][VX] = pos.mV[VX]; + temp_matrix.mMatrix[VW][VY] = pos.mV[VY]; + temp_matrix.mMatrix[VW][VZ] = pos.mV[VZ]; + + LLMatrix4 parentWorldMatrix = mParent->getWorldMatrix(); + LLMatrix4 invParentWorldMatrix = parentWorldMatrix.invert(); + + temp_matrix *= invParentWorldMatrix; + + LLVector3 localPos( temp_matrix.mMatrix[VW][VX], + temp_matrix.mMatrix[VW][VY], + temp_matrix.mMatrix[VW][VZ] ); + + setPosition( localPos ); +} + + +//-------------------------------------------------------------------- +// mXform.getRotation() +//-------------------------------------------------------------------- +const LLQuaternion& LLJoint::getRotation() +{ + return mXform.getRotation(); +} + + +//-------------------------------------------------------------------- +// setRotation() +//-------------------------------------------------------------------- +void LLJoint::setRotation( const LLQuaternion& rot ) +{ + if (rot.isFinite()) + { + mXform.setRotation(rot); + touch(MATRIX_DIRTY | ROTATION_DIRTY); + } +} + + +//-------------------------------------------------------------------- +// getWorldRotation() +//-------------------------------------------------------------------- +LLQuaternion LLJoint::getWorldRotation() +{ + updateWorldPRSParent(); + + return mXform.getWorldRotation(); +} + +//----------------------------------------------------------------------------- +// getLastWorldRotation() +//----------------------------------------------------------------------------- +LLQuaternion LLJoint::getLastWorldRotation() +{ + return mXform.getWorldRotation(); +} + +//-------------------------------------------------------------------- +// setWorldRotation() +//-------------------------------------------------------------------- +void LLJoint::setWorldRotation( const LLQuaternion& rot ) +{ + if (mParent == NULL) + { + this->setRotation( rot ); + return; + } + + LLMatrix4 temp_mat(rot); + + LLMatrix4 parentWorldMatrix = mParent->getWorldMatrix(); + parentWorldMatrix.mMatrix[VW][VX] = 0; + parentWorldMatrix.mMatrix[VW][VY] = 0; + parentWorldMatrix.mMatrix[VW][VZ] = 0; + + LLMatrix4 invParentWorldMatrix = parentWorldMatrix.invert(); + + temp_mat *= invParentWorldMatrix; + + setRotation(LLQuaternion(temp_mat)); +} + + +//-------------------------------------------------------------------- +// getScale() +//-------------------------------------------------------------------- +const LLVector3& LLJoint::getScale() +{ + return mXform.getScale(); +} + +//-------------------------------------------------------------------- +// setScale() +//-------------------------------------------------------------------- +void LLJoint::setScale( const LLVector3& scale ) +{ + mXform.setScale(scale); + touch(); +} + + + +//-------------------------------------------------------------------- +// getWorldMatrix() +//-------------------------------------------------------------------- +const LLMatrix4 &LLJoint::getWorldMatrix() +{ + updateWorldMatrixParent(); + + return mXform.getWorldMatrix(); +} + + +//-------------------------------------------------------------------- +// setWorldMatrix() +//-------------------------------------------------------------------- +void LLJoint::setWorldMatrix( const LLMatrix4& mat ) +{ +llinfos << "WARNING: LLJoint::setWorldMatrix() not correctly implemented yet" << llendl; + // extract global translation + LLVector3 trans( mat.mMatrix[VW][VX], + mat.mMatrix[VW][VY], + mat.mMatrix[VW][VZ] ); + + // extract global rotation + LLQuaternion rot( mat ); + + setWorldPosition( trans ); + setWorldRotation( rot ); +} + +//----------------------------------------------------------------------------- +// updateWorldMatrixParent() +//----------------------------------------------------------------------------- +void LLJoint::updateWorldMatrixParent() +{ + if (mDirtyFlags & MATRIX_DIRTY) + { + LLJoint *parent = getParent(); + if (parent) + { + parent->updateWorldMatrixParent(); + } + updateWorldMatrix(); + } +} + +//----------------------------------------------------------------------------- +// updateWorldPRSParent() +//----------------------------------------------------------------------------- +void LLJoint::updateWorldPRSParent() +{ + if (mDirtyFlags & (ROTATION_DIRTY | POSITION_DIRTY)) + { + LLJoint *parent = getParent(); + if (parent) + { + parent->updateWorldPRSParent(); + } + + mXform.update(); + mDirtyFlags &= ~(ROTATION_DIRTY | POSITION_DIRTY); + } +} + +//----------------------------------------------------------------------------- +// updateWorldMatrixChildren() +//----------------------------------------------------------------------------- +void LLJoint::updateWorldMatrixChildren() +{ + if (mDirtyFlags & MATRIX_DIRTY) + { + updateWorldMatrix(); + } + for (LLJoint *child = mChildren.getFirstData(); child; child = mChildren.getNextData()) + { + child->updateWorldMatrixChildren(); + } +} + +//----------------------------------------------------------------------------- +// updateWorldMatrix() +//----------------------------------------------------------------------------- +void LLJoint::updateWorldMatrix() +{ + if (mDirtyFlags & MATRIX_DIRTY) + { + sNumUpdates++; + mXform.updateMatrix(FALSE); + mDirtyFlags = 0x0; + } +} + +//-------------------------------------------------------------------- +// getSkinOffset() +//-------------------------------------------------------------------- +const LLVector3 &LLJoint::getSkinOffset() +{ + return mSkinOffset; +} + + +//-------------------------------------------------------------------- +// setSkinOffset() +//-------------------------------------------------------------------- +void LLJoint::setSkinOffset( const LLVector3& offset ) +{ + mSkinOffset = offset; +} + +//----------------------------------------------------------------------------- +// setConstraintSilhouette() +//----------------------------------------------------------------------------- +void LLJoint::setConstraintSilhouette(LLDynamicArray<LLVector3>& silhouette) +{ + S32 i; + + mConstraintSilhouette.reset(); + for (i = 0; i < silhouette.count(); i++) + { + if (i % 2 == 1) + { + // skip normals + continue; + } + mConstraintSilhouette[i / 2] = silhouette[i]; + } + LLQuaternion inv_parent_rotation = LLQuaternion::DEFAULT; + + if (getParent()) + { + inv_parent_rotation = ~getParent()->getWorldRotation(); + } + + for (i = 0; i < mConstraintSilhouette.count(); i++) + { + LLVector3 vert = mConstraintSilhouette[i]; + + vert -= getWorldPosition(); + vert.normVec(); + vert = vert * inv_parent_rotation; + } +} + +//----------------------------------------------------------------------------- +// clampRotation() +//----------------------------------------------------------------------------- +void LLJoint::clampRotation(LLQuaternion old_rot, LLQuaternion new_rot) +{ + LLVector3 main_axis(1.f, 0.f, 0.f); + + for (LLJoint* joint = mChildren.getFirstData(); joint; joint = mChildren.getNextData()) + { + if (joint->isAnimatable()) + { + main_axis = joint->getPosition(); + main_axis.normVec(); + // only care about first animatable child + break; + } + } + + // 2003.03.26 - This code was just using up cpu cycles. AB + +// LLVector3 old_axis = main_axis * old_rot; +// LLVector3 new_axis = main_axis * new_rot; + +// for (S32 i = 0; i < mConstraintSilhouette.count() - 1; i++) +// { +// LLVector3 vert1 = mConstraintSilhouette[i]; +// LLVector3 vert2 = mConstraintSilhouette[i + 1]; + + // figure out how to clamp rotation to line on 3-sphere + +// } +} + +// End diff --git a/indra/llcharacter/lljoint.h b/indra/llcharacter/lljoint.h new file mode 100644 index 0000000000..2fc86e87df --- /dev/null +++ b/indra/llcharacter/lljoint.h @@ -0,0 +1,163 @@ +/** + * @file lljoint.h + * @brief Implementation of LLJoint class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLJOINT_H +#define LL_LLJOINT_H + +//----------------------------------------------------------------------------- +// Header Files +//----------------------------------------------------------------------------- +#include <string> + +#include "linked_lists.h" +#include "v3math.h" +#include "v4math.h" +#include "m4math.h" +#include "llquaternion.h" +#include "xform.h" +#include "lldarray.h" + +const S32 LL_CHARACTER_MAX_JOINTS_PER_MESH = 15; +const U32 LL_CHARACTER_MAX_JOINTS = 32; // must be divisible by 4! +const U32 LL_HAND_JOINT_NUM = 31; +const U32 LL_FACE_JOINT_NUM = 30; +const S32 LL_CHARACTER_MAX_PRIORITY = 7; +const F32 LL_MAX_PELVIS_OFFSET = 5.f; + +//----------------------------------------------------------------------------- +// class LLJoint +//----------------------------------------------------------------------------- +class LLJoint +{ +public: + // priority levels, from highest to lowest + enum JointPriority + { + USE_MOTION_PRIORITY = -1, + LOW_PRIORITY = 0, + MEDIUM_PRIORITY, + HIGH_PRIORITY, + HIGHER_PRIORITY, + HIGHEST_PRIORITY, + ADDITIVE_PRIORITY = LL_CHARACTER_MAX_PRIORITY + }; + + enum DirtyFlags + { + MATRIX_DIRTY = 0x1 << 0, + ROTATION_DIRTY = 0x1 << 1, + POSITION_DIRTY = 0x1 << 2, + ALL_DIRTY = 0x7 + }; +protected: + std::string mName; + + // parent joint + LLJoint *mParent; + + // explicit transformation members + LLXformMatrix mXform; + +public: + U32 mDirtyFlags; + BOOL mWorldRotationDirty; + BOOL mUpdateXform; + + // describes the skin binding pose + LLVector3 mSkinOffset; + + S32 mJointNum; + + LLDynamicArray<LLVector3> mConstraintSilhouette; + + // child joints + LLLinkedList<LLJoint> mChildren; + + // debug statics + static S32 sNumTouches; + static S32 sNumUpdates; + +public: + LLJoint(); + LLJoint( const std::string &name, LLJoint *parent=NULL ); + + virtual ~LLJoint(); + + // set name and parent + void setup( const std::string &name, LLJoint *parent=NULL ); + + void touch(U32 flags = ALL_DIRTY); + + // get/set name + const std::string &getName() { return mName; } + void setName( const std::string &name ) { mName = name; } + + // getParent + LLJoint *getParent() { return mParent; } + + // getRoot + LLJoint *getRoot(); + + // search for child joints by name + LLJoint *findJoint( const std::string &name ); + + // add/remove children + void addChild( LLJoint *joint ); + void removeChild( LLJoint *joint ); + void removeAllChildren(); + + // get/set local position + const LLVector3& getPosition(); + void setPosition( const LLVector3& pos ); + + // get/set world position + LLVector3 getWorldPosition(); + LLVector3 getLastWorldPosition(); + void setWorldPosition( const LLVector3& pos ); + + // get/set local rotation + const LLQuaternion& getRotation(); + void setRotation( const LLQuaternion& rot ); + + // get/set world rotation + LLQuaternion getWorldRotation(); + LLQuaternion getLastWorldRotation(); + void setWorldRotation( const LLQuaternion& rot ); + + // get/set local scale + const LLVector3& getScale(); + void setScale( const LLVector3& scale ); + + // get/set world matrix + const LLMatrix4 &getWorldMatrix(); + void setWorldMatrix( const LLMatrix4& mat ); + + void updateWorldMatrixChildren(); + void updateWorldMatrixParent(); + + void updateWorldPRSParent(); + + void updateWorldMatrix(); + + // get/set skin offset + const LLVector3 &getSkinOffset(); + void setSkinOffset( const LLVector3 &offset); + + LLXformMatrix *getXform() { return &mXform; } + + void setConstraintSilhouette(LLDynamicArray<LLVector3>& silhouette); + + void clampRotation(LLQuaternion old_rot, LLQuaternion new_rot); + + virtual BOOL isAnimatable() { return TRUE; } + + S32 getJointNum() { return mJointNum; } + void setJointNum(S32 joint_num) { mJointNum = joint_num; } +}; +#endif // LL_LLJOINT_H + diff --git a/indra/llcharacter/lljointsolverrp3.cpp b/indra/llcharacter/lljointsolverrp3.cpp new file mode 100644 index 0000000000..60b836734a --- /dev/null +++ b/indra/llcharacter/lljointsolverrp3.cpp @@ -0,0 +1,366 @@ +/** + * @file lljointsolverrp3.cpp + * @brief Implementation of LLJointSolverRP3 class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +//----------------------------------------------------------------------------- +// Header Files +//----------------------------------------------------------------------------- +#include "linden_common.h" + +#include "lljointsolverrp3.h" + +#include <math.h> + +#include "llmath.h" + +#define F_EPSILON 0.00001f + + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +LLJointSolverRP3::LLJointSolverRP3() +{ + mJointA = NULL; + mJointB = NULL; + mJointC = NULL; + mJointGoal = NULL; + mLengthAB = 1.0f; + mLengthBC = 1.0f; + mPoleVector.setVec( 1.0f, 0.0f, 0.0f ); + mbUseBAxis = FALSE; + mTwist = 0.0f; + mFirstTime = TRUE; +} + + +//----------------------------------------------------------------------------- +// Destructor +//----------------------------------------------------------------------------- +/*virtual*/ LLJointSolverRP3::~LLJointSolverRP3() +{ +} + + +//----------------------------------------------------------------------------- +// setupJoints() +//----------------------------------------------------------------------------- +void LLJointSolverRP3::setupJoints( LLJoint* jointA, + LLJoint* jointB, + LLJoint* jointC, + LLJoint* jointGoal ) +{ + mJointA = jointA; + mJointB = jointB; + mJointC = jointC; + mJointGoal = jointGoal; + + mLengthAB = mJointB->getPosition().magVec(); + mLengthBC = mJointC->getPosition().magVec(); + + mJointABaseRotation = jointA->getRotation(); + mJointBBaseRotation = jointB->getRotation(); +} + + +//----------------------------------------------------------------------------- +// getPoleVector() +//----------------------------------------------------------------------------- +const LLVector3& LLJointSolverRP3::getPoleVector() +{ + return mPoleVector; +} + + +//----------------------------------------------------------------------------- +// setPoleVector() +//----------------------------------------------------------------------------- +void LLJointSolverRP3::setPoleVector( const LLVector3& poleVector ) +{ + mPoleVector = poleVector; + mPoleVector.normVec(); +} + + +//----------------------------------------------------------------------------- +// setPoleVector() +//----------------------------------------------------------------------------- +void LLJointSolverRP3::setBAxis( const LLVector3& bAxis ) +{ + mBAxis = bAxis; + mBAxis.normVec(); + mbUseBAxis = TRUE; +} + +//----------------------------------------------------------------------------- +// getTwist() +//----------------------------------------------------------------------------- +F32 LLJointSolverRP3::getTwist() +{ + return mTwist; +} + + +//----------------------------------------------------------------------------- +// setTwist() +//----------------------------------------------------------------------------- +void LLJointSolverRP3::setTwist( F32 twist ) +{ + mTwist = twist; +} + + +//----------------------------------------------------------------------------- +// solve() +//----------------------------------------------------------------------------- +void LLJointSolverRP3::solve() +{ +// llinfos << llendl; +// llinfos << "LLJointSolverRP3::solve()" << llendl; + + //------------------------------------------------------------------------- + // setup joints in their base rotations + //------------------------------------------------------------------------- + mJointA->setRotation( mJointABaseRotation ); + mJointB->setRotation( mJointBBaseRotation ); + + //------------------------------------------------------------------------- + // get joint positions in world space + //------------------------------------------------------------------------- + LLVector3 aPos = mJointA->getWorldPosition(); + LLVector3 bPos = mJointB->getWorldPosition(); + LLVector3 cPos = mJointC->getWorldPosition(); + LLVector3 gPos = mJointGoal->getWorldPosition(); + +// llinfos << "bPosLocal = " << mJointB->getPosition() << llendl; +// llinfos << "cPosLocal = " << mJointC->getPosition() << llendl; +// llinfos << "bRotLocal = " << mJointB->getRotation() << llendl; +// llinfos << "cRotLocal = " << mJointC->getRotation() << llendl; + +// llinfos << "aPos : " << aPos << llendl; +// llinfos << "bPos : " << bPos << llendl; +// llinfos << "cPos : " << cPos << llendl; +// llinfos << "gPos : " << gPos << llendl; + + //------------------------------------------------------------------------- + // get the poleVector in world space + //------------------------------------------------------------------------- + LLMatrix4 worldJointAParentMat; + if ( mJointA->getParent() ) + { + worldJointAParentMat = mJointA->getParent()->getWorldMatrix(); + } + LLVector3 poleVec = rotate_vector( mPoleVector, worldJointAParentMat ); + + //------------------------------------------------------------------------- + // compute the following: + // vector from A to B + // vector from B to C + // vector from A to C + // vector from A to G (goal) + //------------------------------------------------------------------------- + LLVector3 abVec = bPos - aPos; + LLVector3 bcVec = cPos - bPos; + LLVector3 acVec = cPos - aPos; + LLVector3 agVec = gPos - aPos; + +// llinfos << "abVec : " << abVec << llendl; +// llinfos << "bcVec : " << bcVec << llendl; +// llinfos << "acVec : " << acVec << llendl; +// llinfos << "agVec : " << agVec << llendl; + + //------------------------------------------------------------------------- + // compute needed lengths of those vectors + //------------------------------------------------------------------------- + F32 abLen = abVec.magVec(); + F32 bcLen = bcVec.magVec(); + F32 agLen = agVec.magVec(); + +// llinfos << "abLen : " << abLen << llendl; +// llinfos << "bcLen : " << bcLen << llendl; +// llinfos << "agLen : " << agLen << llendl; + + //------------------------------------------------------------------------- + // compute component vector of (A->B) orthogonal to (A->C) + //------------------------------------------------------------------------- + LLVector3 abacCompOrthoVec = abVec - acVec * ((abVec * acVec)/(acVec * acVec)); + +// llinfos << "abacCompOrthoVec : " << abacCompOrthoVec << llendl + + //------------------------------------------------------------------------- + // compute the normal of the original ABC plane (and store for later) + //------------------------------------------------------------------------- + LLVector3 abcNorm; + if (!mbUseBAxis) + { + if( are_parallel(abVec, bcVec, 0.001f) ) + { + // the current solution is maxed out, so we use the axis that is + // orthogonal to both poleVec and A->B + if ( are_parallel(poleVec, abVec, 0.001f) ) + { + // ACK! the problem is singular + if ( are_parallel(poleVec, agVec, 0.001f) ) + { + // the solutions is also singular + return; + } + else + { + abcNorm = poleVec % agVec; + } + } + else + { + abcNorm = poleVec % abVec; + } + } + else + { + abcNorm = abVec % bcVec; + } + } + else + { + abcNorm = mBAxis * mJointB->getWorldRotation(); + } + + //------------------------------------------------------------------------- + // compute rotation of B + //------------------------------------------------------------------------- + // angle between A->B and B->C + F32 abbcAng = angle_between(abVec, bcVec); + + // vector orthogonal to A->B and B->C + LLVector3 abbcOrthoVec = abVec % bcVec; + if (abbcOrthoVec.magVecSquared() < 0.001f) + { + abbcOrthoVec = poleVec % abVec; + abacCompOrthoVec = poleVec; + } + abbcOrthoVec.normVec(); + + F32 agLenSq = agLen * agLen; + + // angle arm for extension + F32 cosTheta = (agLenSq - abLen*abLen - bcLen*bcLen) / (2.0f * abLen * bcLen); + if (cosTheta > 1.0f) + cosTheta = 1.0f; + else if (cosTheta < -1.0f) + cosTheta = -1.0f; + + F32 theta = acos(cosTheta); + + LLQuaternion bRot(theta - abbcAng, abbcOrthoVec); + +// llinfos << "abbcAng : " << abbcAng << llendl; +// llinfos << "abbcOrthoVec : " << abbcOrthoVec << llendl; +// llinfos << "agLenSq : " << agLenSq << llendl; +// llinfos << "cosTheta : " << cosTheta << llendl; +// llinfos << "theta : " << theta << llendl; +// llinfos << "bRot : " << bRot << llendl; +// llinfos << "theta abbcAng theta-abbcAng: " << theta*180.0/F_PI << " " << abbcAng*180.0f/F_PI << " " << (theta - abbcAng)*180.0f/F_PI << llendl; + + //------------------------------------------------------------------------- + // compute rotation that rotates new A->C to A->G + //------------------------------------------------------------------------- + // rotate B->C by bRot + bcVec = bcVec * bRot; + + // update A->C + acVec = abVec + bcVec; + + LLQuaternion cgRot; + cgRot.shortestArc( acVec, agVec ); + +// llinfos << "bcVec : " << bcVec << llendl; +// llinfos << "acVec : " << acVec << llendl; +// llinfos << "cgRot : " << cgRot << llendl; + + // update A->B and B->C with rotation from C to G + abVec = abVec * cgRot; + bcVec = bcVec * cgRot; + abcNorm = abcNorm * cgRot; + acVec = abVec + bcVec; + + //------------------------------------------------------------------------- + // compute the normal of the APG plane + //------------------------------------------------------------------------- + if (are_parallel(agVec, poleVec, 0.001f)) + { + // the solution plane is undefined ==> we're done + return; + } + LLVector3 apgNorm = poleVec % agVec; + apgNorm.normVec(); + + if (!mbUseBAxis) + { + //--------------------------------------------------------------------- + // compute the normal of the new ABC plane + // (only necessary if we're NOT using mBAxis) + //--------------------------------------------------------------------- + if( are_parallel(abVec, bcVec, 0.001f) ) + { + // G is either too close or too far away + // we'll use the old ABCnormal + } + else + { + abcNorm = abVec % bcVec; + } + abcNorm.normVec(); + } + + //------------------------------------------------------------------------- + // calcuate plane rotation + //------------------------------------------------------------------------- + LLQuaternion pRot; + if ( are_parallel( abcNorm, apgNorm, 0.001f) ) + { + if (abcNorm * apgNorm < 0.0f) + { + // we must be PI radians off ==> rotate by PI around agVec + pRot.setQuat(F_PI, agVec); + } + else + { + // we're done + } + } + else + { + pRot.shortestArc( abcNorm, apgNorm ); + } + +// llinfos << "abcNorm = " << abcNorm << llendl; +// llinfos << "apgNorm = " << apgNorm << llendl; +// llinfos << "pRot = " << pRot << llendl; + + //------------------------------------------------------------------------- + // compute twist rotation + //------------------------------------------------------------------------- + LLQuaternion twistRot( mTwist, agVec ); + +// llinfos << "twist : " << mTwist*180.0/F_PI << llendl; +// llinfos << "agNormVec: " << agNormVec << llendl; +// llinfos << "twistRot : " << twistRot << llendl; + + //------------------------------------------------------------------------- + // compute rotation of A + //------------------------------------------------------------------------- + LLQuaternion aRot = cgRot * pRot * twistRot; + + //------------------------------------------------------------------------- + // apply the rotations + //------------------------------------------------------------------------- + mJointB->setWorldRotation( mJointB->getWorldRotation() * bRot ); + mJointA->setWorldRotation( mJointA->getWorldRotation() * aRot ); +} + + +// End diff --git a/indra/llcharacter/lljointsolverrp3.h b/indra/llcharacter/lljointsolverrp3.h new file mode 100644 index 0000000000..d1507351b6 --- /dev/null +++ b/indra/llcharacter/lljointsolverrp3.h @@ -0,0 +1,158 @@ +/** + * @file lljointsolverrp3.h + * @brief Implementation of LLJointSolverRP3 class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLJOINTSOLVERRP3_H +#define LL_LLJOINTSOLVERRP3_H + +//----------------------------------------------------------------------------- +// Header Files +//----------------------------------------------------------------------------- +#include "lljoint.h" + +/* -some compilers don't like line continuation chars- +//----------------------------------------------------------------------------- +// class LLJointSolverRP3 +// +// This class is a "poor man's" IK for simple 3 joint kinematic chains. +// It is modeled after the 'ikRPSolver' in Maya. +// This class takes 4 LLJoints: +// jointA +// jointB +// jointC +// jointGoal +// +// Such that jointA is the parent of jointB, jointB is the parent of jointC. +// When invoked, this class modifies the rotations of jointA and jointB such +// that the position of the jointC attempts to reach the position of jointGoal. +// +// At object initialization time, the distances between jointA - jointB and +// jointB - jointC are cached. During evaluation these bone lengths are +// preserved. +// +// A A +// | | +// | | +// B B---CG A---B---C...G +// \ +// \ +// CG +// +// +// In addition a "poleVector" is specified that does two things: +// +// a) defines the plane in which the solution occurs, thus +// reducing an infinite number of solutions, down to 2. +// +// b) disambiguates the resulting two solutions as follows: +// +// A A A--->poleVector +// | \ \ +// | \ \ +// B vs. B ==> B +// \ | | +// \ | | +// CG CG CG +// +// A "twist" setting allows the solution plane to be rotated about the +// line between A and C. A handy animation feature. +// +// For "smarter" results for non-coplanar limbs, specify the joints axis +// of bend in the B's local frame (see setBAxis()) +//----------------------------------------------------------------------------- +*/ + +class LLJointSolverRP3 +{ +protected: + LLJoint *mJointA; + LLJoint *mJointB; + LLJoint *mJointC; + LLJoint *mJointGoal; + + F32 mLengthAB; + F32 mLengthBC; + + LLVector3 mPoleVector; + LLVector3 mBAxis; + BOOL mbUseBAxis; + + F32 mTwist; + + BOOL mFirstTime; + LLMatrix4 mSavedJointAMat; + LLMatrix4 mSavedInvPlaneMat; + + LLQuaternion mJointABaseRotation; + LLQuaternion mJointBBaseRotation; + +public: + //------------------------------------------------------------------------- + // Constructor/Destructor + //------------------------------------------------------------------------- + LLJointSolverRP3(); + virtual ~LLJointSolverRP3(); + + //------------------------------------------------------------------------- + // setupJoints() + // This must be called one time to setup the solver. + // This must be called AFTER the skeleton has been created, all parent/child + // relationships are established, and after the joints are placed in + // a valid configuration (as distances between them will be cached). + //------------------------------------------------------------------------- + void setupJoints( LLJoint* jointA, + LLJoint* jointB, + LLJoint* jointC, + LLJoint* jointGoal ); + + //------------------------------------------------------------------------- + // getPoleVector() + // Returns the current pole vector. + //------------------------------------------------------------------------- + const LLVector3& getPoleVector(); + + //------------------------------------------------------------------------- + // setPoleVector() + // Sets the pole vector. + // The pole vector is defined relative to (in the space of) jointA's parent. + // The default pole vector is (1,0,0), and this is used if this function + // is never called. + // This vector is normalized when set. + //------------------------------------------------------------------------- + void setPoleVector( const LLVector3& poleVector ); + + //------------------------------------------------------------------------- + // setBAxis() + // Sets the joint's axis in B's local frame, and enable "smarter" solve(). + // This allows for smarter IK when for twisted limbs. + //------------------------------------------------------------------------- + void setBAxis( const LLVector3& bAxis ); + + //------------------------------------------------------------------------- + // getTwist() + // Returns the current twist in radians. + //------------------------------------------------------------------------- + F32 getTwist(); + + //------------------------------------------------------------------------- + // setTwist() + // Sets the twist value. + // The default is 0.0. + //------------------------------------------------------------------------- + void setTwist( F32 twist ); + + //------------------------------------------------------------------------- + // solve() + // This is the "work" function. + // When called, the rotations of jointA and jointB will be modified + // such that jointC attempts to reach jointGoal. + //------------------------------------------------------------------------- + void solve(); +}; + +#endif // LL_LLJOINTSOLVERRP3_H + diff --git a/indra/llcharacter/lljointstate.h b/indra/llcharacter/lljointstate.h new file mode 100644 index 0000000000..82a2b345b0 --- /dev/null +++ b/indra/llcharacter/lljointstate.h @@ -0,0 +1,106 @@ +/** + * @file lljointstate.h + * @brief Implementation of LLJointState class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLJOINTSTATE_H +#define LL_LLJOINTSTATE_H + +//----------------------------------------------------------------------------- +// Header Files +//----------------------------------------------------------------------------- +#include "lljoint.h" + +//----------------------------------------------------------------------------- +// class LLJointState +//----------------------------------------------------------------------------- +class LLJointState +{ +public: + enum BlendPhase + { + INACTIVE, + EASE_IN, + ACTIVE, + EASE_OUT + }; +protected: + // associated joint + LLJoint *mJoint; + + // indicates which members are used + U32 mUsage; + + // indicates weighted effect of this joint + F32 mWeight; + + // transformation members + LLVector3 mPosition; // position relative to parent joint + LLQuaternion mRotation; // joint rotation relative to parent joint + LLVector3 mScale; // scale relative to rotated frame + LLJoint::JointPriority mPriority; // how important this joint state is relative to others +public: + // Constructor + LLJointState() + { + mUsage = 0; + mJoint = NULL; + mUsage = 0; + mWeight = 0.f; + mPriority = LLJoint::USE_MOTION_PRIORITY; + } + + LLJointState(LLJoint* joint) + { + mUsage = 0; + mJoint = joint; + mUsage = 0; + mWeight = 0.f; + mPriority = LLJoint::USE_MOTION_PRIORITY; + } + + // Destructor + virtual ~LLJointState() + { + } + + // joint that this state is applied to + LLJoint *getJoint() { return mJoint; } + BOOL setJoint( LLJoint *joint ) { mJoint = joint; return mJoint != NULL; } + + // transform type (bitwise flags can be combined) + // Note that these are set automatically when various + // member setPos/setRot/setScale functions are called. + enum Usage + { + POS = 1, + ROT = 2, + SCALE = 4, + }; + U32 getUsage() { return mUsage; } + void setUsage( U32 usage ) { mUsage = usage; } + F32 getWeight() { return mWeight; } + void setWeight( F32 weight ) { mWeight = weight; } + + // get/set position + const LLVector3& getPosition() { return mPosition; } + void setPosition( const LLVector3& pos ) { llassert(mUsage & POS); mPosition = pos; } + + // get/set rotation + const LLQuaternion& getRotation() { return mRotation; } + void setRotation( const LLQuaternion& rot ) { llassert(mUsage & ROT); mRotation = rot; } + + // get/set scale + const LLVector3& getScale() { return mScale; } + void setScale( const LLVector3& scale ) { llassert(mUsage & SCALE); mScale = scale; } + + // get/set priority + const LLJoint::JointPriority getPriority() { return mPriority; } + void setPriority( const LLJoint::JointPriority priority ) { mPriority = priority; } +}; + +#endif // LL_LLJOINTSTATE_H + diff --git a/indra/llcharacter/llkeyframefallmotion.cpp b/indra/llcharacter/llkeyframefallmotion.cpp new file mode 100644 index 0000000000..248d22c22c --- /dev/null +++ b/indra/llcharacter/llkeyframefallmotion.cpp @@ -0,0 +1,123 @@ +/** + * @file llkeyframefallmotion.cpp + * @brief Implementation of LLKeyframeFallMotion class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +//----------------------------------------------------------------------------- +// Header Files +//----------------------------------------------------------------------------- +#include "linden_common.h" + +#include "llkeyframefallmotion.h" +#include "llcharacter.h" +#include "m3math.h" + +//----------------------------------------------------------------------------- +// Macros +//----------------------------------------------------------------------------- +#define GO_TO_KEY_POSE 1 +#define MIN_TRACK_SPEED 0.01f + +//----------------------------------------------------------------------------- +// LLKeyframeFallMotion() +// Class Constructor +//----------------------------------------------------------------------------- +LLKeyframeFallMotion::LLKeyframeFallMotion(const LLUUID &id) : LLKeyframeMotion(id) +{ + mVelocityZ = 0.f; + mCharacter = NULL; +} + + +//----------------------------------------------------------------------------- +// ~LLKeyframeFallMotion() +// Class Destructor +//----------------------------------------------------------------------------- +LLKeyframeFallMotion::~LLKeyframeFallMotion() +{ +} + + +//----------------------------------------------------------------------------- +// LLKeyframeFallMotion::onInitialize() +//----------------------------------------------------------------------------- +LLMotion::LLMotionInitStatus LLKeyframeFallMotion::onInitialize(LLCharacter *character) +{ + // save character pointer for later use + mCharacter = character; + + // load keyframe data, setup pose and joint states + LLMotion::LLMotionInitStatus result = LLKeyframeMotion::onInitialize(character); + + for (U32 jm=0; jm<mJointMotionList->mNumJointMotions; jm++) + { + if (!mJointStates[jm].getJoint()) + continue; + if (mJointStates[jm].getJoint()->getName() == std::string("mPelvis")) + { + mPelvisStatep = &mJointStates[jm]; + } + } + + return result; +} + +//----------------------------------------------------------------------------- +// LLKeyframeFallMotion::onActivate() +//----------------------------------------------------------------------------- +BOOL LLKeyframeFallMotion::onActivate() +{ + LLVector3 ground_pos; + LLVector3 ground_normal; + LLQuaternion inverse_pelvis_rot; + LLVector3 fwd_axis(1.f, 0.f, 0.f); + + mVelocityZ = -mCharacter->getCharacterVelocity().mV[VZ]; + mCharacter->getGround( mCharacter->getCharacterPosition(), ground_pos, ground_normal); + ground_normal.normVec(); + + inverse_pelvis_rot = mCharacter->getCharacterRotation(); + inverse_pelvis_rot.transQuat(); + + // find ground normal in pelvis space + ground_normal = ground_normal * inverse_pelvis_rot; + + // calculate new foward axis + fwd_axis = fwd_axis - (ground_normal * (ground_normal * fwd_axis)); + fwd_axis.normVec(); + mRotationToGroundNormal = LLQuaternion(fwd_axis, ground_normal % fwd_axis, ground_normal); + + return LLKeyframeMotion::onActivate(); +} + +//----------------------------------------------------------------------------- +// LLKeyframeFallMotion::onUpdate() +//----------------------------------------------------------------------------- +BOOL LLKeyframeFallMotion::onUpdate(F32 activeTime, U8* joint_mask) +{ + BOOL result = LLKeyframeMotion::onUpdate(activeTime, joint_mask); + F32 slerp_amt = clamp_rescale(activeTime / getDuration(), 0.5f, 0.75f, 0.f, 1.f); + + mPelvisStatep->setRotation(mPelvisStatep->getRotation() * slerp(slerp_amt, mRotationToGroundNormal, LLQuaternion())); + + return result; +} + +//----------------------------------------------------------------------------- +// LLKeyframeFallMotion::getEaseInDuration() +//----------------------------------------------------------------------------- +F32 LLKeyframeFallMotion::getEaseInDuration() +{ + if (mVelocityZ == 0.f) + { + // we've already hit the ground + return 0.4f; + } + + return mCharacter->getPreferredPelvisHeight() / mVelocityZ; +} + +// End diff --git a/indra/llcharacter/llkeyframefallmotion.h b/indra/llcharacter/llkeyframefallmotion.h new file mode 100644 index 0000000000..5cc15fd38b --- /dev/null +++ b/indra/llcharacter/llkeyframefallmotion.h @@ -0,0 +1,60 @@ +/** + * @file llkeyframefallmotion.h + * @brief Implementation of LLKeframeWalkMotion class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLKeyframeFallMotion_H +#define LL_LLKeyframeFallMotion_H + +//----------------------------------------------------------------------------- +// Header files +//----------------------------------------------------------------------------- +#include "llkeyframemotion.h" +#include "llcharacter.h" + +//----------------------------------------------------------------------------- +// class LLKeyframeFallMotion +//----------------------------------------------------------------------------- +class LLKeyframeFallMotion : + public LLKeyframeMotion +{ +public: + // Constructor + LLKeyframeFallMotion(const LLUUID &id); + + // Destructor + virtual ~LLKeyframeFallMotion(); + +public: + //------------------------------------------------------------------------- + // functions to support MotionController and MotionRegistry + //------------------------------------------------------------------------- + + // static constructor + // all subclasses must implement such a function and register it + static LLMotion *create(const LLUUID &id) { return new LLKeyframeFallMotion(id); } + +public: + //------------------------------------------------------------------------- + // animation callbacks to be implemented by subclasses + //------------------------------------------------------------------------- + virtual LLMotionInitStatus onInitialize(LLCharacter *character); + virtual BOOL onActivate(); + virtual F32 getEaseInDuration(); + virtual BOOL onUpdate(F32 activeTime, U8* joint_mask); + +protected: + //------------------------------------------------------------------------- + // Member Data + //------------------------------------------------------------------------- + LLCharacter* mCharacter; + F32 mVelocityZ; + LLJointState* mPelvisStatep; + LLQuaternion mRotationToGroundNormal; +}; + +#endif // LL_LLKeyframeFallMotion_H + diff --git a/indra/llcharacter/llkeyframemotion.cpp b/indra/llcharacter/llkeyframemotion.cpp new file mode 100644 index 0000000000..4dd976d5f1 --- /dev/null +++ b/indra/llcharacter/llkeyframemotion.cpp @@ -0,0 +1,2112 @@ +/** + * @file llkeyframemotion.cpp + * @brief Implementation of LLKeyframeMotion class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +//----------------------------------------------------------------------------- +// Header Files +//----------------------------------------------------------------------------- +#include "linden_common.h" + +#include "llmath.h" +#include "llanimationstates.h" +#include "llassetstorage.h" +#include "lldatapacker.h" +#include "llcharacter.h" +#include "llcriticaldamp.h" +#include "lldir.h" +#include "llendianswizzle.h" +#include "llkeyframemotion.h" +#include "llquantize.h" +#include "llvfile.h" +#include "m3math.h" +#include "message.h" + +//----------------------------------------------------------------------------- +// Static Definitions +//----------------------------------------------------------------------------- +LLVFS* LLKeyframeMotion::sVFS = NULL; +LLKeyframeDataCache::LLKeyframeDataMap LLKeyframeDataCache::sKeyframeDataMap; + +//----------------------------------------------------------------------------- +// Globals +//----------------------------------------------------------------------------- +static F32 JOINT_LENGTH_K = 0.7f; +static S32 MAX_ITERATIONS = 20; +static S32 MIN_ITERATIONS = 1; +static S32 MIN_ITERATION_COUNT = 2; +static F32 MAX_PIXEL_AREA_CONSTRAINTS = 80000.f; +static F32 MIN_PIXEL_AREA_CONSTRAINTS = 1000.f; +static F32 MIN_ACCELERATION_SQUARED = 0.0005f * 0.0005f; + +static F32 MAX_CONSTRAINTS = 10; + +//----------------------------------------------------------------------------- +// JointMotionList::dumpDiagInfo() +//----------------------------------------------------------------------------- +U32 LLKeyframeMotion::JointMotionList::dumpDiagInfo() +{ + S32 total_size = sizeof(JointMotionList); + + for (U32 i = 0; i < mNumJointMotions; i++) + { + LLKeyframeMotion::JointMotion* joint_motion_p = &mJointMotionArray[i]; + + llinfos << "\tJoint " << joint_motion_p->mJointName << llendl; + if (joint_motion_p->mUsage & LLJointState::SCALE) + { + llinfos << "\t" << joint_motion_p->mScaleCurve.mNumKeys << " scale keys at " + << joint_motion_p->mScaleCurve.mNumKeys * sizeof(ScaleKey) << " bytes" << llendl; + + total_size += joint_motion_p->mScaleCurve.mNumKeys * sizeof(ScaleKey); + } + if (joint_motion_p->mUsage & LLJointState::ROT) + { + llinfos << "\t" << joint_motion_p->mRotationCurve.mNumKeys << " rotation keys at " + << joint_motion_p->mRotationCurve.mNumKeys * sizeof(RotationKey) << " bytes" << llendl; + + total_size += joint_motion_p->mRotationCurve.mNumKeys * sizeof(RotationKey); + } + if (joint_motion_p->mUsage & LLJointState::POS) + { + llinfos << "\t" << joint_motion_p->mPositionCurve.mNumKeys << " position keys at " + << joint_motion_p->mPositionCurve.mNumKeys * sizeof(PositionKey) << " bytes" << llendl; + + total_size += joint_motion_p->mPositionCurve.mNumKeys * sizeof(PositionKey); + } + } + llinfos << "Size: " << total_size << " bytes" << llendl; + + return total_size; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +// ****Curve classes +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// ScaleCurve::ScaleCurve() +//----------------------------------------------------------------------------- +LLKeyframeMotion::ScaleCurve::ScaleCurve() +{ + mInterpolationType = LLKeyframeMotion::IT_LINEAR; + mNumKeys = 0; +} + +//----------------------------------------------------------------------------- +// ScaleCurve::~ScaleCurve() +//----------------------------------------------------------------------------- +LLKeyframeMotion::ScaleCurve::~ScaleCurve() +{ + mKeys.deleteAllData(); + mNumKeys = 0; +} + +//----------------------------------------------------------------------------- +// getValue() +//----------------------------------------------------------------------------- +LLVector3 LLKeyframeMotion::ScaleCurve::getValue(F32 time, F32 duration) +{ + LLVector3 value; + F32 index_before, index_after; + ScaleKey* scale_before; + ScaleKey* scale_after; + + mKeys.getInterval(time, index_before, index_after, scale_before, scale_after); + if (scale_before) + { + if (!scale_after) + { + scale_after = &mLoopInKey; + index_after = duration; + } + + if (index_after == index_before) + { + value = scale_after->mScale; + } + else + { + F32 u = (time - index_before) / (index_after - index_before); + value = interp(u, *scale_before, *scale_after); + } + } + else + { + // before first key + if (scale_after) + { + value = scale_after->mScale; + } + // no keys? + else + { + value.clearVec(); + } + } + + return value; +} + +//----------------------------------------------------------------------------- +// interp() +//----------------------------------------------------------------------------- +LLVector3 LLKeyframeMotion::ScaleCurve::interp(F32 u, ScaleKey& before, ScaleKey& after) +{ + switch (mInterpolationType) + { + case IT_STEP: + return before.mScale; + + default: + case IT_LINEAR: + case IT_SPLINE: + return lerp(before.mScale, after.mScale, u); + } +} + +//----------------------------------------------------------------------------- +// RotationCurve::RotationCurve() +//----------------------------------------------------------------------------- +LLKeyframeMotion::RotationCurve::RotationCurve() +{ + mInterpolationType = LLKeyframeMotion::IT_LINEAR; + mNumKeys = 0; +} + +//----------------------------------------------------------------------------- +// RotationCurve::~RotationCurve() +//----------------------------------------------------------------------------- +LLKeyframeMotion::RotationCurve::~RotationCurve() +{ + mKeys.deleteAllData(); + mNumKeys = 0; +} + +//----------------------------------------------------------------------------- +// RotationCurve::getValue() +//----------------------------------------------------------------------------- +LLQuaternion LLKeyframeMotion::RotationCurve::getValue(F32 time, F32 duration) +{ + LLQuaternion value; + F32 index_before, index_after; + RotationKey* rot_before; + RotationKey* rot_after; + + mKeys.getInterval(time, index_before, index_after, rot_before, rot_after); + + if (rot_before) + { + if (!rot_after) + { + rot_after = &mLoopInKey; + index_after = duration; + } + + if (index_after == index_before) + { + value = rot_after->mRotation; + } + else + { + F32 u = (time - index_before) / (index_after - index_before); + value = interp(u, *rot_before, *rot_after); + } + } + else + { + // before first key + if (rot_after) + { + value = rot_after->mRotation; + } + // no keys? + else + { + value = LLQuaternion::DEFAULT; + } + } + + return value; +} + +//----------------------------------------------------------------------------- +// interp() +//----------------------------------------------------------------------------- +LLQuaternion LLKeyframeMotion::RotationCurve::interp(F32 u, RotationKey& before, RotationKey& after) +{ + switch (mInterpolationType) + { + case IT_STEP: + return before.mRotation; + + default: + case IT_LINEAR: + case IT_SPLINE: + return nlerp(u, before.mRotation, after.mRotation); + } +} + + +//----------------------------------------------------------------------------- +// PositionCurve::PositionCurve() +//----------------------------------------------------------------------------- +LLKeyframeMotion::PositionCurve::PositionCurve() +{ + mInterpolationType = LLKeyframeMotion::IT_LINEAR; + mNumKeys = 0; +} + +//----------------------------------------------------------------------------- +// PositionCurve::~PositionCurve() +//----------------------------------------------------------------------------- +LLKeyframeMotion::PositionCurve::~PositionCurve() +{ + mKeys.deleteAllData(); + mNumKeys = 0; +} + +//----------------------------------------------------------------------------- +// PositionCurve::getValue() +//----------------------------------------------------------------------------- +LLVector3 LLKeyframeMotion::PositionCurve::getValue(F32 time, F32 duration) +{ + LLVector3 value; + F32 index_before, index_after; + PositionKey* pos_before; + PositionKey* pos_after; + + mKeys.getInterval(time, index_before, index_after, pos_before, pos_after); + + if (pos_before) + { + if (!pos_after) + { + pos_after = &mLoopInKey; + index_after = duration; + } + + if (index_after == index_before) + { + value = pos_after->mPosition; + } + else + { + F32 u = (time - index_before) / (index_after - index_before); + value = interp(u, *pos_before, *pos_after); + } + } + else + { + // before first key + if (pos_after) + { + value = pos_after->mPosition; + } + // no keys? + else + { + value.clearVec(); + } + } + + llassert(value.isFinite()); + + return value; +} + +//----------------------------------------------------------------------------- +// interp() +//----------------------------------------------------------------------------- +LLVector3 LLKeyframeMotion::PositionCurve::interp(F32 u, PositionKey& before, PositionKey& after) +{ + switch (mInterpolationType) + { + case IT_STEP: + return before.mPosition; + default: + case IT_LINEAR: + case IT_SPLINE: + return lerp(before.mPosition, after.mPosition, u); + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +// JointMotion class +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// JointMotion::update() +//----------------------------------------------------------------------------- +void LLKeyframeMotion::JointMotion::update(LLJointState* joint_state, F32 time, F32 duration) +{ + // this value being 0 is the cause of https://jira.lindenlab.com/browse/SL-22678 but I haven't + // managed to get a stack to see how it got here. Testing for 0 here will stop the crash. + if ( joint_state == 0 ) + { + return; + }; + + U32 usage = joint_state->getUsage(); + + //------------------------------------------------------------------------- + // update scale component of joint state + //------------------------------------------------------------------------- + if ((usage & LLJointState::SCALE) && mScaleCurve.mNumKeys) + { + joint_state->setScale( mScaleCurve.getValue( time, duration ) ); + } + + //------------------------------------------------------------------------- + // update rotation component of joint state + //------------------------------------------------------------------------- + if ((usage & LLJointState::ROT) && mRotationCurve.mNumKeys) + { + joint_state->setRotation( mRotationCurve.getValue( time, duration ) ); + } + + //------------------------------------------------------------------------- + // update position component of joint state + //------------------------------------------------------------------------- + if ((usage & LLJointState::POS) && mPositionCurve.mNumKeys) + { + joint_state->setPosition( mPositionCurve.getValue( time, duration ) ); + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +// LLKeyframeMotion class +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// LLKeyframeMotion() +// Class Constructor +//----------------------------------------------------------------------------- +LLKeyframeMotion::LLKeyframeMotion( const LLUUID &id) : LLMotion(id) +{ + mJointMotionList = NULL; + mJointStates = NULL; + mLastSkeletonSerialNum = 0; + mLastLoopedTime = 0.f; + mLastUpdateTime = 0.f; + mAssetStatus = ASSET_UNDEFINED; + mPelvisp = NULL; +} + + +//----------------------------------------------------------------------------- +// ~LLKeyframeMotion() +// Class Destructor +//----------------------------------------------------------------------------- +LLKeyframeMotion::~LLKeyframeMotion() +{ + if (mJointStates) + { + delete [] mJointStates; + } + mConstraints.deleteAllData(); +} + +//----------------------------------------------------------------------------- +// create() +//----------------------------------------------------------------------------- +LLMotion *LLKeyframeMotion::create(const LLUUID &id) +{ + return new LLKeyframeMotion(id); +} + +//----------------------------------------------------------------------------- +// LLKeyframeMotion::onInitialize(LLCharacter *character) +//----------------------------------------------------------------------------- +LLMotion::LLMotionInitStatus LLKeyframeMotion::onInitialize(LLCharacter *character) +{ + mCharacter = character; + + LLUUID* character_id; + + // asset already loaded? + switch(mAssetStatus) + { + case ASSET_NEEDS_FETCH: + // request asset + mAssetStatus = ASSET_FETCHED; + + character_id = new LLUUID(mCharacter->getID()); + gAssetStorage->getAssetData(mID, + LLAssetType::AT_ANIMATION, + onLoadComplete, + (void *)character_id, + FALSE); + + return STATUS_HOLD; + case ASSET_FETCHED: + return STATUS_HOLD; + case ASSET_FETCH_FAILED: + return STATUS_FAILURE; + case ASSET_LOADED: + return STATUS_SUCCESS; + default: + // we don't know what state the asset is in yet, so keep going + // check keyframe cache first then static vfs then asset request + break; + } + + LLKeyframeMotion::JointMotionList* joint_motion_list = LLKeyframeDataCache::getKeyframeData(getID()); + + if(joint_motion_list) + { + // motion already existed in cache, so grab it + mJointMotionList = joint_motion_list; + + // don't forget to allocate joint states + mJointStates = new LLJointState[mJointMotionList->mNumJointMotions]; + + // set up joint states to point to character joints + for(U32 i = 0; i < mJointMotionList->mNumJointMotions; i++) + { + if (LLJoint *jointp = mCharacter->getJoint(mJointMotionList->mJointMotionArray[i].mJointName)) + { + mJointStates[i].setJoint(jointp); + mJointStates[i].setUsage(mJointMotionList->mJointMotionArray[i].mUsage); + mJointStates[i].setPriority(joint_motion_list->mJointMotionArray[i].mPriority); + } + } + mAssetStatus = ASSET_LOADED; + setupPose(); + return STATUS_SUCCESS; + } + + //------------------------------------------------------------------------- + // Load named file by concatenating the character prefix with the motion name. + // Load data into a buffer to be parsed. + //------------------------------------------------------------------------- + U8 *anim_data; + S32 anim_file_size; + + if (!sVFS) + { + llerrs << "Must call LLKeyframeMotion::setVFS() first before loading a keyframe file!" << llendl; + } + + BOOL success = FALSE; + LLVFile* anim_file = new LLVFile(sVFS, mID, LLAssetType::AT_ANIMATION); + if (!anim_file || !anim_file->getSize()) + { + delete anim_file; + anim_file = NULL; + + // request asset over network on next call to load + mAssetStatus = ASSET_NEEDS_FETCH; + + return STATUS_HOLD; + } + else + { + anim_file_size = anim_file->getSize(); + anim_data = new U8[anim_file_size]; + success = anim_file->read(anim_data, anim_file_size); /*Flawfinder: ignore*/ + delete anim_file; + anim_file = NULL; + } + + if (!success) + { + llwarns << "Can't open animation file " << mID << llendl; + mAssetStatus = ASSET_FETCH_FAILED; + return STATUS_FAILURE; + } + + lldebugs << "Loading keyframe data for: " << getName() << ":" << getID() << " (" << anim_file_size << " bytes)" << llendl; + + LLDataPackerBinaryBuffer dp(anim_data, anim_file_size); + + if (!deserialize(dp)) + { + llwarns << "Failed to decode asset for animation " << getName() << ":" << getID() << llendl; + mAssetStatus = ASSET_FETCH_FAILED; + return STATUS_FAILURE; + } + + delete []anim_data; + + mAssetStatus = ASSET_LOADED; + return STATUS_SUCCESS; +} + +//----------------------------------------------------------------------------- +// setupPose() +//----------------------------------------------------------------------------- +BOOL LLKeyframeMotion::setupPose() +{ + // add all valid joint states to the pose + U32 jm; + for (jm=0; jm<mJointMotionList->mNumJointMotions; jm++) + { + if ( mJointStates[jm].getJoint() ) + { + addJointState( &mJointStates[jm] ); + } + } + + // initialize joint constraints + for (JointConstraintSharedData* shared_constraintp = mJointMotionList->mConstraints.getFirstData(); + shared_constraintp; + shared_constraintp = mJointMotionList->mConstraints.getNextData()) + { + JointConstraint* constraintp = new JointConstraint(shared_constraintp); + initializeConstraint(constraintp); + mConstraints.addData(constraintp); + } + + if (mJointMotionList->mConstraints.getLength()) + { + mPelvisp = mCharacter->getJoint("mPelvis"); + if (!mPelvisp) + { + return FALSE; + } + } + + // setup loop keys + setLoopIn(mJointMotionList->mLoopInPoint); + setLoopOut(mJointMotionList->mLoopOutPoint); + + return TRUE; +} + +//----------------------------------------------------------------------------- +// LLKeyframeMotion::onActivate() +//----------------------------------------------------------------------------- +BOOL LLKeyframeMotion::onActivate() +{ + // If the keyframe anim has an associated emote, trigger it. + if( mEmoteName.length() > 0 ) + { + mCharacter->startMotion( gAnimLibrary.stringToAnimState(mEmoteName.c_str()) ); + } + + mLastLoopedTime = 0.f; + + return TRUE; +} + +//----------------------------------------------------------------------------- +// LLKeyframeMotion::onUpdate() +//----------------------------------------------------------------------------- +BOOL LLKeyframeMotion::onUpdate(F32 time, U8* joint_mask) +{ + llassert(time >= 0.f); + + if (mJointMotionList->mLoop) + { + if (mJointMotionList->mDuration == 0.0f) + { + time = 0.f; + mLastLoopedTime = 0.0f; + } + else if (mStopped) + { + mLastLoopedTime = llmin(mJointMotionList->mDuration, mLastLoopedTime + time - mLastUpdateTime); + } + else if (time > mJointMotionList->mLoopOutPoint) + { + if ((mJointMotionList->mLoopOutPoint - mJointMotionList->mLoopInPoint) == 0.f) + { + mLastLoopedTime = mJointMotionList->mLoopOutPoint; + } + else + { + mLastLoopedTime = mJointMotionList->mLoopInPoint + + fmod(time - mJointMotionList->mLoopOutPoint, + mJointMotionList->mLoopOutPoint - mJointMotionList->mLoopInPoint); + } + } + else + { + mLastLoopedTime = time; + } + } + else + { + mLastLoopedTime = time; + } + + applyKeyframes(mLastLoopedTime); + + applyConstraints(mLastLoopedTime, joint_mask); + + mLastUpdateTime = time; + + return mLastLoopedTime <= mJointMotionList->mDuration; +} + +//----------------------------------------------------------------------------- +// applyKeyframes() +//----------------------------------------------------------------------------- +void LLKeyframeMotion::applyKeyframes(F32 time) +{ + U32 i; + for (i=0; i<mJointMotionList->mNumJointMotions; i++) + { + mJointMotionList->mJointMotionArray[i].update( + &mJointStates[i], + time, + mJointMotionList->mDuration ); + } + + LLJoint::JointPriority* pose_priority = (LLJoint::JointPriority* )mCharacter->getAnimationData("Hand Pose Priority"); + if (pose_priority) + { + if (mJointMotionList->mMaxPriority >= *pose_priority) + { + mCharacter->setAnimationData("Hand Pose", &mJointMotionList->mHandPose); + mCharacter->setAnimationData("Hand Pose Priority", &mJointMotionList->mMaxPriority); + } + } + else + { + mCharacter->setAnimationData("Hand Pose", &mJointMotionList->mHandPose); + mCharacter->setAnimationData("Hand Pose Priority", &mJointMotionList->mMaxPriority); + } +} + +//----------------------------------------------------------------------------- +// applyConstraints() +//----------------------------------------------------------------------------- +void LLKeyframeMotion::applyConstraints(F32 time, U8* joint_mask) +{ + //TODO: investigate replacing spring simulation with critically damped motion + + // re-init constraints if skeleton has changed + if (mCharacter->getSkeletonSerialNum() != mLastSkeletonSerialNum) + { + mLastSkeletonSerialNum = mCharacter->getSkeletonSerialNum(); + for (JointConstraint* constraintp = mConstraints.getFirstData(); + constraintp; + constraintp = mConstraints.getNextData()) + { + initializeConstraint(constraintp); + } + } + + // apply constraints + for (JointConstraint* constraintp = mConstraints.getFirstData(); + constraintp; + constraintp = mConstraints.getNextData()) + { + applyConstraint(constraintp, time, joint_mask); + } +} + +//----------------------------------------------------------------------------- +// LLKeyframeMotion::onDeactivate() +//----------------------------------------------------------------------------- +void LLKeyframeMotion::onDeactivate() +{ + for (JointConstraint* constraintp = mConstraints.getFirstData(); + constraintp; + constraintp = mConstraints.getNextData()) + { + deactivateConstraint(constraintp); + } +} + +//----------------------------------------------------------------------------- +// setStopTime() +//----------------------------------------------------------------------------- +// time is in seconds since character creation +void LLKeyframeMotion::setStopTime(F32 time) +{ + LLMotion::setStopTime(time); + + if (mJointMotionList->mLoop && mJointMotionList->mLoopOutPoint != mJointMotionList->mDuration) + { + F32 start_loop_time = mActivationTimestamp + mJointMotionList->mLoopInPoint; + F32 loop_fraction_time; + if (mJointMotionList->mLoopOutPoint == mJointMotionList->mLoopInPoint) + { + loop_fraction_time = 0.f; + } + else + { + loop_fraction_time = fmod(time - start_loop_time, + mJointMotionList->mLoopOutPoint - mJointMotionList->mLoopInPoint); + } + mStopTimestamp = llmax(time, + (time - loop_fraction_time) + (mJointMotionList->mDuration - mJointMotionList->mLoopInPoint) - getEaseOutDuration()); + } +} + +//----------------------------------------------------------------------------- +// initializeConstraint() +//----------------------------------------------------------------------------- +void LLKeyframeMotion::initializeConstraint(JointConstraint* constraint) +{ + JointConstraintSharedData *shared_data = constraint->mSharedData; + + S32 joint_num; + LLVector3 source_pos = mCharacter->getVolumePos(shared_data->mSourceConstraintVolume, shared_data->mSourceConstraintOffset); + LLJoint* cur_joint = mJointStates[shared_data->mJointStateIndices[0]].getJoint(); + + F32 source_pos_offset = dist_vec(source_pos, cur_joint->getWorldPosition()); + + constraint->mTotalLength = constraint->mJointLengths[0] = dist_vec(cur_joint->getParent()->getWorldPosition(), source_pos); + + // grab joint lengths + for (joint_num = 1; joint_num < shared_data->mChainLength; joint_num++) + { + cur_joint = mJointStates[shared_data->mJointStateIndices[joint_num]].getJoint(); + if (!cur_joint) + { + return; + } + constraint->mJointLengths[joint_num] = dist_vec(cur_joint->getWorldPosition(), cur_joint->getParent()->getWorldPosition()); + constraint->mTotalLength += constraint->mJointLengths[joint_num]; + } + + // store fraction of total chain length so we know how to shear the entire chain towards the goal position + for (joint_num = 1; joint_num < shared_data->mChainLength; joint_num++) + { + constraint->mJointLengthFractions[joint_num] = constraint->mJointLengths[joint_num] / constraint->mTotalLength; + } + + // add last step in chain, from final joint to constraint position + constraint->mTotalLength += source_pos_offset; + + constraint->mSourceVolume = mCharacter->findCollisionVolume(shared_data->mSourceConstraintVolume); + constraint->mTargetVolume = mCharacter->findCollisionVolume(shared_data->mTargetConstraintVolume); +} + +//----------------------------------------------------------------------------- +// activateConstraint() +//----------------------------------------------------------------------------- +void LLKeyframeMotion::activateConstraint(JointConstraint* constraint) +{ + JointConstraintSharedData *shared_data = constraint->mSharedData; + constraint->mActive = TRUE; + S32 joint_num; + + // grab ground position if we need to + if (shared_data->mConstraintTargetType == TYPE_GROUND) + { + LLVector3 source_pos = mCharacter->getVolumePos(shared_data->mSourceConstraintVolume, shared_data->mSourceConstraintOffset); + LLVector3 ground_pos_agent; + mCharacter->getGround(source_pos, ground_pos_agent, constraint->mGroundNorm); + constraint->mGroundPos = mCharacter->getPosGlobalFromAgent(ground_pos_agent + shared_data->mTargetConstraintOffset); + } + + for (joint_num = 1; joint_num < shared_data->mChainLength; joint_num++) + { + LLJoint* cur_joint = mJointStates[shared_data->mJointStateIndices[joint_num]].getJoint(); + constraint->mPositions[joint_num] = (cur_joint->getWorldPosition() - mPelvisp->getWorldPosition()) * ~mPelvisp->getWorldRotation(); + } + + constraint->mWeight = 1.f; +} + +//----------------------------------------------------------------------------- +// deactivateConstraint() +//----------------------------------------------------------------------------- +void LLKeyframeMotion::deactivateConstraint(JointConstraint *constraintp) +{ + if (constraintp->mSourceVolume) + { + constraintp->mSourceVolume->mUpdateXform = FALSE; + } + + if (!constraintp->mSharedData->mConstraintTargetType == TYPE_GROUND) + { + if (constraintp->mTargetVolume) + { + constraintp->mTargetVolume->mUpdateXform = FALSE; + } + } + constraintp->mActive = FALSE; +} + +//----------------------------------------------------------------------------- +// applyConstraint() +//----------------------------------------------------------------------------- +void LLKeyframeMotion::applyConstraint(JointConstraint* constraint, F32 time, U8* joint_mask) +{ + JointConstraintSharedData *shared_data = constraint->mSharedData; + if (!shared_data) return; + + LLVector3 positions[MAX_CHAIN_LENGTH]; + const F32* joint_lengths = constraint->mJointLengths; + LLVector3 velocities[MAX_CHAIN_LENGTH - 1]; + LLQuaternion old_rots[MAX_CHAIN_LENGTH]; + S32 joint_num; + LLJoint* cur_joint; + + if (time < shared_data->mEaseInStartTime) + { + return; + } + + if (time > shared_data->mEaseOutStopTime) + { + if (constraint->mActive) + { + deactivateConstraint(constraint); + } + return; + } + + if (!constraint->mActive || time < shared_data->mEaseInStopTime) + { + activateConstraint(constraint); + } + + LLJoint* root_joint = mJointStates[shared_data->mJointStateIndices[shared_data->mChainLength]].getJoint(); + LLVector3 root_pos = root_joint->getWorldPosition(); +// LLQuaternion root_rot = + root_joint->getParent()->getWorldRotation(); +// LLQuaternion inv_root_rot = ~root_rot; + +// LLVector3 current_source_pos = mCharacter->getVolumePos(shared_data->mSourceConstraintVolume, shared_data->mSourceConstraintOffset); + + //apply underlying keyframe animation to get nominal "kinematic" joint positions + for (joint_num = 0; joint_num <= shared_data->mChainLength; joint_num++) + { + cur_joint = mJointStates[shared_data->mJointStateIndices[joint_num]].getJoint(); + if (joint_mask[cur_joint->getJointNum()] >= (0xff >> (7 - getPriority()))) + { + // skip constraint + return; + } + old_rots[joint_num] = cur_joint->getRotation(); + cur_joint->setRotation(mJointStates[shared_data->mJointStateIndices[joint_num]].getRotation()); + } + + + LLVector3 keyframe_source_pos = mCharacter->getVolumePos(shared_data->mSourceConstraintVolume, shared_data->mSourceConstraintOffset); + LLVector3 target_pos; + + switch(shared_data->mConstraintTargetType) + { + case TYPE_GROUND: + target_pos = mCharacter->getPosAgentFromGlobal(constraint->mGroundPos); +// llinfos << "Target Pos " << constraint->mGroundPos << " on " << mCharacter->findCollisionVolume(shared_data->mSourceConstraintVolume)->getName() << llendl; + break; + case TYPE_BODY: + target_pos = mCharacter->getVolumePos(shared_data->mTargetConstraintVolume, shared_data->mTargetConstraintOffset); + break; + default: + break; + } + + LLVector3 norm; + LLJoint *source_jointp = NULL; + LLJoint *target_jointp = NULL; + + if (shared_data->mConstraintType == TYPE_PLANE) + { + switch(shared_data->mConstraintTargetType) + { + case TYPE_GROUND: + norm = constraint->mGroundNorm; + break; + case TYPE_BODY: + target_jointp = mCharacter->findCollisionVolume(shared_data->mTargetConstraintVolume); + if (target_jointp) + { + // FIXME: do proper normal calculation for stretched spheres (inverse transpose) + norm = target_pos - target_jointp->getWorldPosition(); + } + + if (norm.isExactlyZero()) + { + source_jointp = mCharacter->findCollisionVolume(shared_data->mSourceConstraintVolume); + norm = -1.f * shared_data->mSourceConstraintOffset; + if (source_jointp) + { + norm = norm * source_jointp->getWorldRotation(); + } + } + norm.normVec(); + break; + default: + norm.clearVec(); + break; + } + + target_pos = keyframe_source_pos + (norm * ((target_pos - keyframe_source_pos) * norm)); + } + + if (constraint->mSharedData->mChainLength != 0 && + dist_vec_squared(root_pos, target_pos) * 0.95f > constraint->mTotalLength * constraint->mTotalLength) + { + constraint->mWeight = lerp(constraint->mWeight, 0.f, LLCriticalDamp::getInterpolant(0.1f)); + } + else + { + constraint->mWeight = lerp(constraint->mWeight, 1.f, LLCriticalDamp::getInterpolant(0.3f)); + } + + F32 weight = constraint->mWeight * ((shared_data->mEaseOutStopTime == 0.f) ? 1.f : + llmin(clamp_rescale(time, shared_data->mEaseInStartTime, shared_data->mEaseInStopTime, 0.f, 1.f), + clamp_rescale(time, shared_data->mEaseOutStartTime, shared_data->mEaseOutStopTime, 1.f, 0.f))); + + LLVector3 source_to_target = target_pos - keyframe_source_pos; + + S32 max_iteration_count = llround(clamp_rescale( + mCharacter->getPixelArea(), + MAX_PIXEL_AREA_CONSTRAINTS, + MIN_PIXEL_AREA_CONSTRAINTS, + (F32)MAX_ITERATIONS, + (F32)MIN_ITERATIONS)); + + if (shared_data->mChainLength) + { + LLQuaternion end_rot = mJointStates[shared_data->mJointStateIndices[0]].getJoint()->getWorldRotation(); + + // slam start and end of chain to the proper positions (rest of chain stays put) + positions[0] = lerp(keyframe_source_pos, target_pos, weight); + positions[shared_data->mChainLength] = root_pos; + + // grab keyframe-specified positions of joints + for (joint_num = 1; joint_num < shared_data->mChainLength; joint_num++) + { + LLVector3 kinematic_position = mJointStates[shared_data->mJointStateIndices[joint_num]].getJoint()->getWorldPosition() + + (source_to_target * constraint->mJointLengthFractions[joint_num]); + + // convert intermediate joint positions to world coordinates + positions[joint_num] = ( constraint->mPositions[joint_num] * mPelvisp->getWorldRotation()) + mPelvisp->getWorldPosition(); + F32 time_constant = 1.f / clamp_rescale(constraint->mFixupDistanceRMS, 0.f, 0.5f, 0.2f, 8.f); +// llinfos << "Interpolant " << LLCriticalDamp::getInterpolant(time_constant, FALSE) << " and fixup distance " << constraint->mFixupDistanceRMS << " on " << mCharacter->findCollisionVolume(shared_data->mSourceConstraintVolume)->getName() << llendl; + positions[joint_num] = lerp(positions[joint_num], kinematic_position, + LLCriticalDamp::getInterpolant(time_constant, FALSE)); + } + + S32 iteration_count; + for (iteration_count = 0; iteration_count < max_iteration_count; iteration_count++) + { + S32 num_joints_finished = 0; + for (joint_num = 1; joint_num < shared_data->mChainLength; joint_num++) + { + // constraint to child + LLVector3 acceleration = (positions[joint_num - 1] - positions[joint_num]) * + (dist_vec(positions[joint_num], positions[joint_num - 1]) - joint_lengths[joint_num - 1]) * JOINT_LENGTH_K; + // constraint to parent + acceleration += (positions[joint_num + 1] - positions[joint_num]) * + (dist_vec(positions[joint_num + 1], positions[joint_num]) - joint_lengths[joint_num]) * JOINT_LENGTH_K; + + if (acceleration.magVecSquared() < MIN_ACCELERATION_SQUARED) + { + num_joints_finished++; + } + + velocities[joint_num - 1] = velocities[joint_num - 1] * 0.7f; + positions[joint_num] += velocities[joint_num - 1] + (acceleration * 0.5f); + velocities[joint_num - 1] += acceleration; + } + + if ((iteration_count >= MIN_ITERATION_COUNT) && + (num_joints_finished == shared_data->mChainLength - 1)) + { +// llinfos << iteration_count << " iterations on " << +// mCharacter->findCollisionVolume(shared_data->mSourceConstraintVolume)->getName() << llendl; + break; + } + } + + for (joint_num = shared_data->mChainLength; joint_num > 0; joint_num--) + { + LLQuaternion parent_rot = mJointStates[shared_data->mJointStateIndices[joint_num]].getJoint()->getParent()->getWorldRotation(); + cur_joint = mJointStates[shared_data->mJointStateIndices[joint_num]].getJoint(); + LLJoint* child_joint = mJointStates[shared_data->mJointStateIndices[joint_num - 1]].getJoint(); + + LLQuaternion cur_rot = cur_joint->getWorldRotation(); + LLQuaternion fixup_rot; + + LLVector3 target_at = positions[joint_num - 1] - positions[joint_num]; + LLVector3 current_at; + + // at bottom of chain, use point on collision volume, not joint position + if (joint_num == 1) + { + current_at = mCharacter->getVolumePos(shared_data->mSourceConstraintVolume, shared_data->mSourceConstraintOffset) - + cur_joint->getWorldPosition(); + } + else + { + current_at = child_joint->getPosition() * cur_rot; + } + fixup_rot.shortestArc(current_at, target_at); + + LLQuaternion target_rot = cur_rot * fixup_rot; + target_rot = target_rot * ~parent_rot; + + if (weight != 1.f) + { + LLQuaternion cur_rot = mJointStates[shared_data->mJointStateIndices[joint_num]].getRotation(); + target_rot = nlerp(weight, cur_rot, target_rot); + } + + mJointStates[shared_data->mJointStateIndices[joint_num]].setRotation(target_rot); + cur_joint->setRotation(target_rot); + } + + LLJoint* end_joint = mJointStates[shared_data->mJointStateIndices[0]].getJoint(); + LLQuaternion end_local_rot = end_rot * ~end_joint->getParent()->getWorldRotation(); + + if (weight == 1.f) + { + mJointStates[shared_data->mJointStateIndices[0]].setRotation(end_local_rot); + } + else + { + LLQuaternion cur_rot = mJointStates[shared_data->mJointStateIndices[0]].getRotation(); + mJointStates[shared_data->mJointStateIndices[0]].setRotation(nlerp(weight, cur_rot, end_local_rot)); + } + + // save simulated positions in pelvis-space and calculate total fixup distance + constraint->mFixupDistanceRMS = 0.f; + F32 delta_time = llmax(0.02f, llabs(time - mLastUpdateTime)); + for (joint_num = 1; joint_num < shared_data->mChainLength; joint_num++) + { + LLVector3 new_pos = (positions[joint_num] - mPelvisp->getWorldPosition()) * ~mPelvisp->getWorldRotation(); + constraint->mFixupDistanceRMS += dist_vec_squared(new_pos, constraint->mPositions[joint_num]) / delta_time; + constraint->mPositions[joint_num] = new_pos; + } + constraint->mFixupDistanceRMS *= 1.f / (constraint->mTotalLength * (F32)(shared_data->mChainLength - 1)); + constraint->mFixupDistanceRMS = fsqrtf(constraint->mFixupDistanceRMS); + + //reset old joint rots + for (joint_num = 0; joint_num <= shared_data->mChainLength; joint_num++) + { + mJointStates[shared_data->mJointStateIndices[joint_num]].getJoint()->setRotation(old_rots[joint_num]); + } + } + // simple positional constraint (pelvis only) + else if (mJointStates[shared_data->mJointStateIndices[0]].getUsage() & LLJointState::POS) + { + LLVector3 delta = source_to_target * weight; + LLJointState* current_joint_statep = &mJointStates[shared_data->mJointStateIndices[0]]; + LLQuaternion parent_rot = current_joint_statep->getJoint()->getParent()->getWorldRotation(); + delta = delta * ~parent_rot; + current_joint_statep->setPosition(current_joint_statep->getJoint()->getPosition() + delta); + } +} + +//----------------------------------------------------------------------------- +// deserialize() +//----------------------------------------------------------------------------- +BOOL LLKeyframeMotion::deserialize(LLDataPacker& dp) +{ + BOOL old_version = FALSE; + mJointMotionList = new LLKeyframeMotion::JointMotionList; + mJointMotionList->mNumJointMotions = 0; + + //------------------------------------------------------------------------- + // get base priority + //------------------------------------------------------------------------- + S32 temp_priority; + U16 version; + U16 sub_version; + + if (!dp.unpackU16(version, "version")) + { + llwarns << "can't read version number" << llendl; + return FALSE; + } + + if (!dp.unpackU16(sub_version, "sub_version")) + { + llwarns << "can't read sub version number" << llendl; + return FALSE; + } + + if (version == 0 && sub_version == 1) + { + old_version = TRUE; + } + else if (version != KEYFRAME_MOTION_VERSION || sub_version != KEYFRAME_MOTION_SUBVERSION) + { +#if LL_RELEASE + llwarns << "Bad animation version " << version << "." << sub_version << llendl; + return FALSE; +#else + llerrs << "Bad animation version " << version << "." << sub_version << llendl; +#endif + } + + if (!dp.unpackS32(temp_priority, "base_priority")) + { + llwarns << "can't read priority" << llendl; + return FALSE; + } + mJointMotionList->mBasePriority = (LLJoint::JointPriority) temp_priority; + + if (mJointMotionList->mBasePriority >= LLJoint::ADDITIVE_PRIORITY) + { + mJointMotionList->mBasePriority = (LLJoint::JointPriority)((int)LLJoint::ADDITIVE_PRIORITY-1); + mJointMotionList->mMaxPriority = mJointMotionList->mBasePriority; + } + + //------------------------------------------------------------------------- + // get duration + //------------------------------------------------------------------------- + if (!dp.unpackF32(mJointMotionList->mDuration, "duration")) + { + llwarns << "can't read duration" << llendl; + return FALSE; + } + + //------------------------------------------------------------------------- + // get emote (optional) + //------------------------------------------------------------------------- + char read_string[128]; + if (!dp.unpackString(read_string, "emote_name")) + { + llwarns << "can't read optional_emote_animation" << llendl; + return FALSE; + } + + mEmoteName.assign( read_string ); + + //------------------------------------------------------------------------- + // get loop + //------------------------------------------------------------------------- + if (!dp.unpackF32(mJointMotionList->mLoopInPoint, "loop_in_point")) + { + llwarns << "can't read loop point" << llendl; + return FALSE; + } + + if (!dp.unpackF32(mJointMotionList->mLoopOutPoint, "loop_out_point")) + { + llwarns << "can't read loop point" << llendl; + return FALSE; + } + + if (!dp.unpackS32(mJointMotionList->mLoop, "loop")) + { + llwarns << "can't read loop" << llendl; + return FALSE; + } + + //------------------------------------------------------------------------- + // get easeIn and easeOut + //------------------------------------------------------------------------- + if (!dp.unpackF32(mJointMotionList->mEaseInDuration, "ease_in_duration")) + { + llwarns << "can't read easeIn" << llendl; + return FALSE; + } + + if (!dp.unpackF32(mJointMotionList->mEaseOutDuration, "ease_out_duration")) + { + llwarns << "can't read easeOut" << llendl; + return FALSE; + } + + //------------------------------------------------------------------------- + // get hand pose + //------------------------------------------------------------------------- + U32 word; + if (!dp.unpackU32(word, "hand_pose")) + { + llwarns << "can't read hand pose" << llendl; + return FALSE; + } + mJointMotionList->mHandPose = (LLHandMotion::eHandPose)word; + + //------------------------------------------------------------------------- + // get number of joint motions + //------------------------------------------------------------------------- + if (!dp.unpackU32(mJointMotionList->mNumJointMotions, "num_joints")) + { + llwarns << "can't read number of joints" << llendl; + return FALSE; + } + + if (mJointMotionList->mNumJointMotions == 0) + { + llwarns << "no joints in animation" << llendl; + return FALSE; + } + else if (mJointMotionList->mNumJointMotions > LL_CHARACTER_MAX_JOINTS) + { + llwarns << "too many joints in animation" << llendl; + return FALSE; + } + + mJointMotionList->mJointMotionArray = new JointMotion[mJointMotionList->mNumJointMotions]; + mJointStates = new LLJointState[mJointMotionList->mNumJointMotions]; + + if (!mJointMotionList->mJointMotionArray) + { + mJointMotionList->mDuration = 0.0f; + mJointMotionList->mEaseInDuration = 0.0f; + mJointMotionList->mEaseOutDuration = 0.0f; + return FALSE; + } + + //------------------------------------------------------------------------- + // initialize joint motions + //------------------------------------------------------------------------- + S32 k; + for(U32 i=0; i<mJointMotionList->mNumJointMotions; ++i) + { + if (!dp.unpackString(read_string, "joint_name")) + { + llwarns << "can't read joint name" << llendl; + return FALSE; + } + + //--------------------------------------------------------------------- + // find the corresponding joint + //--------------------------------------------------------------------- + LLJoint *joint = mCharacter->getJoint( read_string ); + if (joint) + { +// llinfos << " joint: " << read_string << llendl; + } + else + { + llwarns << "joint not found: " << read_string << llendl; + //return FALSE; + } + + mJointMotionList->mJointMotionArray[i].mJointName = read_string; + mJointStates[i].setJoint( joint ); + mJointStates[i].setUsage( 0 ); + + //--------------------------------------------------------------------- + // get joint priority + //--------------------------------------------------------------------- + S32 joint_priority; + if (!dp.unpackS32(joint_priority, "joint_priority")) + { + llwarns << "can't read joint priority." << llendl; + return FALSE; + } + + mJointMotionList->mJointMotionArray[i].mPriority = (LLJoint::JointPriority)joint_priority; + if (joint_priority != LLJoint::USE_MOTION_PRIORITY && + joint_priority > mJointMotionList->mMaxPriority) + { + mJointMotionList->mMaxPriority = (LLJoint::JointPriority)joint_priority; + } + + mJointStates[i].setPriority((LLJoint::JointPriority)joint_priority); + + //--------------------------------------------------------------------- + // scan rotation curve header + //--------------------------------------------------------------------- + if (!dp.unpackS32(mJointMotionList->mJointMotionArray[i].mRotationCurve.mNumKeys, "num_rot_keys")) + { + llwarns << "can't read number of rotation keys" << llendl; + return FALSE; + } + + mJointMotionList->mJointMotionArray[i].mRotationCurve.mInterpolationType = IT_LINEAR; + if (mJointMotionList->mJointMotionArray[i].mRotationCurve.mNumKeys != 0) + { + mJointStates[i].setUsage(mJointStates[i].getUsage() | LLJointState::ROT ); + } + + //--------------------------------------------------------------------- + // scan rotation curve keys + //--------------------------------------------------------------------- + RotationCurve *rCurve = &mJointMotionList->mJointMotionArray[i].mRotationCurve; + + for (k = 0; k < mJointMotionList->mJointMotionArray[i].mRotationCurve.mNumKeys; k++) + { + F32 time; + U16 time_short; + + if (old_version) + { + if (!dp.unpackF32(time, "time")) + { + llwarns << "can't read rotation key (" << k << ")" << llendl; + return FALSE; + } + + } + else + { + if (!dp.unpackU16(time_short, "time")) + { + llwarns << "can't read rotation key (" << k << ")" << llendl; + return FALSE; + } + + time = U16_to_F32(time_short, 0.f, mJointMotionList->mDuration); + } + + RotationKey *rot_key = new RotationKey; + rot_key->mTime = time; + LLVector3 rot_angles; + U16 x, y, z; + + BOOL success = TRUE; + + if (old_version) + { + success = dp.unpackVector3(rot_angles, "rot_angles"); + + LLQuaternion::Order ro = StringToOrder("ZYX"); + rot_key->mRotation = mayaQ(rot_angles.mV[VX], rot_angles.mV[VY], rot_angles.mV[VZ], ro); + } + else + { + success &= dp.unpackU16(x, "rot_angle_x"); + success &= dp.unpackU16(y, "rot_angle_y"); + success &= dp.unpackU16(z, "rot_angle_z"); + + LLVector3 rot_vec; + rot_vec.mV[VX] = U16_to_F32(x, -1.f, 1.f); + rot_vec.mV[VY] = U16_to_F32(y, -1.f, 1.f); + rot_vec.mV[VZ] = U16_to_F32(z, -1.f, 1.f); + rot_key->mRotation.unpackFromVector3(rot_vec); + } + + if (!success) + { + llwarns << "can't read rotation key (" << k << ")" << llendl; + return FALSE; + } + + rCurve->mKeys[time] = rot_key; + } + + //--------------------------------------------------------------------- + // scan position curve header + //--------------------------------------------------------------------- + if (!dp.unpackS32(mJointMotionList->mJointMotionArray[i].mPositionCurve.mNumKeys, "num_pos_keys")) + { + llwarns << "can't read number of position keys" << llendl; + return FALSE; + } + + mJointMotionList->mJointMotionArray[i].mPositionCurve.mInterpolationType = IT_LINEAR; + if (mJointMotionList->mJointMotionArray[i].mPositionCurve.mNumKeys != 0) + { + mJointStates[i].setUsage(mJointStates[i].getUsage() | LLJointState::POS ); + } + + //--------------------------------------------------------------------- + // scan position curve keys + //--------------------------------------------------------------------- + PositionCurve *pCurve = &mJointMotionList->mJointMotionArray[i].mPositionCurve; + BOOL is_pelvis = mJointMotionList->mJointMotionArray[i].mJointName == "mPelvis"; + for (k = 0; k < mJointMotionList->mJointMotionArray[i].mPositionCurve.mNumKeys; k++) + { + U16 time_short; + PositionKey* pos_key = new PositionKey; + + if (old_version) + { + if (!dp.unpackF32(pos_key->mTime, "time")) + { + llwarns << "can't read position key (" << k << ")" << llendl; + delete pos_key; + return FALSE; + } + } + else + { + if (!dp.unpackU16(time_short, "time")) + { + llwarns << "can't read position key (" << k << ")" << llendl; + delete pos_key; + return FALSE; + } + + pos_key->mTime = U16_to_F32(time_short, 0.f, mJointMotionList->mDuration); + } + + BOOL success = TRUE; + + if (old_version) + { + success = dp.unpackVector3(pos_key->mPosition, "pos"); + } + else + { + U16 x, y, z; + + success &= dp.unpackU16(x, "pos_x"); + success &= dp.unpackU16(y, "pos_y"); + success &= dp.unpackU16(z, "pos_z"); + + pos_key->mPosition.mV[VX] = U16_to_F32(x, -LL_MAX_PELVIS_OFFSET, LL_MAX_PELVIS_OFFSET); + pos_key->mPosition.mV[VY] = U16_to_F32(y, -LL_MAX_PELVIS_OFFSET, LL_MAX_PELVIS_OFFSET); + pos_key->mPosition.mV[VZ] = U16_to_F32(z, -LL_MAX_PELVIS_OFFSET, LL_MAX_PELVIS_OFFSET); + } + + if (!success) + { + llwarns << "can't read position key (" << k << ")" << llendl; + delete pos_key; + return FALSE; + } + + pCurve->mKeys[pos_key->mTime] = pos_key; + + if (is_pelvis) + { + mJointMotionList->mPelvisBBox.addPoint(pos_key->mPosition); + } + } + + mJointMotionList->mJointMotionArray[i].mUsage = mJointStates[i].getUsage(); + } + + //------------------------------------------------------------------------- + // get number of constraints + //------------------------------------------------------------------------- + S32 num_constraints = 0; + if (!dp.unpackS32(num_constraints, "num_constraints")) + { + llwarns << "can't read number of constraints" << llendl; + return FALSE; + } + + if (num_constraints > MAX_CONSTRAINTS) + { + llwarns << "Too many constraints...ignoring" << llendl; + } + else + { + //------------------------------------------------------------------------- + // get constraints + //------------------------------------------------------------------------- + std::string str; + for(S32 i = 0; i < num_constraints; ++i) + { + // read in constraint data + JointConstraintSharedData* constraintp = new JointConstraintSharedData; + U8 byte = 0; + + if (!dp.unpackU8(byte, "chain_length")) + { + llwarns << "can't read constraint chain length" << llendl; + return FALSE; + } + constraintp->mChainLength = (S32) byte; + + if (!dp.unpackU8(byte, "constraint_type")) + { + llwarns << "can't read constraint type" << llendl; + return FALSE; + } + constraintp->mConstraintType = (EConstraintType)byte; + + if (!dp.unpackBinaryDataFixed((unsigned char*)read_string, 16, "source_volume")) + { + llwarns << "can't read source volume name" << llendl; + return FALSE; + } + + str.assign(read_string); + constraintp->mSourceConstraintVolume = mCharacter->getCollisionVolumeID(str); + + if (!dp.unpackVector3(constraintp->mSourceConstraintOffset, "source_offset")) + { + llwarns << "can't read constraint source offset" << llendl; + return FALSE; + } + + if (!dp.unpackBinaryDataFixed((unsigned char*)read_string, 16, "target_volume")) + { + llwarns << "can't read target volume name" << llendl; + return FALSE; + } + + str.assign(read_string); + if (str == "GROUND") + { + // constrain to ground + constraintp->mConstraintTargetType = TYPE_GROUND; + } + else + { + constraintp->mConstraintTargetType = TYPE_BODY; + constraintp->mTargetConstraintVolume = mCharacter->getCollisionVolumeID(str); + } + + if (!dp.unpackVector3(constraintp->mTargetConstraintOffset, "target_offset")) + { + llwarns << "can't read constraint target offset" << llendl; + return FALSE; + } + + if (!dp.unpackVector3(constraintp->mTargetConstraintDir, "target_dir")) + { + llwarns << "can't read constraint target direction" << llendl; + return FALSE; + } + + if (!constraintp->mTargetConstraintDir.isExactlyZero()) + { + constraintp->mUseTargetOffset = TRUE; + // constraintp->mTargetConstraintDir *= constraintp->mSourceConstraintOffset.magVec(); + } + + if (!dp.unpackF32(constraintp->mEaseInStartTime, "ease_in_start")) + { + llwarns << "can't read constraint ease in start time" << llendl; + return FALSE; + } + + if (!dp.unpackF32(constraintp->mEaseInStopTime, "ease_in_stop")) + { + llwarns << "can't read constraint ease in stop time" << llendl; + return FALSE; + } + + if (!dp.unpackF32(constraintp->mEaseOutStartTime, "ease_out_start")) + { + llwarns << "can't read constraint ease out start time" << llendl; + return FALSE; + } + + if (!dp.unpackF32(constraintp->mEaseOutStopTime, "ease_out_stop")) + { + llwarns << "can't read constraint ease out stop time" << llendl; + return FALSE; + } + + mJointMotionList->mConstraints.addData(constraintp); + + constraintp->mJointStateIndices = new S32[constraintp->mChainLength + 1]; + + LLJoint* joint = mCharacter->findCollisionVolume(constraintp->mSourceConstraintVolume); + if (!joint) + { + return FALSE; + } + + // get joint to which this collision volume is attached + joint = joint->getParent(); + + for (S32 i = 0; i < constraintp->mChainLength + 1; i++) + { + constraintp->mJointStateIndices[i] = -1; + for (U32 j = 0; j < mJointMotionList->mNumJointMotions; j++) + { + if(mJointStates[j].getJoint() == joint) + { + constraintp->mJointStateIndices[i] = (S32)j; + break; + } + } + joint = joint->getParent(); + } + + } + } + + // FIXME: support cleanup of old keyframe data + LLKeyframeDataCache::addKeyframeData(getID(), mJointMotionList); + mAssetStatus = ASSET_LOADED; + + setupPose(); + + return TRUE; +} + +//----------------------------------------------------------------------------- +// serialize() +//----------------------------------------------------------------------------- +BOOL LLKeyframeMotion::serialize(LLDataPacker& dp) const +{ + BOOL success = TRUE; + + success &= dp.packU16(KEYFRAME_MOTION_VERSION, "version"); + success &= dp.packU16(KEYFRAME_MOTION_SUBVERSION, "sub_version"); + success &= dp.packS32(mJointMotionList->mBasePriority, "base_priority"); + success &= dp.packF32(mJointMotionList->mDuration, "duration"); + success &= dp.packString(mEmoteName.c_str(), "emote_name"); + success &= dp.packF32(mJointMotionList->mLoopInPoint, "loop_in_point"); + success &= dp.packF32(mJointMotionList->mLoopOutPoint, "loop_out_point"); + success &= dp.packS32(mJointMotionList->mLoop, "loop"); + success &= dp.packF32(mJointMotionList->mEaseInDuration, "ease_in_duration"); + success &= dp.packF32(mJointMotionList->mEaseOutDuration, "ease_out_duration"); + success &= dp.packU32(mJointMotionList->mHandPose, "hand_pose"); + success &= dp.packU32(mJointMotionList->mNumJointMotions, "num_joints"); + + for (U32 i = 0; i < mJointMotionList->mNumJointMotions; i++) + { + JointMotion* joint_motionp = &mJointMotionList->mJointMotionArray[i]; + success &= dp.packString(joint_motionp->mJointName.c_str(), "joint_name"); + success &= dp.packS32(joint_motionp->mPriority, "joint_priority"); + success &= dp.packS32(joint_motionp->mRotationCurve.mNumKeys, "num_rot_keys"); + + for (RotationKey* rot_keyp = joint_motionp->mRotationCurve.mKeys.getFirstData(); + rot_keyp; + rot_keyp = joint_motionp->mRotationCurve.mKeys.getNextData()) + { + U16 time_short = F32_to_U16(rot_keyp->mTime, 0.f, mJointMotionList->mDuration); + success &= dp.packU16(time_short, "time"); + + LLVector3 rot_angles = rot_keyp->mRotation.packToVector3(); + + U16 x, y, z; + rot_angles.quantize16(-1.f, 1.f, -1.f, 1.f); + x = F32_to_U16(rot_angles.mV[VX], -1.f, 1.f); + y = F32_to_U16(rot_angles.mV[VY], -1.f, 1.f); + z = F32_to_U16(rot_angles.mV[VZ], -1.f, 1.f); + success &= dp.packU16(x, "rot_angle_x"); + success &= dp.packU16(y, "rot_angle_y"); + success &= dp.packU16(z, "rot_angle_z"); + } + + success &= dp.packS32(joint_motionp->mPositionCurve.mNumKeys, "num_pos_keys"); + for (PositionKey* pos_keyp = joint_motionp->mPositionCurve.mKeys.getFirstData(); + pos_keyp; + pos_keyp = joint_motionp->mPositionCurve.mKeys.getNextData()) + { + U16 time_short = F32_to_U16(pos_keyp->mTime, 0.f, mJointMotionList->mDuration); + success &= dp.packU16(time_short, "time"); + + U16 x, y, z; + pos_keyp->mPosition.quantize16(-LL_MAX_PELVIS_OFFSET, LL_MAX_PELVIS_OFFSET, -LL_MAX_PELVIS_OFFSET, LL_MAX_PELVIS_OFFSET); + x = F32_to_U16(pos_keyp->mPosition.mV[VX], -LL_MAX_PELVIS_OFFSET, LL_MAX_PELVIS_OFFSET); + y = F32_to_U16(pos_keyp->mPosition.mV[VY], -LL_MAX_PELVIS_OFFSET, LL_MAX_PELVIS_OFFSET); + z = F32_to_U16(pos_keyp->mPosition.mV[VZ], -LL_MAX_PELVIS_OFFSET, LL_MAX_PELVIS_OFFSET); + success &= dp.packU16(x, "pos_x"); + success &= dp.packU16(y, "pos_y"); + success &= dp.packU16(z, "pos_z"); + } + } + + success &= dp.packS32(mJointMotionList->mConstraints.getLength(), "num_constraints"); + for (JointConstraintSharedData* shared_constraintp = mJointMotionList->mConstraints.getFirstData(); + shared_constraintp; + shared_constraintp = mJointMotionList->mConstraints.getNextData()) + { + success &= dp.packU8(shared_constraintp->mChainLength, "chain_length"); + success &= dp.packU8(shared_constraintp->mConstraintType, "constraint_type"); + char volume_name[16]; + snprintf(volume_name, sizeof(volume_name), "%s", + mCharacter->findCollisionVolume(shared_constraintp->mSourceConstraintVolume)->getName().c_str()); /* Flawfinder: ignore */ + success &= dp.packBinaryDataFixed((U8*)volume_name, 16, "source_volume"); + success &= dp.packVector3(shared_constraintp->mSourceConstraintOffset, "source_offset"); + if (shared_constraintp->mConstraintTargetType == TYPE_GROUND) + { + snprintf(volume_name,sizeof(volume_name), "%s", "GROUND"); /* Flawfinder: ignore */ + } + else + { + snprintf(volume_name, sizeof(volume_name),"%s", + mCharacter->findCollisionVolume(shared_constraintp->mTargetConstraintVolume)->getName().c_str()); /* Flawfinder: ignore */ + } + success &= dp.packBinaryDataFixed((U8*)volume_name, 16, "target_volume"); + success &= dp.packVector3(shared_constraintp->mTargetConstraintOffset, "target_offset"); + success &= dp.packVector3(shared_constraintp->mTargetConstraintDir, "target_dir"); + success &= dp.packF32(shared_constraintp->mEaseInStartTime, "ease_in_start"); + success &= dp.packF32(shared_constraintp->mEaseInStopTime, "ease_in_stop"); + success &= dp.packF32(shared_constraintp->mEaseOutStartTime, "ease_out_start"); + success &= dp.packF32(shared_constraintp->mEaseOutStopTime, "ease_out_stop"); + } + + return success; +} + +//----------------------------------------------------------------------------- +// getFileSize() +//----------------------------------------------------------------------------- +U32 LLKeyframeMotion::getFileSize() +{ + // serialize into a dummy buffer to calculate required size + LLDataPackerBinaryBuffer dp; + serialize(dp); + + return dp.getCurrentSize(); +} + +//----------------------------------------------------------------------------- +// getPelvisBBox() +//----------------------------------------------------------------------------- +const LLBBoxLocal &LLKeyframeMotion::getPelvisBBox() +{ + return mJointMotionList->mPelvisBBox; +} + +//----------------------------------------------------------------------------- +// setPriority() +//----------------------------------------------------------------------------- +void LLKeyframeMotion::setPriority(S32 priority) +{ + if (mJointMotionList) + { + S32 priority_delta = priority - mJointMotionList->mBasePriority; + mJointMotionList->mBasePriority = (LLJoint::JointPriority)priority; + mJointMotionList->mMaxPriority = mJointMotionList->mBasePriority; + + for (U32 i = 0; i < mJointMotionList->mNumJointMotions; i++) + { + mJointMotionList->mJointMotionArray[i].mPriority = (LLJoint::JointPriority)llclamp( + (S32)mJointMotionList->mJointMotionArray[i].mPriority + priority_delta, + (S32)LLJoint::LOW_PRIORITY, + (S32)LLJoint::HIGHEST_PRIORITY); + mJointStates[i].setPriority(mJointMotionList->mJointMotionArray[i].mPriority); + } + } +} + +//----------------------------------------------------------------------------- +// setEmote() +//----------------------------------------------------------------------------- +void LLKeyframeMotion::setEmote(const LLUUID& emote_id) +{ + const char* emote_name = gAnimLibrary.animStateToString(emote_id); + if (emote_name) + { + mEmoteName = emote_name; + } + else + { + mEmoteName = ""; + } +} + +//----------------------------------------------------------------------------- +// setEaseIn() +//----------------------------------------------------------------------------- +void LLKeyframeMotion::setEaseIn(F32 ease_in) +{ + if (mJointMotionList) + { + mJointMotionList->mEaseInDuration = llmax(ease_in, 0.f); + } +} + +//----------------------------------------------------------------------------- +// setEaseOut() +//----------------------------------------------------------------------------- +void LLKeyframeMotion::setEaseOut(F32 ease_in) +{ + if (mJointMotionList) + { + mJointMotionList->mEaseOutDuration = llmax(ease_in, 0.f); + } +} + + +//----------------------------------------------------------------------------- +// flushKeyframeCache() +//----------------------------------------------------------------------------- +void LLKeyframeMotion::flushKeyframeCache() +{ + LLKeyframeDataCache::clear(); +} + +//----------------------------------------------------------------------------- +// setLoop() +//----------------------------------------------------------------------------- +void LLKeyframeMotion::setLoop(BOOL loop) +{ + if (mJointMotionList) + { + mJointMotionList->mLoop = loop; + mSendStopTimestamp = F32_MAX; + } +} + + +//----------------------------------------------------------------------------- +// setLoopIn() +//----------------------------------------------------------------------------- +void LLKeyframeMotion::setLoopIn(F32 in_point) +{ + if (mJointMotionList) + { + mJointMotionList->mLoopInPoint = in_point; + + // set up loop keys + for (U32 i = 0; i < mJointMotionList->mNumJointMotions; i++) + { + PositionCurve* pos_curve = &mJointMotionList->mJointMotionArray[i].mPositionCurve; + RotationCurve* rot_curve = &mJointMotionList->mJointMotionArray[i].mRotationCurve; + ScaleCurve* scale_curve = &mJointMotionList->mJointMotionArray[i].mScaleCurve; + + pos_curve->mLoopInKey.mTime = mJointMotionList->mLoopInPoint; + rot_curve->mLoopInKey.mTime = mJointMotionList->mLoopInPoint; + scale_curve->mLoopInKey.mTime = mJointMotionList->mLoopInPoint; + + pos_curve->mLoopInKey.mPosition = pos_curve->getValue(mJointMotionList->mLoopInPoint, mJointMotionList->mDuration); + rot_curve->mLoopInKey.mRotation = rot_curve->getValue(mJointMotionList->mLoopInPoint, mJointMotionList->mDuration); + scale_curve->mLoopInKey.mScale = scale_curve->getValue(mJointMotionList->mLoopInPoint, mJointMotionList->mDuration); + } + } +} + +//----------------------------------------------------------------------------- +// setLoopOut() +//----------------------------------------------------------------------------- +void LLKeyframeMotion::setLoopOut(F32 out_point) +{ + if (mJointMotionList) + { + mJointMotionList->mLoopOutPoint = out_point; + + // set up loop keys + for (U32 i = 0; i < mJointMotionList->mNumJointMotions; i++) + { + PositionCurve* pos_curve = &mJointMotionList->mJointMotionArray[i].mPositionCurve; + RotationCurve* rot_curve = &mJointMotionList->mJointMotionArray[i].mRotationCurve; + ScaleCurve* scale_curve = &mJointMotionList->mJointMotionArray[i].mScaleCurve; + + pos_curve->mLoopOutKey.mTime = mJointMotionList->mLoopOutPoint; + rot_curve->mLoopOutKey.mTime = mJointMotionList->mLoopOutPoint; + scale_curve->mLoopOutKey.mTime = mJointMotionList->mLoopOutPoint; + + pos_curve->mLoopOutKey.mPosition = pos_curve->getValue(mJointMotionList->mLoopOutPoint, mJointMotionList->mDuration); + rot_curve->mLoopOutKey.mRotation = rot_curve->getValue(mJointMotionList->mLoopOutPoint, mJointMotionList->mDuration); + scale_curve->mLoopOutKey.mScale = scale_curve->getValue(mJointMotionList->mLoopOutPoint, mJointMotionList->mDuration); + } + } +} + +//----------------------------------------------------------------------------- +// onLoadComplete() +//----------------------------------------------------------------------------- +void LLKeyframeMotion::onLoadComplete(LLVFS *vfs, + const LLUUID& asset_uuid, + LLAssetType::EType type, + void* user_data, S32 status) +{ + LLUUID* id = (LLUUID*)user_data; + + LLCharacter* character = NULL; + + for(character = LLCharacter::sInstances.getFirstData(); + character; + character = LLCharacter::sInstances.getNextData()) + { + if (character->getID() == *id) + { + break; + } + } + + delete id; + + if (!character) + { + return; + } + + // create an instance of this motion (it may or may not already exist) + LLKeyframeMotion* motionp = (LLKeyframeMotion*)character->createMotion(asset_uuid); + + if (0 == status && motionp) + { + if (motionp->mAssetStatus == ASSET_LOADED) + { + // asset already loaded + return; + } + LLVFile file(vfs, asset_uuid, type, LLVFile::READ); + S32 size = file.getSize(); + + U8* buffer = new U8[size]; + file.read((U8*)buffer, size); /*Flawfinder: ignore*/ + + lldebugs << "Loading keyframe data for: " << motionp->getName() << ":" << motionp->getID() << " (" << size << " bytes)" << llendl; + + LLDataPackerBinaryBuffer dp(buffer, size); + if (motionp->deserialize(dp)) + { + motionp->mAssetStatus = ASSET_LOADED; + } + else + { + llwarns << "Failed to decode asset for animation " << motionp->getName() << ":" << motionp->getID() << llendl; + motionp->mAssetStatus = ASSET_FETCH_FAILED; + } + + delete []buffer; + + } + else + { + llwarns << "Failed to load asset for animation " << motionp->getName() << ":" << motionp->getID() << llendl; + motionp->mAssetStatus = ASSET_FETCH_FAILED; + } +} + + +//----------------------------------------------------------------------------- +// writeCAL3D() +//----------------------------------------------------------------------------- +void LLKeyframeMotion::writeCAL3D(apr_file_t* fp) +{ +// <ANIMATION VERSION="1000" DURATION="1.03333" NUMTRACKS="58"> +// <TRACK BONEID="0" NUMKEYFRAMES="31"> +// <KEYFRAME TIME="0"> +// <TRANSLATION>0 0 48.8332</TRANSLATION> +// <ROTATION>0.0512905 0.05657 0.66973 0.738668</ROTATION> +// </KEYFRAME> +// </TRACK> +// </ANIMATION> + + apr_file_printf(fp, "<ANIMATION VERSION=\"1000\" DURATION=\"%.5f\" NUMTRACKS=\"%d\">\n", getDuration(), mJointMotionList->mNumJointMotions); + for (U32 joint_index = 0; joint_index < mJointMotionList->mNumJointMotions; joint_index++) + { + JointMotion* joint_motionp = &mJointMotionList->mJointMotionArray[joint_index]; + LLJoint* animated_joint = mCharacter->getJoint(joint_motionp->mJointName); + S32 joint_num = animated_joint->mJointNum + 1; + + apr_file_printf(fp, " <TRACK BONEID=\"%d\" NUMKEYFRAMES=\"%d\">\n", joint_num, joint_motionp->mRotationCurve.mNumKeys ); + PositionKey* pos_keyp = joint_motionp->mPositionCurve.mKeys.getFirstData(); + for (RotationKey* rot_keyp = joint_motionp->mRotationCurve.mKeys.getFirstData(); + rot_keyp; + rot_keyp = joint_motionp->mRotationCurve.mKeys.getNextData()) + { + apr_file_printf(fp, " <KEYFRAME TIME=\"%0.3f\">\n", rot_keyp->mTime); + LLVector3 nominal_pos = animated_joint->getPosition(); + if (animated_joint->getParent()) + { + nominal_pos.scaleVec(animated_joint->getParent()->getScale()); + } + nominal_pos = nominal_pos * 100.f; + + if (joint_motionp->mUsage & LLJointState::POS && pos_keyp) + { + LLVector3 pos_val = pos_keyp->mPosition; + pos_val = pos_val * 100.f; + pos_val += nominal_pos; + apr_file_printf(fp, " <TRANSLATION>%0.4f %0.4f %0.4f</TRANSLATION>\n", pos_val.mV[VX], pos_val.mV[VY], pos_val.mV[VZ]); + pos_keyp = joint_motionp->mPositionCurve.mKeys.getNextData(); + } + else + { + apr_file_printf(fp, " <TRANSLATION>%0.4f %0.4f %0.4f</TRANSLATION>\n", nominal_pos.mV[VX], nominal_pos.mV[VY], nominal_pos.mV[VZ]); + } + + LLQuaternion rot_val = ~rot_keyp->mRotation; + apr_file_printf(fp, " <ROTATION>%0.4f %0.4f %0.4f %0.4f</ROTATION>\n", rot_val.mQ[VX], rot_val.mQ[VY], rot_val.mQ[VZ], rot_val.mQ[VW]); + apr_file_printf(fp, " </KEYFRAME>\n"); + } + apr_file_printf(fp, " </TRACK>\n"); + } + apr_file_printf(fp, "</ANIMATION>\n"); +} + +//-------------------------------------------------------------------- +// LLKeyframeDataCache::dumpDiagInfo() +//-------------------------------------------------------------------- +void LLKeyframeDataCache::dumpDiagInfo() +{ + // keep track of totals + U32 total_size = 0; + + char buf[1024]; /* Flawfinder: ignore */ + + llinfos << "-----------------------------------------------------" << llendl; + llinfos << " Global Motion Table (DEBUG only)" << llendl; + llinfos << "-----------------------------------------------------" << llendl; + + // print each loaded mesh, and it's memory usage + LLKeyframeDataMap::iterator map_it; + for (map_it = sKeyframeDataMap.begin(); map_it != sKeyframeDataMap.end(); ++map_it) + { + U32 joint_motion_kb; + + LLKeyframeMotion::JointMotionList *motion_list_p = map_it->second; + + llinfos << "Motion: " << map_it->first << llendl; + + joint_motion_kb = motion_list_p->dumpDiagInfo(); + + total_size += joint_motion_kb; + } + + llinfos << "-----------------------------------------------------" << llendl; + llinfos << "Motions\tTotal Size" << llendl; + snprintf(buf, sizeof(buf), "%d\t\t%d bytes", (S32)sKeyframeDataMap.size(), total_size ); /* Flawfinder: ignore */ + llinfos << buf << llendl; + llinfos << "-----------------------------------------------------" << llendl; +} + + +//-------------------------------------------------------------------- +// LLKeyframeDataCache::addKeyframeData() +//-------------------------------------------------------------------- +void LLKeyframeDataCache::addKeyframeData(const LLUUID& id, LLKeyframeMotion::JointMotionList* joint_motion_listp) +{ + sKeyframeDataMap[id] = joint_motion_listp; +} + +//-------------------------------------------------------------------- +// LLKeyframeDataCache::removeKeyframeData() +//-------------------------------------------------------------------- +void LLKeyframeDataCache::removeKeyframeData(const LLUUID& id) +{ + LLKeyframeMotion::JointMotionList* joint_motion_listp = getKeyframeData(id); + if (joint_motion_listp) + { + delete joint_motion_listp; + } + sKeyframeDataMap.erase(id); +} + +//-------------------------------------------------------------------- +// LLKeyframeDataCache::getKeyframeData() +//-------------------------------------------------------------------- +LLKeyframeMotion::JointMotionList* LLKeyframeDataCache::getKeyframeData(const LLUUID& id) +{ + LLKeyframeDataMap::iterator found_data = sKeyframeDataMap.find(id); + if (found_data == sKeyframeDataMap.end()) + { + return NULL; + } + return found_data->second; +} + +//-------------------------------------------------------------------- +// ~LLKeyframeDataCache::LLKeyframeDataCache() +//-------------------------------------------------------------------- +LLKeyframeDataCache::~LLKeyframeDataCache() +{ + clear(); +} + +//----------------------------------------------------------------------------- +// clear() +//----------------------------------------------------------------------------- +void LLKeyframeDataCache::clear() +{ + for_each(sKeyframeDataMap.begin(), sKeyframeDataMap.end(), DeletePairedPointer()); + sKeyframeDataMap.clear(); +} + +//----------------------------------------------------------------------------- +// JointConstraint() +//----------------------------------------------------------------------------- +LLKeyframeMotion::JointConstraint::JointConstraint(JointConstraintSharedData* shared_data) : mSharedData(shared_data) +{ + mTotalLength = 0.f; + mActive = FALSE; + mSourceVolume = NULL; + mTargetVolume = NULL; + mFixupDistanceRMS = 0.f; +} + +//----------------------------------------------------------------------------- +// ~JointConstraint() +//----------------------------------------------------------------------------- +LLKeyframeMotion::JointConstraint::~JointConstraint() +{ +} + +// End diff --git a/indra/llcharacter/llkeyframemotion.h b/indra/llcharacter/llkeyframemotion.h new file mode 100644 index 0000000000..4e82c0672a --- /dev/null +++ b/indra/llcharacter/llkeyframemotion.h @@ -0,0 +1,437 @@ +/** + * @file llkeyframemotion.h + * @brief Implementation of LLKeframeMotion class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLKEYFRAMEMOTION_H +#define LL_LLKEYFRAMEMOTION_H + +//----------------------------------------------------------------------------- +// Header files +//----------------------------------------------------------------------------- + +#include <string> + +#include "llassetstorage.h" +#include "llassoclist.h" +#include "llbboxlocal.h" +#include "llhandmotion.h" +#include "lljointstate.h" +#include "llmotion.h" +#include "llptrskipmap.h" +#include "llquaternion.h" +#include "v3dmath.h" +#include "v3math.h" +#include "llapr.h" + +class LLKeyframeDataCache; +class LLVFS; +class LLDataPacker; + +#define MIN_REQUIRED_PIXEL_AREA_KEYFRAME (40.f) +#define MAX_CHAIN_LENGTH (4) + +const S32 KEYFRAME_MOTION_VERSION = 1; +const S32 KEYFRAME_MOTION_SUBVERSION = 0; + +//----------------------------------------------------------------------------- +// class LLKeyframeMotion +//----------------------------------------------------------------------------- +class LLKeyframeMotion : + public LLMotion +{ + friend class LLKeyframeDataCache; +public: + // Constructor + LLKeyframeMotion(const LLUUID &id); + + // Destructor + virtual ~LLKeyframeMotion(); + +public: + //------------------------------------------------------------------------- + // functions to support MotionController and MotionRegistry + //------------------------------------------------------------------------- + + // static constructor + // all subclasses must implement such a function and register it + static LLMotion *create(const LLUUID& id); + +public: + //------------------------------------------------------------------------- + // animation callbacks to be implemented by subclasses + //------------------------------------------------------------------------- + + // motions must specify whether or not they loop + virtual BOOL getLoop() { + if (mJointMotionList) return mJointMotionList->mLoop; + else return FALSE; + } + + // motions must report their total duration + virtual F32 getDuration() { + if (mJointMotionList) return mJointMotionList->mDuration; + else return 0.f; + } + + // motions must report their "ease in" duration + virtual F32 getEaseInDuration() { + if (mJointMotionList) return mJointMotionList->mEaseInDuration; + else return 0.f; + } + + // motions must report their "ease out" duration. + virtual F32 getEaseOutDuration() { + if (mJointMotionList) return mJointMotionList->mEaseOutDuration; + else return 0.f; + } + + // motions must report their priority + virtual LLJoint::JointPriority getPriority() { + if (mJointMotionList) return mJointMotionList->mBasePriority; + else return LLJoint::LOW_PRIORITY; + } + + virtual LLMotionBlendType getBlendType() { return NORMAL_BLEND; } + + // called to determine when a motion should be activated/deactivated based on avatar pixel coverage + virtual F32 getMinPixelArea() { return MIN_REQUIRED_PIXEL_AREA_KEYFRAME; } + + // run-time (post constructor) initialization, + // called after parameters have been set + // must return true to indicate success and be available for activation + virtual LLMotionInitStatus onInitialize(LLCharacter *character); + + // called when a motion is activated + // must return TRUE to indicate success, or else + // it will be deactivated + virtual BOOL onActivate(); + + // called per time step + // must return TRUE while it is active, and + // must return FALSE when the motion is completed. + virtual BOOL onUpdate(F32 time, U8* joint_mask); + + // called when a motion is deactivated + virtual void onDeactivate(); + + virtual void setStopTime(F32 time); + + static void setVFS(LLVFS* vfs) { sVFS = vfs; } + + static void onLoadComplete(LLVFS *vfs, + const LLUUID& asset_uuid, + LLAssetType::EType type, + void* user_data, S32 status); + +public: + U32 getFileSize(); + BOOL serialize(LLDataPacker& dp) const; + BOOL deserialize(LLDataPacker& dp); + void writeCAL3D(apr_file_t* fp); + BOOL isLoaded() { return mJointMotionList != NULL; } + + + // setters for modifying a keyframe animation + void setLoop(BOOL loop); + + F32 getLoopIn() { + return (mJointMotionList) ? mJointMotionList->mLoopInPoint : 0.f; + } + + F32 getLoopOut() { + return (mJointMotionList) ? mJointMotionList->mLoopOutPoint : 0.f; + } + + void setLoopIn(F32 in_point); + + void setLoopOut(F32 out_point); + + void setHandPose(LLHandMotion::eHandPose pose) { + if (mJointMotionList) mJointMotionList->mHandPose = pose; + } + + LLHandMotion::eHandPose getHandPose() { + return (mJointMotionList) ? mJointMotionList->mHandPose : LLHandMotion::HAND_POSE_RELAXED; + } + + void setPriority(S32 priority); + + void setEmote(const LLUUID& emote_id); + + void setEaseIn(F32 ease_in); + + void setEaseOut(F32 ease_in); + + F32 getLastUpdateTime() { return mLastLoopedTime; } + + const LLBBoxLocal& getPelvisBBox(); + + static void flushKeyframeCache(); + + typedef enum e_constraint_type + { + TYPE_POINT, + TYPE_PLANE + } EConstraintType; + + typedef enum e_constraint_target_type + { + TYPE_BODY, + TYPE_GROUND + } EConstraintTargetType; + +protected: + //------------------------------------------------------------------------- + // JointConstraintSharedData + //------------------------------------------------------------------------- + class JointConstraintSharedData + { + public: + JointConstraintSharedData() : + mChainLength(0), + mEaseInStartTime(0.f), + mEaseInStopTime(0.f), + mEaseOutStartTime(0.f), + mEaseOutStopTime(0.f), + mUseTargetOffset(FALSE), + mConstraintType(TYPE_POINT), + mConstraintTargetType(TYPE_BODY) {}; + ~JointConstraintSharedData() { delete [] mJointStateIndices; } + + S32 mSourceConstraintVolume; + LLVector3 mSourceConstraintOffset; + S32 mTargetConstraintVolume; + LLVector3 mTargetConstraintOffset; + LLVector3 mTargetConstraintDir; + S32 mChainLength; + S32* mJointStateIndices; + F32 mEaseInStartTime; + F32 mEaseInStopTime; + F32 mEaseOutStartTime; + F32 mEaseOutStopTime; + BOOL mUseTargetOffset; + EConstraintType mConstraintType; + EConstraintTargetType mConstraintTargetType; + }; + + //----------------------------------------------------------------------------- + // JointConstraint() + //----------------------------------------------------------------------------- + class JointConstraint + { + public: + JointConstraint(JointConstraintSharedData* shared_data); + ~JointConstraint(); + + JointConstraintSharedData* mSharedData; + F32 mWeight; + F32 mTotalLength; + LLVector3 mPositions[MAX_CHAIN_LENGTH]; + F32 mJointLengths[MAX_CHAIN_LENGTH]; + F32 mJointLengthFractions[MAX_CHAIN_LENGTH]; + BOOL mActive; + LLVector3d mGroundPos; + LLVector3 mGroundNorm; + LLJoint* mSourceVolume; + LLJoint* mTargetVolume; + F32 mFixupDistanceRMS; + }; + + void applyKeyframes(F32 time); + + void applyConstraints(F32 time, U8* joint_mask); + + void activateConstraint(JointConstraint* constraintp); + + void initializeConstraint(JointConstraint* constraint); + + void deactivateConstraint(JointConstraint *constraintp); + + void applyConstraint(JointConstraint* constraintp, F32 time, U8* joint_mask); + + BOOL setupPose(); + +public: + enum AssetStatus { ASSET_LOADED, ASSET_FETCHED, ASSET_NEEDS_FETCH, ASSET_FETCH_FAILED, ASSET_UNDEFINED }; + + enum InterpolationType { IT_STEP, IT_LINEAR, IT_SPLINE }; + + //------------------------------------------------------------------------- + // ScaleKey + //------------------------------------------------------------------------- + class ScaleKey + { + public: + ScaleKey() { mTime = 0.0f; } + ScaleKey(F32 time, const LLVector3 &scale) { mTime = time; mScale = scale; } + + F32 mTime; + LLVector3 mScale; + }; + + //------------------------------------------------------------------------- + // RotationKey + //------------------------------------------------------------------------- + class RotationKey + { + public: + RotationKey() { mTime = 0.0f; } + RotationKey(F32 time, const LLQuaternion &rotation) { mTime = time; mRotation = rotation; } + + F32 mTime; + LLQuaternion mRotation; + }; + + //------------------------------------------------------------------------- + // PositionKey + //------------------------------------------------------------------------- + class PositionKey + { + public: + PositionKey() { mTime = 0.0f; } + PositionKey(F32 time, const LLVector3 &position) { mTime = time; mPosition = position; } + + F32 mTime; + LLVector3 mPosition; + }; + + //------------------------------------------------------------------------- + // ScaleCurve + //------------------------------------------------------------------------- + class ScaleCurve + { + public: + ScaleCurve(); + ~ScaleCurve(); + LLVector3 getValue(F32 time, F32 duration); + LLVector3 interp(F32 u, ScaleKey& before, ScaleKey& after); + + InterpolationType mInterpolationType; + S32 mNumKeys; + LLPtrSkipMap<F32, ScaleKey*> mKeys; + ScaleKey mLoopInKey; + ScaleKey mLoopOutKey; + }; + + //------------------------------------------------------------------------- + // RotationCurve + //------------------------------------------------------------------------- + class RotationCurve + { + public: + RotationCurve(); + ~RotationCurve(); + LLQuaternion getValue(F32 time, F32 duration); + LLQuaternion interp(F32 u, RotationKey& before, RotationKey& after); + + InterpolationType mInterpolationType; + S32 mNumKeys; + LLPtrSkipMap<F32, RotationKey*> mKeys; + RotationKey mLoopInKey; + RotationKey mLoopOutKey; + }; + + //------------------------------------------------------------------------- + // PositionCurve + //------------------------------------------------------------------------- + class PositionCurve + { + public: + PositionCurve(); + ~PositionCurve(); + LLVector3 getValue(F32 time, F32 duration); + LLVector3 interp(F32 u, PositionKey& before, PositionKey& after); + + InterpolationType mInterpolationType; + S32 mNumKeys; + LLPtrSkipMap<F32, PositionKey*> mKeys; + PositionKey mLoopInKey; + PositionKey mLoopOutKey; + }; + + //------------------------------------------------------------------------- + // JointMotion + //------------------------------------------------------------------------- + class JointMotion + { + public: + PositionCurve mPositionCurve; + RotationCurve mRotationCurve; + ScaleCurve mScaleCurve; + std::string mJointName; + U32 mUsage; + LLJoint::JointPriority mPriority; + + void update(LLJointState *joint_state, F32 time, F32 duration); + }; + + //------------------------------------------------------------------------- + // JointMotionList + //------------------------------------------------------------------------- + class JointMotionList + { + public: + U32 mNumJointMotions; + JointMotion* mJointMotionArray; + F32 mDuration; + BOOL mLoop; + F32 mLoopInPoint; + F32 mLoopOutPoint; + F32 mEaseInDuration; + F32 mEaseOutDuration; + LLJoint::JointPriority mBasePriority; + LLHandMotion::eHandPose mHandPose; + LLJoint::JointPriority mMaxPriority; + LLLinkedList<JointConstraintSharedData> mConstraints; + LLBBoxLocal mPelvisBBox; + public: + JointMotionList() : mNumJointMotions(0), mJointMotionArray(NULL) {}; + ~JointMotionList() { mConstraints.deleteAllData(); delete [] mJointMotionArray; } + U32 dumpDiagInfo(); + }; + + +protected: + static LLVFS* sVFS; + + //------------------------------------------------------------------------- + // Member Data + //------------------------------------------------------------------------- + JointMotionList* mJointMotionList; + LLJointState* mJointStates; + LLJoint* mPelvisp; + LLCharacter* mCharacter; + std::string mEmoteName; + LLLinkedList<JointConstraint> mConstraints; + U32 mLastSkeletonSerialNum; + F32 mLastUpdateTime; + F32 mLastLoopedTime; + AssetStatus mAssetStatus; +}; + +class LLKeyframeDataCache +{ +public: + //FIXME: implement this as an actual singleton member of LLKeyframeMotion + LLKeyframeDataCache(){}; + ~LLKeyframeDataCache(); + + typedef std::map<LLUUID, class LLKeyframeMotion::JointMotionList*> LLKeyframeDataMap; + static LLKeyframeDataMap sKeyframeDataMap; + + static void addKeyframeData(const LLUUID& id, LLKeyframeMotion::JointMotionList*); + static LLKeyframeMotion::JointMotionList* getKeyframeData(const LLUUID& id); + + static void removeKeyframeData(const LLUUID& id); + + //print out diagnostic info + static void dumpDiagInfo(); + static void clear(); +}; + +#endif // LL_LLKEYFRAMEMOTION_H + diff --git a/indra/llcharacter/llkeyframemotionparam.cpp b/indra/llcharacter/llkeyframemotionparam.cpp new file mode 100644 index 0000000000..c57079fc2b --- /dev/null +++ b/indra/llcharacter/llkeyframemotionparam.cpp @@ -0,0 +1,442 @@ +/** + * @file llkeyframemotionparam.cpp + * @brief Implementation of LLKeyframeMotion class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +//----------------------------------------------------------------------------- +// Header Files +//----------------------------------------------------------------------------- +#include "linden_common.h" + +#include "llkeyframemotionparam.h" +#include "llcharacter.h" +#include "llmath.h" +#include "m3math.h" +#include "lldir.h" +#include "llanimationstates.h" + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +// LLKeyframeMotionParam class +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// sortFunc() +//----------------------------------------------------------------------------- +BOOL LLKeyframeMotionParam::sortFunc(ParameterizedMotion *new_motion, ParameterizedMotion *tested_motion) +{ + return (new_motion->second < tested_motion->second); +} + +//----------------------------------------------------------------------------- +// LLKeyframeMotionParam() +// Class Constructor +//----------------------------------------------------------------------------- +LLKeyframeMotionParam::LLKeyframeMotionParam( const LLUUID &id) : LLMotion(id) +{ + mJointStates = NULL; + mDefaultKeyframeMotion = NULL; + mCharacter = NULL; + + mEaseInDuration = 0.f; + mEaseOutDuration = 0.f; + mDuration = 0.f; + mPriority = LLJoint::LOW_PRIORITY; +} + + +//----------------------------------------------------------------------------- +// ~LLKeyframeMotionParam() +// Class Destructor +//----------------------------------------------------------------------------- +LLKeyframeMotionParam::~LLKeyframeMotionParam() +{ + for (U32 i = 0; i < mParameterizedMotions.length(); i++) + { + LLLinkedList< ParameterizedMotion > *motionList = *mParameterizedMotions.getValueAt(i); + for (ParameterizedMotion* paramMotion = motionList->getFirstData(); paramMotion; paramMotion = motionList->getNextData()) + { + delete paramMotion->first; + } + delete motionList; + } + + mParameterizedMotions.removeAll(); +} + +//----------------------------------------------------------------------------- +// LLKeyframeMotionParam::onInitialize(LLCharacter *character) +//----------------------------------------------------------------------------- +LLMotion::LLMotionInitStatus LLKeyframeMotionParam::onInitialize(LLCharacter *character) +{ + mCharacter = character; + + if (!loadMotions()) + { + return STATUS_FAILURE; + } + + for (U32 i = 0; i < mParameterizedMotions.length(); i++) + { + LLLinkedList< ParameterizedMotion > *motionList = *mParameterizedMotions.getValueAt(i); + for (ParameterizedMotion* paramMotion = motionList->getFirstData(); paramMotion; paramMotion = motionList->getNextData()) + { + paramMotion->first->onInitialize(character); + + if (paramMotion->first->getDuration() > mEaseInDuration) + { + mEaseInDuration = paramMotion->first->getEaseInDuration(); + } + + if (paramMotion->first->getEaseOutDuration() > mEaseOutDuration) + { + mEaseOutDuration = paramMotion->first->getEaseOutDuration(); + } + + if (paramMotion->first->getDuration() > mDuration) + { + mDuration = paramMotion->first->getDuration(); + } + + if (paramMotion->first->getPriority() > mPriority) + { + mPriority = paramMotion->first->getPriority(); + } + + LLPose *pose = paramMotion->first->getPose(); + + mPoseBlender.addMotion(paramMotion->first); + for (LLJointState *jsp = pose->getFirstJointState(); jsp; jsp = pose->getNextJointState()) + { + LLPose *blendedPose = mPoseBlender.getBlendedPose(); + blendedPose->addJointState(jsp); + } + } + } + + return STATUS_SUCCESS; +} + +//----------------------------------------------------------------------------- +// LLKeyframeMotionParam::onActivate() +//----------------------------------------------------------------------------- +BOOL LLKeyframeMotionParam::onActivate() +{ + for (U32 i = 0; i < mParameterizedMotions.length(); i++) + { + LLLinkedList< ParameterizedMotion > *motionList = *mParameterizedMotions.getValueAt(i); + for (ParameterizedMotion* paramMotion = motionList->getFirstData(); paramMotion; paramMotion = motionList->getNextData()) + { + paramMotion->first->activate(); + } + } + return TRUE; +} + + +//----------------------------------------------------------------------------- +// LLKeyframeMotionParam::onUpdate() +//----------------------------------------------------------------------------- +BOOL LLKeyframeMotionParam::onUpdate(F32 time, U8* joint_mask) +{ + F32 weightFactor = 1.f / (F32)mParameterizedMotions.length(); + U32 i; + + // zero out all pose weights + for (i = 0; i < mParameterizedMotions.length(); i++) + { + LLLinkedList< ParameterizedMotion > *motionList = *mParameterizedMotions.getValueAt(i); + + for (ParameterizedMotion* paramMotion = motionList->getFirstData(); paramMotion; paramMotion = motionList->getNextData()) + { +// llinfos << "Weight for pose " << paramMotion->first->getName() << " is " << paramMotion->first->getPose()->getWeight() << llendl; + paramMotion->first->getPose()->setWeight(0.f); + } + } + + + for (i = 0; i < mParameterizedMotions.length(); i++) + { + LLLinkedList< ParameterizedMotion > *motionList = *mParameterizedMotions.getValueAt(i); + std::string *paramName = mParameterizedMotions.getIndexAt(i); + F32* paramValue = (F32 *)mCharacter->getAnimationData(*paramName); + ParameterizedMotion* firstMotion = NULL; + ParameterizedMotion* secondMotion = NULL; + + for (ParameterizedMotion* paramMotion = motionList->getFirstData(); paramMotion; paramMotion = motionList->getNextData()) + { + paramMotion->first->onUpdate(time, joint_mask); + + F32 distToParam = paramMotion->second - *paramValue; + + if ( distToParam <= 0.f) + { + // keep track of the motion closest to the parameter value + firstMotion = paramMotion; + } + else + { + // we've passed the parameter value + // so store the first motion we find as the second one we want to blend... + if (firstMotion && !secondMotion ) + { + secondMotion = paramMotion; + } + //...or, if we've seen no other motion so far, make sure we blend to this only + else if (!firstMotion) + { + firstMotion = paramMotion; + secondMotion = paramMotion; + } + } + } + + LLPose *firstPose; + LLPose *secondPose; + + if (firstMotion) + firstPose = firstMotion->first->getPose(); + else + firstPose = NULL; + + if (secondMotion) + secondPose = secondMotion->first->getPose(); + else + secondPose = NULL; + + // now modify weight of the subanim (only if we are blending between two motions) + if (firstMotion && secondMotion) + { + if (firstMotion == secondMotion) + { + firstPose->setWeight(weightFactor); + } + else if (firstMotion->second == secondMotion->second) + { + firstPose->setWeight(0.5f * weightFactor); + secondPose->setWeight(0.5f * weightFactor); + } + else + { + F32 first_weight = 1.f - + ((llclamp(*paramValue - firstMotion->second, 0.f, (secondMotion->second - firstMotion->second))) / + (secondMotion->second - firstMotion->second)); + first_weight = llclamp(first_weight, 0.f, 1.f); + + F32 second_weight = 1.f - first_weight; + + firstPose->setWeight(first_weight * weightFactor); + secondPose->setWeight(second_weight * weightFactor); + +// llinfos << "Parameter " << *paramName << ": " << *paramValue << llendl; +// llinfos << "Weights " << firstPose->getWeight() << " " << secondPose->getWeight() << llendl; + } + } + else if (firstMotion && !secondMotion) + { + firstPose->setWeight(weightFactor); + } + } + + // blend poses + mPoseBlender.blendAndApply(); + + llinfos << "Param Motion weight " << mPoseBlender.getBlendedPose()->getWeight() << llendl; + + return TRUE; +} + +//----------------------------------------------------------------------------- +// LLKeyframeMotionParam::onDeactivate() +//----------------------------------------------------------------------------- +void LLKeyframeMotionParam::onDeactivate() +{ + for (U32 i = 0; i < mParameterizedMotions.length(); i++) + { + LLLinkedList< ParameterizedMotion > *motionList = *mParameterizedMotions.getValueAt(i); + for (ParameterizedMotion* paramMotion = motionList->getFirstData(); paramMotion; paramMotion = motionList->getNextData()) + { + paramMotion->first->onDeactivate(); + } + } +} + +//----------------------------------------------------------------------------- +// LLKeyframeMotionParam::addKeyframeMotion() +//----------------------------------------------------------------------------- +BOOL LLKeyframeMotionParam::addKeyframeMotion(char *name, const LLUUID &id, char *param, F32 value) +{ + LLMotion *newMotion = mCharacter->createMotion( id ); + + if (!newMotion) + { + return FALSE; + } + + newMotion->setName(name); + + // make sure a list of motions exists for this parameter + LLLinkedList< ParameterizedMotion > *motionList; + if (mParameterizedMotions.getValue(param)) + { + motionList = *mParameterizedMotions.getValue(param); + } + else + { + motionList = new LLLinkedList< ParameterizedMotion >; + motionList->setInsertBefore(sortFunc); + mParameterizedMotions.addToHead(param, motionList); + } + + // now add motion to this list + ParameterizedMotion *parameterizedMotion = new ParameterizedMotion(newMotion, value); + + motionList->addDataSorted(parameterizedMotion); + + return TRUE; +} + + +//----------------------------------------------------------------------------- +// LLKeyframeMotionParam::setDefaultKeyframeMotion() +//----------------------------------------------------------------------------- +void LLKeyframeMotionParam::setDefaultKeyframeMotion(char *name) +{ + for (U32 i = 0; i < mParameterizedMotions.length(); i++) + { + LLLinkedList< ParameterizedMotion > *motionList = *mParameterizedMotions.getValueAt(i); + for (ParameterizedMotion* paramMotion = motionList->getFirstData(); paramMotion; paramMotion = motionList->getNextData()) + { + if (paramMotion->first->getName() == name) + { + mDefaultKeyframeMotion = paramMotion->first; + } + } + } +} + +//----------------------------------------------------------------------------- +// loadMotions() +//----------------------------------------------------------------------------- +BOOL LLKeyframeMotionParam::loadMotions() +{ + //------------------------------------------------------------------------- + // Load named file by concatenating the character prefix with the motion name. + // Load data into a buffer to be parsed. + //------------------------------------------------------------------------- + char path[LL_MAX_PATH]; /* Flawfinder: ignore */ + snprintf( path, sizeof(path), "%s_%s.llp", + gDirUtilp->getExpandedFilename(LL_PATH_MOTIONS,mCharacter->getAnimationPrefix()).c_str(), + getName().c_str() ); /* Flawfinder: ignore */ + + //------------------------------------------------------------------------- + // open the file + //------------------------------------------------------------------------- + S32 fileSize = 0; + apr_file_t* fp = ll_apr_file_open(path, LL_APR_R, &fileSize); + if (!fp || fileSize == 0) + { + llinfos << "ERROR: can't open: " << path << llendl; + return FALSE; + } + + // allocate a text buffer + char *text = new char[ fileSize+1 ]; + if ( !text ) + { + llinfos << "ERROR: can't allocated keyframe text buffer." << llendl; + apr_file_close(fp); + return FALSE; + } + + //------------------------------------------------------------------------- + // load data from file into buffer + //------------------------------------------------------------------------- + bool error = false; + char *p = text; + while ( 1 ) + { + if (apr_file_eof(fp) == APR_EOF) + { + break; + } + if (apr_file_gets(p, 1024, fp) != APR_SUCCESS) + { + error = true; + break; + } + while ( *(++p) ) + ; + } + + //------------------------------------------------------------------------- + // close the file + //------------------------------------------------------------------------- + apr_file_close( fp ); + + //------------------------------------------------------------------------- + // check for error + //------------------------------------------------------------------------- + llassert( p <= (text+fileSize) ); + + if ( error ) + { + llinfos << "ERROR: error while reading from " << path << llendl; + delete [] text; + return FALSE; + } + + llinfos << "Loading parametric keyframe data for: " << getName() << llendl; + + //------------------------------------------------------------------------- + // parse the text and build keyframe data structures + //------------------------------------------------------------------------- + p = text; + S32 num; + char strA[80]; /* Flawfinder: ignore */ + char strB[80]; /* Flawfinder: ignore */ + F32 floatA = 0.0f; + + + //------------------------------------------------------------------------- + // get priority + //------------------------------------------------------------------------- + BOOL isFirstMotion = TRUE; + num = sscanf(p, "%79s %79s %f", strA, strB, &floatA); + + while(1) + { + if (num == 0 || num == EOF) break; + if ((num != 3)) + { + llinfos << "WARNING: can't read parametric motion" << llendl; + delete [] text; + return FALSE; + } + + addKeyframeMotion(strA, gAnimLibrary.stringToAnimState(strA), strB, floatA); + if (isFirstMotion) + { + isFirstMotion = FALSE; + setDefaultKeyframeMotion(strA); + } + + p = strstr(p, "\n"); + if (!p) + { + break; + } + + p++; + num = sscanf(p, "%79s %79s %f", strA, strB, &floatA); + } + + delete [] text; + return TRUE; +} + +// End diff --git a/indra/llcharacter/llkeyframemotionparam.h b/indra/llcharacter/llkeyframemotionparam.h new file mode 100644 index 0000000000..6b23100d5b --- /dev/null +++ b/indra/llcharacter/llkeyframemotionparam.h @@ -0,0 +1,138 @@ +/** + * @file llkeyframemotionparam.h + * @brief Implementation of LLKeframeMotionParam class. + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLKEYFRAMEMOTIONPARAM_H +#define LL_LLKEYFRAMEMOTIONPARAM_H + +//----------------------------------------------------------------------------- +// Header files +//----------------------------------------------------------------------------- + +#include <string> + +#include "llmotion.h" +#include "lljointstate.h" +#include "v3math.h" +#include "llquaternion.h" +#include "linked_lists.h" +#include "llkeyframemotion.h" + +//----------------------------------------------------------------------------- +// class LLKeyframeMotionParam +//----------------------------------------------------------------------------- +class LLKeyframeMotionParam : + public LLMotion +{ +public: + // Constructor + LLKeyframeMotionParam(const LLUUID &id); + + // Destructor + virtual ~LLKeyframeMotionParam(); + +public: + //------------------------------------------------------------------------- + // functions to support MotionController and MotionRegistry + //------------------------------------------------------------------------- + + // static constructor + // all subclasses must implement such a function and register it + static LLMotion *create(const LLUUID &id) { return new LLKeyframeMotionParam(id); } + +public: + //------------------------------------------------------------------------- + // animation callbacks to be implemented by subclasses + //------------------------------------------------------------------------- + + // motions must specify whether or not they loop + virtual BOOL getLoop() { + return TRUE; + } + + // motions must report their total duration + virtual F32 getDuration() { + return mDuration; + } + + // motions must report their "ease in" duration + virtual F32 getEaseInDuration() { + return mEaseInDuration; + } + + // motions must report their "ease out" duration. + virtual F32 getEaseOutDuration() { + return mEaseOutDuration; + } + + // motions must report their priority + virtual LLJoint::JointPriority getPriority() { + return mPriority; + } + + virtual LLMotionBlendType getBlendType() { return NORMAL_BLEND; } + + // called to determine when a motion should be activated/deactivated based on avatar pixel coverage + virtual F32 getMinPixelArea() { return MIN_REQUIRED_PIXEL_AREA_KEYFRAME; } + + // run-time (post constructor) initialization, + // called after parameters have been set + // must return true to indicate success and be available for activation + virtual LLMotionInitStatus onInitialize(LLCharacter *character); + + // called when a motion is activated + // must return TRUE to indicate success, or else + // it will be deactivated + virtual BOOL onActivate(); + + // called per time step + // must return TRUE while it is active, and + // must return FALSE when the motion is completed. + virtual BOOL onUpdate(F32 time, U8* joint_mask); + + // called when a motion is deactivated + virtual void onDeactivate(); + + virtual LLPose* getPose() { return mPoseBlender.getBlendedPose();} + +protected: + //------------------------------------------------------------------------- + // new functions defined by this subclass + //------------------------------------------------------------------------- + typedef std::pair<LLMotion*, F32> ParameterizedMotion; + + // add a motion and associated parameter triplet + BOOL addKeyframeMotion(char *name, const LLUUID &id, char *param, F32 value); + + // set default motion for LOD and retrieving blend constants + void setDefaultKeyframeMotion(char *); + + static BOOL sortFunc(ParameterizedMotion *new_motion, ParameterizedMotion *tested_motion); + + BOOL loadMotions(); + +protected: + //------------------------------------------------------------------------- + // Member Data + //------------------------------------------------------------------------- + + typedef LLLinkedList < ParameterizedMotion > motion_list_t; + LLAssocList <std::string, motion_list_t* > mParameterizedMotions; + LLJointState* mJointStates; + LLMotion* mDefaultKeyframeMotion; + LLCharacter* mCharacter; + LLPoseBlender mPoseBlender; + + F32 mEaseInDuration; + F32 mEaseOutDuration; + F32 mDuration; + LLJoint::JointPriority mPriority; + + LLUUID mTransactionID; +}; + +#endif // LL_LLKEYFRAMEMOTIONPARAM_H diff --git a/indra/llcharacter/llkeyframestandmotion.cpp b/indra/llcharacter/llkeyframestandmotion.cpp new file mode 100644 index 0000000000..6810fb450f --- /dev/null +++ b/indra/llcharacter/llkeyframestandmotion.cpp @@ -0,0 +1,320 @@ +/** + * @file llkeyframestandmotion.cpp + * @brief Implementation of LLKeyframeStandMotion class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +//----------------------------------------------------------------------------- +// Header Files +//----------------------------------------------------------------------------- +#include "linden_common.h" + +#include "llkeyframestandmotion.h" +#include "llcharacter.h" + +//----------------------------------------------------------------------------- +// Macros and consts +//----------------------------------------------------------------------------- +#define GO_TO_KEY_POSE 1 +#define MIN_TRACK_SPEED 0.01f +const F32 ROTATION_THRESHOLD = 0.6f; +const F32 POSITION_THRESHOLD = 0.1f; + +//----------------------------------------------------------------------------- +// LLKeyframeStandMotion() +// Class Constructor +//----------------------------------------------------------------------------- +LLKeyframeStandMotion::LLKeyframeStandMotion(const LLUUID &id) : LLKeyframeMotion(id) +{ + mFlipFeet = FALSE; + mCharacter = NULL; + + // create kinematic hierarchy + mPelvisJoint.addChild( &mHipLeftJoint ); + mHipLeftJoint.addChild( &mKneeLeftJoint ); + mKneeLeftJoint.addChild( &mAnkleLeftJoint ); + mPelvisJoint.addChild( &mHipRightJoint ); + mHipRightJoint.addChild( &mKneeRightJoint ); + mKneeRightJoint.addChild( &mAnkleRightJoint ); + + mPelvisState = NULL; + + mHipLeftState = NULL; + mKneeLeftState = NULL; + mAnkleLeftState = NULL; + + mHipRightState = NULL; + mKneeRightState = NULL; + mAnkleRightState = NULL; + + mTrackAnkles = TRUE; + + mFrameNum = 0; +} + + +//----------------------------------------------------------------------------- +// ~LLKeyframeStandMotion() +// Class Destructor +//----------------------------------------------------------------------------- +LLKeyframeStandMotion::~LLKeyframeStandMotion() +{ +} + + +//----------------------------------------------------------------------------- +// LLKeyframeStandMotion::onInitialize() +//----------------------------------------------------------------------------- +LLMotion::LLMotionInitStatus LLKeyframeStandMotion::onInitialize(LLCharacter *character) +{ + // save character pointer for later use + mCharacter = character; + + mFlipFeet = FALSE; + + // load keyframe data, setup pose and joint states + LLMotion::LLMotionInitStatus status = LLKeyframeMotion::onInitialize(character); + if ( status == STATUS_FAILURE ) + { + return status; + } + + // find the necessary joint states + LLPose *pose = getPose(); + mPelvisState = pose->findJointState("mPelvis"); + + mHipLeftState = pose->findJointState("mHipLeft"); + mKneeLeftState = pose->findJointState("mKneeLeft"); + mAnkleLeftState = pose->findJointState("mAnkleLeft"); + + mHipRightState = pose->findJointState("mHipRight"); + mKneeRightState = pose->findJointState("mKneeRight"); + mAnkleRightState = pose->findJointState("mAnkleRight"); + + if ( !mPelvisState || + !mHipLeftState || + !mKneeLeftState || + !mAnkleLeftState || + !mHipRightState || + !mKneeRightState || + !mAnkleRightState ) + { + llinfos << getName() << ": Can't find necessary joint states" << llendl; + return STATUS_FAILURE; + } + + return STATUS_SUCCESS; +} + +//----------------------------------------------------------------------------- +// LLKeyframeStandMotion::onActivate() +//----------------------------------------------------------------------------- +BOOL LLKeyframeStandMotion::onActivate() +{ + //------------------------------------------------------------------------- + // setup the IK solvers + //------------------------------------------------------------------------- + mIKLeft.setPoleVector( LLVector3(1.0f, 0.0f, 0.0f)); + mIKRight.setPoleVector( LLVector3(1.0f, 0.0f, 0.0f)); + mIKLeft.setBAxis( LLVector3(0.05f, 1.0f, 0.0f)); + mIKRight.setBAxis( LLVector3(-0.05f, 1.0f, 0.0f)); + + mLastGoodPelvisRotation.loadIdentity(); + mLastGoodPosition.clearVec(); + + mFrameNum = 0; + + return LLKeyframeMotion::onActivate(); +} + +//----------------------------------------------------------------------------- +// LLKeyframeStandMotion::onDeactivate() +//----------------------------------------------------------------------------- +void LLKeyframeStandMotion::onDeactivate() +{ + LLKeyframeMotion::onDeactivate(); +} + +//----------------------------------------------------------------------------- +// LLKeyframeStandMotion::onUpdate() +//----------------------------------------------------------------------------- +BOOL LLKeyframeStandMotion::onUpdate(F32 time, U8* joint_mask) +{ + //------------------------------------------------------------------------- + // let the base class update the cycle + //------------------------------------------------------------------------- + BOOL status = LLKeyframeMotion::onUpdate(time, joint_mask); + if (!status) + { + return FALSE; + } + + LLVector3 root_world_pos = mPelvisState->getJoint()->getParent()->getWorldPosition(); + + // have we received a valid world position for this avatar? + if (root_world_pos.isExactlyZero()) + { + return TRUE; + } + + //------------------------------------------------------------------------- + // Stop tracking (start locking) ankles once ease in is done. + // Setting this here ensures we track until we get valid foot position. + //------------------------------------------------------------------------- + if (dot(mPelvisState->getJoint()->getWorldRotation(), mLastGoodPelvisRotation) < ROTATION_THRESHOLD) + { + mLastGoodPelvisRotation = mPelvisState->getJoint()->getWorldRotation(); + mLastGoodPelvisRotation.normQuat(); + mTrackAnkles = TRUE; + } + else if ((mCharacter->getCharacterPosition() - mLastGoodPosition).magVecSquared() > POSITION_THRESHOLD) + { + mLastGoodPosition = mCharacter->getCharacterPosition(); + mTrackAnkles = TRUE; + } + else if (mPose.getWeight() < 1.f) + { + mTrackAnkles = TRUE; + } + + + //------------------------------------------------------------------------- + // propagate joint positions to internal versions + //------------------------------------------------------------------------- + mPelvisJoint.setPosition( + root_world_pos + + mPelvisState->getPosition() ); + + mHipLeftJoint.setPosition( mHipLeftState->getJoint()->getPosition() ); + mKneeLeftJoint.setPosition( mKneeLeftState->getJoint()->getPosition() ); + mAnkleLeftJoint.setPosition( mAnkleLeftState->getJoint()->getPosition() ); + + mHipLeftJoint.setScale( mHipLeftState->getJoint()->getScale() ); + mKneeLeftJoint.setScale( mKneeLeftState->getJoint()->getScale() ); + mAnkleLeftJoint.setScale( mAnkleLeftState->getJoint()->getScale() ); + + mHipRightJoint.setPosition( mHipRightState->getJoint()->getPosition() ); + mKneeRightJoint.setPosition( mKneeRightState->getJoint()->getPosition() ); + mAnkleRightJoint.setPosition( mAnkleRightState->getJoint()->getPosition() ); + + mHipRightJoint.setScale( mHipRightState->getJoint()->getScale() ); + mKneeRightJoint.setScale( mKneeRightState->getJoint()->getScale() ); + mAnkleRightJoint.setScale( mAnkleRightState->getJoint()->getScale() ); + //------------------------------------------------------------------------- + // propagate joint rotations to internal versions + //------------------------------------------------------------------------- + mPelvisJoint.setRotation( mPelvisState->getJoint()->getWorldRotation() ); + +#if GO_TO_KEY_POSE + mHipLeftJoint.setRotation( mHipLeftState->getRotation() ); + mKneeLeftJoint.setRotation( mKneeLeftState->getRotation() ); + mAnkleLeftJoint.setRotation( mAnkleLeftState->getRotation() ); + + mHipRightJoint.setRotation( mHipRightState->getRotation() ); + mKneeRightJoint.setRotation( mKneeRightState->getRotation() ); + mAnkleRightJoint.setRotation( mAnkleRightState->getRotation() ); +#else + mHipLeftJoint.setRotation( mHipLeftState->getJoint()->getRotation() ); + mKneeLeftJoint.setRotation( mKneeLeftState->getJoint()->getRotation() ); + mAnkleLeftJoint.setRotation( mAnkleLeftState->getJoint()->getRotation() ); + + mHipRightJoint.setRotation( mHipRightState->getJoint()->getRotation() ); + mKneeRightJoint.setRotation( mKneeRightState->getJoint()->getRotation() ); + mAnkleRightJoint.setRotation( mAnkleRightState->getJoint()->getRotation() ); +#endif + + // need to wait for underlying keyframe motion to affect the skeleton + if (mFrameNum == 2) + { + mIKLeft.setupJoints( &mHipLeftJoint, &mKneeLeftJoint, &mAnkleLeftJoint, &mTargetLeft ); + mIKRight.setupJoints( &mHipRightJoint, &mKneeRightJoint, &mAnkleRightJoint, &mTargetRight ); + } + else if (mFrameNum < 2) + { + mFrameNum++; + return TRUE; + } + + mFrameNum++; + + //------------------------------------------------------------------------- + // compute target position by projecting ankles to the ground + //------------------------------------------------------------------------- + if ( mTrackAnkles ) + { + mCharacter->getGround( mAnkleLeftJoint.getWorldPosition(), mPositionLeft, mNormalLeft); + mCharacter->getGround( mAnkleRightJoint.getWorldPosition(), mPositionRight, mNormalRight); + + mTargetLeft.setPosition( mPositionLeft ); + mTargetRight.setPosition( mPositionRight ); + } + + //------------------------------------------------------------------------- + // update solvers + //------------------------------------------------------------------------- + mIKLeft.solve(); + mIKRight.solve(); + + //------------------------------------------------------------------------- + // make ankle rotation conform to the ground + //------------------------------------------------------------------------- + if ( mTrackAnkles ) + { + LLVector4 dirLeft4 = mAnkleLeftJoint.getWorldMatrix().getFwdRow4(); + LLVector4 dirRight4 = mAnkleRightJoint.getWorldMatrix().getFwdRow4(); + LLVector3 dirLeft = vec4to3( dirLeft4 ); + LLVector3 dirRight = vec4to3( dirRight4 ); + + LLVector3 up; + LLVector3 dir; + LLVector3 left; + + up = mNormalLeft; + up.normVec(); + if (mFlipFeet) + { + up *= -1.0f; + } + dir = dirLeft; + dir.normVec(); + left = up % dir; + left.normVec(); + dir = left % up; + mRotationLeft = LLQuaternion( dir, left, up ); + + up = mNormalRight; + up.normVec(); + if (mFlipFeet) + { + up *= -1.0f; + } + dir = dirRight; + dir.normVec(); + left = up % dir; + left.normVec(); + dir = left % up; + mRotationRight = LLQuaternion( dir, left, up ); + } + mAnkleLeftJoint.setWorldRotation( mRotationLeft ); + mAnkleRightJoint.setWorldRotation( mRotationRight ); + + //------------------------------------------------------------------------- + // propagate joint rotations to joint states + //------------------------------------------------------------------------- + mHipLeftState->setRotation( mHipLeftJoint.getRotation() ); + mKneeLeftState->setRotation( mKneeLeftJoint.getRotation() ); + mAnkleLeftState->setRotation( mAnkleLeftJoint.getRotation() ); + + mHipRightState->setRotation( mHipRightJoint.getRotation() ); + mKneeRightState->setRotation( mKneeRightJoint.getRotation() ); + mAnkleRightState->setRotation( mAnkleRightJoint.getRotation() ); + + //llinfos << "Stand drift amount " << (mCharacter->getCharacterPosition() - mLastGoodPosition).magVec() << llendl; + +// llinfos << "DEBUG: " << speed << " : " << mTrackAnkles << llendl; + return TRUE; +} + +// End diff --git a/indra/llcharacter/llkeyframestandmotion.h b/indra/llcharacter/llkeyframestandmotion.h new file mode 100644 index 0000000000..a82c92d07f --- /dev/null +++ b/indra/llcharacter/llkeyframestandmotion.h @@ -0,0 +1,98 @@ +/** + * @file llkeyframestandmotion.h + * @brief Implementation of LLKeyframeStandMotion class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLKEYFRAMESTANDMOTION_H +#define LL_LLKEYFRAMESTANDMOTION_H + +//----------------------------------------------------------------------------- +// Header files +//----------------------------------------------------------------------------- +#include "llkeyframemotion.h" +#include "lljointsolverrp3.h" + + +//----------------------------------------------------------------------------- +// class LLKeyframeStandMotion +//----------------------------------------------------------------------------- +class LLKeyframeStandMotion : + public LLKeyframeMotion +{ +public: + // Constructor + LLKeyframeStandMotion(const LLUUID &id); + + // Destructor + virtual ~LLKeyframeStandMotion(); + +public: + //------------------------------------------------------------------------- + // functions to support MotionController and MotionRegistry + //------------------------------------------------------------------------- + + // static constructor + // all subclasses must implement such a function and register it + static LLMotion *create(const LLUUID &id) { return new LLKeyframeStandMotion(id); } + +public: + //------------------------------------------------------------------------- + // animation callbacks to be implemented by subclasses + //------------------------------------------------------------------------- + virtual LLMotionInitStatus onInitialize(LLCharacter *character); + virtual BOOL onActivate(); + void onDeactivate(); + virtual BOOL onUpdate(F32 time, U8* joint_mask); + +public: + //------------------------------------------------------------------------- + // Member Data + //------------------------------------------------------------------------- + LLCharacter *mCharacter; + + BOOL mFlipFeet; + + LLJointState *mPelvisState; + + LLJointState *mHipLeftState; + LLJointState *mKneeLeftState; + LLJointState *mAnkleLeftState; + + LLJointState *mHipRightState; + LLJointState *mKneeRightState; + LLJointState *mAnkleRightState; + + LLJoint mPelvisJoint; + + LLJoint mHipLeftJoint; + LLJoint mKneeLeftJoint; + LLJoint mAnkleLeftJoint; + LLJoint mTargetLeft; + + LLJoint mHipRightJoint; + LLJoint mKneeRightJoint; + LLJoint mAnkleRightJoint; + LLJoint mTargetRight; + + LLJointSolverRP3 mIKLeft; + LLJointSolverRP3 mIKRight; + + LLVector3 mPositionLeft; + LLVector3 mPositionRight; + LLVector3 mNormalLeft; + LLVector3 mNormalRight; + LLQuaternion mRotationLeft; + LLQuaternion mRotationRight; + + LLQuaternion mLastGoodPelvisRotation; + LLVector3 mLastGoodPosition; + BOOL mTrackAnkles; + + S32 mFrameNum; +}; + +#endif // LL_LLKEYFRAMESTANDMOTION_H + diff --git a/indra/llcharacter/llkeyframewalkmotion.cpp b/indra/llcharacter/llkeyframewalkmotion.cpp new file mode 100644 index 0000000000..930427aa7a --- /dev/null +++ b/indra/llcharacter/llkeyframewalkmotion.cpp @@ -0,0 +1,379 @@ +/** + * @file llkeyframewalkmotion.cpp + * @brief Implementation of LLKeyframeWalkMotion class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +//----------------------------------------------------------------------------- +// Header Files +//----------------------------------------------------------------------------- +#include "linden_common.h" + +#include "llkeyframewalkmotion.h" +#include "llcharacter.h" +#include "llmath.h" +#include "m3math.h" +#include "llcriticaldamp.h" + +//----------------------------------------------------------------------------- +// Macros +//----------------------------------------------------------------------------- +const F32 MAX_WALK_PLAYBACK_SPEED = 8.f; // max m/s for which we adjust walk cycle speed + +const F32 MIN_WALK_SPEED = 0.1f; // minimum speed at which we use velocity for down foot detection +const F32 MAX_TIME_DELTA = 2.f; //max two seconds a frame for calculating interpolation +const F32 SPEED_ADJUST_MAX = 2.5f; // maximum adjustment of walk animation playback speed +const F32 SPEED_ADJUST_MAX_SEC = 3.f; // maximum adjustment to walk animation playback speed for a second +const F32 DRIFT_COMP_MAX_TOTAL = 0.07f;//0.55f; // maximum drift compensation overall, in any direction +const F32 DRIFT_COMP_MAX_SPEED = 4.f; // speed at which drift compensation total maxes out +const F32 MAX_ROLL = 0.6f; + +//----------------------------------------------------------------------------- +// LLKeyframeWalkMotion() +// Class Constructor +//----------------------------------------------------------------------------- +LLKeyframeWalkMotion::LLKeyframeWalkMotion(const LLUUID &id) : LLKeyframeMotion(id) +{ + mRealTimeLast = 0.0f; + mAdjTimeLast = 0.0f; + mCharacter = NULL; +} + + +//----------------------------------------------------------------------------- +// ~LLKeyframeWalkMotion() +// Class Destructor +//----------------------------------------------------------------------------- +LLKeyframeWalkMotion::~LLKeyframeWalkMotion() +{ +} + + +//----------------------------------------------------------------------------- +// LLKeyframeWalkMotion::onInitialize() +//----------------------------------------------------------------------------- +LLMotion::LLMotionInitStatus LLKeyframeWalkMotion::onInitialize(LLCharacter *character) +{ + mCharacter = character; + + return LLKeyframeMotion::onInitialize(character); +} + +//----------------------------------------------------------------------------- +// LLKeyframeWalkMotion::onActivate() +//----------------------------------------------------------------------------- +BOOL LLKeyframeWalkMotion::onActivate() +{ + mRealTimeLast = 0.0f; + mAdjTimeLast = 0.0f; + + return LLKeyframeMotion::onActivate(); +} + +//----------------------------------------------------------------------------- +// LLKeyframeWalkMotion::onDeactivate() +//----------------------------------------------------------------------------- +void LLKeyframeWalkMotion::onDeactivate() +{ + mCharacter->removeAnimationData("Down Foot"); + LLKeyframeMotion::onDeactivate(); +} + +//----------------------------------------------------------------------------- +// LLKeyframeWalkMotion::onUpdate() +//----------------------------------------------------------------------------- +BOOL LLKeyframeWalkMotion::onUpdate(F32 time, U8* joint_mask) +{ + // compute time since last update + F32 deltaTime = time - mRealTimeLast; + + void* speed_ptr = mCharacter->getAnimationData("Walk Speed"); + F32 speed = (speed_ptr) ? *((F32 *)speed_ptr) : 1.f; + + // adjust the passage of time accordingly + F32 adjusted_time = mAdjTimeLast + (deltaTime * speed); + + // save time for next update + mRealTimeLast = time; + mAdjTimeLast = adjusted_time; + + // handle wrap around + if (adjusted_time < 0.0f) + { + adjusted_time = getDuration() + fmod(adjusted_time, getDuration()); + } + + // let the base class update the cycle + return LLKeyframeMotion::onUpdate( adjusted_time, joint_mask ); +} + +// End + + +//----------------------------------------------------------------------------- +// LLWalkAdjustMotion() +// Class Constructor +//----------------------------------------------------------------------------- +LLWalkAdjustMotion::LLWalkAdjustMotion(const LLUUID &id) : LLMotion(id) +{ + mLastTime = 0.f; + mName = "walk_adjust"; +} + +//----------------------------------------------------------------------------- +// LLWalkAdjustMotion::onInitialize() +//----------------------------------------------------------------------------- +LLMotion::LLMotionInitStatus LLWalkAdjustMotion::onInitialize(LLCharacter *character) +{ + mCharacter = character; + mLeftAnkleJoint = mCharacter->getJoint("mAnkleLeft"); + mRightAnkleJoint = mCharacter->getJoint("mAnkleRight"); + + mPelvisJoint = mCharacter->getJoint("mPelvis"); + mPelvisState.setJoint( mPelvisJoint ); + if ( !mPelvisJoint ) + { + llwarns << getName() << ": Can't get pelvis joint." << llendl; + return STATUS_FAILURE; + } + + mPelvisState.setUsage(LLJointState::POS); + addJointState( &mPelvisState ); + + return STATUS_SUCCESS; +} + +//----------------------------------------------------------------------------- +// LLWalkAdjustMotion::onActivate() +//----------------------------------------------------------------------------- +BOOL LLWalkAdjustMotion::onActivate() +{ + mAvgCorrection = 0.f; + mSpeedAdjust = 0.f; + mAnimSpeed = 0.f; + mAvgSpeed = 0.f; + mRelativeDir = 1.f; + mPelvisState.setPosition(LLVector3::zero); + // store ankle positions for next frame + mLastLeftAnklePos = mCharacter->getPosGlobalFromAgent(mLeftAnkleJoint->getWorldPosition()); + mLastRightAnklePos = mCharacter->getPosGlobalFromAgent(mRightAnkleJoint->getWorldPosition()); + + F32 leftAnkleOffset = (mLeftAnkleJoint->getWorldPosition() - mCharacter->getCharacterPosition()).magVec(); + F32 rightAnkleOffset = (mRightAnkleJoint->getWorldPosition() - mCharacter->getCharacterPosition()).magVec(); + mAnkleOffset = llmax(leftAnkleOffset, rightAnkleOffset); + + return TRUE; +} + +//----------------------------------------------------------------------------- +// LLWalkAdjustMotion::onUpdate() +//----------------------------------------------------------------------------- +BOOL LLWalkAdjustMotion::onUpdate(F32 time, U8* joint_mask) +{ + LLVector3 footCorrection; + LLVector3 vel = mCharacter->getCharacterVelocity() * mCharacter->getTimeDilation(); + F32 deltaTime = llclamp(time - mLastTime, 0.f, MAX_TIME_DELTA); + mLastTime = time; + + LLQuaternion inv_rotation = ~mPelvisJoint->getWorldRotation(); + + // get speed and normalize velocity vector + LLVector3 ang_vel = mCharacter->getCharacterAngularVelocity() * mCharacter->getTimeDilation(); + F32 speed = llmin(vel.normVec(), MAX_WALK_PLAYBACK_SPEED); + mAvgSpeed = lerp(mAvgSpeed, speed, LLCriticalDamp::getInterpolant(0.2f)); + + // calculate facing vector in pelvis-local space + // (either straight forward or back, depending on velocity) + LLVector3 localVel = vel * inv_rotation; + if (localVel.mV[VX] > 0.f) + { + mRelativeDir = 1.f; + } + else if (localVel.mV[VX] < 0.f) + { + mRelativeDir = -1.f; + } + + // calculate world-space foot drift + LLVector3 leftFootDelta; + LLVector3 leftFootWorldPosition = mLeftAnkleJoint->getWorldPosition(); + LLVector3d leftFootGlobalPosition = mCharacter->getPosGlobalFromAgent(leftFootWorldPosition); + leftFootDelta.setVec(mLastLeftAnklePos - leftFootGlobalPosition); + mLastLeftAnklePos = leftFootGlobalPosition; + + LLVector3 rightFootDelta; + LLVector3 rightFootWorldPosition = mRightAnkleJoint->getWorldPosition(); + LLVector3d rightFootGlobalPosition = mCharacter->getPosGlobalFromAgent(rightFootWorldPosition); + rightFootDelta.setVec(mLastRightAnklePos - rightFootGlobalPosition); + mLastRightAnklePos = rightFootGlobalPosition; + + // find foot drift along velocity vector + if (mAvgSpeed > 0.1) + { + // walking/running + F32 leftFootDriftAmt = leftFootDelta * vel; + F32 rightFootDriftAmt = rightFootDelta * vel; + + if (rightFootDriftAmt > leftFootDriftAmt) + { + footCorrection = rightFootDelta; + } else + { + footCorrection = leftFootDelta; + } + } + else + { + mAvgSpeed = ang_vel.magVec() * mAnkleOffset; + mRelativeDir = 1.f; + + // standing/turning + // find the lower foot + if (leftFootWorldPosition.mV[VZ] < rightFootWorldPosition.mV[VZ]) + { + // pivot on left foot + footCorrection = leftFootDelta; + } + else + { + // pivot on right foot + footCorrection = rightFootDelta; + } + } + + // rotate into avatar coordinates + footCorrection = footCorrection * inv_rotation; + + // calculate ideal pelvis offset so that foot is glued to ground and damp towards it + // the amount of foot slippage this frame + the offset applied last frame + mPelvisOffset = mPelvisState.getPosition() + lerp(LLVector3::zero, footCorrection, LLCriticalDamp::getInterpolant(0.2f)); + + // pelvis drift (along walk direction) + mAvgCorrection = lerp(mAvgCorrection, footCorrection.mV[VX] * mRelativeDir, LLCriticalDamp::getInterpolant(0.1f)); + + // calculate average velocity of foot slippage + F32 footSlipVelocity = (deltaTime != 0.f) ? (-mAvgCorrection / deltaTime) : 0.f; + + F32 newSpeedAdjust = 0.f; + + // modulate speed by dot products of facing and velocity + // so that if we are moving sideways, we slow down the animation + // and if we're moving backward, we walk backward + + F32 directional_factor = localVel.mV[VX] * mRelativeDir; + if (speed > 0.1f) + { + // calculate ratio of desired foot velocity to detected foot velocity + newSpeedAdjust = llclamp(footSlipVelocity - mAvgSpeed * (1.f - directional_factor), + -SPEED_ADJUST_MAX, SPEED_ADJUST_MAX); + newSpeedAdjust = lerp(mSpeedAdjust, newSpeedAdjust, LLCriticalDamp::getInterpolant(0.2f)); + + F32 speedDelta = newSpeedAdjust - mSpeedAdjust; + speedDelta = llclamp(speedDelta, -SPEED_ADJUST_MAX_SEC * deltaTime, SPEED_ADJUST_MAX_SEC * deltaTime); + + mSpeedAdjust = mSpeedAdjust + speedDelta; + } + else + { + mSpeedAdjust = lerp(mSpeedAdjust, 0.f, LLCriticalDamp::getInterpolant(0.2f)); + } + + mAnimSpeed = (mAvgSpeed + mSpeedAdjust) * mRelativeDir; +// char debug_text[64]; +// sprintf(debug_text, "Foot slip vel: %.2f", footSlipVelocity); +// mCharacter->addDebugText(debug_text); +// sprintf(debug_text, "Speed: %.2f", mAvgSpeed); +// mCharacter->addDebugText(debug_text); +// sprintf(debug_text, "Speed Adjust: %.2f", mSpeedAdjust); +// mCharacter->addDebugText(debug_text); +// sprintf(debug_text, "Animation Playback Speed: %.2f", mAnimSpeed); +// mCharacter->addDebugText(debug_text); + mCharacter->setAnimationData("Walk Speed", &mAnimSpeed); + + // clamp pelvis offset to a 90 degree arc behind the nominal position + F32 drift_comp_max = llclamp(speed, 0.f, DRIFT_COMP_MAX_SPEED) / DRIFT_COMP_MAX_SPEED; + drift_comp_max *= DRIFT_COMP_MAX_TOTAL; + + LLVector3 currentPelvisPos = mPelvisState.getJoint()->getPosition(); + + // NB: this is an ADDITIVE amount that is accumulated every frame, so clamping it alone won't do the trick + // must clamp with absolute position of pelvis in mind + mPelvisOffset.mV[VX] = llclamp( mPelvisOffset.mV[VX], -drift_comp_max - currentPelvisPos.mV[VX], drift_comp_max - currentPelvisPos.mV[VX] ); + mPelvisOffset.mV[VY] = llclamp( mPelvisOffset.mV[VY], -drift_comp_max - currentPelvisPos.mV[VY], drift_comp_max - currentPelvisPos.mV[VY]); + mPelvisOffset.mV[VZ] = 0.f; + + // set position + mPelvisState.setPosition(mPelvisOffset); + + mCharacter->setAnimationData("Pelvis Offset", &mPelvisOffset); + + return TRUE; +} + +//----------------------------------------------------------------------------- +// LLWalkAdjustMotion::onDeactivate() +//----------------------------------------------------------------------------- +void LLWalkAdjustMotion::onDeactivate() +{ + mCharacter->removeAnimationData("Walk Speed"); +} + +//----------------------------------------------------------------------------- +// LLFlyAdjustMotion::onInitialize() +//----------------------------------------------------------------------------- +LLMotion::LLMotionInitStatus LLFlyAdjustMotion::onInitialize(LLCharacter *character) +{ + mCharacter = character; + + LLJoint* pelvisJoint = mCharacter->getJoint("mPelvis"); + mPelvisState.setJoint( pelvisJoint ); + if ( !pelvisJoint ) + { + llwarns << getName() << ": Can't get pelvis joint." << llendl; + return STATUS_FAILURE; + } + + mPelvisState.setUsage(LLJointState::POS | LLJointState::ROT); + addJointState( &mPelvisState ); + + return STATUS_SUCCESS; +} + +//----------------------------------------------------------------------------- +// LLFlyAdjustMotion::onActivate() +//----------------------------------------------------------------------------- +BOOL LLFlyAdjustMotion::onActivate() +{ + mPelvisState.setPosition(LLVector3::zero); + mPelvisState.setRotation(LLQuaternion::DEFAULT); + mRoll = 0.f; + return TRUE; +} + +//----------------------------------------------------------------------------- +// LLFlyAdjustMotion::onUpdate() +//----------------------------------------------------------------------------- +BOOL LLFlyAdjustMotion::onUpdate(F32 time, U8* joint_mask) +{ + LLVector3 ang_vel = mCharacter->getCharacterAngularVelocity() * mCharacter->getTimeDilation(); + F32 speed = mCharacter->getCharacterVelocity().magVec(); + + F32 roll_factor = clamp_rescale(speed, 7.f, 15.f, 0.f, -MAX_ROLL); + F32 target_roll = llclamp(ang_vel.mV[VZ], -4.f, 4.f) * roll_factor; + + // roll is critically damped interpolation between current roll and angular velocity-derived target roll + mRoll = lerp(mRoll, target_roll, LLCriticalDamp::getInterpolant(0.1f)); + +// llinfos << mRoll << llendl; + + LLQuaternion roll(mRoll, LLVector3(0.f, 0.f, 1.f)); + mPelvisState.setRotation(roll); + +// F32 lerp_amt = LLCriticalDamp::getInterpolant(0.2f); +// +// LLVector3 pelvis_correction = mPelvisState.getPosition() - lerp(LLVector3::zero, mPelvisState.getJoint()->getPosition() + mPelvisState.getPosition(), lerp_amt); +// mPelvisState.setPosition(pelvis_correction); + return TRUE; +} diff --git a/indra/llcharacter/llkeyframewalkmotion.h b/indra/llcharacter/llkeyframewalkmotion.h new file mode 100644 index 0000000000..05286456d3 --- /dev/null +++ b/indra/llcharacter/llkeyframewalkmotion.h @@ -0,0 +1,158 @@ +/** + * @file llkeyframewalkmotion.h + * @brief Implementation of LLKeframeWalkMotion class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLKEYFRAMEWALKMOTION_H +#define LL_LLKEYFRAMEWALKMOTION_H + +//----------------------------------------------------------------------------- +// Header files +//----------------------------------------------------------------------------- +#include "llkeyframemotion.h" +#include "llcharacter.h" +#include "v3dmath.h" + +#define MIN_REQUIRED_PIXEL_AREA_WALK_ADJUST (20.f) +#define MIN_REQUIRED_PIXEL_AREA_FLY_ADJUST (20.f) + +//----------------------------------------------------------------------------- +// class LLKeyframeWalkMotion +//----------------------------------------------------------------------------- +class LLKeyframeWalkMotion : + public LLKeyframeMotion +{ + friend class LLWalkAdjustMotion; +public: + // Constructor + LLKeyframeWalkMotion(const LLUUID &id); + + // Destructor + virtual ~LLKeyframeWalkMotion(); + +public: + //------------------------------------------------------------------------- + // functions to support MotionController and MotionRegistry + //------------------------------------------------------------------------- + + // static constructor + // all subclasses must implement such a function and register it + static LLMotion *create(const LLUUID &id) { return new LLKeyframeWalkMotion(id); } + +public: + //------------------------------------------------------------------------- + // animation callbacks to be implemented by subclasses + //------------------------------------------------------------------------- + virtual LLMotionInitStatus onInitialize(LLCharacter *character); + virtual BOOL onActivate(); + virtual void onDeactivate(); + virtual BOOL onUpdate(F32 time, U8* joint_mask); + +public: + //------------------------------------------------------------------------- + // Member Data + //------------------------------------------------------------------------- + LLCharacter *mCharacter; + F32 mCyclePhase; + F32 mRealTimeLast; + F32 mAdjTimeLast; + S32 mDownFoot; +}; + +class LLWalkAdjustMotion : public LLMotion +{ +public: + // Constructor + LLWalkAdjustMotion(const LLUUID &id); + +public: + //------------------------------------------------------------------------- + // functions to support MotionController and MotionRegistry + //------------------------------------------------------------------------- + + // static constructor + // all subclasses must implement such a function and register it + static LLMotion *create(const LLUUID &id) { return new LLWalkAdjustMotion(id); } + +public: + //------------------------------------------------------------------------- + // animation callbacks to be implemented by subclasses + //------------------------------------------------------------------------- + virtual LLMotionInitStatus onInitialize(LLCharacter *character); + virtual BOOL onActivate(); + virtual void onDeactivate(); + virtual BOOL onUpdate(F32 time, U8* joint_mask); + virtual LLJoint::JointPriority getPriority(){return LLJoint::HIGH_PRIORITY;} + virtual BOOL getLoop() { return TRUE; } + virtual F32 getDuration() { return 0.f; } + virtual F32 getEaseInDuration() { return 0.f; } + virtual F32 getEaseOutDuration() { return 0.f; } + virtual F32 getMinPixelArea() { return MIN_REQUIRED_PIXEL_AREA_WALK_ADJUST; } + virtual LLMotionBlendType getBlendType() { return ADDITIVE_BLEND; } + +public: + //------------------------------------------------------------------------- + // Member Data + //------------------------------------------------------------------------- + LLCharacter *mCharacter; + LLJoint* mLeftAnkleJoint; + LLJoint* mRightAnkleJoint; + LLJointState mPelvisState; + LLJoint* mPelvisJoint; + LLVector3d mLastLeftAnklePos; + LLVector3d mLastRightAnklePos; + F32 mLastTime; + F32 mAvgCorrection; + F32 mSpeedAdjust; + F32 mAnimSpeed; + F32 mAvgSpeed; + F32 mRelativeDir; + LLVector3 mPelvisOffset; + F32 mAnkleOffset; +}; + +class LLFlyAdjustMotion : public LLMotion +{ +public: + // Constructor + LLFlyAdjustMotion(const LLUUID &id) : LLMotion(id) {mName = "fly_adjust";} + +public: + //------------------------------------------------------------------------- + // functions to support MotionController and MotionRegistry + //------------------------------------------------------------------------- + + // static constructor + // all subclasses must implement such a function and register it + static LLMotion *create(const LLUUID &id) { return new LLFlyAdjustMotion(id); } + +public: + //------------------------------------------------------------------------- + // animation callbacks to be implemented by subclasses + //------------------------------------------------------------------------- + virtual LLMotionInitStatus onInitialize(LLCharacter *character); + virtual BOOL onActivate(); + virtual void onDeactivate() {}; + virtual BOOL onUpdate(F32 time, U8* joint_mask); + virtual LLJoint::JointPriority getPriority(){return LLJoint::HIGHER_PRIORITY;} + virtual BOOL getLoop() { return TRUE; } + virtual F32 getDuration() { return 0.f; } + virtual F32 getEaseInDuration() { return 0.f; } + virtual F32 getEaseOutDuration() { return 0.f; } + virtual F32 getMinPixelArea() { return MIN_REQUIRED_PIXEL_AREA_FLY_ADJUST; } + virtual LLMotionBlendType getBlendType() { return ADDITIVE_BLEND; } + +protected: + //------------------------------------------------------------------------- + // Member Data + //------------------------------------------------------------------------- + LLCharacter *mCharacter; + LLJointState mPelvisState; + F32 mRoll; +}; + +#endif // LL_LLKeyframeWalkMotion_H + diff --git a/indra/llcharacter/llmotion.cpp b/indra/llcharacter/llmotion.cpp new file mode 100644 index 0000000000..a53956223c --- /dev/null +++ b/indra/llcharacter/llmotion.cpp @@ -0,0 +1,131 @@ +/** + * @file llmotion.cpp + * @brief Implementation of LLMotion class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +//----------------------------------------------------------------------------- +// Header Files +//----------------------------------------------------------------------------- +#include "linden_common.h" + +#include "llmotion.h" +#include "llcriticaldamp.h" + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +// LLMotion class +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// LLMotion() +// Class Constructor +//----------------------------------------------------------------------------- +LLMotion::LLMotion( const LLUUID &id ) +{ + mActivationTimestamp = 0.f; + mStopTimestamp = 0.f; + mSendStopTimestamp = F32_MAX; + mResidualWeight = 0.f; + mFadeWeight = 1.f; + mStopped = TRUE; + mActive = FALSE; + mDeactivateCallback = NULL; + + memset(&mJointSignature[0][0], 0, sizeof(U8) * LL_CHARACTER_MAX_JOINTS); + memset(&mJointSignature[1][0], 0, sizeof(U8) * LL_CHARACTER_MAX_JOINTS); + memset(&mJointSignature[2][0], 0, sizeof(U8) * LL_CHARACTER_MAX_JOINTS); + + mID = id; +} + +//----------------------------------------------------------------------------- +// ~LLMotion() +// Class Destructor +//----------------------------------------------------------------------------- +LLMotion::~LLMotion() +{ +} + +//----------------------------------------------------------------------------- +// fadeOut() +//----------------------------------------------------------------------------- +void LLMotion::fadeOut() +{ + if (mFadeWeight > 0.01f) + { + mFadeWeight = lerp(mFadeWeight, 0.f, LLCriticalDamp::getInterpolant(0.15f)); + } + else + { + mFadeWeight = 0.f; + } +} + +//----------------------------------------------------------------------------- +// fadeIn() +//----------------------------------------------------------------------------- +void LLMotion::fadeIn() +{ + if (mFadeWeight < 0.99f) + { + mFadeWeight = lerp(mFadeWeight, 1.f, LLCriticalDamp::getInterpolant(0.15f)); + } + else + { + mFadeWeight = 1.f; + } +} + +//----------------------------------------------------------------------------- +// addJointState() +//----------------------------------------------------------------------------- +void LLMotion::addJointState(LLJointState* jointState) +{ + mPose.addJointState(jointState); + S32 priority = jointState->getPriority(); + if (priority == LLJoint::USE_MOTION_PRIORITY) + { + priority = getPriority(); + } + + U32 usage = jointState->getUsage(); + + // for now, usage is everything + mJointSignature[0][jointState->getJoint()->getJointNum()] = (usage & LLJointState::POS) ? (0xff >> (7 - priority)) : 0; + mJointSignature[1][jointState->getJoint()->getJointNum()] = (usage & LLJointState::ROT) ? (0xff >> (7 - priority)) : 0; + mJointSignature[2][jointState->getJoint()->getJointNum()] = (usage & LLJointState::SCALE) ? (0xff >> (7 - priority)) : 0; +} + +void LLMotion::setDeactivateCallback( void (*cb)(void *), void* userdata ) +{ + mDeactivateCallback = cb; + mDeactivateCallbackUserData = userdata; +} + +//----------------------------------------------------------------------------- +// activate() +//----------------------------------------------------------------------------- +void LLMotion::activate() +{ + mStopped = FALSE; + mActive = TRUE; + onActivate(); +} + +//----------------------------------------------------------------------------- +// deactivate() +//----------------------------------------------------------------------------- +void LLMotion::deactivate() +{ + mActive = FALSE; + + if (mDeactivateCallback) (*mDeactivateCallback)(mDeactivateCallbackUserData); + + onDeactivate(); +} + +// End diff --git a/indra/llcharacter/llmotion.h b/indra/llcharacter/llmotion.h new file mode 100644 index 0000000000..66882a2c11 --- /dev/null +++ b/indra/llcharacter/llmotion.h @@ -0,0 +1,237 @@ +/** + * @file llmotion.h + * @brief Implementation of LLMotion class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLMOTION_H +#define LL_LLMOTION_H + +//----------------------------------------------------------------------------- +// Header files +//----------------------------------------------------------------------------- +#include <string> + +#include "llerror.h" +#include "llpose.h" +#include "lluuid.h" + +class LLCharacter; + +//----------------------------------------------------------------------------- +// class LLMotion +//----------------------------------------------------------------------------- +class LLMotion +{ +public: + enum LLMotionBlendType + { + NORMAL_BLEND, + ADDITIVE_BLEND + }; + + enum LLMotionInitStatus + { + STATUS_FAILURE, + STATUS_SUCCESS, + STATUS_HOLD + }; + + // Constructor + LLMotion(const LLUUID &id); + + // Destructor + virtual ~LLMotion(); + +public: + //------------------------------------------------------------------------- + // functions to support MotionController and MotionRegistry + //------------------------------------------------------------------------- + + // static constructor + // all subclasses must implement such a function and register it + static LLMotion *create(const LLUUID &id) { return NULL; } + + // get the name of this instance + const std::string &getName() const { return mName; } + + // set the name of this instance + void setName(const std::string &name) { mName = name; } + + const LLUUID& getID() const { return mID; } + + // returns the pose associated with the current state of this motion + virtual LLPose* getPose() { return &mPose;} + + void fadeOut(); + + void fadeIn(); + + F32 getFadeWeight() const { return mFadeWeight; } + + F32 getStopTime() const { return mStopTimestamp; } + + virtual void setStopTime(F32 time) { mStopTimestamp = time; mStopped = TRUE; } + + BOOL isStopped() const { return mStopped; } + + void setStopped(BOOL stopped) { mStopped = stopped; } + + void activate(); + + void deactivate(); + + BOOL isActive() { return mActive; } + + +public: + //------------------------------------------------------------------------- + // animation callbacks to be implemented by subclasses + //------------------------------------------------------------------------- + + // motions must specify whether or not they loop + virtual BOOL getLoop() = 0; + + // motions must report their total duration + virtual F32 getDuration() = 0; + + // motions must report their "ease in" duration + virtual F32 getEaseInDuration() = 0; + + // motions must report their "ease out" duration. + virtual F32 getEaseOutDuration() = 0; + + // motions must report their priority level + virtual LLJoint::JointPriority getPriority() = 0; + + // motions must report their blend type + virtual LLMotionBlendType getBlendType() = 0; + + // called to determine when a motion should be activated/deactivated based on avatar pixel coverage + virtual F32 getMinPixelArea() = 0; + + // run-time (post constructor) initialization, + // called after parameters have been set + // must return true to indicate success and be available for activation + virtual LLMotionInitStatus onInitialize(LLCharacter *character) = 0; + + // called per time step + // must return TRUE while it is active, and + // must return FALSE when the motion is completed. + virtual BOOL onUpdate(F32 activeTime, U8* joint_mask) = 0; + + // called when a motion is deactivated + virtual void onDeactivate() = 0; + + // optional callback routine called when animation deactivated. + void setDeactivateCallback( void (*cb)(void *), void* userdata ); + +protected: + // called when a motion is activated + // must return TRUE to indicate success, or else + // it will be deactivated + virtual BOOL onActivate() = 0; + + void addJointState(LLJointState* jointState); + +protected: + LLPose mPose; + BOOL mStopped; // motion has been stopped; + BOOL mActive; // motion is on active list (can be stopped or not stopped) + +public: + //------------------------------------------------------------------------- + // these are set implicitly by the motion controller and + // may be referenced (read only) in the above handlers. + //------------------------------------------------------------------------- + std::string mName; // instance name assigned by motion controller + LLUUID mID; + + F32 mActivationTimestamp; // time when motion was activated + F32 mStopTimestamp; // time when motion was told to stop + F32 mSendStopTimestamp; // time when simulator should be told to stop this motion + F32 mResidualWeight; // blend weight at beginning of stop motion phase + F32 mFadeWeight; // for fading in and out based on LOD + U8 mJointSignature[3][LL_CHARACTER_MAX_JOINTS]; // signature of which joints are animated at what priority + void (*mDeactivateCallback)(void* data); + void* mDeactivateCallbackUserData; +}; + + +//----------------------------------------------------------------------------- +// LLTestMotion +//----------------------------------------------------------------------------- +class LLTestMotion : public LLMotion +{ +public: + LLTestMotion(const LLUUID &id) : LLMotion(id){} + ~LLTestMotion() {} + static LLMotion *create(const LLUUID& id) { return new LLTestMotion(id); } + BOOL getLoop() { return FALSE; } + F32 getDuration() { return 0.0f; } + F32 getEaseInDuration() { return 0.0f; } + F32 getEaseOutDuration() { return 0.0f; } + LLJoint::JointPriority getPriority() { return LLJoint::HIGH_PRIORITY; } + LLMotionBlendType getBlendType() { return NORMAL_BLEND; } + F32 getMinPixelArea() { return 0.f; } + + LLMotionInitStatus onInitialize(LLCharacter*) { llinfos << "LLTestMotion::onInitialize()" << llendl; return STATUS_SUCCESS; } + BOOL onActivate() { llinfos << "LLTestMotion::onActivate()" << llendl; return TRUE; } + BOOL onUpdate(F32 time, U8* joint_mask) { llinfos << "LLTestMotion::onUpdate(" << time << ")" << llendl; return TRUE; } + void onDeactivate() { llinfos << "LLTestMotion::onDeactivate()" << llendl; } +}; + + +//----------------------------------------------------------------------------- +// LLNullMotion +//----------------------------------------------------------------------------- +class LLNullMotion : public LLMotion +{ +public: + LLNullMotion(const LLUUID &id) : LLMotion(id) {} + ~LLNullMotion() {} + static LLMotion *create(const LLUUID &id) { return new LLNullMotion(id); } + + // motions must specify whether or not they loop + /*virtual*/ BOOL getLoop() { return TRUE; } + + // motions must report their total duration + /*virtual*/ F32 getDuration() { return 1.f; } + + // motions must report their "ease in" duration + /*virtual*/ F32 getEaseInDuration() { return 0.f; } + + // motions must report their "ease out" duration. + /*virtual*/ F32 getEaseOutDuration() { return 0.f; } + + // motions must report their priority level + /*virtual*/ LLJoint::JointPriority getPriority() { return LLJoint::HIGH_PRIORITY; } + + // motions must report their blend type + /*virtual*/ LLMotionBlendType getBlendType() { return NORMAL_BLEND; } + + // called to determine when a motion should be activated/deactivated based on avatar pixel coverage + /*virtual*/ F32 getMinPixelArea() { return 0.f; } + + // run-time (post constructor) initialization, + // called after parameters have been set + // must return true to indicate success and be available for activation + /*virtual*/ LLMotionInitStatus onInitialize(LLCharacter *character) { return STATUS_SUCCESS; } + + // called when a motion is activated + // must return TRUE to indicate success, or else + // it will be deactivated + /*virtual*/ BOOL onActivate() { return TRUE; } + + // called per time step + // must return TRUE while it is active, and + // must return FALSE when the motion is completed. + /*virtual*/ BOOL onUpdate(F32 activeTime, U8* joint_mask) { return TRUE; } + + // called when a motion is deactivated + /*virtual*/ void onDeactivate() {} +}; +#endif // LL_LLMOTION_H + diff --git a/indra/llcharacter/llmotioncontroller.cpp b/indra/llcharacter/llmotioncontroller.cpp new file mode 100644 index 0000000000..7ec67b5fd4 --- /dev/null +++ b/indra/llcharacter/llmotioncontroller.cpp @@ -0,0 +1,927 @@ +/** + * @file llmotioncontroller.cpp + * @brief Implementation of LLMotionController class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +//----------------------------------------------------------------------------- +// 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() ) + { + //FIXME - RN: need to replace with a better default scheme + 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<LLMotion*>::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 + // FIXME: 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<LLUUID> active_motions; + LLDynamicArray<F32> 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 diff --git a/indra/llcharacter/llmotioncontroller.h b/indra/llcharacter/llmotioncontroller.h new file mode 100644 index 0000000000..d43d6d9a8f --- /dev/null +++ b/indra/llcharacter/llmotioncontroller.h @@ -0,0 +1,207 @@ +/** + * @file llmotioncontroller.h + * @brief Implementation of LLMotionController class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLMOTIONCONTROLLER_H +#define LL_LLMOTIONCONTROLLER_H + +//----------------------------------------------------------------------------- +// Header files +//----------------------------------------------------------------------------- +#include <string> +#include <map> +#include <deque> + +#include "linked_lists.h" +#include "lluuidhashmap.h" +#include "llmotion.h" +#include "llpose.h" +#include "llframetimer.h" +#include "llstatemachine.h" +#include "llstring.h" + +//----------------------------------------------------------------------------- +// Class predeclaration +// This is necessary because llcharacter.h includes this file. +//----------------------------------------------------------------------------- +class LLCharacter; + +//----------------------------------------------------------------------------- +// LLMotionRegistry +//----------------------------------------------------------------------------- +typedef LLMotion*(*LLMotionConstructor)(const LLUUID &id); + +class LLMotionTableEntry +{ +public: + LLMotionTableEntry(); + LLMotionTableEntry(LLMotionConstructor constructor, const LLUUID& id); + ~LLMotionTableEntry(){}; + + LLMotion* create(const LLUUID& id); + static BOOL uuidEq(const LLUUID &uuid, const LLMotionTableEntry &id_pair) + { + if (uuid == id_pair.mID) + { + return TRUE; + } + return FALSE; + } + + const LLUUID& getID() { return mID; } + +protected: + LLMotionConstructor mConstructor; + LLUUID mID; +}; + +class LLMotionRegistry +{ +public: + // Constructor + LLMotionRegistry(); + + // Destructor + ~LLMotionRegistry(); + + // adds motion classes to the registry + // returns true if successfull + BOOL addMotion( const LLUUID& id, LLMotionConstructor create); + + // creates a new instance of a named motion + // returns NULL motion is not registered + LLMotion *createMotion( const LLUUID &id ); + + // initialization of motion failed, don't try to create this motion again + void markBad( const LLUUID& id ); + + +protected: + LLUUIDHashMap<LLMotionTableEntry, 32> mMotionTable; +}; + +//----------------------------------------------------------------------------- +// class LLMotionController +//----------------------------------------------------------------------------- +class LLMotionController +{ +public: + // Constructor + LLMotionController(); + + // Destructor + virtual ~LLMotionController(); + + // set associated character + // this must be called exactly once by the containing character class. + // this is generally done in the Character constructor + void setCharacter( LLCharacter *character ); + + // registers a motion with the controller + // (actually just forwards call to motion registry) + // returns true if successfull + BOOL addMotion( const LLUUID& id, LLMotionConstructor create ); + + // creates a motion from the registry + LLMotion *createMotion( const LLUUID &id ); + + // unregisters a motion with the controller + // (actually just forwards call to motion registry) + // returns true if successfull + void removeMotion( const LLUUID& id ); + + // start motion + // begins playing the specified motion + // returns true if successful + BOOL startMotion( const LLUUID &id, F32 start_offset ); + + // stop motion + // stops a playing motion + // in reality, it begins the ease out transition phase + // returns true if successful + BOOL stopMotionLocally( const LLUUID &id, BOOL stop_immediate ); + + // update motions + // invokes the update handlers for each active motion + // activates sequenced motions + // deactivates terminated motions` + void updateMotion(); + + // flush motions + // releases all motion instances + void flushAllMotions(); + + // pause and continue all motions + void pause(); + void unpause(); + BOOL isPaused() { return mPaused; } + + void setTimeStep(F32 step); + + void setTimeFactor(F32 time_factor); + F32 getTimeFactor() { return mTimeFactor; } + + LLMotion* getFirstActiveMotion(); + LLMotion* getNextActiveMotion(); + +//protected: + BOOL isMotionActive( LLMotion *motion ); + BOOL isMotionLoading( LLMotion *motion ); + LLMotion *findMotion( const LLUUID& id ); + +protected: + void deleteAllMotions(); + void addLoadedMotion(LLMotion *motion); + BOOL activateMotion(LLMotion *motion, F32 time); + BOOL deactivateMotion(LLMotion *motion); + void updateRegularMotions(); + void updateAdditiveMotions(); + void resetJointSignatures(); + void updateMotionsByType(LLMotion::LLMotionBlendType motion_type); +protected: + + F32 mTimeFactor; + static LLMotionRegistry sRegistry; + LLPoseBlender mPoseBlender; + + LLCharacter *mCharacter; + +// Life cycle of an animation: +// +// Animations are instantiated and immediately put in the mAllMotions map for their entire lifetime. +// If the animations depend on any asset data, the appropriate data is fetched from the data server, +// and the animation is put on the mLoadingMotions list. +// Once an animations is loaded, it will be initialized and put on the mLoadedMotions deque. +// Any animation that is currently playing also sits in the mActiveMotions list. + + std::map<LLUUID, LLMotion*> mAllMotions; + + LLLinkedList<LLMotion> mLoadingMotions; + std::deque<LLMotion*> mLoadedMotions; + LLLinkedList<LLMotion> mActiveMotions; + + LLFrameTimer mTimer; + F32 mTime; + F32 mTimeOffset; + F32 mLastTime; + BOOL mHasRunOnce; + BOOL mPaused; + F32 mTimeStep; + S32 mTimeStepCount; + F32 mLastInterp; + F32 mPauseTime; + + U8 mJointSignature[2][LL_CHARACTER_MAX_JOINTS]; +}; + +//----------------------------------------------------------------------------- +// Class declaractions +//----------------------------------------------------------------------------- +#include "llcharacter.h" + +#endif // LL_LLMOTIONCONTROLLER_H + diff --git a/indra/llcharacter/llmultigesture.cpp b/indra/llcharacter/llmultigesture.cpp new file mode 100644 index 0000000000..1e42352d74 --- /dev/null +++ b/indra/llcharacter/llmultigesture.cpp @@ -0,0 +1,478 @@ +/** + * @file llmultigesture.cpp + * @brief Gestures that are asset-based and can have multiple steps. + * + * Copyright (c) 2004-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include <algorithm> + +#include "stdio.h" + +#include "llmultigesture.h" + +#include "llerror.h" +#include "lldatapacker.h" +#include "llstl.h" + +const S32 GESTURE_VERSION = 2; + +//--------------------------------------------------------------------------- +// LLMultiGesture +//--------------------------------------------------------------------------- +LLMultiGesture::LLMultiGesture() +: mKey(), + mMask(), + mTrigger(), + mReplaceText(), + mSteps(), + mPlaying(FALSE), + mCurrentStep(0), + mDoneCallback(NULL), + mCallbackData(NULL) +{ + reset(); +} + +LLMultiGesture::~LLMultiGesture() +{ + std::for_each(mSteps.begin(), mSteps.end(), DeletePointer()); +} + +void LLMultiGesture::reset() +{ + mPlaying = FALSE; + mCurrentStep = 0; + mWaitTimer.reset(); + mWaitingTimer = FALSE; + mWaitingAnimations = FALSE; + mWaitingAtEnd = FALSE; + mRequestedAnimIDs.clear(); + mPlayingAnimIDs.clear(); +} + +S32 LLMultiGesture::getMaxSerialSize() const +{ + S32 max_size = 0; + + // ascii format, being very conservative about possible + // label lengths. + max_size += 64; // version S32 + max_size += 64; // key U8 + max_size += 64; // mask U32 + max_size += 256; // trigger string + max_size += 256; // replace string + + max_size += 64; // step count S32 + + std::vector<LLGestureStep*>::const_iterator it; + for (it = mSteps.begin(); it != mSteps.end(); ++it) + { + LLGestureStep* step = *it; + max_size += 64; // type S32 + max_size += step->getMaxSerialSize(); + } + + /* binary format + max_size += sizeof(S32); // version + max_size += sizeof(mKey); + max_size += sizeof(mMask); + max_size += mTrigger.length() + 1; // for null + + max_size += sizeof(S32); // step count + + std::vector<LLGestureStep*>::const_iterator it; + for (it = mSteps.begin(); it != mSteps.end(); ++it) + { + LLGestureStep* step = *it; + max_size += sizeof(S32); // type + max_size += step->getMaxSerialSize(); + } + */ + + return max_size; +} + +BOOL LLMultiGesture::serialize(LLDataPacker& dp) const +{ + dp.packS32(GESTURE_VERSION, "version"); + dp.packU8(mKey, "key"); + dp.packU32(mMask, "mask"); + dp.packString(mTrigger.c_str(), "trigger"); + dp.packString(mReplaceText.c_str(), "replace"); + + S32 count = (S32)mSteps.size(); + dp.packS32(count, "step_count"); + S32 i; + for (i = 0; i < count; ++i) + { + LLGestureStep* step = mSteps[i]; + + dp.packS32(step->getType(), "step_type"); + BOOL ok = step->serialize(dp); + if (!ok) + { + return FALSE; + } + } + return TRUE; +} + +BOOL LLMultiGesture::deserialize(LLDataPacker& dp) +{ + S32 version; + dp.unpackS32(version, "version"); + if (version != GESTURE_VERSION) + { + llwarns << "Bad LLMultiGesture version " << version + << " should be " << GESTURE_VERSION + << llendl; + return FALSE; + } + + dp.unpackU8(mKey, "key"); + dp.unpackU32(mMask, "mask"); + + char buffer[256]; /* Flawfinder: ignore */ + dp.unpackString(buffer, "trigger"); + mTrigger = buffer; + + dp.unpackString(buffer, "replace"); + mReplaceText = buffer; + + S32 count; + dp.unpackS32(count, "step_count"); + if (count < 0) + { + llwarns << "Bad LLMultiGesture step count " << count << llendl; + return FALSE; + } + + S32 i; + for (i = 0; i < count; ++i) + { + S32 type; + dp.unpackS32(type, "step_type"); + + EStepType step_type = (EStepType)type; + switch(step_type) + { + case STEP_ANIMATION: + { + LLGestureStepAnimation* step = new LLGestureStepAnimation(); + BOOL ok = step->deserialize(dp); + if (!ok) return FALSE; + mSteps.push_back(step); + break; + } + case STEP_SOUND: + { + LLGestureStepSound* step = new LLGestureStepSound(); + BOOL ok = step->deserialize(dp); + if (!ok) return FALSE; + mSteps.push_back(step); + break; + } + case STEP_CHAT: + { + LLGestureStepChat* step = new LLGestureStepChat(); + BOOL ok = step->deserialize(dp); + if (!ok) return FALSE; + mSteps.push_back(step); + break; + } + case STEP_WAIT: + { + LLGestureStepWait* step = new LLGestureStepWait(); + BOOL ok = step->deserialize(dp); + if (!ok) return FALSE; + mSteps.push_back(step); + break; + } + default: + { + llwarns << "Bad LLMultiGesture step type " << type << llendl; + return FALSE; + } + } + } + return TRUE; +} + +void LLMultiGesture::dump() +{ + llinfos << "key " << S32(mKey) << " mask " << U32(mMask) + << " trigger " << mTrigger + << " replace " << mReplaceText + << llendl; + U32 i; + for (i = 0; i < mSteps.size(); ++i) + { + LLGestureStep* step = mSteps[i]; + step->dump(); + } +} + +//--------------------------------------------------------------------------- +// LLGestureStepAnimation +//--------------------------------------------------------------------------- +LLGestureStepAnimation::LLGestureStepAnimation() +: LLGestureStep(), + mAnimName("None"), + mAnimAssetID(), + mFlags(0x0) +{ } + +LLGestureStepAnimation::~LLGestureStepAnimation() +{ } + +S32 LLGestureStepAnimation::getMaxSerialSize() const +{ + S32 max_size = 0; + + // ascii + max_size += 256; // anim name + max_size += 64; // anim asset id + max_size += 64; // flags + + /* binary + max_size += mAnimName.length() + 1; + max_size += sizeof(mAnimAssetID); + max_size += sizeof(mFlags); + */ + return max_size; +} + +BOOL LLGestureStepAnimation::serialize(LLDataPacker& dp) const +{ + dp.packString(mAnimName.c_str(), "anim_name"); + dp.packUUID(mAnimAssetID, "asset_id"); + dp.packU32(mFlags, "flags"); + return TRUE; +} + +BOOL LLGestureStepAnimation::deserialize(LLDataPacker& dp) +{ + char buffer[256]; /* Flawfinder: ignore */ + dp.unpackString(buffer, "anim_name"); + mAnimName = buffer; + + // Apparently an earlier version of the gesture code added \r to the end + // of the animation names. Get rid of it. JC + if (mAnimName[mAnimName.length() - 1] == '\r') + { + // chop the last character + mAnimName.resize(mAnimName.length() - 1); + } + + dp.unpackUUID(mAnimAssetID, "asset_id"); + dp.unpackU32(mFlags, "flags"); + return TRUE; +} + +std::string LLGestureStepAnimation::getLabel() const +{ + std::string label; + if (mFlags & ANIM_FLAG_STOP) + { + label = "Stop Animation: "; + } + else + { + label = "Start Animation: "; + } + label += mAnimName; + return label; +} + +void LLGestureStepAnimation::dump() +{ + llinfos << "step animation " << mAnimName + << " id " << mAnimAssetID + << " flags " << mFlags + << llendl; +} + +//--------------------------------------------------------------------------- +// LLGestureStepSound +//--------------------------------------------------------------------------- +LLGestureStepSound::LLGestureStepSound() +: LLGestureStep(), + mSoundName("None"), + mSoundAssetID(), + mFlags(0x0) +{ } + +LLGestureStepSound::~LLGestureStepSound() +{ } + +S32 LLGestureStepSound::getMaxSerialSize() const +{ + S32 max_size = 0; + max_size += 256; // sound name + max_size += 64; // sound asset id + max_size += 64; // flags + /* binary + max_size += mSoundName.length() + 1; + max_size += sizeof(mSoundAssetID); + max_size += sizeof(mFlags); + */ + return max_size; +} + +BOOL LLGestureStepSound::serialize(LLDataPacker& dp) const +{ + dp.packString(mSoundName.c_str(), "sound_name"); + dp.packUUID(mSoundAssetID, "asset_id"); + dp.packU32(mFlags, "flags"); + return TRUE; +} + +BOOL LLGestureStepSound::deserialize(LLDataPacker& dp) +{ + char buffer[256]; /* Flawfinder: ignore */ + dp.unpackString(buffer, "sound_name"); + mSoundName = buffer; + + dp.unpackUUID(mSoundAssetID, "asset_id"); + dp.unpackU32(mFlags, "flags"); + return TRUE; +} + +std::string LLGestureStepSound::getLabel() const +{ + std::string label("Sound: "); + label += mSoundName; + return label; +} + +void LLGestureStepSound::dump() +{ + llinfos << "step sound " << mSoundName + << " id " << mSoundAssetID + << " flags " << mFlags + << llendl; +} + + +//--------------------------------------------------------------------------- +// LLGestureStepChat +//--------------------------------------------------------------------------- +LLGestureStepChat::LLGestureStepChat() +: LLGestureStep(), + mChatText(), + mFlags(0x0) +{ } + +LLGestureStepChat::~LLGestureStepChat() +{ } + +S32 LLGestureStepChat::getMaxSerialSize() const +{ + S32 max_size = 0; + max_size += 256; // chat text + max_size += 64; // flags + /* binary + max_size += mChatText.length() + 1; + max_size += sizeof(mFlags); + */ + return max_size; +} + +BOOL LLGestureStepChat::serialize(LLDataPacker& dp) const +{ + dp.packString(mChatText.c_str(), "chat_text"); + dp.packU32(mFlags, "flags"); + return TRUE; +} + +BOOL LLGestureStepChat::deserialize(LLDataPacker& dp) +{ + char buffer[256]; /* Flawfinder: ignore */ + dp.unpackString(buffer, "chat_text"); + mChatText = buffer; + + dp.unpackU32(mFlags, "flags"); + return TRUE; +} + +std::string LLGestureStepChat::getLabel() const +{ + std::string label("Chat: "); + label += mChatText; + return label; +} + +void LLGestureStepChat::dump() +{ + llinfos << "step chat " << mChatText + << " flags " << mFlags + << llendl; +} + + +//--------------------------------------------------------------------------- +// LLGestureStepWait +//--------------------------------------------------------------------------- +LLGestureStepWait::LLGestureStepWait() +: LLGestureStep(), + mWaitSeconds(0.f), + mFlags(0x0) +{ } + +LLGestureStepWait::~LLGestureStepWait() +{ } + +S32 LLGestureStepWait::getMaxSerialSize() const +{ + S32 max_size = 0; + max_size += 64; // wait seconds + max_size += 64; // flags + /* binary + max_size += sizeof(mWaitSeconds); + max_size += sizeof(mFlags); + */ + return max_size; +} + +BOOL LLGestureStepWait::serialize(LLDataPacker& dp) const +{ + dp.packF32(mWaitSeconds, "wait_seconds"); + dp.packU32(mFlags, "flags"); + return TRUE; +} + +BOOL LLGestureStepWait::deserialize(LLDataPacker& dp) +{ + dp.unpackF32(mWaitSeconds, "wait_seconds"); + dp.unpackU32(mFlags, "flags"); + return TRUE; +} + +std::string LLGestureStepWait::getLabel() const +{ + std::string label("--- Wait: "); + if (mFlags & WAIT_FLAG_TIME) + { + char buffer[64]; /* Flawfinder: ignore */ + snprintf(buffer, sizeof(buffer), "%.1f seconds", (double)mWaitSeconds); /* Flawfinder: ignore */ + label += buffer; + } + else if (mFlags & WAIT_FLAG_ALL_ANIM) + { + label += "until animations are done"; + } + + return label; +} + + +void LLGestureStepWait::dump() +{ + llinfos << "step wait " << mWaitSeconds + << " flags " << mFlags + << llendl; +} diff --git a/indra/llcharacter/llmultigesture.h b/indra/llcharacter/llmultigesture.h new file mode 100644 index 0000000000..39ee30568b --- /dev/null +++ b/indra/llcharacter/llmultigesture.h @@ -0,0 +1,213 @@ +/** + * @file llmultigesture.h + * @brief Gestures that are asset-based and can have multiple steps. + * + * Copyright (c) 2004-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLMULTIGESTURE_H +#define LL_LLMULTIGESTURE_H + +#include <set> +#include <string> +#include <vector> + +#include "lluuid.h" +#include "llframetimer.h" + +class LLDataPacker; +class LLGestureStep; + +class LLMultiGesture +{ +public: + LLMultiGesture(); + virtual ~LLMultiGesture(); + + // Maximum number of bytes this could hold once serialized. + S32 getMaxSerialSize() const; + + BOOL serialize(LLDataPacker& dp) const; + BOOL deserialize(LLDataPacker& dp); + + void dump(); + + void reset(); + + const std::string& getTrigger() const { return mTrigger; } +protected: + LLMultiGesture(const LLMultiGesture& gest); + const LLMultiGesture& operator=(const LLMultiGesture& rhs); + +public: + // name is stored at asset level + // desc is stored at asset level + KEY mKey; + MASK mMask; + + // String, like "/foo" or "hello" that makes it play + std::string mTrigger; + + // Replaces the trigger substring with this text + std::string mReplaceText; + + std::vector<LLGestureStep*> mSteps; + + // Is the gesture currently playing? + BOOL mPlaying; + + // "instruction pointer" for steps + S32 mCurrentStep; + + // We're waiting for triggered animations to stop playing + BOOL mWaitingAnimations; + + // We're waiting a fixed amount of time + BOOL mWaitingTimer; + + // Waiting after the last step played for all animations to complete + BOOL mWaitingAtEnd; + + // Timer for waiting + LLFrameTimer mWaitTimer; + + void (*mDoneCallback)(LLMultiGesture* gesture, void* data); + void* mCallbackData; + + // Animations that we requested to start + std::set<LLUUID> mRequestedAnimIDs; + + // Once the animation starts playing (sim says to start playing) + // the ID is moved from mRequestedAnimIDs to here. + std::set<LLUUID> mPlayingAnimIDs; +}; + + +enum EStepType +{ + STEP_ANIMATION = 0, + STEP_SOUND = 1, + STEP_CHAT = 2, + STEP_WAIT = 3, + + STEP_EOF = 4 +}; + + +class LLGestureStep +{ +public: + LLGestureStep() {} + virtual ~LLGestureStep() {} + + virtual EStepType getType() = 0; + + // Return a user-readable label for this step + virtual std::string getLabel() const = 0; + + virtual S32 getMaxSerialSize() const = 0; + virtual BOOL serialize(LLDataPacker& dp) const = 0; + virtual BOOL deserialize(LLDataPacker& dp) = 0; + + virtual void dump() = 0; +}; + + +// By default, animation steps start animations. +// If the least significant bit is 1, it will stop animations. +const U32 ANIM_FLAG_STOP = 0x01; + +class LLGestureStepAnimation : public LLGestureStep +{ +public: + LLGestureStepAnimation(); + virtual ~LLGestureStepAnimation(); + + virtual EStepType getType() { return STEP_ANIMATION; } + + virtual std::string getLabel() const; + + virtual S32 getMaxSerialSize() const; + virtual BOOL serialize(LLDataPacker& dp) const; + virtual BOOL deserialize(LLDataPacker& dp); + + virtual void dump(); + +public: + std::string mAnimName; + LLUUID mAnimAssetID; + U32 mFlags; +}; + + +class LLGestureStepSound : public LLGestureStep +{ +public: + LLGestureStepSound(); + virtual ~LLGestureStepSound(); + + virtual EStepType getType() { return STEP_SOUND; } + + virtual std::string getLabel() const; + + virtual S32 getMaxSerialSize() const; + virtual BOOL serialize(LLDataPacker& dp) const; + virtual BOOL deserialize(LLDataPacker& dp); + + virtual void dump(); + +public: + std::string mSoundName; + LLUUID mSoundAssetID; + U32 mFlags; +}; + + +class LLGestureStepChat : public LLGestureStep +{ +public: + LLGestureStepChat(); + virtual ~LLGestureStepChat(); + + virtual EStepType getType() { return STEP_CHAT; } + + virtual std::string getLabel() const; + + virtual S32 getMaxSerialSize() const; + virtual BOOL serialize(LLDataPacker& dp) const; + virtual BOOL deserialize(LLDataPacker& dp); + + virtual void dump(); + +public: + std::string mChatText; + U32 mFlags; +}; + + +const U32 WAIT_FLAG_TIME = 0x01; +const U32 WAIT_FLAG_ALL_ANIM = 0x02; + +class LLGestureStepWait : public LLGestureStep +{ +public: + LLGestureStepWait(); + virtual ~LLGestureStepWait(); + + virtual EStepType getType() { return STEP_WAIT; } + + virtual std::string getLabel() const; + + virtual S32 getMaxSerialSize() const; + virtual BOOL serialize(LLDataPacker& dp) const; + virtual BOOL deserialize(LLDataPacker& dp); + + virtual void dump(); + +public: + F32 mWaitSeconds; + U32 mFlags; +}; + +#endif diff --git a/indra/llcharacter/llpose.cpp b/indra/llcharacter/llpose.cpp new file mode 100644 index 0000000000..ff56dba585 --- /dev/null +++ b/indra/llcharacter/llpose.cpp @@ -0,0 +1,544 @@ +/** + * @file llpose.cpp + * @brief Implementation of LLPose class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +//----------------------------------------------------------------------------- +// Header Files +//----------------------------------------------------------------------------- +#include "linden_common.h" + +#include "llpose.h" + +#include "llmotion.h" +#include "llmath.h" + +//----------------------------------------------------------------------------- +// Static +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// LLPose +//----------------------------------------------------------------------------- +LLPose::~LLPose() +{ +} + +//----------------------------------------------------------------------------- +// getFirstJointState() +//----------------------------------------------------------------------------- +LLJointState *LLPose::getFirstJointState() +{ + mListIter = mJointMap.begin(); + if (mListIter == mJointMap.end()) + { + return NULL; + } + else + { + return mListIter->second; + } +} + +//----------------------------------------------------------------------------- +// getNextJointState() +//----------------------------------------------------------------------------- +LLJointState *LLPose::getNextJointState() +{ + mListIter++; + if (mListIter == mJointMap.end()) + { + return NULL; + } + else + { + return mListIter->second; + } +} + +//----------------------------------------------------------------------------- +// addJointState() +//----------------------------------------------------------------------------- +BOOL LLPose::addJointState(LLJointState *jointState) +{ + if (mJointMap.find(jointState->getJoint()->getName()) == mJointMap.end()) + { + mJointMap[jointState->getJoint()->getName()] = jointState; + } + return TRUE; +} + +//----------------------------------------------------------------------------- +// removeJointState() +//----------------------------------------------------------------------------- +BOOL LLPose::removeJointState(LLJointState *jointState) +{ + mJointMap.erase(jointState->getJoint()->getName()); + return TRUE; +} + +//----------------------------------------------------------------------------- +// removeAllJointStates() +//----------------------------------------------------------------------------- +BOOL LLPose::removeAllJointStates() +{ + mJointMap.clear(); + return TRUE; +} + +//----------------------------------------------------------------------------- +// findJointState() +//----------------------------------------------------------------------------- +LLJointState* LLPose::findJointState(LLJoint *joint) +{ + joint_map_iterator iter = mJointMap.find(joint->getName()); + + if (iter == mJointMap.end()) + { + return NULL; + } + else + { + return iter->second; + } +} + +//----------------------------------------------------------------------------- +// findJointState() +//----------------------------------------------------------------------------- +LLJointState* LLPose::findJointState(const std::string &name) +{ + joint_map_iterator iter = mJointMap.find(name); + + if (iter == mJointMap.end()) + { + return NULL; + } + else + { + return iter->second; + } +} + +//----------------------------------------------------------------------------- +// setWeight() +//----------------------------------------------------------------------------- +void LLPose::setWeight(F32 weight) +{ + joint_map_iterator iter; + for(iter = mJointMap.begin(); + iter != mJointMap.end(); + ++iter) + { + iter->second->setWeight(weight); + } + mWeight = weight; +} + +//----------------------------------------------------------------------------- +// getWeight() +//----------------------------------------------------------------------------- +F32 LLPose::getWeight() const +{ + return mWeight; +} + +//----------------------------------------------------------------------------- +// getNumJointStates() +//----------------------------------------------------------------------------- +S32 LLPose::getNumJointStates() const +{ + return (S32)mJointMap.size(); +} + +//----------------------------------------------------------------------------- +// LLJointStateBlender +//----------------------------------------------------------------------------- + +LLJointStateBlender::LLJointStateBlender() +{ + for(S32 i = 0; i < JSB_NUM_JOINT_STATES; i++) + { + mJointStates[i] = NULL; + mPriorities[i] = S32_MIN; + } +} + +LLJointStateBlender::~LLJointStateBlender() +{ + +} + +//----------------------------------------------------------------------------- +// addJointState() +//----------------------------------------------------------------------------- +BOOL LLJointStateBlender::addJointState(LLJointState *joint_state, S32 priority, BOOL additive_blend) +{ + llassert(joint_state); + + if (!joint_state->getJoint()) + // this joint state doesn't point to an actual joint, so we don't care about applying it + return FALSE; + + for(S32 i = 0; i < JSB_NUM_JOINT_STATES; i++) + { + if (NULL == mJointStates[i]) + { + mJointStates[i] = joint_state; + mPriorities[i] = priority; + mAdditiveBlends[i] = additive_blend; + return TRUE; + } + else if (priority > mPriorities[i]) + { + // we're at a higher priority than the current joint state in this slot + // so shift everyone over + // previous joint states (newer motions) with same priority should stay in place + for (S32 j = JSB_NUM_JOINT_STATES - 1; j > i; j--) + { + mJointStates[j] = mJointStates[j - 1]; + mPriorities[j] = mPriorities[j - 1]; + mAdditiveBlends[j] = mAdditiveBlends[j - 1]; + } + // now store ourselves in this slot + mJointStates[i] = joint_state; + mPriorities[i] = priority; + mAdditiveBlends[i] = additive_blend; + return TRUE; + } + } + + return FALSE; +} + +//----------------------------------------------------------------------------- +// blendJointStates() +//----------------------------------------------------------------------------- +void LLJointStateBlender::blendJointStates(BOOL apply_now) +{ + // we need at least one joint to blend + // if there is one, it will be in slot zero according to insertion logic + // instead of resetting joint state to default, just leave it unchanged from last frame + if (NULL == mJointStates[0]) + { + return; + } + + LLJoint* target_joint = apply_now ? mJointStates[0]->getJoint() : &mJointCache; + + const S32 POS_WEIGHT = 0; + const S32 ROT_WEIGHT = 1; + const S32 SCALE_WEIGHT = 2; + + F32 sum_weights[3]; + U32 sum_usage = 0; + + LLVector3 blended_pos = target_joint->getPosition(); + LLQuaternion blended_rot = target_joint->getRotation(); + LLVector3 blended_scale = target_joint->getScale(); + + LLVector3 added_pos; + LLQuaternion added_rot; + LLVector3 added_scale; + + //S32 joint_state_index; + + sum_weights[POS_WEIGHT] = 0.f; + sum_weights[ROT_WEIGHT] = 0.f; + sum_weights[SCALE_WEIGHT] = 0.f; + + for(S32 joint_state_index = 0; + joint_state_index < JSB_NUM_JOINT_STATES && mJointStates[joint_state_index] != NULL; + joint_state_index++) + { + U32 current_usage = mJointStates[joint_state_index]->getUsage(); + F32 current_weight = mJointStates[joint_state_index]->getWeight(); + LLJointState* jsp = mJointStates[joint_state_index]; + + if (current_weight == 0.f) + { + continue; + } + + if (mAdditiveBlends[joint_state_index]) + { + if(current_usage & LLJointState::POS) + { + F32 new_weight_sum = llmin(1.f, current_weight + sum_weights[POS_WEIGHT]); + + // add in pos for this jointstate modulated by weight + added_pos += jsp->getPosition() * (new_weight_sum - sum_weights[POS_WEIGHT]); + //sum_weights[POS_WEIGHT] = new_weight_sum; + } + + // now do scale + if(current_usage & LLJointState::SCALE) + { + F32 new_weight_sum = llmin(1.f, current_weight + sum_weights[SCALE_WEIGHT]); + + // add in scale for this jointstate modulated by weight + added_scale += jsp->getScale() * (new_weight_sum - sum_weights[SCALE_WEIGHT]); + //sum_weights[SCALE_WEIGHT] = new_weight_sum; + } + + if (current_usage & LLJointState::ROT) + { + F32 new_weight_sum = llmin(1.f, current_weight + sum_weights[ROT_WEIGHT]); + + // add in rotation for this jointstate modulated by weight + added_rot = nlerp((new_weight_sum - sum_weights[ROT_WEIGHT]), added_rot, jsp->getRotation()) * added_rot; + //sum_weights[ROT_WEIGHT] = new_weight_sum; + } + } + else + { + // blend two jointstates together + + // blend position + if(current_usage & LLJointState::POS) + { + if(sum_usage & LLJointState::POS) + { + F32 new_weight_sum = llmin(1.f, current_weight + sum_weights[POS_WEIGHT]); + + // blend positions from both + blended_pos = lerp(mJointStates[joint_state_index]->getPosition(), blended_pos, sum_weights[POS_WEIGHT] / new_weight_sum); + sum_weights[POS_WEIGHT] = new_weight_sum; + } + else + { + // copy position from current + blended_pos = mJointStates[joint_state_index]->getPosition(); + sum_weights[POS_WEIGHT] = current_weight; + } + } + + // now do scale + if(current_usage & LLJointState::SCALE) + { + if(sum_usage & LLJointState::SCALE) + { + F32 new_weight_sum = llmin(1.f, current_weight + sum_weights[SCALE_WEIGHT]); + + // blend scales from both + blended_scale = lerp(mJointStates[joint_state_index]->getScale(), blended_scale, sum_weights[SCALE_WEIGHT] / new_weight_sum); + sum_weights[SCALE_WEIGHT] = new_weight_sum; + } + else + { + // copy scale from current + blended_scale = mJointStates[joint_state_index]->getScale(); + sum_weights[SCALE_WEIGHT] = current_weight; + } + } + + // rotation + if (current_usage & LLJointState::ROT) + { + if(sum_usage & LLJointState::ROT) + { + F32 new_weight_sum = llmin(1.f, current_weight + sum_weights[ROT_WEIGHT]); + + // blend rotations from both + blended_rot = nlerp(sum_weights[ROT_WEIGHT] / new_weight_sum, mJointStates[joint_state_index]->getRotation(), blended_rot); + sum_weights[ROT_WEIGHT] = new_weight_sum; + } + else + { + // copy rotation from current + blended_rot = mJointStates[joint_state_index]->getRotation(); + sum_weights[ROT_WEIGHT] = current_weight; + } + } + + // update resulting usage mask + sum_usage = sum_usage | current_usage; + } + } + + // apply blended transforms + target_joint->setPosition(blended_pos); + target_joint->setScale(blended_scale); + target_joint->setRotation(blended_rot); + + // apply additive transforms + target_joint->setPosition(target_joint->getPosition() + added_pos); + target_joint->setScale(target_joint->getScale() + added_scale); + target_joint->setRotation(added_rot * target_joint->getRotation()); + + if (apply_now) + { + // now clear joint states + for(S32 i = 0; i < JSB_NUM_JOINT_STATES; i++) + { + mJointStates[i] = NULL; + } + } +} + +//----------------------------------------------------------------------------- +// interpolate() +//----------------------------------------------------------------------------- +void LLJointStateBlender::interpolate(F32 u) +{ + // only interpolate if we have a joint state + if (!mJointStates[0]) + { + return; + } + LLJoint* target_joint = mJointStates[0]->getJoint(); + + if (!target_joint) + { + return; + } + + target_joint->setPosition(lerp(target_joint->getPosition(), mJointCache.getPosition(), u)); + target_joint->setScale(lerp(target_joint->getScale(), mJointCache.getScale(), u)); + target_joint->setRotation(nlerp(u, target_joint->getRotation(), mJointCache.getRotation())); +} + +//----------------------------------------------------------------------------- +// clear() +//----------------------------------------------------------------------------- +void LLJointStateBlender::clear() +{ + // now clear joint states + for(S32 i = 0; i < JSB_NUM_JOINT_STATES; i++) + { + mJointStates[i] = NULL; + } +} + +//----------------------------------------------------------------------------- +// resetCachedJoint() +//----------------------------------------------------------------------------- +void LLJointStateBlender::resetCachedJoint() +{ + if (!mJointStates[0]) + { + return; + } + LLJoint* source_joint = mJointStates[0]->getJoint(); + mJointCache.setPosition(source_joint->getPosition()); + mJointCache.setScale(source_joint->getScale()); + mJointCache.setRotation(source_joint->getRotation()); +} + +//----------------------------------------------------------------------------- +// LLPoseBlender +//----------------------------------------------------------------------------- + +LLPoseBlender::LLPoseBlender() +{ +} + +LLPoseBlender::~LLPoseBlender() +{ + mJointStateBlenderPool.deleteAllData(); +} + +//----------------------------------------------------------------------------- +// addMotion() +//----------------------------------------------------------------------------- +BOOL LLPoseBlender::addMotion(LLMotion* motion) +{ + LLPose* pose = motion->getPose(); + + for(LLJointState *jsp = pose->getFirstJointState(); jsp; jsp = pose->getNextJointState()) + { + LLJoint *jointp = jsp->getJoint(); + LLJointStateBlender* joint_blender; + if (!mJointStateBlenderPool.checkData(jointp)) + { + // this is the first time we are animating this joint + // so create new jointblender and add it to our pool + joint_blender = new LLJointStateBlender(); + mJointStateBlenderPool.addData(jointp, joint_blender); + } else + { + joint_blender = mJointStateBlenderPool.getData(jointp); + } + + if (jsp->getPriority() == LLJoint::USE_MOTION_PRIORITY) + { + joint_blender->addJointState(jsp, motion->getPriority(), motion->getBlendType() == LLMotion::ADDITIVE_BLEND); + } + else + { + joint_blender->addJointState(jsp, jsp->getPriority(), motion->getBlendType() == LLMotion::ADDITIVE_BLEND); + } + + // add it to our list of active blenders + if(!mActiveBlenders.checkData(joint_blender)) + { + mActiveBlenders.addData(joint_blender); + } + } + return TRUE; +} + +//----------------------------------------------------------------------------- +// blendAndApply() +//----------------------------------------------------------------------------- +void LLPoseBlender::blendAndApply() +{ + for (LLJointStateBlender* jsbp = mActiveBlenders.getFirstData(); + jsbp; + jsbp = mActiveBlenders.getNextData()) + { + jsbp->blendJointStates(); + } + + // we're done now so there are no more active blenders for this frame + mActiveBlenders.removeAllNodes(); +} + +//----------------------------------------------------------------------------- +// blendAndCache() +//----------------------------------------------------------------------------- +void LLPoseBlender::blendAndCache(BOOL reset_cached_joints) +{ + for (LLJointStateBlender* jsbp = mActiveBlenders.getFirstData(); + jsbp; + jsbp = mActiveBlenders.getNextData()) + { + if (reset_cached_joints) + { + jsbp->resetCachedJoint(); + } + jsbp->blendJointStates(FALSE); + } +} + +//----------------------------------------------------------------------------- +// interpolate() +//----------------------------------------------------------------------------- +void LLPoseBlender::interpolate(F32 u) +{ + for (LLJointStateBlender* jsbp = mActiveBlenders.getFirstData(); + jsbp; + jsbp = mActiveBlenders.getNextData()) + { + jsbp->interpolate(u); + } +} + +//----------------------------------------------------------------------------- +// clearBlenders() +//----------------------------------------------------------------------------- +void LLPoseBlender::clearBlenders() +{ + for (LLJointStateBlender* jsbp = mActiveBlenders.getFirstData(); + jsbp; + jsbp = mActiveBlenders.getNextData()) + { + jsbp->clear(); + } + + mActiveBlenders.removeAllNodes(); +} + diff --git a/indra/llcharacter/llpose.h b/indra/llcharacter/llpose.h new file mode 100644 index 0000000000..e286c14e21 --- /dev/null +++ b/indra/llcharacter/llpose.h @@ -0,0 +1,120 @@ +/** + * @file llpose.h + * @brief Implementation of LLPose class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLPOSE_H +#define LL_LLPOSE_H + +//----------------------------------------------------------------------------- +// Header Files +//----------------------------------------------------------------------------- +#include <string> + +#include "linked_lists.h" +#include "llmap.h" +#include "lljointstate.h" +#include "llassoclist.h" +#include "lljoint.h" +#include <map> + + +//----------------------------------------------------------------------------- +// class LLPose +//----------------------------------------------------------------------------- +class LLPose +{ + friend class LLPoseBlender; +protected: + typedef std::map<std::string, LLJointState*> joint_map; + typedef joint_map::iterator joint_map_iterator; + typedef joint_map::value_type joint_map_value_type; + + joint_map mJointMap; + F32 mWeight; + joint_map_iterator mListIter; +public: + // Iterate through jointStates + LLJointState *getFirstJointState(); + LLJointState *getNextJointState(); + LLJointState *findJointState(LLJoint *joint); + LLJointState *findJointState(const std::string &name); +public: + // Constructor + LLPose() : mWeight(0.f) {} + // Destructor + ~LLPose(); + // add a joint state in this pose + BOOL addJointState(LLJointState *jointState); + // remove a joint state from this pose + BOOL removeJointState(LLJointState *jointState); + // removes all joint states from this pose + BOOL removeAllJointStates(); + // set weight for all joint states in this pose + void setWeight(F32 weight); + // get weight for this pose + F32 getWeight() const; + // returns number of joint states stored in this pose + S32 getNumJointStates() const; +}; + +const S32 JSB_NUM_JOINT_STATES = 4; + +class LLJointStateBlender +{ +protected: + LLJointState* mJointStates[JSB_NUM_JOINT_STATES]; + S32 mPriorities[JSB_NUM_JOINT_STATES]; + BOOL mAdditiveBlends[JSB_NUM_JOINT_STATES]; +public: + LLJointStateBlender(); + ~LLJointStateBlender(); + void blendJointStates(BOOL apply_now = TRUE); + BOOL addJointState(LLJointState *joint_state, S32 priority, BOOL additive_blend); + void interpolate(F32 u); + void clear(); + void resetCachedJoint(); + +public: + LLJoint mJointCache; +}; + +class LLMotion; + +class LLPoseBlender +{ +protected: + LLMap<LLJoint*,LLJointStateBlender*> mJointStateBlenderPool; + LLLinkedList<LLJointStateBlender> mActiveBlenders; + + S32 mNextPoseSlot; + LLPose mBlendedPose; +public: + // Constructor + LLPoseBlender(); + // Destructor + ~LLPoseBlender(); + + // request motion joint states to be added to pose blender joint state records + BOOL addMotion(LLMotion* motion); + + // blend all joint states and apply to skeleton + void blendAndApply(); + + // removes all joint state blenders from last time + void clearBlenders(); + + // blend all joint states and cache results + void blendAndCache(BOOL reset_cached_joints); + + // interpolate all joints towards cached values + void interpolate(F32 u); + + LLPose* getBlendedPose() { return &mBlendedPose; } +}; + +#endif // LL_LLPOSE_H + diff --git a/indra/llcharacter/llstatemachine.cpp b/indra/llcharacter/llstatemachine.cpp new file mode 100644 index 0000000000..d51b2f75aa --- /dev/null +++ b/indra/llcharacter/llstatemachine.cpp @@ -0,0 +1,378 @@ +/** + * @file llstatemachine.cpp + * @brief LLStateMachine implementation file. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llstatemachine.h" +#include "llapr.h" + +#define FSM_PRINT_STATE_TRANSITIONS (0) + +U32 LLUniqueID::sNextID = 0; + +bool operator==(const LLUniqueID &a, const LLUniqueID &b) +{ + return (a.mId == b.mId); +} + +bool operator!=(const LLUniqueID &a, const LLUniqueID &b) +{ + return (a.mId != b.mId); +} + +//----------------------------------------------------------------------------- +// LLStateDiagram +//----------------------------------------------------------------------------- +LLStateDiagram::LLStateDiagram() +{ + mUseDefaultState = FALSE; +} + +LLStateDiagram::~LLStateDiagram() +{ + +} + +// add a state to the state graph +BOOL LLStateDiagram::addState(LLFSMState *state) +{ + mStates[state] = Transitions(); + return TRUE; +} + +// add a directed transition between 2 states +BOOL LLStateDiagram::addTransition(LLFSMState& start_state, LLFSMState& end_state, LLFSMTransition& transition) +{ + StateMap::iterator state_it; + state_it = mStates.find(&start_state); + Transitions* state_transitions = NULL; + if (state_it == mStates.end() ) + { + addState(&start_state); + state_transitions = &mStates[&start_state]; + } + else + { + state_transitions = &state_it->second; + } + state_it = mStates.find(&end_state); + if (state_it == mStates.end() ) + { + addState(&end_state); + } + + Transitions::iterator transition_it = state_transitions->find(&transition); + if (transition_it != state_transitions->end()) + { + llerrs << "LLStateTable::addDirectedTransition() : transition already exists" << llendl; + return FALSE; // transition already exists + } + + (*state_transitions)[&transition] = &end_state; + return TRUE; +} + +// add an undirected transition between 2 states +BOOL LLStateDiagram::addUndirectedTransition(LLFSMState& start_state, LLFSMState& end_state, LLFSMTransition& transition) +{ + BOOL result; + result = addTransition(start_state, end_state, transition); + if (result) + { + result = addTransition(end_state, start_state, transition); + } + return result; +} + +// add a transition that exists for every state +void LLStateDiagram::addDefaultTransition(LLFSMState& end_state, LLFSMTransition& transition) +{ + mDefaultTransitions[&transition] = &end_state; +} + +// process a possible transition, and get the resulting state +LLFSMState* LLStateDiagram::processTransition(LLFSMState& start_state, LLFSMTransition& transition) +{ + // look up transition + //LLFSMState** dest_state = (mStates.getValue(&start_state))->getValue(&transition); + LLFSMState* dest_state = NULL; + StateMap::iterator state_it = mStates.find(&start_state); + if (state_it == mStates.end()) + { + return NULL; + } + Transitions::iterator transition_it = state_it->second.find(&transition); + + // try default transitions if state-specific transition not found + if (transition_it == state_it->second.end()) + { + dest_state = mDefaultTransitions[&transition]; + } + else + { + dest_state = transition_it->second; + } + + // if we have a destination state... + if (NULL != dest_state) + { + // ...return it... + return dest_state; + } + // ... otherwise ... + else + { + // ...look for default state... + if (mUseDefaultState) + { + // ...return it if we have it... + return mDefaultState; + } + else + { + // ...or else we're still in the same state. + return &start_state; + } + } +} + +void LLStateDiagram::setDefaultState(LLFSMState& default_state) +{ + mUseDefaultState = TRUE; + mDefaultState = &default_state; +} + +S32 LLStateDiagram::numDeadendStates() +{ + S32 numDeadends = 0; + StateMap::iterator state_it; + for(state_it = mStates.begin(); state_it != mStates.end(); ++state_it) + { + if (state_it->second.size() == 0) + { + numDeadends++; + } + } + return numDeadends; +} + +BOOL LLStateDiagram::stateIsValid(LLFSMState& state) +{ + if (mStates.find(&state) != mStates.end()) + { + return TRUE; + } + return FALSE; +} + +LLFSMState* LLStateDiagram::getState(U32 state_id) +{ + StateMap::iterator state_it; + for(state_it = mStates.begin(); state_it != mStates.end(); ++state_it) + { + if (state_it->first->getID() == state_id) + { + return state_it->first; + } + } + return NULL; +} + +BOOL LLStateDiagram::saveDotFile(const char* filename) +{ + apr_file_t* dot_file = ll_apr_file_open(filename, LL_APR_W); + + if (!dot_file) + { + llwarns << "LLStateDiagram::saveDotFile() : Couldn't open " << filename << " to save state diagram." << llendl; + return FALSE; + } + apr_file_printf(dot_file, "digraph StateMachine {\n\tsize=\"100,100\";\n\tfontsize=40;\n\tlabel=\"Finite State Machine\";\n\torientation=landscape\n\tratio=.77\n"); + + StateMap::iterator state_it; + for(state_it = mStates.begin(); state_it != mStates.end(); ++state_it) + { + apr_file_printf(dot_file, "\t\"%s\" [fontsize=28,shape=box]\n", state_it->first->getName().c_str()); + } + apr_file_printf(dot_file, "\t\"All States\" [fontsize=30,style=bold,shape=box]\n"); + + Transitions::iterator transitions_it; + for(transitions_it = mDefaultTransitions.begin(); transitions_it != mDefaultTransitions.end(); ++transitions_it) + { + apr_file_printf(dot_file, "\t\"All States\" -> \"%s\" [label = \"%s\",fontsize=24];\n", transitions_it->second->getName().c_str(), + transitions_it->second->getName().c_str()); + } + + if (mDefaultState) + { + apr_file_printf(dot_file, "\t\"All States\" -> \"%s\";\n", mDefaultState->getName().c_str()); + } + + + for(state_it = mStates.begin(); state_it != mStates.end(); ++state_it) + { + LLFSMState *state = state_it->first; + + Transitions::iterator transitions_it; + for(transitions_it = state_it->second.begin(); + transitions_it != state_it->second.end(); + ++transitions_it) + { + std::string state_name = state->getName(); + std::string target_name = transitions_it->second->getName(); + std::string transition_name = transitions_it->first->getName(); + apr_file_printf(dot_file, "\t\"%s\" -> \"%s\" [label = \"%s\",fontsize=24];\n", state->getName().c_str(), + target_name.c_str(), + transition_name.c_str()); + } + } + + apr_file_printf(dot_file, "}\n"); + + apr_file_close(dot_file); + + return TRUE; +} + +std::ostream& operator<<(std::ostream &s, LLStateDiagram &FSM) +{ + if (FSM.mDefaultState) + { + s << "Default State: " << FSM.mDefaultState->getName() << "\n"; + } + + LLStateDiagram::Transitions::iterator transitions_it; + for(transitions_it = FSM.mDefaultTransitions.begin(); + transitions_it != FSM.mDefaultTransitions.end(); + ++transitions_it) + { + s << "Any State -- " << transitions_it->first->getName() + << " --> " << transitions_it->second->getName() << "\n"; + } + + LLStateDiagram::StateMap::iterator state_it; + for(state_it = FSM.mStates.begin(); state_it != FSM.mStates.end(); ++state_it) + { + LLStateDiagram::Transitions::iterator transitions_it; + for(transitions_it = state_it->second.begin(); + transitions_it != state_it->second.end(); + ++transitions_it) + { + s << state_it->first->getName() << " -- " << transitions_it->first->getName() + << " --> " << transitions_it->second->getName() << "\n"; + } + s << "\n"; + } + + return s; +} + +//----------------------------------------------------------------------------- +// LLStateMachine +//----------------------------------------------------------------------------- + +LLStateMachine::LLStateMachine() +{ + // we haven't received a starting state yet + mCurrentState = NULL; + mLastState = NULL; + mStateDiagram = NULL; +} + +LLStateMachine::~LLStateMachine() +{ + +} + +// returns current state +LLFSMState* LLStateMachine::getCurrentState() const +{ + return mCurrentState; +} + +// executes current state +void LLStateMachine::runCurrentState(void *data) +{ + mCurrentState->execute(data); +} + +// set current state +BOOL LLStateMachine::setCurrentState(LLFSMState *initial_state, void* user_data, BOOL skip_entry) +{ + llassert(mStateDiagram); + + if (mStateDiagram->stateIsValid(*initial_state)) + { + mLastState = mCurrentState = initial_state; + if (!skip_entry) + { + initial_state->onEntry(user_data); + } + return TRUE; + } + + return FALSE; +} + +BOOL LLStateMachine::setCurrentState(U32 state_id, void* user_data, BOOL skip_entry) +{ + llassert(mStateDiagram); + + LLFSMState* state = mStateDiagram->getState(state_id); + + if (state) + { + mLastState = mCurrentState = state; + if (!skip_entry) + { + state->onEntry(user_data); + } + return TRUE; + } + + return FALSE; +} + +void LLStateMachine::processTransition(LLFSMTransition& transition, void* user_data) +{ + llassert(mStateDiagram); + + LLFSMState* new_state = mStateDiagram->processTransition(*mCurrentState, transition); + + mLastTransition = &transition; + mLastState = mCurrentState; + + if (*mCurrentState != *new_state) + { + if (mCurrentState && new_state && *mCurrentState != *new_state) + { + mCurrentState->onExit(user_data); + } + if (new_state) + { + if (!mCurrentState || *mCurrentState != *new_state) + { + mCurrentState = new_state; + if (mCurrentState) + { + mCurrentState->onEntry(user_data); + } + } + } +#if FSM_PRINT_STATE_TRANSITIONS + llinfos << "Entering state " << mCurrentState->getName() << + " on transition " << transition.getName() << " from state " << + mLastState->getName() << llendl; +#endif + } +} + +void LLStateMachine::setStateDiagram(LLStateDiagram* diagram) +{ + mStateDiagram = diagram; +} diff --git a/indra/llcharacter/llstatemachine.h b/indra/llcharacter/llstatemachine.h new file mode 100644 index 0000000000..837fc57b9b --- /dev/null +++ b/indra/llcharacter/llstatemachine.h @@ -0,0 +1,130 @@ +/** + * @file llstatemachine.h + * @brief LLStateMachine class header file. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLSTATEMACHINE_H +#define LL_LLSTATEMACHINE_H + +#include <string> + +#include "llassoclist.h" +#include "llerror.h" +#include <map> + +class LLUniqueID +{ + friend bool operator==(const LLUniqueID &a, const LLUniqueID &b); + friend bool operator!=(const LLUniqueID &a, const LLUniqueID &b); +protected: + static U32 sNextID; + U32 mId; +public: + LLUniqueID(){mId = sNextID++;} + virtual ~LLUniqueID(){} + U32 getID() {return mId;} +}; + +class LLFSMTransition : public LLUniqueID +{ +public: + LLFSMTransition() : LLUniqueID(){}; + virtual std::string getName(){ return "unnamed"; } +}; + +class LLFSMState : public LLUniqueID +{ +public: + LLFSMState() : LLUniqueID(){}; + virtual void onEntry(void *){}; + virtual void onExit(void *){}; + virtual void execute(void *){}; + virtual std::string getName(){ return "unnamed"; } +}; + +class LLStateDiagram +{ +typedef std::map<LLFSMTransition*, LLFSMState*> Transitions; + +friend std::ostream& operator<<(std::ostream &s, LLStateDiagram &FSM); +friend class LLStateMachine; + +protected: + typedef std::map<LLFSMState*, Transitions> StateMap; + StateMap mStates; + Transitions mDefaultTransitions; + LLFSMState* mDefaultState; + BOOL mUseDefaultState; + +public: + LLStateDiagram(); + virtual ~LLStateDiagram(); + +protected: + // add a state to the state graph, executed implicitly when adding transitions + BOOL addState(LLFSMState *state); + + // add a directed transition between 2 states + BOOL addTransition(LLFSMState& start_state, LLFSMState& end_state, LLFSMTransition& transition); + + // add an undirected transition between 2 states + BOOL addUndirectedTransition(LLFSMState& start_state, LLFSMState& end_state, LLFSMTransition& transition); + + // add a transition that is taken if none other exist + void addDefaultTransition(LLFSMState& end_state, LLFSMTransition& transition); + + // process a possible transition, and get the resulting state + LLFSMState* processTransition(LLFSMState& start_state, LLFSMTransition& transition); + + // add a transition that exists for every state + void setDefaultState(LLFSMState& default_state); + + // return total number of states with no outgoing transitions + S32 numDeadendStates(); + + // does this state exist in the state diagram? + BOOL stateIsValid(LLFSMState& state); + + // get a state pointer by ID + LLFSMState* getState(U32 state_id); + +public: + // save the graph in a DOT file for rendering and visualization + BOOL saveDotFile(const char* filename); +}; + +class LLStateMachine +{ +protected: + LLFSMState* mCurrentState; + LLFSMState* mLastState; + LLFSMTransition* mLastTransition; + LLStateDiagram* mStateDiagram; + +public: + LLStateMachine(); + virtual ~LLStateMachine(); + + // set state diagram + void setStateDiagram(LLStateDiagram* diagram); + + // process this transition + void processTransition(LLFSMTransition &transition, void* user_data); + + // returns current state + LLFSMState* getCurrentState() const; + + // execute current state + void runCurrentState(void *data); + + // set state by state pointer + BOOL setCurrentState(LLFSMState *initial_state, void* user_data, BOOL skip_entry = TRUE); + + // set state by unique ID + BOOL setCurrentState(U32 state_id, void* user_data, BOOL skip_entry = TRUE); +}; + +#endif //_LL_LLSTATEMACHINE_H diff --git a/indra/llcharacter/lltargetingmotion.cpp b/indra/llcharacter/lltargetingmotion.cpp new file mode 100644 index 0000000000..0f3eaa855b --- /dev/null +++ b/indra/llcharacter/lltargetingmotion.cpp @@ -0,0 +1,151 @@ +/** + * @file lltargetingmotion.cpp + * @brief Implementation of LLTargetingMotion class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +//----------------------------------------------------------------------------- +// Header Files +//----------------------------------------------------------------------------- +#include "linden_common.h" + +#include "lltargetingmotion.h" +#include "llcharacter.h" +#include "v3dmath.h" +#include "llcriticaldamp.h" + +//----------------------------------------------------------------------------- +// Constants +//----------------------------------------------------------------------------- +const F32 TORSO_TARGET_HALF_LIFE = 0.25f; +const F32 MAX_TIME_DELTA = 2.f; //max two seconds a frame for calculating interpolation +const F32 TARGET_PLANE_THRESHOLD_DOT = 0.6f; +const F32 TORSO_ROT_FRACTION = 0.5f; + +//----------------------------------------------------------------------------- +// LLTargetingMotion() +// Class Constructor +//----------------------------------------------------------------------------- +LLTargetingMotion::LLTargetingMotion(const LLUUID &id) : LLMotion(id) +{ + mCharacter = NULL; + mName = "targeting"; +} + + +//----------------------------------------------------------------------------- +// ~LLTargetingMotion() +// Class Destructor +//----------------------------------------------------------------------------- +LLTargetingMotion::~LLTargetingMotion() +{ +} + +//----------------------------------------------------------------------------- +// LLTargetingMotion::onInitialize(LLCharacter *character) +//----------------------------------------------------------------------------- +LLMotion::LLMotionInitStatus LLTargetingMotion::onInitialize(LLCharacter *character) +{ + // save character for future use + mCharacter = character; + + mPelvisJoint = mCharacter->getJoint("mPelvis"); + mTorsoJoint = mCharacter->getJoint("mTorso"); + mRightHandJoint = mCharacter->getJoint("mWristRight"); + + // make sure character skeleton is copacetic + if (!mPelvisJoint || + !mTorsoJoint || + !mRightHandJoint) + { + llwarns << "Invalid skeleton for targeting motion!" << llendl; + return STATUS_FAILURE; + } + + mTorsoState.setJoint( mTorsoJoint ); + + // add joint states to the pose + mTorsoState.setUsage(LLJointState::ROT); + addJointState( &mTorsoState ); + + return STATUS_SUCCESS; +} + +//----------------------------------------------------------------------------- +// LLTargetingMotion::onActivate() +//----------------------------------------------------------------------------- +BOOL LLTargetingMotion::onActivate() +{ + return TRUE; +} + +//----------------------------------------------------------------------------- +// LLTargetingMotion::onUpdate() +//----------------------------------------------------------------------------- +BOOL LLTargetingMotion::onUpdate(F32 time, U8* joint_mask) +{ + F32 slerp_amt = LLCriticalDamp::getInterpolant(TORSO_TARGET_HALF_LIFE); + + LLVector3 target; + LLVector3* lookAtPoint = (LLVector3*)mCharacter->getAnimationData("LookAtPoint"); + + BOOL result = TRUE; + + if (!lookAtPoint) + { + return TRUE; + } + else + { + target = *lookAtPoint; + target.normVec(); + } + + //LLVector3 target_plane_normal = LLVector3(1.f, 0.f, 0.f) * mPelvisJoint->getWorldRotation(); + //LLVector3 torso_dir = LLVector3(1.f, 0.f, 0.f) * (mTorsoJoint->getWorldRotation() * mTorsoState.getRotation()); + + LLVector3 skyward(0.f, 0.f, 1.f); + LLVector3 left(skyward % target); + left.normVec(); + LLVector3 up(target % left); + up.normVec(); + LLQuaternion target_aim_rot(target, left, up); + + LLQuaternion cur_torso_rot = mTorsoJoint->getWorldRotation(); + + LLVector3 right_hand_at = LLVector3(0.f, -1.f, 0.f) * mRightHandJoint->getWorldRotation(); + left.setVec(skyward % right_hand_at); + left.normVec(); + up.setVec(right_hand_at % left); + up.normVec(); + LLQuaternion right_hand_rot(right_hand_at, left, up); + + LLQuaternion new_torso_rot = (cur_torso_rot * ~right_hand_rot) * target_aim_rot; + + // find ideal additive rotation to make torso point in correct direction + new_torso_rot = new_torso_rot * ~cur_torso_rot; + + // slerp from current additive rotation to ideal additive rotation + new_torso_rot = nlerp(slerp_amt, mTorsoState.getRotation(), new_torso_rot); + + // constraint overall torso rotation + LLQuaternion total_rot = new_torso_rot * mTorsoJoint->getRotation(); + total_rot.constrain(F_PI_BY_TWO * 0.8f); + new_torso_rot = total_rot * ~mTorsoJoint->getRotation(); + + mTorsoState.setRotation(new_torso_rot); + + return result; +} + +//----------------------------------------------------------------------------- +// LLTargetingMotion::onDeactivate() +//----------------------------------------------------------------------------- +void LLTargetingMotion::onDeactivate() +{ +} + + +// End diff --git a/indra/llcharacter/lltargetingmotion.h b/indra/llcharacter/lltargetingmotion.h new file mode 100644 index 0000000000..708dd374a2 --- /dev/null +++ b/indra/llcharacter/lltargetingmotion.h @@ -0,0 +1,98 @@ +/** + * @file lltargetingmotion.h + * @brief Implementation of LLTargetingMotion class. + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLTARGETINGMOTION_H +#define LL_LLTARGETINGMOTION_H + +//----------------------------------------------------------------------------- +// Header files +//----------------------------------------------------------------------------- +#include "llmotion.h" + +#define TARGETING_EASEIN_DURATION 0.3f +#define TARGETING_EASEOUT_DURATION 0.5f +#define TARGETING_PRIORITY LLJoint::HIGH_PRIORITY +#define MIN_REQUIRED_PIXEL_AREA_TARGETING 1000.f; + + +//----------------------------------------------------------------------------- +// class LLTargetingMotion +//----------------------------------------------------------------------------- +class LLTargetingMotion : + public LLMotion +{ +public: + // Constructor + LLTargetingMotion(const LLUUID &id); + + // Destructor + virtual ~LLTargetingMotion(); + +public: + //------------------------------------------------------------------------- + // functions to support MotionController and MotionRegistry + //------------------------------------------------------------------------- + + // static constructor + // all subclasses must implement such a function and register it + static LLMotion *create(const LLUUID &id) { return new LLTargetingMotion(id); } + +public: + //------------------------------------------------------------------------- + // animation callbacks to be implemented by subclasses + //------------------------------------------------------------------------- + + // motions must specify whether or not they loop + virtual BOOL getLoop() { return TRUE; } + + // motions must report their total duration + virtual F32 getDuration() { return 0.0; } + + // motions must report their "ease in" duration + virtual F32 getEaseInDuration() { return TARGETING_EASEIN_DURATION; } + + // motions must report their "ease out" duration. + virtual F32 getEaseOutDuration() { return TARGETING_EASEOUT_DURATION; } + + // motions must report their priority + virtual LLJoint::JointPriority getPriority() { return TARGETING_PRIORITY; } + + virtual LLMotionBlendType getBlendType() { return ADDITIVE_BLEND; } + + // called to determine when a motion should be activated/deactivated based on avatar pixel coverage + virtual F32 getMinPixelArea() { return MIN_REQUIRED_PIXEL_AREA_TARGETING; } + + // run-time (post constructor) initialization, + // called after parameters have been set + // must return true to indicate success and be available for activation + virtual LLMotionInitStatus onInitialize(LLCharacter *character); + + // called when a motion is activated + // must return TRUE to indicate success, or else + // it will be deactivated + virtual BOOL onActivate(); + + // called per time step + // must return TRUE while it is active, and + // must return FALSE when the motion is completed. + virtual BOOL onUpdate(F32 time, U8* joint_mask); + + // called when a motion is deactivated + virtual void onDeactivate(); + +public: + + LLCharacter *mCharacter; + LLJointState mTorsoState; + LLJoint* mPelvisJoint; + LLJoint* mTorsoJoint; + LLJoint* mRightHandJoint; +}; + +#endif // LL_LLTARGETINGMOTION_H + diff --git a/indra/llcharacter/llvisualparam.cpp b/indra/llcharacter/llvisualparam.cpp new file mode 100644 index 0000000000..a6fd9b974f --- /dev/null +++ b/indra/llcharacter/llvisualparam.cpp @@ -0,0 +1,266 @@ +/** + * @file llvisualparam.cpp + * @brief Implementation of LLPolyMesh class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +//----------------------------------------------------------------------------- +// Header Files +//----------------------------------------------------------------------------- +#include "linden_common.h" + +#include "llvisualparam.h" + +//----------------------------------------------------------------------------- +// LLVisualParamInfo() +//----------------------------------------------------------------------------- +LLVisualParamInfo::LLVisualParamInfo() + : + mID( -1 ), + mGroup( VISUAL_PARAM_GROUP_TWEAKABLE ), + mMinWeight( 0.f ), + mMaxWeight( 1.f ), + mDefaultWeight( 0.f ), + mSex( SEX_BOTH ) +{ +} + +//----------------------------------------------------------------------------- +// parseXml() +//----------------------------------------------------------------------------- +BOOL LLVisualParamInfo::parseXml(LLXmlTreeNode *node) +{ + // attribute: id + static LLStdStringHandle id_string = LLXmlTree::addAttributeString("id"); + node->getFastAttributeS32( id_string, mID ); + + // attribute: group + U32 group = 0; + static LLStdStringHandle group_string = LLXmlTree::addAttributeString("group"); + if( node->getFastAttributeU32( group_string, group ) ) + { + if( group < NUM_VISUAL_PARAM_GROUPS ) + { + mGroup = (EVisualParamGroup)group; + } + } + + // attribute: value_min, value_max + static LLStdStringHandle value_min_string = LLXmlTree::addAttributeString("value_min"); + static LLStdStringHandle value_max_string = LLXmlTree::addAttributeString("value_max"); + node->getFastAttributeF32( value_min_string, mMinWeight ); + node->getFastAttributeF32( value_max_string, mMaxWeight ); + + // attribute: value_default + F32 default_weight = 0; + static LLStdStringHandle value_default_string = LLXmlTree::addAttributeString("value_default"); + if( node->getFastAttributeF32( value_default_string, default_weight ) ) + { + mDefaultWeight = llclamp( default_weight, mMinWeight, mMaxWeight ); + if( default_weight != mDefaultWeight ) + { + llwarns << "value_default attribute is out of range in node " << mName << " " << default_weight << llendl; + } + } + + // attribute: sex + LLString sex = "both"; + static LLStdStringHandle sex_string = LLXmlTree::addAttributeString("sex"); + node->getFastAttributeString( sex_string, sex ); // optional + if( sex == "both" ) + { + mSex = SEX_BOTH; + } + else if( sex == "male" ) + { + mSex = SEX_MALE; + } + else if( sex == "female" ) + { + mSex = SEX_FEMALE; + } + else + { + llwarns << "Avatar file: <param> has invalid sex attribute: " << sex << llendl; + return FALSE; + } + + // attribute: name + static LLStdStringHandle name_string = LLXmlTree::addAttributeString("name"); + if( !node->getFastAttributeString( name_string, mName ) ) + { + llwarns << "Avatar file: <param> is missing name attribute" << llendl; + return FALSE; + } + + // attribute: label + static LLStdStringHandle label_string = LLXmlTree::addAttributeString("label"); + if( !node->getFastAttributeString( label_string, mDisplayName ) ) + { + mDisplayName = mName; + } + + // JC - make sure the display name includes the capitalization in the XML file, + // not the lowercased version. + LLString::toLower(mName); + + // attribute: label_min + static LLStdStringHandle label_min_string = LLXmlTree::addAttributeString("label_min"); + if( !node->getFastAttributeString( label_min_string, mMinName ) ) + { + mMinName = "Less"; + } + + // attribute: label_max + static LLStdStringHandle label_max_string = LLXmlTree::addAttributeString("label_max"); + if( !node->getFastAttributeString( label_max_string, mMaxName ) ) + { + mMaxName = "More"; + } + + return TRUE; +} + +//----------------------------------------------------------------------------- +// LLVisualParam() +//----------------------------------------------------------------------------- +LLVisualParam::LLVisualParam() + : + mCurWeight( 0.f ), + mLastWeight( 0.f ), + mNext( NULL ), + mTargetWeight( 0.f ), + mIsAnimating( FALSE ), + mID( -1 ), + mInfo( 0 ) +{ +} + +//----------------------------------------------------------------------------- +// ~LLVisualParam() +//----------------------------------------------------------------------------- +LLVisualParam::~LLVisualParam() +{ + delete mNext; +} + +/* +//============================================================================= +// These virtual functions should always be overridden, +// but are included here for use as templates +//============================================================================= + +//----------------------------------------------------------------------------- +// setInfo() +//----------------------------------------------------------------------------- + +BOOL LLVisualParam::setInfo(LLVisualParamInfo *info) +{ + llassert(mInfo == NULL); + if (info->mID < 0) + return FALSE; + mInfo = info; + mID = info->mID; + setWeight(getDefaultWeight(), FALSE ); + return TRUE; +} + +//----------------------------------------------------------------------------- +// parseData() +//----------------------------------------------------------------------------- +BOOL LLVisualParam::parseData(LLXmlTreeNode *node) +{ + LLVisualParamInfo *info = new LLVisualParamInfo; + + info->parseXml(node); + if (!setInfo(info)) + return FALSE; + + return TRUE; +} +*/ + +//----------------------------------------------------------------------------- +// setWeight() +//----------------------------------------------------------------------------- +void LLVisualParam::setWeight(F32 weight, BOOL set_by_user) +{ + if (mIsAnimating) + { + //RN: allow overshoot + mCurWeight = weight; + } + else if (mInfo) + { + mCurWeight = llclamp(weight, mInfo->mMinWeight, mInfo->mMaxWeight); + } + else + { + mCurWeight = weight; + } + + if (mNext) + { + mNext->setWeight(weight, set_by_user); + } +} + +//----------------------------------------------------------------------------- +// setAnimationTarget() +//----------------------------------------------------------------------------- +void LLVisualParam::setAnimationTarget(F32 target_value, BOOL set_by_user) +{ + if (getGroup() == VISUAL_PARAM_GROUP_TWEAKABLE) + { + if (mInfo) + { + mTargetWeight = llclamp(target_value, mInfo->mMinWeight, mInfo->mMaxWeight); + } + else + { + mTargetWeight = target_value; + } + } + mIsAnimating = TRUE; + + if (mNext) + { + mNext->setAnimationTarget(target_value, set_by_user); + } +} + +//----------------------------------------------------------------------------- +// setNextParam() +//----------------------------------------------------------------------------- +void LLVisualParam::setNextParam( LLVisualParam *next ) +{ + llassert(!mNext); + + mNext = next; +} + +//----------------------------------------------------------------------------- +// animate() +//----------------------------------------------------------------------------- +void LLVisualParam::animate( F32 delta, BOOL set_by_user ) +{ + if (mIsAnimating) + { + F32 new_weight = ((mTargetWeight - mCurWeight) * delta) + mCurWeight; + setWeight(new_weight, set_by_user); + } +} + +//----------------------------------------------------------------------------- +// stopAnimating() +//----------------------------------------------------------------------------- +void LLVisualParam::stopAnimating(BOOL set_by_user) +{ + if (mIsAnimating && getGroup() == VISUAL_PARAM_GROUP_TWEAKABLE) + { + mIsAnimating = FALSE; + setWeight(mTargetWeight, set_by_user); + } +} diff --git a/indra/llcharacter/llvisualparam.h b/indra/llcharacter/llvisualparam.h new file mode 100644 index 0000000000..2a8d03d431 --- /dev/null +++ b/indra/llcharacter/llvisualparam.h @@ -0,0 +1,131 @@ +/** + * @file llvisualparam.h + * @brief Implementation of LLPolyMesh class. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLVisualParam_H +#define LL_LLVisualParam_H + +#include "v3math.h" +#include "llstring.h" +#include "llxmltree.h" + +class LLPolyMesh; +class LLXmlTreeNode; + +enum ESex +{ + SEX_FEMALE = 0x01, + SEX_MALE = 0x02, + SEX_BOTH = 0x03 // values chosen to allow use as a bit field. +}; + +enum EVisualParamGroup +{ + VISUAL_PARAM_GROUP_TWEAKABLE, + VISUAL_PARAM_GROUP_ANIMATABLE, + NUM_VISUAL_PARAM_GROUPS +}; + +const S32 MAX_TRANSMITTED_VISUAL_PARAMS = 255; + +//----------------------------------------------------------------------------- +// LLVisualParamInfo +// Contains shared data for VisualParams +//----------------------------------------------------------------------------- +class LLVisualParamInfo +{ + friend class LLVisualParam; +public: + LLVisualParamInfo(); + virtual ~LLVisualParamInfo() {}; + + virtual BOOL parseXml(LLXmlTreeNode *node); + +protected: + S32 mID; // ID associated with VisualParam + + LLString mName; // name (for internal purposes) + LLString mDisplayName; // name displayed to the user + LLString mMinName; // name associated with minimum value + LLString mMaxName; // name associated with maximum value + EVisualParamGroup mGroup; // morph group for separating UI controls + F32 mMinWeight; // minimum weight that can be assigned to this morph target + F32 mMaxWeight; // maximum weight that can be assigned to this morph target + F32 mDefaultWeight; + ESex mSex; // Which gender(s) this param applies to. +}; + +//----------------------------------------------------------------------------- +// LLVisualParam +// VIRTUAL CLASS +// An interface class for a generalized parametric modification of the avatar mesh +// Contains data that is specific to each Avatar +//----------------------------------------------------------------------------- +class LLVisualParam +{ +public: + LLVisualParam(); + virtual ~LLVisualParam(); + + // Special: These functions are overridden by child classes + // (They can not be virtual because they use specific derived Info classes) + LLVisualParamInfo* getInfo() const { return mInfo; } + // This sets mInfo and calls initialization functions + BOOL setInfo(LLVisualParamInfo *info); + + // Virtual functions + // Pure virtuals + //virtual BOOL parseData( LLXmlTreeNode *node ) = 0; + virtual void apply( ESex avatar_sex ) = 0; + // Default functions + virtual void setWeight(F32 weight, BOOL set_by_user); + virtual void setAnimationTarget( F32 target_value, BOOL set_by_user ); + virtual void animate(F32 delta, BOOL set_by_user); + virtual void stopAnimating(BOOL set_by_user); + + // Interface methods + const S32 getID() { return mID; } + void setID(S32 id) { llassert(!mInfo); mID = id; } + + const LLString& getName() const { return mInfo->mName; } + const LLString& getDisplayName() const { return mInfo->mDisplayName; } + const LLString& getMaxDisplayName() const { return mInfo->mMaxName; } + const LLString& getMinDisplayName() const { return mInfo->mMinName; } + + void setDisplayName(const char* s) { mInfo->mDisplayName = s; } + void setMaxDisplayName(const char* s) { mInfo->mMaxName = s; } + void setMinDisplayName(const char* s) { mInfo->mMinName = s; } + + const EVisualParamGroup getGroup() { return mInfo->mGroup; } + F32 getMinWeight() { return mInfo->mMinWeight; } + F32 getMaxWeight() { return mInfo->mMaxWeight; } + F32 getDefaultWeight() { return mInfo->mDefaultWeight; } + ESex getSex() { return mInfo->mSex; } + + F32 getWeight() { return mIsAnimating ? mTargetWeight : mCurWeight; } + F32 getCurrentWeight() { return mCurWeight; } + F32 getLastWeight() { return mLastWeight; } + BOOL isAnimating() { return mIsAnimating; } + + LLVisualParam* getNextParam() { return mNext; } + void setNextParam( LLVisualParam *next ); + + virtual void setAnimating(BOOL is_animating) { mIsAnimating = is_animating; } + BOOL getAnimating() { return mIsAnimating; } + +protected: + F32 mCurWeight; // current weight + F32 mLastWeight; // last weight + LLVisualParam* mNext; // next param in a shared chain + F32 mTargetWeight; // interpolation target + BOOL mIsAnimating; // this value has been given an interpolation target + + S32 mID; // id for storing weight/morphtarget compares compactly + LLVisualParamInfo *mInfo; +}; + +#endif // LL_LLVisualParam_H |