/*
* Copyright 2024 The Ray Optics Simulation authors and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @typedef {Object} Point
* @property {number} x
* @property {number} y
*/
/**
* @typedef {Object} Line
* @property {Point} p1
* @property {Point} p2
*/
/**
* @typedef {Object} Circle
* @property {Point} c
* @property {number|Line} r
*/
/**
* The geometry module, which provides basic geometric figures and operations.
* @namespace geometry
*/
const geometry = {
/**
* Create a point
* @param {number} x - The x-coordinate of the point.
* @param {number} y - The y-coordinate of the point.
* @returns {Point}
*/
point: function (x, y) { return { x: x, y: y } },
/**
* Create a line, which also represents a ray or a segment.
* When used as a line, p1 and p2 are two distinct points on the line.
* When used as a ray, p1 is the starting point and p2 is another point on the ray.
* When used as a segment, p1 and p2 are the two endpoints of the segment.
* @param {Point} p1
* @param {Point} p2
* @returns {Line}
*/
line: function (p1, p2) { return { p1: p1, p2: p2 } },
/**
* Create a circle
* @param {Point} c - The center point of the circle.
* @param {number|Point} r - The radius of the circle or a point on the circle.
*/
circle: function (c, r) {
if (typeof r == 'object') {
return { c: c, r: geometry.line(c, r) }
} else {
return { c: c, r: r }
}
},
/**
* Calculate the dot product, where the two points are treated as vectors.
* @param {Point} p1
* @param {Point} p2
* @return {Number}
**/
dot: function (p1, p2) {
return p1.x * p2.x + p1.y * p2.y;
},
/**
* Calculate the cross product, where the two points are treated as vectors.
* @param {Point} p1
* @param {Point} p2
* @return {Number}
**/
cross: function (p1, p2) {
return p1.x * p2.y - p1.y * p2.x;
},
/**
* Calculate the intersection of two lines.
* @param {Line} l1
* @param {Line} l2
* @return {Point}
**/
linesIntersection: function (l1, l2) {
var A = l1.p2.x * l1.p1.y - l1.p1.x * l1.p2.y;
var B = l2.p2.x * l2.p1.y - l2.p1.x * l2.p2.y;
var xa = l1.p2.x - l1.p1.x;
var xb = l2.p2.x - l2.p1.x;
var ya = l1.p2.y - l1.p1.y;
var yb = l2.p2.y - l2.p1.y;
return geometry.point((A * xb - B * xa) / (xa * yb - xb * ya), (A * yb - B * ya) / (xa * yb - xb * ya));
},
/**
* Calculate the intersections of a line and a circle.
* @param {Line} l1
* @param {Circle} c1
* @return {Point[]}
*/
lineCircleIntersections: function (l1, c1) {
var xa = l1.p2.x - l1.p1.x;
var ya = l1.p2.y - l1.p1.y;
var cx = c1.c.x;
var cy = c1.c.y;
var r_sq = (typeof c1.r == 'object') ? ((c1.r.p1.x - c1.r.p2.x) * (c1.r.p1.x - c1.r.p2.x) + (c1.r.p1.y - c1.r.p2.y) * (c1.r.p1.y - c1.r.p2.y)) : (c1.r * c1.r);
var l = Math.sqrt(xa * xa + ya * ya);
var ux = xa / l;
var uy = ya / l;
var cu = ((cx - l1.p1.x) * ux + (cy - l1.p1.y) * uy);
var px = l1.p1.x + cu * ux;
var py = l1.p1.y + cu * uy;
var d = Math.sqrt(r_sq - (px - cx) * (px - cx) - (py - cy) * (py - cy));
var ret = [];
ret[1] = geometry.point(px + ux * d, py + uy * d);
ret[2] = geometry.point(px - ux * d, py - uy * d);
return ret;
},
/**
* Test if a point on the extension of a ray is actually on the ray.
* @param {Point} p1
* @param {Line} r1
* @return {Boolean}
*/
intersectionIsOnRay: function (p1, r1) {
return (p1.x - r1.p1.x) * (r1.p2.x - r1.p1.x) + (p1.y - r1.p1.y) * (r1.p2.y - r1.p1.y) >= 0;
},
/**
* Test if a point on the extension of a segment is actually on the segment.
* @param {Point} p1
* @param {Line} s1
* @return {Boolean}
*/
intersectionIsOnSegment: function (p1, s1) {
return (p1.x - s1.p1.x) * (s1.p2.x - s1.p1.x) + (p1.y - s1.p1.y) * (s1.p2.y - s1.p1.y) >= 0 && (p1.x - s1.p2.x) * (s1.p1.x - s1.p2.x) + (p1.y - s1.p2.y) * (s1.p1.y - s1.p2.y) >= 0;
},
/**
* Calculate the length of a line segment.
* @param {Line} seg
* @return {Number}
*/
segmentLength: function (seg) {
return Math.sqrt(geometry.segmentLengthSquared(seg));
},
/**
* Calculate the squared length of a line segment.
* @param {Line} seg
* @return {Number}
*/
segmentLengthSquared: function (seg) {
return geometry.distanceSquared(seg.p1, seg.p2);
},
/**
* Calculate the distance between two points.
* @param {Point} p1
* @param {Point} p2
* @return {Number}
*/
distance: function (p1, p2) {
return Math.sqrt(geometry.distanceSquared(p1, p2));
},
/**
* Calculate the squared distance between two points.
* @param {Point} p1
* @param {Point} p2
* @return {Number}
*/
distanceSquared: function (p1, p2) {
var dx = p1.x - p2.x;
var dy = p1.y - p2.y;
return dx * dx + dy * dy;
},
/**
* Calculate the midpoint of a segment.
* @param {Line} l1
* @return {Point}
*/
segmentMidpoint: function (l1) {
var nx = (l1.p1.x + l1.p2.x) * 0.5;
var ny = (l1.p1.y + l1.p2.y) * 0.5;
return geometry.point(nx, ny);
},
/**
* Calculate the midpoint between two points.
* @param {Point} p1
* @param {Point} p2
* @return {Point}
*/
midpoint: function (p1, p2) {
var nx = (p1.x + p2.x) * 0.5;
var ny = (p1.y + p2.y) * 0.5;
return geometry.point(nx, ny);
},
/**
* Calculate the perpendicular bisector of a segment.
* @param {Line} l1
* @return {Line}
*/
perpendicularBisector: function (l1) {
return geometry.line(
geometry.point(
(-l1.p1.y + l1.p2.y + l1.p1.x + l1.p2.x) * 0.5,
(l1.p1.x - l1.p2.x + l1.p1.y + l1.p2.y) * 0.5
),
geometry.point(
(l1.p1.y - l1.p2.y + l1.p1.x + l1.p2.x) * 0.5,
(-l1.p1.x + l1.p2.x + l1.p1.y + l1.p2.y) * 0.5
)
);
},
/**
* Calculate the line though p1 and parallel to l1.
* @param {Line} l1
* @param {Point} p1
* @return {Line}
*/
parallelLineThroughPoint: function (l1, p1) {
var dx = l1.p2.x - l1.p1.x;
var dy = l1.p2.y - l1.p1.y;
return geometry.line(p1, geometry.point(p1.x + dx, p1.y + dy));
}
};
export default geometry;