Class: VectorSalad::StandardShapes::Path
- Inherits:
-
BasicShape
- Object
- BasicShape
- VectorSalad::StandardShapes::Path
- Defined in:
- lib/vector_salad/standard_shapes/path.rb,
lib/vector_salad/exporters/svg_exporter.rb
Overview
The simplest shape primitive, all shapes can be represented as a Path.
Instance Attribute Summary (collapse)
-
- (Object) closed
readonly
Returns the value of attribute closed.
-
- (Object) nodes
readonly
Returns the value of attribute nodes.
-
- (Object) options
inherited
from BasicShape
Returns the value of attribute options.
Instance Method Summary (collapse)
-
- (Path) [](x, y)
Move the path absolutely.
-
- (Path) flip(axis)
Flips the path on the specified axis.
-
- (Path) flip_x
Flips on the x axis.
-
- (Path) flip_y
Flips on the y axis.
-
- (Path) initialize(*nodes, closed: true, **options)
constructor
A path is made up of N nodes and these nodes can have different types (see N).
-
- (Path) jitter(max, min: 0, fn: nil)
Jitter the position of nodes in a Path randomly.
-
- (Path) move(x, y)
Move the path relatively.
-
- (Path) rotate(angle)
Rotates the Path by the specified angle about the origin.
-
- (Path) scale(x_multiplier, y_multiplier = x_multiplier)
Scale a Path by multiplier about the origin.
-
- (Object) to_a
Return the nodes as an array of coordinates.
-
- (Object) to_bezier_path
Convert the complex path to a bezier path.
-
- (Object) to_cubic_path
Convert the path into a cubic bezier path (no quadratics).
-
- (Object) to_multi_path
Wrap the path in a multi_path.
-
- (Object) to_path
Convert the path to a path (it returns self).
-
- (Object) to_simple_path(*_)
Flatten any curves in the path into many small straight line segments.
-
- (Object) to_svg
Export the shape to an svg string.
Constructor Details
- (Path) initialize(*nodes, closed: true, **options)
A path is made up of N nodes and these nodes can have different types (see N).
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
# File 'lib/vector_salad/standard_shapes/path.rb', line 22 def initialize(*nodes, closed: true, **) @nodes = [] nodes.each_index do |i| node = nodes[i].class == Array ? N.new(*nodes[i]) : nodes[i] if i == 0 && ![:node, :g2, :g4, :left, :right].include?(node.type) fail "First node in a path must be :node or :spiro type." end case node.type when :cubic unless nodes[i - 1].type == :node || (nodes[i - 2].type == :node && nodes[i - 1].type == :cubic) fail ":cubic node must follow a :node and at most 1 other :cubic." end when :quadratic unless nodes[i - 1].type == :node fail ":quadratic nodes must follow a :node." end when :mirror if nodes[i - 1].type == :node && (nodes[i - 2].type == :quadratic || nodes[i - 2].type == :cubic) pivot = nodes[i - 1] source = nodes[i - 2] dx = pivot.x - source.x dy = pivot.y - source.y node.at = [pivot.x + dx, pivot.y + dy] node.type = source.type else fail ":reflect nodes must be preceeded by a :node with a :quadratic or :cubic before that." end when :node end @nodes << node end @closed = closed @options = self end |
Instance Attribute Details
- (Object) closed (readonly)
Returns the value of attribute closed
11 12 13 |
# File 'lib/vector_salad/standard_shapes/path.rb', line 11 def closed @closed end |
- (Object) nodes (readonly)
Returns the value of attribute nodes
11 12 13 |
# File 'lib/vector_salad/standard_shapes/path.rb', line 11 def nodes @nodes end |
- (Object) options Originally defined in class BasicShape
Returns the value of attribute options
Instance Method Details
- (Path) [](x, y)
Move the path absolutely.
66 67 68 69 70 71 72 73 74 75 76 77 78 |
# File 'lib/vector_salad/standard_shapes/path.rb', line 66 def [](x, y) # finds the top-left most point as if the path is in a bounding box top_left_point = nodes[0].at nodes.each do |node| top_left_point[0] = node.at[0] if node.at[0] < top_left_point[0] top_left_point[1] = node.at[1] if node.at[1] < top_left_point[1] end # finds the delta between the current top-left and new top-left coords dx = x - top_left_point[0] dy = y - top_left_point[1] # move path to the new delta move(dx, dy) end |
- (Path) flip(axis)
Flips the path on the specified axis.
111 112 113 114 115 116 117 118 119 |
# File 'lib/vector_salad/standard_shapes/path.rb', line 111 def flip(axis) x = axis == :y ? -1 : 1 y = axis == :x ? -1 : 1 Path.new( *to_path.nodes.map { |n| N.new(n.x * x, n.y * y, n.type) }, closed: @closed, **@options ) end |
- (Path) flip_x
Flips on the x axis.
94 95 96 |
# File 'lib/vector_salad/standard_shapes/path.rb', line 94 def flip_x flip(:x) end |
- (Path) flip_y
Flips on the y axis.
100 101 102 |
# File 'lib/vector_salad/standard_shapes/path.rb', line 100 def flip_y flip(:y) end |
- (Path) jitter(max, min: 0, fn: nil)
Jitter the position of nodes in a Path randomly.
169 170 171 172 173 174 175 176 177 178 179 |
# File 'lib/vector_salad/standard_shapes/path.rb', line 169 def jitter(max, min: 0, fn: nil) Path.new( *to_simple_path(fn).nodes.map do |n| r = Random.rand(min..max) a = Random.rand(0..Math::PI * 2) x = r * Math.cos(a) y = r * Math.sin(a) n.move(x, y) end, closed: @closed, **@options ) end |
- (Path) move(x, y)
Move the path relatively.
82 83 84 85 86 87 88 89 90 |
# File 'lib/vector_salad/standard_shapes/path.rb', line 82 def move(x, y) Path.new( *to_path.nodes.map do |node| node.move(x, y) end, closed: @closed, **@options ) end |
- (Path) rotate(angle)
Rotates the Path by the specified angle about the origin.
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 |
# File 'lib/vector_salad/standard_shapes/path.rb', line 128 def rotate(angle) theta = angle / 180.0 * Math::PI # http://stackoverflow.com/a/786508 # p'x = cos(theta) * (px-ox) - sin(theta) * (py-oy) + ox # p'y = sin(theta) * (px-ox) + cos(theta) * (py-oy) + oy Path.new( *to_path.nodes.map do |n| N.new( Math.cos(theta) * n.x - Math.sin(theta) * n.y, Math.sin(theta) * n.x + Math.cos(theta) * n.y, n.type ) end, closed: @closed, **@options ) end |
- (Path) scale(x_multiplier, y_multiplier = x_multiplier)
Scale a Path by multiplier about the origin. Supply just 1 multiplier to scale evenly, or x and y multipliers to stretch or squash the axies.
151 152 153 154 155 156 157 158 159 160 161 |
# File 'lib/vector_salad/standard_shapes/path.rb', line 151 def scale(x_multiplier, y_multiplier = x_multiplier) Path.new( *to_path.nodes.map do |n| N.new( n.x * x_multiplier, n.y * y_multiplier, n.type ) end, closed: @closed, **@options ) end |
- (Object) to_a
Return the nodes as an array of coordinates.
274 275 276 |
# File 'lib/vector_salad/standard_shapes/path.rb', line 274 def to_a nodes.map(&:at) end |
- (Object) to_bezier_path
Convert the complex path to a bezier path. This will convert any Spiro curve nodes into beziers.
188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 |
# File 'lib/vector_salad/standard_shapes/path.rb', line 188 def to_bezier_path path = to_path spiro = false flat_path = path.nodes.map do |n| spiro = true if spiro || [:g2, :g4, :left, :right].include?(n.type) [n.x, n.y, n.type] end if spiro flat_spline_path = Spiro.spiros_to_splines(flat_path, @closed) if flat_spline_path.nil? fail "Spiro failed, try different coordinates or using G2 nodes." else path = Path.new(*flat_spline_path.map do |n| N.new(n[0], n[1], n[2]) end, closed: @closed, **@options) end end path end |
- (Object) to_cubic_path
Convert the path into a cubic bezier path (no quadratics). This will convert any Spiro curve nodes into beziers and any quadratic beziers into cubics.
211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 |
# File 'lib/vector_salad/standard_shapes/path.rb', line 211 def to_cubic_path path = to_bezier_path.nodes cubic_path = [] quadratic_last = false path.each_index do |i| n = path[i] if quadratic_last n0 = path[i - 2] q = path[i - 1] # CP1 = QP0 + 2/3 * (QP1-QP0) # CP2 = QP2 + 2/3 * (QP1-QP2) third = 2 / 3.0 cubic_path << N.c( n0.x + third * (q.x - n0.x), n0.y + third * (q.y - n0.y) ) cubic_path << N.c( n.x + third * (q.x - n.x), n.y + third * (q.y - n.y) ) cubic_path << n quadratic_last = false elsif n.type == :quadratic quadratic_last = true else cubic_path << n end end Path.new(*cubic_path, closed: @closed, **@options) end |
- (Object) to_multi_path
Wrap the path in a multi_path.
269 270 271 |
# File 'lib/vector_salad/standard_shapes/path.rb', line 269 def to_multi_path MultiPath.new(self) end |
- (Object) to_path
Convert the path to a path (it returns self)
182 183 184 |
# File 'lib/vector_salad/standard_shapes/path.rb', line 182 def to_path self end |
- (Object) to_simple_path(*_)
Flatten any curves in the path into many small straight line segments.
245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 |
# File 'lib/vector_salad/standard_shapes/path.rb', line 245 def to_simple_path(*_) # convert bezier curves and spiro splines path = to_cubic_path.nodes nodes = [] path.each_index do |i| case path[i].type when :node if path[i - 1].type == :cubic curve = path[i - 3..i].map(&:at) nodes += VectorSalad::Interpolate.new.casteljau(curve) else nodes << path[i] end when :cubic else fail "Only :node and :cubic nodes in a path can be converted to a simple path, was #{path[i].type}." end end Path.new(*nodes, closed: @closed, **@options) end |
- (Object) to_svg
Export the shape to an svg string
37 38 39 40 41 42 43 |
# File 'lib/vector_salad/exporters/svg_exporter.rb', line 37 def to_svg svg = '<path d="' svg << to_svg_d_attribute svg << '"' svg << Exporters::SvgExporter.(@options) svg << "/>" end |