#include "lc_global.h" #include "lc_library.h" #include "lc_zipfile.h" #include "lc_file.h" #include "pieceinf.h" #include "lc_colors.h" #include "lc_texture.h" #include "lc_category.h" #include "lc_application.h" #include "lc_context.h" #include "lc_glextensions.h" #include "lc_synth.h" #include "project.h" #include #include #include #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)) #include #endif #if MAX_MEM_LEVEL >= 8 # define DEF_MEM_LEVEL 8 #else # define DEF_MEM_LEVEL MAX_MEM_LEVEL #endif #define LC_LIBRARY_CACHE_VERSION 0x0106 #define LC_LIBRARY_CACHE_ARCHIVE 0x0001 #define LC_LIBRARY_CACHE_DIRECTORY 0x0002 static lcVector2 lcCalculateTexCoord(const lcVector3& Position, const lcLibraryTextureMap* TextureMap) { switch (TextureMap->Type) { case lcLibraryTextureMapType::PLANAR: return lcVector2(lcDot3(Position, TextureMap->Params.Planar.Planes[0]) + TextureMap->Params.Planar.Planes[0].w, lcDot3(Position, TextureMap->Params.Planar.Planes[1]) + TextureMap->Params.Planar.Planes[1].w); case lcLibraryTextureMapType::CYLINDRICAL: { const lcVector4& FrontPlane = TextureMap->Params.Cylindrical.FrontPlane; const lcVector4& Plane1 = TextureMap->Params.Cylindrical.Plane1; const lcVector4& Plane2 = TextureMap->Params.Cylindrical.Plane2; lcVector2 TexCoord; float DotPlane1 = lcDot(lcVector4(Position, 1.0f), Plane1); lcVector3 PointInPlane1 = Position - lcVector3(Plane1) * DotPlane1; float DotFrontPlane = lcDot(lcVector4(PointInPlane1, 1.0f), FrontPlane); float DotPlane2 = lcDot(lcVector4(PointInPlane1, 1.0f), Plane2); float Angle1 = atan2f(DotPlane2, DotFrontPlane) / LC_PI * TextureMap->Angle1; TexCoord.x = lcClamp(0.5f + 0.5f * Angle1, 0.0f, 1.0f); TexCoord.y = DotPlane1 / TextureMap->Params.Cylindrical.UpLength; return TexCoord; } case lcLibraryTextureMapType::SPHERICAL: { const lcVector4& FrontPlane = TextureMap->Params.Spherical.FrontPlane; const lcVector3& Center = TextureMap->Params.Spherical.Center; const lcVector4& Plane1 = TextureMap->Params.Spherical.Plane1; const lcVector4& Plane2 = TextureMap->Params.Spherical.Plane2; lcVector2 TexCoord; lcVector3 VertexDir = Position - Center; float DotPlane1 = lcDot(lcVector4(Position, 1.0f), Plane1); lcVector3 PointInPlane1 = Position - lcVector3(Plane1) * DotPlane1; float DotFrontPlane = lcDot(lcVector4(PointInPlane1, 1.0f), FrontPlane); float DotPlane2 = lcDot(lcVector4(PointInPlane1, 1.0f), Plane2); float Angle1 = atan2f(DotPlane2, DotFrontPlane) / LC_PI * TextureMap->Angle1; TexCoord.x = 0.5f + 0.5f * Angle1; float Angle2 = asinf(DotPlane1 / lcLength(VertexDir)) / LC_PI * TextureMap->Angle2; TexCoord.y = 0.5f - Angle2; return TexCoord; } } return lcVector2(0.0f, 0.0f); } lcPiecesLibrary::lcPiecesLibrary() : mLoadMutex(QMutex::Recursive) { #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)) QStringList cachePathList = QStandardPaths::standardLocations(QStandardPaths::CacheLocation); mCachePath = cachePathList.first(); #else mCachePath = QDesktopServices::storageLocation(QDesktopServices::CacheLocation); #endif QDir Dir; Dir.mkpath(mCachePath); mNumOfficialPieces = 0; mZipFiles[LC_ZIPFILE_OFFICIAL] = nullptr; mZipFiles[LC_ZIPFILE_UNOFFICIAL] = nullptr; mBuffersDirty = false; mHasUnofficial = false; mCancelLoading = false; } lcPiecesLibrary::~lcPiecesLibrary() { mLoadMutex.lock(); mLoadQueue.clear(); mLoadMutex.unlock(); mCancelLoading = true; WaitForLoadQueue(); Unload(); } void lcPiecesLibrary::Unload() { for (const auto& PieceIt : mPieces) delete PieceIt.second; mPieces.clear(); for (const auto& PrimitiveIt : mPrimitives) delete PrimitiveIt.second; mPrimitives.clear(); for (int TextureIdx = 0; TextureIdx < mTextures.GetSize(); TextureIdx++) delete mTextures[TextureIdx]; mTextures.RemoveAll(); mNumOfficialPieces = 0; delete mZipFiles[LC_ZIPFILE_OFFICIAL]; mZipFiles[LC_ZIPFILE_OFFICIAL] = nullptr; delete mZipFiles[LC_ZIPFILE_UNOFFICIAL]; mZipFiles[LC_ZIPFILE_UNOFFICIAL] = nullptr; } void lcPiecesLibrary::RemoveTemporaryPieces() { QMutexLocker LoadLock(&mLoadMutex); for (auto PieceIt = mPieces.begin(); PieceIt != mPieces.end();) { PieceInfo* Info = PieceIt->second; if (Info->IsTemporary() && Info->GetRefCount() == 0) { PieceIt = mPieces.erase(PieceIt); delete Info; } else PieceIt++; } } void lcPiecesLibrary::RemovePiece(PieceInfo* Info) { for (auto PieceIt = mPieces.begin(); PieceIt != mPieces.end(); PieceIt++) { if (PieceIt->second == Info) { mPieces.erase(PieceIt); break; } } delete Info; } void lcPiecesLibrary::RenamePiece(PieceInfo* Info, const char* NewName) { for (auto PieceIt = mPieces.begin(); PieceIt != mPieces.end(); PieceIt++) { if (PieceIt->second == Info) { mPieces.erase(PieceIt); break; } } strncpy(Info->mFileName, NewName, sizeof(Info->mFileName)); Info->mFileName[sizeof(Info->mFileName) - 1] = 0; strncpy(Info->m_strDescription, NewName, sizeof(Info->m_strDescription)); Info->m_strDescription[sizeof(Info->m_strDescription) - 1] = 0; char PieceName[LC_PIECE_NAME_LEN]; strcpy(PieceName, Info->mFileName); strupr(PieceName); mPieces[PieceName] = Info; } PieceInfo* lcPiecesLibrary::FindPiece(const char* PieceName, Project* CurrentProject, bool CreatePlaceholder, bool SearchProjectFolder) { QString ProjectPath; if (SearchProjectFolder) { QString FileName = CurrentProject->GetFileName(); if (!FileName.isEmpty()) ProjectPath = QFileInfo(FileName).absolutePath(); } char CleanName[LC_PIECE_NAME_LEN]; const char* Src = PieceName; char* Dst = CleanName; while (*Src && Dst - CleanName != sizeof(CleanName)) { if (*Src == '\\') *Dst = '/'; else if (*Src >= 'a' && *Src <= 'z') *Dst = *Src + 'A' - 'a'; else *Dst = *Src; Src++; Dst++; } *Dst = 0; const auto PieceIt = mPieces.find(CleanName); if (PieceIt != mPieces.end()) { PieceInfo* Info = PieceIt->second; if ((!CurrentProject || !Info->IsModel() || CurrentProject->GetModels().FindIndex(Info->GetModel()) != -1) && (!ProjectPath.isEmpty() || !Info->IsProject())) return Info; } if (!ProjectPath.isEmpty()) { QFileInfo ProjectFile = QFileInfo(ProjectPath + QDir::separator() + PieceName); if (ProjectFile.isFile()) { Project* NewProject = new Project(); if (NewProject->Load(ProjectFile.absoluteFilePath())) { PieceInfo* Info = new PieceInfo(); Info->CreateProject(NewProject, PieceName); mPieces[CleanName] = Info; return Info; } else delete NewProject; } } if (CreatePlaceholder) { PieceInfo* Info = new PieceInfo(); Info->CreatePlaceholder(PieceName); mPieces[CleanName] = Info; return Info; } return nullptr; } lcTexture* lcPiecesLibrary::FindTexture(const char* TextureName, Project* CurrentProject, bool SearchProjectFolder) { for (int TextureIdx = 0; TextureIdx < mTextures.GetSize(); TextureIdx++) if (!strcmp(TextureName, mTextures[TextureIdx]->mName)) return mTextures[TextureIdx]; QString ProjectPath; if (SearchProjectFolder) { QString FileName = CurrentProject->GetFileName(); if (!FileName.isEmpty()) ProjectPath = QFileInfo(FileName).absolutePath(); } if (!ProjectPath.isEmpty()) { QFileInfo TextureFile = QFileInfo(ProjectPath + QDir::separator() + TextureName + ".png"); if (TextureFile.isFile()) { lcTexture* Texture = lcLoadTexture(TextureFile.absoluteFilePath(), LC_TEXTURE_WRAPU | LC_TEXTURE_WRAPV); if (Texture) { mTextures.Add(Texture); return Texture; } } } return nullptr; } bool lcPiecesLibrary::Load(const QString& LibraryPath, bool ShowProgress) { Unload(); if (OpenArchive(LibraryPath, LC_ZIPFILE_OFFICIAL)) { lcMemFile ColorFile; if (!mZipFiles[LC_ZIPFILE_OFFICIAL]->ExtractFile("ldraw/ldconfig.ldr", ColorFile) || !lcLoadColorFile(ColorFile)) lcLoadDefaultColors(); mLibraryDir = QFileInfo(LibraryPath).absoluteDir(); QString UnofficialFileName = mLibraryDir.absoluteFilePath(QLatin1String("ldrawunf.zip")); if (!OpenArchive(UnofficialFileName, LC_ZIPFILE_UNOFFICIAL)) UnofficialFileName.clear(); ReadArchiveDescriptions(LibraryPath, UnofficialFileName); } else { mLibraryDir = LibraryPath; if (OpenDirectory(mLibraryDir, ShowProgress)) { lcDiskFile ColorFile(mLibraryDir.absoluteFilePath(QLatin1String("ldconfig.ldr"))); if (!ColorFile.Open(QIODevice::ReadOnly) || !lcLoadColorFile(ColorFile)) { ColorFile.SetFileName(mLibraryDir.absoluteFilePath(QLatin1String("LDConfig.ldr"))); if (!ColorFile.Open(QIODevice::ReadOnly) || !lcLoadColorFile(ColorFile)) lcLoadDefaultColors(); } } else return false; } lcLoadDefaultCategories(); lcSynthInit(); return true; } bool lcPiecesLibrary::OpenArchive(const QString& FileName, lcZipFileType ZipFileType) { lcDiskFile* File = new lcDiskFile(FileName); if (!File->Open(QIODevice::ReadOnly) || !OpenArchive(File, FileName, ZipFileType)) { delete File; return false; } return true; } bool lcPiecesLibrary::OpenArchive(lcFile* File, const QString& FileName, lcZipFileType ZipFileType) { lcZipFile* ZipFile = new lcZipFile(); if (!ZipFile->OpenRead(File)) { delete ZipFile; return false; } mZipFiles[ZipFileType] = ZipFile; if (ZipFileType == LC_ZIPFILE_OFFICIAL) mLibraryFileName = FileName; else mUnofficialFileName = FileName; for (int FileIdx = 0; FileIdx < ZipFile->mFiles.GetSize(); FileIdx++) { lcZipFileInfo& FileInfo = ZipFile->mFiles[FileIdx]; char NameBuffer[LC_PIECE_NAME_LEN]; char* Name = NameBuffer; const char* Src = FileInfo.file_name; char* Dst = Name; while (*Src && Dst - Name < LC_PIECE_NAME_LEN) { if (*Src >= 'a' && *Src <= 'z') *Dst = *Src + 'A' - 'a'; else if (*Src == '\\') *Dst = '/'; else *Dst = *Src; Src++; Dst++; } if (Dst - Name <= 4) continue; *Dst = 0; Dst -= 4; if (memcmp(Dst, ".DAT", 4)) { if (!memcmp(Dst, ".PNG", 4)) { if ((ZipFileType == LC_ZIPFILE_OFFICIAL && !memcmp(Name, "LDRAW/PARTS/TEXTURES/", 21)) || (ZipFileType == LC_ZIPFILE_UNOFFICIAL && !memcmp(Name, "PARTS/TEXTURES/", 15))) { lcTexture* Texture = new lcTexture(); mTextures.Add(Texture); *Dst = 0; strncpy(Texture->mName, Name + (ZipFileType == LC_ZIPFILE_OFFICIAL ? 21 : 15), sizeof(Texture->mName)); Texture->mName[sizeof(Texture->mName) - 1] = 0; } } continue; } if (ZipFileType == LC_ZIPFILE_OFFICIAL) { if (memcmp(Name, "LDRAW/", 6)) continue; Name += 6; } if (!memcmp(Name, "PARTS/", 6)) { Name += 6; if (memcmp(Name, "S/", 2)) { PieceInfo* Info = FindPiece(Name, nullptr, false, false); if (!Info) { Info = new PieceInfo(); strncpy(Info->mFileName, FileInfo.file_name + (Name - NameBuffer), sizeof(Info->mFileName)); Info->mFileName[sizeof(Info->mFileName) - 1] = 0; mPieces[Name] = Info; } Info->SetZipFile(ZipFileType, FileIdx); } else { lcLibraryPrimitive* Primitive = FindPrimitive(Name); if (!Primitive) mPrimitives[Name] = new lcLibraryPrimitive(QString(), FileInfo.file_name + (Name - NameBuffer), ZipFileType, FileIdx, false, true); else Primitive->SetZipFile(ZipFileType, FileIdx); } } else if (!memcmp(Name, "P/", 2)) { Name += 2; lcLibraryPrimitive* Primitive = FindPrimitive(Name); if (!Primitive) mPrimitives[Name] = new lcLibraryPrimitive(QString(), FileInfo.file_name + (Name - NameBuffer), ZipFileType, FileIdx, (memcmp(Name, "STU", 3) == 0), false); else Primitive->SetZipFile(ZipFileType, FileIdx); } } return true; } void lcPiecesLibrary::ReadArchiveDescriptions(const QString& OfficialFileName, const QString& UnofficialFileName) { QFileInfo OfficialInfo(OfficialFileName); QFileInfo UnofficialInfo(UnofficialFileName); mArchiveCheckSum[0] = OfficialInfo.size(); #if (QT_VERSION >= QT_VERSION_CHECK(4, 7, 0)) mArchiveCheckSum[1] = OfficialInfo.lastModified().toMSecsSinceEpoch(); #else mArchiveCheckSum[1] = OfficialInfo.lastModified().toTime_t(); #endif if (!UnofficialFileName.isEmpty()) { mArchiveCheckSum[2] = UnofficialInfo.size(); #if (QT_VERSION >= QT_VERSION_CHECK(4, 7, 0)) mArchiveCheckSum[3] = UnofficialInfo.lastModified().toMSecsSinceEpoch(); #else mArchiveCheckSum[3] = UnofficialInfo.lastModified().toTime_t(); #endif } else { mArchiveCheckSum[2] = 0; mArchiveCheckSum[3] = 0; } QString IndexFileName = QFileInfo(QDir(mCachePath), QLatin1String("index")).absoluteFilePath(); if (!LoadCacheIndex(IndexFileName)) { lcMemFile PieceFile; for (const auto& PieceIt : mPieces) { PieceInfo* Info = PieceIt.second; mZipFiles[Info->mZipFileType]->ExtractFile(Info->mZipFileIndex, PieceFile, 256); PieceFile.Seek(0, SEEK_END); PieceFile.WriteU8(0); char* Src = (char*)PieceFile.mBuffer + 2; char* Dst = Info->m_strDescription; for (;;) { if (*Src != '\r' && *Src != '\n' && *Src && Dst - Info->m_strDescription < (int)sizeof(Info->m_strDescription) - 1) { *Dst++ = *Src++; continue; } *Dst = 0; break; } } SaveArchiveCacheIndex(IndexFileName); } } bool lcPiecesLibrary::OpenDirectory(const QDir& LibraryDir, bool ShowProgress) { const QLatin1String BaseFolders[LC_NUM_FOLDERTYPES] = { QLatin1String("unofficial/"), QLatin1String("") }; const int NumBaseFolders = sizeof(BaseFolders) / sizeof(BaseFolders[0]); QFileInfoList FileLists[NumBaseFolders]; for (unsigned int BaseFolderIdx = 0; BaseFolderIdx < NumBaseFolders; BaseFolderIdx++) { QString ParstPath = QDir(LibraryDir.absoluteFilePath(BaseFolders[BaseFolderIdx])).absoluteFilePath(QLatin1String("parts/")); QDir Dir = QDir(ParstPath, QLatin1String("*.dat"), QDir::SortFlags(QDir::Name | QDir::IgnoreCase), QDir::Files | QDir::Hidden | QDir::Readable); FileLists[BaseFolderIdx] = Dir.entryInfoList(); } if (FileLists[LC_FOLDER_OFFICIAL].isEmpty()) return false; mHasUnofficial = !FileLists[LC_FOLDER_UNOFFICIAL].isEmpty(); ReadDirectoryDescriptions(FileLists, ShowProgress); for (unsigned int BaseFolderIdx = 0; BaseFolderIdx < sizeof(BaseFolders) / sizeof(BaseFolders[0]); BaseFolderIdx++) { const char* PrimitiveDirectories[] = { "p/", "parts/s/" }; bool SubFileDirectories[] = { false, false, true }; QDir BaseDir(LibraryDir.absoluteFilePath(QLatin1String(BaseFolders[BaseFolderIdx]))); for (int DirectoryIdx = 0; DirectoryIdx < (int)(sizeof(PrimitiveDirectories) / sizeof(PrimitiveDirectories[0])); DirectoryIdx++) { QString ChildPath = BaseDir.absoluteFilePath(QLatin1String(PrimitiveDirectories[DirectoryIdx])); QDirIterator DirIterator(ChildPath, QStringList() << QLatin1String("*.dat"), QDir::Files | QDir::Hidden | QDir::Readable, QDirIterator::Subdirectories); while (DirIterator.hasNext()) { char Name[LC_PIECE_NAME_LEN]; QString FileName = DirIterator.next(); QByteArray FileString = BaseDir.relativeFilePath(FileName).toLatin1(); const char* Src = strchr(FileString, '/') + 1; char* Dst = Name; while (*Src && Dst - Name < (int)sizeof(Name)) { if (*Src >= 'a' && *Src <= 'z') *Dst = *Src + 'A' - 'a'; else if (*Src == '\\') *Dst = '/'; else *Dst = *Src; Src++; Dst++; } *Dst = 0; if (Dst - Name <= 4) continue; Dst -= 4; if (memcmp(Dst, ".DAT", 4)) continue; if (mHasUnofficial && IsPrimitive(Name)) continue; if (BaseFolderIdx == 0) mHasUnofficial = true; bool SubFile = SubFileDirectories[DirectoryIdx]; mPrimitives[Name] = new lcLibraryPrimitive(std::move(FileName), strchr(FileString, '/') + 1, LC_NUM_ZIPFILES, 0, !SubFile && (memcmp(Name, "STU", 3) == 0), SubFile); } } } QDir Dir(LibraryDir.absoluteFilePath(QLatin1String("parts/textures/")), QLatin1String("*.png"), QDir::SortFlags(QDir::Name | QDir::IgnoreCase), QDir::Files | QDir::Hidden | QDir::Readable); QStringList FileList = Dir.entryList(); mTextures.AllocGrow(FileList.size()); for (int FileIdx = 0; FileIdx < FileList.size(); FileIdx++) { char Name[LC_MAXPATH]; QByteArray FileString = FileList[FileIdx].toLatin1(); const char* Src = FileString; char* Dst = Name; while (*Src && Dst - Name < (int)sizeof(Name)) { if (*Src >= 'a' && *Src <= 'z') *Dst = *Src + 'A' - 'a'; else if (*Src == '\\') *Dst = '/'; else *Dst = *Src; Src++; Dst++; } if (Dst - Name <= 4) continue; Dst -= 4; if (memcmp(Dst, ".PNG", 4)) continue; *Dst = 0; lcTexture* Texture = new lcTexture(); mTextures.Add(Texture); strncpy(Texture->mName, Name, sizeof(Texture->mName)); Texture->mName[sizeof(Texture->mName) - 1] = 0; } return true; } void lcPiecesLibrary::ReadDirectoryDescriptions(const QFileInfoList (&FileLists)[LC_NUM_FOLDERTYPES], bool ShowProgress) { QString IndexFileName = QFileInfo(QDir(mCachePath), QLatin1String("index")).absoluteFilePath(); lcMemFile IndexFile; std::vector CachedDescriptions; if (ReadDirectoryCacheFile(IndexFileName, IndexFile)) { QString LibraryPath = IndexFile.ReadQString(); if (LibraryPath == mLibraryDir.absolutePath()) { int NumDescriptions = IndexFile.ReadU32(); CachedDescriptions.reserve(NumDescriptions); while (NumDescriptions--) { const char* FileName = (const char*)IndexFile.mBuffer + IndexFile.GetPosition(); CachedDescriptions.push_back(FileName); IndexFile.Seek(strlen(FileName) + 1, SEEK_CUR); const char* Description = (const char*)IndexFile.mBuffer + IndexFile.GetPosition(); IndexFile.Seek(strlen(Description) + 1, SEEK_CUR); IndexFile.Seek(4 + 1 + 8, SEEK_CUR); } } } for (int FolderIdx = 0; FolderIdx < LC_NUM_FOLDERTYPES; FolderIdx++) { const QFileInfoList& FileList = FileLists[FolderIdx]; for (int FileIdx = 0; FileIdx < FileList.size(); FileIdx++) { char Name[LC_PIECE_NAME_LEN]; QByteArray FileString = FileList[FileIdx].fileName().toLatin1(); const char* Src = FileString; char* Dst = Name; while (*Src && Dst - Name < (int)sizeof(Name)) { if (*Src >= 'a' && *Src <= 'z') *Dst = *Src + 'A' - 'a'; else if (*Src == '\\') *Dst = '/'; else *Dst = *Src; Src++; Dst++; } *Dst = 0; if (FolderIdx == LC_FOLDER_OFFICIAL && mHasUnofficial && mPieces.find(Name) != mPieces.end()) continue; PieceInfo* Info = new PieceInfo(); strncpy(Info->mFileName, FileString, sizeof(Info->mFileName)); Info->mFileName[sizeof(Info->mFileName) - 1] = 0; Info->mFolderType = FolderIdx; Info->mFolderIndex = FileIdx; mPieces[Name] = Info; } } QAtomicInt FilesLoaded; bool Modified = false; auto ReadDescriptions = [&FileLists, &CachedDescriptions, &FilesLoaded, &Modified](const std::pair& Entry) { PieceInfo* Info = Entry.second; FilesLoaded.ref(); lcDiskFile PieceFile(FileLists[Info->mFolderType][Info->mFolderIndex].absoluteFilePath()); char Line[1024]; if (!CachedDescriptions.empty()) { auto DescriptionCompare = [](const void* Key, const void* Element) { return strcmp((const char*)Key, *(const char**)Element); }; void* CachedDescription = bsearch(Info->mFileName, &CachedDescriptions.front(), CachedDescriptions.size(), sizeof(char*), DescriptionCompare); if (CachedDescription) { const char* FileName = *(const char**)CachedDescription; const char* Description = FileName + strlen(FileName) + 1; uint64_t CachedFileTime = *(uint64_t*)(Description + strlen(Description) + 1 + 4 + 1); #if (QT_VERSION >= QT_VERSION_CHECK(4, 7, 0)) quint64 FileTime = FileLists[Info->mFolderType][Info->mFolderIndex].lastModified().toMSecsSinceEpoch(); #else quint64 FileTime = FileLists[Info->mFolderType][Info->mFolderIndex].lastModified().toTime_t(); #endif if (FileTime == CachedFileTime) { strcpy(Info->m_strDescription, Description); return; } } } if (!PieceFile.Open(QIODevice::ReadOnly) || !PieceFile.ReadLine(Line, sizeof(Line))) { strcpy(Info->m_strDescription, "Unknown"); return; } const char* Src = Line + 2; char* Dst = Info->m_strDescription; for (;;) { if (*Src != '\r' && *Src != '\n' && *Src && Dst - Info->m_strDescription < (int)sizeof(Info->m_strDescription) - 1) { *Dst++ = *Src++; continue; } *Dst = 0; break; } Modified = true; }; QProgressDialog* ProgressDialog = new QProgressDialog(nullptr); ProgressDialog->setWindowFlags(ProgressDialog->windowFlags() & ~Qt::WindowCloseButtonHint); ProgressDialog->setWindowTitle(tr("Initializing")); ProgressDialog->setLabelText(tr("Loading Parts Library")); ProgressDialog->setMaximum((int)mPieces.size()); ProgressDialog->setMinimum(0); ProgressDialog->setValue(0); ProgressDialog->setCancelButton(nullptr); ProgressDialog->setAutoReset(false); if (ShowProgress) ProgressDialog->show(); QFuture LoadFuture = QtConcurrent::map(mPieces, ReadDescriptions); while (!LoadFuture.isFinished()) { #if (QT_VERSION >= QT_VERSION_CHECK(5, 3, 0) || QT_VERSION < QT_VERSION_CHECK(5, 0, 0) ) ProgressDialog->setValue(FilesLoaded); #else ProgressDialog->setValue(FilesLoaded.load()); #endif QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); } #if (QT_VERSION >= QT_VERSION_CHECK(5, 3, 0) || QT_VERSION < QT_VERSION_CHECK(5, 0, 0) ) ProgressDialog->setValue(FilesLoaded); #else ProgressDialog->setValue(FilesLoaded.load()); #endif QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); ProgressDialog->deleteLater(); if (Modified) { lcMemFile NewIndexFile; NewIndexFile.WriteQString(mLibraryDir.absolutePath()); NewIndexFile.WriteU32((quint32)mPieces.size()); std::vector SortedPieces; SortedPieces.reserve(mPieces.size()); for (const auto& PieceIt : mPieces) SortedPieces.push_back(PieceIt.second); auto PieceInfoCompare = [](PieceInfo* Info1, PieceInfo* Info2) { return strcmp(Info1->mFileName, Info2->mFileName) < 0; }; std::sort(SortedPieces.begin(), SortedPieces.end(), PieceInfoCompare); for (const PieceInfo* Info : SortedPieces) { if (NewIndexFile.WriteBuffer(Info->mFileName, strlen(Info->mFileName) + 1) == 0) return; if (NewIndexFile.WriteBuffer(Info->m_strDescription, strlen(Info->m_strDescription) + 1) == 0) return; NewIndexFile.WriteU32(Info->mFlags); NewIndexFile.WriteU8(Info->mFolderType); #if (QT_VERSION >= QT_VERSION_CHECK(4, 7, 0)) quint64 FileTime = FileLists[Info->mFolderType][Info->mFolderIndex].lastModified().toMSecsSinceEpoch(); #else quint64 FileTime = FileLists[Info->mFolderType][Info->mFolderIndex].lastModified().toTime_t(); #endif NewIndexFile.WriteU64(FileTime); } WriteDirectoryCacheFile(IndexFileName, NewIndexFile); } } bool lcPiecesLibrary::ReadArchiveCacheFile(const QString& FileName, lcMemFile& CacheFile) { QFile File(FileName); if (!File.open(QIODevice::ReadOnly)) return false; quint32 CacheVersion, CacheFlags; if (File.read((char*)&CacheVersion, sizeof(CacheVersion)) == -1 || CacheVersion != LC_LIBRARY_CACHE_VERSION) return false; if (File.read((char*)&CacheFlags, sizeof(CacheFlags)) == -1 || CacheFlags != LC_LIBRARY_CACHE_ARCHIVE) return false; qint64 CacheCheckSum[4]; if (File.read((char*)&CacheCheckSum, sizeof(CacheCheckSum)) == -1 || memcmp(CacheCheckSum, mArchiveCheckSum, sizeof(CacheCheckSum))) return false; quint32 UncompressedSize; if (File.read((char*)&UncompressedSize, sizeof(UncompressedSize)) == -1) return false; QByteArray CompressedData = File.readAll(); CacheFile.SetLength(UncompressedSize); CacheFile.Seek(0, SEEK_SET); const int CHUNK = 16384; int ret; unsigned have; z_stream strm; unsigned char in[CHUNK]; unsigned char out[CHUNK]; int pos; strm.zalloc = Z_NULL; strm.zfree = Z_NULL; strm.opaque = Z_NULL; strm.avail_in = 0; strm.next_in = Z_NULL; pos = 0; ret = inflateInit2(&strm, -MAX_WBITS); if (ret != Z_OK) return ret; do { strm.avail_in = lcMin(CompressedData.size() - pos, CHUNK); strm.next_in = in; if (strm.avail_in == 0) break; memcpy(in, CompressedData.constData() + pos, strm.avail_in); pos += strm.avail_in; do { strm.avail_out = CHUNK; strm.next_out = out; ret = inflate(&strm, Z_NO_FLUSH); switch (ret) { case Z_NEED_DICT: ret = Z_DATA_ERROR; Q_FALLTHROUGH(); case Z_DATA_ERROR: Q_FALLTHROUGH(); case Z_MEM_ERROR: (void)inflateEnd(&strm); return ret; } have = CHUNK - strm.avail_out; CacheFile.WriteBuffer(out, have); } while (strm.avail_out == 0); } while (ret != Z_STREAM_END); (void)inflateEnd(&strm); CacheFile.Seek(0, SEEK_SET); return ret == Z_STREAM_END; } bool lcPiecesLibrary::WriteArchiveCacheFile(const QString& FileName, lcMemFile& CacheFile) { QFile File(FileName); if (!File.open(QIODevice::WriteOnly)) return false; quint32 CacheVersion = LC_LIBRARY_CACHE_VERSION; quint32 CacheFlags = LC_LIBRARY_CACHE_ARCHIVE; if (File.write((char*)&CacheVersion, sizeof(CacheVersion)) == -1) return false; if (File.write((char*)&CacheFlags, sizeof(CacheFlags)) == -1) return false; if (File.write((char*)&mArchiveCheckSum, sizeof(mArchiveCheckSum)) == -1) return false; quint32 UncompressedSize = (quint32)CacheFile.GetLength(); if (File.write((char*)&UncompressedSize, sizeof(UncompressedSize)) == -1) return false; const size_t BufferSize = 16384; char WriteBuffer[BufferSize]; z_stream Stream; quint32 Crc32 = 0; CacheFile.Seek(0, SEEK_SET); Stream.zalloc = nullptr; Stream.zfree = nullptr; Stream.opaque = nullptr; if (deflateInit2(&Stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY) != Z_OK) return false; Bytef* BufferIn = CacheFile.mBuffer; int FlushMode; do { uInt Read = (uInt)lcMin(CacheFile.GetLength() - (BufferIn - CacheFile.mBuffer), BufferSize); Stream.avail_in = Read; Stream.next_in = BufferIn; Crc32 = crc32(Crc32, BufferIn, Read); BufferIn += Read; FlushMode = (BufferIn >= CacheFile.mBuffer + CacheFile.GetLength()) ? Z_FINISH : Z_NO_FLUSH; do { Stream.avail_out = BufferSize; Stream.next_out = (Bytef*)WriteBuffer; deflate(&Stream, FlushMode); File.write(WriteBuffer, BufferSize - Stream.avail_out); } while (Stream.avail_out == 0); } while (FlushMode != Z_FINISH); deflateEnd(&Stream); return true; } bool lcPiecesLibrary::ReadDirectoryCacheFile(const QString& FileName, lcMemFile& CacheFile) { QFile File(FileName); if (!File.open(QIODevice::ReadOnly)) return false; quint32 CacheVersion, CacheFlags; if (File.read((char*)&CacheVersion, sizeof(CacheVersion)) == -1 || CacheVersion != LC_LIBRARY_CACHE_VERSION) return false; if (File.read((char*)&CacheFlags, sizeof(CacheFlags)) == -1 || CacheFlags != LC_LIBRARY_CACHE_DIRECTORY) return false; quint32 UncompressedSize; if (File.read((char*)&UncompressedSize, sizeof(UncompressedSize)) == -1) return false; QByteArray Data = qUncompress(File.readAll()); if (Data.isEmpty()) return false; CacheFile.SetLength(Data.size()); CacheFile.Seek(0, SEEK_SET); CacheFile.WriteBuffer(Data.constData(), Data.size()); CacheFile.Seek(0, SEEK_SET); return true; } bool lcPiecesLibrary::WriteDirectoryCacheFile(const QString& FileName, lcMemFile& CacheFile) { QFile File(FileName); if (!File.open(QIODevice::WriteOnly)) return false; quint32 CacheVersion = LC_LIBRARY_CACHE_VERSION; if (File.write((char*)&CacheVersion, sizeof(CacheVersion)) == -1) return false; quint32 CacheFlags = LC_LIBRARY_CACHE_DIRECTORY; if (File.write((char*)&CacheFlags, sizeof(CacheFlags)) == -1) return false; quint32 UncompressedSize = (quint32)CacheFile.GetLength(); if (File.write((char*)&UncompressedSize, sizeof(UncompressedSize)) == -1) return false; File.write(qCompress(CacheFile.mBuffer, (int)CacheFile.GetLength())); return true; } bool lcPiecesLibrary::LoadCacheIndex(const QString& FileName) { lcMemFile IndexFile; if (!ReadArchiveCacheFile(FileName, IndexFile)) return false; quint32 NumFiles; if (IndexFile.ReadBuffer((char*)&NumFiles, sizeof(NumFiles)) == 0 || NumFiles != mPieces.size()) return false; for (const auto& PieceIt : mPieces) { PieceInfo* Info = PieceIt.second; quint8 Length; if (IndexFile.ReadBuffer((char*)&Length, sizeof(Length)) == 0 || Length >= sizeof(Info->m_strDescription)) return false; if (IndexFile.ReadBuffer((char*)Info->m_strDescription, Length) == 0 || IndexFile.ReadBuffer((char*)&Info->mFlags, sizeof(Info->mFlags)) == 0) return false; Info->m_strDescription[Length] = 0; } return true; } bool lcPiecesLibrary::SaveArchiveCacheIndex(const QString& FileName) { lcMemFile IndexFile; quint32 NumFiles = (quint32)mPieces.size(); if (IndexFile.WriteBuffer((char*)&NumFiles, sizeof(NumFiles)) == 0) return false; for (const auto& PieceIt : mPieces) { PieceInfo* Info = PieceIt.second; quint8 Length = (quint8)strlen(Info->m_strDescription); if (IndexFile.WriteBuffer((char*)&Length, sizeof(Length)) == 0) return false; if (IndexFile.WriteBuffer((char*)Info->m_strDescription, Length) == 0 || IndexFile.WriteBuffer((char*)&Info->mFlags, sizeof(Info->mFlags)) == 0) return false; } return WriteArchiveCacheFile(FileName, IndexFile); } bool lcPiecesLibrary::LoadCachePiece(PieceInfo* Info) { QString FileName = QFileInfo(QDir(mCachePath), QString::fromLatin1(Info->mFileName)).absoluteFilePath(); lcMemFile MeshData; if (!ReadArchiveCacheFile(FileName, MeshData)) return false; quint32 Flags; if (MeshData.ReadBuffer((char*)&Flags, sizeof(Flags)) == 0) return false; Info->mFlags = Flags; lcMesh* Mesh = new lcMesh; if (Mesh->FileLoad(MeshData)) { Info->SetMesh(Mesh); return true; } else { delete Mesh; return false; } } bool lcPiecesLibrary::SaveCachePiece(PieceInfo* Info) { lcMemFile MeshData; quint32 Flags = Info->mFlags; if (MeshData.WriteBuffer((char*)&Flags, sizeof(Flags)) == 0) return false; if (!Info->GetMesh()->FileSave(MeshData)) return false; QString FileName = QFileInfo(QDir(mCachePath), QString::fromLatin1(Info->mFileName)).absoluteFilePath(); return WriteArchiveCacheFile(FileName, MeshData); } class lcSleeper : public QThread { public: static void msleep(unsigned long Msecs) { QThread::msleep(Msecs); } }; void lcPiecesLibrary::LoadPieceInfo(PieceInfo* Info, bool Wait, bool Priority) { QMutexLocker LoadLock(&mLoadMutex); if (Wait) { if (Info->AddRef() == 1) Info->Load(); else { if (Info->mState == LC_PIECEINFO_UNLOADED) { Info->Load(); emit PartLoaded(Info); } else { LoadLock.unlock(); while (Info->mState != LC_PIECEINFO_LOADED) lcSleeper::msleep(10); } } } else { if (Info->AddRef() == 1) { if (Priority) mLoadQueue.prepend(Info); else mLoadQueue.append(Info); mLoadFutures.append(QtConcurrent::run([this]() { LoadQueuedPiece(); })); } } } void lcPiecesLibrary::ReleasePieceInfo(PieceInfo* Info) { QMutexLocker LoadLock(&mLoadMutex); if (Info->GetRefCount() == 0 || Info->Release() == 0) Info->Unload(); } void lcPiecesLibrary::LoadQueuedPiece() { mLoadMutex.lock(); PieceInfo* Info = nullptr; while (!mLoadQueue.isEmpty()) { Info = mLoadQueue.takeFirst(); if (Info->mState == LC_PIECEINFO_UNLOADED && Info->GetRefCount() > 0) { Info->mState = LC_PIECEINFO_LOADING; break; } Info = nullptr; } mLoadMutex.unlock(); if (Info) Info->Load(); emit PartLoaded(Info); } void lcPiecesLibrary::WaitForLoadQueue() { for (QFuture& Future : mLoadFutures) Future.waitForFinished(); mLoadFutures.clear(); } struct lcMergeSection { lcLibraryMeshSection* Shared; lcLibraryMeshSection* Lod; }; static int LibraryMeshSectionCompare(lcMergeSection const& First, lcMergeSection const& Second) { lcLibraryMeshSection* a = First.Lod ? First.Lod : First.Shared; lcLibraryMeshSection* b = Second.Lod ? Second.Lod : Second.Shared; if (a->mPrimitiveType != b->mPrimitiveType) { int PrimitiveOrder[LC_MESH_NUM_PRIMITIVE_TYPES] = { LC_MESH_TRIANGLES, LC_MESH_TEXTURED_TRIANGLES, LC_MESH_LINES, LC_MESH_TEXTURED_LINES, LC_MESH_CONDITIONAL_LINES }; for (int PrimitiveType = 0; PrimitiveType < LC_MESH_NUM_PRIMITIVE_TYPES; PrimitiveType++) { int Primitive = PrimitiveOrder[PrimitiveType]; if (a->mPrimitiveType == Primitive) return -1; if (b->mPrimitiveType == Primitive) return 1; } } bool TranslucentA = lcIsColorTranslucent(a->mColor); bool TranslucentB = lcIsColorTranslucent(b->mColor); if (TranslucentA != TranslucentB) return TranslucentA ? 1 : -1; return a->mColor > b->mColor ? -1 : 1; } bool lcPiecesLibrary::LoadPieceData(PieceInfo* Info) { lcLibraryMeshData MeshData; lcArray TextureStack; bool Loaded = false; bool SaveCache = false; if (Info->mZipFileType != LC_NUM_ZIPFILES && mZipFiles[Info->mZipFileType]) { if (LoadCachePiece(Info)) return true; lcMemFile PieceFile; if (mZipFiles[Info->mZipFileType]->ExtractFile(Info->mZipFileIndex, PieceFile)) Loaded = ReadMeshData(PieceFile, lcMatrix44Identity(), 16, false, TextureStack, MeshData, LC_MESHDATA_SHARED, true, nullptr, false); SaveCache = Loaded && (Info->mZipFileType == LC_ZIPFILE_OFFICIAL); } else { char FileName[LC_MAXPATH]; lcDiskFile PieceFile; if (mHasUnofficial) { sprintf(FileName, "unofficial/parts/%s", Info->mFileName); PieceFile.SetFileName(mLibraryDir.absoluteFilePath(QLatin1String(FileName))); if (PieceFile.Open(QIODevice::ReadOnly)) Loaded = ReadMeshData(PieceFile, lcMatrix44Identity(), 16, false, TextureStack, MeshData, LC_MESHDATA_SHARED, true, nullptr, false); } if (!Loaded) { sprintf(FileName, "parts/%s", Info->mFileName); PieceFile.SetFileName(mLibraryDir.absoluteFilePath(QLatin1String(FileName))); if (PieceFile.Open(QIODevice::ReadOnly)) Loaded = ReadMeshData(PieceFile, lcMatrix44Identity(), 16, false, TextureStack, MeshData, LC_MESHDATA_SHARED, true, nullptr, false); } } if (!Loaded || mCancelLoading) return false; CreateMesh(Info, MeshData); if (SaveCache) SaveCachePiece(Info); return true; } lcMesh* lcPiecesLibrary::CreateMesh(PieceInfo* Info, lcLibraryMeshData& MeshData) { lcMesh* Mesh = new lcMesh(); int BaseVertices[LC_NUM_MESHDATA_TYPES]; int BaseTexturedVertices[LC_NUM_MESHDATA_TYPES]; int NumVertices = 0; int NumTexturedVertices = 0; std::vector IndexRemap[LC_NUM_MESHDATA_TYPES]; std::vector TexturedIndexRemap[LC_NUM_MESHDATA_TYPES]; if (!MeshData.mHasTextures) { for (int MeshDataIdx = 0; MeshDataIdx < LC_NUM_MESHDATA_TYPES; MeshDataIdx++) { lcArray& Sections = MeshData.mSections[MeshDataIdx]; for (int SectionIdx = 0; SectionIdx < Sections.GetSize(); SectionIdx++) { lcLibraryMeshSection* Section = Sections[SectionIdx]; Section->mColor = lcGetColorIndex(Section->mColor); } BaseVertices[MeshDataIdx] = NumVertices; NumVertices += MeshData.mVertices[MeshDataIdx].GetSize(); } } else { for (int MeshDataIdx = 0; MeshDataIdx < LC_NUM_MESHDATA_TYPES; MeshDataIdx++) { lcArray& Sections = MeshData.mSections[MeshDataIdx]; for (int SectionIdx = 0; SectionIdx < Sections.GetSize(); SectionIdx++) { lcLibraryMeshSection* Section = Sections[SectionIdx]; Section->mColor = lcGetColorIndex(Section->mColor); } BaseVertices[MeshDataIdx] = NumVertices; BaseTexturedVertices[MeshDataIdx] = NumTexturedVertices; const lcArray& Vertices = MeshData.mVertices[MeshDataIdx]; IndexRemap[MeshDataIdx].resize(Vertices.GetSize()); TexturedIndexRemap[MeshDataIdx].resize(Vertices.GetSize()); for (int VertexIdx = 0; VertexIdx < Vertices.GetSize(); VertexIdx++) { const lcLibraryMeshVertex& Vertex = Vertices[VertexIdx]; if (Vertex.Usage & LC_LIBRARY_VERTEX_UNTEXTURED) { IndexRemap[MeshDataIdx][VertexIdx] = NumVertices; NumVertices++; } if (Vertex.Usage & LC_LIBRARY_VERTEX_TEXTURED) { TexturedIndexRemap[MeshDataIdx][VertexIdx] = NumTexturedVertices; NumTexturedVertices++; } } } } quint16 NumSections[LC_NUM_MESH_LODS]; int NumIndices = 0; lcArray MergeSections[LC_NUM_MESH_LODS]; for (int LodIdx = 0; LodIdx < LC_NUM_MESH_LODS; LodIdx++) { const lcArray& SharedSections = MeshData.mSections[LC_MESHDATA_SHARED]; const lcArray& Sections = MeshData.mSections[LodIdx]; for (int SharedSectionIdx = 0; SharedSectionIdx < SharedSections.GetSize(); SharedSectionIdx++) { lcLibraryMeshSection* SharedSection = SharedSections[SharedSectionIdx]; NumIndices += SharedSection->mIndices.GetSize(); lcMergeSection& MergeSection = MergeSections[LodIdx].Add(); MergeSection.Shared = SharedSection; MergeSection.Lod = nullptr; } for (int SectionIdx = 0; SectionIdx < Sections.GetSize(); SectionIdx++) { lcLibraryMeshSection* Section = Sections[SectionIdx]; bool Found = false; NumIndices += Section->mIndices.GetSize(); for (int SharedSectionIdx = 0; SharedSectionIdx < SharedSections.GetSize(); SharedSectionIdx++) { lcLibraryMeshSection* SharedSection = SharedSections[SharedSectionIdx]; if (SharedSection->mColor == Section->mColor && SharedSection->mPrimitiveType == Section->mPrimitiveType && SharedSection->mTexture == Section->mTexture) { lcMergeSection& MergeSection = MergeSections[LodIdx][SharedSectionIdx]; MergeSection.Lod = Section; Found = true; break; } } if (!Found) { lcMergeSection& MergeSection = MergeSections[LodIdx].Add(); MergeSection.Shared = nullptr; MergeSection.Lod = Section; } } NumSections[LodIdx] = MergeSections[LodIdx].GetSize(); MergeSections[LodIdx].Sort(LibraryMeshSectionCompare); } Mesh->Create(NumSections, NumVertices, NumTexturedVertices, NumIndices); lcVertex* DstVerts = (lcVertex*)Mesh->mVertexData; lcVector3 Min(FLT_MAX, FLT_MAX, FLT_MAX), Max(-FLT_MAX, -FLT_MAX, -FLT_MAX); bool UpdatedBoundingBox = false; if (!MeshData.mHasTextures) { for (int MeshDataIdx = 0; MeshDataIdx < LC_NUM_MESHDATA_TYPES; MeshDataIdx++) { const lcArray& Vertices = MeshData.mVertices[MeshDataIdx]; for (const lcLibraryMeshVertex& SrcVertex : Vertices) { lcVertex& DstVertex = *DstVerts++; DstVertex.Position = lcVector3LDrawToLeoCAD(SrcVertex.Position); DstVertex.Normal = lcPackNormal(lcVector3LDrawToLeoCAD(SrcVertex.Normal)); } for (const lcLibraryMeshSection* Section : MeshData.mSections[MeshDataIdx]) { if (Section->mPrimitiveType != LC_MESH_TRIANGLES) continue; UpdatedBoundingBox = true; for (quint32 Index : Section->mIndices) { lcVector3 Position = lcVector3LDrawToLeoCAD(Vertices[Index].Position); Min = lcMin(Min, Position); Max = lcMax(Max, Position); } } } } else { for (int MeshDataIdx = 0; MeshDataIdx < LC_NUM_MESHDATA_TYPES; MeshDataIdx++) { for (const lcLibraryMeshVertex& SrcVertex : MeshData.mVertices[MeshDataIdx]) { if ((SrcVertex.Usage & LC_LIBRARY_VERTEX_UNTEXTURED) == 0) continue; lcVertex& DstVertex = *DstVerts++; DstVertex.Position = lcVector3LDrawToLeoCAD(SrcVertex.Position); DstVertex.Normal = lcPackNormal(lcVector3LDrawToLeoCAD(SrcVertex.Normal)); } } lcVertexTextured* DstTexturedVerts = (lcVertexTextured*)DstVerts; for (int MeshDataIdx = 0; MeshDataIdx < LC_NUM_MESHDATA_TYPES; MeshDataIdx++) { for (const lcLibraryMeshVertex& SrcVertex : MeshData.mVertices[MeshDataIdx]) { if ((SrcVertex.Usage & LC_LIBRARY_VERTEX_TEXTURED) == 0) continue; lcVertexTextured& DstVertex = *DstTexturedVerts++; DstVertex.Position = lcVector3LDrawToLeoCAD(SrcVertex.Position); DstVertex.Normal = lcPackNormal(lcVector3LDrawToLeoCAD(SrcVertex.Normal)); DstVertex.TexCoord = SrcVertex.TexCoord; lcVector3& Position = DstVertex.Position; Min = lcMin(Min, Position); Max = lcMax(Max, Position); } } for (int MeshDataIdx = 0; MeshDataIdx < LC_NUM_MESHDATA_TYPES; MeshDataIdx++) { const lcArray& Vertices = MeshData.mVertices[MeshDataIdx]; for (lcLibraryMeshSection* Section : MeshData.mSections[MeshDataIdx]) { if (Section->mPrimitiveType == LC_MESH_TRIANGLES) { UpdatedBoundingBox = true; for (quint32& Index : Section->mIndices) { lcVector3 Position = lcVector3LDrawToLeoCAD(Vertices[Index].Position); Min = lcMin(Min, Position); Max = lcMax(Max, Position); Index = IndexRemap[MeshDataIdx][Index]; } } else { if (!Section->mTexture) { for (quint32& Index : Section->mIndices) Index = IndexRemap[MeshDataIdx][Index]; } else { for (quint32& Index : Section->mIndices) Index = TexturedIndexRemap[MeshDataIdx][Index]; } } } } } if (!UpdatedBoundingBox) Min = Max = lcVector3(0.0f, 0.0f, 0.0f); Mesh->mBoundingBox.Max = Max; Mesh->mBoundingBox.Min = Min; Mesh->mRadius = lcLength((Max - Min) / 2.0f); NumIndices = 0; for (int LodIdx = 0; LodIdx < LC_NUM_MESH_LODS; LodIdx++) { for (int SectionIdx = 0; SectionIdx < MergeSections[LodIdx].GetSize(); SectionIdx++) { lcMergeSection& MergeSection = MergeSections[LodIdx][SectionIdx]; lcMeshSection& DstSection = Mesh->mLods[LodIdx].Sections[SectionIdx]; lcLibraryMeshSection* SetupSection = MergeSection.Shared ? MergeSection.Shared : MergeSection.Lod; DstSection.ColorIndex = SetupSection->mColor; DstSection.PrimitiveType = SetupSection->mPrimitiveType; DstSection.NumIndices = 0; DstSection.Texture = SetupSection->mTexture; if (DstSection.Texture) DstSection.Texture->AddRef(); if (Mesh->mNumVertices < 0x10000) { DstSection.IndexOffset = NumIndices * 2; quint16* Index = (quint16*)Mesh->mIndexData + NumIndices; if (MergeSection.Shared) { quint16 BaseVertex = DstSection.Texture ? BaseTexturedVertices[LC_MESHDATA_SHARED] : BaseVertices[LC_MESHDATA_SHARED]; lcLibraryMeshSection* SrcSection = MergeSection.Shared; for (int IndexIdx = 0; IndexIdx < SrcSection->mIndices.GetSize(); IndexIdx++) *Index++ = BaseVertex + SrcSection->mIndices[IndexIdx]; DstSection.NumIndices += SrcSection->mIndices.GetSize(); } if (MergeSection.Lod) { quint16 BaseVertex = DstSection.Texture ? BaseTexturedVertices[LodIdx] : BaseVertices[LodIdx]; lcLibraryMeshSection* SrcSection = MergeSection.Lod; for (int IndexIdx = 0; IndexIdx < SrcSection->mIndices.GetSize(); IndexIdx++) *Index++ = BaseVertex + SrcSection->mIndices[IndexIdx]; DstSection.NumIndices += SrcSection->mIndices.GetSize(); } } else { DstSection.IndexOffset = NumIndices * 4; quint32* Index = (quint32*)Mesh->mIndexData + NumIndices; if (MergeSection.Shared) { quint32 BaseVertex = DstSection.Texture ? BaseTexturedVertices[LC_MESHDATA_SHARED] : BaseVertices[LC_MESHDATA_SHARED]; lcLibraryMeshSection* SrcSection = MergeSection.Shared; for (int IndexIdx = 0; IndexIdx < SrcSection->mIndices.GetSize(); IndexIdx++) *Index++ = BaseVertex + SrcSection->mIndices[IndexIdx]; DstSection.NumIndices += SrcSection->mIndices.GetSize(); } if (MergeSection.Lod) { quint32 BaseVertex = DstSection.Texture ? BaseTexturedVertices[LodIdx] : BaseVertices[LodIdx]; lcLibraryMeshSection* SrcSection = MergeSection.Lod; for (int IndexIdx = 0; IndexIdx < SrcSection->mIndices.GetSize(); IndexIdx++) *Index++ = BaseVertex + SrcSection->mIndices[IndexIdx]; DstSection.NumIndices += SrcSection->mIndices.GetSize(); } } if (Info) { if (DstSection.PrimitiveType == LC_MESH_TRIANGLES || DstSection.PrimitiveType == LC_MESH_TEXTURED_TRIANGLES) { if (DstSection.ColorIndex == gDefaultColor) Info->mFlags |= LC_PIECE_HAS_DEFAULT; else { if (lcIsColorTranslucent(DstSection.ColorIndex)) Info->mFlags |= LC_PIECE_HAS_TRANSLUCENT; else Info->mFlags |= LC_PIECE_HAS_SOLID; } } else Info->mFlags |= LC_PIECE_HAS_LINES; if (DstSection.PrimitiveType == LC_MESH_TEXTURED_TRIANGLES || DstSection.PrimitiveType == LC_MESH_TEXTURED_LINES) Info->mFlags |= LC_PIECE_HAS_TEXTURE; } NumIndices += DstSection.NumIndices; } } /* for (int SectionIdx = 0; SectionIdx < MeshData.mSections.GetSize(); SectionIdx++) { lcMeshSection& DstSection = Mesh->mSections[SectionIdx]; lcLibraryMeshSection* SrcSection = MeshData.mSections[SectionIdx]; DstSection.ColorIndex = SrcSection->mColor; DstSection.PrimitiveType = SrcSection->mPrimitiveType; DstSection.NumIndices = SrcSection->mIndices.GetSize(); DstSection.Texture = SrcSection->mTexture; if (DstSection.Texture) DstSection.Texture->AddRef(); if (Mesh->mNumVertices < 0x10000) { DstSection.IndexOffset = NumIndices * 2; quint16* Index = (quint16*)Mesh->mIndexData + NumIndices; for (int IndexIdx = 0; IndexIdx < DstSection.NumIndices; IndexIdx++) *Index++ = SrcSection->mIndices[IndexIdx]; } else { DstSection.IndexOffset = NumIndices * 4; quint32* Index = (quint32*)Mesh->mIndexData + NumIndices; for (int IndexIdx = 0; IndexIdx < DstSection.NumIndices; IndexIdx++) *Index++ = SrcSection->mIndices[IndexIdx]; } if (DstSection.PrimitiveType == LC_MESH_TRIANGLES || DstSection.PrimitiveType == LC_MESH_TEXTURED_TRIANGLES) { if (DstSection.ColorIndex == gDefaultColor) Info->mFlags |= LC_PIECE_HAS_DEFAULT; else { if (lcIsColorTranslucent(DstSection.ColorIndex)) Info->mFlags |= LC_PIECE_HAS_TRANSLUCENT; else Info->mFlags |= LC_PIECE_HAS_SOLID; } } else Info->mFlags |= LC_PIECE_HAS_LINES; NumIndices += DstSection.NumIndices; } */ if (Info) Info->SetMesh(Mesh); return Mesh; } void lcPiecesLibrary::ReleaseBuffers(lcContext* Context) { Context->DestroyVertexBuffer(mVertexBuffer); Context->DestroyIndexBuffer(mIndexBuffer); mBuffersDirty = true; } void lcPiecesLibrary::UpdateBuffers(lcContext* Context) { if (!gSupportsVertexBufferObject || !mBuffersDirty) return; int VertexDataSize = 0; int IndexDataSize = 0; for (const auto& PieceIt : mPieces) { PieceInfo* Info = PieceIt.second; lcMesh* Mesh = Info->IsPlaceholder() ? gPlaceholderMesh : Info->GetMesh(); if (!Mesh) continue; if (Mesh->mVertexDataSize > 16 * 1024 * 1024 || Mesh->mIndexDataSize > 16 * 1024 * 1024) continue; VertexDataSize += Mesh->mVertexDataSize; IndexDataSize += Mesh->mIndexDataSize; } Context->DestroyVertexBuffer(mVertexBuffer); Context->DestroyIndexBuffer(mIndexBuffer); if (!VertexDataSize || !IndexDataSize) return; void* VertexData = malloc(VertexDataSize); void* IndexData = malloc(IndexDataSize); VertexDataSize = 0; IndexDataSize = 0; for (const auto& PieceIt : mPieces) { PieceInfo* Info = PieceIt.second; lcMesh* Mesh = Info->IsPlaceholder() ? gPlaceholderMesh : Info->GetMesh(); if (!Mesh) continue; if (Mesh->mVertexDataSize > 16 * 1024 * 1024 || Mesh->mIndexDataSize > 16 * 1024 * 1024) continue; Mesh->mVertexCacheOffset = VertexDataSize; Mesh->mIndexCacheOffset = IndexDataSize; memcpy((char*)VertexData + VertexDataSize, Mesh->mVertexData, Mesh->mVertexDataSize); memcpy((char*)IndexData + IndexDataSize, Mesh->mIndexData, Mesh->mIndexDataSize); VertexDataSize += Mesh->mVertexDataSize; IndexDataSize += Mesh->mIndexDataSize; } mVertexBuffer = Context->CreateVertexBuffer(VertexDataSize, VertexData); mIndexBuffer = Context->CreateIndexBuffer(IndexDataSize, IndexData); mBuffersDirty = false; free(VertexData); free(IndexData); } void lcPiecesLibrary::UnloadUnusedParts() { QMutexLocker LoadLock(&mLoadMutex); for (const auto& PieceIt : mPieces) { PieceInfo* Info = PieceIt.second; if (Info->GetRefCount() == 0 && Info->mState != LC_PIECEINFO_UNLOADED) ReleasePieceInfo(Info); } } bool lcPiecesLibrary::LoadTexture(lcTexture* Texture) { char FileName[2*LC_MAXPATH]; if (mZipFiles[LC_ZIPFILE_OFFICIAL]) { lcMemFile TextureFile; sprintf(FileName, "parts/textures/%s.png", Texture->mName); if (!mZipFiles[LC_ZIPFILE_UNOFFICIAL] || !mZipFiles[LC_ZIPFILE_UNOFFICIAL]->ExtractFile(FileName, TextureFile)) { sprintf(FileName, "ldraw/parts/textures/%s.png", Texture->mName); if (!mZipFiles[LC_ZIPFILE_OFFICIAL]->ExtractFile(FileName, TextureFile)) return false; } return Texture->Load(TextureFile); } else { sprintf(FileName, "parts/textures/%s.png", Texture->mName); if (Texture->Load(mLibraryDir.absoluteFilePath(QLatin1String(FileName)))) return true; #if defined(Q_OS_MACOS) || defined(Q_OS_LINUX) char Name[LC_MAXPATH]; strcpy(Name, Texture->mName); strlwr(Name); sprintf(FileName, "parts/textures/%s.png", Name); return Texture->Load(mLibraryDir.absoluteFilePath(QLatin1String(FileName))); #else return false; #endif } } void lcPiecesLibrary::ReleaseTexture(lcTexture* Texture) { QMutexLocker LoadLock(&mLoadMutex); if (Texture->Release() == 0 && Texture->IsTemporary()) { mTextures.Remove(Texture); delete Texture; } } void lcPiecesLibrary::QueueTextureUpload(lcTexture* Texture) { QMutexLocker Lock(&mTextureMutex); mTextureUploads.push_back(Texture); } void lcPiecesLibrary::UploadTextures(lcContext* Context) { QMutexLocker Lock(&mTextureMutex); for (lcTexture* Texture : mTextureUploads) Texture->Upload(Context); mTextureUploads.clear(); } bool lcPiecesLibrary::LoadPrimitive(lcLibraryPrimitive* Primitive) { mLoadMutex.lock(); if (Primitive->mState == lcPrimitiveState::NOT_LOADED) Primitive->mState = lcPrimitiveState::LOADING; else { mLoadMutex.unlock(); while (Primitive->mState == lcPrimitiveState::LOADING) lcSleeper::msleep(5); return Primitive->mState == lcPrimitiveState::LOADED; } mLoadMutex.unlock(); lcArray TextureStack; if (mZipFiles[LC_ZIPFILE_OFFICIAL]) { lcLibraryPrimitive* LowPrimitive = nullptr; if (Primitive->mStud && strncmp(Primitive->mName, "8/", 2)) // todo: this is currently the only place that uses mName so use mFileName instead. this should also be done for the loose file libraries. { char Name[LC_PIECE_NAME_LEN]; strcpy(Name, "8/"); strcat(Name, Primitive->mName); LowPrimitive = FindPrimitive(Name); } lcMemFile PrimFile; if (!mZipFiles[Primitive->mZipFileType]->ExtractFile(Primitive->mZipFileIndex, PrimFile)) return false; if (!LowPrimitive) { if (!ReadMeshData(PrimFile, lcMatrix44Identity(), 16, false, TextureStack, Primitive->mMeshData, LC_MESHDATA_SHARED, true, nullptr, false)) return false; } else { if (!ReadMeshData(PrimFile, lcMatrix44Identity(), 16, false, TextureStack, Primitive->mMeshData, LC_MESHDATA_HIGH, true, nullptr, false)) return false; if (!mZipFiles[LowPrimitive->mZipFileType]->ExtractFile(LowPrimitive->mZipFileIndex, PrimFile)) return false; TextureStack.RemoveAll(); if (!ReadMeshData(PrimFile, lcMatrix44Identity(), 16, false, TextureStack, Primitive->mMeshData, LC_MESHDATA_LOW, true, nullptr, false)) return false; } } else { lcDiskFile PrimFile(Primitive->mFileName); if (!PrimFile.Open(QIODevice::ReadOnly) || !ReadMeshData(PrimFile, lcMatrix44Identity(), 16, false, TextureStack, Primitive->mMeshData, LC_MESHDATA_SHARED, true, nullptr, false)) return false; } mLoadMutex.lock(); Primitive->mState = lcPrimitiveState::LOADED; mLoadMutex.unlock(); return true; } bool lcPiecesLibrary::ReadMeshData(lcFile& File, const lcMatrix44& CurrentTransform, quint32 CurrentColorCode, bool InvertWinding, lcArray& TextureStack, lcLibraryMeshData& MeshData, lcMeshDataType MeshDataType, bool Optimize, Project* CurrentProject, bool SearchProjectFolder) { char Buffer[1024]; char* Line; bool InvertNext = false; bool WindingCCW = !InvertWinding; while (File.ReadLine(Buffer, sizeof(Buffer))) { if (mCancelLoading) return false; quint32 ColorCode, ColorCodeHex; bool LastToken = false; int LineType; Line = Buffer; if (sscanf(Line, "%d", &LineType) != 1) continue; if (LineType == 0) { char* Token = Line; while (*Token && *Token <= 32) Token++; Token++; while (*Token && *Token <= 32) Token++; char* End = Token; while (*End && *End > 32) End++; LastToken = (*End == 0); *End = 0; if (!strcmp(Token, "!TEXMAP")) { Token += 8; while (*Token && *Token <= 32) Token++; End = Token; while (*End && *End > 32) End++; *End = 0; bool Start = false; bool Next = false; if (!strcmp(Token, "START")) { Token += 6; Start = true; } else if (!strcmp(Token, "NEXT")) { Token += 5; Next = true; } if (Start || Next) { while (*Token && *Token <= 32) Token++; End = Token; while (*End && *End > 32) End++; *End = 0; auto CleanTextureName = [](char* FileName) { char* Ch; for (Ch = FileName; *Ch; Ch++) { if (*Ch >= 'a' && *Ch <= 'z') *Ch = *Ch + 'A' - 'a'; else if (*Ch == '\\') *Ch = '/'; } if (Ch - FileName > 4) { Ch -= 4; if (!memcmp(Ch, ".PNG", 4)) *Ch = 0; } }; if (!strcmp(Token, "PLANAR")) { Token += 7; char FileName[LC_MAXPATH]; lcVector3 Points[3]; sscanf(Token, "%f %f %f %f %f %f %f %f %f %s", &Points[0].x, &Points[0].y, &Points[0].z, &Points[1].x, &Points[1].y, &Points[1].z, &Points[2].x, &Points[2].y, &Points[2].z, FileName); Points[0] = lcMul31(Points[0], CurrentTransform); Points[1] = lcMul31(Points[1], CurrentTransform); Points[2] = lcMul31(Points[2], CurrentTransform); CleanTextureName(FileName); lcLibraryTextureMap& Map = TextureStack.Add(); Map.Next = false; Map.Fallback = false; Map.Texture = FindTexture(FileName, CurrentProject, SearchProjectFolder); Map.Type = lcLibraryTextureMapType::PLANAR; for (int EdgeIdx = 0; EdgeIdx < 2; EdgeIdx++) { lcVector3 Normal = Points[EdgeIdx + 1] - Points[0]; float Length = lcLength(Normal); Normal /= Length; Map.Params.Planar.Planes[EdgeIdx].x = Normal.x / Length; Map.Params.Planar.Planes[EdgeIdx].y = Normal.y / Length; Map.Params.Planar.Planes[EdgeIdx].z = Normal.z / Length; Map.Params.Planar.Planes[EdgeIdx].w = -lcDot(Normal, Points[0]) / Length; } } else if (!strcmp(Token, "CYLINDRICAL")) { Token += 12; char FileName[LC_MAXPATH]; lcVector3 Points[3]; float Angle; sscanf(Token, "%f %f %f %f %f %f %f %f %f %f %s", &Points[0].x, &Points[0].y, &Points[0].z, &Points[1].x, &Points[1].y, &Points[1].z, &Points[2].x, &Points[2].y, &Points[2].z, &Angle, FileName); Points[0] = lcMul31(Points[0], CurrentTransform); Points[1] = lcMul31(Points[1], CurrentTransform); Points[2] = lcMul31(Points[2], CurrentTransform); CleanTextureName(FileName); lcLibraryTextureMap& Map = TextureStack.Add(); Map.Next = false; Map.Fallback = false; Map.Texture = FindTexture(FileName, CurrentProject, SearchProjectFolder); Map.Type = lcLibraryTextureMapType::CYLINDRICAL; lcVector3 Up = Points[0] - Points[1]; float UpLength = lcLength(Up); lcVector3 Front = lcNormalize(Points[2] - Points[1]); lcVector3 Plane1Normal = Up / UpLength; lcVector3 Plane2Normal = lcNormalize(lcCross(Front, Up)); Map.Params.Cylindrical.FrontPlane = lcVector4(Front, -lcDot(Front, Points[1])); Map.Params.Cylindrical.UpLength = UpLength; Map.Params.Cylindrical.Plane1 = lcVector4(Plane1Normal, -lcDot(Plane1Normal, Points[1])); Map.Params.Cylindrical.Plane2 = lcVector4(Plane2Normal, -lcDot(Plane2Normal, Points[1])); Map.Angle1 = 360.0f / Angle; } else if (!strcmp(Token, "SPHERICAL")) { Token += 10; char FileName[LC_MAXPATH]; lcVector3 Points[3]; float Angle1, Angle2; sscanf(Token, "%f %f %f %f %f %f %f %f %f %f %f %s", &Points[0].x, &Points[0].y, &Points[0].z, &Points[1].x, &Points[1].y, &Points[1].z, &Points[2].x, &Points[2].y, &Points[2].z, &Angle1, &Angle2, FileName); Points[0] = lcMul31(Points[0], CurrentTransform); Points[1] = lcMul31(Points[1], CurrentTransform); Points[2] = lcMul31(Points[2], CurrentTransform); CleanTextureName(FileName); lcLibraryTextureMap& Map = TextureStack.Add(); Map.Next = false; Map.Fallback = false; Map.Texture = FindTexture(FileName, CurrentProject, SearchProjectFolder); Map.Type = lcLibraryTextureMapType::SPHERICAL; lcVector3 Front = lcNormalize(Points[1] - Points[0]); lcVector3 Plane1Normal = lcNormalize(lcCross(Front, Points[2] - Points[0])); lcVector3 Plane2Normal = lcNormalize(lcCross(Plane1Normal, Front)); Map.Params.Spherical.FrontPlane = lcVector4(Front, -lcDot(Front, Points[0])); Map.Params.Spherical.Center = Points[0]; Map.Params.Spherical.Plane1 = lcVector4(Plane1Normal, -lcDot(Plane1Normal, Points[0])); Map.Params.Spherical.Plane2 = lcVector4(Plane2Normal, -lcDot(Plane2Normal, Points[0])); Map.Angle1 = 360.0f / Angle1; Map.Angle2 = 180.0f / Angle2; } } else if (!strcmp(Token, "FALLBACK")) { if (TextureStack.GetSize()) TextureStack[TextureStack.GetSize() - 1].Fallback = true; } else if (!strcmp(Token, "END")) { if (TextureStack.GetSize()) TextureStack.RemoveIndex(TextureStack.GetSize() - 1); } continue; } else if (!strcmp(Token, "BFC")) { while (!LastToken) { Token = End + 1; while (*Token && *Token <= 32) Token++; End = Token; while (*End && *End > 32) End++; LastToken = (*End == 0); *End = 0; if (!strcmp(Token, "INVERTNEXT")) InvertNext = true; else if (!strcmp(Token, "CCW")) WindingCCW = !InvertWinding; else if (!strcmp(Token, "CW")) WindingCCW = InvertWinding; } } else if (!strcmp(Token, "!:")) { Token += 3; Line = Token; if (!TextureStack.GetSize()) continue; } else continue; } if (sscanf(Line, "%d %d", &LineType, &ColorCode) != 2) continue; if (LineType < 1 || LineType > 5) continue; if (ColorCode == 0) { sscanf(Line, "%d %i", &LineType, &ColorCodeHex); if (ColorCode != ColorCodeHex) ColorCode = ColorCodeHex | LC_COLOR_DIRECT; } if (ColorCode == 16) ColorCode = CurrentColorCode; lcLibraryTextureMap* TextureMap = nullptr; if (TextureStack.GetSize()) { TextureMap = &TextureStack[TextureStack.GetSize() - 1]; if (TextureMap->Texture) { if (TextureMap->Fallback) continue; } else { if (!TextureMap->Fallback) continue; TextureMap = nullptr; } } int Dummy; lcVector3 Points[4]; switch (LineType) { case 1: { char OriginalFileName[LC_MAXPATH]; float fm[12]; sscanf(Line, "%d %i %f %f %f %f %f %f %f %f %f %f %f %f %s", &LineType, &Dummy, &fm[0], &fm[1], &fm[2], &fm[3], &fm[4], &fm[5], &fm[6], &fm[7], &fm[8], &fm[9], &fm[10], &fm[11], OriginalFileName); char FileName[LC_MAXPATH]; strcpy(FileName, OriginalFileName); char* Ch; for (Ch = FileName; *Ch; Ch++) { if (*Ch >= 'a' && *Ch <= 'z') *Ch = *Ch + 'A' - 'a'; else if (*Ch == '\\') *Ch = '/'; } lcLibraryPrimitive* Primitive = !TextureMap ? FindPrimitive(FileName) : nullptr; lcMatrix44 IncludeTransform(lcVector4(fm[3], fm[6], fm[9], 0.0f), lcVector4(fm[4], fm[7], fm[10], 0.0f), lcVector4(fm[5], fm[8], fm[11], 0.0f), lcVector4(fm[0], fm[1], fm[2], 1.0f)); IncludeTransform = lcMul(IncludeTransform, CurrentTransform); bool Mirror = IncludeTransform.Determinant() < 0.0f; if (Primitive) { if (Primitive->mState != lcPrimitiveState::LOADED && !LoadPrimitive(Primitive)) break; if (Primitive->mStud) MeshData.AddMeshDataNoDuplicateCheck(Primitive->mMeshData, IncludeTransform, ColorCode, Mirror ^ InvertNext, InvertNext, TextureMap, MeshDataType); else if (!Primitive->mSubFile) { if (Optimize) MeshData.AddMeshData(Primitive->mMeshData, IncludeTransform, ColorCode, Mirror ^ InvertNext, InvertNext, TextureMap, MeshDataType); else MeshData.AddMeshDataNoDuplicateCheck(Primitive->mMeshData, IncludeTransform, ColorCode, Mirror ^ InvertNext, InvertNext, TextureMap, MeshDataType); } else { if (mZipFiles[LC_ZIPFILE_OFFICIAL]) { lcMemFile IncludeFile; if (mZipFiles[Primitive->mZipFileType]->ExtractFile(Primitive->mZipFileIndex, IncludeFile)) ReadMeshData(IncludeFile, IncludeTransform, ColorCode, Mirror ^ InvertNext, TextureStack, MeshData, MeshDataType, Optimize, CurrentProject, SearchProjectFolder); } else { lcDiskFile IncludeFile(Primitive->mFileName); if (IncludeFile.Open(QIODevice::ReadOnly)) ReadMeshData(IncludeFile, IncludeTransform, ColorCode, Mirror ^ InvertNext, TextureStack, MeshData, MeshDataType, Optimize, CurrentProject, SearchProjectFolder); } } } else { const auto PieceIt = mPieces.find(FileName); if (PieceIt != mPieces.end()) { PieceInfo* Info = PieceIt->second; if (mZipFiles[LC_ZIPFILE_OFFICIAL] && Info->mZipFileType != LC_NUM_ZIPFILES) { lcMemFile IncludeFile; if (mZipFiles[Info->mZipFileType]->ExtractFile(Info->mZipFileIndex, IncludeFile)) ReadMeshData(IncludeFile, IncludeTransform, ColorCode, Mirror ^ InvertNext, TextureStack, MeshData, MeshDataType, Optimize, CurrentProject, SearchProjectFolder); } else { lcDiskFile IncludeFile; bool Found = false; if (mHasUnofficial) { sprintf(FileName, "unofficial/parts/%s", Info->mFileName); IncludeFile.SetFileName(mLibraryDir.absoluteFilePath(QLatin1String(FileName))); Found = IncludeFile.Open(QIODevice::ReadOnly); } if (!Found) { sprintf(FileName, "parts/%s", Info->mFileName); IncludeFile.SetFileName(mLibraryDir.absoluteFilePath(QLatin1String(FileName))); Found = IncludeFile.Open(QIODevice::ReadOnly); } if (Found) ReadMeshData(IncludeFile, IncludeTransform, ColorCode, Mirror ^ InvertNext, TextureStack, MeshData, MeshDataType, Optimize, CurrentProject, SearchProjectFolder); } break; } else { bool Found = false; if (mZipFiles[LC_ZIPFILE_OFFICIAL]) { lcMemFile IncludeFile; auto LoadIncludeFile = [&IncludeFile, &FileName, this](const char* Folder, int ZipFileIndex) { char IncludeFileName[LC_MAXPATH]; sprintf(IncludeFileName, Folder, FileName); return mZipFiles[ZipFileIndex]->ExtractFile(IncludeFileName, IncludeFile); }; if (mHasUnofficial) { Found = LoadIncludeFile("parts/%s", LC_ZIPFILE_UNOFFICIAL); if (!Found) Found = LoadIncludeFile("p/%s", LC_ZIPFILE_UNOFFICIAL); } if (!Found) { Found = LoadIncludeFile("ldraw/parts/%s", LC_ZIPFILE_OFFICIAL); if (!Found) Found = LoadIncludeFile("ldraw/p/%s", LC_ZIPFILE_OFFICIAL); } if (Found) ReadMeshData(IncludeFile, IncludeTransform, ColorCode, Mirror ^ InvertNext, TextureStack, MeshData, MeshDataType, Optimize, CurrentProject, SearchProjectFolder); } else { lcDiskFile IncludeFile; auto LoadIncludeFile = [&IncludeFile, &FileName, this](const char* Folder) { char IncludeFileName[LC_MAXPATH]; sprintf(IncludeFileName, Folder, FileName); IncludeFile.SetFileName(mLibraryDir.absoluteFilePath(QLatin1String(IncludeFileName))); if (IncludeFile.Open(QIODevice::ReadOnly)) return true; #if defined(Q_OS_MACOS) || defined(Q_OS_LINUX) // todo: instead of using strlwr, search the parts/primitive lists and get the file name from there strlwr(IncludeFileName); IncludeFile.SetFileName(mLibraryDir.absoluteFilePath(QLatin1String(IncludeFileName))); return IncludeFile.Open(QIODevice::ReadOnly); #else return false; #endif }; if (mHasUnofficial) { Found = LoadIncludeFile("unofficial/parts/%s"); if (!Found) Found = LoadIncludeFile("unofficial/p/%s"); } if (!Found) { Found = LoadIncludeFile("parts/%s"); if (!Found) Found = LoadIncludeFile("p/%s"); } if (Found) ReadMeshData(IncludeFile, IncludeTransform, ColorCode, Mirror ^ InvertNext, TextureStack, MeshData, MeshDataType, Optimize, CurrentProject, SearchProjectFolder); } } } } break; case 2: sscanf(Line, "%d %i %f %f %f %f %f %f", &LineType, &Dummy, &Points[0].x, &Points[0].y, &Points[0].z, &Points[1].x, &Points[1].y, &Points[1].z); Points[0] = lcMul31(Points[0], CurrentTransform); Points[1] = lcMul31(Points[1], CurrentTransform); if (TextureMap) { MeshData.AddTexturedLine(MeshDataType, LineType, ColorCode, WindingCCW, *TextureMap, Points, Optimize); if (TextureMap->Next) TextureStack.RemoveIndex(TextureStack.GetSize() - 1); } else MeshData.AddLine(MeshDataType, LineType, ColorCode, WindingCCW, Points, Optimize); break; case 3: sscanf(Line, "%d %i %f %f %f %f %f %f %f %f %f", &LineType, &Dummy, &Points[0].x, &Points[0].y, &Points[0].z, &Points[1].x, &Points[1].y, &Points[1].z, &Points[2].x, &Points[2].y, &Points[2].z); Points[0] = lcMul31(Points[0], CurrentTransform); Points[1] = lcMul31(Points[1], CurrentTransform); Points[2] = lcMul31(Points[2], CurrentTransform); if (TextureMap) { MeshData.AddTexturedLine(MeshDataType, LineType, ColorCode, WindingCCW, *TextureMap, Points, Optimize); if (TextureMap->Next) TextureStack.RemoveIndex(TextureStack.GetSize() - 1); } else MeshData.AddLine(MeshDataType, LineType, ColorCode, WindingCCW, Points, Optimize); break; case 4: sscanf(Line, "%d %i %f %f %f %f %f %f %f %f %f %f %f %f", &LineType, &Dummy, &Points[0].x, &Points[0].y, &Points[0].z, &Points[1].x, &Points[1].y, &Points[1].z, &Points[2].x, &Points[2].y, &Points[2].z, &Points[3].x, &Points[3].y, &Points[3].z); Points[0] = lcMul31(Points[0], CurrentTransform); Points[1] = lcMul31(Points[1], CurrentTransform); Points[2] = lcMul31(Points[2], CurrentTransform); Points[3] = lcMul31(Points[3], CurrentTransform); if (TextureMap) { MeshData.AddTexturedLine(MeshDataType, LineType, ColorCode, WindingCCW, *TextureMap, Points, Optimize); if (TextureMap->Next) TextureStack.RemoveIndex(TextureStack.GetSize() - 1); } else MeshData.AddLine(MeshDataType, LineType, ColorCode, WindingCCW, Points, Optimize); break; case 5: sscanf(Line, "%d %i %f %f %f %f %f %f %f %f %f %f %f %f", &LineType, &Dummy, &Points[0].x, &Points[0].y, &Points[0].z, &Points[1].x, &Points[1].y, &Points[1].z, &Points[2].x, &Points[2].y, &Points[2].z, &Points[3].x, &Points[3].y, &Points[3].z); Points[0] = lcMul31(Points[0], CurrentTransform); Points[1] = lcMul31(Points[1], CurrentTransform); Points[2] = lcMul31(Points[2], CurrentTransform); Points[3] = lcMul31(Points[3], CurrentTransform); MeshData.AddLine(MeshDataType, LineType, ColorCode, WindingCCW, Points, Optimize); break; } InvertNext = false; } return true; } void lcLibraryMeshData::ResequenceQuad(int* Indices, int a, int b, int c, int d) { Indices[0] = a; Indices[1] = b; Indices[2] = c; Indices[3] = d; } void lcLibraryMeshData::TestQuad(int* QuadIndices, const lcVector3* Vertices) { lcVector3 v01 = Vertices[1] - Vertices[0]; lcVector3 v02 = Vertices[2] - Vertices[0]; lcVector3 v03 = Vertices[3] - Vertices[0]; lcVector3 cp1 = lcCross(v01, v02); lcVector3 cp2 = lcCross(v02, v03); if (lcDot(cp1, cp2) > 0.0f) return; lcVector3 v12 = Vertices[2] - Vertices[1]; lcVector3 v13 = Vertices[3] - Vertices[1]; lcVector3 v23 = Vertices[3] - Vertices[2]; if (lcDot(lcCross(v12, v01), lcCross(v01, v13)) > 0.0f) { if (-lcDot(lcCross(v02, v12), lcCross(v12, v23)) > 0.0f) ResequenceQuad(QuadIndices, 1, 2, 3, 0); else ResequenceQuad(QuadIndices, 0, 3, 1, 2); } else { if (-lcDot(lcCross(v02, v12), lcCross(v12, v23)) > 0.0f) ResequenceQuad(QuadIndices, 0, 1, 3, 2); else ResequenceQuad(QuadIndices, 1, 2, 3, 0); } } lcLibraryMeshSection* lcLibraryMeshData::AddSection(lcMeshDataType MeshDataType, lcMeshPrimitiveType PrimitiveType, quint32 ColorCode, lcTexture* Texture) { lcArray& Sections = mSections[MeshDataType]; lcLibraryMeshSection* Section; for (int SectionIdx = 0; SectionIdx < Sections.GetSize(); SectionIdx++) { Section = Sections[SectionIdx]; if (Section->mColor == ColorCode && Section->mPrimitiveType == PrimitiveType && Section->mTexture == Texture) return Section; } Section = new lcLibraryMeshSection(PrimitiveType, ColorCode, Texture); Sections.Add(Section); return Section; } void lcLibraryMeshData::AddVertices(lcMeshDataType MeshDataType, int VertexCount, int* BaseVertex, lcLibraryMeshVertex** VertexBuffer) { lcArray& Vertices = mVertices[MeshDataType]; int CurrentSize = Vertices.GetSize(); Vertices.SetSize(CurrentSize + VertexCount); *BaseVertex = CurrentSize; *VertexBuffer = &Vertices[CurrentSize]; } const float lcDistanceEpsilon = 0.01f; // Maximum value for 50591.dat const float lcTexCoordEpsilon = 0.01f; inline bool lcCompareVertices(const lcVector3& Position1, const lcVector3& Position2) { return fabsf(Position1.x - Position2.x) < lcDistanceEpsilon && fabsf(Position1.y - Position2.y) < lcDistanceEpsilon && fabsf(Position1.z - Position2.z) < lcDistanceEpsilon; } inline bool lcCompareVertices(const lcVector3& Position1, const lcVector2& TexCoord1, const lcVector3& Position2, const lcVector2& TexCoord2) { return fabsf(Position1.x - Position2.x) < lcDistanceEpsilon && fabsf(Position1.y - Position2.y) < lcDistanceEpsilon && fabsf(Position1.z - Position2.z) < lcDistanceEpsilon && fabsf(TexCoord1.x - TexCoord2.x) < lcTexCoordEpsilon && fabsf(TexCoord1.y - TexCoord2.y) < lcTexCoordEpsilon; } quint32 lcLibraryMeshData::AddVertex(lcMeshDataType MeshDataType, const lcVector3& Position, bool Optimize) { lcArray& VertexArray = mVertices[MeshDataType]; if (Optimize) { for (int VertexIdx = VertexArray.GetSize() - 1; VertexIdx >= 0; VertexIdx--) { lcLibraryMeshVertex& Vertex = VertexArray[VertexIdx]; if (lcCompareVertices(Position, Vertex.Position)) { Vertex.Usage |= LC_LIBRARY_VERTEX_UNTEXTURED; return VertexIdx; } } } lcLibraryMeshVertex& Vertex = VertexArray.Add(); Vertex.Position = Position; Vertex.Normal = lcVector3(0.0f, 0.0f, 0.0f); Vertex.NormalWeight = 0.0f; Vertex.TexCoord = lcVector2(0.0f, 0.0f); Vertex.Usage = LC_LIBRARY_VERTEX_UNTEXTURED; return VertexArray.GetSize() - 1; } quint32 lcLibraryMeshData::AddVertex(lcMeshDataType MeshDataType, const lcVector3& Position, const lcVector3& Normal, bool Optimize) { lcArray& VertexArray = mVertices[MeshDataType]; if (Optimize) { for (int VertexIdx = VertexArray.GetSize() - 1; VertexIdx >= 0; VertexIdx--) { lcLibraryMeshVertex& Vertex = VertexArray[VertexIdx]; if (lcCompareVertices(Position, Vertex.Position)) { if (Vertex.NormalWeight == 0.0f) { Vertex.Normal = Normal; Vertex.NormalWeight = 1.0f; Vertex.Usage |= LC_LIBRARY_VERTEX_UNTEXTURED; return VertexIdx; } else if (lcDot(Normal, Vertex.Normal) > 0.707f) { Vertex.Normal = lcNormalize(Vertex.Normal * Vertex.NormalWeight + Normal); Vertex.NormalWeight += 1.0f; Vertex.Usage |= LC_LIBRARY_VERTEX_UNTEXTURED; return VertexIdx; } } } } lcLibraryMeshVertex& Vertex = VertexArray.Add(); Vertex.Position = Position; Vertex.Normal = Normal; Vertex.NormalWeight = 1.0f; Vertex.TexCoord = lcVector2(0.0f, 0.0f); Vertex.Usage = LC_LIBRARY_VERTEX_UNTEXTURED; return VertexArray.GetSize() - 1; } quint32 lcLibraryMeshData::AddTexturedVertex(lcMeshDataType MeshDataType, const lcVector3& Position, const lcVector2& TexCoord, bool Optimize) { mHasTextures = true; lcArray& VertexArray = mVertices[MeshDataType]; if (Optimize) { for (int VertexIdx = VertexArray.GetSize() - 1; VertexIdx >= 0; VertexIdx--) { lcLibraryMeshVertex& Vertex = VertexArray[VertexIdx]; if (Vertex.Usage & LC_LIBRARY_VERTEX_TEXTURED) { if (lcCompareVertices(Position, TexCoord, Vertex.Position, Vertex.TexCoord)) return VertexIdx; } else { if (lcCompareVertices(Position, Vertex.Position)) { Vertex.TexCoord = TexCoord; Vertex.Usage |= LC_LIBRARY_VERTEX_TEXTURED; return VertexIdx; } } } } lcLibraryMeshVertex& Vertex = VertexArray.Add(); Vertex.Position = Position; Vertex.Normal = lcVector3(0.0f, 0.0f, 0.0f); Vertex.NormalWeight = 0.0f; Vertex.TexCoord = TexCoord; Vertex.Usage = LC_LIBRARY_VERTEX_TEXTURED; return VertexArray.GetSize() - 1; } quint32 lcLibraryMeshData::AddTexturedVertex(lcMeshDataType MeshDataType, const lcVector3& Position, const lcVector3& Normal, const lcVector2& TexCoord, bool Optimize) { mHasTextures = true; lcArray& VertexArray = mVertices[MeshDataType]; if (Optimize) { for (int VertexIdx = VertexArray.GetSize() - 1; VertexIdx >= 0; VertexIdx--) { lcLibraryMeshVertex& Vertex = VertexArray[VertexIdx]; if (Vertex.Usage & LC_LIBRARY_VERTEX_TEXTURED) { if (lcCompareVertices(Position, TexCoord, Vertex.Position, Vertex.TexCoord)) { if (Vertex.NormalWeight == 0.0f) { Vertex.Normal = Normal; Vertex.NormalWeight = 1.0f; return VertexIdx; } else if (lcDot(Normal, Vertex.Normal) > 0.707f) { Vertex.Normal = lcNormalize(Vertex.Normal * Vertex.NormalWeight + Normal); Vertex.NormalWeight += 1.0f; return VertexIdx; } } } else { if (lcCompareVertices(Position, Vertex.Position)) { if (Vertex.NormalWeight == 0.0f) { Vertex.Normal = Normal; Vertex.NormalWeight = 1.0f; Vertex.TexCoord = TexCoord; Vertex.Usage |= LC_LIBRARY_VERTEX_TEXTURED; return VertexIdx; } else if (lcDot(Normal, Vertex.Normal) > 0.707f) { Vertex.Normal = lcNormalize(Vertex.Normal * Vertex.NormalWeight + Normal); Vertex.NormalWeight += 1.0f; Vertex.TexCoord = TexCoord; Vertex.Usage |= LC_LIBRARY_VERTEX_TEXTURED; return VertexIdx; } } } } } lcLibraryMeshVertex& Vertex = VertexArray.Add(); Vertex.Position = Position; Vertex.Normal = Normal; Vertex.NormalWeight = 1.0f; Vertex.TexCoord = TexCoord; Vertex.Usage = LC_LIBRARY_VERTEX_TEXTURED; return VertexArray.GetSize() - 1; } void lcLibraryMeshData::AddIndices(lcMeshDataType MeshDataType, lcMeshPrimitiveType PrimitiveType, quint32 ColorCode, int IndexCount, quint32** IndexBuffer) { lcLibraryMeshSection* Section = AddSection(MeshDataType, PrimitiveType, ColorCode, nullptr); lcArray& Indices = Section->mIndices; int CurrentSize = Indices.GetSize(); Indices.SetSize(CurrentSize + IndexCount); *IndexBuffer = &Indices[CurrentSize]; } void lcLibraryMeshData::AddLine(lcMeshDataType MeshDataType, int LineType, quint32 ColorCode, bool WindingCCW, const lcVector3* Vertices, bool Optimize) { lcMeshPrimitiveType PrimitiveTypes[4] = { LC_MESH_LINES, LC_MESH_TRIANGLES, LC_MESH_TRIANGLES, LC_MESH_CONDITIONAL_LINES }; lcMeshPrimitiveType PrimitiveType = PrimitiveTypes[LineType - 2]; lcLibraryMeshSection* Section = AddSection(MeshDataType, PrimitiveType, ColorCode, nullptr); int QuadIndices[4] = { 0, 1, 2, 3 }; int Indices[4] = { -1, -1, -1, -1 }; if (LineType == 3 || LineType == 4) { if (LineType == 4) TestQuad(QuadIndices, Vertices); lcVector3 Normal = lcNormalize(lcCross(Vertices[1] - Vertices[0], Vertices[2] - Vertices[0])); if (!WindingCCW) Normal = -Normal; for (int IndexIdx = 0; IndexIdx < lcMin(LineType, 4); IndexIdx++) { const lcVector3& Position = Vertices[QuadIndices[IndexIdx]]; Indices[IndexIdx] = AddVertex(MeshDataType, Position, Normal, Optimize); } } else { for (int IndexIdx = 0; IndexIdx < lcMin(LineType, 4); IndexIdx++) { const lcVector3& Position = Vertices[QuadIndices[IndexIdx]]; Indices[IndexIdx] = AddVertex(MeshDataType, Position, Optimize); } } switch (LineType) { case 5: if (Indices[0] != Indices[1] && Indices[0] != Indices[2] && Indices[0] != Indices[3] && Indices[1] != Indices[2] && Indices[1] != Indices[3] && Indices[2] != Indices[3]) { Section->mIndices.Add(Indices[0]); Section->mIndices.Add(Indices[1]); Section->mIndices.Add(Indices[2]); Section->mIndices.Add(Indices[3]); } break; case 4: if (Indices[0] != Indices[2] && Indices[0] != Indices[3] && Indices[2] != Indices[3]) { if (WindingCCW) { Section->mIndices.Add(Indices[2]); Section->mIndices.Add(Indices[3]); Section->mIndices.Add(Indices[0]); } else { Section->mIndices.Add(Indices[0]); Section->mIndices.Add(Indices[3]); Section->mIndices.Add(Indices[2]); } } Q_FALLTHROUGH(); case 3: if (Indices[0] != Indices[1] && Indices[0] != Indices[2] && Indices[1] != Indices[2]) { if (WindingCCW) { Section->mIndices.Add(Indices[0]); Section->mIndices.Add(Indices[1]); Section->mIndices.Add(Indices[2]); } else { Section->mIndices.Add(Indices[2]); Section->mIndices.Add(Indices[1]); Section->mIndices.Add(Indices[0]); } } break; case 2: if (Indices[0] != Indices[1]) { Section->mIndices.Add(Indices[0]); Section->mIndices.Add(Indices[1]); } break; } } void lcLibraryMeshData::AddTexturedLine(lcMeshDataType MeshDataType, int LineType, quint32 ColorCode, bool WindingCCW, const lcLibraryTextureMap& TextureMap, const lcVector3* Vertices, bool Optimize) { lcMeshPrimitiveType PrimitiveType = (LineType == 2) ? LC_MESH_TEXTURED_LINES : LC_MESH_TEXTURED_TRIANGLES; lcLibraryMeshSection* Section = AddSection(MeshDataType, PrimitiveType, ColorCode, TextureMap.Texture); int QuadIndices[4] = { 0, 1, 2, 3 }; int Indices[4] = { -1, -1, -1, -1 }; if (LineType == 3 || LineType == 4) { if (LineType == 4) TestQuad(QuadIndices, Vertices); lcVector3 Normal = lcNormalize(lcCross(Vertices[1] - Vertices[0], Vertices[2] - Vertices[0])); if (!WindingCCW) Normal = -Normal; lcVector2 TexCoords[4]; for (int IndexIdx = 0; IndexIdx < lcMin(LineType, 4); IndexIdx++) { const lcVector3& Position = Vertices[QuadIndices[IndexIdx]]; TexCoords[QuadIndices[IndexIdx]] = lcCalculateTexCoord(Position, &TextureMap); } if (TextureMap.Type == lcLibraryTextureMapType::CYLINDRICAL || TextureMap.Type == lcLibraryTextureMapType::SPHERICAL) { auto CheckTexCoordsWrap = [&TexCoords, &Vertices, &TextureMap](int Index1, int Index2, int Index3) { float u12 = fabsf(TexCoords[Index1].x - TexCoords[Index2].x); float u13 = fabsf(TexCoords[Index1].x - TexCoords[Index3].x); float u23 = fabsf(TexCoords[Index2].x - TexCoords[Index3].x); if (u12 < 0.5f && u13 < 0.5f && u23 < 0.5f) return; const lcVector4& Plane2 = (TextureMap.Type == lcLibraryTextureMapType::CYLINDRICAL) ? TextureMap.Params.Cylindrical.Plane2 : TextureMap.Params.Spherical.Plane2; float Dot1 = fabsf(lcDot(Plane2, lcVector4(Vertices[Index1], 1.0f))); float Dot2 = fabsf(lcDot(Plane2, lcVector4(Vertices[Index2], 1.0f))); float Dot3 = fabsf(lcDot(Plane2, lcVector4(Vertices[Index3], 1.0f))); if (Dot1 > Dot2) { if (Dot1 > Dot3) { if (u12 > 0.5f) TexCoords[Index2].x += TexCoords[Index2].x < 0.5f ? 1.0f : -1.0f; if (u13 > 0.5f) TexCoords[Index3].x += TexCoords[Index3].x < 0.5f ? 1.0f : -1.0f; } else { if (u13 > 0.5f) TexCoords[Index1].x += TexCoords[Index1].x < 0.5f ? 1.0f : -1.0f; if (u23 > 0.5f) TexCoords[Index2].x += TexCoords[Index2].x < 0.5f ? 1.0f : -1.0f; } } else { if (Dot2 > Dot3) { if (u12 > 0.5f) TexCoords[Index1].x += TexCoords[Index1].x < 0.5f ? 1.0f : -1.0f; if (u23 > 0.5f) TexCoords[Index3].x += TexCoords[Index3].x < 0.5f ? 1.0f : -1.0f; } else { if (u13 > 0.5f) TexCoords[Index1].x += TexCoords[Index1].x < 0.5f ? 1.0f : -1.0f; if (u23 > 0.5f) TexCoords[Index2].x += TexCoords[Index2].x < 0.5f ? 1.0f : -1.0f; } } }; CheckTexCoordsWrap(QuadIndices[0], QuadIndices[1], QuadIndices[2]); if (LineType == 4) CheckTexCoordsWrap(QuadIndices[2], QuadIndices[3], QuadIndices[0]); } if (TextureMap.Type == lcLibraryTextureMapType::SPHERICAL) { auto CheckTexCoordsPole = [&TexCoords, &Vertices, &TextureMap](int Index1, int Index2, int Index3) { const lcVector4& FrontPlane = TextureMap.Params.Spherical.FrontPlane; const lcVector4& Plane2 = TextureMap.Params.Spherical.Plane2; int PoleIndex; int EdgeIndex1, EdgeIndex2; if (fabsf(lcDot(lcVector4(Vertices[Index1], 1.0f), FrontPlane)) < 0.01f && fabsf(lcDot(lcVector4(Vertices[Index1], 1.0f), Plane2)) < 0.01f) { PoleIndex = Index1; EdgeIndex1 = Index2; EdgeIndex2 = Index3; } else if (fabsf(lcDot(lcVector4(Vertices[Index2], 1.0f), FrontPlane)) < 0.01f && fabsf(lcDot(lcVector4(Vertices[Index2], 1.0f), Plane2)) < 0.01f) { PoleIndex = Index2; EdgeIndex1 = Index1; EdgeIndex2 = Index3; } else if (fabsf(lcDot(lcVector4(Vertices[Index3], 1.0f), FrontPlane)) < 0.01f && fabsf(lcDot(lcVector4(Vertices[Index3], 1.0f), Plane2)) < 0.01f) { PoleIndex = Index3; EdgeIndex1 = Index1; EdgeIndex2 = Index2; } else return; lcVector3 OppositeEdge = Vertices[EdgeIndex2] - Vertices[EdgeIndex1]; lcVector3 SideEdge = Vertices[PoleIndex] - Vertices[EdgeIndex1]; float OppositeLength = lcLength(OppositeEdge); float Projection = lcDot(OppositeEdge, SideEdge) / (OppositeLength * OppositeLength); TexCoords[PoleIndex].x = TexCoords[EdgeIndex1].x + (TexCoords[EdgeIndex2].x - TexCoords[EdgeIndex1].x) * Projection; }; CheckTexCoordsPole(QuadIndices[0], QuadIndices[1], QuadIndices[2]); if (LineType == 4) CheckTexCoordsPole(QuadIndices[2], QuadIndices[3], QuadIndices[0]); } for (int IndexIdx = 0; IndexIdx < lcMin(LineType, 4); IndexIdx++) { const lcVector3& Position = Vertices[QuadIndices[IndexIdx]]; Indices[IndexIdx] = AddTexturedVertex(MeshDataType, Position, Normal, TexCoords[QuadIndices[IndexIdx]], Optimize); } } else { for (int IndexIdx = 0; IndexIdx < lcMin(LineType, 4); IndexIdx++) { const lcVector3& Position = Vertices[QuadIndices[IndexIdx]]; lcVector2 TexCoord = lcCalculateTexCoord(Position, &TextureMap); Indices[IndexIdx] = AddTexturedVertex(MeshDataType, Position, TexCoord, Optimize); } } switch (LineType) { case 4: if (Indices[0] != Indices[2] && Indices[0] != Indices[3] && Indices[2] != Indices[3]) { if (WindingCCW) { Section->mIndices.Add(Indices[2]); Section->mIndices.Add(Indices[3]); Section->mIndices.Add(Indices[0]); } else { Section->mIndices.Add(Indices[0]); Section->mIndices.Add(Indices[3]); Section->mIndices.Add(Indices[2]); } } Q_FALLTHROUGH(); case 3: if (Indices[0] != Indices[1] && Indices[0] != Indices[2] && Indices[1] != Indices[2]) { if (WindingCCW) { Section->mIndices.Add(Indices[0]); Section->mIndices.Add(Indices[1]); Section->mIndices.Add(Indices[2]); } else { Section->mIndices.Add(Indices[2]); Section->mIndices.Add(Indices[1]); Section->mIndices.Add(Indices[0]); } } break; case 2: if (Indices[0] != Indices[1]) { Section->mIndices.Add(Indices[0]); Section->mIndices.Add(Indices[1]); } break; } } void lcLibraryMeshData::AddMeshData(const lcLibraryMeshData& Data, const lcMatrix44& Transform, quint32 CurrentColorCode, bool InvertWinding, bool InvertNormals, lcLibraryTextureMap* TextureMap, lcMeshDataType OverrideDestIndex) { for (int MeshDataIdx = 0; MeshDataIdx < LC_NUM_MESHDATA_TYPES; MeshDataIdx++) { int DestIndex = OverrideDestIndex == LC_MESHDATA_SHARED ? MeshDataIdx : OverrideDestIndex; const lcArray& DataVertices = Data.mVertices[MeshDataIdx]; lcArray& Vertices = mVertices[DestIndex]; int VertexCount = DataVertices.GetSize(); lcArray IndexRemap(VertexCount); if (!TextureMap) { Vertices.AllocGrow(VertexCount); for (int SrcVertexIdx = 0; SrcVertexIdx < VertexCount; SrcVertexIdx++) { lcVector3 Position = lcMul31(DataVertices[SrcVertexIdx].Position, Transform); int Index; if ((DataVertices[SrcVertexIdx].Usage & LC_LIBRARY_VERTEX_TEXTURED) == 0) { if (DataVertices[SrcVertexIdx].NormalWeight == 0.0f) Index = AddVertex((lcMeshDataType)DestIndex, Position, true); else { lcVector3 Normal = lcNormalize(lcMul30(DataVertices[SrcVertexIdx].Normal, Transform)); if (InvertNormals) Normal = -Normal; Index = AddVertex((lcMeshDataType)DestIndex, Position, Normal, true); } } else { mHasTextures = true; if (DataVertices[SrcVertexIdx].NormalWeight == 0.0f) Index = AddTexturedVertex((lcMeshDataType)DestIndex, Position, DataVertices[SrcVertexIdx].TexCoord, true); else { lcVector3 Normal = lcNormalize(lcMul30(DataVertices[SrcVertexIdx].Normal, Transform)); if (InvertNormals) Normal = -Normal; Index = AddTexturedVertex((lcMeshDataType)DestIndex, Position, Normal, DataVertices[SrcVertexIdx].TexCoord, true); } Vertices[Index].Usage = DataVertices[SrcVertexIdx].Usage; } IndexRemap.Add(Index); } } else { mHasTextures = true; for (int SrcVertexIdx = 0; SrcVertexIdx < VertexCount; SrcVertexIdx++) { const lcLibraryMeshVertex& SrcVertex = DataVertices[SrcVertexIdx]; lcVector3 Position = lcMul31(SrcVertex.Position, Transform); lcVector2 TexCoord = lcCalculateTexCoord(Position, TextureMap); int Index; if (DataVertices[SrcVertexIdx].NormalWeight == 0.0f) Index = AddTexturedVertex((lcMeshDataType)DestIndex, Position, TexCoord, true); else { lcVector3 Normal = lcNormalize(lcMul30(DataVertices[SrcVertexIdx].Normal, Transform)); if (InvertNormals) Normal = -Normal; Index = AddTexturedVertex((lcMeshDataType)DestIndex, Position, Normal, TexCoord, true); } IndexRemap.Add(Index); } } const lcArray& DataSections = Data.mSections[MeshDataIdx]; lcArray& Sections = mSections[DestIndex]; for (int SrcSectionIdx = 0; SrcSectionIdx < DataSections.GetSize(); SrcSectionIdx++) { lcLibraryMeshSection* SrcSection = DataSections[SrcSectionIdx]; lcLibraryMeshSection* DstSection = nullptr; quint32 ColorCode = SrcSection->mColor == 16 ? CurrentColorCode : SrcSection->mColor; lcTexture* Texture; lcMeshPrimitiveType PrimitiveType = SrcSection->mPrimitiveType; if (SrcSection->mTexture) Texture = SrcSection->mTexture; else if (TextureMap) { Texture = TextureMap->Texture; if (SrcSection->mPrimitiveType == LC_MESH_TRIANGLES) PrimitiveType = LC_MESH_TEXTURED_TRIANGLES; else if (SrcSection->mPrimitiveType == LC_MESH_LINES) PrimitiveType = LC_MESH_TEXTURED_LINES; } else Texture = nullptr; for (int DstSectionIdx = 0; DstSectionIdx < Sections.GetSize(); DstSectionIdx++) { lcLibraryMeshSection* Section = Sections[DstSectionIdx]; if (Section->mColor == ColorCode && Section->mPrimitiveType == PrimitiveType && Section->mTexture == Texture) { DstSection = Section; break; } } if (!DstSection) { DstSection = new lcLibraryMeshSection(PrimitiveType, ColorCode, Texture); Sections.Add(DstSection); } DstSection->mIndices.AllocGrow(SrcSection->mIndices.GetSize()); if (!InvertWinding || (PrimitiveType != LC_MESH_TRIANGLES && PrimitiveType != LC_MESH_TEXTURED_TRIANGLES)) { for (int IndexIdx = 0; IndexIdx < SrcSection->mIndices.GetSize(); IndexIdx++) DstSection->mIndices.Add(IndexRemap[SrcSection->mIndices[IndexIdx]]); } else { for (int IndexIdx = 0; IndexIdx < SrcSection->mIndices.GetSize(); IndexIdx += 3) { DstSection->mIndices.Add(IndexRemap[SrcSection->mIndices[IndexIdx + 2]]); DstSection->mIndices.Add(IndexRemap[SrcSection->mIndices[IndexIdx + 1]]); DstSection->mIndices.Add(IndexRemap[SrcSection->mIndices[IndexIdx + 0]]); } } } } } void lcLibraryMeshData::AddMeshDataNoDuplicateCheck(const lcLibraryMeshData& Data, const lcMatrix44& Transform, quint32 CurrentColorCode, bool InvertWinding, bool InvertNormals, lcLibraryTextureMap* TextureMap, lcMeshDataType OverrideDestIndex) { for (int MeshDataIdx = 0; MeshDataIdx < LC_NUM_MESHDATA_TYPES; MeshDataIdx++) { int DestIndex = OverrideDestIndex == LC_MESHDATA_SHARED ? MeshDataIdx : OverrideDestIndex; const lcArray& DataVertices = Data.mVertices[MeshDataIdx]; lcArray& Vertices = mVertices[DestIndex]; quint32 BaseIndex; if (!TextureMap) { BaseIndex = Vertices.GetSize(); Vertices.SetGrow(lcMin(Vertices.GetSize(), 8 * 1024 * 1024)); Vertices.AllocGrow(DataVertices.GetSize()); for (int SrcVertexIdx = 0; SrcVertexIdx < DataVertices.GetSize(); SrcVertexIdx++) { const lcLibraryMeshVertex& SrcVertex = DataVertices[SrcVertexIdx]; lcLibraryMeshVertex& DstVertex = Vertices.Add(); DstVertex.Position = lcMul31(SrcVertex.Position, Transform); DstVertex.Normal = lcNormalize(lcMul30(SrcVertex.Normal, Transform)); if (InvertNormals) DstVertex.Normal = -DstVertex.Normal; DstVertex.NormalWeight = SrcVertex.NormalWeight; DstVertex.TexCoord = SrcVertex.TexCoord; DstVertex.Usage = SrcVertex.Usage; } } else { mHasTextures = true; BaseIndex = Vertices.GetSize(); Vertices.AllocGrow(DataVertices.GetSize()); for (int SrcVertexIdx = 0; SrcVertexIdx < DataVertices.GetSize(); SrcVertexIdx++) { const lcLibraryMeshVertex& SrcVertex = DataVertices[SrcVertexIdx]; lcLibraryMeshVertex& DstVertex = Vertices.Add(); lcVector3 Position = lcMul31(SrcVertex.Position, Transform); lcVector2 TexCoord = lcCalculateTexCoord(Position, TextureMap); DstVertex.Position = Position; DstVertex.Normal = lcNormalize(lcMul30(SrcVertex.Normal, Transform)); if (InvertNormals) DstVertex.Normal = -DstVertex.Normal; DstVertex.NormalWeight = SrcVertex.NormalWeight; DstVertex.TexCoord = TexCoord; DstVertex.Usage = LC_LIBRARY_VERTEX_TEXTURED; } } const lcArray& DataSections = Data.mSections[MeshDataIdx]; lcArray& Sections = mSections[DestIndex]; for (int SrcSectionIdx = 0; SrcSectionIdx < DataSections.GetSize(); SrcSectionIdx++) { lcLibraryMeshSection* SrcSection = DataSections[SrcSectionIdx]; lcLibraryMeshSection* DstSection = nullptr; quint32 ColorCode = SrcSection->mColor == 16 ? CurrentColorCode : SrcSection->mColor; lcTexture* Texture; if (SrcSection->mTexture) Texture = SrcSection->mTexture; else if (TextureMap) Texture = TextureMap->Texture; else Texture = nullptr; for (int DstSectionIdx = 0; DstSectionIdx < Sections.GetSize(); DstSectionIdx++) { lcLibraryMeshSection* Section = Sections[DstSectionIdx]; if (Section->mColor == ColorCode && Section->mPrimitiveType == SrcSection->mPrimitiveType && Section->mTexture == Texture) { DstSection = Section; break; } } if (!DstSection) { DstSection = new lcLibraryMeshSection(SrcSection->mPrimitiveType, ColorCode, Texture); Sections.Add(DstSection); } DstSection->mIndices.SetGrow(lcMin(DstSection->mIndices.GetSize(), 8 * 1024 * 1024)); DstSection->mIndices.AllocGrow(SrcSection->mIndices.GetSize()); if (!InvertWinding || (SrcSection->mPrimitiveType != LC_MESH_TRIANGLES && SrcSection->mPrimitiveType != LC_MESH_TEXTURED_TRIANGLES)) { for (int IndexIdx = 0; IndexIdx < SrcSection->mIndices.GetSize(); IndexIdx++) DstSection->mIndices.Add(BaseIndex + SrcSection->mIndices[IndexIdx]); } else { for (int IndexIdx = 0; IndexIdx < SrcSection->mIndices.GetSize(); IndexIdx += 3) { DstSection->mIndices.Add(BaseIndex + SrcSection->mIndices[IndexIdx + 2]); DstSection->mIndices.Add(BaseIndex + SrcSection->mIndices[IndexIdx + 1]); DstSection->mIndices.Add(BaseIndex + SrcSection->mIndices[IndexIdx + 0]); } } } } } bool lcPiecesLibrary::PieceInCategory(PieceInfo* Info, const char* CategoryKeywords) const { if (Info->IsTemporary()) return false; const char* PieceName; if (Info->m_strDescription[0] == '~' || Info->m_strDescription[0] == '_') PieceName = Info->m_strDescription + 1; else PieceName = Info->m_strDescription; return lcMatchCategory(PieceName, CategoryKeywords); } void lcPiecesLibrary::GetCategoryEntries(int CategoryIndex, bool GroupPieces, lcArray& SinglePieces, lcArray& GroupedPieces) { if (CategoryIndex >= 0 && CategoryIndex < gCategories.GetSize()) GetCategoryEntries(gCategories[CategoryIndex].Keywords.constData(), GroupPieces, SinglePieces, GroupedPieces); } void lcPiecesLibrary::GetCategoryEntries(const char* CategoryKeywords, bool GroupPieces, lcArray& SinglePieces, lcArray& GroupedPieces) { SinglePieces.RemoveAll(); GroupedPieces.RemoveAll(); for (const auto& PieceIt : mPieces) { PieceInfo* Info = PieceIt.second; if (!PieceInCategory(Info, CategoryKeywords)) continue; if (!GroupPieces) { SinglePieces.Add(Info); continue; } // Check if it's a patterned piece. if (Info->IsPatterned()) { PieceInfo* Parent; // Find the parent of this patterned piece. char ParentName[LC_PIECE_NAME_LEN]; strcpy(ParentName, Info->mFileName); *strchr(ParentName, 'P') = '\0'; strcat(ParentName, ".dat"); Parent = FindPiece(ParentName, nullptr, false, false); if (Parent) { // Check if the parent was added as a single piece. int Index = SinglePieces.FindIndex(Parent); if (Index != -1) SinglePieces.RemoveIndex(Index); Index = GroupedPieces.FindIndex(Parent); if (Index == -1) GroupedPieces.Add(Parent); } else { // Patterned pieces should have a parent but in case they don't just add them anyway. SinglePieces.Add(Info); } } else { // Check if this piece has already been added to this category by one of its children. int Index = GroupedPieces.FindIndex(Info); if (Index == -1) SinglePieces.Add(Info); } } } void lcPiecesLibrary::GetPatternedPieces(PieceInfo* Parent, lcArray& Pieces) const { char Name[LC_PIECE_NAME_LEN]; strcpy(Name, Parent->mFileName); char* Ext = strchr(Name, '.'); if (Ext) *Ext = 0; strcat(Name, "P"); strupr(Name); Pieces.RemoveAll(); for (const auto& PieceIt : mPieces) if (strncmp(Name, PieceIt.first.c_str(), strlen(Name)) == 0) Pieces.Add(PieceIt.second); // Sometimes pieces with A and B versions don't follow the same convention (for example, 3040Pxx instead of 3040BPxx). if (Pieces.GetSize() == 0) { strcpy(Name, Parent->mFileName); Ext = strchr(Name, '.'); if (Ext) *Ext = 0; size_t Len = strlen(Name); if (Name[Len-1] < '0' || Name[Len-1] > '9') Name[Len-1] = 'P'; for (const auto& PieceIt : mPieces) if (strncmp(Name, PieceIt.first.c_str(), strlen(Name)) == 0) Pieces.Add(PieceIt.second); } } void lcPiecesLibrary::GetParts(lcArray& Parts) { Parts.SetSize(0); Parts.AllocGrow(mPieces.size()); for (const auto& PartIt : mPieces) Parts.Add(PartIt.second); } bool lcPiecesLibrary::LoadBuiltinPieces() { QResource Resource(":/resources/library.zip"); if (!Resource.isValid()) return false; lcMemFile* File = new lcMemFile(); File->WriteBuffer(Resource.data(), Resource.size()); if (!OpenArchive(File, "builtin", LC_ZIPFILE_OFFICIAL)) { delete File; return false; } lcMemFile PieceFile; for (const auto& PieceIt : mPieces) { PieceInfo* Info = PieceIt.second; mZipFiles[Info->mZipFileType]->ExtractFile(Info->mZipFileIndex, PieceFile, 256); PieceFile.Seek(0, SEEK_END); PieceFile.WriteU8(0); char* Src = (char*)PieceFile.mBuffer + 2; char* Dst = Info->m_strDescription; for (;;) { if (*Src != '\r' && *Src != '\n' && *Src && Dst - Info->m_strDescription < (int)sizeof(Info->m_strDescription) - 1) { *Dst++ = *Src++; continue; } *Dst = 0; break; } } lcLoadDefaultColors(); lcLoadDefaultCategories(true); lcSynthInit(); return true; }