glimr 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/glimr.rb +2 -0
- data/lib/glimr/configurable.rb +37 -0
- data/lib/glimr/default_theme/button_bg.png +0 -0
- data/lib/glimr/default_theme/button_bg_down.png +0 -0
- data/lib/glimr/default_theme/button_cover.png +0 -0
- data/lib/glimr/default_theme/button_cover_down.png +0 -0
- data/lib/glimr/default_theme/button_focus.png +0 -0
- data/lib/glimr/default_theme/checkbox_bg.png +0 -0
- data/lib/glimr/default_theme/checkbox_checked_bg.png +0 -0
- data/lib/glimr/default_theme/font.ttf +0 -0
- data/lib/glimr/default_theme/hscroller_bg.png +0 -0
- data/lib/glimr/default_theme/radiobutton_bg.png +0 -0
- data/lib/glimr/default_theme/radiobutton_checked_bg.png +0 -0
- data/lib/glimr/default_theme/resizer_down.png +0 -0
- data/lib/glimr/default_theme/resizer_up.png +0 -0
- data/lib/glimr/default_theme/scroll_down_down.png +0 -0
- data/lib/glimr/default_theme/scroll_down_up.png +0 -0
- data/lib/glimr/default_theme/scroll_hknob_down.png +0 -0
- data/lib/glimr/default_theme/scroll_hknob_up.png +0 -0
- data/lib/glimr/default_theme/scroll_left_down.png +0 -0
- data/lib/glimr/default_theme/scroll_left_up.png +0 -0
- data/lib/glimr/default_theme/scroll_right_down.png +0 -0
- data/lib/glimr/default_theme/scroll_right_up.png +0 -0
- data/lib/glimr/default_theme/scroll_up_down.png +0 -0
- data/lib/glimr/default_theme/scroll_up_up.png +0 -0
- data/lib/glimr/default_theme/scroll_vknob_down.png +0 -0
- data/lib/glimr/default_theme/scroll_vknob_up.png +0 -0
- data/lib/glimr/default_theme/text_cursor.png +0 -0
- data/lib/glimr/default_theme/text_cursor_insert.png +0 -0
- data/lib/glimr/default_theme/text_input_bg.png +0 -0
- data/lib/glimr/default_theme/vscroller_bg.png +0 -0
- data/lib/glimr/event.rb +41 -0
- data/lib/glimr/eventlistener.rb +209 -0
- data/lib/glimr/layoutable.rb +520 -0
- data/lib/glimr/renderer.rb +2 -0
- data/lib/glimr/renderer/camera.rb +63 -0
- data/lib/glimr/renderer/geometry.rb +194 -0
- data/lib/glimr/renderer/glutwindow.rb +387 -0
- data/lib/glimr/renderer/light.rb +43 -0
- data/lib/glimr/renderer/material.rb +66 -0
- data/lib/glimr/renderer/model.rb +103 -0
- data/lib/glimr/renderer/orthoprojection.rb +21 -0
- data/lib/glimr/renderer/raw.rb +34 -0
- data/lib/glimr/renderer/sceneobject.rb +279 -0
- data/lib/glimr/renderer/shader.rb +14 -0
- data/lib/glimr/renderer/texture.rb +280 -0
- data/lib/glimr/renderer/transform.rb +322 -0
- data/lib/glimr/renderer/viewport.rb +349 -0
- data/lib/glimr/renderer_core.rb +10 -0
- data/lib/glimr/util.rb +247 -0
- data/lib/glimr/widget.rb +87 -0
- data/lib/glimr/widgets.rb +37 -0
- data/lib/glimr/widgets/button.rb +277 -0
- data/lib/glimr/widgets/checkbox.rb +82 -0
- data/lib/glimr/widgets/container.rb +84 -0
- data/lib/glimr/widgets/image.rb +82 -0
- data/lib/glimr/widgets/label.rb +91 -0
- data/lib/glimr/widgets/layout.rb +227 -0
- data/lib/glimr/widgets/list.rb +28 -0
- data/lib/glimr/widgets/radiogroup.rb +118 -0
- data/lib/glimr/widgets/resizer.rb +31 -0
- data/lib/glimr/widgets/scrollable_container.rb +67 -0
- data/lib/glimr/widgets/scrollbar.rb +496 -0
- data/lib/glimr/widgets/stretchable_image.rb +135 -0
- data/lib/glimr/widgets/text_editor.rb +349 -0
- data/tests/assets/datatowers_crop.jpg +0 -0
- data/tests/assets/download_progress_meter.png +0 -0
- data/tests/assets/metalwing2.png +0 -0
- data/tests/assets/redhairgreeneyes3.jpg +0 -0
- data/tests/demo_apps/spinning_ruby.rb +37 -0
- data/tests/integration_tests/run_all.rb +8 -0
- data/tests/integration_tests/test_button.rb +22 -0
- data/tests/integration_tests/test_checkbox.rb +21 -0
- data/tests/integration_tests/test_container.rb +22 -0
- data/tests/integration_tests/test_label.rb +12 -0
- data/tests/integration_tests/test_layout.rb +43 -0
- data/tests/integration_tests/test_radiogroup.rb +16 -0
- data/tests/integration_tests/test_renderer.rb +44 -0
- data/tests/integration_tests/test_renderer2.rb +36 -0
- data/tests/integration_tests/test_scrollable_container.rb +34 -0
- data/tests/integration_tests/test_scrollbar.rb +20 -0
- data/tests/integration_tests/test_stretchable_image.rb +34 -0
- data/tests/integration_tests/test_text_input.rb +20 -0
- data/tests/integration_tests/test_zsort.rb +18 -0
- data/tests/unit_tests/test_button.rb +93 -0
- data/tests/unit_tests/test_checkbox.rb +35 -0
- data/tests/unit_tests/test_label.rb +36 -0
- data/tests/unit_tests/test_layout.rb +229 -0
- data/tests/unit_tests/test_widget.rb +3 -0
- metadata +139 -0
@@ -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
|