/* 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, Maciej Czapkiewicz and others */ #include "stdafx.h" #include "Model3d.h" #include "Globals.h" #include "Logs.h" #include "utilities.h" #include "renderer.h" #include "Timer.h" #include "simulation.h" #include "simulationtime.h" #include "mtable.h" #include "sn_utils.h" //--------------------------------------------------------------------------- using namespace Mtable; float TSubModel::fSquareDist = 0.f; std::uintptr_t TSubModel::iInstance; // numer renderowanego egzemplarza obiektu texture_handle const *TSubModel::ReplacableSkinId = NULL; int TSubModel::iAlpha = 0x30300030; // maska do testowania flag tekstur wymiennych TModel3d *TSubModel::pRoot; // Ra: tymczasowo wskaźnik na model widoczny z submodelu std::string *TSubModel::pasText; // przykłady dla TSubModel::iAlpha: // 0x30300030 - wszystkie bez kanału alfa // 0x31310031 - tekstura -1 używana w danym cyklu, pozostałe nie // 0x32320032 - tekstura -2 używana w danym cyklu, pozostałe nie // 0x34340034 - tekstura -3 używana w danym cyklu, pozostałe nie // 0x38380038 - tekstura -4 używana w danym cyklu, pozostałe nie // 0x3F3F003F - wszystkie wymienne tekstury używane w danym cyklu // Ale w TModel3d okerśla przezroczystość tekstur wymiennych! TSubModel::~TSubModel() { if (iFlags & 0x0200) { // wczytany z pliku tekstowego musi sam posprzątać SafeDelete(Next); SafeDelete(Child); delete fMatrix; // własny transform trzeba usunąć (zawsze jeden) } delete[] smLetter; // używany tylko roboczo dla TP_TEXT, do przyspieszenia // wyświetlania }; void TSubModel::Name_Material(std::string const &Name) { // ustawienie nazwy submodelu, o // ile nie jest wczytany z E3D if (iFlags & 0x0200) { // tylko jeżeli submodel zosta utworzony przez new m_materialname = Name; } }; void TSubModel::Name(std::string const &Name) { // ustawienie nazwy submodelu, o ile // nie jest wczytany z E3D if (iFlags & 0x0200) pName = Name; }; // sets rgb components of diffuse color override to specified value void TSubModel::SetDiffuseOverride(glm::vec3 const &Color, bool const Includechildren, bool const Includesiblings) { if (eType == TP_FREESPOTLIGHT) { DiffuseOverride = Color; } if (true == Includesiblings) { auto sibling{this}; while ((sibling = sibling->Next) != nullptr) { sibling->SetDiffuseOverride(Color, Includechildren, false); // no need for all siblings to duplicate the work } } if ((true == Includechildren) && (Child != nullptr)) { Child->SetDiffuseOverride(Color, Includechildren, true); // node's children include child's siblings and children } } std::optional TSubModel::GetDiffuse(float Includesiblings) { if (eType == TP_FREESPOTLIGHT) { if (DiffuseOverride.x >= 0.0f) return DiffuseOverride; else return glm::vec3(f4Diffuse); } if (Includesiblings) { auto sibling = this; while ((sibling = sibling->Next)) { auto result = sibling->GetDiffuse(true); if (result) return result; } } if (Child) return Child->GetDiffuse(true); return std::nullopt; } // sets visibility level (alpha component) to specified value void TSubModel::SetVisibilityLevel(float const Level, bool const Includechildren, bool const Includesiblings) { fVisible = Level; if (true == Includesiblings) { auto sibling{this}; while ((sibling = sibling->Next) != nullptr) { sibling->SetVisibilityLevel(Level, Includechildren, false); // no need for all siblings to duplicate the work } } if ((true == Includechildren) && (Child != nullptr)) { Child->SetVisibilityLevel(Level, Includechildren, true); // node's children include child's siblings and children } } // sets light level (alpha component of illumination color) to specified value void TSubModel::SetLightLevel(glm::vec4 const &Level, bool const Includechildren, bool const Includesiblings) { /* f4Emision = Level; */ f4Diffuse = {Level.r, Level.g, Level.b, f4Diffuse.a}; f4Emision.a = Level.a; if (true == Includesiblings) { auto sibling{this}; while ((sibling = sibling->Next) != nullptr) { sibling->SetLightLevel(Level, Includechildren, false); // no need for all siblings to duplicate the work } } if ((true == Includechildren) && (Child != nullptr)) { Child->SetLightLevel(Level, Includechildren, true); // node's children include child's siblings and children } } // sets activation threshold of self-illumination to specitied value void TSubModel::SetSelfIllum(float const Threshold, bool const Includechildren, bool const Includesiblings) { fLight = Threshold; if (true == Includesiblings) { auto sibling{this}; while ((sibling = sibling->Next) != nullptr) { sibling->SetSelfIllum(Threshold, Includechildren, false); // no need for all siblings to duplicate the work } } if ((true == Includechildren) && (Child != nullptr)) { Child->SetSelfIllum(Threshold, Includechildren, true); // node's children include child's siblings and children } } int TSubModel::SeekFaceNormal(std::vector const &Masks, int const Startface, unsigned int const Mask, glm::vec3 const &Position, gfx::vertex_array const &Vertices) { // szukanie punktu stycznego do (pt), zwraca numer wierzchołka, a nie trójkąta int facecount = m_geometry.vertex_count / 3; for (int faceidx = Startface; faceidx < facecount; ++faceidx) { // pętla po trójkątach, od trójkąta (f) if (Masks[faceidx] & Mask) { // jeśli wspólna maska powierzchni for (int vertexidx = 0; vertexidx < 3; ++vertexidx) { if (Vertices[3 * faceidx + vertexidx].position == Position) { return 3 * faceidx + vertexidx; } } } } return -1; // nie znaleziono stycznego wierzchołka } float emm1[] = {1, 1, 1, 0}; float emm2[] = {0, 0, 0, 1}; inline void readColor(cParser &parser, glm::vec4 &color) { int discard; parser.getTokens(4, false); parser >> discard >> color.r >> color.g >> color.b; color /= 255.0f; }; inline void readMatrix(cParser &parser, float4x4 &matrix) { // Ra: wczytanie transforma parser.getTokens(16, false); for (int x = 0; x <= 3; ++x) // wiersze for (int y = 0; y <= 3; ++y) // kolumny parser >> matrix(x)[y]; }; template void UserdataParse(cParser &parser, gfx::vertex_userdata &vertex) { parser.getTokens(4); union { T InType; float FloatType; } buf{}; for (int i = 0; i < 4; ++i) { parser >> buf.InType; vertex.data[i] = buf.FloatType; } } std::pair TSubModel::Load(cParser &parser, bool dynamic) { // Ra: VBO tworzone na poziomie modelu, a nie submodeli auto token{parser.getToken()}; if (token != "type:") { std::string errormessage{"Bad model: expected submodel type definition not found while loading model \"" + parser.Name() + "\"" + "\ncurrent model data stream content: \""}; auto count{10}; while ((true == parser.getTokens()) && (false == (token = parser.peek()).empty()) && (token != "parent:")) { // skip data until next submodel, dump first few tokens in the error message if (--count > 0) { errormessage += token + " "; } } errormessage += "(...)\""; ErrorLog(errormessage); return {0, 0}; } { auto const type{parser.getToken()}; if (type == "mesh") eType = GL_TRIANGLES; // submodel - trójkaty else if (type == "point") eType = GL_POINTS; // co to niby jest? else if (type == "freespotlight") eType = TP_FREESPOTLIGHT; // światełko else if (type == "text") eType = TP_TEXT; // wyświetlacz tekstowy (generator napisów) else if (type == "stars") eType = TP_STARS; // wiele punktów świetlnych }; parser.ignoreToken(); parser.getTokens(1, false); // nazwa submodelu bez zmieny na małe parser >> token; Name(token); if (dynamic) { // dla pojazdu, blokujemy załączone submodele, które mogą być nieobsługiwane if ((token.size() >= 3) && (token.find("_on") + 3 == token.length())) { // jeśli nazwa kończy się na "_on" to domyślnie wyłączyć, żeby się nie nakładało z obiektem "_off" iVisible = 0; } } else { // dla pozostałych modeli blokujemy zapalone światła, które mogą być nieobsługiwane if (token.compare(0, 8, "Light_On") == 0) { // jeśli nazwa zaczyna się od "Light_On" to domyślnie wyłączyć, żeby się nie nakładało z obiektem "Light_Off" iVisible = 0; } } if (parser.expectToken("anim:")) // Ra: ta informacja by się przydała! { // rodzaj animacji std::string type = parser.getToken(); if (type != "false") { iFlags |= 0x4000; // jak animacja, to trzeba przechowywać macierz zawsze if (type == "seconds_jump") b_Anim = b_aAnim = TAnimType::at_SecondsJump; // sekundy z przeskokiem else if (type == "minutes_jump") b_Anim = b_aAnim = TAnimType::at_MinutesJump; // minuty z przeskokiem else if (type == "hours_jump") b_Anim = b_aAnim = TAnimType::at_HoursJump; // godziny z przeskokiem else if (type == "hours24_jump") b_Anim = b_aAnim = TAnimType::at_Hours24Jump; // godziny z przeskokiem else if (type == "seconds") b_Anim = b_aAnim = TAnimType::at_Seconds; // minuty płynnie else if (type == "minutes") b_Anim = b_aAnim = TAnimType::at_Minutes; // minuty płynnie else if (type == "hours") b_Anim = b_aAnim = TAnimType::at_Hours; // godziny płynnie else if (type == "hours24") b_Anim = b_aAnim = TAnimType::at_Hours24; // godziny płynnie else if (type == "billboard") b_Anim = b_aAnim = TAnimType::at_Billboard; // obrót w pionie do kamery else if (type == "wind") b_Anim = b_aAnim = TAnimType::at_Wind; // ruch pod wpływem wiatru else if (type == "sky") b_Anim = b_aAnim = TAnimType::at_Sky; // aniamacja nieba else if (type == "digital") b_Anim = b_aAnim = TAnimType::at_Digital; // licznik mechaniczny else if (type == "digiclk") b_Anim = b_aAnim = TAnimType::at_DigiClk; // zegar cyfrowy else b_Anim = b_aAnim = TAnimType::at_Undefined; // nieznana forma animacji } } if (eType < TP_ROTATOR) readColor(parser, f4Ambient); // ignoruje token przed readColor(parser, f4Diffuse); if (eType < TP_ROTATOR) { readColor(parser, f4Specular); if (pName == "cien") { // crude workaround to kill specular on shadow geometry of legacy models f4Specular = glm::vec4{0.0f, 0.0f, 0.0f, 1.0f}; } } parser.ignoreToken(); // zignorowanie nazwy "SelfIllum:" { std::string light = parser.getToken(); if (light == "true") fLight = 2.0; // zawsze świeci else if (light == "false") fLight = -1.0; // zawsze ciemy else fLight = std::stod(light); }; if (eType == TP_FREESPOTLIGHT) { if (!parser.expectToken("nearattenstart:")) { Error("Model light parse failure!"); } std::string discard; discard.reserve(64); parser.getTokens(13, false); parser >> fNearAttenStart >> discard >> fNearAttenEnd >> discard >> bUseNearAtten >> discard >> iFarAttenDecay >> discard >> fFarDecayRadius >> discard >> fCosFalloffAngle // kąt liczony dla średnicy, a nie promienia >> discard >> fCosHotspotAngle; // kąt liczony dla średnicy, a nie promienia // trzeba pobrac do kolejki 2 tokeny parser.getTokens(2); if (parser.peek() == "hotspotpower:") { float multiplier; parser >> discard >> multiplier; WriteLog("Found hotspot power!"); // recaluclate multiplier diffuseMultiplier = multiplier / 100.f; // apply color only on nvrenderer if (Global.NvRenderer) f4Diffuse *= diffuseMultiplier; } else parser.autoclear(false); // convert conve parameters if specified in degrees if (fCosFalloffAngle > 1.0) { fCosFalloffAngle = std::cos(DegToRad(0.5f * fCosFalloffAngle)); } if (fCosHotspotAngle > 1.0) { fCosHotspotAngle = std::cos(DegToRad(0.5f * fCosHotspotAngle)); } m_geometry.vertex_count = 1; iFlags |= 0x4030; // drawn both in solid (light point) and transparent (light glare) phases } else if (eType < TP_ROTATOR) { std::string discard; parser.getTokens(6, false); parser >> discard >> bWire >> discard >> fWireSize >> discard >> Opacity; // wymagane jest 0 dla szyb, 100 idzie w nieprzezroczyste if (Opacity > 1.f) { Opacity = std::min(1.f, Opacity * 0.01f); } if (Opacity < -1.f) { Opacity = std::max(-1.f, Opacity * 0.01f); } if (!parser.expectToken("map:")) Error("Model map parse failure!"); std::string material = parser.getToken(); std::replace(material.begin(), material.end(), '\\', '/'); if (material == "none") { // rysowanie podanym kolorem Name_Material("colored"); m_material = GfxRenderer->Fetch_Material(m_materialname); iFlags |= 0x10; // rysowane w cyklu nieprzezroczystych } else if (material.find("replacableskin") != material.npos) { // McZapkie-060702: zmienialne skory modelu m_material = -1; iFlags |= (Opacity < 0.999) ? 1 : 0x10; // zmienna tekstura 1 } else if (material == "-1") { m_material = -1; iFlags |= (Opacity < 0.999) ? 1 : 0x10; // zmienna tekstura 1 } else if (material == "-2") { m_material = -2; iFlags |= (Opacity < 0.999) ? 2 : 0x10; // zmienna tekstura 2 } else if (material == "-3") { m_material = -3; iFlags |= (Opacity < 0.999) ? 4 : 0x10; // zmienna tekstura 3 } else if (material == "-4") { m_material = -4; iFlags |= (Opacity < 0.999) ? 8 : 0x10; // zmienna tekstura 4 } else { Name_Material(material); /* if( material.find_first_of( "/" ) == material.npos ) { // jeśli tylko nazwa pliku, to dawać bieżącą ścieżkę do tekstur material.insert( 0, Global.asCurrentTexturePath ); } */ m_material = GfxRenderer->Fetch_Material(material); // renderowanie w cyklu przezroczystych tylko jeśli: // 1. Opacity=0 (przejściowo <1, czy tam <100) iFlags |= Opacity < 0.999f ? 0x20 : 0x10; // 0x20-przezroczysta, 0x10-nieprzezroczysta }; } else if (eType == TP_STARS) { m_material = GfxRenderer->Fetch_Material("stars"); iFlags |= 0x10; } else { iFlags |= 0x10; } if (m_material > 0) { const IMaterial *mat = GfxRenderer->Material(m_material); /* // if material does have opacity set, replace submodel opacity with it if (mat.opacity) { iFlags &= ~0x30; if (*mat.opacity == 0.0f) iFlags |= 0x20; // translucent else iFlags |= 0x10; // opaque } */ // and same thing with selfillum if (mat->GetSelfillum()) fLight = *mat->GetSelfillum(); } // visibility range std::string discard; parser.getTokens(5, false); parser.autoclear(true); parser >> discard >> fSquareMaxDist >> discard >> fSquareMinDist >> discard; if (fSquareMaxDist <= 0.0) { // 15km to więcej, niż się obecnie wyświetla fSquareMaxDist = 15000.0; } fSquareMaxDist *= fSquareMaxDist; fSquareMinDist *= fSquareMinDist; // transformation matrix fMatrix = new float4x4(); readMatrix(parser, *fMatrix); // wczytanie transform if (Parent != nullptr) transformscalestack = Parent->transformscalestack; if (!fMatrix->IdentityIs()) { iFlags |= 0x8000; // transform niejedynkowy - trzeba go przechować // check the scaling auto const matrix = glm::make_mat4(fMatrix->readArray()); glm::vec3 const scale{glm::length(glm::vec3(glm::column(matrix, 0))), glm::length(glm::vec3(glm::column(matrix, 1))), glm::length(glm::vec3(glm::column(matrix, 2)))}; if ((std::abs(scale.x - 1.0f) > 0.01) || (std::abs(scale.y - 1.0f) > 0.01) || (std::abs(scale.z - 1.0f) > 0.01)) { ErrorLog("Bad model: transformation matrix for sub-model \"" + pName + "\" imposes geometry scaling (factors: " + to_string(scale) + ")", logtype::model); m_normalizenormals = (((std::abs(scale.x - scale.y) < 0.01f) && (std::abs(scale.y - scale.z) < 0.01f)) ? rescale : normalize); } transformscalestack *= (scale.x + scale.y + scale.z) / 3.0f; } if (eType < TP_ROTATOR) { // wczytywanie wierzchołków token = parser.getToken(); bool has_userdata = false; if (token == "userdata:") { has_userdata = parser.getToken(); token = parser.getToken(); } if (token == "numindices:") // optional block, potentially preceeding vertex list { m_geometry.index_count = parser.getToken(false); Indices.resize(m_geometry.index_count); for (auto idx = 0; idx < m_geometry.index_count; ++idx) { Indices[idx] = parser.getToken(false); } token = parser.getToken(); } if ((token == "numverts:") || (token == "numverts")) { // normalna lista wierzchołków /* // Ra 15-01: to wczytać jako tekst - jeśli pierwszy znak zawiera "*", to // dalej będzie nazwa wcześniejszego submodelu, z którego należy wziąć wierzchołki // zapewni to jakąś zgodność wstecz, bo zamiast liczby będzie ciąg, którego // wartość powinna być uznana jako zerowa token = parser.getToken(); if( token.front() == '*' ) { // jeśli pierwszy znak jest gwiazdką, poszukać submodelu o nazwie bez tej gwiazdki i wziąć z niego wierzchołki Error( "Vertices reference not yet supported!" ); } */ m_geometry.vertex_count = parser.getToken(false); if ((m_geometry.index_count <= 0) && (m_geometry.vertex_count % 3 != 0)) { m_geometry.vertex_count = 0; Error("Bad model: incomplete triangle encountered in submodel \"" + pName + "\""); return {0, 0}; } if (m_geometry.vertex_count) { Vertices.resize(m_geometry.vertex_count); if (m_geometry.index_count) { // indexed geometry chunks are expected to come with pre-generated normals and tangents, to avoid hassle required to generate them manually auto vertices{std::begin(Vertices)}; for (auto idx = 0; idx < m_geometry.vertex_count; ++idx) { auto vertex{vertices + idx}; parser.getTokens(3 + 3 + 2 + 4, false); parser >> vertex->position.x >> vertex->position.y >> vertex->position.z >> vertex->normal.x >> vertex->normal.y >> vertex->normal.z >> vertex->texture.s >> vertex->texture.t >> vertex->tangent.x >> vertex->tangent.y >> vertex->tangent.z >> vertex->tangent.w; } } else { // legacy geometry, more or less incomplete // to stay on the safe side we'll potentially need to convert indexed geometry to raw triangle form, cal auto vertices{std::begin(Vertices)}; int facecount = m_geometry.vertex_count / 3; std::vector sg; sg.resize(facecount); // maski przynależności trójkątów do powierzchni std::vector wsp; wsp.resize(m_geometry.vertex_count); // z którego wierzchołka kopiować wektor normalny int maska = 0; int vertexidx = 0; // used to keep track of vertex indices in source file for (auto idx = 0; idx < m_geometry.vertex_count; ++idx) { ++vertexidx; // Ra: z konwersją na układ scenerii - będzie wydajniejsze wyświetlanie wsp[idx] = -1; // wektory normalne nie są policzone dla tego wierzchołka if ((idx % 3) == 0) { // jeśli będzie maska -1, to dalej będą wierzchołki z wektorami normalnymi, podanymi jawnie maska = parser.getToken(false); // maska powierzchni trójkąta // dla maski -1 będzie 0, czyli nie ma wspólnych wektorów normalnych sg[idx / 3] = ((maska == -1) ? 0 : maska); } auto vertex{vertices + idx}; parser.getTokens(3, false); parser >> vertex->position.x >> vertex->position.y >> vertex->position.z; if (maska == -1) { // jeśli wektory normalne podane jawnie parser.getTokens(3, false); parser >> vertex->normal.x >> vertex->normal.y >> vertex->normal.z; if (glm::length2(vertex->normal) > 0.0f) { glm::normalize(vertex->normal); } else { WriteLog("Bad model: zero length normal vector specified in submodel \"" + pName + "\", vertex " + std::to_string(idx), logtype::model); } wsp[idx] = idx; // wektory normalne "są już policzone" } parser.getTokens(2, false); parser >> vertex->texture.s >> vertex->texture.t; if (idx % 3 == 2) { // jeżeli wczytano 3 punkty if (true == degenerate(vertex->position, (vertex - 1)->position, (vertex - 2)->position)) { // jeżeli punkty się nakładają na siebie --facecount; // o jeden trójkąt mniej m_geometry.vertex_count -= 3; // czyli o 3 wierzchołki idx -= 3; // wczytanie kolejnego w to miejsce WriteLog("Bad model: degenerated triangle ignored in: \"" + pName + "\", vertices " + std::to_string(vertexidx - 2) + "-" + std::to_string(vertexidx), logtype::model); } if (idx > 0) { // jeśli pierwszy trójkąt będzie zdegenerowany, to zostanie usunięty i nie ma co sprawdzać if ((glm::length((vertex)->position - (vertex - 1)->position) > 1000.0) || (glm::length((vertex - 1)->position - (vertex - 2)->position) > 1000.0) || (glm::length((vertex - 2)->position - (vertex)->position) > 1000.0)) { // jeżeli są dalej niż 2km od siebie //Ra 15-01: // obiekt wstawiany nie powinien być większy niż 300m (trójkąty terenu w E3D mogą mieć 1.5km) --facecount; // o jeden trójkąt mniej m_geometry.vertex_count -= 3; // czyli o 3 wierzchołki idx -= 3; // wczytanie kolejnego w to miejsce WriteLog("Bad model: too large triangle ignored in: \"" + pName + "\"", logtype::model); } } } } std::vector facenormals; facenormals.reserve(facecount); for (int i = 0; i < facecount; ++i) { // pętla po trójkątach - będzie szybciej, jak wstępnie przeliczymy normalne trójkątów auto const vertex{vertices + (i * 3)}; auto facenormal = glm::cross(vertex->position - (vertex + 1)->position, vertex->position - (vertex + 2)->position); facenormals.emplace_back(glm::length2(facenormal) > 0.0f ? glm::normalize(facenormal) : glm::vec3()); } glm::vec3 vertexnormal; // roboczy wektor normalny for (int vertexidx = 0; vertexidx < m_geometry.vertex_count; ++vertexidx) { // pętla po wierzchołkach trójkątów auto vertex{vertices + vertexidx}; if (wsp[vertexidx] >= 0) { // jeśli już był liczony wektor normalny z użyciem tego wierzchołka to wystarczy skopiować policzony wcześniej vertex->normal = (vertices + wsp[vertexidx])->normal; } else { // inaczej musimy dopiero policzyć auto const faceidx = vertexidx / 3; // numer trójkąta vertexnormal = glm::vec3(); // liczenie zaczynamy od zera auto adjacenvertextidx = vertexidx; // zaczynamy dodawanie wektorów normalnych od własnego while (adjacenvertextidx >= 0) { // sumowanie z wektorem normalnym sąsiada (włącznie ze sobą) if (glm::dot(vertexnormal, facenormals[adjacenvertextidx / 3]) > -0.99f) { wsp[adjacenvertextidx] = vertexidx; // informacja, że w tym wierzchołku jest już policzony wektor normalny vertexnormal += facenormals[adjacenvertextidx / 3]; } /* else { ErrorLog( "Bad model: opposite normals in the same smoothing group, check sub-model \"" + pName + "\" for two-sided faces and/or scaling", logtype::model ); } */ // i szukanie od kolejnego trójkąta adjacenvertextidx = SeekFaceNormal(sg, adjacenvertextidx / 3 + 1, sg[faceidx], vertex->position, Vertices); } // Ra 15-01: należało by jeszcze uwzględnić skalowanie wprowadzane przez transformy, aby normalne po przeskalowaniu były jednostkowe if (glm::length2(vertexnormal) == 0.0f) { WriteLog("Bad model: zero length normal vector generated for sub-model \"" + pName + "\"", logtype::model); } vertex->normal = (glm::length2(vertexnormal) > 0.0f ? glm::normalize(vertexnormal) : facenormals[vertexidx / 3]); // przepisanie do wierzchołka trójkąta } } Vertices.resize(m_geometry.vertex_count); // in case we had some degenerate triangles along the way gfx::calculate_indices(Indices, Vertices, Userdata, transformscalestack); gfx::calculate_tangents(Vertices, Indices, GL_TRIANGLES); // update values potentially changed by indexing m_geometry.index_count = Indices.size(); m_geometry.vertex_count = Vertices.size(); } if (has_userdata) { Userdata.resize(m_geometry.vertex_count); auto userdata{std::begin(Userdata)}; for (auto idx = 0; idx < m_geometry.vertex_count; ++idx) { auto vertex{userdata + idx}; UserdataParse(parser, *vertex); } } } else // gdy brak wierzchołków { eType = TP_ROTATOR; // submodel pomocniczy, ma tylko macierz przekształcenia // dla formalności m_geometry.vertex_offset = 0; m_geometry.vertex_count = 0; } } // obsługa submodelu z własną listą wierzchołków } else if (eType == TP_STARS) { // punkty świecące dookólnie - składnia jak // dla smt_Mesh std::string discard; parser.getTokens(2, false); parser >> discard >> m_geometry.vertex_count; Vertices.resize(m_geometry.vertex_count); int idx; unsigned int color; auto vertices{std::begin(Vertices)}; for (idx = 0; idx < m_geometry.vertex_count; ++idx) { if (idx % 3 == 0) { parser.ignoreToken(); // maska powierzchni trójkąta } auto vertex{vertices + idx}; parser.getTokens(5, false); parser >> vertex->position.x >> vertex->position.y >> vertex->position.z >> color // zakodowany kolor >> discard; vertex->normal = {((color) & 0xff) / 255.0f, // R ((color >> 8) & 0xff) / 255.0f, // G ((color >> 16) & 0xff) / 255.0f}; // B } } else if (eType == TP_FREESPOTLIGHT) { // single light points only have single data point, duh Vertices.emplace_back(); m_geometry.vertex_count = 1; } // Visible=true; //się potem wyłączy w razie potrzeby // iFlags|=0x0200; //wczytano z pliku tekstowego (jest właścicielem tablic) if (m_geometry.vertex_count < 1) iFlags &= ~0x3F; // cykl renderowania uzależniony od potomnych return {m_geometry.index_count, m_geometry.vertex_count}; // do określenia wielkości VBO }; /* int TSubModel::TriangleAdd(TModel3d *m, material_handle tex, int tri) { // dodanie trójkątów do submodelu, używane przy tworzeniu E3D terenu TSubModel *s = this; while (s ? (s->m_material != tex) : false) { // szukanie submodelu o danej teksturze if (s == this) s = Child; else s = s->Next; } if (!s) { if (m_material <= 0) s = this; // użycie głównego else { // dodanie nowego submodelu do listy potomnych s = new TSubModel(); m->AddTo(this, s); } s->Name_Material(GfxRenderer->Material(tex).name); s->m_material = tex; s->eType = GL_TRIANGLES; } if (s->iNumVerts < 0) s->iNumVerts = tri; // bo na początku jest -1, czyli że nie wiadomo else s->iNumVerts += tri; // aktualizacja ilości wierzchołków return s->iNumVerts - tri; // zwraca pozycję tych trójkątów w submodelu }; */ /* basic_vertex *TSubModel::TrianglePtr(int tex, int pos, glm::vec3 const &Ambient, glm::vec3 const &Diffuse, glm::vec3 const &Specular ) { // zwraca wskaźnik do wypełnienia tabeli wierzchołków, używane przy tworzeniu E3D terenu TSubModel *s = this; while (s ? s->TextureID != tex : false) { // szukanie submodelu o danej teksturze if (s == this) s = Child; else s = s->Next; } if (!s) return NULL; // coś nie tak poszło if (!s->Vertices) { // utworznie tabeli trójkątów s->Vertices = new basic_vertex[s->iNumVerts]; s->iVboPtr = iInstance; // pozycja submodelu w tabeli wierzchołków iInstance += s->iNumVerts; // pozycja dla następnego } s->ColorsSet(Ambient, Diffuse, Specular); // ustawienie kolorów świateł return s->Vertices + pos; // wskaźnik na wolne miejsce w tabeli wierzchołków }; */ void TSubModel::InitialRotate(bool doit) { // konwersja układu współrzędnych na zgodny ze scenerią if (iFlags & 0xC000) // jeśli jest animacja albo niejednostkowy transform { // niejednostkowy transform jest mnożony i wystarczy zabawy if (doit) { // obrót lewostronny if (!fMatrix) { // macierzy może nie być w dodanym "bananie" fMatrix = new float4x4(); // tworzy macierz o przypadkowej zawartości fMatrix->Identity(); // a zaczynamy obracanie od jednostkowej } iFlags |= 0x8000; // po obróceniu będzie raczej niejedynkowy matrix fMatrix->InitialRotate(); // zmiana znaku X oraz zamiana Y i Z if (fMatrix->IdentityIs()) iFlags &= ~0x8000; // jednak jednostkowa po obróceniu } if (Child) { // potomnych nie obracamy już, tylko ewentualnie optymalizujemy Child->InitialRotate(false); } else if (Global.iConvertModels & 2) { // optymalizacja jest opcjonalna if (((iFlags & 0xC000) == 0x8000) // o ile nie ma animacji && (false == is_emitter())) // don't optimize smoke emitter attachment points { // jak nie ma potomnych, można wymnożyć przez transform i wyjedynkować go float4x4 *mat = GetMatrix(); // transform submodelu if (false == Vertices.empty()) { for (auto &vertex : Vertices) { vertex.position = (*mat) * vertex.position; } // zerujemy przesunięcie przed obracaniem normalnych (*mat)(3)[0] = (*mat)(3)[1] = (*mat)(3)[2] = 0.0; if (eType != TP_STARS) { // gwiazdki mają kolory zamiast normalnych, to ich wtedy nie ruszamy for (auto &vertex : Vertices) { vertex.normal = (*mat) * vertex.normal; vertex.tangent.xyz = (*mat) * vertex.tangent.xyz; } } } mat->Identity(); // jedynkowanie transformu po przeliczeniu wierzchołków iFlags &= ~0x8000; // transform jedynkowy } } } else // jak jest jednostkowy i nie ma animacji if (doit) { // jeśli jest jednostkowy transform, to przeliczamy // wierzchołki, a mnożenie podajemy dalej for (auto &vertex : Vertices) { glm::mat4 vertexTransform{{-1.f, 0.f, 0.f, 0.f}, {0.f, 0.f, 1.f, 0.f}, {0.f, 1.f, 0.f, 0.f}, {0.f, 0.f, 0.f, 1.f}}; vertex.position = vertexTransform * glm::vec4(vertex.position, 1.f); // wektory normalne również trzeba przekształcić, bo się źle oświetlają if (eType != TP_STARS) { glm::mat3 normalTransform{{-1.f, 0.f, 0.f}, {0.f, 0.f, 1.f}, {0.f, 1.f, 0.f}}; // gwiazdki mają kolory zamiast normalnych, to // ich wtedy nie ruszamy vertex.normal = normalTransform * vertex.normal; vertex.tangent.xyz = normalTransform * vertex.tangent.xyz; } } if (Child) Child->InitialRotate(doit); // potomne ewentualnie obrócimy } if (Next) Next->InitialRotate(doit); }; void TSubModel::ChildAdd(TSubModel *SubModel) { // dodanie submodelu potemnego (uzależnionego) // Ra: zmiana kolejności, żeby kolejne móc renderować po aktualnym (było // przed) if (SubModel) SubModel->NextAdd(Child); // Ra: zmiana kolejności renderowania Child = SubModel; }; void TSubModel::NextAdd(TSubModel *SubModel) { // dodanie submodelu kolejnego (wspólny przodek) if (Next) Next->NextAdd(SubModel); else Next = SubModel; }; int TSubModel::count_siblings() { auto siblingcount{0}; auto *sibling{Next}; while (sibling != nullptr) { ++siblingcount; sibling = sibling->Next; } return siblingcount; } int TSubModel::count_children() { return (Child == nullptr ? 0 : 1 + Child->count_siblings()); } // locates submodel mapped with replacable -4 std::tuple TSubModel::find_replacable4() { if (m_material == -4) { return std::make_tuple(this, (fLight != -1.0)); } if (Next != nullptr) { auto lookup{Next->find_replacable4()}; if (std::get(lookup) != nullptr) { return lookup; } } if (Child != nullptr) { auto lookup{Child->find_replacable4()}; if (std::get(lookup) != nullptr) { return lookup; } } return std::make_tuple(nullptr, false); } // locates particle emitter submodels and adds them to provided list void TSubModel::find_smoke_sources(nameoffset_sequence &Sourcelist) const { auto const name{ToLower(pName)}; if ((eType == TP_ROTATOR) && (pName.find("smokesource_") == 0)) { Sourcelist.emplace_back(pName, offset()); } if (Next != nullptr) { Next->find_smoke_sources(Sourcelist); } if (Child != nullptr) { Child->find_smoke_sources(Sourcelist); } } uint32_t TSubModel::FlagsCheck() { // analiza koniecznych zmian pomiędzy submodelami // samo pomijanie glBindTexture() nie poprawi wydajności // ale można sprawdzić, czy można w ogóle pominąć kod do tekstur (sprawdzanie // replaceskin) m_rotation_init_done = true; uint32_t i = 0; if (Child) { // Child jest renderowany po danym submodelu if (Child->m_material) // o ile ma teksturę if (Child->m_material != m_material) // i jest ona inna niż rodzica Child->iFlags |= 0x80; // to trzeba sprawdzać, jak z teksturami jest i = Child->FlagsCheck(); iFlags |= 0x00FF0000 & ((i << 16) | (i) | (i >> 8)); // potomny, rodzeństwo i dzieci if (eType == TP_TEXT) { // wyłączenie renderowania Next dla znaków // wyświetlacza tekstowego TSubModel *p = Child; while (p) { p->iFlags &= 0xC0FFFFFF; p = p->Next; } } } if (Next) { // Next jest renderowany po danym submodelu (kolejność odwrócona // po wczytaniu T3D) if (m_material) // o ile dany ma teksturę if ((m_material != Next->m_material) || (i & 0x00800000)) // a ma inną albo dzieci zmieniają iFlags |= 0x80; // to dany submodel musi sobie ją ustawiać i = Next->FlagsCheck(); iFlags |= 0xFF000000 & ((i << 24) | (i << 8) | (i)); // następny, kolejne i ich dzieci // tekstury nie ustawiamy tylko wtedy, gdy jest taka sama jak Next i jego // dzieci nie zmieniają } return iFlags; }; void TSubModel::SetRotate(float3 vNewRotateAxis, float fNewAngle) { // obrócenie submodelu wg podanej // osi (np. wskazówki w kabinie) v_RotateAxis = vNewRotateAxis; f_Angle = fNewAngle; if (fNewAngle != 0.0) { b_Anim = TAnimType::at_Rotate; b_aAnim = TAnimType::at_Rotate; } iAnimOwner = iInstance; // zapamiętanie czyja jest animacja } void TSubModel::SetRotateXYZ(float3 vNewAngles) { // obrócenie submodelu o // podane kąty wokół osi // lokalnego układu v_Angles = vNewAngles; b_Anim = TAnimType::at_RotateXYZ; b_aAnim = TAnimType::at_RotateXYZ; iAnimOwner = iInstance; // zapamiętanie czyja jest animacja } void TSubModel::SetRotateXYZ(Math3D::vector3 vNewAngles) { // obrócenie submodelu o // podane kąty wokół osi // lokalnego układu v_Angles.x = vNewAngles.x; v_Angles.y = vNewAngles.y; v_Angles.z = vNewAngles.z; b_Anim = TAnimType::at_RotateXYZ; b_aAnim = TAnimType::at_RotateXYZ; iAnimOwner = iInstance; // zapamiętanie czyja jest animacja } void TSubModel::SetTranslate(float3 vNewTransVector) { // przesunięcie submodelu (np. w kabinie) v_TransVector = vNewTransVector; b_Anim = TAnimType::at_Translate; b_aAnim = TAnimType::at_Translate; iAnimOwner = iInstance; // zapamiętanie czyja jest animacja } void TSubModel::SetTranslate(Math3D::vector3 vNewTransVector) { // przesunięcie submodelu (np. w kabinie) v_TransVector.x = vNewTransVector.x; v_TransVector.y = vNewTransVector.y; v_TransVector.z = vNewTransVector.z; b_Anim = TAnimType::at_Translate; b_aAnim = TAnimType::at_Translate; iAnimOwner = iInstance; // zapamiętanie czyja jest animacja } void TSubModel::SetRotateIK1(float3 vNewAngles) { // obrócenie submodelu o // podane kąty wokół osi // lokalnego układu v_Angles = vNewAngles; iAnimOwner = iInstance; // zapamiętanie czyja jest animacja } struct ToLower { char operator()(char input) { return tolower(input); } }; TSubModel *TSubModel::GetFromName(std::string const &search, bool i) { TSubModel *result; // std::transform(search.begin(),search.end(),search.begin(),ToLower()); // search=search.LowerCase(); // AnsiString name=AnsiString(); std::string search_lc = search; if (i) std::transform(search_lc.begin(), search_lc.end(), search_lc.begin(), ::tolower); std::string pName_lc = pName; if (i) std::transform(pName_lc.begin(), pName_lc.end(), pName_lc.begin(), ::tolower); if (pName.size() && search.size()) if (pName_lc == search_lc) return this; if (Next) { result = Next->GetFromName(search); if (result) return result; } if (Child) { result = Child->GetFromName(search); if (result) return result; } return NULL; }; // WORD hbIndices[18]={3,0,1,5,4,2,1,0,4,1,5,3,2,3,5,2,4,0}; void TSubModel::RaAnimation(TAnimType a) { glm::mat4 m = OpenGLMatrices.data(GL_MODELVIEW); RaAnimation(m, a); glLoadMatrixf(glm::value_ptr(m)); } void TSubModel::RaAnimation(glm::mat4 &m, TAnimType a) { // wykonanie animacji niezależnie od renderowania switch (a) { // korekcja położenia, jeśli submodel jest animowany case TAnimType::at_Translate: // Ra: było "true" if (iAnimOwner != iInstance) break; // cudza animacja m = glm::translate(m, glm::vec3(v_TransVector.x, v_TransVector.y, v_TransVector.z)); break; case TAnimType::at_Rotate: // Ra: było "true" if (iAnimOwner != iInstance) break; // cudza animacja m = glm::rotate(m, glm::radians(f_Angle), glm::vec3(v_RotateAxis.x, v_RotateAxis.y, v_RotateAxis.z)); break; case TAnimType::at_RotateXYZ: if (iAnimOwner != iInstance) break; // cudza animacja m = glm::translate(m, glm::vec3(v_TransVector.x, v_TransVector.y, v_TransVector.z)); m = glm::rotate(m, glm::radians(v_Angles.x), glm::vec3(1.0f, 0.0f, 0.0f)); m = glm::rotate(m, glm::radians(v_Angles.y), glm::vec3(0.0f, 1.0f, 0.0f)); m = glm::rotate(m, glm::radians(v_Angles.z), glm::vec3(0.0f, 0.0f, 1.0f)); break; case TAnimType::at_SecondsJump: // sekundy z przeskokiem m = glm::rotate(m, glm::radians(simulation::Time.data().wSecond * 6.0f), glm::vec3(0.0f, 1.0f, 0.0f)); break; case TAnimType::at_MinutesJump: // minuty z przeskokiem m = glm::rotate(m, glm::radians(simulation::Time.data().wMinute * 6.0f), glm::vec3(0.0f, 1.0f, 0.0f)); break; case TAnimType::at_HoursJump: // godziny skokowo 12h/360° m = glm::rotate(m, glm::radians(simulation::Time.data().wHour * 30.0f * 0.5f), glm::vec3(0.0f, 1.0f, 0.0f)); break; case TAnimType::at_Hours24Jump: // godziny skokowo 24h/360° m = glm::rotate(m, glm::radians(simulation::Time.data().wHour * 15.0f * 0.25f), glm::vec3(0.0f, 1.0f, 0.0f)); break; case TAnimType::at_Seconds: // sekundy płynnie m = glm::rotate(m, glm::radians((float)simulation::Time.second() * 6.0f), glm::vec3(0.0f, 1.0f, 0.0f)); break; case TAnimType::at_Minutes: // minuty płynnie m = glm::rotate(m, glm::radians(simulation::Time.data().wMinute * 6.0f + (float)simulation::Time.second() * 0.1f), glm::vec3(0.0f, 1.0f, 0.0f)); break; case TAnimType::at_Hours: // godziny płynnie 12h/360° // glRotatef(GlobalTime->hh*30.0+GlobalTime->mm*0.5+GlobalTime->mr/120.0,0.0,1.0,0.0); m = glm::rotate(m, glm::radians(2.0f * (float)Global.fTimeAngleDeg), glm::vec3(0.0f, 1.0f, 0.0f)); break; case TAnimType::at_Hours24: // godziny płynnie 24h/360° // glRotatef(GlobalTime->hh*15.0+GlobalTime->mm*0.25+GlobalTime->mr/240.0,0.0,1.0,0.0); m = glm::rotate(m, glm::radians((float)Global.fTimeAngleDeg), glm::vec3(0.0f, 1.0f, 0.0f)); break; case TAnimType::at_Billboard: // obrót w pionie do kamery { Math3D::matrix4x4 mat; mat.OpenGL_Matrix(OpenGLMatrices.data_array(GL_MODELVIEW)); float3 gdzie = float3(mat[3][0], mat[3][1], mat[3][2]); // początek układu współrzędnych submodelu względem kamery m = glm::mat4(1.0f); m = glm::translate(m, glm::vec3(gdzie.x, gdzie.y, gdzie.z)); // początek układu zostaje bez zmian m = glm::rotate(m, (float)atan2(gdzie.x, gdzie.z), glm::vec3(0.0f, 1.0f, 0.0f)); // jedynie obracamy w pionie o kąt } break; case TAnimType::at_Wind: // ruch pod wpływem wiatru (wiatr będziemy liczyć potem...) m = glm::rotate(m, glm::radians(1.5f * (float)sin(M_PI * simulation::Time.second() / 6.0)), glm::vec3(0.0f, 1.0f, 0.0f)); break; case TAnimType::at_Sky: // animacja nieba m = glm::rotate(m, glm::radians((float)Global.fLatitudeDeg), glm::vec3(0.0f, 1.0f, 0.0f)); // ustawienie osi OY na północ m = glm::rotate(m, glm::radians((float)-fmod(Global.fTimeAngleDeg, 360.0)), glm::vec3(0.0f, 1.0f, 0.0f)); break; case TAnimType::at_DigiClk: // animacja zegara cyfrowego { // ustawienie animacji w submodelach potomnych TSubModel *sm = ChildGet(); do { // pętla po submodelach potomnych i obracanie ich o kąt zależy od czasu if (sm->pName.size()) { // musi mieć niepustą nazwę if ((sm->pName[0] >= '0') && (sm->pName[0] <= '5')) { // zegarek ma 6 cyfr maksymalnie sm->SetRotate(float3(0, 1, 0), -Global.fClockAngleDeg[sm->pName[0] - '0']); } } sm = sm->NextGet(); } while (sm); } break; } if (mAnimMatrix) // można by to dać np. do at_Translate { m *= glm::make_mat4(mAnimMatrix->e); mAnimMatrix.reset(); // jak animator będzie potrzebował, to ustawi ponownie } }; //--------------------------------------------------------------------------- void TSubModel::serialize_geometry(std::ostream &Output, bool const Packed, bool const Indexed, bool const UserData) const { if (Child) { Child->serialize_geometry(Output, Packed, Indexed, UserData); } if (m_geometry.handle != null_handle) { if (Packed) { auto vertices = GfxRenderer->Vertices(m_geometry.handle); auto userdatas = GfxRenderer->UserData(m_geometry.handle); bool has_userdata = !userdatas.empty(); for (int i = 0; i < vertices.size(); ++i) { vertices[i].serialize_packed(Output, Indexed); if (UserData) { if (has_userdata) userdatas[i].serialize_packed(Output); else gfx::vertex_userdata{}.serialize_packed(Output); } } } else { auto vertices = GfxRenderer->Vertices(m_geometry.handle); auto userdatas = GfxRenderer->UserData(m_geometry.handle); bool has_userdata = !userdatas.empty(); for (int i = 0; i < vertices.size(); ++i) { vertices[i].serialize(Output, Indexed); if (UserData) { if (has_userdata) userdatas[i].serialize(Output); else gfx::vertex_userdata{}.serialize(Output); } } } } if (Next) { Next->serialize_geometry(Output, Packed, Indexed, UserData); } }; int TSubModel::index_size() const { int size{1}; if (Child) { size = std::max(size, Child->index_size()); } if ((size < 4) && (m_geometry.handle != null_handle)) { auto const indexcount{GfxRenderer->Indices(m_geometry.handle).size()}; size = std::max(size, (indexcount >= (1 << 16) ? 4 : indexcount >= (1 << 8) ? 2 : 1)); } if ((size < 4) && (Next)) { size = std::max(size, Next->index_size()); } return size; } void TSubModel::serialize_indices(std::ostream &Output, int const Size) const { if (Child) { Child->serialize_indices(Output, Size); } if (m_geometry.handle != null_handle) { switch (Size) { case 1: { for (auto const &index : GfxRenderer->Indices(m_geometry.handle)) { sn_utils::s_uint8(Output, index); } break; } case 2: { for (auto const &index : GfxRenderer->Indices(m_geometry.handle)) { sn_utils::ls_uint16(Output, index); } break; } case 4: { for (auto const &index : GfxRenderer->Indices(m_geometry.handle)) { sn_utils::ls_uint32(Output, index); } break; } default: { break; } } } if (Next) { Next->serialize_indices(Output, Size); } }; void TSubModel::create_geometry(std::size_t &Indexoffset, std::size_t &Vertexoffset, gfx::geometrybank_handle const &Bank) { // data offset is used to determine data offset of each submodel into single shared geometry bank // (the offsets are part of legacy system which we now need to work around for backward compatibility) if (Child) { Child->create_geometry(Indexoffset, Vertexoffset, Bank); } if (false == Vertices.empty()) { m_geometry.index_offset = static_cast(Indexoffset); Indexoffset += Indices.size(); m_geometry.vertex_offset = static_cast(Vertexoffset); Vertexoffset += Vertices.size(); // conveniently all relevant custom node types use GL_POINTS, or we'd have to determine the type on individual basis auto type = (eType < TP_ROTATOR ? eType : GL_POINTS); m_geometry.handle = GfxRenderer->Insert(Indices, Vertices, Userdata, Bank, type); } if (m_geometry.handle != 0) { // calculate bounding radius while we're at it float squaredradius{}; // if this happens to be root node it may already have non-squared radius of the largest child // since we're comparing squared radii, we need to square it back for correct results m_boundingradius *= m_boundingradius; auto const submodeloffset{offset(std::numeric_limits::max())}; for (auto const &vertex : GfxRenderer->Vertices(m_geometry.handle)) { squaredradius = glm::length2(submodeloffset + vertex.position); if (squaredradius > m_boundingradius) { m_boundingradius = squaredradius; } } if (m_boundingradius > 0.f) { m_boundingradius = std::sqrt(m_boundingradius); } // adjust overall radius if needed // NOTE: the method to access root submodel is... less than ideal auto *root{this}; while (root->Parent != nullptr) { root = root->Parent; } root->m_boundingradius = std::max(root->m_boundingradius, m_boundingradius); } if (Next) { Next->create_geometry(Indexoffset, Vertexoffset, Bank); } } void TSubModel::ColorsSet(glm::vec3 const &Ambient, glm::vec3 const &Diffuse, glm::vec3 const &Specular) { // ustawienie kolorów dla modelu terenu f4Ambient = glm::vec4(Ambient, 1.0f); f4Diffuse = glm::vec4(Diffuse, 1.0f); f4Specular = glm::vec4(Specular, 1.0f); /* int i; if (a) for (i = 0; i < 4; ++i) f4Ambient[i] = a[i] / 255.0; if (d) for (i = 0; i < 4; ++i) f4Diffuse[i] = d[i] / 255.0; if (s) for (i = 0; i < 4; ++i) f4Specular[i] = s[i] / 255.0; */ }; bool TSubModel::is_emitter() const { return ((eType == TP_ROTATOR) && (ToLower(pName).find("smokesource_") == 0)); } // pobranie transformacji względem wstawienia modelu void TSubModel::ParentMatrix(float4x4 *m) const { m->Identity(); float4x4 submodelmatrix; auto *submodel = this; do { // for given step in hierarchy there can be custom transformation matrix, or no transformation // retrieve it... submodelmatrix.Identity(); if (submodel->GetMatrix()) { submodelmatrix = float4x4(*submodel->GetMatrix()); } // ...potentially adjust transformations of the root matrix if the model wasn't yet initialized... if ((submodel->Parent == nullptr) && (false == submodel->m_rotation_init_done)) { // dla ostatniego może być potrzebny dodatkowy obrót, jeśli wczytano z T3D, a nie obrócono jeszcze submodelmatrix.InitialRotate(); } // ...combine the transformations... *m = submodelmatrix * (*m); // ...and move up the transformation chain for the iteration... submodel = submodel->Parent; // ... until we hit the root } while (submodel != nullptr); /* if( fMatrix != nullptr ) { // skopiowanie, bo będziemy mnożyć *m = float4x4( *fMatrix ); } else { m->Identity(); } auto *sm = this; while (sm->Parent) { // przenieść tę funkcję do modelu if (sm->Parent->GetMatrix()) *m = *sm->Parent->GetMatrix() * *m; sm = sm->Parent; } */ }; void TSubModel::ReplaceMatrix(const glm::mat4 &mat) { *fMatrix = float4x4(glm::value_ptr(mat)); } void TSubModel::ReplaceMaterial(const std::string &name) { m_material = GfxRenderer->Fetch_Material(name); } // obliczenie maksymalnej wysokości, na początek ślizgu w pantografie float TSubModel::MaxY(float4x4 const &m) { // tylko dla trójkątów liczymy if (eType != GL_TRIANGLES) { return 0; } auto maxy{0.0f}; // binary and text models invoke this function at different stages, either after or before geometry data was sent to the geometry manager if (m_geometry.handle != null_handle) { for (auto const &vertex : GfxRenderer->Vertices(m_geometry.handle)) { maxy = std::max(maxy, m[0][1] * vertex.position.x + m[1][1] * vertex.position.y + m[2][1] * vertex.position.z + m[3][1]); } } else if (false == Vertices.empty()) { for (auto const &vertex : Vertices) { maxy = std::max(maxy, m[0][1] * vertex.position.x + m[1][1] * vertex.position.y + m[2][1] * vertex.position.z + m[3][1]); } } return maxy; }; //--------------------------------------------------------------------------- TModel3d::~TModel3d() { if (iFlags & 0x0200) { // wczytany z pliku tekstowego, submodele sprzątają same SafeDelete(Root); } else { // wczytano z pliku binarnego (jest właścicielem tablic) SafeDeleteArray(Root); // submodele się usuną rekurencyjnie } }; TSubModel *TModel3d::AddToNamed(const char *Name, TSubModel *SubModel) { TSubModel *sm = Name ? GetFromName(Name) : nullptr; if ((sm == nullptr) && (Name != nullptr) && (std::strcmp(Name, "none") != 0)) { ErrorLog("Bad model: parent for sub-model \"" + SubModel->pName + "\" doesn't exist or is located later in the model data", logtype::model); } AddTo(sm, SubModel); // szukanie nadrzędnego return sm; // zwracamy wskaźnik do nadrzędnego submodelu }; // jedyny poprawny sposób dodawania submodeli, inaczej mogą zginąć przy zapisie E3D void TModel3d::AddTo(TSubModel *tmp, TSubModel *SubModel) { if (tmp) { // jeśli znaleziony, podłączamy mu jako potomny tmp->ChildAdd(SubModel); } else { // jeśli nie znaleziony, podczepiamy do łańcucha głównego SubModel->NextAdd(Root); // Ra: zmiana kolejności renderowania wymusza zmianę tu Root = SubModel; } ++iSubModelsCount; // teraz jest o 1 submodel więcej iFlags |= 0x0200; // submodele są oddzielne }; TSubModel *TModel3d::GetFromName(std::string const &Name) const { // wyszukanie submodelu po nazwie if (Name.empty()) return Root; // potrzebne do terenu z E3D if (iFlags & 0x0200) // wczytany z pliku tekstowego, wyszukiwanie rekurencyjne return Root ? Root->GetFromName(Name) : nullptr; else // wczytano z pliku binarnego, można wyszukać iteracyjnie { // for (int i=0;iGetFromName(Name) : nullptr; } }; // locates particle source submodels and stores them on internal list nameoffset_sequence const &TModel3d::find_smoke_sources() { m_smokesources.clear(); if (Root != nullptr) { Root->find_smoke_sources(m_smokesources); } return smoke_sources(); } // returns offset vector from root glm::vec3 TSubModel::offset(float const Geometrytestoffsetthreshold) const { float4x4 parentmatrix; ParentMatrix(&parentmatrix); auto offset{glm::vec3{glm::make_mat4(parentmatrix.readArray()) * glm::vec4{0, 0, 0, 1}}}; if (glm::length(offset) < Geometrytestoffsetthreshold) { // offset of zero generally means the submodel has optimized identity matrix // for such cases we resort to an estimate from submodel geometry // TODO: do proper bounding area calculation for submodel when loading mesh and grab the centre point from it here auto const &vertices{(m_geometry.handle != null_handle ? GfxRenderer->Vertices(m_geometry.handle) : Vertices)}; if (false == vertices.empty()) { // transformation matrix for the submodel can still contain rotation and/or scaling, // so we pass the vertex positions through it rather than just grab them directly offset = glm::vec3(); auto const vertexfactor{1.f / vertices.size()}; auto const transformationmatrix{glm::make_mat4(parentmatrix.readArray())}; for (auto const &vertex : vertices) { offset += glm::vec3{transformationmatrix * glm::vec4{vertex.position, 1}} * vertexfactor; } } } return offset; } bool TModel3d::LoadFromFile(std::string const &FileName, bool dynamic) { // wczytanie modelu z pliku /* // NOTE: disabled, this work is now done by the model manager std::string name = ToLower(FileName); // trim extension if needed if( name.rfind( '.' ) != std::string::npos ) { name.erase(name.rfind('.')); } */ auto const name{FileName}; // cache the file name, in case someone wants it later m_filename = name; asBinary = name + ".e3d"; // Hirek: Jesli mamy ustawione priorityLoadText3D na yes to wpierw ladujemy t3d if (Global.priorityLoadText3D && FileExists(name + ".t3d")) { WriteLog("Forced loading text model \"" + name + ".t3d\""); LoadFromTextFile(name + ".t3d", dynamic); if (!dynamic) Init(); } else if (FileExists(asBinary)) { LoadFromBinFile(asBinary, dynamic); asBinary = ""; Init(); } else if (FileExists(name + ".t3d")) { LoadFromTextFile(name + ".t3d", dynamic); if (!dynamic) Init(); } bool const result = Root ? (iSubModelsCount > 0) : false; // brak pliku albo problem z wczytaniem if (false == result) { ErrorLog("Bad model: failed to load 3d model \"" + name + "\""); } return result; }; // E3D serialization // http://rainsted.com/pl/Format_binarny_modeli_-_E3D // m7todo: wymyślić lepszą nazwę template size_t get_container_pos(L &list, T o) { auto i = std::find(list.begin(), list.end(), o); if (i == list.end()) { list.push_back(o); return list.size() - 1; } else { return std::distance(list.begin(), i); } } // m7todo: za dużo argumentów, może przenieść do osobnej // klasy serializera mającej własny stan, albo zrobić // strukturę TModel3d::SerializerContext? void TSubModel::serialize(std::ostream &s, std::vector &models, std::vector &names, std::vector &textures, std::vector &transforms) { size_t end = (size_t)s.tellp() + 256; if (!Next) sn_utils::ls_int32(s, -1); else sn_utils::ls_int32(s, (int32_t)get_container_pos(models, Next)); if (!Child) sn_utils::ls_int32(s, -1); else sn_utils::ls_int32(s, (int32_t)get_container_pos(models, Child)); sn_utils::ls_int32(s, eType); if (pName.size() == 0) sn_utils::ls_int32(s, -1); else sn_utils::ls_int32(s, (int32_t)get_container_pos(names, pName)); sn_utils::ls_int32(s, (int)b_Anim); uint32_t flags = iFlags; if (m_material > 0 && (Global.iConvertModels & 16)) flags &= ~0x30; // don't save phase information, will be guessed on binary load from material sn_utils::ls_uint32(s, flags); sn_utils::ls_int32(s, (int32_t)get_container_pos(transforms, *fMatrix)); sn_utils::ls_int32(s, m_geometry.vertex_count); sn_utils::ls_int32(s, m_geometry.vertex_offset); if (m_material <= 0) sn_utils::ls_int32(s, m_material); else sn_utils::ls_int32(s, (int32_t)get_container_pos(textures, m_materialname)); sn_utils::ls_float32(s, 1.f); // fVisible sn_utils::ls_float32(s, fLight); sn_utils::s_vec4(s, f4Ambient); sn_utils::s_vec4(s, f4Diffuse); sn_utils::s_vec4(s, f4Specular); sn_utils::s_vec4(s, f4Emision); sn_utils::ls_float32(s, fWireSize); sn_utils::ls_float32(s, fSquareMaxDist); sn_utils::ls_float32(s, fSquareMinDist); sn_utils::ls_float32(s, fNearAttenStart); sn_utils::ls_float32(s, fNearAttenEnd); sn_utils::ls_uint32(s, bUseNearAtten ? 1 : 0); sn_utils::ls_int32(s, iFarAttenDecay); sn_utils::ls_float32(s, fFarDecayRadius); sn_utils::ls_float32(s, fCosFalloffAngle); sn_utils::ls_float32(s, fCosHotspotAngle); sn_utils::ls_float32(s, fCosViewAngle); sn_utils::ls_int32(s, m_geometry.index_count); sn_utils::ls_int32(s, m_geometry.index_offset); // external data sn_utils::ls_float32(s, diffuseMultiplier); // fill empty space size_t fill = end - s.tellp(); for (size_t i = 0; i < fill; i++) s.put(0); } void TModel3d::SaveToBinFile(std::string const &FileName) { WriteLog("saving e3d model.."); // m7todo: można by zoptymalizować robiąc unordered_map // na wyszukiwanie numerów już dodanych stringów i osobno // vector na wskaźniki do stringów w kolejności numeracji // tylko czy potrzeba? std::vector models; models.push_back(Root); std::vector names; std::vector textures; textures.push_back(""); std::vector transforms; std::ofstream s(FileName, std::ios::binary); sn_utils::ls_uint32(s, MAKE_ID4('E', '3', 'D', '0')); auto const e3d_spos = s.tellp(); sn_utils::ls_uint32(s, 0); bool has_any_userdata = Root->HasAnyVertexUserData(); { sn_utils::ls_uint32(s, MAKE_ID4('S', 'U', 'B', '0')); auto const sub_spos = s.tellp(); sn_utils::ls_uint32(s, 0); for (size_t i = 0; i < models.size(); i++) models[i]->serialize(s, models, names, textures, transforms); auto const pos = s.tellp(); s.seekp(sub_spos); sn_utils::ls_uint32(s, (uint32_t)(4 + pos - sub_spos)); s.seekp(pos); } sn_utils::ls_uint32(s, MAKE_ID4('T', 'R', 'A', '0')); sn_utils::ls_uint32(s, 8 + (uint32_t)transforms.size() * 64); for (size_t i = 0; i < transforms.size(); i++) transforms[i].serialize_float32(s); auto const isindexed{m_indexcount > 0}; if (isindexed) { auto const indexsize{Root->index_size()}; sn_utils::ls_uint32(s, MAKE_ID4('I', 'D', 'X', '0' + indexsize)); sn_utils::ls_uint32(s, 8 + m_indexcount * indexsize); Root->serialize_indices(s, indexsize); if (!(Global.iConvertModels & 8)) { int modeltype = 1; int vertexsize = 20; if (has_any_userdata) { modeltype |= 4; vertexsize += 8; } sn_utils::ls_uint32(s, MAKE_ID4('V', 'N', 'T', '0' + modeltype)); sn_utils::ls_uint32(s, 8 + m_vertexcount * vertexsize); Root->serialize_geometry(s, true, true, has_any_userdata); } else { int modeltype = 2; int vertexsize = 48; if (has_any_userdata) { modeltype |= 4; vertexsize += 16; } sn_utils::ls_uint32(s, MAKE_ID4('V', 'N', 'T', '0' + modeltype)); sn_utils::ls_uint32(s, 8 + m_vertexcount * vertexsize); Root->serialize_geometry(s, false, true, has_any_userdata); } } else { int modeltype = 0; int vertexsize = 32; if (has_any_userdata) { modeltype |= 4; vertexsize += 16; } sn_utils::ls_uint32(s, MAKE_ID4('V', 'N', 'T', '0' + modeltype)); sn_utils::ls_uint32(s, 8 + m_vertexcount * vertexsize); Root->serialize_geometry(s, false, false, has_any_userdata); } if (textures.size()) { sn_utils::ls_uint32(s, MAKE_ID4('T', 'E', 'X', '0')); auto const tex_spos = s.tellp(); sn_utils::ls_uint32(s, 0); for (size_t i = 0; i < textures.size(); i++) sn_utils::s_str(s, textures[i]); auto const pos = s.tellp(); s.seekp(tex_spos); sn_utils::ls_uint32(s, (uint32_t)(4 + pos - tex_spos)); s.seekp(pos); } if (names.size()) { sn_utils::ls_uint32(s, MAKE_ID4('N', 'A', 'M', '0')); auto const nam_spos = s.tellp(); sn_utils::ls_uint32(s, 0); for (size_t i = 0; i < names.size(); i++) sn_utils::s_str(s, names[i]); auto const pos = s.tellp(); s.seekp(nam_spos); sn_utils::ls_uint32(s, (uint32_t)(4 + pos - nam_spos)); s.seekp(pos); } auto const end = s.tellp(); s.seekp(e3d_spos); sn_utils::ls_uint32(s, (uint32_t)(4 + end - e3d_spos)); s.close(); WriteLog("..done."); } void TSubModel::deserialize(std::istream &s) { iNext = sn_utils::ld_int32(s); iChild = sn_utils::ld_int32(s); eType = sn_utils::ld_int32(s); iName = sn_utils::ld_int32(s); b_Anim = (TAnimType)sn_utils::ld_int32(s); iFlags = sn_utils::ld_uint32(s); iMatrix = sn_utils::ld_int32(s); m_geometry.vertex_count = sn_utils::ld_int32(s); m_geometry.vertex_offset = sn_utils::ld_int32(s); iTexture = sn_utils::ld_int32(s); auto discard = sn_utils::ld_float32(s); // fVisible fLight = sn_utils::ld_float32(s); f4Ambient = sn_utils::d_vec4(s); f4Diffuse = sn_utils::d_vec4(s); f4Specular = sn_utils::d_vec4(s); f4Emision = sn_utils::d_vec4(s); fWireSize = sn_utils::ld_float32(s); fSquareMaxDist = sn_utils::ld_float32(s); fSquareMinDist = sn_utils::ld_float32(s); fNearAttenStart = sn_utils::ld_float32(s); fNearAttenEnd = sn_utils::ld_float32(s); bUseNearAtten = sn_utils::ld_uint32(s) != 0; iFarAttenDecay = sn_utils::ld_int32(s); fFarDecayRadius = sn_utils::ld_float32(s); fCosFalloffAngle = sn_utils::ld_float32(s); fCosHotspotAngle = sn_utils::ld_float32(s); fCosViewAngle = sn_utils::ld_float32(s); // HACK: the values will be 0 also when reading legacy chunk m_geometry.index_count = sn_utils::ld_int32(s); m_geometry.index_offset = sn_utils::ld_int32(s); // extra data block diffuseMultiplier = sn_utils::ld_float32(s); // only multiply diffuse on experimental renderer if (!Global.NvRenderer) f4Diffuse /= diffuseMultiplier <= 0.0 ? 1.0 : diffuseMultiplier; // necessary rotations were already done during t3d->e3d conversion m_rotation_init_done = true; } void TModel3d::deserialize(std::istream &s, size_t size, bool dynamic) { Root = nullptr; if (m_geometrybank == null_handle) { m_geometrybank = GfxRenderer->Create_Bank(); } std::streampos end = s.tellg() + (std::streampos)size; bool hastangents{false}; bool hasuserdata{false}; while (s.tellg() < end) { uint32_t type = sn_utils::ld_uint32(s); uint32_t size = sn_utils::ld_uint32(s) - 8; size_t pos = s.tellg(); std::streampos end = pos + (std::streampos)size; if ((type & 0x00FFFFFF) == MAKE_ID4('S', 'U', 'B', 0)) // submodels { if (Root != nullptr) throw std::runtime_error("e3d: duplicated SUB chunk"); size_t sm_size = 256 + 64 * (((type & 0xFF000000) >> 24) - '0'); size_t sm_cnt = size / sm_size; iSubModelsCount = (int)sm_cnt; Root = new TSubModel[sm_cnt]; for (size_t i = 0; i < sm_cnt; ++i) { s.seekg(pos + sm_size * i); Root[i].deserialize(s); } } else if ((type & 0x00FFFFFF) == MAKE_ID4('V', 'N', 'T', 0)) // geometry vertices { // we rely on the SUB chunk coming before the vertex data, and on the overall vertex count matching the size of data in the chunk. // geometry associated with chunks isn't stored in the same order as the chunks themselves, so we need to sort that out first if (Root == nullptr) throw std::runtime_error("e3d: VNT chunk encountered before SUB chunk"); std::vector> submodeloffsets; // vertex data offset, submodel index submodeloffsets.reserve(iSubModelsCount); for (auto submodelindex = 0; submodelindex < iSubModelsCount; ++submodelindex) { auto const &submodelgeometry{Root[submodelindex].m_geometry}; if (submodelgeometry.vertex_count <= 0) { continue; } submodeloffsets.emplace_back(submodelgeometry.vertex_offset, submodelindex); } std::sort(std::begin(submodeloffsets), std::end(submodeloffsets), [](std::pair const &Left, std::pair const &Right) { return (Left.first) < (Right.first); }); // once sorted we can grab geometry as it comes, and assign it to the chunks it belongs to size_t const vertextype{(((type & 0xFF000000) >> 24) - '0')}; hastangents = ((vertextype & 3) > 0); hasuserdata = (vertextype & 4); size_t vertex_size = 0; switch (vertextype & 3) { case 0: case 2: { vertex_size = 8; if (hastangents) vertex_size += 4; if (hasuserdata) vertex_size += 4; break; } case 1: { vertex_size = 4; if (hastangents) vertex_size += 1; if (hasuserdata) vertex_size += 2; break; } } for (auto const &submodeloffset : submodeloffsets) { s.seekg(pos + submodeloffset.first * vertex_size * sizeof(float)); // Ensure that we start reading from the correct offset even if geometry is not encoded contiguously auto &submodel{Root[submodeloffset.second]}; auto const &submodelgeometry{submodel.m_geometry}; submodel.Vertices.resize(submodelgeometry.vertex_count); if (hasuserdata) submodel.Userdata.resize(submodelgeometry.vertex_count); m_vertexcount += submodelgeometry.vertex_count; switch (vertextype & 3) { case 0: { // legacy vnt0 format for (int i = 0; i < submodel.Vertices.size(); ++i) { submodel.Vertices[i].deserialize(s, hastangents); if (hasuserdata) submodel.Userdata[i].deserialize(s); if (submodel.eType < TP_ROTATOR) { // normal vectors debug routine if ((false == submodel.m_normalizenormals) && (std::abs(glm::length2(submodel.Vertices[i].normal) - 1.0f) > 0.01f)) { submodel.m_normalizenormals = TSubModel::normalize; // we don't know if uniform scaling would suffice WriteLog("Bad model: non-unit normal vector(s) encountered during sub-model geometry deserialization", logtype::model); } } } break; } case 1: { // expanded chunk formats for (int i = 0; i < submodel.Vertices.size(); ++i) { submodel.Vertices[i].deserialize_packed(s, hastangents); if (hasuserdata) submodel.Userdata[i].deserialize_packed(s); } break; } case 2: { // expanded chunk formats for (int i = 0; i < submodel.Vertices.size(); ++i) { submodel.Vertices[i].deserialize(s, hastangents); if (hasuserdata) submodel.Userdata[i].deserialize(s); } break; } default: { // TBD, TODO: throw error here? break; } } } } else if ((type & 0x00FFFFFF) == MAKE_ID4('I', 'D', 'X', 0)) // geometry indices { // handled similarly to vertex data chunk // we rely on the SUB chunk coming before the vertex data, and on the overall vertex count matching the size of data in the chunk. // geometry associated with chunks isn't stored in the same order as the chunks themselves, so we need to sort that out first if (Root == nullptr) throw std::runtime_error("e3d: IDX chunk encountered before SUB chunk"); std::vector> submodeloffsets; // index data offset, submodel index submodeloffsets.reserve(iSubModelsCount); for (auto submodelindex = 0; submodelindex < iSubModelsCount; ++submodelindex) { auto const &submodelgeometry{Root[submodelindex].m_geometry}; if (submodelgeometry.index_count <= 0) { continue; } submodeloffsets.emplace_back(submodelgeometry.index_offset, submodelindex); } std::sort(std::begin(submodeloffsets), std::end(submodeloffsets), [](std::pair const &Left, std::pair const &Right) { return (Left.first) < (Right.first); }); // once sorted we can grab indices in a continuous read, and assign them to the chunks they belong to size_t const indexsize{(((type & 0xFF000000) >> 24) - '0')}; for (auto const &submodeloffset : submodeloffsets) { s.seekg(pos + submodeloffset.first * indexsize); auto &submodel{Root[submodeloffset.second]}; auto const &submodelgeometry{submodel.m_geometry}; submodel.Indices.resize(submodelgeometry.index_count); m_indexcount += submodelgeometry.index_count; switch (indexsize) { case 1: { for (auto &index : submodel.Indices) { index = sn_utils::d_uint8(s); } break; } case 2: { for (auto &index : submodel.Indices) { index = sn_utils::ld_uint16(s); } break; } case 4: { for (auto &index : submodel.Indices) { index = sn_utils::ld_uint32(s); } break; } default: { break; } } } } else if (type == MAKE_ID4('T', 'R', 'A', '0')) { if (false == Matrices.empty()) throw std::runtime_error("e3d: duplicated TRA chunk"); size_t t_cnt = size / 64; Matrices.resize(t_cnt); for (size_t i = 0; i < t_cnt; ++i) Matrices[i].deserialize_float32(s); } else if (type == MAKE_ID4('T', 'R', 'A', '1')) { if (false == Matrices.empty()) throw std::runtime_error("e3d: duplicated TRA chunk"); size_t t_cnt = size / 128; Matrices.resize(t_cnt); for (size_t i = 0; i < t_cnt; ++i) Matrices[i].deserialize_float64(s); } else if (type == MAKE_ID4('T', 'E', 'X', '0')) { if (Textures.size()) throw std::runtime_error("e3d: duplicated TEX chunk"); while (s.tellg() < end) { std::string str = sn_utils::d_str(s); std::replace(str.begin(), str.end(), '\\', '/'); Textures.push_back(str); } } else if (type == MAKE_ID4('N', 'A', 'M', '0')) { if (Names.size()) throw std::runtime_error("e3d: duplicated NAM chunk"); while (s.tellg() < end) Names.push_back(sn_utils::d_str(s)); } s.seekg(end); } if (!Root) throw std::runtime_error("e3d: no submodels"); for (size_t i = 0; (int)i < iSubModelsCount; ++i) { Root[i].BinInit(Root, Matrices.data(), &Textures, &Names, dynamic); if (Root[i].ChildGet()) Root[i].ChildGet()->Parent = &Root[i]; if (Root[i].NextGet()) Root[i].NextGet()->Parent = Root[i].Parent; // remap geometry type for custom type submodels int type; switch (Root[i].eType) { case TP_FREESPOTLIGHT: case TP_STARS: { type = GL_POINTS; break; } default: { type = Root[i].eType; break; } } if (false == hastangents) { gfx::calculate_tangents(Root[i].Vertices, Root[i].Indices, type); } Root[i].m_geometry.handle = GfxRenderer->Insert(Root[i].Indices, Root[i].Vertices, Root[i].Userdata, m_geometrybank, type); } } void TSubModel::BinInit(TSubModel *s, float4x4 *m, std::vector *t, std::vector *n, bool dynamic) { // ustawienie wskaźników w submodelu // m7todo: brzydko iVisible = 1; // tymczasowo używane Child = (iChild > 0) ? s + iChild : nullptr; // zerowy nie może być potomnym Next = (iNext > 0) ? s + iNext : nullptr; // zerowy nie może być następnym fMatrix = ((iMatrix >= 0) && m) ? m + iMatrix : nullptr; if (n->size() && (iName >= 0)) { pName = n->at(iName); if (!pName.empty()) { // jeśli dany submodel jest zgaszonym światłem, to // domyślnie go ukrywamy if (starts_with(pName, "Light_On")) { // jeśli jest światłem numerowanym iVisible = 0; // to domyślnie wyłączyć, żeby się nie nakładało z obiektem "Light_Off" } else if (dynamic) { // inaczej wyłączało smugę w latarniach if (ends_with(pName, "_on")) { // jeśli jest kontrolką w stanie zapalonym to domyślnie wyłączyć, // żeby się nie nakładało z obiektem "_off" iVisible = 0; } } // hack: reset specular light value for shadow submodels if (pName == "cien") { f4Specular = glm::vec4{0.0f, 0.0f, 0.0f, 1.0f}; } } } else pName = ""; if (iTexture > 0) { // obsługa stałej tekstury auto const materialindex = static_cast(iTexture); if (materialindex < t->size()) { m_materialname = t->at(materialindex); /* if( m_materialname.find_last_of( "/" ) == std::string::npos ) { m_materialname = Global.asCurrentTexturePath + m_materialname; } */ m_material = GfxRenderer->Fetch_Material(m_materialname); // if we don't have phase flags set for some reason, try to fix it if (!(iFlags & 0x30) && m_material != null_handle) { const IMaterial *mat = GfxRenderer->Material(m_material); float opacity = mat->get_or_guess_opacity(); // set phase flag based on material opacity if (opacity == 0.0f) iFlags |= 0x20; // translucent else iFlags |= 0x10; // opaque } if (m_material != null_handle) { IMaterial const *mat = GfxRenderer->Material(m_material); /* // if material does have opacity set, replace submodel opacity with it if (mat.opacity) { iFlags &= ~0x30; if (*mat.opacity == 0.0f) iFlags |= 0x20; // translucent else iFlags |= 0x10; // opaque } */ // replace submodel selfillum with material one if (mat->GetSelfillum()) { fLight = mat->GetSelfillum().value(); } } } else { ErrorLog("Bad model: reference to nonexistent texture index in sub-model" + (pName.empty() ? "" : " \"" + pName + "\""), logtype::model); m_material = null_handle; } } else { if (iTexture == 0) m_material = GfxRenderer->Fetch_Material("colored"); else m_material = iTexture; } b_aAnim = b_Anim; // skopiowanie animacji do drugiego cyklu if (eType == TP_STARS) { m_material = GfxRenderer->Fetch_Material("stars"); iFlags |= 0x10; } else if ((eType == TP_FREESPOTLIGHT) && (iFlags & 0x10)) { // we've added light glare which needs to be rendered during transparent phase, // but models converted to e3d before addition won't have the render flag set correctly for this // so as a workaround we're doing it here manually iFlags |= 0x20; } // intercept and fix hotspot values if specified in degrees and not directly if (fCosFalloffAngle > 1.0f) { fCosFalloffAngle = std::cos(DegToRad(0.5f * fCosFalloffAngle)); } if (fCosHotspotAngle > 1.0f) { fCosHotspotAngle = std::cos(DegToRad(0.5f * fCosHotspotAngle)); } // cap specular values for legacy models f4Specular = glm::vec4{clamp(f4Specular.r, 0.0f, 1.0f), clamp(f4Specular.g, 0.0f, 1.0f), clamp(f4Specular.b, 0.0f, 1.0f), clamp(f4Specular.a, 0.0f, 1.0f)}; iFlags &= ~0x0200; // wczytano z pliku binarnego (nie jest właścicielem tablic) if (fMatrix != nullptr) { auto const matrix = glm::make_mat4(fMatrix->readArray()); glm::vec3 const scale{glm::length(glm::vec3(glm::column(matrix, 0))), glm::length(glm::vec3(glm::column(matrix, 1))), glm::length(glm::vec3(glm::column(matrix, 2)))}; if ((std::abs(scale.x - 1.0f) > 0.01) || (std::abs(scale.y - 1.0f) > 0.01) || (std::abs(scale.z - 1.0f) > 0.01)) { ErrorLog("Bad model: transformation matrix for sub-model \"" + pName + "\" imposes geometry scaling (factors: " + to_string(scale) + ")", logtype::model); m_normalizenormals = (((std::abs(scale.x - scale.y) < 0.01f) && (std::abs(scale.y - scale.z) < 0.01f)) ? rescale : normalize); } } } bool TSubModel::HasAnyVertexUserData() const { for (const TSubModel *sm = this; sm; sm = sm->Next) { if (m_geometry.handle) { if (!GfxRenderer->UserData(m_geometry.handle).empty()) return true; } else { if (!sm->Userdata.empty()) return true; } if (sm->Child && sm->Child->HasAnyVertexUserData()) return true; } return false; }; void TModel3d::LoadFromBinFile(std::string const &FileName, bool dynamic) { // wczytanie modelu z pliku binarnego WriteLog("Loading binary format 3d model data from \"" + FileName + "\"...", logtype::model); std::ifstream file(FileName, std::ios::binary); uint32_t type = sn_utils::ld_uint32(file); uint32_t size = sn_utils::ld_uint32(file) - 8; if (type == MAKE_ID4('E', '3', 'D', '0')) { deserialize(file, size, dynamic); file.close(); WriteLog("Finished loading 3d model data from \"" + FileName + "\"", logtype::model); } else { // throw std::runtime_error("e3d: unknown main chunk"); ErrorLog("Bad model: unknown main chunk in file \"" + FileName + "\"", logtype::model); file.close(); } }; TSubModel *TModel3d::AppendChildFromGeometry(const std::string &name, const std::string &parent, const gfx::vertex_array &vertices, const gfx::index_array &indices) { iFlags |= 0x0200; TSubModel *sm = new TSubModel(); sm->Parent = AddToNamed(parent.c_str(), sm); sm->m_geometry.vertex_count = vertices.size(); sm->m_geometry.index_count = indices.size(); sm->eType = GL_TRIANGLES; sm->pName = name; sm->m_material = GfxRenderer->Fetch_Material("colored"); sm->fMatrix = new float4x4(); sm->fMatrix->Identity(); sm->iFlags |= 0x10; sm->iFlags |= 0x8000; sm->WillBeAnimated(); if (vertices.empty()) sm->iFlags &= ~0x3F; sm->Vertices = vertices; sm->Indices = indices; m_vertexcount += vertices.size(); m_indexcount += indices.size(); if (!Root) Root = sm; return sm; } void TModel3d::LoadFromTextFile(std::string const &FileName, bool dynamic) { // wczytanie submodelu z pliku tekstowego WriteLog("Loading text format 3d model data from \"" + FileName + "\"...", logtype::model); iFlags |= 0x0200; // wczytano z pliku tekstowego (właścicielami tablic są submodle) cParser parser(FileName, cParser::buffer_FILE); // Ra: tu powinno być "models/"... TSubModel *SubModel; std::string token = parser.getToken(); while (token != "" || parser.eof()) { std::string parent; // parser.getToken(parent); parser.getTokens(1, false); // nazwa submodelu nadrzędnego bez zmieny na małe parser >> parent; if (parent == "") { break; } SubModel = new TSubModel(); SubModel->Parent = GetFromName(parent); { auto const result{SubModel->Load(parser, dynamic)}; m_indexcount += result.first; m_vertexcount += result.second; } if (SubModel->Parent == nullptr && parent != "none") ErrorLog("Bad model: parent for sub-model \"" + SubModel->pName + "\" doesn't exist or is located later in the model data", logtype::model); AddTo(SubModel->Parent, SubModel); parser.getTokens(); parser >> token; } // Ra: od wersji 334 przechylany jest cały model, a nie tylko pierwszy submodel // ale bujanie kabiny nadal używa bananów :( od 393 przywrócone, ale z dodatkowym warunkiem if (Global.iConvertModels & 4) { // automatyczne banany czasem psuły przechylanie kabin... if (dynamic && Root) { if (Root->NextGet()) // jeśli ma jakiekolwiek kolejne { // dynamic musi mieć "banana", bo tylko pierwszy obiekt jest animowany, a następne nie SubModel = new TSubModel(); // utworzenie pustego SubModel->ChildAdd(Root); Root = SubModel; ++iSubModelsCount; } Root->WillBeAnimated(); // bo z tym jest dużo problemów } } } void TModel3d::Init() { // obrócenie początkowe układu współrzędnych, dla // pojazdów wykonywane po analizie animacji if (iFlags & 0x8000) return; // operacje zostały już wykonane if (Root) { if (iFlags & 0x0200) { // jeśli wczytano z pliku tekstowego jest jakiś dziwny błąd, // że obkręcany ma być tylko ostatni submodel głównego łańcucha // argumet określa, czy wykonać pierwotny obrót Root->InitialRotate(true); } iFlags |= Root->FlagsCheck() | 0x8000; // flagi całego modelu if (m_vertexcount) { if (m_geometrybank == null_handle) { m_geometrybank = GfxRenderer->Create_Bank(); } std::size_t indexoffset = 0; std::size_t vertexoffset = 0; Root->create_geometry(indexoffset, vertexoffset, m_geometrybank); } // determine final bounding radius from the root-level siblings auto const *root{Root}; while ((root = root->Next) != nullptr) { Root->m_boundingradius = std::max(Root->m_boundingradius, root->m_boundingradius); } if ((Global.iConvertModels & 1) && (false == asBinary.empty())) { SaveToBinFile(asBinary); asBinary = ""; // zablokowanie powtórnego zapisu } } // check if the model contains particle emitters find_smoke_sources(); }; //----------------------------------------------------------------------------- // 2012-02 funkcje do tworzenia terenu z E3D //----------------------------------------------------------------------------- int TModel3d::TerrainCount() const { // zliczanie kwadratów kilometrowych (główna // linia po Next) do tworznia tablicy int i = 0; TSubModel *r = Root; while (r) { r = r->NextGet(); ++i; } return i; }; TSubModel *TModel3d::TerrainSquare(int n) { // pobieranie wskaźnika do submodelu (n) int i = 0; TSubModel *r = Root; while (i < n) { r = r->NextGet(); ++i; } r->UnFlagNext(); // blokowanie wyświetlania po Next głównej listy return r; };