/******************************************************************************* * Copyright 2011 See AUTHORS file. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. ******************************************************************************/ package com.badlogic.gdx.graphics.g3d.model.skeleton; import com.badlogic.gdx.graphics.VertexAttributes.Usage; import com.badlogic.gdx.graphics.g3d.materials.Material; import com.badlogic.gdx.graphics.g3d.model.AnimatedModel; import com.badlogic.gdx.graphics.g3d.model.Model; import com.badlogic.gdx.graphics.g3d.model.SubMesh; import com.badlogic.gdx.graphics.glutils.ShaderProgram; import com.badlogic.gdx.math.Matrix4; import com.badlogic.gdx.math.Vector3; import com.badlogic.gdx.math.collision.BoundingBox; import com.badlogic.gdx.utils.Array; public class SkeletonModel implements AnimatedModel { public final Skeleton skeleton; public final SkeletonSubMesh[] subMeshes; public SkeletonModel(Skeleton skeleton, SubMesh[] subMeshes) { this.skeleton = skeleton; this.subMeshes = new SkeletonSubMesh[subMeshes.length]; for (int i = 0; i < subMeshes.length; ++i) { this.subMeshes[i] = (SkeletonSubMesh) subMeshes[i]; } setMaterial(new Material("default")); } public void setBindPose() { skeleton.setBindPose(); for (int i = 0; i < subMeshes.length; i++) { skin(subMeshes[i], skeleton.combinedMatrices); } } @Override public void setAnimation(String animation, float time, boolean loop) { skeleton.setAnimation(animation, time); for (int i = 0; i < subMeshes.length; i++) { skin(subMeshes[i], skeleton.combinedMatrices); } } final Vector3 v = new Vector3(); public void skin(SkeletonSubMesh subMesh, Array<Matrix4> boneMatrices) { final int stride = subMesh.mesh.getVertexSize() / 4; final int numVertices = subMesh.mesh.getNumVertices(); int idx = 0; int nidx = subMesh.mesh.getVertexAttribute(Usage.Normal) == null ? -1 : subMesh.mesh .getVertexAttribute(Usage.Normal).offset / 4; final float[] vertices = subMesh.vertices; final float[] skinnedVertices = subMesh.skinnedVertices; System.arraycopy(subMesh.vertices, 0, skinnedVertices, 0, subMesh.vertices.length); for (int i = 0; i < numVertices; i++, idx += stride, nidx += stride) { final int[] boneIndices = subMesh.boneAssignments[i]; final float[] boneWeights = subMesh.boneWeights[i]; final float ox = vertices[idx], oy = vertices[idx + 1], oz = vertices[idx + 2]; float x = 0, y = 0, z = 0; float onx = 0, ony = 0, onz = 0; float nx = 0, ny = 0, nz = 0; if (nidx != -1) { onx = vertices[nidx]; ony = vertices[nidx + 1]; onz = vertices[nidx + 2]; } for (int j = 0; j < boneIndices.length; j++) { int boneIndex = boneIndices[j]; float weight = boneWeights[j]; v.set(ox, oy, oz); v.mul(boneMatrices.get(boneIndex)); x += v.x * weight; y += v.y * weight; z += v.z * weight; if (nidx != -1) { v.set(onx, ony, onz); v.rot(boneMatrices.get(boneIndex)); nx += v.x * weight; ny += v.y * weight; nz += v.z * weight; } } skinnedVertices[idx] = x; skinnedVertices[idx + 1] = y; skinnedVertices[idx + 2] = z; if (nidx != -1) { skinnedVertices[nidx] = nx; skinnedVertices[nidx + 1] = ny; skinnedVertices[nidx + 2] = nz; } } subMesh.mesh.setVertices(skinnedVertices); } @Override public void render() { int len = subMeshes.length; for (int i = 0; i < len; i++) { SkeletonSubMesh subMesh = subMeshes[i]; if (i == 0) { subMesh.material.bind(); } else if (!subMeshes[i - 1].material.equals(subMesh.material)) { subMesh.material.bind(); } subMesh.mesh.render(subMesh.primitiveType); } } @Override public void render(ShaderProgram program) { int len = subMeshes.length; for (int i = 0; i < len; i++) { SkeletonSubMesh subMesh = subMeshes[i]; if (i == 0) { subMesh.material.bind(program); } else if (!subMeshes[i - 1].material.equals(subMesh.material)) { subMesh.material.bind(program); } subMesh.mesh.render(program, subMesh.primitiveType); } } @Override public void setMaterials(Material... materials) { if (materials.length != subMeshes.length) throw new UnsupportedOperationException("number of materials must equal number of sub-meshes"); int len = materials.length; for (int i = 0; i < len; i++) { subMeshes[i].material = materials[i]; } } @Override public void setMaterial(Material material) { int len = subMeshes.length; for (int i = 0; i < len; i++) { subMeshes[i].material = material; } } @Override public SubMesh getSubMesh(String name) { int len = subMeshes.length; for (int i = 0; i < len; i++) { if (subMeshes[i].name.equals(name)) return subMeshes[i]; } return null; } @Override public SubMesh[] getSubMeshes() { return subMeshes; } @Override public SkeletonAnimation getAnimation(String name) { return skeleton.animations.get(name); } // FIXME, ugh... protected SkeletonAnimation[] animations; @Override public SkeletonAnimation[] getAnimations() { if (animations == null || animations.length != skeleton.animations.size) { animations = new SkeletonAnimation[skeleton.animations.size]; int i = 0; for (SkeletonAnimation anim : skeleton.animations.values()) { animations[i++] = anim; } } return animations; } @Override public Model getSubModel(String... subMeshNames) { // FIXME return null; } private final static BoundingBox tmpBox = new BoundingBox(); @Override public void getBoundingBox(BoundingBox bbox) { bbox.inf(); for (int i = 0; i < subMeshes.length; i++) { subMeshes[i].mesh.calculateBoundingBox(tmpBox); bbox.ext(tmpBox); } } @Override public void dispose() { for (int i = 0; i < subMeshes.length; i++) { subMeshes[i].mesh.dispose(); } } }