GDC2011 - Implementation and Application of the Real-Time Helper-Joint System

7,615 views

Published on

Published in: Technology
0 Comments
8 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
7,615
On SlideShare
0
From Embeds
0
Number of Embeds
3,271
Actions
Shares
0
Downloads
0
Comments
0
Likes
8
Embeds 0
No embeds

No notes for slide

GDC2011 - Implementation and Application of the Real-Time Helper-Joint System

  1. 1. GDC2011 | Jubok Kim, Choong-Hyo Kim Implementation and Application of theReal-Time Helper-Joint System Ⓒ 2011 NEXON Corporation & devCAT Studio. All Rights ReservedM2 team, Game Development Team for Project M2 in longCAT (The 3rd New Development Division in NEXON Corp.). M2 team Director is Kim, Dong-Gun | Project M2 is produced by Kim, Dong-Gun GT-R team, Engine Development Team for Project M2 and more. GT-R Team Technical Director is Jeon, Hyeong-Kyu
  2. 2. Jubok KimTechnical Director of “Mabinogi 2”Worked as a game programmer for 10 years in Nexoneias@nexon.co.krhttp://twitter.com/eiaserinnysChoong-Hyo KimTechnical Art Director of “Mabinogi 2”Worked as a 3D artist for 10 years in Nexonuc2612@nexon.co.krhttp://twitter.com/siyoskii
  3. 3. Nexon? Nexon is the first Korean online game company to direct its attention to the overseas market Cartoon Rendered-MMORPG, MabinogiA Casual MMORPGMapleStory MMO-Action Vindictus (Mabinogi:Heroes in Korea)
  4. 4. Mabinogi 2“Mabinogi 2” is the third one of the series and the official sequel to “Mabinogi.” Weare aiming very high quality bar of graphics and animation unlike its casual-lookingprequel
  5. 5. What we’re gonna talk. 1. Real-time helper-joint system Problem definition Study about helper-joints Replication of helper-joints 2. Layering the helper-joints Concept / Authoring conventions Implementation 3. Optimization 4. Conclusion Pros/Cons/Considerations **Annotation slides
  6. 6. ProblemDefinition
  7. 7. Candy-wrap problemBasic bone structure + Skinning = Twisted wristYou will see problems like this even at shoulder or other complex joint
  8. 8. Twist bone is a solutionMeasure and calculate the average angle between forearm and wrist to get therotation of the twist bone
  9. 9. Helper-JointA joint which measures the movement of other joints and settle the movement ofitself Many DCC tools provide features which are actually helper-joints Popular controllers in 3DS MAX : Position Constraint Look At Constraint Orientation Constraint…
  10. 10. So, what’s the problem left? All we’ve got helper-joints in our DCC tools already.
  11. 11. Hard to modify behaviorAll animation asset should re-baked if you change behavior of a helper-joint It becomes more and morepainful to modify behavior of the skeletal structure in the later stage of development because animation asset size is growing bigger and bigger
  12. 12. Procedural MotionHelper-joint does not react properly to procedural animation like ragdoll or full-body IK http://forum.unity3d.com/threads/51805-Additive-animations-mess-up-skinning
  13. 13. CustomizationHard to author and modify costumes if you need to bake all the animation for them.(Most of typical MMOGs have rich character customization feature) Costumes from Vindictus, Nexon
  14. 14. We needed Helper-Joint whichdoes not require pre-baked animation Let’s call it “Real-Time Helper-Joint”
  15. 15. Expected effectsExpected effects with Real-Time Helper-Joint system are… Flexibility of the content creation pipeline Smaller package size to distribute Proper reaction to procedural animation Low cost for authoring various and complex costumes
  16. 16. Problems predictedWe predicted some problems before beginning to build the system Inexperience Artists were not experienced to the concept of Helper-Joint. We did not use Helper-Joint for the previous game, and materials are very rare in Korea. Hardness of replication We did not know even if it was possible to replicate arbitrary Helper-Joint in DCC tool. There was a high chance of failing if we try to replicate too complex and unpredictable Helper-Joint. Performance Issue Even if we succeeded to replicate Helper-Joint in the game code, can we get enough performance to process many characters in the crowd scene of an MMOG?
  17. 17. Solving StrategyWe prepared clear strategies for each predicted problems before starting to buildthe system for Inexperience Technical artist decided to study about human skin changes rather than muscle movement, because Helper-Joint is about skinning for Hardness of replication Technical artist and programmer reached an agreement to maintain the list of Helper-Joints of 3DS MAX compact by choosing easy and intuitive ones for Performance Issue Programmer decided to adopt simple and rigid architecture to support multi-threading
  18. 18. Study aboutHelper-Joints
  19. 19. Where do we need it?We collected cases which is hard to set up skinning without Helper-Joint first Usually hard part to set up
  20. 20. Parts requiring twist bone
  21. 21. Parts with caution forrubbery joint problem
  22. 22. Parts affect to attractive character silhouette
  23. 23. Focused on female shoulderFemale shoulder was the best study subject with all the reviewed casesWhy shoulder?Shoulder has 3 degrees of freedomMost skinning issues get resolved if wesolve the issues around shoulder
  24. 24. Focused on female shoulderFemale shoulder was the best study subject with all the reviewed cases Why female? Expression of subtle silhouette of a female character is much more harder than tough expression of muscles of a male character
  25. 25. More early decisionsWe made some more decisions before start to set up female shoulder 3DS MAX native features only Maintenance cost of the system might grow bigger, if we develop our own Helper-Joint system (what if there is 3DS MAX upgrade?)
  26. 26. More early decisionsWe made some more decisions before start to set up female shoulder Focus on movement of skin only The system would be an over-engineered one if we have studied and replicated the movement of muscles
  27. 27. Rotation around the longitudinal axis
  28. 28. Rotation with the arm forward
  29. 29. Rotation with the arm forward
  30. 30. Rotation with the arm upward Noticeable crease of the skin surface
  31. 31. Basic ComponentsBasic components to settle movement of a Helper-Joint Animation of the base framework Nodes for Measurement Controllers for intermediate calculation
  32. 32. No detail for the rigWill not talk about it right now. There’s No Fancy technique. Keep it simple.
  33. 33. Final rigPreview with 3ds max
  34. 34. DemonstrationVideo 1This demonstration shows the our final rig with3DS MAX native Helper-Joints
  35. 35. Final list to replicateWe determined the minimal list of Helper-Joints to replicate by the study PositionConstraint LookAtConstraint OrientationConstraint ListController (S/R/T) ReactionController (S/R/T/Float) ExpressionController (S/R/T/Float) WireParameter we decided not to use WireParameter later, because it is too slow and hard to modify ExposeTM
  36. 36. Replication ofHelper-Joints
  37. 37. PositionConstraintLet’s start with the simplest Helper-Joint in the list mentioned before Simply adds and calculates the average of position targets multiplied by weight
  38. 38. PreparationTechnical artist part Sample Scene Technical artist created a scene 2~3 bones using position constraints and export its setting as an XML file by MAXScript
  39. 39. PreparationProgrammer part Interface Definition Programmer defined the simple interface for Helper-Joint and integrate it into the existing animation system struct HelperJointResult { enum Type { Scale, Rotation, Position, }; Type type; Vector4 result; }; class IHelperJoint { virtual void Process(CurrentPose& pose) = 0; virtual const HelperJointResult& GetResult() const = 0; };
  40. 40. Pseudo-codeReally easy one, huh? class PositionConstraint : public IHelperJoint { virtual void Process(const CurrentPose& pose) { Calculate the matrix T which transforms a ‘world space coordinate’ into a ‘parent space coordinate’ Calculate the average position P of the positions of target bones multiplied by weight in the world space Transform P by T and store it } virtual const HelperJointResult& GetResult() const { return P; } };
  41. 41. Pseudo-codeReally easy one, huh? class PositionConstraint : public IHelperJoint { virtual void Process(const CurrentPose& pose) { Calculate the matrix T which transforms a ‘world space coordinate’ into a ‘parent space coordinate’ Calculate the average position P of the positions of target bones multiplied by weight in the world space Transform P by T and store it } virtual const HelperJointResult& GetResult() const { return P; } };
  42. 42. Life isn’t that simple…3DS MAX calculates and stores the differences between bind pose and the weightedaverage of target positions in the bind pose, and add them to later calculationresults if ‘Keep Initial Offset’ option is checked Bone position from PositionAtConstraint Actual bind position Position adjusted by ‘Keep initial offset’ option
  43. 43. Introducing preprocess stepPreprocess() function has been added to IHelperJoint interface to handle the ‘KeepInitial Offset’ option class PositionConstraint : public IHelperJoint { virtual void Preprocess(const BindPose& bindPose) { if ‘keep initial offset’ option is cheked { Run Process() with bind pose and store the result in P_base Calculate the differences of position between P_base and actual bind pose and store the results in Offset } } virtual void Process(const CurrentPose& pose) { Calculate the matrix T which transform a ‘world space coordinate’ into a ‘parent space coordinate’ Calculate the averaged position P of the positions of target bones multiplied by weight in the world space Transform P by T and store it if ‘keep initial offset’ option is cheked Add the preprocessed Offset to P } }
  44. 44. Replicate the remainderIt is straightforward to replicate except non-intuitive options like ‘Keep Initial Offset’ Detailed implementation note of other Helper-Joints will be added in appendix
  45. 45. PositionConstraint LookAtConstraint OrientationConstraint ListController (S/R/T) ReactionController (S/R/T/Float)ExpressionController (S/R/T/Float) WireParameter ExposeTM Cool, it’s all xxxxing done All replications are done, but this is too easy, and bad premonition always proved right…
  46. 46. Unexpected problemThe order of evaluation was not a straightforward one 0 In general animation calculation process, if you calculate the 1 8 world transform in order, accurate result will be guaranteed 2 5 9 10 3 4 6 7 11 12 13 14
  47. 47. 0 Unfortunately, this simple and easy 1 8 solution does not work LookAt Constraint with Helper-Joint LookAt 2 5 9 Target 10 A Helper-Joint can refer to the bone which is not its own ancestor3 4 6 7 11 12 13 14
  48. 48. A naïve solution?The order of evaluation can be calculated by traversing Helper-Joints in postorderTo traverse Helper-Joints, we should figure out their dependency first /* Let’s add a function to the IHelperJoint interface to return the list of bone indices which the Helper-Joint referred to */ class LookAtConstraint { virtual void GetDependency( vector<BoneIndex>& dependency) { Add indices of look-at targets to dependency Add index of upnode to dependency } /* Other functions */ }
  49. 49. is not a rigid solution…We have a lot of Helper-Joints to implement, so more solid and human error proofsolution is required /* What if there is more complicated Helper-Joint like an expression controller? */ /* Furthermore, how do I validate and convince the dependency list, and evaluate that order is correct? */ class ReactorExpressionBinary { void GetDependancy(std::vector<int>& dependancies); }; class ReactorExpressionID { void GetDependancy(std::vector<int>& dependancies); }; class ReactorExpressionFunction { void GetDependancy(std::vector<int>& dependancies); }; class ReactorExpressionConstant { void GetDependancy(std::vector<int>& dependancies); };
  50. 50. Concrete solutionUse evaluation step itself as dependency traversal stepEach implementation of Helper-Joint is modified not to access theanimation pose array directly. Instead, Helper-Joint usesIHelperJointPoseSource whenever it requires pose of other bones PositionConstraint IHelperJointPoseSource LookAtConstraint GetWorldTranslation(int boneIndex) OrientationConstraint GetWorldRotation(int boneIndex) GetWorldScale(int boneIndex) PositionReactionController GetLocalTranslation(int boneIndex) GetLocalRotation(int boneIndex) RotationReactionController GetLocalScale(int boneIndex) ScaleReactionConstraint GetWorldTransform(int boneIndex) … FloatWireConstraint
  51. 51. A special implementation of IHelperJointPoseSource -EvaluationOrderBuilder is used when order of evaluation iscalculated. PositionConstraint 9, 10 OrientationConstraint Assumes that bones with index less 12 than 11 are already evaluated ScaleXYZ bone11 EvaluationOrderBuilder : public IHelperJointPoseSource PositionXYZ … 10 LookAtConstraint 24 ScaleXYZ bone12
  52. 52. Bone 9, 10 are already evaulated, so they can PositionConstraint be referenced without 9, 10 problem. OrientationConstraint 12 ScaleXYZbone11 EvaluationOrderBuilder : public IHelperJointPoseSource PositionXYZ … 10 LookAtConstraint 24 ScaleXYZbone12
  53. 53. PositionConstraint Bone 12 is not traversed yet. 9, 10 So evaluation of bone 11 is holded, and evaluation of bone 12 begins OrientationConstraint instead. 12 ScaleXYZbone11 EvaluationOrderBuilder : public IHelperJointPoseSource PositionXYZ … 10 LookAtConstraint 24 ScaleXYZbone12
  54. 54. If a bone is calculated by pre-baked animation, then it can be referenced PositionConstraint anytime. 9, 10 Let’s assume bone 24 is settled by pre-baked animation. Bone 24 is OrientationConstraint assumed to be traversed now, and 12 added to the traversal order list. ScaleXYZbone11 EvaluationOrderBuilder : public IHelperJointPoseSource PositionXYZ … 10 24 LookAtConstraint 24 ScaleXYZbone12
  55. 55. PositionConstraint 9, 10 Bone 12 is evaluated, so add it to the OrientationConstraint traversal list. 12 ScaleXYZbone11 EvaluationOrderBuilder : public IHelperJointPoseSource PositionXYZ … 10 24 12 LookAtConstraint 24 ScaleXYZbone12
  56. 56. PositionConstraint 9, 10 OrientationConstraint Now bone 11 can reference bone 12 12 safely. ScaleXYZbone11 EvaluationOrderBuilder : public IHelperJointPoseSource PositionXYZ … 10 24 12 LookAtConstraint 24 ScaleXYZbone12
  57. 57. PositionConstraint 9, 10 OrientationConstraint Bone 11 is evaluated now, so add it 12 to the list too ScaleXYZbone11 EvaluationOrderBuilder : public IHelperJointPoseSource PositionXYZ … 10 24 12 11 LookAtConstraint Bone 12 is already evaluated, so 24 evaluate bone 13 next. ScaleXYZbone12
  58. 58. DemonstrationVideo 2This demonstration shows the basic implementation result.(Shoulder, Knee, reaction to IK)
  59. 59. LayeringHelper-Joints
  60. 60. CustomizationLet’s extend the realtime Helper-Joint system to character customization MMOGs have many kinds of costumes Many of them require its own animation. And costumes can be changed at any time. Too Big to bake them out We can’t export all the costume animations. Single base animation …for all the costumes.
  61. 61. Layering skeletal structure We introduced the concept of layer of the skeletal structureMost pre-baked animation Contains Helper-Joint settings Contains Helper-Joint settings Face and separate pose clipscontain poses for this layer of shoulder, knee and others of costumesBody Layer Face & Hands Helper-Joint for body Helper-Joint for costume
  62. 62. Layering exampleBasic concept for framework layering Sets of various frameworks Nude body Hand Long Skirt Face Short Skirt Muscle Mantle Base
  63. 63. Layering exampleBasic concept for framework layering Sets of various frameworks Face Mesh Hand Long Skirt Face Short Skirt Muscle Mantle Base
  64. 64. Layering exampleBasic concept for framework layering Sets of various frameworks Robe Mesh Hand Long Skirt Face Short Skirt Muscle Mantle Base
  65. 65. Layering exampleBasic concept for framework layering Sets of various frameworks Short Skirt Hand Long Skirt Face Short Skirt Muscle Mantle Base
  66. 66. Authoring conventionWe used a naming convention to determine the layer where a bone is containedand what the bone is for _0Base{B}#Spine1 Skin Weight Layer Pre-baked/Runtime Name _ 0Base {B} # Spine1 _ 0Base 1Face {B} : Baked + 1Hand 1Muscle {D} : Deformable ~ 2Costume 2Tool
  67. 67. Authoring conventionWe used a naming convention to determine the layer where a bone is containedand what the bone is for _0Base{B}#Spine1 Skin Weight Layer Pre-baked/Runtime Name _ 0Base {B} # Spine1 _ 0Base 1Face {B} : Baked + 1Hand 1Muscle {D} : Deformable ~ 2Costume 2Tool
  68. 68. Authoring conventionWe used a naming convention to determine the layer where a bone is containedand what the bone is for _0Base{B}#Spine1 Skin Weight Layer Pre-baked/Runtime Name _ 0Base {B} # Spine1 _ 0Base 1Face {B} : Baked + 1Hand 1Muscle {D} : Deformable ~ 2Costume 2Tool
  69. 69. Authoring conventionWe used a naming convention to determine the layer where a bone is containedand what the bone is for _0Base{B}#Spine1 Skin Weight Layer Pre-baked/Runtime Name _ 0Base {B} # Spine1 _ 0Base 1Face {B} : Baked + 1Hand 1Muscle {D} : Deformable ~ 2Costume 2Tool
  70. 70. Authoring conventionA layer should not be cyclic, or bi-directional (Frm Layer 0) _Layer0{B}#AAA (Layer 0 + Layer 1) (Animation Data) _Layer0{B}#BBB _Layer0{B}#AAA _Layer0{B}#AAA _Layer0{B}#BBB _Layer0{B}#BBB _Layer1{B}#CCC _Layer1{B}#CCC (Frm Layer 1) _Layer0{B}#BBB _Layer1{B}#CCC
  71. 71. Export and importExporting from and importing back into the DCC tool is done layer by layer AAA.max AAA.Mesh AAA.Layer0.Framework AAA.Layer0.Animation Pre-baked Layer “I’ll use these layers.” AAA.Layer1.Framework AAA.Layer1.HelperJointSetting AAA.Layer2.Framework AAA.Layer2.HelperJointSetting
  72. 72. Framework templateWe maintained a list of typical framework settings as templates Predefined framework templates Popular fantasy costumes like skirts, and robes Delivered to the outsourcing company Low cost for modification Behavior of the procedural layers can be changed at any time with low cost Just re-export the helper-joint setting for the layer Great benefit to quality control
  73. 73. ImplementationAnimation pose player and Helper-Joint processor should be modified to handleframework layers Framework class is introduced A Framework instance maintains current stack of layers and bind poses AnimationPose class is introduced A AnimationPose instance contains current bone poses and flows through each animation module.
  74. 74. One animation player for one layerThere can be multiple instances of animation players but there is one AnimationPose instance only. Eachanimation player matches the bone name to the framework to get the indices to fill the final pose array inAnimationPose instanceOne Helper-Joint processor for one layerThere can be multiple instances of Helper-Joint processors and works similar to an animation player.Actually, the animation player and the Helper-Joint processor are implemented as a node in the generalmodule based on the animation system Pose Pose SRT (Layer0) SRT (Layer0) SRT SRT AnimationPlayer SRT SRT HelperJointProc Bind pose Bind Pose AdvanceProcessor HelperJointLayerProc0Logic Bind pose Bind Pose ILayerPlayer HelperJointLayerProc SRT (Layer1) SRT (Layer1)1Base ILayerPlayer HelperJointLayerProc SRT SRT ILayerPlayer2Hand SRT SRT Bind pose SRT (Layer3) Bind pose SRT
  75. 75. DemonstrationVideo 3This demonstration shows several costumeswhich have their own Helper-Joint setting
  76. 76. Optimization
  77. 77. Unexpected problem againNot like that we expected, multi-threading was an easy part Multithreading was an easy part Data parallel approach and Intel TBB did all the work Thanks, Intel! World transform calculation Performance loss on scale calculation
  78. 78. World transform calculationUsually, multiplying local transform with parent’s world transform occurs once for abone in a frame When the The Helper-Joint evaluation calculation result of a process updates local poses Helper-Joint is applied… of bones almost randomly To make things worse, implementation of Helper- Joints require world transform of other bones in many cases then all world transform of entire sub-tree are invalidated
  79. 79. Typical character framework contains 100~200 bones So performance hit will be drastic ifyou re-calculate world transform of all bones whenever world transform of a bone is referenced
  80. 80. If a world transform for a bone is calculated, we would set the dirtymask for the boneIf local bone pose of a bone is updated, we would reset the dirtymask of the bone and its descendants 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 Local pose of bone 8 is 0 0 1 8 updated, then reset the dirty 234567 9 10 11 12 13 14 mask of the bone and its descendants 01 08 34 2 5 9 12 13 14012 0 8 12 3 4 6 7 10 11 13 14
  81. 81. Dirty maskThis performance problem can be simply solved if each entry in the world transformarray has dirty mask 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 All entries in the world transform array have 0 Ancestor of bone 8 is 0 0 dirty mask 234567 1 only 8 9 10 11 12 13 14 Decendants of All bones have list of bone 8 is 9~14 their own ancestors 01 34 2 5 9 12 08 13 14 and descendants. These lists are maintained by Framework instance012 0 8 12 3 4 6 7 10 11 13 14
  82. 82. If world transform of a bone is referred when its dirty mask is reset,we would check and calculate world transforms of its ancestors andthe bone itselfIt can be done recursively without list of ancestors and descendants, but there is performancehit by function call in that case 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 0 0 234567 1 8 9 10 11 12 13 14 When we need world 01 08 transform of bone 12, then 34 2 5 9 12 13 14 update ancestors of bone 12 and bone 12 itself only012 0 8 12 3 4 6 7 10 11 13 14
  83. 83. Improved, but not enoughThe result of performance measurement showed that much more time consumedthan expected time from the complexity of Helper-Joint calculation code L2 cache miss was the cause Fine measurement revealed that L2 cache miss is the cause Helper-Joint is evaluated in random order, and references to world transform of other bones occur in random and sporadic manner
  84. 84. Access should be gatheredAccesses to memory should be predicted and gathered Calculating the order of reference The order of referencing to world transform is calculated in similar way to the way order of evaluation of Helper-Joint is calculated Ready world transforms in advance Calculate world transform in the order before evaluating a Helper-Joint so the world transform can be loaded in L2 cache Prefetch worked well many L2 cache hot spots have been removed by this approach by combination with some prefetch instructions
  85. 85. Performance loss on scaleScale transform is just one multiplication step and does not impact the performancein usual animation system, but again, it is a different story from the real-timeHelper-Joint system Getting SRT from a matrix is expensive too Many Helper-Joint requires inverse matrix of world transform of arbitrary bone Calculate inverse transform of a matrix is expensive.
  86. 86. But non-trivial scale is rareIf scale is trivial, inverse transform calculation becomes getting transposeGetting SRT from a matrix becomes much simpler too R-1 = RT SRT(M) = { (1, 1, 1), QuaternionFromMatrix(M), (M[3][1], M[3][2], M[3][3] }
  87. 87. Scale maskScale mask which is similar to world transform dirty mask introduced intoAnimationPose and array of world transform We would set scale mask for the bone if local pose of a bone contains non-trivial scale Getting SRT from a world transform is replaced by matrix into quaternion conversion if scale mask is not set. Matrix inversion is replaced by matrix transpose if scale mask is not set.
  88. 88. Result is goodPerformance gain comes from the fact that animation containing non-trivial scale isvery rare Further optimization is possible for the cases with non-trivial scale. For example, code calculating x component transformed by inverse of parent’s world transform can be reduced into one line.
  89. 89. Performance chartHere goes actual performance chart for current implementation Avg count in Time per HelperJoint Total Clocks No.of Calls Time per Call (ms) one char one char (ms)
  90. 90. Conclusion
  91. 91. pros. Animation asset maintenance became easier Polishing the behaviors of Helper-Joint became easier Authoring high quality costumes became easier Variance in monsters can be introduced easily with additional Helper-Joint layer Package size to distribute got reduced
  92. 92. cons. Riggers should get more familiar to the concept of real- time Helper-Joint system Relation between costume and Helper-Joint layer introduces another complexity in asset definition management Performance hit is not ignorable
  93. 93. Considerations Do enough research before you begin to implement. Avoid over design Make the best use of native features of DCC tools Maintenance will be hard if there are too many in-house tools Runtime Helper-Joint system has a very different runtime execution path Integrating it into the existing system needs careful consideration
  94. 94. Considerations Set a standard of expression and performance which artist and programmer both can reach an agreement
  95. 95. Jubok Kimeias@nexon.co.kr | twitter.com/eiaserinnys (Korean)Choong-Hyo Kimuc2612@nexon.co.kr | twitter.com/siyoskii (Korean) Q&A
  96. 96. Thank you
  97. 97. ADDITIONAL NOTEReplicating 3DS MAX Native ControllersLookAtConstraint | OrientationConstraint | ReactionController | ExpressionController&FloatWire | XYZ&ListControllers Ⓒ 2011 NEXON Corporation & devCAT Studio. All Rights Reserved M2 team, Game Development Team for Project M2 in longCAT (The 3rd New Development Division in NEXON Corp.). M2 team Director is Kim, Dong-Gun | Project M2 is produced by Kim, Dong-Gun GT-R team, Engine Development Team for Project M2 and more. GT-R Team Technical Director is Jeon, Hyeong-Kyu
  98. 98. Make it look at targetsLookAtConstraint first calculates the weighted average of look-at targets, thenmakes the constrainted bone to point the position
  99. 99. Relatively easy to replicateLookAtConstraint works in a very clear and intuitive manner, and the first version ofreplication is also simple void LookAtConstraint::Process(/* Arguments omitted */) { Calculate the transform T doing [World space]→[Parent space] Tranform the positon of constrainted bone into its parental space with T Calculated the weighted average position of targets P Transform the P with T, and calculate the forward vector F Get the upward vector U by transform the upward target with T Calculate the result matrix from F and U, and convert it into quaternion }
  100. 100. Prepare the basis to look atLookAtConstraint acually works in the world space, but results in local spaceIt is favorable to do the calcuation with the inverse of parent’s world transform void LookAtConstraint::Process(/* Arguments omitted */) { Calculate the transform T doing [World space]→[Parent space] Tranform the positon of constrainted bone into its parental space with T Calculated the weighted average position of targets P Transform the P with T, and calculate the forward vector F Get the upward vector U by transform the upward target with T Calculate the result matrix from F and U, and convert it into quaternion }
  101. 101. Finding the forward vectorCalculate the weighted average position of targets, and transform it with the basistransform T to find the forward vector void LookAtConstraint::Process(/* Arguments omitted */) { Calculate the transform T doing [World space]→[Parent space] Tranform the positon of constrainted bone into its parental space with T Calculated the weighted average position of targets P Transform the P with T, and calculate the forward vector F Get the upward vector U by transform the upward target with T Calculate the result matrix from F and U, and convert it into quaternion }
  102. 102. Finding the upward vectorSimple and straight? void LookAtConstraint::Process(/* Arguments omitted */) { Calculate the transform T doing [World space]→[Parent space] Tranform the positon of constrainted bone into its parental space with T Calculated the weighted average position of targets P Transform the P with T, and calculate the forward vector F Get the upward vector U by transform the upward target with T Calculate the result matrix from F and U, and convert it into quaternion }
  103. 103. …takes a lot of careThere are so many options for upnode control
  104. 104. Finding the result rotationWe’ve get the forward and upward vectors, so cross them to get the result rotationDoes this concludes the replication of LookAtConstraint? void LookAtConstraint::Process(/* Arguments omitted */) { Calculate the transform T doing [World space]→[Parent space] Tranform the positon of constrainted bone into its parental space with T Calculated the weighted average position of targets P Transform the P with T, and calculate the forward vector F Get the upward vector U by transform the upward target with T Calculate the result matrix from F and U, and convert it into quaternion }
  105. 105. Some options left in rolloutWow, the “Keep Initial Offset” checkbox AGAIN!
  106. 106. Preserving the differenceThe “Keep Initial Offset” option of LookAtConstraint works similar to the sameoption of PositionConstaint The bone which is constrainted by LookAtConstraint
  107. 107. …concludes the replicationThe final replication which calculates and preserves the difference by “Keep InitialOffset” void LookAtConstraint::Preprocess() { Preserve the difference of rotation between the bind pose and the result with Process() only when “Keep Initial Offset” is checked } void LookAtConstraint::Process(/* Arguments Omitted */) { /* Ommitted */ Add the preserved rotation O to F when the “Keep Initial Offset” is checked Calculate the result matrix from F and U, and convert it into quaternion }
  108. 108. ADDITIONAL NOTEReplicating 3DS MAX Native ControllersLookAtConstraint | OrientationConstraint | ReactionController | ExpressionController&FloatWire | XYZ&ListControllers Ⓒ 2011 NEXON Corporation & devCAT Studio. All Rights Reserved M2 team, Game Development Team for Project M2 in longCAT (The 3rd New Development Division in NEXON Corp.). M2 team Director is Kim, Dong-Gun | Project M2 is produced by Kim, Dong-Gun GT-R team, Engine Development Team for Project M2 and more. GT-R Team Technical Director is Jeon, Hyeong-Kyu
  109. 109. Takes the average rotationOrientationConstraint calculates the weighted average of rotations of targets
  110. 110. The simplest one ever?It even looks like that it is simpler than PositionConstraint void OrientationConstraint::Process(/* Omitted */) { Get local rotations of targets Calculate the weighted average of the rotations }
  111. 111. No, it is not a simple oneIt has the “Keep initial offset” checkbox too, and a very complex option whichcontrols blend method void OrientationConstraint::Preprocess() { Preserve the difference of rotation D between the bind pose and the result with Process() only when “Keep Initial Offset” is checked } void OrientationConstraint::Process(/* Omitted */) { if (“Transform rule” is “Local to Local”) Preserve the local rotations of targets else // “World to World” Transform the rotation of targets to the parental space and preserve them Calculate the weighted average of preserved rotations Add preserved difference D to the average when “Keep initial offset” is checked Preserved the result }
  112. 112. Usual “Keep Initial Offset”This option works in the same manner to LookAtConstraint void OrientationConstraint::Preprocess() { Preserve the difference of rotation D between the bind pose and the result with Process() only when “Keep Initial Offset” is checked } void OrientationConstraint::Process(/* Omitted */) { if (“Transform rule” is “Local to Local”) Preserve the local rotations of targets else // “World to World” Transform the rotation of targets to the parental space and preserve them Calculate the weighted average of preserved rotations Add preserved difference D to the average when “Keep initial offset” is checked Preserved the result }
  113. 113. Strange “Transform Rules”This option betrays the usual concept of animation blending void OrientationConstraint::Preprocess() { Preserve the difference of rotation D between the bind pose and the result with Process() only when “Keep Initial Offset” is checked } void OrientationConstraint::Process(/* Omitted */) { if (“Transform rule” is “Local to Local”) Preserve the local rotations of targets else // “World to World” Transform the rotation of targets to the parental space and preserve them Calculate the weighted average of preserved rotations Add preserved difference D to the average when “Keep initial offset” is checked Preserved the result }
  114. 114. Blending what you “see”This option is needed because target bones generally have different parents, soblended result can be very different to the result which an artist expects 15 degree rotated (30 degree in world) Child “Local→Local” case A bone which is (15 + 15) / 2 = 15 constrainted by World→World case Parent OrientationConstraint (15 + 30) / 2 = 22.5 degree with “World→World” transform rule 15 degree rotated (15 degree in world)
  115. 115. ADDITIONAL NOTEReplicating 3DS MAX Native ControllersLookAtConstraint | OrientationConstraint | ReactionController | ExpressionController&FloatWire | XYZ&ListControllers Ⓒ 2011 NEXON Corporation & devCAT Studio. All Rights Reserved M2 team, Game Development Team for Project M2 in longCAT (The 3rd New Development Division in NEXON Corp.). M2 team Director is Kim, Dong-Gun | Project M2 is produced by Kim, Dong-Gun GT-R team, Engine Development Team for Project M2 and more. GT-R Team Technical Director is Jeon, Hyeong-Kyu
  116. 116. Input-Graph-OutputAll *ReactionManagers calculate the output value through the plotted graph This UI is VEEEEEEERY unintuitive DO SOMETHING, AUTODESK!
  117. 117. Too many input types!You need to standarize the way to retrieve all the possible inputs Local Rotation X/Y/Z World Rotation X/Y/Z Local Position X/Y/Z World Position X/Y/Z Distance
  118. 118. Abstract the input at firstThe main function of the *ReactionManager is the “float →Graph →result”transformation DistanceEvaluator PositionEvaluator RotationEvaluator
  119. 119. PositionEvaluatorThis class handles input types “Local Position X/Y/Z” and “World Position X/Y/Z” const float PositionEvaluator::Process(/* Omitted */) { if (demands the position in local space) if (relative to the parent bone) return (local translation)[channel] else if (relative to a reference bone) transform into the space of the reference bone return (transformed translation)[channel] else // reference is undefined return (world translation)[channel] else return (world translation)[channel] }
  120. 120. Relative to a reference?Relative to the parent bone, and world space is very intuitiveBut the correct replication requires translation relative to the reference bone const float PositionEvaluator::Process(/* Omitted */) { if (demands the position in local space) if (relative to the parent bone) return (local translation)[channel] else if (relative to a reference bone) transform into the space of the reference bone return (transformed translation)[channel] else // reference is undefined return (world translation)[channel] else return (world translation)[channel] }
  121. 121. …comes from helper-boneHelper-bone rollout have many options which are hard to implementYou should agree upon the options which would be supported by the replication
  122. 122. RotationEvaluatorThis class handles input types “Local Rotation X/Y/Z” and “World Rotation X/Y/Z” const float RotationEvaluator::Process(/* Omitted */) { if (demands the rotation in local space) if (relative to the parent bone) return (local rotation)[channel] else if (relative to a reference bone) transform into the space of the reference bone return (transformed rotation)[channel] else // reference is undefined, then returns in world space return (world rotation)[channel] else return (world rotation)[channel] }
  123. 123. Quaternion→EulerXYZIt can be derived from a conversion of a quaternion to a matrix and decompositionof a matrix to euler angles
  124. 124. DistanceEvaluatorThis handles input type “Distance” const float DistanceEvaluator::Process(/* Omitted */) { if (the reference bone is valid) returns the distance between the base bone and the reference bone else return the distance between the base bone and the world origin }
  125. 125. Graph lookup is easyIt can be implemented in the same way as a key frame animation lookup routine const ReactorResult ReactionControllerBase::Process(/* Omitted */) { Find the largest master/slave index whose master value is not larger than the given master value in the sorted master/slave table Interpolate the reation and the next to it returns the result }
  126. 126. ADDITIONAL NOTEReplicating 3DS MAX Native ControllersLookAtConstraint | OrientationConstraint | ReactionController | ExpressionController&FloatWire | XYZ&ListControllers Ⓒ 2011 NEXON Corporation & devCAT Studio. All Rights Reserved M2 team, Game Development Team for Project M2 in longCAT (The 3rd New Development Division in NEXON Corp.). M2 team Director is Kim, Dong-Gun | Project M2 is produced by Kim, Dong-Gun GT-R team, Engine Development Team for Project M2 and more. GT-R Team Technical Director is Jeon, Hyeong-Kyu
  127. 127. Uses a raw expressionBoth FloatWire and *ExpressionControl need parsing an expression
  128. 128. Replication is simple…except you need to implement an expression parser Interpreting the given expression every frame would hurt runtime performance, so you should build a parser for it But explaining how to build a parser is beyond the scope, so I will skip this part
  129. 129. Reuses the implementationsThe implementations of ID of the expression are almost same as handlers of inputtypes of *ReactionManager RotationEvaluator, PositionEvaluator can be used in these controllers again
  130. 130. ADDITIONAL NOTEReplicating 3DS MAX Native ControllersLookAtConstraint | OrientationConstraint | ReactionController | ExpressionController&FloatWire | XYZ&ListControllers Ⓒ 2011 NEXON Corporation & devCAT Studio. All Rights Reserved M2 team, Game Development Team for Project M2 in longCAT (The 3rd New Development Division in NEXON Corp.). M2 team Director is Kim, Dong-Gun | Project M2 is produced by Kim, Dong-Gun GT-R team, Engine Development Team for Project M2 and more. GT-R Team Technical Director is Jeon, Hyeong-Kyu
  131. 131. Group of float controllersWe replicated 3 types of XYZ controllers - PositionXYZ, EulerXYZ, and ScaleXYZ
  132. 132. Need an explaination?In special case, it performs radian to degree conversion void EulerXYZ::Process(/* Omitted */) { if (is it dynamically changing?) preserve the default value foreach (controllers associated with X/Y/Z channel) result[channel] = calculate the float controller if (the controller is FloatReactionControl) convert result[channel] to radian convert the result euler angles into a quaternion else preserve the default value }
  133. 133. Group of controllersWe replicated 3 types of lists – PositionList, RotationList, and ScaleList
  134. 134. Need an explaination? (2)In special case, the calculation process changes! void RotationList::Process(/* Omitted */) { foreach (controllers in the list) [current] = result of the controller if (controller is a *Constraint) [result] = Slerp([result], [current], [weight]) else [result] = [result] * [current] }
  135. 135. Jubok Kimeias@nexon.co.kr | twitter.com/eiaserinnys (Korean)Choong-Hyo Kimuc2612@nexon.co.kr | twitter.com/siyoskii (Korean)EoD

×