Fixed #503, #1011, #1257, #1902, #1903, #2048 FLA export - shapes with overlapping edges

Do not show depths in FLA layer name
This commit is contained in:
Jindra Petřík
2023-11-03 22:56:19 +01:00
parent e00865ed6f
commit 659dd45045
12 changed files with 2007 additions and 519 deletions

View File

@@ -20,8 +20,12 @@ import com.jpexs.helpers.Reference;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.Serializable;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Stack;
/**
@@ -36,6 +40,21 @@ public class BezierEdge implements Serializable {
this.points = points;
}
@Override
public BezierEdge clone() {
return new BezierEdge(new ArrayList<>(points));
}
public boolean isEmpty() {
Point2D p1 = getBeginPoint();
for (int i = 1; i < points.size(); i++) {
if (!points.get(i).equals(p1)) {
return false;
}
}
return true;
}
public BezierEdge(double x0, double y0, double x1, double y1) {
points.add(new Point2D.Double(x0, y0));
points.add(new Point2D.Double(x1, y1));
@@ -50,11 +69,19 @@ public class BezierEdge implements Serializable {
public Point2D getBeginPoint() {
return points.get(0);
}
public Point2D getEndPoint() {
return points.get(points.size() - 1);
}
public void setBeginPoint(Point2D p) {
points.set(0, p);
}
public void setEndPoint(Point2D p) {
points.set(points.size() - 1, p);
}
public Point2D pointAt(double t) {
if (points.size() == 2) {
double x = (1 - t) * points.get(0).getX() + t * points.get(1).getX();
@@ -100,7 +127,7 @@ public class BezierEdge implements Serializable {
return b;
}
private final double MIN_SIZE = 1.0;
private final double MIN_SIZE = 0.5;
public double area() {
Rectangle2D rect = bbox();
@@ -134,12 +161,82 @@ public class BezierEdge implements Serializable {
return false;
}
public boolean intersects(BezierEdge b2, List<Double> t1Ref, List<Double> t2Ref) {
return intersects(b2,
public List<Point2D> getIntersections(BezierEdge b2) {
if (points.size() == 2) {
if (b2.points.size() == 2) {
return Intersections.intersectLineLine(points.get(0), points.get(1), b2.points.get(0), b2.points.get(1), true);
} else {
return Intersections.intersectBezier2Line(b2.points.get(0), b2.points.get(1), b2.points.get(2), points.get(0), points.get(1));
}
} else {
if (b2.points.size() == 2) {
return Intersections.intersectBezier2Line(points.get(0), points.get(1), points.get(2), b2.points.get(0), b2.points.get(1));
} else {
return Intersections.intersectBezier2Bezier2(points.get(0), points.get(1), points.get(2), b2.points.get(0), b2.points.get(1), b2.points.get(2));
}
}
}
public boolean intersectsOld(BezierEdge b2, List<Double> t1Ref, List<Double> t2Ref) {
List<Point2D> interPoints = new ArrayList<>();
List<Double> t1RefA = new ArrayList<>();
List<Double> t2RefA = new ArrayList<>();
boolean ret = intersects(b2,
0,
1,
0,
1, t1Ref, t2Ref);
1, t1RefA, t2RefA, interPoints);
Point2D last = new Point2D.Double(Double.MAX_VALUE, Double.MAX_VALUE);
int numSame = 0;
double sumT1 = 0;
double sumT2 = 0;
for (int i = 0; i < interPoints.size(); i++) {
double dist = interPoints.get(i).distance(last);
System.err.println("dist=" + dist);
if (dist <= 5.0) {
numSame++;
sumT1 += t1RefA.get(i);
sumT2 += t2RefA.get(i);
last = interPoints.get(i);
} else {
if (numSame > 0) {
t1Ref.add(sumT1 / numSame);
t2Ref.add(sumT2 / numSame);
}
numSame = 1;
last = interPoints.get(i);
sumT1 = t1RefA.get(i);
sumT2 = t2RefA.get(i);
}
}
if (numSame > 0) {
t1Ref.add(sumT1 / numSame);
t2Ref.add(sumT2 / numSame);
}
return ret;
}
public boolean intersects(BezierEdge b2, List<Double> t1Ref, List<Double> t2Ref, List<Point2D> intPoints) {
List<Point2D> inter = getIntersections(b2);
BezierUtils utils = new BezierUtils();
for (Point2D p : inter) {
Point2D p1;
Point2D p3;
Point2D p2;
p1 = this.points.get(0);
p3 = this.points.get(this.points.size() - 1);
p2 = this.points.size() == 3 ? this.points.get(1) : new Point2D.Double((p1.getX() + p3.getX()) / 2, (p1.getY() + p3.getY()) / 2);
t1Ref.add(utils.closestPointToBezier(p, p1, p2, p3));
p1 = b2.points.get(0);
p3 = b2.points.get(b2.points.size() - 1);
p2 = b2.points.size() == 3 ? b2.points.get(1) : new Point2D.Double((p1.getX() + p3.getX()) / 2, (p1.getY() + p3.getY()) / 2);
t2Ref.add(utils.closestPointToBezier(p, p1, p2, p3));
}
intPoints.addAll(inter);
return !inter.isEmpty();
}
private boolean intersects(
@@ -149,7 +246,9 @@ public class BezierEdge implements Serializable {
double start2,
double end2,
List<Double> t1Ref,
List<Double> t2Ref) {
List<Double> t2Ref,
List<Point2D> interPoints
) {
final double threshold = MIN_SIZE * 2.0; //?
Rectangle2D bb1 = bbox();
Rectangle2D bb2 = b2.bbox();
@@ -161,7 +260,8 @@ public class BezierEdge implements Serializable {
if (Double.compare(sumAreas, threshold) <= 0) {
double t1 = (start1 + end1) / 2;
double t2 = (start2 + end2) / 2;
Point2D selPoint = getBeginPoint();
interPoints.add(selPoint);
t1Ref.add(t1);
t2Ref.add(t2);
return true;
@@ -185,26 +285,25 @@ public class BezierEdge implements Serializable {
double half2 = start2 + (end2 - start2) / 2.0;
boolean ok = false;
if (b1a.intersects(b2a, start1, half1, start2, half2, t1Ref, t2Ref)) {
if (b1a.intersects(b2a, start1, half1, start2, half2, t1Ref, t2Ref, interPoints)) {
ok = true;
}
if (b1a.intersects(b2b, start1, half1, half2, end2, t1Ref, t2Ref)) {
if (b1a.intersects(b2b, start1, half1, half2, end2, t1Ref, t2Ref, interPoints)) {
ok = true;
}
if (b1b.intersects(b2a, half1, end1, start2, half2, t1Ref, t2Ref)) {
if (b1b.intersects(b2a, half1, end1, start2, half2, t1Ref, t2Ref, interPoints)) {
ok = true;
}
if (b1b.intersects(b2b, half1, end1, half2, end2, t1Ref, t2Ref)) {
if (b1b.intersects(b2b, half1, end1, half2, end2, t1Ref, t2Ref, interPoints)) {
ok = true;
}
return ok;
}
public double length() {
double distance = 0;
double epsilon = 1;
Stack<BezierEdge> parts = new Stack<BezierEdge>();
parts.push(this);
@@ -233,15 +332,44 @@ public class BezierEdge implements Serializable {
return "{" + String.join("-", list) + "}";
}
public String toSvg() {
DecimalFormat df = new DecimalFormat("0.###", DecimalFormatSymbols.getInstance(Locale.ENGLISH));
df.setGroupingUsed(false);
String ret = "";
ret += "M ";
ret += df.format(points.get(0).getX());
ret += " ";
ret += df.format(points.get(0).getY());
ret += " ";
if (points.size() == 3) {
ret += "Q ";
} else {
ret += "L ";
}
ret += df.format(points.get(1).getX());
ret += " ";
ret += df.format(points.get(1).getY());
if (points.size() == 3) {
ret += " ";
ret += df.format(points.get(2).getX());
ret += " ";
ret += df.format(points.get(2).getY());
}
return ret;
}
public void split(double t, Reference<BezierEdge> left, Reference<BezierEdge> right) {
List<Point2D> leftPoints = new ArrayList<>();
List<Point2D> rightPoints = new ArrayList<>();
BezierUtils bu = new BezierUtils();
bu.subdivide(points, t, leftPoints, rightPoints);
left.setVal(new BezierEdge(leftPoints));
rightPoints.set(0, leftPoints.get(leftPoints.size() - 1));
right.setVal(new BezierEdge(rightPoints));
}
public BezierEdge reverse() {
List<Point2D> revPoints = new ArrayList<>();
for (int i = points.size() - 1; i >= 0; i--) {
@@ -250,6 +378,46 @@ public class BezierEdge implements Serializable {
return new BezierEdge(revPoints);
}
public void round() {
for (int i = 0; i < this.points.size(); i++) {
this.points.set(i, new Point2D.Double(
Math.round(this.points.get(i).getX()),
Math.round(this.points.get(i).getY())
));
}
}
public void roundHalf() {
for (int i = 0; i < this.points.size(); i++) {
this.points.set(i, new Point2D.Double(
Math.round(this.points.get(i).getX() * 2.0) / 2.0,
Math.round(this.points.get(i).getY() * 2.0) / 2.0
));
}
}
@Override
public int hashCode() {
int hash = 7;
hash = 41 * hash + Objects.hashCode(this.points);
return hash;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final BezierEdge other = (BezierEdge) obj;
return Objects.equals(this.points, other.points);
}
public static void main(String[] args) {
List<Double> t1 = new ArrayList<>();
List<Double> t2 = new ArrayList<>();
@@ -276,12 +444,48 @@ public class BezierEdge implements Serializable {
BezierEdge be5 = new BezierEdge(25, 0, 25, 100);
BezierEdge be6 = new BezierEdge(0, 50, 100, 50);
System.out.println("lines " + be5 + " and " + be6);
//System.err.println("lines " + be5 + " and " + be6);
BezierEdge q1 = new BezierEdge(3469, 3124, 3320, 3148, 3355, 3215);
BezierEdge q2 = new BezierEdge(3442, 3191, 3316, 3146, 3317, 3071);
BezierEdge q3 = new BezierEdge(3310, 3222, 3450, 3172, 3300, 3181);
BezierEdge li = new BezierEdge(3423, 3040, 3277, 3164);
BezierEdge li2 = new BezierEdge(3399, 3095, 3365, 3039);
System.out.println("hasIntersection = " + be5.intersects(be6, t1, t2));
System.out.println("intersection ts: " + t1 + ", " + t2);
List<Point2D> ints;
/*ints = q2.getIntersections(q1);
System.err.println("intersections is "+ints);
ints = q2.getIntersections(li);
System.err.println("intersections is "+ints);
ints = li2.getIntersections(li);
System.err.println("intersections is "+ints);
ints = q1.getIntersections(q3);
System.err.println("intersections is "+ints);*/
Point2D c = new Point2D.Double(
BezierEdge qa = new BezierEdge(-81.0, -78.0, -85.0, -76.0, -86.0, -66.0);
BezierEdge qb = new BezierEdge(-166.0, 37.0, -172.0, -21.0, -81.0, -78.0);
/*ints = qa.getIntersections(qb);
System.err.println("intersections is " + ints);
BezierEdge qc = new BezierEdge(-106.0,39.0, -104.0,33.0);
BezierEdge qd = new BezierEdge(-104.0,33.0,-105.0,36.0,-102.0,26.0);
ints = qc.getIntersections(qd);
System.err.println("intersections is " + ints);
ints = Intersections.intersectLineLine(new Point2D.Double(0,0), new Point2D.Double(10,0), new Point2D.Double(2,0), new Point2D.Double(5,0), true);
System.err.println("intersections is " + ints);
BezierEdge qe = new BezierEdge(-104,33,-104.5,35,-104,32);
BezierEdge qf = new BezierEdge(-106,39 ,-104,33);
ints = qe.getIntersections(qf);*/
BezierEdge qg = new BezierEdge(-66, 139, -67, 140, -61, 135);
BezierEdge qh = new BezierEdge(-64, 169, -66.5, 139.5, -66, 139);
List<Point2D> ps = new ArrayList<>();
qg.intersects(qh, t1, t2, ps);
System.err.println("t1 is " + t1);
System.err.println("t2 is " + t2);
System.err.println("intersections is " + ps);
/*Point2D c = new Point2D.Double(
(1 - t1.get(0)) * be5.points.get(0).getX() + t1.get(0) * be5.points.get(1).getX(),
(1 - t1.get(0)) * be5.points.get(0).getY() + t1.get(0) * be5.points.get(1).getY()
);
@@ -290,8 +494,7 @@ public class BezierEdge implements Serializable {
BezierEdge be = new BezierEdge(0, 0, 100, 50, 0, 100);
System.out.println("be5.dist: " + be.length());
System.out.println("be5.dist: " + be.length());*/
//Rectangle2D out = new Rectangle2D.Double();
//rectIntersection(new Rectangle2D.Double(0,0,50,50), new Rectangle2D.Double(0,50,50,50), out);
//System.out.println("out = "+out);

View File

@@ -0,0 +1,454 @@
/*
* Copyright (C) 2010-2023 JPEXS, All rights reserved.
*
* This library 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 3.0 of the License, or (at your option) any later version.
*
* This library 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 library.
*/
package com.jpexs.decompiler.flash.math;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Intersection calculating. Based on Node.js library kld-intersections:
* https://github.com/thelonious/kld-intersections/
*/
public class Intersections {
private static final double FLATNESS = 0.01;
private static Point2D min(Point2D p1, Point2D p2) {
return new Point2D.Double(Math.min(p1.getX(), p2.getX()), Math.min(p1.getY(), p2.getY()));
}
private static Point2D max(Point2D p1, Point2D p2) {
return new Point2D.Double(Math.max(p1.getX(), p2.getX()), Math.max(p1.getY(), p2.getY()));
}
private static Point2D multiply(Point2D p, double scalar) {
return new Point2D.Double(p.getX() * scalar, p.getY() * scalar);
}
private static Point2D add(Point2D p1, Point2D p2) {
return new Point2D.Double(p1.getX() + p2.getX(), p1.getY() + p2.getY());
}
private static Point2D lerp(Point2D p1, Point2D p2, double t) {
double omt = 1.0 - t;
return new Point2D.Double(
p1.getX() * omt + p2.getX() * t,
p1.getY() * omt + p2.getY() * t
);
}
private static List<Point2D> intersectLinePolyline(Point2D a1, Point2D a2, List<Point2D> points) {
List<Point2D> result = new ArrayList<>();
for (int i = 0; i < points.size() - 1; i++) {
Point2D b1 = points.get(i);
Point2D b2 = points.get(i + 1);
List<Point2D> inter = intersectLineLine(a1, a2, b1, b2, false);
for (Point2D p : inter) { //???
if (!result.contains(p)) {
result.add(p);
}
}
}
return result;
}
private static List<Point2D> intersectPolylinePolyline(List<Point2D> points1, List<Point2D> points2) {
List<Point2D> result = new ArrayList<>();
for (int i = 0; i < points1.size() - 1; i++) {
Point2D a1 = points1.get(i);
Point2D a2 = points1.get(i + 1);
List<Point2D> inter = intersectLinePolyline(a1, a2, points2);
result.addAll(inter);
}
return result;
}
private static Point2D vectorFromPoints(Point2D p1, Point2D p2) {
return new Point2D.Double(
p2.getX() - p1.getX(),
p2.getY() - p1.getY()
);
}
private static Point2D subtract(Point2D p1, Point2D p2) {
return new Point2D.Double(p1.getX() - p2.getX(), p1.getY() - p2.getY());
}
private static Point2D project(Point2D p1, Point2D that) {
double percent = dot(p1, that) / dot(that, that);
return multiply(that, percent);
}
private static Point2D perpendicular(Point2D p1, Point2D that) {
return subtract(p1, project(p1, that));
}
private static double vectorLength(Point2D p) {
return Math.sqrt(p.getX() * p.getX() + p.getY() * p.getY());
}
private static void tesselateInterior(double flatness, Point2D zeroVector, Point2D p1, Point2D p2, Point2D p3, List<Point2D> points) {
// round 1
Point2D p4 = lerp(p1, p2, 0.5);
Point2D p5 = lerp(p2, p3, 0.5);
// round 2
Point2D p6 = lerp(p4, p5, 0.5);
Point2D baseline = vectorFromPoints(p1, p3);
Point2D tangent = vectorFromPoints(p1, p2);
double dmax = 0;
if (!zeroVector.equals(tangent)) {
Point2D perpendicular = perpendicular(baseline, tangent);
dmax = vectorLength(perpendicular);
}
if (dmax > flatness) {
tesselateInterior(flatness, zeroVector, p1, p4, p6, points);
points.add(new Point2D.Double(p6.getX(), p6.getY()));
tesselateInterior(flatness, zeroVector, p6, p5, p3, points);
} else {
points.add(new Point2D.Double(p6.getX(), p6.getY()));
}
}
public static List<Point2D> quadraticBezierToToPolyline(Point2D p1, Point2D p2, Point2D p3) {
return quadraticBezierToToPolyline(p1, p2, p3, null);
}
public static List<Point2D> quadraticBezierToToPolyline(Point2D p1, Point2D p2, Point2D p3, Double flatness) {
List<Point2D> points = new ArrayList<>();
Point2D zeroVector = new Point2D.Double(0, 0);
flatness = flatness != null ? flatness : 1.0;
// add first point
points.add(p1);
// add interior points
tesselateInterior(flatness, zeroVector, p1, p2, p3, points);
// add last point
points.add(p3);
return points;
}
public static List<Point2D> intersectBezier2Bezier2Slow(Point2D a1, Point2D a2, Point2D a3, Point2D b1, Point2D b2, Point2D b3) {
List<Point2D> a = quadraticBezierToToPolyline(a1, a2, a3, FLATNESS);
List<Point2D> b = quadraticBezierToToPolyline(b1, b2, b3, FLATNESS);
return intersectPolylinePolyline(a, b);
}
public static List<Point2D> intersectBezier2Bezier2(Point2D a1, Point2D a2, Point2D a3, Point2D b1, Point2D b2, Point2D b3) {
Point2D pa;
Point2D pb;
List<Point2D> result = new ArrayList<>();
pa = multiply(a2, -2);
Point2D x = add(pa, a3);
Point2D c12 = add(a1, x);
pa = multiply(a1, -2);
pb = multiply(a2, 2);
Point2D c11 = add(pa, pb);
Point2D c10 = new Point2D.Double(a1.getX(), a1.getY());
pa = multiply(b2, -2);
Point2D c22 = add(b1, add(pa, b3));
pa = multiply(b1, -2);
pb = multiply(b2, 2);
Point2D c21 = add(pa, pb);
Point2D c20 = new Point2D.Double(b1.getX(), b1.getY());
// bezout
double a = c12.getX() * c11.getY() - c11.getX() * c12.getY();
double b = c22.getX() * c11.getY() - c11.getX() * c22.getY();
double c = c21.getX() * c11.getY() - c11.getX() * c21.getY();
double d = c11.getX() * (c10.getY() - c20.getY()) + c11.getY() * (-c10.getX() + c20.getX());
double e = c22.getX() * c12.getY() - c12.getX() * c22.getY();
double f = c21.getX() * c12.getY() - c12.getX() * c21.getY();
double g = c12.getX() * (c10.getY() - c20.getY()) + c12.getY() * (-c10.getX() + c20.getX());
// determinant
Polynomial poly = new Polynomial(Arrays.asList(
-e * e,
-2 * e * f,
a * b - f * f - 2 * e * g,
a * c - 2 * f * g,
a * d - g * g
)
);
List<Double> roots = poly.getRoots();
for (double s : roots) {
if (0 <= s && s <= 1) {
Polynomial xp = new Polynomial(Arrays.asList(
c12.getX(),
c11.getX(),
c10.getX() - c20.getX() - s * c21.getX() - s * s * c22.getX()
)
);
xp.simplifyEquals();
List<Double> xRoots = xp.getRoots();
Polynomial yp = new Polynomial(Arrays.asList(
c12.getY(),
c11.getY(),
c10.getY() - c20.getY() - s * c21.getY() - s * s * c22.getY()
)
);
yp.simplifyEquals();
List<Double> yRoots = yp.getRoots();
if (!xRoots.isEmpty() && !yRoots.isEmpty()) {
double TOLERANCE = 1e-4;
checkRoots:
for (double xRoot : xRoots) {
if (0 <= xRoot && xRoot <= 1) {
for (int k = 0; k < yRoots.size(); k++) {
if (Math.abs(xRoot - yRoots.get(k)) < TOLERANCE) {
result.add(add(multiply(c22, s * s), (add(multiply(c21, s), c20))));
break checkRoots;
}
}
}
}
}
}
}
//JPEXS: fix rounding errors
if (a1.equals(b1) && !result.contains(a1)) {
result.add(0, a1);
}
if (a3.equals(b3) && !result.contains(a3)) {
result.add(a3);
}
if (a1.equals(b3) && !result.contains(a1)) {
result.add(0, a1);
}
if (a3.equals(b1) && !result.contains(a3)) {
result.add(0, a3);
}
return result;
}
private static double dot(Point2D p1, Point2D p2) {
return p1.getX() * p2.getX() + p1.getY() * p2.getY();
}
public static List<Point2D> intersectBezier2LineSlow(Point2D p1, Point2D p2, Point2D p3, Point2D a1, Point2D a2) {
List<Point2D> p = quadraticBezierToToPolyline(p1, p2, p3, FLATNESS);
return intersectLinePolyline(a1, a2, p);
}
public static List<Point2D> intersectBezier2Line(Point2D p1, Point2D p2, Point2D p3, Point2D a1, Point2D a2) {
Point2D a; // temporary variables
Point2D min = min(a1, a2); // used to determine if point is on line segment
Point2D max = max(a1, a2); // used to determine if point is on line segment
List<Point2D> result = new ArrayList<>();
a = multiply(p2, -2);
Point2D c2 = add(p1, add(a, p3));
a = multiply(p1, -2);
Point2D b = multiply(p2, 2);
Point2D c1 = add(a, b);
Point2D c0 = new Point2D.Double(p1.getX(), p1.getY());
// Convert line to normal form: ax + by + c = 0
// Find normal to line: negative inverse of original line's slope
Point2D n = new Point2D.Double(a1.getY() - a2.getY(), a2.getX() - a1.getX());
// Determine new c coefficient
double cl = a1.getX() * a2.getY() - a2.getX() * a1.getY();
// Transform cubic coefficients to line's coordinate system and find roots
// of cubic
List<Double> roots = new Polynomial(
Arrays.asList(
dot(n, c2),
dot(n, c1),
dot(n, c0) + cl
)
).getRoots();
// Any roots in closed interval [0,1] are intersections on Bezier, but
// might not be on the line segment.
// Find intersections and calculate point coordinates
for (double t : roots) {
if (0 <= t && t <= 1) {
// We're within the Bezier curve
// Find point on Bezier
Point2D p4 = lerp(p1, p2, t);
Point2D p5 = lerp(p2, p3, t);
Point2D p6 = lerp(p4, p5, t);
// See if point is on line segment
// Had to make special cases for vertical and horizontal lines due
// to slight errors in calculation of p6
if (a1.getX() == a2.getX()) {
if (min.getY() <= p6.getY() && p6.getY() <= max.getY()) {
result.add(p6);
}
} else if (a1.getY() == a2.getY()) {
if (min.getX() <= p6.getX() && p6.getX() <= max.getX()) {
result.add(p6);
}
} else if (min.getX() <= p6.getX() && p6.getX() <= max.getX() && min.getY() <= p6.getY() && p6.getY() <= max.getY()) {
result.add(p6);
}
}
}
//JPEXS: fix rounding errors
if (a1.equals(p1) && !result.contains(a1)) {
result.add(0, a1);
}
if (a2.equals(p3) && !result.contains(a2)) {
result.add(a2);
}
if (a1.equals(p3) && !result.contains(a1)) {
result.add(0, a1);
}
if (a2.equals(p1) && !result.contains(a2)) {
result.add(0, a2);
}
return result;
}
public static List<Point2D> intersectLineLine(Point2D a1, Point2D a2, Point2D b1, Point2D b2, boolean addCoincident) {
List<Point2D> result = new ArrayList<>();
double ua_t = (b2.getX() - b1.getX()) * (a1.getY() - b1.getY()) - (b2.getY() - b1.getY()) * (a1.getX() - b1.getX());
double ub_t = (a2.getX() - a1.getX()) * (a1.getY() - b1.getY()) - (a2.getY() - a1.getY()) * (a1.getX() - b1.getX());
double u_b = (b2.getY() - b1.getY()) * (a2.getX() - a1.getX()) - (b2.getX() - b1.getX()) * (a2.getY() - a1.getY());
if (u_b != 0) {
double ua = ua_t / u_b;
double ub = ub_t / u_b;
if (0 <= ua && ua <= 1 && 0 <= ub && ub <= 1) {
result.add(
new Point2D.Double(
a1.getX() + ua * (a2.getX() - a1.getX()),
a1.getY() + ua * (a2.getY() - a1.getY())
)
);
} else {
//No Intersection
}
} else if (ua_t == 0 || ub_t == 0) {
if (!addCoincident) {
return result;
}
//WARNING: This is actually not an intersection,
//but Coincident. But we treat it equally
double a1v;
double a2v;
double b1v;
double b2v;
if (!(a1.getX() == b1.getX() && a2.getX() == b2.getX() && a1.getX() == a2.getX())) {
a1v = a1.getX();
a2v = a2.getX();
b1v = b1.getX();
b2v = b2.getX();
} else {
a1v = a1.getY();
a2v = a2.getY();
b1v = b1.getY();
b2v = b2.getY();
}
if (a1v > a2v) {
double td;
td = a1v;
a1v = a2v;
a2v = td;
}
if (b1v > b2v) {
double td;
td = b1v;
b1v = b2v;
b2v = td;
}
if (b1v < a1v) {
//swap a, b
Point2D t;
t = b1;
b1 = a1;
a1 = t;
t = b2;
b2 = a2;
a2 = t;
double td;
td = b1v;
b1v = a1v;
a1v = td;
td = b2v;
b2v = a2v;
a2v = td;
}
if (a2v < b1v) {
//no overlap
return result;
}
if (a2v == b1v) {
//single point, ignore
return result;
}
//A1 B1 A2 B2
// |----|----|----|
result.add(b1);
result.add(a2);
} else {
//Parallel
}
return result;
}
}

View File

@@ -0,0 +1,617 @@
/*
* Copyright (C) 2010-2023 JPEXS, All rights reserved.
*
* This library 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 3.0 of the License, or (at your option) any later version.
*
* This library 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 library.
*/
package com.jpexs.decompiler.flash.math;
import com.jpexs.helpers.Reference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
/**
* Based on Node.js library kld-polynomial:
* https://github.com/thelonious/kld-polynomial
*/
public class Polynomial {
private double[] coefs;
private String _variable;
private int _s;
public Polynomial(List<Double> coefs) {
this.coefs = new double[coefs.size()];
for (int i = 0; i < coefs.size(); i++) {
this.coefs[i] = coefs.get(coefs.size() - 1 - i);
}
this._variable = "t";
this._s = 0;
}
public int getDegree() {
return this.coefs.length - 1;
}
private List<Double> getLinearRoot() {
List<Double> result = new ArrayList<>();
double a = this.coefs[1];
if (a != 0) {
result.add(-this.coefs[0] / a);
}
return result;
}
private List<Double> getQuadraticRoots() {
List<Double> results = new ArrayList<>();
if (this.getDegree() == 2) {
double a = this.coefs[2];
double b = this.coefs[1] / a;
double c = this.coefs[0] / a;
double d = b * b - 4 * c;
if (d > 0) {
double e = Math.sqrt(d);
results.add(0.5 * (-b + e));
results.add(0.5 * (-b - e));
} else if (d == 0) {
// really two roots with same value, but we only return one
results.add(0.5 * -b);
}
// else imaginary results
}
return results;
}
private boolean coefSelectionFunc(int i, int n, double[] a) {
return i < n && a[i] < 0;
}
private void find2Max(Reference<Double> max, Reference<Double> nearmax, double bi, int i, int n, double[] a) {
if (coefSelectionFunc(i, n, a)) {
if (max.getVal() < bi) {
nearmax.setVal(max.getVal());
max.setVal(bi);
} else if (nearmax.getVal() < bi) {
nearmax.setVal(bi);
}
}
}
;
/**
*
* @return negX, posX
*/
private double[] boundsUpperRealFujiwara() {
double[] ax = this.coefs;
double[] a = ax;
int n = a.length - 1;
double an = a[n];
if (an != 1) {
a = new double[a.length];
for (int i = 0; i < this.coefs.length; i++) {
a[i] = ax[i] / an;
}
}
double[] b = new double[a.length];
for (int i = 0; i < a.length; i++) {
double v = a[i];
if (i < n) {
b[i] = Math.pow(Math.abs((i == 0) ? v / 2 : v), 1 / (double) (n - i));
} else {
b[i] = v;
}
}
// eslint-disable-next-line unicorn/no-fn-reference-in-iterator
double max_pos = 0;
double nearmax_pos = 0;
for (int i = 0; i < b.length; i++) {
if (i < n && a[i] < 0) {
if (max_pos < b[i]) {
nearmax_pos = max_pos;
max_pos = b[i];
} else if (nearmax_pos < b[i]) {
nearmax_pos = b[i];
}
}
}
double max_neg = 0;
double nearmax_neg = 0;
for (int i = 0; i < b.length; i++) {
if (i < n && ((n % 2 == i % 2) ? a[i] < 0 : a[i] > 0)) {
if (max_neg < b[i]) {
nearmax_neg = max_neg;
max_neg = b[i];
} else if (nearmax_neg < b[i]) {
nearmax_neg = b[i];
}
}
}
return new double[]{
-2 * max_neg,
2 * max_pos
};
}
private class Rect {
double minX;
double minY;
double maxX;
double maxY;
public Rect(double minX, double minY, double maxX, double maxY) {
this.minX = minX;
this.minY = minY;
this.maxX = maxX;
this.maxY = maxY;
}
}
private double[] boundsLowerRealFujiwara() {
Polynomial poly = new Polynomial(new ArrayList<>());
poly.coefs = new double[coefs.length];
for (int i = 0; i < coefs.length; i++) {
poly.coefs[coefs.length - 1 - i] = coefs[i];
}
double[] res = poly.boundsUpperRealFujiwara();
res[0] = 1 / res[0];
res[1] = 1 / res[1];
return res;
}
private Rect bounds() {
double[] urb = this.boundsUpperRealFujiwara();
Rect rb = new Rect(urb[0], 0, urb[1], 0);
if (urb[0] == 0 && urb[1] == 0) {
return rb;
}
if (urb[0] == 0) {
rb.minX = this.boundsLowerRealFujiwara()[1];
} else if (urb[1] == 0) {
rb.maxX = this.boundsLowerRealFujiwara()[0];
}
if (rb.minX > rb.maxX) {
rb.minX = rb.maxX = 0;
}
return rb;
// TODO: if sure that there are no complex roots
// (maybe by using Sturm's theorem) use:
// return this.boundsRealLaguerre();
}
private double eval(double x) {
if (Double.isNaN(x)) {
throw new RuntimeException("Parameter must be a number");
}
double result = 0;
for (int i = this.coefs.length - 1; i >= 0; i--) {
result = result * x + this.coefs[i];
}
return result;
}
public double zeroErrorEstimate() {
return zeroErrorEstimate(null);
}
public double zeroErrorEstimate(Double maxAbsX) {
Polynomial poly = this;
double ERRF = 1e-15;
if (maxAbsX == null) {
Rect rb = poly.bounds();
maxAbsX = Math.max(Math.abs(rb.minX), Math.abs(rb.maxX));
}
if (maxAbsX < 0.001) {
return 2 * Math.abs(poly.eval(ERRF));
}
int n = poly.coefs.length - 1;
double an = poly.coefs[n];
double m = 0;
for (int i = 0; i < poly.coefs.length; i++) {
double v = poly.coefs[i];
double nm = v / an * Math.pow(maxAbsX, i);
m = nm > m ? nm : m;
}
double x = 10 * ERRF * m;
return x;
}
public List<Double> getCubicRoots() {
List<Double> results = new ArrayList<>();
if (this.getDegree() == 3) {
double c3 = this.coefs[3];
double c2 = this.coefs[2] / c3;
double c1 = this.coefs[1] / c3;
double c0 = this.coefs[0] / c3;
double a = (3 * c1 - c2 * c2) / 3;
double b = (2 * c2 * c2 * c2 - 9 * c1 * c2 + 27 * c0) / 27;
double offset = c2 / 3;
double discrim = b * b / 4 + a * a * a / 27;
double halfB = b / 2;
double ZEROepsilon = this.zeroErrorEstimate();
if (Math.abs(discrim) <= ZEROepsilon) {
discrim = 0;
}
if (discrim > 0) {
double e = Math.sqrt(discrim);
double root; // eslint-disable-line no-shadow
double tmp = -halfB + e;
if (tmp >= 0) {
root = Math.pow(tmp, 1 / 3.0);
} else {
root = -Math.pow(-tmp, 1 / 3.0);
}
tmp = -halfB - e;
if (tmp >= 0) {
root += Math.pow(tmp, 1 / 3.0);
} else {
root -= Math.pow(-tmp, 1 / 3.0);
}
results.add(root - offset);
} else if (discrim < 0) {
double distance = Math.sqrt(-a / 3.0);
double angle = Math.atan2(Math.sqrt(-discrim), -halfB) / 3.0;
double cos = Math.cos(angle);
double sin = Math.sin(angle);
double sqrt3 = Math.sqrt(3);
results.add(2 * distance * cos - offset);
results.add(-distance * (cos + sqrt3 * sin) - offset);
results.add(-distance * (cos - sqrt3 * sin) - offset);
} else {
double tmp;
if (halfB >= 0) {
tmp = -Math.pow(halfB, 1 / 3.0);
} else {
tmp = Math.pow(-halfB, 1 / 3.0);
}
results.add(2 * tmp - offset);
// really should return next root twice, but we return only one
results.add(-tmp - offset);
}
}
return results;
}
public void simplifyEquals() {
simplifyEquals(1e-12);
}
public void simplifyEquals(double tolerance) {
for (int i = this.getDegree(); i >= 0; i--) {
if (Math.abs(this.coefs[i]) <= tolerance) {
double[] newc = new double[this.coefs.length - 1];
for (int j = 0; j < newc.length; j++) {
newc[j] = this.coefs[j];
}
this.coefs = newc;
} else {
break;
}
}
}
private void divideEqualsScalar(double scalar) {
for (int i = 0; i < this.coefs.length; i++) {
this.coefs[i] /= scalar;
}
}
private Polynomial getDerivative() {
Polynomial pol = new Polynomial(new ArrayList<>());
List<Double> newCoefs = new ArrayList<>();
pol.coefs = new double[this.coefs.length - 1];
for (int i = 1; i < this.coefs.length; i++) {
pol.coefs[i - 1] = i * this.coefs[i];
}
return pol;
}
public List<Double> getRoots() {
List<Double> result;
this.simplifyEquals();
switch (this.getDegree()) {
case 0:
result = new ArrayList<>();
break;
case 1:
result = this.getLinearRoot();
break;
case 2:
result = this.getQuadraticRoots();
break;
case 3:
result = this.getCubicRoots();
break;
case 4:
result = this.getQuarticRoots();
break;
default:
result = new ArrayList<>();
}
return result;
}
private static Double sign(Double x) {
// eslint-disable-next-line no-self-compare
if (x == null) {
return null;
}
return x < 0 ? -1.0 : 1.0;
}
private interface DoubleFunc {
public double apply(double val);
}
private static double newtonSecantBisection(double x0, DoubleFunc f, DoubleFunc df, int max_iterations, Double min, Double max) {
double x;
double prev_dfx = 0;
double dfx;
double prev_x_ef_correction = 0;
double x_correction;
double x_new;
double y;
Double y_atmin = null;
Double y_atmax = null;
x = x0;
double ACCURACY = 14;
double min_correction_factor = Math.pow(10, -ACCURACY);
boolean isBounded = min != null && max != null;
if (isBounded) {
if (min > max) {
throw new RuntimeException("Min must be greater than max");
}
y_atmin = f.apply(min);
y_atmax = f.apply(max);
if (Double.compare(sign(y_atmin), sign(y_atmax)) == 0) {
throw new RuntimeException("Y values of bounds must be of opposite sign");
}
}
for (int i = 0; i < max_iterations; i++) {
dfx = df.apply(x);
if (dfx == 0) {
if (prev_dfx == 0) {
// error
throw new RuntimeException("df(x) is zero");
} else {
// use previous derivation value
dfx = prev_dfx;
}
// or move x a little?
// dfx = df(x != 0 ? x + x * 1e-15 : 1e-15);
}
prev_dfx = dfx;
y = f.apply(x);
x_correction = y / dfx;
x_new = x - x_correction;
boolean isEnoughCorrection = (Math.abs(x_correction) <= min_correction_factor * Math.abs(x))
|| (prev_x_ef_correction == (x - x_correction) - x);
if (isEnoughCorrection) {
break;
}
if (isBounded) {
if (Double.compare(sign(y), sign(y_atmax)) == 0) {
max = x;
y_atmax = y;
} else if (Double.compare(sign(y), sign(y_atmin)) == 0) {
min = x;
y_atmin = y;
} else {
x = x_new;
break;
}
if ((x_new < min) || (x_new > max)) {
if (Double.compare(sign(y_atmin), sign(y_atmax)) == 0) {
break;
}
double RATIO_LIMIT = 50;
double AIMED_BISECT_OFFSET = 0.25; // [0, 0.5)
double dy = y_atmax - y_atmin;
double dx = max - min;
if (dy == 0) {
x_correction = x - (min + dx * 0.5);
} else if (Math.abs(dy / Math.min(y_atmin, y_atmax)) > RATIO_LIMIT) {
x_correction = x - (min + dx * (0.5 + (Math.abs(y_atmin) < Math.abs(y_atmax) ? -AIMED_BISECT_OFFSET : AIMED_BISECT_OFFSET)));
} else {
x_correction = x - (min - y_atmin / dy * dx);
}
x_new = x - x_correction;
isEnoughCorrection = (Math.abs(x_correction) <= min_correction_factor * Math.abs(x))
|| (prev_x_ef_correction == (x - x_correction) - x);
if (isEnoughCorrection) {
break;
}
}
}
prev_x_ef_correction = x - x_new;
x = x_new;
}
return x;
}
private List<Double> getQuarticRoots() {
List<Double> results = new ArrayList<>();
int n = this.getDegree();
if (n == 4) {
Polynomial poly = new Polynomial(new ArrayList<>());
poly.coefs = Arrays.copyOf(this.coefs, this.coefs.length);
poly.divideEqualsScalar(poly.coefs[n]);
double ERRF = 1e-15;
if (Math.abs(poly.coefs[0]) < 10 * ERRF * Math.abs(poly.coefs[3])) {
poly.coefs[0] = 0;
}
Polynomial poly_d = poly.getDerivative();
List<Double> derrt = poly_d.getRoots();
derrt.sort(new Comparator<Double>() {
@Override
public int compare(Double a, Double b) {
if (Double.compare(a, b) == 0) {
return 0;
}
if (a - b < 0) {
return -1;
}
return 1;
}
});
List<Double> dery = new ArrayList<>();
int nr = derrt.size() - 1;
Rect rb = this.bounds();
double maxabsX = Math.max(Math.abs(rb.minX), Math.abs(rb.maxX));
double ZEROepsilon = this.zeroErrorEstimate(maxabsX);
for (int i = 0; i <= nr; i++) {
dery.add(poly.eval(derrt.get(i)));
}
for (int i = 0; i <= nr; i++) {
if (Math.abs(dery.get(i)) < ZEROepsilon) {
dery.set(i, 0.0);
}
}
int i = 0;
double dx = Math.max(0.1 * (rb.maxX - rb.minX) / n, ERRF);
List<Double> guesses = new ArrayList<>();
List<double[]> minmax = new ArrayList<>();
if (nr > -1) {
if (dery.get(0) != 0) {
if (Double.compare(sign(dery.get(0)), sign(poly.eval(derrt.get(0) - dx) - dery.get(0))) != 0) {
guesses.add(derrt.get(0) - dx);
minmax.add(new double[]{rb.minX, derrt.get(0)});
}
} else {
results.add(derrt.get(0));
results.add(derrt.get(0));
i++;
}
for (; i < nr; i++) {
if (dery.get(i + 1) == 0) {
results.add(derrt.get(i + 1));
results.add(derrt.get(i + 1));
i++;
} else if (Double.compare(sign(dery.get(i)), sign(dery.get(i + 1))) != 0) {
guesses.add((derrt.get(i) + derrt.get(i + 1)) / 2);
minmax.add(new double[]{derrt.get(i), derrt.get(i + 1)});
}
}
if (dery.get(nr) != 0 && Double.compare(sign(dery.get(nr)), sign(poly.eval(derrt.get(nr) + dx) - dery.get(nr))) != 0) {
guesses.add(derrt.get(nr) + dx);
minmax.add(new double[]{derrt.get(nr), rb.maxX});
}
}
DoubleFunc f = new DoubleFunc() {
@Override
public double apply(double x) {
return poly.eval(x);
}
};
DoubleFunc df = new DoubleFunc() {
@Override
public double apply(double x) {
return poly_d.eval(x);
}
};
if (!guesses.isEmpty()) {
for (i = 0; i < guesses.size(); i++) {
guesses.set(i, Polynomial.newtonSecantBisection(guesses.get(i), f, df, 32, minmax.get(i)[0], minmax.get(i)[1]));
}
}
results.addAll(guesses);
}
return results;
}
}