You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
401 lines
16 KiB
401 lines
16 KiB
10 years ago
|
/*
|
||
|
* Copyright (c) 2009-2015 jMonkeyEngine
|
||
|
* All rights reserved.
|
||
|
*
|
||
|
* Redistribution and use in source and binary forms, with or without
|
||
|
* modification, are permitted provided that the following conditions are
|
||
|
* met:
|
||
|
*
|
||
|
* * Redistributions of source code must retain the above copyright
|
||
|
* notice, this list of conditions and the following disclaimer.
|
||
|
*
|
||
|
* * Redistributions in binary form must reproduce the above copyright
|
||
|
* notice, this list of conditions and the following disclaimer in the
|
||
|
* documentation and/or other materials provided with the distribution.
|
||
|
*
|
||
|
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
|
||
|
* may be used to endorse or promote products derived from this software
|
||
|
* without specific prior written permission.
|
||
|
*
|
||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||
|
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||
|
* 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.scene.plugins;
|
||
|
|
||
|
import com.jme3.math.Vector4f;
|
||
|
import com.jme3.scene.Mesh;
|
||
|
import com.jme3.scene.VertexBuffer;
|
||
|
import com.jme3.scene.mesh.IndexBuffer;
|
||
|
import com.jme3.scene.mesh.IndexIntBuffer;
|
||
|
import com.jme3.scene.mesh.IndexShortBuffer;
|
||
|
import com.jme3.util.BufferUtils;
|
||
|
import com.jme3.util.IntMap;
|
||
|
import java.nio.ByteBuffer;
|
||
|
import java.nio.FloatBuffer;
|
||
|
import java.nio.IntBuffer;
|
||
|
import java.nio.ShortBuffer;
|
||
|
import java.util.ArrayList;
|
||
|
import java.util.Arrays;
|
||
|
import java.util.HashMap;
|
||
|
import java.util.List;
|
||
|
import java.util.Map;
|
||
|
import java.util.logging.Level;
|
||
|
import java.util.logging.Logger;
|
||
|
|
||
|
public final class IrUtils {
|
||
|
|
||
|
private static final Logger logger = Logger.getLogger(IrUtils.class.getName());
|
||
|
|
||
|
private IrUtils() { }
|
||
|
|
||
|
private static IrPolygon[] quadToTri(IrPolygon quad) {
|
||
|
if (quad.vertices.length == 3) {
|
||
|
throw new IllegalStateException("Already a triangle");
|
||
|
}
|
||
|
|
||
|
IrPolygon[] t = new IrPolygon[]{ new IrPolygon(), new IrPolygon() };
|
||
|
t[0].vertices = new IrVertex[3];
|
||
|
t[1].vertices = new IrVertex[3];
|
||
|
|
||
|
IrVertex v0 = quad.vertices[0];
|
||
|
IrVertex v1 = quad.vertices[1];
|
||
|
IrVertex v2 = quad.vertices[2];
|
||
|
IrVertex v3 = quad.vertices[3];
|
||
|
|
||
|
// find the pair of verticies that is closest to each over
|
||
|
// v0 and v2
|
||
|
// OR
|
||
|
// v1 and v3
|
||
|
float d1 = v0.pos.distanceSquared(v2.pos);
|
||
|
float d2 = v1.pos.distanceSquared(v3.pos);
|
||
|
if (d1 < d2) {
|
||
|
// v0 is close to v2
|
||
|
// put an edge in v0, v2
|
||
|
t[0].vertices[0] = v0;
|
||
|
t[0].vertices[1] = v1;
|
||
|
t[0].vertices[2] = v3;
|
||
|
|
||
|
t[1].vertices[0] = v1;
|
||
|
t[1].vertices[1] = v2;
|
||
|
t[1].vertices[2] = v3;
|
||
|
} else {
|
||
|
// put an edge in v1, v3
|
||
|
t[0].vertices[0] = v0;
|
||
|
t[0].vertices[1] = v1;
|
||
|
t[0].vertices[2] = v2;
|
||
|
|
||
|
t[1].vertices[0] = v0;
|
||
|
t[1].vertices[1] = v2;
|
||
|
t[1].vertices[2] = v3;
|
||
|
}
|
||
|
|
||
|
return t;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Applies smoothing groups to vertex normals.
|
||
|
*/
|
||
|
public static IrMesh applySmoothingGroups(IrMesh mesh) {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
private static void toTangentsWithParity(IrVertex vertex) {
|
||
|
if (vertex.tang != null && vertex.bitang != null) {
|
||
|
float wCoord = vertex.norm.cross(vertex.tang).dot(vertex.bitang) < 0f ? -1f : 1f;
|
||
|
vertex.tang4d = new Vector4f(vertex.tang.x, vertex.tang.y, vertex.tang.z, wCoord);
|
||
|
vertex.tang = null;
|
||
|
vertex.bitang = null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public static void toTangentsWithParity(IrMesh mesh) {
|
||
|
for (IrPolygon polygon : mesh.polygons) {
|
||
|
for (IrVertex vertex : polygon.vertices) {
|
||
|
toTangentsWithParity(vertex);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static void trimBoneWeights(IrVertex vertex) {
|
||
|
if (vertex.boneWeightsIndices == null) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
IrBoneWeightIndex[] boneWeightsIndices = vertex.boneWeightsIndices;
|
||
|
|
||
|
if (boneWeightsIndices.length <= 4) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Sort by weight
|
||
|
boneWeightsIndices = Arrays.copyOf(boneWeightsIndices, boneWeightsIndices.length);
|
||
|
Arrays.sort(boneWeightsIndices);
|
||
|
|
||
|
// Trim to four weights at most
|
||
|
boneWeightsIndices = Arrays.copyOf(boneWeightsIndices, 4);
|
||
|
|
||
|
// Renormalize weights
|
||
|
float sum = 0;
|
||
|
|
||
|
for (int i = 0; i < boneWeightsIndices.length; i++) {
|
||
|
sum += boneWeightsIndices[i].boneWeight;
|
||
|
}
|
||
|
|
||
|
if (sum != 1f) {
|
||
|
float sumToB = sum == 0 ? 0 : 1f / sum;
|
||
|
for (int i = 0; i < boneWeightsIndices.length; i++) {
|
||
|
IrBoneWeightIndex original = boneWeightsIndices[i];
|
||
|
boneWeightsIndices[i] = new IrBoneWeightIndex(original.boneIndex, original.boneWeight * sumToB);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
vertex.boneWeightsIndices = boneWeightsIndices;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Removes low bone weights from mesh, leaving only 4 bone weights at max.
|
||
|
*/
|
||
|
public static void trimBoneWeights(IrMesh mesh) {
|
||
|
for (IrPolygon polygon : mesh.polygons) {
|
||
|
for (IrVertex vertex : polygon.vertices) {
|
||
|
trimBoneWeights(vertex);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Convert mesh from quads / triangles to triangles only.
|
||
|
*/
|
||
|
public static void triangulate(IrMesh mesh) {
|
||
|
List<IrPolygon> newPolygons = new ArrayList<IrPolygon>(mesh.polygons.length);
|
||
|
for (IrPolygon inputPoly : mesh.polygons) {
|
||
|
if (inputPoly.vertices.length == 4) {
|
||
|
IrPolygon[] tris = quadToTri(inputPoly);
|
||
|
newPolygons.add(tris[0]);
|
||
|
newPolygons.add(tris[1]);
|
||
|
} else if (inputPoly.vertices.length == 3) {
|
||
|
newPolygons.add(inputPoly);
|
||
|
} else {
|
||
|
// N-gon. We have to ignore it..
|
||
|
logger.log(Level.WARNING, "N-gon encountered, ignoring. "
|
||
|
+ "The mesh may not appear correctly. "
|
||
|
+ "Triangulate your model prior to export.");
|
||
|
}
|
||
|
}
|
||
|
mesh.polygons = new IrPolygon[newPolygons.size()];
|
||
|
newPolygons.toArray(mesh.polygons);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Separate mesh with multiple materials into multiple meshes each with
|
||
|
* one material each.
|
||
|
*
|
||
|
* Polygons without a material will be added to key = -1.
|
||
|
*/
|
||
|
public static IntMap<IrMesh> splitByMaterial(IrMesh mesh) {
|
||
|
IntMap<List<IrPolygon>> materialToPolyList = new IntMap<List<IrPolygon>>();
|
||
|
for (IrPolygon polygon : mesh.polygons) {
|
||
|
int materialIndex = -1;
|
||
|
for (IrVertex vertex : polygon.vertices) {
|
||
|
if (vertex.material == null) {
|
||
|
continue;
|
||
|
}
|
||
|
if (materialIndex == -1) {
|
||
|
materialIndex = vertex.material;
|
||
|
} else if (materialIndex != vertex.material) {
|
||
|
throw new UnsupportedOperationException("Multiple materials "
|
||
|
+ "assigned to the same polygon");
|
||
|
}
|
||
|
}
|
||
|
List<IrPolygon> polyList = materialToPolyList.get(materialIndex);
|
||
|
if (polyList == null) {
|
||
|
polyList = new ArrayList<IrPolygon>();
|
||
|
materialToPolyList.put(materialIndex, polyList);
|
||
|
}
|
||
|
polyList.add(polygon);
|
||
|
}
|
||
|
IntMap<IrMesh> materialToMesh = new IntMap<IrMesh>();
|
||
|
for (IntMap.Entry<List<IrPolygon>> entry : materialToPolyList) {
|
||
|
int key = entry.getKey();
|
||
|
List<IrPolygon> polygons = entry.getValue();
|
||
|
if (polygons.size() > 0) {
|
||
|
IrMesh newMesh = new IrMesh();
|
||
|
newMesh.polygons = new IrPolygon[polygons.size()];
|
||
|
polygons.toArray(newMesh.polygons);
|
||
|
materialToMesh.put(key, newMesh);
|
||
|
}
|
||
|
}
|
||
|
return materialToMesh;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Convert IrMesh to jME3 mesh.
|
||
|
*/
|
||
|
public static Mesh convertIrMeshToJmeMesh(IrMesh mesh) {
|
||
|
Map<IrVertex, Integer> vertexToVertexIndex = new HashMap<IrVertex, Integer>();
|
||
|
List<IrVertex> vertices = new ArrayList<IrVertex>();
|
||
|
List<Integer> indexes = new ArrayList<Integer>();
|
||
|
|
||
|
int vertexIndex = 0;
|
||
|
for (IrPolygon polygon : mesh.polygons) {
|
||
|
if (polygon.vertices.length != 3) {
|
||
|
throw new UnsupportedOperationException("IrMesh must be triangulated first");
|
||
|
}
|
||
|
for (IrVertex vertex : polygon.vertices) {
|
||
|
// Is this vertex already indexed?
|
||
|
Integer existingIndex = vertexToVertexIndex.get(vertex);
|
||
|
if (existingIndex == null) {
|
||
|
// Not indexed yet, allocate index.
|
||
|
indexes.add(vertexIndex);
|
||
|
vertexToVertexIndex.put(vertex, vertexIndex);
|
||
|
vertices.add(vertex);
|
||
|
vertexIndex++;
|
||
|
} else {
|
||
|
// Index already allocated for this vertex, reuse it.
|
||
|
indexes.add(existingIndex);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Mesh jmeMesh = new Mesh();
|
||
|
jmeMesh.setMode(Mesh.Mode.Triangles);
|
||
|
|
||
|
FloatBuffer posBuf = null;
|
||
|
FloatBuffer normBuf = null;
|
||
|
FloatBuffer tangBuf = null;
|
||
|
FloatBuffer uv0Buf = null;
|
||
|
FloatBuffer uv1Buf = null;
|
||
|
ByteBuffer colorBuf = null;
|
||
|
ByteBuffer boneIndices = null;
|
||
|
FloatBuffer boneWeights = null;
|
||
|
IndexBuffer indexBuf = null;
|
||
|
|
||
|
IrVertex inspectionVertex = vertices.get(0);
|
||
|
if (inspectionVertex.pos != null) {
|
||
|
posBuf = BufferUtils.createVector3Buffer(vertices.size());
|
||
|
jmeMesh.setBuffer(VertexBuffer.Type.Position, 3, posBuf);
|
||
|
}
|
||
|
if (inspectionVertex.norm != null) {
|
||
|
normBuf = BufferUtils.createVector3Buffer(vertices.size());
|
||
|
jmeMesh.setBuffer(VertexBuffer.Type.Normal, 3, normBuf);
|
||
|
}
|
||
|
if (inspectionVertex.tang4d != null) {
|
||
|
tangBuf = BufferUtils.createFloatBuffer(vertices.size() * 4);
|
||
|
jmeMesh.setBuffer(VertexBuffer.Type.Tangent, 4, tangBuf);
|
||
|
}
|
||
|
if (inspectionVertex.tang != null || inspectionVertex.bitang != null) {
|
||
|
throw new IllegalStateException("Mesh is using 3D tangents, must be converted to 4D tangents first.");
|
||
|
}
|
||
|
if (inspectionVertex.uv0 != null) {
|
||
|
uv0Buf = BufferUtils.createVector2Buffer(vertices.size());
|
||
|
jmeMesh.setBuffer(VertexBuffer.Type.TexCoord, 2, uv0Buf);
|
||
|
}
|
||
|
if (inspectionVertex.uv1 != null) {
|
||
|
uv1Buf = BufferUtils.createVector2Buffer(vertices.size());
|
||
|
jmeMesh.setBuffer(VertexBuffer.Type.TexCoord2, 2, uv1Buf);
|
||
|
}
|
||
|
if (inspectionVertex.color != null) {
|
||
|
colorBuf = BufferUtils.createByteBuffer(vertices.size() * 4);
|
||
|
jmeMesh.setBuffer(VertexBuffer.Type.Color, 4, colorBuf);
|
||
|
jmeMesh.getBuffer(VertexBuffer.Type.Color).setNormalized(true);
|
||
|
}
|
||
|
if (inspectionVertex.boneWeightsIndices != null) {
|
||
|
boneIndices = BufferUtils.createByteBuffer(vertices.size() * 4);
|
||
|
boneWeights = BufferUtils.createFloatBuffer(vertices.size() * 4);
|
||
|
jmeMesh.setBuffer(VertexBuffer.Type.BoneIndex, 4, boneIndices);
|
||
|
jmeMesh.setBuffer(VertexBuffer.Type.BoneWeight, 4, boneWeights);
|
||
|
|
||
|
//creating empty buffers for HW skinning
|
||
|
//the buffers will be setup if ever used.
|
||
|
VertexBuffer weightsHW = new VertexBuffer(VertexBuffer.Type.HWBoneWeight);
|
||
|
VertexBuffer indicesHW = new VertexBuffer(VertexBuffer.Type.HWBoneIndex);
|
||
|
//setting usage to cpuOnly so that the buffer is not send empty to the GPU
|
||
|
indicesHW.setUsage(VertexBuffer.Usage.CpuOnly);
|
||
|
weightsHW.setUsage(VertexBuffer.Usage.CpuOnly);
|
||
|
|
||
|
jmeMesh.setBuffer(weightsHW);
|
||
|
jmeMesh.setBuffer(indicesHW);
|
||
|
}
|
||
|
if (vertices.size() >= 65536) {
|
||
|
// too many verticies: use intbuffer instead of shortbuffer
|
||
|
IntBuffer ib = BufferUtils.createIntBuffer(indexes.size());
|
||
|
jmeMesh.setBuffer(VertexBuffer.Type.Index, 3, ib);
|
||
|
indexBuf = new IndexIntBuffer(ib);
|
||
|
} else {
|
||
|
ShortBuffer sb = BufferUtils.createShortBuffer(indexes.size());
|
||
|
jmeMesh.setBuffer(VertexBuffer.Type.Index, 3, sb);
|
||
|
indexBuf = new IndexShortBuffer(sb);
|
||
|
}
|
||
|
|
||
|
jmeMesh.setStatic();
|
||
|
|
||
|
int maxBonesPerVertex = -1;
|
||
|
|
||
|
for (IrVertex vertex : vertices) {
|
||
|
if (posBuf != null) {
|
||
|
posBuf.put(vertex.pos.x).put(vertex.pos.y).put(vertex.pos.z);
|
||
|
}
|
||
|
if (normBuf != null) {
|
||
|
normBuf.put(vertex.norm.x).put(vertex.norm.y).put(vertex.norm.z);
|
||
|
}
|
||
|
if (tangBuf != null) {
|
||
|
tangBuf.put(vertex.tang4d.x).put(vertex.tang4d.y).put(vertex.tang4d.z).put(vertex.tang4d.w);
|
||
|
}
|
||
|
if (uv0Buf != null) {
|
||
|
uv0Buf.put(vertex.uv0.x).put(vertex.uv0.y);
|
||
|
}
|
||
|
if (uv1Buf != null) {
|
||
|
uv1Buf.put(vertex.uv1.x).put(vertex.uv1.y);
|
||
|
}
|
||
|
if (colorBuf != null) {
|
||
|
colorBuf.putInt(vertex.color.asIntABGR());
|
||
|
}
|
||
|
if (boneIndices != null) {
|
||
|
if (vertex.boneWeightsIndices != null) {
|
||
|
if (vertex.boneWeightsIndices.length > 4) {
|
||
|
throw new UnsupportedOperationException("Mesh uses more than 4 weights per bone. " +
|
||
|
"Call trimBoneWeights() to allieviate this");
|
||
|
}
|
||
|
for (int i = 0; i < vertex.boneWeightsIndices.length; i++) {
|
||
|
boneIndices.put((byte) (vertex.boneWeightsIndices[i].boneIndex & 0xFF));
|
||
|
boneWeights.put(vertex.boneWeightsIndices[i].boneWeight);
|
||
|
}
|
||
|
for (int i = 0; i < 4 - vertex.boneWeightsIndices.length; i++) {
|
||
|
boneIndices.put((byte)0);
|
||
|
boneWeights.put(0f);
|
||
|
}
|
||
|
} else {
|
||
|
boneIndices.putInt(0);
|
||
|
boneWeights.put(0f).put(0f).put(0f).put(0f);
|
||
|
}
|
||
|
|
||
|
maxBonesPerVertex = Math.max(maxBonesPerVertex, vertex.boneWeightsIndices.length);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for (int i = 0; i < indexes.size(); i++) {
|
||
|
indexBuf.put(i, indexes.get(i));
|
||
|
}
|
||
|
|
||
|
jmeMesh.updateCounts();
|
||
|
jmeMesh.updateBound();
|
||
|
|
||
|
if (boneIndices != null) {
|
||
|
jmeMesh.setMaxNumWeights(maxBonesPerVertex);
|
||
|
jmeMesh.prepareForAnim(true);
|
||
|
jmeMesh.generateBindPose(true);
|
||
|
}
|
||
|
|
||
|
return jmeMesh;
|
||
|
}
|
||
|
}
|