i am a real newbe in the world of helixtoolkit and Graphics 3D.
What i make:
I have the homework to code a programm which lets you decorate a christmastree with different types of decoration.
One type of decoration is a candle. The problem. The candle is too small in relation to the tree.
My question:
How can i resize my FileModelVisual3D inside of the view_tree with code behind?
private void view_tree_MouseDown(object sender, MouseButtonEventArgs e)
{
if (typeOfdecoration == 1)
{
Decoration1Visual3D decoration;
decoration = new Decoration1Visual3D();
decoration.Fill = Brushes.Red;
Point3D? pt = view_tree.FindNearestPoint(e.GetPosition(view_tree));
if (pt.HasValue)
{
Point3D p = pt.Value;
decoration.Transform = new TranslateTransform3D(p.X, p.Y, p.Z);
view_tree.Children.Add(decoration);
MessageBox.Show(decoration.ToString());
decoration = null;
}
}
else if (typeOfdecoration == 2)
{
FileModelVisual3D fmv3D = new FileModelVisual3D();
fmv3D.Source = "C:/Users/flori/Documents/Schulisches/WFSST/christmastree_burtscherflorian/christmastree_burtscherflorian/2245176fd65db964db79f88f870f8154/candle.3DS";
Point3D? pt = view_tree.FindNearestPoint(e.GetPosition(view_tree));
if (pt.HasValue)
{
Point3D p = pt.Value;
fmv3D.Transform = new TranslateTransform3D(p.X, p.Y, p.Z);
view_tree.Children.Add(fmv3D);
MessageBox.Show(fmv3D.ToString());
fmv3D = null;
}
}
}
XAML-Code
<helix:HelixViewport3D x:Name="view_tree" Camera="{helix:PerspectiveCamera 5.3,-12.3,900,-6.3,11,-6.6}" CameraChanged="view_tree_CameraChanged" MouseDown="view_tree_MouseDown" Grid.Row="0" Grid.Column="0">
<helix:SunLight/>
<helix:FileModelVisual3D x:Name="model_tree" Source="c:/Users/flori/Documents/Schulisches/WFSST/christmastree_burtscherflorian/christmastree_burtscherflorian/Conifers tree 1 N100616.3DS"/>
</helix:HelixViewport3D>
<StackPanel Grid.Row="0" Grid.Column="1">
<RadioButton Name="rb_candle" Margin="10,10,10,0" IsChecked="True" Checked="rb_candle_Checked">Kerze</RadioButton>
<RadioButton Name="rb_ball" Margin="10,10,10,10" Checked="rb_candle_Checked">Kugel</RadioButton>
</StackPanel>
Hope that anybode can help!
I know for others its easy but i have no experiences in 3D-coding.
Thanks
In addition to your TranslateTransform3D, apply a ScaleTransform3D. Put both of them into a Transform3DGroup and use this as Transform on your FileModelVisual3D:
double factor = 2.0;
var transformGroup = new Transform3DGroup();
transformGroup.Children.Add(new TranslateTransform3D(p.X, p.Y, p.Z));
transformGroup.Children.Add(new ScaleTransform3D(factor, factor, factor));
fmv3D.Transform = transformGroup;
Of course, you may want to adjust the factor.
I am trying to crop my image using ellipse shape.I'll able to do that with rectangle but for ellipse not able to do.
void ClipImage()
{
EllipseGeometry geo = new EllipseGeometry();
r = (Ellipse)(from c in LayoutRoot.Children where c.Opacity == .5 select c).First();
GeneralTransform gt = r.TransformToVisual(LayoutRoot);
Point p = gt.Transform(new Point(0, 0));
geo.Rect = new Rect(p.X, p.Y, r.Width, r.Height);
image1.Clip = geo;
r.Visibility = System.Windows.Visibility.Collapsed;
TranslateTransform t = new TranslateTransform();
t.X = -p.X;
t.Y = -p.Y;
image1.RenderTransform = t;
}
r is ellipse and p is
GeneralTransform gt = ((Ellipse)sender).TransformToVisual(LayoutRoot);
Point p = gt.Transform(new Point(0, 0));
It might be worth using an ImageBrush instead. You don't need to clip your image.
<Ellipse ... >
<Ellipse.Fill>
<ImageBrush ImageSource="..."/>
</Ellipse.Fill>
</Ellipse>
Intro
I noticed an issue while implementing clipping (see this).
It looks like UIElement.Clip still render invisible parts
Rendering relatively small geometry (lines to only fill 1920x1200 area ~ 2000 vertical lines) take a lot of time. When using Clip and moving that geometry offscreen (so that clipping should remove significant part of it) it is still take same time (around 1 sec).
Ok, I found what using Geometry.Combine will do a clip (render time is reduced proportionally to removed after clipping geometry). Perfect!
Problem
Geometry.Combine doesn't work with non-closed geometry properly. It produce closed geometry. And it looks ugly, connecting first and last point:
Question
How can I perform clipping (reducing amount of geometry to be rendered) for non-closed figures?
Edit
Here is geometry before (small peace of shown on picture)
{M0;50L0;50L1;53,1395259764657L2;56,2666616782152L3;59,3690657292862L4;62,4344943582427L5;65,4508497187474L6;68,4062276342339L7;71,2889645782536L8; ...
and after
{F1M54,9999923706055;34,5491371154785L53,9999885559082;37,5655174255371 53,0000114440918;40,6309471130371 52,0000076293945;43,7333335876465 ...
Notice change at beginning, was M 0;50 L ..., become F 1 M 55;34 L ...
F1 means NonZero filling
Rule that determines whether a point is in the fill region of the path by drawing a ray from that point to infinity in any direction and then examining the places where a segment of the shape crosses the ray. Starting with a count of zero, add one each time a segment crosses the ray from left to right and subtract one each time a path segment crosses the ray from right to left. After counting the crossings, if the result is zero then the point is outside the path. Otherwise, it is inside.
And I have absolutely no clue what that means. But maybe it is important?
Edit
I should have been looking at the end of strings. There is z at the end of Path.Data, which means figure is closed.
Strangely enough, trying to remove z (by using Geometry.ToString()/Geometry.Parse() combo) doesn't works. After some investigation I found what Combine produces physically enclosing figures (commands L x;y, where x;y is the leftmost point). And the worst thing is what it's not always the last point, so simply removing last L x;y before parsing doesn't works either. =(
Edit
Sample to demonstrate problem:
Xaml:
<Path x:Name="path" Stroke="Red"/>
Code:
var geometry1 = new RectangleGeometry(new Rect(100, 100, 100, 100));
var geometry2 = new PathGeometry(new[] { new PathFigure(new Point(0,0), new[] {
new LineSegment(new Point(300, 300), true),
new LineSegment(new Point(300, 0), true),
}, false) });
//path.Data = geometry1;
//path.Data = geometry2;
//path.Data = Geometry.Combine(geometry1, geometry2, GeometryCombineMode.Intersect, null);
Pictures of geometry1 and geometry2:
Resulting Combine:
As you can see 2 lines become 3 after clipping, debugging proves it:
{F1M100;100L200;100 200;200 100;100z}
Notice, it's not only z, but also 100;100 point at the end, connecting starting point.
I attempted to implement a clipping solutions for non closed geometry based on this line intersection algorithm
Code
public static PathGeometry ClipGeometry(PathGeometry geom, Rect clipRect)
{
PathGeometry clipped = new PathGeometry();
foreach (var fig in geom.Figures)
{
PathSegmentCollection segments = new PathSegmentCollection();
Point lastPoint = fig.StartPoint;
foreach (LineSegment seg in fig.Segments)
{
List<Point> points;
if (LineIntersectsRect(lastPoint, seg.Point, clipRect, out points))
{
LineSegment newSeg = new LineSegment(points[1], true);
PathFigure newFig = new PathFigure(points[0], new[] { newSeg }, false);
clipped.Figures.Add(newFig);
}
lastPoint = seg.Point;
}
}
return clipped;
}
static bool LineIntersectsRect(Point lineStart, Point lineEnd, Rect rect, out List<Point> points)
{
points = new List<Point>();
if (rect.Contains(lineStart) && rect.Contains(lineEnd))
{
points.Add(lineStart);
points.Add(lineEnd);
return true;
}
Point outPoint;
if (Intersects(lineStart, lineEnd, rect.TopLeft, rect.TopRight, out outPoint))
{
points.Add(outPoint);
}
if (Intersects(lineStart, lineEnd, rect.BottomLeft, rect.BottomRight, out outPoint))
{
points.Add(outPoint);
}
if (Intersects(lineStart, lineEnd, rect.TopLeft, rect.BottomLeft, out outPoint))
{
points.Add(outPoint);
}
if (Intersects(lineStart, lineEnd, rect.TopRight, rect.BottomRight, out outPoint))
{
points.Add(outPoint);
}
if (points.Count == 1)
{
if (rect.Contains(lineStart))
points.Add(lineStart);
else
points.Add(lineEnd);
}
return points.Count > 0;
}
static bool Intersects(Point a1, Point a2, Point b1, Point b2, out Point intersection)
{
intersection = new Point(0, 0);
Vector b = a2 - a1;
Vector d = b2 - b1;
double bDotDPerp = b.X * d.Y - b.Y * d.X;
if (bDotDPerp == 0)
return false;
Vector c = b1 - a1;
double t = (c.X * d.Y - c.Y * d.X) / bDotDPerp;
if (t < 0 || t > 1)
return false;
double u = (c.X * b.Y - c.Y * b.X) / bDotDPerp;
if (u < 0 || u > 1)
return false;
intersection = a1 + t * b;
return true;
}
currently solution works for line based geometry, other types perhaps need to be included if needed.
test xaml
<UniformGrid Columns="2"
Margin="250,250,0,0">
<Grid>
<Path x:Name="pathClip"
Fill="#22ff0000" />
<Path x:Name="path"
Stroke="Black" />
</Grid>
<Path x:Name="path2"
Margin="100,0,0,0"
Stroke="Black" />
</UniformGrid>
test code 1
void test()
{
var geometry = new PathGeometry(new[] { new PathFigure(new Point(0,0), new[] {
new LineSegment(new Point(300, 300), true),
new LineSegment(new Point(300, 0), true),
}, false) });
Rect clipRect= new Rect(10, 10, 180, 180);
path.Data = ClipGeometry(geometry, clipRect);
path2.Data = geometry;
pathClip.Data = new RectangleGeometry(clipRect);
}
result
test code 2
void test()
{
var radius = 1.0;
var figures = new List<LineSegment>();
for (int i = 0; i < 2000; i++, radius += 0.1)
{
var segment = new LineSegment(new Point(radius * Math.Sin(i), radius * Math.Cos(i)), true);
segment.Freeze();
figures.Add(segment);
}
var geometry = new PathGeometry(new[] { new PathFigure(figures[0].Point, figures, false) });
Rect clipRect= new Rect(10, 10, 180, 180);
path.Data = ClipGeometry(geometry, clipRect);
path2.Data = geometry;
pathClip.Data = new RectangleGeometry(clipRect);
}
result
give it a try and see how close it is.
If i am right your mainquestion is:"How do i improve the performance of drawing many shapes?"
To get this working you have to understand Geometry Math.
Geometric objects can only merged/combined if they connect or overlap. And there is a big difference between path-geometry and shape-geometry.
As example if two circle overlap you can combine them in WPF
to get the overlapping region: Intersect
to get the difference: Xor
to get the combined surface: Union
to get the difference of only one shape: Exclude
For path-geometry it's a little different, because a path has no surface a path cannot Intersect | Xor | Union | Exclude another path or shape.
But WPF thinks you just forgot to close the path and is doing that for you, which result in the given result in your question.
so to achieve a performanceboost you have to filter all the geometry first for shapes and paths.
foreach(Shape geometryObj in ControlsOrWhatEver)
{
if(geometryObj is Line || geometryObj is Path || geometryObj is Polypath)
{
pathList.Add(geometryObj);
}
else
{
shapeList.Add(geometryObj);
}
}
for the shapeList you can use Geometry.Combine, but for the pathList you have to do some other work. You have to check if the connect at some point, doesnt matter if beginningPoint, endPoint or somwhere inbetween.
If you have done that you can Merge not Combine the path by yourself like:
public Polyline mergePaths(Shape line1, Shape line2)
{
if(!checkLineType(line1) || !checkLineType(line2))
{
return null;
}
if(hitTest(line1, line2))
{
//here you have to do some math to determine the overlapping points
//on these points you can do something like this:
foreach(Point p in Overlapping Points)
{
//add the first line until p then add line2 and go on to add lin 1 until another p
}
}
else
{
return null;
}
}
NOTE: I'm not looking for a XAML Solution.
I'm having trouble figuring out how to attach a line to two shapes. The best visible representation of what I'm looking for would be two balls attached to both ends of a straight stick. The problem I'm having is on how to display the line which is dependent on both the positions of ball01's and ball02's center position. As of now, both balls display as I want it, but when ball02 moves away from ball01 (ball02 starts off centered on ball01), the line is not visible.
ball01 = new Ellipse() { Height = BIG_SIZE, Width = BIG_SIZE };
ball01.Fill = baseBrush;
ball01.SetValue(RenderOptions.EdgeModeProperty, EdgeMode.Aliased);
setBall01X(e.GetPosition(canvas).X - (BIG_SIZE / 2));
setBall01Y(e.GetPosition(canvas).Y - (BIG_SIZE / 2));
Canvas.SetLeft(ball01, getBall01X());
Canvas.SetTop(ball01, getBall01Y());
canvas.Children.Add(ball01);
ball02 = new Ellipse() { Height = SMALL_SIZE, Width = SMALL_SIZE };
ball02.Fill = childBrush;
ball02.SetValue(RenderOptions.EdgeModeProperty, EdgeMode.Aliased);
setBall02X(e.GetPosition(canvas).X - (SMALL_SIZE / 2));
setBall02Y(e.GetPosition(canvas).Y - (SMALL_SIZE / 2));
Canvas.SetLeft(ball02, getBall02X());
Canvas.SetTop(ball02, getBall02Y());
canvas.Children.Add(ball02);
// line's X's and Y's are to point to the center of both balls
// Regardless of where the balls move.
line01 = new Line()
{
X1 = getBall01X() + (BIG_SIZE / 2),
Y1 = getBall01Y() + (BIG_SIZE / 2),
X2 = getBall02X() + (SMALL_SIZE / 2),
Y2 = getBall02Y() + (SMALL_SIZE / 2)
};
line01.Fill = baseBrush;
line01.SnapsToDevicePixels = true;
line01.SetValue(RenderOptions.EdgeModeProperty, EdgeMode.Aliased);
line01.StrokeThickness = 2;
// Canvas.Set???
canvas.Children.Add(line01);
Instead of using Ellipse and Line controls and positioning them by Canvas.Left and Canvas.Top you may prefer to use three Path controls with appropriate geometries. Especially the EllipseGeometry provides far easier handling of its center point, compared to an Ellipse control.
private EllipseGeometry ball1Geometry = new EllipseGeometry();
private EllipseGeometry ball2Geometry = new EllipseGeometry();
private LineGeometry lineGeometry = new LineGeometry();
public MainWindow()
{
InitializeComponent();
canvas.Children.Add(new Path
{
Stroke = Brushes.Black,
Data = ball1Geometry
});
canvas.Children.Add(new Path
{
Stroke = Brushes.Black,
Data = ball2Geometry
});
canvas.Children.Add(new Path
{
Stroke = Brushes.Black,
Data = lineGeometry
});
}
...
private void UpdateDrawing(
Point ball1Position, double ball1Radius,
Point ball2Position, double ball2Radius)
{
ball1Geometry.RadiusX = ball1Radius;
ball1Geometry.RadiusY = ball1Radius;
ball1Geometry.Center = ball1Position;
ball2Geometry.RadiusX = ball2Radius;
ball2Geometry.RadiusY = ball2Radius;
ball2Geometry.Center = ball2Position;
lineGeometry.StartPoint = ball1Position;
lineGeometry.EndPoint = ball2Position;
}
Then you may also prefer to do it the WPF way and create the Paths in XAML:
<Canvas>
<Path Stroke="Black">
<Path.Data>
<EllipseGeometry x:Name="ball1Geometry"/>
</Path.Data>
</Path>
<Path Stroke="Black">
<Path.Data>
<EllipseGeometry x:Name="ball2Geometry"/>
</Path.Data>
</Path>
<Path Stroke="Black">
<Path.Data>
<LineGeometry x:Name="lineGeometry"/>
</Path.Data>
</Path>
</Canvas>
I think you'd better draw in two steps :
1) add the 3 figures and store them (when building your window).
2) update the coordinates in an animating loop.
It will be faster / handier than clearing/filling the canvas on each frame.
For your line issue : hook it on circle 1's center, and have it go to circle 2's center :
// new line coordinates :
X1 = Y1 = 0
X2 = Balle02X - Balle01X + ( SMALL_SIZE / 2 )
Y2 = Balle02Y - Balle01Y + ( SMALL_SIZE / 2 )
Canvas.SetTop ( line01, Balle01X + (BIG_SIZE / 2) )
Canvas.SetLeft( line01, Balle01Y + (BIG_SIZE / 2) )
I am looking for a way to draw a single point (with a color) on C# canvas.
In android I would do something like
paint.Color = Color.Rgb (10, 10, 10);
canvas.DrawPoint (x, y, paint);
So I thought that I would be able to find it in the Shape class, but it was not there. Am I missing something or there is no way to draw a single point?
In the second case, what is a recommended way of drawing a point? In HTML5 canvas there is a similar problem and people are drawing points using rectangles/circles.
P.S. a question with similar title Add Point to Canvas is not answering it and moving into "how to draw a shape".
I just ran in the same question for UWP, I finally decided to use an Ellipse:
int dotSize = 10;
Ellipse currentDot = new Ellipse();
currentDot.Stroke = new SolidColorBrush(Colors.Green);
currentDot.StrokeThickness = 3;
Canvas.SetZIndex(currentDot, 3);
currentDot.Height = dotSize;
currentDot.Width = dotSize;
currentDot.Fill = new SolidColorBrush(Colors.Green);
currentDot.Margin = new Thickness(100, 200, 0, 0); // Sets the position.
myGrid.Children.Add(currentDot);
What about a Polyline?
xaml:
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<Canvas x:Name="canvas" Background="#00FFFFFF" MouseMove="Canvas_MouseMove">
<Polyline x:Name="polyline" Stroke="DarkGreen" StrokeThickness="3"/>
</Canvas>
</Grid>
c#:
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
polyline.Points.Add(new Point(0,0));
polyline.Points.Add(new Point(0, 1));
polyline.Points.Add(new Point(1, 0));
polyline.Points.Add(new Point(1, 1));
}