Add wiper animation

This commit is contained in:
2025-03-06 22:51:24 +01:00
parent d832ce3d96
commit b5172e940b
10 changed files with 330 additions and 6 deletions

View File

@@ -254,6 +254,9 @@ int TAnim::TypeSet(int i, TMoverParameters currentMover, int fl)
case 8:
iFlags = 0x080;
break; // mirror
case 9:
iFlags = 0x023; // 2: Rotating/Motion-based, 8: Uses 3 submodels
break;
default:
iFlags = 0;
}
@@ -761,6 +764,24 @@ void TDynamicObject::UpdateMirror( TAnim *pAnim ) {
interpolate( 0.0, MoverParameters->MirrorMaxShift, dMirrorMoveL * isactive ) );
}
// wipers
void TDynamicObject::UpdateWiper(TAnim* pAnim)
{
if (!pAnim || !pAnim->smElement)
return;
int i = pAnim->iNumber;
// odwaramy animacje dla parzystych indexow
const double rotateAngle = (i + 1) % 2 == 0 ? -MoverParameters->WiperAngle : MoverParameters->WiperAngle;
if (pAnim->smElement[0]) // ramie 1
pAnim->smElement[0]->SetRotate(float3(0, 1, 0), smoothInterpolate(0.0, rotateAngle, dWiperPos[i]));
if (pAnim->smElement[1]) // ramie 2
pAnim->smElement[1]->SetRotate(float3(0, 1, 0), smoothInterpolate(0.0, rotateAngle, dWiperPos[i]));
if (pAnim->smElement[2]) // pioro
pAnim->smElement[2]->SetRotate(float3(0, 1, 0), smoothInterpolate(0.0, -rotateAngle, dWiperPos[i]));
}
/*
void TDynamicObject::UpdateLeverDouble(TAnim *pAnim)
{ // animacja gałki zależna od double
@@ -4105,6 +4126,99 @@ bool TDynamicObject::Update(double dt, double dt1)
MoverParameters->PantFrontVolt = 0.95 * MoverParameters->EnginePowerSource.MaxVoltage;
}
// wipers
if (dWiperPos.size() > 0) // tylko dla wozow ze zdefiniowanymi wycierakami
{
for (int i = 0; i < dWiperPos.size(); i++) // iteracja po kazdej wycieraczce
{
bool wipersActive;
auto const bytesum = MoverParameters->WiperList[MoverParameters->wiperSwitchPos].byteSum;
// rozroznienie kabinowe
if (MoverParameters->CabActive == 1)
{
wipersActive = ((bytesum & (1 << i)) != 0) && MoverParameters->Battery;
}
else if (MoverParameters->CabActive == -1)
{
// odwroconie indexow wycieraczek
wipersActive = ((bytesum & (1 << dWiperPos.size() - 1 - i)) != 0) && MoverParameters->Battery;
}
else {
wipersActive = false;
}
auto const currentWiperParams = MoverParameters->WiperList[workingSwitchPos[i]];
wiperOutTimer[i] += dt1; // aktualizujemy zegarek
wiperParkTimer[i] += dt1; // aktualizujemy zegarek
if (wipersActive || dWiperPos[i] > 0.0) // zeby wrocily do trybu park
{
if (dWiperPos[i] > 0.0 && !wipersActive) // bezwzgledny powrot do zera
{
dWiperPos[i] = std::max(0.0, dWiperPos[i] - (1.f / currentWiperParams.WiperSpeed) * dt1);
}
else {
if (dWiperPos[i] < 1.0 && !wiperDirection[i] && wiperParkTimer[i] > currentWiperParams.interval)
// go out
{
if (!sWiperToPark.is_playing())
{
wiper_playSoundFromStart[i] = true;
}
dWiperPos[i] = std::min(1.0, dWiperPos[i] + (1.f / currentWiperParams.WiperSpeed) * dt1);
}
if (dWiperPos[i] > 0.0 && wiperDirection[i] && wiperOutTimer[i] > currentWiperParams.outBackDelay)
// return back
{
if (!sWiperToPark.is_playing())
{
wiper_playSoundToStart[i] = true;
}
dWiperPos[i] = std::max(0.0, dWiperPos[i] - (1.f / currentWiperParams.WiperSpeed) * dt1);
}
if (dWiperPos[i] == 1.0) // we reached end
{
wiperParkTimer[i] = 0.0;
wiperDirection[i] = true; // switch direction
}
if (dWiperPos[i] == 0.0)
{
// when in park position
wiperOutTimer[i] = 0.0;
wiperDirection[i] = false;
workingSwitchPos[i] = MoverParameters->wiperSwitchPos; // update configuration
}
}
}
// sound
if (wiper_playSoundFromStart[i] && dWiperPos[i] == 1.0)
{
sWiperFromPark.play(sound_flags::exclusive);
wiper_playSoundFromStart[i] = false;
}
else
{
sWiperToPark.stop();
}
if (wiper_playSoundToStart[i] && dWiperPos[i] == 0.0)
{
sWiperToPark.play(sound_flags::exclusive);
wiper_playSoundToStart[i] = false;
}
else
{
sWiperToPark.stop();
}
}
}
// mirrors
if( (MoverParameters->Vel > MoverParameters->MirrorVelClose)
|| (MoverParameters->CabActive == 0) && (activation::mirrors)
@@ -5169,6 +5283,7 @@ void TDynamicObject::LoadMMediaFile( std::string const &TypeName, std::string co
std::string asAnimName;
bool Stop_InternalData = false;
pants = NULL; // wskaźnik pierwszego obiektu animującego dla pantografów
wipers = NULL; // wskaznik pierwszego obiektu animujacego dla wycieraczek
{
// preliminary check whether the file exists
cParser parser( TypeName + ".mmd", cParser::buffer_FILE, asBaseDir );
@@ -5255,13 +5370,17 @@ void TDynamicObject::LoadMMediaFile( std::string const &TypeName, std::string co
pAnimations.resize( iAnimations );
int i, j, k = 0, sm = 0;
for (j = 0; j < ANIM_TYPES; ++j)
for (i = 0; i < iAnimType[j]; ++i)
for (j = 0; j < ANIM_TYPES; ++j) // petla po wszystkich wpisach w animations
for (i = 0; i < iAnimType[j]; ++i) // petla iteruje sie tyle razy ile mamy wpisane w animations
{
if (j == ANIM_PANTS) // zliczamy poprzednie animacje
if (!pants)
if (iAnimType[ANIM_PANTS]) // o ile jakieś pantografy są (a domyślnie są)
pants = &pAnimations[k]; // zapamiętanie na potrzeby wyszukania submodeli
if (j == ANIM_WIPERS)
if (!wipers)
if (iAnimType[ANIM_WIPERS])
wipers = &pAnimations[k];
pAnimations[k].iShift = sm; // przesunięcie do przydzielenia wskaźnika
sm += pAnimations[k++].TypeSet(j, *MoverParameters); // ustawienie typu animacji i zliczanie tablicowanych submodeli
}
@@ -5834,6 +5953,52 @@ void TDynamicObject::LoadMMediaFile( std::string const &TypeName, std::string co
}
}
else if (token == "animwiperprefix:")
{
parser.getTokens(1, false);
parser >> token;
TSubModel *sm;
if (wipers)
{
for (int i = 0; i < iAnimType[ANIM_WIPERS]; i++) // zebranie wszystkich submodeli wycieraczek
{
dWiperPos.emplace_back(0.0); // dodajemy na koniec zeby sie miejsce tam zrobilo i nie bylo invalid addressow potem
asAnimName = token + std::to_string(i + 1);
// element wycieraczki nr 1
sm = GetSubmodelFromName(mdModel, asAnimName + "_p1");
wipers[i].smElement[0] = sm;
if (sm)
{
wipers[i].smElement[0]->WillBeAnimated();
// auto const offset{wipers[i].smElement[0]->offset()};
}
// element wycieraczki nr 2
sm = GetSubmodelFromName(mdModel, asAnimName + "_p2");
wipers[i].smElement[1] = sm;
if (sm)
{
wipers[i].smElement[1]->WillBeAnimated();
// auto const offset{wipers[i].smElement[0]->offset()};
}
// element wycieraczki nr 3
sm = GetSubmodelFromName(mdModel, asAnimName + "_p3");
wipers[i].smElement[2] = sm;
if (sm)
{
wipers[i].smElement[2]->WillBeAnimated();
// auto const offset{wipers[i].smElement[0]->offset()};
}
wipers[i].yUpdate = std::bind(&TDynamicObject::UpdateWiper, this, std::placeholders::_1);
wipers[i].fMaxDist = 150 * 150;
wipers[i].iNumber = i;
}
}
}
} while( ( token != "" )
&& ( token != "endmodels" ) );
@@ -6585,6 +6750,19 @@ void TDynamicObject::LoadMMediaFile( std::string const &TypeName, std::string co
m_powertrainsounds.rsEngageSlippery.m_frequencyfactor /= ( 1 + MoverParameters->nmax );
}
// dzwieki wycieraczkuf
else if (token == "wiperFromPark:")
{
sWiperFromPark.deserialize(parser, sound_type::single);
sWiperFromPark.owner(this);
}
else if (token == "wiperToPark:")
{
sWiperToPark.deserialize(parser, sound_type::single);
sWiperToPark.owner(this);
}
else if (token == "retarder:") {
m_powertrainsounds.retarder.deserialize(parser, sound_type::single, sound_parameters::amplitude | sound_parameters::frequency);
m_powertrainsounds.retarder.owner(this);

View File

@@ -22,6 +22,8 @@ http://mozilla.org/MPL/2.0/.
#include "sound.h"
#include "Spring.h"
#include <vector>
#define EU07_SOUND_BOGIESOUNDS
//---------------------------------------------------------------------------
@@ -36,7 +38,8 @@ int const ANIM_PANTS = 5; // pantografy
int const ANIM_STEAMS = 6; // napęd parowozu
int const ANIM_DOORSTEPS = 7;
int const ANIM_MIRRORS = 8;
int const ANIM_TYPES = 9; // Ra: ilość typów animacji
int const ANIM_WIPERS = 9;
int const ANIM_TYPES = 10; // Ra: ilość typów animacji
class TAnim;
//typedef void(__closure *TUpdate)(TAnim *pAnim); // typ funkcji aktualizującej położenie submodeli
@@ -291,7 +294,8 @@ private:
void UpdatePlatformTranslate(TAnim *pAnim); // doorstep animation, shift
void UpdatePlatformRotate(TAnim *pAnim); // doorstep animation, rotate
void UpdateMirror(TAnim *pAnim); // mirror animation
/*
void UpdateWiper(TAnim *pAnim); // wiper animation
/*
void UpdateLeverDouble(TAnim *pAnim); // animacja gałki zależna od double
void UpdateLeverFloat(TAnim *pAnim); // animacja gałki zależna od float
void UpdateLeverInt(TAnim *pAnim); // animacja gałki zależna od int (wartość)
@@ -315,6 +319,14 @@ private:
Math3D::vector3 vFloor; // podłoga dla ładunku
public:
TAnim *pants; // indeks obiektu animującego dla pantografu 0
TAnim *wipers; // wycieraczki
std::vector<double> wiperOutTimer = std::vector<double>(8, 0.0);
std::vector<double> wiperParkTimer = std::vector<double>(8, 0.0);
std::vector<int> workingSwitchPos = std::vector<int>(8, 0); // working switch position (to not break wipers when switching modes)
std::vector<double> dWiperPos; // timing na osi czasu animacji wycieraczki
std::vector<bool> wiperDirection = std::vector<bool>(8, false); // false - return direction; true - out direction
std::vector<bool> wiper_playSoundFromStart = std::vector<bool>(8, false);
std::vector<bool> wiper_playSoundToStart = std::vector<bool>(8, false);
double NoVoltTime; // czas od utraty zasilania
double dMirrorMoveL{ 0.0 };
double dMirrorMoveR{ 0.0 };
@@ -534,6 +546,8 @@ private:
springbrake_sounds m_springbrakesounds;
sound_source rsSlippery { sound_placement::external, EU07_SOUND_BRAKINGCUTOFFRANGE }; // moved from cab
sound_source sSand { sound_placement::external };
sound_source sWiperToPark { sound_placement::internal };
sound_source sWiperFromPark { sound_placement::internal };
// moving part and other external sounds
sound_source m_startjolt { sound_placement::general }; // movement start jolt, played once on initial acceleration at slow enough speed
bool m_startjoltplayed { false };

View File

@@ -609,6 +609,17 @@ struct TDEScheme
double Umax = 0.0; /*napiecie maksymalne*/
double Imax = 0.0; /*prad maksymalny*/
};
struct TWiperScheme
{
uint8_t byteSum = 0; // suma bitowa pracujacych wycieraczek
double WiperSpeed = 0.0; // predkosc wycieraczki
double interval = 0.0; // interwal pracy wycieraczki
double outBackDelay = 0.0; // czas po jakim wycieraczka zacznie wracac z konca do poczatku
};
typedef TWiperScheme TWiperSchemeTable[16];
typedef TDEScheme TDESchemeTable[33]; /*tablica rezystorow rozr.*/
struct TShuntScheme
{
@@ -1135,6 +1146,9 @@ class TMoverParameters
bool LocHandleTimeTraxx = false; /*hamulec dodatkowy typu traxx*/
double MBPM = 1.0; /*masa najwiekszego cisnienia*/
int wiperSwitchPos = 0; // pozycja przelacznika wycieraczek
double WiperAngle = {45.0}; // kat pracy wycieraczek
std::shared_ptr<TBrake> Hamulec;
std::shared_ptr<TDriverHandle> Handle;
std::shared_ptr<TDriverHandle> LocHandle;
@@ -1361,6 +1375,9 @@ class TMoverParameters
bool Flat = false;
double Vhyp = 1.0;
TDESchemeTable DElist;
TWiperSchemeTable WiperList;
int WiperListSize;
double Vadd = 1.0;
TMPTRelayTable MPTRelay;
int RelayType = 0;
@@ -2048,6 +2065,7 @@ private:
void LoadFIZ_UCList(std::string const &Input);
void LoadFIZ_DList( std::string const &Input );
void LoadFIZ_FFList( std::string const &Input );
void LoadFIZ_WiperList(std::string const &Input);
void LoadFIZ_LightsList( std::string const &Input );
void LoadFIZ_CompressorList(std::string const &Input);
void LoadFIZ_PowerParamsDecode( TPowerParameters &Powerparameters, std::string const Prefix, std::string const &Input );
@@ -2069,6 +2087,7 @@ private:
bool readPmaxList(std::string const &line);
bool readFFList( std::string const &line );
bool readWWList( std::string const &line );
bool readWiperList( std::string const &line );
bool readLightsList( std::string const &Input );
bool readCompressorList(std::string const &Input);
void BrakeValveDecode( std::string const &s ); //Q 20160719

View File

@@ -8927,7 +8927,7 @@ bool startBPT;
bool startMPT, startMPT0;
bool startRLIST, startUCLIST;
bool startDIZELMOMENTUMLIST, startDIZELV2NMAXLIST, startHYDROTCLIST, startPMAXLIST;
bool startDLIST, startFFLIST, startWWLIST;
bool startDLIST, startFFLIST, startWWLIST, startWiperList;
bool startLIGHTSLIST;
bool startCOMPRESSORLIST;
int LISTLINE;
@@ -9279,6 +9279,25 @@ bool TMoverParameters::readFFList( std::string const &line ) {
return true;
}
// parsowanie wiperList
bool TMoverParameters::readWiperList(std::string const& line)
{
cParser parser(line);
if (false == parser.getTokens(4, false))
{
WriteLog("Read WiperList: arguments missing in line " + std::to_string(LISTLINE + 1));
return false;
}
int idx = LISTLINE++;
if (idx >= sizeof(WiperList) / sizeof(TWiperScheme))
{
WriteLog("Read WiperList: number of entries exceeded capacity of the data table");
return false;
}
parser >> WiperList[idx].byteSum >> WiperList[idx].WiperSpeed >> WiperList[idx].interval >> WiperList[idx].outBackDelay;
return true;
}
// parsowanie WWList
bool TMoverParameters::readWWList( std::string const &line ) {
@@ -9462,6 +9481,7 @@ bool TMoverParameters::LoadFIZ(std::string chkpath)
startPMAXLIST = false;
startFFLIST = false;
startWWLIST = false;
startWiperList = false;
startLIGHTSLIST = false;
startCOMPRESSORLIST = false;
std::string file = TypeName + ".fiz";
@@ -9562,6 +9582,13 @@ bool TMoverParameters::LoadFIZ(std::string chkpath)
startFFLIST = false;
continue;
}
if (issection("endwl", inputline))
{
// skonczylismy czytac liste konfiguracji wycieraczek
startBPT = false;
startWiperList = false;
continue;
}
if( issection( "END-WWL", inputline ) ) {
startBPT = false;
startWWLIST = false;
@@ -9853,9 +9880,22 @@ bool TMoverParameters::LoadFIZ(std::string chkpath)
{
startBPT = false;
startWWLIST = true; LISTLINE = 0;
continue;
}
if (issection("WiperList:", inputline))
{
startBPT = false;
fizlines.emplace("WiperList", inputline);
startWiperList = true;
LISTLINE = 0;
LoadFIZ_WiperList(inputline);
continue;
}
if( issection( "LightsList:", inputline ) ) {
startBPT = false;
fizlines.emplace( "LightsList", inputline );
@@ -9922,6 +9962,11 @@ bool TMoverParameters::LoadFIZ(std::string chkpath)
readWWList( inputline );
continue;
}
if (true == startWiperList)
{
readWiperList(inputline);
continue;
}
if( true == startLIGHTSLIST ) {
readLightsList( inputline );
continue;
@@ -11277,6 +11322,13 @@ void TMoverParameters::LoadFIZ_FFList( std::string const &Input ) {
extract_value( RlistSize, "Size", Input, "" );
}
void TMoverParameters::LoadFIZ_WiperList(std::string const &Input)
{
extract_value(WiperListSize, "Size", Input, "");
extract_value(WiperAngle, "Angle", Input, "");
}
void TMoverParameters::LoadFIZ_LightsList( std::string const &Input ) {
extract_value( LightsPosNo, "Size", Input, "" );

View File

@@ -288,6 +288,10 @@ TTrain::commandhandler_map const TTrain::m_commandhandlers = {
{ user_command::pantographraiserear, &TTrain::OnCommand_pantographraiserear },
{ user_command::pantographlowerfront, &TTrain::OnCommand_pantographlowerfront },
{ user_command::pantographlowerrear, &TTrain::OnCommand_pantographlowerrear },
{user_command::wiperswitchincrease, &TTrain::OnCommand_wiperswitchincrease},
{user_command::wiperswitchdecrease, &TTrain::OnCommand_wiperswitchdecrease},
{ user_command::pantographlowerall, &TTrain::OnCommand_pantographlowerall },
{ user_command::pantographselectnext, &TTrain::OnCommand_pantographselectnext },
{ user_command::pantographselectprevious, &TTrain::OnCommand_pantographselectprevious },
@@ -2183,6 +2187,31 @@ void TTrain::OnCommand_mubrakingindicatortoggle( TTrain *Train, command_data con
}
}
void TTrain::OnCommand_wiperswitchincrease(TTrain *Train, command_data const &Command)
{
if (Command.action == GLFW_PRESS)
{
Train->mvOccupied->wiperSwitchPos++;
if (Train->mvOccupied->wiperSwitchPos > Train->mvOccupied->WiperListSize)
Train->mvOccupied->wiperSwitchPos = Train->mvOccupied->WiperListSize - 1;
// Visual feedback
Train->ggWiperSw.UpdateValue(Train->mvOccupied->wiperSwitchPos, Train->dsbSwitch);
}
}
void TTrain::OnCommand_wiperswitchdecrease(TTrain *Train, command_data const &Command)
{
if (Command.action == GLFW_PRESS)
{
Train->mvOccupied->wiperSwitchPos--;
if (Train->mvOccupied->wiperSwitchPos < 0)
Train->mvOccupied->wiperSwitchPos = 0;
// visual feedback
Train->ggWiperSw.UpdateValue(Train->mvOccupied->wiperSwitchPos, Train->dsbSwitch);
}
}
void TTrain::OnCommand_reverserincrease( TTrain *Train, command_data const &Command ) {
if( Command.action == GLFW_PRESS ) {
@@ -6963,6 +6992,7 @@ void TTrain::OnCommand_vehicleboost(TTrain *Train, const command_data &Command)
}
}
// cab movement update, fixed step part
void TTrain::UpdateCab() {
@@ -8134,6 +8164,7 @@ bool TTrain::Update( double const Deltatime )
ggBrakeProfileG.Update();
ggBrakeProfileR.Update();
ggBrakeOperationModeCtrl.Update();
ggWiperSw.Update();
ggMaxCurrentCtrl.UpdateValue(
( true == mvControlled->ShuntModeAllow ?
( true == mvControlled->ShuntMode ?
@@ -9613,6 +9644,7 @@ void TTrain::clear_cab_controls()
ggBrakeProfileG.Clear();
ggBrakeProfileR.Clear();
ggBrakeOperationModeCtrl.Clear();
ggWiperSw.Clear();
ggMaxCurrentCtrl.Clear();
ggMainOffButton.Clear();
ggMainOnButton.Clear();
@@ -10082,6 +10114,12 @@ void TTrain::set_cab_controls( int const Cab ) {
1.f :
0.f );
}
if (ggWiperSw.SubModel != nullptr)
{
ggWiperSw.PutValue(mvOccupied->wiperSwitchPos);
}
if (ggBrakeOperationModeCtrl.SubModel != nullptr) {
ggBrakeOperationModeCtrl.PutValue(
(mvOccupied->BrakeOpModeFlag > 0 ?
@@ -10556,7 +10594,8 @@ bool TTrain::initialize_gauge(cParser &Parser, std::string const &Label, int con
{ "invertertoggle11_bt:", ggInverterToggleButtons[10] },
{ "invertertoggle12_bt:", ggInverterToggleButtons[11] },
{"pantvalvesupdate_bt:", ggPantValvesUpdate},
{"pantvalvesoff_bt:", ggPantValvesOff}
{"pantvalvesoff_bt:", ggPantValvesOff},
{"wipers_sw:", ggWiperSw}
};
{
auto const lookup { gauges.find( Label ) };

View File

@@ -226,6 +226,8 @@ class TTrain {
// command handlers
// NOTE: we're currently using universal handlers and static handler map but it may be beneficial to have these implemented on individual class instance basis
// TBD, TODO: consider this approach if we ever want to have customized consist behaviour to received commands, based on the consist/vehicle type or whatever
static void OnCommand_wiperswitchincrease(TTrain *Train, command_data const &Command);
static void OnCommand_wiperswitchdecrease(TTrain *Train, command_data const &Command);
static void OnCommand_aidriverenable( TTrain *Train, command_data const &Command );
static void OnCommand_aidriverdisable( TTrain *Train, command_data const &Command );
static void OnCommand_jointcontrollerset( TTrain *Train, command_data const &Command );
@@ -544,6 +546,8 @@ public: // reszta może by?publiczna
TGauge ggBrakeProfileR; // nastawiacz PR - hamowanie dwustopniowe
TGauge ggBrakeOperationModeCtrl; //przełącznik trybu pracy PS/PN/EP/MED
TGauge ggWiperSw; // przelacznik wycieraczek
TGauge ggMaxCurrentCtrl;
TGauge ggMainOffButton;

View File

@@ -373,6 +373,8 @@ commanddescription_sequence Commands_descriptions = {
{ "spawntrainset", command_target::simulation, command_mode::oneoff },
{ "destroytrainset", command_target::simulation, command_mode::oneoff },
{ "quitsimulation", command_target::simulation, command_mode::oneoff },
{"wiperswitchincrease", command_target::vehicle, command_mode::oneoff},
{"wiperswitchdecrease", command_target::vehicle, command_mode::oneoff},
};
// Maps of command and coresponding strings
@@ -727,6 +729,8 @@ std::unordered_map<std::string, user_command> commandMap = {
{"spawntrainset", user_command::spawntrainset},
{"destroytrainset", user_command::destroytrainset},
{"quitsimulation", user_command::quitsimulation},
{"wiperswitchincrease", user_command::wiperswitchincrease},
{"wiperswitchdecrease", user_command::wiperswitchdecrease},
{"none", user_command::none}};
} // simulation

View File

@@ -371,6 +371,8 @@ enum class user_command {
spawntrainset,
destroytrainset,
quitsimulation,
wiperswitchincrease,
wiperswitchdecrease,
none = -1
};

View File

@@ -1109,6 +1109,10 @@ drivermouse_input::default_bindings() {
{ "invertertoggle12_bt:",{
user_command::invertertoggle12,
user_command::none } },
{ "wipers_sw:",{
user_command::wiperswitchincrease,
user_command::wiperswitchdecrease
} },
};
}

View File

@@ -335,6 +335,14 @@ interpolate( Type_ const &First, Type_ const &Second, double const Factor ) {
return static_cast<Type_>( ( First * ( 1.0 - Factor ) ) + ( Second * Factor ) );
}
template <typename Type_> Type_ smoothInterpolate(Type_ const &First, Type_ const &Second, double Factor)
{
// Apply smoothing (ease-in-out quadratic)
Factor = Factor * Factor * (3 - 2 * Factor);
return static_cast<Type_>((First * (1.0 - Factor)) + (Second * Factor));
}
// tests whether provided points form a degenerate triangle
template <typename VecType_>
bool