My program shows a static scene containing several instances, at different places, of a 3d model, for example a tree. This model can be 'heavy', between 50000 and 100000 triangles, and the performance degrade quickly with the number of instances (performance is unacceptable with more than 10 instances).
My first code, without optimizations, looks like this :
public partial class Vue3D : Window
{
public ModelVisual3D modelVisual = new ModelVisual3D();
public Model3DGroup model3DGroup = new Model3DGroup();
public Vue3D()
{
...
modelVisual.Content = model3DGroup;
view1.Children.Add(modelVisual); // view1 = viewport3D
...
BuildScene();
}
void BuildScene()
{
Model3DGroup model = GetModel("tree");
foreach (Point3D pnt in <point collection>)
{
Model3DGroup modelClone = model.Clone();
Transform3DGroup tg = new Transform3DGroup();
tg.Children.Add(new TranslateTransform3D(pnt.X, pnt.Y, pnt.Z>));
modelClone.Transform = tg;
model3DGroup.Children.Add(modelClone);
}
}
}
For each instance i clone the complete model, and add the clone to the viewport after transformation.
Let's say the tree model contains 2 GeometryModel3D :
{mesh1 (20000 triangles), texture1} and {mesh2 (40000 triangles), texture2}.
If i show 10 trees, i'm adding 20 GeometryModel3D.
Since all these instances share the same 2 textures, i thought i could speed up things by creating only 2 GeometryModel3D : one with all the triangles associated with texture1, one with all the triangles associated with texture2. That's what i tried in this code :
void BuildScene()
{
List<Transform3DGroup>> listTg = new List<Transform3DGroup>();
foreach (Point3D pnt in <point collection>)
{
Transform3DGroup tg = new Transform3DGroup();
tg.Children.Add(new TranslateTransform3D(pnt.X, pnt.Y, pnt.Z>));
listTg.Add(tg);
}
Model3DGroup model = GetModel("tree");
foreach (GeometryModel3D gmodel in model.Children)
{
MeshGeometry3D mesh = gmodel.Geometry as MeshGeometry3D;
GeometryModel3D newgmodel = new GeometryModel3D();
newgmodel.Material = gmodel.Material;
newgmodel.BackMaterial = gmodel.BackMaterial;
MeshGeometry3D newmesh = new MeshGeometry3D();
newgmodel.Geometry = newmesh;
foreach (Transform3DGroup tg in listTg)
{
foreach (int indice in mesh.TriangleIndices)
newmesh.TriangleIndices.Add(indice + newmesh.Positions.Count);
foreach (Point3D p3 in mesh.Positions)
newmesh.Positions.Add(tg.Transform(p3));
if (mesh.TextureCoordinates != null)
{
foreach (System.Windows.Point p in mesh.TextureCoordinates)
newmesh.TextureCoordinates.Add(p);
}
}
newmesh.TriangleIndices.Freeze();
newmesh.TextureCoordinates.Freeze();
newmesh.Positions.Freeze();
newgmodel.Material.Freeze();
newgmodel.BackMaterial.Freeze();
newgmodel.Freeze();
model3DGroup.Children.Add(newgmodel);
}
}
And the performance is not better, in fact it's rather worse ... What am I missing here ? It seems that my assumption, i.e. minimizing the number GeometryModel3D would be beneficial, is false. I have read somewhere that in wpf 3d, the number of triangles has no big impact on performance, but that the number of textures was more critical. Here i have a big number of triangles but only 2 textures, only one ModelVisual3D, and all is frozen... Any suggestion ?
I have had good luck with detaching the mesh applying any transformations and then reattaching the mesh afterward. I got the idea from here: http://msdn.microsoft.com/en-us/library/bb613553.aspx
Related
I'm developing a flight simulator and have come across the problem of animating the individual pieces of the airplane (i.e. propeller, elevator, rudder, etc...). I created a class that stores the parts of the planes, which contains GeometryModel and a ModelVisual3D that is constructed from an STL file. When I apply the rotation to the specific part, nothing happens in the application. Full code can be found at: https://github.com/espina7/CSProblems/blob/main/MainWindow.xaml.cs\
public Part RotatePart()
{
//Console.WriteLine("Rotating Part");
Part temp;
temp = this;
AxisAngleRotation3D axisAngle = new AxisAngleRotation3D(new Vector3D(1, 0, 0), 10);
RotateTransform3D myRotate = new RotateTransform3D(axisAngle);
Vector3DAnimation myVectorAnimation = new Vector3DAnimation(new Vector3D(-1, -1, -1), new Duration(TimeSpan.FromMilliseconds(500)));
myVectorAnimation.RepeatBehavior = RepeatBehavior.Forever;
myRotate.Rotation.BeginAnimation(AxisAngleRotation3D.AxisProperty, myVectorAnimation);
Transform3DGroup partGroup = new Transform3DGroup();
partGroup.Children.Add(this.part.Transform);
partGroup.Children.Add(myRotate);
temp.part.Transform = partGroup;
temp.recalculateNormals();
return temp;
}
I am currently using the OptaPlanner's job schedule algorithm to create a certain planning. I want every execution mode used in the planning to be shown in a different color (instead of all different projects to be shown in different colors). Is it possible to implement this and if so, how? I have been searching through the code for a while now and have no idea how to do this.
This cannot be done easily with the Project Scheduling Swing application that's part of OptaPlanner project. It plots the data using JFreeChart and I couldn't find a simple way to associate metadata (like color) with the data that's being plotted.
You can override YIntervalRenderer behavior to return color of your choice based on data item's row (seriesIndex) and column (item's index in the series) but you have to keep the mapping between execution mode and [row, column] yourself, which is cumbersome.
Here's an example of modified ProjectJobSchedulingPanel that does the above:
public class ProjectJobSchedulingPanel extends SolutionPanel<Schedule> {
private static final Logger logger = LoggerFactory.getLogger(ProjectJobSchedulingPanel.class);
private static final Paint[] PAINT_SEQUENCE = DefaultDrawingSupplier.DEFAULT_PAINT_SEQUENCE;
public static final String LOGO_PATH = "/org/optaplanner/examples/projectjobscheduling/swingui/projectJobSchedulingLogo.png";
public ProjectJobSchedulingPanel() {
setLayout(new BorderLayout());
}
#Override
public void resetPanel(Schedule schedule) {
removeAll();
ChartPanel chartPanel = new ChartPanel(createChart(schedule));
add(chartPanel, BorderLayout.CENTER);
}
private JFreeChart createChart(Schedule schedule) {
YIntervalSeriesCollection seriesCollection = new YIntervalSeriesCollection();
Map<Project, YIntervalSeries> projectSeriesMap = new LinkedHashMap<>(
schedule.getProjectList().size());
ExecutionMode[][] executionModeByRowAndColumn = new ExecutionMode[schedule.getProjectList().size()][schedule.getAllocationList().size()];
YIntervalRenderer renderer = new YIntervalRenderer() {
#Override
public Paint getItemPaint(int row, int column) {
ExecutionMode executionMode = executionModeByRowAndColumn[row][column];
logger.info("getItemPaint: ExecutionMode [{},{}]: {}", row, column, executionMode);
return executionMode == null
? TangoColorFactory.ALUMINIUM_5
: PAINT_SEQUENCE[(int) (executionMode.getId() % PAINT_SEQUENCE.length)];
}
};
Map<Project, Integer> seriesIndexByProject = new HashMap<>();
int maximumEndDate = 0;
int seriesIndex = 0;
for (Project project : schedule.getProjectList()) {
YIntervalSeries projectSeries = new YIntervalSeries(project.getLabel());
seriesCollection.addSeries(projectSeries);
projectSeriesMap.put(project, projectSeries);
renderer.setSeriesShape(seriesIndex, new Rectangle());
renderer.setSeriesStroke(seriesIndex, new BasicStroke(3.0f));
seriesIndexByProject.put(project, seriesIndex);
seriesIndex++;
}
for (Allocation allocation : schedule.getAllocationList()) {
int startDate = allocation.getStartDate();
int endDate = allocation.getEndDate();
YIntervalSeries projectSeries = projectSeriesMap.get(allocation.getProject());
int column = projectSeries.getItemCount();
executionModeByRowAndColumn[seriesIndexByProject.get(allocation.getProject())][column] = allocation.getExecutionMode();
logger.info("ExecutionMode [{},{}] = {}", seriesIndexByProject.get(allocation.getProject()), column, allocation.getExecutionMode());
projectSeries.add(allocation.getId(), (startDate + endDate) / 2.0,
startDate, endDate);
maximumEndDate = Math.max(maximumEndDate, endDate);
}
NumberAxis domainAxis = new NumberAxis("Job");
domainAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
domainAxis.setRange(-0.5, schedule.getAllocationList().size() - 0.5);
domainAxis.setInverted(true);
NumberAxis rangeAxis = new NumberAxis("Day (start to end date)");
rangeAxis.setRange(-0.5, maximumEndDate + 0.5);
XYPlot plot = new XYPlot(seriesCollection, domainAxis, rangeAxis, renderer);
plot.setOrientation(PlotOrientation.HORIZONTAL);
// Uncomment this to use Tango color sequence instead of JFreeChart default sequence.
// This results in color per project mode.
// DefaultDrawingSupplier drawingSupplier = new DefaultDrawingSupplier(
// TangoColorFactory.SEQUENCE_1,
// DefaultDrawingSupplier.DEFAULT_FILL_PAINT_SEQUENCE,
// DefaultDrawingSupplier.DEFAULT_OUTLINE_PAINT_SEQUENCE,
// DefaultDrawingSupplier.DEFAULT_STROKE_SEQUENCE,
// DefaultDrawingSupplier.DEFAULT_OUTLINE_STROKE_SEQUENCE,
// DefaultDrawingSupplier.DEFAULT_SHAPE_SEQUENCE);
// plot.setDrawingSupplier(drawingSupplier);
return new JFreeChart("Project Job Scheduling", JFreeChart.DEFAULT_TITLE_FONT, plot, true);
}
}
Result:
Another approach would be to implement JFreeChart interfaces and make custom Dataset and Renderer so that you could plot Allocations directly. Similar to the Gantt chart implementaion in JFreeChart.
Or write your custom UI from the ground up. Depends op how much effort you're willing to put into it :)
I am visualizing some spatial data using a MeshGeometry3D in WPF. I noticed that passing an ImageBrush to the constructor of DiffuseMaterial makes 3D visualization and manipulation fast and efficient (than say using a VisualBrush). My solution is in the following code block where this.dataMeshModel is a GeometryModel3D that includes the mesh. The problem is that I have to give very high resolutions (1000) to the Bitmap image source to make the grid look nice and distinguishable. Choosing this high dpi seems odd and easily raises memory exceptions. Any suggestion? The data model looks like this for 1000 dpi and this for 4000 dpi. THANKS!
private void assignTexture(Geometry gridGeom, double textureDPI, double gridThickness)
{
// rendering the grid on a DrawingVisual
DrawingVisual dv = new DrawingVisual();
using (var dvc = dv.RenderOpen())
{
dvc.DrawRectangle(Brushes.Tomato, null, gridGeom.Bounds);
dvc.DrawGeometry(null, new Pen(Brushes.Black, gridThickness), gridGeom);
}
if (dv.Drawing.CanFreeze)
{
dv.Drawing.Freeze();
}
//rendering the DrawingVisual to an image
Rect bounds = dv.ContentBounds;
RenderTargetBitmap renderedBitmap = new RenderTargetBitmap(
(int)(bounds.Width * this.textureDPI / 96),
(int)(bounds.Height * this.textureDPI / 96),
this.textureDPI,
this.textureDPI,
PixelFormats.Pbgra32);
renderedBitmap.Render(dv);
// adding the rendered image to create an imagebrush
ImageBrush imBrush = new ImageBrush(renderedBitmap);
//creating the material
DiffuseMaterial dataMeshMaterial = new DiffuseMaterial(imBrush);
this.dataMeshModel.Material = this.dataMeshMaterial;
}
I need to calculate the intersection between two geometries to check if one is fully inside the other or not.
The Geometry "container", based on a System.Windows.Shapes.Polygon, is created as follows:
List<PathSegment> basePolygonSegments = new List<PathSegment> {
new PolyLineSegment(basePolygon.Points, true) };
PathGeometry baseGeometry = new PathGeometry();
baseGeometry.Figures.Add(
new PathFigure(basePolygon.Points[0], basePolygonSegments, true));
The Geometry "contained" can be:
another System.Windows.Shapes.Polygon
a System.Windows.Shapes.Polyline, that can have only one line or three lines (the shape is a |_|, or U)
The Polyline is created as follows:
Polyline bracketDrawingPolyline = new Polyline();
foreach(Point p in listOfPoints)
bracketDrawingPolyline.Points.Add(p);
LineGeometry lineGeometry =
new LineGeometry(
bracketDrawingPolyline.Points[0],
bracketDrawingPolyline.Points[bracketDrawingPolyline.Points.Count - 1]);
PathGeometry bracketGeometry = new PathGeometry();
bracketGeometry = lineGeometry.GetWidenedPathGeometry(
new Pen(Brushes.Black, 1.0));
To understand if the "contained" Geometry is contained in the "container", I do the following:
CombinedGeometry intersectionGeometry =
new CombinedGeometry(GeometryCombineMode.Intersect,
baseGeometry, bracketGeometry);
double intersectionArea =
intersectionGeometry.GetArea(0.0001, ToleranceType.Absolute);
double bracketArea = bracketGeometry.GetArea(0.0001, ToleranceType.Absolute);
if (intersectionArea < bracketArea)
{
//the second Geometry is not fully contained in the "container" Geometry
}
else
//it is fully contained
....
In case of Polygon or Polyline with only one line everything works as expected. But with Polyline U, intersectionArea and bracketArea are always the same.
I've also tried to perform the following checks:
bool result = baseGeometry.FillContains(bracketGeometry);
IntersectionDetail idtl =
baseGeometry.FillContainsWithDetail(bracketGeometry);
but I have the same results.
I've found a solution on my own, but I don't know if it is the best one.
Considered that with a single line everything works well, I just do the check for each line of the polyline, that is:
for (int i = 1; i < bracketDrawingPolyline.Points.Count; i++)
{
LineGeometry lineGeometry =
new LineGeometry(bracketDrawingPolyline.Points[i - 1],
bracketDrawingPolyline.Points[i]);
...//continue with the check of the line as described in the post...
}
I've just started to learn WPF/C# and I picked a project which would actually be useful to me instead of variations of "Hello, World!" programs.
It's a small app that polls a game server for player information and binds it to a DataGrid. The data is grouped by team, which can be one of four values: Blue, Red, Spectator, or None.
I've got the Linq query working fine, and the DataGrid grouping is almost good, except for one small problem: the order of the four teams groups is different every time. Sometimes Red is first, sometimes None, etc.
Is there any way I can force the groups into the order above?
Here is the Linq query (addr is the server ip):
private void get_server(string addr)
{
var loc = "http://ukcs.gameme.com/api/serverinfo/" + addr + "//players";
XDocument doc = XDocument.Load(#loc);
var server = new ObservableCollection<Player>
(from player in doc.Descendants("player")
orderby player.Element("rank").Value
select new Player
{
name = player.Element("name").Value,
team = player.Element("team").Value,
frags = player.Element("kills").Value + ":" + player.Element("deaths").Value,
rank = int.Parse(player.Element("rank").Value)
});
server.OrderBy(p => p.rank);
ListCollectionView collection = new ListCollectionView(server);
collection.GroupDescriptions.Add(new PropertyGroupDescription("team"));
player_list.ItemsSource = collection;
}
A second problem is that neither of the OrderBys seem to have an effect.
Any help would be appreciated!
To answer your last question first :) The OrderBy's have no meaning here, because after you sort the players, you put the list inside a CollectionView with a grouping which promptly sorts it by Team (since the grouping is on Team).
To get them in the proper order, you can sort them by Team before putting them inside the ListCollectionView. However, this will put them in alphabetical order - and not exactly the order you wanted. You need to implement IComparable and put that in your sort method - i've rewritten your method a bit (I am not so good at that Linq-form - so, bear with me :)):
You made it a bit harder on yourself by introducing a few unnecessary things - I've tried to root them out.
private void get_server(string addr)
{
var loc = "http://ukcs.gameme.com/api/serverinfo/" + addr + "//players";
var doc = XDocument.Load(#loc);
var server = doc.Descendants("player")
.Select(player => new Player
{
name =player.Element("name").Value,
team=player.Element("team").Value,
frags=player.Element("kills").Value +":" +player.Element("deaths").Value,
rank=int.Parse(player.Element("rank").Value)
})
.OrderBy(p => p.team,new CustomSort())
.ThenBy(p => p.rank).ToList();
var collection = new ListCollectionView(server);
collection.GroupDescriptions.Add(new PropertyGroupDescription("team"));
player_list.ItemsSource = collection;
}
public class CustomSort : IComparer<string>
{
public int Compare(string x, string y)
{
if (x.Equals(y))
return 0;
if (y.Equals("None") || x.Equals("Blue"))
return 1;
if (x.Equals("None") || y.Equals("Blue"))
return -1;
if (x.Equals("Red")|| y.Equals("Spectator"))
return -1;
return 1; // y == "Red" and x == "Spectator"
}
}
Hope this helps!