using System; using System.Diagnostics; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Media3D; namespace HelixToolkit { public enum MouseAction { None, Pan, Zoom, Rotate, ShowContextMenu, ResetCamera, ChangeLookAt, Select } // http://en.wikipedia.org/wiki/Virtual_camera_system /// /// Inspect/Examine: orbits around a point (fixed target position, move closer target when zooming) /// WalkAround: walk around (fixed camera position, move in cameradirection when zooming) /// FixedPosition: fixed camera position, change FOV when zooming /// public enum CameraMode { Inspect, WalkAround, FixedPosition } /// /// TwoAxis: constrained to two axes of rotation /// VirtualTrackball: free rotation /// public enum CameraRotationMode { TwoAxis, VirtualTrackball } ; public class CameraController : Border { private const double MinimumFoV = 3; private const double MaximumFoV = 160; public static readonly DependencyProperty RotationSensitivityProperty = DependencyProperty.Register("RotationSensitivity", typeof(double), typeof(CameraController), new UIPropertyMetadata(1.0)); public static readonly DependencyProperty CameraProperty = DependencyProperty.Register("Camera", typeof(ProjectionCamera), typeof(CameraController), new UIPropertyMetadata(null)); public static readonly DependencyProperty FixedMouseDownPointProperty = DependencyProperty.Register("FixedMouseDownPoint", typeof(bool), typeof(CameraController), new UIPropertyMetadata(false)); public static readonly DependencyProperty InertiaFactorProperty = DependencyProperty.Register("InertiaFactor", typeof(double), typeof(CameraController), new UIPropertyMetadata(0.9)); public static readonly DependencyProperty InfiniteSpinProperty = DependencyProperty.Register("InfiniteSpin", typeof(bool), typeof(CameraController), new UIPropertyMetadata(false)); public static readonly DependencyProperty IsPanEnabledProperty = DependencyProperty.Register("IsPanEnabled", typeof(bool), typeof(CameraController), new UIPropertyMetadata(true)); public static readonly DependencyProperty IsZoomEnabledProperty = DependencyProperty.Register("IsZoomEnabled", typeof(bool), typeof(CameraController), new UIPropertyMetadata(true)); public static readonly DependencyProperty ShowCameraTargetProperty = DependencyProperty.Register("ShowCameraTarget", typeof(bool), typeof(CameraController), new UIPropertyMetadata(true)); public static readonly DependencyProperty SpinReleaseTimeProperty = DependencyProperty.Register("SpinReleaseTime", typeof(int), typeof(CameraController), new UIPropertyMetadata(200)); public static readonly DependencyProperty TargetModelProperty = DependencyProperty.Register("TargetModel", typeof(ModelVisual3D), typeof(CameraController), new UIPropertyMetadata(null)); public static readonly DependencyProperty CameraModeProperty = DependencyProperty.Register("CameraMode", typeof(CameraMode), typeof(CameraController), new UIPropertyMetadata(CameraMode.Inspect)); public static readonly DependencyProperty CameraRotationModeProperty = DependencyProperty.Register("CameraRotationMode", typeof(CameraRotationMode), typeof(CameraController), new UIPropertyMetadata(CameraRotationMode.TwoAxis)); public static readonly DependencyProperty EnabledProperty = DependencyProperty.Register("Enabled", typeof(bool), typeof(CameraController), new UIPropertyMetadata(true)); public static readonly DependencyProperty EventSurfaceProperty = DependencyProperty.Register("EventSurface", typeof(FrameworkElement), typeof(CameraController), new UIPropertyMetadata(null)); public static readonly DependencyProperty ViewportProperty = DependencyProperty.Register("Viewport", typeof(Viewport3D), typeof(CameraController), new PropertyMetadata(null, ViewportChanged)); public static readonly DependencyProperty UpDirectionProperty = DependencyProperty.Register("ModelUpDirection", typeof(Vector3D), typeof(CameraController), new UIPropertyMetadata(new Vector3D(0, 0, 1))); private readonly Stopwatch spinWatch = new Stopwatch(); private readonly Stopwatch watch = new Stopwatch(); public Action SelectionChanged; private bool isSpinning; private Point3D? lastPoint3D; private Point lastPosition; private long lastTick; private Point mouseDownPosition; private Vector3D panSpeed; private bool panning; private bool rotating; private Vector rotationSpeed; private Vector spinningSpeed; private Adorner targetAdorner; private double zoomSpeed; private bool zooming; private Vector3D fixRelative; private bool isFixed; private Point3D? mouseDownPoint3D; static CameraController() { BackgroundProperty.OverrideMetadata(typeof(CameraController), new FrameworkPropertyMetadata(Brushes.Transparent)); } public CameraController() { ControlRightButtonAction = MouseAction.Zoom; ShiftRightButtonAction = MouseAction.Pan; MiddleButtonAction = MouseAction.Pan; RightButtonAction = MouseAction.Rotate; MiddleDoubleClickAction = MouseAction.ResetCamera; RightDoubleClickAction = MouseAction.ChangeLookAt; LeftButtonAction = MouseAction.Select; EventSurface = this; SubscribeEvents(); watch.Start(); lastTick = watch.ElapsedTicks; } public Vector3D ModelUpDirection { get { return (Vector3D)GetValue(UpDirectionProperty); } set { SetValue(UpDirectionProperty, value); } } public double RotationSensitivity { get { return (double)GetValue(RotationSensitivityProperty); } set { SetValue(RotationSensitivityProperty, value); } } public double InertiaFactor { get { return (double)GetValue(InertiaFactorProperty); } set { SetValue(InertiaFactorProperty, value); } } /// /// Max duration of mouse drag to activate spin /// If the time between mouse down and mouse up is less than this value, spin is activated. /// public int SpinReleaseTime { get { return (int)GetValue(SpinReleaseTimeProperty); } set { SetValue(SpinReleaseTimeProperty, value); } } public bool InfiniteSpin { get { return (bool)GetValue(InfiniteSpinProperty); } set { SetValue(InfiniteSpinProperty, value); } } public bool IsPanEnabled { get { return (bool)GetValue(IsPanEnabledProperty); } set { SetValue(IsPanEnabledProperty, value); } } public bool IsZoomEnabled { get { return (bool)GetValue(IsZoomEnabledProperty); } set { SetValue(IsZoomEnabledProperty, value); } } /// /// Show a 3D model at the target position when manipulating the camera /// public bool ShowCameraTarget { get { return (bool)GetValue(ShowCameraTargetProperty); } set { SetValue(ShowCameraTargetProperty, value); } } /// /// Children to show at the camera target position /// public ModelVisual3D TargetModel { get { return (ModelVisual3D)GetValue(TargetModelProperty); } set { SetValue(TargetModelProperty, value); } } /// /// Keep the point (3D) where rotation/zoom started at the same screen position(2D) /// public bool FixedMouseDownPoint { get { return (bool)GetValue(FixedMouseDownPointProperty); } set { SetValue(FixedMouseDownPointProperty, value); } } private bool IsPerspectiveCamera { get { return ActualCamera is PerspectiveCamera; } } private bool IsOrthographicCamera { get { return ActualCamera is OrthographicCamera; } } private PerspectiveCamera PerspectiveCamera { get { return ActualCamera as PerspectiveCamera; } } private OrthographicCamera OrthographicCamera { get { return ActualCamera as OrthographicCamera; } } public ProjectionCamera Camera { get { return (ProjectionCamera)GetValue(CameraProperty); } set { SetValue(CameraProperty, value); } } private Vector3D LookDirection { get { return ActualCamera.LookDirection; } set { ActualCamera.LookDirection = value; } } private Vector3D UpDirection { get { return ActualCamera.UpDirection; } set { ActualCamera.UpDirection = value; } } private Point3D Position { get { return ActualCamera.Position; } set { ActualCamera.Position = value; } } public Point3D CameraTarget { get { return Position + LookDirection; } } public MouseAction LeftDoubleClickAction { get; set; } public MouseAction MiddleDoubleClickAction { get; set; } public MouseAction RightDoubleClickAction { get; set; } public MouseAction LeftButtonAction { get; set; } public MouseAction ShiftLeftButtonAction { get; set; } public MouseAction ControlLeftButtonAction { get; set; } public MouseAction MiddleButtonAction { get; set; } public MouseAction ShiftMiddleButtonAction { get; set; } public MouseAction ControlMiddleButtonAction { get; set; } public MouseAction RightButtonAction { get; set; } public MouseAction ShiftRightButtonAction { get; set; } public MouseAction ControlRightButtonAction { get; set; } public double ZoomSensitivity { get; set; } public double RotateSensitivity { get; set; } public double PanSensitivity { get; set; } public CameraRotationMode CameraRotationMode { get { return (CameraRotationMode)GetValue(CameraRotationModeProperty); } set { SetValue(CameraRotationModeProperty, value); } } public CameraMode CameraMode { get { return (CameraMode)GetValue(CameraModeProperty); } set { SetValue(CameraModeProperty, value); } } /// /// The element that receives mouse events /// public FrameworkElement EventSurface { get { return (FrameworkElement)GetValue(EventSurfaceProperty); } set { SetValue(EventSurfaceProperty, value); } } public Viewport3D Viewport { get { return (Viewport3D)GetValue(ViewportProperty); } set { SetValue(ViewportProperty, value); } } public bool Enabled { get { return (bool)GetValue(EnabledProperty); } set { SetValue(EnabledProperty, value); } } public bool IsActive { get { return Enabled && Viewport != null && ActualCamera != null; } } private void OnSelectionChanged(DependencyObject visual) { if (SelectionChanged != null) SelectionChanged(visual); } private static void ViewportChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { ((CameraController)d).OnViewportChanged(); } private void OnViewportChanged() { } public ProjectionCamera ActualCamera { get { if (Camera != null) return Camera; if (Viewport != null) return Viewport.Camera as ProjectionCamera; return null; } } private void SubscribeEvents() { EventSurface.MouseMove += MouseMoveHandler; EventSurface.MouseDown += MouseDownHandler; EventSurface.MouseUp += MouseUpHandler; EventSurface.MouseWheel += OnMouseWheel; //EventSurface.KeyDown += OnKeyDown; CompositionTarget.Rendering += CompositionTarget_Rendering; } // todo private void UnSubscribeEvents() { EventSurface.MouseMove -= MouseMoveHandler; EventSurface.MouseDown -= MouseDownHandler; EventSurface.MouseUp -= MouseUpHandler; EventSurface.MouseWheel -= OnMouseWheel; //EventSurface.KeyDown -= OnKeyDown; CompositionTarget.Rendering -= CompositionTarget_Rendering; } private void CompositionTarget_Rendering(object sender, EventArgs e) { // Time in seconds double time = 1.0 * (watch.ElapsedTicks - lastTick) / Stopwatch.Frequency; lastTick = watch.ElapsedTicks; OnTimeStep(time); } private void OnTimeStep(double time) { // should be independent of time double factor = Math.Pow(InertiaFactor, time / 0.012); factor = Clamp(factor, 0.2, 1); if (isSpinning && spinningSpeed.LengthSquared > 0) { Rotate(spinningSpeed.X * time, spinningSpeed.Y * time); if (!InfiniteSpin) spinningSpeed *= factor; spinWatch.Reset(); spinWatch.Start(); } if (rotationSpeed.LengthSquared > 0.1) { Rotate(rotationSpeed.X * time, rotationSpeed.Y * time); rotationSpeed *= factor; } if (Math.Abs(panSpeed.LengthSquared) > 0.0001) { Pan(panSpeed * time); panSpeed *= factor; } if (Math.Abs(zoomSpeed) > 0.1) { Zoom(zoomSpeed * time); zoomSpeed *= factor; } } private double Clamp(double factor, double min, double max) { if (factor < min) return min; if (factor > max) return max; return factor; } // todo private void OnKeyDown(object sender, KeyEventArgs e) { base.OnKeyDown(e); switch (e.Key) { case Key.Left: AddRotateForce(-50, 0); break; case Key.Right: AddRotateForce(50, 0); break; } } private void MouseDownHandler(object sender, MouseButtonEventArgs e) { if (!Enabled) return; if (Viewport == null) throw new NullReferenceException("Viewport"); var element = (UIElement)sender; if (element.IsMouseCaptured) return; mouseDownPosition = e.GetPosition(this); fixRelative = new Vector3D(); // reset camera if (CheckButton(e, MouseAction.ResetCamera)) ResetCamera(); Point3D point; Vector3D normal; DependencyObject visual; if (Viewport3DHelper.FindNearest(Viewport, mouseDownPosition, out point, out normal, out visual)) mouseDownPoint3D = point; else mouseDownPoint3D = null; lastPoint3D = UnProject(mouseDownPosition, CameraTarget, LookDirection); // select object if (CheckButton(e, MouseAction.Select) && visual != null) OnSelectionChanged(visual); zooming = CheckButton(e, MouseAction.Zoom); panning = CheckButton(e, MouseAction.Pan); rotating = CheckButton(e, MouseAction.Rotate); isFixed = false; // change the 'lookat' point if (mouseDownPoint3D != null && CheckButton(e, MouseAction.ChangeLookAt)) { LookAt(mouseDownPoint3D.Value, 0); rotating = true; } if (zooming || panning || rotating) { bool rightWinKey = (Keyboard.IsKeyDown(Key.RWin)); if (FixedMouseDownPoint || rightWinKey) { if (!panning && mouseDownPoint3D != null) { fixRelative = mouseDownPoint3D.Value - CameraTarget; ShowTargetAdorner(mouseDownPosition); isFixed = true; } } else { // show the adorner in the middle ShowTargetAdorner(new Point(Viewport.ActualWidth / 2, Viewport.ActualHeight / 2)); } /* Position += _fixRelative; ShowTargetModel(); Position -= _fixRelative; */ if (zooming || panning || rotating) { e.Handled = true; element.CaptureMouse(); } spinWatch.Reset(); spinWatch.Start(); // ProjectToTrackball(EventSurface.ActualWidth, EventSurface.ActualHeight, _mouseDownPosition); lastPosition = mouseDownPosition; } isSpinning = false; } private bool CheckButton(MouseButtonEventArgs e, MouseAction a) { bool control = (Keyboard.IsKeyDown(Key.LeftCtrl)); bool shift = (Keyboard.IsKeyDown(Key.LeftShift)); bool doubleClick = e.ClickCount == 2; if (e.LeftButton == MouseButtonState.Pressed) { if (control) return a == ControlLeftButtonAction; if (shift) return a == ShiftLeftButtonAction; if (doubleClick) return a == LeftDoubleClickAction; return a == LeftButtonAction; } if (e.MiddleButton == MouseButtonState.Pressed) { if (control) return a == ControlMiddleButtonAction; if (shift) return a == ShiftMiddleButtonAction; if (doubleClick) return a == MiddleDoubleClickAction; return a == MiddleButtonAction; } if (e.RightButton == MouseButtonState.Pressed) { if (control) return a == ControlRightButtonAction; if (shift) return a == ShiftRightButtonAction; if (doubleClick) return a == RightDoubleClickAction; return a == RightButtonAction; } return false; } // From 3dtools private static 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; return new Vector3D(x, z, y); } private void MouseMoveHandler(object sender, MouseEventArgs e) { if (!IsActive) return; var element = (UIElement)sender; if (element.IsMouseCaptured) { Point point = e.MouseDevice.GetPosition(element); // move target point to mouse down point (3D) // camera will be positioned back later Position += fixRelative; Point3D? thisPoint3D = UnProject(point, CameraTarget, LookDirection); Vector3D delta3D = lastPoint3D.Value - thisPoint3D.Value; Vector delta = point - lastPosition; lastPosition = point; // var thisTrack3D = ProjectToTrackball(EventSurface.ActualWidth, EventSurface.ActualHeight, point); if (rotating) { Rotate(delta.X, delta.Y); } if (zooming) Zoom(delta.Y * 0.01); if (panning) Pan(delta3D); UpdateTargetModel(); lastPoint3D = UnProject(point, CameraTarget, LookDirection); Position -= fixRelative; if (isFixed) { // todo: // reposition the camera so mouse down point (3D) matches the mousedown position (2D) if (mouseDownPoint3D != null) Pan(mouseDownPoint3D.Value, mouseDownPosition); } e.Handled = true; } } private void Pan(Point3D point3D, Point position) { Point3D? nowPoint3D = UnProject(position, point3D, LookDirection); Pan(point3D - nowPoint3D.Value); Point newPosition = Project(point3D); Debug.Assert(newPosition == position); } private void MouseUpHandler(object sender, MouseButtonEventArgs e) { if (!Enabled) return; var element = (UIElement)sender; if (spinWatch.ElapsedMilliseconds < SpinReleaseTime) { if (rotating) { spinningSpeed = 4 * (lastPosition - mouseDownPosition) * ((double)SpinReleaseTime / spinWatch.ElapsedMilliseconds); spinWatch.Reset(); spinWatch.Start(); isSpinning = true; } } rotating = false; zooming = false; panning = false; if (element.IsMouseCaptured) { e.Handled = true; HideTargetModel(); HideTargetAdorner(); element.ReleaseMouseCapture(); } } private void OnMouseWheel(object sender, MouseWheelEventArgs e) { e.Handled = true; //Zoom(-e.Delta * 0.001); AddZoomForce(-e.Delta * 0.001); } public void ResetCamera() { CameraHelper.Reset(ActualCamera); } /// /// Changes the field of view and tries to keep the scale fixed. /// /// The relative change in fov. public void ChangeFov(double delta) { if (!IsPerspectiveCamera) return; double fov = PerspectiveCamera.FieldOfView; double d = LookDirection.Length; double r = d * Math.Tan(0.5 * fov / 180 * Math.PI); fov *= (1 + delta * 0.5); if (fov < MinimumFoV) fov = MinimumFoV; if (fov > MaximumFoV) fov = MaximumFoV; PerspectiveCamera.FieldOfView = fov; double d2 = r / Math.Tan(0.5 * fov / 180 * Math.PI); Vector3D dir = LookDirection; dir.Normalize(); dir *= d2; Point3D target = Position + LookDirection; Position = target - dir; LookDirection = dir; } public void Zoom(double delta) { if (!IsZoomEnabled) return; bool shift = Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift); if (shift && IsPerspectiveCamera) ChangeFov(delta); else ChangeCameraDistance(delta); } /// /// Changes the camera distance. /// /// The delta. public void ChangeCameraDistance(double delta) { bool alt = Keyboard.IsKeyDown(Key.LeftAlt) || Keyboard.IsKeyDown(Key.RightAlt); CameraMode cm = CameraMode; if (alt) cm = (CameraMode)(((int)CameraMode + 1) % 2); if (delta < -0.5) delta = -0.5; if (IsPerspectiveCamera) { switch (cm) { case CameraMode.Inspect: Point3D target = Position + LookDirection; LookDirection *= (1 + delta); Position = target - LookDirection; break; case CameraMode.WalkAround: Position -= LookDirection * delta; break; case CameraMode.FixedPosition: double fov = PerspectiveCamera.FieldOfView; fov *= (1 + delta); if (fov < MinimumFoV) fov = MinimumFoV; if (fov > MaximumFoV) fov = MaximumFoV; PerspectiveCamera.FieldOfView = fov; break; } } if (IsOrthographicCamera) { switch (cm) { // todo... case CameraMode.WalkAround: case CameraMode.Inspect: case CameraMode.FixedPosition: OrthographicCamera.Width *= 1 + delta; break; } } } public void Pan(Vector3D delta) { if (!IsPanEnabled) return; if (CameraMode == CameraMode.FixedPosition) return; Position += delta; } public void Rotate(double dx, double dy) { Point3D target = CameraTarget; // toggle rotation mode if the user presses alt bool alt = (Keyboard.IsKeyDown(Key.LeftAlt)); if ((CameraRotationMode == CameraRotationMode.VirtualTrackball) != alt) { RotateRoam(dx, dy); // Track(thisTrack3D, lastTrack3D); // lastTrack3D = thisTrack3D; } else { RotateTwoAxes(dx, dy); } if (Math.Abs(UpDirection.Length - 1) > 1e-8) UpDirection.Normalize(); if (IsFixedPosition()) Position = target - LookDirection; } public bool IsFixedPosition() { bool leftWinKey = Keyboard.IsKeyDown(Key.LWin); // fix the camera position if user presses left Windows key if (leftWinKey) return CameraMode != CameraMode.Inspect; return CameraMode == CameraMode.Inspect; } // http://www.codeplex.com/3DTools/Thread/View.aspx?ThreadId=22310 private void RotateRoam(double dx, double dy) { double dist = LookDirection.Length; Vector3D camZ = LookDirection; camZ.Normalize(); Vector3D camX = -Vector3D.CrossProduct(camZ, UpDirection); camX.Normalize(); Vector3D camY = Vector3D.CrossProduct(camZ, camX); camY.Normalize(); double d = 0.5 * RotationSensitivity; var aarY = new AxisAngleRotation3D(camY, -dx * d); var aarX = new AxisAngleRotation3D(camX, dy * d); var rotY = new RotateTransform3D(aarY); var rotX = new RotateTransform3D(aarX); camZ = camZ * rotY.Value * rotX.Value; camZ.Normalize(); camY = camY * rotX.Value * rotY.Value; camY.Normalize(); Vector3D newLookDir = camZ * dist; Vector3D newUpDir = camY; Vector3D right = Vector3D.CrossProduct(newLookDir, newUpDir); right.Normalize(); Vector3D modUpDir = Vector3D.CrossProduct(right, newLookDir); modUpDir.Normalize(); if ((newUpDir - modUpDir).Length > 1e-8) newUpDir = modUpDir; LookDirection = newLookDir; UpDirection = newUpDir; } public void RotateTwoAxes(double dx, double dy) { Vector3D up = ModelUpDirection; // new Vector3D(0, 0, 1); Vector3D dir = LookDirection; dir.Normalize(); Vector3D right = Vector3D.CrossProduct(dir, UpDirection); right.Normalize(); double d = -0.5; if (CameraMode == CameraMode.WalkAround) d = 0.1; d *= RotationSensitivity; //double sgn = -Math.Acos(Vector3D.DotProduct(up,dir)); var q1 = new Quaternion(up, d * dx); var q2 = new Quaternion(right, d * dy); Quaternion q = q1 * q2; var m = new Matrix3D(); m.Rotate(q); Vector3D newLookDir = m.Transform(LookDirection); Vector3D newUpDir = m.Transform(UpDirection); right = Vector3D.CrossProduct(newLookDir, newUpDir); right.Normalize(); Vector3D modUpDir = Vector3D.CrossProduct(right, newLookDir); modUpDir.Normalize(); if ((newUpDir - modUpDir).Length > 1e-8) newUpDir = modUpDir; LookDirection = newLookDir; UpDirection = newUpDir; } public void AddPanForce(double dx, double dy) { AddPanForce(FindPanVector(dx, dy)); } private Vector3D FindPanVector(double dx, double dy) { Vector3D axis1 = Vector3D.CrossProduct(LookDirection, UpDirection); Vector3D axis2 = Vector3D.CrossProduct(axis1, LookDirection); axis1.Normalize(); axis2.Normalize(); double l = LookDirection.Length; double f = l * 0.001; Vector3D move = -axis1 * f * dx + axis2 * f * dy; // this should be dependent on distance to target? return move; } public void AddPanForce(Vector3D pan) { panSpeed += pan * 40; } public void AddRotateForce(double dx, double dy) { rotationSpeed.X += dx * 40; rotationSpeed.Y += dy * 40; } public void AddZoomForce(double dx) { zoomSpeed += dx * 8; } /// /// Get the ray into the view volume given by the position in 2D (screen coordinates) /// /// /// public Ray3D GetRay(Point position) { Point3D point1, point2; bool ok = Viewport3DHelper.Point2DtoPoint3D(Viewport, position, out point1, out point2); if (!ok) return null; return new Ray3D { Origin = point1, Direction = point2 - point1 }; } /// /// Unproject a point from the screen (2D) to a point on plane (3D) /// /// /// plane position /// plane normal /// public Point3D? UnProject(Point p, Point3D position, Vector3D normal) { Ray3D ray = GetRay(p); if (ray == null) return null; return ray.PlaneIntersection(position, normal); } /// /// Calculate the screen position of a 3D point /// /// /// public Point Project(Point3D p) { return Viewport3DHelper.Point3DtoPoint2D(Viewport, p); } /// /// Set the camera target point /// /// The target. /// The animation time. public void LookAt(Point3D target, double animationTime) { LookAt(target, LookDirection, animationTime); } /// /// Set the camera target point /// /// The target point. /// The distance to the camera. /// The animation time. public void LookAt(Point3D target, double distance, double animationTime) { Vector3D d = LookDirection; d.Normalize(); LookAt(target, d * distance, animationTime); } /// /// Set the camera target point /// /// The target. /// The new direction. /// The animation time. public void LookAt(Point3D target, Vector3D newDirection, double animationTime) { Point3D newPosition = target - newDirection; if (IsPerspectiveCamera) CameraHelper.AnimateTo(PerspectiveCamera, newPosition, newDirection, UpDirection, animationTime); if (IsOrthographicCamera) CameraHelper.AnimateTo(OrthographicCamera, newPosition, newDirection, UpDirection, animationTime); } private void UpdateTargetModel() { Point3D target = Position + LookDirection; if (TargetModel != null) TargetModel.Transform = new TranslateTransform3D(target.X, target.Y, target.Z); } private void ShowTargetModel() { if (TargetModel != null && ShowCameraTarget) { if (!Viewport.Children.Contains(TargetModel)) Viewport.Children.Insert(0, TargetModel); UpdateTargetModel(); } } private void HideTargetModel() { if (TargetModel != null) { if (Viewport.Children.Contains(TargetModel)) Viewport.Children.Remove(TargetModel); } } private void ShowTargetAdorner(Point position) { if (!ShowCameraTarget) return; if (targetAdorner != null) return; AdornerLayer myAdornerLayer = AdornerLayer.GetAdornerLayer(Viewport); targetAdorner = new TargetSymbolAdorner(Viewport, position); myAdornerLayer.Add(targetAdorner); } private void HideTargetAdorner() { AdornerLayer myAdornerLayer = AdornerLayer.GetAdornerLayer(Viewport); if (targetAdorner != null) myAdornerLayer.Remove(targetAdorner); targetAdorner = null; } } }