1/*
2 * To change this template, choose Tools | Templates
3 * and open the template in the editor.
4 */
5package com.jme3.bullet.control.ragdoll;
6
7import com.jme3.animation.Bone;
8import com.jme3.animation.Skeleton;
9import com.jme3.bullet.collision.shapes.HullCollisionShape;
10import com.jme3.bullet.joints.SixDofJoint;
11import com.jme3.math.Quaternion;
12import com.jme3.math.Transform;
13import com.jme3.math.Vector3f;
14import com.jme3.scene.Geometry;
15import com.jme3.scene.Mesh;
16import com.jme3.scene.Node;
17import com.jme3.scene.Spatial;
18import com.jme3.scene.VertexBuffer.Type;
19import java.nio.ByteBuffer;
20import java.nio.FloatBuffer;
21import java.util.*;
22
23/**
24 *
25 * @author Nehon
26 */
27public class RagdollUtils {
28
29    public static void setJointLimit(SixDofJoint joint, float maxX, float minX, float maxY, float minY, float maxZ, float minZ) {
30
31        joint.getRotationalLimitMotor(0).setHiLimit(maxX);
32        joint.getRotationalLimitMotor(0).setLoLimit(minX);
33        joint.getRotationalLimitMotor(1).setHiLimit(maxY);
34        joint.getRotationalLimitMotor(1).setLoLimit(minY);
35        joint.getRotationalLimitMotor(2).setHiLimit(maxZ);
36        joint.getRotationalLimitMotor(2).setLoLimit(minZ);
37    }
38
39    public static Map<Integer, List<Float>> buildPointMap(Spatial model) {
40
41
42        Map<Integer, List<Float>> map = new HashMap<Integer, List<Float>>();
43        if (model instanceof Geometry) {
44            Geometry g = (Geometry) model;
45            buildPointMapForMesh(g.getMesh(), map);
46        } else if (model instanceof Node) {
47            Node node = (Node) model;
48            for (Spatial s : node.getChildren()) {
49                if (s instanceof Geometry) {
50                    Geometry g = (Geometry) s;
51                    buildPointMapForMesh(g.getMesh(), map);
52                }
53            }
54        }
55        return map;
56    }
57
58    private static Map<Integer, List<Float>> buildPointMapForMesh(Mesh mesh, Map<Integer, List<Float>> map) {
59
60        FloatBuffer vertices = mesh.getFloatBuffer(Type.Position);
61        ByteBuffer boneIndices = (ByteBuffer) mesh.getBuffer(Type.BoneIndex).getData();
62        FloatBuffer boneWeight = (FloatBuffer) mesh.getBuffer(Type.BoneWeight).getData();
63
64        vertices.rewind();
65        boneIndices.rewind();
66        boneWeight.rewind();
67
68        int vertexComponents = mesh.getVertexCount() * 3;
69        int k, start, index;
70        float maxWeight = 0;
71
72        for (int i = 0; i < vertexComponents; i += 3) {
73
74
75            start = i / 3 * 4;
76            index = 0;
77            maxWeight = -1;
78            for (k = start; k < start + 4; k++) {
79                float weight = boneWeight.get(k);
80                if (weight > maxWeight) {
81                    maxWeight = weight;
82                    index = boneIndices.get(k);
83                }
84            }
85            List<Float> points = map.get(index);
86            if (points == null) {
87                points = new ArrayList<Float>();
88                map.put(index, points);
89            }
90            points.add(vertices.get(i));
91            points.add(vertices.get(i + 1));
92            points.add(vertices.get(i + 2));
93        }
94        return map;
95    }
96
97    /**
98     * Create a hull collision shape from linked vertices to this bone.
99     * Vertices have to be previoulsly gathered in a map using buildPointMap method
100     * @param link
101     * @param model
102     * @return
103     */
104    public static HullCollisionShape makeShapeFromPointMap(Map<Integer, List<Float>> pointsMap, List<Integer> boneIndices, Vector3f initialScale, Vector3f initialPosition) {
105
106        ArrayList<Float> points = new ArrayList<Float>();
107        for (Integer index : boneIndices) {
108            List<Float> l = pointsMap.get(index);
109            if (l != null) {
110
111                for (int i = 0; i < l.size(); i += 3) {
112                    Vector3f pos = new Vector3f();
113                    pos.x = l.get(i);
114                    pos.y = l.get(i + 1);
115                    pos.z = l.get(i + 2);
116                    pos.subtractLocal(initialPosition).multLocal(initialScale);
117                    points.add(pos.x);
118                    points.add(pos.y);
119                    points.add(pos.z);
120                }
121            }
122        }
123
124        float[] p = new float[points.size()];
125        for (int i = 0; i < points.size(); i++) {
126            p[i] = points.get(i);
127        }
128
129
130        return new HullCollisionShape(p);
131    }
132
133    //retruns the list of bone indices of the given bone and its child(if they are not in the boneList)
134    public static List<Integer> getBoneIndices(Bone bone, Skeleton skeleton, Set<String> boneList) {
135        List<Integer> list = new LinkedList<Integer>();
136        if (boneList.isEmpty()) {
137            list.add(skeleton.getBoneIndex(bone));
138        } else {
139            list.add(skeleton.getBoneIndex(bone));
140            for (Bone chilBone : bone.getChildren()) {
141                if (!boneList.contains(chilBone.getName())) {
142                    list.addAll(getBoneIndices(chilBone, skeleton, boneList));
143                }
144            }
145        }
146        return list;
147    }
148
149    /**
150     * Create a hull collision shape from linked vertices to this bone.
151     *
152     * @param link
153     * @param model
154     * @return
155     */
156    public static HullCollisionShape makeShapeFromVerticeWeights(Spatial model, List<Integer> boneIndices, Vector3f initialScale, Vector3f initialPosition, float weightThreshold) {
157
158        ArrayList<Float> points = new ArrayList<Float>();
159        if (model instanceof Geometry) {
160            Geometry g = (Geometry) model;
161            for (Integer index : boneIndices) {
162                points.addAll(getPoints(g.getMesh(), index, initialScale, initialPosition, weightThreshold));
163            }
164        } else if (model instanceof Node) {
165            Node node = (Node) model;
166            for (Spatial s : node.getChildren()) {
167                if (s instanceof Geometry) {
168                    Geometry g = (Geometry) s;
169                    for (Integer index : boneIndices) {
170                        points.addAll(getPoints(g.getMesh(), index, initialScale, initialPosition, weightThreshold));
171                    }
172
173                }
174            }
175        }
176        float[] p = new float[points.size()];
177        for (int i = 0; i < points.size(); i++) {
178            p[i] = points.get(i);
179        }
180
181
182        return new HullCollisionShape(p);
183    }
184
185    /**
186     * returns a list of points for the given bone
187     * @param mesh
188     * @param boneIndex
189     * @param offset
190     * @param link
191     * @return
192     */
193    private static List<Float> getPoints(Mesh mesh, int boneIndex, Vector3f initialScale, Vector3f offset, float weightThreshold) {
194
195        FloatBuffer vertices = mesh.getFloatBuffer(Type.Position);
196        ByteBuffer boneIndices = (ByteBuffer) mesh.getBuffer(Type.BoneIndex).getData();
197        FloatBuffer boneWeight = (FloatBuffer) mesh.getBuffer(Type.BoneWeight).getData();
198
199        vertices.rewind();
200        boneIndices.rewind();
201        boneWeight.rewind();
202
203        ArrayList<Float> results = new ArrayList<Float>();
204
205        int vertexComponents = mesh.getVertexCount() * 3;
206
207        for (int i = 0; i < vertexComponents; i += 3) {
208            int k;
209            boolean add = false;
210            int start = i / 3 * 4;
211            for (k = start; k < start + 4; k++) {
212                if (boneIndices.get(k) == boneIndex && boneWeight.get(k) >= weightThreshold) {
213                    add = true;
214                    break;
215                }
216            }
217            if (add) {
218
219                Vector3f pos = new Vector3f();
220                pos.x = vertices.get(i);
221                pos.y = vertices.get(i + 1);
222                pos.z = vertices.get(i + 2);
223                pos.subtractLocal(offset).multLocal(initialScale);
224                results.add(pos.x);
225                results.add(pos.y);
226                results.add(pos.z);
227
228            }
229        }
230
231        return results;
232    }
233
234    /**
235     * Updates a bone position and rotation.
236     * if the child bones are not in the bone list this means, they are not associated with a physic shape.
237     * So they have to be updated
238     * @param bone the bone
239     * @param pos the position
240     * @param rot the rotation
241     */
242    public static void setTransform(Bone bone, Vector3f pos, Quaternion rot, boolean restoreBoneControl, Set<String> boneList) {
243        //we ensure that we have the control
244        if (restoreBoneControl) {
245            bone.setUserControl(true);
246        }
247        //we set te user transforms of the bone
248        bone.setUserTransformsWorld(pos, rot);
249        for (Bone childBone : bone.getChildren()) {
250            //each child bone that is not in the list is updated
251            if (!boneList.contains(childBone.getName())) {
252                Transform t = childBone.getCombinedTransform(pos, rot);
253                setTransform(childBone, t.getTranslation(), t.getRotation(), restoreBoneControl, boneList);
254            }
255        }
256        //we give back the control to the keyframed animation
257        if (restoreBoneControl) {
258            bone.setUserControl(false);
259        }
260    }
261
262    public static void setUserControl(Bone bone, boolean bool) {
263        bone.setUserControl(bool);
264        for (Bone child : bone.getChildren()) {
265            setUserControl(child, bool);
266        }
267    }
268}
269