mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-17 06:19:19 +02:00
* 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.
4568 lines
187 KiB
C++
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());
|
|
}
|
|
|