| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016 |
- /*
- * Copyright (c) 2009-2013 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 jme3tools.optimize;
- import com.jme3.bounding.BoundingSphere;
- import com.jme3.math.Vector3f;
- import com.jme3.scene.Geometry;
- import com.jme3.scene.Mesh;
- import com.jme3.scene.VertexBuffer;
- import com.jme3.util.BufferUtils;
- import java.nio.Buffer;
- import java.nio.FloatBuffer;
- import java.nio.IntBuffer;
- import java.nio.ShortBuffer;
- import java.util.ArrayList;
- import java.util.Collections;
- import java.util.Comparator;
- import java.util.HashSet;
- import java.util.Iterator;
- import java.util.List;
- import java.util.Set;
- import java.util.SortedSet;
- import java.util.TreeSet;
- import java.util.logging.Level;
- import java.util.logging.Logger;
- /**
- * This is an utility class that allows to generated the lod levels for an
- * arbitrary mesh. It computes a collapse cost for each vertex and each edges.
- * The higher the cost the most likely collapsing the edge or the vertex will
- * produce artifacts on the mesh. <p>This class is the java implementation of
- * the enhanced version og Ogre engine Lod generator, by Péter Szücs, originally
- * based on Stan Melax "easy mesh simplification". The MIT licences C++ source
- * code can be found here
- * https://github.com/worldforge/ember/tree/master/src/components/ogre/lod more
- * informations can be found here http://www.melax.com/polychop
- * http://sajty.elementfx.com/progressivemesh/GSoC2012.pdf </p>
- *
- * <p>The algorithm sort the vertice according to their collapsse cost
- * ascending. It collapse from the "cheapest" vertex to the more expensive.<br>
- * <strong>Usage : </strong><br>
- * <pre>
- * LodGenerator lODGenerator = new LodGenerator(geometry);
- * lODGenerator.bakeLods(reductionMethod,reductionvalue);
- * </pre> redutionMethod type is VertexReductionMethod described here
- * {@link TriangleReductionMethod} reductionvalue depends on the
- * reductionMethod<p>
- *
- *
- * @author Nehon
- */
- public class LodGenerator {
-
- private static final Logger logger = Logger.getLogger(LodGenerator.class.getName());
- private static final float NEVER_COLLAPSE_COST = Float.MAX_VALUE;
- private static final float UNINITIALIZED_COLLAPSE_COST = Float.POSITIVE_INFINITY;
- private Vector3f tmpV1 = new Vector3f();
- private Vector3f tmpV2 = new Vector3f();
- private boolean bestQuality = true;
- private int indexCount = 0;
- private List<Vertex> collapseCostSet = new ArrayList<Vertex>();
- private float collapseCostLimit;
- private List<Triangle> triangleList;
- private List<Vertex> vertexList = new ArrayList<Vertex>();
- private float meshBoundingSphereRadius;
- private Mesh mesh;
- /**
- * Describe the way trinagles will be removed. <br> PROPORTIONAL :
- * Percentage of triangles to be removed from the mesh. Valid range is a
- * number between 0.0 and 1.0 <br> CONSTANT : Triangle count to be removed
- * from the mesh. Pass only integers or it will be rounded. <br>
- * COLLAPSE_COST : Reduces the vertices, until the cost is bigger then the
- * given value. Collapse cost is equal to the amount of artifact the
- * reduction causes. This generates the best Lod output, but the collapse
- * cost depends on implementation.
- */
- public enum TriangleReductionMethod {
- /**
- * Percentage of triangles to be removed from the mesh.
- *
- * Valid range is a number between 0.0 and 1.0
- */
- PROPORTIONAL,
- /**
- * Triangle count to be removed from the mesh.
- *
- * Pass only integers or it will be rounded.
- */
- CONSTANT,
- /**
- * Reduces the vertices, until the cost is bigger then the given value.
- *
- * Collapse cost is equal to the amount of artifact the reduction
- * causes. This generates the best Lod output, but the collapse cost
- * depends on implementation.
- */
- COLLAPSE_COST
- };
-
- private class Edge {
-
- Vertex destination;
- float collapseCost = UNINITIALIZED_COLLAPSE_COST;
- int refCount;
-
- public Edge(Vertex destination) {
- this.destination = destination;
- }
-
- public void set(Edge other) {
- destination = other.destination;
- collapseCost = other.collapseCost;
- refCount = other.refCount;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (!(obj instanceof Edge)) {
- return false;
- }
- return destination == ((Edge) obj).destination;
- }
-
- @Override
- public int hashCode() {
- return destination.hashCode();
- }
-
- @Override
- public String toString() {
- return "Edge{" + "collapsTo " + destination.index + '}';
- }
- }
-
- private class Vertex {
-
- Vector3f position = new Vector3f();
- float collapseCost = UNINITIALIZED_COLLAPSE_COST;
- List<Edge> edges = new ArrayList<Edge>();
- Set<Triangle> triangles = new HashSet<Triangle>();
- Vertex collapseTo;
- boolean isSeam;
- int index;//index in the buffer for debugging
- @Override
- public String toString() {
- return index + " : " + position.toString();
- }
- }
-
- private class Triangle {
-
- Vertex[] vertex = new Vertex[3];
- Vector3f normal;
- boolean isRemoved;
- //indices of the vertices in the vertex buffer
- int[] vertexId = new int[3];
-
- void computeNormal() {
- // Cross-product 2 edges
- tmpV1.set(vertex[1].position).subtractLocal(vertex[0].position);
- tmpV2.set(vertex[2].position).subtractLocal(vertex[1].position);
-
- normal = tmpV1.cross(tmpV2);
- normal.normalizeLocal();
- }
-
- boolean hasVertex(Vertex v) {
- return (v == vertex[0] || v == vertex[1] || v == vertex[2]);
- }
-
- int getVertexIndex(Vertex v) {
- for (int i = 0; i < 3; i++) {
- if (vertex[i] == v) {
- return vertexId[i];
- }
- }
- throw new IllegalArgumentException("Vertex " + v + "is not part of triangle" + this);
- }
-
- boolean isMalformed() {
- return vertex[0] == vertex[1] || vertex[0] == vertex[2] || vertex[1] == vertex[2];
- }
-
- @Override
- public String toString() {
- String out = "Triangle{\n";
- for (int i = 0; i < 3; i++) {
- out += vertexId[i] + " : " + vertex[i].toString() + "\n";
- }
- out += '}';
- return out;
- }
- }
- /**
- * Compartator used to sort vertices according to their collapse cost
- */
- private Comparator collapseComparator = new Comparator<Vertex>() {
- public int compare(Vertex o1, Vertex o2) {
- if (Float.compare(o1.collapseCost, o2.collapseCost) == 0) {
- return 0;
- }
- if (o1.collapseCost < o2.collapseCost) {
- return -1;
- }
- return 1;
- }
- };
- /**
- * Construct a LodGenerator for the given geometry
- *
- * @param geom the geometry to consider to generate de Lods.
- */
- public LodGenerator(Geometry geom) {
- mesh = geom.getMesh();
- build();
- }
-
- private void build() {
- BoundingSphere bs = new BoundingSphere();
- bs.computeFromPoints(mesh.getFloatBuffer(VertexBuffer.Type.Position));
- meshBoundingSphereRadius = bs.getRadius();
- List<Vertex> vertexLookup = new ArrayList<Vertex>();
- initialize();
-
- gatherVertexData(mesh, vertexLookup);
- gatherIndexData(mesh, vertexLookup);
- computeCosts();
- assert (assertValidMesh());
-
- }
-
- private void gatherVertexData(Mesh mesh, List<Vertex> vertexLookup) {
- //in case the model is currently animating with software animation
- //attempting to retrieve the bind position instead of the position.
- VertexBuffer position = mesh.getBuffer(VertexBuffer.Type.BindPosePosition);
- if (position == null) {
- position = mesh.getBuffer(VertexBuffer.Type.Position);
- }
- FloatBuffer pos = (FloatBuffer) position.getDataReadOnly();
- pos.rewind();
-
- while (pos.remaining() != 0) {
- Vertex v = new Vertex();
- v.position.setX(pos.get());
- v.position.setY(pos.get());
- v.position.setZ(pos.get());
- v.isSeam = false;
- Vertex existingV = findSimilar(v);
- if (existingV != null) {
- //vertex position already exists
- existingV.isSeam = true;
- v.isSeam = true;
- } else {
- vertexList.add(v);
- }
- vertexLookup.add(v);
- }
- pos.rewind();
- }
-
- private Vertex findSimilar(Vertex v) {
- for (Vertex vertex : vertexList) {
- if (vertex.position.equals(v.position)) {
- return vertex;
- }
- }
- return null;
- }
-
- private void gatherIndexData(Mesh mesh, List<Vertex> vertexLookup) {
- VertexBuffer indexBuffer = mesh.getBuffer(VertexBuffer.Type.Index);
- indexCount = indexBuffer.getNumElements() * 3;
- Buffer b = indexBuffer.getDataReadOnly();
- b.rewind();
-
- while (b.remaining() != 0) {
- Triangle tri = new Triangle();
- tri.isRemoved = false;
- triangleList.add(tri);
- for (int i = 0; i < 3; i++) {
- if (b instanceof IntBuffer) {
- tri.vertexId[i] = ((IntBuffer) b).get();
- } else {
- tri.vertexId[i] = ((ShortBuffer) b).get();
- }
- assert (tri.vertexId[i] < vertexLookup.size());
- tri.vertex[i] = vertexLookup.get(tri.vertexId[i]);
- //debug only;
- tri.vertex[i].index = tri.vertexId[i];
- }
- if (tri.isMalformed()) {
- if (!tri.isRemoved) {
- logger.log(Level.FINE, "malformed triangle found with ID:{0}\n{1} It will be excluded from Lod level calculations.", new Object[]{triangleList.indexOf(tri), tri.toString()});
- tri.isRemoved = true;
- indexCount -= 3;
- }
-
- } else {
- tri.computeNormal();
- addTriangleToEdges(tri);
- }
- }
- b.rewind();
- }
-
- private void computeCosts() {
- collapseCostSet.clear();
-
- for (Vertex vertex : vertexList) {
-
- if (!vertex.edges.isEmpty()) {
- computeVertexCollapseCost(vertex);
- } else {
- logger.log(Level.FINE, "Found isolated vertex {0} It will be excluded from Lod level calculations.", vertex);
- }
- }
- assert (vertexList.size() == collapseCostSet.size());
- assert (checkCosts());
- }
- //Debug only
- private boolean checkCosts() {
- for (Vertex vertex : vertexList) {
- boolean test = find(collapseCostSet, vertex);
- if (!test) {
- System.out.println("vertex " + vertex.index + " not present in collapse costs");
- return false;
- }
- }
- return true;
- }
-
- private void computeVertexCollapseCost(Vertex vertex) {
-
- vertex.collapseCost = UNINITIALIZED_COLLAPSE_COST;
- assert (!vertex.edges.isEmpty());
- for (Edge edge : vertex.edges) {
- edge.collapseCost = computeEdgeCollapseCost(vertex, edge);
- assert (edge.collapseCost != UNINITIALIZED_COLLAPSE_COST);
- if (vertex.collapseCost > edge.collapseCost) {
- vertex.collapseCost = edge.collapseCost;
- vertex.collapseTo = edge.destination;
- }
- }
- assert (vertex.collapseCost != UNINITIALIZED_COLLAPSE_COST);
- collapseCostSet.add(vertex);
- }
-
- float computeEdgeCollapseCost(Vertex src, Edge dstEdge) {
- // This is based on Ogre's collapse cost calculation algorithm.
- Vertex dest = dstEdge.destination;
- // Check for singular triangle destruction
- // If src and dest both only have 1 triangle (and it must be a shared one)
- // then this would destroy the shape, so don't do this
- if (src.triangles.size() == 1 && dest.triangles.size() == 1) {
- return NEVER_COLLAPSE_COST;
- }
- // Degenerate case check
- // Are we going to invert a face normal of one of the neighbouring faces?
- // Can occur when we have a very small remaining edge and collapse crosses it
- // Look for a face normal changing by > 90 degrees
- for (Triangle triangle : src.triangles) {
- // Ignore the deleted faces (those including src & dest)
- if (!triangle.hasVertex(dest)) {
- // Test the new face normal
- Vertex pv0, pv1, pv2;
- // Replace src with dest wherever it is
- pv0 = (triangle.vertex[0] == src) ? dest : triangle.vertex[0];
- pv1 = (triangle.vertex[1] == src) ? dest : triangle.vertex[1];
- pv2 = (triangle.vertex[2] == src) ? dest : triangle.vertex[2];
- // Cross-product 2 edges
- tmpV1.set(pv1.position).subtractLocal(pv0.position);
- tmpV2.set(pv2.position).subtractLocal(pv1.position);
- //computing the normal
- Vector3f newNormal = tmpV1.crossLocal(tmpV2);
- newNormal.normalizeLocal();
- // Dot old and new face normal
- // If < 0 then more than 90 degree difference
- if (newNormal.dot(triangle.normal) < 0.0f) {
- // Don't do it!
- return NEVER_COLLAPSE_COST;
- }
- }
- }
-
- float cost;
- // Special cases
- // If we're looking at a border vertex
- if (isBorderVertex(src)) {
- if (dstEdge.refCount > 1) {
- // src is on a border, but the src-dest edge has more than one tri on it
- // So it must be collapsing inwards
- // Mark as very high-value cost
- // curvature = 1.0f;
- cost = 1.0f;
- } else {
- // Collapsing ALONG a border
- // We can't use curvature to measure the effect on the model
- // Instead, see what effect it has on 'pulling' the other border edges
- // The more colinear, the less effect it will have
- // So measure the 'kinkiness' (for want of a better term)
- // Find the only triangle using this edge.
- // PMTriangle* triangle = findSideTriangle(src, dst);
- cost = 0.0f;
- Vector3f collapseEdge = tmpV1.set(src.position).subtractLocal(dest.position);
- collapseEdge.normalizeLocal();
-
- for (Edge edge : src.edges) {
-
- Vertex neighbor = edge.destination;
- //reference check intended
- if (neighbor != dest && edge.refCount == 1) {
- Vector3f otherBorderEdge = tmpV2.set(src.position).subtractLocal(neighbor.position);
- otherBorderEdge.normalizeLocal();
- // This time, the nearer the dot is to -1, the better, because that means
- // the edges are opposite each other, therefore less kinkiness
- // Scale into [0..1]
- float kinkiness = (otherBorderEdge.dot(collapseEdge) + 1.002f) * 0.5f;
- cost = Math.max(cost, kinkiness);
- }
- }
- }
- } else { // not a border
- // Standard inner vertex
- // Calculate curvature
- // use the triangle facing most away from the sides
- // to determine our curvature term
- // Iterate over src's faces again
- cost = 0.001f;
-
- for (Triangle triangle : src.triangles) {
- float mincurv = 1.0f; // curve for face i and closer side to it
- for (Triangle triangle2 : src.triangles) {
- if (triangle2.hasVertex(dest)) {
- // Dot product of face normal gives a good delta angle
- float dotprod = triangle.normal.dot(triangle2.normal);
- // NB we do (1-..) to invert curvature where 1 is high curvature [0..1]
- // Whilst dot product is high when angle difference is low
- mincurv = Math.min(mincurv, (1.002f - dotprod) * 0.5f);
- }
- }
- cost = Math.max(cost, mincurv);
- }
- }
- // check for texture seam ripping
- if (src.isSeam) {
- if (!dest.isSeam) {
- cost += meshBoundingSphereRadius;
- } else {
- cost += meshBoundingSphereRadius * 0.5;
- }
- }
-
- assert (cost >= 0);
- // TODO: use squared distance.
- return cost * src.position.distance(dest.position);
- }
- int nbCollapsedTri = 0;
- /**
- * Computes the lod and return a list of VertexBuffers that can then be used
- * for lod (use Mesg.setLodLevels(VertexBuffer[]))<br>
- *
- * This method must be fed with the reduction method
- * {@link TriangleReductionMethod} and a list of reduction values.<br> for
- * each value a lod will be generated. <br> The resulting array will always
- * contain at index 0 the original index buffer of the mesh. <p>
- * <strong>Important note :</strong> some meshes cannot be decimated, so the
- * result of this method can varry depending of the given mesh. Also the
- * reduction values are indicative and the produces mesh will not always
- * meet the required reduction.
- *
- * @param reductionMethod the reduction method to use
- * @param reductionValues the reduction value to use for each lod level.
- * @return an array of VertexBuffers containing the different index buffers
- * representing the lod levels.
- */
- public VertexBuffer[] computeLods(TriangleReductionMethod reductionMethod, float... reductionValues) {
- int tricount = triangleList.size();
- int lastBakeVertexCount = tricount;
- int lodCount = reductionValues.length;
- VertexBuffer[] lods = new VertexBuffer[lodCount + 1];
- int numBakedLods = 1;
- lods[0] = mesh.getBuffer(VertexBuffer.Type.Index);
- for (int curLod = 0; curLod < lodCount; curLod++) {
- int neededTriCount = calcLodTriCount(reductionMethod, reductionValues[curLod]);
- while (neededTriCount < tricount) {
- Collections.sort(collapseCostSet, collapseComparator);
- Iterator<Vertex> it = collapseCostSet.iterator();
-
- if (it.hasNext()) {
- Vertex v = it.next();
- if (v.collapseCost < collapseCostLimit) {
- if (!collapse(v)) {
- logger.log(Level.FINE, "Couldn''t collapse vertex{0}", v.index);
- }
- Iterator<Vertex> it2 = collapseCostSet.iterator();
- if (it2.hasNext()) {
- it2.next();
- it2.remove();// Remove src from collapse costs.
- }
-
- } else {
- break;
- }
- } else {
- break;
- }
- tricount = triangleList.size() - nbCollapsedTri;
- }
- logger.log(Level.FINE, "collapsed {0} tris", nbCollapsedTri);
- boolean outSkipped = (lastBakeVertexCount == tricount);
- if (!outSkipped) {
- lastBakeVertexCount = tricount;
- lods[curLod + 1] = makeLod(mesh);
- numBakedLods++;
- }
- }
- if (numBakedLods <= lodCount) {
- VertexBuffer[] bakedLods = new VertexBuffer[numBakedLods];
- System.arraycopy(lods, 0, bakedLods, 0, numBakedLods);
- return bakedLods;
- } else {
- return lods;
- }
- }
- /**
- * Computes the lods and bake them into the mesh<br>
- *
- * This method must be fed with the reduction method
- * {@link TriangleReductionMethod} and a list of reduction values.<br> for
- * each value a lod will be generated. <p> <strong>Important note :</strong>
- * some meshes cannot be decimated, so the result of this method can varry
- * depending of the given mesh. Also the reduction values are indicative and
- * the produces mesh will not always meet the required reduction.
- *
- * @param reductionMethod the reduction method to use
- * @param reductionValues the reduction value to use for each lod level.
- */
- public void bakeLods(TriangleReductionMethod reductionMethod, float... reductionValues) {
- mesh.setLodLevels(computeLods(reductionMethod, reductionValues));
- }
-
- private VertexBuffer makeLod(Mesh mesh) {
- VertexBuffer indexBuffer = mesh.getBuffer(VertexBuffer.Type.Index);
-
- boolean isShortBuffer = indexBuffer.getFormat() == VertexBuffer.Format.UnsignedShort;
- // Create buffers.
- VertexBuffer lodBuffer = new VertexBuffer(VertexBuffer.Type.Index);
- int bufsize = indexCount == 0 ? 3 : indexCount;
-
- if (isShortBuffer) {
- lodBuffer.setupData(VertexBuffer.Usage.Static, 3, VertexBuffer.Format.UnsignedShort, BufferUtils.createShortBuffer(bufsize));
- } else {
- lodBuffer.setupData(VertexBuffer.Usage.Static, 3, VertexBuffer.Format.UnsignedInt, BufferUtils.createIntBuffer(bufsize));
- }
-
-
-
- lodBuffer.getData().rewind();
- //Check if we should fill it with a "dummy" triangle.
- if (indexCount == 0) {
- if (isShortBuffer) {
- for (int m = 0; m < 3; m++) {
- ((ShortBuffer) lodBuffer.getData()).put((short) 0);
- }
- } else {
- for (int m = 0; m < 3; m++) {
- ((IntBuffer) lodBuffer.getData()).put(0);
- }
- }
- }
- // Fill buffers.
- Buffer buf = lodBuffer.getData();
- buf.rewind();
- for (Triangle triangle : triangleList) {
- if (!triangle.isRemoved) {
- assert (indexCount != 0);
- if (isShortBuffer) {
- for (int m = 0; m < 3; m++) {
- ((ShortBuffer) buf).put((short) triangle.vertexId[m]);
-
- }
- } else {
- for (int m = 0; m < 3; m++) {
- ((IntBuffer) buf).put(triangle.vertexId[m]);
- }
-
- }
- }
- }
- buf.clear();
- lodBuffer.updateData(buf);
- return lodBuffer;
- }
-
- private int calcLodTriCount(TriangleReductionMethod reductionMethod, float reductionValue) {
- int nbTris = mesh.getTriangleCount();
- switch (reductionMethod) {
- case PROPORTIONAL:
- collapseCostLimit = NEVER_COLLAPSE_COST;
- return (int) (nbTris - (nbTris * (reductionValue)));
-
- case CONSTANT:
- collapseCostLimit = NEVER_COLLAPSE_COST;
- if (reductionValue < nbTris) {
- return nbTris - (int) reductionValue;
- }
- return 0;
-
- case COLLAPSE_COST:
- collapseCostLimit = reductionValue;
- return 0;
-
- default:
- return nbTris;
- }
- }
-
- private int findDstID(int srcId, List<CollapsedEdge> tmpCollapsedEdges) {
- int i = 0;
- for (CollapsedEdge collapsedEdge : tmpCollapsedEdges) {
- if (collapsedEdge.srcID == srcId) {
- return i;
- }
- i++;
- }
- return Integer.MAX_VALUE;
- }
-
- private class CollapsedEdge {
-
- int srcID;
- int dstID;
- };
-
- private void removeTriangleFromEdges(Triangle triangle, Vertex skip) {
- // skip is needed if we are iterating on the vertex's edges or triangles.
- for (int i = 0; i < 3; i++) {
- if (triangle.vertex[i] != skip) {
- triangle.vertex[i].triangles.remove(triangle);
- }
- }
- for (int i = 0; i < 3; i++) {
- for (int n = 0; n < 3; n++) {
- if (i != n) {
- removeEdge(triangle.vertex[i], new Edge(triangle.vertex[n]));
- }
- }
- }
- }
-
- private void removeEdge(Vertex v, Edge edge) {
- Edge ed = null;
- for (Edge edge1 : v.edges) {
- if (edge1.equals(edge)) {
- ed = edge1;
- break;
- }
- }
-
- if (ed.refCount == 1) {
- v.edges.remove(ed);
- } else {
- ed.refCount--;
- }
-
- }
-
- boolean isBorderVertex(Vertex vertex) {
- for (Edge edge : vertex.edges) {
- if (edge.refCount == 1) {
- return true;
- }
- }
- return false;
- }
-
- private void addTriangleToEdges(Triangle tri) {
- if (bestQuality) {
- Triangle duplicate = getDuplicate(tri);
- if (duplicate != null) {
- if (!tri.isRemoved) {
- tri.isRemoved = true;
- indexCount -= 3;
- logger.log(Level.FINE, "duplicate triangle found{0}{1} It will be excluded from Lod level calculations.", new Object[]{tri, duplicate});
- }
- }
- }
- for (int i = 0; i < 3; i++) {
- tri.vertex[i].triangles.add(tri);
- }
- for (int i = 0; i < 3; i++) {
- for (int n = 0; n < 3; n++) {
- if (i != n) {
- addEdge(tri.vertex[i], new Edge(tri.vertex[n]));
- }
- }
- }
- }
-
- private void addEdge(Vertex v, Edge edge) {
- assert (edge.destination != v);
-
- for (Edge ed : v.edges) {
- if (ed.equals(edge)) {
- ed.refCount++;
- return;
- }
- }
-
- v.edges.add(edge);
- edge.refCount = 1;
-
- }
-
- private void initialize() {
- triangleList = new ArrayList<LodGenerator.Triangle>();
- }
-
- private Triangle getDuplicate(Triangle triangle) {
- // duplicate triangle detection (where all vertices has the same position)
- for (Triangle tri : triangle.vertex[0].triangles) {
- if (isDuplicateTriangle(triangle, tri)) {
- return tri;
- }
- }
- return null;
- }
-
- private boolean isDuplicateTriangle(Triangle triangle, Triangle triangle2) {
- for (int i = 0; i < 3; i++) {
- if (triangle.vertex[i] != triangle2.vertex[0]
- || triangle.vertex[i] != triangle2.vertex[1]
- || triangle.vertex[i] != triangle2.vertex[2]) {
- return false;
- }
- }
- return true;
- }
-
- private void replaceVertexID(Triangle triangle, int oldID, int newID, Vertex dst) {
- dst.triangles.add(triangle);
- // NOTE: triangle is not removed from src. This is implementation specific optimization.
- // Its up to the compiler to unroll everything.
- for (int i = 0; i < 3; i++) {
- if (triangle.vertexId[i] == oldID) {
- for (int n = 0; n < 3; n++) {
- if (i != n) {
- // This is implementation specific optimization to remove following line.
- //removeEdge(triangle.vertex[i], new Edge(triangle.vertex[n]));
- removeEdge(triangle.vertex[n], new Edge(triangle.vertex[i]));
- addEdge(triangle.vertex[n], new Edge(dst));
- addEdge(dst, new Edge(triangle.vertex[n]));
- }
- }
- triangle.vertex[i] = dst;
- triangle.vertexId[i] = newID;
- return;
- }
- }
- assert (false);
- }
-
- private void updateVertexCollapseCost(Vertex vertex) {
- float collapseCost = UNINITIALIZED_COLLAPSE_COST;
- Vertex collapseTo = null;
-
- for (Edge edge : vertex.edges) {
- edge.collapseCost = computeEdgeCollapseCost(vertex, edge);
- assert (edge.collapseCost != UNINITIALIZED_COLLAPSE_COST);
- if (collapseCost > edge.collapseCost) {
- collapseCost = edge.collapseCost;
- collapseTo = edge.destination;
- }
- }
- if (collapseCost != vertex.collapseCost || vertex.collapseTo != collapseTo) {
- assert (vertex.collapseTo != null);
- assert (find(collapseCostSet, vertex));
- collapseCostSet.remove(vertex);
- if (collapseCost != UNINITIALIZED_COLLAPSE_COST) {
- vertex.collapseCost = collapseCost;
- vertex.collapseTo = collapseTo;
- collapseCostSet.add(vertex);
- }
- }
- assert (vertex.collapseCost != UNINITIALIZED_COLLAPSE_COST);
- }
-
- private boolean hasSrcID(int srcID, List<CollapsedEdge> cEdges) {
- // This will only return exact matches.
- for (CollapsedEdge collapsedEdge : cEdges) {
- if (collapsedEdge.srcID == srcID) {
- return true;
- }
- }
-
- return false; // Not found
- }
-
- private boolean collapse(Vertex src) {
- Vertex dest = src.collapseTo;
- if (src.edges.isEmpty()) {
- return false;
- }
- assert (assertValidVertex(dest));
- assert (assertValidVertex(src));
-
- assert (src.collapseCost != NEVER_COLLAPSE_COST);
- assert (src.collapseCost != UNINITIALIZED_COLLAPSE_COST);
- assert (!src.edges.isEmpty());
- assert (!src.triangles.isEmpty());
- assert (src.edges.contains(new Edge(dest)));
- // It may have vertexIDs and triangles from different submeshes(different vertex buffers),
- // so we need to connect them correctly based on deleted triangle's edge.
- // mCollapsedEdgeIDs will be used, when looking up the connections for replacement.
- List<CollapsedEdge> tmpCollapsedEdges = new ArrayList<CollapsedEdge>();
- for (Iterator<Triangle> it = src.triangles.iterator(); it.hasNext();) {
- Triangle triangle = it.next();
- if (triangle.hasVertex(dest)) {
- // Remove a triangle
- // Tasks:
- // 1. Add it to the collapsed edges list.
- // 2. Reduce index count for the Lods, which will not have this triangle.
- // 3. Mark as removed, so it will not be added in upcoming Lod levels.
- // 4. Remove references/pointers to this triangle.
- // 1. task
- int srcID = triangle.getVertexIndex(src);
- if (!hasSrcID(srcID, tmpCollapsedEdges)) {
- CollapsedEdge cEdge = new CollapsedEdge();
- cEdge.srcID = srcID;
- cEdge.dstID = triangle.getVertexIndex(dest);
- tmpCollapsedEdges.add(cEdge);
- }
- // 2. task
- indexCount -= 3;
- // 3. task
- triangle.isRemoved = true;
- nbCollapsedTri++;
- // 4. task
- removeTriangleFromEdges(triangle, src);
- it.remove();
-
- }
- }
- assert (!tmpCollapsedEdges.isEmpty());
- assert (!dest.edges.contains(new Edge(src)));
-
-
- for (Iterator<Triangle> it = src.triangles.iterator(); it.hasNext();) {
- Triangle triangle = it.next();
- if (!triangle.hasVertex(dest)) {
- // Replace a triangle
- // Tasks:
- // 1. Determine the edge which we will move along. (we need to modify single vertex only)
- // 2. Move along the selected edge.
- // 1. task
- int srcID = triangle.getVertexIndex(src);
- int id = findDstID(srcID, tmpCollapsedEdges);
- if (id == Integer.MAX_VALUE) {
- // Not found any edge to move along.
- // Destroy the triangle.
- // if (!triangle.isRemoved) {
- triangle.isRemoved = true;
- indexCount -= 3;
- removeTriangleFromEdges(triangle, src);
- it.remove();
- nbCollapsedTri++;
- continue;
- }
- int dstID = tmpCollapsedEdges.get(id).dstID;
- // 2. task
- replaceVertexID(triangle, srcID, dstID, dest);
-
-
- if (bestQuality) {
- triangle.computeNormal();
- }
-
- }
- }
-
- if (bestQuality) {
- for (Edge edge : src.edges) {
- updateVertexCollapseCost(edge.destination);
- }
- updateVertexCollapseCost(dest);
- for (Edge edge : dest.edges) {
- updateVertexCollapseCost(edge.destination);
- }
-
- } else {
- // TODO: Find out why is this needed. assertOutdatedCollapseCost() fails on some
- // rare situations without this. For example goblin.mesh fails.
- //Treeset to have an ordered list with unique values
- SortedSet<Vertex> updatable = new TreeSet<Vertex>(collapseComparator);
-
- for (Edge edge : src.edges) {
- updatable.add(edge.destination);
- for (Edge edge1 : edge.destination.edges) {
- updatable.add(edge1.destination);
- }
- }
-
-
- for (Vertex vertex : updatable) {
- updateVertexCollapseCost(vertex);
- }
-
- }
- return true;
- }
-
- private boolean assertValidMesh() {
- // Allows to find bugs in collapsing.
- for (Vertex vertex : collapseCostSet) {
- assertValidVertex(vertex);
- }
- return true;
-
- }
-
- private boolean assertValidVertex(Vertex v) {
- // Allows to find bugs in collapsing.
- // System.out.println("Asserting " + v.index);
- for (Triangle t : v.triangles) {
- for (int i = 0; i < 3; i++) {
- // System.out.println("check " + t.vertex[i].index);
- //assert (collapseCostSet.contains(t.vertex[i]));
- assert (find(collapseCostSet, t.vertex[i]));
-
- assert (t.vertex[i].edges.contains(new Edge(t.vertex[i].collapseTo)));
- for (int n = 0; n < 3; n++) {
- if (i != n) {
-
- int id = t.vertex[i].edges.indexOf(new Edge(t.vertex[n]));
- Edge ed = t.vertex[i].edges.get(id);
- //assert (ed.collapseCost != UNINITIALIZED_COLLAPSE_COST);
- } else {
- assert (!t.vertex[i].edges.contains(new Edge(t.vertex[n])));
- }
- }
- }
- }
- return true;
- }
-
- private boolean find(List<Vertex> set, Vertex v) {
- for (Vertex vertex : set) {
- if (v == vertex) {
- return true;
- }
- }
- return false;
- }
- }
|