- 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
3.0
rem..om 13 years ago
parent 40e32790d6
commit a5ff915fc1
  1. 246
      engine/src/core/com/jme3/animation/SkeletonControl.java
  2. 11
      engine/src/core/com/jme3/scene/Mesh.java
  3. 13
      engine/src/core/com/jme3/scene/VertexBuffer.java
  4. 364
      engine/src/core/com/jme3/util/TangentBinormalGenerator.java
  5. 2
      engine/src/core/com/jme3/util/TempVars.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<Mesh> animatedMeshes = new ArrayList<Mesh>();
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

@ -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);
}
}
}

@ -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).

@ -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<TriangleData> triangles =
new ArrayList<TriangleData>();
new ArrayList<TriangleData>();
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<TriangleData> 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;

@ -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
*/

Loading…
Cancel
Save