1package com.jme3.scene.plugins.blender.animations;
2
3import com.jme3.animation.Bone;
4import com.jme3.animation.BoneTrack;
5import com.jme3.math.Quaternion;
6import com.jme3.math.Vector3f;
7import com.jme3.scene.Node;
8import com.jme3.scene.Spatial;
9import java.util.Arrays;
10
11/**
12 * The purpose of this class is to imitate bone's movement when calculating inverse kinematics.
13 * @author Marcin Roguski (Kaelthas)
14 */
15public class CalculationBone extends Node {
16	private Bone bone;
17	/** The bone's tracks. Will be altered at the end of calculation process. */
18	private BoneTrack track;
19	/** The starting position of the bone. */
20	private Vector3f startTranslation;
21	/** The starting rotation of the bone. */
22	private Quaternion startRotation;
23	/** The starting scale of the bone. */
24	private Vector3f startScale;
25	private Vector3f[] translations;
26	private Quaternion[] rotations;
27	private Vector3f[] scales;
28
29	public CalculationBone(Bone bone, int boneFramesCount) {
30		this.bone = bone;
31		this.startRotation = bone.getModelSpaceRotation().clone();
32		this.startTranslation = bone.getModelSpacePosition().clone();
33		this.startScale = bone.getModelSpaceScale().clone();
34		this.reset();
35		if(boneFramesCount > 0) {
36			this.translations = new Vector3f[boneFramesCount];
37			this.rotations = new Quaternion[boneFramesCount];
38			this.scales = new Vector3f[boneFramesCount];
39
40			Arrays.fill(this.translations, 0, boneFramesCount, this.startTranslation);
41			Arrays.fill(this.rotations, 0, boneFramesCount, this.startRotation);
42			Arrays.fill(this.scales, 0, boneFramesCount, this.startScale);
43		}
44	}
45
46	/**
47	 * Constructor. Stores the track, starting transformation and sets the transformation to the starting positions.
48	 * @param bone
49	 *        the bone this class will imitate
50	 * @param track
51	 *        the bone's tracks
52	 */
53	public CalculationBone(Bone bone, BoneTrack track) {
54		this(bone, 0);
55		this.track = track;
56		this.translations = track.getTranslations();
57		this.rotations = track.getRotations();
58		this.scales = track.getScales();
59	}
60
61	public int getBoneFramesCount() {
62		return this.translations==null ? 0 : this.translations.length;
63	}
64
65	/**
66	 * This method returns the end point of the bone. If the bone has parent it is calculated from the start point
67	 * of parent to the start point of this bone. If the bone doesn't have a parent the end location is considered
68	 * to be 1 point up along Y axis (scale is applied if set to != 1.0);
69	 * @return the end point of this bone
70	 */
71	//TODO: set to Z axis if user defined it this way
72	public Vector3f getEndPoint() {
73		if (this.getParent() == null) {
74			return new Vector3f(0, this.getLocalScale().y, 0);
75		} else {
76			Node parent = this.getParent();
77			return parent.getWorldTranslation().subtract(this.getWorldTranslation()).multLocal(this.getWorldScale());
78		}
79	}
80
81	/**
82	 * This method resets the calculation bone to the starting position.
83	 */
84	public void reset() {
85		this.setLocalTranslation(startTranslation);
86		this.setLocalRotation(startRotation);
87		this.setLocalScale(startScale);
88	}
89
90	@Override
91	public int attachChild(Spatial child) {
92		if (this.getChildren() != null && this.getChildren().size() > 1) {
93			throw new IllegalStateException(this.getClass().getName() + " class instance can only have one child!");
94		}
95		return super.attachChild(child);
96	}
97
98	public Spatial rotate(Quaternion rot, int frame) {
99		Spatial spatial = super.rotate(rot);
100		this.updateWorldTransforms();
101		if (this.getChildren() != null && this.getChildren().size() > 0) {
102			CalculationBone child = (CalculationBone) this.getChild(0);
103			child.updateWorldTransforms();
104		}
105		rotations[frame].set(this.getLocalRotation());
106		translations[frame].set(this.getLocalTranslation());
107		if (scales != null) {
108			scales[frame].set(this.getLocalScale());
109		}
110		return spatial;
111	}
112
113	public void applyCalculatedTracks() {
114		if(track != null) {
115			track.setKeyframes(track.getTimes(), translations, rotations, scales);
116		} else {
117			bone.setUserControl(true);
118			bone.setUserTransforms(translations[0], rotations[0], scales[0]);
119			bone.setUserControl(false);
120			bone.updateWorldVectors();
121		}
122	}
123
124	@Override
125	public String toString() {
126		return bone.getName() + ": " + this.getLocalRotation() + " " + this.getLocalTranslation();
127	}
128}