/* --------------------------------------------------------- * * __________ 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;} }