Source: core/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;