line_segment_circle_intersection.cpp 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. /* You can drag the circle and the line segment, and change the radius, the
  2. * segment size and direction by holding the control key.
  3. * The circle is highlighted green if the line segment intersects it.
  4. * The projection point of the circle center on the line is drawn as a small
  5. * grey circle. The intersection points - even smaller red(first/entry),
  6. * blue(second/exit) and purple(touch/tangent/never happens) circles.
  7. * Holding shift will scale the mouse motion down to allow a bit more precise
  8. * positioning.
  9. */
  10. #include "common/sketchbook.hpp"
  11. constexpr float circle_radius = .12;
  12. constexpr float corner_radius = 14.f;
  13. constexpr float precision_mode_motion_scale = .01f;
  14. constexpr float2 half = float2::one(.5f);
  15. struct line
  16. {
  17. float2 start;
  18. float2 direction;
  19. explicit operator range2f() const;
  20. };
  21. struct circle
  22. {
  23. float2 center;
  24. float radius;
  25. float magnitude() const;
  26. explicit operator range2f() const;
  27. bool contains(float2 point) const;
  28. };
  29. line l;
  30. circle c;
  31. bool resize_mode = false;
  32. bool precision_mode = false;
  33. float2* dragged_point = nullptr;
  34. float* dragged_radius = nullptr;
  35. circle* dragged_circle = nullptr;
  36. bool is_near(float2 corner, float2 position);
  37. sketch arrow(sketch s, float2 start, float2 end);
  38. float2 project(float2 a, float2 b);
  39. void start(Program& program)
  40. {
  41. program.key_down = [](scancode code, keycode)
  42. {
  43. switch(code)
  44. {
  45. case scancode::lctrl:
  46. case scancode::rctrl:
  47. resize_mode = true;
  48. break;
  49. case scancode::lshift:
  50. case scancode::rshift:
  51. precision_mode = true;
  52. break;
  53. default: break;
  54. }
  55. };
  56. program.key_up = [&program](scancode code, keycode)
  57. {
  58. switch(code)
  59. {
  60. case scancode::leftbracket:
  61. case scancode::c:
  62. if(pressed(scancode::rctrl) || pressed(scancode::lctrl))
  63. case scancode::escape:
  64. program.end();
  65. break;
  66. case scancode::lctrl:
  67. case scancode::rctrl:
  68. resize_mode = false;
  69. break;
  70. case scancode::lshift:
  71. case scancode::rshift:
  72. precision_mode = false;
  73. break;
  74. default: break;
  75. }
  76. };
  77. program.mouse_down = [](float2 position, auto)
  78. {
  79. if(resize_mode)
  80. {
  81. if(is_near(l.start + l.direction, position))
  82. dragged_point = &l.direction;
  83. else if(is_near(c.center + float2::i(c.radius), position))
  84. dragged_radius = &c.radius;
  85. }
  86. else
  87. {
  88. if(is_near(l.start, position))
  89. dragged_point = &l.start;
  90. else if(c.contains(position))
  91. dragged_circle = &c;
  92. }
  93. };
  94. program.mouse_up = [](auto, auto)
  95. {
  96. dragged_point = nullptr;
  97. dragged_circle = nullptr;
  98. dragged_radius = nullptr;
  99. };
  100. program.mouse_move = [](auto, float2 motion)
  101. {
  102. if(precision_mode)
  103. motion *= precision_mode_motion_scale;
  104. if(dragged_circle)
  105. dragged_circle->center += motion;
  106. if(dragged_point)
  107. (*dragged_point) += motion;
  108. if(dragged_radius)
  109. (*dragged_radius) += motion.x();
  110. };
  111. program.draw_once = [](auto frame)
  112. {
  113. const float2 center = frame.size / 2;
  114. c.center = center;
  115. c.radius = frame.size.x() * circle_radius;
  116. l.start = center - 2 * c.radius;
  117. l.direction = float2::one(4 * c.radius);
  118. };
  119. program.draw_loop = [](auto frame, auto)
  120. {
  121. const float2 center = c.center - l.start;
  122. const float2 center_projection = project(center, l.direction);
  123. const float2 center_rejection = center - center_projection;
  124. const bool projection_in_segment = range{0.f, l.direction[0]}.fix().contains(center_projection[0]);
  125. const bool line_in_circle = (center_rejection).magnitude() < c.magnitude();
  126. const bool poke_check = c.contains(l.start) || c.contains(l.start + l.direction);
  127. frame.begin_sketch()
  128. .rectangle(rect{ frame.size })
  129. .fill(0xffffff_rgb)
  130. ;
  131. frame.begin_sketch()
  132. .ellipse(range2f(c))
  133. .fill(poke_check || (projection_in_segment && line_in_circle) ? 0x00aa00_rgb : 0xaaaaaa_rgb)
  134. ;
  135. arrow(frame.begin_sketch(), l.start, l.start + l.direction)
  136. .line_width(1).outline(0x770077_rgb)
  137. ;
  138. frame.begin_sketch()
  139. .ellipse(rect{float2::one(corner_radius), l.start, half})
  140. .line_width(1).outline(0x555555_rgb)
  141. ;
  142. frame.begin_sketch()
  143. .ellipse(rect{float2::one(corner_radius/2), l.start + center_projection, half})
  144. .line_width(3).outline(0x555555_rgb)
  145. .fill(0xcccccc_rgb);
  146. ;
  147. if(resize_mode)
  148. {
  149. const float2 radius_point = c.center + float2::i(c.radius);
  150. const float2 line_point = l.start + l.direction;
  151. frame.begin_sketch()
  152. .ellipse(rect{float2::one(corner_radius), radius_point, half})
  153. .ellipse(rect{float2::one(corner_radius), line_point, half})
  154. .line_width(1).outline(0x555555_rgb)
  155. ;
  156. }
  157. const float magnitude_of_solution = (c.magnitude() - center_rejection.magnitude()) / l.direction.magnitude();
  158. if(magnitude_of_solution > 0)
  159. {
  160. const float solution_length = sqrt(magnitude_of_solution);
  161. const float2 solutions[2] =
  162. {
  163. center_projection - solution_length * l.direction,
  164. center_projection + solution_length * l.direction
  165. };
  166. frame.begin_sketch()
  167. .ellipse(rect{float2::one(corner_radius/4), l.start + solutions[0], half})
  168. .line_width(3).outline(0x555555_rgb)
  169. .fill(0xff0000_rgb);
  170. ;
  171. frame.begin_sketch()
  172. .ellipse(rect{float2::one(corner_radius/4), l.start + solutions[1], half})
  173. .line_width(3).outline(0x555555_rgb)
  174. .fill(0x0000ff_rgb);
  175. ;
  176. }
  177. // teah, like this will ever happen...
  178. // can add some tolerance in the checks if want this to be more likely.
  179. else if(magnitude_of_solution == 0)
  180. {
  181. frame.begin_sketch()
  182. .ellipse(rect{float2::one(corner_radius), l.start + center_projection, half})
  183. .line_width(3).outline(0x555555_rgb)
  184. .fill(0xff00ff_rgb);
  185. ;
  186. }
  187. };
  188. }
  189. line::operator range2f() const
  190. {
  191. auto result = range2f{start, start + direction};
  192. result.fix();
  193. return result;
  194. }
  195. float circle::magnitude() const
  196. {
  197. return radius * radius;
  198. }
  199. circle::operator range2f() const
  200. {
  201. return {center - radius, center + radius};
  202. }
  203. bool circle::contains(float2 point) const
  204. {
  205. return (center - point).magnitude() < magnitude();
  206. }
  207. bool is_near(float2 corner, float2 position)
  208. {
  209. return circle{corner, corner_radius}.contains(position);
  210. }
  211. sketch arrow(sketch s, float2 start, float2 end)
  212. {
  213. float2 direction = end - start;
  214. float2 perpendicular = direction.mix<1,0>() * float2(-1.f,1.f);
  215. float2 shaft = start + direction * .8f;
  216. s.line(start, end);
  217. s.line(end, shaft + perpendicular * .1f);
  218. s.line(end, shaft - perpendicular * .1f);
  219. return s;
  220. }
  221. float2 project(float2 a, float2 b)
  222. {
  223. return b * b(a) / b.magnitude();
  224. }