17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-17 03:59:19 +02:00
Files
maszyna/rendering/openglrenderer.cpp
pawelw 51521a135a Text displays: accept characters above 127, in windows-1250 or utf-8 (#198)
* Text displays: accept characters above 127, in windows-1250 or utf-8

A text display (sub-model of type "text") looks up the sub-model of each character
in a 256 entry table, and indexed it with a plain char. With char being signed, any
character above 127 gave a negative index: reading the text went outside the table,
and so did filling the table if a character sub-model had such a name. In effect the
displays could only show ASCII, and a text with national characters could crash.

The lookup is now keyed by unicode code point. Both the displayed text and the names
of the character sub-models are decoded as utf-8 if they are valid utf-8, and as
windows-1250 otherwise, so legacy (ansi) sceneries and models work next to utf-8
ones, and the two can be mixed. A short ansi text can be valid utf-8 by accident
(two upper case accented letters in a row will do); when both readings are possible
the one which finds more of its characters in the model is used.

The rest is unchanged: the first character of the text still isn't displayed, the
characters are still drawn in the translucent pass only. A missing text pointer no
longer gets dereferenced.

* Text displays: prepare the characters once for each text

The sequence of character sub-models was cached for the last text only, so
displays of several instances drawn in turn decoded their texts again on
every draw. It is now kept for each text the display shows; the texts of the
instances don't change, so a draw costs a single lookup.

Also the comments added in the renderers are now in English.

* Text displays: move the text decoding to utilities

utf8_to_utf32() and win1250_to_utf32(), with the windows-1250 table, now sit
next to win1250_to_utf8(). The existing function maps only the Polish letters
and passes other bytes above 127 through, so it can't serve the displays.

* Text displays: address the SonarCloud findings

std::array instead of the C-style arrays, the utf-8 sequence length worked
out in a function of its own instead of nested conditional operators,
std::byte for the bit operations, std::ranges::count_if and contains().
The character loop of the renderers moved to Render_Alpha_text(), which
takes the nesting back to the limit, and the static pasText is accessed
through the class.
2026-10-03 12:05:32 +02:00

4568 lines
187 KiB
C++

/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "rendering/openglrenderer.h"
#include "utilities/color.h"
#include "utilities/Globals.h"
#include "vehicle/Camera.h"
#include "utilities/Timer.h"
#include "simulation/simulation.h"
#include "simulation/simulationtime.h"
#include "vehicle/Train.h"
#include "vehicle/DynObj.h"
#include "model/AnimModel.h"
#include "world/Traction.h"
#include "application/application.h"
#include "utilities/Logs.h"
#include "rendering/openglgeometrybank.h"
#include "rendering/openglcolor.h"
#include "rendering/screenshot.h"
#include <imgui/imgui_impl_opengl2.h>
int constexpr EU07_PICKBUFFERSIZE { 1024 }; // size of (square) textures bound with the pick framebuffer
int constexpr EU07_ENVIRONMENTBUFFERSIZE { 256 }; // size of (square) environmental cube map texture
int constexpr EU07_REFLECTIONFIDELITYOFFSET { 250 }; // artificial increase of range for reflection pass detail reduction
namespace {
// returns material assigned to the submodel, resolving replacable skins. null_handle if no skin set is bound
material_handle
submodel_material( TSubModel const *Submodel ) {
if( Submodel->m_material >= 0 ) {
return Submodel->m_material;
}
return (
TSubModel::ReplacableSkinId != nullptr ?
TSubModel::ReplacableSkinId[ -Submodel->m_material ] :
null_handle );
}
} // namespace
float const EU07_OPACITYDEFAULT { 0.5f };
bool
opengl_renderer::Init( GLFWwindow *Window ) {
if( false == Init_caps() ) { return false; }
m_window = Window;
::glPixelStorei( GL_UNPACK_ALIGNMENT, 1 );
::glPixelStorei( GL_PACK_ALIGNMENT, 1 );
glClearDepth( 1.0f );
glClearColor( 51.0f / 255.0f, 102.0f / 255.0f, 85.0f / 255.0f, 1.0f ); // initial background Color
glPolygonMode( GL_FRONT, GL_FILL );
glFrontFace( GL_CCW ); // Counter clock-wise polygons face out
glEnable( GL_CULL_FACE ); // Cull back-facing triangles
glShadeModel( GL_SMOOTH ); // Enable Smooth Shading
m_geometry.units().texture = (
Global.BasicRenderer ?
std::vector<GLint>{ m_diffusetextureunit } :
std::vector<GLint>{ m_normaltextureunit, m_diffusetextureunit } );
ui_layer::set_unit( m_diffusetextureunit );
select_unit( m_diffusetextureunit );
::glDepthFunc( GL_LEQUAL );
glEnable( GL_DEPTH_TEST );
glAlphaFunc( GL_GREATER, 0.f );
glEnable( GL_ALPHA_TEST );
glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
glEnable( GL_BLEND );
glEnable( GL_TEXTURE_2D ); // Enable Texture Mapping
glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST ); // Really Nice Perspective Calculations
glHint( GL_POLYGON_SMOOTH_HINT, GL_NICEST );
glHint( GL_LINE_SMOOTH_HINT, GL_NICEST );
glLineWidth( 1.0f );
glPointSize( 3.0f );
glEnable( GL_POINT_SMOOTH );
::glLightModeli( GL_LIGHT_MODEL_COLOR_CONTROL, GL_SEPARATE_SPECULAR_COLOR );
::glMaterialf( GL_FRONT, GL_SHININESS, 15.0f );
if( true == Global.ScaleSpecularValues ) {
m_specularopaquescalefactor = 0.25f;
m_speculartranslucentscalefactor = 1.5f;
}
::glEnable( GL_COLOR_MATERIAL );
::glColorMaterial( GL_FRONT, GL_AMBIENT_AND_DIFFUSE );
// setup lighting
::glLightModelfv( GL_LIGHT_MODEL_AMBIENT, glm::value_ptr(m_baseambient) );
::glEnable( GL_LIGHTING );
::glEnable( opengl_renderer::sunlight );
// rgb value for 5780 kelvin
Global.DayLight.diffuse[0] = 1.0f; // 255 / 255
Global.DayLight.diffuse[1] = 0.9490196f; // 242 / 255
Global.DayLight.diffuse[2] = 0.9058824f; // 231 / 255
Global.DayLight.is_directional = true;
m_sunlight.id = opengl_renderer::sunlight;
// ::glLightf( opengl_renderer::sunlight, GL_SPOT_CUTOFF, 90.0f );
// create dynamic light pool
for( int idx = 0; idx < Global.DynamicLightCount; ++idx ) {
opengl_light light;
light.id = GL_LIGHT1 + idx;
light.is_directional = false;
::glEnable( light.id ); // experimental intel chipset fix
::glLightf( light.id, GL_SPOT_CUTOFF, 7.5f );
::glLightf( light.id, GL_SPOT_EXPONENT, 7.5f );
::glLightf( light.id, GL_CONSTANT_ATTENUATION, 0.0f );
::glLightf( light.id, GL_LINEAR_ATTENUATION, 0.035f );
::glDisable( light.id ); // experimental intel chipset fix
m_lights.emplace_back( light );
}
// preload some common textures
m_glaretexture = Fetch_Texture( "fx/lightglare" );
m_suntexture = Fetch_Texture( "fx/sun" );
m_moontexture = Fetch_Texture( "fx/moon" );
if( m_helpertextureunit >= 0 ) {
m_reflectiontexture = Fetch_Texture( "fx/reflections" );
}
m_smoketexture = Fetch_Texture( "fx/smoke" );
m_invalid_material = Fetch_Material( "fx/invalid" );
#ifdef EU07_USE_PICKING_FRAMEBUFFER
// pick buffer resources
if( true == m_framebuffersupport ) {
// try to create the pick buffer. RGBA8 2D texture, 24 bit depth texture, 1024x1024 (height of 512 would suffice but, eh)
// texture
::glGenTextures( 1, &m_picktexture );
::glBindTexture( GL_TEXTURE_2D, m_picktexture );
::glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA8, EU07_PICKBUFFERSIZE, EU07_PICKBUFFERSIZE, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL );
::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
// depth buffer
::glGenRenderbuffersEXT( 1, &m_pickdepthbuffer );
::glBindRenderbufferEXT( GL_RENDERBUFFER_EXT, m_pickdepthbuffer );
::glRenderbufferStorageEXT( GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT24, EU07_PICKBUFFERSIZE, EU07_PICKBUFFERSIZE );
// create and assemble the framebuffer
::glGenFramebuffersEXT( 1, &m_pickframebuffer );
::glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, m_pickframebuffer );
::glFramebufferTexture2DEXT( GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, m_picktexture, 0 );
::glFramebufferRenderbufferEXT( GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, m_pickdepthbuffer );
// check if we got it working
GLenum status = ::glCheckFramebufferStatusEXT( GL_FRAMEBUFFER_EXT );
if( status == GL_FRAMEBUFFER_COMPLETE_EXT ) {
WriteLog( "Picking framebuffer setup complete" );
}
else{
ErrorLog( "Picking framebuffer setup failed" );
m_framebuffersupport = false;
}
::glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, 0 ); // switch back to primary render target for now
}
#endif
// shadowmap resources
if( ( true == Global.RenderShadows )
&& ( true == m_framebuffersupport ) ) {
// primary shadow map
{
// texture:
::glGenTextures( 1, &m_shadowtexture );
::glBindTexture( GL_TEXTURE_2D, m_shadowtexture );
// allocate memory
::glTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, m_shadowbuffersize, m_shadowbuffersize, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, NULL );
// setup parameters
::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
// enable shadow comparison: true (ie not in shadow) if r<=texture...
::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE );
::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL );
::glTexParameteri( GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE, GL_LUMINANCE );
::glBindTexture( GL_TEXTURE_2D, 0 );
#ifdef EU07_USE_DEBUG_SHADOWMAP
::glGenTextures( 1, &m_shadowdebugtexture );
::glBindTexture( GL_TEXTURE_2D, m_shadowdebugtexture );
// allocate memory
::glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA8, m_shadowbuffersize, m_shadowbuffersize, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL );
// setup parameters
::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
::glBindTexture( GL_TEXTURE_2D, 0 );
#endif
// create and assemble the framebuffer
::glGenFramebuffersEXT( 1, &m_shadowframebuffer );
::glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, m_shadowframebuffer );
#ifdef EU07_USE_DEBUG_SHADOWMAP
::glFramebufferTexture2DEXT( GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, m_shadowdebugtexture, 0 );
#else
::glDrawBuffer( GL_NONE ); // we won't be rendering colour data, so can skip the colour attachment
::glReadBuffer( GL_NONE );
#endif
::glFramebufferTexture2DEXT( GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, m_shadowtexture, 0 );
// check if we got it working
GLenum const status{ ::glCheckFramebufferStatusEXT( GL_FRAMEBUFFER_EXT ) };
if( status == GL_FRAMEBUFFER_COMPLETE_EXT ) {
WriteLog( "Shadows framebuffer setup complete" );
}
else {
ErrorLog( "Shadows framebuffer setup failed" );
Global.RenderShadows = false;
}
// switch back to primary render target for now
::glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, 0 );
}
// cab shadow map
{
// NOTE: using separate framebuffer as more efficient arrangement than swapping attachments on a single one
// TODO: for shader-based implementation use instead a quarter of the CSM
// texture:
::glGenTextures( 1, &m_cabshadowtexture );
::glBindTexture( GL_TEXTURE_2D, m_cabshadowtexture );
// allocate memory
::glTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, m_shadowbuffersize / 2, m_shadowbuffersize / 2, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, NULL );
// setup parameters
::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
// enable shadow comparison: true (ie not in shadow) if r<=texture...
::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE );
::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL );
::glTexParameteri( GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE, GL_LUMINANCE );
::glBindTexture( GL_TEXTURE_2D, 0 );
#ifdef EU07_USE_DEBUG_CABSHADOWMAP
::glGenTextures( 1, &m_cabshadowdebugtexture );
::glBindTexture( GL_TEXTURE_2D, m_cabshadowdebugtexture );
// allocate memory
::glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA8, m_shadowbuffersize / 2, m_shadowbuffersize / 2, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL );
// setup parameters
::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
::glBindTexture( GL_TEXTURE_2D, 0 );
#endif
// create and assemble the framebuffer
::glGenFramebuffersEXT( 1, &m_cabshadowframebuffer );
::glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, m_cabshadowframebuffer );
#ifdef EU07_USE_DEBUG_CABSHADOWMAP
::glFramebufferTexture2DEXT( GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, m_cabshadowdebugtexture, 0 );
#else
::glDrawBuffer( GL_NONE ); // we won't be rendering colour data, so can skip the colour attachment
::glReadBuffer( GL_NONE );
#endif
::glFramebufferTexture2DEXT( GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, m_cabshadowtexture, 0 );
// check if we got it working
GLenum const status{ ::glCheckFramebufferStatusEXT( GL_FRAMEBUFFER_EXT ) };
if( status == GL_FRAMEBUFFER_COMPLETE_EXT ) {
WriteLog( "Cab shadows framebuffer setup complete" );
}
else {
ErrorLog( "Cab shadows framebuffer setup failed" );
Global.RenderShadows = false;
}
}
// switch back to primary render target for now
::glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, 0 );
}
// environment cube map resources
if( ( false == Global.BasicRenderer )
&& ( true == m_framebuffersupport ) ) {
// texture:
::glGenTextures( 1, &m_environmentcubetexture );
::glBindTexture( GL_TEXTURE_CUBE_MAP, m_environmentcubetexture );
// allocate space
for( GLuint faceindex = GL_TEXTURE_CUBE_MAP_POSITIVE_X; faceindex < GL_TEXTURE_CUBE_MAP_POSITIVE_X + 6; ++faceindex ) {
::glTexImage2D( faceindex, 0, GL_RGBA8, EU07_ENVIRONMENTBUFFERSIZE, EU07_ENVIRONMENTBUFFERSIZE, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL );
}
// setup parameters
::glTexParameteri( GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
::glTexParameteri( GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
::glTexParameteri( GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
::glTexParameteri( GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
::glTexParameteri( GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE );
// depth buffer
::glGenRenderbuffersEXT( 1, &m_environmentdepthbuffer );
::glBindRenderbufferEXT( GL_RENDERBUFFER_EXT, m_environmentdepthbuffer );
::glRenderbufferStorageEXT( GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT24, EU07_ENVIRONMENTBUFFERSIZE, EU07_ENVIRONMENTBUFFERSIZE );
// create and assemble the framebuffer
::glGenFramebuffersEXT( 1, &m_environmentframebuffer );
::glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, m_environmentframebuffer );
::glFramebufferTexture2DEXT( GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_CUBE_MAP_POSITIVE_X, m_environmentcubetexture, 0 );
::glFramebufferRenderbufferEXT( GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, m_environmentdepthbuffer );
// check if we got it working
GLenum status = ::glCheckFramebufferStatusEXT( GL_FRAMEBUFFER_EXT );
if( status == GL_FRAMEBUFFER_COMPLETE_EXT ) {
WriteLog( "Reflections framebuffer setup complete" );
m_environmentcubetexturesupport = true;
}
else {
ErrorLog( "Reflections framebuffer setup failed" );
m_environmentcubetexturesupport = false;
}
::glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, 0 ); // switch back to primary render target for now
}
// prepare basic geometry chunks
auto const geometrybank = Create_Bank();
float const size = 2.5f;
gfx::vertex_array billboard_array{
{ { -size, size, 0.f }, glm::vec3(), { 1.f, 1.f } },
{ { size, size, 0.f }, glm::vec3(), { 0.f, 1.f } },
{ { -size, -size, 0.f }, glm::vec3(), { 1.f, 0.f } },
{ { size, -size, 0.f }, glm::vec3(), { 0.f, 0.f } } };
gfx::userdata_array userdata{};
m_billboardgeometry = m_geometry.create_chunk(billboard_array, userdata, geometrybank, GL_TRIANGLE_STRIP);
// prepare debug mode objects
//m_quadric = ::gluNewQuadric();
//::gluQuadricNormals( m_quadric, GLU_FLAT );
WriteLog( "Gfx Renderer: setup complete" );
return true;
}
bool
opengl_renderer::Render() {
Timer::subsystem.gfx_total.stop();
Timer::subsystem.gfx_total.start(); // note: gfx_total is actually frame total, clean this up
Timer::subsystem.gfx_color.start();
// fetch simulation data
if( simulation::is_ready ) {
m_sunlight = Global.DayLight;
// quantize sun angle to reduce shadow crawl
auto const quantizationstep { 0.004f };
m_sunlight.direction = glm::normalize( quantizationstep * glm::roundEven( m_sunlight.direction * ( 1.f / quantizationstep ) ) );
}
// generate new frame
opengl_texture::reset_unit_cache();
m_renderpass.draw_mode = rendermode::none; // force setup anew
m_renderpass.draw_stats = debug_stats();
m_geometry.primitives_count() = 0;
Render_pass( rendermode::color );
Timer::subsystem.gfx_color.stop();
// add user interface
setup_units( true, false, false );
::glPushClientAttrib( GL_CLIENT_VERTEX_ARRAY_BIT );
::glClientActiveTexture( GL_TEXTURE0 + m_diffusetextureunit );
// clear state for ui
::glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, 0 );
::glBindBuffer( GL_ARRAY_BUFFER, 0 );
Application.render_ui();
::glPopClientAttrib();
if( Global.bUseVBO ) {
// swapbuffers() will unbind current buffers so we prepare for it on our end
gfx::opengl_vbogeometrybank::reset();
}
++m_framestamp;
return true; // for now always succeed
}
void
opengl_renderer::SwapBuffers() {
Timer::subsystem.gfx_swap.start();
glfwSwapBuffers( m_window );
Timer::subsystem.gfx_swap.stop();
m_debugtimestext.clear();
m_debugtimestext =
"cpu frame total: " + to_string( Timer::subsystem.gfx_color.average() + Timer::subsystem.gfx_shadows.average() + Timer::subsystem.gfx_swap.average(), 2 ) + " ms\n"
+ " color: " + to_string( Timer::subsystem.gfx_color.average(), 2 ) + " ms (" + std::to_string( m_cellqueue.size() ) + " sectors)\n";
if( Global.RenderShadows ) {
m_debugtimestext +=
" shadows: " + to_string( Timer::subsystem.gfx_shadows.average(), 2 ) + " ms\n";
}
m_debugtimestext += " swap: " + to_string( Timer::subsystem.gfx_swap.average(), 2 ) + " ms\n";
m_debugtimestext += "uilayer: " + to_string( Timer::subsystem.gfx_gui.average(), 2 ) + " ms\n";
m_debugstatstext =
"triangles: " + to_string( static_cast<int>(m_geometry.primitives_count()), 7 ) + "\n"
+ "vehicles: " + to_string( m_colorpass.draw_stats.dynamics, 7 ) + " +" + to_string( m_shadowpass.draw_stats.dynamics, 7 )
+ " =" + to_string( m_colorpass.draw_stats.dynamics + m_shadowpass.draw_stats.dynamics, 7 ) + "\n"
+ "models: " + to_string( m_colorpass.draw_stats.models, 7 ) + " +" + to_string( m_shadowpass.draw_stats.models, 7 )
+ " =" + to_string( m_colorpass.draw_stats.models + m_shadowpass.draw_stats.models, 7 ) + "\n"
+ "drawcalls: " + to_string( m_colorpass.draw_stats.drawcalls, 7 ) + " +" + to_string( m_shadowpass.draw_stats.drawcalls, 7 )
+ " =" + to_string( m_colorpass.draw_stats.drawcalls + m_shadowpass.draw_stats.drawcalls, 7 ) + "\n"
+ " submodels:" + to_string( m_colorpass.draw_stats.submodels, 7 ) + " +" + to_string( m_shadowpass.draw_stats.submodels, 7 )
+ " =" + to_string( m_colorpass.draw_stats.submodels + m_shadowpass.draw_stats.submodels, 7 ) + "\n"
+ " paths: " + to_string( m_colorpass.draw_stats.paths, 7 ) + " +" + to_string( m_shadowpass.draw_stats.paths, 7 )
+ " =" + to_string( m_colorpass.draw_stats.paths + m_shadowpass.draw_stats.paths, 7 ) + "\n"
+ " shapes: " + to_string( m_colorpass.draw_stats.shapes, 7 ) + " +" + to_string( m_shadowpass.draw_stats.shapes, 7 )
+ " =" + to_string( m_colorpass.draw_stats.shapes + m_shadowpass.draw_stats.shapes, 7 ) + "\n"
+ " traction: " + to_string( m_colorpass.draw_stats.traction, 7 ) + "\n"
+ " lines: " + to_string( m_colorpass.draw_stats.lines, 7 ) + "\n"
+ "particles: " + to_string( m_colorpass.draw_stats.particles, 7 );
}
// runs jobs needed to generate graphics for specified render pass
void
opengl_renderer::Render_pass( rendermode const Mode ) {
#ifdef EU07_USE_DEBUG_CAMERA
// setup world camera for potential visualization
setup_pass(
m_worldcamera,
rendermode::color,
0.f,
1.0,
true );
#endif
setup_pass( m_renderpass, Mode );
switch( m_renderpass.draw_mode ) {
case rendermode::color: {
m_colorpass = m_renderpass;
if( ( !simulation::is_ready ) || ( Global.gfx_skiprendering ) ) {
::glClearColor( 51.0f / 255.f, 102.0f / 255.f, 85.0f / 255.f, 1.f ); // initial background Color
::glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
break;
}
if (Global.bWireFrame)
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
else
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
if( ( true == Global.RenderShadows )
&& ( false == Global.bWireFrame )
&& ( m_shadowcolor != colors::white ) ) {
// run shadowmaps pass before color
Timer::subsystem.gfx_shadows.start();
if( false == FreeFlyModeFlag ) {
Render_pass( rendermode::cabshadows );
}
Render_pass( rendermode::shadows );
Timer::subsystem.gfx_shadows.stop();
#ifdef EU07_USE_DEBUG_SHADOWMAP
UILayer.set_texture( m_shadowdebugtexture );
#endif
#ifdef EU07_USE_DEBUG_CABSHADOWMAP
UILayer.set_texture( m_cabshadowdebugtexture );
#endif
setup_pass( m_renderpass, Mode ); // restore draw mode. TBD, TODO: render mode stack
// setup shadowmap matrices
m_shadowtexturematrix =
glm::mat4 { 0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 0.5f, 0.0f, 0.5f, 0.5f, 0.5f, 1.0f } // bias from [-1, 1] to [0, 1]
* m_shadowpass.camera.projection()
// during colour pass coordinates are moved from camera-centric to light-centric, essentially the difference between these two origins
* glm::translate(
glm::mat4{ glm::mat3{ m_shadowpass.camera.modelview() } },
glm::vec3{ m_renderpass.camera.position() - m_shadowpass.camera.position() } );
if( false == FreeFlyModeFlag ) {
m_cabshadowtexturematrix =
glm::mat4{ 0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 0.5f, 0.0f, 0.5f, 0.5f, 0.5f, 1.0f } // bias from [-1, 1] to [0, 1]
* m_cabshadowpass.camera.projection()
// during colour pass coordinates are moved from camera-centric to light-centric, essentially the difference between these two origins
* glm::translate(
glm::mat4{ glm::mat3{ m_cabshadowpass.camera.modelview() } },
glm::vec3{ m_renderpass.camera.position() - m_cabshadowpass.camera.position() } );
}
}
if( true == m_environmentcubetexturesupport ) {
// potentially update environmental cube map
m_renderpass.draw_stats = {};
if( true == Render_reflections() ) {
setup_pass( m_renderpass, Mode ); // restore draw mode. TBD, TODO: render mode stack
}
}
::glViewport( 0, 0, Global.fb_size.x, Global.fb_size.y );
auto const skydomecolour = simulation::Environment.m_skydome.GetAverageColor();
::glClearColor( skydomecolour.x, skydomecolour.y, skydomecolour.z, 0.f ); // kolor nieba
::glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
// setup
setup_matrices();
// render
setup_drawing( true );
setup_units( true, false, false );
m_renderpass.draw_stats = {};
Render( &simulation::Environment );
// opaque parts...
setup_drawing( false );
setup_units( true, true, true );
#ifdef EU07_USE_DEBUG_CAMERA
if( DebugModeFlag ) {
// draw light frustum
::glLineWidth( 2.f );
::glColor4f( 1.f, 0.9f, 0.8f, 1.f );
::glDisable( GL_LIGHTING );
::glDisable( GL_TEXTURE_2D );
if( ( true == Global.RenderShadows ) && ( false == Global.bWireFrame ) ) {
m_shadowpass.camera.draw( m_renderpass.camera.position() - m_shadowpass.camera.position() );
}
if( DebugCameraFlag ) {
::glColor4f( 0.8f, 1.f, 0.9f, 1.f );
m_worldcamera.camera.draw( m_renderpass.camera.position() - m_worldcamera.camera.position() );
}
::glLineWidth( 1.f );
::glEnable( GL_LIGHTING );
::glEnable( GL_TEXTURE_2D );
}
#endif
// without rain/snow we can render the cab early to limit the overdraw
if( ( false == FreeFlyModeFlag )
&& ( Global.Overcast <= 1.f ) ) { // precipitation happens when overcast is in 1-2 range
#ifdef EU07_DISABLECABREFLECTIONS
switch_units( true, true, false );
#endif
setup_shadow_map( m_cabshadowtexture, m_cabshadowtexturematrix );
// cache shadow colour in case we need to account for cab light
auto const shadowcolor { m_shadowcolor };
auto const *vehicle{ simulation::Train->Dynamic() };
if( vehicle->InteriorLightLevel > 0.f ) {
setup_shadow_color( glm::min( colors::white, shadowcolor + glm::vec4( vehicle->InteriorLight * vehicle->InteriorLightLevel, 1.f ) ) );
}
Render_cab( vehicle, vehicle->InteriorLightLevel, false );
if( vehicle->InteriorLightLevel > 0.f ) {
setup_shadow_color( shadowcolor );
}
}
switch_units( true, true, true );
setup_shadow_map( m_shadowtexture, m_shadowtexturematrix );
Render( simulation::Region );
// ...translucent parts
setup_drawing( true );
Render_Alpha( simulation::Region );
// particles
Render_particles();
// precipitation; done at the end, only before cab render
Render_precipitation();
// cab render
if( false == FreeFlyModeFlag ) {
#ifdef EU07_DISABLECABREFLECTIONS
switch_units( true, true, false );
#endif
setup_shadow_map( m_cabshadowtexture, m_cabshadowtexturematrix );
// cache shadow colour in case we need to account for cab light
auto const shadowcolor{ m_shadowcolor };
auto *vehicle { simulation::Train->Dynamic() };
if( vehicle->InteriorLightLevel > 0.f ) {
setup_shadow_color( glm::min( colors::white, shadowcolor + glm::vec4( vehicle->InteriorLight * vehicle->InteriorLightLevel, 1.f ) ) );
}
if( Global.Overcast > 1.f ) {
// with active precipitation draw the opaque cab parts here to mask rain/snow placed 'inside' the cab
setup_drawing( false );
Render_cab( vehicle, vehicle->InteriorLightLevel, false );
Render_interior( false);
setup_drawing( true );
Render_interior( true );
}
Render_cab( vehicle, vehicle->InteriorLightLevel, true );
if( vehicle->InteriorLightLevel > 0.f ) {
setup_shadow_color( shadowcolor );
}
// with the cab in place we can (finally) safely draw translucent part of the occupied vehicle
Render_Alpha( vehicle );
}
if( m_environmentcubetexturesupport ) {
// restore default texture matrix for reflections cube map
select_unit( m_helpertextureunit );
::glMatrixMode( GL_TEXTURE );
// ::glPopMatrix();
::glLoadIdentity();
select_unit( m_diffusetextureunit );
::glMatrixMode( GL_MODELVIEW );
}
// store draw stats
m_colorpass.draw_stats = m_renderpass.draw_stats;
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
break;
}
case rendermode::shadows: {
if( simulation::is_ready ) {
// setup
::glEnable( GL_POLYGON_OFFSET_FILL ); // alleviate depth-fighting
::glPolygonOffset( 1.f, 1.f );
// main shadowmap
::glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, m_shadowframebuffer );
::glViewport( 0, 0, m_shadowbuffersize, m_shadowbuffersize );
#ifdef EU07_USE_DEBUG_SHADOWMAP
::glClearColor( 0.f / 255.f, 0.f / 255.f, 0.f / 255.f, 1.f ); // initial background Color
::glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
#else
::glClear( GL_DEPTH_BUFFER_BIT );
#endif
::glScissor( 1, 1, m_shadowbuffersize - 2, m_shadowbuffersize - 2 );
::glEnable( GL_SCISSOR_TEST );
setup_matrices();
// render
// opaque parts...
setup_drawing( false );
#ifdef EU07_USE_DEBUG_SHADOWMAP
setup_units( true, false, false );
#else
setup_units( false, false, false );
#endif
Render( simulation::Region );
m_shadowpass = m_renderpass;
// post-render restore
::glDisable( GL_SCISSOR_TEST );
::glDisable( GL_POLYGON_OFFSET_FILL );
::glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, 0 ); // switch back to primary render target
}
break;
}
case rendermode::cabshadows: {
if( ( simulation::is_ready )
&& ( false == FreeFlyModeFlag ) ) {
// setup
::glEnable( GL_POLYGON_OFFSET_FILL ); // alleviate depth-fighting
::glPolygonOffset( 1.f, 1.f );
::glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, m_cabshadowframebuffer );
::glViewport( 0, 0, m_shadowbuffersize / 2, m_shadowbuffersize / 2 );
#ifdef EU07_USE_DEBUG_CABSHADOWMAP
::glClearColor( 0.f / 255.f, 0.f / 255.f, 0.f / 255.f, 1.f ); // initial background Color
::glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
#else
::glClear( GL_DEPTH_BUFFER_BIT );
#endif
::glScissor( 1, 1, m_shadowbuffersize / 2 - 2, m_shadowbuffersize / 2 - 2 );
::glEnable( GL_SCISSOR_TEST );
setup_matrices();
// render
// opaque parts...
setup_drawing( false );
#ifdef EU07_USE_DEBUG_CABSHADOWMAP
setup_units( true, false, false );
#else
setup_units( false, false, false );
#endif
::glDisable( GL_CULL_FACE );
if( Global.RenderCabShadowsRange > 0 ) {
Render( simulation::Region );
}
Render_cab( simulation::Train->Dynamic(), 0.f, false );
Render_cab( simulation::Train->Dynamic(), 0.f, true );
m_cabshadowpass = m_renderpass;
// post-render restore
::glDisable( GL_SCISSOR_TEST );
::glEnable( GL_CULL_FACE );
::glDisable( GL_POLYGON_OFFSET_FILL );
::glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, 0 ); // switch back to primary render target
}
break;
}
case rendermode::reflections: {
// NOTE: buffer and viewport setup in this mode is handled by the wrapper method
::glClearColor( 0.f, 0.f, 0.f, 1.f );
::glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
if( simulation::is_ready ) {
::glColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE );
// setup
setup_matrices();
// render
setup_drawing( true );
setup_units( true, false, false );
Render( &simulation::Environment );
// opaque parts...
setup_drawing( false );
setup_units( true, true, true );
setup_shadow_map( m_shadowtexture, m_shadowtexturematrix );
Render( simulation::Region );
/*
// reflections are limited to sky and ground only, the update rate is too low for anything else
// ...translucent parts
setup_drawing( true );
Render_Alpha( &World.Ground );
// cab render is performed without shadows, due to low resolution and number of models without windows :|
switch_units( true, false, false );
// cab render is done in translucent phase to deal with badly configured vehicles
if( World.Train != nullptr ) { Render_cab( World.Train->Dynamic() ); }
*/
::glColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE );
}
break;
}
case rendermode::pickcontrols: {
if( simulation::is_ready ) {
// setup
#ifdef EU07_USE_PICKING_FRAMEBUFFER
::glViewport( 0, 0, EU07_PICKBUFFERSIZE, EU07_PICKBUFFERSIZE );
#endif
::glClearColor( 0.f, 0.f, 0.f, 1.f );
::glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
m_pickcontrolsitems.clear();
setup_matrices();
// render
// opaque parts...
setup_drawing( false );
setup_units( false, false, false );
// cab render skips translucent parts, so we can do it here
if( simulation::Train != nullptr ) {
Render_cab( simulation::Train->Dynamic(), 0.f );
Render( simulation::Train->Dynamic() );
}
// post-render cleanup
}
break;
}
case rendermode::pickscenery: {
if( simulation::is_ready ) {
// setup
m_picksceneryitems.clear();
#ifdef EU07_USE_PICKING_FRAMEBUFFER
::glViewport( 0, 0, EU07_PICKBUFFERSIZE, EU07_PICKBUFFERSIZE );
#endif
::glClearColor( 0.f, 0.f, 0.f, 1.f );
::glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
setup_matrices();
// render
// opaque parts...
setup_drawing( false );
setup_units( false, false, false );
Render( simulation::Region );
// post-render cleanup
}
break;
}
default: {
break;
}
}
}
bool opengl_renderer::Render_interior( bool const Alpha ) {
// NOTE: legacy renderer doesn't need to repaint translucent parts
// TODO: investigate reasons (depth writing?) and duplicate in opengl33 branch
if( Alpha ) { return false; }
// TODO: early frustum based cull, camera might be pointing elsewhere
std::vector< std::pair<float, TDynamicObject *> > dynamics;
auto *dynamic { simulation::Train->Dynamic() };
// draw interiors of the occupied vehicle, and all vehicles behind it with permanent coupling, in case they're open-ended
while( dynamic != nullptr ) {
glm::dvec3 const originoffset { dynamic->vPosition - m_renderpass.camera.position() };
float const squaredistance{ glm::length2( glm::vec3{ originoffset } / Global.ZoomFactor ) };
dynamics.emplace_back( squaredistance, dynamic );
dynamic = dynamic->Next( coupling::permanent );
}
// draw also interiors of permanently coupled vehicles in front, if there's any
dynamic = simulation::Train->Dynamic()->Prev( coupling::permanent );
while( dynamic != nullptr ) {
glm::dvec3 const originoffset { dynamic->vPosition - m_renderpass.camera.position() };
float const squaredistance{ glm::length2( glm::vec3{ originoffset } / Global.ZoomFactor ) };
dynamics.emplace_back( squaredistance, dynamic );
dynamic = dynamic->Prev( coupling::permanent );
}
if( Alpha ) {
std::sort(
std::begin( dynamics ), std::end( dynamics ),
[]( std::pair<float, TDynamicObject *> const &Left, std::pair<float, TDynamicObject *> const &Right ) {
return ( Left.first ) > ( Right.first ); } );
}
for( auto &dynamic : dynamics ) {
Render_lowpoly( dynamic.second, dynamic.first, true, Alpha );
}
return true;
}
bool opengl_renderer::Render_lowpoly( TDynamicObject *Dynamic, float const Squaredistance, bool const Setup, bool const Alpha ) {
if( Dynamic->mdLowPolyInt == nullptr ) { return false; }
// low poly interior
glm::dvec3 const originoffset { Dynamic->vPosition - m_renderpass.camera.position() };
if( Setup ) {
TSubModel::iInstance = reinterpret_cast<std::uintptr_t>( Dynamic ); //żeby nie robić cudzych animacji
TSubModel::iVariantSeed = Dynamic->variant_seed();
Dynamic->ABuLittleUpdate( Squaredistance ); // ustawianie zmiennych submodeli dla wspólnego modelu
::glPushMatrix();
::glTranslated( originoffset.x, originoffset.y, originoffset.z );
::glMultMatrixd( glm::value_ptr(Dynamic->mMatrix) );
m_renderspecular = true; // vehicles are rendered with specular component. static models without, at least for the time being
}
// HACK: reduce light level for vehicle interior if there's strong global lighting source
if( false == Alpha ) {
auto const luminance{ static_cast<float>(
0.5
* ( std::max(
0.3,
( Global.fLuminance - Global.Overcast )
* ( Dynamic->fShade > 0.0 ?
Dynamic->fShade :
1.0 ) ) ) ) };
m_sunlight.apply_intensity(
std::clamp( (
Dynamic->fShade > 0.f ?
Dynamic->fShade :
1.f )
- luminance,
0.f, 1.f ) );
Render( Dynamic->mdLowPolyInt, Dynamic->Material(), Squaredistance );
// HACK: if the model has low poly interior, we presume the load is placed inside and also affected by reduced light level
if( Dynamic->mdLoad ) {
// renderowanie nieprzezroczystego ładunku
Render( Dynamic->mdLoad, Dynamic->Material(), Squaredistance, { 0.f, Dynamic->LoadOffset, 0.f }, {} );
}
m_sunlight.apply_intensity( Dynamic->fShade > 0.f ? Dynamic->fShade : 1.f );
}
else {
// Render_Alpha( Dynamic->mdLowPolyInt, Dynamic->Material(), Squaredistance );
// HACK: some models have windows included as part of the main model instead of lowpoly
if( Dynamic->mdModel ) {
// main model
Render_Alpha( Dynamic->mdModel, Dynamic->Material(), Squaredistance );
}
}
if( Setup ) {
m_renderspecular = false;
if( Dynamic->fShade > 0.0f ) {
// restore regular light level
m_sunlight.apply_intensity();
}
::glPopMatrix();
// TODO: check if this reset is needed. In theory each object should render all parts based on its own instance data anyway?
if( Dynamic->btnOn ) {
Dynamic->TurnOff(); // przywrócenie domyślnych pozycji submodeli
}
}
return true;
}
bool opengl_renderer::Render_coupler_adapter( TDynamicObject *Dynamic, float const Squaredistance, int const End, bool const Alpha ) {
if( Dynamic->m_coupleradapters[ End ] == nullptr ) { return false; }
auto const position { glm::dvec3 {
0.f,
Dynamic->MoverParameters->Couplers[ End ].adapter_height,
( Dynamic->MoverParameters->Couplers[ End ].adapter_length + Dynamic->MoverParameters->Dim.L * 0.5 ) * ( End == end::front ? 1 : -1 ) } };
auto const angle{ glm::vec3{ 0, ( End == end::front ? 0 : 180 ), 0 } };
if( Alpha ) {
Render_Alpha( Dynamic->m_coupleradapters[ End ], Dynamic->Material(), Squaredistance, position, angle );
}
else {
Render( Dynamic->m_coupleradapters[ End ], Dynamic->Material(), Squaredistance, position, angle );
}
return true;
}
// creates dynamic environment cubemap
bool
opengl_renderer::Render_reflections() {
if( Global.reflectiontune.update_interval == 0 ) { return false; }
auto const timestamp { Timer::GetRenderTime() };
if( ( timestamp - m_environmentupdatetime < Global.reflectiontune.update_interval )
&& ( glm::length2( m_renderpass.camera.position() - m_environmentupdatelocation ) < sq(1000.0)) ) {
// run update every 5+ mins of simulation time, or at least 1km from the last location
return false;
}
m_environmentupdatetime = timestamp;
m_environmentupdatelocation = m_renderpass.camera.position();
::glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, m_environmentframebuffer );
::glViewport( 0, 0, EU07_ENVIRONMENTBUFFERSIZE, EU07_ENVIRONMENTBUFFERSIZE );
for( m_environmentcubetextureface = GL_TEXTURE_CUBE_MAP_POSITIVE_X;
m_environmentcubetextureface < GL_TEXTURE_CUBE_MAP_POSITIVE_X + 6;
++m_environmentcubetextureface ) {
::glFramebufferTexture2DEXT( GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, m_environmentcubetextureface, m_environmentcubetexture, 0 );
Render_pass( rendermode::reflections );
}
::glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, 0 );
return true;
}
void
opengl_renderer::setup_pass( renderpass_config &Config, rendermode const Mode, float const Znear, float const Zfar, bool const Ignoredebug ) {
Config.draw_mode = Mode;
if( false == simulation::is_ready ) { return; }
// setup draw range
switch( Mode ) {
case rendermode::color: { Config.draw_range = Global.BaseDrawRange * Global.fDistanceFactor; break; }
case rendermode::shadows: { Config.draw_range = Global.shadowtune.range; break; }
case rendermode::cabshadows: { Config.draw_range = ( Global.RenderCabShadowsRange > 0 ? std::clamp( Global.RenderCabShadowsRange, 5, 100 ) : simulation::Train->Occupied()->Dim.L ); break; }
case rendermode::reflections: { Config.draw_range = Global.BaseDrawRange; break; }
case rendermode::pickcontrols: { Config.draw_range = 50.f; break; }
case rendermode::pickscenery: { Config.draw_range = Global.BaseDrawRange * 0.5f; break; }
default: { Config.draw_range = 0.f; break; }
}
// setup camera
auto &camera = Config.camera;
camera.projection() = glm::mat4( 1.f );
glm::dmat4 viewmatrix( 1.0 );
switch( Mode ) {
case rendermode::color: {
// modelview
if( ( false == DebugCameraFlag ) || ( true == Ignoredebug ) ) {
camera.position() = Global.pCamera.Pos;
Global.pCamera.SetMatrix( viewmatrix );
}
else {
camera.position() = Global.pDebugCamera.Pos;
Global.pDebugCamera.SetMatrix( viewmatrix );
}
// optimization: don't bother drawing things completely blended with the background
// but ensure at least 2 km range to account for signals and lights
Config.draw_range = std::min( Config.draw_range, m_fogrange * 2 );
Config.draw_range = std::max( 2000.f, Config.draw_range );
// projection
auto const zfar = ( Zfar > 1.f ? Zfar : Config.draw_range * Zfar );
auto const znear = ( Znear > 1.f ? Znear : Znear > 0.f ? Znear * zfar : 0.1f * Global.ZoomFactor);
camera.projection() *=
glm::perspective(
glm::radians( Global.FieldOfView / Global.ZoomFactor ),
std::max( 1.f, (float)Global.window_size.x ) / std::max( 1.f, (float)Global.window_size.y ),
znear,
zfar );
/*
m_sunandviewangle =
glm::dot(
m_sunlight.direction,
glm::vec3( 0.f, 0.f, -1.f ) * glm::mat3( viewmatrix ) );
*/
break;
}
case rendermode::shadows: {
// calculate lightview boundaries based on relevant area of the world camera frustum:
// ...setup chunk of frustum we're interested in...
// auto const zfar = std::min( 1.f, Global.shadowtune.range / ( Global.BaseDrawRange * Global.fDistanceFactor ) * std::max( 1.f, Global.ZoomFactor * 0.5f ) );
// auto const zfar = Global.shadowtune.range * std::max( Zfar, Global.ZoomFactor * 0.5f );
auto const zfar = ( Zfar > 1.f ? Zfar : Zfar * Global.shadowtune.range * std::max( Zfar, Global.ZoomFactor * 0.5f ) );
renderpass_config worldview;
setup_pass( worldview, rendermode::color, Znear, zfar, true );
auto &frustumchunkshapepoints = worldview.camera.frustum_points();
// ...modelview matrix: determine the centre of frustum chunk in world space...
glm::vec3 frustumchunkmin, frustumchunkmax;
bounding_box( frustumchunkmin, frustumchunkmax, std::begin( frustumchunkshapepoints ), std::end( frustumchunkshapepoints ) );
auto const frustumchunkcentre = ( frustumchunkmin + frustumchunkmax ) * 0.5f;
// ...cap the vertical angle to keep shadows from getting too long...
auto const lightvector =
glm::normalize( glm::vec3{
m_sunlight.direction.x,
std::min( m_sunlight.direction.y, Global.gfx_shadow_angle_min ),
m_sunlight.direction.z } );
// ...place the light source at the calculated centre and setup world space light view matrix...
camera.position() = worldview.camera.position() + glm::dvec3{ frustumchunkcentre };
viewmatrix *= glm::lookAt(
camera.position(),
camera.position() + glm::dvec3{ lightvector },
glm::dvec3{ 0.f, 1.f, 0.f } );
// ...projection matrix: calculate boundaries of the frustum chunk in light space...
auto const lightviewmatrix =
glm::translate(
glm::mat4{ glm::mat3{ viewmatrix } },
-frustumchunkcentre );
for( auto &point : frustumchunkshapepoints ) {
point = lightviewmatrix * point;
}
bounding_box( frustumchunkmin, frustumchunkmax, std::begin( frustumchunkshapepoints ), std::end( frustumchunkshapepoints ) );
// quantize the frustum points and add some padding, to reduce shadow shimmer on scale changes
auto const frustumchunkdepth { zfar - ( Znear > 1.f ? Znear : 0.f ) };
auto const quantizationstep{std::min(frustumchunkdepth, 50.f)};
frustumchunkmin = quantizationstep * glm::floor( frustumchunkmin * ( 1.f / quantizationstep ) );
frustumchunkmax = quantizationstep * glm::ceil( frustumchunkmax * ( 1.f / quantizationstep ) );
// ...use the dimensions to set up light projection boundaries...
// NOTE: since we only have one cascade map stage, we extend the chunk forward/back to catch areas normally covered by other stages
camera.projection() *=
glm::ortho(
frustumchunkmin.x, frustumchunkmax.x,
frustumchunkmin.y, frustumchunkmax.y,
frustumchunkmin.z - 500.f, frustumchunkmax.z + 500.f );
// ... and adjust the projection to sample complete shadow map texels:
// get coordinates for a sample texel...
auto shadowmaptexel = glm::vec2 { camera.projection() * glm::mat4{ viewmatrix } * glm::vec4{ 0.f, 0.f, 0.f, 1.f } };
// ...convert result from clip space to texture coordinates, and calculate adjustment...
shadowmaptexel *= m_shadowbuffersize * 0.5f;
auto shadowmapadjustment = glm::round( shadowmaptexel ) - shadowmaptexel;
// ...transform coordinate change back to homogenous light space...
shadowmapadjustment /= m_shadowbuffersize * 0.5f;
// ... and bake the adjustment into the projection matrix
camera.projection() =
glm::translate(
glm::mat4{ 1.f },
glm::vec3{ shadowmapadjustment, 0.f } )
* camera.projection();
break;
}
case rendermode::cabshadows: {
// fixed size cube large enough to enclose a vehicle compartment
// modelview
auto const lightvector =
glm::normalize( glm::vec3{
m_sunlight.direction.x,
std::min( m_sunlight.direction.y, Global.gfx_shadow_angle_min ),
m_sunlight.direction.z } );
camera.position() = Global.pCamera.Pos - glm::dvec3 { lightvector };
viewmatrix *= glm::lookAt(
camera.position(),
Global.pCamera.Pos,
glm::dvec3 { 0, 1, 0 } );
// projection
auto const maphalfsize { std::min( 10.f, Config.draw_range * 0.5f ) };
camera.projection() *=
glm::ortho(
-maphalfsize, maphalfsize,
-maphalfsize, maphalfsize,
-Config.draw_range, Config.draw_range );
/*
// adjust the projection to sample complete shadow map texels
auto shadowmaptexel = glm::vec2 { camera.projection() * glm::mat4{ viewmatrix } * glm::vec4{ 0.f, 0.f, 0.f, 1.f } };
shadowmaptexel *= ( m_shadowbuffersize / 2 ) * 0.5f;
auto shadowmapadjustment = glm::round( shadowmaptexel ) - shadowmaptexel;
shadowmapadjustment /= ( m_shadowbuffersize / 2 ) * 0.5f;
camera.projection() = glm::translate( glm::mat4{ 1.f }, glm::vec3{ shadowmapadjustment, 0.f } ) * camera.projection();
*/
break;
}
case rendermode::reflections: {
// modelview
camera.position() = (
( ( true == DebugCameraFlag ) && ( false == Ignoredebug ) ) ?
Global.pDebugCamera.Pos :
Global.pCamera.Pos );
glm::dvec3 const cubefacetargetvectors[ 6 ] = { { 1.0, 0.0, 0.0 }, { -1.0, 0.0, 0.0 }, { 0.0, 1.0, 0.0 }, { 0.0, -1.0, 0.0 }, { 0.0, 0.0, 1.0 }, { 0.0, 0.0, -1.0 } };
glm::dvec3 const cubefaceupvectors[ 6 ] = { { 0.0, -1.0, 0.0 }, { 0.0, -1.0, 0.0 }, { 0.0, 0.0, 1.0 }, { 0.0, 0.0, -1.0 }, { 0.0, -1.0, 0.0 }, { 0.0, -1.0, 0.0 } };
auto const cubefaceindex = m_environmentcubetextureface - GL_TEXTURE_CUBE_MAP_POSITIVE_X;
viewmatrix *=
glm::lookAt(
camera.position(),
camera.position() + cubefacetargetvectors[ cubefaceindex ],
cubefaceupvectors[ cubefaceindex ] );
// projection
camera.projection() *=
glm::perspective(
glm::radians( 90.f ),
1.f,
0.1f * Global.ZoomFactor,
Config.draw_range * Global.fDistanceFactor );
break;
}
case rendermode::pickcontrols:
case rendermode::pickscenery: {
// TODO: scissor test for pick modes
// modelview
camera.position() = Global.pCamera.Pos;
Global.pCamera.SetMatrix( viewmatrix );
// projection
camera.projection() *=
glm::perspective(
glm::radians( Global.FieldOfView / Global.ZoomFactor ),
std::max( 1.f, (float)Global.window_size.x ) / std::max( 1.f, (float)Global.window_size.y ),
0.1f * Global.ZoomFactor,
Config.draw_range * Global.fDistanceFactor );
break;
}
default: {
break;
}
}
camera.modelview() = viewmatrix;
camera.update_frustum();
}
void
opengl_renderer::setup_matrices() {
::glMatrixMode( GL_PROJECTION );
OpenGLMatrices.load_matrix( m_renderpass.camera.projection() );
if( ( m_renderpass.draw_mode == rendermode::color )
&& ( m_environmentcubetexturesupport ) ) {
// special case, for colour render pass setup texture matrix for reflections cube map
select_unit( m_helpertextureunit );
::glMatrixMode( GL_TEXTURE );
// ::glPushMatrix();
::glLoadIdentity();
::glMultMatrixf( glm::value_ptr( glm::inverse( glm::mat4{ glm::mat3{ m_renderpass.camera.modelview() } } ) ) );
select_unit( m_diffusetextureunit );
}
// trim modelview matrix just to rotation, since rendering is done in camera-centric world space
::glMatrixMode( GL_MODELVIEW );
OpenGLMatrices.load_matrix( glm::mat4( glm::mat3( m_renderpass.camera.modelview() ) ) );
}
void
opengl_renderer::setup_drawing( bool const Alpha ) {
if( true == Alpha ) {
::glEnable( GL_BLEND );
::glAlphaFunc( GL_GREATER, 0.f );
}
else {
::glDisable( GL_BLEND );
::glAlphaFunc( GL_GREATER, EU07_OPACITYDEFAULT );
}
switch( m_renderpass.draw_mode ) {
case rendermode::color:
case rendermode::reflections: {
::glEnable( GL_LIGHTING );
::glShadeModel( GL_SMOOTH );
if( Global.iMultisampling ) {
::glEnable( GL_MULTISAMPLE );
}
// setup fog
if( Global.fFogEnd > 0 ) {
// fog setup
m_fogrange = Global.fFogEnd / std::max( 1.f, Global.Overcast * 2.f );
::glFogfv( GL_FOG_COLOR, glm::value_ptr( Global.FogColor ) );
::glFogf( GL_FOG_DENSITY, static_cast<GLfloat>( 1.0 / m_fogrange ) );
::glEnable( GL_FOG );
}
else { ::glDisable( GL_FOG ); }
break;
}
case rendermode::pickcontrols:
case rendermode::pickscenery:
::glAlphaFunc( GL_ALWAYS, 0.0f );
[[ fallthrough ]];
case rendermode::shadows:
case rendermode::cabshadows: {
::glColor4fv( glm::value_ptr( colors::white ) );
::glDisable( GL_LIGHTING );
::glShadeModel( GL_FLAT );
if( Global.iMultisampling ) {
::glDisable( GL_MULTISAMPLE );
}
::glDisable( GL_FOG );
break;
}
default: {
break;
}
}
}
// configures, enables and disables specified texture units
void
opengl_renderer::setup_units( bool const Diffuse, bool const Shadows, bool const Reflections ) {
// helper texture unit.
// darkens previous stage, preparing data for the shadow texture unit to select from
if( m_helpertextureunit >= 0 ) {
select_unit( m_helpertextureunit );
if( ( true == Reflections )
|| ( ( true == Global.RenderShadows ) && ( true == Shadows ) && ( false == Global.bWireFrame ) ) ) {
// we need to have texture on the helper for either the reflection and shadow generation (or both)
if( true == m_environmentcubetexturesupport ) {
// bind dynamic environment cube if it's enabled...
// NOTE: environment cube map isn't part of regular texture system, so we use direct bind call here
::glBindTexture( GL_TEXTURE_CUBE_MAP, m_environmentcubetexture );
::glEnable( GL_TEXTURE_CUBE_MAP );
}
else {
// ...otherwise fallback on static spherical image
m_textures.bind( textureunit::helper, m_reflectiontexture );
::glEnable( GL_TEXTURE_2D );
}
}
else {
if( true == m_environmentcubetexturesupport ) {
::glDisable( GL_TEXTURE_CUBE_MAP );
}
else {
::glDisable( GL_TEXTURE_2D );
}
}
if( ( true == Global.RenderShadows ) && ( true == Shadows ) && ( false == Global.bWireFrame ) ) {
::glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE );
::glTexEnvfv( GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, glm::value_ptr( m_shadowcolor ) );
::glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE ); // darken the previous stage
::glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_PREVIOUS );
::glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR );
::glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_CONSTANT );
::glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR );
::glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE ); // pass the previous stage alpha down the chain
::glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_PREVIOUS );
}
else {
::glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE );
::glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_REPLACE ); // pass the previous stage colour down the chain
::glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_PREVIOUS );
::glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE ); // pass the previous stage alpha down the chain
::glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_PREVIOUS );
}
if( true == Reflections ) {
if( true == m_environmentcubetexturesupport ) {
::glTexGeni( GL_S, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP );
::glTexGeni( GL_T, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP );
::glTexGeni( GL_R, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP );
::glEnable( GL_TEXTURE_GEN_S );
::glEnable( GL_TEXTURE_GEN_T );
::glEnable( GL_TEXTURE_GEN_R );
}
else {
// fixed texture fall back
::glTexGeni( GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP );
::glTexGeni( GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP );
::glEnable( GL_TEXTURE_GEN_S );
::glEnable( GL_TEXTURE_GEN_T );
}
}
else {
if( true == m_environmentcubetexturesupport ) {
::glDisable( GL_TEXTURE_GEN_S );
::glDisable( GL_TEXTURE_GEN_T );
::glDisable( GL_TEXTURE_GEN_R );
}
else {
::glDisable( GL_TEXTURE_GEN_S );
::glDisable( GL_TEXTURE_GEN_T );
}
}
}
// shadow texture unit.
// interpolates between primary colour and the previous unit, which should hold darkened variant of the primary colour
if( m_shadowtextureunit >= 0 ) {
if( ( true == Global.RenderShadows )
&& ( true == Shadows )
&& ( false == Global.bWireFrame )
&& ( m_shadowcolor != colors::white ) ) {
select_unit( m_shadowtextureunit );
// NOTE: texture and transformation matrix setup are done through separate call
::glEnable( GL_TEXTURE_2D );
// s
::glEnable( GL_TEXTURE_GEN_S );
// t
::glEnable( GL_TEXTURE_GEN_T );
// r
::glEnable( GL_TEXTURE_GEN_R );
// q
::glEnable( GL_TEXTURE_GEN_Q );
::glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE );
::glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_INTERPOLATE ); // choose between lit and lit * shadow colour, based on depth test
::glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_PRIMARY_COLOR );
::glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR );
::glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_PREVIOUS );
::glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR );
::glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE2_RGB, GL_TEXTURE );
::glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND2_RGB, GL_SRC_COLOR );
::glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE ); // pass the previous stage alpha down the chain
::glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_PREVIOUS );
}
else {
// turn off shadow map tests
select_unit( m_shadowtextureunit );
::glDisable( GL_TEXTURE_2D );
::glDisable( GL_TEXTURE_GEN_S );
::glDisable( GL_TEXTURE_GEN_T );
::glDisable( GL_TEXTURE_GEN_R );
::glDisable( GL_TEXTURE_GEN_Q );
}
}
// reflections/normals texture unit
// NOTE: comes after diffuse stage in the operation chain
if( m_normaltextureunit >= 0 ) {
select_unit( m_normaltextureunit );
if( true == Reflections ) {
::glEnable( GL_TEXTURE_2D );
::glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE );
::glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_INTERPOLATE ); // blend between object colour and env.reflection
::glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_TEXTURE0 );
::glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR );
::glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_PREVIOUS );
::glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR );
::glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE2_RGB, GL_TEXTURE ); // alpha channel of the normal map controls reflection strength
::glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND2_RGB, GL_SRC_ALPHA );
::glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE ); // pass the previous stage alpha down the chain
::glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_PREVIOUS );
}
else {
::glDisable( GL_TEXTURE_2D );
}
}
// diffuse texture unit.
// NOTE: diffuse texture mapping is never fully disabled, alpha channel information is always included
select_unit( m_diffusetextureunit );
::glEnable( GL_TEXTURE_2D );
if( true == Diffuse ) {
// default behaviour, modulate with previous stage
::glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE );
::glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE );
::glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_TEXTURE );
::glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR );
/*
::glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_MODULATE );
::glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_TEXTURE );
::glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA );
*/
}
else {
// solid colour with texture alpha
::glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE );
::glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_REPLACE );
::glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_PREVIOUS );
::glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR );
/*
::glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE );
::glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_TEXTURE );
::glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA );
*/
}
// update unit state
m_unitstate.diffuse = Diffuse;
m_unitstate.shadows = Shadows;
m_unitstate.reflections = Reflections;
}
// configures shadow texture unit for specified shadow map and conersion matrix
void
opengl_renderer::setup_shadow_map( GLuint const Texture, glm::mat4 const &Transformation ) {
if( ( m_shadowtextureunit == -1 )
|| ( false == Global.RenderShadows )
|| ( true == Global.bWireFrame )
|| ( m_shadowcolor == colors::white ) ) {
// shadows are off
return;
}
select_unit( m_shadowtextureunit );
// NOTE: shadowmap isn't part of regular texture system, so we use direct bind call here
::glBindTexture( GL_TEXTURE_2D, Texture );
// s
::glTexGenfv( GL_S, GL_EYE_PLANE, glm::value_ptr( glm::row( Transformation, 0 ) ) );
// t
::glTexGenfv( GL_T, GL_EYE_PLANE, glm::value_ptr( glm::row( Transformation, 1 ) ) );
// r
::glTexGenfv( GL_R, GL_EYE_PLANE, glm::value_ptr( glm::row( Transformation, 2 ) ) );
// q
::glTexGenfv( GL_Q, GL_EYE_PLANE, glm::value_ptr( glm::row( Transformation, 3 ) ) );
select_unit( m_diffusetextureunit );
}
// enables and disables specified texture units
void
opengl_renderer::switch_units( bool const Diffuse, bool const Shadows, bool const Reflections ) {
// helper texture unit.
if( m_helpertextureunit >= 0 ) {
select_unit( m_helpertextureunit );
if( ( true == Reflections )
|| ( ( true == Global.RenderShadows )
&& ( true == Shadows )
&& ( false == Global.bWireFrame )
&& ( m_shadowcolor != colors::white ) ) ) {
if( true == m_environmentcubetexturesupport ) {
::glEnable( GL_TEXTURE_CUBE_MAP );
}
else {
::glEnable( GL_TEXTURE_2D );
}
}
else {
if( true == m_environmentcubetexturesupport ) {
::glDisable( GL_TEXTURE_CUBE_MAP );
}
else {
::glDisable( GL_TEXTURE_2D );
}
}
}
// shadow texture unit.
if( m_shadowtextureunit >= 0 ) {
if( ( true == Global.RenderShadows ) && ( true == Shadows ) && ( false == Global.bWireFrame ) ) {
select_unit( m_shadowtextureunit );
::glEnable( GL_TEXTURE_2D );
}
else {
select_unit( m_shadowtextureunit );
::glDisable( GL_TEXTURE_2D );
}
}
// normal/reflection texture unit
if( m_normaltextureunit >= 0 ) {
if( true == Reflections ) {
select_unit( m_normaltextureunit );
::glEnable( GL_TEXTURE_2D );
}
else {
select_unit( m_normaltextureunit );
::glDisable( GL_TEXTURE_2D );
}
}
// diffuse texture unit.
// NOTE: toggle actually disables diffuse texture mapping, unlike setup counterpart
if( true == Diffuse ) {
select_unit( m_diffusetextureunit );
::glEnable( GL_TEXTURE_2D );
}
else {
select_unit( m_diffusetextureunit );
::glDisable( GL_TEXTURE_2D );
}
// update unit state
m_unitstate.diffuse = Diffuse;
m_unitstate.shadows = Shadows;
m_unitstate.reflections = Reflections;
}
void
opengl_renderer::setup_shadow_color( glm::vec4 const &Shadowcolor ) {
select_unit( m_helpertextureunit );
::glTexEnvfv( GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, glm::value_ptr( Shadowcolor ) ); // in-shadow colour multiplier
select_unit( m_diffusetextureunit );
}
void
opengl_renderer::setup_environment_light( TEnvironmentType const Environment ) {
switch( Environment ) {
case e_flat: {
m_sunlight.apply_intensity();
// m_environment = Environment;
break;
}
case e_canyon: {
m_sunlight.apply_intensity( 0.4f );
// m_environment = Environment;
break;
}
case e_tunnel: {
m_sunlight.apply_intensity( 0.2f );
// m_environment = Environment;
break;
}
default: {
break;
}
}
}
bool
opengl_renderer::Render( world_environment *Environment ) {
// calculate shadow tone, based on positions of celestial bodies
m_shadowcolor = glm::mix(
glm::vec4{ colors::shadow },
glm::vec4{ colors::white },
std::clamp( -Environment->m_sun.getAngle(), 0.f, 6.f ) / 6.f );
if( ( Environment->m_sun.getAngle() < -18.f )
&& ( Environment->m_moon.getAngle() > 0.f ) ) {
// turn on moon shadows after nautical twilight, if the moon is actually up
m_shadowcolor = colors::shadow;
}
// soften shadows depending on sky overcast factor
m_shadowcolor = glm::min(
colors::white,
m_shadowcolor + ( ( colors::white - colors::shadow ) * Global.Overcast ) );
if( Global.bWireFrame ) {
// bez nieba w trybie rysowania linii
return false;
}
Bind_Material( null_handle );
::glDisable( GL_LIGHTING );
::glDisable( GL_DEPTH_TEST );
::glDepthMask( GL_FALSE );
// skydome
// drawn with 500m radius to blend in if the fog range is low
::glPushMatrix();
::glScalef( 500.f, 500.f, 500.f );
m_skydomerenderer.render();
if( Global.bUseVBO ) {
gfx::opengl_vbogeometrybank::reset();
}
::glPopMatrix();
// stars
if( Environment->m_stars.m_stars != nullptr ) {
// setup
::glPushMatrix();
::glRotatef( Environment->m_stars.m_latitude, 1.f, 0.f, 0.f ); // ustawienie osi OY na północ
::glRotatef( -std::fmod( (float)Global.fTimeAngleDeg, 360.f ), 0.f, 1.f, 0.f ); // obrót dobowy osi OX
::glPointSize( 2.f );
// render
Render( Environment->m_stars.m_stars, nullptr, 1.0 );
// post-render cleanup
::glPointSize( 3.f );
::glPopMatrix();
}
// celestial bodies
if( DebugModeFlag == true ) {
// mark sun position for easier debugging
Environment->m_sun.render();
Environment->m_moon.render();
}
// render actual sun and moon
::glPushAttrib( GL_ENABLE_BIT | GL_COLOR_BUFFER_BIT );
::glDisable( GL_LIGHTING );
::glDisable( GL_ALPHA_TEST );
::glEnable( GL_BLEND );
::glBlendFunc( GL_SRC_ALPHA, GL_ONE );
auto const &modelview = OpenGLMatrices.data( GL_MODELVIEW );
auto const fogfactor { std::clamp( Global.fFogEnd / 2000.f, 0.f, 1.f ) }; // closer/denser fog reduces opacity of the celestial bodies
float const duskfactor = 1.0f - std::clamp( std::abs( Environment->m_sun.getAngle() ), 0.0f, 12.0f ) / 12.0f;
glm::vec3 suncolor = glm::mix(
glm::vec3( 255.0f / 255.0f, 242.0f / 255.0f, 231.0f / 255.0f ),
glm::vec3( 235.0f / 255.0f, 140.0f / 255.0f, 36.0f / 255.0f ),
duskfactor );
// sun
if (!m_isATI)
{
Bind_Texture( m_suntexture );
::glColor4f( suncolor.x, suncolor.y, suncolor.z, std::clamp( 1.5f - Global.Overcast, 0.f, 1.f ) * fogfactor );
auto const sunvector = Environment->m_sun.getDirection();
auto const sunposition = modelview * glm::vec4( sunvector.x, sunvector.y, sunvector.z, 1.0f );
::glPushMatrix();
::glLoadIdentity(); // macierz jedynkowa
::glTranslatef( sunposition.x, sunposition.y, sunposition.z ); // początek układu zostaje bez zmian
float const size = std::lerp( // TODO: expose distance/scale factor from the moon object
0.0325f,
0.0275f,
std::clamp( Environment->m_sun.getAngle(), 0.f, 90.f ) / 90.f );
::glBegin( GL_TRIANGLE_STRIP );
::glMultiTexCoord2f( m_diffusetextureunit, 1.f, 1.f ); ::glVertex3f( -size, size, 0.f );
::glMultiTexCoord2f( m_diffusetextureunit, 1.f, 0.f ); ::glVertex3f( -size, -size, 0.f );
::glMultiTexCoord2f( m_diffusetextureunit, 0.f, 1.f ); ::glVertex3f( size, size, 0.f );
::glMultiTexCoord2f( m_diffusetextureunit, 0.f, 0.f ); ::glVertex3f( size, -size, 0.f );
::glEnd();
::glPopMatrix();
}
// moon
if (!m_isATI)
{
Bind_Texture( m_moontexture );
glm::vec3 mooncolor(1.0f, 0.9490196f, 0.9058824f);
::glColor4f(
mooncolor.r, mooncolor.g, mooncolor.b,
// fade the moon if it's near the sun in the sky, especially during the day
std::max<float>(
0.f,
1.0
- 0.5 * Global.fLuminance
- 0.65 * std::max( 0.f, glm::dot( Environment->m_sun.getDirection(), Environment->m_moon.getDirection() ) ) )
* fogfactor );
auto const moonposition = modelview * glm::vec4( Environment->m_moon.getDirection(), 1.0f );
::glPushMatrix();
::glLoadIdentity(); // macierz jedynkowa
::glTranslatef( moonposition.x, moonposition.y, moonposition.z );
float const size = std::lerp( // TODO: expose distance/scale factor from the moon object
0.0160f,
0.0135f,
std::clamp( Environment->m_moon.getAngle(), 0.f, 90.f ) / 90.f );
// choose the moon appearance variant, based on current moon phase
// NOTE: implementation specific, 8 variants are laid out in 3x3 arrangement
// from new moon onwards, top left to right bottom (last spot is left for future use, if any)
auto const moonphase = Environment->m_moon.getPhase();
float moonu, moonv;
if( moonphase < 1.84566f ) { moonv = 1.0f - 0.0f; moonu = 0.0f; }
else if( moonphase < 5.53699f ) { moonv = 1.0f - 0.0f; moonu = 0.333f; }
else if( moonphase < 9.22831f ) { moonv = 1.0f - 0.0f; moonu = 0.667f; }
else if( moonphase < 12.91963f ) { moonv = 1.0f - 0.333f; moonu = 0.0f; }
else if( moonphase < 16.61096f ) { moonv = 1.0f - 0.333f; moonu = 0.333f; }
else if( moonphase < 20.30228f ) { moonv = 1.0f - 0.333f; moonu = 0.667f; }
else if( moonphase < 23.99361f ) { moonv = 1.0f - 0.667f; moonu = 0.0f; }
else if( moonphase < 27.68493f ) { moonv = 1.0f - 0.667f; moonu = 0.333f; }
else if( moonphase == 50 ) { moonv = 1.0f - 0.667f; moonu = 0.66f; } //9th slot used for easter egg
else { moonv = 1.0f - 0.0f; moonu = 0.0f; }
::glBegin( GL_TRIANGLE_STRIP );
::glMultiTexCoord2f( m_diffusetextureunit, moonu, moonv ); ::glVertex3f( -size, size, 0.0f );
::glMultiTexCoord2f( m_diffusetextureunit, moonu, moonv - 0.333f ); ::glVertex3f( -size, -size, 0.0f );
::glMultiTexCoord2f( m_diffusetextureunit, moonu + 0.333f, moonv ); ::glVertex3f( size, size, 0.0f );
::glMultiTexCoord2f( m_diffusetextureunit, moonu + 0.333f, moonv - 0.333f ); ::glVertex3f( size, -size, 0.0f );
::glEnd();
::glPopMatrix();
}
::glPopAttrib();
m_sunlight.apply_angle();
m_sunlight.apply_intensity();
// clouds
if( Environment->m_clouds.mdCloud ) {
// setup
Disable_Lights();
::glEnable( GL_LIGHTING );
::glEnable( GL_LIGHT0 ); // other lights will be enabled during lights update
::glLightModelfv(
GL_LIGHT_MODEL_AMBIENT,
glm::value_ptr(
glm::mix( Environment->m_skydome.GetAverageColor(), suncolor, duskfactor * 0.25f )
* std::lerp( 1.f, 0.35f, Global.Overcast / 2.f ) // overcast darkens the clouds
* 0.5f // arbitrary adjustment factor
) );
// render
Render( Environment->m_clouds.mdCloud, nullptr, 100.0 );
Render_Alpha( Environment->m_clouds.mdCloud, nullptr, 100.0 );
// post-render cleanup
::glLightModelfv( GL_LIGHT_MODEL_AMBIENT, glm::value_ptr( colors::none ) );
::glDisable( GL_LIGHTING );
}
::glDepthMask( GL_TRUE );
::glEnable( GL_DEPTH_TEST );
::glEnable( GL_LIGHTING );
return true;
}
// geometry methods
// creates a new geometry bank. returns: handle to the bank or NULL
gfx::geometrybank_handle
opengl_renderer::Create_Bank() {
if (Global.bUseVBO)
return m_geometry.register_bank(std::make_unique<gfx::opengl_vbogeometrybank>());
else
return m_geometry.register_bank(std::make_unique<gfx::opengl_dlgeometrybank>());
}
// creates a new indexed geometry chunk of specified type from supplied data, in specified bank. returns: handle to the chunk or NULL
gfx::geometry_handle opengl_renderer::Insert(gfx::index_array &Indices, gfx::vertex_array &Vertices, gfx::userdata_array &Userdata, gfx::geometrybank_handle const &Geometry, int const Type)
{
return m_geometry.create_chunk( Indices, Vertices, Userdata, Geometry, Type );
}
// creates a new geometry chunk of specified type from supplied data, in specified bank. returns: handle to the chunk or NULL
gfx::geometry_handle opengl_renderer::Insert(gfx::vertex_array &Vertices, gfx::userdata_array &Userdata, gfx::geometrybank_handle const &Geometry, int const Type)
{
return m_geometry.create_chunk( Vertices, Userdata, Geometry, Type );
}
// replaces data of specified chunk with the supplied vertex data, starting from specified offset
bool opengl_renderer::Replace(gfx::vertex_array &Vertices, gfx::userdata_array &Userdata, gfx::geometry_handle const &Geometry, int const Type, const std::size_t Offset)
{
return m_geometry.replace( Vertices, Userdata, Geometry, Offset );
}
// adds supplied vertex data at the end of specified chunk
bool opengl_renderer::Append(gfx::vertex_array &Vertices, gfx::userdata_array &Userdata, gfx::geometry_handle const &Geometry, int const Type)
{
return m_geometry.append( Vertices, Userdata, Geometry );
}
// provides direct access to vertex data of specfied chunk
gfx::index_array const &
opengl_renderer::Indices( gfx::geometry_handle const &Geometry ) const {
return m_geometry.indices( Geometry );
}
// provides direct access to vertex data of specfied chunk
gfx::vertex_array const &
opengl_renderer::Vertices( gfx::geometry_handle const &Geometry ) const {
return m_geometry.vertices( Geometry );
}
gfx::userdata_array const &opengl_renderer::UserData(const gfx::geometry_handle &Geometry) const
{
return m_geometry.userdata( Geometry );
}
// material methods
material_handle
opengl_renderer::Fetch_Material( std::string const &Filename, bool const Loadnow ) {
return m_materials.create( Filename, Loadnow );
}
void
opengl_renderer::Bind_Material( material_handle const Material, TSubModel const *sm, lighting_data const *lighting ) {
if( Material != null_handle ) {
auto const &material { m_materials.material( Material ) };
if( false == material.is_good ) {
// use failure indicator instead
if( Material != m_invalid_material ) {
Bind_Material( m_invalid_material );
}
return;
}
// materials which declare texture variants provide a different texture set per model instance,
// picked from the seed of the rendered instance and the variant group of the submodel
auto const &materialtextures {
material.texture_variants.empty() ?
material.textures :
material.GetTextures( TSubModel::VariantId( sm ) ) };
if( false == Global.BasicRenderer ) {
m_textures.bind( m_normaltextureunit, materialtextures[1] );
}
m_textures.bind( m_diffusetextureunit, materialtextures[0] );
}
else {
// null material, unbind all textures
if( false == Global.BasicRenderer ) {
m_textures.bind( m_normaltextureunit, null_handle );
}
m_textures.bind( m_diffusetextureunit, null_handle );
}
}
IMaterial const *
opengl_renderer::Material( material_handle const Material ) const {
return &m_materials.material( Material );
}
opengl_material const &
opengl_renderer::Material( TSubModel const * Submodel ) const {
return m_materials.material( submodel_material( Submodel ) );
}
// shader methods
std::shared_ptr<gl::program>
opengl_renderer::Fetch_Shader( std::string const &name ) {
return std::shared_ptr<gl::program>();
}
// texture methods
void
opengl_renderer::select_unit( GLint const Textureunit ) {
return m_textures.unit( Textureunit );
}
texture_handle
opengl_renderer::Fetch_Texture( std::string const &Filename, bool const Loadnow, GLint format_hint ) {
return m_textures.create( Filename, Loadnow, GL_RGBA );
}
void
opengl_renderer::Bind_Texture( texture_handle const Texture ) {
m_textures.bind( m_diffusetextureunit, Texture );
}
void
opengl_renderer::Bind_Texture( std::size_t const Unit, texture_handle const Texture ) {
m_textures.bind( Unit, Texture );
}
ITexture &
opengl_renderer::Texture( texture_handle const Texture ) {
return m_textures.texture( Texture );
}
ITexture const &
opengl_renderer::Texture( texture_handle const Texture ) const {
return m_textures.texture( Texture );
}
void
opengl_renderer::Pick_Control_Callback( std::function<void( TSubModel const *, const glm::vec2 )> Callback ) {
m_control_pick_requests.emplace_back( Callback );
}
void
opengl_renderer::Pick_Node_Callback( std::function<void( scene::basic_node * )> Callback ) {
m_node_pick_requests.emplace_back( Callback );
}
void
opengl_renderer::Render( scene::basic_region *Region ) {
m_sectionqueue.clear();
m_cellqueue.clear();
// build a list of region sections to render
glm::vec3 const cameraposition { m_renderpass.camera.position() };
auto const camerax = static_cast<int>( std::floor( cameraposition.x / scene::EU07_SECTIONSIZE + scene::EU07_REGIONSIDESECTIONCOUNT / 2 ) );
auto const cameraz = static_cast<int>( std::floor( cameraposition.z / scene::EU07_SECTIONSIZE + scene::EU07_REGIONSIDESECTIONCOUNT / 2 ) );
int const segmentcount = 2 * static_cast<int>( std::ceil( m_renderpass.draw_range * Global.fDistanceFactor / scene::EU07_SECTIONSIZE ) );
int const originx = camerax - segmentcount / 2;
int const originz = cameraz - segmentcount / 2;
for( int row = originz; row <= originz + segmentcount; ++row ) {
if( row < 0 ) { continue; }
if( row >= scene::EU07_REGIONSIDESECTIONCOUNT ) { break; }
for( int column = originx; column <= originx + segmentcount; ++column ) {
if( column < 0 ) { continue; }
if( column >= scene::EU07_REGIONSIDESECTIONCOUNT ) { break; }
auto *section { Region->m_sections[ row * scene::EU07_REGIONSIDESECTIONCOUNT + column ] };
if( ( section != nullptr )
&& ( m_renderpass.camera.visible( section->m_area ) ) ) {
m_sectionqueue.emplace_back( section );
}
}
}
switch( m_renderpass.draw_mode ) {
case rendermode::color: {
Update_Lights( simulation::Lights );
Render( std::begin( m_sectionqueue ), std::end( m_sectionqueue ) );
if( EditorModeFlag ) {
// when editor mode is active calculate world position of the cursor
// at this stage the z-buffer is filled with only ground geometry
Update_Mouse_Position();
}
// draw queue is filled while rendering sections
Render( std::begin( m_cellqueue ), std::end( m_cellqueue ) );
break;
}
case rendermode::shadows:
case rendermode::cabshadows:
case rendermode::pickscenery: {
// these render modes don't bother with lights
Render( std::begin( m_sectionqueue ), std::end( m_sectionqueue ) );
// they can also skip queue sorting, as they only deal with opaque geometry
// NOTE: there's benefit from rendering front-to-back, but is it significant enough? TODO: investigate
Render( std::begin( m_cellqueue ), std::end( m_cellqueue ) );
break;
}
case rendermode::reflections: {
Render( std::begin( m_sectionqueue ), std::end( m_sectionqueue ) );
if( Global.reflectiontune.fidelity >= 1 ) {
Render(std::begin(m_cellqueue), std::end(m_cellqueue));
}
break;
}
case rendermode::pickcontrols:
default: {
// no need to render anything ourside of the cab in control picking mode
break;
}
}
}
void
opengl_renderer::Render( section_sequence::iterator First, section_sequence::iterator Last ) {
switch( m_renderpass.draw_mode ) {
case rendermode::color:
case rendermode::reflections: {
break;
}
case rendermode::shadows:
case rendermode::cabshadows: {
// experimental, for shadows render both back and front faces, to supply back faces of the 'forest strips'
::glDisable( GL_CULL_FACE );
break; }
case rendermode::pickscenery: {
// non-interactive scenery elements get neutral colour
::glColor3fv( glm::value_ptr( colors::none ) );
break;
}
default: {
break; }
}
while( First != Last ) {
auto *section = *First;
section->create_geometry();
// render shapes held by the section
switch( m_renderpass.draw_mode ) {
case rendermode::color:
case rendermode::reflections:
case rendermode::shadows:
case rendermode::cabshadows:
case rendermode::pickscenery: {
if( false == section->m_shapes.empty() ) {
// since all shapes of the section share center point we can optimize out a few calls here
::glPushMatrix();
auto const originoffset { section->m_area.center - m_renderpass.camera.position() };
::glTranslated( originoffset.x, originoffset.y, originoffset.z );
// render
for( auto const &shape : section->m_shapes ) { Render( shape, true ); }
// post-render cleanup
::glPopMatrix();
}
break;
}
case rendermode::pickcontrols:
default: {
break;
}
}
// add the section's cells to the cell queue
switch( m_renderpass.draw_mode ) {
case rendermode::color:
case rendermode::shadows:
case rendermode::cabshadows:
case rendermode::pickscenery: {
// TBD, TODO: refactor into a method to reuse below?
for( auto &cell : section->m_cells ) {
if( ( true == cell.m_active )
&& ( m_renderpass.camera.visible( cell.m_area ) ) ) {
// store visible cells with content as well as their current distance, for sorting later
m_cellqueue.emplace_back(
glm::length2( m_renderpass.camera.position() - cell.m_area.center ),
&cell );
}
}
break;
}
case rendermode::reflections: {
// we can skip filling the cell queue if reflections pass isn't going to use it
if( Global.reflectiontune.fidelity == 0 ) { break; }
for( auto &cell : section->m_cells ) {
if( ( true == cell.m_active ) && ( m_renderpass.camera.visible( cell.m_area ) ) ) {
// store visible cells with content as well as their current distance, for sorting later
m_cellqueue.emplace_back( glm::length2( m_renderpass.camera.position() - cell.m_area.center ), &cell );
}
}
break;
}
case rendermode::pickcontrols:
default: {
break;
}
}
// proceed to next section
++First;
}
switch( m_renderpass.draw_mode ) {
case rendermode::shadows:
case rendermode::cabshadows: {
// restore standard face cull mode
::glEnable( GL_CULL_FACE );
break; }
default: {
break; }
}
}
void
opengl_renderer::Render( cell_sequence::iterator First, cell_sequence::iterator Last ) {
// cache initial iterator for the second sweep
auto first { First };
// first pass draws elements which we know are located in section banks, to reduce vbo switching
while( First != Last ) {
auto *cell = First->second;
// przeliczenia animacji torów w sektorze
cell->RaAnimate( m_framestamp );
switch( m_renderpass.draw_mode ) {
case rendermode::color: {
// since all shapes of the section share center point we can optimize out a few calls here
::glPushMatrix();
auto const originoffset { cell->m_area.center - m_renderpass.camera.position() };
::glTranslated( originoffset.x, originoffset.y, originoffset.z );
// render
// opaque non-instanced shapes
for( auto const &shape : cell->m_shapesopaque ) { Render( shape, false ); }
// tracks
// TODO: update after path node refactoring
Render( std::begin( cell->m_paths ), std::end( cell->m_paths ) );
#ifdef EU07_USE_DEBUG_CULLING
// debug
::glLineWidth( 2.f );
float const width = cell->m_area.radius;
float const height = cell->m_area.radius * 0.2f;
glDisable( GL_LIGHTING );
glDisable( GL_TEXTURE_2D );
glColor3ub( 255, 128, 128 );
glBegin( GL_LINE_LOOP );
glVertex3f( -width, height, width );
glVertex3f( -width, height, -width );
glVertex3f( width, height, -width );
glVertex3f( width, height, width );
glEnd();
glBegin( GL_LINE_LOOP );
glVertex3f( -width, 0, width );
glVertex3f( -width, 0, -width );
glVertex3f( width, 0, -width );
glVertex3f( width, 0, width );
glEnd();
glBegin( GL_LINES );
glVertex3f( -width, height, width ); glVertex3f( -width, 0, width );
glVertex3f( -width, height, -width ); glVertex3f( -width, 0, -width );
glVertex3f( width, height, -width ); glVertex3f( width, 0, -width );
glVertex3f( width, height, width ); glVertex3f( width, 0, width );
glEnd();
glColor3ub( 255, 255, 255 );
glEnable( GL_TEXTURE_2D );
glEnable( GL_LIGHTING );
glLineWidth( 1.f );
#endif
// post-render cleanup
::glPopMatrix();
break;
}
case rendermode::reflections:
{
// since all shapes of the section share center point we can optimize out a few calls here
::glPushMatrix();
auto const originoffset{ cell->m_area.center - m_renderpass.camera.position() };
::glTranslated( originoffset.x, originoffset.y, originoffset.z );
// render
// opaque non-instanced shapes
for( auto const &shape : cell->m_shapesopaque ) { Render( shape, false ); }
// post-render cleanup
::glPopMatrix();
break;
}
case rendermode::shadows: {
// since all shapes of the section share center point we can optimize out a few calls here
::glPushMatrix();
auto const originoffset { cell->m_area.center - m_renderpass.camera.position() };
::glTranslated( originoffset.x, originoffset.y, originoffset.z );
// render
// opaque non-instanced shapes
for( auto const &shape : cell->m_shapesopaque ) {
Render( shape, false );
}
// tracks
Render( std::begin( cell->m_paths ), std::end( cell->m_paths ) );
// post-render cleanup
::glPopMatrix();
break;
}
case rendermode::cabshadows: {
// since all shapes of the section share center point we can optimize out a few calls here
::glPushMatrix();
auto const originoffset { cell->m_area.center - m_renderpass.camera.position() };
::glTranslated( originoffset.x, originoffset.y, originoffset.z );
// render
// opaque non-instanced shapes
for( auto const &shape : cell->m_shapesopaque ) {
Render( shape, false );
}
// NOTE: tracks aren't likely to cast shadows into the cab, so we skip them in this pass
// post-render cleanup
::glPopMatrix();
break;
}
case rendermode::pickscenery: {
// same procedure like with regular render, but editor-enabled nodes receive custom colour used for picking
// since all shapes of the section share center point we can optimize out a few calls here
::glPushMatrix();
auto const originoffset { cell->m_area.center - m_renderpass.camera.position() };
::glTranslated( originoffset.x, originoffset.y, originoffset.z );
// render
// opaque non-instanced shapes
// non-interactive scenery elements get neutral colour
::glColor3fv( glm::value_ptr( colors::none ) );
for( auto const &shape : cell->m_shapesopaque ) { Render( shape, false ); }
// tracks
for( auto *path : cell->m_paths ) {
::glColor3fv( glm::value_ptr( pick_color( m_picksceneryitems.size() + 1 ) ) );
Render( path );
}
// post-render cleanup
::glPopMatrix();
break;
}
case rendermode::pickcontrols:
default: {
break;
}
}
++First;
}
// second pass draws elements with their own vbos
while( first != Last ) {
auto const *cell = first->second;
switch( m_renderpass.draw_mode ) {
case rendermode::color:
case rendermode::shadows:
case rendermode::cabshadows: {
// TBD, TODO: refactor in to a method to reuse in branch below?
// opaque parts of instanced models
for( auto *instance : cell->m_instancesopaque ) {
Render( instance );
}
// opaque parts of vehicles
for( auto *path : cell->m_paths ) {
for( auto *dynamic : path->Dynamics ) {
Render( dynamic );
}
}
// memcells
if( ( EditorModeFlag )
&& ( DebugModeFlag ) ) {
::glPushAttrib( GL_ENABLE_BIT );
::glDisable( GL_TEXTURE_2D );
::glColor3f( 0.36f, 0.75f, 0.35f );
for( auto *memorycell : cell->m_memorycells ) {
Render( memorycell );
}
::glPopAttrib();
}
break;
}
case rendermode::reflections: {
if( Global.reflectiontune.fidelity >= 1 ) {
// opaque parts of instanced models
for( auto *instance : cell->m_instancesopaque ) {
Render( instance );
}
}
if( Global.reflectiontune.fidelity >= 2 ) {
// opaque parts of vehicles
for( auto *path : cell->m_paths ) {
for( auto *dynamic : path->Dynamics ) {
Render( dynamic );
}
}
}
break;
}
case rendermode::pickscenery: {
// opaque parts of instanced models
// same procedure like with regular render, but each node receives custom colour used for picking
for( auto *instance : cell->m_instancesopaque ) {
::glColor3fv( glm::value_ptr( pick_color( m_picksceneryitems.size() + 1 ) ) );
Render( instance );
}
// memcells
if( ( EditorModeFlag )
&& ( DebugModeFlag ) ) {
for( auto *memorycell : cell->m_memorycells ) {
::glColor3fv( glm::value_ptr( pick_color( m_picksceneryitems.size() + 1 ) ) );
Render( memorycell );
}
}
// vehicles aren't included in scenery picking for the time being
break;
}
case rendermode::pickcontrols:
default: {
break;
}
}
++first;
}
#ifdef EU07_USE_DEBUG_SOUNDEMITTERS
// sound emitters
if( DebugModeFlag ) {
switch( m_renderpass.draw_mode ) {
case rendermode::color: {
::glPushAttrib( GL_ENABLE_BIT );
::glDisable( GL_TEXTURE_2D );
::glColor3f( 0.36f, 0.75f, 0.35f );
for( auto const &audiosource : audio::renderer.m_sources ) {
::glPushMatrix();
auto const position = audiosource.properties.location - m_renderpass.camera.position();
::glTranslated( position.x, position.y, position.z );
::gluSphere( m_quadric, 0.1, 4, 2 );
::glPopMatrix();
}
::glPopAttrib();
break;
}
default: {
break;
}
}
}
#endif
}
void
opengl_renderer::Render( scene::shape_node const &Shape, bool const Ignorerange ) {
auto const &data { Shape.data() };
if( false == Ignorerange ) {
double distancesquared;
switch( m_renderpass.draw_mode ) {
case rendermode::shadows:
case rendermode::cabshadows: {
// 'camera' for the light pass is the light source, but we need to draw what the 'real' camera sees
distancesquared = glm::length2( ( data.area.center - Global.pCamera.Pos ) / (double)Global.ZoomFactor ) / Global.fDistanceFactor;
break;
}
case rendermode::reflections: {
// reflection mode draws simplified version of the shapes, by artificially increasing view range
distancesquared =
// TBD, TODO: bind offset value with setting variable?
sq(EU07_REFLECTIONFIDELITYOFFSET)
// TBD: take into account distance multipliers?
+ glm::length2( ( data.area.center - m_renderpass.camera.position() ) ) /* / Global.fDistanceFactor */;
break;
}
default: {
distancesquared = glm::length2( ( data.area.center - m_renderpass.camera.position() ) / (double)Global.ZoomFactor ) / Global.fDistanceFactor;
break;
}
}
if( ( distancesquared < data.rangesquared_min )
|| ( distancesquared >= data.rangesquared_max ) ) {
return;
}
}
// setup
Bind_Material( data.material );
switch( m_renderpass.draw_mode ) {
case rendermode::color:
case rendermode::reflections: {
::glColor3fv( glm::value_ptr( data.lighting.diffuse ) );
/*
// NOTE: ambient component is set by diffuse component
// NOTE: for the time being non-instanced shapes are rendered without specular component due to wrong/arbitrary values set in legacy scenarios
// TBD, TODO: find a way to resolve this with the least amount of tears?
::glMaterialfv( GL_FRONT, GL_SPECULAR, glm::value_ptr( data.lighting.specular * m_sunlight.specular.a * m_specularopaquescalefactor ) );
*/
break;
}
// pick modes are painted with custom colours, and shadow pass doesn't use any
case rendermode::shadows: {
// skip if the shadow caster rank is too low for currently set threshold
if( Material( data.material )->GetShadowRank() > Global.gfx_shadow_rank_cutoff ) {
return;
}
}
[[ fallthrough ]];
case rendermode::cabshadows:
case rendermode::pickscenery:
case rendermode::pickcontrols:
default: {
break;
}
}
// render
m_geometry.draw( data.geometry );
// debug data
++m_renderpass.draw_stats.shapes;
++m_renderpass.draw_stats.drawcalls;
}
void
opengl_renderer::Render( TAnimModel *Instance ) {
if( false == Instance->m_visible ) { return; }
if( false == m_renderpass.camera.visible( Instance->m_area ) ) { return; }
double distancesquared;
switch( m_renderpass.draw_mode ) {
case rendermode::shadows:
case rendermode::cabshadows: {
// 'camera' for the light pass is the light source, but we need to draw what the 'real' camera sees
distancesquared = glm::length2((Instance->location() - m_renderpass.camera.position()) / (double)Global.ZoomFactor) / Global.fDistanceFactor;
break;
}
case rendermode::reflections: {
// reflection mode draws simplified version of the shapes, by artificially increasing view range
// it also ignores zoom settings and distance multipliers
// TBD: take into account distance multipliers?
distancesquared = glm::length2( ( Instance->location() - m_renderpass.camera.position() ) ) /* / Global.fDistanceFactor */;
// NOTE: arbitrary draw range limit
if( distancesquared > Global.reflectiontune.range_instances * Global.reflectiontune.range_instances ) {
return;
}
// TBD, TODO: bind offset value with setting variable?
distancesquared += sq(EU07_REFLECTIONFIDELITYOFFSET);
break;
}
default: {
distancesquared = glm::length2((Instance->location() - m_renderpass.camera.position()) / (double)Global.ZoomFactor) / Global.fDistanceFactor;
break;
}
}
if( ( distancesquared < Instance->m_rangesquaredmin )
|| ( distancesquared >= Instance->m_rangesquaredmax ) ) {
return;
}
// crude way to reject early items too far to affect the output (mostly relevant for shadow passes)
auto const drawdistancethreshold{ m_renderpass.draw_range + 250 };
if( distancesquared > sq(drawdistancethreshold) ) {
return;
}
// second stage visibility cull, reject modelstoo far away to be noticeable
auto const radiussquared { Instance->radius() * Instance->radius() };
if( radiussquared * Global.ZoomFactor / distancesquared < 0.003 * 0.003 ) {
return;
}
switch( m_renderpass.draw_mode ) {
case rendermode::pickscenery: {
// add the node to the pick list
m_picksceneryitems.emplace_back( Instance );
break;
}
default: {
break;
}
}
Instance->RaAnimate( m_framestamp ); // jednorazowe przeliczenie animacji
Instance->RaPrepare();
if( Instance->pModel ) {
// renderowanie rekurencyjne submodeli
Render(
Instance->pModel,
Instance->Material(),
distancesquared,
Instance->location() - m_renderpass.camera.position(),
Instance->vAngle );
// debug data
++m_renderpass.draw_stats.models;
}
}
bool
opengl_renderer::Render( TDynamicObject *Dynamic ) {
Dynamic->renderme = (
( Global.pCamera.m_owner == Dynamic && !FreeFlyModeFlag )
|| ( m_renderpass.camera.visible( Dynamic ) ) );
if( false == Dynamic->renderme ) { return false; }
// lod visibility ranges are defined for base (x 1.0) viewing distance. for render we adjust them for actual range multiplier and zoom
float squaredistance;
glm::dvec3 const originoffset = Dynamic->vPosition - m_renderpass.camera.position();
switch( m_renderpass.draw_mode ) {
case rendermode::shadows: {
squaredistance = glm::length2( glm::vec3{ glm::dvec3{ Dynamic->vPosition - Global.pCamera.Pos } } / Global.ZoomFactor );
if( false == FreeFlyModeFlag ) {
// filter out small details if we're in vehicle cab
squaredistance = std::max( sq(100.f), squaredistance );
}
break;
}
case rendermode::cabshadows: {
squaredistance = glm::length2( glm::vec3{ glm::dvec3{ Dynamic->vPosition - Global.pCamera.Pos } } / Global.ZoomFactor );
// filter out small details
squaredistance = std::max( sq(100.f), squaredistance );
break;
}
case rendermode::reflections: {
// reflection mode draws simplified version of the shapes, by artificially increasing view range
// it also ignores zoom settings and distance multipliers
squaredistance =
std::max(
sq(100.f),
// TBD: take into account distance multipliers?
glm::length2( glm::vec3{ originoffset } ) /* / Global.fDistanceFactor */ );
// NOTE: arbitrary draw range limit
if( squaredistance > Global.reflectiontune.range_vehicles * Global.reflectiontune.range_vehicles ) {
return false;
}
// TBD, TODO: bind offset value with setting variable?
// NOTE: combined 'squared' distance doesn't equal actual squared (distance + offset) but, eh
squaredistance += sq(EU07_REFLECTIONFIDELITYOFFSET);
break;
}
default: {
squaredistance = glm::length2( glm::vec3{ originoffset } / Global.ZoomFactor );
// TODO: filter out small details based on fidelity setting
break;
}
}
// crude way to reject early items too far to affect the output (mostly relevant for shadow and reflection passes)
auto const drawdistancethreshold{ m_renderpass.draw_range + 250 };
if( squaredistance > sq(drawdistancethreshold) ) {
return false;
}
// second stage visibility cull, reject vehicles too far away to be noticeable
Dynamic->renderme = ( Dynamic->radius() * Global.ZoomFactor / std::sqrt( squaredistance ) > 0.003 );
if( false == Dynamic->renderme ) {
return false;
}
// debug data
++m_renderpass.draw_stats.dynamics;
// setup
TSubModel::iInstance = reinterpret_cast<std::uintptr_t>( Dynamic ); //żeby nie robić cudzych animacji
TSubModel::iVariantSeed = Dynamic->variant_seed();
Dynamic->ABuLittleUpdate( squaredistance ); // ustawianie zmiennych submodeli dla wspólnego modelu
::glPushMatrix();
::glTranslated( originoffset.x, originoffset.y, originoffset.z );
::glMultMatrixd( glm::value_ptr(Dynamic->mMatrix) );
switch( m_renderpass.draw_mode ) {
case rendermode::color: {
if( Dynamic->fShade > 0.0f ) {
// change light level based on light level of the occupied track
m_sunlight.apply_intensity( Dynamic->fShade );
}
m_renderspecular = true; // vehicles are rendered with specular component. static models without, at least for the time being
// render
if( Dynamic->mdLowPolyInt ) {
Render_lowpoly( Dynamic, squaredistance, false );
}
else {
// HACK: if the model lacks low poly interior, we presume the load is placed outside
if( Dynamic->mdLoad ) {
// renderowanie nieprzezroczystego ładunku
Render( Dynamic->mdLoad, Dynamic->Material(), squaredistance, { 0.f, Dynamic->LoadOffset, 0.f }, {} );
}
}
if( Dynamic->mdModel ) {
// main model
Render( Dynamic->mdModel, Dynamic->Material(), squaredistance );
}
// optional attached models
for( auto *attachment : Dynamic->mdAttachments ) {
Render( attachment, Dynamic->Material(), squaredistance );
}
// optional coupling adapters
Render_coupler_adapter( Dynamic, squaredistance, end::front );
Render_coupler_adapter( Dynamic, squaredistance, end::rear );
// post-render cleanup
m_renderspecular = false;
if( Dynamic->fShade > 0.0f ) {
// restore regular light level
m_sunlight.apply_intensity();
}
break;
}
case rendermode::shadows:
case rendermode::cabshadows: {
if( Dynamic->mdLowPolyInt ) {
// low poly interior
// if( FreeFlyModeFlag ? true : !Dynamic->mdKabina || !Dynamic->bDisplayCab ) {
Render( Dynamic->mdLowPolyInt, Dynamic->Material(), squaredistance );
// }
}
if( Dynamic->mdModel ) {
// main model
Render( Dynamic->mdModel, Dynamic->Material(), squaredistance );
}
// optional attached models
for( auto *attachment : Dynamic->mdAttachments ) {
Render( attachment, Dynamic->Material(), squaredistance );
}
// optional coupling adapters
Render_coupler_adapter( Dynamic, squaredistance, end::front );
Render_coupler_adapter( Dynamic, squaredistance, end::rear );
if( Dynamic->mdLoad ) {
// renderowanie nieprzezroczystego ładunku
Render( Dynamic->mdLoad, Dynamic->Material(), squaredistance, { 0.f, Dynamic->LoadOffset, 0.f }, {} );
}
// post-render cleanup
break;
}
case rendermode::pickcontrols: {
if( Dynamic->mdLowPolyInt ) {
// low poly interior
Render( Dynamic->mdLowPolyInt, Dynamic->Material(), squaredistance );
}
break;
}
case rendermode::pickscenery:
default: {
break;
}
}
::glPopMatrix();
// TODO: check if this reset is needed. In theory each object should render all parts based on its own instance data anyway?
if( Dynamic->btnOn )
Dynamic->TurnOff(); // przywrócenie domyślnych pozycji submodeli
return true;
}
// rendering kabiny gdy jest oddzielnym modelem i ma byc wyswietlana
bool
opengl_renderer::Render_cab( TDynamicObject const *Dynamic, float const Lightlevel, bool const Alpha ) {
if( Dynamic == nullptr ) {
TSubModel::iInstance = 0;
TSubModel::iVariantSeed = 0;
return false;
}
TSubModel::iInstance = reinterpret_cast<std::uintptr_t>( Dynamic );
TSubModel::iVariantSeed = Dynamic->variant_seed();
if( ( true == FreeFlyModeFlag )
|| ( false == Dynamic->bDisplayCab )
|| ( Dynamic->mdKabina == Dynamic->mdModel ) ) {
// ABu: Rendering kabiny jako ostatniej, zeby bylo widac przez szyby, tylko w widoku ze srodka
return false;
}
if( Dynamic->mdKabina ) { // bo mogła zniknąć przy przechodzeniu do innego pojazdu
// setup shared by all render paths
::glPushMatrix();
auto const originoffset = Dynamic->GetPosition() - m_renderpass.camera.position();
::glTranslated( originoffset.x, originoffset.y, originoffset.z );
::glMultMatrixd( glm::value_ptr(Dynamic->mMatrix) );
switch( m_renderpass.draw_mode ) {
case rendermode::color: {
// render path specific setup:
if( Dynamic->fShade > 0.0f ) {
// change light level based on light level of the occupied track
m_sunlight.apply_intensity( Dynamic->fShade );
}
if( Lightlevel > 0.f ) {
// crude way to light the cabin, until we have something more complete in place
auto const luminance { Global.fLuminance * ( Dynamic->fShade > 0.0f ? Dynamic->fShade : 1.0f ) };
::glLightModelfv(
GL_LIGHT_MODEL_AMBIENT,
glm::value_ptr(
glm::vec3( m_baseambient )
+ ( Dynamic->InteriorLight * Lightlevel ) * static_cast<float>( std::clamp( 1.25 - luminance, 0.0, 1.0 ) ) ) );
}
// render
if( true == Alpha ) {
// translucent parts
Render_Alpha( Dynamic->mdKabina, Dynamic->Material(), 0.0 );
}
else {
// opaque parts
Render( Dynamic->mdKabina, Dynamic->Material(), 0.0 );
}
// post-render restore
if( Dynamic->fShade > 0.0f ) {
// change light level based on light level of the occupied track
m_sunlight.apply_intensity();
}
if( Lightlevel > 0.f ) {
// reset the overall ambient
::glLightModelfv( GL_LIGHT_MODEL_AMBIENT, glm::value_ptr(m_baseambient) );
}
break;
}
case rendermode::cabshadows: {
// cab shadowmap mode skips lighting setup and translucent parts
// render
if( true == Alpha ) {
// translucent parts
Render_Alpha( Dynamic->mdKabina, Dynamic->Material(), 0.0 );
}
else {
// opaque parts
Render( Dynamic->mdKabina, Dynamic->Material(), 0.0 );
}
// since the setup is simpler, there's nothing to reset afterwards
break;
}
case rendermode::pickcontrols: {
// control picking mode skips lighting setup and translucent parts
// render
Render( Dynamic->mdKabina, Dynamic->Material(), 0.0 );
// since the setup is simpler, there's nothing to reset afterwards
break;
}
default: {
break;
}
}
// post-render restore
::glPopMatrix();
}
return true;
}
bool
opengl_renderer::Render( TModel3d *Model, material_data const *Material, float const Squaredistance ) {
auto alpha =
( Material != nullptr ?
Material->textures_alpha :
0x30300030 );
alpha ^= 0x0F0F000F; // odwrócenie flag tekstur, aby wyłapać nieprzezroczyste
if( 0 == ( alpha & Model->iFlags & 0x1F1F001F ) ) {
// czy w ogóle jest co robić w tym cyklu?
return false;
}
Model->Root->fSquareDist = Squaredistance; // zmienna globalna!
// setup
Model->Root->ReplacableSet(
( Material != nullptr ?
Material->replacable_skins :
nullptr ),
alpha );
Model->Root->pRoot = Model;
// render
Render( Model->Root );
// post-render cleanup
return true;
}
bool
opengl_renderer::Render( TModel3d *Model, material_data const *Material, float const Squaredistance, glm::dvec3 const &Position, glm::vec3 const &Angle ) {
::glPushMatrix();
::glTranslated( Position.x, Position.y, Position.z );
if( Angle.y != 0.0 )
::glRotatef( Angle.y, 0.f, 1.f, 0.f );
if( Angle.x != 0.0 )
::glRotatef( Angle.x, 1.f, 0.f, 0.f );
if( Angle.z != 0.0 )
::glRotatef( Angle.z, 0.f, 0.f, 1.f );
auto const result = Render( Model, Material, Squaredistance );
::glPopMatrix();
return result;
}
void
opengl_renderer::Render( TSubModel *Submodel ) {
if( ( Submodel->iVisible )
&& ( TSubModel::fSquareDist >= Submodel->fSquareMinDist )
&& ( TSubModel::fSquareDist < Submodel->fSquareMaxDist ) ) {
if( Submodel->iFlags & 0xC000 ) {
::glPushMatrix();
if( Submodel->fMatrix )
::glMultMatrixf( Submodel->fMatrix->readArray() );
if( Submodel->b_Anim != TAnimType::at_None )
Submodel->RaAnimation( Submodel->b_Anim );
}
if( Submodel->eType < TP_ROTATOR ) {
// renderowanie obiektów OpenGL
if( Submodel->iAlpha & Submodel->iFlags & 0x1F ) {
// rysuj gdy element nieprzezroczysty
// debug data
++m_renderpass.draw_stats.submodels;
++m_renderpass.draw_stats.drawcalls;
switch( m_renderpass.draw_mode ) {
case rendermode::color:
case rendermode::reflections: {
// NOTE: code disabled as normalization marking doesn't take into account scaling propagation down hierarchy chains
// for the time being we'll do with enforced worst-case scaling method, when speculars are enabled
#ifdef EU07_USE_OPTIMIZED_NORMALIZATION
switch( Submodel->m_normalizenormals ) {
case TSubModel::normalize: {
::glEnable( GL_NORMALIZE ); break; }
case TSubModel::rescale: {
::glEnable( GL_RESCALE_NORMAL ); break; }
default: {
break; }
}
#else
if( true == m_renderspecular ) {
::glEnable( GL_NORMALIZE );
}
#endif
// material configuration:
auto const material{ submodel_material( Submodel ) }; // zmienialne skóry, również 0
// textures...
Bind_Material( material );
// ...colors and opacity...
auto const opacity { std::clamp( Material( material )->get_or_guess_opacity(), 0.f, 1.f ) };
if( Submodel->fVisible < 1.f ) {
// setup
::glAlphaFunc( GL_GREATER, 0.f );
::glEnable( GL_BLEND );
::glColor4f(
Submodel->f4Diffuse.r,
Submodel->f4Diffuse.g,
Submodel->f4Diffuse.b,
Submodel->fVisible );
}
else {
::glColor3fv( glm::value_ptr( Submodel->f4Diffuse ) ); // McZapkie-240702: zamiast ub
if( ( opacity != 0.f )
&& ( opacity != EU07_OPACITYDEFAULT ) ) {
::glAlphaFunc( GL_GREATER, opacity );
}
}
// ...specular...
if( ( true == m_renderspecular ) && ( m_sunlight.specular.a > 0.01f ) ) {
// specular strength in legacy models is set uniformly to 150, 150, 150 so we scale it down for opaque elements
::glMaterialfv( GL_FRONT, GL_SPECULAR, glm::value_ptr( Submodel->f4Specular * m_sunlight.specular.a * m_specularopaquescalefactor ) );
::glEnable( GL_RESCALE_NORMAL );
}
// ...luminance
auto const unitstate = m_unitstate;
auto const isemissive { ( Submodel->f4Emision.a > 0.f ) && ( Global.fLuminance < Submodel->fLight ) };
if( isemissive ) {
// zeby swiecilo na kolorowo
::glMaterialfv( GL_FRONT, GL_EMISSION, glm::value_ptr( /* Submodel->f4Emision */ Submodel->f4Diffuse * Submodel->f4Emision.a ) );
// disable shadows so they don't obstruct self-lit items
/*
setup_shadow_color( colors::white );
*/
switch_units( unitstate.diffuse, false, false );
}
// main draw call
m_geometry.draw( Submodel->m_geometry.handle );
/*
if( DebugModeFlag ) {
auto const & vertices { m_geometry.vertices( Submodel->m_geometry ) };
::glBegin( GL_LINES );
for( auto const &vertex : vertices ) {
::glVertex3fv( glm::value_ptr( vertex.position ) );
::glVertex3fv( glm::value_ptr( vertex.position + vertex.normal * 0.2f ) );
}
::glEnd();
}
*/
// post-draw reset
if( Submodel->fVisible < 1.f ) {
::glAlphaFunc( GL_GREATER, EU07_OPACITYDEFAULT );
::glDisable( GL_BLEND );
}
else if( ( opacity != 0.f )
&& ( opacity != EU07_OPACITYDEFAULT ) ) {
::glAlphaFunc( GL_GREATER, EU07_OPACITYDEFAULT );
}
if( ( true == m_renderspecular ) && ( m_sunlight.specular.a > 0.01f ) ) {
::glMaterialfv( GL_FRONT, GL_SPECULAR, glm::value_ptr( colors::none ) );
}
if( isemissive ) {
// restore default (lack of) brightness
::glMaterialfv( GL_FRONT, GL_EMISSION, glm::value_ptr( colors::none ) );
/*
setup_shadow_color( m_shadowcolor );
*/
switch_units( unitstate.diffuse, unitstate.shadows, unitstate.reflections );
}
#ifdef EU07_USE_OPTIMIZED_NORMALIZATION
switch( Submodel->m_normalizenormals ) {
case TSubModel::normalize: {
::glDisable( GL_NORMALIZE ); break; }
case TSubModel::rescale: {
::glDisable( GL_RESCALE_NORMAL ); break; }
default: {
break; }
}
#else
if( true == m_renderspecular ) {
::glDisable( GL_NORMALIZE );
}
#endif
break;
}
case rendermode::shadows: {
// skip if the shadow caster rank is too low for currently set threshold
// or if the submodel is hidden (colour pass only fades it out, e.g. inactive seasonal variant)
if( ( Material( Submodel ).shadow_rank > Global.gfx_shadow_rank_cutoff )
|| ( Submodel->fVisible <= 0.f ) )
{
--m_renderpass.draw_stats.submodels;
--m_renderpass.draw_stats.drawcalls;
break;
}
}
[[fallthrough]];
case rendermode::cabshadows:
case rendermode::pickscenery: {
// scenery picking and shadow both use enforced colour and no frills
// material configuration:
// textures...
if( Submodel->m_material < 0 ) { // zmienialne skóry
Bind_Material( submodel_material( Submodel ), Submodel );
}
else {
// również 0
Bind_Material( Submodel->m_material, Submodel );
}
// main draw call
m_geometry.draw( Submodel->m_geometry.handle );
// post-draw reset
break;
}
case rendermode::pickcontrols: {
// material configuration:
// control picking applies individual colour for each submodel
m_pickcontrolsitems.emplace_back( Submodel );
::glColor3fv( glm::value_ptr( pick_color( m_pickcontrolsitems.size() ) ) );
// textures...
if( Submodel->m_material < 0 ) { // zmienialne skóry
Bind_Material( submodel_material( Submodel ), Submodel );
}
else {
// również 0
Bind_Material( Submodel->m_material, Submodel );
}
// main draw call
m_geometry.draw( Submodel->m_geometry.handle );
// post-draw reset
break;
}
default: {
break;
}
}
}
}
else if( Submodel->eType == TP_FREESPOTLIGHT ) {
switch( m_renderpass.draw_mode ) {
// spotlights are only rendered in colour mode(s)
case rendermode::color:
case rendermode::reflections: {
auto const &modelview = OpenGLMatrices.data( GL_MODELVIEW );
auto const lightcenter =
modelview
* glm::mix(
glm::vec4( 0.f, 0.f, -0.05f, 1.f ),
glm::vec4( 0.f, 0.f, -0.25f, 1.f ),
static_cast<float>( TSubModel::fSquareDist / Submodel->fSquareMaxDist ) ); // pozycja punktu świecącego względem kamery
Submodel->fCosViewAngle = glm::dot( glm::normalize( modelview * glm::vec4( 0.f, 0.f, -1.f, 1.f ) - lightcenter ), glm::normalize( -lightcenter ) );
if( Submodel->fCosViewAngle > Submodel->fCosFalloffAngle ) {
// kąt większy niż maksymalny stożek swiatła
float lightlevel = 1.f; // TODO, TBD: parameter to control light strength
// view angle attenuation
float const anglefactor = std::clamp(
( Submodel->fCosViewAngle - Submodel->fCosFalloffAngle ) / ( Submodel->fCosHotspotAngle - Submodel->fCosFalloffAngle ),
0.f, 1.f );
lightlevel *= anglefactor;
// distance attenuation. NOTE: since it's fixed pipeline with built-in gamma correction we're using linear attenuation
// we're capping how much effect the distance attenuation can have, otherwise the lights get too tiny at regular distances
float const distancefactor { std::max( 0.5f, ( Submodel->fSquareMaxDist - TSubModel::fSquareDist ) / Submodel->fSquareMaxDist ) };
auto const pointsize { std::max( 3.f, 5.f * distancefactor * anglefactor ) };
// additionally reduce light strength for farther sources in rain or snow
if( Global.Overcast > 0.75f ) {
float const precipitationfactor{
std::lerp(
std::lerp( 1.f, 0.25f, std::clamp( Global.Overcast * 0.75f - 0.5f, 0.f, 1.f ) ),
1.f,
distancefactor ) };
lightlevel *= precipitationfactor;
}
if( lightlevel > 0.f ) {
// material configuration:
Bind_Material( null_handle );
// limit impact of dense fog on the lights
auto const lightrange { std::max<float>( 500, m_fogrange * 2 ) }; // arbitrary, visibility at least 500m
::glFogf( GL_FOG_DENSITY, static_cast<GLfloat>( 1.0 / lightrange ) );
::glPushAttrib( GL_ENABLE_BIT | GL_COLOR_BUFFER_BIT | GL_POINT_BIT );
::glDisable( GL_LIGHTING );
::glEnable( GL_BLEND );
::glAlphaFunc( GL_GREATER, 0.f );
::glPushMatrix();
::glLoadIdentity();
::glTranslatef( lightcenter.x, lightcenter.y, lightcenter.z ); // początek układu zostaje bez zmian
auto const unitstate = m_unitstate;
switch_units( m_unitstate.diffuse, false, false );
glm::vec3 const lightcolor {
Submodel->DiffuseOverride.r < 0.f ? // -1 indicates no override
glm::vec3( Submodel->f4Diffuse ) :
Submodel->DiffuseOverride };
// main draw call
if( Global.Overcast > 1.f ) {
// fake fog halo
float const fogfactor {
std::lerp(
2.f, 1.f,
std::clamp( Global.fFogEnd / 2000, 0.f, 1.f ) )
* std::max( 1.f, Global.Overcast ) };
::glPointSize( pointsize * fogfactor );
::glColor4f(
lightcolor.r,
lightcolor.g,
lightcolor.b,
Submodel->fVisible * std::min( 1.f, lightlevel ) * 0.5f );
::glDepthMask( GL_FALSE );
m_geometry.draw( Submodel->m_geometry.handle );
::glDepthMask( GL_TRUE );
}
::glPointSize( pointsize );
::glColor4f(
lightcolor.r,
lightcolor.g,
lightcolor.b,
Submodel->fVisible * std::min( 1.f, lightlevel ) );
m_geometry.draw( Submodel->m_geometry.handle );
// post-draw reset
switch_units( unitstate.diffuse, unitstate.shadows, unitstate.reflections );
::glPopMatrix();
::glPopAttrib();
::glFogf( GL_FOG_DENSITY, static_cast<GLfloat>( 1.0 / m_fogrange ) );
}
}
break;
}
default: {
break;
}
}
}
else if( Submodel->eType == TP_STARS ) {
switch( m_renderpass.draw_mode ) {
// colour points are only rendered in colour mode(s)
case rendermode::color:
case rendermode::reflections: {
if( Global.fLuminance < Submodel->fLight ) {
// material configuration:
::glPushAttrib( GL_ENABLE_BIT );
Bind_Material( null_handle );
::glDisable( GL_LIGHTING );
// main draw call
m_geometry.draw( Submodel->m_geometry.handle, gfx::color_streams );
// post-draw reset
::glPopAttrib();
}
break;
}
default: {
break;
}
}
}
if( Submodel->Child != nullptr )
if( Submodel->iAlpha & Submodel->iFlags & 0x001F0000 )
Render( Submodel->Child );
if( Submodel->iFlags & 0xC000 )
::glPopMatrix();
}
/*
if( Submodel->b_Anim < at_SecondsJump )
Submodel->b_Anim = at_None; // wyłączenie animacji dla kolejnego użycia subm
*/
if( Submodel->Next )
if( Submodel->iAlpha & Submodel->iFlags & 0x1F000000 )
Render( Submodel->Next ); // dalsze rekurencyjnie
}
void
opengl_renderer::Render( TTrack *Track ) {
if( ( Track->m_material1 == 0 )
&& ( Track->m_material2 == 0 )
&& ( ( Track->eType != tt_Switch )
|| ( Track->SwitchExtension->m_material3 == 0 ) ) ) {
return;
}
if( false == Track->m_visible ) {
return;
}
++m_renderpass.draw_stats.paths;
++m_renderpass.draw_stats.drawcalls;
switch( m_renderpass.draw_mode ) {
// single path pieces are rendererd in pick scenery mode only
case rendermode::pickscenery: {
// add the node to the pick list
m_picksceneryitems.emplace_back( Track );
if( Track->m_material1 != 0 ) {
Bind_Material( Track->m_material1 );
m_geometry.draw( std::begin( Track->Geometry1 ), std::end( Track->Geometry1 ) );
}
if( Track->m_material2 != 0 ) {
Bind_Material( Track->m_material2 );
m_geometry.draw( std::begin( Track->Geometry2 ), std::end( Track->Geometry2 ) );
}
if( ( Track->eType == tt_Switch )
&& ( Track->SwitchExtension->m_material3 != 0 ) ) {
Bind_Material( Track->SwitchExtension->m_material3 );
m_geometry.draw( Track->SwitchExtension->Geometry3 );
}
break;
}
default: {
break;
}
}
}
// experimental, does track rendering in two passes, to take advantage of reduced texture switching
void
opengl_renderer::Render( scene::basic_cell::path_sequence::const_iterator First, scene::basic_cell::path_sequence::const_iterator Last ) {
::glColor3fv( glm::value_ptr( colors::white ) );
// setup
switch( m_renderpass.draw_mode ) {
case rendermode::shadows:
case rendermode::cabshadows: {
// NOTE: roads-based platforms tend to miss parts of shadows if rendered with either back or front culling
::glDisable( GL_CULL_FACE );
break;
}
default: {
break;
}
}
// TODO: render auto generated trackbeds together with regular trackbeds in pass 1, and all rails in pass 2
// first pass, material 1
for( auto first { First }; first != Last; ++first ) {
auto const track { *first };
if( track->m_material1 == 0 ) {
continue;
}
if( false == track->m_visible ) {
continue;
}
++m_renderpass.draw_stats.paths;
++m_renderpass.draw_stats.drawcalls;
switch( m_renderpass.draw_mode ) {
case rendermode::color:
case rendermode::reflections: {
if( track->eEnvironment != e_flat ) {
setup_environment_light( track->eEnvironment );
}
Bind_Material( track->m_material1 );
m_geometry.draw( std::begin( track->Geometry1 ), std::end( track->Geometry1 ) );
if( track->eEnvironment != e_flat ) {
// restore default lighting
setup_environment_light();
}
break;
}
case rendermode::shadows:
case rendermode::cabshadows: {
if( ( std::abs( track->fTexHeight1 ) < 0.35f )
|| ( track->iCategoryFlag != 2 ) ) {
// shadows are only calculated for high enough roads, typically meaning track platforms
continue;
}
if( Material( track->m_material1 )->GetShadowRank() > Global.gfx_shadow_rank_cutoff )
{
// skip if the shadow caster rank is too low for currently set threshold
continue;
}
Bind_Material( track->m_material1 );
m_geometry.draw( std::begin( track->Geometry1 ), std::end( track->Geometry1 ) );
break;
}
case rendermode::pickscenery: // pick scenery mode uses piece-by-piece approach
case rendermode::pickcontrols:
default: {
break;
}
}
}
// second pass, material 2
for( auto first { First }; first != Last; ++first ) {
auto const track { *first };
if( track->m_material2 == 0 ) {
continue;
}
if( false == track->m_visible ) {
continue;
}
switch( m_renderpass.draw_mode ) {
case rendermode::color:
case rendermode::reflections: {
if( track->eEnvironment != e_flat ) {
setup_environment_light( track->eEnvironment );
}
Bind_Material( track->m_material2 );
m_geometry.draw( std::begin( track->Geometry2 ), std::end( track->Geometry2 ) );
if( track->eEnvironment != e_flat ) {
// restore default lighting
setup_environment_light();
}
break;
}
case rendermode::shadows:
case rendermode::cabshadows: {
if( ( std::abs( track->fTexHeight1 ) < 0.35f )
|| ( ( track->iCategoryFlag == 1 )
&& ( track->eType != tt_Normal ) ) ) {
// shadows are only calculated for high enough trackbeds
continue;
}
if( Material( track->m_material2 )->GetShadowRank() > Global.gfx_shadow_rank_cutoff )
{
// skip if the shadow caster rank is too low for currently set threshold
continue;
}
Bind_Material( track->m_material2 );
m_geometry.draw( std::begin( track->Geometry2 ), std::end( track->Geometry2 ) );
break;
}
case rendermode::pickscenery: // pick scenery mode uses piece-by-piece approach
case rendermode::pickcontrols:
default: {
break;
}
}
}
// third pass, material 3
for( auto first { First }; first != Last; ++first ) {
auto const track { *first };
if( track->eType != tt_Switch ) {
continue;
}
if( track->SwitchExtension->m_material3 == 0 ) {
continue;
}
if( false == track->m_visible ) {
continue;
}
switch( m_renderpass.draw_mode ) {
case rendermode::color:
case rendermode::reflections: {
if( track->eEnvironment != e_flat ) {
setup_environment_light( track->eEnvironment );
}
Bind_Material( track->SwitchExtension->m_material3 );
m_geometry.draw( track->SwitchExtension->Geometry3 );
if( track->eEnvironment != e_flat ) {
// restore default lighting
setup_environment_light();
}
break;
}
case rendermode::shadows:
case rendermode::cabshadows: {
if( ( std::abs( track->fTexHeight1 ) < 0.35f )
|| ( ( track->iCategoryFlag == 1 )
&& ( track->eType != tt_Normal ) ) ) {
// shadows are only calculated for high enough trackbeds
continue;
}
if( Material( track->SwitchExtension->m_material3 )->GetShadowRank() > Global.gfx_shadow_rank_cutoff )
{
// skip if the shadow caster rank is too low for currently set threshold
continue;
}
Bind_Material( track->SwitchExtension->m_material3 );
m_geometry.draw( track->SwitchExtension->Geometry3 );
break;
}
case rendermode::pickscenery: // pick scenery mode uses piece-by-piece approach
case rendermode::pickcontrols:
default: {
break;
}
}
}
// post-render reset
switch( m_renderpass.draw_mode ) {
case rendermode::shadows:
case rendermode::cabshadows: {
::glEnable( GL_CULL_FACE );
break;
}
default: {
break;
}
}
}
void
opengl_renderer::Render( TMemCell *Memcell ) {
::glPushMatrix();
auto const position = Memcell->location() - m_renderpass.camera.position();
::glTranslated( position.x, position.y + 0.5, position.z );
switch( m_renderpass.draw_mode ) {
case rendermode::color:
case rendermode::shadows:
case rendermode::cabshadows: {
//::gluSphere( m_quadric, 0.35, 4, 2 );
break;
}
case rendermode::pickscenery: {
// add the node to the pick list
m_picksceneryitems.emplace_back( Memcell );
//::gluSphere( m_quadric, 0.35, 4, 2 );
break;
}
case rendermode::reflections:
case rendermode::pickcontrols: {
break;
}
default: {
break;
}
}
::glPopMatrix();
}
void
opengl_renderer::Render_particles() {
switch_units( true, false, false );
Bind_Material( null_handle ); // TODO: bind smoke texture
// TBD: leave lighting on to allow vehicle lights to affect it?
::glDisable( GL_LIGHTING );
// momentarily disable depth write, to allow vehicle cab drawn afterwards to mask it instead of leaving it 'inside'
::glDepthMask( GL_FALSE );
Bind_Texture( m_smoketexture );
m_renderpass.draw_stats.particles = m_particlerenderer.render( m_diffusetextureunit );
if( Global.bUseVBO ) {
gfx::opengl_vbogeometrybank::reset();
}
::glDepthMask( GL_TRUE );
::glEnable( GL_LIGHTING );
}
void
opengl_renderer::Render_precipitation() {
if( Global.Overcast <= 1.f ) { return; }
switch_units( true, false, false );
// ::glColor4fv( glm::value_ptr( glm::vec4( glm::min( glm::vec3( Global.fLuminance ), glm::vec3( 1 ) ), 1 ) ) );
::glColor4fv(
glm::value_ptr(
glm::mix(
0.5f * ( Global.DayLight.diffuse + Global.DayLight.ambient ),
colors::white,
0.5f * std::clamp( (float)Global.fLuminance, 0.f, 1.f ) ) ) );
::glPushMatrix();
// tilt the precipitation cone against the camera movement vector for crude motion blur
// include current wind vector while at it
auto const velocity {
simulation::Environment.m_precipitation.m_cameramove * -1.0
+ glm::dvec3{ simulation::Environment.wind() } * 0.5 };
if( glm::length2( velocity ) > 0.0 ) {
auto const forward{ glm::normalize( velocity ) };
auto left { glm::cross( forward, {0.0,1.0,0.0} ) };
auto const rotationangle {
std::min(
45.0,
( FreeFlyModeFlag ?
5 * glm::length( velocity ) :
simulation::Train->Dynamic()->GetVelocity() * 0.2 ) ) };
::glRotated( rotationangle, left.x, 0.0, left.z );
}
if( false == FreeFlyModeFlag ) {
// counter potential vehicle roll
auto const roll { 0.5 * glm::degrees( simulation::Train->Dynamic()->Roll() ) };
if( roll != 0.0 ) {
auto const forward { simulation::Train->Dynamic()->VectorFront() };
auto const vehicledirection = simulation::Train->Dynamic()->DirectionGet();
::glRotated( roll, forward.x, 0.0, forward.z );
}
}
if( Global.Weather == "rain:" ) {
// oddly enough random streaks produce more natural looking rain than ones the eye can follow
::glRotated( LocalRandom() * 360, 0.0, 1.0, 0.0 );
}
// TBD: leave lighting on to allow vehicle lights to affect it?
::glDisable( GL_LIGHTING );
// momentarily disable depth write, to allow vehicle cab drawn afterwards to mask it instead of leaving it 'inside'
::glDepthMask( GL_FALSE );
m_precipitationrenderer.render( m_diffusetextureunit );
if( Global.bUseVBO ) {
gfx::opengl_vbogeometrybank::reset();
}
::glDepthMask( GL_TRUE );
::glEnable( GL_LIGHTING );
::glPopMatrix();
}
void
opengl_renderer::Render_Alpha( scene::basic_region *Region ) {
// sort the nodes based on their distance to viewer
std::sort(
std::begin( m_cellqueue ),
std::end( m_cellqueue ),
[]( distancecell_pair const &Left, distancecell_pair const &Right ) {
return ( Left.first ) < ( Right.first ); } );
Render_Alpha( std::rbegin( m_cellqueue ), std::rend( m_cellqueue ) );
}
void
opengl_renderer::Render_Alpha( cell_sequence::reverse_iterator First, cell_sequence::reverse_iterator Last ) {
// NOTE: this method is launched only during color pass therefore we don't bother with mode test here
// first pass draws elements which we know are located in section banks, to reduce vbo switching
{
auto first { First };
while( first != Last ) {
auto const *cell = first->second;
if( false == cell->m_shapestranslucent.empty() ) {
// since all shapes of the cell share center point we can optimize out a few calls here
::glPushMatrix();
auto const originoffset{ cell->m_area.center - m_renderpass.camera.position() };
::glTranslated( originoffset.x, originoffset.y, originoffset.z );
// render
// NOTE: we can reuse the method used to draw opaque geometry
for( auto const &shape : cell->m_shapestranslucent ) { Render( shape, false ); }
// post-render cleanup
::glPopMatrix();
}
++first;
}
}
// second pass draws elements with their own vbos
{
auto first { First };
while( first != Last ) {
auto const *cell = first->second;
// translucent parts of instanced models
for( auto *instance : cell->m_instancetranslucent ) { Render_Alpha( instance ); }
// translucent parts of vehicles
for( auto *path : cell->m_paths ) {
for( auto *dynamic : path->Dynamics ) {
if( ( false == FreeFlyModeFlag )
&& ( simulation::Train->Dynamic() == dynamic ) ) {
// delay drawing translucent parts of occupied vehicle until after the cab was drawn
continue;
}
Render_Alpha( dynamic );
}
}
++first;
}
}
// third pass draws the wires;
// wires use section vbos, but for the time being we want to draw them at the very end
{
::glDisable( GL_LIGHTING ); // linie nie powinny świecić
auto first{ First };
while( first != Last ) {
auto const *cell = first->second;
if( ( false == cell->m_traction.empty()
|| ( false == cell->m_lines.empty() ) ) ) {
// since all shapes of the cell share center point we can optimize out a few calls here
::glPushMatrix();
auto const originoffset { cell->m_area.center - m_renderpass.camera.position() };
::glTranslated( originoffset.x, originoffset.y, originoffset.z );
if( !Global.bSmoothTraction ) {
// na liniach kiepsko wygląda - robi gradient
::glDisable( GL_LINE_SMOOTH );
}
Bind_Material( null_handle );
// render
for( auto *traction : cell->m_traction ) { Render_Alpha( traction ); }
for( auto &lines : cell->m_lines ) { Render_Alpha( lines ); }
// post-render cleanup
::glLineWidth( 1.0 );
if( !Global.bSmoothTraction ) {
::glEnable( GL_LINE_SMOOTH );
}
::glPopMatrix();
}
++first;
}
::glEnable( GL_LIGHTING );
}
}
void
opengl_renderer::Render_Alpha( TAnimModel *Instance ) {
if (false == Instance->m_visible) { return; }
if( false == m_renderpass.camera.visible( Instance->m_area ) ) { return; }
double distancesquared;
switch( m_renderpass.draw_mode ) {
case rendermode::shadows: {
// 'camera' for the light pass is the light source, but we need to draw what the 'real' camera sees
distancesquared = glm::length2( ( Instance->location() - Global.pCamera.Pos ) / (double)Global.ZoomFactor ) / Global.fDistanceFactor;
break;
}
default: {
distancesquared = glm::length2( ( Instance->location() - m_renderpass.camera.position() ) / (double)Global.ZoomFactor ) / Global.fDistanceFactor;
break;
}
}
if( ( distancesquared < Instance->m_rangesquaredmin )
|| ( distancesquared >= Instance->m_rangesquaredmax ) ) {
return;
}
// crude way to reject early items too far to affect the output (mostly relevant for shadow passes)
auto const drawdistancethreshold{ m_renderpass.draw_range + 250 };
if( distancesquared > sq(drawdistancethreshold) ) {
return;
}
// second stage visibility cull, reject modelstoo far away to be noticeable
auto const radiussquared { Instance->radius() * Instance->radius() };
if( radiussquared * Global.ZoomFactor / distancesquared < 0.003 * 0.003 ) {
return;
}
Instance->RaAnimate( m_framestamp ); // jednorazowe przeliczenie animacji
Instance->RaPrepare();
if( Instance->pModel ) {
// renderowanie rekurencyjne submodeli
Render_Alpha(
Instance->pModel,
Instance->Material(),
distancesquared,
Instance->location() - m_renderpass.camera.position(),
Instance->vAngle );
}
}
void
opengl_renderer::Render_Alpha( TTraction *Traction ) {
/*
// NOTE: test disabled as this call is only executed as part of the colour pass
double distancesquared;
switch( m_renderpass.draw_mode ) {
case rendermode::shadows: {
// 'camera' for the light pass is the light source, but we need to draw what the 'real' camera sees
distancesquared = glm::length2( ( Traction->location() - Global.pCamera.Pos ) / Global.ZoomFactor ) / Global.fDistanceFactor;
break;
}
default: {
distancesquared = glm::length2( ( Traction->location() - m_renderpass.camera.position() ) / (double)Global.ZoomFactor ) / Global.fDistanceFactor;
break;
}
}
*/
auto const distancesquared { glm::length2( ( Traction->location() - m_renderpass.camera.position() ) / (double)Global.ZoomFactor ) / Global.fDistanceFactor };
if( ( distancesquared < Traction->m_rangesquaredmin )
|| ( distancesquared >= Traction->m_rangesquaredmax ) ) {
return;
}
if( false == Traction->m_visible ) {
return;
}
// rysuj jesli sa druty i nie zerwana
if( ( Traction->Wires == 0 )
|| ( true == TestFlag( Traction->DamageFlag, 128 ) ) ) {
return;
}
// setup
/*
float const linealpha = static_cast<float>(
std::min(
1.25,
5000 * Traction->WireThickness / ( distancesquared + 1.0 ) ) ); // zbyt grube nie są dobre
::glLineWidth( linealpha );
*/
auto const distance { static_cast<float>( std::sqrt( distancesquared ) ) };
auto const linealpha {
20.f * Traction->WireThickness
/ std::max(
0.5f * Traction->radius() + 1.f,
distance - ( 0.5f * Traction->radius() ) ) };
::glLineWidth(
std::clamp(
0.5f * linealpha + Traction->WireThickness * Traction->radius() / 1000.f,
1.f, 1.75f ) );
// McZapkie-261102: kolor zalezy od materialu i zasniedzenia
::glColor4fv(
glm::value_ptr(
glm::vec4{
Traction->wire_color() /* * ( DebugModeFlag ? 1.f : std::clamp( m_sunandviewangle, 0.25f, 1.f ) ) */,
linealpha } ) );
// render
m_geometry.draw( Traction->m_geometry );
// debug data
++m_renderpass.draw_stats.traction;
++m_renderpass.draw_stats.drawcalls;
}
void
opengl_renderer::Render_Alpha( scene::lines_node const &Lines ) {
auto const &data { Lines.data() };
/*
// NOTE: test disabled as this call is only executed as part of the colour pass
double distancesquared;
switch( m_renderpass.draw_mode ) {
case rendermode::shadows: {
// 'camera' for the light pass is the light source, but we need to draw what the 'real' camera sees
distancesquared = glm::length2( ( data.area.center - Global.pCamera.Pos ) / Global.ZoomFactor ) / Global.fDistanceFactor;
break;
}
default: {
distancesquared = glm::length2( ( data.area.center - m_renderpass.camera.position() ) / (double)Global.ZoomFactor ) / Global.fDistanceFactor;
break;
}
}
*/
auto const distancesquared { glm::length2( ( data.area.center - m_renderpass.camera.position() ) / (double)Global.ZoomFactor ) / Global.fDistanceFactor };
if( ( distancesquared < data.rangesquared_min )
|| ( distancesquared >= data.rangesquared_max ) ) {
return;
}
// setup
auto const distance { static_cast<float>( std::sqrt( distancesquared ) ) };
auto const linealpha = (
data.line_width > 0.f ?
10.f * data.line_width
/ std::max(
0.5f * data.area.radius + 1.f,
distance - ( 0.5f * data.area.radius ) ) :
1.f ); // negative width means the lines are always opague
::glLineWidth(
std::clamp(
0.5f * linealpha + data.line_width * data.area.radius / 1000.f,
1.f, 8.f ) );
::glColor4fv(
glm::value_ptr(
glm::vec4{
glm::vec3{ data.lighting.diffuse * m_sunlight.ambient }, // w zaleznosci od koloru swiatla
std::min( 1.f, linealpha ) } ) );
// render
m_geometry.draw( data.geometry );
++m_renderpass.draw_stats.lines;
++m_renderpass.draw_stats.drawcalls;
}
bool
opengl_renderer::Render_Alpha( TDynamicObject *Dynamic ) {
if( false == Dynamic->renderme ) { return false; }
// setup
TSubModel::iInstance = ( size_t )Dynamic; //żeby nie robić cudzych animacji
TSubModel::iVariantSeed = Dynamic->variant_seed();
glm::dvec3 const originoffset = Dynamic->vPosition - m_renderpass.camera.position();
// lod visibility ranges are defined for base (x 1.0) viewing distance. for render we adjust them for actual range multiplier and zoom
float squaredistance;
switch( m_renderpass.draw_mode ) {
case rendermode::shadows: {
squaredistance = glm::length2( glm::vec3{ glm::dvec3{ Dynamic->vPosition - Global.pCamera.Pos } } / Global.ZoomFactor );
break;
}
default: {
squaredistance = glm::length2( glm::vec3{ originoffset } / Global.ZoomFactor );
break;
}
}
Dynamic->ABuLittleUpdate( squaredistance ); // ustawianie zmiennych submodeli dla wspólnego modelu
::glPushMatrix();
::glTranslated( originoffset.x, originoffset.y, originoffset.z );
::glMultMatrixd( glm::value_ptr(Dynamic->mMatrix) );
if( Dynamic->fShade > 0.0f ) {
// change light level based on light level of the occupied track
m_sunlight.apply_intensity( Dynamic->fShade );
}
m_renderspecular = true;
// render
if( Dynamic->mdLowPolyInt ) {
// low poly interior
// if( FreeFlyModeFlag ? true : !Dynamic->mdKabina || !Dynamic->bDisplayCab ) {
/*
// enable cab light if needed
if( Dynamic->InteriorLightLevel > 0.0f ) {
// crude way to light the cabin, until we have something more complete in place
::glLightModelfv( GL_LIGHT_MODEL_AMBIENT, glm::value_ptr( Dynamic->InteriorLight * Dynamic->InteriorLightLevel ) );
}
*/
Render_Alpha( Dynamic->mdLowPolyInt, Dynamic->Material(), squaredistance );
/*
if( Dynamic->InteriorLightLevel > 0.0f ) {
// reset the overall ambient
::glLightModelfv( GL_LIGHT_MODEL_AMBIENT, glm::value_ptr( m_baseambient ) );
}
*/
// }
}
if( Dynamic->mdModel ) {
// main model
Render_Alpha( Dynamic->mdModel, Dynamic->Material(), squaredistance );
}
// optional attached models
for( auto *attachment : Dynamic->mdAttachments ) {
Render_Alpha( attachment, Dynamic->Material(), squaredistance );
}
// optional coupling adapters
Render_coupler_adapter( Dynamic, squaredistance, end::front, true );
Render_coupler_adapter( Dynamic, squaredistance, end::rear, true );
if( Dynamic->mdLoad ) {
// renderowanie nieprzezroczystego ładunku
Render_Alpha( Dynamic->mdLoad, Dynamic->Material(), squaredistance, { 0.f, Dynamic->LoadOffset, 0.f }, {} );
}
// post-render cleanup
m_renderspecular = false;
if( Dynamic->fShade > 0.0f ) {
// restore regular light level
m_sunlight.apply_intensity();
}
::glPopMatrix();
if( Dynamic->btnOn )
Dynamic->TurnOff(); // przywrócenie domyślnych pozycji submodeli
return true;
}
bool
opengl_renderer::Render_Alpha( TModel3d *Model, material_data const *Material, float const Squaredistance ) {
auto alpha =
( Material != nullptr ?
Material->textures_alpha :
0x30300030 );
if( 0 == ( alpha & Model->iFlags & 0x2F2F002F ) ) {
// nothing to render
return false;
}
Model->Root->fSquareDist = Squaredistance; // zmienna globalna!
// setup
Model->Root->ReplacableSet(
( Material != nullptr ?
Material->replacable_skins :
nullptr ),
alpha );
Model->Root->pRoot = Model;
// render
Render_Alpha( Model->Root );
// post-render cleanup
return true;
}
bool
opengl_renderer::Render_Alpha( TModel3d *Model, material_data const *Material, float const Squaredistance, glm::dvec3 const &Position, glm::vec3 const &Angle ) {
::glPushMatrix();
::glTranslated( Position.x, Position.y, Position.z );
if( Angle.y != 0.0 )
::glRotatef( Angle.y, 0.f, 1.f, 0.f );
if( Angle.x != 0.0 )
::glRotatef( Angle.x, 1.f, 0.f, 0.f );
if( Angle.z != 0.0 )
::glRotatef( Angle.z, 0.f, 0.f, 1.f );
auto const result = Render_Alpha( Model, Material, Squaredistance ); // position is effectively camera offset
::glPopMatrix();
return result;
}
// characters of a text display, laid out in a row. the text can be encoded in utf-8 or in windows-1250
void
opengl_renderer::Render_Alpha_text( TSubModel *Submodel ) {
if( TSubModel::pasText == nullptr ) { return; }
for( auto *p : Submodel->text_letters( *TSubModel::pasText ) ) { // translucent only, for the time being
Render_Alpha( p );
if( p->fMatrix )
::glMultMatrixf( p->fMatrix->readArray() ); // move on to the place of the next character
}
}
void
opengl_renderer::Render_Alpha( TSubModel *Submodel ) {
// renderowanie przezroczystych przez DL
if( ( Submodel->iVisible )
&& ( TSubModel::fSquareDist >= Submodel->fSquareMinDist )
&& ( TSubModel::fSquareDist < Submodel->fSquareMaxDist ) ) {
if( Submodel->iFlags & 0xC000 ) {
::glPushMatrix();
if( Submodel->fMatrix )
::glMultMatrixf( Submodel->fMatrix->readArray() );
if( Submodel->b_aAnim != TAnimType::at_None )
Submodel->RaAnimation( Submodel->b_aAnim );
}
if( Submodel->eType < TP_ROTATOR ) {
// renderowanie obiektów OpenGL
if( Submodel->iAlpha & Submodel->iFlags & 0x2F ) {
// rysuj gdy element przezroczysty
// debug data
++m_renderpass.draw_stats.submodels;
++m_renderpass.draw_stats.drawcalls;
switch( m_renderpass.draw_mode ) {
case rendermode::color: {
// NOTE: code disabled as normalization marking doesn't take into account scaling propagation down hierarchy chains
// for the time being we'll do with enforced worst-case scaling method, when speculars are enabled
#ifdef EU07_USE_OPTIMIZED_NORMALIZATION
switch( Submodel->m_normalizenormals ) {
case TSubModel::normalize: {
::glEnable( GL_NORMALIZE ); break; }
case TSubModel::rescale: {
::glEnable( GL_RESCALE_NORMAL ); break; }
default: {
break; }
}
#else
if( true == m_renderspecular ) {
::glEnable( GL_NORMALIZE );
}
#endif
// material configuration:
// textures...
auto const material { submodel_material( Submodel ) }; // zmienialne skóry, również 0
// textures...
Bind_Material( material );
// ...colors and opacity...
auto const opacity { std::clamp( Material( material )->get_or_guess_opacity(), 0.f, 1.f ) };
if( Submodel->fVisible < 1.f ) {
::glColor4f(
Submodel->f4Diffuse.r,
Submodel->f4Diffuse.g,
Submodel->f4Diffuse.b,
Submodel->fVisible );
}
else {
::glColor3fv( glm::value_ptr( Submodel->f4Diffuse ) ); // McZapkie-240702: zamiast ub
}
if( opacity != 0.f ) {
// discard fragments with opacity exceeding the threshold
::glAlphaFunc( GL_LEQUAL, opacity );
}
if( ( true == m_renderspecular ) && ( m_sunlight.specular.a > 0.01f ) ) {
::glMaterialfv( GL_FRONT, GL_SPECULAR, glm::value_ptr( Submodel->f4Specular * m_sunlight.specular.a * m_speculartranslucentscalefactor ) );
}
// ...luminance
auto const unitstate = m_unitstate;
auto const isemissive { ( Submodel->f4Emision.a > 0.f ) && ( Global.fLuminance < Submodel->fLight ) };
if( isemissive ) {
// zeby swiecilo na kolorowo
::glMaterialfv( GL_FRONT, GL_EMISSION, glm::value_ptr( /* Submodel->f4Emision */ Submodel->f4Diffuse * Submodel->f4Emision.a ) );
// disable shadows so they don't obstruct self-lit items
/*
setup_shadow_color( colors::white );
*/
switch_units( unitstate.diffuse, false, false );
}
// main draw call
m_geometry.draw( Submodel->m_geometry.handle );
// post-draw reset
if( opacity != 0.f ) {
// discard fragments with opacity exceeding the threshold
::glAlphaFunc( GL_GREATER, 0.0f );
}
if( ( true == m_renderspecular ) && ( m_sunlight.specular.a > 0.01f ) ) {
::glMaterialfv( GL_FRONT, GL_SPECULAR, glm::value_ptr( colors::none ) );
}
if( isemissive ) {
// restore default (lack of) brightness
::glMaterialfv( GL_FRONT, GL_EMISSION, glm::value_ptr( colors::none ) );
/*
setup_shadow_color( m_shadowcolor );
*/
switch_units( unitstate.diffuse, unitstate.shadows, unitstate.reflections );
}
#ifdef EU07_USE_OPTIMIZED_NORMALIZATION
switch( Submodel->m_normalizenormals ) {
case TSubModel::normalize: {
::glDisable( GL_NORMALIZE ); break; }
case TSubModel::rescale: {
::glDisable( GL_RESCALE_NORMAL ); break; }
default: {
break; }
}
#else
if( true == m_renderspecular ) {
::glDisable( GL_NORMALIZE );
}
#endif
break;
}
case rendermode::cabshadows: {
// scenery picking and shadow both use enforced colour and no frills
// material configuration:
// textures...
if( Submodel->m_material < 0 ) { // zmienialne skóry
Bind_Material( submodel_material( Submodel ), Submodel );
}
else {
// również 0
Bind_Material( Submodel->m_material, Submodel );
}
// main draw call
m_geometry.draw( Submodel->m_geometry.handle );
// post-draw reset
break;
}
default: {
break;
}
}
}
}
else if( Submodel->eType == TP_FREESPOTLIGHT ) {
if( ( Global.fLuminance < Submodel->fLight ) || ( Global.Overcast > 1.f ) ) {
// NOTE: we're forced here to redo view angle calculations etc, because this data isn't instanced but stored along with the single mesh
// TODO: separate instance data from reusable geometry
auto const &modelview = OpenGLMatrices.data( GL_MODELVIEW );
auto const lightcenter =
modelview
* glm::mix(
glm::vec4( 0.f, 0.f, -0.05f, 1.f ),
glm::vec4( 0.f, 0.f, -0.10f, 1.f ),
static_cast<float>( TSubModel::fSquareDist / Submodel->fSquareMaxDist ) ); // pozycja punktu świecącego względem kamery
Submodel->fCosViewAngle = glm::dot( glm::normalize( modelview * glm::vec4( 0.f, 0.f, -1.f, 1.f ) - lightcenter ), glm::normalize( -lightcenter ) );
if( Submodel->fCosViewAngle > Submodel->fCosFalloffAngle ) {
// only bother if the viewer is inside the visibility cone
// luminosity at night is at level of ~0.1, so the overall resulting transparency in clear conditions is ~0.5 at full 'brightness'
auto glarelevel { std::clamp(
std::max<float>(
0.6f - Global.fLuminance, // reduce the glare in bright daylight
Global.Overcast - 1.f ), // ensure some glare in rainy/foggy conditions
0.f, 1.f ) };
// view angle attenuation
float const anglefactor { std::clamp(
( Submodel->fCosViewAngle - Submodel->fCosFalloffAngle ) / ( Submodel->fCosHotspotAngle - Submodel->fCosFalloffAngle ),
0.f, 1.f ) };
glarelevel *= anglefactor;
if( glarelevel > 0.0f ) {
// setup
::glPushAttrib( GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT | GL_ENABLE_BIT );
Bind_Texture( m_glaretexture );
::glDisable( GL_LIGHTING );
::glDisable( GL_FOG );
::glDepthMask( GL_FALSE );
::glBlendFunc( GL_SRC_ALPHA, GL_ONE );
::glPushMatrix();
::glLoadIdentity(); // macierz jedynkowa
::glTranslatef( lightcenter.x, lightcenter.y, lightcenter.z ); // początek układu zostaje bez zmian
::glRotated( std::atan2( lightcenter.x, lightcenter.z ) * 180.0 / M_PI, 0.0, 1.0, 0.0 ); // jedynie obracamy w pionie o kąt
// disable shadows so they don't obstruct self-lit items
auto const unitstate = m_unitstate;
switch_units( unitstate.diffuse, false, false );
glm::vec3 const lightcolor {
Submodel->DiffuseOverride.r < 0.f ? // -1 indicates no override
glm::vec3( Submodel->f4Diffuse ) :
Submodel->DiffuseOverride };
::glColor4f(
lightcolor.r,
lightcolor.g,
lightcolor.b,
Submodel->fVisible * glarelevel );
// main draw call
m_geometry.draw( m_billboardgeometry );
/*
// NOTE: we could do simply...
vec3 vertexPosition_worldspace =
particleCenter_wordspace
+ CameraRight_worldspace * squareVertices.x * BillboardSize.x
+ CameraUp_worldspace * squareVertices.y * BillboardSize.y;
// ...etc instead IF we had easy access to camera's forward and right vectors. TODO: check if Camera matrix is accessible
*/
// post-render cleanup
switch_units( unitstate.diffuse, unitstate.shadows, unitstate.reflections );
::glPopMatrix();
::glPopAttrib();
}
}
}
}
if( Submodel->Child != nullptr ) {
if( Submodel->eType == TP_TEXT ) { // tekst renderujemy w specjalny sposób, zamiast submodeli z łańcucha Child
Render_Alpha_text( Submodel );
}
else if( Submodel->iAlpha & Submodel->iFlags & 0x002F0000 )
Render_Alpha( Submodel->Child );
}
if( Submodel->iFlags & 0xC000 )
::glPopMatrix();
}
/*
if( Submodel->b_aAnim < at_SecondsJump )
Submodel->b_aAnim = at_None; // wyłączenie animacji dla kolejnego użycia submodelu
*/
if( Submodel->Next != nullptr )
if( Submodel->iAlpha & Submodel->iFlags & 0x2F000000 )
Render_Alpha( Submodel->Next );
};
// utility methods
void
opengl_renderer::Update_Pick_Control() {
#ifdef EU07_USE_PICKING_FRAMEBUFFER
if( true == m_framebuffersupport ) {
::glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, m_pickframebuffer );
}
#endif
Render_pass( rendermode::pickcontrols );
// determine point to examine
glm::vec2 mousepos = Global.cursor_pos;
mousepos.y = Global.window_size.y - mousepos.y; // cursor coordinates are flipped compared to opengl
#ifdef EU07_USE_PICKING_FRAMEBUFFER
glm::ivec2 pickbufferpos;
if( true == m_framebuffersupport ) {
// ::glReadBuffer( GL_COLOR_ATTACHMENT0_EXT );
pickbufferpos = mousepos * glm::vec2(EU07_PICKBUFFERSIZE) / glm::vec2(Global.window_size);
}
else {
// ::glReadBuffer( GL_BACK );
pickbufferpos = mousepos * glm::vec2(Global.fb_size) / glm::vec2(Global.window_size);
}
#else
// ::glReadBuffer( GL_BACK );
glm::ivec2 pickbufferpos{ mousepos };
#endif
unsigned char pickreadout[4];
::glReadPixels( pickbufferpos.x, pickbufferpos.y, 1, 1, GL_BGRA, GL_UNSIGNED_BYTE, pickreadout );
auto const controlindex = pick_index( glm::ivec3{ pickreadout[ 2 ], pickreadout[ 1 ], pickreadout[ 0 ] } );
TSubModel *control { nullptr };
if( ( controlindex > 0 )
&& ( controlindex <= m_pickcontrolsitems.size() ) ) {
control = m_pickcontrolsitems[ controlindex - 1 ];
}
#ifdef EU07_USE_PICKING_FRAMEBUFFER
if( true == m_framebuffersupport ) {
::glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, 0 );
}
#endif
m_pickcontrolitem = control;
// return control;
for( auto& callback : m_control_pick_requests ) {
callback( m_pickcontrolitem, glm::vec2() );
}
m_control_pick_requests.clear();
}
void
opengl_renderer::Update_Pick_Node() {
#ifdef EU07_USE_PICKING_FRAMEBUFFER
if( true == m_framebuffersupport ) {
::glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, m_pickframebuffer );
}
#endif
Render_pass( rendermode::pickscenery );
// determine point to examine
glm::vec2 mousepos = Global.cursor_pos;
mousepos.y = Global.window_size.y - mousepos.y; // cursor coordinates are flipped compared to opengl
#ifdef EU07_USE_PICKING_FRAMEBUFFER
glm::ivec2 pickbufferpos;
if( true == m_framebuffersupport ) {
// ::glReadBuffer( GL_COLOR_ATTACHMENT0_EXT );
pickbufferpos = mousepos * glm::vec2(EU07_PICKBUFFERSIZE) / glm::vec2(Global.window_size);
}
else {
// ::glReadBuffer( GL_BACK );
pickbufferpos = mousepos * glm::vec2(Global.fb_size) / glm::vec2(Global.window_size);
}
#else
// ::glReadBuffer( GL_BACK );
glm::ivec2 pickbufferpos{ mousepos };
#endif
unsigned char pickreadout[4];
::glReadPixels( pickbufferpos.x, pickbufferpos.y, 1, 1, GL_BGRA, GL_UNSIGNED_BYTE, pickreadout );
auto const nodeindex = pick_index( glm::ivec3{ pickreadout[ 2 ], pickreadout[ 1 ], pickreadout[ 0 ] } );
scene::basic_node *node { nullptr };
if( ( nodeindex > 0 )
&& ( nodeindex <= m_picksceneryitems.size() ) ) {
node = m_picksceneryitems[ nodeindex - 1 ];
}
#ifdef EU07_USE_PICKING_FRAMEBUFFER
if( true == m_framebuffersupport ) {
::glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, 0 );
}
#endif
m_picksceneryitem = node;
// return node;
for( auto& callback : m_node_pick_requests ) {
callback( m_picksceneryitem );
}
m_node_pick_requests.clear();
}
// converts provided screen coordinates to world coordinates of most recent color pass
glm::dvec3
opengl_renderer::Update_Mouse_Position() {
glm::ivec2 mousepos = Global.cursor_pos * Global.fb_size / Global.window_size;
mousepos.x = std::clamp( mousepos.x, 0, Global.fb_size.x - 1 );
mousepos.y = std::clamp(Global.fb_size.y - mousepos.y, 0, Global.fb_size.y - 1);
GLfloat pointdepth;
::glReadPixels( mousepos.x, mousepos.y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &pointdepth );
if( pointdepth < 1.0 ) {
m_worldmousecoordinates =
glm::unProject(
glm::vec3{ mousepos, pointdepth },
glm::mat4{ glm::mat3{ m_colorpass.camera.modelview() } },
m_colorpass.camera.projection(),
glm::vec4{ 0, 0, Global.fb_size.x, Global.fb_size.y } );
}
return m_colorpass.camera.position() + glm::dvec3{ m_worldmousecoordinates };
}
void
opengl_renderer::Update( double const Deltatime ) {
// per frame updates
if( simulation::is_ready ) {
// update particle subsystem
renderpass_config renderpass;
setup_pass( renderpass, rendermode::color );
m_skydomerenderer.update();
m_precipitationrenderer.update();
m_particlerenderer.update( renderpass.camera );
}
// fixed step updates
/*
m_pickupdateaccumulator += Deltatime;
if( m_updateaccumulator > 0.5 ) {
m_pickupdateaccumulator = 0.0;
if( ( true == Global.ControlPicking )
&& ( false == FreeFlyModeFlag ) ) {
Update_Pick_Control();
}
else {
m_pickcontrolitem = nullptr;
}
// temporary conditions for testing. eventually will be coupled with editor mode
if( ( true == Global.ControlPicking )
&& ( true == DebugModeFlag )
&& ( true == FreeFlyModeFlag ) ) {
Update_Pick_Node();
}
else {
m_picksceneryitem = nullptr;
}
}
*/
m_updateaccumulator += Deltatime;
if( ( true == Global.ControlPicking )
&& ( false == FreeFlyModeFlag ) ) {
if( ( false == m_control_pick_requests.empty() )
|| ( m_updateaccumulator >= 1.0 ) ) {
Update_Pick_Control();
}
}
else {
m_pickcontrolitem = nullptr;
}
// temporary conditions for testing. eventually will be coupled with editor mode
if( ( true == Global.ControlPicking )
&& ( true == FreeFlyModeFlag )
&& ( ( true == DebugModeFlag )
|| ( true == EditorModeFlag ) ) ) {
if( ( false == m_node_pick_requests.empty() )
|| ( m_updateaccumulator >= 1.0 ) ) {
Update_Pick_Node();
}
}
else {
m_picksceneryitem = nullptr;
}
if( m_updateaccumulator < 1.0 ) {
// too early for any work
return;
}
m_updateaccumulator = 0.0;
m_framerate = 1000.f / ( Timer::subsystem.gfx_total.average() );
// adjust draw ranges etc, based on recent performance
// TODO: it doesn't make much sense with vsync
if( Global.targetfps != 0.f ) {
auto const fps_diff = m_framerate - Global.targetfps;
if( fps_diff < -0.5f ) {
Global.fDistanceFactor = std::min( std::max( 1.0f, Global.fDistanceFactor - 0.05f ), Global.gfx_distance_factor_max );
}
else if( fps_diff > 0.5f ) {
Global.fDistanceFactor = std::min( std::min( 3.0f, Global.fDistanceFactor + 0.05f ), Global.gfx_distance_factor_max );
}
}
// legacy framerate parameters
else if( Global.fFpsAverage != 0.f ) {
auto const fps_diff = m_framerate - Global.fFpsAverage;
if( fps_diff < -Global.fFpsDeviation ) {
Global.fDistanceFactor = std::min( std::max( 1.0f, Global.fDistanceFactor - 0.05f ), Global.gfx_distance_factor_max );
}
else if( fps_diff > Global.fFpsDeviation ) {
Global.fDistanceFactor = std::min( std::min( 3.0f, Global.fDistanceFactor + 0.05f ), Global.gfx_distance_factor_max );
}
}
if( Global.UpdateMaterials ) {
// update resources if there was environmental change
simulation_state simulationstate{
Global.Weather,
Global.Season
};
std::swap( m_simulationstate, simulationstate );
if( ( m_simulationstate.season != simulationstate.season ) && ( false == simulationstate.season.empty() ) ) {
m_materials.on_season_change();
}
if( ( m_simulationstate.weather != simulationstate.weather ) && ( false == simulationstate.weather.empty() ) ) {
m_materials.on_weather_change();
}
}
if( ( true == Global.ResourceSweep )
&& ( true == simulation::is_ready ) ) {
// garbage collection
m_geometry.update();
m_textures.update();
}
if( true == DebugModeFlag ) {
m_debugtimestext += m_textures.info();
}
// dump last opengl error, if any
auto const glerror = ::glGetError();
if( glerror != GL_NO_ERROR ) {
std::string glerrorstring;
win1250_to_ascii( glerrorstring );
Global.LastGLError = std::to_string( glerror ) + " (" + glerrorstring + ")";
}
}
// debug performance string
std::string const &
opengl_renderer::info_times() const {
return m_debugtimestext;
}
std::string const &
opengl_renderer::info_stats() const {
return m_debugstatstext;
}
void opengl_renderer::MakeScreenshot()
{
screenshot_manager::make_screenshot();
}
void
opengl_renderer::Update_Lights( light_array &Lights ) {
// arrange the light array from closest to farthest from current position of the camera
auto const &camera = m_renderpass.camera.position();
std::sort(
std::begin( Lights.data ),
std::end( Lights.data ),
[&camera]( light_array::light_record const &Left, light_array::light_record const &Right ) {
// move lights which are off at the end...
if( Left.intensity == 0.f ) { return false; }
if( Right.intensity == 0.f ) { return true; }
// ...otherwise prefer closer and/or brigher light sources
return ( glm::length2( camera - Left.position ) / Left.intensity ) < ( glm::length2( camera - Right.position ) / Right.intensity ); } );
size_t const count = std::min( m_lights.size(), Lights.data.size() );
if( count == 0 ) { return; }
auto renderlight = m_lights.begin();
for( auto const &scenelight : Lights.data ) {
if( renderlight == m_lights.end() ) {
// we ran out of lights to assign
break;
}
if( scenelight.intensity == 0.f ) {
// all lights past this one are bound to be off
break;
}
auto const lightoffset = glm::vec3{ scenelight.position - camera };
// length2 is better than length for comparing because it does not require sqrt function
if( glm::length2( lightoffset ) > sq(1000.f)) {
// we don't care about lights past arbitrary limit of 1 km.
// but there could still be weaker lights which are closer, so keep looking
continue;
}
// if the light passed tests so far, it's good enough
renderlight->position = lightoffset;
renderlight->direction = scenelight.direction;
auto luminance = static_cast<float>( Global.fLuminance );
// adjust luminance level based on vehicle's location, e.g. tunnels
auto const environment = scenelight.owner->fShade;
if( environment > 0.f ) {
luminance *= environment;
}
renderlight->diffuse =
glm::vec4{
glm::max( glm::vec3{ colors::none }, scenelight.color - glm::vec3{ luminance } ),
renderlight->diffuse[ 3 ] };
renderlight->ambient =
glm::vec4{
glm::max( glm::vec3{ colors::none }, scenelight.color * glm::vec3{ scenelight.intensity } - glm::vec3{ luminance } ),
renderlight->ambient[ 3 ] };
::glLightf( renderlight->id, GL_LINEAR_ATTENUATION, static_cast<GLfloat>( (0.25 * scenelight.count) / std::pow( scenelight.count, 2 ) * (scenelight.owner->DimHeadlights ? 1.25 : 1.0) ) );
::glEnable( renderlight->id );
renderlight->apply_intensity();
renderlight->apply_angle();
++renderlight;
}
while( renderlight != m_lights.end() ) {
// if we went through all scene lights and there's still opengl lights remaining, kill these
::glDisable( renderlight->id );
++renderlight;
}
}
void
opengl_renderer::Disable_Lights() {
for( size_t idx = 0; idx < m_lights.size() + 1; ++idx ) {
::glDisable( GL_LIGHT0 + (int)idx );
}
}
bool
opengl_renderer::Init_caps() {
std::string gl_renderer((char *)glGetString(GL_RENDERER));
std::string gl_vendor((char *)glGetString(GL_VENDOR));
std::string gl_version((char *)glGetString(GL_VERSION));
crashreport_add_info("gl_renderer", gl_renderer);
crashreport_add_info("gl_vendor", gl_vendor);
crashreport_add_info("gl_version", gl_version);
crashreport_add_info("gl_mode", Global.bUseVBO ? "vbo" : "dl");
WriteLog(
"Gfx Renderer: " + gl_renderer
+ " Vendor: " + gl_vendor
+ " OpenGL Version: " + gl_version );
if( !GLAD_GL_VERSION_2_1 ) {
ErrorLog( "Requires openGL >= 2.1" );
return false;
}
m_isATI = (gl_vendor.find("ATI") != -1);
char* extensions = (char*)glGetString( GL_EXTENSIONS );
if (extensions)
WriteLog( "Supported extensions: \n" + std::string(extensions));
WriteLog( std::string("render path: ") + ( Global.bUseVBO ? "VBO" : "Display lists" ) );
if( GL_EXT_framebuffer_object ) {
m_framebuffersupport = true;
WriteLog( "framebuffer objects enabled" );
}
else {
WriteLog( "framebuffer objects not supported, resorting to back buffer rendering where possible" );
}
// ograniczenie maksymalnego rozmiaru tekstur - parametr dla skalowania tekstur
{
GLint texturesize;
::glGetIntegerv( GL_MAX_TEXTURE_SIZE, &texturesize );
Global.iMaxTextureSize = std::min(Global.iMaxTextureSize, texturesize);
Global.iMaxCabTextureSize = std::min( Global.iMaxCabTextureSize, texturesize );
WriteLog( "texture sizes capped at " + std::to_string(Global.iMaxTextureSize) + "p (" + std::to_string( Global.iMaxCabTextureSize ) + "p for cab textures)" );
Global.CurrentMaxTextureSize = Global.iMaxTextureSize;
m_shadowbuffersize = Global.shadowtune.map_size;
m_shadowbuffersize = std::min( m_shadowbuffersize, texturesize );
WriteLog( "shadows map size capped at " + std::to_string( m_shadowbuffersize ) + "p" );
}
// cap the number of supported lights based on hardware
{
GLint maxlights;
::glGetIntegerv( GL_MAX_LIGHTS, &maxlights );
Global.DynamicLightCount = std::min( Global.DynamicLightCount, maxlights - 1 );
WriteLog( "dynamic light amount capped at " + std::to_string( Global.DynamicLightCount ) + " (" + std::to_string(maxlights) + " lights total supported by the gfx card)" );
}
// select renderer mode
if( true == Global.BasicRenderer ) {
WriteLog( "basic renderer selected, shadow and reflection mapping will be disabled" );
Global.RenderShadows = false;
m_diffusetextureunit = 0;
m_helpertextureunit = -1;
m_shadowtextureunit = -1;
m_normaltextureunit = -1;
}
else {
GLint maxtextureunits;
::glGetIntegerv( GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &maxtextureunits );
if( maxtextureunits < 4 ) {
WriteLog( "less than 4 texture units, shadow and reflection mapping will be disabled" );
Global.BasicRenderer = true;
Global.RenderShadows = false;
m_diffusetextureunit = 0;
m_helpertextureunit = -1;
m_shadowtextureunit = -1;
m_normaltextureunit = -1;
}
}
if( Global.iMultisampling ) {
WriteLog( "using multisampling x" + std::to_string( 1 << Global.iMultisampling ) );
}
WriteLog( "main window size: " + std::to_string( Global.fb_size.x ) + "x" + std::to_string( Global.fb_size.y ) );
return true;
}
glm::vec3
opengl_renderer::pick_color( std::size_t const Index ) {
/*
// pick colours are set with step of 4 for some slightly easier visual debugging. not strictly needed but, eh
int const colourstep = 4;
int const componentcapacity = 256 / colourstep;
auto const redgreen = std::div( Index, componentcapacity * componentcapacity );
auto const greenblue = std::div( redgreen.rem, componentcapacity );
auto const blue = Index % componentcapacity;
return
glm::vec3 {
redgreen.quot * colourstep / 255.0f,
greenblue.quot * colourstep / 255.0f,
greenblue.rem * colourstep / 255.0f };
*/
// alternatively
return
glm::vec3{
( ( Index & 0xff0000 ) >> 16 ) / 255.0f,
( ( Index & 0x00ff00 ) >> 8 ) / 255.0f,
( Index & 0x0000ff ) / 255.0f };
}
std::size_t
opengl_renderer::pick_index( glm::ivec3 const &Color ) {
/*
return (
std::floor( Color.b / 4 )
+ std::floor( Color.g / 4 ) * 64
+ std::floor( Color.r / 4 ) * 64 * 64 );
*/
// alternatively
return
Color.b
+ ( Color.g * 256 )
+ ( Color.r * 256 * 256 );
}
std::unique_ptr<gfx_renderer> opengl_renderer::create_func()
{
return std::unique_ptr<gfx_renderer>(new opengl_renderer());
}
bool opengl_renderer::renderer_register = gfx_renderer_factory::get_instance()->register_backend("legacy", opengl_renderer::create_func);
bool opengl_renderer::opengl_imgui_renderer::Init()
{
crashreport_add_info("imgui_ver", "gl2");
return ImGui_ImplOpenGL2_Init();
}
void opengl_renderer::opengl_imgui_renderer::Shutdown()
{
ImGui_ImplOpenGL2_Shutdown();
}
void opengl_renderer::opengl_imgui_renderer::BeginFrame()
{
ImGui_ImplOpenGL2_NewFrame();
}
void opengl_renderer::opengl_imgui_renderer::Render()
{
gl::buffer::unbind(gl::buffer::ARRAY_BUFFER);
ImGui_ImplOpenGL2_RenderDrawData(ImGui::GetDrawData());
}