/* * $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.continous.object2d; import java.util.Iterator; import org.arakhne.afc.math.MathConstants; import org.arakhne.afc.math.generic.Path2D; import org.arakhne.afc.math.generic.PathElementType; import org.arakhne.afc.math.generic.PathWindingRule; import org.arakhne.afc.math.geometry.d2.afp.Segment2afp; import org.arakhne.afc.vmutil.ReflectionUtil; /** Shadow of a path. * * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ * @deprecated since 13.0, not public replacement, see {@code BasicPathShadow2afp}. */ @Deprecated @SuppressWarnings("all") public class PathShadow2f { private final Path2D<?,Rectangle2f,PathElement2f,PathIterator2f> path; private final Rectangle2f bounds; /** * @param path */ public PathShadow2f(Path2D<?,Rectangle2f,PathElement2f,PathIterator2f> path) { this.path = path; this.bounds = this.path.toBoundingBox(); } /** 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}. */ public int computeCrossings( int crossings, float x0, float y0, float x1, float y1) { if (this.bounds==null) return crossings; int numCrosses = Segment2f.computeCrossingsFromRect(crossings, this.bounds.getMinX(), this.bounds.getMinY(), this.bounds.getMaxX(), this.bounds.getMaxY(), 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. PathShadowData data = new PathShadowData( this.bounds.getMaxX(), this.bounds.getMinY(), this.bounds.getMaxY()); computeCrossings1( this.path.getPathIterator((float) MathConstants.SPLINE_APPROXIMATION_RATIO), x0, y0, x1, y1, false, data); numCrosses = data.crossings; int mask = (this.path.getWindingRule() == PathWindingRule.NON_ZERO ? -1 : 2); if (numCrosses == MathConstants.SHAPE_INTERSECTS || (numCrosses & mask) != 0) { // The given line is intersecting the path shape return MathConstants.SHAPE_INTERSECTS; } // There is no intersection with the shadow path's shape. int inc = 0; if (data.hasX4ymin && data.x4ymin>=data.xmin4ymin) { ++inc; } if (data.hasX4ymax && data.x4ymax>=data.xmin4ymax) { ++inc; } if (y0<y1) { numCrosses += inc; } else { numCrosses -= inc; } // Apply the previously computed crossings numCrosses += crossings; } return numCrosses; } private static void computeCrossings1( Iterator<PathElement2f> pi, float x1, float y1, float x2, float y2, boolean closeable, PathShadowData data) { if (!pi.hasNext() || data.crossings==MathConstants.SHAPE_INTERSECTS) return; PathElement2f element; element = pi.next(); if (element.type != PathElementType.MOVE_TO) { throw new IllegalArgumentException("missing initial moveto in path definition"); //$NON-NLS-1$ } float movx = element.toX; float movy = element.toY; float curx = movx; float cury = movy; float endx, endy; while (data.crossings!=MathConstants.SHAPE_INTERSECTS && pi.hasNext()) { element = pi.next(); switch (element.type) { case MOVE_TO: movx = curx = element.toX; movy = cury = element.toY; break; case LINE_TO: endx = element.toX; endy = element.toY; computeCrossings2( curx, cury, endx, endy, x1, y1, x2, y2, data); if (data.crossings==MathConstants.SHAPE_INTERSECTS) { return; } curx = endx; cury = endy; break; case QUAD_TO: { endx = element.toX; endy = element.toY; Path2f localPath = new Path2f(); localPath.moveTo(curx, cury); localPath.quadTo( element.ctrlX1, element.ctrlY1, endx, endy); computeCrossings1( localPath.getPathIterator((float) MathConstants.SPLINE_APPROXIMATION_RATIO), x1, y1, x2, y2, false, data); if (data.crossings==MathConstants.SHAPE_INTERSECTS) { return; } curx = endx; cury = endy; break; } case CURVE_TO: endx = element.toX; endy = element.toY; Path2f localPath = new Path2f(); localPath.moveTo(curx, cury); localPath.curveTo( element.ctrlX1, element.ctrlY1, element.ctrlX2, element.ctrlY2, endx, endy); computeCrossings1( localPath.getPathIterator((float) MathConstants.SPLINE_APPROXIMATION_RATIO), x1, y1, x2, y2, false, data); if (data.crossings==MathConstants.SHAPE_INTERSECTS) { return; } curx = endx; cury = endy; break; case CLOSE: if (cury != movy || curx != movx) { computeCrossings2( curx, cury, movx, movy, x1, y1, x2, y2, data); } if (data.crossings!=0) return; curx = movx; cury = movy; break; default: } } assert(data.crossings!=MathConstants.SHAPE_INTERSECTS); boolean isOpen = (curx != movx) || (cury != movy); if (isOpen) { if (closeable) { computeCrossings2( curx, cury, movx, movy, x1, y1, x2, y2, data); } else { // Assume that when is the path is open, only // SHAPE_INTERSECTS may be return data.crossings = 0; } } } private static void computeCrossings2( float shadow_x0, float shadow_y0, float shadow_x1, float shadow_y1, float sx0, float sy0, float sx1, float sy1, PathShadowData data) { float shadow_xmin = Math.min(shadow_x0, shadow_x1); float shadow_xmax = Math.max(shadow_x0, shadow_x1); float shadow_ymin = Math.min(shadow_y0, shadow_y1); float shadow_ymax = Math.max(shadow_y0, shadow_y1); data.updateShadowLimits(shadow_x0, shadow_y0, shadow_x1, shadow_y1); if (sy0<=shadow_ymin && sy1<=shadow_ymin) return; if (sy0>=shadow_ymax && sy1>=shadow_ymax) return; if (sx0<=shadow_xmin && sx1<=shadow_xmin) return; if (sx0>=shadow_xmax && sx1>=shadow_xmax) { float xintercept; // The line is entirely at the right of the shadow float alpha = (sx1 - sx0) / (sy1 - sy0); if (sy0<sy1) { if (sy0<=shadow_ymin) { xintercept = sx0 + (shadow_ymin - sy0) * alpha; data.setCrossingForYMin(xintercept, shadow_ymin); ++data.crossings; } if (sy1>=shadow_ymax) { xintercept = sx0 + (shadow_ymax - sy0) * alpha; data.setCrossingForYMax(xintercept, shadow_ymax); ++data.crossings; } } else { if (sy1<=shadow_ymin) { xintercept = sx0 + (shadow_ymin - sy0) * alpha; data.setCrossingForYMin(xintercept, shadow_ymin); --data.crossings; } if (sy0>=shadow_ymax) { xintercept = sx0 + (shadow_ymax - sy0) * alpha; data.setCrossingForYMax(xintercept, shadow_ymax); --data.crossings; } } } else if (Segment2f.intersectsSegmentSegmentWithoutEnds( shadow_x0, shadow_y0, shadow_x1, shadow_y1, sx0, sy0, sx1, sy1)) { data.crossings = MathConstants.SHAPE_INTERSECTS; } else { int side1, side2; boolean isUp = (shadow_y0<=shadow_y1); if (isUp) { side1 = Segment2afp.findsSideLinePoint( shadow_x0, shadow_y0, shadow_x1, shadow_y1, sx0, sy0, 0); side2 = Segment2afp.findsSideLinePoint( shadow_x0, shadow_y0, shadow_x1, shadow_y1, sx1, sy1, 0); } else { side1 = Segment2afp.findsSideLinePoint( shadow_x1, shadow_y1, shadow_x0, shadow_y0, sx0, sy0, 0); side2 = Segment2afp.findsSideLinePoint( shadow_x1, shadow_y1, shadow_x0, shadow_y0, sx1, sy1, 0); } if (side1>0 || side2>0) { computeCrossings3( shadow_x0, shadow_y0, sx0, sy0, sx1, sy1, data, isUp); computeCrossings3( shadow_x1, shadow_y1, sx0, sy0, sx1, sy1, data, !isUp); } } } private static void computeCrossings3( float shadowx, float shadowy, float sx0, float sy0, float sx1, float sy1, PathShadowData data, boolean isUp) { if (shadowy < sy0 && shadowy < sy1) return; if (shadowy > sy0 && shadowy > sy1) return; if (shadowx > sx0 && shadowx > sx1) return; float xintercept = sx0 + (shadowy - sy0) * (sx1 - sx0) / (sy1 - sy0); if (shadowx > xintercept) return; if (isUp) { data.setCrossingForYMax(xintercept, shadowy); } else { data.setCrossingForYMin(xintercept, shadowy); } data.crossings += (sy0 < sy1) ? 1 : -1; } /** * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ */ private static class PathShadowData { public int crossings = 0; public boolean hasX4ymin = false; public boolean hasX4ymax = false; public float x4ymin; public float x4ymax; public float xmin4ymin; public float xmin4ymax; public float ymin; public float ymax; @Override public String toString() { return ReflectionUtil.toString(this); } public PathShadowData(float xmax, float miny, float maxy) { this.x4ymin = this.x4ymax = xmax; this.xmin4ymax = this.xmin4ymin = xmax; this.ymin = miny; this.ymax = maxy; } public void setCrossingForYMax(float x, float y) { if (y>=this.ymax) { if (x<this.x4ymax) { this.x4ymax = x; this.hasX4ymax = true; } } } public void setCrossingForYMin(float x, float y) { if (y<=this.ymin) { if (x<this.x4ymin) { this.x4ymin = x; this.hasX4ymin = true; } } } public void updateShadowLimits(float shadow_x0, float shadow_y0, float shadow_x1, float shadow_y1) { float xl, yl; float xh, yh; if (shadow_y0<shadow_y1) { xl = shadow_x0; yl = shadow_y0; xh = shadow_x1; yh = shadow_y1; } else if (shadow_y1<shadow_y0) { xl = shadow_x1; yl = shadow_y1; xh = shadow_x0; yh = shadow_y0; } else { xl = xh = Math.min(shadow_x0, shadow_x1); yl = yh = shadow_y0; } if (yl<=this.ymin && xl<this.xmin4ymin) { this.xmin4ymin = xl; } if (yh>=this.ymax && xh<this.xmin4ymax) { this.xmin4ymax = xh; } } } }