Class: Doom::Render::Renderer

Inherits:
Object
  • Object
show all
Defined in:
lib/doom/render/renderer.rb

Defined Under Namespace

Classes: ProjectileStub

Constant Summary collapse

SKY_TEXTUREMID =

Render sky ceiling as columns (column-based like walls, not spans) Sky texture mid-point: texel row at screen center. Chocolate Doom: skytexturemid = SCREENHEIGHT/2 = 100 (for 200px screen). Scale factor: our 240px screen maps to DOOM’s 200px sky coordinates.

100.0
SKY_YSCALE =

0.833 - maps our pixels to DOOM’s 200px space

200.0 / SCREEN_HEIGHT
SKY_XSCALE =

Chocolate Doom: ANGLETOSKYSHIFT=22 gives 4 sky repetitions per 360 degrees. Full circle (2pi) * 512/pi = 1024 columns, masked to 256 = 4 repetitions.

512.0 / Math::PI

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(wad, map, textures, palette, colormap, flats, sprites = nil, animations = nil) ⇒ Renderer

Returns a new instance of Renderer.



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/doom/render/renderer.rb', line 60

def initialize(wad, map, textures, palette, colormap, flats, sprites = nil, animations = nil)
  @wad = wad
  @map = map
  @textures = textures
  @palette = palette
  @colormap = colormap
  @flats = flats.to_h { |f| [f.name, f] }
  @sprites = sprites
  @animations = animations
  @hidden_things = nil
  @combat = nil
  @monster_ai = nil
  @leveltime = 0

  @framebuffer = Array.new(SCREEN_WIDTH * SCREEN_HEIGHT, 0)

  @player_x = 0.0
  @player_y = 0.0
  @player_z = 41.0
  @player_angle = 0.0

  # Projection constant - distance to projection plane
  @projection = HALF_WIDTH / Math.tan(FOV * Math::PI / 360.0)

  # Precomputed column data (cached based on player angle)
  @column_cos = Array.new(SCREEN_WIDTH)
  @column_sin = Array.new(SCREEN_WIDTH)
  @cached_player_angle = nil

  # Column distance scale is constant (doesn't depend on player angle)
  @column_distscale = Array.new(SCREEN_WIDTH) do |x|
    dx = x - HALF_WIDTH
    Math.sqrt(dx * dx + @projection * @projection) / @projection
  end

  # Clipping arrays
  @ceiling_clip = Array.new(SCREEN_WIDTH, -1)
  @floor_clip = Array.new(SCREEN_WIDTH, SCREEN_HEIGHT)

  # Sprite clip arrays (copy of wall clips for sprite clipping)
  @sprite_ceiling_clip = Array.new(SCREEN_WIDTH, -1)
  @sprite_floor_clip = Array.new(SCREEN_WIDTH, SCREEN_HEIGHT)

  # Wall depth array - tracks distance to nearest wall at each column
  @wall_depth = Array.new(SCREEN_WIDTH, Float::INFINITY)
  @sprite_wall_depth = Array.new(SCREEN_WIDTH, Float::INFINITY)

  # Preallocated y_slope arrays for floor/ceiling rendering (avoids per-frame allocation)
  @y_slope_ceil = Array.new(HALF_HEIGHT + 1, 0.0)
  @y_slope_floor = Array.new(HALF_HEIGHT + 1, 0.0)
end

Instance Attribute Details

#combat=(value) ⇒ Object (writeonly)

Sets the attribute combat

Parameters:

  • value

    the value to set the attribute combat to.



113
114
115
# File 'lib/doom/render/renderer.rb', line 113

def combat=(value)
  @combat = value
end

#cos_angleObject (readonly)

Returns the value of attribute cos_angle.



112
113
114
# File 'lib/doom/render/renderer.rb', line 112

def cos_angle
  @cos_angle
end

#framebufferObject (readonly)

Returns the value of attribute framebuffer.



58
59
60
# File 'lib/doom/render/renderer.rb', line 58

def framebuffer
  @framebuffer
end

#hidden_things=(value) ⇒ Object (writeonly)

Sets the attribute hidden_things

Parameters:

  • value

    the value to set the attribute hidden_things to.



113
114
115
# File 'lib/doom/render/renderer.rb', line 113

def hidden_things=(value)
  @hidden_things = value
end

#leveltime=(value) ⇒ Object (writeonly)

Sets the attribute leveltime

Parameters:

  • value

    the value to set the attribute leveltime to.



113
114
115
# File 'lib/doom/render/renderer.rb', line 113

def leveltime=(value)
  @leveltime = value
end

#monster_ai=(value) ⇒ Object (writeonly)

Sets the attribute monster_ai

Parameters:

  • value

    the value to set the attribute monster_ai to.



113
114
115
# File 'lib/doom/render/renderer.rb', line 113

def monster_ai=(value)
  @monster_ai = value
end

#player_xObject (readonly)

Returns the value of attribute player_x.



112
113
114
# File 'lib/doom/render/renderer.rb', line 112

def player_x
  @player_x
end

#player_yObject (readonly)

Returns the value of attribute player_y.



112
113
114
# File 'lib/doom/render/renderer.rb', line 112

def player_y
  @player_y
end

#player_zObject (readonly)

Returns the value of attribute player_z.



112
113
114
# File 'lib/doom/render/renderer.rb', line 112

def player_z
  @player_z
end

#sin_angleObject (readonly)

Returns the value of attribute sin_angle.



112
113
114
# File 'lib/doom/render/renderer.rb', line 112

def sin_angle
  @sin_angle
end

Instance Method Details

#check_plane(plane, start_x, stop_x) ⇒ Object

R_CheckPlane equivalent - check if columns in range are already marked If overlap exists, create a new visplane; otherwise update minx/maxx



457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
# File 'lib/doom/render/renderer.rb', line 457

def check_plane(plane, start_x, stop_x)
  return plane unless plane

  # Calculate intersection and union of column ranges
  if start_x < plane.minx
    intrl = plane.minx
    unionl = start_x
  else
    unionl = plane.minx
    intrl = start_x
  end

  if stop_x > plane.maxx
    intrh = plane.maxx
    unionh = stop_x
  else
    unionh = plane.maxx
    intrh = stop_x
  end

  # Check if any column in intersection range is already marked
  # A column is marked if top[x] <= bottom[x] (valid range)
  overlap = false
  (intrl..intrh).each do |x|
    next if x < 0 || x >= SCREEN_WIDTH
    if plane.top[x] <= plane.bottom[x]
      overlap = true
      break
    end
  end

  if !overlap
    # No overlap - reuse same visplane with expanded range
    plane.minx = unionl if unionl < plane.minx
    plane.maxx = unionh if unionh > plane.maxx
    return plane
  end

  # Overlap detected - create new visplane with same properties
  new_plane = Visplane.new(
    plane.sector,
    plane.height,
    plane.texture,
    plane.light_level,
    plane.is_ceiling
  )
  new_plane.minx = start_x
  new_plane.maxx = stop_x
  @visplanes << new_plane

  # Update hash to point to the new plane (for subsequent lookups)
  key = [plane.height, plane.texture, plane.light_level, plane.is_ceiling]
  @visplane_hash[key] = new_plane

  new_plane
end

#draw_all_visplanesObject

Render all visplanes after BSP traversal (R_DrawPlanes in Chocolate Doom)



283
284
285
286
287
288
289
290
291
292
293
# File 'lib/doom/render/renderer.rb', line 283

def draw_all_visplanes
  @visplanes.each do |plane|
    next unless plane.valid?

    if plane.texture == 'F_SKY1'
      draw_sky_plane(plane)
    else
      render_visplane_spans(plane)
    end
  end
end

#draw_floor_ceiling_backgroundObject



275
276
277
278
279
280
# File 'lib/doom/render/renderer.rb', line 275

def draw_floor_ceiling_background
  player_sector = @map.sector_at(@player_x, @player_y)
  return unless player_sector

  fill_uncovered_with_sector(player_sector)
end

#draw_sky_plane(plane) ⇒ Object



402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
# File 'lib/doom/render/renderer.rb', line 402

def draw_sky_plane(plane)
  sky_texture = @textures['SKY1']
  return unless sky_texture

  framebuffer = @framebuffer
  player_angle = @player_angle
  projection = @projection
  sky_width = sky_texture.width
  sky_height = sky_texture.height

  # Clamp to screen bounds
  minx = [plane.minx, 0].max
  maxx = [plane.maxx, SCREEN_WIDTH - 1].min

  (minx..maxx).each do |x|
    y1 = plane.top[x]
    y2 = plane.bottom[x]
    next if y1 > y2

    # Clamp y bounds
    y1 = 0 if y1 < 0
    y2 = SCREEN_HEIGHT - 1 if y2 >= SCREEN_HEIGHT

    # Sky X: 4 repetitions per 360 degrees (matching ANGLETOSKYSHIFT=22)
    column_angle = player_angle - Math.atan2(x - HALF_WIDTH, projection)
    sky_x = (column_angle * SKY_XSCALE).to_i % sky_width
    column = sky_texture.column_pixels(sky_x)
    next unless column

    # Sky Y: texel = skytexturemid + (y - centery) * scale
    # Maps texel 0 to screen top, texel 100 to horizon (1:1 for DOOM's 200px)
    (y1..y2).each do |y|
      tex_y = (SKY_TEXTUREMID + (y - HALF_HEIGHT) * SKY_YSCALE).to_i % sky_height
      color = column[tex_y]
      framebuffer[y * SCREEN_WIDTH + x] = color
    end
  end
end

#draw_span(plane, y, x1, x2) ⇒ Object

Render one horizontal span with texture mapping (R_MapPlane in Chocolate Doom)



345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
# File 'lib/doom/render/renderer.rb', line 345

def draw_span(plane, y, x1, x2)
  return if x1.nil? || x1 > x2 || y < 0 || y >= SCREEN_HEIGHT

  flat = @flats[anim_flat(plane.texture)]
  return unless flat

  # Distance from horizon (y=100 for 200-high screen)
  dy = y - HALF_HEIGHT
  return if dy == 0

  # Plane height relative to player eye level
  plane_height = (plane.height - @player_z).abs
  return if plane_height == 0

  # Perpendicular distance to this row: distance = height * projection / dy
  perp_dist = plane_height * @projection / dy.abs

  # Calculate lighting for this distance
  light = calculate_flat_light(plane.light_level, perp_dist)
  cmap = @colormap.maps[light]

  # Cache locals for inner loop
  framebuffer = @framebuffer
  column_distscale = @column_distscale
  column_cos = @column_cos
  column_sin = @column_sin
  player_x = @player_x
  neg_player_y = -@player_y
  row_offset = y * SCREEN_WIDTH
  flat_pixels = flat.pixels

  # Clamp to screen bounds
  x1 = 0 if x1 < 0
  x2 = SCREEN_WIDTH - 1 if x2 >= SCREEN_WIDTH

  # Draw each pixel in the span using while loop
  x = x1
  while x <= x2
    ray_dist = perp_dist * column_distscale[x]
    tex_x = (player_x + ray_dist * column_cos[x]).to_i & 63
    tex_y = (neg_player_y - ray_dist * column_sin[x]).to_i & 63
    color = flat_pixels[tex_y * 64 + tex_x]
    framebuffer[row_offset + x] = cmap[color]
    x += 1
  end
end

#fill_uncovered_with_sector(default_sector) ⇒ Object



514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
# File 'lib/doom/render/renderer.rb', line 514

def fill_uncovered_with_sector(default_sector)
  # Cache all instance variables as locals for faster access
  framebuffer = @framebuffer
  column_cos = @column_cos
  column_sin = @column_sin
  column_distscale = @column_distscale
  projection = @projection
  player_angle = @player_angle
  player_x = @player_x
  neg_player_y = -@player_y

  ceil_height = (default_sector.ceiling_height - @player_z).abs
  floor_height = (default_sector.floor_height - @player_z).abs
  ceil_flat = @flats[anim_flat(default_sector.ceiling_texture)]
  floor_flat = @flats[anim_flat(default_sector.floor_texture)]
  ceil_pixels = ceil_flat&.pixels
  floor_pixels = floor_flat&.pixels
  is_sky = default_sector.ceiling_texture == 'F_SKY1'
  sky_texture = is_sky ? @textures['SKY1'] : nil
  light_level = default_sector.light_level
  colormap_maps = @colormap.maps

  # Compute y_slope for each row (perpendicular distance) - reuse preallocated arrays
  y_slope_ceil = @y_slope_ceil
  y_slope_floor = @y_slope_floor
  (1..HALF_HEIGHT).each do |dy|
    y_slope_ceil[dy] = ceil_height * projection / dy.to_f
    y_slope_floor[dy] = floor_height * projection / dy.to_f
  end

  # Draw ceiling (rows 0 to HALF_HEIGHT-1) using while loops for speed
  y = 0
  while y < HALF_HEIGHT
    dy = HALF_HEIGHT - y
    if dy > 0
      perp_dist = y_slope_ceil[dy]
      if perp_dist > 0
        light = calculate_flat_light(light_level, perp_dist)
        cmap = colormap_maps[light]
        row_offset = y * SCREEN_WIDTH

        if is_sky && sky_texture
          sky_w = sky_texture.width
          sky_h = sky_texture.height
          sky_y = (SKY_TEXTUREMID + (y - HALF_HEIGHT) * SKY_YSCALE).to_i % sky_h
          x = 0
          while x < SCREEN_WIDTH
            column_angle = player_angle - Math.atan2(x - HALF_WIDTH, projection)
            sky_x = (column_angle * SKY_XSCALE).to_i % sky_w
            color = sky_texture.column_pixels(sky_x)[sky_y]
            framebuffer[row_offset + x] = color
            x += 1
          end
        elsif ceil_flat
          x = 0
          while x < SCREEN_WIDTH
            ray_dist = perp_dist * column_distscale[x]
            tex_x = (player_x + ray_dist * column_cos[x]).to_i & 63
            tex_y = (neg_player_y - ray_dist * column_sin[x]).to_i & 63
            color = ceil_pixels[tex_y * 64 + tex_x]
            framebuffer[row_offset + x] = cmap[color]
            x += 1
          end
        end
      end
    end
    y += 1
  end

  # Draw floor (rows HALF_HEIGHT to SCREEN_HEIGHT-1)
  y = HALF_HEIGHT
  while y < SCREEN_HEIGHT
    dy = y - HALF_HEIGHT
    if dy > 0
      perp_dist = y_slope_floor[dy]
      if perp_dist > 0
        light = calculate_flat_light(light_level, perp_dist)
        cmap = colormap_maps[light]
        row_offset = y * SCREEN_WIDTH

        if floor_flat
          x = 0
          while x < SCREEN_WIDTH
            ray_dist = perp_dist * column_distscale[x]
            tex_x = (player_x + ray_dist * column_cos[x]).to_i & 63
            tex_y = (neg_player_y - ray_dist * column_sin[x]).to_i & 63
            color = floor_pixels[tex_y * 64 + tex_x]
            framebuffer[row_offset + x] = cmap[color]
            x += 1
          end
        end
      end
    end
    y += 1
  end
end

#find_or_create_visplane(sector, height, texture, light_level, is_ceiling) ⇒ Object



441
442
443
444
445
446
447
448
449
450
451
452
453
# File 'lib/doom/render/renderer.rb', line 441

def find_or_create_visplane(sector, height, texture, light_level, is_ceiling)
  # O(1) hash lookup instead of O(n) linear search
  key = [height, texture, light_level, is_ceiling]
  plane = @visplane_hash[key]

  unless plane
    plane = Visplane.new(sector, height, texture, light_level, is_ceiling)
    @visplanes << plane
    @visplane_hash[key] = plane
  end

  plane
end

#move_to(x, y) ⇒ Object



210
211
212
213
# File 'lib/doom/render/renderer.rb', line 210

def move_to(x, y)
  @player_x = x.to_f
  @player_y = y.to_f
end

#precompute_column_dataObject

Precompute column-based data for floor/ceiling rendering (R_InitLightTables-like) Cached: only recomputes sin/cos when player angle changes



263
264
265
266
267
268
269
270
271
272
273
# File 'lib/doom/render/renderer.rb', line 263

def precompute_column_data
  return if @cached_player_angle == @player_angle

  @cached_player_angle = @player_angle

  SCREEN_WIDTH.times do |x|
    column_angle = @player_angle - Math.atan2(x - HALF_WIDTH, @projection)
    @column_cos[x] = Math.cos(column_angle)
    @column_sin[x] = Math.sin(column_angle)
  end
end

#render_frameObject



223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
# File 'lib/doom/render/renderer.rb', line 223

def render_frame
  clear_framebuffer
  reset_clipping

  @sin_angle = Math.sin(@player_angle)
  @cos_angle = Math.cos(@player_angle)

  # Precompute column angles for floor/ceiling rendering
  precompute_column_data

  # Draw player's sector floor/ceiling as background fallback.
  # Visplanes (with correct per-sector lighting) overwrite this for sectors
  # with different properties. This only remains visible at gaps between
  # same-property sectors where the light level matches anyway.
  draw_floor_ceiling_background

  # Initialize visplanes for tracking visible floor/ceiling spans
  @visplanes = []
  @visplane_hash = {}  # Hash for O(1) lookup by (height, texture, light_level, is_ceiling)

  # Initialize drawsegs for sprite clipping
  @drawsegs = []

  # Render walls via BSP traversal
  render_bsp_node(@map.nodes.size - 1)

  # Draw visplanes for sectors different from background
  draw_all_visplanes

  # Save wall clip arrays for sprite clipping (reuse preallocated arrays)
  @sprite_ceiling_clip.replace(@ceiling_clip)
  @sprite_floor_clip.replace(@floor_clip)
  @sprite_wall_depth.replace(@wall_depth)

  # R_DrawMasked: render sprites and masked middle textures interleaved
  draw_masked if @sprites
end

#render_visplane_spans(plane) ⇒ Object

Render visplane using horizontal spans (R_MakeSpans in Chocolate Doom) This processes columns left-to-right, building spans and rendering them



297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
# File 'lib/doom/render/renderer.rb', line 297

def render_visplane_spans(plane)
  return if plane.minx > plane.maxx

  spanstart = Array.new(SCREEN_HEIGHT)  # Track where each row's span started

  # Process columns left to right
  ((plane.minx)..(plane.maxx + 1)).each do |x|
    # Get current column bounds
    if x <= plane.maxx
      t2 = plane.top[x]
      b2 = plane.bottom[x]
      t2 = SCREEN_HEIGHT if t2 > b2  # Invalid = empty
    else
      t2, b2 = SCREEN_HEIGHT, -1  # Sentinel for final column
    end

    # Get previous column bounds
    if x > plane.minx
      t1 = plane.top[x - 1]
      b1 = plane.bottom[x - 1]
      t1 = SCREEN_HEIGHT if t1 > b1
    else
      t1, b1 = SCREEN_HEIGHT, -1
    end

    # Close spans that ended (visible in prev column, not in current)
    if t1 < SCREEN_HEIGHT
      # Rows visible in previous but not current (above current or below current)
      (t1..[b1, t2 - 1].min).each do |y|
        draw_span(plane, y, spanstart[y], x - 1) if spanstart[y]
        spanstart[y] = nil
      end
      ([t1, b2 + 1].max..b1).each do |y|
        draw_span(plane, y, spanstart[y], x - 1) if spanstart[y]
        spanstart[y] = nil
      end
    end

    # Open new spans (visible in current, not started yet)
    if t2 < SCREEN_HEIGHT
      (t2..b2).each do |y|
        spanstart[y] ||= x
      end
    end
  end
end

#set_player(x, y, z, angle) ⇒ Object



203
204
205
206
207
208
# File 'lib/doom/render/renderer.rb', line 203

def set_player(x, y, z, angle)
  @player_x = x.to_f
  @player_y = y.to_f
  @player_z = z.to_f
  @player_angle = angle * Math::PI / 180.0
end

#set_z(z) ⇒ Object



215
216
217
# File 'lib/doom/render/renderer.rb', line 215

def set_z(z)
  @player_z = z.to_f
end

#sprite_diagnosticsObject

Diagnostic: returns info about all sprites and why they are/aren’t visible



116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/doom/render/renderer.rb', line 116

def sprite_diagnostics
  return [] unless @sprites

  results = []
  @map.things.each do |thing|
    prefix = @sprites.prefix_for(thing.type)
    next unless prefix

    info = { type: thing.type, x: thing.x, y: thing.y, prefix: prefix }

    view_x, view_y = transform_point(thing.x, thing.y)
    info[:view_x] = view_x.round(1)
    info[:view_y] = view_y.round(1)

    if view_y <= 0
      info[:status] = "behind_player"
      results << info
      next
    end

    dist = view_y
    screen_x = HALF_WIDTH + (view_x * @projection / view_y)
    info[:screen_x] = screen_x.round(1)
    info[:dist] = dist.round(1)

    dx = thing.x - @player_x
    dy = thing.y - @player_y
    angle_to_thing = Math.atan2(dy, dx)
    sprite = @sprites.get_rotated(thing.type, angle_to_thing, thing.angle)
    unless sprite
      info[:status] = "no_sprite_frame"
      results << info
      next
    end

    sprite_scale = @projection / dist
    sprite_half_width = (sprite.width * @projection / dist / 2).to_i
    info[:sprite_scale] = sprite_scale.round(3)

    if screen_x + sprite_half_width < 0
      info[:status] = "off_screen_left"
    elsif screen_x - sprite_half_width >= SCREEN_WIDTH
      info[:status] = "off_screen_right"
    else
      # Check drawseg clipping
      sprite_left = (screen_x - sprite.left_offset * sprite_scale).to_i
      sprite_right = sprite_left + (sprite.width * sprite_scale).to_i - 1
      x1 = [sprite_left, 0].max
      x2 = [sprite_right, SCREEN_WIDTH - 1].min

      sector = @map.sector_at(thing.x, thing.y)
      thing_floor = sector ? sector.floor_height : 0
      sprite_gz = thing_floor
      sprite_gzt = thing_floor + sprite.top_offset

      clipping_segs = []
      @drawsegs.reverse_each do |ds|
        next if ds.x1 > x2 || ds.x2 < x1
        next if ds.silhouette == SIL_NONE

        lowscale = [ds.scale1, ds.scale2].min
        highscale = [ds.scale1, ds.scale2].max

        if highscale < sprite_scale
          next
        elsif lowscale < sprite_scale
          next unless point_on_seg_side(thing.x, thing.y, ds.curline)
        end

        clipping_segs << {
          x1: ds.x1, x2: ds.x2,
          scale: "#{ds.scale1.round(3)}..#{ds.scale2.round(3)}",
          sil: ds.silhouette
        }
      end

      info[:screen_range] = "#{x1}..#{x2}"
      info[:clipping_segs] = clipping_segs.size
      info[:clipping_detail] = clipping_segs
      info[:status] = "visible"
    end

    results << info
  end
  results
end

#turn(degrees) ⇒ Object



219
220
221
# File 'lib/doom/render/renderer.rb', line 219

def turn(degrees)
  @player_angle += degrees * Math::PI / 180.0
end