disp3D 0.2.1 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. data/Gemfile +1 -1
  2. data/README.md +56 -0
  3. data/VERSION +1 -1
  4. data/disp3D.gemspec +24 -20
  5. data/example/stl_viewer/document_ctrl.rb +2 -2
  6. data/example/stl_viewer/qt_widget_controller.rb +3 -7
  7. data/example/tutorial/03_CameraScene.rb +5 -5
  8. data/example/tutorial/04_Qt.rb +4 -4
  9. data/example/tutorial/05_Pick.rb +13 -13
  10. data/example/tutorial/06_FileParser.rb +3 -3
  11. data/example/tutorial/07_SceneGraph.rb +16 -4
  12. data/example/tutorial/08_SceneGraph2.rb +16 -15
  13. data/example/tutorial/09_Texture.rb +28 -0
  14. data/example/tutorial/10_Animation.rb +27 -0
  15. data/example/tutorial/10_AnimationQt.rb +53 -0
  16. data/example/tutorial/11_MultiView.rb +41 -0
  17. data/example/tutorial/12_Light.rb +65 -0
  18. data/example/tutorial/13_RectPick.rb +38 -0
  19. data/example/tutorial/14_LineRubberband.rb +43 -0
  20. data/example/{test → tutorial}/data/test.png +0 -0
  21. data/example/{test → tutorial}/data/test2.jpg +0 -0
  22. data/lib/camera.rb +8 -0
  23. data/lib/compass.rb +21 -14
  24. data/lib/disp3D.rb +3 -0
  25. data/lib/gl_view.rb +70 -40
  26. data/lib/glut_window.rb +24 -18
  27. data/lib/light.rb +35 -21
  28. data/lib/manipulator.rb +0 -7
  29. data/lib/node/node.rb +100 -105
  30. data/lib/node/node_arrows.rb +1 -1
  31. data/lib/node/node_collection.rb +63 -17
  32. data/lib/node/node_cone.rb +49 -0
  33. data/lib/node/node_coord.rb +26 -11
  34. data/lib/node/node_leaf.rb +17 -11
  35. data/lib/node/node_lines.rb +1 -1
  36. data/lib/node/node_points.rb +2 -1
  37. data/lib/node/node_polylines.rb +1 -1
  38. data/lib/node/node_rectangle.rb +9 -4
  39. data/lib/node/node_sphere.rb +28 -0
  40. data/lib/node/node_tea_pod.rb +4 -4
  41. data/lib/node/node_text.rb +2 -2
  42. data/lib/node/node_tris.rb +1 -1
  43. data/lib/node/node_workplane.rb +33 -1
  44. data/lib/picker.rb +157 -3
  45. data/lib/qt_widget_gl.rb +24 -11
  46. data/lib/scene_graph.rb +3 -3
  47. data/test/node/test_node.rb +230 -0
  48. data/test/node/test_node_collection.rb +99 -79
  49. data/test/test_picker.rb +6 -6
  50. metadata +28 -24
  51. data/README.rdoc +0 -63
  52. data/example/test/capture_test.rb +0 -16
  53. data/example/test/dsl_test.rb +0 -46
  54. data/example/test/texture_test.rb +0 -39
  55. data/test/data/binary_test.stl +0 -0
  56. data/test/data/bunny-flatfoot.stl +0 -0
@@ -0,0 +1,49 @@
1
+ require 'disp3D'
2
+
3
+ module Disp3D
4
+ class NodeCone < NodeLeaf
5
+ attr_for_disp :radius
6
+ attr_for_disp :height
7
+ attr_for_disp :base_point
8
+ attr_for_disp :direction
9
+
10
+ def initialize(geom=nil, name=nil)
11
+ super
12
+ @radius = 1.0
13
+ @height = 3.0
14
+ @slices = 10
15
+ @stacks = 10
16
+ @base_point = Vector3.new()
17
+ @direction = Vector3.new(0,0,1)
18
+ end
19
+
20
+ def box
21
+ rtn_box = Box.new(Vector3.new(-@radius,-@radius,0), Vector3.new(@radius,@radius,@height))
22
+ rtn_box = box_transform(rtn_box)
23
+ rot = calc_rotate_from_direction
24
+ rtn_box = rtn_box.rotate(rot) if(!rot.nil?)
25
+ rtn_box = rtn_box.translate(@base_point)
26
+ return rtn_box
27
+ end
28
+
29
+ protected
30
+ def draw_element
31
+ GL.PushMatrix()
32
+ rot = calc_rotate_from_direction
33
+ GL.Translate(@base_point.x, @base_point.y, @base_point.z) if(@base_point)
34
+ GL.MultMatrix(rot.to_array) if(!rot.nil?)
35
+ draw_color
36
+ GLUT.SolidCone(@radius, @height, @slices, @stacks)
37
+ GL.PopMatrix()
38
+ end
39
+
40
+ private
41
+ def calc_rotate_from_direction
42
+ return nil if(@direction.length < 1e-2)
43
+ base_direction = Vector3.new(0,0,1)
44
+ angle = base_direction.angle(@direction)
45
+ axis = base_direction.cross(@direction)
46
+ rotate = Quat.from_axis(axis, angle)
47
+ end
48
+ end
49
+ end
@@ -2,26 +2,41 @@ require 'disp3D'
2
2
 
3
3
  module Disp3D
4
4
  class NodeCoord < NodeCollection
5
- def initialize(base_position = Vector3.new(), length = 1)
6
- super()
5
+ attr_for_disp :length
6
+ attr_for_disp :base_position
7
+
8
+ def initialize(name = nil, base_position = Vector3.new(), length = 1)
9
+ super(name)
7
10
  @length = length
8
11
  @base_position = base_position
9
12
  @x_color = [1,0,0,1]
10
13
  @y_color = [0,1,0,1]
11
14
  @z_color = [0,0,1,1]
12
15
 
16
+ @x_node = NodeArrows.new(geom[0])
17
+ @y_node = NodeArrows.new(geom[1])
18
+ @z_node = NodeArrows.new(geom[2])
19
+ @x_node.colors = @x_color
20
+ @y_node.colors = @y_color
21
+ @z_node.colors = @z_color
22
+ add(@x_node)
23
+ add(@y_node)
24
+ add(@z_node)
25
+ end
26
+
27
+ def update_for_display
28
+ @x_node.geom = geom[0]
29
+ @y_node.geom = geom[1]
30
+ @z_node.geom = geom[2]
31
+ super
32
+ end
33
+
34
+ private
35
+ def geom
13
36
  x_geom = FiniteLine.new(@base_position, @base_position + Vector3.new(@length,0,0))
14
37
  y_geom = FiniteLine.new(@base_position, @base_position + Vector3.new(0,@length,0))
15
38
  z_geom = FiniteLine.new(@base_position, @base_position + Vector3.new(0,0,@length))
16
- x_node = NodeArrows.new(x_geom)
17
- y_node = NodeArrows.new(y_geom)
18
- z_node = NodeArrows.new(z_geom)
19
- x_node.colors = @x_color
20
- y_node.colors = @y_color
21
- z_node.colors = @z_color
22
- add(x_node)
23
- add(y_node)
24
- add(z_node)
39
+ return [x_geom, y_geom, z_geom]
25
40
  end
26
41
  end
27
42
  end
@@ -3,9 +3,10 @@ require 'disp3D'
3
3
 
4
4
  module Disp3D
5
5
  class NodeLeaf < Node
6
- attr_accessor :material_color
7
- attr_accessor :colors
8
- attr_accessor :shininess
6
+ attr_for_disp :material_color
7
+ attr_for_disp :colors
8
+ attr_for_disp :shininess
9
+ attr_for_disp :geom
9
10
 
10
11
  def initialize(geometry = nil, name = nil)
11
12
  Util3D.check_arg_type(Symbol, name, true)
@@ -17,11 +18,11 @@ module Disp3D
17
18
  @shininess = nil
18
19
  @shininess_default = 32.0
19
20
 
20
- @list_created = false
21
+ @dislay_list_created = nil
21
22
  end
22
23
 
23
- def draw
24
- draw_inner(self.method(:draw_element))
24
+ def draw currnet_view
25
+ draw_inner(self.method(:draw_element), currnet_view)
25
26
  end
26
27
 
27
28
  def box
@@ -38,8 +39,12 @@ module Disp3D
38
39
  return box_transform(rtn_box)
39
40
  end
40
41
 
42
+ def update_for_display
43
+ @dislay_list_created = nil
44
+ end
45
+
41
46
  protected
42
- def draw_inner(draw_element)
47
+ def draw_inner(draw_element, current_view)
43
48
  # colors���ݒ肳��Ă�����A�������D��I�ɕ\������B���̍ہA���C�e�B���O�̓I�t�ɂ���K�v������
44
49
  if(@colors.nil?)
45
50
  GL.Enable(GL::GL_LIGHTING)
@@ -58,15 +63,16 @@ protected
58
63
  GL.Disable(GL::GL_LIGHTING)
59
64
  end
60
65
 
61
- if(@list_created == false)
62
- @list_created = true
63
- GL.NewList(@instance_id, GL::COMPILE_AND_EXECUTE)
66
+ if(@dislay_list_created.nil? || @dislay_list_created[current_view] == nil)
67
+ @dislay_list_created ||= Hash.new()
68
+ @dislay_list_created[current_view] = true
69
+ GL.NewList(self.instance_id, GL::COMPILE_AND_EXECUTE)
64
70
  pre_draw # matrix manipulation
65
71
  draw_element.call
66
72
  post_draw # matrix manipulation
67
73
  GL.EndList()
68
74
  else
69
- GL.CallList(@instance_id)
75
+ GL.CallList(self.instance_id)
70
76
  end
71
77
  end
72
78
 
@@ -2,7 +2,7 @@ require 'disp3D'
2
2
 
3
3
  module Disp3D
4
4
  class NodeLines < NodeLeaf
5
- attr_accessor :width
5
+ attr_for_disp :width
6
6
 
7
7
  def initialize(geom, name = nil)
8
8
  Util3D.check_arg_type(Symbol, name, true)
@@ -2,7 +2,7 @@ require 'disp3D'
2
2
 
3
3
  module Disp3D
4
4
  class NodePoints < NodeLeaf
5
- attr_accessor :size
5
+ attr_for_disp :size
6
6
 
7
7
  def initialize(geom, name = nil)
8
8
  Util3D.check_arg_type(Symbol, name, true)
@@ -16,6 +16,7 @@ protected
16
16
  if(@geom)
17
17
  GL.PointSize(@size)
18
18
  draw_color
19
+ GL.Enable(GL::POINT_SMOOTH)
19
20
  GL.Begin(GL::POINTS)
20
21
  if(@geom.kind_of?(GMath3D::Vector3))
21
22
  GL.Vertex( @geom.x, @geom.y, @geom.z )
@@ -2,7 +2,7 @@ require 'disp3D'
2
2
 
3
3
  module Disp3D
4
4
  class NodePolylines < NodeLeaf
5
- attr_accessor :width
5
+ attr_for_disp :width
6
6
 
7
7
  def initialize(geom, name = nil)
8
8
  Util3D.check_arg_type(Symbol, name, true)
@@ -3,18 +3,23 @@ require 'disp3D'
3
3
  module Disp3D
4
4
  class NodeRectangle < NodeLeaf
5
5
  # texture_image should be rmagick image
6
- def initialize(geom, name = nil, texture_image = nil)
6
+ def initialize(geom, name = nil)
7
7
  Util3D.check_arg_type(Symbol, name, true)
8
8
  Util3D.check_arg_type(GMath3D::Rectangle, geom, false, false) #Array is not allowed
9
- super(geom, name)
10
- @image = texture_image
11
- initialize_texture
9
+ super
12
10
  end
13
11
 
14
12
  def box
15
13
  return @geom.box
16
14
  end
17
15
 
16
+ def image=(texture_image)
17
+ Util3D.check_arg_type(Magick::Image, texture_image, true)
18
+ @image = texture_image
19
+ initialize_texture
20
+ update_for_display
21
+ end
22
+
18
23
  protected
19
24
  def draw_element
20
25
  if(@geom && @geom.kind_of?(GMath3D::Rectangle))
@@ -0,0 +1,28 @@
1
+ require 'disp3D'
2
+
3
+ module Disp3D
4
+ class NodeSphere < NodeLeaf
5
+ attr_for_disp :radius
6
+ attr_for_disp :center
7
+
8
+ def initialize(geom=nil, name=nil)
9
+ super
10
+ @radius = 1.0
11
+ @center = Vector3.new()
12
+ @slices = 10
13
+ @stacks = 10
14
+ end
15
+
16
+ def box
17
+ rtn_box = Box.new(Vector3.new(-@radius,-@radius,-@radius)+@center, Vector3.new(@radius,@radius,@radius)+@center)
18
+ return box_transform(rtn_box)
19
+ end
20
+
21
+ protected
22
+ def draw_element
23
+ draw_color
24
+ GL.Translate(@center.x, @center.y, @center.z) if(@center)
25
+ GLUT.SolidSphere(@radius, @slices, @stacks)
26
+ end
27
+ end
28
+ end
@@ -2,11 +2,11 @@ require 'disp3D'
2
2
 
3
3
  module Disp3D
4
4
  class NodeTeaPod < NodeLeaf
5
- attr_accessor :size
5
+ attr_for_disp :size
6
6
 
7
- def initialize(geom = nil, name = nil, size)
8
- super(geom, name)
9
- @size = size
7
+ def initialize(geom=nil, name=nil)
8
+ super
9
+ @size = 1.0
10
10
  end
11
11
 
12
12
  def box
@@ -2,8 +2,8 @@ require 'disp3D'
2
2
 
3
3
  module Disp3D
4
4
  class NodeText < NodeLeaf
5
- attr_accessor :text
6
- attr_accessor :position
5
+ attr_for_disp :text
6
+ attr_for_disp :position
7
7
 
8
8
  def initialize(position, name = nil, text = nil)
9
9
  Util3D.check_arg_type(Vector3, position)
@@ -2,7 +2,7 @@ require 'disp3D'
2
2
 
3
3
  module Disp3D
4
4
  class NodeTris < NodeLeaf
5
- attr_accessor :normal_mode
5
+ attr_for_disp :normal_mode
6
6
 
7
7
  NORMAL_EACH_FACE = 0
8
8
  NORMAL_EACH_VERTEX = 1
@@ -2,13 +2,15 @@ require 'disp3D'
2
2
 
3
3
  module Disp3D
4
4
  class NodeWorkplane < NodeLeaf
5
- attr_accessor :length
5
+ attr_for_disp :length
6
+ attr_for_disp :grid
6
7
 
7
8
  def initialize(geom, name = nil)
8
9
  Util3D.check_arg_type(Symbol, name, true)
9
10
  Util3D.check_arg_type(GMath3D::Plane, geom, false)
10
11
  super
11
12
  @length = 1000
13
+ @grid = nil
12
14
  end
13
15
 
14
16
  def box
@@ -25,12 +27,42 @@ protected
25
27
  GL.Vertex( vertex.x, vertex.y, vertex.z )
26
28
  end
27
29
  GL.End()
30
+
31
+ GL.Color(0,0,0,1)
32
+ GL.LineWidth(3)
33
+ if(!@grid.nil?)
34
+ line_vertices_ary = grid_lines_vertices
35
+ GL.Begin(GL::LINES)
36
+ line_vertices_ary.each do |vertex|
37
+ GL.Vertex( vertex.x, vertex.y, vertex.z )
38
+ end
39
+ GL.End()
40
+ end
28
41
  else
29
42
  raise
30
43
  end
31
44
  end
32
45
 
33
46
  private
47
+ def grid_lines_vertices
48
+ count = (@length/@grid) + 1
49
+ u_vec = @geom.normal
50
+ u_vec = @geom.normal.arbitrary_orthogonal
51
+ v_vec = @geom.normal.cross(u_vec)
52
+ vertices = Array.new(count*4)
53
+ count.times.each do |idx|
54
+ point_tmp = @geom.base_point + u_vec * @grid * (idx - count/2)
55
+ vertices[2*idx] = point_tmp + v_vec * @length/2.0
56
+ vertices[2*idx+1] = point_tmp - v_vec * @length/2.0
57
+ end
58
+ count.times.each do |idx|
59
+ point_tmp = @geom.base_point + v_vec * @grid * (idx - count/2)
60
+ vertices[2*idx+2*count] = point_tmp + u_vec * @length/2.0
61
+ vertices[2*idx+2*count+1] = point_tmp - u_vec * @length/2.0
62
+ end
63
+ return vertices
64
+ end
65
+
34
66
  def calc_vertices
35
67
  tan_vec1 = @geom.normal.arbitrary_orthogonal
36
68
  tan_vec2 = @geom.normal.cross(tan_vec1)
data/lib/picker.rb CHANGED
@@ -2,12 +2,106 @@ require 'disp3D'
2
2
 
3
3
  module Disp3D
4
4
  class Picker
5
+ attr_reader :pick_mode
6
+ attr_accessor :max_select_count
7
+
8
+ # pick modes
9
+ NONE = 0
10
+ POINT_PICK = 1
11
+ LINE_PICK = 2
12
+ RECT_PICK = 3
13
+
5
14
  def initialize(view)
6
15
  @view = view
7
16
  @max_select_count = 100
17
+ @pick_mode = NONE
18
+ end
19
+
20
+ def point_pick(x,y)
21
+ pick(x, y)
22
+ end
23
+
24
+ def post_picked(&block)
25
+ @post_pick_process = block
26
+ end
27
+
28
+ def start_point_pick(&block)
29
+ @pick_mode = POINT_PICK
30
+ post_picked(&block) if(block_given?)
31
+ end
32
+
33
+ def start_line_pick(&block)
34
+ @pick_mode = LINE_PICK
35
+ post_picked(&block) if(block_given?)
36
+ end
37
+
38
+ def start_rect_pick(&block)
39
+ @pick_mode = RECT_PICK
40
+ post_picked(&block) if(block_given?)
41
+ end
42
+
43
+ def end_pick
44
+ @pick_mode = NONE
45
+ end
46
+
47
+ # users donot need to use this.
48
+ def mouse(button,state,x,y)
49
+ return if(@pick_mode == NONE)
50
+ if(button == GLUT::GLUT_LEFT_BUTTON && state == GLUT::GLUT_DOWN)
51
+ if(@pick_mode == POINT_PICK)
52
+ picked_result = point_pick(x,y)
53
+ @post_pick_process.call(picked_result) if(!@post_pick_process.nil?)
54
+ return
55
+ elsif(@pick_mode == LINE_PICK)
56
+ @line_start_result = point_pick(x,y)
57
+ end
58
+ @last_pos = Vector3.new(x, y)
59
+ @rubber_band = false
60
+ elsif(button == GLUT::GLUT_LEFT_BUTTON && state == GLUT::GLUT_UP)
61
+ if(@pick_mode == LINE_PICK)
62
+ draw_rubber_band(FiniteLine.new(@last_pos, @save_pos)) # delete rubber band
63
+ line_end_result = pick(x, y)
64
+ @post_pick_process.call(@line_start_result, line_end_result) if(!@post_pick_process.nil?)
65
+ @line_start_result = nil
66
+ elsif(@pick_mode == RECT_PICK)
67
+ draw_rubber_band(Box.new(@last_pos, @save_pos)) # delete rubber band
68
+ pick_x = (x + @last_pos.x)/2
69
+ pick_y = (y + @last_pos.y)/2
70
+ width = (x - @last_pos.x).abs
71
+ height = (y - @last_pos.y).abs
72
+ picked_result = pick(pick_x, pick_y, width, height)
73
+ @post_pick_process.call(picked_result) if(!@post_pick_process.nil?)
74
+ end
75
+ @save_pos = nil
76
+ @last_pos = nil
77
+ @rubber_band = false
78
+ end
79
+ end
80
+
81
+ # users donot need to use this.
82
+ # return if picking process is in progress?
83
+ def motion(x,y)
84
+ return false if(@pick_mode == NONE || @last_pos.nil?)
85
+ if(@pick_mode == LINE_PICK)
86
+ if(@rubber_band)
87
+ draw_rubber_band([FiniteLine.new(@last_pos, @save_pos), FiniteLine.new(@last_pos, Vector3.new(x,y))])
88
+ else
89
+ draw_rubber_band(FiniteLine.new(@last_pos, Vector3.new(x,y)))
90
+ end
91
+ elsif(@pick_mode == RECT_PICK)
92
+ if(@rubber_band)
93
+ draw_rubber_band([Box.new(@last_pos, @save_pos), Box.new(@last_pos, Vector3.new(x,y))])
94
+ else
95
+ draw_rubber_band(Box.new(@last_pos, Vector3.new(x,y)))
96
+ end
97
+ end
98
+ @save_pos = Vector3.new(x, y)
99
+ @rubber_band=true
100
+ return true
8
101
  end
9
102
 
10
- def pick(x,y)
103
+ private
104
+ def pick(x, y, width = 1, height = 1)
11
105
  vp = GL.GetIntegerv(GL::VIEWPORT)
12
106
 
13
107
  selection = GL.SelectBuffer(@max_select_count)
@@ -19,7 +113,7 @@ module Disp3D
19
113
  GL.PushMatrix()
20
114
  GL.LoadIdentity()
21
115
 
22
- GLU.PickMatrix(x, vp[3] - y - 1, 1, 1, vp)
116
+ GLU.PickMatrix(x, vp[3] - y - 1, width, height, vp)
23
117
  @view.camera.set_screen(vp[2], vp[3])
24
118
 
25
119
  GL.MatrixMode(GL::MODELVIEW)
@@ -47,7 +141,7 @@ module Disp3D
47
141
  node_info = Array.new()
48
142
  count.times do | j |
49
143
  path_id = data[4*i+3 + j]
50
- picked_node = Node.find_node_by_path_id(path_id)
144
+ picked_node = NodePathDB.find_by_path_id(path_id)
51
145
  if (picked_node != nil)
52
146
  parent_node = picked_node.parents.find {|parent| parent.include?(path_id) }
53
147
  picked_node_info = PathInfo.new(picked_node, parent_node, path_id)
@@ -58,5 +152,65 @@ module Disp3D
58
152
  end
59
153
  return picked_result
60
154
  end
155
+
156
+ def pre_rubber_band_process
157
+ dmy,dmy, w,h = GL.GetIntegerv(GL::VIEWPORT)
158
+ @view.camera.set_projection_for_camera_scene
159
+ GL.MatrixMode(GL::GL_MODELVIEW)
160
+ GL.PushMatrix()
161
+ GL.LoadIdentity()
162
+ GLU.LookAt(0, 0, 1, 0, 0, 0, 0, 1, 0)
163
+
164
+ GL.Enable(GL::COLOR_LOGIC_OP)
165
+ GL.LogicOp(GL::INVERT)
166
+ GL.DrawBuffer( GL::FRONT )
167
+
168
+ GL.LineWidth(2)
169
+
170
+ return w,h
171
+ end
172
+
173
+ def post_rubber_band_process
174
+ GL.Flush()
175
+
176
+ GL.Disable(GL::COLOR_LOGIC_OP)
177
+ GL.LogicOp(GL::COPY)
178
+
179
+ GL.PopMatrix()
180
+ end
181
+
182
+ # [Input]
183
+ # _elemnets_ should be FiniteLine or Box or Array of them.
184
+ def draw_rubber_band(elements)
185
+ w,h = pre_rubber_band_process
186
+ draw_rubber_band_lines_inner(elements, w, h)
187
+ post_rubber_band_process
188
+ end
189
+
190
+ def screen_to_rubberband( vec, w , h )
191
+ [-w/2 + vec.x, h/2 - vec.y - 1]
192
+ end
193
+
194
+ def draw_rubber_band_lines_inner(elements, w, h)
195
+ return if(elements.nil?)
196
+ if(elements.kind_of?(FiniteLine))
197
+ GL.Begin(GL::LINES)
198
+ GL.Vertex(screen_to_rubberband( elements.start_point, w, h ))
199
+ GL.Vertex(screen_to_rubberband( elements.end_point, w, h ))
200
+ GL.End()
201
+ elsif(elements.kind_of?(Box))
202
+ box = elements
203
+ lines = Array.new(4)
204
+ lines[0] = FiniteLine.new(Vector3.new(box.min_point.x, box.min_point.y), Vector3.new(box.max_point.x, box.min_point.y))
205
+ lines[1] = FiniteLine.new(Vector3.new(box.max_point.x, box.min_point.y), Vector3.new(box.max_point.x, box.max_point.y))
206
+ lines[2] = FiniteLine.new(Vector3.new(box.max_point.x, box.max_point.y), Vector3.new(box.min_point.x, box.max_point.y))
207
+ lines[3] = FiniteLine.new(Vector3.new(box.min_point.x, box.max_point.y), Vector3.new(box.min_point.x, box.min_point.y))
208
+ draw_rubber_band_lines_inner(lines, w, h)
209
+ elsif(elements.kind_of?(Array))
210
+ elements.each do |item|
211
+ draw_rubber_band_lines_inner(item, w, h)
212
+ end
213
+ end
214
+ end
61
215
  end
62
216
  end