normalgenerator.java
来自「java 3d game jme 工程开发源代码」· Java 代码 · 共 856 行 · 第 1/2 页
JAVA
856 行
/*
* Copyright (c) 2003-2009 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.jme.util.geom;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.ListIterator;
import java.util.logging.Logger;
import com.jme.math.FastMath;
import com.jme.math.Vector2f;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.scene.TexCoords;
import com.jme.scene.TriMesh;
/**
* A utility class to generate normals for TriMeshes.<br />
* <br />
* The generator generates the normals using a given crease angle up to which
* the transitions between two triangles are to be smoothed. Normals will be
* generated for the mesh, as long as it uses the default triangle mode (<code>TRIANGLE</code>) -
* the modes <code>TRIANGLE_FAN</code> and <code>TRIANGLE_STRIP</code> are
* not supported.<br />
* <br />
* The generator currently only supports a single set of texture coordinates in
* a mesh. If more than one set of texture coordinates is specified in a mesh,
* all sets after the first one will be deleted.<br />
* <br />
* <strong>Please note:</strong> The mesh must be <cite>manifold</cite>, i.e.
* only 2 triangles may be connected by one edge, and the mesh has to be
* connected by means of edges, not vertices. Otherwise, the normal generation
* might fail, with undefined results.
*
* @version 2008-03-11
* @author Michael Sattler
*/
public class NormalGenerator {
private static final Logger logger = Logger.getLogger(NormalGenerator.class
.getName());
// The angle up to which edges between triangles are smoothed
private float creaseAngle;
// Source data
private Vector3f[] sourceVerts;
private ColorRGBA[] sourceColors;
private Vector2f[] sourceTexCoords;
private int[] sourceInds;
private LinkedList<Triangle> triangles;
// Computed (destination) data for one mesh split
private ArrayList<Vector3f> destVerts;
private ArrayList<ColorRGBA> destColors;
private ArrayList<Vector2f> destTexCoords;
private LinkedList<Triangle> destTris;
private LinkedList<Edge> edges;
// The lists to store the split data for the final mesh
private LinkedList<LinkedList<Triangle>> splitMeshes;
private LinkedList<LinkedList<Edge>> splitMeshBorders;
private Vector3f[] splitVerts;
private ColorRGBA[] splitColors;
private Vector2f[] splitTexCoords;
private Vector3f[] splitNormals;
private int[] splitIndices;
// The data used to compute the final mesh
private boolean[] borderIndices;
// Temporary data used for computation
private Vector3f compVect0 = new Vector3f();
private Vector3f compVect1 = new Vector3f();
/**
* Generates the normals for one TriMesh, using the specified crease
* angle.
*
* @param mesh
* The TriMesh to generate the normals for
* @param creaseAngle
* The angle between two triangles up to which the normal between
* the two triangles will be interpolated, creating a smooth
* transition
*/
public void generateNormals(TriMesh mesh, float creaseAngle) {
if (mesh != null) {
this.creaseAngle = creaseAngle;
generateNormals(mesh);
cleanup();
}
}
/**
* Generates the normals for one TriMesh, using the crease angle
* stored in the field <code>creaseAngle</code>
*
* @param mesh
* The TriMesh to generate the normals for
*/
private void generateNormals(TriMesh mesh) {
if (mesh.getMode() != TriMesh.Mode.Triangles) {
logger.info("Invalid triangles mode in " + mesh);
return;
}
// Get the data of the mesh as arrays
sourceInds = BufferUtils.getIntArray(mesh.getIndexBuffer());
sourceVerts = BufferUtils.getVector3Array(mesh.getVertexBuffer());
if (mesh.getColorBuffer() != null) {
sourceColors = BufferUtils.getColorArray(mesh.getColorBuffer());
} else {
sourceColors = null;
}
if (mesh.getTextureCoords(0) != null) {
sourceTexCoords = BufferUtils.getVector2Array(mesh
.getTextureCoords(0).coords);
} else {
sourceTexCoords = null;
}
// Initialize the lists needed to generate the normals for the mesh
initialize();
// Process all triangles in the mesh. Create one connected mesh for
// every set of triangles that are connected by their vertex indices
// with an angle not greater than the creaseAngle.
while (!triangles.isEmpty()) {
createMeshSplit();
}
// Duplicate all vertices that are shared by different split meshes
if (!splitMeshes.isEmpty()) {
borderIndices = new boolean[sourceVerts.length];
fillBorderIndices();
duplicateCreaseVertices();
}
// Set up the arrays for reconstructing the mesh: Vertices,
// texture coordinates, colors, normals and indices
splitVerts = destVerts.toArray(new Vector3f[destVerts.size()]);
if (destColors != null) {
splitColors = destColors.toArray(new ColorRGBA[destColors.size()]);
} else {
splitColors = null;
}
if (destTexCoords != null) {
splitTexCoords = destTexCoords.toArray(new Vector2f[destTexCoords
.size()]);
} else {
splitTexCoords = null;
}
splitNormals = new Vector3f[destVerts.size()];
for (int j = 0; j < splitNormals.length; j++) {
splitNormals[j] = new Vector3f();
}
int numTris = 0;
for (LinkedList<Triangle> tris : splitMeshes) {
numTris += tris.size();
}
splitIndices = new int[numTris * 3];
// For each of the split meshes, create the interpolated normals
// between its triangles and set up its index array in the process
computeNormalsAndIndices();
// Set up the buffers for the mesh
// Vertex buffer:
FloatBuffer vertices = mesh.getVertexBuffer();
if (vertices.capacity() < splitVerts.length * 3) {
vertices = BufferUtils.createFloatBuffer(splitVerts);
} else {
vertices.clear();
for (Vector3f vertex : splitVerts) {
vertices.put(vertex.x).put(vertex.y).put(vertex.z);
}
vertices.flip();
}
// Normal buffer:
FloatBuffer normals = mesh.getNormalBuffer();
if (normals == null || normals.capacity() < splitNormals.length * 3) {
normals = BufferUtils.createFloatBuffer(splitNormals);
} else {
normals.clear();
for (Vector3f normal : splitNormals) {
normals.put(normal.x).put(normal.y).put(normal.z);
}
normals.flip();
}
// Color buffer:
FloatBuffer colors = null;
if (splitColors != null) {
colors = mesh.getColorBuffer();
if (colors.capacity() < splitColors.length * 4) {
colors = BufferUtils.createFloatBuffer(splitColors);
} else {
colors.clear();
for (ColorRGBA color : splitColors) {
colors.put(color.r).put(color.g).put(color.b).put(color.a);
}
colors.flip();
}
}
// Tex coord buffer:
FloatBuffer texCoords = null;
if (splitTexCoords != null) {
texCoords = mesh.getTextureCoords(0).coords;
if (texCoords.capacity() < splitTexCoords.length * 2) {
texCoords = BufferUtils.createFloatBuffer(splitTexCoords);
} else {
texCoords.clear();
for (Vector2f texCoord : splitTexCoords) {
texCoords.put(texCoord.x).put(texCoord.y);
}
texCoords.flip();
}
}
// Index buffer:
IntBuffer indices = mesh.getIndexBuffer();
if (indices.capacity() < splitIndices.length) {
indices = BufferUtils.createIntBuffer(splitIndices);
} else {
indices.clear();
indices.put(splitIndices);
indices.flip();
}
// Apply the buffers to the mesh
mesh.setVertexBuffer(vertices);
mesh.setNormalBuffer(normals);
mesh.setColorBuffer(colors);
mesh.getTextureCoords().clear();
mesh.setTextureCoords(new TexCoords(texCoords), 0);
mesh.setIndexBuffer(indices);
mesh.setHasDirtyVertices(true);
}
/**
* Sets up the lists to get the data for normal generation from.
*/
private void initialize() {
// Copy the source vertices as a base for the normal generation
destVerts = new ArrayList<Vector3f>(sourceVerts.length);
for (int i = 0; i < sourceVerts.length; i++) {
destVerts.add(sourceVerts[i]);
}
if (sourceColors != null) {
destColors = new ArrayList<ColorRGBA>(sourceColors.length);
for (int i = 0; i < sourceColors.length; i++) {
destColors.add(sourceColors[i]);
}
} else {
destColors = null;
}
if (sourceTexCoords != null) {
destTexCoords = new ArrayList<Vector2f>(sourceTexCoords.length);
for (int i = 0; i < sourceTexCoords.length; i++) {
destTexCoords.add(sourceTexCoords[i]);
}
} else {
destTexCoords = null;
}
// Set up the base triangles of the mesh and their face normals
triangles = new LinkedList<Triangle>();
for (int i = 0; i * 3 < sourceInds.length; i++) {
Triangle tri = new Triangle(sourceInds[i * 3 + 0],
sourceInds[i * 3 + 1], sourceInds[i * 3 + 2]);
tri.computeNormal(sourceVerts);
triangles.add(tri);
}
// Set up the lists to store the created mesh split data
if (splitMeshes == null) {
splitMeshes = new LinkedList<LinkedList<Triangle>>();
} else {
splitMeshes.clear();
}
if (splitMeshBorders == null) {
splitMeshBorders = new LinkedList<LinkedList<Edge>>();
} else {
splitMeshBorders.clear();
}
}
/**
* Assembles one set of triangles ("split mesh") that are connected and do
* not contain a transition with an angle greater than the creaseAngle. The
* Triangles of the split mesh are stored in splitMeshes, and the Edges
* along the border of the split mesh in splitMeshBorders.
*/
private void createMeshSplit() {
destTris = new LinkedList<Triangle>();
edges = new LinkedList<Edge>();
Triangle tri = triangles.removeFirst();
destTris.addLast(tri);
edges.addLast(tri.edges[0]);
edges.addLast(tri.edges[1]);
edges.addLast(tri.edges[2]);
Triangle newTri;
do {
newTri = insertTriangle();
} while (newTri != null);
splitMeshes.addLast(destTris);
splitMeshBorders.addLast(edges);
}
/**
* Finds one triangle connected to the split mesh currently being assembled
* over an edge whose angle does not exceed the creaseAngle. The Triangle is
* inserted into destTris and the list edges is updated with the edges of
* the triangle accordingly.
*
* @return The triangle, if one was found, or <code>null</code> otherwise
*/
private Triangle insertTriangle() {
ListIterator<Triangle> triIt = triangles.listIterator();
ListIterator<Edge> edgeIt = null;
// Find a triangle that is connected to the border of the currently
// assembled mesh split and whose angle to the connected border triangle
// is less than or equal to the creaseAngle
Triangle result = null;
int connected = -1;
Edge borderEdge = null;
while (result == null && triIt.hasNext()) {
Triangle tri = triIt.next();
edgeIt = edges.listIterator();
while (result == null && edgeIt.hasNext()) {
borderEdge = edgeIt.next();
for (int i = 0; i < tri.edges.length && result == null; i++) {
if (borderEdge.isConnectedTo(tri.edges[i])
&& checkAngle(tri, borderEdge.parent)) {
connected = i;
result = tri;
}
}
}
}
// If a triangle has been found, remove it from the list of remaining
// triangles and add it to the current split mesh, including its borders
if (result != null) {
// Connect the triangle to the split mesh
triIt.remove();
destTris.addLast(result);
borderEdge.connected = result;
Edge resultEdge = result.edges[connected];
resultEdge.connected = borderEdge.parent;
edgeIt.remove();
edgeIt.add(result.edges[(connected + 1) % 3]);
edgeIt.add(result.edges[(connected + 2) % 3]);
// If the connected edge had cloned vertices, use them for the new
// triangle too
if (borderEdge.newI0 > -1) {
resultEdge.newI1 = borderEdge.newI0;
result.edges[(connected + 1) % 3].newI0 = borderEdge.newI0;
}
if (borderEdge.newI1 > -1) {
resultEdge.newI0 = borderEdge.newI1;
result.edges[(connected + 2) % 3].newI1 = borderEdge.newI1;
}
// Check if the triangle is connected to other edges along the
// border of the current split mesh
for (int i = connected + 1; i < connected + 3; i++) {
connectEdge(result, i % 3);
}
}
return result;
}
/**
* Connects the remaining edges of the given triangle to the split mesh
* currently being assembled, if possible. The respective edges are removed
* from the border, and if the crease angle at this additional connection is
* exceeded, the vertices at this link are duplicated.
*
* @param triangle
* The triangle being connected to the split mesh
* @param i
* The index of the edge in the triangle that is already
* connected to the split mesh
*/
private void connectEdge(Triangle triangle, int i) {
Edge edge = triangle.edges[i];
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?