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,14 @@
1
+ require 'glimr/renderer/sceneobject'
2
+
3
+
4
+ module GlimR
5
+
6
+ class Shader < SceneObject
7
+
8
+ def replace!(other)
9
+ self
10
+ end
11
+
12
+ end
13
+
14
+ end
@@ -0,0 +1,280 @@
1
+ require 'glimr/renderer/sceneobject'
2
+
3
+ begin
4
+ require 'sdl'
5
+ $sdl = true
6
+ rescue LoadError
7
+ begin
8
+ require 'imlib2'
9
+ $imlib = true
10
+ rescue LoadError
11
+ begin
12
+ require 'RMagick'
13
+ $rmagick = true
14
+ rescue LoadError
15
+ warn "Couldn't load any image loading library."
16
+ warn "Trying to load images will fail."
17
+ warn "Install one of SDL, Ruby-Imlib2, or RMagick to fix this."
18
+ end
19
+ end
20
+ end
21
+
22
+ module GlimR
23
+
24
+
25
+ # A Texture is a wrapper around an OpenGL texture object.
26
+ # It generates a texture id when it is first used.
27
+ #
28
+ # To load a texture from an image file, use Texture.load(image_filename)
29
+ # or Texture#load(image_filename).
30
+ #
31
+ # The #width and #height of a texture refer to the dimensions of the
32
+ # loaded image, while the #bound of a texture is the closest containing
33
+ # power of two for the image dimensions (as in, the texture dimensions
34
+ # are a power of two square, inside which the image is stamped.)
35
+ #
36
+ # The #mode of a texture is GL::TEXTURE_2D. If feeling particularly
37
+ # adventurous, extend the class to do rectangular textures, non-power-of-two
38
+ # textures, 1D and 3D textures.
39
+ #
40
+ # Uses SDL, Imlib2, or RMagick to load images. If you have none of these,
41
+ # trying to load texture images will fail.
42
+ #
43
+ class Texture < SceneObject
44
+
45
+ attr_accessor :width, :height, :mode, :pixels, :bound
46
+ attr_reader :tex_id, :filename
47
+
48
+ def default_config
49
+ super.merge(
50
+ :mode => TEXTURE_2D,
51
+ :width => 0, :height => 0
52
+ )
53
+ end
54
+
55
+ # Creates a new Texture from the given image filename.
56
+ def self.load(filename)
57
+ t = new
58
+ t.load(filename)
59
+ t
60
+ end
61
+
62
+ def initialize(*a, &b)
63
+ @tex_id = []
64
+ super
65
+ end
66
+
67
+ # The collapsed texture for a texture is the texture itself.
68
+ def absolute_texture
69
+ self
70
+ end
71
+
72
+ # Loads the image filename into the texture.
73
+ def load(filename)
74
+ if $sdl
75
+ surf = SDL::Surface.load(filename.to_s)
76
+ if surf.bpp < 32
77
+ rmask = 0xff000000
78
+ gmask = 0x00ff0000
79
+ bmask = 0x0000ff00
80
+ amask = 0x000000ff
81
+ image = SDL::Surface.new(
82
+ SDL::SWSURFACE|SDL::SRCALPHA,
83
+ surf.w, surf.h, 32,
84
+ amask,bmask,gmask,rmask)
85
+ image.put(surf,0,0)
86
+ else
87
+ image = surf
88
+ end
89
+ w = image.w
90
+ h = image.h
91
+ px = image.pixels
92
+ elsif $imlib
93
+ image = Imlib2::Image.load(filename.to_s)
94
+ w = image.width
95
+ h = image.height
96
+ px = image.data.gsub(/(.)(.)(.)(.)/, '\2\3\4\1')
97
+ elsif $rmagick
98
+ image = Magick::Image.read(filename.to_s).first
99
+ w = image.width
100
+ h = image.height
101
+ px = image.to_blob{|i|
102
+ i.format = "RGBA"
103
+ i.depth = 8
104
+ }
105
+ end
106
+ @filename = filename
107
+ self.mode = TEXTURE_2D
108
+ self.width = w.to_i
109
+ self.height = h.to_i
110
+ self.pixels = px + ""
111
+ end
112
+
113
+ # Deletes the OpenGL texture object.
114
+ def finish
115
+ DeleteTextures(@tex_id)
116
+ @tex_id.clear
117
+ end
118
+
119
+ # Bind tex_id as the OpenGL texture and upload the texture if it has been changed
120
+ # since last use.
121
+ def apply
122
+ return unless pixels
123
+ Enable(mode)
124
+ if not @tex_id[0]
125
+ @tex_id[0] = GenTextures(1)[0]
126
+ push_state
127
+ BindTexture(mode, @tex_id[0])
128
+ upload_texture
129
+ pop_state
130
+ end
131
+ BindTexture(mode, @tex_id[0])
132
+ upload_texture if @changed
133
+ end
134
+
135
+ def power_of_two?(n)
136
+ (n & (n - 1) == 0)
137
+ end
138
+
139
+ def width= w
140
+ @bound = nil if @width != w
141
+ @width = w
142
+ end
143
+
144
+ def height= h
145
+ @bound = nil if @height != h
146
+ @height = h
147
+ end
148
+
149
+ def pixels= px
150
+ @changed = true
151
+ @pixels = px
152
+ end
153
+
154
+ # Pushes GL::TEXTURE_BIT to attrib stack.
155
+ def push_state
156
+ PushAttrib(TEXTURE_BIT)
157
+ end
158
+
159
+ # Pops the GL attrib stack.
160
+ def pop_state
161
+ PopAttrib()
162
+ end
163
+
164
+ # Returns the smallest power of two that is
165
+ # larger or equal to the larger of width and height.
166
+ #
167
+ def bound
168
+ b = [width, height].max
169
+ if not power_of_two?(b)
170
+ pow = (0..12).find{|i| 2**i > b}
171
+ b = 2**pow
172
+ end
173
+ b
174
+ end
175
+
176
+ # Uploads the texture to OpenGL.
177
+ # Uses TexImage2D if the texture dimensions have
178
+ # changed since last upload, otherwise uses TexSubImage2D.
179
+ #
180
+ def upload_texture
181
+ if !@bound
182
+ @bound = self.bound
183
+ @bound_pixels = "\000"*(4*@bound*@bound)
184
+ TexImage2D(
185
+ mode, 0, 4,
186
+ @bound, @bound, 0,
187
+ RGBA,
188
+ UNSIGNED_BYTE,
189
+ @bound_pixels
190
+ )
191
+ end
192
+ TexSubImage2D(
193
+ mode, 0,
194
+ 0, 0, width, height,
195
+ RGBA,
196
+ UNSIGNED_BYTE,
197
+ pixels
198
+ )
199
+ TexParameteri(mode, TEXTURE_WRAP_S, CLAMP)
200
+ TexParameteri(mode, TEXTURE_WRAP_T, CLAMP)
201
+ TexParameteri(mode, TEXTURE_MIN_FILTER, LINEAR)
202
+ TexParameteri(mode, TEXTURE_MAG_FILTER, LINEAR)
203
+ @changed = false
204
+ end
205
+
206
+ # Replace main instance variables with the other's.
207
+ # Namely: @tex_id, @bound, @bound_pixels, @width, @height, @pixels, @changed, @mode.
208
+ #
209
+ # Use this to swap a texture for another when you want to preserve the rest of the state.
210
+ #
211
+ # But beware, the current texture is lost unless you copy it somewhere else first.
212
+ #
213
+ # E.g.
214
+ # t = Texture.load 'foo.jpg'
215
+ # backup = Texture.new
216
+ # backup.replace! deep_texture_node
217
+ # deep_texture_node.replace! t
218
+ #
219
+ def replace!(other)
220
+ @tex_id = other.tex_id
221
+ @bound = other.instance_variable_get(:@bound)
222
+ @bound_pixels = other.instance_variable_get(:@bound_pixels)
223
+ @width = other.instance_variable_get(:@width)
224
+ @height = other.instance_variable_get(:@height)
225
+ @pixels = other.instance_variable_get(:@pixels)
226
+ @changed = other.instance_variable_get(:@changed)
227
+ @mode = other.instance_variable_get(:@mode)
228
+ self
229
+ end
230
+
231
+ touching :touch!, :replace!, :pixels=, :width=, :height=, :mode=, :bound=
232
+
233
+ def inspect
234
+ "#<#{self.class.name}:#{hash}:TEXID#@tex_id:FILE:#@filename:MODE#@mode:W#@width:H#@height:B#@bound>"
235
+ end
236
+
237
+ end
238
+
239
+
240
+ end
241
+
242
+
243
+
244
+ class String
245
+
246
+ def unpremultiply!
247
+ if MAC_OS_X
248
+ i = 0
249
+ while i < size
250
+ a = self[i+3]
251
+ a2 = a / 2
252
+ if a > 0
253
+ r = self[i]
254
+ self[i] = (self[i+2] * 255 + a2) / a
255
+ self[i+1] = (self[i+1] * 255 + a2) / a
256
+ self[i+2] = (r * 255 + a2) / a
257
+ end
258
+ i += 4
259
+ end
260
+ else
261
+ i = 0
262
+ while i < size
263
+ r = self[i+1]
264
+ g = self[i+2]
265
+ b = self[i+3]
266
+ a = self[i]
267
+ if a > 0
268
+ self[i] = (r * 255 + a / 2) / a
269
+ self[i+1] = (g * 255 + a / 2) / a
270
+ self[i+2] = (b * 255 + a / 2) / a
271
+ end
272
+ self[i+3] = a
273
+ i += 4
274
+ end
275
+ end
276
+ self
277
+ end
278
+
279
+ end
280
+
@@ -0,0 +1,322 @@
1
+ require 'matrix'
2
+ require 'glimr/renderer/sceneobject'
3
+
4
+
5
+ class Vector
6
+
7
+ # Returns the vector cross product of two vectors.
8
+ #
9
+ def cross_product(v)
10
+ Vector[
11
+ self[1]*v[2] - self[2]*v[1],
12
+ self[2]*v[0] - self[0]*v[2],
13
+ self[0]*v[1] - self[1]*v[0]
14
+ ]
15
+ end
16
+
17
+ # Normalizes the vector to unit length.
18
+ #
19
+ def normalize
20
+ self * (1.0 / r)
21
+ end
22
+
23
+ end
24
+
25
+
26
+ module GlimR
27
+
28
+ # Transform is a SceneObject that handles OpenGL transformations.
29
+ #
30
+ # It has a rotation, scale, position and matrix, These are applied in the
31
+ # following order: first translate, then scale, then rotate, then multiply by matrix.
32
+ #
33
+ class Transform < SceneObject
34
+
35
+ attr_accessor :matrix, :collapsed_matrix
36
+
37
+ attr_reader :rotation, :scale, :position
38
+
39
+ # Creates a new Transform with position set to [x,y,z].
40
+ def self.translate(x,y=x,z=0)
41
+ t = new
42
+ t.position = [x,y,z]
43
+ t
44
+ end
45
+
46
+ # Creates a new Transform with scale set to [x,y,z].
47
+ def self.scale(x,y=x,z=x)
48
+ t = new
49
+ t.scale = [x,y,z]
50
+ t
51
+ end
52
+
53
+ # Creates a new Transform with rotation set to r.
54
+ def self.rotate(r)
55
+ t = new
56
+ t.rotation = r
57
+ t
58
+ end
59
+
60
+ # Creates a new rotation Transform based on the given angle-axis rotation.
61
+ def self.rotate_aa(angle_deg, x=0,y=0,z=1)
62
+ rotate AngleAxis[angle_deg,x,y,z]
63
+ end
64
+
65
+ # Creates a new rotation Transform based on the given quaternion rotation.
66
+ def self.rotate_quat(w,x,y,z)
67
+ rotate Quaternion[w,x,y,z]
68
+ end
69
+
70
+ def default_config
71
+ super.merge(
72
+ :position => [0,0,0],
73
+ :rotation => AngleAxis[0,0,0,1],
74
+ :scale => [1,1,1],
75
+ :changed => true
76
+ )
77
+ end
78
+
79
+ # Sets scale to sca.
80
+ # If sca is a Numeric, sets all dimension scale factors to sca.
81
+ # If the sca array has only two members, uses 1 for z.
82
+ #
83
+ def scale= sca
84
+ if sca.is_a? Array
85
+ sca[2] ||= 1
86
+ elsif sca.is_a? Numeric
87
+ sca = [sca,sca,sca]
88
+ end
89
+ @scale = sca
90
+ end
91
+
92
+ # Sets position to pos. If the pos array has only two members, uses 0 for z.
93
+ def position= pos
94
+ pos[2] ||= 0 if pos.is_a? Array
95
+ @position = pos
96
+ end
97
+
98
+ # Sets rotation to rot. If rot is a number, sets the rotation angle to rot.
99
+ def rotation= rot
100
+ if rot.is_a? Numeric
101
+ @rotation = @rotation.to_aa
102
+ @rotation[0] = rot
103
+ else
104
+ @rotation = rot
105
+ end
106
+ end
107
+
108
+ def x
109
+ @position[0]
110
+ end
111
+
112
+ def y
113
+ @position[1]
114
+ end
115
+
116
+ def z
117
+ @position[2]
118
+ end
119
+
120
+ def x= nx
121
+ @position[0] = nx
122
+ end
123
+
124
+ def y= ny
125
+ @position[1] = ny
126
+ end
127
+
128
+ def z= nz
129
+ @position[2] = nz
130
+ end
131
+
132
+ def angle
133
+ @rotation.to_aa[0]
134
+ end
135
+
136
+ def angle= a
137
+ self.rotation = a
138
+ end
139
+
140
+ # Adds the given coordinates to position.
141
+ def translate(x,y=0,z=0)
142
+ tl = [x,y,z]
143
+ tl = x if x.is_a? Array
144
+ self.position = @position.zip(tl).map{|a,b| a+b}
145
+ end
146
+
147
+ # Sets transform matrix to m.
148
+ def matrix= m
149
+ if @matrix and m.is_a? Array
150
+ @matrix.replace(m.map{|i|i.to_f})
151
+ else
152
+ @matrix = m
153
+ end
154
+ end
155
+
156
+ # Pushes the OpenGL matrix stack.
157
+ def push_state
158
+ PushMatrix()
159
+ end
160
+
161
+ # Pops OpenGL matrix stack.
162
+ def pop_state
163
+ PopMatrix()
164
+ end
165
+
166
+ # Applies the Transform to the current OpenGL transform matrix.
167
+ def apply
168
+ if @collapsed_matrix
169
+ MultMatrix(@collapsed_matrix)
170
+ else
171
+ gl_transform
172
+ end
173
+ end
174
+
175
+ # Collapses the transform into a single transform matrix and returns it.
176
+ def collapse!
177
+ PushMatrix()
178
+ LoadIdentity()
179
+ gl_transform
180
+ @collapsed_matrix = GetDoublev(MODELVIEW_MATRIX).flatten
181
+ PopMatrix()
182
+ @absolute_transform = false
183
+ @collapsed_matrix
184
+ end
185
+
186
+ # Apply position, scale, rotation and matrix to OpenGL state.
187
+ def gl_transform
188
+ Translate(*position) if @position
189
+ Scale(*scale) if @scale
190
+ Rotate(*rotation) if @rotation
191
+ MultMatrix(*matrix) if @matrix
192
+ end
193
+
194
+ # Compares two transformations by their transformations.
195
+ def ===(t)
196
+ return super unless t.is_a?(Transform)
197
+ matrix == t.matrix and position == t.position and scale == t.scale and rotation == t.rotation
198
+ end
199
+
200
+ # The absolute transform for a Transform is the parent's absolute transform multiplied with the current transform's collapsed matrix.
201
+ def absolute_transform
202
+ return parent.absolute_transform unless (@position or @scale or @rotation or @matrix or @collapsed_matrix)
203
+ collapse! unless @collapsed_matrix
204
+ if not (@absolute_transform and @parent_absolute_transform == (parent and (parent.absolute_transform)))
205
+ @parent_absolute_transform = (parent and (parent.absolute_transform))
206
+ PushMatrix()
207
+ LoadMatrix(@parent_absolute_transform) if @parent_absolute_transform
208
+ MultMatrix(@collapsed_matrix)
209
+ @absolute_transform = GetDoublev(MODELVIEW_MATRIX)
210
+ PopMatrix()
211
+ end
212
+ @absolute_transform
213
+ end
214
+
215
+ # Clears collapsed matrix.
216
+ def touch!(*a)
217
+ super
218
+ @collapsed_matrix = false
219
+ end
220
+
221
+ # Clones instance variables as well.
222
+ def clone(*a)
223
+ c = super
224
+ c.instance_variable_set(:@position, @position.clone) if @position
225
+ c.instance_variable_set(:@scale, @scale.clone) if @scale
226
+ c.instance_variable_set(:@rotation, @rotation.clone) if @rotation
227
+ c.instance_variable_set(:@matrix, @matrix.clone) if @matrix
228
+ c.instance_variable_set(:@collapsed_matrix, @collapsed_matrix.clone) if @collapsed_matrix
229
+ c
230
+ end
231
+
232
+ # Replaces ivars with the other's.
233
+ def replace!(other)
234
+ @position = other.position
235
+ @scale = other.scale
236
+ @rotation = other.rotation
237
+ @matrix = other.matrix
238
+ @collapsed_matrix = other.collapsed_matrix
239
+ @absolute_transform = other.instance_variable_get(:@absolute_transform)
240
+ @parent_absolute_transform = other.instance_variable_get(:@parent_absolute_transform)
241
+ self
242
+ end
243
+
244
+ touching :touch!, :replace!, :rotation=, :scale=, :position=, :matrix=, :x=, :y=, :z=, :angle=
245
+
246
+ def inspect
247
+ "#<#{self.class.name}:#{hash}:POS#@position:ROT#@rotation:SCA#@scale:MAT#@matrix>"
248
+ end
249
+
250
+ end
251
+
252
+
253
+ class Quaternion < Array
254
+
255
+ def self.[](w,x,y,z)
256
+ aa = new
257
+ aa.push(w,x,y,z)
258
+ aa
259
+ end
260
+
261
+ def normalize
262
+ self.class[*(Vector[*self] * (1.0 / Vector[*self].r)).to_a]
263
+ end
264
+
265
+ def to_matrix
266
+ w,x,y,z = normalize
267
+ [
268
+ [1-2*y*y-2*z*z, 2*x*y-2*w*z, 2*z*x+2*w*y, 0],
269
+ [2*x*y+2*w*z, 1-2*x*x-2*z*z, 2*y*z-2*w*x, 0],
270
+ [2*z*x-2*w*y, 2*y*z+2*w*x, 1-2*x*x-2*y*y, 0],
271
+ [0,0,0,1]
272
+ ].transpose.flatten
273
+ end
274
+
275
+ def to_aa
276
+ w,x,y,z = normalize
277
+ c = w
278
+ angle = (Math.acos(c) * 2) * (180/Math::PI)
279
+ s = Math.sqrt( 1 - c*c )
280
+ s = 1 if (s.abs < 0.0005)
281
+ AngleAxis[angle, x/s, y/s, z/s]
282
+ end
283
+
284
+ def to_quat
285
+ self
286
+ end
287
+
288
+ end
289
+
290
+
291
+ class AngleAxis < Array
292
+
293
+ def self.[](a,x,y,z)
294
+ aa = new
295
+ aa.push(a,*normalize(Vector[x,y,z]))
296
+ aa
297
+ end
298
+
299
+ def to_quat
300
+ a,x,y,z = normalize
301
+ s = Math.sin(a*Math::PI/90)
302
+ c = Math.cos(a*Math::PI/90)
303
+ Quaternion[c, x*s, y*s, z*s]
304
+ end
305
+
306
+ def to_aa
307
+ self
308
+ end
309
+
310
+ def normalize
311
+ self.class[*self]
312
+ end
313
+
314
+ def self.normalize(v)
315
+ v = (v * (1.0 / v.r)).to_a if (v.r - 1) > 0.001
316
+ v.to_a
317
+ end
318
+
319
+ end
320
+
321
+
322
+ end