/* * Scriptographer * * This file is part of Scriptographer, a Scripting Plugin for Adobe Illustrator * http://scriptographer.org/ * * Copyright (c) 2002-2010, Juerg Lehni * http://scratchdisk.com/ * * All rights reserved. See LICENSE file for details. * * File created on 20.12.2004. */ package com.scriptographer.ai; import java.awt.geom.Point2D; import com.scratchdisk.script.ArgumentReader; import com.scratchdisk.script.ChangeEmitter; import com.scriptographer.ScriptographerEngine; /** * The Point object represents a point in the two dimensional space of the * Illustrator document. It is also used to represent two dimensional vector * objects. * * @jsreference {@type field} {@name selected} {@reference * SegmentPoint#selected} {@after angle} * * @author lehni */ public class Point implements ChangeEmitter { // TODO: Move TOLERANCE somewhere where it makes more sense private static final double TOLERANCE = 10e-6; protected double x; protected double y; // Caching of angle if used protected Double angle; public Point() { x = y = 0; } /** * Creates a Point object with the given x and y coordinates. * * Sample code: * <code> * // Create a point at x: 10pt, y: 5pt * var point = new Point(10, 5); * print(point.x); // 10 * print(point.y); // 5 * </code> * @param x The x coordinate of the point {@default 0} * @param y The y coordinate of the point {@default 0} */ public Point(double x, double y) { this.x = x; this.y = y; } public Point(float x, float y) { this.x = x; this.y = y; } /** * Creates a Point object using the width and height values of the given * Size object. * * Sample code: * <code> * // Create a Size with a width of 100pt and a height of 50pt * var size = new Size(100, 50); * print(size); // prints { width: 100.0, height: 50.0 } * var point = new Point(size); * print(point); // prints { x: 100.0, y: 50.0 } * </code> * * @param size */ public Point(Size size) { x = size.width; y = size.height; } /** * Creates a Point object using the coordinates of the given Point object. * @param point */ public Point(Point point) { set(point); } /** * @jshide */ public Point(Point2D point) { if (point != null) set(point.getX(), point.getY()); } /** * @jshide */ public Point(ArgumentReader reader) { if (reader.isArray()) { this.x = reader.readDouble(0); this.y = reader.readDouble(0); } else if (reader.has("x")) { this.x = reader.readDouble("x", 0); this.y = reader.readDouble("y", 0); } else if (reader.has("width")) { this.x = reader.readDouble("width", 0); this.y = reader.readDouble("height", 0); } else if (reader.has("length")) { this.x = reader.readDouble("length", 0); this.setAngle(reader.readDouble("angle", 0)); } } /** * @jshide */ public void set(double x, double y) { this.x = x; this.y = y; // Reset angle angle = null; } /** * @jshide */ public final void set(Point point) { if (point != null) { set(point.x, point.y); // Copy over angle, in case of length == 0 angle = point.angle; } else { set(0, 0); } } /** * The x coordinate of the point */ public double getX() { return x; } public void setX(double x) { this.x = x; // Reset angle angle = null; } /** * The y coordinate of the point */ public double getY() { return y; } public void setY(double y) { this.y = y; // Reset angle angle = null; } /** * Returns a copy of the point. * This is useful as the following code only generates a flat copy: * * <code> * var point1 = new Point(); * var point2 = point1; * point2.x = 1; // also changes point1.x * * var point2 = point1.clone(); * point2.x = 1; // doesn't change point1.x * </code> * * @return the cloned point */ public Object clone() { return new Point(this); } /** * @jshide */ public Point add(double x, double y) { return new Point(this.x + x, this.y + y); } /** * Returns the addition of the supplied point to the point as a new * point. * The object itself is not modified! * * Sample code: * <code> * var point1 = new Point(5, 10); * var point2 = new Point(10, 20); * var result = point1 + point2; * print(result); // { x: 15.0, y: 30.0 } * </code> * * @param point the point to add * @return the addition of the two points as a new point */ public Point add(Point point) { return add(point.x, point.y); } /** * Returns the addition of the supplied value to both coordinates of * the point as a new point. * The object itself is not modified! * * Sample code: * <code> * var point = new Point(5, 10); * var result = point + 20; * print(result); // { x: 25.0, y: 30.0 } * </code> * * @param value the value to add * @return the addition of the point and the value as a new point */ public Point add(double value) { return add(value, value); } /** * Returns the subtraction of the supplied x and y values from the point * as a new point. * The object itself is not modified! * * Sample code: * <code> * var firstPoint = new Point(10, 20); * var result = firstPoint.subtract(5,5); * print(result); // { x: 5.0, y: 15.0 } * </code> * * @param x The x value to subtract * @param y The y value to subtract * @return the subtraction of the two points as a new point * * @jshide */ public Point subtract(double x, double y) { return new Point(this.x - x, this.y - y); } /** * Returns the subtraction of the supplied point from the point as a * new point. * The object itself is not modified! * * Sample code: * <code> * var firstPoint = new Point(10, 20); * var secondPoint = new Point(5, 5); * var result = firstPoint - secondPoint; * print(result); // { x: 5.0, y: 15.0 } * </code> * * @param point the point to subtract * @return the subtraction of the two points as a new point */ public Point subtract(Point point) { return subtract(point.x, point.y); } /** * Returns the subtraction of the supplied value from both coordinates of * the point as a new point. * The object itself is not modified! * * Sample code: * <code> * var point = new Point(10, 20); * var result = point - 5; * print(result); // { x: 5.0, y: 15.0 } * </code> * * @param point the value to subtract * @return the subtraction of the value from the point as a new point */ public Point subtract(double value) { return subtract(value, value); } /** * Returns the multiplication of the point with the supplied x and y * values as a new point. When no y value is supplied, the point's x and y * values are multiplied by scale (x). * The object itself is not modified! * * Sample code: * <code> * var point = new Point(5, 10); * * var result = point.multiply(4, 2); * print(result); // { x: 20.0, y: 20.0 } * * var result = point.multiply(2); * print(result); // { x: 10.0, y: 20.0 } * </code> * * @param x the x (or scale) value to multiply with * @param y the y value to multiply with * @return the multiplication of the two points as a new point * * @jshide */ public Point multiply(double x, double y) { return new Point(this.x * x, this.y * y); } /** * Returns the multiplication of the point with the supplied point as a * new point. * The object itself is not modified! * * Sample code: * <code> * var firstPoint = new Point(5, 10); * var secondPoint = new Point(4, 2); * var result = firstPoint * secondPoint; * print(result); // { x: 20.0, y: 20.0 } * </code> * * @param point the point to multiply with * @return the multiplication of the two points as a new point */ public Point multiply(Point point) { return multiply(point.x, point.y); } /** * Returns the multiplication of the supplied value with both coordinates of * the point as a new point. * The object itself is not modified! * * Sample code: * <code> * var point = new Point(10, 20); * var result = point * 2; * print(result); // { x: 20.0, y: 40.0 } * </code> * * @param point the value to multiply with * @return the multiplication of the point by the supplied value as a new * point */ public Point multiply(double value) { Point res = new Point(x * value, y * value); // Preserve angle res.angle = angle; return res; } /** * @jshide */ public Point divide(double x, double y) { return new Point(this.x / x, this.y / y); } /** * Returns the division of the point by the supplied point as a * new point. * The object itself is not modified! * * Sample code: * <code> * var firstPoint = new Point(8, 10); * var secondPoint = new Point(2, 5); * var result = firstPoint / secondPoint; * print(result); // { x: 4.0, y: 2.0 } * </code> * * @param point the point to divide by * @return the division of the two points as a new point */ public Point divide(Point point) { return divide(point.x, point.y); } /** * Returns the division of both coordinates of the point by the supplied * value and returns it as a new point. * The object itself is not modified! * * Sample code: * <code> * var point = new Point(10, 20); * var result = point / 2; * print(result); // { x: 5.0, y: 10.0 } * </code> * * @param point the value to divide by * @return the division of the point by the supplied value as a new point */ public Point divide(double value) { Point res = new Point(x / value, y / value); // Preserve angle res.angle = angle; return res; } /** * @jshide */ public Point modulo(double x, double y) { return new Point(this.x % x, this.y % y); } /** * The modulo operator returns the integer remainders of dividing the point * by the supplied point as a new point. * * Sample code: * <code> * var point = new Point(12, 6); * print(point % new Point(5, 2)); // {x: 2, y: 0} * </code> * * @param point * @return the integer remainders of dividing the points by each other as a * new point */ public Point modulo(Point point) { return modulo(point.x, point.y); } /** * The modulo operator returns the integer remainders of dividing the point * by the supplied value as a new point. * * Sample code: * <code> * var point = new Point(12, 6); * print(point % 5); // {x: 2, y: 1} * </code> * * @param value * @return the integer remainders of dividing the point by the value as a new point */ public Point modulo(double value) { return modulo(value, value); } /** * @jshide */ public Point negate() { return new Point(-x, -y); } /** * Checks whether the coordinates of the point are equal to that of the * supplied point. * * Sample code: * <code> * var point = new Point(5, 10); * print(point == new Point(5, 10)); // true * print(point == new Point(1, 1)); // false * print(point != new Point(1, 1)); // true * </code> */ public boolean equals(Object object) { if (object instanceof Point) { Point pt = (Point) object; return pt.x == x && pt.y == y; } // TODO: support other point types? return false; } public Point transform(Matrix matrix) { return matrix != null ? matrix.transform(this) : new Point(this); } /** * Returns the distance between the point and another point. * * Sample code: * <code> * var firstPoint = new Point(5, 10); * * var distance = firstPoint.getDistance(5, 20); * * print(distance); // 10 * </code> * @param px * @param py * * @jshide */ public double getDistance(double px, double py) { px -= x; py -= y; return Math.sqrt(px * px + py * py); } /** * {@grouptitle Distance & Length} * * Returns the distance between the point and another point. * * Sample code: * <code> * var firstPoint = new Point(5, 10); * var secondPoint = new Point(5, 20); * * var distance = firstPoint.getDistance(secondPoint); * * print(distance); // 10 * </code> * * @param px * @param py */ public double getDistance(Point point) { return getDistance(point.x, point.y); } /** * @jshide */ public double getDistanceSquared(double px, double py) { px -= x; py -= y; return px * px + py * py; } /** * @jshide */ public double getDistanceSquared(Point point) { return getDistanceSquared(point.x, point.y); } /** * The length of the vector that is represented by this point's coordinates. * Each point can be interpreted as a vector that points from the origin * ({@code x = 0},{@code y = 0}) to the point's location. * Setting the length changes the location but keeps the vector's angle. */ public double getLength() { return Math.sqrt(x * x + y * y); } public void setLength(double length) { if (isZero()) { // Use angle now to set x and y if (angle != null) { double a = angle; x = Math.cos(a) * length; y = Math.sin(a) * length; } else { // Assume angle = 0 x = length; // y is already 0 } } else { double scale = length / getLength(); if (scale == 0.0) { // Calculate angle now, so it will be preserved even when // x and y are 0. getAngle(); } x *= scale; y *= scale; } } public Point normalize(double length) { double len = getLength(); // Prevent division by 0 double scale = len != 0 ? length / len : 0; Point res = new Point(x * scale, y * scale); // Preserve angle. res.angle = angle; return res; } public Point normalize() { return normalize(1); } /** * For internal use regardless of userland angle units. */ protected double getAngleInRadians() { return Math.atan2(y, x); } /** * For internal use regardless of userland angle units. */ protected double getAngleInDegrees() { return Math.atan2(y, x) * 180.0 / Math.PI; } /** * The vector's angle, measured from the x-axis to the vector. * * Angle units are controlled by the * {@link com.scriptographer.sg.Script#getAngleUnits() } property, and are in * degrees by default. * * The angle orientation is controlled by the * {@link com.scriptographer.sg.Script#getCoordinateSystem() } property, * which is {@code 'top-down' } by default, leading to clockwise angle * orientation. In the {@code 'bottom-up' } coordinate system, angles are * specified in counter-clockwise orientation. */ public double getAngle() { // Cache the angle in the internal angle field, so we can return // that next time and also preserve the angle if length is set to 0. if (angle == null) angle = Math.atan2(y, x); return ScriptographerEngine.anglesInDegrees ? angle * 180.0 / Math.PI : angle; } public int getQuadrant() { if (x >= 0) { if (y >= 0) { return 1; } else { return 4; } } else { if (y >= 0) { return 2; } else { return 3; } } } public void setAngle(double angle) { if (ScriptographerEngine.anglesInDegrees) angle = angle * Math.PI / 180.0; this.angle = angle; if (!isZero()) { double length = getLength(); x = Math.cos(angle) * length; y = Math.sin(angle) * length; } } /** * {@grouptitle Angle & Rotation} * * Returns the smaller angle between two vectors. The angle is unsigned, no * information about rotational direction is given. * * Read more about angle units and orientation in the description of the * {@link #getAngle()} property. * * @param point */ public double getAngle(Point point) { double div = getLength() * point.getLength(); if (div == 0) return Double.NaN; else { double a = this.dot(point) / div; double angle = Math.acos(a < -1.0 ? -1.0 : a > 1.0 ? 1.0 : a); return ScriptographerEngine.anglesInDegrees ? angle * 180.0 / Math.PI : angle; } } /** * Returns the angle between two vectors. The angle is directional and * signed, giving information about the rotational direction. * * Read more about angle units and orientation in the description of the * {@link #getAngle()} property. * * @param point */ public double getDirectedAngle(Point point) { double angle = Math.atan2(this.cross(point), this.dot(point)); return ScriptographerEngine.anglesInDegrees ? angle * 180 / Math.PI : angle; } /** * Rotates the point by the given angle. * The object itself is not modified. * * Read more about angle units and orientation in the description of the * {@link #getAngle()} property. * * @param angle the rotation angle * @return the rotated point */ public Point rotate(double angle) { if (ScriptographerEngine.anglesInDegrees) angle = angle * Math.PI / 180.0; double s = Math.sin(angle); double c = Math.cos(angle); return new Point( x * c - y * s, y * c + x * s ); } /** * Rotates the point around a center point. * The object itself is not modified. * * Read more about angle units and orientation in the description of the * {@link #getAngle()} property. * * @param angle the rotation angle * @param center the center point of the rotation * @return the rotated point */ public Point rotate(double angle, Point center) { return rotate(angle, center != null ? center.x : 0, center != null ? center.y : 0 ); } /** * Rotates the point around a center point. * The object itself is not modified. * * Read more about angle units and orientation in the description of the * {@link #getAngle()} property. * * @param angle the rotation angle * @param x the x coordinate of the center point * @param y the y coordinate of the center point * @return the rotated point * * @jshide */ public Point rotate(double angle, double x, double y) { return subtract(x, y).rotate(angle).add(x, y); } /** * Returns the interpolation point between the point and another point. * The object itself is not modified! * * @param point * @param t the position between the two points as a value between 0 and 1 * @return the interpolation point * * @jshide */ public Point interpolate(Point point, double t) { return new Point( x * (1f - t) + point.x * t, y * (1f - t) + point.y * t ); } /** * {@grouptitle Tests} * * Checks whether the point is inside the boundaries of the rectangle. * * @param rect the rectangle to check against * @return {@true if the point is inside the rectangle} */ public boolean isInside(Rectangle rect) { return rect.contains(this); } /** * Checks if the point is within a given distance of another point. * * @param point the point to check against * @param tolerance the maximum distance allowed * @return {@true if it is within the given distance} */ public boolean isClose(Point point, double tolerance) { return getDistance(point) < tolerance; } /** * Checks if the vector represented by this point is collinear (parallel) to * another vector. * * @param point the vector to check against * @return {@true if it is parallel} */ public boolean isColinear(Point point) { return this.cross(point) < TOLERANCE; } /** * @deprecated */ public boolean isParallel(Point point) { return isColinear(point); } /** * Checks if the vector represented by this point is orthogonal * (perpendicular) to another vector. * * @param point the vector to check against * @return {@true if it is orthogonal} */ public boolean isOrthogonal(Point point) { return this.dot(point) < TOLERANCE; } /** * Checks if this point has both the x and y coordinate set to 0. * * @return {@true if both x and y are 0} */ public boolean isZero() { return x == 0 && y == 0; } /** * Checks if this point has an undefined value for at least one of its * coordinates. * * @return {@true if either x or y are not a number} */ public boolean isNaN() { return Double.isNaN(x) || Double.isNaN(y); } /** * {@grouptitle Math Functions} * * Returns a new point with rounded {@link #x} and {@link #y} values. The * object itself is not modified! * * Sample code: * <code> * var point = new Point(10.2, 10.9); * var roundPoint = point.round(); * print(roundPoint); // { x: 10.0, y: 11.0 } * </code> */ public Point round() { return new Point(Math.round(x), Math.round(y)); } /** * Returns a new point with the nearest greater non-fractional values to the * specified {@link #x} and {@link #y} values. The object itself is not * modified! * * Sample code: * <code> * var point = new Point(10.2, 10.9); * var ceilPoint = point.ceil(); * print(ceilPoint); // { x: 11.0, y: 11.0 } * </code> */ public Point ceil() { return new Point(Math.ceil(x), Math.ceil(y)); } /** * Returns a new point with the nearest smaller non-fractional values to the * specified {@link #x} and {@link #y} values. The object itself is not * modified! * * Sample code: * <code> * var point = new Point(10.2, 10.9); * var floorPoint = point.floor(); * print(floorPoint); // { x: 10.0, y: 10.0 } * </code> */ public Point floor() { return new Point(Math.floor(x), Math.floor(y)); } /** * Returns a new point with the absolute values of the specified {@link #x} * and {@link #y} values. The object itself is not modified! * * Sample code: * <code> * var point = new Point(-5, 10); * var absPoint = point.abs(); * print(absPoint); // { x: 5.0, y: 10.0 } * </code> */ public Point abs() { return new Point(Math.abs(x), Math.abs(y)); } /** * {@grouptitle Vectorial Math Functions} * * Returns the dot product of the point and another point. * @param point * @return the dot product of the two points */ public double dot(Point point) { return x * point.x + y * point.y; } /** * Returns the cross product of the point and another point. * @param point * @return the cross product of the two points */ public double cross(Point point) { return x * point.y - y * point.x; } /** * Returns the projection of the point on another point. * Both points are interpreted as vectors. * * @param point * @return the project of the point on another point */ public Point project(Point point) { if (point.x == 0 && point.y == 0) { return new Point(0, 0); } else { double scale = dot(point) / point.dot(point); return new Point( point.x * scale, point.y * scale ); } } /** * Returns a new point object with the smallest {@link #x} and * {@link #y} of the supplied points. * * Sample code: * <code> * var point1 = new Point(10, 100); * var point2 = new Point(200, 5); * var minPoint = Point.min(point1, point2); * print(minPoint); // { x: 10.0, y: 5.0 } * </code> * * @param point1 * @param point2 * @return The newly created point object */ public static Point min(Point point1, Point point2) { return new Point( Math.min(point1.x, point2.x), Math.min(point1.y, point2.y)); } /** * Returns a new point object with the largest {@link #x} and * {@link #y} of the supplied points. * * Sample code: * <code> * var point1 = new Point(10, 100); * var point2 = new Point(200, 5); * var maxPoint = Point.max(point1, point2); * print(maxPoint); // { x: 200.0, y: 100.0 } * </code> * * @param point1 * @param point2 * @return The newly created point object */ public static Point max(Point point1, Point point2) { return new Point( Math.max(point1.x, point2.x), Math.max(point1.y, point2.y)); } /** * Returns a point object with random {@link #x} and {@link #y} values * between {@code 0} and {@code 1}. * * Sample code: * <code> * var maxPoint = new Point(100, 100); * var randomPoint = Point.random(); * * // A point between {x:0, y:0} and {x:100, y:100}: * var point = maxPoint * randomPoint; * </code> */ public static Point random() { return new Point(Math.random(), Math.random()); } protected Point2D toPoint2D() { return new Point2D.Double(x, y); } public String toString() { return "{ x: " + ScriptographerEngine.numberFormat.format(x) + ", y: " + ScriptographerEngine.numberFormat.format(y) + " }"; } }