After introducing the series and seeing how we can add simple vertex and edge geometry to a scene, today we’re going to start digging into the guts of the problem of how to display skeletons in the Forge viewer. This proved to be a really interesting process: I ended up learning a lot about how the Forge viewer and the three.js library it uses both work.
I don’t recall the full history of the Forge viewer’s use of three.js, but recent releases have all depended on r71. This can present challenges, largely because three.js is (at the time of writing) now at r96. Which means that many of the recent three.js samples focus on a different version of the library, so finding generic three.js code that works with the Forge viewer can be tricky. I’m not fully aware of the logic behind staying with r71, but I do know it would be a significant investment to move up to a more recent release. Beyond the effort required to upgrade the Forge viewer, the change would have a massive impact on applications making use of it. There’s a trade-off here between the effort required (and the negative impact created) vs. the benefits the latest version would bring. Again, I don’t have special insights into the decision-making process – I’m just a consumer of the Forge viewer, much as you all are – it’s just something I see exists.
Which basically means that digging in and making use of core three.js capabilities can sometimes be a bit frustrating. The good news for this particular task was that three.js contains the definition of a SkeletonHelper object that can be used to organise and manage a connected set of bones that depend upon each other positionally. The bad news is that some legacy issues with three.js – and, in fairness, the way it’s been used by the Forge viewer – make it really hard to make use of its full range of capabilities.
SkeletonHelper can be used in a couple of ways: the first is to display line graphics that can be animated over time. The second is to drive a connected SkinnedMesh, and have the changes to the underlying skeleton deform the mesh: super interesting when doing advanced character animation for games, but arguably also something that would be extremely useful for animating robots in a digital twin of the factory of the future. (Spoiler: it turns out not currently to be possible to use a SkinnedMesh inside the Forge viewer – at least at v6.1 or earlier, I can’t speak for the future – but we’ll see more about that in the next post.)
Our next step is to see the kind of graphics we get from a basic SkeletonHelper object and how it might be used to animate a walking skeleton. For now we’ll be animating based on simply calculated joint angles – rather than genuine 3D positions complexly calculated from video footage, as we’ll be doing eventually – but the main thing is to test out the animation capabilities.
Here’s a screenshot of a bunch of skeletons created using the standard SkeletonHelper object in the Forge viewer:
Here’s how they look while being added and animated:
As you can see they’re very skinny indeed: a single pixel (well, it’s actually two pixels on my MacBook Pro’s retina screen, but anyway) is simply not enough to see them clearly. But we’ll look at options for addressing that in the coming posts.
The main point of today’s code is to create a basic skeleton and have the SkeletonHelper control it, animating it over time. There’s a bunch of code, below (check the addSkeleton() function), that shows how you can create a connected topology of bones and have them managed as a coherent skeleton. I’ve done my best to parameterise the creation of the skeleton to make it easier to adjust proportions. As I said earlier in the post, SkeletonHelper is actually a really helpful mechanism that has all kinds of potential usage scenarios (beyond displaying a human skeleton).
Another potentially interesting aspect of the implementation is the calculation of the joint angles to simulate walking. The update() function shows how you can do a reasonable job of faking a walking skeleton with a single angle calculation (although it will be either positive or negative, depending on whether the limb is in front of or behind the torso). It doesn’t work perfectly for the lower leg – the leg which in front of the torso has its knee bend backwards – but it’s a tolerable simplification for this scenario: we’ll be throwing the code away to drive joint positions from a database, in due course. To animate the skeletons over time, we’re calling update() from a standard (but internal) Dasher 360 event driven by the timeline, which simulates the effect of having it respond to historical data being “played”.
In the next post we’ll take a look at our first strategy for making the skeletons more visible: adding a SkinnedMesh to the scene. We know already (from my earlier spoiler) that this doesn’t work, but I think it’s interesting to take a look at the code, anyway, and to understand why it doesn’t (yet) work inside the Forge viewer.