Wednesday, 10 March 2010

The BVH Loader

This last month has been a rather productive one in terms of finishing off sections of the API. This post is about the BVH loader itself.

As mentioned previously, the evolution of various core data structures resulted in breaking changes to the old BVH loader. The old loader could only parse the MOTION section of a given BVH document anyway, it was also heavily dependent on reading 'good' files, otherwise resulting in seg-faults - a result of having been quickly hacked together in an evening.

My first attempt at writing a revamped loader concentrated on using the Boost Xpressive library to perform the parsing. With this library you construct expressions using a familiar regular expression style syntax, expressions may recursively refer to themselves thus permitting the construction of context-free grammars (conceptually, push-down automata). The library is built to be more than just a toy however, so rather than sticking to the technical, academic, models it pragmatically gives the user control over backtracking and offers the ability to populate data-structures mid-parse so that by the end you will have accumulated all the relevant information together, no need to further traverse the parse tree afterwards.

Unfortunately, after spending the time to perfect the regexes and tracking down bugs in the grammar, I discovered that for some unknown reason certain files would fail and the Xpressive library would emit a null pointer exception  (which cannot be caught in C++) whilst parsing the HIERARCHY region of the .BVH. Presumably it was caused due to some misuse on my part but the fact that it worked slightly more than half the time was highly confusing. I was unable to identify anything wrong with the offending files either, but an invalid file ought to simply not match the grammar and fail at loading, the library should still not be accessing null pointers.

Ultimately I decided to scrap that loader and take a more straight-forward route. I probably should have just obeyed KISS in the first place although I did enjoy the learning experience associated with Xpressive which is likely to benefit me if/when I use it in other projects. With this version of the parser I simply went with using the white-space tokenisation feature afforded by std::istreams to split up the tokens without reading the entire file into a string to begin with (Xpressive operated primarily on strings). I then used boost::lexical_cast to convert certain numeric values that the stream might fail at (mainly the frame-time part).

Overall the new BVH Parser Version 3 is faster, smaller and simpler than it's Xpressive counterpart, not to mention the fact that it works for all BVH's I've found so far. It also handles invalid or corrupt files much better; the Xpressive version would simply fail at matching the grammar within the library, reporting success or failure back to me as a boolean and so you would get a generic invalid-file exception upon failure. The new version can attach a string message indicating exactly what went wrong and roughly where that is within the file. It doesn't report a line number, it could do but ultimately I didn't want to assume new-lines within the file, as long as tokens are separated by white-space then the parser can correctly interpret the file. As such no new-line counting is performed either.

Both the Xpressive and the new stream-based parsers incrementally populate a data-structure that is tailored to approximate the structure of a BVH file. Abstractly this is a parse-tree, although it can only loosely be called a tree, being a flat structure. This is stored as a data member within a bvh_document class instance, the class exposes a compile() member function that uses this structure and, using the SoA tools described in a previous blog post, will construct a mocap instance (which is little more than an aggregator of skeleton and motion class instances).

The parse-tree structure was originally created for the Xpressive parser; populating a data-structure that is dissimilar to the grammar would be have a more challenging task. After replacing the Xpressive parser with the stream-based parser it opened up the possibility of constructing a mocap directly at parse time; I didn't go down this route though, in addition to not really wanting to re-write more code than is necessary I also like the separation of concerns involved although it may be somewhat less efficient to do things this way. mocaps don't quite maintain all the information that a BVH file provides either, only keeping those bits relevant to the API; mainly information about the order of channels (Euler angles primarily) and ordering of the hierarchy is lost, although this isn't a problem as such, it does mean that a bvh_document instance can round-trip much better due to its internal parse-tree. It sets a precedent for future file formats too for which round-trip imports/exports might be more desirable.

Since, for the time being, exporting isn't supported, the bvh_document class is really just a go-between temporary object. Even when exports are supported the typical use-case will still primarily be imports; as such a convenience function called import_bvh_file exists, it's a pretty trivial function really, implemented like this:


    bvh_document::mocap import_bvh_file(const std::string & path, bool first_frame_as_bind_pose)
    {
        std::ifstream ifs(path.c_str());
        return bvh_document(ifs).compile(first_frame_as_bind_pose);
    }

It makes importing BVH's nice and simple though.

You'll notice that the loader supports the option to interpret the first frame of the MOTION section as being the bind-pose rather than being actual motion. This is quite a common thing to do with BVH's and it's something that has been done to most of the BVH's that I'm using (they're a conversion of Carnegie Mellon University's data-set).

Well that's all for this post, you can see the BVH's in action in the video I posted last time :-)
Next up, I'll talk a bit about the new renderer, stay tuned.

1 comments:

  1. Can you make a C User Interface too and compiled it as a dll binary? I really need this.

    ReplyDelete