Bezier Curves and Moving Objects with the Mouse in XNA

EDN Admin

Well-known member
Joined
Aug 7, 2010
Messages
12,794
Location
In the Machine
Im having a problem with cubic Bezier curves as well as moving the points with the mouse in XNA C#. Basically, Ive figured out how to draw the curve using a 1x1 pixel image (Texture2D) but I need help figuring out how to draw the control point handles
in the same way. I also am having trouble placing the points in their correct locations on the curve (or at least thats what it looks like) and would like a little help. The people over at the App Hub forums havent been too helpful in it. I also need some
help with moving the points using the mouse (also a link to some information on creating curves with more than 4 points (for example if I want a curve with 5 points or more) that would be helpful. Im pretty decent with creating my algorithms from formulas.
Im going to post my source code as well as a screen shot of whats happening so far.

// Mouse Reader Class (Gives information on the mouses location and if the user has clicked the left mouse button.)
<pre class="prettyprint public class MouseReader
{
public Vector2 ClickLocation;
public Vector2 HoverLocation;

public MouseState PreviousState;
public MouseState CurrentState;

public void Update()
{
PreviousState = CurrentState;
CurrentState = Mouse.GetState();

HoverLocation = new Vector2(CurrentState.X, CurrentState.Y);

if (LeftClick())
ClickLocation = new Vector2(CurrentState.X, CurrentState.Y);
}

public bool LeftClick()
{
return (PreviousState.LeftButton == ButtonState.Released && CurrentState.LeftButton == ButtonState.Pressed);
}
}[/code]
<br/>
// Bezier Point Class (Holds the information on each point in the Bezier curve :: Location, Texture)
<pre class="prettyprint public class BezierPoint
{
Texture2D Image;

public Vector2 Location;

public BezierPoint(Texture2D Texture, Vector2 InitialLocation)
{
Image = Texture;
Location = InitialLocation;
}

public bool IntersectsWithMouseClick(MouseReader Mouse)
{
return Mouse.LeftClick() && (Mouse.ClickLocation.X >= Location.X &&
Mouse.ClickLocation.X <= Location.X + (Image.Width/ 2)) ||
(Mouse.ClickLocation.Y >= Location.Y &&
Mouse.ClickLocation.Y <= Location.Y + (Image.Height / 2));
}

public void Update(MouseReader Mouse)
{
if (Mouse.LeftClick() && IntersectsWithMouseClick(Mouse))
this.Location = Mouse.HoverLocation;
}

public void Draw(SpriteBatch spriteBatch)
{
spriteBatch.Draw(Image, new Vector2(Location.X - Image.Width / 2, Location.Y - Image.Height / 2),
null, Color.White, 0f, new Vector2(Image.Width / 2, Image.Height / 2), 1.0f, SpriteEffects.None, 0f);
}
}[/code]
<br/>
// Game1 Class (The main class where everything in the game happens.)
<pre class="prettyprint public class BezierCurveTest : Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
SpriteFont spriteFont;

List<BezierPoint> Points = new List<BezierPoint>();

BezierPoint Point1;
BezierPoint Point2;
BezierPoint Point3;
BezierPoint Point4;

Vector2 ObjectPoint = Vector2.Zero;
Vector2 NextObjectPoint = Vector2.Zero;
Vector2 Origin;

float Angle = 0f;

Texture2D ObjectTexture;
Texture2D PointTexture;
Texture2D PixelTexture;

List<Vector2> PointsInCurve = new List<Vector2>();

TimeSpan MoveTime = TimeSpan.FromMilliseconds(50);
TimeSpan LastMove;

MouseReader Mouse;

public Vector2 GetPoint(float t, Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3)
{
float cube = t * t * t;
float square = t * t;

Vector2 a = 3 * (p1 - p0);
Vector2 b = 3 * (p2 - p1) - a;
Vector2 c = p3 - p0 - a - b;

Vector2 r = (c * cube) + (b * square) + (a * t) + p0;

return r;
}

private float Rotate(Vector2 Location, Vector2 TurnTowards, float CurrentAngle, float TurnSpeed)
{
float x = TurnTowards.X - Location.X;
float y = TurnTowards.Y - Location.Y;

float DesiredAngle = (float)Math.Atan2(y, x);
float Difference = Wrap(DesiredAngle - CurrentAngle);

Difference = MathHelper.Clamp(Difference, -TurnSpeed, TurnSpeed);

return Wrap(CurrentAngle + Difference);
}

private float Wrap(float Radians)
{
while (Radians < -MathHelper.Pi)
Radians += MathHelper.TwoPi;

while (Radians > MathHelper.Pi)
Radians -= MathHelper.TwoPi;

return Radians;
}

private void ReversePoints()
{
BezierPoint p0 = Point1;
BezierPoint p1 = Point2;
BezierPoint p2 = Point3;
BezierPoint p3 = Point4;

Point1 = p3;
Point2 = p2;
Point3 = p1;
Point4 = p0;
}

public BezierCurveTest()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
}

protected override void Initialize()
{
IsMouseVisible = true;

Mouse = new MouseReader();

ObjectTexture = Content.Load<Texture2D>("Cross");
PointTexture = Content.Load<Texture2D>("Point");
PixelTexture = Content.Load<Texture2D>("Pixel");

Point1 = new BezierPoint(PointTexture, new Vector2(200, 80));
Point2 = new BezierPoint(PointTexture, new Vector2(120, 160));
Point3 = new BezierPoint(PointTexture, new Vector2(400, 320));
Point4 = new BezierPoint(PointTexture, new Vector2(200, 250));

Origin = new Vector2(ObjectTexture.Width / 2, ObjectTexture.Height / 2);

Points.Add(Point1);
Points.Add(Point2);
Points.Add(Point3);
Points.Add(Point4);

base.Initialize();
}

protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
spriteFont = Content.Load<SpriteFont>("Font");
}

float t = 0.0f;

protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();

if (PointsInCurve.Count > 0)
PointsInCurve.Clear();

Mouse.Update();

foreach (BezierPoint Point in Points)
Point.Update(Mouse);

if (gameTime.TotalGameTime - LastMove > MoveTime)
{
LastMove = gameTime.TotalGameTime;

t += 0.01f;

ObjectPoint = GetPoint(t, Point1.Location, Point2.Location, Point3.Location, Point4.Location);
NextObjectPoint = GetPoint(t + 0.01f, Point1.Location, Point2.Location, Point3.Location, Point4.Location);

if (t > 1.0f)
{
t = 0.0f;
ReversePoints();
}
}

Angle = Rotate(ObjectPoint, NextObjectPoint, Angle, 3f);

for (float i = 0.0f; i < 1.0f; i += 0.001f)
PointsInCurve.Add(GetPoint(i, Point1.Location, Point2.Location, Point3.Location, Point4.Location));

base.Update(gameTime);
}

protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.MidnightBlue);

spriteBatch.Begin();

if (PointsInCurve.Count > 0)
{
foreach (Vector2 PixelPoint in PointsInCurve)
spriteBatch.Draw(PixelTexture, PixelPoint, null, Color.White, 0f, Origin, 1.0f, SpriteEffects.None, 0f);
}

foreach (BezierPoint Point in Points)
Point.Draw(spriteBatch);

spriteBatch.Draw(ObjectTexture, new Vector2(ObjectPoint.X - (ObjectTexture.Width / 2), ObjectPoint.Y - (ObjectTexture.Height / 2)),
null, Color.White, 0f, Origin, 1.0f, SpriteEffects.None, 0f);
spriteBatch.DrawString(spriteFont, ((int)ObjectPoint.X).ToString() + ", " + ((int)ObjectPoint.Y).ToString(), new Vector2(30, 30),
Color.White);

spriteBatch.End();

base.Draw(gameTime);
}
}[/code]
<br/>

Screen shot of the current result:
<img alt="" src="http://social.msdn.microsoft.com/Forums/getfile/72668

The numbers on the screen are the current location of the object within the curve (just to make sure all values are moving correctly) and as you can clearly see there is something wrong going on here.

The problem with the mouse is that it will sometimes drop the point that Im dragging without me having to let go of the left mouse button. It will also grab a second point from time to time which is also frustrating. So any help with this matter is appreciated.

Thanks,
Sarah

View the full article
 
Back
Top