Create Blender bones and animate and import with Assimp

This article is about

  • creating objects and armatures consisting of bones
  • adding inverse kinematics
  • creating animations
  • exporting to glTF
  • importing the animations with Assimp

Here is the Youtube video showing the steps involved:

Here is a corresponding step-by-step procedure to create a proof-of-concept kinematic chain in Blender

  • create armature
    • first clear scene (in layout mode)
    • in Object mode add an armature which is going to be named “Armature”
    • rotate the armature around the y-axis by 90 degrees so that the bone points in the direction of the x-axis
    • in Edit mode move the tip of the bone to enlarge it
    • press E and extrude the bone to add a second bone to the chain
    • in Edit mode add an additional bone to the armature by pressing Shift-A and move it to the end of the previous chain
    • in the tree view rename the bones to “upper bone”, “lower bone”, and “stay IK”
  • set up inverse kinematic constraint
    • in Pose mode select the stay IK bone first (!) and then shift-click on the lower bone.
    • go to bone constraints (not constraints) and add an Inverse Kinematics constraint
    • set target to “Armature” and bone to “stay IK”
    • set the chain length to 2
    • note that the bone constraint should appear under the lower bone in the tree view
    • moving the stay IK bone in Pose mode should now update the kinematic chain of upper and lower bone
  • add objects and connect them to armature
    • in Object mode select the armature, go to Object properties -> Viewport display, and check name and in front
    • in Object mode create a cuboid and move it to the position of the upper bone
    • click on the cube and shift-click on the armature
    • switch to Pose mode, click on the cube, and shift-click on the upper bone
    • press Ctrl-P and set the parent to bone
    • moving the stay IK bone should now move the cuboid
    • add another cube and set the parent to the lower bone (similar to the last 5 steps but for the lower bone)
    • add a small sphere and move it to the stay IK bone position
    • select the armature in Object mode
    • switch to Pose mode and select the stay IK bone
    • add the “Child Of” bone constraint and set the target to the sphere
    • moving the sphere in Object mode should move the cuboids now
  • add top-level armatures
    • add another armature in Object mode and set the name to “Control” in the tree view
    • click the upper arm bone and shift click the control armature, use Ctrl-P and set it as parent object
    • set the parent object of the sphere to the control armature as well (object, keep transform)
    • moving the control bone now should move everything else
    • create a root armature
    • shift drag and drop control armature under root armature (an extra node in the tree is useful if you want to overwrite the top-level transform programmatically)
  • create animations and export to glTF
    • go to the animation editor
    • add two keyframes for 90 degree rotation of the control bone around the y-axis
    • switch from Dope sheet to Nonlinear animation editor and push down the action
    • select the control bone in the tree view
    • go to Edit -> Bake action and bake only the selected bones and Object data
    • delete the original action, rename the current action to “ControlAction”, and push it down
    • uncheck the control action
    • add two keyframes moving the sphere
    • select the armature and the sphere and bake another action
    • push down the generated actions and the sphere action and rename to the same name (e.g. “CubeAction”) so that they will later become one animation in glTF
    • check the control action back on so that all actions are checked now
    • export the glTF with the animations (no need to check bake all actions)

You can use the LWJGL Assimp Java library to import the animations into Clojure as follows:

(import '[org.lwjgl.assimp Assimp AIAnimation AINodeAnim])
(def scene (Assimp/aiImportFile "test.gltf" (bit-or Assimp/aiProcess_Triangulate Assimp/aiProcess_CalcTangentSpace)))
(.mNumAnimations scene)
; 2
(def animation (AIAnimation/create ^long (.get (.mAnimations scene) 1)))
(/ (.mDuration animation) (.mTicksPerSecond animation))
; 10.4166669921875
(.mNumChannels animation)
; 5
(map #(.dataString (.mNodeName (AINodeAnim/create ^long (.get (.mChannels animation) %)))) (range (.mNumChannels animation)))
; ("Sphere" "Armature" "Stay IK" "Lowe Bone" "Upper Bone")
(def node-anim (AINodeAnim/create ^long (.get (.mChannels animation) 4)))
(.dataString (.mNodeName node-anim))
; "Upper Bone"
(.mNumPositionKeys node-anim)
; 2
(.mNumRotationKeys node-anim)
; 250
(.mNumScalingKeys node-anim)
; 2
(def quaternion (.mValue (.get (.mRotationKeys node-anim) 249)))
[(.w quaternion) (.x quaternion) (.y quaternion) (.z quaternion)]
; [0.99631643 -1.20924955E-17 -1.5678631E-17 0.08575298]
(/ (.mTime (.get (.mRotationKeys node-anim) 249)) (.mTicksPerSecond animation))
; 10.4166669921875

I used tmux, Clojure with rebel-readline, and vim with vim-slime to do the coding.

For an in-depth introduction to rigging I can recommend Mark Alloway’s video on how to rig and animate a landing gear.

Enjoy!

Mechanical Rigging using Ryan King Art's tutorial

After finishing the Blender Guru donut tutorial I wanted to look into articulated objects. Initially I looked at Blender skeletons but then I found Ryan King Art’s tutorial on mechanical rigging.

Basically for each rigid object the following steps are executed:

  • The 3D cursor is set to a point on the center of the joint axis
  • The origin of the rigid object is set to the 3D cursor
  • The parent of the rigid object is set to the other object it is connected to via the joint (while maintaining the transformation)
  • All forbidden translation axes and rotation axes are locked

The resulting object then can be animated by defining keyframes for joint angles as shown in Blender Guru donut tutorial part 11. Rendering the video using the Cycles engine on my 8 core CPU took 3.5 hours. If you have a GPU supported by Blender this would take much less time.

See below for the resulting video:

Doing the Blender Guru donut tutorial

I finally did the Blender Guru donut tutorial for beginners. Blender is a powerful 3D editor to create models and animate them. Here is a series of images made while progressing through the tutorial.

Initially one creates a torus and distorts it. Furthermore the upper half of the torus is copied, solidified, and sculpted to create the icing of the donut.

image

One can set the material properties of the donut to make the icing more reflective and add subsurface scattering.

image

Here is another example using white icing.

image

One can use texture painting to add a bright ring at the middle height of the torus.

image

Using geometry nodes one can distribute cylindrical sprinkles on the surface and rotate them randomly around the surface normal. By doing weight painting one can ensure that the sprinkles are less frequent on the sides of the donut.

image

Furthermore one can select from a set of differently shaped sprinkles and color them randomly using a color map with a few distinct colors.

image

Using key, fill, and rim lighting, one can illuminate the donut.

image

Compositing is used to add a background with a blurred white circle creating a color gradient. Also one can add lens distortion and color dispersion to make the image look more like a photo.

image

See below for the resulting video:

I really enjoyed the tutorial and I hope I will remember the right tool at the right moment when creating models in the future.

All the time there are people finishing the tutorial and posting it on r/BlenderDoughnuts.

OpenGL example in Clojure using LWJGL version 3

Some time ago I published a minimal OpenGL example in Clojure using LWJGL version 2. I used LWJGL 2 because it is available as a Debian package unlike version 3. However one can get LWJGL 3.3.2 with native bindings from the Maven Central Repository.

Note that LWJGL version 3 consists of several packages. Furthermore most packages have native bindings for different platforms. See LWJGL customization page for more information. There is a Leiningen LWJGL project example by Roger Allen which shows how to install all LWJGL libraries using Leiningen. However here we are going to use the more modern approach with a deps.edn file and we are going to use more efficient OpenGL indirect rendering.

The deps.edn file uses symbols of the form <groupID>/<artifactId>$<classifier> to specify libraries. For LWJGL version 3 the classifier is used to install native extensions. The deps.edn file for installing LWJGL with OpenGL and GLFW under GNU/Linux is as follows:

There are more native packages for lwjgl-opengl such as natives-windows and natives-macos.

Displaying a window with GLFW instead of LWJGL 2 is a bit different. The updated code to use lwjgl-glfw and lwjgl-opengl to display a triangle with a small texture is shown below:

If you download the two files, you can run the code as follows:

clj -M raw-opengl-lwjgl3.clj

It should show a triangle like this:

image

Any feedback, comments, and suggestions are welcome.

Enjoy!

Clojure/Java Matrix Library Performance Comparison

This is a quick performance comparison of the Clojure core.matrix library and the Efficient Java Matrix Library. Because core.matrix uses the VectorZ Java library as a backend, direct calls to VectorZ were also included in the comparison. Finally I added fastmath to the comparison after it was pointed out to me by the developer. The criterium 0.4.6 benchmark library was used to measure the performance of common matrix expressions. The Clojure version was 1.11.1 and OpenJDK runtime version was 17.0.6. Here are the results running it on an AMD Ryzen 7 4700U with a turbo speed of 4.1 GHz:

op core. matrix 0.63.0 ejml-all 0.43 vectorz- clj 0.48.0 fastmath 2.2.1
make 4x4 matrix 675 ns 135 ns 50.5 ns 13.1 ns
make 4D vector 299 ns 47.6 ns 9.27 ns 3.67 ns
add 4D vectors 13.5 ns 18.2 ns 9.02 ns 4.29 ns
inverse matrix 439 ns 81.4 ns 440 ns 43.6 ns
element­wise matrix multi­plication 64.9 ns 29.0 ns 29.1 ns 13.7 ns
matrix multi­ plication 102 ns 74.7 ns 100 ns 22.4 ns
matrix-vector multi­plication 20.9 ns 31.2 ns 19.1 ns 6.46 ns
vector dot product 6.56 ns 6.90 ns 4.46 ns 6.36 ns
vector norm 10.1 ns 11.4 ns no support? 3.74 ns
matrix deter­minant 170 ns 7.35 ns 166 ns 7.67 ns
matrix element access 4.14 ns 3.35 ns 3.26 ns 3.53 ns1
get raw data array 12.0 ns 3.00 ns 11.9 ns 13.2 ns1

1requires fastmath 2.2.2-SNAPSHOT or later

See matperf.clj for source code of benchmark script.

Comparing EJML with a mix of core.matrix and direct calls to vectorz:

  • EJML has support for both single and double precision floating point numbers
  • it uses single column matrices to represent vectors leading to slower matrix-vector multiplication
  • it has a fast 4x4 matrix inverse
  • it does not come with a Clojure wrapper
  • it offers fast access to raw data
  • it does not support multi-dimensional arrays

Comparing EJML with fastmath:

  • EJML has support for matrices larger than 4x4
  • EJML gives you access to the matrix as a flat floating point array (fastmath will add support in the future)
  • EJML is mostly slower

The implementations of the libraries are all quite impressive with custom optimisations for small matrices and vectors. Note that I didn’t include Neanderthal in the comparison because it is more suitable for large matrices.

I hope you find this comparison useful.

Update:

The large performance difference for matrix inversion is probably because EJML has custom 4x4 matrix classes while VectorZ stops at 3x3. Here is a performance comparison of matrix inverse for 3x3, 4x4, and 5x5 matrices:

op core. matrix 0.63.0 ejml-all 0.43 vectorz- clj 0.48.0 fastmath 2.2.1
3x3 matrix inverse 13.0 ns 48.3 ns 12.2 ns 10.8 ns
4x4 matrix inverse 471 ns 98.3 ns 465 ns 50.3 ns
5x5 matrix inverse 669 ns 172 ns 666 ns not supported

Further updates: