• R/O
  • HTTP
  • SSH
  • HTTPS

Commit

Tags
No Tags

Frequently used words (click to add to your profile)

javac++androidlinuxc#windowsobjective-ccocoa誰得qtpythonphprubygameguibathyscaphec計画中(planning stage)翻訳omegatframeworktwitterdomtestvb.netdirectxゲームエンジンbtronarduinopreviewer

Main repository of MikuMikuStudio


Commit MetaInfo

Revisãobacce56db44fee70023b48e77f925303997355a7 (tree)
Hora2013-06-03 01:44:47
Autorremy.bouquet@gmail.com <remy.bouquet@gmai...>
Commiterremy.bouquet@gmail.com

Mensagem de Log

Implemented a Lod Generator based on Ogre progressive mesh

git-svn-id: http://jmonkeyengine.googlecode.com/svn/trunk@10638 75d07b2b-3a1a-0410-a2c5-0572b91ccdca

Mudança Sumário

Diff

--- /dev/null
+++ b/engine/src/test/jme3test/stress/TestLodGeneration.java
@@ -0,0 +1,243 @@
1+/*
2+ * Copyright (c) 2009-2012 jMonkeyEngine
3+ * All rights reserved.
4+ *
5+ * Redistribution and use in source and binary forms, with or without
6+ * modification, are permitted provided that the following conditions are
7+ * met:
8+ *
9+ * * Redistributions of source code must retain the above copyright
10+ * notice, this list of conditions and the following disclaimer.
11+ *
12+ * * Redistributions in binary form must reproduce the above copyright
13+ * notice, this list of conditions and the following disclaimer in the
14+ * documentation and/or other materials provided with the distribution.
15+ *
16+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
17+ * may be used to endorse or promote products derived from this software
18+ * without specific prior written permission.
19+ *
20+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31+ */
32+package jme3test.stress;
33+
34+import com.jme3.animation.AnimChannel;
35+import com.jme3.animation.SkeletonControl;
36+import com.jme3.app.SimpleApplication;
37+import com.jme3.bounding.BoundingBox;
38+import com.jme3.font.BitmapText;
39+import com.jme3.input.ChaseCamera;
40+import com.jme3.input.KeyInput;
41+import com.jme3.input.controls.ActionListener;
42+import com.jme3.input.controls.KeyTrigger;
43+import com.jme3.light.AmbientLight;
44+import com.jme3.light.DirectionalLight;
45+import com.jme3.math.ColorRGBA;
46+import com.jme3.math.FastMath;
47+import com.jme3.math.Vector3f;
48+import com.jme3.scene.Geometry;
49+import com.jme3.scene.Node;
50+import com.jme3.scene.Spatial;
51+import com.jme3.scene.VertexBuffer;
52+import java.util.ArrayList;
53+import java.util.List;
54+import java.util.concurrent.Callable;
55+import java.util.concurrent.ScheduledThreadPoolExecutor;
56+import jme3tools.optimize.LodGenerator;
57+
58+public class TestLodGeneration extends SimpleApplication {
59+
60+ public static void main(String[] args) {
61+ TestLodGeneration app = new TestLodGeneration();
62+ app.start();
63+ }
64+ boolean wireFrame = false;
65+ float reductionvalue = 0.0f;
66+ private int lodLevel = 0;
67+ private Node model;
68+ private BitmapText hudText;
69+ private List<Geometry> listGeoms = new ArrayList<Geometry>();
70+ private ScheduledThreadPoolExecutor exec = new ScheduledThreadPoolExecutor(5);
71+ private AnimChannel ch;
72+
73+ public void simpleInitApp() {
74+
75+ DirectionalLight dl = new DirectionalLight();
76+ dl.setDirection(new Vector3f(-1, -1, -1).normalizeLocal());
77+ rootNode.addLight(dl);
78+ AmbientLight al = new AmbientLight();
79+ al.setColor(ColorRGBA.White.mult(0.6f));
80+ rootNode.addLight(al);
81+
82+ model = (Node) assetManager.loadModel("Models/Sinbad/Sinbad.mesh.xml");
83+ //model = (Node) assetManager.loadModel("Models/Jaime/Jaime.j3o");
84+ BoundingBox b = ((BoundingBox) model.getWorldBound());
85+ model.setLocalScale(1.2f / (b.getYExtent() * 2));
86+ // model.setLocalTranslation(0,-(b.getCenter().y - b.getYExtent())* model.getLocalScale().y, 0);
87+ for (Spatial spatial : model.getChildren()) {
88+ if (spatial instanceof Geometry) {
89+ listGeoms.add((Geometry) spatial);
90+ }
91+ }
92+ ChaseCamera chaseCam = new ChaseCamera(cam, inputManager);
93+ model.addControl(chaseCam);
94+ chaseCam.setLookAtOffset(b.getCenter());
95+ chaseCam.setDefaultDistance(5);
96+ chaseCam.setMinVerticalRotation(-FastMath.HALF_PI + 0.01f);
97+ chaseCam.setZoomSensitivity(0.5f);
98+
99+
100+
101+ // ch = model.getControl(AnimControl.class).createChannel();
102+ // ch.setAnim("Wave");
103+ SkeletonControl c = model.getControl(SkeletonControl.class);
104+ if (c != null) {
105+ c.setEnabled(false);
106+ }
107+
108+
109+ reductionvalue = 0.001f;
110+ // makeLod(LodGenerator.VertexReductionMethod.PROPORTIONAL, reductionvalue, 1);
111+
112+ lodLevel = 1;
113+ for (final Geometry geometry : listGeoms) {
114+ LodGenerator lODGenerator = new LodGenerator(geometry);
115+ lODGenerator.bakeLods(LodGenerator.TriangleReductionMethod.PROPORTIONAL, reductionvalue);
116+ geometry.setLodLevel(lodLevel);
117+
118+
119+ }
120+
121+ rootNode.attachChild(model);
122+ flyCam.setEnabled(false);
123+
124+
125+
126+ guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
127+ hudText = new BitmapText(guiFont, false);
128+ hudText.setSize(guiFont.getCharSet().getRenderedSize());
129+ hudText.setText(computeNbTri() + " tris");
130+ hudText.setLocalTranslation(cam.getWidth() / 2, hudText.getLineHeight(), 0);
131+ guiNode.attachChild(hudText);
132+
133+ inputManager.addListener(new ActionListener() {
134+ public void onAction(String name, boolean isPressed, float tpf) {
135+ if (isPressed) {
136+ if (name.equals("plus")) {
137+// lodLevel++;
138+// for (Geometry geometry : listGeoms) {
139+// if (geometry.getMesh().getNumLodLevels() <= lodLevel) {
140+// lodLevel = 0;
141+// }
142+// geometry.setLodLevel(lodLevel);
143+// }
144+// jaimeText.setText(computeNbTri() + " tris");
145+
146+
147+
148+ reductionvalue += 0.05f;
149+ updateLod();
150+
151+
152+
153+ }
154+ if (name.equals("minus")) {
155+// lodLevel--;
156+// for (Geometry geometry : listGeoms) {
157+// if (lodLevel < 0) {
158+// lodLevel = geometry.getMesh().getNumLodLevels() - 1;
159+// }
160+// geometry.setLodLevel(lodLevel);
161+// }
162+// jaimeText.setText(computeNbTri() + " tris");
163+
164+
165+
166+ reductionvalue -= 0.05f;
167+ updateLod();
168+
169+
170+ }
171+ if (name.equals("wireFrame")) {
172+ wireFrame = !wireFrame;
173+ for (Geometry geometry : listGeoms) {
174+ geometry.getMaterial().getAdditionalRenderState().setWireframe(wireFrame);
175+ }
176+ }
177+
178+ }
179+
180+ }
181+
182+ private void updateLod() {
183+ reductionvalue = FastMath.clamp(reductionvalue, 0.0f, 1.0f);
184+ makeLod(LodGenerator.TriangleReductionMethod.PROPORTIONAL, reductionvalue, 1);
185+ }
186+ }, "plus", "minus", "wireFrame");
187+
188+ inputManager.addMapping("plus", new KeyTrigger(KeyInput.KEY_ADD));
189+ inputManager.addMapping("minus", new KeyTrigger(KeyInput.KEY_SUBTRACT));
190+ inputManager.addMapping("wireFrame", new KeyTrigger(KeyInput.KEY_SPACE));
191+
192+
193+
194+ }
195+
196+ @Override
197+ public void simpleUpdate(float tpf) {
198+ // model.rotate(0, tpf, 0);
199+ }
200+
201+ private int computeNbTri() {
202+ int nbTri = 0;
203+ for (Geometry geometry : listGeoms) {
204+ if (geometry.getMesh().getNumLodLevels() > 0) {
205+ nbTri += geometry.getMesh().getLodLevel(lodLevel).getNumElements();
206+ } else {
207+ nbTri += geometry.getMesh().getTriangleCount();
208+ }
209+ }
210+ return nbTri;
211+ }
212+
213+ @Override
214+ public void destroy() {
215+ super.destroy();
216+ exec.shutdown();
217+ }
218+
219+ private void makeLod(final LodGenerator.TriangleReductionMethod method, final float value, final int ll) {
220+ exec.execute(new Runnable() {
221+ public void run() {
222+ for (final Geometry geometry : listGeoms) {
223+ LodGenerator lODGenerator = new LodGenerator(geometry);
224+ final VertexBuffer[] lods = lODGenerator.computeLods(method, value);
225+
226+ enqueue(new Callable<Void>() {
227+ public Void call() throws Exception {
228+ geometry.getMesh().setLodLevels(lods);
229+ lodLevel = 0;
230+ if (geometry.getMesh().getNumLodLevels() > ll) {
231+ lodLevel = ll;
232+ }
233+ geometry.setLodLevel(lodLevel);
234+ hudText.setText(computeNbTri() + " tris");
235+ return null;
236+ }
237+ });
238+ }
239+ }
240+ });
241+
242+ }
243+}
--- /dev/null
+++ b/engine/src/tools/jme3tools/optimize/LodGenerator.java
@@ -0,0 +1,1016 @@
1+/*
2+ * Copyright (c) 2009-2013 jMonkeyEngine
3+ * All rights reserved.
4+ *
5+ * Redistribution and use in source and binary forms, with or without
6+ * modification, are permitted provided that the following conditions are
7+ * met:
8+ *
9+ * * Redistributions of source code must retain the above copyright
10+ * notice, this list of conditions and the following disclaimer.
11+ *
12+ * * Redistributions in binary form must reproduce the above copyright
13+ * notice, this list of conditions and the following disclaimer in the
14+ * documentation and/or other materials provided with the distribution.
15+ *
16+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
17+ * may be used to endorse or promote products derived from this software
18+ * without specific prior written permission.
19+ *
20+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31+ */
32+package jme3tools.optimize;
33+
34+import com.jme3.bounding.BoundingSphere;
35+import com.jme3.math.Vector3f;
36+import com.jme3.scene.Geometry;
37+import com.jme3.scene.Mesh;
38+import com.jme3.scene.VertexBuffer;
39+import com.jme3.util.BufferUtils;
40+import java.nio.Buffer;
41+import java.nio.FloatBuffer;
42+import java.nio.IntBuffer;
43+import java.nio.ShortBuffer;
44+import java.util.ArrayList;
45+import java.util.Collections;
46+import java.util.Comparator;
47+import java.util.HashSet;
48+import java.util.Iterator;
49+import java.util.List;
50+import java.util.Set;
51+import java.util.SortedSet;
52+import java.util.TreeSet;
53+import java.util.logging.Level;
54+import java.util.logging.Logger;
55+
56+/**
57+ * This is an utility class that allows to generated the lod levels for an
58+ * arbitrary mesh. It computes a collapse cost for each vertex and each edges.
59+ * The higher the cost the most likely collapsing the edge or the vertex will
60+ * produce artifacts on the mesh. <p>This class is the java implementation of
61+ * the enhanced version og Ogre engine Lod generator, by Péter Szücs, originally
62+ * based on Stan Melax "easy mesh simplification". The MIT licences C++ source
63+ * code can be found here
64+ * https://github.com/worldforge/ember/tree/master/src/components/ogre/lod more
65+ * informations can be found here http://www.melax.com/polychop
66+ * http://sajty.elementfx.com/progressivemesh/GSoC2012.pdf </p>
67+ *
68+ * <p>The algorithm sort the vertice according to their collapsse cost
69+ * ascending. It collapse from the "cheapest" vertex to the more expensive.<br>
70+ * <strong>Usage : </strong><br>
71+ * <pre>
72+ * LodGenerator lODGenerator = new LodGenerator(geometry);
73+ * lODGenerator.bakeLods(reductionMethod,reductionvalue);
74+ * </pre> redutionMethod type is VertexReductionMethod described here
75+ * {@link TriangleReductionMethod} reductionvalue depends on the
76+ * reductionMethod<p>
77+ *
78+ *
79+ * @author Nehon
80+ */
81+public class LodGenerator {
82+
83+ private static final Logger logger = Logger.getLogger(LodGenerator.class.getName());
84+ private static final float NEVER_COLLAPSE_COST = Float.MAX_VALUE;
85+ private static final float UNINITIALIZED_COLLAPSE_COST = Float.POSITIVE_INFINITY;
86+ private Vector3f tmpV1 = new Vector3f();
87+ private Vector3f tmpV2 = new Vector3f();
88+ private boolean bestQuality = true;
89+ private int indexCount = 0;
90+ private List<Vertex> collapseCostSet = new ArrayList<Vertex>();
91+ private float collapseCostLimit;
92+ private List<Triangle> triangleList;
93+ private List<Vertex> vertexList = new ArrayList<Vertex>();
94+ private float meshBoundingSphereRadius;
95+ private Mesh mesh;
96+
97+ /**
98+ * Describe the way trinagles will be removed. <br> PROPORTIONAL :
99+ * Percentage of triangles to be removed from the mesh. Valid range is a
100+ * number between 0.0 and 1.0 <br> CONSTANT : Triangle count to be removed
101+ * from the mesh. Pass only integers or it will be rounded. <br>
102+ * COLLAPSE_COST : Reduces the vertices, until the cost is bigger then the
103+ * given value. Collapse cost is equal to the amount of artifact the
104+ * reduction causes. This generates the best Lod output, but the collapse
105+ * cost depends on implementation.
106+ */
107+ public enum TriangleReductionMethod {
108+
109+ /**
110+ * Percentage of triangles to be removed from the mesh.
111+ *
112+ * Valid range is a number between 0.0 and 1.0
113+ */
114+ PROPORTIONAL,
115+ /**
116+ * Triangle count to be removed from the mesh.
117+ *
118+ * Pass only integers or it will be rounded.
119+ */
120+ CONSTANT,
121+ /**
122+ * Reduces the vertices, until the cost is bigger then the given value.
123+ *
124+ * Collapse cost is equal to the amount of artifact the reduction
125+ * causes. This generates the best Lod output, but the collapse cost
126+ * depends on implementation.
127+ */
128+ COLLAPSE_COST
129+ };
130+
131+ private class Edge {
132+
133+ Vertex destination;
134+ float collapseCost = UNINITIALIZED_COLLAPSE_COST;
135+ int refCount;
136+
137+ public Edge(Vertex destination) {
138+ this.destination = destination;
139+ }
140+
141+ public void set(Edge other) {
142+ destination = other.destination;
143+ collapseCost = other.collapseCost;
144+ refCount = other.refCount;
145+ }
146+
147+ @Override
148+ public boolean equals(Object obj) {
149+ if (!(obj instanceof Edge)) {
150+ return false;
151+ }
152+ return destination == ((Edge) obj).destination;
153+ }
154+
155+ @Override
156+ public int hashCode() {
157+ return destination.hashCode();
158+ }
159+
160+ @Override
161+ public String toString() {
162+ return "Edge{" + "collapsTo " + destination.index + '}';
163+ }
164+ }
165+
166+ private class Vertex {
167+
168+ Vector3f position = new Vector3f();
169+ float collapseCost = UNINITIALIZED_COLLAPSE_COST;
170+ List<Edge> edges = new ArrayList<Edge>();
171+ Set<Triangle> triangles = new HashSet<Triangle>();
172+ Vertex collapseTo;
173+ boolean isSeam;
174+ int index;//index in the buffer for debugging
175+
176+ @Override
177+ public String toString() {
178+ return index + " : " + position.toString();
179+ }
180+ }
181+
182+ private class Triangle {
183+
184+ Vertex[] vertex = new Vertex[3];
185+ Vector3f normal;
186+ boolean isRemoved;
187+ //indices of the vertices in the vertex buffer
188+ int[] vertexId = new int[3];
189+
190+ void computeNormal() {
191+ // Cross-product 2 edges
192+ tmpV1.set(vertex[1].position).subtractLocal(vertex[0].position);
193+ tmpV2.set(vertex[2].position).subtractLocal(vertex[1].position);
194+
195+ normal = tmpV1.cross(tmpV2);
196+ normal.normalizeLocal();
197+ }
198+
199+ boolean hasVertex(Vertex v) {
200+ return (v == vertex[0] || v == vertex[1] || v == vertex[2]);
201+ }
202+
203+ int getVertexIndex(Vertex v) {
204+ for (int i = 0; i < 3; i++) {
205+ if (vertex[i] == v) {
206+ return vertexId[i];
207+ }
208+ }
209+ throw new IllegalArgumentException("Vertex " + v + "is not part of triangle" + this);
210+ }
211+
212+ boolean isMalformed() {
213+ return vertex[0] == vertex[1] || vertex[0] == vertex[2] || vertex[1] == vertex[2];
214+ }
215+
216+ @Override
217+ public String toString() {
218+ String out = "Triangle{\n";
219+ for (int i = 0; i < 3; i++) {
220+ out += vertexId[i] + " : " + vertex[i].toString() + "\n";
221+ }
222+ out += '}';
223+ return out;
224+ }
225+ }
226+ /**
227+ * Compartator used to sort vertices according to their collapse cost
228+ */
229+ private Comparator collapseComparator = new Comparator<Vertex>() {
230+ public int compare(Vertex o1, Vertex o2) {
231+ if (Float.compare(o1.collapseCost, o2.collapseCost) == 0) {
232+ return 0;
233+ }
234+ if (o1.collapseCost < o2.collapseCost) {
235+ return -1;
236+ }
237+ return 1;
238+ }
239+ };
240+
241+ /**
242+ * Construct a LodGenerator for the given geometry
243+ *
244+ * @param geom the geometry to consider to generate de Lods.
245+ */
246+ public LodGenerator(Geometry geom) {
247+ mesh = geom.getMesh();
248+ build();
249+ }
250+
251+ private void build() {
252+ BoundingSphere bs = new BoundingSphere();
253+ bs.computeFromPoints(mesh.getFloatBuffer(VertexBuffer.Type.Position));
254+ meshBoundingSphereRadius = bs.getRadius();
255+ List<Vertex> vertexLookup = new ArrayList<Vertex>();
256+ initialize();
257+
258+ gatherVertexData(mesh, vertexLookup);
259+ gatherIndexData(mesh, vertexLookup);
260+ computeCosts();
261+ assert (assertValidMesh());
262+
263+ }
264+
265+ private void gatherVertexData(Mesh mesh, List<Vertex> vertexLookup) {
266+
267+ //in case the model is currently animating with software animation
268+ //attempting to retrieve the bind position instead of the position.
269+ VertexBuffer position = mesh.getBuffer(VertexBuffer.Type.BindPosePosition);
270+ if (position == null) {
271+ position = mesh.getBuffer(VertexBuffer.Type.Position);
272+ }
273+ FloatBuffer pos = (FloatBuffer) position.getDataReadOnly();
274+ pos.rewind();
275+
276+ while (pos.remaining() != 0) {
277+ Vertex v = new Vertex();
278+ v.position.setX(pos.get());
279+ v.position.setY(pos.get());
280+ v.position.setZ(pos.get());
281+ v.isSeam = false;
282+ Vertex existingV = findSimilar(v);
283+ if (existingV != null) {
284+ //vertex position already exists
285+ existingV.isSeam = true;
286+ v.isSeam = true;
287+ } else {
288+ vertexList.add(v);
289+ }
290+ vertexLookup.add(v);
291+ }
292+ pos.rewind();
293+ }
294+
295+ private Vertex findSimilar(Vertex v) {
296+ for (Vertex vertex : vertexList) {
297+ if (vertex.position.equals(v.position)) {
298+ return vertex;
299+ }
300+ }
301+ return null;
302+ }
303+
304+ private void gatherIndexData(Mesh mesh, List<Vertex> vertexLookup) {
305+ VertexBuffer indexBuffer = mesh.getBuffer(VertexBuffer.Type.Index);
306+ indexCount = indexBuffer.getNumElements() * 3;
307+ Buffer b = indexBuffer.getDataReadOnly();
308+ b.rewind();
309+
310+ while (b.remaining() != 0) {
311+ Triangle tri = new Triangle();
312+ tri.isRemoved = false;
313+ triangleList.add(tri);
314+ for (int i = 0; i < 3; i++) {
315+ if (b instanceof IntBuffer) {
316+ tri.vertexId[i] = ((IntBuffer) b).get();
317+ } else {
318+ tri.vertexId[i] = ((ShortBuffer) b).get();
319+ }
320+ assert (tri.vertexId[i] < vertexLookup.size());
321+ tri.vertex[i] = vertexLookup.get(tri.vertexId[i]);
322+ //debug only;
323+ tri.vertex[i].index = tri.vertexId[i];
324+ }
325+ if (tri.isMalformed()) {
326+ if (!tri.isRemoved) {
327+ 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()});
328+ tri.isRemoved = true;
329+ indexCount -= 3;
330+ }
331+
332+ } else {
333+ tri.computeNormal();
334+ addTriangleToEdges(tri);
335+ }
336+ }
337+ b.rewind();
338+ }
339+
340+ private void computeCosts() {
341+ collapseCostSet.clear();
342+
343+ for (Vertex vertex : vertexList) {
344+
345+ if (!vertex.edges.isEmpty()) {
346+ computeVertexCollapseCost(vertex);
347+ } else {
348+ logger.log(Level.FINE, "Found isolated vertex {0} It will be excluded from Lod level calculations.", vertex);
349+ }
350+ }
351+ assert (vertexList.size() == collapseCostSet.size());
352+ assert (checkCosts());
353+ }
354+
355+ //Debug only
356+ private boolean checkCosts() {
357+ for (Vertex vertex : vertexList) {
358+ boolean test = find(collapseCostSet, vertex);
359+ if (!test) {
360+ System.out.println("vertex " + vertex.index + " not present in collapse costs");
361+ return false;
362+ }
363+ }
364+ return true;
365+ }
366+
367+ private void computeVertexCollapseCost(Vertex vertex) {
368+
369+ vertex.collapseCost = UNINITIALIZED_COLLAPSE_COST;
370+ assert (!vertex.edges.isEmpty());
371+ for (Edge edge : vertex.edges) {
372+ edge.collapseCost = computeEdgeCollapseCost(vertex, edge);
373+ assert (edge.collapseCost != UNINITIALIZED_COLLAPSE_COST);
374+ if (vertex.collapseCost > edge.collapseCost) {
375+ vertex.collapseCost = edge.collapseCost;
376+ vertex.collapseTo = edge.destination;
377+ }
378+ }
379+ assert (vertex.collapseCost != UNINITIALIZED_COLLAPSE_COST);
380+ collapseCostSet.add(vertex);
381+ }
382+
383+ float computeEdgeCollapseCost(Vertex src, Edge dstEdge) {
384+ // This is based on Ogre's collapse cost calculation algorithm.
385+
386+ Vertex dest = dstEdge.destination;
387+
388+ // Check for singular triangle destruction
389+ // If src and dest both only have 1 triangle (and it must be a shared one)
390+ // then this would destroy the shape, so don't do this
391+ if (src.triangles.size() == 1 && dest.triangles.size() == 1) {
392+ return NEVER_COLLAPSE_COST;
393+ }
394+
395+ // Degenerate case check
396+ // Are we going to invert a face normal of one of the neighbouring faces?
397+ // Can occur when we have a very small remaining edge and collapse crosses it
398+ // Look for a face normal changing by > 90 degrees
399+ for (Triangle triangle : src.triangles) {
400+ // Ignore the deleted faces (those including src & dest)
401+ if (!triangle.hasVertex(dest)) {
402+ // Test the new face normal
403+ Vertex pv0, pv1, pv2;
404+
405+ // Replace src with dest wherever it is
406+ pv0 = (triangle.vertex[0] == src) ? dest : triangle.vertex[0];
407+ pv1 = (triangle.vertex[1] == src) ? dest : triangle.vertex[1];
408+ pv2 = (triangle.vertex[2] == src) ? dest : triangle.vertex[2];
409+
410+ // Cross-product 2 edges
411+ tmpV1.set(pv1.position).subtractLocal(pv0.position);
412+ tmpV2.set(pv2.position).subtractLocal(pv1.position);
413+
414+ //computing the normal
415+ Vector3f newNormal = tmpV1.crossLocal(tmpV2);
416+ newNormal.normalizeLocal();
417+
418+ // Dot old and new face normal
419+ // If < 0 then more than 90 degree difference
420+ if (newNormal.dot(triangle.normal) < 0.0f) {
421+ // Don't do it!
422+ return NEVER_COLLAPSE_COST;
423+ }
424+ }
425+ }
426+
427+ float cost;
428+
429+ // Special cases
430+ // If we're looking at a border vertex
431+ if (isBorderVertex(src)) {
432+ if (dstEdge.refCount > 1) {
433+ // src is on a border, but the src-dest edge has more than one tri on it
434+ // So it must be collapsing inwards
435+ // Mark as very high-value cost
436+ // curvature = 1.0f;
437+ cost = 1.0f;
438+ } else {
439+ // Collapsing ALONG a border
440+ // We can't use curvature to measure the effect on the model
441+ // Instead, see what effect it has on 'pulling' the other border edges
442+ // The more colinear, the less effect it will have
443+ // So measure the 'kinkiness' (for want of a better term)
444+
445+ // Find the only triangle using this edge.
446+ // PMTriangle* triangle = findSideTriangle(src, dst);
447+
448+ cost = 0.0f;
449+ Vector3f collapseEdge = tmpV1.set(src.position).subtractLocal(dest.position);
450+ collapseEdge.normalizeLocal();
451+
452+ for (Edge edge : src.edges) {
453+
454+ Vertex neighbor = edge.destination;
455+ //reference check intended
456+ if (neighbor != dest && edge.refCount == 1) {
457+ Vector3f otherBorderEdge = tmpV2.set(src.position).subtractLocal(neighbor.position);
458+ otherBorderEdge.normalizeLocal();
459+ // This time, the nearer the dot is to -1, the better, because that means
460+ // the edges are opposite each other, therefore less kinkiness
461+ // Scale into [0..1]
462+ float kinkiness = (otherBorderEdge.dot(collapseEdge) + 1.002f) * 0.5f;
463+ cost = Math.max(cost, kinkiness);
464+ }
465+ }
466+ }
467+ } else { // not a border
468+
469+ // Standard inner vertex
470+ // Calculate curvature
471+ // use the triangle facing most away from the sides
472+ // to determine our curvature term
473+ // Iterate over src's faces again
474+ cost = 0.001f;
475+
476+ for (Triangle triangle : src.triangles) {
477+ float mincurv = 1.0f; // curve for face i and closer side to it
478+
479+ for (Triangle triangle2 : src.triangles) {
480+ if (triangle2.hasVertex(dest)) {
481+
482+ // Dot product of face normal gives a good delta angle
483+ float dotprod = triangle.normal.dot(triangle2.normal);
484+ // NB we do (1-..) to invert curvature where 1 is high curvature [0..1]
485+ // Whilst dot product is high when angle difference is low
486+ mincurv = Math.min(mincurv, (1.002f - dotprod) * 0.5f);
487+ }
488+ }
489+ cost = Math.max(cost, mincurv);
490+ }
491+ }
492+
493+ // check for texture seam ripping
494+ if (src.isSeam) {
495+ if (!dest.isSeam) {
496+ cost += meshBoundingSphereRadius;
497+ } else {
498+ cost += meshBoundingSphereRadius * 0.5;
499+ }
500+ }
501+
502+ assert (cost >= 0);
503+ // TODO: use squared distance.
504+ return cost * src.position.distance(dest.position);
505+ }
506+ int nbCollapsedTri = 0;
507+
508+ /**
509+ * Computes the lod and return a list of VertexBuffers that can then be used
510+ * for lod (use Mesg.setLodLevels(VertexBuffer[]))<br>
511+ *
512+ * This method must be fed with the reduction method
513+ * {@link TriangleReductionMethod} and a list of reduction values.<br> for
514+ * each value a lod will be generated. <br> The resulting array will always
515+ * contain at index 0 the original index buffer of the mesh. <p>
516+ * <strong>Important note :</strong> some meshes cannot be decimated, so the
517+ * result of this method can varry depending of the given mesh. Also the
518+ * reduction values are indicative and the produces mesh will not always
519+ * meet the required reduction.
520+ *
521+ * @param reductionMethod the reduction method to use
522+ * @param reductionValues the reduction value to use for each lod level.
523+ * @return an array of VertexBuffers containing the different index buffers
524+ * representing the lod levels.
525+ */
526+ public VertexBuffer[] computeLods(TriangleReductionMethod reductionMethod, float... reductionValues) {
527+ int tricount = triangleList.size();
528+ int lastBakeVertexCount = tricount;
529+ int lodCount = reductionValues.length;
530+ VertexBuffer[] lods = new VertexBuffer[lodCount + 1];
531+ int numBakedLods = 1;
532+ lods[0] = mesh.getBuffer(VertexBuffer.Type.Index);
533+ for (int curLod = 0; curLod < lodCount; curLod++) {
534+ int neededTriCount = calcLodTriCount(reductionMethod, reductionValues[curLod]);
535+ while (neededTriCount < tricount) {
536+ Collections.sort(collapseCostSet, collapseComparator);
537+ Iterator<Vertex> it = collapseCostSet.iterator();
538+
539+ if (it.hasNext()) {
540+ Vertex v = it.next();
541+ if (v.collapseCost < collapseCostLimit) {
542+ if (!collapse(v)) {
543+ logger.log(Level.FINE, "Couldn''t collapse vertex{0}", v.index);
544+ }
545+ Iterator<Vertex> it2 = collapseCostSet.iterator();
546+ if (it2.hasNext()) {
547+ it2.next();
548+ it2.remove();// Remove src from collapse costs.
549+ }
550+
551+ } else {
552+ break;
553+ }
554+ } else {
555+ break;
556+ }
557+ tricount = triangleList.size() - nbCollapsedTri;
558+ }
559+ logger.log(Level.FINE, "collapsed {0} tris", nbCollapsedTri);
560+ boolean outSkipped = (lastBakeVertexCount == tricount);
561+ if (!outSkipped) {
562+ lastBakeVertexCount = tricount;
563+ lods[curLod + 1] = makeLod(mesh);
564+ numBakedLods++;
565+ }
566+ }
567+ if (numBakedLods <= lodCount) {
568+ VertexBuffer[] bakedLods = new VertexBuffer[numBakedLods];
569+ System.arraycopy(lods, 0, bakedLods, 0, numBakedLods);
570+ return bakedLods;
571+ } else {
572+ return lods;
573+ }
574+ }
575+
576+ /**
577+ * Computes the lods and bake them into the mesh<br>
578+ *
579+ * This method must be fed with the reduction method
580+ * {@link TriangleReductionMethod} and a list of reduction values.<br> for
581+ * each value a lod will be generated. <p> <strong>Important note :</strong>
582+ * some meshes cannot be decimated, so the result of this method can varry
583+ * depending of the given mesh. Also the reduction values are indicative and
584+ * the produces mesh will not always meet the required reduction.
585+ *
586+ * @param reductionMethod the reduction method to use
587+ * @param reductionValues the reduction value to use for each lod level.
588+ */
589+ public void bakeLods(TriangleReductionMethod reductionMethod, float... reductionValues) {
590+ mesh.setLodLevels(computeLods(reductionMethod, reductionValues));
591+ }
592+
593+ private VertexBuffer makeLod(Mesh mesh) {
594+ VertexBuffer indexBuffer = mesh.getBuffer(VertexBuffer.Type.Index);
595+
596+ boolean isShortBuffer = indexBuffer.getFormat() == VertexBuffer.Format.UnsignedShort;
597+ // Create buffers.
598+ VertexBuffer lodBuffer = new VertexBuffer(VertexBuffer.Type.Index);
599+ int bufsize = indexCount == 0 ? 3 : indexCount;
600+
601+ if (isShortBuffer) {
602+ lodBuffer.setupData(VertexBuffer.Usage.Static, 3, VertexBuffer.Format.UnsignedShort, BufferUtils.createShortBuffer(bufsize));
603+ } else {
604+ lodBuffer.setupData(VertexBuffer.Usage.Static, 3, VertexBuffer.Format.UnsignedInt, BufferUtils.createIntBuffer(bufsize));
605+ }
606+
607+
608+
609+ lodBuffer.getData().rewind();
610+ //Check if we should fill it with a "dummy" triangle.
611+ if (indexCount == 0) {
612+ if (isShortBuffer) {
613+ for (int m = 0; m < 3; m++) {
614+ ((ShortBuffer) lodBuffer.getData()).put((short) 0);
615+ }
616+ } else {
617+ for (int m = 0; m < 3; m++) {
618+ ((IntBuffer) lodBuffer.getData()).put(0);
619+ }
620+ }
621+ }
622+
623+ // Fill buffers.
624+ Buffer buf = lodBuffer.getData();
625+ buf.rewind();
626+ for (Triangle triangle : triangleList) {
627+ if (!triangle.isRemoved) {
628+ assert (indexCount != 0);
629+ if (isShortBuffer) {
630+ for (int m = 0; m < 3; m++) {
631+ ((ShortBuffer) buf).put((short) triangle.vertexId[m]);
632+
633+ }
634+ } else {
635+ for (int m = 0; m < 3; m++) {
636+ ((IntBuffer) buf).put(triangle.vertexId[m]);
637+ }
638+
639+ }
640+ }
641+ }
642+ buf.clear();
643+ lodBuffer.updateData(buf);
644+ return lodBuffer;
645+ }
646+
647+ private int calcLodTriCount(TriangleReductionMethod reductionMethod, float reductionValue) {
648+ int nbTris = mesh.getTriangleCount();
649+ switch (reductionMethod) {
650+ case PROPORTIONAL:
651+ collapseCostLimit = NEVER_COLLAPSE_COST;
652+ return (int) (nbTris - (nbTris * (reductionValue)));
653+
654+ case CONSTANT:
655+ collapseCostLimit = NEVER_COLLAPSE_COST;
656+ if (reductionValue < nbTris) {
657+ return nbTris - (int) reductionValue;
658+ }
659+ return 0;
660+
661+ case COLLAPSE_COST:
662+ collapseCostLimit = reductionValue;
663+ return 0;
664+
665+ default:
666+ return nbTris;
667+ }
668+ }
669+
670+ private int findDstID(int srcId, List<CollapsedEdge> tmpCollapsedEdges) {
671+ int i = 0;
672+ for (CollapsedEdge collapsedEdge : tmpCollapsedEdges) {
673+ if (collapsedEdge.srcID == srcId) {
674+ return i;
675+ }
676+ i++;
677+ }
678+ return Integer.MAX_VALUE;
679+ }
680+
681+ private class CollapsedEdge {
682+
683+ int srcID;
684+ int dstID;
685+ };
686+
687+ private void removeTriangleFromEdges(Triangle triangle, Vertex skip) {
688+ // skip is needed if we are iterating on the vertex's edges or triangles.
689+ for (int i = 0; i < 3; i++) {
690+ if (triangle.vertex[i] != skip) {
691+ triangle.vertex[i].triangles.remove(triangle);
692+ }
693+ }
694+ for (int i = 0; i < 3; i++) {
695+ for (int n = 0; n < 3; n++) {
696+ if (i != n) {
697+ removeEdge(triangle.vertex[i], new Edge(triangle.vertex[n]));
698+ }
699+ }
700+ }
701+ }
702+
703+ private void removeEdge(Vertex v, Edge edge) {
704+ Edge ed = null;
705+ for (Edge edge1 : v.edges) {
706+ if (edge1.equals(edge)) {
707+ ed = edge1;
708+ break;
709+ }
710+ }
711+
712+ if (ed.refCount == 1) {
713+ v.edges.remove(ed);
714+ } else {
715+ ed.refCount--;
716+ }
717+
718+ }
719+
720+ boolean isBorderVertex(Vertex vertex) {
721+ for (Edge edge : vertex.edges) {
722+ if (edge.refCount == 1) {
723+ return true;
724+ }
725+ }
726+ return false;
727+ }
728+
729+ private void addTriangleToEdges(Triangle tri) {
730+ if (bestQuality) {
731+ Triangle duplicate = getDuplicate(tri);
732+ if (duplicate != null) {
733+ if (!tri.isRemoved) {
734+ tri.isRemoved = true;
735+ indexCount -= 3;
736+ logger.log(Level.FINE, "duplicate triangle found{0}{1} It will be excluded from Lod level calculations.", new Object[]{tri, duplicate});
737+ }
738+ }
739+ }
740+ for (int i = 0; i < 3; i++) {
741+ tri.vertex[i].triangles.add(tri);
742+ }
743+ for (int i = 0; i < 3; i++) {
744+ for (int n = 0; n < 3; n++) {
745+ if (i != n) {
746+ addEdge(tri.vertex[i], new Edge(tri.vertex[n]));
747+ }
748+ }
749+ }
750+ }
751+
752+ private void addEdge(Vertex v, Edge edge) {
753+ assert (edge.destination != v);
754+
755+ for (Edge ed : v.edges) {
756+ if (ed.equals(edge)) {
757+ ed.refCount++;
758+ return;
759+ }
760+ }
761+
762+ v.edges.add(edge);
763+ edge.refCount = 1;
764+
765+ }
766+
767+ private void initialize() {
768+ triangleList = new ArrayList<LodGenerator.Triangle>();
769+ }
770+
771+ private Triangle getDuplicate(Triangle triangle) {
772+ // duplicate triangle detection (where all vertices has the same position)
773+ for (Triangle tri : triangle.vertex[0].triangles) {
774+ if (isDuplicateTriangle(triangle, tri)) {
775+ return tri;
776+ }
777+ }
778+ return null;
779+ }
780+
781+ private boolean isDuplicateTriangle(Triangle triangle, Triangle triangle2) {
782+ for (int i = 0; i < 3; i++) {
783+ if (triangle.vertex[i] != triangle2.vertex[0]
784+ || triangle.vertex[i] != triangle2.vertex[1]
785+ || triangle.vertex[i] != triangle2.vertex[2]) {
786+ return false;
787+ }
788+ }
789+ return true;
790+ }
791+
792+ private void replaceVertexID(Triangle triangle, int oldID, int newID, Vertex dst) {
793+ dst.triangles.add(triangle);
794+ // NOTE: triangle is not removed from src. This is implementation specific optimization.
795+
796+ // Its up to the compiler to unroll everything.
797+ for (int i = 0; i < 3; i++) {
798+ if (triangle.vertexId[i] == oldID) {
799+ for (int n = 0; n < 3; n++) {
800+ if (i != n) {
801+ // This is implementation specific optimization to remove following line.
802+ //removeEdge(triangle.vertex[i], new Edge(triangle.vertex[n]));
803+
804+ removeEdge(triangle.vertex[n], new Edge(triangle.vertex[i]));
805+ addEdge(triangle.vertex[n], new Edge(dst));
806+ addEdge(dst, new Edge(triangle.vertex[n]));
807+ }
808+ }
809+ triangle.vertex[i] = dst;
810+ triangle.vertexId[i] = newID;
811+ return;
812+ }
813+ }
814+ assert (false);
815+ }
816+
817+ private void updateVertexCollapseCost(Vertex vertex) {
818+ float collapseCost = UNINITIALIZED_COLLAPSE_COST;
819+ Vertex collapseTo = null;
820+
821+ for (Edge edge : vertex.edges) {
822+ edge.collapseCost = computeEdgeCollapseCost(vertex, edge);
823+ assert (edge.collapseCost != UNINITIALIZED_COLLAPSE_COST);
824+ if (collapseCost > edge.collapseCost) {
825+ collapseCost = edge.collapseCost;
826+ collapseTo = edge.destination;
827+ }
828+ }
829+ if (collapseCost != vertex.collapseCost || vertex.collapseTo != collapseTo) {
830+ assert (vertex.collapseTo != null);
831+ assert (find(collapseCostSet, vertex));
832+ collapseCostSet.remove(vertex);
833+ if (collapseCost != UNINITIALIZED_COLLAPSE_COST) {
834+ vertex.collapseCost = collapseCost;
835+ vertex.collapseTo = collapseTo;
836+ collapseCostSet.add(vertex);
837+ }
838+ }
839+ assert (vertex.collapseCost != UNINITIALIZED_COLLAPSE_COST);
840+ }
841+
842+ private boolean hasSrcID(int srcID, List<CollapsedEdge> cEdges) {
843+ // This will only return exact matches.
844+ for (CollapsedEdge collapsedEdge : cEdges) {
845+ if (collapsedEdge.srcID == srcID) {
846+ return true;
847+ }
848+ }
849+
850+ return false; // Not found
851+ }
852+
853+ private boolean collapse(Vertex src) {
854+ Vertex dest = src.collapseTo;
855+ if (src.edges.isEmpty()) {
856+ return false;
857+ }
858+ assert (assertValidVertex(dest));
859+ assert (assertValidVertex(src));
860+
861+ assert (src.collapseCost != NEVER_COLLAPSE_COST);
862+ assert (src.collapseCost != UNINITIALIZED_COLLAPSE_COST);
863+ assert (!src.edges.isEmpty());
864+ assert (!src.triangles.isEmpty());
865+ assert (src.edges.contains(new Edge(dest)));
866+
867+ // It may have vertexIDs and triangles from different submeshes(different vertex buffers),
868+ // so we need to connect them correctly based on deleted triangle's edge.
869+ // mCollapsedEdgeIDs will be used, when looking up the connections for replacement.
870+ List<CollapsedEdge> tmpCollapsedEdges = new ArrayList<CollapsedEdge>();
871+ for (Iterator<Triangle> it = src.triangles.iterator(); it.hasNext();) {
872+ Triangle triangle = it.next();
873+ if (triangle.hasVertex(dest)) {
874+ // Remove a triangle
875+ // Tasks:
876+ // 1. Add it to the collapsed edges list.
877+ // 2. Reduce index count for the Lods, which will not have this triangle.
878+ // 3. Mark as removed, so it will not be added in upcoming Lod levels.
879+ // 4. Remove references/pointers to this triangle.
880+
881+ // 1. task
882+ int srcID = triangle.getVertexIndex(src);
883+ if (!hasSrcID(srcID, tmpCollapsedEdges)) {
884+ CollapsedEdge cEdge = new CollapsedEdge();
885+ cEdge.srcID = srcID;
886+ cEdge.dstID = triangle.getVertexIndex(dest);
887+ tmpCollapsedEdges.add(cEdge);
888+ }
889+
890+ // 2. task
891+ indexCount -= 3;
892+
893+ // 3. task
894+ triangle.isRemoved = true;
895+ nbCollapsedTri++;
896+
897+ // 4. task
898+ removeTriangleFromEdges(triangle, src);
899+ it.remove();
900+
901+ }
902+ }
903+ assert (!tmpCollapsedEdges.isEmpty());
904+ assert (!dest.edges.contains(new Edge(src)));
905+
906+
907+ for (Iterator<Triangle> it = src.triangles.iterator(); it.hasNext();) {
908+ Triangle triangle = it.next();
909+ if (!triangle.hasVertex(dest)) {
910+ // Replace a triangle
911+ // Tasks:
912+ // 1. Determine the edge which we will move along. (we need to modify single vertex only)
913+ // 2. Move along the selected edge.
914+
915+ // 1. task
916+ int srcID = triangle.getVertexIndex(src);
917+ int id = findDstID(srcID, tmpCollapsedEdges);
918+ if (id == Integer.MAX_VALUE) {
919+ // Not found any edge to move along.
920+ // Destroy the triangle.
921+ // if (!triangle.isRemoved) {
922+ triangle.isRemoved = true;
923+ indexCount -= 3;
924+ removeTriangleFromEdges(triangle, src);
925+ it.remove();
926+ nbCollapsedTri++;
927+ continue;
928+ }
929+ int dstID = tmpCollapsedEdges.get(id).dstID;
930+
931+ // 2. task
932+ replaceVertexID(triangle, srcID, dstID, dest);
933+
934+
935+ if (bestQuality) {
936+ triangle.computeNormal();
937+ }
938+
939+ }
940+ }
941+
942+ if (bestQuality) {
943+ for (Edge edge : src.edges) {
944+ updateVertexCollapseCost(edge.destination);
945+ }
946+ updateVertexCollapseCost(dest);
947+ for (Edge edge : dest.edges) {
948+ updateVertexCollapseCost(edge.destination);
949+ }
950+
951+ } else {
952+ // TODO: Find out why is this needed. assertOutdatedCollapseCost() fails on some
953+ // rare situations without this. For example goblin.mesh fails.
954+ //Treeset to have an ordered list with unique values
955+ SortedSet<Vertex> updatable = new TreeSet<Vertex>(collapseComparator);
956+
957+ for (Edge edge : src.edges) {
958+ updatable.add(edge.destination);
959+ for (Edge edge1 : edge.destination.edges) {
960+ updatable.add(edge1.destination);
961+ }
962+ }
963+
964+
965+ for (Vertex vertex : updatable) {
966+ updateVertexCollapseCost(vertex);
967+ }
968+
969+ }
970+ return true;
971+ }
972+
973+ private boolean assertValidMesh() {
974+ // Allows to find bugs in collapsing.
975+ for (Vertex vertex : collapseCostSet) {
976+ assertValidVertex(vertex);
977+ }
978+ return true;
979+
980+ }
981+
982+ private boolean assertValidVertex(Vertex v) {
983+ // Allows to find bugs in collapsing.
984+ // System.out.println("Asserting " + v.index);
985+ for (Triangle t : v.triangles) {
986+ for (int i = 0; i < 3; i++) {
987+ // System.out.println("check " + t.vertex[i].index);
988+
989+ //assert (collapseCostSet.contains(t.vertex[i]));
990+ assert (find(collapseCostSet, t.vertex[i]));
991+
992+ assert (t.vertex[i].edges.contains(new Edge(t.vertex[i].collapseTo)));
993+ for (int n = 0; n < 3; n++) {
994+ if (i != n) {
995+
996+ int id = t.vertex[i].edges.indexOf(new Edge(t.vertex[n]));
997+ Edge ed = t.vertex[i].edges.get(id);
998+ //assert (ed.collapseCost != UNINITIALIZED_COLLAPSE_COST);
999+ } else {
1000+ assert (!t.vertex[i].edges.contains(new Edge(t.vertex[n])));
1001+ }
1002+ }
1003+ }
1004+ }
1005+ return true;
1006+ }
1007+
1008+ private boolean find(List<Vertex> set, Vertex v) {
1009+ for (Vertex vertex : set) {
1010+ if (v == vertex) {
1011+ return true;
1012+ }
1013+ }
1014+ return false;
1015+ }
1016+}