//------------------------------------------------------------------
//
// 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 http://www.codeplex.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;
namespace Xenki.DefaultRenderer
{
public class CameraControl
{
private FrameworkElement _eventSource;
private Point _previousPosition2D;
private Vector3D _previousPosition3D = new Vector3D(0, 0, 1);
private Transform3DGroup _transform;
private ScaleTransform3D _scale = new ScaleTransform3D();
private AxisAngleRotation3D _rotation = new AxisAngleRotation3D();
private TranslateTransform3D _translate = new TranslateTransform3D();
private Vector3D _cameraLookDirection = new Vector3D(128, 128, 10);
private PerspectiveCamera _camera;
private Point3D _cameraLookPoint;
private RotateTransform3D _rotationTransform = new RotateTransform3D();
ModelVisual3D visul3d;
//rotate and pan object clicked
private TranslateTransform3D _clickedTranslate = new TranslateTransform3D();
private RotateTransform3D _clickedRotate = new RotateTransform3D();
private AxisAngleRotation3D _clickedAxisAngleRotate = new AxisAngleRotation3D();
Point3D _clickedCenterPosition;
Transform3DGroup _clickedGroup;
bool isTracking = false;
public CameraControl(PerspectiveCamera camera)
{
_camera = camera;
_rotationTransform.Rotation = _rotation;
_transform = new Transform3DGroup();
// _transform.Children.Add(_translate);
_transform.Children.Add(_rotationTransform);
_cameraLookDirection = _camera.LookDirection;
}
///
/// A transform to move the camera or scene to the trackball's
/// current orientation and scale.
///
public Transform3DGroup Transform
{
get { return _transform; }
}
[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);
#region Event Handling
///
/// The FrameworkElement we listen to for mouse events.
///
public FrameworkElement EventSource
{
get { return _eventSource; }
set
{
if (_eventSource != null)
{
_eventSource.MouseWheel -= _eventSource_MouseWheel;
_eventSource.MouseUp -= this.OnMouseUp;
_eventSource.MouseMove -= this.OnMouseMove;
_eventSource.MouseLeftButtonDown -= _eventSource_MouseLeftButtonDown;
_eventSource.MouseRightButtonDown -= _eventSource_MouseRightButtonDown;
}
_eventSource = value;
_eventSource.MouseWheel += _eventSource_MouseWheel;
_eventSource.MouseLeftButtonDown += _eventSource_MouseLeftButtonDown;
_eventSource.MouseRightButtonDown += _eventSource_MouseRightButtonDown;
_eventSource.MouseUp += this.OnMouseUp;
_eventSource.MouseMove += this.OnMouseMove;
}
}
void _eventSource_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
Mouse.Capture(_eventSource, CaptureMode.Element);
Point pclick = e.GetPosition(_eventSource);
HitTestResult hitresult = VisualTreeHelper.HitTest(EventSource, pclick);
RayMeshGeometry3DHitTestResult result3d = hitresult as RayMeshGeometry3DHitTestResult;
if (result3d == null)
return;
visul3d = result3d.VisualHit as ModelVisual3D;
if (visul3d == null)
return;
Transform3DGroup group = visul3d.Transform as Transform3DGroup;
if (group != null)
{
foreach (Transform3D tran in group.Children)
{
if (tran is RotateTransform3D)
{
_clickedRotate = tran as RotateTransform3D;
if (_clickedRotate != null)
{
_clickedAxisAngleRotate = new AxisAngleRotation3D();
_clickedRotate.Rotation = _clickedAxisAngleRotate;
}
}
else if (tran is TranslateTransform3D)
_clickedTranslate = tran as TranslateTransform3D;
}
}
else
return;
//for ALT + MOUSE + ROTATE
VisualTreeHelper.HitTest(_eventSource, null, HitTestCallback, new PointHitTestParameters(pclick));
_rotationTransform.CenterX = _cameraLookPoint.X;
_rotationTransform.CenterY = _cameraLookPoint.Y;
_rotationTransform.CenterZ = _cameraLookPoint.Z;
_cameraLookDirection = Point3D.Subtract(_cameraLookPoint, _camera.Position);
_camera.LookDirection = _cameraLookDirection;
isTracking = true;
_eventSource.CaptureMouse();
_eventSource.Focus();
e.Handled = true;
}
Point originalMouseClickPos = new Point();
void _eventSource_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Mouse.Capture(EventSource, CaptureMode.Element);
Point pclick = e.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;
_clickedGroup = visul3d.Transform as Transform3DGroup;
if (_clickedGroup != null)
{
foreach (Transform3D tran in _clickedGroup.Children)
{
if (tran is RotateTransform3D)
{
_clickedRotate = tran as RotateTransform3D;
if (_clickedRotate != null)
{
_clickedAxisAngleRotate = _clickedRotate.Rotation as AxisAngleRotation3D;
if (_clickedAxisAngleRotate == null)
_clickedAxisAngleRotate = new AxisAngleRotation3D();
_clickedRotate.Rotation = _clickedAxisAngleRotate;
}
}
else if (tran is TranslateTransform3D)
{
_clickedTranslate = tran as TranslateTransform3D;
}
}
}
else
return;
//for ALT + MOUSE + ROTATE
VisualTreeHelper.HitTest(_eventSource, null, HitTestCallback, new PointHitTestParameters(pclick));
_rotationTransform.CenterX = _cameraLookPoint.X;
_rotationTransform.CenterY = _cameraLookPoint.Y;
_rotationTransform.CenterZ = _cameraLookPoint.Z;
//for rotate the object mouse clicked
_clickedRotate.CenterX = _clickedCenterPosition.X = visul3d.Content.Bounds.Location.X + visul3d.Content.Bounds.SizeX / 2;
_clickedRotate.CenterY = _clickedCenterPosition.Y = visul3d.Content.Bounds.Location.Y + visul3d.Content.Bounds.SizeY / 2;
_clickedRotate.CenterZ = _clickedCenterPosition.Z = visul3d.Content.Bounds.Location.Z + visul3d.Content.Bounds.SizeZ / 2;
_cameraLookDirection = Point3D.Subtract(_cameraLookPoint, _camera.Position);
_camera.LookDirection = _cameraLookDirection;
//TRACK BALL
_previousPosition3D = ProjectToTrackball(
EventSource.ActualWidth,
EventSource.ActualHeight,
_previousPosition2D);
isTracking = true;
_eventSource.CaptureMouse();
_eventSource.Focus();
e.Handled = true;
}
private void OnMouseUp(object sender, MouseEventArgs e)
{
Mouse.Capture(EventSource, CaptureMode.None);
isTracking = false;
//set the mouse to the original position when clicked the object.
SetCursorPos((int)originalMouseClickPos.X, (int)originalMouseClickPos.Y);
_eventSource.ReleaseMouseCapture();
_eventSource.Cursor = Cursors.Arrow;
}
private void OnMouseMove(object sender, MouseEventArgs e)
{
Point 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.Q))
{
if(isTracking)
CameraPanUP(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;
PanObject(currentPosition);
}
}
else if (e.LeftButton == MouseButtonState.Pressed)
{
LookAround(currentPosition);
}
_previousPosition2D = currentPosition;
}
private void LookAround(Point currentPosition)
{
Vector3D currentPosition3D = ProjectToTrackball(EventSource.ActualWidth, EventSource.ActualHeight, currentPosition);
Vector3D axis = Vector3D.CrossProduct(_previousPosition3D, currentPosition3D);
double angle = Vector3D.AngleBetween(_previousPosition3D, currentPosition3D);
// We negate the angle because we are rotating the camera.
// Do not do this if you are rotating the scene instead.
Quaternion delta = new Quaternion(axis, -angle);
// Get the current orientantion from the RotateTransform3D
Transform3DGroup group = _camera.Transform as Transform3DGroup;
if (group == null)
return;
foreach (Transform3D tran in group.Children)
{
RotateTransform3D rotate = tran as RotateTransform3D;
if (rotate == null)
continue;
RotateTransform3D rt = (RotateTransform3D)rotate;
AxisAngleRotation3D r = (AxisAngleRotation3D)rt.Rotation;
Quaternion q = new Quaternion(r.Axis, r.Angle);
// Compose the delta with the previous orientation
q *= delta;
// Write the new orientation back to the Rotation3D
r.Axis = q.Axis;
r.Angle = q.Angle;
}
_previousPosition3D = currentPosition3D;
}
void _eventSource_MouseWheel(object sender, MouseWheelEventArgs e)
{
double scale = -e.Delta / 1200.000;
AdjustCameraPosition(_camera, _cameraLookDirection, scale);
}
private void RotateObject(Point currentPosition)
{
Vector3D currentPosition3D = ProjectToTrackball(EventSource.ActualWidth, EventSource.ActualHeight, currentPosition);
Vector3D axis = Vector3D.CrossProduct(_previousPosition3D, currentPosition3D);
double angle = Vector3D.AngleBetween(_previousPosition3D, currentPosition3D);
if (_clickedAxisAngleRotate.Angle >= 360 || _clickedAxisAngleRotate.Angle <= -360)
_clickedAxisAngleRotate.Angle = 0;
_clickedAxisAngleRotate.Axis = axis;
_clickedAxisAngleRotate.Angle -= angle;
_previousPosition3D = currentPosition3D;
}
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;
}
#endregion Event Handling
private void CameraPanLeftRight(Point currentPosition)
{
double scalex = currentPosition.X - _previousPosition2D.X;
Vector3D vector3D = GetTranslationVector3D(visul3d, _previousPosition2D, currentPosition);
_camera.Position += Math.Abs(scalex) * vector3D;
}
private void PanObject(Point currentPosition)
{
double scaley = currentPosition.Y - _previousPosition2D.Y;
scaley = Math.Abs( scaley );
double scalex = currentPosition.X - _previousPosition2D.X;
scalex = Math.Abs( scalex);
Vector3D vector3D = GetTranslationVector3D(visul3d, _previousPosition2D, currentPosition);
_clickedTranslate.OffsetX += scalex * vector3D.X;// _clickedTranslateOriginal.OffsetX + vector3D.X;
_clickedTranslate.OffsetY += scalex * vector3D.Y;// _clickedTranslateOriginal.OffsetY + vector3D.Y;
_clickedTranslate.OffsetZ += scaley * vector3D.Z;// _clickedTranslateOriginal.OffsetZ + vector3D.Z;// vectMouse.Z;
visul3d.Transform.Transform(new Point3D(visul3d.Content.Bounds.Location.X + scalex * vector3D.X, visul3d.Content.Bounds.Location.Y + scalex * vector3D.Y, visul3d.Content.Bounds.Location.Z + scaley * vector3D.Z));
}
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;
}
private void LookUpDown(Point currentPosition)
{
double scale = currentPosition.Y- _previousPosition2D.Y;
scale = scale/5.000000;
_camera.LookDirection = new Vector3D(_camera.LookDirection.X, _camera.LookDirection.Y, _camera.LookDirection.Z + scale);
}
private void Focus(Point currentPosition)
{
//zoom
double yDelta = currentPosition.Y - _previousPosition2D.Y;
double scale = yDelta / 100.000000;
AdjustCameraPosition(_camera,_cameraLookDirection,scale);
//rotate the camera
double axisAngle = currentPosition.X - _previousPosition2D.X;
_rotation.Axis = new Vector3D(0, 0, 1);
_rotation.Angle -= axisAngle;
_camera.LookDirection = Point3D.Subtract(_cameraLookPoint, _camera.Position);
}
private void AdjustCameraPosition(PerspectiveCamera camera,Vector3D lookDirection, double scale )
{
Point3D cameraPos = new Point3D(camera.Position.X - scale * lookDirection.X,
camera.Position.Y - scale * lookDirection.Y,
camera.Position.Z - scale * lookDirection.Z);
//too fast
if (Point3D.Subtract(cameraPos, camera.Position).Length > 1.000000000)
cameraPos = new Point3D(camera.Position.X - scale / 10 * lookDirection.X,
camera.Position.Y - scale / 10 * lookDirection.Y,
camera.Position.Z - scale / 10 * lookDirection.Z);
double distance = Point3D.Subtract(_cameraLookPoint, cameraPos).Length;
if (distance > 524 || distance <= 1.000000)
{
}
else
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;
}
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);
}
}
}