This project has moved and is read-only. For the latest updates, please go here.

Collisions

Jul 26, 2011 at 8:06 PM

Hi Nich

Can you show a very basic example of using a collision-event?

Jul 27, 2011 at 2:37 AM

Collisions come from the geometry of a body. So to attach to them you have to declare the geometry in the Xaml like this.

 

<Rectangle Canvas.Left="80" Canvas.Top="40" Width="100" Height="100" Fill="DarkRed">
    <physics:PhysicalBox.Body>
        <physics:RectangleBody>
            <physics:RectangleBody.Geometries>
                <physics:RectangleGeometry />
            </physics:RectangleBody.Geometries>
        </physics:RectangleBody>
    </physics:PhysicalBox.Body>
</Rectangle>

If you are using a CircleBody use a CircleGeometry and the same goes for the other Body classes. Each GemoertyClass has a Collide event you can attach to just like you do with a button. The event will not run on the UI thread so you may have to make a call to Dispatcher.Invoke if you want to interact with the UI. In the CollideEventArgs is a Cancel property.

Setting this to True will cause 2 bodies to pass right though each other but you have to do it on the Collide event for each geometry. Just canceling one of them will do nothing.

 

Jul 27, 2011 at 10:21 AM

I was in Blend when I tried, and the event is'nt visible there, but in VS I found it...

But using it throws an exception:

 

System.Windows.Markup.XamlParseException occurred
  Message='Collection property 'XamlPhysics.WPF.RectangleBody'.'Geometries' is null.' Line number '36' and line position '23'.
  Source=PresentationFramework
  LineNumber=36
  LinePosition=23
  StackTrace:
       at System.Windows.Markup.XamlReader.RewrapException(Exception e, IXamlLineInfo lineInfo, Uri baseUri)
  InnerException: System.Xaml.XamlObjectWriterException
       Message='Collection property 'XamlPhysics.WPF.RectangleBody'.'Geometries' is null.' Line number '36' and line position '23'.
       Source=System.Xaml
       LineNumber=36
       LinePosition=23
       StackTrace:
            at System.Xaml.XamlObjectWriter.WriteGetObject()
            at System.Xaml.XamlWriter.WriteNode(XamlReader reader)
            at System.Windows.Markup.WpfXamlLoader.TransformNodes(XamlReader xamlReader, XamlObjectWriter xamlWriter, Boolean onlyLoadOneNode, Boolean skipJournaledProperties, Boolean shouldPassLineNumberInfo, IXamlLineInfo xamlLineInfo, IXamlLineInfoConsumer xamlLineInfoConsumer, XamlContextStack`1 stack, IStyleConnector styleConnector)
            at System.Windows.Markup.WpfXamlLoader.Load(XamlReader xamlReader, IXamlObjectWriterFactory writerFactory, Boolean skipJournaledProperties, Object rootObject, XamlObjectWriterSettings settings, Uri baseUri)
       InnerException: 

 

Here's my XAML:

 

<Rectangle x:Name="TestBox" Canvas.Top="50" Canvas.Left="500" Width="50" Height="50" Fill="Black">
    <Physics:PhysicalBox.Body>
        <Physics:RectangleBody>
            	<Physics:RectangleBody.Geometries>
               		<Physics:RectangleGeometry Collide="TellGUI" />
            	</Physics:RectangleBody.Geometries>
        </Physics:RectangleBody>
    </Physics:PhysicalBox.Body>
</Rectangle>
<TextBox Text="Hit" IsHitTestVisible="False" Name="GUI" Background="{x:Null}"></TextBox>

And the C#:

        private void TellGUI(object Sender, XamlPhysics.WPF.CollideEventArgs E)
        {
            GUI.Text = "HIT! YES!";
        }
GUI being the textbox...

 

 

Jul 28, 2011 at 12:24 AM

That's really strange because that does work in Silverlight, but I get the same thing you do WPF. It was caused by an incorrectly implemented DependencyProperty on the Geometries property. I just removed the DependencyProperty (that wasn't doing anything anyway) and it's working fine now.

There is a problem with your event handler too. For performance reasons the event doesn't run on the UI thread. It's best to do as much off of the UI thread as possible so it's left up to you to invoke the UI thread only if you need to. To work with the textbox you will need something like this.

Dispatcher.Invoke((Action)delegate()
{
    GUI.Text = "HIT! YES!";
});
Or you can also use BeginInvoke instead of Invoke which has the advantage of not blocking the thread running the physics and instead they will run in parallel. It just depends on what you need.

Aug 2, 2011 at 12:24 PM

How can I check which item it collides with?

Aug 3, 2011 at 1:43 AM

The event args has a FixtureA and FixtureB property. These represent objects from the physics engine itself. To find the UIElement that each belongs to, you have to loop though and search all of them.

var CollidedWith = new List<UIElement>();

foreach (UIElement CurElement in PhysicalBox.Children)
{
    var Body = XamlPhysics.WPF.PhysicalBox.GetBody(CurElement);
    if (Body != null)
    {
        foreach (var Geom in Body.Geometries)
        {
            if (Geom.Fixture == E.FixtureA || Geom.Fixture == E.FixtureB)
            {
                CollidedWith.Add(CurElement);
            }
        }
    }
}

This will search all of the UIEllements that are a direct child of the PhysicalBox. You can search more obviously, and if you are just testing if it collided with a particular UIElement then you could just search that one.

Aug 3, 2011 at 11:59 AM

Kind of embarrassing for me - this is basic stuff. But I can't find any matching names at all, only ID's that I assume is dynamic. It seems CurElement disregards x:Name. Or am I missing something completely?

Aug 3, 2011 at 11:37 PM

There are 2 things I can think of that could go wrong.

First, the loop is not searching the correct container. The sample I gave you searches the children of PhysicalBox. Perhaps your elements are in a TouchDrag or something else and you need to search there. In a effort to make this situation a little easier I added FindElement to PhysicalBox. So now you can do something like this.

 

PhysicalBox.FindElement(
    delegate(XamlPhysics.WPF.PhysicalBody Body)
    {
        return Body.Body == E.FixtureA.Body;
    }
);

 

Second, Name is not a property of UIElement but FrameworkElement. FrameworkElement does inherit from UIElement but the children collection is of UIElement. To check the name you will have to check the type of each child and cast it.

 

var FElement = CurElement as FrameworkElement;
                    
if (FElement != null)
{
    var Name = FElement.Name;
}

 

Aug 4, 2011 at 12:16 PM

Hmmm, no, can't figure it out. I did change the search-area. Maybe I'm doing it all wrong. I'm trying to delete the object that collide, IF it is named ellipse (alternatively if it is NOT named something else), and then create a new. The easiest way to remove is by name:

PhysicsRoot.Children.Remove(ellipse);
CreateEllipse;

So the CreateEllipse includes:

ellipse.Name = "ellipse";

This works fine for the initial ellipse created in XAML. It gets deleted and it creates a new. On next collision it does'nt get deleted. I did try to check the name cast, and it is indeed 'ellipse'. Right now I'm considering just putting all ellipses in a seperate container and instead of deleting by name, just delete all in the container. This does'nt fix the initial problem though - it still seems to me nothing is returned also with FindElement.

FixtureA is the object that initializes the script, is'nt it, and FixtureB is the foreign object?

Aug 5, 2011 at 2:56 AM

FixtureA is the object that raised the Collide event and FixtureB would be the one it collided with. I have no idea why you are not getting the name of the element. I wrote a sample program that creates ellipses at runtime and removes them when they hit a rectangle. Then another is created and it is working fine for me.

This is my xaml.

 

<Physics:PhysicalBox x:Name="PhysicalBox">
    <Physics:PhysicalBox.Clock>
        <Physics:GameLoop />
    </Physics:PhysicalBox.Clock>
    <Physics:TouchDrag x:Name="Drag" Background="Black" Width="1024" Height="768">
        <Rectangle Canvas.Top="700" Canvas.Left="0" Width="600" Height="30" Fill="White">
            <Physics:PhysicalBox.Body>
                <Physics:RectangleBody IsStatic="True">
                    <Physics:RectangleBody.Geometries>
                        <Physics:RectangleGeometry Collide="TellGUI" />
                    </Physics:RectangleBody.Geometries>
                </Physics:RectangleBody>
            </Physics:PhysicalBox.Body>
        </Rectangle>
        <Rectangle x:Name="TestBox" Canvas.Top="50" Canvas.Left="500" Width="50" Height="50" Fill="Red">
            <Physics:PhysicalBox.Body>
                <Physics:RectangleBody>
                    <Physics:RectangleBody.Geometries>
                        <Physics:RectangleGeometry />
                    </Physics:RectangleBody.Geometries>
                </Physics:RectangleBody>
            </Physics:PhysicalBox.Body>
        </Rectangle>
    </Physics:TouchDrag>
</Physics:PhysicalBox>

and C#

private void SurfaceWindow_Loaded(object sender, RoutedEventArgs e)
{
    CreateEllipse();
    PhysicalBox.Clock.Start();
    PhysicalBox.Clock.UITick += UITick;
}

List<UIElement> Add = new List<UIElement>();
List<FarseerPhysics.Dynamics.Body> Remove = new List<FarseerPhysics.Dynamics.Body>();

private void UITick(object sender, XamlPhysics.WPF.GameLoop.TickEventArgs e)
{
    Dispatcher.Invoke((Action)delegate()
    {
        if (Add.Count > 0)
        {
            foreach (var Element in Add)
            {
                Drag.Children.Add(Element);
            }
            PhysicalBox.FindNewBodies();
            Add.Clear();
        }

        foreach (var Body in Remove)
        {
            PhysicalBox.World.RemoveBody(Body);
        }
        Remove.Clear();
    });

}

private void TellGUI(object Sender, XamlPhysics.WPF.CollideEventArgs E)
{

    Dispatcher.Invoke((Action)delegate()
    {
        UIElement CollidedWith;

        CollidedWith = PhysicalBox.FindElement(
            delegate(XamlPhysics.WPF.PhysicalBody Body)
            {
                return Body.Body == E.FixtureB.Body;
            }
        );

        var Element = CollidedWith as FrameworkElement;
        if (Element != null && Element.Name == "ellipse")
        {
            CreateEllipse();
            Drag.Children.Remove(Element);
            Remove.Add(E.FixtureB.Body);
        }

    });
}

public void CreateEllipse()
{
    Ellipse ellipse = new Ellipse();
    ellipse.Name = "ellipse";
    ellipse.Fill = new SolidColorBrush(Colors.Green);
    Canvas.SetLeft(ellipse, 100);
    Canvas.SetTop(ellipse, 120);
    ellipse.Height = 50;
    ellipse.Width = 50;
    XamlPhysics.WPF.CircleBody ellipseBody = new XamlPhysics.WPF.CircleBody();
    XamlPhysics.WPF.PhysicalBox.SetBody(ellipse, ellipseBody);

    Add.Add(ellipse);
}

Instead of stopping the simulation I've attached to the clocks UITick event and do the work of adding and removing there. That removes any worries about upsetting the physics engine. I don't think it would respond well to removing something while in the middle of the collision event.

If this doesn't work for you then I have no idea what is wrong.