bvh_anim_parser 1.0.1

A .bvh file parser
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
use crate::types::*;
use crate::utils;
use cgmath::{Decomposed, InnerSpace, Rad, Rotation3, Transform, Zero};
use core::panic;
use regex::Regex;
use std::str::Lines;

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/// Used during joint creation to fill in its parent index (after parent index has been assigned, this function becomes redundant).
/// Alogirthm: searches for the joint with depth 1 less than the current joint's depth.
fn __find_parent_joint_index_by_depth(
    joint_depth: Depth,
    joint_index: ParentIndex,
    joints: &Vec<Joint>,
) -> ParentIndex {
    // detect root joint
    if joints.is_empty() {
        return -1;
    }
    let mut i = joint_index - 1;
    while i >= 0 {
        if joints[i as Index].depth == joint_depth - 1 {
            return i as isize;
        }
        i -= 1;
    }
    panic!("BUG: Parent joint not found. Report this bug.");
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/// Get the tail offset of a joint (i.e the vector pointing from joint's head to its tail (in rest pose)).
/// It's used to calculate joint's rest pose rotation.
fn __get_tail_offset(joint: &Joint, offsets: &Vec<Position>) -> Position {
    let num_children = joint.children.len();

    if num_children == 1 {
        // return the offset of the only child
        return offsets[joint.children[0] as Index];
    } else if num_children > 1 {
        // return the average of all children's offsets (i.e the average tail offset);
        // This is very important - it lets us calculate the rotation of hips and the highest spine joint.
        return joint
            .children
            .iter()
            .map(|&child_index| offsets[child_index])
            .sum::<Position>()
            / num_children as f64;
    } else if joint.endsite.is_some() {
        // return an arbitrary vector (e.g. pointing upwards) if joint has no children (i.e. it's an endsite joint) - it doesn't matter
        return joint.endsite.as_ref().unwrap().offset;
    } else {
        panic!("Joint has no children and is not a leaf (i.e. has no endsites) joint.");
        // return an arbitrary vector (e.g. pointing upwards) if joint has no children (i.e. it's an endsite joint) - it doesn't matter
    }
}

/// Calculate the global rest pose of a joint.
fn __calc_rest_pose(bvh: &BvhMetadata, data: &mut BvhData) {
    for joint in bvh.joints.iter() {
        //// CALCULATE REST GLOBAL POSITIONS
        data.rest_global_positions[joint.index] = if joint.parent_index != -1 {
            data.rest_local_positions[joint.index]
                + data.rest_global_positions[joint.parent_index as Index]
        } else {
            data.rest_local_positions[joint.index]
        };

        //// CALCULATE REST GLOBAL ROTATIONS (this quite specific to .bvh files as they don't specify the rest post orientation of joints, so we have to calculate it ourselves)
        // code source: https://github.com/Wasserwecken/bvhio/blob/c91641e3e41ab5e1281b200a754399ae082f95dd/bvhio/lib/bvh/BvhJoint.py#L48
        let tail_offset = __get_tail_offset(joint, &data.rest_local_positions);
        let dir = if tail_offset != Position::zero() {
            tail_offset.normalize()
        } else {
            Position::new(0.0, 1.0, 0.0)
        }; // prevent NaNs in case of zero offset joints
        let axs = Position {
            x: 0.0,
            y: 1.0,
            z: 0.0,
        };
        let dot = dir.dot(axs);
        let rest_global_rotation: Quaternion = if dot < -0.9999 {
            Quaternion::new(0.0, 0.0, 0.0, 1.0)
        } else if dot > 0.9999 {
            Quaternion::new(1.0, 0.0, 0.0, 0.0)
        } else {
            let angle = (dot).acos();
            let axis = axs.cross(dir).normalize();
            cgmath::Quaternion::from_axis_angle(axis, Rad(angle))
        };
        data.rest_global_rotations[joint.index] = rest_global_rotation;

        //// CALCULATE REST LOCAL ROTATIONS
        data.rest_local_rotations[joint.index] = if joint.parent_index == -1 {
            rest_global_rotation
        } else {
            let r = Decomposed {
                scale: 1.0,
                rot: data.rest_global_rotations[joint.parent_index as Index],
                disp: Position::identity(),
            }
            .inverse_transform()
            .expect("Error during inverting a matrix. This shouldn't have happened.")
                * Decomposed {
                    scale: 1.0,
                    rot: data.rest_global_rotations[joint.index],
                    disp: Position::identity(),
                };
            r.rot
        };
    }
}

/// Calculate the global pose position of a joint. Basically forward kinematics.
fn __calc_pose(bvh: &BvhMetadata, data: &mut BvhData) {
    fn ____recursive_transform(
        joint_index: Index,
        frame: usize,
        metadata: &BvhMetadata,
        data: &mut BvhData,
    ) {
        let i: usize = joint_index;
        let transform = Decomposed {
            scale: 1.0,
            rot: data.pose_local_rotations[i][frame],
            disp: if i == 0 {
                data.pose_global_positions[i][frame]
            } else {
                data.rest_local_positions[i]
            },
        };
        
        let parent_index = metadata.joints[i].parent_index;

        let parent_transform = if parent_index == -1 {
            Decomposed {
                scale: 1.0,
                rot: Quaternion::identity(),
                disp: Position::identity(),
            }
        } else {
            Decomposed {
                scale: 1.0,
                rot: data.pose_global_rotations[parent_index as Index][frame],
                disp: data.pose_global_positions[parent_index as Index][frame],
            }
        };

        let transform = parent_transform * transform ;
        data.pose_global_positions[i][frame] = transform.disp;
        data.pose_global_rotations[i][frame] = transform.rot;
    }

    for joint in bvh.joints.iter() {
        for frame in 0..bvh.num_frames {
            ____recursive_transform(joint.index, frame, bvh, data);
        }
    }
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

fn parse_bvh(lines: Lines) -> (BvhMetadata, BvhData) {
    let mut rest_local_positions: Vec<Position> = Vec::new();

    let mut num_frames = 0;
    let frame_time ;
    let fps ;
    let mut joints: Vec<Joint> = Vec::new();

    let mut rotation_order = String::new();
    // let mut positional_channels: Vec<Vec<Index>> = Vec::new();
    let mut rotational_channels: Vec<Vec<Index>> = Vec::new();

    let mut parsing_endsite = false;
    let mut channels_index = 0;
    let mut depth: Depth = 0;

    let re_joint = Regex::new(r"(ROOT|JOINT) (\w+)").unwrap();
    let re_offset = Regex::new(r"OFFSET (.+)").unwrap();
    let re_channels = Regex::new(r"CHANNELS (\d) (.+)").unwrap();

    //// PARSING LINE BY LINE
    let mut it = lines.into_iter();
    loop {
        let line = it
            .next()
            .expect("Error parsing bvh file. Unexpected end of file.");
        let line = line.trim();

        if line.starts_with("HIERARCHY") || line.is_empty() {
            continue;
        } else if line.starts_with("ROOT") || line.starts_with("JOINT") {
            //// Create joint
            let captures = re_joint.captures(&line);
            if let Some(captures) = captures {
                let name = captures.get(2).unwrap().as_str().to_string();
                //// index of parent joint depends on whether we are starting a new branch or not
                let joint_index = joints.len() as Index;
                let parent_index =
                    __find_parent_joint_index_by_depth(depth, joint_index as ParentIndex, &joints);
                let joint = Joint {
                    name,
                    index: joint_index,
                    parent_index,
                    children: Vec::new(),
                    is_leaf: false,
                    endsite: None,
                    depth: depth,
                };
                // positional_channels.push(Vec::new());
                rotational_channels.push(Vec::new());

                //// If joint has a parent, add this joint to its parent's children
                if joint.parent_index != -1 {
                    if let Some(parent) = joints.get_mut(joint.parent_index as Index) {
                        parent.children.push(joint.index);
                    }
                }
                joints.push(joint);
            } else {
                panic!("Error parsing joint name. Line starts with ROOT or JOINT but joint name was not found.");
            }
        } else if line.to_lowercase().starts_with("end") {
            //// Create endsite
            parsing_endsite = true;
            let endsite = Endsite {
                offset: Position::identity(),
            };
            if let Some(joint) = joints.last_mut() {
                joint.endsite = Some(endsite);
            }
        } else if line == "{" {
            //// Increase parent_index
            depth += 1;
        } else if line == "}" {
            //// Decrease parent_index
            depth -= 1;
        } else if line.starts_with("OFFSET") {
            //// Parse offset
            let captures = re_offset.captures(&line);
            if let Some(captures) = captures {
                let offset: Vec<f64> = captures
                    .get(1)
                    .unwrap()
                    .as_str()
                    .split_whitespace()
                    .map(|s| s.parse::<f64>().unwrap())
                    .collect();
                let offset: Position = Position {
                    x: offset[0],
                    y: offset[1],
                    z: offset[2],
                };

                if let Some(joint) = joints.last_mut() {
                    if parsing_endsite {
                        joint.endsite = Some(Endsite { offset });
                        joint.is_leaf = true;
                        parsing_endsite = false;
                    } else {
                        rest_local_positions.push(offset);
                    }
                }
            }
        } else if line.starts_with("CHANNELS") {
            //// Parse channels
            let captures = re_channels.captures(&line);
            if let Some(captures) = captures {
                // let num_channels = captures.get(1).unwrap().as_str().parse::<usize>().unwrap();
                let channel_names = captures
                    .get(2)
                    .unwrap()
                    .as_str()
                    .split_whitespace()
                    .collect::<Vec<&str>>();
                //// if rotation order hasn't been assigned yet assign it with the first letter of each channel name
                if rotation_order.is_empty() && channel_names.len() == 3 {
                    rotation_order = channel_names
                        .iter()
                        .map(|s| s.chars().next().unwrap())
                        .collect::<String>();
                }
                if let Some(joint) = joints.last_mut() {
                    for channel_name in channel_names {
                        match channel_name {
                            "Xposition" | "Yposition" | "Zposition" => {
                                // positional_channels[joint.index].push(channels_index);
                                channels_index += 1;
                            }
                            "Xrotation" | "Yrotation" | "Zrotation" => {
                                rotational_channels[joint.index].push(channels_index);
                                channels_index += 1;
                            }
                            _ => {}
                        }
                    }
                }
            }
        } else if line.starts_with("Frames:") {
            //// Parse number of frames
            num_frames = line
                .split_whitespace()
                .nth(1)
                .unwrap()
                .parse::<usize>()
                .unwrap();
        } else if line.starts_with("Frame Time:") {
            //// Parse frame time
            frame_time = line
                .split_whitespace()
                .nth(2)
                .unwrap()
                .parse::<f64>()
                .unwrap();
            fps = (1.0 / frame_time) as u32;
            break; // jump to parsing Motion
        }
    }

    //// initialize fields which will be filled in later
    let rest_local_rotations: Vec<Quaternion> = vec![Quaternion::identity(); joints.len()];
    let rest_global_positions: Vec<Position> = vec![Position::identity(); joints.len()];
    let rest_global_rotations: Vec<Quaternion> = vec![Quaternion::identity(); joints.len()];

    let mut pose_global_positions: Vec<Vec<Position>> =
        vec![vec![Position::identity(); num_frames]; joints.len()];
    let pose_global_rotations: Vec<Vec<Quaternion>> =
        vec![vec![Quaternion::identity(); num_frames]; joints.len()];
    let pose_local_positions: Vec<Vec<Position>> =
        vec![vec![Position::identity(); num_frames]; joints.len()];
    let mut pose_local_rotations: Vec<Vec<Quaternion>> =
        vec![vec![Quaternion::identity(); num_frames]; joints.len()];

    /////////////////////////////////// PARSING MOTION ///////////////////////////////////

    for (i, line) in it.enumerate() {
        //// Parse motion data (positional_channels and rotational_channels are already fully filled in)
        let motion_line: Vec<f64> = line
            .split_whitespace()
            .map(|s| s.parse::<f64>().expect("Error parsing motion data"))
            .collect();

        //// Parse positional channels
        pose_global_positions[0][i] = Position::new(motion_line[0], motion_line[1], motion_line[2]);

        //// Parse rotational channels
        for (joint_index, motion_indices) in rotational_channels.iter().enumerate() {
            let eul = (
                motion_line[motion_indices[0]],
                motion_line[motion_indices[1]],
                motion_line[motion_indices[2]],
            );
            let eul = utils::__reorder_vector(eul.0, eul.1, eul.2, &rotation_order);
            let quat = utils::__from_euler_to_quat(eul.0, eul.1, eul.2, &rotation_order);
            pose_local_rotations[joint_index][i] = quat;
        }
    }

    let mut data = BvhData {
        rest_local_positions,
        rest_local_rotations,
        rest_global_positions,
        rest_global_rotations,
        pose_local_positions,
        pose_local_rotations,
        pose_global_positions,
        pose_global_rotations,
    };

    let metadata = BvhMetadata {
        joints,
        num_frames,
        frame_time,
        fps,
    };

    //// for each joint fill it's global rest pose and global pose
    __calc_rest_pose(&metadata, &mut data);
    __calc_pose(&metadata, &mut data);

    return (metadata, data);
}

//////////////////////////////////////////////////////////////// PUBLIC ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/// load a bvh file from a file path
pub fn load_bvh_from_file(file_path: &str) -> (BvhMetadata, BvhData) {
    let contents = std::fs::read_to_string(file_path).expect("Error reading file");
    return __load_bvh(contents.lines());
}

/// load a bvh file from a string
pub fn load_bvh_from_string(bvh_string: &str) -> (BvhMetadata, BvhData) {
    return __load_bvh(bvh_string.lines());
}

fn __load_bvh(lines: Lines) -> (BvhMetadata, BvhData) {
    //// parse bvh file
    let (metadata, data) = parse_bvh(lines);
    return (metadata, data);
}