/**
* The Point object represents a point in the two dimensional space of the
* Paper.js document. It is also used to represent two dimensional vector
* objects.
*/
var Point = this.Point = Base.extend({
beans: true,
initialize: function(arg0, arg1) {
if (arguments.length == 2) {
this.x = arg0;
this.y = arg1;
} else if (arguments.length == 1) {
if (arg0 == null) {
this.x = this.y = 0;
} else if (arg0.x !== undefined) {
this.x = arg0.x;
this.y = arg0.y;
} else if (arg0.width !== undefined) {
this.x = arg0.width;
this.y = arg0.height;
} else if (Array.isArray(arg0)) {
this.x = arg0[0];
this.y = arg0.length > 1 ? arg0[1] : arg0[0];
} else if (typeof arg0 === 'number') {
this.x = this.y = arg0;
} else {
this.x = this.y = 0;
}
} else {
this.x = this.y = 0;
}
},
set: function(x, y) {
this.x = x;
this.y = y;
return this;
},
/**
* Returns a copy of the point.
* This is useful as the following code only generates a flat copy:
*
*
* 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
*
*
* @return the cloned point
*/
clone: function() {
return Point.create(this.x, this.y);
},
add: function() {
var point = Point.read(arguments);
return Point.create(this.x + point.x, this.y + point.y);
},
subtract: function() {
var point = Point.read(arguments);
return Point.create(this.x - point.x, this.y - point.y);
},
multiply: function() {
var point = Point.read(arguments);
return Point.create(this.x * point.x, this.y * point.y);
},
divide: function() {
var point = Point.read(arguments);
return Point.create(this.x / point.x, this.y / point.y);
},
modulo: function() {
var point = Point.read(arguments);
return Point.create(this.x % point.x, this.y % point.y);
},
negate: function() {
return Point.create(-this.x, -this.y);
},
equals: function() {
var point = Point.read(arguments);
return this.x == point.x && this.y == point.y;
},
transform: function(matrix) {
return matrix.transform(this);
},
/**
* Returns the distance between the point and another point.
*
* Sample code:
*
* var firstPoint = new Point(5, 10);
* var secondPoint = new Point(5, 20);
*
* var distance = firstPoint.getDistance(secondPoint);
*
* print(distance); // 10
*
*
* @param px
* @param py
*/
getDistance: function() {
var point = Point.read(arguments);
var px = point.x - this.x;
var py = point.y - this.y;
return Math.sqrt(px * px + py * py);
},
getDistanceSquared: function() {
var point = Point.read(arguments);
var px = point.x - this.x;
var py = point.y - this.y;
return px * px + py * py;
},
/**
* 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.
*/
getLength: function() {
var point = Point.read(arguments);
return Math.sqrt(this.x * this.x + this.y * this.y);
},
setLength: function(length) {
if (this.isZero()) {
if (this._angle != null) {
var a = this._angle;
this.x = Math.cos(a) * length;
this.y = Math.sin(a) * length;
} else {
// Assume angle = 0
this.x = length;
// y is already 0
}
} else {
var scale = length / this.getLength();
if (scale == 0.0) {
// Calculate angle now, so it will be preserved even when
// x and y are 0
this.getAngle();
}
this.x *= scale;
this.y *= scale;
}
return this;
},
normalize: function(length) {
if (length === null)
length = 1;
var len = this.getLength();
var scale = len != 0 ? length / len : 0;
var res = Point.create(this.x * scale, this.y * scale);
// Preserve angle.
res._angle = this._angle;
return res;
},
getAngleInRadians: function() {
// TODO: Not sur we want this one, but if so, just rely on
// this.getAngle(), which caches values internally?
return Math.atan2(this.y, this.x);
},
getAngleInDegrees: function() {
// TODO: Not sur we want this one, but if so, just rely on
// this.getAngle(), which caches values internally?
return Math.atan2(this.y, this.x) * 180 / Math.PI;
},
getQuadrant: function() {
if (this.x >= 0) {
if (this.y >= 0) {
return 1;
} else {
return 4;
}
} else {
if (this.y >= 0) {
return 2;
} else {
return 3;
}
}
},
/**
* {@grouptitle Angle & Rotation}
*
* The vector's angle, measured from the x-axis to the vector.
*
* When supplied with a point, 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
*/
getAngle: function() {
var angle;
if (arguments.length) {
var point = Point.read(arguments);
var div = this.getLength() * point.getLength();
if (div == 0) {
return NaN;
} else {
angle = Math.acos(this.dot(point) / div);
}
} else {
if (this._angle == null)
this._angle = Math.atan2(this.y, this.x);
angle = this._angle;
}
return angle * 180 / Math.PI;
},
setAngle: function(angle) {
angle = this._angle = angle * Math.PI / 180;
if (!this.isZero()) {
var length = this.getLength();
this.x = Math.cos(angle) * length;
this.y = Math.sin(angle) * length;
}
return this;
},
/**
* 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
*/
getDirectedAngle: function() {
var point = Point.read(arguments);
var angle = this.getAngle() - point.getAngle();
var bounds = 180;
if (angle < - bounds) {
return angle + bounds * 2;
} else if (angle > bounds) {
return angle - bounds * 2;
} else {
return angle;
}
},
/**
* Rotates the point by the given angle around an optional 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
*/
rotate: function(angle, center) {
var point = center ? this.subtract(center) : this;
angle = angle * Math.PI / 180;
var s = Math.sin(angle);
var c = Math.cos(angle);
point = Point.create(
point.x * c - point.y * s,
point.y * c + point.x * s
);
return center ? point.add(center) : point;
},
/**
* 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
*/
interpolate: function(point, t) {
return Point.create(
this.x * (1 - t) + point.x * t,
this.y * (1 - 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}
*/
isInside: function(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}
*/
isClose: function(point, tolerance) {
return this.getDistance(point) < tolerance;
},
/**
* Checks if the vector represented by this point is parallel (collinear) to
* another vector.
*
* @param point the vector to check against
* @return {@true if it is parallel}
*/
isParallel: function(point) {
// TODO: Tolerance seems rather high!
return Math.abs(this.x / point.x - this.y / point.y) < 0.00001;
},
/**
* Checks if this point has both the x and y coordinate set to 0.
*
* @return {@true if both x and y are 0}
*/
isZero: function() {
return this.x == 0 && this.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}
*/
isNaN: function() {
return isNaN(this.x) || isNaN(this.y);
},
/**
* {@grouptitle Math Functions}
*
* Returns a new point with rounded {@link #x} and {@link #y} values. The
* object itself is not modified!
*
* Sample code:
*
* var point = new Point(10.2, 10.9);
* var roundPoint = point.round();
* print(roundPoint); // { x: 10.0, y: 11.0 }
*
*/
round: function() {
return Point.create(Math.round(this.x), Math.round(this.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:
*
* var point = new Point(10.2, 10.9);
* var ceilPoint = point.ceil();
* print(ceilPoint); // { x: 11.0, y: 11.0 }
*
*/
ceil: function() {
return Point.create(Math.ceil(this.x), Math.ceil(this.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:
*
* var point = new Point(10.2, 10.9);
* var floorPoint = point.floor();
* print(floorPoint); // { x: 10.0, y: 10.0 }
*
*/
floor: function() {
return Point.create(Math.floor(this.x), Math.floor(this.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:
*
* var point = new Point(-5, 10);
* var absPoint = point.abs();
* print(absPoint); // { x: 5.0, y: 10.0 }
*
*/
abs: function() {
return Point.create(Math.abs(this.x), Math.abs(this.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
*/
dot: function() {
var point = Point.read(arguments);
return this.x * point.x + this.y * point.y;
},
/**
* Returns the cross product of the point and another point.
* @param point
* @return the cross product of the two points
*/
cross: function() {
var point = Point.read(arguments);
return this.x * point.y - this.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
*/
project: function() {
var point = Point.read(arguments);
if (point.isZero()) {
return Point.create(0, 0);
} else {
var scale = this.dot(point) / point.dot(point);
return Point.create(
point.x * scale,
point.y * scale
);
}
},
toString: function() {
return '{ x: ' + this.x + ', y: ' + this.y + ' }';
},
statics: {
/**
* Provide a faster creator for Points out of two coordinates that
* does not rely on Point#initialize at all. This speeds up all math
* operations a lot.
*/
create: function(x, y) {
var point = new Point(Point.dont);
point.x = x;
point.y = y;
return point;
},
/**
* Returns a new point object with the smallest {@link #x} and
* {@link #y} of the supplied points.
*
* Sample 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 }
*
*
* @param point1
* @param point2
* @return The newly created point object
*/
min: function(point1, point2) {
return Point.create(
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:
*
* 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 }
*
*
* @param point1
* @param point2
* @return The newly created point object
*/
max: function(point1, point2) {
return Point.create(
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:
*
* 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;
*
*/
random: function() {
return Point.create(Math.random(), Math.random());
}
}
});