Custom DrawingVisual making application sluggish - wpf

I'm wanting to render a bezier curve that will contain many hundreds of points. This curve doesn't need to be hit testable or interactable in any way, so I thought I'd try a Visual as that seems to be the most light weight.
Using the code below though, why is it causing the rest of the application to run slowly? for example, window resizing is very slow.
I'm just looking for the most efficient way to render curves without any of the input handling functionality (even with this example, you can hook up to the MouseOver event and it will only fire when your cursor is actually over the lines, so it looks like I'm still paying for that (setting IsHitTestVisiable doesn't seem to help with the performance))
public class VisualHost : FrameworkElement
{
VisualCollection _children;
public VisualHost()
{
_children = new VisualCollection(this);
_children.Add(CreateDrawingVisualRectangle());
}
DrawingVisual CreateDrawingVisualRectangle()
{
var drawingVisual = new DrawingVisual();
var drawingContext = drawingVisual.RenderOpen();
var geometry = new StreamGeometry();
using (var ctx = geometry.Open())
{
ctx.BeginFigure(new Point(0, 0), false, false);
var r = new Random();
for (int i = 0; i < 500; ++i)
{
var p1 = new Point(r.Next(0, 1000), r.Next(0, 1000));
var p2 = new Point(r.Next(0, 1000), r.Next(0, 1000));
ctx.QuadraticBezierTo(p1, p2, true, false);
}
}
geometry.Freeze();
drawingContext.DrawGeometry(null, new Pen(Brushes.Red, 1), geometry);
drawingContext.Close();
return drawingVisual;
}
protected override int VisualChildrenCount
{
get { return _children.Count; }
}
protected override Visual GetVisualChild(int index)
{
if (index < 0 || index >= _children.Count)
{
throw new ArgumentOutOfRangeException();
}
return _children[index];
}
}
public partial class MainWindow : Window
{
public MainWindow()
{
Content = new VisualHost();
}
}

You could use a BitmapCache to create a bitmap that caches the rendering of the DrawingVisual...so that when your FrameworkElement is invalided (due to the sizing) the cached bitmap is used to provide the "visual bits", instead of the slower route of having to render the drawing instructions inside of the "DrawingVisual" again (i.e. what was described by StreamGeometry in the drawingcontext).
DrawingVisual CreateDrawingVisualRectangle()
{
var drawingVisual = new DrawingVisual();
var drawingContext = drawingVisual.RenderOpen();
var geometry = new StreamGeometry();
using (var ctx = geometry.Open())
{
ctx.BeginFigure(new Point(0, 0), false, false);
var r = new Random();
for (int i = 0; i < 500; ++i)
{
var p1 = new Point(r.Next(0, 1000), r.Next(0, 1000));
var p2 = new Point(r.Next(0, 1000), r.Next(0, 1000));
ctx.QuadraticBezierTo(p1, p2, true, false);
}
}
geometry.Freeze();
drawingContext.DrawGeometry(null, new Pen(Brushes.Red, 1), geometry);
drawingContext.Close();
drawingVisual.CacheMode = new BitmapCache();
return drawingVisual;
}

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.

How to parallelize 3d model creation?

Bit of a special case here, because I'm using a library called helix-toolkit but bear with me.
The thing is I would like to parallelize the creation of model objects in my code using a backgroundworker.
I know that there is a big issue with mutlithreading and working on UI elements but maybe somehow there is a workaround.
Here is the rough structure of my code:
First file in which I create the Backgroundwoker, splitting the workload for creating Geometry3D objects and finally calling SetModelGeometry to bind the geometries to the viewport. The second file shows how the binding is done.
MainWindow.xaml.cs
private void Draw_Building()
{
_worker = new BackgroundWorker { WorkerReportsProgress = true, WorkerSupportsCancellation = true };
_worker.DoWork += Draw_Building_DoWork;
_worker.ProgressChanged += DrawBuilding_ProgressChanged;
_worker.RunWorkerCompleted += DrawBuilding_RunWorkerCompleted;
_worker.RunWorkerAsync(10000);
}
private void Draw_Building_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
// returns a list containing Geometry3D objects ( created by meshBuilder.ToMeshGeometry3D() )
Geometryhandler.Draw_Building(sender, e);
}
private void DrawBuilding_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
StatusProgressBar.Value = e.ProgressPercentage;
var i = (int)e.UserState;
var actualComponent = MyBuildingComponents.First(c => c.Id == i);
LblStatusbarInfo.Text = "Currently meshing element #" + actualComponent.Globalid + " (" +
actualComponent.Objectname + ")";
StatusProgressbarMsg.Text = "Meshing (" + e.ProgressPercentage + " %)";
}
private void DrawBuilding_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
StatusProgressbarMsg.Text = "-";
StatusProgressBar.Value = 0;
LblStatusbarInfo.Text = "Meshing completed.";
Geometry = e.Result as List<MeshIdandGeometry>;
// creates MeshGeometryModel3D objects and binds them to the viewport using the List of Geometries
MainViewModel.SetModelGeometry(Geometry);
}
MainViewModel.cs
public void SetModelGeometry(List<MeshIdandGeometry> geometry)
{
MyModelGeometry = new Element3DCollection();
if (geometry != null)
{
foreach (var mygeometry in geometry)
{
var s = new MeshGeometryModel3D
{
Geometry = mygeometry.Geometry,
Material = mygeometry.Material,
};
this.MyModelGeometry.Add(s);
s.Attach(MyModelViewport.RenderHost);
}
}
this.OnPropertyChanged("MyModelGeometry");
}
My problem at the moment is the following error message:
The calling thread cannot access this object because a different
thread owns it.
which is thrown in the SetModelGeometry function when trying to attach the ModelGeometry to the viewport.
I guess the compiler is complaining about the fact that the geometries were created in different threads, to which he has no access now.
Is there any workaround/solution without destroying the parallel execution of the DrawBuilding function?
EDIT:
EDIT 2: posted the wrong version of the Draw_Building method
The Draw_Building method in the Geometryhandler:
public void Draw_Building(object sender, DoWorkEventArgs e)
{
var geometry = new List<MeshIdandGeometry>();
var standardMaterial = new PhongMaterial()
{
AmbientColor = SharpDX.Color.LightGray,
//DiffuseColor = new Color4(0.35f, 0.35f, 0.35f, 1.0f),
//DiffuseMap = new BitmapImage(new System.Uri(#"Con_Diffuse_2.jpg", System.UriKind.RelativeOrAbsolute)),
//NormalMap = new BitmapImage(new System.Uri(#"Con_Normal_2.jpg", System.UriKind.RelativeOrAbsolute)),
};
var max = _mainWindow.MyBuildingComponents.Count;
var i = 1;
// Loop over building components
foreach (var component in _mainWindow.MyBuildingComponents)
{
//if (i == 5) break;
var component1 = component;
var componentTriangles = _mainWindow.MyTriangles.Where(triangle => triangle.ComponentId == component1.Id);
var meshBuilder = new MeshBuilder(true, true, true);
// Loop over triangles in building element
foreach (var triangle in componentTriangles)
{
var triangle1 = triangle;
var p1 = _mainWindow.MyVertices.Find(
vt => vt.Id == triangle1.PointId1);
var p2 = _mainWindow.MyVertices.Find(
vt => vt.Id == triangle1.PointId2);
var p3 = _mainWindow.MyVertices.Find(
vt => vt.Id == triangle1.PointId3);
if (p1 != null && p2 != null && p3 != null)
{
//meshBuilder.AddTriangle(new Vector3((float)p1.X, (float)p1.Y, (float)p1.Z),
// new Vector3((float)p2.X, (float)p2.Y, (float)p2.Z),
// new Vector3((float)p3.X, (float)p3.Y, (float)p3.Z));
// coordination are switched to match the coordinate system in SharpDX viewport
meshBuilder.AddTriangle(new Vector3(-(float)p1.Y, (float)p1.Z, -(float)p1.X),
new Vector3(-(float)p2.Y, (float)p2.Z, -(float)p2.X),
new Vector3(-(float)p3.Y, (float)p3.Z, -(float)p3.X));
}
}
var mesh = meshBuilder.ToMeshGeometry3D();
var meshandtriangle = new MeshIdandGeometry
{
Id = component1.Id,
Geometry = mesh,
Material = standardMaterial,
};
geometry.Add(meshandtriangle);
i++;
var progressPercentage = Convert.ToInt32(((double)i / max) * 100);
var backgroundWorker = sender as BackgroundWorker;
backgroundWorker?.ReportProgress(progressPercentage, component1.Id);
}
e.Result = geometry;
}
Big thanks to #egse for finding a solution.
Part of the code that causes the problem:
var standardMaterial = new PhongMaterial()
{
AmbientColor = SharpDX.Color.LightGray,
//DiffuseColor = new Color4(0.35f, 0.35f, 0.35f, 1.0f),
//DiffuseMap = new BitmapImage(new System.Uri(#"Con_Diffuse_2.jpg", System.UriKind.RelativeOrAbsolute)),
//NormalMap = new BitmapImage(new System.Uri(#"Con_Normal_2.jpg", System.UriKind.RelativeOrAbsolute)),
};
Basically the problem with the above code is that the material is created as a local variable inside the scope of the backgroundworker. This causes problems with ownership when the UI thread tries to enter the material objects.
The solution to this problem is to make sure that the material is created by the UI thread (e.g in this case, the constructor of the Geometryhandler)
TL;DR: Do not create instances of classes which inherit from DependencyObject in another thread than the UI.

generating animated line

(x,y) =(10,20),(50,30),(20,20),(40,22),(45,20),(50,35),.........
I want to create an animation with .Net.Normally I am using windows form. But if needed, i can use WPF.
It starts with (10,20) point.Actually a line starts from (10,20) then goes to (50,30) point with 10ms delay. then, from (50,30) to (20,20) after 10ms and so on.
This values will be read from a text file. I can simply make two ArrayList x&y, to put values from the text file.I just Have to know how could i generate the animated line from this x,y with 10 ms delay from each node to another?
If my question is difficult to understand, please let me know. I will try to make it easier.
Thanks in Advance.
If I understand you correctly, you want to animate the line as it would be just drawn. Here is a simple example with your values.
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Canvas Name="lineCanvas" MouseLeftButtonDown="lineCanvas_MouseLeftButtonDown" Background="White" />
</Window>
The event handler will start the animation later. First, we'll define the data:
List<Point> Points = new List<Point>();
Storyboard sb;
public MainWindow()
{
InitializeComponent();
Points.Add(new Point(10, 20));
Points.Add(new Point(50, 30));
Points.Add(new Point(20, 20));
Points.Add(new Point(40, 22));
Points.Add(new Point(45, 20));
Points.Add(new Point(50, 35));
InitAnimation();
}
sb is the Storyboard that will contain the animation. Points can easily be filled with the values from a file.
Let's initialize the animation. A new line will be added for each segment. Then the endpoint of the line will be animated.
public void InitAnimation()
{
sb = new Storyboard();
for (int i = 0; i < Points.Count - 1; ++i)
{
//new line for current line segment
var l = new Line();
l.Stroke = Brushes.Black;
l.StrokeThickness = 2;
//data from list
var startPoint = Points[i];
var endPoint = Points[i + 1];
//set startpoint = endpoint will result in the line not being drawn
l.X1 = startPoint.X;
l.Y1 = startPoint.Y;
l.X2 = startPoint.X;
l.Y2 = startPoint.Y;
lineCanvas.Children.Add(l);
//Initialize the animations with duration of 1 second for each segment
var daX = new DoubleAnimation(endPoint.X, new Duration(TimeSpan.FromMilliseconds(1000)));
var daY = new DoubleAnimation(endPoint.Y, new Duration(TimeSpan.FromMilliseconds(1000)));
//Define the begin time. This is sum of durations of earlier animations + 10 ms delay for each
daX.BeginTime = TimeSpan.FromMilliseconds(i * 1010);
daY.BeginTime = TimeSpan.FromMilliseconds(i * 1010);
sb.Children.Add(daX);
sb.Children.Add(daY);
//Set the targets for the animations
Storyboard.SetTarget(daX, l);
Storyboard.SetTarget(daY, l);
Storyboard.SetTargetProperty(daX, new PropertyPath(Line.X2Property));
Storyboard.SetTargetProperty(daY, new PropertyPath(Line.Y2Property));
}
}
The duration of the animations can easily be changed according to the length of the line to make it look nicer.
Last task, show the animation:
private void lineCanvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
sb.Begin(this);
}
Here's a variation on Nico's great answer. I use PointAnimation to animate the EndPoint of a LineGeometry which serves as the Data for a Path.
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace SoGeneratingAnimatedLine
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var canvas = new Canvas();
Content = canvas;
var points =
new List<Point>()
{
new Point(10, 10),
new Point(90, 10),
new Point(90, 90),
new Point(10, 90),
new Point(10, 10)
};
var sb = new Storyboard();
for (int i = 0; i < points.Count - 1; i++)
{
var lineGeometry = new LineGeometry(points[i], points[i]);
var path =
new Path()
{
Stroke = Brushes.Black,
StrokeThickness = 2,
Data = lineGeometry
};
canvas.Children.Add(path);
var animation =
new PointAnimation(points[i], points[i + 1], new Duration(TimeSpan.FromMilliseconds(1000)))
{
BeginTime = TimeSpan.FromMilliseconds(i * 1010)
};
sb.Children.Add(animation);
RegisterName("geometry" + i, lineGeometry);
Storyboard.SetTargetName(animation, "geometry" + i);
Storyboard.SetTargetProperty(animation, new PropertyPath(LineGeometry.EndPointProperty));
}
MouseDown += (s, e) => sb.Begin(this);
}
}
}

Animation in codebehind for loop, using RenderTransform

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

Problem adding Viewport2DVisual3D from Code

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

Resources