mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-03-22 15:05:03 +01:00
Reorganize source files into logical subdirectories
Co-authored-by: Hirek193 <23196899+Hirek193@users.noreply.github.com>
This commit is contained in:
720
model/AnimModel.cpp
Normal file
720
model/AnimModel.cpp
Normal 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
201
model/AnimModel.h
Normal 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
159
model/MdlMngr.cpp
Normal 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
42
model/MdlMngr.h
Normal 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
2533
model/Model3d.cpp
Normal file
File diff suppressed because it is too large
Load Diff
306
model/Model3d.h
Normal file
306
model/Model3d.h
Normal 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
89
model/ResourceManager.cpp
Normal 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
81
model/ResourceManager.h
Normal 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
1508
model/Texture.cpp
Normal file
File diff suppressed because it is too large
Load Diff
254
model/Texture.h
Normal file
254
model/Texture.h
Normal 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
609
model/material.cpp
Normal 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
142
model/material.h
Normal 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
64
model/vertex.cpp
Normal 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
88
model/vertex.h
Normal 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 );
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
Reference in New Issue
Block a user