/* * 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 2012, by EADS France */ package org.jcae.mesh.amibe.algos3d; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.TreeSet; import java.util.logging.Level; import java.util.logging.Logger; import org.jcae.mesh.amibe.ds.AbstractHalfEdge; import org.jcae.mesh.amibe.ds.Mesh; import org.jcae.mesh.amibe.ds.Triangle; import org.jcae.mesh.amibe.ds.Vertex; import org.jcae.mesh.amibe.traits.MeshTraitsBuilder; import org.jcae.mesh.amibe.util.HashFactory; import org.jcae.mesh.xmldata.Amibe2VTK; import org.jcae.mesh.xmldata.MeshReader; import org.jcae.mesh.xmldata.MeshWriter; /** * Compute polylines from non-manifold, boundary, and group boundary edges * @author Jerome Robert */ public class Skeleton { private final Collection<List<AbstractHalfEdge>> polylines; private final Set<Integer> groups1 = HashFactory.createSet(); private final Set<Integer> groups2 = HashFactory.createSet(); /** * @param mesh * @param angle min polyline angle */ public Skeleton(Mesh mesh, double angle) { polylines = computePolylines(getNonManifoldHE(mesh), angle); } /** * Return half edges witch are border of the give groups * @param groupIds * @return */ public Collection<AbstractHalfEdge> getByGroups(int ... groupIds) { ArrayList<AbstractHalfEdge> a = new ArrayList<AbstractHalfEdge>(); for(List<AbstractHalfEdge> l:getPolylines(groupIds)) a.addAll(l); return a; } public Collection<AbstractHalfEdge> getByGroups(int groupIds) { ArrayList<AbstractHalfEdge> a = new ArrayList<AbstractHalfEdge>(); for(List<AbstractHalfEdge> l:getPolylines(groupIds)) a.addAll(l); return a; } public Collection<AbstractHalfEdge> getByGroupsAtLeast(int ... groupIds) { ArrayList<AbstractHalfEdge> a = new ArrayList<AbstractHalfEdge>(); for(List<AbstractHalfEdge> l:getPolylinesAtLeast(groupIds)) a.addAll(l); return a; } public Collection<List<AbstractHalfEdge>> getPolylines(int groupIds) { ArrayList<List<AbstractHalfEdge>> toReturn = new ArrayList<List<AbstractHalfEdge>>(); main: for(List<AbstractHalfEdge> l: polylines) { AbstractHalfEdge e = l.get(0); if(e.getTri().getGroupId() == groupIds) toReturn.add(l); } return toReturn; } /** * Return polylines which are border of, at least, the given groups without * taking the order into account * @param groupIds * @return */ public Collection<List<AbstractHalfEdge>> getPolylinesAtLeast(int ... groupIds) { ArrayList<List<AbstractHalfEdge>> toReturn = new ArrayList<List<AbstractHalfEdge>>(); ArrayList<Integer> query = new ArrayList<Integer>(); for(int i: groupIds) query.add(i); TreeSet<Integer> current = new TreeSet<Integer>(); for(List<AbstractHalfEdge> l: polylines) { AbstractHalfEdge e = l.get(0); if(e.getTri().getGroupId() == groupIds[0]) { if(e.hasAttributes(AbstractHalfEdge.NONMANIFOLD)) { Iterator<AbstractHalfEdge> it = e.fanIterator(); while(it.hasNext()) { AbstractHalfEdge ne = it.next(); current.add(ne.getTri().getGroupId()); } } else if(e.hasAttributes(AbstractHalfEdge.BOUNDARY) && groupIds.length == 1) { current.add(e.getTri().getGroupId()); } else { current.add(e.getTri().getGroupId()); current.add(e.sym().getTri().getGroupId()); } } if(current.containsAll(query)) toReturn.add(l); current.clear(); } return toReturn; } /** * Return polylines which are border of the given groups * @param groupIds * @return */ public Collection<List<AbstractHalfEdge>> getPolylines(int ... groupIds) { ArrayList<List<AbstractHalfEdge>> toReturn = new ArrayList<List<AbstractHalfEdge>>(); main: for(List<AbstractHalfEdge> l: polylines) { AbstractHalfEdge e = l.get(0); if(e.getTri().getGroupId() == groupIds[0]) { if(e.hasAttributes(AbstractHalfEdge.NONMANIFOLD)) { int k = 0; Iterator<AbstractHalfEdge> it = e.fanIterator(); while(it.hasNext()) { AbstractHalfEdge ne = it.next(); if(k >= groupIds.length || ne.getTri().getGroupId() != groupIds[k++]) continue main; } toReturn.add(l); } else if(e.hasAttributes(AbstractHalfEdge.BOUNDARY) && groupIds.length == 1) { toReturn.add(l); } else if(groupIds.length == 2 && e.sym().getTri().getGroupId() == groupIds[1]) { toReturn.add(l); } } } return toReturn; } /** Wrap List<Vertex> to ensure polyline unicity */ private class VertexPolyline { public final List<Vertex> vertices; public VertexPolyline(List<AbstractHalfEdge> edges) { vertices = new ArrayList<Vertex>(edges.size() + 1); for(AbstractHalfEdge e:edges) vertices.add(e.origin()); vertices.add(edges.get(edges.size()-1).destination()); } @Override public boolean equals(Object obj) { List<Vertex> o = ((VertexPolyline) obj).vertices; int s = vertices.size() - 1; int os = o.size() - 1; return s == os && ((vertices.get(0) == o.get(0) && vertices.get(1) == o.get(1) && vertices.get(s) == o.get(s) && vertices.get(s - 1) == o.get(s - 1)) || (vertices.get(0) == o.get(s) && vertices.get(1) == o.get(s-1) && vertices.get(s) == o.get(0) && vertices.get(1) == o.get(s - 1))); } @Override public int hashCode() { return vertices.get(0).hashCode() + vertices.get(vertices.size()-1).hashCode(); } } /** * Return all polylines as vertices. * */ public Collection<List<Vertex>> getPolylinesVertices() { Set<VertexPolyline> hs = HashFactory.createSet(polylines.size()); for(List<AbstractHalfEdge> l:polylines) hs.add(new VertexPolyline(l)); ArrayList<List<Vertex>> toReturn = new ArrayList<List<Vertex>>(hs.size()); for(VertexPolyline l:hs) toReturn.add(l.vertices); return toReturn; } /** * Return all polylines as half edges. * One polyline is returned by fan (ex 3 polyline for a T junction). */ public Collection<List<AbstractHalfEdge>> getPolylines() { return Collections.unmodifiableCollection(polylines); } private Collection<AbstractHalfEdge> getNonManifoldHE(Mesh mesh) { Set<AbstractHalfEdge> toReturn = HashFactory.createSet(); for(Triangle t:mesh.getTriangles()) { AbstractHalfEdge he = t.getAbstractHalfEdge(); assert he != null; for(int i = 0; i < 3; i++) { if(isNonManifold(he)) toReturn.add(he); he = he.next(); } } return toReturn; } /** * Method use to filter hald edges which will be included in the skeleton * @return true if the half edge must be included */ protected boolean isNonManifold(AbstractHalfEdge he) { if(he.hasAttributes(AbstractHalfEdge.OUTER)) return false; return he.hasAttributes(AbstractHalfEdge.NONMANIFOLD) || he.hasAttributes(AbstractHalfEdge.BOUNDARY) || he.getTri().getGroupId() != he.sym().getTri().getGroupId(); } private void getGroups(AbstractHalfEdge v, Collection<Integer> groups) { if(v.hasAttributes(AbstractHalfEdge.BOUNDARY)) { groups.add(v.getTri().getGroupId()); } else if(v.hasAttributes(AbstractHalfEdge.NONMANIFOLD)) { Iterator<AbstractHalfEdge> it = v.fanIterator(); while(it.hasNext()) groups.add(it.next().getTri().getGroupId()); } else { groups.add(v.getTri().getGroupId()); groups.add(v.sym().getTri().getGroupId()); } } /** Return true if edge.destination() is a polyline end */ private boolean isPolylineEnd(AbstractHalfEdge edge, double angle) { AbstractHalfEdge next = null; if(!edge.destination().isMutable()) { return true; } else if(edge.destination().isManifold()) { Triangle triangle = (Triangle) edge.destination().getLink(); AbstractHalfEdge ot = edge.destination().getIncidentAbstractHalfEdge(triangle, null); assert ot.origin() == edge.destination(); Vertex d = ot.destination(); do { if(ot.destination() != edge.origin() && isNonManifold(ot)) { if(next == null) next = ot; else return true; } ot = ot.nextOriginLoop(); } while (ot.destination() != d); } else { Iterator<AbstractHalfEdge> it = edge.destination().getNeighbourIteratorAbstractHalfEdge(); while(it.hasNext()) { AbstractHalfEdge e = it.next(); assert e != edge; assert e.origin() == edge.destination(); if((next == null || e.destination() != next.destination()) && e.destination() != edge.origin() && isNonManifold(e)) { if(next == null) next = e; else return true; } } } if(next == null) // Cannot find the next segment so this is a end. // this happen when the next whould a have been of type which is // filtered by the isNonManifold method return true; if(edge.hasAttributes(AbstractHalfEdge.IMMUTABLE) != next.hasAttributes( AbstractHalfEdge.IMMUTABLE)) return true; if(edge.hasAttributes(AbstractHalfEdge.BOUNDARY) && next.hasAttributes(AbstractHalfEdge.BOUNDARY)) { if(edge.getTri().getGroupId() != next.getTri().getGroupId()) return true; } else { groups1.clear(); groups2.clear(); getGroups(edge, groups1); getGroups(next, groups2); if(!groups1.equals(groups2)) return true; } if(angle > 2*Math.PI) return true; return edge.destination().angle3D(edge.origin(), next.destination()) < angle; } private List<AbstractHalfEdge> createPolyline(AbstractHalfEdge startEdge, Collection<Vertex> possibleEnds) { ArrayList<AbstractHalfEdge> beams = new ArrayList<AbstractHalfEdge>(); Vertex cv = startEdge.destination(); AbstractHalfEdge cb = startEdge; beams.add(startEdge); while(cv != startEdge.origin() && !possibleEnds.contains(cv)) { cb = cb.next(); while(!isNonManifold(cb)) cb = cb.sym().next(); beams.add(cb); cv = cb.destination(); } return beams; } private Collection<List<AbstractHalfEdge>> computePolylines( Collection<AbstractHalfEdge> input, double angle) { ArrayList<List<AbstractHalfEdge>> toReturn = new ArrayList<List<AbstractHalfEdge>>(); Collection<AbstractHalfEdge> beamSet = HashFactory.createSet(input); Set<Vertex> polylineEnds = HashFactory.createSet(); for(AbstractHalfEdge b:beamSet) { if(isPolylineEnd(b, angle)) polylineEnds.add(b.destination()); } //The first iteration is for polyline ends detected by isPolylineEnd. //Following iterations are for smooth polylines do { for(Vertex bv:polylineEnds) { Iterator<AbstractHalfEdge> it = bv.getNeighbourIteratorAbstractHalfEdge(); while(it.hasNext()) { AbstractHalfEdge startBeam = it.next(); if(beamSet.contains(startBeam)) { List<AbstractHalfEdge> polylineB = createPolyline( startBeam, polylineEnds); beamSet.removeAll(polylineB); toReturn.add(Collections.unmodifiableList(polylineB)); } } } polylineEnds.clear(); // Beams which are in smooth loops are not detected by // isPolylineEnd. At this step beamSet should only contains such // beams, so any vertex could be concidered as a polyline end. if(!beamSet.isEmpty()) polylineEnds.add(beamSet.iterator().next().origin()); } while(!polylineEnds.isEmpty()); return toReturn; } public static void main(final String[] args) { try { Mesh m = new Mesh(MeshTraitsBuilder.getDefault3D()); assert m.hasAdjacency(); MeshReader.readObject3D(m, "/home/robert/ast-a319-neo/demo-anabelle/demo/amibe.dir"); Skeleton sk = new Skeleton(m, 0); System.out.println(sk.getPolylines().size()); int k = 1000; for(List<AbstractHalfEdge> p:sk.getPolylines()) { for(AbstractHalfEdge e:p) m.addBeam(e.origin(), e.destination(), k); k++; } m.getTriangles().clear(); MeshWriter.writeObject3D(m, "/tmp/zob.amibe", null); new Amibe2VTK("/tmp/zob.amibe").write("/tmp/zob.vtp"); } catch (Exception ex) { Logger.getLogger(Skeleton.class.getName()).log(Level.SEVERE, null, ex); } } }