diff options
author | James Cook <james@lindenlab.com> | 2007-01-02 08:33:20 +0000 |
---|---|---|
committer | James Cook <james@lindenlab.com> | 2007-01-02 08:33:20 +0000 |
commit | 420b91db29485df39fd6e724e782c449158811cb (patch) | |
tree | b471a94563af914d3ed3edd3e856d21cb1b69945 /indra/newview/llagent.cpp |
Print done when done.
Diffstat (limited to 'indra/newview/llagent.cpp')
-rw-r--r-- | indra/newview/llagent.cpp | 7233 |
1 files changed, 7233 insertions, 0 deletions
diff --git a/indra/newview/llagent.cpp b/indra/newview/llagent.cpp new file mode 100644 index 0000000000..e46800fff1 --- /dev/null +++ b/indra/newview/llagent.cpp @@ -0,0 +1,7233 @@ +/** + * @file llagent.cpp + * @brief LLAgent class implementation + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "stdtypes.h" +#include "stdenums.h" + +#include "llagent.h" + +#include "llcoordframe.h" +#include "indra_constants.h" +#include "llmath.h" +#include "llcriticaldamp.h" +#include "llfocusmgr.h" +#include "llglheaders.h" +#include "llparcel.h" +#include "llpermissions.h" +#include "llregionhandle.h" +#include "m3math.h" +#include "m4math.h" +#include "message.h" +#include "llquaternion.h" +#include "v3math.h" +#include "v4math.h" +//#include "vmath.h" + +#include "imageids.h" +#include "llbox.h" +#include "llbutton.h" +#include "llcameraview.h" +#include "llcallingcard.h" +#include "llchatbar.h" +#include "llconsole.h" +#include "lldrawable.h" +#include "llface.h" +#include "llfirstuse.h" +#include "llfloater.h" +#include "llfloateravatarinfo.h" +#include "llfloaterbuildoptions.h" +#include "llfloaterchat.h" +#include "llfloatercustomize.h" +#include "llfloaterdirectory.h" +#include "llfloatergroupinfo.h" +#include "llfloatergroups.h" +#include "llfloatermap.h" +#include "llfloatermute.h" +#include "llfloatersnapshot.h" +#include "llfloatertools.h" +#include "llfloaterworldmap.h" +#include "llgroupmgr.h" +#include "llhudeffectlookat.h" +#include "llhudmanager.h" +#include "llinventorymodel.h" +#include "llinventoryview.h" +#include "lljoystickbutton.h" +#include "llmenugl.h" +#include "llmorphview.h" +#include "llmoveview.h" +#include "llnotify.h" +#include "llquantize.h" +#include "llselectmgr.h" +#include "llsky.h" +#include "llsphere.h" +#include "llstatusbar.h" +#include "llimview.h" +#include "lltool.h" +#include "lltoolcomp.h" // for gToolGun +#include "lltoolfocus.h" +#include "lltoolgrab.h" +#include "lltoolmgr.h" +#include "lltoolpie.h" +#include "lltoolview.h" +#include "llui.h" // for make_ui_sound +#include "llviewercamera.h" +#include "llviewerinventory.h" +#include "llviewermenu.h" +#include "llviewernetwork.h" +#include "llviewerobjectlist.h" +#include "llviewerparcelmgr.h" +#include "llviewerparceloverlay.h" +#include "llviewerregion.h" +#include "llviewerstats.h" +#include "llviewerwindow.h" +#include "llvoavatar.h" +#include "llvoground.h" +#include "llvosky.h" +#include "llwearable.h" +#include "llwearablelist.h" +#include "llworld.h" +#include "llworldmap.h" +#include "pipeline.h" +#include "roles_constants.h" +#include "viewer.h" + +// Ventrella +#include "llfollowcam.h" +// end Ventrella + +extern LLMenuBarGL* gMenuBarView; +extern F32 gMinObjectDistance; +extern U8 gLastPickAlpha; +extern F32 gFrameDTClamped; + +//drone wandering constants +const F32 MAX_WANDER_TIME = 20.f; // seconds +const F32 MAX_HEADING_HALF_ERROR = 0.2f; // radians +const F32 WANDER_MAX_SLEW_RATE = 2.f * DEG_TO_RAD; // radians / frame +const F32 WANDER_TARGET_MIN_DISTANCE = 10.f; // meters + +// Autopilot constants +const F32 AUTOPILOT_HEADING_HALF_ERROR = 10.f * DEG_TO_RAD; // radians +const F32 AUTOPILOT_MAX_SLEW_RATE = 1.f * DEG_TO_RAD; // radians / frame +const F32 AUTOPILOT_STOP_DISTANCE = 2.f; // meters +const F32 AUTOPILOT_HEIGHT_ADJUST_DISTANCE = 8.f; // meters +const F32 AUTOPILOT_MIN_TARGET_HEIGHT_OFF_GROUND = 1.f; // meters +const F32 AUTOPILOT_MAX_TIME_NO_PROGRESS = 1.5f; // seconds + +// face editing constants +const LLVector3d FACE_EDIT_CAMERA_OFFSET(0.4f, -0.05f, 0.07f); +const LLVector3d FACE_EDIT_TARGET_OFFSET(0.f, 0.f, 0.05f); + +// Mousewheel camera zoom +const F32 MIN_ZOOM_FRACTION = 0.25f; +const F32 INITIAL_ZOOM_FRACTION = 1.f; +const F32 MAX_ZOOM_FRACTION = 8.f; +const F32 METERS_PER_WHEEL_CLICK = 1.f; + +const F32 MAX_TIME_DELTA = 1.f; + +const F32 CAMERA_ZOOM_HALF_LIFE = 0.07f; // seconds +const F32 FOV_ZOOM_HALF_LIFE = 0.07f; // seconds + +const F32 CAMERA_FOCUS_HALF_LIFE = 0.f;//0.02f; +const F32 CAMERA_LAG_HALF_LIFE = 0.25f; +const F32 MIN_CAMERA_LAG = 0.5f; +const F32 MAX_CAMERA_LAG = 5.f; + +const F32 CAMERA_COLLIDE_EPSILON = 0.1f; +const F32 MIN_CAMERA_DISTANCE = 0.1f; +const F32 AVATAR_ZOOM_MIN_X_FACTOR = 0.55f; +const F32 AVATAR_ZOOM_MIN_Y_FACTOR = 0.7f; +const F32 AVATAR_ZOOM_MIN_Z_FACTOR = 1.15f; + +const F32 MAX_CAMERA_DISTANCE_FROM_AGENT = 50.f; + +const F32 HEAD_BUFFER_SIZE = 0.3f; +const F32 CUSTOMIZE_AVATAR_CAMERA_ANIM_SLOP = 0.2f; + +const F32 LAND_MIN_ZOOM = 0.15f; +const F32 AVATAR_MIN_ZOOM = 0.5f; +const F32 OBJECT_MIN_ZOOM = 0.02f; + +const F32 APPEARANCE_MIN_ZOOM = 0.39f; +const F32 APPEARANCE_MAX_ZOOM = 8.f; + +// fidget constants +const F32 MIN_FIDGET_TIME = 8.f; // seconds +const F32 MAX_FIDGET_TIME = 20.f; // seconds + +const S32 MAX_NUM_CHAT_POSITIONS = 10; +const F32 GROUND_TO_AIR_CAMERA_TRANSITION_TIME = 0.5f; +const F32 GROUND_TO_AIR_CAMERA_TRANSITION_START_TIME = 0.5f; + +const F32 MAX_VELOCITY_AUTO_LAND_SQUARED = 4.f * 4.f; + +const F32 MAX_FOCUS_OFFSET = 20.f; + +const F32 OBJECT_EXTENTS_PADDING = 0.5f; + +const F32 MIN_RADIUS_ALPHA_SIZZLE = 0.5f; + +const F64 CHAT_AGE_FAST_RATE = 3.0; + +const S32 MAX_WEARABLES_PER_LAYERSET = 7; + +const EWearableType WEARABLE_BAKE_TEXTURE_MAP[BAKED_TEXTURE_COUNT][MAX_WEARABLES_PER_LAYERSET] = +{ + { WT_SHAPE, WT_SKIN, WT_HAIR, WT_INVALID, WT_INVALID, WT_INVALID, WT_INVALID }, // TEX_HEAD_BAKED + { WT_SHAPE, WT_SKIN, WT_SHIRT, WT_JACKET, WT_GLOVES, WT_UNDERSHIRT, WT_INVALID }, // TEX_UPPER_BAKED + { WT_SHAPE, WT_SKIN, WT_PANTS, WT_SHOES, WT_SOCKS, WT_JACKET, WT_UNDERPANTS }, // TEX_LOWER_BAKED + { WT_EYES, WT_INVALID, WT_INVALID, WT_INVALID, WT_INVALID, WT_INVALID, WT_INVALID }, // TEX_EYES_BAKED + { WT_SKIRT, WT_INVALID, WT_INVALID, WT_INVALID, WT_INVALID, WT_INVALID, WT_INVALID } // TEX_SKIRT_BAKED +}; + +const LLUUID BAKED_TEXTURE_HASH[BAKED_TEXTURE_COUNT] = +{ + LLUUID("18ded8d6-bcfc-e415-8539-944c0f5ea7a6"), + LLUUID("338c29e3-3024-4dbb-998d-7c04cf4fa88f"), + LLUUID("91b4a2c7-1b1a-ba16-9a16-1f8f8dcc1c3f"), + LLUUID("b2cf28af-b840-1071-3c6a-78085d8128b5"), + LLUUID("ea800387-ea1a-14e0-56cb-24f2022f969a") +}; + +// +// Statics +// +BOOL LLAgent::sDebugDisplayTarget = FALSE; + +const F32 LLAgent::TYPING_TIMEOUT_SECS = 5.f; + +class LLAgentFriendObserver : public LLFriendObserver +{ +public: + LLAgentFriendObserver() {} + virtual ~LLAgentFriendObserver() {} + virtual void changed(U32 mask); +}; + +void LLAgentFriendObserver::changed(U32 mask) +{ + // if there's a change we're interested in. + if((mask & (LLFriendObserver::POWERS)) != 0) + { + gAgent.friendsChanged(); + } +} + +// ************************************************************ +// Enabled this definition to compile a 'hacked' viewer that +// locally believes the end user has godlike powers. +// #define HACKED_GODLIKE_VIEWER +// For a toggled version, see viewer.h for the +// TOGGLE_HACKED_GODLIKE_VIEWER define, instead. +// ************************************************************ + +// Constructors and Destructors + +// JC - Please try to make this order match the order in the header +// file. Otherwise it's hard to find variables that aren't initialized. +//----------------------------------------------------------------------------- +// LLAgent() +//----------------------------------------------------------------------------- +LLAgent::LLAgent() +: mViewerPort(NET_USE_OS_ASSIGNED_PORT), + mDrawDistance( DEFAULT_FAR_PLANE ), + mAccess(SIM_ACCESS_PG), + mGroupPowers(0), + mGroupID(), + //mGroupInsigniaID(), + mMapOriginX(0), + mMapOriginY(0), + mMapWidth(0), + mMapHeight(0), + mLookAt(NULL), + mPointAt(NULL), + mInitialized(FALSE), + mNumPendingQueries(0), + mForceMouselook(FALSE), + mTeleportState( TELEPORT_NONE ), + mRegionp(NULL), + + mAgentOriginGlobal(), + mPositionGlobal(), + + mDistanceTraveled(0), + mLastPositionGlobal(LLVector3d::zero), + + mAvatarObject(NULL), + + mRenderState(0), + mTypingTimer(), + + mCameraMode( CAMERA_MODE_THIRD_PERSON ), + mLastCameraMode( CAMERA_MODE_THIRD_PERSON ), + mViewsPushed(FALSE), + + mbAlwaysRun(FALSE), + mShowAvatar(TRUE), + + mCameraAnimating( FALSE ), + mAnimationCameraStartGlobal(), + mAnimationFocusStartGlobal(), + mAnimationTimer(), + mAnimationDuration(0.33f), + mCameraFOVZoomFactor(0.f), + mCameraCurrentFOVZoomFactor(0.f), + mCameraFocusOffset(), + mCameraOffsetDefault(), +// mCameraOffsetNorm(), + mCameraCollidePlane(), + mCurrentCameraDistance(2.f), // meters, set in init() + mTargetCameraDistance(2.f), + mCameraZoomFraction(1.f), // deprecated + mThirdPersonHeadOffset(0.f, 0.f, 1.f), + mSitCameraEnabled(FALSE), + mFocusOnAvatar(TRUE), + mFocusGlobal(), + mFocusTargetGlobal(), + mFocusObject(NULL), + mFocusObjectOffset(), + mFocusDotRadius( 0.1f ), // meters + mTrackFocusObject(TRUE), + + mFrameAgent(), + + mCrouching(FALSE), + mIsBusy(FALSE), + + // movement keys below + + mControlFlags(0x00000000), + mbFlagsDirty(FALSE), + mbFlagsNeedReset(FALSE), + + mbJump(FALSE), + + mWanderTimer(), + mWanderTargetGlobal( LLVector3d::zero ), + + mAutoPilot(FALSE), + mAutoPilotFlyOnStop(FALSE), + mAutoPilotTargetGlobal(), + mAutoPilotStopDistance(1.f), + mAutoPilotUseRotation(FALSE), + mAutoPilotTargetFacing(LLVector3::zero), + mAutoPilotTargetDist(0.f), + mAutoPilotFinishedCallback(NULL), + mAutoPilotCallbackData(NULL), + + + mEffectColor(0.f, 1.f, 1.f, 1.f), + mHaveHomePosition(FALSE), + mHomeRegionHandle( 0 ), + mNearChatRadius(10.f), + mGodLevel( GOD_NOT ), + + + mNextFidgetTime(0.f), + mCurrentFidget(0), + mFirstLogin(FALSE), + mGenderChosen(FALSE), + mAgentWearablesUpdateSerialNum(0), + mWearablesLoaded(FALSE), + mTextureCacheQueryID(0), + mAppearanceSerialNum(0) +{ + + U32 i; + for (i = 0; i < TOTAL_CONTROLS; i++) + { + mControlsTakenCount[i] = 0; + mControlsTakenPassedOnCount[i] = 0; + } + + // Initialize movement keys + mAtKey = 0; // Either 1, 0, or -1... indicates that movement-key is pressed + mWalkKey = 0; // like AtKey, but causes less forward thrust + mLeftKey = 0; + mUpKey = 0; + mYawKey = 0.f; + mPitchKey = 0; + + mOrbitLeftKey = 0.f; + mOrbitRightKey = 0.f; + mOrbitUpKey = 0.f; + mOrbitDownKey = 0.f; + mOrbitInKey = 0.f; + mOrbitOutKey = 0.f; + + mPanUpKey = 0.f; + mPanDownKey = 0.f; + mPanLeftKey = 0.f; + mPanRightKey = 0.f; + mPanInKey = 0.f; + mPanOutKey = 0.f; + + mActiveCacheQueries = new S32[BAKED_TEXTURE_COUNT]; + for (i = 0; i < (U32)BAKED_TEXTURE_COUNT; i++) + { + mActiveCacheQueries[i] = 0; + } + + //Ventrella + mCameraUpVector = LLVector3::z_axis;// default is straight up + mFollowCam.setMaxCameraDistantFromSubject( MAX_CAMERA_DISTANCE_FROM_AGENT ); + //end ventrella +} + +// Requires gSavedSettings to be initialized. +//----------------------------------------------------------------------------- +// init() +//----------------------------------------------------------------------------- +void LLAgent::init() +{ + mDrawDistance = gSavedSettings.getF32("RenderFarClip"); + + gCamera = new LLViewerCamera(); + + gCamera->setView(DEFAULT_FIELD_OF_VIEW); + // Leave at 0.1 meters until we have real near clip management + gCamera->setNear(0.1f); + gCamera->setFar(mDrawDistance); // if you want to change camera settings, do so in camera.h + gCamera->setAspect( gViewerWindow->getDisplayAspectRatio() ); // default, overridden in LLViewerWindow::reshape + gCamera->setViewHeightInPixels(768); // default, overridden in LLViewerWindow::reshape + + setFlying( gSavedSettings.getBOOL("FlyingAtExit") ); + + mCameraFocusOffsetTarget = LLVector4(gSavedSettings.getVector3("CameraOffsetBuild")); + mCameraOffsetDefault = gSavedSettings.getVector3("CameraOffsetDefault"); +// mCameraOffsetNorm = mCameraOffsetDefault; +// mCameraOffsetNorm.normVec(); + mCameraCollidePlane.clearVec(); + mCurrentCameraDistance = mCameraOffsetDefault.magVec(); + mTargetCameraDistance = mCurrentCameraDistance; + mCameraZoomFraction = 1.f; + mTrackFocusObject = gSavedSettings.getBOOL("TrackFocusObject"); + +// LLDebugVarMessageBox::show("Camera Lag", &CAMERA_FOCUS_HALF_LIFE, 0.5f, 0.01f); + gSavedSettings.getControl("RenderHideGroupTitle")->addListener(&mHideGroupTitleListener); + gSavedSettings.getControl("EffectColor")->addListener(&mEffectColorListener); + + mEffectColor = gSavedSettings.getColor4("EffectColor"); + + mInitialized = TRUE; +} + +//----------------------------------------------------------------------------- +// cleanup() +//----------------------------------------------------------------------------- +void LLAgent::cleanup() +{ + setSitCamera(LLUUID::null); + mAvatarObject = NULL; + mLookAt = NULL; + mPointAt = NULL; + mRegionp = NULL; + setFocusObject(NULL); + mFadeObjects.clear(); +} + +//----------------------------------------------------------------------------- +// LLAgent() +//----------------------------------------------------------------------------- +LLAgent::~LLAgent() +{ + cleanup(); + + delete [] mActiveCacheQueries; + mActiveCacheQueries = NULL; + + delete gCamera; + gCamera = NULL; +} + +// Change camera back to third person, stop the autopilot, +// deselect stuff, etc. +//----------------------------------------------------------------------------- +// resetView() +//----------------------------------------------------------------------------- +void LLAgent::resetView(BOOL reset_camera) +{ + if (mAutoPilot) + { + stopAutoPilot(TRUE); + } + + if (!gNoRender) + { + gSelectMgr->deselectAll(); + gSelectMgr->unhighlightAll(); + + // By popular request, keep land selection while walking around. JC + // gParcelMgr->deselectLand(); + + // Hide all popup menus + gPieSelf->hide(FALSE); + gPieAvatar->hide(FALSE); + gPieObject->hide(FALSE); + gPieLand->hide(FALSE); + } + + if (reset_camera && !gSavedSettings.getBOOL("FreezeTime")) + { + if (!gViewerWindow->getLeftMouseDown() && cameraThirdPerson()) + { + // leaving mouse-steer mode + LLVector3 agent_at_axis = getAtAxis(); + agent_at_axis -= projected_vec(agent_at_axis, getReferenceUpVector()); + agent_at_axis.normVec(); + gAgent.resetAxes(lerp(getAtAxis(), agent_at_axis, LLCriticalDamp::getInterpolant(0.3f))); + } + + setFocusOnAvatar(TRUE, ANIMATE); + } + + if (mAvatarObject.notNull()) + { + mAvatarObject->mHUDTargetZoom = 1.f; + } +} + +void LLAgent::ageChat() +{ + if (mAvatarObject) + { + // get amount of time since I last chatted + F64 elapsed_time = (F64)mAvatarObject->mChatTimer.getElapsedTimeF32(); + // add in frame time * 3 (so it ages 4x) + mAvatarObject->mChatTimer.setAge(elapsed_time + (F64)gFrameDTClamped * (CHAT_AGE_FAST_RATE - 1.0)); + } +} + +// Allow camera to be moved somewhere other than behind avatar. +//----------------------------------------------------------------------------- +// unlockView() +//----------------------------------------------------------------------------- +void LLAgent::unlockView() +{ + if (getFocusOnAvatar()) + { + if (mAvatarObject) + { + setFocusGlobal( LLVector3d::zero, mAvatarObject->mID ); + } + setFocusOnAvatar(FALSE, FALSE); // no animation + } +} + + +//----------------------------------------------------------------------------- +// moveAt() +//----------------------------------------------------------------------------- +void LLAgent::moveAt(S32 direction) +{ + // age chat timer so it fades more quickly when you are intentionally moving + ageChat(); + + setKey(direction, mAtKey); + + if (direction > 0) + { + setControlFlags(AGENT_CONTROL_AT_POS | AGENT_CONTROL_FAST_AT); + } + else if (direction < 0) + { + setControlFlags(AGENT_CONTROL_AT_NEG | AGENT_CONTROL_FAST_AT); + } + + resetView(); +} + +//----------------------------------------------------------------------------- +// moveAtNudge() +//----------------------------------------------------------------------------- +void LLAgent::moveAtNudge(S32 direction) +{ + // age chat timer so it fades more quickly when you are intentionally moving + ageChat(); + + setKey(direction, mWalkKey); + + if (direction > 0) + { + setControlFlags(AGENT_CONTROL_NUDGE_AT_POS); + } + else if (direction < 0) + { + setControlFlags(AGENT_CONTROL_NUDGE_AT_NEG); + } + + resetView(); +} + +//----------------------------------------------------------------------------- +// moveLeft() +//----------------------------------------------------------------------------- +void LLAgent::moveLeft(S32 direction) +{ + // age chat timer so it fades more quickly when you are intentionally moving + ageChat(); + + setKey(direction, mLeftKey); + + if (direction > 0) + { + setControlFlags(AGENT_CONTROL_LEFT_POS | AGENT_CONTROL_FAST_LEFT); + } + else if (direction < 0) + { + setControlFlags(AGENT_CONTROL_LEFT_NEG | AGENT_CONTROL_FAST_LEFT); + } + + resetView(); +} + +//----------------------------------------------------------------------------- +// moveLeftNudge() +//----------------------------------------------------------------------------- +void LLAgent::moveLeftNudge(S32 direction) +{ + // age chat timer so it fades more quickly when you are intentionally moving + ageChat(); + + setKey(direction, mLeftKey); + + if (direction > 0) + { + setControlFlags(AGENT_CONTROL_NUDGE_LEFT_POS); + } + else if (direction < 0) + { + setControlFlags(AGENT_CONTROL_NUDGE_LEFT_NEG); + } + + resetView(); +} + +//----------------------------------------------------------------------------- +// moveUp() +//----------------------------------------------------------------------------- +void LLAgent::moveUp(S32 direction) +{ + // age chat timer so it fades more quickly when you are intentionally moving + ageChat(); + + setKey(direction, mUpKey); + + if (direction > 0) + { + setControlFlags(AGENT_CONTROL_UP_POS | AGENT_CONTROL_FAST_UP); + } + else if (direction < 0) + { + setControlFlags(AGENT_CONTROL_UP_NEG | AGENT_CONTROL_FAST_UP); + } + + resetView(); +} + +//----------------------------------------------------------------------------- +// moveYaw() +//----------------------------------------------------------------------------- +void LLAgent::moveYaw(F32 mag) +{ + mYawKey = mag; + + if (mag > 0) + { + setControlFlags(AGENT_CONTROL_YAW_POS); + } + else if (mag < 0) + { + setControlFlags(AGENT_CONTROL_YAW_NEG); + } + + resetView(); +} + +//----------------------------------------------------------------------------- +// movePitch() +//----------------------------------------------------------------------------- +void LLAgent::movePitch(S32 direction) +{ + setKey(direction, mPitchKey); + + if (direction > 0) + { + setControlFlags(AGENT_CONTROL_PITCH_POS ); + } + else if (direction < 0) + { + setControlFlags(AGENT_CONTROL_PITCH_NEG); + } +} + + +// Does this parcel allow you to fly? +BOOL LLAgent::canFly() +{ + if (isGodlike()) return TRUE; + + if (!gParcelMgr) return FALSE; + + LLViewerRegion* regionp = getRegion(); + if (regionp && regionp->getBlockFly()) return FALSE; + + LLParcel* parcel = gParcelMgr->getAgentParcel(); + if (!parcel) return FALSE; + + // Allow owners to fly on their own land. + if (LLViewerParcelMgr::isParcelOwnedByAgent(parcel, GP_LAND_ALLOW_FLY)) + { + return TRUE; + } + + return parcel->getAllowFly(); +} + + +//----------------------------------------------------------------------------- +// setFlying() +//----------------------------------------------------------------------------- +void LLAgent::setFlying(BOOL fly) +{ + if (mAvatarObject.notNull()) + { + if(mAvatarObject->mSignaledAnimations.find(ANIM_AGENT_STANDUP) != mAvatarObject->mSignaledAnimations.end()) + { + return; + } + + // don't allow taking off while sitting + if (fly && mAvatarObject->mIsSitting) + { + return; + } + } + + if (fly) + { + BOOL was_flying = getFlying(); + if (gParcelMgr) + { + if (!canFly() && !was_flying) + { + // parcel doesn't let you start fly + // gods can always fly + // and it's OK if you're already flying + make_ui_sound("UISndBadKeystroke"); + return; + } + } + if( !was_flying ) + { + gViewerStats->incStat(LLViewerStats::ST_FLY_COUNT); + } + setControlFlags(AGENT_CONTROL_FLY); + gSavedSettings.setBOOL("FlyBtnState", TRUE); + } + else + { + clearControlFlags(AGENT_CONTROL_FLY); + gSavedSettings.setBOOL("FlyBtnState", FALSE); + } + mbFlagsDirty = TRUE; +} + + +// UI based mechanism of setting fly state +//----------------------------------------------------------------------------- +// toggleFlying() +//----------------------------------------------------------------------------- +void LLAgent::toggleFlying() +{ + BOOL fly = !(mControlFlags & AGENT_CONTROL_FLY); + + setFlying( fly ); + resetView(); +} + + +//----------------------------------------------------------------------------- +// setRegion() +//----------------------------------------------------------------------------- +void LLAgent::setRegion(LLViewerRegion *regionp) +{ + llassert(regionp); + if (mRegionp != regionp) + { + // JC - Avoid this, causes out-of-bounds array write deep within + // Windows. + // char host_name[MAX_STRING]; + // regionp->getHost().getHostName(host_name, MAX_STRING); + + char ip[MAX_STRING]; + regionp->getHost().getString(ip, MAX_STRING); + llinfos << "Moving agent into region: " << regionp->getName() + << " located at " << ip << llendl; + if (mRegionp) + { + // We've changed regions, we're now going to change our agent coordinate frame. + mAgentOriginGlobal = regionp->getOriginGlobal(); + LLVector3d agent_offset_global = mRegionp->getOriginGlobal(); + + LLVector3 delta; + delta.setVec(regionp->getOriginGlobal() - mRegionp->getOriginGlobal()); + + setPositionAgent(getPositionAgent() - delta); + LLVector3 camera_position_agent = gCamera->getOrigin(); + gCamera->setOrigin(camera_position_agent - delta); + + // Update all of the regions. + gWorldPointer->updateAgentOffset(agent_offset_global); + + // Hack to keep sky in the agent's region, otherwise it may get deleted - DJS 08/02/02 + if (gSky.mVOSkyp) + { + gSky.mVOSkyp->setRegion(regionp); + } + if (gSky.mVOStarsp) + { + gSky.mVOStarsp->setRegion(regionp); + } + if (gSky.mVOGroundp) + { + gSky.mVOGroundp->setRegion(regionp); + } + + } + else + { + // First time initialization. + // We've changed regions, we're now going to change our agent coordinate frame. + mAgentOriginGlobal = regionp->getOriginGlobal(); + + LLVector3 delta; + delta.setVec(regionp->getOriginGlobal()); + + setPositionAgent(getPositionAgent() - delta); + LLVector3 camera_position_agent = gCamera->getOrigin(); + gCamera->setOrigin(camera_position_agent - delta); + + // Update all of the regions. + gWorldPointer->updateAgentOffset(mAgentOriginGlobal); + } + } + mRegionp = regionp; + + // Must shift hole-covering water object locations because local + // coordinate frame changed. + gWorldPointer->updateWaterObjects(); + + // keep a list of regions we've been too + // this is just an interesting stat, logged at the dataserver + // we could trake this at the dataserver side, but that's harder + U64 handle = regionp->getHandle(); + mRegionsVisited.insert(handle); +} + + +//----------------------------------------------------------------------------- +// getRegion() +//----------------------------------------------------------------------------- +LLViewerRegion *LLAgent::getRegion() const +{ + return mRegionp; +} + + +const LLHost& LLAgent::getRegionHost() const +{ + if (mRegionp) + { + return mRegionp->getHost(); + } + else + { + return LLHost::invalid; + } +} + + +//----------------------------------------------------------------------------- +// inPrelude() +//----------------------------------------------------------------------------- +BOOL LLAgent::inPrelude() +{ + return mRegionp && mRegionp->isPrelude(); +} + + +//----------------------------------------------------------------------------- +// canManageEstate() +//----------------------------------------------------------------------------- + +BOOL LLAgent::canManageEstate() const +{ + return mRegionp && mRegionp->canManageEstate(); +} +//----------------------------------------------------------------------------- +// sendMessage() +//----------------------------------------------------------------------------- +void LLAgent::sendMessage() +{ + if (gDisconnected) + { + llwarns << "Trying to send message when disconnected!" << llendl; + return; + } + if (!mRegionp) + { + llerrs << "No region for agent yet!" << llendl; + } + gMessageSystem->sendMessage(mRegionp->getHost()); +} + + +//----------------------------------------------------------------------------- +// sendReliableMessage() +//----------------------------------------------------------------------------- +void LLAgent::sendReliableMessage() +{ + if (gDisconnected) + { + llwarns << "Trying to send message when disconnected!" << llendl; + return; + } + if (!mRegionp) + { + llwarns << "LLAgent::sendReliableMessage No region for agent yet, not sending message!" << llendl; + return; + } + gMessageSystem->sendReliable(mRegionp->getHost()); +} + +//----------------------------------------------------------------------------- +// getVelocity() +//----------------------------------------------------------------------------- +LLVector3 LLAgent::getVelocity() const +{ + if (mAvatarObject) + { + return mAvatarObject->getVelocity(); + } + else + { + return LLVector3::zero; + } +} + + +//----------------------------------------------------------------------------- +// setPositionAgent() +//----------------------------------------------------------------------------- +void LLAgent::setPositionAgent(const LLVector3 &pos_agent) +{ + if (!pos_agent.isFinite()) + { + llerrs << "setPositionAgent is not a number" << llendl; + } + + if (!mAvatarObject.isNull() && mAvatarObject->getParent()) + { + LLVector3 pos_agent_sitting; + LLVector3d pos_agent_d; + LLViewerObject *parent = (LLViewerObject*)mAvatarObject->getParent(); + + pos_agent_sitting = mAvatarObject->getPosition() * parent->getRotation() + parent->getPositionAgent(); + pos_agent_d.setVec(pos_agent_sitting); + + mFrameAgent.setOrigin(pos_agent_sitting); + mPositionGlobal = pos_agent_d + mAgentOriginGlobal; + } + else + { + mFrameAgent.setOrigin(pos_agent); + + LLVector3d pos_agent_d; + pos_agent_d.setVec(pos_agent); + mPositionGlobal = pos_agent_d + mAgentOriginGlobal; + } +} + +//----------------------------------------------------------------------------- +// slamLookAt() +//----------------------------------------------------------------------------- +void LLAgent::slamLookAt(const LLVector3 &look_at) +{ + LLVector3 look_at_norm = look_at; + look_at_norm.mV[VZ] = 0.f; + look_at_norm.normVec(); + resetAxes(look_at_norm); +} + +//----------------------------------------------------------------------------- +// getPositionGlobal() +//----------------------------------------------------------------------------- +const LLVector3d &LLAgent::getPositionGlobal() +{ + if (!mAvatarObject.isNull() && !mAvatarObject->mDrawable.isNull()) + { + mPositionGlobal = getPosGlobalFromAgent(mAvatarObject->getRenderPosition()); + } + else + { + mPositionGlobal = getPosGlobalFromAgent(mFrameAgent.getOrigin()); + } + + return mPositionGlobal; +} + +//----------------------------------------------------------------------------- +// getPositionAgent() +//----------------------------------------------------------------------------- +const LLVector3 &LLAgent::getPositionAgent() +{ + if(!mAvatarObject.isNull() && !mAvatarObject->mDrawable.isNull()) + { + mFrameAgent.setOrigin(mAvatarObject->getRenderPosition()); + } + + return mFrameAgent.getOrigin(); +} + +//----------------------------------------------------------------------------- +// getRegionsVisited() +//----------------------------------------------------------------------------- +const S32 LLAgent::getRegionsVisited() const +{ + return mRegionsVisited.size(); +} + +//----------------------------------------------------------------------------- +// getDistanceTraveled() +//----------------------------------------------------------------------------- +const F64 LLAgent::getDistanceTraveled() const +{ + return mDistanceTraveled; +} + + +//----------------------------------------------------------------------------- +// getPosAgentFromGlobal() +//----------------------------------------------------------------------------- +LLVector3 LLAgent::getPosAgentFromGlobal(const LLVector3d &pos_global) const +{ + LLVector3 pos_agent; + pos_agent.setVec(pos_global - mAgentOriginGlobal); + return pos_agent; +} + + +//----------------------------------------------------------------------------- +// getPosGlobalFromAgent() +//----------------------------------------------------------------------------- +LLVector3d LLAgent::getPosGlobalFromAgent(const LLVector3 &pos_agent) const +{ + LLVector3d pos_agent_d; + pos_agent_d.setVec(pos_agent); + return pos_agent_d + mAgentOriginGlobal; +} + + +//----------------------------------------------------------------------------- +// resetAxes() +//----------------------------------------------------------------------------- +void LLAgent::resetAxes() +{ + mFrameAgent.resetAxes(); +} + + +// Copied from LLCamera::setOriginAndLookAt +// Look_at must be unit vector +//----------------------------------------------------------------------------- +// resetAxes() +//----------------------------------------------------------------------------- +void LLAgent::resetAxes(const LLVector3 &look_at) +{ + LLVector3 skyward = getReferenceUpVector(); + + // if look_at has zero length, fail + // if look_at and skyward are parallel, fail + // + // Test both of these conditions with a cross product. + LLVector3 cross(look_at % skyward); + if (cross.isNull()) + { + llinfos << "LLAgent::resetAxes cross-product is zero" << llendl; + return; + } + + // Make sure look_at and skyward are not parallel + // and neither are zero length + LLVector3 left(skyward % look_at); + LLVector3 up(look_at % left); + + mFrameAgent.setAxes(look_at, left, up); +} + + +//----------------------------------------------------------------------------- +// rotate() +//----------------------------------------------------------------------------- +void LLAgent::rotate(F32 angle, const LLVector3 &axis) +{ + mFrameAgent.rotate(angle, axis); +} + + +//----------------------------------------------------------------------------- +// rotate() +//----------------------------------------------------------------------------- +void LLAgent::rotate(F32 angle, F32 x, F32 y, F32 z) +{ + mFrameAgent.rotate(angle, x, y, z); +} + + +//----------------------------------------------------------------------------- +// rotate() +//----------------------------------------------------------------------------- +void LLAgent::rotate(const LLMatrix3 &matrix) +{ + mFrameAgent.rotate(matrix); +} + + +//----------------------------------------------------------------------------- +// rotate() +//----------------------------------------------------------------------------- +void LLAgent::rotate(const LLQuaternion &quaternion) +{ + mFrameAgent.rotate(quaternion); +} + + +//----------------------------------------------------------------------------- +// getReferenceUpVector() +//----------------------------------------------------------------------------- +LLVector3 LLAgent::getReferenceUpVector() +{ + // this vector is in the coordinate frame of the avatar's parent object, or the world if none + LLVector3 up_vector = LLVector3::z_axis; + if (mAvatarObject.notNull() && + mAvatarObject->getParent() && + mAvatarObject->mDrawable.notNull()) + { + U32 camera_mode = mCameraAnimating ? mLastCameraMode : mCameraMode; + // and in third person... + if (camera_mode == CAMERA_MODE_THIRD_PERSON) + { + // make the up vector point to the absolute +z axis + up_vector = up_vector * ~((LLViewerObject*)mAvatarObject->getParent())->getRenderRotation(); + } + else if (camera_mode == CAMERA_MODE_MOUSELOOK) + { + // make the up vector point to the avatar's +z axis + up_vector = up_vector * mAvatarObject->mDrawable->getRotation(); + } + } + + return up_vector; +} + + +// Radians, positive is forward into ground +//----------------------------------------------------------------------------- +// pitch() +//----------------------------------------------------------------------------- +void LLAgent::pitch(F32 angle) +{ + // don't let user pitch if pointed almost all the way down or up + + // A dot B = mag(A) * mag(B) * cos(angle between A and B) + // so... cos(angle between A and B) = A dot B / mag(A) / mag(B) + // = A dot B for unit vectors + + LLVector3 skyward = getReferenceUpVector(); + + F32 look_down_limit; + F32 look_up_limit = 10.f * DEG_TO_RAD; + + F32 angle_from_skyward = acos( mFrameAgent.getAtAxis() * skyward ); + + if (mAvatarObject.notNull() && mAvatarObject->mIsSitting) + { + look_down_limit = 130.f * DEG_TO_RAD; + } + else + { + look_down_limit = 170.f * DEG_TO_RAD; + } + + // clamp pitch to limits + if ((angle >= 0.f) && (angle_from_skyward + angle > look_down_limit)) + { + angle = look_down_limit - angle_from_skyward; + } + else if ((angle < 0.f) && (angle_from_skyward + angle < look_up_limit)) + { + angle = look_up_limit - angle_from_skyward; + } + + mFrameAgent.pitch(angle); +} + + +//----------------------------------------------------------------------------- +// roll() +//----------------------------------------------------------------------------- +void LLAgent::roll(F32 angle) +{ + mFrameAgent.roll(angle); +} + + +//----------------------------------------------------------------------------- +// yaw() +//----------------------------------------------------------------------------- +void LLAgent::yaw(F32 angle) +{ + if (!rotateGrabbed()) + { + mFrameAgent.rotate(angle, getReferenceUpVector()); + } +} + + +// Returns a quat that represents the rotation of the agent in the absolute frame +//----------------------------------------------------------------------------- +// getQuat() +//----------------------------------------------------------------------------- +LLQuaternion LLAgent::getQuat() const +{ + return mFrameAgent.getQuaternion(); +} + + +//----------------------------------------------------------------------------- +// calcFocusOffset() +//----------------------------------------------------------------------------- +LLVector3d LLAgent::calcFocusOffset(LLViewerObject *object, S32 x, S32 y) +{ + // calculate offset based on view direction + BOOL is_avatar = object->isAvatar(); + LLMatrix4 obj_matrix = is_avatar ? ((LLVOAvatar*)object)->mPelvisp->getWorldMatrix() : object->getRenderMatrix(); + LLQuaternion obj_rot = is_avatar ? ((LLVOAvatar*)object)->mPelvisp->getWorldRotation() : object->getRenderRotation(); + LLVector3 obj_pos = is_avatar ? ((LLVOAvatar*)object)->mPelvisp->getWorldPosition() : object->getRenderPosition(); + LLQuaternion inv_obj_rot = ~obj_rot; + + LLVector3 obj_dir_abs = obj_pos - gCamera->getOrigin(); + obj_dir_abs.rotVec(inv_obj_rot); + obj_dir_abs.normVec(); + obj_dir_abs.abs(); + + LLVector3 object_extents = object->getScale(); + // make sure they object extents are non-zero + object_extents.clamp(0.001f, F32_MAX); + LLVector3 object_half_extents = object_extents * 0.5f; + + obj_dir_abs.mV[VX] = obj_dir_abs.mV[VX] / object_extents.mV[VX]; + obj_dir_abs.mV[VY] = obj_dir_abs.mV[VY] / object_extents.mV[VY]; + obj_dir_abs.mV[VZ] = obj_dir_abs.mV[VZ] / object_extents.mV[VZ]; + + LLVector3 normal; + if (obj_dir_abs.mV[VX] > obj_dir_abs.mV[VY] && obj_dir_abs.mV[VX] > obj_dir_abs.mV[VZ]) + { + normal.setVec(obj_matrix.getFwdRow4()); + } + else if (obj_dir_abs.mV[VY] > obj_dir_abs.mV[VZ]) + { + normal.setVec(obj_matrix.getLeftRow4()); + } + else + { + normal.setVec(obj_matrix.getUpRow4()); + } + normal.normVec(); + + LLVector3d focus_pt_global; + // RN: should we check return value for valid pick? + gViewerWindow->mousePointOnPlaneGlobal(focus_pt_global, x, y, gAgent.getPosGlobalFromAgent(obj_pos), normal); + LLVector3 focus_pt = gAgent.getPosAgentFromGlobal(focus_pt_global); + // find vector from camera to focus point in object coordinates + LLVector3 camera_focus_vec = focus_pt - gCamera->getOrigin(); + // convert to object-local space + camera_focus_vec.rotVec(inv_obj_rot); + + // find vector from object origin to focus point in object coordinates + LLVector3 focus_delta = focus_pt - obj_pos; + // convert to object-local space + focus_delta.rotVec(inv_obj_rot); + + // calculate clip percentage needed to get focus offset back in bounds along the camera_focus axis + LLVector3 clip_fraction; + + for (U32 axis = VX; axis <= VZ; axis++) + { + F32 clip_amt; + if (focus_delta.mV[axis] > 0.f) + { + clip_amt = llmax(0.f, focus_delta.mV[axis] - object_half_extents.mV[axis]); + } + else + { + clip_amt = llmin(0.f, focus_delta.mV[axis] + object_half_extents.mV[axis]); + } + + // don't divide by very small nunber + if (llabs(camera_focus_vec.mV[axis]) < 0.0001f) + { + clip_fraction.mV[axis] = 0.f; + } + else + { + clip_fraction.mV[axis] = clip_amt / camera_focus_vec.mV[axis]; + } + } + + LLVector3 abs_clip_fraction = clip_fraction; + abs_clip_fraction.abs(); + + // find greatest shrinkage factor and + // rescale focus offset to inside object extents + if (abs_clip_fraction.mV[VX] > abs_clip_fraction.mV[VY] && + abs_clip_fraction.mV[VX] > abs_clip_fraction.mV[VZ]) + { + focus_delta -= clip_fraction.mV[VX] * camera_focus_vec; + } + else if (abs_clip_fraction.mV[VY] > abs_clip_fraction.mV[VZ]) + { + focus_delta -= clip_fraction.mV[VY] * camera_focus_vec; + } + else + { + focus_delta -= clip_fraction.mV[VZ] * camera_focus_vec; + } + + // convert back to world space + focus_delta.rotVec(obj_rot); + + if (!is_avatar) + { + //unproject relative clicked coordinate from window coordinate using GL + glPushMatrix(); + glMatrixMode(GL_PROJECTION); + glLoadMatrixf((const GLfloat*) gCamera->getProjection().mMatrix); + glMatrixMode(GL_MODELVIEW); + glLoadMatrixf((const GLfloat*) gCamera->getModelview().mMatrix); + glMultMatrixf((const GLfloat*) obj_matrix.mMatrix); + + GLint viewport[4]; + GLdouble modelview[16]; + GLdouble projection[16]; + GLfloat winX, winY, winZ; + GLdouble posX, posY, posZ; + + glGetDoublev( GL_MODELVIEW_MATRIX, modelview ); + glGetDoublev( GL_PROJECTION_MATRIX, projection ); + glGetIntegerv( GL_VIEWPORT, viewport ); + + winX = ((F32)x) * gViewerWindow->getDisplayScale().mV[VX]; + winY = ((F32)y) * gViewerWindow->getDisplayScale().mV[VY]; + glReadPixels( llfloor(winX), llfloor(winY), 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &winZ ); + + gluUnProject( winX, winY, winZ, modelview, projection, viewport, &posX, &posY, &posZ); + + glPopMatrix(); + + LLVector3 obj_rel = LLVector3((F32)posX, (F32)posY, (F32)posZ); + LLVector3 obj_center = LLVector3(0, 0, 0); + obj_rel = obj_rel * object->getRenderMatrix(); //mDrawable->getWorldMatrix(); + obj_center = obj_center * object->getRenderMatrix();//mDrawable->getWorldMatrix(); + obj_rel -= object->getRenderPosition();//mDrawable->getWorldPosition(); + + //now that we have the object relative position, we should bias toward the center of the object + //based on the distance of the camera to the focus point vs. the distance of the camera to the focus + + F32 relDist = llabs(obj_rel * gCamera->getAtAxis()); + F32 viewDist = dist_vec(obj_center + obj_rel, gCamera->getOrigin()); + + + LLBBox obj_bbox = object->getBoundingBoxAgent(); + F32 bias = 0.f; + + LLVector3 virtual_camera_pos = gAgent.getPosAgentFromGlobal(mFocusTargetGlobal + (getCameraPositionGlobal() - mFocusTargetGlobal) / (1.f + mCameraFOVZoomFactor)); + + if(obj_bbox.containsPointAgent(virtual_camera_pos)) + { + // if the camera is inside the object (large, hollow objects, for example) + // force focus point all the way to destination depth, away from object center + bias = 1.f; + } + else + { + // perform magic number biasing of focus point towards surface vs. planar center + bias = clamp_rescale(relDist/viewDist, 0.1f, 0.7f, 0.0f, 1.0f); + } + + obj_rel = lerp(focus_delta, obj_rel, bias); + + return LLVector3d(obj_rel); + } + + return LLVector3d(focus_delta.mV[VX], focus_delta.mV[VY], focus_delta.mV[VZ]); +} + +//----------------------------------------------------------------------------- +// calcCameraMinDistance() +//----------------------------------------------------------------------------- +BOOL LLAgent::calcCameraMinDistance(F32 &obj_min_distance) +{ + BOOL soft_limit = FALSE; // is the bounding box to be treated literally (volumes) or as an approximation (avatars) + + if (!mFocusObject || mFocusObject->isDead()) + { + obj_min_distance = 0.f; + return TRUE; + } + + if (mFocusObject->mDrawable.isNull()) + { +#ifdef LL_RELEASE_FOR_DOWNLOAD + llwarns << "Focus object with no drawable!" << llendl; +#else + mFocusObject->dump(); + llerrs << "Focus object with no drawable!" << llendl; +#endif + obj_min_distance = 0.f; + return TRUE; + } + + LLQuaternion inv_object_rot = ~mFocusObject->getRenderRotation(); + LLVector3 target_offset_origin = mFocusObjectOffset; + LLVector3 camera_offset_target(getCameraPositionAgent() - getPosAgentFromGlobal(mFocusTargetGlobal)); + + // convert offsets into object local space + camera_offset_target.rotVec(inv_object_rot); + target_offset_origin.rotVec(inv_object_rot); + + // push around object extents based on target offset + LLVector3 object_extents = mFocusObject->getScale(); + if (mFocusObject->isAvatar()) + { + // fudge factors that lets you zoom in on avatars a bit more (which don't do FOV zoom) + object_extents.mV[VX] *= AVATAR_ZOOM_MIN_X_FACTOR; + object_extents.mV[VY] *= AVATAR_ZOOM_MIN_Y_FACTOR; + object_extents.mV[VZ] *= AVATAR_ZOOM_MIN_Z_FACTOR; + soft_limit = TRUE; + } + LLVector3 abs_target_offset = target_offset_origin; + abs_target_offset.abs(); + + LLVector3 target_offset_dir = target_offset_origin; + F32 object_radius = mFocusObject->getVObjRadius(); + + BOOL target_outside_object_extents = FALSE; + + for (U32 i = VX; i <= VZ; i++) + { + if (abs_target_offset.mV[i] * 2.f > object_extents.mV[i] + OBJECT_EXTENTS_PADDING) + { + target_outside_object_extents = TRUE; + } + if (camera_offset_target.mV[i] > 0.f) + { + object_extents.mV[i] -= target_offset_origin.mV[i] * 2.f; + } + else + { + object_extents.mV[i] += target_offset_origin.mV[i] * 2.f; + } + } + + // don't shrink the object extents so far that the object inverts + object_extents.clamp(0.001f, F32_MAX); + + // move into first octant + LLVector3 camera_offset_target_abs_norm = camera_offset_target; + camera_offset_target_abs_norm.abs(); + // make sure offset is non-zero + camera_offset_target_abs_norm.clamp(0.001f, F32_MAX); + camera_offset_target_abs_norm.normVec(); + + // find camera position relative to normalized object extents + LLVector3 camera_offset_target_scaled = camera_offset_target_abs_norm; + camera_offset_target_scaled.mV[VX] /= object_extents.mV[VX]; + camera_offset_target_scaled.mV[VY] /= object_extents.mV[VY]; + camera_offset_target_scaled.mV[VZ] /= object_extents.mV[VZ]; + + if (camera_offset_target_scaled.mV[VX] > camera_offset_target_scaled.mV[VY] && + camera_offset_target_scaled.mV[VX] > camera_offset_target_scaled.mV[VZ]) + { + if (camera_offset_target_abs_norm.mV[VX] < 0.001f) + { + obj_min_distance = object_extents.mV[VX] * 0.5f; + } + else + { + obj_min_distance = object_extents.mV[VX] * 0.5f / camera_offset_target_abs_norm.mV[VX]; + } + } + else if (camera_offset_target_scaled.mV[VY] > camera_offset_target_scaled.mV[VZ]) + { + if (camera_offset_target_abs_norm.mV[VY] < 0.001f) + { + obj_min_distance = object_extents.mV[VY] * 0.5f; + } + else + { + obj_min_distance = object_extents.mV[VY] * 0.5f / camera_offset_target_abs_norm.mV[VY]; + } + } + else + { + if (camera_offset_target_abs_norm.mV[VZ] < 0.001f) + { + obj_min_distance = object_extents.mV[VZ] * 0.5f; + } + else + { + obj_min_distance = object_extents.mV[VZ] * 0.5f / camera_offset_target_abs_norm.mV[VZ]; + } + } + + LLVector3 object_split_axis; + LLVector3 target_offset_scaled = target_offset_origin; + target_offset_scaled.abs(); + target_offset_scaled.normVec(); + target_offset_scaled.mV[VX] /= object_extents.mV[VX]; + target_offset_scaled.mV[VY] /= object_extents.mV[VY]; + target_offset_scaled.mV[VZ] /= object_extents.mV[VZ]; + + if (target_offset_scaled.mV[VX] > target_offset_scaled.mV[VY] && + target_offset_scaled.mV[VX] > target_offset_scaled.mV[VZ]) + { + object_split_axis = LLVector3::x_axis; + } + else if (target_offset_scaled.mV[VY] > target_offset_scaled.mV[VZ]) + { + object_split_axis = LLVector3::y_axis; + } + else + { + object_split_axis = LLVector3::z_axis; + } + + LLVector3 camera_offset_object(getCameraPositionAgent() - mFocusObject->getPositionAgent()); + + // length projected orthogonal to target offset + F32 camera_offset_dist = (camera_offset_object - target_offset_dir * (camera_offset_object * target_offset_dir)).magVec(); + + // calculate whether the target point would be "visible" if it were outside the bounding box + // on the opposite of the splitting plane defined by object_split_axis; + BOOL exterior_target_visible = FALSE; + if (camera_offset_dist > object_radius) + { + // target is visible from camera, so turn off fov zoom + exterior_target_visible = TRUE; + } + + F32 camera_offset_clip = camera_offset_object * object_split_axis; + F32 target_offset_clip = target_offset_dir * object_split_axis; + + // target has moved outside of object extents + // check to see if camera and target are on same side + if (target_outside_object_extents) + { + if (camera_offset_clip > 0.f && target_offset_clip > 0.f) + { + return FALSE; + } + else if (camera_offset_clip < 0.f && target_offset_clip < 0.f) + { + return FALSE; + } + } + + // clamp obj distance to diagonal of 10 by 10 cube + obj_min_distance = llmin(obj_min_distance, 10.f * F_SQRT3); + + obj_min_distance += gCamera->getNear() + (soft_limit ? 0.1f : 0.2f); + + return TRUE; +} + +F32 LLAgent::getCameraZoomFraction() +{ + // 0.f -> camera zoomed all the way out + // 1.f -> camera zoomed all the way in + if (gSelectMgr->getObjectCount() && gSelectMgr->getSelectType() == SELECT_TYPE_HUD) + { + // already [0,1] + return mAvatarObject->mHUDTargetZoom; + } + else if (mFocusOnAvatar && cameraThirdPerson()) + { + return clamp_rescale(mCameraZoomFraction, MIN_ZOOM_FRACTION, MAX_ZOOM_FRACTION, 1.f, 0.f); + } + else if (cameraCustomizeAvatar()) + { + F32 distance = (F32)mCameraFocusOffsetTarget.magVec(); + return clamp_rescale(distance, APPEARANCE_MIN_ZOOM, APPEARANCE_MAX_ZOOM, 1.f, 0.f ); + } + else + { + F32 min_zoom; + const F32 DIST_FUDGE = 16.f; // meters + F32 max_zoom = gWorldPointer ? llmin(mDrawDistance - DIST_FUDGE, + gWorldPointer->getRegionWidthInMeters() - DIST_FUDGE, + MAX_CAMERA_DISTANCE_FROM_AGENT) : MAX_CAMERA_DISTANCE_FROM_AGENT; + + F32 distance = (F32)mCameraFocusOffsetTarget.magVec(); + if (mFocusObject.notNull()) + { + if (mFocusObject->isAvatar()) + { + min_zoom = AVATAR_MIN_ZOOM; + } + else + { + min_zoom = OBJECT_MIN_ZOOM; + } + } + else + { + min_zoom = LAND_MIN_ZOOM; + } + + return clamp_rescale(distance, min_zoom, max_zoom, 1.f, 0.f); + } +} + +void LLAgent::setCameraZoomFraction(F32 fraction) +{ + // 0.f -> camera zoomed all the way out + // 1.f -> camera zoomed all the way in + if (gSelectMgr->getObjectCount() && gSelectMgr->getSelectType() == SELECT_TYPE_HUD) + { + mAvatarObject->mHUDTargetZoom = fraction; + } + else if (mFocusOnAvatar && cameraThirdPerson()) + { + mCameraZoomFraction = rescale(fraction, 0.f, 1.f, MAX_ZOOM_FRACTION, MIN_ZOOM_FRACTION); + } + else if (cameraCustomizeAvatar()) + { + LLVector3d camera_offset_dir = mCameraFocusOffsetTarget; + camera_offset_dir.normVec(); + mCameraFocusOffsetTarget = camera_offset_dir * rescale(fraction, 0.f, 1.f, APPEARANCE_MAX_ZOOM, APPEARANCE_MIN_ZOOM); + } + else + { + F32 min_zoom = LAND_MIN_ZOOM; + const F32 DIST_FUDGE = 16.f; // meters + F32 max_zoom = llmin(mDrawDistance - DIST_FUDGE, + gWorldPointer->getRegionWidthInMeters() - DIST_FUDGE, + MAX_CAMERA_DISTANCE_FROM_AGENT); + + if (mFocusObject.notNull()) + { + if (mFocusObject.notNull()) + { + if (mFocusObject->isAvatar()) + { + min_zoom = AVATAR_MIN_ZOOM; + } + else + { + min_zoom = OBJECT_MIN_ZOOM; + } + } + } + + LLVector3d camera_offset_dir = mCameraFocusOffsetTarget; + camera_offset_dir.normVec(); + mCameraFocusOffsetTarget = camera_offset_dir * rescale(fraction, 0.f, 1.f, max_zoom, min_zoom); + } + startCameraAnimation(); +} + + +//----------------------------------------------------------------------------- +// cameraOrbitAround() +//----------------------------------------------------------------------------- +void LLAgent::cameraOrbitAround(const F32 radians) +{ + if (gSelectMgr->getObjectCount() && gSelectMgr->getSelectType() == SELECT_TYPE_HUD) + { + // do nothing for hud selection + } + else if (mFocusOnAvatar && (mCameraMode == CAMERA_MODE_THIRD_PERSON || mCameraMode == CAMERA_MODE_FOLLOW)) + { + mFrameAgent.rotate(radians, getReferenceUpVector()); + } + else + { + mCameraFocusOffsetTarget.rotVec(radians, 0.f, 0.f, 1.f); + + cameraZoomIn(1.f); + } +} + + +//----------------------------------------------------------------------------- +// cameraOrbitOver() +//----------------------------------------------------------------------------- +void LLAgent::cameraOrbitOver(const F32 angle) +{ + if (gSelectMgr->getObjectCount() && gSelectMgr->getSelectType() == SELECT_TYPE_HUD) + { + // do nothing for hud selection + } + else if (mFocusOnAvatar && mCameraMode == CAMERA_MODE_THIRD_PERSON) + { + pitch(angle); + } + else + { + LLVector3 camera_offset_unit(mCameraFocusOffsetTarget); + camera_offset_unit.normVec(); + + F32 angle_from_up = acos( camera_offset_unit * getReferenceUpVector() ); + + LLVector3d left_axis; + left_axis.setVec(gCamera->getLeftAxis()); + F32 new_angle = llclamp(angle_from_up - angle, 1.f * DEG_TO_RAD, 179.f * DEG_TO_RAD); + mCameraFocusOffsetTarget.rotVec(angle_from_up - new_angle, left_axis); + + cameraZoomIn(1.f); + } +} + +//----------------------------------------------------------------------------- +// cameraZoomIn() +//----------------------------------------------------------------------------- +void LLAgent::cameraZoomIn(const F32 fraction) +{ + if (gDisconnected) + { + return; + } + + if (gSelectMgr->getObjectCount() && gSelectMgr->getSelectType() == SELECT_TYPE_HUD) + { + // just update hud zoom level + mAvatarObject->mHUDTargetZoom /= fraction; + return; + } + + + LLVector3d camera_offset(mCameraFocusOffsetTarget); + LLVector3d camera_offset_unit(mCameraFocusOffsetTarget); + F32 min_zoom = LAND_MIN_ZOOM; + F32 current_distance = (F32)camera_offset_unit.normVec(); + F32 new_distance = current_distance * fraction; + + // Don't move through focus point + if (mFocusObject) + { + LLVector3 camera_offset_dir((F32)camera_offset_unit.mdV[VX], (F32)camera_offset_unit.mdV[VY], (F32)camera_offset_unit.mdV[VZ]); + + if (mFocusObject->isAvatar()) + { + calcCameraMinDistance(min_zoom); + } + else + { + min_zoom = OBJECT_MIN_ZOOM; + } + } + + new_distance = llmax(new_distance, min_zoom); + + // Don't zoom too far back + const F32 DIST_FUDGE = 16.f; // meters + F32 max_distance = llmin(mDrawDistance - DIST_FUDGE, + gWorldPointer->getRegionWidthInMeters() - DIST_FUDGE ); + + if (new_distance > max_distance) + { + new_distance = max_distance; + + /* + // Unless camera is unlocked + if (!LLViewerCamera::sDisableCameraConstraints) + { + return; + } + */ + } + + if( cameraCustomizeAvatar() ) + { + new_distance = llclamp( new_distance, APPEARANCE_MIN_ZOOM, APPEARANCE_MAX_ZOOM ); + } + + mCameraFocusOffsetTarget = new_distance * camera_offset_unit; +} + +//----------------------------------------------------------------------------- +// cameraOrbitIn() +//----------------------------------------------------------------------------- +void LLAgent::cameraOrbitIn(const F32 meters) +{ + if (mFocusOnAvatar && mCameraMode == CAMERA_MODE_THIRD_PERSON) + { + F32 camera_offset_dist = llmax(0.001f, mCameraOffsetDefault.magVec()); + + mCameraZoomFraction = (mTargetCameraDistance - meters) / camera_offset_dist; + + if (!gSavedSettings.getBOOL("FreezeTime") && mCameraZoomFraction < MIN_ZOOM_FRACTION && meters > 0.f) + { + // No need to animate, camera is already there. + changeCameraToMouselook(FALSE); + } + + mCameraZoomFraction = llclamp(mCameraZoomFraction, MIN_ZOOM_FRACTION, MAX_ZOOM_FRACTION); + } + else + { + LLVector3d camera_offset(mCameraFocusOffsetTarget); + LLVector3d camera_offset_unit(mCameraFocusOffsetTarget); + F32 current_distance = (F32)camera_offset_unit.normVec(); + F32 new_distance = current_distance - meters; + F32 min_zoom = LAND_MIN_ZOOM; + + // Don't move through focus point + if (mFocusObject.notNull()) + { + if (mFocusObject->isAvatar()) + { + min_zoom = AVATAR_MIN_ZOOM; + } + else + { + min_zoom = OBJECT_MIN_ZOOM; + } + } + + new_distance = llmax(new_distance, min_zoom); + + // Don't zoom too far back + const F32 DIST_FUDGE = 16.f; // meters + F32 max_distance = llmin(mDrawDistance - DIST_FUDGE, + gWorldPointer->getRegionWidthInMeters() - DIST_FUDGE ); + + if (new_distance > max_distance) + { + // Unless camera is unlocked + if (!LLViewerCamera::sDisableCameraConstraints) + { + return; + } + } + + if( CAMERA_MODE_CUSTOMIZE_AVATAR == getCameraMode() ) + { + llclamp( new_distance, APPEARANCE_MIN_ZOOM, APPEARANCE_MAX_ZOOM ); + } + + // Compute new camera offset + mCameraFocusOffsetTarget = new_distance * camera_offset_unit; + cameraZoomIn(1.f); + } +} + + +//----------------------------------------------------------------------------- +// cameraPanIn() +//----------------------------------------------------------------------------- +void LLAgent::cameraPanIn(F32 meters) +{ + LLVector3d at_axis; + at_axis.setVec(gCamera->getAtAxis()); + + mFocusTargetGlobal += meters * at_axis; + mFocusGlobal = mFocusTargetGlobal; + // don't enforce zoom constraints as this is the only way for users to get past them easily + updateFocusOffset(); +} + +//----------------------------------------------------------------------------- +// cameraPanLeft() +//----------------------------------------------------------------------------- +void LLAgent::cameraPanLeft(F32 meters) +{ + LLVector3d left_axis; + left_axis.setVec(gCamera->getLeftAxis()); + + mFocusTargetGlobal += meters * left_axis; + mFocusGlobal = mFocusTargetGlobal; + cameraZoomIn(1.f); + updateFocusOffset(); +} + +//----------------------------------------------------------------------------- +// cameraPanUp() +//----------------------------------------------------------------------------- +void LLAgent::cameraPanUp(F32 meters) +{ + LLVector3d up_axis; + up_axis.setVec(gCamera->getUpAxis()); + + mFocusTargetGlobal += meters * up_axis; + mFocusGlobal = mFocusTargetGlobal; + cameraZoomIn(1.f); + updateFocusOffset(); +} + +//----------------------------------------------------------------------------- +// setKey() +//----------------------------------------------------------------------------- +void LLAgent::setKey(const S32 direction, S32 &key) +{ + if (direction > 0) + { + key = 1; + } + else if (direction < 0) + { + key = -1; + } + else + { + key = 0; + } +} + + +//----------------------------------------------------------------------------- +// getControlFlags() +//----------------------------------------------------------------------------- +U32 LLAgent::getControlFlags() +{ +/* + // HACK -- avoids maintenance of control flags when camera mode is turned on or off, + // only worries about it when the flags are measured + if (mCameraMode == CAMERA_MODE_MOUSELOOK) + { + if ( !(mControlFlags & AGENT_CONTROL_MOUSELOOK) ) + { + mControlFlags |= AGENT_CONTROL_MOUSELOOK; + } + } +*/ + return mControlFlags; +} + +//----------------------------------------------------------------------------- +// setControlFlags() +//----------------------------------------------------------------------------- +void LLAgent::setControlFlags(U32 mask) +{ + mControlFlags |= mask; + mbFlagsDirty = TRUE; +} + + +//----------------------------------------------------------------------------- +// clearControlFlags() +//----------------------------------------------------------------------------- +void LLAgent::clearControlFlags(U32 mask) +{ + U32 old_flags = mControlFlags; + mControlFlags &= ~mask; + if (old_flags != mControlFlags) + { + mbFlagsDirty = TRUE; + } +} + +//----------------------------------------------------------------------------- +// controlFlagsDirty() +//----------------------------------------------------------------------------- +BOOL LLAgent::controlFlagsDirty() const +{ + return mbFlagsDirty; +} + +//----------------------------------------------------------------------------- +// enableControlFlagReset() +//----------------------------------------------------------------------------- +void LLAgent::enableControlFlagReset() +{ + mbFlagsNeedReset = TRUE; +} + +//----------------------------------------------------------------------------- +// resetControlFlags() +//----------------------------------------------------------------------------- +void LLAgent::resetControlFlags() +{ + if (mbFlagsNeedReset) + { + mbFlagsNeedReset = FALSE; + mbFlagsDirty = FALSE; + // reset all of the ephemeral flags + // some flags are managed elsewhere + mControlFlags &= AGENT_CONTROL_AWAY | AGENT_CONTROL_FLY | AGENT_CONTROL_MOUSELOOK; + } +} + +//----------------------------------------------------------------------------- +// setAFK() +//----------------------------------------------------------------------------- +void LLAgent::setAFK() +{ + // Drones can't go AFK + if (gNoRender) + { + return; + } + + if (!gAllowAFK) + { + return; + } + + if (!gAgent.getRegion()) + { + // Don't set AFK if we're not talking to a region yet. + return; + } + + if (!(mControlFlags & AGENT_CONTROL_AWAY)) + { + sendAnimationRequest(ANIM_AGENT_AWAY, ANIM_REQUEST_START); + setControlFlags(AGENT_CONTROL_AWAY | AGENT_CONTROL_STOP); + gAwayTimer.start(); + if (gAFKMenu) + { + gAFKMenu->setLabel("Set Not Away"); + } + } +} + +//----------------------------------------------------------------------------- +// clearAFK() +//----------------------------------------------------------------------------- +void LLAgent::clearAFK() +{ + gAwayTriggerTimer.reset(); + + // Gods can sometimes get into away state (via gestures) + // without setting the appropriate control flag. JC + LLVOAvatar* av = mAvatarObject; + if (mControlFlags & AGENT_CONTROL_AWAY + || (av + && (av->mSignaledAnimations.find(ANIM_AGENT_AWAY) != av->mSignaledAnimations.end()))) + { + sendAnimationRequest(ANIM_AGENT_AWAY, ANIM_REQUEST_STOP); + clearControlFlags(AGENT_CONTROL_AWAY); + if (gAFKMenu) + { + gAFKMenu->setLabel("Set Away"); + } + } +} + +//----------------------------------------------------------------------------- +// getAFK() +//----------------------------------------------------------------------------- +BOOL LLAgent::getAFK() const +{ + return (mControlFlags & AGENT_CONTROL_AWAY) != 0; +} + +//----------------------------------------------------------------------------- +// setBusy() +//----------------------------------------------------------------------------- +void LLAgent::setBusy() +{ + sendAnimationRequest(ANIM_AGENT_BUSY, ANIM_REQUEST_START); + mIsBusy = TRUE; + if (gBusyMenu) + { + gBusyMenu->setLabel("Set Not Busy"); + } + if (gFloaterMute) + { + gFloaterMute->updateButtons(); + } +} + +//----------------------------------------------------------------------------- +// clearBusy() +//----------------------------------------------------------------------------- +void LLAgent::clearBusy() +{ + mIsBusy = FALSE; + sendAnimationRequest(ANIM_AGENT_BUSY, ANIM_REQUEST_STOP); + if (gBusyMenu) + { + gBusyMenu->setLabel("Set Busy"); + } + if (gFloaterMute) + { + gFloaterMute->updateButtons(); + } +} + +//----------------------------------------------------------------------------- +// getBusy() +//----------------------------------------------------------------------------- +BOOL LLAgent::getBusy() const +{ + return mIsBusy; +} + + +//----------------------------------------------------------------------------- +// updateWanderTarget() +//----------------------------------------------------------------------------- +void LLAgent::updateWanderTarget() +{ + S32 num_regions; + LLViewerRegion* rand_region; + F32 rand_x; + F32 rand_y; + + if (mWanderTimer.checkExpirationAndReset(frand(MAX_WANDER_TIME))) + { + // Pick a random spot to wander towards + num_regions = gWorldPointer->mActiveRegionList.getLength(); + S32 region_num = llround(frand(1.f) * num_regions); + rand_region = gWorldPointer->mActiveRegionList.getFirstData(); + S32 i = 0; + while (i < region_num) + { + rand_region = gWorldPointer->mActiveRegionList.getNextData(); + i++; + } + rand_x = frand(rand_region->getWidth()); + rand_y = frand(rand_region->getWidth()); + + stopAutoPilot(); + startAutoPilotGlobal(rand_region->getPosGlobalFromRegion(LLVector3(rand_x, rand_y, 0.f))); + } +} + +//----------------------------------------------------------------------------- +// startAutoPilotGlobal() +//----------------------------------------------------------------------------- +void LLAgent::startAutoPilotGlobal(const LLVector3d &target_global, const std::string& behavior_name, const LLQuaternion *target_rotation, void (*finish_callback)(BOOL, void *), void *callback_data, F32 stop_distance, F32 rot_threshold) +{ + if (!gAgent.getAvatarObject() || !gWorldPointer) + { + return; + } + + mAutoPilotFinishedCallback = finish_callback; + mAutoPilotCallbackData = callback_data; + mAutoPilotRotationThreshold = rot_threshold; + mAutoPilotBehaviorName = behavior_name; + + LLVector3d delta_pos( target_global ); + delta_pos -= getPositionGlobal(); + F64 distance = delta_pos.magVec(); + LLVector3d trace_target = target_global; + + trace_target.mdV[VZ] -= 10.f; + + LLVector3d intersection; + LLVector3 normal; + LLViewerObject *hit_obj; + F32 heightDelta = gWorldPointer->resolveStepHeightGlobal(NULL, target_global, trace_target, intersection, normal, &hit_obj); + + if (stop_distance > 0.f) + { + mAutoPilotStopDistance = stop_distance; + } + else + { + // Guess at a reasonable stop distance. + mAutoPilotStopDistance = fsqrtf( distance ); + if (mAutoPilotStopDistance < 0.5f) + { + mAutoPilotStopDistance = 0.5f; + } + } + + mAutoPilotFlyOnStop = getFlying(); + + if (distance > 30.0) + { + setFlying(TRUE); + } + + if ( distance > 1.f && heightDelta > (sqrtf(mAutoPilotStopDistance) + 1.f)) + { + setFlying(TRUE); + mAutoPilotFlyOnStop = TRUE; + } + + mAutoPilot = TRUE; + mAutoPilotTargetGlobal = target_global; + + // trace ray down to find height of destination from ground + LLVector3d traceEndPt = target_global; + traceEndPt.mdV[VZ] -= 20.f; + + LLVector3d targetOnGround; + LLVector3 groundNorm; + LLViewerObject *obj; + + gWorldPointer->resolveStepHeightGlobal(NULL, target_global, traceEndPt, targetOnGround, groundNorm, &obj); + F64 target_height = llmax((F64)gAgent.getAvatarObject()->getPelvisToFoot(), target_global.mdV[VZ] - targetOnGround.mdV[VZ]); + + // clamp z value of target to minimum height above ground + mAutoPilotTargetGlobal.mdV[VZ] = targetOnGround.mdV[VZ] + target_height; + mAutoPilotTargetDist = (F32)dist_vec(gAgent.getPositionGlobal(), mAutoPilotTargetGlobal); + if (target_rotation) + { + mAutoPilotUseRotation = TRUE; + mAutoPilotTargetFacing = LLVector3::x_axis * *target_rotation; + mAutoPilotTargetFacing.mV[VZ] = 0.f; + mAutoPilotTargetFacing.normVec(); + } + else + { + mAutoPilotUseRotation = FALSE; + } + + mAutoPilotNoProgressFrameCount = 0; +} + + +//----------------------------------------------------------------------------- +// startFollowPilot() +//----------------------------------------------------------------------------- +void LLAgent::startFollowPilot(const LLUUID &leader_id) +{ + if (!mAutoPilot) return; + + mLeaderID = leader_id; + if ( mLeaderID.isNull() ) return; + + LLViewerObject* object = gObjectList.findObject(mLeaderID); + if (!object) + { + mLeaderID = LLUUID::null; + return; + } + + startAutoPilotGlobal(object->getPositionGlobal()); +} + + +//----------------------------------------------------------------------------- +// stopAutoPilot() +//----------------------------------------------------------------------------- +void LLAgent::stopAutoPilot(BOOL user_cancel) +{ + if (mAutoPilot) + { + mAutoPilot = FALSE; + if (mAutoPilotUseRotation && !user_cancel) + { + resetAxes(mAutoPilotTargetFacing); + } + //NB: auto pilot can terminate for a reason other than reaching the destination + //TODO: enforce rotation constraint here as well + if (mAutoPilotFinishedCallback && + ((mAutoPilotTargetDist < mAutoPilotStopDistance) || (mAutoPilotNoProgressFrameCount > AUTOPILOT_MAX_TIME_NO_PROGRESS * gFPSClamped))) + { + mAutoPilotFinishedCallback(!user_cancel && dist_vec(gAgent.getPositionGlobal(), mAutoPilotTargetGlobal) < mAutoPilotTargetDist, mAutoPilotCallbackData); + } + mLeaderID = LLUUID::null; + + // If the user cancelled, don't change the fly state + if (!user_cancel) + { + setFlying(mAutoPilotFlyOnStop); + } + setControlFlags(AGENT_CONTROL_STOP); + + if (user_cancel && !mAutoPilotBehaviorName.empty()) + { + if (mAutoPilotBehaviorName == "Sit") + LLNotifyBox::showXml("CancelledSit"); + else if (mAutoPilotBehaviorName == "Attach") + LLNotifyBox::showXml("CancelledAttach"); + else + LLNotifyBox::showXml("Cancelled"); + } + } +} + + +// Returns necessary agent pitch and yaw changes, radians. +//----------------------------------------------------------------------------- +// autoPilot() +//----------------------------------------------------------------------------- +void LLAgent::autoPilot(F32 *delta_yaw) +{ + if (mAutoPilot) + { + if (!mLeaderID.isNull()) + { + LLViewerObject* object = gObjectList.findObject(mLeaderID); + if (!object) + { + stopAutoPilot(); + return; + } + mAutoPilotTargetGlobal = object->getPositionGlobal(); + } + + if (!mAvatarObject) + { + return; + } + + if (mAvatarObject->mInAir) + { + setFlying(TRUE); + } + + LLVector3 at; + at.setVec(mFrameAgent.getAtAxis()); + LLVector3 target_agent = getPosAgentFromGlobal(mAutoPilotTargetGlobal); + LLVector3 direction = target_agent - getPositionAgent(); + + F32 target_dist = direction.magVec(); + + if (target_dist >= mAutoPilotTargetDist) + { + mAutoPilotNoProgressFrameCount++; + if (mAutoPilotNoProgressFrameCount > AUTOPILOT_MAX_TIME_NO_PROGRESS * gFPSClamped) + { + stopAutoPilot(); + return; + } + } + + mAutoPilotTargetDist = target_dist; + + // Make this a two-dimensional solution + at.mV[VZ] = 0.f; + direction.mV[VZ] = 0.f; + + at.normVec(); + F32 xy_distance = direction.normVec(); + + F32 yaw = 0.f; + if (mAutoPilotTargetDist > mAutoPilotStopDistance) + { + yaw = angle_between(mFrameAgent.getAtAxis(), direction); + } + else if (mAutoPilotUseRotation) + { + // we're close now just aim at target facing + yaw = angle_between(at, mAutoPilotTargetFacing); + direction = mAutoPilotTargetFacing; + } + + yaw = 4.f * yaw / gFPSClamped; + + // figure out which direction to turn + LLVector3 scratch(at % direction); + + if (scratch.mV[VZ] > 0.f) + { + setControlFlags(AGENT_CONTROL_YAW_POS); + } + else + { + yaw = -yaw; + setControlFlags(AGENT_CONTROL_YAW_NEG); + } + + *delta_yaw = yaw; + + // Compute when to start slowing down and when to stop + F32 stop_distance = mAutoPilotStopDistance; + F32 slow_distance; + if (getFlying()) + { + slow_distance = llmax(6.f, mAutoPilotStopDistance + 5.f); + stop_distance = llmax(2.f, mAutoPilotStopDistance); + } + else + { + slow_distance = llmax(3.f, mAutoPilotStopDistance + 2.f); + } + + // If we're flying, handle autopilot points above or below you. + if (getFlying() && xy_distance < AUTOPILOT_HEIGHT_ADJUST_DISTANCE) + { + if (mAvatarObject) + { + F64 current_height = mAvatarObject->getPositionGlobal().mdV[VZ]; + F32 delta_z = (F32)(mAutoPilotTargetGlobal.mdV[VZ] - current_height); + F32 slope = delta_z / xy_distance; + if (slope > 0.45f && delta_z > 6.f) + { + setControlFlags(AGENT_CONTROL_FAST_UP | AGENT_CONTROL_UP_POS); + } + else if (slope > 0.002f && delta_z > 0.5f) + { + setControlFlags(AGENT_CONTROL_UP_POS); + } + else if (slope < -0.45f && delta_z < -6.f && current_height > AUTOPILOT_MIN_TARGET_HEIGHT_OFF_GROUND) + { + setControlFlags(AGENT_CONTROL_FAST_UP | AGENT_CONTROL_UP_NEG); + } + else if (slope < -0.002f && delta_z < -0.5f && current_height > AUTOPILOT_MIN_TARGET_HEIGHT_OFF_GROUND) + { + setControlFlags(AGENT_CONTROL_UP_NEG); + } + } + } + + // calculate delta rotation to target heading + F32 delta_target_heading = angle_between(mFrameAgent.getAtAxis(), mAutoPilotTargetFacing); + + if (xy_distance > slow_distance && yaw < (F_PI / 10.f)) + { + // walking/flying fast + setControlFlags(AGENT_CONTROL_FAST_AT | AGENT_CONTROL_AT_POS); + } + else if (mAutoPilotTargetDist > mAutoPilotStopDistance) + { + // walking/flying slow + if (at * direction > 0.9f) + { + setControlFlags(AGENT_CONTROL_AT_POS); + } + else if (at * direction < -0.9f) + { + setControlFlags(AGENT_CONTROL_AT_NEG); + } + } + + // check to see if we need to keep rotating to target orientation + if (mAutoPilotTargetDist < mAutoPilotStopDistance) + { + setControlFlags(AGENT_CONTROL_STOP); + if(!mAutoPilotUseRotation || (delta_target_heading < mAutoPilotRotationThreshold)) + { + stopAutoPilot(); + } + } + } +} + + +//----------------------------------------------------------------------------- +// propagate() +//----------------------------------------------------------------------------- +void LLAgent::propagate(const F32 dt) +{ + // Update UI based on agent motion + LLFloaterMove *floater_move = LLFloaterMove::getInstance(); + if (floater_move) + { + floater_move->mForwardButton ->setToggleState( mAtKey > 0 || mWalkKey > 0 ); + floater_move->mBackwardButton ->setToggleState( mAtKey < 0 || mWalkKey < 0 ); + floater_move->mSlideLeftButton ->setToggleState( mLeftKey > 0 ); + floater_move->mSlideRightButton->setToggleState( mLeftKey < 0 ); + floater_move->mTurnLeftButton ->setToggleState( mYawKey > 0.f ); + floater_move->mTurnRightButton ->setToggleState( mYawKey < 0.f ); + floater_move->mMoveUpButton ->setToggleState( mUpKey > 0 ); + floater_move->mMoveDownButton ->setToggleState( mUpKey < 0 ); + } + + // handle rotation based on keyboard levels + const F32 YAW_RATE = 90.f * DEG_TO_RAD; // radians per second + yaw( YAW_RATE * mYawKey * dt ); + + const F32 PITCH_RATE = 90.f * DEG_TO_RAD; // radians per second + pitch(PITCH_RATE * (F32) mPitchKey * dt); + + // handle auto-land behavior + if (mAvatarObject) + { + BOOL in_air = mAvatarObject->mInAir; + LLVector3 land_vel = getVelocity(); + land_vel.mV[VZ] = 0.f; + + if (!in_air + && mUpKey < 0 + && land_vel.magVecSquared() < MAX_VELOCITY_AUTO_LAND_SQUARED + && gSavedSettings.getBOOL("AutomaticFly")) + { + // land automatically + setFlying(FALSE); + } + } + + // clear keys + mAtKey = 0; + mWalkKey = 0; + mLeftKey = 0; + mUpKey = 0; + mYawKey = 0.f; + mPitchKey = 0; +} + +//----------------------------------------------------------------------------- +// updateAgentPosition() +//----------------------------------------------------------------------------- +void LLAgent::updateAgentPosition(const F32 dt, const F32 yaw_radians, const S32 mouse_x, const S32 mouse_y) +{ + propagate(dt); + + // static S32 cameraUpdateCount = 0; + + rotate(yaw_radians, 0, 0, 1); + + // + // Check for water and land collision, set underwater flag + // + + updateLookAt(mouse_x, mouse_y); +} + +//----------------------------------------------------------------------------- +// updateLookAt() +//----------------------------------------------------------------------------- +void LLAgent::updateLookAt(const S32 mouse_x, const S32 mouse_y) +{ + static LLVector3 last_at_axis; + + + if ( mAvatarObject.isNull() ) + { + return; + } + + LLQuaternion av_inv_rot = ~mAvatarObject->mRoot.getWorldRotation(); + LLVector3 root_at = LLVector3::x_axis * mAvatarObject->mRoot.getWorldRotation(); + + if ((gViewerWindow->getMouseVelocityStat()->getCurrent() < 0.01f) && + (root_at * last_at_axis > 0.95f )) + { + LLVector3 vel = mAvatarObject->getVelocity(); + if (vel.magVecSquared() > 4.f) + { + setLookAt(LOOKAT_TARGET_IDLE, mAvatarObject, vel * av_inv_rot); + } + else + { + //FIXME: rotate mframeagent by sit object's rotation? + LLQuaternion look_rotation = mAvatarObject->mIsSitting ? mAvatarObject->getRenderRotation() : mFrameAgent.getQuaternion(); // use camera's current rotation + LLVector3 look_offset = LLVector3(2.f, 0.f, 0.f) * look_rotation * av_inv_rot; + setLookAt(LOOKAT_TARGET_IDLE, mAvatarObject, look_offset); + } + last_at_axis = root_at; + return; + } + + last_at_axis = root_at; + + if (CAMERA_MODE_CUSTOMIZE_AVATAR == getCameraMode()) + { + setLookAt(LOOKAT_TARGET_NONE, mAvatarObject, LLVector3(-2.f, 0.f, 0.f)); + } + else + { + // Move head based on cursor position + ELookAtType lookAtType = LOOKAT_TARGET_NONE; + LLVector3 headLookAxis; + LLCoordFrame frameCamera = *((LLCoordFrame*)gCamera); + + F32 x_from_center = mouse_x_from_center( mouse_x ); // range from -0.5 to 0.5 + F32 y_from_center = mouse_y_from_center( mouse_y ); // range from -0.5 to 0.5 + + if (cameraMouselook()) + { + lookAtType = LOOKAT_TARGET_MOUSELOOK; + } + else if (cameraThirdPerson()) + { + frameCamera.yaw( - x_from_center * gSavedSettings.getF32("YawFromMousePosition") * DEG_TO_RAD); + frameCamera.pitch( - y_from_center * gSavedSettings.getF32("PitchFromMousePosition") * DEG_TO_RAD); + lookAtType = LOOKAT_TARGET_FREELOOK; + } + + headLookAxis = frameCamera.getAtAxis(); + // RN: we use world-space offset for mouselook and freelook + //headLookAxis = headLookAxis * av_inv_rot; + setLookAt(lookAtType, mAvatarObject, headLookAxis); + } +} + +// friends and operators + +std::ostream& operator<<(std::ostream &s, const LLAgent &agent) +{ + // This is unfinished, but might never be used. + // We'll just leave it for now; we can always delete it. + s << " { " + << " Frame = " << agent.mFrameAgent << "\n" + << " }"; + return s; +} + + +// ------------------- Beginning of legacy LLCamera hack ---------------------- +// This section is included for legacy LLCamera support until +// it is no longer needed. Some legacy code must exist in +// non-legacy functions, and is labeled with "// legacy" comments. + +//----------------------------------------------------------------------------- +// setAvatarObject() +//----------------------------------------------------------------------------- +void LLAgent::setAvatarObject(LLVOAvatar *avatar) +{ + mAvatarObject = avatar; + + if (!avatar) + { + llinfos << "Setting LLAgent::mAvatarObject to NULL" << llendl; + return; + } + + if (!mLookAt) + { + mLookAt = (LLHUDEffectLookAt *)gHUDManager->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_LOOKAT); + } + if (!mPointAt) + { + mPointAt = (LLHUDEffectPointAt *)gHUDManager->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_POINTAT); + } + + if (!mLookAt.isNull()) + { + mLookAt->setSourceObject(avatar); + } + if (!mPointAt.isNull()) + { + mPointAt->setSourceObject(avatar); + } + + sendAgentWearablesRequest(); +} + +// TRUE if your own avatar needs to be rendered. Usually only +// in third person and build. +//----------------------------------------------------------------------------- +// needsRenderAvatar() +//----------------------------------------------------------------------------- +BOOL LLAgent::needsRenderAvatar() +{ + if (cameraMouselook() && !LLVOAvatar::sVisibleInFirstPerson) + { + return FALSE; + } + + return mShowAvatar && mGenderChosen; +} + +// TRUE if we need to render your own avatar's head. +BOOL LLAgent::needsRenderHead() +{ + return mShowAvatar && !cameraMouselook(); +} + +//----------------------------------------------------------------------------- +// startTyping() +//----------------------------------------------------------------------------- +void LLAgent::startTyping() +{ + mTypingTimer.reset(); + + if (getRenderState() & AGENT_STATE_TYPING) + { + // already typing, don't trigger a different animation + return; + } + setRenderState(AGENT_STATE_TYPING); + + if (mChatTimer.getElapsedTimeF32() < 2.f) + { + LLViewerObject* chatter = gObjectList.findObject(mLastChatterID); + if (chatter && chatter->isAvatar()) + { + gAgent.setLookAt(LOOKAT_TARGET_RESPOND, chatter, LLVector3::zero); + } + } + + if (gSavedSettings.getBOOL("PlayTypingAnim")) + { + sendAnimationRequest(ANIM_AGENT_TYPE, ANIM_REQUEST_START); + } + gChatBar->sendChatFromViewer("", CHAT_TYPE_START, FALSE); +} + +//----------------------------------------------------------------------------- +// stopTyping() +//----------------------------------------------------------------------------- +void LLAgent::stopTyping() +{ + if (mRenderState & AGENT_STATE_TYPING) + { + clearRenderState(AGENT_STATE_TYPING); + sendAnimationRequest(ANIM_AGENT_TYPE, ANIM_REQUEST_STOP); + gChatBar->sendChatFromViewer("", CHAT_TYPE_STOP, FALSE); + } +} + +//----------------------------------------------------------------------------- +// setRenderState() +//----------------------------------------------------------------------------- +void LLAgent::setRenderState(U8 newstate) +{ + mRenderState |= newstate; +} + +//----------------------------------------------------------------------------- +// clearRenderState() +//----------------------------------------------------------------------------- +void LLAgent::clearRenderState(U8 clearstate) +{ + mRenderState &= ~clearstate; +} + + +//----------------------------------------------------------------------------- +// getRenderState() +//----------------------------------------------------------------------------- +U8 LLAgent::getRenderState() +{ + if (gNoRender || gToolMgr == NULL || gSelectMgr == NULL || gKeyboard == NULL) + { + return 0; + } + + // FIXME: don't do stuff in a getter! This is infinite loop city! + if ((mTypingTimer.getElapsedTimeF32() > TYPING_TIMEOUT_SECS) + && (mRenderState & AGENT_STATE_TYPING)) + { + stopTyping(); + } + + if ((!gSelectMgr->isEmpty() && gSelectMgr->shouldShowSelection()) + || gToolMgr->getCurrentTool( gKeyboard->currentMask(TRUE) )->isEditing() ) + { + setRenderState(AGENT_STATE_EDITING); + } + else + { + clearRenderState(AGENT_STATE_EDITING); + } + + return mRenderState; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +static const LLFloaterView::skip_list_t& get_skip_list() +{ + static LLFloaterView::skip_list_t skip_list; + skip_list.insert(gFloaterMap); + return skip_list; +} + +//----------------------------------------------------------------------------- +// endAnimationUpdateUI() +//----------------------------------------------------------------------------- +void LLAgent::endAnimationUpdateUI() +{ + if (mCameraMode == mLastCameraMode) + { + // We're already done endAnimationUpdateUI for this transition. + return; + } + + // clean up UI from mode we're leaving + if ( mLastCameraMode == CAMERA_MODE_MOUSELOOK ) + { + // show mouse cursor + gViewerWindow->showCursor(); + // show menus + gMenuBarView->setVisible(TRUE); + gStatusBar->setVisibleForMouselook(true); + + gCurrentToolset = gBasicToolset; + gToolMgr->useSelectedTool( gCurrentToolset ); + + // Only pop if we have pushed... + if (TRUE == mViewsPushed) + { + mViewsPushed = FALSE; + gFloaterView->popVisibleAll(get_skip_list()); + } + + gAgent.setLookAt(LOOKAT_TARGET_CLEAR); + if( gMorphView ) + { + gMorphView->setVisible( FALSE ); + } + + // Disable mouselook-specific animations + if (mAvatarObject) + { + if( mAvatarObject->isAnyAnimationSignaled(AGENT_GUN_AIM_ANIMS, NUM_AGENT_GUN_AIM_ANIMS) ) + { + if (mAvatarObject->mSignaledAnimations.find(ANIM_AGENT_AIM_RIFLE_R) != mAvatarObject->mSignaledAnimations.end()) + { + sendAnimationRequest(ANIM_AGENT_AIM_RIFLE_R, ANIM_REQUEST_STOP); + sendAnimationRequest(ANIM_AGENT_HOLD_RIFLE_R, ANIM_REQUEST_START); + } + if (mAvatarObject->mSignaledAnimations.find(ANIM_AGENT_AIM_HANDGUN_R) != mAvatarObject->mSignaledAnimations.end()) + { + sendAnimationRequest(ANIM_AGENT_AIM_HANDGUN_R, ANIM_REQUEST_STOP); + sendAnimationRequest(ANIM_AGENT_HOLD_HANDGUN_R, ANIM_REQUEST_START); + } + if (mAvatarObject->mSignaledAnimations.find(ANIM_AGENT_AIM_BAZOOKA_R) != mAvatarObject->mSignaledAnimations.end()) + { + sendAnimationRequest(ANIM_AGENT_AIM_BAZOOKA_R, ANIM_REQUEST_STOP); + sendAnimationRequest(ANIM_AGENT_HOLD_BAZOOKA_R, ANIM_REQUEST_START); + } + if (mAvatarObject->mSignaledAnimations.find(ANIM_AGENT_AIM_BOW_L) != mAvatarObject->mSignaledAnimations.end()) + { + sendAnimationRequest(ANIM_AGENT_AIM_BOW_L, ANIM_REQUEST_STOP); + sendAnimationRequest(ANIM_AGENT_HOLD_BOW_L, ANIM_REQUEST_START); + } + } + } + } + else + if( mLastCameraMode == CAMERA_MODE_CUSTOMIZE_AVATAR ) + { + // make sure we ask to save changes + + gCurrentToolset = gBasicToolset; + gToolMgr->useSelectedTool( gCurrentToolset ); + + // HACK: If we're quitting, and we were in customize avatar, don't + // let the mini-map go visible again. JC + if (!gQuitRequested) + { + gFloaterMap->popVisible(); + } + + if( gMorphView ) + { + gMorphView->setVisible( FALSE ); + } + + if (mAvatarObject) + { + sendAnimationRequest(ANIM_AGENT_CUSTOMIZE, ANIM_REQUEST_STOP); + sendAnimationRequest(ANIM_AGENT_CUSTOMIZE_DONE, ANIM_REQUEST_START); + } + setLookAt(LOOKAT_TARGET_CLEAR); + } + + //--------------------------------------------------------------------- + // Set up UI for mode we're entering + //--------------------------------------------------------------------- + if (mCameraMode == CAMERA_MODE_MOUSELOOK) + { + // hide menus + gMenuBarView->setVisible(FALSE); + gStatusBar->setVisibleForMouselook(false); + + // clear out camera lag effect + mCameraLag.clearVec(); + + // JC - Added for always chat in third person option + gFocusMgr.setKeyboardFocus(NULL, NULL); + + gCurrentToolset = gMouselookToolset; + gToolMgr->useSelectedTool( gMouselookToolset ); + + mViewsPushed = TRUE; + + gFloaterView->pushVisibleAll(FALSE, get_skip_list()); + + if( gMorphView ) + { + gMorphView->setVisible(FALSE); + } + + gIMView->setFloaterOpen( FALSE ); + gConsole->setVisible( TRUE ); + + if (mAvatarObject) + { + // Trigger mouselook-specific animations + if( mAvatarObject->isAnyAnimationSignaled(AGENT_GUN_HOLD_ANIMS, NUM_AGENT_GUN_HOLD_ANIMS) ) + { + if (mAvatarObject->mSignaledAnimations.find(ANIM_AGENT_HOLD_RIFLE_R) != mAvatarObject->mSignaledAnimations.end()) + { + sendAnimationRequest(ANIM_AGENT_HOLD_RIFLE_R, ANIM_REQUEST_STOP); + sendAnimationRequest(ANIM_AGENT_AIM_RIFLE_R, ANIM_REQUEST_START); + } + if (mAvatarObject->mSignaledAnimations.find(ANIM_AGENT_HOLD_HANDGUN_R) != mAvatarObject->mSignaledAnimations.end()) + { + sendAnimationRequest(ANIM_AGENT_HOLD_HANDGUN_R, ANIM_REQUEST_STOP); + sendAnimationRequest(ANIM_AGENT_AIM_HANDGUN_R, ANIM_REQUEST_START); + } + if (mAvatarObject->mSignaledAnimations.find(ANIM_AGENT_HOLD_BAZOOKA_R) != mAvatarObject->mSignaledAnimations.end()) + { + sendAnimationRequest(ANIM_AGENT_HOLD_BAZOOKA_R, ANIM_REQUEST_STOP); + sendAnimationRequest(ANIM_AGENT_AIM_BAZOOKA_R, ANIM_REQUEST_START); + } + if (mAvatarObject->mSignaledAnimations.find(ANIM_AGENT_HOLD_BOW_L) != mAvatarObject->mSignaledAnimations.end()) + { + sendAnimationRequest(ANIM_AGENT_HOLD_BOW_L, ANIM_REQUEST_STOP); + sendAnimationRequest(ANIM_AGENT_AIM_BOW_L, ANIM_REQUEST_START); + } + } + if (mAvatarObject->getParent()) + { + LLVector3 at_axis = gCamera->getAtAxis(); + LLViewerObject* root_object = (LLViewerObject*)mAvatarObject->getRoot(); + if (root_object->flagCameraDecoupled()) + { + resetAxes(at_axis); + } + else + { + resetAxes(at_axis * ~((LLViewerObject*)mAvatarObject->getParent())->getRenderRotation()); + } + } + } + + } + else if (mCameraMode == CAMERA_MODE_CUSTOMIZE_AVATAR) + { + gCurrentToolset = gFaceEditToolset; + gToolMgr->useSelectedTool( gFaceEditToolset ); + + gFloaterMap->pushVisible(FALSE); + /* + LLView *view; + for (view = gFloaterView->getFirstChild(); view; view = gFloaterView->getNextChild()) + { + view->pushVisible(FALSE); + } + */ + + if( gMorphView ) + { + gMorphView->setVisible( TRUE ); + } + + // freeze avatar + if (mAvatarObject) + { + mPauseRequest = mAvatarObject->requestPause(); + } + } + + if (getAvatarObject()) + { + getAvatarObject()->updateAttachmentVisibility(mCameraMode); + } + + gFloaterTools->dirty(); + + // Don't let this be called more than once if the camera + // mode hasn't changed. --JC + mLastCameraMode = mCameraMode; +} + + +//----------------------------------------------------------------------------- +// updateCamera() +//----------------------------------------------------------------------------- +void LLAgent::updateCamera() +{ + //Ventrella - changed camera_skyward to the new global "mCameraUpVector" + mCameraUpVector = LLVector3::z_axis; + //LLVector3 camera_skyward(0.f, 0.f, 1.f); + //end Ventrella + + U32 camera_mode = mCameraAnimating ? mLastCameraMode : mCameraMode; + + validateFocusObject(); + + if (!mAvatarObject.isNull() && + mAvatarObject->mIsSitting && + camera_mode == CAMERA_MODE_MOUSELOOK) + { + //Ventrella + //changed camera_skyward to the new global "mCameraUpVector" + mCameraUpVector = mCameraUpVector * mAvatarObject->getRenderRotation(); + //end Ventrella + } + + if (cameraThirdPerson() && mFocusOnAvatar && LLFollowCamMgr::getActiveFollowCamParams()) + { + changeCameraToFollow(); + } + + //Ventrella + //NOTE - this needs to be integrated into a general upVector system here within llAgent. + if ( camera_mode == CAMERA_MODE_FOLLOW && mFocusOnAvatar ) + { + mCameraUpVector = mFollowCam.getUpVector(); + } + //end Ventrella + + if (mSitCameraEnabled) + { + if (mSitCameraReferenceObject->isDead()) + { + setSitCamera(LLUUID::null); + } + } + + // Update UI with our camera inputs + if (gFloaterCamera) + { + gFloaterCamera->mRotate->setToggleState( + mOrbitRightKey > 0.f, // left + mOrbitUpKey > 0.f, // top + mOrbitLeftKey > 0.f, // right + mOrbitDownKey > 0.f); // bottom + + gFloaterCamera->mZoom->setToggleState( + mOrbitInKey > 0.f, // top + mOrbitOutKey > 0.f); // bottom + + gFloaterCamera->mTrack->setToggleState( + mPanLeftKey > 0.f, // left + mPanUpKey > 0.f, // top + mPanRightKey > 0.f, // right + mPanDownKey > 0.f); // bottom + } + + // Handle camera movement based on keyboard. + const F32 ORBIT_OVER_RATE = 90.f * DEG_TO_RAD; // radians per second + const F32 ORBIT_AROUND_RATE = 90.f * DEG_TO_RAD; // radians per second + const F32 PAN_RATE = 5.f; // meters per second + + if( mOrbitUpKey || mOrbitDownKey ) + { + F32 input_rate = mOrbitUpKey - mOrbitDownKey; + cameraOrbitOver( input_rate * ORBIT_OVER_RATE / gFPSClamped ); + } + + if( mOrbitLeftKey || mOrbitRightKey) + { + F32 input_rate = mOrbitLeftKey - mOrbitRightKey; + cameraOrbitAround( input_rate * ORBIT_AROUND_RATE / gFPSClamped ); + } + + if( mOrbitInKey || mOrbitOutKey ) + { + F32 input_rate = mOrbitInKey - mOrbitOutKey; + + LLVector3d to_focus = gAgent.getPosGlobalFromAgent(gCamera->getOrigin()) - calcFocusPositionTargetGlobal(); + F32 distance_to_focus = (F32)to_focus.magVec(); + // Move at distance (in meters) meters per second + cameraOrbitIn( input_rate * distance_to_focus / gFPSClamped ); + } + + if( mPanInKey || mPanOutKey ) + { + F32 input_rate = mPanInKey - mPanOutKey; + cameraPanIn( input_rate * PAN_RATE / gFPSClamped ); + } + + if( mPanRightKey || mPanLeftKey ) + { + F32 input_rate = mPanRightKey - mPanLeftKey; + cameraPanLeft( input_rate * -PAN_RATE / gFPSClamped ); + } + + if( mPanUpKey || mPanDownKey ) + { + F32 input_rate = mPanUpKey - mPanDownKey; + cameraPanUp( input_rate * PAN_RATE / gFPSClamped ); + } + + // Clear camera keyboard keys. + mOrbitLeftKey = 0.f; + mOrbitRightKey = 0.f; + mOrbitUpKey = 0.f; + mOrbitDownKey = 0.f; + mOrbitInKey = 0.f; + mOrbitOutKey = 0.f; + + mPanRightKey = 0.f; + mPanLeftKey = 0.f; + mPanUpKey = 0.f; + mPanDownKey = 0.f; + mPanInKey = 0.f; + mPanOutKey = 0.f; + + // lerp camera focus offset + mCameraFocusOffset = lerp(mCameraFocusOffset, mCameraFocusOffsetTarget, LLCriticalDamp::getInterpolant(CAMERA_FOCUS_HALF_LIFE)); + + //Ventrella + if ( mCameraMode == CAMERA_MODE_FOLLOW ) + { + if ( !mAvatarObject.isNull() ) + { + //-------------------------------------------------------------------------------- + // this is where the avatar's position and rotation are given to followCam, and + // where it is updated. All three of its attributes are updated: (1) position, + // (2) focus, and (3) upvector. They can then be queried elsewhere in llAgent. + //-------------------------------------------------------------------------------- + //FIXME: use combined rotation of frameagent and sit object + LLQuaternion avatarRotationForFollowCam = mAvatarObject->mIsSitting ? mAvatarObject->getRenderRotation() : mFrameAgent.getQuaternion(); + + LLFollowCamParams* current_cam = LLFollowCamMgr::getActiveFollowCamParams(); + if (current_cam) + { + mFollowCam.copyParams(*current_cam); + mFollowCam.setSubjectPositionAndRotation( mAvatarObject->getRenderPosition(), avatarRotationForFollowCam ); + mFollowCam.update(); + } + else + { + changeCameraToThirdPerson(TRUE); + } + } + } + // end Ventrella + + BOOL hit_limit; + LLVector3d camera_pos_global; + LLVector3d camera_target_global = calcCameraPositionTargetGlobal(&hit_limit); + mCameraVirtualPositionAgent = getPosAgentFromGlobal(camera_target_global); + LLVector3d focus_target_global = calcFocusPositionTargetGlobal(); + + // perform field of view correction + mCameraFOVZoomFactor = calcCameraFOVZoomFactor(); + camera_target_global = focus_target_global + (camera_target_global - focus_target_global) * (1.f + mCameraFOVZoomFactor); + + // do alpha fade on focus object + F32 fade_increment = mFocusObjectFadeTimer.getElapsedTimeAndResetF32(); + + if (mFocusObject.notNull() && !mFocusObject->isAttachment() && mFocusObject->mDrawable.notNull()) + { + F32 increment = fade_increment; + if (mFocusObjectDist < -0.2f) + { + increment *= -1.f; + } + + if (mFocusObject->getVObjRadius() > MIN_RADIUS_ALPHA_SIZZLE) + { + S32 num_faces = mFocusObject->mDrawable->getNumFaces(); + for (S32 i = 0; i < num_faces; i++) + { + LLFace* facep = mFocusObject->mDrawable->getFace(i); + F32 fade = facep->mAlphaFade; + fade = llclamp(fade + increment, 0.f, 1.f); + facep->mAlphaFade = fade; + } + } + } + + // do alpha fade in on fade objects + std::set< LLPointer<LLViewerObject> >::iterator fade_object_it; + for (fade_object_it = mFadeObjects.begin(); fade_object_it != mFadeObjects.end(); ) + { + LLViewerObject* fade_object = *fade_object_it; + if (fade_object->isDead()) + { + // remove from list + mFadeObjects.erase(fade_object_it++); + } + else + { + LLDrawable* drawablep = fade_object->mDrawable; + if (drawablep && fade_object->getVObjRadius() > MIN_RADIUS_ALPHA_SIZZLE) + { + S32 num_faces = drawablep->getNumFaces(); + BOOL fade_done = TRUE; + for (S32 i = 0; i < num_faces; i++) + { + LLFace* facep = drawablep->getFace(i); + F32 fade = facep->mAlphaFade; + fade = llclamp(fade - fade_increment, 0.f, 1.f); + facep->mAlphaFade = fade; + if (fade > 0.f) + { + fade_done = FALSE; + } + } + if (fade_done) + { + mFadeObjects.erase(fade_object_it++); + } + else + { + fade_object_it++; + } + } + else + { + fade_object_it++; + } + } + } + + mShowAvatar = TRUE; // can see avatar by default + + // Adjust position for animation + if (mCameraAnimating) + { + F32 time = mAnimationTimer.getElapsedTimeF32(); + + // yet another instance of critically damped motion, hooray! + // F32 fraction_of_animation = 1.f - pow(2.f, -time / CAMERA_ZOOM_HALF_LIFE); + + // linear interpolation + F32 fraction_of_animation = time / mAnimationDuration; + + BOOL isfirstPerson = mCameraMode == CAMERA_MODE_MOUSELOOK; + BOOL wasfirstPerson = mLastCameraMode == CAMERA_MODE_MOUSELOOK; + F32 fraction_animation_to_skip; + + if (mAnimationCameraStartGlobal == camera_target_global) + { + fraction_animation_to_skip = 0.f; + } + else + { + LLVector3d cam_delta = mAnimationCameraStartGlobal - camera_target_global; + fraction_animation_to_skip = HEAD_BUFFER_SIZE / (F32)cam_delta.magVec(); + } + F32 animation_start_fraction = (wasfirstPerson) ? fraction_animation_to_skip : 0.f; + F32 animation_finish_fraction = (isfirstPerson) ? (1.f - fraction_animation_to_skip) : 1.f; + + if (fraction_of_animation < animation_finish_fraction) + { + if (fraction_of_animation < animation_start_fraction || fraction_of_animation > animation_finish_fraction ) + { + mShowAvatar = FALSE; + } + + // ...adjust position for animation + camera_pos_global = lerp(mAnimationCameraStartGlobal, camera_target_global, fraction_of_animation); + mFocusGlobal = lerp(mAnimationFocusStartGlobal, focus_target_global, fraction_of_animation); + } + else + { + // ...animation complete + mCameraAnimating = FALSE; + + camera_pos_global = camera_target_global; + mFocusGlobal = focus_target_global; + + endAnimationUpdateUI(); + mShowAvatar = TRUE; + } + + if (getAvatarObject() && mCameraMode != CAMERA_MODE_MOUSELOOK) + { + getAvatarObject()->updateAttachmentVisibility(mCameraMode); + } + } + else + { + camera_pos_global = camera_target_global; + mFocusGlobal = focus_target_global; + mShowAvatar = TRUE; + } + + mCameraCurrentFOVZoomFactor = lerp(mCameraCurrentFOVZoomFactor, mCameraFOVZoomFactor, LLCriticalDamp::getInterpolant(FOV_ZOOM_HALF_LIFE)); + +// llinfos << "Current FOV Zoom: " << mCameraCurrentFOVZoomFactor << " Target FOV Zoom: " << mCameraFOVZoomFactor << " Object penetration: " << mFocusObjectDist << llendl; + + F32 ui_offset = 0.f; + if( CAMERA_MODE_CUSTOMIZE_AVATAR == mCameraMode ) + { + ui_offset = calcCustomizeAvatarUIOffset( camera_pos_global ); + } + + + LLVector3 focus_agent = getPosAgentFromGlobal(mFocusGlobal); + + mCameraPositionAgent = getPosAgentFromGlobal(camera_pos_global); + + // Move the camera + + //Ventrella + gCamera->updateCameraLocation(mCameraPositionAgent, mCameraUpVector, focus_agent); + //gCamera->updateCameraLocation(mCameraPositionAgent, camera_skyward, focus_agent); + //end Ventrella + + //RN: translate UI offset after camera is oriented properly + gCamera->translate(gCamera->getLeftAxis() * ui_offset); + + // Change FOV + gCamera->setView(gCamera->getDefaultFOV() / (1.f + mCameraCurrentFOVZoomFactor)); + + // follow camera when in customize mode + if (cameraCustomizeAvatar()) + { + setLookAt(LOOKAT_TARGET_FOCUS, NULL, mCameraPositionAgent); + } + + // update the travel distance stat + // this isn't directly related to the camera + // but this seemed like the best place to do this + LLVector3d global_pos = getPositionGlobal(); + if (! mLastPositionGlobal.isExactlyZero()) + { + LLVector3d delta = global_pos - mLastPositionGlobal; + mDistanceTraveled += delta.magVec(); + } + mLastPositionGlobal = global_pos; + + if (LLVOAvatar::sVisibleInFirstPerson && mAvatarObject.notNull() && !mAvatarObject->mIsSitting && cameraMouselook()) + { + LLVector3 head_pos = mAvatarObject->mHeadp->getWorldPosition() + + LLVector3(0.08f, 0.f, 0.05f) * mAvatarObject->mHeadp->getWorldRotation() + + LLVector3(0.1f, 0.f, 0.f) * mAvatarObject->mPelvisp->getWorldRotation(); + LLVector3 diff = mCameraPositionAgent - head_pos; + diff = diff * ~mAvatarObject->mRoot.getWorldRotation(); + + LLJoint* torso_joint = mAvatarObject->mTorsop; + LLJoint* chest_joint = mAvatarObject->mChestp; + LLVector3 torso_scale = torso_joint->getScale(); + LLVector3 chest_scale = chest_joint->getScale(); + + // shorten avatar skeleton to avoid foot interpenetration + if (!mAvatarObject->mInAir) + { + LLVector3 chest_offset = LLVector3(0.f, 0.f, chest_joint->getPosition().mV[VZ]) * torso_joint->getWorldRotation(); + F32 z_compensate = llclamp(-diff.mV[VZ], -0.2f, 1.f); + F32 scale_factor = llclamp(1.f - ((z_compensate * 0.5f) / chest_offset.mV[VZ]), 0.5f, 1.2f); + torso_joint->setScale(LLVector3(1.f, 1.f, scale_factor)); + + LLJoint* neck_joint = mAvatarObject->mNeckp; + LLVector3 neck_offset = LLVector3(0.f, 0.f, neck_joint->getPosition().mV[VZ]) * chest_joint->getWorldRotation(); + scale_factor = llclamp(1.f - ((z_compensate * 0.5f) / neck_offset.mV[VZ]), 0.5f, 1.2f); + chest_joint->setScale(LLVector3(1.f, 1.f, scale_factor)); + diff.mV[VZ] = 0.f; + } + + mAvatarObject->mPelvisp->setPosition(mAvatarObject->mPelvisp->getPosition() + diff); + + mAvatarObject->mRoot.updateWorldMatrixChildren(); + + for(LLViewerJointAttachment *attachment = mAvatarObject->mAttachmentPoints.getFirstData(); + attachment; + attachment = mAvatarObject->mAttachmentPoints.getNextData()) + { + LLViewerObject *attached_object = attachment->getObject(0); + if (attached_object && !attached_object->isDead() && attached_object->mDrawable.notNull()) + { + // clear any existing "early" movements of attachment + attached_object->mDrawable->clearState(LLDrawable::EARLY_MOVE); + gPipeline.updateMoveNormalAsync(attached_object->mDrawable); + attached_object->updateText(); + } + } + + torso_joint->setScale(torso_scale); + chest_joint->setScale(chest_scale); + } +} + +void LLAgent::updateFocusOffset() +{ + validateFocusObject(); + if (mFocusObject.notNull()) + { + LLVector3d obj_pos = getPosGlobalFromAgent(mFocusObject->getRenderPosition()); + mFocusObjectOffset.setVec(mFocusTargetGlobal - obj_pos); + } +} + +void LLAgent::validateFocusObject() +{ + if (mFocusObject.notNull() && + (mFocusObject->isDead())) + { + mFocusObjectOffset.clearVec(); + clearFocusObject(); + mCameraFOVZoomFactor = 0.f; + } +} + +//----------------------------------------------------------------------------- +// calcCustomizeAvatarUIOffset() +//----------------------------------------------------------------------------- +F32 LLAgent::calcCustomizeAvatarUIOffset( const LLVector3d& camera_pos_global ) +{ + F32 ui_offset = 0.f; + + if( gFloaterCustomize ) + { + const LLRect& rect = gFloaterCustomize->getRect(); + + // Move the camera so that the avatar isn't covered up by this floater. + F32 fraction_of_fov = 0.5f - (0.5f * (1.f - llmin(1.f, ((F32)rect.getWidth() / (F32)gViewerWindow->getWindowWidth())))); + F32 apparent_angle = fraction_of_fov * gCamera->getView() * gCamera->getAspect(); // radians + F32 offset = tan(apparent_angle); + + if( rect.mLeft < (gViewerWindow->getWindowWidth() - rect.mRight) ) + { + // Move the avatar to the right (camera to the left) + ui_offset = offset; + } + else + { + // Move the avatar to the left (camera to the right) + ui_offset = -offset; + } + } + F32 range = (F32)dist_vec(camera_pos_global, gAgent.getFocusGlobal()); + mUIOffset = lerp(mUIOffset, ui_offset, LLCriticalDamp::getInterpolant(0.05f)); + return mUIOffset * range; +} + +//----------------------------------------------------------------------------- +// calcFocusPositionTargetGlobal() +//----------------------------------------------------------------------------- +LLVector3d LLAgent::calcFocusPositionTargetGlobal() +{ + if (mFocusObject.notNull() && mFocusObject->isDead()) + { + clearFocusObject(); + } + + // Ventrella + if ( mCameraMode == CAMERA_MODE_FOLLOW && mFocusOnAvatar ) + { + mFocusTargetGlobal = gAgent.getPosGlobalFromAgent(mFollowCam.getSimulatedFocus()); + return mFocusTargetGlobal; + }// End Ventrella + else if (mCameraMode == CAMERA_MODE_MOUSELOOK) + { + LLVector3d at_axis(1.0, 0.0, 0.0); + LLQuaternion agent_rot = mFrameAgent.getQuaternion(); + if (!mAvatarObject.isNull() && mAvatarObject->getParent()) + { + LLViewerObject* root_object = (LLViewerObject*)mAvatarObject->getRoot(); + if (!root_object->flagCameraDecoupled()) + { + agent_rot *= ((LLViewerObject*)(mAvatarObject->getParent()))->getRenderRotation(); + } + } + at_axis = at_axis * agent_rot; + mFocusTargetGlobal = calcCameraPositionTargetGlobal() + at_axis; + return mFocusTargetGlobal; + } + else if (mCameraMode == CAMERA_MODE_CUSTOMIZE_AVATAR) + { + return mFocusTargetGlobal; + } + else if (!mFocusOnAvatar) + { + if (mFocusObject.notNull() && !mFocusObject->isDead() && mFocusObject->mDrawable.notNull()) + { + LLDrawable* drawablep = mFocusObject->mDrawable; + + if (mTrackFocusObject && drawablep && drawablep->isActive()) + { + if (mFocusObject->isSelected()) + { + gPipeline.updateMoveNormalAsync(drawablep); + } + else + { + if (drawablep->isState(LLDrawable::MOVE_UNDAMPED)) + { + gPipeline.updateMoveNormalAsync(drawablep); + } + else + { + gPipeline.updateMoveDampedAsync(drawablep); + } + } + } + // if not tracking object, update offset based on new object position + else + { + updateFocusOffset(); + } + LLVector3 focus_agent = mFocusObject->getRenderPosition() + mFocusObjectOffset; + mFocusTargetGlobal.setVec(getPosGlobalFromAgent(focus_agent)); + //FIXME: get camera pointat behavior working + //if (mTrackFocusObject) + //{ + // mCameraFocusOffset = gAgent.getPosGlobalFromAgent(gCamera->getOrigin()) - mFocusTargetGlobal; + //} + } + return mFocusTargetGlobal; + } + else if (mSitCameraEnabled && mAvatarObject.notNull() && mAvatarObject->mIsSitting && mSitCameraReferenceObject.notNull()) + { + // sit camera + LLVector3 object_pos = mSitCameraReferenceObject->getRenderPosition(); + LLQuaternion object_rot = mSitCameraReferenceObject->getRenderRotation(); + + LLVector3 target_pos = object_pos + (mSitCameraFocus * object_rot); + return getPosGlobalFromAgent(target_pos); + } + else + { + // ...offset from avatar + LLVector3d focus_offset; + focus_offset.setVec(gSavedSettings.getVector3("FocusOffsetDefault")); + + LLQuaternion agent_rot = mFrameAgent.getQuaternion(); + if (!mAvatarObject.isNull() && mAvatarObject->getParent()) + { + agent_rot *= ((LLViewerObject*)(mAvatarObject->getParent()))->getRenderRotation(); + } + + focus_offset = focus_offset * agent_rot; + + return getPositionGlobal() + focus_offset; + } +} + +void LLAgent::setupSitCamera() +{ + // agent frame entering this function is in world coordinates + if (mAvatarObject.notNull() && mAvatarObject->getParent()) + { + LLQuaternion parent_rot = ((LLViewerObject*)mAvatarObject->getParent())->getRenderRotation(); + // slam agent coordinate frame to proper parent local version + LLVector3 at_axis = mFrameAgent.getAtAxis(); + at_axis.mV[VZ] = 0.f; + at_axis.normVec(); + resetAxes(at_axis * ~parent_rot); + } +} + +//----------------------------------------------------------------------------- +// getCameraPositionAgent() +//----------------------------------------------------------------------------- +const LLVector3 &LLAgent::getCameraPositionAgent() const +{ + return gCamera->getOrigin(); +} + +//----------------------------------------------------------------------------- +// getCameraPositionGlobal() +//----------------------------------------------------------------------------- +LLVector3d LLAgent::getCameraPositionGlobal() const +{ + if (gCamera) + { + return getPosGlobalFromAgent(gCamera->getOrigin()); + } + else + { + return (LLVector3d::zero); + } +} + +//----------------------------------------------------------------------------- +// calcCameraFOVZoomFactor() +//----------------------------------------------------------------------------- +F32 LLAgent::calcCameraFOVZoomFactor() +{ + LLVector3 camera_offset_dir; + camera_offset_dir.setVec(mCameraFocusOffset); + + if (mCameraMode == CAMERA_MODE_MOUSELOOK) + { + return 0.f; + } + else if (mFocusObject.notNull() && !mFocusObject->isAvatar()) + { + // don't FOV zoom on mostly transparent objects + LLVector3 focus_offset = mFocusObjectOffset; + F32 obj_min_dist = 0.f; + calcCameraMinDistance(obj_min_dist); + F32 current_distance = llmax(0.001f, camera_offset_dir.magVec()); + + mFocusObjectDist = obj_min_dist - current_distance; + + F32 new_fov_zoom = llclamp(mFocusObjectDist / current_distance, 0.f, 1000.f); + return new_fov_zoom; + } + else // focusing on land or avatar + { + // keep old field of view until user changes focus explicitly + return mCameraFOVZoomFactor; + //return 0.f; + } +} + +//----------------------------------------------------------------------------- +// calcCameraPositionTargetGlobal() +//----------------------------------------------------------------------------- +LLVector3d LLAgent::calcCameraPositionTargetGlobal(BOOL *hit_limit) +{ + // Compute base camera position and look-at points. + F32 camera_land_height; + LLVector3d frame_center_global = mAvatarObject.isNull() ? getPositionGlobal() + : getPosGlobalFromAgent(mAvatarObject->mRoot.getWorldPosition()); + + LLVector3 upAxis = getUpAxis(); + BOOL isConstrained = FALSE; + LLVector3d head_offset; + head_offset.setVec(mThirdPersonHeadOffset); + + LLVector3d camera_position_global; + + // Ventrella + if ( mCameraMode == CAMERA_MODE_FOLLOW && mFocusOnAvatar ) + { + camera_position_global = gAgent.getPosGlobalFromAgent(mFollowCam.getSimulatedPosition()); + }// End Ventrella + else if (mCameraMode == CAMERA_MODE_MOUSELOOK) + { + if (mAvatarObject.isNull() || mAvatarObject->mDrawable.isNull()) + { + llwarns << "Null avatar drawable!" << llendl; + return LLVector3d::zero; + } + head_offset.clearVec(); + if (mAvatarObject->mIsSitting && mAvatarObject->getParent()) + { + mAvatarObject->updateHeadOffset(); + head_offset.mdV[VX] = mAvatarObject->mHeadOffset.mV[VX]; + head_offset.mdV[VY] = mAvatarObject->mHeadOffset.mV[VY]; + head_offset.mdV[VZ] = mAvatarObject->mHeadOffset.mV[VZ] + 0.1f; + const LLMatrix4& mat = ((LLViewerObject*) mAvatarObject->getParent())->getRenderMatrix(); + camera_position_global = getPosGlobalFromAgent + ((mAvatarObject->getPosition()+ + LLVector3(head_offset)*mAvatarObject->getRotation()) * mat); + } + else + { + head_offset.mdV[VZ] = mAvatarObject->mHeadOffset.mV[VZ]; + if (mAvatarObject->mIsSitting) + { + head_offset.mdV[VZ] += 0.1; + } + camera_position_global = getPosGlobalFromAgent(mAvatarObject->getRenderPosition());//frame_center_global; + head_offset = head_offset * mAvatarObject->getRenderRotation(); + camera_position_global = camera_position_global + head_offset; + } + } + else if (mCameraMode == CAMERA_MODE_THIRD_PERSON && mFocusOnAvatar) + { + LLVector3 local_camera_offset; + F32 camera_distance = 0.f; + + if (mSitCameraEnabled + && mAvatarObject.notNull() + && mAvatarObject->mIsSitting + && mSitCameraReferenceObject.notNull()) + { + // sit camera + LLVector3 object_pos = mSitCameraReferenceObject->getRenderPosition(); + LLQuaternion object_rot = mSitCameraReferenceObject->getRenderRotation(); + + LLVector3 target_pos = object_pos + (mSitCameraPos * object_rot); + + camera_position_global = getPosGlobalFromAgent(target_pos); + } + else + { + local_camera_offset = mCameraZoomFraction * mCameraOffsetDefault; + + // are we sitting down? + if (mAvatarObject.notNull() && mAvatarObject->getParent()) + { + LLQuaternion parent_rot = ((LLViewerObject*)mAvatarObject->getParent())->getRenderRotation(); + // slam agent coordinate frame to proper parent local version + LLVector3 at_axis = mFrameAgent.getAtAxis() * parent_rot; + at_axis.mV[VZ] = 0.f; + at_axis.normVec(); + resetAxes(at_axis * ~parent_rot); + + local_camera_offset = local_camera_offset * mFrameAgent.getQuaternion() * parent_rot; + } + else + { + local_camera_offset = mFrameAgent.rotateToAbsolute( local_camera_offset ); + } + + if (!mCameraCollidePlane.isExactlyZero() && (mAvatarObject.isNull() || !mAvatarObject->mIsSitting)) + { + LLVector3 plane_normal; + plane_normal.setVec(mCameraCollidePlane.mV); + + F32 offset_dot_norm = local_camera_offset * plane_normal; + if (llabs(offset_dot_norm) < 0.001f) + { + offset_dot_norm = 0.001f; + } + + camera_distance = local_camera_offset.normVec(); + + F32 pos_dot_norm = getPosAgentFromGlobal(frame_center_global + head_offset) * plane_normal; + + // if agent is outside the colliding half-plane + if (pos_dot_norm > mCameraCollidePlane.mV[VW]) + { + // check to see if camera is on the opposite side (inside) the half-plane + if (offset_dot_norm + pos_dot_norm < mCameraCollidePlane.mV[VW]) + { + // diminish offset by factor to push it back outside the half-plane + camera_distance *= (pos_dot_norm - mCameraCollidePlane.mV[VW] - CAMERA_COLLIDE_EPSILON) / -offset_dot_norm; + } + } + else + { + if (offset_dot_norm + pos_dot_norm > mCameraCollidePlane.mV[VW]) + { + camera_distance *= (mCameraCollidePlane.mV[VW] - pos_dot_norm - CAMERA_COLLIDE_EPSILON) / offset_dot_norm; + } + } + } + else + { + camera_distance = local_camera_offset.normVec(); + } + + mTargetCameraDistance = llmax(camera_distance, MIN_CAMERA_DISTANCE); + + if (mTargetCameraDistance != mCurrentCameraDistance) + { + F32 camera_lerp_amt = LLCriticalDamp::getInterpolant(CAMERA_ZOOM_HALF_LIFE); + + mCurrentCameraDistance = lerp(mCurrentCameraDistance, mTargetCameraDistance, camera_lerp_amt); + } + + // Make the camera distance current + local_camera_offset *= mCurrentCameraDistance; + + // set the global camera position + LLVector3d camera_offset; + + LLVector3 av_pos = mAvatarObject.isNull() ? LLVector3::zero : mAvatarObject->getRenderPosition(); + camera_offset.setVec( local_camera_offset ); + camera_position_global = frame_center_global + head_offset + camera_offset; + + if (!mAvatarObject.isNull()) + { + LLVector3d camera_lag_d; + F32 lag_interp = LLCriticalDamp::getInterpolant(CAMERA_LAG_HALF_LIFE); + LLVector3 target_lag; + LLVector3 vel = getVelocity(); + + // lag by appropriate amount for flying + F32 time_in_air = mAvatarObject->mTimeInAir.getElapsedTimeF32(); + if(!mCameraAnimating && mAvatarObject->mInAir && time_in_air > GROUND_TO_AIR_CAMERA_TRANSITION_START_TIME) + { + LLVector3 frame_at_axis = mFrameAgent.getAtAxis(); + frame_at_axis -= projected_vec(frame_at_axis, getReferenceUpVector()); + frame_at_axis.normVec(); + + //transition smoothly in air mode, to avoid camera pop + F32 u = (time_in_air - GROUND_TO_AIR_CAMERA_TRANSITION_START_TIME) / GROUND_TO_AIR_CAMERA_TRANSITION_TIME; + u = llclamp(u, 0.f, 1.f); + + lag_interp *= u; + + if (gViewerWindow->getLeftMouseDown() && gLastHitObjectID == mAvatarObject->getID()) + { + // disable camera lag when using mouse-directed steering + target_lag.clearVec(); + } + else + { + target_lag = vel * gSavedSettings.getF32("DynamicCameraStrength") / 30.f; + } + + mCameraLag = lerp(mCameraLag, target_lag, lag_interp); + + F32 lag_dist = mCameraLag.magVec(); + if (lag_dist > MAX_CAMERA_LAG) + { + mCameraLag = mCameraLag * MAX_CAMERA_LAG / lag_dist; + } + + // clamp camera lag so that avatar is always in front + F32 dot = (mCameraLag - (frame_at_axis * (MIN_CAMERA_LAG * u))) * frame_at_axis; + if (dot < -(MIN_CAMERA_LAG * u)) + { + mCameraLag -= (dot + (MIN_CAMERA_LAG * u)) * frame_at_axis; + } + } + else + { + mCameraLag = lerp(mCameraLag, LLVector3::zero, LLCriticalDamp::getInterpolant(0.15f)); + } + + camera_lag_d.setVec(mCameraLag); + camera_position_global = camera_position_global - camera_lag_d; + } + } + } + else + { + LLVector3d focusPosGlobal = calcFocusPositionTargetGlobal(); + // camera gets pushed out later wrt mCameraFOVZoomFactor...this is "raw" value + camera_position_global = focusPosGlobal + mCameraFocusOffset; + } + + if (!LLViewerCamera::sDisableCameraConstraints && !gAgent.isGodlike()) + { + LLViewerRegion* regionp = gWorldPointer->getRegionFromPosGlobal( + camera_position_global); + bool constrain = true; + if(regionp && regionp->canManageEstate()) + { + constrain = false; + } + if(constrain) + { + F32 max_dist = ( CAMERA_MODE_CUSTOMIZE_AVATAR == mCameraMode ) ? + APPEARANCE_MAX_ZOOM : MAX_CAMERA_DISTANCE_FROM_AGENT; + + LLVector3d camera_offset = camera_position_global + - gAgent.getPositionGlobal(); + F32 camera_distance = (F32)camera_offset.magVec(); + + if(camera_distance > max_dist) + { + camera_position_global = gAgent.getPositionGlobal() + + (max_dist / camera_distance) * camera_offset; + isConstrained = TRUE; + } + } + +// JC - Could constrain camera based on parcel stuff here. +// LLViewerRegion *regionp = gWorldPointer->getRegionFromPosGlobal(camera_position_global); +// +// if (regionp && !regionp->mParcelOverlay->isBuildCameraAllowed(regionp->getPosRegionFromGlobal(camera_position_global))) +// { +// camera_position_global = last_position_global; +// +// isConstrained = TRUE; +// } + } + + // Don't let camera go underground + F32 camera_min_off_ground = getCameraMinOffGround(); + + if (gWorldPointer) + { + camera_land_height = gWorldPointer->resolveLandHeightGlobal(camera_position_global); + } + else + { + camera_land_height = 0.f; + } + + if (camera_position_global.mdV[VZ] < camera_land_height + camera_min_off_ground) + { + camera_position_global.mdV[VZ] = camera_land_height + camera_min_off_ground; + + gMinObjectDistance = MIN_NEAR_PLANE; + isConstrained = TRUE; + } + + + if (hit_limit) + { + *hit_limit = isConstrained; + } + + return camera_position_global; +} + + +//----------------------------------------------------------------------------- +// handleScrollWheel() +//----------------------------------------------------------------------------- +void LLAgent::handleScrollWheel(S32 clicks) +{ + if ( mCameraMode == CAMERA_MODE_FOLLOW && gAgent.getFocusOnAvatar()) + { + if ( ! mFollowCam.getPositionLocked() ) // not if the followCam position is locked in place + { + mFollowCam.zoom( clicks ); + if ( mFollowCam.isZoomedToMinimumDistance() ) + { + changeCameraToMouselook(FALSE); + } + } + } + else + { + const F32 ROOT_ROOT_TWO = sqrt(F_SQRT2); + + // Block if camera is animating + if (mCameraAnimating) + { + return; + } + + if (gSelectMgr->getObjectCount() && gSelectMgr->getSelectType() == SELECT_TYPE_HUD) + { + F32 zoom_factor = (F32)pow(0.8, -clicks); + cameraZoomIn(zoom_factor); + } + else if (mFocusOnAvatar && mCameraMode == CAMERA_MODE_THIRD_PERSON) + { + F32 current_zoom_fraction = mTargetCameraDistance / mCameraOffsetDefault.magVec(); + current_zoom_fraction *= 1.f - pow(ROOT_ROOT_TWO, clicks); + + cameraOrbitIn(current_zoom_fraction * mCameraOffsetDefault.magVec()); + } + else + { + F32 current_zoom_fraction = (F32)mCameraFocusOffsetTarget.magVec(); + cameraOrbitIn(current_zoom_fraction * (1.f - pow(ROOT_ROOT_TWO, clicks))); + } + } +} + + +//----------------------------------------------------------------------------- +// getCameraMinOffGround() +//----------------------------------------------------------------------------- +F32 LLAgent::getCameraMinOffGround() +{ + if (mCameraMode == CAMERA_MODE_MOUSELOOK) + { + return 0.f; + } + else + { + if (LLViewerCamera::sDisableCameraConstraints) + { + return -1000.f; + } + else + { + return 0.5f; + } + } +} + + +//----------------------------------------------------------------------------- +// resetCamera() +//----------------------------------------------------------------------------- +void LLAgent::resetCamera() +{ + // Remove any pitch from the avatar + LLVector3 at = mFrameAgent.getAtAxis(); + at.mV[VZ] = 0.f; + at.normVec(); + gAgent.resetAxes(at); + // have to explicitly clear field of view zoom now + mCameraFOVZoomFactor = 0.f; + + updateCamera(); +} + +//----------------------------------------------------------------------------- +// changeCameraToMouselook() +//----------------------------------------------------------------------------- +void LLAgent::changeCameraToMouselook(BOOL animate) +{ + // visibility changes at end of animation + gViewerWindow->getWindow()->resetBusyCount(); + + // unpause avatar animation + mPauseRequest = NULL; + + gCurrentToolset = gMouselookToolset; + gCurrentToolset->selectFirstTool(); + gToolMgr->useSelectedTool( gCurrentToolset ); + + gSavedSettings.setBOOL("FirstPersonBtnState", FALSE); + gSavedSettings.setBOOL("MouselookBtnState", TRUE); + gSavedSettings.setBOOL("ThirdPersonBtnState", FALSE); + gSavedSettings.setBOOL("BuildBtnState", FALSE); + + if (mAvatarObject) + { + mAvatarObject->stopMotion( ANIM_AGENT_BODY_NOISE ); + mAvatarObject->stopMotion( ANIM_AGENT_BREATHE_ROT ); + } + + //gViewerWindow->stopGrab(); + gSelectMgr->deselectAll(); + gViewerWindow->hideCursor(); + gViewerWindow->moveCursorToCenter(); + + if( mCameraMode != CAMERA_MODE_MOUSELOOK ) + { + gViewerWindow->setKeyboardFocus( NULL, NULL ); + + mLastCameraMode = mCameraMode; + mCameraMode = CAMERA_MODE_MOUSELOOK; + U32 old_flags = mControlFlags; + setControlFlags(AGENT_CONTROL_MOUSELOOK); + if (old_flags != mControlFlags) + { + mbFlagsDirty = TRUE; + } + + if (animate) + { + startCameraAnimation(); + } + else + { + mCameraAnimating = FALSE; + endAnimationUpdateUI(); + } + } +} + + +//----------------------------------------------------------------------------- +// changeCameraToDefault() +//----------------------------------------------------------------------------- +void LLAgent::changeCameraToDefault() +{ + if (LLFollowCamMgr::getActiveFollowCamParams()) + { + changeCameraToFollow(); + } + else + { + changeCameraToThirdPerson(); + } +} + + +// Ventrella +//----------------------------------------------------------------------------- +// changeCameraToFollow() +//----------------------------------------------------------------------------- +void LLAgent::changeCameraToFollow(BOOL animate) +{ + if( mCameraMode != CAMERA_MODE_FOLLOW ) + { + if (mCameraMode == CAMERA_MODE_MOUSELOOK) + { + animate = FALSE; + } + startCameraAnimation(); + + mLastCameraMode = mCameraMode; + mCameraMode = CAMERA_MODE_FOLLOW; + + // bang-in the current focus, position, and up vector of the follow cam + mFollowCam.reset( mCameraPositionAgent, gCamera->getPointOfInterest(), LLVector3::z_axis ); + + if (gBasicToolset) + { + gCurrentToolset = gBasicToolset; + gCurrentToolset->selectFirstTool(); + gToolMgr->useSelectedTool( gCurrentToolset ); + } + + if (mAvatarObject) + { + mAvatarObject->mPelvisp->setPosition(LLVector3::zero); + mAvatarObject->startMotion( ANIM_AGENT_BODY_NOISE ); + mAvatarObject->startMotion( ANIM_AGENT_BREATHE_ROT ); + } + + gSavedSettings.setBOOL("FirstPersonBtnState", FALSE); + gSavedSettings.setBOOL("MouselookBtnState", FALSE); + gSavedSettings.setBOOL("ThirdPersonBtnState", TRUE); + gSavedSettings.setBOOL("BuildBtnState", FALSE); + + // unpause avatar animation + mPauseRequest = NULL; + + U32 old_flags = mControlFlags; + clearControlFlags(AGENT_CONTROL_MOUSELOOK); + if (old_flags != mControlFlags) + { + mbFlagsDirty = TRUE; + } + + //RN: this doesn't seem to be necessary and destroys the UE for script-driven cameras + //gViewerWindow->setKeyboardFocus( NULL, NULL ); + //gViewerWindow->setMouseCapture( NULL, NULL ); + + if (animate) + { + startCameraAnimation(); + } + else + { + mCameraAnimating = FALSE; + endAnimationUpdateUI(); + } + } +} + +//----------------------------------------------------------------------------- +// changeCameraToThirdPerson() +//----------------------------------------------------------------------------- +void LLAgent::changeCameraToThirdPerson(BOOL animate) +{ +//printf( "changeCameraToThirdPerson\n" ); + + gViewerWindow->getWindow()->resetBusyCount(); + + mCameraZoomFraction = INITIAL_ZOOM_FRACTION; + + if (mAvatarObject) + { + mAvatarObject->mPelvisp->setPosition(LLVector3::zero); + mAvatarObject->startMotion( ANIM_AGENT_BODY_NOISE ); + mAvatarObject->startMotion( ANIM_AGENT_BREATHE_ROT ); + } + + gSavedSettings.setBOOL("FirstPersonBtnState", FALSE); + gSavedSettings.setBOOL("MouselookBtnState", FALSE); + gSavedSettings.setBOOL("ThirdPersonBtnState", TRUE); + gSavedSettings.setBOOL("BuildBtnState", FALSE); + + LLVector3 at_axis; + + // unpause avatar animation + mPauseRequest = NULL; + + if( mCameraMode != CAMERA_MODE_THIRD_PERSON ) + { + if (gBasicToolset) + { + gCurrentToolset = gBasicToolset; + gCurrentToolset->selectFirstTool(); + gToolMgr->useSelectedTool( gCurrentToolset ); + } + + mCameraLag.clearVec(); + if (mCameraMode == CAMERA_MODE_MOUSELOOK) + { + mCurrentCameraDistance = MIN_CAMERA_DISTANCE; + mTargetCameraDistance = MIN_CAMERA_DISTANCE; + animate = FALSE; + } + mLastCameraMode = mCameraMode; + mCameraMode = CAMERA_MODE_THIRD_PERSON; + U32 old_flags = mControlFlags; + clearControlFlags(AGENT_CONTROL_MOUSELOOK); + if (old_flags != mControlFlags) + { + mbFlagsDirty = TRUE; + } + + //RN: this doesn't seem to be necessary and destroys the UE for script-driven cameras + //gViewerWindow->setKeyboardFocus( NULL, NULL ); + //gViewerWindow->setMouseCapture( NULL, NULL ); + } + + // Remove any pitch from the avatar + if (!mAvatarObject.isNull() && mAvatarObject->getParent()) + { + LLQuaternion obj_rot = ((LLViewerObject*)mAvatarObject->getParent())->getRenderRotation(); + at_axis = gCamera->getAtAxis(); + at_axis.mV[VZ] = 0.f; + at_axis.normVec(); + resetAxes(at_axis * ~obj_rot); + } + else + { + at_axis = mFrameAgent.getAtAxis(); + at_axis.mV[VZ] = 0.f; + at_axis.normVec(); + resetAxes(at_axis); + } + + + if (animate) + { + startCameraAnimation(); + } + else + { + mCameraAnimating = FALSE; + endAnimationUpdateUI(); + } +} + +//----------------------------------------------------------------------------- +// changeCameraToCustomizeAvatar() +//----------------------------------------------------------------------------- +void LLAgent::changeCameraToCustomizeAvatar(BOOL animate) +{ + setControlFlags(AGENT_CONTROL_STAND_UP); // force stand up + gViewerWindow->getWindow()->resetBusyCount(); + + if (gFaceEditToolset) + { + gCurrentToolset = gFaceEditToolset; + gCurrentToolset->selectFirstTool(); + gToolMgr->useSelectedTool( gCurrentToolset ); + } + + gSavedSettings.setBOOL("FirstPersonBtnState", FALSE); + gSavedSettings.setBOOL("MouselookBtnState", FALSE); + gSavedSettings.setBOOL("ThirdPersonBtnState", FALSE); + gSavedSettings.setBOOL("BuildBtnState", FALSE); + + if (animate) + { + startCameraAnimation(); + } + + // Remove any pitch from the avatar + LLVector3 at = mFrameAgent.getAtAxis(); + at.mV[VZ] = 0.f; + at.normVec(); + gAgent.resetAxes(at); + + if( mCameraMode != CAMERA_MODE_CUSTOMIZE_AVATAR ) + { + mLastCameraMode = mCameraMode; + mCameraMode = CAMERA_MODE_CUSTOMIZE_AVATAR; + U32 old_flags = mControlFlags; + clearControlFlags(AGENT_CONTROL_MOUSELOOK); + if (old_flags != mControlFlags) + { + mbFlagsDirty = TRUE; + } + + gViewerWindow->setKeyboardFocus( NULL, NULL ); + gViewerWindow->setMouseCapture( NULL, NULL ); + + LLVOAvatar::onCustomizeStart(); + } + + if (animate && !mAvatarObject.isNull()) + { + sendAnimationRequest(ANIM_AGENT_CUSTOMIZE, ANIM_REQUEST_START); + LLMotion* turn_motion = mAvatarObject->findMotion(ANIM_AGENT_CUSTOMIZE); + if (turn_motion) + { + mAnimationDuration = turn_motion->getDuration() + CUSTOMIZE_AVATAR_CAMERA_ANIM_SLOP; + } + else + { + mAnimationDuration = gSavedSettings.getF32("ZoomTime"); + } + gAgent.setFocusGlobal(LLVector3d::zero); + } + else + { + mCameraAnimating = FALSE; + endAnimationUpdateUI(); + } + +} + + +// +// Focus point management +// + +//----------------------------------------------------------------------------- +// startCameraAnimation() +//----------------------------------------------------------------------------- +void LLAgent::startCameraAnimation() +{ + mAnimationCameraStartGlobal = getCameraPositionGlobal(); + mAnimationFocusStartGlobal = mFocusGlobal; + mAnimationTimer.reset(); + mCameraAnimating = TRUE; + mAnimationDuration = gSavedSettings.getF32("ZoomTime"); +} + +//----------------------------------------------------------------------------- +// stopCameraAnimation() +//----------------------------------------------------------------------------- +void LLAgent::stopCameraAnimation() +{ + mCameraAnimating = FALSE; +} + +void LLAgent::clearFocusObject() +{ + if (mFocusObject.notNull()) + { + startCameraAnimation(); + + setFocusObject(NULL); + mFocusObjectOffset.clearVec(); + } +} + +void LLAgent::setFocusObject(LLViewerObject* object) +{ + if (mFocusObject.notNull() && + mFocusObject->mDrawable.notNull() && + mFocusObject->getPCode() == LL_PCODE_VOLUME && + mFocusObject != object) + { + LLPointer<LLViewerObject> fade_object_ptr(mFocusObject); + + if (fade_object_ptr.notNull() && mFadeObjects.find(fade_object_ptr) == mFadeObjects.end()) + { + mFadeObjects.insert(fade_object_ptr); + } + } + + mFocusObject = object; +} + +// Focus on a point, but try to keep camera position stable. +//----------------------------------------------------------------------------- +// setFocusGlobal() +//----------------------------------------------------------------------------- +void LLAgent::setFocusGlobal(const LLVector3d& focus, const LLUUID &object_id) +{ + setFocusObject(gObjectList.findObject(object_id)); + LLVector3d old_focus = mFocusTargetGlobal; + LLViewerObject *focus_obj = mFocusObject; + + // if focus has changed + if (old_focus != focus) + { + if (focus.isExactlyZero()) + { + if (!mAvatarObject.isNull()) + { + mFocusTargetGlobal = getPosGlobalFromAgent(mAvatarObject->mHeadp->getWorldPosition()); + } + else + { + mFocusTargetGlobal = getPositionGlobal(); + } + mCameraFocusOffsetTarget = getCameraPositionGlobal() - mFocusTargetGlobal; + mCameraFocusOffset = mCameraFocusOffsetTarget; + setLookAt(LOOKAT_TARGET_CLEAR); + } + else + { + mFocusTargetGlobal = focus; + if (!focus_obj) + { + mCameraFOVZoomFactor = 0.f; + } + + mCameraFocusOffsetTarget = gAgent.getPosGlobalFromAgent(mCameraVirtualPositionAgent) - mFocusTargetGlobal; + + startCameraAnimation(); + + if (focus_obj) + { + if (focus_obj->isAvatar()) + { + setLookAt(LOOKAT_TARGET_FOCUS, focus_obj); + } + else + { + setLookAt(LOOKAT_TARGET_FOCUS, focus_obj, (getPosAgentFromGlobal(focus) - focus_obj->getRenderPosition()) * ~focus_obj->getRenderRotation()); + } + } + else + { + setLookAt(LOOKAT_TARGET_FOCUS, NULL, getPosAgentFromGlobal(mFocusTargetGlobal)); + } + } + } + else // focus == mFocusTargetGlobal + { + if (focus.isExactlyZero()) + { + if (!mAvatarObject.isNull()) + { + mFocusTargetGlobal = getPosGlobalFromAgent(mAvatarObject->mHeadp->getWorldPosition()); + } + else + { + mFocusTargetGlobal = getPositionGlobal(); + } + } + mCameraFocusOffsetTarget = (getCameraPositionGlobal() - mFocusTargetGlobal) / (1.f + mCameraFOVZoomFactor);; + mCameraFocusOffset = mCameraFocusOffsetTarget; + } + + if (mFocusObject.notNull()) + { + // for attachments, make offset relative to avatar, not the attachment + if (mFocusObject->isAttachment()) + { + while (!mFocusObject->isAvatar()) + { + mFocusObject = (LLViewerObject*) mFocusObject->getParent(); + } + setFocusObject((LLViewerObject*)mFocusObject); + } + updateFocusOffset(); + } +} + +// Used for avatar customization +//----------------------------------------------------------------------------- +// setCameraPosAndFocusGlobal() +//----------------------------------------------------------------------------- +void LLAgent::setCameraPosAndFocusGlobal(const LLVector3d& camera_pos, const LLVector3d& focus, const LLUUID &object_id) +{ + LLVector3d old_focus = mFocusTargetGlobal; + + F64 focus_delta_squared = (old_focus - focus).magVecSquared(); + const F64 ANIM_EPSILON_SQUARED = 0.0001; + if( focus_delta_squared > ANIM_EPSILON_SQUARED ) + { + startCameraAnimation(); + + if( CAMERA_MODE_CUSTOMIZE_AVATAR == mCameraMode ) + { + // Compensate for the fact that the camera has already been offset to make room for LLFloaterCustomize. + mAnimationCameraStartGlobal -= LLVector3d(gCamera->getLeftAxis() * calcCustomizeAvatarUIOffset( mAnimationCameraStartGlobal )); + } + } + + //gCamera->setOrigin( gAgent.getPosAgentFromGlobal( camera_pos ) ); + setFocusObject(gObjectList.findObject(object_id)); + mFocusTargetGlobal = focus; + mCameraFocusOffsetTarget = camera_pos - focus; + mCameraFocusOffset = mCameraFocusOffsetTarget; + + if (mFocusObject) + { + if (mFocusObject->isAvatar()) + { + setLookAt(LOOKAT_TARGET_FOCUS, mFocusObject); + } + else + { + setLookAt(LOOKAT_TARGET_FOCUS, mFocusObject, (getPosAgentFromGlobal(focus) - mFocusObject->getRenderPosition()) * ~mFocusObject->getRenderRotation()); + } + } + else + { + setLookAt(LOOKAT_TARGET_FOCUS, NULL, getPosAgentFromGlobal(mFocusTargetGlobal)); + } + + if( mCameraAnimating ) + { + const F64 ANIM_METERS_PER_SECOND = 10.0; + const F64 MIN_ANIM_SECONDS = 0.5; + F64 anim_duration = llmax( MIN_ANIM_SECONDS, sqrt(focus_delta_squared) / ANIM_METERS_PER_SECOND ); + setAnimationDuration( (F32)anim_duration ); + } + + updateFocusOffset(); +} + +//----------------------------------------------------------------------------- +// setSitCamera() +//----------------------------------------------------------------------------- +void LLAgent::setSitCamera(const LLUUID &object_id, const LLVector3 &camera_pos, const LLVector3 &camera_focus) +{ + BOOL camera_enabled = !object_id.isNull(); + + if (camera_enabled) + { + LLViewerObject *reference_object = gObjectList.findObject(object_id); + if (reference_object) + { + //convert to root object relative? + mSitCameraPos = camera_pos; + mSitCameraFocus = camera_focus; + mSitCameraReferenceObject = reference_object; + mSitCameraEnabled = TRUE; + } + } + else + { + mSitCameraPos.clearVec(); + mSitCameraFocus.clearVec(); + mSitCameraReferenceObject = NULL; + mSitCameraEnabled = FALSE; + } +} + +//----------------------------------------------------------------------------- +// setFocusOnAvatar() +//----------------------------------------------------------------------------- +void LLAgent::setFocusOnAvatar(BOOL focus_on_avatar, BOOL animate) +{ + if (focus_on_avatar != mFocusOnAvatar) + { + if (animate) + { + startCameraAnimation(); + } + else + { + stopCameraAnimation(); + } + } + + //RN: when focused on the avatar, we're not "looking" at it + // looking implies intent while focusing on avatar means + // you're just walking around with a camera on you...eesh. + if (focus_on_avatar && !mFocusOnAvatar) + { + setFocusGlobal(LLVector3d::zero); + mCameraFOVZoomFactor = 0.f; + if (mCameraMode == CAMERA_MODE_THIRD_PERSON) + { + LLVector3 at_axis; + if (!mAvatarObject.isNull() && mAvatarObject->getParent()) + { + LLQuaternion obj_rot = ((LLViewerObject*)mAvatarObject->getParent())->getRenderRotation(); + at_axis = gCamera->getAtAxis(); + at_axis.mV[VZ] = 0.f; + at_axis.normVec(); + resetAxes(at_axis * ~obj_rot); + } + else + { + at_axis = gCamera->getAtAxis(); + at_axis.mV[VZ] = 0.f; + at_axis.normVec(); + resetAxes(at_axis); + } + } + } + + mFocusOnAvatar = focus_on_avatar; +} + +//----------------------------------------------------------------------------- +// heardChat() +//----------------------------------------------------------------------------- +void LLAgent::heardChat(const LLChat& chat) +{ + if (chat.mChatType == CHAT_TYPE_START + || chat.mChatType == CHAT_TYPE_STOP) + { + return; + } + + mLastChatterID = chat.mFromID; + mChatTimer.reset(); + + mNearChatRadius = CHAT_NORMAL_RADIUS / 2.f; +} + +//----------------------------------------------------------------------------- +// lookAtLastChat() +//----------------------------------------------------------------------------- +void LLAgent::lookAtLastChat() +{ + // Block if camera is animating or not in normal third person camera mode + if (mCameraAnimating || !cameraThirdPerson()) + { + return; + } + + LLViewerObject *chatter = gObjectList.findObject(mLastChatterID); + if (chatter) + { + LLVector3 delta_pos; + if (chatter->isAvatar()) + { + LLVOAvatar *chatter_av = (LLVOAvatar*)chatter; + if (!mAvatarObject.isNull() && chatter_av->mHeadp) + { + delta_pos = chatter_av->mHeadp->getWorldPosition() - mAvatarObject->mHeadp->getWorldPosition(); + } + else + { + delta_pos = chatter->getPositionAgent() - getPositionAgent(); + } + delta_pos.normVec(); + + setControlFlags(AGENT_CONTROL_STOP); + + changeCameraToThirdPerson(); + + LLVector3 new_camera_pos = mAvatarObject->mHeadp->getWorldPosition(); + LLVector3 left = delta_pos % LLVector3::z_axis; + left.normVec(); + LLVector3 up = left % delta_pos; + up.normVec(); + new_camera_pos -= delta_pos * 0.4f; + new_camera_pos += left * 0.3f; + new_camera_pos += up * 0.2f; + if (chatter_av->mHeadp) + { + setFocusGlobal(getPosGlobalFromAgent(chatter_av->mHeadp->getWorldPosition()), mLastChatterID); + mCameraFocusOffsetTarget = getPosGlobalFromAgent(new_camera_pos) - gAgent.getPosGlobalFromAgent(chatter_av->mHeadp->getWorldPosition()); + } + else + { + setFocusGlobal(chatter->getPositionGlobal(), mLastChatterID); + mCameraFocusOffsetTarget = getPosGlobalFromAgent(new_camera_pos) - chatter->getPositionGlobal(); + } + setFocusOnAvatar(FALSE, TRUE); + } + else + { + delta_pos = chatter->getRenderPosition() - getPositionAgent(); + delta_pos.normVec(); + + setControlFlags(AGENT_CONTROL_STOP); + + changeCameraToThirdPerson(); + + LLVector3 new_camera_pos = mAvatarObject->mHeadp->getWorldPosition(); + LLVector3 left = delta_pos % LLVector3::z_axis; + left.normVec(); + LLVector3 up = left % delta_pos; + up.normVec(); + new_camera_pos -= delta_pos * 0.4f; + new_camera_pos += left * 0.3f; + new_camera_pos += up * 0.2f; + + setFocusGlobal(chatter->getPositionGlobal(), mLastChatterID); + mCameraFocusOffsetTarget = getPosGlobalFromAgent(new_camera_pos) - chatter->getPositionGlobal(); + setFocusOnAvatar(FALSE, TRUE); + } + } +} + +const F32 SIT_POINT_EXTENTS = 0.2f; + +// Grabs current position +void LLAgent::setStartPosition(U32 location_id) +{ + LLViewerObject *object; + + if ( !(gAgentID == LLUUID::null) ) + { + // we've got an ID for an agent viewerobject + object = gObjectList.findObject(gAgentID); + if (object) + { + // we've got the viewer object + // Sometimes the agent can be velocity interpolated off of + // this simulator. Clamp it to the region the agent is + // in, a little bit in on each side. + const F32 INSET = 0.5f; //meters + const F32 REGION_WIDTH = gWorldPointer->getRegionWidthInMeters(); + + LLVector3 agent_pos = getPositionAgent(); + + if (mAvatarObject) + { + // the z height is at the agent's feet + agent_pos.mV[VZ] -= 0.5f * mAvatarObject->mBodySize.mV[VZ]; + } + + agent_pos.mV[VX] = llclamp( agent_pos.mV[VX], INSET, REGION_WIDTH - INSET ); + agent_pos.mV[VY] = llclamp( agent_pos.mV[VY], INSET, REGION_WIDTH - INSET ); + + // Don't let them go below ground, or too high. + agent_pos.mV[VZ] = llclamp( agent_pos.mV[VZ], + mRegionp->getLandHeightRegion( agent_pos ), + gWorldPointer->getRegionMaxHeight() ); + + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_SetStartLocationRequest); + msg->nextBlockFast( _PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, getID()); + msg->addUUIDFast(_PREHASH_SessionID, getSessionID()); + msg->nextBlockFast( _PREHASH_StartLocationData); + // corrected by sim + msg->addStringFast(_PREHASH_SimName, ""); + msg->addU32Fast(_PREHASH_LocationID, location_id); + msg->addVector3Fast(_PREHASH_LocationPos, agent_pos); + msg->addVector3Fast(_PREHASH_LocationLookAt,mFrameAgent.getAtAxis()); + + // Reliable only helps when setting home location. Last + // location is sent on quit, and we don't have time to ack + // the packets. + msg->sendReliable(mRegionp->getHost()); + + const U32 HOME_INDEX = 1; + if( HOME_INDEX == location_id ) + { + setHomePosRegion( mRegionp->getHandle(), getPositionAgent() ); + } + } + else + { + llinfos << "setStartPosition - Can't find agent viewerobject id " << gAgentID << llendl; + } + } +} + +void LLAgent::requestStopMotion( LLMotion* motion ) +{ + // Notify all avatars that a motion has stopped. + // This is needed to clear the animation state bits + LLUUID anim_state = motion->getID(); + + // if motion is not looping, it could have stopped by running out of time + // so we need to tell the server this +// llinfos << "Sending stop for motion " << motion->getName() << llendl; + sendAnimationRequest( anim_state, ANIM_REQUEST_STOP ); + + // handle automatic state transitions (based on completion of animation playback) + if (anim_state == ANIM_AGENT_STANDUP) + { + // send stand up command + setControlFlags(AGENT_CONTROL_FINISH_ANIM); + + // now trigger dusting self off animation + if (mAvatarObject.notNull() && !mAvatarObject->mBelowWater && rand() % 3 == 0) + sendAnimationRequest( ANIM_AGENT_BRUSH, ANIM_REQUEST_START ); + } + else if (anim_state == ANIM_AGENT_PRE_JUMP || anim_state == ANIM_AGENT_LAND || anim_state == ANIM_AGENT_MEDIUM_LAND) + { + setControlFlags(AGENT_CONTROL_FINISH_ANIM); + } +} + +BOOL LLAgent::isGodlike() const +{ +#ifdef HACKED_GODLIKE_VIEWER + return TRUE; +#else + if(mAdminOverride) return TRUE; + return mGodLevel > GOD_NOT; +#endif +} + +U8 LLAgent::getGodLevel() const +{ +#ifdef HACKED_GODLIKE_VIEWER + return GOD_MAINTENANCE; +#else + if(mAdminOverride) return GOD_FULL; + return mGodLevel; +#endif +} + + +void LLAgent::buildFullname(std::string& name) const +{ + if (mAvatarObject) + { + name = mAvatarObject->getFullname(); + } +} + +void LLAgent::buildFullnameAndTitle(std::string& name) const +{ + if (isGroupMember()) + { + name = mGroupTitle; + name += ' '; + } + else + { + name.erase(0, name.length()); + } + + if (mAvatarObject) + { + name += mAvatarObject->getFullname(); + } +} + +BOOL LLAgent::isInGroup(const LLUUID& group_id) const +{ + S32 count = mGroups.count(); + for(S32 i = 0; i < count; ++i) + { + if(mGroups.get(i).mID == group_id) + { + return TRUE; + } + } + return FALSE; +} + +// This implementation should mirror LLAgentInfo::hasPowerInGroup +BOOL LLAgent::hasPowerInGroup(const LLUUID& group_id, U64 power) const +{ + // GP_NO_POWERS can also mean no power is enough to grant an ability. + if (GP_NO_POWERS == power) return FALSE; + + S32 count = mGroups.count(); + for(S32 i = 0; i < count; ++i) + { + if(mGroups.get(i).mID == group_id) + { + return (BOOL)((mGroups.get(i).mPowers & power) > 0); + } + } + return FALSE; +} + +BOOL LLAgent::hasPowerInActiveGroup(U64 power) const +{ + return (mGroupID.notNull() && (hasPowerInGroup(mGroupID, power))); +} + +U64 LLAgent::getPowerInGroup(const LLUUID& group_id) const +{ + S32 count = mGroups.count(); + for(S32 i = 0; i < count; ++i) + { + if(mGroups.get(i).mID == group_id) + { + return (mGroups.get(i).mPowers); + } + } + + return GP_NO_POWERS; +} + +BOOL LLAgent::getGroupData(const LLUUID& group_id, LLGroupData& data) const +{ + S32 count = mGroups.count(); + for(S32 i = 0; i < count; ++i) + { + if(mGroups.get(i).mID == group_id) + { + data = mGroups.get(i); + return TRUE; + } + } + return FALSE; +} + +S32 LLAgent::getGroupContribution(const LLUUID& group_id) const +{ + S32 count = mGroups.count(); + for(S32 i = 0; i < count; ++i) + { + if(mGroups.get(i).mID == group_id) + { + S32 contribution = mGroups.get(i).mContribution; + return contribution; + } + } + return 0; +} + +BOOL LLAgent::setGroupContribution(const LLUUID& group_id, S32 contribution) +{ + S32 count = mGroups.count(); + for(S32 i = 0; i < count; ++i) + { + if(mGroups.get(i).mID == group_id) + { + mGroups.get(i).mContribution = contribution; + LLMessageSystem* msg = gMessageSystem; + msg->newMessage("SetGroupContribution"); + msg->nextBlock("AgentData"); + msg->addUUID("AgentID", gAgentID); + msg->addUUID("SessionID", gAgentSessionID); + msg->nextBlock("Data"); + msg->addUUID("GroupID", group_id); + msg->addS32("Contribution", contribution); + sendReliableMessage(); + return TRUE; + } + } + return FALSE; +} + +BOOL LLAgent::setGroupAcceptNotices(const LLUUID& group_id, BOOL accept_notices) +{ + S32 count = mGroups.count(); + for(S32 i = 0; i < count; ++i) + { + if(mGroups.get(i).mID == group_id) + { + mGroups.get(i).mAcceptNotices = accept_notices; + LLMessageSystem* msg = gMessageSystem; + msg->newMessage("SetGroupAcceptNotices"); + msg->nextBlock("AgentData"); + msg->addUUID("AgentID", gAgentID); + msg->addUUID("SessionID", gAgentSessionID); + msg->nextBlock("Data"); + msg->addUUID("GroupID", group_id); + msg->addBOOL("AcceptNotices", accept_notices); + sendReliableMessage(); + return TRUE; + } + } + return FALSE; +} + +// utility to build a location string +void LLAgent::buildLocationString(std::string& str) +{ + const LLVector3& agent_pos_region = getPositionAgent(); + S32 pos_x = S32(agent_pos_region.mV[VX]); + S32 pos_y = S32(agent_pos_region.mV[VY]); + S32 pos_z = S32(agent_pos_region.mV[VZ]); + + // Round the numbers based on the velocity + LLVector3 agent_velocity = getVelocity(); + F32 velocity_mag_sq = agent_velocity.magVecSquared(); + + const F32 FLY_CUTOFF = 6.f; // meters/sec + const F32 FLY_CUTOFF_SQ = FLY_CUTOFF * FLY_CUTOFF; + const F32 WALK_CUTOFF = 1.5f; // meters/sec + const F32 WALK_CUTOFF_SQ = WALK_CUTOFF * WALK_CUTOFF; + + if (velocity_mag_sq > FLY_CUTOFF_SQ) + { + pos_x -= pos_x % 4; + pos_y -= pos_y % 4; + } + else if (velocity_mag_sq > WALK_CUTOFF_SQ) + { + pos_x -= pos_x % 2; + pos_y -= pos_y % 2; + } + + // create a defult name and description for the landmark + std::string buffer; + if( !strcmp("", gParcelMgr->getAgentParcelName()) ) + { + // the parcel doesn't have a name + buffer = llformat("%.32s (%d, %d, %d)", + getRegion()->getName().c_str(), + pos_x, pos_y, pos_z); + } + else + { + // the parcel has a name, so include it in the landmark name + buffer = llformat("%.32s, %.32s (%d, %d, %d)", + gParcelMgr->getAgentParcelName(), + getRegion()->getName().c_str(), + pos_x, pos_y, pos_z); + } + str = buffer; +} + +LLQuaternion LLAgent::getHeadRotation() +{ + if (mAvatarObject.isNull() || !mAvatarObject->mPelvisp || !mAvatarObject->mHeadp) + { + return LLQuaternion::DEFAULT; + } + + if (!gAgent.cameraMouselook()) + { + return mAvatarObject->getRotation(); + } + + // We must be in mouselook + LLVector3 look_dir( gCamera->getAtAxis() ); + LLVector3 up = look_dir % mFrameAgent.getLeftAxis(); + LLVector3 left = up % look_dir; + + LLQuaternion rot(look_dir, left, up); + if (mAvatarObject->getParent()) + { + rot = rot * ~mAvatarObject->getParent()->getRotation(); + } + + return rot; +} + +void LLAgent::sendAnimationRequests(LLDynamicArray<LLUUID> &anim_ids, EAnimRequest request) +{ + if (gAgentID.isNull()) + { + return; + } + + S32 num_valid_anims = 0; + + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_AgentAnimation); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, getID()); + msg->addUUIDFast(_PREHASH_SessionID, getSessionID()); + + for (S32 i = 0; i < anim_ids.count(); i++) + { + if (anim_ids[i].isNull()) + { + continue; + } + msg->nextBlockFast(_PREHASH_AnimationList); + msg->addUUIDFast(_PREHASH_AnimID, (anim_ids[i]) ); + msg->addBOOLFast(_PREHASH_StartAnim, (request == ANIM_REQUEST_START) ? TRUE : FALSE); + num_valid_anims++; + } + + if (num_valid_anims) + { + sendReliableMessage(); + } +} + +void LLAgent::sendAnimationRequest(const LLUUID &anim_id, EAnimRequest request) +{ + if (gAgentID.isNull() || anim_id.isNull() || !mRegionp) + { + return; + } + + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_AgentAnimation); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, getID()); + msg->addUUIDFast(_PREHASH_SessionID, getSessionID()); + + msg->nextBlockFast(_PREHASH_AnimationList); + msg->addUUIDFast(_PREHASH_AnimID, (anim_id) ); + msg->addBOOLFast(_PREHASH_StartAnim, (request == ANIM_REQUEST_START) ? TRUE : FALSE); + + sendReliableMessage(); +} + +void LLAgent::friendsChanged() +{ + LLCollectProxyBuddies collector; + LLAvatarTracker::instance().applyFunctor(collector); + mProxyForAgents = collector.mProxy; +} + +BOOL LLAgent::isGrantedProxy(const LLPermissions& perm) +{ + return (mProxyForAgents.count(perm.getOwner()) > 0); +} + +BOOL LLAgent::allowOperation(PermissionBit op, + const LLPermissions& perm, + U64 group_proxy_power, + U8 god_minimum) +{ + // Check god level. + if (getGodLevel() >= god_minimum) return TRUE; + + if (!perm.isOwned()) return FALSE; + + // A group member with group_proxy_power can act as owner. + BOOL is_group_owned; + LLUUID owner_id; + perm.getOwnership(owner_id, is_group_owned); + LLUUID group_id(perm.getGroup()); + LLUUID agent_proxy(getID()); + + if (is_group_owned) + { + if (hasPowerInGroup(group_id, group_proxy_power)) + { + // Let the member assume the group's id for permission requests. + agent_proxy = owner_id; + } + } + else + { + // Check for granted mod permissions. + if ((PERM_OWNER != op) && isGrantedProxy(perm)) + { + agent_proxy = owner_id; + } + } + + // This is the group id to use for permission requests. + // Only group members may use this field. + LLUUID group_proxy = LLUUID::null; + if (group_id.notNull() && isInGroup(group_id)) + { + group_proxy = group_id; + } + + // We now have max ownership information. + if (PERM_OWNER == op) + { + // This this was just a check for ownership, we can now return the answer. + return (agent_proxy == owner_id); + } + + return perm.allowOperationBy(op, agent_proxy, group_proxy); +} + + +void LLAgent::getName(LLString& name) +{ + // Note: assumes that name points to a buffer of at least DB_FULL_NAME_BUF_SIZE bytes. + name.clear(); + + if (mAvatarObject) + { + LLNameValue *first_nv = mAvatarObject->getNVPair("FirstName"); + LLNameValue *last_nv = mAvatarObject->getNVPair("LastName"); + if (first_nv && last_nv) + { + name = first_nv->printData() + " " + last_nv->printData(); + } + else + { + llwarns << "Agent is missing FirstName and/or LastName nv pair." << llendl; + } + } + else + { + name = gSavedSettings.getString("FirstName") + " " + gSavedSettings.getString("LastName"); + } +} + +const LLColor4 &LLAgent::getEffectColor() +{ + return mEffectColor; +} + +void LLAgent::setEffectColor(const LLColor4 &color) +{ + mEffectColor = color; +} + +void LLAgent::initOriginGlobal(const LLVector3d &origin_global) +{ + mAgentOriginGlobal = origin_global; +} + +void update_group_floaters(const LLUUID& group_id) +{ + // *HACK: added to do a live update of the groups floater if it is + // open. + LLFloaterGroups* fg = LLFloaterGroups::getInstance(gAgent.getID()); + if(fg) + { + fg->reset(); + } + + LLFloaterGroupInfo::refreshGroup(group_id); + + // update avatar info + LLFloaterAvatarInfo* fa = LLFloaterAvatarInfo::getInstance(gAgent.getID()); + if(fa) + { + fa->resetGroupList(); + } + + if (gIMView) + { + // update the talk view + gIMView->refresh(); + } +} + +// static +void LLAgent::processAgentDropGroup(LLMessageSystem *msg, void **) +{ + LLUUID agent_id; + msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id ); + + if (agent_id != gAgentID) + { + llwarns << "processAgentDropGroup for agent other than me" << llendl; + return; + } + + LLUUID group_id; + msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_GroupID, group_id ); + + // Remove the group if it already exists remove it and add the new data to pick up changes. + LLGroupData gd; + gd.mID = group_id; + S32 index = gAgent.mGroups.find(gd); + if (index != -1) + { + gAgent.mGroups.remove(index); + if (gAgent.getGroupID() == group_id) + { + gAgent.mGroupID.setNull(); + gAgent.mGroupPowers = 0; + gAgent.mGroupName[0] = '\0'; + gAgent.mGroupTitle[0] = '\0'; + } + + // refresh all group information + gAgent.sendAgentDataUpdateRequest(); + + gGroupMgr->clearGroupData(group_id); + // close the floater for this group, if any. + LLFloaterGroupInfo::closeGroup(group_id); + // refresh the group panel of the search window, if necessary. + LLFloaterDirectory::refreshGroup(group_id); + } + else + { + llwarns << "processAgentDropGroup, agent is not part of group " << group_id << llendl; + } +} + +// static +void LLAgent::processAgentGroupDataUpdate(LLMessageSystem *msg, void **) +{ + LLUUID agent_id; + + msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id ); + + if (agent_id != gAgentID) + { + llwarns << "processAgentGroupDataUpdate for agent other than me" << llendl; + return; + } + + S32 count = msg->getNumberOfBlocksFast(_PREHASH_GroupData); + LLGroupData group; + S32 index = -1; + bool need_floater_update = false; + char group_name[DB_GROUP_NAME_BUF_SIZE]; + for(S32 i = 0; i < count; ++i) + { + msg->getUUIDFast(_PREHASH_GroupData, _PREHASH_GroupID, group.mID, i); + msg->getUUIDFast(_PREHASH_GroupData, _PREHASH_GroupInsigniaID, group.mInsigniaID, i); + msg->getU64(_PREHASH_GroupData, "GroupPowers", group.mPowers, i); + msg->getBOOL(_PREHASH_GroupData, "AcceptNotices", group.mAcceptNotices, i); + msg->getS32(_PREHASH_GroupData, "Contribution", group.mContribution, i); + msg->getStringFast(_PREHASH_GroupData, _PREHASH_GroupName, DB_GROUP_NAME_BUF_SIZE, group_name, i); + group.mName.assign(group_name); + + if(group.mID.notNull()) + { + need_floater_update = true; + // Remove the group if it already exists remove it and add the new data to pick up changes. + index = gAgent.mGroups.find(group); + if (index != -1) + { + gAgent.mGroups.remove(index); + } + gAgent.mGroups.put(group); + } + if (need_floater_update) + { + update_group_floaters(group.mID); + } + } + +} + +// static +void LLAgent::processAgentDataUpdate(LLMessageSystem *msg, void **) +{ + LLUUID agent_id; + + msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id ); + + if (agent_id != gAgentID) + { + llwarns << "processAgentDataUpdate for agent other than me" << llendl; + return; + } + + msg->getStringFast(_PREHASH_AgentData, _PREHASH_GroupTitle, DB_GROUP_TITLE_BUF_SIZE, gAgent.mGroupTitle); + LLUUID active_id; + msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_ActiveGroupID, active_id); + + + if(active_id.notNull()) + { + gAgent.mGroupID = active_id; + msg->getU64(_PREHASH_AgentData, "GroupPowers", gAgent.mGroupPowers); + msg->getString(_PREHASH_AgentData, _PREHASH_GroupName, DB_GROUP_NAME_BUF_SIZE, gAgent.mGroupName); + } + else + { + gAgent.mGroupID.setNull(); + gAgent.mGroupPowers = 0; + gAgent.mGroupName[0] = '\0'; + } + + update_group_floaters(active_id); + + gAgent.fireEvent(new LLEvent(&gAgent, "new group"), ""); +} + +// static +void LLAgent::processScriptControlChange(LLMessageSystem *msg, void **) +{ + S32 block_count = msg->getNumberOfBlocks("Data"); + for (S32 block_index = 0; block_index < block_count; block_index++) + { + BOOL take_controls; + U32 controls; + BOOL passon; + U32 i; + msg->getBOOL("Data", "TakeControls", take_controls, block_index); + if (take_controls) + { + // take controls + msg->getU32("Data", "Controls", controls, block_index ); + msg->getBOOL("Data", "PassToAgent", passon, block_index ); + U32 total_count = 0; + for (i = 0; i < TOTAL_CONTROLS; i++) + { + if (controls & ( 1 << i)) + { + if (passon) + { + gAgent.mControlsTakenPassedOnCount[i]++; + } + else + { + gAgent.mControlsTakenCount[i]++; + } + total_count++; + } + } + + // Any control taken? If so, might be first time. + if (total_count > 0) + { + LLFirstUse::useOverrideKeys(); + } + } + else + { + // release controls + msg->getU32("Data", "Controls", controls, block_index ); + msg->getBOOL("Data", "PassToAgent", passon, block_index ); + for (i = 0; i < TOTAL_CONTROLS; i++) + { + if (controls & ( 1 << i)) + { + if (passon) + { + gAgent.mControlsTakenPassedOnCount[i]--; + if (gAgent.mControlsTakenPassedOnCount[i] < 0) + { + gAgent.mControlsTakenPassedOnCount[i] = 0; + } + } + else + { + gAgent.mControlsTakenCount[i]--; + if (gAgent.mControlsTakenCount[i] < 0) + { + gAgent.mControlsTakenCount[i] = 0; + } + } + } + } + } + } +} + +/* +// static +void LLAgent::processControlTake(LLMessageSystem *msg, void **) +{ + U32 controls; + msg->getU32("Data", "Controls", controls ); + U32 passon; + msg->getBOOL("Data", "PassToAgent", passon ); + + S32 i; + S32 total_count = 0; + for (i = 0; i < TOTAL_CONTROLS; i++) + { + if (controls & ( 1 << i)) + { + if (passon) + { + gAgent.mControlsTakenPassedOnCount[i]++; + } + else + { + gAgent.mControlsTakenCount[i]++; + } + total_count++; + } + } + + // Any control taken? If so, might be first time. + if (total_count > 0) + { + LLFirstUse::useOverrideKeys(); + } +} + +// static +void LLAgent::processControlRelease(LLMessageSystem *msg, void **) +{ + U32 controls; + msg->getU32("Data", "Controls", controls ); + U32 passon; + msg->getBOOL("Data", "PassToAgent", passon ); + + S32 i; + for (i = 0; i < TOTAL_CONTROLS; i++) + { + if (controls & ( 1 << i)) + { + if (passon) + { + gAgent.mControlsTakenPassedOnCount[i]--; + if (gAgent.mControlsTakenPassedOnCount[i] < 0) + { + gAgent.mControlsTakenPassedOnCount[i] = 0; + } + } + else + { + gAgent.mControlsTakenCount[i]--; + if (gAgent.mControlsTakenCount[i] < 0) + { + gAgent.mControlsTakenCount[i] = 0; + } + } + } + } +} +*/ + +//static +void LLAgent::processAgentCachedTextureResponse(LLMessageSystem *mesgsys, void **user_data) +{ + gAgent.mNumPendingQueries--; + + LLVOAvatar* avatarp = gAgent.getAvatarObject(); + if (!avatarp || avatarp->isDead()) + { + llwarns << "No avatar for user in cached texture update!" << llendl; + return; + } + + if (gAgent.cameraCustomizeAvatar()) + { + // ignore baked textures when in customize mode + return; + } + + S32 query_id; + mesgsys->getS32Fast(_PREHASH_AgentData, _PREHASH_SerialNum, query_id); + + S32 num_texture_blocks = mesgsys->getNumberOfBlocksFast(_PREHASH_WearableData); + + + S32 num_results = 0; + for (S32 texture_block = 0; texture_block < num_texture_blocks; texture_block++) + { + LLUUID texture_id; + U8 texture_index; + + mesgsys->getUUIDFast(_PREHASH_WearableData, _PREHASH_TextureID, texture_id, texture_block); + mesgsys->getU8Fast(_PREHASH_WearableData, _PREHASH_TextureIndex, texture_index, texture_block); + + if (texture_id.notNull() + && (S32)texture_index < BAKED_TEXTURE_COUNT + && gAgent.mActiveCacheQueries[ texture_index ] == query_id) + { + //llinfos << "Received cached texture " << (U32)texture_index << ": " << texture_id << llendl; + avatarp->setCachedBakedTexture((LLVOAvatar::ETextureIndex)LLVOAvatar::sBakedTextureIndices[texture_index], texture_id); + //avatarp->setTETexture( LLVOAvatar::sBakedTextureIndices[texture_index], texture_id ); + gAgent.mActiveCacheQueries[ texture_index ] = 0; + num_results++; + } + } + + llinfos << "Received cached texture response for " << num_results << " textures." << llendl; + + avatarp->updateMeshTextures(); + + if (gAgent.mNumPendingQueries == 0) + { + // RN: not sure why composites are disabled at this point + avatarp->setCompositeUpdatesEnabled(TRUE); + gAgent.sendAgentSetAppearance(); + } +} + +BOOL LLAgent::anyControlGrabbed() const +{ + U32 i; + for (i = 0; i < TOTAL_CONTROLS; i++) + { + if (gAgent.mControlsTakenCount[i] > 0) + return TRUE; + if (gAgent.mControlsTakenPassedOnCount[i] > 0) + return TRUE; + } + return FALSE; +} + +BOOL LLAgent::isControlGrabbed(S32 control_index) const +{ + return mControlsTakenCount[control_index] > 0; +} + +void LLAgent::forceReleaseControls() +{ + gMessageSystem->newMessage("ForceScriptControlRelease"); + gMessageSystem->nextBlock("AgentData"); + gMessageSystem->addUUID("AgentID", getID()); + gMessageSystem->addUUID("SessionID", getSessionID()); + sendReliableMessage(); +} + +void LLAgent::setHomePosRegion( const U64& region_handle, const LLVector3& pos_region) +{ + mHaveHomePosition = TRUE; + mHomeRegionHandle = region_handle; + mHomePosRegion = pos_region; +} + +BOOL LLAgent::getHomePosGlobal( LLVector3d* pos_global ) +{ + if(!mHaveHomePosition) + { + return FALSE; + } + F32 x = 0; + F32 y = 0; + from_region_handle( mHomeRegionHandle, &x, &y); + pos_global->setVec( x + mHomePosRegion.mV[VX], y + mHomePosRegion.mV[VY], mHomePosRegion.mV[VZ] ); + return TRUE; +} + +void LLAgent::clearVisualParams(void *data) +{ + LLVOAvatar* avatarp = gAgent.getAvatarObject(); + if (avatarp) + { + avatarp->clearVisualParamWeights(); + avatarp->updateVisualParams(); + } +} + +//--------------------------------------------------------------------------- +// Teleport +//--------------------------------------------------------------------------- + +// teleportCore() - stuff to do on any teleport +// protected +bool LLAgent::teleportCore(bool is_local) +{ + if(TELEPORT_NONE != mTeleportState) + { + llwarns << "Attempt to teleport when already teleporting." << llendl; + return false; + } + + // Don't call LLFirstUse::useTeleport because we don't know + // yet if the teleport will succeed. Look in + // process_teleport_location_reply + + // close the map and find panels so we can see our destination + LLFloaterWorldMap::hide(NULL); + LLFloaterDirectory::hide(NULL); + + // Close all pie menus, deselect land, etc. + // Don't change the camera until we know teleport succeeded. JC + resetView(FALSE); + + // local logic + gViewerStats->incStat(LLViewerStats::ST_TELEPORT_COUNT); + if (!is_local) + { + gTeleportDisplay = TRUE; + gAgent.setTeleportState( LLAgent::TELEPORT_START ); + } + make_ui_sound("UISndTeleportOut"); + return true; +} + +void LLAgent::teleportRequest( + const U64& region_handle, + const LLVector3& pos_local) +{ + LLViewerRegion* regionp = getRegion(); + if(regionp && teleportCore()) + { + llinfos << "TeleportRequest: '" << region_handle << "':" << pos_local + << llendl; + LLMessageSystem* msg = gMessageSystem; + msg->newMessage("TeleportLocationRequest"); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, getID()); + msg->addUUIDFast(_PREHASH_SessionID, getSessionID()); + msg->nextBlockFast(_PREHASH_Info); + msg->addU64("RegionHandle", region_handle); + msg->addVector3("Position", pos_local); + LLVector3 look_at(0,1,0); + msg->addVector3("LookAt", look_at); + sendReliableMessage(); + } +} + +// Landmark ID = LLUUID::null means teleport home +void LLAgent::teleportViaLandmark(const LLUUID& landmark_id) +{ + LLViewerRegion *regionp = getRegion(); + if(regionp && teleportCore()) + { + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_TeleportLandmarkRequest); + msg->nextBlockFast(_PREHASH_Info); + msg->addUUIDFast(_PREHASH_AgentID, getID()); + msg->addUUIDFast(_PREHASH_SessionID, getSessionID()); + msg->addUUIDFast(_PREHASH_LandmarkID, landmark_id); + sendReliableMessage(); + } +} + +void LLAgent::teleportViaLure(const LLUUID& lure_id, BOOL godlike) +{ + LLViewerRegion* regionp = getRegion(); + if(regionp && teleportCore()) + { + U32 teleport_flags = 0x0; + if (godlike) + { + teleport_flags |= TELEPORT_FLAGS_VIA_GODLIKE_LURE; + teleport_flags |= TELEPORT_FLAGS_DISABLE_CANCEL; + } + else + { + teleport_flags |= TELEPORT_FLAGS_VIA_LURE; + } + + // send the message + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_TeleportLureRequest); + msg->nextBlockFast(_PREHASH_Info); + msg->addUUIDFast(_PREHASH_AgentID, getID()); + msg->addUUIDFast(_PREHASH_SessionID, getSessionID()); + msg->addUUIDFast(_PREHASH_LureID, lure_id); + msg->addU32("TeleportFlags", teleport_flags); + sendReliableMessage(); + } +} + + +// James Cook, July 28, 2005 +void LLAgent::teleportCancel() +{ + LLViewerRegion* regionp = getRegion(); + if(regionp) + { + // send the message + LLMessageSystem* msg = gMessageSystem; + msg->newMessage("TeleportCancel"); + msg->nextBlockFast(_PREHASH_Info); + msg->addUUIDFast(_PREHASH_AgentID, getID()); + msg->addUUIDFast(_PREHASH_SessionID, getSessionID()); + sendReliableMessage(); + } + gTeleportDisplay = FALSE; + gAgent.setTeleportState( LLAgent::TELEPORT_CANCELLING ); +} + + +void LLAgent::teleportViaLocation(const LLVector3d& pos_global) +{ + LLViewerRegion* regionp = getRegion(); + LLSimInfo* info = gWorldMap->simInfoFromPosGlobal(pos_global); + if(regionp && info) + { + U32 x_pos; + U32 y_pos; + from_region_handle(info->mHandle, &x_pos, &y_pos); + LLVector3 pos_local( + (F32)(pos_global.mdV[VX] - x_pos), + (F32)(pos_global.mdV[VY] - y_pos), + (F32)(pos_global.mdV[VZ])); + teleportRequest(info->mHandle, pos_local); + } + else if(regionp && + teleportCore(regionp->getHandle() == to_region_handle_global((F32)pos_global.mdV[VX], (F32)pos_global.mdV[VY]))) + { + llwarns << "Using deprecated teleportlocationrequest." << llendl; + // send the message + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_TeleportLocationRequest); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, getID()); + msg->addUUIDFast(_PREHASH_SessionID, getSessionID()); + + msg->nextBlockFast(_PREHASH_Info); + F32 width = regionp->getWidth(); + LLVector3 pos(fmod((F32)pos_global.mdV[VX], width), + fmod((F32)pos_global.mdV[VY], width), + (F32)pos_global.mdV[VZ]); + F32 region_x = (F32)(pos_global.mdV[VX]); + F32 region_y = (F32)(pos_global.mdV[VY]); + U64 region_handle = to_region_handle_global(region_x, region_y); + msg->addU64Fast(_PREHASH_RegionHandle, region_handle); + msg->addVector3Fast(_PREHASH_Position, pos); + pos.mV[VX] += 1; + msg->addVector3Fast(_PREHASH_LookAt, pos); + sendReliableMessage(); + } +} + +void LLAgent::setTeleportState(ETeleportState state) +{ + mTeleportState = state; + if (mTeleportState > TELEPORT_NONE && gSavedSettings.getBOOL("FreezeTime")) + { + LLFloaterSnapshot::hide(0); + } +} + +void LLAgent::fidget() +{ + if (!getAFK()) + { + F32 curTime = mFidgetTimer.getElapsedTimeF32(); + if (curTime > mNextFidgetTime) + { + // pick a random fidget anim here + S32 oldFidget = mCurrentFidget; + + mCurrentFidget = gLindenLabRandomNumber.llrand(NUM_AGENT_STAND_ANIMS); + + if (mCurrentFidget != oldFidget) + { + LLAgent::stopFidget(); + + + switch(mCurrentFidget) + { + case 0: + mCurrentFidget = 0; + break; + case 1: + sendAnimationRequest(ANIM_AGENT_STAND_1, ANIM_REQUEST_START); + mCurrentFidget = 1; + break; + case 2: + sendAnimationRequest(ANIM_AGENT_STAND_2, ANIM_REQUEST_START); + mCurrentFidget = 2; + break; + case 3: + sendAnimationRequest(ANIM_AGENT_STAND_3, ANIM_REQUEST_START); + mCurrentFidget = 3; + break; + case 4: + sendAnimationRequest(ANIM_AGENT_STAND_4, ANIM_REQUEST_START); + mCurrentFidget = 4; + break; + } + } + + // calculate next fidget time + mNextFidgetTime = curTime + gLindenLabRandomNumber.llfrand(MAX_FIDGET_TIME - MIN_FIDGET_TIME) + MIN_FIDGET_TIME; + } + } +} + +void LLAgent::stopFidget() +{ + LLDynamicArray<LLUUID> anims; + anims.put(ANIM_AGENT_STAND_1); + anims.put(ANIM_AGENT_STAND_2); + anims.put(ANIM_AGENT_STAND_3); + anims.put(ANIM_AGENT_STAND_4); + + gAgent.sendAnimationRequests(anims, ANIM_REQUEST_STOP); +} + + +void LLAgent::requestEnterGodMode() +{ + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_RequestGodlikePowers); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_RequestBlock); + msg->addBOOLFast(_PREHASH_Godlike, TRUE); + msg->addUUIDFast(_PREHASH_Token, LLUUID::null); + + // simulator and userserver need to know about your request + sendReliableMessage(); + msg->sendReliable(gUserServer); +} + +void LLAgent::requestLeaveGodMode() +{ + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_RequestGodlikePowers); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_RequestBlock); + msg->addBOOLFast(_PREHASH_Godlike, FALSE); + msg->addUUIDFast(_PREHASH_Token, LLUUID::null); + + // simulator and userserver need to know about your request + sendReliableMessage(); + msg->sendReliable(gUserServer); +} + +// wearables +LLAgent::createStandardWearablesAllDoneCallback::~createStandardWearablesAllDoneCallback() +{ + gAgent.createStandardWearablesAllDone(); +} + +LLAgent::sendAgentWearablesUpdateCallback::~sendAgentWearablesUpdateCallback() +{ + gAgent.sendAgentWearablesUpdate(); +} + +LLAgent::addWearableToAgentInventoryCallback::addWearableToAgentInventoryCallback( + LLPointer<LLRefCount> cb, S32 index, LLWearable* wearable, U32 todo) : + mIndex(index), + mWearable(wearable), + mTodo(todo), + mCB(cb) +{ +} + +void LLAgent::addWearableToAgentInventoryCallback::fire(const LLUUID& inv_item) +{ + if (inv_item.isNull()) + return; + + gAgent.addWearabletoAgentInventoryDone(mIndex, inv_item, mWearable); + + if (mTodo & CALL_UPDATE) + { + gAgent.sendAgentWearablesUpdate(); + } + if (mTodo & CALL_RECOVERDONE) + { + gAgent.recoverMissingWearableDone(); + } + /* + * Do this for every one in the loop + */ + if (mTodo & CALL_CREATESTANDARDDONE) + { + gAgent.createStandardWearablesDone(mIndex); + } + if (mTodo & CALL_MAKENEWOUTFITDONE) + { + gAgent.makeNewOutfitDone(mIndex); + } +} + +void LLAgent::addWearabletoAgentInventoryDone( + S32 index, + const LLUUID& item_id, + LLWearable* wearable) +{ + if (item_id.isNull()) + return; + + LLUUID old_item_id = mWearableEntry[index].mItemID; + mWearableEntry[index].mItemID = item_id; + mWearableEntry[index].mWearable = wearable; + if (old_item_id.notNull()) + gInventory.addChangedMask(LLInventoryObserver::LABEL, old_item_id); + gInventory.addChangedMask(LLInventoryObserver::LABEL, item_id); + LLViewerInventoryItem* item = gInventory.getItem(item_id); + if(item && wearable) + { + // We're changing the asset id, so we both need to set it + // locally via setAssetUUID() and via setTransactionID() which + // will be decoded on the server. JC + item->setAssetUUID(wearable->getID()); + item->setTransactionID(wearable->getTransactionID()); + gInventory.addChangedMask(LLInventoryObserver::INTERNAL, item_id); + item->updateServer(FALSE); + } + gInventory.notifyObservers(); +} + +void LLAgent::sendAgentWearablesUpdate() +{ + // First make sure that we have inventory items for each wearable + S32 i; + for(i=0; i < WT_COUNT; ++i) + { + LLWearable* wearable = mWearableEntry[ i ].mWearable; + if (wearable) + { + if( mWearableEntry[ i ].mItemID.isNull() ) + { + LLPointer<LLInventoryCallback> cb = + new addWearableToAgentInventoryCallback( + LLPointer<LLRefCount>(NULL), + i, + wearable, + addWearableToAgentInventoryCallback::CALL_NONE); + addWearableToAgentInventory(cb, wearable); + } + else + { + gInventory.addChangedMask( LLInventoryObserver::LABEL, + mWearableEntry[i].mItemID ); + } + } + } + + // Then make sure the inventory is in sync with the avatar. + gInventory.notifyObservers(); + + // Send the AgentIsNowWearing + gMessageSystem->newMessageFast(_PREHASH_AgentIsNowWearing); + + gMessageSystem->nextBlockFast(_PREHASH_AgentData); + gMessageSystem->addUUIDFast(_PREHASH_AgentID, getID()); + gMessageSystem->addUUIDFast(_PREHASH_SessionID, getSessionID()); + + lldebugs << "sendAgentWearablesUpdate()" << llendl; + for(i=0; i < WT_COUNT; ++i) + { + gMessageSystem->nextBlockFast(_PREHASH_WearableData); + + U8 type_u8 = (U8)i; + gMessageSystem->addU8Fast(_PREHASH_WearableType, type_u8 ); + + LLWearable* wearable = mWearableEntry[ i ].mWearable; + if( wearable ) + { + //llinfos << "Sending wearable " << wearable->getName() << llendl; + gMessageSystem->addUUIDFast(_PREHASH_ItemID, mWearableEntry[ i ].mItemID ); + } + else + { + //llinfos << "Not wearing wearable type " << LLWearable::typeToTypeName((EWearableType)i) << llendl; + gMessageSystem->addUUIDFast(_PREHASH_ItemID, LLUUID::null ); + } + + lldebugs << " " << LLWearable::typeToTypeLabel((EWearableType)i) << ": " << (wearable ? wearable->getID() : LLUUID::null) << llendl; + } + gAgent.sendReliableMessage(); +} + +void LLAgent::saveWearable( EWearableType type, BOOL send_update ) +{ + LLWearable* old_wearable = mWearableEntry[(S32)type].mWearable; + if( old_wearable && (old_wearable->isDirty() || old_wearable->isOldVersion()) ) + { + LLWearable* new_wearable = gWearableList.createCopyFromAvatar( old_wearable ); + mWearableEntry[(S32)type].mWearable = new_wearable; + + LLInventoryItem* item = gInventory.getItem(mWearableEntry[(S32)type].mItemID); + if( item ) + { + // Update existing inventory item + LLPointer<LLViewerInventoryItem> template_item = + new LLViewerInventoryItem(item->getUUID(), + item->getParentUUID(), + item->getPermissions(), + new_wearable->getID(), + new_wearable->getAssetType(), + item->getInventoryType(), + item->getName(), + item->getDescription(), + item->getSaleInfo(), + item->getFlags(), + item->getCreationDate()); + template_item->setTransactionID(new_wearable->getTransactionID()); + template_item->updateServer(FALSE); + gInventory.updateItem(template_item); + } + else + { + // Add a new inventory item (shouldn't ever happen here) + U32 todo = addWearableToAgentInventoryCallback::CALL_NONE; + if (send_update) + { + todo |= addWearableToAgentInventoryCallback::CALL_UPDATE; + } + LLPointer<LLInventoryCallback> cb = + new addWearableToAgentInventoryCallback( + LLPointer<LLRefCount>(NULL), + (S32)type, + new_wearable, + todo); + addWearableToAgentInventory(cb, new_wearable); + return; + } + + if( send_update ) + { + sendAgentWearablesUpdate(); + } + } +} + +void LLAgent::saveWearableAs( + EWearableType type, + const std::string& new_name, + BOOL save_in_lost_and_found) +{ + if(!isWearableCopyable(type)) + { + llwarns << "LLAgent::saveWearableAs() not copyable." << llendl; + return; + } + LLWearable* old_wearable = getWearable(type); + if(!old_wearable) + { + llwarns << "LLAgent::saveWearableAs() no old wearable." << llendl; + return; + } + LLInventoryItem* item = gInventory.getItem(mWearableEntry[type].mItemID); + if(!item) + { + llwarns << "LLAgent::saveWearableAs() no inventory item." << llendl; + return; + } + std::string trunc_name(new_name); + LLString::truncate(trunc_name, DB_INV_ITEM_NAME_STR_LEN); + LLWearable* new_wearable = gWearableList.createCopyFromAvatar( + old_wearable, + trunc_name); + LLPointer<LLInventoryCallback> cb = + new addWearableToAgentInventoryCallback( + LLPointer<LLRefCount>(NULL), + type, + new_wearable, + addWearableToAgentInventoryCallback::CALL_UPDATE); + LLUUID category_id; + if (save_in_lost_and_found) + { + category_id = gInventory.findCategoryUUIDForType( + LLAssetType::AT_LOST_AND_FOUND); + } + else + { + // put in same folder as original + category_id = item->getParentUUID(); + } + + copy_inventory_item( + gAgent.getID(), + item->getPermissions().getOwner(), + item->getUUID(), + category_id, + new_name, + cb); + +/* + LLWearable* old_wearable = getWearable( type ); + if( old_wearable ) + { + LLString old_name = old_wearable->getName(); + old_wearable->setName( new_name ); + LLWearable* new_wearable = gWearableList.createCopyFromAvatar( old_wearable ); + old_wearable->setName( old_name ); + + LLUUID category_id; + LLInventoryItem* item = gInventory.getItem( mWearableEntry[ type ].mItemID ); + if( item ) + { + new_wearable->setPermissions(item->getPermissions()); + if (save_in_lost_and_found) + { + category_id = gInventory.findCategoryUUIDForType(LLAssetType::AT_LOST_AND_FOUND); + } + else + { + // put in same folder as original + category_id = item->getParentUUID(); + } + LLInventoryView* view = LLInventoryView::getActiveInventory(); + if(view) + { + view->getPanel()->setSelection(item->getUUID(), TAKE_FOCUS_NO); + } + } + + mWearableEntry[ type ].mWearable = new_wearable; + LLPointer<LLInventoryCallback> cb = + new addWearableToAgentInventoryCallback( + LLPointer<LLRefCount>(NULL), + type, + addWearableToAgentInventoryCallback::CALL_UPDATE); + addWearableToAgentInventory(cb, new_wearable, category_id); + } +*/ +} + +void LLAgent::revertWearable( EWearableType type ) +{ + LLWearable* wearable = mWearableEntry[(S32)type].mWearable; + if( wearable ) + { + wearable->writeToAvatar( TRUE ); + } + sendAgentSetAppearance(); +} + +void LLAgent::revertAllWearables() +{ + for( S32 i=0; i < WT_COUNT; i++ ) + { + revertWearable( (EWearableType)i ); + } +} + +void LLAgent::saveAllWearables() +{ + //if(!gInventory.isLoaded()) + //{ + // return; + //} + + for( S32 i=0; i < WT_COUNT; i++ ) + { + saveWearable( (EWearableType)i, FALSE ); + } + sendAgentWearablesUpdate(); +} + +// Called when the user changes the name of a wearable inventory item that is currenlty being worn. +void LLAgent::setWearableName( const LLUUID& item_id, const std::string& new_name ) +{ + for( S32 i=0; i < WT_COUNT; i++ ) + { + if( mWearableEntry[i].mItemID == item_id ) + { + LLWearable* old_wearable = mWearableEntry[i].mWearable; + llassert( old_wearable ); + + LLString old_name = old_wearable->getName(); + old_wearable->setName( new_name ); + LLWearable* new_wearable = gWearableList.createCopy( old_wearable ); + LLInventoryItem* item = gInventory.getItem(item_id); + if(item) + { + new_wearable->setPermissions(item->getPermissions()); + } + old_wearable->setName( old_name ); + + mWearableEntry[i].mWearable = new_wearable; + sendAgentWearablesUpdate(); + break; + } + } +} + + +BOOL LLAgent::isWearableModifiable(EWearableType type) +{ + LLUUID item_id = getWearableItem(type); + if(!item_id.isNull()) + { + LLInventoryItem* item = gInventory.getItem(item_id); + if(item && item->getPermissions().allowModifyBy(gAgent.getID(), + gAgent.getGroupID())) + { + return TRUE; + } + } + return FALSE; +} + +BOOL LLAgent::isWearableCopyable(EWearableType type) +{ + LLUUID item_id = getWearableItem(type); + if(!item_id.isNull()) + { + LLInventoryItem* item = gInventory.getItem(item_id); + if(item && item->getPermissions().allowCopyBy(gAgent.getID(), + gAgent.getGroupID())) + { + return TRUE; + } + } + return FALSE; +} + +U32 LLAgent::getWearablePermMask(EWearableType type) +{ + LLUUID item_id = getWearableItem(type); + if(!item_id.isNull()) + { + LLInventoryItem* item = gInventory.getItem(item_id); + if(item) + { + return item->getPermissions().getMaskOwner(); + } + } + return PERM_NONE; +} + +LLInventoryItem* LLAgent::getWearableInventoryItem(EWearableType type) +{ + LLUUID item_id = getWearableItem(type); + LLInventoryItem* item = NULL; + if(item_id.notNull()) + { + item = gInventory.getItem(item_id); + } + return item; +} + +LLWearable* LLAgent::getWearableFromWearableItem( const LLUUID& item_id ) +{ + for( S32 i=0; i < WT_COUNT; i++ ) + { + if( mWearableEntry[i].mItemID == item_id ) + { + return mWearableEntry[i].mWearable; + } + } + return NULL; +} + + +void LLAgent::sendAgentWearablesRequest() +{ + gMessageSystem->newMessageFast(_PREHASH_AgentWearablesRequest); + gMessageSystem->nextBlockFast(_PREHASH_AgentData); + gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); + gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID() ); + sendReliableMessage(); +} + +// Used to enable/disable menu items. +// static +BOOL LLAgent::selfHasWearable( void* userdata ) +{ + EWearableType type = (EWearableType)(intptr_t)userdata; + return gAgent.getWearable( type ) != NULL; +} + +BOOL LLAgent::isWearingItem( const LLUUID& item_id ) +{ + return (getWearableFromWearableItem( item_id ) != NULL); +} + + +// static +void LLAgent::processAgentInitialWearablesUpdate( LLMessageSystem* mesgsys, void** user_data ) +{ + // We should only receive this message a single time. Ignore subsequent AgentWearablesUpdates + // that may result from AgentWearablesRequest having been sent more than once. + static BOOL first = TRUE; + if( first ) + { + first = FALSE; + } + else + { + return; + } + + if (gNoRender) + { + return; + } + + LLUUID agent_id; + gMessageSystem->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id ); + + LLVOAvatar* avatar = gAgent.getAvatarObject(); + if( avatar && (agent_id == avatar->getID()) ) + { + gMessageSystem->getU32Fast(_PREHASH_AgentData, _PREHASH_SerialNum, gAgent.mAgentWearablesUpdateSerialNum ); + + S32 num_wearables = gMessageSystem->getNumberOfBlocksFast(_PREHASH_WearableData); + if( num_wearables < 4 ) + { + // Transitional state. Avatars should always have at least their body parts (hair, eyes, shape and skin). + // The fact that they don't have any here (only a dummy is sent) implies that this account existed + // before we had wearables, or that the database has gotten messed up. + // Deal with this by creating new body parts. + //avatar->createStandardWearables(); + + // no, deal with it by noting that we need to choose a + // gender. + gAgent.setGenderChosen(FALSE); + return; + } + + //lldebugs << "processAgentInitialWearablesUpdate()" << llendl; + // Add wearables + LLUUID asset_id_array[ WT_COUNT ]; + S32 i; + for( i=0; i < num_wearables; i++ ) + { + U8 type_u8 = 0; + gMessageSystem->getU8Fast(_PREHASH_WearableData, _PREHASH_WearableType, type_u8, i ); + if( type_u8 >= WT_COUNT ) + { + continue; + } + EWearableType type = (EWearableType) type_u8; + + LLUUID item_id; + gMessageSystem->getUUIDFast(_PREHASH_WearableData, _PREHASH_ItemID, item_id, i ); + + LLUUID asset_id; + gMessageSystem->getUUIDFast(_PREHASH_WearableData, _PREHASH_AssetID, asset_id, i ); + if( asset_id.isNull() ) + { + LLWearable::removeFromAvatar( type, FALSE ); + } + else + { + LLAssetType::EType asset_type = LLWearable::typeToAssetType( type ); + if( asset_type == LLAssetType::AT_NONE ) + { + continue; + } + + gAgent.mWearableEntry[type].mItemID = item_id; + asset_id_array[type] = asset_id; + } + + lldebugs << " " << LLWearable::typeToTypeLabel(type) << llendl; + } + + // now that we have the asset ids...request the wearable assets + for( i = 0; i < WT_COUNT; i++ ) + { + if( !gAgent.mWearableEntry[i].mItemID.isNull() ) + { + gWearableList.getAsset( + asset_id_array[i], + LLString::null, + LLWearable::typeToAssetType( (EWearableType) i ), + LLAgent::onInitialWearableAssetArrived, (void*)(intptr_t)i ); + } + } + } +} + +// A single wearable that the avatar was wearing on start-up has arrived from the database. +// static +void LLAgent::onInitialWearableAssetArrived( LLWearable* wearable, void* userdata ) +{ + EWearableType type = (EWearableType)(intptr_t)userdata; + + LLVOAvatar* avatar = gAgent.getAvatarObject(); + if( !avatar ) + { + return; + } + + if( wearable ) + { + llassert( type == wearable->getType() ); + gAgent.mWearableEntry[ type ].mWearable = wearable; + + // disable composites if initial textures are baked + avatar->setupComposites(); + gAgent.queryWearableCache(); + + wearable->writeToAvatar( FALSE ); + avatar->setCompositeUpdatesEnabled(TRUE); + gInventory.addChangedMask( LLInventoryObserver::LABEL, gAgent.mWearableEntry[type].mItemID ); + } + else + { + // Somehow the asset doesn't exist in the database. + gAgent.recoverMissingWearable( type ); + } + + gInventory.notifyObservers(); + + // Have all the wearables that the avatar was wearing at log-in arrived? + if( !gAgent.mWearablesLoaded ) + { + gAgent.mWearablesLoaded = TRUE; + for( S32 i = 0; i < WT_COUNT; i++ ) + { + if( !gAgent.mWearableEntry[i].mItemID.isNull() && !gAgent.mWearableEntry[i].mWearable ) + { + gAgent.mWearablesLoaded = FALSE; + break; + } + } + } + + if( gAgent.mWearablesLoaded ) + { + // Make sure that the server's idea of the avatar's wearables actually match the wearables. + gAgent.sendAgentSetAppearance(); + + // Check to see if there are any baked textures that we hadn't uploaded before we logged off last time. + // If there are any, schedule them to be uploaded as soon as the layer textures they depend on arrive. + if( !gAgent.cameraCustomizeAvatar() ) + { + avatar->requestLayerSetUploads(); + } + } +} + +// Normally, all wearables referred to "AgentWearablesUpdate" will correspond to actual assets in the +// database. If for some reason, we can't load one of those assets, we can try to reconstruct it so that +// the user isn't left without a shape, for example. (We can do that only after the inventory has loaded.) +void LLAgent::recoverMissingWearable( EWearableType type ) +{ + // Try to recover by replacing missing wearable with a new one. + LLNotifyBox::showXml("ReplacedMissingWearable"); + lldebugs << "Wearable " << LLWearable::typeToTypeLabel( type ) << " could not be downloaded. Replaced inventory item with default wearable." << llendl; + LLWearable* new_wearable = gWearableList.createNewWearable(type); + + S32 type_s32 = (S32) type; + mWearableEntry[type_s32].mWearable = new_wearable; + new_wearable->writeToAvatar( TRUE ); + + // Add a new one in the lost and found folder. + // (We used to overwrite the "not found" one, but that could potentially + // destory content.) JC + LLUUID lost_and_found_id = + gInventory.findCategoryUUIDForType(LLAssetType::AT_LOST_AND_FOUND); + LLPointer<LLInventoryCallback> cb = + new addWearableToAgentInventoryCallback( + LLPointer<LLRefCount>(NULL), + type_s32, + new_wearable, + addWearableToAgentInventoryCallback::CALL_RECOVERDONE); + addWearableToAgentInventory( cb, new_wearable, lost_and_found_id, TRUE); +} + +void LLAgent::recoverMissingWearableDone() +{ + // Have all the wearables that the avatar was wearing at log-in arrived or been fabricated? + mWearablesLoaded = TRUE; + for( S32 i = 0; i < WT_COUNT; i++ ) + { + if( !mWearableEntry[i].mItemID.isNull() && !mWearableEntry[i].mWearable ) + { + mWearablesLoaded = FALSE; + break; + } + } + + if( mWearablesLoaded ) + { + // Make sure that the server's idea of the avatar's wearables actually match the wearables. + sendAgentSetAppearance(); + } + else + { + gInventory.addChangedMask( LLInventoryObserver::LABEL, LLUUID::null ); + gInventory.notifyObservers(); + } +} + +void LLAgent::createStandardWearables(BOOL female) +{ + llwarns << "Creating Standard " << (female ? "female" : "male" ) + << " Wearables" << llendl; + + if (mAvatarObject.isNull()) + { + return; + } + + if(female) mAvatarObject->setSex(SEX_FEMALE); + else mAvatarObject->setSex(SEX_MALE); + + BOOL create[WT_COUNT] = + { + TRUE, //WT_SHAPE + TRUE, //WT_SKIN + TRUE, //WT_HAIR + TRUE, //WT_EYES + TRUE, //WT_SHIRT + TRUE, //WT_PANTS + TRUE, //WT_SHOES + TRUE, //WT_SOCKS + FALSE, //WT_JACKET + FALSE, //WT_GLOVES + TRUE, //WT_UNDERSHIRT + TRUE, //WT_UNDERPANTS + FALSE //WT_SKIRT + }; + + for( S32 i=0; i < WT_COUNT; i++ ) + { + bool once = false; + LLPointer<LLRefCount> donecb = NULL; + if( create[i] ) + { + if (!once) + { + once = true; + donecb = new createStandardWearablesAllDoneCallback; + } + llassert( mWearableEntry[i].mWearable == NULL ); + LLWearable* wearable = gWearableList.createNewWearable((EWearableType)i); + mWearableEntry[i].mWearable = wearable; + // no need to update here... + LLPointer<LLInventoryCallback> cb = + new addWearableToAgentInventoryCallback( + donecb, + i, + wearable, + addWearableToAgentInventoryCallback::CALL_CREATESTANDARDDONE); + addWearableToAgentInventory(cb, wearable, LLUUID::null, FALSE); + } + } +} +void LLAgent::createStandardWearablesDone(S32 index) +{ + LLWearable* wearable = mWearableEntry[index].mWearable; + + if (wearable) + { + wearable->writeToAvatar(TRUE); + } +} + +void LLAgent::createStandardWearablesAllDone() +{ + // ... because sendAgentWearablesUpdate will notify inventory + // observers. + mWearablesLoaded = TRUE; + sendAgentWearablesUpdate(); + sendAgentSetAppearance(); + + // Treat this as the first texture entry message, if none received yet + mAvatarObject->onFirstTEMessageReceived(); +} + +void LLAgent::makeNewOutfit( + const std::string& new_folder_name, + const LLDynamicArray<S32>& wearables_to_include, + const LLDynamicArray<S32>& attachments_to_include, + BOOL rename_clothing) +{ + if (mAvatarObject.isNull()) + { + return; + } + + // First, make a folder in the Clothes directory. + LLUUID folder_id = gInventory.createNewCategory( + gInventory.findCategoryUUIDForType(LLAssetType::AT_CLOTHING), + LLAssetType::AT_NONE, + new_folder_name); + + bool found_first_item = false; + + /////////////////// + // Wearables + + if( wearables_to_include.count() ) + { + // Then, iterate though each of the wearables and save copies of them in the folder. + S32 i; + S32 count = wearables_to_include.count(); + LLDynamicArray<LLUUID> delete_items; + LLPointer<LLRefCount> cbdone = NULL; + for( i = 0; i < count; ++i ) + { + S32 index = wearables_to_include[i]; + LLWearable* old_wearable = mWearableEntry[ index ].mWearable; + if( old_wearable ) + { + std::string new_name; + LLWearable* new_wearable; + new_wearable = gWearableList.createCopy(old_wearable); + if (rename_clothing) + { + new_name = new_folder_name; + new_name.append(" "); + new_name.append(old_wearable->getTypeLabel()); + LLString::truncate(new_name, DB_INV_ITEM_NAME_STR_LEN); + new_wearable->setName(new_name); + } + + LLViewerInventoryItem* item = gInventory.getItem(mWearableEntry[index].mItemID); + S32 todo = addWearableToAgentInventoryCallback::CALL_NONE; + if (!found_first_item) + { + found_first_item = true; + /* set the focus to the first item */ + todo |= addWearableToAgentInventoryCallback::CALL_MAKENEWOUTFITDONE; + /* send the agent wearables update when done */ + cbdone = new sendAgentWearablesUpdateCallback; + } + LLPointer<LLInventoryCallback> cb = + new addWearableToAgentInventoryCallback( + cbdone, + index, + new_wearable, + todo); + if (isWearableCopyable((EWearableType)index)) + { + copy_inventory_item( + gAgent.getID(), + item->getPermissions().getOwner(), + item->getUUID(), + folder_id, + new_name, + cb); + } + else + { + move_inventory_item( + gAgent.getID(), + gAgent.getSessionID(), + item->getUUID(), + folder_id, + new_name, + cb); + } + } + } + gInventory.notifyObservers(); + } + + + /////////////////// + // Attachments + + if( attachments_to_include.count() ) + { + BOOL msg_started = FALSE; + LLMessageSystem* msg = gMessageSystem; + S32 i; + for( i = 0; i < attachments_to_include.count(); i++ ) + { + S32 attachment_pt = attachments_to_include[i]; + LLViewerJointAttachment* attachment = mAvatarObject->mAttachmentPoints.getIfThere( attachment_pt ); + if(!attachment) continue; + LLViewerObject* attached_object = attachment->getObject(0); + if(!attached_object) continue; + const LLUUID& item_id = attachment->getItemID(); + if(item_id.isNull()) continue; + LLInventoryItem* item = gInventory.getItem(item_id); + if(!item) continue; + if(!msg_started) + { + msg_started = TRUE; + msg->newMessage("CreateNewOutfitAttachments"); + msg->nextBlock("AgentData"); + msg->addUUID("AgentID", getID()); + msg->addUUID("SessionID", getSessionID()); + msg->nextBlock("HeaderData"); + msg->addUUID("NewFolderID", folder_id); + } + msg->nextBlock("ObjectData"); + msg->addUUID("OldItemID", item_id); + msg->addUUID("OldFolderID", item->getParentUUID()); + } + + if( msg_started ) + { + sendReliableMessage(); + } + + } +} + +void LLAgent::makeNewOutfitDone(S32 index) +{ + LLUUID first_item_id = mWearableEntry[index].mItemID; + // Open the inventory and select the first item we added. + if( first_item_id.notNull() ) + { + LLInventoryView* view = LLInventoryView::getActiveInventory(); + if(view) + { + view->getPanel()->setSelection(first_item_id, TAKE_FOCUS_NO); + } + } +} + + +void LLAgent::addWearableToAgentInventory( + LLPointer<LLInventoryCallback> cb, + LLWearable* wearable, + const LLUUID& category_id, + BOOL notify) +{ + create_inventory_item( + gAgent.getID(), + gAgent.getSessionID(), + category_id, + wearable->getTransactionID(), + wearable->getName(), + wearable->getDescription(), + wearable->getAssetType(), + LLInventoryType::IT_WEARABLE, + wearable->getType(), + wearable->getPermissions().getMaskNextOwner(), + cb); +} + +//----------------------------------------------------------------------------- +// sendAgentSetAppearance() +//----------------------------------------------------------------------------- +void LLAgent::sendAgentSetAppearance() +{ + if (mAvatarObject.isNull()) return; + + if (mNumPendingQueries > 0 && !gAgent.cameraCustomizeAvatar()) + { + return; + } + + llinfos << "TAT: Sent AgentSetAppearance: " << + (( mAvatarObject->getTEImage( LLVOAvatar::TEX_HEAD_BAKED )->getID() != IMG_DEFAULT_AVATAR ) ? "HEAD " : "head " ) << + (( mAvatarObject->getTEImage( LLVOAvatar::TEX_UPPER_BAKED )->getID() != IMG_DEFAULT_AVATAR ) ? "UPPER " : "upper " ) << + (( mAvatarObject->getTEImage( LLVOAvatar::TEX_LOWER_BAKED )->getID() != IMG_DEFAULT_AVATAR ) ? "LOWER " : "lower " ) << + (( mAvatarObject->getTEImage( LLVOAvatar::TEX_EYES_BAKED )->getID() != IMG_DEFAULT_AVATAR ) ? "EYES" : "eyes" ) << llendl; + //dumpAvatarTEs( "sendAgentSetAppearance()" ); + + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_AgentSetAppearance); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, getID()); + msg->addUUIDFast(_PREHASH_SessionID, getSessionID()); + + // correct for the collisiton tolerance (to make it look like the + // agent is actually walking on the ground/object) + // NOTE -- when we start correcting all of the other Havok geometry + // to compensate for the COLLISION_TOLERANCE ugliness we will have + // to tweak this number again + LLVector3 body_size = mAvatarObject->mBodySize; + msg->addVector3Fast(_PREHASH_Size, body_size); + + // To guard against out of order packets + // Note: always start by sending 1. This resets the server's count. 0 on the server means "uninitialized" + mAppearanceSerialNum++; + msg->addU32Fast(_PREHASH_SerialNum, mAppearanceSerialNum ); + + // is texture data current relative to wearables? + // KLW - TAT this will probably need to check the local queue. + BOOL textures_current = !mAvatarObject->hasPendingBakedUploads() && mWearablesLoaded; + + S32 baked_texture_index; + for( baked_texture_index = 0; baked_texture_index < BAKED_TEXTURE_COUNT; baked_texture_index++ ) + { + S32 tex_index = LLVOAvatar::sBakedTextureIndices[baked_texture_index]; + + // if we're not wearing a skirt, we don't need the texture to be baked + if (tex_index == LLVOAvatar::TEX_SKIRT_BAKED && !mAvatarObject->isWearingWearableType(WT_SKIRT)) + { + continue; + } + + // IMG_DEFAULT_AVATAR means not baked + if (mAvatarObject->getTEImage( tex_index)->getID() == IMG_DEFAULT_AVATAR) + { + textures_current = FALSE; + break; + } + } + + // only update cache entries if we have all our baked textures + if (textures_current) + { + llinfos << "TAT: Sending cached texture data" << llendl; + for (baked_texture_index = 0; baked_texture_index < BAKED_TEXTURE_COUNT; baked_texture_index++) + { + LLUUID hash; + + for( S32 wearable_num = 0; wearable_num < MAX_WEARABLES_PER_LAYERSET; wearable_num++ ) + { + EWearableType wearable_type = WEARABLE_BAKE_TEXTURE_MAP[baked_texture_index][wearable_num]; + + LLWearable* wearable = getWearable( wearable_type ); + if (wearable) + { + hash ^= wearable->getID(); + } + } + + if (hash.notNull()) + { + hash ^= BAKED_TEXTURE_HASH[baked_texture_index]; + } + + S32 tex_index = LLVOAvatar::sBakedTextureIndices[baked_texture_index]; + + msg->nextBlockFast(_PREHASH_WearableData); + msg->addUUIDFast(_PREHASH_CacheID, hash); + msg->addU8Fast(_PREHASH_TextureIndex, (U8)tex_index); + } + } + + msg->nextBlockFast(_PREHASH_ObjectData); + mAvatarObject->packTEMessage( gMessageSystem ); + + S32 transmitted_params = 0; + for (LLViewerVisualParam* param = (LLViewerVisualParam*)mAvatarObject->getFirstVisualParam(); + param; + param = (LLViewerVisualParam*)mAvatarObject->getNextVisualParam()) + { + F32 param_value = param->getWeight(); + + if (param->getGroup() == VISUAL_PARAM_GROUP_TWEAKABLE) + { + msg->nextBlockFast(_PREHASH_VisualParam ); + + // We don't send the param ids. Instead, we assume that the receiver has the same params in the same sequence. + U8 new_weight = F32_to_U8(param_value, param->getMinWeight(), param->getMaxWeight()); + msg->addU8Fast(_PREHASH_ParamValue, new_weight ); + transmitted_params++; + } + } + +// llinfos << "Avatar XML num VisualParams transmitted = " << transmitted_params << llendl; + sendReliableMessage(); +} + +void LLAgent::sendAgentDataUpdateRequest() +{ + gMessageSystem->newMessageFast(_PREHASH_AgentDataUpdateRequest); + gMessageSystem->nextBlockFast(_PREHASH_AgentData); + gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + sendReliableMessage(); +} + +void LLAgent::removeWearable( EWearableType type ) +{ + LLWearable* old_wearable = mWearableEntry[ type ].mWearable; + + if ( (gAgent.mAccess < SIM_ACCESS_MATURE) + && (type == WT_UNDERSHIRT || type == WT_UNDERPANTS)) + { + // Can't take off underclothing in simple UI mode or on PG accounts + return; + } + + if( old_wearable ) + { + if( old_wearable->isDirty() ) + { + // Bring up view-modal dialog: Save changes? Yes, No, Cancel + gViewerWindow->alertXml("RemoveWearableSave", LLAgent::onRemoveWearableDialog, (void*)(S32)type ); + return; + } + else + { + removeWearableFinal( type ); + } + } +} + +// static +void LLAgent::onRemoveWearableDialog( S32 option, void* userdata ) +{ + EWearableType type = (EWearableType)(intptr_t)userdata; + switch( option ) + { + case 0: // "Save" + gAgent.saveWearable( type ); + gAgent.removeWearableFinal( type ); + break; + + case 1: // "Don't Save" + gAgent.removeWearableFinal( type ); + break; + + case 2: // "Cancel" + break; + + default: + llassert(0); + break; + } +} + +// Called by removeWearable() and onRemoveWearableDialog() to actually do the removal. +void LLAgent::removeWearableFinal( EWearableType type ) +{ + LLWearable* old_wearable = mWearableEntry[ type ].mWearable; + + gInventory.addChangedMask( LLInventoryObserver::LABEL, mWearableEntry[type].mItemID ); + + mWearableEntry[ type ].mWearable = NULL; + mWearableEntry[ type ].mItemID.setNull(); + + queryWearableCache(); + + if( old_wearable ) + { + old_wearable->removeFromAvatar( TRUE ); + } + + // Update the server + sendAgentWearablesUpdate(); + sendAgentSetAppearance(); + gInventory.notifyObservers(); +} + +void LLAgent::copyWearableToInventory( EWearableType type ) +{ + LLWearable* wearable = mWearableEntry[ type ].mWearable; + if( wearable ) + { + // Save the old wearable if it has changed. + if( wearable->isDirty() ) + { + wearable = gWearableList.createCopyFromAvatar( wearable ); + mWearableEntry[ type ].mWearable = wearable; + } + + // Make a new entry in the inventory. (Put it in the same folder as the original item if possible.) + LLUUID category_id; + LLInventoryItem* item = gInventory.getItem( mWearableEntry[ type ].mItemID ); + if( item ) + { + category_id = item->getParentUUID(); + wearable->setPermissions(item->getPermissions()); + } + LLPointer<LLInventoryCallback> cb = + new addWearableToAgentInventoryCallback( + LLPointer<LLRefCount>(NULL), + type, + wearable); + addWearableToAgentInventory(cb, wearable, category_id); + } +} + + +// A little struct to let setWearable() communicate more than one value with onSetWearableDialog(). +struct LLSetWearableData +{ + LLSetWearableData( const LLUUID& new_item_id, LLWearable* new_wearable ) : + mNewItemID( new_item_id ), mNewWearable( new_wearable ) {} + LLUUID mNewItemID; + LLWearable* mNewWearable; +}; + +BOOL LLAgent::needsReplacement(EWearableType wearableType, S32 remove) +{ + return TRUE; + /*if (remove) return TRUE; + + return getWearable(wearableType) ? TRUE : FALSE;*/ +} + +// Assumes existing wearables are not dirty. +void LLAgent::setWearableOutfit( + const LLInventoryItem::item_array_t& items, + const LLDynamicArray< LLWearable* >& wearables, + BOOL remove ) +{ + lldebugs << "setWearableOutfit() start" << llendl; + + BOOL wearables_to_remove[WT_COUNT]; + wearables_to_remove[WT_SHAPE] = FALSE; + wearables_to_remove[WT_SKIN] = FALSE; + wearables_to_remove[WT_HAIR] = FALSE; + wearables_to_remove[WT_EYES] = FALSE; + wearables_to_remove[WT_SHIRT] = remove; + wearables_to_remove[WT_PANTS] = remove; + wearables_to_remove[WT_SHOES] = remove; + wearables_to_remove[WT_SOCKS] = remove; + wearables_to_remove[WT_JACKET] = remove; + wearables_to_remove[WT_GLOVES] = remove; + wearables_to_remove[WT_UNDERSHIRT] = (gAgent.mAccess >= SIM_ACCESS_MATURE) & remove; + wearables_to_remove[WT_UNDERPANTS] = (gAgent.mAccess >= SIM_ACCESS_MATURE) & remove; + wearables_to_remove[WT_SKIRT] = remove; + + S32 count = wearables.count(); + llassert( items.count() == count ); + + S32 i; + for( i = 0; i < count; i++ ) + { + LLWearable* new_wearable = wearables[i]; + LLPointer<LLInventoryItem> new_item = items[i]; + + EWearableType type = new_wearable->getType(); + wearables_to_remove[type] = FALSE; + + LLWearable* old_wearable = mWearableEntry[ type ].mWearable; + if( old_wearable ) + { + const LLUUID& old_item_id = mWearableEntry[ type ].mItemID; + if( (old_wearable->getID() == new_wearable->getID()) && + (old_item_id == new_item->getUUID()) ) + { + lldebugs << "No change to wearable asset and item: " << LLWearable::typeToTypeName( type ) << llendl; + continue; + } + + gInventory.addChangedMask(LLInventoryObserver::LABEL, old_item_id); + + // Assumes existing wearables are not dirty. + if( old_wearable->isDirty() ) + { + llassert(0); + continue; + } + } + + mWearableEntry[ type ].mItemID = new_item->getUUID(); + mWearableEntry[ type ].mWearable = new_wearable; + } + + std::vector<LLWearable*> wearables_being_removed; + + for( i = 0; i < WT_COUNT; i++ ) + { + if( wearables_to_remove[i] ) + { + wearables_being_removed.push_back(mWearableEntry[ i ].mWearable); + mWearableEntry[ i ].mWearable = NULL; + + gInventory.addChangedMask(LLInventoryObserver::LABEL, mWearableEntry[ i ].mItemID); + mWearableEntry[ i ].mItemID.setNull(); + } + } + + gInventory.notifyObservers(); + + queryWearableCache(); + + std::vector<LLWearable*>::iterator wearable_iter; + + for( wearable_iter = wearables_being_removed.begin(); + wearable_iter != wearables_being_removed.end(); + ++wearable_iter) + { + LLWearable* wearablep = *wearable_iter; + if (wearablep) + { + wearablep->removeFromAvatar( TRUE ); + } + } + + for( i = 0; i < count; i++ ) + { + wearables[i]->writeToAvatar( TRUE ); + } + + LLFloaterCustomize::setCurrentWearableType( WT_SHAPE ); + + // Start rendering & update the server + mWearablesLoaded = TRUE; + sendAgentWearablesUpdate(); + sendAgentSetAppearance(); + + lldebugs << "setWearableOutfit() end" << llendl; +} + + +// User has picked "wear on avatar" from a menu. +void LLAgent::setWearable( LLInventoryItem* new_item, LLWearable* new_wearable ) +{ + EWearableType type = new_wearable->getType(); + + LLWearable* old_wearable = mWearableEntry[ type ].mWearable; + if( old_wearable ) + { + const LLUUID& old_item_id = mWearableEntry[ type ].mItemID; + if( (old_wearable->getID() == new_wearable->getID()) && + (old_item_id == new_item->getUUID()) ) + { + lldebugs << "No change to wearable asset and item: " << LLWearable::typeToTypeName( type ) << llendl; + return; + } + + if( old_wearable->isDirty() ) + { + // Bring up modal dialog: Save changes? Yes, No, Cancel + gViewerWindow->alertXml( "SetWearableSave", LLAgent::onSetWearableDialog, + new LLSetWearableData( new_item->getUUID(), new_wearable )); + return; + } + } + + setWearableFinal( new_item, new_wearable ); +} + +// static +void LLAgent::onSetWearableDialog( S32 option, void* userdata ) +{ + LLSetWearableData* data = (LLSetWearableData*)userdata; + LLInventoryItem* new_item = gInventory.getItem( data->mNewItemID ); + if( !new_item ) + { + delete data; + return; + } + + switch( option ) + { + case 0: // "Save" + gAgent.saveWearable( data->mNewWearable->getType() ); + gAgent.setWearableFinal( new_item, data->mNewWearable ); + break; + + case 1: // "Don't Save" + gAgent.setWearableFinal( new_item, data->mNewWearable ); + break; + + case 2: // "Cancel" + break; + + default: + llassert(0); + break; + } + + delete data; +} + +// Called from setWearable() and onSetWearableDialog() to actually set the wearable. +void LLAgent::setWearableFinal( LLInventoryItem* new_item, LLWearable* new_wearable ) +{ + EWearableType type = new_wearable->getType(); + + // Replace the old wearable with a new one. + llassert( new_item->getAssetUUID() == new_wearable->getID() ); + LLUUID old_item_id = mWearableEntry[ type ].mItemID; + mWearableEntry[ type ].mItemID = new_item->getUUID(); + mWearableEntry[ type ].mWearable = new_wearable; + + if (old_item_id.notNull()) + { + gInventory.addChangedMask( LLInventoryObserver::LABEL, old_item_id ); + gInventory.notifyObservers(); + } + + //llinfos << "LLVOAvatar::setWearable()" << llendl; + queryWearableCache(); + new_wearable->writeToAvatar( TRUE ); + + // Update the server + sendAgentWearablesUpdate(); + sendAgentSetAppearance(); +} + +void LLAgent::queryWearableCache() +{ + if (!mWearablesLoaded) + { + return; + } + + // Look up affected baked textures. + // If they exist: + // disallow updates for affected layersets (until dataserver responds with cache request.) + // If cache miss…turn updates back on and invalidate composite. + // If cache hit, modify baked texture entries. + // + // Cache requests contain list of hashes for each baked texture entry. + // Response is list of valid baked texture assets. (same message) + + gMessageSystem->newMessageFast(_PREHASH_AgentCachedTexture); + gMessageSystem->nextBlockFast(_PREHASH_AgentData); + gMessageSystem->addUUIDFast(_PREHASH_AgentID, getID()); + gMessageSystem->addUUIDFast(_PREHASH_SessionID, getSessionID()); + gMessageSystem->addS32Fast(_PREHASH_SerialNum, mTextureCacheQueryID); + + S32 num_queries = 0; + for (S32 baked_texture_index = 0; baked_texture_index < BAKED_TEXTURE_COUNT; baked_texture_index++) + { + LLUUID hash; + for (S32 wearable_num = 0; wearable_num < MAX_WEARABLES_PER_LAYERSET; wearable_num++) + { + EWearableType wearable_type = WEARABLE_BAKE_TEXTURE_MAP[baked_texture_index][wearable_num]; + + LLWearable* wearable = getWearable( wearable_type ); + if (wearable) + { + hash ^= wearable->getID(); + } + } + if (hash.notNull()) + { + hash ^= BAKED_TEXTURE_HASH[baked_texture_index]; + num_queries++; + //FIXME: make sure at least one request gets packed + + //llinfos << "Requesting texture for hash " << hash << " in baked texture slot " << baked_texture_index << llendl; + gMessageSystem->nextBlockFast(_PREHASH_WearableData); + gMessageSystem->addUUIDFast(_PREHASH_ID, hash); + gMessageSystem->addU8Fast(_PREHASH_TextureIndex, (U8)baked_texture_index); + } + + mActiveCacheQueries[ baked_texture_index ] = mTextureCacheQueryID; + } + + llinfos << "Requesting texture cache entry for " << num_queries << " baked textures" << llendl; + gMessageSystem->sendReliable(getRegion()->getHost()); + mNumPendingQueries++; + mTextureCacheQueryID++; +} + +// User has picked "remove from avatar" from a menu. +// static +void LLAgent::userRemoveWearable( void* userdata ) +{ + EWearableType type = (EWearableType)(intptr_t)userdata; + + if( !(type==WT_SHAPE || type==WT_SKIN || type==WT_HAIR ) ) //&& + //!((gAgent.mAccess >= SIM_ACCESS_MATURE) && ( type==WT_UNDERPANTS || type==WT_UNDERSHIRT )) ) + { + gAgent.removeWearable( type ); + } +} + +void LLAgent::userRemoveAllClothes( void* userdata ) +{ + // We have to do this up front to avoid having to deal with the case of multiple wearables being dirty. + if( gFloaterCustomize ) + { + gFloaterCustomize->askToSaveAllIfDirty( LLAgent::userRemoveAllClothesStep2, NULL ); + } + else + { + LLAgent::userRemoveAllClothesStep2( TRUE, NULL ); + } +} + +void LLAgent::userRemoveAllClothesStep2( BOOL proceed, void* userdata ) +{ + if( proceed ) + { + gAgent.removeWearable( WT_SHIRT ); + gAgent.removeWearable( WT_PANTS ); + gAgent.removeWearable( WT_SHOES ); + gAgent.removeWearable( WT_SOCKS ); + gAgent.removeWearable( WT_JACKET ); + gAgent.removeWearable( WT_GLOVES ); + gAgent.removeWearable( WT_UNDERSHIRT ); + gAgent.removeWearable( WT_UNDERPANTS ); + gAgent.removeWearable( WT_SKIRT ); + } +} + +void LLAgent::userRemoveAllAttachments( void* userdata ) +{ + LLVOAvatar* avatarp = gAgent.getAvatarObject(); + if(!avatarp) + { + llwarns << "No avatar found." << llendl; + return; + } + + gMessageSystem->newMessage("ObjectDetach"); + gMessageSystem->nextBlockFast(_PREHASH_AgentData); + gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); + gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + + LLViewerJointAttachment* attachment; + for (attachment = avatarp->mAttachmentPoints.getFirstData(); + attachment; + attachment = avatarp->mAttachmentPoints.getNextData()) + { + LLViewerObject* objectp = attachment->getObject(0); + if (objectp) + { + gMessageSystem->nextBlockFast(_PREHASH_ObjectData); + gMessageSystem->addU32Fast(_PREHASH_ObjectLocalID, objectp->getLocalID()); + } + } + gMessageSystem->sendReliable( gAgent.getRegionHost() ); +} + +bool LLAgent::LLHideGroupTitleListener::handleEvent(LLPointer<LLEvent> event, const LLSD &userdata) +{ + gAgent.setHideGroupTitle(event->getValue()); + return true; + +} + +bool LLAgent::LLEffectColorListener::handleEvent(LLPointer<LLEvent> event, const LLSD &userdata) +{ + gAgent.setEffectColor(LLColor4(event->getValue())); + return true; +} + +void LLAgent::observeFriends() +{ + if(!mFriendObserver) + { + mFriendObserver = new LLAgentFriendObserver; + LLAvatarTracker::instance().addObserver(mFriendObserver); + friendsChanged(); + } +} + +// EOF |