# Source: sceneObjs/glass/Glass.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.
     */

    import BaseGlass from '../BaseGlass.js';
    import i18next from 'i18next';
    import Simulator from '../../Simulator.js';
    import geometry from '../../geometry.js';

    /**
     * Glass of the shape consists of line segments or circular arcs.
     *
     * Tools -> Glass -> Polygon / Circular Arcs
     * @class
     * @extends BaseGlass
     * @memberof sceneObjs
     * @property {Array<object>} path - The path of the glass. Each element is an object with `x` and `y` properties for coordinates, and a boolean `arc`. If `path[i].arc === false`, it means that `path[i-1]`--`path[i]` and `path[i]`--`path[i+1]` are line segments, if `path[i].arc === true`, it means that `path[i-1]`--`path[i]`--`path[i+1]` is a circular arc.
     * @property {boolean} notDone - Whether the user is still drawing the glass.
     * @property {number} refIndex - The refractive index of the glass, or the Cauchy coefficient A of the glass if "Simulate Colors" is on.
     * @property {number} cauchyB - The Cauchy coefficient B of the glass if "Simulate Colors" is on, in micrometer squared.
     */
    class Glass extends BaseGlass {
      static type = 'Glass';
      static isOptical = true;
      static supportsSurfaceMerging = true;
      static serializableDefaults = {
        path: [],
        notDone: false,
        refIndex: 1.5,
        cauchyB: 0.004
      };

      populateObjBar(objBar) {
        objBar.setTitle(i18next.t('main:tools.categories.glass'));
        super.populateObjBar(objBar);
      }

      draw(canvasRenderer, isAboveLight, isHovered) {
        const ctx = canvasRenderer.ctx;
        const ls = canvasRenderer.lengthScale;

        var p1;
        var p2;
        var p3;
        var center;
        var r;
        var a1;
        var a2;
        var a3;
        var acw;

        if (this.notDone) {
          // The user has not finish drawing the object yet

          if (this.path.length === 2 && this.path[0].x === this.path[1].x && this.path[0].y === this.path[1].y) {
            ctx.fillStyle = 'rgb(255,0,0)';
            ctx.fillRect(this.path[0].x - 1.5 * ls, this.path[0].y - 1.5 * ls, 3 * ls, 3 * ls);
            return;
          }
          ctx.beginPath();
          ctx.moveTo(this.path[0].x, this.path[0].y);

          for (var i = 0; i < this.path.length - 1; i++) {
            if (this.path[(i + 1)].arc && !this.path[i].arc && i < this.path.length - 2) {
              p1 = geometry.point(this.path[i].x, this.path[i].y);
              p2 = geometry.point(this.path[(i + 2)].x, this.path[(i + 2)].y);
              p3 = geometry.point(this.path[(i + 1)].x, this.path[(i + 1)].y);
              center = geometry.linesIntersection(geometry.perpendicularBisector(geometry.line(p1, p3)), geometry.perpendicularBisector(geometry.line(p2, p3)));
              if (isFinite(center.x) && isFinite(center.y)) {
                r = geometry.distance(center, p3);
                a1 = Math.atan2(p1.y - center.y, p1.x - center.x);
                a2 = Math.atan2(p2.y - center.y, p2.x - center.x);
                a3 = Math.atan2(p3.y - center.y, p3.x - center.x);
                acw = (a2 < a3 && a3 < a1) || (a1 < a2 && a2 < a3) || (a3 < a1 && a1 < a2); // The rotation direction of p1->p3->p2. True indicates counterclockwise

                ctx.arc(center.x, center.y, r, a1, a2, acw);
              } else {
                // The three points on the arc is colinear. Treat as a line segment.
                ctx.lineTo(this.path[(i + 2)].x, this.path[(i + 2)].y);
              }
            } else {
              ctx.lineTo(this.path[(i + 1)].x, this.path[(i + 1)].y);
            }
          }
          ctx.globalAlpha = 1;
          ctx.strokeStyle = 'rgb(128,128,128)';
          ctx.lineWidth = 1 * ls;
          ctx.stroke();
        } else {
          // The user has completed drawing the object
          ctx.beginPath();
          ctx.moveTo(this.path[0].x, this.path[0].y);

          for (var i = 0; i < this.path.length; i++) {
            if (this.path[(i + 1) % this.path.length].arc && !this.path[i % this.path.length].arc) {
              p1 = geometry.point(this.path[i % this.path.length].x, this.path[i % this.path.length].y);
              p2 = geometry.point(this.path[(i + 2) % this.path.length].x, this.path[(i + 2) % this.path.length].y);
              p3 = geometry.point(this.path[(i + 1) % this.path.length].x, this.path[(i + 1) % this.path.length].y);
              center = geometry.linesIntersection(geometry.perpendicularBisector(geometry.line(p1, p3)), geometry.perpendicularBisector(geometry.line(p2, p3)));
              if (isFinite(center.x) && isFinite(center.y)) {
                r = geometry.distance(center, p3);
                a1 = Math.atan2(p1.y - center.y, p1.x - center.x);
                a2 = Math.atan2(p2.y - center.y, p2.x - center.x);
                a3 = Math.atan2(p3.y - center.y, p3.x - center.x);
                acw = (a2 < a3 && a3 < a1) || (a1 < a2 && a2 < a3) || (a3 < a1 && a1 < a2); // The rotation direction of p1->p3->p2. True indicates counterclockwise

                ctx.arc(center.x, center.y, r, a1, a2, acw);
              } else {
                // The three points on the arc is colinear. Treat as a line segment.
                ctx.lineTo(this.path[(i + 2) % this.path.length].x, this.path[(i + 2) % this.path.length].y);
              }
            } else if (!this.path[(i + 1) % this.path.length].arc && !this.path[i % this.path.length].arc) {
              ctx.lineTo(this.path[(i + 1) % this.path.length].x, this.path[(i + 1) % this.path.length].y);
            }
          }
          this.fillGlass(canvasRenderer, isAboveLight, isHovered);
        }
        ctx.lineWidth = 1;

        if (isHovered) {
          for (var i = 0; i < this.path.length; i++) {
            if (typeof this.path[i].arc != 'undefined') {
              if (this.path[i].arc) {
                ctx.fillStyle = 'rgb(255,0,255)';
                ctx.fillRect(this.path[i].x - 1.5 * ls, this.path[i].y - 1.5 * ls, 3 * ls, 3 * ls);
              } else {
                ctx.fillStyle = 'rgb(255,0,0)';
                ctx.fillRect(this.path[i].x - 1.5 * ls, this.path[i].y - 1.5 * ls, 3 * ls, 3 * ls);
              }
            }
          }
        }
      }

      move(diffX, diffY) {
        for (var i = 0; i < this.path.length; i++) {
          this.path[i].x += diffX;
          this.path[i].y += diffY;
        }
      }

      onConstructMouseDown(mouse, ctrl, shift) {
        const mousePos = mouse.getPosSnappedToGrid();
        if (!this.notDone) {
          // Initialize the construction stage
          this.notDone = true;
          this.path = [{ x: mousePos.x, y: mousePos.y, arc: false }];
        }

        if (this.path.length > 1) {
          if (this.path.length > 3 && mouse.snapsOnPoint(this.path[0])) {
            // Clicked the first point
            this.path.length--;
            this.notDone = false;
            return;
          }
          this.path[this.path.length - 1] = { x: mousePos.x, y: mousePos.y }; // Move the last point
          this.path[this.path.length - 1].arc = true;
        }
      }

      onConstructMouseMove(mouse, ctrl, shift) {
        if (!this.notDone) { return; }
        const mousePos = mouse.getPosSnappedToGrid();
        if (typeof this.path[this.path.length - 1].arc != 'undefined') {
          if (this.path[this.path.length - 1].arc && Math.sqrt(Math.pow(this.path[this.path.length - 1].x - mousePos.x, 2) + Math.pow(this.path[this.path.length - 1].y - mousePos.y, 2)) >= 5 * this.scene.lengthScale) {
            this.path[this.path.length] = mousePos;
          }
        } else {
          this.path[this.path.length - 1] = { x: mousePos.x, y: mousePos.y }; // Move the last point
        }
      }

      onConstructMouseUp(mouse, ctrl, shift) {
        if (!this.notDone) {
          return {
            isDone: true
          };
        }
        if (this.path.length > 3 && mouse.isOnPoint(this.path[0])) {
          // Mouse released at the first point
          this.path.length--;
          this.notDone = false;
          return {
            isDone: true
          };
        }
        if (this.path[this.path.length - 2] && !this.path[this.path.length - 2].arc && mouse.isOnPoint(this.path[this.path.length - 2])) {
          delete this.path[this.path.length - 1].arc;
        } else {
          const mousePos = mouse.getPosSnappedToGrid();
          this.path[this.path.length - 1] = { x: mousePos.x, y: mousePos.y }; // Move the last point
          this.path[this.path.length - 1].arc = false;
          this.path[this.path.length] = { x: mousePos.x, y: mousePos.y }; // Create a new point
        }
      }

      onConstructUndo() {
        if (this.path.length <= 2) {
          return {
            isCancelled: true
          };
        } else {
          this.path.pop();
          if (this.path[this.path.length - 2].arc) {
            this.path.pop();
          }
          delete this.path[this.path.length - 1].arc
        }
      }

      checkMouseOver(mouse) {
        let dragContext = {};

        var p1;
        var p2;
        var p3;
        var center;
        var r;
        var a1;
        var a2;
        var a3;

        var click_lensq = Infinity;
        var click_lensq_temp;
        var targetPoint_index = -1;
        for (var i = 0; i < this.path.length; i++) {
          if (mouse.isOnPoint(this.path[i])) {
            click_lensq_temp = geometry.distanceSquared(mouse.pos, this.path[i]);
            if (click_lensq_temp <= click_lensq) {
              click_lensq = click_lensq_temp;
              targetPoint_index = i;
            }
          }
        }
        if (targetPoint_index != -1) {
          dragContext.part = 1;
          dragContext.index = targetPoint_index;
          dragContext.targetPoint = geometry.point(this.path[targetPoint_index].x, this.path[targetPoint_index].y);
          return dragContext;
        }

        for (var i = 0; i < this.path.length; i++) {
          if (this.path[(i + 1) % this.path.length].arc && !this.path[i % this.path.length].arc) {
            p1 = geometry.point(this.path[i % this.path.length].x, this.path[i % this.path.length].y);
            p2 = geometry.point(this.path[(i + 2) % this.path.length].x, this.path[(i + 2) % this.path.length].y);
            p3 = geometry.point(this.path[(i + 1) % this.path.length].x, this.path[(i + 1) % this.path.length].y);
            center = geometry.linesIntersection(geometry.perpendicularBisector(geometry.line(p1, p3)), geometry.perpendicularBisector(geometry.line(p2, p3)));
            if (isFinite(center.x) && isFinite(center.y)) {
              r = geometry.distance(center, p3);
              a1 = Math.atan2(p1.y - center.y, p1.x - center.x);
              a2 = Math.atan2(p2.y - center.y, p2.x - center.x);
              a3 = Math.atan2(p3.y - center.y, p3.x - center.x);
              var a_m = Math.atan2(mouse.pos.y - center.y, mouse.pos.x - center.x);
              if (Math.abs(geometry.distance(center, mouse.pos) - r) < mouse.getClickExtent() && (((a2 < a3 && a3 < a1) || (a1 < a2 && a2 < a3) || (a3 < a1 && a1 < a2)) == ((a2 < a_m && a_m < a1) || (a1 < a2 && a2 < a_m) || (a_m < a1 && a1 < a2)))) {
                // Dragging the entire this
                const mousePos = mouse.getPosSnappedToGrid();
                dragContext.part = 0;
                dragContext.mousePos0 = mousePos; // Mouse position when the user starts dragging
                dragContext.mousePos1 = mousePos; // Mouse position at the last moment during dragging
                dragContext.snapContext = {};
                return dragContext;
              }
            } else {
              // The three points on the arc is colinear. Treat as a line segment.
              if (mouse.isOnSegment(geometry.line(this.path[(i) % this.path.length], this.path[(i + 2) % this.path.length]))) {
                // Dragging the entire this
                const mousePos = mouse.getPosSnappedToGrid();
                dragContext.part = 0;
                dragContext.mousePos0 = mousePos; // Mouse position when the user starts dragging
                dragContext.mousePos1 = mousePos; // Mouse position at the last moment during dragging
                dragContext.snapContext = {};
                return dragContext;
              }
            }
          } else if (!this.path[(i + 1) % this.path.length].arc && !this.path[i % this.path.length].arc) {
            if (mouse.isOnSegment(geometry.line(this.path[(i) % this.path.length], this.path[(i + 1) % this.path.length]))) {
              // Dragging the entire this
              const mousePos = mouse.getPosSnappedToGrid();
              dragContext.part = 0;
              dragContext.mousePos0 = mousePos; // Mouse position when the user starts dragging
              dragContext.mousePos1 = mousePos; // Mouse position at the last moment during dragging
              dragContext.snapContext = {};
              return dragContext;
            }
          }
        }
      }

      onDrag(mouse, dragContext, ctrl, shift) {
        const mousePos = mouse.getPosSnappedToGrid();

        if (dragContext.part == 1) {
          this.path[dragContext.index].x = mousePos.x;
          this.path[dragContext.index].y = mousePos.y;
        }

        if (dragContext.part == 0) {
          if (shift) {
            var mousePosSnapped = mouse.getPosSnappedToDirection(dragContext.mousePos0, [{ x: 1, y: 0 }, { x: 0, y: 1 }], dragContext.snapContext);
          } else {
            var mousePosSnapped = mouse.getPosSnappedToGrid();
            dragContext.snapContext = {}; // Unlock the dragging direction when the user release the shift key
          }
          this.move(mousePosSnapped.x - dragContext.mousePos1.x, mousePosSnapped.y - dragContext.mousePos1.y);
          dragContext.mousePos1 = mousePosSnapped;
        }
      }

      checkRayIntersects(ray) {
        if (this.notDone || this.refIndex <= 0) return;

        var s_lensq = Infinity;
        var s_lensq_temp;
        var s_point = null;
        var s_point_temp = null;
        var rp_exist = [];
        var rp_lensq = [];
        var rp_temp;

        var p1;
        var p2;
        var p3;
        var center;
        var r;

        for (var i = 0; i < this.path.length; i++) {
          s_point_temp = null;
          if (this.path[(i + 1) % this.path.length].arc && !this.path[i % this.path.length].arc) {
            // The arc i->i+1->i+2
            p1 = geometry.point(this.path[i % this.path.length].x, this.path[i % this.path.length].y);
            p2 = geometry.point(this.path[(i + 2) % this.path.length].x, this.path[(i + 2) % this.path.length].y);
            p3 = geometry.point(this.path[(i + 1) % this.path.length].x, this.path[(i + 1) % this.path.length].y);
            center = geometry.linesIntersection(geometry.perpendicularBisector(geometry.line(p1, p3)), geometry.perpendicularBisector(geometry.line(p2, p3)));
            if (isFinite(center.x) && isFinite(center.y)) {
              r = geometry.distance(center, p3);
              rp_temp = geometry.lineCircleIntersections(geometry.line(ray.p1, ray.p2), geometry.circle(center, p2));
              for (var ii = 1; ii <= 2; ii++) {
                rp_exist[ii] = !geometry.intersectionIsOnSegment(geometry.linesIntersection(geometry.line(p1, p2), geometry.line(p3, rp_temp[ii])), geometry.line(p3, rp_temp[ii])) && geometry.intersectionIsOnRay(rp_temp[ii], ray) && geometry.distanceSquared(rp_temp[ii], ray.p1) > Simulator.MIN_RAY_SEGMENT_LENGTH_SQUARED * this.scene.lengthScale * this.scene.lengthScale;
                rp_lensq[ii] = geometry.distanceSquared(ray.p1, rp_temp[ii]);
              }
              if (rp_exist[1] && ((!rp_exist[2]) || rp_lensq[1] < rp_lensq[2]) && rp_lensq[1] > Simulator.MIN_RAY_SEGMENT_LENGTH_SQUARED * this.scene.lengthScale * this.scene.lengthScale) {
                s_point_temp = rp_temp[1];
                s_lensq_temp = rp_lensq[1];
              }
              if (rp_exist[2] && ((!rp_exist[1]) || rp_lensq[2] < rp_lensq[1]) && rp_lensq[2] > Simulator.MIN_RAY_SEGMENT_LENGTH_SQUARED * this.scene.lengthScale * this.scene.lengthScale) {
                s_point_temp = rp_temp[2];
                s_lensq_temp = rp_lensq[2];
              }
            } else {
              // The three points on the arc is colinear. Treat as a line segment.
              var rp_temp = geometry.linesIntersection(geometry.line(ray.p1, ray.p2), geometry.line(this.path[i % this.path.length], this.path[(i + 2) % this.path.length]));

              if (geometry.intersectionIsOnSegment(rp_temp, geometry.line(this.path[i % this.path.length], this.path[(i + 2) % this.path.length])) && geometry.intersectionIsOnRay(rp_temp, ray) && geometry.distanceSquared(ray.p1, rp_temp) > Simulator.MIN_RAY_SEGMENT_LENGTH_SQUARED * this.scene.lengthScale * this.scene.lengthScale) {
                s_lensq_temp = geometry.distanceSquared(ray.p1, rp_temp);
                s_point_temp = rp_temp;
              }
            }
          } else if (!this.path[(i + 1) % this.path.length].arc && !this.path[i % this.path.length].arc) {
            //Line segment i->i+1
            var rp_temp = geometry.linesIntersection(geometry.line(ray.p1, ray.p2), geometry.line(this.path[i % this.path.length], this.path[(i + 1) % this.path.length]));

            if (geometry.intersectionIsOnSegment(rp_temp, geometry.line(this.path[i % this.path.length], this.path[(i + 1) % this.path.length])) && geometry.intersectionIsOnRay(rp_temp, ray) && geometry.distanceSquared(ray.p1, rp_temp) > Simulator.MIN_RAY_SEGMENT_LENGTH_SQUARED * this.scene.lengthScale * this.scene.lengthScale) {
              s_lensq_temp = geometry.distanceSquared(ray.p1, rp_temp);
              s_point_temp = rp_temp;
            }
          }
          if (s_point_temp) {
            if (s_lensq_temp < s_lensq) {
              s_lensq = s_lensq_temp;
              s_point = s_point_temp;
            }
          }
        }
        if (s_point) {
          return s_point;
        }
      }

      onRayIncident(ray, rayIndex, incidentPoint, surfaceMergingObjs) {
        if (this.notDone) { return; }

        var incidentData = this.getIncidentData(ray);
        var incidentType = incidentData.incidentType;
        if (incidentType == 1) {
          // From inside to outside
          var n1 = this.getRefIndexAt(incidentPoint, ray);
        } else if (incidentType == -1) {
          // From outside to inside
          var n1 = 1 / this.getRefIndexAt(incidentPoint, ray);
        } else if (incidentType == 0) {
          // Equivalent to not intersecting with the object (e.g. two interfaces overlap)
          var n1 = 1;
        } else {
          // The situation that may cause bugs (e.g. incident on an edge point)
          // To prevent shooting the ray to a wrong direction, absorb the ray
          return {
            isAbsorbed: true
          };
        }

        return this.refract(ray, rayIndex, incidentPoint, incidentData.normal, n1, surfaceMergingObjs, ray.bodyMergingthis);
      }

      getIncidentType(ray) {
        return this.getIncidentData(ray).incidentType;
      }

      /* Utility function */

      getIncidentData(ray) {
        var s_lensq = Infinity;
        var s_lensq_temp;
        var s_point = null;
        var s_point_temp = null;
        var s_point_index;

        var surfaceMultiplicity = 1; // How many time the surfaces coincide

        var rp_on_ray = [];
        var rp_exist = [];
        var rp_lensq = [];
        var rp_temp;

        var rp2_exist = [];
        var rp2_lensq = [];
        var rp2_temp;

        var normal_x;
        var normal_x_temp;

        var normal_y;
        var normal_y_temp;

        var rdots;
        var ssq;

        var nearEdge = false;
        var nearEdge_temp = false;

        var p1;
        var p2;
        var p3;
        var center;
        var ray2 = geometry.line(ray.p1, geometry.point(ray.p2.x + this.scene.rng() * 1e-5, ray.p2.y + this.scene.rng() * 1e-5)); // The ray to test the inside/outside (the test ray)
        var ray_intersect_count = 0; // The intersection count (odd means from outside)

        for (var i = 0; i < this.path.length; i++) {
          s_point_temp = null;
          nearEdge_temp = false;
          if (this.path[(i + 1) % this.path.length].arc && !this.path[i % this.path.length].arc) {
            // The arc i->i+1->i+2
            p1 = geometry.point(this.path[i % this.path.length].x, this.path[i % this.path.length].y);
            p2 = geometry.point(this.path[(i + 2) % this.path.length].x, this.path[(i + 2) % this.path.length].y);
            p3 = geometry.point(this.path[(i + 1) % this.path.length].x, this.path[(i + 1) % this.path.length].y);
            center = geometry.linesIntersection(geometry.perpendicularBisector(geometry.line(p1, p3)), geometry.perpendicularBisector(geometry.line(p2, p3)));
            if (isFinite(center.x) && isFinite(center.y)) {
              rp_temp = geometry.lineCircleIntersections(geometry.line(ray.p1, ray.p2), geometry.circle(center, p2));
              rp2_temp = geometry.lineCircleIntersections(geometry.line(ray2.p1, ray2.p2), geometry.circle(center, p2));
              for (var ii = 1; ii <= 2; ii++) {
                rp_on_ray[ii] = geometry.intersectionIsOnRay(rp_temp[ii], ray);
                rp_exist[ii] = rp_on_ray[ii] && !geometry.intersectionIsOnSegment(geometry.linesIntersection(geometry.line(p1, p2), geometry.line(p3, rp_temp[ii])), geometry.line(p3, rp_temp[ii])) && geometry.distanceSquared(rp_temp[ii], ray.p1) > Simulator.MIN_RAY_SEGMENT_LENGTH_SQUARED * this.scene.lengthScale * this.scene.lengthScale;
                rp_lensq[ii] = geometry.distanceSquared(ray.p1, rp_temp[ii]);

                rp2_exist[ii] = !geometry.intersectionIsOnSegment(geometry.linesIntersection(geometry.line(p1, p2), geometry.line(p3, rp2_temp[ii])), geometry.line(p3, rp2_temp[ii])) && geometry.intersectionIsOnRay(rp2_temp[ii], ray2) && geometry.distanceSquared(rp2_temp[ii], ray2.p1) > Simulator.MIN_RAY_SEGMENT_LENGTH_SQUARED * this.scene.lengthScale * this.scene.lengthScale;
                rp2_lensq[ii] = geometry.distanceSquared(ray2.p1, rp2_temp[ii]);
              }

              if (rp_exist[1] && ((!rp_exist[2]) || rp_lensq[1] < rp_lensq[2]) && rp_lensq[1] > Simulator.MIN_RAY_SEGMENT_LENGTH_SQUARED * this.scene.lengthScale * this.scene.lengthScale) {
                s_point_temp = rp_temp[1];
                s_lensq_temp = rp_lensq[1];
                if (rp_on_ray[2] && rp_lensq[1] < rp_lensq[2]) {
                  //The ray is from outside to inside (with respect to the arc itself)
                  normal_x_temp = s_point_temp.x - center.x;
                  normal_y_temp = s_point_temp.y - center.y;
                } else {
                  normal_x_temp = center.x - s_point_temp.x;
                  normal_y_temp = center.y - s_point_temp.y;
                }
              }
              if (rp_exist[2] && ((!rp_exist[1]) || rp_lensq[2] < rp_lensq[1]) && rp_lensq[2] > Simulator.MIN_RAY_SEGMENT_LENGTH_SQUARED * this.scene.lengthScale * this.scene.lengthScale) {
                s_point_temp = rp_temp[2];
                s_lensq_temp = rp_lensq[2];
                if (rp_on_ray[1] && rp_lensq[2] < rp_lensq[1]) {
                  //The ray is from outside to inside (with respect to the arc itself)
                  normal_x_temp = s_point_temp.x - center.x;
                  normal_y_temp = s_point_temp.y - center.y;
                } else {
                  normal_x_temp = center.x - s_point_temp.x;
                  normal_y_temp = center.y - s_point_temp.y;
                }
              }
              if (rp2_exist[1] && rp2_lensq[1] > Simulator.MIN_RAY_SEGMENT_LENGTH_SQUARED * this.scene.lengthScale * this.scene.lengthScale) {
                ray_intersect_count++;
              }
              if (rp2_exist[2] && rp2_lensq[2] > Simulator.MIN_RAY_SEGMENT_LENGTH_SQUARED * this.scene.lengthScale * this.scene.lengthScale) {
                ray_intersect_count++;
              }

              // Test if too close to an edge
              if (s_point_temp && (geometry.distanceSquared(s_point_temp, p1) < Simulator.MIN_RAY_SEGMENT_LENGTH_SQUARED * this.scene.lengthScale * this.scene.lengthScale || geometry.distanceSquared(s_point_temp, p2) < Simulator.MIN_RAY_SEGMENT_LENGTH_SQUARED * this.scene.lengthScale * this.scene.lengthScale)) {
                nearEdge_temp = true;
              }

            } else {
              // The three points on the arc is colinear. Treat as a line segment.
              rp_temp = geometry.linesIntersection(geometry.line(ray.p1, ray.p2), geometry.line(this.path[i % this.path.length], this.path[(i + 2) % this.path.length]));

              rp2_temp = geometry.linesIntersection(geometry.line(ray2.p1, ray2.p2), geometry.line(this.path[i % this.path.length], this.path[(i + 2) % this.path.length]));
              if (geometry.intersectionIsOnSegment(rp_temp, geometry.line(this.path[i % this.path.length], this.path[(i + 2) % this.path.length])) && geometry.intersectionIsOnRay(rp_temp, ray) && geometry.distanceSquared(ray.p1, rp_temp) > Simulator.MIN_RAY_SEGMENT_LENGTH_SQUARED * this.scene.lengthScale * this.scene.lengthScale) {
                s_lensq_temp = geometry.distanceSquared(ray.p1, rp_temp);
                s_point_temp = rp_temp;

                rdots = (ray.p2.x - ray.p1.x) * (this.path[(i + 2) % this.path.length].x - this.path[i % this.path.length].x) + (ray.p2.y - ray.p1.y) * (this.path[(i + 2) % this.path.length].y - this.path[i % this.path.length].y);
                ssq = (this.path[(i + 2) % this.path.length].x - this.path[i % this.path.length].x) * (this.path[(i + 2) % this.path.length].x - this.path[i % this.path.length].x) + (this.path[(i + 2) % this.path.length].y - this.path[i % this.path.length].y) * (this.path[(i + 2) % this.path.length].y - this.path[i % this.path.length].y);

                normal_x_temp = rdots * (this.path[(i + 2) % this.path.length].x - this.path[i % this.path.length].x) - ssq * (ray.p2.x - ray.p1.x);
                normal_y_temp = rdots * (this.path[(i + 2) % this.path.length].y - this.path[i % this.path.length].y) - ssq * (ray.p2.y - ray.p1.y);

              }

              if (geometry.intersectionIsOnSegment(rp2_temp, geometry.line(this.path[i % this.path.length], this.path[(i + 2) % this.path.length])) && geometry.intersectionIsOnRay(rp2_temp, ray2) && geometry.distanceSquared(ray2.p1, rp2_temp) > Simulator.MIN_RAY_SEGMENT_LENGTH_SQUARED * this.scene.lengthScale * this.scene.lengthScale) {
                ray_intersect_count++;
              }

              // Test if too close to an edge
              if (s_point_temp && (geometry.distanceSquared(s_point_temp, this.path[i % this.path.length]) < Simulator.MIN_RAY_SEGMENT_LENGTH_SQUARED * this.scene.lengthScale * this.scene.lengthScale || geometry.distanceSquared(s_point_temp, this.path[(i + 2) % this.path.length]) < Simulator.MIN_RAY_SEGMENT_LENGTH_SQUARED * this.scene.lengthScale * this.scene.lengthScale)) {
                nearEdge_temp = true;
              }
            }
          } else if (!this.path[(i + 1) % this.path.length].arc && !this.path[i % this.path.length].arc) {
            //Line segment i->i+1
            rp_temp = geometry.linesIntersection(geometry.line(ray.p1, ray.p2), geometry.line(this.path[i % this.path.length], this.path[(i + 1) % this.path.length]));

            rp2_temp = geometry.linesIntersection(geometry.line(ray2.p1, ray2.p2), geometry.line(this.path[i % this.path.length], this.path[(i + 1) % this.path.length]));
            if (geometry.intersectionIsOnSegment(rp_temp, geometry.line(this.path[i % this.path.length], this.path[(i + 1) % this.path.length])) && geometry.intersectionIsOnRay(rp_temp, ray) && geometry.distanceSquared(ray.p1, rp_temp) > Simulator.MIN_RAY_SEGMENT_LENGTH_SQUARED * this.scene.lengthScale * this.scene.lengthScale) {
              s_lensq_temp = geometry.distanceSquared(ray.p1, rp_temp);
              s_point_temp = rp_temp;

              rdots = (ray.p2.x - ray.p1.x) * (this.path[(i + 1) % this.path.length].x - this.path[i % this.path.length].x) + (ray.p2.y - ray.p1.y) * (this.path[(i + 1) % this.path.length].y - this.path[i % this.path.length].y);
              ssq = (this.path[(i + 1) % this.path.length].x - this.path[i % this.path.length].x) * (this.path[(i + 1) % this.path.length].x - this.path[i % this.path.length].x) + (this.path[(i + 1) % this.path.length].y - this.path[i % this.path.length].y) * (this.path[(i + 1) % this.path.length].y - this.path[i % this.path.length].y);

              normal_x_temp = rdots * (this.path[(i + 1) % this.path.length].x - this.path[i % this.path.length].x) - ssq * (ray.p2.x - ray.p1.x);
              normal_y_temp = rdots * (this.path[(i + 1) % this.path.length].y - this.path[i % this.path.length].y) - ssq * (ray.p2.y - ray.p1.y);

            }

            if (geometry.intersectionIsOnSegment(rp2_temp, geometry.line(this.path[i % this.path.length], this.path[(i + 1) % this.path.length])) && geometry.intersectionIsOnRay(rp2_temp, ray2) && geometry.distanceSquared(ray2.p1, rp2_temp) > Simulator.MIN_RAY_SEGMENT_LENGTH_SQUARED * this.scene.lengthScale * this.scene.lengthScale) {
              ray_intersect_count++;
            }

            // Test if too close to an edge
            if (s_point_temp && (geometry.distanceSquared(s_point_temp, this.path[i % this.path.length]) < Simulator.MIN_RAY_SEGMENT_LENGTH_SQUARED * this.scene.lengthScale * this.scene.lengthScale || geometry.distanceSquared(s_point_temp, this.path[(i + 1) % this.path.length]) < Simulator.MIN_RAY_SEGMENT_LENGTH_SQUARED * this.scene.lengthScale * this.scene.lengthScale)) {
              nearEdge_temp = true;
            }
          }
          if (s_point_temp) {
            if (s_point && geometry.distanceSquared(s_point_temp, s_point) < Simulator.MIN_RAY_SEGMENT_LENGTH_SQUARED * this.scene.lengthScale * this.scene.lengthScale) {
              // Self surface merging
              surfaceMultiplicity++;
            } else if (s_lensq_temp < s_lensq) {
              s_lensq = s_lensq_temp;
              s_point = s_point_temp;
              s_point_index = i;
              normal_x = normal_x_temp;
              normal_y = normal_y_temp;
              nearEdge = nearEdge_temp;
              surfaceMultiplicity = 1;
            }
          }
        }

        if (nearEdge) {
          var incidentType = NaN; // Incident on an edge point
        } else if (surfaceMultiplicity % 2 == 0) {
          var incidentType = 0; // Equivalent to not intersecting with the object
        } else if (ray_intersect_count % 2 == 1) {
          var incidentType = 1; // From inside to outside
        } else {
          var incidentType = -1; // From outside to inside
        }

        return { s_point: s_point, normal: { x: normal_x, y: normal_y }, incidentType: incidentType };
      }
    };

    export default Glass;