I've got a WPF component showing a collection of Equipments. Each of these Equipments contains one or more Coordinate. On MouseMove over my component a function is called checking if the mouse currently hovers within the bounds of any Coordinate of any Equipment. If so the Equipment in question is returned indication that a pop-up window with text information about the Equipment should be shown.
Aside from this, all Coordinates contain an ImageDrawing that shows a symbol for each Equipment (the symbol the user hovers the mouse over to see the text pop-up). These are put in a separate DrawingGroup to speed up rendering. Another benefit of this is that if some Equipments are to be hidden, we can just remove their Coordinates' ImageDrawing from the DrawingGroup, and they will still be left in the list of Equipments (as they should).
However, this does not hinder the pop-up from showing when the mouse hovers over the Coordinate, as this is separate from the DrawingGroup. So, to check if a text pop-up is to be shown, I have to check both if the mouse is within the bounds of any Coordinate, and also check if the ImageDrawing for that Coordinate is in the DrawingGroup.
On to my question (tl;dr): which way to iterate through all these items would be fastest? I've got :
List<Equipment> equipments;
And for each of these (this list 9 times out of 10 holds one item, and never more than five)
List<Coordinate> coordinates;
For each of these Coordinates I've got to check if their ImageDrawing is in the DrawingCollection (which in this case is DrawingGroup.Children) which, according to msdn, is an ordered collection of Drawing objects.
To do this, I started out with this:
foreach (Equipment equipment in equipments)
{
foreach (Coordinate coordinate in equipment.Coordinates)
{
ImageDrawing image = coordinate.ImageDrawing;
if (image != null)
{
if (currentDrawingGroup.Children.Contains(image))
{
if (image.Rect.Bottom > y &&
image.Rect.Top < y &&
image.Rect.Left < x &&
image.Rect.Right > x)
{
return equipment;
}
}
}
}
}
But thought that this becomes to many iterations in the cases where a lot of equipments are hidden, and that I could change it to this (since Children.Contains(image) brobably iterates behind the scenes anyway):
foreach (var child in currentDrawingGroup.Children)
{
foreach (Equipment equipment in equipments)
{
foreach (Coordinate coordinate in equipment.Coordinates)
{
ImageDrawing image = coordinate.ImageDrawing;
if (image != null)
{
if (image == child)
{
if (image.Rect.Bottom > y &&
image.Rect.Top < y &&
image.Rect.Left < x &&
image.Rect.Right > x)
{
return equipment;
}
}
}
}
}
}
I know this question is way too long and the optimizations I'm looking at here probably don't matter much in the long run. But is there a way this could be done without so many loops in loops? I feel like there should be a LINQ-expression helping me somewhere, though I don't know whether any of those are faster than a foreach loop. The way things are sorted (Equipments being hid by removing them from the DrawingGroup and such) is beyond my control and hard to change. Thanks a lot in advance.
if some equipments are to be hidden, wouldn't it be cleaner to let the equipment himself manage its visibility? This way you just ask the equipment if it is visible in the first place and skip it if not?
Thus your equipments are also always present, but visible or not and when you mouseover it you won't have to check if the coordinates are in the DrawingGroup or not... it's already handled for you.
Regarding iterating vs Contains(), the contains function is a linear search so it will be the same as iterating through the list.
Related
I want to create a 2D map of tiles. Example:
Cell[,] cells;
for(int x = 0; x < columns; x++)
{
for(int y = 0; y < rows; y++)
{
cells[x, y] = new Cell();
}
}
The first cell would be at (0|0). What if I want to have this cell as my center and create new cells on the left and top side? These cells would have negative indices.
One way to fix this would be a value that determines the maximum length of one direction. Having a map of 100 tiles per side would place the center of the map at (50|50).
Let's say there would be no hardware limitations and no maximum length per side, what is the best way to create a 2D map with a (0|0) center? I can't image a better way than accessing a cell by its x and y coordinate in a 2D array.
Well, Arrays are logical constructs, not physical ones.
This means that the way we look at the the 0,0 as the top left corner, while might help visualize the content of a 2-D array (and in fact, a 2-D array is also somewhat of a visualization aid), is not accurate at all - the 0,0 "cell" is not a corner, and indexes are not coordinates, though it really helps to understand them when you think about them like they are.
That being said, there is nothing stopping you from creating your own class, that implement an indexer that can take both positive and negative values - in fact, according to Indexers (C# Programming Guide) -
Indexers do not have to be indexed by an integer value; it is up to you how to define the specific look-up mechanism.
Since you are not even obligated to use integers, you most certainly can use both positive and negative values as your indexer.
I was testing an idea to use a list of lists for storage and dynamically calculate the storage index based on the class indexer, but it's getting too late here and I guess I'm too tired to do it right. It's kinda like the solution on the other answer but I was attempting to do it without making you set the final size in the constructor.
Well, you can't use negative indices in an array or list, they're just not the right structure for a problem like this... You could, however, write your own class that handles something like this.
Simply pass in the size of the grid into the constructor, and then use the index operator to return a value based off of an an adjusted index... Something like this... Wrote it up really fast, so it probably isn't ideal in terms of optimization.
public class Grid<T> {
T[,] grid { get; }
int adjustment { get; }
int FindIndex(int provided) {
return provided + adjustment;
}
public Grid(int dimension) {
if (dimension <= 0)
throw new ArgumentException("Grid dimension cannot be <= 0");
if (dimension % 2 != 0)
throw new ArgumentException("Grid must be evenly divisible");
adjustment = dimension / 2;
grid = new T[dimension, dimension];
}
public T this[int key, int key2] {
get {
return grid[FindIndex(key), FindIndex(key2)];
}
set {
grid[FindIndex(key), FindIndex(key2)] = value;
}
}
}
I used these to test it:
var grid = new Grid<int>(100);
grid[-50, -50] = 5;
grid[0, 1] = 10;
You can just switch it to:
var grid = new Grid<Cell>(100);
This only works for a grid with equal dimensions... If you need separate dimensions, you'll need to adjust the constructor and the FindIndex method.
I think that an infinitely sized grid would be dangerous. If you increase the size to the right, you'd have to reposition the center.. Which means, what you think will be at 0,0 will now be shifted as the grid is no longer properly centered.
Additionally, performance of such a structure would be a nightmare as you cannot rely on an array to be infinite (as it inherently isn't). So you'd either have to continuously copy the array (like how a list works) or use a linked list.. If using a linked list, you would have to do enormous amounts of iteration to get whatever value you want.
I have set of random generated points in 3D Scene, and in runtime I want to change the type of point markers to, for example, triangles, as in the picture:
Is it possible? How can I achieve this? Also I need change color for some points.
Scene initialization code below:
ILArray<float> points = ILMath.tosingle(ILMath.randn(3, 1000));
var scene = new ILScene
{
new ILPlotCube(twoDMode: false)
{
new ILPoints
{
Positions = points,
Color = null,
Size = 2
}
}
};
Markers (ILMarker) and 'points' (ILPoints) are very different beasts. Markers are much more flexible configurable, mostly look nicer and are much more expensive to render. They commonly consist out of a border (line shape) and a filled area (triangles shape) and come with a number of predefined looks.
ILPoints on the other hand are designed to be fast and easy. one can easily create millions of points without decreasing the plotting performance. Don't try this with markers! But such points are what they are: filled circles. It's it. No borders, no different shapes.
However, if you want to give it a try - even for the 1000 points in your questions - you can do so. Just use an ILLinePlot instead and configure a marker for it. You may set the line color to Color.Empty to have the markers showing up alone.
new ILLinePlot(points, lineColor: Color.Empty, markerStyle: MarkerStyle.TriangleDown)
In order to get individual colors for individual point markers you would split your markers (points) up into individual set of points. Create an ILLinePlot for each set of points using the scheme described above.
The part of your question dealing with 'dynamic' is also easy: You can change the type of markers as well as any other property at runtime. Here is one simple example which toogles the markers between red triangle markers and white rectangle markers by clicking anywhere on the scene:
ilPanel1.Scene.MouseClick += (_s, _a) => {
if (_a.DirectionUp) return;
var lp = ilPanel1.Scene.First<ILLinePlot>();
if (lp != null) {
if (lp.Marker.Style == MarkerStyle.TriangleDown) {
lp.Marker.Style = MarkerStyle.Rectangle;
lp.Marker.Fill.Color = Color.White;
} else {
lp.Marker.Style = MarkerStyle.TriangleDown;
lp.Marker.Fill.Color = Color.Red;
}
lp.Configure();
ilPanel1.Refresh();
}
};
I'm using C# charts to display/compare some data. I changed the graph scale to logarithmic (as my data points have huge differences) but since logarithmic scaling doesn't support zero values, I want to just add an empty point (or skip a data point) for such cases. I have tried the following but non works and all crashes:
if (/*the point is zero*/)
{
// myChart.Series["mySeries"].Points.AddY(null);
// or
// myChart.Series["mySeries"].Points.AddY();
// or just skip the point
}
Is it possible to add an empty point or just skip a point?
I found two ways to solve the problem.
One is using double.NaN (suddenly!):
if (myChart.ChartAreas[0].AxisY.IsLogarithmic && y == 0)
myChart.Series["mySeries"].Points.AddY(double.NaN);
// or ...Points.Add(double.NaN)
This looks like zero
And in my case it didn't crash with following SeriesChartTypes:
Column, Doughnut, FastPoint, Funnel, Kagi, Pie, Point, Polar, Pyramid, Radar, Renko, Spline, SplineArea, StackedBar, StackedColumn, ThreeLineBreak
The other way is a built-in concept of an empty point:
if (myChart.ChartAreas[0].AxisY.IsLogarithmic && y == 0)
myChart.Series["mySeries"].Points.Add(new DataPoint { IsEmpty = true });
This looks like a missing point (a gap):
And in my case it didn't crash with following SeriesChartTypes:
Area, Bar, Column, Doughnut, FastPoint, Funnel, Line, Pie, Point, Polar, Pyramid, Radar, Renko, Spline, SplineArea, StackedArea, StackedArea100, StackedBar, StackedBar100, StackedColumn, StackedColumn100, StepLine, ThreeLineBreak
The 2nd approach feels like the right (by design) one. The 1st one looks like a hack, that accidentally appears to work.
You can do a trick. You obviously have to clear you series. When you start adding points to an empty points collection x-coordinates are generated automatically starting at 1. You can create surrogate x yourself and skip some x-values.
int x = 0;
foreach(var y in yValues)
{
x++;
if (myChart.ChartAreas[0].AxisY.IsLogarithmic && y == 0)
continue;
myChart.Series["mySeries"].Points.AddXY(x, y);
}
With bar-like chart types it will look like a missing value.
I have a canvas where there are several polygons, what I want to do is try detect whether the polygons are overlapping. I'v looked around on various websites and most of what i'v found is to do with object collision - this for example, my polygons aren't moving so that's not going to be an issue.
I was wondering if someone could point me in the right direction on how to detect if they are overlapping. Is there a method that can calculate the space that's used on screen? or the region of the Polygon to compare the two?
So for example like the mock up here, the red shape overlaps the green one.
essentially all i want is to say yes they are overlapping or no they are not.
http://peterfleming.net84.net/Slice%201.png
Thanks in advance.
Pete
This library here (free and open source) will show polygon clipping: http://www.angusj.com/delphi/clipper.php
That said, if by polygons overlapping you mean at least one point of one is inside the other, you can test each polygon's point against the others by either looking at the point in point polygon problem or checking each polygons lines to see if it cuts across another polygon.
These methods will all work with different efficiency, try and see what's best for your situation.
However, your diagram seems to suggest you want to see if these polygons are 'side by side' or something similar. It would help to get clarification on this. Overlapping generally needs some coordinate plan to determine overlap against.
Assuming that each polygon is a Shape (either Path or Polygon) you could use the FillContainsWithDetail method of their RenderedGeometry to pairwise check interscetion.
I was having the same problem too and I used this implementation (which is heavenly inspired by this: C# Point in polygon ):
bool DoesPolygonsOverlap(IList<Point> firstPolygon, IList<Point> secondPolygon)
{
foreach (var item in firstPolygon)
{
if (IsPointInPolygon(secondPolygon, item))
{
return true;
}
}
foreach (var item in secondPolygon)
{
if (IsPointInPolygon(firstPolygon, item))
{
return true;
}
}
return false;
}
bool IsPointInPolygon(IList<Point> polygon, Point testPoint)
{
bool result = false;
int j = polygon.Count() - 1;
for (int i = 0; i < polygon.Count(); i++)
{
if (polygon[i].Y < testPoint.Y && polygon[j].Y >= testPoint.Y || polygon[j].Y < testPoint.Y && polygon[i].Y >= testPoint.Y)
{
if (polygon[i].X + (testPoint.Y - polygon[i].Y) / (polygon[j].Y - polygon[i].Y) * (polygon[j].X - polygon[i].X) < testPoint.X)
{
result = !result;
}
}
j = i;
}
return result;
}
Attention: The function was not very much tested and has a big potential for improvement. Please tell me if you find a bug/problem.
I'm working on a simple drawing app to further advance my skills and I can't seem to get the logic down for an eraser tool. The app simply uses the Line class to create lines as the user moves their finger. For the eraser tool I tried using the VisualTreeHelper as follows:
List<UIElement> elements = (List<UIElement>)VisualTreeHelper.FindElementsInHostCoordinates(e.GetPosition(tree), ContentPanelCanvas);
foreach (UIElement element in elements)
{
if (element is Line)
{
this.ContentPanelCanvas.Children.Remove(element);
}
}
It at some points but can be very slow and laggy. Sometimes I would have to touch the area more than 5 times to get rid of the line there.
Is there an alternative to this?
The e.GetPosition(tree) will be returning a point. Try instead using a Rect with the position as its center.
const double fingerMargin = 10.0;
Point p = e.GetPosition(tree);
Rect r = new Rect(p.X - fingerMargin, p.Y - fingerMargin, fingerMargin * 2, fingerMargin * 2);
var elements = VisualTreeHelper.FindElementsInHostCoordinates(r, ContentPanelCanvas);
Line lineToRemove = elements.OfType<Line>().FirstOrDefault();
if (lineToRemove != null)
{
ContentPanelCanvas.Children.Remove(lineToRemove);
}
Note don't cast the result of FindElementsInHostCoordinates to List<T>, that is an implementation detail, the documentation only guarantees it to be an IEnumerable<UIElement>, besides which it is an unnecessary cast.
You are actually looking for the set of elements that match the hit test of a single pixel. If your lines are narrow, then it's like a needle in a haystack; it's very hard to hit the line precisely to remove it.
Instead you need to use a fuzzy match using a rectangle instead of a point. You can use the same API, just the rectangle version of it:
VisualTreeHelper.FindElementsInHostCoordinates Method (Rect, UIElement)
VisualTreeHelper.FindElementsInHostCoordinates(r, MainCanvas);
Is not returning any Elements.