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.
- 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
|