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 + -
显示快捷键?