Skip to content

Commit 663f18d

Browse files
author
Roberto De Ioris
authored
Update SnippetsForStaticAndSkeletalMeshes.md
1 parent 9f0eec1 commit 663f18d

File tree

1 file changed

+105
-0
lines changed

1 file changed

+105
-0
lines changed

tutorials/SnippetsForStaticAndSkeletalMeshes.md

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1265,3 +1265,108 @@ Remember, you can use the same approach with quaternions, but NOT with rotators
12651265
## Animations: Getting curves from BVH files
12661266

12671267
BVH files are interesting for lot of reasons. First of all, BVH is basically the de-facto standard for motion capture devices. It is a textual human-readable format. And, maybe more important for this page, will heavily push your linear-algebra skills...
1268+
1269+
The BVH specs are described in this old document: https://research.cs.wisc.edu/graphics/Courses/cs-838-1999/Jeff/BVH.html
1270+
1271+
Lucky enough, here in 20tab we built a python module for parsing bvh files: https://github.com/20tab/bvh-python
1272+
1273+
```
1274+
pip install bvh
1275+
```
1276+
1277+
It is a simple parser that returns simple float lists/tuples that we can use to build vectors and quaternions.
1278+
1279+
BVH files only contain a skeleton definition (by specifying joint positions) and an animation.
1280+
1281+
This script will parse a bvh file and will return a transient skeleton (with an empty skeletal mesh associated) and an animation:
1282+
1283+
```python
1284+
import unreal_engine as ue
1285+
from unreal_engine.classes import Skeleton, SkeletalMesh, AnimSequence
1286+
from unreal_engine import FTransform, FVector, FRotator, FQuat, FRawAnimSequenceTrack
1287+
1288+
from bvh import Bvh
1289+
1290+
filename = ue.open_file_dialog('Choose your bvh file', '', '', 'Mocap BVH files|*.bvh;')[0]
1291+
1292+
with open(filename) as f:
1293+
mocap = Bvh(f.read())
1294+
1295+
skeleton = Skeleton()
1296+
1297+
for joint_name in mocap.get_joints_names():
1298+
offset = mocap.joint_offset(joint_name)
1299+
parent_id = mocap.joint_parent_index(joint_name)
1300+
# fix z value by inverting it, it will avoid left/right bone name mismatching
1301+
position = FVector(*offset) * FVector(1, 1, -1)
1302+
skeleton.skeleton_add_bone(joint_name, parent_id, FTransform(position))
1303+
1304+
mesh = SkeletalMesh()
1305+
mesh.skeletal_mesh_set_skeleton(skeleton)
1306+
1307+
# build an empty LOD, this is the minimal required to see bones
1308+
# in the editor
1309+
mesh.skeletal_mesh_build_lod([])
1310+
1311+
anim = AnimSequence()
1312+
anim.Skeleton = skeleton
1313+
anim.NumFrames = mocap.nframes
1314+
anim.SequenceLength = mocap.frame_time * mocap.nframes
1315+
1316+
# iterate each skeleton joint and create a track for each one
1317+
for joint_id, joint_name in enumerate(mocap.get_joints_names()):
1318+
track = FRawAnimSequenceTrack()
1319+
bone_transform = skeleton.skeleton_get_ref_bone_pose(joint_id)
1320+
pos_keys = []
1321+
rot_keys = []
1322+
# the python bvh module allows us to choose in which order we want channels
1323+
rotations = mocap.frames_joint_channels(joint_name, ('Xrotation', 'Yrotation', 'Zrotation'))
1324+
positions = []
1325+
1326+
# get root motion data for first bone
1327+
if joint_id == 0:
1328+
positions = mocap.frames_joint_channels(joint_name, ('Xposition', 'Yposition', 'Zposition'), 0)
1329+
1330+
# quaternions do not suffer from gimbal lock as we can choose the rotation order
1331+
for rotation in rotations:
1332+
# BVH uses the ZXY rotation order (roll, pitch, yaw)
1333+
roll = FRotator(rotation[0], 0, 0).quaternion()
1334+
pitch = FRotator(0, rotation[1], 0).quaternion()
1335+
yaw = FRotator(0, 0, rotation[2]).quaternion()
1336+
# remember that quaternion multiplications are applied in the reverse
1337+
q = yaw * pitch * roll
1338+
1339+
# as we did not fix the bones axis, we rotate the root one accordingly
1340+
if joint_id == 0:
1341+
q = FRotator(-90, 0, 0).quaternion() * q
1342+
1343+
rot_keys.append(q)
1344+
1345+
# remember to always add the bind pose position to the animation track
1346+
for position in positions:
1347+
pos_keys.append(bone_transform.translation + FVector(position[0], position[2], position[1]))
1348+
1349+
if not pos_keys:
1350+
pos_keys = [bone_transform.translation]
1351+
1352+
track.pos_keys = pos_keys
1353+
track.rot_keys = rot_keys
1354+
anim.add_new_raw_track(joint_name, track)
1355+
1356+
ue.open_editor_for_asset(anim)
1357+
```
1358+
1359+
Again, dealing with different convention here is the most complex part. In this example we do not swap axis, instead we rotate the root bone in the animation itelf:
1360+
1361+
![BVH](https://github.com/20tab/UnrealEnginePython/blob/master/tutorials/SnippetsForStaticAndSkeletalMeshes_Assets/bvh.PNG)
1362+
1363+
1364+
## Final Notes
1365+
1366+
The following two tutorials are good companions for the snippets:
1367+
1368+
https://github.com/20tab/UnrealEnginePython/blob/master/tutorials/FixingMixamoRootMotionWithPython.md (for fixing Mixamo root motion)
1369+
1370+
https://github.com/20tab/UnrealEnginePython/blob/master/tutorials/WritingAColladaFactoryWithPython.md (Collada StaticMesh importer)
1371+
1372+
If you have a cool snippet that you want to share, just make a pull request !

0 commit comments

Comments
 (0)