/** 
 * @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 "llui.h"
#include "lltooltip.h"

#include "llglheaders.h"

// Viewer includes
#include "llagent.h"
#include "llagentcamera.h"
#include "llappviewer.h" // for gDisconnected
// [SL:KB] - Patch: World-MiniMap | Checked: 2012-07-08 (Catznip-3.3)
#include "llavataractions.h"
#include "llfloatersidepanelcontainer.h"
// [/SL:KB]
#include "llcallingcard.h" // LLAvatarTracker
#include "llfloaterworldmap.h"
// [SL:KB] - Patch: World-MinimapOverlay | Checked: 2012-06-20 (Catznip-3.3)
#include "llparcel.h"
// [/SL:KB]
#include "lltracker.h"
#include "llsurface.h"
// [SL:KB] - Patch: World-MiniMap | Checked: 2012-07-08 (Catznip-3.3)
#include "lltrans.h"
// [/SL:KB]
#include "llviewercamera.h"
#include "llviewercontrol.h"
#include "llviewertexture.h"
#include "llviewertexturelist.h"
#include "llviewermenu.h"
#include "llviewerobjectlist.h"
// [SL:KB] - Patch: World-MinimapOverlay | Checked: 2012-06-20 (Catznip-3.3)
#include "llviewerparcelmgr.h"
#include "llviewerparceloverlay.h"
// [/SL:KB]
#include "llviewerregion.h"
#include "llviewerwindow.h"
#include "llworld.h"
#include "llworldmapview.h"		// shared draw code
// [RLVa:KB] - Checked: RLVa-2.0.1
#include "rlvactions.h"
#include "rlvcommon.h"
// [/RLVa:KB]
#include "llmutelist.h"
#include "lfsimfeaturehandler.h"
#include "fsfloaterplacedetails.h"

// Ansariel: For accessing the radar data
#include "fsradar.h"
#include "lggcontactsets.h"
#include "fscommon.h"

static LLDefaultChildRegistry::Register<LLNetMap> r1("net_map");

const F32 LLNetMap::MAP_SCALE_MIN = 32;
const F32 LLNetMap::MAP_SCALE_MID = 1024;
const F32 LLNetMap::MAP_SCALE_MAX = 4096;

const F32 MAP_SCALE_ZOOM_FACTOR = 1.04f; // Zoom in factor per click of scroll wheel (4%)
const F32 MIN_DOT_RADIUS = 3.5f;
const F32 DOT_SCALE = 0.75f;
const F32 MIN_PICK_SCALE = 2.f;
const S32 MOUSE_DRAG_SLOP = 2;		// How far the mouse needs to move before we think it's a drag

const F64 COARSEUPDATE_MAX_Z = 1020.0f;

const F32 WIDTH_PIXELS = 2.f;
const S32 CIRCLE_STEPS = 100;

LLNetMap::avatar_marks_map_t LLNetMap::sAvatarMarksMap; // <FS:Ansariel>
F32 LLNetMap::sScale; // <FS:Ansariel> Synchronizing netmaps throughout instances

// <FS:Ansariel> Synchronize tooltips throughout instances
std::string LLNetMap::sToolTipMsg;
// </FS:Ansariel> Synchronize tooltips throughout instances

LLNetMap::LLNetMap (const Params & p)
:	LLUICtrl (p),
	mBackgroundColor (p.bg_color()),
	mScale( MAP_SCALE_MID ),
	mPixelsPerMeter( MAP_SCALE_MID / REGION_WIDTH_METERS ),
	mObjectMapTPM(0.f),
	mObjectMapPixels(0.f),
	mTargetPan(0.f, 0.f),
	mCurPan(0.f, 0.f),
	mStartPan(0.f, 0.f),
	mMouseDown(0, 0),
	mPanning(false),
//	mUpdateNow(false),
// [SL:KB] - Patch: World-MinimapOverlay | Checked: 2012-06-20 (Catznip-3.3)
	mUpdateObjectImage(false),
	mUpdateParcelImage(false),
// [/SL:KB]
	mObjectImageCenterGlobal( gAgentCamera.getCameraPositionGlobal() ),
	mObjectRawImagep(),
	mObjectImagep(),
// [SL:KB] - Patch: World-MinimapOverlay | Checked: 2012-06-20 (Catznip-3.3)
	mParcelImageCenterGlobal( gAgentCamera.getCameraPositionGlobal() ),
	mParcelRawImagep(),
	mParcelImagep(),
// [/SL:KB]
	mClosestAgentToCursor(),
//	mClosestAgentAtLastRightClick(),
	// <FS:Ansariel> Synchronize tooltips throughout instances
	//mToolTipMsg(),
	mPopupMenu(NULL)
{
	// <FS:Ansariel> Fixing borked minimap zoom level persistance
	//mScale = gSavedSettings.getF32("MiniMapScale");
	//mPixelsPerMeter = mScale / REGION_WIDTH_METERS;
	//mDotRadius = llmax(DOT_SCALE * mPixelsPerMeter, MIN_DOT_RADIUS);
	setScale(gSavedSettings.getF32("MiniMapScale"));
	// </FS:Ansariel>
}

LLNetMap::~LLNetMap()
{
	// <FS:Ansariel> Protect avatar name lookup callbacks
	for (avatar_name_cache_connection_map_t::iterator it = mAvatarNameCacheConnections.begin(); it != mAvatarNameCacheConnections.end(); ++it)
	{
		if (it->second.connected())
		{
			it->second.disconnect();
		}
	}
	mAvatarNameCacheConnections.clear();
	// </FS:Ansariel>

	// <FS:Ansariel> Fixing borked minimap zoom level persistance
	gSavedSettings.setF32("MiniMapScale", sScale);

// [SL:KB] - Patch: World-MinimapOverlay | Checked: 2012-06-20 (Catznip-3.3)
	if (mParcelMgrConn.connected())
	{
		mParcelMgrConn.disconnect();
	}
	if (mParcelOverlayConn.connected())
	{
		mParcelOverlayConn.disconnect();
	}
// [/SL:KB]
}

BOOL LLNetMap::postBuild()
{
	LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar;
	
	registrar.add("Minimap.Zoom", boost::bind(&LLNetMap::handleZoom, this, _2));
	registrar.add("Minimap.Tracker", boost::bind(&LLNetMap::handleStopTracking, this, _2));
	// <FS:Ansariel>
	registrar.add("Minimap.Mark", boost::bind(&LLNetMap::handleMark, this, _2));
	registrar.add("Minimap.ClearMark", boost::bind(&LLNetMap::handleClearMark, this));
	registrar.add("Minimap.ClearMarks", boost::bind(&LLNetMap::handleClearMarks, this));
	// </FS:Ansariel>
	registrar.add("Minimap.Cam", boost::bind(&LLNetMap::handleCam, this));
	registrar.add("Minimap.StartTracking", boost::bind(&LLNetMap::handleStartTracking, this));
// [SL:KB] - Patch: World-MiniMap | Checked: 2012-07-08 (Catznip-3.3)
	registrar.add("Minimap.ShowProfile", boost::bind(&LLNetMap::handleShowProfile, this, _2));
	registrar.add("Minimap.TextureType", boost::bind(&LLNetMap::handleTextureType, this, _2));
	registrar.add("Minimap.ToggleOverlay", boost::bind(&LLNetMap::handleOverlayToggle, this, _2));

	registrar.add("Minimap.AddFriend", boost::bind(&LLNetMap::handleAddFriend, this));
	registrar.add("Minimap.AddToContactSet", boost::bind(&LLNetMap::handleAddToContactSet, this));
	registrar.add("Minimap.RemoveFriend", boost::bind(&LLNetMap::handleRemoveFriend, this));
	registrar.add("Minimap.IM", boost::bind(&LLNetMap::handleIM, this));
	registrar.add("Minimap.Call", boost::bind(&LLNetMap::handleCall, this));
	registrar.add("Minimap.Map", boost::bind(&LLNetMap::handleMap, this));
	registrar.add("Minimap.Share", boost::bind(&LLNetMap::handleShare, this));
	registrar.add("Minimap.Pay", boost::bind(&LLNetMap::handlePay, this));
	registrar.add("Minimap.OfferTeleport", boost::bind(&LLNetMap::handleOfferTeleport, this));
	registrar.add("Minimap.RequestTeleport", boost::bind(&LLNetMap::handleRequestTeleport, this));
	registrar.add("Minimap.TeleportToAvatar", boost::bind(&LLNetMap::handleTeleportToAvatar, this));
	registrar.add("Minimap.GroupInvite", boost::bind(&LLNetMap::handleGroupInvite, this));
	registrar.add("Minimap.GetScriptInfo", boost::bind(&LLNetMap::handleGetScriptInfo, this));
	registrar.add("Minimap.BlockUnblock", boost::bind(&LLNetMap::handleBlockUnblock, this));
	registrar.add("Minimap.Report", boost::bind(&LLNetMap::handleReport, this));
	registrar.add("Minimap.Freeze", boost::bind(&LLNetMap::handleFreeze, this));
	registrar.add("Minimap.Eject", boost::bind(&LLNetMap::handleEject, this));
	registrar.add("Minimap.Kick", boost::bind(&LLNetMap::handleKick, this));
	registrar.add("Minimap.TeleportHome", boost::bind(&LLNetMap::handleTeleportHome, this));
	registrar.add("Minimap.EstateBan", boost::bind(&LLNetMap::handleEstateBan, this));
	registrar.add("Minimap.Derender", boost::bind(&LLNetMap::handleDerender, this, false));
	registrar.add("Minimap.DerenderPermanent", boost::bind(&LLNetMap::handleDerender, this, true));

	LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar;
	enable_registrar.add("Minimap.CheckTextureType", boost::bind(&LLNetMap::checkTextureType, this, _2));

	enable_registrar.add("Minimap.CanAddFriend", boost::bind(&LLNetMap::canAddFriend, this));
	enable_registrar.add("Minimap.CanRemoveFriend", boost::bind(&LLNetMap::canRemoveFriend, this));
	enable_registrar.add("Minimap.CanCall", boost::bind(&LLNetMap::canCall, this));
	enable_registrar.add("Minimap.CanMap", boost::bind(&LLNetMap::canMap, this));
	enable_registrar.add("Minimap.CanShare", boost::bind(&LLNetMap::canShare, this));
	enable_registrar.add("Minimap.CanOfferTeleport", boost::bind(&LLNetMap::canOfferTeleport, this));
	enable_registrar.add("Minimap.CanRequestTeleport", boost::bind(&LLNetMap::canRequestTeleport, this));
	enable_registrar.add("Minimap.IsBlocked", boost::bind(&LLNetMap::isBlocked, this));
	enable_registrar.add("Minimap.CanBlock", boost::bind(&LLNetMap::canBlock, this));
	enable_registrar.add("Minimap.VisibleFreezeEject", boost::bind(&LLNetMap::canFreezeEject, this));
	enable_registrar.add("Minimap.VisibleKickTeleportHome", boost::bind(&LLNetMap::canKickTeleportHome, this));
// [/SL:KB]

// [SL:KB] - Patch: World-MinimapOverlay | Checked: 2012-06-20 (Catznip-3.3)
	mParcelMgrConn = LLViewerParcelMgr::instance().setCollisionUpdateCallback(boost::bind(&LLNetMap::refreshParcelOverlay, this));
	mParcelOverlayConn = LLViewerParcelOverlay::setUpdateCallback(boost::bind(&LLNetMap::refreshParcelOverlay, this));
// [/SL:KB]

	mPopupMenu = LLUICtrlFactory::getInstance()->createFromFile<LLMenuGL>("menu_mini_map.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance());

	// <FS:Ansariel> Synchronize tooltips throughout instances
	LLNetMap::updateToolTipMsg();
	return TRUE;
}

void LLNetMap::setScale( F32 scale )
{
	scale = llclamp(scale, MAP_SCALE_MIN, MAP_SCALE_MAX);
	mCurPan *= scale / mScale;
	mScale = scale;
	// <FS:Ansariel> Synchronize scale throughout instances
	sScale = 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;
// <FS:CR> Aurora Sim
		//F32 meters = region_widths * LLWorld::getInstance()->getRegionWidthInMeters();
		F32 meters = region_widths * REGION_WIDTH_METERS;
// </FS:CR> Aurora Sim
		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);

	// <FS:Ansariel> Fixing borked minimap zoom level persistance
	//gSavedSettings.setF32("MiniMapScale", mScale);

// [SL:KB] - Patch: World-MinimapOverlay | Checked: 2012-06-20 (Catznip-3.3)
	mUpdateObjectImage = true;
	mUpdateParcelImage = true;
// [/SL:KB]
//	mUpdateNow = true;
}


///////////////////////////////////////////////////////////////////////////////////

void LLNetMap::draw()
{
	// <FS:Ansariel>: Synchronize netmap scale throughout instances
	if (mScale != sScale)
	{
		setScale(sScale);
	}
	// </FS:Ansariel>: Synchronize netmap scale throughout instances

// <FS:Ansariel> Aurora Sim
	if (!LLWorld::getInstance()->getAllowMinimap())
	{
		return;
	}
// <FS:Ansariel> Aurora Sim

	static LLFrameTimer map_timer;
	static LLUIColor map_avatar_color = LLUIColorTable::instance().getColor("MapAvatarColor", 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_frustum_rotating_color = LLUIColorTable::instance().getColor("MapFrustumRotatingColor", LLColor4::white);
	static LLUIColor map_whisper_ring_color = LLUIColorTable::instance().getColor("MapWhisperRingColor", LLColor4::blue); // <FS:LO> FIRE-17460 Add Whisper Chat Ring to Minimap
	static LLUIColor map_chat_ring_color = LLUIColorTable::instance().getColor("MapChatRingColor", LLColor4::yellow);
	static LLUIColor map_shout_ring_color = LLUIColorTable::instance().getColor("MapShoutRingColor", LLColor4::red);
// [SL:KB] - Patch: World-MinimapOverlay | Checked: 2012-08-17 (Catznip-3.3)
	static LLUIColor map_property_line = LLUIColorTable::instance().getColor("MiniMapPropertyLine", LLColor4::white);
// [/SL:KB]
	
	if (mObjectImagep.isNull())
	{
		createObjectImage();
	}

// [SL:KB] - Patch: World-MinimapOverlay | Checked: 2012-06-20 (Catznip-3.3)
	if (mParcelImagep.isNull())
	{
		createParcelImage();
	}
// [/SL:KB]

	static LLUICachedControl<bool> auto_center("MiniMapAutoCenter", true);
	if (auto_center)
	{
		mCurPan = lerp(mCurPan, mTargetPan, LLSmoothInterpolation::getInterpolant(0.1f));
	}

	// 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
// <FS:CR> Aurora Sim
		//S32 region_width = ll_round(LLWorld::getInstance()->getRegionWidthInMeters());
		S32 region_width = ll_round(REGION_WIDTH_METERS);
// </FS:CR> Aurora Sim

		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] / region_width) * mScale;
			F32 relative_y = (rel_region_pos.mV[1] / region_width) * mScale;

			// background region rectangle
			F32 bottom =	relative_y;
			F32 left =		relative_x;
// <FS:CR> Aurora Sim
			//F32 top =		bottom + mScale ;
			//F32 right =		left + mScale ;
			const F32 real_width(regionp->getWidth());
			F32 top =		bottom + (real_width / region_width) * mScale ;
			F32 right =		left + (real_width / region_width) * mScale ;
// </FS:CR> Aurora Sim

			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);
			}

// [SL:KB] - Patch: World-MinimapOverlay | Checked: 2012-07-26 (Catznip-3.3)
			static LLCachedControl<bool> s_fUseWorldMapTextures(gSavedSettings, "MiniMapWorldMapTextures") ;
			bool fRenderTerrain = true;

			if (s_fUseWorldMapTextures)
			{
				const LLViewerRegion::tex_matrix_t& tiles(regionp->getWorldMapTiles());
				for (S32 i(0), scaled_width(real_width / region_width), square_width(scaled_width * scaled_width);
					i < square_width; ++i)
				{
					const F32 y(i / scaled_width);
					const F32 x(i - y * scaled_width);
					const F32 local_left(left + x * mScale);
					const F32 local_right(local_left + mScale);
					const F32 local_bottom(bottom + y * mScale);
					const F32 local_top(local_bottom + mScale);
					LLViewerTexture* pRegionImage = tiles[x * scaled_width + y];
					if (pRegionImage && pRegionImage->hasGLTexture())
					{
						gGL.getTexUnit(0)->bind(pRegionImage);
						gGL.begin(LLRender::TRIANGLES);
						{
							gGL.texCoord2f(0.f, 1.f);
							gGL.vertex2f(local_left, local_top);
							gGL.texCoord2f(0.f, 0.f);
							gGL.vertex2f(local_left, local_bottom);
							gGL.texCoord2f(1.f, 0.f);
							gGL.vertex2f(local_right, local_bottom);

							gGL.texCoord2f(0.f, 1.f);
							gGL.vertex2f(local_left, local_top);
							gGL.texCoord2f(1.f, 0.f);
							gGL.vertex2f(local_right, local_bottom);
							gGL.texCoord2f(1.f, 1.f);
							gGL.vertex2f(local_right, local_top);
						}
						gGL.end();

						pRegionImage->setBoostLevel(LLViewerTexture::BOOST_MAP_VISIBLE);
						fRenderTerrain = false;
					}
				}
			}
// [/SL:KB]

// [SL:KB] - Patch: World-MinimapOverlay | Checked: 2012-07-26 (Catznip-3.3)
			if (fRenderTerrain)
			{
// [/SL:KB]
				// Draw using texture.
				gGL.getTexUnit(0)->bind(regionp->getLand().getSTexture());
				// <FS:Ansariel> Remove QUADS rendering mode
				//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.begin(LLRender::TRIANGLES);
				{
					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(0.f, 1.f);
					gGL.vertex2f(left, top);
					gGL.texCoord2f(1.f, 0.f);
					gGL.vertex2f(right, bottom);
					gGL.texCoord2f(1.f, 1.f);
					gGL.vertex2f(right, top);
				}
				gGL.end();
				// </FS:Ansariel>

				// Draw water
				gGL.setAlphaRejectSettings(LLRender::CF_GREATER, ABOVE_WATERLINE_ALPHA / 255.f);
				{
					if (regionp->getLand().getWaterTexture())
					{
						gGL.getTexUnit(0)->bind(regionp->getLand().getWaterTexture());
						// <FS:Ansariel> Remove QUADS rendering mode
						//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.begin(LLRender::TRIANGLES);
						{
							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(0.f, 1.f);
							gGL.vertex2f(left, top);
							gGL.texCoord2f(1.f, 0.f);
							gGL.vertex2f(right, bottom);
							gGL.texCoord2f(1.f, 1.f);
							gGL.vertex2f(right, top);
						}
						gGL.end();
						// </FS:Ansariel>
					}
				}
				gGL.setAlphaRejectSettings(LLRender::CF_DEFAULT);
// [SL:KB] - Patch: World-MinimapOverlay | Checked: 2012-07-26 (Catznip-3.3)
			}
// [/SL:KB]
		}

		// Redraw object layer periodically
//		if (mUpdateNow || (map_timer.getElapsedTimeF32() > 0.5f))
//		{
//			mUpdateNow = false;
// [SL:KB] - Patch: World-MinimapOverlay | Checked: 2012-06-20 (Catznip-3.3)
		// Locate the center
		LLVector3 posCenter = globalPosToView(gAgentCamera.getCameraPositionGlobal());
		posCenter.mV[VX] -= mCurPan.mV[VX];
		posCenter.mV[VY] -= mCurPan.mV[VY];
		posCenter.mV[VZ] = 0.f;
		LLVector3d posCenterGlobal = viewPosToGlobal(llfloor(posCenter.mV[VX]), llfloor(posCenter.mV[VY]));

		static LLCachedControl<bool> s_fShowObjects(gSavedSettings, "MiniMapObjects") ;
		if ( (s_fShowObjects) && ((mUpdateObjectImage) || (map_timer.getElapsedTimeF32() > 0.5f)) )
		{
			mUpdateObjectImage = false;
// [/SL:KB]

//			// 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]));
// [SL:KB] - Patch: World-MinimapOverlay | Checked: 2012-06-20 (Catznip-3.3)
			mObjectImageCenterGlobal = posCenterGlobal;
// [/SL:KB]

			// Create the base texture.
			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();
		}

// [SL:KB] - Patch: World-MinimapOverlay | Checked: 2012-06-20 (Catznip-3.3)
		static LLCachedControl<bool> s_fShowPropertyLines(gSavedSettings, "MiniMapPropertyLines") ;
		if ( (s_fShowPropertyLines) && ((mUpdateParcelImage) || (dist_vec_squared2D(mParcelImageCenterGlobal, posCenterGlobal) > 9.0f)) )
		{
			mUpdateParcelImage = false;
			mParcelImageCenterGlobal = posCenterGlobal;

			U8* pTextureData = mParcelRawImagep->getData();
			memset(pTextureData, 0, mParcelImagep->getWidth() * mParcelImagep->getHeight() * mParcelImagep->getComponents());

			// Process each region
			for (LLWorld::region_list_t::const_iterator itRegion = LLWorld::getInstance()->getRegionList().begin();
					itRegion != LLWorld::getInstance()->getRegionList().end(); ++itRegion)
			{
				const LLViewerRegion* pRegion = *itRegion; LLColor4U clrOverlay;
				if (pRegion->isAlive())
					clrOverlay = map_property_line.get();
				else
					clrOverlay = LLColor4U(255, 128, 128, 255);
				renderPropertyLinesForRegion(pRegion, clrOverlay);
			}

			mParcelImagep->setSubImage(mParcelRawImagep, 0, 0, mParcelImagep->getWidth(), mParcelImagep->getHeight());
		}
// [/SL:KB]

		LLVector3 map_center_agent = gAgent.getPosAgentFromGlobal(mObjectImageCenterGlobal);
		LLVector3 camera_position = gAgentCamera.getCameraPositionAgent();
		map_center_agent -= camera_position;
		map_center_agent.mV[VX] *= mScale/region_width;
		map_center_agent.mV[VY] *= mScale/region_width;

//		gGL.getTexUnit(0)->bind(mObjectImagep);
		F32 image_half_width = 0.5f*mObjectMapPixels;
		F32 image_half_height = 0.5f*mObjectMapPixels;

// [SL:KB] - Patch: World-MinimapOverlay | Checked: 2012-07-26 (Catznip-3.3)
		if (s_fShowObjects)
		{
			gGL.color4f(1.f, 1.f, 1.f, 1.f);
			gGL.getTexUnit(0)->bind(mObjectImagep);
// [/SL:KB]
			// <FS:Ansariel> Remove QUADS rendering mode
			//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();
			gGL.begin(LLRender::TRIANGLES);
				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(0.f, 1.f);
				gGL.vertex2f(map_center_agent.mV[VX] - image_half_width, image_half_height + map_center_agent.mV[VY]);
				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();
			// </FS:Ansariel>
// [SL:KB] - Patch: World-MinimapOverlay | Checked: 2012-07-26 (Catznip-3.3)
		}
// [/SL:KB]

// [SL:KB] - Patch: World-MinimapOverlay | Checked: 2012-06-20 (Catznip-3.3)
		if (s_fShowPropertyLines)
		{
			map_center_agent = gAgent.getPosAgentFromGlobal(mParcelImageCenterGlobal) - camera_position;
			map_center_agent.mV[VX] *= mScale / region_width;
			map_center_agent.mV[VY] *= mScale / region_width;

			gGL.color4f(1.f, 1.f, 1.f, 1.f);
			gGL.getTexUnit(0)->bind(mParcelImagep);
			gGL.begin(LLRender::TRIANGLES);
				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(0.f, 1.f);
				gGL.vertex2f(map_center_agent.mV[VX] - image_half_width, image_half_height + map_center_agent.mV[VY]);
				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();
		}
// [/SL:KB]

		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);
// [SL:KB] - Patch: World-MiniMap | Checked: 2012-07-08 (Catznip-3.3)
		bool local_mouse = this->pointInView(local_mouse_x, local_mouse_y);
// [/SL:KB]
		mClosestAgentToCursor.setNull();
// [SL:KB] - Patch: World-MiniMap | Checked: 2012-07-08 (Catznip-3.3)
		mClosestAgentsToCursor.clear();
// [/SL:KB]
		F32 closest_dist_squared = F32_MAX; // value will be overridden in the loop
		// <FS:Ansariel> Configurable pick distance
		//F32 min_pick_dist_squared = (mDotRadius * MIN_PICK_SCALE) * (mDotRadius * MIN_PICK_SCALE);
		static LLCachedControl<F32> fsMinimapPickScale(gSavedSettings, "FSMinimapPickScale");
		F32 min_pick_dist_squared = (mDotRadius * fsMinimapPickScale) * (mDotRadius * fsMinimapPickScale);
		// </FS:Ansariel>

		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]);

			// <FS:Ansariel> Check for unknown Z-offset => AVATAR_UNKNOWN_Z_OFFSET
			//unknown_relative_z = positions[i].mdV[VZ] >= COARSEUPDATE_MAX_Z &&
			//		camera_position.mV[VZ] >= COARSEUPDATE_MAX_Z;
			unknown_relative_z = false;
			if (positions[i].mdV[VZ] == AVATAR_UNKNOWN_Z_OFFSET)
			{
				if (camera_position.mV[VZ] >= COARSEUPDATE_MAX_Z)
				{
					// No exact data and cam >=1020 => we don't know if
					// other avatar is above or below us => unknown
					unknown_relative_z = true;
				}
				else
				{
					// No exact data but cam is below 1020 => other avatar
					// is definitely above us => bump Z-offset to F32_MAX
					// so we get the up chevron
					pos_map.mV[VZ] = F32_MAX;
				}
			}
			// </FS:Ansariel>	
			
			LLColor4 color = getAvatarColor(uuid);	// <FS:CR>

// [RLVa:KB] - Checked: 2010-04-19 (RLVa-1.2.0f) | Modified: RLVa-1.2.0f | FS-Specific
			LLWorldMapView::drawAvatar(
				pos_map.mV[VX], pos_map.mV[VY],
				(RlvActions::canShowName(RlvActions::SNC_DEFAULT, uuid)) ? color : map_avatar_color.get(),
				pos_map.mV[VZ], mDotRadius,
				unknown_relative_z);
// [/RLVa:KB]
//			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);
					}
				}
			}

// [SL:KB] - Patch: World-MiniMap | Checked: 2012-07-08 (Catznip-3.3)
			if (local_mouse)
			{
// [/SL:KB]
				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)
				{
					if (dist_to_cursor_squared < closest_dist_squared)
					{
						closest_dist_squared = dist_to_cursor_squared;
						mClosestAgentToCursor = uuid;
						mClosestAgentPosition = positions[i];
					}
					mClosestAgentsToCursor.push_back(uuid);
				}
// [SL:KB] - Patch: World-MiniMap | Checked: 2012-07-08 (Catznip-3.3)
			}
// [/SL:KB]
//			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
		static LLUIColor self_tag_color = LLUIColorTable::instance().getColor("MapAvatarSelfColor", LLColor4::yellow); // <FS:CR> FIRE-1061
		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,
					  self_tag_color);	// <FS:CR> FIRE-1061

			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();
				mClosestAgentPosition = pos_global;
			}

			// Draw chat range ring(s)
			static LLUICachedControl<bool> chat_ring("MiniMapChatRing", true);
			// <FS:LO> FIRE-22954 Make each chat range ring in the minimap optional
			static LLUICachedControl<bool> fs_whisper_ring("FSMiniMapWhisperRing", true);
			static LLUICachedControl<bool> fs_chat_ring("FSMiniMapChatRing", true);
			static LLUICachedControl<bool> fs_shout_ring("FSMiniMapShoutRing", true);
			// </FS:LO>
			if(chat_ring)
			{
// <FS:CR> Opensim
				//drawRing(20.0, pos_map, map_chat_ring_color);
				//drawRing(100.0, pos_map, map_shout_ring_color);
				if (fs_whisper_ring) drawRing(LFSimFeatureHandler::getInstance()->whisperRange(), pos_map, map_whisper_ring_color);
				if (fs_chat_ring) drawRing(LFSimFeatureHandler::getInstance()->sayRange(), pos_map, map_chat_ring_color);
				if (fs_shout_ring) drawRing(LFSimFeatureHandler::getInstance()->shoutRange(), pos_map, map_shout_ring_color);
// </FS:CR> Opensim
			}
		}

		// Draw frustum
// <FS:CR> Aurora Sim
		//F32 meters_to_pixels = mScale/ LLWorld::getInstance()->getRegionWidthInMeters();
		F32 meters_to_pixels = mScale/ REGION_WIDTH_METERS;
// </FS:CR> Aurora Sim

		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 half_width_meters = far_clip_meters * tan( horiz_fov / 2 );
		F32 half_width_pixels = half_width_meters * meters_to_pixels;
		
		F32 ctr_x = (F32)center_sw_left;
		F32 ctr_y = (F32)center_sw_bottom;


		gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);

		// <FS:Ansariel> Draw pick radius; from Ayamo Nozaki (Exodus Viewer)
		static LLUIColor pick_radius_color = LLUIColorTable::instance().getColor("MapPickRadiusColor", map_frustum_color());
		gGL.color4fv((pick_radius_color()).mV);
		gl_circle_2d(local_mouse_x, local_mouse_y, mDotRadius * fsMinimapPickScale, 32, true);
		// </FS:Ansariel>

		if( rotate_map )
		{
			gGL.color4fv((map_frustum_color()).mV);

			gGL.begin( LLRender::TRIANGLES  );
				gGL.vertex2f( ctr_x, ctr_y );
				gGL.vertex2f( ctr_x - half_width_pixels, ctr_y + far_clip_pixels );
				gGL.vertex2f( ctr_x + half_width_pixels, ctr_y + far_clip_pixels );
			gGL.end();
		}
		else
		{
			gGL.color4fv((map_frustum_rotating_color()).mV);
			
			// If we don't rotate the map, we have to rotate the frustum.
			gGL.pushMatrix();
				gGL.translatef( ctr_x, ctr_y, 0 );
				gGL.rotatef( atan2( LLViewerCamera::getInstance()->getAtAxis().mV[VX], LLViewerCamera::getInstance()->getAtAxis().mV[VY] ) * RAD_TO_DEG, 0.f, 0.f, -1.f);
				gGL.begin( LLRender::TRIANGLES  );
					gGL.vertex2f( 0, 0 );
					gGL.vertex2f( -half_width_pixels, far_clip_pixels );
					gGL.vertex2f(  half_width_pixels, far_clip_pixels );
				gGL.end();
			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();
// [SL:KB] - Patch: World-MinimapOverlay | Checked: 2012-07-28 (Catznip-3.3)
	createParcelImage();
// [/SL:KB]
}

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

// <FS:CR> Aurora Sim
	mPixelsPerMeter = mScale / REGION_WIDTH_METERS;
// </FS:CR> Aurora Sim
	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::drawRing(const F32 radius, const LLVector3 pos_map, const LLUIColor& color)

{
// <FS:CR> Aurora Sim
	//F32 meters_to_pixels = mScale / LLWorld::getInstance()->getRegionWidthInMeters();
	F32 meters_to_pixels = mScale / REGION_WIDTH_METERS;
// </FS:CR> Aurora Sim
	F32 radius_pixels = radius * meters_to_pixels;

	gGL.matrixMode(LLRender::MM_MODELVIEW);
	gGL.pushMatrix();
	gGL.translatef((F32)pos_map.mV[VX], (F32)pos_map.mV[VY], 0.f);
	gl_ring(radius_pixels, WIDTH_PIXELS, color, color, CIRCLE_STEPS, FALSE);
	gGL.popMatrix();
}

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]);
	}
}

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 );
	}

// <FS:CR> Aurora Sim
	//pos_local *= ( LLWorld::getInstance()->getRegionWidthInMeters() / mScale );
	pos_local *= ( REGION_WIDTH_METERS / mScale );
// </FS:CR> Aurora Sim
	
	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))
// [RLVa:KB] - Checked: RLVa-1.2.2
	bool fRlvCanShowName = (mClosestAgentToCursor.notNull()) && (RlvActions::canShowName(RlvActions::SNC_DEFAULT, mClosestAgentToCursor));
	if ( (fRlvCanShowName) && (handleToolTipAgent(mClosestAgentToCursor)) )
// [/RLVa:KB]
	{
		return TRUE;
	}

// [RLVa:KB] - Checked: RLVa-1.2.2
	LLStringUtil::format_map_t args; LLAvatarName avName;
	args["[AGENT]"] = ( (!fRlvCanShowName) && (mClosestAgentToCursor.notNull()) && (LLAvatarNameCache::get(mClosestAgentToCursor, &avName)) ) ? RlvStrings::getAnonym(avName) + "\n" : "";
// [/RLVa:KB]

	LLRect sticky_rect;
	std::string region_name;
	LLViewerRegion*	region = LLWorld::getInstance()->getRegionFromPosGlobal( viewPosToGlobal( x, y ) );
	if(region)
	{
		// set 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;

//		region_name = region->getName();
// [RLVa:KB] - Checked: RLVa-1.2.2
		region_name = (RlvActions::canShowLocation()) ? region->getName() : RlvStrings::getString(RlvStringKeys::Hidden::Region);
// [/RLVa:KB]
		// <FS:Ansariel> Synchronize tooltips throughout instances
		//if (!region_name.empty())
		if (!region_name.empty() && LLNetMap::sToolTipMsg != "[REGION]")
		{
			region_name += "\n";
		}
	}

//	LLStringUtil::format_map_t args;
	args["[REGION]"] = region_name;
	// <FS:Ansariel> Synchronize tooltips throughout instances
	//std::string msg = mToolTipMsg;
	std::string msg = LLNetMap::sToolTipMsg;
	// </FS:Ansariel> Synchronize tooltips throughout instances
	LLStringUtil::format(msg, args);
	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>());
		
		// Add distance to avatars in hovertip for minimap
		if (avatar_id != gAgentID)
		{
			F32 distance(0.f);

			// If avatar is >=1020, the value for Z might be returned as AVATAR_UNKNOWN_Z_OFFSET
			bool isHigher1020mBug = (mClosestAgentPosition[VZ] == AVATAR_UNKNOWN_Z_OFFSET);

			// <FS:Ansariel> Try to get distance from the nearby people panel
			//               aka radar when above 1020m.
			if (isHigher1020mBug)
			{
				FSRadar* radar = FSRadar::getInstance();
				if (radar)
				{
					FSRadarEntry* entry = radar->getEntry(avatar_id);
					if (entry)
					{
						F32 radar_distance = entry->getRange();

						if (radar_distance > AVATAR_UNKNOWN_RANGE)
						{
							distance = radar_distance;
							isHigher1020mBug = false;
						}
					}
				}
			}
			else
			{
				distance = dist_vec(gAgent.getPositionGlobal(), mClosestAgentPosition);
			}

			LLStringUtil::format_map_t args;

			if (!isHigher1020mBug)
			{
				args["DISTANCE"] = llformat("%.02f", distance);
			}
			else
			{
				static LLCachedControl<F32> farClip(gSavedSettings, "RenderFarClip");
				args["DISTANCE"] = llformat("> %.02f", farClip());
			}
			std::string distanceLabel = LLTrans::getString("minimap_distance");
			LLStringUtil::format(distanceLabel, args);
			p.message(av_name.getCompleteName() + "\n" + distanceLabel);
		}
		else
		{
			p.message(av_name.getCompleteName());
		}
		
		// <FS:Ansariel> Get rid of the useless and clumsy I-button on the hovertip
		//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;
	}

	// <FS:Ansariel> FIRE-9045: Remove avatar inspector
	//LLFloaterReg::showInstance("inspect_avatar", params);
	if (gSavedSettings.getBOOL("FSInspectAvatarSlurlOpensProfile"))
	{
		LLAvatarActions::showProfile(avatar_id);
	}
	else
	{
		LLFloaterReg::showInstance("inspect_avatar", params);
	}
	// </FS:Ansariel>
}

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;
	}

	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();
			}
		}
	}
}

// [SL:KB] - Patch: World-MinimapOverlay | Checked: 2012-06-20 (Catznip-3.3)
void LLNetMap::renderPropertyLinesForRegion(const LLViewerRegion* pRegion, const LLColor4U& clrOverlay)
{
	const S32 imgWidth = (S32)mParcelImagep->getWidth();
	const S32 imgHeight = (S32)mParcelImagep->getHeight();

	const LLVector3 originLocal(pRegion->getOriginGlobal() - mParcelImageCenterGlobal);
	const S32 originX = ll_round(originLocal.mV[VX] * mObjectMapTPM + imgWidth / 2);
	const S32 originY = ll_round(originLocal.mV[VY] * mObjectMapTPM + imgHeight / 2);

	U32* pTextureData = (U32*)mParcelRawImagep->getData();

	//
	// Draw the north and east region borders
	//
	const F32 real_width(pRegion->getWidth());
	const S32 borderY = originY + ll_round(real_width * mObjectMapTPM);
	if ( (borderY >= 0) && (borderY < imgHeight) )
	{
		S32 curX = llclamp(originX, 0, imgWidth), endX = llclamp(originX + ll_round(real_width * mObjectMapTPM), 0, imgWidth - 1);
		for (; curX <= endX; curX++)
			pTextureData[borderY * imgWidth + curX] = clrOverlay.asRGBA();
	}
	const S32 borderX = originX + ll_round(real_width * mObjectMapTPM);
	if ( (borderX >= 0) && (borderX < imgWidth) )
	{
		S32 curY = llclamp(originY, 0, imgHeight), endY = llclamp(originY + ll_round(real_width * mObjectMapTPM), 0, imgHeight - 1);
		for (; curY <= endY; curY++)
			pTextureData[curY * imgWidth + borderX] = clrOverlay.asRGBA();
	}

	//
	// Render parcel lines
	//
	const F32 GRID_STEP = PARCEL_GRID_STEP_METERS;
	const S32 GRIDS_PER_EDGE = real_width / GRID_STEP;

	const U8* pOwnership = pRegion->getParcelOverlay()->getOwnership();
	const U8* pCollision = (pRegion->getHandle() == LLViewerParcelMgr::instance().getCollisionRegionHandle()) ? LLViewerParcelMgr::instance().getCollisionBitmap() : NULL;
	for (S32 idxRow = 0; idxRow < GRIDS_PER_EDGE; idxRow++)
	{
		for (S32 idxCol = 0; idxCol < GRIDS_PER_EDGE; idxCol++)
		{
			S32 overlay = pOwnership[idxRow * GRIDS_PER_EDGE + idxCol];
			S32 idxCollision = idxRow * GRIDS_PER_EDGE + idxCol;
			bool fForSale = ((overlay & PARCEL_COLOR_MASK) == PARCEL_FOR_SALE);
			bool fAuction = ((overlay & PARCEL_COLOR_MASK) == PARCEL_AUCTION);
			bool fCollision = (pCollision) && (pCollision[idxCollision / 8] & (1 << (idxCollision % 8)));
			if ( (!fForSale) && (!fCollision) && (!fAuction) && (0 == (overlay & (PARCEL_SOUTH_LINE | PARCEL_WEST_LINE))) )
				continue;

			const S32 posX = originX + ll_round(idxCol * GRID_STEP * mObjectMapTPM);
			const S32 posY = originY + ll_round(idxRow * GRID_STEP * mObjectMapTPM);

			static LLCachedControl<bool> s_fForSaleParcels(gSavedSettings, "MiniMapForSaleParcels");
			static LLCachedControl<bool> s_fShowCollisionParcels(gSavedSettings, "MiniMapCollisionParcels");
			if ( ((s_fForSaleParcels) && (fForSale || fAuction)) || ((s_fShowCollisionParcels) && (fCollision)) )
			{
				S32 curY = llclamp(posY, 0, imgHeight), endY = llclamp(posY + ll_round(GRID_STEP * mObjectMapTPM), 0, imgHeight - 1);
				for (; curY <= endY; curY++)
				{
					S32 curX = llclamp(posX, 0, imgWidth) , endX = llclamp(posX + ll_round(GRID_STEP * mObjectMapTPM), 0, imgWidth - 1);
					for (; curX <= endX; curX++)
					{
						U32 texcolor = LLColor4U(255, 128, 128, 192).asRGBA();
						if (fForSale)
						{
							texcolor = LLColor4U(255, 255, 128, 192).asRGBA();
						}
						else if (fAuction)
						{
							texcolor = LLColor4U(128, 0, 255, 102).asRGBA();
						}
						
						pTextureData[curY * imgWidth + curX] = texcolor;
					}
				}
			}
			if (overlay & PARCEL_SOUTH_LINE)
			{
				if ( (posY >= 0) && (posY < imgHeight) )
				{
					S32 curX = llclamp(posX, 0, imgWidth), endX = llclamp(posX + ll_round(GRID_STEP * mObjectMapTPM), 0, imgWidth - 1);
					for (; curX <= endX; curX++)
						pTextureData[posY * imgWidth + curX] = clrOverlay.asRGBA();
				}
			}
			if (overlay & PARCEL_WEST_LINE)
			{
				if ( (posX >= 0) && (posX < imgWidth) )
				{
					S32 curY = llclamp(posY, 0, imgHeight), endY = llclamp(posY + ll_round(GRID_STEP * mObjectMapTPM), 0, imgHeight - 1);
					for (; curY <= endY; curY++)
						pTextureData[curY * imgWidth + posX] = clrOverlay.asRGBA();
				}
			}
		}
	}
}
// [/SL:KB]

//void LLNetMap::createObjectImage()
// [SL:KB] - Patch: World-MinimapOverlay | Checked: 2012-06-20 (Catznip-3.3)
bool LLNetMap::createImage(LLPointer<LLImageRaw>& rawimagep) const
// [/SL:KB]
{
	// 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;
// [SL:KB] - Patch: World-MinimapOverlay | Checked: 2012-07-28 (Catznip-3.3)
	const S32 MAX_SIZE = 512;
// [/SL:KB]
//	const S32 MAX_SIZE = 256;
	S32 img_size = MIN_SIZE;
	while( (img_size*2 < square_size ) && (img_size < MAX_SIZE) )
	{
		img_size <<= 1;
	}

// [SL:KB] - Patch: World-MinimapOverlay | Checked: 2012-06-20 (Catznip-3.3)
	if( rawimagep.isNull() || (rawimagep->getWidth() != img_size) || (rawimagep->getHeight() != img_size) )
	{
		rawimagep = new LLImageRaw(img_size, img_size, 4);
		U8* data = rawimagep->getData();
		memset( data, 0, img_size * img_size * 4 );
		return true;
	}
	return false;
// [/SL:KB]
//	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;
}

// [SL:KB] - Patch: World-MinimapOverlay | Checked: 2012-06-20 (Catznip-3.3)
void LLNetMap::createObjectImage()
{
	if (createImage(mObjectRawImagep))
		mObjectImagep = LLViewerTextureManager::getLocalTexture( mObjectRawImagep.get(), FALSE);
	// <FS:Ansariel> Synchronize scale throughout instances
	//setScale(mScale);
	setScale(sScale);
	// </FS:Ansariel> Synchronize scale throughout instances
	mUpdateObjectImage = true;
}

void LLNetMap::createParcelImage()
{
	if (createImage(mParcelRawImagep))
		mParcelImagep = LLViewerTextureManager::getLocalTexture( mParcelRawImagep.get(), FALSE);
	mUpdateParcelImage = true;
}
// [/SL:KB]

BOOL LLNetMap::handleMouseDown( S32 x, S32 y, MASK mask )
{
	if (!(mask & MASK_SHIFT)) return FALSE;

	// 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);

			// auto centre
			mTargetPan.setZero();
		}
		gViewerWindow->showCursor();
		gFocusMgr.setMouseCapture(NULL);
		return TRUE;
	}
	return FALSE;
}

// [SL:KB] - Patch: World-MiniMap | Checked: 2012-07-08 (Catznip-3.3)
void LLNetMap::setAvatarProfileLabel(const LLUUID& av_id, const LLAvatarName& avName, const std::string& item_name)
{
	avatar_name_cache_connection_map_t::iterator it = mAvatarNameCacheConnections.find(av_id);
	if (it != mAvatarNameCacheConnections.end())
	{
		if (it->second.connected())
		{
			it->second.disconnect();
		}
		mAvatarNameCacheConnections.erase(it);
	}

	LLMenuItemGL* pItem = mPopupMenu->findChild<LLMenuItemGL>(item_name, TRUE /*recurse*/);
	if (pItem)
	{
		pItem->setLabel(avName.getCompleteName());
		pItem->getMenu()->arrange();
	}
}

void LLNetMap::handleOverlayToggle(const LLSD& sdParam)
{
	// Toggle the setting
	const std::string strControl = sdParam.asString();
	BOOL fCurValue = gSavedSettings.getBOOL(strControl);
	gSavedSettings.setBOOL(strControl, !fCurValue);

	// Force an overlay update
	mUpdateParcelImage = true;
}

void LLNetMap::handleShowProfile(const LLSD& sdParam) const
{
	const std::string strParam = sdParam.asString();
	if ("closest" == strParam)
	{
		LLAvatarActions::showProfile(mClosestAgentRightClick);
	}
	else if ("place" == strParam)
	{
		LLSD sdParams;
		sdParams["type"] = "remote_place";
		sdParams["x"] = mPosGlobalRightClick.mdV[VX];
		sdParams["y"] = mPosGlobalRightClick.mdV[VY];
		sdParams["z"] = mPosGlobalRightClick.mdV[VZ];

		FSFloaterPlaceDetails::showPlaceDetails(sdParams);
	}
}

bool LLNetMap::checkTextureType(const LLSD& sdParam) const
{
	const std::string strParam = sdParam.asString();

	bool fWorldMapTextures = gSavedSettings.getBOOL("MiniMapWorldMapTextures");
	if ("maptile" == strParam)
		return fWorldMapTextures;
	else if ("terrain" == strParam)
		return !fWorldMapTextures;
	return false;
}

void LLNetMap::handleTextureType(const LLSD& sdParam) const
{
	gSavedSettings.setBOOL("MiniMapWorldMapTextures", ("maptile" == sdParam.asString()));
}
// [/SL:KB]

BOOL LLNetMap::handleRightMouseDown(S32 x, S32 y, MASK mask)
{
	if (mPopupMenu)
	{
// [SL:KB] - Patch: World-MiniMap | Checked: 2012-07-08 (Catznip-3.3)
		mClosestAgentRightClick = mClosestAgentToCursor;
		mClosestAgentsRightClick = mClosestAgentsToCursor;
		mPosGlobalRightClick = viewPosToGlobal(x, y);

		mPopupMenu->setItemVisible("Add to Set Multiple", mClosestAgentsToCursor.size() > 1);
		mPopupMenu->setItemVisible("More Options", mClosestAgentsToCursor.size() == 1);
		mPopupMenu->setItemVisible("View Profile", mClosestAgentsToCursor.size() == 1);

		bool can_show_names = !RlvActions::hasBehaviour(RLV_BHVR_SHOWNAMES);
		mPopupMenu->setItemEnabled("Add to Set Multiple", can_show_names);
		mPopupMenu->setItemEnabled("More Options", can_show_names);
		mPopupMenu->setItemEnabled("View Profile", can_show_names);

		LLMenuItemBranchGL* pProfilesMenu = mPopupMenu->getChild<LLMenuItemBranchGL>("View Profiles");
		if (pProfilesMenu)
		{
			pProfilesMenu->setVisible(mClosestAgentsToCursor.size() > 1);
			pProfilesMenu->setEnabled(can_show_names);

			pProfilesMenu->getBranch()->empty();
			for (uuid_vec_t::const_iterator itAgent = mClosestAgentsToCursor.begin(); itAgent != mClosestAgentsToCursor.end(); ++itAgent)
			{
				LLMenuItemCallGL::Params p;
				p.name = llformat("Profile Item %d", itAgent - mClosestAgentsToCursor.begin());

				LLAvatarName avName; const LLUUID& idAgent = *itAgent;
				if (LLAvatarNameCache::get(idAgent, &avName))
				{
					p.label = avName.getCompleteName();
				}
				else
				{
					p.label = LLTrans::getString("LoadingData");
					avatar_name_cache_connection_map_t::iterator it = mAvatarNameCacheConnections.find(idAgent);
					if (it != mAvatarNameCacheConnections.end())
					{
						if (it->second.connected())
						{
							it->second.disconnect();
						}
						mAvatarNameCacheConnections.erase(it);
					}
					mAvatarNameCacheConnections[idAgent] = LLAvatarNameCache::get(idAgent, boost::bind(&LLNetMap::setAvatarProfileLabel, this, _1, _2, p.name.getValue()));
				}
				p.on_click.function = boost::bind(&LLAvatarActions::showProfile, _2);
				p.on_click.parameter = idAgent;

				LLMenuItemCallGL* pMenuItem  = LLUICtrlFactory::create<LLMenuItemCallGL>(p);
				if (pMenuItem)
					pProfilesMenu->getBranch()->addChild(pMenuItem);
			}
		}
		mPopupMenu->setItemVisible("Cam", LLAvatarActions::canZoomIn(mClosestAgentToCursor));
		mPopupMenu->setItemVisible("MarkAvatar", mClosestAgentToCursor.notNull());
		mPopupMenu->setItemVisible("Start Tracking", mClosestAgentToCursor.notNull());
		mPopupMenu->setItemVisible("Profile Separator", (mClosestAgentsToCursor.size() >= 1 || mClosestAgentToCursor.notNull()));
		mPopupMenu->setItemEnabled("Place Profile", RlvActions::canShowLocation());
		mPopupMenu->setItemEnabled("World Map", !RlvActions::hasBehaviour(RLV_BHVR_SHOWWORLDMAP));

// [/SL:KB]
		mPopupMenu->buildDrawLabels();
		mPopupMenu->updateParent(LLMenuGL::sMenuContainer);
// [SL:KB] - Patch: World-MiniMap | Checked: 2012-07-08 (Catznip-3.3)
		mPopupMenu->setItemVisible("Stop Tracking", LLTracker::isTracking(0));
		mPopupMenu->setItemVisible("Stop Tracking Separator", LLTracker::isTracking(0));
// [/SL:KB]
//		mPopupMenu->setItemEnabled("Stop Tracking", LLTracker::isTracking(0));
		LLMenuGL::showPopup(this, mPopupMenu, 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);

	// <FS:Ansariel> Synchronize double click handling throughout instances
	//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");
	//}
	performDoubleClickAction(pos_global);
	// </FS:Ansariel> Synchronize double click handling throughout instances

	return TRUE;
}

// 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, so 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;
			mTargetPan = mCurPan;

			gViewerWindow->moveCursorToCenter();
		}

		// Doesn't really matter, cursor should be hidden
		gViewerWindow->setCursor( UI_CURSOR_TOOLPAN );
	}
	else
	{
		if (mask & MASK_SHIFT)
		{
			// If shift is held, change the cursor to hint that the map can be dragged
			gViewerWindow->setCursor( UI_CURSOR_TOOLPAN );
		}
		else
		{
			gViewerWindow->setCursor( UI_CURSOR_CROSS );
		}
	}

	return TRUE;
}

void LLNetMap::handleZoom(const LLSD& userdata)
{
	std::string level = userdata.asString();
	
	F32 scale = 0.0f;
// [SL:KB] - Patch: World-MinimapZoom | Checked: 2012-08-15 (Catznip-3.3)
	//if (level == "close")
	//	scale = 2048.f;
	//else if (level == "medium")
	//	scale = 512.f;
	//else if (level == "far")
	//	scale = 128.f;
// [/Sl:KB]
	if (level == std::string("default"))
	{
		LLControlVariable *pvar = gSavedSettings.getControl("MiniMapScale");
		if(pvar)
		{
			pvar->resetToDefault();
			scale = gSavedSettings.getF32("MiniMapScale");
		}
	}
	else if (level == std::string("close"))
		scale = LLNetMap::MAP_SCALE_MAX;
	else if (level == std::string("medium"))
		scale = LLNetMap::MAP_SCALE_MID;
	else if (level == std::string("far"))
		scale = LLNetMap::MAP_SCALE_MIN;
	if (scale != 0.0f)
	{
		setScale(scale);
	}
}

// <FS:Ansariel> Mark avatar feature
void LLNetMap::handleMark(const LLSD& userdata)
{
	setAvatarMarkColors(mClosestAgentsRightClick, userdata);
}

void LLNetMap::handleClearMark()
{
	clearAvatarMarkColors(mClosestAgentsRightClick);
}

void LLNetMap::handleClearMarks()
{
	clearAvatarMarkColors();
}

// static
bool LLNetMap::getAvatarMarkColor(const LLUUID& avatar_id, LLColor4& color)
{
	avatar_marks_map_t::iterator found = sAvatarMarksMap.find(avatar_id);
	if (found != sAvatarMarksMap.end())
	{
		color = found->second;
		return true;
	}
	return false;
}

// static
void LLNetMap::setAvatarMarkColor(const LLUUID& avatar_id, const LLSD& color)
{
	uuid_vec_t ids;
	ids.push_back(avatar_id);
	setAvatarMarkColors(ids, color);
}

// static
void LLNetMap::setAvatarMarkColors(const uuid_vec_t& avatar_ids, const LLSD& color)
{
	// Use the name as color definition name from colors.xml
	LLColor4 mark_color = LLUIColorTable::instance().getColor(color.asString(), LLColor4::green);

	for (uuid_vec_t::const_iterator it = avatar_ids.begin(); it != avatar_ids.end(); ++it)
	{
		sAvatarMarksMap[*it] = mark_color;
	}
}

// static
void LLNetMap::clearAvatarMarkColor(const LLUUID& avatar_id)
{
	uuid_vec_t ids;
	ids.push_back(avatar_id);
	clearAvatarMarkColors(ids);
}

// static
void LLNetMap::clearAvatarMarkColors(const uuid_vec_t& avatar_ids)
{
	for (uuid_vec_t::const_iterator it = avatar_ids.begin(); it != avatar_ids.end(); ++it)
	{
		avatar_marks_map_t::iterator found = sAvatarMarksMap.find(*it);
		if (found != sAvatarMarksMap.end())
		{
			sAvatarMarksMap.erase(found);
		}
	}
}

// static
void LLNetMap::clearAvatarMarkColors()
{
	sAvatarMarksMap.clear();
}

// static
LLColor4 LLNetMap::getAvatarColor(const LLUUID& avatar_id)
{
	static LLUIColor map_avatar_color = LLUIColorTable::instance().getColor("MapAvatarColor", LLColor4::white);
	LLColor4 color = map_avatar_color;

	LGGContactSets& cs_instance = LGGContactSets::instance();

	// Color "special" avatars with special colors (Friends, muted, Lindens, etc)
	color = cs_instance.colorize(avatar_id, color, LGG_CS_MINIMAP);

	// Color based on contact sets prefs
	if (cs_instance.hasFriendColorThatShouldShow(avatar_id, LGG_CS_MINIMAP))
	{
		color = cs_instance.getFriendColor(avatar_id);
	}

	// Mark Avatars with special colors
	avatar_marks_map_t::iterator found = sAvatarMarksMap.find(avatar_id);
	if (found != sAvatarMarksMap.end())
	{
		color = found->second;
	}

	return color;
}
//</FS:Ansariel>

void LLNetMap::handleCam()
{
	if (LLAvatarActions::canZoomIn(mClosestAgentRightClick))
	{
		LLAvatarActions::zoomIn(mClosestAgentRightClick);
	}
	else
	{
		report_to_nearby_chat(LLTrans::getString("minimap_no_focus"));
	}
}

// <FS:Ansariel> Avatar tracking feature
void LLNetMap::handleStartTracking()
{
	if (mClosestAgentRightClick.notNull())
	{
		LLAvatarActions::track(mClosestAgentRightClick);
	}
}
// </FS:Ansariel> Avatar tracking feature

void LLNetMap::handleStopTracking (const LLSD& userdata)
{
	if (mPopupMenu)
	{
		// <FS:Ansariel> Hide tracking option instead of disabling
		//mPopupMenu->setItemEnabled ("Stop Tracking", false);
		mPopupMenu->setItemVisible ("Stop Tracking", false);
		// </FS:Ansariel>
		LLTracker::stopTracking (LLTracker::isTracking(NULL));
	}
}

// <FS:Ansariel> Synchronize tooltips throughout instances
// static
void LLNetMap::updateToolTipMsg()
{
	S32 fsNetMapDoubleClickAction = gSavedSettings.getS32("FSNetMapDoubleClickAction");
	switch (fsNetMapDoubleClickAction)
	{
		case 1:
			LLNetMap::setToolTipMsg(LLTrans::getString("NetMapDoubleClickShowWorldMapToolTipMsg"));
			break;
		case 2:
			LLNetMap::setToolTipMsg(LLTrans::getString("NetMapDoubleClickTeleportToolTipMsg"));
			break;
		default:
			LLNetMap::setToolTipMsg(LLTrans::getString("NetMapDoubleClickNoActionToolTipMsg"));
			break;
	}
}
// </FS:Ansariel> Synchronize tooltips throughout instances

// <FS:Ansariel> Synchronize double click handling throughout instances
void LLNetMap::performDoubleClickAction(LLVector3d pos_global)
{
	S32 fsNetMapDoubleClickAction = gSavedSettings.getS32("FSNetMapDoubleClickAction");
	
	// 1 = Double click show world map
	// 2 = Double click teleport
	if (fsNetMapDoubleClickAction == 1 || fsNetMapDoubleClickAction == 2)
	{
		// 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);
			}
		}
	}

	switch (fsNetMapDoubleClickAction)
	{
		case 1:
			LLFloaterReg::showInstance("world_map");
			break;
		case 2:
			gAgent.teleportViaLocationLookAt(pos_global);
			break;
		default:
			break;
	}
}
// </FS:Ansariel> Synchronize double click handling throughout instances


bool LLNetMap::canAddFriend()
{
	return FSCommon::checkIsActionEnabled(mClosestAgentRightClick, FS_RGSTR_ACT_ADD_FRIEND);
}

bool LLNetMap::canRemoveFriend()
{
	return FSCommon::checkIsActionEnabled(mClosestAgentRightClick, FS_RGSTR_ACT_REMOVE_FRIEND);
}

bool LLNetMap::canCall()
{
	return LLAvatarActions::canCall();
}

bool LLNetMap::canMap()
{
	return (LLAvatarTracker::instance().isBuddyOnline(mClosestAgentRightClick) && is_agent_mappable(mClosestAgentRightClick));
}

bool LLNetMap::canShare()
{
	return (!RlvActions::hasBehaviour(RLV_BHVR_SHOWINV));
}

bool LLNetMap::canOfferTeleport()
{
	return FSCommon::checkIsActionEnabled(mClosestAgentRightClick, FS_RGSTR_ACT_OFFER_TELEPORT);
}

// <FS:Ansariel> Extra request teleport
bool LLNetMap::canRequestTeleport()
{
	return FSCommon::checkIsActionEnabled(mClosestAgentRightClick, FS_RGSTR_ACT_REQUEST_TELEPORT);
}
// </FS:Ansariel>

bool LLNetMap::canBlock()
{
	return LLAvatarActions::canBlock(mClosestAgentRightClick);
}

bool LLNetMap::canFreezeEject()
{
	return LLAvatarActions::canLandFreezeOrEject(mClosestAgentRightClick);
}

bool LLNetMap::canKickTeleportHome()
{
	return LLAvatarActions::canEstateKickOrTeleportHome(mClosestAgentRightClick);
}

bool LLNetMap::isBlocked()
{
	return LLAvatarActions::isBlocked(mClosestAgentRightClick);
}

void LLNetMap::handleAddFriend()
{
	LLAvatarActions::requestFriendshipDialog(mClosestAgentRightClick);
}

void LLNetMap::handleAddToContactSet()
{
	LLAvatarActions::addToContactSet(mClosestAgentsRightClick);
}

void LLNetMap::handleRemoveFriend()
{
	LLAvatarActions::removeFriendDialog(mClosestAgentRightClick);
}

void LLNetMap::handleIM()
{
	LLAvatarActions::startIM(mClosestAgentRightClick);
}

void LLNetMap::handleCall()
{
	LLAvatarActions::startCall(mClosestAgentRightClick);
}

void LLNetMap::handleMap()
{
	LLAvatarActions::showOnMap(mClosestAgentRightClick);
}

void LLNetMap::handleShare()
{
	LLAvatarActions::share(mClosestAgentRightClick);
}

void LLNetMap::handlePay()
{
	LLAvatarActions::pay(mClosestAgentRightClick);
}

void LLNetMap::handleOfferTeleport()
{
	LLAvatarActions::offerTeleport(mClosestAgentRightClick);
}

void LLNetMap::handleRequestTeleport()
{
	LLAvatarActions::teleportRequest(mClosestAgentRightClick);
}

void LLNetMap::handleTeleportToAvatar()
{
	LLAvatarActions::teleportTo(mClosestAgentRightClick);
}

void LLNetMap::handleGroupInvite()
{
	LLAvatarActions::inviteToGroup(mClosestAgentRightClick);
}

void LLNetMap::handleGetScriptInfo()
{
	LLAvatarActions::getScriptInfo(mClosestAgentRightClick);
}

void LLNetMap::handleBlockUnblock()
{
	LLAvatarActions::toggleBlock(mClosestAgentRightClick);
}

void LLNetMap::handleReport()
{
	LLAvatarActions::report(mClosestAgentRightClick);
}

void LLNetMap::handleFreeze()
{
	LLAvatarActions::landFreeze(mClosestAgentRightClick);
}

void LLNetMap::handleEject()
{
	LLAvatarActions::landEject(mClosestAgentRightClick);
}

void LLNetMap::handleKick()
{
	LLAvatarActions::estateKick(mClosestAgentRightClick);
}

void LLNetMap::handleTeleportHome()
{
	LLAvatarActions::estateTeleportHome(mClosestAgentRightClick);
}

void LLNetMap::handleEstateBan()
{
	LLAvatarActions::estateBan(mClosestAgentRightClick);
}

void LLNetMap::handleDerender(bool permanent)
{
	LLAvatarActions::derender(mClosestAgentRightClick, permanent);
}
