//------------------------------------------------------------------
//
// The following article discusses the mechanics behind this
// trackball implementation: http://viewport3d.com/trackball.htm
//
// Reading the article is not required to use this sample code,
// but skimming it might be useful.
//
// For licensing information and to get the latest version go to:
// http://workspaces.gotdotnet.com/3dtools
//
//------------------------------------------------------------------
using System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using System.Windows.Controls;
//using Petzold.Media3D;
using _3DTools;
using Xenki.Framework;
using System.Collections.Generic;
using OpenMetaverse;
using Quaternion = System.Windows.Media.Media3D.Quaternion;
namespace Xenki.DefaultRenderer
{
public delegate void CamPositionChanged(Point3D oldpos,Point3D newpos);
public class CameraControl
{
private FrameworkElement _eventSource;
private Point _previousPosition2D;
private Vector3D _previousPosition3D = new Vector3D(0, 0, 1);
public event CamPositionChanged OnCameraPositionChanged;
private PerspectiveCamera _camera;
private Point3D _cameraLookPoint;
ModelVisual3D visul3d;
Point3D _clickedCenterPosition;
DefaultAvatarManager avatarManager = DefaultAvatarManager.SingletonInstance();
HotkeyDispatcher KeyDispatcher = HotkeyDispatcher.GetInstance();
DefaultDataControl m_primitiveData;
bool isTracking = false;
bool isPanningObject = false;
[System.Runtime.InteropServices.DllImportAttribute("user32.dll", EntryPoint = "SetCursorPos")]
[return: System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.Bool)]
public static extern bool SetCursorPos(int X, int Y);
///
/// The FrameworkElement we listen to for mouse events.
///
public FrameworkElement EventSource
{
get { return _eventSource; }
set
{
_eventSource = value;
//Mouse.Capture(_eventSource, CaptureMode.Element);
//_eventSource.CaptureMouse();
_eventSource.Focus();
Keyboard.Focus(_eventSource);
Mouse.Capture(_eventSource, CaptureMode.Element);
}
}
///
/// construct camera control
///
///
///
/// the public datas which included primitives and avatars.
/// be used for the collision detection etc.
///
public CameraControl(PerspectiveCamera camera, DefaultDataControl datacontrol)
{
_camera = camera;
m_primitiveData = datacontrol;
avatarManager.OnAvatarPosChanged += new AvatarPositionChanged(avatarManager_OnAvatarPosChanged);
avatarManager.OnAvatarTrunAround += new AvatarTurnAround(avatarManager_OnAvatarTrunAround);
KeyDispatcher.OnMouseLeftDown += new MouseLeftDown(KeyDispatcher_OnMouseLeftDown);
KeyDispatcher.OnMouseLeftUp += new MouseLeftUp(KeyDispatcher_OnMouseLeftUp);
KeyDispatcher.OnMouseMove += new MouseMove(KeyDispatcher_OnMouseMove);
KeyDispatcher.OnMouseRightDown += new MouseRightDown(KeyDispatcher_OnMouseRightDown);
KeyDispatcher.OnMouseWheel += new MouseWheel(KeyDispatcher_OnMouseWheel);
}
///
/// invoke when left mouse click some prim to get the focus of prim
/// before rotate/move it.
///
///
///
void KeyDispatcher_OnMouseLeftDown(object sender, MouseButtonEventArgs e)
{
Mouse.Capture(_eventSource, CaptureMode.Element);
EventSource.Focus();
Point pclick = e.GetPosition(EventSource);// arg.MouseClicked;
originalMouseClickPos = EventSource.PointToScreen(pclick);// arg.ScreenPoint;
HitTestResult hitresult = VisualTreeHelper.HitTest(EventSource, pclick);
RayMeshGeometry3DHitTestResult result3d = hitresult as RayMeshGeometry3DHitTestResult;
if (result3d == null)
return;
visul3d = result3d.VisualHit as ModelVisual3D;
if (visul3d == null)
return;
//for ALT + MOUSE + ROTATE
VisualTreeHelper.HitTest(EventSource, null, HitTestCallback, new PointHitTestParameters(pclick));
_camera.LookDirection = Point3D.Subtract(_cameraLookPoint, _camera.Position);
//for test
Getmesh(visul3d);
_clickedCenterPosition = GetObjectCenterPosition(visul3d);
//TRACK BALL
_previousPosition3D = ProjectToTrackball(
EventSource.ActualWidth,
EventSource.ActualHeight,
_previousPosition2D);
isTracking = true;
}
//test the HitTest method of WPF
public void Getmesh(ModelVisual3D v)
{
//GeometryModel3D mesh = v.Content as GeometryModel3D;
//Visual3D myself = m_primitiveData.GetVisualInScene(avatarManager.Myself.avatar.LocalID);
//ModelVisual3D vv = myself as ModelVisual3D;
//vv.Content = mesh;
//m_primitiveData.AddNewPrimitive(avatarManager.Myself.avatar.LocalID, v, 0);
}
///
/// Release the left mouse click
/// used for showing the hidden mouse cursor at the original position where it's clicked.
///
///
///
void KeyDispatcher_OnMouseLeftUp(object sender, MouseButtonEventArgs e)
{
if (_eventSource.Cursor != Cursors.Arrow)
{
SetCursorPos((int)originalMouseClickPos.X, (int)originalMouseClickPos.Y);
_eventSource.Cursor = Cursors.Arrow;
}
Mouse.Capture(EventSource, CaptureMode.None);
isTracking = false;
if (isPanningObject == true)
{
isPanningObject = false;
_cameraLookPoint = GetObjectCenterPosition(visul3d);
}
_eventSource.ReleaseMouseCapture();
}
///
/// zoom in/out via move the camera's position along the vector which camera looks.
///
///
///
void KeyDispatcher_OnMouseWheel(object sender,MouseWheelEventArgs arg)
{
double scale = -arg.Delta / 800.000;
if (_cameraLookPoint != null && _cameraLookPoint != new Point3D(0, 0, 0))
_camera.LookDirection = Point3D.Subtract(_cameraLookPoint, _camera.Position);
int count = Math.Abs((int)(scale / zoomFactor));
for (int i = 1; i <= count; i++)
{
if (scale > 0)
AdjustCameraPosition(_camera, _camera.LookDirection, zoomFactor);
else
AdjustCameraPosition(_camera, _camera.LookDirection, -zoomFactor);
}
}
///
/// used for elevate/lower the camera's altitude.
///
///
///
void KeyDispatcher_OnMouseRightDown(object sender, MouseButtonEventArgs arg)
{
Mouse.Capture(_eventSource, CaptureMode.Element);
Point pclick = arg.GetPosition(EventSource);
originalMouseClickPos = EventSource.PointToScreen(pclick);
HitTestResult hitresult = VisualTreeHelper.HitTest(EventSource, pclick);
RayMeshGeometry3DHitTestResult result3d = hitresult as RayMeshGeometry3DHitTestResult;
if (result3d == null)
return;
visul3d = result3d.VisualHit as ModelVisual3D;
if (visul3d == null)
return;
//for ALT + MOUSE + ROTATE
VisualTreeHelper.HitTest(_eventSource, null, HitTestCallback, new PointHitTestParameters(pclick));
_camera.LookDirection = Point3D.Subtract(_cameraLookPoint, _camera.Position);
isTracking = true;
_eventSource.CaptureMouse();
_eventSource.Focus();
}
void KeyDispatcher_OnMouseMove(object sender, MouseEventArgs arg)
{
currentPosition = arg.GetPosition(EventSource);// arg.MouseClicked;
if (isTracking)
{
if (arg.RightButton == MouseButtonState.Pressed
&& (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift)))
{
LookUpDown(currentPosition);
}
else if (arg.LeftButton == MouseButtonState.Pressed && (Keyboard.IsKeyDown(Key.LeftAlt) || Keyboard.IsKeyDown(Key.RightAlt)))
{
_eventSource.Cursor = Cursors.None;
Focus(currentPosition);
}
else if (arg.RightButton == MouseButtonState.Pressed && Keyboard.IsKeyDown(Key.A))
{
_eventSource.Cursor = Cursors.None;
CameraPanLeftRight(currentPosition);
}
else if (arg.LeftButton == MouseButtonState.Pressed && (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
&& (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift)))
{
_eventSource.Cursor = Cursors.None;
RotateObject(currentPosition);
}
else if (arg.LeftButton == MouseButtonState.Pressed && (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)))
{
_eventSource.Cursor = Cursors.None;
isPanningObject = true;
PanObject(currentPosition);
}
else if (arg.LeftButton == MouseButtonState.Pressed)
{
LookAround(currentPosition);
}
else if (arg.RightButton == MouseButtonState.Pressed)
{
CameraPanUP(currentPosition);
}
}
_previousPosition2D = currentPosition;
}
void avatarManager_OnAvatarTrunAround(Point3D avatarposition, Vector3D avatarlookdir)
{
_camera.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal,
new System.Windows.Forms.MethodInvoker(delegate
{
ResetCameraPos(avatarposition,avatarlookdir);
}));
}
void avatarManager_OnAvatarPosChanged(uint avatarid, Point3D oldpos, Point3D newpos,Vector3D avatarlookdir)
{
ResetCameraPos(newpos,avatarlookdir);
}
void ResetCameraPos(Point3D avatarpos,Vector3D avatarLookDirection)
{
EventSource.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal,
new System.Windows.Forms.MethodInvoker(delegate
{
Point3D oldpos = _camera.Position;
_camera.Position = Point3D.Add(avatarpos, avatarLookDirection*10);
Vector3D cameralookdir = avatarLookDirection;
cameralookdir.Z += -0.2;
_camera.LookDirection = cameralookdir;
if (OnCameraPositionChanged != null)
{
OnCameraPositionChanged(oldpos, _camera.Position);
}
}));
}
void ResetCameraLookDirection(Point3D npos)
{
Vector3D cameralookdir = npos - _camera.Position;
cameralookdir.Z += 8;
_camera.LookDirection = cameralookdir;
}
void ResetCameraLookDirection(Vector3D avatarlookdir)
{
Vector3D cameralookdir = avatarlookdir;
cameralookdir.Z += 8;
_camera.LookDirection = cameralookdir;
}
private void CameraLookLeftRight(Point3D rotatecenter, double angle)
{
Vector3D axis = new Vector3D(0, 0, 1);
Quaternion delta = new Quaternion(axis, -angle);
Matrix3D avatarMatrix3D = new Matrix3D();
avatarMatrix3D.RotateAt(delta, rotatecenter);
_camera.LookDirection = Vector3D.Add(avatarManager.MySelfLookDirection,new Vector3D(0,0,-2));// avatarMatrix3D);// Point3D.Subtract(_cameraLookPoint, _camera.Position);
}
Point originalMouseClickPos = new Point();
private Point3D GetObjectCenterPosition(ModelVisual3D visual)
{
return Point3D.Add(visual.Content.Bounds.Location,
new Vector3D(visual.Content.Bounds.SizeX / 2, visual.Content.Bounds.SizeY / 2, visual.Content.Bounds.SizeZ / 2));
}
Point currentPosition;
private void OnMouseMove(object sender, MouseEventArgs e)
{
currentPosition = e.GetPosition(EventSource);
if (e.RightButton == MouseButtonState.Pressed && (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift)))
{
LookUpDown(currentPosition);
}
else if (e.LeftButton == MouseButtonState.Pressed
&& (Keyboard.IsKeyDown(Key.LeftAlt) || Keyboard.IsKeyDown(Key.RightAlt)))
{
if (isTracking)
{
_eventSource.Cursor = Cursors.None;
Focus(currentPosition);
}
}
else if (e.RightButton == MouseButtonState.Pressed && Keyboard.IsKeyDown(Key.A))
{
if (isTracking)
{
_eventSource.Cursor = Cursors.None;
CameraPanLeftRight(currentPosition);
}
}
else if (e.LeftButton == MouseButtonState.Pressed && (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
&& (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift)))
{
if (isTracking)
{
_eventSource.Cursor = Cursors.None;
RotateObject(currentPosition);
}
}
else if (e.LeftButton == MouseButtonState.Pressed && (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)))
{
if (isTracking)
{
_eventSource.Cursor = Cursors.None;
isPanningObject = true;
PanObject(currentPosition);
}
}
else if (e.LeftButton == MouseButtonState.Pressed)
{
if(isTracking)
LookAround(currentPosition);
}
else if (e.RightButton == MouseButtonState.Pressed)
{
if (isTracking)
CameraPanUP(currentPosition);
}
_previousPosition2D = currentPosition;
}
private void LookAround(Point currentPosition)
{
if (_cameraLookPoint == new Point3D(0, 0, 0))
return;
Vector3D currentPosition3D = ProjectToTrackball(EventSource.ActualWidth, EventSource.ActualHeight, currentPosition);
if (_previousPosition3D == currentPosition3D)
return;
Vector3D axis = Vector3D.CrossProduct(_previousPosition3D, currentPosition3D);
double angle = Vector3D.AngleBetween(_previousPosition3D, currentPosition3D);
angle *= 3;
Quaternion delta = new Quaternion(axis, -angle);
Matrix3D cameraMatrix3D = new Matrix3D();
cameraMatrix3D.RotateAt(delta,_cameraLookPoint);
_camera.Position = Point3D.Multiply(_camera.Position, cameraMatrix3D);
_camera.LookDirection = Point3D.Subtract(_cameraLookPoint, _camera.Position);
_previousPosition3D = currentPosition3D;
}
private void RotateObject(Point currentPosition)
{
Vector3D currentPosition3D = ProjectToTrackball(_eventSource.ActualWidth, _eventSource.ActualHeight, currentPosition);
Vector3D axis = Vector3D.CrossProduct(_previousPosition3D, currentPosition3D);
double angle = Vector3D.AngleBetween(currentPosition3D, _previousPosition3D);
angle *= 3.00000;
Matrix3D mat = new Matrix3D();
mat.RotateAt(new Quaternion(axis, angle), _clickedCenterPosition);
RotateObject(mat, visul3d);
IEnumerator children = m_primitiveData.GetChildrenByVisual3D(visul3d);
while (children.MoveNext())
{
RotateObject(mat, children.Current);
}
_previousPosition3D = currentPosition3D;
}
private void RotateObject(Matrix3D mat,Visual3D visual)
{
GeometryModel3D geometryModel3D = (visual as ModelVisual3D).Content as GeometryModel3D;
if (geometryModel3D != null)
{
MeshGeometry3D meshGeometry3D = geometryModel3D.Geometry as MeshGeometry3D;
if (meshGeometry3D != null)
{
Point3DCollection points = meshGeometry3D.Positions;
for (int i = 0; i < points.Count; i++)
points[i] = Point3D.Multiply(points[i], mat);
}
}
}
private Vector3D ProjectToTrackball(double width, double height, Point point)
{
double x = point.X / (width / 2); // Scale so bounds map to [0,0] - [2,2]
double y = point.Y / (height / 2);
x = x - 1; // Translate 0,0 to the center
y = 1 - y; // Flip so +Y is up instead of down
double z2 = 1 - x * x - y * y; // z^2 = 1 - x^2 - y^2
double z = z2 > 0 ? Math.Sqrt(z2) : 0;
Vector3D v = new Vector3D(x, z, y);
v.Normalize();
return v;
}
private void CameraPanLeftRight(Point currentPosition)
{
double scalex = currentPosition.X - _previousPosition2D.X;
Vector3D vector3D = GetTranslationVector3D(visul3d, _previousPosition2D, currentPosition);
Point3D cPos = _camera.Position;
cPos += Math.Abs(scalex) * vector3D;
if (OnCameraPositionChanged != null)
OnCameraPositionChanged(_camera.Position, cPos);
_camera.Position = cPos;
}
///
/// move prim along horizontal.
///
///
private void PanObject(Point currentPosition)
{
IEnumerator children = m_primitiveData.GetChildrenByVisual3D(visul3d);
Vector3D vector3D = GetTranslationVector3D(visul3d, _previousPosition2D, currentPosition);
MoveObject(visul3d, vector3D);
while( children.MoveNext())
{
MoveObject(children.Current, vector3D);
}
}
///
/// move the prim along with a vector
///
/// the prim need be moved
/// vector
private void MoveObject(Visual3D visual, Vector3D vector3D)
{
GeometryModel3D geometryModel3D = (visual as ModelVisual3D).Content as GeometryModel3D;
if (geometryModel3D != null)
{
MeshGeometry3D meshGeometry3D = geometryModel3D.Geometry as MeshGeometry3D;
if (meshGeometry3D != null)
{
Point3DCollection points = meshGeometry3D.Positions;
for (int i = 0; i < points.Count; i++)
points[i] = Point3D.Add(points[i], vector3D * 50);
}
}
}
string mm = "";
private Vector3D GetTranslationVector3D(DependencyObject modelHit, Point startPosition, Point endPosition)
{
Vector3D translationVector3D = new Vector3D();
Viewport3DVisual viewport = null;
bool success = false;
Matrix3D matrix3D = MathUtils.TryTransformTo2DAncestor(modelHit, out viewport, out success);
if (success && matrix3D.HasInverse)
{
matrix3D.Invert();
Point3D startPoint3D = new Point3D(startPosition.X, startPosition.Y, 0);
Point3D endPoint3D = new Point3D(endPosition.X, endPosition.Y, 0);
Vector3D vector3D = endPoint3D - startPoint3D;
translationVector3D = matrix3D.Transform(vector3D);
}
return translationVector3D;
}
///
/// camera look up or down direction
///
/// the current mouse position on the screen(2dimension)
private void LookUpDown(Point currentPosition)
{
double scale = currentPosition.Y- _previousPosition2D.Y;
// scale = scale;
_camera.LookDirection = new Vector3D(_camera.LookDirection.X, _camera.LookDirection.Y, _camera.LookDirection.Z + scale);
}
///
/// modify camera's z index value the implement the camera's al and down
///
///
private void CameraPanUP(Point currentPosition)
{
double yDelta = currentPosition.Y - _previousPosition2D.Y;
double xDelta = currentPosition.X - _previousPosition2D.X;
double scale = yDelta / 5.00;
_camera.Position = new Point3D(_camera.Position.X, _camera.Position.Y, _camera.Position.Z + scale);
}
double zoomFactor = 0.0500;
///
/// zoom in/out surround a 3d position.
///
///
private void Focus(Point currentPosition)
{
//zoom
double yDelta = currentPosition.Y - _previousPosition2D.Y;
double scale = yDelta/20 ;
int count = Math.Abs( (int)(scale / zoomFactor));
for (int i = 1; i <= count; i++)
{
if (yDelta > 0)
AdjustCameraPosition(_camera, _camera.LookDirection, zoomFactor);
else
AdjustCameraPosition(_camera, _camera.LookDirection, -zoomFactor);
}
//rotate the camera
double axisAngle = _previousPosition2D.X - currentPosition.X;
Matrix3D matrixRoateCamera = new Matrix3D();
matrixRoateCamera.RotateAt(new Quaternion(new Vector3D(0, 0, 1), axisAngle), _cameraLookPoint);
Point3D pos = Point3D.Multiply(_camera.Position, matrixRoateCamera);
_camera.Position = pos;
_camera.LookDirection = Point3D.Subtract(_cameraLookPoint, _camera.Position);
if (OnCameraPositionChanged != null)
OnCameraPositionChanged(_camera.Position, pos);
}
///
/// move camera along the camera's look direction.
///
///
/// camera's look direction
/// factor used for calculate the step length of movement
private void AdjustCameraPosition(PerspectiveCamera camera,Vector3D lookDirection, double factor )
{
Point3D cameraPos = new Point3D(camera.Position.X - factor * lookDirection.X,
camera.Position.Y - factor * lookDirection.Y,
camera.Position.Z - factor * lookDirection.Z);
//too fast so I comment the code below:
//if (Point3D.Subtract(cameraPos, camera.Position).Length > 0.5000000000)
// cameraPos = new Point3D(camera.Position.X - factor / 10 * lookDirection.X,
// camera.Position.Y - factor / 10 * lookDirection.Y,
// camera.Position.Z - factor / 10 * lookDirection.Z);
double distance = Point3D.Subtract(_cameraLookPoint, cameraPos).Length;
if (distance > 512.0000 || distance <= 0.5000000)
{
}
else
{
if (OnCameraPositionChanged != null)
OnCameraPositionChanged(camera.Position, cameraPos);
camera.Position = cameraPos;
}
}
//get the clicked 3d position
public HitTestResultBehavior HitTestCallback(HitTestResult result)
{
if (result.VisualHit != null)
{
RayHitTestResult r = result as RayHitTestResult;
if (r != null)
{
RayMeshGeometry3DHitTestResult t = r as RayMeshGeometry3DHitTestResult;
_cameraLookPoint = t.PointHit;
return HitTestResultBehavior.Stop;
}
}
return HitTestResultBehavior.Continue;
}
}
}