mirror of
https://git.huckle.dev/Huckles-Minecraft-Archive/jpexs-decompiler.git
synced 2026-06-22 22:37:49 +00:00
Still not working WIP
This commit is contained in:
@@ -904,7 +904,7 @@ public class BezierEdge implements Serializable {
|
||||
BezierEdge q2 = other.toQuad();
|
||||
return QuadOverlap.findQuadraticOverlaps(
|
||||
(Point2D.Double) q1.getBeginPoint(), (Point2D.Double) q1.getControlPoint(), (Point2D.Double) q1.getEndPoint(),
|
||||
(Point2D.Double) q2.getBeginPoint(), (Point2D.Double) q2.getControlPoint(), (Point2D.Double) q2.getEndPoint(), 10
|
||||
(Point2D.Double) q2.getBeginPoint(), (Point2D.Double) q2.getControlPoint(), (Point2D.Double) q2.getEndPoint(), 1
|
||||
);
|
||||
}
|
||||
|
||||
@@ -919,4 +919,14 @@ public class BezierEdge implements Serializable {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public double closestTForPoint(Point2D point) {
|
||||
return new BezierUtils().closestPointToBezier(point, getBeginPoint(), getControlPoint(), getEndPoint());
|
||||
}
|
||||
|
||||
public double distanceFromPoint(Point2D point) {
|
||||
double t = closestTForPoint(point);
|
||||
Point2D p = pointAt(t);
|
||||
return p.distance(point);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ public final class QuadOverlap {
|
||||
// Tolerances: tune as needed
|
||||
final double flatTol = tol * 0.5; // Hausdorff bound for flattening
|
||||
final double distTol = tol; // max allowed normal offset between near-collinear segments
|
||||
final double angleTol = 0.01; // radians; ~0.057° — treat as parallel if below
|
||||
final double angleTol = Math.toRadians(1);//0.01; // radians; ~0.057° — treat as parallel if below
|
||||
|
||||
ParamPoly polyA = flattenQuad(A0, A1, A2, flatTol);
|
||||
ParamPoly polyB = flattenQuad(B0, B1, B2, flatTol);
|
||||
|
||||
@@ -3,12 +3,15 @@ package com.jpexs.decompiler.flash.xfl.shapefixer;
|
||||
import com.jpexs.decompiler.flash.math.BezierEdge;
|
||||
import com.jpexs.decompiler.flash.math.Intersections;
|
||||
import com.jpexs.decompiler.flash.math.OverlapInterval;
|
||||
import com.jpexs.decompiler.flash.types.FILLSTYLEARRAY;
|
||||
import com.jpexs.decompiler.flash.types.LINESTYLEARRAY;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
@@ -22,8 +25,10 @@ import java.util.stream.Collectors;
|
||||
* @author JPEXS
|
||||
*/
|
||||
public class OverlappingEdgesSplitter {
|
||||
|
||||
private static class BezierEdgeWrapper {
|
||||
|
||||
int id;
|
||||
BezierEdge be;
|
||||
int layer;
|
||||
int pathIndex;
|
||||
@@ -89,20 +94,20 @@ public class OverlappingEdgesSplitter {
|
||||
}
|
||||
|
||||
static class BezierEdgePair {
|
||||
|
||||
BezierEdgeWrapper be1;
|
||||
BezierEdgeWrapper be2;
|
||||
BezierEdgeWrapper be2;
|
||||
|
||||
public BezierEdgePair(BezierEdgeWrapper be1, BezierEdgeWrapper be2) {
|
||||
this.be1 = be1;
|
||||
this.be2 = be2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static final class Sweep {
|
||||
|
||||
Map<BezierEdgeWrapper, List<Double>> splitPoints = new LinkedHashMap<>();
|
||||
Map<BezierEdgeWrapper, List<Point2D>> splitPoints2D = new LinkedHashMap<>();
|
||||
Map<BezierEdgeWrapper, List<Point2D>> splitPointsControl = new LinkedHashMap<>();
|
||||
List<OverlapPair> overlapPairs = new ArrayList<>();
|
||||
//Map<BezierEdgePair, List<OverlapInterval>> overlapIntervals = new LinkedHashMap<>();
|
||||
final java.util.Comparator<BezierEdgeWrapper> statusCmp = (e1, e2) -> {
|
||||
int cMinY = Double.compare(e1.minY(), e2.minY());
|
||||
@@ -119,8 +124,8 @@ public class OverlappingEdgesSplitter {
|
||||
static final double EPS = 1e-9;
|
||||
|
||||
void addEdge(BezierEdgeWrapper e) {
|
||||
pq.add(new Event(Event.Type.START, e.minX(), e));
|
||||
pq.add(new Event(Event.Type.END, e.maxX(), e));
|
||||
pq.add(new Event(Event.Type.START, e.minX() - 10, e));
|
||||
pq.add(new Event(Event.Type.END, e.maxX() + 10, e));
|
||||
}
|
||||
|
||||
void run() {
|
||||
@@ -165,19 +170,18 @@ public class OverlappingEdgesSplitter {
|
||||
List<Double> t1Ref = new ArrayList<>();
|
||||
List<Double> t2Ref = new ArrayList<>();
|
||||
List<Point2D> intPoint = new ArrayList<>();
|
||||
|
||||
|
||||
if (!Intersections.rectIntersection(e1.bbox(), e2.bbox())) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
boolean hasIntersections = e1.be.intersects(e2.be, t1Ref, t2Ref, intPoint);
|
||||
List<OverlapInterval> overlapIntervals = e1.be.overlap(e2.be);
|
||||
|
||||
List<OverlapInterval> overlapIntervals = new ArrayList<>();
|
||||
overlapIntervals = e1.be.overlap(e2.be);
|
||||
|
||||
if (!hasIntersections && overlapIntervals.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (!splitPoints.containsKey(e1)) {
|
||||
splitPoints.put(e1, new ArrayList<>());
|
||||
@@ -185,89 +189,43 @@ public class OverlappingEdgesSplitter {
|
||||
if (!splitPoints.containsKey(e2)) {
|
||||
splitPoints.put(e2, new ArrayList<>());
|
||||
}
|
||||
|
||||
if (!splitPoints2D.containsKey(e1)) {
|
||||
splitPoints2D.put(e1, new ArrayList<>());
|
||||
}
|
||||
if (!splitPoints2D.containsKey(e2)) {
|
||||
splitPoints2D.put(e2, new ArrayList<>());
|
||||
}
|
||||
if (!splitPointsControl.containsKey(e1)) {
|
||||
splitPointsControl.put(e1, new ArrayList<>());
|
||||
}
|
||||
if (!splitPointsControl.containsKey(e2)) {
|
||||
splitPointsControl.put(e2, new ArrayList<>());
|
||||
}
|
||||
|
||||
splitPoints.get(e1).addAll(t1Ref);
|
||||
splitPoints.get(e2).addAll(t2Ref);
|
||||
splitPoints2D.get(e1).addAll(intPoint);
|
||||
splitPoints2D.get(e2).addAll(intPoint);
|
||||
for (int i = 0; i < intPoint.size(); i++) {
|
||||
splitPointsControl.get(e1).add(null);
|
||||
splitPointsControl.get(e2).add(null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (!overlapIntervals.isEmpty()) {
|
||||
for (OverlapInterval interval : overlapIntervals) {
|
||||
for (OverlapInterval interval : overlapIntervals) {
|
||||
if (interval.t0 == interval.t1) {
|
||||
continue;
|
||||
continue;
|
||||
}
|
||||
BezierEdge middle;
|
||||
if (interval.t0 == 1.0) {
|
||||
if (interval.t1 == 0.0) {
|
||||
middle = e1.be;
|
||||
} else {
|
||||
middle = e1.be.split(Arrays.asList(interval.t1)).get(0);
|
||||
}
|
||||
} else {
|
||||
List<BezierEdge> splitted = e1.be.split(Arrays.asList(interval.t0, interval.t1));
|
||||
middle = splitted.get(1);
|
||||
}
|
||||
|
||||
|
||||
overlapPairs.add(new OverlapPair(e1.id, e2.id, interval.t0, interval.t1, interval.u0, interval.u1));
|
||||
splitPoints.get(e1).add(interval.t0);
|
||||
splitPoints.get(e1).add(interval.t1);
|
||||
|
||||
splitPoints.get(e2).add(interval.u0);
|
||||
splitPoints.get(e2).add(interval.u1);
|
||||
|
||||
|
||||
System.err.println("Overlapping " + e1.be.toSvg()+" AND " + e2.be.toSvg()+" by " + middle.toSvg());
|
||||
|
||||
splitPoints2D.get(e1).add(middle.getBeginPoint());
|
||||
splitPoints2D.get(e1).add(middle.getEndPoint());
|
||||
splitPointsControl.get(e1).add(middle.getControlPoint());
|
||||
splitPointsControl.get(e1).add(middle.getControlPoint());
|
||||
|
||||
splitPoints2D.get(e2).add(middle.getBeginPoint());
|
||||
splitPoints2D.get(e2).add(middle.getEndPoint());
|
||||
splitPointsControl.get(e2).add(middle.getControlPoint());
|
||||
splitPointsControl.get(e2).add(middle.getControlPoint());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private static class TPoint {
|
||||
double t;
|
||||
Point2D point;
|
||||
Point2D controlPoint;
|
||||
|
||||
public TPoint(double t, Point2D point, Point2D controlPoint) {
|
||||
this.t = t;
|
||||
this.point = point;
|
||||
this.controlPoint = controlPoint;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
private void handleBewList(List<BezierEdgeWrapper> bewList, List<Layer> layers, Map<Integer, List<OutputEdge>> outs) {
|
||||
|
||||
private void handleBewList(List<BezierEdgeWrapper> bewList, List<Layer> layers) {
|
||||
Map<Integer, List<BezierEdgeWrapper>> bewMap = bewList.stream()
|
||||
.collect(Collectors.groupingBy(b -> b.layer));
|
||||
|
||||
for (Map.Entry<Integer, List<BezierEdgeWrapper>> entry : bewMap.entrySet()) {
|
||||
|
||||
int id = 0;
|
||||
for (BezierEdgeWrapper bew : entry.getValue()) {
|
||||
bew.id = id++;
|
||||
}
|
||||
|
||||
int layer = entry.getKey();
|
||||
Map<Integer, List<Fragment>> splittedEdges = new HashMap<>();
|
||||
|
||||
Set<BezierEdgeWrapper> bewsToIgnore = new LinkedHashSet<>();
|
||||
|
||||
Map<BezierEdge, BezierEdgeWrapper> existingEdges = new HashMap<>();
|
||||
@@ -290,60 +248,22 @@ public class OverlappingEdgesSplitter {
|
||||
existingEdges.put(rev, bew1);
|
||||
}
|
||||
|
||||
//eliminate duplicates
|
||||
/*for (BezierEdgeWrapper bew1 : entry.getValue()) {
|
||||
for (BezierEdgeWrapper bew2 : entry.getValue()) {
|
||||
if (bew1 != bew2) {
|
||||
if (bew1.beOriginal.equals(bew2.beOriginal)
|
||||
|| bew1.beOriginal.equalsReverse(bew2.beOriginal)) {
|
||||
bewsToIgnore.add(bew1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
boolean useSweep = true;
|
||||
|
||||
Map<BezierEdgeWrapper, List<Double>> splitPointsMap = new LinkedHashMap<>();
|
||||
Map<BezierEdgeWrapper, List<Point2D>> splitPoints2DMap = new LinkedHashMap<>();
|
||||
Map<BezierEdgeWrapper, List<Point2D>> splitPointsControlMap = new LinkedHashMap<>();
|
||||
List<OverlapPair> overlapPairs = new ArrayList<>();
|
||||
|
||||
if (useSweep) {
|
||||
Sweep sweep = new Sweep();
|
||||
for (BezierEdgeWrapper bew : entry.getValue()) {
|
||||
if (bewsToIgnore.contains(bew)) {
|
||||
continue;
|
||||
}
|
||||
sweep.addEdge(bew);
|
||||
}
|
||||
sweep.run();
|
||||
splitPointsMap = sweep.splitPoints;
|
||||
splitPoints2DMap = sweep.splitPoints2D;
|
||||
splitPointsControlMap = sweep.splitPointsControl;
|
||||
} else {
|
||||
|
||||
for (BezierEdgeWrapper bew1 : entry.getValue()) {
|
||||
for (BezierEdgeWrapper bew2 : entry.getValue()) {
|
||||
if (bew1 != bew2) {
|
||||
List<Double> t1Ref = new ArrayList<>();
|
||||
List<Double> t2Ref = new ArrayList<>();
|
||||
List<Point2D> intPoints = new ArrayList<>();
|
||||
if (bew1.be.intersects(bew2.be, t1Ref, t2Ref, intPoints)) {
|
||||
if (!splitPointsMap.containsKey(bew1)) {
|
||||
splitPointsMap.put(bew1, new ArrayList<>());
|
||||
}
|
||||
splitPointsMap.get(bew1).addAll(t1Ref);
|
||||
|
||||
if (!splitPointsMap.containsKey(bew2)) {
|
||||
splitPointsMap.put(bew2, new ArrayList<>());
|
||||
}
|
||||
splitPointsMap.get(bew2).addAll(t2Ref);
|
||||
}
|
||||
}
|
||||
}
|
||||
Sweep sweep = new Sweep();
|
||||
for (BezierEdgeWrapper bew : entry.getValue()) {
|
||||
if (bewsToIgnore.contains(bew)) {
|
||||
continue;
|
||||
}
|
||||
sweep.addEdge(bew);
|
||||
}
|
||||
sweep.run();
|
||||
splitPointsMap = sweep.splitPoints;
|
||||
overlapPairs = sweep.overlapPairs;
|
||||
|
||||
List<BezierEdgeWrapper> splittedBewList = new ArrayList<>(splitPointsMap.keySet());
|
||||
List<Intersection> intersectionsList = new ArrayList<>();
|
||||
|
||||
splittedBewList.sort((BezierEdgeWrapper o1, BezierEdgeWrapper o2) -> {
|
||||
int dShapeIndex = o1.pathIndex - o2.pathIndex;
|
||||
@@ -357,70 +277,498 @@ public class OverlappingEdgesSplitter {
|
||||
return System.identityHashCode(o1) - System.identityHashCode(o2);
|
||||
});
|
||||
|
||||
Set<Integer> splittedIds = new HashSet<>();
|
||||
|
||||
for (int i = splittedBewList.size() - 1; i >= 0; i--) {
|
||||
BezierEdgeWrapper bew = splittedBewList.get(i);
|
||||
|
||||
List<Double> splitT = splitPointsMap.get(bew);
|
||||
List<Point2D> splitPoint = splitPoints2DMap.get(bew);
|
||||
List<Point2D> splitControls = splitPointsControlMap.get(bew);
|
||||
|
||||
List<TPoint> splits = new ArrayList<>();
|
||||
for (int j = 0; j < splitT.size(); j++) {
|
||||
splits.add(new TPoint(splitT.get(j), splitPoint.get(j), splitControls.get(j)));
|
||||
splittedIds.add(bew.id);
|
||||
|
||||
if (bew.be.getBeginPoint().getX() == 2755.0) {
|
||||
System.err.println("xxx");
|
||||
}
|
||||
|
||||
splits.sort((a, b) -> Double.compare(a.t, b.t));
|
||||
final double epsT = 1e-9; // param eps
|
||||
|
||||
List<Double> splitT = splitPointsMap.get(bew);
|
||||
|
||||
splitT.sort((a, b) -> Double.compare(a, b));
|
||||
|
||||
BezierEdge be = bew.be;
|
||||
List<Double> realSplitT = new ArrayList<>();
|
||||
for (TPoint tp : splits) {
|
||||
if (tp.t == 0.0 || tp.t == 1.0) {
|
||||
for (Double t : splitT) {
|
||||
if (t < epsT || t >= 1.0 - epsT) {
|
||||
continue;
|
||||
}
|
||||
|
||||
realSplitT.add(tp.t);
|
||||
realSplitT.add(t);
|
||||
}
|
||||
|
||||
if (realSplitT.isEmpty()) {
|
||||
splittedIds.remove(bew.id);
|
||||
continue;
|
||||
}
|
||||
|
||||
List<BezierEdge> splitted = be.split(realSplitT);
|
||||
if (splits.get(0).t != 0.0) {
|
||||
splits.add(0, new TPoint(0.0, bew.be.getBeginPoint(), null));
|
||||
if (splitT.get(0) != 0.0) {
|
||||
splitT.add(0, 0.0);
|
||||
}
|
||||
if (splits.get(splits.size() - 1).t != 1.0) {
|
||||
splits.add(new TPoint(1.0, bew.be.getEndPoint(), null));
|
||||
if (splitT.get(splitT.size() - 1) != 1.0) {
|
||||
splitT.add(1.0);
|
||||
}
|
||||
|
||||
List<Double> uniq = new ArrayList<>();
|
||||
double prev = Double.NaN;
|
||||
|
||||
int p = 0;
|
||||
for (int j = 0; j < splits.size(); j++) {
|
||||
if (splits.get(j).t == 0.0 || splits.get(j).t == 1.0) {
|
||||
continue;
|
||||
}
|
||||
splitted.get(p).setBeginPoint(splits.get(j - 1).point);
|
||||
splitted.get(p).setEndPoint(splits.get(j).point);
|
||||
if (splits.get(j - 1).controlPoint == splits.get(j).controlPoint && splits.get(j).controlPoint != null) {
|
||||
splitted.get(p).setControlPoint(splits.get(j).controlPoint);
|
||||
for (Double t : splitT) {
|
||||
if (uniq.isEmpty() || Math.abs(t - prev) > epsT) {
|
||||
uniq.add(Math.max(0, Math.min(1, t)));
|
||||
prev = t;
|
||||
Point2D pat = bew.be.pointAt(t);
|
||||
intersectionsList.add(new Intersection(bew.id, t, pat.getX(), pat.getY()));
|
||||
}
|
||||
p++;
|
||||
}
|
||||
layers.get(bew.layer).paths.get(bew.pathIndex).edges.remove(bew.edgeIndex);
|
||||
int pos = 0;
|
||||
for (BezierEdge bes : splitted) {
|
||||
layers.get(bew.layer).paths.get(bew.pathIndex).edges.add(bew.edgeIndex + pos, bes);
|
||||
pos++;
|
||||
|
||||
List<Fragment> frs = new ArrayList<>();
|
||||
for (int j = 0; j + 1 < uniq.size(); j++) {
|
||||
double t0 = uniq.get(j);
|
||||
double t1 = uniq.get(j + 1);
|
||||
if (t1 - t0 <= epsT) {
|
||||
continue; // skip zero-length
|
||||
}
|
||||
frs.add(new Fragment(new Edge(bew.id, bew.be, bew.pathIndex), t0, t1, false, bew.pathIndex));
|
||||
}
|
||||
splittedEdges.put(bew.id, frs);
|
||||
}
|
||||
|
||||
Map<Integer, List<FragRef>> comps = buildOverlapComponents(splittedEdges, overlapPairs);
|
||||
|
||||
List<OutputEdge> outSplitted = normalizeOverlaps(splittedEdges, comps);
|
||||
|
||||
remapIntersections(intersectionsList, splittedEdges, comps, outSplitted, 1);
|
||||
|
||||
List<OutputEdge> allOut = new ArrayList<>();
|
||||
System.err.println("NON Splitted edges:");
|
||||
for (BezierEdgeWrapper bew : entry.getValue()) {
|
||||
if (!splittedIds.contains(bew.id)) {
|
||||
allOut.add(new OutputEdge(new Edge(bew.id, bew.be, bew.pathIndex), bew.pathIndex));
|
||||
System.err.println(bew.be.toSvg());
|
||||
}
|
||||
}
|
||||
|
||||
System.err.println("Splitted edges:");
|
||||
for (OutputEdge ie : outSplitted) {
|
||||
System.err.println("" + ie.geom.be.toSvg());
|
||||
}
|
||||
|
||||
allOut.addAll(outSplitted);
|
||||
|
||||
snapEndpoints(allOut, 2);
|
||||
|
||||
outs.put(layer, allOut);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Intersection {
|
||||
|
||||
public final int edgeId;
|
||||
public final double t; // original param on that edge
|
||||
public final double x, y;
|
||||
|
||||
public Intersection(int e, double t, double x, double y) {
|
||||
this.edgeId = e;
|
||||
this.t = t;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class IntersectionOut {
|
||||
|
||||
public final OutputEdge edge; // where it ended up
|
||||
public final double u; // local param in [0,1] on edge.geom
|
||||
public final double x, y; // (optional) exact point
|
||||
|
||||
public IntersectionOut(OutputEdge e, double u, double x, double y) {
|
||||
this.edge = e;
|
||||
this.u = u;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
}
|
||||
|
||||
public static List<IntersectionOut> remapIntersections(
|
||||
List<Intersection> oldInts,
|
||||
Map<Integer, List<Fragment>> fragsPerEdge,
|
||||
Map<Integer, List<FragRef>> comps,
|
||||
List<OutputEdge> outputs,
|
||||
double tol) {
|
||||
|
||||
// Build fast lookup: for each original fragment tell which OutputEdge instance represents it (by fillStyle).
|
||||
// Multiple participants in a component create multiple OutputEdges with same geometry; we pick by matching fillStyle.
|
||||
Map<String, OutputEdge> rep = new HashMap<>();
|
||||
|
||||
// Mark components first
|
||||
Set<String> compMembers = new HashSet<>();
|
||||
for (Map.Entry<Integer, List<FragRef>> e : comps.entrySet()) {
|
||||
List<FragRef> refs = e.getValue();
|
||||
// canonical geometry is from min ref:
|
||||
FragRef canRef = refs.stream()
|
||||
.min((a, b) -> {
|
||||
int c = Integer.compare(a.edgeId, b.edgeId);
|
||||
return c != 0 ? c : Integer.compare(a.idxInEdge, b.idxInEdge);
|
||||
}).get();
|
||||
Fragment canFrag = fragsPerEdge.get(canRef.edgeId).get(canRef.idxInEdge);
|
||||
Edge canonical = canFrag.toEdge();
|
||||
|
||||
// find all OutputEdges built from this component:
|
||||
List<OutputEdge> outs = new ArrayList<>();
|
||||
for (OutputEdge oe : outputs) {
|
||||
// Heuristic: same object reference? Not guaranteed. Use geometry equality within tol.
|
||||
if (sameGeometry(oe.geom, canonical, tol)) {
|
||||
outs.add(oe);
|
||||
}
|
||||
}
|
||||
// Map each member (edgeId:idx) to corresponding OutputEdge by fill style
|
||||
for (FragRef r : refs) {
|
||||
Fragment f = fragsPerEdge.get(r.edgeId).get(r.idxInEdge);
|
||||
compMembers.add(r.edgeId + ":" + r.idxInEdge);
|
||||
OutputEdge match = outs.stream()
|
||||
.filter(oe -> oe.pathIndex == f.pathIndex)
|
||||
.findFirst().orElse(null);
|
||||
if (match != null) {
|
||||
rep.put(r.edgeId + ":" + r.idxInEdge, match);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For non-overlap fragments, map to their own OutputEdge (geometry equals fragment)
|
||||
for (Map.Entry<Integer, List<Fragment>> e : fragsPerEdge.entrySet()) {
|
||||
int edgeId = e.getKey();
|
||||
List<Fragment> L = e.getValue();
|
||||
for (int i = 0; i < L.size(); i++) {
|
||||
String key = edgeId + ":" + i;
|
||||
if (compMembers.contains(key)) {
|
||||
continue;
|
||||
}
|
||||
Fragment f = L.get(i);
|
||||
// Find OutputEdge with same geometry & fill
|
||||
OutputEdge match = outputs.stream()
|
||||
.filter(oe -> oe.pathIndex == f.pathIndex && sameGeometry(oe.geom, f.toEdge(), tol))
|
||||
.findFirst().orElse(null);
|
||||
if (match != null) {
|
||||
rep.put(key, match);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now convert each original intersection
|
||||
List<IntersectionOut> result = new ArrayList<>();
|
||||
for (Intersection in : oldInts) {
|
||||
List<Fragment> frs = fragsPerEdge.get(in.edgeId);
|
||||
if (frs == null) {
|
||||
continue;
|
||||
}
|
||||
// locate fragment [t0,t1] containing t
|
||||
Fragment f = locateFragment(frs, in.t);
|
||||
if (f == null) {
|
||||
continue;
|
||||
}
|
||||
OutputEdge dst = rep.get(in.edgeId + ":" + frs.indexOf(f));
|
||||
if (dst == null) {
|
||||
continue;
|
||||
}
|
||||
// convert original t to local u on fragment
|
||||
double uLocal = (in.t - f.t0) / (f.t1 - f.t0);
|
||||
if (f.reversed) {
|
||||
uLocal = 1.0 - uLocal;
|
||||
}
|
||||
|
||||
result.add(new IntersectionOut(dst, uLocal, in.x, in.y));
|
||||
}
|
||||
|
||||
// optional: deduplicate by (dst, rounded point)
|
||||
return result;
|
||||
}
|
||||
|
||||
static Fragment locateFragment(List<Fragment> fs, double t) {
|
||||
// Binary search over sorted fragments by t0/t1
|
||||
int lo = 0, hi = fs.size() - 1;
|
||||
final double eps = 1e-12;
|
||||
while (lo <= hi) {
|
||||
int mid = (lo + hi) / 2;
|
||||
Fragment f = fs.get(mid);
|
||||
if (t < f.t0 - eps) {
|
||||
hi = mid - 1;
|
||||
} else if (t > f.t1 + eps) {
|
||||
lo = mid + 1;
|
||||
} else {
|
||||
return f;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static boolean sameGeometry(Edge a, Edge b, double tol) {
|
||||
// As with geometricallyCoincident but cheaper thresholds.
|
||||
return geometricallyCoincident(
|
||||
new Fragment(a, 0, 1, false, a.pathIndex),
|
||||
new Fragment(b, 0, 1, false, b.pathIndex),
|
||||
tol);
|
||||
}
|
||||
|
||||
public static List<OutputEdge> normalizeOverlaps(
|
||||
Map<Integer, List<Fragment>> fragsPerEdge,
|
||||
Map<Integer, List<FragRef>> comps) {
|
||||
|
||||
List<OutputEdge> out = new ArrayList<>();
|
||||
// Keep a mark for fragments which belong to any overlap component
|
||||
Set<String> inComp = new HashSet<>();
|
||||
for (List<FragRef> refs : comps.values()) {
|
||||
// pick canonical: smallest (edgeId, idx)
|
||||
FragRef canRef = refs.stream()
|
||||
.min((a, b) -> {
|
||||
int c = Integer.compare(a.edgeId, b.edgeId);
|
||||
return c != 0 ? c : Integer.compare(a.idxInEdge, b.idxInEdge);
|
||||
}).get();
|
||||
Fragment canFrag = fragsPerEdge.get(canRef.edgeId).get(canRef.idxInEdge);
|
||||
Edge canonicalGeom = canFrag.toEdge();
|
||||
|
||||
for (FragRef r : refs) {
|
||||
Fragment f = fragsPerEdge.get(r.edgeId).get(r.idxInEdge);
|
||||
inComp.add(r.edgeId + ":" + r.idxInEdge);
|
||||
|
||||
// Align orientation (optional – if you require identical direction):
|
||||
Edge g = canonicalGeom;
|
||||
// If you must ensure f's direction equals canonical, just use canonical as-is.
|
||||
// If you want "two same segments", the geometry object can literally be same.
|
||||
|
||||
out.add(new OutputEdge(g, f.pathIndex));
|
||||
}
|
||||
}
|
||||
|
||||
// Non-overlapped fragments go through unchanged
|
||||
for (Map.Entry<Integer, List<Fragment>> e : fragsPerEdge.entrySet()) {
|
||||
List<Fragment> fs = e.getValue();
|
||||
for (int i = 0; i < fs.size(); i++) {
|
||||
String key = e.getKey() + ":" + i;
|
||||
if (inComp.contains(key)) {
|
||||
continue; // already replaced
|
||||
}
|
||||
Fragment f = fs.get(i);
|
||||
out.add(new OutputEdge(f.toEdge(), f.pathIndex));
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
public static class OutputEdge {
|
||||
|
||||
public final Edge geom; // canonical geometry
|
||||
public final int pathIndex; // one per participant (you will have N of these)
|
||||
|
||||
public OutputEdge(Edge g, int f) {
|
||||
this.geom = g;
|
||||
this.pathIndex = f;
|
||||
}
|
||||
}
|
||||
|
||||
private static Map<Integer, List<FragRef>> buildOverlapComponents(
|
||||
Map<Integer, List<Fragment>> fragsPerEdge,
|
||||
List<OverlapPair> overlaps) {
|
||||
|
||||
// Index all fragments by a running id for DSU
|
||||
List<FragRef> allRefs = new ArrayList<>();
|
||||
Map<Integer, Integer> baseIndex = new HashMap<>(); // edgeId -> start index
|
||||
int cnt = 0;
|
||||
for (Map.Entry<Integer, List<Fragment>> e : fragsPerEdge.entrySet()) {
|
||||
baseIndex.put(e.getKey(), cnt);
|
||||
for (int i = 0; i < e.getValue().size(); i++) {
|
||||
allRefs.add(new FragRef(e.getKey(), i));
|
||||
}
|
||||
cnt += e.getValue().size();
|
||||
}
|
||||
|
||||
DSU dsu = new DSU(cnt);
|
||||
|
||||
// For each reported overlap, link fragments whose [t0,t1] lies inside.
|
||||
for (OverlapPair op : overlaps) {
|
||||
List<Fragment> A = fragsPerEdge.get(op.edgeA);
|
||||
List<Fragment> B = fragsPerEdge.get(op.edgeB);
|
||||
if (A == null || B == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
double aMin = Math.min(op.a0, op.a1);
|
||||
double aMax = Math.max(op.a0, op.a1);
|
||||
double bMin = Math.min(op.b0, op.b1);
|
||||
double bMax = Math.max(op.b0, op.b1);
|
||||
|
||||
final double epsT = 1e-9;
|
||||
|
||||
for (int i = 0; i < A.size(); i++) {
|
||||
Fragment fa = A.get(i);
|
||||
if (fa.t0 + epsT >= aMax || fa.t1 - epsT <= aMin) {
|
||||
continue; // no overlap in param
|
||||
}
|
||||
for (int j = 0; j < B.size(); j++) {
|
||||
Fragment fb = B.get(j);
|
||||
if (fb.t0 + epsT >= bMax || fb.t1 - epsT <= bMin) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Optional: geometric check with tolerance to avoid false positives:
|
||||
if (!geometricallyCoincident(fa, fb, /*tol=*/ 10)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int ia = baseIndex.get(op.edgeA) + i;
|
||||
int ib = baseIndex.get(op.edgeB) + j;
|
||||
dsu.u(ia, ib);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Gather components
|
||||
Map<Integer, List<FragRef>> comps = new HashMap<>();
|
||||
for (int k = 0; k < allRefs.size(); k++) {
|
||||
int root = dsu.f(k);
|
||||
comps.computeIfAbsent(root, key -> new ArrayList<>()).add(allRefs.get(k));
|
||||
}
|
||||
return comps;
|
||||
}
|
||||
|
||||
// Geometric equality test within tolerance using sampling
|
||||
static boolean geometricallyCoincident(Fragment f1, Fragment f2, double tol) {
|
||||
// If line-line: check collinearity + projection overlap more strictly.
|
||||
// For general case: symmetric sample Hausdorff approx.
|
||||
final int S = 24;
|
||||
double maxd = 0;
|
||||
for (int i = 0; i <= S; i++) {
|
||||
double u = (double) i / S;
|
||||
Point2D p = f1.toEdge().be.pointAt(u);
|
||||
double d = distancePointToEdge(p, f2.toEdge());
|
||||
maxd = Math.max(maxd, d);
|
||||
if (maxd > tol) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i <= S; i++) {
|
||||
double u = (double) i / S;
|
||||
Point2D p = f2.toEdge().be.pointAt(u);
|
||||
double d = distancePointToEdge(p, f1.toEdge());
|
||||
maxd = Math.max(maxd, d);
|
||||
if (maxd > tol) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Distance from point to edge (line or quadratic), via projection / sampling.
|
||||
static double distancePointToEdge(Point2D p, Edge e) {
|
||||
return e.be.distanceFromPoint(p);
|
||||
}
|
||||
|
||||
public static void snapEndpoints(List<OutputEdge> edges, double snapTol) {
|
||||
// Collect all endpoints
|
||||
List<Point2D> pts = new ArrayList<>();
|
||||
for (OutputEdge e : edges) {
|
||||
Point2D a = e.geom.be.getBeginPoint();
|
||||
Point2D c = e.geom.be.getControlPoint();
|
||||
Point2D b = e.geom.be.getEndPoint();
|
||||
pts.add(a);
|
||||
pts.add(c);
|
||||
pts.add(b);
|
||||
}
|
||||
|
||||
// Build clusters of points within snapTol (simple O(n^2) is often OK; use grid hash if needed)
|
||||
int n = pts.size();
|
||||
int[] parent = new int[n];
|
||||
for (int i = 0; i < n; i++) {
|
||||
parent[i] = i;
|
||||
}
|
||||
java.util.function.IntUnaryOperator find = new java.util.function.IntUnaryOperator() {
|
||||
@Override
|
||||
public int applyAsInt(int x) {
|
||||
return parent[x] == x ? x : (parent[x] = applyAsInt(parent[x]));
|
||||
}
|
||||
};
|
||||
java.util.function.BiConsumer<Integer, Integer> unite = (ii, jj) -> {
|
||||
int i = find.applyAsInt(ii), j = find.applyAsInt(jj);
|
||||
if (i != j) {
|
||||
parent[j] = i;
|
||||
}
|
||||
};
|
||||
|
||||
double tol2 = snapTol * snapTol;
|
||||
for (int i = 0; i < n; i++) {
|
||||
Point2D pi = pts.get(i);
|
||||
for (int j = i + 1; j < n; j++) {
|
||||
Point2D pj = pts.get(j);
|
||||
double dx = pi.getX() - pj.getX();
|
||||
double dy = pi.getY() - pj.getY();
|
||||
if (dx * dx + dy * dy <= tol2) {
|
||||
unite.accept(i, j);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compute cluster representatives as arithmetic mean for stability
|
||||
Map<Integer, Point2D> rep = new HashMap<>();
|
||||
Map<Integer, Integer> cnt = new HashMap<>();
|
||||
for (int i = 0; i < n; i++) {
|
||||
int r = find.applyAsInt(i);
|
||||
Point2D p = pts.get(i);
|
||||
rep.compute(r, (k, v) -> {
|
||||
if (v == null) {
|
||||
return new Point2D.Double(p.getX(), p.getY());
|
||||
}
|
||||
v.setLocation(v.getX() + p.getX(), v.getY() + p.getY());
|
||||
return v;
|
||||
});
|
||||
cnt.merge(r, 1, Integer::sum);
|
||||
}
|
||||
for (Map.Entry<Integer, Point2D> e : rep.entrySet()) {
|
||||
int c = cnt.get(e.getKey());
|
||||
e.getValue().setLocation(e.getValue().getX() / c, e.getValue().getY() / c);;
|
||||
}
|
||||
|
||||
// Write snapped endpoints back into edges
|
||||
int idx = 0;
|
||||
for (OutputEdge e : edges) {
|
||||
int rA = find.applyAsInt(idx++);
|
||||
int rC = find.applyAsInt(idx++);
|
||||
int rB = find.applyAsInt(idx++);
|
||||
Point2D A = rep.get(rA);
|
||||
Point2D C = rep.get(rC);
|
||||
Point2D B = rep.get(rB);
|
||||
// Rebuild edge with snapped endpoints (keep control point for quads via De Casteljau split at 0/1)
|
||||
if (!e.geom.be.isQuad()) {
|
||||
// create new LineEdge with same fill/id but snapped ends
|
||||
e.geom.be.setBeginPoint(A);
|
||||
e.geom.be.setEndPoint(B);
|
||||
} else {
|
||||
double dx0 = A.getX() - e.geom.be.getBeginPoint().getX();
|
||||
double dy0 = A.getY() - e.geom.be.getBeginPoint().getY();
|
||||
double dx1 = B.getX() - e.geom.be.getEndPoint().getX();
|
||||
double dy1 = B.getY() - e.geom.be.getEndPoint().getY();
|
||||
|
||||
e.geom.be.setBeginPoint(A);
|
||||
/*e.geom.be.setControlPoint(new Point2D.Double(
|
||||
e.geom.be.getControlPoint().getX() + 0.5 * (dx0 + dx1),
|
||||
e.geom.be.getControlPoint().getY() + 0.5 * (dy0 + dy1)
|
||||
));*/
|
||||
e.geom.be.setControlPoint(C);
|
||||
e.geom.be.setEndPoint(B);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void splitOverlappingEdges(
|
||||
List<Layer> layers
|
||||
) {
|
||||
List<BezierEdgeWrapper> strokesBewList = new ArrayList<>();
|
||||
List<BezierEdgeWrapper> fillsBewList = new ArrayList<>();
|
||||
|
||||
|
||||
for (int layer = 0; layer < layers.size(); layer++) {
|
||||
for (int p = 0; p < layers.get(layer).paths.size(); p++) {
|
||||
Path path = layers.get(layer).paths.get(p);
|
||||
@@ -435,11 +783,43 @@ public class OverlappingEdgesSplitter {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleBewList(strokesBewList, layers);
|
||||
handleBewList(fillsBewList, layers);
|
||||
|
||||
for (int layer = 0; layer < layers.size(); layer++) {
|
||||
Map<Integer, List<OutputEdge>> outStrokes = new HashMap<>();
|
||||
Map<Integer, List<OutputEdge>> outFills = new HashMap<>();
|
||||
|
||||
handleBewList(strokesBewList, layers, outStrokes);
|
||||
handleBewList(fillsBewList, layers, outFills);
|
||||
|
||||
Map<Integer, List<OutputEdge>> outAll = outFills;
|
||||
for (int layer : outStrokes.keySet()) {
|
||||
if (!outAll.containsKey(layer)) {
|
||||
outAll.put(layer, new ArrayList<>());
|
||||
}
|
||||
outAll.get(layer).addAll(outStrokes.get(layer));
|
||||
}
|
||||
|
||||
for (int i = 0; i < layers.size(); i++) {
|
||||
Layer layer = layers.get(i);
|
||||
Map<Integer, List<OutputEdge>> pathIndexToEdge = outAll.get(i).stream()
|
||||
.collect(Collectors.groupingBy(b -> b.pathIndex));
|
||||
for (int p = 0; p < layer.paths.size(); p++) {
|
||||
Path path = layer.paths.get(p);
|
||||
path.edges.clear();
|
||||
if (pathIndexToEdge.containsKey(p)) {
|
||||
for (OutputEdge oe : pathIndexToEdge.get(p)) {
|
||||
path.edges.add(oe.geom.be);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int p = 0; p < layer.paths.size(); p++) {
|
||||
if (layer.paths.get(p).edges.isEmpty()) {
|
||||
layer.paths.remove(p);
|
||||
p--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*for (int layer = 0; layer < layers.size(); layer++) {
|
||||
for (int p = 0; p < layers.get(layer).paths.size(); p++) {
|
||||
Path path = layers.get(layer).paths.get(p);
|
||||
for (int e = 0; e < path.edges.size(); e++) {
|
||||
@@ -447,6 +827,156 @@ public class OverlappingEdgesSplitter {
|
||||
be1.shrinkToLine();
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
private static class Edge {
|
||||
|
||||
int id;
|
||||
BezierEdge be;
|
||||
int pathIndex;
|
||||
|
||||
public Edge(int id, BezierEdge be, int fillStyle) {
|
||||
this.id = id;
|
||||
this.be = be;
|
||||
this.pathIndex = fillStyle;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class Fragment {
|
||||
|
||||
Edge source;
|
||||
double t0;
|
||||
double t1;
|
||||
boolean reversed;
|
||||
int pathIndex;
|
||||
|
||||
public Fragment(Edge source, double t0, double t1, boolean reversed, int fillStyle) {
|
||||
this.source = source;
|
||||
this.t0 = t0;
|
||||
this.t1 = t1;
|
||||
this.reversed = reversed;
|
||||
this.pathIndex = fillStyle;
|
||||
}
|
||||
|
||||
public Edge toEdge() {
|
||||
return new Edge(source.id, toBezierEdge(), pathIndex);
|
||||
}
|
||||
|
||||
public BezierEdge toBezierEdge() {
|
||||
if (t0 == 0.0) {
|
||||
if (t1 == 1.0) {
|
||||
return source.be;
|
||||
}
|
||||
return source.be.split(Arrays.asList(t1)).get(0);
|
||||
}
|
||||
if (t1 == 1.0) {
|
||||
return source.be.split(Arrays.asList(t0)).get(1);
|
||||
}
|
||||
|
||||
return source.be.split(Arrays.asList(t0, t1)).get(1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class OverlapPair {
|
||||
|
||||
int edgeA;
|
||||
int edgeB;
|
||||
double a0;
|
||||
double a1;
|
||||
double b0;
|
||||
double b1; // can be a0>a1 or b0>b1 if opposite direction
|
||||
|
||||
public OverlapPair(int edgeA, int edgeB, double a0, double a1, double b0, double b1) {
|
||||
this.edgeA = edgeA;
|
||||
this.edgeB = edgeB;
|
||||
this.a0 = a0;
|
||||
this.a1 = a1;
|
||||
this.b0 = b0;
|
||||
this.b1 = b1;
|
||||
}
|
||||
}
|
||||
|
||||
// Tiny union-find
|
||||
static final class DSU {
|
||||
|
||||
int[] p;
|
||||
int[] r;
|
||||
|
||||
DSU(int n) {
|
||||
p = new int[n];
|
||||
r = new int[n];
|
||||
for (int i = 0; i < n; i++) {
|
||||
p[i] = i;
|
||||
}
|
||||
}
|
||||
|
||||
int f(int x) {
|
||||
return p[x] == x ? x : (p[x] = f(p[x]));
|
||||
}
|
||||
|
||||
void u(int a, int b) {
|
||||
a = f(a);
|
||||
b = f(b);
|
||||
if (a == b) {
|
||||
return;
|
||||
}
|
||||
if (r[a] < r[b]) {
|
||||
int t = a;
|
||||
a = b;
|
||||
b = t;
|
||||
}
|
||||
p[b] = a;
|
||||
if (r[a] == r[b]) {
|
||||
r[a]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static final class FragRef {
|
||||
|
||||
final int edgeId;
|
||||
final int idxInEdge; // index in that edge's fragment list
|
||||
|
||||
FragRef(int e, int i) {
|
||||
edgeId = e;
|
||||
idxInEdge = i;
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
OverlappingEdgesSplitter sp = new OverlappingEdgesSplitter();
|
||||
List<Layer> layers = new ArrayList<>();
|
||||
Layer lay = new Layer();
|
||||
lay.fillStyleArray = new FILLSTYLEARRAY();
|
||||
lay.lineStyleArray = new LINESTYLEARRAY();
|
||||
lay.paths = new ArrayList<>();
|
||||
Path p = new Path();
|
||||
|
||||
//M 105 1238 Q -84 1185 -273 1133
|
||||
//M -456 1085L -452 1087Q -229 1144 -49 1195
|
||||
|
||||
p.edges.add(new BezierEdge(105,1238 , -84 ,1185 ,-273, 1133));
|
||||
|
||||
p.edges.add(new BezierEdge(-456, 1085, -452, 1087));
|
||||
p.edges.add(new BezierEdge(-452,1087, -229, 1144, -49, 1195));
|
||||
lay.paths.add(p);
|
||||
layers.add(lay);
|
||||
|
||||
System.err.println("BEFORE:");
|
||||
for (Path path : layers.get(0).paths) {
|
||||
System.err.println("" + path.toString());
|
||||
}
|
||||
System.err.println("-----------------");
|
||||
sp.splitOverlappingEdges(layers);
|
||||
|
||||
|
||||
System.err.println("AFTER:");
|
||||
System.err.println("----------------");
|
||||
for (Path path : layers.get(0).paths) {
|
||||
System.err.println("" + path.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,16 +226,18 @@ public class ShapeFixer2 {
|
||||
removeEmpty(layers);*/
|
||||
|
||||
OverlappingEdgesSplitter splitter = new OverlappingEdgesSplitter();
|
||||
splitter.splitOverlappingEdges(layers);
|
||||
splitter.splitOverlappingEdges(layers);
|
||||
|
||||
for (Layer layer : layers) {
|
||||
detectEdgeFills(layer);
|
||||
}
|
||||
|
||||
|
||||
for (Layer layer : layers) {
|
||||
layer.round(wasSmall);
|
||||
}
|
||||
|
||||
for (Layer layer : layers) {
|
||||
detectEdgeFills(layer);
|
||||
}
|
||||
|
||||
removeEmpty(layers);
|
||||
|
||||
/*for (Layer layer : layers) {
|
||||
@@ -278,7 +280,7 @@ public class ShapeFixer2 {
|
||||
}
|
||||
}
|
||||
|
||||
private void detectEdgeFills(Layer layer) {
|
||||
public void detectEdgeFills(Layer layer) {
|
||||
|
||||
double epsBase = 1e-4;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user