/*
* $Id$
* This file is a part of the Arakhne Foundation Classes, http://www.arakhne.org/afc
*
* Copyright (c) 2000-2012 Stephane GALLAND.
* Copyright (c) 2005-10, Multiagent Team, Laboratoire Systemes et Transports,
* Universite de Technologie de Belfort-Montbeliard.
* Copyright (c) 2013-2016 The original authors, and other authors.
*
* 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 org.arakhne.afc.math.geometry.d2.afp;
import org.arakhne.afc.math.MathConstants;
import org.arakhne.afc.math.MathUtil;
import org.arakhne.afc.math.geometry.PathElementType;
import org.arakhne.afc.math.geometry.PathWindingRule;
import org.arakhne.afc.vmutil.asserts.AssertMessages;
import org.arakhne.afc.vmutil.locale.Locale;
/** Shadow of a path that is used for computing the crossing values
* between a shape and the shadow.
*
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
* @since 13.0
*/
class BasicPathShadow2afp {
private final PathIterator2afp<?> pathIterator;
private CoordinatesParam coordinateParam;
private final double boundingMinX;
private final double boundingMinY;
private final double boundingMaxX;
private final double boundingMaxY;
private boolean started;
private int crossings;
private boolean hasX4ymin;
private boolean hasX4ymax;
private double x4ymin;
private double x4ymax;
/** Construct new path shadow.
* @param path the path that is constituting the shadow.
*/
BasicPathShadow2afp(Path2afp<?, ?, ?, ?, ?, ?> path) {
this(path.getPathIterator(), path.toBoundingBox());
}
/** Construct new path shadow.
* @param pathIterator the iterator on the path that is constituting the shadow.
* @param bounds the bounds of the shadow.
*/
BasicPathShadow2afp(PathIterator2afp<?> pathIterator, Rectangle2afp<?, ?, ?, ?, ?, ?> bounds) {
assert pathIterator != null : AssertMessages.notNullParameter(0);
assert bounds != null : AssertMessages.notNullParameter(1);
this.pathIterator = pathIterator;
this.boundingMinX = bounds.getMinX();
this.boundingMinY = bounds.getMinY();
this.boundingMaxX = bounds.getMaxX();
this.boundingMaxY = bounds.getMaxY();
}
/** Construct new path shadow.
* @param pathIterator the iterator on the path that is constituting the shadow.
* @param minX x coordinate of the lower corner of the shadow's bouding box.
* @param minY y coordinate of the lower corner of the shadow's bouding box.
* @param maxX x coordinate of the upper corner of the shadow's bouding box.
* @param maxY y coordinate of the upper corner of the shadow's bouding box.
*/
@SuppressWarnings("checkstyle:magicnumber")
BasicPathShadow2afp(PathIterator2afp<?> pathIterator, double minX, double minY, double maxX, double maxY) {
assert pathIterator != null : AssertMessages.notNullParameter(0);
assert minX <= maxX : AssertMessages.lowerEqualParameters(1, minX, 3, maxX);
assert minY <= maxY : AssertMessages.lowerEqualParameters(2, minY, 4, maxY);
this.pathIterator = pathIterator;
this.boundingMinX = minX;
this.boundingMinY = minY;
this.boundingMaxX = maxX;
this.boundingMaxY = maxY;
}
/** Compute the crossings between this shadow and
* the given segment.
*
* @param crossings is the initial value of the crossings.
* @param x0 is the first point of the segment.
* @param y0 is the first point of the segment.
* @param x1 is the second point of the segment.
* @param y1 is the second point of the segment.
* @return the crossings or {@link MathConstants#SHAPE_INTERSECTS}.
*/
@SuppressWarnings("checkstyle:npathcomplexity")
public int computeCrossings(
int crossings,
double x0, double y0,
double x1, double y1) {
int numCrosses =
Segment2afp.calculatesCrossingsRectangleShadowSegment(crossings,
this.boundingMinX,
this.boundingMinY,
this.boundingMaxX,
this.boundingMaxY,
x0, y0,
x1, y1);
if (numCrosses == MathConstants.SHAPE_INTERSECTS) {
// The segment is intersecting the bounds of the shadow path.
// We must consider the shape of shadow path now.
this.x4ymin = this.boundingMinX;
this.x4ymax = this.boundingMinX;
this.crossings = 0;
this.hasX4ymin = false;
this.hasX4ymax = false;
final PathIterator2afp<?> iterator;
if (this.started) {
iterator = this.pathIterator.restartIterations();
} else {
this.started = true;
iterator = this.pathIterator;
}
discretizePathIterator(
iterator,
x0, y0, x1, y1);
// Test if the shape is intesecting the shadow shape.
final int mask = iterator.getWindingRule() == PathWindingRule.NON_ZERO ? -1 : 2;
if (this.crossings == MathConstants.SHAPE_INTERSECTS
|| (this.crossings & mask) != 0) {
// The given line is intersecting the path shape
return MathConstants.SHAPE_INTERSECTS;
}
// There is no intersection with the shadow path's shape.
// Compute the crossings with the minimum/maximum y borders.
int inc = 0;
if (this.hasX4ymin) {
++inc;
}
if (this.hasX4ymax) {
++inc;
}
if (y0 < y1) {
numCrosses = inc;
} else {
numCrosses = -inc;
}
// Apply the previously computed crossings
numCrosses += crossings;
}
return numCrosses;
}
@SuppressWarnings({"checkstyle:parameternumber", "checkstyle:cyclomaticcomplexity",
"checkstyle:npathcomplexity"})
private void discretizePathIterator(
PathIterator2afp<?> pi,
double x1, double y1, double x2, double y2) {
if (!pi.hasNext() || this.crossings == MathConstants.SHAPE_INTERSECTS) {
return;
}
PathElement2afp element;
element = pi.next();
if (element.getType() != PathElementType.MOVE_TO) {
throw new IllegalArgumentException(Locale.getString(Path2afp.class, "E1")); //$NON-NLS-1$
}
double movx = element.getToX();
double movy = element.getToY();
double curx = movx;
double cury = movy;
while (pi.hasNext()) {
this.coordinateParam = new BasicPathShadow2afp.CoordinatesParam(x1, y1, x2, y2, curx, cury);
element = pi.next();
switch (element.getType()) {
case MOVE_TO:
movx = element.getToX();
curx = movx;
movy = element.getToY();
cury = movy;
break;
case LINE_TO:
setLineToFromDiscretizePathIterator(element, this.coordinateParam);
if (this.crossings == MathConstants.SHAPE_INTERSECTS) {
return;
}
curx = element.getToX();
cury = element.getToY();
break;
case QUAD_TO:
// only for local use.
setQuadToFromDiscretizePathIterator(element, pi, this.coordinateParam);
if (this.crossings == MathConstants.SHAPE_INTERSECTS) {
return;
}
curx = element.getToX();
cury = element.getToY();
break;
case CURVE_TO:
// only for local use.
setCurveToFromDiscretizePathIterator(element, pi, this.coordinateParam);
if (this.crossings == MathConstants.SHAPE_INTERSECTS) {
return;
}
curx = element.getToX();
cury = element.getToY();
break;
case ARC_TO:
// only for local use.
setArcToFromDiscretizePathIterator(element, pi, this.coordinateParam);
if (this.crossings == MathConstants.SHAPE_INTERSECTS) {
return;
}
curx = element.getToX();
cury = element.getToY();
break;
case CLOSE:
setCloseFromDiscretizePathIterator(this.coordinateParam, movx, movy);
if (this.crossings != 0) {
return;
}
curx = movx;
cury = movy;
break;
default:
}
}
assert this.crossings != MathConstants.SHAPE_INTERSECTS;
final boolean isOpen = (curx != movx) || (cury != movy);
if (isOpen) {
// Assume that when is the path is open, only
// SHAPE_INTERSECTS may be return
this.crossings = 0;
}
}
private void setLineToFromDiscretizePathIterator(PathElement2afp elm, CoordinatesParam param) {
final double endx = elm.getToX();
final double endy = elm.getToY();
crossSegmentTwoShadowLines(
param.getCurx(), param.getCury(),
endx, endy,
param.getX1(), param.getY1(), param.getX2(), param.getY2());
}
private void setQuadToFromDiscretizePathIterator(PathElement2afp elm, PathIterator2afp<?> pi, CoordinatesParam param) {
final Path2afp<?, ?, ?, ?, ?, ?> localPath;
final double endx = elm.getToX();
final double endy = elm.getToY();
localPath = pi.getGeomFactory().newPath(pi.getWindingRule());
localPath.moveTo(param.getCurx(), param.getCury());
localPath.quadTo(
elm.getCtrlX1(), elm.getCtrlY1(),
endx, endy);
discretizePathIterator(
localPath.getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO),
param.getX1(), param.getY1(), param.getX2(), param.getY2());
}
private void setCurveToFromDiscretizePathIterator(PathElement2afp elm, PathIterator2afp<?> pi, CoordinatesParam prm) {
final Path2afp<?, ?, ?, ?, ?, ?> localPath;
final double endx = elm.getToX();
final double endy = elm.getToY();
localPath = pi.getGeomFactory().newPath(pi.getWindingRule());
localPath.moveTo(prm.getCurx(), prm.getCury());
localPath.curveTo(
elm.getCtrlX1(), elm.getCtrlY1(), elm.getCtrlX2(), elm.getCtrlY2(),
endx, endy);
discretizePathIterator(
localPath.getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO),
prm.getX1(), prm.getY1(), prm.getX2(), prm.getY2());
}
private void setArcToFromDiscretizePathIterator(PathElement2afp elm, PathIterator2afp<?> pi, CoordinatesParam param) {
final Path2afp<?, ?, ?, ?, ?, ?> localPath;
final double endx = elm.getToX();
final double endy = elm.getToY();
localPath = pi.getGeomFactory().newPath(pi.getWindingRule());
localPath.moveTo(param.getCurx(), param.getCury());
localPath.arcTo(
endx, endy,
elm.getRadiusX(), elm.getRadiusY(),
elm.getRotationX(), elm.getLargeArcFlag(),
elm.getSweepFlag());
discretizePathIterator(
localPath.getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO),
param.getX1(), param.getY1(), param.getX2(), param.getY2());
}
private void setCloseFromDiscretizePathIterator(CoordinatesParam param, double movx, double movy) {
if (param.getCury() != movy || param.getCurx() != movx) {
crossSegmentTwoShadowLines(
param.getCurx(), param.getCury(),
movx, movy,
param.getX1(), param.getY1(), param.getX2(), param.getY2());
}
}
private void setCrossingCoordinateForYMax(double x, double y) {
if (MathUtil.compareEpsilon(y, this.boundingMaxY) >= 0 && x > this.x4ymax) {
this.x4ymax = x;
this.hasX4ymax = true;
}
}
private void setCrossingCoordinateForYMin(double x, double y) {
if (MathUtil.compareEpsilon(y, this.boundingMinY) <= 0 && x > this.x4ymin) {
this.x4ymin = x;
this.hasX4ymin = true;
}
}
/** Determine where the segment is crossing the two shadow lines.
*
* @param shadowX0 x coordinate of the reference point of the first shadow line.
* @param shadowY0 y coordinate of the reference point of the first shadow line.
* @param shadowX1 x coordinate of the reference point of the second shadow line.
* @param shadowY1 y coordinate of the reference point of the second shadow line.
* @param sx0 x coordinate of the first point of the segment.
* @param sy0 y coordinate of the first point of the segment.
* @param sx1 x coordinate of the second point of the segment.
* @param sy1 y coordinate of the second point of the segment.
*/
@SuppressWarnings({"checkstyle:parameternumber", "checkstyle:cyclomaticcomplexity",
"checkstyle:npathcomplexity"})
private void crossSegmentTwoShadowLines(
double shadowX0, double shadowY0,
double shadowX1, double shadowY1,
double sx0, double sy0,
double sx1, double sy1) {
// Update the global bounds of the shadow.
final double shadowXmin = Math.min(shadowX0, shadowX1);
final double shadowXmax = Math.max(shadowX0, shadowX1);
final double shadowYmin = Math.min(shadowY0, shadowY1);
final double shadowYmax = Math.max(shadowY0, shadowY1);
if (sy0 < shadowYmin && sy1 < shadowYmin) {
// The segment is entirely at the bottom of the shadow.
return;
}
if (sy0 > shadowYmax && sy1 > shadowYmax) {
// The segment is entirely at the top of the shadow.
return;
}
if (sx0 < shadowXmin && sx1 < shadowXmin) {
// The segment is entirely at the left of the shadow.
return;
}
if (sx0 >= shadowXmax && sx1 >= shadowXmax) {
// The line is entirely at the right of the shadow
final double alpha = (sx1 - sx0) / (sy1 - sy0);
if (sy0 < sy1) {
if (sy0 <= shadowYmin) {
final double xintercept = sx0 + (shadowYmin - sy0) * alpha;
setCrossingCoordinateForYMin(xintercept, shadowYmin);
++this.crossings;
}
if (sy1 >= shadowYmax) {
final double xintercept = sx0 + (shadowYmax - sy0) * alpha;
setCrossingCoordinateForYMax(xintercept, shadowYmax);
++this.crossings;
}
} else {
if (sy1 <= shadowYmin) {
final double xintercept = sx0 + (shadowYmin - sy0) * alpha;
setCrossingCoordinateForYMin(xintercept, shadowYmin);
--this.crossings;
}
if (sy0 >= shadowYmax) {
final double xintercept = sx0 + (shadowYmax - sy0) * alpha;
setCrossingCoordinateForYMax(xintercept, shadowYmax);
--this.crossings;
}
}
} else if (Segment2afp.intersectsSegmentSegmentWithoutEnds(
shadowX0, shadowY0, shadowX1, shadowY1,
sx0, sy0, sx1, sy1)) {
// The segment is intersecting the shadowed segment.
this.crossings = MathConstants.SHAPE_INTERSECTS;
} else {
final int side1;
final int side2;
final boolean isUp = shadowY0 <= shadowY1;
if (isUp) {
side1 = Segment2afp.findsSideLinePoint(
shadowX0, shadowY0,
shadowX1, shadowY1,
sx0, sy0, 0.);
side2 = Segment2afp.findsSideLinePoint(
shadowX0, shadowY0,
shadowX1, shadowY1,
sx1, sy1, 0.);
} else {
side1 = Segment2afp.findsSideLinePoint(
shadowX1, shadowY1,
shadowX0, shadowY0,
sx0, sy0, 0.);
side2 = Segment2afp.findsSideLinePoint(
shadowX1, shadowY1,
shadowX0, shadowY0,
sx1, sy1, 0.);
}
if (side1 > 0 || side2 > 0) {
final double x0;
final double x1;
if (shadowY0 <= shadowY1) {
x0 = shadowX0;
x1 = shadowX1;
} else {
x0 = shadowX1;
x1 = shadowX0;
}
crossSegmentShadowLine(
x1, shadowYmax,
sx0, sy0, sx1, sy1,
isUp);
crossSegmentShadowLine(
x0, shadowYmin,
sx0, sy0, sx1, sy1,
!isUp);
}
}
}
/** Determine where the segment is crossing the shadow line.
*
* @param shadowx x coordinate of the reference point of the shadow line.
* @param shadowy y coordinate of the reference point of the shadow line.
* @param sx0 x coordinate of the first point of the segment.
* @param sy0 y coordinate of the first point of the segment.
* @param sx1 x coordinate of the second point of the segment.
* @param sy1 y coordinate of the second point of the segment.
* @param isMax <code>true</code> if the shadow line is for ymax; <code>false</code> if it is for
* ymin.
*/
private void crossSegmentShadowLine(
double shadowx, double shadowy,
double sx0, double sy0,
double sx1, double sy1,
boolean isMax) {
if (shadowy < sy0 && shadowy < sy1) {
// Segment is entirely at the top of shadow line
return;
}
if (shadowy > sy0 && shadowy > sy1) {
// Segment is entirely at the bottom of the shadow line
return;
}
if (shadowx > sx0 && shadowx > sx1) {
// Segment is entirely at the left of the shadow line
return;
}
// Compute the intersection point between the segment and the shadow line
final double xintercept = sx0 + (shadowy - sy0) * (sx1 - sx0) / (sy1 - sy0);
if (shadowx > xintercept) {
// The intersection point is on the left of the shadow line.
return;
}
if (isMax) {
setCrossingCoordinateForYMax(xintercept, shadowy);
} else {
setCrossingCoordinateForYMin(xintercept, shadowy);
}
if (sy0 < sy1) {
++this.crossings;
} else {
--this.crossings;
}
}
/** Passing params to methods via this class in order to reduce the line length.
* @author $Author: fevzi ozgul$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
*/
private static final class CoordinatesParam {
private double x1;
private double y1;
private double x2;
private double y2;
private double curx;
private double cury;
/** Determine where the segment is crossing the shadow line.
*
* @param x1 x coordinate.
* @param y1 y coordinate .
* @param x2 x coordinate of the end point .
* @param y2 y coordinate of the end point .
* @param curx current xcoordinate .
* @param cury current y coordinate .
*/
CoordinatesParam(double x1, double y1, double x2, double y2, double curx, double cury) {
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
this.curx = curx;
this.cury = cury;
}
public double getX1() {
return this.x1;
}
public double getY1() {
return this.y1;
}
public double getX2() {
return this.x2;
}
public double getY2() {
return this.y2;
}
public double getCurx() {
return this.curx;
}
public double getCury() {
return this.cury;
}
}
}