glimr 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. data/lib/glimr.rb +2 -0
  2. data/lib/glimr/configurable.rb +37 -0
  3. data/lib/glimr/default_theme/button_bg.png +0 -0
  4. data/lib/glimr/default_theme/button_bg_down.png +0 -0
  5. data/lib/glimr/default_theme/button_cover.png +0 -0
  6. data/lib/glimr/default_theme/button_cover_down.png +0 -0
  7. data/lib/glimr/default_theme/button_focus.png +0 -0
  8. data/lib/glimr/default_theme/checkbox_bg.png +0 -0
  9. data/lib/glimr/default_theme/checkbox_checked_bg.png +0 -0
  10. data/lib/glimr/default_theme/font.ttf +0 -0
  11. data/lib/glimr/default_theme/hscroller_bg.png +0 -0
  12. data/lib/glimr/default_theme/radiobutton_bg.png +0 -0
  13. data/lib/glimr/default_theme/radiobutton_checked_bg.png +0 -0
  14. data/lib/glimr/default_theme/resizer_down.png +0 -0
  15. data/lib/glimr/default_theme/resizer_up.png +0 -0
  16. data/lib/glimr/default_theme/scroll_down_down.png +0 -0
  17. data/lib/glimr/default_theme/scroll_down_up.png +0 -0
  18. data/lib/glimr/default_theme/scroll_hknob_down.png +0 -0
  19. data/lib/glimr/default_theme/scroll_hknob_up.png +0 -0
  20. data/lib/glimr/default_theme/scroll_left_down.png +0 -0
  21. data/lib/glimr/default_theme/scroll_left_up.png +0 -0
  22. data/lib/glimr/default_theme/scroll_right_down.png +0 -0
  23. data/lib/glimr/default_theme/scroll_right_up.png +0 -0
  24. data/lib/glimr/default_theme/scroll_up_down.png +0 -0
  25. data/lib/glimr/default_theme/scroll_up_up.png +0 -0
  26. data/lib/glimr/default_theme/scroll_vknob_down.png +0 -0
  27. data/lib/glimr/default_theme/scroll_vknob_up.png +0 -0
  28. data/lib/glimr/default_theme/text_cursor.png +0 -0
  29. data/lib/glimr/default_theme/text_cursor_insert.png +0 -0
  30. data/lib/glimr/default_theme/text_input_bg.png +0 -0
  31. data/lib/glimr/default_theme/vscroller_bg.png +0 -0
  32. data/lib/glimr/event.rb +41 -0
  33. data/lib/glimr/eventlistener.rb +209 -0
  34. data/lib/glimr/layoutable.rb +520 -0
  35. data/lib/glimr/renderer.rb +2 -0
  36. data/lib/glimr/renderer/camera.rb +63 -0
  37. data/lib/glimr/renderer/geometry.rb +194 -0
  38. data/lib/glimr/renderer/glutwindow.rb +387 -0
  39. data/lib/glimr/renderer/light.rb +43 -0
  40. data/lib/glimr/renderer/material.rb +66 -0
  41. data/lib/glimr/renderer/model.rb +103 -0
  42. data/lib/glimr/renderer/orthoprojection.rb +21 -0
  43. data/lib/glimr/renderer/raw.rb +34 -0
  44. data/lib/glimr/renderer/sceneobject.rb +279 -0
  45. data/lib/glimr/renderer/shader.rb +14 -0
  46. data/lib/glimr/renderer/texture.rb +280 -0
  47. data/lib/glimr/renderer/transform.rb +322 -0
  48. data/lib/glimr/renderer/viewport.rb +349 -0
  49. data/lib/glimr/renderer_core.rb +10 -0
  50. data/lib/glimr/util.rb +247 -0
  51. data/lib/glimr/widget.rb +87 -0
  52. data/lib/glimr/widgets.rb +37 -0
  53. data/lib/glimr/widgets/button.rb +277 -0
  54. data/lib/glimr/widgets/checkbox.rb +82 -0
  55. data/lib/glimr/widgets/container.rb +84 -0
  56. data/lib/glimr/widgets/image.rb +82 -0
  57. data/lib/glimr/widgets/label.rb +91 -0
  58. data/lib/glimr/widgets/layout.rb +227 -0
  59. data/lib/glimr/widgets/list.rb +28 -0
  60. data/lib/glimr/widgets/radiogroup.rb +118 -0
  61. data/lib/glimr/widgets/resizer.rb +31 -0
  62. data/lib/glimr/widgets/scrollable_container.rb +67 -0
  63. data/lib/glimr/widgets/scrollbar.rb +496 -0
  64. data/lib/glimr/widgets/stretchable_image.rb +135 -0
  65. data/lib/glimr/widgets/text_editor.rb +349 -0
  66. data/tests/assets/datatowers_crop.jpg +0 -0
  67. data/tests/assets/download_progress_meter.png +0 -0
  68. data/tests/assets/metalwing2.png +0 -0
  69. data/tests/assets/redhairgreeneyes3.jpg +0 -0
  70. data/tests/demo_apps/spinning_ruby.rb +37 -0
  71. data/tests/integration_tests/run_all.rb +8 -0
  72. data/tests/integration_tests/test_button.rb +22 -0
  73. data/tests/integration_tests/test_checkbox.rb +21 -0
  74. data/tests/integration_tests/test_container.rb +22 -0
  75. data/tests/integration_tests/test_label.rb +12 -0
  76. data/tests/integration_tests/test_layout.rb +43 -0
  77. data/tests/integration_tests/test_radiogroup.rb +16 -0
  78. data/tests/integration_tests/test_renderer.rb +44 -0
  79. data/tests/integration_tests/test_renderer2.rb +36 -0
  80. data/tests/integration_tests/test_scrollable_container.rb +34 -0
  81. data/tests/integration_tests/test_scrollbar.rb +20 -0
  82. data/tests/integration_tests/test_stretchable_image.rb +34 -0
  83. data/tests/integration_tests/test_text_input.rb +20 -0
  84. data/tests/integration_tests/test_zsort.rb +18 -0
  85. data/tests/unit_tests/test_button.rb +93 -0
  86. data/tests/unit_tests/test_checkbox.rb +35 -0
  87. data/tests/unit_tests/test_label.rb +36 -0
  88. data/tests/unit_tests/test_layout.rb +229 -0
  89. data/tests/unit_tests/test_widget.rb +3 -0
  90. metadata +139 -0
@@ -0,0 +1,349 @@
1
+ require 'glimr/renderer/sceneobject'
2
+ require 'glimr/renderer/material'
3
+ require 'glimr/renderer/texture'
4
+ require 'glimr/renderer/camera'
5
+ require 'glimr/renderer/transform'
6
+
7
+
8
+ module GlimR
9
+
10
+ # Viewport is a scene graph root node and takes care of
11
+ # drawing its subtree and mapping mouse events to
12
+ # its child nodes.
13
+ #
14
+ class Viewport < SceneObject
15
+
16
+ attr_accessor :width, :height, :x, :y, :camera
17
+ touching_accessor :clear_depth_buffer, :background
18
+
19
+ def default_config
20
+ super.merge(
21
+ :click_time => 0.3,
22
+ :click_radius => 4,
23
+ :background => nil,
24
+ :clear_depth_buffer => false,
25
+ :camera => Camera.new,
26
+ :x => 0, :y => 0, :width => 1, :height => 1
27
+ )
28
+ end
29
+
30
+ def initialize(*a,&b)
31
+ @down_x = 0
32
+ @down_y = 0
33
+ @old_collisions = []
34
+ @mouse_down_collisions = []
35
+ @drag_targets = {}
36
+ super
37
+ add_event_listener(:mouse_move, :mouse_move_handler, true)
38
+ add_event_listener(:click, :click_handler, true)
39
+ add_event_listener(:mouse_down, :mouse_down_handler, true)
40
+ add_event_listener(:mouse_up, :mouse_up_handler, true)
41
+ add_event_listener(:resize, :resize_handler, true)
42
+ end
43
+
44
+ # Acts as root node for dispatched events
45
+ def event_root
46
+ true
47
+ end
48
+
49
+ def min_x
50
+ @x
51
+ end
52
+
53
+ def max_x
54
+ viewport_width
55
+ end
56
+
57
+ def min_y
58
+ @y
59
+ end
60
+
61
+ def max_y
62
+ viewport_height
63
+ end
64
+
65
+ def viewport_x
66
+ if parent
67
+ abs_x = @x + parent.absolute_transform.flatten[12]
68
+ else
69
+ @x
70
+ end
71
+ end
72
+
73
+ def viewport_y
74
+ if parent
75
+ abs_y = @y + parent.absolute_transform.flatten[13]
76
+ parent.viewport.height - abs_y - @height
77
+ else
78
+ -@y
79
+ end
80
+ end
81
+
82
+ def viewport_width
83
+ if parent
84
+ @width * parent.absolute_transform.flatten[0]
85
+ else
86
+ @width
87
+ end
88
+ end
89
+
90
+ def viewport_height
91
+ if parent
92
+ @height * parent.absolute_transform.flatten[5]
93
+ else
94
+ @height
95
+ end
96
+ end
97
+
98
+ def resize_handler(o,e)
99
+ if !parent
100
+ self.width = e.width
101
+ self.height = e.height
102
+ end
103
+ end
104
+
105
+ # Tracks mouse pointer position and sends hover, mouse_out, and mouse_over
106
+ # -events to nodes interacting with the mouse pointer.
107
+ #
108
+ def mouse_move_handler(o,e)
109
+ params = e.params.clone
110
+ params[:x] += x
111
+ params[:y] += y
112
+ if Vector[@down_x - params[:x], @down_y - params[:y]].r > @click_radius
113
+ @click_valid = false
114
+ end
115
+ duc = drawables_under_cursor(e)
116
+ collisions = duc.map{|rx,ry,c| c }
117
+ (@old_collisions - collisions).each{|oc|
118
+ evt = Event.new(:mouse_out,
119
+ params.merge(:sender => self, :target => oc))
120
+ oc.dispatch_event(evt)
121
+ }
122
+ (collisions - @old_collisions).each{|oc|
123
+ evt = Event.new(:mouse_over,
124
+ params.merge(:sender => self, :target => oc))
125
+ oc.dispatch_event(evt)
126
+ }
127
+ @old_collisions = collisions
128
+ children.each{|c|
129
+ c.multicast_event(Event.new(:mouse_move, params))
130
+ }
131
+ @drag_targets.each{|k,t|
132
+ t.dispatch_event(Event.new(:drag, params.merge(:button => k)))
133
+ }
134
+ e.phase = :bubble
135
+ end
136
+
137
+ def mouse_down_handler(o,e)
138
+ params = e.params.clone
139
+ params[:x] += x
140
+ params[:y] += y
141
+ @down_x = params[:x]
142
+ @down_y = params[:y]
143
+ @click_valid = true
144
+ @down_time = Time.now.to_f
145
+ collisions = drawables_under_cursor(e)
146
+ @mouse_down_collisions = collisions.map{|rx,ry,c| c }
147
+ collisions.each{|rx, ry, c|
148
+ evt = Event.new(:mouse_down,
149
+ e.params.merge(:sender => self, :target => c, :rx => rx, :ry => ry))
150
+ c.dispatch_event(evt)
151
+ }
152
+ if collisions.first
153
+ @drag_targets[e.button] = collisions.first[2]
154
+ else
155
+ @drag_targets.delete(e.button)
156
+ end
157
+ e.phase = :bubble
158
+ end
159
+
160
+ def mouse_up_handler(o,e)
161
+ params = e.params.clone
162
+ params[:x] += x
163
+ params[:y] += y
164
+ if Vector[@down_x - params[:x], @down_y - params[:y]].r > @click_radius or (Time.now.to_f - @down_time) > @click_time
165
+ @click_valid = false
166
+ end
167
+ children.each{|c|
168
+ c.multicast_event(Event.new(:mouse_up, params))
169
+ }
170
+ if @click_valid or @drag_targets[e.button]
171
+ collisions = drawables_under_cursor(e)
172
+ if @click_valid
173
+ collisions.each{|rx,ry,c|
174
+ if @mouse_down_collisions.include? c
175
+ evt = Event.new(:click, params.merge(:sender => self, :rx => rx, :ry => ry))
176
+ c.dispatch_event(evt)
177
+ end
178
+ break
179
+ }
180
+ end
181
+ if @drag_targets[e.button]
182
+ if collisions.first
183
+ @drag_targets[e.button].dispatch_event(
184
+ Event.new(
185
+ :drop,
186
+ params.merge(:drop_target => collisions.first[2], :rx => collisions.first[0], :ry => collisions.first[1])
187
+ )
188
+ )
189
+ end
190
+ @drag_targets.delete(e.button)
191
+ end
192
+ end
193
+ @mouse_down_collisions.clear
194
+ e.phase = :bubble
195
+ end
196
+
197
+ def click_handler(o,e)
198
+ e.phase = :bubble
199
+ end
200
+
201
+ def drawables_under_cursor(e)
202
+ collapsed = (@drawables - [self])
203
+ collisions = collapsed.map{|d|
204
+ collides?( d, e.x, e.y ) if d.visible
205
+ }.compact
206
+ collisions.sort_by{|c| -c[2].absolute_transform.flatten[14] } # reverse z-sort
207
+ end
208
+
209
+ # Tests if the coordinates ex and ey fall within the rectangle of drawable d.
210
+ def collides?( d, ex, ey )
211
+ MatrixMode MODELVIEW
212
+ PushMatrix()
213
+ LoadIdentity()
214
+ MultMatrix(d.absolute_transform_for_drawing)
215
+ Translate(-ex,-ey,0)
216
+ camatrix = GetDoublev(MODELVIEW_MATRIX)
217
+ PopMatrix()
218
+ if (camatrix[3][0]+d.min_x+d.max_x >= d.min_x and camatrix[3][0]+d.min_x+d.max_x <= d.max_x and
219
+ camatrix[3][1]+d.min_y+d.max_y >= d.min_y and camatrix[3][1]+d.min_y+d.max_y <= d.max_y)
220
+ [-camatrix[3][0], -camatrix[3][1], d] # return relative x & y -coordinates and the drawable
221
+ else
222
+ nil
223
+ end
224
+ end
225
+
226
+ # Draws the viewport drawables using painter's algorithm.
227
+ def render
228
+ apply
229
+ end
230
+
231
+ # The absolute state of a viewport is empty starter state.
232
+ # This to provide the SceneObject absolute state methods
233
+ # a starting point.
234
+ #
235
+ def absolute_transform
236
+ camera.absolute_transform
237
+ end
238
+
239
+ # Behave like a square when going through drawing list.
240
+ def absolute_transform_for_drawing
241
+ parent.absolute_transform
242
+ end
243
+
244
+ def absolute_material
245
+ Material.new
246
+ end
247
+
248
+ def absolute_texture
249
+ Texture.new
250
+ end
251
+
252
+ def absolute_geometry
253
+ Geometry.new
254
+ end
255
+
256
+ def absolute_shader
257
+ Shader.new
258
+ end
259
+
260
+ def changed?
261
+ @last_frame_time != @mtime
262
+ end
263
+
264
+ # Applies viewport to OpenGL state, scissor to viewport,
265
+ # enable blending and apply camera.
266
+ def apply
267
+ @last_frame_time = @mtime
268
+ apply_clip
269
+ clear_viewport
270
+ apply_camera
271
+ apply_blend
272
+ draw_drawables
273
+ end
274
+
275
+ def apply_clip
276
+ Viewport(viewport_x, viewport_y, viewport_width, viewport_height)
277
+ Enable SCISSOR_TEST
278
+ Scissor(viewport_x, viewport_y, viewport_width, viewport_height)
279
+ end
280
+
281
+ def clear_viewport
282
+ if background
283
+ GL.ClearColor(*background)
284
+ GL.Clear(GL::COLOR_BUFFER_BIT)
285
+ GL.Clear(GL::DEPTH_BUFFER_BIT) if clear_depth_buffer
286
+ end
287
+ end
288
+
289
+ def apply_blend
290
+ Enable GL::BLEND
291
+ BlendFunc(GL::SRC_ALPHA, GL::ONE_MINUS_SRC_ALPHA)
292
+ end
293
+
294
+ def apply_camera
295
+ if camera
296
+ camera.viewport = self
297
+ camera.apply
298
+ end
299
+ end
300
+
301
+ # Draws the viewport drawables using painter's algorithm.
302
+ def draw_drawables
303
+ collapsed = (@drawables).find_all{|d| d.visible }
304
+ collapsed = collapsed.sort_by{|d|
305
+ d.absolute_transform.flatten[14]
306
+ }
307
+ collapsed.each{|d|
308
+ PushAttrib( TEXTURE_BIT | LIGHTING_BIT )
309
+ LoadMatrix(d.absolute_transform_for_drawing)
310
+ d.absolute_texture.apply
311
+ d.absolute_material.apply
312
+ d.push_state
313
+ d.apply
314
+ d.pop_state
315
+ PopAttrib()
316
+ if d.is_a? Viewport
317
+ apply_clip
318
+ apply_camera
319
+ end
320
+ }
321
+ end
322
+
323
+ # The viewport of a Viewport is the Viewport itself.
324
+ def viewport
325
+ self
326
+ end
327
+
328
+ # Viewport presents only itself to its parent.
329
+ def drawables
330
+ Set.new + [self]
331
+ end
332
+
333
+ # Width per height aspect of the viewport.
334
+ def aspect
335
+ width.to_f / height
336
+ end
337
+
338
+ private
339
+
340
+ def adopt(c)
341
+ super
342
+ c.viewport = self
343
+ end
344
+
345
+ end
346
+
347
+ end
348
+
349
+
@@ -0,0 +1,10 @@
1
+ require 'glimr/renderer/sceneobject'
2
+ require 'glimr/renderer/transform'
3
+ require 'glimr/renderer/shader'
4
+ require 'glimr/renderer/texture'
5
+ require 'glimr/renderer/material'
6
+ require 'glimr/renderer/geometry'
7
+ require 'glimr/renderer/light'
8
+ require 'glimr/renderer/raw'
9
+ require 'glimr/renderer/model'
10
+ require 'glimr/renderer/viewport'
@@ -0,0 +1,247 @@
1
+ require 'glimr/configurable'
2
+
3
+
4
+ MAC_OS_X = !(RUBY_PLATFORM =~ /darwin/).nil?
5
+ WINDOWS = !(RUBY_PLATFORM =~ /win32/).nil?
6
+
7
+
8
+ class Percentage
9
+
10
+ attr_accessor :value
11
+
12
+ def initialize(value)
13
+ @value = value
14
+ end
15
+
16
+ def to_f
17
+ value / 100.0
18
+ end
19
+
20
+ def method_missing(*args, &block)
21
+ to_f.__send__(*args, &block)
22
+ rescue => e
23
+ raise e
24
+ end
25
+
26
+ end
27
+
28
+
29
+ class Numeric
30
+
31
+ def pct
32
+ Percentage.new(self)
33
+ end
34
+
35
+ end
36
+
37
+
38
+ class Module
39
+
40
+ def attr_accessor_delegate(*method_names, &delegate)
41
+ method_names.each{|mn|
42
+ define_method(mn){|*args| instance_eval(&delegate).__send__(mn,*args) }
43
+ setter = "#{mn}=".to_sym
44
+ define_method(setter){|*args| instance_eval(&delegate).__send__(setter,*args) }
45
+ }
46
+ end
47
+
48
+ def ivar_delegate(ivar_name, *method_names)
49
+ method_names.each{|mn|
50
+ define_method(mn){|*args|
51
+ instance_variable_get(ivar_name).__send__(mn, *args)
52
+ }
53
+ }
54
+ end
55
+
56
+ def ivar_accessor_delegate(ivar_name, *method_names)
57
+ method_names.each{|mn|
58
+ define_method(mn){|*args| instance_variable_get(ivar_name).__send__(mn,*args) }
59
+ setter = "#{mn}=".to_sym
60
+ define_method(setter){|*args|
61
+ instance_variable_get(ivar_name).__send__(setter,*args)
62
+ }
63
+ }
64
+ end
65
+
66
+ def delegate(target, *mnames)
67
+ mnames.each{|mn|
68
+ args = "*a, &b"
69
+ args = "a" if mn.to_s[-1,1] == "="
70
+ class_eval <<-EOF
71
+ def #{mn}(#{args})
72
+ #{target}.#{mn}(#{args})
73
+ end
74
+ EOF
75
+ }
76
+ end
77
+
78
+ def delegate_accessor(target, *mnames)
79
+ delegate(target, *mnames.map{|mn| [mn, "#{mn}="] }.flatten)
80
+ end
81
+
82
+ def block_delegate(*method_names, &delegate)
83
+ method_names.each{|mn|
84
+ define_method(mn){|*args|
85
+ instance_eval(&delegate).__send__(mn,*args)
86
+ }
87
+ }
88
+ end
89
+
90
+ def touching(touch_method_name, *mnames)
91
+ mnames.each{|mn|
92
+ non_touching_mn = "__non_touching_#{mn}"
93
+ alias_method non_touching_mn, mn
94
+ private(non_touching_mn)
95
+ define_method(mn){|*args|
96
+ rv = __send__(non_touching_mn,*args)
97
+ __send__(touch_method_name)
98
+ rv
99
+ }
100
+ }
101
+ end
102
+
103
+ def touching_accessor *names
104
+ attr_accessor *names
105
+ touching :touch!, *names.map{|n| n.to_s+"=" }
106
+ end
107
+
108
+ end
109
+
110
+
111
+ class Class
112
+
113
+ def wrap_obj_count(*mnames)
114
+ mnames.each{|mn|
115
+ alias_method "__orig__#{mn}", mn
116
+ class_eval <<-EOF
117
+ def #{mn}(*args, &block)
118
+ acount = fcount = hcount = 0
119
+ ObjectSpace.each_object(Array){|o|
120
+ acount -= 1
121
+ }
122
+ ObjectSpace.each_object(Hash){|o|
123
+ hcount -= 1
124
+ }
125
+ ObjectSpace.each_object(Float){|o|
126
+ fcount -= 1
127
+ }
128
+ rv = self.__send__(:__orig__#{mn}, *args, &block)
129
+ ObjectSpace.each_object(Array){|o|
130
+ acount += 1
131
+ }
132
+ ObjectSpace.each_object(Hash){|o|
133
+ hcount += 1
134
+ }
135
+ ObjectSpace.each_object(Float){|o|
136
+ fcount += 1
137
+ }
138
+ puts "After #{self}##{mn}:\n"+
139
+ " Arrays: "+(acount-1).to_s+"\n"+ # *args == +1
140
+ " Hashes: "+hcount.to_s+"\n"+
141
+ " Floats: "+fcount.to_s+"\n"+
142
+ "\n"
143
+ rv
144
+ end
145
+ EOF
146
+ }
147
+ end
148
+
149
+ end
150
+
151
+
152
+ class Object
153
+
154
+ def touch!
155
+ @mtime = Time.now.to_f
156
+ end
157
+
158
+ def silently_fail(exception = StandardError)
159
+ yield
160
+ rescue exception => e
161
+ e
162
+ end
163
+ alias_method :silently, :silently_fail
164
+
165
+ def print_obj_count(label="block", &block)
166
+ acount = fcount = hcount = 0
167
+ ObjectSpace.each_object(Array){|o|
168
+ acount -= 1
169
+ }
170
+ ObjectSpace.each_object(Hash){|o|
171
+ hcount -= 1
172
+ }
173
+ ObjectSpace.each_object(Float){|o|
174
+ fcount -= 1
175
+ }
176
+ rv = yield
177
+ ObjectSpace.each_object(Array){|o|
178
+ acount += 1
179
+ }
180
+ ObjectSpace.each_object(Hash){|o|
181
+ hcount += 1
182
+ }
183
+ ObjectSpace.each_object(Float){|o|
184
+ fcount += 1
185
+ }
186
+ puts "After #{label}:\n"+
187
+ " Arrays: "+acount.to_s+"\n"+
188
+ " Hashes: "+hcount.to_s+"\n"+
189
+ " Floats: "+fcount.to_s+"\n"+
190
+ "\n"
191
+ rv
192
+ end
193
+
194
+ end
195
+
196
+
197
+ class Array
198
+
199
+ def add(x)
200
+ i = 0
201
+ x.each{|v|
202
+ self[i] += v
203
+ i += 1
204
+ }
205
+ self
206
+ end
207
+
208
+ def sub(x)
209
+ i = 0
210
+ x.each{|v|
211
+ self[i] -= v
212
+ i += 1
213
+ }
214
+ self
215
+ end
216
+
217
+ alias_method :add!, :add
218
+ alias_method :sub!, :sub
219
+
220
+ def dot(x)
221
+ if x.is_a? Array
222
+ zip(x).map{|i,j| i*j}
223
+ else
224
+ map{|i| i*x}
225
+ end
226
+ end
227
+
228
+ def cross_product(v)
229
+ u = self
230
+ [ u[1]*v[2] - v[1]*u[2],
231
+ v[0]*u[2] - u[0]*v[2],
232
+ u[0]*v[1] - v[0]*u[1]]
233
+ end
234
+
235
+ def normalize
236
+ l = Math.sqrt(inject(0){|s,i| s+i**2})
237
+ map{|i| i / l }
238
+ end
239
+
240
+ def -@
241
+ map{|i| -i}
242
+ end
243
+
244
+ end
245
+
246
+
247
+