11261 lines
341 KiB
C++
11261 lines
341 KiB
C++
/**
|
|
* @File llvoavatar.cpp
|
|
* @brief Implementation of LLVOAvatar class which is a derivation of LLViewerObject
|
|
*
|
|
* $LicenseInfo:firstyear=2001&license=viewerlgpl$
|
|
* Second Life Viewer Source Code
|
|
* Copyright (C) 2010, Linden Research, Inc.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation;
|
|
* version 2.1 of the License only.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*
|
|
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
|
|
* $/LicenseInfo$
|
|
*/
|
|
|
|
#if LL_MSVC
|
|
// disable warning about boost::lexical_cast returning uninitialized data
|
|
// when it fails to parse the string
|
|
#pragma warning (disable:4701)
|
|
#endif
|
|
|
|
#include "llviewerprecompiledheaders.h"
|
|
|
|
#include "llvoavatar.h"
|
|
|
|
#include <stdio.h>
|
|
#include <ctype.h>
|
|
|
|
#include "llaudioengine.h"
|
|
#include "llperlin.h"
|
|
#include "raytrace.h"
|
|
|
|
#include "llagent.h" // Get state values from here
|
|
#include "llagentcamera.h"
|
|
#include "llagentwearables.h"
|
|
#include "llanimationstates.h"
|
|
#include "llavatarnamecache.h"
|
|
#include "llavatarpropertiesprocessor.h"
|
|
#include "llcontrolavatar.h"
|
|
#include "llphysicsmotion.h"
|
|
#include "llviewercontrol.h"
|
|
#include "lldrawpoolavatar.h"
|
|
#include "lldriverparam.h"
|
|
#include "llpolyskeletaldistortion.h"
|
|
#include "lleditingmotion.h"
|
|
#include "llemote.h"
|
|
#include "llfirstuse.h"
|
|
#include "llfloaterchat.h"
|
|
#include "llfloaterinventory.h"
|
|
#include "llheadrotmotion.h"
|
|
#include "llhudeffecttrail.h"
|
|
#include "llhudmanager.h"
|
|
#include "llinventorybridge.h"
|
|
#include "llinventoryfunctions.h"
|
|
#include "llhudnametag.h"
|
|
#include "llhudtext.h" // for mText/mDebugText
|
|
#include "llkeyframefallmotion.h"
|
|
#include "llkeyframestandmotion.h"
|
|
#include "llkeyframewalkmotion.h"
|
|
#include "llmanipscale.h" // for get_default_max_prim_scale()
|
|
#include "llmeshrepository.h"
|
|
#include "llmutelist.h"
|
|
#include "llnotificationsutil.h"
|
|
#include "llquantize.h"
|
|
#include "llrand.h"
|
|
#include "llregionhandle.h"
|
|
#include "llresmgr.h"
|
|
#include "llselectmgr.h"
|
|
#include "llsprite.h"
|
|
#include "lltargetingmotion.h"
|
|
#include "lltoolmorph.h"
|
|
#include "llviewercamera.h"
|
|
#include "llviewergenericmessage.h" //for Auto Deruth
|
|
#include "llviewercontrol.h"
|
|
#include "llviewertexlayer.h"
|
|
#include "llviewertexturelist.h"
|
|
#include "llviewermedia.h"
|
|
#include "llviewermenu.h"
|
|
#include "llviewerobjectlist.h"
|
|
#include "llviewerparcelmgr.h"
|
|
#include "llviewerregion.h"
|
|
#include "llviewershadermgr.h"
|
|
#include "llviewerstats.h"
|
|
#include "llviewerwearable.h"
|
|
#include "llvoavatarself.h"
|
|
#include "llvovolume.h"
|
|
#include "llworld.h"
|
|
#include "pipeline.h"
|
|
#include "llviewershadermgr.h"
|
|
#include "llsky.h"
|
|
#include "llanimstatelabels.h"
|
|
#include "lltrans.h"
|
|
#include "llappearancemgr.h"
|
|
|
|
#include "llgesturemgr.h" //needed to trigger the voice gesticulations
|
|
#include "llvoiceclient.h"
|
|
#include "llvoicevisualizer.h" // Ventrella
|
|
|
|
#include "llsdserialize.h" //For the client definitions
|
|
#include "llcachename.h"
|
|
#include "floaterao.h"
|
|
#include "llsdutil.h"
|
|
|
|
#include "llskinningutil.h"
|
|
|
|
#include "llfloaterexploreanimations.h"
|
|
#include "aixmllindengenepool.h"
|
|
|
|
#include "llavatarname.h"
|
|
#include "../lscript/lscript_byteformat.h"
|
|
|
|
#include "hippogridmanager.h"
|
|
|
|
#include "llrendersphere.h"
|
|
|
|
// [RLVa:KB] - Checked: 2010-04-01 (RLVa-1.2.0c)
|
|
#include "rlvhandler.h"
|
|
// [/RLVa:KB]
|
|
|
|
#if LL_MSVC
|
|
// disable boost::lexical_cast warning
|
|
#pragma warning (disable:4702)
|
|
#endif
|
|
|
|
#include <boost/algorithm/string/replace.hpp>
|
|
#include <boost/lexical_cast.hpp>
|
|
|
|
#if LL_DARWIN
|
|
size_t strnlen(const char *s, size_t n)
|
|
{
|
|
const char *p = (const char *)memchr(s, 0, n);
|
|
return(p ? p-s : n);
|
|
}
|
|
#endif
|
|
|
|
extern U32 JOINT_COUNT_REQUIRED_FOR_FULLRIG;
|
|
|
|
const F32 MAX_HOVER_Z = 2.0;
|
|
const F32 MIN_HOVER_Z = -2.0;
|
|
|
|
const F32 MIN_ATTACHMENT_COMPLEXITY = 0.f;
|
|
const F32 DEFAULT_MAX_ATTACHMENT_COMPLEXITY = 1.0e6f;
|
|
|
|
using namespace LLAvatarAppearanceDefines;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Global constants
|
|
//-----------------------------------------------------------------------------
|
|
const LLUUID ANIM_AGENT_BODY_NOISE_ID = LLUUID("9aa8b0a6-0c6f-9518-c7c3-4f41f2c001ad"); //"body_noise"
|
|
const LLUUID ANIM_AGENT_BREATHE_ROT_ID = LLUUID("4c5a103e-b830-2f1c-16bc-224aa0ad5bc8"); //"breathe_rot"
|
|
const LLUUID ANIM_AGENT_EDITING_ID = LLUUID("2a8eba1d-a7f8-5596-d44a-b4977bf8c8bb"); //"editing"
|
|
const LLUUID ANIM_AGENT_EYE_ID = LLUUID("5c780ea8-1cd1-c463-a128-48c023f6fbea"); //"eye"
|
|
const LLUUID ANIM_AGENT_FLY_ADJUST_ID = LLUUID("db95561f-f1b0-9f9a-7224-b12f71af126e"); //"fly_adjust"
|
|
const LLUUID ANIM_AGENT_HAND_MOTION_ID = LLUUID("ce986325-0ba7-6e6e-cc24-b17c4b795578"); //"hand_motion"
|
|
const LLUUID ANIM_AGENT_HEAD_ROT_ID = LLUUID("e6e8d1dd-e643-fff7-b238-c6b4b056a68d"); //"head_rot"
|
|
const LLUUID ANIM_AGENT_PELVIS_FIX_ID = LLUUID("0c5dd2a2-514d-8893-d44d-05beffad208b"); //"pelvis_fix"
|
|
const LLUUID ANIM_AGENT_TARGET_ID = LLUUID("0e4896cb-fba4-926c-f355-8720189d5b55"); //"target"
|
|
const LLUUID ANIM_AGENT_WALK_ADJUST_ID = LLUUID("829bc85b-02fc-ec41-be2e-74cc6dd7215d"); //"walk_adjust"
|
|
const LLUUID ANIM_AGENT_PHYSICS_MOTION_ID = LLUUID("7360e029-3cb8-ebc4-863e-212df440d987"); //"physics_motion"
|
|
|
|
//<singu>
|
|
// This must be in the same order as ANIM_AGENT_BODY_NOISE through ANIM_AGENT_WALK_ADJUST (see llmotion.h)!
|
|
static LLUUID const* lookup[] = {
|
|
&ANIM_AGENT_BODY_NOISE_ID,
|
|
&ANIM_AGENT_BREATHE_ROT_ID,
|
|
&ANIM_AGENT_PHYSICS_MOTION_ID,
|
|
&ANIM_AGENT_EDITING_ID,
|
|
&ANIM_AGENT_EYE_ID,
|
|
&ANIM_AGENT_FLY_ADJUST_ID,
|
|
&ANIM_AGENT_HAND_MOTION_ID,
|
|
&ANIM_AGENT_HEAD_ROT_ID,
|
|
&ANIM_AGENT_PELVIS_FIX_ID,
|
|
&ANIM_AGENT_TARGET_ID,
|
|
&ANIM_AGENT_WALK_ADJUST_ID
|
|
};
|
|
|
|
LLUUID const& mask2ID(U32 bit)
|
|
{
|
|
int const lookupsize = sizeof(lookup) / sizeof(LLUUID const*);
|
|
int i = lookupsize - 1;
|
|
U32 mask = 1 << i;
|
|
for(;;)
|
|
{
|
|
if (bit == mask)
|
|
{
|
|
return *lookup[i];
|
|
}
|
|
--i;
|
|
mask >>= 1;
|
|
llassert_always(i >= 0);
|
|
}
|
|
}
|
|
|
|
#ifdef CWDEBUG
|
|
static char const* strlookup[] = {
|
|
"ANIM_AGENT_BODY_NOISE",
|
|
"ANIM_AGENT_BREATHE_ROT",
|
|
"ANIM_AGENT_PHYSICS_MOTION",
|
|
"ANIM_AGENT_EDITING",
|
|
"ANIM_AGENT_EYE",
|
|
"ANIM_AGENT_FLY_ADJUST",
|
|
"ANIM_AGENT_HAND_MOTION",
|
|
"ANIM_AGENT_HEAD_ROT",
|
|
"ANIM_AGENT_PELVIS_FIX",
|
|
"ANIM_AGENT_TARGET",
|
|
"ANIM_AGENT_WALK_ADJUST"
|
|
};
|
|
|
|
char const* mask2str(U32 bit)
|
|
{
|
|
int const lookupsize = sizeof(lookup) / sizeof(LLUUID const*);
|
|
int i = lookupsize - 1;
|
|
U32 mask = 1 << i;
|
|
do
|
|
{
|
|
if (bit == mask)
|
|
{
|
|
return strlookup[i];
|
|
}
|
|
--i;
|
|
mask >>= 1;
|
|
}
|
|
while(i >= 0);
|
|
return "<unknown>";
|
|
}
|
|
#endif
|
|
|
|
// stopMotion(ANIM_AGENT_WALK_ADJUST) is called every frame, and for every avatar on the radar.
|
|
// That can be like 1000 times per second, so... speed that up a bit and lets not lookup the same LLUUID 1000 times
|
|
// per second in a std::map. Added the rest of the animations while I was at it.
|
|
void LLVOAvatar::startMotion(U32 bit, F32 time_offset)
|
|
{
|
|
if (!isMotionActive(bit))
|
|
{
|
|
mMotionController.disable_syncing(); // Don't attempt to synchronize AIMaskedMotion.
|
|
startMotion(mask2ID(bit), time_offset);
|
|
mMotionController.enable_syncing();
|
|
}
|
|
}
|
|
|
|
void LLVOAvatar::stopMotion(U32 bit, BOOL stop_immediate)
|
|
{
|
|
if (isMotionActive(bit))
|
|
{
|
|
stopMotion(mask2ID(bit), stop_immediate);
|
|
}
|
|
}
|
|
//</singu>
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Constants
|
|
//-----------------------------------------------------------------------------
|
|
|
|
const S32 MIN_PIXEL_AREA_FOR_COMPOSITE = 1024;
|
|
const F32 SHADOW_OFFSET_AMT = 0.03f;
|
|
|
|
const F32 DELTA_TIME_MIN = 0.01f; // we clamp measured deltaTime to this
|
|
const F32 DELTA_TIME_MAX = 0.2f; // range to insure stability of computations.
|
|
|
|
const F32 PELVIS_LAG_FLYING = 0.22f;// pelvis follow half life while flying
|
|
const F32 PELVIS_LAG_WALKING = 0.4f; // ...while walking
|
|
const F32 PELVIS_LAG_MOUSELOOK = 0.15f;
|
|
const F32 MOUSELOOK_PELVIS_FOLLOW_FACTOR = 0.5f;
|
|
const F32 PELVIS_LAG_WHEN_FOLLOW_CAM_IS_ON = 0.0001f; // not zero! - something gets divided by this!
|
|
|
|
const F32 PELVIS_ROT_THRESHOLD_SLOW = 60.0f; // amount of deviation allowed between
|
|
const F32 PELVIS_ROT_THRESHOLD_FAST = 2.0f; // the pelvis and the view direction
|
|
// when moving fast & slow
|
|
const F32 TORSO_NOISE_AMOUNT = 1.0f; // Amount of deviation from up-axis, in degrees
|
|
const F32 TORSO_NOISE_SPEED = 0.2f; // Time scale factor on torso noise.
|
|
|
|
const F32 BREATHE_ROT_MOTION_STRENGTH = 0.05f;
|
|
const F32 BREATHE_SCALE_MOTION_STRENGTH = 0.005f;
|
|
|
|
const F32 MIN_SHADOW_HEIGHT = 0.f;
|
|
const F32 MAX_SHADOW_HEIGHT = 0.3f;
|
|
|
|
const S32 MIN_REQUIRED_PIXEL_AREA_BODY_NOISE = 10000;
|
|
const S32 MIN_REQUIRED_PIXEL_AREA_BREATHE = 10000;
|
|
const S32 MIN_REQUIRED_PIXEL_AREA_PELVIS_FIX = 40;
|
|
|
|
const S32 TEX_IMAGE_SIZE_SELF = 512;
|
|
const S32 TEX_IMAGE_AREA_SELF = TEX_IMAGE_SIZE_SELF * TEX_IMAGE_SIZE_SELF;
|
|
const S32 TEX_IMAGE_SIZE_OTHER = 512 / 4; // The size of local textures for other (!isSelf()) avatars
|
|
|
|
const F32 HEAD_MOVEMENT_AVG_TIME = 0.9f;
|
|
|
|
const S32 MORPH_MASK_REQUESTED_DISCARD = 0;
|
|
|
|
const F32 MAX_STANDOFF_FROM_ORIGIN = 3;
|
|
const F32 MAX_STANDOFF_DISTANCE_CHANGE = 32;
|
|
|
|
// Discard level at which to switch to baked textures
|
|
// Should probably be 4 or 3, but didn't want to change it while change other logic - SJB
|
|
const S32 SWITCH_TO_BAKED_DISCARD = 5;
|
|
|
|
const F32 FOOT_COLLIDE_FUDGE = 0.04f;
|
|
|
|
const F32 HOVER_EFFECT_MAX_SPEED = 3.f;
|
|
const F32 HOVER_EFFECT_STRENGTH = 0.f;
|
|
const F32 UNDERWATER_EFFECT_STRENGTH = 0.1f;
|
|
const F32 UNDERWATER_FREQUENCY_DAMP = 0.33f;
|
|
const F32 APPEARANCE_MORPH_TIME = 0.65f;
|
|
const F32 TIME_BEFORE_MESH_CLEANUP = 5.f; // seconds
|
|
const S32 AVATAR_RELEASE_THRESHOLD = 10; // number of avatar instances before releasing memory
|
|
const F32 FOOT_GROUND_COLLISION_TOLERANCE = 0.25f;
|
|
const F32 AVATAR_LOD_TWEAK_RANGE = 0.7f;
|
|
const S32 MAX_BUBBLE_CHAT_LENGTH = DB_CHAT_MSG_STR_LEN;
|
|
const S32 MAX_BUBBLE_CHAT_UTTERANCES = 12;
|
|
const F32 CHAT_FADE_TIME = 8.0;
|
|
const F32 BUBBLE_CHAT_TIME = CHAT_FADE_TIME * 3.f;
|
|
const F32 NAMETAG_UPDATE_THRESHOLD = 0.3f;
|
|
const F32 NAMETAG_VERTICAL_SCREEN_OFFSET = 25.f;
|
|
const F32 NAMETAG_VERT_OFFSET_WEIGHT = 0.17f;
|
|
|
|
const U32 LLVOAvatar::VISUAL_COMPLEXITY_UNKNOWN = 0;
|
|
const F64 HUD_OVERSIZED_TEXTURE_DATA_SIZE = 1024 * 1024;
|
|
|
|
#define SLOW_ATTACHMENT_LIST 0
|
|
|
|
//Singu note: FADE and ALWAYS are swapped around from LL's source to match our preference panel.
|
|
// Changing the "RenderName" order would cause confusion when 'always' setting suddenly gets
|
|
// interpreted as 'fade', and vice versa.
|
|
enum ERenderName
|
|
{
|
|
RENDER_NAME_NEVER,
|
|
RENDER_NAME_FADE,
|
|
RENDER_NAME_ALWAYS
|
|
};
|
|
|
|
|
|
// Utility func - FIXME move out of avatar.
|
|
std::string get_sequential_numbered_file_name(const std::string& prefix,
|
|
const std::string& suffix);
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Callback data
|
|
//-----------------------------------------------------------------------------
|
|
|
|
struct LLTextureMaskData
|
|
{
|
|
LLTextureMaskData( const LLUUID& id ) :
|
|
mAvatarID(id),
|
|
mLastDiscardLevel(S32_MAX)
|
|
{}
|
|
LLUUID mAvatarID;
|
|
S32 mLastDiscardLevel;
|
|
};
|
|
|
|
/*********************************************************************************
|
|
** **
|
|
** Begin private LLVOAvatar Support classes
|
|
**
|
|
**/
|
|
|
|
struct LLAppearanceMessageContents: public LLRefCount
|
|
{
|
|
LLAppearanceMessageContents():
|
|
mAppearanceVersion(-1),
|
|
mParamAppearanceVersion(-1),
|
|
mCOFVersion(LLViewerInventoryCategory::VERSION_UNKNOWN)
|
|
{
|
|
}
|
|
LLTEContents mTEContents;
|
|
S32 mAppearanceVersion;
|
|
S32 mParamAppearanceVersion;
|
|
S32 mCOFVersion;
|
|
// For future use:
|
|
//U32 appearance_flags = 0;
|
|
std::vector<F32> mParamWeights;
|
|
std::vector<LLVisualParam*> mParams;
|
|
LLVector3 mHoverOffset;
|
|
bool mHoverOffsetWasSet;
|
|
};
|
|
//-----------------------------------------------------------------------------
|
|
// class LLBodyNoiseMotion
|
|
//-----------------------------------------------------------------------------
|
|
class LLBodyNoiseMotion :
|
|
public AIMaskedMotion
|
|
{
|
|
public:
|
|
// Constructor
|
|
LLBodyNoiseMotion(LLUUID const& id, LLMotionController* controller)
|
|
: AIMaskedMotion(id, controller, ANIM_AGENT_BODY_NOISE)
|
|
{
|
|
mName = "body_noise";
|
|
mTorsoState = new LLJointState;
|
|
}
|
|
|
|
// Destructor
|
|
virtual ~LLBodyNoiseMotion() { }
|
|
|
|
public:
|
|
//-------------------------------------------------------------------------
|
|
// functions to support MotionController and MotionRegistry
|
|
//-------------------------------------------------------------------------
|
|
// static constructor
|
|
// all subclasses must implement such a function and register it
|
|
static LLMotion* create(LLUUID const& id, LLMotionController* controller) { return new LLBodyNoiseMotion(id, controller); }
|
|
|
|
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; }
|
|
|
|
// motions must report their priority
|
|
virtual LLJoint::JointPriority getPriority() { return LLJoint::HIGH_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_BODY_NOISE; }
|
|
|
|
// 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)
|
|
{
|
|
if( !mTorsoState->setJoint( character->getJoint("mTorso") ))
|
|
{
|
|
return STATUS_FAILURE;
|
|
}
|
|
|
|
mTorsoState->setUsage(LLJointState::ROT);
|
|
|
|
addJointState( mTorsoState );
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
F32 noiseX = LLPerlinNoise::noise(LLVector2(time*TORSO_NOISE_SPEED, 0));
|
|
F32 noiseY = LLPerlinNoise::noise(LLVector2(0, time*TORSO_NOISE_SPEED));
|
|
|
|
F32 rx = TORSO_NOISE_AMOUNT * DEG_TO_RAD * noiseX / 0.42f;
|
|
F32 ry = TORSO_NOISE_AMOUNT * DEG_TO_RAD * noiseY / 0.42f;
|
|
LLQuaternion tQn;
|
|
tQn.setQuat( rx, ry, 0.0f );
|
|
mTorsoState->setRotation( tQn );
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
private:
|
|
//-------------------------------------------------------------------------
|
|
// joint states to be animated
|
|
//-------------------------------------------------------------------------
|
|
LLPointer<LLJointState> mTorsoState;
|
|
};
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// class LLBreatheMotionRot
|
|
//-----------------------------------------------------------------------------
|
|
class LLBreatheMotionRot :
|
|
public AIMaskedMotion
|
|
{
|
|
public:
|
|
// Constructor
|
|
LLBreatheMotionRot(LLUUID const& id, LLMotionController* controller) :
|
|
AIMaskedMotion(id, controller, ANIM_AGENT_BREATHE_ROT),
|
|
mBreatheRate(1.f),
|
|
mCharacter(NULL)
|
|
{
|
|
mName = "breathe_rot";
|
|
mChestState = new LLJointState;
|
|
}
|
|
|
|
// Destructor
|
|
virtual ~LLBreatheMotionRot() {}
|
|
|
|
public:
|
|
//-------------------------------------------------------------------------
|
|
// functions to support MotionController and MotionRegistry
|
|
//-------------------------------------------------------------------------
|
|
// static constructor
|
|
// all subclasses must implement such a function and register it
|
|
static LLMotion* create(LLUUID const& id, LLMotionController* controller) { return new LLBreatheMotionRot(id, controller); }
|
|
|
|
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; }
|
|
|
|
// motions must report their priority
|
|
virtual LLJoint::JointPriority getPriority() { return LLJoint::MEDIUM_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_BREATHE; }
|
|
|
|
// 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)
|
|
{
|
|
mCharacter = character;
|
|
bool success = true;
|
|
|
|
if ( !mChestState->setJoint( character->getJoint( "mChest" ) ) ) { success = false; }
|
|
|
|
if ( success )
|
|
{
|
|
mChestState->setUsage(LLJointState::ROT);
|
|
addJointState( mChestState );
|
|
}
|
|
|
|
if ( success )
|
|
{
|
|
return STATUS_SUCCESS;
|
|
}
|
|
else
|
|
{
|
|
return STATUS_FAILURE;
|
|
}
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
mBreatheRate = 1.f;
|
|
|
|
F32 breathe_amt = (sinf(mBreatheRate * time) * BREATHE_ROT_MOTION_STRENGTH);
|
|
|
|
mChestState->setRotation(LLQuaternion(breathe_amt, LLVector3(0.f, 1.f, 0.f)));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
private:
|
|
//-------------------------------------------------------------------------
|
|
// joint states to be animated
|
|
//-------------------------------------------------------------------------
|
|
LLPointer<LLJointState> mChestState;
|
|
F32 mBreatheRate;
|
|
LLCharacter* mCharacter;
|
|
};
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// class LLPelvisFixMotion
|
|
//-----------------------------------------------------------------------------
|
|
class LLPelvisFixMotion :
|
|
public AIMaskedMotion
|
|
{
|
|
public:
|
|
// Constructor
|
|
LLPelvisFixMotion(LLUUID const& id, LLMotionController* controller)
|
|
: AIMaskedMotion(id, controller, ANIM_AGENT_PELVIS_FIX), mCharacter(NULL)
|
|
{
|
|
mName = "pelvis_fix";
|
|
|
|
mPelvisState = new LLJointState;
|
|
}
|
|
|
|
// Destructor
|
|
virtual ~LLPelvisFixMotion() { }
|
|
|
|
public:
|
|
//-------------------------------------------------------------------------
|
|
// functions to support MotionController and MotionRegistry
|
|
//-------------------------------------------------------------------------
|
|
// static constructor
|
|
// all subclasses must implement such a function and register it
|
|
static LLMotion* create(LLUUID const& id, LLMotionController* controller) { return new LLPelvisFixMotion(id, controller); }
|
|
|
|
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; }
|
|
|
|
// motions must report their priority
|
|
virtual LLJoint::JointPriority getPriority() { 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_PELVIS_FIX; }
|
|
|
|
// 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)
|
|
{
|
|
mCharacter = character;
|
|
|
|
if (!mPelvisState->setJoint( character->getJoint("mPelvis")))
|
|
{
|
|
return STATUS_FAILURE;
|
|
}
|
|
|
|
mPelvisState->setUsage(LLJointState::POS);
|
|
|
|
addJointState( mPelvisState );
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
mPelvisState->setPosition(LLVector3::zero);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
private:
|
|
//-------------------------------------------------------------------------
|
|
// joint states to be animated
|
|
//-------------------------------------------------------------------------
|
|
LLPointer<LLJointState> mPelvisState;
|
|
LLCharacter* mCharacter;
|
|
};
|
|
|
|
SHClientTagMgr::SHClientTagMgr()
|
|
{
|
|
//Status colors/friend tags are always relevant, regardless of grid.
|
|
gSavedSettings.getControl("AscentShowFriendsTag")->getSignal()->connect(boost::bind(&LLVOAvatar::invalidateNameTags));
|
|
gSavedSettings.getControl("AscentUseStatusColors")->getSignal()->connect(boost::bind(&LLVOAvatar::invalidateNameTags));
|
|
gSavedSettings.getControl("AscentShowSelfTagColor")->getSignal()->connect(boost::bind(&LLVOAvatar::invalidateNameTags));
|
|
gSavedSettings.getControl("AscentShowOthersTagColor")->getSignal()->connect(boost::bind(&LLVOAvatar::invalidateNameTags));
|
|
gSavedSettings.getControl("AscentLindenColor")->getSignal()->connect(boost::bind(&LLVOAvatar::invalidateNameTags));
|
|
gSavedSettings.getControl("AscentEstateOwnerColor")->getSignal()->connect(boost::bind(&LLVOAvatar::invalidateNameTags));
|
|
gSavedSettings.getControl("AscentFriendColor")->getSignal()->connect(boost::bind(&LLVOAvatar::invalidateNameTags));
|
|
gSavedSettings.getControl("AscentMutedColor")->getSignal()->connect(boost::bind(&LLVOAvatar::invalidateNameTags));
|
|
gSavedSettings.getControl("SLBDisplayClientTagOnNewLine")->getSignal()->connect(boost::bind(&LLVOAvatar::invalidateNameTags));
|
|
|
|
//Following group of settings all actually manipulate the tag cache for agent avatar. Even if the tag system is 'disabled', we still allow an
|
|
//entry to exist for the agent avatar.
|
|
gSavedSettings.getControl("AscentUseCustomTag")->getSignal()->connect(boost::bind(&SHClientTagMgr::updateAgentAvatarTag, this));
|
|
gSavedSettings.getControl("AscentCustomTagColor")->getSignal()->connect(boost::bind(&SHClientTagMgr::updateAgentAvatarTag, this));
|
|
gSavedSettings.getControl("AscentCustomTagLabel")->getSignal()->connect(boost::bind(&SHClientTagMgr::updateAgentAvatarTag, this));
|
|
|
|
//And because there can be an entry for the self avatar, always perform this as well.
|
|
gSavedSettings.getControl("AscentShowSelfTag")->getSignal()->connect(boost::bind(&LLVOAvatar::invalidateNameTags));
|
|
|
|
if(!getIsEnabled())
|
|
return;
|
|
|
|
if (gSavedSettings.getBOOL("AscentUpdateTagsOnLoad"))
|
|
fetchDefinitions();
|
|
parseDefinitions();
|
|
|
|
//Tags for other users only exist if the tag system is enabled. No point in registering this callback if non-agent avatars can't have tags.
|
|
gSavedSettings.getControl("AscentShowOthersTag")->getSignal()->connect(boost::bind(&LLVOAvatar::invalidateNameTags));
|
|
|
|
//Update the cached tag for the agent avatar. AscentReportClientUUID dictates what tag the agent avatar sees on their self.
|
|
gSavedSettings.getControl("AscentReportClientUUID")->getSignal()->connect(boost::bind(&SHClientTagMgr::updateAgentAvatarTag, this));
|
|
|
|
//Fire off a AgentSetAppearance update if these change. Essentially, forces the new clientid (or lack thereof) to be sent off to the server for others to see.
|
|
gSavedSettings.getControl("AscentBroadcastTag")->getSignal()->connect(boost::bind(&LLAgent::sendAgentSetAppearance, &gAgent));
|
|
gSavedSettings.getControl("AscentReportClientUUID")->getSignal()->connect(boost::bind(&LLAgent::sendAgentSetAppearance, &gAgent));
|
|
}
|
|
|
|
bool SHClientTagMgr::getIsEnabled() const
|
|
{
|
|
//TE info is wiped out by the simulators on the MAIN grid. Disable client tagging when connected to it.
|
|
return !gHippoGridManager->getConnectedGrid()->isSecondLife();
|
|
}
|
|
|
|
bool SHClientTagMgr::fetchDefinitions() const
|
|
{
|
|
std::string client_list_filename = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, "client_tags_sg1.xml");
|
|
std::string client_list_url = gSavedSettings.getString("ClientDefinitionsURL");
|
|
LLSD response = LLHTTPClient::blockingGet(client_list_url);
|
|
if(response.has("body"))
|
|
{
|
|
const LLSD &client_list = response["body"];
|
|
|
|
if(client_list.has("isComplete"))
|
|
{
|
|
llofstream export_file;
|
|
export_file.open(client_list_filename);
|
|
LLSDSerialize::toPrettyXML(client_list, export_file);
|
|
export_file.close();
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
bool SHClientTagMgr::parseDefinitions()
|
|
{
|
|
std::string client_list_filename = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, "client_tags_sg1.xml");
|
|
|
|
if(!LLFile::isfile(client_list_filename))
|
|
{
|
|
client_list_filename = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "client_tags_sg1.xml");
|
|
}
|
|
|
|
if(LLFile::isfile(client_list_filename))
|
|
{
|
|
LLSD client_list;
|
|
llifstream importer(client_list_filename);
|
|
if(importer.is_open())
|
|
{
|
|
LLSDSerialize::fromXMLDocument(client_list, importer);
|
|
importer.close();
|
|
if(client_list.has("isComplete"))
|
|
{
|
|
mClientDefinitions.clear();
|
|
for (LLSD::map_const_iterator it = client_list.beginMap(); it != client_list.endMap(); ++it)
|
|
{
|
|
LL_INFOS() << it->first << LL_ENDL;
|
|
LLUUID id;
|
|
if(id.set(it->first,false))
|
|
{
|
|
LLSD info = it->second;
|
|
info["id"] = id;
|
|
F32 mult = info["multiple"].asReal();
|
|
if(mult != 0.f)
|
|
{
|
|
static const LLCachedControl<LLColor4> avatar_name_color(gColors,"AvatarNameColor",LLColor4(LLColor4U(251, 175, 93, 255)) );
|
|
LLColor4 color(info["color"]);
|
|
color += avatar_name_color;
|
|
color *= 1.f/(mult+1.f);
|
|
info.insert("color", color.getValue());
|
|
}
|
|
LL_INFOS() << info["name"] << " : " << info["id"] << LL_ENDL;
|
|
mClientDefinitions[id]=info;
|
|
LL_INFOS() << mClientDefinitions.size() << LL_ENDL;
|
|
}
|
|
}
|
|
if(getIsEnabled())
|
|
{
|
|
for (std::vector<LLCharacter*>::iterator iter = LLCharacter::sInstances.begin(); iter != LLCharacter::sInstances.end(); ++iter)
|
|
{
|
|
LLVOAvatar* pAvatar = dynamic_cast<LLVOAvatar*>(*iter);
|
|
if(pAvatar)
|
|
updateAvatarTag(pAvatar);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
void SHClientTagMgr::updateAgentAvatarTag()
|
|
{
|
|
if(isAgentAvatarValid())
|
|
updateAvatarTag(gAgentAvatarp);
|
|
}
|
|
const LLSD SHClientTagMgr::generateClientTag(const LLVOAvatar* pAvatar) const
|
|
{
|
|
static const LLCachedControl<LLColor4> avatar_name_color(gColors,"AvatarNameColor",LLColor4(LLColor4U(251, 175, 93, 255)) );
|
|
LLUUID id;
|
|
|
|
if (pAvatar->isSelf())
|
|
{
|
|
static const LLCachedControl<bool> ascent_use_custom_tag("AscentUseCustomTag", false);
|
|
static const LLCachedControl<LLColor4> ascent_custom_tag_color("AscentCustomTagColor", LLColor4(.5f,1.f,.25f,1.f));
|
|
static const LLCachedControl<std::string> ascent_custom_tag_label("AscentCustomTagLabel","custom");
|
|
static const LLCachedControl<std::string> ascent_report_client_uuid("AscentReportClientUUID","f25263b7-6167-4f34-a4ef-af65213b2e39");
|
|
|
|
if (ascent_use_custom_tag)
|
|
{
|
|
LLSD info;
|
|
info.insert("name", ascent_custom_tag_label.get());
|
|
info.insert("color", ascent_custom_tag_color.get().getValue());
|
|
return info;
|
|
}
|
|
else if(!getIsEnabled())
|
|
{
|
|
return LLSD();
|
|
}
|
|
else
|
|
{
|
|
id.set(ascent_report_client_uuid,false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(!getIsEnabled())
|
|
return LLSD();
|
|
|
|
LLTextureEntry* pTextureEntry = pAvatar->getTE(TEX_HEAD_BODYPAINT);
|
|
if (!pTextureEntry)
|
|
return LLSD();
|
|
|
|
id = pTextureEntry->getID();
|
|
|
|
if(pAvatar->isFullyLoaded() && pTextureEntry->getGlow() > 0.0)
|
|
{
|
|
///LL_INFOS() << "Using new client identifier." << LL_ENDL;
|
|
U32 tag_len = strnlen((const char*)&id.mData[0], UUID_BYTES);
|
|
std::string client((const char*)&id.mData[0], tag_len);
|
|
LLStringFn::replace_ascii_controlchars(client, LL_UNKNOWN_CHAR);
|
|
LLSD info;
|
|
info.insert("name", client);
|
|
info.insert("color", pTextureEntry->getColor().getValue());
|
|
return info;
|
|
}
|
|
if(pAvatar->getTEImage(TEX_HEAD_BODYPAINT)->getID() == IMG_DEFAULT_AVATAR)
|
|
{
|
|
for(S32 te = TEX_UPPER_SHIRT; te <= TEX_SKIRT; ++te) //Don't iterate past skirt. The sim doesn't even send those TEs (Those TEs assume TEX_HAIR_BAKED's uuid)
|
|
{
|
|
LLWearableType::EType wearable = LLAvatarAppearanceDictionary::getTEWearableType((LLAvatarAppearanceDefines::ETextureIndex)te);
|
|
if( wearable != LLWearableType::WT_INVALID &&
|
|
wearable != LLWearableType::WT_ALPHA &&
|
|
wearable != LLWearableType::WT_TATTOO &&
|
|
pAvatar->getTEImage(te)->getID() != IMG_DEFAULT_AVATAR)
|
|
{
|
|
static LLUUID grey_id("4934f1bf-3b1f-cf4f-dbdf-a72550d05bc6");
|
|
|
|
LLSD info;
|
|
|
|
//Apparently this happens with broken clothing protection...
|
|
if( pAvatar->getTEImage(TEX_EYES_IRIS)->getID() == grey_id &&
|
|
pAvatar->getTEImage(TEX_UPPER_BODYPAINT)->getID() == grey_id &&
|
|
pAvatar->getTEImage(TEX_LOWER_BODYPAINT)->getID() == grey_id )
|
|
{
|
|
info.insert("name", "?");
|
|
info.insert("color", avatar_name_color.get().getValue());
|
|
}
|
|
return info;
|
|
}
|
|
}
|
|
|
|
//Verify if this actually ever happens on opensim with V3 clients.
|
|
LLSD info;
|
|
info.insert("name", "Viewer 2.0");
|
|
info.insert("color", LLColor4::white.getValue());
|
|
return info;
|
|
}
|
|
else if(pAvatar->getTEImage(TEX_HEAD_BODYPAINT)->isMissingAsset())
|
|
{
|
|
LLSD info;
|
|
info.insert("name", "Unknown");
|
|
info.insert("color", LLColor4(0.5f, 0.0f, 0.0f).getValue());
|
|
return info;
|
|
}
|
|
}
|
|
|
|
if(!mClientDefinitions.empty())
|
|
{
|
|
std::map<LLUUID, LLSD>::const_iterator it = mClientDefinitions.find(id);
|
|
if(it != mClientDefinitions.end())
|
|
{
|
|
return it->second;
|
|
}
|
|
}
|
|
//Unhandled. Just return blank.
|
|
return LLSD();
|
|
}
|
|
void SHClientTagMgr::updateAvatarTag(LLVOAvatar* pAvatar)
|
|
{
|
|
LLUUID id = pAvatar->getID();
|
|
std::map<LLUUID, LLSD>::iterator it = mAvatarTags.find(id);
|
|
|
|
LLSD new_tag = generateClientTag(pAvatar);
|
|
LLSD old_tag = (it != mAvatarTags.end()) ? it->second : LLSD();
|
|
|
|
bool dirty = old_tag.size() != new_tag.size() || !llsd_equals(new_tag,old_tag);
|
|
if(dirty)
|
|
{
|
|
if(new_tag.isUndefined())
|
|
mAvatarTags.erase(id);
|
|
else
|
|
mAvatarTags[id]=new_tag;
|
|
pAvatar->clearNameTag(); //LLVOAvatar::idleUpdateNameTag will pick up on mNameString being cleared.
|
|
}
|
|
}
|
|
const std::string SHClientTagMgr::getClientName(const LLVOAvatar* pAvatar, bool is_friend) const
|
|
{
|
|
static LLCachedControl<bool> ascent_show_friends_tag("AscentShowFriendsTag", false);
|
|
static LLCachedControl<bool> ascent_show_self_tag("AscentShowSelfTag", false);
|
|
static LLCachedControl<bool> ascent_show_others_tag("AscentShowOthersTag", false);
|
|
if(is_friend && ascent_show_friends_tag)
|
|
return "Friend";
|
|
else
|
|
{
|
|
if((!pAvatar->isSelf() && ascent_show_others_tag) ||
|
|
(pAvatar->isSelf() && ascent_show_self_tag))
|
|
{
|
|
LLSD tag;
|
|
std::map<LLUUID, LLSD>::const_iterator it = mAvatarTags.find(pAvatar->getID());
|
|
if(it != mAvatarTags.end())
|
|
{
|
|
tag = it->second.get("name");
|
|
}
|
|
return tag.asString();
|
|
}
|
|
else
|
|
return std::string();
|
|
}
|
|
}
|
|
const LLUUID SHClientTagMgr::getClientID(const LLVOAvatar* pAvatar) const
|
|
{
|
|
LLSD tag;
|
|
std::map<LLUUID, LLSD>::const_iterator it = mAvatarTags.find(pAvatar->getID());
|
|
if(it != mAvatarTags.end())
|
|
{
|
|
tag = it->second.get("id");
|
|
}
|
|
return tag.asUUID();
|
|
}
|
|
bool getColorFor(const LLUUID& id, LLViewerRegion* parent_estate, LLColor4& color, bool name_restricted = false)
|
|
{
|
|
static const LLCachedControl<LLColor4> ascent_linden_color("AscentLindenColor");
|
|
static const LLCachedControl<LLColor4> ascent_estate_owner_color("AscentEstateOwnerColor" );
|
|
static const LLCachedControl<LLColor4> ascent_friend_color("AscentFriendColor");
|
|
static const LLCachedControl<LLColor4> ascent_muted_color("AscentMutedColor");
|
|
//Lindens are always more Linden than your friend, make that take precedence
|
|
if (LLMuteList::getInstance()->isLinden(id))
|
|
color = ascent_linden_color;
|
|
//check if they are an estate owner at their current position
|
|
else if (parent_estate && parent_estate->isAlive() && id.notNull() && id == parent_estate->getOwner())
|
|
color = ascent_estate_owner_color;
|
|
//without these dots, SL would suck.
|
|
else if (!name_restricted && LLAvatarTracker::instance().isBuddy(id))
|
|
color = ascent_friend_color;
|
|
//big fat jerkface who is probably a jerk, display them as such.
|
|
else if (LLMuteList::getInstance()->isMuted(id))
|
|
color = ascent_muted_color;
|
|
else
|
|
return false;
|
|
return true;
|
|
}
|
|
bool mm_getMarkerColor(const LLUUID&, LLColor4&);
|
|
bool getCustomColor(const LLUUID& id, LLColor4& color, LLViewerRegion* parent_estate)
|
|
{
|
|
return mm_getMarkerColor(id, color) || getColorFor(id, parent_estate, color);
|
|
}
|
|
bool getCustomColorRLV(const LLUUID& id, LLColor4& color, LLViewerRegion* parent_estate, bool name_restricted)
|
|
{
|
|
return mm_getMarkerColor(id, color) || getColorFor(id, parent_estate, color, name_restricted);
|
|
}
|
|
bool SHClientTagMgr::getClientColor(const LLVOAvatar* pAvatar, bool check_status, LLColor4& color) const
|
|
{
|
|
const LLUUID& id(pAvatar->getID());
|
|
if (mm_getMarkerColor(id, color)) return true;
|
|
static const LLCachedControl<bool> ascent_use_status_colors("AscentUseStatusColors",true);
|
|
static const LLCachedControl<bool> ascent_show_self_tag_color("AscentShowSelfTagColor");
|
|
static const LLCachedControl<bool> ascent_show_others_tag_color("AscentShowOthersTagColor");
|
|
|
|
if (check_status && ascent_use_status_colors && !pAvatar->isSelf())
|
|
{
|
|
if (getColorFor(id, pAvatar->getRegion(), color, false))
|
|
return true;
|
|
}
|
|
std::map<LLUUID, LLSD>::const_iterator it;
|
|
LLSD tag;
|
|
if(
|
|
(pAvatar->isSelf() ? ascent_show_self_tag_color : ascent_show_others_tag_color) &&
|
|
(it = mAvatarTags.find(id))!=mAvatarTags.end() &&
|
|
(tag = it->second.get("color")).isDefined())
|
|
{
|
|
color.setValue(tag);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
//Call on destruction of avatar. Removes entry in mAvatarTags map.
|
|
void SHClientTagMgr::clearAvatarTag(const LLVOAvatar* pAvatar)
|
|
{
|
|
mAvatarTags.erase(pAvatar->getID());
|
|
}
|
|
|
|
/**
|
|
**
|
|
** End LLVOAvatar Support classes
|
|
** **
|
|
*********************************************************************************/
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Static Data
|
|
//-----------------------------------------------------------------------------
|
|
LLAvatarAppearanceDictionary *LLVOAvatar::sAvatarDictionary = NULL;
|
|
S32 LLVOAvatar::sFreezeCounter = 0;
|
|
U32 LLVOAvatar::sMaxVisible = 50;
|
|
F32 LLVOAvatar::sRenderDistance = 256.f;
|
|
S32 LLVOAvatar::sNumVisibleAvatars = 0;
|
|
S32 LLVOAvatar::sNumLODChangesThisFrame = 0;
|
|
|
|
const LLUUID LLVOAvatar::sStepSoundOnLand("e8af4a28-aa83-4310-a7c4-c047e15ea0df");
|
|
const LLUUID LLVOAvatar::sStepSounds[LL_MCODE_END] =
|
|
{
|
|
SND_STONE_RUBBER,
|
|
SND_METAL_RUBBER,
|
|
SND_GLASS_RUBBER,
|
|
SND_WOOD_RUBBER,
|
|
SND_FLESH_RUBBER,
|
|
SND_RUBBER_PLASTIC,
|
|
SND_RUBBER_RUBBER
|
|
};
|
|
|
|
S32 LLVOAvatar::sRenderName = RENDER_NAME_ALWAYS;
|
|
bool LLVOAvatar::sRenderGroupTitles = true;
|
|
S32 LLVOAvatar::sNumVisibleChatBubbles = 0;
|
|
BOOL LLVOAvatar::sDebugInvisible = false;
|
|
bool LLVOAvatar::sShowAttachmentPoints = false;
|
|
BOOL LLVOAvatar::sShowAnimationDebug = false;
|
|
bool LLVOAvatar::sShowFootPlane = false;
|
|
bool LLVOAvatar::sVisibleInFirstPerson = false;
|
|
F32 LLVOAvatar::sLODFactor = 1.f;
|
|
F32 LLVOAvatar::sPhysicsLODFactor = 1.f;
|
|
bool LLVOAvatar::sUseImpostors = false;
|
|
BOOL LLVOAvatar::sJointDebug = false;
|
|
F32 LLVOAvatar::sUnbakedTime = 0.f;
|
|
F32 LLVOAvatar::sUnbakedUpdateTime = 0.f;
|
|
F32 LLVOAvatar::sGreyTime = 0.f;
|
|
F32 LLVOAvatar::sGreyUpdateTime = 0.f;
|
|
|
|
//Move to LLVOAvatarSelf
|
|
BOOL LLVOAvatar::sDebugAvatarRotation = false;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Helper functions
|
|
//-----------------------------------------------------------------------------
|
|
static F32 calc_bouncy_animation(F32 x);
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// LLVOAvatar()
|
|
//-----------------------------------------------------------------------------
|
|
LLVOAvatar::LLVOAvatar(const LLUUID& id,
|
|
const LLPCode pcode,
|
|
LLViewerRegion* regionp) :
|
|
LLAvatarAppearance(&gAgentWearables),
|
|
LLViewerObject(id, pcode, regionp),
|
|
mSpecialRenderMode(0),
|
|
mAttachmentSurfaceArea(0.f),
|
|
mAttachmentVisibleTriangleCount(0),
|
|
mAttachmentEstTriangleCount(0.f),
|
|
mReportedVisualComplexity(VISUAL_COMPLEXITY_UNKNOWN),
|
|
mTurning(FALSE),
|
|
mFreezeTimeLangolier(false),
|
|
mFreezeTimeDead(false),
|
|
mLastSkeletonSerialNum( 0 ),
|
|
mIsSitting(FALSE),
|
|
mTimeVisible(),
|
|
mTyping(FALSE),
|
|
mMeshValid(FALSE),
|
|
mVisible(FALSE),
|
|
mLastImpostorUpdateFrameTime(0.f),
|
|
mWindFreq(0.f),
|
|
mRipplePhase( 0.f ),
|
|
mBelowWater(FALSE),
|
|
mLastAppearanceBlendTime(0.f),
|
|
mAppearanceAnimating(FALSE),
|
|
mNameString(),
|
|
mTitle(),
|
|
mNameAway(false),
|
|
mNameBusy(false),
|
|
mNameMute(false),
|
|
mNameAppearance(false),
|
|
mNameFriend(false),
|
|
mNameAlpha(0.f),
|
|
mRenderGroupTitles(sRenderGroupTitles),
|
|
mNameCloud(false),
|
|
mFirstTEMessageReceived( FALSE ),
|
|
mFirstAppearanceMessageReceived( FALSE ),
|
|
mCulled( FALSE ),
|
|
mVisibilityRank(0),
|
|
mNeedsSkin(FALSE),
|
|
mLastSkinTime(0.f),
|
|
mUpdatePeriod(1),
|
|
mVisualComplexityStale(true),
|
|
mFirstFullyVisible(TRUE),
|
|
mFullyLoaded(FALSE),
|
|
mPreviousFullyLoaded(FALSE),
|
|
mFullyLoadedInitialized(FALSE),
|
|
mVisualComplexity(0),
|
|
mSupportsAlphaLayers(FALSE),
|
|
mLoadedCallbacksPaused(FALSE),
|
|
mLastRezzedStatus(-1),
|
|
mIsEditingAppearance(FALSE),
|
|
mUseLocalAppearance(FALSE),
|
|
mUseServerBakes(FALSE),
|
|
mLastUpdateRequestCOFVersion(-1),
|
|
mLastUpdateReceivedCOFVersion(-1),
|
|
mIsControlAvatar(false),
|
|
mIsUIAvatar(false),
|
|
mEnableDefaultMotions(true),
|
|
// <edit>
|
|
mHasPhysicsParameters( false ),
|
|
mIdleMinute(0),
|
|
mCCSChatTextOverride(false)
|
|
// </edit>
|
|
{
|
|
mAttachedObjectsVector.reserve(MAX_AGENT_ATTACHMENTS);
|
|
|
|
static LLCachedControl<bool> const freeze_time("FreezeTime", false);
|
|
mFreezeTimeLangolier = freeze_time;
|
|
|
|
//VTResume(); // VTune
|
|
setHoverOffset(LLVector3(0.0, 0.0, 0.0));
|
|
|
|
// mVoiceVisualizer is created by the hud effects manager and uses the HUD Effects pipeline
|
|
const bool needsSendToSim = false; // currently, this HUD effect doesn't need to pack and unpack data to do its job
|
|
mVoiceVisualizer = ( LLVoiceVisualizer *)LLHUDManager::getInstance()->createViewerEffect( LLHUDObject::LL_HUD_EFFECT_VOICE_VISUALIZER, needsSendToSim );
|
|
|
|
LL_DEBUGS("Avatar","Message") << "LLVOAvatar Constructor (0x" << this << ") id:" << mID << LL_ENDL;
|
|
|
|
mPelvisp = NULL;
|
|
|
|
mDirtyMesh = 2; // Dirty geometry, need to regenerate.
|
|
mMeshTexturesDirty = FALSE;
|
|
mHeadp = NULL;
|
|
|
|
|
|
// set up animation variables
|
|
mSpeed = 0.f;
|
|
setAnimationData("Speed", &mSpeed);
|
|
|
|
mNeedsImpostorUpdate = TRUE;
|
|
mNeedsAnimUpdate = TRUE;
|
|
|
|
mNeedsExtentUpdate = true;
|
|
|
|
mImpostorDistance = 0;
|
|
mImpostorPixelArea = 0;
|
|
|
|
setNumTEs(TEX_NUM_INDICES);
|
|
|
|
mbCanSelect = TRUE;
|
|
|
|
mSignaledAnimations.clear();
|
|
mPlayingAnimations.clear();
|
|
|
|
mWasOnGroundLeft = FALSE;
|
|
mWasOnGroundRight = FALSE;
|
|
|
|
mTimeLast = 0.0f;
|
|
mSpeedAccum = 0.0f;
|
|
|
|
mRippleTimeLast = 0.f;
|
|
|
|
mInAir = FALSE;
|
|
|
|
mStepOnLand = TRUE;
|
|
mStepMaterial = 0;
|
|
|
|
mLipSyncActive = false;
|
|
mOohMorph = NULL;
|
|
mAahMorph = NULL;
|
|
|
|
mCurrentGesticulationLevel = 0;
|
|
|
|
mRuthTimer.reset();
|
|
mRuthDebugTimer.reset();
|
|
mDebugExistenceTimer.reset();
|
|
mLastAppearanceMessageTimer.reset();
|
|
}
|
|
|
|
std::string LLVOAvatar::avString() const
|
|
{
|
|
if (isControlAvatar())
|
|
{
|
|
return getFullname();
|
|
}
|
|
else
|
|
{
|
|
std::string viz_string = LLVOAvatar::rezStatusToString(getRezzedStatus());
|
|
return " Avatar '" + getFullname() + "' " + viz_string + " ";
|
|
}
|
|
}
|
|
|
|
void LLVOAvatar::debugAvatarRezTime(std::string notification_name, std::string comment)
|
|
{
|
|
LL_INFOS("Avatar") << "REZTIME: [ " << (U32)mDebugExistenceTimer.getElapsedTimeF32()
|
|
<< "sec ]"
|
|
<< avString()
|
|
<< "RuthTimer " << (U32)mRuthDebugTimer.getElapsedTimeF32()
|
|
<< " Notification " << notification_name
|
|
<< " : " << comment
|
|
<< LL_ENDL;
|
|
|
|
if (gSavedSettings.getBOOL("DebugAvatarRezTime"))
|
|
{
|
|
LLSD args;
|
|
args["EXISTENCE"] = llformat("%d",(U32)mDebugExistenceTimer.getElapsedTimeF32());
|
|
args["TIME"] = llformat("%d",(U32)mRuthDebugTimer.getElapsedTimeF32());
|
|
args["NAME"] = getFullname();
|
|
LLNotificationsUtil::add(notification_name,args);
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
// LLVOAvatar::~LLVOAvatar()
|
|
//------------------------------------------------------------------------
|
|
LLVOAvatar::~LLVOAvatar()
|
|
{
|
|
//App teardown is a mess. Avatar destruction can be unpredictable due to all potential refs to the smartptr.
|
|
//Cannot guarantee that LLNotificationUtil will be usable during shutdown chain.
|
|
if (!LLApp::isQuitting())
|
|
{
|
|
if (!mFullyLoaded)
|
|
{
|
|
debugAvatarRezTime("AvatarRezLeftCloudNotification","left after ruth seconds as cloud");
|
|
}
|
|
else
|
|
{
|
|
debugAvatarRezTime("AvatarRezLeftNotification","left sometime after declouding");
|
|
}
|
|
}
|
|
|
|
//logPendingPhases();
|
|
|
|
LL_DEBUGS("Avatar") << "LLVOAvatar Destructor (0x" << this << ") id:" << mID << LL_ENDL;
|
|
|
|
std::for_each(mAttachmentPoints.begin(), mAttachmentPoints.end(), DeletePairedPointer());
|
|
|
|
mDead = TRUE;
|
|
|
|
mAnimationSources.clear();
|
|
LLLoadedCallbackEntry::cleanUpCallbackList(&mCallbackTextureList) ;
|
|
|
|
getPhases().clearPhases();
|
|
|
|
SHClientTagMgr::instance().clearAvatarTag(this);
|
|
|
|
LL_DEBUGS() << "LLVOAvatar Destructor end" << LL_ENDL;
|
|
}
|
|
|
|
void LLVOAvatar::markDead()
|
|
{
|
|
static const LLCachedControl<bool> freeze_time("FreezeTime", false);
|
|
if (freeze_time && !mFreezeTimeLangolier)
|
|
{
|
|
// Delay the call to this function until FreezeTime is reset, otherwise avatars disappear from the frozen scene.
|
|
mFreezeTimeDead = true;
|
|
return;
|
|
}
|
|
if (mNameText)
|
|
{
|
|
mNameText->markDead();
|
|
mNameText = NULL;
|
|
sNumVisibleChatBubbles--;
|
|
}
|
|
mVoiceVisualizer->markDead();
|
|
LLLoadedCallbackEntry::cleanUpCallbackList(&mCallbackTextureList) ;
|
|
|
|
if(!isDead())
|
|
logPendingPhases();
|
|
|
|
LLViewerObject::markDead();
|
|
}
|
|
|
|
|
|
BOOL LLVOAvatar::isFullyBaked()
|
|
{
|
|
if (mIsDummy) return TRUE;
|
|
if (getNumTEs() == 0) return FALSE;
|
|
|
|
for (U32 i = 0; i < mBakedTextureDatas.size(); i++)
|
|
{
|
|
if (!isTextureDefined(mBakedTextureDatas[i].mTextureIndex)
|
|
&& ( (i != BAKED_SKIRT) || isWearingWearableType(LLWearableType::WT_SKIRT) )
|
|
&& (i != BAKED_LEFT_ARM) && (i != BAKED_LEFT_LEG) && (i != BAKED_AUX1) && (i != BAKED_AUX2) && (i != BAKED_AUX3) )
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL LLVOAvatar::isFullyTextured() const
|
|
{
|
|
for (U32 i = 0; i < mMeshLOD.size(); i++)
|
|
{
|
|
LLAvatarJoint* joint = mMeshLOD[i];
|
|
if (i==MESH_ID_SKIRT && !isWearingWearableType(LLWearableType::WT_SKIRT))
|
|
{
|
|
continue; // don't care about skirt textures if we're not wearing one.
|
|
}
|
|
if (!joint)
|
|
{
|
|
continue; // nonexistent LOD OK.
|
|
}
|
|
avatar_joint_mesh_list_t::iterator meshIter = joint->mMeshParts.begin();
|
|
if (meshIter != joint->mMeshParts.end())
|
|
{
|
|
LLAvatarJointMesh *mesh = (*meshIter);
|
|
if (!mesh)
|
|
{
|
|
continue; // nonexistent mesh OK
|
|
}
|
|
if (mesh->hasGLTexture())
|
|
{
|
|
continue; // Mesh exists and has a baked texture.
|
|
}
|
|
if (mesh->hasComposite())
|
|
{
|
|
continue; // Mesh exists and has a composite texture.
|
|
}
|
|
// Fail
|
|
return FALSE;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL LLVOAvatar::hasGray() const
|
|
{
|
|
return !getIsCloud() && !isFullyTextured();
|
|
}
|
|
|
|
S32 LLVOAvatar::getRezzedStatus() const
|
|
{
|
|
if (getIsCloud()) return 0;
|
|
if (isFullyTextured() && allBakedTexturesCompletelyDownloaded()) return 3;
|
|
if (isFullyTextured()) return 2;
|
|
llassert(hasGray());
|
|
return 1; // gray
|
|
}
|
|
|
|
void LLVOAvatar::deleteLayerSetCaches(bool clearAll)
|
|
{
|
|
for (U32 i = 0; i < mBakedTextureDatas.size(); i++)
|
|
{
|
|
if (mBakedTextureDatas[i].mTexLayerSet)
|
|
{
|
|
// ! BACKWARDS COMPATIBILITY !
|
|
// Can be removed after hair baking is mandatory on the grid
|
|
if ((i != BAKED_HAIR || isSelf()) && !clearAll)
|
|
{
|
|
mBakedTextureDatas[i].mTexLayerSet->deleteCaches();
|
|
}
|
|
}
|
|
mBakedTextureDatas[i].mMaskTexName.reset();
|
|
}
|
|
}
|
|
|
|
// static
|
|
BOOL LLVOAvatar::areAllNearbyInstancesBaked(S32& grey_avatars)
|
|
{
|
|
BOOL res = TRUE;
|
|
grey_avatars = 0;
|
|
for (std::vector<LLCharacter*>::iterator iter = LLCharacter::sInstances.begin();
|
|
iter != LLCharacter::sInstances.end(); ++iter)
|
|
{
|
|
LLVOAvatar* inst = (LLVOAvatar*) *iter;
|
|
if( inst->isDead() )
|
|
{
|
|
continue;
|
|
}
|
|
else if( !inst->isFullyBaked() )
|
|
{
|
|
res = FALSE;
|
|
if (inst->mHasGrey)
|
|
{
|
|
++grey_avatars;
|
|
}
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
// static
|
|
void LLVOAvatar::getNearbyRezzedStats(std::vector<S32>& counts)
|
|
{
|
|
counts.clear();
|
|
counts.resize(4);
|
|
for (std::vector<LLCharacter*>::iterator iter = LLCharacter::sInstances.begin();
|
|
iter != LLCharacter::sInstances.end(); ++iter)
|
|
{
|
|
LLVOAvatar* inst = (LLVOAvatar*) *iter;
|
|
if (inst)
|
|
{
|
|
S32 rez_status = inst->getRezzedStatus();
|
|
counts[rez_status]++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// static
|
|
std::string LLVOAvatar::rezStatusToString(S32 rez_status)
|
|
{
|
|
if (rez_status==0) return "cloud";
|
|
if (rez_status==1) return "gray";
|
|
if (rez_status==2) return "downloading";
|
|
if (rez_status==3) return "full";
|
|
return "unknown";
|
|
}
|
|
|
|
// static
|
|
void LLVOAvatar::dumpBakedStatus()
|
|
{
|
|
LLVector3d camera_pos_global = gAgentCamera.getCameraPositionGlobal();
|
|
|
|
for (std::vector<LLCharacter*>::iterator iter = LLCharacter::sInstances.begin();
|
|
iter != LLCharacter::sInstances.end(); ++iter)
|
|
{
|
|
LLVOAvatar* inst = (LLVOAvatar*) *iter;
|
|
LL_INFOS() << "Avatar ";
|
|
|
|
LLNameValue* firstname = inst->getNVPair("FirstName");
|
|
LLNameValue* lastname = inst->getNVPair("LastName");
|
|
|
|
if( firstname )
|
|
{
|
|
LL_CONT << firstname->getString();
|
|
}
|
|
if( lastname )
|
|
{
|
|
LL_CONT << " " << lastname->getString();
|
|
}
|
|
|
|
LL_CONT << " " << inst->mID;
|
|
|
|
if( inst->isDead() )
|
|
{
|
|
LL_CONT << " DEAD ("<< inst->getNumRefs() << " refs)";
|
|
}
|
|
|
|
if( inst->isSelf() )
|
|
{
|
|
LL_CONT << " (self)";
|
|
}
|
|
|
|
|
|
F64 dist_to_camera = (inst->getPositionGlobal() - camera_pos_global).length();
|
|
LL_CONT << " " << dist_to_camera << "m ";
|
|
|
|
LL_CONT << " " << inst->mPixelArea << " pixels";
|
|
|
|
if( inst->isVisible() )
|
|
{
|
|
LL_CONT << " (visible)";
|
|
}
|
|
else
|
|
{
|
|
LL_CONT << " (not visible)";
|
|
}
|
|
|
|
if( inst->isFullyBaked() )
|
|
{
|
|
LL_CONT << " Baked";
|
|
}
|
|
else
|
|
{
|
|
LL_CONT << " Unbaked (";
|
|
|
|
for (LLAvatarAppearanceDictionary::BakedTextures::const_iterator iter = LLAvatarAppearanceDictionary::getInstance()->getBakedTextures().begin();
|
|
iter != LLAvatarAppearanceDictionary::getInstance()->getBakedTextures().end();
|
|
++iter)
|
|
{
|
|
const LLAvatarAppearanceDictionary::BakedEntry *baked_dict = iter->second;
|
|
const ETextureIndex index = baked_dict->mTextureIndex;
|
|
if (!inst->isTextureDefined(index))
|
|
{
|
|
LL_CONT << " " << (LLAvatarAppearanceDictionary::getInstance()->getTexture(index) ? LLAvatarAppearanceDictionary::getInstance()->getTexture(index)->mName : "");
|
|
}
|
|
}
|
|
LL_CONT << " ) " << inst->getUnbakedPixelAreaRank();
|
|
if( inst->isCulled() )
|
|
{
|
|
LL_CONT << " culled";
|
|
}
|
|
}
|
|
LL_CONT << LL_ENDL;
|
|
}
|
|
}
|
|
|
|
//static
|
|
void LLVOAvatar::restoreGL()
|
|
{
|
|
if (!isAgentAvatarValid()) return;
|
|
|
|
gAgentAvatarp->setCompositeUpdatesEnabled(TRUE);
|
|
for (U32 i = 0; i < gAgentAvatarp->mBakedTextureDatas.size(); i++)
|
|
{
|
|
gAgentAvatarp->invalidateComposite(gAgentAvatarp->getTexLayerSet(i), FALSE);
|
|
}
|
|
gAgentAvatarp->updateMeshTextures();
|
|
}
|
|
|
|
//static
|
|
void LLVOAvatar::destroyGL()
|
|
{
|
|
deleteCachedImages();
|
|
|
|
resetImpostors();
|
|
}
|
|
|
|
//static
|
|
void LLVOAvatar::resetImpostors()
|
|
{
|
|
for (std::vector<LLCharacter*>::iterator iter = LLCharacter::sInstances.begin();
|
|
iter != LLCharacter::sInstances.end(); ++iter)
|
|
{
|
|
LLVOAvatar* avatar = (LLVOAvatar*) *iter;
|
|
avatar->mImpostor.release();
|
|
avatar->mNeedsImpostorUpdate = TRUE;
|
|
}
|
|
}
|
|
|
|
// static
|
|
void LLVOAvatar::deleteCachedImages(bool clearAll)
|
|
{
|
|
if (LLViewerTexLayerSet::sHasCaches)
|
|
{
|
|
LL_DEBUGS() << "Deleting layer set caches" << LL_ENDL;
|
|
for (std::vector<LLCharacter*>::iterator iter = LLCharacter::sInstances.begin();
|
|
iter != LLCharacter::sInstances.end(); ++iter)
|
|
{
|
|
LLVOAvatar* inst = (LLVOAvatar*) *iter;
|
|
inst->deleteLayerSetCaches(clearAll);
|
|
}
|
|
LLViewerTexLayerSet::sHasCaches = FALSE;
|
|
}
|
|
LLTexLayerStaticImageList::getInstance()->deleteCachedImages();
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------------
|
|
// static
|
|
// LLVOAvatar::initClass()
|
|
//------------------------------------------------------------------------
|
|
void LLVOAvatar::initClass()
|
|
{
|
|
gAnimLibrary.animStateSetString(ANIM_AGENT_BODY_NOISE_ID,"body_noise");
|
|
gAnimLibrary.animStateSetString(ANIM_AGENT_BREATHE_ROT_ID,"breathe_rot");
|
|
gAnimLibrary.animStateSetString(ANIM_AGENT_PHYSICS_MOTION_ID,"physics_motion");
|
|
gAnimLibrary.animStateSetString(ANIM_AGENT_EDITING_ID,"editing");
|
|
gAnimLibrary.animStateSetString(ANIM_AGENT_EYE_ID,"eye");
|
|
gAnimLibrary.animStateSetString(ANIM_AGENT_FLY_ADJUST_ID,"fly_adjust");
|
|
gAnimLibrary.animStateSetString(ANIM_AGENT_HAND_MOTION_ID,"hand_motion");
|
|
gAnimLibrary.animStateSetString(ANIM_AGENT_HEAD_ROT_ID,"head_rot");
|
|
gAnimLibrary.animStateSetString(ANIM_AGENT_PELVIS_FIX_ID,"pelvis_fix");
|
|
gAnimLibrary.animStateSetString(ANIM_AGENT_TARGET_ID,"target");
|
|
gAnimLibrary.animStateSetString(ANIM_AGENT_WALK_ADJUST_ID,"walk_adjust");
|
|
|
|
SHClientTagMgr::instance(); //Instantiate. Parse. Will fetch a new tag file if AscentUpdateTagsOnLoad is true.
|
|
|
|
LLControlAvatar::sRegionChangedSlot = gAgent.addRegionChangedCallback(&LLControlAvatar::onRegionChanged);
|
|
}
|
|
|
|
|
|
void LLVOAvatar::cleanupClass()
|
|
{
|
|
}
|
|
|
|
// virtual
|
|
void LLVOAvatar::initInstance()
|
|
{
|
|
//-------------------------------------------------------------------------
|
|
// register motions
|
|
//-------------------------------------------------------------------------
|
|
if (LLCharacter::sInstances.size() == 1)
|
|
{
|
|
LLKeyframeMotion::setVFS(gStaticVFS);
|
|
registerMotion(ANIM_AGENT_DO_NOT_DISTURB, LLNullMotion::create );
|
|
registerMotion( ANIM_AGENT_CROUCH, LLKeyframeStandMotion::create );
|
|
registerMotion( ANIM_AGENT_CROUCHWALK, LLKeyframeWalkMotion::create );
|
|
registerMotion( ANIM_AGENT_EXPRESS_AFRAID, LLEmote::create );
|
|
registerMotion( ANIM_AGENT_EXPRESS_ANGER, LLEmote::create );
|
|
registerMotion( ANIM_AGENT_EXPRESS_BORED, LLEmote::create );
|
|
registerMotion( ANIM_AGENT_EXPRESS_CRY, LLEmote::create );
|
|
registerMotion( ANIM_AGENT_EXPRESS_DISDAIN, LLEmote::create );
|
|
registerMotion( ANIM_AGENT_EXPRESS_EMBARRASSED, LLEmote::create );
|
|
registerMotion( ANIM_AGENT_EXPRESS_FROWN, LLEmote::create );
|
|
registerMotion( ANIM_AGENT_EXPRESS_KISS, LLEmote::create );
|
|
registerMotion( ANIM_AGENT_EXPRESS_LAUGH, LLEmote::create );
|
|
registerMotion( ANIM_AGENT_EXPRESS_OPEN_MOUTH, LLEmote::create );
|
|
registerMotion( ANIM_AGENT_EXPRESS_REPULSED, LLEmote::create );
|
|
registerMotion( ANIM_AGENT_EXPRESS_SAD, LLEmote::create );
|
|
registerMotion( ANIM_AGENT_EXPRESS_SHRUG, LLEmote::create );
|
|
registerMotion( ANIM_AGENT_EXPRESS_SMILE, LLEmote::create );
|
|
registerMotion( ANIM_AGENT_EXPRESS_SURPRISE, LLEmote::create );
|
|
registerMotion( ANIM_AGENT_EXPRESS_TONGUE_OUT, LLEmote::create );
|
|
registerMotion( ANIM_AGENT_EXPRESS_TOOTHSMILE, LLEmote::create );
|
|
registerMotion( ANIM_AGENT_EXPRESS_WINK, LLEmote::create );
|
|
registerMotion( ANIM_AGENT_EXPRESS_WORRY, LLEmote::create );
|
|
registerMotion( ANIM_AGENT_FEMALE_RUN_NEW, LLKeyframeWalkMotion::create ); //v2
|
|
registerMotion( ANIM_AGENT_FEMALE_WALK, LLKeyframeWalkMotion::create );
|
|
registerMotion( ANIM_AGENT_FEMALE_WALK_NEW, LLKeyframeWalkMotion::create ); //v2
|
|
registerMotion( ANIM_AGENT_RUN, LLKeyframeWalkMotion::create );
|
|
registerMotion( ANIM_AGENT_RUN_NEW, LLKeyframeWalkMotion::create ); //v2
|
|
registerMotion( ANIM_AGENT_STAND, LLKeyframeStandMotion::create );
|
|
registerMotion( ANIM_AGENT_STAND_1, LLKeyframeStandMotion::create );
|
|
registerMotion( ANIM_AGENT_STAND_2, LLKeyframeStandMotion::create );
|
|
registerMotion( ANIM_AGENT_STAND_3, LLKeyframeStandMotion::create );
|
|
registerMotion( ANIM_AGENT_STAND_4, LLKeyframeStandMotion::create );
|
|
registerMotion( ANIM_AGENT_STANDUP, LLKeyframeFallMotion::create );
|
|
registerMotion( ANIM_AGENT_TURNLEFT, LLKeyframeWalkMotion::create );
|
|
registerMotion( ANIM_AGENT_TURNRIGHT, LLKeyframeWalkMotion::create );
|
|
registerMotion( ANIM_AGENT_WALK, LLKeyframeWalkMotion::create );
|
|
registerMotion( ANIM_AGENT_WALK_NEW, LLKeyframeWalkMotion::create ); //v2
|
|
|
|
// motions without a start/stop bit
|
|
registerMotion( ANIM_AGENT_BODY_NOISE_ID, LLBodyNoiseMotion::create );
|
|
registerMotion( ANIM_AGENT_BREATHE_ROT_ID, LLBreatheMotionRot::create );
|
|
registerMotion( ANIM_AGENT_PHYSICS_MOTION_ID, LLPhysicsMotionController::create );
|
|
registerMotion( ANIM_AGENT_EDITING_ID, LLEditingMotion::create );
|
|
registerMotion( ANIM_AGENT_EYE_ID, LLEyeMotion::create );
|
|
//registerMotion( ANIM_AGENT_FEMALE_WALK, LLKeyframeWalkMotion::create );
|
|
registerMotion( ANIM_AGENT_FLY_ADJUST_ID, LLFlyAdjustMotion::create );
|
|
registerMotion( ANIM_AGENT_HAND_MOTION_ID, LLHandMotion::create );
|
|
registerMotion( ANIM_AGENT_HEAD_ROT_ID, LLHeadRotMotion::create );
|
|
registerMotion( ANIM_AGENT_PELVIS_FIX_ID, LLPelvisFixMotion::create );
|
|
registerMotion( ANIM_AGENT_SIT_FEMALE, LLKeyframeMotion::create );
|
|
registerMotion( ANIM_AGENT_TARGET_ID, LLTargetingMotion::create );
|
|
registerMotion( ANIM_AGENT_WALK_ADJUST_ID, LLWalkAdjustMotion::create );
|
|
}
|
|
|
|
LLAvatarAppearance::initInstance();
|
|
|
|
// preload specific motions here
|
|
createMotion( ANIM_AGENT_CUSTOMIZE);
|
|
createMotion( ANIM_AGENT_CUSTOMIZE_DONE);
|
|
|
|
//VTPause(); // VTune
|
|
|
|
mVoiceVisualizer->setVoiceEnabled( LLVoiceClient::getInstance()->getVoiceEnabled( mID ) );
|
|
|
|
mInitFlags |= 1<<1;
|
|
}
|
|
|
|
// virtual
|
|
LLAvatarJoint* LLVOAvatar::createAvatarJoint()
|
|
{
|
|
return new LLViewerJoint();
|
|
}
|
|
|
|
// virtual
|
|
LLAvatarJoint* LLVOAvatar::createAvatarJoint(S32 joint_num)
|
|
{
|
|
return new LLViewerJoint(joint_num);
|
|
}
|
|
|
|
// virtual
|
|
LLAvatarJointMesh* LLVOAvatar::createAvatarJointMesh()
|
|
{
|
|
return new LLViewerJointMesh();
|
|
}
|
|
|
|
// virtual
|
|
LLTexLayerSet* LLVOAvatar::createTexLayerSet()
|
|
{
|
|
return new LLViewerTexLayerSet(this);
|
|
}
|
|
|
|
const LLVector3 LLVOAvatar::getRenderPosition() const
|
|
{
|
|
|
|
if (mDrawable.isNull() || mDrawable->getGeneration() < 0)
|
|
{
|
|
return getPositionAgent();
|
|
}
|
|
else if (isRoot()/* || !mDrawable->getParent()*/) // Animesh-
|
|
{
|
|
F32 fixup;
|
|
if ( hasPelvisFixup( fixup) )
|
|
{
|
|
//Apply a pelvis fixup (as defined by the avs skin)
|
|
LLVector3 pos = mDrawable->getPositionAgent();
|
|
pos[VZ] += fixup;
|
|
return pos;
|
|
}
|
|
else
|
|
{
|
|
return mDrawable->getPositionAgent();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LLVector4a pos;
|
|
pos.load3(getPosition().mV);
|
|
mDrawable->getParent()->getRenderMatrix().affineTransform(pos,pos);
|
|
return LLVector3(pos.getF32ptr());
|
|
}
|
|
}
|
|
|
|
void LLVOAvatar::updateDrawable(BOOL force_damped)
|
|
{
|
|
clearChanged(SHIFTED);
|
|
}
|
|
|
|
void LLVOAvatar::onShift(const LLVector4a& shift_vector)
|
|
{
|
|
const LLVector3& shift = reinterpret_cast<const LLVector3&>(shift_vector);
|
|
mLastAnimExtents[0] += shift;
|
|
mLastAnimExtents[1] += shift;
|
|
}
|
|
|
|
void LLVOAvatar::updateSpatialExtents(LLVector4a& newMin, LLVector4a &newMax)
|
|
{
|
|
if (mDrawable.isNull())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (mNeedsExtentUpdate)
|
|
{
|
|
calculateSpatialExtents(newMin,newMax);
|
|
mLastAnimExtents[0].set(newMin.getF32ptr());
|
|
mLastAnimExtents[1].set(newMax.getF32ptr());
|
|
mLastAnimBasePos = mPelvisp->getWorldPosition();
|
|
mNeedsExtentUpdate = false;
|
|
}
|
|
else
|
|
{
|
|
LLVector3 new_base_pos = mPelvisp->getWorldPosition();
|
|
LLVector3 shift = new_base_pos-mLastAnimBasePos;
|
|
mLastAnimExtents[0] += shift;
|
|
mLastAnimExtents[1] += shift;
|
|
mLastAnimBasePos = new_base_pos;
|
|
|
|
}
|
|
|
|
if (isImpostor() && !needsImpostorUpdate())
|
|
{
|
|
LLVector3 delta = getRenderPosition() -
|
|
((LLVector3(mDrawable->getPositionGroup().getF32ptr())-mImpostorOffset));
|
|
|
|
newMin.load3( (mLastAnimExtents[0] + delta).mV);
|
|
newMax.load3( (mLastAnimExtents[1] + delta).mV);
|
|
}
|
|
else
|
|
{
|
|
newMin.load3(mLastAnimExtents[0].mV);
|
|
newMax.load3(mLastAnimExtents[1].mV);
|
|
LLVector4a pos_group;
|
|
pos_group.setAdd(newMin,newMax);
|
|
pos_group.mul(0.5f);
|
|
mImpostorOffset = LLVector3(pos_group.getF32ptr())-getRenderPosition();
|
|
mDrawable->setPositionGroup(pos_group);
|
|
}
|
|
}
|
|
|
|
|
|
static LLTrace::BlockTimerStatHandle FTM_AVATAR_EXTENT_UPDATE("Av Upd Extent");
|
|
|
|
void LLVOAvatar::calculateSpatialExtents(LLVector4a& newMin, LLVector4a& newMax)
|
|
{
|
|
|
|
LL_RECORD_BLOCK_TIME(FTM_AVATAR_EXTENT_UPDATE);
|
|
|
|
static const LLCachedControl<bool> control_derender_huge_attachments("DerenderHugeAttachments", true);
|
|
static const LLCachedControl<S32> box_detail("AvatarBoundingBoxComplexity");
|
|
|
|
// FIXME the update_min_max function used below assumes there is a
|
|
// known starting point, but in general there isn't. Ideally the
|
|
// box update logic should be modified to handle the no-point-yet
|
|
// case. For most models, starting with the pelvis is safe though.
|
|
LLVector3 zero_pos;
|
|
LLVector4a pos;
|
|
if (dist_vec(zero_pos, mPelvisp->getWorldPosition())<0.001)
|
|
{
|
|
// Don't use pelvis until av initialized
|
|
pos.load3(getRenderPosition().mV);
|
|
}
|
|
else
|
|
{
|
|
pos.load3(mPelvisp->getWorldPosition().mV);
|
|
}
|
|
newMin = pos;
|
|
newMax = pos;
|
|
|
|
//stretch bounding box by joint positions. Doing this for
|
|
//control avs, where the polymeshes aren't maintained or
|
|
//displayed, can give inaccurate boxes due to joints stuck at (0,0,0).
|
|
if ((box_detail >= 1) && !isControlAvatar())
|
|
{
|
|
for (polymesh_map_t::iterator i = mPolyMeshes.begin(); i != mPolyMeshes.end(); ++i)
|
|
{
|
|
LLPolyMesh* mesh = i->second;
|
|
for (const auto& joint : mesh->mJointRenderData)
|
|
{
|
|
static const LLVector4Logical mask = _mm_load_ps((F32*)&S_V4LOGICAL_MASK_TABLE[3 * 4]);
|
|
LLVector4a trans;
|
|
trans.setSelectWithMask(mask, _mm_setzero_ps(), joint->mWorldMatrix->getRow<3>());
|
|
update_min_max(newMin, newMax, trans);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Pad bounding box for starting joint, plus polymesh if
|
|
// applicable. Subsequent calcs should be accurate enough to not
|
|
// need padding.
|
|
LLVector4a padding(0.25);
|
|
newMin.sub(padding);
|
|
newMax.add(padding);
|
|
|
|
static std::vector<LLViewerObject*> removal;
|
|
|
|
//stretch bounding box by static attachments
|
|
if (box_detail >= 2)
|
|
{
|
|
|
|
float max_attachment_span = get_default_max_prim_scale() * 5.0f;
|
|
#if SLOW_ATTACHMENT_LIST
|
|
for (attachment_map_t::iterator iter = mAttachmentPoints.begin();
|
|
iter != mAttachmentPoints.end();
|
|
++iter)
|
|
{
|
|
LLViewerJointAttachment* attachment = iter->second;
|
|
|
|
if (attachment->getValid())
|
|
{
|
|
for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin();
|
|
attachment_iter != attachment->mAttachedObjects.end();
|
|
++attachment_iter)
|
|
{
|
|
// Don't we need to look at children of attached_object as well?
|
|
const LLViewerObject* attached_object = (*attachment_iter);
|
|
#else
|
|
for(auto& iter : mAttachedObjectsVector)
|
|
{{{
|
|
LLViewerJointAttachment* attachment = iter.second;
|
|
if (!attachment->getValid())
|
|
continue;
|
|
const LLViewerObject* attached_object = iter.first;
|
|
#endif
|
|
if (attached_object && !attached_object->isHUDAttachment())
|
|
{
|
|
const LLVOVolume *vol = dynamic_cast<const LLVOVolume*>(attached_object);
|
|
if (vol && vol->isAnimatedObject())
|
|
{
|
|
// Animated objects already have a bounding box in their control av, use that.
|
|
// Could lag by a frame if there's no guarantee on order of processing for avatars.
|
|
LLControlAvatar *cav = vol->getControlAvatar();
|
|
if (cav)
|
|
{
|
|
LLVector4a cav_min;
|
|
cav_min.load3(cav->mLastAnimExtents[0].mV);
|
|
LLVector4a cav_max;
|
|
cav_max.load3(cav->mLastAnimExtents[1].mV);
|
|
update_min_max(newMin,newMax,cav_min);
|
|
update_min_max(newMin,newMax,cav_max);
|
|
continue;
|
|
}
|
|
}
|
|
if (vol && vol->isRiggedMesh())
|
|
{
|
|
continue;
|
|
}
|
|
LLDrawable* drawable = attached_object->mDrawable;
|
|
if (drawable && !drawable->isState(LLDrawable::RIGGED))
|
|
{
|
|
LLSpatialBridge* bridge = drawable->getSpatialBridge();
|
|
if (bridge)
|
|
{
|
|
const LLVector4a* ext = bridge->getSpatialExtents();
|
|
LLVector4a distance;
|
|
distance.setSub(ext[1], ext[0]);
|
|
LLVector4a max_span(max_attachment_span);
|
|
|
|
S32 lt = distance.lessThan(max_span).getGatheredBits() & 0x7;
|
|
|
|
// Only add the prim to spatial extents calculations if it isn't a megaprim.
|
|
// max_attachment_span calculated at the start of the function
|
|
// (currently 5 times our max prim size)
|
|
if (lt == 0x7)
|
|
{
|
|
update_min_max(newMin,newMax,ext[0]);
|
|
update_min_max(newMin,newMax,ext[1]);
|
|
}
|
|
else if(control_derender_huge_attachments)
|
|
{
|
|
removal.push_back((LLViewerObject *)attached_object);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(removal.size() > 0)
|
|
{
|
|
for(std::vector<LLViewerObject*>::iterator removal_iter = removal.begin(); removal_iter != removal.end(); ++removal_iter)
|
|
{
|
|
LLViewerObject *object_to_remove = *removal_iter;
|
|
gObjectList.killObject(object_to_remove);
|
|
}
|
|
removal.clear();
|
|
}
|
|
|
|
// Stretch bounding box by rigged mesh joint boxes
|
|
if (box_detail>=3 && gMeshRepo.meshRezEnabled())
|
|
{
|
|
updateRiggingInfo();
|
|
for (S32 joint_num = 0; joint_num < LL_CHARACTER_MAX_ANIMATED_JOINTS; joint_num++)
|
|
{
|
|
LLJoint *joint = getJoint(joint_num);
|
|
LLJointRiggingInfo *rig_info = NULL;
|
|
if (joint_num < mJointRiggingInfoTab.size())
|
|
{
|
|
rig_info = &mJointRiggingInfoTab[joint_num];
|
|
}
|
|
|
|
if (joint && rig_info && rig_info->isRiggedTo())
|
|
{
|
|
LLViewerJointAttachment *as_joint_attach = dynamic_cast<LLViewerJointAttachment*>(joint);
|
|
if (as_joint_attach && as_joint_attach->getIsHUDAttachment())
|
|
{
|
|
// Ignore bounding box of HUD joints
|
|
continue;
|
|
}
|
|
const LLMatrix4a& mat = joint->getWorldMatrix();
|
|
LLVector4a new_extents[2];
|
|
matMulBoundBox(mat, rig_info->getRiggedExtents(), new_extents);
|
|
update_min_max(newMin, newMax, new_extents[0]);
|
|
update_min_max(newMin, newMax, new_extents[1]);
|
|
//if (isSelf())
|
|
//{
|
|
// LL_INFOS() << joint->getName() << " extents " << new_extents[0] << "," << new_extents[1] << LL_ENDL;
|
|
// LL_INFOS() << joint->getName() << " av box is " << newMin << "," << newMax << LL_ENDL;
|
|
//}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update pixel area
|
|
LLVector4a center, size;
|
|
center.setAdd(newMin, newMax);
|
|
center.mul(0.5f);
|
|
|
|
size.setSub(newMax,newMin);
|
|
size.mul(0.5f);
|
|
|
|
mPixelArea = LLPipeline::calcPixelArea(center, size, *LLViewerCamera::getInstance());
|
|
}
|
|
|
|
void render_sphere_and_line(const LLVector3& begin_pos, const LLVector3& end_pos, F32 sphere_scale, const LLVector3& occ_color, const LLVector3& visible_color)
|
|
{
|
|
// Unoccluded bone portions
|
|
LLGLDepthTest normal_depth(GL_TRUE);
|
|
|
|
// Draw line segment for unoccluded joint
|
|
gGL.diffuseColor3f(visible_color[0], visible_color[1], visible_color[2]);
|
|
|
|
gGL.begin(LLRender::LINES);
|
|
gGL.vertex3fv(begin_pos.mV);
|
|
gGL.vertex3fv(end_pos.mV);
|
|
gGL.end();
|
|
|
|
|
|
// Draw sphere representing joint pos
|
|
gGL.pushMatrix();
|
|
gGL.scalef(sphere_scale, sphere_scale, sphere_scale);
|
|
gSphere.renderGGL();
|
|
gGL.popMatrix();
|
|
|
|
LLGLDepthTest depth_under(GL_TRUE, GL_FALSE, GL_GREATER);
|
|
|
|
// Occluded bone portions
|
|
gGL.diffuseColor3f(occ_color[0], occ_color[1], occ_color[2]);
|
|
|
|
gGL.begin(LLRender::LINES);
|
|
gGL.vertex3fv(begin_pos.mV);
|
|
gGL.vertex3fv(end_pos.mV);
|
|
gGL.end();
|
|
|
|
// Draw sphere representing joint pos
|
|
gGL.pushMatrix();
|
|
gGL.scalef(sphere_scale, sphere_scale, sphere_scale);
|
|
gSphere.renderGGL();
|
|
gGL.popMatrix();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// renderCollisionVolumes()
|
|
//-----------------------------------------------------------------------------
|
|
void LLVOAvatar::renderCollisionVolumes()
|
|
{
|
|
std::ostringstream ostr;
|
|
LLGLDepthTest gls_depth(GL_FALSE);
|
|
for (size_t i = 0; i < mCollisionVolumes.size(); i++)
|
|
{
|
|
ostr << mCollisionVolumes[i]->getName() << ", ";
|
|
LLAvatarJointCollisionVolume& collision_volume = *mCollisionVolumes[i];
|
|
|
|
collision_volume.updateWorldMatrix();
|
|
|
|
gGL.pushMatrix();
|
|
gGL.multMatrix( collision_volume.getXform()->getWorldMatrix() );
|
|
|
|
LLVector3 begin_pos(0,0,0);
|
|
LLVector3 end_pos(collision_volume.getEnd());
|
|
static F32 sphere_scale = 1.0f;
|
|
static F32 center_dot_scale = 0.05f;
|
|
|
|
static LLVector3 BLUE(0.0f, 0.0f, 1.0f);
|
|
static LLVector3 PASTEL_BLUE(0.5f, 0.5f, 1.0f);
|
|
static LLVector3 RED(1.0f, 0.0f, 0.0f);
|
|
static LLVector3 PASTEL_RED(1.0f, 0.5f, 0.5f);
|
|
static LLVector3 WHITE(1.0f, 1.0f, 1.0f);
|
|
|
|
|
|
LLVector3 cv_color_occluded;
|
|
LLVector3 cv_color_visible;
|
|
LLVector3 dot_color_occluded(WHITE);
|
|
LLVector3 dot_color_visible(WHITE);
|
|
if (isControlAvatar())
|
|
{
|
|
cv_color_occluded = RED;
|
|
cv_color_visible = PASTEL_RED;
|
|
}
|
|
else
|
|
{
|
|
cv_color_occluded = BLUE;
|
|
cv_color_visible = PASTEL_BLUE;
|
|
}
|
|
render_sphere_and_line(begin_pos, end_pos, sphere_scale, cv_color_occluded, cv_color_visible);
|
|
render_sphere_and_line(begin_pos, end_pos, center_dot_scale, dot_color_occluded, dot_color_visible);
|
|
|
|
gGL.popMatrix();
|
|
}
|
|
|
|
if (mNameText.notNull())
|
|
{
|
|
LLVector4a unused;
|
|
|
|
mNameText->lineSegmentIntersect(unused, unused, unused, TRUE);
|
|
}
|
|
|
|
mDebugText.clear();
|
|
addDebugText(ostr.str());
|
|
}
|
|
|
|
void LLVOAvatar::renderBones()
|
|
{
|
|
LLGLEnable<GL_BLEND> blend;
|
|
|
|
avatar_joint_list_t::iterator iter = mSkeleton.begin();
|
|
avatar_joint_list_t::iterator end = mSkeleton.end();
|
|
|
|
// For bones with position overrides defined
|
|
static LLVector3 OVERRIDE_COLOR_OCCLUDED(1.0f, 0.0f, 0.0f);
|
|
static LLVector3 OVERRIDE_COLOR_VISIBLE(0.5f, 0.5f, 0.5f);
|
|
// For bones which are rigged to by at least one attachment
|
|
static LLVector3 RIGGED_COLOR_OCCLUDED(0.0f, 1.0f, 1.0f);
|
|
static LLVector3 RIGGED_COLOR_VISIBLE(0.5f, 0.5f, 0.5f);
|
|
// For bones not otherwise colored
|
|
static LLVector3 OTHER_COLOR_OCCLUDED(0.0f, 1.0f, 0.0f);
|
|
static LLVector3 OTHER_COLOR_VISIBLE(0.5f, 0.5f, 0.5f);
|
|
|
|
static F32 SPHERE_SCALEF = 0.001f;
|
|
|
|
for (; iter != end; ++iter)
|
|
{
|
|
LLJoint* jointp = *iter;
|
|
if (!jointp)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
jointp->updateWorldMatrix();
|
|
|
|
LLVector3 occ_color, visible_color;
|
|
|
|
LLVector3 pos;
|
|
LLUUID mesh_id;
|
|
if (jointp->hasAttachmentPosOverride(pos,mesh_id))
|
|
{
|
|
occ_color = OVERRIDE_COLOR_OCCLUDED;
|
|
visible_color = OVERRIDE_COLOR_VISIBLE;
|
|
}
|
|
else
|
|
{
|
|
if (jointIsRiggedTo(jointp))
|
|
{
|
|
occ_color = RIGGED_COLOR_OCCLUDED;
|
|
visible_color = RIGGED_COLOR_VISIBLE;
|
|
}
|
|
else
|
|
{
|
|
occ_color = OTHER_COLOR_OCCLUDED;
|
|
visible_color = OTHER_COLOR_VISIBLE;
|
|
}
|
|
}
|
|
LLVector3 begin_pos(0,0,0);
|
|
LLVector3 end_pos(jointp->getEnd());
|
|
|
|
F32 sphere_scale = SPHERE_SCALEF;
|
|
|
|
gGL.pushMatrix();
|
|
gGL.multMatrix( jointp->getXform()->getWorldMatrix() );
|
|
|
|
render_sphere_and_line(begin_pos, end_pos, sphere_scale, occ_color, visible_color);
|
|
|
|
gGL.popMatrix();
|
|
}
|
|
}
|
|
|
|
void LLVOAvatar::renderJoints()
|
|
{
|
|
std::ostringstream ostr;
|
|
std::ostringstream nullstr;
|
|
|
|
for (joint_map_t::iterator iter = mJointMap.begin(); iter != mJointMap.end(); ++iter)
|
|
{
|
|
LLJoint* jointp = iter->second;
|
|
if (!jointp)
|
|
{
|
|
nullstr << iter->first << " is NULL" << std::endl;
|
|
continue;
|
|
}
|
|
|
|
ostr << jointp->getName() << ", ";
|
|
|
|
jointp->updateWorldMatrix();
|
|
|
|
gGL.pushMatrix();
|
|
gGL.multMatrix(jointp->getXform()->getWorldMatrix());
|
|
|
|
gGL.diffuseColor3f( 1.f, 0.f, 1.f );
|
|
|
|
gGL.begin(LLRender::LINES);
|
|
|
|
LLVector3 v[] =
|
|
{
|
|
LLVector3(1,0,0),
|
|
LLVector3(-1,0,0),
|
|
LLVector3(0,1,0),
|
|
LLVector3(0,-1,0),
|
|
|
|
LLVector3(0,0,-1),
|
|
LLVector3(0,0,1),
|
|
};
|
|
|
|
//sides
|
|
gGL.vertex3fv(v[0].mV);
|
|
gGL.vertex3fv(v[2].mV);
|
|
|
|
gGL.vertex3fv(v[0].mV);
|
|
gGL.vertex3fv(v[3].mV);
|
|
|
|
gGL.vertex3fv(v[1].mV);
|
|
gGL.vertex3fv(v[2].mV);
|
|
|
|
gGL.vertex3fv(v[1].mV);
|
|
gGL.vertex3fv(v[3].mV);
|
|
|
|
|
|
//top
|
|
gGL.vertex3fv(v[0].mV);
|
|
gGL.vertex3fv(v[4].mV);
|
|
|
|
gGL.vertex3fv(v[1].mV);
|
|
gGL.vertex3fv(v[4].mV);
|
|
|
|
gGL.vertex3fv(v[2].mV);
|
|
gGL.vertex3fv(v[4].mV);
|
|
|
|
gGL.vertex3fv(v[3].mV);
|
|
gGL.vertex3fv(v[4].mV);
|
|
|
|
|
|
//bottom
|
|
gGL.vertex3fv(v[0].mV);
|
|
gGL.vertex3fv(v[5].mV);
|
|
|
|
gGL.vertex3fv(v[1].mV);
|
|
gGL.vertex3fv(v[5].mV);
|
|
|
|
gGL.vertex3fv(v[2].mV);
|
|
gGL.vertex3fv(v[5].mV);
|
|
|
|
gGL.vertex3fv(v[3].mV);
|
|
gGL.vertex3fv(v[5].mV);
|
|
|
|
gGL.end();
|
|
|
|
gGL.popMatrix();
|
|
}
|
|
|
|
mDebugText.clear();
|
|
addDebugText(ostr.str());
|
|
addDebugText(nullstr.str());
|
|
}
|
|
|
|
|
|
BOOL LLVOAvatar::lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end,
|
|
S32 face,
|
|
BOOL pick_transparent,
|
|
BOOL pick_rigged,
|
|
S32* face_hit,
|
|
LLVector4a* intersection,
|
|
LLVector2* tex_coord,
|
|
LLVector4a* normal,
|
|
LLVector4a* tangent)
|
|
{
|
|
if ((isSelf() && !gAgent.needsRenderAvatar()) || !LLPipeline::sPickAvatar)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
if (isControlAvatar())
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (lineSegmentBoundingBox(start, end))
|
|
{
|
|
for (S32 i = 0; i < (S32)mCollisionVolumes.size(); ++i)
|
|
{
|
|
mCollisionVolumes[i]->updateWorldMatrix();
|
|
|
|
const LLMatrix4a& mat = mCollisionVolumes[i]->getXform()->getWorldMatrix();
|
|
LLMatrix4a inverse = mat;
|
|
inverse.invert();
|
|
LLMatrix4a norm_mat = inverse;
|
|
norm_mat.transpose();
|
|
|
|
|
|
LLVector4a p1, p2;
|
|
inverse.affineTransform(start,p1); //Might need to use perspectiveTransform here.
|
|
inverse.affineTransform(end,p2);
|
|
|
|
LLVector3 position;
|
|
LLVector3 norm;
|
|
|
|
if (linesegment_sphere(LLVector3(p1.getF32ptr()), LLVector3(p2.getF32ptr()), LLVector3(0,0,0), 1.f, position, norm))
|
|
{
|
|
if (intersection)
|
|
{
|
|
LLVector4a res_pos;
|
|
res_pos.load3(position.mV);
|
|
mat.affineTransform(res_pos,res_pos);
|
|
*intersection = res_pos;
|
|
}
|
|
|
|
if (normal)
|
|
{
|
|
LLVector4a res_norm;
|
|
res_norm.load3(norm.mV);
|
|
res_norm.normalize3fast();
|
|
norm_mat.perspectiveTransform(res_norm,res_norm);
|
|
*normal = res_norm;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
if (isSelf())
|
|
{
|
|
#if SLOW_ATTACHMENT_LIST
|
|
for (attachment_map_t::iterator iter = mAttachmentPoints.begin();
|
|
iter != mAttachmentPoints.end();
|
|
++iter)
|
|
{
|
|
LLViewerJointAttachment* attachment = iter->second;
|
|
|
|
for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin();
|
|
attachment_iter != attachment->mAttachedObjects.end();
|
|
++attachment_iter)
|
|
{
|
|
LLViewerObject* attached_object = (*attachment_iter);
|
|
#else
|
|
for(auto& iter : mAttachedObjectsVector)
|
|
{{
|
|
const LLViewerJointAttachment* attachment = iter.second;
|
|
const LLViewerObject* attached_object = iter.first;
|
|
#endif
|
|
|
|
if (attached_object && !attached_object->isDead() && attachment->getValid())
|
|
{
|
|
LLDrawable* drawable = attached_object->mDrawable;
|
|
if (drawable->isState(LLDrawable::RIGGED))
|
|
{ //regenerate octree for rigged attachment
|
|
gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_RIGGED, TRUE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
LLVector4a position;
|
|
if (mNameText.notNull() && mNameText->lineSegmentIntersect(start, end, position))
|
|
{
|
|
if (intersection)
|
|
{
|
|
*intersection = position;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
LLViewerObject* LLVOAvatar::lineSegmentIntersectRiggedAttachments(const LLVector4a& start, const LLVector4a& end,
|
|
S32 face,
|
|
BOOL pick_transparent,
|
|
BOOL pick_rigged,
|
|
S32* face_hit,
|
|
LLVector4a* intersection,
|
|
LLVector2* tex_coord,
|
|
LLVector4a* normal,
|
|
LLVector4a* tangent)
|
|
{
|
|
if (isSelf() && !gAgent.needsRenderAvatar())
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
LLViewerObject* hit = NULL;
|
|
|
|
if (lineSegmentBoundingBox(start, end))
|
|
{
|
|
LLVector4a local_end = end;
|
|
LLVector4a local_intersection;
|
|
|
|
#if SLOW_ATTACHMENT_LIST
|
|
for (attachment_map_t::iterator iter = mAttachmentPoints.begin();
|
|
iter != mAttachmentPoints.end();
|
|
++iter)
|
|
{
|
|
LLViewerJointAttachment* attachment = iter->second;
|
|
|
|
for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin();
|
|
attachment_iter != attachment->mAttachedObjects.end();
|
|
++attachment_iter)
|
|
{
|
|
LLViewerObject* attached_object = (*attachment_iter);
|
|
#else
|
|
for(auto& iter : mAttachedObjectsVector)
|
|
{{
|
|
LLViewerObject* attached_object = iter.first;
|
|
#endif
|
|
|
|
if (attached_object->lineSegmentIntersect(start, local_end, face, pick_transparent, pick_rigged, face_hit, &local_intersection, tex_coord, normal, tangent))
|
|
{
|
|
local_end = local_intersection;
|
|
if (intersection)
|
|
{
|
|
*intersection = local_intersection;
|
|
}
|
|
|
|
hit = attached_object;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return hit;
|
|
}
|
|
|
|
LLVOAvatar* LLVOAvatar::asAvatar()
|
|
{
|
|
return this;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// LLVOAvatar::startDefaultMotions()
|
|
//-----------------------------------------------------------------------------
|
|
void LLVOAvatar::startDefaultMotions()
|
|
{
|
|
//-------------------------------------------------------------------------
|
|
// start default motions
|
|
//-------------------------------------------------------------------------
|
|
startMotion( ANIM_AGENT_HEAD_ROT );
|
|
startMotion( ANIM_AGENT_EYE );
|
|
startMotion( ANIM_AGENT_BODY_NOISE );
|
|
startMotion( ANIM_AGENT_BREATHE_ROT );
|
|
startMotion( ANIM_AGENT_PHYSICS_MOTION );
|
|
startMotion( ANIM_AGENT_HAND_MOTION );
|
|
startMotion( ANIM_AGENT_PELVIS_FIX );
|
|
|
|
//-------------------------------------------------------------------------
|
|
// restart any currently active motions
|
|
//-------------------------------------------------------------------------
|
|
processAnimationStateChanges();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// LLVOAvatar::buildCharacter()
|
|
// Deferred initialization and rebuild of the avatar.
|
|
//-----------------------------------------------------------------------------
|
|
// virtual
|
|
void LLVOAvatar::buildCharacter()
|
|
{
|
|
LLAvatarAppearance::buildCharacter();
|
|
|
|
// Not done building yet; more to do.
|
|
mIsBuilt = FALSE;
|
|
|
|
//-------------------------------------------------------------------------
|
|
// set head offset from pelvis
|
|
//-------------------------------------------------------------------------
|
|
updateHeadOffset();
|
|
|
|
//-------------------------------------------------------------------------
|
|
// initialize lip sync morph pointers
|
|
//-------------------------------------------------------------------------
|
|
mOohMorph = getVisualParam( "Lipsync_Ooh" );
|
|
mAahMorph = getVisualParam( "Lipsync_Aah" );
|
|
|
|
// If we don't have the Ooh morph, use the Kiss morph
|
|
if (!mOohMorph)
|
|
{
|
|
LL_WARNS() << "Missing 'Ooh' morph for lipsync, using fallback." << LL_ENDL;
|
|
mOohMorph = getVisualParam( "Express_Kiss" );
|
|
}
|
|
|
|
// If we don't have the Aah morph, use the Open Mouth morph
|
|
if (!mAahMorph)
|
|
{
|
|
LL_WARNS() << "Missing 'Aah' morph for lipsync, using fallback." << LL_ENDL;
|
|
mAahMorph = getVisualParam( "Express_Open_Mouth" );
|
|
}
|
|
|
|
// Currently disabled for control avatars (animated objects), enabled for all others.
|
|
if (mEnableDefaultMotions)
|
|
{
|
|
startDefaultMotions();
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// restart any currently active motions
|
|
//-------------------------------------------------------------------------
|
|
processAnimationStateChanges();
|
|
|
|
mIsBuilt = TRUE;
|
|
stop_glerror();
|
|
|
|
mMeshValid = TRUE;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// resetVisualParams()
|
|
//-----------------------------------------------------------------------------
|
|
void LLVOAvatar::resetVisualParams()
|
|
{
|
|
// Skeletal params
|
|
{
|
|
LLAvatarXmlInfo::skeletal_distortion_info_list_t::iterator iter;
|
|
for (iter = sAvatarXmlInfo->mSkeletalDistortionInfoList.begin();
|
|
iter != sAvatarXmlInfo->mSkeletalDistortionInfoList.end();
|
|
++iter)
|
|
{
|
|
LLPolySkeletalDistortionInfo *info = (LLPolySkeletalDistortionInfo*)*iter;
|
|
LLPolySkeletalDistortion *param = dynamic_cast<LLPolySkeletalDistortion*>(getVisualParam(info->getID()));
|
|
*param = LLPolySkeletalDistortion(this);
|
|
llassert(param);
|
|
if (!param->setInfo(info))
|
|
{
|
|
llassert(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Driver parameters
|
|
for (LLAvatarXmlInfo::driver_info_list_t::iterator iter = sAvatarXmlInfo->mDriverInfoList.begin();
|
|
iter != sAvatarXmlInfo->mDriverInfoList.end();
|
|
++iter)
|
|
{
|
|
LLDriverParamInfo *info = *iter;
|
|
LLDriverParam *param = dynamic_cast<LLDriverParam*>(getVisualParam(info->getID()));
|
|
LLDriverParam::entry_list_t driven_list = param->getDrivenList();
|
|
*param = LLDriverParam(this);
|
|
llassert(param);
|
|
if (!param->setInfo(info))
|
|
{
|
|
llassert(false);
|
|
}
|
|
param->setDrivenList(driven_list);
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// resetSkeleton()
|
|
//-----------------------------------------------------------------------------
|
|
void LLVOAvatar::resetSkeleton(bool reset_animations)
|
|
{
|
|
LL_DEBUGS("Avatar") << avString() << " reset starts" << LL_ENDL;
|
|
if (!isControlAvatar() && !mLastProcessedAppearance)
|
|
{
|
|
LL_WARNS() << "Can't reset avatar; no appearance message has been received yet." << LL_ENDL;
|
|
return;
|
|
}
|
|
|
|
// Save mPelvis state
|
|
//LLVector3 pelvis_pos = getJoint("mPelvis")->getPosition();
|
|
//LLQuaternion pelvis_rot = getJoint("mPelvis")->getRotation();
|
|
|
|
// Clear all attachment pos and scale overrides
|
|
clearAttachmentOverrides();
|
|
|
|
// Note that we call buildSkeleton twice in this function. The first time is
|
|
// just to get the right scale for the collision volumes, because
|
|
// this will be used in setting the mJointScales for the
|
|
// LLPolySkeletalDistortions of which the CVs are children.
|
|
if( !buildSkeleton(sAvatarSkeletonInfo) )
|
|
{
|
|
LL_ERRS() << "Error resetting skeleton" << LL_ENDL;
|
|
}
|
|
|
|
// Reset some params to default state, without propagating changes downstream.
|
|
resetVisualParams();
|
|
|
|
// Now we have to reset the skeleton again, because its state
|
|
// got clobbered by the resetVisualParams() calls
|
|
// above.
|
|
if( !buildSkeleton(sAvatarSkeletonInfo) )
|
|
{
|
|
LL_ERRS() << "Error resetting skeleton" << LL_ENDL;
|
|
}
|
|
|
|
// Reset attachment points (buildSkeleton only does bones and CVs)
|
|
bool ignore_hud_joints = true;
|
|
initAttachmentPoints(ignore_hud_joints);
|
|
|
|
// Fix up collision volumes
|
|
for (LLVisualParam *param = getFirstVisualParam();
|
|
param;
|
|
param = getNextVisualParam())
|
|
{
|
|
LLPolyMorphTarget *poly_morph = dynamic_cast<LLPolyMorphTarget*>(param);
|
|
if (poly_morph)
|
|
{
|
|
// This is a kludgy way to correct for the fact that the
|
|
// collision volumes have been reset out from under the
|
|
// poly morph sliders.
|
|
F32 delta_weight = poly_morph->getLastWeight() - poly_morph->getDefaultWeight();
|
|
poly_morph->applyVolumeChanges(delta_weight);
|
|
}
|
|
}
|
|
|
|
// Reset tweakable params to preserved state
|
|
if (mLastProcessedAppearance)
|
|
{
|
|
bool slam_params = true;
|
|
applyParsedAppearanceMessage(*mLastProcessedAppearance, slam_params);
|
|
}
|
|
updateVisualParams();
|
|
|
|
// Restore attachment pos overrides
|
|
updateAttachmentOverrides();
|
|
|
|
// Animations
|
|
if (reset_animations)
|
|
{
|
|
if (isSelf())
|
|
{
|
|
// This is equivalent to "Stop Animating Me". Will reset
|
|
// all animations and propagate the changes to other
|
|
// viewers.
|
|
gAgent.stopCurrentAnimations();
|
|
}
|
|
else
|
|
{
|
|
// Local viewer-side reset for non-self avatars.
|
|
resetAnimations();
|
|
}
|
|
}
|
|
|
|
LL_DEBUGS("Avatar") << avString() << " reset ends" << LL_ENDL;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// releaseMeshData()
|
|
//-----------------------------------------------------------------------------
|
|
void LLVOAvatar::releaseMeshData()
|
|
{
|
|
if (sInstances.size() < AVATAR_RELEASE_THRESHOLD || isUIAvatar())
|
|
{
|
|
return;
|
|
}
|
|
|
|
// cleanup mesh data
|
|
for (avatar_joint_list_t::iterator iter = mMeshLOD.begin();
|
|
iter != mMeshLOD.end();
|
|
++iter)
|
|
{
|
|
LLAvatarJoint* joint = (*iter);
|
|
joint->setValid(FALSE, TRUE);
|
|
}
|
|
|
|
//cleanup data
|
|
if (mDrawable.notNull())
|
|
{
|
|
LLFace* facep = mDrawable->getFace(0);
|
|
if (facep)
|
|
{
|
|
facep->setSize(0, 0);
|
|
for(S32 i = mNumInitFaces ; i < mDrawable->getNumFaces(); i++)
|
|
{
|
|
facep = mDrawable->getFace(i);
|
|
if (facep)
|
|
{
|
|
facep->setSize(0, 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (attachment_map_t::iterator iter = mAttachmentPoints.begin();
|
|
iter != mAttachmentPoints.end();
|
|
++iter)
|
|
{
|
|
LLViewerJointAttachment* attachment = iter->second;
|
|
if (!attachment->getIsHUDAttachment())
|
|
{
|
|
attachment->setAttachmentVisibility(FALSE);
|
|
}
|
|
}
|
|
mMeshValid = FALSE;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// restoreMeshData()
|
|
//-----------------------------------------------------------------------------
|
|
// virtual
|
|
void LLVOAvatar::restoreMeshData()
|
|
{
|
|
llassert(!isSelf());
|
|
if (mDrawable.isNull())
|
|
{
|
|
return;
|
|
}
|
|
|
|
//LL_INFOS() << "Restoring" << LL_ENDL;
|
|
mMeshValid = TRUE;
|
|
updateJointLODs();
|
|
|
|
for (attachment_map_t::iterator iter = mAttachmentPoints.begin();
|
|
iter != mAttachmentPoints.end();
|
|
++iter)
|
|
{
|
|
LLViewerJointAttachment* attachment = iter->second;
|
|
if (!attachment->getIsHUDAttachment())
|
|
{
|
|
attachment->setAttachmentVisibility(TRUE);
|
|
}
|
|
}
|
|
|
|
// force mesh update as LOD might not have changed to trigger this
|
|
gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_GEOMETRY, TRUE);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// updateMeshData()
|
|
//-----------------------------------------------------------------------------
|
|
void LLVOAvatar::updateMeshData()
|
|
{
|
|
if (mDrawable.notNull())
|
|
{
|
|
stop_glerror();
|
|
|
|
S32 f_num = 0 ;
|
|
const U32 VERTEX_NUMBER_THRESHOLD = 128 ;//small number of this means each part of an avatar has its own vertex buffer.
|
|
const S32 num_parts = mMeshLOD.size();
|
|
|
|
// this order is determined by number of LODS
|
|
// if a mesh earlier in this list changed LODs while a later mesh doesn't,
|
|
// the later mesh's index offset will be inaccurate
|
|
for(S32 part_index = 0 ; part_index < num_parts ;)
|
|
{
|
|
S32 j = part_index ;
|
|
U32 last_v_num = 0, num_vertices = 0 ;
|
|
U32 last_i_num = 0, num_indices = 0 ;
|
|
|
|
while(part_index < num_parts && num_vertices < VERTEX_NUMBER_THRESHOLD)
|
|
{
|
|
last_v_num = num_vertices ;
|
|
last_i_num = num_indices ;
|
|
|
|
LLViewerJoint* part_mesh = getViewerJoint(part_index++);
|
|
if (part_mesh)
|
|
{
|
|
part_mesh->updateFaceSizes(num_vertices, num_indices, mAdjustedPixelArea);
|
|
}
|
|
}
|
|
if(num_vertices < 1)//skip empty meshes
|
|
{
|
|
continue ;
|
|
}
|
|
if(last_v_num > 0)//put the last inserted part into next vertex buffer.
|
|
{
|
|
num_vertices = last_v_num ;
|
|
num_indices = last_i_num ;
|
|
part_index-- ;
|
|
}
|
|
|
|
LLFace* facep = NULL;
|
|
if(f_num < mDrawable->getNumFaces())
|
|
{
|
|
facep = mDrawable->getFace(f_num);
|
|
}
|
|
else
|
|
{
|
|
facep = mDrawable->getFace(0);
|
|
if (facep)
|
|
{
|
|
facep = mDrawable->addFace(facep->getPool(), facep->getTexture()) ;
|
|
}
|
|
}
|
|
if (!facep) continue;
|
|
|
|
// resize immediately
|
|
facep->setSize(num_vertices, num_indices);
|
|
|
|
bool terse_update = false;
|
|
|
|
facep->setGeomIndex(0);
|
|
facep->setIndicesIndex(0);
|
|
|
|
LLVertexBuffer* buff = facep->getVertexBuffer();
|
|
if(!facep->getVertexBuffer())
|
|
{
|
|
buff = new LLVertexBufferAvatar();
|
|
buff->allocateBuffer(num_vertices, num_indices, TRUE);
|
|
facep->setVertexBuffer(buff);
|
|
}
|
|
else
|
|
{
|
|
if (buff->getNumIndices() == num_indices &&
|
|
buff->getNumVerts() == num_vertices)
|
|
{
|
|
terse_update = true;
|
|
}
|
|
else
|
|
{
|
|
buff->resizeBuffer(num_vertices, num_indices);
|
|
}
|
|
}
|
|
|
|
|
|
// This is a hack! Avatars have their own pool, so we are detecting
|
|
// the case of more than one avatar in the pool (thus > 0 instead of >= 0)
|
|
if (facep->getGeomIndex() > 0)
|
|
{
|
|
LL_ERRS() << "non-zero geom index: " << facep->getGeomIndex() << " in LLVOAvatar::restoreMeshData" << LL_ENDL;
|
|
}
|
|
|
|
for(S32 k = j ; k < part_index ; k++)
|
|
{
|
|
bool rigid = false;
|
|
if (k == MESH_ID_EYEBALL_LEFT ||
|
|
k == MESH_ID_EYEBALL_RIGHT)
|
|
{ //eyeballs can't have terse updates since they're never rendered with
|
|
//the hardware skinning shader
|
|
rigid = true;
|
|
}
|
|
|
|
LLViewerJoint* mesh = getViewerJoint(k);
|
|
if (mesh)
|
|
{
|
|
mesh->updateFaceData(facep, mAdjustedPixelArea, k == MESH_ID_HAIR, terse_update && !rigid);
|
|
}
|
|
}
|
|
|
|
stop_glerror();
|
|
buff->flush();
|
|
|
|
if(!f_num)
|
|
{
|
|
f_num += mNumInitFaces ;
|
|
}
|
|
else
|
|
{
|
|
f_num++ ;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
|
|
//------------------------------------------------------------------------
|
|
// LLVOAvatar::processUpdateMessage()
|
|
//------------------------------------------------------------------------
|
|
U32 LLVOAvatar::processUpdateMessage(LLMessageSystem *mesgsys,
|
|
void **user_data,
|
|
U32 block_num, const EObjectUpdateType update_type,
|
|
LLDataPacker *dp)
|
|
{
|
|
const BOOL has_name = !getNVPair("FirstName");
|
|
|
|
// Do base class updates...
|
|
U32 retval = LLViewerObject::processUpdateMessage(mesgsys, user_data, block_num, update_type, dp);
|
|
|
|
// Print out arrival information once we have name of avatar.
|
|
if (has_name && getNVPair("FirstName"))
|
|
{
|
|
mDebugExistenceTimer.reset();
|
|
debugAvatarRezTime("AvatarRezArrivedNotification","avatar arrived");
|
|
}
|
|
|
|
if(retval & LLViewerObject::INVALID_UPDATE)
|
|
{
|
|
if(isSelf())
|
|
{
|
|
//tell sim to cancel this update
|
|
gAgent.teleportViaLocation(gAgent.getPositionGlobal());
|
|
}
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
LLViewerFetchedTexture *LLVOAvatar::getBakedTextureImage(const U8 te, const LLUUID& uuid)
|
|
{
|
|
LLViewerFetchedTexture *result = NULL;
|
|
|
|
if (uuid == IMG_DEFAULT_AVATAR ||
|
|
uuid == IMG_DEFAULT ||
|
|
uuid == IMG_INVISIBLE)
|
|
{
|
|
// Should already exist, don't need to find it on sim or baked-texture host.
|
|
result = gTextureList.findImage(uuid, TEX_LIST_STANDARD);
|
|
}
|
|
|
|
if (!result)
|
|
{
|
|
const std::string url = getImageURL(te,uuid);
|
|
|
|
if (!url.empty())
|
|
{
|
|
LL_DEBUGS("Avatar") << avString() << "get server-bake image from URL " << url << LL_ENDL;
|
|
result = LLViewerTextureManager::getFetchedTextureFromUrl(
|
|
url, FTT_SERVER_BAKE, TRUE, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE, 0, 0, uuid);
|
|
if (result->isMissingAsset())
|
|
{
|
|
result->setIsMissingAsset(false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LL_DEBUGS("Avatar") << avString() << "from host " << uuid << LL_ENDL;
|
|
LLHost host = getObjectHost();
|
|
result = LLViewerTextureManager::getFetchedTexture(
|
|
uuid, FTT_SERVER_BAKE, TRUE, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE, 0, 0, host);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// virtual
|
|
S32 LLVOAvatar::setTETexture(const U8 te, const LLUUID& uuid)
|
|
{
|
|
if (!isIndexBakedTexture((ETextureIndex)te))
|
|
{
|
|
// Sim still sends some uuids for non-baked slots sometimes - ignore.
|
|
return LLViewerObject::setTETexture(te, LLUUID::null);
|
|
}
|
|
|
|
LLViewerFetchedTexture *image = getBakedTextureImage(te,uuid);
|
|
llassert(image);
|
|
return setTETextureCore(te, image);
|
|
}
|
|
|
|
static LLFastTimer::DeclareTimer FTM_AVATAR_UPDATE("Avatar Update");
|
|
static LLFastTimer::DeclareTimer FTM_JOINT_UPDATE("Update Joints");
|
|
static LLFastTimer::DeclareTimer FTM_CHARACTER_UPDATE("Character Update");
|
|
static LLFastTimer::DeclareTimer FTM_BASE_UPDATE("Base Update");
|
|
static LLFastTimer::DeclareTimer FTM_MISC_UPDATE("Misc Update");
|
|
static LLFastTimer::DeclareTimer FTM_DETAIL_UPDATE("Detail Update");
|
|
|
|
//------------------------------------------------------------------------
|
|
// LLVOAvatar::dumpAnimationState()
|
|
//------------------------------------------------------------------------
|
|
void LLVOAvatar::dumpAnimationState()
|
|
{
|
|
LL_INFOS() << "==============================================" << LL_ENDL;
|
|
for (LLVOAvatar::AnimIterator it = mSignaledAnimations.begin(); it != mSignaledAnimations.end(); ++it)
|
|
{
|
|
LLUUID id = it->first;
|
|
std::string playtag = "";
|
|
if (mPlayingAnimations.find(id) != mPlayingAnimations.end())
|
|
{
|
|
playtag = "*";
|
|
}
|
|
LL_INFOS() << gAnimLibrary.animationName(id) << playtag << LL_ENDL;
|
|
}
|
|
for (LLVOAvatar::AnimIterator it = mPlayingAnimations.begin(); it != mPlayingAnimations.end(); ++it)
|
|
{
|
|
LLUUID id = it->first;
|
|
bool is_signaled = mSignaledAnimations.find(id) != mSignaledAnimations.end();
|
|
if (!is_signaled)
|
|
{
|
|
LL_INFOS() << gAnimLibrary.animationName(id) << "!S" << LL_ENDL;
|
|
}
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
// idleUpdate()
|
|
//------------------------------------------------------------------------
|
|
void LLVOAvatar::idleUpdate(LLAgent &agent, LLWorld &world, const F64 &time)
|
|
{
|
|
LL_RECORD_BLOCK_TIME(FTM_AVATAR_UPDATE);
|
|
|
|
if (isDead())
|
|
{
|
|
LL_INFOS() << "Warning! Idle on dead avatar" << LL_ENDL;
|
|
return;
|
|
}
|
|
|
|
if (!(gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_AVATAR)))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Update should be happening max once per frame.
|
|
const S32 upd_freq = 4; // force update every upd_freq frames.
|
|
if ((mLastAnimExtents[0]==LLVector3())||
|
|
(mLastAnimExtents[1])==LLVector3())
|
|
{
|
|
mNeedsExtentUpdate = true;
|
|
}
|
|
else
|
|
{
|
|
mNeedsExtentUpdate = ((LLDrawable::getCurrentFrame()+mID.mData[0])%upd_freq==0);
|
|
}
|
|
|
|
checkTextureLoading() ;
|
|
|
|
// force immediate pixel area update on avatars using last frames data (before drawable or camera updates)
|
|
setPixelAreaAndAngle(gAgent);
|
|
|
|
// force asynchronous drawable update
|
|
if(mDrawable.notNull() && !gNoRender)
|
|
{
|
|
LL_RECORD_BLOCK_TIME(FTM_JOINT_UPDATE);
|
|
|
|
if (isSitting() && getParent())
|
|
{
|
|
LLViewerObject *root_object = (LLViewerObject*)getRoot();
|
|
LLDrawable* drawablep = root_object->mDrawable;
|
|
// if this object hasn't already been updated by another avatar...
|
|
if (drawablep) // && !drawablep->isState(LLDrawable::EARLY_MOVE))
|
|
{
|
|
if (root_object->isSelected())
|
|
{
|
|
gPipeline.updateMoveNormalAsync(drawablep);
|
|
}
|
|
else
|
|
{
|
|
gPipeline.updateMoveDampedAsync(drawablep);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
gPipeline.updateMoveDampedAsync(mDrawable);
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
// set alpha flag depending on state
|
|
//--------------------------------------------------------------------
|
|
|
|
if (isSelf())
|
|
{
|
|
{
|
|
LL_RECORD_BLOCK_TIME(FTM_BASE_UPDATE);
|
|
LLViewerObject::idleUpdate(agent, world, time);
|
|
}
|
|
|
|
// trigger fidget anims
|
|
if (isAnyAnimationSignaled(AGENT_STAND_ANIMS, NUM_AGENT_STAND_ANIMS))
|
|
{
|
|
agent.fidget();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Should override the idleUpdate stuff and leave out the angular update part.
|
|
LLQuaternion rotation = getRotation();
|
|
{
|
|
LL_RECORD_BLOCK_TIME(FTM_BASE_UPDATE);
|
|
LLViewerObject::idleUpdate(agent, world, time);
|
|
}
|
|
setRotation(rotation);
|
|
}
|
|
|
|
// attach objects that were waiting for a drawable
|
|
lazyAttach();
|
|
|
|
// animate the character
|
|
// store off last frame's root position to be consistent with camera position
|
|
mLastRootPos = mRoot->getWorldPosition();
|
|
bool detailed_update;
|
|
{
|
|
LL_RECORD_BLOCK_TIME(FTM_CHARACTER_UPDATE);
|
|
detailed_update = updateCharacter(agent);
|
|
}
|
|
if (gNoRender)
|
|
{
|
|
return;
|
|
}
|
|
|
|
static LLUICachedControl<bool> visualizers_in_calls("ShowVoiceVisualizersInCalls", false);
|
|
bool voice_enabled = (visualizers_in_calls || LLVoiceClient::getInstance()->inProximalChannel()) &&
|
|
LLVoiceClient::getInstance()->getVoiceEnabled(mID);
|
|
|
|
LL_RECORD_BLOCK_TIME(FTM_MISC_UPDATE);
|
|
idleUpdateVoiceVisualizer(voice_enabled);
|
|
idleUpdateMisc(detailed_update);
|
|
idleUpdateAppearanceAnimation();
|
|
if (detailed_update)
|
|
{
|
|
LL_RECORD_BLOCK_TIME(FTM_DETAIL_UPDATE);
|
|
idleUpdateLipSync(voice_enabled);
|
|
idleUpdateLoadingEffect();
|
|
idleUpdateBelowWater(); // wind effect uses this
|
|
idleUpdateWindEffect();
|
|
}
|
|
|
|
idleUpdateNameTag(mLastRootPos);
|
|
idleUpdateRenderComplexity();
|
|
}
|
|
|
|
void LLVOAvatar::idleUpdateVoiceVisualizer(bool voice_enabled)
|
|
{
|
|
bool render_visualizer = voice_enabled;
|
|
|
|
// Don't render the user's own voice visualizer when in mouselook, or when opening the mic is disabled.
|
|
if(isSelf())
|
|
{
|
|
if(gAgentCamera.cameraMouselook() || gSavedSettings.getBOOL("VoiceDisableMic"))
|
|
{
|
|
render_visualizer = false;
|
|
}
|
|
}
|
|
else if (gRlvHandler.hasBehaviour(RLV_BHVR_SHOWNAMETAGS) || (gRlvHandler.hasBehaviour(RLV_BHVR_CAMAVDIST) && (gAgent.getPosGlobalFromAgent(getCharacterPosition()) - gAgent.getPosGlobalFromAgent(gAgentAvatarp->getRenderPosition())).magVec() > gRlvHandler.camPole(RLV_BHVR_CAMAVDIST))) // RLVa:LF - You get nothing now!
|
|
{
|
|
render_visualizer = false;
|
|
}
|
|
|
|
|
|
mVoiceVisualizer->setVoiceEnabled(render_visualizer);
|
|
|
|
if ( voice_enabled )
|
|
{
|
|
//----------------------------------------------------------------
|
|
// Only do gesture triggering for your own avatar, and only when you're in a proximal channel.
|
|
//----------------------------------------------------------------
|
|
if( isSelf() )
|
|
{
|
|
//----------------------------------------------------------------------------------------
|
|
// The following takes the voice signal and uses that to trigger gesticulations.
|
|
//----------------------------------------------------------------------------------------
|
|
int lastGesticulationLevel = mCurrentGesticulationLevel;
|
|
mCurrentGesticulationLevel = mVoiceVisualizer->getCurrentGesticulationLevel();
|
|
|
|
//---------------------------------------------------------------------------------------------------
|
|
// If "current gesticulation level" changes, we catch this, and trigger the new gesture
|
|
//---------------------------------------------------------------------------------------------------
|
|
if ( lastGesticulationLevel != mCurrentGesticulationLevel )
|
|
{
|
|
if ( mCurrentGesticulationLevel != VOICE_GESTICULATION_LEVEL_OFF )
|
|
{
|
|
std::string gestureString = "unInitialized";
|
|
if ( mCurrentGesticulationLevel == 0 ) { gestureString = "/voicelevel1"; }
|
|
else if ( mCurrentGesticulationLevel == 1 ) { gestureString = "/voicelevel2"; }
|
|
else if ( mCurrentGesticulationLevel == 2 ) { gestureString = "/voicelevel3"; }
|
|
else { LL_INFOS() << "oops - CurrentGesticulationLevel can be only 0, 1, or 2" << LL_ENDL; }
|
|
|
|
// this is the call that Karl S. created for triggering gestures from within the code.
|
|
LLGestureMgr::instance().triggerAndReviseString( gestureString );
|
|
}
|
|
}
|
|
|
|
} //if( isSelf() )
|
|
|
|
//-----------------------------------------------------------------------------------------------------------------
|
|
// If the avatar is speaking, then the voice amplitude signal is passed to the voice visualizer.
|
|
// Also, here we trigger voice visualizer start and stop speaking, so it can animate the voice symbol.
|
|
//
|
|
// Notice the calls to "gAwayTimer.reset()". This resets the timer that determines how long the avatar has been
|
|
// "away", so that the avatar doesn't lapse into away-mode (and slump over) while the user is still talking.
|
|
//-----------------------------------------------------------------------------------------------------------------
|
|
if (LLVoiceClient::getInstance()->getIsSpeaking( mID ))
|
|
{
|
|
if ( ! mVoiceVisualizer->getCurrentlySpeaking() )
|
|
{
|
|
mVoiceVisualizer->setStartSpeaking();
|
|
|
|
//printf( "gAwayTimer.reset();\n" );
|
|
}
|
|
|
|
mVoiceVisualizer->setSpeakingAmplitude( LLVoiceClient::getInstance()->getCurrentPower( mID ) );
|
|
|
|
if( isSelf() )
|
|
{
|
|
gAgent.clearAFK();
|
|
}
|
|
mIdleTimer.reset();
|
|
}
|
|
else
|
|
{
|
|
if ( mVoiceVisualizer->getCurrentlySpeaking() )
|
|
{
|
|
mVoiceVisualizer->setStopSpeaking();
|
|
|
|
if ( mLipSyncActive )
|
|
{
|
|
if( mOohMorph ) mOohMorph->setWeight(mOohMorph->getMinWeight(), FALSE);
|
|
if( mAahMorph ) mAahMorph->setWeight(mAahMorph->getMinWeight(), FALSE);
|
|
|
|
mLipSyncActive = false;
|
|
LLCharacter::updateVisualParams();
|
|
dirtyMesh();
|
|
}
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------
|
|
// here we get the approximate head position and set as sound source for the voice symbol
|
|
// (the following version uses a tweak of "mHeadOffset" which handle sitting vs. standing)
|
|
//--------------------------------------------------------------------------------------------
|
|
|
|
if ( isSitting() )
|
|
{
|
|
LLVector3 headOffset = LLVector3( 0.0f, 0.0f, mHeadOffset.mV[2] );
|
|
mVoiceVisualizer->setVoiceSourceWorldPosition( mRoot->getWorldPosition() + headOffset );
|
|
}
|
|
else
|
|
{
|
|
LLVector3 tagPos = mRoot->getWorldPosition();
|
|
tagPos[VZ] -= mPelvisToFoot;
|
|
tagPos[VZ] += ( mBodySize[VZ] + 0.125f );
|
|
mVoiceVisualizer->setVoiceSourceWorldPosition( tagPos );
|
|
}
|
|
}//if ( voiceEnabled )
|
|
}
|
|
|
|
static LLFastTimer::DeclareTimer FTM_ATTACHMENT_UPDATE("Update Attachments");
|
|
|
|
void LLVOAvatar::idleUpdateMisc(bool detailed_update)
|
|
{
|
|
if (LLVOAvatar::sJointDebug)
|
|
{
|
|
LL_INFOS() << getFullname() << ": joint touches: " << LLJoint::sNumTouches << " updates: " << LLJoint::sNumUpdates << LL_ENDL;
|
|
}
|
|
|
|
LLJoint::sNumUpdates = 0;
|
|
LLJoint::sNumTouches = 0;
|
|
|
|
BOOL visible = isVisible() || mNeedsAnimUpdate;
|
|
|
|
// update attachments positions
|
|
if (detailed_update || !sUseImpostors)
|
|
{
|
|
LL_RECORD_BLOCK_TIME(FTM_ATTACHMENT_UPDATE);
|
|
#if SLOW_ATTACHMENT_LIST
|
|
for (attachment_map_t::iterator iter = mAttachmentPoints.begin();
|
|
iter != mAttachmentPoints.end();
|
|
++iter)
|
|
{
|
|
LLViewerJointAttachment* attachment = iter->second;
|
|
|
|
for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin();
|
|
attachment_iter != attachment->mAttachedObjects.end();
|
|
++attachment_iter)
|
|
{
|
|
LLViewerObject* attached_object = (*attachment_iter);
|
|
#else
|
|
for(auto& iter : mAttachedObjectsVector)
|
|
{{
|
|
LLViewerJointAttachment* attachment = iter.second;
|
|
LLViewerObject* attached_object = iter.first;
|
|
#endif
|
|
|
|
BOOL visibleAttachment = visible || (attached_object &&
|
|
!(attached_object->mDrawable->getSpatialBridge() &&
|
|
attached_object->mDrawable->getSpatialBridge()->getRadius() < 2.0));
|
|
|
|
if (visibleAttachment && attached_object && !attached_object->isDead() && attachment->getValid())
|
|
{
|
|
// if selecting any attachments, update all of them as non-damped
|
|
if (LLSelectMgr::getInstance()->getSelection()->getObjectCount() && LLSelectMgr::getInstance()->getSelection()->isAttachment())
|
|
{
|
|
gPipeline.updateMoveNormalAsync(attached_object->mDrawable);
|
|
}
|
|
else
|
|
{
|
|
gPipeline.updateMoveDampedAsync(attached_object->mDrawable);
|
|
}
|
|
|
|
LLSpatialBridge* bridge = attached_object->mDrawable->getSpatialBridge();
|
|
if (bridge)
|
|
{
|
|
gPipeline.updateMoveNormalAsync(bridge);
|
|
}
|
|
attached_object->updateText();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
mNeedsAnimUpdate = FALSE;
|
|
|
|
if (isImpostor() && !mNeedsImpostorUpdate)
|
|
{
|
|
LL_ALIGN_16(LLVector4a ext[2]);
|
|
F32 distance;
|
|
LLVector3 angle;
|
|
|
|
getImpostorValues(ext, angle, distance);
|
|
|
|
for (U32 i = 0; i < 3 && !mNeedsImpostorUpdate; i++)
|
|
{
|
|
F32 cur_angle = angle.mV[i];
|
|
F32 old_angle = mImpostorAngle.mV[i];
|
|
F32 angle_diff = fabsf(cur_angle-old_angle);
|
|
|
|
if (angle_diff > F_PI/512.f*distance*mUpdatePeriod)
|
|
{
|
|
mNeedsImpostorUpdate = TRUE;
|
|
}
|
|
}
|
|
|
|
if (detailed_update && !mNeedsImpostorUpdate)
|
|
{ //update impostor if view angle, distance, or bounding box change
|
|
//significantly
|
|
|
|
F32 dist_diff = fabsf(distance-mImpostorDistance);
|
|
if (dist_diff/mImpostorDistance > 0.1f)
|
|
{
|
|
mNeedsImpostorUpdate = TRUE;
|
|
}
|
|
else
|
|
{
|
|
ext[0].load3(mLastAnimExtents[0].mV);
|
|
ext[1].load3(mLastAnimExtents[1].mV);
|
|
// Expensive. Just call this once per frame, in updateSpatialExtents();
|
|
//calculateSpatialExtents(ext[0], ext[1]);
|
|
LLVector4a diff;
|
|
diff.setSub(ext[1], mImpostorExtents[1]);
|
|
if (diff.getLength3().getF32() > 0.05f)
|
|
{
|
|
mNeedsImpostorUpdate = TRUE;
|
|
}
|
|
else
|
|
{
|
|
diff.setSub(ext[0], mImpostorExtents[0]);
|
|
if (diff.getLength3().getF32() > 0.05f)
|
|
{
|
|
mNeedsImpostorUpdate = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mDrawable.notNull())
|
|
{
|
|
mDrawable->movePartition();
|
|
|
|
//force a move if sitting on an active object
|
|
if (getParent() && ((LLViewerObject*)getParent())->mDrawable.notNull() && ((LLViewerObject*) getParent())->mDrawable->isActive())
|
|
{
|
|
gPipeline.markMoved(mDrawable, TRUE);
|
|
}
|
|
}
|
|
}
|
|
|
|
void LLVOAvatar::idleUpdateAppearanceAnimation()
|
|
{
|
|
// update morphing params
|
|
if (mAppearanceAnimating)
|
|
{
|
|
ESex avatar_sex = getSex();
|
|
F32 appearance_anim_time = mAppearanceMorphTimer.getElapsedTimeF32();
|
|
if (appearance_anim_time >= APPEARANCE_MORPH_TIME)
|
|
{
|
|
mAppearanceAnimating = FALSE;
|
|
for (LLVisualParam *param = getFirstVisualParam();
|
|
param;
|
|
param = getNextVisualParam())
|
|
{
|
|
if (param->isTweakable())
|
|
{
|
|
param->stopAnimating(FALSE);
|
|
}
|
|
}
|
|
updateVisualParams();
|
|
if (isSelf())
|
|
{
|
|
gAgent.sendAgentSetAppearance();
|
|
}
|
|
mIdleTimer.reset();
|
|
}
|
|
else
|
|
{
|
|
F32 morph_amt = calcMorphAmount();
|
|
LLVisualParam *param;
|
|
|
|
if (!isSelf())
|
|
{
|
|
// animate only top level params for non-self avatars
|
|
for (param = getFirstVisualParam();
|
|
param;
|
|
param = getNextVisualParam())
|
|
{
|
|
if (param->isTweakable())
|
|
{
|
|
param->animate(morph_amt, FALSE);
|
|
}
|
|
}
|
|
}
|
|
|
|
// apply all params
|
|
for (param = getFirstVisualParam();
|
|
param;
|
|
param = getNextVisualParam())
|
|
{
|
|
param->apply(avatar_sex);
|
|
}
|
|
|
|
mLastAppearanceBlendTime = appearance_anim_time;
|
|
}
|
|
dirtyMesh();
|
|
}
|
|
}
|
|
|
|
F32 LLVOAvatar::calcMorphAmount()
|
|
{
|
|
F32 appearance_anim_time = mAppearanceMorphTimer.getElapsedTimeF32();
|
|
F32 blend_frac = calc_bouncy_animation(appearance_anim_time / APPEARANCE_MORPH_TIME);
|
|
F32 last_blend_frac = calc_bouncy_animation(mLastAppearanceBlendTime / APPEARANCE_MORPH_TIME);
|
|
|
|
F32 morph_amt;
|
|
if (last_blend_frac == 1.f)
|
|
{
|
|
morph_amt = 1.f;
|
|
}
|
|
else
|
|
{
|
|
morph_amt = (blend_frac - last_blend_frac) / (1.f - last_blend_frac);
|
|
}
|
|
|
|
return morph_amt;
|
|
}
|
|
|
|
void LLVOAvatar::idleUpdateLipSync(bool voice_enabled)
|
|
{
|
|
// Use the Lipsync_Ooh and Lipsync_Aah morphs for lip sync
|
|
if ( voice_enabled && (LLVoiceClient::getInstance()->lipSyncEnabled()) && LLVoiceClient::getInstance()->getIsSpeaking( mID ) )
|
|
{
|
|
F32 ooh_morph_amount = 0.0f;
|
|
F32 aah_morph_amount = 0.0f;
|
|
|
|
mVoiceVisualizer->lipSyncOohAah( ooh_morph_amount, aah_morph_amount );
|
|
|
|
if( mOohMorph )
|
|
{
|
|
F32 ooh_weight = mOohMorph->getMinWeight()
|
|
+ ooh_morph_amount * (mOohMorph->getMaxWeight() - mOohMorph->getMinWeight());
|
|
|
|
mOohMorph->setWeight( ooh_weight, FALSE );
|
|
}
|
|
|
|
if( mAahMorph )
|
|
{
|
|
F32 aah_weight = mAahMorph->getMinWeight()
|
|
+ aah_morph_amount * (mAahMorph->getMaxWeight() - mAahMorph->getMinWeight());
|
|
|
|
mAahMorph->setWeight( aah_weight, FALSE );
|
|
}
|
|
|
|
mLipSyncActive = true;
|
|
LLCharacter::updateVisualParams();
|
|
dirtyMesh();
|
|
mIdleTimer.reset();
|
|
}
|
|
}
|
|
|
|
void LLVOAvatar::idleUpdateLoadingEffect()
|
|
{
|
|
// update visibility when avatar is partially loaded
|
|
if (updateIsFullyLoaded()) // changed?
|
|
{
|
|
if (isFullyLoaded())
|
|
{
|
|
if (mFirstFullyVisible)
|
|
{
|
|
mFirstFullyVisible = FALSE;
|
|
if (isSelf())
|
|
{
|
|
LL_INFOS("Avatar") << avString() << "self isFullyLoaded, mFirstFullyVisible" << LL_ENDL;
|
|
LLAppearanceMgr::instance().onFirstFullyVisible();
|
|
}
|
|
else
|
|
{
|
|
LL_INFOS("Avatar") << avString() << "other isFullyLoaded, mFirstFullyVisible" << LL_ENDL;
|
|
}
|
|
}
|
|
|
|
deleteParticleSource();
|
|
updateLOD();
|
|
}
|
|
else
|
|
{
|
|
LLPartSysData particle_parameters;
|
|
|
|
// fancy particle cloud designed by Brent
|
|
particle_parameters.mPartData.mMaxAge = 4.f;
|
|
particle_parameters.mPartData.mStartScale.mV[VX] = 0.8f;
|
|
particle_parameters.mPartData.mStartScale.mV[VX] = 0.8f;
|
|
particle_parameters.mPartData.mStartScale.mV[VY] = 1.0f;
|
|
particle_parameters.mPartData.mEndScale.mV[VX] = 0.02f;
|
|
particle_parameters.mPartData.mEndScale.mV[VY] = 0.02f;
|
|
particle_parameters.mPartData.mStartColor = LLColor4(1, 1, 1, 0.5f);
|
|
particle_parameters.mPartData.mEndColor = LLColor4(1, 1, 1, 0.0f);
|
|
particle_parameters.mPartData.mStartScale.mV[VX] = 0.8f;
|
|
LLViewerTexture* cloud = LLViewerTextureManager::getFetchedTextureFromFile("cloud-particle.j2c");
|
|
particle_parameters.mPartImageID = cloud->getID();
|
|
particle_parameters.mMaxAge = 0.f;
|
|
particle_parameters.mPattern = LLPartSysData::LL_PART_SRC_PATTERN_ANGLE_CONE;
|
|
particle_parameters.mInnerAngle = F_PI;
|
|
particle_parameters.mOuterAngle = 0.f;
|
|
particle_parameters.mBurstRate = 0.02f;
|
|
particle_parameters.mBurstRadius = 0.0f;
|
|
particle_parameters.mBurstPartCount = 1;
|
|
particle_parameters.mBurstSpeedMin = 0.1f;
|
|
particle_parameters.mBurstSpeedMax = 1.f;
|
|
particle_parameters.mPartData.mFlags = ( LLPartData::LL_PART_INTERP_COLOR_MASK | LLPartData::LL_PART_INTERP_SCALE_MASK |
|
|
LLPartData::LL_PART_EMISSIVE_MASK | // LLPartData::LL_PART_FOLLOW_SRC_MASK |
|
|
LLPartData::LL_PART_TARGET_POS_MASK );
|
|
|
|
if (!mIsDummy && !isTooComplex())
|
|
{
|
|
setParticleSource(particle_parameters, getID());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void LLVOAvatar::idleUpdateWindEffect()
|
|
{
|
|
// update wind effect
|
|
if ((LLViewerShaderMgr::instance()->getVertexShaderLevel(LLViewerShaderMgr::SHADER_AVATAR) >= LLDrawPoolAvatar::SHADER_LEVEL_CLOTH))
|
|
{
|
|
F32 hover_strength = 0.f;
|
|
F32 time_delta = mRippleTimer.getElapsedTimeF32() - mRippleTimeLast;
|
|
mRippleTimeLast = mRippleTimer.getElapsedTimeF32();
|
|
LLVector3 velocity = getVelocity();
|
|
F32 speed = velocity.length();
|
|
//RN: velocity varies too much frame to frame for this to work
|
|
mRippleAccel.clearVec();//lerp(mRippleAccel, (velocity - mLastVel) * time_delta, LLSmoothInterpolation::getInterpolant(0.02f));
|
|
mLastVel = velocity;
|
|
LLVector4 wind;
|
|
wind.setVec(getRegion()->mWind.getVelocityNoisy(getPositionAgent(), 4.f) - velocity);
|
|
|
|
if (mInAir)
|
|
{
|
|
hover_strength = HOVER_EFFECT_STRENGTH * llmax(0.f, HOVER_EFFECT_MAX_SPEED - speed);
|
|
}
|
|
|
|
if (mBelowWater)
|
|
{
|
|
// TODO: make cloth flow more gracefully when underwater
|
|
hover_strength += UNDERWATER_EFFECT_STRENGTH;
|
|
}
|
|
|
|
wind.mV[VZ] += hover_strength;
|
|
wind.normalize();
|
|
|
|
wind.mV[VW] = llmin(0.025f + (speed * 0.015f) + hover_strength, 0.5f);
|
|
F32 interp;
|
|
if (wind.mV[VW] > mWindVec.mV[VW])
|
|
{
|
|
interp = LLSmoothInterpolation::getInterpolant(0.2f);
|
|
}
|
|
else
|
|
{
|
|
interp = LLSmoothInterpolation::getInterpolant(0.4f);
|
|
}
|
|
mWindVec = lerp(mWindVec, wind, interp);
|
|
|
|
F32 wind_freq = hover_strength + llclamp(8.f + (speed * 0.7f) + (LLPerlinNoise::noise(mRipplePhase) * 4.f), 8.f, 25.f);
|
|
mWindFreq = lerp(mWindFreq, wind_freq, interp);
|
|
|
|
if (mBelowWater)
|
|
{
|
|
mWindFreq *= UNDERWATER_FREQUENCY_DAMP;
|
|
}
|
|
|
|
mRipplePhase += (time_delta * mWindFreq);
|
|
if (mRipplePhase > 256.f)
|
|
{
|
|
mRipplePhase = fmodf(mRipplePhase, 256.f);
|
|
}
|
|
}
|
|
}
|
|
|
|
void LLVOAvatar::idleUpdateNameTag(const LLVector3& root_pos_last)
|
|
{
|
|
// update chat bubble
|
|
//--------------------------------------------------------------------
|
|
// draw text label over character's head
|
|
//--------------------------------------------------------------------
|
|
if (mChatTimer.getElapsedTimeF32() > BUBBLE_CHAT_TIME)
|
|
{
|
|
mChats.clear();
|
|
}
|
|
|
|
const F32 time_visible = mTimeVisible.getElapsedTimeF32();
|
|
static const LLCachedControl<F32> NAME_SHOW_TIME("RenderNameShowTime",10); // seconds
|
|
static const LLCachedControl<F32> FADE_DURATION("RenderNameFadeDuration",1); // seconds
|
|
static const LLCachedControl<bool> use_chat_bubbles("UseChatBubbles",false);
|
|
static const LLCachedControl<bool> use_typing_bubbles("UseTypingBubbles");
|
|
static const LLCachedControl<bool> render_name_hide_self("RenderNameHideSelf",false);
|
|
static const LLCachedControl<bool> allow_nameplate_override ("CCSAllowNameplateOverride", true);
|
|
// [RLVa:KB] - Checked: 2010-04-04 (RLVa-1.2.2a) | Added: RLVa-0.2.0b
|
|
if (gRlvHandler.hasBehaviour(RLV_BHVR_SHOWNAMETAGS))
|
|
return; // No tags
|
|
if (gRlvHandler.hasBehaviour(RLV_BHVR_CAMAVDIST) && (gAgent.getPosGlobalFromAgent(getCharacterPosition()) - gAgent.getPosGlobalFromAgent(gAgentAvatarp->getRenderPosition())).magVec() > gRlvHandler.camPole(RLV_BHVR_CAMAVDIST))
|
|
{
|
|
clearNameTag(); // Dynamically remove this avatar's tag
|
|
return;
|
|
}
|
|
bool fRlvShowNames = gRlvHandler.hasBehaviour(RLV_BHVR_SHOWNAMES);
|
|
// [/RLVa:KB]
|
|
BOOL visible_avatar = isVisible() || mNeedsAnimUpdate;
|
|
BOOL visible_chat = use_chat_bubbles && (mChats.size() || mTyping);
|
|
bool visible_typing = use_typing_bubbles && mTyping;
|
|
BOOL render_name = visible_chat ||
|
|
visible_typing ||
|
|
(visible_avatar &&
|
|
// [RLVa:KB] - Checked: 2010-04-04 (RLVa-1.2.2a) | Added: RLVa-1.0.0h
|
|
( (!fRlvShowNames) || (RlvSettings::getShowNameTags()) ) &&
|
|
// [/RLVa:KB]
|
|
((sRenderName == RENDER_NAME_ALWAYS) ||
|
|
(sRenderName == RENDER_NAME_FADE && time_visible < NAME_SHOW_TIME) || (allow_nameplate_override && mCCSChatTextOverride)));
|
|
// If it's your own avatar, don't draw in mouselook, and don't
|
|
// draw if we're specifically hiding our own name.
|
|
if (isSelf())
|
|
{
|
|
render_name = render_name
|
|
&& !gAgentCamera.cameraMouselook()
|
|
&& (visible_chat || !render_name_hide_self);
|
|
}
|
|
|
|
if ( !render_name )
|
|
{
|
|
if (mNameText)
|
|
{
|
|
// ...clean up old name tag
|
|
mNameText->markDead();
|
|
mNameText = NULL;
|
|
sNumVisibleChatBubbles--;
|
|
}
|
|
return;
|
|
}
|
|
|
|
BOOL new_name = FALSE;
|
|
if (visible_chat != mVisibleChat)
|
|
{
|
|
mVisibleChat = visible_chat;
|
|
new_name = TRUE;
|
|
}
|
|
if (visible_typing != mVisibleTyping)
|
|
{
|
|
mVisibleTyping = visible_typing;
|
|
new_name = true;
|
|
}
|
|
|
|
// [RLVa:KB] - Checked: 2010-04-04 (RLVa-1.2.2a) | Added: RLVa-0.2.0b
|
|
if (fRlvShowNames)
|
|
{
|
|
if (mRenderGroupTitles)
|
|
{
|
|
mRenderGroupTitles = FALSE;
|
|
new_name = TRUE;
|
|
}
|
|
}
|
|
else if (sRenderGroupTitles != (bool)mRenderGroupTitles)
|
|
// [/RLVa]
|
|
//if (sRenderGroupTitles != (bool)mRenderGroupTitles)
|
|
{
|
|
mRenderGroupTitles = sRenderGroupTitles;
|
|
new_name = TRUE;
|
|
}
|
|
|
|
// First Calculate Alpha
|
|
// If alpha > 0, create mNameText if necessary, otherwise delete it
|
|
F32 alpha = 0.f;
|
|
if (mAppAngle > 5.f)
|
|
{
|
|
const F32 START_FADE_TIME = NAME_SHOW_TIME - FADE_DURATION;
|
|
if (!visible_chat && !visible_typing && sRenderName == RENDER_NAME_FADE && time_visible > START_FADE_TIME)
|
|
{
|
|
alpha = 1.f - (time_visible - START_FADE_TIME) / FADE_DURATION;
|
|
}
|
|
else
|
|
{
|
|
// ...not fading, full alpha
|
|
alpha = 1.f;
|
|
}
|
|
}
|
|
else if (mAppAngle > 2.f)
|
|
{
|
|
// far away is faded out also
|
|
alpha = (mAppAngle-2.f)/3.f;
|
|
}
|
|
|
|
if (alpha <= 0.f)
|
|
{
|
|
if (mNameText)
|
|
{
|
|
mNameText->markDead();
|
|
mNameText = NULL;
|
|
sNumVisibleChatBubbles--;
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (!mNameText)
|
|
{
|
|
mNameText = static_cast<LLHUDNameTag*>( LLHUDObject::addHUDObject(
|
|
LLHUDObject::LL_HUD_NAME_TAG) );
|
|
//mNameText->setMass(10.f);
|
|
mNameText->setSourceObject(this);
|
|
mNameText->setVertAlignment(LLHUDNameTag::ALIGN_VERT_TOP);
|
|
mNameText->setVisibleOffScreen(TRUE);
|
|
mNameText->setMaxLines(11);
|
|
mNameText->setFadeDistance(CHAT_NORMAL_RADIUS, 5.f);
|
|
sNumVisibleChatBubbles++;
|
|
new_name = TRUE;
|
|
}
|
|
|
|
LLVector3 name_position = idleUpdateNameTagPosition(root_pos_last);
|
|
mNameText->setPositionAgent(name_position);
|
|
idleCCSUpdateAttachmentText(render_name);
|
|
idleUpdateNameTagText(new_name);
|
|
idleUpdateNameTagAlpha(new_name, alpha);
|
|
}
|
|
|
|
void LLVOAvatar::idleCCSUpdateAttachmentText(bool render_name)
|
|
{
|
|
const F32 SECS_BETWEEN_UPDATES = 0.5f;
|
|
static const LLCachedControl<bool> allow_nameplate_override ("CCSAllowNameplateOverride", true);
|
|
|
|
std::string nameplate = "";
|
|
|
|
if(allow_nameplate_override)
|
|
{
|
|
if(!mCCSUpdateAttachmentTimer.checkExpirationAndReset(SECS_BETWEEN_UPDATES))
|
|
return;
|
|
|
|
/*for (attachment_map_t::iterator it=mAttachmentPoints.begin(); it!=mAttachmentPoints.end(); ++it)
|
|
{
|
|
// get attached object
|
|
LLViewerJointAttachment *joint = it->second;
|
|
if (!joint)
|
|
continue;
|
|
for(std::vector<LLViewerObject *>::const_iterator parent_it = joint->mAttachedObjects.begin();
|
|
parent_it != joint->mAttachedObjects.end(); ++parent_it)
|
|
{
|
|
LLViewerObject* pObject = (*parent_it);*/
|
|
std::vector<std::pair<LLViewerObject*,LLViewerJointAttachment*> >::iterator parent_it = mAttachedObjectsVector.begin();
|
|
for(;parent_it!=mAttachedObjectsVector.end();++parent_it)
|
|
{{
|
|
LLViewerObject* pObject = parent_it->first;
|
|
if(!pObject)
|
|
continue;
|
|
const LLTextureEntry *te = pObject->getTE(0);
|
|
if(!te) continue;
|
|
const LLColor4 &col = te->getColor();
|
|
if( (fabs(col[0] - 0.012f) < 0.001f) &&
|
|
(fabs(col[1] - 0.036f) < 0.001f) &&
|
|
(fabs(col[2] - 0.008f) < 0.001f) &&
|
|
(fabs(col[3] - 0.000f) < 0.001f) )
|
|
{
|
|
pObject->mIsNameAttachment = true;
|
|
if (pObject->mText) pObject->mText->setHidden(true);
|
|
|
|
//This extra loop seems really silly.
|
|
const_child_list_t &children = pObject->getChildren();
|
|
for (const_child_list_t::const_iterator child_it=children.begin(); child_it!=children.end(); ++child_it)
|
|
{
|
|
LLViewerObject* pChildObject = (*child_it);
|
|
const LLTextureEntry *te2 = pChildObject ? pChildObject->getTE(0) : NULL;
|
|
if(!te2 || !pChildObject->mText) continue;
|
|
const LLColor4 &col = te2->getColor();
|
|
if ((fabs(col[0] - 0.012f) < 0.001f) ||
|
|
(fabs(col[1] - 0.036f) < 0.001f) ||
|
|
(fabs(col[2] - 0.012f) < 0.001f))
|
|
{
|
|
if ((fabs(col[3] - 0.004f) < 0.001f) ||
|
|
(render_name && (fabs(col[3] - 0.000f) < 0.001f)))
|
|
{
|
|
nameplate += pChildObject->mText->getStringUTF8();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if(pObject->mIsNameAttachment)
|
|
{
|
|
pObject->mIsNameAttachment = false;
|
|
if (pObject->mText) pObject->mText->setHidden(false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if(mCCSAttachmentText != nameplate)
|
|
{
|
|
mCCSAttachmentText = nameplate;
|
|
clearNameTag();
|
|
}
|
|
}
|
|
|
|
void LLVOAvatar::idleUpdateNameTagText(BOOL new_name)
|
|
{
|
|
LLNameValue *title = getNVPair("Title");
|
|
LLNameValue* firstname = getNVPair("FirstName");
|
|
LLNameValue* lastname = getNVPair("LastName");
|
|
static const LLCachedControl<bool> display_client_new_line("SLBDisplayClientTagOnNewLine");
|
|
|
|
// Avatars must have a first and last name
|
|
if (!firstname || !lastname) return;
|
|
|
|
// [RLVa:KB] - Checked: 2010-10-31 (RLVa-1.2.2a) | Added: RLVa-1.2.2a
|
|
if (gRlvHandler.hasBehaviour(RLV_BHVR_SHOWNAMETAGS))
|
|
return; // No tags
|
|
if (gRlvHandler.hasBehaviour(RLV_BHVR_CAMAVDIST) && (gAgent.getPosGlobalFromAgent(getCharacterPosition()) - gAgent.getPosGlobalFromAgent(gAgentAvatarp->getRenderPosition())).magVec() > gRlvHandler.camPole(RLV_BHVR_CAMAVDIST))
|
|
{
|
|
clearNameTag(); // Dynamically remove this avatar's tag
|
|
return;
|
|
}
|
|
bool fRlvShowNames = gRlvHandler.hasBehaviour(RLV_BHVR_SHOWNAMES);
|
|
// [/RLVa:KB]
|
|
bool is_away = mSignaledAnimations.find(ANIM_AGENT_AWAY) != mSignaledAnimations.end();
|
|
bool is_busy = mSignaledAnimations.find(ANIM_AGENT_DO_NOT_DISTURB) != mSignaledAnimations.end();
|
|
bool is_appearance = mSignaledAnimations.find(ANIM_AGENT_CUSTOMIZE) != mSignaledAnimations.end();
|
|
bool is_muted;
|
|
if (isSelf())
|
|
{
|
|
is_muted = false;
|
|
}
|
|
else
|
|
{
|
|
is_muted = LLMuteList::getInstance()->isMuted(getID());
|
|
}
|
|
// bool is_friend = LLAvatarTracker::instance().isBuddy(getID());
|
|
// [RLVa:KB] - Checked: 2010-10-31 (RLVa-1.2.2a) | Added: RLVa-1.2.2a
|
|
bool is_friend = (!fRlvShowNames) && (LLAvatarTracker::instance().isBuddy(getID()));
|
|
// [/RLVa:KB]
|
|
bool is_cloud = getIsCloud();
|
|
bool is_langolier = isLangolier();
|
|
|
|
if (is_appearance != mNameAppearance)
|
|
{
|
|
if (is_appearance)
|
|
{
|
|
debugAvatarRezTime("AvatarRezEnteredAppearanceNotification","entered appearance mode");
|
|
}
|
|
else
|
|
{
|
|
debugAvatarRezTime("AvatarRezLeftAppearanceNotification","left appearance mode");
|
|
}
|
|
}
|
|
|
|
std::string idle_string = getIdleTime(is_away, is_busy, is_appearance);
|
|
|
|
// Rebuild name tag if state change detected
|
|
if (mNameString.empty()
|
|
|| new_name
|
|
|| (!title && !mTitle.empty())
|
|
|| (title && mTitle != title->getString())
|
|
|| is_away != mNameAway
|
|
|| is_busy != mNameBusy
|
|
|| is_muted != mNameMute
|
|
|| is_appearance != mNameAppearance
|
|
|| is_friend != mNameFriend
|
|
|| is_cloud != mNameCloud
|
|
|| is_langolier != mNameLangolier)
|
|
{
|
|
LLColor4 name_tag_color = getNameTagColor(is_friend);
|
|
|
|
clearNameTag();
|
|
|
|
std::string groupText;
|
|
std::string firstnameText;
|
|
std::string lastnameText;
|
|
|
|
if (is_away || is_muted || is_busy || is_appearance || is_langolier || !idle_string.empty())
|
|
{
|
|
std::string line;
|
|
if (is_away)
|
|
{
|
|
line += LLTrans::getString("AvatarAway"); //"Away"
|
|
line += ", ";
|
|
}
|
|
if (is_busy)
|
|
{
|
|
line += LLTrans::getString("AvatarBusy"); //"Busy"
|
|
line += ", ";
|
|
}
|
|
if (is_muted)
|
|
{
|
|
line += LLTrans::getString("AvatarMuted"); //"Muted"
|
|
line += ", ";
|
|
}
|
|
if (is_appearance)
|
|
{
|
|
line += LLTrans::getString("AvatarEditingAppearance"); //"(Editing Appearance)"
|
|
line += ", ";
|
|
}
|
|
if (is_langolier)
|
|
{
|
|
line += LLTrans::getString("AvatarLangolier"); //"Langolier"
|
|
line += ", ";
|
|
}
|
|
else if (is_cloud)
|
|
{
|
|
line += LLTrans::getString("LoadingData"); //"Loading..."
|
|
line += ", ";
|
|
}
|
|
if (!idle_string.empty())
|
|
{
|
|
line += idle_string; //"(Idle #min)"
|
|
line += ", ";
|
|
}
|
|
// trim last ", "
|
|
line.resize( line.length() - 2 );
|
|
addNameTagLine(line, name_tag_color, LLFontGL::NORMAL,
|
|
LLFontGL::getFontSansSerifSmall());
|
|
}
|
|
|
|
// if (sRenderGroupTitles
|
|
// [RLVa:KB] - Checked: 2010-10-31 (RLVa-1.2.2a) | Modified: RLVa-1.2.2a
|
|
if (sRenderGroupTitles && !fRlvShowNames
|
|
// [/RLVa:KB]
|
|
&& title && title->getString() && title->getString()[0] != '\0')
|
|
{
|
|
std::string title_str = title->getString();
|
|
LLStringFn::replace_ascii_controlchars(title_str,LL_UNKNOWN_CHAR);
|
|
groupText=title_str; //Defer for later formatting
|
|
//addNameTagLine(title_str, name_tag_color, LLFontGL::NORMAL,
|
|
// LLFontGL::getFontSansSerifSmall());
|
|
}
|
|
|
|
// On SecondLife we can take a shortcut through getNSName, which will strip out Resident
|
|
if (gHippoGridManager->getConnectedGrid()->isSecondLife())
|
|
{
|
|
if (!LLAvatarNameCache::getNSName(getID(), firstnameText))
|
|
{
|
|
// ...call this function back when the name arrives and force a rebuild
|
|
LLAvatarNameCache::get(getID(), boost::bind(&LLVOAvatar::clearNameTag, this));
|
|
}
|
|
// [RLVa:KB] - Checked: 2010-10-31 (RLVa-1.2.2a) | Modified: RLVa-1.2.2a
|
|
else if (fRlvShowNames && !isSelf())
|
|
{
|
|
firstnameText = RlvStrings::getAnonym(firstnameText);
|
|
}
|
|
// [/RLVa:KB]
|
|
}
|
|
else
|
|
{
|
|
// static LLUICachedControl<bool> show_display_names("NameTagShowDisplayNames");
|
|
// static LLUICachedControl<bool> show_usernames("NameTagShowUsernames");
|
|
|
|
static const LLCachedControl<S32> phoenix_name_system("PhoenixNameSystem", 0);
|
|
|
|
bool show_display_names = phoenix_name_system > 0 || phoenix_name_system < 4;
|
|
bool show_usernames = phoenix_name_system != 2;
|
|
if (show_display_names && LLAvatarName::useDisplayNames())
|
|
{
|
|
LLAvatarName av_name;
|
|
if (!LLAvatarNameCache::get(getID(), &av_name))
|
|
{
|
|
// ...call this function back when the name arrives
|
|
// and force a rebuild
|
|
LLAvatarNameCache::get(getID(),
|
|
boost::bind(&LLVOAvatar::clearNameTag, this));
|
|
}
|
|
|
|
// [RLVa:KB] - Checked: 2010-10-31 (RLVa-1.2.2a) | Modified: RLVa-1.2.2a
|
|
if ( (!fRlvShowNames) || (isSelf()) )
|
|
{
|
|
// [/RLVa:KB]
|
|
// Might be blank if name not available yet, that's OK
|
|
if (show_display_names)
|
|
{
|
|
firstnameText = phoenix_name_system == 3 ? av_name.getUserName() : av_name.getDisplayName(); //Defer for later formatting
|
|
//addNameTagLine(av_name.getDisplayName(), name_tag_color, LLFontGL::NORMAL,
|
|
// LLFontGL::getFontSansSerif());
|
|
}
|
|
// Suppress SLID display if display name matches exactly (ugh)
|
|
if (show_usernames && !av_name.isDisplayNameDefault())
|
|
{
|
|
firstnameText.push_back(' ');
|
|
firstnameText.push_back('(');
|
|
firstnameText.append(phoenix_name_system == 3 ? av_name.getDisplayName() : av_name.getAccountName()); //Defer for later formatting
|
|
firstnameText.push_back(')');
|
|
// *HACK: Desaturate the color
|
|
//LLColor4 username_color = name_tag_color * 0.83f;
|
|
//addNameTagLine(av_name.getUserName(), username_color, LLFontGL::NORMAL,
|
|
// LLFontGL::getFontSansSerifSmall());
|
|
}
|
|
// [RLVa:KB] - Checked: 2010-10-31 (RLVa-1.2.2a) | Modified: RLVa-1.2.2a
|
|
}
|
|
else
|
|
{
|
|
firstnameText = RlvStrings::getAnonym(av_name);
|
|
}
|
|
// [/RLVa:KB]
|
|
}
|
|
else
|
|
{
|
|
// [RLVa:KB] - Checked: 2010-10-31 (RLVa-1.2.2a) | Modified: RLVa-1.2.2a
|
|
if ( (!fRlvShowNames) || (isSelf()) )
|
|
{
|
|
// [/RLVa:KB]
|
|
firstnameText=firstname->getString(); //Defer for later formatting
|
|
lastnameText=lastname->getString(); //Defer for later formatting
|
|
//const LLFontGL* font = LLFontGL::getFontSansSerif();
|
|
//std::string full_name =
|
|
// LLCacheName::buildFullName( firstname->getString(), lastname->getString() );
|
|
//addNameTagLine(full_name, name_tag_color, LLFontGL::NORMAL, font);
|
|
}
|
|
// [RLVa:KB] - Checked: 2010-10-31 (RLVa-1.2.2a) | Modified: RLVa-1.2.2a
|
|
else
|
|
{
|
|
std::string full_name = LLCacheName::buildFullName( firstname->getString(), lastname->getString() );
|
|
firstnameText = RlvStrings::getAnonym(full_name);
|
|
}
|
|
// [/RLVa:KB]
|
|
|
|
}
|
|
}
|
|
|
|
static const LLCachedControl<bool> allow_nameplate_override ("CCSAllowNameplateOverride", true);
|
|
std::string client_name = SHClientTagMgr::instance().getClientName(this, is_friend);
|
|
if(!client_name.empty())
|
|
{
|
|
client_name.insert(0,1,'(');
|
|
client_name.push_back(')');
|
|
}
|
|
std::string tag_format;
|
|
if(allow_nameplate_override && mCCSChatTextOverride)
|
|
tag_format=mCCSChatText;
|
|
else if(allow_nameplate_override && !mCCSAttachmentText.empty())
|
|
tag_format=mCCSAttachmentText;
|
|
else
|
|
{
|
|
if(!display_client_new_line)
|
|
tag_format=sRenderGroupTitles ? "%g\n%f %l %t" : "%f %l %t";
|
|
else
|
|
tag_format=sRenderGroupTitles ? "%g\n%f %l\n%t" : "%f %l\n%t";
|
|
}
|
|
|
|
// replace first name, last name and title
|
|
typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
|
|
boost::char_separator<char> sep("\r\n");
|
|
tokenizer tokens(tag_format, sep);
|
|
for(tokenizer::iterator it=tokens.begin();it!=tokens.end();++it)
|
|
{
|
|
std::string line = *it;
|
|
LLStringUtil::trim(line);
|
|
if(line.empty())
|
|
continue;
|
|
if(line == "%g")
|
|
addNameTagLine(groupText, name_tag_color, LLFontGL::NORMAL, LLFontGL::getFontSansSerifSmall());
|
|
else if(line == "%t")
|
|
addNameTagLine(client_name, name_tag_color, LLFontGL::NORMAL, LLFontGL::getFontSansSerifSmall());
|
|
else
|
|
{
|
|
boost::algorithm::replace_all(line, "%f", firstnameText);
|
|
if (lastnameText.empty()) //Entire displayname string crammed into firstname so eat the extra space.
|
|
{
|
|
boost::algorithm::replace_all(line, " %l", "");
|
|
boost::algorithm::replace_all(line, "%l", "");
|
|
}
|
|
else
|
|
{
|
|
boost::algorithm::replace_all(line, "%l", lastnameText);
|
|
}
|
|
boost::algorithm::replace_all(line, "%g", groupText);
|
|
boost::algorithm::replace_all(line, "%t", client_name);
|
|
LLStringUtil::trim(line);
|
|
if(!line.empty())
|
|
addNameTagLine(line, name_tag_color, LLFontGL::NORMAL, LLFontGL::getFontSansSerif());
|
|
}
|
|
}
|
|
|
|
mNameAway = is_away;
|
|
mNameBusy = is_busy;
|
|
mNameMute = is_muted;
|
|
mNameAppearance = is_appearance;
|
|
mNameFriend = is_friend;
|
|
mNameCloud = is_cloud;
|
|
mNameLangolier = is_langolier;
|
|
mTitle = title ? title->getString() : "";
|
|
LLStringFn::replace_ascii_controlchars(mTitle,LL_UNKNOWN_CHAR);
|
|
new_name = TRUE;
|
|
}
|
|
|
|
if (mVisibleChat || mVisibleTyping)
|
|
{
|
|
mNameText->setFont(LLFontGL::getFontSansSerif());
|
|
mNameText->setTextAlignment(LLHUDNameTag::ALIGN_TEXT_LEFT);
|
|
mNameText->setFadeDistance(CHAT_NORMAL_RADIUS * 2.f, 5.f);
|
|
|
|
std::deque<LLChat>::iterator chat_iter = mChats.begin();
|
|
mNameText->clearString();
|
|
|
|
LLColor4 new_chat = getNameTagColor(is_friend);
|
|
if (mVisibleChat)
|
|
{
|
|
LLColor4 normal_chat = lerp(new_chat, LLColor4(0.8f, 0.8f, 0.8f, 1.f), 0.7f);
|
|
LLColor4 old_chat = lerp(normal_chat, LLColor4(0.6f, 0.6f, 0.6f, 1.f), 0.7f);
|
|
if (mTyping && mChats.size() >= MAX_BUBBLE_CHAT_UTTERANCES)
|
|
{
|
|
++chat_iter;
|
|
}
|
|
|
|
for(; chat_iter != mChats.end(); ++chat_iter)
|
|
{
|
|
F32 chat_fade_amt = llclamp((F32)((LLFrameTimer::getElapsedSeconds() - chat_iter->mTime) / CHAT_FADE_TIME), 0.f, 4.f);
|
|
LLFontGL::StyleFlags style;
|
|
|
|
// Singu Note: The following tweak may be a bad idea, though they've asked for actions to be italicized, the chat type for actions becomes irrelevant
|
|
// If LLFontGL::StyleFlags wasn't the parameter type, font styles could be combined and underline could be used, but that may be unnatural...
|
|
static const LLCachedControl<bool> italicize("LiruItalicizeActions");
|
|
if (italicize && chat_iter->mChatStyle == CHAT_STYLE_IRC)
|
|
style = LLFontGL::ITALIC;
|
|
else
|
|
switch(chat_iter->mChatType)
|
|
{
|
|
case CHAT_TYPE_WHISPER:
|
|
style = LLFontGL::ITALIC;
|
|
break;
|
|
case CHAT_TYPE_SHOUT:
|
|
style = LLFontGL::BOLD;
|
|
break;
|
|
default:
|
|
style = LLFontGL::NORMAL;
|
|
break;
|
|
}
|
|
if (chat_fade_amt < 1.f)
|
|
{
|
|
F32 u = clamp_rescale(chat_fade_amt, 0.9f, 1.f, 0.f, 1.f);
|
|
mNameText->addLine(chat_iter->mText, lerp(new_chat, normal_chat, u), style);
|
|
}
|
|
else if (chat_fade_amt < 2.f)
|
|
{
|
|
F32 u = clamp_rescale(chat_fade_amt, 1.9f, 2.f, 0.f, 1.f);
|
|
mNameText->addLine(chat_iter->mText, lerp(normal_chat, old_chat, u), style);
|
|
}
|
|
else if (chat_fade_amt < 3.f)
|
|
{
|
|
// *NOTE: only remove lines down to minimum number
|
|
mNameText->addLine(chat_iter->mText, old_chat, style);
|
|
}
|
|
}
|
|
}
|
|
mNameText->setVisibleOffScreen(TRUE);
|
|
|
|
if (mTyping)
|
|
{
|
|
S32 dot_count = (llfloor(mTypingTimer.getElapsedTimeF32() * 3.f) + 2) % 3 + 1;
|
|
switch(dot_count)
|
|
{
|
|
case 1:
|
|
mNameText->addLine(".", new_chat);
|
|
break;
|
|
case 2:
|
|
mNameText->addLine("..", new_chat);
|
|
break;
|
|
case 3:
|
|
mNameText->addLine("...", new_chat);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// ...not using chat bubbles, just names
|
|
mNameText->setTextAlignment(LLHUDNameTag::ALIGN_TEXT_CENTER);
|
|
mNameText->setFadeDistance(CHAT_NORMAL_RADIUS, 5.f);
|
|
mNameText->setVisibleOffScreen(FALSE);
|
|
}
|
|
}
|
|
|
|
void LLVOAvatar::addNameTagLine(const std::string& line, const LLColor4& color, S32 style, const LLFontGL* font)
|
|
{
|
|
llassert(mNameText);
|
|
if (mVisibleChat || mVisibleTyping)
|
|
{
|
|
mNameText->addLabel(line);
|
|
}
|
|
else
|
|
{
|
|
mNameText->addLine(line, color, (LLFontGL::StyleFlags)style, font);
|
|
}
|
|
mNameString += line;
|
|
mNameString += '\n';
|
|
}
|
|
|
|
void LLVOAvatar::clearNameTag()
|
|
{
|
|
mNameString.clear();
|
|
if (mNameText)
|
|
{
|
|
mNameText->setLabel("");
|
|
mNameText->setString( "" );
|
|
}
|
|
}
|
|
|
|
//static
|
|
void LLVOAvatar::invalidateNameTag(const LLUUID& agent_id)
|
|
{
|
|
LLViewerObject* obj = gObjectList.findObject(agent_id);
|
|
if (!obj) return;
|
|
|
|
LLVOAvatar* avatar = dynamic_cast<LLVOAvatar*>(obj);
|
|
if (!avatar) return;
|
|
|
|
avatar->clearNameTag();
|
|
}
|
|
|
|
//static
|
|
void LLVOAvatar::invalidateNameTags()
|
|
{
|
|
std::vector<LLCharacter*>::iterator it = LLCharacter::sInstances.begin();
|
|
for ( ; it != LLCharacter::sInstances.end(); ++it)
|
|
{
|
|
LLVOAvatar* avatar = dynamic_cast<LLVOAvatar*>(*it);
|
|
if (!avatar) continue;
|
|
if (avatar->isDead()) continue;
|
|
|
|
avatar->clearNameTag();
|
|
}
|
|
}
|
|
|
|
// Compute name tag position during idle update
|
|
LLVector3 LLVOAvatar::idleUpdateNameTagPosition(const LLVector3& root_pos_last)
|
|
{
|
|
LLQuaternion root_rot = mRoot->getWorldRotation();
|
|
LLQuaternion inv_root_rot = ~root_rot;
|
|
LLVector3 pixel_right_vec;
|
|
LLVector3 pixel_up_vec;
|
|
LLViewerCamera::getInstance()->getPixelVectors(root_pos_last, pixel_up_vec, pixel_right_vec);
|
|
LLVector3 camera_to_av = root_pos_last - LLViewerCamera::getInstance()->getOrigin();
|
|
camera_to_av.normalize();
|
|
LLVector3 local_camera_at = camera_to_av * inv_root_rot;
|
|
LLVector3 local_camera_up = camera_to_av % LLViewerCamera::getInstance()->getLeftAxis();
|
|
local_camera_up.normalize();
|
|
local_camera_up = local_camera_up * inv_root_rot;
|
|
|
|
|
|
// position is based on head position, does not require mAvatarOffset here. - Nyx
|
|
LLVector3 avatar_ellipsoid(mBodySize.mV[VX] * 0.4f,
|
|
mBodySize.mV[VY] * 0.4f,
|
|
mBodySize.mV[VZ] * NAMETAG_VERT_OFFSET_WEIGHT);
|
|
|
|
local_camera_up.scaleVec(avatar_ellipsoid);
|
|
local_camera_at.scaleVec(avatar_ellipsoid);
|
|
|
|
LLVector3 head_offset = (mHeadp->getLastWorldPosition() - mRoot->getLastWorldPosition()) * inv_root_rot;
|
|
|
|
if (dist_vec(head_offset, mTargetRootToHeadOffset) > NAMETAG_UPDATE_THRESHOLD)
|
|
{
|
|
mTargetRootToHeadOffset = head_offset;
|
|
}
|
|
|
|
mCurRootToHeadOffset = lerp(mCurRootToHeadOffset, mTargetRootToHeadOffset, LLSmoothInterpolation::getInterpolant(0.2f));
|
|
|
|
LLVector3 name_position = mRoot->getLastWorldPosition() + (mCurRootToHeadOffset * root_rot);
|
|
name_position += (local_camera_up * root_rot) - (projected_vec(local_camera_at * root_rot, camera_to_av));
|
|
name_position += pixel_up_vec * NAMETAG_VERTICAL_SCREEN_OFFSET;
|
|
|
|
return name_position;
|
|
}
|
|
|
|
void LLVOAvatar::idleUpdateNameTagAlpha(BOOL new_name, F32 alpha)
|
|
{
|
|
llassert(mNameText);
|
|
|
|
if (new_name
|
|
|| alpha != mNameAlpha)
|
|
{
|
|
mNameText->setAlpha(alpha);
|
|
mNameAlpha = alpha;
|
|
}
|
|
}
|
|
|
|
LLColor4 LLVOAvatar::getNameTagColor(bool is_friend)
|
|
{
|
|
LLColor4 color;
|
|
if(SHClientTagMgr::instance().getClientColor(this,true,color))
|
|
return color;
|
|
else
|
|
{
|
|
//Always fall back to this color, for now.
|
|
static const LLCachedControl<LLColor4> avatar_name_color(gColors,"AvatarNameColor",LLColor4(LLColor4U(251, 175, 93, 255)) );
|
|
return avatar_name_color;
|
|
}
|
|
/*else if (LLAvatarName::useDisplayNames())
|
|
{
|
|
// ...color based on whether username "matches" a computed display name
|
|
LLAvatarName av_name;
|
|
if (LLAvatarNameCache::get(getID(), &av_name) && av_name.isDisplayNameDefault())
|
|
{
|
|
color_name = "NameTagMatch";
|
|
}
|
|
else
|
|
{
|
|
color_name = "NameTagMismatch";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// ...not using display names
|
|
color_name = "NameTagLegacy";
|
|
}*/
|
|
}
|
|
|
|
void LLVOAvatar::idleUpdateBelowWater()
|
|
{
|
|
F32 avatar_height = (F32)(getPositionGlobal().mdV[VZ]);
|
|
|
|
F32 water_height;
|
|
water_height = getRegion()->getWaterHeight();
|
|
|
|
BOOL old_below = mBelowWater;
|
|
mBelowWater = avatar_height < water_height;
|
|
if (old_below != mBelowWater)
|
|
LLFloaterAO::toggleSwim(mBelowWater);
|
|
}
|
|
|
|
void LLVOAvatar::slamPosition()
|
|
{
|
|
gAgent.setPositionAgent(getPositionAgent());
|
|
mRoot->setWorldPosition(getPositionAgent()); // teleport
|
|
setChanged(TRANSLATED);
|
|
if (mDrawable.notNull())
|
|
{
|
|
gPipeline.updateMoveNormalAsync(mDrawable);
|
|
}
|
|
mRoot->updateWorldMatrixChildren();
|
|
}
|
|
|
|
bool LLVOAvatar::isVisuallyMuted() const
|
|
{
|
|
if (!isSelf())
|
|
{
|
|
static const LLCachedControl<bool> show_muted(gSavedSettings, "LiruLegacyDisplayMuteds", false);
|
|
return (!show_muted && LLMuteList::getInstance()->isMuted(getID()) ||
|
|
// [RLVa:LF] - RLV 2.9 camavdist
|
|
(gRlvHandler.hasBehaviour(RLV_BHVR_CAMAVDIST) && (gAgent.getPosGlobalFromAgent(const_cast<LLVOAvatar&>(*this).getCharacterPosition()) - gAgent.getPosGlobalFromAgent(gAgentAvatarp->getRenderPosition())).magVec() > gRlvHandler.camPole(RLV_BHVR_CAMAVDIST)) ||
|
|
// [/RLVa:LF]
|
|
isLangolier() ||
|
|
isTooComplex());
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void LLVOAvatar::resetFreezeTime()
|
|
{
|
|
bool dead = mFreezeTimeDead;
|
|
mFreezeTimeLangolier = mFreezeTimeDead = false;
|
|
if (dead)
|
|
{
|
|
markDead();
|
|
}
|
|
}
|
|
|
|
|
|
void LLVOAvatar::updateAppearanceMessageDebugText()
|
|
{
|
|
S32 central_bake_version = -1;
|
|
LLViewerRegion* region = getRegion();
|
|
if (region)
|
|
{
|
|
central_bake_version = getRegion()->getCentralBakeVersion();
|
|
}
|
|
bool all_baked_downloaded = allBakedTexturesCompletelyDownloaded();
|
|
bool all_local_downloaded = allLocalTexturesCompletelyDownloaded();
|
|
std::string debug_line = llformat("%s%s - mLocal: %d, mEdit: %d, mUSB: %d, CBV: %d",
|
|
isSelf() ? (all_local_downloaded ? "L" : "l") : "-",
|
|
all_baked_downloaded ? "B" : "b",
|
|
mUseLocalAppearance, mIsEditingAppearance,
|
|
mUseServerBakes, central_bake_version);
|
|
std::string origin_string = bakedTextureOriginInfo();
|
|
debug_line += " [" + origin_string + "]";
|
|
S32 curr_cof_version = LLAppearanceMgr::instance().getCOFVersion();
|
|
S32 last_request_cof_version = mLastUpdateRequestCOFVersion;
|
|
S32 last_received_cof_version = mLastUpdateReceivedCOFVersion;
|
|
if (isSelf())
|
|
{
|
|
debug_line += llformat(" - cof: %d req: %d rcv:%d",
|
|
curr_cof_version, last_request_cof_version, last_received_cof_version);
|
|
}
|
|
else
|
|
{
|
|
debug_line += llformat(" - cof rcv:%d", last_received_cof_version);
|
|
}
|
|
debug_line += llformat(" bsz-z: %.3f", mBodySize[2]);
|
|
if (mAvatarOffset[2] != 0.0f)
|
|
{
|
|
debug_line += llformat("avofs-z: %.3f", mAvatarOffset[2]);
|
|
}
|
|
bool hover_enabled = region && region->avatarHoverHeightEnabled();
|
|
debug_line += hover_enabled ? " H" : " h";
|
|
const LLVector3& hover_offset = getHoverOffset();
|
|
if (hover_offset[2] != 0.0)
|
|
{
|
|
debug_line += llformat(" hov_z: %.3f", hover_offset[2]);
|
|
debug_line += llformat(" %s", (isSitting() ? "S" : "T"));
|
|
debug_line += llformat("%s", (isMotionActive(ANIM_AGENT_SIT_GROUND_CONSTRAINED) ? "G" : "-"));
|
|
}
|
|
LLVector3 ankle_right_pos_agent = mFootRightp->getWorldPosition();
|
|
LLVector3 normal;
|
|
LLVector3 ankle_right_ground_agent = ankle_right_pos_agent;
|
|
resolveHeightAgent(ankle_right_pos_agent, ankle_right_ground_agent, normal);
|
|
F32 rightElev = llmax(-0.2f, ankle_right_pos_agent.mV[VZ] - ankle_right_ground_agent.mV[VZ]);
|
|
debug_line += llformat(" relev %.3f", rightElev);
|
|
|
|
LLVector3 root_pos = mRoot->getPosition();
|
|
LLVector3 pelvis_pos = mPelvisp->getPosition();
|
|
debug_line += llformat(" rp %.3f pp %.3f", root_pos[2], pelvis_pos[2]);
|
|
|
|
S32 is_visible = (S32) isVisible();
|
|
S32 is_m_visible = (S32) mVisible;
|
|
debug_line += llformat(" v %d/%d", is_visible, is_m_visible);
|
|
F32 elapsed = mLastAppearanceMessageTimer.getElapsedTimeF32();
|
|
static const char *elapsed_chars = "Xx*...";
|
|
U32 bucket = U32(elapsed*2);
|
|
if (bucket < strlen(elapsed_chars))
|
|
{
|
|
debug_line += llformat(" %c", elapsed_chars[bucket]);
|
|
}
|
|
addDebugText(debug_line);
|
|
}
|
|
|
|
LLViewerInventoryItem* getObjectInventoryItem(LLViewerObject *vobj, LLUUID asset_id)
|
|
{
|
|
LLViewerInventoryItem *item = NULL;
|
|
|
|
if (vobj)
|
|
{
|
|
if (vobj->getInventorySerial() <= 0)
|
|
{
|
|
vobj->requestInventory();
|
|
}
|
|
item = vobj->getInventoryItemByAsset(asset_id);
|
|
}
|
|
return item;
|
|
}
|
|
|
|
LLViewerInventoryItem* recursiveGetObjectInventoryItem(LLViewerObject *vobj, LLUUID asset_id)
|
|
{
|
|
LLViewerInventoryItem *item = getObjectInventoryItem(vobj, asset_id);
|
|
if (!item)
|
|
{
|
|
LLViewerObject::const_child_list_t& children = vobj->getChildren();
|
|
for (LLViewerObject::const_child_list_t::const_iterator it = children.begin();
|
|
it != children.end(); ++it)
|
|
{
|
|
LLViewerObject *childp = *it;
|
|
item = getObjectInventoryItem(childp, asset_id);
|
|
if (item)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return item;
|
|
}
|
|
|
|
void LLVOAvatar::updateAnimationDebugText()
|
|
{
|
|
addDebugText(llformat("at=%.1f", mMotionController.getAnimTime()));
|
|
for (LLMotionController::motion_list_t::iterator iter = mMotionController.getActiveMotions().begin();
|
|
iter != mMotionController.getActiveMotions().end(); ++iter)
|
|
{
|
|
LLMotion* motionp = *iter;
|
|
if (motionp->getMinPixelArea() < getPixelArea())
|
|
{
|
|
std::string output;
|
|
std::string motion_name = motionp->getName();
|
|
if (motion_name.empty())
|
|
{
|
|
if (isControlAvatar())
|
|
{
|
|
LLControlAvatar *control_av = dynamic_cast<LLControlAvatar*>(this);
|
|
// Try to get name from inventory of associated object
|
|
LLVOVolume *volp = control_av->mRootVolp;
|
|
LLViewerInventoryItem *item = recursiveGetObjectInventoryItem(volp, motionp->getID());
|
|
if (item)
|
|
{
|
|
motion_name = item->getName();
|
|
}
|
|
}
|
|
}
|
|
if (motion_name.empty())
|
|
{
|
|
std::string name;
|
|
name = motionp->getID().asString();
|
|
output = llformat("%s - %d",
|
|
name.c_str(),
|
|
(U32)motionp->getPriority());
|
|
}
|
|
else
|
|
{
|
|
output = llformat("%s - %d",
|
|
motion_name.c_str(),
|
|
(U32)motionp->getPriority());
|
|
}
|
|
if (motionp->server())
|
|
{
|
|
#ifdef SHOW_ASSERT
|
|
output += llformat(" rt=%.1f r=%d s=0x%xl", motionp->getRuntime(), motionp->mReadyEvents, motionp->server());
|
|
#else
|
|
output += llformat(" rt=%.1f s=0x%xl", motionp->getRuntime(), motionp->server());
|
|
#endif
|
|
}
|
|
addDebugText(output);
|
|
}
|
|
}
|
|
}
|
|
|
|
void LLVOAvatar::updateDebugText()
|
|
{
|
|
// Leave mDebugText uncleared here, in case a derived class has added some state first
|
|
|
|
if (gSavedSettings.getBOOL("DebugAvatarAppearanceMessage"))
|
|
{
|
|
updateAppearanceMessageDebugText();
|
|
}
|
|
|
|
if (gSavedSettings.getBOOL("DebugAvatarCompositeBaked"))
|
|
{
|
|
if (!mBakedTextureDebugText.empty())
|
|
addDebugText(mBakedTextureDebugText);
|
|
}
|
|
|
|
// Develop -> Avatar -> Animation Info
|
|
if (LLVOAvatar::sShowAnimationDebug)
|
|
{
|
|
updateAnimationDebugText();
|
|
}
|
|
|
|
if (!mDebugText.size() && mText.notNull())
|
|
{
|
|
mText->markDead();
|
|
mText = NULL;
|
|
}
|
|
else if (mDebugText.size())
|
|
{
|
|
setDebugText(mDebugText);
|
|
}
|
|
mDebugText.clear();
|
|
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
// updateFootstepSounds
|
|
// Factored out from updateCharacter()
|
|
// Generate footstep sounds when feet hit the ground
|
|
//------------------------------------------------------------------------
|
|
void LLVOAvatar::updateFootstepSounds()
|
|
{
|
|
if (mIsDummy)
|
|
{
|
|
return;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// Find the ground under each foot, these are used for a variety
|
|
// of things that follow
|
|
//-------------------------------------------------------------------------
|
|
LLVector3 ankle_left_pos_agent = mFootLeftp->getWorldPosition();
|
|
LLVector3 ankle_right_pos_agent = mFootRightp->getWorldPosition();
|
|
|
|
LLVector3 ankle_left_ground_agent = ankle_left_pos_agent;
|
|
LLVector3 ankle_right_ground_agent = ankle_right_pos_agent;
|
|
LLVector3 normal;
|
|
resolveHeightAgent(ankle_left_pos_agent, ankle_left_ground_agent, normal);
|
|
resolveHeightAgent(ankle_right_pos_agent, ankle_right_ground_agent, normal);
|
|
|
|
F32 leftElev = llmax(-0.2f, ankle_left_pos_agent.mV[VZ] - ankle_left_ground_agent.mV[VZ]);
|
|
F32 rightElev = llmax(-0.2f, ankle_right_pos_agent.mV[VZ] - ankle_right_ground_agent.mV[VZ]);
|
|
|
|
if (!isSitting())
|
|
{
|
|
//-------------------------------------------------------------------------
|
|
// Figure out which foot is on ground
|
|
//-------------------------------------------------------------------------
|
|
if (!mInAir)
|
|
{
|
|
if ((leftElev < 0.0f) || (rightElev < 0.0f))
|
|
{
|
|
ankle_left_pos_agent = mFootLeftp->getWorldPosition();
|
|
ankle_right_pos_agent = mFootRightp->getWorldPosition();
|
|
leftElev = ankle_left_pos_agent.mV[VZ] - ankle_left_ground_agent.mV[VZ];
|
|
rightElev = ankle_right_pos_agent.mV[VZ] - ankle_right_ground_agent.mV[VZ];
|
|
}
|
|
}
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// Generate footstep sounds when feet hit the ground
|
|
//-------------------------------------------------------------------------
|
|
const LLUUID AGENT_FOOTSTEP_ANIMS[] = {ANIM_AGENT_WALK, ANIM_AGENT_RUN, ANIM_AGENT_LAND};
|
|
const S32 NUM_AGENT_FOOTSTEP_ANIMS = LL_ARRAY_SIZE(AGENT_FOOTSTEP_ANIMS);
|
|
|
|
if ( gAudiop && isAnyAnimationSignaled(AGENT_FOOTSTEP_ANIMS, NUM_AGENT_FOOTSTEP_ANIMS) )
|
|
{
|
|
BOOL playSound = FALSE;
|
|
LLVector3 foot_pos_agent;
|
|
|
|
BOOL onGroundLeft = (leftElev <= 0.05f);
|
|
BOOL onGroundRight = (rightElev <= 0.05f);
|
|
|
|
// did left foot hit the ground?
|
|
if ( onGroundLeft && !mWasOnGroundLeft )
|
|
{
|
|
foot_pos_agent = ankle_left_pos_agent;
|
|
playSound = TRUE;
|
|
}
|
|
|
|
// did right foot hit the ground?
|
|
if ( onGroundRight && !mWasOnGroundRight )
|
|
{
|
|
foot_pos_agent = ankle_right_pos_agent;
|
|
playSound = TRUE;
|
|
}
|
|
|
|
mWasOnGroundLeft = onGroundLeft;
|
|
mWasOnGroundRight = onGroundRight;
|
|
|
|
if ( playSound )
|
|
{
|
|
const F32 STEP_VOLUME = 0.5f;
|
|
const LLUUID& step_sound_id = getStepSound();
|
|
|
|
LLVector3d foot_pos_global = gAgent.getPosGlobalFromAgent(foot_pos_agent);
|
|
|
|
if (LLViewerParcelMgr::getInstance()->canHearSound(foot_pos_global)
|
|
&& !LLMuteList::getInstance()->isMuted(getID(), LLMute::flagObjectSounds))
|
|
{
|
|
gAudiop->triggerSound(step_sound_id, getID(), STEP_VOLUME, LLAudioEngine::AUDIO_TYPE_AMBIENT, foot_pos_global);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
// computeUpdatePeriod()
|
|
// Factored out from updateCharacter()
|
|
// Set new value for mUpdatePeriod based on distance and various other factors.
|
|
//------------------------------------------------------------------------
|
|
void LLVOAvatar::computeUpdatePeriod()
|
|
{
|
|
bool visually_muted = isVisuallyMuted();
|
|
if (mDrawable.notNull()
|
|
&& isVisible()
|
|
&& (!isSelf() || visually_muted)
|
|
&& !isUIAvatar()
|
|
&& sUseImpostors
|
|
&& !mNeedsAnimUpdate
|
|
&& !sFreezeCounter)
|
|
{
|
|
const LLVector4a* ext = mDrawable->getSpatialExtents();
|
|
LLVector4a size;
|
|
size.setSub(ext[1],ext[0]);
|
|
F32 mag = size.getLength3().getF32()*0.5f;
|
|
|
|
|
|
F32 impostor_area = 256.f*512.f*(8.125f - LLVOAvatar::sLODFactor*8.f);
|
|
if (visually_muted)
|
|
{ // visually muted avatars update at 16 hz
|
|
mUpdatePeriod = 16;
|
|
}
|
|
else if (! shouldImpostor()
|
|
|| mDrawable->mDistanceWRTCamera < 1.f + mag)
|
|
{ // first 25% of max visible avatars are not impostored
|
|
// also, don't impostor avatars whose bounding box may be penetrating the
|
|
// impostor camera near clip plane
|
|
mUpdatePeriod = 1;
|
|
}
|
|
else if ( shouldImpostor(4) )
|
|
{ //background avatars are REALLY slow updating impostors
|
|
mUpdatePeriod = 16;
|
|
}
|
|
else if ( shouldImpostor(3) )
|
|
{ //back 25% of max visible avatars are slow updating impostors
|
|
mUpdatePeriod = 8;
|
|
}
|
|
else if (mImpostorPixelArea <= impostor_area)
|
|
{ // stuff in between gets an update period based on pixel area
|
|
mUpdatePeriod = llclamp((S32) sqrtf(impostor_area*4.f/mImpostorPixelArea), 2, 8);
|
|
}
|
|
else
|
|
{
|
|
//nearby avatars, update the impostors more frequently.
|
|
mUpdatePeriod = 4;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
mUpdatePeriod = 1;
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
// updateOrientation()
|
|
// Factored out from updateCharacter()
|
|
// This is used by updateCharacter() to update the avatar's orientation:
|
|
// - updates mTurning state
|
|
// - updates rotation of the mRoot joint in the skeleton
|
|
// - for self, calls setControlFlags() to notify the simulator about any turns
|
|
//------------------------------------------------------------------------
|
|
void LLVOAvatar::updateOrientation(LLAgent& agent, F32 speed, F32 delta_time)
|
|
{
|
|
LLQuaternion iQ;
|
|
LLVector3 upDir( 0.0f, 0.0f, 1.0f );
|
|
|
|
// Compute a forward direction vector derived from the primitive rotation
|
|
// and the velocity vector. When walking or jumping, don't let body deviate
|
|
// more than 90 from the view, if necessary, flip the velocity vector.
|
|
|
|
LLVector3 primDir;
|
|
if (isSelf())
|
|
{
|
|
primDir = agent.getAtAxis() - projected_vec(agent.getAtAxis(), agent.getReferenceUpVector());
|
|
primDir.normalize();
|
|
}
|
|
else
|
|
{
|
|
primDir = getRotation().getMatrix3().getFwdRow();
|
|
}
|
|
LLVector3 velDir = getVelocity();
|
|
velDir.normalize();
|
|
static LLCachedControl<bool> TurnAround("TurnAroundWhenWalkingBackwards");
|
|
if (!TurnAround && (mSignaledAnimations.find(ANIM_AGENT_WALK) != mSignaledAnimations.end()))
|
|
{
|
|
F32 vpD = velDir * primDir;
|
|
if (vpD < -0.5f)
|
|
{
|
|
velDir *= -1.0f;
|
|
}
|
|
}
|
|
LLVector3 fwdDir = lerp(primDir, velDir, clamp_rescale(speed, 0.5f, 2.0f, 0.0f, 1.0f));
|
|
if (isSelf() && gAgentCamera.cameraMouselook())
|
|
{
|
|
// make sure fwdDir stays in same general direction as primdir
|
|
if (gAgent.getFlying())
|
|
{
|
|
fwdDir = LLViewerCamera::getInstance()->getAtAxis();
|
|
}
|
|
else
|
|
{
|
|
LLVector3 at_axis = LLViewerCamera::getInstance()->getAtAxis();
|
|
LLVector3 up_vector = gAgent.getReferenceUpVector();
|
|
at_axis -= up_vector * (at_axis * up_vector);
|
|
at_axis.normalize();
|
|
|
|
F32 dot = fwdDir * at_axis;
|
|
if (dot < 0.f)
|
|
{
|
|
fwdDir -= 2.f * at_axis * dot;
|
|
fwdDir.normalize();
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
LLQuaternion root_rotation = LLMatrix4(mRoot->getWorldMatrix().getF32ptr()).quaternion();
|
|
F32 root_roll, root_pitch, root_yaw;
|
|
root_rotation.getEulerAngles(&root_roll, &root_pitch, &root_yaw);
|
|
|
|
if (sDebugAvatarRotation)
|
|
{
|
|
LL_INFOS() << "root_roll " << RAD_TO_DEG * root_roll
|
|
<< " root_pitch " << RAD_TO_DEG * root_pitch
|
|
<< " root_yaw " << RAD_TO_DEG * root_yaw
|
|
<< LL_ENDL;
|
|
}
|
|
|
|
// When moving very slow, the pelvis is allowed to deviate from the
|
|
// forward direction to allow it to hold it's position while the torso
|
|
// and head turn. Once in motion, it must conform however.
|
|
BOOL self_in_mouselook = isSelf() && gAgentCamera.cameraMouselook();
|
|
|
|
LLVector3 pelvisDir( mRoot->getWorldMatrix().getRow<LLMatrix4a::ROW_FWD>().getF32ptr() );
|
|
|
|
static const LLCachedControl<F32> s_pelvis_rot_threshold_slow(gSavedSettings, "AvatarRotateThresholdSlow", 60.0);
|
|
static const LLCachedControl<F32> s_pelvis_rot_threshold_fast(gSavedSettings, "AvatarRotateThresholdFast", 2.0);
|
|
static const LLCachedControl<bool> useRealisticMouselook("UseRealisticMouselook");
|
|
F32 pelvis_rot_threshold = clamp_rescale(speed, 0.1f, 1.0f, useRealisticMouselook ? s_pelvis_rot_threshold_slow * 2 : s_pelvis_rot_threshold_slow, s_pelvis_rot_threshold_fast);
|
|
|
|
if (self_in_mouselook && !useRealisticMouselook)
|
|
{
|
|
pelvis_rot_threshold *= MOUSELOOK_PELVIS_FOLLOW_FACTOR;
|
|
}
|
|
pelvis_rot_threshold *= DEG_TO_RAD;
|
|
|
|
F32 angle = angle_between( pelvisDir, fwdDir );
|
|
|
|
// The avatar's root is allowed to have a yaw that deviates widely
|
|
// from the forward direction, but if roll or pitch are off even
|
|
// a little bit we need to correct the rotation.
|
|
if(root_roll < 1.f * DEG_TO_RAD
|
|
&& root_pitch < 5.f * DEG_TO_RAD)
|
|
{
|
|
// smaller correction vector means pelvis follows prim direction more closely
|
|
if (!mTurning && angle > pelvis_rot_threshold*0.75f)
|
|
{
|
|
mTurning = TRUE;
|
|
}
|
|
|
|
// use tighter threshold when turning
|
|
if (mTurning)
|
|
{
|
|
pelvis_rot_threshold *= 0.4f;
|
|
}
|
|
|
|
// am I done turning?
|
|
if (angle < pelvis_rot_threshold)
|
|
{
|
|
mTurning = FALSE;
|
|
}
|
|
|
|
LLVector3 correction_vector = (pelvisDir - fwdDir) * clamp_rescale(angle, pelvis_rot_threshold*0.75f, pelvis_rot_threshold, 1.0f, 0.0f);
|
|
fwdDir += correction_vector;
|
|
}
|
|
else
|
|
{
|
|
mTurning = FALSE;
|
|
}
|
|
|
|
// Now compute the full world space rotation for the whole body (wQv)
|
|
LLVector3 leftDir = upDir % fwdDir;
|
|
leftDir.normalize();
|
|
fwdDir = leftDir % upDir;
|
|
LLQuaternion wQv( fwdDir, leftDir, upDir );
|
|
|
|
if (isSelf() && mTurning)
|
|
{
|
|
if ((fwdDir % pelvisDir) * upDir > 0.f)
|
|
{
|
|
gAgent.setControlFlags(AGENT_CONTROL_TURN_RIGHT);
|
|
}
|
|
else
|
|
{
|
|
gAgent.setControlFlags(AGENT_CONTROL_TURN_LEFT);
|
|
}
|
|
}
|
|
|
|
// Set the root rotation, but do so incrementally so that it
|
|
// lags in time by some fixed amount.
|
|
//F32 u = LLSmoothInterpolation::getInterpolant(PELVIS_LAG);
|
|
F32 pelvis_lag_time = 0.f;
|
|
if (self_in_mouselook)
|
|
{
|
|
pelvis_lag_time = PELVIS_LAG_MOUSELOOK;
|
|
}
|
|
else if (mInAir)
|
|
{
|
|
pelvis_lag_time = PELVIS_LAG_FLYING;
|
|
// increase pelvis lag time when moving slowly
|
|
pelvis_lag_time *= clamp_rescale(mSpeedAccum, 0.f, 15.f, 3.f, 1.f);
|
|
}
|
|
else
|
|
{
|
|
pelvis_lag_time = PELVIS_LAG_WALKING;
|
|
}
|
|
|
|
F32 u = llclamp((delta_time / pelvis_lag_time), 0.0f, 1.0f);
|
|
|
|
mRoot->setWorldRotation( slerp(u, mRoot->getWorldRotation(), wQv) );
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
// updateTimeStep()
|
|
// Factored out from updateCharacter().
|
|
//
|
|
// Updates the time step used by the motion controller, based on area
|
|
// and avatar count criteria. This will also stop the
|
|
// ANIM_AGENT_WALK_ADJUST animation under some circumstances.
|
|
// ------------------------------------------------------------------------
|
|
void LLVOAvatar::updateTimeStep()
|
|
{
|
|
if (!isSelf() && !isUIAvatar()) // ie, non-self avatars, and animated objects will be affected.
|
|
{
|
|
F32 time_quantum = clamp_rescale((F32)sInstances.size(), 10.f, 35.f, 0.f, 0.25f);
|
|
F32 pixel_area_scale = clamp_rescale(mPixelArea, 100, 5000, 1.f, 0.f);
|
|
F32 time_step = time_quantum * pixel_area_scale;
|
|
if (time_step != 0.f)
|
|
{
|
|
// disable walk motion servo controller as it doesn't work with motion timesteps
|
|
stopMotion(ANIM_AGENT_WALK_ADJUST);
|
|
removeAnimationData("Walk Speed");
|
|
}
|
|
mMotionController.setTimeStep(time_step);
|
|
}
|
|
}
|
|
|
|
void LLVOAvatar::updateRootPositionAndRotation(LLAgent& agent, F32 speed, bool was_sit_ground_constrained)
|
|
{
|
|
if (!(isSitting() && getParent()))
|
|
{
|
|
// This case includes all configurations except sitting on an
|
|
// object, so does include ground sit.
|
|
|
|
//--------------------------------------------------------------------
|
|
// get timing info
|
|
// handle initial condition case
|
|
//--------------------------------------------------------------------
|
|
F32 animation_time = mAnimTimer.getElapsedTimeF32();
|
|
if (mTimeLast == 0.0f)
|
|
{
|
|
mTimeLast = animation_time;
|
|
|
|
// put the pelvis at slaved position/mRotation
|
|
mRoot->setWorldPosition( getPositionAgent() ); // first frame
|
|
mRoot->setWorldRotation( getRotation() );
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
// dont' let dT get larger than 1/5th of a second
|
|
//--------------------------------------------------------------------
|
|
F32 delta_time = animation_time - mTimeLast;
|
|
|
|
delta_time = llclamp( delta_time, DELTA_TIME_MIN, DELTA_TIME_MAX );
|
|
mTimeLast = animation_time;
|
|
|
|
mSpeedAccum = (mSpeedAccum * 0.95f) + (speed * 0.05f);
|
|
|
|
//--------------------------------------------------------------------
|
|
// compute the position of the avatar's root
|
|
//--------------------------------------------------------------------
|
|
LLVector3d root_pos;
|
|
LLVector3d ground_under_pelvis;
|
|
|
|
if (isSelf())
|
|
{
|
|
gAgent.setPositionAgent(getRenderPosition());
|
|
}
|
|
|
|
root_pos = gAgent.getPosGlobalFromAgent(getRenderPosition());
|
|
root_pos.mdV[VZ] += getVisualParamWeight(AVATAR_HOVER);
|
|
|
|
LLVector3 normal;
|
|
resolveHeightGlobal(root_pos, ground_under_pelvis, normal);
|
|
F32 foot_to_ground = (F32) (root_pos.mdV[VZ] - mPelvisToFoot - ground_under_pelvis.mdV[VZ]);
|
|
BOOL in_air = ((!LLWorld::getInstance()->getRegionFromPosGlobal(ground_under_pelvis)) ||
|
|
foot_to_ground > FOOT_GROUND_COLLISION_TOLERANCE);
|
|
|
|
if (in_air && !mInAir)
|
|
{
|
|
mTimeInAir.reset();
|
|
}
|
|
mInAir = in_air;
|
|
|
|
// SL-402: with the ability to animate the position of joints
|
|
// that affect the body size calculation, computed body size
|
|
// can get stale much more easily. Simplest fix is to update
|
|
// it frequently.
|
|
// SL-427: this appears to be too frequent, moving to only do on animation state change.
|
|
//computeBodySize();
|
|
|
|
// correct for the fact that the pelvis is not necessarily the center
|
|
// of the agent's physical representation
|
|
root_pos.mdV[VZ] -= (0.5f * mBodySize.mV[VZ]) - mPelvisToFoot;
|
|
if (!isSitting() && !was_sit_ground_constrained)
|
|
{
|
|
root_pos += LLVector3d(getHoverOffset());
|
|
}
|
|
|
|
LLControlAvatar *cav = asControlAvatar();
|
|
if (cav)
|
|
{
|
|
// SL-1350: Moved to LLDrawable::updateXform()
|
|
cav->matchVolumeTransform();
|
|
}
|
|
else
|
|
{
|
|
LLVector3 newPosition = gAgent.getPosAgentFromGlobal(root_pos);
|
|
|
|
if (newPosition != mRoot->getXform()->getWorldPosition())
|
|
{
|
|
mRoot->touch();
|
|
mRoot->setWorldPosition( newPosition ); // regular update
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
// Propagate viewer object rotation to root of avatar
|
|
//--------------------------------------------------------------------
|
|
if (!isControlAvatar() && !isAnyAnimationSignaled(AGENT_NO_ROTATE_ANIMS, NUM_AGENT_NO_ROTATE_ANIMS))
|
|
{
|
|
// Rotation fixups for avatars in motion.
|
|
// Skip for animated objects.
|
|
updateOrientation(agent, speed, delta_time);
|
|
}
|
|
}
|
|
else if (mDrawable.notNull())
|
|
{
|
|
LLVector3 pos = mDrawable->getPosition();
|
|
const LLQuaternion& rot = mDrawable->getRotation();
|
|
pos += getHoverOffset() * rot;
|
|
mRoot->setPosition(pos);
|
|
mRoot->setRotation(rot);
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
// updateCharacter()
|
|
//
|
|
// This is called for all avatars, so there are 4 possible situations:
|
|
//
|
|
// 1) Avatar is your own. In this case the class is LLVOAvatarSelf,
|
|
// isSelf() is true, and agent specifies the corresponding agent
|
|
// information for you. In all the other cases, agent is irrelevant
|
|
// and it would be less confusing if it were null or something.
|
|
//
|
|
// 2) Avatar is controlled by another resident. Class is LLVOAvatar,
|
|
// and isSelf() is false.
|
|
//
|
|
// 3) Avatar is the controller for an animated object. Class is
|
|
// LLControlAvatar and mIsDummy is true. Avatar is a purely
|
|
// viewer-side entity with no representation on the simulator.
|
|
//
|
|
// 4) Avatar is a UI avatar used in some areas of the UI, such as when
|
|
// previewing uploaded animations. Class is LLUIAvatar, and mIsDummy
|
|
// is true. Avatar is purely viewer-side with no representation on the
|
|
// simulator.
|
|
//
|
|
//------------------------------------------------------------------------
|
|
BOOL LLVOAvatar::updateCharacter(LLAgent &agent)
|
|
{
|
|
// Frozen!
|
|
if (areAnimationsPaused())
|
|
{
|
|
updateMotions(LLCharacter::NORMAL_UPDATE); // This is necessary to get unpaused again.
|
|
return FALSE;
|
|
}
|
|
|
|
updateDebugText();
|
|
|
|
if (gNoRender)
|
|
{
|
|
// Hack if we're running drones...
|
|
if (isSelf())
|
|
{
|
|
gAgent.setPositionAgent(getPositionAgent());
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
if (!mIsBuilt)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
bool visible = isVisible();
|
|
bool is_control_avatar = isControlAvatar(); // capture state to simplify tracing
|
|
bool is_attachment = false;
|
|
if (is_control_avatar)
|
|
{
|
|
LLControlAvatar *cav = asControlAvatar();
|
|
is_attachment = cav && cav->mRootVolp && cav->mRootVolp->isAttachment(); // For attached animated objects
|
|
}
|
|
|
|
// For fading out the names above heads, only let the timer
|
|
// run if we're visible.
|
|
if (mDrawable.notNull() && !visible)
|
|
{
|
|
mTimeVisible.reset();
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
// The rest should only be done occasionally for far away avatars.
|
|
// Set mUpdatePeriod and visible based on distance and other criteria.
|
|
//--------------------------------------------------------------------
|
|
computeUpdatePeriod();
|
|
visible = (LLDrawable::getCurrentFrame()+mID.mData[0])%mUpdatePeriod == 0 ? TRUE : FALSE;
|
|
|
|
//--------------------------------------------------------------------
|
|
|
|
// don't early out for your own avatar, as we rely on your animations playing reliably
|
|
// for example, the "turn around" animation when entering customize avatar needs to trigger
|
|
// even when your avatar is offscreen
|
|
if (!visible && !isSelf())
|
|
{
|
|
updateMotions(LLCharacter::HIDDEN_UPDATE);
|
|
return FALSE;
|
|
}
|
|
|
|
// change animation time quanta based on avatar render load
|
|
//updateTimeStep();
|
|
if (getParent() && !isSitting())
|
|
{
|
|
sitOnObject((LLViewerObject*)getParent());
|
|
}
|
|
else if (!getParent() && isSitting() && !isMotionActive(ANIM_AGENT_SIT_GROUND_CONSTRAINED))
|
|
{
|
|
getOffObject();
|
|
//<edit>
|
|
//Singu note: this appears to be a safety catch:
|
|
// when getParent() is NULL and we're not playing ANIM_AGENT_SIT_GROUND_CONSTRAINED then we aren't sitting!
|
|
// The previous call existed in an attempt to fix this inconsistent state by standing up from an object.
|
|
// However, since getParent() is NULL that function would crash!
|
|
// Since we never got crash reports regarding to this, that apparently never happened, except, I discovered
|
|
// today, when you are ground sitting and then LLMotionController::deleteAllMotions or
|
|
// LLMotionController::deactivateAllMotions is called, which seems to only happen when previewing an
|
|
// to-be-uploaded animation on your own avatar (while ground sitting).
|
|
// Hence, we DO need this safety net but not for standing up from an object but for standing up from the ground.
|
|
// I fixed the crash in getOffObject(), so it's ok to call that. In order to make things consistent with
|
|
// the server we need to actually stand up though, or we can't move anymore afterwards.
|
|
if (isSelf())
|
|
{
|
|
gAgent.setControlFlags(AGENT_CONTROL_STAND_UP);
|
|
}
|
|
//</edit>
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
// create local variables in world coords for region position values
|
|
//--------------------------------------------------------------------
|
|
LLVector3 xyVel = getVelocity();
|
|
xyVel.mV[VZ] = 0.0f;
|
|
F32 speed = xyVel.length();
|
|
// remembering the value here prevents a display glitch if the
|
|
// animation gets toggled during this update.
|
|
bool was_sit_ground_constrained = isMotionActive(ANIM_AGENT_SIT_GROUND_CONSTRAINED);
|
|
|
|
//--------------------------------------------------------------------
|
|
// This does a bunch of state updating, including figuring out
|
|
// whether av is in the air, setting mRoot position and rotation
|
|
// In some cases, calls updateOrientation() for a lot of the
|
|
// work
|
|
// --------------------------------------------------------------------
|
|
updateRootPositionAndRotation(agent, speed, was_sit_ground_constrained);
|
|
|
|
//-------------------------------------------------------------------------
|
|
// Update character motions
|
|
//-------------------------------------------------------------------------
|
|
// store data relevant to motions
|
|
mSpeed = speed;
|
|
|
|
// update animations
|
|
if (mSpecialRenderMode == 1) // Animation Preview
|
|
{
|
|
updateMotions(LLCharacter::FORCE_UPDATE);
|
|
}
|
|
else
|
|
{
|
|
updateMotions(LLCharacter::NORMAL_UPDATE);
|
|
}
|
|
|
|
// Special handling for sitting on ground.
|
|
if (!getParent() && (isSitting() || was_sit_ground_constrained))
|
|
{
|
|
F32 off_z = LLVector3d(getHoverOffset()).mdV[VZ];
|
|
if (off_z != 0.0)
|
|
{
|
|
LLVector3 pos = mRoot->getWorldPosition();
|
|
pos.mV[VZ] += off_z;
|
|
mRoot->touch();
|
|
mRoot->setWorldPosition(pos);
|
|
}
|
|
}
|
|
|
|
// update head position
|
|
updateHeadOffset();
|
|
|
|
// Generate footstep sounds when feet hit the ground
|
|
updateFootstepSounds();
|
|
|
|
// Update child joints as needed.
|
|
mRoot->updateWorldMatrixChildren();
|
|
|
|
//mesh vertices need to be reskinned
|
|
mNeedsSkin = TRUE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// updateHeadOffset()
|
|
//-----------------------------------------------------------------------------
|
|
void LLVOAvatar::updateHeadOffset()
|
|
{
|
|
// since we only care about Z, just grab one of the eyes
|
|
LLVector3 midEyePt = mEyeLeftp->getWorldPosition();
|
|
midEyePt -= mDrawable.notNull() ? mDrawable->getWorldPosition() : mRoot->getWorldPosition();
|
|
midEyePt.mV[VZ] = llmax(-mPelvisToFoot + LLViewerCamera::getInstance()->getNear(), midEyePt.mV[VZ]);
|
|
|
|
if (mDrawable.notNull())
|
|
{
|
|
midEyePt = midEyePt * ~mDrawable->getWorldRotation();
|
|
}
|
|
if (isSitting())
|
|
{
|
|
mHeadOffset = midEyePt;
|
|
}
|
|
else
|
|
{
|
|
F32 u = llmax(0.f, HEAD_MOVEMENT_AVG_TIME - (1.f / gFPSClamped));
|
|
mHeadOffset = lerp(midEyePt, mHeadOffset, u);
|
|
}
|
|
}
|
|
//------------------------------------------------------------------------
|
|
// postPelvisSetRecalc
|
|
//------------------------------------------------------------------------
|
|
void LLVOAvatar::postPelvisSetRecalc()
|
|
{
|
|
mRoot->updateWorldMatrixChildren();
|
|
computeBodySize();
|
|
dirtyMesh(2);
|
|
}
|
|
//------------------------------------------------------------------------
|
|
// updateVisibility()
|
|
//------------------------------------------------------------------------
|
|
void LLVOAvatar::updateVisibility()
|
|
{
|
|
BOOL visible = FALSE;
|
|
|
|
if (mIsDummy)
|
|
{
|
|
visible = FALSE;
|
|
}
|
|
else if (mDrawable.isNull())
|
|
{
|
|
visible = FALSE;
|
|
}
|
|
else
|
|
{
|
|
if (!mDrawable->getSpatialGroup() || mDrawable->getSpatialGroup()->isVisible())
|
|
{
|
|
visible = TRUE;
|
|
}
|
|
else
|
|
{
|
|
visible = FALSE;
|
|
}
|
|
|
|
if (isSelf())
|
|
{
|
|
if (!gAgentWearables.areWearablesLoaded())
|
|
{
|
|
visible = FALSE;
|
|
}
|
|
}
|
|
else if (!mFirstAppearanceMessageReceived)
|
|
{
|
|
visible = FALSE;
|
|
}
|
|
|
|
if (sDebugInvisible)
|
|
{
|
|
LLNameValue* firstname = getNVPair("FirstName");
|
|
if (firstname)
|
|
{
|
|
LL_DEBUGS("Avatar") << avString() << " updating visibility" << LL_ENDL;
|
|
}
|
|
else
|
|
{
|
|
LL_INFOS() << "Avatar " << this << " updating visiblity" << LL_ENDL;
|
|
}
|
|
|
|
if (visible)
|
|
{
|
|
LL_INFOS() << "Visible" << LL_ENDL;
|
|
}
|
|
else
|
|
{
|
|
LL_INFOS() << "Not visible" << LL_ENDL;
|
|
}
|
|
|
|
/*if (avatar_in_frustum)
|
|
{
|
|
LL_INFOS() << "Avatar in frustum" << LL_ENDL;
|
|
}
|
|
else
|
|
{
|
|
LL_INFOS() << "Avatar not in frustum" << LL_ENDL;
|
|
}*/
|
|
|
|
/*if (LLViewerCamera::getInstance()->sphereInFrustum(sel_pos_agent, 2.0f))
|
|
{
|
|
LL_INFOS() << "Sel pos visible" << LL_ENDL;
|
|
}
|
|
if (LLViewerCamera::getInstance()->sphereInFrustum(wrist_right_pos_agent, 0.2f))
|
|
{
|
|
LL_INFOS() << "Wrist pos visible" << LL_ENDL;
|
|
}
|
|
if (LLViewerCamera::getInstance()->sphereInFrustum(getPositionAgent(), getMaxScale()*2.f))
|
|
{
|
|
LL_INFOS() << "Agent visible" << LL_ENDL;
|
|
}*/
|
|
LL_INFOS() << "PA: " << getPositionAgent() << LL_ENDL;
|
|
/*LL_INFOS() << "SPA: " << sel_pos_agent << LL_ENDL;
|
|
LL_INFOS() << "WPA: " << wrist_right_pos_agent << LL_ENDL;*/
|
|
|
|
|
|
#if SLOW_ATTACHMENT_LIST
|
|
for (attachment_map_t::iterator iter = mAttachmentPoints.begin();
|
|
iter != mAttachmentPoints.end();
|
|
++iter)
|
|
{
|
|
LLViewerJointAttachment* attachment = iter->second;
|
|
|
|
for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin();
|
|
attachment_iter != attachment->mAttachedObjects.end();
|
|
++attachment_iter)
|
|
{
|
|
if (LLViewerObject *attached_object = (*attachment_iter))
|
|
#else
|
|
for (auto& iter : mAttachedObjectsVector)
|
|
{{
|
|
const LLViewerObject *attached_object = iter.first;
|
|
const LLViewerJointAttachment *attachment = iter.second;
|
|
if (attachment)
|
|
#endif
|
|
{
|
|
if (attached_object && attached_object->mDrawable->isVisible())
|
|
{
|
|
LL_INFOS() << attachment->getName() << " visible" << LL_ENDL;
|
|
}
|
|
else
|
|
{
|
|
LL_INFOS() << attachment->getName() << " not visible at " << mDrawable->getWorldPosition() << " and radius " << mDrawable->getRadius() << LL_ENDL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!visible && mVisible)
|
|
{
|
|
mMeshInvisibleTime.reset();
|
|
}
|
|
|
|
if (visible)
|
|
{
|
|
if (!mMeshValid)
|
|
{
|
|
restoreMeshData();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (mMeshValid &&
|
|
(isControlAvatar() || mMeshInvisibleTime.getElapsedTimeF32() > TIME_BEFORE_MESH_CLEANUP))
|
|
{
|
|
releaseMeshData();
|
|
}
|
|
}
|
|
|
|
mVisible = visible;
|
|
}
|
|
|
|
// private
|
|
bool LLVOAvatar::shouldAlphaMask()
|
|
{
|
|
const bool should_alpha_mask = mSupportsAlphaLayers && !LLDrawPoolAlpha::sShowDebugAlpha // Don't alpha mask if "Highlight Transparent" checked
|
|
&& !LLDrawPoolAvatar::sSkipTransparent;
|
|
|
|
return should_alpha_mask;
|
|
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// renderSkinned()
|
|
//-----------------------------------------------------------------------------
|
|
U32 LLVOAvatar::renderSkinned(EAvatarRenderPass pass)
|
|
{
|
|
U32 num_indices = 0;
|
|
|
|
if (!mIsBuilt)
|
|
{
|
|
return num_indices;
|
|
}
|
|
|
|
if (mDrawable.isNull())
|
|
{
|
|
return num_indices;
|
|
}
|
|
|
|
LLFace* face = mDrawable->getFace(0);
|
|
|
|
bool needs_rebuild = !face || !face->getVertexBuffer() || mDrawable->isState(LLDrawable::REBUILD_GEOMETRY);
|
|
|
|
if (needs_rebuild || mDirtyMesh)
|
|
{ //LOD changed or new mesh created, allocate new vertex buffer if needed
|
|
if (needs_rebuild || mDirtyMesh >= 2 || mVisibilityRank <= 4)
|
|
{
|
|
updateMeshData();
|
|
mDirtyMesh = 0;
|
|
mNeedsSkin = TRUE;
|
|
mDrawable->clearState(LLDrawable::REBUILD_GEOMETRY);
|
|
}
|
|
}
|
|
|
|
if (LLViewerShaderMgr::instance()->getVertexShaderLevel(LLViewerShaderMgr::SHADER_AVATAR) <= 0)
|
|
{
|
|
if (mNeedsSkin)
|
|
{
|
|
//generate animated mesh
|
|
LLViewerJoint* lower_mesh = getViewerJoint(MESH_ID_LOWER_BODY);
|
|
LLViewerJoint* upper_mesh = getViewerJoint(MESH_ID_UPPER_BODY);
|
|
LLViewerJoint* skirt_mesh = getViewerJoint(MESH_ID_SKIRT);
|
|
LLViewerJoint* eyelash_mesh = getViewerJoint(MESH_ID_EYELASH);
|
|
LLViewerJoint* head_mesh = getViewerJoint(MESH_ID_HEAD);
|
|
LLViewerJoint* hair_mesh = getViewerJoint(MESH_ID_HAIR);
|
|
|
|
if(upper_mesh)
|
|
{
|
|
upper_mesh->updateJointGeometry();
|
|
}
|
|
if (lower_mesh)
|
|
{
|
|
lower_mesh->updateJointGeometry();
|
|
}
|
|
|
|
if( isWearingWearableType( LLWearableType::WT_SKIRT ) )
|
|
{
|
|
if(skirt_mesh)
|
|
{
|
|
skirt_mesh->updateJointGeometry();
|
|
}
|
|
}
|
|
|
|
if (!isSelf() || gAgent.needsRenderHead() || LLPipeline::sShadowRender)
|
|
{
|
|
if(eyelash_mesh)
|
|
{
|
|
eyelash_mesh->updateJointGeometry();
|
|
}
|
|
if(head_mesh)
|
|
{
|
|
head_mesh->updateJointGeometry();
|
|
}
|
|
if(hair_mesh)
|
|
{
|
|
hair_mesh->updateJointGeometry();
|
|
}
|
|
}
|
|
mNeedsSkin = FALSE;
|
|
mLastSkinTime = gFrameTimeSeconds;
|
|
|
|
LLFace * face = mDrawable->getFace(0);
|
|
if (face)
|
|
{
|
|
LLVertexBuffer* vb = face->getVertexBuffer();
|
|
if (vb)
|
|
{
|
|
vb->flush();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
mNeedsSkin = FALSE;
|
|
}
|
|
|
|
if (sDebugInvisible)
|
|
{
|
|
LLNameValue* firstname = getNVPair("FirstName");
|
|
if (firstname)
|
|
{
|
|
LL_DEBUGS("Avatar") << avString() << " in render" << LL_ENDL;
|
|
}
|
|
else
|
|
{
|
|
LL_INFOS() << "Avatar " << this << " in render" << LL_ENDL;
|
|
}
|
|
if (!mIsBuilt)
|
|
{
|
|
LL_INFOS() << "Not built!" << LL_ENDL;
|
|
}
|
|
else if (!gAgent.needsRenderAvatar())
|
|
{
|
|
LL_INFOS() << "Doesn't need avatar render!" << LL_ENDL;
|
|
}
|
|
else
|
|
{
|
|
LL_INFOS() << "Rendering!" << LL_ENDL;
|
|
}
|
|
}
|
|
|
|
if (!mIsBuilt)
|
|
{
|
|
return num_indices;
|
|
}
|
|
|
|
if (isSelf() && !gAgent.needsRenderAvatar())
|
|
{
|
|
return num_indices;
|
|
}
|
|
|
|
// render collision normal
|
|
// *NOTE: this is disabled (there is no UI for enabling sShowFootPlane) due
|
|
// to DEV-14477. the code is left here to aid in tracking down the cause
|
|
// of the crash in the future. -brad
|
|
if (sShowFootPlane && mDrawable.notNull())
|
|
{
|
|
LLVector3 slaved_pos = mDrawable->getPositionAgent();
|
|
LLVector3 foot_plane_normal(mFootPlane.mV[VX], mFootPlane.mV[VY], mFootPlane.mV[VZ]);
|
|
F32 dist_from_plane = (slaved_pos * foot_plane_normal) - mFootPlane.mV[VW];
|
|
LLVector3 collide_point = slaved_pos;
|
|
collide_point.mV[VZ] -= foot_plane_normal.mV[VZ] * (dist_from_plane + COLLISION_TOLERANCE - FOOT_COLLIDE_FUDGE);
|
|
|
|
gGL.begin(LLRender::LINES);
|
|
{
|
|
F32 SQUARE_SIZE = 0.2f;
|
|
gGL.color4f(1.f, 0.f, 0.f, 1.f);
|
|
|
|
gGL.vertex3f(collide_point.mV[VX] - SQUARE_SIZE, collide_point.mV[VY] - SQUARE_SIZE, collide_point.mV[VZ]);
|
|
gGL.vertex3f(collide_point.mV[VX] + SQUARE_SIZE, collide_point.mV[VY] - SQUARE_SIZE, collide_point.mV[VZ]);
|
|
|
|
gGL.vertex3f(collide_point.mV[VX] + SQUARE_SIZE, collide_point.mV[VY] - SQUARE_SIZE, collide_point.mV[VZ]);
|
|
gGL.vertex3f(collide_point.mV[VX] + SQUARE_SIZE, collide_point.mV[VY] + SQUARE_SIZE, collide_point.mV[VZ]);
|
|
|
|
gGL.vertex3f(collide_point.mV[VX] + SQUARE_SIZE, collide_point.mV[VY] + SQUARE_SIZE, collide_point.mV[VZ]);
|
|
gGL.vertex3f(collide_point.mV[VX] - SQUARE_SIZE, collide_point.mV[VY] + SQUARE_SIZE, collide_point.mV[VZ]);
|
|
|
|
gGL.vertex3f(collide_point.mV[VX] - SQUARE_SIZE, collide_point.mV[VY] + SQUARE_SIZE, collide_point.mV[VZ]);
|
|
gGL.vertex3f(collide_point.mV[VX] - SQUARE_SIZE, collide_point.mV[VY] - SQUARE_SIZE, collide_point.mV[VZ]);
|
|
|
|
gGL.vertex3f(collide_point.mV[VX], collide_point.mV[VY], collide_point.mV[VZ]);
|
|
gGL.vertex3f(collide_point.mV[VX] + mFootPlane.mV[VX], collide_point.mV[VY] + mFootPlane.mV[VY], collide_point.mV[VZ] + mFootPlane.mV[VZ]);
|
|
|
|
}
|
|
gGL.end();
|
|
gGL.flush();
|
|
}
|
|
//--------------------------------------------------------------------
|
|
// render all geometry attached to the skeleton
|
|
//--------------------------------------------------------------------
|
|
static LLStat render_stat;
|
|
|
|
LLViewerJointMesh::sRenderPass = pass;
|
|
|
|
if (pass == AVATAR_RENDER_PASS_SINGLE)
|
|
{
|
|
bool is_muted = LLPipeline::sImpostorRender && isVisuallyMuted(); //Disable masking and also disable alpha in LLViewerJoint::render
|
|
const bool should_alpha_mask = !is_muted && shouldAlphaMask();
|
|
LLGLState<GL_ALPHA_TEST> test(should_alpha_mask);
|
|
|
|
if (should_alpha_mask && !LLGLSLShader::sNoFixedFunction)
|
|
{
|
|
gGL.setAlphaRejectSettings(LLRender::CF_GREATER, 0.5f);
|
|
}
|
|
|
|
BOOL first_pass = TRUE;
|
|
if (!LLDrawPoolAvatar::sSkipOpaque)
|
|
{
|
|
if (!isSelf() || gAgent.needsRenderHead() || LLPipeline::sShadowRender)
|
|
{
|
|
if (isTextureVisible(TEX_HEAD_BAKED) || isUIAvatar())
|
|
{
|
|
LLViewerJoint* head_mesh = getViewerJoint(MESH_ID_HEAD);
|
|
if (head_mesh)
|
|
{
|
|
num_indices += head_mesh->render(mAdjustedPixelArea, TRUE, mIsDummy);
|
|
}
|
|
first_pass = FALSE;
|
|
}
|
|
}
|
|
if (isTextureVisible(TEX_UPPER_BAKED) || isUIAvatar())
|
|
{
|
|
LLViewerJoint* upper_mesh = getViewerJoint(MESH_ID_UPPER_BODY);
|
|
if (upper_mesh)
|
|
{
|
|
num_indices += upper_mesh->render(mAdjustedPixelArea, first_pass, mIsDummy);
|
|
}
|
|
first_pass = FALSE;
|
|
}
|
|
|
|
if (isTextureVisible(TEX_LOWER_BAKED) || isUIAvatar())
|
|
{
|
|
LLViewerJoint* lower_mesh = getViewerJoint(MESH_ID_LOWER_BODY);
|
|
if (lower_mesh)
|
|
{
|
|
num_indices += lower_mesh->render(mAdjustedPixelArea, first_pass, mIsDummy);
|
|
}
|
|
first_pass = FALSE;
|
|
}
|
|
}
|
|
|
|
if (should_alpha_mask && !LLGLSLShader::sNoFixedFunction)
|
|
{
|
|
gGL.setAlphaRejectSettings(LLRender::CF_DEFAULT);
|
|
}
|
|
|
|
if (!LLDrawPoolAvatar::sSkipTransparent || LLPipeline::sImpostorRender)
|
|
{
|
|
LLGLState<GL_BLEND> blend(!mIsDummy);
|
|
LLGLState<GL_ALPHA_TEST> test(!mIsDummy);
|
|
num_indices += renderTransparent(first_pass);
|
|
}
|
|
}
|
|
|
|
LLViewerJointMesh::sRenderPass = AVATAR_RENDER_PASS_SINGLE;
|
|
|
|
return num_indices;
|
|
}
|
|
|
|
U32 LLVOAvatar::renderTransparent(BOOL first_pass)
|
|
{
|
|
U32 num_indices = 0;
|
|
if( isWearingWearableType( LLWearableType::WT_SKIRT ) && (isUIAvatar() || isTextureVisible(TEX_SKIRT_BAKED)) )
|
|
{
|
|
gGL.setAlphaRejectSettings(LLRender::CF_GREATER, 0.25f);
|
|
LLViewerJoint* skirt_mesh = getViewerJoint(MESH_ID_SKIRT);
|
|
if (skirt_mesh)
|
|
{
|
|
num_indices += skirt_mesh->render(mAdjustedPixelArea, FALSE);
|
|
}
|
|
first_pass = FALSE;
|
|
gGL.setAlphaRejectSettings(LLRender::CF_DEFAULT);
|
|
}
|
|
|
|
if (!isSelf() || gAgent.needsRenderHead() || LLPipeline::sShadowRender)
|
|
{
|
|
if (LLPipeline::sImpostorRender)
|
|
{
|
|
gGL.setAlphaRejectSettings(LLRender::CF_GREATER, 0.5f);
|
|
}
|
|
|
|
if (isTextureVisible(TEX_HEAD_BAKED))
|
|
{
|
|
LLViewerJoint* eyelash_mesh = getViewerJoint(MESH_ID_EYELASH);
|
|
if (eyelash_mesh)
|
|
{
|
|
num_indices += eyelash_mesh->render(mAdjustedPixelArea, first_pass, mIsDummy);
|
|
}
|
|
first_pass = FALSE;
|
|
}
|
|
bool show_hair = false;
|
|
if (isControlAvatar())
|
|
{
|
|
show_hair = isTextureVisible(TEX_HAIR_BAKED);
|
|
}
|
|
else
|
|
{
|
|
// Can't test for baked hair being defined, since that won't always be the case (not all viewers send baked hair)
|
|
// TODO: 1.25 will be able to switch this logic back to calling isTextureVisible();
|
|
auto image = getImage(TEX_HAIR_BAKED, 0);
|
|
show_hair = LLDrawPoolAlpha::sShowDebugAlpha || (image && image->getID() != IMG_INVISIBLE);
|
|
}
|
|
if (show_hair)
|
|
{
|
|
LLViewerJoint* hair_mesh = getViewerJoint(MESH_ID_HAIR);
|
|
if (hair_mesh)
|
|
{
|
|
num_indices += hair_mesh->render(mAdjustedPixelArea, first_pass, mIsDummy);
|
|
}
|
|
first_pass = FALSE;
|
|
}
|
|
if (LLPipeline::sImpostorRender)
|
|
{
|
|
gGL.setAlphaRejectSettings(LLRender::CF_DEFAULT);
|
|
}
|
|
}
|
|
|
|
return num_indices;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// renderRigid()
|
|
//-----------------------------------------------------------------------------
|
|
U32 LLVOAvatar::renderRigid()
|
|
{
|
|
U32 num_indices = 0;
|
|
|
|
if (!mIsBuilt)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (isSelf() && (!gAgent.needsRenderAvatar() || !gAgent.needsRenderHead()))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (!mIsBuilt)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
const bool should_alpha_mask = shouldAlphaMask();
|
|
LLGLState<GL_ALPHA_TEST> test(should_alpha_mask);
|
|
|
|
if (should_alpha_mask && !LLGLSLShader::sNoFixedFunction)
|
|
{
|
|
gGL.setAlphaRejectSettings(LLRender::CF_GREATER, 0.5f);
|
|
}
|
|
|
|
if (isTextureVisible(TEX_EYES_BAKED) || isUIAvatar())
|
|
{
|
|
LLViewerJoint* eyeball_left = getViewerJoint(MESH_ID_EYEBALL_LEFT);
|
|
LLViewerJoint* eyeball_right = getViewerJoint(MESH_ID_EYEBALL_RIGHT);
|
|
if (eyeball_left)
|
|
{
|
|
num_indices += eyeball_left->render(mAdjustedPixelArea, TRUE, mIsDummy);
|
|
}
|
|
if(eyeball_right)
|
|
{
|
|
num_indices += eyeball_right->render(mAdjustedPixelArea, TRUE, mIsDummy);
|
|
}
|
|
}
|
|
|
|
if (should_alpha_mask && !LLGLSLShader::sNoFixedFunction)
|
|
{
|
|
gGL.setAlphaRejectSettings(LLRender::CF_DEFAULT);
|
|
}
|
|
|
|
return num_indices;
|
|
}
|
|
|
|
/*U32 LLVOAvatar::renderFootShadows()
|
|
{
|
|
U32 num_indices = 0;
|
|
|
|
if (!mIsBuilt)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (isSelf() && (!gAgent.needsRenderAvatar() || !gAgent.needsRenderHead()))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (!mIsBuilt)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
// Don't render foot shadows if your lower body is completely invisible.
|
|
// (non-humanoid avatars rule!)
|
|
if (! isTextureVisible(TEX_LOWER_BAKED))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
// Update the shadow, tractor, and text label geometry.
|
|
if (mDrawable->isState(LLDrawable::REBUILD_SHADOW) && !isImpostor())
|
|
{
|
|
updateShadowFaces();
|
|
mDrawable->clearState(LLDrawable::REBUILD_SHADOW);
|
|
}
|
|
|
|
U32 foot_mask = LLVertexBuffer::MAP_VERTEX |
|
|
LLVertexBuffer::MAP_TEXCOORD0;
|
|
|
|
LLGLDepthTest test(GL_TRUE, GL_FALSE);
|
|
//render foot shadows
|
|
LLGLEnable<GL_BLEND> blend;
|
|
gGL.getTexUnit(0)->bind(mShadowImagep.get(), TRUE);
|
|
gGL.diffuseColor4fv(mShadow0Facep->getRenderColor().mV);
|
|
mShadow0Facep->renderIndexed(foot_mask);
|
|
gGL.diffuseColor4fv(mShadow1Facep->getRenderColor().mV);
|
|
mShadow1Facep->renderIndexed(foot_mask);
|
|
|
|
return num_indices;
|
|
}*/
|
|
|
|
U32 LLVOAvatar::renderImpostor(LLColor4U color, S32 diffuse_channel)
|
|
{
|
|
if (!mImpostor.isComplete())
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
LLVector3 pos(getRenderPosition()+mImpostorOffset);
|
|
LLVector3 at = (pos - LLViewerCamera::getInstance()->getOrigin());
|
|
at.normalize();
|
|
LLVector3 left = LLViewerCamera::getInstance()->getUpAxis() % at;
|
|
LLVector3 up = at%left;
|
|
|
|
left *= mImpostorDim.mV[0];
|
|
up *= mImpostorDim.mV[1];
|
|
|
|
LLGLEnable<GL_ALPHA_TEST> test;
|
|
gGL.setAlphaRejectSettings(LLRender::CF_GREATER, 0.f);
|
|
|
|
gGL.color4ubv(color.mV);
|
|
gGL.getTexUnit(diffuse_channel)->bind(&mImpostor);
|
|
gGL.begin(LLRender::TRIANGLE_STRIP);
|
|
gGL.texCoord2f(0,0);
|
|
gGL.vertex3fv((pos+left-up).mV);
|
|
gGL.texCoord2f(1,0);
|
|
gGL.vertex3fv((pos-left-up).mV);
|
|
gGL.texCoord2f(0, 1);
|
|
gGL.vertex3fv((pos + left + up).mV);
|
|
gGL.texCoord2f(1,1);
|
|
gGL.vertex3fv((pos-left+up).mV);
|
|
gGL.end();
|
|
gGL.flush();
|
|
|
|
return 6;
|
|
}
|
|
|
|
bool LLVOAvatar::allTexturesCompletelyDownloaded(uuid_set_t& ids) const
|
|
{
|
|
for (auto it = ids.begin(); it != ids.end(); ++it)
|
|
{
|
|
LLViewerFetchedTexture *imagep = gTextureList.findImage(*it, TEX_LIST_STANDARD);
|
|
if (imagep && imagep->getDiscardLevel()!=0)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool LLVOAvatar::allLocalTexturesCompletelyDownloaded() const
|
|
{
|
|
uuid_set_t local_ids;
|
|
collectLocalTextureUUIDs(local_ids);
|
|
return allTexturesCompletelyDownloaded(local_ids);
|
|
}
|
|
|
|
bool LLVOAvatar::allBakedTexturesCompletelyDownloaded() const
|
|
{
|
|
uuid_set_t baked_ids;
|
|
collectBakedTextureUUIDs(baked_ids);
|
|
return allTexturesCompletelyDownloaded(baked_ids);
|
|
}
|
|
|
|
void LLVOAvatar::bakedTextureOriginCounts(S32 &sb_count, // server-bake, has origin URL.
|
|
S32 &host_count, // host-based bake, has host.
|
|
S32 &both_count, // error - both host and URL set.
|
|
S32 &neither_count) // error - neither set.
|
|
{
|
|
sb_count = host_count = both_count = neither_count = 0;
|
|
|
|
uuid_set_t baked_ids;
|
|
collectBakedTextureUUIDs(baked_ids);
|
|
for (auto it = baked_ids.begin(); it != baked_ids.end(); ++it)
|
|
{
|
|
LLViewerFetchedTexture *imagep = gTextureList.findImage(*it, TEX_LIST_STANDARD);
|
|
bool has_url = false, has_host = false;
|
|
if (!imagep->getUrl().empty())
|
|
{
|
|
has_url = true;
|
|
}
|
|
if (imagep->getTargetHost().isOk())
|
|
{
|
|
has_host = true;
|
|
}
|
|
if (has_url && !has_host) sb_count++;
|
|
else if (has_host && !has_url) host_count++;
|
|
else if (has_host && has_url) both_count++;
|
|
else if (!has_host && !has_url) neither_count++;
|
|
}
|
|
}
|
|
|
|
std::string LLVOAvatar::bakedTextureOriginInfo()
|
|
{
|
|
std::string result;
|
|
|
|
uuid_set_t baked_ids;
|
|
collectBakedTextureUUIDs(baked_ids);
|
|
for (auto it = baked_ids.begin(); it != baked_ids.end(); ++it)
|
|
{
|
|
LLViewerFetchedTexture *imagep = gTextureList.findImage(*it, TEX_LIST_STANDARD);
|
|
bool has_url = false, has_host = false;
|
|
if (!imagep->getUrl().empty())
|
|
{
|
|
has_url = true;
|
|
}
|
|
if (imagep->getTargetHost().isOk())
|
|
{
|
|
has_host = true;
|
|
}
|
|
S32 discard = imagep->getDiscardLevel();
|
|
if (has_url && !has_host) result += discard ? "u" : "U"; // server-bake texture with url
|
|
else if (has_host && !has_url) result += discard ? "h" : "H"; // old-style texture on sim
|
|
else if (has_host && has_url) result += discard ? "x" : "X"; // both origins?
|
|
else if (!has_host && !has_url) result += discard ? "n" : "N"; // no origin?
|
|
if (discard != 0)
|
|
{
|
|
result += llformat("(%d/%d)",discard,imagep->getDesiredDiscardLevel());
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
S32Bytes LLVOAvatar::totalTextureMemForUUIDS(uuid_set_t& ids)
|
|
{
|
|
S32Bytes result(0);
|
|
for (auto it = ids.begin(); it != ids.end(); ++it)
|
|
{
|
|
LLViewerFetchedTexture *imagep = gTextureList.findImage(*it, TEX_LIST_STANDARD);
|
|
if (imagep)
|
|
{
|
|
result += imagep->getTextureMemory();
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void LLVOAvatar::collectLocalTextureUUIDs(uuid_set_t& ids) const
|
|
{
|
|
for (U32 texture_index = 0; texture_index < getNumTEs(); texture_index++)
|
|
{
|
|
LLWearableType::EType wearable_type = LLAvatarAppearanceDictionary::getTEWearableType((ETextureIndex)texture_index);
|
|
U32 num_wearables = gAgentWearables.getWearableCount(wearable_type);
|
|
|
|
LLViewerFetchedTexture *imagep = NULL;
|
|
for (U32 wearable_index = 0; wearable_index < num_wearables; wearable_index++)
|
|
{
|
|
imagep = LLViewerTextureManager::staticCastToFetchedTexture(getImage(texture_index, wearable_index), TRUE);
|
|
if (imagep)
|
|
{
|
|
const LLAvatarAppearanceDictionary::TextureEntry *texture_dict = LLAvatarAppearanceDictionary::getInstance()->getTexture((ETextureIndex)texture_index);
|
|
if (texture_dict && texture_dict->mIsLocalTexture)
|
|
{
|
|
ids.insert(imagep->getID());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
ids.erase(IMG_DEFAULT);
|
|
ids.erase(IMG_DEFAULT_AVATAR);
|
|
ids.erase(IMG_INVISIBLE);
|
|
}
|
|
|
|
void LLVOAvatar::collectBakedTextureUUIDs(uuid_set_t& ids) const
|
|
{
|
|
for (U32 texture_index = 0; texture_index < getNumTEs(); texture_index++)
|
|
{
|
|
LLViewerFetchedTexture *imagep = NULL;
|
|
if (isIndexBakedTexture((ETextureIndex) texture_index))
|
|
{
|
|
imagep = LLViewerTextureManager::staticCastToFetchedTexture(getImage(texture_index,0), TRUE);
|
|
if (imagep)
|
|
{
|
|
ids.insert(imagep->getID());
|
|
}
|
|
}
|
|
}
|
|
ids.erase(IMG_DEFAULT);
|
|
ids.erase(IMG_DEFAULT_AVATAR);
|
|
ids.erase(IMG_INVISIBLE);
|
|
}
|
|
|
|
void LLVOAvatar::collectTextureUUIDs(uuid_set_t& ids)
|
|
{
|
|
collectLocalTextureUUIDs(ids);
|
|
collectBakedTextureUUIDs(ids);
|
|
}
|
|
|
|
void LLVOAvatar::releaseOldTextures()
|
|
{
|
|
S32Bytes current_texture_mem;
|
|
|
|
// Any textures that we used to be using but are no longer using should no longer be flagged as "NO_DELETE"
|
|
uuid_set_t baked_texture_ids;
|
|
collectBakedTextureUUIDs(baked_texture_ids);
|
|
S32Bytes new_baked_mem = totalTextureMemForUUIDS(baked_texture_ids);
|
|
|
|
uuid_set_t local_texture_ids;
|
|
collectLocalTextureUUIDs(local_texture_ids);
|
|
//S32 new_local_mem = totalTextureMemForUUIDS(local_texture_ids);
|
|
|
|
uuid_set_t new_texture_ids;
|
|
new_texture_ids.insert(baked_texture_ids.begin(),baked_texture_ids.end());
|
|
new_texture_ids.insert(local_texture_ids.begin(),local_texture_ids.end());
|
|
S32Bytes new_total_mem = totalTextureMemForUUIDS(new_texture_ids);
|
|
|
|
//S32 old_total_mem = totalTextureMemForUUIDS(mTextureIDs);
|
|
//LL_DEBUGS("Avatar") << getFullname() << " old_total_mem: " << old_total_mem << " new_total_mem (L/B): " << new_total_mem << " (" << new_local_mem <<", " << new_baked_mem << ")" << LL_ENDL;
|
|
if (!isSelf() && new_total_mem > new_baked_mem)
|
|
{
|
|
LL_WARNS() << "extra local textures stored for non-self av" << LL_ENDL;
|
|
}
|
|
for (auto it = mTextureIDs.begin(); it != mTextureIDs.end(); ++it)
|
|
{
|
|
if (new_texture_ids.find(*it) == new_texture_ids.end())
|
|
{
|
|
LLViewerFetchedTexture *imagep = gTextureList.findImage(*it, TEX_LIST_STANDARD);
|
|
if (imagep)
|
|
{
|
|
current_texture_mem += imagep->getTextureMemory();
|
|
if (imagep->getTextureState() == LLGLTexture::NO_DELETE)
|
|
{
|
|
// This will allow the texture to be deleted if not in use.
|
|
imagep->forceActive();
|
|
|
|
// This resets the clock to texture being flagged
|
|
// as unused, preventing the texture from being
|
|
// deleted immediately. If other avatars or
|
|
// objects are using it, it can still be flagged
|
|
// no-delete by them.
|
|
imagep->forceUpdateBindStats();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
mTextureIDs = new_texture_ids;
|
|
}
|
|
|
|
static LLFastTimer::DeclareTimer FTM_TEXTURE_UPDATE("Update Textures");
|
|
void LLVOAvatar::updateTextures()
|
|
{
|
|
LL_RECORD_BLOCK_TIME(FTM_TEXTURE_UPDATE);
|
|
releaseOldTextures();
|
|
|
|
BOOL render_avatar = TRUE;
|
|
|
|
if (mIsDummy)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if( isSelf() )
|
|
{
|
|
render_avatar = TRUE;
|
|
}
|
|
else
|
|
{
|
|
if(!isVisible())
|
|
{
|
|
return ;//do not update for invisible avatar.
|
|
}
|
|
|
|
render_avatar = !mCulled; //visible and not culled.
|
|
}
|
|
|
|
std::vector<bool> layer_baked;
|
|
// GL NOT ACTIVE HERE - *TODO
|
|
for (U32 i = 0; i < mBakedTextureDatas.size(); i++)
|
|
{
|
|
layer_baked.push_back(isTextureDefined(mBakedTextureDatas[i].mTextureIndex));
|
|
// bind the texture so that they'll be decoded slightly
|
|
// inefficient, we can short-circuit this if we have to
|
|
if (render_avatar && !gGLManager.mIsDisabled)
|
|
{
|
|
if (layer_baked[i] && !mBakedTextureDatas[i].mIsLoaded)
|
|
{
|
|
gGL.getTexUnit(0)->bind(getImage( mBakedTextureDatas[i].mTextureIndex, 0 ));
|
|
}
|
|
}
|
|
}
|
|
|
|
mMaxPixelArea = 0.f;
|
|
mMinPixelArea = 99999999.f;
|
|
mHasGrey = FALSE; // debug
|
|
for (U32 texture_index = 0; texture_index < getNumTEs(); texture_index++)
|
|
{
|
|
LLWearableType::EType wearable_type = LLAvatarAppearanceDictionary::getTEWearableType((ETextureIndex)texture_index);
|
|
U32 num_wearables = gAgentWearables.getWearableCount(wearable_type);
|
|
const LLTextureEntry *te = getTE(texture_index);
|
|
|
|
// getTE can return 0.
|
|
// Not sure yet why it does, but of course it crashes when te->mScale? gets used.
|
|
// Put safeguard in place so this corner case get better handling and does not result in a crash.
|
|
F32 texel_area_ratio = 1.0f;
|
|
if( te )
|
|
{
|
|
texel_area_ratio = fabs(te->mScaleS * te->mScaleT);
|
|
}
|
|
else
|
|
{
|
|
LL_WARNS() << "getTE( " << texture_index << " ) returned 0" <<LL_ENDL;
|
|
}
|
|
|
|
LLViewerFetchedTexture *imagep = NULL;
|
|
for (U32 wearable_index = 0; wearable_index < num_wearables; wearable_index++)
|
|
{
|
|
imagep = LLViewerTextureManager::staticCastToFetchedTexture(getImage(texture_index, wearable_index), TRUE);
|
|
if (imagep)
|
|
{
|
|
const LLAvatarAppearanceDictionary::TextureEntry *texture_dict = LLAvatarAppearanceDictionary::getInstance()->getTexture((ETextureIndex)texture_index);
|
|
const EBakedTextureIndex baked_index = texture_dict ? texture_dict->mBakedTextureIndex : EBakedTextureIndex::BAKED_NUM_INDICES;
|
|
if (texture_dict && texture_dict->mIsLocalTexture)
|
|
{
|
|
addLocalTextureStats((ETextureIndex)texture_index, imagep, texel_area_ratio, render_avatar, mBakedTextureDatas[baked_index].mIsUsed);
|
|
}
|
|
}
|
|
}
|
|
if (isIndexBakedTexture((ETextureIndex) texture_index) && render_avatar)
|
|
{
|
|
const S32 boost_level = getAvatarBakedBoostLevel();
|
|
imagep = LLViewerTextureManager::staticCastToFetchedTexture(getImage(texture_index,0), TRUE);
|
|
// Spam if this is a baked texture, not set to default image, without valid host info
|
|
if (isIndexBakedTexture((ETextureIndex)texture_index)
|
|
&& imagep->getID() != IMG_DEFAULT_AVATAR
|
|
&& imagep->getID() != IMG_INVISIBLE
|
|
&& !isUsingServerBakes()
|
|
&& !imagep->getTargetHost().isOk())
|
|
{
|
|
LL_WARNS_ONCE("Texture") << "LLVOAvatar::updateTextures No host for texture "
|
|
<< imagep->getID() << " for avatar "
|
|
<< (isSelf() ? "<myself>" : getID().asString())
|
|
<< " on host " << getRegion()->getHost() << LL_ENDL;
|
|
}
|
|
|
|
addBakedTextureStats( imagep, mPixelArea, texel_area_ratio, boost_level );
|
|
}
|
|
}
|
|
|
|
if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_TEXTURE_AREA))
|
|
{
|
|
setDebugText(llformat("%4.0f:%4.0f", (F32) sqrt(mMinPixelArea),(F32) sqrt(mMaxPixelArea)));
|
|
}
|
|
}
|
|
|
|
|
|
void LLVOAvatar::addLocalTextureStats( ETextureIndex idx, LLViewerFetchedTexture* imagep,
|
|
F32 texel_area_ratio, BOOL render_avatar, BOOL covered_by_baked)
|
|
{
|
|
// No local texture stats for non-self avatars
|
|
return;
|
|
}
|
|
|
|
const S32 MAX_TEXTURE_UPDATE_INTERVAL = 64 ; //need to call updateTextures() at least every 32 frames.
|
|
const S32 MAX_TEXTURE_VIRTUAL_SIZE_RESET_INTERVAL = S32_MAX ; //frames
|
|
void LLVOAvatar::checkTextureLoading()
|
|
{
|
|
static const F32 MAX_INVISIBLE_WAITING_TIME = 15.f ; //seconds
|
|
|
|
BOOL pause = !isVisible() ;
|
|
if(!pause)
|
|
{
|
|
mInvisibleTimer.reset() ;
|
|
}
|
|
if(mLoadedCallbacksPaused == pause)
|
|
{
|
|
return ;
|
|
}
|
|
|
|
if(mCallbackTextureList.empty()) //when is self or no callbacks. Note: this list for self is always empty.
|
|
{
|
|
mLoadedCallbacksPaused = pause ;
|
|
return ; //nothing to check.
|
|
}
|
|
|
|
if(pause && mInvisibleTimer.getElapsedTimeF32() < MAX_INVISIBLE_WAITING_TIME)
|
|
{
|
|
return ; //have not been invisible for enough time.
|
|
}
|
|
|
|
for(LLLoadedCallbackEntry::source_callback_list_t::iterator iter = mCallbackTextureList.begin();
|
|
iter != mCallbackTextureList.end(); ++iter)
|
|
{
|
|
LLViewerFetchedTexture* tex = gTextureList.findImage(*iter) ;
|
|
if(tex)
|
|
{
|
|
if(pause)//pause texture fetching.
|
|
{
|
|
tex->pauseLoadedCallbacks(&mCallbackTextureList) ;
|
|
|
|
//set to terminate texture fetching after MAX_TEXTURE_UPDATE_INTERVAL frames.
|
|
tex->setMaxVirtualSizeResetInterval(MAX_TEXTURE_UPDATE_INTERVAL);
|
|
tex->resetMaxVirtualSizeResetCounter() ;
|
|
}
|
|
else//unpause
|
|
{
|
|
static const F32 START_AREA = 100.f ;
|
|
|
|
tex->unpauseLoadedCallbacks(&mCallbackTextureList) ;
|
|
tex->addTextureStats(START_AREA); //jump start the fetching again
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!pause)
|
|
{
|
|
updateTextures() ; //refresh texture stats.
|
|
}
|
|
mLoadedCallbacksPaused = pause ;
|
|
return ;
|
|
}
|
|
|
|
const F32 SELF_ADDITIONAL_PRI = 0.75f ;
|
|
const F32 ADDITIONAL_PRI = 0.5f;
|
|
void LLVOAvatar::addBakedTextureStats( LLViewerFetchedTexture* imagep, F32 pixel_area, F32 texel_area_ratio, S32 boost_level)
|
|
{
|
|
//Note:
|
|
//if this function is not called for the last MAX_TEXTURE_VIRTUAL_SIZE_RESET_INTERVAL frames,
|
|
//the texture pipeline will stop fetching this texture.
|
|
|
|
imagep->resetTextureStats();
|
|
// TODO: currently default to HTTP texture and fall back to UDP if cannot be found there.
|
|
// Once server messaging is in place, we should call setCanUseHTTP(false) for old style
|
|
// appearance requests
|
|
imagep->setCanUseHTTP(true);
|
|
imagep->setMaxVirtualSizeResetInterval(MAX_TEXTURE_VIRTUAL_SIZE_RESET_INTERVAL);
|
|
imagep->resetMaxVirtualSizeResetCounter() ;
|
|
|
|
mMaxPixelArea = llmax(pixel_area, mMaxPixelArea);
|
|
mMinPixelArea = llmin(pixel_area, mMinPixelArea);
|
|
imagep->addTextureStats(pixel_area / texel_area_ratio);
|
|
imagep->setBoostLevel(boost_level);
|
|
|
|
if(boost_level != LLGLTexture::BOOST_AVATAR_BAKED_SELF)
|
|
{
|
|
imagep->setAdditionalDecodePriority(ADDITIONAL_PRI) ;
|
|
}
|
|
else
|
|
{
|
|
imagep->setAdditionalDecodePriority(SELF_ADDITIONAL_PRI) ;
|
|
}
|
|
}
|
|
|
|
//virtual
|
|
void LLVOAvatar::setImage(const U8 te, LLViewerTexture *imagep, const U32 index)
|
|
{
|
|
setTEImage(te, imagep);
|
|
}
|
|
|
|
//virtual
|
|
LLViewerTexture* LLVOAvatar::getImage(const U8 te, const U32 index) const
|
|
{
|
|
return getTEImage(te);
|
|
}
|
|
//virtual
|
|
const LLTextureEntry* LLVOAvatar::getTexEntry(const U8 te_num) const
|
|
{
|
|
return getTE(te_num);
|
|
}
|
|
|
|
//virtual
|
|
void LLVOAvatar::setTexEntry(const U8 index, const LLTextureEntry &te)
|
|
{
|
|
setTE(index, te);
|
|
}
|
|
|
|
const std::string LLVOAvatar::getImageURL(const U8 te, const LLUUID &uuid)
|
|
{
|
|
llassert(isIndexBakedTexture(ETextureIndex(te)));
|
|
std::string url = "";
|
|
if (isUsingServerBakes())
|
|
{
|
|
const std::string& appearance_service_url = LLAppearanceMgr::instance().getAppearanceServiceURL();
|
|
if (appearance_service_url.empty())
|
|
{
|
|
// Probably a server-side issue if we get here:
|
|
LL_WARNS() << "AgentAppearanceServiceURL not set - Baked texture requests will fail" << LL_ENDL;
|
|
return url;
|
|
}
|
|
|
|
const LLAvatarAppearanceDictionary::TextureEntry* texture_entry = LLAvatarAppearanceDictionary::getInstance()->getTexture((ETextureIndex)te);
|
|
if (texture_entry != NULL)
|
|
{
|
|
url = appearance_service_url + "texture/" + getID().asString() + "/" + texture_entry->mDefaultImageName + "/" + uuid.asString();
|
|
//LL_INFOS() << "baked texture url: " << url << LL_ENDL;
|
|
}
|
|
}
|
|
return url;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// resolveHeight()
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void LLVOAvatar::resolveHeightAgent(const LLVector3 &in_pos_agent, LLVector3 &out_pos_agent, LLVector3 &out_norm)
|
|
{
|
|
LLVector3d in_pos_global, out_pos_global;
|
|
|
|
in_pos_global = gAgent.getPosGlobalFromAgent(in_pos_agent);
|
|
resolveHeightGlobal(in_pos_global, out_pos_global, out_norm);
|
|
out_pos_agent = gAgent.getPosAgentFromGlobal(out_pos_global);
|
|
}
|
|
|
|
|
|
void LLVOAvatar::resolveRayCollisionAgent(const LLVector3d start_pt, const LLVector3d end_pt, LLVector3d &out_pos, LLVector3 &out_norm)
|
|
{
|
|
LLViewerObject *obj;
|
|
LLWorld::getInstance()->resolveStepHeightGlobal(this, start_pt, end_pt, out_pos, out_norm, &obj);
|
|
}
|
|
|
|
void LLVOAvatar::resolveHeightGlobal(const LLVector3d &inPos, LLVector3d &outPos, LLVector3 &outNorm)
|
|
{
|
|
LLVector3d zVec(0.0f, 0.0f, 0.5f);
|
|
LLVector3d p0 = inPos + zVec;
|
|
LLVector3d p1 = inPos - zVec;
|
|
LLViewerObject *obj;
|
|
LLWorld::getInstance()->resolveStepHeightGlobal(this, p0, p1, outPos, outNorm, &obj);
|
|
if (!obj)
|
|
{
|
|
mStepOnLand = TRUE;
|
|
mStepMaterial = 0;
|
|
mStepObjectVelocity.setVec(0.0f, 0.0f, 0.0f);
|
|
}
|
|
else
|
|
{
|
|
mStepOnLand = FALSE;
|
|
mStepMaterial = obj->getMaterial();
|
|
|
|
// We want the primitive velocity, not our velocity... (which actually subtracts the
|
|
// step object velocity)
|
|
LLVector3 angularVelocity = obj->getAngularVelocity();
|
|
LLVector3 relativePos = gAgent.getPosAgentFromGlobal(outPos) - obj->getPositionAgent();
|
|
|
|
LLVector3 linearComponent = angularVelocity % relativePos;
|
|
// LL_INFOS() << "Linear Component of Rotation Velocity " << linearComponent << LL_ENDL;
|
|
mStepObjectVelocity = obj->getVelocity() + linearComponent;
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// getStepSound()
|
|
//-----------------------------------------------------------------------------
|
|
const LLUUID& LLVOAvatar::getStepSound() const
|
|
{
|
|
if ( mStepOnLand )
|
|
{
|
|
return sStepSoundOnLand;
|
|
}
|
|
|
|
return sStepSounds[mStepMaterial];
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// processAnimationStateChanges()
|
|
//-----------------------------------------------------------------------------
|
|
void LLVOAvatar::processAnimationStateChanges()
|
|
{
|
|
if ( isAnyAnimationSignaled(AGENT_WALK_ANIMS, NUM_AGENT_WALK_ANIMS) )
|
|
{
|
|
startMotion(ANIM_AGENT_WALK_ADJUST);
|
|
stopMotion(ANIM_AGENT_FLY_ADJUST);
|
|
}
|
|
else if (mInAir && !isSitting())
|
|
{
|
|
stopMotion(ANIM_AGENT_WALK_ADJUST);
|
|
if (mEnableDefaultMotions)
|
|
{
|
|
startMotion(ANIM_AGENT_FLY_ADJUST);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
stopMotion(ANIM_AGENT_WALK_ADJUST);
|
|
stopMotion(ANIM_AGENT_FLY_ADJUST);
|
|
}
|
|
|
|
if ( isAnyAnimationSignaled(AGENT_GUN_AIM_ANIMS, NUM_AGENT_GUN_AIM_ANIMS) )
|
|
{
|
|
if (mEnableDefaultMotions)
|
|
{
|
|
startMotion(ANIM_AGENT_TARGET);
|
|
}
|
|
stopMotion(ANIM_AGENT_BODY_NOISE);
|
|
}
|
|
else
|
|
{
|
|
stopMotion(ANIM_AGENT_TARGET);
|
|
if (mEnableDefaultMotions)
|
|
{
|
|
startMotion(ANIM_AGENT_BODY_NOISE);
|
|
}
|
|
}
|
|
|
|
// clear all current animations
|
|
const bool AOEnabled(gSavedSettings.getBOOL("AOEnabled")); // <singu/>
|
|
AnimIterator anim_it;
|
|
for (anim_it = mPlayingAnimations.begin(); anim_it != mPlayingAnimations.end();)
|
|
{
|
|
AnimIterator found_anim = mSignaledAnimations.find(anim_it->first);
|
|
|
|
// playing, but not signaled, so stop
|
|
if (found_anim == mSignaledAnimations.end())
|
|
{
|
|
if (AOEnabled && isSelf())
|
|
LLFloaterAO::stopMotion(anim_it->first, FALSE); // if the AO replaced this anim serverside then stop it serverside
|
|
|
|
processSingleAnimationStateChange(anim_it->first, FALSE);
|
|
// <edit>
|
|
LLFloaterExploreAnimations::processAnim(getID(), anim_it->first, false);
|
|
// </edit>
|
|
mPlayingAnimations.erase(anim_it++);
|
|
continue;
|
|
}
|
|
|
|
++anim_it;
|
|
}
|
|
|
|
// start up all new anims
|
|
for (anim_it = mSignaledAnimations.begin(); anim_it != mSignaledAnimations.end();)
|
|
{
|
|
AnimIterator found_anim = mPlayingAnimations.find(anim_it->first);
|
|
|
|
// signaled but not playing, or different sequence id, start motion
|
|
if (found_anim == mPlayingAnimations.end() || found_anim->second != anim_it->second)
|
|
{
|
|
// <edit>
|
|
LLFloaterExploreAnimations::processAnim(getID(), anim_it->first, true);
|
|
// </edit>
|
|
if (processSingleAnimationStateChange(anim_it->first, TRUE))
|
|
{
|
|
if (AOEnabled && isSelf()) // AO is only for ME
|
|
{
|
|
LLFloaterAO::startMotion(anim_it->first, false); // AO overrides the anim if needed
|
|
}
|
|
|
|
mPlayingAnimations[anim_it->first] = anim_it->second;
|
|
++anim_it;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
++anim_it;
|
|
}
|
|
|
|
// clear source information for animations which have been stopped
|
|
if (isSelf())
|
|
{
|
|
AnimSourceIterator source_it = mAnimationSources.begin();
|
|
|
|
for (source_it = mAnimationSources.begin(); source_it != mAnimationSources.end();)
|
|
{
|
|
if (mSignaledAnimations.find(source_it->second) == mSignaledAnimations.end())
|
|
{
|
|
mAnimationSources.erase(source_it++);
|
|
}
|
|
else
|
|
{
|
|
++source_it;
|
|
}
|
|
}
|
|
}
|
|
|
|
stop_glerror();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// processSingleAnimationStateChange();
|
|
//-----------------------------------------------------------------------------
|
|
BOOL LLVOAvatar::processSingleAnimationStateChange( const LLUUID& anim_id, BOOL start )
|
|
{
|
|
// SL-402, SL-427 - we need to update body size often enough to
|
|
// keep appearances in sync, but not so often that animations
|
|
// cause constant jiggling of the body or camera. Possible
|
|
// compromise is to do it on animation changes:
|
|
computeBodySize();
|
|
|
|
BOOL result = FALSE;
|
|
|
|
if ( start ) // start animation
|
|
{
|
|
static LLCachedControl<bool> play_typing_sound("PlayTypingSound");
|
|
if (anim_id == ANIM_AGENT_TYPE && play_typing_sound)
|
|
{
|
|
if (gAudiop)
|
|
{
|
|
LLVector3d char_pos_global = gAgent.getPosGlobalFromAgent(getCharacterPosition());
|
|
if (LLViewerParcelMgr::getInstance()->canHearSound(char_pos_global)
|
|
&& !LLMuteList::getInstance()->isMuted(getID(), LLMute::flagObjectSounds))
|
|
{
|
|
// RN: uncomment this to play on typing sound at fixed volume once sound engine is fixed
|
|
// to support both spatialized and non-spatialized instances of the same sound
|
|
//if (isSelf())
|
|
//{
|
|
// gAudiop->triggerSound(LLUUID(gSavedSettings.getString("UISndTyping")), 1.0f, LLAudioEngine::AUDIO_TYPE_UI);
|
|
//}
|
|
//else
|
|
{
|
|
LLUUID sound_id = LLUUID(gSavedSettings.getString("UISndTyping"));
|
|
gAudiop->triggerSound(sound_id, getID(), 1.0f, LLAudioEngine::AUDIO_TYPE_SFX, char_pos_global);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (anim_id == ANIM_AGENT_SIT_GROUND_CONSTRAINED)
|
|
{
|
|
sitDown(TRUE);
|
|
}
|
|
else if(anim_id == ANIM_AGENT_SNAPSHOT)
|
|
{
|
|
mIdleTimer.reset(); // Snapshot, not idle
|
|
static LLCachedControl<bool> announce_snapshots("AnnounceSnapshots");
|
|
if (announce_snapshots)
|
|
{
|
|
std::string name;
|
|
LLAvatarNameCache::getNSName(mID, name);
|
|
LLChat chat;
|
|
chat.mFromName = name;
|
|
chat.mText = name + " " + LLTrans::getString("took_a_snapshot") + ".";
|
|
chat.mURL = llformat("secondlife:///app/agent/%s/about",mID.asString().c_str());
|
|
chat.mSourceType = CHAT_SOURCE_SYSTEM;
|
|
LLFloaterChat::addChat(chat);
|
|
}
|
|
}
|
|
|
|
|
|
if (startMotion(anim_id))
|
|
{
|
|
result = TRUE;
|
|
}
|
|
else
|
|
{
|
|
LL_WARNS() << "Failed to start motion!" << LL_ENDL;
|
|
}
|
|
}
|
|
else //stop animation
|
|
{
|
|
if (anim_id == ANIM_AGENT_SIT_GROUND_CONSTRAINED)
|
|
{
|
|
sitDown(FALSE);
|
|
}
|
|
if ((anim_id == ANIM_AGENT_DO_NOT_DISTURB) && gAgent.isDoNotDisturb())
|
|
{
|
|
// re-assert DND tag animation
|
|
gAgent.sendAnimationRequest(ANIM_AGENT_DO_NOT_DISTURB, ANIM_REQUEST_START);
|
|
return result;
|
|
}
|
|
stopMotion(anim_id);
|
|
result = TRUE;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// isAnyAnimationSignaled()
|
|
//-----------------------------------------------------------------------------
|
|
BOOL LLVOAvatar::isAnyAnimationSignaled(const LLUUID *anim_array, const S32 num_anims) const
|
|
{
|
|
for (S32 i = 0; i < num_anims; i++)
|
|
{
|
|
if(mSignaledAnimations.find(anim_array[i]) != mSignaledAnimations.end())
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// resetAnimations()
|
|
//-----------------------------------------------------------------------------
|
|
void LLVOAvatar::resetAnimations()
|
|
{
|
|
LLKeyframeMotion::flushKeyframeCache();
|
|
flushAllMotions();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// getIdleTime()
|
|
//-----------------------------------------------------------------------------
|
|
std::string LLVOAvatar::getIdleTime(bool is_away, bool is_busy, bool is_appearance)
|
|
{
|
|
if( (mNameAway && ! is_away) ||
|
|
(mNameBusy && ! is_busy) ||
|
|
(mNameAppearance && ! is_appearance) ||
|
|
mTyping)
|
|
mIdleTimer.reset();
|
|
|
|
static LLCachedControl<bool> ascent_show_idle_time("AscentShowIdleTime", false);
|
|
U32 minutes = 0;
|
|
if(ascent_show_idle_time && !isSelf() && mIdleTimer.getElapsedTimeF32() > 120)
|
|
{
|
|
minutes = (U32)(mIdleTimer.getElapsedTimeF32()/60);
|
|
}
|
|
if(minutes != mIdleMinute)
|
|
clearNameTag();
|
|
mIdleMinute = minutes;
|
|
if(mIdleMinute)
|
|
{
|
|
LLSD args;
|
|
args["[MINUTES]"]=(LLSD::Integer)minutes;
|
|
return LLTrans::getString("AvatarIdle", args);
|
|
}
|
|
return "";
|
|
}
|
|
|
|
// Override selectively based on avatar sex and whether we're using new
|
|
// animations.
|
|
LLUUID LLVOAvatar::remapMotionID(const LLUUID& id)
|
|
{
|
|
static const LLCachedControl<bool> use_new_walk_run("UseNewWalkRun",true);
|
|
static const LLCachedControl<bool> use_cross_walk_run("UseCrossWalkRun",false);
|
|
LLUUID result = id;
|
|
|
|
// start special case female walk for female avatars
|
|
if ((getSex() == SEX_FEMALE) != use_cross_walk_run)
|
|
{
|
|
if (id == ANIM_AGENT_WALK)
|
|
{
|
|
if (use_new_walk_run)
|
|
result = ANIM_AGENT_FEMALE_WALK_NEW;
|
|
else
|
|
result = ANIM_AGENT_FEMALE_WALK;
|
|
}
|
|
else if (id == ANIM_AGENT_RUN)
|
|
{
|
|
// There is no old female run animation, so only override
|
|
// in one case.
|
|
if (use_new_walk_run)
|
|
result = ANIM_AGENT_FEMALE_RUN_NEW;
|
|
}
|
|
else if (id == ANIM_AGENT_SIT)
|
|
{
|
|
result = ANIM_AGENT_SIT_FEMALE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Male avatar.
|
|
if (id == ANIM_AGENT_WALK)
|
|
{
|
|
if (use_new_walk_run)
|
|
result = ANIM_AGENT_WALK_NEW;
|
|
}
|
|
else if (id == ANIM_AGENT_RUN)
|
|
{
|
|
if (use_new_walk_run)
|
|
result = ANIM_AGENT_RUN_NEW;
|
|
}
|
|
// keeps in sync with setSex() related code (viewer controls sit's sex)
|
|
else if (id == ANIM_AGENT_SIT_FEMALE)
|
|
{
|
|
result = ANIM_AGENT_SIT;
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// startMotion()
|
|
// id is the asset id of the animation to start
|
|
// time_offset is the offset into the animation at which to start playing
|
|
//-----------------------------------------------------------------------------
|
|
BOOL LLVOAvatar::startMotion(const LLUUID& id, F32 time_offset)
|
|
{
|
|
// [Ansariel Hiller]: Disable pesky hover up animation that changes
|
|
// hand and finger position and often breaks correct
|
|
// fit of prim nails, rings etc. when flying and
|
|
// using an AO.
|
|
if ("62c5de58-cb33-5743-3d07-9e4cd4352864" == id.getString() && gSavedSettings.getBOOL("DisableInternalFlyUpAnimation"))
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
LL_DEBUGS() << "motion requested " << id.asString() << " " << gAnimLibrary.animationName(id) << LL_ENDL;
|
|
|
|
LLUUID remap_id = remapMotionID(id);
|
|
|
|
if (remap_id != id)
|
|
{
|
|
LL_DEBUGS() << "motion resultant " << remap_id.asString() << " " << gAnimLibrary.animationName(remap_id) << LL_ENDL;
|
|
}
|
|
|
|
if (isSelf() && remap_id == ANIM_AGENT_AWAY)
|
|
{
|
|
gAgent.setAFK();
|
|
}
|
|
|
|
return LLCharacter::startMotion(remap_id, time_offset);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// stopMotion()
|
|
//-----------------------------------------------------------------------------
|
|
BOOL LLVOAvatar::stopMotion(const LLUUID& id, BOOL stop_immediate)
|
|
{
|
|
LL_DEBUGS() << "motion requested " << id.asString() << " " << gAnimLibrary.animationName(id) << LL_ENDL;
|
|
|
|
LLUUID remap_id = remapMotionID(id);
|
|
|
|
if (remap_id != id)
|
|
{
|
|
LL_DEBUGS() << "motion resultant " << remap_id.asString() << " " << gAnimLibrary.animationName(remap_id) << LL_ENDL;
|
|
}
|
|
|
|
if (isSelf())
|
|
{
|
|
gAgent.onAnimStop(remap_id);
|
|
}
|
|
|
|
return LLCharacter::stopMotion(remap_id, stop_immediate);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// hasMotionFromSource()
|
|
//-----------------------------------------------------------------------------
|
|
// virtual
|
|
bool LLVOAvatar::hasMotionFromSource(const LLUUID& source_id)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// stopMotionFromSource()
|
|
//-----------------------------------------------------------------------------
|
|
// virtual
|
|
void LLVOAvatar::stopMotionFromSource(const LLUUID& source_id)
|
|
{
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// addDebugText()
|
|
//-----------------------------------------------------------------------------
|
|
void LLVOAvatar::addDebugText(const std::string& text)
|
|
{
|
|
mDebugText.append(1, '\n');
|
|
mDebugText.append(text);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// getID()
|
|
//-----------------------------------------------------------------------------
|
|
const LLUUID& LLVOAvatar::getID() const
|
|
{
|
|
return mID;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// getJoint()
|
|
//-----------------------------------------------------------------------------
|
|
// RN: avatar joints are multi-rooted to include screen-based attachments
|
|
LLJoint *LLVOAvatar::getJoint( const std::string &name )
|
|
{
|
|
joint_map_t::iterator iter = std::find_if(mJointMap.begin(), mJointMap.end(), [&name](joint_map_t::value_type &pair) { return strcmp(name.c_str(), pair.first) == 0; });
|
|
|
|
LLJoint* jointp = NULL;
|
|
|
|
if (iter == mJointMap.end() || iter->second == NULL)
|
|
{ //search for joint and cache found joint in lookup table
|
|
jointp = mRoot->findJoint(name);
|
|
joint_map_t::value_type entry;
|
|
strncpy(entry.first, name.c_str(), sizeof(entry.first));
|
|
entry.second = jointp;
|
|
mJointMap.emplace_back(entry);
|
|
}
|
|
else
|
|
{ //return cached pointer
|
|
jointp = iter->second;
|
|
}
|
|
|
|
return jointp;
|
|
}
|
|
|
|
LLJoint *LLVOAvatar::getJoint( S32 joint_num )
|
|
{
|
|
LLJoint *pJoint = NULL;
|
|
S32 collision_start = mNumBones;
|
|
S32 attachment_start = mNumBones + mCollisionVolumes.size();
|
|
if (joint_num>=attachment_start)
|
|
{
|
|
// Attachment IDs start at 1
|
|
S32 attachment_id = joint_num - attachment_start + 1;
|
|
attachment_map_t::iterator iter = mAttachmentPoints.find(attachment_id);
|
|
if (iter != mAttachmentPoints.end())
|
|
{
|
|
pJoint = iter->second;
|
|
}
|
|
}
|
|
else if (joint_num>=collision_start)
|
|
{
|
|
S32 collision_id = joint_num-collision_start;
|
|
pJoint = mCollisionVolumes[collision_id];
|
|
}
|
|
else if (joint_num>=0)
|
|
{
|
|
pJoint = mSkeleton[joint_num];
|
|
}
|
|
llassert(!pJoint || pJoint->getJointNum() == joint_num);
|
|
return pJoint;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// getRiggedMeshID
|
|
//
|
|
// If viewer object is a rigged mesh, set the mesh id and return true.
|
|
// Otherwise, null out the id and return false.
|
|
//-----------------------------------------------------------------------------
|
|
// static
|
|
bool LLVOAvatar::getRiggedMeshID(LLViewerObject* pVO, LLUUID& mesh_id)
|
|
{
|
|
mesh_id.setNull();
|
|
|
|
//If a VO has a skin that we'll reset the joint positions to their default
|
|
if ( pVO && pVO->mDrawable )
|
|
{
|
|
LLVOVolume* pVObj = pVO->mDrawable->getVOVolume();
|
|
if ( pVObj )
|
|
{
|
|
const LLMeshSkinInfo* pSkinData = pVObj->getSkinInfo();
|
|
if (pSkinData
|
|
&& pSkinData->mJointNames.size() > JOINT_COUNT_REQUIRED_FOR_FULLRIG // full rig
|
|
&& pSkinData->mAlternateBindMatrix.size() > 0 )
|
|
{
|
|
mesh_id = pSkinData->mMeshID;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool LLVOAvatar::jointIsRiggedTo(const LLJoint *joint) const
|
|
{
|
|
if (joint)
|
|
{
|
|
const LLJointRiggingInfoTab& tab = mJointRiggingInfoTab;
|
|
S32 joint_num = joint->getJointNum();
|
|
if (joint_num < tab.size() && tab[joint_num].isRiggedTo())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void LLVOAvatar::clearAttachmentOverrides()
|
|
{
|
|
for (S32 i=0; i<LL_CHARACTER_MAX_ANIMATED_JOINTS; i++)
|
|
{
|
|
LLJoint *pJoint = getJoint(i);
|
|
if (pJoint)
|
|
{
|
|
pJoint->clearAttachmentPosOverrides();
|
|
pJoint->clearAttachmentScaleOverrides();
|
|
}
|
|
}
|
|
|
|
if (mPelvisFixups.count()>0)
|
|
{
|
|
mPelvisFixups.clear();
|
|
LLJoint* pJointPelvis = getJoint("mPelvis");
|
|
if (pJointPelvis)
|
|
{
|
|
pJointPelvis->setPosition( LLVector3( 0.0f, 0.0f, 0.0f) );
|
|
}
|
|
postPelvisSetRecalc();
|
|
}
|
|
|
|
mActiveOverrideMeshes.clear();
|
|
onActiveOverrideMeshesChanged();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// rebuildAttachmentOverrides
|
|
//-----------------------------------------------------------------------------
|
|
void LLVOAvatar::rebuildAttachmentOverrides()
|
|
{
|
|
clearAttachmentOverrides();
|
|
|
|
// Handle the case that we're resetting the skeleton of an animated object.
|
|
LLControlAvatar *control_av = asControlAvatar();
|
|
if (control_av)
|
|
{
|
|
LLVOVolume *volp = control_av->mRootVolp;
|
|
if (volp)
|
|
{
|
|
LL_DEBUGS("Avatar") << volp->getID() << " adding attachment overrides for root vol, prim count "
|
|
<< (S32) (1+volp->numChildren()) << LL_ENDL;
|
|
addAttachmentOverridesForObject(volp);
|
|
}
|
|
}
|
|
|
|
// Attached objects
|
|
#if SLOW_ATTACHMENT_LIST
|
|
for (attachment_map_t::iterator iter = mAttachmentPoints.begin();
|
|
iter != mAttachmentPoints.end();
|
|
++iter)
|
|
{
|
|
LLViewerJointAttachment *attachment_pt = (*iter).second;
|
|
if (attachment_pt)
|
|
{
|
|
for (LLViewerJointAttachment::attachedobjs_vec_t::iterator at_it = attachment_pt->mAttachedObjects.begin();
|
|
at_it != attachment_pt->mAttachedObjects.end(); ++at_it)
|
|
{
|
|
LLViewerObject *vo = *at_it;
|
|
#else
|
|
for(auto& iter : mAttachedObjectsVector)
|
|
{{{
|
|
LLViewerObject *vo = iter.first;
|
|
#endif
|
|
// Attached animated objects affect joints in their control
|
|
// avs, not the avs to which they are attached.
|
|
if (vo && !vo->isAnimatedObject())
|
|
{
|
|
addAttachmentOverridesForObject(vo);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// updateAttachmentOverrides
|
|
//
|
|
// This is intended to give the same results as
|
|
// rebuildAttachmentOverrides(), while avoiding redundant work.
|
|
// -----------------------------------------------------------------------------
|
|
void LLVOAvatar::updateAttachmentOverrides()
|
|
{
|
|
LL_DEBUGS("AnimatedObjects") << "updating" << LL_ENDL;
|
|
|
|
uuid_set_t meshes_seen;
|
|
|
|
// Handle the case that we're updating the skeleton of an animated object.
|
|
LLControlAvatar *control_av = asControlAvatar();
|
|
if (control_av)
|
|
{
|
|
LLVOVolume *volp = control_av->mRootVolp;
|
|
if (volp)
|
|
{
|
|
LL_DEBUGS("Avatar") << volp->getID() << " adding attachment overrides for root vol, prim count "
|
|
<< (S32) (1+volp->numChildren()) << LL_ENDL;
|
|
addAttachmentOverridesForObject(volp, &meshes_seen);
|
|
}
|
|
}
|
|
|
|
// Attached objects
|
|
#if SLOW_ATTACHMENT_LIST
|
|
for (attachment_map_t::iterator iter = mAttachmentPoints.begin();
|
|
iter != mAttachmentPoints.end();
|
|
++iter)
|
|
{
|
|
LLViewerJointAttachment *attachment_pt = (*iter).second;
|
|
if (attachment_pt)
|
|
{
|
|
for (LLViewerJointAttachment::attachedobjs_vec_t::iterator at_it = attachment_pt->mAttachedObjects.begin();
|
|
at_it != attachment_pt->mAttachedObjects.end(); ++at_it)
|
|
{
|
|
LLViewerObject *vo = *at_it;
|
|
#else
|
|
for(auto& iter : mAttachedObjectsVector)
|
|
{{{
|
|
LLViewerObject *vo = iter.first;
|
|
#endif
|
|
// Attached animated objects affect joints in their control
|
|
// avs, not the avs to which they are attached.
|
|
if (!vo->isAnimatedObject())
|
|
{
|
|
addAttachmentOverridesForObject(vo, &meshes_seen);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Remove meshes that are no longer present on the skeleton
|
|
|
|
// have to work with a copy because removeAttachmentOverrides() will change mActiveOverrideMeshes.
|
|
uuid_set_t active_override_meshes = mActiveOverrideMeshes;
|
|
for (auto it = active_override_meshes.begin(); it != active_override_meshes.end(); ++it)
|
|
{
|
|
if (meshes_seen.find(*it) == meshes_seen.end())
|
|
{
|
|
removeAttachmentOverridesForObject(*it);
|
|
}
|
|
}
|
|
}
|
|
// addAttachmentPosOverridesForObject
|
|
//-----------------------------------------------------------------------------
|
|
void LLVOAvatar::addAttachmentOverridesForObject(LLViewerObject *vo, uuid_set_t* meshes_seen, bool recursive)
|
|
{
|
|
if (vo->getAvatar() != this && vo->getAvatarAncestor() != this)
|
|
{
|
|
LL_WARNS("Avatar") << "called with invalid avatar" << LL_ENDL;
|
|
return;
|
|
}
|
|
|
|
LL_DEBUGS("AnimatedObjects") << "adding" << LL_ENDL;
|
|
|
|
// Process all children
|
|
if (recursive)
|
|
{
|
|
LLViewerObject::const_child_list_t& children = vo->getChildren();
|
|
for (LLViewerObject::const_child_list_t::const_iterator it = children.begin();
|
|
it != children.end(); ++it)
|
|
{
|
|
LLViewerObject *childp = *it;
|
|
addAttachmentOverridesForObject(childp, meshes_seen, true);
|
|
}
|
|
}
|
|
|
|
LLVOVolume *vobj = dynamic_cast<LLVOVolume*>(vo);
|
|
if (vobj && vobj->isRiggedMesh() &&
|
|
vobj->getVolume() && vobj->getVolume()->isMeshAssetLoaded() && gMeshRepo.meshRezEnabled())
|
|
{
|
|
bool pelvisGotSet = false;
|
|
const LLMeshSkinInfo* pSkinData = vobj->getSkinInfo();
|
|
const U32 bindCnt = pSkinData->mAlternateBindMatrix.size();
|
|
const U32 jointCnt = pSkinData->mJointNames.size();
|
|
if ((bindCnt > 0) && (bindCnt != jointCnt))
|
|
{
|
|
LL_WARNS_ONCE() << "invalid mesh, bindCnt " << bindCnt << "!= jointCnt " << jointCnt << ", joint overrides will be ignored." << LL_ENDL;
|
|
}
|
|
if ((bindCnt > 0) && (bindCnt == jointCnt))
|
|
{
|
|
const F32 pelvisZOffset = pSkinData->mPelvisOffset;
|
|
const LLUUID& mesh_id = pSkinData->mMeshID;
|
|
|
|
if (meshes_seen)
|
|
{
|
|
meshes_seen->insert(mesh_id);
|
|
}
|
|
bool mesh_overrides_loaded = (mActiveOverrideMeshes.find(mesh_id) != mActiveOverrideMeshes.end());
|
|
bool fullRig = (jointCnt>=JOINT_COUNT_REQUIRED_FOR_FULLRIG) ? true : false;
|
|
if ( fullRig && !mesh_overrides_loaded )
|
|
{
|
|
for ( U32 i=0; i<jointCnt; ++i )
|
|
{
|
|
std::string lookingForJoint = pSkinData->mJointNames[i].c_str();
|
|
LLJoint* pJoint = getJoint( lookingForJoint );
|
|
if ( pJoint )
|
|
{
|
|
const LLVector3& jointPos = pSkinData->mAlternateBindMatrix[i].getTranslation();
|
|
if (pJoint->aboveJointPosThreshold(jointPos))
|
|
{
|
|
bool override_changed;
|
|
pJoint->addAttachmentPosOverride( jointPos, mesh_id, avString(), override_changed );
|
|
|
|
if (override_changed)
|
|
{
|
|
//If joint is a pelvis then handle old/new pelvis to foot values
|
|
if ( lookingForJoint == "mPelvis" )
|
|
{
|
|
pelvisGotSet = true;
|
|
}
|
|
}
|
|
if (pSkinData->mLockScaleIfJointPosition)
|
|
{
|
|
// Note that unlike positions, there's no threshold check here,
|
|
// just a lock at the default value.
|
|
pJoint->addAttachmentScaleOverride(pJoint->getDefaultScale(), mesh_id, avString());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (pelvisZOffset != 0.0F)
|
|
{
|
|
F32 pelvis_fixup_before;
|
|
bool has_fixup_before = hasPelvisFixup(pelvis_fixup_before);
|
|
addPelvisFixup( pelvisZOffset, mesh_id );
|
|
F32 pelvis_fixup_after;
|
|
hasPelvisFixup(pelvis_fixup_after); // Don't have to check bool here because we just added it...
|
|
if (!has_fixup_before || (pelvis_fixup_before != pelvis_fixup_after))
|
|
{
|
|
pelvisGotSet = true;
|
|
}
|
|
|
|
}
|
|
mActiveOverrideMeshes.insert(mesh_id);
|
|
onActiveOverrideMeshesChanged();
|
|
}
|
|
}
|
|
//Rebuild body data if we altered joints/pelvis
|
|
if (pelvisGotSet)
|
|
{
|
|
postPelvisSetRecalc();
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// getAttachmentOverrideNames
|
|
//-----------------------------------------------------------------------------
|
|
void LLVOAvatar::getAttachmentOverrideNames(std::set<std::string>& pos_names, std::set<std::string>& scale_names) const
|
|
{
|
|
LLVector3 pos;
|
|
LLVector3 scale;
|
|
LLUUID mesh_id;
|
|
|
|
// Bones
|
|
for (avatar_joint_list_t::const_iterator iter = mSkeleton.begin();
|
|
iter != mSkeleton.end(); ++iter)
|
|
{
|
|
const LLJoint* pJoint = (*iter);
|
|
if (pJoint && pJoint->hasAttachmentPosOverride(pos,mesh_id))
|
|
{
|
|
pos_names.insert(pJoint->getName());
|
|
}
|
|
if (pJoint && pJoint->hasAttachmentScaleOverride(scale,mesh_id))
|
|
{
|
|
scale_names.insert(pJoint->getName());
|
|
}
|
|
}
|
|
|
|
// Attachment points
|
|
for (attachment_map_t::const_iterator iter = mAttachmentPoints.begin();
|
|
iter != mAttachmentPoints.end();
|
|
++iter)
|
|
{
|
|
const LLViewerJointAttachment *attachment_pt = (*iter).second;
|
|
if (attachment_pt && attachment_pt->hasAttachmentPosOverride(pos,mesh_id))
|
|
{
|
|
pos_names.insert(attachment_pt->getName());
|
|
}
|
|
// Attachment points don't have scales.
|
|
}
|
|
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// showAttachmentOverrides
|
|
//-----------------------------------------------------------------------------
|
|
void LLVOAvatar::showAttachmentOverrides(bool verbose) const
|
|
{
|
|
std::set<std::string> pos_names, scale_names;
|
|
getAttachmentOverrideNames(pos_names, scale_names);
|
|
if (pos_names.size())
|
|
{
|
|
std::stringstream ss;
|
|
std::copy(pos_names.begin(), pos_names.end(), std::ostream_iterator<std::string>(ss, ","));
|
|
LL_INFOS() << getFullname() << " attachment positions defined for joints: " << ss.str() << "\n" << LL_ENDL;
|
|
}
|
|
else
|
|
{
|
|
LL_DEBUGS("Avatar") << getFullname() << " no attachment positions defined for any joints" << "\n" << LL_ENDL;
|
|
}
|
|
if (scale_names.size())
|
|
{
|
|
std::stringstream ss;
|
|
std::copy(scale_names.begin(), scale_names.end(), std::ostream_iterator<std::string>(ss, ","));
|
|
LL_INFOS() << getFullname() << " attachment scales defined for joints: " << ss.str() << "\n" << LL_ENDL;
|
|
}
|
|
else
|
|
{
|
|
LL_INFOS() << getFullname() << " no attachment scales defined for any joints" << "\n" << LL_ENDL;
|
|
}
|
|
|
|
if (!verbose)
|
|
{
|
|
return;
|
|
}
|
|
|
|
LLVector3 pos, scale;
|
|
LLUUID mesh_id;
|
|
S32 count = 0;
|
|
|
|
// Bones
|
|
for (avatar_joint_list_t::const_iterator iter = mSkeleton.begin();
|
|
iter != mSkeleton.end(); ++iter)
|
|
{
|
|
const LLJoint* pJoint = (*iter);
|
|
if (pJoint && pJoint->hasAttachmentPosOverride(pos,mesh_id))
|
|
{
|
|
pJoint->showAttachmentPosOverrides(getFullname());
|
|
count++;
|
|
}
|
|
if (pJoint && pJoint->hasAttachmentScaleOverride(scale,mesh_id))
|
|
{
|
|
pJoint->showAttachmentScaleOverrides(getFullname());
|
|
count++;
|
|
}
|
|
}
|
|
|
|
// Attachment points
|
|
for (attachment_map_t::const_iterator iter = mAttachmentPoints.begin();
|
|
iter != mAttachmentPoints.end();
|
|
++iter)
|
|
{
|
|
const LLViewerJointAttachment *attachment_pt = (*iter).second;
|
|
if (attachment_pt && attachment_pt->hasAttachmentPosOverride(pos,mesh_id))
|
|
{
|
|
attachment_pt->showAttachmentPosOverrides(getFullname());
|
|
count++;
|
|
}
|
|
}
|
|
|
|
if (count)
|
|
{
|
|
LL_DEBUGS("Avatar") << avString() << " end of pos, scale overrides" << LL_ENDL;
|
|
LL_DEBUGS("Avatar") << "=================================" << LL_ENDL;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// removeAttachmentOverridesForObject
|
|
//-----------------------------------------------------------------------------
|
|
void LLVOAvatar::removeAttachmentOverridesForObject(LLViewerObject *vo)
|
|
{
|
|
if (vo->getAvatar() != this && vo->getAvatarAncestor() != this)
|
|
{
|
|
LL_WARNS("Avatar") << "called with invalid avatar" << LL_ENDL;
|
|
return;
|
|
}
|
|
|
|
// Process all children
|
|
LLViewerObject::const_child_list_t& children = vo->getChildren();
|
|
for (LLViewerObject::const_child_list_t::const_iterator it = children.begin();
|
|
it != children.end(); ++it)
|
|
{
|
|
LLViewerObject *childp = *it;
|
|
removeAttachmentOverridesForObject(childp);
|
|
}
|
|
|
|
// Process self.
|
|
LLUUID mesh_id;
|
|
if (getRiggedMeshID(vo,mesh_id))
|
|
{
|
|
removeAttachmentOverridesForObject(mesh_id);
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// removeAttachmentOverridesForObject
|
|
//-----------------------------------------------------------------------------
|
|
void LLVOAvatar::removeAttachmentOverridesForObject(const LLUUID& mesh_id)
|
|
{
|
|
LLJoint* pJointPelvis = getJoint("mPelvis");
|
|
const std::string av_string = avString();
|
|
for (S32 joint_num = 0; joint_num < LL_CHARACTER_MAX_ANIMATED_JOINTS; joint_num++)
|
|
{
|
|
LLJoint *pJoint = getJoint(joint_num);
|
|
if ( pJoint )
|
|
{
|
|
bool dummy; // unused
|
|
pJoint->removeAttachmentPosOverride(mesh_id, av_string, dummy);
|
|
pJoint->removeAttachmentScaleOverride(mesh_id, av_string);
|
|
}
|
|
if ( pJoint && pJoint == pJointPelvis)
|
|
{
|
|
removePelvisFixup( mesh_id );
|
|
// SL-315
|
|
pJoint->setPosition( LLVector3( 0.0f, 0.0f, 0.0f) );
|
|
}
|
|
}
|
|
|
|
postPelvisSetRecalc();
|
|
|
|
mActiveOverrideMeshes.erase(mesh_id);
|
|
onActiveOverrideMeshesChanged();
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
// getCharacterPosition()
|
|
//-----------------------------------------------------------------------------
|
|
LLVector3 LLVOAvatar::getCharacterPosition()
|
|
{
|
|
if (mDrawable.notNull())
|
|
{
|
|
return mDrawable->getPositionAgent();
|
|
}
|
|
else
|
|
{
|
|
return getPositionAgent();
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// LLVOAvatar::getCharacterRotation()
|
|
//-----------------------------------------------------------------------------
|
|
LLQuaternion LLVOAvatar::getCharacterRotation()
|
|
{
|
|
return getRotation();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// LLVOAvatar::getCharacterVelocity()
|
|
//-----------------------------------------------------------------------------
|
|
LLVector3 LLVOAvatar::getCharacterVelocity()
|
|
{
|
|
return getVelocity() - mStepObjectVelocity;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// LLVOAvatar::getCharacterAngularVelocity()
|
|
//-----------------------------------------------------------------------------
|
|
LLVector3 LLVOAvatar::getCharacterAngularVelocity()
|
|
{
|
|
return getAngularVelocity();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// LLVOAvatar::getGround()
|
|
//-----------------------------------------------------------------------------
|
|
void LLVOAvatar::getGround(const LLVector3 &in_pos_agent, LLVector3 &out_pos_agent, LLVector3 &outNorm)
|
|
{
|
|
LLVector3d z_vec(0.0f, 0.0f, 1.0f);
|
|
LLVector3d p0_global, p1_global;
|
|
|
|
if (isUIAvatar())
|
|
{
|
|
outNorm.setVec(z_vec);
|
|
out_pos_agent = in_pos_agent;
|
|
return;
|
|
}
|
|
|
|
p0_global = gAgent.getPosGlobalFromAgent(in_pos_agent) + z_vec;
|
|
p1_global = gAgent.getPosGlobalFromAgent(in_pos_agent) - z_vec;
|
|
LLViewerObject *obj;
|
|
LLVector3d out_pos_global;
|
|
LLWorld::getInstance()->resolveStepHeightGlobal(this, p0_global, p1_global, out_pos_global, outNorm, &obj);
|
|
out_pos_agent = gAgent.getPosAgentFromGlobal(out_pos_global);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// LLVOAvatar::getTimeDilation()
|
|
//-----------------------------------------------------------------------------
|
|
F32 LLVOAvatar::getTimeDilation()
|
|
{
|
|
return mRegionp ? mRegionp->getTimeDilation() : 1.f;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// LLVOAvatar::getPixelArea()
|
|
//-----------------------------------------------------------------------------
|
|
F32 LLVOAvatar::getPixelArea() const
|
|
{
|
|
if (isUIAvatar())
|
|
{
|
|
return 100000.f;
|
|
}
|
|
return mPixelArea;
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// LLVOAvatar::getPosGlobalFromAgent()
|
|
//-----------------------------------------------------------------------------
|
|
LLVector3d LLVOAvatar::getPosGlobalFromAgent(const LLVector3 &position)
|
|
{
|
|
return gAgent.getPosGlobalFromAgent(position);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// getPosAgentFromGlobal()
|
|
//-----------------------------------------------------------------------------
|
|
LLVector3 LLVOAvatar::getPosAgentFromGlobal(const LLVector3d &position)
|
|
{
|
|
return gAgent.getPosAgentFromGlobal(position);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// requestStopMotion()
|
|
//-----------------------------------------------------------------------------
|
|
// virtual
|
|
void LLVOAvatar::requestStopMotion( LLMotion* motion )
|
|
{
|
|
// Only agent avatars should handle the stop motion notifications.
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// loadSkeletonNode(): loads <skeleton> node from XML tree
|
|
//-----------------------------------------------------------------------------
|
|
//virtual
|
|
BOOL LLVOAvatar::loadSkeletonNode ()
|
|
{
|
|
if (!LLAvatarAppearance::loadSkeletonNode())
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
bool ignore_hud_joints = false;
|
|
initAttachmentPoints(ignore_hud_joints);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// initAttachmentPoints(): creates attachment points if needed, sets state based on avatar_lad.xml.
|
|
//-----------------------------------------------------------------------------
|
|
void LLVOAvatar::initAttachmentPoints(bool ignore_hud_joints)
|
|
{
|
|
LLAvatarXmlInfo::attachment_info_list_t::iterator iter;
|
|
for (iter = sAvatarXmlInfo->mAttachmentInfoList.begin();
|
|
iter != sAvatarXmlInfo->mAttachmentInfoList.end();
|
|
++iter)
|
|
{
|
|
LLAvatarXmlInfo::LLAvatarAttachmentInfo *info = *iter;
|
|
if (info->mIsHUDAttachment && (!isSelf() || ignore_hud_joints))
|
|
{
|
|
//don't process hud joint for other avatars, or when doing a skeleton reset.
|
|
continue;
|
|
}
|
|
|
|
S32 attachmentID = info->mAttachmentID;
|
|
if (attachmentID < 1 || attachmentID > 255)
|
|
{
|
|
LL_WARNS() << "Attachment point out of range [1-255]: " << attachmentID << " on attachment point " << info->mName << LL_ENDL;
|
|
continue;
|
|
}
|
|
|
|
LLViewerJointAttachment* attachment = NULL;
|
|
bool newly_created = false;
|
|
if (mAttachmentPoints.find(attachmentID) == mAttachmentPoints.end())
|
|
{
|
|
attachment = new LLViewerJointAttachment();
|
|
newly_created = true;
|
|
}
|
|
else
|
|
{
|
|
attachment = mAttachmentPoints[attachmentID];
|
|
}
|
|
|
|
attachment->setName(info->mName);
|
|
LLJoint *parent_joint = getJoint(info->mJointName);
|
|
if (!parent_joint)
|
|
{
|
|
// If the intended parent for attachment point is unavailable, avatar_lad.xml is corrupt.
|
|
LL_WARNS() << "No parent joint by name " << info->mJointName << " found for attachment point " << info->mName << LL_ENDL;
|
|
if( newly_created )
|
|
{
|
|
delete attachment;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (info->mHasPosition)
|
|
{
|
|
attachment->setOriginalPosition(info->mPosition);
|
|
attachment->setDefaultPosition(info->mPosition);
|
|
}
|
|
|
|
if (info->mHasRotation)
|
|
{
|
|
LLQuaternion rotation;
|
|
rotation.setQuat(info->mRotationEuler.mV[VX] * DEG_TO_RAD,
|
|
info->mRotationEuler.mV[VY] * DEG_TO_RAD,
|
|
info->mRotationEuler.mV[VZ] * DEG_TO_RAD);
|
|
attachment->setRotation(rotation);
|
|
}
|
|
|
|
int group = info->mGroup;
|
|
if (group >= 0)
|
|
{
|
|
if (group < 0 || group >= NUM_ATTACHMENT_GROUPS )
|
|
{
|
|
LL_WARNS() << "Invalid group number (" << group << ") for attachment point " << info->mName << LL_ENDL;
|
|
}
|
|
else
|
|
{
|
|
attachment->setGroup(group);
|
|
}
|
|
}
|
|
|
|
attachment->setPieSlice(info->mPieMenuSlice);
|
|
attachment->setVisibleInFirstPerson(info->mVisibleFirstPerson);
|
|
attachment->setIsHUDAttachment(info->mIsHUDAttachment);
|
|
// attachment can potentially be animated, needs a number.
|
|
attachment->setJointNum(mNumBones + mCollisionVolumes.size() + attachmentID - 1);
|
|
|
|
if (newly_created)
|
|
{
|
|
mAttachmentPoints[attachmentID] = attachment;
|
|
|
|
// now add attachment joint
|
|
parent_joint->addChild(attachment);
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// updateVisualParams()
|
|
//-----------------------------------------------------------------------------
|
|
void LLVOAvatar::updateVisualParams()
|
|
{
|
|
ESex avatar_sex = (getVisualParamWeight("male") > 0.5f) ? SEX_MALE : SEX_FEMALE;
|
|
if (getSex() != avatar_sex)
|
|
{
|
|
if (mIsSitting && findMotion(avatar_sex == SEX_MALE ? ANIM_AGENT_SIT_FEMALE : ANIM_AGENT_SIT) != NULL)
|
|
{
|
|
// In some cases of gender change server changes sit motion with motion message,
|
|
// but in case of some avatars (legacy?) there is no update from server side,
|
|
// likely because server doesn't know about difference between motions
|
|
// (female and male sit ids are same server side, so it is likely unaware that it
|
|
// need to send update)
|
|
// Make sure motion is up to date
|
|
stopMotion(ANIM_AGENT_SIT);
|
|
setSex(avatar_sex);
|
|
startMotion(ANIM_AGENT_SIT);
|
|
}
|
|
else
|
|
{
|
|
setSex(avatar_sex);
|
|
}
|
|
}
|
|
|
|
LLCharacter::updateVisualParams();
|
|
|
|
if (mLastSkeletonSerialNum != mSkeletonSerialNum)
|
|
{
|
|
computeBodySize();
|
|
mLastSkeletonSerialNum = mSkeletonSerialNum;
|
|
mRoot->updateWorldMatrixChildren();
|
|
}
|
|
|
|
dirtyMesh();
|
|
updateHeadOffset();
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
// isActive()
|
|
//-----------------------------------------------------------------------------
|
|
BOOL LLVOAvatar::isActive() const
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// setPixelAreaAndAngle()
|
|
//-----------------------------------------------------------------------------
|
|
static LLFastTimer::DeclareTimer FTM_PIXEL_AREA("Pixel Area");
|
|
void LLVOAvatar::setPixelAreaAndAngle(LLAgent &agent)
|
|
{
|
|
LL_RECORD_BLOCK_TIME(FTM_PIXEL_AREA);
|
|
if (mDrawable.isNull())
|
|
{
|
|
return;
|
|
}
|
|
|
|
const LLVector4a* ext = mDrawable->getSpatialExtents();
|
|
LLVector4a center;
|
|
center.setAdd(ext[1], ext[0]);
|
|
center.mul(0.5f);
|
|
LLVector4a size;
|
|
size.setSub(ext[1], ext[0]);
|
|
size.mul(0.5f);
|
|
|
|
mImpostorPixelArea = LLPipeline::calcPixelArea(center, size, *LLViewerCamera::getInstance());
|
|
|
|
F32 range = mDrawable->mDistanceWRTCamera;
|
|
|
|
if (range < 0.001f) // range == zero
|
|
{
|
|
mAppAngle = 180.f;
|
|
}
|
|
else
|
|
{
|
|
F32 radius = size.getLength3().getF32();
|
|
mAppAngle = (F32) atan2( radius, range) * RAD_TO_DEG;
|
|
}
|
|
|
|
// We always want to look good to ourselves
|
|
if( isSelf() )
|
|
{
|
|
mPixelArea = llmax( mPixelArea, F32(getTexImageSize() / 16) );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// updateJointLODs()
|
|
//-----------------------------------------------------------------------------
|
|
BOOL LLVOAvatar::updateJointLODs()
|
|
{
|
|
const F32 MAX_PIXEL_AREA = 100000000.f;
|
|
F32 lod_factor = (sLODFactor * AVATAR_LOD_TWEAK_RANGE + (1.f - AVATAR_LOD_TWEAK_RANGE));
|
|
F32 avatar_num_min_factor = clamp_rescale(sLODFactor, 0.f, 1.f, 0.25f, 0.6f);
|
|
F32 avatar_num_factor = clamp_rescale((F32)sNumVisibleAvatars, 8, 25, 1.f, avatar_num_min_factor);
|
|
F32 area_scale = 0.16f;
|
|
|
|
if (isSelf())
|
|
{
|
|
if(gAgentCamera.cameraCustomizeAvatar() || gAgentCamera.cameraMouselook())
|
|
{
|
|
mAdjustedPixelArea = MAX_PIXEL_AREA;
|
|
}
|
|
else
|
|
{
|
|
mAdjustedPixelArea = mPixelArea*area_scale;
|
|
}
|
|
}
|
|
else if (mIsDummy)
|
|
{
|
|
mAdjustedPixelArea = MAX_PIXEL_AREA;
|
|
}
|
|
else
|
|
{
|
|
// reported avatar pixel area is dependent on avatar render load, based on number of visible avatars
|
|
mAdjustedPixelArea = (F32)mPixelArea * area_scale * lod_factor * lod_factor * avatar_num_factor * avatar_num_factor;
|
|
}
|
|
|
|
// now select meshes to render based on adjusted pixel area
|
|
LLViewerJoint* root = dynamic_cast<LLViewerJoint*>(mRoot);
|
|
BOOL res = FALSE;
|
|
if (root)
|
|
{
|
|
res = root->updateLOD(mAdjustedPixelArea, TRUE);
|
|
}
|
|
if (res)
|
|
{
|
|
sNumLODChangesThisFrame++;
|
|
dirtyMesh(2);
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// createDrawable()
|
|
//-----------------------------------------------------------------------------
|
|
LLDrawable *LLVOAvatar::createDrawable(LLPipeline *pipeline)
|
|
{
|
|
pipeline->allocDrawable(this);
|
|
mDrawable->setLit(FALSE);
|
|
|
|
LLDrawPoolAvatar *poolp = (LLDrawPoolAvatar*) gPipeline.getPool(LLDrawPool::POOL_AVATAR);
|
|
|
|
// Only a single face (one per avatar)
|
|
//this face will be splitted into several if its vertex buffer is too long.
|
|
mDrawable->setState(LLDrawable::ACTIVE);
|
|
mDrawable->addFace(poolp, NULL);
|
|
mDrawable->setRenderType(LLPipeline::RENDER_TYPE_AVATAR);
|
|
|
|
mNumInitFaces = mDrawable->getNumFaces() ;
|
|
|
|
dirtyMesh(2);
|
|
return mDrawable;
|
|
}
|
|
|
|
|
|
void LLVOAvatar::updateGL()
|
|
{
|
|
if (mMeshTexturesDirty)
|
|
{
|
|
updateMeshTextures();
|
|
mMeshTexturesDirty = FALSE;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// updateGeometry()
|
|
//-----------------------------------------------------------------------------
|
|
static LLFastTimer::DeclareTimer FTM_UPDATE_AVATAR("Update Avatar");
|
|
BOOL LLVOAvatar::updateGeometry(LLDrawable *drawable)
|
|
{
|
|
LL_RECORD_BLOCK_TIME(FTM_UPDATE_AVATAR);
|
|
if (!(gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_AVATAR)))
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
if (!mMeshValid)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
if (!drawable)
|
|
{
|
|
LL_ERRS() << "LLVOAvatar::updateGeometry() called with NULL drawable" << LL_ENDL;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// updateSexDependentLayerSets()
|
|
//-----------------------------------------------------------------------------
|
|
void LLVOAvatar::updateSexDependentLayerSets( bool upload_bake )
|
|
{
|
|
invalidateComposite( mBakedTextureDatas[BAKED_HEAD].mTexLayerSet, upload_bake );
|
|
invalidateComposite( mBakedTextureDatas[BAKED_UPPER].mTexLayerSet, upload_bake );
|
|
invalidateComposite( mBakedTextureDatas[BAKED_LOWER].mTexLayerSet, upload_bake );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// dirtyMesh()
|
|
//-----------------------------------------------------------------------------
|
|
void LLVOAvatar::dirtyMesh()
|
|
{
|
|
dirtyMesh(1);
|
|
}
|
|
void LLVOAvatar::dirtyMesh(S32 priority)
|
|
{
|
|
mDirtyMesh = llmax(mDirtyMesh, priority);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// getViewerJoint()
|
|
//-----------------------------------------------------------------------------
|
|
LLViewerJoint* LLVOAvatar::getViewerJoint(S32 idx)
|
|
{
|
|
return dynamic_cast<LLViewerJoint*>(mMeshLOD[idx]);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// hideSkirt()
|
|
//-----------------------------------------------------------------------------
|
|
void LLVOAvatar::hideSkirt()
|
|
{
|
|
mMeshLOD[MESH_ID_SKIRT]->setVisible(FALSE, TRUE);
|
|
}
|
|
|
|
BOOL LLVOAvatar::setParent(LLViewerObject* parent)
|
|
{
|
|
BOOL ret ;
|
|
if (parent == NULL)
|
|
{
|
|
getOffObject();
|
|
ret = LLViewerObject::setParent(parent);
|
|
if (isSelf())
|
|
{
|
|
gAgentCamera.resetCamera();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ret = LLViewerObject::setParent(parent);
|
|
if (ret)
|
|
{
|
|
sitOnObject(parent);
|
|
}
|
|
}
|
|
return ret ;
|
|
}
|
|
|
|
void LLVOAvatar::addChild(LLViewerObject *childp)
|
|
{
|
|
childp->extractAttachmentItemID(); // find the inventory item this object is associated with.
|
|
if (isSelf())
|
|
{
|
|
const LLUUID& item_id = childp->getAttachmentItemID();
|
|
LLViewerInventoryItem *item = gInventory.getItem(item_id);
|
|
LL_DEBUGS("Avatar") << "ATT attachment child added " << (item ? item->getName() : "UNKNOWN") << " id " << item_id << LL_ENDL;
|
|
|
|
}
|
|
|
|
LLViewerObject::addChild(childp);
|
|
if (childp->mDrawable)
|
|
{
|
|
if (!attachObject(childp))
|
|
{
|
|
LL_WARNS() << "ATT addChild() failed for "
|
|
<< childp->getID()
|
|
<< " item " << childp->getAttachmentItemID()
|
|
<< LL_ENDL;
|
|
// MAINT-3312 backout
|
|
// mPendingAttachment.push_back(childp);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
mPendingAttachment.push_back(childp);
|
|
}
|
|
}
|
|
|
|
void LLVOAvatar::removeChild(LLViewerObject *childp)
|
|
{
|
|
LLViewerObject::removeChild(childp);
|
|
if (!detachObject(childp))
|
|
{
|
|
LL_WARNS() << "Calling detach on non-attached object " << LL_ENDL;
|
|
}
|
|
}
|
|
|
|
LLViewerJointAttachment* LLVOAvatar::getTargetAttachmentPoint(LLViewerObject* viewer_object)
|
|
{
|
|
S32 attachmentID = ATTACHMENT_ID_FROM_STATE(viewer_object->getAttachmentState());
|
|
|
|
// This should never happen unless the server didn't process the attachment point
|
|
// correctly, but putting this check in here to be safe.
|
|
if (attachmentID & ATTACHMENT_ADD)
|
|
{
|
|
LL_WARNS() << "Got an attachment with ATTACHMENT_ADD mask, removing ( attach pt:" << attachmentID << " )" << LL_ENDL;
|
|
attachmentID &= ~ATTACHMENT_ADD;
|
|
}
|
|
|
|
LLViewerJointAttachment* attachment = get_if_there(mAttachmentPoints, attachmentID, (LLViewerJointAttachment*)NULL);
|
|
|
|
if (!attachment)
|
|
{
|
|
LL_WARNS() << "Object attachment point invalid: " << attachmentID
|
|
<< " trying to use 1 (chest)"
|
|
<< LL_ENDL;
|
|
|
|
attachment = get_if_there(mAttachmentPoints, 1, (LLViewerJointAttachment*)NULL); // Arbitrary using 1 (chest)
|
|
if (attachment)
|
|
{
|
|
LL_WARNS() << "Object attachment point invalid: " << attachmentID
|
|
<< " on object " << viewer_object->getID()
|
|
<< " attachment item " << viewer_object->getAttachmentItemID()
|
|
<< " falling back to 1 (chest)"
|
|
<< LL_ENDL;
|
|
}
|
|
else
|
|
{
|
|
LL_WARNS() << "Object attachment point invalid: " << attachmentID
|
|
<< " on object " << viewer_object->getID()
|
|
<< " attachment item " << viewer_object->getAttachmentItemID()
|
|
<< "Unable to use fallback attachment point 1 (chest)"
|
|
<< LL_ENDL;
|
|
}
|
|
}
|
|
|
|
return attachment;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// attachObject()
|
|
//-----------------------------------------------------------------------------
|
|
const LLViewerJointAttachment *LLVOAvatar::attachObject(LLViewerObject *viewer_object)
|
|
{
|
|
if (isSelf())
|
|
{
|
|
const LLUUID& item_id = viewer_object->getAttachmentItemID();
|
|
LLViewerInventoryItem *item = gInventory.getItem(item_id);
|
|
LL_DEBUGS("Avatar") << "ATT attaching object "
|
|
<< (item ? item->getName() : "UNKNOWN") << " id " << item_id << LL_ENDL;
|
|
}
|
|
LLViewerJointAttachment* attachment = getTargetAttachmentPoint(viewer_object);
|
|
|
|
if (!attachment || !attachment->addObject(viewer_object))
|
|
{
|
|
const LLUUID& item_id = viewer_object->getAttachmentItemID();
|
|
LLViewerInventoryItem *item = gInventory.getItem(item_id);
|
|
LL_WARNS("Avatar") << "ATT attach failed "
|
|
<< (item ? item->getName() : "UNKNOWN") << " id " << item_id << LL_ENDL;
|
|
return 0;
|
|
}
|
|
|
|
// The object can already exist in the vector if it was attached while was already attached (causing a re-attach).
|
|
std::pair<LLViewerObject*, LLViewerJointAttachment*> const val(viewer_object, attachment);
|
|
if (std::find(mAttachedObjectsVector.begin(), mAttachedObjectsVector.end(), val) == mAttachedObjectsVector.end())
|
|
{
|
|
mAttachedObjectsVector.push_back(val);
|
|
}
|
|
|
|
if (!viewer_object->isAnimatedObject())
|
|
{
|
|
updateAttachmentOverrides();
|
|
}
|
|
|
|
updateVisualComplexity();
|
|
|
|
if (viewer_object->isSelected())
|
|
{
|
|
LLSelectMgr::getInstance()->updateSelectionCenter();
|
|
LLSelectMgr::getInstance()->updatePointAt();
|
|
}
|
|
|
|
viewer_object->refreshBakeTexture();
|
|
|
|
|
|
LLViewerObject::const_child_list_t& child_list = viewer_object->getChildren();
|
|
for (const auto& iter : child_list)
|
|
{
|
|
LLViewerObject* objectp = iter;
|
|
if (objectp)
|
|
{
|
|
objectp->refreshBakeTexture();
|
|
}
|
|
}
|
|
|
|
updateMeshVisibility();
|
|
|
|
return attachment;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// attachObject()
|
|
//-----------------------------------------------------------------------------
|
|
U32 LLVOAvatar::getNumAttachments() const
|
|
{
|
|
U32 num_attachments = 0;
|
|
for (attachment_map_t::const_iterator iter = mAttachmentPoints.begin();
|
|
iter != mAttachmentPoints.end();
|
|
++iter)
|
|
{
|
|
const LLViewerJointAttachment *attachment_pt = (*iter).second;
|
|
num_attachments += attachment_pt->getNumObjects();
|
|
}
|
|
return num_attachments;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// canAttachMoreObjects()
|
|
// Returns true if we can attach <n> more objects.
|
|
//-----------------------------------------------------------------------------
|
|
BOOL LLVOAvatar::canAttachMoreObjects(U32 n) const
|
|
{
|
|
return (getNumAttachments() + n) <= MAX_AGENT_ATTACHMENTS;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// getNumAnimatedObjectAttachments()
|
|
//-----------------------------------------------------------------------------
|
|
U32 LLVOAvatar::getNumAnimatedObjectAttachments() const
|
|
{
|
|
U32 num_attachments = 0;
|
|
for (attachment_map_t::const_iterator iter = mAttachmentPoints.begin();
|
|
iter != mAttachmentPoints.end();
|
|
++iter)
|
|
{
|
|
const LLViewerJointAttachment *attachment_pt = (*iter).second;
|
|
num_attachments += attachment_pt->getNumAnimatedObjects();
|
|
}
|
|
return num_attachments;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// getMaxAnimatedObjectAttachments()
|
|
// Gets from simulator feature if available, otherwise 0.
|
|
//-----------------------------------------------------------------------------
|
|
U32 LLVOAvatar::getMaxAnimatedObjectAttachments() const
|
|
{
|
|
U32 max_attach = 0;
|
|
if (gSavedSettings.getBOOL("AnimatedObjectsIgnoreLimits"))
|
|
{
|
|
max_attach = MAX_AGENT_ATTACHMENTS;
|
|
}
|
|
else
|
|
{
|
|
if (gAgent.getRegion())
|
|
{
|
|
LLSD features;
|
|
gAgent.getRegion()->getSimulatorFeatures(features);
|
|
if (features.has("AnimatedObjects"))
|
|
{
|
|
max_attach = (U32)llmax(0,features["AnimatedObjects"]["MaxAgentAnimatedObjectAttachments"].asInteger());
|
|
}
|
|
}
|
|
}
|
|
return max_attach;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// canAttachMoreAnimatedObjects()
|
|
// Returns true if we can attach <n> more animated objects.
|
|
//-----------------------------------------------------------------------------
|
|
BOOL LLVOAvatar::canAttachMoreAnimatedObjects(U32 n) const
|
|
{
|
|
return (getNumAnimatedObjectAttachments() + n) <= getMaxAnimatedObjectAttachments();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// lazyAttach()
|
|
//-----------------------------------------------------------------------------
|
|
void LLVOAvatar::lazyAttach()
|
|
{
|
|
std::vector<LLPointer<LLViewerObject> > still_pending;
|
|
|
|
for (U32 i = 0; i < mPendingAttachment.size(); i++)
|
|
{
|
|
LLPointer<LLViewerObject> cur_attachment = mPendingAttachment[i];
|
|
if (cur_attachment->mDrawable)
|
|
{
|
|
if (isSelf())
|
|
{
|
|
const LLUUID& item_id = cur_attachment->getAttachmentItemID();
|
|
LLViewerInventoryItem *item = gInventory.getItem(item_id);
|
|
LL_DEBUGS("Avatar") << "ATT attaching object "
|
|
<< (item ? item->getName() : "UNKNOWN") << " id " << item_id << LL_ENDL;
|
|
}
|
|
if (!attachObject(cur_attachment))
|
|
{ // Drop it
|
|
LL_WARNS() << "attachObject() failed for "
|
|
<< cur_attachment->getID()
|
|
<< " item " << cur_attachment->getAttachmentItemID()
|
|
<< LL_ENDL;
|
|
// MAINT-3312 backout
|
|
//still_pending.push_back(cur_attachment);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
still_pending.push_back(cur_attachment);
|
|
}
|
|
}
|
|
|
|
mPendingAttachment = still_pending;
|
|
}
|
|
|
|
void LLVOAvatar::resetHUDAttachments()
|
|
{
|
|
#if SLOW_ATTACHMENT_LIST
|
|
for (attachment_map_t::iterator iter = mAttachmentPoints.begin();
|
|
iter != mAttachmentPoints.end();
|
|
++iter)
|
|
{
|
|
LLViewerJointAttachment* attachment = iter->second;
|
|
if (attachment->getIsHUDAttachment())
|
|
{
|
|
for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin();
|
|
attachment_iter != attachment->mAttachedObjects.end();
|
|
++attachment_iter)
|
|
{
|
|
const LLViewerObject* attached_object = (*attachment_iter);
|
|
#else
|
|
for(auto& iter : mAttachedObjectsVector)
|
|
{{{
|
|
const LLViewerJointAttachment* attachment = iter.second;
|
|
if (!attachment->getIsHUDAttachment())
|
|
continue;
|
|
const LLViewerObject* attached_object = iter.first;
|
|
#endif
|
|
if (attached_object && attached_object->mDrawable.notNull())
|
|
{
|
|
gPipeline.markMoved(attached_object->mDrawable);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void LLVOAvatar::rebuildRiggedAttachments( void )
|
|
{
|
|
#if SLOW_ATTACHMENT_LIST
|
|
for ( attachment_map_t::iterator iter = mAttachmentPoints.begin(); iter != mAttachmentPoints.end(); ++iter )
|
|
{
|
|
LLViewerJointAttachment* pAttachment = iter->second;
|
|
LLViewerJointAttachment::attachedobjs_vec_t::iterator attachmentIterEnd = pAttachment->mAttachedObjects.end();
|
|
|
|
for ( LLViewerJointAttachment::attachedobjs_vec_t::iterator attachmentIter = pAttachment->mAttachedObjects.begin();
|
|
attachmentIter != attachmentIterEnd; ++attachmentIter)
|
|
{
|
|
const LLViewerObject* pAttachedObject = *attachmentIter;
|
|
#else
|
|
for(auto& iter : mAttachedObjectsVector)
|
|
{{
|
|
const LLViewerObject* pAttachedObject = iter.first;
|
|
const LLViewerJointAttachment* pAttachment = iter.second;
|
|
#endif
|
|
if ( pAttachment && pAttachedObject->mDrawable.notNull() )
|
|
{
|
|
gPipeline.markRebuild(pAttachedObject->mDrawable);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
// cleanupAttachedMesh()
|
|
//-----------------------------------------------------------------------------
|
|
void LLVOAvatar::cleanupAttachedMesh( LLViewerObject* pVO )
|
|
{
|
|
LLUUID mesh_id;
|
|
if (getRiggedMeshID(pVO, mesh_id))
|
|
{
|
|
//resetJointsOnDetach(mesh_id);
|
|
if ( gAgentCamera.cameraCustomizeAvatar() )
|
|
{
|
|
gAgent.unpauseAnimation();
|
|
//Still want to refocus on head bone
|
|
gAgentCamera.changeCameraToCustomizeAvatar();
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// detachObject()
|
|
//-----------------------------------------------------------------------------
|
|
BOOL LLVOAvatar::detachObject(LLViewerObject *viewer_object)
|
|
{
|
|
|
|
for (attachment_map_t::iterator iter = mAttachmentPoints.begin();
|
|
iter != mAttachmentPoints.end();
|
|
++iter)
|
|
{
|
|
LLViewerJointAttachment* attachment = iter->second;
|
|
|
|
if (attachment->isObjectAttached(viewer_object))
|
|
{
|
|
updateVisualComplexity();
|
|
vector_replace_with_last(mAttachedObjectsVector,std::make_pair(viewer_object,attachment));
|
|
bool is_animated_object = viewer_object->isAnimatedObject();
|
|
cleanupAttachedMesh( viewer_object );
|
|
|
|
attachment->removeObject(viewer_object);
|
|
if (!is_animated_object)
|
|
{
|
|
updateAttachmentOverrides();
|
|
}
|
|
viewer_object->refreshBakeTexture();
|
|
|
|
LLViewerObject::const_child_list_t& child_list = viewer_object->getChildren();
|
|
for (const auto& iter1 : child_list)
|
|
{
|
|
LLViewerObject* objectp = iter1;
|
|
if (objectp)
|
|
{
|
|
objectp->refreshBakeTexture();
|
|
}
|
|
}
|
|
|
|
updateMeshVisibility();
|
|
|
|
LL_DEBUGS() << "Detaching object " << viewer_object->mID << " from " << attachment->getName() << LL_ENDL;
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
std::vector<LLPointer<LLViewerObject> >::iterator iter = std::find(mPendingAttachment.begin(), mPendingAttachment.end(), viewer_object);
|
|
if (iter != mPendingAttachment.end())
|
|
{
|
|
mPendingAttachment.erase(iter);
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// sitDown()
|
|
//-----------------------------------------------------------------------------
|
|
void LLVOAvatar::sitDown(BOOL bSitting)
|
|
{
|
|
if (isSitting() != bSitting) mIdleTimer.reset(); // Sitting changed, not idle
|
|
mIsSitting = bSitting;
|
|
if (isSelf())
|
|
{
|
|
LLFloaterAO::ChangeStand();
|
|
|
|
// [RLVa:KB] - Checked: 2010-08-29 (RLVa-1.2.1c) | Modified: RLVa-1.2.1c
|
|
if (rlv_handler_t::isEnabled())
|
|
{
|
|
gRlvHandler.onSitOrStand(bSitting);
|
|
}
|
|
// [/RLVa:KB]
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// sitOnObject()
|
|
//-----------------------------------------------------------------------------
|
|
void LLVOAvatar::sitOnObject(LLViewerObject *sit_object)
|
|
{
|
|
if (isSelf())
|
|
{
|
|
// Might be first sit
|
|
LLFirstUse::useSit();
|
|
|
|
gAgent.setFlying(FALSE);
|
|
gAgentCamera.setThirdPersonHeadOffset(LLVector3::zero);
|
|
//interpolate to new camera position
|
|
gAgentCamera.startCameraAnimation();
|
|
// make sure we are not trying to autopilot
|
|
gAgent.stopAutoPilot();
|
|
gAgentCamera.setupSitCamera();
|
|
if (gAgentCamera.getForceMouselook())
|
|
{
|
|
gAgentCamera.changeCameraToMouselook();
|
|
}
|
|
}
|
|
|
|
if (mDrawable.isNull() || sit_object->mDrawable.isNull())
|
|
{
|
|
return;
|
|
}
|
|
LLQuaternion inv_obj_rot = ~sit_object->getRenderRotation();
|
|
LLVector3 obj_pos = sit_object->getRenderPosition();
|
|
|
|
LLVector3 rel_pos = getRenderPosition() - obj_pos;
|
|
rel_pos.rotVec(inv_obj_rot);
|
|
|
|
mDrawable->mXform.setPosition(rel_pos);
|
|
mDrawable->mXform.setRotation(mDrawable->getWorldRotation() * inv_obj_rot);
|
|
|
|
gPipeline.markMoved(mDrawable, TRUE);
|
|
// Notice that removing sitDown() from here causes avatars sitting on
|
|
// objects to be not rendered for new arrivals. See EXT-6835 and EXT-1655.
|
|
sitDown(TRUE);
|
|
mRoot->getXform()->setParent(&sit_object->mDrawable->mXform); // LLVOAvatar::sitOnObject
|
|
mRoot->setPosition(getPosition());
|
|
mRoot->updateWorldMatrixChildren();
|
|
|
|
stopMotion(ANIM_AGENT_BODY_NOISE);
|
|
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// getOffObject()
|
|
//-----------------------------------------------------------------------------
|
|
void LLVOAvatar::getOffObject()
|
|
{
|
|
if (mDrawable.isNull())
|
|
{
|
|
return;
|
|
}
|
|
|
|
LLViewerObject* sit_object = (LLViewerObject*)getParent();
|
|
|
|
if (sit_object)
|
|
{
|
|
stopMotionFromSource(sit_object->getID());
|
|
LLFollowCamMgr::setCameraActive(sit_object->getID(), FALSE);
|
|
|
|
LLViewerObject::const_child_list_t& child_list = sit_object->getChildren();
|
|
for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin();
|
|
iter != child_list.end(); ++iter)
|
|
{
|
|
LLViewerObject* child_objectp = *iter;
|
|
|
|
stopMotionFromSource(child_objectp->getID());
|
|
LLFollowCamMgr::setCameraActive(child_objectp->getID(), FALSE);
|
|
}
|
|
}
|
|
|
|
// assumes that transform will not be updated with drawable still having a parent
|
|
LLVector3 cur_position_world = mDrawable->getWorldPosition();
|
|
LLQuaternion cur_rotation_world = mDrawable->getWorldRotation();
|
|
|
|
if (mLastRootPos.length() >= MAX_STANDOFF_FROM_ORIGIN
|
|
&& (cur_position_world.length() < MAX_STANDOFF_FROM_ORIGIN
|
|
|| dist_vec(cur_position_world, mLastRootPos) > MAX_STANDOFF_DISTANCE_CHANGE))
|
|
{
|
|
// Most likely drawable got updated too early or some updates were missed - we got relative position to non-existing parent
|
|
// restore coordinates from cache
|
|
cur_position_world = mLastRootPos;
|
|
}
|
|
|
|
// set *local* position based on last *world* position, since we're unparenting the avatar
|
|
mDrawable->mXform.setPosition(cur_position_world);
|
|
mDrawable->mXform.setRotation(cur_rotation_world);
|
|
|
|
gPipeline.markMoved(mDrawable, TRUE);
|
|
|
|
sitDown(FALSE);
|
|
|
|
mRoot->getXform()->setParent(NULL); // LLVOAvatar::getOffObject
|
|
mRoot->setPosition(cur_position_world);
|
|
mRoot->setRotation(cur_rotation_world);
|
|
mRoot->getXform()->update();
|
|
|
|
if (mEnableDefaultMotions)
|
|
{
|
|
startMotion(ANIM_AGENT_BODY_NOISE);
|
|
}
|
|
|
|
if (isSelf())
|
|
{
|
|
LLQuaternion av_rot = gAgent.getFrameAgent().getQuaternion();
|
|
LLQuaternion obj_rot = sit_object ? sit_object->getRenderRotation() : LLQuaternion::DEFAULT;
|
|
av_rot = av_rot * obj_rot;
|
|
LLVector3 at_axis = LLVector3::x_axis;
|
|
at_axis = at_axis * av_rot;
|
|
at_axis.mV[VZ] = 0.f;
|
|
at_axis.normalize();
|
|
gAgent.resetAxes(at_axis);
|
|
gAgentCamera.setThirdPersonHeadOffset(LLVector3(0.f, 0.f, 1.f));
|
|
gAgentCamera.setSitCamera(LLUUID::null);
|
|
|
|
if (sit_object && !sit_object->permYouOwner() && gSavedSettings.getBOOL("RevokePermsOnStandUp"))
|
|
{
|
|
U32 permissions = LSCRIPTRunTimePermissionBits[SCRIPT_PERMISSION_TRIGGER_ANIMATION] | LSCRIPTRunTimePermissionBits[SCRIPT_PERMISSION_OVERRIDE_ANIMATIONS];
|
|
gAgent.sendRevokePermissions(sit_object->getID(), permissions);
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// findAvatarFromAttachment()
|
|
//-----------------------------------------------------------------------------
|
|
// static
|
|
LLVOAvatar* LLVOAvatar::findAvatarFromAttachment( LLViewerObject* obj )
|
|
{
|
|
if( obj->isAttachment() )
|
|
{
|
|
do
|
|
{
|
|
obj = (LLViewerObject*) obj->getParent();
|
|
}
|
|
while( obj && !obj->isAvatar() );
|
|
|
|
if( obj && !obj->isDead() )
|
|
{
|
|
return (LLVOAvatar*)obj;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
// warning: order(N) not order(1)
|
|
S32 LLVOAvatar::getAttachmentCount()
|
|
{
|
|
S32 count = mAttachmentPoints.size();
|
|
return count;
|
|
}
|
|
|
|
BOOL LLVOAvatar::isWearingWearableType(LLWearableType::EType type) const
|
|
{
|
|
if (mIsDummy) return TRUE;
|
|
|
|
if (isSelf())
|
|
{
|
|
return LLAvatarAppearance::isWearingWearableType(type);
|
|
}
|
|
|
|
switch(type)
|
|
{
|
|
case LLWearableType::WT_SHAPE:
|
|
case LLWearableType::WT_SKIN:
|
|
case LLWearableType::WT_HAIR:
|
|
case LLWearableType::WT_EYES:
|
|
return TRUE; // everyone has all bodyparts
|
|
default:
|
|
break; // Do nothing
|
|
}
|
|
|
|
for (LLAvatarAppearanceDictionary::Textures::const_iterator tex_iter = LLAvatarAppearanceDictionary::getInstance()->getTextures().begin();
|
|
tex_iter != LLAvatarAppearanceDictionary::getInstance()->getTextures().end();
|
|
++tex_iter)
|
|
{
|
|
const LLAvatarAppearanceDictionary::TextureEntry *texture_dict = tex_iter->second;
|
|
if (texture_dict->mWearableType == type)
|
|
{
|
|
// If you're checking another avatar's clothing, you don't have component textures.
|
|
// Thus, you must check to see if the corresponding baked texture is defined.
|
|
// NOTE: this is a poor substitute if you actually want to know about individual pieces of clothing
|
|
// this works for detecting a skirt (most important), but is ineffective at any piece of clothing that
|
|
// gets baked into a texture that always exists (upper or lower).
|
|
if (texture_dict->mIsUsedByBakedTexture)
|
|
{
|
|
const EBakedTextureIndex baked_index = texture_dict->mBakedTextureIndex;
|
|
return isTextureDefined(LLAvatarAppearanceDictionary::getInstance()->getBakedTexture(baked_index)->mTextureIndex);
|
|
}
|
|
return FALSE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// isWearingAttachment()
|
|
//-----------------------------------------------------------------------------
|
|
BOOL LLVOAvatar::isWearingAttachment( const LLUUID& inv_item_id )
|
|
{
|
|
const LLUUID& base_inv_item_id = gInventory.getLinkedItemID(inv_item_id);
|
|
for (attachment_map_t::iterator iter = mAttachmentPoints.begin();
|
|
iter != mAttachmentPoints.end(); )
|
|
{
|
|
attachment_map_t::iterator curiter = iter++;
|
|
LLViewerJointAttachment* attachment = curiter->second;
|
|
if(attachment->getAttachedObject(base_inv_item_id))
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// getWornAttachment()
|
|
//-----------------------------------------------------------------------------
|
|
LLViewerObject* LLVOAvatar::getWornAttachment( const LLUUID& inv_item_id )
|
|
{
|
|
const LLUUID& base_inv_item_id = gInventory.getLinkedItemID(inv_item_id);
|
|
for (attachment_map_t::iterator iter = mAttachmentPoints.begin();
|
|
iter != mAttachmentPoints.end(); )
|
|
{
|
|
attachment_map_t::iterator curiter = iter++;
|
|
LLViewerJointAttachment* attachment = curiter->second;
|
|
if (LLViewerObject *attached_object = attachment->getAttachedObject(base_inv_item_id))
|
|
{
|
|
return attached_object;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
LLViewerObject * LLVOAvatar::findAttachmentByID( const LLUUID & target_id ) const
|
|
{
|
|
#if SLOW_ATTACHMENT_LIST
|
|
for(attachment_map_t::const_iterator attachment_points_iter = mAttachmentPoints.begin();
|
|
attachment_points_iter != mAttachmentPoints.end();
|
|
++attachment_points_iter)
|
|
{
|
|
LLViewerJointAttachment* attachment = attachment_points_iter->second;
|
|
for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin();
|
|
attachment_iter != attachment->mAttachedObjects.end();
|
|
++attachment_iter)
|
|
{
|
|
LLViewerObject *attached_object = (*attachment_iter);
|
|
#else
|
|
for(auto& iter : mAttachedObjectsVector)
|
|
{{
|
|
LLViewerObject* attached_object = iter.first;
|
|
#endif
|
|
if (attached_object &&
|
|
attached_object->getID() == target_id)
|
|
{
|
|
return attached_object;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
// virtual
|
|
void LLVOAvatar::invalidateComposite( LLTexLayerSet* layerset, BOOL upload_result )
|
|
{
|
|
}
|
|
|
|
void LLVOAvatar::invalidateAll()
|
|
{
|
|
}
|
|
|
|
// virtual
|
|
void LLVOAvatar::onGlobalColorChanged(const LLTexGlobalColor* global_color, bool upload_bake )
|
|
{
|
|
if (global_color == mTexSkinColor)
|
|
{
|
|
invalidateComposite( mBakedTextureDatas[BAKED_HEAD].mTexLayerSet, upload_bake );
|
|
invalidateComposite( mBakedTextureDatas[BAKED_UPPER].mTexLayerSet, upload_bake );
|
|
invalidateComposite( mBakedTextureDatas[BAKED_LOWER].mTexLayerSet, upload_bake );
|
|
}
|
|
else if (global_color == mTexHairColor)
|
|
{
|
|
invalidateComposite( mBakedTextureDatas[BAKED_HEAD].mTexLayerSet, upload_bake );
|
|
invalidateComposite( mBakedTextureDatas[BAKED_HAIR].mTexLayerSet, upload_bake );
|
|
|
|
// ! BACKWARDS COMPATIBILITY !
|
|
// Fix for dealing with avatars from viewers that don't bake hair.
|
|
if (!isTextureDefined(mBakedTextureDatas[BAKED_HAIR].mTextureIndex))
|
|
{
|
|
LLColor4 color = mTexHairColor->getColor();
|
|
avatar_joint_mesh_list_t::iterator iter = mBakedTextureDatas[BAKED_HAIR].mJointMeshes.begin();
|
|
avatar_joint_mesh_list_t::iterator end = mBakedTextureDatas[BAKED_HAIR].mJointMeshes.end();
|
|
for (; iter != end; ++iter)
|
|
{
|
|
LLAvatarJointMesh* mesh = (*iter);
|
|
if (mesh)
|
|
{
|
|
mesh->setColor( color );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (global_color == mTexEyeColor)
|
|
{
|
|
// LL_INFOS() << "invalidateComposite cause: onGlobalColorChanged( eyecolor )" << LL_ENDL;
|
|
invalidateComposite( mBakedTextureDatas[BAKED_EYES].mTexLayerSet, upload_bake );
|
|
}
|
|
updateMeshTextures();
|
|
}
|
|
|
|
// virtual
|
|
bool LLVOAvatar::shouldRenderRigged() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// FIXME: We have an mVisible member, set in updateVisibility(), but this
|
|
// function doesn't return it! isVisible() and mVisible are used
|
|
// different places for different purposes. mVisible seems to be more
|
|
// related to whether the actual avatar mesh is shown, and isVisible()
|
|
// to whether anything about the avatar is displayed in the scene.
|
|
// Maybe better naming could make this clearer?
|
|
BOOL LLVOAvatar::isVisible() const
|
|
{
|
|
return mDrawable.notNull()
|
|
&& (!mOrphaned || isSelf())
|
|
&& (mDrawable->isVisible() || mIsDummy);
|
|
}
|
|
|
|
// Determine if we have enough avatar data to render
|
|
BOOL LLVOAvatar::getIsCloud() const
|
|
{
|
|
if (mIsDummy)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return ( ((const_cast<LLVOAvatar*>(this))->visualParamWeightsAreDefault())// Do we have a shape?
|
|
|| ( !isTextureDefined(TEX_LOWER_BAKED)
|
|
|| !isTextureDefined(TEX_UPPER_BAKED)
|
|
|| !isTextureDefined(TEX_HEAD_BAKED)
|
|
)
|
|
);
|
|
}
|
|
|
|
void LLVOAvatar::updateRezzedStatusTimers()
|
|
{
|
|
// State machine for rezzed status. Statuses are -1 on startup, 0
|
|
// = cloud, 1 = gray, 2 = textured, 3 = textured_and_downloaded.
|
|
// Purpose is to collect time data for each it takes avatar to reach
|
|
// various loading landmarks: gray, textured (partial), textured fully.
|
|
|
|
S32 rez_status = getRezzedStatus();
|
|
if (rez_status != mLastRezzedStatus)
|
|
{
|
|
LL_DEBUGS("Avatar") << avString() << "rez state change: " << mLastRezzedStatus << " -> " << rez_status << LL_ENDL;
|
|
|
|
if (mLastRezzedStatus == -1 && rez_status != -1)
|
|
{
|
|
// First time initialization, start all timers.
|
|
for (S32 i = 1; i < 4; i++)
|
|
{
|
|
startPhase("load_" + LLVOAvatar::rezStatusToString(i));
|
|
startPhase("first_load_" + LLVOAvatar::rezStatusToString(i));
|
|
}
|
|
}
|
|
if (rez_status < mLastRezzedStatus)
|
|
{
|
|
// load level has decreased. start phase timers for higher load levels.
|
|
for (S32 i = rez_status+1; i <= mLastRezzedStatus; i++)
|
|
{
|
|
startPhase("load_" + LLVOAvatar::rezStatusToString(i));
|
|
}
|
|
}
|
|
else if (rez_status > mLastRezzedStatus)
|
|
{
|
|
// load level has increased. stop phase timers for lower and equal load levels.
|
|
for (S32 i = llmax(mLastRezzedStatus+1,1); i <= rez_status; i++)
|
|
{
|
|
stopPhase("load_" + LLVOAvatar::rezStatusToString(i));
|
|
stopPhase("first_load_" + LLVOAvatar::rezStatusToString(i), false);
|
|
}
|
|
if (rez_status == 3)
|
|
{
|
|
// "fully loaded", mark any pending appearance change complete.
|
|
selfStopPhase("update_appearance_from_cof");
|
|
selfStopPhase("wear_inventory_category", false);
|
|
selfStopPhase("process_initial_wearables_update", false);
|
|
|
|
updateVisualComplexity();
|
|
}
|
|
}
|
|
mLastRezzedStatus = rez_status;
|
|
}
|
|
}
|
|
|
|
void LLVOAvatar::clearPhases()
|
|
{
|
|
getPhases().clearPhases();
|
|
}
|
|
|
|
void LLVOAvatar::startPhase(const std::string& phase_name)
|
|
{
|
|
F32 elapsed = 0.0;
|
|
bool completed = false;
|
|
bool found = getPhases().getPhaseValues(phase_name, elapsed, completed);
|
|
//LL_DEBUGS("Avatar") << avString() << " phase state " << phase_name
|
|
// << " found " << found << " elapsed " << elapsed << " completed " << completed << LL_ENDL;
|
|
if (found)
|
|
{
|
|
if (!completed)
|
|
{
|
|
LL_DEBUGS("Avatar") << avString() << "no-op, start when started already for " << phase_name << LL_ENDL;
|
|
return;
|
|
}
|
|
}
|
|
LL_DEBUGS("Avatar") << "started phase " << phase_name << LL_ENDL;
|
|
getPhases().startPhase(phase_name);
|
|
}
|
|
|
|
void LLVOAvatar::stopPhase(const std::string& phase_name, bool err_check)
|
|
{
|
|
F32 elapsed = 0.0;
|
|
bool completed = false;
|
|
if (getPhases().getPhaseValues(phase_name, elapsed, completed))
|
|
{
|
|
if (!completed)
|
|
{
|
|
getPhases().stopPhase(phase_name);
|
|
completed = true;
|
|
logMetricsTimerRecord(phase_name, elapsed, completed);
|
|
LL_DEBUGS("Avatar") << avString() << "stopped phase " << phase_name << " elapsed " << elapsed << LL_ENDL;
|
|
}
|
|
else
|
|
{
|
|
if (err_check)
|
|
{
|
|
LL_DEBUGS("Avatar") << "no-op, stop when stopped already for " << phase_name << LL_ENDL;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (err_check)
|
|
{
|
|
LL_DEBUGS("Avatar") << "no-op, stop when not started for " << phase_name << LL_ENDL;
|
|
}
|
|
}
|
|
}
|
|
|
|
void LLVOAvatar::logPendingPhases()
|
|
{
|
|
if (!isAgentAvatarValid())
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (LLViewerStats::phase_map_t::iterator it = getPhases().begin();
|
|
it != getPhases().end();
|
|
++it)
|
|
{
|
|
const std::string& phase_name = it->first;
|
|
F32 elapsed;
|
|
bool completed;
|
|
if (getPhases().getPhaseValues(phase_name, elapsed, completed))
|
|
{
|
|
if (!completed)
|
|
{
|
|
logMetricsTimerRecord(phase_name, elapsed, completed);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//static
|
|
void LLVOAvatar::logPendingPhasesAllAvatars()
|
|
{
|
|
for (std::vector<LLCharacter*>::iterator iter = LLCharacter::sInstances.begin();
|
|
iter != LLCharacter::sInstances.end(); ++iter)
|
|
{
|
|
LLVOAvatar* inst = (LLVOAvatar*) *iter;
|
|
if( inst->isDead() )
|
|
{
|
|
continue;
|
|
}
|
|
inst->logPendingPhases();
|
|
}
|
|
}
|
|
|
|
void LLVOAvatar::logMetricsTimerRecord(const std::string& phase_name, F32 elapsed, bool completed)
|
|
{
|
|
if (!isAgentAvatarValid())
|
|
{
|
|
return;
|
|
}
|
|
|
|
LLSD record;
|
|
record["timer_name"] = phase_name;
|
|
record["avatar_id"] = getID();
|
|
record["elapsed"] = elapsed;
|
|
record["completed"] = completed;
|
|
U32 grid_x(0), grid_y(0);
|
|
if (getRegion())
|
|
{
|
|
record["central_bake_version"] = LLSD::Integer(getRegion()->getCentralBakeVersion());
|
|
grid_from_region_handle(getRegion()->getHandle(), &grid_x, &grid_y);
|
|
}
|
|
record["grid_x"] = LLSD::Integer(grid_x);
|
|
record["grid_y"] = LLSD::Integer(grid_y);
|
|
record["is_using_server_bakes"] = ((bool) isUsingServerBakes());
|
|
record["is_self"] = isSelf();
|
|
|
|
if (isAgentAvatarValid())
|
|
{
|
|
gAgentAvatarp->addMetricsTimerRecord(record);
|
|
}
|
|
}
|
|
|
|
// call periodically to keep isFullyLoaded up to date.
|
|
// returns true if the value has changed.
|
|
BOOL LLVOAvatar::updateIsFullyLoaded()
|
|
{
|
|
const BOOL loading = getIsCloud();
|
|
updateRezzedStatusTimers();
|
|
updateRuthTimer(loading);
|
|
return processFullyLoadedChange(loading);
|
|
}
|
|
|
|
void LLVOAvatar::updateRuthTimer(bool loading)
|
|
{
|
|
if (isSelf() || !loading)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (mPreviousFullyLoaded)
|
|
{
|
|
mRuthTimer.reset();
|
|
debugAvatarRezTime("AvatarRezCloudNotification","became cloud");
|
|
}
|
|
|
|
const F32 LOADING_TIMEOUT__SECONDS = 120.f;
|
|
if (mRuthTimer.getElapsedTimeF32() > LOADING_TIMEOUT__SECONDS)
|
|
{
|
|
LL_DEBUGS("Avatar") << avString()
|
|
<< "Ruth Timer timeout: Missing texture data for '" << getFullname() << "' "
|
|
<< "( Params loaded : " << !visualParamWeightsAreDefault() << " ) "
|
|
<< "( Lower : " << isTextureDefined(TEX_LOWER_BAKED) << " ) "
|
|
<< "( Upper : " << isTextureDefined(TEX_UPPER_BAKED) << " ) "
|
|
<< "( Head : " << isTextureDefined(TEX_HEAD_BAKED) << " )."
|
|
<< LL_ENDL;
|
|
|
|
LLAvatarPropertiesProcessor::getInstance()->sendAvatarTexturesRequest(getID());
|
|
mRuthTimer.reset();
|
|
}
|
|
}
|
|
|
|
BOOL LLVOAvatar::processFullyLoadedChange(bool loading)
|
|
{
|
|
// we wait a little bit before giving the all clear,
|
|
// to let textures settle down
|
|
const F32 PAUSE = 1.f;
|
|
if (loading)
|
|
mFullyLoadedTimer.reset();
|
|
|
|
mFullyLoaded = (mFullyLoadedTimer.getElapsedTimeF32() > PAUSE);
|
|
|
|
if (!mPreviousFullyLoaded && !loading && mFullyLoaded)
|
|
{
|
|
debugAvatarRezTime("AvatarRezNotification","fully loaded");
|
|
}
|
|
|
|
// did our loading state "change" from last call?
|
|
// runway - why are we updating every 30 calls even if nothing has changed?
|
|
const S32 UPDATE_RATE = 30;
|
|
BOOL changed =
|
|
((mFullyLoaded != mPreviousFullyLoaded) || // if the value is different from the previous call
|
|
(!mFullyLoadedInitialized) || // if we've never been called before
|
|
(mFullyLoadedFrameCounter % UPDATE_RATE == 0)); // every now and then issue a change
|
|
|
|
mPreviousFullyLoaded = mFullyLoaded;
|
|
mFullyLoadedInitialized = TRUE;
|
|
mFullyLoadedFrameCounter++;
|
|
|
|
return changed;
|
|
}
|
|
|
|
BOOL LLVOAvatar::isFullyLoaded() const
|
|
{
|
|
static const LLCachedControl<bool> render_unloaded_avatar("RenderUnloadedAvatar", false);
|
|
|
|
// [SL:KB] - Patch: Appearance-SyncAttach | Checked: 2010-09-22 (Catznip-2.2)
|
|
// Changes to LLAppearanceMgr::updateAppearanceFromCOF() expect this function to actually return mFullyLoaded for gAgentAvatarp
|
|
return (render_unloaded_avatar && !isSelf()) ||(mFullyLoaded);
|
|
// [/SL:KB]
|
|
// return (render_unloaded_avatar || mFullyLoaded);
|
|
}
|
|
|
|
bool LLVOAvatar::isTooComplex() const
|
|
{
|
|
static const LLCachedControl<S32> always_render_friends("AlwaysRenderFriends", 0);
|
|
bool too_complex;
|
|
if (isSelf() || (always_render_friends && always_render_friends != 3 && LLAvatarTracker::instance().isBuddy(getID())))
|
|
{
|
|
too_complex = false;
|
|
}
|
|
else if (always_render_friends >= 2)
|
|
{
|
|
too_complex = true;
|
|
}
|
|
else
|
|
{
|
|
// Determine if visually muted or not
|
|
static LLCachedControl<U32> max_render_cost(gSavedSettings, "RenderAvatarMaxComplexity", 0U);
|
|
static LLCachedControl<F32> max_attachment_area(gSavedSettings, "RenderAutoMuteSurfaceAreaLimit", 1000.0f);
|
|
// If the user has chosen unlimited max complexity, we also disregard max attachment area
|
|
// so that unlimited will completely disable the overly complex impostor rendering
|
|
// yes, this leaves them vulnerable to griefing objects... their choice
|
|
too_complex = ( max_render_cost > 0
|
|
&& (mVisualComplexity > max_render_cost
|
|
|| (max_attachment_area > 0.0f && mAttachmentSurfaceArea > max_attachment_area)
|
|
));
|
|
}
|
|
|
|
return too_complex;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// findMotion()
|
|
//-----------------------------------------------------------------------------
|
|
LLMotion* LLVOAvatar::findMotion(const LLUUID& id) const
|
|
{
|
|
return mMotionController.findMotion(id);
|
|
}
|
|
|
|
// This is a semi-deprecated debugging tool - meshes will not show as
|
|
// colorized if using deferred rendering.
|
|
void LLVOAvatar::debugColorizeSubMeshes(U32 i, const LLColor4& color)
|
|
{
|
|
static LLCachedControl<bool> debug_avatar_comp_baked(gSavedSettings, "DebugAvatarCompositeBaked");
|
|
if (debug_avatar_comp_baked)
|
|
{
|
|
avatar_joint_mesh_list_t::iterator iter = mBakedTextureDatas[i].mJointMeshes.begin();
|
|
avatar_joint_mesh_list_t::iterator end = mBakedTextureDatas[i].mJointMeshes.end();
|
|
for (; iter != end; ++iter)
|
|
{
|
|
LLAvatarJointMesh* mesh = (*iter);
|
|
if (mesh)
|
|
{
|
|
mesh->setColor(color);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// updateMeshVisibility()
|
|
// Hide the mesh joints if attachments are using baked textures
|
|
//-----------------------------------------------------------------------------
|
|
void LLVOAvatar::updateMeshVisibility()
|
|
{
|
|
bool bake_flag[BAKED_NUM_INDICES];
|
|
memset(bake_flag, 0, BAKED_NUM_INDICES*sizeof(bool));
|
|
|
|
for (auto& attachment_point : mAttachmentPoints)
|
|
{
|
|
LLViewerJointAttachment* attachment = attachment_point.second;
|
|
if (attachment)
|
|
{
|
|
for (auto objectp : attachment->mAttachedObjects)
|
|
{
|
|
if (objectp)
|
|
{
|
|
for (int face_index = 0; face_index < objectp->getNumTEs(); face_index++)
|
|
{
|
|
LLTextureEntry* tex_entry = objectp->getTE(face_index);
|
|
bake_flag[BAKED_HEAD] |= (tex_entry->getID() == IMG_USE_BAKED_HEAD);
|
|
bake_flag[BAKED_EYES] |= (tex_entry->getID() == IMG_USE_BAKED_EYES);
|
|
bake_flag[BAKED_HAIR] |= (tex_entry->getID() == IMG_USE_BAKED_HAIR);
|
|
bake_flag[BAKED_LOWER] |= (tex_entry->getID() == IMG_USE_BAKED_LOWER);
|
|
bake_flag[BAKED_UPPER] |= (tex_entry->getID() == IMG_USE_BAKED_UPPER);
|
|
bake_flag[BAKED_SKIRT] |= (tex_entry->getID() == IMG_USE_BAKED_SKIRT);
|
|
bake_flag[BAKED_LEFT_ARM] |= (tex_entry->getID() == IMG_USE_BAKED_LEFTARM);
|
|
bake_flag[BAKED_LEFT_LEG] |= (tex_entry->getID() == IMG_USE_BAKED_LEFTLEG);
|
|
bake_flag[BAKED_AUX1] |= (tex_entry->getID() == IMG_USE_BAKED_AUX1);
|
|
bake_flag[BAKED_AUX2] |= (tex_entry->getID() == IMG_USE_BAKED_AUX2);
|
|
bake_flag[BAKED_AUX3] |= (tex_entry->getID() == IMG_USE_BAKED_AUX3);
|
|
}
|
|
}
|
|
|
|
LLViewerObject::const_child_list_t& child_list = objectp->getChildren();
|
|
for (const auto& iter1 : child_list)
|
|
{
|
|
LLViewerObject* objectchild = iter1;
|
|
if (objectchild)
|
|
{
|
|
for (int face_index = 0; face_index < objectchild->getNumTEs(); face_index++)
|
|
{
|
|
LLTextureEntry* tex_entry = objectchild->getTE(face_index);
|
|
bake_flag[BAKED_HEAD] |= (tex_entry->getID() == IMG_USE_BAKED_HEAD);
|
|
bake_flag[BAKED_EYES] |= (tex_entry->getID() == IMG_USE_BAKED_EYES);
|
|
bake_flag[BAKED_HAIR] |= (tex_entry->getID() == IMG_USE_BAKED_HAIR);
|
|
bake_flag[BAKED_LOWER] |= (tex_entry->getID() == IMG_USE_BAKED_LOWER);
|
|
bake_flag[BAKED_UPPER] |= (tex_entry->getID() == IMG_USE_BAKED_UPPER);
|
|
bake_flag[BAKED_SKIRT] |= (tex_entry->getID() == IMG_USE_BAKED_SKIRT);
|
|
bake_flag[BAKED_LEFT_ARM] |= (tex_entry->getID() == IMG_USE_BAKED_LEFTARM);
|
|
bake_flag[BAKED_LEFT_LEG] |= (tex_entry->getID() == IMG_USE_BAKED_LEFTLEG);
|
|
bake_flag[BAKED_AUX1] |= (tex_entry->getID() == IMG_USE_BAKED_AUX1);
|
|
bake_flag[BAKED_AUX2] |= (tex_entry->getID() == IMG_USE_BAKED_AUX2);
|
|
bake_flag[BAKED_AUX3] |= (tex_entry->getID() == IMG_USE_BAKED_AUX3);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//LL_INFOS() << "head " << bake_flag[BAKED_HEAD] << "eyes " << bake_flag[BAKED_EYES] << "hair " << bake_flag[BAKED_HAIR] << "lower " << bake_flag[BAKED_LOWER] << "upper " << bake_flag[BAKED_UPPER] << "skirt " << bake_flag[BAKED_SKIRT] << LL_ENDL;
|
|
|
|
for (size_t i = 0; i < mMeshLOD.size(); i++)
|
|
{
|
|
LLAvatarJoint* joint = mMeshLOD[i];
|
|
if (i == MESH_ID_HAIR)
|
|
{
|
|
joint->setVisible(!bake_flag[BAKED_HAIR], TRUE);
|
|
}
|
|
else if (i == MESH_ID_HEAD)
|
|
{
|
|
joint->setVisible(!bake_flag[BAKED_HEAD], TRUE);
|
|
}
|
|
else if (i == MESH_ID_SKIRT)
|
|
{
|
|
joint->setVisible(!bake_flag[BAKED_SKIRT], TRUE);
|
|
}
|
|
else if (i == MESH_ID_UPPER_BODY)
|
|
{
|
|
joint->setVisible(!bake_flag[BAKED_UPPER], TRUE);
|
|
}
|
|
else if (i == MESH_ID_LOWER_BODY)
|
|
{
|
|
joint->setVisible(!bake_flag[BAKED_LOWER], TRUE);
|
|
}
|
|
else if (i == MESH_ID_EYEBALL_LEFT)
|
|
{
|
|
joint->setVisible(!bake_flag[BAKED_EYES], TRUE);
|
|
}
|
|
else if (i == MESH_ID_EYEBALL_RIGHT)
|
|
{
|
|
joint->setVisible(!bake_flag[BAKED_EYES], TRUE);
|
|
}
|
|
else if (i == MESH_ID_EYELASH)
|
|
{
|
|
joint->setVisible(!bake_flag[BAKED_HEAD], TRUE);
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// updateMeshTextures()
|
|
// Uses the current TE values to set the meshes' and layersets' textures.
|
|
//-----------------------------------------------------------------------------
|
|
// virtual
|
|
void LLVOAvatar::updateMeshTextures()
|
|
{
|
|
static S32 update_counter = 0;
|
|
mBakedTextureDebugText.clear();
|
|
|
|
// if user has never specified a texture, assign the default
|
|
for (U32 i=0; i < getNumTEs(); i++)
|
|
{
|
|
const LLViewerTexture* te_image = getImage(i, 0);
|
|
if(!te_image || te_image->getID().isNull() || (te_image->getID() == IMG_DEFAULT))
|
|
{
|
|
// IMG_DEFAULT_AVATAR = a special texture that's never rendered.
|
|
const LLUUID& image_id = (i == TEX_HAIR ? IMG_DEFAULT : IMG_DEFAULT_AVATAR);
|
|
setImage(i, LLViewerTextureManager::getFetchedTexture(image_id), 0);
|
|
}
|
|
}
|
|
|
|
const BOOL other_culled = !isSelf() && mCulled;
|
|
LLLoadedCallbackEntry::source_callback_list_t* src_callback_list = NULL ;
|
|
BOOL paused = FALSE;
|
|
if(!isSelf())
|
|
{
|
|
src_callback_list = &mCallbackTextureList ;
|
|
paused = !isVisible();
|
|
}
|
|
|
|
std::vector<BOOL> is_layer_baked;
|
|
is_layer_baked.resize(mBakedTextureDatas.size(), false);
|
|
|
|
std::vector<BOOL> use_lkg_baked_layer; // lkg = "last known good"
|
|
use_lkg_baked_layer.resize(mBakedTextureDatas.size(), false);
|
|
|
|
mBakedTextureDebugText += llformat("%06d\n",update_counter++);
|
|
mBakedTextureDebugText += "indx layerset linvld ltda ilb ulkg ltid\n";
|
|
for (U32 i=0; i < mBakedTextureDatas.size(); i++)
|
|
{
|
|
is_layer_baked[i] = isTextureDefined(mBakedTextureDatas[i].mTextureIndex);
|
|
LLViewerTexLayerSet* layerset = NULL;
|
|
bool layerset_invalid = false;
|
|
if (!other_culled)
|
|
{
|
|
// When an avatar is changing clothes and not in Appearance mode,
|
|
// use the last-known good baked texture until it finishes the first
|
|
// render of the new layerset.
|
|
layerset = getTexLayerSet(i);
|
|
layerset_invalid = layerset && ( !layerset->getViewerComposite()->isInitialized()
|
|
|| !layerset->isLocalTextureDataAvailable() );
|
|
use_lkg_baked_layer[i] = (!is_layer_baked[i]
|
|
&& (mBakedTextureDatas[i].mLastTextureID != IMG_DEFAULT_AVATAR)
|
|
&& layerset_invalid);
|
|
if (use_lkg_baked_layer[i])
|
|
{
|
|
layerset->setUpdatesEnabled(TRUE);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
use_lkg_baked_layer[i] = (!is_layer_baked[i]
|
|
&& mBakedTextureDatas[i].mLastTextureID != IMG_DEFAULT_AVATAR);
|
|
}
|
|
|
|
std::string last_id_string;
|
|
if (mBakedTextureDatas[i].mLastTextureID == IMG_DEFAULT_AVATAR)
|
|
last_id_string = "A";
|
|
else if (mBakedTextureDatas[i].mLastTextureID == IMG_DEFAULT)
|
|
last_id_string = "D";
|
|
else if (mBakedTextureDatas[i].mLastTextureID == IMG_INVISIBLE)
|
|
last_id_string = "I";
|
|
else
|
|
last_id_string = "*";
|
|
bool is_ltda = layerset
|
|
&& layerset->getViewerComposite()->isInitialized()
|
|
&& layerset->isLocalTextureDataAvailable();
|
|
mBakedTextureDebugText += llformat("%4d %4s %4d %4d %4d %4d %4s\n",
|
|
i,
|
|
(layerset?"*":"0"),
|
|
layerset_invalid,
|
|
is_ltda,
|
|
is_layer_baked[i],
|
|
use_lkg_baked_layer[i],
|
|
last_id_string.c_str());
|
|
}
|
|
// Turn on alpha masking correctly for yourself and other avatars on 1.23+
|
|
mSupportsAlphaLayers = isSelf() || is_layer_baked[BAKED_HAIR];
|
|
|
|
for (U32 i=0; i < mBakedTextureDatas.size(); i++)
|
|
{
|
|
debugColorizeSubMeshes(i, LLColor4::white);
|
|
|
|
LLViewerTexLayerSet* layerset = getTexLayerSet(i);
|
|
if (use_lkg_baked_layer[i] && !isUsingLocalAppearance() )
|
|
{
|
|
LLViewerFetchedTexture* baked_img;
|
|
#ifndef LL_RELEASE_FOR_DOWNLOAD
|
|
LLViewerFetchedTexture* existing_baked_img = LLViewerTextureManager::getFetchedTexture(mBakedTextureDatas[i].mLastTextureID);
|
|
#endif
|
|
ETextureIndex te = ETextureIndex(mBakedTextureDatas[i].mTextureIndex);
|
|
const std::string url = getImageURL(te, mBakedTextureDatas[i].mLastTextureID);
|
|
if (!url.empty())
|
|
{
|
|
baked_img = LLViewerTextureManager::getFetchedTextureFromUrl(url, FTT_HOST_BAKE, TRUE, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE, 0, 0, mBakedTextureDatas[i].mLastTextureID);
|
|
}
|
|
else
|
|
{
|
|
// Baked textures should be requested from the sim this avatar is on. JC
|
|
const LLHost target_host = getObjectHost();
|
|
if (!target_host.isOk())
|
|
{
|
|
LL_WARNS() << "updateMeshTextures: invalid host for object: " << getID() << LL_ENDL;
|
|
}
|
|
|
|
baked_img = LLViewerTextureManager::getFetchedTextureFromHost( mBakedTextureDatas[i].mLastTextureID, FTT_HOST_BAKE, target_host );
|
|
}
|
|
llassert(baked_img == existing_baked_img);
|
|
|
|
mBakedTextureDatas[i].mIsUsed = TRUE;
|
|
|
|
debugColorizeSubMeshes(i,LLColor4::red);
|
|
|
|
avatar_joint_mesh_list_t::iterator iter = mBakedTextureDatas[i].mJointMeshes.begin();
|
|
avatar_joint_mesh_list_t::iterator end = mBakedTextureDatas[i].mJointMeshes.end();
|
|
for (; iter != end; ++iter)
|
|
{
|
|
LLAvatarJointMesh* mesh = (*iter);
|
|
if (mesh)
|
|
{
|
|
mesh->setTexture( baked_img );
|
|
}
|
|
}
|
|
}
|
|
else if (!isUsingLocalAppearance() && is_layer_baked[i])
|
|
{
|
|
LLViewerFetchedTexture* baked_img =
|
|
LLViewerTextureManager::staticCastToFetchedTexture(
|
|
getImage( mBakedTextureDatas[i].mTextureIndex, 0 ), TRUE) ;
|
|
if( baked_img->getID() == mBakedTextureDatas[i].mLastTextureID )
|
|
{
|
|
// Even though the file may not be finished loading,
|
|
// we'll consider it loaded and use it (rather than
|
|
// doing compositing).
|
|
useBakedTexture( baked_img->getID() );
|
|
mLoadedCallbacksPaused |= !isVisible();
|
|
checkTextureLoading();
|
|
}
|
|
else
|
|
{
|
|
mBakedTextureDatas[i].mIsLoaded = FALSE;
|
|
if ( (baked_img->getID() != IMG_INVISIBLE) &&
|
|
((i == BAKED_HEAD) || (i == BAKED_UPPER) || (i == BAKED_LOWER)) )
|
|
{
|
|
baked_img->setLoadedCallback(onBakedTextureMasksLoaded, MORPH_MASK_REQUESTED_DISCARD, TRUE, TRUE, new LLTextureMaskData( mID ),
|
|
src_callback_list, paused);
|
|
}
|
|
baked_img->setLoadedCallback(onBakedTextureLoaded, SWITCH_TO_BAKED_DISCARD, FALSE, FALSE, new LLUUID( mID ),
|
|
src_callback_list, paused );
|
|
|
|
// this could add paused texture callbacks
|
|
mLoadedCallbacksPaused |= paused;
|
|
checkTextureLoading();
|
|
}
|
|
}
|
|
else if (layerset && isUsingLocalAppearance())
|
|
{
|
|
debugColorizeSubMeshes(i,LLColor4::yellow );
|
|
|
|
layerset->createComposite();
|
|
layerset->setUpdatesEnabled( TRUE );
|
|
mBakedTextureDatas[i].mIsUsed = FALSE;
|
|
|
|
avatar_joint_mesh_list_t::iterator iter = mBakedTextureDatas[i].mJointMeshes.begin();
|
|
avatar_joint_mesh_list_t::iterator end = mBakedTextureDatas[i].mJointMeshes.end();
|
|
for (; iter != end; ++iter)
|
|
{
|
|
LLAvatarJointMesh* mesh = (*iter);
|
|
if (mesh)
|
|
{
|
|
mesh->setLayerSet( layerset );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
debugColorizeSubMeshes(i,LLColor4::blue);
|
|
}
|
|
}
|
|
|
|
// set texture and color of hair manually if we are not using a baked image.
|
|
// This can happen while loading hair for yourself, or for clients that did not
|
|
// bake a hair texture. Still needed for yourself after 1.22 is depricated.
|
|
if (!is_layer_baked[BAKED_HAIR] || isEditingAppearance())
|
|
{
|
|
const LLColor4 color = mTexHairColor ? mTexHairColor->getColor() : LLColor4(1,1,1,1);
|
|
LLViewerTexture* hair_img = getImage( TEX_HAIR, 0 );
|
|
avatar_joint_mesh_list_t::iterator iter = mBakedTextureDatas[BAKED_HAIR].mJointMeshes.begin();
|
|
avatar_joint_mesh_list_t::iterator end = mBakedTextureDatas[BAKED_HAIR].mJointMeshes.end();
|
|
for (; iter != end; ++iter)
|
|
{
|
|
LLAvatarJointMesh* mesh = (*iter);
|
|
if (mesh)
|
|
{
|
|
mesh->setColor( color );
|
|
mesh->setTexture( hair_img );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
for (LLAvatarAppearanceDictionary::BakedTextures::const_iterator baked_iter =
|
|
LLAvatarAppearanceDictionary::getInstance()->getBakedTextures().begin();
|
|
baked_iter != LLAvatarAppearanceDictionary::getInstance()->getBakedTextures().end();
|
|
++baked_iter)
|
|
{
|
|
const EBakedTextureIndex baked_index = baked_iter->first;
|
|
const LLAvatarAppearanceDictionary::BakedEntry *baked_dict = baked_iter->second;
|
|
|
|
for (texture_vec_t::const_iterator local_tex_iter = baked_dict->mLocalTextures.begin();
|
|
local_tex_iter != baked_dict->mLocalTextures.end();
|
|
++local_tex_iter)
|
|
{
|
|
const ETextureIndex texture_index = *local_tex_iter;
|
|
const BOOL is_baked_ready = (is_layer_baked[baked_index] && mBakedTextureDatas[baked_index].mIsLoaded) || other_culled;
|
|
if (isSelf())
|
|
{
|
|
setBakedReady(texture_index, is_baked_ready);
|
|
}
|
|
}
|
|
}
|
|
|
|
// removeMissingBakedTextures() will call back into this rountine if something is removed, and can blow up the stack
|
|
static bool call_remove_missing = true;
|
|
if (call_remove_missing)
|
|
{
|
|
call_remove_missing = false;
|
|
removeMissingBakedTextures(); // May call back into this function if anything is removed
|
|
call_remove_missing = true;
|
|
}
|
|
|
|
//refresh bakes on any attached objects
|
|
for (auto& attachment_point : mAttachmentPoints)
|
|
{
|
|
LLViewerJointAttachment* attachment = attachment_point.second;
|
|
|
|
for (auto attached_object : attachment->mAttachedObjects)
|
|
{
|
|
if (attached_object && !attached_object->isDead())
|
|
{
|
|
attached_object->refreshBakeTexture();
|
|
|
|
LLViewerObject::const_child_list_t& child_list = attached_object->getChildren();
|
|
for (const auto& iter : child_list)
|
|
{
|
|
LLViewerObject* objectp = iter;
|
|
if (objectp && !objectp->isDead())
|
|
{
|
|
objectp->refreshBakeTexture();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// virtual
|
|
//-----------------------------------------------------------------------------
|
|
// setLocalTexture()
|
|
//-----------------------------------------------------------------------------
|
|
void LLVOAvatar::setLocalTexture( ETextureIndex type, LLViewerTexture* in_tex, BOOL baked_version_ready, U32 index )
|
|
{
|
|
// invalid for anyone but self
|
|
llassert(0);
|
|
}
|
|
|
|
//virtual
|
|
void LLVOAvatar::setBakedReady(LLAvatarAppearanceDefines::ETextureIndex type, BOOL baked_version_exists, U32 index)
|
|
{
|
|
// invalid for anyone but self
|
|
llassert(0);
|
|
}
|
|
|
|
void LLVOAvatar::setNameFromChat(const std::string &text)
|
|
{
|
|
if(!mCCSChatTextOverride || mCCSChatText != text)
|
|
clearNameTag();
|
|
mCCSChatTextOverride = true;
|
|
mCCSChatText = text;
|
|
}
|
|
|
|
void LLVOAvatar::clearNameFromChat()
|
|
{
|
|
if(mCCSChatTextOverride || mCCSChatText != "")
|
|
clearNameTag();
|
|
mCCSChatTextOverride = false;
|
|
mCCSChatText = "";
|
|
}
|
|
|
|
void LLVOAvatar::addChat(const LLChat& chat)
|
|
{
|
|
std::deque<LLChat>::iterator chat_iter;
|
|
|
|
mChats.push_back(chat);
|
|
|
|
S32 chat_length = 0;
|
|
for( chat_iter = mChats.begin(); chat_iter != mChats.end(); ++chat_iter)
|
|
{
|
|
chat_length += chat_iter->mText.size();
|
|
}
|
|
|
|
// remove any excess chat
|
|
chat_iter = mChats.begin();
|
|
while ((chat_length > MAX_BUBBLE_CHAT_LENGTH || mChats.size() > MAX_BUBBLE_CHAT_UTTERANCES) && chat_iter != mChats.end())
|
|
{
|
|
chat_length -= chat_iter->mText.size();
|
|
mChats.pop_front();
|
|
chat_iter = mChats.begin();
|
|
}
|
|
|
|
mChatTimer.reset();
|
|
mIdleTimer.reset(); // Also reset idle timer
|
|
}
|
|
|
|
void LLVOAvatar::clearChat()
|
|
{
|
|
mChats.clear();
|
|
}
|
|
|
|
|
|
void LLVOAvatar::applyMorphMask(U8* tex_data, S32 width, S32 height, S32 num_components, LLAvatarAppearanceDefines::EBakedTextureIndex index)
|
|
{
|
|
if (index >= BAKED_NUM_INDICES)
|
|
{
|
|
LL_WARNS() << "invalid baked texture index passed to applyMorphMask" << LL_ENDL;
|
|
return;
|
|
}
|
|
|
|
for (morph_list_t::const_iterator iter = mBakedTextureDatas[index].mMaskedMorphs.begin();
|
|
iter != mBakedTextureDatas[index].mMaskedMorphs.end(); ++iter)
|
|
{
|
|
const LLMaskedMorph* maskedMorph = (*iter);
|
|
LLPolyMorphTarget* morph_target = dynamic_cast<LLPolyMorphTarget*>(maskedMorph->mMorphTarget);
|
|
if (morph_target)
|
|
{
|
|
morph_target->applyMask(tex_data, width, height, num_components, maskedMorph->mInvert);
|
|
}
|
|
}
|
|
}
|
|
|
|
// returns TRUE if morph masks are present and not valid for a given baked texture, FALSE otherwise
|
|
BOOL LLVOAvatar::morphMaskNeedsUpdate(LLAvatarAppearanceDefines::EBakedTextureIndex index)
|
|
{
|
|
if (index >= BAKED_NUM_INDICES)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (!mBakedTextureDatas[index].mMaskedMorphs.empty())
|
|
{
|
|
if (isSelf())
|
|
{
|
|
LLViewerTexLayerSet *layer_set = getTexLayerSet(index);
|
|
if (layer_set)
|
|
{
|
|
return !layer_set->isMorphValid();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// releaseComponentTextures()
|
|
// release any component texture UUIDs for which we have a baked texture
|
|
// ! BACKWARDS COMPATIBILITY !
|
|
// This is only called for non-self avatars, it can be taken out once component
|
|
// textures aren't communicated by non-self avatars.
|
|
//-----------------------------------------------------------------------------
|
|
void LLVOAvatar::releaseComponentTextures()
|
|
{
|
|
// ! BACKWARDS COMPATIBILITY !
|
|
// Detect if the baked hair texture actually wasn't sent, and if so set to default
|
|
if (isTextureDefined(TEX_HAIR_BAKED) && getImage(TEX_HAIR_BAKED,0)->getID() == getImage(TEX_SKIRT_BAKED,0)->getID())
|
|
{
|
|
if (getImage(TEX_HAIR_BAKED,0)->getID() != IMG_INVISIBLE)
|
|
{
|
|
// Regression case of messaging system. Expected 21 textures, received 20. last texture is not valid so set to default
|
|
setTETexture(TEX_HAIR_BAKED, IMG_DEFAULT_AVATAR);
|
|
}
|
|
}
|
|
|
|
for (U8 baked_index = 0; baked_index < BAKED_NUM_INDICES; baked_index++)
|
|
{
|
|
const LLAvatarAppearanceDictionary::BakedEntry * bakedDicEntry = LLAvatarAppearanceDictionary::getInstance()->getBakedTexture((EBakedTextureIndex)baked_index);
|
|
// skip if this is a skirt and av is not wearing one, or if we don't have a baked texture UUID
|
|
if (!isTextureDefined(bakedDicEntry->mTextureIndex)
|
|
&& ( (baked_index != BAKED_SKIRT) || isWearingWearableType(LLWearableType::WT_SKIRT) ))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
for (U8 texture = 0; texture < bakedDicEntry->mLocalTextures.size(); texture++)
|
|
{
|
|
const U8 te = (ETextureIndex)bakedDicEntry->mLocalTextures[texture];
|
|
setTETexture(te, IMG_DEFAULT_AVATAR);
|
|
}
|
|
}
|
|
}
|
|
|
|
void LLVOAvatar::dumpAvatarTEs( const std::string& context ) const
|
|
{
|
|
LL_DEBUGS("Avatar") << avString() << (isSelf() ? "Self: " : "Other: ") << context << LL_ENDL;
|
|
for (LLAvatarAppearanceDictionary::Textures::const_iterator iter = LLAvatarAppearanceDictionary::getInstance()->getTextures().begin();
|
|
iter != LLAvatarAppearanceDictionary::getInstance()->getTextures().end();
|
|
++iter)
|
|
{
|
|
const LLAvatarAppearanceDictionary::TextureEntry *texture_dict = iter->second;
|
|
// TODO: MULTI-WEARABLE: handle multiple textures for self
|
|
const LLViewerTexture* te_image = getImage(iter->first,0);
|
|
if( !te_image )
|
|
{
|
|
LL_DEBUGS("Avatar") << avString() << " " << texture_dict->mName << ": null ptr" << LL_ENDL;
|
|
}
|
|
else if( te_image->getID().isNull() )
|
|
{
|
|
LL_DEBUGS("Avatar") << avString() << " " << texture_dict->mName << ": null UUID" << LL_ENDL;
|
|
}
|
|
else if( te_image->getID() == IMG_DEFAULT )
|
|
{
|
|
LL_DEBUGS("Avatar") << avString() << " " << texture_dict->mName << ": IMG_DEFAULT" << LL_ENDL;
|
|
}
|
|
else if (te_image->getID() == IMG_INVISIBLE)
|
|
{
|
|
LL_DEBUGS("Avatar") << avString() << " " << texture_dict->mName << ": IMG_INVISIBLE" << LL_ENDL;
|
|
}
|
|
else if( te_image->getID() == IMG_DEFAULT_AVATAR )
|
|
{
|
|
LL_DEBUGS("Avatar") << avString() << " " << texture_dict->mName << ": IMG_DEFAULT_AVATAR" << LL_ENDL;
|
|
}
|
|
else
|
|
{
|
|
LL_DEBUGS("Avatar") << avString() << " " << texture_dict->mName << ": " << te_image->getID() << LL_ENDL;
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// clampAttachmentPositions()
|
|
//-----------------------------------------------------------------------------
|
|
void LLVOAvatar::clampAttachmentPositions()
|
|
{
|
|
if (isDead())
|
|
{
|
|
return;
|
|
}
|
|
for (attachment_map_t::iterator iter = mAttachmentPoints.begin();
|
|
iter != mAttachmentPoints.end();
|
|
++iter)
|
|
{
|
|
LLViewerJointAttachment* attachment = iter->second;
|
|
if (attachment)
|
|
{
|
|
attachment->clampObjectPosition();
|
|
}
|
|
}
|
|
}
|
|
|
|
BOOL LLVOAvatar::hasHUDAttachment() const
|
|
{
|
|
for (attachment_map_t::const_iterator iter = mAttachmentPoints.begin();
|
|
iter != mAttachmentPoints.end();
|
|
++iter)
|
|
{
|
|
LLViewerJointAttachment* attachment = iter->second;
|
|
if (attachment->getIsHUDAttachment() && attachment->getNumObjects() > 0)
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
LLBBox LLVOAvatar::getHUDBBox() const
|
|
{
|
|
LLBBox bbox;
|
|
#if SLOW_ATTACHMENT_LIST
|
|
for (attachment_map_t::const_iterator iter = mAttachmentPoints.begin();
|
|
iter != mAttachmentPoints.end();
|
|
++iter)
|
|
{
|
|
LLViewerJointAttachment* attachment = iter->second;
|
|
if (attachment->getIsHUDAttachment())
|
|
{
|
|
for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin();
|
|
attachment_iter != attachment->mAttachedObjects.end();
|
|
++attachment_iter)
|
|
{
|
|
const LLViewerObject* attached_object = (*attachment_iter);
|
|
#else
|
|
for(auto& iter : mAttachedObjectsVector)
|
|
{{{
|
|
const LLViewerJointAttachment* attachment = iter.second;
|
|
if (!attachment || !attachment->getIsHUDAttachment())
|
|
continue;
|
|
const LLViewerObject* attached_object = iter.first;
|
|
#endif
|
|
if (attached_object == NULL)
|
|
{
|
|
LL_WARNS() << "HUD attached object is NULL!" << LL_ENDL;
|
|
continue;
|
|
}
|
|
// initialize bounding box to contain identity orientation and center point for attached object
|
|
bbox.addPointLocal(attached_object->getPosition());
|
|
// add rotated bounding box for attached object
|
|
bbox.addBBoxAgent(attached_object->getBoundingBoxAgent());
|
|
LLViewerObject::const_child_list_t& child_list = attached_object->getChildren();
|
|
for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin();
|
|
iter != child_list.end();
|
|
++iter)
|
|
{
|
|
const LLViewerObject* child_objectp = *iter;
|
|
bbox.addBBoxAgent(child_objectp->getBoundingBoxAgent());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return bbox;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// onFirstTEMessageReceived()
|
|
//-----------------------------------------------------------------------------
|
|
void LLVOAvatar::onFirstTEMessageReceived()
|
|
{
|
|
LL_DEBUGS("Avatar") << avString() << LL_ENDL;
|
|
if( !mFirstTEMessageReceived )
|
|
{
|
|
mFirstTEMessageReceived = TRUE;
|
|
|
|
LLLoadedCallbackEntry::source_callback_list_t* src_callback_list = NULL ;
|
|
BOOL paused = FALSE ;
|
|
if(!isSelf())
|
|
{
|
|
src_callback_list = &mCallbackTextureList ;
|
|
paused = !isVisible();
|
|
}
|
|
|
|
for (U32 i = 0; i < mBakedTextureDatas.size(); i++)
|
|
{
|
|
const BOOL layer_baked = isTextureDefined(mBakedTextureDatas[i].mTextureIndex);
|
|
|
|
// Use any baked textures that we have even if they haven't downloaded yet.
|
|
// (That is, don't do a transition from unbaked to baked.)
|
|
if (layer_baked)
|
|
{
|
|
LLViewerFetchedTexture* image = LLViewerTextureManager::staticCastToFetchedTexture(getImage( mBakedTextureDatas[i].mTextureIndex, 0 ), TRUE) ;
|
|
mBakedTextureDatas[i].mLastTextureID = image->getID();
|
|
// If we have more than one texture for the other baked layers, we'll want to call this for them too.
|
|
if ( (image->getID() != IMG_INVISIBLE) && ((i == BAKED_HEAD) || (i == BAKED_UPPER) || (i == BAKED_LOWER)) )
|
|
{
|
|
image->setLoadedCallback( onBakedTextureMasksLoaded, MORPH_MASK_REQUESTED_DISCARD, TRUE, TRUE, new LLTextureMaskData( mID ),
|
|
src_callback_list, paused);
|
|
}
|
|
LL_DEBUGS("Avatar") << avString() << "layer_baked, setting onInitialBakedTextureLoaded as callback" << LL_ENDL;
|
|
image->setLoadedCallback( onInitialBakedTextureLoaded, MAX_DISCARD_LEVEL, FALSE, FALSE, new LLUUID( mID ),
|
|
src_callback_list, paused );
|
|
|
|
// this could add paused texture callbacks
|
|
mLoadedCallbacksPaused |= paused;
|
|
}
|
|
}
|
|
|
|
mMeshTexturesDirty = TRUE;
|
|
gPipeline.markGLRebuild(this);
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// bool visualParamWeightsAreDefault()
|
|
//-----------------------------------------------------------------------------
|
|
bool LLVOAvatar::visualParamWeightsAreDefault()
|
|
{
|
|
bool rtn = true;
|
|
|
|
bool is_wearing_skirt = isWearingWearableType(LLWearableType::WT_SKIRT);
|
|
for (LLVisualParam *param = getFirstVisualParam();
|
|
param;
|
|
param = getNextVisualParam())
|
|
{
|
|
if (param->isTweakable())
|
|
{
|
|
LLViewerVisualParam* vparam = dynamic_cast<LLViewerVisualParam*>(param);
|
|
llassert(vparam);
|
|
bool is_skirt_param = vparam &&
|
|
LLWearableType::WT_SKIRT == vparam->getWearableType();
|
|
if (param->getWeight() != param->getDefaultWeight() &&
|
|
// we have to not care whether skirt weights are default, if we're not actually wearing a skirt
|
|
(is_wearing_skirt || !is_skirt_param))
|
|
{
|
|
//LL_INFOS() << "param '" << param->getName() << "'=" << param->getWeight() << " which differs from default=" << param->getDefaultWeight() << LL_ENDL;
|
|
rtn = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//LL_INFOS() << "params are default ? " << int(rtn) << LL_ENDL;
|
|
|
|
return rtn;
|
|
}
|
|
|
|
void dump_visual_param(apr_file_t* file, LLVisualParam const* viewer_param, F32 value)
|
|
{
|
|
S32 u8_value = F32_to_U8(value,viewer_param->getMinWeight(),viewer_param->getMaxWeight());
|
|
apr_file_printf(file, " <param id=\"%d\" name=\"%s\" value=\"%.3f\" u8=\"%d\" type=\"%s\" wearable=\"%s\"/>\n",
|
|
viewer_param->getID(), viewer_param->getName().c_str(), value, u8_value, viewer_param->getTypeString(),
|
|
viewer_param->getDumpWearableTypeName().c_str());
|
|
}
|
|
|
|
|
|
void LLVOAvatar::dumpAppearanceMsgParams( const std::string& dump_prefix,
|
|
const LLAppearanceMessageContents& contents)
|
|
{
|
|
std::string outfilename = get_sequential_numbered_file_name(dump_prefix,".xml");
|
|
const std::vector<F32>& params_for_dump = contents.mParamWeights;
|
|
const LLTEContents& tec = contents.mTEContents;
|
|
|
|
LLAPRFile outfile;
|
|
std::string fullpath = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,outfilename);
|
|
outfile.open(fullpath, LL_APR_WB );
|
|
apr_file_t* file = outfile.getFileHandle();
|
|
if (!file)
|
|
{
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
LL_DEBUGS("Avatar") << "dumping appearance message to " << fullpath << LL_ENDL;
|
|
}
|
|
|
|
apr_file_printf(file, "<header>\n");
|
|
apr_file_printf(file, "\t\t<cof_version %i />\n", contents.mCOFVersion);
|
|
apr_file_printf(file, "\t\t<appearance_version %i />\n", contents.mAppearanceVersion);
|
|
apr_file_printf(file, "</header>\n");
|
|
|
|
apr_file_printf(file, "\n<params>\n");
|
|
LLVisualParam* param = getFirstVisualParam();
|
|
for (const auto& param_for_dump : params_for_dump)
|
|
{
|
|
while( param && ((param->getGroup() != VISUAL_PARAM_GROUP_TWEAKABLE) &&
|
|
(param->getGroup() != VISUAL_PARAM_GROUP_TRANSMIT_NOT_TWEAKABLE)) ) // should not be any of group VISUAL_PARAM_GROUP_TWEAKABLE_NO_TRANSMIT
|
|
{
|
|
param = getNextVisualParam();
|
|
}
|
|
LLViewerVisualParam* viewer_param = (LLViewerVisualParam*)param;
|
|
F32 value = param_for_dump;
|
|
dump_visual_param(file, viewer_param, value);
|
|
param = getNextVisualParam();
|
|
}
|
|
apr_file_printf(file, "</params>\n");
|
|
|
|
apr_file_printf(file, "\n<textures>\n");
|
|
for (U32 i = 0; i < tec.face_count; i++)
|
|
{
|
|
std::string uuid_str;
|
|
((LLUUID*)tec.image_data)[i].toString(uuid_str);
|
|
apr_file_printf( file, "\t\t<texture te=\"%i\" uuid=\"%s\"/>\n", i, uuid_str.c_str());
|
|
}
|
|
apr_file_printf(file, "</textures>\n");
|
|
}
|
|
|
|
void LLVOAvatar::parseAppearanceMessage(LLMessageSystem* mesgsys, LLAppearanceMessageContents& contents)
|
|
{
|
|
parseTEMessage(mesgsys, _PREHASH_ObjectData, -1, contents.mTEContents);
|
|
|
|
// Parse the AppearanceData field, if any.
|
|
if (mesgsys->has(_PREHASH_AppearanceData))
|
|
{
|
|
U8 av_u8;
|
|
mesgsys->getU8Fast(_PREHASH_AppearanceData, _PREHASH_AppearanceVersion, av_u8, 0);
|
|
contents.mAppearanceVersion = av_u8;
|
|
LL_DEBUGS("Avatar") << "appversion set by AppearanceData field: " << contents.mAppearanceVersion << LL_ENDL;
|
|
mesgsys->getS32Fast(_PREHASH_AppearanceData, _PREHASH_CofVersion, contents.mCOFVersion, 0);
|
|
// For future use:
|
|
//mesgsys->getU32Fast(_PREHASH_AppearanceData, _PREHASH_Flags, appearance_flags, 0);
|
|
}
|
|
|
|
// Parse the AppearanceHover field, if any.
|
|
contents.mHoverOffsetWasSet = false;
|
|
if (mesgsys->has(_PREHASH_AppearanceHover))
|
|
{
|
|
LLVector3 hover;
|
|
mesgsys->getVector3Fast(_PREHASH_AppearanceHover, _PREHASH_HoverHeight, hover);
|
|
LL_DEBUGS("Avatar") << avString() << " hover received " << hover.mV[ VX ] << "," << hover.mV[ VY ] << "," << hover.mV[ VZ ] << LL_ENDL;
|
|
contents.mHoverOffset = hover;
|
|
contents.mHoverOffsetWasSet = true;
|
|
}
|
|
|
|
// Parse visual params, if any.
|
|
S32 num_blocks = mesgsys->getNumberOfBlocksFast(_PREHASH_VisualParam);
|
|
bool drop_visual_params_debug = gSavedSettings.getBOOL("BlockSomeAvatarAppearanceVisualParams") && (ll_rand(2) == 0); // pretend that ~12% of AvatarAppearance messages arrived without a VisualParam block, for testing
|
|
if( num_blocks > 1 && !drop_visual_params_debug)
|
|
{
|
|
LL_DEBUGS("Avatar") << avString() << " handle visual params, num_blocks " << num_blocks << LL_ENDL;
|
|
|
|
LLVisualParam* param = getFirstVisualParam();
|
|
llassert(param); // if this ever fires, we should do the same as when num_blocks<=1
|
|
if (!param)
|
|
{
|
|
LL_WARNS() << "No visual params!" << LL_ENDL;
|
|
}
|
|
else
|
|
{
|
|
for( S32 i = 0; i < num_blocks; i++ )
|
|
{
|
|
while( param && ((param->getGroup() != VISUAL_PARAM_GROUP_TWEAKABLE) &&
|
|
(param->getGroup() != VISUAL_PARAM_GROUP_TRANSMIT_NOT_TWEAKABLE)) ) // should not be any of group VISUAL_PARAM_GROUP_TWEAKABLE_NO_TRANSMIT
|
|
{
|
|
param = getNextVisualParam();
|
|
}
|
|
|
|
if( !param )
|
|
{
|
|
// more visual params supplied than expected - just process what we know about
|
|
break;
|
|
}
|
|
|
|
U8 value;
|
|
mesgsys->getU8Fast(_PREHASH_VisualParam, _PREHASH_ParamValue, value, i);
|
|
F32 newWeight = U8_to_F32(value, param->getMinWeight(), param->getMaxWeight());
|
|
contents.mParamWeights.push_back(newWeight);
|
|
contents.mParams.push_back(param);
|
|
|
|
param = getNextVisualParam();
|
|
}
|
|
}
|
|
|
|
const S32 expected_tweakable_count = getVisualParamCountInGroup(VISUAL_PARAM_GROUP_TWEAKABLE) +
|
|
getVisualParamCountInGroup(VISUAL_PARAM_GROUP_TRANSMIT_NOT_TWEAKABLE); // don't worry about VISUAL_PARAM_GROUP_TWEAKABLE_NO_TRANSMIT
|
|
if (num_blocks != expected_tweakable_count)
|
|
{
|
|
LL_DEBUGS("Avatar") << "Number of params in AvatarAppearance msg (" << num_blocks << ") does not match number of tweakable params in avatar xml file (" << expected_tweakable_count << "). Processing what we can. object: " << getID() << LL_ENDL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (drop_visual_params_debug)
|
|
{
|
|
LL_INFOS() << "Debug-faked lack of parameters on AvatarAppearance for object: " << getID() << LL_ENDL;
|
|
}
|
|
else
|
|
{
|
|
LL_DEBUGS("Avatar") << "AvatarAppearance msg received without any parameters, object: " << getID() << LL_ENDL;
|
|
}
|
|
}
|
|
|
|
LLVisualParam* appearance_version_param = getVisualParam(11000);
|
|
if (appearance_version_param)
|
|
{
|
|
std::vector<LLVisualParam*>::iterator it = std::find(contents.mParams.begin(), contents.mParams.end(),appearance_version_param);
|
|
if (it != contents.mParams.end())
|
|
{
|
|
S32 index = it - contents.mParams.begin();
|
|
contents.mParamAppearanceVersion = ll_round(contents.mParamWeights[index]);
|
|
LL_DEBUGS("Avatar") << "appversion req by appearance_version param: " << contents.mParamAppearanceVersion << LL_ENDL;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool resolve_appearance_version(const LLAppearanceMessageContents& contents, S32& appearance_version)
|
|
{
|
|
appearance_version = -1;
|
|
|
|
if ((contents.mAppearanceVersion) >= 0 &&
|
|
(contents.mParamAppearanceVersion >= 0) &&
|
|
(contents.mAppearanceVersion != contents.mParamAppearanceVersion))
|
|
{
|
|
LL_WARNS() << "inconsistent appearance_version settings - field: " <<
|
|
contents.mAppearanceVersion << ", param: " << contents.mParamAppearanceVersion << LL_ENDL;
|
|
return false;
|
|
}
|
|
if (contents.mParamAppearanceVersion >= 0) // use visual param if available.
|
|
{
|
|
appearance_version = contents.mParamAppearanceVersion;
|
|
}
|
|
else if (contents.mAppearanceVersion > 0)
|
|
{
|
|
appearance_version = contents.mAppearanceVersion;
|
|
}
|
|
else // still not set, go with 1.
|
|
{
|
|
appearance_version = 1;
|
|
}
|
|
LL_DEBUGS("Avatar") << "appearance version info - field " << contents.mAppearanceVersion
|
|
<< " param: " << contents.mParamAppearanceVersion
|
|
<< " final: " << appearance_version << LL_ENDL;
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// processAvatarAppearance()
|
|
//-----------------------------------------------------------------------------
|
|
void LLVOAvatar::processAvatarAppearance( LLMessageSystem* mesgsys )
|
|
{
|
|
static S32 largestSelfCOFSeen(LLViewerInventoryCategory::VERSION_UNKNOWN);
|
|
LL_DEBUGS("Avatar") << "starts" << LL_ENDL;
|
|
|
|
bool enable_verbose_dumps = gSavedSettings.getBOOL("DebugAvatarAppearanceMessage");
|
|
std::string dump_prefix = getFullname() + "_" + (isSelf()?"s":"o") + "_";
|
|
if (gSavedSettings.getBOOL("BlockAvatarAppearanceMessages"))
|
|
{
|
|
LL_WARNS() << "Blocking AvatarAppearance message" << LL_ENDL;
|
|
return;
|
|
}
|
|
|
|
mLastAppearanceMessageTimer.reset();
|
|
|
|
LLPointer<LLAppearanceMessageContents> contents(new LLAppearanceMessageContents);
|
|
parseAppearanceMessage(mesgsys, *contents);
|
|
if (enable_verbose_dumps)
|
|
{
|
|
dumpAppearanceMsgParams(dump_prefix + "appearance_msg", *contents);
|
|
}
|
|
|
|
S32 appearance_version;
|
|
if (!resolve_appearance_version(*contents, appearance_version))
|
|
{
|
|
LL_WARNS() << "bad appearance version info, discarding" << LL_ENDL;
|
|
return;
|
|
}
|
|
llassert(appearance_version > 0);
|
|
if (appearance_version > 1)
|
|
{
|
|
LL_WARNS() << "unsupported appearance version " << appearance_version << ", discarding appearance message" << LL_ENDL;
|
|
return;
|
|
}
|
|
|
|
S32 thisAppearanceVersion(contents->mCOFVersion);
|
|
|
|
// Only now that we have result of appearance_version can we decide whether to bail out.
|
|
if( isSelf() )
|
|
{
|
|
LL_DEBUGS("Avatar") << "thisAppearanceVersion " << thisAppearanceVersion
|
|
<< " mLastUpdateRequestCOFVersion " << mLastUpdateRequestCOFVersion
|
|
<< " my_cof_version " << LLAppearanceMgr::instance().getCOFVersion() << LL_ENDL;
|
|
|
|
if (largestSelfCOFSeen > thisAppearanceVersion)
|
|
{
|
|
LL_WARNS("Avatar") << "Already processed appearance for COF version " <<
|
|
largestSelfCOFSeen << ", discarding appearance with COF " << thisAppearanceVersion << LL_ENDL;
|
|
return;
|
|
}
|
|
largestSelfCOFSeen = thisAppearanceVersion;
|
|
|
|
}
|
|
else
|
|
{
|
|
LL_DEBUGS("Avatar") << "appearance message received" << LL_ENDL;
|
|
}
|
|
|
|
// Check for stale update.
|
|
if (isSelf()
|
|
&& (appearance_version>0)
|
|
&& (thisAppearanceVersion < mLastUpdateRequestCOFVersion))
|
|
{
|
|
LL_WARNS() << "Stale appearance update, wanted version " << mLastUpdateRequestCOFVersion
|
|
<< ", got " << thisAppearanceVersion << LL_ENDL;
|
|
return;
|
|
}
|
|
|
|
if (isSelf() && isEditingAppearance())
|
|
{
|
|
LL_DEBUGS("Avatar") << "ignoring appearance message while in appearance edit" << LL_ENDL;
|
|
return;
|
|
}
|
|
|
|
// SUNSHINE CLEANUP - is this case OK now?
|
|
S32 num_params = contents->mParamWeights.size();
|
|
if (num_params <= 1)
|
|
{
|
|
// In this case, we have no reliable basis for knowing
|
|
// appearance version, which may cause us to look for baked
|
|
// textures in the wrong place and flag them as missing
|
|
// assets.
|
|
LL_DEBUGS("Avatar") << "ignoring appearance message due to lack of params" << LL_ENDL;
|
|
return;
|
|
}
|
|
|
|
// No backsies zone - if we get here, the message should be valid and usable, will be processed.
|
|
LL_INFOS("Avatar") << "Processing appearance message version " << thisAppearanceVersion << LL_ENDL;
|
|
setIsUsingServerBakes(appearance_version > 0);
|
|
|
|
// Note:
|
|
// RequestAgentUpdateAppearanceResponder::onRequestRequested()
|
|
// assumes that cof version is only updated with server-bake
|
|
// appearance messages.
|
|
mLastUpdateReceivedCOFVersion = thisAppearanceVersion;
|
|
|
|
SHClientTagMgr::instance().updateAvatarTag(this);
|
|
mLastProcessedAppearance = contents;
|
|
|
|
bool slam_params = false;
|
|
applyParsedAppearanceMessage(*contents, slam_params);
|
|
}
|
|
|
|
void LLVOAvatar::applyParsedAppearanceMessage(LLAppearanceMessageContents& contents, bool slam_params)
|
|
{
|
|
S32 num_params = contents.mParamWeights.size();
|
|
ESex old_sex = getSex();
|
|
|
|
if (applyParsedTEMessage(contents.mTEContents) > 0 && isChanged(TEXTURE))
|
|
{
|
|
updateVisualComplexity();
|
|
}
|
|
|
|
// prevent the overwriting of valid baked textures with invalid baked textures
|
|
for (U8 baked_index = 0; baked_index < mBakedTextureDatas.size(); baked_index++)
|
|
{
|
|
if (!isTextureDefined(mBakedTextureDatas[baked_index].mTextureIndex)
|
|
&& mBakedTextureDatas[baked_index].mLastTextureID != IMG_DEFAULT
|
|
&& baked_index != BAKED_SKIRT && baked_index != BAKED_LEFT_ARM && baked_index != BAKED_LEFT_LEG && baked_index != BAKED_AUX1 && baked_index != BAKED_AUX2 && baked_index != BAKED_AUX3)
|
|
{
|
|
LL_DEBUGS("Avatar") << avString() << " baked_index " << (S32) baked_index << " using mLastTextureID " << mBakedTextureDatas[baked_index].mLastTextureID << LL_ENDL;
|
|
setTEImage(mBakedTextureDatas[baked_index].mTextureIndex,
|
|
LLViewerTextureManager::getFetchedTexture(mBakedTextureDatas[baked_index].mLastTextureID, FTT_HOST_BAKE, TRUE, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE));
|
|
}
|
|
else
|
|
{
|
|
LL_DEBUGS("Avatar") << avString() << " baked_index " << (S32) baked_index << " using texture id "
|
|
<< getTE(mBakedTextureDatas[baked_index].mTextureIndex)->getID() << LL_ENDL;
|
|
}
|
|
}
|
|
|
|
// runway - was
|
|
// if (!is_first_appearance_message )
|
|
// which means it would be called on second appearance message - probably wrong.
|
|
BOOL is_first_appearance_message = !mFirstAppearanceMessageReceived;
|
|
mFirstAppearanceMessageReceived = TRUE;
|
|
|
|
LL_DEBUGS("Avatar") << avString() << "processAvatarAppearance start " << mID
|
|
<< " first? " << is_first_appearance_message << " self? " << isSelf() << LL_ENDL;
|
|
|
|
if (is_first_appearance_message )
|
|
{
|
|
onFirstTEMessageReceived();
|
|
}
|
|
|
|
setCompositeUpdatesEnabled( FALSE );
|
|
gPipeline.markGLRebuild(this);
|
|
|
|
mHasPhysicsParameters = false;
|
|
|
|
// Apply visual params
|
|
if( num_params > 1)
|
|
{
|
|
LL_DEBUGS("Avatar") << avString() << " handle visual params, num_params " << num_params << LL_ENDL;
|
|
BOOL params_changed = FALSE;
|
|
BOOL interp_params = FALSE;
|
|
S32 params_changed_count = 0;
|
|
|
|
for( S32 i = 0; i < num_params; i++ )
|
|
{
|
|
LLVisualParam* param = contents.mParams[i];
|
|
F32 newWeight = contents.mParamWeights[i];
|
|
|
|
if (!mHasPhysicsParameters && param->getID() == 10000)
|
|
mHasPhysicsParameters = true;
|
|
|
|
if (slam_params || is_first_appearance_message || (param->getWeight() != newWeight))
|
|
{
|
|
params_changed = TRUE;
|
|
params_changed_count++;
|
|
|
|
if(is_first_appearance_message || slam_params )
|
|
{
|
|
//LL_DEBUGS("Avatar") << "param slam " << i << " " << newWeight << LL_ENDL;
|
|
param->setWeight(newWeight, FALSE);
|
|
}
|
|
else
|
|
{
|
|
interp_params = TRUE;
|
|
param->setAnimationTarget(newWeight, FALSE);
|
|
}
|
|
}
|
|
}
|
|
const S32 expected_tweakable_count = getVisualParamCountInGroup(VISUAL_PARAM_GROUP_TWEAKABLE) +
|
|
getVisualParamCountInGroup(VISUAL_PARAM_GROUP_TRANSMIT_NOT_TWEAKABLE); // don't worry about VISUAL_PARAM_GROUP_TWEAKABLE_NO_TRANSMIT
|
|
if (num_params != expected_tweakable_count)
|
|
{
|
|
LL_DEBUGS("Avatar") << "Number of params in AvatarAppearance msg (" << num_params << ") does not match number of tweakable params in avatar xml file (" << expected_tweakable_count << "). Processing what we can. object: " << getID() << LL_ENDL;
|
|
}
|
|
|
|
LL_DEBUGS("Avatar") << "Changed " << params_changed_count << " params" << LL_ENDL;
|
|
if (params_changed)
|
|
{
|
|
if (interp_params)
|
|
{
|
|
startAppearanceAnimation();
|
|
}
|
|
updateVisualParams();
|
|
|
|
ESex new_sex = getSex();
|
|
if( old_sex != new_sex )
|
|
{
|
|
updateSexDependentLayerSets( FALSE );
|
|
}
|
|
}
|
|
|
|
llassert( getSex() == ((getVisualParamWeight( "male" ) > 0.5f) ? SEX_MALE : SEX_FEMALE) );
|
|
}
|
|
else
|
|
{
|
|
// AvatarAppearance message arrived without visual params
|
|
LL_DEBUGS("Avatar") << avString() << "no visual params" << LL_ENDL;
|
|
|
|
const F32 LOADING_TIMEOUT_SECONDS = 60.f;
|
|
// this isn't really a problem if we already have a non-default shape
|
|
if (visualParamWeightsAreDefault() && mRuthTimer.getElapsedTimeF32() > LOADING_TIMEOUT_SECONDS)
|
|
{
|
|
// re-request appearance, hoping that it comes back with a shape next time
|
|
LL_INFOS() << "Re-requesting AvatarAppearance for object: " << getID() << LL_ENDL;
|
|
LLAvatarPropertiesProcessor::getInstance()->sendAvatarTexturesRequest(getID());
|
|
mRuthTimer.reset();
|
|
}
|
|
else
|
|
{
|
|
LL_INFOS() << "That's okay, we already have a non-default shape for object: " << getID() << LL_ENDL;
|
|
// we don't really care.
|
|
}
|
|
}
|
|
|
|
if (contents.mHoverOffsetWasSet && !isSelf())
|
|
{
|
|
// Got an update for some other avatar
|
|
// Ignore updates for self, because we have a more authoritative value in the preferences.
|
|
setHoverOffset(contents.mHoverOffset);
|
|
LL_INFOS("Avatar") << avString() << "setting hover from message" << contents.mHoverOffset[2] << LL_ENDL;
|
|
}
|
|
|
|
if (!contents.mHoverOffsetWasSet && !isSelf())
|
|
{
|
|
// If we don't get a value at all, we are presumably in a
|
|
// region that does not support hover height.
|
|
LL_WARNS() << avString() << "zeroing hover because not defined in appearance message" << LL_ENDL;
|
|
setHoverOffset(LLVector3(0.0, 0.0, 0.0));
|
|
}
|
|
|
|
setCompositeUpdatesEnabled( TRUE );
|
|
|
|
// If all of the avatars are completely baked, release the global image caches to conserve memory.
|
|
LLVOAvatar::cullAvatarsByPixelArea();
|
|
|
|
if (isSelf())
|
|
{
|
|
mUseLocalAppearance = false;
|
|
}
|
|
|
|
updateMeshTextures();
|
|
//if (enable_verbose_dumps) dumpArchetypeXML(dump_prefix + "process_end");
|
|
updateMeshVisibility();
|
|
}
|
|
|
|
LLViewerTexture* LLVOAvatar::getBakedTexture(const U8 te)
|
|
{
|
|
if (te < 0 || te >= BAKED_NUM_INDICES)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
BOOL is_layer_baked = isTextureDefined(mBakedTextureDatas[te].mTextureIndex);
|
|
|
|
LLViewerTexLayerSet* layerset = NULL;
|
|
layerset = getTexLayerSet(te);
|
|
|
|
|
|
if (!isEditingAppearance() && is_layer_baked)
|
|
{
|
|
LLViewerFetchedTexture* baked_img = LLViewerTextureManager::staticCastToFetchedTexture(getImage(mBakedTextureDatas[te].mTextureIndex, 0), TRUE);
|
|
return baked_img;
|
|
}
|
|
else if (layerset && isEditingAppearance())
|
|
{
|
|
layerset->createComposite();
|
|
layerset->setUpdatesEnabled(TRUE);
|
|
|
|
return layerset->getViewerComposite();
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
// static
|
|
void LLVOAvatar::getAnimLabels( std::vector<std::string>* labels )
|
|
{
|
|
S32 i;
|
|
labels->reserve(gUserAnimStatesCount);
|
|
for( i = 0; i < gUserAnimStatesCount; i++ )
|
|
{
|
|
labels->push_back( LLAnimStateLabels::getStateLabel( gUserAnimStates[i].mName ) );
|
|
}
|
|
|
|
// Special case to trigger away (AFK) state
|
|
labels->push_back( "Away From Keyboard" );
|
|
}
|
|
|
|
// static
|
|
void LLVOAvatar::getAnimNames( std::vector<std::string>* names )
|
|
{
|
|
S32 i;
|
|
|
|
names->reserve(gUserAnimStatesCount);
|
|
for( i = 0; i < gUserAnimStatesCount; i++ )
|
|
{
|
|
names->push_back( std::string(gUserAnimStates[i].mName) );
|
|
}
|
|
|
|
// Special case to trigger away (AFK) state
|
|
names->push_back( "enter_away_from_keyboard_state" );
|
|
}
|
|
|
|
// static
|
|
void LLVOAvatar::onBakedTextureMasksLoaded( BOOL success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* aux_src, S32 discard_level, BOOL final, void* userdata )
|
|
{
|
|
if (!userdata) return;
|
|
|
|
//LL_INFOS() << "onBakedTextureMasksLoaded: " << src_vi->getID() << LL_ENDL;
|
|
const LLUUID id = src_vi->getID();
|
|
|
|
LLTextureMaskData* maskData = (LLTextureMaskData*) userdata;
|
|
LLVOAvatar* self = gObjectList.findAvatar( maskData->mAvatarID );
|
|
|
|
// if discard level is 2 less than last discard level we processed, or we hit 0,
|
|
// then generate morph masks
|
|
if(self && success && (discard_level < maskData->mLastDiscardLevel - 2 || discard_level == 0))
|
|
{
|
|
if(aux_src && aux_src->getComponents() == 1)
|
|
{
|
|
if (!aux_src->getData())
|
|
{
|
|
LL_WARNS() << "No auxiliary source (morph mask) data for image id " << id << LL_ENDL;
|
|
return;
|
|
}
|
|
|
|
auto gl_name = LLImageGL::createTextureName();
|
|
stop_glerror();
|
|
|
|
gGL.getTexUnit(0)->bindManual(LLTexUnit::TT_TEXTURE, gl_name->getTexName());
|
|
stop_glerror();
|
|
|
|
LLImageGL::setManualImage(
|
|
GL_TEXTURE_2D, 0, GL_ALPHA8,
|
|
aux_src->getWidth(), aux_src->getHeight(),
|
|
GL_ALPHA, GL_UNSIGNED_BYTE, aux_src->getData());
|
|
stop_glerror();
|
|
|
|
gGL.getTexUnit(0)->setTextureFilteringOption(LLTexUnit::TFO_BILINEAR);
|
|
|
|
/* if( id == head_baked->getID() )
|
|
if (self->mBakedTextureDatas[BAKED_HEAD].mTexLayerSet)
|
|
//LL_INFOS() << "onBakedTextureMasksLoaded for head " << id << " discard = " << discard_level << LL_ENDL;
|
|
self->mBakedTextureDatas[BAKED_HEAD].mTexLayerSet->applyMorphMask(aux_src->getData(), aux_src->getWidth(), aux_src->getHeight(), 1);
|
|
maskData->mLastDiscardLevel = discard_level; */
|
|
BOOL found_texture_id = false;
|
|
for (LLAvatarAppearanceDictionary::Textures::const_iterator iter = LLAvatarAppearanceDictionary::getInstance()->getTextures().begin();
|
|
iter != LLAvatarAppearanceDictionary::getInstance()->getTextures().end();
|
|
++iter)
|
|
{
|
|
|
|
const LLAvatarAppearanceDictionary::TextureEntry *texture_dict = iter->second;
|
|
if (texture_dict->mIsUsedByBakedTexture)
|
|
{
|
|
const ETextureIndex texture_index = iter->first;
|
|
const LLViewerTexture *baked_img = self->getImage(texture_index, 0);
|
|
if (baked_img && id == baked_img->getID())
|
|
{
|
|
const EBakedTextureIndex baked_index = texture_dict->mBakedTextureIndex;
|
|
self->applyMorphMask(aux_src->getData(), aux_src->getWidth(), aux_src->getHeight(), 1, baked_index);
|
|
maskData->mLastDiscardLevel = discard_level;
|
|
self->mBakedTextureDatas[baked_index].mMaskTexName = gl_name;
|
|
found_texture_id = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!found_texture_id)
|
|
{
|
|
LL_INFOS() << "unexpected image id: " << id << LL_ENDL;
|
|
}
|
|
self->dirtyMesh();
|
|
}
|
|
else
|
|
{
|
|
// this can happen when someone uses an old baked texture possibly provided by
|
|
// viewer-side baked texture caching
|
|
LL_WARNS() << "Masks loaded callback but NO aux source, id " << id << LL_ENDL;
|
|
}
|
|
}
|
|
|
|
if (final || !success)
|
|
{
|
|
delete maskData;
|
|
}
|
|
}
|
|
|
|
// static
|
|
void LLVOAvatar::onInitialBakedTextureLoaded( BOOL success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* aux_src, S32 discard_level, BOOL final, void* userdata )
|
|
{
|
|
LLUUID *avatar_idp = (LLUUID *)userdata;
|
|
LLVOAvatar *selfp = gObjectList.findAvatar(*avatar_idp);
|
|
|
|
if (selfp)
|
|
{
|
|
LL_DEBUGS("Avatar") << selfp->avString() << "discard_level " << discard_level << " success " << success << " final " << final << LL_ENDL;
|
|
}
|
|
|
|
if (!success && selfp)
|
|
{
|
|
selfp->removeMissingBakedTextures();
|
|
}
|
|
if (final || !success )
|
|
{
|
|
delete avatar_idp;
|
|
}
|
|
}
|
|
|
|
// Static
|
|
void LLVOAvatar::onBakedTextureLoaded(BOOL success,
|
|
LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* aux_src,
|
|
S32 discard_level, BOOL final, void* userdata)
|
|
{
|
|
LL_DEBUGS("Avatar") << "onBakedTextureLoaded: " << src_vi->getID() << LL_ENDL;
|
|
|
|
LLUUID id = src_vi->getID();
|
|
LLUUID *avatar_idp = (LLUUID *)userdata;
|
|
LLVOAvatar *selfp = gObjectList.findAvatar(*avatar_idp);
|
|
if (selfp)
|
|
{
|
|
LL_DEBUGS("Avatar") << selfp->avString() << "discard_level " << discard_level << " success " << success << " final " << final << " id " << src_vi->getID() << LL_ENDL;
|
|
}
|
|
|
|
if (selfp && !success)
|
|
{
|
|
selfp->removeMissingBakedTextures();
|
|
}
|
|
|
|
if( final || !success )
|
|
{
|
|
delete avatar_idp;
|
|
}
|
|
|
|
if( selfp && success && final )
|
|
{
|
|
selfp->useBakedTexture( id );
|
|
}
|
|
}
|
|
|
|
|
|
// Called when baked texture is loaded and also when we start up with a baked texture
|
|
void LLVOAvatar::useBakedTexture( const LLUUID& id )
|
|
{
|
|
for (U32 i = 0; i < mBakedTextureDatas.size(); i++)
|
|
{
|
|
LLViewerTexture* image_baked = getImage( mBakedTextureDatas[i].mTextureIndex, 0 );
|
|
if (id == image_baked->getID())
|
|
{
|
|
//LL_DEBUGS("Avatar") << avString() << " i " << i << " id " << id << LL_ENDL;
|
|
mBakedTextureDatas[i].mIsLoaded = true;
|
|
mBakedTextureDatas[i].mLastTextureID = id;
|
|
mBakedTextureDatas[i].mIsUsed = true;
|
|
|
|
if (isUsingLocalAppearance())
|
|
{
|
|
LL_INFOS() << "not changing to baked texture while isUsingLocalAppearance" << LL_ENDL;
|
|
}
|
|
else
|
|
{
|
|
debugColorizeSubMeshes(i,LLColor4::green);
|
|
|
|
avatar_joint_mesh_list_t::iterator iter = mBakedTextureDatas[i].mJointMeshes.begin();
|
|
avatar_joint_mesh_list_t::iterator end = mBakedTextureDatas[i].mJointMeshes.end();
|
|
for (; iter != end; ++iter)
|
|
{
|
|
LLAvatarJointMesh* mesh = (*iter);
|
|
if (mesh)
|
|
{
|
|
mesh->setTexture( image_baked );
|
|
}
|
|
}
|
|
}
|
|
|
|
const LLAvatarAppearanceDictionary::BakedEntry *baked_dict =
|
|
LLAvatarAppearanceDictionary::getInstance()->getBakedTexture((EBakedTextureIndex)i);
|
|
for (texture_vec_t::const_iterator local_tex_iter = baked_dict->mLocalTextures.begin();
|
|
local_tex_iter != baked_dict->mLocalTextures.end();
|
|
++local_tex_iter)
|
|
{
|
|
if (isSelf()) setBakedReady(*local_tex_iter, TRUE);
|
|
}
|
|
|
|
// ! BACKWARDS COMPATIBILITY !
|
|
// Workaround for viewing avatars from old viewers that haven't baked hair textures.
|
|
// This is paired with similar code in updateMeshTextures that sets hair mesh color.
|
|
if (i == BAKED_HAIR)
|
|
{
|
|
avatar_joint_mesh_list_t::iterator iter = mBakedTextureDatas[i].mJointMeshes.begin();
|
|
avatar_joint_mesh_list_t::iterator end = mBakedTextureDatas[i].mJointMeshes.end();
|
|
for (; iter != end; ++iter)
|
|
{
|
|
LLAvatarJointMesh* mesh = (*iter);
|
|
if (mesh)
|
|
{
|
|
mesh->setColor( LLColor4::white );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
dirtyMesh();
|
|
}
|
|
|
|
std::string get_sequential_numbered_file_name(const std::string& prefix,
|
|
const std::string& suffix)
|
|
{
|
|
typedef std::map<std::string,S32> file_num_type;
|
|
static file_num_type file_nums;
|
|
file_num_type::iterator it = file_nums.find(prefix);
|
|
S32 num = 0;
|
|
if (it != file_nums.end())
|
|
{
|
|
num = it->second;
|
|
}
|
|
file_nums[prefix] = num+1;
|
|
std::string outfilename = prefix + " " + llformat("%04d",num) + ".xml";
|
|
std::replace(outfilename.begin(),outfilename.end(),' ','_');
|
|
return outfilename;
|
|
}
|
|
|
|
void dump_sequential_xml(const std::string outprefix, const LLSD& content)
|
|
{
|
|
std::string outfilename = get_sequential_numbered_file_name(outprefix,".xml");
|
|
std::string fullpath = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,outfilename);
|
|
std::ofstream ofs(fullpath.c_str(), std::ios_base::out);
|
|
ofs << LLSDOStreamer<LLSDXMLFormatter>(content, LLSDFormatter::OPTIONS_PRETTY);
|
|
LL_DEBUGS("Avatar") << "results saved to: " << fullpath << LL_ENDL;
|
|
}
|
|
|
|
|
|
void LLVOAvatar::getSortedJointNames(S32 joint_type, std::vector<std::string>& result) const
|
|
{
|
|
result.clear();
|
|
if (joint_type==0)
|
|
{
|
|
avatar_joint_list_t::const_iterator iter = mSkeleton.begin();
|
|
avatar_joint_list_t::const_iterator end = mSkeleton.end();
|
|
for (; iter != end; ++iter)
|
|
{
|
|
LLJoint* pJoint = (*iter);
|
|
result.push_back(pJoint->getName());
|
|
}
|
|
}
|
|
else if (joint_type==1)
|
|
{
|
|
for (const auto& pJoint : mCollisionVolumes)
|
|
{
|
|
result.push_back(pJoint->getName());
|
|
}
|
|
}
|
|
else if (joint_type==2)
|
|
{
|
|
for (LLVOAvatar::attachment_map_t::const_iterator iter = mAttachmentPoints.begin();
|
|
iter != mAttachmentPoints.end(); ++iter)
|
|
{
|
|
LLViewerJointAttachment* pJoint = iter->second;
|
|
if (!pJoint) continue;
|
|
result.push_back(pJoint->getName());
|
|
}
|
|
}
|
|
std::sort(result.begin(), result.end());
|
|
}
|
|
|
|
void LLVOAvatar::dumpArchetypeXML(const std::string& prefix, bool group_by_wearables )
|
|
{
|
|
std::string outprefix(prefix);
|
|
if (outprefix.empty())
|
|
{
|
|
outprefix = getFullname() + (isSelf()?"_s":"_o");
|
|
}
|
|
std::string outfilename = get_sequential_numbered_file_name(outprefix,".xml");
|
|
|
|
std::string fullpath = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,outfilename);
|
|
dumpArchetypeXML_cont(fullpath, group_by_wearables);
|
|
}
|
|
|
|
void LLVOAvatar::dumpArchetypeXML_cont(std::string const& fullpath, bool group_by_wearables)
|
|
{
|
|
try
|
|
{
|
|
AIXMLLindenGenepool linden_genepool(fullpath);
|
|
|
|
if (group_by_wearables)
|
|
{
|
|
for (S32 type = LLWearableType::WT_SHAPE; type < LLWearableType::WT_COUNT; type++)
|
|
{
|
|
AIArchetype archetype((LLWearableType::EType)type);
|
|
|
|
for (LLVisualParam* param = getFirstVisualParam(); param; param = getNextVisualParam())
|
|
{
|
|
LLViewerVisualParam* viewer_param = (LLViewerVisualParam*)param;
|
|
if( (viewer_param->getWearableType() == type) &&
|
|
(viewer_param->isTweakable() ) )
|
|
{
|
|
archetype.add(AIVisualParamIDValuePair(param));
|
|
}
|
|
}
|
|
|
|
for (U8 te = 0; te < TEX_NUM_INDICES; te++)
|
|
{
|
|
if (LLAvatarAppearanceDictionary::getTEWearableType((ETextureIndex)te) == type)
|
|
{
|
|
// MULTIPLE_WEARABLES: extend to multiple wearables?
|
|
LLViewerTexture* te_image = getImage((ETextureIndex)te, 0);
|
|
if( te_image )
|
|
{
|
|
archetype.add(AITextureIDUUIDPair(te, te_image->getID()));
|
|
}
|
|
}
|
|
}
|
|
|
|
linden_genepool.child(archetype);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Just dump all params sequentially.
|
|
AIArchetype archetype; // Legacy: Type is set to WT_NONE and will result in <archetype name="???">.
|
|
|
|
for (LLVisualParam* param = getFirstVisualParam(); param; param = getNextVisualParam())
|
|
{
|
|
archetype.add(AIVisualParamIDValuePair(param));
|
|
}
|
|
|
|
for (U8 te = 0; te < TEX_NUM_INDICES; te++)
|
|
{
|
|
{
|
|
// MULTIPLE_WEARABLES: extend to multiple wearables?
|
|
LLViewerTexture* te_image = getImage((ETextureIndex)te, 0);
|
|
if( te_image )
|
|
{
|
|
archetype.add(AITextureIDUUIDPair(te, te_image->getID()));
|
|
}
|
|
}
|
|
}
|
|
|
|
linden_genepool.child(archetype);
|
|
}
|
|
|
|
#if 0 // Wasn't used anyway.
|
|
bool ultra_verbose = false;
|
|
if (isSelf() && ultra_verbose)
|
|
{
|
|
// show the cloned params inside the wearables as well.
|
|
gAgentAvatarp->dumpWearableInfo(outfile);
|
|
}
|
|
#endif
|
|
}
|
|
catch (AIAlert::Error const& error)
|
|
{
|
|
AIAlert::add_modal("AIXMLdumpArchetypeXMLError", AIArgs("[FILE]", fullpath), error);
|
|
}
|
|
}
|
|
|
|
|
|
void LLVOAvatar::setVisibilityRank(U32 rank)
|
|
{
|
|
if (mDrawable.isNull() || mDrawable->isDead())
|
|
{
|
|
// do nothing
|
|
return;
|
|
}
|
|
mVisibilityRank = rank;
|
|
}
|
|
|
|
// Assumes LLVOAvatar::sInstances has already been sorted.
|
|
S32 LLVOAvatar::getUnbakedPixelAreaRank()
|
|
{
|
|
S32 rank = 1;
|
|
for (std::vector<LLCharacter*>::iterator iter = LLCharacter::sInstances.begin();
|
|
iter != LLCharacter::sInstances.end(); ++iter)
|
|
{
|
|
LLVOAvatar* inst = (LLVOAvatar*) *iter;
|
|
if (inst == this)
|
|
{
|
|
return rank;
|
|
}
|
|
else if (!inst->isDead() && !inst->isFullyBaked())
|
|
{
|
|
rank++;
|
|
}
|
|
}
|
|
|
|
llassert(0);
|
|
return 0;
|
|
}
|
|
|
|
struct CompareScreenAreaGreater
|
|
{
|
|
BOOL operator()(const LLCharacter* const& lhs, const LLCharacter* const& rhs)
|
|
{
|
|
return lhs->getPixelArea() > rhs->getPixelArea();
|
|
}
|
|
};
|
|
|
|
// static
|
|
void LLVOAvatar::cullAvatarsByPixelArea()
|
|
{
|
|
std::sort(LLCharacter::sInstances.begin(), LLCharacter::sInstances.end(), CompareScreenAreaGreater());
|
|
|
|
// Update the avatars that have changed status
|
|
U32 rank = 2; //1 is reserved for self.
|
|
for (std::vector<LLCharacter*>::iterator iter = LLCharacter::sInstances.begin();
|
|
iter != LLCharacter::sInstances.end(); ++iter)
|
|
{
|
|
LLVOAvatar* inst = (LLVOAvatar*) *iter;
|
|
BOOL culled;
|
|
if (inst->isSelf() || inst->isFullyBaked())
|
|
{
|
|
culled = FALSE;
|
|
}
|
|
else
|
|
{
|
|
culled = TRUE;
|
|
}
|
|
|
|
if (inst->mCulled != culled)
|
|
{
|
|
inst->mCulled = culled;
|
|
LL_DEBUGS() << "avatar " << inst->getID() << (culled ? " start culled" : " start not culled" ) << LL_ENDL;
|
|
inst->updateMeshTextures();
|
|
}
|
|
|
|
if (inst->isSelf())
|
|
{
|
|
inst->setVisibilityRank(1);
|
|
}
|
|
else if (inst->mDrawable.notNull() && inst->mDrawable->isVisible())
|
|
{
|
|
inst->setVisibilityRank(rank++);
|
|
}
|
|
}
|
|
|
|
// runway - this doesn't really detect gray/grey state.
|
|
S32 grey_avatars = 0;
|
|
if (!LLVOAvatar::areAllNearbyInstancesBaked(grey_avatars))
|
|
{
|
|
if (gFrameTimeSeconds != sUnbakedUpdateTime) // only update once per frame
|
|
{
|
|
sUnbakedUpdateTime = gFrameTimeSeconds;
|
|
sUnbakedTime += gFrameIntervalSeconds.value();
|
|
}
|
|
if (grey_avatars > 0)
|
|
{
|
|
if (gFrameTimeSeconds != sGreyUpdateTime) // only update once per frame
|
|
{
|
|
sGreyUpdateTime = gFrameTimeSeconds;
|
|
sGreyTime += gFrameIntervalSeconds.value();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void LLVOAvatar::startAppearanceAnimation()
|
|
{
|
|
if(!mAppearanceAnimating)
|
|
{
|
|
mAppearanceAnimating = TRUE;
|
|
mAppearanceMorphTimer.reset();
|
|
mLastAppearanceBlendTime = 0.f;
|
|
}
|
|
}
|
|
|
|
BOOL LLVOAvatar::isUsingServerBakes() const
|
|
{
|
|
#if 1
|
|
// Sanity check - visual param for appearance version should match mUseServerBakes
|
|
LLVisualParam* appearance_version_param = getVisualParam(11000);
|
|
llassert(appearance_version_param);
|
|
F32 wt = appearance_version_param->getWeight();
|
|
F32 expect_wt = mUseServerBakes ? 1.0 : 0.0;
|
|
if (!is_approx_equal(wt,expect_wt))
|
|
{
|
|
LL_WARNS() << "wt " << wt << " differs from expected " << expect_wt << LL_ENDL;
|
|
}
|
|
#endif
|
|
|
|
return mUseServerBakes;
|
|
}
|
|
|
|
void LLVOAvatar::setIsUsingServerBakes(BOOL newval)
|
|
{
|
|
mUseServerBakes = newval;
|
|
LLVisualParam* appearance_version_param = getVisualParam(11000);
|
|
llassert(appearance_version_param);
|
|
appearance_version_param->setWeight(newval ? 1.0 : 0.0, false);
|
|
}
|
|
|
|
// virtual
|
|
void LLVOAvatar::removeMissingBakedTextures()
|
|
{
|
|
}
|
|
|
|
//virtual
|
|
void LLVOAvatar::updateRegion(LLViewerRegion *regionp)
|
|
{
|
|
LLViewerObject::updateRegion(regionp);
|
|
}
|
|
|
|
// virtual
|
|
std::string LLVOAvatar::getFullname() const
|
|
{
|
|
std::string name;
|
|
|
|
LLNameValue* first = getNVPair("FirstName");
|
|
LLNameValue* last = getNVPair("LastName");
|
|
if (first && last)
|
|
{
|
|
name = LLCacheName::buildFullName( first->getString(), last->getString() );
|
|
}
|
|
|
|
return name;
|
|
}
|
|
|
|
LLHost LLVOAvatar::getObjectHost() const
|
|
{
|
|
LLViewerRegion* region = getRegion();
|
|
if (region && !isDead())
|
|
{
|
|
return region->getHost();
|
|
}
|
|
else
|
|
{
|
|
return LLHost::invalid;
|
|
}
|
|
}
|
|
|
|
//static
|
|
void LLVOAvatar::updateFreezeCounter(S32 counter)
|
|
{
|
|
if(counter)
|
|
{
|
|
sFreezeCounter = counter;
|
|
}
|
|
else if(sFreezeCounter > 0)
|
|
{
|
|
sFreezeCounter--;
|
|
}
|
|
else
|
|
{
|
|
sFreezeCounter = 0;
|
|
}
|
|
}
|
|
|
|
BOOL LLVOAvatar::updateLOD()
|
|
{
|
|
if (mDrawable.isNull())
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (isImpostor() && 0 != mDrawable->getNumFaces() && mDrawable->getFace(0)->hasGeometry())
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL res = updateJointLODs();
|
|
|
|
LLFace* facep = mDrawable->getFace(0);
|
|
if (!facep || !facep->getVertexBuffer())
|
|
{
|
|
dirtyMesh(2);
|
|
}
|
|
|
|
if (mDirtyMesh >= 2 || mDrawable->isState(LLDrawable::REBUILD_GEOMETRY))
|
|
{ //LOD changed or new mesh created, allocate new vertex buffer if needed
|
|
updateMeshData();
|
|
mDirtyMesh = 0;
|
|
mNeedsSkin = TRUE;
|
|
mDrawable->clearState(LLDrawable::REBUILD_GEOMETRY);
|
|
}
|
|
updateVisibility();
|
|
|
|
return res;
|
|
}
|
|
|
|
void LLVOAvatar::updateLODRiggedAttachments()
|
|
{
|
|
updateLOD();
|
|
rebuildRiggedAttachments();
|
|
}
|
|
|
|
void showRigInfoTabExtents(LLVOAvatar *avatar, LLJointRiggingInfoTab& tab, S32& count_rigged, S32& count_box)
|
|
{
|
|
count_rigged = count_box = 0;
|
|
LLVector4a zero_vec;
|
|
zero_vec.clear();
|
|
for (S32 i=0; i<tab.size(); i++)
|
|
{
|
|
if (tab[i].isRiggedTo())
|
|
{
|
|
count_rigged++;
|
|
LLJoint *joint = avatar->getJoint(i);
|
|
LL_DEBUGS("RigSpam") << "joint " << i << " name " << joint->getName() << " box "
|
|
<< tab[i].getRiggedExtents()[0] << ", " << tab[i].getRiggedExtents()[1] << LL_ENDL;
|
|
if ((!tab[i].getRiggedExtents()[0].equals3(zero_vec)) ||
|
|
(!tab[i].getRiggedExtents()[1].equals3(zero_vec)))
|
|
{
|
|
count_box++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void LLVOAvatar::getAssociatedVolumes(std::vector<LLVOVolume*>& volumes)
|
|
{
|
|
#if SLOW_ATTACHMENT_LIST
|
|
for ( LLVOAvatar::attachment_map_t::iterator iter = mAttachmentPoints.begin(); iter != mAttachmentPoints.end(); ++iter )
|
|
{
|
|
LLViewerJointAttachment* attachment = iter->second;
|
|
LLViewerJointAttachment::attachedobjs_vec_t::iterator attach_end = attachment->mAttachedObjects.end();
|
|
|
|
for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attach_iter = attachment->mAttachedObjects.begin();
|
|
attach_iter != attach_end; ++attach_iter)
|
|
{
|
|
LLViewerObject* attached_object = *attach_iter;
|
|
#else
|
|
for(auto& iter : mAttachedObjectsVector)
|
|
{{
|
|
LLViewerObject* attached_object = iter.first;
|
|
#endif
|
|
LLVOVolume *volume = dynamic_cast<LLVOVolume*>(attached_object);
|
|
if (volume)
|
|
{
|
|
volumes.push_back(volume);
|
|
if (volume->isAnimatedObject())
|
|
{
|
|
// For animated object attachment, don't need
|
|
// the children. Will just get bounding box
|
|
// from the control avatar.
|
|
continue;
|
|
}
|
|
}
|
|
LLViewerObject::const_child_list_t& children = attached_object->getChildren();
|
|
for (LLViewerObject::const_child_list_t::const_iterator it = children.begin();
|
|
it != children.end(); ++it)
|
|
{
|
|
LLViewerObject *childp = *it;
|
|
LLVOVolume *volume = dynamic_cast<LLVOVolume*>(childp);
|
|
if (volume)
|
|
{
|
|
volumes.push_back(volume);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
LLControlAvatar *control_av = asControlAvatar();
|
|
if (control_av)
|
|
{
|
|
LLVOVolume *volp = control_av->mRootVolp;
|
|
if (volp)
|
|
{
|
|
volumes.push_back(volp);
|
|
LLViewerObject::const_child_list_t& children = volp->getChildren();
|
|
for (LLViewerObject::const_child_list_t::const_iterator it = children.begin();
|
|
it != children.end(); ++it)
|
|
{
|
|
LLViewerObject *childp = *it;
|
|
LLVOVolume *volume = dynamic_cast<LLVOVolume*>(childp);
|
|
if (volume)
|
|
{
|
|
volumes.push_back(volume);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static LLTrace::BlockTimerStatHandle FTM_AVATAR_RIGGING_INFO_UPDATE("Av Upd Rig Info");
|
|
static LLTrace::BlockTimerStatHandle FTM_AVATAR_RIGGING_KEY_UPDATE("Av Upd Rig Key");
|
|
static LLTrace::BlockTimerStatHandle FTM_AVATAR_RIGGING_AVOL_UPDATE("Av Upd Avol");
|
|
|
|
// virtual
|
|
void LLVOAvatar::updateRiggingInfo()
|
|
{
|
|
LL_RECORD_BLOCK_TIME(FTM_AVATAR_RIGGING_INFO_UPDATE);
|
|
|
|
LL_DEBUGS("RigSpammish") << getFullname() << " updating rig tab" << LL_ENDL;
|
|
|
|
std::vector<LLVOVolume*> volumes;
|
|
|
|
{
|
|
LL_RECORD_BLOCK_TIME(FTM_AVATAR_RIGGING_AVOL_UPDATE);
|
|
getAssociatedVolumes(volumes);
|
|
}
|
|
|
|
std::map<LLUUID,S32> curr_rigging_info_key;
|
|
{
|
|
LL_RECORD_BLOCK_TIME(FTM_AVATAR_RIGGING_KEY_UPDATE);
|
|
// Get current rigging info key
|
|
for (std::vector<LLVOVolume*>::iterator it = volumes.begin(); it != volumes.end(); ++it)
|
|
{
|
|
LLVOVolume *vol = *it;
|
|
if (vol->isRiggedMesh() && vol->getVolume() && vol->getVolume()->isMeshAssetLoaded())
|
|
{
|
|
const LLUUID& mesh_id = vol->getVolume()->getParams().getSculptID();
|
|
S32 max_lod = llmax(vol->getLOD(), vol->mLastRiggingInfoLOD);
|
|
curr_rigging_info_key[mesh_id] = max_lod;
|
|
}
|
|
}
|
|
|
|
// Check for key change, which indicates some change in volume composition or LOD.
|
|
if (curr_rigging_info_key == mLastRiggingInfoKey)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Something changed. Update.
|
|
mLastRiggingInfoKey = curr_rigging_info_key;
|
|
mJointRiggingInfoTab.clear();
|
|
for (std::vector<LLVOVolume*>::iterator it = volumes.begin(); it != volumes.end(); ++it)
|
|
{
|
|
LLVOVolume *vol = *it;
|
|
vol->updateRiggingInfo();
|
|
mJointRiggingInfoTab.merge(vol->mJointRiggingInfoTab);
|
|
}
|
|
|
|
//LL_INFOS() << "done update rig count is " << countRigInfoTab(mJointRiggingInfoTab) << LL_ENDL;
|
|
LL_DEBUGS("RigSpammish") << getFullname() << " after update rig tab:" << LL_ENDL;
|
|
S32 joint_count, box_count;
|
|
showRigInfoTabExtents(this, mJointRiggingInfoTab, joint_count, box_count);
|
|
LL_DEBUGS("RigSpammish") << "uses " << joint_count << " joints " << " nonzero boxes: " << box_count << LL_ENDL;
|
|
}
|
|
|
|
void LLVOAvatar::updateSoftwareSkinnedVertices(const LLMeshSkinInfo* skin, const LLVector4a* weight, const LLVolumeFace& vol_face, LLVertexBuffer *buffer)
|
|
{
|
|
//perform software vertex skinning for this face
|
|
LLStrider<LLVector3> position;
|
|
LLStrider<LLVector3> normal;
|
|
|
|
bool has_normal = buffer->hasDataType(LLVertexBuffer::TYPE_NORMAL);
|
|
buffer->getVertexStrider(position);
|
|
|
|
if (has_normal)
|
|
{
|
|
buffer->getNormalStrider(normal);
|
|
}
|
|
|
|
LLVector4a* pos = (LLVector4a*) position.get();
|
|
|
|
LLVector4a* norm = has_normal ? (LLVector4a*) normal.get() : NULL;
|
|
|
|
//build matrix palette
|
|
LLMatrix4a mat[LL_MAX_JOINTS_PER_MESH_OBJECT];
|
|
U32 count = LLSkinningUtil::getMeshJointCount(skin);
|
|
LLSkinningUtil::initSkinningMatrixPalette(mat, count, skin, this, true);
|
|
LLSkinningUtil::checkSkinWeights(weight, buffer->getNumVerts(), skin);
|
|
|
|
LLMatrix4a bind_shape_matrix;
|
|
bind_shape_matrix.loadu(skin->mBindShapeMatrix);
|
|
|
|
LLVector4a av_pos;
|
|
av_pos.load3(getPosition().mV);
|
|
|
|
const U32 max_joints = LLSkinningUtil::getMaxJointCount();
|
|
for (U32 j = 0; j < (U32)buffer->getNumVerts(); ++j)
|
|
{
|
|
LLMatrix4a final_mat;
|
|
LLSkinningUtil::getPerVertexSkinMatrix(weight[j].getF32ptr(), mat, false, final_mat, max_joints);
|
|
|
|
LLVector4a& v = vol_face.mPositions[j];
|
|
|
|
LLVector4a t;
|
|
bind_shape_matrix.affineTransform(v, t);
|
|
final_mat.affineTransform(t, pos[j]);
|
|
|
|
pos[j].add(av_pos); // Algorithm tweaked to stop hosing up normals.
|
|
|
|
if (norm)
|
|
{
|
|
LLVector4a& n = vol_face.mNormals[j];
|
|
final_mat.invert();
|
|
final_mat.transpose();
|
|
final_mat.affineTransform(n, norm[j]);
|
|
}
|
|
}
|
|
}
|
|
|
|
void LLVOAvatar::onActiveOverrideMeshesChanged()
|
|
{
|
|
mJointRiggingInfoTab.setNeedsUpdate(true);
|
|
}
|
|
|
|
U32 LLVOAvatar::getPartitionType() const
|
|
{
|
|
// Avatars merely exist as drawables in the bridge partition
|
|
return LLViewerRegion::PARTITION_ATTACHMENT;
|
|
}
|
|
|
|
//static
|
|
void LLVOAvatar::updateImpostors()
|
|
{
|
|
LLViewerCamera::sCurCameraID = LLViewerCamera::CAMERA_WORLD;
|
|
std::vector<LLCharacter*> instances_copy = LLCharacter::sInstances;
|
|
for (std::vector<LLCharacter*>::iterator iter = instances_copy.begin();
|
|
iter != instances_copy.end(); ++iter)
|
|
{
|
|
LLVOAvatar* avatar = (LLVOAvatar*) *iter;
|
|
if (!avatar->isDead() && avatar->needsImpostorUpdate() && avatar->isVisible() && avatar->isImpostor())
|
|
{
|
|
gPipeline.generateImpostor(avatar);
|
|
}
|
|
}
|
|
}
|
|
|
|
BOOL LLVOAvatar::isImpostor() const
|
|
{
|
|
return (isVisuallyMuted() || (sUseImpostors && mUpdatePeriod >= IMPOSTOR_PERIOD)) ? TRUE : FALSE;
|
|
}
|
|
|
|
BOOL LLVOAvatar::shouldImpostor(const U32 rank_factor) const
|
|
{
|
|
return (!isSelf() && sUseImpostors && mVisibilityRank > (sMaxVisible * rank_factor));
|
|
}
|
|
|
|
BOOL LLVOAvatar::needsImpostorUpdate() const
|
|
{
|
|
return mNeedsImpostorUpdate;
|
|
}
|
|
|
|
const LLVector3& LLVOAvatar::getImpostorOffset() const
|
|
{
|
|
return mImpostorOffset;
|
|
}
|
|
|
|
const LLVector2& LLVOAvatar::getImpostorDim() const
|
|
{
|
|
return mImpostorDim;
|
|
}
|
|
|
|
void LLVOAvatar::setImpostorDim(const LLVector2& dim)
|
|
{
|
|
mImpostorDim = dim;
|
|
}
|
|
|
|
void LLVOAvatar::cacheImpostorValues()
|
|
{
|
|
getImpostorValues(mImpostorExtents, mImpostorAngle, mImpostorDistance);
|
|
}
|
|
|
|
void LLVOAvatar::getImpostorValues(LLVector4a* extents, LLVector3& angle, F32& distance) const
|
|
{
|
|
const LLVector4a* ext = mDrawable->getSpatialExtents();
|
|
extents[0] = ext[0];
|
|
extents[1] = ext[1];
|
|
|
|
LLVector3 at = LLViewerCamera::getInstance()->getOrigin()-(getRenderPosition()+mImpostorOffset);
|
|
distance = at.normalize();
|
|
F32 da = 1.f - (at*LLViewerCamera::getInstance()->getAtAxis());
|
|
angle.mV[0] = LLViewerCamera::getInstance()->getYaw()*da;
|
|
angle.mV[1] = LLViewerCamera::getInstance()->getPitch()*da;
|
|
angle.mV[2] = da;
|
|
}
|
|
|
|
|
|
void LLVOAvatar::idleUpdateRenderComplexity()
|
|
{
|
|
if (isControlAvatar())
|
|
{
|
|
LLControlAvatar *cav = asControlAvatar();
|
|
bool is_attachment = cav && cav->mRootVolp && cav->mRootVolp->isAttachment(); // For attached animated objects
|
|
if (is_attachment)
|
|
{
|
|
// ARC for animated object attachments is accounted with the avatar they're attached to.
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (mComplexityTimer.getElapsedTimeF32() > 5.f)
|
|
{
|
|
// Render Complexity
|
|
calculateUpdateRenderComplexity(); // Update mVisualComplexity if needed
|
|
mComplexityTimer.start();
|
|
}
|
|
|
|
if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_SHAME))
|
|
{
|
|
std::string info_line;
|
|
F32 red_level;
|
|
F32 green_level;
|
|
LLColor4 info_color;
|
|
LLFontGL::StyleFlags info_style;
|
|
|
|
if ( !mText )
|
|
{
|
|
initHudText();
|
|
mText->setFadeDistance(20.0, 5.0); // limit clutter in large crowds
|
|
}
|
|
else
|
|
{
|
|
mText->clearString(); // clear debug text
|
|
}
|
|
|
|
/*
|
|
* NOTE: the logic for whether or not each of the values below
|
|
* controls muting MUST match that in the isVisuallyMuted and isTooComplex methods.
|
|
*/
|
|
|
|
static LLCachedControl<U32> max_render_cost(gSavedSettings, "RenderAvatarMaxComplexity", 0);
|
|
info_line = llformat("%d Complexity", mVisualComplexity);
|
|
|
|
if (max_render_cost != 0) // zero means don't care, so don't bother coloring based on this
|
|
{
|
|
green_level = 1.f-llclamp(((F32) mVisualComplexity-(F32)max_render_cost)/(F32)max_render_cost, 0.f, 1.f);
|
|
red_level = llmin((F32) mVisualComplexity/(F32)max_render_cost, 1.f);
|
|
info_color.set(red_level, green_level, 0.0, 1.0);
|
|
info_style = ( mVisualComplexity > max_render_cost
|
|
? LLFontGL::BOLD : LLFontGL::NORMAL );
|
|
}
|
|
else
|
|
{
|
|
info_color.set(LLColor4::grey);
|
|
info_style = LLFontGL::NORMAL;
|
|
}
|
|
mText->addLine(info_line, info_color, info_style);
|
|
|
|
// Visual rank
|
|
info_line = llformat("%d rank", mVisibilityRank);
|
|
// Use grey for imposters, white for normal rendering or no impostors
|
|
info_color.set(isImpostor() ? LLColor4::grey : (isControlAvatar() ? LLColor4::yellow : LLColor4::white));
|
|
info_style = LLFontGL::NORMAL;
|
|
mText->addLine(info_line, info_color, info_style);
|
|
|
|
// Triangle count
|
|
mText->addLine(std::string("VisTris ") + LLStringOps::getReadableNumber(mAttachmentVisibleTriangleCount),
|
|
info_color, info_style);
|
|
mText->addLine(std::string("EstMaxTris ") + LLStringOps::getReadableNumber(mAttachmentEstTriangleCount),
|
|
info_color, info_style);
|
|
|
|
// Attachment Surface Area
|
|
static LLCachedControl<F32> max_attachment_area(gSavedSettings, "RenderAutoMuteSurfaceAreaLimit", 1000.0f);
|
|
info_line = llformat("%.0f m^2", mAttachmentSurfaceArea);
|
|
|
|
if (max_render_cost != 0 && max_attachment_area != 0) // zero means don't care, so don't bother coloring based on this
|
|
{
|
|
green_level = 1.f-llclamp((mAttachmentSurfaceArea-max_attachment_area)/max_attachment_area, 0.f, 1.f);
|
|
red_level = llmin(mAttachmentSurfaceArea/max_attachment_area, 1.f);
|
|
info_color.set(red_level, green_level, 0.0, 1.0);
|
|
info_style = ( mAttachmentSurfaceArea > max_attachment_area
|
|
? LLFontGL::BOLD : LLFontGL::NORMAL );
|
|
|
|
}
|
|
else
|
|
{
|
|
info_color.set(LLColor4::grey);
|
|
info_style = LLFontGL::NORMAL;
|
|
}
|
|
|
|
mText->addLine(info_line, info_color, info_style);
|
|
|
|
updateText(); // corrects position
|
|
}
|
|
}
|
|
|
|
void LLVOAvatar::updateVisualComplexity()
|
|
{
|
|
LL_DEBUGS("AvatarRender") << "avatar " << getID() << " appearance changed" << LL_ENDL;
|
|
// Set the cache time to in the past so it's updated ASAP
|
|
mVisualComplexityStale = true;
|
|
}
|
|
|
|
// Account for the complexity of a single top-level object associated
|
|
// with an avatar. This will be either an attached object or an animated
|
|
// object.
|
|
void LLVOAvatar::accountRenderComplexityForObject(
|
|
const LLViewerObject *attached_object,
|
|
const F32 max_attachment_complexity,
|
|
LLVOVolume::texture_cost_t& textures,
|
|
U32& cost/*,
|
|
hud_complexity_list_t& hud_complexity_list*/)
|
|
{
|
|
if (attached_object && !attached_object->isHUDAttachment())
|
|
{
|
|
mAttachmentVisibleTriangleCount += attached_object->recursiveGetTriangleCount();
|
|
mAttachmentEstTriangleCount += attached_object->recursiveGetEstTrianglesMax();
|
|
mAttachmentSurfaceArea += attached_object->recursiveGetScaledSurfaceArea();
|
|
|
|
textures.clear();
|
|
const LLDrawable* drawable = attached_object->mDrawable;
|
|
if (drawable)
|
|
{
|
|
const LLVOVolume* volume = drawable->getVOVolume();
|
|
if (volume)
|
|
{
|
|
F32 attachment_total_cost = 0;
|
|
F32 attachment_volume_cost = 0;
|
|
F32 attachment_texture_cost = 0;
|
|
F32 attachment_children_cost = 0;
|
|
const F32 animated_object_attachment_surcharge = 1000;
|
|
|
|
if (attached_object->isAnimatedObject())
|
|
{
|
|
attachment_volume_cost += animated_object_attachment_surcharge;
|
|
}
|
|
attachment_volume_cost += volume->getRenderCost(textures);
|
|
|
|
const_child_list_t children = volume->getChildren();
|
|
for (const_child_list_t::const_iterator child_iter = children.begin();
|
|
child_iter != children.end();
|
|
++child_iter)
|
|
{
|
|
LLViewerObject* child_obj = *child_iter;
|
|
LLVOVolume *child = dynamic_cast<LLVOVolume*>(child_obj);
|
|
if (child)
|
|
{
|
|
attachment_children_cost += child->getRenderCost(textures);
|
|
}
|
|
}
|
|
|
|
for (LLVOVolume::texture_cost_t::iterator volume_texture = textures.begin();
|
|
volume_texture != textures.end();
|
|
++volume_texture)
|
|
{
|
|
// add the cost of each individual texture in the linkset
|
|
attachment_texture_cost += volume_texture->second;
|
|
}
|
|
attachment_total_cost = attachment_volume_cost + attachment_texture_cost + attachment_children_cost;
|
|
LL_DEBUGS("ARCdetail") << "Attachment costs " << attached_object->getAttachmentItemID()
|
|
<< " total: " << attachment_total_cost
|
|
<< ", volume: " << attachment_volume_cost
|
|
<< ", textures: " << attachment_texture_cost
|
|
<< ", " << volume->numChildren()
|
|
<< " children: " << attachment_children_cost
|
|
<< LL_ENDL;
|
|
// Limit attachment complexity to avoid signed integer flipping of the wearer's ACI
|
|
cost += (U32)llclamp(attachment_total_cost, MIN_ATTACHMENT_COMPLEXITY, max_attachment_complexity);
|
|
}
|
|
}
|
|
}
|
|
if (isSelf()
|
|
&& attached_object
|
|
&& attached_object->isHUDAttachment()
|
|
&& !attached_object->isTempAttachment()
|
|
&& attached_object->mDrawable)
|
|
{
|
|
textures.clear();
|
|
|
|
mAttachmentSurfaceArea += attached_object->recursiveGetScaledSurfaceArea();
|
|
|
|
#if 0
|
|
const LLVOVolume* volume = attached_object->mDrawable->getVOVolume();
|
|
if (volume)
|
|
{
|
|
|
|
LLHUDComplexity hud_object_complexity;
|
|
hud_object_complexity.objectName = attached_object->getAttachmentItemName();
|
|
hud_object_complexity.objectId = attached_object->getAttachmentItemID();
|
|
std::string joint_name;
|
|
gAgentAvatarp->getAttachedPointName(attached_object->getAttachmentItemID(), joint_name);
|
|
hud_object_complexity.jointName = joint_name;
|
|
// get cost and individual textures
|
|
hud_object_complexity.objectsCost += volume->getRenderCost(textures);
|
|
hud_object_complexity.objectsCount++;
|
|
|
|
LLViewerObject::const_child_list_t& child_list = attached_object->getChildren();
|
|
for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin();
|
|
iter != child_list.end(); ++iter)
|
|
{
|
|
LLViewerObject* childp = *iter;
|
|
const LLVOVolume* chld_volume = dynamic_cast<LLVOVolume*>(childp);
|
|
if (chld_volume)
|
|
{
|
|
// get cost and individual textures
|
|
hud_object_complexity.objectsCost += chld_volume->getRenderCost(textures);
|
|
hud_object_complexity.objectsCount++;
|
|
}
|
|
}
|
|
|
|
hud_object_complexity.texturesCount += textures.size();
|
|
|
|
for (LLVOVolume::texture_cost_t::iterator volume_texture = textures.begin();
|
|
volume_texture != textures.end();
|
|
++volume_texture)
|
|
{
|
|
// add the cost of each individual texture (ignores duplicates)
|
|
hud_object_complexity.texturesCost += volume_texture->second;
|
|
LLViewerFetchedTexture *tex = LLViewerTextureManager::getFetchedTexture(volume_texture->first);
|
|
if (tex)
|
|
{
|
|
// Note: Texture memory might be incorect since texture might be still loading.
|
|
hud_object_complexity.texturesMemoryTotal += tex->getTextureMemory();
|
|
if (tex->getOriginalHeight() * tex->getOriginalWidth() >= HUD_OVERSIZED_TEXTURE_DATA_SIZE)
|
|
{
|
|
hud_object_complexity.largeTexturesCount++;
|
|
}
|
|
}
|
|
}
|
|
hud_complexity_list.push_back(hud_object_complexity);
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
// Calculations for mVisualComplexity value
|
|
void LLVOAvatar::calculateUpdateRenderComplexity()
|
|
{
|
|
/*****************************************************************
|
|
* This calculation should not be modified by third party viewers,
|
|
* since it is used to limit rendering and should be uniform for
|
|
* everyone. If you have suggested improvements, submit them to
|
|
* the official viewer for consideration.
|
|
*****************************************************************/
|
|
static const U32 COMPLEXITY_BODY_PART_COST = 200;
|
|
static LLCachedControl<F32> max_complexity_setting(gSavedSettings,"MaxAttachmentComplexity");
|
|
F32 max_attachment_complexity = max_complexity_setting;
|
|
max_attachment_complexity = llmax(max_attachment_complexity, DEFAULT_MAX_ATTACHMENT_COMPLEXITY);
|
|
|
|
// Diagnostic list of all textures on our avatar
|
|
static uuid_set_t all_textures;
|
|
|
|
if (mVisualComplexityStale)
|
|
{
|
|
U32 cost = VISUAL_COMPLEXITY_UNKNOWN;
|
|
LLVOVolume::texture_cost_t textures;
|
|
//hud_complexity_list_t hud_complexity_list;
|
|
|
|
for (U8 baked_index = 0; baked_index < BAKED_NUM_INDICES; baked_index++)
|
|
{
|
|
const LLAvatarAppearanceDictionary::BakedEntry *baked_dict
|
|
= LLAvatarAppearanceDictionary::getInstance()->getBakedTexture((EBakedTextureIndex)baked_index);
|
|
ETextureIndex tex_index = baked_dict->mTextureIndex;
|
|
if ((tex_index != TEX_SKIRT_BAKED) || (isWearingWearableType(LLWearableType::WT_SKIRT)))
|
|
{
|
|
if (isTextureVisible(tex_index))
|
|
{
|
|
cost +=COMPLEXITY_BODY_PART_COST;
|
|
}
|
|
}
|
|
}
|
|
LL_DEBUGS("ARCdetail") << "Avatar body parts complexity: " << cost << LL_ENDL;
|
|
|
|
mAttachmentVisibleTriangleCount = 0;
|
|
mAttachmentEstTriangleCount = 0.f;
|
|
mAttachmentSurfaceArea = 0.f;
|
|
|
|
// A standalone animated object needs to be accounted for
|
|
// using its associated volume. Attached animated objects
|
|
// will be covered by the subsequent loop over attachments.
|
|
LLControlAvatar *control_av = asControlAvatar();
|
|
if (control_av)
|
|
{
|
|
LLVOVolume *volp = control_av->mRootVolp;
|
|
if (volp && !volp->isAttachment())
|
|
{
|
|
accountRenderComplexityForObject(volp, max_attachment_complexity,
|
|
textures, cost/*, hud_complexity_list*/);
|
|
}
|
|
}
|
|
|
|
// Account for complexity of all attachments.
|
|
#if SLOW_ATTACHMENT_LIST
|
|
for (attachment_map_t::const_iterator attachment_point = mAttachmentPoints.begin();
|
|
attachment_point != mAttachmentPoints.end();
|
|
++attachment_point)
|
|
{
|
|
LLViewerJointAttachment* attachment = attachment_point->second;
|
|
for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin();
|
|
attachment_iter != attachment->mAttachedObjects.end();
|
|
++attachment_iter)
|
|
{
|
|
const LLViewerObject* attached_object = (*attachment_iter);
|
|
#else
|
|
for(auto& iter : mAttachedObjectsVector)
|
|
{{
|
|
const LLViewerObject* attached_object = iter.first;
|
|
#endif
|
|
accountRenderComplexityForObject(attached_object, max_attachment_complexity,
|
|
textures, cost/*, hud_complexity_list*/);
|
|
}
|
|
}
|
|
|
|
// Diagnostic output to identify all avatar-related textures.
|
|
// Does not affect rendering cost calculation.
|
|
// Could be wrapped in a debug option if output becomes problematic.
|
|
if (isSelf())
|
|
{
|
|
// print any attachment textures we didn't already know about.
|
|
for (LLVOVolume::texture_cost_t::iterator it = textures.begin(); it != textures.end(); ++it)
|
|
{
|
|
LLUUID image_id = it->first;
|
|
if( ! (image_id.isNull() || image_id == IMG_DEFAULT || image_id == IMG_DEFAULT_AVATAR)
|
|
&& (all_textures.find(image_id) == all_textures.end()))
|
|
{
|
|
// attachment texture not previously seen.
|
|
LL_DEBUGS("ARCdetail") << "attachment_texture: " << image_id.asString() << LL_ENDL;
|
|
all_textures.insert(image_id);
|
|
}
|
|
}
|
|
|
|
// print any avatar textures we didn't already know about
|
|
for (LLAvatarAppearanceDictionary::Textures::const_iterator iter = LLAvatarAppearanceDictionary::getInstance()->getTextures().begin();
|
|
iter != LLAvatarAppearanceDictionary::getInstance()->getTextures().end();
|
|
++iter)
|
|
{
|
|
const LLAvatarAppearanceDictionary::TextureEntry *texture_dict = iter->second;
|
|
// TODO: MULTI-WEARABLE: handle multiple textures for self
|
|
const LLViewerTexture* te_image = getImage(iter->first,0);
|
|
if (!te_image)
|
|
continue;
|
|
LLUUID image_id = te_image->getID();
|
|
if( image_id.isNull() || image_id == IMG_DEFAULT || image_id == IMG_DEFAULT_AVATAR)
|
|
continue;
|
|
if (all_textures.find(image_id) == all_textures.end())
|
|
{
|
|
LL_DEBUGS("ARCdetail") << "local_texture: " << texture_dict->mName << ": " << image_id << LL_ENDL;
|
|
all_textures.insert(image_id);
|
|
}
|
|
}
|
|
}
|
|
|
|
mVisualComplexity = cost;
|
|
mVisualComplexityStale = false;
|
|
}
|
|
|
|
}
|
|
|
|
// static
|
|
BOOL LLVOAvatar::isIndexLocalTexture(ETextureIndex index)
|
|
{
|
|
return (index < 0 || index >= TEX_NUM_INDICES)
|
|
? false
|
|
: LLAvatarAppearanceDictionary::getInstance()->getTexture(index)->mIsLocalTexture;
|
|
}
|
|
|
|
// static
|
|
BOOL LLVOAvatar::isIndexBakedTexture(ETextureIndex index)
|
|
{
|
|
return (index < 0 || index >= TEX_NUM_INDICES)
|
|
? false
|
|
: LLAvatarAppearanceDictionary::getInstance()->getTexture(index)->mIsBakedTexture;
|
|
}
|
|
|
|
const std::string LLVOAvatar::getBakedStatusForPrintout() const
|
|
{
|
|
std::string line;
|
|
|
|
for (LLAvatarAppearanceDictionary::Textures::const_iterator iter = LLAvatarAppearanceDictionary::getInstance()->getTextures().begin();
|
|
iter != LLAvatarAppearanceDictionary::getInstance()->getTextures().end();
|
|
++iter)
|
|
{
|
|
const ETextureIndex index = iter->first;
|
|
const LLAvatarAppearanceDictionary::TextureEntry *texture_dict = iter->second;
|
|
if (texture_dict->mIsBakedTexture)
|
|
{
|
|
line += texture_dict->mName;
|
|
if (isTextureDefined(index))
|
|
{
|
|
line += "_baked";
|
|
}
|
|
line += " ";
|
|
}
|
|
}
|
|
return line;
|
|
}
|
|
|
|
|
|
|
|
//virtual
|
|
S32 LLVOAvatar::getTexImageSize() const
|
|
{
|
|
return TEX_IMAGE_SIZE_OTHER;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Utility functions
|
|
//-----------------------------------------------------------------------------
|
|
|
|
F32 calc_bouncy_animation(F32 x)
|
|
{
|
|
return -(cosf(x * F_PI * 2.5f - F_PI_BY_TWO))*(0.4f + x * -0.1f) + x * 1.3f;
|
|
}
|
|
|
|
//virtual
|
|
BOOL LLVOAvatar::isTextureDefined(LLAvatarAppearanceDefines::ETextureIndex te, U32 index ) const
|
|
{
|
|
if (isIndexLocalTexture(te))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if( !getImage( te, index ) )
|
|
{
|
|
LL_WARNS() << "getImage( " << te << ", " << index << " ) returned 0" << LL_ENDL;
|
|
return FALSE;
|
|
}
|
|
|
|
return (getImage(te, index)->getID() != IMG_DEFAULT_AVATAR &&
|
|
getImage(te, index)->getID() != IMG_DEFAULT);
|
|
}
|
|
|
|
//virtual
|
|
BOOL LLVOAvatar::isTextureVisible(LLAvatarAppearanceDefines::ETextureIndex type, U32 index) const
|
|
{
|
|
if (isIndexLocalTexture(type))
|
|
{
|
|
return isTextureDefined(type, index);
|
|
}
|
|
else
|
|
{
|
|
// baked textures can use TE images directly
|
|
return ((isTextureDefined(type) || isSelf())
|
|
&& (getTEImage(type)->getID() != IMG_INVISIBLE
|
|
|| LLDrawPoolAlpha::sShowDebugAlpha));
|
|
}
|
|
}
|
|
|
|
//virtual
|
|
BOOL LLVOAvatar::isTextureVisible(LLAvatarAppearanceDefines::ETextureIndex type, LLViewerWearable *wearable) const
|
|
{
|
|
// non-self avatars don't have wearables
|
|
return FALSE;
|
|
}
|
|
|