void light_map::generate(game* g, int x, int y, float natural_light, float luminance) { build_light_cache(g, x, y); fill(lm, 0.0f); fill(sm, 0.0f); int dir_x[] = { 1, 0 , -1, 0 }; int dir_y[] = { 0, 1 , 0, -1 }; int dir_d[] = { 180, 270, 0, 90 }; // Daylight vision handling returned back to map due to issues it causes here if (natural_light > LIGHT_SOURCE_BRIGHT) { // Apply sunlight, first light source so just assign for(int sx = x - SEEX; sx <= x + SEEX; ++sx) { for(int sy = y - SEEY; sy <= y + SEEY; ++sy) { // In bright light indoor light exists to some degree if (!c[sx - x + LIGHTMAP_RANGE_X][sy - y + LIGHTMAP_RANGE_Y].is_outside) lm[sx - x + SEEX][sy - y + SEEY] = LIGHT_AMBIENT_LOW; } } } // Apply player light sources if (luminance > LIGHT_AMBIENT_LOW) apply_light_source(x, y, x, y, luminance); for(int sx = x - LIGHTMAP_RANGE_X; sx <= x + LIGHTMAP_RANGE_X; ++sx) { for(int sy = y - LIGHTMAP_RANGE_Y; sy <= y + LIGHTMAP_RANGE_Y; ++sy) { // When underground natural_light is 0, if this changes we need to revisit if (natural_light > LIGHT_AMBIENT_LOW) { if (!c[sx - x + LIGHTMAP_RANGE_X][sy - y + LIGHTMAP_RANGE_Y].is_outside) { // Apply light sources for external/internal divide for(int i = 0; i < 4; ++i) { if (INBOUNDS_LARGE(sx - x + dir_x[i], sy - y + dir_y[i]) && c[sx - x + LIGHTMAP_RANGE_X + dir_x[i]][sy - y + LIGHTMAP_RANGE_Y + dir_y[i]].is_outside) { if (INBOUNDS(sx - x, sy - y) && c[LIGHTMAP_RANGE_X][LIGHTMAP_RANGE_Y].is_outside) lm[sx - x + SEEX][sy - y + SEEY] = natural_light; if (c[sx - x + LIGHTMAP_RANGE_X][sy - y + LIGHTMAP_RANGE_Y].transparency > LIGHT_TRANSPARENCY_SOLID) apply_light_arc(sx, sy, dir_d[i], x, y, natural_light); } } } } if (g->m.i_at(sx, sy).size() == 1 && g->m.i_at(sx, sy)[0].type->id == itm_flashlight_on) apply_light_source(sx, sy, x, y, 20); if(g->m.ter(sx, sy) == t_lava) apply_light_source(sx, sy, x, y, 50); if(g->m.ter(sx, sy) == t_console) apply_light_source(sx, sy, x, y, 3); if (g->m.i_at(sx, sy).size() == 1 && g->m.i_at(sx, sy)[0].type->id == itm_candle_lit) apply_light_source(sx, sy, x, y, 4); if(g->m.ter(sx, sy) == t_emergency_light) apply_light_source(sx, sy, x, y, 3); // TODO: [lightmap] Attach light brightness to fields switch(g->m.field_at(sx, sy).type) { case fd_fire: if (3 == g->m.field_at(sx, sy).density) apply_light_source(sx, sy, x, y, 160); else if (2 == g->m.field_at(sx, sy).density) apply_light_source(sx, sy, x, y, 60); else apply_light_source(sx, sy, x, y, 16); break; case fd_fire_vent: case fd_flame_burst: apply_light_source(sx, sy, x, y, 8); break; case fd_electricity: if (3 == g->m.field_at(sx, sy).density) apply_light_source(sx, sy, x, y, 8); else if (2 == g->m.field_at(sx, sy).density) apply_light_source(sx, sy, x, y, 1); else apply_light_source(sx, sy, x, y, LIGHT_SOURCE_LOCAL); // kinda a hack as the square will still get marked break; } // Apply any vehicle light sources if (c[sx - x + LIGHTMAP_RANGE_X][sy - y + LIGHTMAP_RANGE_Y].veh && c[sx - x + LIGHTMAP_RANGE_X][sy - y + LIGHTMAP_RANGE_Y].veh_light > LL_DARK) { if (c[sx - x + LIGHTMAP_RANGE_X][sy - y + LIGHTMAP_RANGE_Y].veh_light > LL_LIT) { int dir = c[sx - x + LIGHTMAP_RANGE_X][sy - y + LIGHTMAP_RANGE_Y].veh->face.dir(); float luminance = c[sx - x + LIGHTMAP_RANGE_X][sy - y + LIGHTMAP_RANGE_Y].veh_light; apply_light_arc(sx, sy, dir, x, y, luminance); } } if (c[sx - x + LIGHTMAP_RANGE_X][sy - y + LIGHTMAP_RANGE_Y].mon >= 0) { if (g->z[c[sx - x + LIGHTMAP_RANGE_X][sy - y + LIGHTMAP_RANGE_Y].mon].has_effect(ME_ONFIRE)) apply_light_source(sx, sy, x, y, 3); // TODO: [lightmap] Attach natural light brightness to creatures // TODO: [lightmap] Allow creatures to have light attacks (ie: eyebot) // TODO: [lightmap] Allow creatures to have facing and arc lights switch(g->z[c[sx - x + LIGHTMAP_RANGE_X][sy - y + LIGHTMAP_RANGE_Y].mon].type->id) { case mon_zombie_electric: apply_light_source(sx, sy, x, y, 1); break; case mon_flaming_eye: apply_light_source(sx, sy, x, y, LIGHT_SOURCE_BRIGHT); break; case mon_manhack: apply_light_source(sx, sy, x, y, LIGHT_SOURCE_LOCAL); break; } } } } }
void map::generate_lightmap( const int zlev ) { auto &map_cache = get_cache( zlev ); auto &lm = map_cache.lm; auto &sm = map_cache.sm; auto &outside_cache = map_cache.outside_cache; std::memset(lm, 0, sizeof(lm)); std::memset(sm, 0, sizeof(sm)); /* Bulk light sources wastefully cast rays into neighbors; a burning hospital can produce significant slowdown, so for stuff like fire and lava: * Step 1: Store the position and luminance in buffer via add_light_source, for efficient checking of neighbors. * Step 2: After everything else, iterate buffer and apply_light_source only in non-redundant directions * Step 3: ???? * Step 4: Profit! */ auto &light_source_buffer = map_cache.light_source_buffer; std::memset(light_source_buffer, 0, sizeof(light_source_buffer)); constexpr std::array<int, 4> dir_x = {{ 0, -1 , 1, 0 }}; // [0] constexpr std::array<int, 4> dir_y = {{ -1, 0 , 0, 1 }}; // [1][X][2] constexpr std::array<int, 4> dir_d = {{ 90, 0, 180, 270 }}; // [3] const float natural_light = g->natural_light_level( zlev ); const float inside_light = (natural_light > LIGHT_SOURCE_BRIGHT) ? LIGHT_AMBIENT_LOW + 1.0 : LIGHT_AMBIENT_MINIMAL; // Apply sunlight, first light source so just assign for( int sx = 0; sx < LIGHTMAP_CACHE_X; ++sx ) { for( int sy = 0; sy < LIGHTMAP_CACHE_Y; ++sy ) { // In bright light indoor light exists to some degree if( !outside_cache[sx][sy] ) { lm[sx][sy] = inside_light; } else { lm[sx][sy] = natural_light; } } } apply_character_light( g->u ); for( auto &n : g->active_npc ) { apply_character_light( *n ); } // Traverse the submaps in order for (int smx = 0; smx < my_MAPSIZE; ++smx) { for (int smy = 0; smy < my_MAPSIZE; ++smy) { auto const cur_submap = get_submap_at_grid( smx, smy, zlev ); for (int sx = 0; sx < SEEX; ++sx) { for (int sy = 0; sy < SEEY; ++sy) { const int x = sx + smx * SEEX; const int y = sy + smy * SEEY; const tripoint p( x, y, zlev ); // Project light into any openings into buildings. if (natural_light > LIGHT_SOURCE_BRIGHT && !outside_cache[p.x][p.y]) { // Apply light sources for external/internal divide for(int i = 0; i < 4; ++i) { if (INBOUNDS(p.x + dir_x[i], p.y + dir_y[i]) && outside_cache[p.x + dir_x[i]][p.y + dir_y[i]]) { lm[p.x][p.y] = natural_light; if (light_transparency( p ) > LIGHT_TRANSPARENCY_SOLID) { apply_directional_light( p, dir_d[i], natural_light ); } } } } if( cur_submap->lum[sx][sy] && has_items( p ) ) { auto items = i_at( p ); add_light_from_items( p, items.begin(), items.end() ); } const ter_id terrain = cur_submap->ter[sx][sy]; if (terrain == t_lava) { add_light_source( p, 50 ); } else if (terrain == t_console) { add_light_source( p, 10 ); } else if (terrain == t_utility_light) { add_light_source( p, 240 ); } for( auto &fld : cur_submap->fld[sx][sy] ) { const field_entry *cur = &fld.second; // TODO: [lightmap] Attach light brightness to fields switch(cur->getFieldType()) { case fd_fire: if (3 == cur->getFieldDensity()) { add_light_source( p, 160 ); } else if (2 == cur->getFieldDensity()) { add_light_source( p, 60 ); } else { add_light_source( p, 20 ); } break; case fd_fire_vent: case fd_flame_burst: add_light_source( p, 20 ); break; case fd_electricity: case fd_plasma: if (3 == cur->getFieldDensity()) { add_light_source( p, 20 ); } else if (2 == cur->getFieldDensity()) { add_light_source( p, 4 ); } else { // Kinda a hack as the square will still get marked. apply_light_source( p, LIGHT_SOURCE_LOCAL ); } break; case fd_incendiary: if (3 == cur->getFieldDensity()) { add_light_source( p, 160 ); } else if (2 == cur->getFieldDensity()) { add_light_source( p, 60 ); } else { add_light_source( p, 20 ); } break; case fd_laser: apply_light_source( p, 4 ); break; case fd_spotlight: add_light_source( p, 80 ); break; case fd_dazzling: add_light_source( p, 5 ); break; default: //Suppress warnings break; } } } } } } for (size_t i = 0; i < g->num_zombies(); ++i) { auto &critter = g->zombie(i); if(critter.is_hallucination()) { continue; } const tripoint &mp = critter.pos(); if( inbounds( mp ) ) { if (critter.has_effect( effect_onfire)) { apply_light_source( mp, 8 ); } // TODO: [lightmap] Attach natural light brightness to creatures // TODO: [lightmap] Allow creatures to have light attacks (ie: eyebot) // TODO: [lightmap] Allow creatures to have facing and arc lights if (critter.type->luminance > 0) { apply_light_source( mp, critter.type->luminance ); } } } // Apply any vehicle light sources VehicleList vehs = get_vehicles(); for( auto &vv : vehs ) { vehicle *v = vv.v; auto lights = v->lights( true ); float veh_luminance = 0.0; float iteration = 1.0; for( const auto pt : lights ) { const auto &vp = pt->info(); if( vp.has_flag( VPFLAG_CONE_LIGHT ) ) { veh_luminance += vp.bonus / iteration; iteration = iteration * 1.1; } } for( const auto pt : lights ) { const auto &vp = pt->info(); tripoint src = v->global_part_pos3( *pt ); if( !inbounds( src ) ) { continue; } if( vp.has_flag( VPFLAG_CONE_LIGHT ) ) { if( veh_luminance > LL_LIT ) { add_light_source( src, SQRT_2 ); // Add a little surrounding light apply_light_arc( src, v->face.dir() + pt->direction, veh_luminance, 45 ); } } else if( vp.has_flag( VPFLAG_CIRCLE_LIGHT ) ) { if( ( calendar::turn % 2 && vp.has_flag( VPFLAG_ODDTURN ) ) || ( !( calendar::turn % 2 ) && vp.has_flag( VPFLAG_EVENTURN ) ) || ( !( vp.has_flag( VPFLAG_EVENTURN ) || vp.has_flag( VPFLAG_ODDTURN ) ) ) ) { add_light_source( src, vp.bonus ); } } else { add_light_source( src, vp.bonus ); } }; for( size_t p = 0; p < v->parts.size(); ++p ) { tripoint pp = tripoint( vv.x, vv.y, vv.z ) + v->parts[p].precalc[0]; if( !inbounds( pp ) ) { continue; } if( v->part_flag( p, VPFLAG_CARGO ) && !v->part_flag( p, "COVERED" ) ) { add_light_from_items( pp, v->get_items(p).begin(), v->get_items(p).end() ); } } } /* Now that we have position and intensity of all bulk light sources, apply_ them This may seem like extra work, but take a 12x12 raging inferno: unbuffered: (12^2)*(160*4) = apply_light_ray x 92160 buffered: (12*4)*(160) = apply_light_ray x 7680 */ const tripoint cache_start( 0, 0, zlev ); const tripoint cache_end( LIGHTMAP_CACHE_X, LIGHTMAP_CACHE_Y, zlev ); for( const tripoint &p : points_in_rectangle( cache_start, cache_end ) ) { if( light_source_buffer[p.x][p.y] > 0.0 ) { apply_light_source( p, light_source_buffer[p.x][p.y] ); } } if (g->u.has_active_bionic("bio_night") ) { for( const tripoint &p : points_in_rectangle( cache_start, cache_end ) ) { if( rl_dist( p, g->u.pos() ) < 15 ) { lm[p.x][p.y] = LIGHT_AMBIENT_MINIMAL; } } } }
void light_map::generate(game* g, int x, int y, float natural_light, float luminance) { build_light_cache(g, x, y); memset(lm, 0, sizeof(lm)); memset(sm, 0, sizeof(sm)); int dir_x[] = { 1, 0 , -1, 0 }; int dir_y[] = { 0, 1 , 0, -1 }; int dir_d[] = { 180, 270, 0, 90 }; //g->add_msg("natural_light %f", natural_light); //CAT-mgs: this is just for indoors to initialize it at low ambient light if(natural_light > LIGHT_AMBIENT_LOW) // LIGHT_SOURCE_BRIGHT { for(int sx = x - CAT_VX; sx <= x + CAT_VX; ++sx) { for(int sy = y - CAT_VY; sy <= y + CAT_VY; ++sy) { if(!is_outside(sx - x + g->u.view_offset_x, sy - y + g->u.view_offset_y)) lm[sx - x + CAT_VX][sy - y + CAT_VY] = LIGHT_AMBIENT_LOW; } } } if(luminance > LIGHT_AMBIENT_LOW) apply_light_source(g->u.posx, g->u.posy, x, y, luminance); for(int sx = x - LIGHTMAP_RANGE_X; sx <= x + LIGHTMAP_RANGE_X; ++sx) { for(int sy = y - LIGHTMAP_RANGE_Y; sy <= y + LIGHTMAP_RANGE_Y; ++sy) { const ter_id terrain = g->m.ter(sx, sy); const std::vector<item> items = g->m.i_at(sx, sy); const field current_field = g->m.field_at(sx, sy); // When underground natural_light is 0, if this changes we need to revisit if (natural_light > LIGHT_AMBIENT_LOW) { int lx= sx - x + g->u.view_offset_x; int ly= sy - y + g->u.view_offset_y; if(!is_outside(lx, ly)) { // Apply light sources for external/internal divide for(int i = 0; i < 4; ++i) { if(INBOUNDS_LARGE(lx + dir_x[i], ly + dir_y[i]) && is_outside(lx + dir_x[i], ly + dir_y[i])) { //CAT-mgs: I did that above, didn't I? //... no, yes, what's this for anyway? if(INBOUNDS(sx - x, sy - y) && is_outside(0, 0)) lm[sx - x + CAT_VX][sy - y + CAT_VY]= natural_light; if(c[sx - x + LIGHTMAP_RANGE_X][sy - y + LIGHTMAP_RANGE_Y].transparency > LIGHT_TRANSPARENCY_SOLID) apply_light_arc(sx, sy, dir_d[i], x, y, natural_light); } } } } //CAT-g: if(items.size() > 0) { if(items[0].type->id == itm_flashlight_on || items[items.size()-1].type->id == itm_flashlight_on) apply_light_source(sx, sy, x, y, 48); if(items[0].type->id == itm_torch_lit || items[items.size()-1].type->id == itm_torch_lit) apply_light_source(sx, sy, x, y, 20); if(items[0].type->id == itm_candle_lit || items[items.size()-1].type->id == itm_candle_lit) apply_light_source(sx, sy, x, y, 9); } if(terrain == t_lava) apply_light_source(sx, sy, x, y, 48); if(terrain == t_console) apply_light_source(sx, sy, x, y, 3); if(terrain == t_emergency_light) apply_light_source(sx, sy, x, y, 3); // TODO: [lightmap] Attach light brightness to fields switch(current_field.type) { case fd_fire: if(current_field.density > 5) apply_light_source(sx, sy, x, y, 48); else if (current_field.density > 2) apply_light_source(sx, sy, x, y, 20); else apply_light_source(sx, sy, x, y, 9); break; case fd_fire_vent: apply_light_source(sx, sy, x, y, 3); case fd_flame_burst: apply_light_source(sx, sy, x, y, 8); break; case fd_electricity: if (3 == current_field.density) apply_light_source(sx, sy, x, y, 9); else if (2 == current_field.density) apply_light_source(sx, sy, x, y, 1); else apply_light_source(sx, sy, x, y, LIGHT_SOURCE_LOCAL); // kinda a hack as the square will still get marked break; } // Apply any vehicle light sources if (c[sx - x + LIGHTMAP_RANGE_X][sy - y + LIGHTMAP_RANGE_Y].veh && c[sx - x + LIGHTMAP_RANGE_X][sy - y + LIGHTMAP_RANGE_Y].veh_light > LL_DARK) { if (c[sx - x + LIGHTMAP_RANGE_X][sy - y + LIGHTMAP_RANGE_Y].veh_light > LL_LIT) { int dir = c[sx - x + LIGHTMAP_RANGE_X][sy - y + LIGHTMAP_RANGE_Y].veh->face.dir(); //CAT-g: longer range headlights // float luminance = c[sx - x + LIGHTMAP_RANGE_X][sy - y + LIGHTMAP_RANGE_Y].veh_light; float luminance = 4000 + c[sx - x + LIGHTMAP_RANGE_X][sy - y + LIGHTMAP_RANGE_Y].veh_light; apply_light_arc(sx, sy, dir, x, y, luminance); } } if (c[sx - x + LIGHTMAP_RANGE_X][sy - y + LIGHTMAP_RANGE_Y].mon >= 0) { if (g->z[c[sx - x + LIGHTMAP_RANGE_X][sy - y + LIGHTMAP_RANGE_Y].mon].has_effect(ME_ONFIRE)) apply_light_source(sx, sy, x, y, 3); // TODO: [lightmap] Attach natural light brightness to creatures // TODO: [lightmap] Allow creatures to have light attacks (ie: eyebot) // TODO: [lightmap] Allow creatures to have facing and arc lights switch(g->z[c[sx - x + LIGHTMAP_RANGE_X][sy - y + LIGHTMAP_RANGE_Y].mon].type->id) { case mon_zombie_electric: apply_light_source(sx, sy, x, y, 1); break; case mon_turret: apply_light_source(sx, sy, x, y, 2); break; case mon_flaming_eye: apply_light_source(sx, sy, x, y, LIGHT_SOURCE_BRIGHT); break; case mon_manhack: apply_light_source(sx, sy, x, y, LIGHT_SOURCE_LOCAL); break; } } } } }
void map::apply_light_ray(bool lit[LIGHTMAP_CACHE_X][LIGHTMAP_CACHE_Y], const tripoint &s, const tripoint &e, float luminance) { int ax = abs(e.x - s.x) * 2; int ay = abs(e.y - s.y) * 2; int dx = (s.x < e.x) ? 1 : -1; int dy = (s.y < e.y) ? 1 : -1; int x = s.x; int y = s.y; // TODO: Invert that z comparison when it's sane if( s.z != e.z || (s.x == e.x && s.y == e.y) ) { return; } auto &lm = get_cache( s.z ).lm; auto &transparency_cache = get_cache( s.z ).transparency_cache; float distance = 1.0; float transparency = LIGHT_TRANSPARENCY_OPEN_AIR; const float scaling_factor = (float)rl_dist( s, e ) / (float)square_dist( s, e ); // TODO: [lightmap] Pull out the common code here rather than duplication if (ax > ay) { int t = ay - (ax / 2); do { if(t >= 0) { y += dy; t -= ax; } x += dx; t += ay; // TODO: clamp coordinates to map bounds before this method is called. if (INBOUNDS(x, y)) { if (!lit[x][y]) { // Multiple rays will pass through the same squares so we need to record that lit[x][y] = true; lm[x][y] = std::max( lm[x][y], luminance / ((float)exp( transparency * distance ) * distance) ); } float current_transparency = transparency_cache[x][y]; if(current_transparency == LIGHT_TRANSPARENCY_SOLID) { break; } // Cumulative average of the transparency values encountered. transparency = ((distance - 1.0) * transparency + current_transparency) / distance; } else { break; } distance += scaling_factor; } while(!(x == e.x && y == e.y)); } else { int t = ax - (ay / 2); do { if(t >= 0) { x += dx; t -= ay; } y += dy; t += ax; if (INBOUNDS(x, y)) { if(!lit[x][y]) { // Multiple rays will pass through the same squares so we need to record that lit[x][y] = true; lm[x][y] = std::max(lm[x][y], luminance / ((float)exp( transparency * distance ) * distance) ); } float current_transparency = transparency_cache[x][y]; if(current_transparency == LIGHT_TRANSPARENCY_SOLID) { break; } // Cumulative average of the transparency values encountered. transparency = ((distance - 1.0) * transparency + current_transparency) / distance; } else { break; } distance += scaling_factor; } while(!(x == e.x && y == e.y)); } }