17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-11-08 22:07:20 +01:00

Light the cab with cablight/dashboardlight light points of the cab model

This commit is contained in:
2026-10-28 20:55:45 +01:00
parent 8f891fdc14
commit 9904eddd0d
22 changed files with 512 additions and 214 deletions

View File

@@ -13,8 +13,10 @@ http://mozilla.org/MPL/2.0/.
#include <algorithm>
#include <array>
#include <cctype>
#include <chrono>
#include <limits>
#include <string_view>
#include "utilities/Globals.h"
#include "rendering/common/skyocclusion.h"
@@ -38,6 +40,20 @@ float const LIGHT_RANGE_MAX{60.f};
// the lights of models and compartments fade in, as they would be lost in the daylight anyway
float const LIGHT_DUSK_LUMINANCE{0.325f};
float const LIGHT_DAY_LUMINANCE{1.0f};
// lights of the occupied cab, at most
std::size_t const CAB_LIGHTS_MAX{16};
// kinds of the light points of a cab model, by the start of their names (any case): the lamps of the cab and the lights of the desk
std::uint8_t const CAB_LIGHT_LAMP{0x1};
std::uint8_t const CAB_LIGHT_DASHBOARD{0x2};
std::uint8_t cab_light_kind(std::string const &Name)
{
auto const starts = [&Name](std::string_view const Prefix) {
return (Name.size() >= Prefix.size()) &&
std::equal(std::begin(Prefix), std::end(Prefix), std::begin(Name), [](char const Left, char const Right) { return Left == static_cast<char>(std::tolower(static_cast<unsigned char>(Right))); });
};
return starts("cablight") ? CAB_LIGHT_LAMP : starts("dashboardlight") ? CAB_LIGHT_DASHBOARD : 0;
}
glm::vec3 to_linear(glm::vec3 const &Color)
{
@@ -113,6 +129,7 @@ void gfx::local_lights::gather(glm::dvec3 const &Camera, scene::basic_region &Re
auto const start{std::chrono::steady_clock::now()};
m_lights.clear();
m_cablights.clear();
m_cablampon = false;
m_scenerycount = m_vehiclecount = m_compartmentcount = 0;
@@ -124,7 +141,7 @@ void gfx::local_lights::gather(glm::dvec3 const &Camera, scene::basic_region &Re
{
if (Global.gfx_lights_cab)
{
gather_cab_lamp(Geometry);
gather_cab_lights(Geometry);
}
// lamps are lost in the daylight; this also spares the work of gathering them in the middle of the day
@@ -189,8 +206,10 @@ void gfx::local_lights::gather(glm::dvec3 const &Camera, scene::basic_region &Re
void gfx::local_lights::reset()
{
m_lights.clear();
m_cablights.clear();
m_cablampon = false;
m_modellights.clear();
m_cabkinds.clear();
m_cells.clear();
m_sections.clear();
m_models.clear();
@@ -304,44 +323,86 @@ void gfx::local_lights::gather_vehicle(TDynamicObject *Vehicle, glm::dvec3 const
}
}
void gfx::local_lights::gather_cab_lamp(gfx::geometrybank_manager const &Geometry)
void gfx::local_lights::gather_cab_lights(gfx::geometrybank_manager const &Geometry)
{
// the cab is drawn, Render_cab() conditions
if (FreeFlyModeFlag || (false == Global.render_cab) || (simulation::Train == nullptr))
{
return;
}
auto const *vehicle{simulation::Train->Dynamic()};
auto const *train{simulation::Train};
auto const *vehicle{train->Dynamic()};
if ((vehicle == nullptr) || (false == vehicle->bDisplayCab) || (vehicle->mdKabina == nullptr) || (vehicle->mdKabina == vehicle->mdModel))
{
return;
}
if (vehicle->InteriorLightLevel <= 0.f)
{
return;
}
// as bright as the ambient light it used to add to the cab: lost in the daylight
auto const luminance{Global.fLuminance * gfx::vehicle_light_level(vehicle)};
auto const level{vehicle->InteriorLightLevel * std::clamp(1.25f - static_cast<float>(luminance), 0.f, 1.f)};
if (level <= 0.f)
{
return;
}
auto const &bounds{model_bounds(vehicle->mdKabina, Geometry)};
if (bounds.extent == glm::vec3{0.f})
{
return;
}
// under the ceiling, in the middle of the cab
auto const lamp{glm::vec3{bounds.center.x, std::max(bounds.center.y, bounds.center.y + bounds.extent.y - 0.25f), bounds.center.z}};
m_cablamp = light{};
m_cablamp.position = vehicle->GetWorldPosition(glm::dvec3{lamp});
// as bright as the ambient light the lamp used to add to the cab: lost in the daylight. the lamps of the cab at the level of
// their switch (off, dimmed, with or without the 110 V), the lights of the desk with the dashboard light (i-dashboardlight:)
auto const luminance{Global.fLuminance * gfx::vehicle_light_level(vehicle)};
auto const daylight{std::clamp(1.25f - static_cast<float>(luminance), 0.f, 1.f)};
auto const cablevel{std::max(vehicle->InteriorLightLevel, 0.f) * daylight};
auto const dashboardlevel{(train->btDashboardLight.GetValue() ? 1.f : 0.f) * daylight};
auto const kinds{cab_light_kinds(vehicle->mdKabina)};
if ((kinds != 0) && ((cablevel > 0.f) || (dashboardlevel > 0.f)))
{
// the light points of the cab model, in its space, the way Render_cab() sets it up
TSubModel::iInstance = reinterpret_cast<std::uintptr_t>(vehicle);
TSubModel::iVariantSeed = vehicle->variant_seed();
TSubModel::fSquareDist = 0.f;
auto const count{m_lights.size()};
m_walkcab = true;
m_walkcablevel = cablevel;
m_walkdashboardlevel = dashboardlevel;
walk(vehicle->mdKabina->Root, glm::mat4{1.f}, glm::dmat4{1.0}, Global.gfx_lights_freespot_intensity);
m_walkcab = false;
// within the cab: without shadows (gfx.lights.shadows) the light goes through its walls
auto const reach{std::max(2.f * glm::length(bounds.extent), 2.5f)};
auto const rotation{glm::dmat3{vehicle->mMatrix}};
for (auto idx{count}; idx < m_lights.size(); ++idx)
{
cab_light cablight;
cablight.source = m_lights[idx];
cablight.local = glm::vec3{m_lights[idx].position};
cablight.source.position = vehicle->GetWorldPosition(m_lights[idx].position);
auto const direction{glm::vec3{rotation * glm::dvec3{m_lights[idx].direction}}};
cablight.source.direction = (glm::length2(direction) > 0.f ? glm::normalize(direction) : glm::vec3{0.f, -1.f, 0.f});
cablight.source.range = std::min(cablight.source.range, reach);
m_cablights.emplace_back(cablight);
}
m_lights.resize(count);
}
// the lamp of a cab model without lamps of its own: under the ceiling, in the middle of the cab
if (((kinds & CAB_LIGHT_LAMP) == 0) && (cablevel > 0.f))
{
auto const lamp{glm::vec3{bounds.center.x, std::max(bounds.center.y, bounds.center.y + bounds.extent.y - 0.25f), bounds.center.z}};
cab_light cablight;
cablight.local = lamp;
cablight.source.position = vehicle->GetWorldPosition(glm::dvec3{lamp});
cablight.source.color = to_linear(vehicle->InteriorLight) * (cablevel * Global.gfx_lights_cab_intensity);
// within the cab: without shadows (gfx.lights.shadows) the lamp would light the track below and the body of the vehicle around it
cablight.source.range = std::clamp(glm::length(bounds.extent) * 1.25f, 2.5f, 8.f);
cablight.source.part = vehicle;
if (max_component(cablight.source.color) > 0.f)
{
m_cablights.emplace_back(cablight);
}
}
// the brightest first: the first ones get the shadow maps, the brightest lights the probe of the cab
std::stable_sort(std::begin(m_cablights), std::end(m_cablights), [](cab_light const &Left, cab_light const &Right) { return max_component(Left.source.color) > max_component(Right.source.color); });
if (m_cablights.size() > CAB_LIGHTS_MAX)
{
m_cablights.resize(CAB_LIGHTS_MAX);
}
m_cablampvehicle = vehicle;
m_cablamplocal = lamp;
m_cablamp.color = to_linear(vehicle->InteriorLight) * (level * Global.gfx_lights_cab_intensity);
// within the cab: without shadows (gfx.lights.shadows) the lamp would light the track below and the body of the vehicle around it
m_cablamp.range = std::clamp(glm::length(bounds.extent) * 1.25f, 2.5f, 8.f);
m_cablampon = (max_component(m_cablamp.color) > 0.f);
m_cablampon = (false == m_cablights.empty());
}
void gfx::local_lights::walk(TSubModel *Submodel, glm::mat4 const &Transform, glm::dmat4 const &Placement, float const Brightness)
@@ -376,10 +437,20 @@ void gfx::local_lights::walk(TSubModel *Submodel, glm::mat4 const &Transform, gl
}
}
void gfx::local_lights::add_light_point(TSubModel const &Submodel, glm::mat4 const &Transform, glm::dmat4 const &Placement, float const Brightness)
void gfx::local_lights::add_light_point(TSubModel const &Submodel, glm::mat4 const &Transform, glm::dmat4 const &Placement, float Brightness)
{
if (m_walkcab)
{
// the lights of the cab: with their switches, whatever the self-illumination threshold. other light points of the cab only glare
auto const kind{cab_light_kind(Submodel.pName)};
Brightness *= (kind == CAB_LIGHT_LAMP ? m_walkcablevel : kind == CAB_LIGHT_DASHBOARD ? m_walkdashboardlevel : 0.f);
if ((Brightness <= 0.f) || (Submodel.f4Emision.a <= 0.f) || (Submodel.fVisible <= 0.f))
{
return;
}
}
// lit points: with self-illumination, past its threshold. the compartments of vehicles set it with their lights
if ((Submodel.f4Emision.a <= 0.f) || (Global.fLuminance >= Submodel.fLight) || (Submodel.fVisible <= 0.f))
else if ((Submodel.f4Emision.a <= 0.f) || (Global.fLuminance >= Submodel.fLight) || (Submodel.fVisible <= 0.f))
{
return;
}
@@ -411,6 +482,11 @@ void gfx::local_lights::add_light_point(TSubModel const &Submodel, glm::mat4 con
lightpoint.owner = m_walkowner;
lightpoint.part = &Submodel;
}
else if (m_walkcab)
{
// the light of the cab, its shadow map kept by it
lightpoint.part = &Submodel;
}
m_lights.emplace_back(lightpoint);
}
@@ -444,6 +520,35 @@ bool gfx::local_lights::has_light_points(TModel3d *Model)
return found;
}
std::uint8_t gfx::local_lights::cab_light_kinds(TModel3d *Model)
{
auto lookup{m_cabkinds.find(Model)};
if (lookup != m_cabkinds.end())
{
return lookup->second;
}
std::uint8_t kinds{0};
std::vector<TSubModel const *> pending{Model->Root};
while (false == pending.empty())
{
auto const *submodel{pending.back()};
pending.pop_back();
for (; submodel != nullptr; submodel = submodel->Next)
{
if (submodel->eType == TP_FREESPOTLIGHT)
{
kinds |= cab_light_kind(submodel->pName);
}
if (submodel->Child != nullptr)
{
pending.emplace_back(submodel->Child);
}
}
}
m_cabkinds.emplace(Model, kinds);
return kinds;
}
gfx::local_lights::cell_data const &gfx::local_lights::cell_instances(scene::basic_cell &Cell, bool const Rebuild)
{
// light points are drawn in the translucent phase, so the instances which have them are on the translucent list of the cell
@@ -522,7 +627,7 @@ bool gfx::local_lights::cab_bounds(TModel3d *Cab, gfx::geometrybank_manager cons
std::string gfx::local_lights::info() const
{
return "local lights: " + std::to_string(m_lights.size()) + " (scenery " + std::to_string(m_scenerycount) + ", vehicles " + std::to_string(m_vehiclecount) + ", compartments " +
std::to_string(m_compartmentcount) + ")" + (m_cablampon ? " + cab lamp" : "") + ", placed " + std::to_string(m_placed) + ", over capacity " + std::to_string(m_dropped) +
std::to_string(m_compartmentcount) + ")" + (m_cablampon ? " + cab lights " + std::to_string(m_cablights.size()) : "") + ", placed " + std::to_string(m_placed) + ", over capacity " + std::to_string(m_dropped) +
", gather " + to_string(m_gathertime, 2) + " ms";
}
@@ -531,14 +636,14 @@ void gfx::local_lights::draw_debug_ui()
ImGui::TextUnformatted("local lights (need clustered lights)");
ImGui::Checkbox("light points of scenery models", &Global.gfx_lights_scenery);
ImGui::Checkbox("light points and compartments of vehicles", &Global.gfx_lights_vehicles);
ImGui::Checkbox("cab lamp as a light", &Global.gfx_lights_cab);
ImGui::Checkbox("cab lamp as a light (cablight... / dashboardlight... light points of the cab)", &Global.gfx_lights_cab);
ImGui::SliderFloat("light point brightness", &Global.gfx_lights_freespot_intensity, 0.f, 50.f);
ImGui::SliderFloat("compartment brightness", &Global.gfx_lights_compartment_intensity, 0.f, 10.f);
ImGui::SliderFloat("cab lamp brightness", &Global.gfx_lights_cab_intensity, 0.f, 10.f);
ImGui::SliderFloat("cab ambient kept", &Global.gfx_lights_cab_bounce, 0.f, 1.f);
ImGui::SliderFloat("range, m", &Global.gfx_lights_range, 0.f, 1000.f);
ImGui::Text("gathered %u: scenery %u, vehicles %u, compartments %u%s", static_cast<unsigned>(m_lights.size()), m_scenerycount, m_vehiclecount, m_compartmentcount,
m_cablampon ? ", cab lamp" : "");
ImGui::Text("gathered %u: scenery %u, vehicles %u, compartments %u, cab %u", static_cast<unsigned>(m_lights.size()), m_scenerycount, m_vehiclecount, m_compartmentcount,
static_cast<unsigned>(m_cablights.size()));
ImGui::Text("in the light buffer %u, over capacity %u", m_placed, m_dropped);
ImGui::Text("gather %.2f ms (cpu)", m_gathertime);
}

View File

@@ -34,7 +34,9 @@ class TDynamicObject;
// light along the -z axis of its submodel, with the cone of its glare. lamps of scenery models switched with Light_On / Light_Off
// and the self-illumination of vehicle compartments (TDynamicObject::Sections) work through the same state
// - the lit compartments of vehicles (low poly interior sections) without light points of their own: a point light in the middle
// - the lamp of the occupied cab: a point light under the top of the cab model, in place of the ambient light it used to add
// - the lights of the occupied cab, in place of the ambient light its lamp used to add: the light points of the cab model (mdKabina)
// named cablight... (the lamps of the cab, switched and dimmed with them) and dashboardlight... (the lights of the desk, switched
// with the dashboard light, i-dashboardlight:). a cab model without any cablight... gets a point light under its top instead
// gathered once per frame around the camera, written to the light buffers by the renderers (Update_Lights())
namespace gfx
{
@@ -79,20 +81,28 @@ class local_lights
{
return m_lights;
}
// the lamp of the occupied cab, when it is on and the cab is drawn; nullptr otherwise
light const *cab_lamp() const
// a light of the occupied cab: the light (part names it from frame to frame: the light point, or the vehicle for the lamp put
// under the top of the cab) and its position in the space of the cab model (mdKabina)
struct cab_light
{
return m_cablampon ? &m_cablamp : nullptr;
light source;
glm::vec3 local{0.f};
};
// the lights of the occupied cab which are on, when the cab is drawn, the brightest first; empty otherwise
std::vector<cab_light> const &cab_lights() const
{
return m_cablights;
}
// the vehicle of the cab lamp and the position of the lamp in the space of its cab model (mdKabina), when the lamp is on
// the brightest light of the cab, which lights its probe; nullptr when there is none
cab_light const *cab_principal() const
{
return m_cablights.empty() ? nullptr : &m_cablights.front();
}
// the vehicle of the lights of the cab, when any of them is on
TDynamicObject const *cab_lamp_vehicle() const
{
return m_cablampon ? m_cablampvehicle : nullptr;
}
glm::vec3 const &cab_lamp_local() const
{
return m_cablamplocal;
}
// the box of the cab model Cab (model space): its middle and half its size; false when the model has no geometry
bool cab_bounds(TModel3d *Cab, gfx::geometrybank_manager const &Geometry, glm::vec3 &Center, glm::vec3 &Extent);
// counts of the last pass which wrote the lights to the light buffer: placed, and left out for the lack of room
@@ -124,21 +134,28 @@ class local_lights
// lights of the light points of the submodel tree, gone through the way the render goes through it. Transform: model space of
// Submodel's parent, Placement: model space to world space, Brightness: of a light point of white colour
void walk(TSubModel *Submodel, glm::mat4 const &Transform, glm::dmat4 const &Placement, float const Brightness);
void add_light_point(TSubModel const &Submodel, glm::mat4 const &Transform, glm::dmat4 const &Placement, float const Brightness);
void add_light_point(TSubModel const &Submodel, glm::mat4 const &Transform, glm::dmat4 const &Placement, float Brightness);
void gather_instance(TAnimModel *Instance, glm::dvec3 const &Camera, float const Brightness, unsigned int const Framestamp);
void gather_vehicle(TDynamicObject *Vehicle, glm::dvec3 const &Camera, gfx::geometrybank_manager const &Geometry, float const Freespotbrightness, float const Compartmentbrightness);
void gather_cab_lamp(gfx::geometrybank_manager const &Geometry);
void gather_cab_lights(gfx::geometrybank_manager const &Geometry);
bool has_light_points(TModel3d *Model);
// the kinds of the light points of a cab model by their names, CAB_LIGHT_LAMP | CAB_LIGHT_DASHBOARD (locallights.cpp)
std::uint8_t cab_light_kinds(TModel3d *Model);
cell_data const &cell_instances(scene::basic_cell &Cell, bool const Rebuild);
section_data const &section(TModel3d *Model, TSubModel *Section, gfx::geometrybank_manager const &Geometry);
section_data const &model_bounds(TModel3d *Model, gfx::geometrybank_manager const &Geometry);
std::vector<light> m_lights;
light m_cablamp;
std::vector<cab_light> m_cablights;
bool m_cablampon{false};
TDynamicObject const *m_cablampvehicle{nullptr};
glm::vec3 m_cablamplocal{0.f};
// walk() goes through the cab model: only its light points of the cab and of the desk, at these levels (0-1) of their switches,
// whatever the self-illumination threshold
bool m_walkcab{false};
float m_walkcablevel{0.f};
float m_walkdashboardlevel{0.f};
std::unordered_map<TModel3d const *, bool> m_modellights; // whether the model has light points
std::unordered_map<TModel3d const *, std::uint8_t> m_cabkinds; // the kinds of light points of cab models
std::unordered_map<scene::basic_cell const *, cell_data> m_cells;
std::unordered_map<TSubModel const *, section_data> m_sections; // compartments, by their submodel
std::unordered_map<TModel3d const *, section_data> m_models; // bounds of cab models

View File

@@ -53,12 +53,12 @@ local_shadows::level_setup local_shadows::setup(int const Level)
}
}
void local_shadows::allocate(int const Size)
void local_shadows::allocate(int const Count, int const Size)
{
release();
if (Size <= 0)
if ((Count <= 0) || (Size <= 0))
return;
if (false == gpu_allocate(Size))
if (false == gpu_allocate(Count, Size))
{
ErrorLog("lamp shadow map framebuffer setup failed, the lamps have no shadows");
release();
@@ -66,14 +66,18 @@ void local_shadows::allocate(int const Size)
return;
}
m_size = Size;
WriteLog("lamp shadow maps: " + std::to_string(Size) + " x " + std::to_string(Size) + " cube");
m_cabslots.assign(static_cast<std::size_t>(Count), cab_slot{});
WriteLog("cab light shadow maps: " + std::to_string(Count) + " x " + std::to_string(Size) + " x " + std::to_string(Size) + " cube (" +
std::to_string(Count * 6 * Size / 1024 * Size * 4 / 1024) + " MB)");
}
void local_shadows::release()
{
gpu_release();
m_size = 0;
m_valid = false;
m_cabslots.clear();
m_cabwanted.clear();
m_cabdraws.clear();
}
void local_shadows::allocate_lamps(int const Count, int const Size)
@@ -129,59 +133,165 @@ void local_shadows::release_headlights()
m_headlightswanted.clear();
}
bool local_shadows::update_wanted(TDynamicObject const *Vehicle, TModel3d const *Cab, glm::vec3 const &Lamp, unsigned int const Framestamp)
void local_shadows::update_cab(TDynamicObject const *Vehicle, TModel3d const *Cab, std::vector<gfx::local_lights::cab_light> const &Lights, unsigned int const Framestamp)
{
m_cabwanted.clear();
m_cabdraws.clear();
auto const level{std::clamp(Global.gfx_lights_shadows, 0, 3)};
if (level != m_level)
auto const limit{level > 0 ? std::clamp(Global.gfx_lights_shadows_cab, 0, cab_slots_max) : 0};
if ((level != m_level) || (static_cast<std::size_t>(limit) < m_cabslots.size()))
{
m_level = level;
auto const size{setup(level).size};
if (size != m_size)
allocate(size);
m_valid = false;
release();
}
if ((m_size == 0) || (Vehicle == nullptr) || (Cab == nullptr))
auto const forget = [this]() {
for (auto &slot : m_cabslots)
{
slot = cab_slot{};
}
};
if ((limit == 0) || (Vehicle == nullptr) || (Cab == nullptr))
{
// with the lamp off nothing reads the map; once it is on again the cab may have changed
m_valid = false;
return false;
// with the lights off nothing reads the maps; once they are on again the cab may have changed
forget();
m_cabvehicle = nullptr;
m_cabmodel = nullptr;
return;
}
if ((false == m_valid) || (Vehicle != m_vehicle) || (Cab != m_cab) || (Lamp != m_lamp))
if ((Vehicle != m_cabvehicle) || (Cab != m_cabmodel))
{
m_valid = false;
forget();
m_cabvehicle = Vehicle;
m_cabmodel = Cab;
}
// the moving parts of the cab are followed every few frames
if (m_valid && (Framestamp - m_drawnframe < setup(level).interval))
// the brightest lights which cast shadows, as many as there may be maps
std::vector<gfx::local_lights::cab_light const *> picked;
for (auto const &light : Lights)
{
return false;
if (picked.size() >= static_cast<std::size_t>(limit))
{
break;
}
if (light.source.cast_shadows && (light.source.range >= LAMP_RANGE_MIN))
{
picked.emplace_back(&light);
}
}
if (picked.empty())
{
return;
}
// the maps are made as they're needed, up to the limit: a cab with its lamp alone takes one
if (picked.size() > m_cabslots.size())
{
allocate(static_cast<int>(picked.size()), setup(level).size);
if (m_cabslots.empty())
{
return;
}
}
auto const place = [&](cab_slot &Slot, std::size_t const Index, gfx::local_lights::cab_light const &Light) {
auto const nearplane{lamp_near(Light.source)};
if ((Slot.lamp != Light.local) || (Slot.nearplane != nearplane))
{
Slot.valid = false;
}
Slot.lamp = Light.local;
Slot.nearplane = nearplane;
Slot.wantedframe = Framestamp;
m_cabwanted.emplace_back(Index);
// the moving parts of the cab are followed every few frames
if ((false == Slot.valid) || (Framestamp - Slot.drawnframe >= setup(level).interval))
{
Slot.drawnframe = Framestamp;
m_cabdraws.emplace_back(Index);
}
};
// lights which already have a slot keep it, the others take the slots of lights no longer picked
std::vector<gfx::local_lights::cab_light const *> unplaced;
for (auto const *light : picked)
{
auto placed{false};
for (std::size_t idx = 0; idx < m_cabslots.size(); ++idx)
{
auto &slot{m_cabslots[idx]};
if ((slot.part == light->source.part) && (slot.wantedframe != Framestamp))
{
place(slot, idx, *light);
placed = true;
break;
}
}
if (false == placed)
{
unplaced.emplace_back(light);
}
}
for (auto const *light : unplaced)
{
for (std::size_t idx = 0; idx < m_cabslots.size(); ++idx)
{
auto &slot{m_cabslots[idx]};
if (slot.wantedframe == Framestamp)
{
continue;
}
slot = cab_slot{};
slot.part = light->source.part;
place(slot, idx, *light);
break;
}
}
m_vehicle = Vehicle;
m_cab = Cab;
m_lamp = Lamp;
m_drawnframe = Framestamp;
return true;
}
void local_shadows::begin()
void local_shadows::begin(std::size_t const Slot)
{
m_cpustart = std::chrono::steady_clock::now();
gpu_begin_cab();
if (m_cabdraws.empty() || (Slot == m_cabdraws.front()))
{
// the first map of the frame
m_framedrawcalls = 0;
m_cpustart = std::chrono::steady_clock::now();
}
m_cabdrawing = Slot;
gpu_begin_cab(Slot);
}
void local_shadows::end(std::uint32_t const Drawcalls)
{
gpu_end_cab();
m_valid = true;
m_drawcalls = Drawcalls;
m_cabslots[m_cabdrawing].valid = true;
m_framedrawcalls += Drawcalls;
++m_updatecount;
auto const elapsed{std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - m_cpustart).count()};
m_cputime = (m_cputime == 0.0 ? elapsed : m_cputime * 0.9 + elapsed * 0.1);
if (m_cabdraws.empty() || (m_cabdrawing == m_cabdraws.back()))
{
// the last map of the frame
m_drawcalls = m_framedrawcalls;
auto const elapsed{std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - m_cpustart).count()};
m_cputime = (m_cputime == 0.0 ? elapsed : m_cputime * 0.9 + elapsed * 0.1);
}
}
glm::vec4 local_shadows::weights() const
int local_shadows::cab_slot_of(TDynamicObject const *Vehicle, gfx::local_lights::cab_light const &Light) const
{
if ((Vehicle == nullptr) || (Vehicle != m_cabvehicle))
{
return -1;
}
for (auto const idx : m_cabwanted)
{
auto const &slot{m_cabslots[idx]};
if ((slot.part == Light.source.part) && slot.valid && (slot.lamp == Light.local))
{
return static_cast<int>(idx);
}
}
return -1;
}
glm::vec4 local_shadows::weights(std::size_t const Slot) const
{
// a face spans twice the distance from the lamp
return glm::vec4{static_cast<float>(cab_slot), shadow_near, 2.f / static_cast<float>(std::max(m_size, 1)), setup(m_level).radius};
return glm::vec4{static_cast<float>(Slot), m_cabslots[Slot].nearplane, 2.f / static_cast<float>(std::max(m_size, 1)), setup(m_level).radius};
}
void local_shadows::update_lamps(std::vector<gfx::local_lights::light> const &Lights, glm::dvec3 const &Camera, std::uint64_t const Version, unsigned int const Framestamp)
@@ -596,8 +706,8 @@ std::string local_shadows::info() const
std::string text;
if (m_size != 0)
{
text += "\nlamp shadows: " + std::to_string(m_size) + " cube, " + (m_valid ? "cab lamp" : "none") + ", " + std::to_string(m_drawcalls) + " drawcalls, cpu " + to_string(m_cputime, 2) +
" ms";
text += "\ncab light shadows: " + std::to_string(m_cabwanted.size()) + " of " + std::to_string(m_cabslots.size()) + " " + std::to_string(m_size) + " cubes, " +
std::to_string(m_drawcalls) + " drawcalls, cpu " + to_string(m_cputime, 2) + " ms";
}
if (false == m_slots.empty())
{
@@ -630,9 +740,11 @@ void local_shadows::draw_debug_ui()
{
char const *levels[]{"no", "low (512, every 4th frame)", "medium (1024, every 2nd frame)", "high (1024, every frame, softer)"};
ImGui::Combo("lamp shadows (gfx.lights.shadows)", &Global.gfx_lights_shadows, levels, IM_ARRAYSIZE(levels));
ImGui::SliderInt("cab lights with shadows (gfx.lights.shadows.cab)", &Global.gfx_lights_shadows_cab, 0, cab_slots_max);
if (m_size != 0)
{
ImGui::Text("cab: cube %d x %d, %s, drawn %u times, %u drawcalls, %.2f ms (cpu)", m_size, m_size, m_valid ? "cab lamp" : "no lamp", m_updatecount, m_drawcalls, m_cputime);
ImGui::Text("cab: %u of %u cubes %d x %d, drawn %u times, %u drawcalls, %.2f ms (cpu)", static_cast<unsigned>(m_cabwanted.size()), static_cast<unsigned>(m_cabslots.size()), m_size,
m_size, m_updatecount, m_drawcalls, m_cputime);
}
ImGui::SliderInt("scenery lamps with shadows (gfx.lights.shadows.lamps)", &Global.gfx_lights_shadows_lamps, 0, 32);
ImGui::SliderFloat("their reach from the camera, m (.lamps.range)", &Global.gfx_lights_shadows_lamps_range, 0.f, 500.f);

View File

@@ -28,9 +28,11 @@ namespace gfx
// the shadow maps of the lamps (Global.gfx_lights_shadows), the part shared by the renderers (stage P4 of
// claude/vulkan14-renderer-plan.md): which lamps and headlights get maps, when they are drawn, what the shading reads; the
// backend (opengl46_local_shadows, the vulkan renderer) keeps the maps and draws to them through the gpu_* methods.
// - the lamp of the occupied cab, with the shadows cast by the cab (the model drawn by Render_cab()). the map is kept in the space
// of the cab model, centred on the lamp: it stays right while the vehicle moves and turns, and is drawn anew only every few frames
// (for the moving parts of the cab: levers, doors, wipers) or when the lamp, the cab or the level change;
// - the lights of the occupied cab (gfx::local_lights::cab_lights(): its lamps, the lights of its desk), the brightest
// Global.gfx_lights_shadows_cab of them, with the shadows cast by the cab (the model drawn by Render_cab()). the map of a light is
// kept in the space of the cab model, centred on the light: it stays right while the vehicle moves and turns, and is drawn anew only
// every few frames (for the moving parts of the cab: levers, doors, wipers) or when the light, the cab or the level change. a light
// keeps its map (slot) while it's among those picked;
// - the lamps of scenery models closest to the camera (Global.gfx_lights_shadows_lamps of them, within
// Global.gfx_lights_shadows_lamps_range). the map of such a lamp is kept in world axes, centred on the lamp, and has two parts:
// the scenery (drawn once, when the lamp gets its slot or the scene changes) and a working copy of it, with the vehicles near
@@ -51,38 +53,50 @@ class local_shadows
public:
// near plane of the maps, metres. surfaces closer to the lamp (its own fitting) cast no shadow
static constexpr float shadow_near{0.15f};
// layer of the map of the cab lamp in its array
static constexpr int cab_slot{0};
// lights of the cab with shadow maps, at most
static constexpr int cab_slots_max{8};
local_shadows() = default;
virtual ~local_shadows() = default;
local_shadows(const local_shadows &) = delete;
local_shadows &operator=(const local_shadows &) = delete;
// the cab lamp
// the lights of the cab
// whether the map of the cab lamp is to be drawn in this frame: Vehicle and Cab the occupied vehicle and its cab model,
// Lamp the position of the lamp in the space of the cab model; Vehicle nullptr when the lamp is off. (re)makes the map to
// the size of the level when the level changed
bool update_wanted(TDynamicObject const *Vehicle, TModel3d const *Cab, glm::vec3 const &Lamp, unsigned int const Framestamp);
// sets up the drawing to the map: the target, viewport, cleared faces, the state of the faces. the geometry is to be
// drawn with the programs of the cube maps and a modelview from the space of the model to the space of the map
void begin();
// a light of the cab with a slot (a cube of the array of the cab)
struct cab_slot
{
void const *part{nullptr}; // the light it is, see gfx::local_lights::cab_light
glm::vec3 lamp{0.f}; // position of the light, in the space of the cab model
float nearplane{shadow_near}; // near plane of the map, see lamp_slot
bool valid{false}; // the map holds the shadows of the light where it is
unsigned int drawnframe{0}; // framestamp the map was last drawn in
unsigned int wantedframe{~0u}; // framestamp it was last among the lights given shadows
};
// picks the lights of the cab which get shadows in this frame, and the maps to be drawn in it: Vehicle and Cab the occupied
// vehicle and its cab model (nullptr when the cab has no lights), Lights its lights, the brightest first. (re)makes the maps when
// the level, the size or the number wanted changed
void update_cab(TDynamicObject const *Vehicle, TModel3d const *Cab, std::vector<gfx::local_lights::cab_light> const &Lights, unsigned int const Framestamp);
// the slots whose maps are to be drawn in this frame
std::vector<std::size_t> const &cab_draws() const
{
return m_cabdraws;
}
cab_slot const &cab(std::size_t const Slot) const
{
return m_cabslots[Slot];
}
// sets up the drawing to the map of the slot: the target, viewport, cleared faces, the state of the faces, its near plane. the
// geometry is to be drawn with the programs of the cube maps and a modelview from the space of the model to the space of the map
// (the light of the slot at the origin)
void begin(std::size_t const Slot);
// ends the drawing to the map
void end(std::uint32_t const Drawcalls);
// whether the map holds the shadows of the lamp of Vehicle, the occupied vehicle
bool ready(TDynamicObject const *Vehicle) const
{
return m_valid && (Vehicle != nullptr) && (Vehicle == m_vehicle);
}
// position of the lamp the map was drawn for, in the space of the cab model
glm::vec3 const &lamp() const
{
return m_lamp;
}
// the headlight_weights of the light which reads the map: layer, near plane, size of a texel one metre from the lamp,
// the slot of the light of the cab of Vehicle with its shadows ready, -1 if it has none
int cab_slot_of(TDynamicObject const *Vehicle, gfx::local_lights::cab_light const &Light) const;
// the headlight_weights of the light which reads the map of the slot: layer, near plane, size of a texel one metre from the lamp,
// radius of the filter in texels
glm::vec4 weights() const;
glm::vec4 weights(std::size_t const Slot) const;
// lamps of scenery models
@@ -228,17 +242,17 @@ class local_shadows
};
static level_setup setup(int const Level);
// what the backend does with the maps: makes them (false: they can't be made) and lets them go; the cab lamp: one cube, the
// lamps: 2 x Count cubes (the scenery parts, then the working copies), the headlights: Count layers
virtual bool gpu_allocate(int const Size) = 0;
// what the backend does with the maps: makes them (false: they can't be made) and lets them go; the lights of the cab: Count
// cubes, the lamps: 2 x Count cubes (the scenery parts, then the working copies), the headlights: Count layers
virtual bool gpu_allocate(int const Count, int const Size) = 0;
virtual void gpu_release() = 0;
virtual bool gpu_allocate_lamps(int const Count, int const Size) = 0;
virtual void gpu_release_lamps() = 0;
virtual bool gpu_allocate_headlights(int const Count, int const Size) = 0;
virtual void gpu_release_headlights() = 0;
// the drawing to the map of the cab lamp, to a cube of the lamps (Vehicles: the working copy, from a copy of the scenery part
// Scenerycube; the scenery part cleared otherwise) and to a layer of the headlights
virtual void gpu_begin_cab() = 0;
// the drawing to the cube of a light of the cab (with the near plane of the slot), to a cube of the lamps (Vehicles: the working
// copy, from a copy of the scenery part Scenerycube; the scenery part cleared otherwise) and to a layer of the headlights
virtual void gpu_begin_cab(std::size_t const Slot) = 0;
virtual void gpu_end_cab() = 0;
virtual void gpu_begin_lamp(int const Scenerycube, int const Workingcube, bool const Vehicles) = 0;
virtual void gpu_end_lamp() = 0;
@@ -248,7 +262,7 @@ class local_shadows
virtual void gpu_lamps_timer(bool const Begin) = 0;
virtual void gpu_headlights_timer(bool const Begin) = 0;
void allocate(int const Size);
void allocate(int const Count, int const Size);
void release();
void allocate_lamps(int const Count, int const Size);
void release_lamps();
@@ -256,13 +270,14 @@ class local_shadows
void release_headlights();
int m_level{-1}; // level the maps were made for
// cab lamp
int m_size{0}; // of a face of the map, 0 when there is none
bool m_valid{false}; // the map holds the shadows of the lamp of m_vehicle
TDynamicObject const *m_vehicle{nullptr};
TModel3d const *m_cab{nullptr};
glm::vec3 m_lamp{0.f};
unsigned int m_drawnframe{0}; // framestamp the map was last drawn in
// lights of the cab: a cube per slot
int m_size{0}; // of a face of the maps, 0 when there are none
std::vector<cab_slot> m_cabslots;
std::vector<std::size_t> m_cabwanted; // slots of the lights picked in this frame
std::vector<std::size_t> m_cabdraws; // and of those whose maps are drawn in it
std::size_t m_cabdrawing{0}; // slot begin() set up the drawing to
TDynamicObject const *m_cabvehicle{nullptr}; // the vehicle and the cab model the maps of the slots are of
TModel3d const *m_cabmodel{nullptr};
// scenery lamps: the scenery parts of the slots first, then their working copies
int m_lampssize{0};
std::vector<lamp_slot> m_slots;
@@ -282,8 +297,9 @@ class local_shadows
std::vector<std::pair<float, light_array::light_record const *>> m_headlightcandidates;
std::vector<light_array::light_record const *> m_headlightpicked;
// statistics
std::uint32_t m_drawcalls{0}; // of the last drawing of the cab map
std::uint32_t m_updatecount{0}; // drawings of the cab map so far
std::uint32_t m_drawcalls{0}; // of the drawings of the cab maps in the last frame with any
std::uint32_t m_framedrawcalls{0};
std::uint32_t m_updatecount{0}; // drawings of the cab maps so far
double m_cputime{0.0}; // milliseconds, smoothed
std::chrono::steady_clock::time_point m_cpustart;
std::uint32_t m_lampsscenery{0}; // scenery parts drawn so far

View File

@@ -567,6 +567,8 @@ class scene_walk
bool m_previewing{false};
// the last lighting of the pass placed the cab lamp among the lights: the cab is lit by it, not by an addition to the ambient light
bool m_cablamplit{false};
// and the number of the lights of the cab it placed, the first ones of the light buffer after the sun
int m_cablamps{0};
// the colour pass reads the probe of the cab (gfx::cab_probe): the light of the cab lamp the cab reflects lights it again from the
// probe, so the ambient light keeps no share of the lamp
bool m_cabprobelit{false};