/*
* 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 2011, by EADS France
*/
package org.jcae.mesh.amibe.algos3d;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.jcae.mesh.amibe.ds.Mesh;
import org.jcae.mesh.amibe.ds.Vertex;
import org.jcae.mesh.amibe.metrics.Matrix3D;
import org.jcae.mesh.amibe.util.HashFactory;
/**
* Compute polylines from the beams of a mesh.
* The key of the map is the group id.
* @author Jerome Robert
*/
public class PolylineFactory extends HashMap<Integer, Collection<List<Vertex>>>{
/** Remove polylines ends which would lead to small polylines */
private static void filterSmall(Set<BeamVertex> polylineEnds, double small2) {
Set<BeamVertex> toRemove = HashFactory.createSet();
for(BeamVertex bv: polylineEnds)
{
for(Beam b:bv)
{
BeamVertex other = b.getOther(bv);
if(polylineEnds.contains(other) && !toRemove.contains(bv) && b.lengthSqr() < small2)
{
if(bv.isManifold() && !other.isManifold())
toRemove.add(bv);
else if(other.isManifold() && !bv.isManifold())
toRemove.add(other);
else if(bv.isManifold() && other.isManifold())
toRemove.add(other);
}
}
}
polylineEnds.removeAll(toRemove);
}
private final boolean ignoreGroups;
private static int beamCounter = 0;
private class BeamVertex extends ArrayList<Beam>
implements Comparable<BeamVertex>
{
/** Replace hashCode to ensure the algorithm is reproducible */
private final int id = beamCounter ++;
public final Vertex vertex;
public boolean isPolylineEnd(double dotProdLimit)
{
return !isManifold() || !isSmooth(dotProdLimit);
}
private boolean isManifold()
{
return size() == 2 && (ignoreGroups || get(0).group == get(1).group);
}
private double[] getVector(int i)
{
Vertex vv1 = get(i).getOther(this).vertex;
double[] r = new double[3];
vv1.sub(vertex, r);
return r;
}
private boolean isSmooth(double dotProdLimit)
{
if(dotProdLimit < -1)
return true;
double[] v1 = getVector(0);
double[] v2 = getVector(1);
double nv1 = Matrix3D.norm(v1);
double nv2 = Matrix3D.norm(v2);
double dot = Matrix3D.prodSca(v1, v2) / nv1 / nv2;
return dot < dotProdLimit;
}
/** Non manifold vertices are concidered higher than others */
public int compareTo(BeamVertex o) {
if(!isManifold() && o.isManifold())
return 1;
else if(isManifold() && !o.isManifold())
return -1;
else
return id - o.id;
}
public BeamVertex(Vertex vertex) {
super(2);
this.vertex = vertex;
}
public Beam getOther(Beam b)
{
assert isManifold();
if(b == get(0))
return get(1);
else
return get(0);
}
@Override
public boolean equals(Object o) {
return id == ((BeamVertex)o).id;
}
@Override
public int hashCode() {
return id;
}
}
private static class Beam
{
public final BeamVertex v1, v2;
public final int group;
public Beam(BeamVertex v1, BeamVertex v2, int group) {
this.v1 = v1;
this.v2 = v2;
this.group = group;
}
public BeamVertex getOther(BeamVertex v)
{
return (v == v1) ? v2 : v1;
}
@Override
public String toString() {
return "(" + v1.id + ", " + v2.id + ")";
}
public void remove()
{
v1.remove(this);
v2.remove(this);
}
public void check()
{
assert v1.contains(this);
assert v2.contains(this);
}
public double lengthSqr()
{
return v1.vertex.sqrDistance3D(v2.vertex);
}
}
private BeamVertex createBeamVertex(Vertex v, Map<Vertex, BeamVertex> map)
{
BeamVertex bv = map.get(v);
if(bv == null)
{
bv = new BeamVertex(v);
map.put(v, bv);
}
return bv;
}
private Map<Integer, Collection<Beam>> indexify(Mesh mesh)
{
List<Vertex> beams = mesh.getBeams();
int nbBeams = beams.size() / 2;
Map<Vertex, BeamVertex> verticeMap = HashFactory.createMap();
Map<Integer, Collection<Beam>> beamMap = HashFactory.createMap();
for(int i = 0; i<nbBeams; i++)
{
BeamVertex v1 = createBeamVertex(beams.get(i*2), verticeMap);
BeamVertex v2 = createBeamVertex(beams.get(i*2+1), verticeMap);
int group = ignoreGroups ? -1 : mesh.getBeamGroup(i);
Beam beam = new Beam(v1, v2, group);
v1.add(beam);
v2.add(beam);
Collection<Beam> beamSet = beamMap.get(group);
if(beamSet == null)
{
beamSet = HashFactory.createSet();
beamMap.put(group, beamSet);
}
beamSet.add(beam);
}
return beamMap;
}
/**
* Create a polyline
* @param startV start vertex of the polyline
* @param start first beam of the polyline
* @param polyline the created polyline as a list of vertex
* @param beams the created polyline as a list of beams
*/
private static void createPolyline(BeamVertex startV, Beam start,
List<Vertex> polyline, List<Beam> beams, Set<BeamVertex> polylineEnds)
{
polyline.add(startV.vertex);
BeamVertex cv = start.getOther(startV);
Beam cb = start;
beams.add(start);
while(cv != startV && !polylineEnds.contains(cv) )
{
assert startV.vertex != cv.vertex;
assert !polyline.contains(cv.vertex):polyline.indexOf(cv.vertex)+" / "+polyline.size();
polyline.add(cv.vertex);
cb = cv.getOther(cb);
beams.add(cb);
cv = cb.getOther(cv);
assert start != cb;
}
polyline.add(cv.vertex);
}
/** Create polylines without angle constraints */
public PolylineFactory(Mesh mesh) {
this(mesh, -1.0, 0);
}
public PolylineFactory(Mesh mesh, double angle, double smallBeams) {
this(mesh, angle, smallBeams, false);
}
/**
* @param mesh
* @param angle Ridge limit angle in degrees. Polylines won't contains angle
* smaller than this value.
* @param smallBeams beams smaller than this value will be ignore when
* calculating the smooth criteria
*/
public PolylineFactory(Mesh mesh, double angle, double smallBeams, boolean ignoreGroups) {
this.ignoreGroups = ignoreGroups;
angle = Math.cos(Math.toRadians(angle));
Map<Integer, Collection<Beam>> beamMap = indexify(mesh);
Set<BeamVertex> polylineEnds = HashFactory.createSet();
ArrayList<Beam> polylineB = new ArrayList<Beam>();
for(Entry<Integer, Collection<Beam>> e:beamMap.entrySet())
{
int group = e.getKey();
Collection<List<Vertex>> polylines = new ArrayList<List<Vertex>>();
put(group, polylines);
Collection<Beam> beamSet = e.getValue();
polylineEnds.clear();
for(Beam b:beamSet)
{
if(b.v1.isPolylineEnd(angle))
polylineEnds.add(b.v1);
if(b.v2.isPolylineEnd(angle))
polylineEnds.add(b.v2);
}
filterSmall(polylineEnds, smallBeams * smallBeams);
//The first iteration is for polyline ends detected by isPolylineEnd.
//Following iterations are for smooth polylines
do
{
for(BeamVertex bv:polylineEnds)
{
for(Beam startBeam:bv)
{
if(beamSet.contains(startBeam))
{
ArrayList<Vertex> polyline = new ArrayList<Vertex>();
polylineB.clear();
createPolyline(bv, startBeam, polyline, polylineB, polylineEnds);
polylines.add(polyline);
beamSet.removeAll(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().v1);
}
while(!polylineEnds.isEmpty());
}
}
}