/*
* $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.ui.vector.android;
import java.util.NoSuchElementException;
import org.arakhne.afc.math.MathConstants;
import org.arakhne.afc.math.continous.object2d.Circle2f;
import org.arakhne.afc.math.continous.object2d.Ellipse2f;
import org.arakhne.afc.math.continous.object2d.Path2f;
import org.arakhne.afc.math.continous.object2d.PathElement2f;
import org.arakhne.afc.math.continous.object2d.PathElement2f.ClosePathElement2f;
import org.arakhne.afc.math.continous.object2d.PathElement2f.LinePathElement2f;
import org.arakhne.afc.math.continous.object2d.PathElement2f.MovePathElement2f;
import org.arakhne.afc.math.continous.object2d.PathIterator2f;
import org.arakhne.afc.math.continous.object2d.PathShadow2f;
import org.arakhne.afc.math.continous.object2d.Point2f;
import org.arakhne.afc.math.continous.object2d.Rectangle2f;
import org.arakhne.afc.math.continous.object2d.Segment2f;
import org.arakhne.afc.math.continous.object2d.Shape2f;
import org.arakhne.afc.math.generic.Path2D;
import org.arakhne.afc.math.generic.PathWindingRule;
import org.arakhne.afc.math.generic.Point2D;
import org.arakhne.afc.math.matrix.Transform2D;
import android.graphics.Path;
import android.graphics.Path.FillType;
import android.graphics.PathMeasure;
import android.graphics.RectF;
/** Android implementation of the Path2f.
*
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
* @deprecated see JavaFX API
*/
@Deprecated
class AndroidPath implements Shape2f, Path2D<Shape2f,Rectangle2f,PathElement2f,PathIterator2f>, NativeWrapper {
private static final long serialVersionUID = 3395487582331474537L;
private Path path;
/**
* @param path
*/
public AndroidPath(Path path) {
assert(path!=null);
this.path = path;
if (this.path.isInverseFillType()) {
this.path.toggleInverseFillType();
}
}
@Override
public AndroidPath clone() {
try {
AndroidPath p = (AndroidPath)super.clone();
p.path = new Path(this.path);
return p;
}
catch (CloneNotSupportedException e) {
throw new Error(e);
}
}
@Override
public void set(Shape2f s) {
this.path.reset();
PathIterator2f iterator = s.getPathIterator();
PathElement2f element;
while (iterator.hasNext()) {
element = iterator.next();
switch(element.getType()) {
case MOVE_TO:
this.path.moveTo(element.toX, element.toY);
break;
case LINE_TO:
this.path.lineTo(element.toX, element.toY);
break;
case QUAD_TO:
this.path.quadTo(
element.ctrlX1, element.ctrlY1,
element.toX, element.toY);
break;
case CURVE_TO:
this.path.cubicTo(
element.ctrlX1, element.ctrlY1,
element.ctrlX2, element.ctrlY2,
element.toX, element.toY);
break;
case CLOSE:
this.path.close();
break;
default:
}
}
}
@Override
public boolean isPolyline() {
return false;
}
/** Replies the native path.
*
* @return the native path.
*/
public Path getPath() {
return this.path;
}
@Override
public void clear() {
this.path.reset();
}
@Override
public Point2D getClosestPointTo(Point2D p) {
return Path2f.getClosestPointTo(getPathIterator(), p.getX(), p.getY());
}
@Override
public PathWindingRule getWindingRule() {
switch(this.path.getFillType()) {
case EVEN_ODD:
case INVERSE_EVEN_ODD:
return PathWindingRule.EVEN_ODD;
case WINDING:
case INVERSE_WINDING:
default:
return PathWindingRule.NON_ZERO;
}
}
@Override
public boolean isEmpty() {
return this.path.isEmpty();
}
@Override
public <T> T getNativeObject(Class<T> type) {
return type.cast(this.path);
}
@Override
public Rectangle2f toBoundingBox() {
RectF r = new RectF();
this.path.computeBounds(r, true);
return new Rectangle2f(r.left, r.top, r.width(), r.height());
}
@Override
public void toBoundingBox(Rectangle2f box) {
RectF r = new RectF();
this.path.computeBounds(r, true);
box.setFromCorners(r.left, r.top, r.right, r.bottom);
}
@Override
public float distance(Point2D p) {
return (float)Math.sqrt(distanceSquared(p));
}
@Override
public float distanceSquared(Point2D p) {
Point2D c = getClosestPointTo(p);
return c.distanceSquared(p);
}
@Override
public float distanceL1(Point2D p) {
Point2D c = getClosestPointTo(p);
return c.distanceL1(p);
}
@Override
public float distanceLinf(Point2D p) {
Point2D c = getClosestPointTo(p);
return c.distanceLinf(p);
}
@Override
public void translate(float dx, float dy) {
this.path.offset(dx, dy);
}
@Override
public Shape2f createTransformedShape(Transform2D transform) {
return new Path2f(getPathIterator(transform));
}
@Override
public boolean contains(Point2D p) {
return contains(p.getX(), p.getY());
}
@Override
public boolean contains(float x, float y) {
return Path2f.contains(getPathIterator((float)MathConstants.SPLINE_APPROXIMATION_RATIO), x, y);
}
@Override
public boolean contains(Rectangle2f r) {
return Path2f.contains(getPathIterator((float)MathConstants.SPLINE_APPROXIMATION_RATIO),
r.getMinX(), r.getMinY(),
r.getWidth(), r.getHeight());
}
@Override
public boolean intersects(Rectangle2f s) {
return Path2f.intersects(getPathIterator((float)MathConstants.SPLINE_APPROXIMATION_RATIO),
s.getMinX(), s.getMinY(),
s.getWidth(), s.getHeight());
}
@Override
public boolean intersects(Ellipse2f s) {
FillType type = this.path.getFillType();
int mask = (type == FillType.WINDING || type == FillType.INVERSE_WINDING ? -1 : 2);
int crossings = Path2f.computeCrossingsFromEllipse(
0,
getPathIterator((float)MathConstants.SPLINE_APPROXIMATION_RATIO),
s.getMinX(), s.getMinY(), s.getWidth(), s.getHeight(),
false, true);
return (crossings == MathConstants.SHAPE_INTERSECTS ||
(crossings & mask) != 0);
}
@Override
public boolean intersects(Circle2f s) {
FillType type = this.path.getFillType();
int mask = (type == FillType.WINDING || type == FillType.INVERSE_WINDING ? -1 : 2);
int crossings = Path2f.computeCrossingsFromCircle(
0,
getPathIterator((float)MathConstants.SPLINE_APPROXIMATION_RATIO),
s.getX(), s.getY(), s.getRadius(),
false,
true);
return (crossings == MathConstants.SHAPE_INTERSECTS ||
(crossings & mask) != 0);
}
@Override
public boolean intersects(Segment2f s) {
FillType type = this.path.getFillType();
int mask = (type == FillType.WINDING || type == FillType.INVERSE_WINDING ? -1 : 2);
int crossings = Path2f.computeCrossingsFromSegment(
0,
getPathIterator((float)MathConstants.SPLINE_APPROXIMATION_RATIO),
s.getX1(), s.getY1(), s.getX2(), s.getY2(),
false);
return (crossings == MathConstants.SHAPE_INTERSECTS ||
(crossings & mask) != 0);
}
@Override
public boolean intersects(Path2f s) {
FillType type = this.path.getFillType();
int mask = (type == FillType.WINDING || type == FillType.INVERSE_WINDING ? -1 : 2);
int crossings = Path2f.computeCrossingsFromPath(
s.getPathIterator((float)MathConstants.SPLINE_APPROXIMATION_RATIO),
new PathShadow2f(this),
false,
true);
return (crossings == MathConstants.SHAPE_INTERSECTS ||
(crossings & mask) != 0);
}
@Override
public boolean intersects(PathIterator2f s) {
FillType type = this.path.getFillType();
int mask = (type == FillType.WINDING || type == FillType.INVERSE_WINDING ? -1 : 2);
int crossings = Path2f.computeCrossingsFromPath(
s,
new PathShadow2f(this),
false,
true);
return (crossings == MathConstants.SHAPE_INTERSECTS ||
(crossings & mask) != 0);
}
@Override
public PathIterator2f getPathIterator(float flatness) {
return new FlatteningPathIterator((float)MathConstants.SPLINE_APPROXIMATION_RATIO, null);
}
@Override
public PathIterator2f getPathIterator(Transform2D transform) {
return new FlatteningPathIterator((float)MathConstants.SPLINE_APPROXIMATION_RATIO, transform);
}
@Override
public PathIterator2f getPathIterator() {
return getPathIterator(null);
}
/**
* Iterator that is replies the points of a flatenning path.
*
* @author $Author: sgalland$
* @version $Name$ $Revision$ $Date$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
*/
private class FlatteningPathIterator implements PathIterator2f {
private final PathMeasure measure;
private final float flatness;
private final Transform2D transform;
private final float[] tmpCoords = new float[2];
private final Point2f tmpPoint = new Point2f();
private float firstX, firstY;
private float dist = 0;
private float length;
private PathElement2f next = null;
private boolean finished = false;
/**
* @param flatness
* @param transform
*/
public FlatteningPathIterator(float flatness, Transform2D transform) {
this.transform = (transform==null || transform.isIdentity()) ? null : transform;
this.flatness = flatness;
this.measure = new PathMeasure(AndroidPath.this.getPath(), false);
this.length = this.measure.getLength();
this.firstX = this.firstY = Float.NaN;
searchNext();
}
private void transform() {
assert(this.tmpCoords!=null && this.tmpCoords.length==2);
if (this.transform!=null) {
this.tmpPoint.set(this.tmpCoords);
this.transform.transform(this.tmpPoint);
this.tmpPoint.get(this.tmpCoords);
}
}
private void searchNext() {
PathElement2f prev = this.next;
this.next = null;
do {
if ((this.dist<=this.length) &&
(this.measure.getPosTan(this.dist, this.tmpCoords, null))) {
transform();
if (prev==null) {
this.firstX = this.tmpCoords[0];
this.firstY = this.tmpCoords[1];
this.next = new MovePathElement2f(this.firstX, this.firstY);
}
else {
this.next = new LinePathElement2f(
prev.toX, prev.toY,
this.tmpCoords[0], this.tmpCoords[1]);
}
this.dist += this.flatness;
}
if (this.next==null) {
if (prev!=null && this.measure.isClosed()) {
this.next = new ClosePathElement2f(
prev.toX, prev.toY,
this.firstX, this.firstY);
}
else if (this.measure.nextContour()) {
prev = null;
this.dist = 0;
this.length = this.measure.getLength();
this.firstX = this.firstY = Float.NaN;
}
else {
this.finished = true;
}
}
}
while (this.next==null && !this.finished);
}
@Override
public boolean hasNext() {
return this.next!=null;
}
@Override
public PathElement2f next() {
PathElement2f n = this.next;
if (n==null) throw new NoSuchElementException();
searchNext();
return n;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
@Override
public PathWindingRule getWindingRule() {
return AndroidPath.this.getWindingRule();
}
@Override
public boolean isPolyline() {
return true; // Because the iterator flats the path.
}
} // class CopyIterator
}