use of org.apache.commons.math3.geometry.euclidean.twod.Line in project chordatlas by twak.
the class FindLines method regress.
private Line regress(Set<Line> things, LinearForm lfDir) {
SimpleRegression fit = new SimpleRegression();
// regression isn't happy on lines with infinite slope: so swap params!
boolean flip = Math.abs(lfDir.x) > Math.abs(lfDir.y);
for (Line l : things) if (flip) {
fit.addData(l.start.y, l.start.x);
fit.addData(l.end.y, l.end.x);
} else {
fit.addData(l.start.x, l.start.y);
fit.addData(l.end.x, l.end.y);
}
double intercept = fit.getIntercept(), slope = fit.getSlope();
if (Double.isNaN(intercept))
return null;
LinearForm lf;
if (flip)
lf = new LinearForm(1, -slope);
else
lf = new LinearForm(-slope, 1);
if (lf.unitVector().angle(lfDir.unitVector()) > Math.PI / 2) {
// if regression is pointing wrong way, flip
lf.x = -lf.x;
lf.y = -lf.y;
}
if (flip)
lf.findC(intercept, 0);
else
lf.findC(0, intercept);
double[] minMax = things.stream().map(x -> new double[] { lf.findPParam(x.start), lf.findPParam(x.end) }).collect(new InAxDoubleArray());
// do regression
return new Line(lf, minMax[0], minMax[1]);
}
use of org.apache.commons.math3.geometry.euclidean.twod.Line in project chordatlas by twak.
the class Prof method parameterize.
public static // all profs from a single profile-edge are in the same 2D space, with strange x-origin
Prof parameterize(// all profs from a single profile-edge are in the same 2D space, with strange x-origin
List<Prof> in) {
// double toProfileEdge;
// {
// Prof eg = in.iterator().next();
// Point3d p = Pointz.to3( profileEdge.start );
// toProfileEdge = eg.to2d( p ).x;
// }
double avgMinY = in.stream().filter(p -> p != null).mapToDouble(p -> p.get(0).y).average().getAsDouble();
Set<Line> lines = new HashSet<>();
for (Prof p : in) for (int i = 1; i < p.size(); i++) lines.add(new Line(p.get(i - 1), p.get(i)));
SliceParameters P = new SliceParameters(5);
P.FL_REGRESS = true;
P.FL_BINS = 20;
// double A = 0.4; // simple = 0.4
// double B = 0.1; // simple = 1;
// simple = 0.4
double A = 0.2;
// simple = 0.1;
double B = 0.3;
// simple = 0.1; rotate threshold, radians
double C = 0.1;
double firstFloorHeight = 2;
P.MIN_LINES = Math.max(1, in.size() * A);
lines = new FindLines(lines, P) {
protected double nextAngle(Set<Line> remaining, int iteration) {
double delta = Math.PI / P.FL_BINS;
// angle bin
Bin<Line> aBin = new Bin(-Math.PI - delta, Math.PI + delta, P.FL_BINS * 2, true);
for (Line l : remaining) {
double len = l.length();
double angle = l.aTan2();
aBin.add(angle, len, l);
}
// return MUtils.PI2;
if (iteration < 1 && aBin.getWeight(Mathz.PI2) >= 10)
return Mathz.PI2;
int aBinI = aBin.maxI();
return aBin.val(aBinI);
}
protected double getTolNearLine(Point2d p) {
return P.FL_NEAR_LINE * (p.y < avgMinY + firstFloorHeight ? 5 : B);
}
protected double getTolNearLine2(Point2d p) {
return P.FL_NEAR_LINE_2 * (p.y < avgMinY + firstFloorHeight ? 10 : B);
}
protected double getTolRemoveAngle(Line l) {
return l.start.y < avgMinY + firstFloorHeight ? Math.PI * 0.5 : Math.PI * 0.2;
}
}.result.all;
List<Line> llines = // is rubbish
lines.stream().filter(l -> l.lengthSquared() > 0.001).filter(// is floor polygon
l -> l.end.y > avgMinY + 1 || Math.abs(l.start.y - l.end.y) > 0.1).collect(Collectors.toList());
Prof clean = new Prof(in.get(in.size() / 2));
clean.clear();
if (llines.isEmpty()) {
clean.add(new Point2d(0, 0));
clean.add(new Point2d(0, 1));
return clean;
}
for (int i = 0; i < llines.size(); i++) {
Line l = llines.get(i);
double angle = l.aTan2();
if (angle < Mathz.PI2 + C && angle > Mathz.PI2 - C)
llines.set(i, FindLines.rotateToAngle(l, l.fromPPram(0.5), Mathz.PI2));
}
// llines.stream().filter( l -> l.start.y > l.end.y ).forEach( l -> l.reverseLocal() );
Collections.sort(llines, new Comparator<Line>() {
public int compare(Line o1, Line o2) {
return Double.compare(o1.fromPPram(0.2).y, o2.fromPPram(0.2).y);
}
});
// for (Line l : llines)
// PaintThing.debug( new Color(170,0,255), 2f, new Line( l.start.x+5, -l.start.y, l.end.x+5, -l.end.y ) );
Line lastL = null;
Point2d lastP = new Point2d(0, -Double.MAX_VALUE);
for (Line l : llines) {
// if (c >= 6)
// continue;
// if ( c== 5)
// System.out.println("here");
// c++;
Point2d mid = l.fromPPram(0.5);
if (!(lastL != null && !lastL.isOnLeft(mid) || (lastP.y == -Double.MAX_VALUE || (mid.y >= lastP.y - 0.5 && mid.x <= lastP.x + 0.5))))
continue;
boolean startAbove = l.start.y >= lastP.y && l.start.x <= lastP.x, endAbove = l.end.y >= lastP.y && l.end.x <= lastP.x;
if (l.end.y < l.start.y)
l.end.y = l.start.y;
if (startAbove && endAbove) {
// okay
} else {
if (lastL != null && l.start.distanceSquared(lastP) < 9) {
Point2d sec = lastL.intersects(l, false);
if (sec != null && sec.distanceSquared(lastP) < 9 && sec.x <= lastL.start.x && sec.y >= lastL.start.y) {
clean.remove(clean.size() - 1);
clean.add(sec);
lastP = sec;
l.start = sec;
} else if (l.start.x < lastP.x) {
sec = new LinearForm(new Vector2d(1, 0)).findC(l.start).intersect(new LinearForm(lastL));
if (sec != null && sec.distanceSquared(lastP) < 9) {
clean.remove(clean.size() - 1);
clean.add(sec);
lastP = sec;
}
}
}
if (l.start.x > lastP.x + 0.01 || l.end.x > lastP.x + 0.01) {
Point2d sec = new LinearForm(new Vector2d(0, 1)).findC(new Point2d(lastP.x, 0)).intersect(new LinearForm(l));
if (sec != null && sec.distanceSquared(lastP) < 9 && sec.distanceSquared(l.start) < 9) {
if (l.start.x > lastP.x)
l.start = sec;
else
l.end = sec;
}
}
}
if (lastL != null && l.start.distanceSquared(lastP) < 4) {
Point2d sec = lastL.intersects(l, false);
if (sec != null && (sec.distanceSquared(lastP) < 4 || Math.abs(sec.y - lastP.y) < 1) && sec.x <= lastL.start.x && sec.y >= lastL.start.y) {
clean.remove(clean.size() - 1);
clean.add(sec);
lastP = sec;
l.start = sec;
} else if (l.start.x < lastP.x) {
sec = new LinearForm(new Vector2d(1, 0)).findC(l.start).intersect(new LinearForm(lastL));
if (sec != null && (sec.distanceSquared(lastP) < 4 || Math.abs(sec.y - lastP.y) < 1)) {
clean.remove(clean.size() - 1);
clean.add(sec);
lastP = sec;
// l.start = sec;
}
}
}
if (lastP.y - l.end.y < 3 && l.end.x - lastP.x < 3) {
for (Point2d pt : l.points()) {
pt.x = Math.min(pt.x, lastP.x);
pt.y = Math.max(pt.y, lastP.y);
}
if (!l.start.equals(l.end))
for (Point2d pt : l.points()) {
// if (c == 2)
// PaintThing.debug.put(1, new Point2d ( pt.x, -pt.y ) );
pt = new Point2d(pt);
pt.x = Math.min(pt.x, lastP.x);
pt.y = Mathz.max(0, pt.y, lastP.y);
if (clean.isEmpty() && pt.y > 0.2) {
clean.add(new Point2d(pt.x, 0));
}
if (lastP != null && pt.distanceSquared(lastP) > 0.02) {
clean.add(pt);
}
lastP = clean.get(clean.size() - 1);
if (clean.size() >= 3) {
Point2d a = clean.get(clean.size() - 1), b = clean.get(clean.size() - 2), c = clean.get(clean.size() - 3);
if (Math.abs(Mathz.area(c, b, a)) < 0.1 || Mathz.absAngleBetween(a, b, c) < 0.1)
clean.remove(clean.size() - 2);
}
}
}
if (clean.size() >= 2)
lastL = new Line(clean.get(clean.size() - 2), clean.get(clean.size() - 1));
}
return clean;
}
use of org.apache.commons.math3.geometry.euclidean.twod.Line in project chordatlas by twak.
the class Prof method findProfileLines.
/**
* We find an initial base offset. Then we cluster the start point of all
* (clean) profiles. If any are a good distance from the initial base, we
* add those as their own profile lines.
*
* The original line is offset by the remaiing data.
*/
public static List<SuperLine> findProfileLines(Collection<Prof> profiles, Line3d line) {
List<SuperLine> out = new ArrayList();
// PaintThing.debug.clear();
SuperLine superLine = new SuperLine(line.start.x, line.start.z, line.end.x, line.end.z);
double outLen = superLine.length();
double min = Double.MAX_VALUE, max = -Double.MAX_VALUE;
Cache<Prof, Double> vLength = new Cache<Prof, Double>() {
@Override
public Double create(Prof i) {
return i.verticalLength(0.5);
}
};
double vLen = profiles.stream().mapToDouble(p -> vLength.get(p)).sum();
boolean useVertical = vLen / profiles.size() > 1;
class Wrapper implements Clusterable {
double[] pt;
public Wrapper(Point2d pt) {
this.pt = new double[] { pt.x, pt.y };
}
@Override
public double[] getPoint() {
return pt;
}
}
List<Wrapper> toCluster = new ArrayList();
List<Double> baseLineOffset = new ArrayList();
for (Prof p : profiles) {
if (// vLen / (5*profiles.size()))
useVertical && vLength.get(p) < 1)
continue;
Prof clean = p.parameterize();
Point2d pt = clean.get(0);
Point3d pt3 = clean.to3d(pt);
double ppram = superLine.findPPram(new Point2d(pt3.x, pt3.z));
baseLineOffset.add(pt.x);
toCluster.add(new Wrapper(new Point2d(pt.x, ppram * outLen)));
min = Math.min(min, ppram);
max = Math.max(max, ppram);
}
if (min == max || toCluster.isEmpty())
return out;
if (true) {
baseLineOffset.sort(Double::compareTo);
double modeBaselineOffset = baseLineOffset.get(baseLineOffset.size() / 2);
DBSCANClusterer<Wrapper> cr = new DBSCANClusterer<>(1.5, 0);
List<Cluster<Wrapper>> results = cr.cluster(toCluster);
Iterator<Cluster<Wrapper>> cit = results.iterator();
while (cit.hasNext()) {
Cluster<Wrapper> cw = cit.next();
if (cw.getPoints().size() < 2 / TweedSettings.settings.profileHSampleDist) {
cit.remove();
double cMeanY = cw.getPoints().stream().mapToDouble(x -> x.pt[1]).average().getAsDouble();
double bestDist = Double.MAX_VALUE;
Cluster<Wrapper> bestWrapper = null;
for (Cluster<Wrapper> near : results) {
double meanY = near.getPoints().stream().mapToDouble(x -> x.pt[1]).average().getAsDouble();
double dist = Math.abs(meanY - cMeanY);
if (dist < bestDist) {
bestDist = dist;
bestWrapper = near;
}
}
if (bestWrapper != null)
bestWrapper.getPoints().addAll(cw.getPoints());
}
}
{
baseLineOffset.clear();
int c = 0;
for (Cluster<Wrapper> cw : results) {
double[] minMax = cw.getPoints().stream().map(p -> new double[] { p.pt[1] }).collect(new InAxDoubleArray());
double[] offsetA = cw.getPoints().stream().mapToDouble(p -> p.pt[0]).sorted().toArray();
double offset = offsetA[offsetA.length / 2];
if (offset - modeBaselineOffset < 1) {
for (Wrapper w : cw.getPoints()) baseLineOffset.add(w.pt[0]);
continue;
}
SuperLine sl = new SuperLine(superLine.fromPPram(minMax[0] / outLen), superLine.fromPPram(minMax[1] / outLen));
sl.moveLeft(offset);
out.add(sl);
List<Point2d> pts = cw.getPoints().stream().map(w -> new Point2d(w.pt[0], w.pt[1])).collect(Collectors.toList());
PaintThing.debug(Rainbow.getColour(c++), 1, pts);
}
}
}
Point2d nStart = superLine.fromPPram(min), nEnd = superLine.fromPPram(max);
superLine.start = nStart;
superLine.end = nEnd;
baseLineOffset.sort(Double::compare);
if (!baseLineOffset.isEmpty())
superLine.moveLeft(baseLineOffset.get(baseLineOffset.size() / 2));
out.add(0, superLine);
return out;
}
use of org.apache.commons.math3.geometry.euclidean.twod.Line in project chordatlas by twak.
the class SkelFootprint method mergeOnProfiles.
private void mergeOnProfiles(HalfMesh2 mesh, List<Line> footprint) {
System.out.println("merging over profiles...");
TreeSet<HalfFace> togo = new TreeSet<>((HalfFace o1, HalfFace o2) -> Double.compare(o1.area(), o2.area()));
togo.addAll(mesh.faces);
int count = 0;
while (!togo.isEmpty()) {
HalfFace f = togo.pollFirst();
Cache<HalfEdge, MutableDouble> crossedBy = new Cach<>(e -> new MutableDouble(0));
for (HalfEdge e : f) {
SuperEdge se = (SuperEdge) e;
if (se.profLine != null) {
MegaFacade mf = ((SuperLine) se.profLine).mega;
if (mf != null)
for (Prof p : mf.getTween(se.start, se.end, 0)) {
Line proj = new Line(Pointz.to2(p.to3d(p.get(0))), Pointz.to2(p.to3d(p.get(p.size() - 1))));
for (HalfEdge e2 : f) {
SuperEdge se2 = (SuperEdge) e2;
if (se2.profLine == null && (se2.over == null || ((SuperEdge) se2.over).profLine == null) && e2.over != null && e2.line().intersects(proj) != null && Mathz.inRange(e2.line().absAngle(proj), 0.25 * Math.PI, 0.75 * Math.PI)) {
crossedBy.get(e2).d += TweedSettings.settings.profileHSampleDist;
}
}
}
}
}
count += crossedBy.cache.size();
Optional<Map.Entry<HalfEdge, MutableDouble>> longestO = crossedBy.cache.entrySet().stream().filter(//
e1 -> ((SuperEdge) e1.getKey()).profLine == null && e1.getValue().d > 0).max((e1, e2) -> Double.compare(e1.getValue().d, e2.getValue().d));
if (longestO.isPresent()) {
Map.Entry<HalfEdge, MutableDouble> longest = longestO.get();
if (longest.getValue().d > 0.6 * longest.getKey().length()) {
HalfFace tgf = longest.getKey().over.face;
togo.remove(tgf);
longest.getKey().face.merge(mesh, tgf);
((SuperFace) longest.getKey().face).mergeFrom((SuperFace) tgf);
togo.add(f);
}
}
}
System.out.println("found crossings " + count);
killDoubleEdges(mesh);
}
use of org.apache.commons.math3.geometry.euclidean.twod.Line in project imagej-ops by imagej.
the class DefaultConvexHull3D method getV2.
/**
* Finds the vertex with the largest distance to the line described by v0, v1.
*
* @param v0 Vertex of the line.
* @param v1 Vertex of the line.
* @return Vertex with the largest distance.
*/
private Vertex getV2(final double epsilon, final Set<Vertex> vertices, final Vertex v0, final Vertex v1) {
Iterator<Vertex> it = vertices.iterator();
// v0 -------------------------------------v1
// |
// | d
// |
// * v
//
// d = |(v - v0) x (v - v1)| / |(v1 - v0)|
// We can omit the common denominator because it does not change over
// all computations.
double distLinePoint = epsilon;
Vertex v2 = null;
while (it.hasNext()) {
Vertex v = it.next();
Vector3D d0 = v.subtract(v1);
Vector3D d1 = v.subtract(v0);
double lengthSq = d0.crossProduct(d1).getNormSq();
if (lengthSq > distLinePoint) {
distLinePoint = lengthSq;
v2 = v;
}
}
return v2;
}
Aggregations