I’ve souped up my previous demo on Polygon Physics using Silverlight and Farseer to include two improvements. First, I’ve added a utility function to create an approximate Farseer Polygon Geometry from any Silverlight Path object. Also, I’ve added the ability to manipulate the polygons using the mouse (turns out this is pretty easy thanks to Farseer). In this blog post, I’ll explain how I made these enhancements.
[TRY THE DEMO] [DOWNLOAD THE SOURCE]
To begin, if you are new to Farseer Physics and Silverlight, you should start with my first post on Polygon Physics.
Creating a Farseer Polygon Geometry based on a Path
In the demo app, if you type out any letters (A-Z) on the keyboard, then you will see a demo of Path objects being translated to Polygon Physics objects. In the Silverlight Project, these Path objects are pulled from the ucAlphabet.xaml user control.
There is an unfortunate problem in Silverlight 2 Beta 2 (not sure if this will be resolved by release time) with getting the geometry information on a Path object. So let’s say you design a Path using Expression Blend:
<Path x:Name="pathTest" Height="65" HorizontalAlignment="Left" Margin="78.5,57.5,0,0" VerticalAlignment="Top" Width="83" Data="M98,89 L79,122 L117,119 L150,108 L161,58 L102,58 z" Fill="#FFFFFFFF" Stretch="Fill" Stroke="#FF000000"/>
…and then you later want to manipulate that Path’s geometry using code –
foreach (PathFigure figure in (pathTest.Data as PathGeometry).Figures)
{
// this won't work in Silverlight 2 Beta 2
// the Figures collection is always empty!
}
…you really can’t do the manipulation in code because the Path.Data property does not get filled.
So, we need another method of getting the Path’s shape so that we can hand it off to Farseer. One way we could do this is by translating the Path “mini-language” which defines the shape. So we could translate this part of the XAML for the Path:
Data="M98,89 L79,122 L117,119 L150,108 L161,58 L102,58 z"
I actually think this would be an ideal approach, but it’s not the one I took. Instead, I used the HitTest method to “scan” the Path for its approximate shape. I did this with a series of loops which scans each side of the Path and tests each x,y point for a hit. If the HitTest succeeds on a point, then I add that point into a list which defines the overall shape. I then give this list of points depicting the shape over to Farseer to define the Polygon Geometry.
Below is an excerpt from the method Utils.GetPointsForPath which scans the left side of the Path to get it’s outline:
// left side
for (double y = top; y < top + height; y++)
{
for (double x = left; x < left + width; x++)
{
Point ptTest = new Point(x,y);
List<UIElement> hits = path.HitTest(ptTest) as List<UIElement>;
if (hits.Contains(path))
{
Point ptHit = new Point(x / scaleX, y / scaleY);
outline.Add(ptHit);
break;
}
}
}
I simply repeat this for the bottom, right, and top sides and then I have a list of points representing the approximate outline of the Path.
Manipulating Polygons with the Mouse
This is almost a freebie and can be nearly copy/pasted from the original Silverlight/Farseer demos ported by Bill Reiss. To allow the user to grab onto a Farseer object and move it around the screen, we need to do three basic steps:
1. In the MouseDown event for the polygon, we keep track of the polygon (“sprite”) that was clicked and create a Fixed Linear Spring at the clicked point.
void sprite_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Vector2 point = new Vector2((float)(e.GetPosition(_parentCanvas).X), (float)(e.GetPosition(_parentCanvas).Y));
_pickedSprite = (sender as SpriteBase);
_pickedSprite.CaptureMouse();
if (_pickedSprite != null)
{
_mousePickSpring = ControllerFactory.Instance.CreateFixedLinearSpring(_physicsSimulator, _pickedSprite.BodyObject, _pickedSprite.BodyObject.GetLocalPosition(point), point, 10, 5);
e.Handled = true;
}
}
2. In the MouseMove event for the polygon, we adjust the attached point for the Fixed Linear Spring to the mouse move point.
void sprite_MouseMove(object sender, MouseEventArgs e)
{
if (_mousePickSpring != null)
{
Vector2 point = new Vector2((float)(e.GetPosition(_parentCanvas).X), (float)(e.GetPosition(_parentCanvas).Y));
_mousePickSpring.WorldAttachPoint = point;
e.Handled = true;
}
}
3. In the MouseUp event for the polygon, we dispose of the Fixed Linear Spring.
void sprite_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (_mousePickSpring != null && _mousePickSpring.IsDisposed == false)
{
_mousePickSpring.Dispose();
_mousePickSpring = null;
_pickedSprite.ReleaseMouseCapture();
e.Handled = true;
}
}
I also embellished with a Line showing the drag direction of the Spring from the Polygon.
Summary
Note that the “HitTest for Path Outline” method is only giving an approximate Polygon outlining the shape of the Path, but it is pretty close! I think with some further tweaks to the Utils.GetPointsForPath method, it could get even closer.