From 061ae2c5fb2ae794c42affcc4f4f093db313d949 Mon Sep 17 00:00:00 2001
From: liteng <930372551@qq.com>
Date: Fri, 30 Nov 2018 12:29:23 +0800
Subject: [PATCH] =?UTF-8?q?bvh=E6=A8=A1=E5=9E=8B=E5=8A=A0=E8=BD=BD?=
=?UTF-8?q?=E5=B8=A6=E5=8A=A8=E7=94=BB=E3=80=82?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../assets/js/loaders/BVHLoader.js | 406 ++++++++++++++++++
ShadowEditor.Web/index.html | 1 +
ShadowEditor.Web/src/loader/BVHLoader.js | 58 +++
ShadowEditor.Web/src/loader/ModelLoader.js | 2 +
4 files changed, 467 insertions(+)
create mode 100644 ShadowEditor.Web/assets/js/loaders/BVHLoader.js
create mode 100644 ShadowEditor.Web/src/loader/BVHLoader.js
diff --git a/ShadowEditor.Web/assets/js/loaders/BVHLoader.js b/ShadowEditor.Web/assets/js/loaders/BVHLoader.js
new file mode 100644
index 00000000..bef2ed28
--- /dev/null
+++ b/ShadowEditor.Web/assets/js/loaders/BVHLoader.js
@@ -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
+ };
+
+ }
+
+};
diff --git a/ShadowEditor.Web/index.html b/ShadowEditor.Web/index.html
index b3a35abd..3be2d9a3 100644
--- a/ShadowEditor.Web/index.html
+++ b/ShadowEditor.Web/index.html
@@ -109,6 +109,7 @@
+
diff --git a/ShadowEditor.Web/src/loader/BVHLoader.js b/ShadowEditor.Web/src/loader/BVHLoader.js
new file mode 100644
index 00000000..822dc374
--- /dev/null
+++ b/ShadowEditor.Web/src/loader/BVHLoader.js
@@ -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;
\ No newline at end of file
diff --git a/ShadowEditor.Web/src/loader/ModelLoader.js b/ShadowEditor.Web/src/loader/ModelLoader.js
index 8c5f43e1..685e36c8 100644
--- a/ShadowEditor.Web/src/loader/ModelLoader.js
+++ b/ShadowEditor.Web/src/loader/ModelLoader.js
@@ -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,