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 +4 -4
- data/CHANGELOG.md +5 -0
- data/README.md +14 -7
- data/VERSION +1 -1
- data/lib/perfect_shape/arc.rb +160 -0
- data/lib/perfect_shape/path.rb +37 -11
- data/perfect-shape.gemspec +4 -4
- metadata +5 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 73a1e3f4816ce8829788f6ad76e83a88429301000bb49aca7bab878148d90527
|
4
|
+
data.tar.gz: f2628350798a4f5285f3c82941154e21a52aa72b401f3a3fdd889e720554eade
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
+
# 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.
|
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.
|
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: :
|
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: :
|
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.0.2
|
data/lib/perfect_shape/arc.rb
CHANGED
@@ -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
|
data/lib/perfect_shape/path.rb
CHANGED
@@ -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,
|
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
|
-
|
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
|
-
|
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 <<
|
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 =
|
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 =
|
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 =
|
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
|
data/perfect-shape.gemspec
CHANGED
@@ -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.
|
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.
|
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-
|
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.
|
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-
|
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
|
110
|
-
aka Nonzero Rule). Additionally, it contains some purely mathematical
|
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: []
|