doom 0.8.0 → 0.11.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 (62) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +17 -0
  3. data/README.md +65 -4
  4. data/bin/doom +134 -24
  5. data/lib/doom/benchmark.rb +282 -0
  6. data/lib/doom/game/animations.rb +9 -2
  7. data/lib/doom/game/combat.rb +239 -153
  8. data/lib/doom/game/framebuffer_blitter.rb +38 -0
  9. data/lib/doom/game/geometry.rb +68 -0
  10. data/lib/doom/game/intermission.rb +22 -39
  11. data/lib/doom/game/item_pickup.rb +87 -75
  12. data/lib/doom/game/menu.rb +90 -85
  13. data/lib/doom/game/monster_ai.rb +150 -136
  14. data/lib/doom/game/player.rb +105 -0
  15. data/lib/doom/game/player_physics.rb +384 -0
  16. data/lib/doom/game/player_state.rb +38 -64
  17. data/lib/doom/game/random.rb +82 -0
  18. data/lib/doom/game/sector_actions.rb +169 -93
  19. data/lib/doom/game/sector_effects.rb +25 -18
  20. data/lib/doom/game/snapshot.rb +584 -0
  21. data/lib/doom/game/sound_engine.rb +33 -58
  22. data/lib/doom/game/state_hash.rb +125 -0
  23. data/lib/doom/game/ticcmd.rb +61 -0
  24. data/lib/doom/game/world.rb +420 -0
  25. data/lib/doom/map/data.rb +95 -0
  26. data/lib/doom/net/client.rb +204 -0
  27. data/lib/doom/net/desync_monitor.rb +101 -0
  28. data/lib/doom/net/game_server.rb +232 -0
  29. data/lib/doom/net/lockstep.rb +170 -0
  30. data/lib/doom/net/protocol.rb +350 -0
  31. data/lib/doom/net/session.rb +282 -0
  32. data/lib/doom/net/transport.rb +110 -0
  33. data/lib/doom/platform/gosu_window.rb +604 -832
  34. data/lib/doom/platform/sdl.rb +74 -0
  35. data/lib/doom/platform/window_logic.rb +61 -0
  36. data/lib/doom/render/font.rb +5 -3
  37. data/lib/doom/render/hardware_renderer.rb +547 -0
  38. data/lib/doom/render/ray_tracing/bvh.rb +103 -0
  39. data/lib/doom/render/ray_tracing/material_state.rb +66 -0
  40. data/lib/doom/render/ray_tracing/texture_atlas.rb +78 -0
  41. data/lib/doom/render/ray_tracing_renderer.rb +940 -0
  42. data/lib/doom/render/renderer.rb +466 -440
  43. data/lib/doom/render/renderer_factory.rb +50 -0
  44. data/lib/doom/render/screen_melt.rb +7 -7
  45. data/lib/doom/render/spinel_native/kernel.rb +780 -0
  46. data/lib/doom/render/spinel_native_renderer.rb +162 -0
  47. data/lib/doom/render/status_bar.rb +14 -10
  48. data/lib/doom/render/weapon_renderer.rb +9 -10
  49. data/lib/doom/render/world_mesh.rb +270 -0
  50. data/lib/doom/render/zbuffer_renderer.rb +151 -0
  51. data/lib/doom/version.rb +1 -1
  52. data/lib/doom/wad/flat.rb +1 -1
  53. data/lib/doom/wad/hud_graphics.rb +7 -3
  54. data/lib/doom/wad/palette.rb +3 -3
  55. data/lib/doom/wad/patch.rb +1 -1
  56. data/lib/doom/wad/reader.rb +12 -15
  57. data/lib/doom/wad/sound.rb +14 -6
  58. data/lib/doom/wad/sprite.rb +36 -35
  59. data/lib/doom/wad/texture.rb +5 -5
  60. data/lib/doom/wad_downloader.rb +38 -56
  61. data/lib/doom.rb +197 -16
  62. metadata +94 -7
@@ -0,0 +1,780 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Load the spinel_native gem. Prefer the local research checkout (it carries
4
+ # the native_prelude / native_state extensions this branch relies on) over any
5
+ # installed gem.
6
+ local_sn = File.expand_path("~/Projects/spin/spinel_native/lib")
7
+ $LOAD_PATH.unshift(local_sn) if File.directory?(local_sn) && !$LOAD_PATH.include?(local_sn)
8
+ require "spinel/native"
9
+
10
+ module Doom
11
+ module Render
12
+ module SpinelNative
13
+ # The classic renderer's hot path, ported to spinel_native. Each method
14
+ # mirrors a method of lib/doom/render/renderer.rb; the object graph is
15
+ # replaced by flat Integer/Float arrays kept as module state INSIDE the
16
+ # compiled kernel (native_state), exactly like renderer.rb keeps @map /
17
+ # @framebuffer / @ceiling_clip in ivars. `load_map` uploads the flattened
18
+ # map/textures once; `render` then draws a frame per call and returns the
19
+ # framebuffer -- no per-frame re-copy of the map across the boundary.
20
+ #
21
+ # Visplanes are filled inline (sn_flat_span / sn_sky_span) instead of being
22
+ # deferred through visplane objects -- the per-pixel sampling is identical,
23
+ # so the output matches the reference renderer.
24
+ #
25
+ # The whole module is one kernel (native_state), so every native method
26
+ # carries a signature. With SPINEL_NATIVE=off the same bodies run as plain
27
+ # Ruby against the module's own ivars, which is how the port is verified
28
+ # against renderer.rb before it is compiled.
29
+ module Kernel
30
+ extend Spinel::Native
31
+
32
+ PROJECTION = 160.0
33
+ SKY_XSCALE = 512.0 / Math::PI
34
+ SKY_YSCALE = 200.0 / 240.0
35
+
36
+ native_prelude <<~SRC
37
+ PROJECTION = 160.0
38
+ SKY_XSCALE = 512.0 / 3.14159265358979
39
+ SKY_YSCALE = 200.0 / 240.0
40
+ SRC
41
+
42
+ native_state do
43
+ @nodes = [0.0]; @verts = [0]; @segs = [0]; @subs = [0]
44
+ @lines = [0]; @sides = [0]; @secs = [0]
45
+ @wpx = [0]; @wtab = [0]; @fpx = [0]; @cmap = [0]
46
+ @fb = Array.new(76800, 0)
47
+ @cclip = Array.new(320, -1)
48
+ @fclip = Array.new(320, 240)
49
+ @wdepth = Array.new(320, 1.0e30)
50
+ @cdepth = Array.new(320, 1.0e30)
51
+ @fdepth = Array.new(320, 1.0e30)
52
+ @ccos = Array.new(320, 0.0)
53
+ @csin = Array.new(320, 0.0)
54
+ @cds = Array.new(320, 0.0)
55
+ @px = 0.0; @py = 0.0; @pz = 41.0; @ang = 0.0; @sina = 0.0; @cosa = 1.0
56
+ @ncount = 0; @skytex = 0
57
+ end
58
+
59
+ # load_map/render are called from Ruby; the getters hand back the wall
60
+ # pass's clip rows so the game's sprite code (kept in Ruby) can occlude
61
+ # sprites against the walls. Everything else stays internal to the kernel.
62
+ native_entries :load_map, :render, :get_cclip, :get_fclip, :get_wdepth
63
+
64
+ # Final ceiling/floor clip rows and per-column nearest-wall depth after a
65
+ # render, for the Ruby sprite pass (@sprite_ceiling_clip etc.).
66
+ native "() -> Array[Integer]"
67
+ def get_cclip
68
+ out = Array.new(320, 0)
69
+ i = 0
70
+ while i < 320
71
+ out[i] = @cclip[i]
72
+ i += 1
73
+ end
74
+ out
75
+ end
76
+
77
+ native "() -> Array[Integer]"
78
+ def get_fclip
79
+ out = Array.new(320, 0)
80
+ i = 0
81
+ while i < 320
82
+ out[i] = @fclip[i]
83
+ i += 1
84
+ end
85
+ out
86
+ end
87
+
88
+ native "() -> Array[Float]"
89
+ def get_wdepth
90
+ out = Array.new(320, 0.0)
91
+ i = 0
92
+ while i < 320
93
+ out[i] = @wdepth[i]
94
+ i += 1
95
+ end
96
+ out
97
+ end
98
+
99
+
100
+ # Upload the flattened map/textures once (kept in kernel state).
101
+ native "(Array[Float], Array[Integer], Array[Integer], Array[Integer], Array[Integer], " \
102
+ "Array[Integer], Array[Integer], Array[Integer], Array[Integer], Array[Integer], " \
103
+ "Array[Integer], Integer, Integer) -> Integer"
104
+ def load_map(nodes, verts, segs, subs, lines, sides, secs, wpx, wtab, fpx, cmap, ncount, skytex)
105
+ @nodes = nodes; @verts = verts; @segs = segs; @subs = subs
106
+ @lines = lines; @sides = sides; @secs = secs
107
+ @wpx = wpx; @wtab = wtab; @fpx = fpx; @cmap = cmap
108
+ @ncount = ncount; @skytex = skytex
109
+ @nodes.length
110
+ end
111
+
112
+ # Render one frame; returns the framebuffer (76800 palette indices).
113
+ native "(Integer, Integer, Integer, Float) -> Array[Integer]"
114
+ def render(px, py, pz, angle)
115
+ @px = px.to_f; @py = py.to_f; @pz = pz.to_f
116
+ @ang = angle
117
+ @sina = Math.sin(angle)
118
+ @cosa = Math.cos(angle)
119
+ i = 0
120
+ while i < 76800
121
+ @fb[i] = 0
122
+ i += 1
123
+ end
124
+ i = 0
125
+ while i < 320
126
+ @cclip[i] = -1
127
+ @fclip[i] = 240
128
+ @wdepth[i] = 1.0e30
129
+ i += 1
130
+ end
131
+ sn_precompute
132
+ sn_bsp(@ncount - 1)
133
+ # Return a locally-built copy with a concrete Array[Integer] type;
134
+ # spinel will not cross the boundary with an ivar of inferred (poly) type.
135
+ out = Array.new(76800, 0)
136
+ j = 0
137
+ while j < 76800
138
+ out[j] = @fb[j]
139
+ j += 1
140
+ end
141
+ out
142
+ end
143
+
144
+ # mirrors precompute_column_data (+ column_distscale from setup)
145
+ native "() -> Integer"
146
+ def sn_precompute
147
+ x = 0
148
+ while x < 320
149
+ ca = @ang - Math.atan2((x - 160).to_f, PROJECTION)
150
+ @ccos[x] = Math.cos(ca)
151
+ @csin[x] = Math.sin(ca)
152
+ dxx = (x - 160).to_f
153
+ @cds[x] = Math.sqrt(dxx * dxx + PROJECTION * PROJECTION) / PROJECTION
154
+ x += 1
155
+ end
156
+ 0
157
+ end
158
+
159
+ # mirrors render_bsp_node (front-to-back), iterative with an explicit stack
160
+ native "(Integer) -> Integer"
161
+ def sn_bsp(root)
162
+ stack = Array.new(2048, 0)
163
+ sp = 0
164
+ stack[sp] = root
165
+ sp = sp + 1
166
+ while sp > 0
167
+ sp = sp - 1
168
+ ni = stack[sp]
169
+ if (ni & 0x8000) != 0
170
+ sn_subsector(ni & 0x7FFF)
171
+ else
172
+ b = ni * 6
173
+ dx = @px - @nodes[b]
174
+ dy = @py - @nodes[b + 1]
175
+ left_v = dy * @nodes[b + 2]
176
+ right_v = dx * @nodes[b + 3]
177
+ if right_v >= left_v
178
+ stack[sp] = @nodes[b + 5].to_i
179
+ sp = sp + 1
180
+ stack[sp] = @nodes[b + 4].to_i
181
+ sp = sp + 1
182
+ else
183
+ stack[sp] = @nodes[b + 4].to_i
184
+ sp = sp + 1
185
+ stack[sp] = @nodes[b + 5].to_i
186
+ sp = sp + 1
187
+ end
188
+ end
189
+ end
190
+ 0
191
+ end
192
+
193
+ # mirrors render_subsector
194
+ native "(Integer) -> Integer"
195
+ def sn_subsector(index)
196
+ first = @subs[index * 2]
197
+ count = @subs[index * 2 + 1]
198
+ i = 0
199
+ while i < count
200
+ sn_seg(first + i)
201
+ i += 1
202
+ end
203
+ 0
204
+ end
205
+
206
+ # mirrors render_seg: transform, near-clip, project, backface, dispatch
207
+ native "(Integer) -> Integer"
208
+ def sn_seg(seg_i)
209
+ v1i = @segs[seg_i * 5]
210
+ v2i = @segs[seg_i * 5 + 1]
211
+ dx1 = @verts[v1i * 2].to_f - @px
212
+ dy1 = @verts[v1i * 2 + 1].to_f - @py
213
+ x1 = dx1 * @sina - dy1 * @cosa
214
+ y1 = dx1 * @cosa + dy1 * @sina
215
+ dx2 = @verts[v2i * 2].to_f - @px
216
+ dy2 = @verts[v2i * 2 + 1].to_f - @py
217
+ x2 = dx2 * @sina - dy2 * @cosa
218
+ y2 = dx2 * @cosa + dy2 * @sina
219
+
220
+ if y1 <= 0.0 && y2 <= 0.0
221
+ return 0
222
+ end
223
+ near = 1.0
224
+ if y1 < near || y2 < near
225
+ if y1 < near && y2 < near
226
+ return 0
227
+ elsif y1 < near
228
+ t = (near - y1) / (y2 - y1)
229
+ x1 = x1 + t * (x2 - x1)
230
+ y1 = near
231
+ elsif y2 < near
232
+ t = (near - y1) / (y2 - y1)
233
+ x2 = x1 + t * (x2 - x1)
234
+ y2 = near
235
+ end
236
+ end
237
+
238
+ sx1 = 160.0 + x1 * PROJECTION / y1
239
+ sx2 = 160.0 + x2 * PROJECTION / y2
240
+ if sx1 >= sx2
241
+ return 0
242
+ end
243
+ if sx2 < 0.0 || sx1 >= 320.0
244
+ return 0
245
+ end
246
+ x1i = sx1.to_i
247
+ if x1i < 0
248
+ x1i = 0
249
+ end
250
+ x2i = sx2.to_i
251
+ if x2i > 319
252
+ x2i = 319
253
+ end
254
+ if x1i > x2i
255
+ return 0
256
+ end
257
+
258
+ ldi = @segs[seg_i * 5 + 2]
259
+ dir = @segs[seg_i * 5 + 3]
260
+ sd_idx = dir == 0 ? @lines[ldi * 3] : @lines[ldi * 3 + 1]
261
+ if sd_idx < 0
262
+ return 0
263
+ end
264
+ fsec = @sides[sd_idx * 6]
265
+ flags = @lines[ldi * 3 + 2]
266
+ back_sd = -1
267
+ if (flags & 4) != 0
268
+ bsi = dir == 0 ? @lines[ldi * 3 + 1] : @lines[ldi * 3]
269
+ if bsi >= 0
270
+ back_sd = bsi
271
+ end
272
+ end
273
+
274
+ segdx = (@verts[v2i * 2] - @verts[v1i * 2]).to_f
275
+ segdy = (@verts[v2i * 2 + 1] - @verts[v1i * 2 + 1]).to_f
276
+ seg_len = Math.sqrt(segdx * segdx + segdy * segdy)
277
+
278
+ sn_seg_range(x1i, x2i, sx1, sx2, y1, y2, fsec, back_sd, sd_idx, flags, seg_i, seg_len)
279
+ 0
280
+ end
281
+
282
+ # mirrors draw_seg_range (texture-column coefficients, per-column loop)
283
+ native "(Integer, Integer, Float, Float, Float, Float, Integer, Integer, Integer, Integer, Integer, Float) -> Integer"
284
+ def sn_seg_range(x1, x2, sx1, sx2, dist1, dist2, fsec, back_sd, sd_idx, flags, seg_i, seg_len)
285
+ v1i = @segs[seg_i * 5]
286
+ v2i = @segs[seg_i * 5 + 1]
287
+ segdx = (@verts[v2i * 2] - @verts[v1i * 2]).to_f
288
+ segdy = (@verts[v2i * 2 + 1] - @verts[v1i * 2 + 1]).to_f
289
+ pxr = @px - @verts[v1i * 2].to_f
290
+ pyr = @py - @verts[v1i * 2 + 1].to_f
291
+ sin_a = @sina
292
+ cos_a = @cosa
293
+ c1 = cos_a * PROJECTION - sin_a * 160.0
294
+ c2 = sin_a * PROJECTION + cos_a * 160.0
295
+ tex_a = segdy * sin_a + segdx * cos_a
296
+ tex_b = segdy * c1 - segdx * c2
297
+ tex_e = pyr * sin_a + pxr * cos_a
298
+ tex_f = pyr * c1 - pxr * c2
299
+ tex_offset = @segs[seg_i * 5 + 4] + @sides[sd_idx * 6 + 4]
300
+
301
+ f_floor = @secs[fsec * 7]
302
+ f_ceil = @secs[fsec * 7 + 1]
303
+ f_csky = @secs[fsec * 7 + 5]
304
+ has_ceil = 0
305
+ if f_ceil.to_f > @pz
306
+ has_ceil = 1
307
+ end
308
+ if f_csky != 0
309
+ has_ceil = 1
310
+ end
311
+ has_floor = 0
312
+ if f_floor.to_f < @pz
313
+ has_floor = 1
314
+ end
315
+
316
+ x = x1
317
+ while x <= x2
318
+ if @cclip[x] < @fclip[x] - 1
319
+ t = 0.0
320
+ if sx2 != sx1
321
+ t = (x.to_f - sx1) / (sx2 - sx1)
322
+ end
323
+ if t < 0.0
324
+ t = 0.0
325
+ end
326
+ if t > 1.0
327
+ t = 1.0
328
+ end
329
+ dist = dist1
330
+ if dist1 > 0.0 && dist2 > 0.0
331
+ inv = (1.0 - t) / dist1 + t / dist2
332
+ dist = 1.0 / inv
333
+ else
334
+ if dist1 > 0.0
335
+ dist = dist1
336
+ else
337
+ dist = dist2
338
+ end
339
+ end
340
+ denom = tex_a * x.to_f + tex_b
341
+ ss = 0.0
342
+ da = denom
343
+ if da < 0.0
344
+ da = -da
345
+ end
346
+ if da >= 0.001
347
+ ss = (tex_e * x.to_f + tex_f) / denom
348
+ end
349
+ tex_col = (tex_offset.to_f + ss * seg_len).to_i
350
+ if dist >= 1.0
351
+ scale = PROJECTION / dist
352
+ front_floor = f_floor.to_f - @pz
353
+ front_ceil = f_ceil.to_f - @pz
354
+ fcf = 120.0 - front_ceil * scale
355
+ front_ceil_y = fcf.to_i
356
+ if fcf > front_ceil_y.to_f
357
+ front_ceil_y = front_ceil_y + 1
358
+ end
359
+ front_floor_y = (120.0 - front_floor * scale).to_i
360
+ ceil_y = front_ceil_y
361
+ if @cclip[x] + 1 > ceil_y
362
+ ceil_y = @cclip[x] + 1
363
+ end
364
+ floor_y = front_floor_y
365
+ if @fclip[x] - 1 < floor_y
366
+ floor_y = @fclip[x] - 1
367
+ end
368
+ if back_sd >= 0
369
+ sn_two_sided(x, ceil_y, floor_y, scale, dist, tex_col, fsec, back_sd, sd_idx, flags, has_ceil, has_floor)
370
+ else
371
+ sn_one_sided(x, ceil_y, floor_y, scale, dist, tex_col, fsec, sd_idx, flags, has_ceil, has_floor)
372
+ end
373
+ end
374
+ end
375
+ x += 1
376
+ end
377
+ 0
378
+ end
379
+
380
+ # mirrors draw_one_sided_seg_column
381
+ native "(Integer, Integer, Integer, Float, Float, Integer, Integer, Integer, Integer, Integer, Integer) -> Integer"
382
+ def sn_one_sided(x, ceil_y, floor_y, scale, dist, tex_col, fsec, sd_idx, flags, has_ceil, has_floor)
383
+ f_floor = @secs[fsec * 7]
384
+ f_ceil = @secs[fsec * 7 + 1]
385
+ f_light = @secs[fsec * 7 + 2]
386
+ f_ff = @secs[fsec * 7 + 3]
387
+ f_cf = @secs[fsec * 7 + 4]
388
+ f_csky = @secs[fsec * 7 + 5]
389
+ f_fsky = @secs[fsec * 7 + 6]
390
+
391
+ if has_ceil != 0
392
+ mt = @cclip[x] + 1
393
+ mb = ceil_y - 1
394
+ if @fclip[x] - 1 < mb
395
+ mb = @fclip[x] - 1
396
+ end
397
+ if mt <= mb
398
+ if f_csky != 0
399
+ sn_sky_span(x, mt, mb)
400
+ else
401
+ sn_flat_span(x, mt, mb, f_ceil, f_light, f_cf)
402
+ end
403
+ end
404
+ end
405
+
406
+ if has_floor != 0
407
+ mt = floor_y + 1
408
+ if @cclip[x] + 1 > mt
409
+ mt = @cclip[x] + 1
410
+ end
411
+ mb = @fclip[x] - 1
412
+ if mt <= mb
413
+ if f_fsky != 0
414
+ sn_sky_span(x, mt, mb)
415
+ else
416
+ sn_flat_span(x, mt, mb, f_floor, f_light, f_ff)
417
+ end
418
+ end
419
+ end
420
+
421
+ mid = @sides[sd_idx * 6 + 3]
422
+ yoff = @sides[sd_idx * 6 + 5]
423
+ if (flags & 16) != 0
424
+ th = 128
425
+ if mid >= 0
426
+ th = @wtab[mid * 3 + 2]
427
+ end
428
+ mid_tex_y = yoff + th - (f_ceil - f_floor)
429
+ else
430
+ mid_tex_y = yoff
431
+ end
432
+ sn_wall_column(x, ceil_y, floor_y, mid, dist, f_light, tex_col, mid_tex_y, scale, f_ceil.to_f)
433
+
434
+ if dist < @wdepth[x]
435
+ @wdepth[x] = dist
436
+ end
437
+ @cclip[x] = 240
438
+ @fclip[x] = -1
439
+ 0
440
+ end
441
+
442
+ # mirrors draw_two_sided_seg_column
443
+ native "(Integer, Integer, Integer, Float, Float, Integer, Integer, Integer, Integer, Integer, Integer, Integer) -> Integer"
444
+ def sn_two_sided(x, ceil_y, floor_y, scale, dist, tex_col, fsec, back_sd, sd_idx, flags, has_ceil, has_floor)
445
+ f_floor = @secs[fsec * 7]
446
+ f_ceil = @secs[fsec * 7 + 1]
447
+ f_light = @secs[fsec * 7 + 2]
448
+ f_ff = @secs[fsec * 7 + 3]
449
+ f_cf = @secs[fsec * 7 + 4]
450
+ f_csky = @secs[fsec * 7 + 5]
451
+ f_fsky = @secs[fsec * 7 + 6]
452
+ bsec = @sides[back_sd * 6]
453
+ b_floor = @secs[bsec * 7]
454
+ b_ceil = @secs[bsec * 7 + 1]
455
+ b_light = @secs[bsec * 7 + 2]
456
+ b_ff = @secs[bsec * 7 + 3]
457
+ b_cf = @secs[bsec * 7 + 4]
458
+ b_csky = @secs[bsec * 7 + 5]
459
+
460
+ back_floor = b_floor.to_f - @pz
461
+ back_ceil = b_ceil.to_f - @pz
462
+ back_ceil_y = (120.0 - back_ceil * scale).to_i
463
+ bfy = 120.0 - back_floor * scale
464
+ back_floor_y = bfy.to_i
465
+ if bfy > back_floor_y.to_f
466
+ back_floor_y = back_floor_y + 1
467
+ end
468
+
469
+ closed_door = 0
470
+ if b_ceil <= f_floor
471
+ closed_door = 1
472
+ end
473
+ if b_floor >= f_ceil
474
+ closed_door = 1
475
+ end
476
+ both_sky = 0
477
+ if f_csky != 0 && b_csky != 0
478
+ both_sky = 1
479
+ end
480
+ eff_front_ceil = f_ceil
481
+ if both_sky != 0
482
+ eff_front_ceil = b_ceil
483
+ end
484
+
485
+ should_mark_ceiling = 0
486
+ should_mark_floor = 0
487
+ if closed_door != 0
488
+ should_mark_ceiling = 1
489
+ should_mark_floor = 1
490
+ else
491
+ if eff_front_ceil != b_ceil
492
+ should_mark_ceiling = 1
493
+ end
494
+ if f_cf != b_cf
495
+ should_mark_ceiling = 1
496
+ end
497
+ if f_light != b_light
498
+ should_mark_ceiling = 1
499
+ end
500
+ if f_floor != b_floor
501
+ should_mark_floor = 1
502
+ end
503
+ if f_ff != b_ff
504
+ should_mark_floor = 1
505
+ end
506
+ if f_light != b_light
507
+ should_mark_floor = 1
508
+ end
509
+ end
510
+
511
+ if has_ceil != 0 && should_mark_ceiling != 0
512
+ mt = @cclip[x] + 1
513
+ mb = ceil_y - 1
514
+ if @fclip[x] - 1 < mb
515
+ mb = @fclip[x] - 1
516
+ end
517
+ if mt <= mb
518
+ if f_csky != 0
519
+ sn_sky_span(x, mt, mb)
520
+ else
521
+ sn_flat_span(x, mt, mb, f_ceil, f_light, f_cf)
522
+ end
523
+ end
524
+ end
525
+
526
+ if has_floor != 0 && should_mark_floor != 0
527
+ mt = floor_y + 1
528
+ mb = @fclip[x] - 1
529
+ if @cclip[x] + 1 > mt
530
+ mt = @cclip[x] + 1
531
+ end
532
+ if mt <= mb
533
+ if f_fsky != 0
534
+ sn_sky_span(x, mt, mb)
535
+ else
536
+ sn_flat_span(x, mt, mb, f_floor, f_light, f_ff)
537
+ end
538
+ end
539
+ end
540
+
541
+ if both_sky == 0 && f_ceil > b_ceil
542
+ upper = @sides[sd_idx * 6 + 1]
543
+ yoff = @sides[sd_idx * 6 + 5]
544
+ if (flags & 8) != 0
545
+ upper_tex_y = yoff
546
+ else
547
+ th = 128
548
+ if upper >= 0
549
+ th = @wtab[upper * 3 + 2]
550
+ end
551
+ upper_tex_y = yoff + b_ceil - f_ceil + th
552
+ end
553
+ sn_wall_column(x, ceil_y, back_ceil_y, upper, dist, f_light, tex_col, upper_tex_y, scale, f_ceil.to_f)
554
+ end
555
+
556
+ if f_floor < b_floor
557
+ lower = @sides[sd_idx * 6 + 2]
558
+ yoff = @sides[sd_idx * 6 + 5]
559
+ if (flags & 16) != 0
560
+ lower_tex_y = yoff + f_ceil - b_floor
561
+ else
562
+ lower_tex_y = yoff
563
+ end
564
+ sn_wall_column(x, back_floor_y, floor_y, lower, dist, f_light, tex_col, lower_tex_y, scale, b_floor.to_f)
565
+ end
566
+
567
+ if closed_door != 0
568
+ if dist < @wdepth[x]
569
+ @wdepth[x] = dist
570
+ end
571
+ @cclip[x] = 240
572
+ @fclip[x] = -1
573
+ else
574
+ if eff_front_ceil > b_ceil
575
+ if back_ceil_y > @cclip[x]
576
+ @cclip[x] = back_ceil_y
577
+ end
578
+ elsif eff_front_ceil < b_ceil
579
+ if ceil_y - 1 > @cclip[x]
580
+ @cclip[x] = ceil_y - 1
581
+ end
582
+ else
583
+ if f_cf != b_cf || f_light != b_light
584
+ if ceil_y - 1 > @cclip[x]
585
+ @cclip[x] = ceil_y - 1
586
+ end
587
+ end
588
+ end
589
+ if f_floor < b_floor
590
+ if back_floor_y < @fclip[x]
591
+ @fclip[x] = back_floor_y
592
+ end
593
+ elsif f_floor > b_floor
594
+ if floor_y + 1 < @fclip[x]
595
+ @fclip[x] = floor_y + 1
596
+ end
597
+ else
598
+ if f_ff != b_ff || f_light != b_light
599
+ if floor_y + 1 < @fclip[x]
600
+ @fclip[x] = floor_y + 1
601
+ end
602
+ end
603
+ end
604
+ end
605
+ 0
606
+ end
607
+
608
+ # mirrors draw_wall_column_ex (top-pegged texel walk, transparent skip)
609
+ native "(Integer, Integer, Integer, Integer, Float, Integer, Integer, Integer, Float, Float) -> Integer"
610
+ def sn_wall_column(x, y1, y2, tex, dist, light, tex_col, tex_y_start, scale, world_top)
611
+ if y1 > y2
612
+ return 0
613
+ end
614
+ if tex < 0
615
+ return 0
616
+ end
617
+ clip_top = @cclip[x] + 1
618
+ clip_bot = @fclip[x] - 1
619
+ if y1 < clip_top
620
+ y1 = clip_top
621
+ end
622
+ if y2 > clip_bot
623
+ y2 = clip_bot
624
+ end
625
+ if y1 > y2
626
+ return 0
627
+ end
628
+ base = @wtab[tex * 3]
629
+ tw = @wtab[tex * 3 + 1]
630
+ th = @wtab[tex * 3 + 2]
631
+ wlnum = light >> 4
632
+ if wlnum > 15
633
+ wlnum = 15
634
+ end
635
+ if wlnum < 0
636
+ wlnum = 0
637
+ end
638
+ wstart = (15 - wlnum) * 4
639
+ wsi = 47
640
+ if dist > 0.0
641
+ wsi = (2560.0 / dist).to_i
642
+ end
643
+ if wsi > 47
644
+ wsi = 47
645
+ end
646
+ if wsi < 0
647
+ wsi = 0
648
+ end
649
+ lvl = wstart - wsi / 2
650
+ if lvl < 0
651
+ lvl = 0
652
+ end
653
+ if lvl > 31
654
+ lvl = 31
655
+ end
656
+ tex_x = tex_col % tw
657
+ if tex_x < 0
658
+ tex_x = tex_x + tw
659
+ end
660
+ tex_step = 1.0 / scale
661
+ unclipped_y1 = 120.0 - (world_top - @pz) * scale
662
+ tex_y_at_y1 = tex_y_start.to_f + (y1.to_f - unclipped_y1) * tex_step
663
+ y = y1
664
+ while y <= y2
665
+ off = y - y1
666
+ ty = (tex_y_at_y1 + off.to_f * tex_step).to_i % th
667
+ if ty < 0
668
+ ty = ty + th
669
+ end
670
+ c = @wpx[base + tex_x * th + ty]
671
+ if c >= 0
672
+ @fb[y * 320 + x] = @cmap[lvl * 256 + c]
673
+ end
674
+ y += 1
675
+ end
676
+ 0
677
+ end
678
+
679
+ # mirrors draw_span's per-pixel flat sampling, done per column inline
680
+ native "(Integer, Integer, Integer, Integer, Integer, Integer) -> Integer"
681
+ def sn_flat_span(x, y1, y2, plane_h, light, flat_idx)
682
+ if y1 > y2
683
+ return 0
684
+ end
685
+ ph = plane_h.to_f - @pz
686
+ if ph < 0.0
687
+ ph = -ph
688
+ end
689
+ if ph == 0.0
690
+ return 0
691
+ end
692
+ lnum = light >> 4
693
+ if lnum > 15
694
+ lnum = 15
695
+ end
696
+ if lnum < 0
697
+ lnum = 0
698
+ end
699
+ startmap = (15 - lnum) * 4
700
+ fbase = flat_idx * 4096
701
+ y = y1
702
+ if y < 0
703
+ y = 0
704
+ end
705
+ yy2 = y2
706
+ if yy2 > 239
707
+ yy2 = 239
708
+ end
709
+ while y <= yy2
710
+ dy = y - 120
711
+ if dy != 0
712
+ absdy = dy
713
+ if absdy < 0
714
+ absdy = -absdy
715
+ end
716
+ perp = ph * PROJECTION / absdy.to_f
717
+ rd = perp * @cds[x]
718
+ tx = (@px + rd * @ccos[x]).to_i & 63
719
+ ty = (0.0 - @py - rd * @csin[x]).to_i & 63
720
+ zc = (perp / 16.0).to_i
721
+ if zc > 127
722
+ zc = 127
723
+ end
724
+ if zc < 0
725
+ zc = 0
726
+ end
727
+ lvl = (startmap.to_f - 10.0 / (zc + 1).to_f).to_i
728
+ if lvl < 0
729
+ lvl = 0
730
+ end
731
+ if lvl > 31
732
+ lvl = 31
733
+ end
734
+ @fb[y * 320 + x] = @cmap[lvl * 256 + @fpx[fbase + ty * 64 + tx]]
735
+ end
736
+ y += 1
737
+ end
738
+ 0
739
+ end
740
+
741
+ # mirrors draw_sky_plane's per-column sky sampling (full bright)
742
+ native "(Integer, Integer, Integer) -> Integer"
743
+ def sn_sky_span(x, y1, y2)
744
+ if y1 > y2
745
+ return 0
746
+ end
747
+ off = @wtab[@skytex * 3]
748
+ sw = @wtab[@skytex * 3 + 1]
749
+ sh = @wtab[@skytex * 3 + 2]
750
+ ca = @ang - Math.atan2((x - 160).to_f, PROJECTION)
751
+ sx = (ca * SKY_XSCALE).to_i % sw
752
+ if sx < 0
753
+ sx = sx + sw
754
+ end
755
+ y = y1
756
+ if y < 0
757
+ y = 0
758
+ end
759
+ yy2 = y2
760
+ if yy2 > 239
761
+ yy2 = 239
762
+ end
763
+ while y <= yy2
764
+ ty = (100.0 + (y - 120).to_f * SKY_YSCALE).to_i % sh
765
+ if ty < 0
766
+ ty = ty + sh
767
+ end
768
+ c = @wpx[off + sx * sh + ty]
769
+ if c >= 0
770
+ @fb[y * 320 + x] = c
771
+ end
772
+ y += 1
773
+ end
774
+ 0
775
+ end
776
+
777
+ end
778
+ end
779
+ end
780
+ end