Class: Quake::Renderer::GLLightmap
- Inherits:
-
Object
- Object
- Quake::Renderer::GLLightmap
- Defined in:
- lib/quake/renderer/gl_lightmap.rb
Overview
Computes and uploads lightmaps for BSP faces. Each face's lightmap is a small texture (typically 4x4 to 18x18) derived from the lighting lump. Lightmap UVs are computed from the face's texture-space extents, quantized to a 16-unit grid.
Defined Under Namespace
Classes: LightmapInfo
Constant Summary collapse
- LIGHTMAP_BLOCK_WIDTH =
128- LIGHTMAP_BLOCK_HEIGHT =
128- MAX_LIGHTSTYLES =
64- DEFAULT_LIGHTSTYLES =
{ 0 => "m", 1 => "mmnmmommommnonmmonqnmmo", 2 => "abcdefghijklmnopqrstuvwxyzyxwvutsrqponmlkjihgfedcba", 3 => "mmmmmaaaaammmmmaaaaaabcdefgabcdefg", 4 => "mamamamamama", 5 => "jklmnopqrstuvwxyzyxwvutsrqponmlkj", 6 => "nmonqnmomnmomomno", 7 => "mmmaaaabcdefgmmmmaaaammmaamm", 8 => "mmmaaammmaaammmabcdefaaaammmmabcdefmmmaaaa", 9 => "aaaaaaaazzzzzzzz", 10 => "mmamammmmammamamaaamammma", 11 => "abcdefghijklmnopqrrqponmlkjihgfedcba", 63 => "a" }.freeze
Instance Attribute Summary collapse
-
#face_lightmaps ⇒ Object
readonly
face_index -> LightmapInfo.
-
#lightstyle_values ⇒ Object
readonly
face_index -> LightmapInfo.
Class Method Summary collapse
Instance Method Summary collapse
- #animate_lightstyles(time) ⇒ Object
- #bind(face_index) ⇒ Object
- #build_all ⇒ Object
-
#initialize(level, palette, lightstyles: nil) ⇒ GLLightmap
constructor
A new instance of GLLightmap.
-
#lightmap_texcoords(face_index, vertex, texinfo) ⇒ Object
Compute lightmap UVs for a vertex given the face's lightmap info.
- #lightstyles=(lightstyles) ⇒ Object
- #set_lightstyle(style, map) ⇒ Object
- #update(time, lightstyles: nil, dynamic_lights: nil) ⇒ Object
Constructor Details
#initialize(level, palette, lightstyles: nil) ⇒ GLLightmap
Returns a new instance of GLLightmap.
35 36 37 38 39 40 41 42 43 44 45 46 47 |
# File 'lib/quake/renderer/gl_lightmap.rb', line 35 def initialize(level, palette, lightstyles: nil) @level = level @palette = palette @face_lightmaps = {} @block_textures = [] # GL texture ids for each block @block_allocated = [] # row allocation per block column @block_pixels = [] # RGBA pixel data per block @face_lightmap_faces = {} @uploaded = false @lightstyles = self.class.default_lightstyles Array(lightstyles).each_with_index { |map, style| @lightstyles[style] = map if map } animate_lightstyles(0.0) end |
Instance Attribute Details
#face_lightmaps ⇒ Object (readonly)
face_index -> LightmapInfo
33 34 35 |
# File 'lib/quake/renderer/gl_lightmap.rb', line 33 def face_lightmaps @face_lightmaps end |
#lightstyle_values ⇒ Object (readonly)
face_index -> LightmapInfo
33 34 35 |
# File 'lib/quake/renderer/gl_lightmap.rb', line 33 def lightstyle_values @lightstyle_values end |
Class Method Details
.default_lightstyles ⇒ Object
49 50 51 |
# File 'lib/quake/renderer/gl_lightmap.rb', line 49 def self.default_lightstyles Array.new(MAX_LIGHTSTYLES) { |style| DEFAULT_LIGHTSTYLES[style] } end |
.lightstyle_values_for(time, lightstyles) ⇒ Object
53 54 55 56 57 58 59 60 61 62 63 |
# File 'lib/quake/renderer/gl_lightmap.rb', line 53 def self.lightstyle_values_for(time, lightstyles) frame = (time.to_f * 10.0).to_i values = Array.new(MAX_LIGHTSTYLES, 256) MAX_LIGHTSTYLES.times do |style| map = lightstyles[style] next if map.nil? || map.empty? values[style] = (map.getbyte(frame % map.length) - "a".ord) * 22 end values end |
Instance Method Details
#animate_lightstyles(time) ⇒ Object
75 76 77 78 |
# File 'lib/quake/renderer/gl_lightmap.rb', line 75 def animate_lightstyles(time) @lightstyle_values = self.class.lightstyle_values_for(time, @lightstyles) @lightstyle_values end |
#bind(face_index) ⇒ Object
132 133 134 135 136 |
# File 'lib/quake/renderer/gl_lightmap.rb', line 132 def bind(face_index) info = @face_lightmaps[face_index] return unless info GL.BindTexture(GL::TEXTURE_2D, info.gl_texture) end |
#build_all ⇒ Object
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 |
# File 'lib/quake/renderer/gl_lightmap.rb', line 111 def build_all animate_lightstyles(0.0) unless @lightstyle_values allocate_block @level.faces.each_with_index do |face, face_index| texinfo = @level.texinfo[face.texinfo_index] next if texinfo.nil? next if texinfo.flags & 1 != 0 # TEX_SPECIAL (sky/turb) # Faces with no samples (light_offset < 0) still get a lightmap: all # zeros renders them black, matching R_BuildLightMap. Only a missing # lighting lump means fullbright. next if @level.lighting.nil? || @level.lighting.empty? build_face_lightmap(face, face_index) end upload_blocks count = @face_lightmaps.size puts "Built #{count} lightmaps in #{@block_textures.size} blocks" end |
#lightmap_texcoords(face_index, vertex, texinfo) ⇒ Object
Compute lightmap UVs for a vertex given the face's lightmap info.
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 |
# File 'lib/quake/renderer/gl_lightmap.rb', line 139 def lightmap_texcoords(face_index, vertex, texinfo) info = @face_lightmaps[face_index] return [0.0, 0.0] unless info # Compute texture-space coordinate s = vertex.dot(texinfo.s_vec) + texinfo.s_offset t = vertex.dot(texinfo.t_vec) + texinfo.t_offset # Convert to lightmap UV: subtract texture mins, scale by 1/16. # The half-luxel centering is applied once, as the +0.5 texel below # (C adds +8 texture units before the /16 -- same thing). extents = face_extents(face_index) return [0.0, 0.0] unless extents tex_mins_s, tex_mins_t, _, _ = extents ls = (s - tex_mins_s) / (info.width * 16.0) lt = (t - tex_mins_t) / (info.height * 16.0) # Map into atlas block coordinates atlas_s = (info.s_offset + ls * info.width + 0.5) / LIGHTMAP_BLOCK_WIDTH atlas_t = (info.t_offset + lt * info.height + 0.5) / LIGHTMAP_BLOCK_HEIGHT [atlas_s, atlas_t] end |
#lightstyles=(lightstyles) ⇒ Object
65 66 67 |
# File 'lib/quake/renderer/gl_lightmap.rb', line 65 def lightstyles=(lightstyles) Array(lightstyles).each_with_index { |map, style| @lightstyles[style] = map if style < MAX_LIGHTSTYLES } end |
#set_lightstyle(style, map) ⇒ Object
69 70 71 72 73 |
# File 'lib/quake/renderer/gl_lightmap.rb', line 69 def set_lightstyle(style, map) return if style.negative? || style >= MAX_LIGHTSTYLES @lightstyles[style] = map end |
#update(time, lightstyles: nil, dynamic_lights: nil) ⇒ Object
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 |
# File 'lib/quake/renderer/gl_lightmap.rb', line 80 def update(time, lightstyles: nil, dynamic_lights: nil) self.lightstyles = lightstyles if lightstyles old_values = @lightstyle_values || Array.new(MAX_LIGHTSTYLES, 256) new_values = animate_lightstyles(time) changed_styles = (0...MAX_LIGHTSTYLES).select { |style| old_values[style] != new_values[style] } # R_MarkLights: faces touched by dynamic lights this frame, plus faces # lit last frame (which must be re-baked back to their static light). dlit_faces = mark_dynamic_faces(Array(dynamic_lights)) previously_dlit = @dlit_faces || {} @dlit_faces = dlit_faces return false if changed_styles.empty? && dlit_faces.empty? && previously_dlit.empty? changed_blocks = {} @face_lightmap_faces.each do |face_index, face| lights = dlit_faces[face_index] needs_rebake = lights || previously_dlit.key?(face_index) || (!changed_styles.empty? && face_uses_styles?(face, changed_styles)) next unless needs_rebake info = @face_lightmaps[face_index] next unless info write_face_lightmap(face, info, face_index: face_index, dlights: lights) changed_blocks[info.block_index] = true end upload_changed_blocks(changed_blocks.keys) if @uploaded !changed_blocks.empty? end |