glimr 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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