use of org.twak.utils.Line in project chordatlas by twak.
the class Concarnie method apply.
private List<Problem> apply(Problem problem, int count) {
if (depth > 10 || problem.soup.isEmpty()) {
problem.addPortal();
return Collections.emptyList();
}
Set<Line> portals = new HashSet<>();
Set<Line> in = new HashSet<>(problem.soup);
for (Line sl : problem.hull) {
RangeMerge<Line> rm = new RangeMerge<>(P.CON_TOL, P.CON_TOL);
LinearForm lf = new LinearForm(sl);
for (Line l : problem.soup) {
if (onHull(sl, l)) {
double pps = lf.findPParam(l.start), ppe = lf.findPParam(l.end);
rm.add(pps, ppe, l);
in.remove(l);
}
}
List<Double> rmGet = rm.get();
if (rmGet.isEmpty()) {
if (!sl.start.equals(sl.end)) {
if (Double.isNaN(sl.start.x))
System.out.println("help!");
// whole thing is portal
portals.add(sl);
}
} else {
List<Point2d> occupied = new ArrayList();
{
double lf1 = lf.findPParam(sl.start), lf2 = lf.findPParam(sl.end);
if (lf1 > lf2) {
double tmp = lf1;
lf1 = lf2;
lf2 = tmp;
}
for (double d : rmGet) {
d = Mathz.clamp(d, lf1, lf2);
occupied.add(lf.fromPParam(d));
}
}
boolean onHull = false;
{
Point2d snapE = occupied.get(0), snapS = occupied.get(occupied.size() - 1);
if (snapS.distance(sl.start) < P.CON_TOL)
snapS.set(sl.start);
else {
occupied.add(new Point2d(sl.start));
}
if (snapE.distance(sl.end) < P.CON_TOL)
snapE.set(sl.end);
else {
occupied.add(0, new Point2d(sl.end));
onHull = true;
}
}
for (Pair<Point2d, Point2d> pair : new ConsecutiveItPairs<Point2d>(occupied)) {
onHull = !onHull;
if (pair.first().equals(pair.second()))
continue;
Line line = new Line(pair.first(), pair.second());
if (onHull) {
if (depth % 2 == 0)
line = line.reverse();
graph.add(line);
} else {
portals.add(line.reverse());
}
}
}
}
if (in.size() == problem.soup.size()) {
// we didn't do anything! remove something, anything...
List<Line> d = new ArrayList(in);
Collections.sort(d, (a, b) -> Double.compare(a.length(), b.length()));
for (// remove the shortest 1/3 lines
int i = 0; // remove the shortest 1/3 lines
i < Math.max(1, in.size() * 0.33); // remove the shortest 1/3 lines
i++) in.remove(d.get(i));
}
List<Portal> mergedPortals = mergeConsecutive(portals);
// assign each member of in to a portal
MultiMapSet<Portal, Line> subproblems = new MultiMapSet();
if (!mergedPortals.isEmpty()) {
// O(n^3) closest-clique assignment
MultiMapSet<Portal, Line> sub2 = new MultiMapSet();
for (Portal p : mergedPortals) sub2.putAll(p, p.lines);
while (!in.isEmpty()) {
double bestDist = Double.MAX_VALUE;
Portal bestP = null;
Line bestL = null;
for (Line l : in) for (Portal p : sub2.keySet()) for (Line sl : sub2.get(p)) {
double dlsl = l.distance(sl);
if (// ignore lines a long way away
dlsl > Math.max(P.CON_TOL * 3, p.length * 0.5))
continue;
double dist = dlsl + 0.1 * l.distance(p.summary);
if (dist < bestDist) {
bestP = p;
bestDist = dist;
bestL = l;
}
}
if (bestL == null)
break;
in.remove(bestL);
double lenBestL = bestL.length();
if (lenBestL > P.CON_TOL && lenBestL > 0.5 * bestP.summary.length()) {
in.add(new Line(bestL.start, bestL.fromPPram(0.5)));
in.add(new Line(bestL.fromPPram(0.5), bestL.end));
} else {
subproblems.put(bestP, bestL);
sub2.put(bestP, bestL);
}
}
} else {
mergedPortals.add(null);
subproblems.putAll(null, in);
}
return mergedPortals.stream().map(x -> new Problem(x == null ? Collections.emptyList() : x.lines, subproblems.get(x))).collect(Collectors.toList());
}
use of org.twak.utils.Line in project chordatlas by twak.
the class Concarnie method tidy.
private void tidy(Graph2D graph) {
UnionWalker uw = new UnionWalker();
for (Point2d a : graph.map.keySet()) {
for (Line l : graph.get(a)) {
uw.addEdge(l.start, l.end);
// PaintThing.debug.put("flibble", l);
}
}
out = uw.findAll();
// if (false)
for (Loop<Point2d> loop : out) {
for (Loopable<Point2d> pt : loop.loopableIterator()) {
Line prev = findSupporting(pt, 1), next = findSupporting(pt, -1);
if (prev != null && next != null) {
Point2d bestFit = prev.intersects(next, false);
if (bestFit != null && pt.get().distance(bestFit) < P.CON_TOL && // if nearer other ends of lines, don't use
bestFit.distanceSquared(prev.start) < bestFit.distanceSquared(prev.end) && bestFit.distanceSquared(next.end) < bestFit.distanceSquared(next.start)) {
pt.get().set(bestFit);
}
}
}
}
Iterator<Loop<Point2d>> lit = out.iterator();
// if (false)
while (lit.hasNext()) {
Loop<Point2d> loop = lit.next();
Loopable<Point2d> start = loop.start, current = start;
int size = loop.count();
boolean again;
do {
again = false;
Point2d a = current.getPrev().get(), b = current.get(), c = current.getNext().get();
Line ab = new Line(a, b), bc = new Line(b, c);
double angle = Anglez.dist(ab.aTan2(), bc.aTan2());
if (// corner filter moves corners to same point!
a.distanceSquared(b) < 0.0001 || b.distanceSquared(c) < 0.0001 || // nearly parallel, but small area removed
angle < 0.2 && Math.abs(Mathz.area(a, b, c)) < 50 * P.CON_TOL * P.CON_TOL) // ab.length() + bc.length() > TOL/2 &&
// angle > Math.PI - 0.1 ) // pointy corner
// a.distanceSquared(c) < 0.0001 )
{
current.getPrev().setNext(current.getNext());
current.getNext().setPrev(current.getPrev());
size--;
if (start == current)
loop.start = start = current.getPrev();
again = true;
current = current.getPrev();
} else
current = current.getNext();
} while ((again || current != start) && size > 2);
if (size <= 2)
lit.remove();
}
}
use of org.twak.utils.Line in project chordatlas by twak.
the class Concarnie method mergeConsecutive.
private List<Portal> mergeConsecutive(Set<Line> portals) {
// merge consecutive portals
List<Portal> portalsOut = new ArrayList();
Map<Point2d, Line> starts = new HashMap<>(), ends = new HashMap<>();
for (Line l : portals) {
Line replace = starts.put(l.start, l);
if (replace != null)
portalsOut.add(new Portal(replace));
replace = ends.put(l.end, l);
if (replace != null)
portalsOut.add(new Portal(replace));
}
while (!starts.isEmpty()) {
Portal p = new Portal();
portalsOut.add(p);
Line start = starts.entrySet().iterator().next().getValue(), current = start;
do {
p.lines.add(current);
p.length += current.length();
starts.remove(current.start);
ends.remove(current.end);
current = starts.get(current.end);
} while (current != null);
current = start;
current = ends.get(current.start);
if (current != null)
do {
p.lines.add(0, current);
p.length += current.length();
starts.remove(current.start);
ends.remove(current.end);
current = ends.get(current.start);
} while (current != null);
p.summary = new Line(p.lines.get(0).start, p.lines.get(p.lines.size() - 1).end);
}
Collections.sort(portalsOut, (a, b) -> -Double.compare(a.length, b.length));
return portalsOut;
}
use of org.twak.utils.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.twak.utils.Line in project chordatlas by twak.
the class LineSoup method clique.
public Map<Line, Integer> clique(double highTol, double lowTol) {
int currentC = 0;
Graph2D lookup = new Graph2D(all);
Set<Line> remaining = new LinkedHashSet<>(all), visited = new HashSet<>();
Map<Line, Integer> out = new HashMap<>();
while (!remaining.isEmpty()) {
Set<Line> queue = new LinkedHashSet<>();
queue.add(remaining.iterator().next());
while (!queue.isEmpty()) {
Line l = queue.iterator().next();
queue.remove(l);
remaining.remove(l);
visited.add(l);
out.put(l, currentC);
for (Point2d pt : l.points()) {
// find nearest with parity one
double tol = lookup.get(pt).size() % 2 == 0 ? highTol : lowTol;
for (Line l2 : getNear(pt, tol)) {
if (!visited.contains(l2) && l2.distance(pt, true) < tol)
queue.add(l2);
}
}
}
currentC++;
}
return out;
}
Aggregations