Jump to content
  • Advertisement

Dirk Gregorius

  • Content Count

  • Joined

  • Last visited

  • Days Won


Dirk Gregorius last won the day on August 16

Dirk Gregorius had the most liked content!

Community Reputation

2826 Excellent


About Dirk Gregorius

  • Rank

Personal Information

  • Role
  • Interests

Recent Profile Visitors

14106 profile views
  1. Dirk Gregorius

    Entity Component Systems and Data Oriented Design

    A really good coverage of game objects can be found in the Game Engine book by J. Gregory. The book also covers the approach where you have a shallow entity hierarchy with a couple of specific derived entity types which assemble specific components. He calls this a hybrid hierarchy / component approach which is similar to what Hodgman is talking about I think. I personally like this approach as well since it much nicer to have some specific entity types for debugging. Here is a good discussion if you don't have the book. I like that it covers the difference between tool-time and run-time object models: https://www.gameenginebook.com/resources/GEA2_GameObjectSystems.pdf
  2. Dirk Gregorius

    Entity Component Systems and Data Oriented Design

    It is kind of funny how they praise the ECS over their old EC design. In practice the old EC approach was and still is very successful for them. Endless small games have shipped using Unity. In my opinion the simplicity was always one of the biggest strength of Unity, I am curious to learn how the new ECS will play out in the future for them and how many people will adapt to it. I agree with earlier posters that there is no silver bullet. As game projects grow larger organizing code and data becomes a difficult problem. ECS is just one way of doing this and I have seen both good and bad implementations of this concept. In particular if systems in ECS can depend on each other the code becomes as 'spaghetti' as in a OO entity hierarchies from my experience. So just using ECS and turning your brain off will not result in great game code.
  3. Dirk Gregorius

    Polygons and the Separating Axis Theorem

    What is the width and height of an arbitrary polygon? Width an height make sense for a rectangle, but not arbitrary polygons.
  4. Dirk Gregorius

    FBX SDK skinned animation

    It is FbxNode::EvaluateGlobalTransform(). I fixed it in my earlier post. I agree that there is indeed a lack of functional examples for animation. Most are way too complicated for such a conceptual easy problem in my opinion. The major obstacle is to understand the data you need and how to get it out of the modeling packages. This knowledge is available in game and movie companies, but is not shared very much. Probably because it is not the sexiest problem to talk about. Once you have the data, the actual algorithm is very easy. Either way, I see what I can do. Saying this, there is pretty good content on animation in general. I recommend to read the two references I posted earlier. In particular the free online course. You can even work through their assignments.
  5. Dirk Gregorius

    FBX SDK skinned animation

    There is probably a small transform issue. Personally I don't want to bake too many transforms into the bindpose. The reason is simply that I want to be able to draw the meshes for debugging without applying any transforms. So conceptually your mesh vertices need to be in model space and your bindpose transforms as well. That means you need to apply also the mesh node transform when you read the mesh vertices. I might have a mistake there since this transform is usually the identity matrix. But this is difficult to check without debugging. You can try this: FbxAMatrix OffsetMatrix = Node->EvaluateGlobalTransform() * FbxAMatrix( GeometricTranslation, GeometricRotation, GeometricScaling ); I can try to put together a demo which assembles a simple skeleton from which you can extend. Not sure when I find time for this.
  6. Dirk Gregorius

    FBX SDK skinned animation

    Here is my cluster code void RnCluster::Read( fbxsdk::FbxCluster* Cluster ) { FbxCluster::ELinkMode LinkMode = Cluster->GetLinkMode(); if ( LinkMode != FbxCluster::eNormalize ) { return; } FbxNode* Link = Cluster->GetLink(); if ( !Link ) { return; } int ControlPointIndexCount = Cluster->GetControlPointIndicesCount(); const int* ControlPointIndices = Cluster->GetControlPointIndices(); const double* ControlPointWeights = Cluster->GetControlPointWeights(); if ( ControlPointIndexCount <= 0 || !ControlPointIndices || !ControlPointWeights ) { return; } FbxAMatrix TransformLinkMatrix; Cluster->GetTransformLinkMatrix( TransformLinkMatrix ); mBindPose = RnMatrix4( TransformLinkMatrix ); mVertexCount = ControlPointIndexCount; mVertexIndices.Resize( ControlPointIndexCount ); mVertexWeights.Resize( ControlPointIndexCount ); for ( int Index = 0; Index < mVertexCount; ++Index ) { mVertexIndices[ Index ] = static_cast< int >( ControlPointIndices[ Index ] ); mVertexWeights[ Index ] = static_cast< float >( ControlPointWeights[ Index ] ); } } Here is my mesh code (only control points for clarity) void RnModelMesh::Read( FbxMesh* Mesh ) { // Geometric transform FbxNode* Node = Mesh->GetNode(); FbxVector4 GeometricTranslation = Node->GetGeometricTranslation( FbxNode::eSourcePivot ); FbxVector4 GeometricRotation = Node->GetGeometricRotation( FbxNode::eSourcePivot ); FbxVector4 GeometricScaling = Node->GetGeometricScaling( FbxNode::eSourcePivot ); FbxAMatrix OffsetMatrix( GeometricTranslation, GeometricRotation, GeometricScaling ); // Topology mTriangleCount = Mesh->GetPolygonCount(); mTriangleIndices = static_cast<int*>( rnAlloc( 3 * mTriangleCount * sizeof( int ) ) ); for ( int Index = 0; Index < mTriangleCount; ++Index ) { mTriangleIndices[ 3 * Index + 0 ] = Mesh->GetPolygonVertex( Index, 0 ); mTriangleIndices[ 3 * Index + 1 ] = Mesh->GetPolygonVertex( Index, 1 ); mTriangleIndices[ 3 * Index + 2 ] = Mesh->GetPolygonVertex( Index, 2 ); } // Vertex attributes mVertexCount = Mesh->GetControlPointsCount(); mVertexPositions = static_cast<RnVector3*>( rnAlloc( mVertexCount * sizeof( RnVector3 ) ) ); for ( int Index = 0; Index < mVertexCount; ++Index ) { FbxVector4 ControPoint = OffsetMatrix.MultT( Mesh->GetControlPointAt( Index ) ); mVertexPositions[ Index ] = RnVector3( ControPoint ); } }
  7. Dirk Gregorius

    FBX SDK skinned animation

    Vector is a 3D vector and the std:fill initializes them to zero. This is so I can correctly accumulate. I need to search through my repository to find the FBX cluster and mesh extraction code. Then I will post it here, but this might take until tomorrow. Sorry for the delay, I switched to exporting directly from Maya since I added ragdolls and cloth and wrote my own plug-ins. It didn't make sense to use FBX for me anymore. I need to dig it up if I find time later today...
  8. Dirk Gregorius

    Polygons and the Separating Axis Theorem

    I would not implement it like this. I gave a presentation on SAT maybe this is helpful. http://media.steampowered.com/apps/valve/2013/DGregorius_GDC2013.zip
  9. Dirk Gregorius

    Polygons and the Separating Axis Theorem

    It seems you are computing the penetration wrong which results in overshooting I guess. The SAT test is really simple in 2D. You only need the vertices (in some order - CCW or CW) and the transform (translation and rotation). How do you compute the MTV?
  10. Dirk Gregorius

    FBX SDK skinned animation

    No problem! I will try to help. Let's talk about the basic algorithm first, this should give you an idea what information/data you need. This should make it much easier for you to navigate the FBX file and retrieve the data you need. Finally we can talk about implementation details and common pitfalls and mistakes. There are four principal data elements 1) A transform hierarchy (skeleton) 2) An animation clip 3) One or more meshes 4) A binding for each mesh to the skeleton (cluster) The first thing you need is the skeleton. I already posted some code how to retrieve the skeleton, but let me know if you need more detail. There are two ways to describe a skeleton: hierarchical or linear. The hierarchical representation is a classic tree structure and could look something like this: struct Bone { Vector Translation; Quaternion Rotation; Matrix4x4 RelativeTransform; Matrix4x4 AbsoluteTransform; Matrix4x4 BindPose; Bone* Parent; std::vector< Bone* > Children; }; struct Skeleton { Bone* Root; }; The second option is to 'linearize' the transform hierarchy. This is usually done by traversing in DFS or BFS order. This assures that the parent bone is located before all children in the array. The linear representation can look something like this: struct Transform { Vector Translation; Quaternion Rotation; }; struct Skeleton { int BoneCount; std::vector< std::string > BoneNames; std::vector< int > BoneParents; std::vector< Transform > BoneBindPoses; }; struct Pose { Skeleton* Skeleton; std::vector< Transform > RelativeTransforms; std::vector< Transform > AbsoluteTransforms; }; Note that I skipped scale as this would only unnecessarily make things more complicated here. The next thing is the animation clip which you sample at each frame to retrieve a pose for the skeleton. A simple animation structure looks like this struct Animation { int FrameCount; float FrameRate; std::vector< Vector > TranslationKeys; std::vector< Quaternion > RotationKeys; }; So each frame you get translation and rotation of each bone and write it into Bone structure (hierarchical representation) or better you just extract the current Pose (linear representation). The next step is to deform the mesh using the new skeleton pose. This is where the skeleton/mesh binding comes in. This binding is often referred to as skin and it tells you how much a bone influences a vertex of the mesh. Again there are two ways to describe the data based on the association. E.g. either each bone knows which vertices it deforms or each vertex knows by which bone it is deformed. This looks something like this: struct Cluster { Bone* Bone; std::vector< int > VertexIndices; std::vector< float > VertexWeight; }; #define MAX_BONE_COUNT 4 struct Vertex { Vector Position; Vector Normal; // ... int BoneIndex[ MAX_BONE_COUNT ]; float BoneWeight[ MAX_BONE_COUNT ]; }; The final step is to apply the new pose to the mesh. For the cluster this looks something like this: std::vector< Vector > DeformMeshPositions( int ClusterCount, const Cluster* Clusters, const Mesh* Mesh ) { std::vector< Vector > VertexPositions; VertexPositions.resize( Mesh->VertexCount ); std::fill( VertexPositions.begin(), VertexPositions.end(), Vector::Zero ); for ( int i = 0; i < ClusterCount; ++i ) { const Cluster* Cluster = Clusters[ i ]; const Bone* Bone = Cluster->Bone; Matrix4x4 BoneTransform = Bone->AbsoluteTransform; Matrix4x4 BindPose = Bone->BindPose; Matrix4x4 Transform = BoneTransform * Inverse( BindPose ); for ( int k = 0; k < Cluster->VertexCount; ++k ) { int VertexIndex = Cluster->VertexIndex[ k ]; int VertexWeight = Cluster->VertexWeight[ k ]; VertexPositions[ VertexIndex ] += VertexWeight * Transform * Mesh->VertexPositions[ VertexIndex ]; } } return VertexPositions; }; The algorithm takes a vertex in model space into the local space of the bone at bind time. Then it moves to the new location of the bone and transforms it back to model space. Think of it as if you are attaching the vertex to the bone, then move the bone, and finally detach the vertex and release it at the new location. Finally apply a weight to this new vertex position and sum it up. HTH, -Dirk PS: IIRC the geometric transform is a concept from 3D MAX. It is a special transform which is applied to the associated shapes of a node, but it is not propagated down the hierarchy. This means you cannot just bake it into your bone transform. The simplest way to deal with it is to ignore it when retrieving the skeleton and then apply it to the mesh vertices when you read them. I look up some code and post it here. It is really simple.
  11. Dirk Gregorius

    FBX SDK skinned animation

    No, I am suggesting that you transform your mesh vertices on the CPU and then render the transformed mesh. Similar to to what you did for the skeleton. Basically I am suggesting for you to take smaller steps so succeeding in solving your problem becomes easier.
  12. Dirk Gregorius

    FBX SDK skinned animation

    In a first step I would simply export the FbxClusters and use them to deform the mesh on the CPU as I showed you in an earlier post. Once you have this working you can implement the GPU version.
  13. Dirk Gregorius

    Quaternion Class Implementation

    I would implement a basic quaternion class which follows the mathematical definition of a quaternion. If I read quaternion this is what I would expect. You still need the basic math operators very often even for unit quaternions. You can derive a Rotation class from your quaternion if you like. Havok does something similar, but with 3x3 matrices. E.g. every rotation can be expressed as a unit quaternion or orthonormal basis (with det == 1), but not every quaternion or matrix is a rotation. This is nice from an API perspective since it makes clear what you are trying to express. In particular when your clients are not super strong at math. I personally don't do this, but it is ok I think. It is somewhat the same as having a separate vector and point class. I am not a friend of this either, but some people seem to like it. Note that there is no right or wrong, but more what is your personal preference. What I don't like is to call a class quaternion, but then just implement a crippled interface for unit quaternions. This is confusing in my opinion. Non-unit quaternions have actually a use in games quite often. E.g. the quaternion derivative is defined as q' = 0.5 * w * q. Here w is a pure quaternion where the imaginary elements hold the angular velocity and the imaginary part is zero. I use this a lot in physics and IK.
  14. Dirk Gregorius

    Adding squared values

    First write your equation in the 'non-squared' form and then square it to get rid of the root. E.g. distance = sqrt( x^2 + y^2 ) < max_dist + epsilon <=> x^2 + y^2 < ( max_dist + epsilon )^2 So you see that you need to add first and then square the result instead of adding the squared values.
  15. Dirk Gregorius

    FBX SDK skinned animation

    This is a typical example of when people don't understand what they are doing. If you support non-uniform scale the matrix cannot be decomposed into TRS anymore. You would need something like: struct Transform { Vector3 Translation; Quaternion Rotation; Matrix3 Stretch; } You can use Polar Decomposition and then you can just linearly interpolate the stretch matrix. This is sloppy implemented in many engines out here. My favorite example is Unity with its 'LossyScale' element. A positive example is Granny from RAD which handles all of this properly. This is a good example of what I meant in my earlier post. Of course you can have the most general system, but it usual casts a long shadow into other engine parts and you need to understand the consequences. Many game engines still don't allow run-time bone scaling. A good compromise is uniform scale in my opinion. I suffer in particular from bad decisions here since writing rigid body transform back into a badly defined bone hierarchy is a major pain in the butt
  • Advertisement

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!