From f5e386527c4a74e6e5733f4f1cfa55873851257e Mon Sep 17 00:00:00 2001
From: Steven Bennetts <steve@lindenlab.com>
Date: Fri, 4 Apr 2008 01:22:03 +0000
Subject: [PATCH] QAR-427 merge 3dconnex-merge -r 84010 : 84039 -> release

---
 indra/llui/llfloater.cpp                |  13 +-
 indra/llwindow/llwindow.cpp             |  41 +-
 indra/llwindow/llwindow.h               |   8 +-
 indra/llwindow/llwindowwin32.cpp        | 139 +---
 indra/newview/app_settings/settings.xml | 366 ++++++++++-
 indra/newview/llagent.cpp               |  38 +-
 indra/newview/llagent.h                 |   5 +-
 indra/newview/llappviewer.cpp           |  30 +-
 indra/newview/llfloaterjoystick.cpp     | 333 ++++++++++
 indra/newview/llfloaterjoystick.h       |  78 +++
 indra/newview/llfloatertools.cpp        |   4 +-
 indra/newview/llmanip.cpp               |   2 +-
 indra/newview/llmanip.h                 |   2 +-
 indra/newview/llselectmgr.cpp           | 195 ++++++
 indra/newview/llselectmgr.h             |   6 +-
 indra/newview/lltoolcomp.cpp            |   2 +-
 indra/newview/lltoolmgr.cpp             |  15 +
 indra/newview/lltoolmgr.h               |   3 +
 indra/newview/llviewercamera.cpp        |   9 +
 indra/newview/llviewercontrol.cpp       |  54 +-
 indra/newview/llviewerjoystick.cpp      | 834 ++++++++++++++++++++----
 indra/newview/llviewerjoystick.h        |  67 +-
 indra/newview/llviewermenu.cpp          |  82 ++-
 indra/newview/llviewermenu.h            |   1 +
 indra/newview/llviewerwindow.cpp        |  36 +
 indra/newview/llviewerwindow.h          |   4 +
 indra/newview/viewer_manifest.py        |   3 +
 27 files changed, 2004 insertions(+), 366 deletions(-)
 create mode 100644 indra/newview/llfloaterjoystick.cpp
 create mode 100644 indra/newview/llfloaterjoystick.h

diff --git a/indra/llui/llfloater.cpp b/indra/llui/llfloater.cpp
index b69ac30e9c..1eb3d169a5 100644
--- a/indra/llui/llfloater.cpp
+++ b/indra/llui/llfloater.cpp
@@ -656,7 +656,8 @@ void LLFloater::setTitle( const LLString& title )
 	{
 		return;
 	}
-	mDragHandle->setTitle( title );
+	if (mDragHandle)
+		mDragHandle->setTitle( title );
 }
 
 const LLString& LLFloater::getTitle() const
@@ -934,7 +935,8 @@ void LLFloater::setIsChrome(BOOL is_chrome)
 	}
 	
 	// no titles displayed on "chrome" floaters
-	mDragHandle->setTitleVisible(!is_chrome);
+	if (mDragHandle)
+		mDragHandle->setTitleVisible(!is_chrome);
 	
 	LLPanel::setIsChrome(is_chrome);
 }
@@ -945,7 +947,8 @@ void LLFloater::setForeground(BOOL front)
 	if (front != mForeground)
 	{
 		mForeground = front;
-		mDragHandle->setForeground( front );
+		if (mDragHandle)
+			mDragHandle->setForeground( front );
 
 		if (!front)
 		{
@@ -1580,8 +1583,8 @@ void LLFloater::updateButtons()
 			mButtons[i]->setVisible(FALSE);
 		}
 	}
-
-	mDragHandle->setMaxTitleWidth(getRect().getWidth() - (button_count * (LLFLOATER_CLOSE_BOX_SIZE + 1)));
+	if (mDragHandle)
+		mDragHandle->setMaxTitleWidth(getRect().getWidth() - (button_count * (LLFLOATER_CLOSE_BOX_SIZE + 1)));
 }
 
 void LLFloater::buildButtons()
diff --git a/indra/llwindow/llwindow.cpp b/indra/llwindow/llwindow.cpp
index 37f089e8be..c640a83328 100644
--- a/indra/llwindow/llwindow.cpp
+++ b/indra/llwindow/llwindow.cpp
@@ -143,6 +143,11 @@ BOOL LLWindowCallbacks::handleActivate(LLWindow *window, BOOL activated)
 	return FALSE;
 }
 
+BOOL LLWindowCallbacks::handleActivateApp(LLWindow *window, BOOL activating)
+{
+	return FALSE;
+}
+
 void LLWindowCallbacks::handleMouseMove(LLWindow *window, const LLCoordGL pos, MASK mask)
 {
 }
@@ -190,6 +195,15 @@ void LLWindowCallbacks::handleDataCopy(LLWindow *window, S32 data_type, void *da
 {
 }
 
+BOOL LLWindowCallbacks::handleTimerEvent(LLWindow *window)
+{
+	return FALSE;
+}
+
+BOOL LLWindowCallbacks::handleDeviceChange(LLWindow *window)
+{
+	return FALSE;
+}
 
 S32 OSMessageBox(const char* text, const char* caption, U32 type)
 {
@@ -247,15 +261,6 @@ LLWindow::LLWindow(BOOL fullscreen, U32 flags)
 	  mFlags(flags),
 	  mHighSurrogate(0)
 {
-	for (U32 i = 0; i < 8; i++)
-	{
-		mJoyAxis[i] = 0;
-	}
-
-	for (U32 i = 0; i < 16; i++)
-	{
-		mJoyButtonState[i] = 0;
-	}
 }
 	
 // virtual
@@ -273,24 +278,6 @@ void LLWindow::decBusyCount()
 	}
 }
 
-F32 LLWindow::getJoystickAxis(U32 axis)
-{
-	if (axis < 8)
-	{
-		return mJoyAxis[axis];
-	}
-	return 0.f;
-}
-
-U8 LLWindow::getJoystickButton(U32 button)
-{
-	if (button < 16)
-	{
-		return mJoyButtonState[button];
-	}
-	return 0;
-}
-
 void LLWindow::setCallbacks(LLWindowCallbacks *callbacks)
 {
 	mCallbacks = callbacks;
diff --git a/indra/llwindow/llwindow.h b/indra/llwindow/llwindow.h
index 6b9414a301..81adf8b13a 100644
--- a/indra/llwindow/llwindow.h
+++ b/indra/llwindow/llwindow.h
@@ -107,6 +107,7 @@ public:
 	virtual BOOL handleMiddleMouseDown(LLWindow *window,  LLCoordGL pos, MASK mask);
 	virtual BOOL handleMiddleMouseUp(LLWindow *window,  LLCoordGL pos, MASK mask);
 	virtual BOOL handleActivate(LLWindow *window, BOOL activated);
+	virtual BOOL handleActivateApp(LLWindow *window, BOOL activating);
 	virtual void handleMouseMove(LLWindow *window,  LLCoordGL pos, MASK mask);
 	virtual void handleScrollWheel(LLWindow *window,  S32 clicks);
 	virtual void handleResize(LLWindow *window,  S32 width,  S32 height);
@@ -118,6 +119,8 @@ public:
 	virtual void handleWindowBlock(LLWindow *window);							// window is taking over CPU for a while
 	virtual void handleWindowUnblock(LLWindow *window);							// window coming back after taking over CPU for a while
 	virtual void handleDataCopy(LLWindow *window, S32 data_type, void *data);
+	virtual BOOL handleTimerEvent(LLWindow *window);
+	virtual BOOL handleDeviceChange(LLWindow *window);
 };
 
 // Refer to llwindow_test in test/common/llwindow for usage example
@@ -214,9 +217,6 @@ public:
 	virtual F32 getPixelAspectRatio() = 0;
 	virtual void setNativeAspectRatio(F32 aspect) = 0;
 	
-	F32 getJoystickAxis(U32 axis);
-	U8 getJoystickButton(U32 button);
-
 	void setCallbacks(LLWindowCallbacks *callbacks);
 
 	virtual void beforeDialog() {};	// prepare to put up an OS dialog (if special measures are required, such as in fullscreen mode)
@@ -260,8 +260,6 @@ protected:
 	ESwapMethod mSwapMethod;
 	BOOL		mHideCursorPermanent;
 	U32			mFlags;
-	F32			mJoyAxis[8]; 
-	U8			mJoyButtonState[16];
 	U16			mHighSurrogate;
 
  	// Handle a UTF-16 encoding unit received from keyboard.
diff --git a/indra/llwindow/llwindowwin32.cpp b/indra/llwindow/llwindowwin32.cpp
index f05397b058..61daacb567 100644
--- a/indra/llwindow/llwindowwin32.cpp
+++ b/indra/llwindow/llwindowwin32.cpp
@@ -46,7 +46,7 @@
 #define DIRECTINPUT_VERSION 0x0800
 
 #include <dinput.h>
-
+#include <Dbt.h.>
 
 #include "llkeyboardwin32.h"
 #include "llerror.h"
@@ -359,14 +359,6 @@ LLWinImm::~LLWinImm()
 }
 
 
-LPDIRECTINPUT8       g_pDI              = NULL;         
-LPDIRECTINPUTDEVICE8 g_pJoystick        = NULL;     
-BOOL CALLBACK EnumJoysticksCallback( const DIDEVICEINSTANCE* pdidInstance,
-									VOID* pContext );
-BOOL CALLBACK EnumObjectsCallback( const DIDEVICEOBJECTINSTANCE* pdidoi,
-								  VOID* pContext );
-
-
 LLWindowWin32::LLWindowWin32(char *title, char *name, S32 x, S32 y, S32 width,
 							 S32 height, U32 flags, 
 							 BOOL fullscreen, BOOL clearBg,
@@ -666,38 +658,6 @@ LLWindowWin32::LLWindowWin32(char *title, char *name, S32 x, S32 y, S32 width,
 	// Initialize (boot strap) the Language text input management,
 	// based on the system's (or user's) default settings.
 	allowLanguageTextInput(NULL, FALSE);
-}
-
-void LLWindowWin32::initInputDevices()
-{
-	// Direct Input
-	HRESULT hr;
-
-	if( FAILED( hr = DirectInput8Create( GetModuleHandle(NULL), DIRECTINPUT_VERSION, 
-		IID_IDirectInput8, (VOID**)&g_pDI, NULL ) ) )
-	{
-		llwarns << "Direct8InputCreate failed!" << llendl;
-	}
-	else
-	{
-		while(1)
-		{
-			// Look for a simple joystick we can use for this sample program.
-			if (FAILED( hr = g_pDI->EnumDevices( DI8DEVCLASS_GAMECTRL, 
-				EnumJoysticksCallback,
-				NULL, DIEDFL_ATTACHEDONLY ) ) )
-				break;
-			if (!g_pJoystick)
-				break;
-			if( FAILED( hr = g_pJoystick->SetDataFormat( &c_dfDIJoystick ) ) )
-				break;
-			if( FAILED( hr = g_pJoystick->EnumObjects( EnumObjectsCallback, 
-				(VOID*)mWindowHandle, DIDFT_ALL ) ) )
-				break;
-			g_pJoystick->Acquire();
-			break;
-		}
-	}
 
 	SetTimer( mWindowHandle, 0, 1000 / 30, NULL ); // 30 fps timer
 }
@@ -1438,8 +1398,6 @@ BOOL LLWindowWin32::switchContext(BOOL fullscreen, const LLCoordScreen &size, BO
 	SetWindowLong(mWindowHandle, GWL_USERDATA, (U32)this);
 	show();
 
-	initInputDevices();
-
 	// ok to post quit messages now
 	mPostQuit = TRUE;
 	return TRUE;
@@ -1730,7 +1688,22 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_
 			S32		update_height;
 
 		case WM_TIMER:
-			window_imp->updateJoystick( );
+			window_imp->mCallbacks->handleTimerEvent(window_imp);
+			break;
+
+		case WM_DEVICECHANGE:
+			if (gDebugWindowProc)
+			{
+				llinfos << "  WM_DEVICECHANGE: wParam=" << w_param 
+						<< "; lParam=" << l_param << llendl;
+			}
+			if (w_param == DBT_DEVNODES_CHANGED || w_param == DBT_DEVICEARRIVAL)
+			{
+				if (window_imp->mCallbacks->handleDeviceChange(window_imp))
+				{
+					return 0;
+				}
+			}
 			break;
 
 		case WM_PAINT:
@@ -1795,6 +1768,9 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_
 						window_imp->resetDisplayResolution();
 					}
 				}
+
+				window_imp->mCallbacks->handleActivateApp(window_imp, activating);
+
 				break;
 			}
 
@@ -2831,81 +2807,6 @@ void LLWindowWin32::swapBuffers()
 }
 
 
-BOOL CALLBACK EnumJoysticksCallback( const DIDEVICEINSTANCE* pdidInstance,
-									VOID* pContext )
-{
-	HRESULT hr;
-
-	// Obtain an interface to the enumerated joystick.
-	hr = g_pDI->CreateDevice( pdidInstance->guidInstance, &g_pJoystick, NULL );
-
-	// If it failed, then we can't use this joystick. (Maybe the user unplugged
-	// it while we were in the middle of enumerating it.)
-	if( FAILED(hr) ) 
-		return DIENUM_CONTINUE;
-
-	// Stop enumeration. Note: we're just taking the first joystick we get. You
-	// could store all the enumerated joysticks and let the user pick.
-	return DIENUM_STOP;
-}
-
-BOOL CALLBACK EnumObjectsCallback( const DIDEVICEOBJECTINSTANCE* pdidoi,
-								  VOID* pContext )
-{
-	if( pdidoi->dwType & DIDFT_AXIS )
-	{
-		DIPROPRANGE diprg; 
-		diprg.diph.dwSize       = sizeof(DIPROPRANGE); 
-		diprg.diph.dwHeaderSize = sizeof(DIPROPHEADER); 
-		diprg.diph.dwHow        = DIPH_BYID; 
-		diprg.diph.dwObj        = pdidoi->dwType; // Specify the enumerated axis
-		diprg.lMin              = -1000; 
-		diprg.lMax              = +1000; 
-
-		// Set the range for the axis
-		if( FAILED( g_pJoystick->SetProperty( DIPROP_RANGE, &diprg.diph ) ) ) 
-			return DIENUM_STOP;
-
-	}
-	return DIENUM_CONTINUE;
-}
-
-void LLWindowWin32::updateJoystick( )
-{
-	HRESULT hr;
-	DIJOYSTATE js;           // DInput joystick state
-
-	if (!g_pJoystick)
-		return;
-	hr = g_pJoystick->Poll();
-	if ( hr == DIERR_INPUTLOST )
-	{
-		hr = g_pJoystick->Acquire();
-		return;
-	}
-	else if ( FAILED(hr) )
-		return;
-
-	// Get the input's device state
-	if( FAILED( hr = g_pJoystick->GetDeviceState( sizeof(DIJOYSTATE), &js ) ) )
-		return; // The device should have been acquired during the Poll()
-
-	mJoyAxis[0] = js.lX/1000.f;
-	mJoyAxis[1] = js.lY/1000.f;
-	mJoyAxis[2] = js.lZ/1000.f;
-	mJoyAxis[3] = js.lRx/1000.f;
-	mJoyAxis[4] = js.lRy/1000.f;
-	mJoyAxis[5] = js.lRz/1000.f;
-	mJoyAxis[6] = js.rglSlider[0]/1000.f;
-	mJoyAxis[7] = js.rglSlider[1]/1000.f;
-
-	for (U32 i = 0; i < 16; i++)
-	{
-		mJoyButtonState[i] = js.rgbButtons[i];
-	}
-}
-
-
 //
 // LLSplashScreenImp
 //
diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml
index 1a26783623..2754b27234 100644
--- a/indra/newview/app_settings/settings.xml
+++ b/indra/newview/app_settings/settings.xml
@@ -2791,7 +2791,7 @@
         <key>Value</key>
             <integer>0</integer>
         </map>
-    <key>FlycamAutoLeveling</key>
+    <key>AutoLeveling</key>
         <map>
         <key>Comment</key>
             <string>Keep Flycam level.</string>
@@ -2802,7 +2802,40 @@
         <key>Value</key>
             <integer>1</integer>
         </map>
-    <key>FlycamAxis0</key>
+    <key>Cursor3D</key>
+        <map>
+        <key>Comment</key>
+            <string>Tread Joystick values as absolute positions (not deltas).</string>
+        <key>Persist</key>
+            <integer>1</integer>
+        <key>Type</key>
+            <string>Boolean</string>
+        <key>Value</key>
+            <integer>1</integer>
+        </map>
+    <key>ZoomDirect</key>
+        <map>
+        <key>Comment</key>
+            <string>Map Joystick zoom axis directly to camera zoom.</string>
+        <key>Persist</key>
+            <integer>1</integer>
+        <key>Type</key>
+            <string>Boolean</string>
+        <key>Value</key>
+            <integer>0</integer>
+        </map>
+    <key>JoystickInitialized</key>
+        <map>
+        <key>Comment</key>
+            <string>Whether or not a joystick has been detected and initiailized.</string>
+        <key>Persist</key>
+            <integer>1</integer>
+        <key>Type</key>
+            <string>String</string>
+        <key>Value</key>
+            <string></string>
+        </map>
+    <key>JoystickAxis0</key>
         <map>
         <key>Comment</key>
             <string>Flycam hardware axis mapping for internal axis 0 ([0, 5]).</string>
@@ -2813,7 +2846,7 @@
         <key>Value</key>
             <integer>0</integer>
         </map>
-    <key>FlycamAxis1</key>
+    <key>JoystickAxis1</key>
         <map>
         <key>Comment</key>
             <string>Flycam hardware axis mapping for internal axis 1 ([0, 5]).</string>
@@ -2822,9 +2855,9 @@
         <key>Type</key>
             <string>S32</string>
         <key>Value</key>
-            <integer>1</integer>
+            <integer>2</integer>
         </map>
-    <key>FlycamAxis2</key>
+    <key>JoystickAxis2</key>
         <map>
         <key>Comment</key>
             <string>Flycam hardware axis mapping for internal axis 2 ([0, 5]).</string>
@@ -2833,9 +2866,9 @@
         <key>Type</key>
             <string>S32</string>
         <key>Value</key>
-            <integer>2</integer>
+            <integer>1</integer>
         </map>
-    <key>FlycamAxis3</key>
+    <key>JoystickAxis3</key>
         <map>
         <key>Comment</key>
             <string>Flycam hardware axis mapping for internal axis 3 ([0, 5]).</string>
@@ -2844,9 +2877,9 @@
         <key>Type</key>
             <string>S32</string>
         <key>Value</key>
-            <integer>3</integer>
+            <integer>5</integer>
         </map>
-    <key>FlycamAxis4</key>
+    <key>JoystickAxis4</key>
         <map>
         <key>Comment</key>
             <string>Flycam hardware axis mapping for internal axis 4 ([0, 5]).</string>
@@ -2857,7 +2890,7 @@
         <key>Value</key>
             <integer>4</integer>
         </map>
-    <key>FlycamAxis5</key>
+    <key>JoystickAxis5</key>
         <map>
         <key>Comment</key>
             <string>Flycam hardware axis mapping for internal axis 5 ([0, 5]).</string>
@@ -2866,9 +2899,9 @@
         <key>Type</key>
             <string>S32</string>
         <key>Value</key>
-            <integer>5</integer>
+            <integer>3</integer>
         </map>
-    <key>FlycamAxis6</key>
+    <key>JoystickAxis6</key>
         <map>
         <key>Comment</key>
             <string>Flycam hardware axis mapping for internal axis 6 ([0, 5]).</string>
@@ -2888,7 +2921,7 @@
         <key>Type</key>
             <string>F32</string>
         <key>Value</key>
-            <real>0.1000000014901161193847656</real>
+            <real>0.1</real>
         </map>
     <key>FlycamAxisDeadZone1</key>
         <map>
@@ -2899,7 +2932,7 @@
         <key>Type</key>
             <string>F32</string>
         <key>Value</key>
-            <real>0.1000000014901161193847656</real>
+            <real>0.1</real>
         </map>
     <key>FlycamAxisDeadZone2</key>
         <map>
@@ -2910,7 +2943,7 @@
         <key>Type</key>
             <string>F32</string>
         <key>Value</key>
-            <real>0.1000000014901161193847656</real>
+            <real>0.1</real>
         </map>
     <key>FlycamAxisDeadZone3</key>
         <map>
@@ -2921,7 +2954,7 @@
         <key>Type</key>
             <string>F32</string>
         <key>Value</key>
-            <real>0.1000000014901161193847656</real>
+            <real>0.1</real>
         </map>
     <key>FlycamAxisDeadZone4</key>
         <map>
@@ -2932,7 +2965,7 @@
         <key>Type</key>
             <string>F32</string>
         <key>Value</key>
-            <real>0.1000000014901161193847656</real>
+            <real>0.1</real>
         </map>
     <key>FlycamAxisDeadZone5</key>
         <map>
@@ -2943,7 +2976,7 @@
         <key>Type</key>
             <string>F32</string>
         <key>Value</key>
-            <real>0.1000000014901161193847656</real>
+            <real>0.1</real>
         </map>
     <key>FlycamAxisDeadZone6</key>
         <map>
@@ -2954,7 +2987,7 @@
         <key>Type</key>
             <string>F32</string>
         <key>Value</key>
-            <real>0.1000000014901161193847656</real>
+            <real>0.1</real>
         </map>
     <key>FlycamAxisScale0</key>
         <map>
@@ -6016,7 +6049,7 @@
         <key>Type</key>
             <string>F32</string>
         <key>Value</key>
-            <real>0.1000000014901161193847656</real>
+            <real>0.1</real>
         </map>
     <key>SelectionHighlightThickness</key>
         <map>
@@ -6049,7 +6082,7 @@
         <key>Type</key>
             <string>F32</string>
         <key>Value</key>
-            <real>0.1000000014901161193847656</real>
+            <real>0.1</real>
         </map>
     <key>SelectionHighlightVAnim</key>
         <map>
@@ -6606,7 +6639,7 @@
             <array>
                 <real>1</real>
                 <real>0</real>
-                <real>0.1000000014901161193847656</real>
+                <real>0.1</real>
             </array>
         </map>
 	<key>SkyUseClassicClouds</key>
@@ -9748,5 +9781,294 @@
 		<key>Value</key>
 			<real>1</real>
 		</map>
+
+    <key>AvatarAxisDeadZone0</key>
+        <map>
+        <key>Comment</key>
+            <string>Avatar axis 0 dead zone.</string>
+        <key>Persist</key>
+            <integer>1</integer>
+        <key>Type</key>
+            <string>F32</string>
+        <key>Value</key>
+            <real>0.1</real>
+        </map>
+    <key>AvatarAxisDeadZone1</key>
+        <map>
+        <key>Comment</key>
+            <string>Avatar axis 1 dead zone.</string>
+        <key>Persist</key>
+            <integer>1</integer>
+        <key>Type</key>
+            <string>F32</string>
+        <key>Value</key>
+            <real>0.1</real>
+        </map>
+    <key>AvatarAxisDeadZone2</key>
+        <map>
+        <key>Comment</key>
+            <string>Avatar axis 2 dead zone.</string>
+        <key>Persist</key>
+            <integer>1</integer>
+        <key>Type</key>
+            <string>F32</string>
+        <key>Value</key>
+            <real>0.1</real>
+        </map>
+    <key>AvatarAxisDeadZone3</key>
+        <map>
+        <key>Comment</key>
+            <string>Avatar axis 3 dead zone.</string>
+        <key>Persist</key>
+            <integer>1</integer>
+        <key>Type</key>
+            <string>F32</string>
+        <key>Value</key>
+            <real>0.1</real>
+        </map>
+    <key>AvatarAxisDeadZone4</key>
+        <map>
+        <key>Comment</key>
+            <string>Avatar axis 4 dead zone.</string>
+        <key>Persist</key>
+            <integer>1</integer>
+        <key>Type</key>
+            <string>F32</string>
+        <key>Value</key>
+            <real>0.1</real>
+        </map>
+    <key>AvatarAxisDeadZone5</key>
+        <map>
+        <key>Comment</key>
+            <string>Avatar axis 5 dead zone.</string>
+        <key>Persist</key>
+            <integer>1</integer>
+        <key>Type</key>
+            <string>F32</string>
+        <key>Value</key>
+            <real>0.1</real>
+        </map>
+    <key>AvatarAxisScale0</key>
+        <map>
+        <key>Comment</key>
+            <string>Avatar axis 0 scaler.</string>
+        <key>Persist</key>
+            <integer>1</integer>
+        <key>Type</key>
+            <string>F32</string>
+        <key>Value</key>
+            <real>1</real>
+        </map>
+    <key>AvatarAxisScale1</key>
+        <map>
+        <key>Comment</key>
+            <string>Avatar axis 1 scaler.</string>
+        <key>Persist</key>
+            <integer>1</integer>
+        <key>Type</key>
+            <string>F32</string>
+        <key>Value</key>
+            <real>1</real>
+        </map>
+    <key>AvatarAxisScale2</key>
+        <map>
+        <key>Comment</key>
+            <string>Avatar axis 2 scaler.</string>
+        <key>Persist</key>
+            <integer>1</integer>
+        <key>Type</key>
+            <string>F32</string>
+        <key>Value</key>
+            <real>1</real>
+        </map>
+    <key>AvatarAxisScale3</key>
+        <map>
+        <key>Comment</key>
+            <string>Avatar axis 3 scaler.</string>
+        <key>Persist</key>
+            <integer>1</integer>
+        <key>Type</key>
+            <string>F32</string>
+        <key>Value</key>
+            <real>1</real>
+        </map>
+    <key>AvatarAxisScale4</key>
+        <map>
+        <key>Comment</key>
+            <string>Avatar axis 4 scaler.</string>
+        <key>Persist</key>
+            <integer>1</integer>
+        <key>Type</key>
+            <string>F32</string>
+        <key>Value</key>
+            <real>1</real>
+        </map>
+    <key>AvatarAxisScale5</key>
+        <map>
+        <key>Comment</key>
+            <string>Avatar axis 5 scaler.</string>
+        <key>Persist</key>
+            <integer>1</integer>
+        <key>Type</key>
+            <string>F32</string>
+        <key>Value</key>
+            <real>1</real>
+        </map>
+    <key>AvatarFeathering</key>
+        <map>
+        <key>Comment</key>
+            <string>Avatar feathering (less is softer)</string>
+        <key>Persist</key>
+            <integer>1</integer>
+        <key>Type</key>
+            <string>F32</string>
+        <key>Value</key>
+            <real>16</real>
+        </map>
+
+    <key>BuildAxisDeadZone0</key>
+        <map>
+        <key>Comment</key>
+            <string>Build axis 0 dead zone.</string>
+        <key>Persist</key>
+            <integer>1</integer>
+        <key>Type</key>
+            <string>F32</string>
+        <key>Value</key>
+            <real>0.1</real>
+        </map>
+    <key>BuildAxisDeadZone1</key>
+        <map>
+        <key>Comment</key>
+            <string>Build axis 1 dead zone.</string>
+        <key>Persist</key>
+            <integer>1</integer>
+        <key>Type</key>
+            <string>F32</string>
+        <key>Value</key>
+            <real>0.1</real>
+        </map>
+    <key>BuildAxisDeadZone2</key>
+        <map>
+        <key>Comment</key>
+            <string>Build axis 2 dead zone.</string>
+        <key>Persist</key>
+            <integer>1</integer>
+        <key>Type</key>
+            <string>F32</string>
+        <key>Value</key>
+            <real>0.1</real>
+        </map>
+    <key>BuildAxisDeadZone3</key>
+        <map>
+        <key>Comment</key>
+            <string>Build axis 3 dead zone.</string>
+        <key>Persist</key>
+            <integer>1</integer>
+        <key>Type</key>
+            <string>F32</string>
+        <key>Value</key>
+            <real>0.1</real>
+        </map>
+    <key>BuildAxisDeadZone4</key>
+        <map>
+        <key>Comment</key>
+            <string>Build axis 4 dead zone.</string>
+        <key>Persist</key>
+            <integer>1</integer>
+        <key>Type</key>
+            <string>F32</string>
+        <key>Value</key>
+            <real>0.1</real>
+        </map>
+    <key>BuildAxisDeadZone5</key>
+        <map>
+        <key>Comment</key>
+            <string>Build axis 5 dead zone.</string>
+        <key>Persist</key>
+            <integer>1</integer>
+        <key>Type</key>
+            <string>F32</string>
+        <key>Value</key>
+            <real>0.1</real>
+        </map>
+    <key>BuildAxisScale0</key>
+        <map>
+        <key>Comment</key>
+            <string>Build axis 0 scaler.</string>
+        <key>Persist</key>
+            <integer>1</integer>
+        <key>Type</key>
+            <string>F32</string>
+        <key>Value</key>
+            <real>1</real>
+        </map>
+    <key>BuildAxisScale1</key>
+        <map>
+        <key>Comment</key>
+            <string>Build axis 1 scaler.</string>
+        <key>Persist</key>
+            <integer>1</integer>
+        <key>Type</key>
+            <string>F32</string>
+        <key>Value</key>
+            <real>1</real>
+        </map>
+    <key>BuildAxisScale2</key>
+        <map>
+        <key>Comment</key>
+            <string>Build axis 2 scaler.</string>
+        <key>Persist</key>
+            <integer>1</integer>
+        <key>Type</key>
+            <string>F32</string>
+        <key>Value</key>
+            <real>1</real>
+        </map>
+    <key>BuildAxisScale3</key>
+        <map>
+        <key>Comment</key>
+            <string>Build axis 3 scaler.</string>
+        <key>Persist</key>
+            <integer>1</integer>
+        <key>Type</key>
+            <string>F32</string>
+        <key>Value</key>
+            <real>1</real>
+        </map>
+    <key>BuildAxisScale4</key>
+        <map>
+        <key>Comment</key>
+            <string>Build axis 4 scaler.</string>
+        <key>Persist</key>
+            <integer>1</integer>
+        <key>Type</key>
+            <string>F32</string>
+        <key>Value</key>
+            <real>1</real>
+        </map>
+    <key>BuildAxisScale5</key>
+        <map>
+        <key>Comment</key>
+            <string>Build axis 5 scaler.</string>
+        <key>Persist</key>
+            <integer>1</integer>
+        <key>Type</key>
+            <string>F32</string>
+        <key>Value</key>
+            <real>1</real>
+        </map>
+    <key>BuildFeathering</key>
+        <map>
+        <key>Comment</key>
+            <string>Build feathering (less is softer)</string>
+        <key>Persist</key>
+            <integer>1</integer>
+        <key>Type</key>
+            <string>F32</string>
+        <key>Value</key>
+            <real>16</real>
+        </map>
+
     </map>
 </llsd>
diff --git a/indra/newview/llagent.cpp b/indra/newview/llagent.cpp
index 2f5589a966..dcea6b2957 100644
--- a/indra/newview/llagent.cpp
+++ b/indra/newview/llagent.cpp
@@ -581,7 +581,7 @@ void LLAgent::unlockView()
 //-----------------------------------------------------------------------------
 // moveAt()
 //-----------------------------------------------------------------------------
-void LLAgent::moveAt(S32 direction)
+void LLAgent::moveAt(S32 direction, bool reset)
 {
 	// age chat timer so it fades more quickly when you are intentionally moving
 	ageChat();
@@ -597,7 +597,10 @@ void LLAgent::moveAt(S32 direction)
 		setControlFlags(AGENT_CONTROL_AT_NEG | AGENT_CONTROL_FAST_AT);
 	}
 
-	resetView();
+	if (reset)
+	{
+		resetView();
+	}
 }
 
 //-----------------------------------------------------------------------------
@@ -691,7 +694,7 @@ void LLAgent::moveUp(S32 direction)
 //-----------------------------------------------------------------------------
 // moveYaw()
 //-----------------------------------------------------------------------------
-void LLAgent::moveYaw(F32 mag)
+void LLAgent::moveYaw(F32 mag, bool reset_view)
 {
 	mYawKey = mag;
 
@@ -704,7 +707,10 @@ void LLAgent::moveYaw(F32 mag)
 		setControlFlags(AGENT_CONTROL_YAW_NEG);
 	}
 
-	resetView();
+    if (reset_view)
+	{
+        resetView();
+	}
 }
 
 //-----------------------------------------------------------------------------
@@ -1216,7 +1222,16 @@ LLVector3 LLAgent::getReferenceUpVector()
 void LLAgent::pitch(F32 angle)
 {
 	// don't let user pitch if pointed almost all the way down or up
-	
+	mFrameAgent.pitch(clampPitchToLimits(angle));
+}
+
+
+// Radians, positive is forward into ground
+//-----------------------------------------------------------------------------
+// clampPitchToLimits()
+//-----------------------------------------------------------------------------
+F32 LLAgent::clampPitchToLimits(F32 angle)
+{
 	// A dot B = mag(A) * mag(B) * cos(angle between A and B)
 	// so... cos(angle between A and B) = A dot B / mag(A) / mag(B)
 	//                                  = A dot B for unit vectors
@@ -1246,8 +1261,8 @@ void LLAgent::pitch(F32 angle)
 	{
 		angle = look_up_limit - angle_from_skyward;
 	}
-
-	mFrameAgent.pitch(angle);
+   
+    return angle;
 }
 
 
@@ -2576,15 +2591,18 @@ void LLAgent::updateLookAt(const S32 mouse_x, const S32 mouse_y)
 		LLVector3 headLookAxis;
 		LLCoordFrame frameCamera = *((LLCoordFrame*)LLViewerCamera::getInstance());
 
-		F32 x_from_center = ((F32) mouse_x / (F32) gViewerWindow->getWindowWidth() ) - 0.5f;
-		F32 y_from_center = ((F32) mouse_y / (F32) gViewerWindow->getWindowHeight() ) - 0.5f;
-
 		if (cameraMouselook())
 		{
 			lookAtType = LOOKAT_TARGET_MOUSELOOK;
 		}
 		else if (cameraThirdPerson())
 		{
+			// range from -.5 to .5
+			F32 x_from_center = 
+				((F32) mouse_x / (F32) gViewerWindow->getWindowWidth() ) - 0.5f;
+			F32 y_from_center = 
+				((F32) mouse_y / (F32) gViewerWindow->getWindowHeight() ) - 0.5f;
+
 			frameCamera.yaw( - x_from_center * gSavedSettings.getF32("YawFromMousePosition") * DEG_TO_RAD);
 			frameCamera.pitch( - y_from_center * gSavedSettings.getF32("PitchFromMousePosition") * DEG_TO_RAD);
 			lookAtType = LOOKAT_TARGET_FREELOOK;
diff --git a/indra/newview/llagent.h b/indra/newview/llagent.h
index 1a7d239288..b50fb745ca 100644
--- a/indra/newview/llagent.h
+++ b/indra/newview/llagent.h
@@ -356,6 +356,7 @@ public:
 	void			roll(F32 angle);
 	void			yaw(F32 angle);
 	LLVector3		getReferenceUpVector();
+    F32             clampPitchToLimits(F32 angle);
 
 	void			setThirdPersonHeadOffset(LLVector3 offset) { mThirdPersonHeadOffset = offset; }
 	// Flight management
@@ -404,12 +405,12 @@ public:
 	// Movement from user input.  All set the appropriate animation flags.
 	// All turn off autopilot and make sure the camera is behind the avatar.
 	// direction is either positive, zero, or negative
-	void			moveAt(S32 direction);
+	void			moveAt(S32 direction, bool reset_view = true);
 	void			moveAtNudge(S32 direction);
 	void			moveLeft(S32 direction);
 	void			moveLeftNudge(S32 direction);
 	void			moveUp(S32 direction);
-	void			moveYaw(F32 mag);
+	void			moveYaw(F32 mag, bool reset_view = true);
 	void			movePitch(S32 direction);
 
 	void			setOrbitLeftKey(F32 mag)				{ mOrbitLeftKey = mag; }
diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp
index fc092e5cba..3d10eda7fd 100644
--- a/indra/newview/llappviewer.cpp
+++ b/indra/newview/llappviewer.cpp
@@ -51,6 +51,7 @@
 #include "llstartup.h"
 #include "llfocusmgr.h"
 #include "llviewerjoystick.h"
+#include "llfloaterjoystick.h"
 #include "llares.h" 
 #include "llcurl.h"
 #include "llfloatersnapshot.h"
@@ -709,7 +710,7 @@ bool LLAppViewer::init()
 
 
 	// Copy settings to globals. *TODO: Remove or move to appropriage class initializers
-    	settings_to_globals();
+	settings_to_globals();
 	// Setup settings listeners
 	settings_setup_listeners();
 	// Modify settings based on system configuration and compile options
@@ -858,6 +859,16 @@ bool LLAppViewer::init()
 	gSimLastTime = gRenderStartTime.getElapsedTimeF32();
 	gSimFrames = (F32)gFrameCount;
 
+	LLViewerJoystick::getInstance()->init();
+	if (LLViewerJoystick::getInstance()->isLikeSpaceNavigator())
+	{
+		if (gSavedSettings.getString("JoystickInitialized") != "SpaceNavigator")
+		{
+			LLFloaterJoystick::setSNDefaults();
+			gSavedSettings.setString("JoystickInitialized", "SpaceNavigator");
+		}
+	}
+	
 	return true;
 }
 
@@ -880,7 +891,9 @@ bool LLAppViewer::mainLoop()
 	LLMemType mt1(LLMemType::MTYPE_MAIN);
 	LLTimer frameTimer,idleTimer;
 	LLTimer debugTime;
-	
+	LLViewerJoystick* joystick(LLViewerJoystick::getInstance());
+	joystick->setNeedsReset(true);
+ 	
 	// Handle messages
 	while (!LLApp::isExiting())
 	{
@@ -921,8 +934,8 @@ bool LLAppViewer::mainLoop()
 					&& !gViewerWindow->getShowProgress()
 					&& !gFocusMgr.focusLocked())
 				{
+					joystick->scanJoystick();
 					gKeyboard->scanKeyboard();
-					LLViewerJoystick::scanJoystick();
 				}
 
 				// Update state based on messages, user input, object idle.
@@ -3217,13 +3230,18 @@ void LLAppViewer::idle()
 	}
 	stop_glerror();
 
-	if (!LLViewerJoystick::sOverrideCamera)
+	if (LLViewerJoystick::sOverrideCamera)
 	{
-		gAgent.updateCamera();
+		LLViewerJoystick::getInstance()->moveFlycam();
 	}
 	else
 	{
-		LLViewerJoystick::updateCamera();
+		if (LLToolMgr::getInstance()->inBuildMode())
+		{
+			LLViewerJoystick::getInstance()->moveObjects();
+		}
+
+		gAgent.updateCamera();
 	}
 
 	// objects and camera should be in sync, do LOD calculations now
diff --git a/indra/newview/llfloaterjoystick.cpp b/indra/newview/llfloaterjoystick.cpp
new file mode 100644
index 0000000000..44abdd3cde
--- /dev/null
+++ b/indra/newview/llfloaterjoystick.cpp
@@ -0,0 +1,333 @@
+/** 
+ * @file llfloaterjoystick.cpp
+ * @brief Joystick preferences panel
+ *
+ * $LicenseInfo:firstyear=2007&license=viewergpl$
+ * 
+ * Copyright (c) 2007-2008, Linden Research, Inc.
+ * 
+ * Second Life Viewer Source Code
+ * The source code in this file ("Source Code") is provided by Linden Lab
+ * to you under the terms of the GNU General Public License, version 2.0
+ * ("GPL"), unless you have obtained a separate licensing agreement
+ * ("Other License"), formally executed by you and Linden Lab.  Terms of
+ * the GPL can be found in doc/GPL-license.txt in this distribution, or
+ * online at http://secondlife.com/developers/opensource/gplv2
+ * 
+ * There are special exceptions to the terms and conditions of the GPL as
+ * it is applied to this Source Code. View the full text of the exception
+ * in the file doc/FLOSS-exception.txt in this software distribution, or
+ * online at http://secondlife.com/developers/opensource/flossexception
+ * 
+ * By copying, modifying or distributing this software, you acknowledge
+ * that you have read and understood your obligations described above,
+ * and agree to abide by those obligations.
+ * 
+ * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+ * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+ * COMPLETENESS OR PERFORMANCE.
+ * $/LicenseInfo$
+ */
+
+#include "llviewerprecompiledheaders.h"
+
+// file include
+#include "llfloaterjoystick.h"
+
+// linden library includes
+#include "llerror.h"
+#include "llrect.h"
+#include "llstring.h"
+
+// project includes
+#include "lluictrlfactory.h"
+#include "llviewercontrol.h"
+#include "llappviewer.h"
+#include "llviewerjoystick.h"
+
+LLFloaterJoystick::LLFloaterJoystick(const LLSD& data)
+	: LLFloater("floater_joystick")
+{
+	LLUICtrlFactory::getInstance()->buildFloater(this, "floater_joystick.xml");
+	center();
+}
+
+void LLFloaterJoystick::draw()
+{
+	LLViewerJoystick* joystick(LLViewerJoystick::getInstance());
+	for (U32 i = 0; i < 6; i++)
+	{
+		F32 value = joystick->getJoystickAxis(i);
+		mAxisStats[i]->addValue(value * gFrameIntervalSeconds);
+		
+		if (mAxisStatsBar[i]->mMinBar > value)
+		{
+			mAxisStatsBar[i]->mMinBar = value;
+		}
+		if (mAxisStatsBar[i]->mMaxBar < value)
+		{
+			mAxisStatsBar[i]->mMaxBar = value;
+		}
+	}
+
+	LLFloater::draw();
+}
+
+BOOL LLFloaterJoystick::postBuild()
+{		
+	F32 range = gSavedSettings.getBOOL("Cursor3D") ? 1024.f : 2.f;
+	LLUIString axis = getUIString("Axis");
+	LLUIString joystick = getUIString("JoystickMonitor");
+
+	// use this child to get relative positioning info; we'll place the
+	// joystick monitor on its right, vertically aligned to it.
+	LLView* child = getChild<LLView>("FlycamAxisScale1");
+	LLRect rect;
+
+	if (child)
+	{
+		LLRect r = child->getRect();
+		LLRect f = getRect();
+		rect = LLRect(350, r.mTop, r.mRight + 200, 0);
+	}
+
+	mAxisStatsView = new LLStatView("axis values", joystick, "", rect);
+	mAxisStatsView->setDisplayChildren(TRUE);
+
+	for (U32 i = 0; i < 6; i++)
+	{
+		axis.setArg("[NUM]", llformat("%d", i));
+		mAxisStats[i] = new LLStat(4);
+		mAxisStatsBar[i] = mAxisStatsView->addStat(axis, mAxisStats[i]);
+		mAxisStatsBar[i]->mMinBar = -range;
+		mAxisStatsBar[i]->mMaxBar = range;
+		mAxisStatsBar[i]->mLabelSpacing = range * 0.5f;
+		mAxisStatsBar[i]->mTickSpacing = range * 0.25f;			
+	}
+
+	addChild(mAxisStatsView);
+	
+	childSetAction("SpaceNavigatorDefaults", onClickRestoreSNDefaults, this);
+	refresh();
+	return TRUE;
+}
+
+LLFloaterJoystick::~LLFloaterJoystick()
+{
+	// Children all cleaned up by default view destructor.
+}
+
+
+void LLFloaterJoystick::apply()
+{
+}
+
+void LLFloaterJoystick::refresh()
+{
+	LLFloater::refresh();
+
+	mJoystickAxis[0] = gSavedSettings.getS32("JoystickAxis0");
+	mJoystickAxis[1] = gSavedSettings.getS32("JoystickAxis1");
+	mJoystickAxis[2] = gSavedSettings.getS32("JoystickAxis2");
+	mJoystickAxis[3] = gSavedSettings.getS32("JoystickAxis3");
+	mJoystickAxis[4] = gSavedSettings.getS32("JoystickAxis4");
+	mJoystickAxis[5] = gSavedSettings.getS32("JoystickAxis5");
+	mJoystickAxis[6] = gSavedSettings.getS32("JoystickAxis6");
+	
+	m3DCursor = gSavedSettings.getBOOL("Cursor3D");
+	mAutoLeveling = gSavedSettings.getBOOL("AutoLeveling");
+	mZoomDirect  = gSavedSettings.getBOOL("ZoomDirect");
+
+	mAvatarAxisScale[0] = gSavedSettings.getF32("AvatarAxisScale0");
+	mAvatarAxisScale[1] = gSavedSettings.getF32("AvatarAxisScale1");
+	mAvatarAxisScale[2] = gSavedSettings.getF32("AvatarAxisScale2");
+	mAvatarAxisScale[3] = gSavedSettings.getF32("AvatarAxisScale3");
+	mAvatarAxisScale[4] = gSavedSettings.getF32("AvatarAxisScale4");
+	mAvatarAxisScale[5] = gSavedSettings.getF32("AvatarAxisScale5");
+
+	mBuildAxisScale[0] = gSavedSettings.getF32("BuildAxisScale0");
+	mBuildAxisScale[1] = gSavedSettings.getF32("BuildAxisScale1");
+	mBuildAxisScale[2] = gSavedSettings.getF32("BuildAxisScale2");
+	mBuildAxisScale[3] = gSavedSettings.getF32("BuildAxisScale3");
+	mBuildAxisScale[4] = gSavedSettings.getF32("BuildAxisScale4");
+	mBuildAxisScale[5] = gSavedSettings.getF32("BuildAxisScale5");
+
+	mFlycamAxisScale[0] = gSavedSettings.getF32("FlycamAxisScale0");
+	mFlycamAxisScale[1] = gSavedSettings.getF32("FlycamAxisScale1");
+	mFlycamAxisScale[2] = gSavedSettings.getF32("FlycamAxisScale2");
+	mFlycamAxisScale[3] = gSavedSettings.getF32("FlycamAxisScale3");
+	mFlycamAxisScale[4] = gSavedSettings.getF32("FlycamAxisScale4");
+	mFlycamAxisScale[5] = gSavedSettings.getF32("FlycamAxisScale5");
+	mFlycamAxisScale[6] = gSavedSettings.getF32("FlycamAxisScale6");
+
+	mAvatarAxisDeadZone[0] = gSavedSettings.getF32("AvatarAxisDeadZone0");
+	mAvatarAxisDeadZone[1] = gSavedSettings.getF32("AvatarAxisDeadZone1");
+	mAvatarAxisDeadZone[2] = gSavedSettings.getF32("AvatarAxisDeadZone2");
+	mAvatarAxisDeadZone[3] = gSavedSettings.getF32("AvatarAxisDeadZone3");
+	mAvatarAxisDeadZone[4] = gSavedSettings.getF32("AvatarAxisDeadZone4");
+	mAvatarAxisDeadZone[5] = gSavedSettings.getF32("AvatarAxisDeadZone5");
+
+	mBuildAxisDeadZone[0] = gSavedSettings.getF32("BuildAxisDeadZone0");
+	mBuildAxisDeadZone[1] = gSavedSettings.getF32("BuildAxisDeadZone1");
+	mBuildAxisDeadZone[2] = gSavedSettings.getF32("BuildAxisDeadZone2");
+	mBuildAxisDeadZone[3] = gSavedSettings.getF32("BuildAxisDeadZone3");
+	mBuildAxisDeadZone[4] = gSavedSettings.getF32("BuildAxisDeadZone4");
+	mBuildAxisDeadZone[5] = gSavedSettings.getF32("BuildAxisDeadZone5");
+
+	mFlycamAxisDeadZone[0] = gSavedSettings.getF32("FlycamAxisDeadZone0");
+	mFlycamAxisDeadZone[1] = gSavedSettings.getF32("FlycamAxisDeadZone1");
+	mFlycamAxisDeadZone[2] = gSavedSettings.getF32("FlycamAxisDeadZone2");
+	mFlycamAxisDeadZone[3] = gSavedSettings.getF32("FlycamAxisDeadZone3");
+	mFlycamAxisDeadZone[4] = gSavedSettings.getF32("FlycamAxisDeadZone4");
+	mFlycamAxisDeadZone[5] = gSavedSettings.getF32("FlycamAxisDeadZone5");
+	mFlycamAxisDeadZone[6] = gSavedSettings.getF32("FlycamAxisDeadZone6");
+
+	mAvatarFeathering = gSavedSettings.getF32("AvatarFeathering");
+	mBuildFeathering = gSavedSettings.getF32("BuildFeathering");
+	mFlycamFeathering = gSavedSettings.getF32("FlycamFeathering");
+}
+
+void LLFloaterJoystick::cancel()
+{
+	llinfos << "reading from gSavedSettings->Cursor3D=" 
+		<< gSavedSettings.getBOOL("Cursor3D") << "; m3DCursor=" 
+		<< m3DCursor << llendl;
+
+	gSavedSettings.setS32("JoystickAxis0", mJoystickAxis[0]);
+	gSavedSettings.setS32("JoystickAxis1", mJoystickAxis[1]);
+	gSavedSettings.setS32("JoystickAxis2", mJoystickAxis[2]);
+	gSavedSettings.setS32("JoystickAxis3", mJoystickAxis[3]);
+	gSavedSettings.setS32("JoystickAxis4", mJoystickAxis[4]);
+	gSavedSettings.setS32("JoystickAxis5", mJoystickAxis[5]);
+	gSavedSettings.setS32("JoystickAxis6", mJoystickAxis[6]);
+
+	gSavedSettings.setBOOL("Cursor3D", m3DCursor);
+	gSavedSettings.setBOOL("AutoLeveling", mAutoLeveling);
+	gSavedSettings.setBOOL("ZoomDirect", mZoomDirect );
+
+	gSavedSettings.setF32("AvatarAxisScale0", mAvatarAxisScale[0]);
+	gSavedSettings.setF32("AvatarAxisScale1", mAvatarAxisScale[1]);
+	gSavedSettings.setF32("AvatarAxisScale2", mAvatarAxisScale[2]);
+	gSavedSettings.setF32("AvatarAxisScale3", mAvatarAxisScale[3]);
+	gSavedSettings.setF32("AvatarAxisScale4", mAvatarAxisScale[4]);
+	gSavedSettings.setF32("AvatarAxisScale5", mAvatarAxisScale[5]);
+
+	gSavedSettings.setF32("BuildAxisScale0", mBuildAxisScale[0]);
+	gSavedSettings.setF32("BuildAxisScale1", mBuildAxisScale[1]);
+	gSavedSettings.setF32("BuildAxisScale2", mBuildAxisScale[2]);
+	gSavedSettings.setF32("BuildAxisScale3", mBuildAxisScale[3]);
+	gSavedSettings.setF32("BuildAxisScale4", mBuildAxisScale[4]);
+	gSavedSettings.setF32("BuildAxisScale5", mBuildAxisScale[5]);
+
+	gSavedSettings.setF32("FlycamAxisScale0", mFlycamAxisScale[0]);
+	gSavedSettings.setF32("FlycamAxisScale1", mFlycamAxisScale[1]);
+	gSavedSettings.setF32("FlycamAxisScale2", mFlycamAxisScale[2]);
+	gSavedSettings.setF32("FlycamAxisScale3", mFlycamAxisScale[3]);
+	gSavedSettings.setF32("FlycamAxisScale4", mFlycamAxisScale[4]);
+	gSavedSettings.setF32("FlycamAxisScale5", mFlycamAxisScale[5]);
+	gSavedSettings.setF32("FlycamAxisScale6", mFlycamAxisScale[6]);
+
+	gSavedSettings.setF32("AvatarAxisDeadZone0", mAvatarAxisDeadZone[0]);
+	gSavedSettings.setF32("AvatarAxisDeadZone1", mAvatarAxisDeadZone[1]);
+	gSavedSettings.setF32("AvatarAxisDeadZone2", mAvatarAxisDeadZone[2]);
+	gSavedSettings.setF32("AvatarAxisDeadZone3", mAvatarAxisDeadZone[3]);
+	gSavedSettings.setF32("AvatarAxisDeadZone4", mAvatarAxisDeadZone[4]);
+	gSavedSettings.setF32("AvatarAxisDeadZone5", mAvatarAxisDeadZone[5]);
+
+	gSavedSettings.setF32("BuildAxisDeadZone0", mBuildAxisDeadZone[0]);
+	gSavedSettings.setF32("BuildAxisDeadZone1", mBuildAxisDeadZone[1]);
+	gSavedSettings.setF32("BuildAxisDeadZone2", mBuildAxisDeadZone[2]);
+	gSavedSettings.setF32("BuildAxisDeadZone3", mBuildAxisDeadZone[3]);
+	gSavedSettings.setF32("BuildAxisDeadZone4", mBuildAxisDeadZone[4]);
+	gSavedSettings.setF32("BuildAxisDeadZone5", mBuildAxisDeadZone[5]);
+
+	gSavedSettings.setF32("FlycamAxisDeadZone0", mFlycamAxisDeadZone[0]);
+	gSavedSettings.setF32("FlycamAxisDeadZone1", mFlycamAxisDeadZone[1]);
+	gSavedSettings.setF32("FlycamAxisDeadZone2", mFlycamAxisDeadZone[2]);
+	gSavedSettings.setF32("FlycamAxisDeadZone3", mFlycamAxisDeadZone[3]);
+	gSavedSettings.setF32("FlycamAxisDeadZone4", mFlycamAxisDeadZone[4]);
+	gSavedSettings.setF32("FlycamAxisDeadZone5", mFlycamAxisDeadZone[5]);
+	gSavedSettings.setF32("FlycamAxisDeadZone6", mFlycamAxisDeadZone[6]);
+
+	gSavedSettings.setF32("AvatarFeathering", mAvatarFeathering);
+	gSavedSettings.setF32("BuildFeathering", mBuildFeathering);
+	gSavedSettings.setF32("FlycamFeathering", mFlycamFeathering);
+}
+
+void LLFloaterJoystick::onClickRestoreSNDefaults(void *joy_panel)
+{
+	setSNDefaults();
+}
+
+void LLFloaterJoystick::setSNDefaults()
+{
+#if LL_DARWIN 
+#define kPlatformScale	20.f
+#else
+#define kPlatformScale	1.f
+#endif
+	
+	//gViewerWindow->alertXml("CacheWillClear");
+	llinfos << "restoring SpaceNavigator defaults..." << llendl;
+	
+	gSavedSettings.setS32("JoystickAxis1", 0); // x (slide)
+	gSavedSettings.setS32("JoystickAxis2", 2); // y (up)
+	gSavedSettings.setS32("JoystickAxis0", 1); // z (at)
+	gSavedSettings.setS32("JoystickAxis4", 3); // roll 
+	gSavedSettings.setS32("JoystickAxis5", 5); // yaw
+	gSavedSettings.setS32("JoystickAxis3", 4); // pitch
+	gSavedSettings.setS32("JoystickAxis6", -1);
+	
+#if LL_DARWIN
+	// The SpaceNavigator doesn't act as a 3D cursor on OS X. 
+	gSavedSettings.setBOOL("Cursor3D", false);
+#else
+	gSavedSettings.setBOOL("Cursor3D", true);
+#endif
+	gSavedSettings.setBOOL("AutoLeveling", true);
+	gSavedSettings.setBOOL("ZoomDirect", false);
+	
+	gSavedSettings.setF32("AvatarAxisScale0", 1.f);
+	gSavedSettings.setF32("AvatarAxisScale1", 1.f);
+	gSavedSettings.setF32("AvatarAxisScale2", 1.f);
+	gSavedSettings.setF32("AvatarAxisScale4", .1f * kPlatformScale);
+	gSavedSettings.setF32("AvatarAxisScale5", .1f * kPlatformScale);
+	gSavedSettings.setF32("AvatarAxisScale3", 0.f * kPlatformScale);
+	gSavedSettings.setF32("BuildAxisScale1", .3f * kPlatformScale);
+	gSavedSettings.setF32("BuildAxisScale2", .3f * kPlatformScale);
+	gSavedSettings.setF32("BuildAxisScale0", .3f * kPlatformScale);
+	gSavedSettings.setF32("BuildAxisScale4", .3f * kPlatformScale);
+	gSavedSettings.setF32("BuildAxisScale5", .3f * kPlatformScale);
+	gSavedSettings.setF32("BuildAxisScale3", .3f * kPlatformScale);
+	gSavedSettings.setF32("FlycamAxisScale1", 2.f * kPlatformScale);
+	gSavedSettings.setF32("FlycamAxisScale2", 2.f * kPlatformScale);
+	gSavedSettings.setF32("FlycamAxisScale0", 2.1f * kPlatformScale);
+	gSavedSettings.setF32("FlycamAxisScale4", .1f * kPlatformScale);
+	gSavedSettings.setF32("FlycamAxisScale5", .15f * kPlatformScale);
+	gSavedSettings.setF32("FlycamAxisScale3", 0.f * kPlatformScale);
+	gSavedSettings.setF32("FlycamAxisScale6", 0.f * kPlatformScale);
+	
+	gSavedSettings.setF32("AvatarAxisDeadZone0", .1f);
+	gSavedSettings.setF32("AvatarAxisDeadZone1", .1f);
+	gSavedSettings.setF32("AvatarAxisDeadZone2", .1f);
+	gSavedSettings.setF32("AvatarAxisDeadZone3", 1.f);
+	gSavedSettings.setF32("AvatarAxisDeadZone4", .02f);
+	gSavedSettings.setF32("AvatarAxisDeadZone5", .01f);
+	gSavedSettings.setF32("BuildAxisDeadZone0", .01f);
+	gSavedSettings.setF32("BuildAxisDeadZone1", .01f);
+	gSavedSettings.setF32("BuildAxisDeadZone2", .01f);
+	gSavedSettings.setF32("BuildAxisDeadZone3", .01f);
+	gSavedSettings.setF32("BuildAxisDeadZone4", .01f);
+	gSavedSettings.setF32("BuildAxisDeadZone5", .01f);
+	gSavedSettings.setF32("FlycamAxisDeadZone0", .01f);
+	gSavedSettings.setF32("FlycamAxisDeadZone1", .01f);
+	gSavedSettings.setF32("FlycamAxisDeadZone2", .01f);
+	gSavedSettings.setF32("FlycamAxisDeadZone3", .01f);
+	gSavedSettings.setF32("FlycamAxisDeadZone4", .01f);
+	gSavedSettings.setF32("FlycamAxisDeadZone5", .01f);
+	gSavedSettings.setF32("FlycamAxisDeadZone6", 1.f);
+	
+	gSavedSettings.setF32("AvatarFeathering", 6.f);
+	gSavedSettings.setF32("BuildFeathering", 12.f);
+	gSavedSettings.setF32("FlycamFeathering", 5.f);
+}
diff --git a/indra/newview/llfloaterjoystick.h b/indra/newview/llfloaterjoystick.h
new file mode 100644
index 0000000000..6dc40704f0
--- /dev/null
+++ b/indra/newview/llfloaterjoystick.h
@@ -0,0 +1,78 @@
+/** 
+ * @file llfloaterjoystick.h
+ * @brief Joystick preferences panel
+ *
+ * $LicenseInfo:firstyear=2007&license=viewergpl$
+ * 
+ * Copyright (c) 2007-2008, Linden Research, Inc.
+ * 
+ * Second Life Viewer Source Code
+ * The source code in this file ("Source Code") is provided by Linden Lab
+ * to you under the terms of the GNU General Public License, version 2.0
+ * ("GPL"), unless you have obtained a separate licensing agreement
+ * ("Other License"), formally executed by you and Linden Lab.  Terms of
+ * the GPL can be found in doc/GPL-license.txt in this distribution, or
+ * online at http://secondlife.com/developers/opensource/gplv2
+ * 
+ * There are special exceptions to the terms and conditions of the GPL as
+ * it is applied to this Source Code. View the full text of the exception
+ * in the file doc/FLOSS-exception.txt in this software distribution, or
+ * online at http://secondlife.com/developers/opensource/flossexception
+ * 
+ * By copying, modifying or distributing this software, you acknowledge
+ * that you have read and understood your obligations described above,
+ * and agree to abide by those obligations.
+ * 
+ * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+ * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+ * COMPLETENESS OR PERFORMANCE.
+ * $/LicenseInfo$
+ */
+
+#ifndef LL_LLFLOATERJOYSTICK_H
+#define LL_LLFLOATERJOYSTICK_H
+
+#include "llfloater.h"
+#include "llstatview.h"
+
+class LLFloaterJoystick : public LLFloater, public LLFloaterSingleton<LLFloaterJoystick >
+{
+public:
+	LLFloaterJoystick(const LLSD& data);
+	virtual ~LLFloaterJoystick();
+
+	virtual BOOL postBuild();
+	virtual void refresh();
+	virtual void apply();	// Apply the changed values.
+	virtual void cancel();	// Cancel the changed values.
+	virtual void draw();
+	static  void setSNDefaults();
+
+private:
+	static void onClickRestoreSNDefaults(void*);
+
+private:
+	// Device prefs
+	S32 mJoystickAxis[7];
+	bool m3DCursor;
+	bool mAutoLeveling;
+	bool mZoomDirect;
+
+	// Modes prefs
+	F32 mAvatarAxisScale[6];
+	F32 mBuildAxisScale[6];
+	F32 mFlycamAxisScale[7];
+	F32 mAvatarAxisDeadZone[6];
+	F32 mBuildAxisDeadZone[6];
+	F32 mFlycamAxisDeadZone[7];
+	F32 mAvatarFeathering;
+	F32 mBuildFeathering;
+	F32 mFlycamFeathering;
+
+	// stats view 
+	LLStatView*		mAxisStatsView;
+	LLStat*			mAxisStats[6];
+	LLStatBar*		mAxisStatsBar[6];
+};
+
+#endif
diff --git a/indra/newview/llfloatertools.cpp b/indra/newview/llfloatertools.cpp
index 7777544a4c..0e58363de5 100644
--- a/indra/newview/llfloatertools.cpp
+++ b/indra/newview/llfloatertools.cpp
@@ -75,7 +75,7 @@
 #include "llviewerparcelmgr.h"
 #include "llviewerwindow.h"
 #include "llviewercontrol.h"
-
+#include "llviewerjoystick.h"
 #include "lluictrlfactory.h"
 
 // Globals
@@ -770,6 +770,8 @@ void LLFloaterTools::onClose(bool app_quitting)
 	setVisible(FALSE);
 	mTab->setVisible(FALSE);
 
+	LLViewerJoystick::getInstance()->moveAvatar(true);
+
     // Different from handle_reset_view in that it doesn't actually 
 	//   move the camera if EditCameraMovement is not set.
 	gAgent.resetView(gSavedSettings.getBOOL("EditCameraMovement"));
diff --git a/indra/newview/llmanip.cpp b/indra/newview/llmanip.cpp
index af682508c9..887db4cf95 100644
--- a/indra/newview/llmanip.cpp
+++ b/indra/newview/llmanip.cpp
@@ -255,7 +255,7 @@ BOOL LLManip::getMousePointOnPlaneAgent(LLVector3& point, S32 x, S32 y, LLVector
 	return result;
 }
 
-BOOL LLManip::getMousePointOnPlaneGlobal(LLVector3d& point, S32 x, S32 y, LLVector3d origin, LLVector3 normal)
+BOOL LLManip::getMousePointOnPlaneGlobal(LLVector3d& point, S32 x, S32 y, LLVector3d origin, LLVector3 normal) const
 {
 	if (mObjectSelection->getSelectType() == SELECT_TYPE_HUD)
 	{
diff --git a/indra/newview/llmanip.h b/indra/newview/llmanip.h
index 2dd0eab8b0..a2ad464f3c 100644
--- a/indra/newview/llmanip.h
+++ b/indra/newview/llmanip.h
@@ -146,7 +146,7 @@ protected:
 	void				renderTickValue(const LLVector3& pos, F32 value, const char* suffix, const LLColor4 &color);
 	void				renderTickText(const LLVector3& pos, const char* suffix, const LLColor4 &color);
 	void				updateGridSettings();
-	BOOL				getMousePointOnPlaneGlobal(LLVector3d& point, S32 x, S32 y, LLVector3d origin, LLVector3 normal);
+	BOOL				getMousePointOnPlaneGlobal(LLVector3d& point, S32 x, S32 y, LLVector3d origin, LLVector3 normal) const;
 	BOOL				getMousePointOnPlaneAgent(LLVector3& point, S32 x, S32 y, LLVector3 origin, LLVector3 normal);
 	BOOL				nearestPointOnLineFromMouse( S32 x, S32 y, const LLVector3& b1, const LLVector3& b2, F32 &a_param, F32 &b_param );
 	LLColor4			setupSnapGuideRenderPass(S32 pass);
diff --git a/indra/newview/llselectmgr.cpp b/indra/newview/llselectmgr.cpp
index c2dfd779a3..cd59c8e300 100644
--- a/indra/newview/llselectmgr.cpp
+++ b/indra/newview/llselectmgr.cpp
@@ -6241,4 +6241,199 @@ LLViewerObject* LLObjectSelection::getFirstMoveableObject(BOOL get_parent)
 	return getFirstSelectedObject(&func, get_parent);
 }
 
+//-----------------------------------------------------------------------------
+// Position + Rotation update methods called from LLViewerJoystick
+//-----------------------------------------------------------------------------
+bool LLSelectMgr::selectionMove(const LLVector3& displ,
+                                  F32 roll, F32 pitch, F32 yaw, U32 update_type)
+{
+	if (update_type == UPD_NONE)
+	{
+		return false;
+	}
+	
+	LLVector3 displ_global;
+	bool update_success = true;
+	bool update_position = update_type & UPD_POSITION;
+	bool update_rotation = update_type & UPD_ROTATION;
+	const bool noedit_linked_parts = !gSavedSettings.getBOOL("EditLinkedParts");
+	
+	if (update_position)
+	{
+		// calculate the distance of the object closest to the camera origin
+		F32 min_dist = 1e+30f;
+		LLVector3 obj_pos;
+		for (LLObjectSelection::root_iterator it = getSelection()->root_begin();
+			 it != getSelection()->root_end(); ++it)
+		{
+			obj_pos = (*it)->getObject()->getPositionEdit();
+			
+			F32 obj_dist = dist_vec(obj_pos, LLViewerCamera::getInstance()->getOrigin());
+			if (obj_dist < min_dist)
+			{
+				min_dist = obj_dist;
+			}
+		}
+		
+		// factor the distance inside the displacement vector. This will get us
+		// equally visible movements for both close and far away selections.
+		min_dist = sqrt(min_dist) / 2;
+		displ_global.setVec(displ.mV[0]*min_dist, 
+							displ.mV[1]*min_dist, 
+							displ.mV[2]*min_dist);
+
+		// equates to: Displ_global = Displ * M_cam_axes_in_global_frame
+		displ_global = LLViewerCamera::getInstance()->rotateToAbsolute(displ_global);
+	}
+
+	LLQuaternion new_rot;
+	if (update_rotation)
+	{
+		// let's calculate the rotation around each camera axes 
+		LLQuaternion qx(roll, LLViewerCamera::getInstance()->getAtAxis());
+		LLQuaternion qy(pitch, LLViewerCamera::getInstance()->getLeftAxis());
+		LLQuaternion qz(yaw, LLViewerCamera::getInstance()->getUpAxis());
+		new_rot.setQuat(qx * qy * qz);
+	}
+	
+	LLViewerObject *obj;
+	S32 obj_count = getSelection()->getObjectCount();
+	for (LLObjectSelection::root_iterator it = getSelection()->root_begin();
+		 it != getSelection()->root_end(); ++it )
+	{
+		obj = (*it)->getObject();
+		bool enable_pos = false, enable_rot = false;
+		bool perm_move = obj->permMove();
+		bool perm_mod = obj->permModify();
+		
+		LLVector3d sel_center(getSelectionCenterGlobal());
+		
+		if (update_rotation)
+		{
+			enable_rot = perm_move 
+				&& ((perm_mod && !obj->isAttachment()) || noedit_linked_parts);
+
+			if (enable_rot)
+			{
+				int children_count = obj->getChildren().size();
+				if (obj_count > 1 && children_count > 0)
+				{
+					// for linked sets, rotate around the group center
+					const LLVector3 t(obj->getPositionGlobal() - sel_center);
+
+					// Ra = T x R x T^-1
+					LLMatrix4 mt;	mt.setTranslation(t);
+					const LLMatrix4 mnew_rot(new_rot);
+					LLMatrix4 mt_1;	mt_1.setTranslation(-t);
+					mt *= mnew_rot;
+					mt *= mt_1;
+					
+					// Rfin = Rcur * Ra
+					obj->setRotation(obj->getRotationEdit() * mt.quaternion());
+					displ_global += mt.getTranslation();
+				}
+				else
+				{
+					obj->setRotation(obj->getRotationEdit() * new_rot);
+				}
+			}
+			else
+			{
+				update_success = false;
+			}
+		}
+
+		if (update_position)
+		{
+			// establish if object can be moved or not
+			enable_pos = perm_move && !obj->isAttachment() 
+			&& (perm_mod || noedit_linked_parts);
+			
+			if (enable_pos)
+			{
+				obj->setPosition(obj->getPositionEdit() + displ_global);
+			}
+			else
+			{
+				update_success = false;
+			}
+		}
+		
+		if (enable_pos && enable_rot && obj->mDrawable.notNull())
+		{
+			gPipeline.markMoved(obj->mDrawable, TRUE);
+		}
+	}
+	
+	if (update_position && update_success && obj_count > 1)
+	{
+		updateSelectionCenter();
+	}
+	
+	return update_success;
+}
+
+void LLSelectMgr::sendSelectionMove()
+{
+	LLSelectNode *node = mSelectedObjects->getFirstRootNode();
+	if (node == NULL)
+	{
+		return;
+	}
+	
+	//saveSelectedObjectTransform(SELECT_ACTION_TYPE_PICK);
+	
+	U32 update_type = UPD_POSITION | UPD_ROTATION;
+	LLViewerRegion *last_region, *curr_region = node->getObject()->getRegion();
+	S32 objects_in_this_packet = 0;
+
+	// apply to linked objects if unable to select their individual parts 
+	if (!gSavedSettings.getBOOL("EditLinkedParts") && !getTEMode())
+	{
+		// tell simulator to apply to whole linked sets
+		update_type |= UPD_LINKED_SETS;
+	}
+
+	// prepare first bulk message
+	gMessageSystem->newMessage("MultipleObjectUpdate");
+	packAgentAndSessionID(&update_type);
+
+	LLViewerObject *obj = NULL;
+	for (LLObjectSelection::root_iterator it = getSelection()->root_begin();
+		 it != getSelection()->root_end(); ++it)
+	{
+		obj = (*it)->getObject();
+
+		// note: following code adapted from sendListToRegions() (@3924)
+		last_region = curr_region;
+		curr_region = obj->getRegion();
 
+		// if not simulator or message too big
+		if (curr_region != last_region
+			|| gMessageSystem->isSendFull(NULL)
+			|| objects_in_this_packet >= MAX_OBJECTS_PER_PACKET)
+		{
+			// send sim the current message and start new one
+			gMessageSystem->sendReliable(last_region->getHost());
+			objects_in_this_packet = 0;
+			gMessageSystem->newMessage("MultipleObjectUpdate");
+			packAgentAndSessionID(&update_type);
+		}
+
+		// add another instance of the body of data
+		packMultipleUpdate(*it, &update_type);
+		++objects_in_this_packet;
+	}
+
+	// flush remaining messages
+	if (gMessageSystem->getCurrentSendTotal() > 0)
+	{
+		gMessageSystem->sendReliable(curr_region->getHost());
+	}
+	else
+	{
+		gMessageSystem->clearMessage();
+	}
+
+	//saveSelectedObjectTransform(SELECT_ACTION_TYPE_PICK);
+}
diff --git a/indra/newview/llselectmgr.h b/indra/newview/llselectmgr.h
index 598a962b3c..4a8f99db1f 100644
--- a/indra/newview/llselectmgr.h
+++ b/indra/newview/llselectmgr.h
@@ -321,7 +321,7 @@ public:
 	bool applyToRootNodes(LLSelectedNodeFunctor* func, bool firstonly = false);
 	bool applyToNodes(LLSelectedNodeFunctor* func, bool firstonly = false);
 
-	ESelectType getSelectType() { return mSelectType; }
+	ESelectType getSelectType() const { return mSelectType; }
 
 private:
 	const LLObjectSelection &operator=(const LLObjectSelection &);
@@ -516,6 +516,10 @@ public:
 
 	void selectionResetRotation();				// sets rotation quat to identity
 	void selectionRotateAroundZ(F32 degrees);
+	bool selectionMove(const LLVector3& displ, F32 rx, F32 ry, F32 rz,
+					   U32 update_type);
+	void sendSelectionMove();
+
 	void sendGodlikeRequest(const LLString& request, const LLString& parameter);
 
 
diff --git a/indra/newview/lltoolcomp.cpp b/indra/newview/lltoolcomp.cpp
index 7865d8efe3..8d1faa850f 100644
--- a/indra/newview/lltoolcomp.cpp
+++ b/indra/newview/lltoolcomp.cpp
@@ -300,7 +300,7 @@ BOOL LLToolCompTranslate::handleDoubleClick(S32 x, S32 y, MASK mask)
 
 void LLToolCompTranslate::render()
 {
-	mCur->render();
+	mCur->render(); // removing this will not draw the RGB arrows and guidelines
 
 	if( mCur != mManip )
 	{
diff --git a/indra/newview/lltoolmgr.cpp b/indra/newview/lltoolmgr.cpp
index b20d4f9d28..a4c7856411 100644
--- a/indra/newview/lltoolmgr.cpp
+++ b/indra/newview/lltoolmgr.cpp
@@ -49,6 +49,8 @@
 #include "lltoolselectland.h"
 #include "lltoolobjpicker.h"
 #include "lltoolpipette.h"
+#include "llagent.h"
+#include "llviewercontrol.h"
 
 
 // Used when app not active to avoid processing hover.
@@ -227,6 +229,19 @@ BOOL LLToolMgr::inEdit()
 	return mBaseTool != LLToolPie::getInstance() && mBaseTool != gToolNull;
 }
 
+bool LLToolMgr::inBuildMode()
+{
+	// when entering mouselook inEdit() immediately returns true before 
+	// cameraMouselook() actually starts returning true.  Also, appearance edit
+	// sets build mode to true, so let's exclude that.
+	bool b=(inEdit() 
+			&& gSavedSettings.getBOOL("BuildBtnState")
+			&& !gAgent.cameraMouselook()
+			&& mCurrentToolset != gFaceEditToolset);
+	
+	return b;
+}
+
 void LLToolMgr::setTransientTool(LLTool* tool)
 {
 	if (!tool)
diff --git a/indra/newview/lltoolmgr.h b/indra/newview/lltoolmgr.h
index 73b8a38c57..e4a4b80dcf 100644
--- a/indra/newview/lltoolmgr.h
+++ b/indra/newview/lltoolmgr.h
@@ -58,6 +58,9 @@ public:
 	LLTool*			getBaseTool(); // returns active tool when overrides are deactivated
 
 	BOOL			inEdit();
+	
+	/* Determines if we are in Build mode or not. */
+	bool			inBuildMode();
 
 	void			setTransientTool(LLTool* tool);
 	void			clearTransientTool();
diff --git a/indra/newview/llviewercamera.cpp b/indra/newview/llviewercamera.cpp
index 8657f59ccb..a1a2c34222 100644
--- a/indra/newview/llviewercamera.cpp
+++ b/indra/newview/llviewercamera.cpp
@@ -47,6 +47,8 @@
 #include "llviewerwindow.h"
 #include "llvovolume.h"
 #include "llworld.h"
+#include "lltoolmgr.h"
+#include "llviewerjoystick.h"
 
 GLfloat gGLZFar;
 GLfloat gGLZNear;
@@ -97,6 +99,13 @@ void LLViewerCamera::updateCameraLocation(const LLVector3 &center,
 											const LLVector3 &up_direction,
 											const LLVector3 &point_of_interest)
 {
+	// do not update if we are in build mode AND avatar didn't move
+	if (LLToolMgr::getInstance()->inBuildMode() 
+		&& !LLViewerJoystick::getInstance()->getCameraNeedsUpdate())
+	{
+		return;
+	}
+
 	LLVector3 last_position;
 	LLVector3 last_axis;
 	last_position = getOrigin();
diff --git a/indra/newview/llviewercontrol.cpp b/indra/newview/llviewercontrol.cpp
index 2e76fe33a3..d40073b3e7 100644
--- a/indra/newview/llviewercontrol.cpp
+++ b/indra/newview/llviewercontrol.cpp
@@ -253,7 +253,7 @@ static void handleAudioVolumeChanged(const LLSD& newvalue)
 
 static bool handleJoystickChanged(const LLSD& newvalue)
 {
-	LLViewerJoystick::updateCamera(TRUE);
+	LLViewerJoystick::getInstance()->setCameraNeedsUpdate(TRUE);
 	return true;
 }
 
@@ -493,13 +493,51 @@ void settings_setup_listeners()
 	gSavedSettings.getControl("WLSkyDetail")->getSignal()->connect(boost::bind(&handleWLSkyDetailChanged, _1));
 	gSavedSettings.getControl("RenderLightingDetail")->getSignal()->connect(boost::bind(&handleRenderLightingDetailChanged, _1));
 	gSavedSettings.getControl("NumpadControl")->getSignal()->connect(boost::bind(&handleNumpadControlChanged, _1));
-	gSavedSettings.getControl("FlycamAxis0")->getSignal()->connect(boost::bind(&handleJoystickChanged, _1));
-	gSavedSettings.getControl("FlycamAxis1")->getSignal()->connect(boost::bind(&handleJoystickChanged, _1));
-	gSavedSettings.getControl("FlycamAxis2")->getSignal()->connect(boost::bind(&handleJoystickChanged, _1));
-	gSavedSettings.getControl("FlycamAxis3")->getSignal()->connect(boost::bind(&handleJoystickChanged, _1));
-	gSavedSettings.getControl("FlycamAxis4")->getSignal()->connect(boost::bind(&handleJoystickChanged, _1));
-	gSavedSettings.getControl("FlycamAxis5")->getSignal()->connect(boost::bind(&handleJoystickChanged, _1));
-	gSavedSettings.getControl("FlycamAxis6")->getSignal()->connect(boost::bind(&handleJoystickChanged, _1));
+	gSavedSettings.getControl("JoystickAxis0")->getSignal()->connect(boost::bind(&handleJoystickChanged, _1));
+	gSavedSettings.getControl("JoystickAxis1")->getSignal()->connect(boost::bind(&handleJoystickChanged, _1));
+	gSavedSettings.getControl("JoystickAxis2")->getSignal()->connect(boost::bind(&handleJoystickChanged, _1));
+	gSavedSettings.getControl("JoystickAxis3")->getSignal()->connect(boost::bind(&handleJoystickChanged, _1));
+	gSavedSettings.getControl("JoystickAxis4")->getSignal()->connect(boost::bind(&handleJoystickChanged, _1));
+	gSavedSettings.getControl("JoystickAxis5")->getSignal()->connect(boost::bind(&handleJoystickChanged, _1));
+	gSavedSettings.getControl("JoystickAxis6")->getSignal()->connect(boost::bind(&handleJoystickChanged, _1));
+	gSavedSettings.getControl("FlycamAxisScale0")->getSignal()->connect(boost::bind(&handleJoystickChanged, _1));
+	gSavedSettings.getControl("FlycamAxisScale1")->getSignal()->connect(boost::bind(&handleJoystickChanged, _1));
+	gSavedSettings.getControl("FlycamAxisScale2")->getSignal()->connect(boost::bind(&handleJoystickChanged, _1));
+	gSavedSettings.getControl("FlycamAxisScale3")->getSignal()->connect(boost::bind(&handleJoystickChanged, _1));
+	gSavedSettings.getControl("FlycamAxisScale4")->getSignal()->connect(boost::bind(&handleJoystickChanged, _1));
+	gSavedSettings.getControl("FlycamAxisScale5")->getSignal()->connect(boost::bind(&handleJoystickChanged, _1));
+	gSavedSettings.getControl("FlycamAxisScale6")->getSignal()->connect(boost::bind(&handleJoystickChanged, _1));
+	gSavedSettings.getControl("FlycamAxisDeadZone0")->getSignal()->connect(boost::bind(&handleJoystickChanged, _1));
+	gSavedSettings.getControl("FlycamAxisDeadZone1")->getSignal()->connect(boost::bind(&handleJoystickChanged, _1));
+	gSavedSettings.getControl("FlycamAxisDeadZone2")->getSignal()->connect(boost::bind(&handleJoystickChanged, _1));
+	gSavedSettings.getControl("FlycamAxisDeadZone3")->getSignal()->connect(boost::bind(&handleJoystickChanged, _1));
+	gSavedSettings.getControl("FlycamAxisDeadZone4")->getSignal()->connect(boost::bind(&handleJoystickChanged, _1));
+	gSavedSettings.getControl("FlycamAxisDeadZone5")->getSignal()->connect(boost::bind(&handleJoystickChanged, _1));
+	gSavedSettings.getControl("FlycamAxisDeadZone6")->getSignal()->connect(boost::bind(&handleJoystickChanged, _1));
+	gSavedSettings.getControl("AvatarAxisScale0")->getSignal()->connect(boost::bind(&handleJoystickChanged, _1));
+	gSavedSettings.getControl("AvatarAxisScale1")->getSignal()->connect(boost::bind(&handleJoystickChanged, _1));
+	gSavedSettings.getControl("AvatarAxisScale2")->getSignal()->connect(boost::bind(&handleJoystickChanged, _1));
+	gSavedSettings.getControl("AvatarAxisScale3")->getSignal()->connect(boost::bind(&handleJoystickChanged, _1));
+	gSavedSettings.getControl("AvatarAxisScale4")->getSignal()->connect(boost::bind(&handleJoystickChanged, _1));
+	gSavedSettings.getControl("AvatarAxisScale5")->getSignal()->connect(boost::bind(&handleJoystickChanged, _1));
+	gSavedSettings.getControl("AvatarAxisDeadZone0")->getSignal()->connect(boost::bind(&handleJoystickChanged, _1));
+	gSavedSettings.getControl("AvatarAxisDeadZone1")->getSignal()->connect(boost::bind(&handleJoystickChanged, _1));
+	gSavedSettings.getControl("AvatarAxisDeadZone2")->getSignal()->connect(boost::bind(&handleJoystickChanged, _1));
+	gSavedSettings.getControl("AvatarAxisDeadZone3")->getSignal()->connect(boost::bind(&handleJoystickChanged, _1));
+	gSavedSettings.getControl("AvatarAxisDeadZone4")->getSignal()->connect(boost::bind(&handleJoystickChanged, _1));
+	gSavedSettings.getControl("AvatarAxisDeadZone5")->getSignal()->connect(boost::bind(&handleJoystickChanged, _1));
+	gSavedSettings.getControl("BuildAxisScale0")->getSignal()->connect(boost::bind(&handleJoystickChanged, _1));
+	gSavedSettings.getControl("BuildAxisScale1")->getSignal()->connect(boost::bind(&handleJoystickChanged, _1));
+	gSavedSettings.getControl("BuildAxisScale2")->getSignal()->connect(boost::bind(&handleJoystickChanged, _1));
+	gSavedSettings.getControl("BuildAxisScale3")->getSignal()->connect(boost::bind(&handleJoystickChanged, _1));
+	gSavedSettings.getControl("BuildAxisScale4")->getSignal()->connect(boost::bind(&handleJoystickChanged, _1));
+	gSavedSettings.getControl("BuildAxisScale5")->getSignal()->connect(boost::bind(&handleJoystickChanged, _1));
+	gSavedSettings.getControl("BuildAxisDeadZone0")->getSignal()->connect(boost::bind(&handleJoystickChanged, _1));
+	gSavedSettings.getControl("BuildAxisDeadZone1")->getSignal()->connect(boost::bind(&handleJoystickChanged, _1));
+	gSavedSettings.getControl("BuildAxisDeadZone2")->getSignal()->connect(boost::bind(&handleJoystickChanged, _1));
+	gSavedSettings.getControl("BuildAxisDeadZone3")->getSignal()->connect(boost::bind(&handleJoystickChanged, _1));
+	gSavedSettings.getControl("BuildAxisDeadZone4")->getSignal()->connect(boost::bind(&handleJoystickChanged, _1));
+	gSavedSettings.getControl("BuildAxisDeadZone5")->getSignal()->connect(boost::bind(&handleJoystickChanged, _1));
     gSavedSettings.getControl("DebugViews")->getSignal()->connect(boost::bind(&handleDebugViewsChanged, _1));
     gSavedSettings.getControl("UserLogFile")->getSignal()->connect(boost::bind(&handleLogFileChanged, _1));
 	gSavedSettings.getControl("RenderHideGroupTitle")->getSignal()->connect(boost::bind(handleHideGroupTitleChanged, _1));
diff --git a/indra/newview/llviewerjoystick.cpp b/indra/newview/llviewerjoystick.cpp
index a8e898999a..cd4eddb5e1 100644
--- a/indra/newview/llviewerjoystick.cpp
+++ b/indra/newview/llviewerjoystick.cpp
@@ -1,6 +1,6 @@
 /** 
  * @file llviewerjoystick.cpp
- * @brief Joystick functionality.
+ * @brief Joystick / NDOF device functionality.
  *
  * $LicenseInfo:firstyear=2002&license=viewergpl$
  * 
@@ -30,39 +30,661 @@
  */
 
 #include "llviewerprecompiledheaders.h"
+
+#include "llviewerjoystick.h"
+
 #include "llviewercontrol.h"
 #include "llviewerwindow.h"
 #include "llviewercamera.h"
-#include "llviewerjoystick.h"
 #include "llappviewer.h"
 #include "llkeyboard.h"
+#include "lltoolmgr.h"
+#include "llselectmgr.h"
+#include "llviewermenu.h"
+#include "llagent.h"
+#include "llfocusmgr.h"
+
+
+// ----------------------------------------------------------------------------
+// Constants
+
+#define  X_I	1
+#define  Y_I	2
+#define  Z_I	0
+#define RX_I	4
+#define RY_I	5
+#define RZ_I	3
+
+// flycam translations in build mode should be reduced
+const F32 BUILDMODE_FLYCAM_T_SCALE = 3.f;
+
+bool LLViewerJoystick::sOverrideCamera = false;
+F32  LLViewerJoystick::sLastDelta[] = {0,0,0,0,0,0,0};
+F32  LLViewerJoystick::sDelta[] = {0,0,0,0,0,0,0};
+
+// These constants specify the maximum absolute value coming in from the device.
+// HACK ALERT! the value of MAX_JOYSTICK_INPUT_VALUE is not arbitrary as it 
+// should be.  It has to be equal to 3000 because the SpaceNavigator on Windows 
+// refuses to respond to the DirectInput SetProperty call; it always returns 
+// values in the [-3000, 3000] range.
+#define MAX_SPACENAVIGATOR_INPUT  3000.0f
+#define MAX_JOYSTICK_INPUT_VALUE  MAX_SPACENAVIGATOR_INPUT
+
+// -----------------------------------------------------------------------------
+#if LIB_NDOF
+NDOF_HotPlugResult LLViewerJoystick::HotPlugAddCallback(NDOF_Device *dev)
+{
+	LLViewerJoystick* joystick(LLViewerJoystick::getInstance());
+	if (joystick->mDriverState == JDS_UNINITIALIZED)
+	{
+        llinfos << "HotPlugAddCallback: will use device:" << llendl;
+		ndof_dump(dev);
+		joystick->mNdofDev = dev;
+        joystick->mDriverState = JDS_INITIALIZED;
+        return NDOF_KEEP_HOTPLUGGED;
+	}
+    return NDOF_DISCARD_HOTPLUGGED;
+}
+#endif
+
+// -----------------------------------------------------------------------------
+#if LIB_NDOF
+void LLViewerJoystick::HotPlugRemovalCallback(NDOF_Device *dev)
+{
+	LLViewerJoystick* joystick(LLViewerJoystick::getInstance());
+	if (joystick->mNdofDev == dev)
+	{
+        llinfos << "HotPlugRemovalCallback: joystick->mNdofDev=" 
+				<< joystick->mNdofDev << "; removed device:" << llendl;
+		ndof_dump(dev);
+		joystick->mDriverState = JDS_UNINITIALIZED;
+	}
+}
+#endif
+
+// -----------------------------------------------------------------------------
+LLViewerJoystick::LLViewerJoystick()
+:	mDriverState(JDS_UNINITIALIZED),
+	mNdofDev(NULL),
+	mResetFlag(false),
+	mCameraUpdated(true)
+{
+	for (int i = 0; i < 6; i++)
+	{
+		mAxes[i] = sDelta[i] = sLastDelta[i] = 0.0f;
+	}
+	
+	memset(mBtn, 0, sizeof(mBtn));
+
+	// factor in bandwidth? bandwidth = gViewerStats->mKBitStat
+	mPerfScale = 4000.f / gSysCPU.getMhz();
+}
+
+// -----------------------------------------------------------------------------
+LLViewerJoystick::~LLViewerJoystick()
+{
+	if (mDriverState == JDS_INITIALIZED)
+	{
+		terminate();
+	}
+}
+
+// -----------------------------------------------------------------------------
+void LLViewerJoystick::init()
+{
+#if LIB_NDOF
+	static bool libinit = false;
+	mDriverState = JDS_INITIALIZING;
+
+	if (libinit == false)
+	{
+		if (ndof_libinit(HotPlugAddCallback, 
+						 HotPlugRemovalCallback, 
+						 NULL))
+		{
+			mDriverState = JDS_UNINITIALIZED;
+		}
+		else
+		{
+			// NB: ndof_libinit succeeds when there's no device
+			libinit = true;
+
+			// allocate memory once for an eventual device
+			mNdofDev = ndof_create();
+		}
+	}
+
+	if (libinit)
+	{
+		if (mNdofDev)
+		{
+			// Different joysticks will return different ranges of raw values.
+			// Since we want to handle every device in the same uniform way, 
+			// we initialize the mNdofDev struct and we set the range 
+			// of values we would like to receive. 
+			// 
+			// HACK: On Windows, libndofdev passes our range to DI with a 
+			// SetProperty call. This works but with one notable exception, the
+			// SpaceNavigator, who doesn't seem to care about the SetProperty
+			// call. In theory, we should handle this case inside libndofdev. 
+			// However, the range we're setting here is arbitrary anyway, 
+			// so let's just use the SpaceNavigator range for our purposes. 
+			mNdofDev->axes_min = (long)-MAX_JOYSTICK_INPUT_VALUE;
+			mNdofDev->axes_max = (long)+MAX_JOYSTICK_INPUT_VALUE;
+
+			// libndofdev could be used to return deltas.  Here we choose to
+			// just have the absolute values instead.
+			mNdofDev->absolute = 1;
+
+			// init & use the first suitable NDOF device found on the USB chain
+			if (ndof_init_first(mNdofDev, NULL))
+			{
+				mDriverState = JDS_UNINITIALIZED;
+				llwarns << "ndof_init_first FAILED" << llendl;
+			}
+			else
+			{
+				mDriverState = JDS_INITIALIZED;
+			}
+		}
+		else
+		{
+			mDriverState = JDS_UNINITIALIZED;
+		}
+	}
+
+	llinfos << "ndof: mDriverState=" << mDriverState << "; mNdofDev=" 
+			<< mNdofDev << "; libinit=" << libinit << llendl;
+#endif
+}
+
+// -----------------------------------------------------------------------------
+void LLViewerJoystick::terminate()
+{
+#if LIB_NDOF
+
+	ndof_libcleanup();
+	llinfos << "Terminated connection with NDOF device." << llendl;
+
+#endif
+}
+
+// -----------------------------------------------------------------------------
+void LLViewerJoystick::updateStatus()
+{
+#if LIB_NDOF
+
+	ndof_update(mNdofDev);
+
+	for (int i=0; i<6; i++)
+	{
+		mAxes[i] = (F32) mNdofDev->axes[i] / mNdofDev->axes_max;
+	}
+
+	for (int i=0; i<16; i++)
+	{
+		mBtn[i] = mNdofDev->buttons[i];
+	}
+	
+#endif
+}
+
+// -----------------------------------------------------------------------------
+F32 LLViewerJoystick::getJoystickAxis(U32 axis) const
+{
+	if (axis < 6)
+	{
+		return mAxes[axis];
+	}
+	return 0.f;
+}
+
+// -----------------------------------------------------------------------------
+U32 LLViewerJoystick::getJoystickButton(U32 button) const
+{
+	if (button < 16)
+	{
+		return mBtn[button];
+	}
+	return 0;
+}
+
+// -----------------------------------------------------------------------------
+void LLViewerJoystick::agentJump()
+{
+    gAgent.moveUp(1);
+}
+
+// -----------------------------------------------------------------------------
+void LLViewerJoystick::agentSlide(F32 inc)
+{
+	if (inc < 0)
+	{
+		gAgent.moveLeft(1);
+	}
+	else if (inc > 0)
+	{
+		gAgent.moveLeft(-1);
+	}
+}
+
+// -----------------------------------------------------------------------------
+void LLViewerJoystick::agentPush(F32 inc)
+{
+	if (inc < 0)                            // forward
+	{
+		gAgent.moveAt(1, false);
+	}
+	else if (inc > 0)                       // backward
+	{
+		gAgent.moveAt(-1, false);
+	}
+}
+
+// -----------------------------------------------------------------------------
+void LLViewerJoystick::agentFly(F32 inc)
+{
+	if (inc < 0)
+	{
+		if (gAgent.getFlying())
+		{
+			gAgent.moveUp(1);
+		}
+		else
+		{
+			gAgent.setFlying(true);
+		}
+	}
+	else if (inc > 0)
+	{
+		// crouch
+		gAgent.moveUp(-1);
+	}
+}
+
+// -----------------------------------------------------------------------------
+void LLViewerJoystick::agentRotate(F32 pitch_inc, F32 yaw_inc)
+{
+	LLQuaternion new_rot;
+	pitch_inc = gAgent.clampPitchToLimits(-pitch_inc);
+	const LLQuaternion qx(pitch_inc, gAgent.getLeftAxis());
+	const LLQuaternion qy(-yaw_inc, gAgent.getReferenceUpVector());
+	new_rot.setQuat(qx * qy);
+	gAgent.rotate(new_rot);
+}
+
+// -----------------------------------------------------------------------------
+void LLViewerJoystick::resetDeltas(S32 axis[], bool flycam_and_build_mode)
+{
+	for (U32 i = 0; i < 6; i++)
+	{
+		sLastDelta[i] = -mAxes[axis[i]];
+		sDelta[i] = 0.f;
+	}
+
+	if (flycam_and_build_mode)
+	{
+		sLastDelta[X_I] /= BUILDMODE_FLYCAM_T_SCALE;
+		sLastDelta[Y_I] /= BUILDMODE_FLYCAM_T_SCALE;
+		sLastDelta[Z_I] /= BUILDMODE_FLYCAM_T_SCALE;
+	}
+
+	sLastDelta[6] = sDelta[6] = 0.f;
+	mResetFlag = false;
+}
+
+// -----------------------------------------------------------------------------
+void LLViewerJoystick::moveObjects(bool reset)
+{
+	static bool toggle_send_to_sim = false;
+
+	if (!gFocusMgr.getAppHasFocus() || mDriverState != JDS_INITIALIZED)
+	{
+		return;
+	}
+
+	S32 axis[] = 
+	{
+		gSavedSettings.getS32("JoystickAxis0"),
+		gSavedSettings.getS32("JoystickAxis1"),
+		gSavedSettings.getS32("JoystickAxis2"),
+		gSavedSettings.getS32("JoystickAxis3"),
+		gSavedSettings.getS32("JoystickAxis4"),
+		gSavedSettings.getS32("JoystickAxis5"),
+	};
+
+	if (reset || mResetFlag)
+	{
+		resetDeltas(axis);
+		return;
+	}
+
+	F32 axis_scale[] =
+	{
+		gSavedSettings.getF32("BuildAxisScale0"),
+		gSavedSettings.getF32("BuildAxisScale1"),
+		gSavedSettings.getF32("BuildAxisScale2"),
+		gSavedSettings.getF32("BuildAxisScale3"),
+		gSavedSettings.getF32("BuildAxisScale4"),
+		gSavedSettings.getF32("BuildAxisScale5"),
+	};
+
+	F32 dead_zone[] =
+	{
+		gSavedSettings.getF32("BuildAxisDeadZone0"),
+		gSavedSettings.getF32("BuildAxisDeadZone1"),
+		gSavedSettings.getF32("BuildAxisDeadZone2"),
+		gSavedSettings.getF32("BuildAxisDeadZone3"),
+		gSavedSettings.getF32("BuildAxisDeadZone4"),
+		gSavedSettings.getF32("BuildAxisDeadZone5"),
+	};
+
+	F32 cur_delta[6];
+	F32 time = gFrameIntervalSeconds;
+
+	// avoid making ridicously big movements if there's a big drop in fps 
+	if (time > .2f)
+	{
+		time = .2f;
+	}
+
+	// max feather is 32
+	F32 feather = gSavedSettings.getF32("BuildFeathering"); 
+	bool is_zero = true, absolute = gSavedSettings.getBOOL("Cursor3D");
+	
+	for (U32 i = 0; i < 6; i++)
+	{
+		cur_delta[i] = -mAxes[axis[i]];
+		F32 tmp = cur_delta[i];
+		if (absolute)
+		{
+			cur_delta[i] = cur_delta[i] - sLastDelta[i];
+		}
+		sLastDelta[i] = tmp;
+		is_zero = is_zero && (cur_delta[i] == 0.f);
+			
+		if (cur_delta[i] > 0)
+		{
+			cur_delta[i] = llmax(cur_delta[i]-dead_zone[i], 0.f);
+		}
+		else
+		{
+			cur_delta[i] = llmin(cur_delta[i]+dead_zone[i], 0.f);
+		}
+		cur_delta[i] *= axis_scale[i];
+		
+		if (!absolute)
+		{
+			cur_delta[i] *= time;
+		}
 
-static LLQuaternion sFlycamRotation;
-static LLVector3 sFlycamPosition;
-static F32		sFlycamZoom;
+		sDelta[i] = sDelta[i] + (cur_delta[i]-sDelta[i])*time*feather;
+	}
 
-BOOL  LLViewerJoystick::sOverrideCamera = FALSE;
+	U32 upd_type = UPD_NONE;
+	LLVector3 v;
+    
+	if (!is_zero)
+	{
+		if (sDelta[0] || sDelta[1] || sDelta[2])
+		{
+			upd_type |= UPD_POSITION;
+			v.setVec(sDelta[0], sDelta[1], sDelta[2]);
+		}
+		
+		if (sDelta[3] || sDelta[4] || sDelta[5])
+		{
+			upd_type |= UPD_ROTATION;
+		}
+				
+		// the selection update could fail, so we won't send 
+		if (LLSelectMgr::getInstance()->selectionMove(v, sDelta[3],sDelta[4],sDelta[5], upd_type))
+		{
+			toggle_send_to_sim = true;
+		}
+	}
+	else if (toggle_send_to_sim)
+	{
+		LLSelectMgr::getInstance()->sendSelectionMove();
+		toggle_send_to_sim = false;
+	}
+}
 
-void LLViewerJoystick::updateCamera(BOOL reset)
+// -----------------------------------------------------------------------------
+void LLViewerJoystick::moveAvatar(bool reset)
 {
-	static F32 last_delta[] = {0,0,0,0,0,0,0};
-	static F32 delta[] = { 0,0,0,0,0,0,0 };
+	if (!gFocusMgr.getAppHasFocus() || mDriverState != JDS_INITIALIZED)
+	{
+		return;
+	}
+
+	S32 axis[] = 
+	{
+		// [1 0 2 4  3  5]
+		// [Z X Y RZ RX RY]
+		gSavedSettings.getS32("JoystickAxis0"),
+		gSavedSettings.getS32("JoystickAxis1"),
+		gSavedSettings.getS32("JoystickAxis2"),
+		gSavedSettings.getS32("JoystickAxis3"),
+		gSavedSettings.getS32("JoystickAxis4"),
+		gSavedSettings.getS32("JoystickAxis5")
+	};
+
+	if (reset || mResetFlag)
+	{
+		resetDeltas(axis);
+		if (reset)
+		{
+			// Note: moving the agent triggers agent camera mode;
+			//  don't do this every time we set mResetFlag (e.g. because we gained focus)
+			gAgent.moveAt(0, true);
+		}
+		return;
+	}
+
+	if (mBtn[1] == 1)
+    {
+		agentJump();
+		return;
+    }
+
+	F32 axis_scale[] =
+	{
+		gSavedSettings.getF32("AvatarAxisScale0"),
+		gSavedSettings.getF32("AvatarAxisScale1"),
+		gSavedSettings.getF32("AvatarAxisScale2"),
+		gSavedSettings.getF32("AvatarAxisScale3"),
+		gSavedSettings.getF32("AvatarAxisScale4"),
+		gSavedSettings.getF32("AvatarAxisScale5")
+	};
 
-	LLWindow* window = gViewerWindow->getWindow();
+	F32 dead_zone[] =
+	{
+		gSavedSettings.getF32("AvatarAxisDeadZone0"),
+		gSavedSettings.getF32("AvatarAxisDeadZone1"),
+		gSavedSettings.getF32("AvatarAxisDeadZone2"),
+		gSavedSettings.getF32("AvatarAxisDeadZone3"),
+		gSavedSettings.getF32("AvatarAxisDeadZone4"),
+		gSavedSettings.getF32("AvatarAxisDeadZone5")
+	};
 
+	// time interval in seconds between this frame and the previous
 	F32 time = gFrameIntervalSeconds;
 
+	// avoid making ridicously big movements if there's a big drop in fps 
+	if (time > .2f)
+	{
+		time = .2f;
+	}
+
+	// note: max feather is 32.0
+	F32 feather = gSavedSettings.getF32("AvatarFeathering"); 
+	
+	F32 cur_delta[6];
+	F32 val, dom_mov = 0.f;
+	U32 dom_axis = Z_I;
+#if LIB_NDOF
+    bool absolute = (gSavedSettings.getBOOL("Cursor3D") && mNdofDev->absolute);
+#else
+    bool absolute = false;
+#endif
+	// remove dead zones and determine biggest movement on the joystick 
+	for (U32 i = 0; i < 6; i++)
+	{
+		cur_delta[i] = -mAxes[axis[i]];
+		if (absolute)
+		{
+			F32 tmp = cur_delta[i];
+			cur_delta[i] = cur_delta[i] - sLastDelta[i];
+			sLastDelta[i] = tmp;
+		}
+
+		if (cur_delta[i] > 0)
+		{
+			cur_delta[i] = llmax(cur_delta[i]-dead_zone[i], 0.f);
+		}
+		else
+		{
+			cur_delta[i] = llmin(cur_delta[i]+dead_zone[i], 0.f);
+		}
+
+		// we don't care about Roll (RZ) and Z is calculated after the loop
+        if (i != Z_I && i != RZ_I)
+		{
+			// find out the axis with the biggest joystick motion
+			val = fabs(cur_delta[i]);
+			if (val > dom_mov)
+			{
+				dom_axis = i;
+				dom_mov = val;
+			}
+		}
+	}
+
+	// forward|backward movements overrule the real dominant movement if 
+	// they're bigger than its 20%. This is what you want cos moving forward
+	// is what you do most. We also added a special (even more lenient) case 
+	// for RX|RY to allow walking while pitching n' turning
+	if (fabs(cur_delta[Z_I]) > .2f * dom_mov
+		|| ((dom_axis == RX_I || dom_axis == RY_I) 
+			&& fabs(cur_delta[Z_I]) > .05f * dom_mov))
+    {
+		dom_axis = Z_I;
+	}
+
+	sDelta[X_I] = -cur_delta[X_I] * axis_scale[X_I];
+	sDelta[Y_I] = -cur_delta[Y_I] * axis_scale[Y_I];
+	sDelta[Z_I] = -cur_delta[Z_I] * axis_scale[Z_I];
+	cur_delta[RX_I] *= -axis_scale[RX_I] * mPerfScale;
+	cur_delta[RY_I] *= -axis_scale[RY_I] * mPerfScale;
+		
+	if (!absolute)
+	{
+		cur_delta[RX_I] *= time;
+		cur_delta[RY_I] *= time;
+	}
+	sDelta[RX_I] += (cur_delta[RX_I] - sDelta[RX_I]) * time * feather;
+	sDelta[RY_I] += (cur_delta[RY_I] - sDelta[RY_I]) * time * feather;
+	
+    switch (dom_axis)
+    {
+        case X_I:                                         // move sideways
+			agentSlide(sDelta[X_I]);
+            break;
+        
+        case Z_I:                                         // forward/back
+		{
+            agentPush(sDelta[Z_I]);
+            
+            if (fabs(sDelta[Y_I])  > .1f)
+			{
+				agentFly(sDelta[Y_I]);
+			}
+		
+			// too many rotations during walking can be confusing, so apply
+			// the deadzones one more time (quick & dirty), at 50%|30% power
+			F32 eff_rx = .3f * dead_zone[RX_I];
+			F32 eff_ry = .3f * dead_zone[RY_I];
+		
+			if (sDelta[RX_I] > 0)
+			{
+				eff_rx = llmax(sDelta[RX_I] - eff_rx, 0.f);
+			}
+			else
+			{
+				eff_rx = llmin(sDelta[RX_I] + eff_rx, 0.f);
+			}
+
+			if (sDelta[RY_I] > 0)
+			{
+				eff_ry = llmax(sDelta[RY_I] - eff_ry, 0.f);
+			}
+			else
+			{
+				eff_ry = llmin(sDelta[RY_I] + eff_ry, 0.f);
+			}
+			
+			
+			if (fabs(eff_rx) > 0.f || fabs(eff_ry) > 0.f)
+			{
+				if (gAgent.getFlying())
+				{
+					agentRotate(eff_rx, eff_ry);
+				}
+				else
+				{
+					agentRotate(eff_rx, 2.f * eff_ry);
+				}
+			}
+            break;
+		}   
+        case Y_I:                                          // up/crouch
+            agentFly(sDelta[Y_I]);
+            break;
+            
+        case RX_I:                                         // pitch
+        case RY_I:                                         // turn
+			agentRotate(sDelta[RX_I], sDelta[RY_I]);
+            break;
+        // case RZ_I: roll is unused in avatar mode
+    }// switch
+}
+
+// -----------------------------------------------------------------------------
+void LLViewerJoystick::moveFlycam(bool reset)
+{
+	static LLQuaternion 		sFlycamRotation;
+	static LLVector3    		sFlycamPosition;
+	static F32          		sFlycamZoom;
+	
+	if (!gFocusMgr.getAppHasFocus() || mDriverState != JDS_INITIALIZED)
+	{
+		return;
+	}
+
 	S32 axis[] = 
 	{
-		gSavedSettings.getS32("FlycamAxis0"),
-		gSavedSettings.getS32("FlycamAxis1"),
-		gSavedSettings.getS32("FlycamAxis2"),
-		gSavedSettings.getS32("FlycamAxis3"),
-		gSavedSettings.getS32("FlycamAxis4"),
-		gSavedSettings.getS32("FlycamAxis5"),
-		gSavedSettings.getS32("FlycamAxis6")
+		gSavedSettings.getS32("JoystickAxis0"),
+		gSavedSettings.getS32("JoystickAxis1"),
+		gSavedSettings.getS32("JoystickAxis2"),
+		gSavedSettings.getS32("JoystickAxis3"),
+		gSavedSettings.getS32("JoystickAxis4"),
+		gSavedSettings.getS32("JoystickAxis5"),
+		gSavedSettings.getS32("JoystickAxis6")
 	};
 
+	bool in_build_mode = LLToolMgr::getInstance()->inBuildMode();
+	if (reset || mResetFlag)
+	{
+		sFlycamPosition = LLViewerCamera::getInstance()->getOrigin();
+		sFlycamRotation = LLViewerCamera::getInstance()->getQuaternion();
+		sFlycamZoom = LLViewerCamera::getInstance()->getView();
+		
+		resetDeltas(axis, in_build_mode);
+
+		return;
+	}
+
 	F32 axis_scale[] =
 	{
 		gSavedSettings.getF32("FlycamAxisScale0"),
@@ -85,33 +707,37 @@ void LLViewerJoystick::updateCamera(BOOL reset)
 		gSavedSettings.getF32("FlycamAxisDeadZone6")
 	};
 
-	if (reset)
-	{
-		sFlycamPosition = LLViewerCamera::getInstance()->getOrigin();
-		sFlycamRotation = LLViewerCamera::getInstance()->getQuaternion();
-		sFlycamZoom = LLViewerCamera::getInstance()->getView();
+	F32 time = gFrameIntervalSeconds;
 
-		for (U32 i = 0; i < 7; i++)
-		{
-			last_delta[i] = -window->getJoystickAxis(axis[i]);
-			delta[i] = 0.f;
-		}
-		return;
+	// avoid making ridicously big movements if there's a big drop in fps 
+	if (time > .2f)
+	{
+		time = .2f;
 	}
 
 	F32 cur_delta[7];
 	F32 feather = gSavedSettings.getF32("FlycamFeathering");
-	BOOL absolute = gSavedSettings.getBOOL("FlycamAbsolute");
+	bool absolute = gSavedSettings.getBOOL("Cursor3D");
 
 	for (U32 i = 0; i < 7; i++)
 	{
-		cur_delta[i] = -window->getJoystickAxis(axis[i]);	
+		cur_delta[i] = -getJoystickAxis(axis[i]);
+
+		// we need smaller camera movements in build mode
+		if (in_build_mode)
+		{
+			if (i == X_I || i == Y_I || i == Z_I)
+			{
+				cur_delta[i] /= BUILDMODE_FLYCAM_T_SCALE;
+			}
+		}
+
 		F32 tmp = cur_delta[i];
 		if (absolute)
 		{
-			cur_delta[i] = cur_delta[i] - last_delta[i];
+			cur_delta[i] = cur_delta[i] - sLastDelta[i];
 		}
-		last_delta[i] = tmp;
+		sLastDelta[i] = tmp;
 
 		if (cur_delta[i] > 0)
 		{
@@ -128,18 +754,15 @@ void LLViewerJoystick::updateCamera(BOOL reset)
 			cur_delta[i] *= time;
 		}
 
-		delta[i] = delta[i] + (cur_delta[i]-delta[i])*time*feather;
+		sDelta[i] = sDelta[i] + (cur_delta[i]-sDelta[i])*time*feather;
 	}
 	
-	sFlycamPosition += LLVector3(delta) * sFlycamRotation;
+	sFlycamPosition += LLVector3(sDelta) * sFlycamRotation;
 
-	LLMatrix3 rot_mat(delta[3],
-					  delta[4],
-					  delta[5]);
-	
+	LLMatrix3 rot_mat(sDelta[3], sDelta[4], sDelta[5]);
 	sFlycamRotation = LLQuaternion(rot_mat)*sFlycamRotation;
 
-	if (gSavedSettings.getBOOL("FlycamAutoLeveling"))
+	if (gSavedSettings.getBOOL("AutoLeveling"))
 	{
 		LLMatrix3 level(sFlycamRotation);
 
@@ -153,17 +776,17 @@ void LLViewerJoystick::updateCamera(BOOL reset)
 		level.setRows(x,y,z);
 		level.orthogonalize();
 				
-		LLQuaternion quat = LLQuaternion(level);
+		LLQuaternion quat(level);
 		sFlycamRotation = nlerp(llmin(feather*time,1.f), sFlycamRotation, quat);
 	}
 
-	if (gSavedSettings.getBOOL("FlycamZoomDirect"))
+	if (gSavedSettings.getBOOL("ZoomDirect"))
 	{
-		sFlycamZoom = last_delta[6]*axis_scale[6]+dead_zone[6];
+		sFlycamZoom = sLastDelta[6]*axis_scale[6]+dead_zone[6];
 	}
 	else
 	{
-		sFlycamZoom += delta[6];
+		sFlycamZoom += sDelta[6];
 	}
 
 	LLMatrix3 mat(sFlycamRotation);
@@ -175,101 +798,52 @@ void LLViewerJoystick::updateCamera(BOOL reset)
 	LLViewerCamera::getInstance()->mZAxis = LLVector3(mat.mMatrix[2]);
 }
 
-
+// -----------------------------------------------------------------------------
 void LLViewerJoystick::scanJoystick()
 {
-	if (!sOverrideCamera)
+	if (mDriverState != JDS_INITIALIZED)
 	{
-		static U32 joystick_state = 0;
-		static U32 button_state = 0;
+		return;
+	}
 
-		F32 xval = gViewerWindow->getWindow()->getJoystickAxis(0);
-		F32 yval = gViewerWindow->getWindow()->getJoystickAxis(1);
+#if LL_WINDOWS
+	// On windows, the flycam is updated syncronously with a timer, so there is
+	// no need to update the status of the joystick here.
+	if (!sOverrideCamera)
+#endif
+	updateStatus();
 
-		if (xval <= -0.5f)
-		{
-			if (!(joystick_state & 0x1))
-			{
-				gKeyboard->handleTranslatedKeyDown(KEY_PAD_LEFT, 0);
-				joystick_state |= 0x1;
-			}
-		}
-		else 
-		{
-			if (joystick_state & 0x1)
-			{
-				gKeyboard->handleTranslatedKeyUp(KEY_PAD_LEFT, 0);
-				joystick_state &= ~0x1;
-			}
-		}
-		if (xval >= 0.5f)
-		{
-			if (!(joystick_state & 0x2))
-			{
-				gKeyboard->handleTranslatedKeyDown(KEY_PAD_RIGHT, 0);
-				joystick_state |= 0x2;
-			}
-		}
-		else 
-		{
-			if (joystick_state & 0x2)
-			{
-				gKeyboard->handleTranslatedKeyUp(KEY_PAD_RIGHT, 0);
-				joystick_state &= ~0x2;
-			}
-		}
-		if (yval <= -0.5f)
-		{
-			if (!(joystick_state & 0x4))
-			{
-				gKeyboard->handleTranslatedKeyDown(KEY_PAD_UP, 0);
-				joystick_state |= 0x4;
-			}
-		}
-		else 
-		{
-			if (joystick_state & 0x4)
-			{
-				gKeyboard->handleTranslatedKeyUp(KEY_PAD_UP, 0);
-				joystick_state &= ~0x4;
-			}
-		}
-		if (yval >=  0.5f)
-		{
-			if (!(joystick_state & 0x8))
-			{
-				gKeyboard->handleTranslatedKeyDown(KEY_PAD_DOWN, 0);
-				joystick_state |= 0x8;
-			}
-		}
-		else 
-		{
-			if (joystick_state & 0x8)
-			{
-				gKeyboard->handleTranslatedKeyUp(KEY_PAD_DOWN, 0);
-				joystick_state &= ~0x8;
-			}
-		}
+	static long toggle_flycam = 0;
 
-		for( int i = 0; i < 15; i++ )
+	if (mBtn[0] == 1)
+    {
+		if (mBtn[0] != toggle_flycam)
 		{
-			if ( gViewerWindow->getWindow()->getJoystickButton(i) & 0x80 )
-			{
-				if (!(button_state & (1<<i)))
-				{
-					gKeyboard->handleTranslatedKeyDown(KEY_BUTTON1+i, 0);
-					button_state |= (1<<i);
-				}
-			}
-			else
-			{
-				if (button_state & (1<<i))
-				{
-					gKeyboard->handleTranslatedKeyUp(KEY_BUTTON1+i, 0);
-					button_state &= ~(1<<i);
-				}
-			}
+			handle_toggle_flycam();
+			toggle_flycam = 1;
 		}
 	}
+	else
+	{
+		toggle_flycam = 0;
+	}
+	
+	if (!sOverrideCamera && !LLToolMgr::getInstance()->inBuildMode())
+	{
+		moveAvatar();
+	}
 }
 
+// -----------------------------------------------------------------------------
+bool LLViewerJoystick::isLikeSpaceNavigator() const
+{
+#if LIB_NDOF	
+	return (isJoystickInitialized() 
+			&& (strncmp(mNdofDev->product, "SpaceNavigator", 14) == 0
+				|| strncmp(mNdofDev->product, "SpaceExplorer", 13) == 0
+				|| strncmp(mNdofDev->product, "SpaceTraveler", 13) == 0
+				|| strncmp(mNdofDev->product, "SpacePilot", 10) == 0));
+#else
+	return false;
+#endif
+}
diff --git a/indra/newview/llviewerjoystick.h b/indra/newview/llviewerjoystick.h
index f2fc24b813..47acf17f17 100644
--- a/indra/newview/llviewerjoystick.h
+++ b/indra/newview/llviewerjoystick.h
@@ -1,6 +1,6 @@
 /** 
  * @file llviewerjoystick.h
- * @brief Viewer joystick functionality.
+ * @brief Viewer joystick / NDOF device functionality.
  *
  * $LicenseInfo:firstyear=2001&license=viewergpl$
  * 
@@ -32,12 +32,69 @@
 #ifndef LL_LLVIEWERJOYSTICK_H
 #define LL_LLVIEWERJOYSTICK_H
 
-class LLViewerJoystick
+#include "stdtypes.h"
+
+#define LIB_NDOF (LL_WINDOWS || LL_DARWIN)
+
+#if LIB_NDOF
+#include "ndofdev_external.h"
+#else
+#define NDOF_Device	void
+#define NDOF_HotPlugResult S32
+#endif
+
+typedef enum e_joystick_driver_state
+{
+	JDS_UNINITIALIZED,
+	JDS_INITIALIZED,
+	JDS_INITIALIZING
+} EJoystickDriverState;
+
+class LLViewerJoystick : public LLSingleton<LLViewerJoystick>
 {
 public:
-	static BOOL sOverrideCamera;
-	static void scanJoystick();
-	static void updateCamera(BOOL reset = FALSE);
+	static bool sOverrideCamera;
+	
+	void init();
+	void updateStatus();
+	void scanJoystick();
+	void moveObjects(bool reset = false);
+	void moveAvatar(bool reset = false);
+	void moveFlycam(bool reset = false);
+	F32 getJoystickAxis(U32 axis) const;
+	U32 getJoystickButton(U32 button) const;
+	bool isJoystickInitialized() const {return (mDriverState==JDS_INITIALIZED);}
+	bool isLikeSpaceNavigator() const;
+	void setNeedsReset(bool reset = true) { mResetFlag = reset; }
+	void setCameraNeedsUpdate(bool b)     { mCameraUpdated = b; }
+	bool getCameraNeedsUpdate() const     { return mCameraUpdated; }
+	
+	LLViewerJoystick();
+	virtual ~LLViewerJoystick();
+	
+protected:
+	void terminate();
+	void agentSlide(F32 inc);
+	void agentPush(F32 inc);
+	void agentFly(F32 inc);
+	void agentRotate(F32 pitch_inc, F32 turn_inc);
+    void agentJump();
+	void resetDeltas(S32 axis[], bool flycam_and_build = false);
+#if LIB_NDOF
+	static NDOF_HotPlugResult HotPlugAddCallback(NDOF_Device *dev);
+	static void HotPlugRemovalCallback(NDOF_Device *dev);
+#endif
+	
+private:
+	F32						mAxes[6];
+	long					mBtn[16];
+	EJoystickDriverState	mDriverState;
+	NDOF_Device				*mNdofDev;
+	bool					mResetFlag;
+	F32						mPerfScale;
+	bool					mCameraUpdated;
+	static F32				sLastDelta[7];
+	static F32				sDelta[7];
 };
 
 #endif
diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp
index 4234d4862c..ec52118098 100644
--- a/indra/newview/llviewermenu.cpp
+++ b/indra/newview/llviewermenu.cpp
@@ -67,7 +67,6 @@
 #include "llagentpilot.h"
 #include "llbox.h"
 #include "llcallingcard.h"
-#include "llcameraview.h"
 #include "llclipboard.h"
 #include "llcompilequeue.h"
 #include "llconsole.h"
@@ -466,8 +465,6 @@ void handle_dump_image_list(void*);
 
 void handle_crash(void*);
 void handle_dump_followcam(void*);
-void handle_toggle_flycam(void*);
-BOOL check_flycam(void*);
 void handle_viewer_enable_message_log(void*);
 void handle_viewer_disable_message_log(void*);
 void handle_send_postcard(void*);
@@ -1082,9 +1079,6 @@ void init_client_menu(LLMenuGL* menu)
 									   &menu_check_control,
 									   (void*)"DisableCameraConstraints"));
 
-	menu->append(new LLMenuItemCheckGL("Joystick Flycam", 
-		&handle_toggle_flycam,NULL,&check_flycam,NULL));
-		
 	menu->append(new LLMenuItemCheckGL("Mouse Smoothing",
 										&menu_toggle_control,
 										NULL,
@@ -1825,9 +1819,17 @@ bool toggle_build_mode()
 	{
 		// just reset the view, will pull us out of edit mode
 		handle_reset_view();
+
+		// avoid spurious avatar movements pulling out of edit mode
+		LLViewerJoystick::getInstance()->moveAvatar(true);
 	}
 	else
 	{
+		if (LLViewerJoystick::sOverrideCamera)
+		{
+			handle_toggle_flycam();
+		}
+			
 		if (gAgent.getFocusOnAvatar() && gSavedSettings.getBOOL("EditCameraMovement") )
 		{
 			// zoom in if we're looking at the avatar
@@ -1855,6 +1857,46 @@ class LLViewBuildMode : public view_listener_t
 };
 
 
+class LLViewJoystickFlycam : public view_listener_t
+{
+	bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata)
+	{
+		handle_toggle_flycam();
+		return true;
+	}
+};
+
+class LLViewCheckJoystickFlycam : public view_listener_t
+{
+	bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata)
+	{
+		bool new_val = LLViewerJoystick::sOverrideCamera;
+		gMenuHolder->findControl(userdata["control"].asString())->setValue(new_val);
+		return true;
+	}
+};
+
+void handle_toggle_flycam()
+{
+	LLViewerJoystick::sOverrideCamera = !LLViewerJoystick::sOverrideCamera;
+	if (LLViewerJoystick::sOverrideCamera)
+	{
+		LLViewerJoystick::getInstance()->moveFlycam(true);
+	}
+	else if (!LLToolMgr::getInstance()->inBuildMode())
+	{
+		LLViewerJoystick::getInstance()->moveAvatar(true);
+	}
+	else 
+	{
+		// we are in build mode, exiting from the flycam mode: since we are 
+		// going to keep the flycam POV for the main camera until the avatar
+		// moves, we need to track this situation.
+		LLViewerJoystick::getInstance()->setCameraNeedsUpdate(false);
+		LLViewerJoystick::getInstance()->setNeedsReset(true);
+	}
+}
+
 class LLObjectBuild : public view_listener_t
 {
 	bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata)
@@ -1916,6 +1958,9 @@ class LLObjectEdit : public view_listener_t
 		LLToolMgr::getInstance()->setCurrentToolset(gBasicToolset);
 		gFloaterTools->setEditTool( LLToolCompTranslate::getInstance() );
 
+		LLViewerJoystick::getInstance()->moveObjects(true);
+		LLViewerJoystick::getInstance()->setNeedsReset(true);
+
 		// Could be first use
 		LLFirstUse::useBuild();
 		return true;
@@ -3339,7 +3384,13 @@ void reset_view_final( BOOL proceed, void* )
 	
 	if (LLViewerJoystick::sOverrideCamera)
 	{
-		handle_toggle_flycam(NULL);
+		handle_toggle_flycam();
+	}
+
+	// reset avatar mode from eventual residual motion
+	if (LLToolMgr::getInstance()->inBuildMode())
+	{
+		LLViewerJoystick::getInstance()->moveAvatar(true);
 	}
 
 	gAgent.resetView(!gFloaterTools->getVisible());
@@ -5252,21 +5303,6 @@ void handle_dump_followcam(void*)
 	LLFollowCamMgr::dump();
 }
 
-BOOL check_flycam(void*)
-{
-	return LLViewerJoystick::sOverrideCamera;
-}
-
-void handle_toggle_flycam(void*)
-{
-	LLViewerJoystick::sOverrideCamera = !LLViewerJoystick::sOverrideCamera;
-	if (LLViewerJoystick::sOverrideCamera)
-	{
-		LLViewerJoystick::updateCamera(TRUE);
-		LLFloaterJoystick::show(NULL);
-	}
-}
-
 void handle_viewer_enable_message_log(void*)
 {
 	gMessageSystem->startLogging();
@@ -7715,6 +7751,7 @@ void initialize_menus()
 	// View menu
 	addMenu(new LLViewMouselook(), "View.Mouselook");
 	addMenu(new LLViewBuildMode(), "View.BuildMode");
+	addMenu(new LLViewJoystickFlycam(), "View.JoystickFlycam");
 	addMenu(new LLViewResetView(), "View.ResetView");
 	addMenu(new LLViewLookAtLastChatter(), "View.LookAtLastChatter");
 	addMenu(new LLViewShowHoverTips(), "View.ShowHoverTips");
@@ -7733,6 +7770,7 @@ void initialize_menus()
 	addMenu(new LLViewEnableLastChatter(), "View.EnableLastChatter");
 
 	addMenu(new LLViewCheckBuildMode(), "View.CheckBuildMode");
+	addMenu(new LLViewCheckJoystickFlycam(), "View.CheckJoystickFlycam");
 	addMenu(new LLViewCheckShowHoverTips(), "View.CheckShowHoverTips");
 	addMenu(new LLViewCheckHighlightTransparent(), "View.CheckHighlightTransparent");
 	addMenu(new LLViewCheckBeaconEnabled(), "View.CheckBeaconEnabled");
diff --git a/indra/newview/llviewermenu.h b/indra/newview/llviewermenu.h
index 21b2554ceb..98b08c4985 100644
--- a/indra/newview/llviewermenu.h
+++ b/indra/newview/llviewermenu.h
@@ -102,6 +102,7 @@ void handle_sit_down(void*);
 bool toggle_build_mode();
 void handle_object_build(void*);
 void handle_save_snapshot(void *);
+void handle_toggle_flycam();
 
 bool handle_sit_or_stand();
 bool handle_give_money_dialog();
diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp
index 697aea8582..696a2ad3c6 100644
--- a/indra/newview/llviewerwindow.cpp
+++ b/indra/newview/llviewerwindow.cpp
@@ -32,6 +32,7 @@
 #include "llviewerprecompiledheaders.h"
 
 #include "llpanellogin.h"
+#include "llviewerkeyboard.h"
 #include "llviewerwindow.h"
 
 // system library includes
@@ -180,6 +181,7 @@
 #include "llurlsimstring.h"
 #include "llviewerdisplay.h"
 #include "llspatialpartition.h"
+#include "llviewerjoystick.h"
 
 #if LL_WINDOWS
 #include "llwindebug.h"
@@ -514,6 +516,13 @@ public:
 			addText(xpos, ypos, llformat("FOV: %2.1f deg", RAD_TO_DEG * LLViewerCamera::getInstance()->getView()));
 			ypos += y_inc;
 		}
+		
+		if (LLViewerJoystick::sOverrideCamera)
+		{
+			addText(xpos + 200, ypos, llformat("Flycam"));
+			ypos += y_inc;
+		}
+		
 		if (gSavedSettings.getBOOL("DebugShowRenderInfo"))
 		{
 			if (gPipeline.getUseVertexShaders() == 0)
@@ -1302,6 +1311,7 @@ BOOL LLViewerWindow::handleTranslatedKeyUp(KEY key,  MASK mask)
 
 void LLViewerWindow::handleScanKey(KEY key, BOOL key_down, BOOL key_up, BOOL key_level)
 {
+	LLViewerJoystick::getInstance()->setCameraNeedsUpdate(true);
 	return gViewerKeyboard.scanKey(key, key_down, key_up, key_level);
 }
 
@@ -1364,6 +1374,12 @@ BOOL LLViewerWindow::handleActivate(LLWindow *window, BOOL activated)
 	return TRUE;
 }
 
+BOOL LLViewerWindow::handleActivateApp(LLWindow *window, BOOL activating)
+{
+	LLViewerJoystick::getInstance()->setNeedsReset(true);
+	return FALSE;
+}
+
 
 void LLViewerWindow::handleMenuSelect(LLWindow *window,  S32 menu_item)
 {
@@ -1450,6 +1466,26 @@ void LLViewerWindow::handleDataCopy(LLWindow *window, S32 data_type, void *data)
 	}
 }
 
+BOOL LLViewerWindow::handleTimerEvent(LLWindow *window)
+{
+	if (LLViewerJoystick::sOverrideCamera)
+	{
+		LLViewerJoystick::getInstance()->updateStatus();
+		return TRUE;
+	}
+	return FALSE;
+}
+
+BOOL LLViewerWindow::handleDeviceChange(LLWindow *window)
+{
+	// give a chance to use a joystick after startup (hot-plugging)
+	if (!LLViewerJoystick::getInstance()->isJoystickInitialized() )
+	{
+		LLViewerJoystick::getInstance()->init();
+		return TRUE;
+	}
+	return FALSE;
+}
 
 //
 // Classes
diff --git a/indra/newview/llviewerwindow.h b/indra/newview/llviewerwindow.h
index ef3b5146e2..cad3bb031f 100644
--- a/indra/newview/llviewerwindow.h
+++ b/indra/newview/llviewerwindow.h
@@ -97,6 +97,7 @@ public:
 	/*virtual*/ void handleFocus(LLWindow *window);
 	/*virtual*/ void handleFocusLost(LLWindow *window);
 	/*virtual*/ BOOL handleActivate(LLWindow *window, BOOL activated);
+	/*virtual*/ BOOL handleActivateApp(LLWindow *window, BOOL activating);
 	/*virtual*/ void handleMenuSelect(LLWindow *window,  S32 menu_item);
 	/*virtual*/ BOOL handlePaint(LLWindow *window,  S32 x,  S32 y,  S32 width,  S32 height);
 	/*virtual*/ void handleScrollWheel(LLWindow *window,  S32 clicks);
@@ -104,6 +105,9 @@ public:
 	/*virtual*/ void handleWindowBlock(LLWindow *window);
 	/*virtual*/ void handleWindowUnblock(LLWindow *window);
 	/*virtual*/ void handleDataCopy(LLWindow *window, S32 data_type, void *data);
+	/*virtual*/ BOOL handleTimerEvent(LLWindow *window);
+	/*virtual*/ BOOL handleDeviceChange(LLWindow *window);
+
 
 
 	//
diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py
index e73d278043..edb7002584 100755
--- a/indra/newview/viewer_manifest.py
+++ b/indra/newview/viewer_manifest.py
@@ -342,6 +342,9 @@ class DarwinManifest(ViewerManifest):
                         #  <bundle>/Contents/MacOS/
                         self.contents_of_tar('mozilla-universal-darwin.tgz', 'MacOS')
 
+                        # copy additional libs in <bundle>/Contents/MacOS/
+                        self.path("../../libraries/universal-darwin/lib_release/libndofdev.dylib", dst="MacOS/libndofdev.dylib")
+
                         # replace the default theme with our custom theme (so scrollbars work).
                         if self.prefix(src="mozilla-theme", dst="MacOS/chrome"):
                                 self.path("classic.jar")
-- 
GitLab