![]() |
Platforms, Frameworks & Libraries »
Windows Presentation Foundation »
Annotations
Intermediate
License: The Code Project Open License (CPOL)
Newton Game Dynamics Extensions for the WPF - The Moon Lander GameBy Leslie GodwinNewton Dynamic Extensions for the WPF |
C# (C#3.0), Windows (WinXP, Vista), .NET (.NET3.0, .NET3.5), Visual-Studio (VS2008), XAML, WPF, Dev
|
||||||||
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||
<newton:World> entry is required somewhere on your page. It will manage the Newton world instance.World.World attached property onto the viewport control binding it to the instance of the world control created right above it.<Window x:Class="Simple_Boxes_Test.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:newton="clr-namespace:System.Physics.RidgedBody.NewtonDynamics;assembly=System.Physics.RidgedBody.NewtonDynamics"
<Grid>
<newton:World x:Name="_world" />
<Viewport3D newton:World.World="{Binding ElementName=_world}">
<Viewport3D.Camera>
<PerspectiveCamera Position="-0.3,0.7,0" LookDirection="0.2,0,-1" />
</Viewport3D.Camera>
The only thing left to do now is identify the visual objects that should become collidable Bodies.ConvexBody3D object to the ModelVisual3D. This will achieve the effect of wrapping a collision mesh around the visual and any of its children.Model3D objects and children ModelVisual3D will be wrapped as well, because when it comes down to it, anything under a visual moves with and is really part of …well the visual object as a whole.<!-- Box 1 -->
<ModelVisual3D newton:World.Body="ConvexBody3D">
<ModelVisual3D.Content>
<GeometryModel3D>
<GeometryModel3D.Material>
<MaterialGroup>
<DiffuseMaterial Brush="Blue" />
<SpecularMaterial Brush="White" />
</MaterialGroup>
</GeometryModel3D.Material>
<GeometryModel3D.Geometry>
<MeshGeometry3D ...MESH DATA... >
</GeometryModel3D.Geometry>
</GeometryModel3D>
</ModelVisual3D.Content>
<ModelVisual3D.Transform>
<Transform3DGroup>
<RotateTransform3D>
<RotateTransform3D.Rotation>
<AxisAngleRotation3D Axis="0,1,0" Angle="-30" />
</RotateTransform3D.Rotation>
</RotateTransform3D>
<TranslateTransform3D OffsetZ="-5" />
</Transform3DGroup>
</ModelVisual3D.Transform>
</ModelVisual3D>
If you would like to define some properties of the ConvexBody3D class, like the Mass or the Update event, then you would have to extend the simple string initialisation to a <newton:ConvexBody3D /> XAML construct. In the following example the mass is set to “0.1” making the yellow cube much lighter than the other ones which have defaulted to 1. <ModelVisual3D>
<newton:World.Body>
<newton:ConvexBody3D x:Name="_yellowCube" Mass="0.1" ApplyForce="_yellowCube_ApplyForce"/>
</newton:World.Body>
...
I’ve also added an event to the body. Within this event I can apply a force to the body. The Newton Physics Engine is event driven. Meaning the process of calculating/simulating reality is involved and it can’t be interrupted. The calculation loop will need to call the “ok, update the bodies now” event when it’s good and ready to incorporate external input events into the system. That’s what the ApplyForce event is for. It will be called for each calculation frame when your code can influence the body (about 25 frames a second). The BodyForceEventArg can be used to apply a force to the body. The following example adds a force to the yellow cube when an arrow key is held down making it fly through the air. (And crash into stuff)// this event is called in every world update event. (About 25 frames a second)
private void _yellowCube_ApplyForce(System.Physics.RidgedBody.NewtonDynamics.Body sender,
System.Physics.RidgedBody.NewtonDynamics.BodyForceEventArgs e)
{
if (Keyboard.IsKeyDown(Key.Up))
{
e.AddForce(new Vector3D(0, 2, 0)); // add a force of 2 along the Y (up) axis
}
else if (Keyboard.IsKeyDown(Key.Left))
{
e.AddForce(new Vector3D(-1, 0, 0)); // add a force of 2 along the X (left) axis
}
else if (Keyboard.IsKeyDown(Key.Right))
{
e.AddForce(new Vector3D(1, 0, 0)); // add a force of 2 along the X (right) axis
}
}
This is how you would simulate thrust from a rocket booster. Basically thrust is a force that the booster would exert on the body in the opposite direction to the exploding fuel been expelled. This version of the AddForce method applies the force in world space meaning up is always the same. Up. No matter which way the object is pointing. There is an overloaded version of the method to apply the force in the object’s local space meaning up would be the object’s up direction. The Moon Lander game uses this overloaded version of the method.TerrianBody3D body. The difference is this body can have a concave shape (can have holes, more on that later), and it isn’t affected by gravity, nor will it move/react to something hitting it. If you had just used a normal ConvexBody3D body then it might have been pushed around by the weight the objects bouncing on and around it. Basically a Terrain is as solid as on object with infinite mass and can also take any shape. (Yes, I misspelt Terrain in the class names. I'm a knob. I know.)<!-- Plane -->
<ModelVisual3D newton:World.Body="TerrianBody3D">
<ModelVisual3D.Content>

ConvexBody3D body. It can’t have any indents or holes, so even though the original mesh might have a complex shape the collision mesh built from it would be something more…um, round, as if you took some shrink wrap and sealed the visual mesh....er, you know, for freshness. You might think that this is very limiting, but if you break your model into many smaller Model3D groups or Visuals then you can pretty much get away with anything. Newton allows a body to consist of many collision models (and even though each singular object in the group needs to be Convex, the group as a whole can take any shape) and my Extension classes uses this feature of Newton.
If you open up the “MoonLander2.blender.xaml-project” file in the “Examples\MoonLander\Files\Lander” folder with the Xaml Editor, select the “View” tab and then hit [F6], or select the “Dynamics/Start Simulation” menu you could play around with the model in real time by clicking and dragging the model around. You would them see the joints and goodies moving. If you don’t see the wire frame or joints select the “View” menu and select “Show Gizmos”.
The x:Name properties can be tied back to the @names in the blender scene nodes. The Newton Extension XAML bits would have been added to the XAML file exported from Blender and then merged into the final output by the “Xaml Editor”. (The next Article will deal with that).
The vBase Visual is part of the vMoonLander Visual which has been marked as a Newton body, so the entire Lander object has been defined as one body. (The legs has been separated out and joined with fixed joints)
The leg Visuals are hierarchically placed under the vBase Visual (The Rectangle). Each leg, even though hierarchically structured under the vBase has also been attributed as a body, but with one difference. The bodies’ Joint property has been set up to include a UserJoint with a breaking force of 120. The user joint consists of 2 Constraints which limit the child’s movement (the Leg) to the parent (the Moon Lander). The FixedLinearConstraint limites up/down/left/right movement and the FixedAngularConstraint limit any rotational movement. These 2 constraints placed under the one user joint basically make a fixed joint, fixing the child to its parent. Now, if you have played the Lander game you would have noticed the legs are very flexible. This "stiffness" is defined by the Stiffness property on the constraint. A value of 1 would mean solid (no flexing at all), and 0.01 very flexible. The FixedLinearConstraint also has a Pivot point property which is the point where it applies it’s constraining force. The Direction property is the Direction of the fixed constraining force that would be applied to hold the joint in place (Yes it's a bit tricky. The Direction is important though. The Z axis should point out along the child body. More amount this in Part 2). Both are local coordinates to the child body (the Leg). The ForceAverageSampleCount is used the specify how many samples of the acting forces to be sampled and averages the force value out over the sample count to reduce quick shocking forces which would normally rip the legs off. An example would be the initial contact with the ground. The force would be much larger than when the Lander is at rest. Before I added this property the legs broke off quite unexpectedly sometimes. One case was the thrust force. If applied to quickly it ripped the Lander's legs off. Hooray for realistic physics simulation ;-). (You'll notice I increment the force applied by the thruster. I wasn't trying to be a smart person. Just had to provide a more realistic simulation.)
<ModelVisual3D x:Name="vMoonLander" ...>
<newton:World.Body>
<newton:ConvexBody3D Mass="100" AlignToGeometryCenter="0,0,0" />
</newton:World.Body>
<ModelVisual3D x:Name="vBase">
...
<ModelVisual3D x:Name="vLeg1">
<newton:World.Body>
<newton:ConvexBody3D Mass="1" AlignToGeometryCenter="0,0,0">
<newton:Body.Joint>
<newton:UserJoint MaxForce="150">
<newton:FixedLinearConstraint Stiffness="0.01" ForceAverageSampleCount="10"
PivotPoint="0.27,0.6,-0.28" Direction="-0.23,-0.97,-0.02" />
<newton:FixedAngularConstraint Stiffness="0.01" ForceAverageSampleCount="10" />
</newton:UserJoint>
</newton:Body.Joint>
</newton:ConvexBody3D>
</newton:World.Body>
<ModelVisual3D.Content>
<Model3DGroup>
...
So...as you can see, there’s quite a few little things the Newton Extensions take care of for you, allowing you to focus more on playing games than head-bashing the keyboard trying to write lines and lines of code setting up forces and calculating joint stop forces and whot not.Viewport3D and linking the 3D viewport's matrix to the Canvas' 2D matrix so the coordinate system would be the same. (Going to any complex Matrix detail here is beyond this article. There are plenty of tutorials on the net about Matrix transformations which is another mathematical monster)private void Update2DPlane()
{
Matrix3D matrix = MathUtils.GetWorldToViewportTransform(_viewport);
_2DPlane.RenderTransform = new MatrixTransform(matrix.M11, matrix.M12, matrix.M21, matrix.M22, matrix.OffsetX, matrix.OffsetY);
Point3D point = _groundVisual.Transform.Value.Transform(new Point3D());
Canvas.SetLeft(_polyGround, point.X);
Canvas.SetTop(_polyGround, point.Y);
}
Basically what this code does is get the viewport's Matrix3D and convert it to a Matrix2D.PointCollection resource which contained a 2D collection of points defining the ground from left to right stored in the “Source\Models\land.xaml” file.
<PointCollection x:Key="landPoints">
0,0 3,4, 6,4, 6,6 10,4, 13,4 14,5 17,14 22,15 25,14, 30,10 35,13 40,6 40,0
</PointCollection>
This resource was merged into the game's resources as a merged resource in the “Source\GamePage.xaml” file.<Polygon x:Name="_polyGround"
Points="{Binding ElementName=groundCollision, Path=Points}"
Stroke="Black" StrokeThickness="0.05">
But how to make the 2D point collection stuff 3D and collidable!? I hear you ask!Viewport3D which also linked to the same points.<ModelVisual3D x:Name="_groundVisual">
<newton:World.Body>
<newton:Visual3DBody x:Name="_landBody">
<newton:Visual3DBody.CollisionMask>
<newton:CollisionCloud x:Name="groundCollision" Points="{StaticResource landPoints}" Depth="5" />
</newton:Visual3DBody.CollisionMask>
</newton:Visual3DBody>
</newton:World.Body>
But this time a linked a Visual3DBody body on to the visual and attached a CollisionCloud to the bodies' CollisionMask. The model data is this time is not coming from the ModelVisua3D itself, but rather the 2D points collection, and that was another thing. The mesh source are 2D points, and a collision mesh is 3D. This is where sneakiness comes in. Instead of automatically building a Convex collision around a visual with a ConvexBody3D which wouldn't have worked because the visual does not contain any model data, I attached a generic Visual3DBody to the visual and used a special Terrain collsion mask called a CollisionCloud with a Depth of 5. What this class does is takes a flat 2D polygon definition and builds a 3D collision mesh with the specified Depth. And Taa daa, a 3D terrain to crash into.ProjectionCamera so there is no need for Z (or depth).BodyTransforming event on the World object is called for each body after it is about to be placed at it's new position, so all I needed to do was override the translations of the Lander and keep it from coming closer to or further from the camera (Z translation) or rotating if a funny direction. Basically, only the Z rotation is allowed.void _world_BodyTransforming(Body sender, BodyTransformEventArgs e)
{
if ((sender == World.GetBody(_moonLander)))
{
e.TranslationZ = 0;
e.RotationX = 0;
e.RotationY = 0;
if ((e.TranslationX < -20) || (e.TranslationX > 20) ||
(e.TranslationY < -14) || (e.TranslationY > 30))
{
Reset();
}
}
}
Also if the Lander went out of the scene at would reset the game back to the title screen.ProjectionCamera’s Width property) when the Lander come close to the ground. The smaller the width the more it zooms into the center of the scene.Ray class which is a visual you can attach to something. You can then query it and see how far away something is. This is called Ray Casting and the Newton Extensions Framework tries to make it easy to use. null hit test result if nothing is within range. The Ray was attached to the Lander so when queried it would be relative to the Lander. Also it was set to World origin and its direction is straight down._ray = new Ray();
_ray.DirectionOrigin = ObjectOrigin.World;
_ray.Direction = new Vector3D(0, -1, 0);
_ray.RayLength = 6;
this.Children.Add(_ray);
All that’s left is to do is query the ray and set the camera zoom.// query the ray to see how far it is from the land object _moonLander.Ray.Update(_world, World.BodyFilterType.IncludeBodies, _landBody); if (_moonLander.Ray.HitResult != null) // something was hit { var newFactor = _moonLander.Ray.HitResult.IntersectFactor; if (newFactor < 0.6) // near to ground _targetZoomFactor = 1; // full zoom else if (newFactor < 1) // far from ground _targetZoomFactor = 0.5; // medium zoom } else _targetZoomFactor = 0;The zoom is limited to 3 zoom factors 0 = all the way out, 0.5 = zoomed in a bit, and 1 = full zoom. As close as ya' going to get.
Canvas with a tiled ImageBrush as the star field. It's translated a bit when the camera moves to give a better motion effect. _world.InitialiseBodies();
_world.IsPaused = false;
Currently the engine isn't totally auto start. The best thing to do it set everything up in the Xaml and tell the world to initialise its bodies.
General
News
Question
Answer
Joke
Rant
Admin
Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads.
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 7 Jul 2008 Editor: |
Copyright 2008 by Leslie Godwin Everything else Copyright © CodeProject, 1999-2010 Web19 | Advertise on the Code Project |