/* * Project Info: http://jcae.sourceforge.net * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation; either version 2.1 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. * * (C) Copyright 2014, by EADS France */ package org.jcae.mesh.amibe.algos3d; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import org.jcae.mesh.amibe.ds.AbstractHalfEdge; import org.jcae.mesh.amibe.ds.Triangle; import org.jcae.mesh.amibe.ds.Vertex; import org.jcae.mesh.amibe.metrics.Matrix3D; import org.jcae.mesh.amibe.util.HashFactory; /** * Create a hole in a mesh from a loop of half edges. * The orientation of the triangles of half edges decide of the inside and * outside of the loop. * @author Jerome Robert */ public class HoleCutter { private boolean isClosedLoop(Collection<AbstractHalfEdge> edges) { Map<Vertex, Vertex> map = HashFactory.createMap(edges.size()); Vertex start = null; for(AbstractHalfEdge e: edges) { map.put(e.origin(), e.destination()); start = e.destination(); } int nbEdges = 0; Vertex current = start; while(true) { current = map.get(current); nbEdges ++; if(current == null) return false; if(current == start) return nbEdges == edges.size(); if(nbEdges > edges.size()) //we are in a sub loop return false; } } /** * Compute the vector normal to an half edge on its triangle * @param result the normalized result vector */ private void edgeNormal(AbstractHalfEdge edge, double[] result, double[] tmp1, double[] tmp2) { for(int i = 0; i < 3; i++) { tmp1[i] = edge.destination().get(i) - edge.origin().get(i); //OD tmp2[i] = edge.apex().get(i) - edge.origin().get(i); //OA } double alpha = Matrix3D.prodSca(tmp1, tmp2) / Matrix3D.prodSca(tmp1, tmp1); for(int i = 0; i < 3; i++) result[i] = tmp2[i] - alpha * tmp1[i]; double norm = Matrix3D.norm(result); for(int i = 0; i < 3; i++) result[i] /= norm; } private Set<AbstractHalfEdge> findLoopFan(Collection<AbstractHalfEdge> edges) { Set<AbstractHalfEdge> loop = HashFactory.createSet(edges.size()); double[] tmp1 = new double[3]; double[] tmp2 = new double[3]; double[] normal = new double[3]; double[] triDir = new double[3]; for(AbstractHalfEdge edge: edges) { AbstractHalfEdge cEdge = getCutter(edge); if(isNormalCut(edge)) { // compute the opposite of the triangle normal because in // normalCut mode, the normal target the out side of the loop Matrix3D.computeNormal3D(cEdge.origin(), cEdge.apex(), cEdge.destination(), tmp1, tmp2, normal); } else edgeNormal(cEdge, normal, tmp1, tmp2); Iterator<AbstractHalfEdge> it = cEdge.fanIterator(); AbstractHalfEdge bestEdge = null; double bestDot = Double.NEGATIVE_INFINITY; while(it.hasNext()) { AbstractHalfEdge otherEdge = it.next(); // select the fan which is inside the loop if(otherEdge != cEdge && isCutted(otherEdge)) { edgeNormal(otherEdge, triDir, tmp1, tmp2); double dot = Matrix3D.prodSca(triDir, normal); // keep the most parallel vector if(dot > bestDot) { bestDot = dot; bestEdge = otherEdge; } } } if(bestEdge != null) { loop.add(bestEdge); } } return loop; } public Collection<Triangle> cut(Collection<AbstractHalfEdge> edges) { return cut(edges, true); } /** * return the list of triangles in the hole */ public Collection<Triangle> cut(Collection<AbstractHalfEdge> edges, boolean searchLoopFan) { Set<Triangle> toReturn = HashFactory.createSet(); if(!isClosedLoop(edges)) return toReturn; Set<AbstractHalfEdge> loop; if(searchLoopFan) { loop = findLoopFan(edges); } else if(edges instanceof Set) { loop = (Set<AbstractHalfEdge>) edges; } else { loop = new HashSet<AbstractHalfEdge>(edges); } if(!loop.isEmpty()) cutHole(toReturn, loop); return toReturn; } private AbstractHalfEdge getCutter(AbstractHalfEdge edge) { Iterator<AbstractHalfEdge> it = edge.fanIterator(); AbstractHalfEdge cutterEdge = null; while(it.hasNext()) { AbstractHalfEdge e = it.next(); if(isCutter(e)) cutterEdge = e; } return cutterEdge; } /** Allow sub class to choose the fan which is the cutter */ protected boolean isCutter(AbstractHalfEdge edge) { return true; } /** Allow sub class to choose the fan which is cutted */ protected boolean isCutted(AbstractHalfEdge edge) { return true; } /** * return true if the cutting triangle is expected to be perpendicular * to the cutted surface, false if the cutting triangle is expected to be * parallel to the cutted surface. */ protected boolean isNormalCut(AbstractHalfEdge edge) { return true; } /** * tag the triangle which are inside the hole * @param triangle the list of tagged triangles * @param loop the border of the hole */ private static void cutHole(Collection<Triangle> triangle, Set<AbstractHalfEdge> loop) { AbstractHalfEdge start = loop.iterator().next(); ArrayList<AbstractHalfEdge> front = new ArrayList<AbstractHalfEdge>(); if(!loop.contains(start.next())) front.add(start.next()); if(!loop.contains(start.prev())) front.add(start.prev()); triangle.add(start.getTri()); while(!front.isEmpty()) { AbstractHalfEdge e = front.get(front.size() - 1).sym(); assert e != null: front.get(front.size() - 1); front.remove(front.size() - 1); Triangle t = e.getTri(); if(triangle.add(t)) { if(!loop.contains(e.next())) { assert !e.next().hasAttributes(AbstractHalfEdge.OUTER): e+"\n"+e.next(); front.add(e.next()); } if(!loop.contains(e.prev())) { assert !e.prev().hasAttributes(AbstractHalfEdge.OUTER): e+"\n"+e.next(); front.add(e.prev()); } } } } }