WPF 3D POINT DRAWING - wpf

I need to draw a point in 3D using WPF. I am using class ScreenSpaceLines3D to draw a line in 3D, but I'm not able to draw point in 3D (while model zooming). Can anybody help me? Some example code would be appreciated. Thanks

Use PointsVisual3D class from helixtoolkit, with it you can add points on viewport with color, size. Even you can apply transforms, too.

A point can't be seen, because it has no surface area. WPF 3d is not a ray tracing engine, unfortunately.
However, someone built a pretty nice ray-tracing starter library. This project is a good starting point if you want to do ray-tracing with WPF: http://raytracer.codeplex.com/.

It is not so complicated task.
Download the Helix3D Library
Find the sphere mesh code in samples
Attach the scphere's radius to zooming realization.

i can create a PointVisual3d Own Class. The code of PointVisual3D is below i think this one is helpful for you...
public class PointVisual3D:ModelVisual3D
{
private readonly GeometryModel3D _model;
private readonly MeshGeometry3D _mesh;
private Matrix3D _visualToScreen;
private Matrix3D _screenToVisual;
public PointVisual3D()
{
_mesh = new MeshGeometry3D();
_model = new GeometryModel3D();
_model.Geometry = _mesh;
SetColor(this.Color);
this.Content = _model;
this.Points = new Point3DCollection();
CompositionTarget.Rendering += OnRender;
}
private void OnRender(object sender, EventArgs e)
{
if (Points.Count == 0 && _mesh.Positions.Count == 0)
{
return;
}
if (UpdateTransforms() && MainWindow.mousedown==false)
{
RebuildGeometry();
}
}
public static readonly DependencyProperty ColorProperty = DependencyProperty.Register("Color", typeof(Color), typeof(PointVisual3D), new PropertyMetadata(Colors.White, OnColorChanged));
private static void OnColorChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
((PointVisual3D)sender).SetColor((Color)args.NewValue);
}
private void SetColor(Color color)
{
MaterialGroup unlitMaterial = new MaterialGroup();
unlitMaterial.Children.Add(new DiffuseMaterial(new SolidColorBrush(Colors.Black)));
unlitMaterial.Children.Add(new EmissiveMaterial(new SolidColorBrush(color)));
unlitMaterial.Freeze();
_model.Material = unlitMaterial;
_model.BackMaterial = unlitMaterial;
}
public Color Color
{
get { return (Color)GetValue(ColorProperty); }
set { SetValue(ColorProperty, value); }
}
//public static readonly DependencyProperty ThicknessProperty = DependencyProperty.Register("Thickness", typeof(double), typeof(PointVisual3D), new PropertyMetadata(1.0, OnThicknessChanged));
//private static void OnThicknessChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
//{
// ((PointVisual3D)sender).GeometryDirty();
//}
//public double Thickness
//{
// get { return (double)GetValue(ThicknessProperty); }
// set { SetValue(ThicknessProperty, value); }
//}
public static readonly DependencyProperty PointsProperty = DependencyProperty.Register("Points", typeof(Point3DCollection), typeof(PointVisual3D), new PropertyMetadata(null, OnPointsChanged));
private static void OnPointsChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
((PointVisual3D)sender).GeometryDirty();
}
public Point3DCollection Points
{
get { return (Point3DCollection)GetValue(PointsProperty); }
set { SetValue(PointsProperty, value); }
}
private void GeometryDirty()
{
_visualToScreen = MathUtils.ZeroMatrix;
}
private void RebuildGeometry()
{
//double halfThickness = Thickness / 2.0;
//int numLines = Points.Count / 2;
//Point3DCollection positions = new Point3DCollection(numLines * 4);
//for (int i = 0; i < numLines; i++)
//{
// int startIndex = i * 2;
// Point3D startPoint = Points[startIndex];
// Point3D endPoint = Points[startIndex + 1];
// AddSegment(positions, startPoint, endPoint, halfThickness);
//}
//positions.Freeze();
//_mesh.Positions = positions;
Int32Collection indices = new Int32Collection(Points.Count * 6);
for (int i = 0; i < Points.Count; i++)
{
indices.Add(i * 4 + 2);
indices.Add(i * 4 + 1);
indices.Add(i * 4 + 0);
indices.Add(i * 4 + 2);
indices.Add(i * 4 + 3);
indices.Add(i * 4 + 1);
}
indices.Freeze();
_mesh.TriangleIndices = indices;
_mesh.Positions = CreatePositions(this.Points, this.Size, 0.0);
}
public Point3DCollection CreatePositions(IList<Point3D> points, double size = 1.0, double depthOffset = 0.0)
{
double halfSize = size / 2.0;
int numPoints = points.Count;
var outline = new[]
{
new Vector(-halfSize, halfSize), new Vector(-halfSize, -halfSize), new Vector(halfSize, halfSize),
new Vector(halfSize, -halfSize)
};
var positions = new Point3DCollection(numPoints * 4);
for (int i = 0; i < numPoints; i++)
{
var screenPoint = (Point4D)points[i] * this._visualToScreen;
double spx = screenPoint.X;
double spy = screenPoint.Y;
double spz = screenPoint.Z;
double spw = screenPoint.W;
if (!depthOffset.Equals(0))
{
spz -= depthOffset * spw;
}
var p0 = new Point4D(spx, spy, spz, spw) * this._screenToVisual;
double pwinverse = 1 / p0.W;
foreach (var v in outline)
{
var p = new Point4D(spx + v.X * spw, spy + v.Y * spw, spz, spw) * this._screenToVisual;
positions.Add(new Point3D(p.X * pwinverse, p.Y * pwinverse, p.Z * pwinverse));
}
}
positions.Freeze();
return positions;
}
/// <summary>
/// Identifies the <see cref="Size"/> dependency property.
/// </summary>
public static readonly DependencyProperty SizeProperty = DependencyProperty.Register(
"Size", typeof(double), typeof(PointVisual3D), new UIPropertyMetadata(1.0, GeometryChanged));
protected static void GeometryChanged(object sender, DependencyPropertyChangedEventArgs e)
{
((PointVisual3D)sender).GeometryDirty();
}
public double Size
{
get
{
return (double)this.GetValue(SizeProperty);
}
set
{
this.SetValue(SizeProperty, value);
}
}
//private void AddSegment(Point3DCollection positions, Point3D startPoint, Point3D endPoint, double halfThickness)
//{
// Vector3D lineDirection = endPoint * _visualToScreen - startPoint * _visualToScreen;
// lineDirection.Z = 0;
// lineDirection.Normalize();
// Vector delta = new Vector(-lineDirection.Y, lineDirection.X);
// delta *= halfThickness;
// Point3D pOut1, pOut2;
// Widen(startPoint, delta, out pOut1, out pOut2);
// positions.Add(pOut1);
// positions.Add(pOut2);
// Widen(endPoint, delta, out pOut1, out pOut2);
// positions.Add(pOut1);
// positions.Add(pOut2);
//}
//private void Widen(Point3D pIn, Vector delta, out Point3D pOut1, out Point3D pOut2)
//{
// Point4D pIn4 = (Point4D)pIn;
// Point4D pOut41 = pIn4 * _visualToScreen;
// Point4D pOut42 = pOut41;
// pOut41.X += delta.X * pOut41.W;
// pOut41.Y += delta.Y * pOut41.W;
// pOut42.X -= delta.X * pOut42.W;
// pOut42.Y -= delta.Y * pOut42.W;
// pOut41 *= _screenToVisual;
// pOut42 *= _screenToVisual;
// pOut1 = new Point3D(pOut41.X / pOut41.W, pOut41.Y / pOut41.W, pOut41.Z / pOut41.W);
// pOut2 = new Point3D(pOut42.X / pOut42.W, pOut42.Y / pOut42.W, pOut42.Z / pOut42.W);
//}
private bool UpdateTransforms()
{
Viewport3DVisual viewport;
bool success;
Matrix3D visualToScreen = MathUtils.TryTransformTo2DAncestor(this, out viewport, out success);
if (!success || !visualToScreen.HasInverse)
{
_mesh.Positions = null;
return false;
}
if (visualToScreen == _visualToScreen)
{
return false;
}
_visualToScreen = _screenToVisual = visualToScreen;
_screenToVisual.Invert();
return true;
}
}

Related

2D over 3D anti-aliasing artifacts

We have 3D models in WPF and use a cursor made from a Canvas and some internal parts. We turn the HW cursor off and move the Canvas through MouseEvent. The issue is that there are terrible artifacts on the screen as you move from left to right (not nearly as bad right to left). I have played with snaptodevicepixels, Edge mode and nvidea AA settings but the only thing that "fixes" is setting edge mode to aliased for the 3dviewport - and we don't want that. I have even made the cursor completely transparent and it still leaves artifacts.
I broke out some of the code for demonstration. You can especially see the artifacts moving upper-left to lower-right.
Anyone think they can help me out here? I'm head banging and not in a good way. It's looking more like a bug in the AA code.
Thanks
T
FYI: I used some Petzold code here for the beach ball.
Bad AA BeachBall
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using System.Windows.Shapes;
namespace Cursoraa2
{
class Program : Window
{
[STAThread]
public static void Main() => new Application().Run(new Program());
public Program()
{
Width = 500;
Height = 500;
var grid = new Grid();
var viewport = new Viewport3D();
grid.Children.Add(viewport);
Content = grid;
//RenderOptions.SetEdgeMode(viewport,EdgeMode.Aliased);
DynamicCurosr.Start(grid, grid, 40, Color.FromArgb(40, 0x33, 0x33, 0xff), Colors.Blue);
MouseMove += MainWindow_MouseMove;
MakeBeachBallSphere(viewport);
}
private void MainWindow_MouseMove(object sender, MouseEventArgs e) => DynamicCurosr.Move(e.GetPosition(this));
public void MakeBeachBallSphere(Viewport3D viewport)
{
// Get the MeshGeometry3D from the GenerateSphere method.
var mesh = GenerateSphere(new Point3D(0, 0, 0), 1, 36, 18);
mesh.Freeze();
// Define a brush for the sphere.
var brushes = new Brush[6] { Brushes.Red, Brushes.Blue,
Brushes.Yellow, Brushes.Orange,
Brushes.White, Brushes.Green };
var drawgrp = new DrawingGroup();
for (var i = 0; i < brushes.Length; i++)
{
var rectgeo = new RectangleGeometry(new Rect(10 * i, 0, 10, 60));
var geodraw = new GeometryDrawing(brushes[i], null, rectgeo);
drawgrp.Children.Add(geodraw);
}
var drawbrsh = new DrawingBrush(drawgrp);
drawbrsh.Freeze();
// Define the GeometryModel3D.
var geomod = new GeometryModel3D
{
Geometry = mesh,
Material = new DiffuseMaterial(drawbrsh)
};
// Create a ModelVisual3D for the GeometryModel3D.
var modvis = new ModelVisual3D { Content = geomod };
viewport.Children.Add(modvis);
// Create another ModelVisual3D for light.
var modgrp = new Model3DGroup();
modgrp.Children.Add(new AmbientLight(Color.FromRgb(128, 128, 128)));
modgrp.Children.Add(new DirectionalLight(Color.FromRgb(128, 128, 128), new Vector3D(2, -3, -1)));
modvis = new ModelVisual3D {Content = modgrp};
viewport.Children.Add(modvis);
// Create the camera.
var cam = new PerspectiveCamera(new Point3D(0, 0, 8), new Vector3D(0, 0, -1), new Vector3D(0, 1, 0), 45);
viewport.Camera = cam;
// Create a transform for the GeometryModel3D.
var axisangle = new AxisAngleRotation3D(new Vector3D(1, 1, 0), 180);
var rotate = new RotateTransform3D(axisangle);
geomod.Transform = rotate;
// Animate the RotateTransform3D.
//DoubleAnimation anima = new DoubleAnimation(360, new Duration(TimeSpan.FromSeconds(5)));
//anima.RepeatBehavior = RepeatBehavior.Forever;
//axisangle.BeginAnimation(AxisAngleRotation3D.AngleProperty, anima);
}
MeshGeometry3D GenerateSphere(Point3D center, double radius, int slices, int stacks)
{
// Create the MeshGeometry3D.
var mesh = new MeshGeometry3D();
// Fill the Position, Normals, and TextureCoordinates collections.
for (var stack = 0; stack <= stacks; stack++)
{
var phi = Math.PI / 2 - stack * Math.PI / stacks;
var y = radius * Math.Sin(phi);
var scale = -radius * Math.Cos(phi);
for (var slice = 0; slice <= slices; slice++)
{
var theta = slice * 2 * Math.PI / slices;
var x = scale * Math.Sin(theta);
var z = scale * Math.Cos(theta);
var normal = new Vector3D(x, y, z);
mesh.Normals.Add(normal);
mesh.Positions.Add(normal + center);
mesh.TextureCoordinates.Add(
new Point((double)slice / slices,
(double)stack / stacks));
}
}
// Fill the TriangleIndices collection.
for (var stack = 0; stack < stacks; stack++)
for (var slice = 0; slice < slices; slice++)
{
var n = slices + 1; // Keep the line length down.
if (stack != 0)
{
mesh.TriangleIndices.Add((stack + 0) * n + slice);
mesh.TriangleIndices.Add((stack + 1) * n + slice);
mesh.TriangleIndices.Add((stack + 0) * n + slice + 1);
}
if (stack != stacks - 1)
{
mesh.TriangleIndices.Add((stack + 0) * n + slice + 1);
mesh.TriangleIndices.Add((stack + 1) * n + slice);
mesh.TriangleIndices.Add((stack + 1) * n + slice + 1);
}
}
return mesh;
}
}
public static class DynamicCurosr
{
static public bool InSession { get; private set; }
private static Panel theCursor;
private static readonly ScaleTransform ScaleTransform = new ScaleTransform(1, 1);
private static readonly MatrixTransform MatrixTransform = new MatrixTransform(1, 0, 0, 1, 0, 0);
private static Color defaultFill = Color.FromArgb(20, 255, 255, 255);
private static Color fillFromUser;
private static double strokeFromUser = 0;
private static Color strokeColorFromUser = Colors.Black;
private static int centerDotSizeFromUser = 10; // need to get from user
private static double initialDiameter = double.NaN;
private static Panel cursorPanel;
private static Panel mousePanel;
public static bool Start(Panel cursorPanelIn, Panel mousePanelIn, double radius)
{
return Start(cursorPanelIn, mousePanelIn, radius, defaultFill);
}
public static bool Start(Panel cursorPanelIn, Panel mousePanelIn, double radius, Color fill, Color strokeColor = default(Color), double strokeSize = .16)
{
strokeColor = strokeColor == default(Color) ? Colors.Black : strokeColor;
strokeColorFromUser = strokeColor;
fillFromUser = fill;
strokeFromUser = strokeColor == default(Color) ? 0 : strokeSize;
initialDiameter = double.IsNaN(initialDiameter) ? radius * 2 : initialDiameter;
return Start(cursorPanelIn, mousePanelIn);
}
private static bool Start(Panel cursorPanelIn, Panel mousePanelIn)
{
if (InSession) return false;
cursorPanel = cursorPanelIn;
mousePanel = mousePanelIn;
var point = Mouse.GetPosition(cursorPanel);
theCursor = MakeACursor(theCursor, initialDiameter / 2);
InSession = true;
cursorPanel.Cursor = Cursors.None;
theCursor.Visibility = Visibility.Visible;
Move(point);
if (cursorPanel.Children.Contains(theCursor))
return false;
cursorPanel.Children.Add(theCursor);
Mouse.OverrideCursor = Cursors.None;
return true;
}
public static void Stop()
{
if (InSession)
{
Mouse.OverrideCursor = null;
theCursor.Visibility = Visibility.Collapsed;
cursorPanel.Children.Remove(theCursor);
InSession = false;
}
}
public static void Move(Point point)
{
if (InSession && theCursor.Visibility == Visibility.Visible)
{
var m = MatrixTransform.Matrix;
m.OffsetX = point.X - theCursor.Width / 2;
m.OffsetY = point.Y - theCursor.Height / 2;
MatrixTransform.Matrix = m;
theCursor.RenderTransform = MatrixTransform;
}
}
public static Panel MakeACursor(Panel theCursor, double radius, Color fillColorIn = default(Color), Color strokeColorIn = default(Color))
{
var strokeColor = new SolidColorBrush(strokeColorIn == default(Color) ? strokeColorFromUser : strokeColorIn);
if (theCursor == null)
{
theCursor = new Grid()
{
Width = radius * 2,
Height = radius * 2,
Background = null,
VerticalAlignment = VerticalAlignment.Top,
HorizontalAlignment = HorizontalAlignment.Left,
RenderTransform = ScaleTransform,
RenderTransformOrigin = new Point(.5, .5),
};
var cursorElement = new Ellipse
{
Width = radius * 2,
Height = radius * 2,
Fill = new SolidColorBrush(fillColorIn == default(Color) ? fillFromUser : fillColorIn),
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Top,
StrokeThickness = strokeFromUser,
Stroke = strokeColor,
RenderTransformOrigin = new Point(.5, .5)
};
theCursor.Children.Add(cursorElement);
}
MakeCursorOverlay(theCursor, radius, strokeColor);
return theCursor;
}
public static void MakeCursorOverlay(Panel theCursor, double radius, SolidColorBrush strokeColor)
{
var save = theCursor.Children[0];
theCursor.Children.Clear();
theCursor.Children.Add(save);
var circle = new Ellipse
{
Width = centerDotSizeFromUser,
Height = centerDotSizeFromUser,
Fill = null,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
StrokeThickness = strokeFromUser,
Stroke = strokeColor,
RenderTransformOrigin = new Point(.5, .5)
};
theCursor.Children.Add(circle);
}
}
}

WPF - Is it possible to have a curved input box?

I want a textbox where you can input text from keyboard but be shown as an arc. Is it possible ?
I have found this solution in codeproject. Author created TextOnAPath control which can display curved text.
Source code:
[ContentProperty("Text")]
public class TextOnAPath : Control
{
static TextOnAPath()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(TextOnAPath), new FrameworkPropertyMetadata(typeof(TextOnAPath)));
Control.FontSizeProperty.OverrideMetadata(typeof(TextOnAPath),
new FrameworkPropertyMetadata(
new PropertyChangedCallback(OnFontPropertyChanged)));
Control.FontFamilyProperty.OverrideMetadata(typeof(TextOnAPath),
new FrameworkPropertyMetadata(
new PropertyChangedCallback(OnFontPropertyChanged)));
Control.FontStretchProperty.OverrideMetadata(typeof(TextOnAPath),
new FrameworkPropertyMetadata(
new PropertyChangedCallback(OnFontPropertyChanged)));
Control.FontStyleProperty.OverrideMetadata(typeof(TextOnAPath),
new FrameworkPropertyMetadata(
new PropertyChangedCallback(OnFontPropertyChanged)));
Control.FontWeightProperty.OverrideMetadata(typeof(TextOnAPath),
new FrameworkPropertyMetadata(
new PropertyChangedCallback(OnFontPropertyChanged)));
}
static void OnFontPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextOnAPath textOnAPath = d as TextOnAPath;
if (textOnAPath == null)
return;
if (e.NewValue == null || e.NewValue == e.OldValue)
return;
textOnAPath.UpdateText();
textOnAPath.Update();
}
double[] _segmentLengths;
TextBlock[] _textBlocks;
Panel _layoutPanel;
bool _layoutHasValidSize = false;
#region Text DP
public String Text
{
get { return (String)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
// Using a DependencyProperty as the backing store for Text. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(String), typeof(TextOnAPath),
new PropertyMetadata(null, new PropertyChangedCallback(OnStringPropertyChanged),
new CoerceValueCallback(CoerceTextValue)));
static void OnStringPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextOnAPath textOnAPath = d as TextOnAPath;
if (textOnAPath == null)
return;
if (e.NewValue == e.OldValue || e.NewValue == null)
{
if (textOnAPath._layoutPanel != null)
textOnAPath._layoutPanel.Children.Clear();
return;
}
textOnAPath.UpdateText();
textOnAPath.Update();
}
static object CoerceTextValue(DependencyObject d, object baseValue)
{
if ((String)baseValue == "")
return null;
return baseValue;
}
#endregion
#region TextPath DP
public Geometry TextPath
{
get { return (Geometry)GetValue(TextPathProperty); }
set { SetValue(TextPathProperty, value); }
}
public static readonly DependencyProperty TextPathProperty =
DependencyProperty.Register("TextPath", typeof(Geometry), typeof(TextOnAPath),
new FrameworkPropertyMetadata(null,
new PropertyChangedCallback(OnTextPathPropertyChanged)));
static void OnTextPathPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextOnAPath textOnAPath = d as TextOnAPath;
if (textOnAPath == null)
return;
if (e.NewValue == e.OldValue || e.NewValue == null)
return;
textOnAPath.TextPath.Transform = null;
textOnAPath.UpdateSize();
textOnAPath.Update();
}
#endregion
#region DrawPath DP
/// <summary>
/// Set this property to True to display the TextPath geometry in the control
/// </summary>
public bool DrawPath
{
get { return (bool)GetValue(DrawPathProperty); }
set { SetValue(DrawPathProperty, value); }
}
public static readonly DependencyProperty DrawPathProperty =
DependencyProperty.Register("DrawPath", typeof(bool), typeof(TextOnAPath),
new PropertyMetadata(false, new PropertyChangedCallback(OnDrawPathPropertyChanged)));
static void OnDrawPathPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextOnAPath textOnAPath = d as TextOnAPath;
if (textOnAPath == null)
return;
if (e.NewValue == e.OldValue || e.NewValue == null)
return;
textOnAPath.Update();
}
#endregion
#region DrawLinePath DP
/// <summary>
/// Set this property to True to display the line segments under the text (flattened path)
/// </summary>
public bool DrawLinePath
{
get { return (bool)GetValue(DrawLinePathProperty); }
set { SetValue(DrawLinePathProperty, value); }
}
// Using a DependencyProperty as the backing store for DrawFlattendPath. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DrawLinePathProperty =
DependencyProperty.Register("DrawLinePath", typeof(bool), typeof(TextOnAPath),
new PropertyMetadata(false, new PropertyChangedCallback(OnDrawLinePathPropertyChanged)));
static void OnDrawLinePathPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextOnAPath textOnAPath = d as TextOnAPath;
if (textOnAPath == null)
return;
if (e.NewValue == e.OldValue || e.NewValue == null)
return;
textOnAPath.Update();
}
#endregion
#region ScaleTextPath DP
/// <summary>
/// If set to True (default) then the geometry defined by TextPath automatically gets scaled to fit the width/height of the control
/// </summary>
public bool ScaleTextPath
{
get { return (bool)GetValue(ScaleTextPathProperty); }
set { SetValue(ScaleTextPathProperty, value); }
}
public static readonly DependencyProperty ScaleTextPathProperty =
DependencyProperty.Register("ScaleTextPath", typeof(bool), typeof(TextOnAPath),
new PropertyMetadata(false, new PropertyChangedCallback(OnScaleTextPathPropertyChanged)));
static void OnScaleTextPathPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextOnAPath textOnAPath = d as TextOnAPath;
if (textOnAPath == null)
return;
if (e.NewValue == e.OldValue)
return;
bool value = (Boolean)e.NewValue;
if (value == false && textOnAPath.TextPath != null)
textOnAPath.TextPath.Transform = null;
textOnAPath.UpdateSize();
textOnAPath.Update();
}
#endregion
void UpdateText()
{
if (Text == null || FontFamily == null || FontWeight == null || FontStyle == null)
return;
_textBlocks = new TextBlock[Text.Length];
_segmentLengths = new double[Text.Length];
for (int i = 0; i < Text.Length; i++)
{
TextBlock t = new TextBlock();
t.FontSize = this.FontSize;
t.FontFamily = this.FontFamily;
t.FontStretch = this.FontStretch;
t.FontWeight = this.FontWeight;
t.FontStyle = this.FontStyle;
t.Text = new String(Text[i], 1);
t.RenderTransformOrigin = new Point(0.0, 1.0);
t.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
_textBlocks[i] = t;
_segmentLengths[i] = t.DesiredSize.Width;
}
}
void Update()
{
if (Text == null || TextPath == null || _layoutPanel == null || !_layoutHasValidSize)
return;
List<Point> intersectionPoints;
intersectionPoints = GeometryHelper.GetIntersectionPoints(TextPath.GetFlattenedPathGeometry(), _segmentLengths);
_layoutPanel.Children.Clear();
_layoutPanel.Margin = new Thickness(FontSize);
for (int i = 0; i < intersectionPoints.Count - 1; i++)
{
double oppositeLen = Math.Sqrt(Math.Pow(intersectionPoints[i].X + _segmentLengths[i] - intersectionPoints[i + 1].X, 2.0) + Math.Pow(intersectionPoints[i].Y - intersectionPoints[i + 1].Y, 2.0)) / 2.0;
double hypLen = Math.Sqrt(Math.Pow(intersectionPoints[i].X - intersectionPoints[i + 1].X, 2.0) + Math.Pow(intersectionPoints[i].Y - intersectionPoints[i + 1].Y, 2.0));
double ratio = oppositeLen / hypLen;
if (ratio > 1.0)
ratio = 1.0;
else if (ratio < -1.0)
ratio = -1.0;
//double angle = 0.0;
double angle = 2.0 * Math.Asin(ratio) * 180.0 / Math.PI;
// adjust sign on angle
if ((intersectionPoints[i].X + _segmentLengths[i]) > intersectionPoints[i].X)
{
if (intersectionPoints[i + 1].Y < intersectionPoints[i].Y)
angle = -angle;
}
else
{
if (intersectionPoints[i + 1].Y > intersectionPoints[i].Y)
angle = -angle;
}
TextBlock currTextBlock = _textBlocks[i];
RotateTransform rotate = new RotateTransform(angle);
TranslateTransform translate = new TranslateTransform(intersectionPoints[i].X, intersectionPoints[i].Y - currTextBlock.DesiredSize.Height);
TransformGroup transformGrp = new TransformGroup();
transformGrp.Children.Add(rotate);
transformGrp.Children.Add(translate);
currTextBlock.RenderTransform = transformGrp;
_layoutPanel.Children.Add(currTextBlock);
if (DrawLinePath == true)
{
Line line = new Line();
line.X1 = intersectionPoints[i].X;
line.Y1 = intersectionPoints[i].Y;
line.X2 = intersectionPoints[i + 1].X;
line.Y2 = intersectionPoints[i + 1].Y;
line.Stroke = Brushes.Black;
_layoutPanel.Children.Add(line);
}
}
// don't draw path if already drawing line path
if (DrawPath == true && DrawLinePath == false)
{
Path path = new Path();
path.Data = TextPath;
path.Stroke = Brushes.Black;
_layoutPanel.Children.Add(path);
}
}
public TextOnAPath()
{
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
_layoutPanel = GetTemplateChild("LayoutPanel") as Panel;
if (_layoutPanel == null)
throw new Exception("Could not find template part: LayoutPanel");
_layoutPanel.SizeChanged += new SizeChangedEventHandler(_layoutPanel_SizeChanged);
}
Size _newSize;
void _layoutPanel_SizeChanged(object sender, SizeChangedEventArgs e)
{
_newSize = e.NewSize;
UpdateSize();
Update();
}
void UpdateSize()
{
if (_newSize == null || TextPath == null)
return;
_layoutHasValidSize = true;
double xScale = _newSize.Width / TextPath.Bounds.Width;
double yScale = _newSize.Height / TextPath.Bounds.Height;
if (TextPath.Bounds.Width <= 0)
xScale = 1.0;
if (TextPath.Bounds.Height <= 0)
xScale = 1.0;
if (xScale <= 0 || yScale <= 0)
return;
if (TextPath.Transform is TransformGroup)
{
TransformGroup grp = TextPath.Transform as TransformGroup;
if (grp.Children[0] is ScaleTransform && grp.Children[1] is TranslateTransform)
{
if (ScaleTextPath)
{
ScaleTransform scale = grp.Children[0] as ScaleTransform;
scale.ScaleX *= xScale;
scale.ScaleY *= yScale;
}
TranslateTransform translate = grp.Children[1] as TranslateTransform;
translate.X += -TextPath.Bounds.X;
translate.Y += -TextPath.Bounds.Y;
}
}
else
{
ScaleTransform scale;
TranslateTransform translate;
if (ScaleTextPath)
{
scale = new ScaleTransform(xScale, yScale);
translate = new TranslateTransform(-TextPath.Bounds.X * xScale, -TextPath.Bounds.Y * yScale);
}
else
{
scale = new ScaleTransform(1.0, 1.0);
translate = new TranslateTransform(-TextPath.Bounds.X, -TextPath.Bounds.Y );
}
TransformGroup grp = new TransformGroup();
grp.Children.Add(scale);
grp.Children.Add(translate);
TextPath.Transform = grp;
}
}
}
public static class GeometryHelper
{
public static List<Point> GetIntersectionPoints(PathGeometry FlattenedPath, double[] SegmentLengths)
{
List<Point> intersectionPoints = new List<Point>();
List<Point> pointsOnFlattenedPath = GetPointsOnFlattenedPath(FlattenedPath);
if (pointsOnFlattenedPath == null || pointsOnFlattenedPath.Count < 2)
return intersectionPoints;
Point currPoint = pointsOnFlattenedPath[0];
intersectionPoints.Add(currPoint);
// find point on flattened path that is segment length away from current point
int flattedPathIndex = 0;
int segmentIndex = 1;
while (flattedPathIndex < pointsOnFlattenedPath.Count - 1 &&
segmentIndex < SegmentLengths.Length + 1)
{
Point? intersectionPoint = GetIntersectionOfSegmentAndCircle(
pointsOnFlattenedPath[flattedPathIndex],
pointsOnFlattenedPath[flattedPathIndex + 1], currPoint, SegmentLengths[segmentIndex - 1]);
if (intersectionPoint == null)
flattedPathIndex++;
else
{
intersectionPoints.Add((Point)intersectionPoint);
currPoint = (Point)intersectionPoint;
pointsOnFlattenedPath[flattedPathIndex] = currPoint;
segmentIndex++;
}
}
return intersectionPoints;
}
static List<Point> GetPointsOnFlattenedPath(PathGeometry FlattenedPath)
{
List<Point> flattenedPathPoints = new List<Point>();
// for flattened geometry there should be just one PathFigure in the Figures
if (FlattenedPath.Figures.Count != 1)
return null;
PathFigure pathFigure = FlattenedPath.Figures[0];
flattenedPathPoints.Add(pathFigure.StartPoint);
// SegmentsCollection should contain PolyLineSegment and LineSegment
foreach (PathSegment pathSegment in pathFigure.Segments)
{
if (pathSegment is PolyLineSegment)
{
PolyLineSegment seg = pathSegment as PolyLineSegment;
foreach (Point point in seg.Points)
flattenedPathPoints.Add(point);
}
else if (pathSegment is LineSegment)
{
LineSegment seg = pathSegment as LineSegment;
flattenedPathPoints.Add(seg.Point);
}
else
throw new Exception("GetIntersectionPoint - unexpected path segment type: " + pathSegment.ToString());
}
return (flattenedPathPoints);
}
static Point? GetIntersectionOfSegmentAndCircle(Point SegmentPoint1, Point SegmentPoint2,
Point CircleCenter, double CircleRadius)
{
// linear equation for segment: y = mx + b
double slope = (SegmentPoint2.Y - SegmentPoint1.Y) / (SegmentPoint2.X - SegmentPoint1.X);
double intercept = SegmentPoint1.Y - (slope * SegmentPoint1.X);
// special case when segment is vertically oriented
if (double.IsInfinity(slope))
{
double root = Math.Pow(CircleRadius, 2.0) - Math.Pow(SegmentPoint1.X - CircleCenter.X, 2.0);
if (root < 0)
return null;
// soln 1
double SolnX1 = SegmentPoint1.X;
double SolnY1 = CircleCenter.Y - Math.Sqrt(root);
Point Soln1 = new Point(SolnX1, SolnY1);
// have valid result if point is between two segment points
if (IsBetween(SolnX1, SegmentPoint1.X, SegmentPoint2.X) &&
IsBetween(SolnY1, SegmentPoint1.Y, SegmentPoint2.Y))
//if (ValidSoln(Soln1, SegmentPoint1, SegmentPoint2, CircleCenter))
{
// found solution
return (Soln1);
}
// soln 2
double SolnX2 = SegmentPoint1.X;
double SolnY2 = CircleCenter.Y + Math.Sqrt(root);
Point Soln2 = new Point(SolnX2, SolnY2);
// have valid result if point is between two segment points
if (IsBetween(SolnX2, SegmentPoint1.X, SegmentPoint2.X) &&
IsBetween(SolnY2, SegmentPoint1.Y, SegmentPoint2.Y))
//if (ValidSoln(Soln2, SegmentPoint1, SegmentPoint2, CircleCenter))
{
// found solution
return (Soln2);
}
}
else
{
// use soln to quadradratic equation to solve intersection of segment and circle:
// x = (-b +/ sqrt(b^2-4ac))/(2a)
double a = 1 + Math.Pow(slope, 2.0);
double b = (-2 * CircleCenter.X) + (2 * (intercept - CircleCenter.Y) * slope);
double c = Math.Pow(CircleCenter.X, 2.0) + Math.Pow(intercept - CircleCenter.Y, 2.0) - Math.Pow(CircleRadius, 2.0);
// check for no solutions, is sqrt negative?
double root = Math.Pow(b, 2.0) - (4 * a * c);
if (root < 0)
return null;
// we might have two solns...
// soln 1
double SolnX1 = (-b + Math.Sqrt(root)) / (2 * a);
double SolnY1 = slope * SolnX1 + intercept;
Point Soln1 = new Point(SolnX1, SolnY1);
// have valid result if point is between two segment points
if (IsBetween(SolnX1, SegmentPoint1.X, SegmentPoint2.X) &&
IsBetween(SolnY1, SegmentPoint1.Y, SegmentPoint2.Y))
//if (ValidSoln(Soln1, SegmentPoint1, SegmentPoint2, CircleCenter))
{
// found solution
return (Soln1);
}
// soln 2
double SolnX2 = (-b - Math.Sqrt(root)) / (2 * a);
double SolnY2 = slope * SolnX2 + intercept;
Point Soln2 = new Point(SolnX2, SolnY2);
// have valid result if point is between two segment points
if (IsBetween(SolnX2, SegmentPoint1.X, SegmentPoint2.X) &&
IsBetween(SolnY2, SegmentPoint1.Y, SegmentPoint2.Y))
//if (ValidSoln(Soln2, SegmentPoint1, SegmentPoint2, CircleCenter))
{
// found solution
return (Soln2);
}
}
// shouldn't get here...but in case
return null;
}
static bool IsBetween(double X, double X1, double X2)
{
if (X1 >= X2 && X <= X1 && X >= X2)
return true;
if (X1 <= X2 && X >= X1 && X <= X2)
return true;
return false;
}
}
Usage:
<TextOnAPath:TextOnAPath FontSize="30" DrawPath="True"
Text="The quick brown fox jumped over the lazy hen.">
<TextOnAPath:TextOnAPath.TextPath>
<PathGeometry Figures="M0,0 C120,361 230.5,276.5 230.5,276.5
L308.5,237.50001 C308.5,237.50001 419.5,179.5002 367.5,265.49993
315.5,351.49966 238.50028,399.49924 238.50028,399.49924 L61.500017,
420.49911"/>
</TextOnAPath:TextOnAPath.TextPath>
</TextOnAPath:TextOnAPath>
</Grid>

WPF: Create XpsDocument Pagination

I am trying to create a Paginator that will in turn create an XpsDocument that I can preview with the and then print. I have the following code that I found on the web but do not understand how it is working.
The issue I am having is that if I run this as is, the pages are generated successfully. If I comment out the lines within OnRender() that generate the actual values of data (all the lines after Random...) I get pages that are about one row high and no text on them but they appear to be the correct length. What is it that is keeping the values of "Row Number" & "Column i" from being shown?
I have included 2 screen shots to illustrate.
public class TaxCodePrintPaginator : DocumentPaginator
{
private int _RowsPerPage;
private Size _PageSize;
private int _Rows;
private List<TaxCode> _dataList;
public TaxCodePrintPaginator(List<TaxCode> dt, int rows, Size pageSize)
{
_dataList = dt;
_Rows = rows;
PageSize = pageSize;
}
public override DocumentPage GetPage(int pageNumber)
{
int currentRow = _RowsPerPage * pageNumber;
var page = new PageElement(currentRow, Math.Min(_RowsPerPage, _Rows - currentRow))
{
Width = PageSize.Width,
Height = PageSize.Height
};
page.Arrange(new Rect(new Point(0, 0), PageSize));
return new DocumentPage(page);
}
public override bool IsPageCountValid
{ get { return true; } }
public override int PageCount
{ get { return (int)Math.Ceiling(_Rows / (double)_RowsPerPage); } }
public override Size PageSize
{
get { return _PageSize; }
set
{
_PageSize = value;
_RowsPerPage = 40;
//Can't print anything if you can't fit a row on a page
Debug.Assert(_RowsPerPage > 0);
}
}
public override IDocumentPaginatorSource Source
{ get { return null; } }
}
public class PageElement : UserControl
{
private const int PageMargin = 75;
private const int HeaderHeight = 25;
private const int LineHeight = 20;
private const int ColumnWidth = 140;
private int _CurrentRow;
private int _Rows;
public PageElement(int currentRow, int rows)
{
Margin = new Thickness(PageMargin);
_CurrentRow = currentRow;
_Rows = rows;
}
private static FormattedText MakeText(string text)
{
return new FormattedText(text, CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
new Typeface("Tahoma"), 14, Brushes.Black);
}
public static int RowsPerPage(double height)
{
return (int)Math.Floor((height - (2 * PageMargin)
- HeaderHeight) / LineHeight);
}
protected override void OnRender(DrawingContext dc)
{
Point curPoint = new Point(0, 0);
dc.DrawText(MakeText("Row Number"), curPoint);
curPoint.X += ColumnWidth;
for (int i = 1; i < 4; i++)
{
dc.DrawText(MakeText("Column " + i), curPoint);
curPoint.X += ColumnWidth;
}
curPoint.X = 0;
curPoint.Y += LineHeight;
dc.DrawRectangle(Brushes.Black, null, new Rect(curPoint, new Size(Width, 2)));
curPoint.Y += HeaderHeight - LineHeight;
Random numberGen = new Random();
for (int i = _CurrentRow; i < _CurrentRow + _Rows; i++)
{
dc.DrawText(MakeText(i.ToString()), curPoint);
curPoint.X += ColumnWidth;
for (int j = 1; j < 4; j++)
{
dc.DrawText(MakeText(numberGen.Next().ToString()), curPoint);
curPoint.X += ColumnWidth;
}
curPoint.Y += LineHeight;
curPoint.X = 0;
}
}
}
Before
After

how to deform an ellipse in running time

i first sorry about my english,i´ll try to explain want i want to do
i need to draw an ellipse with wpf that represents an aura and it´s "deformations" representing problematic zones in it,in short an ellipse that can be deformed in running time in specific points
I'm trying to draw several bezier curves forming an ellipse but it´t very difficult (and i don´t know how) to make points that can be dragged forming convex or hollow zones in that ellipse.
¿i made myselft clear in my spanglish? ¿is there an easy way to do that?
Thanks in advance
I don't know what exactly you are trying to do but I recommend making a high resolution version of the ellipse and keeping track of the deformations yourself. Here is a sample program to get you started.
For this demo the XAML is simple:
<Canvas Name="canvas" Focusable="True" KeyDown="canvas_KeyDown" MouseDown="canvas_MouseDown" MouseMove="canvas_MouseMove" MouseUp="canvas_MouseUp"/>
and a the code-behind:
public partial class EllipseDemo : Window
{
const int resolution = 1000;
const double major = 150;
const double minor = 100;
const double xOrigin = 200;
const double yOrigin = 200;
const double radius = 10;
const double scale = 0.1;
const double spread = 10;
const double magnitude = 10;
Path path;
Ellipse controlPoint;
LineSegment[] segments;
double[] deformation;
double[] perturbation;
int controlPointIndex;
public EllipseDemo()
{
InitializeComponent();
segments = new LineSegment[resolution];
deformation = new double[resolution];
perturbation = new double[resolution];
for (int i = 0; i < resolution; i++)
{
var x = i >= resolution / 2 ? i - resolution : i;
perturbation[i] = magnitude * Math.Exp(-Math.Pow(scale * x, 2) / spread);
}
path = new Path();
path.Stroke = new SolidColorBrush(Colors.Black);
path.StrokeThickness = 5;
CalculateEllipse();
canvas.Children.Add(path);
controlPoint = new Ellipse();
controlPoint.Stroke = new SolidColorBrush(Colors.Red);
controlPoint.Fill = new SolidColorBrush(Colors.Transparent);
controlPoint.Width = 2 * radius;
controlPoint.Height = 2 * radius;
MoveControlPoint(0);
canvas.Children.Add(controlPoint);
canvas.Focus();
}
void CalculateEllipse()
{
for (int i = 0; i < resolution; i++)
{
double angle = 2 * Math.PI * i / resolution;
double x = xOrigin + Math.Cos(angle) * (major + deformation[i]);
double y = yOrigin + Math.Sin(angle) * (minor + deformation[i]);
segments[i] = new LineSegment(new Point(x, y), true);
}
var figure = new PathFigure(segments[0].Point, segments, true);
var figures = new PathFigureCollection();
figures.Add(figure);
var geometry = new PathGeometry();
geometry.Figures = figures;
path.Data = geometry;
}
void MoveControlPoint(int index)
{
controlPointIndex = index;
Canvas.SetLeft(controlPoint, segments[index].Point.X - radius);
Canvas.SetTop(controlPoint, segments[index].Point.Y - radius);
}
bool mouseDown;
void canvas_MouseDown(object sender, MouseButtonEventArgs e)
{
if (Mouse.DirectlyOver != controlPoint)
return;
mouseDown = true;
controlPoint.CaptureMouse();
}
void canvas_MouseMove(object sender, MouseEventArgs e)
{
if (!mouseDown)
return;
int index = FindNearestIndex(e.GetPosition(canvas));
MoveControlPoint(index);
}
void canvas_MouseUp(object sender, MouseButtonEventArgs e)
{
if (!mouseDown)
return;
controlPoint.ReleaseMouseCapture();
mouseDown = false;
}
private void canvas_KeyDown(object sender, KeyEventArgs e)
{
int delta = 0;
switch (e.Key)
{
case Key.Up:
delta = 1;
break;
case Key.Down:
delta = -1;
break;
}
if (delta == 0)
return;
int index = controlPointIndex;
for (int i = 0; i < resolution; i++)
deformation[(i + index) % resolution] += delta * perturbation[i];
CalculateEllipse();
MoveControlPoint(index);
}
int FindNearestIndex(Point point)
{
var min = double.PositiveInfinity;
var index = -1;
for (int i = 0; i < segments.Length; i++)
{
var vector = point - segments[i].Point;
var distance = vector.LengthSquared;
if (distance < min)
{
index = i;
min = distance;
}
}
return index;
}
}
This works mostly with a Path represented by line segments and an Ellipse as a control point. The mouse can move the control point around the ellipse and then the arrow keys add or remove a canned perturbation. Everything is hard coded but if you are OK with the math then it should help you get started.
Here's the program in action:

How to avoid recursive dependency properties

I have a class LineG inherited from a shape which will draw a simple line between two points.. I did that simply by adding two dependency properties StartPointProperty and EndPointProperty... Lastly I want to add another functionality which is MidPoint, so when I draw the line there will be a midPoint in the middle of the line.
When I drag the StartPoint or EndPoint the shape will be redrawn, and when I drag the MidPoint the shape will translate depending on the MidPoint change...
private static void PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
LineG lineG = (LineG)d;
if (e.Property.Name == "StartPoint")
{
}
else if (e.Property.Name == "EndPoint")
{
}
else //if MidPoint
{
Point p1 = (Point)e.OldValue;
Point p2 = (Point)e.NewValue;
double offsetX = p2.X - p1.X;
double offsetY = p2.Y - p1.Y;
lineG.StartPoint = new Point(lineG.StartPoint.X + offsetX, lineG.StartPoint.Y + offsetY);
lineG.EndPoint = new Point(lineG.EndPoint.X + offsetX, lineG.EndPoint.Y + offsetY);
lineG.MidPoint = GeneralMethods.MidPoint(lineG.StartPoint, lineG.EndPoint);
}
lineG.InvalidateMeasure();
}
protected override Geometry DefiningGeometry
{
get
{
lg.StartPoint = StartPoint;
lg.EndPoint = EndPoint;
return lg;
}
}
In such cases you can add an int counter to each operation in your class that you increment during processing. You don't do something if the counter is not 0. Example:
private int _suspendCalculation;
private static void OnPropertyChanged(..)
{
if (_suspendCalculation > 0) return;
_suspendCalculation++;
try
{
CalculateAndSetOtherProperty();
}
finally
{
_suspendCalculation--;
}
}

Resources