Reorganize source files into logical subdirectories

Co-authored-by: Hirek193 <23196899+Hirek193@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-03-14 19:01:57 +00:00
parent f981f81d55
commit 0531086bb9
221 changed files with 131 additions and 108 deletions

720
model/AnimModel.cpp Normal file
View File

@@ -0,0 +1,720 @@
/*
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/.
*/
/*
MaSzyna EU07 locomotive simulator
Copyright (C) 2001-2004 Marcin Wozniak and others
*/
#include "stdafx.h"
#include "AnimModel.h"
#include "renderer.h"
#include "MdlMngr.h"
#include "simulation.h"
#include "simulationtime.h"
#include "Event.h"
#include "Globals.h"
#include "Timer.h"
#include "Logs.h"
#include "renderer.h"
std::list<std::weak_ptr<TAnimContainer>> TAnimModel::acAnimList;
TAnimContainer::TAnimContainer()
{
vRotateAngles = Math3D::vector3(0.0f, 0.0f, 0.0f); // aktualne kąty obrotu
vDesiredAngles = Math3D::vector3(0.0f, 0.0f, 0.0f); // docelowe kąty obrotu
fRotateSpeed = 0.0;
vTranslation = Math3D::vector3(0.0f, 0.0f, 0.0f); // aktualne przesunięcie
vTranslateTo = Math3D::vector3(0.0f, 0.0f, 0.0f); // docelowe przesunięcie
fTranslateSpeed = 0.0;
fAngleSpeed = 0.0;
pSubModel = NULL;
iAnim = 0; // położenie początkowe
evDone = NULL; // powiadamianie o zakończeniu animacji
}
bool TAnimContainer::Init(TSubModel *pNewSubModel)
{
fRotateSpeed = 0.0f;
pSubModel = pNewSubModel;
return (pSubModel != NULL);
}
void TAnimContainer::SetRotateAnim( Math3D::vector3 vNewRotateAngles, double fNewRotateSpeed)
{
vDesiredAngles = vNewRotateAngles;
fRotateSpeed = fNewRotateSpeed;
iAnim |= 1;
/* //Ra 2014-07: jeśli model nie jest renderowany, to obliczyć czas animacji i dodać event
wewnętrzny
//można by też ustawić czas początku animacji zamiast pobierać czas ramki i liczyć różnicę
*/
if (evDone)
{ // dołączyć model do listy aniomowania, żeby animacje były przeliczane również bez
// wyświetlania
if (iAnim >= 0)
{ // jeśli nie jest jeszcze na liście animacyjnej
TAnimModel::acAnimList.push_back(shared_from_this());
iAnim |= 0x80000000; // dodany do listy
}
}
}
void TAnimContainer::SetTranslateAnim( Math3D::vector3 vNewTranslate, double fNewSpeed)
{
vTranslateTo = vNewTranslate;
fTranslateSpeed = fNewSpeed;
iAnim |= 2;
/* //Ra 2014-07: jeśli model nie jest renderowany, to obliczyć czas animacji i dodać event
wewnętrzny
//można by też ustawić czas początku animacji zamiast pobierać czas ramki i liczyć różnicę
*/
if (evDone)
{ // dołączyć model do listy aniomowania, żeby animacje były przeliczane również bez
// wyświetlania
if (iAnim >= 0)
{ // jeśli nie jest jeszcze na liście animacyjnej
TAnimModel::acAnimList.push_back(shared_from_this());
iAnim |= 0x80000000; // dodany do listy
}
}
}
// przeliczanie animacji wykonać tylko raz na model
void TAnimContainer::UpdateModel() {
if (pSubModel) // pozbyć się tego - sprawdzać wcześniej
{
if (fTranslateSpeed != 0.0)
{
auto dif = vTranslateTo - vTranslation; // wektor w kierunku docelowym
double l = LengthSquared3(dif); // długość wektora potrzebnego przemieszczenia
if (l >= 0.0001)
{ // jeśli do przemieszczenia jest ponad 1cm
auto s = Math3D::SafeNormalize(dif); // jednostkowy wektor kierunku
s = s *
(fTranslateSpeed *
Timer::GetDeltaTime()); // przemieszczenie w podanym czasie z daną prędkością
if (LengthSquared3(s) < l) //żeby nie jechało na drugą stronę
vTranslation += s;
else
vTranslation = vTranslateTo; // koniec animacji, "koniec animowania" uruchomi
// się w następnej klatce
}
else
{ // koniec animowania
vTranslation = vTranslateTo;
fTranslateSpeed = 0.0; // wyłączenie przeliczania wektora
if (LengthSquared3(vTranslation) <= 0.0001) // jeśli jest w punkcie początkowym
iAnim &= ~2; // wyłączyć zmianę pozycji submodelu
if( evDone ) {
// wykonanie eventu informującego o zakończeniu
simulation::Events.AddToQuery( evDone, nullptr );
}
}
}
if (fRotateSpeed != 0.0)
{
bool anim = false;
auto dif = vDesiredAngles - vRotateAngles;
double s;
s = std::abs( fRotateSpeed ) * sign(dif.x) * Timer::GetDeltaTime();
if (fabs(s) >= fabs(dif.x))
vRotateAngles.x = vDesiredAngles.x;
else
{
vRotateAngles.x += s;
anim = true;
}
s = std::abs( fRotateSpeed ) * sign(dif.y) * Timer::GetDeltaTime();
if (fabs(s) >= fabs(dif.y))
vRotateAngles.y = vDesiredAngles.y;
else
{
vRotateAngles.y += s;
anim = true;
}
s = std::abs( fRotateSpeed ) * sign(dif.z) * Timer::GetDeltaTime();
if (fabs(s) >= fabs(dif.z))
vRotateAngles.z = vDesiredAngles.z;
else
{
vRotateAngles.z += s;
anim = true;
}
// HACK: negative speed allows to work around legacy behaviour, where desired angle > 360 meant permanent rotation
if( fRotateSpeed > 0.0 ) {
while( vRotateAngles.x >= 360 )
vRotateAngles.x -= 360;
while( vRotateAngles.x <= -360 )
vRotateAngles.x += 360;
while( vRotateAngles.y >= 360 )
vRotateAngles.y -= 360;
while( vRotateAngles.y <= -360 )
vRotateAngles.y += 360;
while( vRotateAngles.z >= 360 )
vRotateAngles.z -= 360;
while( vRotateAngles.z <= -360 )
vRotateAngles.z += 360;
}
if( ( vRotateAngles.x == 0.0 )
&& ( vRotateAngles.y == 0.0 )
&& ( vRotateAngles.z == 0.0 ) ) {
iAnim &= ~1; // kąty są zerowe
}
if (!anim)
{ // nie potrzeba przeliczać już
fRotateSpeed = 0.0;
if( evDone ) {
// wykonanie eventu informującego o zakończeniu
simulation::Events.AddToQuery( evDone, nullptr );
}
}
}
if( fAngleSpeed != 0.f ) {
// NOTE: this is angle- not quaternion-based rotation TBD, TODO: switch to quaternion rotations?
fAngleCurrent += fAngleSpeed * Timer::GetDeltaTime(); // aktualny parametr interpolacji
}
}
};
void TAnimContainer::PrepareModel()
{ // tutaj zostawić tylko ustawienie submodelu, przeliczanie ma być w UpdateModel()
if (pSubModel) // pozbyć się tego - sprawdzać wcześniej
{
// nanoszenie animacji na wzorzec
if (iAnim & 1) // zmieniona pozycja względem początkowej
pSubModel->SetRotateXYZ(vRotateAngles); // ustawia typ animacji
if (iAnim & 2) // zmieniona pozycja względem początkowej
pSubModel->SetTranslate(vTranslation);
if (iAnim & 4) // zmieniona pozycja względem początkowej
{
if (fAngleSpeed > 0.0f)
{
if (fAngleCurrent >= 1.0f)
{ // interpolacja zakończona, ustawienie na pozycję końcową
qCurrent = qDesired;
fAngleSpeed = 0.0; // wyłączenie przeliczania wektora
if( evDone ) {
// wykonanie eventu informującego o zakończeniu
simulation::Events.AddToQuery( evDone, nullptr );
}
}
else
{ // obliczanie pozycji pośredniej
// normalizacja jest wymagana do interpolacji w następnej animacji
qCurrent = Normalize(
Slerp(qStart, qDesired, fAngleCurrent)); // interpolacja sferyczna kąta
// qCurrent=Slerp(qStart,qDesired,fAngleCurrent); //interpolacja sferyczna kąta
if (qCurrent.w ==
1.0) // rozpoznać brak obrotu i wyłączyć w iAnim w takim przypadku
iAnim &= ~4; // kąty są zerowe
}
}
mAnim->Quaternion(&qCurrent); // wypełnienie macierzy (wymaga normalizacji?)
pSubModel->mAnimMatrix = mAnim; // użyczenie do submodelu (na czas renderowania!)
}
}
// if (!strcmp(pSubModel->pName,"?Z?“?^?[")) //jak główna kość
// WriteLog(AnsiString(pMovementData->iFrame)+": "+AnsiString(iAnim)+"
// "+AnsiString(vTranslation.x)+" "+AnsiString(vTranslation.y)+" "+AnsiString(vTranslation.z));
}
bool TAnimContainer::InMovement()
{ // czy trwa animacja - informacja dla obrotnicy
return (fRotateSpeed != 0.0) || (fTranslateSpeed != 0.0);
}
void TAnimContainer::EventAssign(basic_event *ev)
{ // przypisanie eventu wykonywanego po zakończeniu animacji
evDone = ev;
};
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
TAnimModel::TAnimModel( scene::node_data const &Nodedata ) : basic_node( Nodedata ) {
m_lightcolors.fill( glm::vec3{ -1.f } );
m_lightopacities.fill( 1.f );
}
bool TAnimModel::Init(std::string const &asName, std::string const &asReplacableTexture)
{
if( asReplacableTexture.substr( 0, 1 ) == "*" ) {
// od gwiazdki zaczynają się teksty na wyświetlaczach
asText = asReplacableTexture.substr( 1, asReplacableTexture.length() - 1 ); // zapamiętanie tekstu
}
else if( asReplacableTexture != "none" ) {
m_materialdata.assign( asReplacableTexture );
}
// TODO: redo the random timer initialization
// fBlinkTimer = Random() * ( fOnTime + fOffTime );
pModel = TModelsManager::GetModel( asName );
return ( pModel != nullptr );
}
bool
TAnimModel::is_keyword( std::string const &Token ) const {
return ( Token == "endmodel" )
|| ( Token == "lights" )
|| ( Token == "lightcolors" )
|| ( Token == "angles" )
|| ( Token == "notransition" );
}
bool TAnimModel::Load(cParser *parser, bool ter)
{ // rozpoznanie wpisu modelu i ustawienie świateł
std::string name = parser->getToken<std::string>();
std::string texture = parser->getToken<std::string>(false);
replace_slashes( name );
replace_slashes( texture );
if (!Init( name, texture ))
{
if (name != "notload")
{ // gdy brak modelu
if (ter) // jeśli teren
{
if( ends_with( name, ".t3d" ) ) {
name[ name.length() - 3 ] = 'e';
}
#ifdef EU07_USE_OLD_TERRAINCODE
Global.asTerrainModel = name;
WriteLog("Terrain model \"" + name + "\" will be created.");
#endif
}
else
ErrorLog("Missed file: " + name);
}
}
else
{ // wiązanie świateł, o ile model wczytany
LightsOn[0] = pModel->GetFromName("Light_On00");
LightsOn[1] = pModel->GetFromName("Light_On01");
LightsOn[2] = pModel->GetFromName("Light_On02");
LightsOn[3] = pModel->GetFromName("Light_On03");
LightsOn[4] = pModel->GetFromName("Light_On04");
LightsOn[5] = pModel->GetFromName("Light_On05");
LightsOn[6] = pModel->GetFromName("Light_On06");
LightsOn[7] = pModel->GetFromName("Light_On07");
LightsOff[0] = pModel->GetFromName("Light_Off00");
LightsOff[1] = pModel->GetFromName("Light_Off01");
LightsOff[2] = pModel->GetFromName("Light_Off02");
LightsOff[3] = pModel->GetFromName("Light_Off03");
LightsOff[4] = pModel->GetFromName("Light_Off04");
LightsOff[5] = pModel->GetFromName("Light_Off05");
LightsOff[6] = pModel->GetFromName("Light_Off06");
LightsOff[7] = pModel->GetFromName("Light_Off07");
sm_winter_variant = pModel->GetFromName("winter_variant");
sm_spring_variant = pModel->GetFromName("spring_variant");
sm_summer_variant = pModel->GetFromName("summer_variant");
sm_autumn_variant = pModel->GetFromName("autumn_variant");
}
for (int i = 0; i < iMaxNumLights; ++i)
if (LightsOn[i] || LightsOff[i]) // Ra: zlikwidowałem wymóg istnienia obu
iNumLights = i + 1;
std::string token;
do {
token = parser->getToken<std::string>();
if( token == "lights" ) {
auto i{ 0 };
while( ( false == ( token = parser->getToken<std::string>() ).empty() )
&& ( false == is_keyword( token ) ) ) {
if( i < iNumLights ) {
// stan światła jest liczbą z ułamkiem
LightSet( i, std::stof( token ) );
}
++i;
}
}
if( token == "lightcolors" ) {
auto i{ 0 };
while( ( false == ( token = parser->getToken<std::string>() ).empty() )
&& ( false == is_keyword( token ) ) ) {
if( ( i < iNumLights )
&& ( token != "-1" ) ) { // -1 leaves the default color intact
auto const lightcolor { std::stoi( token, 0, 16 ) };
m_lightcolors[i] = {
( ( lightcolor >> 16 ) & 0xff ) / 255.f,
( ( lightcolor >> 8 ) & 0xff ) / 255.f,
( ( lightcolor ) & 0xff ) / 255.f };
}
++i;
}
}
if( token == "angles" ) {
parser->getTokens( 3 );
*parser
>> vAngle[ 0 ]
>> vAngle[ 1 ]
>> vAngle[ 2 ];
}
if( token == "notransition" ) {
m_transition = false;
}
} while( ( false == token.empty() )
&& ( token != "endmodel" ) );
return true;
}
std::shared_ptr<TAnimContainer> TAnimModel::AddContainer(std::string const &Name)
{ // dodanie sterowania submodelem dla egzemplarza
if (!pModel)
return nullptr;
TSubModel *tsb = pModel->GetFromName(Name);
if (tsb)
{
auto tmp = std::make_shared<TAnimContainer>();
tmp->Init(tsb);
m_animlist.push_back(tmp);
return tmp;
}
return nullptr;
}
std::shared_ptr<TAnimContainer> TAnimModel::GetContainer(std::string const &Name)
{ // szukanie/dodanie sterowania submodelem dla egzemplarza
if (true == Name.empty())
return (!m_animlist.empty()) ? m_animlist.front() : nullptr; // pobranie pierwszego (dla obrotnicy)
for (auto entry : m_animlist) {
if (entry->NameGet() == Name)
return entry;
}
return AddContainer(Name);
}
// przeliczenie animacji - jednorazowo na klatkę
void TAnimModel::RaAnimate( unsigned int const Framestamp ) {
if( Framestamp == m_framestamp ) { return; }
auto const timedelta { Timer::GetDeltaTime() };
// interpretacja ułamka zależnie od typu
// case ls_Off: ustalenie czasu migotania, t<1s (f>1Hz), np. 0.1 => t=0.1 (f=10Hz)
// case ls_On: ustalenie wypełnienia ułamkiem, np. 1.25 => zapalony przez 1/4 okresu
// case ls_Blink: ustalenie częstotliwości migotania, f<1Hz (t>1s), np. 2.2 => f=0.2Hz (t=5s)
float modeintegral, modefractional;
for( int idx = 0; idx < iNumLights; ++idx ) {
modefractional = std::modf( std::abs( lsLights[ idx ] ), &modeintegral );
if( modeintegral >= ls_Dark ) {
// light threshold modes don't use timers
continue;
}
auto const mode { static_cast<int>( modeintegral ) };
auto &opacity { m_lightopacities[ idx ] };
auto &timer { m_lighttimers[ idx ] };
if( ( modeintegral < ls_Blink ) && ( modefractional < 0.01f ) ) {
// simple flip modes
auto const transitiontime { ( m_transition ? std::min( 0.65f, std::min( fOnTime, fOffTime ) * 0.45f ) : 0.01f ) };
switch( mode ) {
case ls_Off: {
// reduce to zero
timer = std::max<float>( 0.f, timer - timedelta );
break;
}
case ls_On: {
// increase to max value
timer = std::min<float>( transitiontime, timer + timedelta );
break;
}
default: {
break;
}
}
opacity = timer / transitiontime;
}
else {
// blink modes
auto const ontime { (
( mode == ls_Blink ) ? ( ( modefractional < 0.01f ) ? fOnTime : ( 1.f / modefractional ) * 0.5f ) :
( mode == ls_Off ) ? modefractional * 0.5f :
( mode == ls_On ) ? modefractional * ( fOnTime + fOffTime ) :
fOnTime ) }; // fallback
auto const offtime { (
( mode == ls_Blink ) ? ( ( modefractional < 0.01f ) ? fOffTime : ontime ) :
( mode == ls_Off ) ? ontime :
( mode == ls_On ) ? ( fOnTime + fOffTime ) - ontime :
fOffTime ) }; // fallback
auto const transitiontime { ( m_transition ? std::min(0.65f, std::min( ontime, offtime ) * 0.45f ) : 0.01f ) };
timer = fmod(timer + timedelta * ( lsLights[ idx ] > 0.f ? 1.f : -1.f ), ontime + offtime);
// set opacity depending on blink stage
if( timer < ontime ) {
// blink on
opacity = clamp( timer / transitiontime, 0.f, 1.f );
}
else {
// blink off
opacity = 1.f - clamp( ( timer - ontime ) / transitiontime, 0.f, 1.f );
}
}
}
// Ra 2F1I: to by można pomijać dla modeli bez animacji, których jest większość
for (auto entry : m_animlist) {
if (!entry->evDone) // jeśli jest bez eventu
entry->UpdateModel(); // przeliczenie animacji każdego submodelu
}
m_framestamp = Framestamp;
}
// aktualizujemy submodele w zaleznosci od aktualnej porty roku
void TAnimModel::on_season_update() {
if (this->sm_winter_variant != nullptr) // pokazujemy wariant zimowy
this->sm_winter_variant->SetVisibilityLevel(Global.Season == "winter:" ? 1 : 0, true, false);
if (this->sm_spring_variant != nullptr) // pokazujemy wariant wiosenny
this->sm_spring_variant->SetVisibilityLevel(Global.Season == "spring:" ? 1 : 0, true, false);
if (this->sm_summer_variant != nullptr) // pokazujemy wariant letni
this->sm_summer_variant->SetVisibilityLevel(Global.Season == "summer:" ? 1 : 0, true, false);
if (this->sm_autumn_variant != nullptr) // pokazujemy wariant jesienny
this->sm_autumn_variant->SetVisibilityLevel(Global.Season == "autumn:" ? 1 : 0, true, false);
}
void TAnimModel::RaPrepare()
{ // ustawia światła i animacje we wzorcu modelu przed renderowaniem egzemplarza
bool state; // stan światła
if (Global.UpdateMaterials)
on_season_update();
for (int i = 0; i < iNumLights; ++i)
{
auto const lightmode { static_cast<int>( std::abs( lsLights[ i ] ) ) };
switch( lightmode ) {
case ls_On:
case ls_Off:
case ls_Blink: {
if (LightsOn[i]) {
LightsOn[i]->iVisible = ( m_lightopacities[i] > 0.f );
LightsOn[i]->SetVisibilityLevel( m_lightopacities[i], true, false );
}
if (LightsOff[i]) {
LightsOff[i]->iVisible = ( m_lightopacities[i] < 1.f );
LightsOff[i]->SetVisibilityLevel( 1.f, true, false );
}
break;
}
case ls_Dark: {
// zapalone, gdy ciemno
state = (
Global.fLuminance - std::max( 0.f, Global.Overcast - 1.f ) <= (
lsLights[ i ] == static_cast<float>( ls_Dark ) ?
DefaultDarkThresholdLevel :
( lsLights[ i ] - static_cast<float>( ls_Dark ) ) ) );
break;
}
case ls_Home: {
// like ls_dark but off late at night
auto const simulationhour { simulation::Time.data().wHour };
state = (
Global.fLuminance - std::max( 0.f, Global.Overcast - 1.f ) <= (
lsLights[ i ] == static_cast<float>( ls_Home ) ?
DefaultDarkThresholdLevel :
( lsLights[ i ] - static_cast<float>( ls_Home ) ) ) );
// force the lights off between 1-5am
state = state && (( simulationhour < 1 ) || ( simulationhour >= 5 ));
break;
}
default: {
break;
}
}
if( lightmode >= ls_Dark ) {
// crude as hell but for test will do :x
if (LightsOn[i]) {
LightsOn[i]->iVisible = state;
// TODO: set visibility for the entire submodel's children as well
LightsOn[i]->fVisible = m_lightopacities[i];
}
if (LightsOff[i])
LightsOff[i]->iVisible = !state;
}
// potentially modify freespot colors
if( LightsOn[i] ) {
LightsOn[i]->SetDiffuseOverride( m_lightcolors[i], true);
}
}
TSubModel::iInstance = reinterpret_cast<std::uintptr_t>( this ); //żeby nie robić cudzych animacji
TSubModel::pasText = &asText; // przekazanie tekstu do wyświetlacza (!!!! do przemyślenia)
for (auto entry : m_animlist) {
entry->PrepareModel();
}
}
int TAnimModel::Flags()
{ // informacja dla TGround, czy ma być w Render, RenderAlpha, czy RenderMixed
int i = pModel ? pModel->Flags() : 0; // pobranie flag całego modelu
if( m_materialdata.replacable_skins[ 1 ] > 0 ) // jeśli ma wymienną teksturę 0
i |= (i & 0x01010001) * ((m_materialdata.textures_alpha & 1) ? 0x20 : 0x10);
return i;
}
//---------------------------------------------------------------------------
int TAnimModel::TerrainCount()
{ // zliczanie kwadratów kilometrowych (główna linia po Next) do tworznia tablicy
return pModel ? pModel->TerrainCount() : 0;
}
TSubModel * TAnimModel::TerrainSquare(int n)
{ // pobieranie wskaźników do pierwszego submodelu
return pModel ? pModel->TerrainSquare(n) : 0;
}
//---------------------------------------------------------------------------
void TAnimModel::LightSet(int const n, float const v)
{ // ustawienie światła (n) na wartość (v)
if( n >= iMaxNumLights ) {
return; // przekroczony zakres
}
lsLights[ n ] = v;
}
std::optional<std::tuple<float, float, std::optional<glm::vec3>> > TAnimModel::LightGet(const int n)
{
if (n >= iMaxNumLights)
return std::nullopt;
if (!LightsOn[n] && !LightsOff[n])
return std::nullopt;
std::optional<glm::vec3> color;
if (m_lightcolors[n].r >= 0.0f)
color.emplace(m_lightcolors[n]);
if (!color && LightsOn[n])
color = LightsOn[n]->GetDiffuse();
return std::make_tuple(lsLights[n], m_lightopacities[n], color);
}
void TAnimModel::SkinSet( int const Index, material_handle const Material ) {
m_materialdata.replacable_skins[ clamp( Index, 1, 4 ) ] = Material;
}
void TAnimModel::AnimUpdate(double dt)
{ // wykonanie zakolejkowanych animacji, nawet gdy modele nie są aktualnie wyświetlane
acAnimList.remove_if([](std::weak_ptr<TAnimContainer> ptr)
{
std::shared_ptr<TAnimContainer> container = ptr.lock();
if (!container)
return true;
container->UpdateModel(); // na razie bez usuwania z listy, bo głównie obrotnica na nią wchodzi
return false;
});
}
// radius() subclass details, calculates node's bounding radius
float
TAnimModel::radius_() {
return (
pModel ?
pModel->bounding_radius() :
0.f );
}
// serialize() subclass details, sends content of the subclass to provided stream
void
TAnimModel::serialize_( std::ostream &Output ) const {
// TODO: implement
}
// deserialize() subclass details, restores content of the subclass from provided stream
void
TAnimModel::deserialize_( std::istream &Input ) {
// TODO: implement
}
// export() subclass details, sends basic content of the class in legacy (text) format to provided stream
void
TAnimModel::export_as_text_( std::ostream &Output ) const {
// header
Output << "model ";
// location and rotation
Output << std::fixed << std::setprecision(3) // ustawienie dokładnie 3 cyfr po przecinku
<< location().x << ' '
<< location().y << ' '
<< location().z << ' ';
Output
<< "0 " ;
// 3d shape
auto modelfile { (
pModel ?
pModel->NameGet() + ".t3d" : // rainsted requires model file names to include an extension
"none" ) };
if( modelfile.find( szModelPath ) == 0 ) {
// don't include 'models/' in the path
modelfile.erase( 0, std::string{ szModelPath }.size() );
}
Output << modelfile << ' ';
// texture
auto texturefile { (
m_materialdata.replacable_skins[ 1 ] != null_handle ?
GfxRenderer->Material( m_materialdata.replacable_skins[ 1 ] )->GetName() :
"none" ) };
if( texturefile.find( szTexturePath ) == 0 ) {
// don't include 'textures/' in the path
texturefile.erase( 0, std::string{ szTexturePath }.size() );
}
if( contains( texturefile, ' ' ) ) {
Output << "\"" << texturefile << "\"" << ' ';
}
else {
Output << texturefile << ' ';
}
// light submodels activation configuration
if( iNumLights > 0 ) {
Output << "lights ";
for( int lightidx = 0; lightidx < iNumLights; ++lightidx ) {
Output << lsLights[ lightidx ] << ' ';
}
}
// potential light transition switch
if( false == m_transition ) {
Output << "notransition" << ' ';
}
// footer
Output << "angles "
<< vAngle.x << ' '
<< vAngle.y << ' '
<< vAngle.z << ' ';
// footer
Output
<< "endmodel"
<< "\n";
}
//---------------------------------------------------------------------------

201
model/AnimModel.h Normal file
View File

@@ -0,0 +1,201 @@
/*
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/.
*/
/*
MaSzyna EU07 locomotive simulator
Copyright (C) 2001-2004 Marcin Wozniak and others
*/
#pragma once
#include "Classes.h"
#include "dumb3d.h"
#include "Float3d.h"
#include "Model3d.h"
#include "DynObj.h"
#include "scenenode.h"
const int iMaxNumLights = 8;
float const DefaultDarkThresholdLevel { 0.325f };
// typy stanu świateł
enum TLightState
{
ls_Off = 0, // zgaszone
ls_On = 1, // zapalone
ls_Blink = 2, // migające
ls_Dark = 3, // Ra: zapalajce się automatycznie, gdy zrobi się ciemno
ls_Home = 4, // like ls_dark but off late at night
ls_winter = 5 // turned on when its winter
};
class TAnimVocaloidFrame
{ // ramka animacji typu Vocaloid Motion Data z programu MikuMikuDance
public:
char cBone[15]; // nazwa kości, może być po japońsku
int iFrame; // numer ramki
float3 f3Vector; // przemieszczenie
float4 qAngle; // kwaternion obrotu
char cBezier[64]; // krzywe Béziera do interpolacji dla x,y,z i obrotu
};
class basic_event;
class TAnimContainer : std::enable_shared_from_this<TAnimContainer>
{ // opakowanie submodelu, określające animację egzemplarza - obsługiwane jako lista
friend TAnimModel;
private:
Math3D::vector3 vRotateAngles; // dla obrotów Eulera
Math3D::vector3 vDesiredAngles;
double fRotateSpeed;
Math3D::vector3 vTranslation;
Math3D::vector3 vTranslateTo;
double fTranslateSpeed; // może tu dać wektor?
float4 qCurrent; // aktualny interpolowany
float4 qStart; // pozycja początkowa (0 dla interpolacji)
float4 qDesired; // pozycja końcowa (1 dla interpolacji)
float fAngleCurrent; // parametr interpolacyjny: 0=start, 1=docelowy
float fAngleSpeed; // zmiana parametru interpolacji w sekundach
public:
TSubModel *pSubModel;
private:
std::shared_ptr<float4x4> mAnim; // macierz do animacji kwaternionowych
// dla kinematyki odwróconej używane są kwaterniony
float fLength; // długość kości dla IK
int iAnim; // animacja: +1-obrót Eulera, +2-przesuw, +4-obrót kwaternionem, +8-IK
//+0x80000000: animacja z eventem, wykonywana poza wyświetlaniem
//+0x100: pierwszy stopień IK - obrócić w stronę pierwszego potomnego (dziecka)
//+0x200: drugi stopień IK - dostosować do pozycji potomnego potomnego (wnuka)
basic_event *evDone; // ewent wykonywany po zakończeniu animacji, np. zapór, obrotnicy
public:
// wyświetlania
TAnimContainer();
bool Init(TSubModel *pNewSubModel);
inline
std::string NameGet() {
return (pSubModel ? pSubModel->pName : ""); };
void SetRotateAnim( Math3D::vector3 vNewRotateAngles, double fNewRotateSpeed);
void SetTranslateAnim( Math3D::vector3 vNewTranslate, double fNewSpeed);
void AnimSetVMD(double fNewSpeed);
void PrepareModel();
void UpdateModel();
void UpdateModelIK();
bool InMovement(); // czy w trakcie animacji?
inline
double AngleGet() {
return vRotateAngles.z; }; // jednak ostatnia, T3D ma inny układ
inline
Math3D::vector3 TransGet() {
return Math3D::vector3(-vTranslation.x, vTranslation.z, vTranslation.y); }; // zmiana, bo T3D ma inny układ
inline
void WillBeAnimated() {
if (pSubModel)
pSubModel->WillBeAnimated(); };
void EventAssign(basic_event *ev);
inline
basic_event * Event() {
return evDone; };
};
// opakowanie modelu, określające stan egzemplarza
class TAnimModel : public scene::basic_node {
friend opengl_renderer;
friend opengl33_renderer;
friend itemproperties_panel;
public:
// constructors
explicit TAnimModel( scene::node_data const &Nodedata );
// methods
static void AnimUpdate( double dt );
bool Init(std::string const &asName, std::string const &asReplacableTexture);
bool Load(cParser *parser, bool ter = false);
std::shared_ptr<TAnimContainer> AddContainer(std::string const &Name);
std::shared_ptr<TAnimContainer> GetContainer(std::string const &Name = "");
void LightSet( int const n, float const v );
void SkinSet( int const Index, material_handle const Material );
std::optional<std::tuple<float, float, std::optional<glm::vec3> > > LightGet( int const n );
int TerrainCount();
TSubModel * TerrainSquare(int n);
int Flags();
void on_season_update();
inline
material_data const *
Material() const {
return &m_materialdata; }
inline
TModel3d *
Model() const {
return pModel; }
inline
void
Angles( glm::vec3 const &Angles ) {
vAngle = Angles; }
inline
glm::vec3
Angles() const {
return vAngle; }
// members
std::list<std::shared_ptr<TAnimContainer>> m_animlist;
// lista animacji z eventem, które muszą być przeliczane również bez wyświetlania
static std::list<std::weak_ptr<TAnimContainer>> acAnimList;
public:
// methods
void RaPrepare(); // ustawienie animacji egzemplarza na wzorcu
void RaAnimate( unsigned int const Framestamp ); // przeliczenie animacji egzemplarza
// radius() subclass details, calculates node's bounding radius
float radius_();
// serialize() subclass details, sends content of the subclass to provided stream
void serialize_( std::ostream &Output ) const;
// deserialize() subclass details, restores content of the subclass from provided stream
void deserialize_( std::istream &Input );
// export() subclass details, sends basic content of the class in legacy (text) format to provided stream
void export_as_text_( std::ostream &Output ) const;
// checks whether provided token is a legacy (text) format keyword
bool is_keyword( std::string const &Token ) const;
// members
std::shared_ptr<TAnimContainer> pRoot; // pojemniki sterujące, tylko dla aniomowanych submodeli
TModel3d *pModel { nullptr };
glm::vec3 vAngle; // bazowe obroty egzemplarza względem osi
material_data m_materialdata;
std::string asText; // tekst dla wyświetlacza znakowego
// TODO: wrap into a light state struct, remove fixed element count
int iNumLights { 0 };
std::array<TSubModel *, iMaxNumLights> LightsOn {}; // Ra: te wskaźniki powinny być w ramach TModel3d
std::array<TSubModel *, iMaxNumLights> LightsOff {};
TSubModel *sm_winter_variant {}; // submodel zimowego wariantu
TSubModel *sm_spring_variant {}; // submodel wiosennego wariantu
TSubModel *sm_summer_variant {}; // submodel letniego wariantu
TSubModel *sm_autumn_variant {}; // submodel jesiennego wariantu
std::array<float, iMaxNumLights> lsLights {}; // ls_Off
std::array<glm::vec3, iMaxNumLights> m_lightcolors; // -1 in constructor
std::array<float, iMaxNumLights> m_lighttimers {};
std::array<float, iMaxNumLights> m_lightopacities; // {1} in constructor
float fOnTime { 1.f / 2 };// { 60.f / 45.f / 2 };
float fOffTime { 1.f / 2 };// { 60.f / 45.f / 2 }; // były stałymi, teraz mogą być zmienne dla każdego egzemplarza
// float fTransitionTime { fOnTime * 0.9f }; // time
bool m_transition { true }; // smooth transition between light states
unsigned int m_framestamp { 0 }; // id of last rendered gfx frame
};
class instance_table : public basic_table<TAnimModel> {
};
//---------------------------------------------------------------------------

159
model/MdlMngr.cpp Normal file
View File

@@ -0,0 +1,159 @@
/*
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/.
*/
/*
MaSzyna EU07 locomotive simulator
Copyright (C) 2001-2004 Marcin Wozniak and others
*/
#include "stdafx.h"
#include "MdlMngr.h"
#include "Model3d.h"
#include "Globals.h"
#include "Logs.h"
#include "utilities.h"
// wczytanie modelu do kontenerka
TModel3d *
TMdlContainer::LoadModel(std::string const &Name, bool const Dynamic) {
Model = std::make_shared<TModel3d>();
if( true == Model->LoadFromFile( Name, Dynamic ) ) {
m_name = Name;
return Model.get();
}
else {
m_name.clear();
Model = nullptr;
return nullptr;
}
};
TModelsManager::modelcontainer_sequence TModelsManager::m_models { 1, TMdlContainer{} };
TModelsManager::stringmodelcontainerindex_map TModelsManager::m_modelsmap;
// wczytanie modelu do tablicy
TModel3d *
TModelsManager::LoadModel(std::string const &Name, std::string const &virtualName, bool dynamic) {
m_models.emplace_back();
auto model = m_models.back().LoadModel( Name, dynamic );
if( model != nullptr ) {
m_modelsmap.emplace( virtualName, m_models.size() - 1 );
}
else {
m_models.pop_back();
m_modelsmap.emplace( virtualName, null_handle );
}
return model;
}
TModel3d *
TModelsManager::GetModel(std::string const &Name, bool const Dynamic, bool const Logerrors, int uid )
{ // model może być we wpisie "node...model" albo "node...dynamic", a także być dodatkowym w dynamic
// (kabina, wnętrze, ładunek)
// dla "node...dynamic" mamy podaną ścieżkę w "\dynamic\" i musi być co najmniej 1 poziom, zwkle
// są 2
// dla "node...model" może być typowy model statyczny ze ścieżką, domyślnie w "\scenery\" albo
// "\models"
// albo może być model z "\dynamic\", jeśli potrzebujemy wstawić auto czy wagon nieruchomo
// - ze ścieżki z której jest wywołany, np. dir="scenery\bud\" albo dir="dynamic\pkp\st44_v1\"
// plus name="model.t3d"
// - z domyślnej ścieżki dla modeli, np. "scenery\" albo "models\" plus name="bud\dombale.t3d"
// (dir="")
// - konkretnie podanej ścieżki np. name="\scenery\bud\dombale.t3d" (dir="")
// wywołania:
// - konwersja wszystkiego do E3D, podawana dokładna ścieżka, tekstury tam, gdzie plik
// - wczytanie kabiny, dokładna ścieżka, tekstury z katalogu modelu
// - wczytanie ładunku, ścieżka dokładna, tekstury z katalogu modelu
// - wczytanie modelu, ścieżka dokładna, tekstury z katalogu modelu
// - wczytanie przedsionków, ścieżka dokładna, tekstury z katalogu modelu
// - wczytanie uproszczonego wnętrza, ścieżka dokładna, tekstury z katalogu modelu
// - niebo animowane, ścieżka brana ze wpisu, tekstury nieokreślone
// - wczytanie modelu animowanego - Init() - sprawdzić
std::string const buftp { Global.asCurrentTexturePath }; // zapamiętanie aktualnej ścieżki do tekstur,
std::string filename { Name };
if( ( false == Dynamic )
&& ( contains( Name, '/' ) ) ) {
// pobieranie tekstur z katalogu, w którym jest model
// when loading vehicles the path is set by the calling routine, so we can skip it here
Global.asCurrentTexturePath += Name;
Global.asCurrentTexturePath.erase( Global.asCurrentTexturePath.rfind( '/' ) + 1 );
}
erase_extension( filename );
filename = ToLower( filename );
std::string postfix;
if (uid != 0)
postfix = "^^" + std::to_string(uid);
// see if we have it in the databank
auto banklookup { find_in_databank( filename + postfix ) };
TModel3d *model { banklookup.second };
if( true == banklookup.first ) {
Global.asCurrentTexturePath = buftp;
return model;
}
// first load attempt, check if it's on disk
std::string disklookup { find_on_disk( filename ) };
if( false == disklookup.empty() ) {
model = LoadModel( disklookup, disklookup + postfix, Dynamic ); // model nie znaleziony, to wczytać
}
else {
// there's nothing matching in the databank nor on the disk, report failure...
if( Logerrors ) {
ErrorLog( "Bad file: failed to locate 3d model file \"" + filename + "\"", logtype::file );
}
// ...and link it with the error model slot
m_modelsmap.emplace( filename + postfix, null_handle );
}
Global.asCurrentTexturePath = buftp; // odtworzenie ścieżki do tekstur
return model; // NULL jeśli błąd
};
std::pair<bool, TModel3d *>
TModelsManager::find_in_databank( std::string const &Name ) {
std::vector<std::string> filenames {
Name,
szModelPath + Name };
for( auto const &filename : filenames ) {
auto const lookup { m_modelsmap.find( filename ) };
if( lookup != m_modelsmap.end() ) {
return { true, m_models[ lookup->second ].Model.get() };
}
}
return { false, nullptr };
}
// checks whether specified file exists. returns name of the located file, or empty string.
std::string
TModelsManager::find_on_disk( std::string const &Name ) {
std::vector<std::string> extensions { { ".e3d" }, { ".t3d" } };
for( auto const &extension : extensions ) {
auto lookup = (
FileExists( Name + extension ) ? Name :
FileExists( szModelPath + Name + extension ) ? szModelPath + Name :
"" );
if( false == lookup.empty() ) {
return lookup;
}
}
return {};
}
//---------------------------------------------------------------------------

42
model/MdlMngr.h Normal file
View File

@@ -0,0 +1,42 @@
/*
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/.
*/
#pragma once
#include "Classes.h"
class TMdlContainer {
friend class TModelsManager;
private:
TModel3d *LoadModel( std::string const &Name, bool const Dynamic );
std::shared_ptr<TModel3d> Model { nullptr };
std::string m_name;
};
// klasa statyczna, nie ma obiektu
class TModelsManager {
public:
// McZapkie: dodalem sciezke, notabene Path!=Patch :)
static TModel3d *GetModel(std::string const &Name, bool const dynamic = false, bool const Logerrors = true , int uid = 0);
private:
// types:
typedef std::deque<TMdlContainer> modelcontainer_sequence;
typedef std::unordered_map<std::string, modelcontainer_sequence::size_type> stringmodelcontainerindex_map;
// members:
static modelcontainer_sequence m_models;
static stringmodelcontainerindex_map m_modelsmap;
// methods:
static TModel3d *LoadModel(std::string const &Name, const std::string &virtualName, bool const Dynamic );
static std::pair<bool, TModel3d *> find_in_databank( std::string const &Name );
// checks whether specified file exists. returns name of the located file, or empty string.
static std::string find_on_disk( std::string const &Name );
};
//---------------------------------------------------------------------------

2533
model/Model3d.cpp Normal file

File diff suppressed because it is too large Load Diff

306
model/Model3d.h Normal file
View File

@@ -0,0 +1,306 @@
/*
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/.
*/
#pragma once
#include "Classes.h"
#include "dumb3d.h"
#include "Float3d.h"
#include "geometrybank.h"
#include "material.h"
#include "gl/query.h"
#define EU07_USE_GEOMETRYINDEXING
// Ra: specjalne typy submodeli, poza tym GL_TRIANGLES itp.
const int TP_ROTATOR = 256;
const int TP_FREESPOTLIGHT = 257;
const int TP_STARS = 258;
const int TP_TEXT = 259;
enum class TAnimType // rodzaj animacji
{
at_None, // brak
at_Rotate, // obrót względem wektora o kąt
at_RotateXYZ, // obrót względem osi o kąty
at_Translate, // przesunięcie
at_SecondsJump, // sekundy z przeskokiem
at_MinutesJump, // minuty z przeskokiem
at_HoursJump, // godziny z przeskokiem 12h/360°
at_Hours24Jump, // godziny z przeskokiem 24h/360°
at_Seconds, // sekundy płynnie
at_Minutes, // minuty płynnie
at_Hours, // godziny płynnie 12h/360°
at_Hours24, // godziny płynnie 24h/360°
at_Billboard, // obrót w pionie do kamery
at_Wind, // ruch pod wpływem wiatru
at_Sky, // animacja nieba
at_Digital, // dziesięciocyfrowy licznik mechaniczny (z cylindrami)
at_DigiClk, // zegar cyfrowy jako licznik na dziesięciościanach
at_Undefined // animacja chwilowo nieokreślona
};
namespace scene {
class shape_node;
}
class TModel3d;
using nameoffset_sequence = std::vector<std::pair<std::string, glm::vec3>>;
class TSubModel
{ // klasa submodelu - pojedyncza siatka, punkt świetlny albo grupa punktów
//m7todo: zrobić normalną serializację
friend opengl_renderer;
friend opengl33_renderer;
friend TModel3d; // temporary workaround. TODO: clean up class content/hierarchy
friend TDynamicObject; // temporary etc
friend scene::shape_node; // temporary etc
public:
enum normalization {
none = 0,
rescale,
normalize
};
struct geometry_data {
gfx::geometry_handle handle;
int vertex_offset;
int vertex_count;
int index_offset;
int index_count;
};
public:
int iNext{ 0 };
int iChild{ 0 };
int eType{ TP_ROTATOR }; // Ra: modele binarne dają więcej możliwości niż mesh złożony z trójkątów
int iName{ -1 }; // numer łańcucha z nazwą submodelu, albo -1 gdy anonimowy
public: // chwilowo
TAnimType b_Anim{ TAnimType::at_None };
bool HasAnyVertexUserData() const;
public:
uint32_t iFlags{ 0x0200 }; // bit 9=1: submodel został utworzony a nie ustawiony na wczytany plik
// flagi informacyjne:
// bit 0: =1 faza rysowania zależy od wymiennej tekstury 0
// bit 1: =1 faza rysowania zależy od wymiennej tekstury 1
// bit 2: =1 faza rysowania zależy od wymiennej tekstury 2
// bit 3: =1 faza rysowania zależy od wymiennej tekstury 3
// bit 4: =1 rysowany w fazie nieprzezroczystych (stała tekstura albo brak)
// bit 5: =1 rysowany w fazie przezroczystych (stała tekstura)
// bit 7: =1 ta sama tekstura, co poprzedni albo nadrzędny
// bit 8: =1 wierzchołki wyświetlane z indeksów
// bit 9: =1 wczytano z pliku tekstowego (jest właścicielem tablic)
// bit 13: =1 wystarczy przesunięcie zamiast mnożenia macierzy (trzy jedynki)
// bit 14: =1 wymagane przechowanie macierzy (animacje)
// bit 15: =1 wymagane przechowanie macierzy (transform niejedynkowy)
union
{ // transform, nie każdy submodel musi mieć
float4x4 *fMatrix = nullptr; // pojedyncza precyzja wystarcza
int iMatrix; // w pliku binarnym jest numer matrycy
};
float transformscalestack { 1.0f }; // tolerancescale used in calculate_indices for whole matrix chain
int iTexture { 0 }; // numer nazwy tekstury, -1 wymienna, 0 brak
float fLight { -1.0f }; // próg jasności światła do zadziałania selfillum
glm::vec4
f4Ambient { 1.0f,1.0f,1.0f,1.0f },
f4Diffuse { 1.0f,1.0f,1.0f,1.0f },
f4Specular { 0.0f,0.0f,0.0f,1.0f },
f4Emision { 1.0f,1.0f,1.0f,1.0f };
glm::vec3 DiffuseOverride { -1.f };
normalization m_normalizenormals { normalization::none }; // indicates vectors need to be normalized due to scaling etc
float diffuseMultiplier {1.0};
float fWireSize { 0.0f }; // nie używane, ale wczytywane
float fSquareMaxDist { 10000.0f * 10000.0f };
float fSquareMinDist { 0.0f };
// McZapkie-050702: parametry dla swiatla:
float fNearAttenStart { 40.0f };
float fNearAttenEnd { 80.0f };
bool bUseNearAtten { false }; // te 3 zmienne okreslaja rysowanie aureoli wokol zrodla swiatla
int iFarAttenDecay { 0 }; // ta zmienna okresla typ zaniku natezenia swiatla (0:brak, 1,2: potega 1/R)
float fFarDecayRadius { 100.0f }; // normalizacja j.w.
float fCosFalloffAngle { 0.5f }; // cosinus kąta stożka pod którym widać światło
float fCosHotspotAngle { 0.3f }; // cosinus kąta stożka pod którym widać aureolę i zwiększone natężenie światła
float fCosViewAngle { 0.0f }; // cos kata pod jakim sie teraz patrzy
bool m_rotation_init_done = false;
public:
TSubModel *Next { nullptr };
TSubModel *Child { nullptr };
public:
material_handle m_material { null_handle }; // numer tekstury, -1 wymienna, 0 brak
bool bWire { false }; // nie używane, ale wczytywane
float Opacity { 1.0f };
float f_Angle { 0.0f };
float3 v_RotateAxis { 0.0f, 0.0f, 0.0f };
float3 v_Angles { 0.0f, 0.0f, 0.0f };
public: // chwilowo
float3 v_TransVector { 0.0f, 0.0f, 0.0f };
geometry_data m_geometry { /*this,*/ { 0, 0 }, 0, 0, 0, 0 };
gfx::vertex_array Vertices;
gfx::userdata_array Userdata;
gfx::index_array Indices;
float m_boundingradius { 0 };
std::uintptr_t iAnimOwner{ 0 }; // roboczy numer egzemplarza, który ustawił animację
TAnimType b_aAnim{ TAnimType::at_None }; // kody animacji oddzielnie, bo zerowane
std::shared_ptr<float4x4> mAnimMatrix; // macierz do animacji kwaternionowych
TSubModel **smLetter{ nullptr }; // wskaźnik na tablicę submdeli do generoania tekstu (docelowo zapisać do E3D)
TSubModel *Parent{ nullptr }; // nadrzędny, np. do wymnażania macierzy
int iVisible { 1 }; // roboczy stan widoczności
float fVisible { 1.f }; // visibility level
std::string m_materialname; // robocza nazwa tekstury do zapisania w pliku binarnym
std::string pName; // robocza nazwa
public:
int SeekFaceNormal( std::vector<unsigned int> const &Masks, int const Startface, unsigned int const Mask, glm::vec3 const &Position, gfx::vertex_array const &Vertices );
void RaAnimation(TAnimType a);
void RaAnimation(glm::mat4 &m, TAnimType a);
// returns true if the submodel is a smoke emitter attachment point, false otherwise
bool is_emitter() const;
public:
static size_t iInstance; // identyfikator egzemplarza, który aktualnie renderuje model
static material_handle const *ReplacableSkinId;
static int iAlpha; // maska bitowa dla danego przebiegu
static float fSquareDist;
static TModel3d *pRoot;
static std::string *pasText; // tekst dla wyświetlacza (!!!! do przemyślenia)
TSubModel() = default;
~TSubModel();
std::pair<int, int> Load(cParser &Parser, /*TModel3d *Model, int Pos,*/ bool dynamic);
void ChildAdd(TSubModel *SubModel);
void NextAdd(TSubModel *SubModel);
TSubModel * NextGet() { return Next; };
TSubModel * ChildGet() { return Child; };
int count_siblings();
int count_children();
// locates submodel mapped with replacable -4
std::tuple<TSubModel *, bool> find_replacable4();
// locates particle emitter submodels and adds them to provided list
void find_smoke_sources( nameoffset_sequence &Sourcelist ) const;
#ifndef EU07_USE_GEOMETRYINDEXING
int TriangleAdd(TModel3d *m, material_handle tex, int tri);
#endif
void SetRotate(float3 vNewRotateAxis, float fNewAngle);
void SetRotateXYZ( Math3D::vector3 vNewAngles);
void SetRotateXYZ(float3 vNewAngles);
void SetTranslate( Math3D::vector3 vNewTransVector);
void SetTranslate(float3 vNewTransVector);
void SetRotateIK1(float3 vNewAngles);
TSubModel * GetFromName( std::string const &search, bool i = true );
inline float4x4 * GetMatrix() { return fMatrix; };
inline float4x4 const * GetMatrix() const { return fMatrix; };
// returns offset vector from root
glm::vec3 offset( float const Geometrytestoffsetthreshold = 0.f ) const;
inline void Hide() { iVisible = 0; };
void create_geometry( std::size_t &Indexoffset, std::size_t &Vertexoffset, gfx::geometrybank_handle const &Bank );
uint32_t FlagsCheck();
void WillBeAnimated() {
iFlags |= 0x4000; };
void InitialRotate(bool doit);
void BinInit(TSubModel *s, float4x4 *m, std::vector<std::string> *t, std::vector<std::string> *n, bool dynamic);
static void ReplacableSet(material_handle const *r, int a) {
ReplacableSkinId = r;
iAlpha = a; };
void Name_Material( std::string const &Name );
void Name( std::string const &Name );
// Ra: funkcje do budowania terenu z E3D
uint32_t Flags() const { return iFlags; };
void UnFlagNext() { iFlags &= 0x00FFFFFF; };
void ColorsSet( glm::vec3 const &Ambient, glm::vec3 const &Diffuse, glm::vec3 const &Specular );
// sets rgb components of diffuse color override to specified value
void SetDiffuseOverride( glm::vec3 const &Color, bool const Includechildren = false, bool const Includesiblings = false );
// gets rgb components of any freespot diffuse color (searches also in children)
std::optional<glm::vec3> GetDiffuse( float Includesiblings = false );
// sets visibility level (alpha component) to specified value
void SetVisibilityLevel( float const Level, bool const Includechildren = false, bool const Includesiblings = false );
// sets light level (alpha component of illumination color) to specified value
void SetLightLevel( glm::vec4 const &Level, bool const Includechildren = false, bool const Includesiblings = false );
// sets activation threshold of self-illumination to specitied value
void SetSelfIllum( float const Threshold, bool const Includechildren = false, bool const Includesiblings = false );
inline float3 Translation1Get() {
return fMatrix ? *(fMatrix->TranslationGet()) + v_TransVector : v_TransVector; }
inline float3 Translation2Get() {
return *(fMatrix->TranslationGet()) + Child->Translation1Get(); }
material_handle GetMaterial() const {
return m_material; }
void ParentMatrix(float4x4 *m) const;
void ReplaceMatrix(const glm::mat4 &mat);
void ReplaceMaterial(const std::string &name);
float MaxY( float4x4 const &m );
std::shared_ptr<std::vector<glm::vec2>> screen_touch_list; // for python screen touching
std::optional<gl::query> occlusion_query;
glm::mat4 future_transform;
void deserialize(std::istream&);
void serialize(std::ostream&,
std::vector<TSubModel*>&,
std::vector<std::string>&,
std::vector<std::string>&,
std::vector<float4x4>&);
void serialize_geometry( std::ostream &Output, bool const Packed, bool const Indexed, bool const UserData ) const;
int index_size() const;
void serialize_indices( std::ostream &Output, int const Size ) const;
// places contained geometry in provided ground node
};
class TModel3d
{
friend opengl_renderer;
friend opengl33_renderer;
public:
TSubModel *Root { nullptr }; // drzewo submodeli
uint32_t iFlags { 0 }; // Ra: czy submodele mają przezroczyste tekstury
public: // Ra: tymczasowo
gfx::geometrybank_handle m_geometrybank;
int m_indexcount { 0 };
int m_vertexcount { 0 }; // ilość wierzchołków
private:
std::vector<std::string> Textures; // nazwy tekstur
std::vector<std::string> Names; // nazwy submodeli
std::vector<float4x4> Matrices; // submodel matrices
int iSubModelsCount { 0 }; // Ra: używane do tworzenia binarnych
std::string asBinary; // nazwa pod którą zapisać model binarny
std::string m_filename;
nameoffset_sequence m_smokesources; // list of particle sources defined in the model
public:
TModel3d() = default;
~TModel3d();
float bounding_radius() const {
return (
Root ?
Root->m_boundingradius :
0.f ); }
inline TSubModel * GetSMRoot() { return (Root); };
TSubModel * GetFromName(std::string const &Name) const;
TSubModel * AddToNamed(const char *Name, TSubModel *SubModel);
nameoffset_sequence const & find_smoke_sources();
void AddTo(TSubModel *tmp, TSubModel *SubModel);
void LoadFromTextFile(std::string const &FileName, bool dynamic);
void LoadFromBinFile(std::string const &FileName, bool dynamic);
bool LoadFromFile(std::string const &FileName, bool dynamic);
TSubModel *AppendChildFromGeometry(const std::string &name, const std::string &parent, const gfx::vertex_array &vertices, const gfx::index_array &indices);
void SaveToBinFile(std::string const &FileName);
uint32_t Flags() const { return iFlags; };
void Init();
std::string NameGet() const { return m_filename; };
nameoffset_sequence const & smoke_sources() const {
return m_smokesources; }
int TerrainCount() const;
TSubModel * TerrainSquare(int n);
void deserialize(std::istream &s, size_t size, bool dynamic);
};
//---------------------------------------------------------------------------

89
model/ResourceManager.cpp Normal file
View File

@@ -0,0 +1,89 @@
/*
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 "ResourceManager.h"
#include "Logs.h"
ResourceManager::Resources ResourceManager::_resources;
double ResourceManager::_expiry = 5.0f;
double ResourceManager::_lastUpdate = 0.0f;
double ResourceManager::_lastReport = 0.0f;
void ResourceManager::Register(Resource *resource)
{
_resources.emplace_back(resource);
};
void ResourceManager::Unregister(Resource *resource)
{
Resources::iterator iter = std::find(_resources.begin(), _resources.end(), resource);
if (iter != _resources.end())
_resources.erase(iter);
resource->Release();
};
class ResourceExpired
{
public:
ResourceExpired(double time) : _time(time){};
bool operator()(Resource *resource)
{
return (resource->GetLastUsage() < _time);
}
private:
double _time;
};
void ResourceManager::Sweep(double currentTime)
{
if (currentTime - _lastUpdate < _expiry)
return;
Resources::iterator begin = std::remove_if(_resources.begin(), _resources.end(),
ResourceExpired(currentTime - _expiry));
#ifdef RESOURCE_REPORTING
if (begin != _resources.end())
WriteLog("Releasing resources");
#endif
for (Resources::iterator iter = begin; iter != _resources.end(); ++iter)
(*iter)->Release();
#ifdef RESOURCE_REPORTING
if (begin != _resources.end())
{
std::ostringstream msg;
msg << "Released " << (_resources.end() - begin) << " resources";
WriteLog(msg.str().c_str());
};
#endif
_resources.erase(begin, _resources.end());
#ifdef RESOURCE_REPORTING
if (currentTime - _lastReport > 30.0f)
{
std::ostringstream msg;
msg << "Resources count: " << _resources.size();
WriteLog(msg.str().c_str());
_lastReport = currentTime;
};
#endif
_lastUpdate = currentTime;
};
*/

81
model/ResourceManager.h Normal file
View File

@@ -0,0 +1,81 @@
/*
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/.
*/
#pragma once
enum class resource_state {
none,
loading,
good,
failed
};
using resource_timestamp = std::chrono::steady_clock::time_point;
// takes containers providing access to specific element through operator[]
// with elements of std::pair<resource *, resource_timestamp>
// the element should provide method release() freeing resources owned by the element
template <class Container_>
class garbage_collector {
public:
// constructor:
garbage_collector( Container_ &Container, unsigned int const Secondstolive, std::size_t const Sweepsize, std::string const Resourcename = "resource" ) :
m_unusedresourcetimetolive{ std::chrono::seconds{ Secondstolive } },
m_unusedresourcesweepsize{ Sweepsize },
m_resourcename{ Resourcename },
m_container{ Container }
{}
// methods:
// performs resource sweep. returns: number of released resources
int
sweep() {
m_resourcetimestamp = std::chrono::steady_clock::now();
// garbage collection sweep is limited to a number of records per call, to reduce impact on framerate
auto const sweeplastindex =
std::min(
m_resourcesweepindex + m_unusedresourcesweepsize,
m_container.size() );
auto const blanktimestamp { std::chrono::steady_clock::time_point() };
int releasecount{ 0 };
for( auto resourceindex = m_resourcesweepindex; resourceindex < sweeplastindex; ++resourceindex ) {
if( ( m_container[ resourceindex ].second != blanktimestamp )
&& ( m_resourcetimestamp - m_container[ resourceindex ].second > m_unusedresourcetimetolive ) ) {
m_container[ resourceindex ].first->release();
m_container[ resourceindex ].second = blanktimestamp;
++releasecount;
}
}
/*
if( releasecount ) {
WriteLog( "Resource garbage sweep released " + std::to_string( releasecount ) + " " + ( releasecount == 1 ? m_resourcename : m_resourcename + "s" ) );
}
*/
m_resourcesweepindex = (
m_resourcesweepindex + m_unusedresourcesweepsize >= m_container.size() ?
0 : // if the next sweep chunk is beyond actual data, so start anew
m_resourcesweepindex + m_unusedresourcesweepsize );
return releasecount; }
std::chrono::steady_clock::time_point
timestamp() const {
return m_resourcetimestamp; }
private:
// members:
std::chrono::nanoseconds const m_unusedresourcetimetolive;
typename Container_::size_type const m_unusedresourcesweepsize;
std::string const m_resourcename;
Container_ &m_container;
typename Container_::size_type m_resourcesweepindex { 0 };
std::chrono::steady_clock::time_point m_resourcetimestamp { std::chrono::steady_clock::now() };
};

1508
model/Texture.cpp Normal file

File diff suppressed because it is too large Load Diff

254
model/Texture.h Normal file
View File

@@ -0,0 +1,254 @@
/*
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/.
*/
#pragma once
#include <istream>
#include "winheaders.h"
#include <string>
#include "ResourceManager.h"
#include "gl/ubo.h"
#include "interfaces/ITexture.h"
struct opengl_texture : public ITexture {
static DDSURFACEDESC2 deserialize_ddsd(std::istream&);
static DDCOLORKEY deserialize_ddck(std::istream&);
static DDPIXELFORMAT deserialize_ddpf(std::istream&);
static DDSCAPS2 deserialize_ddscaps(std::istream&);
// constructors
opengl_texture() = default;
// methods
void
load();
bool
bind( size_t unit );
static void
unbind( size_t unit );
virtual bool
create( bool const Static = false ) override;
// releases resources allocated on the opengl end, storing local copy if requested
void
release() override;
void
make_stub() override;
void
alloc_rendertarget( GLint format, GLint components, int width, int height, int layers = 1, int samples = 1, GLint wrap = GL_CLAMP_TO_EDGE );
virtual void
set_components_hint( GLint hint ) override;
static void
reset_unit_cache();
virtual int
get_width() const override {
return data_width; }
virtual
int
get_height() const override {
return data_height; }
virtual
size_t
get_id() const override {
return id; }
virtual
bool
is_stub() const override {
return is_texstub; }
virtual
bool
get_has_alpha() const override {
return has_alpha; }
virtual
bool
get_is_ready() const override {
return is_ready; }
virtual
std::string_view
get_traits() const override {
return traits; }
virtual
std::string_view
get_name() const override {
return name; }
virtual
std::string_view
get_type() const override {
return type; }
virtual void make_from_memory(size_t width, size_t height, const uint8_t *data) override;
virtual void update_from_memory(size_t width, size_t height, const uint8_t *data) override;
// members
GLuint id{ (GLuint)-1 }; // associated GL resource
bool has_alpha{ false }; // indicates the texture has alpha channel
bool is_ready{ false }; // indicates the texture was processed and is ready for use
std::string traits; // requested texture attributes: wrapping modes etc
std::string name; // name of the texture source file
std::string type; // type of the texture source file
std::size_t size{ 0 }; // size of the texture data, in kb
GLint components_hint = 0; // components that material wants
GLenum target = GL_TEXTURE_2D;
static std::array<GLuint, gl::MAX_TEXTURES + gl::HELPER_TEXTURES> units;
static GLint m_activeunit;
public:
// methods
void make_request();
void load_PNG();
void load_DDS();
void load_KTX();
void load_TEX();
void load_STBI();
void load_TGA();
void set_filtering() const;
void downsize( GLuint const Format );
void flip_vertical();
void gles_match_internalformat(GLuint format);
// members
bool is_static = false; // is excluded from garbage collection
bool is_rendertarget = false; // is used as postfx rendertarget, without loaded data
int samples = 1;
int layers = 1;
bool is_texstub = false; // for make_from_memory internal_src: functionality
std::vector<unsigned char> data; // texture data (stored GL-style, bottom-left origin)
resource_state data_state{ resource_state::none }; // current state of texture data
int data_width{ 0 },
data_height{ 0 },
data_mapcount{ 0 };
GLint data_format{ 0 },
data_components{ 0 };
GLint data_type = GL_UNSIGNED_BYTE;
GLint wrap_mode_s = GL_REPEAT;
GLint wrap_mode_t = GL_REPEAT;
/*
std::atomic<bool> is_loaded{ false }; // indicates the texture data was loaded and can be processed
std::atomic<bool> is_good{ false }; // indicates the texture data was retrieved without errors
*/
static std::unordered_map<GLint, int> precompressed_formats;
static std::unordered_map<GLint, GLint> drivercompressed_formats;
static std::unordered_map<GLint, std::unordered_map<GLint, GLint>> mapping;
};
class texture_manager {
public:
texture_manager();
~texture_manager() { delete_textures(); }
// activates specified texture unit
void
unit( GLint const Textureunit );
// creates texture object out of data stored in specified file
texture_handle
create( std::string Filename, bool const Loadnow = true, GLint Formathint = GL_SRGB_ALPHA );
// binds specified texture to specified texture unit
void
bind( std::size_t const Unit, texture_handle const Texture );
opengl_texture &
mark_as_used( texture_handle const Texture );
// provides direct access to specified texture object
opengl_texture &
texture( texture_handle const Texture ) const { return *(m_textures[ Texture ].first); }
// performs a resource sweep
void
update();
// debug performance string
std::string
info() const;
private:
// types:
typedef std::pair<
opengl_texture *,
resource_timestamp > texturetimepoint_pair;
typedef std::vector< texturetimepoint_pair > texturetimepointpair_sequence;
typedef std::unordered_map<std::string, std::size_t> index_map;
// methods:
// checks whether specified texture is in the texture bank. returns texture id, or npos.
texture_handle
find_in_databank( std::string const &Texturename ) const;
public:
// checks whether specified file exists. returns name of the located file, or empty string.
static std::pair<std::string, std::string>
find_on_disk( std::string const &Texturename );
private:
void
delete_textures();
// members:
texture_handle const npos { 0 }; // should be -1, but the rest of the code uses -1 for something else
texturetimepointpair_sequence m_textures;
index_map m_texturemappings;
garbage_collector<texturetimepointpair_sequence> m_garbagecollector { m_textures, 600, 60, "texture" };
};
// reduces provided data image to half of original size, using basic 2x2 average
template <typename Colortype_>
void
downsample( std::size_t const Width, std::size_t const Height, unsigned char *Imagedata ) {
Colortype_ *destination = reinterpret_cast<Colortype_*>( Imagedata );
Colortype_ *sampler = reinterpret_cast<Colortype_*>( Imagedata );
Colortype_ accumulator, color;
/*
_Colortype color;
float component;
*/
for( std::size_t row = 0; row < Height; row += 2, sampler += Width ) { // column movement advances us down another row
for( std::size_t column = 0; column < Width; column += 2, sampler += 2 ) {
/*
// straightforward, but won't work with byte data
auto color = (
*sampler
+ *( sampler + 1 )
+ *( sampler + Width )
+ *( sampler + Width + 1 ) );
color /= 4;
*/
// manual version of the above, but drops colour resolution to 6 bits
accumulator = *sampler;
accumulator /= 4;
color = accumulator;
accumulator = *(sampler + 1);
accumulator /= 4;
color += accumulator;
accumulator = *(sampler + Width);
accumulator /= 4;
color += accumulator;
accumulator = *(sampler + Width + 1);
accumulator /= 4;
color += accumulator;
*destination++ = color;
/*
// "full" 8bit resolution
color = Colortype_(); component = 0;
for( int idx = 0; idx < sizeof( Colortype_ ); ++idx ) {
component = (
(*sampler)[idx]
+ ( *( sampler + 1 ) )[idx]
+ ( *( sampler + Width ) )[idx]
+ ( *( sampler + Width + 1 ))[idx] );
color[ idx ] = component /= 4;
}
*destination++ = color;
*/
}
}
}
//---------------------------------------------------------------------------

609
model/material.cpp Normal file
View File

@@ -0,0 +1,609 @@
/*
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 "material.h"
#include "renderer.h"
#include "parser.h"
#include "utilities.h"
#include "Logs.h"
#include "sn_utils.h"
#include "Globals.h"
#include "Logs.h"
opengl_material::path_data opengl_material::paths;
opengl_material::opengl_material()
{
for (size_t i = 0; i < params.size(); i++)
params[i] = glm::vec4(std::numeric_limits<float>::quiet_NaN());
}
bool
opengl_material::deserialize( cParser &Input, bool const Loadnow ) {
parse_info = std::make_unique<parse_info_s>();
bool result { false };
while( true == deserialize_mapping( Input, 0, Loadnow ) ) {
result = true; // once would suffice but, eh
}
if( ( path == -1 )
&& ( update_on_weather_change || update_on_season_change ) ) {
// record current texture path in the material, potentially needed when material is reloaded on environment change
// NOTE: we're storing this only for textures that can actually change, to keep the size of path database modest
auto const lookup{ paths.index_map.find( Global.asCurrentTexturePath ) };
if( lookup != paths.index_map.end() ) {
path = lookup->second;
}
else {
path = paths.data.size();
paths.data.emplace_back( Global.asCurrentTexturePath );
paths.index_map.emplace( Global.asCurrentTexturePath, path );
}
}
return result;
}
void opengl_material::log_error(const std::string &str)
{
ErrorLog("bad material: " + name + ": " + str, logtype::material);
}
std::map<std::string, int> texture_bindings {
{ "diffuse", 0 },
{ "normals", 1 },
{ "normalmap", 1 }
};
void opengl_material::finalize(bool Loadnow)
{
is_good = true;
if (parse_info)
{
for (auto it : parse_info->tex_mapping)
{
std::string key = it.first;
std::string value = it.second.name;
if (key.size() > 0 && key[0] != '_')
{
key.erase( key.find_first_not_of( "-1234567890" ) );
size_t num = std::stoi(key) - 1;
if (num < gl::MAX_TEXTURES) {
textures[num] = GfxRenderer->Fetch_Texture(value, Loadnow);
}
else {
log_error("invalid texture binding: " + std::to_string(num));
is_good = false;
}
}
else if (key.size() > 2)
{
key.erase(0, 1);
key.pop_back();
std::map<std::string, int>::iterator lookup;
if( shader && shader->texture_conf.find( key ) != shader->texture_conf.end() ) {
textures[ shader->texture_conf[ key ].id ] = GfxRenderer->Fetch_Texture( value, Loadnow );
}
else if( ( shader == nullptr )
&& ( lookup = texture_bindings.find( key ) ) != texture_bindings.end() ) {
textures[ lookup->second ] = GfxRenderer->Fetch_Texture( value, Loadnow );
}
else {
// ignore unrecognized texture bindings in legacy render mode, it's most likely data for more advanced shaders
if( Global.GfxRenderer == "default" ) {
log_error( "unknown texture binding: " + key );
is_good = false;
}
}
}
else {
log_error("unrecognized texture binding: " + key);
is_good = false;
}
}
if (!shader)
{
// TODO: add error severity to logging, re-enable these errors as low severity messages
if (textures[0] == null_handle)
{
// log_error("shader not specified, assuming \"default_0\"");
shader = GfxRenderer->Fetch_Shader("default_0");
}
else if (textures[1] == null_handle)
{
// log_error("shader not specified, assuming \"default_1\"");
shader = GfxRenderer->Fetch_Shader("default_1");
}
else if (textures[2] == null_handle)
{
// log_error("shader not specified, assuming \"default_2\"");
shader = GfxRenderer->Fetch_Shader("default_2");
}
}
// TBD, TODO: move material validation to renderer, to eliminate branching?
if( Global.GfxRenderer == "default" ) {
if( !shader ) {
is_good = false;
return;
}
for (auto it : parse_info->param_mapping)
{
std::string key = it.first;
glm::vec4 value = it.second.data;
if (key.size() > 1 && key[0] != '_')
{
size_t num = std::stoi(key) - 1;
if (num < gl::MAX_PARAMS) {
params[num] = value;
}
else {
log_error("invalid param binding: " + std::to_string(num));
is_good = false;
}
}
else if (key.size() > 2)
{
key.erase(0, 1);
key.pop_back();
if (shader->param_conf.find(key) != shader->param_conf.end())
{
gl::shader::param_entry entry = shader->param_conf[key];
for (size_t i = 0; i < entry.size; i++)
params[entry.location][entry.offset + i] = value[i];
}
else {
log_error("unknown param binding: " + key);
is_good = false;
}
}
else {
log_error("unrecognized param binding: " + key);
is_good = false;
}
}
}
parse_info.reset();
}
if( Global.GfxRenderer != "default" ) {
// basic texture validation for legacy branch
for( auto texturehandle : textures ) {
if( texturehandle == null_handle ) {
break;
}
if( GfxRenderer->Texture( texturehandle ).get_id() <= 0 ) {
is_good = false;
}
}
return;
}
if( !shader ) {
is_good = false;
return;
}
for (auto it : shader->param_conf)
{
gl::shader::param_entry entry = it.second;
if (std::isnan(params[entry.location][entry.offset]))
{
float value = std::numeric_limits<float>::quiet_NaN();
if (entry.defaultparam == gl::shader::defaultparam_e::one)
value = 1.0f;
else if (entry.defaultparam == gl::shader::defaultparam_e::zero)
value = 0.0f;
else if (entry.defaultparam == gl::shader::defaultparam_e::required)
log_error("unspecified required param: " + it.first);
else if (entry.defaultparam != gl::shader::defaultparam_e::nan)
{
params_state.push_back(entry);
continue;
}
for (size_t i = 0; i < entry.size; i++)
params[entry.location][entry.offset + i] = value;
}
}
for (auto it : shader->texture_conf)
{
gl::shader::texture_entry &entry = it.second;
texture_handle handle = textures[entry.id];
// NOTE: texture validation at this stage relies on forced texture load behaviour during its create() call
// TODO: move texture id validation to later stage if/when deferred texture loading is implemented
if( ( handle )
&& ( GfxRenderer->Texture( handle ).get_id() > 0 ) ) {
GfxRenderer->Texture(handle).set_components_hint((GLint)entry.components);
}
else {
log_error("missing texture: " + it.first);
is_good = false;
}
}
}
bool opengl_material::update() {
auto const texturepathbackup { Global.asCurrentTexturePath };
auto const namebackup { name };
auto const pathbackup { path };
cParser materialparser( name + ".mat", cParser::buffer_FILE ); // fairly safe to presume .mat is present for branching materials
// temporarily set texture path to state recorded in the material
Global.asCurrentTexturePath = paths.data[ path ];
// clean material slate, restore relevant members
*this = opengl_material();
name = namebackup;
path = pathbackup;
auto result { false };
if( true == deserialize( materialparser, true ) ) {
try {
finalize( true );
result = true;
}
catch( gl::shader_exception const &e ) {
ErrorLog( "invalid shader: " + std::string( e.what() ) );
}
}
// restore texture path
Global.asCurrentTexturePath = texturepathbackup;
return result;
}
std::unordered_set<std::string> seasons = {
"winter:", "spring:", "summer:", "autumn:"
};
bool is_season( std::string const &String ) {
return ( seasons.find( String ) != seasons.end() );
}
std::unordered_set<std::string> weather = {
"clear:", "cloudy:", "rain:", "snow:" };
bool is_weather( std::string const &String ) {
return ( weather.find( String ) != weather.end() );
}
// imports member data pair from the config file
bool
opengl_material::deserialize_mapping( cParser &Input, int const Priority, bool const Loadnow ) {
// NOTE: comma can be part of legacy file names, so we don't treat it as a separator here
auto key { Input.getToken<std::string>( true, "\n\r\t ;[]" ) };
// key can be an actual key or block end
if( ( true == key.empty() ) || ( key == "}" ) ) { return false; }
if( Priority != -1 ) {
// regular attribute processing mode
// mark potential material change
update_on_weather_change |= is_weather( key );
update_on_season_change |= is_season( key );
if( key == Global.Weather ) {
// weather textures override generic (pri 0) and seasonal (pri 1) textures
// seasonal weather textures (pri 1+2=3) override generic weather (pri 2) textures
// skip the opening bracket
auto const value { Input.getToken<std::string>( true, "\n\r\t ;" ) };
while( true == deserialize_mapping( Input, Priority + 2, Loadnow ) ) {
; // all work is done in the header
}
}
else if( key == Global.Season ) {
// seasonal textures override generic textures
// skip the opening bracket
auto const value { Input.getToken<std::string>( true, "\n\r\t ;" ) };
while( true == deserialize_mapping( Input, Priority + 1, Loadnow ) ) {
; // all work is done in the header
}
}
else if (key.compare(0, 7, "texture") == 0) {
key.erase(0, 7);
auto value { deserialize_random_set( Input ) };
replace_slashes( value );
auto it = parse_info->tex_mapping.find(key);
if (it == parse_info->tex_mapping.end())
parse_info->tex_mapping.emplace(std::make_pair(key, parse_info_s::tex_def({ value, Priority })));
else if (Priority > it->second.priority)
{
parse_info->tex_mapping.erase(it);
parse_info->tex_mapping.emplace(std::make_pair(key, parse_info_s::tex_def({ value, Priority })));
}
}
else if (key.compare(0, 5, "param") == 0) {
key.erase(0, 5);
std::string value = Input.getToken<std::string>( true, "\n\r\t;" );
std::istringstream stream(value);
glm::vec4 data;
stream >> data.r;
stream >> data.g;
stream >> data.b;
stream >> data.a;
auto it = parse_info->param_mapping.find(key);
if (it == parse_info->param_mapping.end())
parse_info->param_mapping.emplace(std::make_pair(key, parse_info_s::param_def({ data, Priority })));
else if (Priority > it->second.priority)
{
parse_info->param_mapping.erase(it);
parse_info->param_mapping.emplace(std::make_pair(key, parse_info_s::param_def({ data, Priority })));
}
}
else if (key == "shader:" &&
(!shader || Priority > m_shader_priority))
{
try
{
std::string value = deserialize_random_set( Input );
shader = GfxRenderer->Fetch_Shader(value);
m_shader_priority = Priority;
}
catch (gl::shader_exception const &e)
{
log_error("invalid shader: " + std::string(e.what()));
}
}
else if (key == "opacity:" &&
Priority > m_opacity_priority)
{
std::string value = deserialize_random_set( Input );
opacity = std::stof(value); //m7t: handle exception
m_opacity_priority = Priority;
}
else if (key == "selfillum:" &&
Priority > m_selfillum_priority)
{
std::string value = deserialize_random_set( Input );
selfillum = std::stof(value); //m7t: handle exception
m_selfillum_priority = Priority;
}
else if (key == "glossiness:" &&
Priority > m_glossiness_priority)
{
std::string value = deserialize_random_set( Input );
glossiness = std::stof(value); //m7t: handle exception
m_glossiness_priority = Priority;
}
else if (key == "shadow_rank:")
{
auto const value { deserialize_random_set( Input ) };
shadow_rank = std::stof(value); //m7t: handle exception
}
else if( key == "size:" ) {
Input.getTokens( 2 );
Input
>> size.x
>> size.y;
}
else {
auto const value = Input.getToken<std::string>( true, "\n\r\t ;" );
if( value == "{" ) {
// unrecognized or ignored token, but comes with attribute block and potential further nesting
// go through it and discard the content
while( true == deserialize_mapping( Input, -1, Loadnow ) ) {
; // all work is done in the header
}
}
}
}
else {
// discard mode; ignores all retrieved tokens
auto const value { Input.getToken<std::string>( true, "\n\r\t ;" ) };
if( value == "{" ) {
// ignored tokens can come with their own blocks, ignore these recursively
// go through it and discard the content
while( true == deserialize_mapping( Input, -1, Loadnow ) ) {
; // all work is done in the header
}
}
}
return true; // return value marks a key: value pair was extracted, nothing about whether it's recognized
}
float opengl_material::get_or_guess_opacity() const {
if( opacity ) {
return opacity.value();
}
if (textures[0] != null_handle)
{
auto const &tex = GfxRenderer->Texture(textures[0]);
if (tex.get_has_alpha())
return 0.0f;
else
return 0.5f;
}
return 0.5f;
}
bool
opengl_material::is_translucent() const {
return (
textures[ 0 ] != null_handle ?
GfxRenderer->Texture( textures[ 0 ] ).get_has_alpha() :
false );
}
// create material object from data stored in specified file.
// NOTE: the deferred load parameter is passed to textures defined by material, the material itself is always loaded immediately
material_handle
material_manager::create( std::string const &Filename, bool const Loadnow ) {
auto filename { Filename };
if( contains( filename, '|' ) ) {
filename.erase( filename.find( '|' ) ); // po | może być nazwa kolejnej tekstury
}
// discern references to textures generated by a script
// TBD: support file: for file resources?
auto const isgenerated { filename.find( "make:" ) == 0 || filename.find( "internal_src:" ) == 0 };
// process supplied resource name
if( isgenerated ) {
// generated resource
// scheme:(user@)path?query
// remove scheme indicator
filename.erase( 0, filename.find(':') + 1 );
// TBD, TODO: allow shader specification as part of the query?
erase_leading_slashes( filename );
}
else {
// regular file resource
// (filepath/)filename.extension
erase_extension( filename );
replace_slashes( filename );
erase_leading_slashes( filename );
}
auto const databanklookup { find_in_databank( filename ) };
if( databanklookup != null_handle ) {
return databanklookup;
}
opengl_material material;
material_handle materialhandle { null_handle };
auto const locator {
isgenerated ?
std::make_pair( filename, "make:" ) :
find_on_disk( filename ) };
if( ( false == isgenerated )
&& ( false == locator.first.empty() ) ) {
// try to parse located file resource
cParser materialparser(
locator.first + locator.second,
cParser::buffer_FILE );
if( true == material.deserialize( materialparser, Loadnow ) ) {
material.name = locator.first;
}
}
else {
// if there's no .mat file, this can be either autogenerated texture,
// or legacy method of referring just to diffuse texture directly.
// wrap basic material around it in either case
auto const texturehandle { GfxRenderer->Fetch_Texture( Filename, Loadnow ) };
if( texturehandle != null_handle ) {
// use texture path and name to tell the newly created materials apart
material.name = GfxRenderer->Texture( texturehandle ).get_name();
/*
// material would attach default shader anyway, but it would spit to error log
try
{
material.shader = GfxRenderer->Fetch_Shader("default_1");
}
catch (gl::shader_exception const &e)
{
ErrorLog("invalid shader: " + std::string(e.what()));
}
}
*/
// HACK: create parse info for material finalize() method
cParser materialparser(
"texture1: \"" + Filename + "\"",
cParser::buffer_TEXT );
material.deserialize( materialparser, Loadnow );
}
}
if( false == material.name.empty() ) {
// if we have material name and shader it means resource was processed succesfully
materialhandle = m_materials.size();
m_materialmappings.emplace( material.name, materialhandle );
try {
material.finalize(Loadnow);
m_materials.emplace_back( std::move(material) );
} catch (gl::shader_exception const &e) {
ErrorLog("invalid shader: " + std::string(e.what()));
}
}
else {
// otherwise record our failure to process the resource, to speed up subsequent attempts
m_materialmappings.emplace( filename, materialhandle );
}
return materialhandle;
};
void
material_manager::on_weather_change() {
for( auto &material : m_materials ) {
if( material.update_on_weather_change ) { material.update(); }
}
}
void
material_manager::on_season_change() {
for( auto &material : m_materials ) {
if( material.update_on_season_change ) { material.update(); }
}
}
// checks whether specified material is in the material bank. returns handle to the material, or a null handle
material_handle
material_manager::find_in_databank( std::string const &Materialname ) const {
std::vector<std::string> const filenames {
Global.asCurrentTexturePath + Materialname,
Materialname,
szTexturePath + Materialname };
for( auto const &filename : filenames ) {
auto const lookup { m_materialmappings.find( filename ) };
if( lookup != m_materialmappings.end() ) {
return lookup->second;
}
}
// all lookups failed
return null_handle;
}
// checks whether specified file exists.
// NOTE: technically could be static, but we might want to switch from global texture path to instance-specific at some point
std::pair<std::string, std::string>
material_manager::find_on_disk( std::string const &Materialname ) {
auto const materialname { ToLower( Materialname ) };
return (
FileExists(
{ Global.asCurrentTexturePath + materialname, materialname, szTexturePath + materialname },
{ ".mat" } ) );
}
//---------------------------------------------------------------------------

142
model/material.h Normal file
View File

@@ -0,0 +1,142 @@
/*
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/.
*/
#pragma once
#include "interfaces/IMaterial.h"
#include "Classes.h"
#include "Texture.h"
#include "gl/shader.h"
#include "gl/ubo.h"
// a collection of parameters for the rendering setup.
// for modern opengl this translates to set of attributes for shaders
struct opengl_material : public IMaterial {
std::array<texture_handle, gl::MAX_TEXTURES> textures = { null_handle };
std::array<glm::vec4, gl::MAX_PARAMS> params;
std::vector<gl::shader::param_entry> params_state;
std::shared_ptr<gl::program> shader;
std::optional<float> opacity;
std::optional<float> selfillum;
float glossiness { 10.f };
int shadow_rank { 0 }; // priority as shadow caster; higher = more likely to be skipped
std::string name;
glm::vec2 size { -1.f, -1.f }; // 'physical' size of bound texture, in meters
// constructors
opengl_material();
// methods
bool deserialize(cParser &Input, bool const Loadnow);
virtual void finalize(bool Loadnow) override;
virtual bool update() override;
virtual float get_or_guess_opacity() const override;
virtual bool is_translucent() const override;
virtual glm::vec2 GetSize() const override
{
return size;
}
virtual std::string GetName() const override
{
return name;
}
virtual std::optional<float> GetSelfillum() const override
{
return selfillum;
}
virtual int GetShadowRank() const override
{
return shadow_rank;
}
virtual texture_handle GetTexture(int slot) const override
{
return textures[slot];
}
// members
static struct path_data {
std::unordered_map<std::string, int> index_map;
std::vector<std::string> data;
} paths;
bool is_good { false }; // indicates material was compiled without failure
int path{ -1 }; // index to material path
bool update_on_weather_change{ false };
bool update_on_season_change{ false };
private:
// methods
// imports member data pair from the config file
bool
deserialize_mapping( cParser &Input, int const Priority, bool const Loadnow );
void log_error(const std::string &str);
// members
// priorities for textures, shader, opacity
int m_shader_priority = -1;
int m_opacity_priority = -1;
int m_selfillum_priority = -1;
int m_glossiness_priority = -1;
struct parse_info_s
{
struct tex_def
{
std::string name;
int priority;
};
struct param_def
{
glm::vec4 data;
int priority;
};
std::unordered_map<std::string, tex_def> tex_mapping;
std::unordered_map<std::string, param_def> param_mapping;
};
std::unique_ptr<parse_info_s> parse_info;
};
class material_manager {
public:
material_manager() { m_materials.emplace_back( opengl_material() ); } // empty bindings for null material
material_handle
create( std::string const &Filename, bool const Loadnow );
opengl_material const &
material( material_handle const Material ) const { return m_materials[ Material ]; }
opengl_material &
material( material_handle const Material ) { return m_materials[ Material ]; }
// material updates executed when environment changes
// TODO: registerable callbacks in environment manager
void
on_weather_change();
void
on_season_change();
private:
// types
typedef std::vector<opengl_material> material_sequence;
typedef std::unordered_map<std::string, std::size_t> index_map;
// methods:
// checks whether specified texture is in the texture bank. returns texture id, or npos.
material_handle
find_in_databank( std::string const &Materialname ) const;
public:
// checks whether specified file exists. returns name of the located file, or empty string.
static std::pair<std::string, std::string>
find_on_disk( std::string const &Materialname );
private:
// members:
material_sequence m_materials;
index_map m_materialmappings;
};
//---------------------------------------------------------------------------

64
model/vertex.cpp Normal file
View File

@@ -0,0 +1,64 @@
/*
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 "vertex.h"
#include "sn_utils.h"
void
world_vertex::serialize( std::ostream &s ) const {
sn_utils::ls_float64( s, position.x );
sn_utils::ls_float64( s, position.y );
sn_utils::ls_float64( s, position.z );
sn_utils::ls_float32( s, normal.x );
sn_utils::ls_float32( s, normal.y );
sn_utils::ls_float32( s, normal.z );
sn_utils::ls_float32( s, texture.x );
sn_utils::ls_float32( s, texture.y );
}
void
world_vertex::deserialize( std::istream &s ) {
position.x = sn_utils::ld_float64( s );
position.y = sn_utils::ld_float64( s );
position.z = sn_utils::ld_float64( s );
normal.x = sn_utils::ld_float32( s );
normal.y = sn_utils::ld_float32( s );
normal.z = sn_utils::ld_float32( s );
texture.x = sn_utils::ld_float32( s );
texture.y = sn_utils::ld_float32( s );
}
template <>
world_vertex &
world_vertex::operator+=( world_vertex const &Right ) {
position += Right.position;
normal += Right.normal;
texture += Right.texture;
return *this;
}
template <>
world_vertex &
world_vertex::operator*=( world_vertex const &Right ) {
position *= Right.position;
normal *= Right.normal;
texture *= Right.texture;
return *this;
}
//---------------------------------------------------------------------------

88
model/vertex.h Normal file
View File

@@ -0,0 +1,88 @@
/*
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/.
*/
#pragma once
#include "utilities.h"
// geometry vertex with double precision position
struct world_vertex {
// members
glm::dvec3 position;
glm::vec3 normal;
glm::vec2 texture;
// overloads
// operator+
template <typename Scalar_>
world_vertex &
operator+=( Scalar_ const &Right ) {
position += Right;
normal += Right;
texture += Right;
return *this; }
template <typename Scalar_>
friend
world_vertex
operator+( world_vertex Left, Scalar_ const &Right ) {
Left += Right;
return Left; }
// operator*
template <typename Scalar_>
world_vertex &
operator*=( Scalar_ const &Right ) {
position *= Right;
normal *= Right;
texture *= Right;
return *this; }
template <typename Type_>
friend
world_vertex
operator*( world_vertex Left, Type_ const &Right ) {
Left *= Right;
return Left; }
// methods
void serialize( std::ostream& ) const;
void deserialize( std::istream& );
// wyliczenie współrzędnych i mapowania punktu na środku odcinka v1<->v2
void
set_half( world_vertex const &Vertex1, world_vertex const &Vertex2 ) {
*this =
interpolate(
Vertex1,
Vertex2,
0.5 ); }
// wyliczenie współrzędnych i mapowania punktu na odcinku v1<->v2
void
set_from_x( world_vertex const &Vertex1, world_vertex const &Vertex2, double const X ) {
*this =
interpolate(
Vertex1,
Vertex2,
( X - Vertex1.position.x ) / ( Vertex2.position.x - Vertex1.position.x ) ); }
// wyliczenie współrzędnych i mapowania punktu na odcinku v1<->v2
void
set_from_z( world_vertex const &Vertex1, world_vertex const &Vertex2, double const Z ) {
*this =
interpolate(
Vertex1,
Vertex2,
( Z - Vertex1.position.z ) / ( Vertex2.position.z - Vertex1.position.z ) ); }
};
template <>
world_vertex &
world_vertex::operator+=( world_vertex const &Right );
template <>
world_vertex &
world_vertex::operator*=( world_vertex const &Right );
//---------------------------------------------------------------------------