Browse Source

animation is working :D

Fabio Arnold 10 years ago
parent
commit
73ce2bd999

+ 10 - 0
assets/shaders/skinned.frag

@@ -0,0 +1,10 @@
+precision mediump float;
+
+uniform vec4 color;
+
+varying vec3 vertexNormal;
+
+void main() {
+	vec3 normal = normalize(vertexNormal);
+	gl_FragColor = max(0.0, -normal.y) * color;
+}

+ 22 - 0
assets/shaders/skinned.vert

@@ -0,0 +1,22 @@
+uniform mat4 mvp;
+uniform mat4 boneMatrices[16];
+
+attribute vec3 position;
+attribute vec3 normal;
+
+attribute vec4 boneIndices; // should be ivec4
+attribute vec3 boneWeights;
+
+varying vec3 vertexNormal;
+
+void main() {
+	float fourthBoneWeight = 1.0 - boneWeights[0] - boneWeights[1] - boneWeights[2];
+
+	mat4 boneMatrix = boneWeights[0] * boneMatrices[int(boneIndices.x)];
+	boneMatrix += boneWeights[1] * boneMatrices[int(boneIndices.y)];
+	boneMatrix += boneWeights[2] * boneMatrices[int(boneIndices.z)];
+	boneMatrix += fourthBoneWeight * boneMatrices[int(boneIndices.w)];
+
+	vertexNormal = mat3(boneMatrix) * normal;
+	gl_Position = mvp * boneMatrix * vec4(position, 1.0);
+}

+ 192 - 1
src/de/tudarmstadt/informatik/hostage/ui2/fragment/opengl/AnimatedMesh.java

@@ -8,8 +8,10 @@ import java.nio.ByteOrder;
 import java.nio.FloatBuffer;
 import java.nio.IntBuffer;
 import java.nio.ShortBuffer;
+import java.util.ArrayList;
 
 import android.opengl.GLES20;
+import android.opengl.Matrix;
 import android.util.Log;
 
 public class AnimatedMesh {
@@ -22,6 +24,128 @@ public class AnimatedMesh {
 	
 	private int vertexBuffer; // vbo
 	private int indexBuffer;
+
+	private ArrayList<Bone> bones;
+	private float[] matrices; // matrix palette for skinning
+	
+	private ArrayList<Action> actions;
+	private Action currentAction;
+	private int currentFrame;
+	
+	private class Bone {
+		public String name; // 15 bytes
+		public int parentIndex; // 1 byte
+		public float[] invBindPose; // 64 bytes
+		
+		Bone(ByteBuffer data) {
+			name = "";
+			boolean stop = false;
+			for (int i = 0; i < 15; i++) {
+				char c;
+				if ((c = (char)data.get()) == '\0') stop = true;
+				if (!stop) name += c;
+			}
+			Log.i("bone", name);
+			parentIndex = (int)data.get();
+			invBindPose = new float[16];
+			data.asFloatBuffer().get(invBindPose);
+			data.position(data.position() + 64);
+		}
+	}
+	
+	private class Action {
+		public String name; // 16 bytes
+		public int numFrames;
+		public ArrayList<Track> tracks;
+		
+		Action(ByteBuffer data) {
+			name = "";
+			boolean stop = false;
+			for (int i = 0; i < 16; i++) {
+				char c;
+				if ((c = (char)data.get()) == '\0') stop = true;
+				if (!stop) name += c;
+			}
+			Log.i("action", name);
+			numFrames = data.getInt();
+			int trackOffset = data.getInt();
+			int trackCount = data.getInt();
+			tracks = new ArrayList<Track>();
+			for (int i = 0; i < trackCount; i++) {
+				data.position(trackOffset + i * 12); // track header size == 12
+				tracks.add(new Track(data));
+			}
+		}
+	}
+	
+	private class Track {
+		public int boneIndex;
+		public ArrayList<JointPose> poses;
+		
+		Track(ByteBuffer data) {
+			boneIndex = data.getInt();
+			int jointPoseOffset = data.getInt();
+			int jointPoseCount = data.getInt();
+			poses = new ArrayList<JointPose>();
+			data.position(jointPoseOffset);
+			for (int i = 0; i < jointPoseCount; i++) {
+				//data.position(jointPoseOffset + i * 32); // joint pose size == 32
+				poses.add(new JointPose(data));
+			}
+		}
+	}
+	
+	private class JointPose {
+		public Quaternion rotation;
+		public float[] translation;
+		float scale;
+		
+		JointPose() { // empty pose == identity
+			rotation = new Quaternion();
+			translation = new float[3];
+			translation[0] = translation[1] = translation[2] = 0.0f;
+			scale = 1.0f;
+		}
+		
+		JointPose(ByteBuffer data) {
+			FloatBuffer floatData = data.asFloatBuffer();
+			data.position(data.position() + 32);
+ 
+			// quat data is x y z w, because of glm
+			float x = floatData.get();
+			float y = floatData.get();
+			float z = floatData.get();
+			float w = floatData.get();
+			rotation = new Quaternion(w, x, y, z);
+			translation = new float[3];
+			floatData.get(translation);
+			scale = floatData.get();
+		}
+		
+		float[] toMatrix() { // TODO: scale
+			float[] matrix = new float[16];
+			Matrix.setIdentityM(matrix, 0);
+
+			Quaternion q = rotation;
+			matrix[0 * 4 + 0] = 1.0f - 2.0f * q.y * q.y - 2.0f * q.z * q.z;
+			matrix[0 * 4 + 1] = 2.0f * q.x * q.y + 2.0f * q.w * q.z;
+			matrix[0 * 4 + 2] = 2.0f * q.x * q.z - 2.0f * q.w * q.y;
+
+			matrix[1 * 4 + 0] = 2.0f * q.x * q.y - 2.0f * q.w * q.z;
+			matrix[1 * 4 + 1] = 1.0f - 2.0f * q.x * q.x - 2.0f * q.z * q.z;
+			matrix[1 * 4 + 2] = 2.0f * q.y * q.z + 2.0f * q.w * q.x;
+
+			matrix[2 * 4 + 0] = 2.0f * q.x * q.z + 2 * q.w * q.y;
+			matrix[2 * 4 + 1] = 2.0f * q.y * q.z - 2 * q.w * q.x;
+			matrix[2 * 4 + 2] = 1.0f - 2.0f * q.x * q.x - 2.0f * q.y * q.y;
+
+			matrix[3 * 4 + 0] = translation[0];
+			matrix[3 * 4 + 1] = translation[1];
+			matrix[3 * 4 + 2] = translation[2];
+			
+			return matrix;
+		}
+	}
 	
 	public AnimatedMesh(InputStream is) {
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
@@ -36,12 +160,13 @@ public class AnimatedMesh {
 			return;
 		}
 
-		//data = ByteBuffer.wrap(out.toByteArray());
+		//data = ByteBuffer.wrap(out.toByteArray()); // doesn't work data needs to be direct
 		data = ByteBuffer.allocateDirect(out.size());
 		data.order(ByteOrder.nativeOrder());
 		data.put(out.toByteArray());
 		data.position(0);
 
+		// header
 		int magicNum = data.getInt();
 		int version = data.getInt();
 		assert(magicNum == ('A' << 24 | 'M' << 16 | 'S' << 8 | 'H') && version == 1);
@@ -56,6 +181,7 @@ public class AnimatedMesh {
 		int actionOffset = data.getInt();
 		int actionCount = data.getInt();
 		
+		// vertices and indices data
 		IntBuffer buffers = IntBuffer.allocate(2);
 		GLES20.glGenBuffers(2, buffers); // buffer names
 		vertexBuffer = buffers.get();
@@ -77,21 +203,84 @@ public class AnimatedMesh {
 		GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
 		GLES20.glBufferData(GLES20.GL_ELEMENT_ARRAY_BUFFER, 6 * triangleCount, indexBufferData, GLES20.GL_STATIC_DRAW);
 		GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
+		
+		// bones
+		bones = new ArrayList<Bone>();
+		data.position(boneOffset);
+		for (int i = 0; i < boneCount; i++)
+			bones.add(new Bone(data));
+		
+		matrices = new float[16 * boneCount];
+		
+		// actions
+		actions = new ArrayList<Action>();
+		for (int i = 0; i < actionCount; i++) {
+			data.position(actionOffset + i * 28); // action header size == 28
+			actions.add(new Action(data));
+		}
+		
+		currentAction = actions.get(0);
+		currentFrame = 0;
+	}
+	
+	public static float[] addVec3(final float[] v1, final float[] v2) {
+		float[] v3 = new float[3];
+		v3[0] = v1[0] + v2[0];
+		v3[1] = v1[1] + v2[1];
+		v3[2] = v1[2] + v2[2];
+		return v3;
+	}
+	
+	public void tick() {
+		currentFrame++;
+		if (currentFrame >= currentAction.numFrames)
+			currentFrame = 0;
+		
+		// empty pose
+		ArrayList<JointPose> pose = new ArrayList<JointPose>();
+		for (int i = 0; i < bones.size(); i++)
+			pose.add(new JointPose());
+		
+		// fill pose with action
+		for (int i = 0; i < currentAction.tracks.size(); i++) {
+			// TODO: do lerp or something nice
+			pose.get(i).rotation = currentAction.tracks.get(i).poses.get(currentFrame).rotation;
+			pose.get(i).translation = currentAction.tracks.get(i).poses.get(currentFrame).translation;
+		}
+		
+		// convert pose to skinning matrices
+		for (int i = 0; i < bones.size(); i++) {
+			int parentIndex = bones.get(i).parentIndex;
+			if (parentIndex != -1) { // bone has parent
+				JointPose parentPose = pose.get(parentIndex);
+				pose.get(i).rotation = parentPose.rotation.multiply(pose.get(i).rotation);
+				pose.get(i).translation = addVec3(parentPose.translation, parentPose.rotation.multiply(pose.get(i).translation));
+			}
+			Matrix.multiplyMM(matrices, i * 16, pose.get(i).toMatrix(), 0, bones.get(i).invBindPose, 0);
+		}
 	}
 	
 	public void draw(int program) {
+		GLES20.glUniformMatrix4fv(GLES20.glGetUniformLocation(program, "boneMatrices"), bones.size(), false, matrices, 0);
+		
 		// TODO: cache attrib locations
 		int positionIndex = GLES20.glGetAttribLocation(program, "position");
 		int normalIndex = GLES20.glGetAttribLocation(program, "normal");
+		int boneIndicesIndex = GLES20.glGetAttribLocation(program, "boneIndices");
+		int boneWeightsIndex = GLES20.glGetAttribLocation(program, "boneWeights");
 		
 		GLES20.glEnableVertexAttribArray(positionIndex);
 		GLES20.glEnableVertexAttribArray(normalIndex);
+		GLES20.glEnableVertexAttribArray(boneIndicesIndex);
+		GLES20.glEnableVertexAttribArray(boneWeightsIndex);
 		
 		//data.position(vertexOffset);
 		//GLES20.glVertexAttribPointer(positionIndex, 3, GLES20.GL_FLOAT, false, vertexSize, data.asFloatBuffer());
 		GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vertexBuffer);
 		GLES20.glVertexAttribPointer(positionIndex, 3, GLES20.GL_FLOAT, false, vertexSize, 0);
 		GLES20.glVertexAttribPointer(normalIndex, 3, GLES20.GL_FLOAT, false, vertexSize, 12);
+		GLES20.glVertexAttribPointer(boneIndicesIndex, 4, GLES20.GL_UNSIGNED_BYTE, false, vertexSize, 32);
+		GLES20.glVertexAttribPointer(boneWeightsIndex, 3, GLES20.GL_FLOAT, false, vertexSize, 36);
 		//data.position(triangleOffset);
 		//GLES20.glDrawElements(GLES20.GL_TRIANGLES, 3 * triangleCount, GLES20.GL_UNSIGNED_INT, data.asIntBuffer());
 		GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
@@ -101,5 +290,7 @@ public class AnimatedMesh {
 		
 		GLES20.glDisableVertexAttribArray(positionIndex);
 		GLES20.glDisableVertexAttribArray(normalIndex);
+		GLES20.glDisableVertexAttribArray(boneIndicesIndex);
+		GLES20.glDisableVertexAttribArray(boneWeightsIndex);
 	}
 }

+ 67 - 0
src/de/tudarmstadt/informatik/hostage/ui2/fragment/opengl/Quaternion.java

@@ -0,0 +1,67 @@
+package de.tudarmstadt.informatik.hostage.ui2.fragment.opengl;
+
+public class Quaternion {
+	public float w, x, y, z;
+	
+	Quaternion() { // identity
+		w = 1.0f; x = y = z = 0.0f;
+	}
+	
+	Quaternion(float w, float x, float y, float z) {
+		this.w = w;
+		this.x = x;
+		this.y = y;
+		this.z = z;
+	}
+	
+	public Quaternion clone() {
+		return new Quaternion(w, x, y, z);
+	}
+	
+	public Quaternion multiply(final Quaternion p) {
+		Quaternion q = this;
+		return new Quaternion(
+				q.w * p.w - q.x * p.x - q.y * p.y - q.z * p.z,
+				q.w * p.x + q.x * p.w + q.y * p.z - q.z * p.y,
+				q.w * p.y + q.y * p.w + q.z * p.x - q.x * p.z,
+				q.w * p.z + q.z * p.w + q.x * p.y - q.y * p.x);
+	}
+	
+	public static float[] cross(final float[] v1, final float[] v2) {
+		float[] v3 = new float[3];
+		v3[0] = v1[1] * v2[2] - v1[2] * v2[1];
+		v3[1] = v1[2] * v2[0] - v1[0] * v2[2];
+		v3[2] = v1[0] * v2[1] - v1[1] * v2[0];	
+		return v3;
+	}
+	
+	public static float[] multiply(final float s, final float[] v) {
+		float[] result = new float[3];
+		result[0] = s * v[0];
+		result[1] = s * v[1];
+		result[2] = s * v[2];
+		return result;
+	}
+	
+	public static float[] add(final float[] v1, final float[] v2) {
+		float[] v3 = new float[3];
+		v3[0] = v1[0] + v2[0];
+		v3[1] = v1[1] + v2[1];
+		v3[2] = v1[2] + v2[2];
+		return v3;
+	}
+	
+	public float[] multiply(final float[] v) { // rotate a point
+		Quaternion q = this;
+		float[] axis = new float[3];
+		axis[0] = q.x; axis[1] = q.y; axis[2] = q.z;
+		
+		float[] uv = cross(axis, v);
+		float[] uuv = cross(axis, uv);
+		
+		uv = multiply(2.0f * q.w, uv);
+		uuv = multiply(2.0f, uuv);
+
+		return add(v, add(uv, uuv));
+	}
+}

+ 24 - 17
src/de/tudarmstadt/informatik/hostage/ui2/fragment/opengl/ThreatIndicatorGLRenderer.java

@@ -5,6 +5,7 @@ import java.io.InputStream;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.nio.FloatBuffer;
+import java.util.Scanner;
 
 import javax.microedition.khronos.egl.EGLConfig;
 import javax.microedition.khronos.opengles.GL10;
@@ -21,6 +22,7 @@ public class ThreatIndicatorGLRenderer implements Renderer {
 		int shader = GLES20.glCreateShader(type);
 		GLES20.glShaderSource(shader, source);
 		GLES20.glCompileShader(shader);
+		Log.i("gl", GLES20.glGetShaderInfoLog(shader));
 		return shader;
 	}
 	
@@ -76,26 +78,29 @@ public class ThreatIndicatorGLRenderer implements Renderer {
 		projection = new float[16];
 		mvp = new float[16];
 		
-		final String vertexSource =
-				"uniform mat4 mvp;" +
-				"attribute vec3 position;" +
-				"attribute vec3 normal;" +
-				"varying vec3 vertexNormal;" +
-				"void main() {" +
-				"	vertexNormal = normal;" +
-				"	gl_Position = mvp * vec4(position, 1.0);" +
-				"}";
-		final String fragmentSource =
-				"precision mediump float;" +
-				"uniform vec4 color;" +
-				"varying vec3 vertexNormal;" +
-				"void main() {" +
-				"	vec3 normal = normalize(vertexNormal);" +
-				"	gl_FragColor = max(0.0, -normal.y) * color;" +
-				"}";
+		String vertexSource = "attribute vec3 position; void main() {gl_Position = vec4(position, 1.0);}";
+		String fragmentSource = "void main() {gl_FragColor = vec4(1.0, 0.0, 1.0, 1.0);}";
+		try {
+			vertexSource = inputStreamToString(assets.open("shaders/skinned.vert"));
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+		try {fragmentSource = inputStreamToString(assets.open("shaders/skinned.frag"));
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
 		program = loadProgram(vertexSource, fragmentSource);
 	}
 	
+	// see http://stackoverflow.com/questions/309424/read-convert-an-inputstream-to-a-string
+	private static String inputStreamToString(InputStream is) {
+	    Scanner scanner = new Scanner(is);
+		Scanner s = scanner.useDelimiter("\\A");
+	    String result = s.hasNext() ? s.next() : "";
+	    scanner.close();
+	    return result;
+	}
+
 	public void onDrawFrame(GL10 arg0) {
 		GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
 		
@@ -107,6 +112,7 @@ public class ThreatIndicatorGLRenderer implements Renderer {
 		GLES20.glUniform4fv(colorUniformLoc, 1, color, 0);
 		//Matrix.rotateM(mvp, 0, 4.0f, 0.0f, 0.0f, 1.0f);
 		Matrix.setIdentityM(modelview, 0);
+		Matrix.translateM(modelview, 0, 0.0f, -0.5f, 0.0f);
 		Matrix.rotateM(modelview, 0, -90.0f, 1.0f, 0.0f, 0.0f);
 		Matrix.multiplyMM(mvp, 0, projection, 0, modelview, 0);
 		
@@ -119,6 +125,7 @@ public class ThreatIndicatorGLRenderer implements Renderer {
 		GLES20.glDisableVertexAttribArray(positionAttribLoc);
 		 */
 		
+		androidMesh.tick();
 		androidMesh.draw(program);
 	}