I've had a lot of requests to share my physics-enabled, WPF layout control for Microsoft Surface. This was demonstrated in the previous series of posts, Surface Physics Demo Part 1, Part 2, and Part 3.
So here it is! The following downloads are available:
- Physics Library (binary), .dll (zip'd), 19Kb. The physics library and layout control.
- Surface Physics Sample (install), .msi (zip'd), 1,046Kb. The sample application for demonstrating the physics library and layout control.
- Readme for Surface Physics Sample, .pdf, 1,626Kb. Readme for the sample application.
- Surface Physics Sample (source code), Visual Studio 2008 Project (zip'd), 907Kb. Source code for the the sample application.
Note that the Physics Library itself is currenly only available as a binary.
The Surface Physics Sample demonstrates many of the features supported by this library. However, to get you started with using the library in your own projects, I'll discuss how to enable basic physics for a simple ScatterView sample.
Migrating ScatterView to PhysicsView
This example demonstrates how to take a simple ScatterView sample and migrate it to make use of the physics-enabled layout control. You'll need the Microsft Surface SDK, available from the MSDN site here, and access to a Microsoft Surface or at least the Surface Simulator in the SDK. You'll also need the physics library.
First create a new Surface Application (WPF) project using the Visual Studio template inlcuded in the SDK. In SurfaceWindow1.xaml add a ScatterView to the default Grid control as follows:
<s:ScatterView Name="ScatterView1">
<s:ScatterView.ItemTemplate>
<DataTemplate>
<Border BorderBrush="White" BorderThickness="2">
<Image Source="{Binding}" Width="128" Height="96" />
</Border>
</DataTemplate>
</s:ScatterView.ItemTemplate>
</s:ScatterView>
In SurfaceWindow1.xaml.cs add an event handler for the Loaded event and add some items to the layout control:
public SurfaceWindow1()
{
InitializeComponent();
// Add handlers for Application activation events
AddActivationHandlers();
this.Loaded += new RoutedEventHandler(SurfaceWindow1_Loaded);
}
void SurfaceWindow1_Loaded(object sender, RoutedEventArgs e)
{
ScatterView1.ItemsSource = System.IO.Directory.GetFiles(@"C:\Users\Public\Pictures\Sample Pictures");
}
Run the project and if you're running the Surface Simulator you should see the following:
Figure 1. Simple ScatterView example. Note that the blank image is from a hidden file (desktop.ini) in the Sample Pictures folder.
We'll use this as a baseline to migrate from ScatterView to PhysicsView. First of all, copy the physics library (Physics.SurfaceControls.dll) into the project, and add a reference to it. Then add a namespace declaration at the top of SurfaceWindow1.xaml as follows:
xmlns:p="clr-namespace:Physics.SurfaceControls;assembly=Physics.SurfaceControls"
In SurfaceWindow1.xaml do a find and replace on s:ScatterView with p:PhysicsView and change the name from ScatterView1 to PhysicsView1. The rest of the markup remains unchanged, and the layout control should now look like this:
<p:PhysicsView Name="PhysicsView1">
<p:PhysicsView.ItemTemplate>
<DataTemplate>
<Border BorderBrush="White" BorderThickness="2">
<Image Source="{Binding}" Width="128" Height="96" />
</Border>
</DataTemplate>
</p:PhysicsView.ItemTemplate>
</p:PhysicsView>
We can now set the ItemsSource on the new layout control. The next thing we need to do is inform the physics library of the physical properties of these items. Before we do this, however, we should set up some walls defining the bounding area. The library doesn't make any assumptions here since this area doesn't have to be rectangular nor aligned with the x,y axes. In this case we'll simply add four walls, inset from the extent of the screen by 16px (remembering that a Surface application runs at 1024 x 768px). Update SurfaceWindow1_Loaded to look like this:
void SurfaceWindow1_Loaded(object sender, RoutedEventArgs e)
{
//ScatterView1.ItemsSource = System.IO.Directory.GetFiles(@"C:\Users\Public\Pictures\Sample Pictures");
// add walls
PhysicsView1.Bounds = GenerateWalls(0.5, new Rect(new Point(16, 16), new Point(1008,752)));
// set data context
PhysicsView1.ItemsSource = System.IO.Directory.GetFiles(@"C:\Users\Public\Pictures\Sample Pictures");
// add behaviours
AddBehaviours(this.PhysicsView1);
}
The methods for setting the bounds and item properties are as follows:
List<WallBody> GenerateWalls(double restitution, Rect rect)
{
return new List<WallBody> {
new WallBody { Normal = new Vector(1, 0), StartPoint = rect.TopLeft,
EndPoint = rect.BottomLeft, Restitution = restitution }, // left
new WallBody { Normal = new Vector(0, 1), StartPoint = rect.TopLeft,
EndPoint = rect.TopRight, Restitution = restitution }, // top
new WallBody { Normal = new Vector(-1, 0), StartPoint = rect.TopRight,
EndPoint = rect.BottomRight, Restitution = restitution }, // right
new WallBody { Normal = new Vector(0, -1), StartPoint = rect.BottomLeft,
EndPoint = rect.BottomRight, Restitution = restitution }}; // bottom
}
void AddBehaviours(PhysicsView physicsView)
{
Random random = new Random();
// ensure layout
physicsView.UpdateLayout();
for (int i = 0; i < physicsView.Items.Count; i++)
{
// get item
PhysicsViewItem item = physicsView.ItemContainerGenerator.ContainerFromIndex(i) as PhysicsViewItem;
// set properties
Body body = new RectangularBody
{
Width = item.DesiredSize.Width,
Height = item.DesiredSize.Height,
Density = 0.01,
InertiaConstant = 0.5,
Restitution = 0.5,
Orientation = random.NextDouble() * Math.PI * 2,
Location = new Point(random.NextDouble() * physicsView.ActualWidth,
random.NextDouble() * physicsView.ActualHeight),
};
// add item
PhysicsCanvas.SetChildBody(item, body);
}
}
That's it! The items will now collide with one another. Run the project and you should now see the following:
Figure 2. PhysicsView Items now collide with one another and do not overlap.
Regrettably I've had no time to extend this work, so a lot of features remain un-implemented at this time. Examples inlcude multi-touch manipulations on the individual items themselves, a better dampening algorithm etc. However, I hope it may still prove useful in some cases.
The source code for this example can be downloaded here.