Files
maszyna/AnimModel.cpp
2019-10-12 18:38:01 +02:00

942 lines
38 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
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"
TAnimContainer *TAnimModel::acAnimList = NULL;
TAnimAdvanced::TAnimAdvanced(){};
TAnimAdvanced::~TAnimAdvanced(){
// delete[] pVocaloidMotionData; //plik został zmodyfikowany
};
int TAnimAdvanced::SortByBone()
{ // sortowanie pliku animacji w celu optymalniejszego wykonania
// rekordy zostają ułożone wg kolejnych ramek dla każdej kości
// ułożenie kości alfabetycznie nie jest niezbędne, ale upraszcza sortowanie bąbelkowe
TAnimVocaloidFrame buf; // bufor roboczy (przydało by się pascalowe Swap()
int i, j, k, swaps = 0, last = iMovements - 1, e;
for (i = 0; i < iMovements; ++i)
for (j = 0; j < 15; ++j)
if (pMovementData[i].cBone[j] == '\0') // jeśli znacznik końca
for (++j; j < 15; ++j)
pMovementData[i].cBone[j] = '\0'; // zerowanie bajtów za znacznikiem końca
for (i = 0; i < last; ++i) // do przedostatniego
{ // dopóki nie posortowane
j = i; // z którym porównywać
k = i; // z którym zamienić (nie trzeba zamieniać, jeśli ==i)
while (++j < iMovements)
{
e = strcmp(pMovementData[k].cBone,
pMovementData[j].cBone); // numery trzeba porównywać inaczej
if (e > 0)
k = j; // trzeba zamienić - ten pod j jest mniejszy
else if (!e)
if (pMovementData[k].iFrame > pMovementData[j].iFrame)
k = j; // numer klatki pod j jest mniejszy
}
if (k > i)
{ // jeśli trzeba przestawić
// buf=pMovementData[i];
// pMovementData[i]=pMovementData[k];
// pMovementData[k]=buf;
memcpy(&buf, pMovementData + i, sizeof(TAnimVocaloidFrame));
memcpy(pMovementData + i, pMovementData + k, sizeof(TAnimVocaloidFrame));
memcpy(pMovementData + k, &buf, sizeof(TAnimVocaloidFrame));
++swaps;
}
}
return swaps;
};
TAnimContainer::TAnimContainer()
{
pNext = NULL;
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
pMovementData = NULL; // nie ma zaawansowanej animacji
mAnim = NULL; // nie ma macierzy obrotu dla submodelu
evDone = NULL; // powiadamianie o zakończeniu animacji
acAnimNext = NULL; // na razie jest poza listą
}
TAnimContainer::~TAnimContainer()
{
SafeDelete(pNext);
delete mAnim; // AnimContainer jest właścicielem takich macierzy
}
bool TAnimContainer::Init(TSubModel *pNewSubModel)
{
fRotateSpeed = 0.0f;
pSubModel = pNewSubModel;
return (pSubModel != NULL);
}
void TAnimContainer::SetRotateAnim( Math3D::vector3 vNewRotateAngles, double fNewRotateSpeed)
{
if (!this)
return; // wywoływane z eventu, gdy brak modelu
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
acAnimNext = TAnimModel::acAnimList; // pozostałe doklić sobie jako ogon
TAnimModel::acAnimList = this; // a wstawić się na początek
iAnim |= 0x80000000; // dodany do listy
}
}
}
void TAnimContainer::SetTranslateAnim( Math3D::vector3 vNewTranslate, double fNewSpeed)
{
if (!this)
return; // wywoływane z eventu, gdy brak modelu
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
acAnimNext = TAnimModel::acAnimList; // pozostałe doklić sobie jako ogon
TAnimModel::acAnimList = this; // a wstawić się na początek
iAnim |= 0x80000000; // dodany do listy
}
}
}
void TAnimContainer::AnimSetVMD(double fNewSpeed)
{
if (!this)
return; // wywoływane z eventu, gdy brak modelu
// skala do ustalenia, "cal" japoński (sun) to nieco ponad 3cm
// X-w lewo, Y-w górę, Z-do tyłu
// minimalna wysokość to -7.66, a nadal musi być ponad podłogą
// if (pMovementData->iFrame>0) return; //tylko pierwsza ramka
vTranslateTo =
Math3D::vector3(
0.1 * pMovementData->f3Vector.x,
0.1 * pMovementData->f3Vector.z,
0.1 * pMovementData->f3Vector.y);
if (LengthSquared3(vTranslateTo) > 0.0 ? true : LengthSquared3(vTranslation) > 0.0)
{ // jeśli ma być przesunięte albo jest przesunięcie
iAnim |= 2; // wyłączy się samo
if (fNewSpeed > 0.0)
fTranslateSpeed = fNewSpeed; // prędkość jest mnożnikiem, nie podlega skalowaniu
else // za późno na animacje, trzeba przestawić
vTranslation = vTranslateTo;
}
// if ((qCurrent.w<1.0)||(pMovementData->qAngle.w<1.0))
{ // jeśli jest jakiś obrót
if (!mAnim)
{
mAnim = new float4x4(); // będzie potrzebna macierz animacji
mAnim->Identity(); // jedynkowanie na początek
}
iAnim |= 4; // animacja kwaternionowa
qStart = qCurrent; // potrzebna początkowa do interpolacji
//---+ - też niby dobrze, ale nie tak trąca włosy na początku (macha w dół)
//-+-+ - dłoń ma w górze zamiast na pasie w pozycji początkowej
//+--+ - głowa do tyłu (broda w górę) w pozycji początkowej
//--++ - pozycja początkowa dobra, trąca u góry, ale z rękami jakoś nie tak, kółko w
// przeciwną stronę
//++++ - kładzie się brzuchem do góry
//-+++ - ręce w górze na początku, zamiast w dół, łokieć jakby w przeciwną stronę
//+-++ - nie podnosi ręki do głowy
//++-+ - dłoń ma w górze zamiast na pasie
qDesired = Normalize(float4(-pMovementData->qAngle.x, -pMovementData->qAngle.z,
-pMovementData->qAngle.y,
pMovementData->qAngle.w)); // tu trzeba będzie osie zamienić
if (fNewSpeed > 0.0)
{
fAngleSpeed = fNewSpeed; // wtedy animować za pomocą interpolacji
fAngleCurrent = 0.0; // początek interpolacji
}
else
{ // za późno na animację, można tylko przestawić w docelowe miejsce
fAngleSpeed = 0.0;
fAngleCurrent = 1.0; // interpolacja zakończona
qCurrent = qDesired;
}
}
// if (!strcmp(pSubModel->pName,"?Z?“?^?[")) //jak główna kość
// if (!strcmp(pSubModel->pName,"Ť¶‚‚Ü?ćhj")) //IK lewej stopy
// WriteLog(AnsiString(pMovementData->iFrame)+": "+AnsiString(pMovementData->f3Vector.x)+"
// "+AnsiString(pMovementData->f3Vector.y)+" "+AnsiString(pMovementData->f3Vector.z));
}
// 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));
}
void TAnimContainer::UpdateModelIK()
{ // odwrotna kinematyka wyliczana dopiero po ustawieniu macierzy w submodelach
if (pSubModel) // pozbyć się tego - sprawdzać wcześniej
{
if ((pSubModel->b_Anim == TAnimType::at_IK)
||(pSubModel->b_Anim == TAnimType::at_IK22))
{ // odwrotna kinematyka
float3 d, k;
TSubModel *ch = pSubModel->ChildGet();
switch (pSubModel->b_Anim)
{
case TAnimType::at_IK11: // stopa: ustawić w kierunku czubka (pierwszy potomny)
d = ch->Translation1Get(); // wektor względem aktualnego układu (nie uwzględnia
// obrotu)
k = float3(RadToDeg(atan2(d.z, hypot(d.x, d.y))), 0.0,
-RadToDeg(atan2(d.y, d.x))); // proste skierowanie na punkt
pSubModel->SetRotateIK1(k);
// if (!strcmp(pSubModel->pName,"?Z?“?^?[")) //jak główna kość
// WriteLog("--> "+AnsiString(k.x)+" "+AnsiString(k.y)+" "+AnsiString(k.z));
// Ra: to już jest dobrze, może być inna ćwiartka i znak
break;
case TAnimType::at_IK22: // udo: ustawić w kierunku pierwszej potomnej pierwszej potomnej (kostki)
// pozycję kostki należy określić względem kości centralnej (+biodro może być
// pochylone)
// potem wyliczyć ewentualne odchylenie w tej i następnej
// w sumie to proste, jak wyznaczenie kątów w trójkącie o znanej długości boków...
d = ch->Translation2Get(); // wektor względem aktualnego układu (nie uwzględnia
// obrotu)
// if ()
{ // kość IK jest dalej niż pozycja spoczynkowa
k = float3(RadToDeg(atan2(d.z, hypot(d.x, d.y))), 0.0,
-RadToDeg(atan2(d.y, d.x))); // proste skierowanie na punkt
pSubModel->SetRotateIK1(k);
}
break;
}
}
}
}
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 );
}
TAnimModel::~TAnimModel()
{
SafeDelete(pAdvanced); // nie ma zaawansowanej animacji
SafeDelete(pRoot);
}
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::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 ); // tekstura (zmienia na małe)
replace_slashes( name );
if (!Init( name, texture ))
{
if (name != "notload")
{ // gdy brak modelu
if (ter) // jeśli teren
{
if( name.substr( name.rfind( '.' ) ) == ".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");
}
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() )
&& ( token != "lightcolors" )
&& ( token != "endmodel" ) ) {
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() )
&& ( token != "lights" )
&& ( token != "endmodel" ) ) {
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;
}
}
} while( ( false == token.empty() )
&& ( token != "endmodel" ) );
return true;
}
TAnimContainer * TAnimModel::AddContainer(std::string const &Name)
{ // dodanie sterowania submodelem dla egzemplarza
if (!pModel)
return NULL;
TSubModel *tsb = pModel->GetFromName(Name);
if (tsb)
{
TAnimContainer *tmp = new TAnimContainer();
tmp->Init(tsb);
tmp->pNext = pRoot;
pRoot = tmp;
return tmp;
}
return NULL;
}
TAnimContainer * TAnimModel::GetContainer(std::string const &Name)
{ // szukanie/dodanie sterowania submodelem dla egzemplarza
if (true == Name.empty())
return pRoot; // pobranie pierwszego (dla obrotnicy)
TAnimContainer *pCurrent;
for (pCurrent = pRoot; pCurrent != NULL; pCurrent = pCurrent->pNext)
// if (pCurrent->GetName()==pName)
if (Name == pCurrent->NameGet())
return pCurrent;
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
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>( fTransitionTime, timer + timedelta );
break;
}
default: {
break;
}
}
opacity = timer / fTransitionTime;
}
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 {
std::min(
1.f,
std::min( ontime, offtime ) * 0.9f ) };
timer = clamp_circular<float>( 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ść
TAnimContainer *pCurrent;
for (pCurrent = pRoot; pCurrent != nullptr; pCurrent = pCurrent->pNext)
if (!pCurrent->evDone) // jeśli jest bez eventu
pCurrent->UpdateModel(); // przeliczenie animacji każdego submodelu
// if () //tylko dla modeli z IK !!!!
for (pCurrent = pRoot; pCurrent != NULL; pCurrent = pCurrent->pNext) // albo osobny łańcuch
pCurrent->UpdateModelIK(); // przeliczenie odwrotnej kinematyki
m_framestamp = Framestamp;
};
void TAnimModel::RaPrepare()
{ // ustawia światła i animacje we wzorcu modelu przed renderowaniem egzemplarza
bool state; // stan światła
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)
if (pAdvanced) // jeśli jest zaawansowana animacja
Advanced(); // wykonać co tam trzeba
TAnimContainer *pCurrent;
for (pCurrent = pRoot; pCurrent != NULL; pCurrent = pCurrent->pNext)
pCurrent->PrepareModel(); // ustawienie animacji egzemplarza dla każdego submodelu
// if () //tylko dla modeli z IK !!!!
// for (pCurrent=pRoot;pCurrent!=NULL;pCurrent=pCurrent->pNext) //albo osobny łańcuch
// pCurrent->UpdateModelIK(); //przeliczenie odwrotnej kinematyki
}
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::Advanced()
{ // wykonanie zaawansowanych animacji na submodelach
pAdvanced->fCurrent +=
pAdvanced->fFrequency * Timer::GetDeltaTime(); // aktualna ramka zmiennoprzecinkowo
int frame = floor(pAdvanced->fCurrent); // numer klatki jako int
TAnimContainer *pCurrent;
if (pAdvanced->fCurrent >= pAdvanced->fLast)
{ // animacja została zakończona
delete pAdvanced;
pAdvanced = NULL; // dalej już nic
for (pCurrent = pRoot; pCurrent != NULL; pCurrent = pCurrent->pNext)
if (pCurrent->pMovementData) // jeśli obsługiwany tabelką animacji
pCurrent->pMovementData = NULL; // usuwanie wskaźników
}
else
{ // coś trzeba poanimować - wszystkie animowane submodele są w tym łańcuchu
for (pCurrent = pRoot; pCurrent != NULL; pCurrent = pCurrent->pNext)
if (pCurrent->pMovementData) // jeśli obsługiwany tabelką animacji
if (frame >= pCurrent->pMovementData->iFrame) // koniec czekania
if (!strcmp(pCurrent->pMovementData->cBone,
(pCurrent->pMovementData + 1)->cBone))
{ // jak kolejna ramka dotyczy tego samego submodelu, ustawić animację do
// kolejnej ramki
++pCurrent->pMovementData; // kolejna klatka
pCurrent->AnimSetVMD(
pAdvanced->fFrequency /
(double(pCurrent->pMovementData->iFrame) - pAdvanced->fCurrent));
}
else
pCurrent->pMovementData =
NULL; // inna nazwa, animowanie zakończone w aktualnym położeniu
}
};
void TAnimModel::AnimationVND(void *pData, double a, double b, double c, double d)
{ // rozpoczęcie wykonywania animacji z podanego pliku
// tabela w pliku musi być posortowana wg klatek dla kolejnych kości!
// skrócone nagranie ma 3:42 = 222 sekundy, animacja kończy się na klatce 6518
// daje to 29.36 (~=30) klatek na sekundę
// w opisach jest podawane 24 albo 36 jako standard => powiedzmy, parametr (d) to FPS animacji
delete pAdvanced; // usunięcie ewentualnego poprzedniego
pAdvanced = NULL; // gdyby się nie udało rozpoznać pliku
if (std::string(static_cast<char *>(pData)) == "Vocaloid Motion Data 0002")
{
pAdvanced = new TAnimAdvanced();
pAdvanced->pVocaloidMotionData = static_cast<unsigned char *>(pData); // podczepienie pliku danych
pAdvanced->iMovements = *((int *)(((char *)pData) + 50)); // numer ostatniej klatki
pAdvanced->pMovementData = (TAnimVocaloidFrame *)(((char *)pData) + 54); // rekordy animacji
// WriteLog(sizeof(TAnimVocaloidFrame));
pAdvanced->fFrequency = d;
pAdvanced->fCurrent = 0.0; // aktualna ramka
pAdvanced->fLast = 0.0; // ostatnia ramka
/*
if (0) //jeśli włączone sortowanie plików VMD (trochę się przeciąga)
if (pAdvanced->SortByBone()) //próba posortowania
{//zapisać posortowany plik, jeśli dokonano zmian
TFileStream *fs=new TFileStream("models\\1.vmd",fmCreate);
fs->Write(pData,2198342); //2948728);
delete fs;
}
*/
/* int i, j, k, idx;
*/ std::string name;
TAnimContainer *pSub;
for (int i = 0; i < pAdvanced->iMovements; ++i)
{
if (strcmp(pAdvanced->pMovementData[i].cBone, name.c_str()))
{ // jeśli pozycja w tabelce nie była wyszukiwana w submodelach
pSub = GetContainer(pAdvanced->pMovementData[i].cBone); // szukanie
if (pSub) // znaleziony
{
pSub->pMovementData = pAdvanced->pMovementData + i; // gotów do animowania
pSub->AnimSetVMD(0.0); // usuawienie pozycji początkowej (powinna być zerowa,
// inaczej będzie skok)
}
name = std::string(pAdvanced->pMovementData[i].cBone); // nowa nazwa do pomijania
}
if (pAdvanced->fLast < pAdvanced->pMovementData[i].iFrame)
pAdvanced->fLast = pAdvanced->pMovementData[i].iFrame;
}
/*
for (i=0;i<pAdvanced->iMovements;++i)
if
(AnsiString(pAdvanced->pMovementData[i+1].cBone)!=AnsiString(pAdvanced->pMovementData[i].cBone))
{//generowane dla ostatniej klatki danej kości
name="";
for (j=0;j<15;j++)
name+=IntToHex((unsigned char)pAdvanced->pMovementData[i].cBone[j],2);
WriteLog(name+","
+AnsiString(pAdvanced->pMovementData[i].cBone)+","
+AnsiString(idx)+"," //indeks
+AnsiString(i+1-idx)+"," //ile pozycji animacji
+AnsiString(k)+"," //pierwsza klatka
+AnsiString(pAdvanced->pMovementData[i].iFrame)+"," //ostatnia klatka
+AnsiString(pAdvanced->pMovementData[i].f3Vector.x)+","
+AnsiString(pAdvanced->pMovementData[i].f3Vector.y)+","
+AnsiString(pAdvanced->pMovementData[i].f3Vector.z)+","
+AnsiString(pAdvanced->pMovementData[i].fAngle[0])+","
+AnsiString(pAdvanced->pMovementData[i].fAngle[1])+","
+AnsiString(pAdvanced->pMovementData[i].fAngle[2])+","
+AnsiString(pAdvanced->pMovementData[i].fAngle[3])
);
idx=i+1;
k=pAdvanced->pMovementData[i+1].iFrame; //pierwsza klatka następnego
}
else
if (pAdvanced->pMovementData[i].iFrame>0)
if ((k>pAdvanced->pMovementData[i].iFrame)||(k==0))
k=pAdvanced->pMovementData[i].iFrame; //pierwsza niezerowa ramka
*/
/*
for (i=0;i<pAdvanced->iMovements;++i)
if (AnsiString(pAdvanced->pMovementData[i].cBone)=="\x89\x45\x90\x65\x8E\x77\x82\x4F")
{name="";
for (j=0;j<15;j++)
name+=IntToHex((unsigned char)pAdvanced->pMovementData[i].cBone[j],2);
WriteLog(name+","
+AnsiString(i)+"," //pozycja w tabeli
+AnsiString(pAdvanced->pMovementData[i].iFrame)+"," //pierwsza klatka
);
}
*/
}
};
//---------------------------------------------------------------------------
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;
};
void TAnimModel::AnimUpdate(double dt)
{ // wykonanie zakolejkowanych animacji, nawet gdy modele nie są aktualnie wyświetlane
TAnimContainer *p = TAnimModel::acAnimList;
while( p ) {
p->UpdateModel();
p = p->acAnimNext; // na razie bez usuwania z listy, bo głównie obrotnica na nią wchodzi
}
};
// 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
<< location().x << ' '
<< location().y << ' '
<< location().z << ' '
<< vAngle.y << ' ';
// 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 ] ).name :
"none" ) };
if( texturefile.find( szTexturePath ) == 0 ) {
// don't include 'textures/' in the path
texturefile.erase( 0, std::string{ szTexturePath }.size() );
}
Output << texturefile << ' ';
// light submodels activation configuration
if( iNumLights > 0 ) {
Output << "lights ";
for( int lightidx = 0; lightidx < iNumLights; ++lightidx ) {
Output << lsLights[ lightidx ] << ' ';
}
}
// footer
Output
<< "endmodel"
<< "\n";
}
//---------------------------------------------------------------------------