1/*
2 * Copyright (c) 2009-2012 jMonkeyEngine
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 * * Redistributions of source code must retain the above copyright
10 *   notice, this list of conditions and the following disclaimer.
11 *
12 * * Redistributions in binary form must reproduce the above copyright
13 *   notice, this list of conditions and the following disclaimer in the
14 *   documentation and/or other materials provided with the distribution.
15 *
16 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
17 *   may be used to endorse or promote products derived from this software
18 *   without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32package com.jme3.animation;
33
34import com.jme3.export.*;
35import com.jme3.math.Quaternion;
36import com.jme3.math.Vector3f;
37import com.jme3.util.TempVars;
38import java.io.IOException;
39import java.util.BitSet;
40
41/**
42 * Contains a list of transforms and times for each keyframe.
43 *
44 * @author Kirill Vainer
45 */
46public final class BoneTrack implements Track {
47
48    /**
49     * Bone index in the skeleton which this track effects.
50     */
51    private int targetBoneIndex;
52
53    /**
54     * Transforms and times for track.
55     */
56    private CompactVector3Array translations;
57    private CompactQuaternionArray rotations;
58    private CompactVector3Array scales;
59    private float[] times;
60
61    /**
62     * Serialization-only. Do not use.
63     */
64    public BoneTrack() {
65    }
66
67    /**
68     * Creates a bone track for the given bone index
69     * @param targetBoneIndex the bone index
70     * @param times a float array with the time of each frame
71     * @param translations the translation of the bone for each frame
72     * @param rotations the rotation of the bone for each frame
73     */
74    public BoneTrack(int targetBoneIndex, float[] times, Vector3f[] translations, Quaternion[] rotations) {
75        this.targetBoneIndex = targetBoneIndex;
76        this.setKeyframes(times, translations, rotations);
77    }
78
79    /**
80     * Creates a bone track for the given bone index
81     * @param targetBoneIndex the bone index
82     * @param times a float array with the time of each frame
83     * @param translations the translation of the bone for each frame
84     * @param rotations the rotation of the bone for each frame
85     * @param scales the scale of the bone for each frame
86     */
87    public BoneTrack(int targetBoneIndex, float[] times, Vector3f[] translations, Quaternion[] rotations, Vector3f[] scales) {
88    	this.targetBoneIndex = targetBoneIndex;
89        this.setKeyframes(times, translations, rotations, scales);
90    }
91
92    /**
93     * Creates a bone track for the given bone index
94     * @param targetBoneIndex the bone's index
95     */
96    public BoneTrack(int targetBoneIndex) {
97        this.targetBoneIndex = targetBoneIndex;
98    }
99
100    /**
101     * @return the bone index of this bone track.
102     */
103    public int getTargetBoneIndex() {
104        return targetBoneIndex;
105    }
106
107    /**
108     * return the array of rotations of this track
109     * @return
110     */
111    public Quaternion[] getRotations() {
112        return rotations.toObjectArray();
113    }
114
115    /**
116     * returns the array of scales for this track
117     * @return
118     */
119    public Vector3f[] getScales() {
120        return scales == null ? null : scales.toObjectArray();
121    }
122
123    /**
124     * returns the arrays of time for this track
125     * @return
126     */
127    public float[] getTimes() {
128        return times;
129    }
130
131    /**
132     * returns the array of translations of this track
133     * @return
134     */
135    public Vector3f[] getTranslations() {
136        return translations.toObjectArray();
137    }
138
139    /**
140     * Set the translations and rotations for this bone track
141     * @param times a float array with the time of each frame
142     * @param translations the translation of the bone for each frame
143     * @param rotations the rotation of the bone for each frame
144     */
145    public void setKeyframes(float[] times, Vector3f[] translations, Quaternion[] rotations) {
146        if (times.length == 0) {
147            throw new RuntimeException("BoneTrack with no keyframes!");
148        }
149
150        assert times.length == translations.length && times.length == rotations.length;
151
152        this.times = times;
153        this.translations = new CompactVector3Array();
154        this.translations.add(translations);
155        this.translations.freeze();
156        this.rotations = new CompactQuaternionArray();
157        this.rotations.add(rotations);
158        this.rotations.freeze();
159    }
160
161    /**
162     * Set the translations, rotations and scales for this bone track
163     * @param times a float array with the time of each frame
164     * @param translations the translation of the bone for each frame
165     * @param rotations the rotation of the bone for each frame
166     * @param scales the scale of the bone for each frame
167     */
168    public void setKeyframes(float[] times, Vector3f[] translations, Quaternion[] rotations, Vector3f[] scales) {
169        this.setKeyframes(times, translations, rotations);
170        assert times.length == scales.length;
171        if (scales != null) {
172            this.scales = new CompactVector3Array();
173            this.scales.add(scales);
174            this.scales.freeze();
175        }
176    }
177
178    /**
179     *
180     * Modify the bone which this track modifies in the skeleton to contain
181     * the correct animation transforms for a given time.
182     * The transforms can be interpolated in some method from the keyframes.
183     *
184     * @param time the current time of the animation
185     * @param weight the weight of the animation
186     * @param control
187     * @param channel
188     * @param vars
189     */
190    public void setTime(float time, float weight, AnimControl control, AnimChannel channel, TempVars vars) {
191        BitSet affectedBones = channel.getAffectedBones();
192        if (affectedBones != null && !affectedBones.get(targetBoneIndex)) {
193            return;
194        }
195
196        Bone target = control.getSkeleton().getBone(targetBoneIndex);
197
198        Vector3f tempV = vars.vect1;
199        Vector3f tempS = vars.vect2;
200        Quaternion tempQ = vars.quat1;
201        Vector3f tempV2 = vars.vect3;
202        Vector3f tempS2 = vars.vect4;
203        Quaternion tempQ2 = vars.quat2;
204
205        int lastFrame = times.length - 1;
206        if (time < 0 || lastFrame == 0) {
207            rotations.get(0, tempQ);
208            translations.get(0, tempV);
209            if (scales != null) {
210                scales.get(0, tempS);
211            }
212        } else if (time >= times[lastFrame]) {
213            rotations.get(lastFrame, tempQ);
214            translations.get(lastFrame, tempV);
215            if (scales != null) {
216                scales.get(lastFrame, tempS);
217            }
218        } else {
219            int startFrame = 0;
220            int endFrame = 1;
221            // use lastFrame so we never overflow the array
222            int i;
223            for (i = 0; i < lastFrame && times[i] < time; i++) {
224                startFrame = i;
225                endFrame = i + 1;
226            }
227
228            float blend = (time - times[startFrame])
229                    / (times[endFrame] - times[startFrame]);
230
231            rotations.get(startFrame, tempQ);
232            translations.get(startFrame, tempV);
233            if (scales != null) {
234                scales.get(startFrame, tempS);
235            }
236            rotations.get(endFrame, tempQ2);
237            translations.get(endFrame, tempV2);
238            if (scales != null) {
239                scales.get(endFrame, tempS2);
240            }
241            tempQ.nlerp(tempQ2, blend);
242            tempV.interpolate(tempV2, blend);
243            tempS.interpolate(tempS2, blend);
244        }
245
246        if (weight != 1f) {
247            target.blendAnimTransforms(tempV, tempQ, scales != null ? tempS : null, weight);
248        } else {
249            target.setAnimTransforms(tempV, tempQ, scales != null ? tempS : null);
250        }
251    }
252
253    /**
254     * @return the length of the track
255     */
256    public float getLength() {
257        return times == null ? 0 : times[times.length - 1] - times[0];
258    }
259
260    /**
261     * This method creates a clone of the current object.
262     * @return a clone of the current object
263     */
264    @Override
265    public BoneTrack clone() {
266        int tablesLength = times.length;
267
268        float[] times = this.times.clone();
269        Vector3f[] sourceTranslations = this.getTranslations();
270        Quaternion[] sourceRotations = this.getRotations();
271        Vector3f[] sourceScales = this.getScales();
272
273        Vector3f[] translations = new Vector3f[tablesLength];
274        Quaternion[] rotations = new Quaternion[tablesLength];
275        Vector3f[] scales = new Vector3f[tablesLength];
276        for (int i = 0; i < tablesLength; ++i) {
277            translations[i] = sourceTranslations[i].clone();
278            rotations[i] = sourceRotations[i].clone();
279            scales[i] = sourceScales != null ? sourceScales[i].clone() : new Vector3f(1.0f, 1.0f, 1.0f);
280        }
281
282        // Need to use the constructor here because of the final fields used in this class
283        return new BoneTrack(targetBoneIndex, times, translations, rotations, scales);
284    }
285
286    @Override
287    public void write(JmeExporter ex) throws IOException {
288        OutputCapsule oc = ex.getCapsule(this);
289        oc.write(targetBoneIndex, "boneIndex", 0);
290        oc.write(translations, "translations", null);
291        oc.write(rotations, "rotations", null);
292        oc.write(times, "times", null);
293        oc.write(scales, "scales", null);
294    }
295
296    @Override
297    public void read(JmeImporter im) throws IOException {
298        InputCapsule ic = im.getCapsule(this);
299        targetBoneIndex = ic.readInt("boneIndex", 0);
300
301        translations = (CompactVector3Array) ic.readSavable("translations", null);
302        rotations = (CompactQuaternionArray) ic.readSavable("rotations", null);
303        times = ic.readFloatArray("times", null);
304        scales = (CompactVector3Array) ic.readSavable("scales", null);
305
306        //Backward compatibility for old j3o files generated before revision 6807
307        if (im.getFormatVersion() == 0){
308            if (translations == null) {
309                Savable[] sav = ic.readSavableArray("translations", null);
310                if (sav != null) {
311                    translations = new CompactVector3Array();
312                    Vector3f[] transCopy = new Vector3f[sav.length];
313                    System.arraycopy(sav, 0, transCopy, 0, sav.length);
314                    translations.add(transCopy);
315                    translations.freeze();
316                }
317            }
318            if (rotations == null) {
319                Savable[] sav = ic.readSavableArray("rotations", null);
320                if (sav != null) {
321                    rotations = new CompactQuaternionArray();
322                    Quaternion[] rotCopy = new Quaternion[sav.length];
323                    System.arraycopy(sav, 0, rotCopy, 0, sav.length);
324                    rotations.add(rotCopy);
325                    rotations.freeze();
326                }
327            }
328        }
329    }
330
331    public void setTime(float time, float weight, AnimControl control, AnimChannel channel) {
332        throw new UnsupportedOperationException("Not supported yet.");
333    }
334}
335