AnimatedMesh.java 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  1. package de.tudarmstadt.informatik.hostage.ui2.fragment.opengl;
  2. import java.io.ByteArrayOutputStream;
  3. import java.io.InputStream;
  4. import java.io.IOException;
  5. import java.nio.ByteBuffer;
  6. import java.nio.ByteOrder;
  7. import java.nio.FloatBuffer;
  8. import java.nio.IntBuffer;
  9. import java.nio.ShortBuffer;
  10. import java.util.ArrayList;
  11. import android.opengl.GLES20;
  12. import android.opengl.Matrix;
  13. import android.util.Log;
  14. /**
  15. * @author Fabio Arnold
  16. *
  17. * Animated Mesh
  18. * This class reads a mesh in the AMSH binary format and creates the necessary OpenGL objects for drawing
  19. */
  20. public class AnimatedMesh {
  21. private ByteBuffer data;
  22. private int vertexOffset;
  23. private int vertexSize;
  24. private int triangleOffset;
  25. private int triangleCount;
  26. private int vertexBuffer; // vbo
  27. private int indexBuffer;
  28. private ArrayList<Bone> bones;
  29. private float[] matrices; // matrix palette for skinning
  30. private ArrayList<Action> actions;
  31. private Action currentAction;
  32. private int currentFrame;
  33. private boolean loopAction = false;
  34. private boolean reverseAction = false;
  35. /**
  36. * start play an animation (action)
  37. * @param actionName name in the AMSH file
  38. * @param loop loop the animation
  39. * @param reverse play the animation backwards
  40. */
  41. public void startAction(String actionName, boolean loop, boolean reverse) {
  42. if (!(currentAction != null && currentAction.name.equals(actionName) && reverse)) // keep the current frame
  43. currentFrame = 0;
  44. loopAction = loop;
  45. reverseAction = reverse;
  46. currentAction = null;
  47. // find the action
  48. for (Action action : actions) {
  49. if (action.name.equals(actionName)) {
  50. currentAction = action;
  51. break;
  52. }
  53. }
  54. if (currentAction != null && reverseAction)
  55. if (!(currentAction != null && currentAction.name.equals(actionName) && reverse)) // keep the current frame
  56. currentFrame = currentAction.numFrames - 1;
  57. }
  58. /**
  59. * @return true if completed OR hasn't started yet.
  60. */
  61. public boolean isActionDone() {
  62. if (currentAction == null)
  63. return true;
  64. return currentFrame <= 1 || currentFrame >= currentAction.numFrames - 1;
  65. /*
  66. if (reverseAction)
  67. return currentFrame <= 0;
  68. else
  69. return currentFrame >= currentAction.numFrames - 1;
  70. */
  71. }
  72. // private classes. don't use these.
  73. private class Bone {
  74. @SuppressWarnings("unused")
  75. public String name; // 15 bytes
  76. public int parentIndex; // 1 byte
  77. public float[] invBindPose; // 64 bytes
  78. Bone(ByteBuffer data) {
  79. name = "";
  80. boolean stop = false;
  81. for (int i = 0; i < 15; i++) {
  82. char c;
  83. if ((c = (char)data.get()) == '\0') stop = true;
  84. if (!stop) name += c;
  85. }
  86. // Log.i("bone", name);
  87. parentIndex = (int)data.get();
  88. invBindPose = new float[16];
  89. data.asFloatBuffer().get(invBindPose);
  90. data.position(data.position() + 64);
  91. }
  92. }
  93. private class Action {
  94. public String name; // 16 bytes
  95. public int numFrames;
  96. public ArrayList<Track> tracks;
  97. public static final int kHeaderSize = 28;
  98. Action(ByteBuffer data) {
  99. name = "";
  100. boolean stop = false;
  101. for (int i = 0; i < 16; i++) {
  102. char c;
  103. if ((c = (char)data.get()) == '\0') stop = true;
  104. if (!stop) name += c;
  105. }
  106. Log.i("action", name);
  107. numFrames = data.getInt();
  108. int trackOffset = data.getInt();
  109. int trackCount = data.getInt();
  110. tracks = new ArrayList<Track>();
  111. for (int i = 0; i < trackCount; i++) {
  112. data.position(trackOffset + i * Track.kHeaderSize);
  113. tracks.add(new Track(data));
  114. }
  115. }
  116. }
  117. private class Track {
  118. @SuppressWarnings("unused")
  119. public int boneIndex;
  120. public ArrayList<JointPose> poses;
  121. public static final int kHeaderSize = 12;
  122. Track(ByteBuffer data) {
  123. boneIndex = data.getInt();
  124. int jointPoseOffset = data.getInt();
  125. int jointPoseCount = data.getInt();
  126. poses = new ArrayList<JointPose>();
  127. data.position(jointPoseOffset);
  128. for (int i = 0; i < jointPoseCount; i++) {
  129. //data.position(jointPoseOffset + i * JointPose::kSize); // joint pose size == 32
  130. poses.add(new JointPose(data));
  131. }
  132. }
  133. }
  134. private class JointPose {
  135. public Quaternion rotation;
  136. public float[] translation;
  137. @SuppressWarnings("unused")
  138. float scale;
  139. public static final int kSize = 32;
  140. JointPose() { // empty pose == identity
  141. rotation = new Quaternion();
  142. translation = new float[3];
  143. translation[0] = translation[1] = translation[2] = 0.0f;
  144. scale = 1.0f;
  145. }
  146. JointPose(ByteBuffer data) {
  147. FloatBuffer floatData = data.asFloatBuffer();
  148. data.position(data.position() + kSize);
  149. // quat data is x y z w, because of glm
  150. float x = floatData.get();
  151. float y = floatData.get();
  152. float z = floatData.get();
  153. float w = floatData.get();
  154. rotation = new Quaternion(w, x, y, z);
  155. translation = new float[3];
  156. floatData.get(translation);
  157. scale = floatData.get();
  158. }
  159. float[] toMatrix() { // TODO: scale
  160. float[] matrix = new float[16];
  161. Matrix.setIdentityM(matrix, 0);
  162. Quaternion q = rotation;
  163. matrix[0 * 4 + 0] = 1.0f - 2.0f * q.y * q.y - 2.0f * q.z * q.z;
  164. matrix[0 * 4 + 1] = 2.0f * q.x * q.y + 2.0f * q.w * q.z;
  165. matrix[0 * 4 + 2] = 2.0f * q.x * q.z - 2.0f * q.w * q.y;
  166. matrix[1 * 4 + 0] = 2.0f * q.x * q.y - 2.0f * q.w * q.z;
  167. matrix[1 * 4 + 1] = 1.0f - 2.0f * q.x * q.x - 2.0f * q.z * q.z;
  168. matrix[1 * 4 + 2] = 2.0f * q.y * q.z + 2.0f * q.w * q.x;
  169. matrix[2 * 4 + 0] = 2.0f * q.x * q.z + 2 * q.w * q.y;
  170. matrix[2 * 4 + 1] = 2.0f * q.y * q.z - 2 * q.w * q.x;
  171. matrix[2 * 4 + 2] = 1.0f - 2.0f * q.x * q.x - 2.0f * q.y * q.y;
  172. matrix[3 * 4 + 0] = translation[0];
  173. matrix[3 * 4 + 1] = translation[1];
  174. matrix[3 * 4 + 2] = translation[2];
  175. return matrix;
  176. }
  177. }
  178. /**
  179. * create an animmesh from an inputstream
  180. * @param is the inputstream
  181. */
  182. public AnimatedMesh(InputStream is) {
  183. ByteArrayOutputStream out = new ByteArrayOutputStream();
  184. try {
  185. final int EOF = -1;
  186. int len;
  187. byte[] buffer = new byte[1 << 12];
  188. while (EOF != (len = is.read(buffer)))
  189. out.write(buffer, 0, len);
  190. } catch (IOException e) {
  191. e.printStackTrace();
  192. return;
  193. }
  194. //data = ByteBuffer.wrap(out.toByteArray()); // doesn't work data needs to be direct
  195. data = ByteBuffer.allocateDirect(out.size());
  196. data.order(ByteOrder.nativeOrder());
  197. data.put(out.toByteArray());
  198. data.position(0);
  199. // header
  200. int magicNum = data.getInt();
  201. int version = data.getInt();
  202. //assert(magicNum == ('A' << 24 | 'M' << 16 | 'S' << 8 | 'H') && version == 1);
  203. vertexSize = 48;
  204. vertexOffset = data.getInt();
  205. int vertexCount = data.getInt();
  206. triangleOffset = data.getInt();
  207. triangleCount = data.getInt();
  208. int boneOffset = data.getInt();
  209. int boneCount = data.getInt();
  210. int actionOffset = data.getInt();
  211. int actionCount = data.getInt();
  212. // vertices and indices data
  213. IntBuffer buffers = IntBuffer.allocate(2);
  214. GLES20.glGenBuffers(2, buffers); // buffer names
  215. vertexBuffer = buffers.get();
  216. indexBuffer = buffers.get();
  217. data.position(vertexOffset);
  218. GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vertexBuffer);
  219. GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, vertexSize * vertexCount, data.asFloatBuffer(), GLES20.GL_STATIC_DRAW);
  220. GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
  221. // convert int indices to short
  222. // TODO: more efficient way?
  223. data.position(triangleOffset);
  224. ShortBuffer indexBufferData = ShortBuffer.allocate(3 * triangleCount);
  225. for (int i = 0; i < 3 * triangleCount; i++)
  226. indexBufferData.put((short)data.getInt());
  227. indexBufferData.position(0);
  228. GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
  229. GLES20.glBufferData(GLES20.GL_ELEMENT_ARRAY_BUFFER, 6 * triangleCount, indexBufferData, GLES20.GL_STATIC_DRAW);
  230. GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
  231. // bones
  232. bones = new ArrayList<Bone>();
  233. data.position(boneOffset);
  234. for (int i = 0; i < boneCount; i++)
  235. bones.add(new Bone(data));
  236. matrices = new float[16 * boneCount];
  237. // actions
  238. actions = new ArrayList<Action>();
  239. for (int i = 0; i < actionCount; i++) {
  240. data.position(actionOffset + i * Action.kHeaderSize); // action header size == 28
  241. actions.add(new Action(data));
  242. }
  243. currentAction = null;
  244. currentFrame = 0;
  245. loopAction = false;
  246. }
  247. public static float[] addVec3(final float[] v1, final float[] v2) {
  248. float[] v3 = new float[3];
  249. v3[0] = v1[0] + v2[0];
  250. v3[1] = v1[1] + v2[1];
  251. v3[2] = v1[2] + v2[2];
  252. return v3;
  253. }
  254. /**
  255. * update the bone transforms of the mesh and advance one frame
  256. */
  257. public void tick() {
  258. // empty pose
  259. ArrayList<JointPose> pose = new ArrayList<JointPose>();
  260. for (int i = 0; i < bones.size(); i++)
  261. pose.add(new JointPose());
  262. if (currentAction != null) {
  263. // fill pose with action
  264. for (int i = 0; i < currentAction.tracks.size(); i++) {
  265. // TODO: do lerp or something nice
  266. pose.get(i).rotation = currentAction.tracks.get(i).poses.get(currentFrame).rotation;
  267. pose.get(i).translation = currentAction.tracks.get(i).poses.get(currentFrame).translation;
  268. }
  269. // advance one frame
  270. if (reverseAction) {
  271. if (currentFrame > 0) {
  272. currentFrame--;
  273. } else if (loopAction) {
  274. currentFrame = currentAction.numFrames - 1;
  275. }
  276. } else {
  277. if (currentFrame < currentAction.numFrames - 1) {
  278. currentFrame++;
  279. } else if (loopAction) {
  280. currentFrame = 0;
  281. }
  282. }
  283. }
  284. // convert pose to skinning matrices
  285. for (int i = 0; i < bones.size(); i++) {
  286. int parentIndex = bones.get(i).parentIndex;
  287. if (parentIndex != -1) { // bone has parent
  288. JointPose parentPose = pose.get(parentIndex);
  289. pose.get(i).rotation = parentPose.rotation.multiply(pose.get(i).rotation);
  290. pose.get(i).translation = addVec3(parentPose.translation, parentPose.rotation.multiply(pose.get(i).translation));
  291. }
  292. Matrix.multiplyMM(matrices, i * 16, pose.get(i).toMatrix(), 0, bones.get(i).invBindPose, 0);
  293. }
  294. }
  295. /**
  296. * draws the mesh
  297. * @param program the currently bound shader program
  298. */
  299. public void draw(int program) {
  300. GLES20.glUniformMatrix4fv(GLES20.glGetUniformLocation(program, "boneMatrices"), bones.size(), false, matrices, 0);
  301. // TODO: cache attrib locations
  302. int positionIndex = GLES20.glGetAttribLocation(program, "position");
  303. int normalIndex = GLES20.glGetAttribLocation(program, "normal");
  304. int texCoordIndex = GLES20.glGetAttribLocation(program, "texCoord");
  305. int boneIndicesIndex = GLES20.glGetAttribLocation(program, "boneIndices");
  306. int boneWeightsIndex = GLES20.glGetAttribLocation(program, "boneWeights");
  307. GLES20.glEnableVertexAttribArray(positionIndex);
  308. GLES20.glEnableVertexAttribArray(normalIndex);
  309. GLES20.glEnableVertexAttribArray(texCoordIndex);
  310. GLES20.glEnableVertexAttribArray(boneIndicesIndex);
  311. GLES20.glEnableVertexAttribArray(boneWeightsIndex);
  312. GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vertexBuffer);
  313. GLES20.glVertexAttribPointer(positionIndex, 3, GLES20.GL_FLOAT, false, vertexSize, 0);
  314. GLES20.glVertexAttribPointer(normalIndex, 3, GLES20.GL_FLOAT, false, vertexSize, 12);
  315. GLES20.glVertexAttribPointer(texCoordIndex, 2, GLES20.GL_FLOAT, false, vertexSize, 24);
  316. GLES20.glVertexAttribPointer(boneIndicesIndex, 4, GLES20.GL_UNSIGNED_BYTE, false, vertexSize, 32);
  317. GLES20.glVertexAttribPointer(boneWeightsIndex, 3, GLES20.GL_FLOAT, false, vertexSize, 36);
  318. GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
  319. GLES20.glDrawElements(GLES20.GL_TRIANGLES, 3 * triangleCount, GLES20.GL_UNSIGNED_SHORT, 0);
  320. GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, 0);
  321. GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
  322. GLES20.glDisableVertexAttribArray(positionIndex);
  323. GLES20.glDisableVertexAttribArray(normalIndex);
  324. GLES20.glDisableVertexAttribArray(texCoordIndex);
  325. GLES20.glDisableVertexAttribArray(boneIndicesIndex);
  326. GLES20.glDisableVertexAttribArray(boneWeightsIndex);
  327. }
  328. }