diff options
Diffstat (limited to 'indra/newview/llnetmap.cpp')
-rw-r--r-- | indra/newview/llnetmap.cpp | 2492 |
1 files changed, 1246 insertions, 1246 deletions
diff --git a/indra/newview/llnetmap.cpp b/indra/newview/llnetmap.cpp index 6a2299076c..1410232a0f 100644 --- a/indra/newview/llnetmap.cpp +++ b/indra/newview/llnetmap.cpp @@ -1,1246 +1,1246 @@ -/**
- * @file llnetmap.cpp
- * @author James Cook
- * @brief Display of surrounding regions, objects, and agents.
- *
- * $LicenseInfo:firstyear=2001&license=viewerlgpl$
- * Second Life Viewer Source Code
- * Copyright (C) 2001-2010, Linden Research, Inc.
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation;
- * version 2.1 of the License only.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
- * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
- * $/LicenseInfo$
- */
-
-#include "llviewerprecompiledheaders.h"
-
-#include "llnetmap.h"
-
-// Library includes (should move below)
-#include "indra_constants.h"
-#include "llavatarnamecache.h"
-#include "llmath.h"
-#include "llfloaterreg.h"
-#include "llfocusmgr.h"
-#include "lllocalcliprect.h"
-#include "llrender.h"
-#include "llresmgr.h"
-#include "llui.h"
-#include "lltooltip.h"
-
-#include "llglheaders.h"
-
-// Viewer includes
-#include "llagent.h"
-#include "llagentcamera.h"
-#include "llappviewer.h" // for gDisconnected
-#include "llcallingcard.h" // LLAvatarTracker
-#include "llfloaterland.h"
-#include "llfloaterworldmap.h"
-#include "llparcel.h"
-#include "lltracker.h"
-#include "llsurface.h"
-#include "llurlmatch.h"
-#include "llurlregistry.h"
-#include "llviewercamera.h"
-#include "llviewercontrol.h"
-#include "llviewerparcelmgr.h"
-#include "llviewertexture.h"
-#include "llviewertexturelist.h"
-#include "llviewermenu.h"
-#include "llviewerobjectlist.h"
-#include "llviewerregion.h"
-#include "llviewerwindow.h"
-#include "llworld.h"
-#include "llworldmapview.h" // shared draw code
-
-static LLDefaultChildRegistry::Register<LLNetMap> r1("net_map");
-
-constexpr F32 LLNetMap::MAP_SCALE_MIN = 32;
-constexpr F32 LLNetMap::MAP_SCALE_FAR = 32;
-constexpr F32 LLNetMap::MAP_SCALE_MEDIUM = 128;
-constexpr F32 LLNetMap::MAP_SCALE_CLOSE = 256;
-constexpr F32 LLNetMap::MAP_SCALE_VERY_CLOSE = 1024;
-constexpr F32 LLNetMap::MAP_SCALE_MAX = 4096;
-
-constexpr F32 MAP_SCALE_ZOOM_FACTOR = 1.04f; // Zoom in factor per click of scroll wheel (4%)
-constexpr F32 MIN_DOT_RADIUS = 3.5f;
-constexpr F32 DOT_SCALE = 0.75f;
-constexpr F32 MIN_PICK_SCALE = 2.f;
-constexpr S32 MOUSE_DRAG_SLOP = 2; // How far the mouse needs to move before we think it's a drag
-
-constexpr F64 COARSEUPDATE_MAX_Z = 1020.0f;
-
-LLNetMap::LLNetMap (const Params & p)
-: LLUICtrl (p),
- mBackgroundColor (p.bg_color()),
- mScale( MAP_SCALE_MEDIUM ),
- mPixelsPerMeter( MAP_SCALE_MEDIUM / REGION_WIDTH_METERS ),
- mObjectMapTPM(0.f),
- mObjectMapPixels(0.f),
- mCurPan(0.f, 0.f),
- mStartPan(0.f, 0.f),
- mPopupWorldPos(0.f, 0.f, 0.f),
- mMouseDown(0, 0),
- mPanning(false),
- mUpdateNow(false),
- mObjectImageCenterGlobal( gAgentCamera.getCameraPositionGlobal() ),
- mObjectRawImagep(),
- mObjectImagep(),
- mClosestAgentToCursor(),
- mClosestAgentAtLastRightClick(),
- mToolTipMsg()
-{
- mScale = gSavedSettings.getF32("MiniMapScale");
- if (gAgent.isFirstLogin())
- {
- // *HACK: On first run, set this to false for new users, otherwise the
- // default is true to maintain consistent experience for existing
- // users.
- gSavedSettings.setBOOL("MiniMapRotate", false);
- }
- mPixelsPerMeter = mScale / REGION_WIDTH_METERS;
- mDotRadius = llmax(DOT_SCALE * mPixelsPerMeter, MIN_DOT_RADIUS);
-}
-
-LLNetMap::~LLNetMap()
-{
- auto menu = static_cast<LLMenuGL*>(mPopupMenuHandle.get());
- if (menu)
- {
- menu->die();
- mPopupMenuHandle.markDead();
- }
-}
-
-bool LLNetMap::postBuild()
-{
- LLUICtrl::CommitCallbackRegistry::ScopedRegistrar commitRegistrar;
- LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enableRegistrar;
-
- enableRegistrar.add("Minimap.Zoom.Check", boost::bind(&LLNetMap::isZoomChecked, this, _2));
- commitRegistrar.add("Minimap.Zoom.Set", boost::bind(&LLNetMap::setZoom, this, _2));
- commitRegistrar.add("Minimap.Tracker", boost::bind(&LLNetMap::handleStopTracking, this, _2));
- commitRegistrar.add("Minimap.Center.Activate", boost::bind(&LLNetMap::activateCenterMap, this, _2));
- enableRegistrar.add("Minimap.MapOrientation.Check", boost::bind(&LLNetMap::isMapOrientationChecked, this, _2));
- commitRegistrar.add("Minimap.MapOrientation.Set", boost::bind(&LLNetMap::setMapOrientation, this, _2));
- commitRegistrar.add("Minimap.AboutLand", boost::bind(&LLNetMap::popupShowAboutLand, this, _2));
-
- LLMenuGL* menu = LLUICtrlFactory::getInstance()->createFromFile<LLMenuGL>("menu_mini_map.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance());
- mPopupMenuHandle = menu->getHandle();
- menu->setItemEnabled("Re-center map", false);
- return true;
-}
-
-void LLNetMap::setScale( F32 scale )
-{
- scale = llclamp(scale, MAP_SCALE_MIN, MAP_SCALE_MAX);
- mCurPan *= scale / mScale;
- mScale = scale;
-
- if (mObjectImagep.notNull())
- {
- F32 width = (F32)(getRect().getWidth());
- F32 height = (F32)(getRect().getHeight());
- F32 diameter = sqrt(width * width + height * height);
- F32 region_widths = diameter / mScale;
- F32 meters = region_widths * LLWorld::getInstance()->getRegionWidthInMeters();
- F32 num_pixels = (F32)mObjectImagep->getWidth();
- mObjectMapTPM = num_pixels / meters;
- mObjectMapPixels = diameter;
- }
-
- mPixelsPerMeter = mScale / REGION_WIDTH_METERS;
- mDotRadius = llmax(DOT_SCALE * mPixelsPerMeter, MIN_DOT_RADIUS);
-
- gSavedSettings.setF32("MiniMapScale", mScale);
-
- mUpdateNow = true;
-}
-
-
-///////////////////////////////////////////////////////////////////////////////////
-
-void LLNetMap::draw()
-{
- if (!LLWorld::instanceExists())
- {
- return;
- }
- LL_PROFILE_ZONE_SCOPED;
- static LLFrameTimer map_timer;
- static LLUIColor map_avatar_color = LLUIColorTable::instance().getColor("MapAvatarColor", LLColor4::white);
- static LLUIColor map_avatar_friend_color = LLUIColorTable::instance().getColor("MapAvatarFriendColor", LLColor4::white);
- static LLUIColor map_track_color = LLUIColorTable::instance().getColor("MapTrackColor", LLColor4::white);
- //static LLUIColor map_track_disabled_color = LLUIColorTable::instance().getColor("MapTrackDisabledColor", LLColor4::white);
- static LLUIColor map_frustum_color = LLUIColorTable::instance().getColor("MapFrustumColor", LLColor4::white);
- static LLUIColor map_parcel_outline_color = LLUIColorTable::instance().getColor("MapParcelOutlineColor", LLColor4(LLColor3(LLColor4::yellow), 0.5f));
-
- if (mObjectImagep.isNull())
- {
- createObjectImage();
- }
-
- static LLUICachedControl<bool> auto_center("MiniMapAutoCenter", true);
- bool auto_centering = auto_center && !mPanning;
- mCentering = mCentering && !mPanning;
-
- if (auto_centering || mCentering)
- {
- mCurPan = lerp(mCurPan, LLVector2(0.0f, 0.0f) , LLSmoothInterpolation::getInterpolant(0.1f));
- }
- bool centered = abs(mCurPan.mV[VX]) < 0.5f && abs(mCurPan.mV[VY]) < 0.5f;
- if (centered)
- {
- mCurPan.mV[0] = 0.0f;
- mCurPan.mV[1] = 0.0f;
- mCentering = false;
- }
-
- auto menu = static_cast<LLMenuGL*>(mPopupMenuHandle.get());
- if (menu)
- {
- bool can_recenter_map = !(centered || mCentering || auto_centering);
- menu->setItemEnabled("Re-center map", can_recenter_map);
- }
- updateAboutLandPopupButton();
-
- // Prepare a scissor region
- F32 rotation = 0;
-
- gGL.pushMatrix();
- gGL.pushUIMatrix();
-
- LLVector3 offset = gGL.getUITranslation();
- LLVector3 scale = gGL.getUIScale();
-
- gGL.loadIdentity();
- gGL.loadUIIdentity();
-
- gGL.scalef(scale.mV[0], scale.mV[1], scale.mV[2]);
- gGL.translatef(offset.mV[0], offset.mV[1], offset.mV[2]);
-
- {
- LLLocalClipRect clip(getLocalRect());
- {
- gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
-
- gGL.matrixMode(LLRender::MM_MODELVIEW);
-
- // Draw background rectangle
- LLColor4 background_color = mBackgroundColor.get();
- gGL.color4fv( background_color.mV );
- gl_rect_2d(0, getRect().getHeight(), getRect().getWidth(), 0);
- }
-
- // region 0,0 is in the middle
- S32 center_sw_left = getRect().getWidth() / 2 + llfloor(mCurPan.mV[VX]);
- S32 center_sw_bottom = getRect().getHeight() / 2 + llfloor(mCurPan.mV[VY]);
-
- gGL.pushMatrix();
-
- gGL.translatef( (F32) center_sw_left, (F32) center_sw_bottom, 0.f);
-
- static LLUICachedControl<bool> rotate_map("MiniMapRotate", true);
- if( rotate_map )
- {
- // rotate subsequent draws to agent rotation
- rotation = atan2( LLViewerCamera::getInstance()->getAtAxis().mV[VX], LLViewerCamera::getInstance()->getAtAxis().mV[VY] );
- gGL.rotatef( rotation * RAD_TO_DEG, 0.f, 0.f, 1.f);
- }
-
- // figure out where agent is
- const S32 region_width = ll_round(LLWorld::getInstance()->getRegionWidthInMeters());
- const F32 scale_pixels_per_meter = mScale / region_width;
-
- for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin();
- iter != LLWorld::getInstance()->getRegionList().end(); ++iter)
- {
- LLViewerRegion* regionp = *iter;
- // Find x and y position relative to camera's center.
- LLVector3 origin_agent = regionp->getOriginAgent();
- LLVector3 rel_region_pos = origin_agent - gAgentCamera.getCameraPositionAgent();
- F32 relative_x = rel_region_pos.mV[0] * scale_pixels_per_meter;
- F32 relative_y = rel_region_pos.mV[1] * scale_pixels_per_meter;
-
- // background region rectangle
- F32 bottom = relative_y;
- F32 left = relative_x;
- F32 top = bottom + mScale ;
- F32 right = left + mScale ;
-
- if (regionp == gAgent.getRegion())
- {
- gGL.color4f(1.f, 1.f, 1.f, 1.f);
- }
- else
- {
- gGL.color4f(0.8f, 0.8f, 0.8f, 1.f);
- }
-
- if (!regionp->isAlive())
- {
- gGL.color4f(1.f, 0.5f, 0.5f, 1.f);
- }
-
-
-
- // Draw using texture.
- gGL.getTexUnit(0)->bind(regionp->getLand().getSTexture());
- gGL.begin(LLRender::QUADS);
- gGL.texCoord2f(0.f, 1.f);
- gGL.vertex2f(left, top);
- gGL.texCoord2f(0.f, 0.f);
- gGL.vertex2f(left, bottom);
- gGL.texCoord2f(1.f, 0.f);
- gGL.vertex2f(right, bottom);
- gGL.texCoord2f(1.f, 1.f);
- gGL.vertex2f(right, top);
- gGL.end();
-
- // Draw water
- gGL.flush();
- {
- if (regionp->getLand().getWaterTexture())
- {
- gGL.getTexUnit(0)->bind(regionp->getLand().getWaterTexture());
- gGL.begin(LLRender::QUADS);
- gGL.texCoord2f(0.f, 1.f);
- gGL.vertex2f(left, top);
- gGL.texCoord2f(0.f, 0.f);
- gGL.vertex2f(left, bottom);
- gGL.texCoord2f(1.f, 0.f);
- gGL.vertex2f(right, bottom);
- gGL.texCoord2f(1.f, 1.f);
- gGL.vertex2f(right, top);
- gGL.end();
- }
- }
- gGL.flush();
- }
-
- // Redraw object layer periodically
- if (mUpdateNow || (map_timer.getElapsedTimeF32() > 0.5f))
- {
- mUpdateNow = false;
-
- // Locate the centre of the object layer, accounting for panning
- LLVector3 new_center = globalPosToView(gAgentCamera.getCameraPositionGlobal());
- new_center.mV[VX] -= mCurPan.mV[VX];
- new_center.mV[VY] -= mCurPan.mV[VY];
- new_center.mV[VZ] = 0.f;
- mObjectImageCenterGlobal = viewPosToGlobal(llfloor(new_center.mV[VX]), llfloor(new_center.mV[VY]));
-
- // Create the base texture.
- LLImageDataLock lock(mObjectRawImagep);
- U8 *default_texture = mObjectRawImagep->getData();
- memset( default_texture, 0, mObjectImagep->getWidth() * mObjectImagep->getHeight() * mObjectImagep->getComponents() );
-
- // Draw objects
- gObjectList.renderObjectsForMap(*this);
-
- mObjectImagep->setSubImage(mObjectRawImagep, 0, 0, mObjectImagep->getWidth(), mObjectImagep->getHeight());
-
- map_timer.reset();
- }
-
- LLVector3 map_center_agent = gAgent.getPosAgentFromGlobal(mObjectImageCenterGlobal);
- LLVector3 camera_position = gAgentCamera.getCameraPositionAgent();
- map_center_agent -= camera_position;
- map_center_agent.mV[VX] *= scale_pixels_per_meter;
- map_center_agent.mV[VY] *= scale_pixels_per_meter;
-
- gGL.getTexUnit(0)->bind(mObjectImagep);
- F32 image_half_width = 0.5f*mObjectMapPixels;
- F32 image_half_height = 0.5f*mObjectMapPixels;
-
- gGL.begin(LLRender::QUADS);
- gGL.texCoord2f(0.f, 1.f);
- gGL.vertex2f(map_center_agent.mV[VX] - image_half_width, image_half_height + map_center_agent.mV[VY]);
- gGL.texCoord2f(0.f, 0.f);
- gGL.vertex2f(map_center_agent.mV[VX] - image_half_width, map_center_agent.mV[VY] - image_half_height);
- gGL.texCoord2f(1.f, 0.f);
- gGL.vertex2f(image_half_width + map_center_agent.mV[VX], map_center_agent.mV[VY] - image_half_height);
- gGL.texCoord2f(1.f, 1.f);
- gGL.vertex2f(image_half_width + map_center_agent.mV[VX], image_half_height + map_center_agent.mV[VY]);
- gGL.end();
-
- for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin();
- iter != LLWorld::getInstance()->getRegionList().end(); ++iter)
- {
- LLViewerRegion* regionp = *iter;
- regionp->renderPropertyLinesOnMinimap(scale_pixels_per_meter, map_parcel_outline_color.get().mV);
- }
-
- gGL.popMatrix();
-
- // Mouse pointer in local coordinates
- S32 local_mouse_x;
- S32 local_mouse_y;
- //localMouse(&local_mouse_x, &local_mouse_y);
- LLUI::getInstance()->getMousePositionLocal(this, &local_mouse_x, &local_mouse_y);
- mClosestAgentToCursor.setNull();
- F32 closest_dist_squared = F32_MAX; // value will be overridden in the loop
- F32 min_pick_dist_squared = (mDotRadius * MIN_PICK_SCALE) * (mDotRadius * MIN_PICK_SCALE);
-
- LLVector3 pos_map;
- uuid_vec_t avatar_ids;
- std::vector<LLVector3d> positions;
- bool unknown_relative_z;
-
- LLWorld::getInstance()->getAvatars(&avatar_ids, &positions, gAgentCamera.getCameraPositionGlobal());
-
- // Draw avatars
- for (U32 i = 0; i < avatar_ids.size(); i++)
- {
- LLUUID uuid = avatar_ids[i];
- // Skip self, we'll draw it later
- if (uuid == gAgent.getID()) continue;
-
- pos_map = globalPosToView(positions[i]);
-
- bool show_as_friend = (LLAvatarTracker::instance().getBuddyInfo(uuid) != NULL);
-
- LLColor4 color = show_as_friend ? map_avatar_friend_color : map_avatar_color;
-
- unknown_relative_z = positions[i].mdV[VZ] >= COARSEUPDATE_MAX_Z &&
- camera_position.mV[VZ] >= COARSEUPDATE_MAX_Z;
-
- LLWorldMapView::drawAvatar(
- pos_map.mV[VX], pos_map.mV[VY],
- color,
- pos_map.mV[VZ], mDotRadius,
- unknown_relative_z);
-
- if(uuid.notNull())
- {
- bool selected = false;
- uuid_vec_t::iterator sel_iter = gmSelected.begin();
- for (; sel_iter != gmSelected.end(); sel_iter++)
- {
- if(*sel_iter == uuid)
- {
- selected = true;
- break;
- }
- }
- if(selected)
- {
- if( (pos_map.mV[VX] < 0) ||
- (pos_map.mV[VY] < 0) ||
- (pos_map.mV[VX] >= getRect().getWidth()) ||
- (pos_map.mV[VY] >= getRect().getHeight()) )
- {
- S32 x = ll_round( pos_map.mV[VX] );
- S32 y = ll_round( pos_map.mV[VY] );
- LLWorldMapView::drawTrackingCircle( getRect(), x, y, color, 1, 10);
- } else
- {
- LLWorldMapView::drawTrackingDot(pos_map.mV[VX],pos_map.mV[VY],color,0.f);
- }
- }
- }
-
- F32 dist_to_cursor_squared = dist_vec_squared(LLVector2(pos_map.mV[VX], pos_map.mV[VY]),
- LLVector2(local_mouse_x,local_mouse_y));
- if(dist_to_cursor_squared < min_pick_dist_squared && dist_to_cursor_squared < closest_dist_squared)
- {
- closest_dist_squared = dist_to_cursor_squared;
- mClosestAgentToCursor = uuid;
- }
- }
-
- // Draw dot for autopilot target
- if (gAgent.getAutoPilot())
- {
- drawTracking( gAgent.getAutoPilotTargetGlobal(), map_track_color );
- }
- else
- {
- LLTracker::ETrackingStatus tracking_status = LLTracker::getTrackingStatus();
- if ( LLTracker::TRACKING_AVATAR == tracking_status )
- {
- drawTracking( LLAvatarTracker::instance().getGlobalPos(), map_track_color );
- }
- else if ( LLTracker::TRACKING_LANDMARK == tracking_status
- || LLTracker::TRACKING_LOCATION == tracking_status )
- {
- drawTracking( LLTracker::getTrackedPositionGlobal(), map_track_color );
- }
- }
-
- // Draw dot for self avatar position
- LLVector3d pos_global = gAgent.getPositionGlobal();
- pos_map = globalPosToView(pos_global);
- S32 dot_width = ll_round(mDotRadius * 2.f);
- LLUIImagePtr you = LLWorldMapView::sAvatarYouLargeImage;
- if (you)
- {
- you->draw(ll_round(pos_map.mV[VX] - mDotRadius),
- ll_round(pos_map.mV[VY] - mDotRadius),
- dot_width,
- dot_width);
-
- F32 dist_to_cursor_squared = dist_vec_squared(LLVector2(pos_map.mV[VX], pos_map.mV[VY]),
- LLVector2(local_mouse_x,local_mouse_y));
- if(dist_to_cursor_squared < min_pick_dist_squared && dist_to_cursor_squared < closest_dist_squared)
- {
- mClosestAgentToCursor = gAgent.getID();
- }
- }
-
- // Draw frustum
- F32 meters_to_pixels = mScale/ LLWorld::getInstance()->getRegionWidthInMeters();
-
- F32 horiz_fov = LLViewerCamera::getInstance()->getView() * LLViewerCamera::getInstance()->getAspect();
- F32 far_clip_meters = LLViewerCamera::getInstance()->getFar();
- F32 far_clip_pixels = far_clip_meters * meters_to_pixels;
-
- F32 ctr_x = (F32)center_sw_left;
- F32 ctr_y = (F32)center_sw_bottom;
-
- const F32 steps_per_circle = 40.0f;
- const F32 steps_per_radian = steps_per_circle / F_TWO_PI;
- const F32 arc_start = -(horiz_fov / 2.0f) + F_PI_BY_TWO;
- const F32 arc_end = (horiz_fov / 2.0f) + F_PI_BY_TWO;
- const S32 steps = llmax(1, (S32)((horiz_fov * steps_per_radian) + 0.5f));
-
- gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
-
- if( rotate_map )
- {
- gGL.pushMatrix();
- gGL.translatef( ctr_x, ctr_y, 0 );
- gl_washer_segment_2d(far_clip_pixels, 0, arc_start, arc_end, steps, map_frustum_color(), map_frustum_color());
- gGL.popMatrix();
- }
- else
- {
- gGL.pushMatrix();
- gGL.translatef( ctr_x, ctr_y, 0 );
- // If we don't rotate the map, we have to rotate the frustum.
- gGL.rotatef( atan2( LLViewerCamera::getInstance()->getAtAxis().mV[VX], LLViewerCamera::getInstance()->getAtAxis().mV[VY] ) * RAD_TO_DEG, 0.f, 0.f, -1.f);
- gl_washer_segment_2d(far_clip_pixels, 0, arc_start, arc_end, steps, map_frustum_color(), map_frustum_color());
- gGL.popMatrix();
- }
- }
-
- gGL.popMatrix();
- gGL.popUIMatrix();
-
- LLUICtrl::draw();
-}
-
-void LLNetMap::reshape(S32 width, S32 height, bool called_from_parent)
-{
- LLUICtrl::reshape(width, height, called_from_parent);
- createObjectImage();
-}
-
-LLVector3 LLNetMap::globalPosToView(const LLVector3d& global_pos)
-{
- LLVector3d camera_position = gAgentCamera.getCameraPositionGlobal();
-
- LLVector3d relative_pos_global = global_pos - camera_position;
- LLVector3 pos_local;
- pos_local.setVec(relative_pos_global); // convert to floats from doubles
-
- pos_local.mV[VX] *= mPixelsPerMeter;
- pos_local.mV[VY] *= mPixelsPerMeter;
- // leave Z component in meters
-
- static LLUICachedControl<bool> rotate_map("MiniMapRotate", true);
- if( rotate_map )
- {
- F32 radians = atan2( LLViewerCamera::getInstance()->getAtAxis().mV[VX], LLViewerCamera::getInstance()->getAtAxis().mV[VY] );
- LLQuaternion rot(radians, LLVector3(0.f, 0.f, 1.f));
- pos_local.rotVec( rot );
- }
-
- pos_local.mV[VX] += getRect().getWidth() / 2 + mCurPan.mV[VX];
- pos_local.mV[VY] += getRect().getHeight() / 2 + mCurPan.mV[VY];
-
- return pos_local;
-}
-
-void LLNetMap::drawTracking(const LLVector3d& pos_global, const LLColor4& color,
- bool draw_arrow )
-{
- LLVector3 pos_local = globalPosToView(pos_global);
- if( (pos_local.mV[VX] < 0) ||
- (pos_local.mV[VY] < 0) ||
- (pos_local.mV[VX] >= getRect().getWidth()) ||
- (pos_local.mV[VY] >= getRect().getHeight()) )
- {
- if (draw_arrow)
- {
- S32 x = ll_round( pos_local.mV[VX] );
- S32 y = ll_round( pos_local.mV[VY] );
- LLWorldMapView::drawTrackingCircle( getRect(), x, y, color, 1, 10 );
- LLWorldMapView::drawTrackingArrow( getRect(), x, y, color );
- }
- }
- else
- {
- LLWorldMapView::drawTrackingDot(pos_local.mV[VX],
- pos_local.mV[VY],
- color,
- pos_local.mV[VZ]);
- }
-}
-
-bool LLNetMap::isMouseOnPopupMenu()
-{
- auto menu = static_cast<LLMenuGL*>(mPopupMenuHandle.get());
- if (!menu || !menu->isOpen())
- {
- return false;
- }
-
- S32 popup_x;
- S32 popup_y;
- LLUI::getInstance()->getMousePositionLocal(menu, &popup_x, &popup_y);
- // *NOTE: Tolerance is larger than it needs to be because the context menu is offset from the mouse when the menu is opened from certain
- // directions. This may be a quirk of LLMenuGL::showPopup. -Cosmic,2022-03-22
- constexpr S32 tolerance = 10;
- // Test tolerance from all four corners, as the popup menu can appear from a different direction if there's not enough space.
- // Assume the size of the popup menu is much larger than the provided tolerance.
- // In practice, this is a [tolerance]px margin around the popup menu.
- for (S32 sign_x = -1; sign_x <= 1; sign_x += 2)
- {
- for (S32 sign_y = -1; sign_y <= 1; sign_y += 2)
- {
- if (menu->pointInView(popup_x + (sign_x * tolerance), popup_y + (sign_y * tolerance)))
- {
- return true;
- }
- }
- }
- return false;
-}
-
-void LLNetMap::updateAboutLandPopupButton()
-{
- auto menu = static_cast<LLMenuGL*>(mPopupMenuHandle.get());
- if (!menu || !menu->isOpen())
- {
- return;
- }
-
- LLViewerRegion *region = LLWorld::getInstance()->getRegionFromPosGlobal(mPopupWorldPos);
- if (!region)
- {
- menu->setItemEnabled("About Land", false);
- }
- else
- {
- // Check if the mouse is in the bounds of the popup. If so, it's safe to assume no other hover function will be called, so the hover
- // parcel can be used to check if location-sensitive tooltip options are available.
- if (isMouseOnPopupMenu())
- {
- LLViewerParcelMgr::getInstance()->setHoverParcel(mPopupWorldPos);
- LLParcel *hover_parcel = LLViewerParcelMgr::getInstance()->getHoverParcel();
- bool valid_parcel = false;
- if (hover_parcel)
- {
- valid_parcel = hover_parcel->getOwnerID().notNull();
- }
- menu->setItemEnabled("About Land", valid_parcel);
- }
- }
-}
-
-LLVector3d LLNetMap::viewPosToGlobal( S32 x, S32 y )
-{
- x -= ll_round(getRect().getWidth() / 2 + mCurPan.mV[VX]);
- y -= ll_round(getRect().getHeight() / 2 + mCurPan.mV[VY]);
-
- LLVector3 pos_local( (F32)x, (F32)y, 0 );
-
- F32 radians = - atan2( LLViewerCamera::getInstance()->getAtAxis().mV[VX], LLViewerCamera::getInstance()->getAtAxis().mV[VY] );
-
- static LLUICachedControl<bool> rotate_map("MiniMapRotate", true);
- if( rotate_map )
- {
- LLQuaternion rot(radians, LLVector3(0.f, 0.f, 1.f));
- pos_local.rotVec( rot );
- }
-
- pos_local *= ( LLWorld::getInstance()->getRegionWidthInMeters() / mScale );
-
- LLVector3d pos_global;
- pos_global.setVec( pos_local );
- pos_global += gAgentCamera.getCameraPositionGlobal();
-
- return pos_global;
-}
-
-bool LLNetMap::handleScrollWheel(S32 x, S32 y, S32 clicks)
-{
- // note that clicks are reversed from what you'd think: i.e. > 0 means zoom out, < 0 means zoom in
- F32 new_scale = mScale * pow(MAP_SCALE_ZOOM_FACTOR, -clicks);
- F32 old_scale = mScale;
-
- setScale(new_scale);
-
- static LLUICachedControl<bool> auto_center("MiniMapAutoCenter", true);
- if (!auto_center)
- {
- // Adjust pan to center the zoom on the mouse pointer
- LLVector2 zoom_offset;
- zoom_offset.mV[VX] = x - getRect().getWidth() / 2;
- zoom_offset.mV[VY] = y - getRect().getHeight() / 2;
- mCurPan -= zoom_offset * mScale / old_scale - zoom_offset;
- }
-
- return true;
-}
-
-bool LLNetMap::handleToolTip(S32 x, S32 y, MASK mask)
-{
- if (gDisconnected)
- {
- return false;
- }
-
- // If the cursor is near an avatar on the minimap, a mini-inspector will be
- // shown for the avatar, instead of the normal map tooltip.
- if (handleToolTipAgent(mClosestAgentToCursor))
- {
- return true;
- }
-
- // The popup menu uses the hover parcel when it is open and the mouse is on
- // top of it, with some additional tolerance. Returning early here prevents
- // fighting over that hover parcel when getting tooltip info in the
- // tolerance region.
- if (isMouseOnPopupMenu())
- {
- return false;
- }
-
- LLRect sticky_rect;
- S32 SLOP = 4;
- localPointToScreen(x - SLOP, y - SLOP, &(sticky_rect.mLeft), &(sticky_rect.mBottom));
- sticky_rect.mRight = sticky_rect.mLeft + 2 * SLOP;
- sticky_rect.mTop = sticky_rect.mBottom + 2 * SLOP;
-
- std::string parcel_name_msg;
- std::string parcel_sale_price_msg;
- std::string parcel_sale_area_msg;
- std::string parcel_owner_msg;
- std::string region_name_msg;
-
- LLVector3d posGlobal = viewPosToGlobal(x, y);
- LLViewerRegion *region = LLWorld::getInstance()->getRegionFromPosGlobal(posGlobal);
- if (region)
- {
- std::string region_name = region->getName();
- if (!region_name.empty())
- {
- region_name_msg = mRegionNameMsg;
- LLStringUtil::format(region_name_msg, {{"[REGION_NAME]", region_name}});
- }
-
- // Only show parcel information in the tooltip if property lines are visible. Otherwise, the parcel the tooltip is referring to is
- // ambiguous.
- if (gSavedSettings.getBOOL("MiniMapShowPropertyLines"))
- {
- LLViewerParcelMgr::getInstance()->setHoverParcel(posGlobal);
- LLParcel *hover_parcel = LLViewerParcelMgr::getInstance()->getHoverParcel();
- if (hover_parcel)
- {
- std::string parcel_name = hover_parcel->getName();
- if (!parcel_name.empty())
- {
- parcel_name_msg = mParcelNameMsg;
- LLStringUtil::format(parcel_name_msg, {{"[PARCEL_NAME]", parcel_name}});
- }
-
- const LLUUID parcel_owner = hover_parcel->getOwnerID();
- std::string parcel_owner_name_url = LLSLURL("agent", parcel_owner, "inspect").getSLURLString();
- static LLUrlMatch parcel_owner_name_url_match;
- LLUrlRegistry::getInstance()->findUrl(parcel_owner_name_url, parcel_owner_name_url_match);
- if (!parcel_owner_name_url_match.empty())
- {
- parcel_owner_msg = mParcelOwnerMsg;
- std::string parcel_owner_name = parcel_owner_name_url_match.getLabel();
- LLStringUtil::format(parcel_owner_msg, {{"[PARCEL_OWNER]", parcel_owner_name}});
- }
-
- if (hover_parcel->getForSale())
- {
- const LLUUID auth_buyer_id = hover_parcel->getAuthorizedBuyerID();
- const LLUUID agent_id = gAgent.getID();
- bool show_for_sale = auth_buyer_id.isNull() || auth_buyer_id == agent_id || parcel_owner == agent_id;
- if (show_for_sale)
- {
- S32 price = hover_parcel->getSalePrice();
- S32 area = hover_parcel->getArea();
- F32 cost_per_sqm = 0.0f;
- if (area > 0)
- {
- cost_per_sqm = F32(price) / area;
- }
- std::string formatted_price = LLResMgr::getInstance()->getMonetaryString(price);
- std::string formatted_cost_per_meter = llformat("%.1f", cost_per_sqm);
- parcel_sale_price_msg = mParcelSalePriceMsg;
- LLStringUtil::format(parcel_sale_price_msg,
- {{"[PRICE]", formatted_price}, {"[PRICE_PER_SQM]", formatted_cost_per_meter}});
- std::string formatted_area = llformat("%d", area);
- parcel_sale_area_msg = mParcelSaleAreaMsg;
- LLStringUtil::format(parcel_sale_area_msg, {{"[AREA]", formatted_area}});
- }
- }
- }
- }
- }
-
- std::string tool_tip_hint_msg;
- if (gSavedSettings.getBOOL("DoubleClickTeleport"))
- {
- tool_tip_hint_msg = mAltToolTipHintMsg;
- }
- else if (gSavedSettings.getBOOL("DoubleClickShowWorldMap"))
- {
- tool_tip_hint_msg = mToolTipHintMsg;
- }
-
- LLStringUtil::format_map_t args;
- args["[PARCEL_NAME_MSG]"] = parcel_name_msg.empty() ? "" : parcel_name_msg + '\n';
- args["[PARCEL_SALE_PRICE_MSG]"] = parcel_sale_price_msg.empty() ? "" : parcel_sale_price_msg + '\n';
- args["[PARCEL_SALE_AREA_MSG]"] = parcel_sale_area_msg.empty() ? "" : parcel_sale_area_msg + '\n';
- args["[PARCEL_OWNER_MSG]"] = parcel_owner_msg.empty() ? "" : parcel_owner_msg + '\n';
- args["[REGION_NAME_MSG]"] = region_name_msg.empty() ? "" : region_name_msg + '\n';
- args["[TOOL_TIP_HINT_MSG]"] = tool_tip_hint_msg.empty() ? "" : tool_tip_hint_msg + '\n';
-
- std::string msg = mToolTipMsg;
- LLStringUtil::format(msg, args);
- if (msg.back() == '\n')
- {
- msg.resize(msg.size() - 1);
- }
- LLToolTipMgr::instance().show(LLToolTip::Params().message(msg).sticky_rect(sticky_rect));
-
- return true;
-}
-
-bool LLNetMap::handleToolTipAgent(const LLUUID& avatar_id)
-{
- LLAvatarName av_name;
- if (avatar_id.isNull() || !LLAvatarNameCache::get(avatar_id, &av_name))
- {
- return false;
- }
-
- // only show tooltip if same inspector not already open
- LLFloater* existing_inspector = LLFloaterReg::findInstance("inspect_avatar");
- if (!existing_inspector
- || !existing_inspector->getVisible()
- || existing_inspector->getKey()["avatar_id"].asUUID() != avatar_id)
- {
- LLInspector::Params p;
- p.fillFrom(LLUICtrlFactory::instance().getDefaultParams<LLInspector>());
- p.message(av_name.getCompleteName());
- p.image.name("Inspector_I");
- p.click_callback(boost::bind(showAvatarInspector, avatar_id));
- p.visible_time_near(6.f);
- p.visible_time_far(3.f);
- p.delay_time(0.35f);
- p.wrap(false);
-
- LLToolTipMgr::instance().show(p);
- }
- return true;
-}
-
-// static
-void LLNetMap::showAvatarInspector(const LLUUID& avatar_id)
-{
- LLSD params;
- params["avatar_id"] = avatar_id;
-
- if (LLToolTipMgr::instance().toolTipVisible())
- {
- LLRect rect = LLToolTipMgr::instance().getToolTipRect();
- params["pos"]["x"] = rect.mLeft;
- params["pos"]["y"] = rect.mTop;
- }
-
- LLFloaterReg::showInstance("inspect_avatar", params);
-}
-
-void LLNetMap::renderScaledPointGlobal( const LLVector3d& pos, const LLColor4U &color, F32 radius_meters )
-{
- LLVector3 local_pos;
- local_pos.setVec( pos - mObjectImageCenterGlobal );
-
- S32 diameter_pixels = ll_round(2 * radius_meters * mObjectMapTPM);
- renderPoint( local_pos, color, diameter_pixels );
-}
-
-
-void LLNetMap::renderPoint(const LLVector3 &pos_local, const LLColor4U &color,
- S32 diameter, S32 relative_height)
-{
- if (diameter <= 0)
- {
- return;
- }
-
- const S32 image_width = (S32)mObjectImagep->getWidth();
- const S32 image_height = (S32)mObjectImagep->getHeight();
-
- S32 x_offset = ll_round(pos_local.mV[VX] * mObjectMapTPM + image_width / 2);
- S32 y_offset = ll_round(pos_local.mV[VY] * mObjectMapTPM + image_height / 2);
-
- if ((x_offset < 0) || (x_offset >= image_width))
- {
- return;
- }
- if ((y_offset < 0) || (y_offset >= image_height))
- {
- return;
- }
-
- LLImageDataLock lock(mObjectRawImagep);
- U8 *datap = mObjectRawImagep->getData();
-
- S32 neg_radius = diameter / 2;
- S32 pos_radius = diameter - neg_radius;
- S32 x, y;
-
- if (relative_height > 0)
- {
- // ...point above agent
- S32 px, py;
-
- // vertical line
- px = x_offset;
- for (y = -neg_radius; y < pos_radius; y++)
- {
- py = y_offset + y;
- if ((py < 0) || (py >= image_height))
- {
- continue;
- }
- S32 offset = px + py * image_width;
- ((U32*)datap)[offset] = color.asRGBA();
- }
-
- // top line
- py = y_offset + pos_radius - 1;
- for (x = -neg_radius; x < pos_radius; x++)
- {
- px = x_offset + x;
- if ((px < 0) || (px >= image_width))
- {
- continue;
- }
- S32 offset = px + py * image_width;
- ((U32*)datap)[offset] = color.asRGBA();
- }
- }
- else
- {
- // ...point level with agent
- for (x = -neg_radius; x < pos_radius; x++)
- {
- S32 p_x = x_offset + x;
- if ((p_x < 0) || (p_x >= image_width))
- {
- continue;
- }
-
- for (y = -neg_radius; y < pos_radius; y++)
- {
- S32 p_y = y_offset + y;
- if ((p_y < 0) || (p_y >= image_height))
- {
- continue;
- }
- S32 offset = p_x + p_y * image_width;
- ((U32*)datap)[offset] = color.asRGBA();
- }
- }
- }
-}
-
-void LLNetMap::createObjectImage()
-{
- // Find the size of the side of a square that surrounds the circle that surrounds getRect().
- // ... which is, the diagonal of the rect.
- F32 width = (F32)getRect().getWidth();
- F32 height = (F32)getRect().getHeight();
- S32 square_size = ll_round( sqrt(width*width + height*height) );
-
- // Find the least power of two >= the minimum size.
- const S32 MIN_SIZE = 64;
- const S32 MAX_SIZE = 256;
- S32 img_size = MIN_SIZE;
- while( (img_size*2 < square_size ) && (img_size < MAX_SIZE) )
- {
- img_size <<= 1;
- }
-
- if( mObjectImagep.isNull() ||
- (mObjectImagep->getWidth() != img_size) ||
- (mObjectImagep->getHeight() != img_size) )
- {
- mObjectRawImagep = new LLImageRaw(img_size, img_size, 4);
- U8* data = mObjectRawImagep->getData();
- memset( data, 0, img_size * img_size * 4 );
- mObjectImagep = LLViewerTextureManager::getLocalTexture( mObjectRawImagep.get(), false);
- }
- setScale(mScale);
- mUpdateNow = true;
-}
-
-bool LLNetMap::handleMouseDown(S32 x, S32 y, MASK mask)
-{
- // Start panning
- gFocusMgr.setMouseCapture(this);
-
- mStartPan = mCurPan;
- mMouseDown.mX = x;
- mMouseDown.mY = y;
- return true;
-}
-
-bool LLNetMap::handleMouseUp(S32 x, S32 y, MASK mask)
-{
- if (abs(mMouseDown.mX - x) < 3 && abs(mMouseDown.mY - y) < 3)
- {
- handleClick(x, y, mask);
- }
-
- if (hasMouseCapture())
- {
- if (mPanning)
- {
- // restore mouse cursor
- S32 local_x, local_y;
- local_x = mMouseDown.mX + llfloor(mCurPan.mV[VX] - mStartPan.mV[VX]);
- local_y = mMouseDown.mY + llfloor(mCurPan.mV[VY] - mStartPan.mV[VY]);
- LLRect clip_rect = getRect();
- clip_rect.stretch(-8);
- clip_rect.clipPointToRect(mMouseDown.mX, mMouseDown.mY, local_x, local_y);
- LLUI::getInstance()->setMousePositionLocal(this, local_x, local_y);
-
- // finish the pan
- mPanning = false;
-
- mMouseDown.set(0, 0);
- }
- gViewerWindow->showCursor();
- gFocusMgr.setMouseCapture(NULL);
- return true;
- }
-
- return false;
-}
-
-bool LLNetMap::handleRightMouseDown(S32 x, S32 y, MASK mask)
-{
- auto menu = static_cast<LLMenuGL*>(mPopupMenuHandle.get());
- if (menu)
- {
- mPopupWorldPos = viewPosToGlobal(x, y);
- menu->buildDrawLabels();
- menu->updateParent(LLMenuGL::sMenuContainer);
- menu->setItemEnabled("Stop Tracking", LLTracker::isTracking(0));
- LLMenuGL::showPopup(this, menu, x, y);
- }
- return true;
-}
-
-bool LLNetMap::handleClick(S32 x, S32 y, MASK mask)
-{
- // TODO: allow clicking an avatar on minimap to select avatar in the nearby avatar list
- // if(mClosestAgentToCursor.notNull())
- // mNearbyList->selectUser(mClosestAgentToCursor);
- // Needs a registered observer i guess to accomplish this without using
- // globals to tell the mNearbyList in llpeoplepanel to select the user
- return true;
-}
-
-bool LLNetMap::handleDoubleClick(S32 x, S32 y, MASK mask)
-{
- LLVector3d pos_global = viewPosToGlobal(x, y);
-
- bool double_click_teleport = gSavedSettings.getBOOL("DoubleClickTeleport");
- bool double_click_show_world_map = gSavedSettings.getBOOL("DoubleClickShowWorldMap");
-
- if (double_click_teleport || double_click_show_world_map)
- {
- // If we're not tracking a beacon already, double-click will set one
- if (!LLTracker::isTracking(NULL))
- {
- LLFloaterWorldMap* world_map = LLFloaterWorldMap::getInstance();
- if (world_map)
- {
- world_map->trackLocation(pos_global);
- }
- }
- }
-
- if (double_click_teleport)
- {
- // If DoubleClickTeleport is on, double clicking the minimap will teleport there
- gAgent.teleportViaLocationLookAt(pos_global);
- }
- else if (double_click_show_world_map)
- {
- LLFloaterReg::showInstance("world_map");
- }
- return true;
-}
-
-F32 LLNetMap::getScaleForName(std::string scale_name)
-{
- if (scale_name == "very close")
- {
- return LLNetMap::MAP_SCALE_VERY_CLOSE;
- }
- else if (scale_name == "close")
- {
- return LLNetMap::MAP_SCALE_CLOSE;
- }
- else if (scale_name == "medium")
- {
- return LLNetMap::MAP_SCALE_MEDIUM;
- }
- else if (scale_name == "far")
- {
- return LLNetMap::MAP_SCALE_FAR;
- }
- return 0.0f;
-}
-
-// static
-bool LLNetMap::outsideSlop( S32 x, S32 y, S32 start_x, S32 start_y, S32 slop )
-{
- S32 dx = x - start_x;
- S32 dy = y - start_y;
-
- return (dx <= -slop || slop <= dx || dy <= -slop || slop <= dy);
-}
-
-bool LLNetMap::handleHover( S32 x, S32 y, MASK mask )
-{
- if (hasMouseCapture())
- {
- if (mPanning || outsideSlop(x, y, mMouseDown.mX, mMouseDown.mY, MOUSE_DRAG_SLOP))
- {
- if (!mPanning)
- {
- // Just started panning. Hide cursor.
- mPanning = true;
- gViewerWindow->hideCursor();
- }
-
- LLVector2 delta(static_cast<F32>(gViewerWindow->getCurrentMouseDX()),
- static_cast<F32>(gViewerWindow->getCurrentMouseDY()));
-
- // Set pan to value at start of drag + offset
- mCurPan += delta;
-
- gViewerWindow->moveCursorToCenter();
- }
- }
-
- if (mask & MASK_SHIFT)
- {
- // If shift is held, change the cursor to hint that the map can be
- // dragged. However, holding shift is not required to drag the map.
- gViewerWindow->setCursor( UI_CURSOR_TOOLPAN );
- }
- else
- {
- gViewerWindow->setCursor( UI_CURSOR_CROSS );
- }
-
- return true;
-}
-
-bool LLNetMap::isZoomChecked(const LLSD &userdata)
-{
- std::string level = userdata.asString();
- F32 scale = getScaleForName(level);
- return scale == mScale;
-}
-
-void LLNetMap::setZoom(const LLSD &userdata)
-{
- std::string level = userdata.asString();
- F32 scale = getScaleForName(level);
- if (scale != 0.0f)
- {
- setScale(scale);
- }
-}
-
-void LLNetMap::handleStopTracking (const LLSD& userdata)
-{
- auto menu = static_cast<LLMenuGL*>(mPopupMenuHandle.get());
- if (menu)
- {
- menu->setItemEnabled ("Stop Tracking", false);
- LLTracker::stopTracking (LLTracker::isTracking(NULL));
- }
-}
-
-void LLNetMap::activateCenterMap(const LLSD &userdata) { mCentering = true; }
-
-bool LLNetMap::isMapOrientationChecked(const LLSD &userdata)
-{
- const std::string command_name = userdata.asString();
- const bool rotate_map = gSavedSettings.getBOOL("MiniMapRotate");
- if (command_name == "north_at_top")
- {
- return !rotate_map;
- }
-
- if (command_name == "camera_at_top")
- {
- return rotate_map;
- }
-
- return false;
-}
-
-void LLNetMap::setMapOrientation(const LLSD &userdata)
-{
- const std::string command_name = userdata.asString();
- if (command_name == "north_at_top")
- {
- gSavedSettings.setBOOL("MiniMapRotate", false);
- }
- else if (command_name == "camera_at_top")
- {
- gSavedSettings.setBOOL("MiniMapRotate", true);
- }
-}
-
-void LLNetMap::popupShowAboutLand(const LLSD &userdata)
-{
- // Update parcel selection. It's important to deselect land first so the "About Land" floater doesn't refresh with the old selection.
- LLViewerParcelMgr::getInstance()->deselectLand();
- LLParcelSelectionHandle selection = LLViewerParcelMgr::getInstance()->selectParcelAt(mPopupWorldPos);
- gMenuHolder->setParcelSelection(selection);
-
- LLFloaterReg::showInstance("about_land", LLSD(), false);
-}
+/** + * @file llnetmap.cpp + * @author James Cook + * @brief Display of surrounding regions, objects, and agents. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2001-2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llnetmap.h" + +// Library includes (should move below) +#include "indra_constants.h" +#include "llavatarnamecache.h" +#include "llmath.h" +#include "llfloaterreg.h" +#include "llfocusmgr.h" +#include "lllocalcliprect.h" +#include "llrender.h" +#include "llresmgr.h" +#include "llui.h" +#include "lltooltip.h" + +#include "llglheaders.h" + +// Viewer includes +#include "llagent.h" +#include "llagentcamera.h" +#include "llappviewer.h" // for gDisconnected +#include "llcallingcard.h" // LLAvatarTracker +#include "llfloaterland.h" +#include "llfloaterworldmap.h" +#include "llparcel.h" +#include "lltracker.h" +#include "llsurface.h" +#include "llurlmatch.h" +#include "llurlregistry.h" +#include "llviewercamera.h" +#include "llviewercontrol.h" +#include "llviewerparcelmgr.h" +#include "llviewertexture.h" +#include "llviewertexturelist.h" +#include "llviewermenu.h" +#include "llviewerobjectlist.h" +#include "llviewerregion.h" +#include "llviewerwindow.h" +#include "llworld.h" +#include "llworldmapview.h" // shared draw code + +static LLDefaultChildRegistry::Register<LLNetMap> r1("net_map"); + +constexpr F32 LLNetMap::MAP_SCALE_MIN = 32; +constexpr F32 LLNetMap::MAP_SCALE_FAR = 32; +constexpr F32 LLNetMap::MAP_SCALE_MEDIUM = 128; +constexpr F32 LLNetMap::MAP_SCALE_CLOSE = 256; +constexpr F32 LLNetMap::MAP_SCALE_VERY_CLOSE = 1024; +constexpr F32 LLNetMap::MAP_SCALE_MAX = 4096; + +constexpr F32 MAP_SCALE_ZOOM_FACTOR = 1.04f; // Zoom in factor per click of scroll wheel (4%) +constexpr F32 MIN_DOT_RADIUS = 3.5f; +constexpr F32 DOT_SCALE = 0.75f; +constexpr F32 MIN_PICK_SCALE = 2.f; +constexpr S32 MOUSE_DRAG_SLOP = 2; // How far the mouse needs to move before we think it's a drag + +constexpr F64 COARSEUPDATE_MAX_Z = 1020.0f; + +LLNetMap::LLNetMap (const Params & p) +: LLUICtrl (p), + mBackgroundColor (p.bg_color()), + mScale( MAP_SCALE_MEDIUM ), + mPixelsPerMeter( MAP_SCALE_MEDIUM / REGION_WIDTH_METERS ), + mObjectMapTPM(0.f), + mObjectMapPixels(0.f), + mCurPan(0.f, 0.f), + mStartPan(0.f, 0.f), + mPopupWorldPos(0.f, 0.f, 0.f), + mMouseDown(0, 0), + mPanning(false), + mUpdateNow(false), + mObjectImageCenterGlobal( gAgentCamera.getCameraPositionGlobal() ), + mObjectRawImagep(), + mObjectImagep(), + mClosestAgentToCursor(), + mClosestAgentAtLastRightClick(), + mToolTipMsg() +{ + mScale = gSavedSettings.getF32("MiniMapScale"); + if (gAgent.isFirstLogin()) + { + // *HACK: On first run, set this to false for new users, otherwise the + // default is true to maintain consistent experience for existing + // users. + gSavedSettings.setBOOL("MiniMapRotate", false); + } + mPixelsPerMeter = mScale / REGION_WIDTH_METERS; + mDotRadius = llmax(DOT_SCALE * mPixelsPerMeter, MIN_DOT_RADIUS); +} + +LLNetMap::~LLNetMap() +{ + auto menu = static_cast<LLMenuGL*>(mPopupMenuHandle.get()); + if (menu) + { + menu->die(); + mPopupMenuHandle.markDead(); + } +} + +bool LLNetMap::postBuild() +{ + LLUICtrl::CommitCallbackRegistry::ScopedRegistrar commitRegistrar; + LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enableRegistrar; + + enableRegistrar.add("Minimap.Zoom.Check", boost::bind(&LLNetMap::isZoomChecked, this, _2)); + commitRegistrar.add("Minimap.Zoom.Set", boost::bind(&LLNetMap::setZoom, this, _2)); + commitRegistrar.add("Minimap.Tracker", boost::bind(&LLNetMap::handleStopTracking, this, _2)); + commitRegistrar.add("Minimap.Center.Activate", boost::bind(&LLNetMap::activateCenterMap, this, _2)); + enableRegistrar.add("Minimap.MapOrientation.Check", boost::bind(&LLNetMap::isMapOrientationChecked, this, _2)); + commitRegistrar.add("Minimap.MapOrientation.Set", boost::bind(&LLNetMap::setMapOrientation, this, _2)); + commitRegistrar.add("Minimap.AboutLand", boost::bind(&LLNetMap::popupShowAboutLand, this, _2)); + + LLMenuGL* menu = LLUICtrlFactory::getInstance()->createFromFile<LLMenuGL>("menu_mini_map.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); + mPopupMenuHandle = menu->getHandle(); + menu->setItemEnabled("Re-center map", false); + return true; +} + +void LLNetMap::setScale( F32 scale ) +{ + scale = llclamp(scale, MAP_SCALE_MIN, MAP_SCALE_MAX); + mCurPan *= scale / mScale; + mScale = scale; + + if (mObjectImagep.notNull()) + { + F32 width = (F32)(getRect().getWidth()); + F32 height = (F32)(getRect().getHeight()); + F32 diameter = sqrt(width * width + height * height); + F32 region_widths = diameter / mScale; + F32 meters = region_widths * LLWorld::getInstance()->getRegionWidthInMeters(); + F32 num_pixels = (F32)mObjectImagep->getWidth(); + mObjectMapTPM = num_pixels / meters; + mObjectMapPixels = diameter; + } + + mPixelsPerMeter = mScale / REGION_WIDTH_METERS; + mDotRadius = llmax(DOT_SCALE * mPixelsPerMeter, MIN_DOT_RADIUS); + + gSavedSettings.setF32("MiniMapScale", mScale); + + mUpdateNow = true; +} + + +/////////////////////////////////////////////////////////////////////////////////// + +void LLNetMap::draw() +{ + if (!LLWorld::instanceExists()) + { + return; + } + LL_PROFILE_ZONE_SCOPED; + static LLFrameTimer map_timer; + static LLUIColor map_avatar_color = LLUIColorTable::instance().getColor("MapAvatarColor", LLColor4::white); + static LLUIColor map_avatar_friend_color = LLUIColorTable::instance().getColor("MapAvatarFriendColor", LLColor4::white); + static LLUIColor map_track_color = LLUIColorTable::instance().getColor("MapTrackColor", LLColor4::white); + //static LLUIColor map_track_disabled_color = LLUIColorTable::instance().getColor("MapTrackDisabledColor", LLColor4::white); + static LLUIColor map_frustum_color = LLUIColorTable::instance().getColor("MapFrustumColor", LLColor4::white); + static LLUIColor map_parcel_outline_color = LLUIColorTable::instance().getColor("MapParcelOutlineColor", LLColor4(LLColor3(LLColor4::yellow), 0.5f)); + + if (mObjectImagep.isNull()) + { + createObjectImage(); + } + + static LLUICachedControl<bool> auto_center("MiniMapAutoCenter", true); + bool auto_centering = auto_center && !mPanning; + mCentering = mCentering && !mPanning; + + if (auto_centering || mCentering) + { + mCurPan = lerp(mCurPan, LLVector2(0.0f, 0.0f) , LLSmoothInterpolation::getInterpolant(0.1f)); + } + bool centered = abs(mCurPan.mV[VX]) < 0.5f && abs(mCurPan.mV[VY]) < 0.5f; + if (centered) + { + mCurPan.mV[0] = 0.0f; + mCurPan.mV[1] = 0.0f; + mCentering = false; + } + + auto menu = static_cast<LLMenuGL*>(mPopupMenuHandle.get()); + if (menu) + { + bool can_recenter_map = !(centered || mCentering || auto_centering); + menu->setItemEnabled("Re-center map", can_recenter_map); + } + updateAboutLandPopupButton(); + + // Prepare a scissor region + F32 rotation = 0; + + gGL.pushMatrix(); + gGL.pushUIMatrix(); + + LLVector3 offset = gGL.getUITranslation(); + LLVector3 scale = gGL.getUIScale(); + + gGL.loadIdentity(); + gGL.loadUIIdentity(); + + gGL.scalef(scale.mV[0], scale.mV[1], scale.mV[2]); + gGL.translatef(offset.mV[0], offset.mV[1], offset.mV[2]); + + { + LLLocalClipRect clip(getLocalRect()); + { + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + + gGL.matrixMode(LLRender::MM_MODELVIEW); + + // Draw background rectangle + LLColor4 background_color = mBackgroundColor.get(); + gGL.color4fv( background_color.mV ); + gl_rect_2d(0, getRect().getHeight(), getRect().getWidth(), 0); + } + + // region 0,0 is in the middle + S32 center_sw_left = getRect().getWidth() / 2 + llfloor(mCurPan.mV[VX]); + S32 center_sw_bottom = getRect().getHeight() / 2 + llfloor(mCurPan.mV[VY]); + + gGL.pushMatrix(); + + gGL.translatef( (F32) center_sw_left, (F32) center_sw_bottom, 0.f); + + static LLUICachedControl<bool> rotate_map("MiniMapRotate", true); + if( rotate_map ) + { + // rotate subsequent draws to agent rotation + rotation = atan2( LLViewerCamera::getInstance()->getAtAxis().mV[VX], LLViewerCamera::getInstance()->getAtAxis().mV[VY] ); + gGL.rotatef( rotation * RAD_TO_DEG, 0.f, 0.f, 1.f); + } + + // figure out where agent is + const S32 region_width = ll_round(LLWorld::getInstance()->getRegionWidthInMeters()); + const F32 scale_pixels_per_meter = mScale / region_width; + + for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin(); + iter != LLWorld::getInstance()->getRegionList().end(); ++iter) + { + LLViewerRegion* regionp = *iter; + // Find x and y position relative to camera's center. + LLVector3 origin_agent = regionp->getOriginAgent(); + LLVector3 rel_region_pos = origin_agent - gAgentCamera.getCameraPositionAgent(); + F32 relative_x = rel_region_pos.mV[0] * scale_pixels_per_meter; + F32 relative_y = rel_region_pos.mV[1] * scale_pixels_per_meter; + + // background region rectangle + F32 bottom = relative_y; + F32 left = relative_x; + F32 top = bottom + mScale ; + F32 right = left + mScale ; + + if (regionp == gAgent.getRegion()) + { + gGL.color4f(1.f, 1.f, 1.f, 1.f); + } + else + { + gGL.color4f(0.8f, 0.8f, 0.8f, 1.f); + } + + if (!regionp->isAlive()) + { + gGL.color4f(1.f, 0.5f, 0.5f, 1.f); + } + + + + // Draw using texture. + gGL.getTexUnit(0)->bind(regionp->getLand().getSTexture()); + gGL.begin(LLRender::QUADS); + gGL.texCoord2f(0.f, 1.f); + gGL.vertex2f(left, top); + gGL.texCoord2f(0.f, 0.f); + gGL.vertex2f(left, bottom); + gGL.texCoord2f(1.f, 0.f); + gGL.vertex2f(right, bottom); + gGL.texCoord2f(1.f, 1.f); + gGL.vertex2f(right, top); + gGL.end(); + + // Draw water + gGL.flush(); + { + if (regionp->getLand().getWaterTexture()) + { + gGL.getTexUnit(0)->bind(regionp->getLand().getWaterTexture()); + gGL.begin(LLRender::QUADS); + gGL.texCoord2f(0.f, 1.f); + gGL.vertex2f(left, top); + gGL.texCoord2f(0.f, 0.f); + gGL.vertex2f(left, bottom); + gGL.texCoord2f(1.f, 0.f); + gGL.vertex2f(right, bottom); + gGL.texCoord2f(1.f, 1.f); + gGL.vertex2f(right, top); + gGL.end(); + } + } + gGL.flush(); + } + + // Redraw object layer periodically + if (mUpdateNow || (map_timer.getElapsedTimeF32() > 0.5f)) + { + mUpdateNow = false; + + // Locate the centre of the object layer, accounting for panning + LLVector3 new_center = globalPosToView(gAgentCamera.getCameraPositionGlobal()); + new_center.mV[VX] -= mCurPan.mV[VX]; + new_center.mV[VY] -= mCurPan.mV[VY]; + new_center.mV[VZ] = 0.f; + mObjectImageCenterGlobal = viewPosToGlobal(llfloor(new_center.mV[VX]), llfloor(new_center.mV[VY])); + + // Create the base texture. + LLImageDataLock lock(mObjectRawImagep); + U8 *default_texture = mObjectRawImagep->getData(); + memset( default_texture, 0, mObjectImagep->getWidth() * mObjectImagep->getHeight() * mObjectImagep->getComponents() ); + + // Draw objects + gObjectList.renderObjectsForMap(*this); + + mObjectImagep->setSubImage(mObjectRawImagep, 0, 0, mObjectImagep->getWidth(), mObjectImagep->getHeight()); + + map_timer.reset(); + } + + LLVector3 map_center_agent = gAgent.getPosAgentFromGlobal(mObjectImageCenterGlobal); + LLVector3 camera_position = gAgentCamera.getCameraPositionAgent(); + map_center_agent -= camera_position; + map_center_agent.mV[VX] *= scale_pixels_per_meter; + map_center_agent.mV[VY] *= scale_pixels_per_meter; + + gGL.getTexUnit(0)->bind(mObjectImagep); + F32 image_half_width = 0.5f*mObjectMapPixels; + F32 image_half_height = 0.5f*mObjectMapPixels; + + gGL.begin(LLRender::QUADS); + gGL.texCoord2f(0.f, 1.f); + gGL.vertex2f(map_center_agent.mV[VX] - image_half_width, image_half_height + map_center_agent.mV[VY]); + gGL.texCoord2f(0.f, 0.f); + gGL.vertex2f(map_center_agent.mV[VX] - image_half_width, map_center_agent.mV[VY] - image_half_height); + gGL.texCoord2f(1.f, 0.f); + gGL.vertex2f(image_half_width + map_center_agent.mV[VX], map_center_agent.mV[VY] - image_half_height); + gGL.texCoord2f(1.f, 1.f); + gGL.vertex2f(image_half_width + map_center_agent.mV[VX], image_half_height + map_center_agent.mV[VY]); + gGL.end(); + + for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin(); + iter != LLWorld::getInstance()->getRegionList().end(); ++iter) + { + LLViewerRegion* regionp = *iter; + regionp->renderPropertyLinesOnMinimap(scale_pixels_per_meter, map_parcel_outline_color.get().mV); + } + + gGL.popMatrix(); + + // Mouse pointer in local coordinates + S32 local_mouse_x; + S32 local_mouse_y; + //localMouse(&local_mouse_x, &local_mouse_y); + LLUI::getInstance()->getMousePositionLocal(this, &local_mouse_x, &local_mouse_y); + mClosestAgentToCursor.setNull(); + F32 closest_dist_squared = F32_MAX; // value will be overridden in the loop + F32 min_pick_dist_squared = (mDotRadius * MIN_PICK_SCALE) * (mDotRadius * MIN_PICK_SCALE); + + LLVector3 pos_map; + uuid_vec_t avatar_ids; + std::vector<LLVector3d> positions; + bool unknown_relative_z; + + LLWorld::getInstance()->getAvatars(&avatar_ids, &positions, gAgentCamera.getCameraPositionGlobal()); + + // Draw avatars + for (U32 i = 0; i < avatar_ids.size(); i++) + { + LLUUID uuid = avatar_ids[i]; + // Skip self, we'll draw it later + if (uuid == gAgent.getID()) continue; + + pos_map = globalPosToView(positions[i]); + + bool show_as_friend = (LLAvatarTracker::instance().getBuddyInfo(uuid) != NULL); + + LLColor4 color = show_as_friend ? map_avatar_friend_color : map_avatar_color; + + unknown_relative_z = positions[i].mdV[VZ] >= COARSEUPDATE_MAX_Z && + camera_position.mV[VZ] >= COARSEUPDATE_MAX_Z; + + LLWorldMapView::drawAvatar( + pos_map.mV[VX], pos_map.mV[VY], + color, + pos_map.mV[VZ], mDotRadius, + unknown_relative_z); + + if(uuid.notNull()) + { + bool selected = false; + uuid_vec_t::iterator sel_iter = gmSelected.begin(); + for (; sel_iter != gmSelected.end(); sel_iter++) + { + if(*sel_iter == uuid) + { + selected = true; + break; + } + } + if(selected) + { + if( (pos_map.mV[VX] < 0) || + (pos_map.mV[VY] < 0) || + (pos_map.mV[VX] >= getRect().getWidth()) || + (pos_map.mV[VY] >= getRect().getHeight()) ) + { + S32 x = ll_round( pos_map.mV[VX] ); + S32 y = ll_round( pos_map.mV[VY] ); + LLWorldMapView::drawTrackingCircle( getRect(), x, y, color, 1, 10); + } else + { + LLWorldMapView::drawTrackingDot(pos_map.mV[VX],pos_map.mV[VY],color,0.f); + } + } + } + + F32 dist_to_cursor_squared = dist_vec_squared(LLVector2(pos_map.mV[VX], pos_map.mV[VY]), + LLVector2(local_mouse_x,local_mouse_y)); + if(dist_to_cursor_squared < min_pick_dist_squared && dist_to_cursor_squared < closest_dist_squared) + { + closest_dist_squared = dist_to_cursor_squared; + mClosestAgentToCursor = uuid; + } + } + + // Draw dot for autopilot target + if (gAgent.getAutoPilot()) + { + drawTracking( gAgent.getAutoPilotTargetGlobal(), map_track_color ); + } + else + { + LLTracker::ETrackingStatus tracking_status = LLTracker::getTrackingStatus(); + if ( LLTracker::TRACKING_AVATAR == tracking_status ) + { + drawTracking( LLAvatarTracker::instance().getGlobalPos(), map_track_color ); + } + else if ( LLTracker::TRACKING_LANDMARK == tracking_status + || LLTracker::TRACKING_LOCATION == tracking_status ) + { + drawTracking( LLTracker::getTrackedPositionGlobal(), map_track_color ); + } + } + + // Draw dot for self avatar position + LLVector3d pos_global = gAgent.getPositionGlobal(); + pos_map = globalPosToView(pos_global); + S32 dot_width = ll_round(mDotRadius * 2.f); + LLUIImagePtr you = LLWorldMapView::sAvatarYouLargeImage; + if (you) + { + you->draw(ll_round(pos_map.mV[VX] - mDotRadius), + ll_round(pos_map.mV[VY] - mDotRadius), + dot_width, + dot_width); + + F32 dist_to_cursor_squared = dist_vec_squared(LLVector2(pos_map.mV[VX], pos_map.mV[VY]), + LLVector2(local_mouse_x,local_mouse_y)); + if(dist_to_cursor_squared < min_pick_dist_squared && dist_to_cursor_squared < closest_dist_squared) + { + mClosestAgentToCursor = gAgent.getID(); + } + } + + // Draw frustum + F32 meters_to_pixels = mScale/ LLWorld::getInstance()->getRegionWidthInMeters(); + + F32 horiz_fov = LLViewerCamera::getInstance()->getView() * LLViewerCamera::getInstance()->getAspect(); + F32 far_clip_meters = LLViewerCamera::getInstance()->getFar(); + F32 far_clip_pixels = far_clip_meters * meters_to_pixels; + + F32 ctr_x = (F32)center_sw_left; + F32 ctr_y = (F32)center_sw_bottom; + + const F32 steps_per_circle = 40.0f; + const F32 steps_per_radian = steps_per_circle / F_TWO_PI; + const F32 arc_start = -(horiz_fov / 2.0f) + F_PI_BY_TWO; + const F32 arc_end = (horiz_fov / 2.0f) + F_PI_BY_TWO; + const S32 steps = llmax(1, (S32)((horiz_fov * steps_per_radian) + 0.5f)); + + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + + if( rotate_map ) + { + gGL.pushMatrix(); + gGL.translatef( ctr_x, ctr_y, 0 ); + gl_washer_segment_2d(far_clip_pixels, 0, arc_start, arc_end, steps, map_frustum_color(), map_frustum_color()); + gGL.popMatrix(); + } + else + { + gGL.pushMatrix(); + gGL.translatef( ctr_x, ctr_y, 0 ); + // If we don't rotate the map, we have to rotate the frustum. + gGL.rotatef( atan2( LLViewerCamera::getInstance()->getAtAxis().mV[VX], LLViewerCamera::getInstance()->getAtAxis().mV[VY] ) * RAD_TO_DEG, 0.f, 0.f, -1.f); + gl_washer_segment_2d(far_clip_pixels, 0, arc_start, arc_end, steps, map_frustum_color(), map_frustum_color()); + gGL.popMatrix(); + } + } + + gGL.popMatrix(); + gGL.popUIMatrix(); + + LLUICtrl::draw(); +} + +void LLNetMap::reshape(S32 width, S32 height, bool called_from_parent) +{ + LLUICtrl::reshape(width, height, called_from_parent); + createObjectImage(); +} + +LLVector3 LLNetMap::globalPosToView(const LLVector3d& global_pos) +{ + LLVector3d camera_position = gAgentCamera.getCameraPositionGlobal(); + + LLVector3d relative_pos_global = global_pos - camera_position; + LLVector3 pos_local; + pos_local.setVec(relative_pos_global); // convert to floats from doubles + + pos_local.mV[VX] *= mPixelsPerMeter; + pos_local.mV[VY] *= mPixelsPerMeter; + // leave Z component in meters + + static LLUICachedControl<bool> rotate_map("MiniMapRotate", true); + if( rotate_map ) + { + F32 radians = atan2( LLViewerCamera::getInstance()->getAtAxis().mV[VX], LLViewerCamera::getInstance()->getAtAxis().mV[VY] ); + LLQuaternion rot(radians, LLVector3(0.f, 0.f, 1.f)); + pos_local.rotVec( rot ); + } + + pos_local.mV[VX] += getRect().getWidth() / 2 + mCurPan.mV[VX]; + pos_local.mV[VY] += getRect().getHeight() / 2 + mCurPan.mV[VY]; + + return pos_local; +} + +void LLNetMap::drawTracking(const LLVector3d& pos_global, const LLColor4& color, + bool draw_arrow ) +{ + LLVector3 pos_local = globalPosToView(pos_global); + if( (pos_local.mV[VX] < 0) || + (pos_local.mV[VY] < 0) || + (pos_local.mV[VX] >= getRect().getWidth()) || + (pos_local.mV[VY] >= getRect().getHeight()) ) + { + if (draw_arrow) + { + S32 x = ll_round( pos_local.mV[VX] ); + S32 y = ll_round( pos_local.mV[VY] ); + LLWorldMapView::drawTrackingCircle( getRect(), x, y, color, 1, 10 ); + LLWorldMapView::drawTrackingArrow( getRect(), x, y, color ); + } + } + else + { + LLWorldMapView::drawTrackingDot(pos_local.mV[VX], + pos_local.mV[VY], + color, + pos_local.mV[VZ]); + } +} + +bool LLNetMap::isMouseOnPopupMenu() +{ + auto menu = static_cast<LLMenuGL*>(mPopupMenuHandle.get()); + if (!menu || !menu->isOpen()) + { + return false; + } + + S32 popup_x; + S32 popup_y; + LLUI::getInstance()->getMousePositionLocal(menu, &popup_x, &popup_y); + // *NOTE: Tolerance is larger than it needs to be because the context menu is offset from the mouse when the menu is opened from certain + // directions. This may be a quirk of LLMenuGL::showPopup. -Cosmic,2022-03-22 + constexpr S32 tolerance = 10; + // Test tolerance from all four corners, as the popup menu can appear from a different direction if there's not enough space. + // Assume the size of the popup menu is much larger than the provided tolerance. + // In practice, this is a [tolerance]px margin around the popup menu. + for (S32 sign_x = -1; sign_x <= 1; sign_x += 2) + { + for (S32 sign_y = -1; sign_y <= 1; sign_y += 2) + { + if (menu->pointInView(popup_x + (sign_x * tolerance), popup_y + (sign_y * tolerance))) + { + return true; + } + } + } + return false; +} + +void LLNetMap::updateAboutLandPopupButton() +{ + auto menu = static_cast<LLMenuGL*>(mPopupMenuHandle.get()); + if (!menu || !menu->isOpen()) + { + return; + } + + LLViewerRegion *region = LLWorld::getInstance()->getRegionFromPosGlobal(mPopupWorldPos); + if (!region) + { + menu->setItemEnabled("About Land", false); + } + else + { + // Check if the mouse is in the bounds of the popup. If so, it's safe to assume no other hover function will be called, so the hover + // parcel can be used to check if location-sensitive tooltip options are available. + if (isMouseOnPopupMenu()) + { + LLViewerParcelMgr::getInstance()->setHoverParcel(mPopupWorldPos); + LLParcel *hover_parcel = LLViewerParcelMgr::getInstance()->getHoverParcel(); + bool valid_parcel = false; + if (hover_parcel) + { + valid_parcel = hover_parcel->getOwnerID().notNull(); + } + menu->setItemEnabled("About Land", valid_parcel); + } + } +} + +LLVector3d LLNetMap::viewPosToGlobal( S32 x, S32 y ) +{ + x -= ll_round(getRect().getWidth() / 2 + mCurPan.mV[VX]); + y -= ll_round(getRect().getHeight() / 2 + mCurPan.mV[VY]); + + LLVector3 pos_local( (F32)x, (F32)y, 0 ); + + F32 radians = - atan2( LLViewerCamera::getInstance()->getAtAxis().mV[VX], LLViewerCamera::getInstance()->getAtAxis().mV[VY] ); + + static LLUICachedControl<bool> rotate_map("MiniMapRotate", true); + if( rotate_map ) + { + LLQuaternion rot(radians, LLVector3(0.f, 0.f, 1.f)); + pos_local.rotVec( rot ); + } + + pos_local *= ( LLWorld::getInstance()->getRegionWidthInMeters() / mScale ); + + LLVector3d pos_global; + pos_global.setVec( pos_local ); + pos_global += gAgentCamera.getCameraPositionGlobal(); + + return pos_global; +} + +bool LLNetMap::handleScrollWheel(S32 x, S32 y, S32 clicks) +{ + // note that clicks are reversed from what you'd think: i.e. > 0 means zoom out, < 0 means zoom in + F32 new_scale = mScale * pow(MAP_SCALE_ZOOM_FACTOR, -clicks); + F32 old_scale = mScale; + + setScale(new_scale); + + static LLUICachedControl<bool> auto_center("MiniMapAutoCenter", true); + if (!auto_center) + { + // Adjust pan to center the zoom on the mouse pointer + LLVector2 zoom_offset; + zoom_offset.mV[VX] = x - getRect().getWidth() / 2; + zoom_offset.mV[VY] = y - getRect().getHeight() / 2; + mCurPan -= zoom_offset * mScale / old_scale - zoom_offset; + } + + return true; +} + +bool LLNetMap::handleToolTip(S32 x, S32 y, MASK mask) +{ + if (gDisconnected) + { + return false; + } + + // If the cursor is near an avatar on the minimap, a mini-inspector will be + // shown for the avatar, instead of the normal map tooltip. + if (handleToolTipAgent(mClosestAgentToCursor)) + { + return true; + } + + // The popup menu uses the hover parcel when it is open and the mouse is on + // top of it, with some additional tolerance. Returning early here prevents + // fighting over that hover parcel when getting tooltip info in the + // tolerance region. + if (isMouseOnPopupMenu()) + { + return false; + } + + LLRect sticky_rect; + S32 SLOP = 4; + localPointToScreen(x - SLOP, y - SLOP, &(sticky_rect.mLeft), &(sticky_rect.mBottom)); + sticky_rect.mRight = sticky_rect.mLeft + 2 * SLOP; + sticky_rect.mTop = sticky_rect.mBottom + 2 * SLOP; + + std::string parcel_name_msg; + std::string parcel_sale_price_msg; + std::string parcel_sale_area_msg; + std::string parcel_owner_msg; + std::string region_name_msg; + + LLVector3d posGlobal = viewPosToGlobal(x, y); + LLViewerRegion *region = LLWorld::getInstance()->getRegionFromPosGlobal(posGlobal); + if (region) + { + std::string region_name = region->getName(); + if (!region_name.empty()) + { + region_name_msg = mRegionNameMsg; + LLStringUtil::format(region_name_msg, {{"[REGION_NAME]", region_name}}); + } + + // Only show parcel information in the tooltip if property lines are visible. Otherwise, the parcel the tooltip is referring to is + // ambiguous. + if (gSavedSettings.getBOOL("MiniMapShowPropertyLines")) + { + LLViewerParcelMgr::getInstance()->setHoverParcel(posGlobal); + LLParcel *hover_parcel = LLViewerParcelMgr::getInstance()->getHoverParcel(); + if (hover_parcel) + { + std::string parcel_name = hover_parcel->getName(); + if (!parcel_name.empty()) + { + parcel_name_msg = mParcelNameMsg; + LLStringUtil::format(parcel_name_msg, {{"[PARCEL_NAME]", parcel_name}}); + } + + const LLUUID parcel_owner = hover_parcel->getOwnerID(); + std::string parcel_owner_name_url = LLSLURL("agent", parcel_owner, "inspect").getSLURLString(); + static LLUrlMatch parcel_owner_name_url_match; + LLUrlRegistry::getInstance()->findUrl(parcel_owner_name_url, parcel_owner_name_url_match); + if (!parcel_owner_name_url_match.empty()) + { + parcel_owner_msg = mParcelOwnerMsg; + std::string parcel_owner_name = parcel_owner_name_url_match.getLabel(); + LLStringUtil::format(parcel_owner_msg, {{"[PARCEL_OWNER]", parcel_owner_name}}); + } + + if (hover_parcel->getForSale()) + { + const LLUUID auth_buyer_id = hover_parcel->getAuthorizedBuyerID(); + const LLUUID agent_id = gAgent.getID(); + bool show_for_sale = auth_buyer_id.isNull() || auth_buyer_id == agent_id || parcel_owner == agent_id; + if (show_for_sale) + { + S32 price = hover_parcel->getSalePrice(); + S32 area = hover_parcel->getArea(); + F32 cost_per_sqm = 0.0f; + if (area > 0) + { + cost_per_sqm = F32(price) / area; + } + std::string formatted_price = LLResMgr::getInstance()->getMonetaryString(price); + std::string formatted_cost_per_meter = llformat("%.1f", cost_per_sqm); + parcel_sale_price_msg = mParcelSalePriceMsg; + LLStringUtil::format(parcel_sale_price_msg, + {{"[PRICE]", formatted_price}, {"[PRICE_PER_SQM]", formatted_cost_per_meter}}); + std::string formatted_area = llformat("%d", area); + parcel_sale_area_msg = mParcelSaleAreaMsg; + LLStringUtil::format(parcel_sale_area_msg, {{"[AREA]", formatted_area}}); + } + } + } + } + } + + std::string tool_tip_hint_msg; + if (gSavedSettings.getBOOL("DoubleClickTeleport")) + { + tool_tip_hint_msg = mAltToolTipHintMsg; + } + else if (gSavedSettings.getBOOL("DoubleClickShowWorldMap")) + { + tool_tip_hint_msg = mToolTipHintMsg; + } + + LLStringUtil::format_map_t args; + args["[PARCEL_NAME_MSG]"] = parcel_name_msg.empty() ? "" : parcel_name_msg + '\n'; + args["[PARCEL_SALE_PRICE_MSG]"] = parcel_sale_price_msg.empty() ? "" : parcel_sale_price_msg + '\n'; + args["[PARCEL_SALE_AREA_MSG]"] = parcel_sale_area_msg.empty() ? "" : parcel_sale_area_msg + '\n'; + args["[PARCEL_OWNER_MSG]"] = parcel_owner_msg.empty() ? "" : parcel_owner_msg + '\n'; + args["[REGION_NAME_MSG]"] = region_name_msg.empty() ? "" : region_name_msg + '\n'; + args["[TOOL_TIP_HINT_MSG]"] = tool_tip_hint_msg.empty() ? "" : tool_tip_hint_msg + '\n'; + + std::string msg = mToolTipMsg; + LLStringUtil::format(msg, args); + if (msg.back() == '\n') + { + msg.resize(msg.size() - 1); + } + LLToolTipMgr::instance().show(LLToolTip::Params().message(msg).sticky_rect(sticky_rect)); + + return true; +} + +bool LLNetMap::handleToolTipAgent(const LLUUID& avatar_id) +{ + LLAvatarName av_name; + if (avatar_id.isNull() || !LLAvatarNameCache::get(avatar_id, &av_name)) + { + return false; + } + + // only show tooltip if same inspector not already open + LLFloater* existing_inspector = LLFloaterReg::findInstance("inspect_avatar"); + if (!existing_inspector + || !existing_inspector->getVisible() + || existing_inspector->getKey()["avatar_id"].asUUID() != avatar_id) + { + LLInspector::Params p; + p.fillFrom(LLUICtrlFactory::instance().getDefaultParams<LLInspector>()); + p.message(av_name.getCompleteName()); + p.image.name("Inspector_I"); + p.click_callback(boost::bind(showAvatarInspector, avatar_id)); + p.visible_time_near(6.f); + p.visible_time_far(3.f); + p.delay_time(0.35f); + p.wrap(false); + + LLToolTipMgr::instance().show(p); + } + return true; +} + +// static +void LLNetMap::showAvatarInspector(const LLUUID& avatar_id) +{ + LLSD params; + params["avatar_id"] = avatar_id; + + if (LLToolTipMgr::instance().toolTipVisible()) + { + LLRect rect = LLToolTipMgr::instance().getToolTipRect(); + params["pos"]["x"] = rect.mLeft; + params["pos"]["y"] = rect.mTop; + } + + LLFloaterReg::showInstance("inspect_avatar", params); +} + +void LLNetMap::renderScaledPointGlobal( const LLVector3d& pos, const LLColor4U &color, F32 radius_meters ) +{ + LLVector3 local_pos; + local_pos.setVec( pos - mObjectImageCenterGlobal ); + + S32 diameter_pixels = ll_round(2 * radius_meters * mObjectMapTPM); + renderPoint( local_pos, color, diameter_pixels ); +} + + +void LLNetMap::renderPoint(const LLVector3 &pos_local, const LLColor4U &color, + S32 diameter, S32 relative_height) +{ + if (diameter <= 0) + { + return; + } + + const S32 image_width = (S32)mObjectImagep->getWidth(); + const S32 image_height = (S32)mObjectImagep->getHeight(); + + S32 x_offset = ll_round(pos_local.mV[VX] * mObjectMapTPM + image_width / 2); + S32 y_offset = ll_round(pos_local.mV[VY] * mObjectMapTPM + image_height / 2); + + if ((x_offset < 0) || (x_offset >= image_width)) + { + return; + } + if ((y_offset < 0) || (y_offset >= image_height)) + { + return; + } + + LLImageDataLock lock(mObjectRawImagep); + U8 *datap = mObjectRawImagep->getData(); + + S32 neg_radius = diameter / 2; + S32 pos_radius = diameter - neg_radius; + S32 x, y; + + if (relative_height > 0) + { + // ...point above agent + S32 px, py; + + // vertical line + px = x_offset; + for (y = -neg_radius; y < pos_radius; y++) + { + py = y_offset + y; + if ((py < 0) || (py >= image_height)) + { + continue; + } + S32 offset = px + py * image_width; + ((U32*)datap)[offset] = color.asRGBA(); + } + + // top line + py = y_offset + pos_radius - 1; + for (x = -neg_radius; x < pos_radius; x++) + { + px = x_offset + x; + if ((px < 0) || (px >= image_width)) + { + continue; + } + S32 offset = px + py * image_width; + ((U32*)datap)[offset] = color.asRGBA(); + } + } + else + { + // ...point level with agent + for (x = -neg_radius; x < pos_radius; x++) + { + S32 p_x = x_offset + x; + if ((p_x < 0) || (p_x >= image_width)) + { + continue; + } + + for (y = -neg_radius; y < pos_radius; y++) + { + S32 p_y = y_offset + y; + if ((p_y < 0) || (p_y >= image_height)) + { + continue; + } + S32 offset = p_x + p_y * image_width; + ((U32*)datap)[offset] = color.asRGBA(); + } + } + } +} + +void LLNetMap::createObjectImage() +{ + // Find the size of the side of a square that surrounds the circle that surrounds getRect(). + // ... which is, the diagonal of the rect. + F32 width = (F32)getRect().getWidth(); + F32 height = (F32)getRect().getHeight(); + S32 square_size = ll_round( sqrt(width*width + height*height) ); + + // Find the least power of two >= the minimum size. + const S32 MIN_SIZE = 64; + const S32 MAX_SIZE = 256; + S32 img_size = MIN_SIZE; + while( (img_size*2 < square_size ) && (img_size < MAX_SIZE) ) + { + img_size <<= 1; + } + + if( mObjectImagep.isNull() || + (mObjectImagep->getWidth() != img_size) || + (mObjectImagep->getHeight() != img_size) ) + { + mObjectRawImagep = new LLImageRaw(img_size, img_size, 4); + U8* data = mObjectRawImagep->getData(); + memset( data, 0, img_size * img_size * 4 ); + mObjectImagep = LLViewerTextureManager::getLocalTexture( mObjectRawImagep.get(), false); + } + setScale(mScale); + mUpdateNow = true; +} + +bool LLNetMap::handleMouseDown(S32 x, S32 y, MASK mask) +{ + // Start panning + gFocusMgr.setMouseCapture(this); + + mStartPan = mCurPan; + mMouseDown.mX = x; + mMouseDown.mY = y; + return true; +} + +bool LLNetMap::handleMouseUp(S32 x, S32 y, MASK mask) +{ + if (abs(mMouseDown.mX - x) < 3 && abs(mMouseDown.mY - y) < 3) + { + handleClick(x, y, mask); + } + + if (hasMouseCapture()) + { + if (mPanning) + { + // restore mouse cursor + S32 local_x, local_y; + local_x = mMouseDown.mX + llfloor(mCurPan.mV[VX] - mStartPan.mV[VX]); + local_y = mMouseDown.mY + llfloor(mCurPan.mV[VY] - mStartPan.mV[VY]); + LLRect clip_rect = getRect(); + clip_rect.stretch(-8); + clip_rect.clipPointToRect(mMouseDown.mX, mMouseDown.mY, local_x, local_y); + LLUI::getInstance()->setMousePositionLocal(this, local_x, local_y); + + // finish the pan + mPanning = false; + + mMouseDown.set(0, 0); + } + gViewerWindow->showCursor(); + gFocusMgr.setMouseCapture(NULL); + return true; + } + + return false; +} + +bool LLNetMap::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + auto menu = static_cast<LLMenuGL*>(mPopupMenuHandle.get()); + if (menu) + { + mPopupWorldPos = viewPosToGlobal(x, y); + menu->buildDrawLabels(); + menu->updateParent(LLMenuGL::sMenuContainer); + menu->setItemEnabled("Stop Tracking", LLTracker::isTracking(0)); + LLMenuGL::showPopup(this, menu, x, y); + } + return true; +} + +bool LLNetMap::handleClick(S32 x, S32 y, MASK mask) +{ + // TODO: allow clicking an avatar on minimap to select avatar in the nearby avatar list + // if(mClosestAgentToCursor.notNull()) + // mNearbyList->selectUser(mClosestAgentToCursor); + // Needs a registered observer i guess to accomplish this without using + // globals to tell the mNearbyList in llpeoplepanel to select the user + return true; +} + +bool LLNetMap::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + LLVector3d pos_global = viewPosToGlobal(x, y); + + bool double_click_teleport = gSavedSettings.getBOOL("DoubleClickTeleport"); + bool double_click_show_world_map = gSavedSettings.getBOOL("DoubleClickShowWorldMap"); + + if (double_click_teleport || double_click_show_world_map) + { + // If we're not tracking a beacon already, double-click will set one + if (!LLTracker::isTracking(NULL)) + { + LLFloaterWorldMap* world_map = LLFloaterWorldMap::getInstance(); + if (world_map) + { + world_map->trackLocation(pos_global); + } + } + } + + if (double_click_teleport) + { + // If DoubleClickTeleport is on, double clicking the minimap will teleport there + gAgent.teleportViaLocationLookAt(pos_global); + } + else if (double_click_show_world_map) + { + LLFloaterReg::showInstance("world_map"); + } + return true; +} + +F32 LLNetMap::getScaleForName(std::string scale_name) +{ + if (scale_name == "very close") + { + return LLNetMap::MAP_SCALE_VERY_CLOSE; + } + else if (scale_name == "close") + { + return LLNetMap::MAP_SCALE_CLOSE; + } + else if (scale_name == "medium") + { + return LLNetMap::MAP_SCALE_MEDIUM; + } + else if (scale_name == "far") + { + return LLNetMap::MAP_SCALE_FAR; + } + return 0.0f; +} + +// static +bool LLNetMap::outsideSlop( S32 x, S32 y, S32 start_x, S32 start_y, S32 slop ) +{ + S32 dx = x - start_x; + S32 dy = y - start_y; + + return (dx <= -slop || slop <= dx || dy <= -slop || slop <= dy); +} + +bool LLNetMap::handleHover( S32 x, S32 y, MASK mask ) +{ + if (hasMouseCapture()) + { + if (mPanning || outsideSlop(x, y, mMouseDown.mX, mMouseDown.mY, MOUSE_DRAG_SLOP)) + { + if (!mPanning) + { + // Just started panning. Hide cursor. + mPanning = true; + gViewerWindow->hideCursor(); + } + + LLVector2 delta(static_cast<F32>(gViewerWindow->getCurrentMouseDX()), + static_cast<F32>(gViewerWindow->getCurrentMouseDY())); + + // Set pan to value at start of drag + offset + mCurPan += delta; + + gViewerWindow->moveCursorToCenter(); + } + } + + if (mask & MASK_SHIFT) + { + // If shift is held, change the cursor to hint that the map can be + // dragged. However, holding shift is not required to drag the map. + gViewerWindow->setCursor( UI_CURSOR_TOOLPAN ); + } + else + { + gViewerWindow->setCursor( UI_CURSOR_CROSS ); + } + + return true; +} + +bool LLNetMap::isZoomChecked(const LLSD &userdata) +{ + std::string level = userdata.asString(); + F32 scale = getScaleForName(level); + return scale == mScale; +} + +void LLNetMap::setZoom(const LLSD &userdata) +{ + std::string level = userdata.asString(); + F32 scale = getScaleForName(level); + if (scale != 0.0f) + { + setScale(scale); + } +} + +void LLNetMap::handleStopTracking (const LLSD& userdata) +{ + auto menu = static_cast<LLMenuGL*>(mPopupMenuHandle.get()); + if (menu) + { + menu->setItemEnabled ("Stop Tracking", false); + LLTracker::stopTracking (LLTracker::isTracking(NULL)); + } +} + +void LLNetMap::activateCenterMap(const LLSD &userdata) { mCentering = true; } + +bool LLNetMap::isMapOrientationChecked(const LLSD &userdata) +{ + const std::string command_name = userdata.asString(); + const bool rotate_map = gSavedSettings.getBOOL("MiniMapRotate"); + if (command_name == "north_at_top") + { + return !rotate_map; + } + + if (command_name == "camera_at_top") + { + return rotate_map; + } + + return false; +} + +void LLNetMap::setMapOrientation(const LLSD &userdata) +{ + const std::string command_name = userdata.asString(); + if (command_name == "north_at_top") + { + gSavedSettings.setBOOL("MiniMapRotate", false); + } + else if (command_name == "camera_at_top") + { + gSavedSettings.setBOOL("MiniMapRotate", true); + } +} + +void LLNetMap::popupShowAboutLand(const LLSD &userdata) +{ + // Update parcel selection. It's important to deselect land first so the "About Land" floater doesn't refresh with the old selection. + LLViewerParcelMgr::getInstance()->deselectLand(); + LLParcelSelectionHandle selection = LLViewerParcelMgr::getInstance()->selectParcelAt(mPopupWorldPos); + gMenuHolder->setParcelSelection(selection); + + LLFloaterReg::showInstance("about_land", LLSD(), false); +} |