mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-03-22 15:05:03 +01:00
942 lines
38 KiB
C++
942 lines
38 KiB
C++
/*
|
||
This Source Code Form is subject to the
|
||
terms of the Mozilla Public License, v.
|
||
2.0. If a copy of the MPL was not
|
||
distributed with this file, You can
|
||
obtain one at
|
||
http://mozilla.org/MPL/2.0/.
|
||
*/
|
||
/*
|
||
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,"Ť¶‚‚Ü?ć‚h‚j")) //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";
|
||
}
|
||
|
||
//---------------------------------------------------------------------------
|