Expanding standard Polygon class - wpf

I am trying to create a program which can union polygons. I want to create a Polygon class, which can do some operations like Initialize, FindOfOrientation and so on. I am using WPF and with an abstract class - Shape. I want to inherit it and use some properties of this class. But I have problem with Property: "System.Windows.Media.Geometry DefiningGeometry" How should I accomplish this?
class SpecialPolygon: Shape
{
#region Constants
private const int STROKE_THIKNESS = 1; // Толщина линии
private const double OPACITY = 0.5; // Прозрачность
#endregion
int _amountOfVertexes; // Количество сторон полигона
List<Point> _listOfVertexes; // Список всех вершин
List<Stroke> _listOfStrokes; // Список ребер
PointCollection Points { get; set; }
protected override System.Windows.Media.Geometry DefiningGeometry
{
WHAT I SHOULD WRITE IN THIS CASE ???
}
/// <summary>
/// Конструктор с параметрами инициализирует полигон с заданным количеством вершин
/// </summary>
/// <param name="amountOfVertexes">Число вершин</param>
/// <param name="listOfVertexes">Список координат</param>
public SpecialPolygon(int amountOfVertexes, List<Point> listOfVertexes)
{
_amountOfVertexes = amountOfVertexes;
_listOfVertexes = listOfVertexes;
_listOfStrokes = new List<Stroke>();
this.Stroke = System.Windows.Media.Brushes.Black;
this.Fill = System.Windows.Media.Brushes.LightBlue;
this.StrokeThickness = STROKE_THIKNESS;
this.Opacity = OPACITY;
this.Points = new PointCollection();
for (int i = 0; i < _amountOfVertexes; i++)
{
Point a;
a = _listOfVertexes[i];
this.Points.Add(a);
}
// Записываем каждую пару точек в отрезок.(Точка начала отрезка и конца)
for (int i = 0; i < _amountOfVertexes - 1; i++)
{
Stroke stroke = new Stroke(_listOfVertexes[i], _listOfVertexes[i + 1]);
this._listOfStrokes.Add(stroke);
}
// Не забываем записать последний отрезок, соединяющий конец ломанной с её началом
// Таким образом получается полигон
Stroke lastStroke = new Stroke(_listOfVertexes[_amountOfVertexes - 1], _listOfVertexes[0]);
this._listOfStrokes.Add(lastStroke);
}

Rather than going over subject matter in detail that has already been written many times before, I'll just give you a summary and a link to a book that has an excellent section that will answer all of your questions and probably a few that you didn't even have. As #Clemens succinctly said, The DefiningGeometry property would simply return the Geometry that makes up your Shape.
The following example of how to create a Geometry object was taken from the excellent WPF Control Development Unleashed book:
private Geometry GetArcGeometry()
{
Point startPoint = PointAtAngle(Math.Min(StartAngle, EndAngle));
Point endPoint = PointAtAngle(Math.Max(StartAngle, EndAngle));
Size arcSize = new Size(Math.Max(0, (RenderSize.Width –
StrokeThickness)/2),
Math.Max(0, (RenderSize.Height - StrokeThickness)/2));
bool isLargeArc = Math.Abs(EndAngle - StartAngle) 180;
StreamGeometry geom = new StreamGeometry();
using (StreamGeometryContext context = geom.Open())
{
context.BeginFigure(startPoint, false, false);
context.ArcTo(endPoint, arcSize, 0, isLargeArc,
SweepDirection.Counterclockwise, true, false);
}
geom.Transform = new TranslateTransform(StrokeThickness/2,
StrokeThickness/2);
return geom;
}
I'd recommend that you read the Sample: Building a Circular Minute Timer section, starting on page 40, but really, you could do a lot worse than read the whole thing. While I'm suggesting reading material for you, it seems that it might be a good idea for you to read through the various pages of the StackOverflow Help Center... it will help you to get the most out of this website.

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.

OxyPlot: How to set ticks/decade to 1 on a logarithmic axis

I have a log-log plot with both axes in the range 0.1 to 1000. I want only 1 major tick per decade. So far I have found no way to control the tick spacing, except to set IntervalLength as in this code.
var logxAxis = new LogarithmicAxis
{
Position = AxisPosition.Bottom,
Title = "Resistivity of Approaching Bed (ohm-m)",
IntervalLength = 100,
MajorGridlineStyle = LineStyle.Solid,
MinorGridlineStyle = LineStyle.None,
MinorTickSize = 0.0,
Key = "logx"
};
The default IntervalLength is 60, which gave me 2 ticks/decade. Unfortunately, as I increase the window size of my application the number of major ticks increase. So setting IntervalLength is not an ideal solution. I have looked through the OxyPlot source and found nothing. Is there something I am missing, or perhaps I need to derive my own LogarithmicAxis class?
Edit: I decided to derive my own logarithmic axis and replace the function the generated the ticks.
public override void GetTickValues(out IList<double> majorLabelValues, out IList<double> majorTickValues,
out IList<double> minorTickValues)
{
majorLabelValues = new List<double> { 0.1, 1, 10, 100, 1000 };
majorTickValues = new List<double> { 0.1, 1, 10, 100, 1000 };
minorTickValues = new List<double>();
}
This at least lets me get my application out the door.
After some thought, I decided that hard coding the ticks was not the best idea. I decided to create a logarithmic axis where I could lock in the tick placement, but I could also unlock, and let the built in algorithms of OxyPlot work. You will that I reused some OxyPlot methods that are internal, so I just copied the code into my class and made them private. This problem was a lot easier to solve with access to the code. I am stiil open to hearing other solutions.
public class LockableLogarithmicAxis : LogarithmicAxis
{
#region Properties
public bool IsLocked { get; set; }
public double[] MajorTickPositions { get; set; }
public double[] MinorTickPositions { get; set; }
#endregion
#region Constructor
public LockableLogarithmicAxis()
{
IsLocked = true;
}
#endregion
#region Methods
public override void GetTickValues(out IList<double> majorLabelValues, out IList<double> majorTickValues,
out IList<double> minorTickValues)
{
if (!IsLocked)
{
base.GetTickValues(out majorLabelValues, out majorTickValues, out minorTickValues);
return;
}
if (MajorTickPositions != null && MajorTickPositions.Length > 0)
{
majorTickValues = MajorTickPositions.ToList();
}
else
{
majorTickValues = this.DecadeTicks();
}
if (MinorTickPositions != null && MinorTickPositions.Length > 0)
{
minorTickValues = MinorTickPositions.ToList();
}
else
{
minorTickValues = this.SubdividedDecadeTicks();
}
majorLabelValues = majorTickValues;
}
/// <summary>
/// Calculates ticks of the decades in the axis range with a specified step size.
/// </summary>
/// <param name="step">The step size.</param>
/// <returns>A new IList containing the decade ticks.</returns>
private IList<double> DecadeTicks(double step = 1)
{
return this.PowList(this.LogDecadeTicks(step));
}
/// <summary>
/// Calculates logarithmic ticks of the decades in the axis range with a specified step size.
/// </summary>
/// <param name="step">The step size.</param>
/// <returns>A new IList containing the logarithmic decade ticks.</returns>
private IList<double> LogDecadeTicks(double step = 1)
{
var ret = new List<double>();
if (step > 0)
{
var last = double.NaN;
for (var exponent = Math.Ceiling(this.LogActualMinimum); exponent <= this.LogActualMaximum; exponent += step)
{
if (exponent <= last)
{
break;
}
last = exponent;
if (exponent >= this.LogActualMinimum)
{
ret.Add(exponent);
}
}
}
return ret;
}
/// <summary>
/// Raises all elements of a List to the power of <c>this.Base</c>.
/// </summary>
/// <param name="logInput">The input values.</param>
/// <param name="clip">If true, discards all values that are not in the axis range.</param>
/// <returns>A new IList containing the resulting values.</returns>
private IList<double> PowList(IList<double> logInput, bool clip = false)
{
return
logInput.Where(item => !clip || !(item < this.LogActualMinimum))
.TakeWhile(item => !clip || !(item > this.LogActualMaximum))
.Select(item => Math.Pow(this.Base, item))
.ToList();
}
/// <summary>
/// Calculates ticks of all decades in the axis range and their subdivisions.
/// </summary>
/// <param name="clip">If true (default), the lowest and highest decade are clipped to the axis range.</param>
/// <returns>A new IList containing the decade ticks.</returns>
private IList<double> SubdividedDecadeTicks(bool clip = true)
{
var ret = new List<double>();
for (var exponent = (int)Math.Floor(this.LogActualMinimum); ; exponent++)
{
if (exponent > this.LogActualMaximum)
{
break;
}
var currentDecade = Math.Pow(this.Base, exponent);
for (var mantissa = 1; mantissa < this.Base; mantissa++)
{
var currentValue = currentDecade * mantissa;
if (clip && currentValue < this.ActualMinimum)
{
continue;
}
if (clip && currentValue > this.ActualMaximum)
{
break;
}
ret.Add(currentDecade * mantissa);
}
}
return ret;
}
#endregion
}

Rectangles are not drawn as expected

Recently I was somewhat unsure about the presentation of my app and therefore I created a small tester app to look at this topic more closely.
For that I derived my own component class which did nothing els than draw the outer bounds of itself and was surprised that it wasn't completely shown - neither in the simulator, nor on my iPhone 5S.
Now to my question: Why is it that a component drawn as aGraphics.drawRect(getX() + 2, getY() + 2, 10, 10) shows as eleven pixels wide and high in the simulator and on my iPhone 5S?
Here is some code which can be used to demonstrate this:
public class FormRectangleComponent extends Form {
private class RectangleComponent extends Component {
private int color;
public RectangleComponent(int aColor) {
color = aColor;
}
#Override
public void paint(Graphics aGraphics) {
aGraphics.setColor(color);
aGraphics.drawRect(getX(), getY(), getWidth(), getHeight());
Map<String,Object> map = new LinkedHashMap<String,Object>() {{
put("Display.displayWidth", Display.getInstance().getDisplayWidth());
put("rectangleComponent.x", getX());
put("rectangleComponent.y", getY());
put("rectangleComponent.width", getWidth());
put("rectangleComponent.height", getHeight());
}};
aGraphics.setColor(0xffffff);
aGraphics.drawRect(getX() + 2, getY() + 2, 10, 10);
report(map);
}
#Override
protected Dimension calcPreferredSize() {
int side = Display.getInstance().getDisplayHeight() / 10;
return new Dimension(side, side);
}
}
public FormRectangleComponent() {
Display.getInstance().areMutableImagesFast();
setTitle("FormRectangleComponent");
setScrollable(false);
Container contentPane = getContentPane();
contentPane.setLayout(new BoxLayout(BoxLayout.Y_AXIS));
contentPane.setScrollableY(true);
contentPane.getAllStyles().setBgPainter((Graphics aGraphics, Rectangle aRectangle) -> {
aGraphics.setColor(0x00c0c0);
aGraphics.fillRect(aRectangle.getX(), aRectangle.getY(), aRectangle.getWidth(), aRectangle.getHeight());
});
contentPane.add(new SpanLabel(
"Below should be some frames showing all sides."));
contentPane.add(new RectangleComponent(0xff0000));
contentPane.add(new RectangleComponent(0x0000ff));
contentPane.add(new RectangleComponent(0x00ff00));
}
private void report(Map<String, Object> map) {
int maximumLength = 0;
for (Entry<String, Object> entry: map.entrySet()) {
maximumLength = Math.max(maximumLength, entry.getKey().length());
}
for (Entry<String, Object> entry: map.entrySet()) {
StringBuilder stringBuilder = new StringBuilder();
for (int tally = 0; tally < (maximumLength - entry.getKey().length()); tally ++) {
stringBuilder.append(' ');
}
stringBuilder
.append(entry.getKey())
.append(": ")
.append(entry.getValue());
Log.p(stringBuilder.toString());
}
}
}
It's mentioned here in brief but this behavior is based on the convention within the Java graphics API's documented here.
Graphics is a low level API and we tried to keep it consistent with the way Java works. All our samples rely on the width/height -1 convention so it's documented by example & consistency if by nothing else.
The logic for this is mathematical, you don't draw on pixels but in a virtual co-ordinate space that resides between the pixels which makes more sense as you start to transform graphics operations.

New Plot Type using OxyPlot

I am attempting to create a new plot type in OxyPlot. I essentally need a StairStepSeries, but with any negative values replaces by their Math.Abs value and when this occurs, the line style to reflect this has happened (by using color and or LineStyle). So, to highlight what I want
To do this, I have created the two classes (I have pasted actual code used below). This is conceptually easy when you know the tools you are working with, which I don't. My problem is directly related to my improper use of rectangle.DrawClippedLineSegments(). I can get a standard StairStepSeries plotting (copying the internal code) but when I attempt to use the rectangle.DrawClippedLineSegments() intuatively I realise I have not got a clue what this method does or how it is supposed to be used, but can't find any documentation. What is rectangle.DrawClippedLineSegments() doing and how should this method be used?
Thanks for your time.
Code:
namespace OxyPlot.Series
{
using System;
using System.Collections.Generic;
using OxyPlot.Series;
/// <summary>
/// Are we reversing positive of negative values?
/// </summary>
public enum ThresholdType { ReflectAbove, ReflectBelow };
/// <summary>
/// Class that renders absolute positive and absolute negative values
/// but changes the line style according to those values that changed sign.
/// The value at which the absolute vaue is taken can be manually set.
/// </summary>
public class AbsoluteStairStepSeries : StairStepSeries
{
/// <summary>
/// The default color used when a value is reversed accross the threshold.
/// </summary>
private OxyColor defaultColorThreshold;
#region Initialization.
/// <summary>
/// Default ctor.
/// </summary>
public AbsoluteStairStepSeries()
{
this.Threshold = 0.0;
this.ThresholdType = OxyPlot.Series.ThresholdType.ReflectAbove;
this.ColorThreshold = this.ActualColor;
this.LineStyleThreshold = OxyPlot.LineStyle.LongDash;
}
#endregion // Initialization.
/// <summary>
/// Sets the default values.
/// </summary>
/// <param name="model">The model.</param>
protected override void SetDefaultValues(PlotModel model)
{
base.SetDefaultValues(model);
if (this.ColorThreshold.IsAutomatic())
this.defaultColorThreshold = model.GetDefaultColor();
if (this.LineStyleThreshold == LineStyle.Automatic)
this.LineStyleThreshold = model.GetDefaultLineStyle();
}
/// <summary>
/// Renders the LineSeries on the specified rendering context.
/// </summary>
/// <param name="rc">The rendering context.</param>
/// <param name="model">The owner plot model.</param>
public override void Render(IRenderContext rc, PlotModel model)
{
if (this.ActualPoints.Count == 0)
return;
// Set defaults.
this.VerifyAxes();
OxyRect clippingRect = this.GetClippingRect();
double[] dashArray = this.ActualDashArray;
double[] verticalLineDashArray = this.VerticalLineStyle.GetDashArray();
LineStyle lineStyle = this.ActualLineStyle;
double verticalStrokeThickness = double.IsNaN(this.VerticalStrokeThickness) ?
this.StrokeThickness : this.VerticalStrokeThickness;
OxyColor actualColor = this.GetSelectableColor(this.ActualColor);
// Perform thresholding on clipping rectangle.
//double threshold = this.YAxis.Transform(this.Threshold);
//switch (ThresholdType)
//{
// // reflect any values below the threshold above the threshold.
// case ThresholdType.ReflectAbove:
// //if (clippingRect.Bottom < threshold)
// clippingRect.Bottom = threshold;
// break;
// case ThresholdType.ReflectBelow:
// break;
// default:
// break;
//}
// Perform the render action.
Action<IList<ScreenPoint>, IList<ScreenPoint>> renderPoints = (lpts, mpts) =>
{
// Clip the line segments with the clipping rectangle.
if (this.StrokeThickness > 0 && lineStyle != LineStyle.None)
{
if (!verticalStrokeThickness.Equals(this.StrokeThickness) ||
this.VerticalLineStyle != lineStyle)
{
// TODO: change to array
List<ScreenPoint> hlptsOk = new List<ScreenPoint>();
List<ScreenPoint> vlptsOk = new List<ScreenPoint>();
List<ScreenPoint> hlptsFlip = new List<ScreenPoint>();
List<ScreenPoint> vlptsFlip = new List<ScreenPoint>();
double threshold = this.YAxis.Transform(this.Threshold);
for (int i = 0; i + 2 < lpts.Count; i += 2)
{
switch (ThresholdType)
{
case ThresholdType.ReflectAbove:
clippingRect.Bottom = threshold;
if (lpts[i].Y < threshold)
hlptsFlip.Add(new ScreenPoint(lpts[i].X, threshold - lpts[i].Y));
else
hlptsOk.Add(lpts[i]);
if (lpts[i + 1].Y < threshold)
{
ScreenPoint tmp = new ScreenPoint(
lpts[i + 1].X, threshold - lpts[i + 1].Y);
hlptsFlip.Add(tmp);
vlptsFlip.Add(tmp);
}
else
{
hlptsOk.Add(lpts[i + 1]);
vlptsOk.Add(lpts[i + 1]);
}
if (lpts[i + 2].Y < threshold)
vlptsFlip.Add(new ScreenPoint(lpts[i + 2].X, threshold - lpts[i + 2].Y));
else
vlptsOk.Add(lpts[i + 2]);
break;
case ThresholdType.ReflectBelow:
break;
default:
break;
}
}
//for (int i = 0; i + 2 < lpts.Count; i += 2)
//{
// hlpts.Add(lpts[i]);
// hlpts.Add(lpts[i + 1]);
// vlpts.Add(lpts[i + 1]);
// vlpts.Add(lpts[i + 2]);
//}
rc.DrawClippedLineSegments(
clippingRect,
hlptsOk,
actualColor,
this.StrokeThickness,
dashArray,
this.LineJoin,
false);
rc.DrawClippedLineSegments(
clippingRect,
hlptsFlip,
OxyColor.FromRgb(255, 0, 0),
this.StrokeThickness,
dashArray,
this.LineJoin,
false);
rc.DrawClippedLineSegments(
clippingRect,
vlptsOk,
actualColor,
verticalStrokeThickness,
verticalLineDashArray,
this.LineJoin,
false);
rc.DrawClippedLineSegments(
clippingRect,
vlptsFlip,
OxyColor.FromRgb(255, 0, 0),
verticalStrokeThickness,
verticalLineDashArray,
this.LineJoin,
false);
}
else
{
rc.DrawClippedLine(
clippingRect,
lpts,
0,
actualColor,
this.StrokeThickness,
dashArray,
this.LineJoin,
false);
}
}
if (this.MarkerType != MarkerType.None)
{
rc.DrawMarkers(
clippingRect,
mpts,
this.MarkerType,
this.MarkerOutline,
new[] { this.MarkerSize },
this.MarkerFill,
this.MarkerStroke,
this.MarkerStrokeThickness);
}
};
// Transform all points to screen coordinates
// Render the line when invalid points occur.
var linePoints = new List<ScreenPoint>();
var markerPoints = new List<ScreenPoint>();
double previousY = double.NaN;
foreach (var point in this.ActualPoints)
{
if (!this.IsValidPoint(point))
{
renderPoints(linePoints, markerPoints);
linePoints.Clear();
markerPoints.Clear();
previousY = double.NaN;
continue;
}
var transformedPoint = this.Transform(point);
if (!double.IsNaN(previousY))
{
// Horizontal line from the previous point to the current x-coordinate
linePoints.Add(new ScreenPoint(transformedPoint.X, previousY));
}
linePoints.Add(transformedPoint);
markerPoints.Add(transformedPoint);
previousY = transformedPoint.Y;
}
renderPoints(linePoints, markerPoints);
if (this.LabelFormatString != null)
{
// Render point labels (not optimized for performance).
this.RenderPointLabels(rc, clippingRect);
}
}
#region Properties.
/// <summary>
/// The value, positive or negative at which any values are reversed
/// accross the threshold.
/// </summary>
public double Threshold { get; set; }
/// <summary>
/// Hold the thresholding type.
/// </summary>
public ThresholdType ThresholdType { get; set; }
/// <summary>
/// Gets or sets the color for the part of the
/// line that is above/below the threshold.
/// </summary>
public OxyColor ColorThreshold { get; set; }
/// <summary>
/// Gets the actual threshold color.
/// </summary>
/// <value>The actual color.</value>
public OxyColor ActualColorThreshold
{
get { return this.ColorThreshold.GetActualColor(this.defaultColorThreshold); }
}
/// <summary>
/// Gets or sets the line style for the part of the
/// line that is above/below the threshold.
/// </summary>
/// <value>The line style.</value>
public LineStyle LineStyleThreshold { get; set; }
/// <summary>
/// Gets the actual line style for the part of the
/// line that is above/below the threshold.
/// </summary>
/// <value>The line style.</value>
public LineStyle ActualLineStyleThreshold
{
get
{
return this.LineStyleThreshold != LineStyle.Automatic ?
this.LineStyleThreshold : LineStyle.Solid;
}
}
#endregion // Properties.
}
}
and the WPF class
namespace OxyPlot.Wpf
{
using System.Windows;
using System.Windows.Media;
using OxyPlot.Series;
/// <summary>
/// The WPF wrapper for OxyPlot.AbsoluteStairStepSeries.
/// </summary>
public class AbsoluteStairStepSeries : StairStepSeries
{
/// <summary>
/// Default ctor.
/// </summary>
public AbsoluteStairStepSeries()
{
this.InternalSeries = new OxyPlot.Series.AbsoluteStairStepSeries();
}
/// <summary>
/// Creates the internal series.
/// </summary>
/// <returns>
/// The internal series.
/// </returns>
public override OxyPlot.Series.Series CreateModel()
{
this.SynchronizeProperties(this.InternalSeries);
return this.InternalSeries;
}
/// <summary>
/// Synchronizes the properties.
/// </summary>
/// <param name="series">The series.</param>
protected override void SynchronizeProperties(OxyPlot.Series.Series series)
{
base.SynchronizeProperties(series);
var s = series as OxyPlot.Series.AbsoluteStairStepSeries;
s.Threshold = this.Threshold;
s.ColorThreshold = this.ColorThreshold.ToOxyColor();
}
/// <summary>
/// Identifies the <see cref="Threshold"/> dependency property.
/// </summary>
public static readonly DependencyProperty ThresholdProperty = DependencyProperty.Register(
"Threshold", typeof(double), typeof(AbsoluteStairStepSeries),
new UIPropertyMetadata(0.0, AppearanceChanged));
/// <summary>
/// Identifies the <see cref="ThresholdType"/> dependency property.
/// </summary>
public static readonly DependencyProperty ThresholdTypeProperty = DependencyProperty.Register(
"ThresholdType", typeof(ThresholdType), typeof(AbsoluteStairStepSeries),
new UIPropertyMetadata(ThresholdType.ReflectAbove, AppearanceChanged));
/// <summary>
/// Identifies the <see cref="ColorThreshold"/> dependency property.
/// </summary>
public static readonly DependencyProperty ColorThresholdProperty = DependencyProperty.Register(
"ColorThreshold", typeof(Color), typeof(AbsoluteStairStepSeries),
new UIPropertyMetadata(Colors.Red, AppearanceChanged));
/// <summary>
/// Identifies the <see cref="LineStyleThreshold"/> dependency property.
/// </summary>
public static readonly DependencyProperty LineStyleThresholdProperty = DependencyProperty.Register(
"LineStyleThreshold", typeof(LineStyle), typeof(AbsoluteStairStepSeries),
new UIPropertyMetadata(LineStyle.LongDash, AppearanceChanged));
/// <summary>
/// Get or set the threshold value.
/// </summary>
public double Threshold
{
get { return (double)GetValue(ThresholdProperty); }
set { SetValue(ThresholdProperty, value); }
}
/// <summary>
/// Get or set the threshold type to be used.
/// </summary>
public ThresholdType ThresholdType
{
get { return (ThresholdType)GetValue(ThresholdTypeProperty); }
set { SetValue(ThresholdTypeProperty, value); }
}
/// <summary>
/// Get or set the threshold color.
/// </summary>
public Color ColorThreshold
{
get { return (Color)GetValue(ColorThresholdProperty); }
set { SetValue(ColorThresholdProperty, value); }
}
/// <summary>
/// Get or set the threshold line style.
/// </summary>
public LineStyle LineStyleThreshold
{
get { return (LineStyle)GetValue(LineStyleThresholdProperty); }
set { SetValue(LineStyleThresholdProperty, value); }
}
}
}
I had a chance to have a look into this, and whilst what I suggest may not be the ideal solution, it should give you some helpful nudges.
Firstly, DrawClippedLineSegments (You can view the source here) and its extension method counterparts (DrawClippedRectangleAsPolygon, DrawClippedEllipse, etc.) are used to draw various plot graphics onto the main plot/rendering area. The clipping rectangle supplied to this method represents the area in which the graph can be plotted, we don't want anything drawn outside of this area, as it wouldn't be within the axis limits, would look odd, and wouldn't be of particular benefit. In your case, you're passing it a list of data points, along with their calculated rendering locations; only data points within the clipping rectangle will be drawn on your plot.
You can see the start of the clipping calculation occuring on line 118 of that source file var clipping = new CohenSutherlandClipping(clippingRectangle); - this is not something I'm particularly familiar with, but a quick wikipedia search shows that it's an algorithm used specifically for working out line clipping, there is it least on other algorithm used elswhere in that source file. I don't believe you need to alter the clipping rectangle, unless the inversion of one of the data points would place it outside the currently drawn region.
As to actually helping arrive at a solution, there are a couple of things I noticed while exploring your code. The first thing I tried was to plot some data points (all positive), and found the entire graph was inverted, essentially because this statement:
if (lpts[i].Y < threshold) is always true for positive values. That's a result of the Y axis coordinate system starting at the top of the window, and increasing toward the bottom of the window. Since the threshold in my case was 0, when translated to a rendering position on the screen, every positive data point's Y position will be smaller than the axis Y value; essentially your logic as to which points are flipped or not, requires inverting. This should get you the behaviour you're after (ensuring the flipped points are calculated correctly.)
Alternative Approach
Rather than get too deep into the clipping rectangle / calculating the transformed data points approach, I went for a slightly lazier route, which could benefit from some tidyup, but may be usefulm depending on your requirements.
I decided to perform the threshold flipping/amendment just before the call to actually render the points is made.
I altered your AbsoluteStairStepSeries class with these changes (To the Render method) in a minimal way, retaining most of your existing structure:
public override void Render(IRenderContext rc, PlotModel model)
{
if (this.ActualPoints.Count == 0)
return;
// Set defaults.
this.VerifyAxes();
OxyRect clippingRect = this.GetClippingRect();
double[] dashArray = this.ActualDashArray;
double[] verticalLineDashArray = this.VerticalLineStyle.GetDashArray();
LineStyle lineStyle = this.ActualLineStyle;
double verticalStrokeThickness = double.IsNaN(this.VerticalStrokeThickness) ?
this.StrokeThickness : this.VerticalStrokeThickness;
OxyColor actualColor = this.GetSelectableColor(this.ActualColor);
// Perform the render action.
Action<IList<Tuple<bool, ScreenPoint>>, IList<Tuple<bool, ScreenPoint>>> renderPoints = (lpts, mpts) =>
{
// Clip the line segments with the clipping rectangle.
if (this.StrokeThickness > 0 && lineStyle != LineStyle.None)
{
if (!verticalStrokeThickness.Equals(this.StrokeThickness) ||
this.VerticalLineStyle != lineStyle)
{
// TODO: change to array
List<ScreenPoint> hlptsOk = new List<ScreenPoint>();
List<ScreenPoint> vlptsOk = new List<ScreenPoint>();
List<ScreenPoint> hlptsFlip = new List<ScreenPoint>();
List<ScreenPoint> vlptsFlip = new List<ScreenPoint>();
double threshold = this.YAxis.Transform(this.Threshold);
for (int i = 0; i + 2 < lpts.Count; i += 2)
{
hlptsOk.Add(lpts[i].Item2);
hlptsOk.Add(lpts[i + 1].Item2);
vlptsOk.Add(lpts[i + 1].Item2);
vlptsOk.Add(lpts[i + 2].Item2);
// Add flipped points so they may be overdrawn.
if (lpts[i].Item1 == true)
{
hlptsFlip.Add(lpts[i].Item2);
hlptsFlip.Add(lpts[i + 1].Item2);
}
}
rc.DrawClippedLineSegments(
clippingRect,
hlptsOk,
actualColor,
this.StrokeThickness,
dashArray,
this.LineJoin,
false);
rc.DrawClippedLineSegments(
clippingRect,
hlptsFlip,
OxyColor.FromRgb(255, 0, 0),
this.StrokeThickness,
dashArray,
this.LineJoin,
false);
rc.DrawClippedLineSegments(
clippingRect,
vlptsOk,
actualColor,
verticalStrokeThickness,
verticalLineDashArray,
this.LineJoin,
false);
rc.DrawClippedLineSegments(
clippingRect,
vlptsFlip,
OxyColor.FromRgb(255, 0, 0),
verticalStrokeThickness,
verticalLineDashArray,
this.LineJoin,
false);
}
else
{
rc.DrawClippedLine(
clippingRect,
lpts.Select(x => x.Item2).ToList(),
0,
actualColor,
this.StrokeThickness,
dashArray,
this.LineJoin,
false);
}
}
if (this.MarkerType != MarkerType.None)
{
rc.DrawMarkers(
clippingRect,
mpts.Select(x => x.Item2).ToList(),
this.MarkerType,
this.MarkerOutline,
new[] { this.MarkerSize },
this.MarkerFill,
this.MarkerStroke,
this.MarkerStrokeThickness);
}
};
// Transform all points to screen coordinates
// Render the line when invalid points occur.
var linePoints = new List<Tuple<bool, ScreenPoint>>();
var markerPoints = new List<Tuple<bool, ScreenPoint>>();
double previousY = double.NaN;
foreach (var point in this.ActualPoints)
{
var localPoint = point;
bool pointAltered = false;
// Amend/Reflect your points data here:
if (localPoint.Y < Threshold)
{
localPoint.Y = Math.Abs(point.Y);
pointAltered = true;
}
if (!this.IsValidPoint(localPoint))
{
renderPoints(linePoints, markerPoints);
linePoints.Clear();
markerPoints.Clear();
previousY = double.NaN;
continue;
}
var transformedPoint = this.Transform(localPoint);
if (!double.IsNaN(previousY))
{
// Horizontal line from the previous point to the current x-coordinate
linePoints.Add(new Tuple<bool, ScreenPoint>(pointAltered, new ScreenPoint(transformedPoint.X, previousY)));
}
linePoints.Add(new Tuple<bool, ScreenPoint>(pointAltered, transformedPoint));
markerPoints.Add(new Tuple<bool, ScreenPoint>(pointAltered, transformedPoint));
previousY = transformedPoint.Y;
}
renderPoints(linePoints, markerPoints);
if (this.LabelFormatString != null)
{
// Render point labels (not optimized for performance).
this.RenderPointLabels(rc, clippingRect);
}
}
I'm using a List<Tuple<bool, ScreenPoint>>, rather than List<ScreenPoint> to store a bool flag against each point, representing whether or not that point has been altered; you could use a small class to simplify the syntax.
Because you're interacting with the point data directly, you don't need to worry about screen position (reversed Y axis), so conceptually the calculation to take the absolute value is easier to read:
// Amend/Reflect your points data here:
if (localPoint.Y < Threshold)
{
localPoint.Y = Math.Abs(point.Y);
pointAltered = true;
}
I notice that your code has reflect above/reflect below, which is probably logic you would insert here if required, I've gone for Math.Abs which you mentioned was your initial requirement.
When it comes to actually rendering the line, I've left the original code which draws the StepSeries in place, so actually the entire series is drawn in green. I've only added a conditional statement to check for modified/reflected points, if any are found, the relevant plot points are added to your existing lists containing flipped points, which are then drawn in red.
The Tuples make things a little messy in the render method (the addition of Item1/Item2), and you could remove the double drawing of the modified points, but I think the results are what you're after (or can certainly point you in the right direction.
Example Behaviour:

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