Looks like a great library

Aug 25, 2010 at 2:46 PM
Edited Aug 25, 2010 at 2:57 PM

I just thought I'd drop in and say this looks like a great library. I've been using JigLibX for a while in my game engine but I keep running into problem after problem. I've tweaked it quite a bit but it's still extremely unreliable, particularly on Xbox 360. Anyway I integrated JigLibX into my game engine last year before Henge3D was available and I only just became aware of it. Prior to finding this I intended re-write huge portions of JigLibX to enable multi-threaded functionality and remove as many inefficiencies as possible.

Anyway looking at Henge3D so far I'm very impressed. The demo runs well, there is a lot of useful functionality but most importantly the code is extremely clean. I haven't had a chance to truly put Henge3D through its paces however I'm dropping it into my game engine right now. I originally designed the physics system with this in mind so once I'm done there will just be a compile time switch to seamlessly swap between Henge3D or JigLibX. This is obviously a really good chance to compare the performance of the two physics libraries so I'll report back here once I'm done. Also, I've contributed some patches/updates to JigLibX in the past so hopefully I'll be able to help out here too!

Just a small note, the documentation doesn't work for some reason if the .chm file is stored too deep in many folders. I'm not sure if the issue is actually the file path length is too long or whether it's just simply nested too deep.

Anyway thanks for releasing such a great physics library!

Aug 25, 2010 at 7:36 PM
Edited Aug 28, 2010 at 3:20 PM

I had a few questions but I've forgotten most of them now, I'll update this post with questions as I remember them.

1. Is there any particular reason PhysicsManager.IsIntegratedOnUpdate exists? Doesn't GameComponent.Enabled do the same thing?

UPDATE: Ah, I see the difference. If I were to set IsIntegratedOnUpdate to false calling Update() manually still wouldn't work. Although I'm not entirely certain how that's useful.

2. Is BroadPhase.Intersect() the correct way to do arbitrary ray casting? It appears as though it will work however it doesn't return the normal at the point of intersection, which is useful information to have.

3. BodySkin will allow you to update DefaultMaterial but doesn't provide a means to update the Material of a Part, is this by design?

4. Part will allow you to update a transform via ApplyTransform() but offers no means to access the current transform, is this by design?

5. Transform supplies functionality to perform uniform scaling (which is something JigLibX was missing), are there any plans to implement non-uniform scaling, if not, will the current design inhibit such functionality being added?

 

6.

public sealed class BodySkin : Composition

Is there any chance the "sealed" could be removed from that class. I've modified it myself but I like to keep things in line with official releases. It is necessary to inherit from this class to store extra user-specific data. In my case I've created a wrapper around Henge3D (and JigLibX). So when a collision callback occurs I need some means of establishing which generic wrapper skin owns the Henge3D BodySkin. Although there might be a few ways to do this the simplest and fastest is to derive from BodySkin and store a reference in the derived class.

NOTE: The following would also obviously need to be changed to public.

internal BodySkin(RigidBody owner)

UPDATE: I see that RigidBody constructs its own Skin. I've had to change that as well so subclasses of BodySkin can be used.

 

7. Henge3D doesn't seem to have a box primitive for collision detection. This seems like a fairly common shape, is there any particular reason you chose not to include one? For the moment, what is the best way to construct one, a Polyhedron?

8. What is the correct way to create a constraint that only affects one body? An example is a constraint on a capsule to keep it upright (used for character collision).

UPDATE: For the moment I just set BodyB to null and added a check for this so that an exception isn't thrown.

 

 

Coordinator
Sep 1, 2010 at 9:39 PM

Thanks very much for your feedback, it's always appreciated. I'd be very interested to hear the results of your comparison with JigLibX.

I'll try to answer your questions here:

1. I probably just wasn't aware of GameComponent.Enabled when I designed the thing. I haven't actually made any games with XNA, so there are some areas that eluded me. If you think IsIntegratedOnUpdate is redundant, then I'd definitely be willing to remove it for the sake of simplicity.

2. BroadPhase.Intersect is what I used for mouse picking; so returning the intersected surface normal wasn't really required to accomplish that at the time. If that's something that would be useful, then I don't see any problem with adding it. I'm not sure that there's a really good reason why Intersect is a member of the BroadPhase-derived classes other than the fact that those classes have a complete list of all the compositions currently in the world. You'll notice that in the sweep-and-prune implementation, Intersect simply picks an arbitrary axis (the X-axis) merely for the purpose of getting a list of all objects in the world, the intersect-tests them all. It's a brute force approach that was never very optimized because mouse picking isn't a frequent event. It's entirely possible that a more optimized segment-casting technique would be better for different scenarios.

3. Yeah, that part was a little awkward. BodySkin derives from Composition which is a purely collision data structure with no concept of a material (materials only affect physics interactions). So that made the design of BodySkin a little strange. In the current release, you can remove a part, then add it back with a new material. I'll go ahead and add a SetMaterial method for the September release.

4. I wouldn't say it was really by design, but rather a side-effect of the way transforms work on parts. Let's take for example a SpherePart. When a transform is applied, it can modify the body center and radius to world center and radius. So the result of the transform is stored in those terms, not in an actual Transform struct. In fact, since Composition.ApplyTransform just applies the same transform to each part, it would be redundant to store it multiple times. Each part should expose its own way of retrieving the outcome of the transform. In SpherePart's case, the World field exposes the transformed center and radius.

5. Scaling is a bit of a tricky issue, especially if it's applied at runtime. The mass properties would have to be re-calculated (and I think those calculations would need to be updated for non-uniform scaling as well). I haven't really done much testing with scaling though. Scaling applied at pipeline time is simpler because the vertices can simply be scaled before any processing is done. I don't see anything preventing non-uniform scaling, but it might take some work.

6. I tend to seal classes unless there's a specific reason to un-seal them, and I think you just found one. Since RigidBody constructs its own, would it be sufficient to create a new constructor on RigidBody that takes a BodySkin you've already created? I'd be concerned about exposing a setter for BodySkin for fear of what strange effects it might have if it's changed during runtime.

7. Basically, I didn't create a box primitive because a box is just a special case of a polyhedron. I wasn't sure there was anything to be gained by having a box primitive other than perhaps a simpler API (although this could be addressed by a helper function that creates a polyhedron using box-like properties).

My reasoning is that, using separating-axis tests, testing two oriented boxes has about the same cost as, say, two oriented trapezoids (trapezoidal prisms?). Last time I used JigLibX, they didn't support polyhedra, so their box code was just a specialized instance of those same tests, with "hard-coded" test axes (face normals, and cross product of length, height, width axes of each box). Since the pipeline in Henge3D pre-computes separating axes and eliminates redundant (parallel) edge axes, the cost for doing the collision check should be roughly the same.

So, in short, I didn't add a box class because I didn't think there would be a performance benefit to it; it'd just be another set of collision functions to maintain. And those are a huuuuuge pain to get right...

8. For an example of a constraint that only affects one body, see WorldPointConstraint. I basically just use BodyA as the primary body and ignore BodyB. It could either be set to null or the same body.

 

I'll try to get the project checked into github or similar mechanism where other people can contribute more easily. Right now it's sitting in a private git repository.

Thanks again for your feedback, I'm glad to hear that people are getting some use out of this thing.

Sep 2, 2010 at 1:41 AM
Edited Sep 2, 2010 at 1:50 AM

First of all thank you very much. Being an open-source project you're in no way obligated to provide support, so I'm especially grateful to receive such detailed responses!

 

1. I totally understand, I've come from the C++ world myself (my game engine being the first C#/XNA thing I've done) and I'm always finding out that I could have done something a different way.

2. I've just been using the segment intersection functionality to check if a RigidBody is on the ground (so that they can jump). If the incline is too steep I'd like them not to be able to jump so having access to the normal makes that a lot simpler.

3. To be honest this isn't such a necessary feature, like you said I can just add and remove the Part from its owning RigidBody, I was mostly just curious here.

4. I noticed that the Parts weren't storing their transform anywhere. Because I'm wrapping around Henge3D's classes I ended up storing the current inverse transform in this class and applying that before applying the new transform. It's a bit hacky though and if you were to do it often enough you'd run into some floating point precision issues.

UPDATE: Actually I just remembered I commented out that functionality from my engine. It seemed too hacky and realistically it's unlikely people will be updating the position of Parts too often. The performance gain from baking the transform into Parts is well worth it so I wouldn't worry about it at all.

5. Yeah scaling is definitely a complex subject, I'm not even sure how well the Xbox 360 would be able to handle all the extra calculations. I'm mostly just curious in this regard because my engine doesn't make use of any special Transform class. All information is simply stored in a Matrix which of course allows arbitrary scaling (which if possible is functionality I'd like to keep).

6. Adding a new constructor that takes a BodySkin might work. However, if you do there is a bit of a chicken and egg issue. At present the BodySkin constructor currently takes RigidBody, so you wouldn't be able to construct either class.

Would a better solution be to make a protected RigidBody constructor that takes a BodySkin? Then I could create a custom RigidBody sub-class that constructs my custom BodySkin sub-class and passes it to the protected RigidBody constructor. The only issue with that is that C# doesn't support initialiser lists so it might look a bit dirty.

The other option would be to replace the BodySkin constructor's RigidBody parameter with an internal setter.

Neither solution seems particularly elegant, sorry!

7. That makes perfect sense, thanks.

8. Thanks, I see now. My problem was I set BodyB to null rather than making it the same as BodyA. As such I had to do the following in Island.ProcessVelocityConstraints():

private void ProcessVelocityConstraints()
{
	bool reverse = false;
	for (int i = 0; i < _constraints.Count; i++)
	{
		var c = _constraints[i];
		c.BodyA.IsActive = true;

		// BEN-EDIT: Check if BodyB is null, used for constraints that affect one body.
		if (c.BodyB != null)
			c.BodyB.IsActive = true;

		c.PreProcess();
	}
	for (int i = 0; i < _manager.VelocityIterations; i++)
	{
		for (int j = reverse ? _constraints.Count-1 : 0; reverse && j >= 0 || !reverse && j < _constraints.Count; j += reverse ? -1 : 1)
		{
			_constraints[j].ProcessVelocity();
		}
		reverse = !reverse;
	}
}

 

 

 

 

Here are sine other modifications that I made you may or may not be interested in...

A) I added a abstract Volume getter to Part:

        // BEN-EDIT: Added Volume property.
        /// <summary>
        /// Returns the volume of this collision part.
        /// </summary>
        public abstract float Volume { get; }

 

Then for each sub-class I override this property. Plane currently throws InvalidOperationException although returning float.PositiveInfinity may have been more appropriate.

 

My reasoning for adding this property is that my engine doesn't make use of the Henge3D pipeline as I have a custom model processor in place that supports GPU instancing of animated models. Anyway more specifically, I wanted users of my engine to be able to specify the mass of a rigid body rather than the density. So by making use of these Volume properties I'm able to set the mass and update the inertia tensor of rigid bodies on the fly. The only real issue here is that MeshPart's Volume property assumes the mesh is actually closed.

 

B) Added functionality for a RigidBody to ignore gravity.

        // BEN-EDIT: Added IgnoreGravity.
        /// <summary>
        /// Gets or sets whether gravity forces are applied to the rigid body.
        /// </summary>
        public bool IgnoreGravity { get { return _ignoreGravity; } set { _ignoreGravity = value; } }

and in GravityForce:

		/// <summary>
		/// Apply forces to bodies.
		/// </summary>
		/// <param name="bodies">The list of bodies to which forces will be applied.</param>
		public void Generate(IList<RigidBody> bodies)
		{
			Vector3 f;

			for (int i = 0; i < bodies.Count; i++)
			{
// BEN-EDIT: Added ability for bodies to ignore gravity.
if (!bodies[i].IgnoreGravity) { Vector3.Multiply(ref _gravity, bodies[i].Mass.Mass, out f); bodies[i].ApplyForce(ref f); } } }

C) RigidBody minor "fix".

		/// <summary>
		/// Construct a new rigid body.
		/// </summary>
		public RigidBody()
		{
			Mass = MassProperties.Immovable;
                        // BEN-EDIT: Added "Transform = "
			Transform = World = Transform.Identity;
			_isMovable = false;
			_contacts = new List<Constraint>();
			_constraints = new List<Constraint>();
		}

 

I try synchronise my graphical objects based on the information provided by the RigidBody class. If this isn't here when I try grab the Transform from the RigidBody class and invert it I get some nasty NaNs which wreaks havoc on graphical side of things.

 

D) "public" was related to the functionality to reposition Parts in my game engine, which is actually commented out at the moment as it was causing issues. However "output.M44 = 1.0f;" was pretty much needed to save my engine from going into a state of despair.

 

                // BEN-EDIT: Made this public
		public static void Combine(float scale, ref Vector3 position, ref Quaternion orientation,
			out Matrix output)
		{
			Matrix.CreateScale(scale, out output);
			Matrix rotation;
			Matrix.CreateFromQuaternion(ref orientation, out rotation);
			Matrix.Multiply(ref output, ref rotation, out output);
			output.M41 = position.X;
			output.M42 = position.Y;
			output.M43 = position.Z;

                        // BEN-EDIT: Needed when you start manipulating the ouput Matrix.
                        output.M44 = 1.0f;
		}

E) A new CompiledMesh constructor that takes the vertices and indices as arrays.

        // BEN-EDIT: Added a new constructor.
        public CompiledMesh(Vector3[] vertices, int[] indices)
        {
            _body = vertices;
            if (indices.Length % 3 != 0)
                throw new ArgumentException("Length of index list is not a multiple of 3.");
            if (indices.Length / 3 > ushort.MaxValue)
                throw new ArgumentException("Too many triangles in mesh.");

            _triangles = new int[indices.Length / 3][];
            _normals = new Vector3[_triangles.Length];
            int j = 0;
            for (int i = 0; i < indices.Length; i += 3)
            {
                _triangles[j] = new int[] { indices[i], indices[i + 1], indices[i + 2] };
                Vector3 v12, v13;
                Vector3.Subtract(ref _body[indices[i + 1]], ref _body[indices[i]], out v12);
                Vector3.Subtract(ref _body[indices[i + 2]], ref _body[indices[i]], out v13);
                Vector3.Cross(ref v12, ref v13, out _normals[j]);
                _normals[j++].Normalize();
            }

            BuildOctree();
        }

 

 

I don't expect you to implement the above changes. It's quite possible I may have done something stupid or that I'm just trying to use the library in way it wasn't intended. However I just thought I'd make you aware so I can see what you think. There were also a couple of other minor changes but they're more difficult for me to justify and are probably just outright incorrect.

Coordinator
Sep 2, 2010 at 1:59 AM

I think all those changes are very reasonable, I'll explore adding them probably this weekend. In the meantime, if you want to verify the fix for z-axis friction, the problem was on line 297 of ContactConstraint.cs. It should read:

impulse.X = -impulse.X; impulse.Y = -impulse.Y; impulse.Z = -impulse.Z; // Vector3.Negate(ref impulse, out impulse);

I had the - and = switched around on the last statement.

Thanks again.

Sep 2, 2010 at 2:43 AM

That's fixed the friction issue right up, thanks!