Welcome, Guest. Please login or register.

Login with username, password and session length

 
Advanced search

1411490 Posts in 69371 Topics- by 58428 Members - Latest Member: shelton786

April 25, 2024, 02:31:30 AM

Need hosting? Check out Digital Ocean
(more details in this thread)
TIGSource ForumsCommunityTownhallDynamic Bouncy Bones Demo + Tech Breakdown
Pages: [1]
Print
Author Topic: Dynamic Bouncy Bones Demo + Tech Breakdown  (Read 426 times)
Allen Chou
Level 0
**



View Profile WWW
« on: August 05, 2019, 06:31:45 AM »

Hi, all. I've finally finished my bouncy bone tech addition to my Unity extension. I initially used Unity-Chan as my test model, but then I decided that I wanted to make my own test model, doubling as a mascot for this extension, so I began learning Blender and made UFO Bunny.





This is a silly video I made that shows off UFO Bunny being initially stiff and becoming bouncy when the bouncy bones feature is turned on. Later on, the video also demonstrates some other applications of the core bouncy tech.

Here is a tech breakdown of how I made the bouncy bones effects:


Data Definition & Construction

First off, I build a chain of bone data by specifying a root bone, referenced by the bone's Transform component. Then I perform a breadth-first search to visit all of the transform's direct and indirect children. All the transforms visited are added to an array of bone data in the visited order. This way, when I want to iterate through the bone data, I can just go through the array once from start to end, guaranteed to always process parents first and then the children. Thus, when it's a child's turn to be processed and it needs to inherit proceed data from its parent (e.g. transform, bone chain length from root, etc.), the parent's data would have already been processed earlier in the array.


Making the Bones Bouncy

The core bouncy logic makes use of numeric springing (Intro / Examples / More Info). It is essentially a specialized type of soft constraint. When given a target value (e.g. float, vector, etc.), a numeric spring tracks its current value closer towards the target by each simulation step, with smooth-changing velocity. For bouncy bones, the core problem is how to compute the target transforms for the bones to be sprung to.

I model the problem using "pose stiffness" and "length stiffness", each of which can be defined as a curve, where the input is the percentage of a bone's chain length from root v.s. the entire chain length, and the output is the stiffness percentage.

I define pose stiffness as a child bone's desire to maintain its relative transform to its parent. So for a bone with full pose stiffness, its target transform is its relative transform to its parent appendeded to its parent's current transform spring values in world space.

As for length stiffness, I define it as a child bone's desire to maintain its distance from its parent. So for a bone with full length stiffness, its target transform will maintain the same distance away from its parent's current transform spring values. Further, during each time step, I remove a percentage equal to length stiffness from a child bone's linear velocity parallel to the vector pointing from its parent to itself, so at full stiffness, there will be no linear velocity component that would alter the distance between the two.

With the target transforms (positions & rotations) defined, the next step is to simulate the numeric springs to track the target values. For position, it's pretty straight forward: just apply numeric springing to individual vector components. However, with rotation, it's a bit tricky. There are multiple ways to represent rotation in 3D, the most common being (1) 3x3 transform matrices, (2) axis-angle vectors (direction is rotation axis & magnitude is rotation angle), and (3) quaternions.

At first I chose (2) axis-angle vectors, because I've been accumulating effectors in this data format (more on effector's later). So my first try on rotation spring is basically a vector spring, where the vector is the axis-angle vector that gets converted to quaternions as a final step (formula here). At first, I thought it was working alright, until I noticed occasional kinks in UFO Bunny's ear (can be observed here when she spin around). Upon further investigation, it was due to the "360-degree wrap-around problem". Basically, a target axis-angle vector of length PI (180 degree) is equivalent to its negated vector, because rotating 180 degrees around an axis is the same as rotating 180 degrees around the opposite axis. So while the logical & spatial target rotation remains the same, the underlying target vector changes drastically, shooting the numeric spring in the other direction wildly.

I tried various way to come up with a mathematically correct way to spring quaternions, but only got as close as to figuring out how to spring a point on a 3D unit sphere surface, and I haven't been able to extend that to 4D (yet). I thought the the 3D unit sphere spring was already too computationally heavy for this task, let alone trying to extend it to 4D, so I decided to cheat by just springing individual components of quaternions as if they are 4D vectors. And, the values are normalized before being read back from the spring into quaternions. It's not ideal. The rotation angle is not sprung in a mathematically correct way. But hey, it LOOKS OKAY, and it's computationally cheap. So that is my final solution.

You can find my code for vector & quaternion springs here.


Effector Accumulation

Effectors are force sources that push or pull things around. I mentioned earlier that I implement this logic using angle-axis vectors. The reasoning comes from torque accumulation in physics simulation. No matter in which order you apply a series of forces, the resulting accumulated torque is always the same. In other words, torque application is order-independent. It is done by taking the cross product each force with the vector from the center of mass to the point of force application. Adding these cross products together gives the final total torque from all forces.

I wanted effectors to apply rotational effects in the same order-independentway, so I accumulate their rotational effects by adding together the cross products of each effector's linear velocity vector and the vector from the affected object's center to the effector's center. Once I have the final accumulated rotational effect, I convert it to a quaternion and combined with a bone's target rotation, computed via the method mentioned above.


Transform Update

Lastly, I have the sprung transform results, and I have to apply them to the bone's Transform component. I can't just set the Transform component's position and rotation to the sprung values, because what values can I base off of to compute new target values in the next frame? So I cache the bone's original transform (position, rotation, and scale; why scale? more on that later) at the beginning of LateUpdate, compute the target transforms, update the transform springs, set the transform values to the sprung values, so the renderer would use these values for rendering, and then restore the transform back to the cached values post-render (using the Camera.OnPostRender() delegate).


Squash & Stretch

I cache scale as well because there's an additional feature that alters a bone's scale for volume preservation (squash & stretch). Imagine a rubber block that's stretched to 4 times its original length in the direction of, say, its local Z axis. If nothing is done to its scale, its volume would become 4 times as large. This is where squash & stretch comes in. To maintain volume, the block needs to be shrunk in the directions perpendicular to the stretching axis, in this case, its X and Y axes. The scale value to shrink to in each perpendicular axis is the square root of the reciprocal of the stretch amount; in this case, it is 1/2 in both the local X and Y axes (4 * (1/2) * (1/2) == 1). Same thing applies when the block is squashed to 1/4 its original length, its local scale in X and Y axes would need to become 2 to maintain volume.

I have the information of each child bone's original distance from its parent. It is compared to the distance between the sprung child bone and its parent. If the sprung distance is larger than the original, it's stretched; otherwise, it's squashed. Bones are scaled accordingly to create the sense of volume preservation.


That's all!
Feel free to ask if you'd like me to elaborate on certain details or explain things I forgot to mention.
And I hope you like the video!
Logged

Keops
Level 6
*


Pixellin' and Gamedev'n


View Profile WWW
« Reply #1 on: August 06, 2019, 05:40:39 PM »

This looks amazing Shocked I'm going to re-read it and understand it! I'm working with Unity and Blender too and this inspires me to no end! Thanks for sharing.
Logged

Hearthstead: Hand Point Right Website - Twitter Hand Point Left

OPEN FOR COMMISSIONS! Behance portfolio
Pages: [1]
Print
Jump to:  

Theme orange-lt created by panic