package com.jme3.scene.plugins.blender.animations; import com.jme3.animation.Bone; import com.jme3.animation.BoneTrack; import com.jme3.math.Quaternion; import com.jme3.math.Vector3f; import com.jme3.scene.Node; import com.jme3.scene.Spatial; import java.util.Arrays; /** * The purpose of this class is to imitate bone's movement when calculating inverse kinematics. * @author Marcin Roguski (Kaelthas) */ public class CalculationBone extends Node { private Bone bone; /** The bone's tracks. Will be altered at the end of calculation process. */ private BoneTrack track; /** The starting position of the bone. */ private Vector3f startTranslation; /** The starting rotation of the bone. */ private Quaternion startRotation; /** The starting scale of the bone. */ private Vector3f startScale; private Vector3f[] translations; private Quaternion[] rotations; private Vector3f[] scales; public CalculationBone(Bone bone, int boneFramesCount) { this.bone = bone; this.startRotation = bone.getModelSpaceRotation().clone(); this.startTranslation = bone.getModelSpacePosition().clone(); this.startScale = bone.getModelSpaceScale().clone(); this.reset(); if(boneFramesCount > 0) { this.translations = new Vector3f[boneFramesCount]; this.rotations = new Quaternion[boneFramesCount]; this.scales = new Vector3f[boneFramesCount]; Arrays.fill(this.translations, 0, boneFramesCount, this.startTranslation); Arrays.fill(this.rotations, 0, boneFramesCount, this.startRotation); Arrays.fill(this.scales, 0, boneFramesCount, this.startScale); } } /** * Constructor. Stores the track, starting transformation and sets the transformation to the starting positions. * @param bone * the bone this class will imitate * @param track * the bone's tracks */ public CalculationBone(Bone bone, BoneTrack track) { this(bone, 0); this.track = track; this.translations = track.getTranslations(); this.rotations = track.getRotations(); this.scales = track.getScales(); } public int getBoneFramesCount() { return this.translations==null ? 0 : this.translations.length; } /** * This method returns the end point of the bone. If the bone has parent it is calculated from the start point * of parent to the start point of this bone. If the bone doesn't have a parent the end location is considered * to be 1 point up along Y axis (scale is applied if set to != 1.0); * @return the end point of this bone */ //TODO: set to Z axis if user defined it this way public Vector3f getEndPoint() { if (this.getParent() == null) { return new Vector3f(0, this.getLocalScale().y, 0); } else { Node parent = this.getParent(); return parent.getWorldTranslation().subtract(this.getWorldTranslation()).multLocal(this.getWorldScale()); } } /** * This method resets the calculation bone to the starting position. */ public void reset() { this.setLocalTranslation(startTranslation); this.setLocalRotation(startRotation); this.setLocalScale(startScale); } @Override public int attachChild(Spatial child) { if (this.getChildren() != null && this.getChildren().size() > 1) { throw new IllegalStateException(this.getClass().getName() + " class instance can only have one child!"); } return super.attachChild(child); } public Spatial rotate(Quaternion rot, int frame) { Spatial spatial = super.rotate(rot); this.updateWorldTransforms(); if (this.getChildren() != null && this.getChildren().size() > 0) { CalculationBone child = (CalculationBone) this.getChild(0); child.updateWorldTransforms(); } rotations[frame].set(this.getLocalRotation()); translations[frame].set(this.getLocalTranslation()); if (scales != null) { scales[frame].set(this.getLocalScale()); } return spatial; } public void applyCalculatedTracks() { if(track != null) { track.setKeyframes(track.getTimes(), translations, rotations, scales); } else { bone.setUserControl(true); bone.setUserTransforms(translations[0], rotations[0], scales[0]); bone.setUserControl(false); bone.updateWorldVectors(); } } @Override public String toString() { return bone.getName() + ": " + this.getLocalRotation() + " " + this.getLocalTranslation(); } }