2
0

LodGenerator.java 38 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016
  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. import com.jme3.bounding.BoundingSphere;
  34. import com.jme3.math.Vector3f;
  35. import com.jme3.scene.Geometry;
  36. import com.jme3.scene.Mesh;
  37. import com.jme3.scene.VertexBuffer;
  38. import com.jme3.util.BufferUtils;
  39. import java.nio.Buffer;
  40. import java.nio.FloatBuffer;
  41. import java.nio.IntBuffer;
  42. import java.nio.ShortBuffer;
  43. import java.util.ArrayList;
  44. import java.util.Collections;
  45. import java.util.Comparator;
  46. import java.util.HashSet;
  47. import java.util.Iterator;
  48. import java.util.List;
  49. import java.util.Set;
  50. import java.util.SortedSet;
  51. import java.util.TreeSet;
  52. import java.util.logging.Level;
  53. import java.util.logging.Logger;
  54. /**
  55. * This is an utility class that allows to generated the lod levels for an
  56. * arbitrary mesh. It computes a collapse cost for each vertex and each edges.
  57. * The higher the cost the most likely collapsing the edge or the vertex will
  58. * produce artifacts on the mesh. <p>This class is the java implementation of
  59. * the enhanced version og Ogre engine Lod generator, by Péter Szücs, originally
  60. * based on Stan Melax "easy mesh simplification". The MIT licences C++ source
  61. * code can be found here
  62. * https://github.com/worldforge/ember/tree/master/src/components/ogre/lod more
  63. * informations can be found here http://www.melax.com/polychop
  64. * http://sajty.elementfx.com/progressivemesh/GSoC2012.pdf </p>
  65. *
  66. * <p>The algorithm sort the vertice according to their collapsse cost
  67. * ascending. It collapse from the "cheapest" vertex to the more expensive.<br>
  68. * <strong>Usage : </strong><br>
  69. * <pre>
  70. * LodGenerator lODGenerator = new LodGenerator(geometry);
  71. * lODGenerator.bakeLods(reductionMethod,reductionvalue);
  72. * </pre> redutionMethod type is VertexReductionMethod described here
  73. * {@link TriangleReductionMethod} reductionvalue depends on the
  74. * reductionMethod<p>
  75. *
  76. *
  77. * @author Nehon
  78. */
  79. public class LodGenerator {
  80. private static final Logger logger = Logger.getLogger(LodGenerator.class.getName());
  81. private static final float NEVER_COLLAPSE_COST = Float.MAX_VALUE;
  82. private static final float UNINITIALIZED_COLLAPSE_COST = Float.POSITIVE_INFINITY;
  83. private Vector3f tmpV1 = new Vector3f();
  84. private Vector3f tmpV2 = new Vector3f();
  85. private boolean bestQuality = true;
  86. private int indexCount = 0;
  87. private List<Vertex> collapseCostSet = new ArrayList<Vertex>();
  88. private float collapseCostLimit;
  89. private List<Triangle> triangleList;
  90. private List<Vertex> vertexList = new ArrayList<Vertex>();
  91. private float meshBoundingSphereRadius;
  92. private Mesh mesh;
  93. /**
  94. * Describe the way trinagles will be removed. <br> PROPORTIONAL :
  95. * Percentage of triangles to be removed from the mesh. Valid range is a
  96. * number between 0.0 and 1.0 <br> CONSTANT : Triangle count to be removed
  97. * from the mesh. Pass only integers or it will be rounded. <br>
  98. * COLLAPSE_COST : Reduces the vertices, until the cost is bigger then the
  99. * given value. Collapse cost is equal to the amount of artifact the
  100. * reduction causes. This generates the best Lod output, but the collapse
  101. * cost depends on implementation.
  102. */
  103. public enum TriangleReductionMethod {
  104. /**
  105. * Percentage of triangles to be removed from the mesh.
  106. *
  107. * Valid range is a number between 0.0 and 1.0
  108. */
  109. PROPORTIONAL,
  110. /**
  111. * Triangle count to be removed from the mesh.
  112. *
  113. * Pass only integers or it will be rounded.
  114. */
  115. CONSTANT,
  116. /**
  117. * Reduces the vertices, until the cost is bigger then the given value.
  118. *
  119. * Collapse cost is equal to the amount of artifact the reduction
  120. * causes. This generates the best Lod output, but the collapse cost
  121. * depends on implementation.
  122. */
  123. COLLAPSE_COST
  124. };
  125. private class Edge {
  126. Vertex destination;
  127. float collapseCost = UNINITIALIZED_COLLAPSE_COST;
  128. int refCount;
  129. public Edge(Vertex destination) {
  130. this.destination = destination;
  131. }
  132. public void set(Edge other) {
  133. destination = other.destination;
  134. collapseCost = other.collapseCost;
  135. refCount = other.refCount;
  136. }
  137. @Override
  138. public boolean equals(Object obj) {
  139. if (!(obj instanceof Edge)) {
  140. return false;
  141. }
  142. return destination == ((Edge) obj).destination;
  143. }
  144. @Override
  145. public int hashCode() {
  146. return destination.hashCode();
  147. }
  148. @Override
  149. public String toString() {
  150. return "Edge{" + "collapsTo " + destination.index + '}';
  151. }
  152. }
  153. private class Vertex {
  154. Vector3f position = new Vector3f();
  155. float collapseCost = UNINITIALIZED_COLLAPSE_COST;
  156. List<Edge> edges = new ArrayList<Edge>();
  157. Set<Triangle> triangles = new HashSet<Triangle>();
  158. Vertex collapseTo;
  159. boolean isSeam;
  160. int index;//index in the buffer for debugging
  161. @Override
  162. public String toString() {
  163. return index + " : " + position.toString();
  164. }
  165. }
  166. private class Triangle {
  167. Vertex[] vertex = new Vertex[3];
  168. Vector3f normal;
  169. boolean isRemoved;
  170. //indices of the vertices in the vertex buffer
  171. int[] vertexId = new int[3];
  172. void computeNormal() {
  173. // Cross-product 2 edges
  174. tmpV1.set(vertex[1].position).subtractLocal(vertex[0].position);
  175. tmpV2.set(vertex[2].position).subtractLocal(vertex[1].position);
  176. normal = tmpV1.cross(tmpV2);
  177. normal.normalizeLocal();
  178. }
  179. boolean hasVertex(Vertex v) {
  180. return (v == vertex[0] || v == vertex[1] || v == vertex[2]);
  181. }
  182. int getVertexIndex(Vertex v) {
  183. for (int i = 0; i < 3; i++) {
  184. if (vertex[i] == v) {
  185. return vertexId[i];
  186. }
  187. }
  188. throw new IllegalArgumentException("Vertex " + v + "is not part of triangle" + this);
  189. }
  190. boolean isMalformed() {
  191. return vertex[0] == vertex[1] || vertex[0] == vertex[2] || vertex[1] == vertex[2];
  192. }
  193. @Override
  194. public String toString() {
  195. String out = "Triangle{\n";
  196. for (int i = 0; i < 3; i++) {
  197. out += vertexId[i] + " : " + vertex[i].toString() + "\n";
  198. }
  199. out += '}';
  200. return out;
  201. }
  202. }
  203. /**
  204. * Compartator used to sort vertices according to their collapse cost
  205. */
  206. private Comparator collapseComparator = new Comparator<Vertex>() {
  207. public int compare(Vertex o1, Vertex o2) {
  208. if (Float.compare(o1.collapseCost, o2.collapseCost) == 0) {
  209. return 0;
  210. }
  211. if (o1.collapseCost < o2.collapseCost) {
  212. return -1;
  213. }
  214. return 1;
  215. }
  216. };
  217. /**
  218. * Construct a LodGenerator for the given geometry
  219. *
  220. * @param geom the geometry to consider to generate de Lods.
  221. */
  222. public LodGenerator(Geometry geom) {
  223. mesh = geom.getMesh();
  224. build();
  225. }
  226. private void build() {
  227. BoundingSphere bs = new BoundingSphere();
  228. bs.computeFromPoints(mesh.getFloatBuffer(VertexBuffer.Type.Position));
  229. meshBoundingSphereRadius = bs.getRadius();
  230. List<Vertex> vertexLookup = new ArrayList<Vertex>();
  231. initialize();
  232. gatherVertexData(mesh, vertexLookup);
  233. gatherIndexData(mesh, vertexLookup);
  234. computeCosts();
  235. assert (assertValidMesh());
  236. }
  237. private void gatherVertexData(Mesh mesh, List<Vertex> vertexLookup) {
  238. //in case the model is currently animating with software animation
  239. //attempting to retrieve the bind position instead of the position.
  240. VertexBuffer position = mesh.getBuffer(VertexBuffer.Type.BindPosePosition);
  241. if (position == null) {
  242. position = mesh.getBuffer(VertexBuffer.Type.Position);
  243. }
  244. FloatBuffer pos = (FloatBuffer) position.getDataReadOnly();
  245. pos.rewind();
  246. while (pos.remaining() != 0) {
  247. Vertex v = new Vertex();
  248. v.position.setX(pos.get());
  249. v.position.setY(pos.get());
  250. v.position.setZ(pos.get());
  251. v.isSeam = false;
  252. Vertex existingV = findSimilar(v);
  253. if (existingV != null) {
  254. //vertex position already exists
  255. existingV.isSeam = true;
  256. v.isSeam = true;
  257. } else {
  258. vertexList.add(v);
  259. }
  260. vertexLookup.add(v);
  261. }
  262. pos.rewind();
  263. }
  264. private Vertex findSimilar(Vertex v) {
  265. for (Vertex vertex : vertexList) {
  266. if (vertex.position.equals(v.position)) {
  267. return vertex;
  268. }
  269. }
  270. return null;
  271. }
  272. private void gatherIndexData(Mesh mesh, List<Vertex> vertexLookup) {
  273. VertexBuffer indexBuffer = mesh.getBuffer(VertexBuffer.Type.Index);
  274. indexCount = indexBuffer.getNumElements() * 3;
  275. Buffer b = indexBuffer.getDataReadOnly();
  276. b.rewind();
  277. while (b.remaining() != 0) {
  278. Triangle tri = new Triangle();
  279. tri.isRemoved = false;
  280. triangleList.add(tri);
  281. for (int i = 0; i < 3; i++) {
  282. if (b instanceof IntBuffer) {
  283. tri.vertexId[i] = ((IntBuffer) b).get();
  284. } else {
  285. tri.vertexId[i] = ((ShortBuffer) b).get();
  286. }
  287. assert (tri.vertexId[i] < vertexLookup.size());
  288. tri.vertex[i] = vertexLookup.get(tri.vertexId[i]);
  289. //debug only;
  290. tri.vertex[i].index = tri.vertexId[i];
  291. }
  292. if (tri.isMalformed()) {
  293. if (!tri.isRemoved) {
  294. 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()});
  295. tri.isRemoved = true;
  296. indexCount -= 3;
  297. }
  298. } else {
  299. tri.computeNormal();
  300. addTriangleToEdges(tri);
  301. }
  302. }
  303. b.rewind();
  304. }
  305. private void computeCosts() {
  306. collapseCostSet.clear();
  307. for (Vertex vertex : vertexList) {
  308. if (!vertex.edges.isEmpty()) {
  309. computeVertexCollapseCost(vertex);
  310. } else {
  311. logger.log(Level.FINE, "Found isolated vertex {0} It will be excluded from Lod level calculations.", vertex);
  312. }
  313. }
  314. assert (vertexList.size() == collapseCostSet.size());
  315. assert (checkCosts());
  316. }
  317. //Debug only
  318. private boolean checkCosts() {
  319. for (Vertex vertex : vertexList) {
  320. boolean test = find(collapseCostSet, vertex);
  321. if (!test) {
  322. System.out.println("vertex " + vertex.index + " not present in collapse costs");
  323. return false;
  324. }
  325. }
  326. return true;
  327. }
  328. private void computeVertexCollapseCost(Vertex vertex) {
  329. vertex.collapseCost = UNINITIALIZED_COLLAPSE_COST;
  330. assert (!vertex.edges.isEmpty());
  331. for (Edge edge : vertex.edges) {
  332. edge.collapseCost = computeEdgeCollapseCost(vertex, edge);
  333. assert (edge.collapseCost != UNINITIALIZED_COLLAPSE_COST);
  334. if (vertex.collapseCost > edge.collapseCost) {
  335. vertex.collapseCost = edge.collapseCost;
  336. vertex.collapseTo = edge.destination;
  337. }
  338. }
  339. assert (vertex.collapseCost != UNINITIALIZED_COLLAPSE_COST);
  340. collapseCostSet.add(vertex);
  341. }
  342. float computeEdgeCollapseCost(Vertex src, Edge dstEdge) {
  343. // This is based on Ogre's collapse cost calculation algorithm.
  344. Vertex dest = dstEdge.destination;
  345. // Check for singular triangle destruction
  346. // If src and dest both only have 1 triangle (and it must be a shared one)
  347. // then this would destroy the shape, so don't do this
  348. if (src.triangles.size() == 1 && dest.triangles.size() == 1) {
  349. return NEVER_COLLAPSE_COST;
  350. }
  351. // Degenerate case check
  352. // Are we going to invert a face normal of one of the neighbouring faces?
  353. // Can occur when we have a very small remaining edge and collapse crosses it
  354. // Look for a face normal changing by > 90 degrees
  355. for (Triangle triangle : src.triangles) {
  356. // Ignore the deleted faces (those including src & dest)
  357. if (!triangle.hasVertex(dest)) {
  358. // Test the new face normal
  359. Vertex pv0, pv1, pv2;
  360. // Replace src with dest wherever it is
  361. pv0 = (triangle.vertex[0] == src) ? dest : triangle.vertex[0];
  362. pv1 = (triangle.vertex[1] == src) ? dest : triangle.vertex[1];
  363. pv2 = (triangle.vertex[2] == src) ? dest : triangle.vertex[2];
  364. // Cross-product 2 edges
  365. tmpV1.set(pv1.position).subtractLocal(pv0.position);
  366. tmpV2.set(pv2.position).subtractLocal(pv1.position);
  367. //computing the normal
  368. Vector3f newNormal = tmpV1.crossLocal(tmpV2);
  369. newNormal.normalizeLocal();
  370. // Dot old and new face normal
  371. // If < 0 then more than 90 degree difference
  372. if (newNormal.dot(triangle.normal) < 0.0f) {
  373. // Don't do it!
  374. return NEVER_COLLAPSE_COST;
  375. }
  376. }
  377. }
  378. float cost;
  379. // Special cases
  380. // If we're looking at a border vertex
  381. if (isBorderVertex(src)) {
  382. if (dstEdge.refCount > 1) {
  383. // src is on a border, but the src-dest edge has more than one tri on it
  384. // So it must be collapsing inwards
  385. // Mark as very high-value cost
  386. // curvature = 1.0f;
  387. cost = 1.0f;
  388. } else {
  389. // Collapsing ALONG a border
  390. // We can't use curvature to measure the effect on the model
  391. // Instead, see what effect it has on 'pulling' the other border edges
  392. // The more colinear, the less effect it will have
  393. // So measure the 'kinkiness' (for want of a better term)
  394. // Find the only triangle using this edge.
  395. // PMTriangle* triangle = findSideTriangle(src, dst);
  396. cost = 0.0f;
  397. Vector3f collapseEdge = tmpV1.set(src.position).subtractLocal(dest.position);
  398. collapseEdge.normalizeLocal();
  399. for (Edge edge : src.edges) {
  400. Vertex neighbor = edge.destination;
  401. //reference check intended
  402. if (neighbor != dest && edge.refCount == 1) {
  403. Vector3f otherBorderEdge = tmpV2.set(src.position).subtractLocal(neighbor.position);
  404. otherBorderEdge.normalizeLocal();
  405. // This time, the nearer the dot is to -1, the better, because that means
  406. // the edges are opposite each other, therefore less kinkiness
  407. // Scale into [0..1]
  408. float kinkiness = (otherBorderEdge.dot(collapseEdge) + 1.002f) * 0.5f;
  409. cost = Math.max(cost, kinkiness);
  410. }
  411. }
  412. }
  413. } else { // not a border
  414. // Standard inner vertex
  415. // Calculate curvature
  416. // use the triangle facing most away from the sides
  417. // to determine our curvature term
  418. // Iterate over src's faces again
  419. cost = 0.001f;
  420. for (Triangle triangle : src.triangles) {
  421. float mincurv = 1.0f; // curve for face i and closer side to it
  422. for (Triangle triangle2 : src.triangles) {
  423. if (triangle2.hasVertex(dest)) {
  424. // Dot product of face normal gives a good delta angle
  425. float dotprod = triangle.normal.dot(triangle2.normal);
  426. // NB we do (1-..) to invert curvature where 1 is high curvature [0..1]
  427. // Whilst dot product is high when angle difference is low
  428. mincurv = Math.min(mincurv, (1.002f - dotprod) * 0.5f);
  429. }
  430. }
  431. cost = Math.max(cost, mincurv);
  432. }
  433. }
  434. // check for texture seam ripping
  435. if (src.isSeam) {
  436. if (!dest.isSeam) {
  437. cost += meshBoundingSphereRadius;
  438. } else {
  439. cost += meshBoundingSphereRadius * 0.5;
  440. }
  441. }
  442. assert (cost >= 0);
  443. // TODO: use squared distance.
  444. return cost * src.position.distance(dest.position);
  445. }
  446. int nbCollapsedTri = 0;
  447. /**
  448. * Computes the lod and return a list of VertexBuffers that can then be used
  449. * for lod (use Mesg.setLodLevels(VertexBuffer[]))<br>
  450. *
  451. * This method must be fed with the reduction method
  452. * {@link TriangleReductionMethod} and a list of reduction values.<br> for
  453. * each value a lod will be generated. <br> The resulting array will always
  454. * contain at index 0 the original index buffer of the mesh. <p>
  455. * <strong>Important note :</strong> some meshes cannot be decimated, so the
  456. * result of this method can varry depending of the given mesh. Also the
  457. * reduction values are indicative and the produces mesh will not always
  458. * meet the required reduction.
  459. *
  460. * @param reductionMethod the reduction method to use
  461. * @param reductionValues the reduction value to use for each lod level.
  462. * @return an array of VertexBuffers containing the different index buffers
  463. * representing the lod levels.
  464. */
  465. public VertexBuffer[] computeLods(TriangleReductionMethod reductionMethod, float... reductionValues) {
  466. int tricount = triangleList.size();
  467. int lastBakeVertexCount = tricount;
  468. int lodCount = reductionValues.length;
  469. VertexBuffer[] lods = new VertexBuffer[lodCount + 1];
  470. int numBakedLods = 1;
  471. lods[0] = mesh.getBuffer(VertexBuffer.Type.Index);
  472. for (int curLod = 0; curLod < lodCount; curLod++) {
  473. int neededTriCount = calcLodTriCount(reductionMethod, reductionValues[curLod]);
  474. while (neededTriCount < tricount) {
  475. Collections.sort(collapseCostSet, collapseComparator);
  476. Iterator<Vertex> it = collapseCostSet.iterator();
  477. if (it.hasNext()) {
  478. Vertex v = it.next();
  479. if (v.collapseCost < collapseCostLimit) {
  480. if (!collapse(v)) {
  481. logger.log(Level.FINE, "Couldn''t collapse vertex{0}", v.index);
  482. }
  483. Iterator<Vertex> it2 = collapseCostSet.iterator();
  484. if (it2.hasNext()) {
  485. it2.next();
  486. it2.remove();// Remove src from collapse costs.
  487. }
  488. } else {
  489. break;
  490. }
  491. } else {
  492. break;
  493. }
  494. tricount = triangleList.size() - nbCollapsedTri;
  495. }
  496. logger.log(Level.FINE, "collapsed {0} tris", nbCollapsedTri);
  497. boolean outSkipped = (lastBakeVertexCount == tricount);
  498. if (!outSkipped) {
  499. lastBakeVertexCount = tricount;
  500. lods[curLod + 1] = makeLod(mesh);
  501. numBakedLods++;
  502. }
  503. }
  504. if (numBakedLods <= lodCount) {
  505. VertexBuffer[] bakedLods = new VertexBuffer[numBakedLods];
  506. System.arraycopy(lods, 0, bakedLods, 0, numBakedLods);
  507. return bakedLods;
  508. } else {
  509. return lods;
  510. }
  511. }
  512. /**
  513. * Computes the lods and bake them into the mesh<br>
  514. *
  515. * This method must be fed with the reduction method
  516. * {@link TriangleReductionMethod} and a list of reduction values.<br> for
  517. * each value a lod will be generated. <p> <strong>Important note :</strong>
  518. * some meshes cannot be decimated, so the result of this method can varry
  519. * depending of the given mesh. Also the reduction values are indicative and
  520. * the produces mesh will not always meet the required reduction.
  521. *
  522. * @param reductionMethod the reduction method to use
  523. * @param reductionValues the reduction value to use for each lod level.
  524. */
  525. public void bakeLods(TriangleReductionMethod reductionMethod, float... reductionValues) {
  526. mesh.setLodLevels(computeLods(reductionMethod, reductionValues));
  527. }
  528. private VertexBuffer makeLod(Mesh mesh) {
  529. VertexBuffer indexBuffer = mesh.getBuffer(VertexBuffer.Type.Index);
  530. boolean isShortBuffer = indexBuffer.getFormat() == VertexBuffer.Format.UnsignedShort;
  531. // Create buffers.
  532. VertexBuffer lodBuffer = new VertexBuffer(VertexBuffer.Type.Index);
  533. int bufsize = indexCount == 0 ? 3 : indexCount;
  534. if (isShortBuffer) {
  535. lodBuffer.setupData(VertexBuffer.Usage.Static, 3, VertexBuffer.Format.UnsignedShort, BufferUtils.createShortBuffer(bufsize));
  536. } else {
  537. lodBuffer.setupData(VertexBuffer.Usage.Static, 3, VertexBuffer.Format.UnsignedInt, BufferUtils.createIntBuffer(bufsize));
  538. }
  539. lodBuffer.getData().rewind();
  540. //Check if we should fill it with a "dummy" triangle.
  541. if (indexCount == 0) {
  542. if (isShortBuffer) {
  543. for (int m = 0; m < 3; m++) {
  544. ((ShortBuffer) lodBuffer.getData()).put((short) 0);
  545. }
  546. } else {
  547. for (int m = 0; m < 3; m++) {
  548. ((IntBuffer) lodBuffer.getData()).put(0);
  549. }
  550. }
  551. }
  552. // Fill buffers.
  553. Buffer buf = lodBuffer.getData();
  554. buf.rewind();
  555. for (Triangle triangle : triangleList) {
  556. if (!triangle.isRemoved) {
  557. assert (indexCount != 0);
  558. if (isShortBuffer) {
  559. for (int m = 0; m < 3; m++) {
  560. ((ShortBuffer) buf).put((short) triangle.vertexId[m]);
  561. }
  562. } else {
  563. for (int m = 0; m < 3; m++) {
  564. ((IntBuffer) buf).put(triangle.vertexId[m]);
  565. }
  566. }
  567. }
  568. }
  569. buf.clear();
  570. lodBuffer.updateData(buf);
  571. return lodBuffer;
  572. }
  573. private int calcLodTriCount(TriangleReductionMethod reductionMethod, float reductionValue) {
  574. int nbTris = mesh.getTriangleCount();
  575. switch (reductionMethod) {
  576. case PROPORTIONAL:
  577. collapseCostLimit = NEVER_COLLAPSE_COST;
  578. return (int) (nbTris - (nbTris * (reductionValue)));
  579. case CONSTANT:
  580. collapseCostLimit = NEVER_COLLAPSE_COST;
  581. if (reductionValue < nbTris) {
  582. return nbTris - (int) reductionValue;
  583. }
  584. return 0;
  585. case COLLAPSE_COST:
  586. collapseCostLimit = reductionValue;
  587. return 0;
  588. default:
  589. return nbTris;
  590. }
  591. }
  592. private int findDstID(int srcId, List<CollapsedEdge> tmpCollapsedEdges) {
  593. int i = 0;
  594. for (CollapsedEdge collapsedEdge : tmpCollapsedEdges) {
  595. if (collapsedEdge.srcID == srcId) {
  596. return i;
  597. }
  598. i++;
  599. }
  600. return Integer.MAX_VALUE;
  601. }
  602. private class CollapsedEdge {
  603. int srcID;
  604. int dstID;
  605. };
  606. private void removeTriangleFromEdges(Triangle triangle, Vertex skip) {
  607. // skip is needed if we are iterating on the vertex's edges or triangles.
  608. for (int i = 0; i < 3; i++) {
  609. if (triangle.vertex[i] != skip) {
  610. triangle.vertex[i].triangles.remove(triangle);
  611. }
  612. }
  613. for (int i = 0; i < 3; i++) {
  614. for (int n = 0; n < 3; n++) {
  615. if (i != n) {
  616. removeEdge(triangle.vertex[i], new Edge(triangle.vertex[n]));
  617. }
  618. }
  619. }
  620. }
  621. private void removeEdge(Vertex v, Edge edge) {
  622. Edge ed = null;
  623. for (Edge edge1 : v.edges) {
  624. if (edge1.equals(edge)) {
  625. ed = edge1;
  626. break;
  627. }
  628. }
  629. if (ed.refCount == 1) {
  630. v.edges.remove(ed);
  631. } else {
  632. ed.refCount--;
  633. }
  634. }
  635. boolean isBorderVertex(Vertex vertex) {
  636. for (Edge edge : vertex.edges) {
  637. if (edge.refCount == 1) {
  638. return true;
  639. }
  640. }
  641. return false;
  642. }
  643. private void addTriangleToEdges(Triangle tri) {
  644. if (bestQuality) {
  645. Triangle duplicate = getDuplicate(tri);
  646. if (duplicate != null) {
  647. if (!tri.isRemoved) {
  648. tri.isRemoved = true;
  649. indexCount -= 3;
  650. logger.log(Level.FINE, "duplicate triangle found{0}{1} It will be excluded from Lod level calculations.", new Object[]{tri, duplicate});
  651. }
  652. }
  653. }
  654. for (int i = 0; i < 3; i++) {
  655. tri.vertex[i].triangles.add(tri);
  656. }
  657. for (int i = 0; i < 3; i++) {
  658. for (int n = 0; n < 3; n++) {
  659. if (i != n) {
  660. addEdge(tri.vertex[i], new Edge(tri.vertex[n]));
  661. }
  662. }
  663. }
  664. }
  665. private void addEdge(Vertex v, Edge edge) {
  666. assert (edge.destination != v);
  667. for (Edge ed : v.edges) {
  668. if (ed.equals(edge)) {
  669. ed.refCount++;
  670. return;
  671. }
  672. }
  673. v.edges.add(edge);
  674. edge.refCount = 1;
  675. }
  676. private void initialize() {
  677. triangleList = new ArrayList<LodGenerator.Triangle>();
  678. }
  679. private Triangle getDuplicate(Triangle triangle) {
  680. // duplicate triangle detection (where all vertices has the same position)
  681. for (Triangle tri : triangle.vertex[0].triangles) {
  682. if (isDuplicateTriangle(triangle, tri)) {
  683. return tri;
  684. }
  685. }
  686. return null;
  687. }
  688. private boolean isDuplicateTriangle(Triangle triangle, Triangle triangle2) {
  689. for (int i = 0; i < 3; i++) {
  690. if (triangle.vertex[i] != triangle2.vertex[0]
  691. || triangle.vertex[i] != triangle2.vertex[1]
  692. || triangle.vertex[i] != triangle2.vertex[2]) {
  693. return false;
  694. }
  695. }
  696. return true;
  697. }
  698. private void replaceVertexID(Triangle triangle, int oldID, int newID, Vertex dst) {
  699. dst.triangles.add(triangle);
  700. // NOTE: triangle is not removed from src. This is implementation specific optimization.
  701. // Its up to the compiler to unroll everything.
  702. for (int i = 0; i < 3; i++) {
  703. if (triangle.vertexId[i] == oldID) {
  704. for (int n = 0; n < 3; n++) {
  705. if (i != n) {
  706. // This is implementation specific optimization to remove following line.
  707. //removeEdge(triangle.vertex[i], new Edge(triangle.vertex[n]));
  708. removeEdge(triangle.vertex[n], new Edge(triangle.vertex[i]));
  709. addEdge(triangle.vertex[n], new Edge(dst));
  710. addEdge(dst, new Edge(triangle.vertex[n]));
  711. }
  712. }
  713. triangle.vertex[i] = dst;
  714. triangle.vertexId[i] = newID;
  715. return;
  716. }
  717. }
  718. assert (false);
  719. }
  720. private void updateVertexCollapseCost(Vertex vertex) {
  721. float collapseCost = UNINITIALIZED_COLLAPSE_COST;
  722. Vertex collapseTo = null;
  723. for (Edge edge : vertex.edges) {
  724. edge.collapseCost = computeEdgeCollapseCost(vertex, edge);
  725. assert (edge.collapseCost != UNINITIALIZED_COLLAPSE_COST);
  726. if (collapseCost > edge.collapseCost) {
  727. collapseCost = edge.collapseCost;
  728. collapseTo = edge.destination;
  729. }
  730. }
  731. if (collapseCost != vertex.collapseCost || vertex.collapseTo != collapseTo) {
  732. assert (vertex.collapseTo != null);
  733. assert (find(collapseCostSet, vertex));
  734. collapseCostSet.remove(vertex);
  735. if (collapseCost != UNINITIALIZED_COLLAPSE_COST) {
  736. vertex.collapseCost = collapseCost;
  737. vertex.collapseTo = collapseTo;
  738. collapseCostSet.add(vertex);
  739. }
  740. }
  741. assert (vertex.collapseCost != UNINITIALIZED_COLLAPSE_COST);
  742. }
  743. private boolean hasSrcID(int srcID, List<CollapsedEdge> cEdges) {
  744. // This will only return exact matches.
  745. for (CollapsedEdge collapsedEdge : cEdges) {
  746. if (collapsedEdge.srcID == srcID) {
  747. return true;
  748. }
  749. }
  750. return false; // Not found
  751. }
  752. private boolean collapse(Vertex src) {
  753. Vertex dest = src.collapseTo;
  754. if (src.edges.isEmpty()) {
  755. return false;
  756. }
  757. assert (assertValidVertex(dest));
  758. assert (assertValidVertex(src));
  759. assert (src.collapseCost != NEVER_COLLAPSE_COST);
  760. assert (src.collapseCost != UNINITIALIZED_COLLAPSE_COST);
  761. assert (!src.edges.isEmpty());
  762. assert (!src.triangles.isEmpty());
  763. assert (src.edges.contains(new Edge(dest)));
  764. // It may have vertexIDs and triangles from different submeshes(different vertex buffers),
  765. // so we need to connect them correctly based on deleted triangle's edge.
  766. // mCollapsedEdgeIDs will be used, when looking up the connections for replacement.
  767. List<CollapsedEdge> tmpCollapsedEdges = new ArrayList<CollapsedEdge>();
  768. for (Iterator<Triangle> it = src.triangles.iterator(); it.hasNext();) {
  769. Triangle triangle = it.next();
  770. if (triangle.hasVertex(dest)) {
  771. // Remove a triangle
  772. // Tasks:
  773. // 1. Add it to the collapsed edges list.
  774. // 2. Reduce index count for the Lods, which will not have this triangle.
  775. // 3. Mark as removed, so it will not be added in upcoming Lod levels.
  776. // 4. Remove references/pointers to this triangle.
  777. // 1. task
  778. int srcID = triangle.getVertexIndex(src);
  779. if (!hasSrcID(srcID, tmpCollapsedEdges)) {
  780. CollapsedEdge cEdge = new CollapsedEdge();
  781. cEdge.srcID = srcID;
  782. cEdge.dstID = triangle.getVertexIndex(dest);
  783. tmpCollapsedEdges.add(cEdge);
  784. }
  785. // 2. task
  786. indexCount -= 3;
  787. // 3. task
  788. triangle.isRemoved = true;
  789. nbCollapsedTri++;
  790. // 4. task
  791. removeTriangleFromEdges(triangle, src);
  792. it.remove();
  793. }
  794. }
  795. assert (!tmpCollapsedEdges.isEmpty());
  796. assert (!dest.edges.contains(new Edge(src)));
  797. for (Iterator<Triangle> it = src.triangles.iterator(); it.hasNext();) {
  798. Triangle triangle = it.next();
  799. if (!triangle.hasVertex(dest)) {
  800. // Replace a triangle
  801. // Tasks:
  802. // 1. Determine the edge which we will move along. (we need to modify single vertex only)
  803. // 2. Move along the selected edge.
  804. // 1. task
  805. int srcID = triangle.getVertexIndex(src);
  806. int id = findDstID(srcID, tmpCollapsedEdges);
  807. if (id == Integer.MAX_VALUE) {
  808. // Not found any edge to move along.
  809. // Destroy the triangle.
  810. // if (!triangle.isRemoved) {
  811. triangle.isRemoved = true;
  812. indexCount -= 3;
  813. removeTriangleFromEdges(triangle, src);
  814. it.remove();
  815. nbCollapsedTri++;
  816. continue;
  817. }
  818. int dstID = tmpCollapsedEdges.get(id).dstID;
  819. // 2. task
  820. replaceVertexID(triangle, srcID, dstID, dest);
  821. if (bestQuality) {
  822. triangle.computeNormal();
  823. }
  824. }
  825. }
  826. if (bestQuality) {
  827. for (Edge edge : src.edges) {
  828. updateVertexCollapseCost(edge.destination);
  829. }
  830. updateVertexCollapseCost(dest);
  831. for (Edge edge : dest.edges) {
  832. updateVertexCollapseCost(edge.destination);
  833. }
  834. } else {
  835. // TODO: Find out why is this needed. assertOutdatedCollapseCost() fails on some
  836. // rare situations without this. For example goblin.mesh fails.
  837. //Treeset to have an ordered list with unique values
  838. SortedSet<Vertex> updatable = new TreeSet<Vertex>(collapseComparator);
  839. for (Edge edge : src.edges) {
  840. updatable.add(edge.destination);
  841. for (Edge edge1 : edge.destination.edges) {
  842. updatable.add(edge1.destination);
  843. }
  844. }
  845. for (Vertex vertex : updatable) {
  846. updateVertexCollapseCost(vertex);
  847. }
  848. }
  849. return true;
  850. }
  851. private boolean assertValidMesh() {
  852. // Allows to find bugs in collapsing.
  853. for (Vertex vertex : collapseCostSet) {
  854. assertValidVertex(vertex);
  855. }
  856. return true;
  857. }
  858. private boolean assertValidVertex(Vertex v) {
  859. // Allows to find bugs in collapsing.
  860. // System.out.println("Asserting " + v.index);
  861. for (Triangle t : v.triangles) {
  862. for (int i = 0; i < 3; i++) {
  863. // System.out.println("check " + t.vertex[i].index);
  864. //assert (collapseCostSet.contains(t.vertex[i]));
  865. assert (find(collapseCostSet, t.vertex[i]));
  866. assert (t.vertex[i].edges.contains(new Edge(t.vertex[i].collapseTo)));
  867. for (int n = 0; n < 3; n++) {
  868. if (i != n) {
  869. int id = t.vertex[i].edges.indexOf(new Edge(t.vertex[n]));
  870. Edge ed = t.vertex[i].edges.get(id);
  871. //assert (ed.collapseCost != UNINITIALIZED_COLLAPSE_COST);
  872. } else {
  873. assert (!t.vertex[i].edges.contains(new Edge(t.vertex[n])));
  874. }
  875. }
  876. }
  877. }
  878. return true;
  879. }
  880. private boolean find(List<Vertex> set, Vertex v) {
  881. for (Vertex vertex : set) {
  882. if (v == vertex) {
  883. return true;
  884. }
  885. }
  886. return false;
  887. }
  888. }