@Override public boolean canProcessEdge(HalfEdge current) { current = uniqueOrientation(current); final Vertex v1 = current.origin(); final Vertex v2 = current.destination(); assert v1 != v2 : current; final Quadric3DError q1 = quadricMap.get(v1); final Quadric3DError q2 = quadricMap.get(v2); assert q1 != null : v1; assert q2 != null : v2; q3.computeQuadric3DError(q1, q2); q3.optimalPlacement(v1, v2, q1, q2, placement, v3); if (!mesh.canCollapseEdge(current, v3)) return false; if (!metrics.isEmpty()) { EuclidianMetric3D m3 = metrics.get(v3, current.getTri()); if (!checkSize(v1, m3)) return false; if (!checkSize(v2, m3)) return false; } return true; }
@Override protected final double cost(final HalfEdge e) { final Vertex o = e.origin(); final Vertex d = e.destination(); HalfEdge current = uniqueOrientation(e); if (current.hasAttributes(AbstractHalfEdge.IMMUTABLE)) return Double.MAX_VALUE; if (freeEdgesOnly && !current.hasAttributes(AbstractHalfEdge.BOUNDARY)) return Double.MAX_VALUE; final Vertex v1 = current.origin(); final Vertex v2 = current.destination(); assert v1 != v2 : current; // If an endpoint is not writable, its neighborhood is // not fully determined and contraction must not be // performed. if (!v1.isWritable() || !v2.isWritable()) return Double.MAX_VALUE; if (!v1.isMutable() && !v2.isMutable()) return Double.MAX_VALUE; final Quadric3DError q1 = quadricMap.get(o); assert q1 != null : o; final Quadric3DError q2 = quadricMap.get(d); assert q2 != null : d; qCostOpt.computeQuadric3DError(q1, q2); qCostOpt.optimalPlacement(o, d, q1, q2, placement, vCostOpt); final double ret = q1.value(vCostOpt) + q2.value(vCostOpt); // TODO: check why this assertion sometimes fail // assert ret >= -1.e-2 : q1+"\n"+q2+"\n"+ret; return ret; }
@Override public void preProcessAllHalfEdges() { metrics.compute(); final int roughNrNodes = mesh.getTriangles().size() / 2; quadricMap = new HashMap<Vertex, Quadric3DError>(roughNrNodes); for (Triangle af : mesh.getTriangles()) { if (!af.isWritable()) continue; for (int i = 0; i < 3; i++) { final Vertex n = af.getV(i); if (!quadricMap.containsKey(n)) quadricMap.put(n, new Quadric3DError()); } } // Compute quadrics final double[] vect1 = new double[3]; final double[] vect2 = new double[3]; final double[] normal = new double[3]; for (Triangle f : mesh.getTriangles()) { if (!f.isWritable()) continue; f.getV1().sub(f.getV0(), vect1); f.getV2().sub(f.getV0(), vect2); Matrix3D.prodVect3D(vect1, vect2, normal); double norm = Matrix3D.norm(normal); // This is in fact 2*area, but that does not matter double area = norm; if (tolerance > 0.0) area /= tolerance; if (norm > 1.e-20) { norm = 1.0 / norm; for (int k = 0; k < 3; k++) normal[k] *= norm; } double d = -Matrix3D.prodSca(normal, f.getV0()); for (int i = 0; i < 3; i++) { final Quadric3DError q = quadricMap.get(f.getV(i)); q.addError(normal, d, area); } // Penalty for boundary triangles HalfEdge e = (HalfEdge) f.getAbstractHalfEdge(); for (int i = 0; i < 3; i++) { e = e.next(); if (e.hasAttributes(AbstractHalfEdge.BOUNDARY | AbstractHalfEdge.NONMANIFOLD)) { for (Iterator<AbstractHalfEdge> it = e.fanIterator(); it.hasNext(); ) { HalfEdge b = (HalfEdge) it.next(); // Add a virtual plane // In his dissertation, Garland suggests to // add a weight proportional to squared edge // length. // Here norm(vect2) == norm(vect1) b.destination().sub(b.origin(), vect1); Matrix3D.prodVect3D(vect1, normal, vect2); norm = Matrix3D.norm(vect2); if (norm > 1.e-20) { double invNorm = 1.0 / norm; for (int k = 0; k < 3; k++) vect2[k] *= invNorm; } d = -Matrix3D.prodSca(vect2, b.origin()); final Quadric3DError q1 = quadricMap.get(b.origin()); final Quadric3DError q2 = quadricMap.get(b.destination()); q1.addWeightedError(vect2, d, norm); q2.addWeightedError(vect2, d, norm); } } } } }