Skip to content

Commit 24eb8b6

Browse files
committed
MMDLoader: Improve Animation system for PMX
1 parent 98d4656 commit 24eb8b6

File tree

4 files changed

+120
-8
lines changed

4 files changed

+120
-8
lines changed

examples/js/animation/MMDAnimationHelper.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,10 @@ THREE.MMDAnimationHelper = ( function () {
505505

506506
},
507507

508+
// Animation system of PMX is a bit too complex and doesn't great match to
509+
// Three.js Animation system. This method attempts to simulate it as much as
510+
// possible based on Three.js Animation system so doesn't perfectly simulate.
511+
// If you need better one you would be required to write your own.
508512
_animateMesh: function ( mesh, delta ) {
509513

510514
var objects = this.objects.get( mesh );
@@ -538,6 +542,20 @@ THREE.MMDAnimationHelper = ( function () {
538542

539543
grantSolver.update();
540544

545+
// In PMX it seems IK should be re-calculated
546+
// after applying grant. Ideally IK should be re-calculated
547+
// every time IK related bone transforms are updated.
548+
// But we re-calculate once here for the performance
549+
// and simplicity. We may revisit if we have quality
550+
// improvement request.
551+
552+
if ( ikSolver && this.enabled.ik ) {
553+
554+
mesh.updateMatrixWorld( true );
555+
ikSolver.update();
556+
557+
}
558+
541559
}
542560

543561
}
@@ -961,6 +979,10 @@ THREE.MMDAnimationHelper = ( function () {
961979
};
962980

963981
/**
982+
* Solver for Grant (Fuyo in Japanese. I just google translated because
983+
* Fuyo may be MMD specific term and may not be common word in 3D CG terms.)
984+
* Grant propagates a bone's transform to other bones transforms even if
985+
* they are not children.
964986
* @param {THREE.SkinnedMesh} mesh
965987
* @param {Array<Object>} grants
966988
*/

examples/js/loaders/MMDLoader.js

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -699,6 +699,9 @@ THREE.MMDLoader = ( function () {
699699

700700
if ( data.metadata.format === 'pmx' ) {
701701

702+
// bone index -> grant entry map
703+
var grantEntryMap = {};
704+
702705
for ( var i = 0; i < data.metadata.boneCount; i ++ ) {
703706

704707
var boneData = data.bones[ i ];
@@ -716,15 +719,46 @@ THREE.MMDLoader = ( function () {
716719
transformationClass: boneData.transformationClass
717720
};
718721

719-
grants.push( param );
722+
grantEntryMap[ i ] = { parent: null, children: [], param: param, visited: false };
720723

721724
}
722725

723-
grants.sort( function ( a, b ) {
726+
var rootEntry = { parent: null, children: [], param: null, visited: false };
724727

725-
return a.transformationClass - b.transformationClass;
728+
// Build a tree representing grant hierarchy
726729

727-
} );
730+
for ( var boneIndex in grantEntryMap ) {
731+
732+
var grantEntry = grantEntryMap[ boneIndex ];
733+
var parentGrantEntry = grantEntryMap[ grantEntry.parentIndex ] || rootEntry;
734+
735+
grantEntry.parent = parentGrantEntry;
736+
parentGrantEntry.children.push( grantEntry );
737+
738+
}
739+
740+
// Sort grant parameters from parents to children because
741+
// grant uses parent's transform that parent's grant is already applied
742+
// so grant should be applied in order from parents to children
743+
744+
function traverse( entry ) {
745+
746+
if ( entry.param ) grants.push( entry.param );
747+
748+
entry.visited = true;
749+
750+
for ( var i = 0, il = entry.children.length; i < il; i ++ ) {
751+
752+
var child = entry.children[ i ];
753+
754+
// Cut off a loop if exists. (Is a grant loop invalid?)
755+
if ( ! child.visited ) traverse( child );
756+
757+
}
758+
759+
}
760+
761+
traverse( rootEntry );
728762

729763
}
730764

examples/jsm/animation/MMDAnimationHelper.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -514,6 +514,10 @@ var MMDAnimationHelper = ( function () {
514514

515515
},
516516

517+
// Animation system of PMX is a bit too complex and doesn't great match to
518+
// Three.js Animation system. This method attempts to simulate it as much as
519+
// possible based on Three.js Animation system so doesn't perfectly simulate.
520+
// If you need better one you would be required to write your own.
517521
_animateMesh: function ( mesh, delta ) {
518522

519523
var objects = this.objects.get( mesh );
@@ -547,6 +551,20 @@ var MMDAnimationHelper = ( function () {
547551

548552
grantSolver.update();
549553

554+
// In PMX it seems IK should be re-calculated
555+
// after applying grant. Ideally IK should be re-calculated
556+
// every time IK related bone transforms are updated.
557+
// But we re-calculate once here for the performance
558+
// and simplicity. We may revisit if we have quality
559+
// improvement request.
560+
561+
if ( ikSolver && this.enabled.ik ) {
562+
563+
mesh.updateMatrixWorld( true );
564+
ikSolver.update();
565+
566+
}
567+
550568
}
551569

552570
}
@@ -970,6 +988,10 @@ var MMDAnimationHelper = ( function () {
970988
};
971989

972990
/**
991+
* Solver for Grant (Fuyo in Japanese. I just google translated because
992+
* Fuyo may be MMD specific term and may not be common word in 3D CG terms.)
993+
* Grant propagates a bone's transform to other bones transforms even if
994+
* they are not children.
973995
* @param {THREE.SkinnedMesh} mesh
974996
* @param {Array<Object>} grants
975997
*/

examples/jsm/loaders/MMDLoader.js

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -734,6 +734,9 @@ var MMDLoader = ( function () {
734734

735735
if ( data.metadata.format === 'pmx' ) {
736736

737+
// bone index -> grant entry map
738+
var grantEntryMap = {};
739+
737740
for ( var i = 0; i < data.metadata.boneCount; i ++ ) {
738741

739742
var boneData = data.bones[ i ];
@@ -751,15 +754,46 @@ var MMDLoader = ( function () {
751754
transformationClass: boneData.transformationClass
752755
};
753756

754-
grants.push( param );
757+
grantEntryMap[ i ] = { parent: null, children: [], param: param, visited: false };
755758

756759
}
757760

758-
grants.sort( function ( a, b ) {
761+
var rootEntry = { parent: null, children: [], param: null, visited: false };
759762

760-
return a.transformationClass - b.transformationClass;
763+
// Build a tree representing grant hierarchy
761764

762-
} );
765+
for ( var boneIndex in grantEntryMap ) {
766+
767+
var grantEntry = grantEntryMap[ boneIndex ];
768+
var parentGrantEntry = grantEntryMap[ grantEntry.parentIndex ] || rootEntry;
769+
770+
grantEntry.parent = parentGrantEntry;
771+
parentGrantEntry.children.push( grantEntry );
772+
773+
}
774+
775+
// Sort grant parameters from parents to children because
776+
// grant uses parent's transform that parent's grant is already applied
777+
// so grant should be applied in order from parents to children
778+
779+
function traverse( entry ) {
780+
781+
if ( entry.param ) grants.push( entry.param );
782+
783+
entry.visited = true;
784+
785+
for ( var i = 0, il = entry.children.length; i < il; i ++ ) {
786+
787+
var child = entry.children[ i ];
788+
789+
// Cut off a loop if exists. (Is a grant loop invalid?)
790+
if ( ! child.visited ) traverse( child );
791+
792+
}
793+
794+
}
795+
796+
traverse( rootEntry );
763797

764798
}
765799

0 commit comments

Comments
 (0)