classes/ray.js

  1. "use strict";
  2. import Flatten from '../flatten';
  3. import * as Intersection from "../algorithms/intersection";
  4. import {Shape} from "./shape";
  5. import {Errors} from "../utils/errors";
  6. import {vector} from './vector'
  7. /**
  8. * Class representing a ray (a half-infinite line).
  9. * @type {Ray}
  10. */
  11. export class Ray extends Shape {
  12. /**
  13. * Ray may be constructed by setting an <b>origin</b> point and a <b>normal</b> vector, so that any point <b>x</b>
  14. * on a ray fit an equation: <br />
  15. * (<b>x</b> - <b>origin</b>) * <b>vector</b> = 0 <br />
  16. * Ray defined by constructor is a right semi-infinite line with respect to the normal vector <br/>
  17. * If normal vector is omitted ray is considered horizontal (normal vector is (0,1)). <br/>
  18. * Don't be confused: direction of the normal vector is orthogonal to the ray <br/>
  19. * @param {Point} pt - start point
  20. * @param {Vector} norm - normal vector
  21. */
  22. constructor(...args) {
  23. super()
  24. this.pt = new Flatten.Point();
  25. this.norm = new Flatten.Vector(0,1);
  26. if (args.length === 0) {
  27. return;
  28. }
  29. if (args.length >= 1 && args[0] instanceof Flatten.Point) {
  30. this.pt = args[0].clone();
  31. }
  32. if (args.length === 1) {
  33. return;
  34. }
  35. if (args.length === 2 && args[1] instanceof Flatten.Vector) {
  36. this.norm = args[1].clone();
  37. return;
  38. }
  39. throw Errors.ILLEGAL_PARAMETERS;
  40. }
  41. /**
  42. * Return new cloned instance of ray
  43. * @returns {Ray}
  44. */
  45. clone() {
  46. return new Ray(this.pt, this.norm);
  47. }
  48. /**
  49. * Slope of the ray - angle in radians between ray and axe x from 0 to 2PI
  50. * @returns {number} - slope of the line
  51. */
  52. get slope() {
  53. let vec = new Flatten.Vector(this.norm.y, -this.norm.x);
  54. return vec.slope;
  55. }
  56. /**
  57. * Returns half-infinite bounding box of the ray
  58. * @returns {Box} - bounding box
  59. */
  60. get box() {
  61. let slope = this.slope;
  62. return new Flatten.Box(
  63. slope > Math.PI/2 && slope < 3*Math.PI/2 ? Number.NEGATIVE_INFINITY : this.pt.x,
  64. slope >= 0 && slope <= Math.PI ? this.pt.y : Number.NEGATIVE_INFINITY,
  65. slope >= Math.PI/2 && slope <= 3*Math.PI/2 ? this.pt.x : Number.POSITIVE_INFINITY,
  66. slope >= Math.PI && slope <= 2*Math.PI || slope === 0 ? this.pt.y : Number.POSITIVE_INFINITY
  67. )
  68. }
  69. /**
  70. * Return ray start point
  71. * @returns {Point} - ray start point
  72. */
  73. get start() {
  74. return this.pt;
  75. }
  76. /**
  77. * Ray has no end point?
  78. * @returns {undefined}
  79. */
  80. get end() {return undefined;}
  81. /**
  82. * Return positive infinity number as length
  83. * @returns {number}
  84. */
  85. get length() {return Number.POSITIVE_INFINITY;}
  86. /**
  87. * Returns true if point belongs to ray
  88. * @param {Point} pt Query point
  89. * @returns {boolean}
  90. */
  91. contains(pt) {
  92. if (this.pt.equalTo(pt)) {
  93. return true;
  94. }
  95. /* Ray contains point if vector to point is orthogonal to the ray normal vector
  96. and cross product from vector to point is positive */
  97. let vec = new Flatten.Vector(this.pt, pt);
  98. return Flatten.Utils.EQ_0(this.norm.dot(vec)) && Flatten.Utils.GE(vec.cross(this.norm),0);
  99. }
  100. /**
  101. * Return coordinate of the point that lies on the ray in the transformed
  102. * coordinate system where center is the projection of the point(0,0) to
  103. * the line containing this ray and axe y is collinear to the normal vector. <br/>
  104. * This method assumes that point lies on the ray
  105. * @param {Point} pt - point on a ray
  106. * @returns {number}
  107. */
  108. coord(pt) {
  109. return vector(pt.x, pt.y).cross(this.norm);
  110. }
  111. /**
  112. * Split ray with point and return array of segment and new ray
  113. * @param {Point} pt
  114. * @returns [Segment,Ray]
  115. */
  116. split(pt) {
  117. if (!this.contains(pt))
  118. return [];
  119. if (this.pt.equalTo(pt)) {
  120. return [this]
  121. }
  122. return [
  123. new Flatten.Segment(this.pt, pt),
  124. new Flatten.Ray(pt, this.norm)
  125. ]
  126. }
  127. /**
  128. * Returns array of intersection points between ray and another shape
  129. * @param {Shape} shape - Shape to intersect with ray
  130. * @returns {Point[]} array of intersection points
  131. */
  132. intersect(shape) {
  133. if (shape instanceof Flatten.Point) {
  134. return this.contains(shape) ? [shape] : [];
  135. }
  136. if (shape instanceof Flatten.Segment) {
  137. return Intersection.intersectRay2Segment(this, shape);
  138. }
  139. if (shape instanceof Flatten.Arc) {
  140. return Intersection.intersectRay2Arc(this, shape);
  141. }
  142. if (shape instanceof Flatten.Line) {
  143. return Intersection.intersectRay2Line(this, shape);
  144. }
  145. if (shape instanceof Flatten.Ray) {
  146. return Intersection.intersectRay2Ray(this, shape)
  147. }
  148. if (shape instanceof Flatten.Circle) {
  149. return Intersection.intersectRay2Circle(this, shape);
  150. }
  151. if (shape instanceof Flatten.Box) {
  152. return Intersection.intersectRay2Box(this, shape);
  153. }
  154. if (shape instanceof Flatten.Polygon) {
  155. return Intersection.intersectRay2Polygon(this, shape);
  156. }
  157. }
  158. /**
  159. * Return new line rotated by angle
  160. * @param {number} angle - angle in radians
  161. * @param {Point} center - center of rotation
  162. */
  163. rotate(angle, center = new Flatten.Point()) {
  164. return new Flatten.Ray(
  165. this.pt.rotate(angle, center),
  166. this.norm.rotate(angle)
  167. )
  168. }
  169. /**
  170. * Return new ray transformed by affine transformation matrix
  171. * @param {Matrix} m - affine transformation matrix (a,b,c,d,tx,ty)
  172. * @returns {Ray}
  173. */
  174. transform(m) {
  175. return new Flatten.Ray(
  176. this.pt.transform(m),
  177. this.norm.clone()
  178. )
  179. }
  180. get name() {
  181. return "ray"
  182. }
  183. /**
  184. * Return string to draw svg segment representing ray inside given box
  185. * @param {Box} box Box representing drawing area
  186. * @param {Object} attrs - an object with attributes of svg segment element
  187. */
  188. svg(box, attrs = {}) {
  189. let line = new Flatten.Line(this.pt, this.norm);
  190. let ip = Intersection.intersectLine2Box(line, box);
  191. ip = ip.filter( pt => this.contains(pt) );
  192. if (ip.length === 0 || ip.length === 2)
  193. return "";
  194. let segment = new Flatten.Segment(this.pt, ip[0]);
  195. return segment.svg(attrs);
  196. }
  197. }
  198. Flatten.Ray = Ray;
  199. export const ray = (...args) => new Flatten.Ray(...args);
  200. Flatten.ray = ray;