How to draw one line only one time - c#

I'm using GDI+ to visualize the schema of some user-specified building.
There are no complex objects in it - all of them can be represented by rectangles.
I do it, but have one issue: many rectangles are overlap, e.g. then rooms are adjacent.
So some lines drawed many times!
This looks bad (fat line) and decrease the application performance (extra work).
Is there a way to draw each line only once on a screen?
My code (simplified) looks like this:
private void Visualizator_Paint( object sender, PaintEventArgs e )
{
if ( m_building == null ) return;
var g = e.Graphics;
// Smooth graphics output and scale
g.SmoothingMode = SmoothingMode.HighQuality;
ScaleGraphics( g );
...
foreach( var room in m_rooms )
{
RectangleF extent = room.Extent;
g.DrawRectangle( brownPen, extent.X, extent.Y, extent.Width, extent.Height );
}
...
}
void ScaleGraphics( Graphics g )
{
// Set margins inside the control client area in pixels
var margin = new Margins( 16, 16, 16, 16 );
// Set the domain of (x,y) values
var range = m_building.Extents;
// Make it smaller by 5%
range.Inflate( 0.05f * range.Width, 0.05f * range.Height );
// Scale graphics
ScaleGraphics( g, Visualizator, range, margin );
}
void ScaleGraphics( Graphics g, Control control, RectangleF domain, Margins margin )
{
// Find the drawable area in pixels (control-margins)
int W = control.Width - margin.Left - margin.Right;
int H = control.Height - margin.Bottom - margin.Top;
// Ensure drawable area is at least 1 pixel wide
W = Math.Max( 1, W );
H = Math.Max( 1, H );
// Find the origin (0,0) in pixels
float OX = margin.Left - W * ( domain.Left / domain.Width );
float OY = margin.Top + H * ( 1 + domain.Top / domain.Height );
// Find the scale to fit the control
float SX = W / domain.Width;
float SY = H / domain.Height;
// Transform the Graphics scene
if ( m_panPoint.IsEmpty )
m_panPoint = new PointF( OX, OY );
g.TranslateTransform( m_panPoint.X, m_panPoint.Y, MatrixOrder.Append );
g.ScaleTransform( SX * m_scale, -SY * m_scale );
}
Screenshot of defect:

I was unable to reproduce the blurring/smearing effect described in the question. However, the basic request to be able to avoid over-drawing lines seems reasonably clear and not terribly complicated to address. So I offer this class which can do that work:
/// <summary>
/// Consolidates horizontal and vertical lines.
/// </summary>
class LineConsolidator : IEnumerable<LineConsolidator.Line>
{
/// <summary>
/// A pair of points defining a line
/// </summary>
public struct Line
{
public Point Start { get; private set; }
public Point End { get; private set; }
public Line(Point start, Point end)
: this()
{
Start = start;
End = end;
}
}
private struct Segment
{
public int Start { get; private set; }
public int End { get; private set; }
public Segment(int start, int end)
: this()
{
if (end < start)
{
throw new ArgumentException("start must be less than or equal to end");
}
Start = start;
End = end;
}
public Segment Union(Segment other)
{
if (End < other.Start || other.End < Start)
{
throw new ArgumentException("Only overlapping segments may be consolidated");
}
return new Segment(
Math.Min(Start, other.Start),
Math.Max(End, other.End));
}
public Segment? Intersect(Segment other)
{
int start = Math.Max(Start, other.Start),
end = Math.Min(End, other.End);
if (end < start)
{
return null;
}
return new Segment(start, end);
}
}
private Dictionary<int, List<Segment>> _horizontalLines = new Dictionary<int, List<Segment>>();
private Dictionary<int, List<Segment>> _verticalLines = new Dictionary<int, List<Segment>>();
/// <summary>
/// Add horizontal line
/// </summary>
/// <param name="y">The Y coordinate of the line to add</param>
/// <param name="start">The first X coordinate of the line to add (must not be larger than <paramref name="end"/></param>
/// <param name="end">The second X coordinate of the line to add (must not be smaller than <paramref name="start"/></param>
/// <remarks>
/// This method submits a new horizontal line to the collection. It is merged with any other
/// horizontal lines with exactly the same Y coordinate that it overlaps.
/// </remarks>
public void AddHorizontal(int y, int start, int end)
{
_AddLine(y, new Segment(start, end), _horizontalLines);
}
/// <summary>
/// Add vertical line
/// </summary>
/// <param name="y">The X coordinate of the line to add</param>
/// <param name="start">The first Y coordinate of the line to add (must not be larger than <paramref name="end"/></param>
/// <param name="end">The second Y coordinate of the line to add (must not be smaller than <paramref name="start"/></param>
/// <remarks>
/// This method submits a new vertical line to the collection. It is merged with any other
/// vertical lines with exactly the same X coordinate that it overlaps.
/// </remarks>
public void AddVertical(int x, int start, int end)
{
_AddLine(x, new Segment(start, end), _verticalLines);
}
/// <summary>
/// Add all four sides of a rectangle as individual lines
/// </summary>
/// <param name="rect">The rectangle containing the lines to add</param>
public void AddRectangle(Rectangle rect)
{
AddHorizontal(rect.Top, rect.Left, rect.Right);
AddHorizontal(rect.Bottom, rect.Left, rect.Right);
AddVertical(rect.Left, rect.Top, rect.Bottom);
AddVertical(rect.Right, rect.Top, rect.Bottom);
}
/// <summary>
/// Gets all of the horizontal lines in the collection
/// </summary>
public IEnumerable<Line> HorizontalLines
{
get
{
foreach (var kvp in _horizontalLines)
{
foreach (var segment in kvp.Value)
{
yield return new Line(new Point(segment.Start, kvp.Key), new Point(segment.End, kvp.Key));
}
}
}
}
/// <summary>
/// Gets all of the vertical lines in the collection
/// </summary>
public IEnumerable<Line> VerticalLines
{
get
{
foreach (var kvp in _verticalLines)
{
foreach (var segment in kvp.Value)
{
yield return new Line(new Point(kvp.Key, segment.Start), new Point(kvp.Key, segment.End));
}
}
}
}
private static void _AddLine(int lineKey, Segment newSegment, Dictionary<int, List<Segment>> segmentKeyToSegments)
{
// Get the list of segments for the given key (X for vertical lines, Y for horizontal lines)
List<Segment> segments;
if (!segmentKeyToSegments.TryGetValue(lineKey, out segments))
{
segments = new List<Segment>();
segmentKeyToSegments[lineKey] = segments;
}
int isegmentInsert = 0, isegmentMergeFirst = -1, ilineSegmentLast = -1;
// Find all existing segments that should be merged with the new one
while (isegmentInsert < segments.Count && segments[isegmentInsert].Start <= newSegment.End)
{
Segment? intersectedSegment = newSegment.Intersect(segments[isegmentInsert]);
if (intersectedSegment != null)
{
// If they overlap, merge them together, keeping track of all the existing
// segments which were merged
newSegment = newSegment.Union(segments[isegmentInsert]);
if (isegmentMergeFirst == -1)
{
isegmentMergeFirst = isegmentInsert;
}
ilineSegmentLast = isegmentInsert;
}
isegmentInsert++;
}
if (isegmentMergeFirst == -1)
{
// If there was no merge, just insert the new segment
segments.Insert(isegmentInsert, newSegment);
}
else
{
// If more than one segment was merged, remove all but one
if (ilineSegmentLast > isegmentMergeFirst)
{
segments.RemoveRange(isegmentMergeFirst + 1, ilineSegmentLast - isegmentMergeFirst);
}
// Copy the new, merged segment back to the first original segment's slot
segments[isegmentMergeFirst] = newSegment;
}
}
public IEnumerator<LineConsolidator.Line> GetEnumerator()
{
return HorizontalLines.Concat(VerticalLines).GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
Note that this is based on integer coordinates. It's a little trickier to apply this sort of logic to floating point coordinates, if one is actually concerned about accommodating round-off error. But if the floating point coordinates are assured of always coming from the same source when they overlap, then they will meet the equality condition this implementation requires and you can just change the types to floating point.
I included the properties to retrieve just the horizontal or vertical lines, so that I could draw them differently from each other (different line end-caps) to verify the algorithm was working. Normally I think you'd just enumerate the whole collection when drawing.
You use it by first creating an empty instance of the collection, then adding your rectangles (or individual lines if desired) via the AddRectangle() method, then finally enumerating all of the resulting lines.
I would expect this to perform just fine up to thousands of lines or so. In my tests, I just recreated the collection from scratch every time I painted the window.
It might perform well enough even at higher magnitudes depending on the PC, but I didn't try to do any specific optimizations, opting instead for easy-to-understand code. In a situation where you're dealing with an extremely large number of rectangles, you might want to keep a persistent instance to collect lines/rectangles as they are generated. Then you don't have to regenerate it every paint event. That may or may not then require adding features to the class to support removal of lines.

Related

Calculate geographic distance between points using NetTopologySuite

I am trying to calculate distance between two points using NetTopologySuite. Since I am referencing Microsoft documentation, I Came up with following GeometryExtension and GeometryHelper classes:
public static class GeometryExtensions
{
private static readonly CoordinateSystemServices _coordinateSystemServices = new CoordinateSystemServices(new Dictionary<int, string>
{
// Coordinate systems:
[4326] = GeographicCoordinateSystem.WGS84.WKT,
// CRS for Denmark ESPG 25832. Source: https://epsg.io/25832 and https://sdfe.dk/
[25832] = #"PROJCS[""ETRS89 / UTM zone 32N"",
GEOGCS[""ETRS89"",
DATUM[""European_Terrestrial_Reference_System_1989"",
SPHEROID[""GRS 1980"",6378137,298.257222101,
AUTHORITY[""EPSG"",""7019""]],
TOWGS84[0,0,0,0,0,0,0],
AUTHORITY[""EPSG"",""6258""]],
PRIMEM[""Greenwich"",0,
AUTHORITY[""EPSG"",""8901""]],
UNIT[""degree"",0.0174532925199433,
AUTHORITY[""EPSG"",""9122""]],
AUTHORITY[""EPSG"",""4258""]],
PROJECTION[""Transverse_Mercator""],
PARAMETER[""latitude_of_origin"",0],
PARAMETER[""central_meridian"",9],
PARAMETER[""scale_factor"",0.9996],
PARAMETER[""false_easting"",500000],
PARAMETER[""false_northing"",0],
UNIT[""metre"",1,
AUTHORITY[""EPSG"",""9001""]],
AXIS[""Easting"",EAST],
AXIS[""Northing"",NORTH],
AUTHORITY[""EPSG"",""25832""]]"
}
);
/// <summary>
/// Projects a geometry to another SRID
/// </summary>
/// <param name="geometry"></param>
/// <param name="targetSrid"></param>
/// <param name="defaultSourceSrid">If the geometry SRID has not been specified (i.e. equals 0) defaultSourceSrid is used</param>
/// <returns></returns>
public static Geometry ProjectTo(this Geometry geometry, int targetSrid, int? defaultSourceSrid = null)
{
if (geometry == null)
throw new Exception("Geometry is null, cannot project");
var sourceSrid = geometry.SRID == 0 && defaultSourceSrid.HasValue ? defaultSourceSrid.Value : geometry.SRID;
var transformation = _coordinateSystemServices.CreateTransformation(sourceSrid, targetSrid);
var result = geometry.Copy();
result.Apply(new MathTransformFilter(transformation.MathTransform));
return result;
}
}
public class GeometryHelper
{
private static readonly int _longLatSRID = 4326;
private static readonly int _targetSRID = 25832;
/// <summary>
/// In order to get the distance in meters, we need to project to an appropriate
/// coordinate system. If no SRID is provided 25832, which covers Denmark is used.
/// If the provided Points have no SRID, 4326 (longitude/latitude) is assumed.
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <param name="targetSrid"></param>
/// <returns></returns>
public static double DistanceInMeters(Point a, Point b, int? targetSrid = null)
{
targetSrid ??= _targetSRID;
try
{
//If SRID is not set, assume long/lat, ie. 4326
return a.ProjectTo(targetSrid.Value, _longLatSRID).Distance(b.ProjectTo(targetSrid.Value, _longLatSRID));
}
catch (Exception e)
{
throw new Exception("Failed to calculate distance", e);
}
}
}
To test if my distance is being calculated properly, I opened Google Maps and selected the calculate distance between 3 different pairs of points. Then I copy pasted the values in my tests to see if they match. The values I am getting from my code is 2 times larger than what I am getting from Google Maps. What am I doing wrong? Here are my tests:
public class GeometryHelperTests
{
[Theory]
[InlineData(55.676518126293466, 12.567203858066554, 55.67645068023813, 12.56698376863065, 15.68)]//I am getting 36.1
[InlineData(55.659368700924475, 12.546625254609248, 55.65940085355421, 12.546601114728679, 3.77)]//I am getting 6.2
[InlineData(55.65896978705746, 12.546674114514795, 55.6596855501795, 12.547258269821455, 87.09)]//I am getting 173.7
public void DistanceInMeters(double x1, double y1, double x2, double y2, double expected)
{
// setup
Point point1 = new Point(x1, y1) { SRID = 4326 };
Point point2 = new Point(x2, y2) { SRID = 4326 };
//CoordinateSystemFactory csFact = new CoordinateSystemFactory();
// act
var result = GeometryHelper.DistanceInMeters(point1, point2);
// assert
result.Should().Be(expected);
}
}
You need to calculate great circle distance.
NetTopologySuite Point.Distance method returns the cartesian distance.
Try the following:
public static double Radians(double x)
{
return x * Math.PI / 180;
}
public static double GreatCircleDistance(double lon1, double lat1, double lon2, double lat2)
{
double R = 6371e3; // m
double sLat1 = Math.Sin(Radians(lat1));
double sLat2 = Math.Sin(Radians(lat2));
double cLat1 = Math.Cos(Radians(lat1));
double cLat2 = Math.Cos(Radians(lat2));
double cLon = Math.Cos(Radians(lon1) - Radians(lon2));
double cosD = sLat1*sLat2 + cLat1*cLat2*cLon;
double d = Math.Acos(cosD);
double dist = R * d;
return dist;
}
You can also use the built-in GeoCoordinate.GetDistanceTo() which implements the Haversine formula that is more accurate for small distances.
Even though saeedkazemi's answer is correct and gives results closer to the real value, results given by my code were also quite close, but I figured out that I had to flip the coordinates given by Google maps. So if I was given 55.6765, 12.5672 I need to give 12.5672,55.6765 to the formula. That being said, saeedkazemi's answer provides results closer to the real value.

Split resize algorithm into two passes

I have written the following resizing algorithm which can correctly scale an image up or down. It's far too slow though due to the inner iteration through the array of weights on each loop.
I'm fairly certain I should be able to split out the algorithm into two passes much like you would with a two pass Gaussian blur which would vastly reduce the operational complexity and speed up performance. Unfortunately I can't get it to work. Would anyone be able to help?
Parallel.For(
startY,
endY,
y =>
{
if (y >= targetY && y < targetBottom)
{
Weight[] verticalValues = this.verticalWeights[y].Values;
for (int x = startX; x < endX; x++)
{
Weight[] horizontalValues = this.horizontalWeights[x].Values;
// Destination color components
Color destination = new Color();
// This is where there is too much operation complexity.
foreach (Weight yw in verticalValues)
{
int originY = yw.Index;
foreach (Weight xw in horizontalValues)
{
int originX = xw.Index;
Color sourceColor = Color.Expand(source[originX, originY]);
float weight = yw.Value * xw.Value;
destination += sourceColor * weight;
}
}
destination = Color.Compress(destination);
target[x, y] = destination;
}
}
});
Weights and indices are calculated as follows. One for each dimension:
/// <summary>
/// Computes the weights to apply at each pixel when resizing.
/// </summary>
/// <param name="destinationSize">The destination section size.</param>
/// <param name="sourceSize">The source section size.</param>
/// <returns>
/// The <see cref="T:Weights[]"/>.
/// </returns>
private Weights[] PrecomputeWeights(int destinationSize, int sourceSize)
{
IResampler sampler = this.Sampler;
float ratio = sourceSize / (float)destinationSize;
float scale = ratio;
// When shrinking, broaden the effective kernel support so that we still
// visit every source pixel.
if (scale < 1)
{
scale = 1;
}
float scaledRadius = (float)Math.Ceiling(scale * sampler.Radius);
Weights[] result = new Weights[destinationSize];
// Make the weights slices, one source for each column or row.
Parallel.For(
0,
destinationSize,
i =>
{
float center = ((i + .5f) * ratio) - 0.5f;
int start = (int)Math.Ceiling(center - scaledRadius);
if (start < 0)
{
start = 0;
}
int end = (int)Math.Floor(center + scaledRadius);
if (end > sourceSize)
{
end = sourceSize;
if (end < start)
{
end = start;
}
}
float sum = 0;
result[i] = new Weights();
List<Weight> builder = new List<Weight>();
for (int a = start; a < end; a++)
{
float w = sampler.GetValue((a - center) / scale);
if (w < 0 || w > 0)
{
sum += w;
builder.Add(new Weight(a, w));
}
}
// Normalise the values
if (sum > 0 || sum < 0)
{
builder.ForEach(w => w.Value /= sum);
}
result[i].Values = builder.ToArray();
result[i].Sum = sum;
});
return result;
}
/// <summary>
/// Represents the weight to be added to a scaled pixel.
/// </summary>
protected class Weight
{
/// <summary>
/// The pixel index.
/// </summary>
public readonly int Index;
/// <summary>
/// Initializes a new instance of the <see cref="Weight"/> class.
/// </summary>
/// <param name="index">The index.</param>
/// <param name="value">The value.</param>
public Weight(int index, float value)
{
this.Index = index;
this.Value = value;
}
/// <summary>
/// Gets or sets the result of the interpolation algorithm.
/// </summary>
public float Value { get; set; }
}
/// <summary>
/// Represents a collection of weights and their sum.
/// </summary>
protected class Weights
{
/// <summary>
/// Gets or sets the values.
/// </summary>
public Weight[] Values { get; set; }
/// <summary>
/// Gets or sets the sum.
/// </summary>
public float Sum { get; set; }
}
Each IResampler provides the appropriate series of weights based on the given index. the bicubic resampler works as follows.
/// <summary>
/// The function implements the bicubic kernel algorithm W(x) as described on
/// <see href="https://en.wikipedia.org/wiki/Bicubic_interpolation#Bicubic_convolution_algorithm">Wikipedia</see>
/// A commonly used algorithm within imageprocessing that preserves sharpness better than triangle interpolation.
/// </summary>
public class BicubicResampler : IResampler
{
/// <inheritdoc/>
public float Radius => 2;
/// <inheritdoc/>
public float GetValue(float x)
{
// The coefficient.
float a = -0.5f;
if (x < 0)
{
x = -x;
}
float result = 0;
if (x <= 1)
{
result = (((1.5f * x) - 2.5f) * x * x) + 1;
}
else if (x < 2)
{
result = (((((a * x) + 2.5f) * x) - 4) * x) + 2;
}
return result;
}
}
Here's an example of an image resized by the existing algorithm. The output is correct (note the silvery sheen is preserved).
Original image
Image halved in size using the bicubic resampler.
The code is part of a much larger library that I am writing to add image processing to corefx.
Judging from an abstract point of view (not knowing much about image manipulation) I think it looks like you're computing the values for weight and sourcecolor (in the innermost foreach loop) multiple times (whenever the same pair of indices crops up again); would it be feasible to simply precompute them in advance?
You would need to compute a 'direct product' Matrix for your HorizontalWeight and VerticalWeight matrices (simply multiplying the values for each pair of indices (x, y)) and could also apply Color.Expand to the source in advance.
These tasks can be done in parallel and the 'direct product' (sorry, I don't know the correct Name for that beast) should be available in lots of libraries.
You can try a weighted voronoi diagram. Try randomly a set of points and compute the voronoi diagram. Smooth the polygons with Lloyd's algorithm and interpolate the color of the polygon. With the weight compute the weighted voronoi diagram. For example voronoi stippling and mosaic:http://www.mrl.nyu.edu/~ajsecord/stipples.html and http://www.evilmadscientist.com/2012/stipplegen-weighted-voronoi-stippling-and-tsp-paths-in-processing/.
Ok so here's how I went about it.
The trick is to first resize only the width of the image keeping the height the same as the original image. We store the resultant pixels in a temporary image.
Then it's a case of resizing that image down to our final output.
As you can see we are no longer iterating through both weight collections on each pixel. Despite having to iterate though the outer pixel loop twice the algorithm was much faster in it's operation averaging around 25% faster on my test images.
// Interpolate the image using the calculated weights.
// First process the columns.
Parallel.For(
0,
sourceBottom,
y =>
{
for (int x = startX; x < endX; x++)
{
Weight[] horizontalValues = this.HorizontalWeights[x].Values;
// Destination color components
Color destination = new Color();
foreach (Weight xw in horizontalValues)
{
int originX = xw.Index;
Color sourceColor = Color.Expand(source[originX, y]);
destination += sourceColor * xw.Value;
}
destination = Color.Compress(destination);
this.firstPass[x, y] = destination;
}
});
// Now process the rows.
Parallel.For(
startY,
endY,
y =>
{
if (y >= targetY && y < targetBottom)
{
Weight[] verticalValues = this.VerticalWeights[y].Values;
for (int x = startX; x < endX; x++)
{
// Destination color components
Color destination = new Color();
foreach (Weight yw in verticalValues)
{
int originY = yw.Index;
int originX = x;
Color sourceColor = Color.Expand(this.firstPass[originX, originY]);
destination += sourceColor * yw.Value;
}
destination = Color.Compress(destination);
target[x, y] = destination;
}
}
});

How to check Ray Picking Cylinders XNA

I'm drawing cylinders using the primitives provided by XNA, and what I want to do is, when the mouse hovers on a cylinder, I detect it. I tried using bounding spheres but it didn't work, as it wasn't accurate. I don't know what to do. Any help?
Summary of what I want to do.
1- Create bounding Box for this Cylinder
2- Rotate this bounding Box.
CODE TO DRAW CYLINDER can be found here
http://xbox.create.msdn.com/en-US/education/catalog/sample/primitives_3d
#region File Description
//-----------------------------------------------------------------------------
// CylinderPrimitive.cs
//
// Microsoft XNA Community Game Platform
// Copyright (C) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
#endregion
#region Using Statements
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
#endregion
namespace TheProteinBundle
{
/// <summary>
/// Geometric primitive class for drawing cylinders.
/// </summary>
public class CylinderPrimitive : GeometricPrimitive
{
/// <summary>
/// Constructs a new cylinder primitive, using default settings.
/// </summary>
public CylinderPrimitive(GraphicsDevice graphicsDevice)
: this(graphicsDevice, 1, 1, 32)
{
}
/// <summary>
/// Constructs a new cylinder primitive,
/// with the specified size and tessellation level.
/// </summary>
public CylinderPrimitive(GraphicsDevice graphicsDevice,
float height, float diameter, int tessellation)
{
if (tessellation < 3)
throw new ArgumentOutOfRangeException("tessellation");
height /= 2;
float radius = diameter / 2;
// Create a ring of triangles around the outside of the cylinder.
for (int i = 0; i < tessellation; i++)
{
Vector3 normal = GetCircleVector(i, tessellation);
AddVertex(normal * radius + Vector3.Up * height, normal);
AddVertex(normal * radius + Vector3.Down * height, normal);
AddIndex(i * 2);
AddIndex(i * 2 + 1);
AddIndex((i * 2 + 2) % (tessellation * 2));
AddIndex(i * 2 + 1);
AddIndex((i * 2 + 3) % (tessellation * 2));
AddIndex((i * 2 + 2) % (tessellation * 2));
}
// Create flat triangle fan caps to seal the top and bottom.
CreateCap(tessellation, height, radius, Vector3.Up);
CreateCap(tessellation, height, radius, Vector3.Down);
InitializePrimitive(graphicsDevice);
base.boundingSphere.Center = Vector3.Zero;
if (height > diameter)
base.boundingSphere.Radius = height;
else
base.boundingSphere.Radius = diameter;
}
/// <summary>
/// Helper method creates a triangle fan to close the ends of the cylinder.
/// </summary>
void CreateCap(int tessellation, float height, float radius, Vector3 normal)
{
// Create cap indices.
for (int i = 0; i < tessellation - 2; i++)
{
if (normal.Y > 0)
{
AddIndex(CurrentVertex);
AddIndex(CurrentVertex + (i + 1) % tessellation);
AddIndex(CurrentVertex + (i + 2) % tessellation);
}
else
{
AddIndex(CurrentVertex);
AddIndex(CurrentVertex + (i + 2) % tessellation);
AddIndex(CurrentVertex + (i + 1) % tessellation);
}
}
// Create cap vertices.
for (int i = 0; i < tessellation; i++)
{
Vector3 position = GetCircleVector(i, tessellation) * radius +
normal * height;
AddVertex(position, normal);
}
}
/// <summary>
/// Helper method computes a point on a circle.
/// </summary>
static Vector3 GetCircleVector(int i, int tessellation)
{
float angle = i * MathHelper.TwoPi / tessellation;
float dx = (float)Math.Cos(angle);
float dz = (float)Math.Sin(angle);
return new Vector3(dx, 0, dz);
}
}
}
Using this class
#region File Description
//-----------------------------------------------------------------------------
// GeometricPrimitive.cs
//
// Microsoft XNA Community Game Platform
// Copyright (C) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
#endregion
#region Using Statements
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
#endregion
namespace TheProteinBundle
{
/// <summary>
/// Base class for simple geometric primitive models. This provides a vertex
/// buffer, an index buffer, plus methods for drawing the model. Classes for
/// specific types of primitive (CubePrimitive, SpherePrimitive, etc.) are
/// derived from this common base, and use the AddVertex and AddIndex methods
/// to specify their geometry.
/// </summary>
public abstract class GeometricPrimitive : IDisposable
{
#region Fields
// During the process of constructing a primitive model, vertex
// and index data is stored on the CPU in these managed lists.
List<VertexPositionNormal> vertices = new List<VertexPositionNormal>();
List<ushort> indices = new List<ushort>();
// Once all the geometry has been specified, the InitializePrimitive
// method copies the vertex and index data into these buffers, which
// store it on the GPU ready for efficient rendering.
VertexBuffer vertexBuffer;
IndexBuffer indexBuffer;
BasicEffect basicEffect;
public BoundingSphere boundingsphere2;
public BoundingSphere boundingSphere;
public Matrix world;
#endregion
#region Initialization
/// <summary>
/// Adds a new vertex to the primitive model. This should only be called
/// during the initialization process, before InitializePrimitive.
/// </summary>
protected void AddVertex(Vector3 position, Vector3 normal)
{
vertices.Add(new VertexPositionNormal(position, normal));
}
/// <summary>
/// Adds a new index to the primitive model. This should only be called
/// during the initialization process, before InitializePrimitive.
/// </summary>
protected void AddIndex(int index)
{
if (index > ushort.MaxValue)
throw new ArgumentOutOfRangeException("index");
indices.Add((ushort)index);
}
/// <summary>
/// Queries the index of the current vertex. This starts at
/// zero, and increments every time AddVertex is called.
/// </summary>
protected int CurrentVertex
{
get { return vertices.Count; }
}
/// <summary>
/// Once all the geometry has been specified by calling AddVertex and AddIndex,
/// this method copies the vertex and index data into GPU format buffers, ready
/// for efficient rendering.
protected void InitializePrimitive(GraphicsDevice graphicsDevice)
{
// Create a vertex declaration, describing the format of our vertex data.
// Create a vertex buffer, and copy our vertex data into it.
vertexBuffer = new VertexBuffer(graphicsDevice,
typeof(VertexPositionNormal),
vertices.Count, BufferUsage.None);
vertexBuffer.SetData(vertices.ToArray());
// Create an index buffer, and copy our index data into it.
indexBuffer = new IndexBuffer(graphicsDevice, typeof(ushort),
indices.Count, BufferUsage.None);
indexBuffer.SetData(indices.ToArray());
// Create a BasicEffect, which will be used to render the primitive.
basicEffect = new BasicEffect(graphicsDevice);
basicEffect.EnableDefaultLighting();
}
/// <summary>
/// Finalizer.
/// </summary>
~GeometricPrimitive()
{
Dispose(false);
}
/// <summary>
/// Frees resources used by this object.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Frees resources used by this object.
/// </summary>
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
if (vertexBuffer != null)
vertexBuffer.Dispose();
if (indexBuffer != null)
indexBuffer.Dispose();
if (basicEffect != null)
basicEffect.Dispose();
}
}
#endregion
public void TransformBoundingSphere(Matrix TransformToWorld)
{
boundingSphere.Transform(ref TransformToWorld, out boundingsphere2);
}
public bool CheckRayIntersection(Ray ray)
{
if (ray.Intersects(boundingsphere2).HasValue) return true;
return false;
}
#region Draw
/// <summary>
/// Draws the primitive model, using the specified effect. Unlike the other
/// Draw overload where you just specify the world/view/projection matrices
/// and color, this method does not set any renderstates, so you must make
/// sure all states are set to sensible values before you call it.
/// </summary>
public void Draw(Effect effect)
{
GraphicsDevice graphicsDevice = effect.GraphicsDevice;
// Set our vertex declaration, vertex buffer, and index buffer.
graphicsDevice.SetVertexBuffer(vertexBuffer);
graphicsDevice.Indices = indexBuffer;
foreach (EffectPass effectPass in effect.CurrentTechnique.Passes)
{
effectPass.Apply();
int primitiveCount = indices.Count / 3;
graphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0,
vertices.Count, 0, primitiveCount);
}
}
/// <summary>
/// Draws the primitive model, using a BasicEffect shader with default
/// lighting. Unlike the other Draw overload where you specify a custom
/// effect, this method sets important renderstates to sensible values
/// for 3D model rendering, so you do not need to set these states before
/// you call it.
/// </summary>
public void Draw(Matrix world, Matrix view, Matrix projection, Color color)
{
// Set BasicEffect parameters.
basicEffect.World = world;
basicEffect.View = view;
basicEffect.Projection = projection;
basicEffect.DiffuseColor = color.ToVector3();
basicEffect.Alpha = color.A / 255.0f;
GraphicsDevice device = basicEffect.GraphicsDevice;
device.DepthStencilState = DepthStencilState.Default;
if (color.A < 255)
{
// Set renderstates for alpha blended rendering.
device.BlendState = BlendState.AlphaBlend;
}
else
{
// Set renderstates for opaque rendering.
device.BlendState = BlendState.Opaque;
}
// Draw the model, using BasicEffect.
Draw(basicEffect);
}
#endregion
}
}

Multithreading Update with ReaderWriterLockSlim

I think I made a huge error when I first started designing my game and would give you some or even all of my code but it is just too complex. So please bear with me.
Now that I get to the more advanced stages of my design I am running into major trouble with threading.
During update I run 3 tasks simultaneously. Update, HitTesting and AI which are even split up into even more threads.
Updating needs to do the movement (including physics) and animation of all my objects.
HitTesting does all Hit testing between thousands of objects and still needs a lot of optimizing…. Things like Devide and conquer to get right.
AI issues commands to objects that are executed by the update cycle. Things like turn left or right, fire, etc.
And of course we have
draw… or in my case GetSprites during draw. That must have full priority over all but the update process.
And the yet unimplemented sound and message system.
As you can see this is not an optimal process for multitasking as they all function on the same objects but it needs to be.
So… I thought to implement System.Threading.ReaderWriterLockSlim
And here is my real question: How do I do that?
Since Update is the only writer to my draw data.
Drawing is a pure Reader
HitTesting might need to recalculate the boundingRectangle and Matrix but that does not influence Drawing
AI only needs to read position data and issue commands, read by Update on the next cycle and is separated in an group of separate classes (I call it master/puppet but probably has an official/better name)
Does it make sense to implement different ReaderWriterLockSlim objects to lock the different properties a thread could need?
I would like to have control during update to bypass a locked object (either by AI or HitTesting) and take the next one so that I can do it later when it is unlocked (or even skip it if update is taking too long but do it on the next cycle)
Does any of you know a book or site on advanced threading. Not the usual little examples I find everywhere so I can figure this out?
I have been stuck for more than a week now and I would like to proceed.
Any help appreciated.
This is the code I use for collision detection between objects. I converted it from a c++ example I found a long time ago but can't remember where.
public abstract class HitTestInfo : Object
{
static protected Random RND = new Random();
static protected Dictionary<String, Color[]> m_TextureDataDictionary;
public static Matrix GetMatrix(iSpriteInfo vDrawObject)
{
Matrix Transform =
Matrix.CreateTranslation(new Vector3(-vDrawObject.Origin, 0.0f)) *
Matrix.CreateScale(vDrawObject.Scale) *
Matrix.CreateRotationZ(vDrawObject.Angle) *
Matrix.CreateTranslation(new Vector3(vDrawObject.X, vDrawObject.Y, 0.0f));
return Transform;
}
/// <summary>
/// Calculates an axis aligned rectangle which fully contains an arbitrarily
/// transformed axis aligned rectangle.
/// </summary>
/// <param name="rectangle">Original bounding rectangle.</param>
/// <param name="transform">World transform of the rectangle.</param>
/// <returns>A new rectangle which contains the trasnformed rectangle.</returns>
public static Rectangle CalculateBoundingRectangle(Rectangle vrectangle,
Matrix transform)
{
Rectangle rectangle = vrectangle;
rectangle.X = 0;
rectangle.Y = 0;
// Get all four corners in local space
Vector2 leftTop = new Vector2(rectangle.Left, rectangle.Top);
Vector2 rightTop = new Vector2(rectangle.Right, rectangle.Top);
Vector2 leftBottom = new Vector2(rectangle.Left, rectangle.Bottom);
Vector2 rightBottom = new Vector2(rectangle.Right, rectangle.Bottom);
// Transform all four corners into work space
Vector2.Transform(ref leftTop, ref transform, out leftTop);
Vector2.Transform(ref rightTop, ref transform, out rightTop);
Vector2.Transform(ref leftBottom, ref transform, out leftBottom);
Vector2.Transform(ref rightBottom, ref transform, out rightBottom);
// Find the minimum and maximum extents of the rectangle in world space
Vector2 min = Vector2.Min(Vector2.Min(leftTop, rightTop),
Vector2.Min(leftBottom, rightBottom));
Vector2 max = Vector2.Max(Vector2.Max(leftTop, rightTop),
Vector2.Max(leftBottom, rightBottom));
// Return that as a rectangle
return new Rectangle((int)min.X, (int)min.Y,
(int)(max.X - min.X), (int)(max.Y - min.Y));
}
/// <summary>
/// Determines if there is overlap of the non-transparent pixels between two
/// sprites.
/// </summary>
/// <param name="transformA">World transform of the first sprite.</param>
/// <param name="widthA">Width of the first sprite's texture.</param>
/// <param name="heightA">Height of the first sprite's texture.</param>
/// <param name="dataA">Pixel color data of the first sprite.</param>
/// <param name="transformB">World transform of the second sprite.</param>
/// <param name="widthB">Width of the second sprite's texture.</param>
/// <param name="heightB">Height of the second sprite's texture.</param>
/// <param name="dataB">Pixel color data of the second sprite.</param>
/// <returns>True if non-transparent pixels overlap; false otherwise</returns>
public static bool IntersectPixels(
Matrix transformA, int widthA, int heightA, Color[] dataA,
Matrix transformB, int widthB, int heightB, Color[] dataB)
{
// Calculate a matrix which transforms from A's local space into
// world space and then into B's local space
Matrix transformAToB = transformA * Matrix.Invert(transformB);
// When a point moves in A's local space, it moves in B's local space with a
// fixed direction and distance proportional to the movement in A.
// This algorithm steps through A one pixel at a time along A's X and Y axes
// Calculate the analogous steps in B:
Vector2 stepX = Vector2.TransformNormal(Vector2.UnitX, transformAToB);
Vector2 stepY = Vector2.TransformNormal(Vector2.UnitY, transformAToB);
// Calculate the top left corner of A in B's local space
// This variable will be reused to keep track of the start of each row
Vector2 yPosInB = Vector2.Transform(Vector2.Zero, transformAToB);
// For each row of pixels in A
for (int yA = 0; yA < heightA; yA++)
{
// Start at the beginning of the row
Vector2 posInB = yPosInB;
// For each pixel in this row
for (int xA = 0; xA < widthA; xA++)
{
// Round to the nearest pixel
int xB = (int)Math.Round(posInB.X);
int yB = (int)Math.Round(posInB.Y);
// If the pixel lies within the bounds of B
if (0 <= xB && xB < widthB &&
0 <= yB && yB < heightB)
{
// Get the colors of the overlapping pixels
Color colorA = dataA[xA + yA * widthA];
Color colorB = dataB[xB + yB * widthB];
// If both pixels are not completely transparent,
if (colorA.A != 0 && colorB.A != 0)
{
// then an intersection has been found
return true;
}
}
// Move to the next pixel in the row
posInB += stepX;
}
// Move to the next row
yPosInB += stepY;
}
// No intersection found
return false;
}
public static List<CollisionData> CollisionCheck<T1, T2>(List<T1> List1, List<T2> List2)
{
List<CollisionData> RetList = new List<CollisionData>();
foreach (T1 obj1 in List1)
{
iSpriteInfo SI1 = obj1 as iSpriteInfo;
if (SI1 != null)
{
Matrix Matrix1 = SI1.Matrix;
Rectangle Rect1 = SI1.BoundingRectangle;
Color[] TextureData1 = SI1.TextureData;
foreach (T2 obj2 in List2)
{
iSpriteInfo SI2 = obj2 as iSpriteInfo;
if (SI1 != null)
{
Matrix Matrix2 = SI2.Matrix;
Rectangle Rect2 = SI2.BoundingRectangle;
Color[] TextureData2 = SI2.TextureData;
// The per-pixel check is expensive, so check the bounding rectangles
// first to prevent testing pixels when collisions are impossible.
if (Rect1.Intersects(Rect2))
{
// Check collision with Player and planets
if (IntersectPixels(Matrix1, (int)SI1.DestinationRectangle.Width,
(int)SI1.DestinationRectangle.Height, TextureData1,
Matrix2, (int)SI2.DestinationRectangle.Width,
(int)SI2.DestinationRectangle.Height, TextureData2))
{
RetList.Add(new CollisionData(SI1, SI2));
}
}
}
}
}
}
return RetList;
}
public static List<CollisionData> CollisionCheck<T1, T2>(T1 Obj1, List<T2> List2)
{
List<CollisionData> RetList = new List<CollisionData>();
lock (Obj1)
{
lock (List2)
{
iSpriteInfo SI1 = Obj1 as iSpriteInfo;
if (SI1 != null)
{
Matrix Matrix1 = SI1.Matrix;
Rectangle Rect1 = SI1.BoundingRectangle;
Color[] TextureData1 = SI1.TextureData;
foreach (T2 obj2 in List2)
{
iSpriteInfo SI2 = obj2 as iSpriteInfo;
if (SI1 != null)
{
Matrix Matrix2 = SI2.Matrix;
Rectangle Rect2 = SI2.BoundingRectangle;
Color[] TextureData2 = SI2.TextureData;
// The per-pixel check is expensive, so check the bounding rectangles
// first to prevent testing pixels when collisions are impossible.
if (Rect1.Intersects(Rect2))
{
// Check collision with Player and planets
if (IntersectPixels(Matrix1, (int)SI1.DestinationRectangle.Width,
(int)SI1.DestinationRectangle.Height, TextureData1,
Matrix2, (int)SI2.DestinationRectangle.Width,
(int)SI2.DestinationRectangle.Height, TextureData2))
{
RetList.Add(new CollisionData(SI1, SI2));
}
}
}
}
}
}
}
return RetList;
}
public static bool CollisionCheck<T1, T2>(T1 Obj1, T2 Obj2)
{
Matrix Matrix1;
Rectangle Rect1;
Color[] TextureData1;
Matrix Matrix2;
Rectangle Rect2;
Color[] TextureData2;
iSpriteInfo SI1 = Obj1 as iSpriteInfo;
if (SI1 != null)
{
lock (SI1)
{
Matrix1 = SI1.Matrix;
Rect1 = SI1.BoundingRectangle;
TextureData1 = SI1.TextureData;
}
iSpriteInfo SI2 = Obj2 as iSpriteInfo;
if (SI1 != null)
{
lock (SI2)
{
Matrix2 = SI2.Matrix;
Rect2 = SI2.BoundingRectangle;
TextureData2 = SI2.TextureData;
}
// The per-pixel check is expensive, so check the bounding rectangles
// first to prevent testing pixels when collisions are impossible.
if (Rect1.Intersects(Rect2))
{
// Check collision with Player and planets
if (IntersectPixels(Matrix1, (int)SI1.DestinationRectangle.Width,
(int)SI1.DestinationRectangle.Height, TextureData1,
Matrix2, (int)SI2.DestinationRectangle.Width,
(int)SI2.DestinationRectangle.Height, TextureData2))
{
return true;
}
}
}
}
return false;
}
}
iSpriteInfo is defined like this
public interface iSpriteInfo
{
float X { get; set; }
float Y { get; set; }
float Angle { get; set; }
Vector2 Origin { get; set; }
float Scale { get; set; }
float Depth { get; set; }
Color Color { get; set; }
Boolean Visible { get; set; }
Rectangle SourceRectangle { get; set; }
Rectangle DestinationRectangle { get; set; }
Rectangle BoundingRectangle { get; }
Matrix Matrix { get; }
SpriteSheet SpriteSheet { get; set; }
int SpriteSheetNum { get;}
Color[] TextureData { get; set; }
Vector2 GetVector2 { get; }
Vector3 GetVector3 { get; }
}
Some calculations in Update may be performed by GPU using CUDA technology (https://developer.nvidia.com/gpu-computing-sdk)
I can recommend several steps, and i hope some of them will be usefull:
1) Split your four spritesheets into several smaller spritesheets by category (Asteroids, Ships, bullets etc). MS always says, that several smaller image sources is better that one huge.
2) Get rid of background tiles for star field etc. Use HLSL to create starfield, explosions and effects. GPU perfomance is near "unlimited" for that kind of tasks and actually is good alternative of using CUDA.
3) Split collision detection process on independence tasks:
a) between active units
b) between active units and inactive environment(using collision map)
In strategies units should precalculate their path. So its nesessary to detect path intersections to recalculate pathes and prevent collisions.
All collisions should recalculates only in users viewport zone.
Active units should check collisions only near themselves.

How can I create a Spline chart with WPF without using a Windows Forms Control or aftermarket package?

I have a need to create a spline chart natively in WPF via either Dynamic Data Display or using the WPF DataVisualization toolkit. Does anyone know if it's possible?
If so, can you point me to an example of a Spline chart?
Thanks in advance.
Have you tried the PolyBezierSegment
Sounds like you are looking for a best fit curve to draw in WPF. You would have to create a custom control to draw it, but a Bezier spline should work nicely for you if you can do the drawing. Here is a class I wrote that will allow you to generate a set of smooth points from a small set of seed points. Instantiate the object, set the SeedPoints property to a set of points, then call the GenerateSpline(int NumberPointsToGenerate) to get the points for a smooth curve. I generally use about 40 points to generate a really smooth curve.
/// <summary>
/// Class to generate a Bezier Curve from a set of seed points.
/// Note that this is a best fit curve through a set of points
/// and a Bezier does NOT require the curve to pass through the points.
/// </summary>
public class BezierSpline
{
/// <summary>
/// Default Constructor
/// </summary>
public BezierSpline()
{
SeedPoints = new List<PointF>();
}
/// <summary>
/// Constructs a BezierSpline object using SeedPoints
/// </summary>
/// <param name="seedPoints"></param>
public BezierSpline(IList<PointF> seedPoints)
: this()
{
SeedPoints = new List<PointF>(seedPoints);
}
/// <summary>
/// Set of SeedPoints to run the spline through
/// </summary>
public List<PointF> SeedPoints { get; set; }
/// <summary>
/// Generates a smooth curve through a series of points
/// </summary>
/// <param name="numberPointsToGenerate">Number of points to generate between P0 and Pn</param>
/// <returns>IList of Points along the curve</returns>
public IList<PointF> GenerateSpline(int numberPointsToGenerate)
{
List<double> ps = new List<double>();
List<PointF> np = new List<PointF>();
for (int i = 0; i < SeedPoints.Count; i++)
{
ps.Add(SeedPoints[i].X);
ps.Add(SeedPoints[i].Y);
}
double[] newpts = Bezier2D(ps.ToArray(), numberPointsToGenerate);
for (int i = 0; i < newpts.Length; i += 2)
np.Add(new PointF(newpts[i], newpts[i + 1], 0));
return np;
}
private double[] Bezier2D(double[] b, int cpts)
{
double[] p = new double[cpts * 2];
int npts = (b.Length) / 2;
int icount, jcount;
double step, t;
// Calculate points on curve
icount = 0;
t = 0;
step = (double)1 / (cpts - 1);
for (int i1 = 0; i1 < cpts; i1++)
{
if ((1.0 - t) < 5e-6)
t = 1.0;
jcount = 0;
p[icount] = 0.0;
p[icount + 1] = 0.0;
for (int i = 0; i != npts; i++)
{
double basis = Bernstein(npts - 1, i, t);
p[icount] += basis * b[jcount];
p[icount + 1] += basis * b[jcount + 1];
jcount += 2;
}
icount += 2;
t += step;
}
return p;
}
private double Ni(int n, int i)
{
return Factorial(n) / (Factorial(i) * Factorial(n - i));
}
/// <summary>
/// Bernstein basis formula
/// </summary>
/// <param name="n">n</param>
/// <param name="i">i</param>
/// <param name="t">t</param>
/// <returns>Bernstein basis</returns>
private double Bernstein(int n, int i, double t)
{
double basis;
double ti; /* t^i */
double tni; /* (1 - t)^i */
if (t == 0.0 && i == 0)
ti = 1.0;
else
ti = System.Math.Pow(t, i);
if (n == i && t == 1.0)
tni = 1.0;
else
tni = System.Math.Pow((1 - t), (n - i));
//Bernstein basis
basis = Ni(n, i) * ti * tni;
return basis;
}
/// <summary>
/// Gets a single y value for a given x value
/// </summary>
/// <param name="x">X value</param>
/// <returns>Y value at the given location</returns>
public PointF GetPointAlongSpline(double x)
{
return new PointF();
}
public static int Factorial(int n)
{
int f = 1;
for (int i = n; i > 1; i--)
f *= i;
return f;
}
}

Categories