Desktop Java

JavaFX Library for Inverse Kinematics 2.0

This is the first tutorial explaining the basics of javafx-ik, a library for inverse kinematics with JavaFX. The sources of the library can be downloaded from GitHub.

What is a Bone object ?

Single Bone
Figure 1: Single Bone

A bone is the fundamental building block for inverse kinematics with the library  javafx-ik. A bone has a length and a joint around which it can be rotated. The end with the joint is called the head of the bone, the other end is called the tail. Figure 1 shows a single bone, its head, tail, and length.

In the library javafx-ik, a bone is resembled by the class Bone. To initialize it, the length has to be passed as a minimum, but usually also the angle is set during construction. The code sample above sets up a bone with a length of 50 and a rotation of 0, which means it will point horizontally to the right.

// Adding bone b2 to the children of bone b1
b1.getChildren().add(b2);

View the code on Gist.

Assembling bones

Assembling two Bones
Figure 2: Assembling two Bones

Bones can be assembled to define the skeleton of an animated object. Two bones are linked by attaching the tail of one bone b1 to the head of another bone b2. Bone b1 is called the parent of bone b2, bone b2 is called a child of bone b1. Figure 2 shows two connected bones b1 and b2.

The class Bone offers two properties for the structure. The read-only property parent stores the parent of a bone, the property children is an ObservableList of all child-bones. The following snippet shows how two bones b1 and b2 can be connected:

// Adding bone b2 to the children of bone b1
b1.getChildren().add(b2);

View the code on Gist.

Structure of the Dummy
Figure 3: Structure of the Dummy

The read-only property angle defines the rotation between a bone and the extension of its parent bone. A value of 0 results in a straight line, a value of 180 results in the child bone overlapping its parent, but pointing in the opposite direction. Although having parents and children imposes an order on the bones in a skeleton, it usually makes no difference, which bone is the parent and which is the child. By repeatedly linking heads and tails of Bone objects, it is possible to create a chain. This simple structure is used in the caterpillar sample. Complex objects are defined using a tree of bones. The exact position of a bone depends on the position of the parent and the rotation. The bone at the top of the tree, which does not have a parent, is called the root bone. The root bone is treated slightly different during initialization. The property angle of a root bone defines its rotation in the overall scene. You can think of it as having a parent bone which points horizontally to the right. Now we are able to define the structure of our dummy, as shown in Figure 3. The code to generate the skeleton of the dummy can be seen in the code sample below.
 
 

// Definition of head
final Bone head = new Bone(30, 90);
 
// Definition of torso
final Bone torso = new Bone(80, 0);
head.getChildren().add(torso);
 
final Bone[] upperArm = new Bone[2];
final Bone[] upperLeg = new Bone[2];
 
for (int i=0; i<2; i++) {
 
    // Definition upper arms
    upperArm[i] = new Bone(60, 60 - 90 * i);
 
    // Definition of lower arms
    final Bone lowerArm = new Bone(60, -90);
    upperArm[i].getChildren().add(lowerArm);
 
    // Definition of upper legs
    upperLeg[i] = new Bone(60, 30 - 90*i);
 
    // Definition of lower legs
    final Bone lowerLeg = new Bone(75, 90);
    upperLeg[i].getChildren().add(lowerLeg);
}
 
// Connect arms and legs to head and torso
head.getChildren().addAll(upperArm);
torso.getChildren().addAll(upperLeg);

View the code on Gist.

Attaching Visual Components

Bones themselves are not visible. They are only defining the structure. The property content, which is an ObservableList of Node objects, can be used to attach visible elements to a bone.

// Attaching visual elements to a bone
final Bone bone = new Bone(50, 30);
bone.getContent().addAll(
new Circle(20),
new Ellipse(45, 0, 25, 15),
new Circle(80, 0, 10)
);

View the code on Gist.

Adding visual elements
Figure 4: Adding visual elements

The position and rotation of the attached Node objects is determined by the underlying bone. The origin of the local coordinate system for all attached nodes is the head position of the bone. If the head position is moved, so are all its nodes. The rotation of a bone is also passed to the nodes. If the bone is not rotated at all and angle has the value 0, it is pointing horizontally to the right. In the code sample above a bone is defined with two circles and an ellipse attached. This bone is shown in Figure 4. The code sample below shows the required changes that define the appearance of our dummy by attaching circles and ellipses. The resulting dummy can be seen in Figure 5. In the picture, I added symbols for the bones to make them visible. Note that none of the visual components are rotated themselves and all positions are local to the bone. The final positions and rotations in the scene are calculated from the positions and rotations of the bones only.

// Definition of head
final Bone head = new Bone(30, 90);
head.getContent().add(new Ellipse(20, 15));

// Definition of torso
final Bone torso = new Bone(80, 0);
torso.getContent().add(new Ellipse(40, 0, 50, 20));
head.getChildren().add(torso);

final Bone[] upperArm = new Bone[2];
final Bone[] upperLeg = new Bone[2];

for (int i=0; i<2; i++) {

    // Definition upper arms
    upperArm[i] = new Bone(60, 60 - 90 * i);
    upperArm[i].getContent().add(new Ellipse(22.5, 0, 30, 12.5));

    // Definition of lower arms
    final Bone lowerArm = new Bone(60, -90);
    lowerArm.getContent().addAll(
            new Circle(12.5), 
            new Ellipse(30, 0, 20, 12.5), 
            new Circle(60, 0, 12.5)
    );
    upperArm[i].getChildren().add(lowerArm);

    // Definition of upper legs
    upperLeg[i] = new Bone(60, 30 - 90*i);
    upperLeg[i].getContent().add(new Ellipse(20, 0, 30, 15));

    // Definition of lower legs
    final Bone lowerLeg = new Bone(75, 90);
    lowerLeg.getContent().addAll(
            new Circle(15), 
            new Ellipse(40, 0, 30, 15), 
            new Ellipse(75, -10, 10, 22.5)
    );
    upperLeg[i].getChildren().add(lowerLeg);
}

// Connect arms and legs to head and torso
head.getChildren().addAll(upperArm);
torso.getChildren().addAll(upperLeg);

View the code on Gist.

Dummy with all visual elements attached
Figure 5: Dummy with all visual elements attached

Adding everything to the scenegraph

Now there is just one final piece missing to be able to render the dummy on the screen: the class Skeleton. The class Skeleton is the bridge between the Scenegraph and the bones with the attached Node objects. It extends Parent and can therefore be added anywhere in the Scenegraph. It can be translated, rotated, scaled and transformed like any other node in the Scenegraph.

A Skeleton object and the bones of an animated object are linked by setting the property skeleton of any bone. You will notice that the property skeleton of all bones in the same structure will be updated to point to the same Skeleton object afterwards. It is not possible that two bones in the same structure point to different Skeleton objects.

To see the result, download the full Dummy.java file. To compile the script, you need to add the library javafx-ik to your classpath. You can download the sources from GitHub.

What’s next?

The first part of this tutorial showed how to define the static structure – or skeleton – of an animated object and how to attach visible components. The second part will explain how this structure can be animated to create naturally looking animations.
 

Reference: JavaFX Library for Inverse Kinematics 2.0 from our JCG partner Michael Heinrichs at the Mike’s Blog blog.

Michael Heinrichs

Java, Android, JavaFX developer. Interested in agile management and public speaking. Loves his family and cooking.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button