Skip to content

Commit 9728bf4

Browse files
authored
MMDLoader: Improve Animation system for PMX (mrdoob#21395)
1 parent 5eca1cc commit 9728bf4

File tree

10 files changed

+782
-258
lines changed

10 files changed

+782
-258
lines changed

docs/examples/en/animations/CCDIKSolver.html

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,12 @@ <h3>[method:CCDIKHelper createHelper]()</h3>
9292

9393
<h3>[method:CCDIKSolver update]()</h3>
9494
<p>
95-
Update bones quaternion by solving CCD algorithm.
95+
Update IK bones quaternion by solving CCD algorithm.
96+
</p>
97+
98+
<h3>[method:CCDIKSolver updateOne]( [param:Object ikParam] )</h3>
99+
<p>
100+
Update an IK bone quaternion by solving CCD algorithm.
96101
</p>
97102

98103
<h2>Source</h2>

docs/examples/en/animations/MMDAnimationHelper.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ <h3>[name]( [param:Object params] )</h3>
7777
<li> [page:Boolean sync] - Whether animation durations of added objects are synched. Default is true.</li>
7878
<li> [page:Number afterglow] - Default is 0.0.</li>
7979
<li> [page:Boolean resetPhysicsOnLoop] - Default is true.</li>
80+
<li> [page:Boolean pmxAnimation] - If it is set to true, the helper follows the complex and costly PMX animation system.
81+
Try this option only if your PMX model animation doesn't work well. Default is false.</li>
8082
</ul>
8183
</p>
8284
<p>

docs/examples/zh/animations/CCDIKSolver.html

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,11 @@ <h3>[method:CCDIKSolver update]()</h3>
9595
Update bones quaternion by solving CCD algorithm.
9696
</p>
9797

98+
<h3>[method:CCDIKSolver updateOne]( [param:Object ikParam] )</h3>
99+
<p>
100+
Update an IK bone quaternion by solving CCD algorithm.
101+
</p>
102+
98103
<h2>Source</h2>
99104

100105
<p>

docs/examples/zh/animations/MMDAnimationHelper.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ <h3>[name]( [param:Object params] )</h3>
7777
<li> [page:Boolean sync] - Whether animation durations of added objects are synched. Default is true.</li>
7878
<li> [page:Number afterglow] - Default is 0.0.</li>
7979
<li> [page:Boolean resetPhysicsOnLoop] - Default is true.</li>
80+
<li> [page:Boolean pmxAnimation] - If it is set to true, the helper follows the complex and costly PMX animation system.
81+
Try this option only if your PMX model animation doesn't work well. Default is false.</li>
8082
</ul>
8183
</p>
8284
<p>

examples/js/animation/CCDIKSolver.js

Lines changed: 98 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,32 @@ THREE.CCDIKSolver = ( function () {
3838
constructor: CCDIKSolver,
3939

4040
/**
41-
* Update IK bones.
41+
* Update all IK bones.
4242
*
43-
* @return {THREE.CCDIKSolver}
43+
* @return {CCDIKSolver}
4444
*/
4545
update: function () {
4646

47+
var iks = this.iks;
48+
49+
for ( var i = 0, il = iks.length; i < il; i ++ ) {
50+
51+
this.updateOne( iks[ i ] );
52+
53+
}
54+
55+
return this;
56+
57+
},
58+
59+
/**
60+
* Update one IK bone
61+
*
62+
* @param {Object} ik parameter
63+
* @return {THREE.CCDIKSolver}
64+
*/
65+
updateOne: function () {
66+
4767
var q = new THREE.Quaternion();
4868
var targetPos = new THREE.Vector3();
4969
var targetVec = new THREE.Vector3();
@@ -55,137 +75,131 @@ THREE.CCDIKSolver = ( function () {
5575
var axis = new THREE.Vector3();
5676
var vector = new THREE.Vector3();
5777

58-
return function update() {
78+
return function update( ik ) {
5979

6080
var bones = this.mesh.skeleton.bones;
61-
var iks = this.iks;
6281

6382
// for reference overhead reduction in loop
6483
var math = Math;
6584

66-
for ( var i = 0, il = iks.length; i < il; i ++ ) {
67-
68-
var ik = iks[ i ];
69-
var effector = bones[ ik.effector ];
70-
var target = bones[ ik.target ];
71-
72-
// don't use getWorldPosition() here for the performance
73-
// because it calls updateMatrixWorld( true ) inside.
74-
targetPos.setFromMatrixPosition( target.matrixWorld );
75-
76-
var links = ik.links;
77-
var iteration = ik.iteration !== undefined ? ik.iteration : 1;
78-
79-
for ( var j = 0; j < iteration; j ++ ) {
85+
var effector = bones[ ik.effector ];
86+
var target = bones[ ik.target ];
8087

81-
var rotated = false;
88+
// don't use getWorldPosition() here for the performance
89+
// because it calls updateMatrixWorld( true ) inside.
90+
targetPos.setFromMatrixPosition( target.matrixWorld );
8291

83-
for ( var k = 0, kl = links.length; k < kl; k ++ ) {
92+
var links = ik.links;
93+
var iteration = ik.iteration !== undefined ? ik.iteration : 1;
8494

85-
var link = bones[ links[ k ].index ];
95+
for ( var i = 0; i < iteration; i ++ ) {
8696

87-
// skip this link and following links.
88-
// this skip is used for MMD performance optimization.
89-
if ( links[ k ].enabled === false ) break;
97+
var rotated = false;
9098

91-
var limitation = links[ k ].limitation;
92-
var rotationMin = links[ k ].rotationMin;
93-
var rotationMax = links[ k ].rotationMax;
99+
for ( var j = 0, jl = links.length; j < jl; j ++ ) {
94100

95-
// don't use getWorldPosition/Quaternion() here for the performance
96-
// because they call updateMatrixWorld( true ) inside.
97-
link.matrixWorld.decompose( linkPos, invLinkQ, linkScale );
98-
invLinkQ.invert();
99-
effectorPos.setFromMatrixPosition( effector.matrixWorld );
101+
var link = bones[ links[ j ].index ];
100102

101-
// work in link world
102-
effectorVec.subVectors( effectorPos, linkPos );
103-
effectorVec.applyQuaternion( invLinkQ );
104-
effectorVec.normalize();
103+
// skip this link and following links.
104+
// this skip is used for MMD performance optimization.
105+
if ( links[ j ].enabled === false ) break;
105106

106-
targetVec.subVectors( targetPos, linkPos );
107-
targetVec.applyQuaternion( invLinkQ );
108-
targetVec.normalize();
107+
var limitation = links[ j ].limitation;
108+
var rotationMin = links[ j ].rotationMin;
109+
var rotationMax = links[ j ].rotationMax;
109110

110-
var angle = targetVec.dot( effectorVec );
111+
// don't use getWorldPosition/Quaternion() here for the performance
112+
// because they call updateMatrixWorld( true ) inside.
113+
link.matrixWorld.decompose( linkPos, invLinkQ, linkScale );
114+
invLinkQ.invert();
115+
effectorPos.setFromMatrixPosition( effector.matrixWorld );
111116

112-
if ( angle > 1.0 ) {
117+
// work in link world
118+
effectorVec.subVectors( effectorPos, linkPos );
119+
effectorVec.applyQuaternion( invLinkQ );
120+
effectorVec.normalize();
113121

114-
angle = 1.0;
122+
targetVec.subVectors( targetPos, linkPos );
123+
targetVec.applyQuaternion( invLinkQ );
124+
targetVec.normalize();
115125

116-
} else if ( angle < - 1.0 ) {
126+
var angle = targetVec.dot( effectorVec );
117127

118-
angle = - 1.0;
128+
if ( angle > 1.0 ) {
119129

120-
}
130+
angle = 1.0;
121131

122-
angle = math.acos( angle );
132+
} else if ( angle < - 1.0 ) {
123133

124-
// skip if changing angle is too small to prevent vibration of bone
125-
// Refer to http://www20.atpages.jp/katwat/three.js_r58/examples/mytest37/mmd.three.js
126-
if ( angle < 1e-5 ) continue;
134+
angle = - 1.0;
127135

128-
if ( ik.minAngle !== undefined && angle < ik.minAngle ) {
136+
}
129137

130-
angle = ik.minAngle;
138+
angle = math.acos( angle );
131139

132-
}
140+
// skip if changing angle is too small to prevent vibration of bone
141+
// Refer to http://www20.atpages.jp/katwat/three.js_r58/examples/mytest37/mmd.three.js
142+
if ( angle < 1e-5 ) continue;
133143

134-
if ( ik.maxAngle !== undefined && angle > ik.maxAngle ) {
144+
if ( ik.minAngle !== undefined && angle < ik.minAngle ) {
135145

136-
angle = ik.maxAngle;
146+
angle = ik.minAngle;
137147

138-
}
148+
}
139149

140-
axis.crossVectors( effectorVec, targetVec );
141-
axis.normalize();
150+
if ( ik.maxAngle !== undefined && angle > ik.maxAngle ) {
142151

143-
q.setFromAxisAngle( axis, angle );
144-
link.quaternion.multiply( q );
152+
angle = ik.maxAngle;
145153

146-
// TODO: re-consider the limitation specification
147-
if ( limitation !== undefined ) {
154+
}
148155

149-
var c = link.quaternion.w;
156+
axis.crossVectors( effectorVec, targetVec );
157+
axis.normalize();
150158

151-
if ( c > 1.0 ) c = 1.0;
159+
q.setFromAxisAngle( axis, angle );
160+
link.quaternion.multiply( q );
152161

153-
var c2 = math.sqrt( 1 - c * c );
154-
link.quaternion.set( limitation.x * c2,
155-
limitation.y * c2,
156-
limitation.z * c2,
157-
c );
162+
// TODO: re-consider the limitation specification
163+
if ( limitation !== undefined ) {
158164

159-
}
165+
var c = link.quaternion.w;
160166

161-
if ( rotationMin !== undefined ) {
167+
if ( c > 1.0 ) c = 1.0;
162168

163-
link.rotation.setFromVector3(
164-
link.rotation
165-
.toVector3( vector )
166-
.max( rotationMin ) );
169+
var c2 = math.sqrt( 1 - c * c );
170+
link.quaternion.set( limitation.x * c2,
171+
limitation.y * c2,
172+
limitation.z * c2,
173+
c );
167174

168-
}
175+
}
169176

170-
if ( rotationMax !== undefined ) {
177+
if ( rotationMin !== undefined ) {
171178

172-
link.rotation.setFromVector3(
173-
link.rotation
174-
.toVector3( vector )
175-
.min( rotationMax ) );
179+
link.rotation.setFromVector3(
180+
link.rotation
181+
.toVector3( vector )
182+
.max( rotationMin ) );
176183

177-
}
184+
}
178185

179-
link.updateMatrixWorld( true );
186+
if ( rotationMax !== undefined ) {
180187

181-
rotated = true;
188+
link.rotation.setFromVector3(
189+
link.rotation
190+
.toVector3( vector )
191+
.min( rotationMax ) );
182192

183193
}
184194

185-
if ( ! rotated ) break;
195+
link.updateMatrixWorld( true );
196+
197+
rotated = true;
186198

187199
}
188200

201+
if ( ! rotated ) break;
202+
189203
}
190204

191205
return this;

0 commit comments

Comments
 (0)