Geometry.pm 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. package Slic3r::Geometry;
  2. use strict;
  3. use warnings;
  4. require Exporter;
  5. our @ISA = qw(Exporter);
  6. # Exported by this module. The last section starting with convex_hull is exported by Geometry.xsp
  7. our @EXPORT_OK = qw(
  8. PI epsilon
  9. angle3points
  10. collinear
  11. dot
  12. line_intersection
  13. normalize
  14. point_in_segment
  15. polyline_lines
  16. polygon_is_convex
  17. polygon_segment_having_point
  18. scale
  19. unscale
  20. scaled_epsilon
  21. size_2D
  22. X Y Z
  23. convex_hull
  24. chained_path_from
  25. deg2rad
  26. rad2deg
  27. rad2deg_dir
  28. );
  29. use constant PI => 4 * atan2(1, 1);
  30. use constant A => 0;
  31. use constant B => 1;
  32. use constant X1 => 0;
  33. use constant Y1 => 1;
  34. use constant X2 => 2;
  35. use constant Y2 => 3;
  36. sub epsilon () { 1E-4 }
  37. sub scaled_epsilon () { epsilon / &Slic3r::SCALING_FACTOR }
  38. sub scale ($) { $_[0] / &Slic3r::SCALING_FACTOR }
  39. sub unscale ($) { $_[0] * &Slic3r::SCALING_FACTOR }
  40. # used by geometry.t, polygon_segment_having_point
  41. sub point_in_segment {
  42. my ($point, $line) = @_;
  43. my ($x, $y) = @$point;
  44. my $line_p = $line->pp;
  45. my @line_x = sort { $a <=> $b } $line_p->[A][X], $line_p->[B][X];
  46. my @line_y = sort { $a <=> $b } $line_p->[A][Y], $line_p->[B][Y];
  47. # check whether the point is in the segment bounding box
  48. return 0 unless $x >= ($line_x[0] - epsilon) && $x <= ($line_x[1] + epsilon)
  49. && $y >= ($line_y[0] - epsilon) && $y <= ($line_y[1] + epsilon);
  50. # if line is vertical, check whether point's X is the same as the line
  51. if ($line_p->[A][X] == $line_p->[B][X]) {
  52. return abs($x - $line_p->[A][X]) < epsilon ? 1 : 0;
  53. }
  54. # calculate the Y in line at X of the point
  55. my $y3 = $line_p->[A][Y] + ($line_p->[B][Y] - $line_p->[A][Y])
  56. * ($x - $line_p->[A][X]) / ($line_p->[B][X] - $line_p->[A][X]);
  57. return abs($y3 - $y) < epsilon ? 1 : 0;
  58. }
  59. # used by geometry.t
  60. sub polyline_lines {
  61. my ($polyline) = @_;
  62. my @points = @$polyline;
  63. return map Slic3r::Line->new(@points[$_, $_+1]), 0 .. $#points-1;
  64. }
  65. # given a $polygon, return the (first) segment having $point
  66. # used by geometry.t
  67. sub polygon_segment_having_point {
  68. my ($polygon, $point) = @_;
  69. foreach my $line (@{ $polygon->lines }) {
  70. return $line if point_in_segment($point, $line);
  71. }
  72. return undef;
  73. }
  74. # polygon must be simple (non complex) and ccw
  75. sub polygon_is_convex {
  76. my ($points) = @_;
  77. for (my $i = 0; $i <= $#$points; $i++) {
  78. my $angle = angle3points($points->[$i-1], $points->[$i-2], $points->[$i]);
  79. return 0 if $angle < PI;
  80. }
  81. return 1;
  82. }
  83. sub normalize {
  84. my ($line) = @_;
  85. my $len = sqrt( ($line->[X]**2) + ($line->[Y]**2) + ($line->[Z]**2) )
  86. or return [0, 0, 0]; # to avoid illegal division by zero
  87. return [ map $_ / $len, @$line ];
  88. }
  89. # 2D dot product
  90. # used by 3DScene.pm
  91. sub dot {
  92. my ($u, $v) = @_;
  93. return $u->[X] * $v->[X] + $u->[Y] * $v->[Y];
  94. }
  95. sub line_intersection {
  96. my ($line1, $line2, $require_crossing) = @_;
  97. $require_crossing ||= 0;
  98. my $intersection = _line_intersection(map @$_, @$line1, @$line2);
  99. return (ref $intersection && $intersection->[1] == $require_crossing)
  100. ? $intersection->[0]
  101. : undef;
  102. }
  103. # Used by test cases.
  104. sub collinear {
  105. my ($line1, $line2, $require_overlapping) = @_;
  106. my $intersection = _line_intersection(map @$_, @$line1, @$line2);
  107. return 0 unless !ref($intersection)
  108. && ($intersection eq 'parallel collinear'
  109. || ($intersection eq 'parallel vertical' && abs($line1->[A][X] - $line2->[A][X]) < epsilon));
  110. if ($require_overlapping) {
  111. my @box_a = bounding_box([ $line1->[0], $line1->[1] ]);
  112. my @box_b = bounding_box([ $line2->[0], $line2->[1] ]);
  113. return 0 unless bounding_box_intersect( 2, @box_a, @box_b );
  114. }
  115. return 1;
  116. }
  117. sub _line_intersection {
  118. my ( $x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3 ) = @_;
  119. my ($x, $y); # The as-yet-undetermined intersection point.
  120. my $dy10 = $y1 - $y0; # dyPQ, dxPQ are the coordinate differences
  121. my $dx10 = $x1 - $x0; # between the points P and Q.
  122. my $dy32 = $y3 - $y2;
  123. my $dx32 = $x3 - $x2;
  124. my $dy10z = abs( $dy10 ) < epsilon; # Is the difference $dy10 "zero"?
  125. my $dx10z = abs( $dx10 ) < epsilon;
  126. my $dy32z = abs( $dy32 ) < epsilon;
  127. my $dx32z = abs( $dx32 ) < epsilon;
  128. my $dyx10; # The slopes.
  129. my $dyx32;
  130. $dyx10 = $dy10 / $dx10 unless $dx10z;
  131. $dyx32 = $dy32 / $dx32 unless $dx32z;
  132. # Now we know all differences and the slopes;
  133. # we can detect horizontal/vertical special cases.
  134. # E.g., slope = 0 means a horizontal line.
  135. unless ( defined $dyx10 or defined $dyx32 ) {
  136. return "parallel vertical";
  137. }
  138. elsif ( $dy10z and not $dy32z ) { # First line horizontal.
  139. $y = $y0;
  140. $x = $x2 + ( $y - $y2 ) * $dx32 / $dy32;
  141. }
  142. elsif ( not $dy10z and $dy32z ) { # Second line horizontal.
  143. $y = $y2;
  144. $x = $x0 + ( $y - $y0 ) * $dx10 / $dy10;
  145. }
  146. elsif ( $dx10z and not $dx32z ) { # First line vertical.
  147. $x = $x0;
  148. $y = $y2 + $dyx32 * ( $x - $x2 );
  149. }
  150. elsif ( not $dx10z and $dx32z ) { # Second line vertical.
  151. $x = $x2;
  152. $y = $y0 + $dyx10 * ( $x - $x0 );
  153. }
  154. elsif ( abs( $dyx10 - $dyx32 ) < epsilon ) {
  155. # The slopes are suspiciously close to each other.
  156. # Either we have parallel collinear or just parallel lines.
  157. # The bounding box checks have already weeded the cases
  158. # "parallel horizontal" and "parallel vertical" away.
  159. my $ya = $y0 - $dyx10 * $x0;
  160. my $yb = $y2 - $dyx32 * $x2;
  161. return "parallel collinear" if abs( $ya - $yb ) < epsilon;
  162. return "parallel";
  163. }
  164. else {
  165. # None of the special cases matched.
  166. # We have a "honest" line intersection.
  167. $x = ($y2 - $y0 + $dyx10*$x0 - $dyx32*$x2)/($dyx10 - $dyx32);
  168. $y = $y0 + $dyx10 * ($x - $x0);
  169. }
  170. my $h10 = $dx10 ? ($x - $x0) / $dx10 : ($dy10 ? ($y - $y0) / $dy10 : 1);
  171. my $h32 = $dx32 ? ($x - $x2) / $dx32 : ($dy32 ? ($y - $y2) / $dy32 : 1);
  172. return [Slic3r::Point->new($x, $y), $h10 >= 0 && $h10 <= 1 && $h32 >= 0 && $h32 <= 1];
  173. }
  174. # 2D
  175. sub bounding_box {
  176. my ($points) = @_;
  177. my @x = map $_->x, @$points;
  178. my @y = map $_->y, @$points; #,,
  179. my @bb = (undef, undef, undef, undef);
  180. for (0..$#x) {
  181. $bb[X1] = $x[$_] if !defined $bb[X1] || $x[$_] < $bb[X1];
  182. $bb[X2] = $x[$_] if !defined $bb[X2] || $x[$_] > $bb[X2];
  183. $bb[Y1] = $y[$_] if !defined $bb[Y1] || $y[$_] < $bb[Y1];
  184. $bb[Y2] = $y[$_] if !defined $bb[Y2] || $y[$_] > $bb[Y2];
  185. }
  186. return @bb[X1,Y1,X2,Y2];
  187. }
  188. # used by ExPolygon::size
  189. sub size_2D {
  190. my @bounding_box = bounding_box(@_);
  191. return (
  192. ($bounding_box[X2] - $bounding_box[X1]),
  193. ($bounding_box[Y2] - $bounding_box[Y1]),
  194. );
  195. }
  196. # Used by sub collinear, which is used by test cases.
  197. # bounding_box_intersect($d, @a, @b)
  198. # Return true if the given bounding boxes @a and @b intersect
  199. # in $d dimensions. Used by sub collinear.
  200. sub bounding_box_intersect {
  201. my ( $d, @bb ) = @_; # Number of dimensions and box coordinates.
  202. my @aa = splice( @bb, 0, 2 * $d ); # The first box.
  203. # (@bb is the second one.)
  204. # Must intersect in all dimensions.
  205. for ( my $i_min = 0; $i_min < $d; $i_min++ ) {
  206. my $i_max = $i_min + $d; # The index for the maximum.
  207. return 0 if ( $aa[ $i_max ] + epsilon ) < $bb[ $i_min ];
  208. return 0 if ( $bb[ $i_max ] + epsilon ) < $aa[ $i_min ];
  209. }
  210. return 1;
  211. }
  212. # Used by test cases.
  213. # this assumes a CCW rotation from $p2 to $p3 around $p1
  214. sub angle3points {
  215. my ($p1, $p2, $p3) = @_;
  216. # p1 is the center
  217. my $angle = atan2($p2->[X] - $p1->[X], $p2->[Y] - $p1->[Y])
  218. - atan2($p3->[X] - $p1->[X], $p3->[Y] - $p1->[Y]);
  219. # we only want to return only positive angles
  220. return $angle <= 0 ? $angle + 2*PI() : $angle;
  221. }
  222. 1;