Class: Ruby2D::Polyline

Inherits:
Object
  • Object
show all
Includes:
Renderable
Defined in:
lib/ruby2d/polyline.rb

Overview

A connected sequence of line segments. Stroke-only (no fill). Set closed: true to connect the last point back to the first.

Constant Summary collapse

MITER_LIMIT =

Miter-join limit, matching R2D_MITER_LIMIT in the C extension (the SVG default). A sharp corner's miter is clamped to MITER_LIMIT * stroke_width / 2.

4.0

Constants included from Interactive

Interactive::OBJECT_EVENTS, Interactive::OBJECT_EVENT_FILTER_PREDICATES

Instance Attribute Summary collapse

Attributes included from Renderable

#color, #padding_bottom, #padding_left, #padding_right, #padding_top, #visible, #x_align, #y_align, #z

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Renderable

#_alignment_anchor_dx, #_alignment_anchor_dy, #_apply_padding, #_extract_alignment, #_point_in_polygon?, #_point_on_segment?, #_require_numeric_position, #_resolve_alignment, #_unrotate, #_validate_dimensions, #add, #colour=, flatten_color, flatten_per_vertex, flatten_points, flatten_resolved_color, #hide, #padding=, #remove, resolve_color_or_default, resolve_single_color, #show

Methods included from Interactive

#_fire_event, #interactive?, #off, #on

Constructor Details

#initialize(points:, z: 0, rotate: 0, rx: nil, ry: nil, color: nil, colour: nil, opacity: nil, stroke_width: 1, closed: false, add: true, visible: true) ⇒ Polyline

Create a polyline points is an array of [x, y] pairs with N >= 2 vertices

Raises:

  • (ArgumentError)


17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/ruby2d/polyline.rb', line 17

def initialize(points:, z: 0, rotate: 0, rx: nil, ry: nil,
               color: nil, colour: nil, opacity: nil,
               stroke_width: 1, closed: false, add: true, visible: true)
  raise ArgumentError, 'Polyline requires at least 2 points' if points.length < 2
  raise ArgumentError, 'points must be an array of [x, y] pairs' \
    unless points.all? { |p| p.is_a?(Array) && p.length == 2 }

  @coordinates = points.flat_map { |x, y| [x.to_f, y.to_f] }
  @z = z
  @rotate = rotate
  @_user_rx = rx
  @_user_ry = ry
  @stroke_width = stroke_width
  @closed = closed
  self.color = color || colour || 'white'
  self.opacity = opacity unless opacity.nil?
  @visible = visible
  self.add if add
end

Instance Attribute Details

#closedObject

Returns the value of attribute closed.



9
10
11
# File 'lib/ruby2d/polyline.rb', line 9

def closed
  @closed
end

#rotateObject

Returns the value of attribute rotate.



9
10
11
# File 'lib/ruby2d/polyline.rb', line 9

def rotate
  @rotate
end

#stroke_widthObject

Returns the value of attribute stroke_width.



9
10
11
# File 'lib/ruby2d/polyline.rb', line 9

def stroke_width
  @stroke_width
end

Class Method Details

.render(points:, rotate: 0, rx: nil, ry: nil, color: nil, colour: nil, opacity: nil, stroke_width: 1, closed: false) ⇒ Object

Render a polyline without creating an instance

Raises:

  • (ArgumentError)


177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/ruby2d/polyline.rb', line 177

def self.render(points:, rotate: 0, rx: nil, ry: nil,
                color: nil, colour: nil, opacity: nil,
                stroke_width: 1, closed: false)
  Window.render_ready_check
  raise ArgumentError, 'Polyline requires at least 2 points' if points.length < 2

  n = points.length
  coords = Renderable.flatten_points(points)
  pvs = Renderable.flatten_color(color || colour, n, opacity, label: self)

  if rotate != 0
    ccx = 0.0; ccy = 0.0
    n.times do |i|
      ccx += coords[i * 2]
      ccy += coords[i * 2 + 1]
    end
    ccx /= n
    ccy /= n
    coords = rotate_coords(coords, rotate, rx || ccx, ry || ccy)
  end

  Ext.stroke_path(coords, stroke_width, pvs, closed) if stroke_width > 0
end

Instance Method Details

#color=(c) ⇒ Object

The stroke color. Accepts a single color or a Color::Set of vertex_count colors (one per vertex) interpolated along the path.



143
144
145
146
# File 'lib/ruby2d/polyline.rb', line 143

def color=(c)
  @color = Renderable.resolve_color_or_default(c, vertex_count, label: self.class)
  @_stroke_cc = nil
end

#contains?(x, y) ⇒ Boolean

Test whether (x, y) lies on the polyline's stroke. Hit-tests against the exact shape the renderer draws — the ribbon between the outer and inner stroke outlines, with mitered joints and butt-capped open ends — so the clickable region matches the visible pixels, corners and all. A closed path joins at every vertex (no open ends).

Returns:

  • (Boolean)


63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/ruby2d/polyline.rb', line 63

def contains?(x, y)
  return false unless @stroke_width.positive?
  x, y = _unrotate(x, y) if @rotate != 0
  n = vertex_count
  outer, inner = compute_stroke_outline
  edges = @closed ? n : n - 1
  edges.times do |i|
    j = (i + 1) % n
    # The renderer fills each edge as the quad outer[i] → inner[i] →
    # inner[j] → outer[j]; the point is on the stroke iff it lies inside any
    # of those quads. The miter outline makes the corners exact.
    oi = outer[i]; ii = inner[i]
    oj = outer[j]; ij = inner[j]
    return true if _point_in_polygon?(
      [oi[0], oi[1], ii[0], ii[1], ij[0], ij[1], oj[0], oj[1]], x, y
    )
  end
  false
end

#heightObject



53
54
55
56
# File 'lib/ruby2d/polyline.rb', line 53

def height
  ys = @coordinates.each_slice(2).map(&:last)
  ys.max - ys.min
end

#opacityObject

Get opacity. Returns the per-vertex array when set, otherwise the uniform alpha from the first vertex color.



172
173
174
# File 'lib/ruby2d/polyline.rb', line 172

def opacity
  @_per_vertex_opacity || @color&.opacity
end

#opacity=(value) ⇒ Object

Set opacity. Accepts a single value (applied to all vertices) or an array of per-vertex values (length must equal vertex_count).



150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/ruby2d/polyline.rb', line 150

def opacity=(value)
  if value.is_a?(Array)
    n = vertex_count
    raise ArgumentError,
          "opacity array must have #{n} values, one for each vertex. #{value.length} were given." \
          unless value.length == n
    # Clamp each entry to 0.0..1.0, matching the scalar path (Color#opacity=).
    # NaN can't be clamped (Float::NAN.clamp raises), so map it to 0.0 first.
    @_per_vertex_opacity = value.map do |v|
      f = v.to_f
      f = 0.0 if f.nan?
      f.clamp(0.0, 1.0)
    end
  else
    @_per_vertex_opacity = nil
    @color.opacity = value
  end
  @_stroke_cc = nil
end

#pointsObject

The vertices as an array of [x, y] pairs



43
44
45
# File 'lib/ruby2d/polyline.rb', line 43

def points
  @coordinates.each_slice(2).to_a
end

#rxObject

Get the rotation center x coordinate



122
123
124
# File 'lib/ruby2d/polyline.rb', line 122

def rx
  @_user_rx.nil? ? x : @_user_rx
end

#rx=(val) ⇒ Object

Set the rotation center x coordinate



132
133
134
# File 'lib/ruby2d/polyline.rb', line 132

def rx=(val)
  @_user_rx = val
end

#ryObject

Get the rotation center y coordinate



127
128
129
# File 'lib/ruby2d/polyline.rb', line 127

def ry
  @_user_ry.nil? ? y : @_user_ry
end

#ry=(val) ⇒ Object

Set the rotation center y coordinate



137
138
139
# File 'lib/ruby2d/polyline.rb', line 137

def ry=(val)
  @_user_ry = val
end

#vertex_countObject

Number of vertices



38
39
40
# File 'lib/ruby2d/polyline.rb', line 38

def vertex_count
  @coordinates.length / 2
end

#widthObject

Bounding-box width and height (extent across all vertices)



48
49
50
51
# File 'lib/ruby2d/polyline.rb', line 48

def width
  xs = @coordinates.each_slice(2).map(&:first)
  xs.max - xs.min
end

#xObject

Centroid x



84
85
86
87
88
89
# File 'lib/ruby2d/polyline.rb', line 84

def x
  sum = 0.0
  n = vertex_count
  n.times { |i| sum += @coordinates[i * 2] }
  sum / n
end

#x=(new_x) ⇒ Object

Set the centroid x coordinate, translating all vertices



100
101
102
103
104
105
106
107
108
# File 'lib/ruby2d/polyline.rb', line 100

def x=(new_x)
  _require_numeric_position(:x, new_x)
  dx = new_x - x
  i = 0
  while i < @coordinates.length
    @coordinates[i] += dx
    i += 2
  end
end

#yObject

Centroid y



92
93
94
95
96
97
# File 'lib/ruby2d/polyline.rb', line 92

def y
  sum = 0.0
  n = vertex_count
  n.times { |i| sum += @coordinates[i * 2 + 1] }
  sum / n
end

#y=(new_y) ⇒ Object

Set the centroid y coordinate, translating all vertices



111
112
113
114
115
116
117
118
119
# File 'lib/ruby2d/polyline.rb', line 111

def y=(new_y)
  _require_numeric_position(:y, new_y)
  dy = new_y - y
  i = 1
  while i < @coordinates.length
    @coordinates[i] += dy
    i += 2
  end
end