rage 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/COPYING +8 -0
- data/LICENSE +674 -0
- data/README.rdoc +48 -0
- data/Rakefile +46 -0
- data/bin/mesh-viewer.rb +91 -0
- data/lib/rage.rb +30 -0
- data/lib/rage/camera.rb +59 -0
- data/lib/rage/color.rb +18 -0
- data/lib/rage/common.rb +52 -0
- data/lib/rage/dsl.rb +36 -0
- data/lib/rage/game.rb +64 -0
- data/lib/rage/input.rb +91 -0
- data/lib/rage/loader.rb +33 -0
- data/lib/rage/location.rb +141 -0
- data/lib/rage/mesh.rb +147 -0
- data/lib/rage/resource.rb +49 -0
- data/lib/rage/viewport.rb +50 -0
- data/lib/rage/window.rb +58 -0
- data/spec/location_spec.rb +112 -0
- data/spec/mesh_spec.rb +84 -0
- data/spec/spec_helper.rb +13 -0
- metadata +75 -0
data/lib/rage/input.rb
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
# Input handler constructs and methods
|
2
|
+
#
|
3
|
+
# Copyright (C) 2010 Mohammed Morsi <movitto@yahoo.com>
|
4
|
+
# Licensed under the GPLv3+ http://www.gnu.org/licenses/gpl.txt
|
5
|
+
|
6
|
+
module RAGE
|
7
|
+
|
8
|
+
# FIXME turn into callback interface
|
9
|
+
|
10
|
+
# Handles and redirects user events to registered callbacks.
|
11
|
+
class InputHandler
|
12
|
+
|
13
|
+
# Invoked during main game execution cycle to handle any SDL input events
|
14
|
+
def self.handle(event)
|
15
|
+
|
16
|
+
@@left_button_pressed = false unless defined? @@left_button_pressed
|
17
|
+
@@right_button_pressed = false unless defined? @@right_button_pressed
|
18
|
+
|
19
|
+
case event
|
20
|
+
when SDL::Event2::Quit
|
21
|
+
exit
|
22
|
+
|
23
|
+
when SDL::Event2::KeyDown
|
24
|
+
case event.sym
|
25
|
+
when SDL::Key::Q, SDL::Key::ESCAPE
|
26
|
+
exit
|
27
|
+
when SDL::Key::W
|
28
|
+
Game.wireframe_mode= !Game.wireframe_mode
|
29
|
+
end
|
30
|
+
|
31
|
+
when SDL::Event::MouseMotion
|
32
|
+
# get state of mouse buttons
|
33
|
+
left_pressed = ((event.state & SDL::Mouse::BUTTON_LMASK) != 0)
|
34
|
+
right_pressed = ((event.state & SDL::Mouse::BUTTON_RMASK) != 0)
|
35
|
+
|
36
|
+
# if left button is down, rotate camera
|
37
|
+
if left_pressed
|
38
|
+
xpos, ypos, zpos = to_3d_coordinates(event.x, event.y)
|
39
|
+
oxpos, oypos, ozpos = to_3d_coordinates(event.x-event.xrel, event.y-event.yrel)
|
40
|
+
|
41
|
+
if xpos > oxpos
|
42
|
+
Game.current_viewport.camera.xrotate += 1
|
43
|
+
elsif xpos < oxpos
|
44
|
+
Game.current_viewport.camera.xrotate -= 1
|
45
|
+
end
|
46
|
+
|
47
|
+
if ypos > oypos
|
48
|
+
Game.current_viewport.camera.yrotate += 1
|
49
|
+
elsif ypos < oypos
|
50
|
+
Game.current_viewport.camera.yrotate -= 1
|
51
|
+
end
|
52
|
+
|
53
|
+
if zpos > ozpos
|
54
|
+
Game.current_viewport.camera.zrotate += 1
|
55
|
+
elsif zpos < ozpos
|
56
|
+
Game.current_viewport.camera.zrotate -= 1
|
57
|
+
end
|
58
|
+
|
59
|
+
# if right is down pan camera
|
60
|
+
elsif right_pressed
|
61
|
+
if event.xrel > 0
|
62
|
+
Game.current_viewport.camera.pos[0] -= 1
|
63
|
+
elsif event.xrel < 0
|
64
|
+
Game.current_viewport.camera.pos[0] += 1
|
65
|
+
end
|
66
|
+
|
67
|
+
if event.yrel > 0
|
68
|
+
Game.current_viewport.camera.pos[1] += 1
|
69
|
+
elsif event.yrel < 0
|
70
|
+
Game.current_viewport.camera.pos[1] -= 1
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
when SDL::Event::MouseButtonDown
|
76
|
+
# zoom out the camera
|
77
|
+
if event.button == 5 # wheeldown
|
78
|
+
Game.current_viewport.camera.pos[2] += 1
|
79
|
+
end
|
80
|
+
|
81
|
+
when SDL::Event::MouseButtonUp
|
82
|
+
# zoom in the camera
|
83
|
+
if event.button == 4 # wheel up
|
84
|
+
Game.current_viewport.camera.pos[2] -= 1
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
data/lib/rage/loader.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# RAGE resource loader
|
2
|
+
#
|
3
|
+
# Copyright (C) 2010 Mohammed Morsi <movitto@yahoo.com>
|
4
|
+
# Licensed under the GPLv3+ http://www.gnu.org/licenses/gpl.txt
|
5
|
+
|
6
|
+
require 'uri' # use uri to parse sources
|
7
|
+
require 'net/http' # get http:// based resources
|
8
|
+
|
9
|
+
module RAGE
|
10
|
+
|
11
|
+
# Loads resources from uris
|
12
|
+
class Loader
|
13
|
+
|
14
|
+
# Loads and return text resource from specified source uri
|
15
|
+
def self.load(source_uri)
|
16
|
+
Logger.info "loading resource from uri #{source_uri}"
|
17
|
+
data = nil
|
18
|
+
uri = URI.parse(source_uri)
|
19
|
+
if uri.scheme == "file"
|
20
|
+
data = File.read_all uri.path
|
21
|
+
elsif uri.scheme == "http"
|
22
|
+
data = Net::HTTP.get_response(uri.host, uri.path).body
|
23
|
+
# elsif FIXME support other uri types
|
24
|
+
end
|
25
|
+
|
26
|
+
return data
|
27
|
+
|
28
|
+
rescue URI::InvalidURIError
|
29
|
+
raise Exceptions::InvalidResourceUri
|
30
|
+
end
|
31
|
+
|
32
|
+
end # class loader
|
33
|
+
end # module RXSD
|
@@ -0,0 +1,141 @@
|
|
1
|
+
# Location related constructs
|
2
|
+
#
|
3
|
+
# Copyright (C) 2010 Mohammed Morsi <movitto@yahoo.com>
|
4
|
+
# Licensed under the GPLv3+ http://www.gnu.org/licenses/gpl.txt
|
5
|
+
|
6
|
+
module RAGE
|
7
|
+
|
8
|
+
# Location w/ coordinates in the 3D system.
|
9
|
+
# Various entities may be associated with Location to be drawn.
|
10
|
+
class Location
|
11
|
+
# x, y, z coordinates of the location,
|
12
|
+
attr_reader :x, :y, :z
|
13
|
+
|
14
|
+
# Mesh which to draw at location
|
15
|
+
attr_reader :mesh
|
16
|
+
|
17
|
+
# TODO lights, other entities to draw at locations
|
18
|
+
|
19
|
+
# Initialize Location with argument hash, which may include
|
20
|
+
# * :x location coordinate
|
21
|
+
# * :y location coordinate
|
22
|
+
# * :z location coordinate
|
23
|
+
# * :mesh resource to draw at location
|
24
|
+
def initialize(args = {})
|
25
|
+
@x = args.has_key?(:x) ? args[:x] : 0
|
26
|
+
@y = args.has_key?(:y) ? args[:y] : 0
|
27
|
+
@z = args.has_key?(:z) ? args[:z] : 0
|
28
|
+
@mesh = args[:mesh]
|
29
|
+
end
|
30
|
+
|
31
|
+
# Update location w/ args, which may include
|
32
|
+
# * :x location coordinate
|
33
|
+
# * :y location coordinate
|
34
|
+
# * :z location coordinate
|
35
|
+
def update(args = {})
|
36
|
+
@x = args[:x] if args.has_key?(:x)
|
37
|
+
@y = args[:y] if args.has_key?(:y)
|
38
|
+
@z = args[:z] if args.has_key?(:z)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Return location boundaries. This is generated by determining maxima/minima
|
42
|
+
# local coordinates of entity associated w/ location and adding those to
|
43
|
+
# the location's coordinates. Return value is an array of six values as follows
|
44
|
+
# max_x, max_y, max_z, min_x, min_y, min_z
|
45
|
+
def boundaries
|
46
|
+
max_x = max_y = max_z = min_x = min_y = min_z = 0
|
47
|
+
unless mesh.nil?
|
48
|
+
max_x, max_y, max_z, min_x, min_y, min_z = mesh.boundaries
|
49
|
+
end
|
50
|
+
return x + max_x, y + max_y, z + max_z, x + min_x, y + min_y, z + min_z
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
# Invoked during draw cycle to draw location & entity associated w/ it
|
55
|
+
def draw
|
56
|
+
Gl.glPushMatrix();
|
57
|
+
|
58
|
+
# translate coordinate system to location's coordinates
|
59
|
+
Gl.glTranslatef(x, y, z)
|
60
|
+
|
61
|
+
# draw mesh
|
62
|
+
mesh.draw
|
63
|
+
|
64
|
+
Gl.glPopMatrix();
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Singleton class managing all locations
|
69
|
+
class LocationsManager
|
70
|
+
include Singleton
|
71
|
+
|
72
|
+
# Array of locations being managed
|
73
|
+
attr_accessor :locations
|
74
|
+
|
75
|
+
def initialize
|
76
|
+
@locations = []
|
77
|
+
end
|
78
|
+
|
79
|
+
# Add location to be managed
|
80
|
+
def add(location)
|
81
|
+
@locations.push location unless @locations.include? location
|
82
|
+
end
|
83
|
+
|
84
|
+
# Empty the array of managed locations
|
85
|
+
def clear
|
86
|
+
@locations.clear
|
87
|
+
end
|
88
|
+
|
89
|
+
# Return the maxima and minima of the x,y,z coordinate values for all the
|
90
|
+
# locations being managed. Return value is an array of six values as follows
|
91
|
+
# max_x, max_y, max_z, min_x, min_y, min_z
|
92
|
+
# This method essentially gives you the "box" in which all locations being managed are in
|
93
|
+
def boundaries
|
94
|
+
max_x = max_y = max_z = min_x = min_y = min_z = 0
|
95
|
+
@locations.each { |loc|
|
96
|
+
locb = loc.boundaries
|
97
|
+
max_x = locb[0] if locb[0] > max_x
|
98
|
+
max_y = locb[1] if locb[1] > max_y
|
99
|
+
max_z = locb[2] if locb[2] > max_z
|
100
|
+
min_x = locb[3] if locb[3] < min_x
|
101
|
+
min_y = locb[4] if locb[4] < min_y
|
102
|
+
min_z = locb[5] if locb[5] < min_z
|
103
|
+
}
|
104
|
+
return max_x, max_y, max_z, min_x, min_y, min_z
|
105
|
+
end
|
106
|
+
|
107
|
+
# Return the center coordinate of location system.
|
108
|
+
# Must specify the metric which to generate center, which may be either
|
109
|
+
# * :avg - compute and return bounaries averages
|
110
|
+
# * :mean - compute and return mean of all coords
|
111
|
+
def center(metric = :mean)
|
112
|
+
if metric == :mean
|
113
|
+
ls = @locations.size
|
114
|
+
mx = my = mz = 0
|
115
|
+
@locations.each { |lm|
|
116
|
+
mx += lm.x / ls
|
117
|
+
my += lm.y / ls
|
118
|
+
mz += lm.z / ls
|
119
|
+
}
|
120
|
+
return mx,my,mz
|
121
|
+
|
122
|
+
elsif metric == :avg
|
123
|
+
b = self.boundaries
|
124
|
+
return (b[0] + b[3]) / 2, (b[1] + b[4]) / 2, (b[2] + b[5]) / 2
|
125
|
+
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
130
|
+
|
131
|
+
# Helper method to get 3d coords cooresponding to 2d ones via OPENGL unprojection
|
132
|
+
def to_3d_coordinates(x2, y2)
|
133
|
+
model_view = Gl.glGetDoublev Gl::GL_MODELVIEW_MATRIX
|
134
|
+
projection = Gl.glGetDoublev Gl::GL_PROJECTION_MATRIX
|
135
|
+
viewport = Gl.glGetIntegerv Gl::GL_VIEWPORT
|
136
|
+
depth = Gl.glReadPixels x2, y2, 1, 1, Gl::GL_DEPTH_COMPONENT, Gl::GL_FLOAT
|
137
|
+
x3, y3, z3 = Glu.gluUnProject x2, y2, depth[0], model_view, projection, viewport
|
138
|
+
return [x3, y3, z3]
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
data/lib/rage/mesh.rb
ADDED
@@ -0,0 +1,147 @@
|
|
1
|
+
# Mesh Resource
|
2
|
+
#
|
3
|
+
# Currently manages X3D meshes
|
4
|
+
#
|
5
|
+
# Copyright (C) 2010 Mohammed Morsi <movitto@yahoo.com>
|
6
|
+
# Licensed under the GPLv3+ http://www.gnu.org/licenses/gpl.txt
|
7
|
+
|
8
|
+
# use rxsd to load x3d xsd schema, declare xsd classes, instantiate scenes
|
9
|
+
require 'rxsd'
|
10
|
+
|
11
|
+
module RAGE
|
12
|
+
|
13
|
+
# 3D mesh to be displayed on the screen.
|
14
|
+
# Contains geometry and visual properties.
|
15
|
+
# Currently implements certain parts of the X3D graphics schema.
|
16
|
+
# http://en.wikipedia.org/wiki/X3D
|
17
|
+
class Mesh
|
18
|
+
# Optional transformation params, set these to
|
19
|
+
# transformations to be factored in if needed
|
20
|
+
attr_accessor :op_translation, :op_scale, :op_rotation
|
21
|
+
|
22
|
+
# X3D schema for shared access
|
23
|
+
class_attr :x3d_schema
|
24
|
+
|
25
|
+
# X3D schema classes for shared access
|
26
|
+
class_attr :x3d_schema_classes
|
27
|
+
|
28
|
+
|
29
|
+
# Create new mesh from raw X3D mesh
|
30
|
+
def initialize(data)
|
31
|
+
# set default attributes
|
32
|
+
@op_translation = [0,0,0]
|
33
|
+
@op_scale = [1,1,1]
|
34
|
+
|
35
|
+
# load the x3d xsd schema and classes if not already done so
|
36
|
+
unless defined? @@x3d_schema
|
37
|
+
@@x3d_schema = RXSD::Parser.parse_xsd :uri => 'http://www.web3d.org/specifications/x3d-3.0.xsd' # TODO allow location which this is pulled from to be configured
|
38
|
+
@@x3d_schema_classes = @@x3d_schema.to :ruby_classes
|
39
|
+
end
|
40
|
+
|
41
|
+
# load the scene from the x3d schema instance
|
42
|
+
objs = RXSD::Parser.parse_xml(:raw => data).to(:ruby_objects, :schema => @@x3d_schema)
|
43
|
+
@scene = objs[0].scene
|
44
|
+
|
45
|
+
# FIXME get all transformations (and groups of) under scene
|
46
|
+
@tf = @scene.collision.transform
|
47
|
+
@translation = @tf.translation.to_a :type => XSDFloat
|
48
|
+
@scale = @tf.scale.to_a :type => XSDFloat
|
49
|
+
#diffuse = tf.shape.appearance.material.diffuse_color.split.collect { |c| c.to_f }
|
50
|
+
#specular = tf.shape.appearance.material.specular_color.split.collect { |c| c.to_f }
|
51
|
+
#emissive = tf.shape.appearance.material.emissive_color.split.collect { |c| c.to_f }
|
52
|
+
@coord_index = @tf.shape.indexed_face_set.coord_index.
|
53
|
+
to_a(:type => String, :delim => ",").
|
54
|
+
collect { |coords| a = coords.to_a(:type => XSDInteger); a[0...a.size-1]}.flatten # cut off the last -1
|
55
|
+
@coord_point = @tf.shape.indexed_face_set.coordinate.point.
|
56
|
+
to_a(:type => String, :delim => ",").
|
57
|
+
collect { |coords| coords.to_a :type => XSDFloat }.flatten
|
58
|
+
end
|
59
|
+
|
60
|
+
# Create location w/ specified args, associated mesh w/ it and add it to the LocationsManager
|
61
|
+
def show_at(args = {})
|
62
|
+
# XXX not a huge fan of storing location interally,
|
63
|
+
# but this is best way to do this for now
|
64
|
+
if ! defined? @location
|
65
|
+
args[:mesh] = self
|
66
|
+
@location = Location.new(args)
|
67
|
+
LocationsManager.instance.add @location
|
68
|
+
else
|
69
|
+
@location.update args
|
70
|
+
end
|
71
|
+
return @location
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
# Return center point of mesh from mesh translation provided in mesh
|
76
|
+
# and op_translation
|
77
|
+
def center
|
78
|
+
return @op_translation[0] + @translation[0],
|
79
|
+
@op_translation[1] + @translation[1],
|
80
|
+
@op_translation[2] + @translation[2]
|
81
|
+
end
|
82
|
+
|
83
|
+
# Return factor which mesh should scale to
|
84
|
+
def scale
|
85
|
+
return @scale[0] * @op_scale[0],
|
86
|
+
@scale[1] * @op_scale[1],
|
87
|
+
@scale[2] * @op_scale[2]
|
88
|
+
end
|
89
|
+
|
90
|
+
# Return mesh boundaries. This is generated by determining the maxima/minima
|
91
|
+
# coordinate points. Return value is an array of six values as follows
|
92
|
+
# max_x, max_y, max_z, min_x, min_y, min_z
|
93
|
+
def boundaries
|
94
|
+
centerx, centery, centerz = *center
|
95
|
+
max_x = max_y = max_z = min_x = min_y = min_z = 0
|
96
|
+
@coord_point.each_index { |cpi|
|
97
|
+
if cpi % 3 == 0
|
98
|
+
adjx = centerx + @coord_point[cpi]
|
99
|
+
if adjx > max_x
|
100
|
+
max_x = adjx
|
101
|
+
elsif adjx < min_x
|
102
|
+
min_x = adjx
|
103
|
+
end
|
104
|
+
elsif cpi % 3 == 1
|
105
|
+
adjy = centery + @coord_point[cpi]
|
106
|
+
if adjy > max_y
|
107
|
+
max_y = adjy
|
108
|
+
elsif adjy < min_y
|
109
|
+
min_y = adjy
|
110
|
+
end
|
111
|
+
else
|
112
|
+
adjz = centerz + @coord_point[cpi]
|
113
|
+
if adjz > max_z
|
114
|
+
max_z = adjz
|
115
|
+
elsif adjz < min_z
|
116
|
+
min_z = adjz
|
117
|
+
end
|
118
|
+
end
|
119
|
+
}
|
120
|
+
return max_x, max_y, max_z, min_x, min_y, min_z
|
121
|
+
end
|
122
|
+
|
123
|
+
|
124
|
+
# Invoked during draw cycle to draw mesh
|
125
|
+
def draw
|
126
|
+
# FIXME mesh material
|
127
|
+
#Gl.glMaterial(Gl::GL_FRONT, Gl::GL_DIFFUSE, diffuse)
|
128
|
+
#Gl.glMaterial(Gl::GL_FRONT, Gl::GL_SPECULAR, specular)
|
129
|
+
#Gl.glMaterial(Gl::GL_FRONT, Gl::GL_EMISSIVE, emissive)
|
130
|
+
|
131
|
+
# translate coordinate system to center position of mesh
|
132
|
+
Gl.glTranslatef(*center)
|
133
|
+
|
134
|
+
# scale as specified
|
135
|
+
Gl.glScalef(*scale)
|
136
|
+
|
137
|
+
# FIXME rotate according to mesh and optional params
|
138
|
+
|
139
|
+
# draw mesh
|
140
|
+
Gl.glEnableClientState(Gl::GL_VERTEX_ARRAY);
|
141
|
+
Gl.glVertexPointer(3, Gl::GL_FLOAT, 0, @coord_point);
|
142
|
+
Gl.glDrawElements(Gl::GL_QUADS, @coord_index.size, Gl::GL_UNSIGNED_INT, @coord_index);
|
143
|
+
Gl.glDisableClientState(Gl::GL_VERTEX_ARRAY);
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# Resources constructs and methods
|
2
|
+
#
|
3
|
+
# Copyright (C) 2010 Mohammed Morsi <movitto@yahoo.com>
|
4
|
+
# Licensed under the GPLv3+ http://www.gnu.org/licenses/gpl.txt
|
5
|
+
|
6
|
+
require 'singleton'
|
7
|
+
|
8
|
+
module RAGE
|
9
|
+
|
10
|
+
# Singleton class managing all RAGE resources.
|
11
|
+
# Resources should be loaded here instead as opposed to creating them manually
|
12
|
+
# so as to mantain a central management repository during game execution.
|
13
|
+
class ResourcesManager
|
14
|
+
include Singleton
|
15
|
+
|
16
|
+
# Hash of ids -> resources we are managing
|
17
|
+
attr_accessor :resources
|
18
|
+
|
19
|
+
def initialize
|
20
|
+
@resources = {}
|
21
|
+
end
|
22
|
+
|
23
|
+
# Load resource from specified args and add to manager. Args may include
|
24
|
+
# * :type type of resource to load
|
25
|
+
# * :id unique identifier to give resource
|
26
|
+
# * :uri uri of resource if appropriate
|
27
|
+
# * :color resource color if appropriate
|
28
|
+
def load_resource(args = {})
|
29
|
+
type = args[:type]
|
30
|
+
id = args[:id]
|
31
|
+
|
32
|
+
resource = nil
|
33
|
+
|
34
|
+
if type == :mesh
|
35
|
+
uri = args[:uri]
|
36
|
+
data = Loader.load uri
|
37
|
+
resource = Mesh.new(data)
|
38
|
+
elsif type == :color
|
39
|
+
color = args[:color]
|
40
|
+
resource = Color.new(:rgb => color)
|
41
|
+
# elsif other resource types
|
42
|
+
end
|
43
|
+
|
44
|
+
@resources[id] = resource
|
45
|
+
return resource
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|