/* * 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.io.FileInputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.DoubleBuffer; import java.nio.channels.FileChannel; import java.util.AbstractList; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Random; import java.util.logging.Level; import java.util.logging.Logger; import org.jcae.mesh.amibe.ds.AbstractHalfEdge; import org.jcae.mesh.amibe.ds.AbstractHalfEdge.Quality; import org.jcae.mesh.amibe.ds.Mesh; import org.jcae.mesh.amibe.ds.Triangle; import org.jcae.mesh.amibe.ds.TriangleHE; import org.jcae.mesh.amibe.ds.Vertex; import org.jcae.mesh.amibe.metrics.Location; import org.jcae.mesh.amibe.metrics.MetricSupport.AnalyticMetricInterface; import org.jcae.mesh.amibe.projection.MeshLiaison; import org.jcae.mesh.amibe.projection.TriangleKdTree; import org.jcae.mesh.xmldata.MeshReader; import org.jcae.mesh.xmldata.MultiDoubleFileReader; /** * * @author Jerome Robert */ public class VertexInsertion { private final MeshLiaison liaison; private final Mesh mesh; private final static Logger LOGGER = Logger.getLogger(VertexInsertion.class.getName()); private final TriangleKdTree kdTree; /** triangles added when inserting a point in the middle of a triangle */ private final Collection<Triangle> tmp = new ArrayList<Triangle>(3); private Collection<Vertex> notMutableInserted, mutableInserted; private final AnalyticMetricInterface metric; private final VertexSwapper swapper; private int swapperGroup; private double swapperDeflection, swapperVolume; private boolean alwaysInsert; public VertexInsertion(MeshLiaison liaison, final double size) { this(liaison.getMesh(), liaison, size); } public VertexInsertion(Mesh mesh, final double size) { this(mesh, null, size); } private VertexInsertion(Mesh mesh, MeshLiaison liaison, final double size) { this(mesh, liaison, new AnalyticMetricInterface() { public double getTargetSize(double x, double y, double z, int groupId) { return size; } public double getTargetSizeTopo(Mesh mesh, Vertex v) { return size; } }); } public VertexInsertion(MeshLiaison liaison, AnalyticMetricInterface metric) { this(liaison.getMesh(), liaison, metric); } private VertexInsertion(Mesh mesh, MeshLiaison liaison, AnalyticMetricInterface metric) { this.liaison = liaison; this.mesh = mesh; LOGGER.info("Start creating kd-tree"); kdTree = new TriangleKdTree(mesh); LOGGER.info(kdTree.stats()); this.metric = metric; if(liaison == null) swapper = new VertexSwapper(mesh) { @Override protected boolean isQualityImproved(Quality quality) { return (super.isQualityImproved(quality) && quality.swappedVolume() < swapperVolume) || needRemoveSharpEdge(quality); } }; else swapper = new VertexSwapper(liaison) { @Override protected boolean isQualityImproved(Quality quality) { return (super.isQualityImproved(quality) && quality.sqrSwappedDeflection(swapperGroup) < swapperDeflection) || needRemoveSharpEdge(quality); } }; swapper.setKdTree(kdTree); } private boolean needRemoveSharpEdge(Quality q) { return q.getAngle() < -0.1 && q.getSwappedAngle() > q.getAngle() && q.getSwappedQuality() > 1E-3; } public void insertNodes(String fileName, int group) throws IOException { FileChannel vertexChannel = new FileInputStream(fileName).getChannel(); ByteBuffer bb = ByteBuffer.allocate((int)vertexChannel.size()); bb.order(ByteOrder.nativeOrder()); vertexChannel.read(bb); bb.rewind(); insertNodes(bb.asDoubleBuffer(), group); } public void insertNodes(final DoubleBuffer vertices, int group) { final int size = vertices.limit() / 3; AbstractList<Vertex> l = new AbstractList<Vertex>() { @Override public Vertex get(int index) { int n = index * 3; return new Vertex(null, vertices.get(n), vertices.get(n + 1), vertices.get(n + 2)); } @Override public int size() { return size; } }; insertNodes(l, group); } /** * Insert vertices from another mesh * @param vertices the vertices to insert * @param group insert vertices only in triangles of this group. Use -1 * to try to insert in all groups * @param liaisonError the maximal acceptable distance between the * inserted point and it's projection. * @param insertionTol Do not the insert point if a point already exist at * a distance lower than this value */ public void insertNodes(List<Vertex> vertices, int group) { if(vertices.isEmpty()) { mutableInserted = Collections.emptyList(); notMutableInserted = Collections.emptyList(); return; } Location projection = new Location(); mutableInserted = new ArrayList<Vertex>(vertices.size()); notMutableInserted = new ArrayList<Vertex>(); int n = vertices.size(); int nbInserted = 0; swapper.setGroup(group); List<Integer> randomIterator = createRandomIterator(n); main: for(int i = 0; i < n; i++) { Vertex v = vertices.get(randomIterator.get(i)); double localMetric = metric.getTargetSize( v.getX(), v.getY(), v.getZ(), group); double localMetric2 = localMetric * localMetric; double tol2 = localMetric2 / (40 * 40); TriangleHE t = (TriangleHE) kdTree.getClosestTriangle( v, projection, group); if(t == null) throw new NullPointerException("Cannot find projection for "+v); if(liaison != null) liaison.move(v, projection, group, true); if(!alwaysInsert) { for(int iv = 0; iv < 3; iv++) { Vertex tv = t.getV(iv); if(tv.sqrDistance3D(v) < tol2) { if(tv.isMutable()) mutableInserted.add(tv); else notMutableInserted.add(tv); continue main; } } } // We could check that we are close from an edge but we don't // because swaping will properly handle this case kdTree.remove(t); t.split(mesh, v, tmp); for(Triangle tt: tmp) kdTree.addTriangle(tt); tmp.clear(); mutableInserted.add(v); swapperGroup = group; swapperDeflection = localMetric2 / 2; swapperVolume = localMetric2 * localMetric; swapper.swap(v); nbInserted ++; } LOGGER.info(nbInserted+" / "+vertices.size()+" inserted nodes on group "+group); } public Collection<Vertex> getNotMutableInserted() { return notMutableInserted; } public Collection<Vertex> getMutableInserted() { return mutableInserted; } /** * Try to insert a vertex into an edge of the given triangle * @param t * @param tol2 * @param v * @return The number of created triangles */ private boolean edgeSplit(Triangle t, double tol2, Vertex v) { AbstractHalfEdge e = t.getAbstractHalfEdge(); for(int i = 0; i < 3; i++) { if(MeshLiaison.sqrOrthoDistance(v, e) < tol2) { if(e.hasAttributes(AbstractHalfEdge.NONMANIFOLD)) { Iterator<AbstractHalfEdge> rit = e.fanIterator(); while(rit.hasNext()) kdTree.remove(rit.next().getTri()); } else { kdTree.remove(t); kdTree.remove(e.sym().getTri()); } mesh.vertexSplit(e, v); Iterator<Triangle> it = v.getNeighbourIteratorTriangle(); while(it.hasNext()) kdTree.addTriangle(it.next()); return true; } e = e.next(); assert e != null; } return false; } /** * Always insert the point even if we are close from an existing vertex. * If true the tolerance is only used to swap. */ public void setAlwaysInsert(boolean alwaysInsert) { this.alwaysInsert = alwaysInsert; } public static void main(final String[] args) { try { MultiDoubleFileReader mdf = new MultiDoubleFileReader("/tmp/tmps2zXh3/nodes.bin"); Mesh mesh = new Mesh(); MeshReader.readObject3D(mesh, "/tmp/debug2.zebra/amibe.dir"); MeshLiaison ml = MeshLiaison.create(mesh); ml.getMesh().buildGroupBoundaries(); VertexInsertion vi = new VertexInsertion(ml, 300); for(int gId = 1; gId <= ml.getMesh().getNumberOfGroups(); gId++) { DoubleBuffer vs = mdf.next(); if(gId == 65) vi.insertNodes(vs, gId); } } catch (Exception ex) { Logger.getLogger(VertexInsertion.class.getName()).log(Level.SEVERE, null, ex); } } private List<Integer> createRandomIterator(final int n) { ArrayList<Integer> toReturn = new ArrayList<Integer>(n); for(int i = 0; i < n; i++) toReturn.add(i); Collections.shuffle(toReturn, new Random(0)); return toReturn; } }