I'm trying to get some primitives working for XNA, including the ability to make outlined rectangles that can rotate. This is pretty useful, as you can do some sweet dynamic content creation with it, as well as it serving a debugging purpose.
The problem I am having can be seen here
Essentially, what is happening is, when drawing an outlined rectangle, and turning the thickness of the line up past one, the lines that make up the rectangle move farther and farther away.
It's very strange but, I'm 90% sure that the problem lies in the origin.
I'm hoping someone could take a look and spot my mistake.
I am rendering the rectangle by drawing rotated lines by setting the origin of the line to be what the center of the rectangle is. To achieve the bottom and right parts of the rectangle, I copy and rotate the top and left lines 180 degrees. (Pi if you are working in radians, which I am)
The biggest weirdness is that when I debug the Draw method I find the values are all exactly as expected!
Here's the rectangle class, the inherited classes are also below in case you need them.
public class Rectangle : Primitive
{
public float X { get { return Body.X; } set { Body.X = value; } }
public float Y { get { return Body.Y; } set { Body.Y = value; } }
public float Width { get { return Body.Width; } set { Body.Width = value; } }
public float Height { get { return Body.Height; } set { Body.Height = value; } }
public float Angle { get { return Body.Angle; } set { Body.Angle = value; } }
public override Vector2 Bounds { get { return new Vector2((Width) * Scale.X, (Height) * Scale.Y);}}
public override Microsoft.Xna.Framework.Rectangle DrawRect
{
get
{
return new Microsoft.Xna.Framework.Rectangle((int) (X - Thickness/2), (int) (Y - Thickness/2),
(int) ((X + Width + Thickness) * Scale.X),
(int) ((Y + Height + Thickness)* Scale.Y));
}
}
public bool Fill;
public Rectangle(Entity parent, string name, float x, float y, float width, float height, bool fill = false)
: base(parent, name)
{
X = x;
Y = y;
Width = width;
Height = height;
Fill = fill;
Origin = new Vector2(.5f,.5f);
}
public Rectangle(IComponent parent, string name, Body body, bool fill) : base(parent, name, body)
{
Fill = fill;
Origin = new Vector2(.5f, .5f);
}
public override void Draw(SpriteBatch sb)
{
base.Draw(sb);
if (!Fill)
{
float minx = X + (Thickness/2) + Origin.X;
float miny = Y + (Thickness/2) +Origin.Y;
//TODO: Fix origin issue
//Draw our top line
sb.Draw(Assets.Pixel,
new Vector2(minx, miny), null, Color * Alpha, Angle, new Vector2(Origin.X, Origin.Y * Bounds.Y), new Vector2(Bounds.X, Thickness * Scale.Y), Flip, Layer);
//Left line
sb.Draw(Assets.Pixel,
new Vector2(minx, miny), null, Color * Alpha, Angle, new Vector2(Origin.X * Bounds.X, Origin.Y), new Vector2(Thickness * Scale.X, Bounds.Y), Flip, Layer);
//Essentially these are the same as the top and bottom just rotated 180 degrees
//I have to do it this way instead of setting the origin to a negative value because XNA
//seems to ignore origins when they are negative
//Right Line
sb.Draw(Assets.Pixel,
new Vector2(minx + 1, miny), null, Color * Alpha, Angle + MathHelper.Pi, new Vector2(Origin.X * Bounds.X, Origin.Y), new Vector2(Thickness * Scale.X, Bounds.Y), Flip, Layer);
//Bottom Line
sb.Draw(Assets.Pixel,
new Vector2(minx, miny + 1), null, Color * Alpha, Angle + MathHelper.Pi, new Vector2(Origin.X, Origin.Y * Bounds.Y), new Vector2(Bounds.X, Thickness * Scale.Y), Flip, Layer);
}
else
{
sb.Draw(Assets.Pixel, new Vector2(X + Origin.X*Width, Y + Origin.Y*Height), null, Color * Alpha, Angle, Origin, Bounds - new Vector2(Thickness), Flip, Layer);
}
}
Primitive
public abstract class Primitive : Render
{
public Body Body;
public float Thickness = 1;
protected Primitive(IComponent parent, string name) : base(parent, name)
{
Body = new Body(this, "Primitive.Body");
}
protected Primitive(IComponent parent, string name, Vector2 pos)
: base(parent, name)
{
Body = new Body(this, "Primitive.Body", pos);
}
protected Primitive(IComponent parent, string name, Body body) : base(parent, name)
{
Body = body;
}
public static void DrawLine(SpriteBatch sb, Vector2 p1, Vector2 p2, float thickness, float layer, Color color)
{
float angle = (float)System.Math.Atan2(p2.Y - p1.Y, p2.X - p1.X);
float length = Vector2.Distance(p1, p2);
sb.Draw(Assets.Pixel, p1, null, color,
angle, Vector2.Zero, new Vector2(length, thickness),
SpriteEffects.None, layer);
}
}
Render class. This one isn't as important as it is just there to give some sort of polymorphism to any rendering class.
using EntityEngineV4.Engine;
using EntityEngineV4.PowerTools;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace EntityEngineV4.Components.Rendering
{
public abstract class Render : Component
{
public float Alpha = 1f;
public Color Color = Color.White;
public SpriteEffects Flip = SpriteEffects.None;
public float Layer;
public Vector2 Scale = Vector2.One;
public Vector2 Origin;
/// <summary>
/// Handy rectangle for getting the drawing position
/// </summary>
public virtual Rectangle DrawRect { get; set; }
/// <summary>
/// Source rectangle of the texture
/// </summary>
public virtual Rectangle SourceRect { get; set; }
/// <summary>
/// Bounds of the DrawRect
/// </summary>
public virtual Vector2 Bounds { get; set; }
protected Render(IComponent parent, string name)
: base(parent, name)
{
}
public override void Update(GameTime gt)
{
base.Update(gt);
}
public override void Draw(SpriteBatch sb = null)
{
base.Draw(sb);
}
}
}
If you have any questions, don't be afraid to ask!
You should divide origin by the scale.
var scale = new Vector2(Bounds.X, Thickness * Scale.Y);
var origin = new Vector2(Origin.X, Origin.Y * Bounds.Y) / scale;
sb.Draw(Assets.Pixel, new Vector2(minx, miny), null, Color * Alpha,
Angle, origin, scale, Flip, Layer);
Though I think you are doing things a bit complicated... I would use a transform matrix to transform the drawed rectangle...
public void DrawBorder( Rectangle Bounds, int ThickNess, float angle, Color color ) {
Vector3 Origin = new Vector3( Bounds.Width, Bounds.Height, 0 ) * 0.5f;
Vector3 Pos = new Vector3( Bounds.X, Bounds.Y, 0 );
Matrix transform = Matrix.CreateTranslation( -Origin )
* Matrix.CreateRotationZ( angle )
* Matrix.CreateTranslation( Pos );
Batch.Begin( SpriteSortMode.Immediate, BlendState.AlphaBlend, transform );
Bounds.X = 0;
Bounds.Y = 0;
Rectangle aux = Bounds;
Bounds.X -= ThickNess;
Bounds.Width = ThickNess;
Batch.Draw( Batch.WhiteTexture, Bounds, color );
Bounds.X = aux.Right;
Batch.Draw( Batch.WhiteTexture, Bounds, color );
Bounds.Y = aux.Top - ThickNess;
Bounds.X = aux.Left - ThickNess;
Bounds.Width = aux.Width + 2 * ThickNess;
Bounds.Height = ThickNess;
Batch.Draw( Batch.WhiteTexture, Bounds, color );
Bounds.Y = aux.Bottom;
Batch.Draw( Batch.WhiteTexture, Bounds, color );
Batch.End( );
}
Related
I can't find a way to drawing ARC between two lines. My constraint is : I have to calculate this Arc stroke points. Because i am using InkCanvas and i have to draw this arc point by point, i can't put any object to screen or canvas. So I know i can draw any arc with PATH object and use ArcSegment. With this method yes i can draw arc but it isn't stroke point on the Canvas. For this reason i cannot delete or save it.
Anyway i need calculate this arch point by point.
I have code for drawing circle on canvas like this :
Stroke GetCircleStroke(int centerX, int centerY, int radiusX, int radiusY,double angletoDraw=2.0)
{
StylusPointCollection strokePoints = new StylusPointCollection();
int numTotalSteps = 180;
for (int i = 0; i <= numTotalSteps; i++)
{
double angle = angletoDraw * Math.PI * (double)i / (double)numTotalSteps;
StylusPoint sp = new StylusPoint();
//compute x and y points
sp.X = centerX + Math.Cos(angle) * radiusX;
sp.Y = centerY - Math.Sin(angle) * radiusY;
//add to the collection
strokePoints.Add(sp);
}
Stroke newStroke = new Stroke(strokePoints);
return newStroke;
}
I can draw circle easly, but i couldn't find a way to draw an arc :(
We know center point X,Y and we know Line1 and Line2 coordinates. I just don't know what is that arc..
Could you please help me for calculate arc points like this way ?
You have a few concepts flying around like Line/Segment, Point, Circle, etc. Instead of making a mess of hard to understand code, let's try to breakdown the problem into smaller parts that are easier to digest.
You have a notion of Point, ok, lets implement one:
public struct Point2D //omitted equality logic
{
public double X { get; }
public double Y { get; }
public Point2D(double x, double y)
{
X = x;
Y = y;
}
public override string ToString() => $"{X:N3}; {Y:N3}";
}
Ok, we also have a notion of Segment or a delimitted Line:
public struct Segment2D
{
public Point2D Start { get; }
public Point2D End { get; }
public double Argument => Math.Atan2(End.Y - Start.Y , End.X - Start.X);
public Segment2D(Point2D start, Point2D end)
{
Start = start;
End = end;
}
}
And last, but not least, we have the notion of Circle:
public struct Circle2D
{
private const double FullCircleAngle = 2 * Math.PI;
public Point2D Center { get; }
public double Radius { get; }
public Circle2D(Point2D center, double radius)
{
if (radius <= 0)
throw new ArgumentOutOfRangeException(nameof(radius));
Center = center;
Radius = radius;
}
public IEnumerable<Point2D> GetPointsOfArch(int numberOfPoints, double startAngle, double endAngle)
{
double normalizedEndAngle;
if (startAngle < endAngle)
{
normalizedEndAngle = endAngle;
}
else
{
normalizedEndAngle = endAngle + FullCircleAngle;
}
var angleRange = normalizedEndAngle - startAngle;
angleRange = angleRange > FullCircleAngle ? FullCircleAngle : angleRange;
var step = angleRange / numberOfPoints;
var currentAngle = startAngle;
while (currentAngle <= normalizedEndAngle)
{
var x = Center.X + Radius * Math.Cos(currentAngle);
var y = Center.Y + Radius * Math.Sin(currentAngle);
yield return new Point2D(x, y);
currentAngle += step;
}
}
public IEnumerable<Point2D> GetPoints(int numberOfPoints)
=> GetPointsOfArch(numberOfPoints, 0, FullCircleAngle);
}
Study the implementation of GetPointsOfArch, it shouldn't be too hard to understand.
And now, to solve your problem, you would do:
var myCircle = new Circle2D(new Point2D(centerX, centerY), radius);
var line1 = ....
var line2 = ....
var archPoints = myCircle.GetPointsOfArch(number, line2.Argument, line1.Argument);
Isn't that much easier to read, follow and understand?
I have a Shape class that has 4 points (Vector2). This way, the shape can be manipulated for whatever purpose. I'm wanting to be able to fill this shape with a color of choice, but am not sure where to begin. I found references to something called "VertexPositionColorTexture" but soon realized it was for 3D space only. My program is 2D only and I am at a loss.
If someone has suggestions to get to where I am trying, I will appreciate the help.
public class CShape
{
public Vector2 PointA { get; set; }
public Vector2 PointB { get; set; }
public Vector2 PointC { get; set; }
public Vector2 PointD { get; set; }
public Color Color { get; set; }
public float GetWidth()
{
Vector2 Left, Right;
Left = PointA;
if (PointB.X < Left.X) { Left = PointB; }
if (PointC.X < Left.X) { Left = PointC; }
if (PointD.X < Left.X) { Left = PointD; }
Right = PointA;
if (PointB.X > Right.X) { Right = PointB; }
if (PointC.X > Right.X) { Right = PointC; }
if (PointD.X > Right.X) { Right = PointD; }
return (Left.X - Right.X);
}
public float GetHeight()
{
Vector2 Top, Bottom;
Top = PointA;
if (PointB.Y < Top.Y) { Top = PointB; }
if (PointC.Y < Top.Y) { Top = PointC; }
if (PointD.Y < Top.Y) { Top = PointD; }
Bottom = PointA;
if (PointB.Y > Bottom.Y) { Bottom = PointB; }
if (PointC.Y > Bottom.Y) { Bottom = PointC; }
if (PointD.Y > Bottom.Y) { Bottom = PointD; }
return (Top.Y - Bottom.Y);
}
public CShape(Vector2 Location, int Width, int Height, Color Color)
{
PointA = Location;
PointB = new Vector2(PointA.X + Width, PointA.Y);
PointC = new Vector2(PointA.X, PointA.Y + Height);
PointD = new Vector2(PointA.X + Width, PointA.Y + Height);
this.Color = Color;
}
public void Move(Vector2 Velocity)
{
PointA = new Vector2(PointA.X + Velocity.X, PointA.Y + Velocity.Y);
PointB = new Vector2(PointB.X + Velocity.X, PointB.Y + Velocity.Y);
PointC = new Vector2(PointC.X + Velocity.X, PointC.Y + Velocity.Y);
PointD = new Vector2(PointD.X + Velocity.X, PointD.Y + Velocity.Y);
}
public void Draw(SpriteBatch sb)
{
sb.Begin();
SpriteBatchEx.DrawLine(sb, PointA, PointB, Color, 3);
SpriteBatchEx.DrawLine(sb, PointB, PointD, Color, 3);
SpriteBatchEx.DrawLine(sb, PointD, PointC, Color, 3);
SpriteBatchEx.DrawLine(sb, PointC, PointA, Color, 3);
sb.End();
}
}
A common approach is simply to draw in 3D but using a 2D projection, it's really simple once setup and there are benefits in doing so (TODO find out more on that topic, there are plenty tutorials out there) !
So here's a simple code that draws squares anywhere and you can tint them:
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
namespace Game1
{
public class Game1 : Game
{
private BasicEffect _basicEffect;
private GraphicsDeviceManager _graphics;
public Game1()
{
_graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
}
protected override void Initialize()
{
_basicEffect = new BasicEffect(GraphicsDevice) {VertexColorEnabled = true};
base.Initialize();
}
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed ||
Keyboard.GetState().IsKeyDown(Keys.Escape))
Exit();
base.Update(gameTime);
}
private void DrawSquare(BasicEffect basicEffect, Vector2 position, Vector2 size, Color tint)
{
// square made out of 2 triangles
var colors = new[]
{
new VertexPositionColor(new Vector3(-0.5f, -0.5f, 0.0f), Color.White),
new VertexPositionColor(new Vector3(+0.5f, -0.5f, 0.0f), Color.White),
new VertexPositionColor(new Vector3(+0.5f, +0.5f, 0.0f), Color.White),
new VertexPositionColor(new Vector3(-0.5f, -0.5f, 0.0f), Color.White),
new VertexPositionColor(new Vector3(+0.5f, +0.5f, 0.0f), Color.White),
new VertexPositionColor(new Vector3(-0.5f, +0.5f, 0.0f), Color.White)
};
basicEffect.World = // NOTE: the correct order for matrices is SRT (scale, rotate, translate)
Matrix.CreateTranslation(0.5f, 0.5f, 0.0f)* // offset by half pixel to get pixel perfect rendering
Matrix.CreateScale(size.X, size.Y, 1.0f)* // set size
Matrix.CreateTranslation(position.X, position.Y, 0.0f)* // set position
Matrix.CreateOrthographicOffCenter // 2d projection
(
0.0f,
GraphicsDevice.Viewport.Width, // NOTE : here not an X-coordinate (i.e. width - 1)
GraphicsDevice.Viewport.Height,
0.0f,
0.0f,
1.0f
);
// tint it however you like
basicEffect.DiffuseColor = tint.ToVector3();
var passes = _basicEffect.CurrentTechnique.Passes;
foreach (var pass in passes)
{
pass.Apply();
GraphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleList, colors, 0, colors.Length/3);
}
}
/// <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);
DrawSquare(_basicEffect, new Vector2(10, 10), new Vector2(100, 100), Color.Red);
DrawSquare(_basicEffect, new Vector2(200, 200), new Vector2(50, 50), Color.Green);
base.Draw(gameTime);
}
}
}
You can still use the good'ol xblig sample :
http://xbox.create.msdn.com/en-US/education/catalog/sample/primitives
I'm very new to C#, the aim here is to edit the Time of an analog Clock by dragging it's handles. https://code.msdn.microsoft.com/windowsapps/Analog-Clock-Control-0e8ffcab#content this code has inpired me. I have three simple functions MouseDown, MouseMove and MouseUp but still I can not get Drag to work. Any suggestions please ?
public partial class Form1 : Form
{
#region Construct the clock
public Point Start { get; set; }
public Point End { get; set; }
public Form1()
{
InitializeComponent();
DoubleBuffered = true;
//Create the timer and start it
ClockTimer.Tick += ClockTimer_Tick;
ClockTimer.Enabled = true;
ClockTimer.Interval = 1;
ClockTimer.Start();
Start = p1;
End = p2;
}
#endregion
#region Update the clock
private void ClockTimer_Tick(object sender, EventArgs e)
{
Refresh();
}
private Timer ClockTimer = new Timer();
private Pen circle = new Pen(Color.Black, 2);
private Pen secondHandle = new Pen(Color.Red, 1);
private Pen minHandle = new Pen(Color.Black, 5);
private Pen hrHandle = new Pen(Color.Black, 5);
private Point p1;
private Point p2;
#endregion
#region On paint
protected override void OnPaint(PaintEventArgs pe)
{
base.OnPaint(pe);
//Clear the graphics to the back color of the control
pe.Graphics.Clear(BackColor);
//Draw the border of the clock
pe.Graphics.DrawEllipse(circle, 0, 0, 300, 300);
//Find the radius of the control by dividing the width by 2
float radius = (300 / 2);
//Find the origin of the circle by dividing the width and height of the control
PointF origin = new PointF(300 / 2, 300 / 2);
//Draw only if ShowMajorSegments is true;
if (ShowMajorSegments)
{
//Draw the Major segments for the clock
for (float i = 0f; i != 390f; i += 30f)
{
pe.Graphics.DrawLine(Pens.White, PointOnCircle(radius - 1, i, origin), PointOnCircle(radius - 21, i, origin));
}
}
//Draw only if ShowMinorSegments is true
if (ShowMinorSegments)
{
//Draw the minor segments for the control
for (float i = 0f; i != 366f; i += 6f)
{
pe.Graphics.DrawLine(Pens.Black, PointOnCircle(radius, i, origin), PointOnCircle(radius - 10, i, origin));
}
}
//Draw only if ShowSecondHand is true
if (ShowSecondhand)
//Draw the second hand
pe.Graphics.DrawLine(secondHandle, origin, PointOnCircle(radius, DateTime.Now.Second * 6f, origin));
//Draw only if ShowMinuteHand is true
if (ShowMinuteHand)
//Draw the minute hand
pe.Graphics.DrawLine(minHandle, origin, PointOnCircle(radius * 0.75f, DateTime.Now.Minute * 6f, origin));
minHandle.StartCap = LineCap.RoundAnchor;
minHandle.EndCap = LineCap.ArrowAnchor;
pe.Graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High;
pe.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
//Draw only if ShowHourHand is true
if (ShowHourHand)
//Draw the hour hand
pe.Graphics.DrawLine(hrHandle, origin, PointOnCircle(radius * 0.50f, DateTime.Now.Hour * 30f, origin));
hrHandle.StartCap = LineCap.RoundAnchor;
hrHandle.EndCap = LineCap.ArrowAnchor;
pe.Graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High;
pe.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
}
#endregion
#region On size changed
protected override void OnSizeChanged(EventArgs e)
{
base.OnSizeChanged(e);
//Make sure the control is square
if (Size.Height != Size.Width)
Size = new Size(Size.Width, Size.Width);
//Redraw the control
Refresh();
}
#endregion
#region Point on circle
private PointF PointOnCircle(float radius, float angleInDegrees, PointF origin)
{
//Find the x and y using the parametric equation for a circle
float x = (float)(radius * Math.Cos((angleInDegrees - 90f) * Math.PI / 180F)) + origin.X;
float y = (float)(radius * Math.Sin((angleInDegrees - 90f) * Math.PI / 180F)) + origin.Y;
return new PointF(x, y);
}
#endregion
#region Show Minor Segments
private bool showMinorSegments = true;
public bool ShowMinorSegments
{
get
{
return showMinorSegments;
}
set
{
showMinorSegments = value;
Refresh();
}
}
#endregion
#region Show Major Segments
private bool showMajorSegments = true;
public bool ShowMajorSegments
{
get
{
return showMajorSegments;
}
set
{
showMajorSegments = value;
Refresh();
}
}
#endregion
#region Show Second Hand
private bool showSecondHand = false;
public bool ShowSecondhand
{
get
{
return showSecondHand;
}
set
{
showSecondHand = value;
Refresh();
}
}
#endregion
#region Show Minute Hand
private bool showMinuteHand = true;
public bool ShowMinuteHand
{
get
{
return showMinuteHand;
}
set
{
showMinuteHand = value;
Refresh();
}
}
#endregion
#region Show Hour Hand
private bool showHourHand = true;
public bool ShowHourHand
{
get
{
return showHourHand;
}
set
{
showHourHand = value;
Refresh();
}
}
#endregion
public float slope
{
get
{
return (((float)p2.Y - (float)p1.Y) / ((float)p2.X - (float)p1.X));
}
}
public float YIntercept
{
get
{
return p1.Y - slope * p1.X;
}
}
public bool IsPointOnLine(Point p, int cushion)
{
float temp = (slope * p.X + YIntercept);
if (temp >= (p.Y - cushion) && temp <= (p.Y + cushion))
{
return true;
}
else
{
return false;
}
}
Point deltaStart;
Point deltaEnd;
bool dragging = false;
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left && IsPointOnLine(e.Location, 5))
{
dragging = true;
deltaStart = new Point(p1.X - e.Location.X, p1.Y - e.Location.Y);
deltaEnd = new Point(p2.X - e.Location.X, p2.Y - e.Location.Y);
}
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (dragging && deltaStart != null && deltaEnd != null)
{
p1 = new Point(deltaStart.X + e.Location.X, deltaStart.Y + e.Location.Y);
p2 = new Point(deltaEnd.X + e.Location.X, deltaEnd.Y + e.Location.Y);
this.Refresh();
}
}
private void Form1_MouseUp(object sender, MouseEventArgs e)
{
dragging = false;
}
}
I give a partial answer about translating a X, Y coordinate to an angle (in degree) based on a circle, where the 0° angle is located at the top.
(Scroll down for a compact solution)
Following the directions of typical GUI coordinates, the absolute 0,0 Point is located top left, positive X values stretch to the right and positive Y values stretch to the bottom.
In order to simplify the math, I use a virtual 0,0 point at the center of the circle, so all coordinates need to be translated to locals before calculation and to globals before actual drawing.
Coordinate overview (imagine the circle around 0; 0):
(0;-1)
(-1; 0) (0; 0) (1; 0)
(0; 1)
Now the task is for any coordinate (X; Y) to find the clock-wise angle between the line (0; 0) - (0; -1) and the line (0; 0) - (X; Y)
The circle can be divided into 4 quarter-circles, each covering a combination of signed (X; Y) values.
Quarter 1 contains the angle values 0° to 90° and is represented by positive X values and negative Y values.
Quarter 2 contains the angle values 90° to 180° and is represented by positive X values and positive Y values.
Quarter 3 contains the angle values 180° to 270° and is represented by negative X values and positive Y values.
Quarter 4 contains the angle values 270° to 360° and is represented by negative X values and negative Y values.
Note that for the corner cases 0°, 90°, 180°, 270°, 360° it doesn't really matter which of the two quarters they are assigned to.
The easiest way to understand such problems is to stick to the normal circle -> read: to normalize the X; Y coordinate to a length of 1. Additionally I go with positive values (it would also work without, but a bit differently in the + and - combinations):
var len = Math.Sqrt(X * X + Y * Y);
var xNorm = Math.Abs(X) / len;
var yNorm = Math.Abs(Y) / len;
Now, the reverse sine / cosine can be used to translate the normalized coordinates back into angle values (there's some redundancy in my calculation for the sake of simplicity and completeness):
var angleFromX = Math.Asin(xNorm) * 180.0 / Math.PI;
var angleFromY = Math.Asin(yNorm) * 180.0 / Math.PI;
Now lets apply the appropriate angle for each of the quarter circle areas
var resultAngle = 0.0;
if (quarter_1)
{
resultAngle = 0 + angleFromX;
// same as
resultAngle = 90 - angleFromY;
}
if (quarter_2)
{
resultAngle = 90 + angleFromY;
// same as
resultAngle = 180 - angleFromX;
}
if (quarter_3)
{
resultAngle = 180 + angleFromX;
// same as
resultAngle = 270 - angleFromY;
}
if (quarter_4)
{
resultAngle = 270 + angleFromY;
// same as
resultAngle = 360 - angleFromX;
}
Ofcourse, the quarter_1 - quarter_4 are pseudo-variables that represent the quarter selection as explained.
A more compact solution can be found by analyzing the different properties of the full solution.
var angleFromYAxis = Math.Asin(Y / Math.Sqrt(X * X + Y * Y)) * 180.0 / Math.PI;
var resultAngle = 0.0;
if (X >= 0)
{
resultAngle = 90 + angleFromYAxis;
}
else
{
resultAngle = 270 - angleFromYAxis;
}
Im playing around with the Platformer Starter Kit and so far I've added in horizontal and vertical "camera" movement and Im trying to add inn a parallaxing background. The problem is that after two background layers it stops showing the rest of them. Im very new to XNA and need a little help :). Heres a pic of the problem:
Heres the code. Please tell me if you need some more :)
Layer classes:
class Layer
{
public Texture2D[] Textures { get; private set; }
public float ScrollRate { get; private set; }
public Layer(ContentManager content, string basePath, float scrollRate)
{
// Assumes each layer only has 3 segments.
Textures = new Texture2D[3];
for (int i = 0; i < 3; ++i)
Textures[i] = content.Load<Texture2D>(basePath + "_" + i);
ScrollRate = scrollRate;
}
public void Draw(SpriteBatch spriteBatch, float cameraPosition, float cameraPositionYAxis)
{
// Assume each segment is the same width.
int segmentWidth = Textures[0].Width;
// Calculate which segments to draw and how much to offset them.
float x = cameraPosition * ScrollRate;
float y = ScrollRate;
int leftSegment = (int)Math.Floor(x / segmentWidth);
int rightSegment = leftSegment + 1;
x = (x / segmentWidth - leftSegment) * -segmentWidth;
spriteBatch.Draw(Textures[leftSegment % Textures.Length], new Vector2(x, -y), Color.White);
spriteBatch.Draw(Textures[rightSegment % Textures.Length], new Vector2(x + segmentWidth, -y), Color.White);
}
}
Heres the draw method in my Level.cs with my ScrollCamera (dont know if ScrollCamera has anything to do with it)
public void Draw(GameTime gameTime, SpriteBatch spriteBatch)
{
ScrollCamera(spriteBatch.GraphicsDevice.Viewport);
Matrix cameraTransformYAxis = Matrix.CreateTranslation(-cameraPosition, -cameraPositionYAxis, 0.0f);
spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.LinearClamp,
DepthStencilState.Default, RasterizerState.CullCounterClockwise, null, cameraTransformYAxis);
//added this foreach loop
foreach (var layer in layers)
{
layer.Draw(spriteBatch, cameraPosition, cameraPositionYAxis);
}
DrawTiles(spriteBatch);
Player.Draw(gameTime, spriteBatch);
foreach (Enemy enemy in enemies)
{
enemy.Draw(gameTime, spriteBatch);
}
spriteBatch.End();
}
private void ScrollCamera(Viewport viewport)
{
#if ZUNE
const float ViewMargin = 0.4f;
#else
const float ViewMargin = 0.5f;
#endif
float marginWidth = viewport.Width * ViewMargin;
float marginLeft = cameraPosition + marginWidth;
float marginRight = cameraPosition + viewport.Width - marginWidth;
const float TopMargin = 0.4f;
const float BottomMargin = 0.4f;
float marginTop = cameraPositionYAxis + viewport.Height * TopMargin;
float marginBottom = cameraPositionYAxis + viewport.Height - viewport.Height * BottomMargin;
// float maxCameraPositionYOffset = Tile.Height * Height - viewport.Height;
float CameraMovement = 0.0f;
if (Player.Position.X < marginLeft)
CameraMovement = Player.Position.X - marginLeft;
else if (Player.Position.X > marginRight)
CameraMovement = Player.Position.X - marginRight;
//Aktualizuj przesuwanie ekranu, ale zapobiegnij wyjściu poza mape
float maxCameraPosition = Tile.Width * Width - viewport.Width;
cameraPosition = MathHelper.Clamp(cameraPosition + CameraMovement, 0.0f, maxCameraPosition);
float cameraMovementY = 0.0f;
if (Player.Position.Y < marginTop) //above the top margin
cameraMovementY = Player.Position.Y - marginTop;
else if (Player.Position.Y > marginBottom) //below the bottom margin
cameraMovementY = Player.Position.Y - marginBottom;
float maxCameraPositionYOffset = Tile.Height * Height - viewport.Height;
cameraPositionYAxis = MathHelper.Clamp(cameraPositionYAxis + cameraMovementY, 0.0f, maxCameraPositionYOffset);
}
And I think thats it. Please tell me if you need some more code :)
You want to use Linear Wrapping. There's an excellent blog post on it right here. This assumes of course that your texture tiles perfect. You just simply need to to set your linear wrapping mode, code example below:
// Use this one instead!
spriteBatch.Begin(SpriteSortMode.Deferred, null, SamplerState.LinearWrap, null, null);
spriteBatch.Draw(texture, position, new Rectangle(-scrollX, -scrollY, texture.Width, texture.Height), Color.White);
spriteBatch.End();
I am currently developing a class for my XNA game whose rendering the lights on the image. At the time, i have made the source to draw my lightmap, however, the FPS is very low in my source. I know that it is brutally reduced upon looping through each pixel, however, I do not know any other way to get & set each pixel on my Texture in XNA but using the "For" statement?
Current Source:
public struct Light
{
public int Range;
public int Intensity;
public Color LightColor;
public Vector2 LightLocation;
public Light(int _Range, int _Intensity, Color _LightColor, Vector2 _LightLocation)
{
Range = _Range;
Intensity = _Intensity;
LightLocation = _LightLocation;
LightColor = _LightColor;
}
}
public class RenderClass
{
[System.Runtime.InteropServices.DllImport("User32.dll")]
public static extern bool MessageBox(IntPtr h, string S, string C, int a);
public static Texture2D RenderImage(Light[] LightLocations, Texture2D ScreenImage, Viewport v, bool ShadowBack = false)
{
Texture2D[] Images = new Texture2D[LightLocations.Count()];
int curCount = 0;
/*LOOP THROUGHT EACH LIGHT*/
foreach (Light LightLocation in LightLocations)
{
/*VARIABLES*/
Color LightColor = LightLocation.LightColor;
int Range = LightLocation.Range;
int Intensity = LightLocation.Intensity;
/*GET COLORS*/
int Width = v.Width;
int Height = v.Height;
Color[] Data = new Color[Width * Height];
ScreenImage.GetData<Color>(Data);
/*VARIABLES TO SET COLOR*/
Color[] SetColorData = new Color[Width * Height];
/*CIRCEL*/
int Radius = 15 / 2; // Define range to middle [Radius]
int Area = (int)Math.PI * (Radius * Radius);
for (int X = 0; X < Width; X++)
{
for (int Y = 0; Y < Height; Y++)
{
int Destination = X + Y * Width;
#region Light
/*GET COLOR*/
Color nColor = Data[Destination];
/*CREATE NEW COLOR*/
Vector2 MiddlePos = new Vector2(LightLocation.LightLocation.X + Radius, LightLocation.LightLocation.Y + Radius);
Vector2 CurrentLocation = new Vector2(X, Y);
float Distance;
Distance = Vector2.Distance(MiddlePos, CurrentLocation);
Distance *= 100;
Distance /= MathHelper.Clamp(Range, 0, 100);
Vector3 newColors = nColor.ToVector3();
nColor = new Color(
newColors.X,
newColors.Y,
newColors.Z,
Distance / 100);
/*SET COLOR*/
SetColorData[Destination] = nColor; // Add to array
#endregion
#region Shadow
#endregion
}
}
ScreenImage.SetData<Color>(SetColorData);
Images[curCount] = ScreenImage;
curCount++;
}
return Images[0]; // Temporarily returning the first image of the array.
}
}
As you can see, this is a slow and bad method. So I was wondering, is there a better way to get & set each pixel?
Thanks in advance, dotTutorials! =)
I think that job would be best done in a pixel shader.
You could create an Effect file that operates over one light at a time.XNA uses DX9 so you'll be limited to 128 constant registers, which I think you can use to squeeze up to three lights.
So you set your lightmap as a render target, loop through all the lights, set the constant data on your effect, render a render-target-sized quad and in your pixel shader compute your lighting equation.
In essence something like that:
// In LoadContent
RenderTarget2D lightmapRT = new RenderTarget2D(graphics.GraphicsDevice,
128,
128,
false, //No mip-mapping
SurfaceFormat.Color,
DepthFormat.Depth24);
// We now render to the lightmap in Render method
graphics.GraphicsDevice.SetRenderTarget(lightmapRT);
// Lightmap is black by default
graphics.GraphicsDevice.Clear(Color.Black);
// Use the sprite batch to draw quads with custom shader
spriteBatch.Begin(0, BlendState.Opaque, null, null, null, lightmapFx);
foreach (var light in lights)
{
// Pass the light parameters to the shader
lightmapFx.Parameters["Viewport"].SetValue(new Vector2(GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height));
lightmapFx.Parameters["Range"].SetValue(light.Range);
lightmapFx.Parameters["Intensity"].SetValue(light.Intensity);
lightmapFx.Parameters["LightColor"].SetValue(light.LightColor);
lightmapFx.Parameters["LightLocation"].SetValue(light.LightLocation);
// Render quad
spriteBatch.Draw(...);
}
spriteBatch.End();
And the FX file would look something like that:
float Range;
float Intensity;
float3 LightColor;
float2 LightLocation;
float2 Viewport;
struct VStoPS
{
float4 Position : POSITION0;
float2 TexCoord : TEXCOORD0;
};
VStoPS VS(in float4 color : COLOR0,
in float2 texCoord : TEXCOORD0,
in float4 position : POSITION0)
{
VStoPS vsout = (VStoPS)0;
// Half pixel offset for correct texel centering.
vsout.Position.xy -= 0.5;
// Viewport adjustment.
vsout.Position.xy = position.xy / Viewport;
vsout.Position.xy *= float2(2, -2);
vsout.Position.xy -= float2(1, -1);
// Pass texcoords as is
vsout.TexCoord = texCoord;
return vsout;
}
float4 PS(VStoPS psin)
{
// Do calculations here
// Here I just set it to white
return float4(1.0f, 1.0f, 1.0f, 1.0f);
}
technique Main
{
pass p0
{
VertexShader = compile vs_3_0 VS();
PixelShader = compile ps_3_0 PS();
}
}
Note that this non-tested code and probably full of errors. I leave it up to you to figure out what needs to go in the pixel shader.