GPUAnimated3D
package yournamespacegoeshere;
import yournamespacegoeshere.GPUAnimated3DShader;
import com.threed.jpct.GLSLShader;
import com.threed.jpct.IRenderHook;
import com.threed.jpct.Object3D;
import raft.jpct.bones.Animated3D;
import raft.jpct.bones.BonesNamespaceUtils;
import raft.jpct.bones.SkinClip;
/**
* Created by Dougie on 6/21/16.
*/
public class GPUAnimated3D extends Animated3D implements IRenderHook {
public GPUAnimated3D(Animated3D object) {
super(object);
BonesNamespaceUtils.setSkinAttributes(object);
setRenderHook(this);
}
public void animateSkin(float index, int sequence) {
if(getSkinClipSequence() != null) {
if(sequence == 0) {
BonesNamespaceUtils.animate(this, index*getSkinClipSequence().getTime(), getSkeletonPose());
} else {
SkinClip clip = getSkinClipSequence().getClip(sequence - 1);
clip.applyTo(index * clip.getTime(), getSkeletonPose());
}
getSkeletonPose().updateTransforms();
}
}
public void animatePose(float index, int sequence, float weight) {
BonesNamespaceUtils.animatePoseDontApply(this, index, sequence, weight);
}
@Override
public void beforeRendering(int i) { ; }
@Override
public void afterRendering(int i) { ; }
@Override
public void setCurrentObject3D(Object3D object3D) { ; }
@Override
public void setCurrentShader(GLSLShader glslShader) {
if(glslShader!=null && glslShader instanceof GPUAnimated3DShader) {
((GPUAnimated3DShader)glslShader).updateBeforeRenderingObject(this);
}
}
@Override
public void setTransparency(float v) { ; }
@Override
public void onDispose() { ; }
@Override
public boolean repeatRendering() { return false; }
}
GPUAnimated3DShader
package yournamespacegoeshere;
import android.opengl.GLES20;
import com.threed.jpct.GLSLShader;
import com.threed.jpct.Matrix;
import raft.jpct.bones.Animated3D;
import raft.jpct.bones.BonesNamespaceUtils;
/**
* Created by Dougie on 6/21/16.
*/
public class GPUAnimated3DShader extends GLSLShader {
int skinWeightsHandle = -1;
int jointIndicesHandle = -1;
public GPUAnimated3DShader(String vertexShaderSource, String fragmentShaderSource) {
super(vertexShaderSource, fragmentShaderSource);
skinWeightsHandle = GLES20.glGetAttribLocation(getProgram(), BonesNamespaceUtils.ATTR_SKIN_WEIGHTS);
jointIndicesHandle = GLES20.glGetAttribLocation(getProgram(), BonesNamespaceUtils.ATTR_JOINT_INDICES);
}
public void updateBeforeRenderingObject(Animated3D pObject) {
if(pObject!=null) {
Matrix[] skelPose = BonesNamespaceUtils.getSkeletonPosePallete(pObject.getSkeletonPose());
if(skelPose!=null && skelPose.length>0) {
setUniform(BonesNamespaceUtils.UNIFORM_SKEL_POSE_SIZE, skelPose.length);
setUniform(BonesNamespaceUtils.UNIFORM_SKEL_POSE, skelPose);
} else {
setUniform(BonesNamespaceUtils.UNIFORM_SKEL_POSE_SIZE, 0);
}
}
}
}
JPCTNamespaceUtils
package com.threed.jpct;
/**
* Created by Dougie on 6/18/16.
*/
public class JPCTNamespaceUtils {
public static boolean VertexAttributesNameIs(VertexAttributes pVertAttrs, String pName) {
if(pVertAttrs!=null && pName!=null && pVertAttrs.name!=null && pVertAttrs.name.equals(pName))
return true;
return false;
}
}
BonesNamespaceUtils
package raft.jpct.bones;
import com.threed.jpct.Matrix;
import com.threed.jpct.JPCTNamespaceUtils;
import com.threed.jpct.VertexAttributes;
/**
* Created by Dougie on 6/17/16.
*/
public class BonesNamespaceUtils {
public static String ATTR_SKIN_WEIGHTS = "skinWeights";
public static String ATTR_JOINT_INDICES = "jointIndices";
public static String UNIFORM_SKEL_POSE = "skelPose";
public static String UNIFORM_SKEL_POSE_SIZE = "skelPoseSize";
public static void animate(Animated3D pAnimated3D, float pTime, SkeletonPose pPose) {
if(pAnimated3D!=null && pAnimated3D.getSkinClipSequence()!=null) {
pAnimated3D.getSkinClipSequence().animate(pTime, pPose);
}
}
public static void animatePoseDontApply(Animated3D pAnimated3D, float index, int sequence, float weight) {
if(pAnimated3D!=null) {
pAnimated3D.animatePoseDontApply(index, sequence, weight);
}
}
public static Matrix[] getSkeletonPosePallete(SkeletonPose pPose) {
if(pPose!=null) {
return pPose.palette;
}
return null;
}
public static void setSkinAttributes(Animated3D pAnimated3D) {
int i, j, k, len;
if(pAnimated3D != null && pAnimated3D.getMesh()!=null) {
boolean hasWeights = false, hasJointIndices = false;
VertexAttributes[] vertAttrs = pAnimated3D.getMesh().getVertexAttributes();
if(vertAttrs!=null) {
//Loop backwards as they should be the last added ones if shared mesh
for(i=vertAttrs.length-1;i>=0;--i) {
if(JPCTNamespaceUtils.VertexAttributesNameIs(vertAttrs[i], ATTR_SKIN_WEIGHTS)) {
hasWeights = true;
}
if(JPCTNamespaceUtils.VertexAttributesNameIs(vertAttrs[i], ATTR_JOINT_INDICES)) {
hasJointIndices = true;
}
if(hasWeights && hasJointIndices) {
break;
}
}
}
if(!hasWeights) {
float[][] weights = pAnimated3D.skin.weights;
if(weights != null && weights.length > 0) {
len = weights[0].length;
float[] weightsArr = new float[pAnimated3D.getMesh().getUniqueVertexCount() * len];
for (j = 0; j < weights.length; ++j) {
for (k = 0; k < len; ++k) {
weightsArr[j * len + k] = weights[j][k];
}
}
if (weights.length < pAnimated3D.getMesh().getUniqueVertexCount()) {
for (j = weights.length; j < pAnimated3D.getMesh().getUniqueVertexCount(); ++j) {
for (k = 0; k < len; ++k) {
weightsArr[j * len + k] = 0f;
}
}
}
pAnimated3D.getMesh().addVertexAttributes(new VertexAttributes(ATTR_SKIN_WEIGHTS, weightsArr, len));
}
}
if(!hasJointIndices) {
short[][] jointIndices = pAnimated3D.skin.jointIndices;
if (jointIndices != null && jointIndices.length > 0) {
len = jointIndices[0].length;
float[] jointIndicesArr = new float[pAnimated3D.getMesh().getUniqueVertexCount() * len];
for (j = 0; j < jointIndices.length; ++j) {
for (k = 0; k < len; ++k) {
jointIndicesArr[j * len + k] = jointIndices[j][k];
}
}
if (jointIndices.length < pAnimated3D.getMesh().getUniqueVertexCount()) {
for (j = jointIndices.length; j < pAnimated3D.getMesh().getUniqueVertexCount(); ++j) {
for (k = 0; k < len; ++k) {
jointIndicesArr[j * len + k] = 0f;
}
}
}
pAnimated3D.getMesh().addVertexAttributes(new VertexAttributes(ATTR_JOINT_INDICES, jointIndicesArr, len));
}
}
}
}
}
Vertex Shader Snippet
...
uniform mat4 modelViewProjectionMatrix;
...
uniform mat4 skelPose[50];
uniform int skelPoseSize;
attribute vec4 position;
attribute vec3 normal;
...
attribute vec4 skinWeights;
attribute vec4 jointIndices;
void main(void)
{
vec4 newPosition = vec4(0.0,0.0,0.0, position[3]);
vec3 newNormal = vec3(0.0,0.0,0.0);
if(skelPoseSize>0) {
float weight;
for(int j=0; j<4; ++j) {
weight = skinWeights[j];
//newPosition[j] += weight;
if(weight != 0.0) {
mat4 boneMat = skelPose[int(floor(jointIndices[j]+0.5))];
newPosition.xyz += vec3(position.x*boneMat[0][0] + position.y*boneMat[1][0] + position.z*boneMat[2][0] + boneMat[3][0],
position.x*boneMat[0][1] + position.y*boneMat[1][1] + position.z*boneMat[2][1] + boneMat[3][1],
position.x*boneMat[0][2] + position.y*boneMat[1][2] + position.z*boneMat[2][2] + boneMat[3][2])*weight;
newNormal += vec3(normal.x*boneMat[0][0] + normal.y*boneMat[1][0] + normal.z*boneMat[2][0],
normal.x*boneMat[0][1] + normal.y*boneMat[1][1] + normal.z*boneMat[2][1],
normal.x*boneMat[0][2] + normal.y*boneMat[1][2] + normal.z*boneMat[2][2])*weight;
}
}
newPosition.yz *= -1.0;
newNormal.yz *= -1.0;
} else {
newPosition = position;
newNormal = normal;
}
...
gl_Position = modelViewProjectionMatrix * newPosition;
}
Usage:
Use the GPUAnimated3D constructor passing in the Animated3D you want it to be. It will clone the Animated3D reusing the Mesh.
Afterwards, call GPUAnimated3D.setShader() passing in a GPUAnimated3DShader using the integrated version of the vertex shader above.
Call your Bones animateSkin() methods as you normally would and watch it animate using the GPU instead.