Package org.joshy.sketch.model

Source Code of org.joshy.sketch.model.SPath$PathTuple

package org.joshy.sketch.model;

import org.joshy.gfx.draw.*;
import org.joshy.gfx.draw.Paint;
import org.joshy.gfx.node.Bounds;
import org.joshy.gfx.util.u;
import org.joshy.sketch.util.Util;

import java.awt.*;
import java.awt.geom.*;
import java.util.ArrayList;
import java.util.List;

/**
* Spath represents a path composed of line segments and curves. It may contain
* multiple subpaths by closing a path and doing a 'moveto' to start a new one.
* All points are represented by a pathpoint. A pathpoint with closeto=true means
* the current subpath is completed by going back to the previous moveto and the
* next subpath begins with a new moveto.
*
*
*
*/
public class SPath extends SShape implements SelfDrawable {
    private Path2D.Double path2d;
    private List<SPath.SubPath> subPaths = new ArrayList<SPath.SubPath>();
    private SubPath currentSubPath;

    public SPath() {
        currentSubPath = new SubPath();
        subPaths.add(currentSubPath);
    }

    @Override
    public Bounds getBounds() {
        if(path2d != null) {
            Rectangle bds = path2d.getBounds();
            return new Bounds(
                    bds.getX(),
                    bds.getY(),
                    bds.getWidth(),
                    bds.getHeight());
        }
        return new Bounds(0,0,100,100);
    }

    public Bounds getTransformedBounds() {
        if(path2d != null) {
            AffineTransform af = new AffineTransform();
            af.translate(getTranslateX(),getTranslateY());
            af.translate(getAnchorX(),getAnchorY());
            af.rotate(Math.toRadians(getRotate()));
            af.scale(getScaleX(), getScaleY());
            af.translate(-getAnchorX(),-getAnchorY());
            Shape sh = af.createTransformedShape(path2d);
            Rectangle2D bds = sh.getBounds2D();
            return Util.toBounds(bds);

        }
        return new Bounds(0,0,100,100);
    }



    @Override
    public boolean contains(Point2D point) {
        if(path2d != null) {
            return path2d.contains(point.getX()-getTranslateX(),point.getY()-getTranslateY());
        }
        return getBounds().contains(point.getX(),point.getY());
    }


    public void draw(GFX g) {
        g.setPureStrokes(true);

        drawShadow(g);

       
        if(anyClosed()) {
            double opacity = -1;
            Paint paint = getFillPaint();
            if(paint != null) {
                if(paint instanceof FlatColor) {
                    g.setPaint(((FlatColor)paint).deriveWithAlpha(getFillOpacity()));
                }
                if(paint instanceof MultiGradientFill) {
                    g.setPaint(paint);
                }
                if(paint instanceof PatternPaint) {
                    opacity = g.getOpacity();
                    g.setOpacity(getFillOpacity());
                    g.setPaint(paint);
                }
                fillShape(g);
            }
            if(opacity >=0) g.setOpacity(opacity);
        }
       

        if(getStrokeWidth() > 0 && getStrokePaint() != null) {
            g.setPaint(getStrokePaint());
            Path2D.Double pth = toPath(this);
            g.setStrokeWidth(getStrokeWidth());
            g.drawPath(pth);
            g.setStrokeWidth(1);
        }

        g.setPureStrokes(false);
    }

    public boolean anyClosed() {
        for(SubPath sub : subPaths) {
            if(sub.closed()) return true;
        }
        return false;
    }

    @Override
    protected void fillShape(GFX g) {
        Path2D.Double path = toPath(this);
        g.fillPath(path);
    }


    public static Path2D.Double toPath(SubPath sub) {
        Path2D.Double path = new Path2D.Double();
        PathPoint first = null;
        PathPoint prev = null;
        for(PathPoint point : sub.points) {
            if(prev == null) {
                first = point;
                path.moveTo(point.x,point.y);
            } else {
                path.curveTo(prev.cx2,prev.cy2,point.cx1,point.cy1,point.x,point.y);
            }
            prev = point;
        }
        if(sub.closed()) {
            path.curveTo(prev.cx2,prev.cy2,first.cx1,first.cy1,first.x,first.y);
            path.closePath();
        }
        return path;
    }

    /*
    there are three possible states
    completely open
    closed using an auto-close
    closed manually. 
    when created in the editor it will be closed manually
     */
    public static Path2D.Double toPath(SPath node) {
        Path2D.Double path = new Path2D.Double();

        for(SubPath sub : node.subPaths) {
            PathPoint first = null;
            PathPoint prev = null;
            for(PathPoint point : sub.points) {
                //first
                if(prev == null) {
                    first = point;
                    path.moveTo(point.x,point.y);
                } else {
                    path.curveTo(prev.cx2,prev.cy2,point.cx1,point.cy1,point.x,point.y);
                }
                prev = point;
            }
            if(sub.closed()) {
                path.curveTo(prev.cx2,prev.cy2,first.cx1,first.cy1,first.x,first.y);
                path.closePath();
            }
        }
        return path;
    }

    public Path2D.Double toTransformedPath() throws NoninvertibleTransformException {
        Path2D.Double p2d = SPath.toPath(this);
        AffineTransform af = new AffineTransform();
        af.translate(getTranslateX(),getTranslateY());
        af.rotate(Math.toRadians(getRotate()));
        af.scale(getScaleX(), getScaleY());
        return new Path2D.Double(p2d,af.createInverse());
    }





    public void recalcPath() {
        this.path2d = toPath(this);
    }





    /*
     * Split the cubic Bezier stored at coords[pos...pos+7] representing
     * the parametric range [0..1] into two subcurves representing the
     * parametric subranges [0..t] and [t..1].  Store the results back
     * into the array at coords[pos...pos+7] and coords[pos+6...pos+13].
     */
    public static void split(double coords[], int pos, double t) {
        double x0, y0, cx0, cy0, cx1, cy1, x1, y1;
        coords[pos+12] = x1 = coords[pos+6];
        coords[pos+13] = y1 = coords[pos+7];
        cx1 = coords[pos+4];
        cy1 = coords[pos+5];
        x1 = cx1 + (x1 - cx1) * t;
        y1 = cy1 + (y1 - cy1) * t;
        x0 = coords[pos+0];
        y0 = coords[pos+1];
        cx0 = coords[pos+2];
        cy0 = coords[pos+3];
        x0 = x0 + (cx0 - x0) * t;
        y0 = y0 + (cy0 - y0) * t;
        cx0 = cx0 + (cx1 - cx0) * t;
        cy0 = cy0 + (cy1 - cy0) * t;
        cx1 = cx0 + (x1 - cx0) * t;
        cy1 = cy0 + (y1 - cy0) * t;
        cx0 = x0 + (cx0 - x0) * t;
        cy0 = y0 + (cy0 - y0) * t;
        coords[pos+2] = x0;
        coords[pos+3] = y0;
        coords[pos+4] = cx0;
        coords[pos+5] = cy0;
        coords[pos+6] = cx0 + (cx1 - cx0) * t;
        coords[pos+7] = cy0 + (cy1 - cy0) * t;
        coords[pos+8] = cx1;
        coords[pos+9] = cy1;
        coords[pos+10] = x1;
        coords[pos+11] = y1;
    }

    public void normalize() {
        double minX = Double.MAX_VALUE;
        double minY = Double.MAX_VALUE;
        double maxX = Double.MIN_VALUE;
        double maxY = Double.MIN_VALUE;
        for(PathPoint pt : allPoints()) {
            minX = Math.min(pt.x,minX);
            minY = Math.min(pt.y,minY);
            maxX = Math.max(pt.x,maxX);
            maxY = Math.max(pt.y,maxY);
        }
        for(PathPoint pt : allPoints()) {
            pt.x -= minX;
            pt.y -= minY;
           
            pt.cx1 -= minX;
            pt.cx2 -= minX;

            pt.cy1 -= minY;
            pt.cy2 -= minY;
        }
        setTranslateX(getTranslateX() + minX);
        setTranslateY(getTranslateY() + minY);
        recalcPath();
        setAnchorX((int) ((maxX - minX) / 2.0));
        setAnchorY((int) ((maxY - minY) / 2.0));
    }

    private Iterable<PathPoint> allPoints() {
        List<PathPoint> pts = new ArrayList<PathPoint>();
        for(SubPath sub : getSubPaths()) {
            pts.addAll(sub.getPoints());
        }
        return pts;
    }


    public PathPoint moveTo(double x, double y) {
        if(currentSubPath.closed()) {
            currentSubPath = new SubPath();
            subPaths.add(currentSubPath);
        }
        PathPoint p = new PathPoint(x, y);
        p.startPath = true;
        currentSubPath.addPoint(p);
        return p;
    }

    public PathPoint lineTo(double x, double y) {
        //u.p("line to: " + x + " " + y);
        PathPoint p = new PathPoint(x, y);
        currentSubPath.addPoint(p);
        return p;
    }

    /**
     *
     * @param prev the previous point
     * @param x1 left control point x
     * @param y1 left control point y
     * @param x2 right control point x
     * @param y2 right control point y
     * @param x final x
     * @param y final y
     * @return
     */
    public PathPoint curveTo(PathPoint prev, double x1, double y1, double x2, double y2, double x, double y) {
        //u.p("curve to: " + x + " " + y);
        PathPoint p = new PathPoint(x,y,x2,y2,x,y);
        prev.cx2 = x1;
        prev.cy2 = y1;
        currentSubPath.addPoint(p);
        return p;
    }


    public static SPath fromPathIterator(PathIterator it) {
        SPath sPath = new SPath();
        PathPoint prev = null;
        while(!it.isDone()) {
            double[] coords = new double[6];
            int n = it.currentSegment(coords);
            if(n == PathIterator.SEG_MOVETO) {
                prev = sPath.moveTo(coords[0],coords[1]);
            }
            if(n == PathIterator.SEG_LINETO) {
                prev = sPath.lineTo(coords[0],coords[1]);
            }
            if(n == PathIterator.SEG_CUBICTO) {
                prev = sPath.curveTo(prev, coords[0],coords[1],coords[2],coords[3],coords[4],coords[5]);
            }
            if(n == PathIterator.SEG_CLOSE) {
                sPath.close();
            }
            it.next();
        }
        sPath.recalcPath();
        return sPath;
    }

    private static void print(int n, double[] coords) {
        switch(n) {
            case PathIterator.SEG_MOVETO:
                u.p("moveto " + coords[0] + " " + coords[1]);
                return;
            case PathIterator.SEG_LINETO:
                u.p("lineto  " + coords[0] + " " + coords[1]);
                return;
            case PathIterator.SEG_CUBICTO:
                u.p("cubic to  "  + coords[0] + " " + coords[1] + " - " + coords[2] + " " + coords[3] + " - " + coords[4] + " " + coords[5]);
                return;
            case PathIterator.SEG_CLOSE:
                u.p("close");
                return;
            default:
                u.p("unknown type: " + n);
        }
    }


    public List<SubPath> getSubPaths() {
        return subPaths;
    }

    public void close() {
        this.currentSubPath.autoClosed = true;
    }

    public void addPoint(PathPoint currentPoint) {
        this.currentSubPath.addPoint(currentPoint);
    }

    public void newSubPath() {
        this.currentSubPath = new SubPath();
        this.subPaths.add(this.currentSubPath);
    }

    public void dump() {
        u.p("SPath: ");
        for(SubPath sub : getSubPaths()) {
            u.p("  sub: closed = " + sub.autoClosed + ", size = " + sub.size());
            for(PathPoint pt : sub.getPoints()) {
                u.p("    pt " + pt.x + " " + pt.y );
            }
        }
    }


    public static class SubPath {
        private List<PathPoint> points = new ArrayList<PathPoint>();
        private boolean autoClosed;

        public int size() {
            return points.size();
        }
       
        public void addPoint(PathPoint point) {
            this.points.add(point);
        }

        public boolean closed() {
            return autoClosed;
        }

        public PathPoint splitPath(PathTuple location) {
            PathPoint a = location.a;
            PathPoint b = location.b;
            PathPoint c = new PathPoint(0,0);

            double co[] = new double[14];
            co[0] = a.x; co[1] = a.y;
            co[2] = a.cx2; co[3] = a.cy2;
            co[4] = b.cx1; co[5] = b.cy1;
            co[6] = b.x; co[7] = b.y;
            split(co,0,location.t);

            a.x = co[0];   a.y = co[1];
            a.cx2 = co[2]; a.cy2 = co[3];
            c.cx1 = co[4]; c.cy1 = co[5];
            c.x = co[6];   c.y = co[7];
            c.cx2 = co[8]; c.cy2 = co[9];
            b.cx1 = co[10]; b.cy1 = co[11];
            b.x = co[12];   b.y = co[13];
            points.add(location.index+1,c);
            return c;
        }

        public void unSplitPath(PathTuple temp, PathPoint a, PathPoint b, PathPoint pt) {
            temp.a.copyFrom(a);
            temp.b.copyFrom(b);
            points.remove(pt);
        }

        public SubPath copy() {
            SubPath dupe = new SubPath();
            for(PathPoint point : this.points) {
                dupe.addPoint(point.copy());
            }
            dupe.autoClosed = this.autoClosed;
            return dupe;
        }

        public PathPoint getPoint(int i) {
            return points.get(i);
        }

        public List<PathPoint> getPoints() {
            return points;
        }

        public void removePoint(PathPoint hoverPoint) {
            this.points.remove(hoverPoint);
        }

        public Iterable<PathSegment> calculateSegments() {
            List<PathSegment> segs = new ArrayList<PathSegment>();
            for(int i=0; i<points.size()-1;i++) {
                PathPoint curr = points.get(i);
                PathPoint next = points.get(i+1);
                segs.add(new PathSegment(curr,next,i));
            }
            if(closed()) {
                int last = points.size()-1;
                segs.add(new PathSegment(points.get(last),points.get(0),last));
            }
            return segs;
        }

        public void doAutoclose() {
            this.autoClosed = true;
        }
    }
   
    public static class PathTuple {
        public double distance;
        public double t;
        public Point2D.Double point;
        public PathPoint a;
        public PathPoint b;
        public int index;

        public PathTuple copy() {
            PathTuple pt = new PathTuple();
            pt.distance = distance;
            pt.t = t;
            pt.point = point;
            pt.a = a;
            pt.b = b;
            pt.index = index;
            return pt;
        }
    }

    public static class PathSegment {
        Point2D.Double p1;
        Point2D.Double p2;
        Point2D.Double p3;
        Point2D.Double p4;
        private PathPoint a;
        private PathPoint b;
        private int index;

        public PathSegment(PathPoint curr, PathPoint next, int index) {
            a = curr;
            b = next;
            this.index = index;
            this.p1 = new Point2D.Double(curr.x,curr.y);
            this.p2 = new Point2D.Double(curr.cx2,curr.cy2);
            this.p3 = new Point2D.Double(next.cx1,next.cy1);
            this.p4 = new Point2D.Double(next.x,next.y);
        }

        public PathTuple closestDistance(Point2D.Double point) {
            Point2D.Double closest = calculatePoint(p1,p2,p3,p4,0);
            double closestDistance = calculateDistance(point.getX(),point.getY(),closest);
            double closestT = 0;
            for(double t=0; t<=1.0; t+=0.01) {
                Point2D.Double b = calculatePoint(p1,p2,p3,p4,t);
                double distance = calculateDistance(point.getX(),point.getY(),b);
                if(distance < closestDistance) {
                    closestDistance = distance;
                    closest = b;
                    closestT = t;
                }
            }
            PathTuple tup = new PathTuple();
            tup.t = closestT;
            tup.point = closest;
            tup.distance = closestDistance;
            tup.a = a;
            tup.b = b;
            tup.index = index;
            return tup;
        }

        private double calculateDistance(double x, double y, Point2D.Double b) {
            double dx = x-b.x;
            double dy = y-b.y;
            double distance = Math.sqrt(dx*dx+dy*dy);
            return distance;
        }
        private Point2D.Double calculatePoint(Point2D.Double p1, Point2D.Double p2, Point2D.Double p3, Point2D.Double p4, double mu) {
            double mum1 = 1 - mu;
            double mum13 = mum1 * mum1 * mum1;
            double mu3 = mu*mu*mu;

            Point2D.Double p = new Point2D.Double();
            p.x = mum13*p1.x + 3*mu*mum1*mum1*p2.x + 3*mu*mu*mum1*p3.x + mu3*p4.x;
            p.y = mum13*p1.y + 3*mu*mum1*mum1*p2.y + 3*mu*mu*mum1*p3.y + mu3*p4.y;
            return p;
        }
    }

    public static class PathPoint {
        public double x;
        public double y;
        public boolean bound;
        public double cx1;
        public double cy1;
        public double cx2;
        public double cy2;
        public boolean startPath = false;
        public boolean closePath = false;

        public PathPoint(double x, double y, double cx1, double cy1, double cx2, double cy2) {
            this.x = x;
            this.y = y;
            this.bound = false;
            this.cx1 = cx1;
            this.cy1 = cy1;
            this.cx2 = cx2;
            this.cy2 = cy2;
        }

        public PathPoint(double x, double y) {
            this.x = x;
            this.y = y;
            this.cx1 = x;
            this.cy1 = y;
            this.cx2 = x;
            this.cy2 = y;
        }

        public double distance(double x, double y) {
            double x2 = this.x - x;
            double y2 = this.y - y;
            return Math.sqrt(x2*x2+y2*y2);
        }

        public PathPoint copy() {
            PathPoint cp = new PathPoint(x, y, cx1, cy1, cx2, cy2);
            cp.startPath = startPath;
            cp.closePath = closePath;
            return cp;
        }

        public void copyFrom(PathPoint a) {
            this.x = a.x;
            this.y = a.y;
            this.cx1 = a.cx1;
            this.cy1 = a.cy1;
            this.cx2 = a.cx2;
            this.cy2 = a.cy2;
            this.startPath = a.startPath;
            this.closePath = a.closePath;
        }
    }

    @Override
    public SNode duplicate(SNode dupe) {
        if(dupe == null) {
            dupe = new SPath();
        }
        for(SubPath sub : this.subPaths) {
            ((SPath)dupe).addSubPath(sub.copy());
        }
        ((SPath)dupe).recalcPath();
        return super.duplicate(dupe);
    }

    private void addSubPath(SubPath copy) {
        this.subPaths.add(copy);
    }

    @Override
    public Area toArea() {
        return new Area(transformShape(this.path2d));
    }
}
TOP

Related Classes of org.joshy.sketch.model.SPath$PathTuple

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.