# Source: geometry.js

    /*
     * 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;