mirror of
https://github.com/tengge1/ShadowEditor.git
synced 2026-01-25 15:08:11 +00:00
bvh模型加载带动画。
This commit is contained in:
parent
75ff167fee
commit
061ae2c5fb
406
ShadowEditor.Web/assets/js/loaders/BVHLoader.js
Normal file
406
ShadowEditor.Web/assets/js/loaders/BVHLoader.js
Normal file
@ -0,0 +1,406 @@
|
||||
/**
|
||||
* @author herzig / http://github.com/herzig
|
||||
* @author Mugen87 / https://github.com/Mugen87
|
||||
*
|
||||
* Description: reads BVH files and outputs a single THREE.Skeleton and an THREE.AnimationClip
|
||||
*
|
||||
* Currently only supports bvh files containing a single root.
|
||||
*
|
||||
*/
|
||||
|
||||
THREE.BVHLoader = function ( manager ) {
|
||||
|
||||
this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
|
||||
|
||||
this.animateBonePositions = true;
|
||||
this.animateBoneRotations = true;
|
||||
|
||||
};
|
||||
|
||||
THREE.BVHLoader.prototype = {
|
||||
|
||||
constructor: THREE.BVHLoader,
|
||||
|
||||
load: function ( url, onLoad, onProgress, onError ) {
|
||||
|
||||
var scope = this;
|
||||
|
||||
var loader = new THREE.FileLoader( scope.manager );
|
||||
loader.load( url, function ( text ) {
|
||||
|
||||
onLoad( scope.parse( text ) );
|
||||
|
||||
}, onProgress, onError );
|
||||
|
||||
},
|
||||
|
||||
parse: function ( text ) {
|
||||
|
||||
/*
|
||||
reads a string array (lines) from a BVH file
|
||||
and outputs a skeleton structure including motion data
|
||||
|
||||
returns thee root node:
|
||||
{ name: '', channels: [], children: [] }
|
||||
*/
|
||||
function readBvh( lines ) {
|
||||
|
||||
// read model structure
|
||||
|
||||
if ( nextLine( lines ) !== 'HIERARCHY' ) {
|
||||
|
||||
console.error( 'THREE.BVHLoader: HIERARCHY expected.' );
|
||||
|
||||
}
|
||||
|
||||
var list = []; // collects flat array of all bones
|
||||
var root = readNode( lines, nextLine( lines ), list );
|
||||
|
||||
// read motion data
|
||||
|
||||
if ( nextLine( lines ) !== 'MOTION' ) {
|
||||
|
||||
console.error( 'THREE.BVHLoader: MOTION expected.' );
|
||||
|
||||
}
|
||||
|
||||
// number of frames
|
||||
|
||||
var tokens = nextLine( lines ).split( /[\s]+/ );
|
||||
var numFrames = parseInt( tokens[ 1 ] );
|
||||
|
||||
if ( isNaN( numFrames ) ) {
|
||||
|
||||
console.error( 'THREE.BVHLoader: Failed to read number of frames.' );
|
||||
|
||||
}
|
||||
|
||||
// frame time
|
||||
|
||||
tokens = nextLine( lines ).split( /[\s]+/ );
|
||||
var frameTime = parseFloat( tokens[ 2 ] );
|
||||
|
||||
if ( isNaN( frameTime ) ) {
|
||||
|
||||
console.error( 'THREE.BVHLoader: Failed to read frame time.' );
|
||||
|
||||
}
|
||||
|
||||
// read frame data line by line
|
||||
|
||||
for ( var i = 0; i < numFrames; i ++ ) {
|
||||
|
||||
tokens = nextLine( lines ).split( /[\s]+/ );
|
||||
readFrameData( tokens, i * frameTime, root );
|
||||
|
||||
}
|
||||
|
||||
return list;
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
Recursively reads data from a single frame into the bone hierarchy.
|
||||
The passed bone hierarchy has to be structured in the same order as the BVH file.
|
||||
keyframe data is stored in bone.frames.
|
||||
|
||||
- data: splitted string array (frame values), values are shift()ed so
|
||||
this should be empty after parsing the whole hierarchy.
|
||||
- frameTime: playback time for this keyframe.
|
||||
- bone: the bone to read frame data from.
|
||||
*/
|
||||
function readFrameData( data, frameTime, bone ) {
|
||||
|
||||
// end sites have no motion data
|
||||
|
||||
if ( bone.type === 'ENDSITE' ) return;
|
||||
|
||||
// add keyframe
|
||||
|
||||
var keyframe = {
|
||||
time: frameTime,
|
||||
position: new THREE.Vector3(),
|
||||
rotation: new THREE.Quaternion()
|
||||
};
|
||||
|
||||
bone.frames.push( keyframe );
|
||||
|
||||
var quat = new THREE.Quaternion();
|
||||
|
||||
var vx = new THREE.Vector3( 1, 0, 0 );
|
||||
var vy = new THREE.Vector3( 0, 1, 0 );
|
||||
var vz = new THREE.Vector3( 0, 0, 1 );
|
||||
|
||||
// parse values for each channel in node
|
||||
|
||||
for ( var i = 0; i < bone.channels.length; i ++ ) {
|
||||
|
||||
switch ( bone.channels[ i ] ) {
|
||||
|
||||
case 'Xposition':
|
||||
keyframe.position.x = parseFloat( data.shift().trim() );
|
||||
break;
|
||||
case 'Yposition':
|
||||
keyframe.position.y = parseFloat( data.shift().trim() );
|
||||
break;
|
||||
case 'Zposition':
|
||||
keyframe.position.z = parseFloat( data.shift().trim() );
|
||||
break;
|
||||
case 'Xrotation':
|
||||
quat.setFromAxisAngle( vx, parseFloat( data.shift().trim() ) * Math.PI / 180 );
|
||||
keyframe.rotation.multiply( quat );
|
||||
break;
|
||||
case 'Yrotation':
|
||||
quat.setFromAxisAngle( vy, parseFloat( data.shift().trim() ) * Math.PI / 180 );
|
||||
keyframe.rotation.multiply( quat );
|
||||
break;
|
||||
case 'Zrotation':
|
||||
quat.setFromAxisAngle( vz, parseFloat( data.shift().trim() ) * Math.PI / 180 );
|
||||
keyframe.rotation.multiply( quat );
|
||||
break;
|
||||
default:
|
||||
console.warn( 'THREE.BVHLoader: Invalid channel type.' );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// parse child nodes
|
||||
|
||||
for ( var i = 0; i < bone.children.length; i ++ ) {
|
||||
|
||||
readFrameData( data, frameTime, bone.children[ i ] );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
Recursively parses the HIERACHY section of the BVH file
|
||||
|
||||
- lines: all lines of the file. lines are consumed as we go along.
|
||||
- firstline: line containing the node type and name e.g. 'JOINT hip'
|
||||
- list: collects a flat list of nodes
|
||||
|
||||
returns: a BVH node including children
|
||||
*/
|
||||
function readNode( lines, firstline, list ) {
|
||||
|
||||
var node = { name: '', type: '', frames: [] };
|
||||
list.push( node );
|
||||
|
||||
// parse node type and name
|
||||
|
||||
var tokens = firstline.split( /[\s]+/ );
|
||||
|
||||
if ( tokens[ 0 ].toUpperCase() === 'END' && tokens[ 1 ].toUpperCase() === 'SITE' ) {
|
||||
|
||||
node.type = 'ENDSITE';
|
||||
node.name = 'ENDSITE'; // bvh end sites have no name
|
||||
|
||||
} else {
|
||||
|
||||
node.name = tokens[ 1 ];
|
||||
node.type = tokens[ 0 ].toUpperCase();
|
||||
|
||||
}
|
||||
|
||||
if ( nextLine( lines ) !== '{' ) {
|
||||
|
||||
console.error( 'THREE.BVHLoader: Expected opening { after type & name' );
|
||||
|
||||
}
|
||||
|
||||
// parse OFFSET
|
||||
|
||||
tokens = nextLine( lines ).split( /[\s]+/ );
|
||||
|
||||
if ( tokens[ 0 ] !== 'OFFSET' ) {
|
||||
|
||||
console.error( 'THREE.BVHLoader: Expected OFFSET but got: ' + tokens[ 0 ] );
|
||||
|
||||
}
|
||||
|
||||
if ( tokens.length !== 4 ) {
|
||||
|
||||
console.error( 'THREE.BVHLoader: Invalid number of values for OFFSET.' );
|
||||
|
||||
}
|
||||
|
||||
var offset = new THREE.Vector3(
|
||||
parseFloat( tokens[ 1 ] ),
|
||||
parseFloat( tokens[ 2 ] ),
|
||||
parseFloat( tokens[ 3 ] )
|
||||
);
|
||||
|
||||
if ( isNaN( offset.x ) || isNaN( offset.y ) || isNaN( offset.z ) ) {
|
||||
|
||||
console.error( 'THREE.BVHLoader: Invalid values of OFFSET.' );
|
||||
|
||||
}
|
||||
|
||||
node.offset = offset;
|
||||
|
||||
// parse CHANNELS definitions
|
||||
|
||||
if ( node.type !== 'ENDSITE' ) {
|
||||
|
||||
tokens = nextLine( lines ).split( /[\s]+/ );
|
||||
|
||||
if ( tokens[ 0 ] !== 'CHANNELS' ) {
|
||||
|
||||
console.error( 'THREE.BVHLoader: Expected CHANNELS definition.' );
|
||||
|
||||
}
|
||||
|
||||
var numChannels = parseInt( tokens[ 1 ] );
|
||||
node.channels = tokens.splice( 2, numChannels );
|
||||
node.children = [];
|
||||
|
||||
}
|
||||
|
||||
// read children
|
||||
|
||||
while ( true ) {
|
||||
|
||||
var line = nextLine( lines );
|
||||
|
||||
if ( line === '}' ) {
|
||||
|
||||
return node;
|
||||
|
||||
} else {
|
||||
|
||||
node.children.push( readNode( lines, line, list ) );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
recursively converts the internal bvh node structure to a THREE.Bone hierarchy
|
||||
|
||||
source: the bvh root node
|
||||
list: pass an empty array, collects a flat list of all converted THREE.Bones
|
||||
|
||||
returns the root THREE.Bone
|
||||
*/
|
||||
function toTHREEBone( source, list ) {
|
||||
|
||||
var bone = new THREE.Bone();
|
||||
list.push( bone );
|
||||
|
||||
bone.position.add( source.offset );
|
||||
bone.name = source.name;
|
||||
|
||||
if ( source.type !== 'ENDSITE' ) {
|
||||
|
||||
for ( var i = 0; i < source.children.length; i ++ ) {
|
||||
|
||||
bone.add( toTHREEBone( source.children[ i ], list ) );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return bone;
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
builds a THREE.AnimationClip from the keyframe data saved in each bone.
|
||||
|
||||
bone: bvh root node
|
||||
|
||||
returns: a THREE.AnimationClip containing position and quaternion tracks
|
||||
*/
|
||||
function toTHREEAnimation( bones ) {
|
||||
|
||||
var tracks = [];
|
||||
|
||||
// create a position and quaternion animation track for each node
|
||||
|
||||
for ( var i = 0; i < bones.length; i ++ ) {
|
||||
|
||||
var bone = bones[ i ];
|
||||
|
||||
if ( bone.type === 'ENDSITE' )
|
||||
continue;
|
||||
|
||||
// track data
|
||||
|
||||
var times = [];
|
||||
var positions = [];
|
||||
var rotations = [];
|
||||
|
||||
for ( var j = 0; j < bone.frames.length; j ++ ) {
|
||||
|
||||
var frame = bone.frames[ j ];
|
||||
|
||||
times.push( frame.time );
|
||||
|
||||
// the animation system animates the position property,
|
||||
// so we have to add the joint offset to all values
|
||||
|
||||
positions.push( frame.position.x + bone.offset.x );
|
||||
positions.push( frame.position.y + bone.offset.y );
|
||||
positions.push( frame.position.z + bone.offset.z );
|
||||
|
||||
rotations.push( frame.rotation.x );
|
||||
rotations.push( frame.rotation.y );
|
||||
rotations.push( frame.rotation.z );
|
||||
rotations.push( frame.rotation.w );
|
||||
|
||||
}
|
||||
|
||||
if ( scope.animateBonePositions ) {
|
||||
|
||||
tracks.push( new THREE.VectorKeyframeTrack( '.bones[' + bone.name + '].position', times, positions ) );
|
||||
|
||||
}
|
||||
|
||||
if ( scope.animateBoneRotations ) {
|
||||
|
||||
tracks.push( new THREE.QuaternionKeyframeTrack( '.bones[' + bone.name + '].quaternion', times, rotations ) );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return new THREE.AnimationClip( 'animation', - 1, tracks );
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
returns the next non-empty line in lines
|
||||
*/
|
||||
function nextLine( lines ) {
|
||||
|
||||
var line;
|
||||
// skip empty lines
|
||||
while ( ( line = lines.shift().trim() ).length === 0 ) { }
|
||||
return line;
|
||||
|
||||
}
|
||||
|
||||
var scope = this;
|
||||
|
||||
var lines = text.split( /[\r\n]+/g );
|
||||
|
||||
var bones = readBvh( lines );
|
||||
|
||||
var threeBones = [];
|
||||
toTHREEBone( bones[ 0 ], threeBones );
|
||||
|
||||
var threeClip = toTHREEAnimation( bones );
|
||||
|
||||
return {
|
||||
skeleton: new THREE.Skeleton( threeBones ),
|
||||
clip: threeClip
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
@ -109,6 +109,7 @@
|
||||
<script src="assets/js/loaders/AWDLoader.js"></script>
|
||||
<script src="assets/js/loaders/BabylonLoader.js"></script>
|
||||
<script src="assets/js/loaders/BinaryLoader.js"></script>
|
||||
<script src="assets/js/loaders/BVHLoader.js"></script>
|
||||
<script src="assets/js/loaders/ColladaLoader2.js"></script>
|
||||
<script src="assets/js/loaders/FBXLoader.js"></script>
|
||||
<script src="assets/js/loaders/DRACOLoader.js"></script>
|
||||
|
||||
58
ShadowEditor.Web/src/loader/BVHLoader.js
Normal file
58
ShadowEditor.Web/src/loader/BVHLoader.js
Normal file
@ -0,0 +1,58 @@
|
||||
import BaseLoader from './BaseLoader';
|
||||
|
||||
/**
|
||||
* BVHLoader
|
||||
* @author tengge / https://github.com/tengge1
|
||||
*/
|
||||
function BVHLoader() {
|
||||
BaseLoader.call(this);
|
||||
}
|
||||
|
||||
BVHLoader.prototype = Object.create(BaseLoader.prototype);
|
||||
BVHLoader.prototype.constructor = BVHLoader;
|
||||
|
||||
BVHLoader.prototype.load = function (url, options) {
|
||||
return new Promise(resolve => {
|
||||
var loader = new THREE.BVHLoader();
|
||||
loader.load(url, result => {
|
||||
var skeletonHelper = new THREE.SkeletonHelper(result.skeleton.bones[0]);
|
||||
skeletonHelper.skeleton = result.skeleton; // allow animation mixer to bind to SkeletonHelper directly
|
||||
|
||||
var boneContainer = new THREE.Group();
|
||||
boneContainer.add(result.skeleton.bones[0]);
|
||||
|
||||
var obj3d = new THREE.Object3D();
|
||||
obj3d.add(skeletonHelper);
|
||||
obj3d.add(boneContainer);
|
||||
|
||||
Object.assign(obj3d.userData, {
|
||||
obj: result,
|
||||
root: skeletonHelper
|
||||
});
|
||||
|
||||
Object.assign(obj3d.userData, {
|
||||
animNames: 'Animation1',
|
||||
scripts: [{
|
||||
id: null,
|
||||
name: `${options.Name}动画`,
|
||||
type: 'javascript',
|
||||
source: this.createScripts(options.Name),
|
||||
uuid: THREE.Math.generateUUID()
|
||||
}]
|
||||
});
|
||||
|
||||
resolve(obj3d);
|
||||
}, undefined, () => {
|
||||
resolve(null);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
BVHLoader.prototype.createScripts = function (name) {
|
||||
return `var mesh = this.getObjectByName('${name}');\n\n` +
|
||||
`var mixer = new THREE.AnimationMixer(mesh.userData.root);\n\n` +
|
||||
`mixer.clipAction(mesh.userData.obj.clip).setEffectiveWeight(1.0).play();` +
|
||||
`function update(clock, deltaTime) { \n mixer.update(deltaTime); \n}`;
|
||||
};
|
||||
|
||||
export default BVHLoader;
|
||||
@ -20,6 +20,7 @@ import JsLoader from './JsLoader';
|
||||
import _3DSLoader from './_3DSLoader';
|
||||
import _3MFLoader from './_3MFLoader';
|
||||
import AssimpLoader from './AssimpLoader';
|
||||
import BVHLoader from './BVHLoader';
|
||||
|
||||
const Loaders = {
|
||||
'_3ds': _3DSLoader,
|
||||
@ -29,6 +30,7 @@ const Loaders = {
|
||||
'awd': AWDLoader,
|
||||
'babylon': BabylonLoader,
|
||||
'binary': BinaryLoader,
|
||||
'bvh': BVHLoader,
|
||||
'ctm': CTMLoader,
|
||||
'dae': ColladaLoader,
|
||||
'fbx': FBXLoader,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user