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
}
}
Related
Hello I am trying to make a paint program using XNA and I followed the guide found in here as much as possible: How to create Paint-like app with XNA?
And it worked all great so far however there is one issue: the drawn rectangles dont connect together to form a line. I am out of ideas and I would appreciate any help offered. Heres my code below. Please also take a look at the image attached to get a better understanding.
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System;
using System.Collections.Generic;
namespace ProfAnas
{
/// <summary>
/// This is the main type for your game.
/// </summary>
public class Game1 : Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Texture2D canvas;
Vector2 brushPos;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
graphics.PreferredBackBufferWidth = 1920; // set this value to the desired width of your window
graphics.PreferredBackBufferHeight = 1080; // set this value to the desired height of your window
graphics.ApplyChanges();
}
/// <summary>
/// Allows the game to perform any initialization it needs to before starting to run.
/// This is where it can query for any required services and load any non-graphic
/// related content. Calling base.Initialize will enumerate through any components
/// and initialize them as well.
/// </summary>
protected override void Initialize()
{
// TODO: Add your initialization logic here
base.Initialize();
Color[] pixel = new Color[1920 * 1080];
for (int i = 0; i < pixel.Length; i++)
{
pixel[i] = Color.White;
}
pixel[1919] = Color.Red;
spriteBatch = new SpriteBatch(GraphicsDevice);
canvas = new Texture2D(this.GraphicsDevice, 1920, 1080);
canvas.SetData<Color>(pixel);
}
/// <summary>
/// LoadContent will be called once per game and is the place to load
/// all of your content.
/// </summary>
protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
// TODO: use this.Content to load your game content here
}
/// <summary>
/// UnloadContent will be called once per game and is the place to unload
/// game-specific content.
/// </summary>
protected override void UnloadContent()
{
// TODO: Unload any non ContentManager content here
}
/// <summary>
/// Allows the game to run logic such as updating the world,
/// checking for collisions, gathering input, and playing audio.
/// </summary>
/// <param name="gameTime">Provides a snapshot of timing values.</param>
protected override void Update(GameTime gameTime)
{
MouseState state = Mouse.GetState();
Color[] pixel = new Color[1920 * 1080];
canvas.GetData<Color>(pixel);
if (state.LeftButton == ButtonState.Pressed)
{
brushPos.X = state.X;
brushPos.Y = state.Y;
double piOn4 = Math.PI / 4;
int xComponent;
int yComponent;
int screenWidth = 1920;
int screenHeight = 1080;
int regionXHalfed = (int)Math.Ceiling((10.0 * Math.Cos(piOn4) + 30.0 * Math.Cos(piOn4))/2);
int regionYHalfed = (int)Math.Ceiling((10.0 * Math.Sin(piOn4) + 30.0 * Math.Sin(piOn4))/2);
double angle;
double centerToBoundary;
double pixelToCenter;
List<int> boundedPixel = new List<int>();
for (int row=(state.Y-regionYHalfed); row < (state.Y + regionYHalfed); row++)
{
for (int column= (state.X - regionXHalfed); column < (state.X + regionXHalfed); column++)
{
xComponent=column - state.X+1;
yComponent=row - state.Y+1;
if (xComponent == 0)
{
pixelToCenter = Math.Sqrt((double)xComponent*xComponent + (double)yComponent*yComponent);
if (Math.Abs(pixelToCenter) <= 5*Math.Sqrt(2))
{
boundedPixel.Add(((row) * screenWidth + column) + 1);
}
continue;
}
angle=Math.Atan( (double)yComponent / (double)xComponent);
if (angle>= (piOn4 - Math.Atan(1.0/3)) && angle<= (piOn4 + Math.Atan(1.0/3)))
{
centerToBoundary = 15 / Math.Cos(angle - piOn4);
pixelToCenter= xComponent / Math.Cos(angle);
if( Math.Abs(pixelToCenter) <= Math.Abs(centerToBoundary))
{
boundedPixel.Add(((row)* screenWidth + column)+1);
}
}
if (angle >= (piOn4 + Math.Atan(1.0 / 3)) && angle <= Math.PI/2)
{
centerToBoundary = 5 / Math.Cos(angle + piOn4);
pixelToCenter = xComponent / Math.Cos(angle);
if (Math.Abs(pixelToCenter) <= Math.Abs(centerToBoundary))
{
boundedPixel.Add(((row) * screenWidth + column) + 1);
}
}
if (angle >= 0.0 && angle <= (piOn4 - Math.Atan(1.0 / 3)))
{
centerToBoundary = 5 / Math.Cos(angle + piOn4);
pixelToCenter = xComponent / Math.Cos(angle);
if (Math.Abs(pixelToCenter) <= Math.Abs(centerToBoundary))
{
boundedPixel.Add(((row) * screenWidth + column) + 1);
}
}
if (angle >= -Math.PI / 2 && angle <= 0.0)
{
centerToBoundary = 5 / Math.Cos(angle + piOn4);
pixelToCenter = xComponent / Math.Cos(angle);
if (Math.Abs(pixelToCenter) <= Math.Abs(centerToBoundary))
{
boundedPixel.Add(((row) * screenWidth + column) + 1);
}
}
}
}
foreach (int i in boundedPixel)
{
if(i>=0)
pixel[i] = Color.Red;
}
}
canvas.SetData<Color>(pixel);
// TODO: Add your update logic here
base.Update(gameTime);
}
/// <summary>
/// This is called when the game should draw itself.
/// </summary>
/// <param name="gameTime">Provides a snapshot of timing values.</param>
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
// TODO: Add your drawing code here
spriteBatch.Begin();
spriteBatch.Draw(canvas, new Vector2(0, 0));
spriteBatch.End();
base.Draw(gameTime);
}
}
}
Thank you :)
As #adv12 suggested, the Update method of XNA games isn't as fast as the one of MSPaint's, and that's why you'll never have a line if drawing pixel-by-pixel (or in your case rectangle-by-rectangle) if you move your mouse fast across the canvas.
The possible solution is to draw new rectangles when you release left mouse button, between the rectangles that were created when left mouse button was pressed. That will give you twice -1 the rectangles you are currently drawing, and you will have a line, no matter how fast the Update is called.
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;
}
}
});
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.
I have some sort of a problem. I'm new to XNA and want to draw a polygon shape that looks something like this (In the end, I want these point to be random):
So I read some articles and this is what I ended up with:
private VertexPositionColor[] vertices;
public TextureClass()
{
setupVertices();
}
public override void Render(SpriteBatch spriteBatch)
{
Texture2D texture = createTexture(spriteBatch);
spriteBatch.Draw(texture, new Rectangle((int)vertices[0].Position.X, (int)vertices[0].Position.Y, 30, 30), Color.Brown);
}
private Texture2D createTexture(SpriteBatch spriteBatch)
{
Texture2D texture = new Texture2D(spriteBatch.GraphicsDevice, 1, 1, false, SurfaceFormat.Color);
texture.SetData<Color>(new Color[] { Color.Brown });
return texture;
}
When I call Render it's starts drawing some squares as if it where in a loop. I'm just guessing I'm doing it all wrong. I would love it if someones points me into the right direction. Just creating a polygon and drawing it. It seemed so simple...
Here it what I have right now.
A class that generates a BasicEffect with some desired asignments.
public class StandardBasicEffect : BasicEffect
{
public StandardBasicEffect(GraphicsDevice graphicsDevice)
: base(graphicsDevice)
{
this.VertexColorEnabled = true;
this.Projection = Matrix.CreateOrthographicOffCenter(
0, graphicsDevice.Viewport.Width, graphicsDevice.Viewport.Height, 0, 0, 1);
}
public StandardBasicEffect(BasicEffect effect)
: base(effect) { }
public BasicEffect Clone()
{
return new StandardBasicEffect(this);
}
}
Here is my PolygonShape class
/// <summary>
/// A Polygon object that you will be able to draw.
/// Animations are being implemented as we speak.
/// </summary>
/// <param name="graphicsDevice">The graphicsdevice from a Game object</param>
/// <param name="vertices">The vertices in a clockwise order</param>
public PolygonShape(GraphicsDevice graphicsDevice, VertexPositionColor[] vertices)
{
this.graphicsDevice = graphicsDevice;
this.vertices = vertices;
this.triangulated = false;
triangulatedVertices = new VertexPositionColor[vertices.Length * 3];
indexes = new int[vertices.Length];
}
/// <summary>
/// Triangulate the set of VertexPositionColors so it will be drawn correcrly
/// </summary>
/// <returns>The triangulated vertices array</returns>}
public VertexPositionColor[] Triangulate()
{
calculateCenterPoint();{
setupIndexes();
for (int i = 0; i < indexes.Length; i++)
{
setupDrawableTriangle(indexes[i]);
}
triangulated = true;
return triangulatedVertices;
}
/// <summary>
/// Calculate the center point needed for triangulation.
/// The polygon will be irregular, so this isn't the actual center of the polygon
/// but it will do for now, as we only need an extra point to make the triangles with</summary>
private void calculateCenterPoint()
{
float xCount = 0, yCount = 0;
foreach (VertexPositionColor vertice in vertices)
{
xCount += vertice.Position.X;
yCount += vertice.Position.Y;
}
centerPoint = new Vector3(xCount / vertices.Length, yCount / vertices.Length, 0);
}
private void setupIndexes()
{
for (int i = 1; i < triangulatedVertices.Length; i = i + 3)
{
indexes[i / 3] = i - 1;
}
}
private void setupDrawableTriangle(int index)
{
triangulatedVertices[index] = vertices[index / 3]; //No DividedByZeroException?...
if (index / 3 != vertices.Length - 1)
triangulatedVertices[index + 1] = vertices[(index / 3) + 1];
else
triangulatedVertices[index + 1] = vertices[0];
triangulatedVertices[index + 2].Position = centerPoint;
}
/// <summary>
/// Draw the polygon. If you haven't called Triangulate yet, I wil do it for you.
/// </summary>
/// <param name="effect">The BasicEffect needed for drawing</param>
public void Draw(BasicEffect effect)
{
try
{
if (!triangulated)
Triangulate();
draw(effect);
}
catch (Exception exception)
{
throw exception;
}
}
private void draw(BasicEffect effect)
{
effect.CurrentTechnique.Passes[0].Apply();
graphicsDevice.DrawUserPrimitives<VertexPositionColor>(
PrimitiveType.TriangleList, triangulatedVertices, 0, vertices.Length);
}
Sorry, it's kind of alot. Now for my next quest. Animation my polygon.
Hope it helped fellow people with the same problem.
this code is useful to draw 2D lines, some calcs can be done into an initilization call, but i prefer for this example to keep all together.
public void DrawLine(VertexPositionColor[] Vertices)
{
Game.GraphicsDevice.DepthStencilState = DepthStencilState.Default;
Vector2 center;
center.X = Game.GraphicsDevice.Viewport.Width * 0.5f;
center.Y = Game.GraphicsDevice.Viewport.Height * 0.5f;
Matrix View = Matrix.CreateLookAt( new Vector3( center, 0 ), new Vector3( center, 1 ), new Vector3( 0, -1, 0 ) );
Matrix Projection = Matrix.CreateOrthographic( center.X * 2, center.Y * 2, -0.5f, 1 );
Effect EffectLines = Game.Content.Load<Effect>( "lines" );
EffectLines.CurrentTechnique = EffectLines.Techniques["Lines"];
EffectLines.Parameters["xViewProjection"].SetValue( View * Projection );
EffectLines.Parameters["xWorld"].SetValue( Matrix.Identity );
foreach ( EffectPass pass in EffectLines.CurrentTechnique.Passes )
{
pass.Apply( );
Game.GraphicsDevice.DrawUserPrimitives<VertexPositionColor>
( PrimitiveType.LineList, Vertices, 0, Vertices.Length/2 );
}
}
LINES.FX
uniform float4x4 xWorld;
uniform float4x4 xViewProjection;
void VS_Basico(in float4 inPos : POSITION, in float4 inColor: COLOR0, out float4 outPos: POSITION, out float4 outColor:COLOR0 )
{
float4 tmp = mul (inPos, xWorld);
outPos = mul (tmp, xViewProjection);
outColor = inColor;
}
float4 PS_Basico(in float4 inColor:COLOR) :COLOR
{
return inColor;
}
technique Lines
{
pass Pass0
{
VertexShader = compile vs_2_0 VS_Basico();
PixelShader = compile ps_2_0 PS_Basico();
FILLMODE = SOLID;
CULLMODE = NONE;
}
}
I worked with XNA in the past on a physics simulation where I had to draw bounding boxes with GraphicsDevice.DrawIndexedPrimitives (You should google or MSDN for this function for more worked examples.)
The below code is what I used in my project for drawing a 3D geometry.
/// <summary>
/// Draw the primitive.
/// </summary>
/// <param name="world">World Matrix</param>
/// <param name="view">View Matrix</param>
/// <param name="projection">Projection Matrix</param>
/// <param name="color">Color of the primitive</param>
public void Draw(Matrix world, Matrix view, Matrix projection, Color color)
{
_mGraphicsDevice.VertexDeclaration = _mVertexDeclaration;
_mGraphicsDevice.Vertices[0].SetSource(_mVertexBuffer, 0, VertexPositionNormal.SizeInBytes);
_mGraphicsDevice.Indices = _mIndexBuffer;
_mBasicEffect.DiffuseColor = color.ToVector3();
_mBasicEffect.World = _mTransform * world;
_mBasicEffect.View = view;
_mBasicEffect.Projection = projection;
int primitiveCount = _mIndex.Count / 3;
_mBasicEffect.Begin();
foreach (EffectPass pass in _mBasicEffect.CurrentTechnique.Passes)
{
pass.Begin();
_mGraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, _mVertex.Count, 0, primitiveCount);
pass.End();
}
_mBasicEffect.End();
}
This function is a member method of a geometry object (class) and is called from the Game class' Draw(GameTime) method
I am a bit new to the DirectX library and I am wondering if anyone can help me with a camera issue. In my main form I load a set of polygon data representing a 3D object and then pass that polygon data to another form and want to draw the polygon as a triangle list. Unfortunately I cannot seem to get the camera to either 1) Have the proper viewing frustum or 2) Get the camera to properly focus and size the image. The polygon data is being loaded as world coordinate data.
Below is the code that initializes the secondary form, directx, camera, etc.
#region Public Members
/// <summary>
/// Default Constructor.
/// </summary>
public STLViewer()
{
// Set the form size, form text, icon
this.ClientSize = new Size(500, 500);
this.Text = "Object Name: " + stlFile.SolidName + ", Polygon Count: " + stlFile.GetPolygons().Count;
this.Icon = RetrieveFormIcon();
// Change our drawing style so there is no drawing happening outside our main form
this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.Opaque, true);
// Get our vertex data in a prepared format
verts = PrepareObjectForRender();
}
/// <summary>
/// This function is responsible for retrieving the specified
/// icon from the assembly.
/// </summary>
/// <returns>Form icon</returns>
private Icon RetrieveFormIcon()
{
Assembly assembly = Assembly.GetExecutingAssembly();
Stream str = assembly.GetManifestResourceStream(icon);
return new Icon(str);
}
#endregion
#region Main Line
public static void Main()
{
// Create our form object
STLViewer stlViewer = new STLViewer();
// Initialize D3D
if (stlViewer.InitializeDirect3D() == false)
{
MessageBox.Show("Could not initialize Direct3D.", "Error");
return;
}
// Display our form
stlViewer.Show();
// Main message loop
while (stlViewer.Created)
{
// Keep rendering the image until the form is terminated
//stlViewer.Render();
// Handle aall events here: keyboard, mouse, etc.
Application.DoEvents();
}
}
#endregion
#region Rendering
protected override void OnPaint(PaintEventArgs e)
{
if (directXDevice.RenderState.FillMode == FillMode.Solid)
{
directXDevice.RenderState.FillMode = FillMode.WireFrame;
}
// Clear the window to black
directXDevice.Clear(ClearFlags.Target, Color.Black, 1.0f, 0);
// Setup the camera for viewing
SetupCamera();
// Begin the rendering process
directXDevice.BeginScene();
// Set the vertext format
directXDevice.VertexFormat = CustomVertex.PositionColored.Format;
// Draw our vertices
directXDevice.DrawUserPrimitives(PrimitiveType.TriangleList, stlFile.GetPolygons().Count, verts);
// End rendering and present the drawing to the screen
directXDevice.EndScene();
directXDevice.Present();
// Force our form to refresh its viewing area
this.Invalidate();
}
/// <summary>
/// This function is responsible for rendering the image
/// to the screen.
/// </summary>
private void Render()
{
// IF we cannot connect to a device then return
if (directXDevice == null)
{
return;
}
// Get our vertex data in a prepared format
CustomVertex.PositionColored[] verts = PrepareObjectForRender();
// Clear the window to black
directXDevice.Clear(ClearFlags.Target, Color.Black, 1.0f, 0);
// Setup the camera for viewing
SetupCamera();
// Begin the rendering process
directXDevice.BeginScene();
// Set the vertext format
directXDevice.VertexFormat = CustomVertex.PositionColored.Format;
// Draw our vertices
directXDevice.DrawUserPrimitives(PrimitiveType.TriangleList, stlFile.GetPolygons().Count, verts);
// End rendering and present the drawing to the screen
directXDevice.EndScene();
directXDevice.Present();
}
/// <summary>
/// This function is responsible for creating the necessary
/// DirectX objects, setting the vertices and normals, colors
/// and/or materials for the object so we can draw it in the
/// form.
/// </summary>
private CustomVertex.PositionColored[] PrepareObjectForRender()
{
// List to hold our colored vertices
List<CustomVertex.PositionColored> vertices = new List<CustomVertex.PositionColored>();
// List to hold our polygons, contained within the STL file
List<Polygon.Polygon> polygons = stlFile.GetPolygons();
// Create a custom vertex that will be used to hold our vertices
CustomVertex.PositionColored custVert = new CustomVertex.PositionColored();
// Iterate through our polygons and pulled a vertex list
for (int i = 0; i < polygons.Count; i++)
{
// Set the position and color of our 1st vertex in our polygon, add it to our list
custVert.Position = new Vector3((float)polygons[i].GetDoublePoints1()[0],
(float)polygons[i].GetDoublePoints1()[1], (float)polygons[i].GetDoublePoints1()[2]);
custVert.Color = Color.YellowGreen.ToArgb();
vertices.Add(custVert);
// Set the position and color of our 2nd vertex in our polygon, add it to our list
custVert.Position = new Vector3((float)polygons[i].GetDoublePoints2()[0],
(float)polygons[i].GetDoublePoints2()[1], (float)polygons[i].GetDoublePoints2()[2]);
custVert.Color = Color.YellowGreen.ToArgb();
vertices.Add(custVert);
// Set the position and color of our 3rd vertex in our polygon, add it to our list
custVert.Position = new Vector3((float)polygons[i].GetDoublePoints3()[0],
(float)polygons[i].GetDoublePoints3()[1], (float)polygons[i].GetDoublePoints3()[2]);
custVert.Color = Color.YellowGreen.ToArgb();
vertices.Add(custVert);
}
// Cast our list to an array of vertices
CustomVertex.PositionColored[] verts = vertices.ToArray();
return verts;
}
#endregion
#region Initialization & Configuration
private bool InitializeDirect3D()
{
bool retVal = false;
try
{
// Create a new PresentParameters object so our device knows how to display
PresentParameters pps = new PresentParameters();
// Display in a windowed mode
pps.Windowed = true;
// After the current screen is draw, discard it from memory
pps.SwapEffect = SwapEffect.Discard;
// Create the new D3D Device and set our PresentParameters within
directXDevice = new Device(0, DeviceType.Hardware, this,
CreateFlags.SoftwareVertexProcessing, pps);
// Use wireframe as our drawing mode
directXDevice.RenderState.FillMode = FillMode.WireFrame;
retVal = true;
}
catch (DirectXException e)
{
// Display our error message
MessageBox.Show(e.ToString(), "Error");
retVal = false;
}
return retVal;
}
private void SetupCamera()
{
directXDevice.Transform.Projection = Matrix.PerspectiveFovLH(1.85f,
(float)(this.Width / this.Height), 1.0f, 1.00f);
directXDevice.Transform.View = Matrix.LookAtLH(new Vector3(0.0f, 0.0f, 5.0f),
new Vector3(0.0f, 0.0f, 0.0f), new Vector3(0.0f, 1.0f, 0.0f));
directXDevice.RenderState.Lighting = false;
}
#endregion
Had a couple small things out of place and found a tutorial at drunkenhyena that I had forgotten about, which helped me through the rest of the way.
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using System.IO;
using System.Reflection;
using Polygon;
using STL;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
namespace STLView
{
public class STLViewer : Form
{
#region Private Members
#region DirectX Objects
/// <summary>
/// DirectX Device object that will allow us to perform drawing
/// </summary>
private Device directXDevice = null;
/// <summary>
/// Array of vertices that will hold our Polygon vertices
/// </summary>
private static CustomVertex.PositionColored[] verts;
#endregion
#region STL Viewer Specific Objects
/// <summary>
/// String to hold the name of the path in the assembly for our form icon
/// </summary>
private static string icon = "STLView.Resources.Symbol17.ico";
/// <summary>
/// STLFile object that will be used to draw the object
/// </summary>
public static STLFile stlFile;
#endregion
#endregion
#region Public Members
/// <summary>
/// Default Constructor.
/// </summary>
public STLViewer()
{
// Set the form size, form text, icon
this.ClientSize = new Size(800, 600);
this.Text = "Object Name: " + stlFile.SolidName + ", Polygon Count: " + stlFile.GetPolygons().Count;
this.Icon = RetrieveFormIcon();
// Change our drawing style so there is no drawing happening outside our main form
this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.Opaque, true);
// Get our vertex data in a prepared format
verts = PrepareObjectForRender();
}
/// <summary>
/// This function is responsible for retrieving the specified
/// icon from the assembly.
/// </summary>
/// <returns>Form icon</returns>
private Icon RetrieveFormIcon()
{
Assembly assembly = Assembly.GetExecutingAssembly();
Stream str = assembly.GetManifestResourceStream(icon);
return new Icon(str);
}
#endregion
#region Main Line
public static void Main()
{
// Create our form object
STLViewer stlViewer = new STLViewer();
// Initialize D3D
if (stlViewer.InitializeDirect3D() == false)
{
MessageBox.Show("Could not initialize Direct3D.", "Error");
return;
}
// Display our form
stlViewer.Show();
// Main message loop
while (stlViewer.Created)
{
// Handle aall events here: keyboard, mouse, etc.
Application.DoEvents();
}
}
#endregion
#region Rendering
/// <summary>
/// This function is the overloaded OnPaint method, which is responsible
/// for drawing our scene.
/// </summary>
/// <param name="e"></param>
protected override void OnPaint(PaintEventArgs e)
{
// IF we have a forced switch to Solid mode switch to Wireframe
if (directXDevice.RenderState.FillMode == FillMode.Solid)
{
directXDevice.RenderState.FillMode = FillMode.WireFrame;
}
// Clear the window to black
directXDevice.Clear(ClearFlags.Target, Color.Navy, 1.0f, 0);
// Disable culling
directXDevice.RenderState.CullMode = Cull.None;
// Setup the camera for viewing
SetupCamera();
// Begin the rendering process
directXDevice.BeginScene();
// Set the vertext format
directXDevice.VertexFormat = CustomVertex.PositionColored.Format;
// Draw our vertices
directXDevice.DrawUserPrimitives(PrimitiveType.TriangleList, stlFile.GetPolygons().Count, verts);
// End rendering and present the drawing to the screen
directXDevice.EndScene();
directXDevice.Present();
// Force our form to refresh its viewing area
this.Invalidate();
}
/// <summary>
/// This function is responsible for creating the necessary
/// DirectX objects, setting the vertices and normals, colors
/// and/or materials for the object so we can draw it in the
/// form.
/// </summary>
private CustomVertex.PositionColored[] PrepareObjectForRender()
{
// List to hold our colored vertices
List<CustomVertex.PositionColored> vertices = new List<CustomVertex.PositionColored>();
// List to hold our polygons, contained within the STL file
List<Polygon.Polygon> polygons = stlFile.GetPolygons();
// Create a custom vertex that will be used to hold our vertices
CustomVertex.PositionColored custVert = new CustomVertex.PositionColored();
// Iterate through our polygons and pulled a vertex list
for (int i = 0; i < polygons.Count; i++)
{
// Set the position and color of our 1st vertex in our polygon, add it to our list
custVert.Position = new Vector3((float)polygons[i].GetDoublePoints1()[0],
(float)polygons[i].GetDoublePoints1()[1], (float)polygons[i].GetDoublePoints1()[2]);
custVert.Color = Color.YellowGreen.ToArgb();
vertices.Add(custVert);
// Set the position and color of our 2nd vertex in our polygon, add it to our list
custVert.Position = new Vector3((float)polygons[i].GetDoublePoints2()[0],
(float)polygons[i].GetDoublePoints2()[1], (float)polygons[i].GetDoublePoints2()[2]);
custVert.Color = Color.YellowGreen.ToArgb();
vertices.Add(custVert);
// Set the position and color of our 3rd vertex in our polygon, add it to our list
custVert.Position = new Vector3((float)polygons[i].GetDoublePoints3()[0],
(float)polygons[i].GetDoublePoints3()[1], (float)polygons[i].GetDoublePoints3()[2]);
custVert.Color = Color.YellowGreen.ToArgb();
vertices.Add(custVert);
}
// Cast our list to an array of vertices
CustomVertex.PositionColored[] verts = vertices.ToArray();
return verts;
}
#endregion
#region Initialization & Configuration
/// <summary>
/// This function is responsible for initializing our Direct3D device.
/// </summary>
/// <returns>TRUE if successful, FALSE if not</returns>
private bool InitializeDirect3D()
{
bool retVal = false;
// TRY to create our Direct3D device
// CATCH and report any errors
try
{
#region Present Parameters
// Create a new PresentParameters object so our device knows how to display
PresentParameters pps = new PresentParameters();
// Display in a windowed mode
pps.Windowed = true;
// After the current screen is draw, discard it from memory
pps.SwapEffect = SwapEffect.Discard;
// No Z (Depth) buffer or Stencil buffer
pps.EnableAutoDepthStencil = false;
// 1 Back buffer for double-buffering
pps.BackBufferCount = 1;
// Set our back buffer dimensions and format
pps.BackBufferHeight = 0;
pps.BackBufferWidth = 0;
pps.BackBufferFormat = Format.Unknown;
#endregion
// Create the new D3D Device and set our PresentParameters within
directXDevice = new Device(0, DeviceType.Hardware, this,
CreateFlags.SoftwareVertexProcessing, pps);
// Use wireframe as our drawing mode
directXDevice.RenderState.FillMode = FillMode.WireFrame;
retVal = true;
}
catch (DirectXException e)
{
// Display our error message
MessageBox.Show(e.ToString(), "Error");
}
return retVal;
}
#endregion
#region Camera
/// <summary>
/// This function is responsible for setting up the camera.
/// </summary>
private void SetupCamera()
{
#region View Matrix
// This is the camera location and is commonly referred to as the "eye point"
Vector3 eyeVector = new Vector3(0.0f, 0.0f, -500.0f);
// The Look At Vector defines the point that the camera is aimed at
Vector3 lookAtVector = new Vector3(0, 0, 0);
// The "up" direction is the positive direction on the y-axis
Vector3 upVector = new Vector3(0, 1, 0);
// Register our View Matrix so it can be used to place and aim our camera
directXDevice.Transform.View = Matrix.LookAtLH(eyeVector,
lookAtVector, upVector);
#endregion
#region Projection Matrix
//45 degree field of view
float fieldOfView = (float)Math.PI / 4.0f;
//Typical aspect ratio is approximately 1.333...
float aspectRatio = (float)ClientSize.Width / ClientSize.Height;
// Register our Projection Matrix so it can be used for viewing items in
// front of the camera
directXDevice.Transform.Projection = Matrix.PerspectiveFovLH(fieldOfView,
aspectRatio, 1.0f, 1000.0f);
#endregion
#region World Matrix
// Create a scaling matrix
Matrix scaleMatrix = Matrix.Scaling(0.5f, 0.5f, 0.5f);
// World Matrix = Scaling Matrix
directXDevice.Transform.World = scaleMatrix;
#endregion
#region Lighting
// Enable/Disable lighting
directXDevice.RenderState.Lighting = false;
#endregion
}
#endregion
}
}