use of org.twak.utils.geom.LinearForm 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.geom.LinearForm 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.geom.LinearForm 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.twak.utils.geom.LinearForm in project chordatlas by twak.
the class Prof method buildProfile.
public static Prof buildProfile(ObjRead mesh, Line3d oLine, Point3d cen, double minH, double maxH, double minD, double maxD, Tweed tweed, Node dbg) {
Prof monotonic = buildProfile(oLine, cen);
Vector3d dir = oLine.dir();
dir.normalize();
Vector3d sliceNormal = new Vector3d(dir.x, 0, dir.z);
LinearForm3D lf = new LinearForm3D(sliceNormal, cen);
List<Line3d> lines = ObjSlice.sliceTri(mesh, lf, 0.5, new Vector3d(-dir.z, 0, dir.x), Math.PI / 2 + 0.1);
// dbg.attachChild( Jme3z.lines( tweed, lines, ColorRGBA.Blue, 2 ) );
Line3d first = null;
double closestStart = Double.MAX_VALUE;
for (Line3d l : lines) {
if (l.start.y > l.end.y)
l.reverse();
double dist = l.distanceSquared(cen);
if (dist < closestStart) {
closestStart = dist;
first = l;
}
}
if (first == null) {
return null;
// lines.clear();
// monotonic.add( cen );
// monotonic.add( new Point3d( cen.x, cen.y - 500, cen.z ) );
} else {
climb(lines, first, monotonic, maxH, true);
climb(lines, first, monotonic, minH, false);
}
{
double tol = 0.2;
minD -= tol;
maxD += tol;
LinearForm min = new LinearForm(Mathz.UP).findC(new Point2d(minD, 0));
LinearForm max = new LinearForm(Mathz.UP).findC(new Point2d(maxD, 0));
for (int i = 0; i < monotonic.size() - 1; i++) {
Point2d a = monotonic.get(i), b = monotonic.get(i + 1);
if (a.x < minD && b.x < minD) {
monotonic.remove(i);
i--;
} else if (a.x < minD) {
monotonic.set(i, new LinearForm(new Line(a, b)).intersect(min));
} else if (b.x < minD) {
monotonic.set(i + 1, new LinearForm(new Line(a, b)).intersect(min));
b.x = minD + Math.ulp(minD);
}
if (a.x > maxD && b.x > maxD) {
monotonic.remove(i);
i--;
} else if (a.x > maxD) {
monotonic.set(i, new LinearForm(new Line(a, b)).intersect(max));
} else if (b.x > maxD) {
monotonic.set(i + 1, new LinearForm(new Line(a, b)).intersect(max));
b.x = maxD - Math.ulp(maxD);
}
}
}
return monotonic;
}
use of org.twak.utils.geom.LinearForm in project chordatlas by twak.
the class CutHoles method cutHoles.
public static void cutHoles(LoopL<Point2d> out, double tol, Map<Point2d, Line> created) {
MultiMap<Boolean, Loop<Point2d>> holeToLoop = new MultiMap<>();
Iterator<Loop<Point2d>> lit = out.iterator();
while (lit.hasNext()) {
// a hole can be a backwards loop...
Loop<Point2d> loop = lit.next();
double area = Loopz.area(loop);
if (Math.abs(area) < tol * tol)
lit.remove();
boolean isHole = area > 0;
holeToLoop.put(isHole, loop);
for (Loop<Point2d> h : loop.holes) {
if (Loopz.area(h) > 0)
h.reverse();
holeToLoop.put(false, h);
}
}
for (Loop<Point2d> hole : holeToLoop.get(false)) {
Point2d origin = new Point2d(Double.MAX_VALUE, 0);
Loopable<Point2d> originL = null;
for (Loopable<Point2d> p : hole.loopableIterator()) {
if (p.get().x < origin.x) {
originL = p;
origin = originL.get();
}
}
LinearForm ray = new LinearForm(0, 1);
ray.findC(origin);
double nearestD = Double.MAX_VALUE;
Loopable<Point2d> nearestL = null;
Point2d nearestH = null;
for (Loop<Point2d> loop : out) {
for (Loopable<Point2d> line : loop.loopableIterator()) {
Point2d a = line.get(), b = line.getNext().get();
if (a.y > origin.y && b.y < origin.y || a.y < origin.y && b.y > origin.y) {
Point2d hit = new Line(a, b).intersects(ray);
if (hit != null && hit.x < origin.x && (origin.x - hit.x < nearestD)) {
nearestD = origin.x - hit.x;
nearestL = line;
nearestH = hit;
}
}
}
}
if (nearestH == null)
System.err.println("failed to find outer ring for hole");
else {
if (created != null)
created.put(nearestH, new Line(nearestL.get(), nearestL.getNext().get()));
Loopable<Point2d> hitPtF = new Loopable<Point2d>(nearestH), hitPtS = new Loopable<Point2d>(nearestH), originL2 = new Loopable(origin);
hitPtS.setNext(nearestL.getNext());
hitPtF.setPrev(nearestL);
hitPtS.getNext().setPrev(hitPtS);
hitPtF.getPrev().setNext(hitPtF);
originL.getNext().setPrev(originL2);
originL2.setNext(originL.getNext());
originL.setNext(hitPtS);
hitPtS.setPrev(originL);
hitPtF.setNext(originL2);
originL2.setPrev(hitPtF);
}
out.remove(hole);
}
}
Aggregations