1/*
2 * To change this template, choose Tools | Templates
3 * and open the template in the editor.
4 */
5package com.jme3.animation;
6
7import com.jme3.export.*;
8import com.jme3.math.FastMath;
9import com.jme3.math.Matrix4f;
10import com.jme3.renderer.RenderManager;
11import com.jme3.renderer.ViewPort;
12import com.jme3.scene.*;
13import com.jme3.scene.VertexBuffer.Type;
14import com.jme3.scene.control.AbstractControl;
15import com.jme3.scene.control.Control;
16import com.jme3.util.TempVars;
17import java.io.IOException;
18import java.nio.ByteBuffer;
19import java.nio.FloatBuffer;
20import java.util.ArrayList;
21
22/**
23 * The Skeleton control deforms a model according to a skeleton,
24 * It handles the computation of the deformation matrices and performs
25 * the transformations on the mesh
26 *
27 * @author Rémy Bouquet Based on AnimControl by Kirill Vainer
28 */
29public class SkeletonControl extends AbstractControl implements Cloneable {
30
31    /**
32     * The skeleton of the model
33     */
34    private Skeleton skeleton;
35    /**
36     * List of targets which this controller effects.
37     */
38    private Mesh[] targets;
39    /**
40     * Used to track when a mesh was updated. Meshes are only updated
41     * if they are visible in at least one camera.
42     */
43    private boolean wasMeshUpdated = false;
44
45    /**
46     * Serialization only. Do not use.
47     */
48    public SkeletonControl() {
49    }
50
51    /**
52     * Creates a skeleton control.
53     * The list of targets will be acquired automatically when
54     * the control is attached to a node.
55     *
56     * @param skeleton the skeleton
57     */
58    public SkeletonControl(Skeleton skeleton) {
59        this.skeleton = skeleton;
60    }
61
62    /**
63     * Creates a skeleton control.
64     *
65     * @param targets the meshes controlled by the skeleton
66     * @param skeleton the skeleton
67     */
68    @Deprecated
69    SkeletonControl(Mesh[] targets, Skeleton skeleton) {
70        this.skeleton = skeleton;
71        this.targets = targets;
72    }
73
74    private boolean isMeshAnimated(Mesh mesh) {
75        return mesh.getBuffer(Type.BindPosePosition) != null;
76    }
77
78    private Mesh[] findTargets(Node node) {
79        Mesh sharedMesh = null;
80        ArrayList<Mesh> animatedMeshes = new ArrayList<Mesh>();
81
82        for (Spatial child : node.getChildren()) {
83            if (!(child instanceof Geometry)) {
84                continue; // could be an attachment node, ignore.
85            }
86
87            Geometry geom = (Geometry) child;
88
89            // is this geometry using a shared mesh?
90            Mesh childSharedMesh = geom.getUserData(UserData.JME_SHAREDMESH);
91
92            if (childSharedMesh != null) {
93                // Don't bother with non-animated shared meshes
94                if (isMeshAnimated(childSharedMesh)) {
95                    // child is using shared mesh,
96                    // so animate the shared mesh but ignore child
97                    if (sharedMesh == null) {
98                        sharedMesh = childSharedMesh;
99                    } else if (sharedMesh != childSharedMesh) {
100                        throw new IllegalStateException("Two conflicting shared meshes for " + node);
101                    }
102                }
103            } else {
104                Mesh mesh = geom.getMesh();
105                if (isMeshAnimated(mesh)) {
106                    animatedMeshes.add(mesh);
107                }
108            }
109        }
110
111        if (sharedMesh != null) {
112            animatedMeshes.add(sharedMesh);
113        }
114
115        return animatedMeshes.toArray(new Mesh[animatedMeshes.size()]);
116    }
117
118    @Override
119    public void setSpatial(Spatial spatial) {
120        super.setSpatial(spatial);
121        if (spatial != null) {
122            Node node = (Node) spatial;
123            targets = findTargets(node);
124        } else {
125            targets = null;
126        }
127    }
128
129    @Override
130    protected void controlRender(RenderManager rm, ViewPort vp) {
131        if (!wasMeshUpdated) {
132            resetToBind(); // reset morph meshes to bind pose
133
134            Matrix4f[] offsetMatrices = skeleton.computeSkinningMatrices();
135
136            // if hardware skinning is supported, the matrices and weight buffer
137            // will be sent by the SkinningShaderLogic object assigned to the shader
138            for (int i = 0; i < targets.length; i++) {
139                // NOTE: This assumes that code higher up
140                // Already ensured those targets are animated
141                // otherwise a crash will happen in skin update
142                //if (isMeshAnimated(targets[i])) {
143                softwareSkinUpdate(targets[i], offsetMatrices);
144                //}
145            }
146
147            wasMeshUpdated = true;
148        }
149    }
150
151    @Override
152    protected void controlUpdate(float tpf) {
153        wasMeshUpdated = false;
154    }
155
156    void resetToBind() {
157        for (Mesh mesh : targets) {
158            if (isMeshAnimated(mesh)) {
159                VertexBuffer bi = mesh.getBuffer(Type.BoneIndex);
160                ByteBuffer bib = (ByteBuffer) bi.getData();
161                if (!bib.hasArray()) {
162                    mesh.prepareForAnim(true); // prepare for software animation
163                }
164                VertexBuffer bindPos = mesh.getBuffer(Type.BindPosePosition);
165                VertexBuffer bindNorm = mesh.getBuffer(Type.BindPoseNormal);
166                VertexBuffer pos = mesh.getBuffer(Type.Position);
167                VertexBuffer norm = mesh.getBuffer(Type.Normal);
168                FloatBuffer pb = (FloatBuffer) pos.getData();
169                FloatBuffer nb = (FloatBuffer) norm.getData();
170                FloatBuffer bpb = (FloatBuffer) bindPos.getData();
171                FloatBuffer bnb = (FloatBuffer) bindNorm.getData();
172                pb.clear();
173                nb.clear();
174                bpb.clear();
175                bnb.clear();
176
177                //reseting bind tangents if there is a bind tangent buffer
178                VertexBuffer bindTangents = mesh.getBuffer(Type.BindPoseTangent);
179                if (bindTangents != null) {
180                    VertexBuffer tangents = mesh.getBuffer(Type.Tangent);
181                    FloatBuffer tb = (FloatBuffer) tangents.getData();
182                    FloatBuffer btb = (FloatBuffer) bindTangents.getData();
183                    tb.clear();
184                    btb.clear();
185                    tb.put(btb).clear();
186                }
187
188
189                pb.put(bpb).clear();
190                nb.put(bnb).clear();
191            }
192        }
193    }
194
195    public Control cloneForSpatial(Spatial spatial) {
196        Node clonedNode = (Node) spatial;
197        AnimControl ctrl = spatial.getControl(AnimControl.class);
198        SkeletonControl clone = new SkeletonControl();
199        clone.setSpatial(clonedNode);
200
201        clone.skeleton = ctrl.getSkeleton();
202        // Fix animated targets for the cloned node
203        clone.targets = findTargets(clonedNode);
204
205        // Fix attachments for the cloned node
206        for (int i = 0; i < clonedNode.getQuantity(); i++) {
207            // go through attachment nodes, apply them to correct bone
208            Spatial child = clonedNode.getChild(i);
209            if (child instanceof Node) {
210                Node clonedAttachNode = (Node) child;
211                Bone originalBone = (Bone) clonedAttachNode.getUserData("AttachedBone");
212
213                if (originalBone != null) {
214                    Bone clonedBone = clone.skeleton.getBone(originalBone.getName());
215
216                    clonedAttachNode.setUserData("AttachedBone", clonedBone);
217                    clonedBone.setAttachmentsNode(clonedAttachNode);
218                }
219            }
220        }
221
222        return clone;
223    }
224
225    /**
226     *
227     * @param boneName the name of the bone
228     * @return the node attached to this bone
229     */
230    public Node getAttachmentsNode(String boneName) {
231        Bone b = skeleton.getBone(boneName);
232        if (b == null) {
233            throw new IllegalArgumentException("Given bone name does not exist "
234                    + "in the skeleton.");
235        }
236
237        Node n = b.getAttachmentsNode();
238        Node model = (Node) spatial;
239        model.attachChild(n);
240        return n;
241    }
242
243    /**
244     * returns the skeleton of this control
245     * @return
246     */
247    public Skeleton getSkeleton() {
248        return skeleton;
249    }
250
251    /**
252     * sets the skeleton for this control
253     * @param skeleton
254     */
255//    public void setSkeleton(Skeleton skeleton) {
256//        this.skeleton = skeleton;
257//    }
258    /**
259     * returns the targets meshes of this control
260     * @return
261     */
262    public Mesh[] getTargets() {
263        return targets;
264    }
265
266    /**
267     * sets the target  meshes of this control
268     * @param targets
269     */
270//    public void setTargets(Mesh[] targets) {
271//        this.targets = targets;
272//    }
273    /**
274     * Update the mesh according to the given transformation matrices
275     * @param mesh then mesh
276     * @param offsetMatrices the transformation matrices to apply
277     */
278    private void softwareSkinUpdate(Mesh mesh, Matrix4f[] offsetMatrices) {
279
280        VertexBuffer tb = mesh.getBuffer(Type.Tangent);
281        if (tb == null) {
282            //if there are no tangents use the classic skinning
283            applySkinning(mesh, offsetMatrices);
284        } else {
285            //if there are tangents use the skinning with tangents
286            applySkinningTangents(mesh, offsetMatrices, tb);
287        }
288
289
290    }
291
292    /**
293     * Method to apply skinning transforms to a mesh's buffers
294     * @param mesh the mesh
295     * @param offsetMatrices the offset matices to apply
296     */
297    private void applySkinning(Mesh mesh, Matrix4f[] offsetMatrices) {
298        int maxWeightsPerVert = mesh.getMaxNumWeights();
299        if (maxWeightsPerVert <= 0) {
300            throw new IllegalStateException("Max weights per vert is incorrectly set!");
301        }
302
303        int fourMinusMaxWeights = 4 - maxWeightsPerVert;
304
305        // NOTE: This code assumes the vertex buffer is in bind pose
306        // resetToBind() has been called this frame
307        VertexBuffer vb = mesh.getBuffer(Type.Position);
308        FloatBuffer fvb = (FloatBuffer) vb.getData();
309        fvb.rewind();
310
311        VertexBuffer nb = mesh.getBuffer(Type.Normal);
312        FloatBuffer fnb = (FloatBuffer) nb.getData();
313        fnb.rewind();
314
315        // get boneIndexes and weights for mesh
316        ByteBuffer ib = (ByteBuffer) mesh.getBuffer(Type.BoneIndex).getData();
317        FloatBuffer wb = (FloatBuffer) mesh.getBuffer(Type.BoneWeight).getData();
318
319        ib.rewind();
320        wb.rewind();
321
322        float[] weights = wb.array();
323        byte[] indices = ib.array();
324        int idxWeights = 0;
325
326        TempVars vars = TempVars.get();
327
328
329        float[] posBuf = vars.skinPositions;
330        float[] normBuf = vars.skinNormals;
331
332        int iterations = (int) FastMath.ceil(fvb.capacity() / ((float) posBuf.length));
333        int bufLength = posBuf.length;
334        for (int i = iterations - 1; i >= 0; i--) {
335            // read next set of positions and normals from native buffer
336            bufLength = Math.min(posBuf.length, fvb.remaining());
337            fvb.get(posBuf, 0, bufLength);
338            fnb.get(normBuf, 0, bufLength);
339            int verts = bufLength / 3;
340            int idxPositions = 0;
341
342            // iterate vertices and apply skinning transform for each effecting bone
343            for (int vert = verts - 1; vert >= 0; vert--) {
344                float nmx = normBuf[idxPositions];
345                float vtx = posBuf[idxPositions++];
346                float nmy = normBuf[idxPositions];
347                float vty = posBuf[idxPositions++];
348                float nmz = normBuf[idxPositions];
349                float vtz = posBuf[idxPositions++];
350
351                float rx = 0, ry = 0, rz = 0, rnx = 0, rny = 0, rnz = 0;
352
353                for (int w = maxWeightsPerVert - 1; w >= 0; w--) {
354                    float weight = weights[idxWeights];
355                    Matrix4f mat = offsetMatrices[indices[idxWeights++]];
356
357                    rx += (mat.m00 * vtx + mat.m01 * vty + mat.m02 * vtz + mat.m03) * weight;
358                    ry += (mat.m10 * vtx + mat.m11 * vty + mat.m12 * vtz + mat.m13) * weight;
359                    rz += (mat.m20 * vtx + mat.m21 * vty + mat.m22 * vtz + mat.m23) * weight;
360
361                    rnx += (nmx * mat.m00 + nmy * mat.m01 + nmz * mat.m02) * weight;
362                    rny += (nmx * mat.m10 + nmy * mat.m11 + nmz * mat.m12) * weight;
363                    rnz += (nmx * mat.m20 + nmy * mat.m21 + nmz * mat.m22) * weight;
364                }
365
366                idxWeights += fourMinusMaxWeights;
367
368                idxPositions -= 3;
369                normBuf[idxPositions] = rnx;
370                posBuf[idxPositions++] = rx;
371                normBuf[idxPositions] = rny;
372                posBuf[idxPositions++] = ry;
373                normBuf[idxPositions] = rnz;
374                posBuf[idxPositions++] = rz;
375            }
376
377            fvb.position(fvb.position() - bufLength);
378            fvb.put(posBuf, 0, bufLength);
379            fnb.position(fnb.position() - bufLength);
380            fnb.put(normBuf, 0, bufLength);
381        }
382
383        vars.release();
384
385        vb.updateData(fvb);
386        nb.updateData(fnb);
387
388    }
389
390    /**
391     * Specific method for skinning with tangents to avoid cluttering the classic skinning calculation with
392     * null checks that would slow down the process even if tangents don't have to be computed.
393     * Also the iteration has additional indexes since tangent has 4 components instead of 3 for pos and norm
394     * @param maxWeightsPerVert maximum number of weights per vertex
395     * @param mesh the mesh
396     * @param offsetMatrices the offsetMaytrices to apply
397     * @param tb the tangent vertexBuffer
398     */
399    private void applySkinningTangents(Mesh mesh, Matrix4f[] offsetMatrices, VertexBuffer tb) {
400        int maxWeightsPerVert = mesh.getMaxNumWeights();
401
402        if (maxWeightsPerVert <= 0) {
403            throw new IllegalStateException("Max weights per vert is incorrectly set!");
404        }
405
406        int fourMinusMaxWeights = 4 - maxWeightsPerVert;
407
408        // NOTE: This code assumes the vertex buffer is in bind pose
409        // resetToBind() has been called this frame
410        VertexBuffer vb = mesh.getBuffer(Type.Position);
411        FloatBuffer fvb = (FloatBuffer) vb.getData();
412        fvb.rewind();
413
414        VertexBuffer nb = mesh.getBuffer(Type.Normal);
415
416        FloatBuffer fnb = (FloatBuffer) nb.getData();
417        fnb.rewind();
418
419
420        FloatBuffer ftb = (FloatBuffer) tb.getData();
421        ftb.rewind();
422
423
424        // get boneIndexes and weights for mesh
425        ByteBuffer ib = (ByteBuffer) mesh.getBuffer(Type.BoneIndex).getData();
426        FloatBuffer wb = (FloatBuffer) mesh.getBuffer(Type.BoneWeight).getData();
427
428        ib.rewind();
429        wb.rewind();
430
431        float[] weights = wb.array();
432        byte[] indices = ib.array();
433        int idxWeights = 0;
434
435        TempVars vars = TempVars.get();
436
437
438        float[] posBuf = vars.skinPositions;
439        float[] normBuf = vars.skinNormals;
440        float[] tanBuf = vars.skinTangents;
441
442        int iterations = (int) FastMath.ceil(fvb.capacity() / ((float) posBuf.length));
443        int bufLength = 0;
444        int tanLength = 0;
445        for (int i = iterations - 1; i >= 0; i--) {
446            // read next set of positions and normals from native buffer
447            bufLength = Math.min(posBuf.length, fvb.remaining());
448            tanLength = Math.min(tanBuf.length, ftb.remaining());
449            fvb.get(posBuf, 0, bufLength);
450            fnb.get(normBuf, 0, bufLength);
451            ftb.get(tanBuf, 0, tanLength);
452            int verts = bufLength / 3;
453            int idxPositions = 0;
454            //tangents has their own index because of the 4 components
455            int idxTangents = 0;
456
457            // iterate vertices and apply skinning transform for each effecting bone
458            for (int vert = verts - 1; vert >= 0; vert--) {
459                float nmx = normBuf[idxPositions];
460                float vtx = posBuf[idxPositions++];
461                float nmy = normBuf[idxPositions];
462                float vty = posBuf[idxPositions++];
463                float nmz = normBuf[idxPositions];
464                float vtz = posBuf[idxPositions++];
465
466                float tnx = tanBuf[idxTangents++];
467                float tny = tanBuf[idxTangents++];
468                float tnz = tanBuf[idxTangents++];
469
470                //skipping the 4th component of the tangent since it doesn't have to be transformed
471                idxTangents++;
472
473                float rx = 0, ry = 0, rz = 0, rnx = 0, rny = 0, rnz = 0, rtx = 0, rty = 0, rtz = 0;
474
475                for (int w = maxWeightsPerVert - 1; w >= 0; w--) {
476                    float weight = weights[idxWeights];
477                    Matrix4f mat = offsetMatrices[indices[idxWeights++]];
478
479                    rx += (mat.m00 * vtx + mat.m01 * vty + mat.m02 * vtz + mat.m03) * weight;
480                    ry += (mat.m10 * vtx + mat.m11 * vty + mat.m12 * vtz + mat.m13) * weight;
481                    rz += (mat.m20 * vtx + mat.m21 * vty + mat.m22 * vtz + mat.m23) * weight;
482
483                    rnx += (nmx * mat.m00 + nmy * mat.m01 + nmz * mat.m02) * weight;
484                    rny += (nmx * mat.m10 + nmy * mat.m11 + nmz * mat.m12) * weight;
485                    rnz += (nmx * mat.m20 + nmy * mat.m21 + nmz * mat.m22) * weight;
486
487                    rtx += (tnx * mat.m00 + tny * mat.m01 + tnz * mat.m02) * weight;
488                    rty += (tnx * mat.m10 + tny * mat.m11 + tnz * mat.m12) * weight;
489                    rtz += (tnx * mat.m20 + tny * mat.m21 + tnz * mat.m22) * weight;
490                }
491
492                idxWeights += fourMinusMaxWeights;
493
494                idxPositions -= 3;
495
496                normBuf[idxPositions] = rnx;
497                posBuf[idxPositions++] = rx;
498                normBuf[idxPositions] = rny;
499                posBuf[idxPositions++] = ry;
500                normBuf[idxPositions] = rnz;
501                posBuf[idxPositions++] = rz;
502
503                idxTangents -= 4;
504
505                tanBuf[idxTangents++] = rtx;
506                tanBuf[idxTangents++] = rty;
507                tanBuf[idxTangents++] = rtz;
508
509                //once again skipping the 4th component of the tangent
510                idxTangents++;
511            }
512
513            fvb.position(fvb.position() - bufLength);
514            fvb.put(posBuf, 0, bufLength);
515            fnb.position(fnb.position() - bufLength);
516            fnb.put(normBuf, 0, bufLength);
517            ftb.position(ftb.position() - tanLength);
518            ftb.put(tanBuf, 0, tanLength);
519        }
520
521        vars.release();
522
523        vb.updateData(fvb);
524        nb.updateData(fnb);
525        tb.updateData(ftb);
526
527
528    }
529
530    @Override
531    public void write(JmeExporter ex) throws IOException {
532        super.write(ex);
533        OutputCapsule oc = ex.getCapsule(this);
534        oc.write(targets, "targets", null);
535        oc.write(skeleton, "skeleton", null);
536    }
537
538    @Override
539    public void read(JmeImporter im) throws IOException {
540        super.read(im);
541        InputCapsule in = im.getCapsule(this);
542        Savable[] sav = in.readSavableArray("targets", null);
543        if (sav != null) {
544            targets = new Mesh[sav.length];
545            System.arraycopy(sav, 0, targets, 0, sav.length);
546        }
547        skeleton = (Skeleton) in.readSavable("skeleton", null);
548    }
549}
550