/* --------------------------------------------------------- *
* __________ D E L T A S C R I P T *
* (_________() *
* / === / - A fast, dynamic scripting language *
* | == | - Version 4.13.11.0 *
* / === / - Developed by Adam R. Nelson *
* | = = | - 2011-2013 *
* / === / - Distributed under GNU LGPL v3 *
* (________() - http://github.com/ar-nelson/deltascript *
* *
* --------------------------------------------------------- */
package com.sector91.delta.script.objects.geom;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import com.sector91.delta.script.annotations.DSDynamicField;
import com.sector91.delta.script.annotations.DSInaccessible;
import com.sector91.delta.script.annotations.DSName;
import com.sector91.delta.script.annotations.DSType;
import com.sector91.delta.script.objects.DS_Object;
import com.sector91.delta.script.objects.DS_Vector;
import com.sector91.delta.script.objects.reflect.DS_JavaClass;
import com.sector91.geom.Curve2D;
import com.sector91.geom.Path;
import com.sector91.geom.PathData;
import com.sector91.geom.PathSegment;
import com.sector91.geom.Shape;
import com.sector91.geom.Shape2D;
import com.sector91.geom.Vector;
import com.sector91.geom.PathSegment.Type;
import com.sector91.geom.algorithms.Algorithms;
@DSType("Path2D")
public class DS_Path2D extends DS_AbstractShape
implements DS_Shape2D
{
public static final String TYPE_NAME = "Path2D";
private static final DS_JavaClass DSCLASS = DS_JavaClass.fromClass(
DS_Path2D.class);
private final PathData data;
private transient DS_Rect bbox;
private transient boolean solid, curved, compound, convex;
private transient DS_Vector[] vertices;
private transient final AtomicBoolean
initialized = new AtomicBoolean(false);
private transient final CountDownLatch
initLatch = new CountDownLatch(1);
@DSInaccessible
public DS_Path2D(DeltaScriptGeometry geom, PathData data)
{
super(geom);
this.data = data;
}
@SuppressWarnings("incomplete-switch")
private void initialize()
{
DS_Vector lastPt = null;
final List<DS_Vector> vertices = new ArrayList<DS_Vector>();
final Set<DS_Vector> bboxPts = new HashSet<DS_Vector>();
boolean solid = false;
boolean curved = false;
boolean compound = false;
boolean convex = false;
for (PathSegment seg : data)
{
solid = solid ||
(seg.type == Type.CLOSE);
curved = curved ||
(seg.type == Type.QUAD || seg.type == Type.CUBIC);
compound = compound ||
(seg.type == Type.MOVE && !vertices.isEmpty());
// TODO Test if a path is convex.
switch (seg.type)
{
case MOVE:
lastPt = geom.convert(seg.pt1);
break;
case LINE:
lastPt = geom.convert(seg.pt1);
break;
case QUAD:
bboxPts.add(geom.convert(seg.pt1));
lastPt = geom.convert(seg.pt2);
break;
case CUBIC:
bboxPts.add(geom.convert(seg.pt1));
bboxPts.add(geom.convert(seg.pt2));
lastPt = geom.convert(seg.pt3);
break;
}
if (seg.type != Type.CLOSE)
{
vertices.add(lastPt);
bboxPts.add(lastPt);
}
}
this.solid = solid;
this.curved = curved;
this.compound = compound;
this.convex = convex;
this.vertices = vertices.toArray(new DS_Vector[vertices.size()]);
switch (bboxPts.size())
{
case 0:
this.bbox = geom.rect(geom.ZERO_VEC_2D, geom.ZERO_VEC_2D);
break;
case 1:
this.bbox = geom.rect(bboxPts.iterator().next(), geom.ZERO_VEC_2D);
break;
default:
this.bbox = geom.boundingRect(
bboxPts.toArray(new Vector[bboxPts.size()]));
}
initLatch.countDown();
}
private void requireInit()
{
if (initialized.compareAndSet(false, true))
initialize();
try
{initLatch.await();}
catch (InterruptedException ex)
{Thread.currentThread().interrupt();}
}
@DSName("dimensions") @DSDynamicField
public final int dimensions()
{return 2;}
@DSName("solidDimensions") @DSDynamicField
public int solidDimensions()
{return closed() ? 2 : 1;}
@DSName("curved") @DSDynamicField
public boolean curved()
{
requireInit();
return curved;
}
@DSName("compound") @DSDynamicField
public boolean compound()
{
requireInit();
return compound;
}
@DSName("vertices") @DSDynamicField
public DS_Vector[] vertices()
{
requireInit();
return vertices;
}
@DSName("curves") @DSDynamicField
public Curve2D[] curves()
{return (Curve2D[])data.curves();}
@DSName("contains")
public boolean contains(Vector pt)
{
throw new UnsupportedOperationException(
"Containment testing for paths is not yet implemented.");
}
@DSName("intersects")
public boolean intersects(Shape2D other)
{return Algorithms.testPathIntersection(this, other, 1.0f);}
@DSName("perimeter") @DSDynamicField
public float perimeter()
{
float total = 0;
for (Curve2D curve : curves())
total += curve.length();
return total;
}
@DSName("area") @DSDynamicField
public float area()
{
throw new UnsupportedOperationException(
"Calculating the area of a path is not yet implemented.");
}
@DSName({"boundingBox", "bbox"}) @DSDynamicField
public DS_Rect boundingBox()
{
requireInit();
return bbox;
}
@DSName("center") @DSDynamicField
public DS_Vector center()
{return bbox.center();}
@DSName("convex") @DSDynamicField
public boolean convex()
{
throw new UnsupportedOperationException(
"Determining convexity of paths is not yet implemented.");
}
@DSName("transform")
public DS_Path2D transform(Vector matrix)
{return new DS_Path2D(geom, data.transform(matrix));}
@DSName("rotate")
public DS_Path2D rotate(float radians)
{return transform(geom.vec(Algorithms.rotateMatrix2D(radians)));}
@DSName("rotate")
public DS_Path2D rotate(float radians, float cx, float cy)
{return translate(-cx, -cy).rotate(radians).translate(cx, cy);}
@DSName("rotate")
public DS_Path2D rotate(float radians, Vector center)
{return rotate(radians, center.x(), center.y());}
@DSName("shear")
public DS_Path2D shear(float x, float y)
{return transform(geom.vec(Algorithms.shearMatrix2D(x, y)));}
@DSName("shear")
public DS_Path2D shear(Vector vec)
{return shear(vec.x(), vec.y());}
@DSName("scale")
public DS_Path2D scale(float x, float y)
{return transform(geom.vec(Algorithms.scaleMatrix2D(x, y)));}
@DSName("scale")
public DS_Path2D scale(Vector vec)
{return scale(vec.x(), vec.y());}
@DSName("translate")
public DS_Path2D translate(float x, float y)
{return transform(geom.vec(Algorithms.translateMatrix2D(x, y)));}
@DSName("translate")
public DS_Path2D translate(Vector vec)
{return translate(vec.x(), vec.y());}
@DSName("congruentTo")
public boolean congruentTo(Shape other)
{
if (other == null)
return false;
if (other == this)
return true;
if (other.dimensions() != 2)
return false;
if (other.solidDimensions() != solidDimensions())
return false;
if (!(other instanceof Path))
return false;
return pathData().equals(((Path)other).pathData());
}
@Override public int hashCode()
{
final int prime = 31;
int result = 1;
result = prime * result + ((data == null) ? 0 : data.hashCode());
return result;
}
@Override public String toString()
{
final StringBuilder sb = new StringBuilder("[Path: ");
for (PathSegment seg : data)
{
switch (seg.type)
{
case MOVE:
sb.append("move(");
sb.append(seg.pt1);
sb.append(")");
break;
case LINE:
sb.append("line(");
sb.append(seg.pt1);
sb.append(")");
break;
case QUAD:
sb.append("quad(");
sb.append(seg.pt1);
sb.append(", ");
sb.append(seg.pt2);
sb.append(")");
break;
case CUBIC:
sb.append("cubic(");
sb.append(seg.pt1);
sb.append(", ");
sb.append(seg.pt2);
sb.append(", ");
sb.append(seg.pt3);
sb.append(")");
break;
case CLOSE:
sb.append("close");
}
sb.append(", ");
}
sb.delete(sb.length()-2, sb.length());
sb.append("]");
return sb.toString();
}
@DSName("closed") @DSDynamicField
public boolean closed()
{
requireInit();
return solid;
}
@DSName("empty") @DSDynamicField
public boolean empty()
{return data.empty();}
@Override public DS_Vector random()
{
throw new UnsupportedOperationException("Finding a random point in"
+ " a path is not yet implemented.");
}
public boolean equals(DS_Object other)
{
if (this == other) return true;
if (other == null) return false;
if (getClass() == other.getClass())
return pathData().equals(((DS_Path2D)other).pathData());
return false;
}
public PathData pathData()
{return data;}
public String getTypeName()
{return TYPE_NAME;}
@Override protected DS_JavaClass getDeltaScriptClass()
{return DSCLASS;}
}