quake-rb 0.1.0 → 0.2.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.
- checksums.yaml +4 -4
- data/README.md +136 -0
- data/bin/quake +18 -1
- data/lib/quake/bsp/reader.rb +241 -38
- data/lib/quake/bsp/types.rb +49 -5
- data/lib/quake/bsp/vis.rb +2 -137
- data/lib/quake/camera.rb +73 -16
- data/lib/quake/entity.rb +413 -25
- data/lib/quake/game/brush_entities.rb +1814 -65
- data/lib/quake/game/engine.rb +4376 -57
- data/lib/quake/game/item_pickups.rb +584 -33
- data/lib/quake/game/player_state.rb +518 -21
- data/lib/quake/mdl/reader.rb +88 -7
- data/lib/quake/mdl/types.rb +2 -2
- data/lib/quake/pak/reader.rb +9 -3
- data/lib/quake/palette.rb +3 -4
- data/lib/quake/physics/hull_trace.rb +77 -4
- data/lib/quake/physics/player.rb +409 -112
- data/lib/quake/renderer/anorm_dots.rb +554 -0
- data/lib/quake/renderer/gl_alias_model.rb +418 -69
- data/lib/quake/renderer/gl_brush_model.rb +129 -17
- data/lib/quake/renderer/gl_hud.rb +384 -31
- data/lib/quake/renderer/gl_lightmap.rb +224 -48
- data/lib/quake/renderer/gl_particles.rb +390 -50
- data/lib/quake/renderer/gl_sky.rb +83 -10
- data/lib/quake/renderer/gl_texture_manager.rb +38 -4
- data/lib/quake/renderer/gl_textured.rb +53 -31
- data/lib/quake/renderer/gl_view_blend.rb +130 -0
- data/lib/quake/renderer/gl_viewmodel.rb +46 -11
- data/lib/quake/renderer/gl_warp_subdivision.rb +74 -0
- data/lib/quake/renderer/gl_water.rb +4 -76
- data/lib/quake/sound/events.rb +126 -2
- data/lib/quake/sound/mixer.rb +44 -9
- data/lib/quake/version.rb +1 -1
- data/lib/quake/wad/reader.rb +18 -8
- data/lib/quake/window.rb +3 -0
- metadata +5 -1
data/lib/quake/bsp/vis.rb
CHANGED
|
@@ -71,119 +71,9 @@ module Quake
|
|
|
71
71
|
end
|
|
72
72
|
end
|
|
73
73
|
|
|
74
|
-
# Compute a "fat" PVS by unioning the PVS of every leaf within ~8 units
|
|
75
|
-
# of the given point. Used near translucent water surfaces so the
|
|
76
|
-
# underwater leaves (which standard PVS excludes from above-water
|
|
77
|
-
# leaves) are included; without this the cave bed isn't drawn and
|
|
78
|
-
# alpha-blended water shows the clear color through gaps.
|
|
79
|
-
# Port of quakespasm SV_FatPVS / SV_AddToFatPVS.
|
|
80
|
-
def self.fat_pvs(level, point)
|
|
81
|
-
num_leafs = level.leafs.size - 1
|
|
82
|
-
result = Set.new
|
|
83
|
-
add_to_fat_pvs(level, point, 0, num_leafs, result)
|
|
84
|
-
result
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
def self.add_to_fat_pvs(level, point, node_index, num_leafs, result)
|
|
88
|
-
loop do
|
|
89
|
-
if node_index < 0
|
|
90
|
-
leaf_index = ~node_index
|
|
91
|
-
return if leaf_index == 0 # skip the universal solid leaf
|
|
92
|
-
leaf = level.leafs[leaf_index]
|
|
93
|
-
return if leaf.nil?
|
|
94
|
-
result.merge(decompress_pvs(level.visibility, leaf.vis_offset, num_leafs))
|
|
95
|
-
return
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
node = level.nodes[node_index]
|
|
99
|
-
plane = level.planes[node.plane_index]
|
|
100
|
-
d = point.dot(plane.normal) - plane.dist
|
|
101
|
-
|
|
102
|
-
if d > 8.0
|
|
103
|
-
node_index = node.children[0]
|
|
104
|
-
elsif d < -8.0
|
|
105
|
-
node_index = node.children[1]
|
|
106
|
-
else
|
|
107
|
-
# Straddling the plane within the fat radius - descend both sides
|
|
108
|
-
add_to_fat_pvs(level, point, node.children[0], num_leafs, result)
|
|
109
|
-
node_index = node.children[1]
|
|
110
|
-
end
|
|
111
|
-
end
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
# True if any of the given leaf's marksurfaces is a turbulent (liquid)
|
|
115
|
-
# surface. Quakespasm's "near water portal" check.
|
|
116
|
-
def self.near_liquid_portal?(level, leaf)
|
|
117
|
-
return false if leaf.nil?
|
|
118
|
-
leaf.num_marksurfaces.times do |i|
|
|
119
|
-
face_idx = level.marksurfaces[leaf.first_marksurface + i]
|
|
120
|
-
face = level.faces[face_idx]
|
|
121
|
-
next if face.nil?
|
|
122
|
-
ti = level.texinfo[face.texinfo_index]
|
|
123
|
-
next if ti.nil?
|
|
124
|
-
tex = level.textures[ti.miptex_index]
|
|
125
|
-
return true if tex && tex.name.start_with?("*")
|
|
126
|
-
end
|
|
127
|
-
false
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
# Precompute, for each liquid (turb) face, the two leaves it physically
|
|
131
|
-
# separates (computed via BSP traversal of a point on either side of
|
|
132
|
-
# the face's plane). The standard Quake VIS compiler treats water as
|
|
133
|
-
# opaque, so above-water and underwater leaves never see each other in
|
|
134
|
-
# the PVS. Marksurface lists are also unreliable here - many water
|
|
135
|
-
# faces are marked in only one leaf - so we use geometry instead. We
|
|
136
|
-
# use this index at runtime to "vis-through" water surfaces: when a
|
|
137
|
-
# water face is in the visible set, also include the leaves on its
|
|
138
|
-
# other side. Without this, translucent water alpha-blends against
|
|
139
|
-
# the clear color where the cave bed should be.
|
|
140
|
-
def self.liquid_face_to_leaves(level)
|
|
141
|
-
@liquid_face_to_leaves_cache ||= {}
|
|
142
|
-
cached = @liquid_face_to_leaves_cache[level.object_id]
|
|
143
|
-
return cached if cached
|
|
144
|
-
|
|
145
|
-
index = {}
|
|
146
|
-
level.faces.each_with_index do |face, face_idx|
|
|
147
|
-
next if face.nil?
|
|
148
|
-
ti = level.texinfo[face.texinfo_index]
|
|
149
|
-
next if ti.nil?
|
|
150
|
-
tex = level.textures[ti.miptex_index]
|
|
151
|
-
next unless tex && tex.name.start_with?("*")
|
|
152
|
-
|
|
153
|
-
# Compute face center
|
|
154
|
-
cx = cy = cz = 0.0
|
|
155
|
-
n = face.num_edges
|
|
156
|
-
n.times do |i|
|
|
157
|
-
se = level.surfedges[face.first_edge + i]
|
|
158
|
-
edge = level.edges[se.abs]
|
|
159
|
-
v = se >= 0 ? level.vertices[edge.v0] : level.vertices[edge.v1]
|
|
160
|
-
cx += v.x; cy += v.y; cz += v.z
|
|
161
|
-
end
|
|
162
|
-
cx /= n; cy /= n; cz /= n
|
|
163
|
-
|
|
164
|
-
plane = level.planes[face.plane_index]
|
|
165
|
-
# Step a small distance along + and - the plane normal, find the
|
|
166
|
-
# leaf each point lands in.
|
|
167
|
-
offsets = [1.0, -1.0]
|
|
168
|
-
leaves = offsets.map do |o|
|
|
169
|
-
pt = Math::Vec3.new(
|
|
170
|
-
cx + plane.normal.x * o,
|
|
171
|
-
cy + plane.normal.y * o,
|
|
172
|
-
cz + plane.normal.z * o
|
|
173
|
-
)
|
|
174
|
-
point_in_leaf(level, pt)
|
|
175
|
-
end.uniq.reject { |l| l == 0 } # skip solid leaf
|
|
176
|
-
|
|
177
|
-
index[face_idx] = leaves
|
|
178
|
-
end
|
|
179
|
-
|
|
180
|
-
@liquid_face_to_leaves_cache[level.object_id] = index
|
|
181
|
-
index
|
|
182
|
-
end
|
|
183
|
-
|
|
184
74
|
# Mark which faces are visible from the given leaf.
|
|
185
75
|
# Returns a Set of face indices that should be rendered.
|
|
186
|
-
def self.visible_faces(level, leaf_index
|
|
76
|
+
def self.visible_faces(level, leaf_index)
|
|
187
77
|
leaf = level.leafs[leaf_index]
|
|
188
78
|
return Set.new if leaf.nil?
|
|
189
79
|
|
|
@@ -195,43 +85,18 @@ module Quake
|
|
|
195
85
|
# solid space (noclip, edge cases) still sees the world.
|
|
196
86
|
visible_leafs = if leaf_index == 0
|
|
197
87
|
all_visible(num_leafs)
|
|
198
|
-
elsif point && near_liquid_portal?(level, leaf)
|
|
199
|
-
# Camera leaf touches a liquid surface. Use FatPVS
|
|
200
|
-
# so the underwater leaves are included and
|
|
201
|
-
# translucent water doesn't reveal void where the
|
|
202
|
-
# bed should be.
|
|
203
|
-
fat_pvs(level, point)
|
|
204
88
|
else
|
|
205
89
|
decompress_pvs(level.visibility, leaf.vis_offset, num_leafs)
|
|
206
90
|
end
|
|
207
91
|
|
|
208
92
|
face_set = Set.new
|
|
209
|
-
|
|
210
|
-
leafs_to_walk << leaf_index # also include current leaf
|
|
211
|
-
|
|
212
|
-
# Vis-through water: any visible water face brings the leaves on its
|
|
213
|
-
# other side into view too. Standard Quake PVS treats water as
|
|
214
|
-
# opaque, so without this expansion translucent water from above
|
|
215
|
-
# blends against void instead of the underwater cave bed.
|
|
216
|
-
liquid_index = liquid_face_to_leaves(level)
|
|
217
|
-
seen_leafs = Set.new
|
|
218
|
-
worklist = leafs_to_walk.to_a
|
|
219
|
-
|
|
220
|
-
while (li = worklist.shift)
|
|
221
|
-
next unless seen_leafs.add?(li)
|
|
93
|
+
visible_leafs.each do |li|
|
|
222
94
|
vl = level.leafs[li]
|
|
223
95
|
next if vl.nil?
|
|
224
96
|
|
|
225
97
|
vl.num_marksurfaces.times do |i|
|
|
226
98
|
face_idx = level.marksurfaces[vl.first_marksurface + i]
|
|
227
99
|
face_set << face_idx
|
|
228
|
-
|
|
229
|
-
# If this is a water face, queue the leaves on its other side.
|
|
230
|
-
other_leaves = liquid_index[face_idx]
|
|
231
|
-
next unless other_leaves
|
|
232
|
-
other_leaves.each do |other_leaf|
|
|
233
|
-
worklist << other_leaf unless seen_leafs.include?(other_leaf)
|
|
234
|
-
end
|
|
235
100
|
end
|
|
236
101
|
end
|
|
237
102
|
|
data/lib/quake/camera.rb
CHANGED
|
@@ -5,7 +5,7 @@ require "glu"
|
|
|
5
5
|
|
|
6
6
|
module Quake
|
|
7
7
|
class Camera
|
|
8
|
-
attr_accessor :position, :yaw, :pitch
|
|
8
|
+
attr_accessor :position, :yaw, :pitch, :roll
|
|
9
9
|
attr_reader :fov, :near, :far
|
|
10
10
|
|
|
11
11
|
SPEED = 320.0 # units per second (Quake run speed)
|
|
@@ -13,10 +13,11 @@ module Quake
|
|
|
13
13
|
MAX_MOUSE_DELTA = 50 # clamp insane deltas (e.g. first frame warp)
|
|
14
14
|
|
|
15
15
|
def initialize(position: Math::Vec3::ORIGIN, yaw: 0.0, pitch: 0.0,
|
|
16
|
-
fov: 90.0, near: 4.0, far:
|
|
16
|
+
fov: 90.0, near: 4.0, far: 6144.0)
|
|
17
17
|
@position = position
|
|
18
18
|
@yaw = yaw
|
|
19
19
|
@pitch = pitch
|
|
20
|
+
@roll = 0.0
|
|
20
21
|
@fov = fov
|
|
21
22
|
@near = near
|
|
22
23
|
@far = far
|
|
@@ -24,25 +25,15 @@ module Quake
|
|
|
24
25
|
end
|
|
25
26
|
|
|
26
27
|
def forward
|
|
27
|
-
|
|
28
|
-
# yaw rotates around Z, pitch rotates around Y
|
|
29
|
-
ry = deg2rad(@yaw)
|
|
30
|
-
rp = deg2rad(@pitch)
|
|
31
|
-
cp = ::Math.cos(rp)
|
|
32
|
-
Math::Vec3.new(
|
|
33
|
-
::Math.cos(ry) * cp,
|
|
34
|
-
::Math.sin(ry) * cp,
|
|
35
|
-
-::Math.sin(rp)
|
|
36
|
-
)
|
|
28
|
+
quake_angle_vectors.fetch(:forward)
|
|
37
29
|
end
|
|
38
30
|
|
|
39
31
|
def right
|
|
40
|
-
|
|
41
|
-
Math::Vec3.new(::Math.cos(ry), ::Math.sin(ry), 0.0)
|
|
32
|
+
quake_angle_vectors.fetch(:right)
|
|
42
33
|
end
|
|
43
34
|
|
|
44
35
|
def up
|
|
45
|
-
|
|
36
|
+
quake_angle_vectors.fetch(:up)
|
|
46
37
|
end
|
|
47
38
|
|
|
48
39
|
def move_forward(dt)
|
|
@@ -72,7 +63,39 @@ module Quake
|
|
|
72
63
|
def apply_projection_gl(aspect)
|
|
73
64
|
GL.MatrixMode(GL::PROJECTION)
|
|
74
65
|
GL.LoadIdentity
|
|
75
|
-
GLU.Perspective(
|
|
66
|
+
GLU.Perspective(fov_y(aspect), aspect, @near, @far)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Quake's fov cvar is the horizontal FOV on a 4:3 screen. Like
|
|
70
|
+
# Quakespasm's fov_adapt (Hor+), the vertical FOV stays at its 4:3
|
|
71
|
+
# value on wide screens and the horizontal FOV widens instead --
|
|
72
|
+
# otherwise widescreen crops the view and the weapon model.
|
|
73
|
+
def fov_y(_aspect = nil)
|
|
74
|
+
2.0 * ::Math.atan(::Math.tan(deg2rad(@fov) / 2.0) * 0.75) * 180.0 / ::Math::PI
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Actual horizontal FOV for a given aspect (follows from fov_y)
|
|
78
|
+
def fov_x(aspect)
|
|
79
|
+
2.0 * ::Math.atan(::Math.tan(deg2rad(fov_y) / 2.0) * aspect) * 180.0 / ::Math::PI
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# R_SetFrustum: 4 view-frustum planes as [normal, dist] pairs, where a
|
|
83
|
+
# point is inside when normal.dot(point) - dist >= 0.
|
|
84
|
+
def frustum_planes(aspect)
|
|
85
|
+
vecs = quake_angle_vectors
|
|
86
|
+
forward = vecs.fetch(:forward)
|
|
87
|
+
right = vecs.fetch(:right)
|
|
88
|
+
up = vecs.fetch(:up)
|
|
89
|
+
|
|
90
|
+
fx = fov_x(aspect)
|
|
91
|
+
fy = fov_y
|
|
92
|
+
normals = [
|
|
93
|
+
rotate_around_axis(forward, up, -(90.0 - fx / 2.0)),
|
|
94
|
+
rotate_around_axis(forward, up, 90.0 - fx / 2.0),
|
|
95
|
+
rotate_around_axis(forward, right, 90.0 - fy / 2.0),
|
|
96
|
+
rotate_around_axis(forward, right, -(90.0 - fy / 2.0))
|
|
97
|
+
]
|
|
98
|
+
normals.map { |normal| [normal, normal.dot(@position)] }
|
|
76
99
|
end
|
|
77
100
|
|
|
78
101
|
def apply_gl
|
|
@@ -85,6 +108,7 @@ module Quake
|
|
|
85
108
|
GL.Rotatef(-90.0, 1.0, 0.0, 0.0) # Z-up -> Y-up
|
|
86
109
|
GL.Rotatef(90.0, 0.0, 0.0, 1.0) # X-forward -> GL
|
|
87
110
|
|
|
111
|
+
GL.Rotatef(-@roll, 1.0, 0.0, 0.0)
|
|
88
112
|
GL.Rotatef(-@pitch, 0.0, 1.0, 0.0)
|
|
89
113
|
GL.Rotatef(-@yaw, 0.0, 0.0, 1.0)
|
|
90
114
|
GL.Translatef(-@position.x, -@position.y, -@position.z)
|
|
@@ -95,5 +119,38 @@ module Quake
|
|
|
95
119
|
def deg2rad(deg)
|
|
96
120
|
deg * ::Math::PI / 180.0
|
|
97
121
|
end
|
|
122
|
+
|
|
123
|
+
# Rodrigues rotation of v around unit axis by degrees
|
|
124
|
+
def rotate_around_axis(v, axis, degrees)
|
|
125
|
+
rad = deg2rad(degrees)
|
|
126
|
+
cos = ::Math.cos(rad)
|
|
127
|
+
sin = ::Math.sin(rad)
|
|
128
|
+
cross = axis.cross(v)
|
|
129
|
+
dot = axis.dot(v)
|
|
130
|
+
Math::Vec3.new(
|
|
131
|
+
v.x * cos + cross.x * sin + axis.x * dot * (1.0 - cos),
|
|
132
|
+
v.y * cos + cross.y * sin + axis.y * dot * (1.0 - cos),
|
|
133
|
+
v.z * cos + cross.z * sin + axis.z * dot * (1.0 - cos)
|
|
134
|
+
)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def quake_angle_vectors
|
|
138
|
+
sy = ::Math.sin(deg2rad(@yaw))
|
|
139
|
+
cy = ::Math.cos(deg2rad(@yaw))
|
|
140
|
+
sp = ::Math.sin(deg2rad(@pitch))
|
|
141
|
+
cp = ::Math.cos(deg2rad(@pitch))
|
|
142
|
+
sr = ::Math.sin(deg2rad(@roll))
|
|
143
|
+
cr = ::Math.cos(deg2rad(@roll))
|
|
144
|
+
|
|
145
|
+
{
|
|
146
|
+
forward: Math::Vec3.new(cp * cy, cp * sy, -sp),
|
|
147
|
+
right: Math::Vec3.new((-sr * sp * cy) + (cr * sy),
|
|
148
|
+
(-sr * sp * sy) - (cr * cy),
|
|
149
|
+
-sr * cp),
|
|
150
|
+
up: Math::Vec3.new((cr * sp * cy) + (sr * sy),
|
|
151
|
+
(cr * sp * sy) - (sr * cy),
|
|
152
|
+
cr * cp)
|
|
153
|
+
}
|
|
154
|
+
end
|
|
98
155
|
end
|
|
99
156
|
end
|