b'\n\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\tAndrew Lilja • VirtuTrace Physics Engine Overhaul\n\n\n\t
\n\t\t
\n\t\t\t
\n\t\t\t\t

VirtuTrace Physics Engine Overhaul

\n\t\t\t\t

In our lab, we try to get insights about high-intensity, time-pressured\ndecisions made by people like soldiers, firefighters, and police\nofficers. To do this, we use VirtuTrace, our flagship simulator software\nthat runs in the C6, one\nof the largest\nCAVEs\nin the world. We like the C6 because it allows us to get really high\necological validity for our studies, but until recently, our simulations\ndid not support dynamic physics — once the scene was loaded, none of the\nobjects could move.

\n

It was my job to update the engine to allow for fully dynamic physics:\ntools floating in zero-g in the International Space Station, houses\nexploding in war zones, and even car collisions were all required in\nupcoming projects. We were using the Bullet\nphysics engine just to stop people from walking through walls, but it\nwas designed for truly dynamic physics simulations, so I didn\'t have to\nreplace it. Even better, a library\nexists that ties together our visual rendering and object hierarchy tree\n(OpenSceneGraph) and Bullet, saving me\neven more time.

\n

I think of code refactoring as a sort of design problem. The users are the developers\nwho have to read, understand, and use your API1 to get their job done\nand the developers who have to edit your code to add a new feature or\nfix a bug. The API user\'s goal could be anything your system allows (and\nin some cases, doesn\'t allow), so a good one needs to have clear and\nexplicit patterns while still remaining flexible. In contrast, the bug\nfixer just wants to get in and solve their problem without spending a\nton of time digging around in complex, arcane code looking for a minus\nsign in the wrong spot. I also had to make migration from the old,\nstatic scenes to the new dynamic physics scenes as painless as possible,\nwhich meant that no matter how fancy I wanted to get, the inputs and\noutputs all had to look the same (or very similar). There\'s nothing\nworse than updating a library and finding out that nothing works the way\nyou expect it to, so now you have go back and rewrite all your old code.

\n

I discussed this at length with my primary stakeholders: Kevin, the\ninitial developer of VirtuTrace and its main developer once I leave, and\nNir, our advisor, who needs to understand its features and limitations\nwhen designing experiments. It was very important to Nir that the\nphysics were realistic, but Kevin wasn\'t able to spend a ton of his time\nrewriting old scenes to make them play nice with the new code. Nir had a\nlong list of features he wanted to see, including zero-g, variations in\nmass, size, and even mass distribution (so a hammer could be heavier at\none end, for example). Even though Nir wanted a bunch of features,\nKevin\'s need for an easy upgrade meant that I couldn\'t just tear\neverything out and start from scratch.

\n

As I read through the code and experimented with changes, however, I quickly\ndiscovered that it would be very challenging to create all these\nfeatures without breaking compatibility with old code. This led to two\nkey decisions: first, there should be a clear distinction between\nstatic objects and dynamic objects, and that everything should be\nstatic unless someone explicitly told them not to be. This way, old\nscenes would be able to play nice with the new system, and if anyone\never wanted to upgrade them to a dynamic scene, they would be going out\nof their way to make that choice, as opposed to having it forced upon\nthem by the design of the system2.

\n

The very first thing I did was find out what wasn\'t necessary.\nThe original, static method looked at all the objects in the scene,\ncreated invisible boxes around each of them, and then froze them in\nplace. This worked fine when things weren\'t moving, but it wasn\'t going\nto fly with dynamic physics. For one thing, keeping track and\nsynchronizing the locations of pairs of objects would be a huge hit to\nperformance. The old method was going to have to go. While this meant\nstarting over in several areas, it had a nice upside and an unexpected\ndownside. Bullet provides an API for adding dynamic objects\n(btRigidBodies* in the Bullet parlance), which allowed me to specify\nthings like mass, size, and starting position. The downside was that all\nthe previous code that had done the math to put objects in the right\nspot had to be removed, and because of a mismatch between how positions\nwere entered by the programmer and how Bullet expected them, that math\nnow fell to me3.

\n

Here\'s an example of what it looks like:

\n
osg::MatrixTransform* master_node = new osg::MatrixTransform();\nmaster_node->setName(model_id);\n\nosg::Matrix matrix = osg::Matrix::scale(scale_x, scale_y, scale_z)\n* osg::Matrix::rotate(osg::inDegrees(rotate_x), osg::X_AXIS)\n* osg::Matrix::rotate(osg::inDegrees(rotate_y), osg::Y_AXIS)\n* osg::Matrix::rotate(osg::inDegrees(rotate_z), osg::Z_AXIS)\n* osg::Matrix::translate(position);\nosg::MatrixTransform* static_matrix_transform = new osg::MatrixTransform(matrix);\nstd::vector<osg::Node* > non_collision_nodes = physics_visitor->get_non_collision_nodes();\nfor(std::vector<osg::Node* >::iterator iter = non_collision_nodes.begin(); iter != non_collision_nodes.end(); ++iter){\n    static_matrix_transform->addChild((*iter));\n}\nmaster_node->addChild(static_matrix_transform);\n
\n

Instead of handling the initial construction and setup of dynamic and static objects\nseparately, I decided to treat their creation as if they were identical\nuntil they really needed to be handled differently. This allowed me to\nuse the many of the same functions and math for both kinds of objects,\ngreatly simplifying the process and speeding up the simulation. Because\nthe math was the same, the various steps of the APIs could both point to\nthe same function when they needed, but still do their own independent\nsteps. This saved me a lot of time, and made it substantially easier for\nfuture programmers to look at my code and see how all the pieces fit\ntogether. With the new positioning code written, I could remove the old,\nredundant code that already existed. However, doing this would remove\nthe API hooks the static scenes relied on, so I created new hooks that\nwere identical in name to the old ones, but just pointed to the new\ncode4.

\n

With the new code in place, I had assumed everything would work. And in\ntests, it did! Objects flying at each other would bounce off the floors\nand walls, and objects would drop out of the sky with gravity pulling\nthem down — and the old scenes still worked just fine. The only problem\nwas that the player object couldn\'t interact with the dynamic objects.\nIt didn\'t matter what the mass or size of the object was: as soon as the\nplayer walked into it, they stopped dead. This was initially amusing, as\nplayers floating down the International Space Station would come to a\ncrashing halt when they run into a screwdriver floating in the air. But\nthis wasn\'t exactly a high-fidelity simulation.

\n

The solution was not easy to find. I tried a huge range of fixes, from\nchanging the way the physics engine treated the player object to writing\nmy own collision math specifically for dealing with the player. I talked\nto Kevin and Nir looking for insights and advice, and I contacted the\nmuch more experienced Ph.D students who worked in VRAC. No one could\nhelp me. I turned to the archaic, spotty, and inconsistent Bullet\ndocumentation over and over again, reading every page, forum post,\ncommit message, and code comment that seemed promising.

\n

In the end, it was this exhaustive coverage of the documentation that\nled me to my answer. Nestled in an unrelated page of the Bullet wiki was\na brief mention that player objects could have collision filters applied\nto them using an old-style method from the previous version of Bullet.\nNowhere else in the official documentation was this listed, and the\nfunction didn\'t even document that it could accept that kind of input.\nBut lo and behold, it worked.

\n

The conversion was even more challenging than I expected. The end result\nfit what both Kevin and Nir wanted, making the upgrade process painless\nand providing most of the features Nir asked for with the potential to\nadd the rest in the near future. I tried to make my code as clear as\npossible, leaving documenting comments in areas that seemed confusing\nand breadcrumb trails showing how all the parts connected. And if\ndevelopers were still confused, I made sure that my email address was\navailable for them.

\n\n\n\t\t\t
\n\t\t\t\n\t\t
\n\t\t↩︎\n\t
\n\n\t\n\n'