perfect-shape 1.0.1 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e83221df061b944acb69abfec2536a0c0bc3c8176d6f5b4929c68bb6283e7f82
4
- data.tar.gz: 5f778729c9b39a259708a0e6995b370f040442b4864f7594418fafdfd1208d12
3
+ metadata.gz: 73a1e3f4816ce8829788f6ad76e83a88429301000bb49aca7bab878148d90527
4
+ data.tar.gz: f2628350798a4f5285f3c82941154e21a52aa72b401f3a3fdd889e720554eade
5
5
  SHA512:
6
- metadata.gz: 9e18a4d841f70f938c220c21e1d928273d5a24a6f114a4a2b8c0f2c0c6ca020a80510f9dd698a092582607d5bd8a224e5c6c6aea875a443a740d64982ae4f2d4
7
- data.tar.gz: 5712f83712d505f3c7156dfd9e194735b73215542c9012bda5359544f72eac4743f00d9e39f97c98a2bee22c43dd45f808554abad3a5ea6f2b983e925f3cadb2
6
+ metadata.gz: d79de0f8201d530f55792faea84bb8e9d47608a06f234d305aeb5dcf8ca8fefc612c9eebda0b449ece028a3d281f6b5d8824c41c6b7703d9e575e9cb6cabaa55
7
+ data.tar.gz: 861950d0dd6107a7de5f5d5f59e192770eb6255024881d67a4e67b3ca229fdc1ff29f176f40223bdb0c8ef67e38cfc46a33313c1d9adb9b37c7fb84f688dc491
data/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Change Log
2
2
 
3
+ ## 1.0.2
4
+
5
+ - `Path` can contain an `Arc`, `Ellipse`, or `Circle` (to get affected by the winding algorithm as opposed to `CompositeShape` which has no winding algorithm)
6
+ - `Arc`, `Ellipse`, and `Circle` `#to_path_shapes` method, which decomposes them into `Point`s, `Line`s, and `CubicBezierCurve`s to be added to a `Path`
7
+
3
8
  ## 1.0.1
4
9
 
5
10
  - Relax `equalizer` gem version to '>= 0.0.11', '< 1.1.0' to avoid conflicts in projects that might use future versions
data/README.md CHANGED
@@ -1,9 +1,9 @@
1
- # Perfect Shape 1.0.1
1
+ # Perfect Shape 1.0.2
2
2
  ## Geometric Algorithms
3
3
  [![Gem Version](https://badge.fury.io/rb/perfect-shape.svg)](http://badge.fury.io/rb/perfect-shape)
4
4
  [![Test](https://github.com/AndyObtiva/perfect-shape/actions/workflows/ruby.yml/badge.svg)](https://github.com/AndyObtiva/perfect-shape/actions/workflows/ruby.yml)
5
5
 
6
- [`PerfectShape`](https://rubygems.org/gems/perfect-shape) is a collection of pure Ruby geometric algorithms that are mostly useful for GUI (Graphical User Interface) manipulation like checking viewport rectangle intersection or containment of a mouse click [point](#perfectshapepoint) in popular geometry shapes such as [rectangle](#perfectshaperectangle), [square](#perfectshapesquare), [arc](#perfectshapearc) (open, chord, and pie), [ellipse](#perfectshapeellipse), [circle](#perfectshapecircle), [polygon](#perfectshapepolygon), and [paths](#perfectshapepath) containing [lines](#perfectshapeline), [quadratic bézier curves](#perfectshapequadraticbeziercurve), and [cubic bezier curves](#perfectshapecubicbeziercurve), potentially with [affine transforms](#perfectshapeaffinetransform) applied like translation, scale, rotation, shear/skew, and inversion (including both [Ray Casting Algorithm](https://en.wikipedia.org/wiki/Point_in_polygon#Ray_casting_algorithm), aka [Even-odd Rule](https://en.wikipedia.org/wiki/Even%E2%80%93odd_rule), and [Winding Number Algorithm](https://en.wikipedia.org/wiki/Point_in_polygon#Winding_number_algorithm), aka [Nonzero Rule](https://en.wikipedia.org/wiki/Nonzero-rule)).
6
+ [`PerfectShape`](https://rubygems.org/gems/perfect-shape) is a collection of pure Ruby geometric algorithms that are mostly useful for GUI (Graphical User Interface) manipulation like checking viewport rectangle intersection or containment of a mouse click [point](#perfectshapepoint) in popular geometry shapes such as [rectangle](#perfectshaperectangle), [square](#perfectshapesquare), [arc](#perfectshapearc) (open, chord, and pie), [ellipse](#perfectshapeellipse), [circle](#perfectshapecircle), [polygon](#perfectshapepolygon), and [paths](#perfectshapepath) containing [lines](#perfectshapeline), [quadratic bézier curves](#perfectshapequadraticbeziercurve), and [cubic bezier curves](#perfectshapecubicbeziercurve), potentially with [affine transforms](#perfectshapeaffinetransform) applied like translation, scale, rotation, shear/skew, and inversion (including both the [Ray Casting Algorithm](https://en.wikipedia.org/wiki/Point_in_polygon#Ray_casting_algorithm), aka [Even-odd Rule](https://en.wikipedia.org/wiki/Even%E2%80%93odd_rule), and the [Winding Number Algorithm](https://en.wikipedia.org/wiki/Point_in_polygon#Winding_number_algorithm), aka [Nonzero Rule](https://en.wikipedia.org/wiki/Nonzero-rule)).
7
7
 
8
8
  Additionally, [`PerfectShape::Math`](#perfectshapemath) contains some purely mathematical algorithms, like [IEEE 754-1985 Remainder](https://en.wikipedia.org/wiki/IEEE_754-1985).
9
9
 
@@ -14,13 +14,13 @@ To ensure high accuracy, this library does all its mathematical operations with
14
14
  Run:
15
15
 
16
16
  ```
17
- gem install perfect-shape -v 1.0.1
17
+ gem install perfect-shape -v 1.0.2
18
18
  ```
19
19
 
20
20
  Or include in Bundler `Gemfile`:
21
21
 
22
22
  ```ruby
23
- gem 'perfect-shape', '~> 1.0.1'
23
+ gem 'perfect-shape', '~> 1.0.2'
24
24
  ```
25
25
 
26
26
  And, run:
@@ -467,6 +467,8 @@ Open Arc | Chord Arc | Pie Arc
467
467
  - `#start`: start angle in degrees
468
468
  - `#extent`: extent angle in degrees
469
469
  - `#center_point`: center point as `Array` of `[center_x, center_y]` coordinates
470
+ - `#start_point`: start point as `Array` of (x,y) coordinates
471
+ - `#end_point`: end point as `Array` of (x,y) coordinates
470
472
  - `#center_x`: center x
471
473
  - `#center_y`: center y
472
474
  - `#radius_x`: radius along the x-axis
@@ -480,6 +482,8 @@ Open Arc | Chord Arc | Pie Arc
480
482
  - `#contain?(x_or_point, y=nil, outline: false, distance_tolerance: 0)`: checks if point is inside when `outline` is `false` or if point is on the outline when `outline` is `true`. `distance_tolerance` can be used as a fuzz factor when `outline` is `true`, for example, to help GUI users mouse-click-select an arc shape from its outline more successfully
481
483
  - `#intersect?(rectangle)`: Returns `true` if intersecting with interior of rectangle or `false` otherwise. This is useful for GUI optimization checks of whether a shape appears in a GUI viewport rectangle and needs redrawing
482
484
  - `#contain_angle?(angle)`: returns `true` if the angle is within the angular extents of the arc and `false` otherwise
485
+ - `#to_path_shapes`: Converts `Arc` into basic `Path` shapes made up of `Point`s, `Line`s, and `CubicBezierCurve`s. Used by `Path` when adding an `Arc` to `Path` `shapes`
486
+ - `#btan(increment)`: btan computes the length (k) of the control segments at the beginning and end of a cubic bezier that approximates a segment of an arc with extent less than or equal to 90 degrees. This length (k) will be used to generate the 2 bezier control points for such a segment.
483
487
 
484
488
  Example:
485
489
 
@@ -604,6 +608,7 @@ Extends `PerfectShape::Arc`
604
608
  - `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
605
609
  - `#contain?(x_or_point, y=nil, outline: false, distance_tolerance: 0)`: checks if point is inside when `outline` is `false` or if point is on the outline when `outline` is `true`. `distance_tolerance` can be used as a fuzz factor when `outline` is `true`, for example, to help GUI users mouse-click-select an ellipse shape from its outline more successfully
606
610
  - `#intersect?(rectangle)`: Returns `true` if intersecting with interior of rectangle or `false` otherwise. This is useful for GUI optimization checks of whether a shape appears in a GUI viewport rectangle and needs redrawing
611
+ - `#to_path_shapes`: Converts `Ellipse` into basic `Path` shapes made up of `Point`s, `Line`s, and `CubicBezierCurve`s. Used by `Path` when adding an `Ellipse` to `Path` `shapes`
607
612
 
608
613
  Example:
609
614
 
@@ -666,6 +671,7 @@ Extends `PerfectShape::Ellipse`
666
671
  - `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
667
672
  - `#contain?(x_or_point, y=nil, outline: false, distance_tolerance: 0)`: checks if point is inside when `outline` is `false` or if point is on the outline when `outline` is `true`. `distance_tolerance` can be used as a fuzz factor when `outline` is `true`, for example, to help GUI users mouse-click-select a circle shape from its outline more successfully
668
673
  - `#intersect?(rectangle)`: Returns `true` if intersecting with interior of rectangle or `false` otherwise. This is useful for GUI optimization checks of whether a shape appears in a GUI viewport rectangle and needs redrawing
674
+ - `#to_path_shapes`: Converts `Circle` into basic `Path` shapes made up of `Point`s, `Line`s, and `CubicBezierCurve`s. Used by `Path` when adding a `Circle` to `Path` `shapes`
669
675
 
670
676
  Example:
671
677
 
@@ -754,8 +760,9 @@ Includes `PerfectShape::MultiPoint`
754
760
 
755
761
  ![path](https://raw.githubusercontent.com/AndyObtiva/perfect-shape/master/images/path.png)
756
762
 
757
- - `::new(shapes: [], closed: false, winding_rule: :wind_non_zero)`: constructs a path with `shapes` as `Array` of shape objects, which can be `PerfectShape::Point` (or `Array` of `[x, y]` coordinates), `PerfectShape::Line`, `PerfectShape::QuadraticBezierCurve`, or `PerfectShape::CubicBezierCurve`. If a path is closed, its last point is automatically connected to its first point with a line segment. The winding rule can be `:wind_non_zero` (default) or `:wind_even_odd`.
758
- - `#shapes`: the shapes that the path is composed of (must always start with `PerfectShape::Point` or Array of [x,y] coordinates representing start point)
763
+ - `::new(shapes: [], closed: false, winding_rule: :wind_even_odd, line_to_complex_shapes: false)`: constructs a path with `shapes` as `Array` of shape objects, which can be `PerfectShape::Point` (or `Array` of `[x, y]` coordinates), `PerfectShape::Line`, `PerfectShape::QuadraticBezierCurve`, `PerfectShape::CubicBezierCurve`, or complex shapes that decompose into the aforementioned basic path shapes, like `PerfectShape::Arc`, `PerfectShape::Ellipse`, and `PerfectShape::Circle`. If a path is closed, its last point is automatically connected to its first point with a line segment. The winding rule can be `:wind_non_zero` (default) or `:wind_even_odd`. `line_to_complex_shapes` can be `true` or `false` (default), indicating whether to connect to complex shapes, meaning `Arc`, `Ellipse`, and `Circle`, with a line, or otherwise move to their start point instead.
764
+ - `#shapes`: the shapes that the path is composed of (must always start with `PerfectShape::Point` or Array of `[x,y]` coordinates representing start point)
765
+ - `#basic_shapes`: the basic shapes that the path is composed of, meaning only `Point`, `Line`, `QuadraticBezierCurve`, and `CubicBezierCurve` shapes (decomposing complex shapes like `Arc`, `Ellipse`, and `Circle` using their `#to_path_shapes` method)
759
766
  - `#closed?`: returns `true` if closed and `false` otherwise
760
767
  - `#winding_rule`: returns winding rule (`:wind_non_zero` or `:wind_even_odd`)
761
768
  - `#points`: path points calculated (derived) from shapes
@@ -786,7 +793,7 @@ path_shapes << PerfectShape::Line.new(points: [250, 170]) # no need for start po
786
793
  path_shapes << PerfectShape::QuadraticBezierCurve.new(points: [[300, 185], [350, 150]]) # no need for start point, just control point and end point
787
794
  path_shapes << PerfectShape::CubicBezierCurve.new(points: [[370, 50], [430, 220], [480, 170]]) # no need for start point, just two control points and end point
788
795
 
789
- shape = PerfectShape::Path.new(shapes: path_shapes, closed: false, winding_rule: :wind_even_odd)
796
+ shape = PerfectShape::Path.new(shapes: path_shapes, closed: false, winding_rule: :wind_non_zero)
790
797
 
791
798
  shape.contain?(275, 165) # => true
792
799
  shape.contain?([275, 165]) # => true
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.1
1
+ 1.0.2
@@ -312,5 +312,165 @@ module PerfectShape
312
312
  y = self.y + (Math.sin(angle) * 0.5 + 0.5) * self.height
313
313
  [x, y]
314
314
  end
315
+
316
+ # Converts Arc into basic Path shapes made up of Points, Lines, and CubicBezierCurves
317
+ # Used by Path when adding an Arc to Path shapes
318
+ def to_path_shapes
319
+ w = BigDecimal(self.width.to_s) / 2
320
+ h = BigDecimal(self.height.to_s) / 2
321
+ x = self.x + w
322
+ y = self.x + h
323
+ ang_st_rad = -Math.degrees_to_radians(self.start)
324
+ ext = -self.extent
325
+ if ext >= 360.0 || ext <= -360
326
+ arc_segs = 4
327
+ increment = Math::PI / 2
328
+ cv = 0.5522847498307933
329
+ if ext < 0
330
+ increment = -increment
331
+ cv = -cv
332
+ end
333
+ else
334
+ arc_segs = (ext.abs / 90.0).ceil
335
+ increment = Math.degrees_to_radians(ext / arc_segs)
336
+ cv = btan(increment)
337
+ arc_segs = 0 if cv == 0
338
+ end
339
+ line_segs = nil
340
+ case self.type
341
+ when :open
342
+ line_segs = 0
343
+ when :chord
344
+ line_segs = 1
345
+ when :pie
346
+ line_segs = 2
347
+ end
348
+ arc_segs = line_segs = -1 if w < 0 || h < 0
349
+
350
+ first_point_x = first_point_y = nil
351
+ last_point_x = last_point_y = nil
352
+ (arc_segs + line_segs + 1).to_i.times.map do |index|
353
+ coords = []
354
+ angle = ang_st_rad
355
+ if index == 0
356
+ first_point_x = coords[0] = x + Math.cos(angle) * w
357
+ first_point_y = coords[1] = y + Math.sin(angle) * h
358
+ Point.new(*coords)
359
+ elsif (index > arc_segs) && (extent - start) != 0 && ((extent - start)%360 == 0)
360
+ nil
361
+ elsif (index > arc_segs) && (index < arc_segs + line_segs) && (extent - start) == 0
362
+ nil
363
+ elsif (index > arc_segs) && (index == arc_segs + line_segs)
364
+ Line.new(points: [[first_point_x, first_point_y]])
365
+ elsif index > arc_segs
366
+ coords[0] = x
367
+ coords[1] = y
368
+ if line_segs == 2
369
+ last_point_x = coords[0]
370
+ last_point_y = coords[1]
371
+ end
372
+ Line.new(points: coords)
373
+ else
374
+ angle += increment * (index - 1)
375
+ relx = Math.cos(angle)
376
+ rely = Math.sin(angle)
377
+ coords[0] = x + (relx - cv * rely) * w
378
+ coords[1] = y + (rely + cv * relx) * h
379
+ angle += increment
380
+ relx = Math.cos(angle)
381
+ rely = Math.sin(angle)
382
+ coords[2] = x + (relx + cv * rely) * w
383
+ coords[3] = y + (rely - cv * relx) * h
384
+ coords[4] = x + relx * w
385
+ coords[5] = y + rely * h
386
+ if line_segs == 1
387
+ last_point_x = coords[4]
388
+ last_point_y = coords[5]
389
+ end
390
+ CubicBezierCurve.new(points: coords)
391
+ end
392
+ end.compact
393
+ end
394
+
395
+ # btan computes the length (k) of the control segments at
396
+ # the beginning and end of a cubic bezier that approximates
397
+ # a segment of an arc with extent less than or equal to
398
+ # 90 degrees. This length (k) will be used to generate the
399
+ # 2 bezier control points for such a segment.
400
+ #
401
+ # Assumptions:
402
+ # a) arc is centered on 0,0 with radius of 1.0
403
+ # b) arc extent is less than 90 degrees
404
+ # c) control points should preserve tangent
405
+ # d) control segments should have equal length
406
+ #
407
+ # Initial data:
408
+ # start angle: ang1
409
+ # end angle: ang2 = ang1 + extent
410
+ # start point: P1 = (x1, y1) = (cos(ang1), sin(ang1))
411
+ # end point: P4 = (x4, y4) = (cos(ang2), sin(ang2))
412
+ #
413
+ # Control points:
414
+ # P2 = (x2, y2)
415
+ # | x2 = x1 - k * sin(ang1) = cos(ang1) - k * sin(ang1)
416
+ # | y2 = y1 + k * cos(ang1) = sin(ang1) + k * cos(ang1)
417
+ #
418
+ # P3 = (x3, y3)
419
+ # | x3 = x4 + k * sin(ang2) = cos(ang2) + k * sin(ang2)
420
+ # | y3 = y4 - k * cos(ang2) = sin(ang2) - k * cos(ang2)
421
+ #
422
+ # The formula for this length (k) can be found using the
423
+ # following derivations:
424
+ #
425
+ # Midpoints:
426
+ # a) bezier (t = 1/2)
427
+ # bPm = P1 * (1-t)^3 +
428
+ # 3 * P2 * t * (1-t)^2 +
429
+ # 3 * P3 * t^2 * (1-t) +
430
+ # P4 * t^3 =
431
+ # = (P1 + 3P2 + 3P3 + P4)/8
432
+ #
433
+ # b) arc
434
+ # aPm = (cos((ang1 + ang2)/2), sin((ang1 + ang2)/2))
435
+ #
436
+ # Let angb = (ang2 - ang1)/2; angb is half of the angle
437
+ # between ang1 and ang2.
438
+ #
439
+ # Solve the equation bPm == aPm
440
+ #
441
+ # a) For xm coord:
442
+ # x1 + 3*x2 + 3*x3 + x4 = 8*cos((ang1 + ang2)/2)
443
+ #
444
+ # cos(ang1) + 3*cos(ang1) - 3*k*sin(ang1) +
445
+ # 3*cos(ang2) + 3*k*sin(ang2) + cos(ang2) =
446
+ # = 8*cos((ang1 + ang2)/2)
447
+ #
448
+ # 4*cos(ang1) + 4*cos(ang2) + 3*k*(sin(ang2) - sin(ang1)) =
449
+ # = 8*cos((ang1 + ang2)/2)
450
+ #
451
+ # 8*cos((ang1 + ang2)/2)*cos((ang2 - ang1)/2) +
452
+ # 6*k*sin((ang2 - ang1)/2)*cos((ang1 + ang2)/2) =
453
+ # = 8*cos((ang1 + ang2)/2)
454
+ #
455
+ # 4*cos(angb) + 3*k*sin(angb) = 4
456
+ #
457
+ # k = 4 / 3 * (1 - cos(angb)) / sin(angb)
458
+ #
459
+ # b) For ym coord we derive the same formula.
460
+ #
461
+ # Since this formula can generate "NaN" values for small
462
+ # angles, we will derive a safer form that does not involve
463
+ # dividing by very small values:
464
+ # (1 - cos(angb)) / sin(angb) =
465
+ # = (1 - cos(angb))*(1 + cos(angb)) / sin(angb)*(1 + cos(angb)) =
466
+ # = (1 - cos(angb)^2) / sin(angb)*(1 + cos(angb)) =
467
+ # = sin(angb)^2 / sin(angb)*(1 + cos(angb)) =
468
+ # = sin(angb) / (1 + cos(angb))
469
+ #
470
+ def btan(increment)
471
+ return 0 if increment.nan?
472
+ increment /= BigDecimal('2.0')
473
+ BigDecimal('4.0') / BigDecimal('3.0') * Math.sin(increment) / (BigDecimal('1.0') + Math.cos(increment))
474
+ end
315
475
  end
316
476
  end
@@ -32,28 +32,34 @@ module PerfectShape
32
32
  include Equalizer.new(:shapes, :closed, :winding_rule)
33
33
 
34
34
  # Available class types for path shapes
35
- SHAPE_TYPES = [Array, PerfectShape::Point, PerfectShape::Line, PerfectShape::QuadraticBezierCurve, PerfectShape::CubicBezierCurve]
35
+ SHAPE_TYPES = [Array, PerfectShape::Point, PerfectShape::Line, PerfectShape::QuadraticBezierCurve, PerfectShape::CubicBezierCurve, PerfectShape::Arc, PerfectShape::Ellipse, PerfectShape::Circle]
36
36
 
37
37
  # Available winding rules
38
38
  WINDING_RULES = [:wind_even_odd, :wind_non_zero]
39
39
 
40
40
  attr_reader :winding_rule
41
- attr_accessor :shapes, :closed
41
+ attr_accessor :shapes, :closed, :line_to_complex_shapes
42
42
  alias closed? closed
43
+ alias line_to_complex_shapes? line_to_complex_shapes
43
44
 
44
- # Constructs Path with winding rule, closed status, and shapes (must always start with PerfectShape::Point or Array of [x,y] coordinates)
45
- # Shape class types can be any of SHAPE_TYPES: Array (x,y coordinates), PerfectShape::Point, PerfectShape::Line, PerfectShape::QuadraticBezierCurve, or PerfectShape::CubicBezierCurve
45
+ # Constructs Path with winding rule, closed status, line_to_complex_shapes option, and shapes (must always start with PerfectShape::Point or Array of [x,y] coordinates)
46
+ # Shape class types can be any of SHAPE_TYPES: Array (x,y coordinates), PerfectShape::Point, PerfectShape::Line, PerfectShape::QuadraticBezierCurve, PerfectShape::CubicBezierCurve
47
+ # PerfectShape::Arc, PerfectShape::Ellipse, or PerfectShape::Circle
48
+ # Complex shapes, meaning Arc, Ellipse, and Circle, are decomposed into basic path shapes, meaning Point, Line, QuadraticBezierCurve, and CubicBezierCurve.
46
49
  # winding_rule can be any of WINDING_RULES: :wind_non_zero (default) or :wind_even_odd
47
- # closed can be true or false
48
- def initialize(shapes: [], closed: false, winding_rule: :wind_even_odd)
50
+ # closed can be true or false (default)
51
+ # line_to_complex_shapes can be true or false (default), indicating whether to connect to complex shapes,
52
+ # meaning Arc, Ellipse, and Circle, with a line, or otherwise move to their start point instead.
53
+ def initialize(shapes: [], closed: false, winding_rule: :wind_even_odd, line_to_complex_shapes: false)
49
54
  self.closed = closed
50
55
  self.winding_rule = winding_rule
51
56
  self.shapes = shapes
57
+ self.line_to_complex_shapes = line_to_complex_shapes
52
58
  end
53
59
 
54
60
  def points
55
61
  the_points = []
56
- @shapes.each do |shape|
62
+ basic_shapes.each do |shape|
57
63
  case shape
58
64
  when Point
59
65
  the_points << shape.to_a
@@ -71,7 +77,7 @@ module PerfectShape
71
77
  end
72
78
  end
73
79
  end
74
- the_points << @shapes.first.to_a if closed?
80
+ the_points << basic_shapes.first.to_a if closed?
75
81
  the_points
76
82
  end
77
83
 
@@ -80,7 +86,7 @@ module PerfectShape
80
86
  end
81
87
 
82
88
  def drawing_types
83
- the_drawing_shapes = @shapes.map do |shape|
89
+ the_drawing_shapes = basic_shapes.map do |shape|
84
90
  case shape
85
91
  when Point
86
92
  :move_to
@@ -233,9 +239,9 @@ module PerfectShape
233
239
  # Lastly, if the path is closed, an extra shape is
234
240
  # added to represent the line connecting the last point to the first
235
241
  def disconnected_shapes
236
- initial_point = start_point = @shapes.first.to_a.map {|n| BigDecimal(n.to_s)}
242
+ initial_point = start_point = basic_shapes.first.to_a.map {|n| BigDecimal(n.to_s)}
237
243
  final_point = nil
238
- the_disconnected_shapes = @shapes.drop(1).map do |shape|
244
+ the_disconnected_shapes = basic_shapes.drop(1).map do |shape|
239
245
  case shape
240
246
  when Point
241
247
  disconnected_shape = Point.new(*shape.to_a)
@@ -372,5 +378,25 @@ module PerfectShape
372
378
  # assert((crossings & 1) != 0)
373
379
  crossings
374
380
  end
381
+
382
+ # Returns basic shapes (i.e. Point, Line, QuadraticBezierCurve, and CubicBezierCurve),
383
+ # decomposed from complex shapes like Arc, Ellipse, and Circle by calling their `#to_path_shapes` method
384
+ def basic_shapes
385
+ the_shapes = []
386
+ @shapes.each do |shape|
387
+ if shape.respond_to?(:to_path_shapes)
388
+ shape_basic_shapes = shape.to_path_shapes
389
+ if @line_to_complex_shapes
390
+ first_basic_shape = shape_basic_shapes.shift
391
+ new_first_basic_shape = PerfectShape::Line.new(points: [first_basic_shape.to_a])
392
+ shape_basic_shapes.unshift(new_first_basic_shape)
393
+ end
394
+ the_shapes += shape_basic_shapes
395
+ else
396
+ the_shapes << shape
397
+ end
398
+ end
399
+ the_shapes
400
+ end
375
401
  end
376
402
  end
@@ -2,17 +2,17 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Juwelier::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
- # stub: perfect-shape 1.0.1 ruby lib
5
+ # stub: perfect-shape 1.0.2 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "perfect-shape".freeze
9
- s.version = "1.0.1"
9
+ s.version = "1.0.2"
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
12
12
  s.require_paths = ["lib".freeze]
13
13
  s.authors = ["Andy Maleh".freeze]
14
- s.date = "2022-02-16"
15
- s.description = "Perfect Shape is a collection of pure Ruby geometric algorithms that are mostly useful for GUI manipulation like checking viewport rectangle intersection or containment of a mouse click point in popular geometry shapes such as rectangle, square, arc (open, chord, and pie), ellipse, circle, polygon, and paths containing lines, quadratic b\u00E9zier curves, and cubic bezier curves, potentially with affine transforms applied like translation, scale, rotation, shear/skew, and inversion (including both Ray Casting Algorithm, aka Even-odd Rule, and Winding Number Algorithm, aka Nonzero Rule). Additionally, it contains some purely mathematical algorithms like IEEEremainder (also known as IEEE-754 remainder).".freeze
14
+ s.date = "2022-04-03"
15
+ s.description = "Perfect Shape is a collection of pure Ruby geometric algorithms that are mostly useful for GUI manipulation like checking viewport rectangle intersection or containment of a mouse click point in popular geometry shapes such as rectangle, square, arc (open, chord, and pie), ellipse, circle, polygon, and paths containing lines, quadratic b\u00E9zier curves, and cubic bezier curves, potentially with affine transforms applied like translation, scale, rotation, shear/skew, and inversion (including both the Ray Casting Algorithm, aka Even-odd Rule, and the Winding Number Algorithm, aka Nonzero Rule). Additionally, it contains some purely mathematical algorithms like IEEEremainder (also known as IEEE-754 remainder).".freeze
16
16
  s.email = "andy.am@gmail.com".freeze
17
17
  s.extra_rdoc_files = [
18
18
  "CHANGELOG.md",
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: perfect-shape
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andy Maleh
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-02-16 00:00:00.000000000 Z
11
+ date: 2022-04-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: equalizer
@@ -106,9 +106,9 @@ description: Perfect Shape is a collection of pure Ruby geometric algorithms tha
106
106
  square, arc (open, chord, and pie), ellipse, circle, polygon, and paths containing
107
107
  lines, quadratic bézier curves, and cubic bezier curves, potentially with affine
108
108
  transforms applied like translation, scale, rotation, shear/skew, and inversion
109
- (including both Ray Casting Algorithm, aka Even-odd Rule, and Winding Number Algorithm,
110
- aka Nonzero Rule). Additionally, it contains some purely mathematical algorithms
111
- like IEEEremainder (also known as IEEE-754 remainder).
109
+ (including both the Ray Casting Algorithm, aka Even-odd Rule, and the Winding Number
110
+ Algorithm, aka Nonzero Rule). Additionally, it contains some purely mathematical
111
+ algorithms like IEEEremainder (also known as IEEE-754 remainder).
112
112
  email: andy.am@gmail.com
113
113
  executables: []
114
114
  extensions: []