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:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 §ion(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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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};
|
||||
|
||||
Reference in New Issue
Block a user