WPF Shape bounding rectangle is always empty - wpf

I have the following code in my WPF application:
public MainWindow()
{
InitializeComponent();
p1 = new Point(50, 50);
p2 = new Point(355, 50);
p3 = new Point(50, 355);
p4 = new Point(355, 355);
Loaded += (x, y) => Draw();
//ContentRendered += (x, y) => Draw();
}
I create a Polygon shape and try to get its bounding rectangle several ways:
private void Draw()
{
Polygon polygon = new Polygon();
polygon.Stroke = System.Windows.Media.Brushes.White;
polygon.Points.Add(p1);
polygon.Points.Add(p2);
polygon.Points.Add(p3);
polygon.Points.Add(p4);
canvas.Children.Add(polygon);
boundingRect = polygon.TransformToVisual(canvas).TransformBounds(new Rect(polygon.RenderSize));
boundingRect = polygon.TransformToVisual(polygon).TransformBounds(System.Windows.Controls.Primitives.LayoutInformation.GetLayoutSlot(polygon));
boundingRect = polygon.RenderTransform.TransformBounds(new Rect(polygon.RenderSize));
boundingRect = GetRectOfObject(polygon);
boundingRect = polygon.RenderedGeometry.Bounds;
}
private Rect GetRectOfObject(FrameworkElement _element)
{
Rect rectangleBounds = new Rect();
rectangleBounds = _element.RenderTransform.TransformBounds(new Rect(0, 0, _element.ActualWidth, _element.ActualHeight));
return rectangleBounds;
}
However, I always get {0;0;0;0}.
On debug, I can see the points but its size is rendered 0:
How should I calculate the correct way?

According to the MSDN remarks of the Measure method, calling UpdateLayout will obtain the necessary data (though it should be treated carefully), so this way the measuring works:
private void Draw()
{
Polygon polygon = new Polygon();
polygon.Stroke = System.Windows.Media.Brushes.White;
polygon.Points.Add(p1);
polygon.Points.Add(p2);
polygon.Points.Add(p3);
polygon.Points.Add(p4);
canvas.Children.Add(polygon);
polygon.UpdateLayout();
boundingRect = polygon.TransformToVisual(canvas).TransformBounds(new Rect(polygon.RenderSize));
}
Thanks #ChrisF for the hints.

Related

WPF - Helix Toolkit Auto Rotation

I have been researching this over the last week - 2 weeks and I have been unable to find a solution. I am loading a 3D Model from an STL file and attempting to rotate the 3D model automatically around 1 axis. The idea would be something like a slow-moving animation that displays a 360-degree view around the Y-axis of the model.
XAML:
<Grid>
<StackPanel x:Name="myViewPort">
<helix:HelixViewport3D x:Name="viewPort3d" ZoomExtentsWhenLoaded="true" RotateAroundMouseDownPoint="true" CameraRotationMode="Turntable" Height="1000" ShowViewCube="false">
<helix:DefaultLights/>
<ModelVisual3D x:Name="visualModel"/>
</helix:HelixViewport3D>
</StackPanel>
</Grid>
C#:
public void load3dModel()
{
StLReader stlReader = new StLReader();
Model3DGroup MyModel = stlReader.Read(MODEL_PATH);
/* Auto Rotate Here */
System.Windows.Media.Media3D.Material mat = MaterialHelper.CreateMaterial(new SolidColorBrush(Color.FromRgb(255, 255, 255)));
foreach (System.Windows.Media.Media3D.GeometryModel3D geometryModel in MyModel.Children)
{
geometryModel.Material = mat;
geometryModel.BackMaterial = mat;
}
visualModel.Content = MyModel;
}
The tricky part about this is I need to be able to rotate the model using CODE ONLY. The models generated can be one of the hundreds, and it will depend on the application for what the model will be... So the code needs to be able to handle rotating around the same axis, I can guarantee when the 3D model is exported to the STL file it will be flat along the X-axis.
--- UPDATE ---
Attempted Rotation via Storyboard:
public void load3dModel()
{
StLReader stlReader = new StLReader();
Model3DGroup MyModel = stlReader.Read(MODEL_PATH);
System.Windows.Media.Media3D.Material mat = MaterialHelper.CreateMaterial(new SolidColorBrush(Color.FromRgb(255, 255, 255)));
foreach (System.Windows.Media.Media3D.GeometryModel3D geometryModel in MyModel.Children)
{
geometryModel.Material = mat;
geometryModel.BackMaterial = mat;
}
visualModel.Content = MyModel;
/* Auto Rotate Here */
GeometryModel3D geoModel = new GeometryModel3D()
{
Transform = new RotateTransform3D()
{
Rotation = new AxisAngleRotation3D()
{
Axis = new Vector3D(0, 1, 0),
Angle = 0
}
}
};
MyModel.Children.Add(geoModel);
var Rotation3DAnimation = new Rotation3DAnimation();
var FromAxis = new AxisAngleRotation3D()
{
Axis = new Vector3D(0, 1, 0),
Angle = 0
};
var ToAxis = new AxisAngleRotation3D()
{
Axis = new Vector3D(0, 1, 0),
Angle = 359
};
Rotation3DAnimation.From = FromAxis;
Rotation3DAnimation.To = ToAxis;
Rotation3DAnimation.Duration = Duration.Forever; //ADDED DURATION, Still did not work!
var rotateStoryboard = new Storyboard
{
Duration = new Timespan(0, 0, 12),
RepeatBehavior = RepeatBehavior.Forever,
};
Storyboard.SetTarget(Rotation3DAnimation, geoModel.Transform);
Storyboard.SetTargetProperty(Rotation3DAnimation, new PropertyPath("Rotation"));
rotateStoryboard.Children.Add(Rotation3DAnimation);
rotateStoryboard.Begin();
}
This did not work... Nothing changed?
Thanks!
I am not sure if I understood correctly what you are trying to accomplish so let me know if I misunderstood:
In the code you showed, you are loading several GeometryModel3D so are you trying to make them all rotate or just one ?
One way you could make it rotate is via the Transform property of the GeometryModel3D.
You will have to set up a DispatcherTimer and update the angle of the rotation on every Tick:
I made an example based on what you provided where I make one 3D model rotate:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.Load3dModel();
this.timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(10) };
this.timer.Tick += Timer_Tick;
this.timer.Start();
}
/// <summary>
/// Change the rotation
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Timer_Tick(object sender, EventArgs e)
{
if (this.angle >= 360)
{
this.angle = 0;
}
else
{
//Nothing to do
}
this.angle = this.angle + 0.25;
//You can adapt the code if you have many children
GeometryModel3D geometryModel3D = (GeometryModel3D)((Model3DGroup)visualModel.Content).Children.First();
if (geometryModel3D.Transform is RotateTransform3D rotateTransform3 && rotateTransform3.Rotation is AxisAngleRotation3D rotation)
{
rotation.Angle = this.angle;
}
else
{
///Initialize the Transform (I didn't do it in my example but you could do this initialization in <see Load3dModel/>)
geometryModel3D.Transform = new RotateTransform3D()
{
Rotation = new AxisAngleRotation3D()
{
Axis = new Vector3D(0, 1, 0),
Angle = this.angle,
}
};
}
}
private DispatcherTimer timer;
public void Load3dModel()
{
StLReader stlReader = new StLReader();
/*
Model3DGroup MyModel = stlReader.Read(OrLoadFromPath));
*/
Model3DGroup myModel = new Model3DGroup();
// Create a mesh builder and add a box to it
var meshBuilder = new MeshBuilder(false, false);
meshBuilder.AddBox(new Point3D(0, 0, 1), 1, 2, 0.5);
meshBuilder.AddBox(new Rect3D(0, 0, 1.2, 0.5, 1, 0.4));
// Create a mesh from the builder (and freeze it)
var mesh = meshBuilder.ToMesh(true);
// Add 3 models to the group (using the same mesh, that's why we had to freeze it)
myModel.Children.Add(new GeometryModel3D { Geometry = mesh});
myModel.Children.Add(new GeometryModel3D { Geometry = mesh, Transform = new TranslateTransform3D(-2, 0, 0)});
myModel.Children.Add(new GeometryModel3D { Geometry = mesh, Transform = new TranslateTransform3D(2, 0, 0)});
System.Windows.Media.Media3D.Material mat = MaterialHelper.CreateMaterial(new SolidColorBrush(Color.FromRgb(255, 255, 255)));
foreach (System.Windows.Media.Media3D.GeometryModel3D geometryModel in myModel.Children)
{
geometryModel.Material = mat;
geometryModel.BackMaterial = mat;
}
visualModel.Content = myModel;
}
private double angle = 0;
}
The rotation was pretty smooth on my end with those parameters but you will have to test/adapt it on your application.

WPF: DrawingContext resolution

I'm using DrawingContext class to draw a bar graph, but the produced labels and ticks are blurry:
In the code I am using a class BarRenderer derived from FrameworkElement. Each bar is represented by a BarRenderer instance, and I do the drawing by overriding the OnRender method. Each "bar" has a label and a value where label is the text below and value defines the height of the bar. and The logic responsible for drawing labels and tick is as follows:
private void DrawLabel(DrawingContext drawingContext, FormattedText text, double x, double y, int direction)
{
drawingContext.DrawLine(_blackPen, new Point(Width * 0.5, y), new Point(Width * 0.5, y + 6));
y += 6;
if (LabelRotationDegree != 0)
{
RotateTransform rotateTransform = new RotateTransform(-LabelRotationDegree, 0.5 * Width, y + text.Height * 0.5);
TranslateTransform translateTransform = new TranslateTransform(0, direction * text.WidthIncludingTrailingWhitespace * 0.5);
drawingContext.PushTransform(translateTransform);
drawingContext.PushTransform(rotateTransform);
}
drawingContext.DrawText(text, new Point(x, y));
if (LabelRotationDegree != 0)
{
drawingContext.Pop();
drawingContext.Pop();
}
}
I doubt that the issue is with the rotation transform, since when the labels are not transformed they are no longer as blurry as before:
But as can be seen some of the ticks are darker than the others. So what am I doing wrong?
EDIT:
I've already set SnapsToDevicePixels to true, and when I set RenderOptions.SetEdgeMode to Aliased some ticks disappear and text is still blurry:
Edit 2: More code
public class GraphUnitRenderer : FrameworkElement
{
private const double TEXT_SIZE = 12;
private const double KEY_LABELS_HEIGHT = 50;
private readonly Typeface _typeface;
private readonly CultureInfo _cultureInfo;
private readonly Pen _blackPen;
private readonly Pen _bluePen;
private readonly Pen _tickBlackPen;
private readonly Pen _whitePen;
public BarData Data { get; set; }
//Some properties
public GraphUnitRenderer()
{
_typeface = new Typeface("Segoe UI");
_cultureInfo = CultureInfo.CurrentCulture;
_blackPen = new Pen(Brushes.Black, 1);
_bluePen = new Pen(Brushes.Blue, 1);
_tickBlackPen = new Pen(Brushes.Black, 2);
_whitePen = new Pen(Brushes.White, 1);
/*
_blackPen.Freeze();
_bluePen.Freeze();
_tickBlackPen.Freeze();
_whitePen.Freeze();
*/
RenderOptions.SetEdgeMode(this, EdgeMode.Aliased);
SnapsToDevicePixels = true;
}
protected override void OnRender(DrawingContext drawingContext)
{
TextOptions.SetTextFormattingMode(this, TextFormattingMode.Display);
FormattedText keyText = GetText(Data.Key);
//Bar drawing logic
if (LabelDisplayIsForced || (LabelRotationDegree == 0 && keyText.WidthIncludingTrailingWhitespace < Width) || (LabelRotationDegree != 0 && keyText.Height < Width))
DrawLabel(drawingContext, keyText, 0.5 * (Width - keyText.WidthIncludingTrailingWhitespace), GetYPosition(1) + 1, 1);
}
private void DrawLabel(DrawingContext drawingContext, FormattedText text, double x, double y, int direction)
{
drawingContext.DrawLine(_blackPen, new Point(Width * 0.5, y), new Point(Width * 0.5, y + 6));
y += 6;
if (LabelRotationDegree != 0)
{
RotateTransform rotateTransform = new RotateTransform(-LabelRotationDegree, 0.5 * Width, y + text.Height * 0.5);
TranslateTransform translateTransform = new TranslateTransform(0, direction * text.WidthIncludingTrailingWhitespace * 0.5);
drawingContext.PushTransform(translateTransform);
drawingContext.PushTransform(rotateTransform);
}
drawingContext.DrawText(text, new Point(x, y));
if (LabelRotationDegree != 0)
{
drawingContext.Pop();
drawingContext.Pop();
}
}
private double GetYPosition(double position) => Height - position - KEY_LABELS_HEIGHT;
private FormattedText GetText(string text) => new FormattedText(text, _cultureInfo, FlowDirection.LeftToRight, _typeface, TEXT_SIZE, Brushes.Black, VisualTreeHelper.GetDpi(this).PixelsPerDip);
}

Animation in codebehind for loop, using RenderTransform

Is it possible to animate the "old school" way, in codebehind, instead of xaml?
I just want an arrow that points to something with a 'bounce effect' which I could easily do in my own for loop. But I do not know how to refresh or do a timer delay, inside the loop. I already placed the image into position in codebehind. All I want to do is this simple animation...
public void validationArrow()
{
var validationArrow = new Image();
validationArrow.Source = new BitmapImage(new Uri("/SlProject;component/arrow.png", UriKind.RelativeOrAbsolute));
LayoutRoot.Children.Add(validationArrow);
validationArrow.Stretch = Stretch.None;
validationArrow.VerticalAlignment = System.Windows.VerticalAlignment.Top;
validationArrow.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;
var arrowPosition = new TranslateTransform { X = 0, Y = 0 };
validationArrow.RenderTransform = arrowPosition;
validationArrow.Name = "validationArrow";
for (int i = 150; i >= 0; i--)
{
arrowPosition.X = i;
validationArrow.RenderTransform = arrowPosition;
// how can I refresh screen and do some timing here?
}
}
There's no school like the old school ;)
Here, this should help you on your way. You can play with the millisecond and Y translation values being passed to the BuildEasing method to change the 'bounce' effect's speed and distance.
private void RunStoryboard()
{
var arrowImage = new Image();
arrowImage.RenderTransform = new CompositeTransform();
arrowImage.Source = new BitmapImage(new Uri("/SlProject;component/arrow.png", UriKind.RelativeOrAbsolute));
LayoutRoot.Children.Add(arrowImage);
Storyboard storyboard = new Storyboard();
storyboard.Children.Add(BuildKeyFrame(arrowImage));
storyboard.Begin();
}
private DoubleAnimationUsingKeyFrames BuildKeyFrame(Image target)
{
DoubleAnimationUsingKeyFrames kf = new DoubleAnimationUsingKeyFrames();
Storyboard.SetTarget(kf, target);
Storyboard.SetTargetProperty(kf, new PropertyPath("(UIElement.RenderTransform).(CompositeTransform.TranslateY)"));
kf.KeyFrames.Add(BuildEasing(100, 10));
kf.KeyFrames.Add(BuildEasing(200, 0));
kf.KeyFrames.Add(BuildEasing(300, 10));
kf.KeyFrames.Add(BuildEasing(400, 0));
kf.KeyFrames.Add(BuildEasing(500, 10));
kf.KeyFrames.Add(BuildEasing(600, 0));
return kf;
}
private EasingDoubleKeyFrame BuildEasing(int ms, int value)
{
return new EasingDoubleKeyFrame()
{
KeyTime = KeyTime.FromTimeSpan(new TimeSpan(0, 0, 0, 0, ms)),
Value = value
};
}

Problem adding Viewport2DVisual3D from Code

I'm trying to add a Viewport2DVisual3D to a Viewport3D in code, but the visual isn't showing up. Any help understanding why not would be appreciated. The following is the code for the main window.
Is it sufficient to just add the Viewport2DVisual3D to the children of the Viewport3D in order for it to be rendered?
public partial class Window1 : System.Windows.Window
{
public Window1()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(temp);
}
public void temp(object sender, RoutedEventArgs e)
{
Viewport2DVisual3D test = new Viewport2DVisual3D();
MeshGeometry3D testGeometry = new MeshGeometry3D();
Vector3D CameraLookDirection = Main_Target_CameraOR20.LookDirection;
// Calculate the Positions based on the Camera
Point3DCollection myPoint3DCollection = new Point3DCollection();
myPoint3DCollection.Add(new Point3D(-1, 1, 0));
myPoint3DCollection.Add(new Point3D(-1, -1, 0));
myPoint3DCollection.Add(new Point3D(1, -1, 0));
myPoint3DCollection.Add(new Point3D(1, 1, 0));
testGeometry.Positions = myPoint3DCollection;
PointCollection myPointCollection = new PointCollection();
myPointCollection.Add(new Point(0, 0));
myPointCollection.Add(new Point(0, 1));
myPointCollection.Add(new Point(1, 1));
myPointCollection.Add(new Point(1, 0));
testGeometry.TextureCoordinates = myPointCollection;
Int32Collection triangleIndicesCollection = new Int32Collection();
triangleIndicesCollection.Add(0);
triangleIndicesCollection.Add(1);
triangleIndicesCollection.Add(2);
triangleIndicesCollection.Add(2);
triangleIndicesCollection.Add(3);
triangleIndicesCollection.Add(0);
testGeometry.TriangleIndices = triangleIndicesCollection;
DiffuseMaterial myDiffuseMaterial = new DiffuseMaterial(Brushes.White);
Viewport2DVisual3D.SetIsVisualHostMaterial(myDiffuseMaterial, true);
Transform3DGroup myTransform3DGroup = new Transform3DGroup();
ScaleTransform3D myScaleTransform3D = new ScaleTransform3D();
myScaleTransform3D.ScaleX = 2;
myScaleTransform3D.ScaleY = 2;
myScaleTransform3D.ScaleZ = 2;
TranslateTransform3D myTranslateTransform3D = new TranslateTransform3D();
myTranslateTransform3D.OffsetX = -27;
myTranslateTransform3D.OffsetY = 13;
myTranslateTransform3D.OffsetZ = 6;
RotateTransform3D rotateTransform = new RotateTransform3D()
{
Rotation = new AxisAngleRotation3D
{
Angle = -50,
Axis = new Vector3D(0, 1, 0)
}
};
myTransform3DGroup.Children.Add(myTranslateTransform3D);
myTransform3DGroup.Children.Add(myScaleTransform3D);
myTransform3DGroup.Children.Add(rotateTransform);
test.Transform = myTransform3DGroup;
Button myButton = new Button();
myButton.Content = "Test Button";
test.Material = myDiffuseMaterial;
test.Geometry = testGeometry;
test.Visual = myButton;
ZAM3DViewport3D.Children.Add(test);
}
}
It turns out that the problem was the Offset value. So, it is sufficient to add the child to the Viewport3D to have it render. Cheers

WPF 3D - How can I save and load a Camera view?

I have a WPF 3D scene where I can pan, rotate and zoom using the TrackballDecorator from the 3DTools library. I would like to save the camera settings (transformation) and be able to re-apply them when the application restarts the next time (so the view is restored).
I tried to save each individual value of the Camera:
private void SaveCameraSettings()
{
var d = Properties.Settings.Default;
d.CameraPositionX = camera.Position.X;
d.CameraPositionY = camera.Position.Y;
...
d.Save();
}
This doesn't work, I guess because those settings are not updated according to the transformations applied to the camera (I always get the initial values set in xaml).
I checked the the Transformation3D class but couldn't find any way to set its value...
The problem is what values do I need to get from the PerspectiveCamera in order to be able to restore it the way it was when I closed my application the last time. The camera is set to a default position (in Xaml), then a transformation is applied to this camera by the TrackBallDecorator. How can I save this transformation (what values to store)? And how can I re-apply them at a later time?
This is going to be a bit long, so bear with me...
1st, you need to modify the 3DTools library so you can apply a transformation to the TrackballDecorator as follow:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using System.Windows.Input;
namespace _3DTools
{
public class TrackballDecorator : Viewport3DDecorator
{
#region Private Members
private Point m_PreviousPosition2D;
private Vector3D m_PreviousPosition3D = new Vector3D(0, 0, 1);
private Transform3DGroup m_Transform;
private ScaleTransform3D m_Scale = new ScaleTransform3D();
private AxisAngleRotation3D m_Rotation = new AxisAngleRotation3D();
private TranslateTransform3D m_Translate = new TranslateTransform3D();
private readonly Border m_EventSource;
#endregion
#region Constructor
public TrackballDecorator()
{
TranslateScale = 10;
ZoomScale = 1;
RotateScale = 1;
// the transform that will be applied to the viewport 3d's camera
m_Transform = new Transform3DGroup();
m_Transform.Children.Add(m_Scale);
m_Transform.Children.Add(new RotateTransform3D(m_Rotation));
m_Transform.Children.Add(m_Translate);
// used so that we always get events while activity occurs within
// the viewport3D
m_EventSource = new Border { Background = Brushes.Transparent };
PreViewportChildren.Add(m_EventSource);
}
#endregion
#region Properties
/// <summary>
/// A transform to move the camera or scene to the trackball's
/// current orientation and scale.
/// </summary>
public Transform3DGroup Transform
{
get { return m_Transform; }
set
{
m_Transform = value;
m_Scale = m_Transform.GetScaleTransform3D();
m_Translate = m_Transform.GetTranslateTransform3D();
m_Rotation = m_Transform.GetRotateTransform3D().Rotation as AxisAngleRotation3D;
ApplyTransform();
}
}
public double TranslateScale { get; set; }
public double RotateScale { get; set; }
public double ZoomScale { get; set; }
#endregion
#region Event Handling
protected override void OnMouseDown(MouseButtonEventArgs e)
{
base.OnMouseDown(e);
m_PreviousPosition2D = e.GetPosition(this);
m_PreviousPosition3D = ProjectToTrackball(ActualWidth,
ActualHeight,
m_PreviousPosition2D);
if (Mouse.Captured == null)
{
Mouse.Capture(this, CaptureMode.Element);
}
}
protected override void OnMouseUp(MouseButtonEventArgs e)
{
base.OnMouseUp(e);
if (IsMouseCaptured)
{
Mouse.Capture(this, CaptureMode.None);
}
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (IsMouseCaptured)
{
Point currentPosition = e.GetPosition(this);
// avoid any zero axis conditions
if (currentPosition == m_PreviousPosition2D) return;
// Prefer tracking to zooming if both buttons are pressed.
if (e.LeftButton == MouseButtonState.Pressed)
{
Track(currentPosition);
}
else if (e.RightButton == MouseButtonState.Pressed)
{
Zoom(currentPosition);
}
else if (e.MiddleButton == MouseButtonState.Pressed)
{
Translate(currentPosition);
}
m_PreviousPosition2D = currentPosition;
ApplyTransform();
}
}
private void ApplyTransform()
{
Viewport3D viewport3D = Viewport3D;
if (viewport3D != null)
{
if (viewport3D.Camera != null)
{
if (viewport3D.Camera.IsFrozen)
{
viewport3D.Camera = viewport3D.Camera.Clone();
}
if (viewport3D.Camera.Transform != m_Transform)
{
viewport3D.Camera.Transform = m_Transform;
}
}
}
}
#endregion Event Handling
private void Track(Point currentPosition)
{
var currentPosition3D = ProjectToTrackball(ActualWidth, ActualHeight, currentPosition);
var axis = Vector3D.CrossProduct(m_PreviousPosition3D, currentPosition3D);
var angle = Vector3D.AngleBetween(m_PreviousPosition3D, currentPosition3D);
// quaterion will throw if this happens - sometimes we can get 3D positions that
// are very similar, so we avoid the throw by doing this check and just ignoring
// the event
if (axis.Length == 0) return;
var delta = new Quaternion(axis, -angle);
// Get the current orientantion from the RotateTransform3D
var r = m_Rotation;
var q = new Quaternion(m_Rotation.Axis, m_Rotation.Angle);
// Compose the delta with the previous orientation
q *= delta;
// Write the new orientation back to the Rotation3D
m_Rotation.Axis = q.Axis;
m_Rotation.Angle = q.Angle;
m_PreviousPosition3D = currentPosition3D;
}
private static Vector3D ProjectToTrackball(double width, double height, Point point)
{
var x = point.X / (width / 2); // Scale so bounds map to [0,0] - [2,2]
var 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
var z2 = 1 - x * x - y * y; // z^2 = 1 - x^2 - y^2
var z = z2 > 0 ? Math.Sqrt(z2) : 0;
return new Vector3D(x, y, z);
}
private void Zoom(Point currentPosition)
{
var yDelta = currentPosition.Y - m_PreviousPosition2D.Y;
var scale = Math.Exp(yDelta / 100) / ZoomScale; // e^(yDelta/100) is fairly arbitrary.
m_Scale.ScaleX *= scale;
m_Scale.ScaleY *= scale;
m_Scale.ScaleZ *= scale;
}
private void Translate(Point currentPosition)
{
// Calculate the panning vector from screen(the vector component of the Quaternion
// the division of the X and Y components scales the vector to the mouse movement
var qV = new Quaternion(((m_PreviousPosition2D.X - currentPosition.X) / TranslateScale),
((currentPosition.Y - m_PreviousPosition2D.Y) / TranslateScale), 0, 0);
// Get the current orientantion from the RotateTransform3D
var q = new Quaternion(m_Rotation.Axis, m_Rotation.Angle);
var qC = q;
qC.Conjugate();
// Here we rotate our panning vector about the the rotaion axis of any current rotation transform
// and then sum the new translation with any exisiting translation
qV = q * qV * qC;
m_Translate.OffsetX += qV.X;
m_Translate.OffsetY += qV.Y;
m_Translate.OffsetZ += qV.Z;
}
}
}
The GetXXXTransform3D methods are extension methods defined as follow:
public static ScaleTransform3D GetScaleTransform3D(this Transform3DGroup transform3DGroup)
{
ScaleTransform3D scaleTransform3D = null;
if (transform3DGroup != null)
{
foreach (var transform in transform3DGroup.Children)
{
scaleTransform3D = transform as ScaleTransform3D;
if (scaleTransform3D != null) return scaleTransform3D;
}
}
return scaleTransform3D;
}
public static RotateTransform3D GetRotateTransform3D(this Transform3DGroup transform3DGroup)
{
RotateTransform3D rotateTransform3D = null;
if (transform3DGroup != null)
{
foreach (var transform in transform3DGroup.Children)
{
rotateTransform3D = transform as RotateTransform3D;
if (rotateTransform3D != null) return rotateTransform3D;
}
}
return rotateTransform3D;
}
public static TranslateTransform3D GetTranslateTransform3D(this Transform3DGroup transform3DGroup)
{
TranslateTransform3D translateTransform3D = null;
if (transform3DGroup != null)
{
foreach (var transform in transform3DGroup.Children)
{
translateTransform3D = transform as TranslateTransform3D;
if (translateTransform3D != null) return translateTransform3D;
}
}
return translateTransform3D;
}
2nd, you need to declare a Transform to your PerspectiveCamera as follow:
(the example is taken from Sasha Barber's Elements3D project which I used to test this)
<Tools:TrackballDecorator x:Name="tbViewPort">
<Viewport3D x:Name="vpFeeds">
<Viewport3D.Camera>
<PerspectiveCamera x:Name="camera" Position="-2,2,40" LookDirection="2,-2,-40" FieldOfView="90">
<PerspectiveCamera.Transform>
<Transform3DGroup />
</PerspectiveCamera.Transform>
</PerspectiveCamera>
</Viewport3D.Camera>
<ContainerUIElement3D x:Name="container" />
<ModelVisual3D x:Name="model">
<ModelVisual3D.Content>
<DirectionalLight Color="White" Direction="-1,-1,-1" />
</ModelVisual3D.Content>
</ModelVisual3D>
</Viewport3D>
</Tools:TrackballDecorator>
3rd, since we are going to store each part of the whole transformation in a separate value, you need to create the relevant properties in your settings file, i.e. CameraScaleX, CameraScaleY, CameraScaleZ, CameraTranslateX, CameraTranslateY, CameraTranslateZ, CameraRotateAxisX, CameraRotateAxisY, CameraRotateAxisZ and CameraRotateAngle. All are of type double and are stored in User scope.
4th and last step is to actually save and load these settings into the camera using the following code:
private void SaveCameraSettings()
{
var transform3DGroup = camera.Transform as Transform3DGroup;
if (transform3DGroup != null)
{
foreach (var transform in transform3DGroup.Children)
{
var scale = transform as ScaleTransform3D;
if (scale != null) SaveCameraSetting(scale);
var rotate = transform as RotateTransform3D;
if (rotate != null) SaveCameraSetting(rotate);
var translate = transform as TranslateTransform3D;
if (translate != null) SaveCameraSetting(translate);
}
Settings.Default.Save();
}
}
private static void SaveCameraSetting(ScaleTransform3D transform)
{
Properties.Settings.Default.CameraScaleX = transform.ScaleX;
Properties.Settings.Default.CameraScaleY = transform.ScaleY;
Properties.Settings.Default.CameraScaleZ = transform.ScaleZ;
}
private static void SaveCameraSetting(RotateTransform3D transform)
{
var axisAngleRotation3D = transform.Rotation as AxisAngleRotation3D;
if (axisAngleRotation3D != null)
{
Properties.Settings.Default.CameraRotateAxisX = axisAngleRotation3D.Axis.X;
Properties.Settings.Default.CameraRotateAxisY = axisAngleRotation3D.Axis.Y;
Properties.Settings.Default.CameraRotateAxisZ = axisAngleRotation3D.Axis.Z;
Properties.Settings.Default.CameraRotateAngle = axisAngleRotation3D.Angle;
}
}
private static void SaveCameraSetting(TranslateTransform3D transform)
{
Properties.Settings.Default.CameraTranslateX = transform.OffsetX;
Properties.Settings.Default.CameraTranslateY = transform.OffsetY;
Properties.Settings.Default.CameraTranslateZ = transform.OffsetZ;
}
private void LoadCameraPosition()
{
var d = Settings.Default;
var transform3DGroup = new Transform3DGroup();
var scaleTransform3D = new ScaleTransform3D(d.CameraScaleX, d.CameraScaleY, d.CameraScaleZ);
var translateTransform3D = new TranslateTransform3D(d.CameraTranslateX, d.CameraTranslateY, d.CameraTranslateZ);
var axisAngleRotation3D = new AxisAngleRotation3D(new Vector3D(d.CameraRotateAxisX, d.CameraRotateAxisY, d.CameraRotateAxisZ),
d.CameraRotateAngle);
var rotateTransform3D = new RotateTransform3D(axisAngleRotation3D);
transform3DGroup.Children.Add(scaleTransform3D);
transform3DGroup.Children.Add(translateTransform3D);
transform3DGroup.Children.Add(rotateTransform3D);
tbViewPort.Transform = transform3DGroup;
}
Hopefully, I didn't forget anything. If you need more help or don't understand something, please don't hesitate to ask ;-)
You would need both the cameras view matrix data and the projection matrix data. The view matrix will contain the data about the position, rotation, scale and translation of the camera and the projection matrix will contain things like the field of view, near plane, far plane and other data.
Sorry I cant help with exporting/importing that data since I've not used WPF, but there might be rawdata properties exposed if it uses anything to do with as3's built in Matrix classes, this is usualy a as3 Vector. Object the the matrices 16 values exposed as row ordered floating point values.
I believe what you need is Position, LookDirection, UpDirection, FieldOfView, NearPlaneDistance, FarPlaneDistance. All the above properties define the camera.

Resources