eu.hansolo.fx.geometry.QuadCurve Maven / Gradle / Ivy
/*
* Copyright (c) 2017 by Gerrit Grunwald
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package eu.hansolo.fx.geometry;
import eu.hansolo.fx.geometry.transform.BaseTransform;
import eu.hansolo.toolboxfx.geom.Point;
import java.util.Objects;
public class QuadCurve extends Shape {
public double x1;
public double y1;
public double ctrlx;
public double ctrly;
public double x2;
public double y2;
private static final int BELOW = -2;
private static final int LOW_EDGE = -1;
private static final int INSIDE = 0;
private static final int HIGH_EDGE = 1;
private static final int ABOVE = 2;
public QuadCurve() { }
public QuadCurve(final double X1, final double Y1, final double CTRL_X, final double CTRL_Y, final double X2, final double Y2) {
setCurve(X1, Y1, CTRL_X, CTRL_Y, X2, Y2);
}
public void setCurve(final double X1, final double Y1, final double CTRL_X, final double CTRL_Y, final double X2, final double Y2) {
x1 = X1;
y1 = Y1;
ctrlx = CTRL_X;
ctrly = CTRL_Y;
x2 = X2;
y2 = Y2;
}
public RectBounds getBounds() {
double left = Math.min(Math.min(x1, x2), ctrlx);
double top = Math.min(Math.min(y1, y2), ctrly);
double right = Math.max(Math.max(x1, x2), ctrlx);
double bottom = Math.max(Math.max(y1, y2), ctrly);
return new RectBounds(left, top, right, bottom);
}
public BezierCurve toCubic() {
return new BezierCurve(x1, y1, (x1 + 2 * ctrlx) / 3, (y1 + 2 * ctrly) / 3, (2 * ctrlx + x2) / 3, (2 * ctrly + y2) / 3, x2, y2);
}
public void setCurve(final double[] COORDS, final int OFFSET) {
setCurve(COORDS[OFFSET + 0], COORDS[OFFSET + 1], COORDS[OFFSET + 2], COORDS[OFFSET + 3], COORDS[OFFSET + 4], COORDS[OFFSET + 5]);
}
public void setCurve(final Point P1, final Point CP, final Point P2) { setCurve(P1.x, P1.y, CP.x, CP.y, P2.x, P2.y); }
public void setCurve(final Point[] POINTS, final int OFFSET) {
setCurve(POINTS[OFFSET + 0].x, POINTS[OFFSET + 0].y, POINTS[OFFSET + 1].x, POINTS[OFFSET + 1].y, POINTS[OFFSET + 2].x, POINTS[OFFSET + 2].y);
}
public void setCurve(final QuadCurve QUAD_CURVE) { setCurve(QUAD_CURVE.x1, QUAD_CURVE.y1, QUAD_CURVE.ctrlx, QUAD_CURVE.ctrly, QUAD_CURVE.x2, QUAD_CURVE.y2); }
public double getFlatnessSq() { return Line.ptSegDistSq(x1, y1, x2, y2, ctrlx, ctrly); }
public static double getFlatnessSq(final double COORDS[], final int OFFSET) {
return Line.ptSegDistSq(COORDS[OFFSET + 0], COORDS[OFFSET + 1], COORDS[OFFSET + 4], COORDS[OFFSET + 5], COORDS[OFFSET + 2], COORDS[OFFSET + 3]);
}
public static double getFlatnessSq(double x1, double y1, double ctrlx, double ctrly, double x2, double y2) {
return Line.ptSegDistSq(x1, y1, x2, y2, ctrlx, ctrly);
}
public double getFlatness() { return Line.ptSegDist(x1, y1, x2, y2, ctrlx, ctrly); }
public static double getFlatness(final double COORDS[], final int OFFSET) {
return Line.ptSegDist(COORDS[OFFSET + 0], COORDS[OFFSET + 1], COORDS[OFFSET + 4], COORDS[OFFSET + 5], COORDS[OFFSET + 2], COORDS[OFFSET + 3]);
}
public static double getFlatness(double x1, double y1, double ctrlx, double ctrly, double x2, double y2) {
return Line.ptSegDist(x1, y1, x2, y2, ctrlx, ctrly);
}
public void subdivide(final QuadCurve LEFT, final QuadCurve RIGHT) { subdivide(this, LEFT, RIGHT); }
public static void subdivide(final QuadCurve SOURCE, final QuadCurve LEFT, final QuadCurve RIGHT) {
double x1 = SOURCE.x1;
double y1 = SOURCE.y1;
double ctrlx = SOURCE.ctrlx;
double ctrly = SOURCE.ctrly;
double x2 = SOURCE.x2;
double y2 = SOURCE.y2;
double ctrlx1 = (x1 + ctrlx) / 2.0;
double ctrly1 = (y1 + ctrly) / 2.0;
double ctrlx2 = (x2 + ctrlx) / 2.0;
double ctrly2 = (y2 + ctrly) / 2.0;
ctrlx = (ctrlx1 + ctrlx2) / 2.0;
ctrly = (ctrly1 + ctrly2) / 2.0;
if (LEFT != null) { LEFT.setCurve(x1, y1, ctrlx1, ctrly1, ctrlx, ctrly); }
if (RIGHT != null) { RIGHT.setCurve(ctrlx, ctrly, ctrlx2, ctrly2, x2, y2); }
}
public static void subdivide(final double SOURCE[], final int SOURCE_OFFSET, final double LEFT[], final int LEFT_OFFSET, final double RIGHT[], final int RIGHT_OFFSET) {
double x1 = SOURCE[SOURCE_OFFSET + 0];
double y1 = SOURCE[SOURCE_OFFSET + 1];
double ctrlx = SOURCE[SOURCE_OFFSET + 2];
double ctrly = SOURCE[SOURCE_OFFSET + 3];
double x2 = SOURCE[SOURCE_OFFSET + 4];
double y2 = SOURCE[SOURCE_OFFSET + 5];
if (LEFT != null) {
LEFT[LEFT_OFFSET + 0] = x1;
LEFT[LEFT_OFFSET + 1] = y1;
}
if (RIGHT != null) {
RIGHT[RIGHT_OFFSET + 4] = x2;
RIGHT[RIGHT_OFFSET + 5] = y2;
}
x1 = (x1 + ctrlx) / 2.0;
y1 = (y1 + ctrly) / 2.0;
x2 = (x2 + ctrlx) / 2.0;
y2 = (y2 + ctrly) / 2.0;
ctrlx = (x1 + x2) / 2.0;
ctrly = (y1 + y2) / 2.0;
if (LEFT != null) {
LEFT[LEFT_OFFSET + 2] = x1;
LEFT[LEFT_OFFSET + 3] = y1;
LEFT[LEFT_OFFSET + 4] = ctrlx;
LEFT[LEFT_OFFSET + 5] = ctrly;
}
if (RIGHT != null) {
RIGHT[RIGHT_OFFSET + 0] = ctrlx;
RIGHT[RIGHT_OFFSET + 1] = ctrly;
RIGHT[RIGHT_OFFSET + 2] = x2;
RIGHT[RIGHT_OFFSET + 3] = y2;
}
}
public static int solveQuadratic(final double EQN[]) { return solveQuadratic(EQN, EQN); }
public static int solveQuadratic(final double EQN[], final double RES[]) {
double a = EQN[2];
double b = EQN[1];
double c = EQN[0];
int roots = 0;
if (Double.compare(a, 0) == 0) {
if (Double.compare(b, 0) == 0) { return -1; }
RES[roots++] = -c / b;
} else {
double d = b * b - 4.0 * a * c;
if (d < 0) { return 0; }
d = Math.sqrt(d);
if (b < 0) { d = -d; }
double q = (b + d) / -2.0;
RES[roots++] = q / a;
if (q != 0) { RES[roots++] = c / q; }
}
return roots;
}
public boolean contains(Point POINT) { return contains(POINT.x, POINT.y); }
public boolean contains(double X, double Y) {
double x1 = this.x1;
double y1 = this.y1;
double xc = this.ctrlx;
double yc = this.ctrly;
double x2 = this.x2;
double y2 = this.y2;
double kx = x1 - 2 * xc + x2;
double ky = y1 - 2 * yc + y2;
double dx = X - x1;
double dy = Y - y1;
double dxl = x2 - x1;
double dyl = y2 - y1;
double t0 = (dx * ky - dy * kx) / (dxl * ky - dyl * kx);
if (t0 < 0 || t0 > 1 || t0 != t0) { return false; }
double xb = kx * t0 * t0 + 2 * (xc - x1) * t0 + x1;
double yb = ky * t0 * t0 + 2 * (yc - y1) * t0 + y1;
double xl = dxl * t0 + x1;
double yl = dyl * t0 + y1;
return (X >= xb && X < xl) ||
(X >= xl && X < xb) ||
(Y >= yb && Y < yl) ||
(Y >= yl && Y < yb);
}
private static void fillEqn(final double EQN[], final double VAL, final double C1, final double CP, final double C2) {
EQN[0] = C1 - VAL;
EQN[1] = CP + CP - C1 - C1;
EQN[2] = C1 - CP - CP + C2;
}
private static int evalQuadratic(final double VALUES[], final int NUM, final boolean INCLUDE_0, final boolean INCLUDE_1, final double INFLECT[], final double C1, final double CTRL, final double C2) {
int j = 0;
for (int i = 0; i < NUM; i++) {
double t = VALUES[i];
if ((INCLUDE_0 ? t >= 0 : t > 0) && (INCLUDE_1 ? t <= 1 : t < 1) && (INFLECT == null || INFLECT[1] + 2*INFLECT[2]*t != 0)) {
double u = 1 - t;
VALUES[j++] = C1*u*u + 2*CTRL*t*u + C2*t*t;
}
}
return j;
}
private static int getTag(final double COORD, final double LOW, final double HIGH) {
if (COORD <= LOW) { return (COORD < LOW ? BELOW : LOW_EDGE); }
if (COORD >= HIGH) { return (COORD > HIGH ? ABOVE : HIGH_EDGE); }
return INSIDE;
}
private static boolean inwards(final int PT_TAG, final int OPT_1_TAG, final int OPT_2_TAG) {
switch (PT_TAG) {
case BELOW :
case ABOVE :
default : return false;
case LOW_EDGE : return (OPT_1_TAG >= INSIDE || OPT_2_TAG >= INSIDE);
case INSIDE : return true;
case HIGH_EDGE: return (OPT_1_TAG <= INSIDE || OPT_2_TAG <= INSIDE);
}
}
public boolean intersects(double X, double Y, double WIDTH, double HEIGHT) {
if (WIDTH <= 0 || HEIGHT <= 0) { return false; }
double x1 = this.x1;
double y1 = this.y1;
int x1tag = getTag(x1, X, X + WIDTH);
int y1tag = getTag(y1, Y, Y + HEIGHT);
if (x1tag == INSIDE && y1tag == INSIDE) { return true; }
double x2 = this.x2;
double y2 = this.y2;
int x2tag = getTag(x2, X, X + WIDTH);
int y2tag = getTag(y2, Y, Y + HEIGHT);
if (x2tag == INSIDE && y2tag == INSIDE) { return true; }
double ctrlx = this.ctrlx;
double ctrly = this.ctrly;
int ctrlxtag = getTag(ctrlx, X, X + WIDTH);
int ctrlytag = getTag(ctrly, Y, Y + HEIGHT);
if (x1tag < INSIDE && x2tag < INSIDE && ctrlxtag < INSIDE) { return false; }
if (y1tag < INSIDE && y2tag < INSIDE && ctrlytag < INSIDE) { return false; }
if (x1tag > INSIDE && x2tag > INSIDE && ctrlxtag > INSIDE) { return false; }
if (y1tag > INSIDE && y2tag > INSIDE && ctrlytag > INSIDE) { return false; }
if (inwards(x1tag, x2tag, ctrlxtag) && inwards(y1tag, y2tag, ctrlytag)) { return true; }
if (inwards(x2tag, x1tag, ctrlxtag) && inwards(y2tag, y1tag, ctrlytag)) { return true; }
boolean xoverlap = (x1tag * x2tag <= 0);
boolean yoverlap = (y1tag * y2tag <= 0);
if (x1tag == INSIDE && x2tag == INSIDE && yoverlap) { return true; }
if (y1tag == INSIDE && y2tag == INSIDE && xoverlap) { return true; }
double[] eqn = new double[3];
double[] res = new double[3];
if (!yoverlap) {
fillEqn(eqn, (y1tag < INSIDE ? Y : Y + HEIGHT), y1, ctrly, y2);
return (solveQuadratic(eqn, res) == 2 &&
evalQuadratic(res, 2, true, true, null, x1, ctrlx, x2) == 2 &&
getTag(res[0], X, X + WIDTH) * getTag(res[1], X, X + WIDTH) <= 0);
}
if (!xoverlap) {
fillEqn(eqn, (x1tag < INSIDE ? X : X + WIDTH), x1, ctrlx, x2);
return (solveQuadratic(eqn, res) == 2 &&
evalQuadratic(res, 2, true, true, null, y1, ctrly, y2) == 2 &&
getTag(res[0], Y, Y + HEIGHT) * getTag(res[1], Y, Y + HEIGHT) <= 0);
}
double dx = x2 - x1;
double dy = y2 - y1;
double k = y2 * x1 - x2 * y1;
int c1tag, c2tag;
if (y1tag == INSIDE) {
c1tag = x1tag;
} else {
c1tag = getTag((k + dx * (y1tag < INSIDE ? Y : Y + HEIGHT)) / dy, X, X + WIDTH);
}
if (y2tag == INSIDE) {
c2tag = x2tag;
} else {
c2tag = getTag((k + dx * (y2tag < INSIDE ? Y : Y + HEIGHT)) / dy, X, X + WIDTH);
}
if (c1tag * c2tag <= 0) { return true; }
c1tag = ((c1tag * x1tag <= 0) ? y1tag : y2tag);
fillEqn(eqn, (c2tag < INSIDE ? X : X + WIDTH), x1, ctrlx, x2);
int num = solveQuadratic(eqn, res);
evalQuadratic(res, num, true, true, null, y1, ctrly, y2);
c2tag = getTag(res[0], Y, Y + HEIGHT);
return (c1tag * c2tag <= 0);
}
public boolean contains(double X, double Y, double WIDTH, double HEIGHT) {
if (WIDTH <= 0 || HEIGHT <= 0) { return false; }
return (contains(X, Y) && contains(X + WIDTH, Y) && contains(X + WIDTH, Y + HEIGHT) && contains(X, Y + HEIGHT));
}
public PathIterator getPathIterator(BaseTransform TRANSFORM) { return new QuadIterator(this, TRANSFORM); }
public PathIterator getPathIterator(BaseTransform TRANSFORM, double FLATNESS) { return new FlatteningPathIterator(getPathIterator(TRANSFORM), FLATNESS); }
@Override public QuadCurve copy() { return new QuadCurve(x1, y1, ctrlx, ctrly, x2, y2); }
@Override public int hashCode() {
return Objects.hash(x1, y1, ctrlx, ctrly, x2, y2);
}
@Override public boolean equals(Object obj) {
if (obj == this) { return true; }
if (obj instanceof QuadCurve) {
QuadCurve curve = (QuadCurve) obj;
return ((x1 == curve.x1) && (y1 == curve.y1) && (x2 == curve.x2) && (y2 == curve.y2) && (ctrlx == curve.ctrlx) && (ctrly == curve.ctrly));
}
return false;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy