//------------------------------------------------------------------ // // 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; namespace Xenki.DefaultRenderer { public class CameraControl { private FrameworkElement _eventSource; private Point _previousPosition2D; private Vector3D _previousPosition3D = new Vector3D(0, 0, 1); private PerspectiveCamera _camera; private Point3D _cameraLookPoint; ModelVisual3D visul3d; Point3D _clickedCenterPosition; bool isTracking = false; bool isPanningObject = false; System.Windows.Threading.DispatcherTimer frameTimer; public CameraControl(PerspectiveCamera camera) { _camera = camera; frameTimer = new System.Windows.Threading.DispatcherTimer(); frameTimer.Tick += new EventHandler(frameTimer_Tick); frameTimer.Interval = new TimeSpan(0, 0, 0, 1, 10); frameTimer.Start(); } void frameTimer_Tick(object sender, EventArgs e) { // FPSMouseMove(); } [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.KeyDown -= this._eventSource_KeyDown; } _eventSource = value; _eventSource.MouseWheel += _eventSource_MouseWheel; _eventSource.MouseLeftButtonDown += _eventSource_MouseLeftButtonDown; _eventSource.MouseRightButtonDown += _eventSource_MouseRightButtonDown; _eventSource.MouseUp += this.OnMouseUp; _eventSource.MouseMove += this.OnMouseMove; _eventSource.Focusable = true; _eventSource.Focus(); Keyboard.Focus(_eventSource); _eventSource.KeyDown += _eventSource_KeyDown; } } void _eventSource_KeyDown(object sender, KeyEventArgs e) { if ((e.Key == Key.Left||e.Key == Key.A)&&Mouse.LeftButton == MouseButtonState.Released&&MouseButtonState.Released==Mouse.RightButton) { CameraLookLeftRight(-4.0); e.Handled = true; } else if (e.Key == Key.Right||e.Key == Key.D) { CameraLookLeftRight(4.0); e.Handled = true; } else if (e.Key == Key.Up || e.Key == Key.W) { //walk forward CameraWalk(0.05); e.Handled = true; } else if (e.Key == Key.Down || e.Key == Key.S) { //walk back CameraWalk(-0.05); e.Handled = true; } } //camera walk should be setting according to the avatar's walking path private void CameraWalk(double distanceTick) { //Vector3D cameraLookDirection = _camera.LookDirection; //Vector3D vzAxis = new Vector3D(0, 0, 1); //Vector3D walkDirection = Vector3D.Add(vzAxis, cameraLookDirection); _camera.LookDirection = new Vector3D(_camera.LookDirection.X, _camera.LookDirection.Y, 0); _camera.Position = Point3D.Add(_camera.Position, _camera.LookDirection * distanceTick); } private void CameraLookLeftRight(double angle) { Vector3D axis = new Vector3D(0, 0, 1); Quaternion delta = new Quaternion(axis, -angle); Matrix3D cameraMatrix3D = new Matrix3D(); cameraMatrix3D.RotateAt(delta, _camera.Position); _camera.LookDirection = Vector3D.Multiply(_camera.LookDirection, cameraMatrix3D);// Point3D.Subtract(_cameraLookPoint, _camera.Position); } void _eventSource_MouseRightButtonDown(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; //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(); 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; //for ALT + MOUSE + ROTATE VisualTreeHelper.HitTest(_eventSource, null, HitTestCallback, new PointHitTestParameters(pclick)); _camera.LookDirection = Point3D.Subtract(_cameraLookPoint, _camera.Position); _clickedCenterPosition = GetObjectCenterPosition(visul3d); //TRACK BALL _previousPosition3D = ProjectToTrackball( EventSource.ActualWidth, EventSource.ActualHeight, _previousPosition2D); isTracking = true; _eventSource.CaptureMouse(); _eventSource.Focus(); e.Handled = true; } 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)); } private void OnMouseUp(object sender, MouseEventArgs e) { //set the mouse to the original position when clicked the object. 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); } string s = mm; _eventSource.ReleaseMouseCapture(); } 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 *= 2; // 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); Matrix3D cameraMatrix3D = new Matrix3D(); cameraMatrix3D.RotateAt(delta,_cameraLookPoint); _camera.Position = Point3D.Multiply(_camera.Position, cameraMatrix3D); _camera.LookDirection = Point3D.Subtract(_cameraLookPoint, _camera.Position); _previousPosition3D = currentPosition3D; } void _eventSource_MouseWheel(object sender, MouseWheelEventArgs e) { double scale = -e.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); } } 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 *= 2; Matrix3D mat = new Matrix3D(); mat.RotateAt(new Quaternion(axis, angle), _clickedCenterPosition); GeometryModel3D geometryModel3D = visul3d.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); } } _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) { Vector3D vector3D = GetTranslationVector3D(visul3d, _previousPosition2D, currentPosition); GeometryModel3D geometryModel3D = visul3d.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; } 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); } double zoomFactor = 0.0500; 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 = currentPosition.X - _previousPosition2D.X; Matrix3D matrixRoateCamera = new Matrix3D(); matrixRoateCamera.RotateAt(new Quaternion(new Vector3D(0, 0, 1), axisAngle), _cameraLookPoint); _camera.Position = Point3D.Multiply(_camera.Position, matrixRoateCamera); _camera.LookDirection = Point3D.Subtract(_cameraLookPoint, _camera.Position); } 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 //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 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); } //for test /* private void FPSMouseMove() { Point curr = Mouse.GetPosition(_eventSource); currentPosition = curr; if (isTracking) { if (Mouse.RightButton == MouseButtonState.Pressed && (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift))) { LookUpDown(currentPosition); } else if (Mouse.LeftButton == MouseButtonState.Pressed && (Keyboard.IsKeyDown(Key.LeftAlt) || Keyboard.IsKeyDown(Key.RightAlt))) { if (isTracking) { _eventSource.Cursor = Cursors.None; Focus(currentPosition); } } else if (Mouse.RightButton == MouseButtonState.Pressed && Keyboard.IsKeyDown(Key.Q)) { if (isTracking) CameraPanUP(currentPosition); } else if (Mouse.RightButton == MouseButtonState.Pressed && Keyboard.IsKeyDown(Key.A)) { if (isTracking) { _eventSource.Cursor = Cursors.None; CameraPanLeftRight(currentPosition); } } else if (Mouse.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 (Mouse.LeftButton == MouseButtonState.Pressed && (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))) { if (isTracking) { _eventSource.Cursor = Cursors.None; isPanningObject = true; PanObject(currentPosition); } } else if (Mouse.LeftButton == MouseButtonState.Pressed) { LookAround(currentPosition); } } _previousPosition2D = currentPosition; } */ } }