From a5ff915fc1e81592db43d4dae886984b2c1e88a9 Mon Sep 17 00:00:00 2001 From: "rem..om" Date: Mon, 31 Oct 2011 14:18:35 +0000 Subject: [PATCH] - Added tangent transforms support for skinning (normal mapped models with bone animation had incorrect tangents during animation) - added a BindPoseTangent buffer type - made generateBindPose generate a BindPoseTangent buffer if Tangent buffer is set in Mesh - added a temp float array in TempVars to compute tangent skinning - Generated bind pose for tangents after tangent generation in TangentBinormalGenerator git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@8563 75d07b2b-3a1a-0410-a2c5-0572b91ccdca --- .../com/jme3/animation/SkeletonControl.java | 246 ++++++++++-- engine/src/core/com/jme3/scene/Mesh.java | 11 + .../src/core/com/jme3/scene/VertexBuffer.java | 13 +- .../jme3/util/TangentBinormalGenerator.java | 364 +++++++++--------- engine/src/core/com/jme3/util/TempVars.java | 2 + 5 files changed, 425 insertions(+), 211 deletions(-) diff --git a/engine/src/core/com/jme3/animation/SkeletonControl.java b/engine/src/core/com/jme3/animation/SkeletonControl.java index 661002ae4..59f70fbff 100644 --- a/engine/src/core/com/jme3/animation/SkeletonControl.java +++ b/engine/src/core/com/jme3/animation/SkeletonControl.java @@ -67,7 +67,7 @@ public class SkeletonControl extends AbstractControl implements Cloneable { public SkeletonControl(Skeleton skeleton) { this.skeleton = skeleton; } - + /** * Creates a skeleton control. * @@ -75,66 +75,66 @@ public class SkeletonControl extends AbstractControl implements Cloneable { * @param skeleton the skeleton */ @Deprecated - SkeletonControl(Mesh[] targets, Skeleton skeleton){ + SkeletonControl(Mesh[] targets, Skeleton skeleton) { this.skeleton = skeleton; this.targets = targets; } - - private boolean isMeshAnimated(Mesh mesh){ + + private boolean isMeshAnimated(Mesh mesh) { return mesh.getBuffer(Type.BindPosePosition) != null; } - private Mesh[] findTargets(Node node){ + private Mesh[] findTargets(Node node) { Mesh sharedMesh = null; ArrayList animatedMeshes = new ArrayList(); - - for (Spatial child : node.getChildren()){ - if (!(child instanceof Geometry)){ + + for (Spatial child : node.getChildren()) { + if (!(child instanceof Geometry)) { continue; // could be an attachment node, ignore. } - + Geometry geom = (Geometry) child; - + // is this geometry using a shared mesh? Mesh childSharedMesh = geom.getUserData(UserData.JME_SHAREDMESH); - - if (childSharedMesh != null){ + + if (childSharedMesh != null) { // Don't bother with non-animated shared meshes - if (isMeshAnimated(childSharedMesh)){ + if (isMeshAnimated(childSharedMesh)) { // child is using shared mesh, // so animate the shared mesh but ignore child - if (sharedMesh == null){ + if (sharedMesh == null) { sharedMesh = childSharedMesh; - }else if (sharedMesh != childSharedMesh){ + } else if (sharedMesh != childSharedMesh) { throw new IllegalStateException("Two conflicting shared meshes for " + node); } } - }else{ + } else { Mesh mesh = geom.getMesh(); - if (isMeshAnimated(mesh)){ + if (isMeshAnimated(mesh)) { animatedMeshes.add(mesh); } } } - - if (sharedMesh != null){ + + if (sharedMesh != null) { animatedMeshes.add(sharedMesh); } - + return animatedMeshes.toArray(new Mesh[animatedMeshes.size()]); } - + @Override - public void setSpatial(Spatial spatial){ + public void setSpatial(Spatial spatial) { super.setSpatial(spatial); - if (spatial != null){ + if (spatial != null) { Node node = (Node) spatial; targets = findTargets(node); - }else{ + } else { targets = null; } } - + @Override protected void controlRender(RenderManager rm, ViewPort vp) { if (!wasMeshUpdated) { @@ -145,11 +145,11 @@ public class SkeletonControl extends AbstractControl implements Cloneable { // if hardware skinning is supported, the matrices and weight buffer // will be sent by the SkinningShaderLogic object assigned to the shader for (int i = 0; i < targets.length; i++) { - // NOTE: This assumes that code higher up - // Already ensured those targets are animated - // otherwise a crash will happen in skin update + // NOTE: This assumes that code higher up + // Already ensured those targets are animated + // otherwise a crash will happen in skin update //if (isMeshAnimated(targets[i])) { - softwareSkinUpdate(targets[i], offsetMatrices); + softwareSkinUpdate(targets[i], offsetMatrices); //} } @@ -163,7 +163,7 @@ public class SkeletonControl extends AbstractControl implements Cloneable { } void resetToBind() { - for (Mesh mesh : targets){ + for (Mesh mesh : targets) { if (isMeshAnimated(mesh)) { VertexBuffer bi = mesh.getBuffer(Type.BoneIndex); ByteBuffer bib = (ByteBuffer) bi.getData(); @@ -182,6 +182,19 @@ public class SkeletonControl extends AbstractControl implements Cloneable { nb.clear(); bpb.clear(); bnb.clear(); + + //reseting bind tangents if there is a bind tangent buffer + VertexBuffer bindTangents = mesh.getBuffer(Type.BindPoseTangent); + if (bindTangents != null) { + VertexBuffer tangents = mesh.getBuffer(Type.Tangent); + FloatBuffer tb = (FloatBuffer) tangents.getData(); + FloatBuffer btb = (FloatBuffer) bindTangents.getData(); + tb.clear(); + btb.clear(); + tb.put(btb).clear(); + } + + pb.put(bpb).clear(); nb.put(bnb).clear(); } @@ -195,10 +208,10 @@ public class SkeletonControl extends AbstractControl implements Cloneable { clone.setSpatial(clonedNode); clone.skeleton = ctrl.getSkeleton(); - // Fix animated targets for the cloned node + // Fix animated targets for the cloned node clone.targets = findTargets(clonedNode); - // Fix attachments for the cloned node + // Fix attachments for the cloned node for (int i = 0; i < clonedNode.getQuantity(); i++) { // go through attachment nodes, apply them to correct bone Spatial child = clonedNode.getChild(i); @@ -251,7 +264,6 @@ public class SkeletonControl extends AbstractControl implements Cloneable { // public void setSkeleton(Skeleton skeleton) { // this.skeleton = skeleton; // } - /** * returns the targets meshes of this control * @return @@ -267,8 +279,31 @@ public class SkeletonControl extends AbstractControl implements Cloneable { // public void setTargets(Mesh[] targets) { // this.targets = targets; // } - + /** + * Update the mesh according to the given transformation matrices + * @param mesh then mesh + * @param offsetMatrices the transformation matrices to apply + */ private void softwareSkinUpdate(Mesh mesh, Matrix4f[] offsetMatrices) { + + VertexBuffer tb = mesh.getBuffer(Type.Tangent); + if (tb == null) { + //if there are no tangents use the classic skinning + applySkinning(mesh, offsetMatrices); + } else { + //if there are tangents use the skinning with tangents + applySkinningTangents(mesh, offsetMatrices, tb); + } + + + } + + /** + * Method to apply skinning transforms to a mesh's buffers + * @param mesh the mesh + * @param offsetMatrices the offset matices to apply + */ + private void applySkinning(Mesh mesh, Matrix4f[] offsetMatrices) { int maxWeightsPerVert = mesh.getMaxNumWeights(); if (maxWeightsPerVert <= 0) { throw new IllegalStateException("Max weights per vert is incorrectly set!"); @@ -304,7 +339,7 @@ public class SkeletonControl extends AbstractControl implements Cloneable { float[] normBuf = vars.skinNormals; int iterations = (int) FastMath.ceil(fvb.capacity() / ((float) posBuf.length)); - int bufLength = posBuf.length * 3; + int bufLength = posBuf.length; for (int i = iterations - 1; i >= 0; i--) { // read next set of positions and normals from native buffer bufLength = Math.min(posBuf.length, fvb.remaining()); @@ -359,7 +394,146 @@ public class SkeletonControl extends AbstractControl implements Cloneable { vb.updateData(fvb); nb.updateData(fnb); -// mesh.updateBound(); + } + + /** + * Specific method for skinning with tangents to avoid cluttering the classic skinning calculation with + * null checks that would slow down the process even if tangents don't have to be computed. + * Also the iteration has additional indexes since tangent has 4 components instead of 3 for pos and norm + * @param maxWeightsPerVert maximum number of weights per vertex + * @param mesh the mesh + * @param offsetMatrices the offsetMaytrices to apply + * @param tb the tangent vertexBuffer + */ + private void applySkinningTangents(Mesh mesh, Matrix4f[] offsetMatrices, VertexBuffer tb) { + int maxWeightsPerVert = mesh.getMaxNumWeights(); + + if (maxWeightsPerVert <= 0) { + throw new IllegalStateException("Max weights per vert is incorrectly set!"); + } + + int fourMinusMaxWeights = 4 - maxWeightsPerVert; + + // NOTE: This code assumes the vertex buffer is in bind pose + // resetToBind() has been called this frame + VertexBuffer vb = mesh.getBuffer(Type.Position); + FloatBuffer fvb = (FloatBuffer) vb.getData(); + fvb.rewind(); + + VertexBuffer nb = mesh.getBuffer(Type.Normal); + + FloatBuffer fnb = (FloatBuffer) nb.getData(); + fnb.rewind(); + + + FloatBuffer ftb = (FloatBuffer) tb.getData(); + ftb.rewind(); + + + // get boneIndexes and weights for mesh + ByteBuffer ib = (ByteBuffer) mesh.getBuffer(Type.BoneIndex).getData(); + FloatBuffer wb = (FloatBuffer) mesh.getBuffer(Type.BoneWeight).getData(); + + ib.rewind(); + wb.rewind(); + + float[] weights = wb.array(); + byte[] indices = ib.array(); + int idxWeights = 0; + + TempVars vars = TempVars.get(); + + + float[] posBuf = vars.skinPositions; + float[] normBuf = vars.skinNormals; + float[] tanBuf = vars.skinTangents; + + int iterations = (int) FastMath.ceil(fvb.capacity() / ((float) posBuf.length)); + int bufLength = 0; + int tanLength = 0; + for (int i = iterations - 1; i >= 0; i--) { + // read next set of positions and normals from native buffer + bufLength = Math.min(posBuf.length, fvb.remaining()); + tanLength = Math.min(tanBuf.length, ftb.remaining()); + fvb.get(posBuf, 0, bufLength); + fnb.get(normBuf, 0, bufLength); + ftb.get(tanBuf, 0, tanLength); + int verts = bufLength / 3; + int idxPositions = 0; + //tangents has their own index because of the 4 components + int idxTangents = 0; + + // iterate vertices and apply skinning transform for each effecting bone + for (int vert = verts - 1; vert >= 0; vert--) { + float nmx = normBuf[idxPositions]; + float vtx = posBuf[idxPositions++]; + float nmy = normBuf[idxPositions]; + float vty = posBuf[idxPositions++]; + float nmz = normBuf[idxPositions]; + float vtz = posBuf[idxPositions++]; + + float tnx = tanBuf[idxTangents++]; + float tny = tanBuf[idxTangents++]; + float tnz = tanBuf[idxTangents++]; + + //skipping the 4th component of the tangent since it doesn't have to be transformed + idxTangents++; + + float rx = 0, ry = 0, rz = 0, rnx = 0, rny = 0, rnz = 0, rtx = 0, rty = 0, rtz = 0; + + for (int w = maxWeightsPerVert - 1; w >= 0; w--) { + float weight = weights[idxWeights]; + Matrix4f mat = offsetMatrices[indices[idxWeights++]]; + + rx += (mat.m00 * vtx + mat.m01 * vty + mat.m02 * vtz + mat.m03) * weight; + ry += (mat.m10 * vtx + mat.m11 * vty + mat.m12 * vtz + mat.m13) * weight; + rz += (mat.m20 * vtx + mat.m21 * vty + mat.m22 * vtz + mat.m23) * weight; + + rnx += (nmx * mat.m00 + nmy * mat.m01 + nmz * mat.m02) * weight; + rny += (nmx * mat.m10 + nmy * mat.m11 + nmz * mat.m12) * weight; + rnz += (nmx * mat.m20 + nmy * mat.m21 + nmz * mat.m22) * weight; + + rtx += (tnx * mat.m00 + tny * mat.m01 + tnz * mat.m02) * weight; + rty += (tnx * mat.m10 + tny * mat.m11 + tnz * mat.m12) * weight; + rtz += (tnx * mat.m20 + tny * mat.m21 + tnz * mat.m22) * weight; + } + + idxWeights += fourMinusMaxWeights; + + idxPositions -= 3; + + normBuf[idxPositions] = rnx; + posBuf[idxPositions++] = rx; + normBuf[idxPositions] = rny; + posBuf[idxPositions++] = ry; + normBuf[idxPositions] = rnz; + posBuf[idxPositions++] = rz; + + idxTangents -= 4; + + tanBuf[idxTangents++] = rtx; + tanBuf[idxTangents++] = rty; + tanBuf[idxTangents++] = rtz; + + //once again skipping the 4th component of the tangent + idxTangents++; + } + + fvb.position(fvb.position() - bufLength); + fvb.put(posBuf, 0, bufLength); + fnb.position(fnb.position() - bufLength); + fnb.put(normBuf, 0, bufLength); + ftb.position(ftb.position() - tanLength); + ftb.put(tanBuf, 0, tanLength); + } + + vars.release(); + + vb.updateData(fvb); + nb.updateData(fnb); + tb.updateData(ftb); + + } @Override diff --git a/engine/src/core/com/jme3/scene/Mesh.java b/engine/src/core/com/jme3/scene/Mesh.java index 176c6776d..3b0b9de0f 100644 --- a/engine/src/core/com/jme3/scene/Mesh.java +++ b/engine/src/core/com/jme3/scene/Mesh.java @@ -334,6 +334,17 @@ public class Mesh implements Savable, Cloneable { setBuffer(bindNorm); norm.setUsage(Usage.Stream); } + + VertexBuffer tangents = getBuffer(Type.Tangent); + if (tangents != null) { + VertexBuffer bindTangents = new VertexBuffer(Type.BindPoseTangent); + bindTangents.setupData(Usage.CpuOnly, + 4, + Format.Float, + BufferUtils.clone(tangents.getData())); + setBuffer(bindTangents); + tangents.setUsage(Usage.Stream); + } } } diff --git a/engine/src/core/com/jme3/scene/VertexBuffer.java b/engine/src/core/com/jme3/scene/VertexBuffer.java index e1b100fc6..694b9b21d 100644 --- a/engine/src/core/com/jme3/scene/VertexBuffer.java +++ b/engine/src/core/com/jme3/scene/VertexBuffer.java @@ -94,7 +94,9 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable { Color, /** - * Tangent vector, normalized (3 floats) + * Tangent vector, normalized (4 floats) (x,y,z,w) + * the w component is called the binormal parity, is not normalized and is either 1f or -1f + * It's used to compuste the direction on the binormal verctor on the GPU at render time. */ Tangent, @@ -139,6 +141,15 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable { * on the heap. */ BindPoseNormal, + + /** + * Initial vertex tangents, used with animation. + * Should have the same format and size as {@link Type#Tangent}. + * If used with software skinning, the usage should be + * {@link Usage#CpuOnly}, and the buffer should be allocated + * on the heap. + */ + BindPoseTangent, /** * Bone weights, used with animation (4 floats). diff --git a/engine/src/core/com/jme3/util/TangentBinormalGenerator.java b/engine/src/core/com/jme3/util/TangentBinormalGenerator.java index 6b9606e64..b74ffd61f 100644 --- a/engine/src/core/com/jme3/util/TangentBinormalGenerator.java +++ b/engine/src/core/com/jme3/util/TangentBinormalGenerator.java @@ -29,9 +29,11 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - package com.jme3.util; +import com.jme3.scene.VertexBuffer; +import com.jme3.scene.VertexBuffer.Format; +import com.jme3.scene.VertexBuffer.Usage; import java.util.logging.Level; import com.jme3.scene.mesh.IndexBuffer; import com.jme3.scene.Geometry; @@ -55,41 +57,40 @@ import static com.jme3.util.BufferUtils.*; * @author Lex */ public class TangentBinormalGenerator { - + private static final float ZERO_TOLERANCE = 0.0000001f; private static final Logger log = Logger.getLogger( - TangentBinormalGenerator.class.getName()); - + TangentBinormalGenerator.class.getName()); private static float toleranceAngle; private static float toleranceDot; - + static { setToleranceAngle(45); } - + private static class VertexData { + public final Vector3f tangent = new Vector3f(); public final Vector3f binormal = new Vector3f(); public final List triangles = - new ArrayList(); - + new ArrayList(); + public VertexData() { - } } - + public static class TriangleData { + public final Vector3f tangent; public final Vector3f binormal; public final Vector3f normal; public int index0; public int index1; public int index2; - + public TriangleData(Vector3f tangent, Vector3f binormal, - Vector3f normal, - int index0, int index1, int index2) - { + Vector3f normal, + int index0, int index1, int index2) { this.tangent = tangent; this.binormal = binormal; this.normal = normal; @@ -99,7 +100,7 @@ public class TangentBinormalGenerator { this.index2 = index2; } } - + private static VertexData[] initVertexData(int size) { VertexData[] vertices = new VertexData[size]; for (int i = 0; i < size; i++) { @@ -107,23 +108,23 @@ public class TangentBinormalGenerator { } return vertices; } - + public static void generate(Mesh mesh) { generate(mesh, true); } - - public static void generate(Spatial scene){ - if (scene instanceof Node){ + + public static void generate(Spatial scene) { + if (scene instanceof Node) { Node node = (Node) scene; - for (Spatial child : node.getChildren()){ + for (Spatial child : node.getChildren()) { generate(child); } - }else{ + } else { Geometry geom = (Geometry) scene; generate(geom.getMesh()); } } - + public static void generate(Mesh mesh, boolean approxTangents) { int[] index = new int[3]; Vector3f[] v = new Vector3f[3]; @@ -133,45 +134,68 @@ public class TangentBinormalGenerator { t[i] = new Vector2f(); } - if (mesh.getBuffer(Type.Normal) == null){ + if (mesh.getBuffer(Type.Normal) == null) { throw new IllegalArgumentException("The given mesh has no normal data!"); } - + VertexData[] vertices; switch (mesh.getMode()) { case Triangles: - vertices = processTriangles(mesh, index, v, t); break; + vertices = processTriangles(mesh, index, v, t); + break; case TriangleStrip: - vertices = processTriangleStrip(mesh, index, v, t); break; + vertices = processTriangleStrip(mesh, index, v, t); + break; case TriangleFan: - vertices = processTriangleFan(mesh, index, v, t); break; - default: throw new UnsupportedOperationException( - mesh.getMode() + " is not supported."); + vertices = processTriangleFan(mesh, index, v, t); + break; + default: + throw new UnsupportedOperationException( + mesh.getMode() + " is not supported."); } - + processTriangleData(mesh, vertices, approxTangents); - } + //if the mesh has a bind pose, we need to generate the bind pose for the tangent buffer + if (mesh.getBuffer(Type.BindPosePosition) != null) { + + VertexBuffer tangents = mesh.getBuffer(Type.Tangent); + if (tangents != null) { + VertexBuffer bindTangents = new VertexBuffer(Type.BindPoseTangent); + bindTangents.setupData(Usage.CpuOnly, + 4, + Format.Float, + BufferUtils.clone(tangents.getData())); + + if (mesh.getBuffer(Type.BindPoseTangent) != null) { + mesh.clearBuffer(Type.BindPoseTangent); + } + mesh.setBuffer(bindTangents); + tangents.setUsage(Usage.Stream); + } + } + } + private static VertexData[] processTriangles(Mesh mesh, - int[] index, Vector3f[] v, Vector2f[] t) - { - IndexBuffer indexBuffer = mesh.getIndexBuffer(); + int[] index, Vector3f[] v, Vector2f[] t) { + IndexBuffer indexBuffer = mesh.getIndexBuffer(); FloatBuffer vertexBuffer = (FloatBuffer) mesh.getBuffer(Type.Position).getData(); - if (mesh.getBuffer(Type.TexCoord) == null) + if (mesh.getBuffer(Type.TexCoord) == null) { throw new IllegalArgumentException("Can only generate tangents for " - + "meshes with texture coordinates"); + + "meshes with texture coordinates"); + } FloatBuffer textureBuffer = (FloatBuffer) mesh.getBuffer(Type.TexCoord).getData(); - + VertexData[] vertices = initVertexData(vertexBuffer.capacity() / 3); - + for (int i = 0; i < indexBuffer.size() / 3; i++) { for (int j = 0; j < 3; j++) { - index[j] = indexBuffer.get(i*3 + j); + index[j] = indexBuffer.get(i * 3 + j); populateFromBuffer(v[j], vertexBuffer, index[j]); populateFromBuffer(t[j], textureBuffer, index[j]); } - + TriangleData triData = processTriangle(index, v, t); if (triData != null) { vertices[index[0]].triangles.add(triData); @@ -182,21 +206,21 @@ public class TangentBinormalGenerator { return vertices; } + private static VertexData[] processTriangleStrip(Mesh mesh, - int[] index, Vector3f[] v, Vector2f[] t) - { - IndexBuffer indexBuffer = mesh.getIndexBuffer(); + int[] index, Vector3f[] v, Vector2f[] t) { + IndexBuffer indexBuffer = mesh.getIndexBuffer(); FloatBuffer vertexBuffer = (FloatBuffer) mesh.getBuffer(Type.Position).getData(); FloatBuffer textureBuffer = (FloatBuffer) mesh.getBuffer(Type.TexCoord).getData(); - + VertexData[] vertices = initVertexData(vertexBuffer.capacity() / 3); - + index[0] = indexBuffer.get(0); index[1] = indexBuffer.get(1); - + populateFromBuffer(v[0], vertexBuffer, index[0]); populateFromBuffer(v[1], vertexBuffer, index[1]); - + populateFromBuffer(t[0], textureBuffer, index[0]); populateFromBuffer(t[1], textureBuffer, index[1]); @@ -213,64 +237,64 @@ public class TangentBinormalGenerator { vertices[index[1]].triangles.add(triData); vertices[index[2]].triangles.add(triData); } - + Vector3f vTemp = v[0]; v[0] = v[1]; v[1] = v[2]; v[2] = vTemp; - + Vector2f tTemp = t[0]; t[0] = t[1]; t[1] = t[2]; t[2] = tTemp; - + index[0] = index[1]; index[1] = index[2]; } - + return vertices; } + private static VertexData[] processTriangleFan(Mesh mesh, - int[] index, Vector3f[] v, Vector2f[] t) - { - IndexBuffer indexBuffer = mesh.getIndexBuffer(); + int[] index, Vector3f[] v, Vector2f[] t) { + IndexBuffer indexBuffer = mesh.getIndexBuffer(); FloatBuffer vertexBuffer = (FloatBuffer) mesh.getBuffer(Type.Position).getData(); FloatBuffer textureBuffer = (FloatBuffer) mesh.getBuffer(Type.TexCoord).getData(); - + VertexData[] vertices = initVertexData(vertexBuffer.capacity() / 3); - + index[0] = indexBuffer.get(0); index[1] = indexBuffer.get(1); - + populateFromBuffer(v[0], vertexBuffer, index[0]); populateFromBuffer(v[1], vertexBuffer, index[1]); - + populateFromBuffer(t[0], textureBuffer, index[0]); populateFromBuffer(t[1], textureBuffer, index[1]); - + for (int i = 2; i < vertexBuffer.capacity() / 3; i++) { index[2] = indexBuffer.get(i); populateFromBuffer(v[2], vertexBuffer, index[2]); populateFromBuffer(t[2], textureBuffer, index[2]); - + TriangleData triData = processTriangle(index, v, t); if (triData != null) { vertices[index[0]].triangles.add(triData); vertices[index[1]].triangles.add(triData); vertices[index[2]].triangles.add(triData); } - + Vector3f vTemp = v[1]; v[1] = v[2]; v[2] = vTemp; - + Vector2f tTemp = t[1]; t[1] = t[2]; t[2] = tTemp; - + index[1] = index[2]; } - + return vertices; } @@ -278,87 +302,86 @@ public class TangentBinormalGenerator { private static boolean isDegenerateTriangle(Vector3f a, Vector3f b, Vector3f c) { return (a.subtract(b).cross(c.subtract(b))).lengthSquared() == 0; } - - + public static TriangleData processTriangle(int[] index, - Vector3f[] v, Vector2f[] t) - { + Vector3f[] v, Vector2f[] t) { Vector3f edge1 = new Vector3f(); Vector3f edge2 = new Vector3f(); Vector2f edge1uv = new Vector2f(); Vector2f edge2uv = new Vector2f(); - + Vector3f tangent = new Vector3f(); Vector3f binormal = new Vector3f(); Vector3f normal = new Vector3f(); - + t[1].subtract(t[0], edge1uv); t[2].subtract(t[0], edge2uv); - float det = edge1uv.x*edge2uv.y - edge1uv.y*edge2uv.x; - + float det = edge1uv.x * edge2uv.y - edge1uv.y * edge2uv.x; + boolean normalize = false; if (Math.abs(det) < ZERO_TOLERANCE) { - log.log(Level.WARNING, "Colinear uv coordinates for triangle " + - "[{0}, {1}, {2}]; tex0 = [{3}, {4}], " + - "tex1 = [{5}, {6}], tex2 = [{7}, {8}]", - new Object[]{ index[0], index[1], index[2], - t[0].x, t[0].y, t[1].x, t[1].y, t[2].x, t[2].y }); + log.log(Level.WARNING, "Colinear uv coordinates for triangle " + + "[{0}, {1}, {2}]; tex0 = [{3}, {4}], " + + "tex1 = [{5}, {6}], tex2 = [{7}, {8}]", + new Object[]{index[0], index[1], index[2], + t[0].x, t[0].y, t[1].x, t[1].y, t[2].x, t[2].y}); det = 1; normalize = true; } - + v[1].subtract(v[0], edge1); v[2].subtract(v[0], edge2); - + tangent.set(edge1); tangent.normalizeLocal(); binormal.set(edge2); binormal.normalizeLocal(); - + if (Math.abs(Math.abs(tangent.dot(binormal)) - 1) - < ZERO_TOLERANCE) - { - log.log(Level.WARNING, "Vertices are on the same line " + - "for triangle [{0}, {1}, {2}].", - new Object[]{ index[0], index[1], index[2] }); + < ZERO_TOLERANCE) { + log.log(Level.WARNING, "Vertices are on the same line " + + "for triangle [{0}, {1}, {2}].", + new Object[]{index[0], index[1], index[2]}); } - - float factor = 1/det; - tangent.x = (edge2uv.y*edge1.x - edge1uv.y*edge2.x)*factor; - tangent.y = (edge2uv.y*edge1.y - edge1uv.y*edge2.y)*factor; - tangent.z = (edge2uv.y*edge1.z - edge1uv.y*edge2.z)*factor; - if (normalize) tangent.normalizeLocal(); - - binormal.x = (edge1uv.x*edge2.x - edge2uv.x*edge1.x)*factor; - binormal.y = (edge1uv.x*edge2.y - edge2uv.x*edge1.y)*factor; - binormal.z = (edge1uv.x*edge2.z - edge2uv.x*edge1.z)*factor; - if (normalize) binormal.normalizeLocal(); - + + float factor = 1 / det; + tangent.x = (edge2uv.y * edge1.x - edge1uv.y * edge2.x) * factor; + tangent.y = (edge2uv.y * edge1.y - edge1uv.y * edge2.y) * factor; + tangent.z = (edge2uv.y * edge1.z - edge1uv.y * edge2.z) * factor; + if (normalize) { + tangent.normalizeLocal(); + } + + binormal.x = (edge1uv.x * edge2.x - edge2uv.x * edge1.x) * factor; + binormal.y = (edge1uv.x * edge2.y - edge2uv.x * edge1.y) * factor; + binormal.z = (edge1uv.x * edge2.z - edge2uv.x * edge1.z) * factor; + if (normalize) { + binormal.normalizeLocal(); + } + tangent.cross(binormal, normal); normal.normalizeLocal(); - + return new TriangleData( - tangent, - binormal, - normal, - index[0], index[1], index[2] - ); + tangent, + binormal, + normal, + index[0], index[1], index[2]); } - + public static void setToleranceAngle(float angle) { if (angle < 0 || angle > 179) { throw new IllegalArgumentException( - "The angle must be between 0 and 179 degrees."); + "The angle must be between 0 and 179 degrees."); } - toleranceDot = FastMath.cos(angle*FastMath.DEG_TO_RAD); + toleranceDot = FastMath.cos(angle * FastMath.DEG_TO_RAD); toleranceAngle = angle; } - + private static void processTriangleData(Mesh mesh, VertexData[] vertices, - boolean approxTangent) - { + boolean approxTangent) { FloatBuffer normalBuffer = (FloatBuffer) mesh.getBuffer(Type.Normal).getData(); - + FloatBuffer tangents = BufferUtils.createFloatBuffer(vertices.length * 4); // FloatBuffer binormals = BufferUtils.createFloatBuffer(vertices.length * 3); @@ -366,7 +389,7 @@ public class TangentBinormalGenerator { Vector3f binormal = new Vector3f(); Vector3f normal = new Vector3f(); Vector3f givenNormal = new Vector3f(); - + Vector3f tangentUnit = new Vector3f(); Vector3f binormalUnit = new Vector3f(); @@ -375,7 +398,7 @@ public class TangentBinormalGenerator { populateFromBuffer(givenNormal, normalBuffer, i); givenNormal.normalizeLocal(); - + VertexData currentVertex = vertices[i]; List triangles = currentVertex.triangles; @@ -384,26 +407,26 @@ public class TangentBinormalGenerator { tangent.normalizeLocal(); binormal.set(triangles.get(0).binormal); binormal.normalizeLocal(); - + for (int j = 1; j < triangles.size(); j++) { TriangleData triangleData = triangles.get(j); - + tangentUnit.set(triangleData.tangent); tangentUnit.normalizeLocal(); if (tangent.dot(tangentUnit) < toleranceDot) { log.log(Level.WARNING, - "Angle between tangents exceeds tolerance " + - "for vertex {0}.", i); + "Angle between tangents exceeds tolerance " + + "for vertex {0}.", i); break; } - + if (!approxTangent) { binormalUnit.set(triangleData.binormal); binormalUnit.normalizeLocal(); if (binormal.dot(binormalUnit) < toleranceDot) { log.log(Level.WARNING, - "Angle between binormals exceeds tolerance " + - "for vertex {0}.", i); + "Angle between binormals exceeds tolerance " + + "for vertex {0}.", i); break; } } @@ -412,13 +435,13 @@ public class TangentBinormalGenerator { // find average tangent tangent.set(0, 0, 0); binormal.set(0, 0, 0); - + boolean flippedNormal = false; for (int j = 0; j < triangles.size(); j++) { TriangleData triangleData = triangles.get(j); tangent.addLocal(triangleData.tangent); binormal.addLocal(triangleData.binormal); - + if (givenNormal.dot(triangleData.normal) < 0) { flippedNormal = true; } @@ -428,10 +451,10 @@ public class TangentBinormalGenerator { // so binormal = normal.cross(tangent) will be flipped in the shader // log.log(Level.WARNING, // "Binormal is flipped for vertex {0}.", i); - + wCoord = 1; } - + if (tangent.length() < ZERO_TOLERANCE) { log.log(Level.WARNING, "Shared tangent is zero for vertex {0}.", i); @@ -439,25 +462,22 @@ public class TangentBinormalGenerator { if (binormal.length() >= ZERO_TOLERANCE) { binormal.cross(givenNormal, tangent); tangent.normalizeLocal(); - } - // if all fails use the tangent from the first triangle + } // if all fails use the tangent from the first triangle else { tangent.set(triangles.get(0).tangent); } - } - else { + } else { tangent.divideLocal(triangles.size()); } - + tangentUnit.set(tangent); tangentUnit.normalizeLocal(); if (Math.abs(Math.abs(tangentUnit.dot(givenNormal)) - 1) - < ZERO_TOLERANCE) - { + < ZERO_TOLERANCE) { log.log(Level.WARNING, "Normal and tangent are parallel for vertex {0}.", i); } - + if (!approxTangent) { if (binormal.length() < ZERO_TOLERANCE) { @@ -467,33 +487,29 @@ public class TangentBinormalGenerator { if (tangent.length() >= ZERO_TOLERANCE) { givenNormal.cross(tangent, binormal); binormal.normalizeLocal(); - } - // if all fails use the binormal from the first triangle + } // if all fails use the binormal from the first triangle else { binormal.set(triangles.get(0).binormal); } - } - else { + } else { binormal.divideLocal(triangles.size()); } - + binormalUnit.set(binormal); binormalUnit.normalizeLocal(); if (Math.abs(Math.abs(binormalUnit.dot(givenNormal)) - 1) - < ZERO_TOLERANCE) - { + < ZERO_TOLERANCE) { log.log(Level.WARNING, "Normal and binormal are parallel for vertex {0}.", i); } if (Math.abs(Math.abs(binormalUnit.dot(tangentUnit)) - 1) - < ZERO_TOLERANCE) - { + < ZERO_TOLERANCE) { log.log(Level.WARNING, "Tangent and binormal are parallel for vertex {0}.", i); } } - + if (approxTangent) { // givenNormal.cross(tangent, binormal); // binormal.cross(givenNormal, tangent); @@ -503,67 +519,67 @@ public class TangentBinormalGenerator { tangents.put((i * 4) + 1, tangent.y); tangents.put((i * 4) + 2, tangent.z); tangents.put((i * 4) + 3, wCoord); - } - else { + } else { tangents.put((i * 4), tangent.x); tangents.put((i * 4) + 1, tangent.y); tangents.put((i * 4) + 2, tangent.z); tangents.put((i * 4) + 3, wCoord); - + // setInBuffer(binormal, binormals, i); } } - - mesh.setBuffer(Type.Tangent, 4, tangents); + + mesh.setBuffer(Type.Tangent, 4, tangents); // if (!approxTangent) mesh.setBuffer(Type.Binormal, 3, binormals); } - + public static Mesh genTbnLines(Mesh mesh, float scale) { - if (mesh.getBuffer(Type.Tangent) == null) + if (mesh.getBuffer(Type.Tangent) == null) { return genNormalLines(mesh, scale); - else + } else { return genTangentLines(mesh, scale); + } } - + public static Mesh genNormalLines(Mesh mesh, float scale) { FloatBuffer vertexBuffer = (FloatBuffer) mesh.getBuffer(Type.Position).getData(); FloatBuffer normalBuffer = (FloatBuffer) mesh.getBuffer(Type.Normal).getData(); - + ColorRGBA originColor = ColorRGBA.White; ColorRGBA normalColor = ColorRGBA.Blue; - + Mesh lineMesh = new Mesh(); lineMesh.setMode(Mesh.Mode.Lines); - + Vector3f origin = new Vector3f(); Vector3f point = new Vector3f(); - + FloatBuffer lineVertex = BufferUtils.createFloatBuffer(vertexBuffer.capacity() * 2); FloatBuffer lineColor = BufferUtils.createFloatBuffer(vertexBuffer.capacity() / 3 * 4 * 2); - + for (int i = 0; i < vertexBuffer.capacity() / 3; i++) { populateFromBuffer(origin, vertexBuffer, i); populateFromBuffer(point, normalBuffer, i); - + int index = i * 2; - + setInBuffer(origin, lineVertex, index); setInBuffer(originColor, lineColor, index); - + point.multLocal(scale); point.addLocal(origin); setInBuffer(point, lineVertex, index + 1); setInBuffer(normalColor, lineColor, index + 1); } - + lineMesh.setBuffer(Type.Position, 3, lineVertex); lineMesh.setBuffer(Type.Color, 4, lineColor); - + lineMesh.setStatic(); lineMesh.setInterleaved(); return lineMesh; } - + private static Mesh genTangentLines(Mesh mesh, float scale) { FloatBuffer vertexBuffer = (FloatBuffer) mesh.getBuffer(Type.Position).getData(); FloatBuffer normalBuffer = (FloatBuffer) mesh.getBuffer(Type.Normal).getData(); @@ -573,15 +589,15 @@ public class TangentBinormalGenerator { if (mesh.getBuffer(Type.Binormal) != null) { binormalBuffer = (FloatBuffer) mesh.getBuffer(Type.Binormal).getData(); } - + ColorRGBA originColor = ColorRGBA.White; ColorRGBA tangentColor = ColorRGBA.Red; ColorRGBA binormalColor = ColorRGBA.Green; ColorRGBA normalColor = ColorRGBA.Blue; - + Mesh lineMesh = new Mesh(); lineMesh.setMode(Mesh.Mode.Lines); - + Vector3f origin = new Vector3f(); Vector3f point = new Vector3f(); Vector3f tangent = new Vector3f(); @@ -598,17 +614,17 @@ public class TangentBinormalGenerator { populateFromBuffer(origin, vertexBuffer, i); populateFromBuffer(normal, normalBuffer, i); - if (hasParity){ + if (hasParity) { tangent.x = tangentBuffer.get(i * 4); tangent.y = tangentBuffer.get(i * 4 + 1); tangent.z = tangentBuffer.get(i * 4 + 2); - tangentW = tangentBuffer.get(i * 4 + 3); - }else{ + tangentW = tangentBuffer.get(i * 4 + 3); + } else { populateFromBuffer(tangent, tangentBuffer, i); } - + int index = i * 4; - + int id = i * 6; lineIndex.put(id, index); lineIndex.put(id + 1, index + 1); @@ -616,7 +632,7 @@ public class TangentBinormalGenerator { lineIndex.put(id + 3, index + 2); lineIndex.put(id + 4, index); lineIndex.put(id + 5, index + 3); - + setInBuffer(origin, lineVertex, index); setInBuffer(originColor, lineColor, index); @@ -627,7 +643,7 @@ public class TangentBinormalGenerator { setInBuffer(tangentColor, lineColor, index + 1); // wvBinormal = cross(wvNormal, wvTangent) * -inTangent.w - + if (binormalBuffer == null) { normal.cross(tangent, point); point.multLocal(-tangentW); @@ -640,18 +656,18 @@ public class TangentBinormalGenerator { point.addLocal(origin); setInBuffer(point, lineVertex, index + 2); setInBuffer(binormalColor, lineColor, index + 2); - + point.set(normal); point.multLocal(scale); point.addLocal(origin); setInBuffer(point, lineVertex, index + 3); setInBuffer(normalColor, lineColor, index + 3); } - + lineMesh.setBuffer(Type.Index, 1, lineIndex); lineMesh.setBuffer(Type.Position, 3, lineVertex); lineMesh.setBuffer(Type.Color, 4, lineColor); - + lineMesh.setStatic(); lineMesh.setInterleaved(); return lineMesh; diff --git a/engine/src/core/com/jme3/util/TempVars.java b/engine/src/core/com/jme3/util/TempVars.java index 0a314aeac..629b87d99 100644 --- a/engine/src/core/com/jme3/util/TempVars.java +++ b/engine/src/core/com/jme3/util/TempVars.java @@ -157,6 +157,8 @@ public class TempVars { */ public final float[] skinPositions = new float[512 * 3]; public final float[] skinNormals = new float[512 * 3]; + //tangent buffer as 4 components by elements + public final float[] skinTangents = new float[512 * 4]; /** * Fetching triangle from mesh */