/*
* $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.d3.ai;
import java.util.Iterator;
import org.eclipse.xtext.xbase.lib.Pure;
import org.arakhne.afc.math.MathConstants;
import org.arakhne.afc.math.geometry.PathElementType;
import org.arakhne.afc.math.geometry.PathWindingRule;
import org.arakhne.afc.vmutil.ReflectionUtil;
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.
*
* @param <B> the type of the bounds.
* @author $Author: sgalland$
* @author $Author: tpiotrow$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
* @since 13.0
*/
public class PathShadow3ai<B extends RectangularPrism3ai<?, ?, ?, ?, ?, B>> {
private final Path3ai<?, ?, ?, ?, ?, B> path;
private final B bounds;
/** Construct new path shadow.
* @param path the path that is constituting the shadow.
*/
public PathShadow3ai(Path3ai<?, ?, ?, ?, ?, B> path) {
assert path != null : AssertMessages.notNullParameter();
this.path = path;
this.bounds = this.path.toBoundingBox();
assert this.bounds != null;
}
/** 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 z0 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.
* @param z1 is the second point of the segment.
* @return the crossings or {@link MathConstants#SHAPE_INTERSECTS}.
*/
@Pure
public int computeCrossings(
int crossings,
int x0, int y0, int z0,
int x1, int y1, int z1) {
int numCrosses =
Segment3ai.computeCrossingsFromRect(crossings,
this.bounds.getMinX(),
this.bounds.getMinY(),
this.bounds.getMinZ(),
this.bounds.getMaxX(),
this.bounds.getMaxY(),
this.bounds.getMaxZ(),
x0, y0, z0,
x1, y1, z1);
if (numCrosses == MathConstants.SHAPE_INTERSECTS) {
// The segment is intersecting the bounds of the shadow path.
// We must consider the shape of shadow path now.
final PathShadowData data = new PathShadowData(
this.bounds.getMaxX(),
this.bounds.getMinY(),
this.bounds.getMaxY());
computeCrossings1(
this.path.getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO),
x0, y0, z0, x1, y1, z1,
false,
this.path.getWindingRule(),
this.path.getGeomFactory(),
data);
numCrosses = data.getCrossings();
final 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.isHasX4ymin() && data.getX4ymin() >= data.getXmin4ymin()) {
++inc;
}
if (data.isHasX4ymax() && data.getX4ymax() >= data.getXmin4ymax()) {
++inc;
}
if (y0 < y1) {
numCrosses += inc;
} else {
numCrosses -= inc;
}
// Apply the previously computed crossings
numCrosses += crossings;
}
return numCrosses;
}
@SuppressWarnings({"checkstyle:npathcomplexity", "checkstyle:cyclomaticcomplexity", "checkstyle:parameternumber"})
private static <E extends PathElement3ai> void computeCrossings1(
Iterator<? extends PathElement3ai> pi,
int x1, int y1, int z1, int x2, int y2, int z2,
boolean closeable,
PathWindingRule rule,
GeomFactory3ai<E, ?, ?, ?> factory,
PathShadowData data) {
if (!pi.hasNext() || data.getCrossings() == MathConstants.SHAPE_INTERSECTS) {
return;
}
PathElement3ai element;
element = pi.next();
if (element.getType() != PathElementType.MOVE_TO) {
throw new IllegalArgumentException(Locale.getString(Path3ai.class, "E1")); //$NON-NLS-1$
}
Path3ai<?, ?, E, ?, ?, ?> localPath;
int movx = element.getToX();
int movy = element.getToY();
int movz = element.getToZ();
int curx = movx;
int cury = movy;
int curz = movz;
int endx;
int endy;
int endz;
while (data.getCrossings() != MathConstants.SHAPE_INTERSECTS && pi.hasNext()) {
element = pi.next();
switch (element.getType()) {
case MOVE_TO:
movx = element.getToX();
curx = movx;
movy = element.getToY();
cury = movy;
movz = element.getToZ();
curz = movz;
break;
case LINE_TO:
endx = element.getToX();
endy = element.getToY();
endz = element.getToZ();
computeCrossings2(
curx, cury, curz,
endx, endy, endz,
x1, y1, z1, x2, y2, z2,
data);
if (data.getCrossings() == MathConstants.SHAPE_INTERSECTS) {
return;
}
curx = endx;
cury = endy;
curz = endz;
break;
case QUAD_TO:
endx = element.getToX();
endy = element.getToY();
endz = element.getToZ();
// only for local use.
localPath = factory.newPath(rule);
localPath.moveTo(curx, cury, curz);
localPath.quadTo(
element.getCtrlX1(), element.getCtrlY1(), element.getCtrlZ1(),
endx, endy, endz);
computeCrossings1(
localPath.getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO),
x1, y1, z1, x2, y2, z2,
false,
rule,
factory,
data);
if (data.getCrossings() == MathConstants.SHAPE_INTERSECTS) {
return;
}
curx = endx;
cury = endy;
curz = endz;
break;
case CURVE_TO:
endx = element.getToX();
endy = element.getToY();
endz = element.getToZ();
// only for local use.
localPath = factory.newPath(rule);
localPath.moveTo(curx, cury, curz);
localPath.curveTo(
element.getCtrlX1(), element.getCtrlY1(), element.getCtrlZ1(),
element.getCtrlX2(), element.getCtrlY2(), element.getCtrlZ2(),
endx, endy, endz);
computeCrossings1(
localPath.getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO),
x1, y1, z1, x2, y2, z2,
false,
rule,
factory,
data);
if (data.getCrossings() == MathConstants.SHAPE_INTERSECTS) {
return;
}
curx = endx;
cury = endy;
curz = endz;
break;
case CLOSE:
if (cury != movy || curx != movx || curz != movz) {
computeCrossings2(
curx, cury, curz,
movx, movy, movz,
x1, y1, z1, x2, y2, z2,
data);
}
if (data.getCrossings() != 0) {
return;
}
curx = movx;
cury = movy;
curz = movz;
break;
case ARC_TO:
default:
}
}
assert data.getCrossings() != MathConstants.SHAPE_INTERSECTS;
final boolean isOpen = (curx != movx) || (cury != movy) || (curz != movz);
if (isOpen) {
if (closeable) {
computeCrossings2(
curx, cury, curz,
movx, movy, movz,
x1, y1, z1, x2, y2, z2,
data);
} else {
// Assume that when is the path is open, only
// SHAPE_INTERSECTS may be return
data.setCrossings(0);
}
}
}
@SuppressWarnings({"checkstyle:parameternumber", "checkstyle:npathcomplexity", "checkstyle:cyclomaticcomplexity"})
private static void computeCrossings2(
int shadowX0, int shadowY0, int shadowZ0,
int shadowX1, int shadowY1, int shadowZ1,
int sx0, int sy0, int sz0,
int sx1, int sy1, int sz1,
PathShadowData data) {
final int shadowXmin = Math.min(shadowX0, shadowX1);
final int shadowXmax = Math.max(shadowX0, shadowX1);
final int shadowYmin = Math.min(shadowY0, shadowY1);
final int shadowYmax = Math.max(shadowY0, shadowY1);
//TODO final int shadowZmin = Math.min(shadow_z0, shadow_z1);
//TODO final int shadowZmax = Math.max(shadow_z0, shadow_z1);
data.updateShadowLimits(shadowX0, shadowY0, shadowZ0, shadowX1, shadowY1, shadowZ1);
if (sy0 < shadowYmin && sy1 < shadowYmin) {
return;
}
if (sy0 > shadowYmax && sy1 > shadowYmax) {
return;
}
if (sx0 < shadowXmin && sx1 < shadowXmin) {
return;
}
if (sx0 > shadowXmax && sx1 > shadowXmax) {
// The line is entirely at the right of the shadow
if (sy1 != sy0) {
final double alpha = (sx1 - sx0) / (sy1 - sy0);
if (sy0 < sy1) {
if (sy0 <= shadowYmin) {
final int xintercept = (int) Math.round(sx0 + (shadowYmin - sy0) * alpha);
data.setCrossingForYMin(xintercept, shadowYmin);
data.setCrossings(data.getCrossings() + 1);
}
if (sy1 >= shadowYmax) {
final int xintercept = (int) Math.round(sx0 + (shadowYmax - sy0) * alpha);
data.setCrossingForYMax(xintercept, shadowYmax);
data.setCrossings(data.getCrossings() + 1);
}
} else {
if (sy1 <= shadowYmin) {
final int xintercept = (int) Math.round(sx0 + (shadowYmin - sy0) * alpha);
data.setCrossingForYMin(xintercept, shadowYmin);
data.setCrossings(data.getCrossings() - 1);
}
if (sy0 >= shadowYmax) {
final int xintercept = (int) Math.round(sx0 + (shadowYmax - sy0) * alpha);
data.setCrossingForYMax(xintercept, shadowYmax);
data.setCrossings(data.getCrossings() - 1);
}
}
}
} else if (Segment3ai.intersectsSegmentSegment(
shadowX0, shadowY0, shadowZ0, shadowX1, shadowY1, shadowZ1,
sx0, sy0, sz0, sx1, sy1, sz1)) {
data.setCrossings(MathConstants.SHAPE_INTERSECTS);
} else {
final int side1;
final int side2;
final boolean isUp = shadowY0 <= shadowY1;
if (isUp) {
side1 = Segment3ai.computeSideLinePoint(
shadowX0, shadowY0, shadowZ0,
shadowX1, shadowY1, shadowZ1,
sx0, sy0, sz0);
side2 = Segment3ai.computeSideLinePoint(
shadowX0, shadowY0, shadowZ0,
shadowX1, shadowY1, shadowZ1,
sx1, sy1, sz1);
} else {
side1 = Segment3ai.computeSideLinePoint(
shadowX1, shadowY1, shadowZ1,
shadowX0, shadowY0, shadowZ0,
sx0, sy0, sz0);
side2 = Segment3ai.computeSideLinePoint(
shadowX1, shadowY1, shadowZ1,
shadowX0, shadowY0, shadowZ0,
sx1, sy1, sz1);
}
if (side1 > 0 || side2 > 0) {
computeCrossings3(
shadowX0, shadowY0, shadowZ0,
sx0, sy0, sz0, sx1, sy1, sz1,
data, isUp);
computeCrossings3(
shadowX1, shadowY1, shadowZ1,
sx0, sy0, sz0, sx1, sy1, sz1,
data, !isUp);
}
}
}
@SuppressWarnings("checkstyle:parameternumber")
private static void computeCrossings3(
int shadowx, int shadowy, int shadowz,
int sx0, int sy0, int sz0,
int sx1, int sy1, int sz1,
PathShadowData data,
boolean isUp) {
if (shadowy < sy0 && shadowy < sy1) {
return;
}
if (shadowy > sy0 && shadowy > sy1) {
return;
}
if (shadowx > sx0 && shadowx > sx1) {
return;
}
final int xintercept = (int) Math.round((double) sx0 + (shadowy - sy0) * (sx1 - sx0) / (sy1 - sy0));
if (shadowx > xintercept) {
return;
}
if (isUp) {
data.setCrossingForYMax(xintercept, shadowy);
} else {
data.setCrossingForYMin(xintercept, shadowy);
}
data.setCrossings(data.getCrossings() + ((sy0 < sy1) ? 1 : -1));
}
/** Data for shadow.
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
*/
private static class PathShadowData {
private int crossings;
private boolean hasX4ymin;
private boolean hasX4ymax;
private int x4ymin;
private int x4ymax;
private int xmin4ymin;
private int xmin4ymax;
private int ymin;
private int ymax;
PathShadowData(int xmax, int miny, int maxy) {
this.setX4ymin(this.setX4ymax(xmax));
this.setXmin4ymax(this.setXmin4ymin(xmax));
this.setYmin(miny);
this.setYmax(maxy);
}
@Pure
@Override
public String toString() {
return ReflectionUtil.toString(this);
}
public void setCrossingForYMax(int x, int y) {
if (y >= this.getYmax() && x < this.getX4ymax()) {
this.setX4ymax(x);
this.setHasX4ymax(true);
}
}
public void setCrossingForYMin(int x, int y) {
if (y <= this.getYmin() && x < this.getX4ymin()) {
this.setX4ymin(x);
this.setHasX4ymin(true);
}
}
public void updateShadowLimits(int shadowX0, int shadowY0, int shadowZ0, int shadowX1, int shadowY1, int shadowZ1) {
final int xl;
final int yl;
final int xh;
final int yh;
if (shadowY0 < shadowY1) {
xl = shadowX0;
yl = shadowY0;
xh = shadowX1;
yh = shadowY1;
} else if (shadowY1 < shadowY0) {
xl = shadowX1;
yl = shadowY1;
xh = shadowX0;
yh = shadowY0;
} else {
xl = Math.min(shadowX0, shadowX1);
xh = xl;
yl = shadowY0;
yh = yl;
}
if (yl <= this.getYmin() && xl < this.getXmin4ymin()) {
this.setXmin4ymin(xl);
}
if (yh >= this.getYmax() && xh < this.getXmin4ymax()) {
this.setXmin4ymax(xh);
}
}
public int getCrossings() {
return this.crossings;
}
public void setCrossings(int crossings) {
this.crossings = crossings;
}
public boolean isHasX4ymin() {
return this.hasX4ymin;
}
public void setHasX4ymin(boolean hasX4ymin) {
this.hasX4ymin = hasX4ymin;
}
public boolean isHasX4ymax() {
return this.hasX4ymax;
}
public void setHasX4ymax(boolean hasX4ymax) {
this.hasX4ymax = hasX4ymax;
}
public int getX4ymin() {
return this.x4ymin;
}
public void setX4ymin(int x4ymin) {
this.x4ymin = x4ymin;
}
public int getX4ymax() {
return this.x4ymax;
}
public int setX4ymax(int x4ymax) {
this.x4ymax = x4ymax;
return x4ymax;
}
public int getXmin4ymin() {
return this.xmin4ymin;
}
public int setXmin4ymin(int xmin4ymin) {
this.xmin4ymin = xmin4ymin;
return xmin4ymin;
}
public int getXmin4ymax() {
return this.xmin4ymax;
}
public void setXmin4ymax(int xmin4ymax) {
this.xmin4ymax = xmin4ymax;
}
public int getYmin() {
return this.ymin;
}
public void setYmin(int ymin) {
this.ymin = ymin;
}
public int getYmax() {
return this.ymax;
}
public void setYmax(int ymax) {
this.ymax = ymax;
}
}
}