001package icy.type.geom;
002
003import java.awt.Point;
004import java.awt.geom.AffineTransform;
005import java.awt.geom.Line2D;
006import java.awt.geom.Point2D;
007import java.awt.geom.Rectangle2D;
008
009/**
010 * Utilities for geom objects
011 * 
012 * @author Stephane Dallongeville
013 */
014public class GeomUtil
015{
016    /**
017     * Get intersection point between 2 lines.
018     * 
019     * @return the intersection point between 2 lines, it can return <code>null</code> if the 2 lines are //
020     */
021    public static Point2D getIntersection(Line2D lineA, Line2D lineB)
022    {
023        final double x1 = lineA.getX1();
024        final double y1 = lineA.getY1();
025        final double x2 = lineA.getX2();
026        final double y2 = lineA.getY2();
027
028        final double x3 = lineB.getX1();
029        final double y3 = lineB.getY1();
030        final double x4 = lineB.getX2();
031        final double y4 = lineB.getY2();
032
033        final double d = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
034
035        if (d != 0)
036        {
037            final double xi = ((x3 - x4) * (x1 * y2 - y1 * x2) - (x1 - x2) * (x3 * y4 - y3 * x4)) / d;
038            final double yi = ((y3 - y4) * (x1 * y2 - y1 * x2) - (y1 - y2) * (x3 * y4 - y3 * x4)) / d;
039
040            return new Point2D.Double(xi, yi);
041        }
042
043        return null;
044    }
045
046    /**
047     * Get intersection point between 2 lines.
048     *
049     * @param lineA
050     *        first line
051     * @param lineB
052     *        second line
053     * @param limitToSegmentA
054     *        Limit intersection to segment <i>lineA</i>, if intersection point is outside <i>lineA</i> then
055     *        <code>null</code> is returned
056     * @param limitToSegmentB
057     *        Limit intersection to segment <i>lineB</i>, if intersection point is outside <i>lineB</i> then
058     *        <code>null</code> is returned
059     */
060    public static Point2D getIntersection(Line2D lineA, Line2D lineB, boolean limitToSegmentA, boolean limitToSegmentB)
061    {
062        final Point2D result = getIntersection(lineA, lineB);
063
064        if (result == null)
065            return null;
066
067        final Rectangle2D rectPt = new Rectangle2D.Double(result.getX() - 0.5d, result.getY() - 0.5d, 1d, 1d);
068
069        // constraint to line segment if wanted
070        if (limitToSegmentA && !lineA.intersects(rectPt))
071            return null;
072        if (limitToSegmentB && !lineB.intersects(rectPt))
073            return null;
074
075        return result;
076    }
077
078    /**
079     * Returns rotation information from AffineTransform.<br>
080     * <b>WARNING:</b> this method may not be 100% accurate because of possible combined transformation
081     */
082    public static double getRotation(AffineTransform transform)
083    {
084        // use the iterative polar decomposition algorithm described by Ken Shoemake:
085        // http://www.cs.wisc.edu/graphics/Courses/838-s2002/Papers/polar-decomp.pdf
086
087        // start with the contents of the upper 2x2 portion of the matrix
088        double m00 = transform.getScaleX();
089        double m01 = transform.getShearX();
090        double m10 = transform.getShearY();
091        double m11 = transform.getScaleY();
092
093        for (int ii = 0; ii < 10; ii++)
094        {
095            // store the results of the previous iteration
096            final double o00 = m00;
097            final double o10 = m10;
098            final double o01 = m01;
099            final double o11 = m11;
100
101            // compute average of the matrix with its inverse transpose
102            final double det = o00 * o11 - o10 * o01;
103
104            // determinant is zero; matrix is not invertible --> use alternative method
105            if (Math.abs(det) == 0d)
106                return getRotationFast(transform);
107
108            final double hrdet = 0.5f / det;
109
110            m00 = +o11 * hrdet + o00 * 0.5f;
111            m10 = -o01 * hrdet + o10 * 0.5f;
112            m01 = -o10 * hrdet + o01 * 0.5f;
113            m11 = +o00 * hrdet + o11 * 0.5f;
114
115            // compute the difference; if it's small enough, we're done
116            final double d00 = m00 - o00;
117            final double d10 = m10 - o10;
118            final double d01 = m01 - o01;
119            final double d11 = m11 - o11;
120
121            if (((d00 * d00) + (d10 * d10) + (d01 * d01) + (d11 * d11)) < 0.0001d)
122                break;
123        }
124
125        // now that we have a nice orthogonal matrix, we can extract the rotation
126        return Math.atan2(m01, m00);
127    }
128
129    /**
130     * Returns rotation information from AffineTransform (fast method).<br>
131     * <b>WARNING:</b> this method may not be 100% accurate because of possible combined transformation
132     */
133    public static double getRotationFast(AffineTransform transform)
134    {
135        // m00, m01
136        final double a = transform.getScaleX();
137        final double b = transform.getShearX();
138
139        if ((Math.abs(a) >= 0.001d) && (Math.abs(b) >= 0.001d))
140            return Math.atan(-b / a);
141
142        // m10, m11
143        final double c = transform.getShearY();
144        final double d = transform.getScaleY();
145
146        if ((Math.abs(c) >= 0.001d) && (Math.abs(d) >= 0.001d))
147            return Math.atan(c / d);
148
149        // use alternative method
150        final Point2D pp0 = transform.transform(new Point(0, 0), null);
151        final Point2D pp1 = transform.transform(new Point(1, 0), null);
152        final double dx = pp1.getX() - pp0.getX();
153        final double dy = pp1.getY() - pp0.getY();
154
155        return Math.atan2(dy, dx);
156    }
157
158    /**
159     * Returns uniform scale (X=Y) information from AffineTransform.<br>
160     * <b>WARNING:</b> this method may not be 100% accurate because of possible combined transformation
161     */
162    public static double getScale(AffineTransform transform)
163    {
164        // m00, m01
165        final double a = transform.getScaleX();
166        final double b = transform.getShearX();
167        // m10, m11
168        final double c = transform.getShearY();
169        final double d = transform.getScaleY();
170
171        // the square root of the signed area of the parallelogram spanned by the axis vectors
172        final double cp = (a * d) - (b * c);
173
174        return (cp < 0f) ? -Math.sqrt(-cp) : Math.sqrt(cp);
175    }
176
177    /**
178     * Returns scale X information from AffineTransform.<br>
179     * <b>WARNING:</b> this method may not be 100% accurate because of possible combined transformation
180     */
181    public static double getScaleX(AffineTransform transform)
182    {
183        // m00, m10
184        final double a = transform.getScaleX();
185        final double c = transform.getShearY();
186
187        return Math.sqrt((a * a) + (c * c));
188    }
189
190    /**
191     * Returns scale Y information from AffineTransform.<br>
192     * <b>WARNING:</b> this method may not be 100% accurate because of possible combined transformation
193     */
194    public static double getScaleY(AffineTransform transform)
195    {
196        // m01, m11
197        final double b = transform.getShearX();
198        final double d = transform.getScaleY();
199
200        return Math.sqrt((b * b) + (d * d));
201    }
202}