17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-18 12:39:18 +02:00

Merge branch 'master' into feat/uart-2.0

This commit is contained in:
2026-09-19 22:14:43 +02:00
15 changed files with 178 additions and 60 deletions

View File

@@ -31,6 +31,7 @@ http://mozilla.org/MPL/2.0/.
#include "utilities/dictionary.h"
#include "version_info.h"
#include <chrono>
#include <format>
#include "utilities/translation.h"
#if WITH_DISCORD_RPC
@@ -320,9 +321,13 @@ int eu07_application::init(int Argc, char *Argv[])
return result;
}
// the run's seed, settled once and kept in Globals: the engines come out of it.
// narrowed on purpose - mt19937 and seed_of() both work on 32 bits, and that is
// the seed somebody can write down and pass on
if (!Global.random_seed)
Global.random_seed = std::random_device{}();
Global.random_seed = static_cast<std::uint32_t>(true_random_seed());
Global.random_engine.seed(Global.random_seed);
Global.local_random_engine.seed(Global.random_seed);
// configure the OS console according to Globals.ShowSystemConsole.
// must run AFTER init_settings (so the ini-loaded value is honoured) and
@@ -1140,10 +1145,21 @@ int eu07_application::init_settings(int Argc, char *Argv[])
Global.local_start_vehicle = ToLower(Argv[++i]);
}
}
else if (token == "-seed")
{
// a number is the seed; anything else is a word that stands for one. said in the
// log as it was given, because that is what gets passed on to somebody else
if (i + 1 < Argc)
{
Global.random_seed = seed_of(Argv[++i]);
WriteLog(std::format("seed given on the command line: {} -> {}", Argv[i], Global.random_seed));
}
}
else
{
std::cout << "usage: " << std::string(Argv[0]) << " [-s sceneryfilepath]"
<< " [-v vehiclename]" << std::endl;
<< " [-v vehiclename]"
<< " [-seed number|word]" << std::endl;
return -1;
}
}
@@ -1421,7 +1437,7 @@ int eu07_application::init_data()
int eu07_application::init_modes()
{
Global.local_random_engine.seed(std::random_device{}());
Global.local_random_engine.seed(static_cast<std::mt19937::result_type>(true_random_seed()));
if ((!Global.network_servers.empty() || Global.network_client) && Global.SceneryFile.empty())
{

View File

@@ -403,10 +403,9 @@ std::string *brush_object_list::GetRandomObject()
if (Objects.empty())
return &empty;
static std::mt19937 rng{std::random_device{}()};
std::uniform_int_distribution<size_t> dist(0, Objects.size() - 1);
return &Objects[dist(rng)];
return &Objects[dist(Global.local_random_engine)];
}
void brush_object_list::render()

View File

@@ -218,7 +218,10 @@ void CSkyDome::RebuildColors() {
vertex = glm::normalize( m_vertices[ i ] );
// angle between sun and vertex
const float gamma = std::acos( glm::dot( vertex, m_sundirection ) );
// NOTE: the dot product can exceed the [ -1, 1 ] range by a rounding error,
// which would turn the vertex - and, through the averages, the fog and ambient
// light - into a NaN, so it gets clamped before feeding it to acos()
const float gamma = std::acos( std::clamp( glm::dot( vertex, m_sundirection ), -1.0f, 1.0f ) );
// warning : major hack!!! .. i had to do something with values under horizon
//vertex.y = Clamp<float>( vertex.y, 0.05f, 1.0f );

View File

@@ -10,9 +10,26 @@ layout(location = 0) out vec4 out_color;
layout(location = 1) out vec4 out_motion;
#endif
// the skydome vertex colours carry Perez luminance scaled by a fixed exposure
// (see CSkyDome::SetExposure), so the circumsolar region and the horizon band
// routinely land well above 1.0. hard-clipping them, as this shader used to do,
// flattened everything within ~20 degrees of the sun - and most of the lower sky -
// into one solid white patch. compress the top end instead: below the knee the
// response is unchanged, above it it rolls off towards SKY_SHOULDER_WHITE, so the
// brightness gradient around the sun survives and the patch keeps a soft edge.
const float SKY_SHOULDER_KNEE = 0.25;
const float SKY_SHOULDER_WHITE = 0.95;
vec3 sky_shoulder(vec3 color)
{
const float range = SKY_SHOULDER_WHITE - SKY_SHOULDER_KNEE;
vec3 excess = max(color - vec3(SKY_SHOULDER_KNEE), vec3(0.0));
return min(color, vec3(SKY_SHOULDER_KNEE)) + range * (vec3(1.0) - exp(-excess / range));
}
void main()
{
vec3 col = clamp(pow(f_color.rgb, vec3(2.2)),0, 1);
vec3 col = sky_shoulder(pow(max(f_color.rgb, vec3(0.0)), vec3(2.2)));
#if POSTFX_ENABLED
out_color = vec4(apply_fog(col), 1.0f);
#else

View File

@@ -45,7 +45,20 @@ float calc_shadow()
if (distance <= cascade_end[cascade])
break;
float dist_casc = distance / cascade_end[cascade];
vec3 coords = f_light_pos[cascade].xyz / f_light_pos[cascade].w;
// Normal offset: sample the shadow map from a point pushed along the surface normal
// by a fraction of the cascade's texel footprint. A constant depth bias can't keep up
// with large texels (far cascades, small shadow maps) on surfaces at a grazing angle
// to the sun, which then show shadow acne stripes. lightview maps view space metres
// to [0,1] shadow map coordinates, so its x/y row lengths give texels per metre.
vec3 shadownormal = normalize(f_normal);
float shadowNdotL = clamp(dot(shadownormal, normalize(-lights[0].dir)), 0.0, 1.0);
mat4 shadowview = lightview[cascade];
float texelspermetre = min(length(vec3(shadowview[0][0], shadowview[1][0], shadowview[2][0])),
length(vec3(shadowview[0][1], shadowview[1][1], shadowview[2][1]))) * float(textureSize(shadowmap, 0).x);
float texelsize = 1.0 / texelspermetre;
vec3 shadowpos = f_pos.xyz + shadownormal * (texelsize * 1.5 * (1.0 - shadowNdotL));
vec4 lightpos = shadowview * vec4(shadowpos, 1.0);
vec3 coords = lightpos.xyz / lightpos.w;
if (coords.z < 0.0)
return 0.0f;
@@ -64,29 +77,28 @@ float calc_shadow()
radius = 0.5;
#if defined(GL_ARB_gpu_shader5) || defined(GL_EXT_gpu_shader5) || __VERSION__ >= 400
// Fast path -- replace the original 4x4 grid of individual hardware-PCF
// lookups with 4 textureGather() calls. Each gather returns the 4 raw
// shadow comparisons of a 2x2 texel footprint, so 4 gathers laid out at
// (+-1, +-1) * radius * texel from the sample center cover the same 4x4
// sample area as the original kernel; summing all 16 comparisons and
// dividing by 16 reproduces the original loop's averaging. The cost on
// the TMUs drops from 16 hardware-PCF samples to 4 gathers (the gather
// path returns 4 values per fetch where the original needed 4 fetches),
// roughly a 4x reduction in shadow-sample work. The only thing dropped
// vs. the hardware-PCF path is the implicit bilinear blending inside
// each 2x2 footprint -- effectively turning a tent-weighted kernel into
// a box-weighted one of the same extent, which is imperceptible in
// motion. calc_shadow() is by far the heaviest piece of the lighting
// shader, so this is a measurable GPU saving on every shaded fragment.
// Fast path: 4 textureGather() calls cover a 4x4 texel block. Raw gathered
// comparisons must be weighted by the sub-texel position (bilinear weights),
// otherwise the result is constant across each shadow map texel and shadow
// edges turn into texel-sized stair steps. With per-axis weights
// (1-f, 1, 1, f) this equals a 3x3 grid of hardware bilinear PCF taps.
vec2 size = vec2(textureSize(shadowmap, 0).xy);
vec2 uv = coords.xy * size - 0.5;
vec2 base = floor(uv);
vec2 f = uv - base;
float refz = coords.z + bias;
float layer = float(cascade);
vec2 off = radius * texel;
vec4 g0 = textureGather(shadowmap, vec3(coords.xy + vec2(-off.x, -off.y), layer), refz);
vec4 g1 = textureGather(shadowmap, vec3(coords.xy + vec2( off.x, -off.y), layer), refz);
vec4 g2 = textureGather(shadowmap, vec3(coords.xy + vec2(-off.x, off.y), layer), refz);
vec4 g3 = textureGather(shadowmap, vec3(coords.xy + vec2( off.x, off.y), layer), refz);
float shadow = dot(g0 + g1 + g2 + g3, vec4(1.0 / 16.0));
return shadow;
// sampling at texel corners selects blocks with lower-left texel base-1 and base+1
vec4 g00 = textureGather(shadowmap, vec3((base + vec2(0.0, 0.0)) / size, layer), refz);
vec4 g10 = textureGather(shadowmap, vec3((base + vec2(2.0, 0.0)) / size, layer), refz);
vec4 g01 = textureGather(shadowmap, vec3((base + vec2(0.0, 2.0)) / size, layer), refz);
vec4 g11 = textureGather(shadowmap, vec3((base + vec2(2.0, 2.0)) / size, layer), refz);
// gather component order: x=(0,1) y=(1,1) z=(1,0) w=(0,0)
float shadow = dot(g00, vec4(1.0 - f.x, 1.0, 1.0 - f.y, (1.0 - f.x) * (1.0 - f.y)))
+ dot(g10, vec4(1.0, f.x, f.x * (1.0 - f.y), 1.0 - f.y))
+ dot(g01, vec4((1.0 - f.x) * f.y, f.y, 1.0, 1.0 - f.x))
+ dot(g11, vec4(f.y, f.x * f.y, f.x, 1.0));
return shadow / 9.0;
#else
// Fallback for drivers without textureGather on shadow samplers
// (notably GLES 3.0 and any 3.3 desktop driver that doesn't expose
@@ -256,17 +268,29 @@ vec3 EnvBRDFApprox(vec3 F0, float roughness, float NoV)
return F0 * AB.x + AB.y;
}
// Diffuse colour of the submodel (t3d/e3d) or shape, used as the albedo multiplier:
// untextured materials take it as their colour, textured ones multiply the base texture by it
// (same as the legacy renderer's GL_MODULATE). param[0].rgb holds the authored sRGB colour,
// param[1].x its intensity (HSV value unless a material overrides it).
vec3 material_diffuse()
{
vec3 diffusecolor = pow(max(param[0].rgb, vec3(0.0)), vec3(2.2));
float value = max(param[0].r, max(param[0].g, param[0].b));
return (value > 0.0 ? diffusecolor * (param[1].x / value) : vec3(0.0));
}
// [0] - diffuse, [1] - specular
// do magic here
vec3 apply_lights(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, float reflectivity, float specularity, float shadowtone)
{
vec3 basecolor = param[0].rgb;
// Scale ambient before it gets tinted by basecolor / texture.
// Diffuse colour is applied once, to the albedo; the light terms below are not scaled by it again
texturecolor *= material_diffuse();
// Scale ambient before it gets tinted by the texture.
// Sun, headlights and emission are added afterwards so they are NOT
// attenuated by AMBIENT_SCALE - this only dims the indirect term.
fragcolor *= basecolor * AMBIENT_SCALE;
fragcolor *= AMBIENT_SCALE;
vec3 emissioncolor = basecolor * emission;
vec3 emissioncolor = vec3(emission);
vec3 view_dir = normalize(-f_pos.xyz);
float NdotV = max(dot(fragnormal, view_dir), 0.0);
@@ -302,7 +326,7 @@ vec3 apply_lights(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, float refl
// panels, vehicle bodies and terrain reads as a clear edge rather
// than a soft Lambertian ramp. Tunable via SUN_NDOTL_SHARPNESS.
float sun_NdotL = pow(sunlight.x, SUN_NDOTL_SHARPNESS);
float diffuseamount = sun_NdotL * param[1].x * lights[0].intensity;
float diffuseamount = sun_NdotL * lights[0].intensity;
float shadow1 = 0.0;
if (shadowtone < 1.0)
@@ -317,7 +341,7 @@ vec3 apply_lights(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, float refl
{
light_s light = lights[i];
vec2 part = calc_headlights(light, fragnormal);
fragcolor += light.color * (part.x * param[1].x + part.y * param[1].y) * light.intensity;
fragcolor += light.color * (part.x + part.y * param[1].y) * light.intensity;
}
float specularamount = sunlight.y * param[1].y * specularity * lights[0].intensity

View File

@@ -9,9 +9,9 @@ in vec4 f_clip_future_pos;
#param (color, 0, 0, 4, diffuse)
#param (diffuse, 1, 0, 1, diffuse)
#param (specular, 0.2, 0.2, 0.2, specular)
#param (reflection, 0.5, 0.5, 0.5, zero)
#param (glossiness, 0.5, 0.5, 0.5, glossiness)
#param (specular, 1, 1, 1, specular)
#param (reflection, 1, 2, 1, zero)
#param (glossiness, 1, 3, 1, glossiness)
layout(location = 0) out vec4 out_color;
#if MOTIONBLUR_ENABLED
@@ -24,7 +24,8 @@ layout(location = 1) out vec4 out_motion;
void main()
{
vec4 tex_color = vec4(pow(param[0].rgb, vec3(2.2)), param[0].a);
// no texture: the diffuse colour alone makes the albedo, apply_lights() applies it to the white base
vec4 tex_color = vec4(1.0, 1.0, 1.0, param[0].a);
// if (tex_color.a < opacity)
// discard;
@@ -32,7 +33,7 @@ void main()
vec3 fragcolor = ambient;
vec3 fragnormal = normalize(f_normal);
float reflectivity = param[1].z;
float specularity = (tex_color.r + tex_color.g + tex_color.b) * 0.5;
float specularity = dot(pow(param[0].rgb, vec3(2.2)), vec3(0.5));
glossiness = abs(param[1].w);
fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone);

View File

@@ -41,7 +41,7 @@ void main()
//float specularity = (tex_color.r + tex_color.g + tex_color.b) * 0.5;
glossiness = abs(param[1].w);
fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, 0, shadow_tone);
fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, 0.0, shadow_tone);
vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult);
/*
float distance = dot(f_pos.xyz, f_pos.xyz);

View File

@@ -44,7 +44,7 @@ void main()
float specularity = (tex_color.r + tex_color.g + tex_color.b) * 0.5;
glossiness = abs(param[1].w);
fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, 0, shadow_tone);
fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, 0.0, shadow_tone);
if(alphatestfail)
fragcolor.r = 1.0;

View File

@@ -32,14 +32,15 @@ uniform sampler2D normalmap;
vec3 apply_lights_sunless(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, float reflectivity, float specularity, float shadowtone)
{
vec3 basecolor = param[0].rgb;
// diffuse colour applied once, to the albedo (see material_diffuse() in light_common.glsl)
texturecolor *= material_diffuse();
// Cab interior: dim ambient less than the exterior path because
// ambient is the dominant indoor illumination. 0.80 trims the
// brightest faces without making the cab feel under-lit.
fragcolor *= basecolor * 0.80;
fragcolor *= 0.80;
vec3 emissioncolor = basecolor * emission;
vec3 emissioncolor = vec3(emission);
vec3 envcolor = envmap_color(fragnormal);
// yuv path
@@ -60,7 +61,7 @@ vec3 apply_lights_sunless(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, fl
// Sharpen N.L for stronger contrast between lit and shaded cab
// surfaces (uses SUN_NDOTL_SHARPNESS from light_common.glsl).
float sun_NdotL = pow(sunlight.x, SUN_NDOTL_SHARPNESS);
float diffuseamount = (sun_NdotL * param[1].x) * lights[0].intensity;
float diffuseamount = sun_NdotL * lights[0].intensity;
fragcolor += envcolor * reflectivity;
float specularamount = (sunlight.y * param[1].y * specularity) * lights[0].intensity;
glossiness = abs(param[1].w);
@@ -79,7 +80,7 @@ vec3 apply_lights_sunless(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, fl
// else if (light.type == LIGHT_HEADLIGHTS)
part = calc_headlights(light, fragnormal);
fragcolor += light.color * (part.x * param[1].x + part.y * param[1].y) * light.intensity;
fragcolor += light.color * (part.x + part.y * param[1].y) * light.intensity;
}
if (shadowtone < 1.0)

View File

@@ -36,12 +36,13 @@ uniform sampler2D specgloss;
vec3 apply_lights_sunless(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, float reflectivity, float specularity, float shadowtone)
{
vec3 basecolor = param[0].rgb;
// diffuse colour applied once, to the albedo (see material_diffuse() in light_common.glsl)
texturecolor *= material_diffuse();
// Cab interior: dim ambient less than the exterior path - ambient
// is the dominant indoor illumination, but it was still too bright.
fragcolor *= basecolor * 0.80;
fragcolor *= 0.80;
vec3 emissioncolor = basecolor * emission;
vec3 emissioncolor = vec3(emission);
vec3 view_dir = normalize(-f_pos.xyz);
float NdotV = max(dot(fragnormal, view_dir), 0.0);
@@ -71,7 +72,7 @@ vec3 apply_lights_sunless(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, fl
// Sharpen N.L for stronger contrast between lit and shaded cab
// surfaces (uses SUN_NDOTL_SHARPNESS from light_common.glsl).
float sun_NdotL = pow(sunlight.x, SUN_NDOTL_SHARPNESS);
float diffuseamount = sun_NdotL * param[1].x * lights[0].intensity;
float diffuseamount = sun_NdotL * lights[0].intensity;
float specularamount = sunlight.y * param[1].y * specularity * lights[0].intensity;
@@ -79,7 +80,7 @@ vec3 apply_lights_sunless(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, fl
{
light_s light = lights[i];
vec2 part = calc_headlights(light, fragnormal);
fragcolor += light.color * (part.x * param[1].x + part.y * param[1].y) * light.intensity;
fragcolor += light.color * (part.x + part.y * param[1].y) * light.intensity;
}
if (shadowtone < 1.0)

View File

@@ -7,3 +7,7 @@ sonar.sourceEncoding=UTF-8
# Exclusions
sonar.exclusions=betterRenderer/**,ref/**,imgui/**,stb/**,build/**,bw-output/**
# Excluding all files from coverage making the gate skip that
# instead of failing it.
sonar.coverage.exclusions=**/*

View File

@@ -726,7 +726,9 @@ bool global_settings::ConfigParseSimulation(cParser& Parser, const std::string&
if (token == "randomseed")
{
ParseOne(Parser, Global.random_seed, 1);
std::string text;
ParseOne(Parser, text);
Global.random_seed = seed_of(text);
return true;
}

View File

@@ -15,6 +15,7 @@ Copyright (C) 2007-2014 Maciej Cierniak
//
//#include <sys/types.h>
//#include <sys/stat.h>
#include <charconv>
#include <ranges>
//#ifndef WIN32
//#include <unistd.h>
@@ -28,9 +29,9 @@ Copyright (C) 2007-2014 Maciej Cierniak
#include "utilities/Globals.h"
#include "utilities/parser.h"
#include "utilities/U8.h"
#include "utilities/Logs.h"
//#include "utilities/Logs.h"
// TODO: This shouldn't be in Globals?
bool DebugModeFlag = false;
@@ -137,15 +138,51 @@ int Random(int min, int max)
return dist(Global.random_engine);
}
std::uint64_t true_random_seed()
{
try
{
std::random_device rd;
if (rd.entropy() > 0.0)
{
// NOTE: both halves have to be 64 bit wide before the shift - shifting a std::uint32_t
// by 32 is undefined behaviour, and drops the high half of the seed
std::uint64_t const high{rd()};
std::uint64_t const low{rd()};
return (high << 32) | low;
}
}
catch (std::exception const &Error)
{
// std::random_device throws when the machine has no usable hardware entropy source;
// the clock below stands in for it, which is worth saying out loud since it is a
// weaker seed than the caller asked for
WriteLog(std::format("random: no hardware entropy source ({}), seeding from the clock instead", Error.what()));
}
return static_cast<std::uint64_t>(std::chrono::high_resolution_clock::now().time_since_epoch().count());
}
std::uint32_t seed_of(std::string const &Text)
{
if (std::uint32_t number{}; std::from_chars(Text.data(), Text.data() + Text.size(), number).ec == std::errc{})
{
return number;
}
std::uint32_t hash{};
for (auto const character : Text)
{
hash = hash * 31u + static_cast<std::uint32_t>(static_cast<unsigned char>(character));
}
return hash;
}
std::string generate_uuid_v4()
{
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<int> dist(0, 255);
std::array<uint8_t, 16> bytes;
for (auto &b : bytes)
b = static_cast<uint8_t>(dist(gen));
b = static_cast<uint8_t>(dist(Global.random_engine));
// UUID v4 (RFC 4122)
bytes[6] = bytes[6] & 0x0F | 0x40;

View File

@@ -62,6 +62,18 @@ inline long Round(double const f)
double Random(double a, double b);
int Random(int min, int max);
/// @author lxvia
/// @brief Returns a random seed from the system's entropy source, if available. Otherwise, falls back to a clock-based seed.
/// @return A random seed as 64 bit unsigned integer
std::uint64_t true_random_seed();
// The seed a word stands for. A number is taken as it is written; anything else is
// hashed the way Java's String.hashCode does it, so the same word gives the same
// world on every platform and every build - which is the whole point of telling
// somebody a seed.
std::uint32_t seed_of(std::string const &Text);
std::string generate_uuid_v4();
double LocalRandom(double a, double b);

View File

@@ -6,6 +6,8 @@
#include <string>
#include <algorithm>
#include <cstdint>
#include "utilities/utilities.h"
#include "utilities/Globals.h"
class UID {
public:
@@ -13,10 +15,9 @@ public:
static UID random() {
static thread_local std::mt19937_64 gen(std::random_device{}());
UID u;
uint64_t a = gen();
uint64_t b = gen();
uint64_t a = Global.random_engine();
uint64_t b = Global.random_engine();
for (int i = 0; i < 8; ++i) u.bytes[i] = uint8_t(a >> (i * 8) & 0xFF);
for (int i = 0; i < 8; ++i) u.bytes[8 + i] = uint8_t(b >> (i * 8) & 0xFF);