All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.codename1.ui.geom.Geometry Maven / Gradle / Ivy

There is a newer version: 7.0.164
Show newest version
/*
 * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Codename One designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *  
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 * 
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 * 
 * Please contact Codename One through http://www.codenameone.com/ if you 
 * need additional information or have any questions.
 */
package com.codename1.ui.geom;

import com.codename1.ui.Graphics;
import com.codename1.ui.Stroke;
import com.codename1.ui.Transform;
import com.codename1.ui.geom.GeneralPath.ShapeUtil;
import com.codename1.util.MathUtil;
import java.util.Arrays;
import java.util.List;

/**
 * A utility class to assist with geometry elements like bezier curves
 * @author Steve Hannah
 */
class Geometry {
    
    /**
     * Encapsulates a BezierCurve.  Some functionality supports curves of arbitrary degree,
     * but the most useful stuff only supports quadratic and cubic curves.
     * 
     * The main point of this class is to provide the ability to segment bezier curves
     * into smaller components so that {@link GeneralPath#intersection(com.codename1.ui.geom.Rectangle) } 
     * will work for paths that contain curves.
     */
    static class BezierCurve {
        
        /**
         * The x, and y points used for the bezier curves.  {@literal (x[0], y[0])} is the starting point
         * , {@literal (x[x.length-1], y[y.length-1])} is the end point, and all indices in between 
         * are the control points.  Cubic curves will have 4 points, quadratic curves, 3 points, lines
         * 2 points.
         */
        final double[] x, y;
        
        private Point2D startPoint, endPoint;
        private Rectangle2D boundingRect;
        
        
        /**
         * Creates a bezier curve with the provided points.  Points should be entered as {@literal (x1, y1, x2, y2, ..., xn, yn}.
         * @param pts The points.
         */
        public BezierCurve(double... pts) {
            int len = pts.length;
            if (len % 2 != 0) {
                throw new IllegalArgumentException("Length of points array must be even.");
            }
            x = new double[len/2];
            y = new double[len/2];
            
            for (int i=0; i=0; i--) {
                params[index++] = x[i];
                params[index++] = y[i];
                
            }
            return new BezierCurve(params);
            
        }
        
        /**
         * Segements this curve into two shorter curves.  Split at point t.
         * @param t t value where split should occur.  0 < t < 1
         * @param out List where the two segemeted curves will be added.
         */
        public void segment(double t, List out) {
            out.add(segment(t));
            out.add(reverse().segment(1-t).reverse());
        }
        
        /**
         * Adds bezier curve to a path.
         * @param p The path to add to.
         * @param join If false, it will first add a moveTo() command to the path.
         */
        public void addToPath(GeneralPath p, boolean join) {
            if (n() == 2) {
                if (!join) p.moveTo(x[0], y[0]);
                p.quadTo(x[1], y[1], x[2], y[2]);
            } else if (n() == 3) {
                if (!join) p.moveTo(x[0], y[0]);
                p.curveTo(x[1], y[1], x[2], y[2], x[3], y[3]);
            } else if (n() == 1) {
                if (join) p.moveTo(x[0], y[0]);
                p.lineTo(x[1], y[1]);
            }
        }
        
        /**
         * Strokes the bezier curve on a graphics context.
         * @param g
         * @param stroke
         * @param translateX
         * @param translateY 
         */
        public void stroke(Graphics g, Stroke stroke, int translateX, int translateY) {
            if (stroke == null) {
                stroke = new Stroke(1, Stroke.CAP_BUTT, Stroke.JOIN_MITER, 1f);
            }
            GeneralPath p = new GeneralPath();
            addToPath(p, false);
            p.transform(Transform.makeTranslation(translateX, translateY));
            g.drawShape(p, stroke);
        }
        
        
        
        public static void extractBezierCurvesFromPath(Shape shape, List out) {
            PathIterator it = shape.getPathIterator();
            int type;
            double[] buf = new double[6];
            double prevX = 0;
            double prevY = 0;
            double markX = 0;
            double markY = 0;
            while (!it.isDone()) {
                type = it.currentSegment(buf);
                switch (type) {
                    case PathIterator.SEG_MOVETO:
                        prevX = buf[0];
                        prevY = buf[1];
                        markX = prevX;
                        markY = prevY;
                        break;
                    case PathIterator.SEG_LINETO:
                        prevX = buf[0];
                        prevY = buf[1];
                        break;
                    case PathIterator.SEG_CLOSE:
                        prevX = markX;
                        prevY = markY;
                        break;
                    case PathIterator.SEG_QUADTO:
                        out.add(new BezierCurve(prevX, prevY, buf[0], buf[1], buf[2], buf[3]));
                        prevX = buf[2];
                        prevY = buf[3];
                        break;
                    case PathIterator.SEG_CUBICTO:
                        out.add(new BezierCurve(prevX, prevY, buf[0], buf[1], buf[2], buf[3], buf[4], buf[5]));
                        prevX = buf[4];
                        prevY = buf[5];
                        break;
                        
                     
                }
                it.next();
                
            }
        }
        
        public Rectangle2D getBoundingRect() {
            if (boundingRect == null) {
                Point2D start = getStartPoint();
                Point2D end = getEndPoint();
                int numSolutions = 0;
                double[] res = new double[3];
                double x1 = Math.min(start.getX(), end.getX());
                double y1 = Math.min(start.getY(), end.getY());
                double x2 = Math.max(start.getX(), end.getX());
                double y2 = Math.max(start.getY(), end.getY());
                switch (n()) {
                    case 1:
                        break;
                    case 0:
                        break;
                    case 2:
                    case 3:

                        numSolutions = ShapeUtil.solveQuad(getDerivativeCoefficientsX(), res);
                        if (numSolutions > 0) {
                            for (int i=0; i 1) {
                                    continue;
                                }
                                double xt = x(t);
                                double yt = y(t);
                                x1 = Math.min(x1, xt);
                                y1 = Math.min(y1, yt);
                                x2 = Math.max(x2, xt);
                                y2 = Math.max(y2, yt);
                            }

                        }
                        numSolutions = ShapeUtil.solveQuad(getDerivativeCoefficientsY(), res);
                        if (numSolutions > 0) {
                            for (int i=0; i 1) {
                                    continue;
                                }
                                double xt = x(t);
                                double yt = y(t);
                                x1 = Math.min(x1, xt);
                                y1 = Math.min(y1, yt);
                                x2 = Math.max(x2, xt);
                                y2 = Math.max(y2, yt);
                            }

                        }
                    break;
                    default:
                        throw new IllegalArgumentException("getBoundingRect() only supported for bezier curves of order 3 or less");




                }
                boundingRect = new Rectangle2D(x1, y1, x2-x1, y2-y1);
            }
            return boundingRect;
        }
        
        /**
         * Segments the curve into 2 smaller component curves split at the given t value.  t in [0 .. 1].
         * Returns only the first segment.  You can use reverse().segment(1-t).reverse() to get the other segment.
         * @param t The value of t to segment on.
         * @return The first segment.
         */
        public BezierCurve segment(double t) {
            return segment(0, t);
        }
        
        /**
         * Finds all of the t values that cross the given x vertical between y=minY and y=maxY.  The
         * t values are added to the {@literal out} array, and the number of matches is returned.
         * On quadratic curves, there should be a maximum of 2 results.  On cubic curves, there will be 
         * a maximum 3 results.
         * @param x The x value for which we wish to find t.
         * @param minY Minimum y value we are interested in.
         * @param maxY Maximum y value we are interested in.
         * @param out Out array.  For quadratics, this needs to have length at least 2.  For cubics, at least 3.
         * @return The number of results found.
         */
        public int findTValuesForX(double x, double minY, double maxY, double[] out) {
            
            int numMatches = findTValuesForX(x, out);
            int numFiltered = 0;
            for (int i=0; i 1) {
                    continue;
                }
                double ty = y(out[i]);
                if (ty >= minY && ty <= maxY) {
                    out[numFiltered] = out[i];
                    numFiltered++;
                }
            }
            return numFiltered;
        }
        
        /**
         * Finds all of the t values that cross the given y horizontal between x=minX and x=maxX.  The
         * t values are added to the {@literal out} array, and the number of matches is returned.
         * On quadratic curves, there should be a maximum of 2 results.  On cubic curves, there will be 
         * a maximum 3 results.
         * @param y The x value for which we wish to find t.
         * @param minX Minimum x value we are interested in.
         * @param maxX Maximum x value we are interested in.
         * @param out Out array.  For quadratics, this needs to have length at least 2.  For cubics, at least 3.
         * @return The number of results found.
         */
        public int findTValuesForY(double y, double minX, double maxX, double[] out) {
            
            int numMatches = findTValuesForY(y, out);
            int numFiltered = 0;
            for (int i=0; i 1) {
                    continue;
                }
                double tx = x(out[i]);
                if (tx >= minX && tx <= maxX) {
                    out[numFiltered] = out[i];
                    numFiltered++;
                }
            }
            return numFiltered;
        }
        
        /**
         * Compares two bezier curves to see if they are equal (within epsilon margin of error).
         * @param c The bezier curve to compare to
         * @param epsilon Curves are equal if all x and y values are within epsilon of the corresponding x/y value
         * in the other curve.  epsilon must be greater than 0
         * @return True if curves are equal within epsilon margin of error.
         */
        public boolean equals(BezierCurve c, double epsilon) {
            if (c.n() != n()) return false;
            int len = x.length;
            for (int i=0; i epsilon) {
                    return false;
                }
                if (Math.abs(y[i]-c.y[i]) > epsilon) {
                    return false;
                }
            }
            return true;
        }
        
        /**
         * Segments the bezier curve on all intersection points of the provided rectangle.
         * @param rect The rectangle on which to segment the curve.
         * @param out list where the segmented curves are appended.
         */
        public void segment(Rectangle2D rect, List out) {
            //System.out.println("segment("+rect+") on  "+this);
            int numIntersections = (n() == 2) ? ShapeUtil.intersectQuad(x[0], y[0], x[1], y[1], x[2], y[2], rect.getX(), rect.getY(), rect.getX()+rect.getWidth(), rect.getY() + rect.getHeight()) :
                    n() == 3 ? ShapeUtil.intersectCubic(x[0], y[0], x[1], y[1], x[2], y[2], x[3], y[3], rect.getX(), rect.getY(), rect.getX() + rect.getWidth(), rect.getHeight() + rect.getY()) :
                    n() == 1 ? ShapeUtil.intersectLine(x[0], y[0], x[1], y[1], rect.getX(), rect.getY(), rect.getX()+rect.getWidth(), rect.getY()+rect.getHeight()) :
                    -1;
            if (numIntersections == -1) {
                throw new IllegalArgumentException("Cannot segment bezier curve of this order: "+n());
            }
            
            if (numIntersections == 0) {
                out.add(new BezierCurve(this));
                return;
            }
            
            
            // To store t values of intersection points
            double[] tvals = new double[numIntersections];
            int nextTvalIndex = 0;
            double[] res = new double[3];
            int numMatches = 0;
            double epsilon = 0.01;
            // left edge
            
            if ((numMatches = findTValuesForX(rect.getX(), rect.getY(), rect.getY() + rect.getHeight(), res)) > 0) {
                //System.out.println("left: "+numMatches);
                nextTvalIndex += arraycopy(res, 0, tvals, nextTvalIndex, numMatches, epsilon);
            }
            // right edge
            if ((numMatches = findTValuesForX(rect.getX()+rect.getWidth(), rect.getY(), rect.getY() + rect.getHeight(), res)) > 0) {
                //System.out.println("right: "+numMatches);
                nextTvalIndex += arraycopy(res, 0, tvals, nextTvalIndex, numMatches, epsilon);
            }
            // top edge
            if ((numMatches = findTValuesForY(rect.getY(), rect.getX(), rect.getX() + rect.getWidth(), res)) > 0) {
                //System.out.println("Top: "+numMatches);
                nextTvalIndex += arraycopy(res, 0, tvals, nextTvalIndex, numMatches, epsilon);
            }
            // bottom edge
            if ((numMatches = findTValuesForY(rect.getY() + rect.getHeight(), rect.getX(), rect.getX() + rect.getWidth(), res)) > 0) {
                //System.out.println("Bottom: "+numMatches+" "+Arrays.toString(res));
                nextTvalIndex += arraycopy(res, 0, tvals, nextTvalIndex, numMatches, epsilon);
            }
            
            Arrays.sort(tvals, 0, nextTvalIndex);
            //System.out.println("tvals="+Arrays.toString(tvals)+"; numSegments="+(nextTvalIndex+1));
            int numSegments = nextTvalIndex+1;
            switch (numSegments) {
                case 1:
                    out.add(new BezierCurve(this));
                    return;
                case 2:
                    if (tvals[0] > epsilon && tvals[0] < 1-epsilon) {
                        segment(tvals[0], out);
                    } else {
                        out.add(new BezierCurve(this));
                    }
                    return;
                default:
                    int tIndex = 0;
                    while (tvals[tIndex] < epsilon || tvals[tIndex] > 1-epsilon) {
                        tIndex++;
                        if (tIndex >= nextTvalIndex) {
                            out.add(new BezierCurve(this));
                            return;
                        }
                    }
                    
                    segment(tvals[tIndex], out);
                    BezierCurve last = out.remove(out.size()-1);
                    if (last.equals(this, epsilon)) {
                        out.add(last);
                    } else {
                        last.segment(rect, out);
                    }
                    return;
            }
            
           
        }
        
        /**
         * Checks an array to see if it already contains a value within the desired epsilon range.
         * @param needle The value to search for
         * @param haystack The array to check
         * @param epsilon The range considered to be a match.
         * @param startIndex Start index to check
         * @param endIndex End index to check
         * @return 
         */
        private static boolean contains(double needle, double[] haystack, double epsilon, int startIndex, int endIndex) {
            for (int i=startIndex; i= 1) {
                throw new IllegalArgumentException("t must be between 0 and 1 but found "+t1);
            }
            if (n() == 2) {
                double x0 = x(t0);
                double y0 = y(t0);
                double xt = x(t1);
                double yt = y(t1);
                double x3 = (x[1] - x[0]) * t1 + x[0];
                double y3 = (y[1] - y[0]) * t1 + y[0];
                
                BezierCurve b1 = new BezierCurve(x0, y0, 
                        x3, y3,
                        xt, yt
                );
                
                return b1;
                
            } else if (n() == 3) {
                if (t0 != 0) {
                    throw new IllegalArgumentException("Only supports t0=0 right now with cubics");
                }
                double t = t1;
                double x1 = x[0];
                double y1 = y[0];
                double x2 = x[1];
                double y2 = y[1];
                double x3 = x[2];
                double y3 = y[2];
                double x4 = x[3];
                double y4 = y[3];
                double x12 = (x2-x1)*t+x1;
                double y12 = (y2-y1)*t+y1;

                double x23 = (x3-x2)*t+x2;
                double y23 = (y3-y2)*t+y2;

                double x34 = (x4-x3)*t+x3;
                double y34 = (y4-y3)*t+y3;

                double x123 = (x23-x12)*t+x12;
                double y123 = (y23-y12)*t+y12;

                double x234 = (x34-x23)*t+x23;
                double y234 = (y34-y23)*t+y23;

                double x1234 = (x234-x123)*t+x123;
                double y1234 = (y234-y123)*t+y123;
                
                return new BezierCurve(x1, y1, x12, y12, x123, y123, x1234, y1234);
                
            }
            throw new IllegalArgumentException("Cannot segment bezier curves with order "+n());
        }
        
        
        
        
    }
    
    private static int factorial(int n) {
        if (n < 0) {
            throw new IllegalArgumentException("factorial does not support negative numbers");
        }
        if (n == 0) return 1;
        return n * factorial(n-1);
    }
    
    
   
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy