geometry.py 2.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071
  1. import typing
  2. import numpy
  3. def _collinear(vector_a: numpy.ndarray, vector_b: numpy.ndarray) -> bool:
  4. # null vector: https://math.stackexchange.com/a/1772580
  5. return numpy.allclose(numpy.cross(vector_a, vector_b), 0)
  6. class _Line:
  7. # pylint: disable=too-few-public-methods
  8. def __init__(self, point, vector):
  9. self.point = numpy.array(point, dtype=float)
  10. self.vector = numpy.array(vector, dtype=float)
  11. def __eq__(self, other: '_Line') -> bool:
  12. if not _collinear(self.vector, other.vector):
  13. return False
  14. return _collinear(self.vector, self.point - other.point)
  15. def __repr__(self) -> str:
  16. return 'line(t) = {} + {} t'.format(self.point, self.vector)
  17. def intersect_line(self, other: '_Line') \
  18. -> typing.Union[numpy.ndarray, bool]:
  19. # https://en.wikipedia.org/wiki/Skew_lines#Distance
  20. lines_normal_vector = numpy.cross(self.vector, other.vector)
  21. if numpy.allclose(lines_normal_vector, 0):
  22. return _collinear(self.vector, self.point - other.point)
  23. plane_normal_vector = numpy.cross(other.vector, lines_normal_vector)
  24. return self.point + self.vector \
  25. * (numpy.inner(other.point - self.point, plane_normal_vector)
  26. / numpy.inner(self.vector, plane_normal_vector))
  27. def _intersect_planes(normal_vector_a: numpy.ndarray,
  28. constant_a: float,
  29. normal_vector_b: numpy.ndarray,
  30. constant_b: float) -> typing.Union[_Line, bool]:
  31. line_vector = numpy.cross(normal_vector_a, normal_vector_b)
  32. if numpy.allclose(line_vector, 0):
  33. return constant_a == constant_b
  34. point = numpy.linalg.solve(
  35. numpy.vstack((normal_vector_a, normal_vector_b, line_vector)),
  36. numpy.vstack((constant_a, constant_b, 0)),
  37. )
  38. return _Line(point=point.reshape(3), vector=line_vector)
  39. def _between(lower_limit: numpy.ndarray,
  40. point: numpy.ndarray,
  41. upper_limit: numpy.ndarray) -> bool:
  42. return (lower_limit <= point).all() and (point <= upper_limit).all()
  43. def _intersect_line_segments(points_a: numpy.ndarray,
  44. points_b: numpy.ndarray) \
  45. -> typing.Union[numpy.ndarray, bool]:
  46. lines = [_Line(points[0], points[1] - points[0])
  47. for points in [points_a, points_b]]
  48. point = lines[0].intersect_line(lines[1])
  49. if isinstance(point, bool):
  50. return point
  51. for points in [points_a, points_b]:
  52. if not _between(points[0], point, points[1]) \
  53. and not _between(points[1], point, points[0]):
  54. return False
  55. return point