Thursday, October 22, 2009

TWO NEW SAMPLES AT XNA CCO

You may be aware of this news, but just in case, I want to mention that two great new samples are now available on the site of XNA Creators Club:

Now, what’s great about them? Glad you’ve asked.

(I) Skinned Model Extension Sample

The first sample shows you how to extend the original Skinned Model sample so that:

  • You can “move” a part of a skinned model independently of an animation sequence (like in this case, the arm and or head),
  • You can position objects relative to a specific bone (in this case, a bat), and
  • You can properly attach bounding geometry to the animated model (in this case, spheres).

The code is a great source of technical knowledge regarding the skinning technique for programmers but, thanks to some minor glitches in the skinned model, they can also learn how cumbersome and picky are the processes of (not only modeling and unwrapping but) rigging, skinning and animating a 3D model, so that it looks ok in each and every (expected) situation before going gold.

First screenshot:

As you can see here, when you move the head one of the vertices remains (almost) still, which means that either it wasn’t attached to the neck's bone at all or, if it was, then its “weights” should be readjusted.

Second screenshot:

It seems that the part of the belt marked above in orange moves along with the hip whilst the rest of the belt moves with the torso. And so, the walking animation, even if it may behave as expected, looks kinda weird.

This is likely not relevant for programmers -being the programming part the only/main purpose of the sample itself, but for indies like me that tend to develop all parts of a game “in solo mode”, its a fabulous example of some of the headaches they are going to deal with on the artistic side of things.

(II) Primitives 3D Sample

The second one presents the set of classes you will need to create a cube, a sphere, a cylinder, a torus and the famous teapot!

These primitives are usually found in most famous modeling apps, among others, like 3D Studio Max or Maya.

You may wonder: why the teapot is considered a primitive? Well, thanks to the type of powerful operations used to create it, procedurally: bezier-curve calculation! Nice one, indeed …

One suggestion for the cylinder:

In case some of you want to create the triangles that conform each cap of the cylinder so that they lay out uniformly around a centered vertex, instead of towards one side -as shown above, then you’ll have to replace the following method in the “CylinderPrimitive” class:

/// <summary>
/// Helper method creates a triangle fan to close the ends of the cylinder.
/// </summary>
void CreateCap( int tessellation, float height, float radius, Vector3 normal )
{
  // Create cap indices.
  for ( int i = 0; i < tessellation - 2; i++ )
  {
    if ( normal.Y > 0 )
    {
      AddIndex( CurrentVertex );
      AddIndex( CurrentVertex + ( i + 1 ) % tessellation );
      AddIndex( CurrentVertex + ( i + 2 ) % tessellation );
    }
    else
    {
      AddIndex( CurrentVertex );
      AddIndex( CurrentVertex + ( i + 2 ) % tessellation );
      AddIndex( CurrentVertex + ( i + 1 ) % tessellation );
    }
  }
 
  // Create cap vertices.
  for ( int i = 0; i < tessellation; i++ )
  {
    Vector3 position = GetCircleVector( i, tessellation ) * radius +
                       normal * height;
 
    AddVertex( position, normal );
  }
}

So that, instead, it looks similar to:

/// <summary>
/// Helper method that creates a cap laying out triangles around a centered vertex.
/// </summary>
void CreateCap( int tessellation, float height, float radius, Vector3 normal )
{
  // Add a vertex in the center of the cap.
  AddVertex( normal * height, normal );
 
  // Create cap indices, taking into account the centered vertex (that is why
  // we use "CurrentVertex - 1" as the first element of the corresponding index).
  for ( int i = 0; i < tessellation; i++ )
  {
    if ( normal.Y > 0 )
    {
      AddIndex( CurrentVertex - 1 );
      AddIndex( CurrentVertex + ( i + 1 ) % tessellation );
      AddIndex( CurrentVertex + ( i + 2 ) % tessellation );
    }
    else
    {
      AddIndex( CurrentVertex - 1 );
      AddIndex( CurrentVertex + ( i + 2 ) % tessellation );
      AddIndex( CurrentVertex + ( i + 1 ) % tessellation );
    }
  }
 
  // Create cap vertices.
  for ( int i = 0; i < tessellation; i++ )
  {
    Vector3 position = GetCircleVector( i, tessellation ) * radius +
                       normal * height;
 
    AddVertex( position, normal );
  }
}

Being the result:

This isn’t critical at all, but for the sake of (better) unwrapping -and even of creating more “sections” (rings) on each cap, programmatically- 2 centered vertices and a few more triangles come in handy without hurting performance.

Code on!
~Pete

 

> Link to Spanish version.