├── .gitignore ├── CMakeLists.txt ├── README.md ├── cmake ├── DragNDrop │ ├── DS_Store │ └── background.png └── MacOSXBundleInfo.plist.in ├── doc └── screenshot_macos.png ├── res ├── HLMV_128x128.png ├── HLMV_256x256.png ├── Icon.icns └── makeicns.sh └── src ├── c ├── hlmv_application.cpp ├── hlmv_application.h ├── hlmv_body.cpp ├── hlmv_cso_model.cpp ├── hlmv_display.cpp ├── hlmv_event.cpp ├── hlmv_file.cpp ├── hlmv_origin.cpp ├── hlmv_seq.cpp ├── hlmv_tab.cpp ├── hlmv_texture.cpp ├── qt_image.cpp └── qt_image.h ├── gl.h ├── m ├── IceKey.cpp ├── IceKey.hpp ├── StudioModel.h ├── TGAlib.cpp ├── TGAlib.h ├── ViewerSettings.cpp ├── ViewerSettings.h ├── bmpread.cpp ├── bmpread.h ├── gl_draw.cpp ├── gl_draw.h ├── mathlib.c ├── mathlib.h ├── mod_decryptor.cpp ├── mod_decryptor.h ├── studio.h ├── studio_render.cpp └── studio_utils.cpp ├── main.cpp └── v ├── hlmv.cpp ├── hlmv.h ├── hlmv.qrc ├── hlmv.ui ├── myopenglwidget.cpp ├── myopenglwidget.h └── platform ├── macos ├── MacWindow.mm └── Touchbar.mm └── win32 └── AeroWindow.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries 2 | *.o 3 | *.so 4 | *.a 5 | *.framework 6 | 7 | # Other 8 | *.save 9 | # Qt Creator for some reason creates *.user.$version files, so exclude it too 10 | *.user* 11 | *~ 12 | 13 | ### Xcode ### 14 | build/ 15 | *.pbxuser 16 | !default.pbxuser 17 | *.mode1v3 18 | !default.mode1v3 19 | *.mode2v3 20 | !default.mode2v3 21 | *.perspectivev3 22 | !default.perspectivev3 23 | xcuserdata 24 | *.xccheckout 25 | *.moved-aside 26 | DerivedData 27 | *.xcuserstate 28 | 29 | 30 | ### OSX ### 31 | .DS_Store 32 | .AppleDouble 33 | .LSOverride 34 | 35 | 36 | # Thumbnails 37 | ._* 38 | 39 | # Files that might appear on external disk 40 | .Spotlight-V100 41 | .Trashes 42 | 43 | # Directories potentially created on remote AFP share 44 | .AppleDB 45 | .AppleDesktop 46 | Network Trash Folder 47 | Temporary Items 48 | .apdisk 49 | 50 | 51 | ### CMake ### 52 | CMakeCache.txt 53 | CMakeFiles 54 | Makefile 55 | cmake_install.cmake 56 | install_manifest.txt 57 | # makedepend 58 | Makefile.dep 59 | *.bak 60 | ALL_BUILD.* 61 | INSTALL.* 62 | ZERO_CHECK.* 63 | 64 | # Visual Studio 65 | *.obj 66 | *.exp 67 | *.suo 68 | *.sdf 69 | Debug/ 70 | Release/ 71 | ipch/ 72 | *.opensdf 73 | 74 | ## Ignore Visual Studio temporary files, build results, and 75 | ## files generated by popular Visual Studio add-ons. 76 | 77 | # User-specific files 78 | *.suo 79 | *.user 80 | *.userosscache 81 | *.sln.docstates 82 | 83 | # User-specific files (MonoDevelop/Xamarin Studio) 84 | *.userprefs 85 | 86 | # Build results 87 | [Dd]ebug/ 88 | [Dd]ebugPublic/ 89 | [Rr]elease/ 90 | [Rr]eleases/ 91 | build/ 92 | bld/ 93 | [Bb]in/ 94 | [Oo]bj/ 95 | 96 | # Visual Studio 2015 cache/options directory 97 | .vs/ 98 | 99 | # Visual Studio Code Preferences 100 | .vscode/ 101 | 102 | # Uncomment if you have tasks that create the project's static files in wwwroot 103 | #wwwroot/ 104 | 105 | # MSTest test Results 106 | [Tt]est[Rr]esult*/ 107 | [Bb]uild[Ll]og.* 108 | 109 | # NUNIT 110 | *.VisualState.xml 111 | TestResult.xml 112 | 113 | # Build Results of an ATL Project 114 | [Dd]ebugPS/ 115 | [Rr]eleasePS/ 116 | dlldata.c 117 | 118 | # DNX 119 | project.lock.json 120 | artifacts/ 121 | 122 | *_i.c 123 | *_p.c 124 | *_i.h 125 | *.ilk 126 | *.meta 127 | *.obj 128 | *.pch 129 | *.pdb 130 | *.pgc 131 | *.pgd 132 | *.rsp 133 | *.sbr 134 | *.tlb 135 | *.tli 136 | *.tlh 137 | *.tmp 138 | *.tmp_proj 139 | *.log 140 | *.vspscc 141 | *.vssscc 142 | .builds 143 | *.pidb 144 | *.svclog 145 | *.scc 146 | 147 | # Chutzpah Test files 148 | _Chutzpah* 149 | 150 | # Visual C++ cache files 151 | ipch/ 152 | *.aps 153 | *.ncb 154 | *.opensdf 155 | *.sdf 156 | *.cachefile 157 | 158 | # Visual Studio profiler 159 | *.psess 160 | *.vsp 161 | *.vspx 162 | *.sap 163 | 164 | # TFS 2012 Local Workspace 165 | $tf/ 166 | 167 | # Guidance Automation Toolkit 168 | *.gpState 169 | 170 | # ReSharper is a .NET coding add-in 171 | _ReSharper*/ 172 | *.[Rr]e[Ss]harper 173 | *.DotSettings.user 174 | 175 | # JustCode is a .NET coding add-in 176 | .JustCode 177 | 178 | # TeamCity is a build add-in 179 | _TeamCity* 180 | 181 | # DotCover is a Code Coverage Tool 182 | *.dotCover 183 | 184 | # NCrunch 185 | _NCrunch_* 186 | .*crunch*.local.xml 187 | nCrunchTemp_* 188 | 189 | # MightyMoose 190 | *.mm.* 191 | AutoTest.Net/ 192 | 193 | # Web workbench (sass) 194 | .sass-cache/ 195 | 196 | # Installshield output folder 197 | [Ee]xpress/ 198 | 199 | # DocProject is a documentation generator add-in 200 | DocProject/buildhelp/ 201 | DocProject/Help/*.HxT 202 | DocProject/Help/*.HxC 203 | DocProject/Help/*.hhc 204 | DocProject/Help/*.hhk 205 | DocProject/Help/*.hhp 206 | DocProject/Help/Html2 207 | DocProject/Help/html 208 | 209 | # Click-Once directory 210 | publish/ 211 | 212 | # Publish Web Output 213 | *.[Pp]ublish.xml 214 | *.azurePubxml 215 | *.pubxml 216 | *.publishproj 217 | 218 | # NuGet Packages 219 | *.nupkg 220 | # The packages folder can be ignored because of Package Restore 221 | **/packages/* 222 | # except build/, which is used as an MSBuild target. 223 | !**/packages/build/ 224 | # Uncomment if necessary however generally it will be regenerated when needed 225 | #!**/packages/repositories.config 226 | 227 | # Windows Azure Build Output 228 | csx/ 229 | *.build.csdef 230 | 231 | # Windows Store app package directory 232 | AppPackages/ 233 | 234 | # Visual Studio cache files 235 | # files ending in .cache can be ignored 236 | *.[Cc]ache 237 | # but keep track of directories ending in .cache 238 | !*.[Cc]ache/ 239 | 240 | # Others 241 | ClientBin/ 242 | [Ss]tyle[Cc]op.* 243 | ~$* 244 | *~ 245 | *.dbmdl 246 | *.dbproj.schemaview 247 | *.pfx 248 | *.publishsettings 249 | node_modules/ 250 | orleans.codegen.cs 251 | 252 | # RIA/Silverlight projects 253 | Generated_Code/ 254 | 255 | # Backup & report files from converting an old project file 256 | # to a newer Visual Studio version. Backup files are not needed, 257 | # because we have git ;-) 258 | _UpgradeReport_Files/ 259 | Backup*/ 260 | UpgradeLog*.XML 261 | UpgradeLog*.htm 262 | 263 | # SQL Server files 264 | *.mdf 265 | *.ldf 266 | 267 | # Business Intelligence projects 268 | *.rdl.data 269 | *.bim.layout 270 | *.bim_*.settings 271 | 272 | # Microsoft Fakes 273 | FakesAssemblies/ 274 | 275 | # Node.js Tools for Visual Studio 276 | .ntvs_analysis.dat 277 | 278 | # Visual Studio 6 build log 279 | *.plg 280 | 281 | # Visual Studio 6 workspace options file 282 | *.opt 283 | 284 | # Visual Studio LightSwitch build output 285 | **/*.HTMLClient/GeneratedArtifacts 286 | **/*.DesktopClient/GeneratedArtifacts 287 | **/*.DesktopClient/ModelManifest.xml 288 | **/*.Server/GeneratedArtifacts 289 | **/*.Server/ModelManifest.xml 290 | _Pvt_Extensions 291 | 292 | # PVS Studio for Linux output 293 | *.cl.cfg 294 | 295 | # Kate 296 | *.kate-swp 297 | *.swp 298 | 299 | # QtCreator 300 | build-* 301 | 302 | # Android 303 | *.apk 304 | 305 | # CLion 306 | cmake-build-*/ 307 | 308 | # Others 309 | /.idea 310 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | project(HLMV VERSION 1.5.2) 3 | 4 | set(CMAKE_CXX_STANDARD 11) 5 | 6 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 7 | set(CMAKE_AUTOMOC ON) 8 | set(CMAKE_AUTOUIC ON) 9 | set(CMAKE_AUTORCC ON) 10 | 11 | # Find Qt5 12 | find_package(Qt5 REQUIRED Core Widgets OpenGL) 13 | 14 | # Find OpenGL : apt install libgl1-mesa-dev 15 | if (MSVC OR APPLE) 16 | find_package( opengl REQUIRED ) 17 | else() 18 | set(OPENGL_LIBRARY GL) 19 | endif() 20 | 21 | if(APPLE) 22 | add_definitions(-DGL_SILENCE_DEPRECATION) 23 | endif() 24 | 25 | include_directories(src) 26 | include_directories(src/v) 27 | include_directories(src/m) 28 | include_directories(src/c) 29 | 30 | 31 | file(GLOB FILE_RES 32 | srv/v/hlmv.qrc 33 | ) 34 | file(GLOB FILE_UI 35 | src/v/hlmv.ui 36 | ) 37 | 38 | file(GLOB FILE_MOC 39 | 40 | src/v/hlmv.h 41 | src/v/myopenglwidget.h 42 | ) 43 | 44 | file(GLOB FILE_SRC 45 | src/main.cpp 46 | 47 | src/v/hlmv.cpp 48 | src/v/myopenglwidget.cpp 49 | 50 | src/c/hlmv_application.cpp 51 | src/c/hlmv_tab.cpp 52 | src/c/hlmv_seq.cpp 53 | src/c/hlmv_event.cpp 54 | src/c/hlmv_texture.cpp 55 | src/c/hlmv_body.cpp 56 | src/c/hlmv_display.cpp 57 | src/c/hlmv_origin.cpp 58 | src/c/hlmv_cso_model.cpp 59 | src/c/hlmv_file.cpp 60 | src/c/qt_image.cpp 61 | 62 | src/m/mathlib.c 63 | src/m/studio_render.cpp 64 | src/m/studio_utils.cpp 65 | src/m/ViewerSettings.cpp 66 | src/m/gl_draw.cpp 67 | src/m/mod_decryptor.cpp 68 | src/m/IceKey.cpp 69 | src/m/TGAlib.cpp 70 | src/m/bmpread.cpp 71 | ) 72 | 73 | if(APPLE) 74 | set(FILE_SRC_PLATFORM 75 | src/v/platform/macos/MacWindow.mm 76 | src/v/platform/macos/Touchbar.mm 77 | ) 78 | endif() 79 | 80 | if(WIN32) 81 | set(FILE_SRC_PLATFORM src/v/platform/win32/AeroWindow.cpp) 82 | endif() 83 | 84 | # Moc settings 85 | qt5_wrap_cpp( GEN_MOC ${FILE_MOC}) 86 | qt5_wrap_ui( GEN_UIC ${FILE_UI}) 87 | qt5_add_resources( GEN_RCC ${FILE_RES}) 88 | 89 | if(APPLE) 90 | 91 | set(APP_ICNS ./res/Icon.icns) 92 | set_source_files_properties(${APP_ICNS} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) 93 | 94 | add_executable(HLMV MACOSX_BUNDLE ${FILE_SRC_PLATFORM} ${FILE_RES} ${FILE_UI} ${FILE_SRC} ${GEN_MOC} ${GEN_UIC} ${GEN_RCC} ${APP_ICNS}) 95 | 96 | set_target_properties(HLMV PROPERTIES MACOSX_BUNDLE_INFO_PLIST "${CMAKE_SOURCE_DIR}/cmake/MacOSXBundleInfo.plist.in") 97 | set_target_properties(HLMV PROPERTIES MACOSX_BUNDLE_ICON_FILE "Icon.icns") 98 | 99 | qt5_use_modules(HLMV Core Widgets OpenGL MacExtras) 100 | target_link_libraries(HLMV "-framework AppKit") 101 | target_link_libraries(HLMV ${OPENGL_LIBRARY}) 102 | 103 | set(CPACK_GENERATOR "DragNDrop") 104 | set(CPACK_PACKAGE_VENDOR "MoeMod") 105 | set(CPACK_PACKAGE_FILE_NAME "HLMV-Qt-macOS-${CMAKE_SYSTEM_PROCESSOR}") 106 | set(CPACK_DMG_VOLUME_NAME "HLMV") 107 | set(CPACK_DMG_FORMAT "UDBZ") #UDRW 108 | set(CPACK_DMG_BACKGROUND_IMAGE "${CMAKE_SOURCE_DIR}/cmake/DragNDrop/background.png") 109 | set(CPACK_DMG_DS_STORE "${CMAKE_SOURCE_DIR}/cmake/DragNDrop/DS_Store") 110 | 111 | add_custom_target(HLMV_macdeployqt 112 | COMMAND "${Qt5_DIR}/../../../bin/macdeployqt" "${CMAKE_CURRENT_BINARY_DIR}/HLMV.app" "-always-overwrite" 113 | DEPENDS HLMV COMMENT "Copying Qt runtime binaries") 114 | 115 | include(CPack) 116 | include(CPackDMG) 117 | add_custom_target(HLMV_cpack_dmg 118 | COMMAND "cpack" 119 | DEPENDS HLMV HLMV_macdeployqt COMMENT "Generating DMG file") 120 | 121 | set(CMAKE_INSTALL_PREFIX "/Applications") 122 | install(TARGETS HLMV BUNDLE DESTINATION ".") 123 | elseif(WIN32) 124 | add_executable(HLMV WIN32 ${FILE_SRC_PLATFORM} ${FILE_RES} ${FILE_UI} ${FILE_SRC} ${GEN_MOC} ${GEN_UIC} ${GEN_RCC}) 125 | qt5_use_modules(HLMV Core Widgets OpenGL WinExtras) 126 | target_link_libraries(HLMV ${OPENGL_LIBRARY}) 127 | 128 | set(CPACK_GENERATOR "NSIS") 129 | set(CPACK_PACKAGE_NAME "Half-Life Model Viewer") 130 | set(CPACK_PACKAGE_VENDOR "MoeMod") 131 | set(CPACK_PACKAGE_FILE_NAME "HLMV-Qt-Win32-${CMAKE_SYSTEM_PROCESSOR}") 132 | set(CPACK_PACKAGE_INSTALL_DIRECTORY "HLMV") 133 | set(CPACK_PACKAGE_EXECUTABLES HLMV "${CPACK_PACKAGE_NAME}") 134 | set(CPACK_CREATE_DESKTOP_LINKS HLMV) 135 | set(CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL ON) 136 | set(CPACK_NSIS_MODIFY_PATH ON) 137 | set(CPACK_NSIS_MUI_FINISHPAGE_RUN HLMV) 138 | 139 | set(EXECUTABLE_OUTPUT_PATH "${CMAKE_CURRENT_BINARY_DIR}/bin") 140 | set(LIBRARY_OUTPUT_PATH "${CMAKE_CURRENT_BINARY_DIR}/bin") 141 | add_custom_target(HLMV_windeployqt 142 | COMMAND "${Qt5_DIR}/../../../bin/windeployqt" "${CMAKE_CURRENT_BINARY_DIR}/bin/HLMV.exe" 143 | DEPENDS HLMV COMMENT "Copying Qt runtime binaries") 144 | 145 | install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/bin" DESTINATION ".") 146 | 147 | include(CPack) 148 | add_custom_target(HLMV_cpack_nsis 149 | COMMAND "echo" "Cleaning NSIS file" 150 | COMMAND "rd" "/s" "/q" "_CPack_Packages" "||" "echo" "Not Found, ignore..." 151 | COMMAND "cpack" "-V" 152 | DEPENDS HLMV HLMV_windeployqt COMMENT "Generating NSIS file") 153 | 154 | set(CMAKE_INSTALL_PREFIX "D:/tools/${PROJECT_NAME}") 155 | 156 | else() 157 | add_executable(HLMV ${FILE_SRC_PLATFORM} ${FILE_RES} ${FILE_UI} ${FILE_SRC} ${GEN_MOC} ${GEN_UIC} ${GEN_RCC}) 158 | target_link_libraries(HLMV ${OPENGL_LIBRARY} Qt5::Core Qt5::Widgets Qt5::OpenGL) 159 | endif() -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HLMV-Qt 2 | Half-Life Model Viewer remixed with Qt5. 3 | Optimized for macOS. 4 | 5 | # Features 6 | - [x] Customized frameless UI on macOS 7 | - [x] Multi-Touch Bar support on macOS 8 | - [x] what the original HLMV does 9 | - [x] Import & Export with BMP/JPG/TIFF/PNG formats (Qt-featured) 10 | - [x] Screenshot to clipboard and/or image file 11 | - [x] CSO encrypted model loading 12 | - [x] CSO external textures loading 13 | - [x] Multi-modality interaction : touchbar, touchpad, touchscreen and mouse 14 | - [x] Automatically packaging and distribution with CMake/CPack : DMG for macOS and NSIS for Windows 15 | 16 | # Installation 17 | ## macOS 18 | Just download .dmg file and drag HLMV into Applications. 19 | Drag .mdl files into HLMV to open it. 20 | ## Windows 21 | Using the NSIS installer 22 | ## Linux 23 | WIP 24 | 25 | # Screenshots 26 | MACOS 27 | 28 | # Contribution 29 | Issue if you want more features. 30 | Pull requests are extremely welcomed. 31 | -------------------------------------------------------------------------------- /cmake/DragNDrop/DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MoeMod/HLMV-Qt/4dc620716d1329795ae30b24c92f28f737d33855/cmake/DragNDrop/DS_Store -------------------------------------------------------------------------------- /cmake/DragNDrop/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MoeMod/HLMV-Qt/4dc620716d1329795ae30b24c92f28f737d33855/cmake/DragNDrop/background.png -------------------------------------------------------------------------------- /cmake/MacOSXBundleInfo.plist.in: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${MACOSX_BUNDLE_EXECUTABLE_NAME} 9 | CFBundleGetInfoString 10 | ${MACOSX_BUNDLE_INFO_STRING} 11 | CFBundleIconFile 12 | ${MACOSX_BUNDLE_ICON_FILE} 13 | CFBundleIdentifier 14 | ${MACOSX_BUNDLE_GUI_IDENTIFIER} 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleLongVersionString 18 | ${MACOSX_BUNDLE_LONG_VERSION_STRING} 19 | CFBundleName 20 | ${MACOSX_BUNDLE_BUNDLE_NAME} 21 | CFBundlePackageType 22 | APPL 23 | CFBundleShortVersionString 24 | ${MACOSX_BUNDLE_SHORT_VERSION_STRING} 25 | CFBundleSignature 26 | ???? 27 | CFBundleVersion 28 | ${MACOSX_BUNDLE_BUNDLE_VERSION} 29 | CSResourcesFileMapped 30 | 31 | NSHumanReadableCopyright 32 | ${MACOSX_BUNDLE_COPYRIGHT} 33 | NSHighResolutionCapable 34 | 35 | NSPrincipalClass 36 | NSApplication 37 | CFBundleDocumentTypes 38 | 39 | 40 | CFBundleTypeExtensions 41 | 42 | mdl 43 | 44 | CFBundleTypeIconFile 45 | Icon.icns 46 | CFBundleTypeOSTypes 47 | 48 | **** 49 | 50 | CFBundleTypeName 51 | Half-Life model 52 | CFBundleTypeRole 53 | Viewer 54 | LSHandlerRank 55 | Default 56 | LSIsAppleDefaultForType 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /doc/screenshot_macos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MoeMod/HLMV-Qt/4dc620716d1329795ae30b24c92f28f737d33855/doc/screenshot_macos.png -------------------------------------------------------------------------------- /res/HLMV_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MoeMod/HLMV-Qt/4dc620716d1329795ae30b24c92f28f737d33855/res/HLMV_128x128.png -------------------------------------------------------------------------------- /res/HLMV_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MoeMod/HLMV-Qt/4dc620716d1329795ae30b24c92f28f737d33855/res/HLMV_256x256.png -------------------------------------------------------------------------------- /res/Icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MoeMod/HLMV-Qt/4dc620716d1329795ae30b24c92f28f737d33855/res/Icon.icns -------------------------------------------------------------------------------- /res/makeicns.sh: -------------------------------------------------------------------------------- 1 | sips -z 16 16 pic.png --out icons.iconset/icon_16x16.png 2 | sips -z 32 32 pic.png --out icons.iconset/icon_16x16@2x.png 3 | sips -z 32 32 pic.png --out icons.iconset/icon_32x32.png 4 | sips -z 64 64 pic.png --out icons.iconset/icon_32x32@2x.png 5 | sips -z 64 64 pic.png --out icons.iconset/icon_64x64.png 6 | sips -z 128 128 pic.png --out icons.iconset/icon_64x64@2x.png 7 | sips -z 128 128 pic.png --out icons.iconset/icon_128x128.png 8 | sips -z 256 256 pic.png --out icons.iconset/icon_128x128@2x.png 9 | sips -z 256 256 pic.png --out icons.iconset/icon_256x256.png 10 | sips -z 512 512 pic.png --out icons.iconset/icon_256x256@2x.png 11 | sips -z 512 512 pic.png --out icons.iconset/icon_512x512.png 12 | sips -z 1024 1024 pic.png --out icons.iconset/icon_512x512@2x.png 13 | -------------------------------------------------------------------------------- /src/c/hlmv_application.cpp: -------------------------------------------------------------------------------- 1 | #include "hlmv_application.h" 2 | #include "ViewerSettings.h" 3 | 4 | #include 5 | 6 | HLMVApplication::HLMVApplication(int &argc, char **argv) : QApplication(argc, argv) 7 | { 8 | InitViewerSettings(); 9 | 10 | setAttribute(Qt::AA_EnableHighDpiScaling, true); 11 | w.show(); 12 | if(argc >= 2) 13 | { 14 | try 15 | { 16 | w.OpenFile(argv[1]); 17 | } 18 | catch(...) 19 | { 20 | // eat up the file-opening exception 21 | // and then invalid argument will be ignored 22 | } 23 | } 24 | 25 | } 26 | 27 | bool HLMVApplication::event(QEvent *event) 28 | { 29 | if (event->type() == QEvent::FileOpen) 30 | { 31 | QString filename = static_cast(event)->file(); 32 | try 33 | { 34 | w.OpenFile(filename); 35 | } 36 | catch (...) 37 | { 38 | 39 | } 40 | return true; 41 | } 42 | 43 | return QApplication::event(event); 44 | } 45 | -------------------------------------------------------------------------------- /src/c/hlmv_application.h: -------------------------------------------------------------------------------- 1 | #ifndef HLMV_APPLICATION_H 2 | #define HLMV_APPLICATION_H 3 | 4 | #include 5 | #include "v/hlmv.h" 6 | 7 | class HLMVApplication : public QApplication 8 | { 9 | public: 10 | HLMVApplication(int &argc, char **argv); 11 | 12 | public: 13 | bool event(QEvent *event) override; 14 | 15 | public: 16 | QtGuiApplication1 w; 17 | }; 18 | 19 | #endif //HLMV_APPLICATION_H 20 | -------------------------------------------------------------------------------- /src/c/hlmv_body.cpp: -------------------------------------------------------------------------------- 1 | #include "hlmv.h" 2 | #include "ViewerSettings.h" 3 | #include "StudioModel.h" 4 | 5 | void QtGuiApplication1::initBodyparts () 6 | { 7 | studiohdr_t *hdr = g_studioModel.getStudioHeader (); 8 | if (hdr) 9 | { 10 | int i; 11 | mstudiobodyparts_t *pbodyparts = (mstudiobodyparts_t *) ((byte *) hdr + hdr->bodypartindex); 12 | 13 | ui.cBodypart->clear (); 14 | if (hdr->numbodyparts > 0) 15 | { 16 | for (i = 0; i < hdr->numbodyparts; i++) 17 | ui.cBodypart->addItem (pbodyparts[i].name); 18 | 19 | ui.cBodypart->setCurrentIndex (0); 20 | setBodypart(0); 21 | 22 | ui.cSubmodel->clear (); 23 | for (i = 0; i < pbodyparts[0].nummodels; i++) 24 | { 25 | char str[64]; 26 | sprintf (str, "Submodel %d", i + 1); 27 | ui.cSubmodel->addItem (str); 28 | } 29 | ui.cSubmodel->setCurrentIndex (0); 30 | setSubmodel(0); 31 | } 32 | } 33 | } 34 | 35 | 36 | 37 | void QtGuiApplication1::setBodypart (int index) 38 | { 39 | if(index < 0) 40 | return; 41 | 42 | studiohdr_t *hdr = g_studioModel.getStudioHeader (); 43 | if (hdr) 44 | { 45 | ui.cBodypart->setCurrentIndex (index); 46 | if (index < hdr->numbodyparts) 47 | { 48 | mstudiobodyparts_t *pbodyparts = (mstudiobodyparts_t *) ((byte *) hdr + hdr->bodypartindex); 49 | ui.cSubmodel->clear (); 50 | 51 | for (int i = 0; i < pbodyparts[index].nummodels; i++) 52 | { 53 | char str[64]; 54 | sprintf (str, "Submodel %d", i + 1); 55 | ui.cSubmodel->addItem (str); 56 | } 57 | ui.cSubmodel->setCurrentIndex (0); 58 | setSubmodel(0); 59 | } 60 | } 61 | } 62 | 63 | void QtGuiApplication1::setSubmodel (int index) 64 | { 65 | if(index < 0) 66 | return; 67 | 68 | g_studioModel.SetBodygroup (ui.cBodypart->currentIndex (), index); 69 | g_viewerSettings.submodels[ui.cBodypart->currentIndex ()] = index; 70 | } 71 | 72 | void QtGuiApplication1::initBoneControllers () 73 | { 74 | studiohdr_t *hdr = g_studioModel.getStudioHeader (); 75 | if (hdr) 76 | { 77 | ui.cController->setEnabled (hdr->numbonecontrollers > 0); 78 | ui.slController->setEnabled (hdr->numbonecontrollers > 0); 79 | ui.cController->clear (); 80 | 81 | mstudiobonecontroller_t *pbonecontrollers = (mstudiobonecontroller_t *) ((byte *) hdr + hdr->bonecontrollerindex); 82 | for (int i = 0; i < hdr->numbonecontrollers; i++) 83 | { 84 | char str[32]; 85 | if (pbonecontrollers[i].index == 4) 86 | sprintf (str, "Mouth"); 87 | else 88 | sprintf (str, "Controller %d", pbonecontrollers[i].index); 89 | ui.cController->addItem (str); 90 | } 91 | 92 | if (hdr->numbonecontrollers > 0) 93 | { 94 | ui.cController->setCurrentIndex (0); 95 | ui.slController->setRange ((int) pbonecontrollers[0].start, (int) pbonecontrollers[0].end); 96 | ui.slController->setValue (0); 97 | } 98 | 99 | } 100 | } 101 | 102 | void QtGuiApplication1::setBoneController (int index) 103 | { 104 | if(index < 0) 105 | return; 106 | 107 | studiohdr_t *hdr = g_studioModel.getStudioHeader (); 108 | if (hdr) 109 | { 110 | mstudiobonecontroller_t *pbonecontrollers = (mstudiobonecontroller_t *) ((byte *) hdr + hdr->bonecontrollerindex); 111 | ui.slController->setRange ((int) pbonecontrollers[index].start, (int) pbonecontrollers[index].end); 112 | ui.slController->setValue (0); 113 | } 114 | } 115 | 116 | 117 | 118 | void QtGuiApplication1::setBoneControllerValue (int index, float value) 119 | { 120 | studiohdr_t *hdr = g_studioModel.getStudioHeader (); 121 | if (hdr) 122 | { 123 | mstudiobonecontroller_t *pbonecontrollers = (mstudiobonecontroller_t *) ((byte *) hdr + hdr->bonecontrollerindex); 124 | if (pbonecontrollers[index].index == 4) 125 | g_studioModel.SetMouth (value); 126 | else 127 | g_studioModel.SetController (pbonecontrollers[index].index, value); 128 | 129 | g_viewerSettings.controllers[index] = value; 130 | } 131 | } 132 | 133 | void QtGuiApplication1::setBoneControllerCurrentValue (int value) 134 | { 135 | if(!ui.cController->count()) 136 | return; 137 | setBoneControllerValue(ui.cController->currentIndex(), static_cast(value)); 138 | } 139 | 140 | void QtGuiApplication1::initSkins () 141 | { 142 | studiohdr_t *hdr = g_studioModel.getTextureHeader (); 143 | if (hdr) 144 | { 145 | ui.cSkin->setEnabled (hdr->numskinfamilies > 0); 146 | ui.cSkin->clear (); 147 | 148 | for (int i = 0; i < hdr->numskinfamilies; i++) 149 | { 150 | char str[32]; 151 | sprintf (str, "Skin %d", i + 1); 152 | ui.cSkin->addItem (str); 153 | } 154 | 155 | ui.cSkin->setCurrentIndex (0); 156 | setSkin(0); 157 | } 158 | } 159 | 160 | void QtGuiApplication1::setModelInfo () 161 | { 162 | char str[1024]; 163 | 164 | studiohdr_t *studioHdr = g_studioModel.getStudioHeader (); 165 | studiohdr_t *textureHdr = g_studioModel.getTextureHeader (); 166 | 167 | if (!studioHdr || !textureHdr) 168 | return; 169 | 170 | sprintf (str, 171 | "Bones: %d\n" 172 | "Bone Controllers: %d\n" 173 | "Hit Boxes: %d\n" 174 | "Sequences: %d\n" 175 | "Sequence Groups: %d\n", 176 | studioHdr->numbones, 177 | studioHdr->numbonecontrollers, 178 | studioHdr->numhitboxes, 179 | studioHdr->numseq, 180 | studioHdr->numseqgroups 181 | ); 182 | 183 | ui.lModelInfo1->setText(str); 184 | 185 | sprintf (str, 186 | "Textures: %d\n" 187 | "Skin Families: %d\n" 188 | "Bodyparts: %d\n" 189 | "Attachments: %d\n" 190 | "Transitions: %d\n", 191 | textureHdr->numtextures, 192 | textureHdr->numskinfamilies, 193 | studioHdr->numbodyparts, 194 | studioHdr->numattachments, 195 | studioHdr->numtransitions); 196 | 197 | ui.lModelInfo2->setText (str); 198 | } 199 | 200 | void QtGuiApplication1::setSkin (int index) 201 | { 202 | if(index < 0) 203 | return; 204 | 205 | g_studioModel.SetSkin (index); 206 | g_viewerSettings.skin = index; 207 | } 208 | 209 | 210 | -------------------------------------------------------------------------------- /src/c/hlmv_cso_model.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MoeMod/HLMV-Qt/4dc620716d1329795ae30b24c92f28f737d33855/src/c/hlmv_cso_model.cpp -------------------------------------------------------------------------------- /src/c/hlmv_display.cpp: -------------------------------------------------------------------------------- 1 | #include "hlmv.h" 2 | #include "ViewerSettings.h" 3 | #include "StudioModel.h" 4 | 5 | void QtGuiApplication1::updateViewSettings() 6 | { 7 | g_viewerSettings.renderMode = ui.cRenderMode->currentIndex(); 8 | 9 | int value = ui.slTransparency->value (); 10 | g_viewerSettings.transparency = (float) value / 100.0f; 11 | ui.lTransparency->setText (QString().sprintf("Opacity: %d%%", value)); 12 | 13 | g_viewerSettings.mirror = ui.cbMirror->isChecked(); 14 | g_viewerSettings.showGround = ui.cbGround->isChecked(); 15 | g_viewerSettings.showBackground = ui.cbBackground->isChecked(); 16 | g_viewerSettings.showWireframeOverlay = ui.cbWireframeOverlay->isChecked(); 17 | 18 | g_viewerSettings.useStencil = (!g_viewerSettings.use3dfx && g_viewerSettings.mirror); 19 | 20 | g_viewerSettings.showHitBoxes = ui.cbHitBoxes->isChecked(); 21 | g_viewerSettings.showBones = ui.cbBones->isChecked(); 22 | g_viewerSettings.showAttachments = ui.cbAttachments->isChecked(); 23 | g_viewerSettings.showEyePosition = ui.cbEyePosition->isChecked(); 24 | 25 | g_studioModel.scaleMeshes (static_cast(ui.leMeshScale->value())); 26 | g_studioModel.scaleBones (static_cast(ui.leBoneScale->value())); 27 | 28 | ui.openglwidget->update (); 29 | } 30 | 31 | void QtGuiApplication1::centerView () 32 | { 33 | float min[3], max[3]; 34 | g_studioModel.ExtractBbox (min, max); 35 | 36 | float dx = max[0] - min[0]; 37 | float dy = max[1] - min[1]; 38 | float dz = max[2] - min[2]; 39 | float d = dx; 40 | if (dy > d) 41 | d = dy; 42 | if (dz > d) 43 | d = dz; 44 | g_viewerSettings.trans[0] = 0; 45 | g_viewerSettings.trans[1] = min[2] + dz / 2; 46 | g_viewerSettings.trans[2] = d * 1.0f; 47 | g_viewerSettings.rot[0] = -90.0f; 48 | g_viewerSettings.rot[1] = -90.0f; 49 | g_viewerSettings.rot[2] = 0.0f; 50 | ui.centralWidget->update (); 51 | } 52 | -------------------------------------------------------------------------------- /src/c/hlmv_event.cpp: -------------------------------------------------------------------------------- 1 | #include "hlmv.h" 2 | #include "ViewerSettings.h" 3 | #include "StudioModel.h" 4 | 5 | void QtGuiApplication1::setEventInfo(int index) 6 | { 7 | char str[1024]; 8 | 9 | studiohdr_t *hdr = g_studioModel.getStudioHeader (); 10 | 11 | if (!hdr) 12 | return; 13 | 14 | mstudioseqdesc_t *pseqdescs = (mstudioseqdesc_t *) ((byte *)hdr + hdr->seqindex); 15 | 16 | if(!ui.cEvent->count()) 17 | { 18 | ui.lEventInfo->setText (""); 19 | return; 20 | } 21 | 22 | if (pseqdescs->numevents) 23 | { 24 | mstudioevent_t *pevents = (mstudioevent_t *) ((byte *)hdr + pseqdescs->eventindex) + index; 25 | 26 | sprintf (str, 27 | "Frame: %d " 28 | "Event: %d " 29 | "Type: %d\n" 30 | "Options: %s", 31 | pevents->frame, 32 | pevents->event, 33 | pevents->type, 34 | pevents->options 35 | ); 36 | } 37 | else 38 | { 39 | str[0] = '\0'; 40 | } 41 | ui.lEventInfo->setText (str); 42 | } 43 | 44 | void QtGuiApplication1::setEvent (int seq) 45 | { 46 | char str[64]; 47 | 48 | studiohdr_t *hdr = g_studioModel.getStudioHeader (); 49 | 50 | if (!hdr) 51 | return; 52 | 53 | mstudioseqdesc_t *pseqdescs = (mstudioseqdesc_t *) ((byte *)hdr + hdr->seqindex); //g_viewerSettings.sequence; 54 | 55 | ui.cEvent->clear (); 56 | if (0 < pseqdescs[seq].numevents) 57 | { 58 | for (int i = 1; i <= pseqdescs[seq].numevents; i++) 59 | { 60 | sprintf (str, "Event %d", i); 61 | ui.cEvent->addItem (str); 62 | } 63 | } 64 | ui.cEvent->setCurrentIndex (0); 65 | setEventInfo(0); 66 | } -------------------------------------------------------------------------------- /src/c/hlmv_file.cpp: -------------------------------------------------------------------------------- 1 | #include "hlmv.h" 2 | #include "StudioModel.h" 3 | #include "ViewerSettings.h" 4 | #include "mod_decryptor.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | 16 | void QtGuiApplication1::dragEnterEvent(QDragEnterEvent *e) 17 | { 18 | // only 1 file is accepted 19 | if (e->mimeData()->urls().size() != 1) 20 | return; 21 | 22 | e->acceptProposedAction(); 23 | } 24 | 25 | void QtGuiApplication1::dropEvent(QDropEvent *e) 26 | { 27 | QList urls = e->mimeData()->urls(); 28 | if (urls.isEmpty()) 29 | return; 30 | 31 | QString filename = urls.first().toLocalFile(); 32 | 33 | if (filename.isEmpty()) 34 | return; 35 | 36 | try 37 | { 38 | OpenFile(filename); 39 | } 40 | catch (const std::exception & e) 41 | { 42 | QMessageBox::warning(this, "Error", e.what()); 43 | } 44 | } 45 | 46 | void QtGuiApplication1::OpenFile(QString qfilename) noexcept(false) 47 | { 48 | std::string sfilename = qfilename.toStdString(); 49 | const char *filename = sfilename.c_str(); 50 | g_studioModel.FreeModel (); 51 | 52 | studiohdr_t *phdr = g_studioModel.LoadModel (filename); 53 | if (!phdr) 54 | throw std::runtime_error("Error loading model."); 55 | 56 | bool bCSOTexture = false; 57 | if(Mod_IsCSOEncryptedModel(phdr)) 58 | { 59 | if(QMessageBox::question(this, 60 | "CSO Model Helper", 61 | "This model is CSO-format and encrypted with IceKey. \nDo you want to decrypt it?" 62 | ) == QMessageBox::Yes) 63 | { 64 | Mod_DecryptModel(filename, phdr); 65 | bCSOTexture = true; 66 | } 67 | } 68 | 69 | if(!bCSOTexture && g_studioModel.hasCSOTexture(phdr)) 70 | { 71 | if(QMessageBox::question(this, 72 | "CSO Model Helper", 73 | "This model is CSO-format and has external textures. \nDo you want to load them?" 74 | ) == QMessageBox::Yes) 75 | { 76 | bCSOTexture = true; 77 | } 78 | } 79 | 80 | 81 | if (!g_studioModel.PostLoadModel (phdr, filename)) 82 | throw std::runtime_error("Error post-loading model."); 83 | 84 | if(bCSOTexture) 85 | g_studioModel.LoadModelTexturesCSO(phdr, (QFileInfo(qfilename).path() + "/texture").toStdString().c_str()); 86 | else 87 | g_studioModel.LoadModelTextures(phdr); 88 | 89 | 90 | initSequences (); 91 | setEvent (0); 92 | initBodyparts (); 93 | initBoneControllers (); 94 | initSkins (); 95 | initTextures (); 96 | centerView (); 97 | strcpy (g_viewerSettings.modelFile, filename); 98 | setModelInfo (); 99 | g_viewerSettings.sequence = 0; 100 | setSequenceInfo (); 101 | g_viewerSettings.speedScale = 1.0f; 102 | 103 | 104 | setWindowFilePath(qfilename); 105 | setWindowTitle("HLMV - " + QFileInfo(qfilename).fileName()); 106 | 107 | std::fill(std::begin(g_viewerSettings.submodels), std::end(g_viewerSettings.submodels), 0); 108 | std::fill(std::begin(g_viewerSettings.controllers), std::end(g_viewerSettings.controllers), 0); 109 | } 110 | 111 | void QtGuiApplication1::SaveFile(QString qfilename) noexcept(false) 112 | { 113 | std::string sfilename = qfilename.toStdString(); 114 | const char *filename = sfilename.c_str(); 115 | 116 | if (!filename || !filename[0]) 117 | throw std::runtime_error("Empty file name."); 118 | 119 | if (!g_studioModel.getStudioHeader()) 120 | throw std::runtime_error("No file opened."); 121 | 122 | bool result = g_studioModel.SaveModel(filename); 123 | if(!result) 124 | throw std::runtime_error("Error writing file."); 125 | } 126 | 127 | void QtGuiApplication1::CloseFile() 128 | { 129 | g_studioModel.FreeModel(); 130 | 131 | ui.cSequence->clear(); 132 | ui.cWpSequence->clear (); 133 | ui.lSequenceInfo1->clear(); 134 | ui.lSequenceInfo2->clear(); 135 | ui.slWeaponFrame->setRange(0, 0); 136 | ui.leWeaponFrame->setRange(0, 0); 137 | ui.slWpSpeedFPS->setRange(0, 0); 138 | 139 | ui.cEvent->clear (); 140 | ui.lEventInfo->clear (); 141 | 142 | ui.cSubmodel->clear (); 143 | ui.cBodypart->clear (); 144 | ui.cController->clear (); 145 | ui.cSkin->clear (); 146 | 147 | ui.cTextures->clear (); 148 | ui.cMesh->clear (); 149 | 150 | strcpy (g_viewerSettings.modelFile, ""); 151 | ui.lModelInfo1->clear(); 152 | ui.lModelInfo2->clear(); 153 | } 154 | -------------------------------------------------------------------------------- /src/c/hlmv_origin.cpp: -------------------------------------------------------------------------------- 1 | #include "hlmv.h" 2 | #include "ViewerSettings.h" 3 | #include "StudioModel.h" 4 | 5 | 6 | void QtGuiApplication1::updateWeaponOriginSettings() 7 | { 8 | g_viewerSettings.showCrosshair = ui.cbCrossHair->isChecked(); 9 | g_viewerSettings.showGuideLines = ui.cbGuildlines->isChecked(); 10 | g_viewerSettings.righthand = ui.cbRightHand->isChecked(); 11 | 12 | g_viewerSettings.vieworigintrans[0] = static_cast(ui.leOriginX->value()); 13 | g_viewerSettings.vieworigintrans[1] = static_cast(ui.leOriginY->value()); 14 | g_viewerSettings.vieworigintrans[2] = static_cast(ui.leOriginZ->value()); 15 | 16 | g_viewerSettings.viewfov = static_cast(ui.leFOV->value()); 17 | g_viewerSettings.viewaspect = static_cast(ui.leAspec->value()); 18 | 19 | } -------------------------------------------------------------------------------- /src/c/hlmv_seq.cpp: -------------------------------------------------------------------------------- 1 | #include "hlmv.h" 2 | #include "ViewerSettings.h" 3 | #include "StudioModel.h" 4 | 5 | void QtGuiApplication1::initSequences () 6 | { 7 | studiohdr_t *hdr = g_studioModel.getStudioHeader (); 8 | if (hdr) 9 | { 10 | ui.cSequence->clear(); 11 | ui.cWpSequence->clear (); 12 | for (int i = 0; i < hdr->numseq; i++) 13 | { 14 | mstudioseqdesc_t *pseqdescs = (mstudioseqdesc_t *) ((byte *) hdr + hdr->seqindex); 15 | ui.cSequence->addItem (pseqdescs[i].label); 16 | ui.cWpSequence->addItem (pseqdescs[i].label); 17 | } 18 | 19 | ui.cSequence->setCurrentRow (0); 20 | ui.cWpSequence->setCurrentIndex (0); 21 | } 22 | } 23 | 24 | void QtGuiApplication1::setSequence(int index) 25 | { 26 | if(index < 0) 27 | return; 28 | 29 | ui.cSequence->setCurrentRow(index); 30 | ui.cWpSequence->setCurrentIndex (index); 31 | 32 | g_studioModel.SetSequence(index); 33 | g_viewerSettings.sequence = index; 34 | setSequenceInfo (); 35 | setEvent (index); 36 | } 37 | 38 | void QtGuiApplication1::setSequenceInfo () 39 | { 40 | char str[1024]; 41 | 42 | studiohdr_t *hdr = g_studioModel.getStudioHeader (); 43 | 44 | if (!hdr) 45 | return; 46 | 47 | mstudioseqdesc_t *pseqdesc = (mstudioseqdesc_t *) ((byte *) hdr + hdr->seqindex) + g_viewerSettings.sequence; 48 | 49 | sprintf (str, 50 | "Sequence#: %d\n" 51 | "Frames: %d\n" 52 | "FPS: %d\n" 53 | "Blends: %d\n" 54 | "# of events: %d\n", 55 | g_viewerSettings.sequence, 56 | pseqdesc->numframes, 57 | (int)pseqdesc->fps, 58 | pseqdesc->numblends, 59 | pseqdesc->numevents 60 | ); 61 | 62 | ui.lSequenceInfo1->setText (str); 63 | ui.lSequenceInfo2->setText (str); 64 | 65 | ui.slFrame->setRange(0, pseqdesc->numframes); 66 | ui.leFrame->setRange(0, pseqdesc->numframes); 67 | ui.slWeaponFrame->setRange(0, pseqdesc->numframes); 68 | ui.leWeaponFrame->setRange(0, pseqdesc->numframes); 69 | 70 | ui.slWpSpeedFPS->setValue(pseqdesc->fps); 71 | ui.slWpSpeedFPS->setRange(0, pseqdesc->fps * 10); 72 | } 73 | 74 | void QtGuiApplication1::OnSetAnimationPlaying(bool play) 75 | { 76 | ui.bPlaySequence->setChecked(play); 77 | ui.bPlaySequence2->setChecked(play); 78 | 79 | ui.openglwidget->setPaused(!play); 80 | 81 | int showcurrent = play ? 0 : g_studioModel.SetFrame (-1); 82 | 83 | ui.slFrame->setValue(showcurrent); 84 | ui.leFrame->setValue(showcurrent); 85 | ui.slWeaponFrame->setValue(showcurrent); 86 | ui.leWeaponFrame->setValue(showcurrent); 87 | 88 | ui.slFrame->setEnabled (!play); 89 | ui.leFrame->setEnabled (!play); 90 | ui.slWeaponFrame->setEnabled (!play); 91 | ui.leWeaponFrame->setEnabled (!play); 92 | } 93 | 94 | void QtGuiApplication1::OnSetAnimationCurrentFrame(int v) 95 | { 96 | ui.slFrame->setValue (v); 97 | ui.leFrame->setValue (v); 98 | ui.slWeaponFrame->setValue (v); 99 | ui.leWeaponFrame->setValue (v); 100 | 101 | g_studioModel.SetFrame (v); 102 | } 103 | 104 | void QtGuiApplication1::OnSetAnimationFPS(double fps) 105 | { 106 | studiohdr_t *hdr = g_studioModel.getStudioHeader (); 107 | if (!hdr) 108 | return; 109 | mstudioseqdesc_t *pseqdesc = (mstudioseqdesc_t *) ((byte *) hdr + hdr->seqindex) + g_viewerSettings.sequence; 110 | //g_viewerSettings.speedScale = static_cast(fps) / pseqdesc->fps; 111 | pseqdesc->fps = fps; 112 | 113 | } 114 | 115 | -------------------------------------------------------------------------------- /src/c/hlmv_tab.cpp: -------------------------------------------------------------------------------- 1 | #include "hlmv.h" 2 | #include "ViewerSettings.h" 3 | 4 | void QtGuiApplication1::OnTabChanged(int n) 5 | { 6 | g_viewerSettings.showTexture = (n == 2); 7 | 8 | if (n == 4) 9 | { 10 | g_viewerSettings.vieworiginmode = true; 11 | g_viewerSettings.yaw = 74.0f; 12 | } 13 | else 14 | { 15 | g_viewerSettings.yaw = 65.0f; 16 | g_viewerSettings.vieworiginmode = false; 17 | } 18 | #ifdef Q_OS_MAC 19 | // update touchbar when tab changed 20 | InstallTouchBar(); 21 | #endif 22 | } 23 | 24 | -------------------------------------------------------------------------------- /src/c/hlmv_texture.cpp: -------------------------------------------------------------------------------- 1 | #include "hlmv.h" 2 | #include "ViewerSettings.h" 3 | #include "StudioModel.h" 4 | #include "qt_image.h" 5 | 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | void QtGuiApplication1::initTextures () 13 | { 14 | studiohdr_t *hdr = g_studioModel.getTextureHeader (); 15 | if (hdr) 16 | { 17 | ui.cTextures->clear (); 18 | mstudiotexture_t *ptextures = (mstudiotexture_t *) ((byte *) hdr + hdr->textureindex); 19 | for (int i = 0; i < hdr->numtextures; i++) 20 | ui.cTextures->addItem (ptextures[i].name); 21 | ui.cTextures->setCurrentIndex (0); 22 | g_viewerSettings.texture = 0; 23 | if (hdr->numtextures > 0) 24 | { 25 | ui.cbChrome->setChecked ((ptextures->flags & STUDIO_NF_CHROME) == STUDIO_NF_CHROME); 26 | ui.cbAdditive->setChecked ((ptextures->flags & STUDIO_NF_ADDITIVE) == STUDIO_NF_ADDITIVE); 27 | ui.cbTransparent->setChecked ((ptextures->flags & STUDIO_NF_TRANSPARENT) == STUDIO_NF_TRANSPARENT); 28 | ui.cbFullBright->setChecked ((ptextures->flags & STUDIO_NF_FULLBRIGHT) == STUDIO_NF_FULLBRIGHT); 29 | initMeshList(0); 30 | } 31 | } 32 | } 33 | 34 | void QtGuiApplication1::setMesh (int index) 35 | { 36 | if (index >= 0) 37 | { 38 | g_viewerSettings.showAllMeshes = true; 39 | if (g_viewerSettings.meshCount >= 1 && index < g_viewerSettings.meshCount) 40 | g_viewerSettings.showAllMeshes = false; 41 | g_viewerSettings.mesh = index; 42 | } 43 | } 44 | 45 | void QtGuiApplication1::initMeshList (int index) 46 | { 47 | char str[64]; 48 | int i, j, k = 0, l; 49 | 50 | studiohdr_t *hdr = g_studioModel.getStudioHeader (); 51 | 52 | if (!hdr) 53 | return; 54 | 55 | ui.cMesh->clear (); 56 | for (i = 0; i < hdr->numbodyparts; i++) 57 | { 58 | mstudiobodyparts_t *pbodyparts = (mstudiobodyparts_t *) ((byte *) hdr + hdr->bodypartindex) + i; 59 | for (j = 0; j < pbodyparts->nummodels; j++) 60 | { 61 | mstudiomodel_t *pmodels = (mstudiomodel_t *) ((byte *) hdr + pbodyparts->modelindex) + j; 62 | for (l = 0; l < pmodels->nummesh; l++) 63 | { 64 | mstudiomesh_t *pmesh = (mstudiomesh_t *) ((byte *) hdr + pmodels->meshindex) + l; 65 | if (pmesh->skinref == index) 66 | { 67 | sprintf (str, "Mesh %d", ++k); 68 | ui.cMesh->addItem (str); 69 | } 70 | } 71 | } 72 | } 73 | 74 | ui.cMesh->addItem ("All"); 75 | 76 | g_viewerSettings.mesh = 0; 77 | g_viewerSettings.meshCount = k; 78 | ui.cMesh->setCurrentIndex (k); 79 | setMesh(k); 80 | } 81 | 82 | void QtGuiApplication1::setTextureCurrent(int index) 83 | { 84 | if (index < 0) 85 | return; 86 | 87 | g_viewerSettings.texture = index; 88 | studiohdr_t *hdr = g_studioModel.getTextureHeader (); 89 | if (hdr) 90 | { 91 | mstudiotexture_t *ptexture = (mstudiotexture_t *) ((byte *) hdr + hdr->textureindex) + index; 92 | ui.lTexSize->setText (QString().sprintf("Texture (size: %d x %d)", ptexture->width, ptexture->height)); 93 | ui.cbChrome->setChecked ((ptexture->flags & STUDIO_NF_CHROME) == STUDIO_NF_CHROME); 94 | ui.cbAdditive->setChecked ((ptexture->flags & STUDIO_NF_ADDITIVE) == STUDIO_NF_ADDITIVE); 95 | ui.cbTransparent->setChecked ((ptexture->flags & STUDIO_NF_TRANSPARENT) == STUDIO_NF_TRANSPARENT); 96 | ui.cbFullBright->setChecked ((ptexture->flags & STUDIO_NF_FULLBRIGHT) == STUDIO_NF_FULLBRIGHT); 97 | } 98 | initMeshList (index); 99 | ui.openglwidget->update (); 100 | } 101 | 102 | void QtGuiApplication1::updateTextureFlags() 103 | { 104 | studiohdr_t *hdr = g_studioModel.getTextureHeader (); 105 | if (hdr) 106 | { 107 | mstudiotexture_t *ptexture = (mstudiotexture_t *) ((byte *) hdr + hdr->textureindex) + g_viewerSettings.texture; 108 | if (ui.cbChrome->isChecked ()) 109 | { 110 | ptexture->flags &= ~STUDIO_NF_TRANSPARENT; 111 | ptexture->flags |= STUDIO_NF_CHROME; 112 | ui.cbTransparent->setChecked (false); 113 | } 114 | else 115 | ptexture->flags &= ~STUDIO_NF_CHROME; 116 | 117 | if (ui.cbAdditive->isChecked ()) 118 | { 119 | ptexture->flags &= ~STUDIO_NF_TRANSPARENT; 120 | ptexture->flags |= STUDIO_NF_ADDITIVE; 121 | ui.cbTransparent->setChecked (false); 122 | } 123 | else 124 | ptexture->flags &= ~STUDIO_NF_ADDITIVE; 125 | 126 | if (ui.cbTransparent->isChecked ()) 127 | { 128 | ptexture->flags &= ~(STUDIO_NF_ADDITIVE|STUDIO_NF_CHROME); 129 | ptexture->flags |= STUDIO_NF_TRANSPARENT; 130 | ui.cbChrome->setChecked (false); 131 | ui.cbAdditive->setChecked (false); 132 | } 133 | else 134 | ptexture->flags &= ~STUDIO_NF_TRANSPARENT; 135 | 136 | if (ui.cbFullBright->isChecked ()) 137 | { 138 | ptexture->flags |= STUDIO_NF_FULLBRIGHT; 139 | } 140 | else 141 | ptexture->flags &= ~STUDIO_NF_FULLBRIGHT; 142 | } 143 | } 144 | 145 | void QtGuiApplication1::updateTextureUVDisplay() 146 | { 147 | g_viewerSettings.showUVMap = ui.cbUVMap->isChecked (); 148 | g_viewerSettings.showUVMapOverlay = ui.cbUVMapOverlay->isChecked (); 149 | g_viewerSettings.showSmoothLines = ui.cbAA->isChecked (); 150 | } 151 | 152 | void QtGuiApplication1::setTextureScale(int texScale) 153 | { 154 | g_viewerSettings.textureScale = 1.0f + static_cast(texScale) * 4.0f / 100.0f; 155 | ui.lTextureScale->setText (QString().sprintf("Scale Texture View (%.fx)", g_viewerSettings.textureScale)); 156 | //d_GlWindow->redraw (); 157 | } 158 | 159 | void QtGuiApplication1::onImportTexture() 160 | { 161 | try 162 | { 163 | const QString filename = getOpenFileImagePath(this); 164 | if(filename.isEmpty()) 165 | return; 166 | 167 | const QImage qiOriginal = getOpenImage(filename); // may throw 168 | 169 | studiohdr_t *phdr = g_studioModel.getTextureHeader (); 170 | if (!phdr) 171 | throw std::runtime_error("studiohdr_t *phdr not available"); 172 | 173 | mstudiotexture_t *ptexture = (mstudiotexture_t *) ((byte *) phdr + phdr->textureindex) + g_viewerSettings.texture; 174 | 175 | const QImage qi = qiOriginal.scaled(ptexture->width, ptexture->height).convertToFormat(QImage::Format::Format_Indexed8); 176 | if(qi.isNull()) 177 | throw std::runtime_error("Can not transform texture"); 178 | 179 | std::copy_n (qi.bits(), ptexture->width * ptexture->height, ((byte *) phdr + ptexture->index)); 180 | 181 | // RGB_RGB_RGB_ => RGBRGB 182 | QVector qv = qi.colorTable(); 183 | for(std::size_t i = 0; i< 256; ++i) 184 | { 185 | byte *p = (byte *) phdr + ptexture->index + ptexture->width * ptexture->height + 3 * i; 186 | QColor color = qv[i]; 187 | p[0] = static_cast(color.red()); 188 | p[1] = static_cast(color.green()); 189 | p[2] = static_cast(color.blue()); 190 | } 191 | 192 | g_studioModel.UploadTexture (ptexture, (byte *) phdr + ptexture->index, (byte *) phdr + ptexture->index + ptexture->width * ptexture->height, g_viewerSettings.texture + 3); 193 | 194 | ui.openglwidget->update (); 195 | } 196 | catch(const std::exception &e) 197 | { 198 | return QMessageBox::critical(this, "Error", e.what()), void(); 199 | } 200 | } 201 | 202 | void QtGuiApplication1::onExportTexture() 203 | { 204 | try 205 | { 206 | studiohdr_t *phdr = g_studioModel.getTextureHeader (); 207 | phdr ? void() : throw std::runtime_error("studiohdr_t *phdr not available"); 208 | 209 | mstudiotexture_t *ptexture = (mstudiotexture_t *) ((byte *) phdr + phdr->textureindex) + g_viewerSettings.texture; 210 | ptexture ? void() : throw std::runtime_error("mstudiotexture_t *ptexture not available"); 211 | 212 | QString filename = getSaveFileImagePath(this, {}, ptexture->name); 213 | if(filename.isEmpty()) 214 | return; 215 | 216 | QImage qi( ((byte *) phdr + ptexture->index), 217 | ptexture->width, 218 | ptexture->height, 219 | QImage::Format::Format_Indexed8 220 | ); 221 | qi.isNull() ? void() : throw std::runtime_error("cannot convert to QImage"); 222 | 223 | // RGBRGB => RGB_RGB_RGB_ 224 | { 225 | QVector qv(256); 226 | for(std::size_t i = 0; i< 256; ++i) 227 | { 228 | const byte *p = (byte *) phdr + ptexture->index + ptexture->width * ptexture->height + 3 * i; 229 | QRgb rgb = QColor(p[0], p[1], p[2]).rgb(); 230 | qv[i] = rgb; 231 | } 232 | qi.setColorTable(std::move(qv)); 233 | } 234 | 235 | saveImageTo(qi, filename); 236 | } 237 | catch (const std::exception & e) 238 | { 239 | return QMessageBox::critical(this, "Error", e.what()), void(); 240 | } 241 | } -------------------------------------------------------------------------------- /src/c/qt_image.cpp: -------------------------------------------------------------------------------- 1 | #include "qt_image.h" 2 | 3 | #include "TGAlib.h" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | QString getOpenFileImagePath(QWidget *parent, const QString &caption, const QString &dir) 10 | { 11 | return QFileDialog::getOpenFileName(parent, caption, dir, "Image file(*.bmp;*.jpg;*.png;*.tiff;*.gif;*.tga);;BMP file (*.bmp);;JPG file (*.jpg);;PNG file (*.png);;TIFF file (*.tiff);;GIF file (*.gif);;TGA file (*.tga)"); 12 | } 13 | 14 | QString getSaveFileImagePath(QWidget *parent, const QString &caption, const QString &dir) 15 | { 16 | return QFileDialog::getSaveFileName(parent, caption, dir, "BMP file (*.bmp);;JPG file (*.jpg);;PNG file (*.png);;TIFF file (*.tiff);;GIF file(*.gif)"); 17 | } 18 | 19 | QImage getOpenImage(const QString &filename) noexcept(false) 20 | { 21 | if(filename.isEmpty()) 22 | throw std::runtime_error("empty path"); 23 | 24 | const QFileInfo file(filename); 25 | if(!file.exists()) 26 | throw std::runtime_error("Image not exists : " + filename.toStdString()); 27 | 28 | if(!file.suffix().compare("tga", Qt::CaseInsensitive)) 29 | { 30 | // note that const char * will expire on next line 31 | std::unique_ptr tga( 32 | tgaLoad(file.absoluteFilePath().toStdString().c_str()), 33 | +[](void *p){tgaDestroy(reinterpret_cast(p));} 34 | ); 35 | 36 | if(!tga || tga->status != TGA_OK) 37 | throw std::runtime_error("Error reading tga file"); 38 | 39 | QImage::Format format = QImage::Format_Invalid; 40 | if(tga->pixelDepth == 32) 41 | format = QImage::Format_RGBA8888; 42 | else if(tga->pixelDepth == 24) 43 | format = QImage::Format_RGB888; 44 | 45 | if(format == QImage::Format_Invalid) 46 | throw std::runtime_error("TGA format not supported"); 47 | 48 | return QImage(tga->imageData, tga->width, tga->height, format, tga.get_deleter(), tga.release()); 49 | } 50 | else 51 | { 52 | const QImage qiOriginal(file.absoluteFilePath()); 53 | if(qiOriginal.isNull()) 54 | throw std::runtime_error("Image not available"); 55 | 56 | return qiOriginal; 57 | } 58 | } 59 | 60 | void saveImageTo(const QImage& what, const QString &filename) noexcept(false) 61 | { 62 | return what.save(filename) ? void() : throw("Error writing texture to : " + filename.toStdString()); 63 | } 64 | -------------------------------------------------------------------------------- /src/c/qt_image.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef HLMV_QT_IMAGE_H 3 | #define HLMV_QT_IMAGE_H 4 | 5 | #include 6 | 7 | QString getOpenFileImagePath(QWidget *parent = Q_NULLPTR, 8 | const QString &caption = QString(), 9 | const QString &dir = QString()); 10 | 11 | QString getSaveFileImagePath(QWidget *parent = Q_NULLPTR, 12 | const QString &caption = QString(), 13 | const QString &dir = QString()); 14 | 15 | QImage getOpenImage(const QString &filename) noexcept(false); 16 | void saveImageTo(const QImage& what, const QString &filename) noexcept(false); 17 | 18 | #endif //HLMV_QT_IMAGE_H 19 | -------------------------------------------------------------------------------- /src/gl.h: -------------------------------------------------------------------------------- 1 | #include -------------------------------------------------------------------------------- /src/m/IceKey.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "IceKey.hpp" 3 | 4 | struct TIceSubKey 5 | { 6 | uint32_t dwValue[3]; 7 | }; 8 | 9 | static bool g_bIceSBoxesInitialised = false; 10 | static uint32_t g_pdwIceSBox[4][1024]; 11 | 12 | static const int g_piIceSMod[4][4] = 13 | { 14 | { 333, 313, 505, 369 }, 15 | { 379, 375, 319, 391 }, 16 | { 361, 445, 451, 397 }, 17 | { 397, 425, 395, 505 } 18 | }; 19 | 20 | static const int g_piIceSXor[4][4] = 21 | { 22 | { 0x83, 0x85, 0x9b, 0xcd }, 23 | { 0xcc, 0xa7, 0xad, 0x41 }, 24 | { 0x4b, 0x2e, 0xd4, 0x33 }, 25 | { 0xea, 0xcb, 0x2e, 0x04 } 26 | }; 27 | 28 | static const uint32_t g_pdwIcePBox[32] = 29 | { 30 | 0x00000001, 0x00000080, 0x00000400, 0x00002000, 31 | 0x00080000, 0x00200000, 0x01000000, 0x40000000, 32 | 0x00000008, 0x00000020, 0x00000100, 0x00004000, 33 | 0x00010000, 0x00800000, 0x04000000, 0x20000000, 34 | 0x00000004, 0x00000010, 0x00000200, 0x00008000, 35 | 0x00020000, 0x00400000, 0x08000000, 0x10000000, 36 | 0x00000002, 0x00000040, 0x00000800, 0x00001000, 37 | 0x00040000, 0x00100000, 0x02000000, 0x80000000 38 | }; 39 | 40 | static const int g_piIceKeyRotation[16] = 41 | { 42 | 0, 1, 2, 3, 2, 1, 3, 0, 43 | 1, 3, 2, 0, 3, 1, 0, 2 44 | }; 45 | 46 | static uint32_t GFMultiply(uint32_t a, uint32_t b, uint32_t m) 47 | { 48 | uint32_t res = 0; 49 | 50 | while (b) 51 | { 52 | if (b & 1) 53 | res ^= a; 54 | 55 | a <<= 1; 56 | b >>= 1; 57 | 58 | if (a >= 256) 59 | a ^= m; 60 | } 61 | 62 | return res; 63 | } 64 | 65 | static uint32_t GFExp7(uint32_t b, uint32_t m) 66 | { 67 | uint32_t x; 68 | 69 | if (b == 0) 70 | return 0; 71 | 72 | x = GFMultiply(b, b, m); 73 | x = GFMultiply(b, x, m); 74 | x = GFMultiply(x, x, m); 75 | return GFMultiply(b, x, m); 76 | } 77 | 78 | static uint32_t IcePerm32(uint32_t x) 79 | { 80 | uint32_t res = 0; 81 | const uint32_t *pbox = g_pdwIcePBox; 82 | 83 | while (x) 84 | { 85 | if (x & 1) 86 | res |= *pbox; 87 | 88 | pbox++; 89 | x >>= 1; 90 | } 91 | 92 | return res; 93 | } 94 | 95 | static void IceSBoxesInitialize(void) 96 | { 97 | int i; 98 | 99 | for (i = 0; i < 1024; ++i) 100 | { 101 | int col = (i >> 1) & 0xff; 102 | int row = (i & 0x1) | ((i & 0x200) >> 8); 103 | uint32_t x; 104 | 105 | x = GFExp7(col ^ g_piIceSXor[0][row], g_piIceSMod[0][row]) << 24; 106 | g_pdwIceSBox[0][i] = IcePerm32(x); 107 | 108 | x = GFExp7(col ^ g_piIceSXor[1][row], g_piIceSMod[1][row]) << 16; 109 | g_pdwIceSBox[1][i] = IcePerm32(x); 110 | 111 | x = GFExp7(col ^ g_piIceSXor[2][row], g_piIceSMod[2][row]) << 8; 112 | g_pdwIceSBox[2][i] = IcePerm32(x); 113 | 114 | x = GFExp7(col ^ g_piIceSXor[3][row], g_piIceSMod[3][row]); 115 | g_pdwIceSBox[3][i] = IcePerm32(x); 116 | } 117 | } 118 | 119 | static uint32_t IceFunction(uint32_t dwValue, const TIceSubKey *sk) 120 | { 121 | uint32_t tl, tr; 122 | uint32_t al, ar; 123 | 124 | tl = ((dwValue >> 16) & 0x3ff) | (((dwValue >> 14) | (dwValue << 18)) & 0xffc00); 125 | tr = (dwValue & 0x3ff) | ((dwValue << 2) & 0xffc00); 126 | 127 | al = sk->dwValue[2] & (tl ^ tr); 128 | ar = al ^ tr; 129 | al ^= tl; 130 | 131 | al ^= sk->dwValue[0]; 132 | ar ^= sk->dwValue[1]; 133 | 134 | return g_pdwIceSBox[0][al >> 10] | g_pdwIceSBox[1][al & 0x3ff] | g_pdwIceSBox[2][ar >> 10] | g_pdwIceSBox[3][ar & 0x3ff]; 135 | } 136 | 137 | CIceKey::CIceKey(int n) : m_iSize(0), m_iRounds(0) 138 | { 139 | if (!g_bIceSBoxesInitialised) 140 | { 141 | IceSBoxesInitialize(); 142 | g_bIceSBoxesInitialised = 1; 143 | } 144 | 145 | if (n < 1) 146 | { 147 | m_iSize = 1; 148 | m_iRounds = 8; 149 | } 150 | else 151 | { 152 | m_iSize = n; 153 | m_iRounds = n * 16; 154 | } 155 | 156 | m_pKeySchedule = new TIceSubKey[m_iRounds]; 157 | } 158 | 159 | CIceKey::~CIceKey(void) 160 | { 161 | int i, j; 162 | 163 | for (i = 0; i < m_iRounds; ++i) 164 | { 165 | for (j = 0; j < 3; ++j) 166 | m_pKeySchedule[i].dwValue[j] = 0; 167 | } 168 | 169 | m_iRounds = m_iSize = 0; 170 | 171 | delete[] m_pKeySchedule; 172 | } 173 | 174 | void CIceKey::BuildSchedule(unsigned short *usKeyBuilder, int n, const int *cpiKeyRotation) 175 | { 176 | int i, j, k; 177 | 178 | for (i = 0; i < 8; ++i) 179 | { 180 | int iKeyRotation = cpiKeyRotation[i]; 181 | TIceSubKey *pSubKey = &m_pKeySchedule[n + i]; 182 | 183 | for (j = 0; j < 3; ++j) 184 | pSubKey->dwValue[j] = 0; 185 | 186 | for (j = 0; j < 15; ++j) 187 | { 188 | uint32_t *pdwCurrentSubKey = &pSubKey->dwValue[j % 3]; 189 | 190 | for (k = 0; k < 4; ++k) 191 | { 192 | unsigned short *pusCurrentKeyBuilder = &usKeyBuilder[(iKeyRotation + k) & 3]; 193 | unsigned short bit = *pusCurrentKeyBuilder & 1; 194 | 195 | *pdwCurrentSubKey = (*pdwCurrentSubKey << 1) | bit; 196 | *pusCurrentKeyBuilder = (*pusCurrentKeyBuilder >> 1) | ((bit ^ 1) << 15); 197 | } 198 | } 199 | } 200 | } 201 | 202 | void CIceKey::SetKey(const uint8_t *pKey) 203 | { 204 | int i, j; 205 | unsigned short pusKeyBuilder[4]; 206 | 207 | if (m_iRounds == 8) 208 | { 209 | for (i = 0; i < 4; ++i) 210 | pusKeyBuilder[3 - i] = (pKey[i << 1] << 8) | pKey[(i << 1) + 1]; 211 | 212 | this->BuildSchedule(pusKeyBuilder, 0, g_piIceKeyRotation); 213 | return; 214 | } 215 | 216 | for (i = 0; i < m_iSize; ++i) 217 | { 218 | for (j = 0; j < 4; ++j) 219 | pusKeyBuilder[3 - j] = (pKey[(i << 3) + (j << 1)] << 8) | pKey[(i << 3) + (j << 1) + 1]; 220 | 221 | this->BuildSchedule(pusKeyBuilder, i << 3, g_piIceKeyRotation); 222 | this->BuildSchedule(pusKeyBuilder, m_iRounds - 8 - (i << 3), &g_piIceKeyRotation[8]); 223 | } 224 | } 225 | 226 | void CIceKey::Encrypt(const uint8_t pPlainText[8], uint8_t pCipherText[8]) const 227 | { 228 | int i; 229 | uint32_t l, r; 230 | 231 | l = (((uint32_t)pPlainText[0]) << 24) | (((uint32_t)pPlainText[1]) << 16) | (((uint32_t)pPlainText[2]) << 8) | pPlainText[3]; 232 | r = (((uint32_t)pPlainText[4]) << 24) | (((uint32_t)pPlainText[5]) << 16) | (((uint32_t)pPlainText[6]) << 8) | pPlainText[7]; 233 | 234 | for (i = 0; i < m_iRounds; i += 2) 235 | { 236 | l ^= IceFunction(r, &m_pKeySchedule[i]); 237 | r ^= IceFunction(l, &m_pKeySchedule[i + 1]); 238 | } 239 | 240 | for (i = 0; i < 4; ++i) 241 | { 242 | pCipherText[3 - i] = (uint8_t)(r & 0xff); 243 | pCipherText[7 - i] = (uint8_t)(l & 0xff); 244 | 245 | r >>= 8; 246 | l >>= 8; 247 | } 248 | } 249 | 250 | void CIceKey::Decrypt(const uint8_t pCipherText[8], uint8_t pPlainText[8]) const 251 | { 252 | int i; 253 | uint32_t l, r; 254 | 255 | l = (((uint32_t)pCipherText[0]) << 24) | (((uint32_t)pCipherText[1]) << 16) | (((uint32_t)pCipherText[2]) << 8) | pCipherText[3]; 256 | r = (((uint32_t)pCipherText[4]) << 24) | (((uint32_t)pCipherText[5]) << 16) | (((uint32_t)pCipherText[6]) << 8) | pCipherText[7]; 257 | 258 | for (i = m_iRounds - 1; i > 0; i -= 2) 259 | { 260 | l ^= IceFunction(r, &m_pKeySchedule[i]); 261 | r ^= IceFunction(l, &m_pKeySchedule[i - 1]); 262 | } 263 | 264 | for (i = 0; i < 4; ++i) 265 | { 266 | pPlainText[3 - i] = (uint8_t)(r & 0xff); 267 | pPlainText[7 - i] = (uint8_t)(l & 0xff); 268 | 269 | r >>= 8; 270 | l >>= 8; 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /src/m/IceKey.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "stdint.h" 4 | 5 | struct TIceSubKey; 6 | 7 | class CIceKey 8 | { 9 | public: 10 | CIceKey(int n = 0); 11 | ~CIceKey(void); 12 | 13 | void SetKey(const uint8_t *pKey); 14 | 15 | void Encrypt(const uint8_t pPlainText[8], uint8_t pCipherText[8]) const; 16 | void Decrypt(const uint8_t pCipherText[8], uint8_t pPlainText[8]) const; 17 | 18 | int GetKeySize() const { return m_iSize << 3; } 19 | static int GetBlockSize() { return 8; } 20 | 21 | private: 22 | void BuildSchedule(unsigned short *usKeyBuilder, int n, const int *cpiKeyRotation); 23 | 24 | int m_iSize; 25 | int m_iRounds; 26 | TIceSubKey *m_pKeySchedule; 27 | }; 28 | -------------------------------------------------------------------------------- /src/m/StudioModel.h: -------------------------------------------------------------------------------- 1 | /*** 2 | * 3 | * Copyright (c) 1998, Valve LLC. All rights reserved. 4 | * 5 | * This product contains software technology licensed from Id 6 | * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. 7 | * All Rights Reserved. 8 | * 9 | ****/ 10 | 11 | #ifndef INCLUDED_STUDIOMODEL 12 | #define INCLUDED_STUDIOMODEL 13 | 14 | 15 | 16 | #ifndef byte 17 | typedef unsigned char byte; 18 | #endif // byte 19 | 20 | 21 | 22 | #include "mathlib.h" 23 | #include "studio.h" 24 | 25 | 26 | 27 | class StudioModel 28 | { 29 | public: 30 | studiohdr_t *getStudioHeader () const { return m_pstudiohdr; } 31 | studiohdr_t *getTextureHeader () const { return m_ptexturehdr; } 32 | studiohdr_t *getAnimHeader (int i) const { return m_panimhdr[i]; } 33 | 34 | void UploadTexture( const mstudiotexture_t *ptexture, const byte *data, const byte *pal, int name ); 35 | bool UploadTextureTGA( mstudiotexture_t *ptexture, const char *path, int name ); 36 | bool UploadTextureBMP( mstudiotexture_t *ptexture, const char *path, int name ); 37 | void FreeModel (); 38 | 39 | studiohdr_t *LoadModel( const char *modelname ); 40 | void LoadModelTextures( const studiohdr_t *phdr ); 41 | void LoadModelTexturesCSO( studiohdr_t *phdr, const char *texturePath ); 42 | bool PostLoadModel ( studiohdr_t *phdr, const char *modelname ); 43 | 44 | bool SaveModel ( const char *modelname ); 45 | void DrawModel( void ); 46 | void AdvanceFrame( float dt ); 47 | int SetFrame (int nFrame); 48 | 49 | void ExtractBbox( float *mins, float *maxs ); 50 | 51 | int SetSequence( int iSequence ); 52 | int GetSequence( void ); 53 | void GetSequenceInfo( float *pflFrameRate, float *pflGroundSpeed ); 54 | 55 | float SetController( int iController, float flValue ); 56 | float SetMouth( float flValue ); 57 | float SetBlending( int iBlender, float flValue ); 58 | int SetBodygroup( int iGroup, int iValue ); 59 | int SetSkin( int iValue ); 60 | 61 | void scaleMeshes (float scale); 62 | void scaleBones (float scale); 63 | 64 | static bool hasCSOTexture(const studiohdr_t *phdr); 65 | 66 | private: 67 | // entity settings 68 | vec3_t m_origin; 69 | vec3_t m_angles; 70 | int m_sequence; // sequence index 71 | float m_frame; // frame 72 | int m_bodynum; // bodypart selection 73 | int m_skinnum; // skin group selection 74 | byte m_controller[4]; // bone controllers 75 | byte m_blending[2]; // animation blending 76 | byte m_mouth; // mouth position 77 | bool m_owntexmodel; // do we have a modelT.mdl ? 78 | 79 | // internal data 80 | studiohdr_t *m_pstudiohdr; 81 | mstudiomodel_t *m_pmodel; 82 | 83 | studiohdr_t *m_ptexturehdr; 84 | studiohdr_t *m_panimhdr[32]; 85 | 86 | vec4_t m_adj; // FIX: non persistant, make static 87 | 88 | void CalcBoneAdj( void ); 89 | void CalcBoneQuaternion( int frame, float s, mstudiobone_t *pbone, mstudioanim_t *panim, float *q ); 90 | void CalcBonePosition( int frame, float s, mstudiobone_t *pbone, mstudioanim_t *panim, float *pos ); 91 | void CalcRotations ( vec3_t *pos, vec4_t *q, mstudioseqdesc_t *pseqdesc, mstudioanim_t *panim, float f ); 92 | mstudioanim_t *GetAnim( mstudioseqdesc_t *pseqdesc ); 93 | void SlerpBones( vec4_t q1[], vec3_t pos1[], vec4_t q2[], vec3_t pos2[], float s ); 94 | void SetUpBones ( void ); 95 | 96 | void DrawPoints( void ); 97 | 98 | void Lighting (float *lv, int bone, int flags, vec3_t normal); 99 | void Chrome (int *chrome, int bone, vec3_t normal); 100 | 101 | void SetupLighting( void ); 102 | 103 | void SetupModel ( int bodypart ); 104 | 105 | static bool isCSOExternalTexture(const mstudiotexture_t &ptexture); 106 | }; 107 | 108 | 109 | 110 | extern vec3_t g_vright; // needs to be set to viewer's right in order for chrome to work 111 | extern float g_lambert; // modifier for pseudo-hemispherical lighting 112 | extern StudioModel g_studioModel; 113 | 114 | 115 | 116 | #endif // INCLUDED_STUDIOMODEL -------------------------------------------------------------------------------- /src/m/TGAlib.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // TGA lib for cocos2d-iphone 3 | // 4 | // sources from: http://www.lighthouse3d.com/opengl/terrain/index.php3?tgasource 5 | // 6 | // TGA RLE compression support by Ernesto Corvi 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include "TGAlib.h" 13 | 14 | void tgaLoadRLEImageData(FILE *file, tImageTGA *info); 15 | void tgaFlipImage( tImageTGA *info ); 16 | 17 | // load the image header fields. We only keep those that matter! 18 | void tgaLoadHeader(FILE *file, tImageTGA *info) { 19 | unsigned char cGarbage; 20 | short int iGarbage; 21 | 22 | fread(&cGarbage, sizeof(unsigned char), 1, file); 23 | fread(&cGarbage, sizeof(unsigned char), 1, file); 24 | 25 | // type must be 2 or 3 26 | fread(&info->type, sizeof(unsigned char), 1, file); 27 | 28 | fread(&iGarbage, sizeof(short int), 1, file); 29 | fread(&iGarbage, sizeof(short int), 1, file); 30 | fread(&cGarbage, sizeof(unsigned char), 1, file); 31 | fread(&iGarbage, sizeof(short int), 1, file); 32 | fread(&iGarbage, sizeof(short int), 1, file); 33 | 34 | fread(&info->width, sizeof(short int), 1, file); 35 | fread(&info->height, sizeof(short int), 1, file); 36 | fread(&info->pixelDepth, sizeof(unsigned char), 1, file); 37 | 38 | fread(&cGarbage, sizeof(unsigned char), 1, file); 39 | 40 | info->flipped = 0; 41 | if ( cGarbage & 0x20 ) info->flipped = 1; 42 | } 43 | 44 | // loads the image pixels. You shouldn't call this function directly 45 | void tgaLoadImageData(FILE *file, tImageTGA *info) { 46 | 47 | int mode,total,i; 48 | unsigned char aux; 49 | 50 | // mode equal the number of components for each pixel 51 | mode = info->pixelDepth / 8; 52 | // total is the number of unsigned chars we'll have to read 53 | total = info->height * info->width * mode; 54 | 55 | fread(info->imageData,sizeof(unsigned char),total,file); 56 | 57 | // mode=3 or 4 implies that the image is RGB(A). However TGA 58 | // stores it as BGR(A) so we'll have to swap R and B. 59 | if (mode >= 3) 60 | for (i=0; i < total; i+= mode) { 61 | aux = info->imageData[i]; 62 | info->imageData[i] = info->imageData[i+2]; 63 | info->imageData[i+2] = aux; 64 | } 65 | } 66 | 67 | // loads the RLE encoded image pixels. You shouldn't call this function directly 68 | void tgaLoadRLEImageData(FILE *file, tImageTGA *info) 69 | { 70 | unsigned int mode,total,i, index = 0; 71 | unsigned char aux[4], runlength = 0; 72 | unsigned int skip = 0, flag = 0; 73 | 74 | // mode equal the number of components for each pixel 75 | mode = info->pixelDepth / 8; 76 | // total is the number of unsigned chars we'll have to read 77 | total = info->height * info->width; 78 | 79 | for( i = 0; i < total; i++ ) 80 | { 81 | // if we have a run length pending, run it 82 | if ( runlength != 0 ) 83 | { 84 | // we do, update the run length count 85 | runlength--; 86 | skip = (flag != 0); 87 | } 88 | else 89 | { 90 | // otherwise, read in the run length token 91 | if ( fread(&runlength,sizeof(unsigned char),1,file) != 1 ) 92 | return; 93 | 94 | // see if it's a RLE encoded sequence 95 | flag = runlength & 0x80; 96 | if ( flag ) runlength -= 128; 97 | skip = 0; 98 | } 99 | 100 | // do we need to skip reading this pixel? 101 | if ( !skip ) 102 | { 103 | // no, read in the pixel data 104 | if ( fread(aux,sizeof(unsigned char),mode,file) != mode ) 105 | return; 106 | 107 | // mode=3 or 4 implies that the image is RGB(A). However TGA 108 | // stores it as BGR(A) so we'll have to swap R and B. 109 | if ( mode >= 3 ) 110 | { 111 | unsigned char tmp; 112 | 113 | tmp = aux[0]; 114 | aux[0] = aux[2]; 115 | aux[2] = tmp; 116 | } 117 | } 118 | 119 | // add the pixel to our image 120 | memcpy(&info->imageData[index], aux, mode); 121 | index += mode; 122 | } 123 | } 124 | 125 | void tgaFlipImage( tImageTGA *info ) 126 | { 127 | // mode equal the number of components for each pixel 128 | int mode = info->pixelDepth / 8; 129 | int rowbytes = info->width*mode; 130 | unsigned char *row = (unsigned char *)malloc(rowbytes); 131 | int y; 132 | 133 | if (row == NULL) return; 134 | 135 | for( y = 0; y < (info->height/2); y++ ) 136 | { 137 | memcpy(row, &info->imageData[y*rowbytes],rowbytes); 138 | memcpy(&info->imageData[y*rowbytes], &info->imageData[(info->height-(y+1))*rowbytes], rowbytes); 139 | memcpy(&info->imageData[(info->height-(y+1))*rowbytes], row, rowbytes); 140 | } 141 | 142 | free(row); 143 | info->flipped = 0; 144 | } 145 | 146 | // this is the function to call when we want to load an image 147 | tImageTGA * tgaLoad(const char *filename) { 148 | 149 | FILE *file; 150 | tImageTGA *info; 151 | int mode,total; 152 | 153 | // allocate memory for the info struct and check! 154 | info = (tImageTGA *)malloc(sizeof(tImageTGA)); 155 | if (info == NULL) 156 | return(NULL); 157 | 158 | 159 | // open the file for reading (binary mode) 160 | file = fopen(filename, "rb"); 161 | if (file == NULL) { 162 | info->status = TGA_ERROR_FILE_OPEN; 163 | return(info); 164 | } 165 | 166 | // load the header 167 | tgaLoadHeader(file,info); 168 | 169 | // check for errors when loading the header 170 | if (ferror(file)) { 171 | info->status = TGA_ERROR_READING_FILE; 172 | fclose(file); 173 | return(info); 174 | } 175 | 176 | // check if the image is color indexed 177 | if (info->type == 1) { 178 | info->status = TGA_ERROR_INDEXED_COLOR; 179 | fclose(file); 180 | return(info); 181 | } 182 | // check for other types (compressed images) 183 | if ((info->type != 2) && (info->type !=3) && (info->type !=10) ) { 184 | info->status = TGA_ERROR_COMPRESSED_FILE; 185 | fclose(file); 186 | return(info); 187 | } 188 | 189 | // mode equals the number of image components 190 | mode = info->pixelDepth / 8; 191 | // total is the number of unsigned chars to read 192 | total = info->height * info->width * mode; 193 | // allocate memory for image pixels 194 | info->imageData = (unsigned char *)malloc(sizeof(unsigned char) * 195 | total); 196 | 197 | // check to make sure we have the memory required 198 | if (info->imageData == NULL) { 199 | info->status = TGA_ERROR_MEMORY; 200 | fclose(file); 201 | return(info); 202 | } 203 | // finally load the image pixels 204 | if ( info->type == 10 ) 205 | tgaLoadRLEImageData(file, info); 206 | else 207 | tgaLoadImageData(file,info); 208 | 209 | // check for errors when reading the pixels 210 | if (ferror(file)) { 211 | info->status = TGA_ERROR_READING_FILE; 212 | fclose(file); 213 | return(info); 214 | } 215 | fclose(file); 216 | info->status = TGA_OK; 217 | 218 | if ( info->flipped ) 219 | { 220 | tgaFlipImage( info ); 221 | if ( info->flipped ) info->status = TGA_ERROR_MEMORY; 222 | } 223 | 224 | return(info); 225 | } 226 | 227 | // converts RGB to greyscale 228 | void tgaRGBtogreyscale(tImageTGA *info) { 229 | 230 | int mode,i,j; 231 | 232 | unsigned char *newImageData; 233 | 234 | // if the image is already greyscale do nothing 235 | if (info->pixelDepth == 8) 236 | return; 237 | 238 | // compute the number of actual components 239 | mode = info->pixelDepth / 8; 240 | 241 | // allocate an array for the new image data 242 | newImageData = (unsigned char *)malloc(sizeof(unsigned char) * 243 | info->height * info->width); 244 | if (newImageData == NULL) { 245 | return; 246 | } 247 | 248 | // convert pixels: greyscale = o.30 * R + 0.59 * G + 0.11 * B 249 | for (i = 0,j = 0; j < info->width * info->height; i +=mode, j++) 250 | newImageData[j] = 251 | (unsigned char)(0.30 * info->imageData[i] + 252 | 0.59 * info->imageData[i+1] + 253 | 0.11 * info->imageData[i+2]); 254 | 255 | 256 | //free old image data 257 | free(info->imageData); 258 | 259 | // reassign pixelDepth and type according to the new image type 260 | info->pixelDepth = 8; 261 | info->type = 3; 262 | // reassing imageData to the new array. 263 | info->imageData = newImageData; 264 | } 265 | 266 | // releases the memory used for the image 267 | void tgaDestroy(tImageTGA *info) { 268 | 269 | if (info != NULL) { 270 | if (info->imageData != NULL) 271 | free(info->imageData); 272 | free(info); 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /src/m/TGAlib.h: -------------------------------------------------------------------------------- 1 | // 2 | // TGA lib for cocos2d-iphone 3 | // 4 | // sources from: http://www.lighthouse3d.com/opengl/terrain/index.php3?tgasource 5 | // 6 | 7 | //#ifndef TGA_LIB 8 | //#define TGA_LIB 9 | 10 | /** 11 | @file 12 | TGA image support 13 | */ 14 | 15 | enum { 16 | TGA_OK, 17 | TGA_ERROR_FILE_OPEN, 18 | TGA_ERROR_READING_FILE, 19 | TGA_ERROR_INDEXED_COLOR, 20 | TGA_ERROR_MEMORY, 21 | TGA_ERROR_COMPRESSED_FILE, 22 | }; 23 | 24 | /** TGA format */ 25 | typedef struct sImageTGA { 26 | int status; 27 | unsigned char type, pixelDepth; 28 | 29 | /** map width */ 30 | short int width; 31 | 32 | /** map height */ 33 | short int height; 34 | 35 | /** raw data */ 36 | unsigned char *imageData; 37 | int flipped; 38 | } tImageTGA; 39 | 40 | /// load the image header fields. We only keep those that matter! 41 | void tgaLoadHeader(FILE *file, tImageTGA *info); 42 | 43 | /// loads the image pixels. You shouldn't call this function directly 44 | void tgaLoadImageData(FILE *file, tImageTGA *info); 45 | 46 | /// this is the function to call when we want to load an image 47 | tImageTGA * tgaLoad(const char *filename); 48 | 49 | // /converts RGB to greyscale 50 | void tgaRGBtogreyscale(tImageTGA *info); 51 | 52 | /// releases the memory used for the image 53 | void tgaDestroy(tImageTGA *info); 54 | 55 | //#endif // TGA_LIB 56 | -------------------------------------------------------------------------------- /src/m/ViewerSettings.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Half-Life Model Viewer (c) 1999 by Mete Ciragan 3 | // 4 | // file: ViewerSettings.cpp 5 | // last modified: May 29 1999, Mete Ciragan 6 | // copyright: The programs and associated files contained in this 7 | // distribution were developed by Mete Ciragan. The programs 8 | // are not in the public domain, but they are freely 9 | // distributable without licensing fees. These programs are 10 | // provided without guarantee or warrantee expressed or 11 | // implied. 12 | // 13 | // version: 1.2 14 | // 15 | // email: mete@swissquake.ch 16 | // web: http://www.swissquake.ch/chumbalum-soft/ 17 | // 18 | #include "ViewerSettings.h" 19 | #include 20 | #include 21 | #include 22 | 23 | 24 | 25 | ViewerSettings g_viewerSettings; 26 | 27 | 28 | 29 | void 30 | InitViewerSettings (void) 31 | { 32 | g_viewerSettings.rot[0] = -90.0f; 33 | g_viewerSettings.trans[2] = 50.0f; 34 | g_viewerSettings.renderMode = RM_TEXTURED; 35 | g_viewerSettings.transparency = 1.0f; 36 | g_viewerSettings.use3dfx = true; 37 | 38 | g_viewerSettings.yaw = 65.0f; 39 | g_viewerSettings.bgColor[0] = 0.5f; // r 40 | g_viewerSettings.bgColor[1] = 0.75f; // g 41 | g_viewerSettings.bgColor[2] = 0.75f; // b 42 | g_viewerSettings.bgColor[3] = 1.0f; // a 43 | 44 | g_viewerSettings.gColor[0] = 0.85f; 45 | g_viewerSettings.gColor[1] = 0.85f; 46 | g_viewerSettings.gColor[2] = 0.69f; 47 | 48 | g_viewerSettings.lColor[0] = 1.0f; 49 | g_viewerSettings.lColor[1] = 1.0f; 50 | g_viewerSettings.lColor[2] = 1.0f; 51 | 52 | g_viewerSettings.guColor[0] = 1.0f; 53 | g_viewerSettings.guColor[1] = 0.0f; 54 | g_viewerSettings.guColor[2] = 0.0f; 55 | 56 | g_viewerSettings.speedScale = 1.0f; 57 | g_viewerSettings.textureLimit = 256; 58 | 59 | g_viewerSettings.textureScale = 1.0f; 60 | 61 | g_viewerSettings.vieworiginmode = false; 62 | g_viewerSettings.viewfov = 90.0f; 63 | g_viewerSettings.viewaspect = 0.0f; 64 | 65 | } 66 | 67 | -------------------------------------------------------------------------------- /src/m/ViewerSettings.h: -------------------------------------------------------------------------------- 1 | // 2 | // Half-Life Model Viewer (c) 1999 by Mete Ciragan 3 | // 4 | // file: ViewerSettings.h 5 | // last modified: May 29 1999, Mete Ciragan 6 | // copyright: The programs and associated files contained in this 7 | // distribution were developed by Mete Ciragan. The programs 8 | // are not in the public domain, but they are freely 9 | // distributable without licensing fees. These programs are 10 | // provided without guarantee or warrantee expressed or 11 | // implied. 12 | // 13 | // version: 1.2 14 | // 15 | // email: mete@swissquake.ch 16 | // web: http://www.swissquake.ch/chumbalum-soft/ 17 | // 18 | #ifndef INCLUDED_VIEWERSETTINGS 19 | #define INCLUDED_VIEWERSETTINGS 20 | 21 | 22 | 23 | enum // render modes 24 | { 25 | RM_WIREFRAME, 26 | RM_FLATSHADED, 27 | RM_SMOOTHSHADED, 28 | RM_TEXTURED 29 | }; 30 | 31 | 32 | 33 | typedef struct 34 | { 35 | // model 36 | float rot[3]; 37 | float trans[3]; 38 | float rotOld[3]; 39 | float transOld[3]; 40 | float rotOld2[3]; 41 | float transOld2[3]; 42 | 43 | // render 44 | int renderMode; 45 | float transparency; 46 | bool showBackground; 47 | bool showGround; 48 | bool showHitBoxes; 49 | bool showBones; 50 | bool showTexture; 51 | bool showAttachments; 52 | bool showGuideLines; 53 | bool showCrosshair; 54 | bool showEyePosition; 55 | bool showUVMap; 56 | bool showUVMapOverlay; 57 | bool showWireframeOverlay; 58 | bool showSmoothLines; 59 | int texture; 60 | float textureScale; 61 | int skin; 62 | int mesh; 63 | int meshCount; 64 | bool mirror; 65 | bool useStencil; // if 3dfx fullscreen set false 66 | bool showAllMeshes; 67 | 68 | // animation 69 | int sequence; 70 | float speedScale; 71 | 72 | // bodyparts and bonecontrollers 73 | int submodels[32]; 74 | float controllers[8]; 75 | 76 | // fullscreen 77 | int width, height; 78 | bool use3dfx; 79 | bool cds; 80 | 81 | // colors 82 | float bgColor[4]; 83 | float lColor[4]; 84 | float gColor[4]; 85 | float guColor[4]; 86 | 87 | // Field of View 88 | float yaw; 89 | 90 | // misc 91 | int textureLimit; 92 | bool pause; 93 | 94 | // only used for fullscreen mode 95 | char modelFile[256]; 96 | char backgroundTexFile[256]; 97 | char groundTexFile[256]; 98 | 99 | // QHLMV spec 100 | bool vieworiginmode; 101 | bool righthand; 102 | float vieworigintrans[3]; 103 | float viewfov; 104 | float viewaspect; 105 | } ViewerSettings; 106 | 107 | 108 | 109 | extern ViewerSettings g_viewerSettings; 110 | 111 | 112 | 113 | #ifdef __cplusplus 114 | extern "C" { 115 | #endif 116 | 117 | void InitViewerSettings (void); 118 | 119 | #ifdef __cplusplus 120 | } 121 | #endif 122 | 123 | 124 | 125 | #endif // INCLUDED_VIEWERSETTINGS 126 | -------------------------------------------------------------------------------- /src/m/bmpread.cpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * libbmpread - tiny, fast bitmap (.bmp) image file loader * 3 | * * 4 | * Copyright (C) 2005, 2012, 2016, 2018 Charles Lindsay * 5 | * * 6 | * This software is provided 'as-is', without any express or implied * 7 | * warranty. In no event will the authors be held liable for any damages * 8 | * arising from the use of this software. * 9 | * * 10 | * Permission is granted to anyone to use this software for any purpose, * 11 | * including commercial applications, and to alter it and redistribute it * 12 | * freely, subject to the following restrictions: * 13 | * * 14 | * 1. The origin of this software must not be misrepresented; you must not * 15 | * claim that you wrote the original software. If you use this software * 16 | * in a product, an acknowledgment in the product documentation would be * 17 | * appreciated but is not required. * 18 | * 2. Altered source versions must be plainly marked as such, and must not be * 19 | * misrepresented as being the original software. * 20 | * 3. This notice may not be removed or altered from any source distribution. * 21 | ******************************************************************************/ 22 | 23 | 24 | /* bmpread.c 25 | * version 3.0 26 | * 2018-02-02 27 | */ 28 | 29 | 30 | #include "bmpread.h" 31 | 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | 38 | /* If your compiler doesn't come with stdint.h, which is technically a C99 39 | * feature, see . There are 3rd party 40 | * solutions to this problem, which you should be able to find with a little 41 | * searching. Alternately, just define the following types yourself: uint8_t, 42 | * uint16_t, uint32_t, and int32_t. 43 | */ 44 | #include 45 | 46 | /* This code makes a number of assumptions about a byte being 8 bits, which is 47 | * technically not required by the C spec(s). It's likely that not a whole lot 48 | * here would need to change if CHAR_BIT != 8, but I haven't taken the time to 49 | * figure out exactly what those changes would be. 50 | */ 51 | #if CHAR_BIT != 8 52 | #error "libbmpread requires CHAR_BIT == 8" 53 | #endif 54 | 55 | 56 | /* Default value for alpha when none is present in the file. */ 57 | #define BMPREAD_DEFAULT_ALPHA 255 58 | 59 | /* I've tried to make every effort to remove the possibility of undefined 60 | * behavior and prevent related errors where maliciously crafted files could 61 | * lead to buffer overflows or the like. To that end, we'll start with some 62 | * functions that check various operations for behaving as expected. This one 63 | * returns nonzero if the two size_ts can be added without wrapping, or 0 if 64 | * the result would wrap. 65 | */ 66 | static int CanAdd(size_t a, size_t b) 67 | { 68 | return a <= SIZE_MAX - b; 69 | } 70 | 71 | /* Returns nonzero if the two size_ts can be multiplied without wrapping, or 0 72 | * if the result would wrap. b must not be 0 (we don't even check here since 73 | * everything we pass in will have been checked before). 74 | */ 75 | static int CanMultiply(size_t a, size_t b) 76 | { 77 | return a <= SIZE_MAX / b; 78 | } 79 | 80 | /* Returns nonzero if the uint32_t can be converted to a size_t without losing 81 | * data, which is always the case on 32-bit systems and higher, or 0 if such a 82 | * conversion would lose data, as could happen on 16-bit systems. 83 | */ 84 | static int CanMakeSizeT(uint32_t x) 85 | { 86 | /* The preprocessor guard is there to prevent a warning about the condition 87 | * inside being true by definition on systems where size_t is at least 32 88 | * bits. I'm relying on C's integer promotion rules to make this all safe. 89 | * I *think* it works as intended here (either way, typecasts don't really 90 | * help clarify things, so I've gone without). 91 | */ 92 | #if UINT32_MAX > SIZE_MAX 93 | if(x > SIZE_MAX) return 0; 94 | #endif 95 | 96 | (void)x; /* Sometimes unused; this prevents a pedantic warning. */ 97 | return 1; 98 | } 99 | 100 | /* Returns nonzero if the uint32_t can be converted to a long without losing 101 | * data, or 0 if the conversion would lose data. 102 | */ 103 | static int CanMakeLong(uint32_t x) 104 | { 105 | #if UINT32_MAX > LONG_MAX 106 | if(x > LONG_MAX) return 0; 107 | #endif 108 | 109 | (void)x; /* Sometimes unused. */ 110 | return 1; 111 | } 112 | 113 | /* Returns nonzero if the int32_t can be negated properly. INT32_MIN doesn't 114 | * work because its positive value isn't representable inside an int32_t (given 115 | * two's complement). 116 | */ 117 | static int CanNegate(int32_t x) 118 | { 119 | return x != INT32_MIN; 120 | } 121 | 122 | /* Reads up to 4 little-endian bytes from fp and stores the result in the 123 | * uint32_t pointed to by dest in the host's byte order. Returns 0 on EOF or 124 | * nonzero on success. 125 | */ 126 | static int ReadLittleBytes(uint32_t * dest, int bytes, FILE * fp) 127 | { 128 | uint32_t shift = 0; 129 | 130 | *dest = 0; 131 | 132 | while(bytes--) 133 | { 134 | int byte; 135 | if((byte = fgetc(fp)) == EOF) return 0; 136 | 137 | *dest += (uint32_t)byte << shift; 138 | shift += 8; 139 | } 140 | 141 | return 1; 142 | } 143 | 144 | /* Reads a little-endian uint32_t from fp and stores the result in *dest in the 145 | * host's byte order. Returns 0 on EOF or nonzero on success. 146 | */ 147 | #define ReadLittleUint32(dest, fp) ReadLittleBytes(dest, 4, fp) 148 | 149 | /* Reads a little-endian int32_t from fp and stores the result in *dest in the 150 | * host's byte order. Returns 0 on EOF or nonzero on success. 151 | */ 152 | static int ReadLittleInt32(int32_t * dest, FILE * fp) 153 | { 154 | /* I *believe* casting unsigned -> signed is implementation-defined when 155 | * the unsigned value is out of range for the signed type, which would be 156 | * the case for any negative number we've just read out of the file into a 157 | * uint. This is a portable way to "reinterpret" the bits as signed 158 | * without running into undefined/implementation-defined behavior. I 159 | * think. 160 | */ 161 | union int32_signedness_swap 162 | { 163 | uint32_t uint32; 164 | int32_t int32; 165 | 166 | } t; 167 | 168 | if(!ReadLittleBytes(&t.uint32, 4, fp)) return 0; 169 | *dest = t.int32; 170 | return 1; 171 | } 172 | 173 | /* Reads a little-endian uint16_t from fp and stores the result in *dest in the 174 | * host's byte order. Returns 0 on EOF or nonzero n success. 175 | */ 176 | static int ReadLittleUint16(uint16_t * dest, FILE * fp) 177 | { 178 | uint32_t t; 179 | if(!ReadLittleBytes(&t, 2, fp)) return 0; 180 | *dest = (uint16_t)t; 181 | return 1; 182 | } 183 | 184 | /* Reads a uint8_t from fp and stores the result in *dest. Returns 0 on EOF or 185 | * nonzero on success. 186 | */ 187 | static int ReadUint8(uint8_t * dest, FILE * fp) 188 | { 189 | int byte; 190 | if((byte = fgetc(fp)) == EOF) return 0; 191 | *dest = (uint8_t)byte; 192 | return 1; 193 | } 194 | 195 | /* Bitmap file header, including magic bytes. 196 | */ 197 | typedef struct bmp_header 198 | { 199 | uint8_t magic[2]; /* Magic bytes 'B' and 'M'. */ 200 | uint32_t file_size; /* Size of whole file. */ 201 | uint32_t unused; /* Should be 0. */ 202 | uint32_t data_offset; /* Offset from beginning of file to bitmap data. */ 203 | 204 | } bmp_header; 205 | 206 | /* Reads a bitmap header from fp into header. Returns 0 on EOF or invalid 207 | * header, or nonzero on success. 208 | */ 209 | static int ReadHeader(bmp_header * header, FILE * fp) 210 | { 211 | if(!ReadUint8(&header->magic[0], fp)) return 0; 212 | if(!ReadUint8(&header->magic[1], fp)) return 0; 213 | 214 | /* If it doesn't look like a bitmap header, don't even bother. */ 215 | if(header->magic[0] != 0x42 /* 'B' */) return 0; 216 | if(header->magic[1] != 0x4d /* 'M' */) return 0; 217 | 218 | if(!ReadLittleUint32(&header->file_size, fp)) return 0; 219 | if(!ReadLittleUint32(&header->unused, fp)) return 0; 220 | if(!ReadLittleUint32(&header->data_offset, fp)) return 0; 221 | 222 | return 1; 223 | } 224 | 225 | /* How many bytes in the file are occupied by a header, by definition in the 226 | * spec. Note that even though our definition logically matches the spec's, C 227 | * struct padding/packing rules mean it might not be the same as 228 | * sizeof(bmp_header). 229 | */ 230 | #define BMP_HEADER_SIZE 14 231 | 232 | /* Bitmap info: comes immediately after the header and describes the image. 233 | */ 234 | typedef struct bmp_info 235 | { 236 | uint32_t info_size; /* Size of info struct (> sizeof(bmp_info)). */ 237 | int32_t width; /* Width of image. */ 238 | int32_t height; /* Height (< 0 means right-side up). */ 239 | uint16_t planes; /* Planes (should be 1). */ 240 | uint16_t bits; /* Number of bits (1, 4, 8, 16, 24, or 32). */ 241 | uint32_t compression; /* See COMPRESSION_* values below. */ 242 | uint32_t unused0[3]; /* We don't care about these fields. */ 243 | uint32_t colors; /* How many colors in the palette, 0 = 1<info_size, fp)) return 0; 278 | 279 | /* Older formats might not have all the fields we require, so this check 280 | * comes first. 281 | */ 282 | if(info->info_size < MIN_INFO_SIZE) return 0; 283 | 284 | if(!ReadLittleInt32( &info->width, fp)) return 0; 285 | if(!ReadLittleInt32( &info->height, fp)) return 0; 286 | if(!ReadLittleUint16(&info->planes, fp)) return 0; 287 | if(!ReadLittleUint16(&info->bits, fp)) return 0; 288 | if(!ReadLittleUint32(&info->compression, fp)) return 0; 289 | if(!ReadLittleUint32(&info->unused0[0], fp)) return 0; 290 | if(!ReadLittleUint32(&info->unused0[1], fp)) return 0; 291 | if(!ReadLittleUint32(&info->unused0[2], fp)) return 0; 292 | if(!ReadLittleUint32(&info->colors, fp)) return 0; 293 | if(!ReadLittleUint32(&info->unused1, fp)) return 0; 294 | 295 | /* We don't bother to even try to read bitmasks if they aren't needed, 296 | * since they won't be present in Windows 3 format bitmap files. 297 | */ 298 | if(info->compression == COMPRESSION_BITFIELDS) 299 | { 300 | /* Reject Windows NT format files with bitfields, since we don't 301 | * support them, and their masks aren't part of the info header anyway. 302 | */ 303 | if(info->info_size == BMP3_INFO_SIZE) return 0; 304 | 305 | if(!ReadLittleUint32(&info->masks[0], fp)) return 0; 306 | if(!ReadLittleUint32(&info->masks[1], fp)) return 0; 307 | if(!ReadLittleUint32(&info->masks[2], fp)) return 0; 308 | if(!ReadLittleUint32(&info->masks[3], fp)) return 0; 309 | } 310 | 311 | return 1; 312 | } 313 | 314 | /* Bitfields for 16- and 32-bit files. We track the first set bit (rightmost 315 | * being 0) and how many bits it spans. 316 | */ 317 | typedef struct bitfield 318 | { 319 | uint32_t start; 320 | uint32_t span; 321 | 322 | } bitfield; 323 | 324 | /* Applies a bitfield mask to a value, x. 325 | */ 326 | #define ApplyBitfield(x, bitfield) \ 327 | (((x) >> (bitfield).start) & ((UINT32_C(1) << (bitfield).span) - 1)) 328 | 329 | /* Turns a single mask component into a bitfield. Returns 0 if the bitmask was 330 | * invalid, or nonzero if it's ok. Span of 0 means the bitmask was absent. 331 | */ 332 | static int ParseBitfield(bitfield * field, uint32_t mask) 333 | { 334 | uint32_t bit; 335 | for(bit = 0; bit < 32 && !(mask & (UINT32_C(1) << bit)); bit++) 336 | ; 337 | 338 | if(bit >= 32) 339 | { 340 | /* Absent bitmasks are valid. */ 341 | field->start = field->span = 0; 342 | return 1; 343 | } 344 | 345 | field->start = bit; 346 | for(; bit < 32 && (mask & (UINT32_C(1) << bit)); bit++) 347 | ; 348 | field->span = bit - field->start; 349 | 350 | /* If there are more set bits, there was a gap, which is invalid. */ 351 | if(bit < 32 && (mask & ~((UINT32_C(1) << bit) - 1))) return 0; 352 | 353 | return 1; 354 | } 355 | 356 | /* A single color entry in the palette, in file order (BGR + one unused byte). 357 | */ 358 | typedef struct bmp_color 359 | { 360 | uint8_t blue; 361 | uint8_t green; 362 | uint8_t red; 363 | uint8_t unused; 364 | 365 | } bmp_color; 366 | 367 | /* How many bytes in the file are occupied by a palette entry, by definition in 368 | * the spec (and again note that it might not be the same as 369 | * sizeof(bmp_color), even if we match). 370 | */ 371 | #define BMP_COLOR_SIZE 4 372 | 373 | /* Reads the given number of colors from fp into the palette array. Returns 0 374 | * on EOF or nonzero on success. 375 | */ 376 | static int ReadPalette(bmp_color * palette, int colors, FILE * fp) 377 | { 378 | /* This isn't the guaranteed-fastest way to implement this, but it should 379 | * perform quite well in practice due to compiler optimization and stdio 380 | * input buffering. It's implemented this way because of how simple the 381 | * code is, while avoiding undefined and implementation-defined behavior or 382 | * allocating any memory. If you aren't averse to an extra allocation (or 383 | * using a chunk of the stack), it might be made faster while still 384 | * avoiding implementation-defined behavior by reading the entire palette 385 | * into one big buffer up front, then copying bytes into place. 386 | */ 387 | int i; 388 | for(i = 0; i < colors; i++) 389 | { 390 | uint8_t components[BMP_COLOR_SIZE]; 391 | if(fread(components, 1, sizeof(components), fp) != sizeof(components)) 392 | return 0; 393 | 394 | palette[i].blue = components[0]; 395 | palette[i].green = components[1]; 396 | palette[i].red = components[2]; 397 | palette[i].unused = components[3]; 398 | } 399 | return 1; 400 | } 401 | 402 | /* Context shared between the below functions. 403 | */ 404 | typedef struct read_context 405 | { 406 | unsigned int flags; /* Flags passed to bmpread. */ 407 | FILE * fp; /* File pointer. */ 408 | bmp_header header; /* Bitmap file header. */ 409 | bmp_info info; /* Bitmap file info. */ 410 | uint32_t headers_size; /* Total size of header + info. */ 411 | uint32_t after_headers; /* Size of space for palette. */ 412 | int32_t lines; /* How many scan lines (abs(height)). */ 413 | size_t file_line_len; /* How many bytes each scan line is. */ 414 | size_t out_channels; /* Output color channels (3, or 4=alpha). */ 415 | size_t out_line_len; /* Bytes in each output line. */ 416 | bitfield bitfields[4]; /* How to decode 16- and 32-bits. */ 417 | bmp_color * palette; /* Enough entries for our bit depth. */ 418 | uint8_t * file_data; /* A line of data in the file. */ 419 | uint8_t * data_out; /* RGB(A) data output buffer. */ 420 | 421 | } read_context; 422 | 423 | /* A sub-function to Validate() that handles the bitfields. Returns 0 on 424 | * invalid bitfields or nonzero on success. Note that we don't treat odd 425 | * bitmasks such as R8G8 or A1G1B1 as invalid, even though they may not load in 426 | * most other loaders. 427 | */ 428 | static int ValidateBitfields(read_context * p_ctx) 429 | { 430 | bitfield * bf = p_ctx->bitfields; 431 | 432 | uint32_t total_mask = 0; 433 | bitfield total_field; 434 | 435 | int i; 436 | 437 | if(p_ctx->info.compression != COMPRESSION_BITFIELDS) 438 | return 1; 439 | 440 | for(i = 0; i < 4; i++) 441 | { 442 | /* No overlapping masks. */ 443 | if(total_mask & p_ctx->info.masks[i]) return 0; 444 | total_mask |= p_ctx->info.masks[i]; 445 | 446 | if(!ParseBitfield(&bf[i], p_ctx->info.masks[i])) return 0; 447 | 448 | /* Make sure we fit in our bit size. */ 449 | if(bf[i].start + bf[i].span > p_ctx->info.bits) return 0; 450 | } 451 | 452 | if(!total_mask) return 0; 453 | 454 | /* Check for contiguous-ity between fields, too. */ 455 | if(!ParseBitfield(&total_field, total_mask)) return 0; 456 | 457 | return 1; 458 | } 459 | 460 | /* A sub-function to Validate() that handles the palette. Returns 0 on EOF or 461 | * invalid palette, or nonzero on success. 462 | */ 463 | static int ValidateAndReadPalette(read_context * p_ctx) 464 | { 465 | uint32_t colors = UINT32_C(1) << p_ctx->info.bits; 466 | uint32_t file_colors = p_ctx->info.colors; 467 | 468 | if(p_ctx->info.bits > 8) 469 | return 1; 470 | 471 | if(file_colors > colors) return 0; 472 | if(!file_colors) 473 | file_colors = colors; 474 | 475 | /* Make sure we actually have space in the file for all the colors. */ 476 | if(p_ctx->after_headers / BMP_COLOR_SIZE < file_colors) return 0; 477 | 478 | /* We always allocate a full palette even if the file only claims to 479 | * contain a smaller number, so we don't have to check for out of bound 480 | * color lookups. Not sure what the desired behavior is, but loading the 481 | * image anyway and treating OOB colors as black seems ok to me. 0-fill so 482 | * lookups beyond the file's palette get set to black. 483 | */ 484 | if(!(p_ctx->palette = (bmp_color *) 485 | calloc(colors, sizeof(p_ctx->palette[0])))) return 0; 486 | 487 | if(!CanMakeLong(p_ctx->headers_size)) return 0; 488 | if(fseek(p_ctx->fp, p_ctx->headers_size, SEEK_SET)) return 0; 489 | if(!ReadPalette(p_ctx->palette, file_colors, p_ctx->fp)) return 0; 490 | 491 | return 1; 492 | } 493 | 494 | /* Returns whether a non-negative integer is a power of 2. 495 | */ 496 | static int IsPowerOf2(uint32_t x) 497 | { 498 | while(x) 499 | { 500 | /* When we find a bit, return whether no other bits are set. */ 501 | if(x & 1) 502 | return !(x & ~UINT32_C(1)); 503 | x = x >> 1; 504 | } 505 | 506 | /* 0, the only value for x which lands us here, isn't a power of 2. */ 507 | return 0; 508 | } 509 | 510 | /* Returns the byte length of a scan line padded as necessary to be divisible 511 | * by four. For example, 3 pixels wide at 24 bpp would yield 12 (3 pixels * 3 512 | * bytes each = 9 bytes, padded by 3 to the next multiple of 4). bpp is *bits* 513 | * per pixel, not bytes. Returns 0 in case of overflow. 514 | */ 515 | static size_t GetLineLength(size_t width, size_t bpp) 516 | { 517 | size_t bits = width * bpp; 518 | size_t pad_bits = (32 - (bits & 0x1f)) & 0x1f; /* x & 0x1f == x % 32 */ 519 | 520 | /* Check for overflow, in both the above multiplication and the below 521 | * addition. It's well defined to do this in any order relative to the 522 | * operations themselves (since size_t is unsigned), so we combine the 523 | * checks into one if. bpp has been checked for being nonzero elsewhere. 524 | */ 525 | if(!CanMultiply(width, bpp) || !CanAdd(bits, pad_bits)) return 0; 526 | 527 | /* Convert to bytes. */ 528 | return (bits + pad_bits) / 8; 529 | } 530 | 531 | /* Reads and validates the bitmap header metadata from the context's file 532 | * object. Assumes the file pointer is at the start of the file. Returns 1 if 533 | * ok or 0 if error or invalid file. 534 | */ 535 | static int Validate(read_context * p_ctx) 536 | { 537 | if(!ReadHeader(&p_ctx->header, p_ctx->fp)) return 0; 538 | if(!ReadInfo( &p_ctx->info, p_ctx->fp)) return 0; 539 | 540 | if(p_ctx->info.info_size > UINT32_MAX - BMP_HEADER_SIZE) return 0; 541 | p_ctx->headers_size = BMP_HEADER_SIZE + p_ctx->info.info_size; 542 | 543 | if(p_ctx->header.data_offset < p_ctx->headers_size) return 0; 544 | p_ctx->after_headers = p_ctx->header.data_offset - p_ctx->headers_size; 545 | 546 | if(p_ctx->info.width <= 0 || p_ctx->info.height == 0) return 0; 547 | 548 | if(!CanMakeSizeT(p_ctx->info.width)) return 0; 549 | if(!CanNegate( p_ctx->info.height)) return 0; 550 | p_ctx->lines = ((p_ctx->info.height < 0) ? 551 | -p_ctx->info.height : 552 | p_ctx->info.height); 553 | 554 | if(!(p_ctx->flags & BMPREAD_ANY_SIZE)) 555 | { 556 | /* Both of these values have just been checked against being negative, 557 | * and thus it's safe to pass them on as uint32_t. 558 | */ 559 | if(!IsPowerOf2(p_ctx->info.width)) return 0; 560 | if(!IsPowerOf2(p_ctx->lines)) return 0; 561 | } 562 | 563 | switch(p_ctx->info.compression) 564 | { 565 | case COMPRESSION_NONE: 566 | if(p_ctx->info.bits != 1 && p_ctx->info.bits != 4 && 567 | p_ctx->info.bits != 8 && p_ctx->info.bits != 24) return 0; 568 | break; 569 | 570 | case COMPRESSION_BITFIELDS: 571 | if(p_ctx->info.bits != 16 && p_ctx->info.bits != 32) return 0; 572 | break; 573 | 574 | default: /* No compression supported yet (TODO: handle RLE). */ 575 | return 0; 576 | } 577 | 578 | p_ctx->file_line_len = GetLineLength(p_ctx->info.width, p_ctx->info.bits); 579 | if(p_ctx->file_line_len == 0) return 0; 580 | 581 | p_ctx->out_channels = ((p_ctx->flags & BMPREAD_ALPHA) ? 4 : 3); 582 | 583 | /* This check happens outside the following if, where it would seem to 584 | * belong, because we make the same computation again in the future. 585 | */ 586 | if(!CanMultiply(p_ctx->info.width, p_ctx->out_channels)) return 0; 587 | 588 | if(p_ctx->flags & BMPREAD_BYTE_ALIGN) 589 | p_ctx->out_line_len = (size_t)p_ctx->info.width * p_ctx->out_channels; 590 | else 591 | { 592 | p_ctx->out_line_len = GetLineLength(p_ctx->info.width, 593 | p_ctx->out_channels * 8); 594 | if(p_ctx->out_line_len == 0) return 0; 595 | } 596 | 597 | if(!ValidateBitfields(p_ctx)) return 0; 598 | if(!ValidateAndReadPalette(p_ctx)) return 0; 599 | 600 | /* Set things up for decoding. */ 601 | if(!(p_ctx->file_data = (uint8_t *)malloc(p_ctx->file_line_len))) return 0; 602 | 603 | if(!CanMakeSizeT(p_ctx->lines)) return 0; 604 | if(!CanMultiply( p_ctx->lines, p_ctx->out_line_len)) return 0; 605 | if(!(p_ctx->data_out = (uint8_t *) 606 | malloc((size_t)p_ctx->lines * p_ctx->out_line_len))) return 0; 607 | 608 | return 1; 609 | } 610 | 611 | /* Evenly distribute a value that spans a given number of bits into 8 bits. 612 | */ 613 | static uint8_t Make8Bits(uint32_t value, uint32_t bitspan) 614 | { 615 | uint32_t output = 0; 616 | 617 | if(bitspan == 8) 618 | return (uint8_t)value; 619 | if(bitspan > 8) 620 | return (uint8_t)(value >> (bitspan - 8)); 621 | 622 | value <<= (8 - bitspan); /* Shift it up into the most significant bits. */ 623 | while(value) 624 | { 625 | /* Repeat the bit pattern down into the least significant bits. This 626 | * gives an even distribution when extrapolating from [0, 2^bitspan-1] 627 | * into [0, 2^8-1], and avoids both floating point and awkward integer 628 | * multiplication. Unfortunately, because we don't enforce a whitelist 629 | * of bit patterns we support and can hard-code for, it necessitates a 630 | * loop. I believe this is a fairly efficient way to express the idea, 631 | * but it'd still be nice if the compiler could optimize this whole 632 | * function heavily, since it's called in a tight decode loop. 633 | */ 634 | output |= value; 635 | value >>= bitspan; 636 | } 637 | 638 | return (uint8_t)output; 639 | } 640 | 641 | /* Reads four bytes out of a memory buffer and converts it to a uint32_t. 642 | */ 643 | #define LoadLittleUint32(buf) (((uint32_t)(buf)[0] ) + \ 644 | ((uint32_t)(buf)[1] << 8) + \ 645 | ((uint32_t)(buf)[2] << 16) + \ 646 | ((uint32_t)(buf)[3] << 24)) 647 | 648 | /* Decodes 32-bit bitmap data by applying bitmasks. The 16- and 32-bit 649 | * decoders could be made more efficient by whitelisting supported bit patterns 650 | * ahead of time and special-casing their decoding here, but this allows us to 651 | * support more bitmask patterns, and shouldn't be *too* inefficient in any 652 | * case. 653 | * 654 | * Takes a pointer to an output buffer scan line (p_out), a pointer to the end 655 | * of the *pixel data* of this scan line (p_out_end), a pointer to the source 656 | * scan line of file data (p_file), and our context. 657 | */ 658 | static void Decode32(uint8_t * p_out, 659 | const uint8_t * p_out_end, 660 | const uint8_t * p_file, 661 | const read_context * p_ctx) 662 | { 663 | const bitfield * bf = p_ctx->bitfields; 664 | 665 | while(p_out < p_out_end) 666 | { 667 | uint32_t value = LoadLittleUint32(p_file); 668 | 669 | *p_out++ = Make8Bits(ApplyBitfield(value, bf[0]), bf[0].span); 670 | *p_out++ = Make8Bits(ApplyBitfield(value, bf[1]), bf[1].span); 671 | *p_out++ = Make8Bits(ApplyBitfield(value, bf[2]), bf[2].span); 672 | if(p_ctx->out_channels == 4) 673 | { 674 | if(bf[3].span) 675 | *p_out++ = Make8Bits(ApplyBitfield(value, bf[3]), bf[3].span); 676 | else 677 | *p_out++ = BMPREAD_DEFAULT_ALPHA; 678 | } 679 | 680 | p_file += 4; 681 | } 682 | } 683 | 684 | /* Decodes 24-bit bitmap data--basically just swaps the order of color 685 | * components. 686 | */ 687 | static void Decode24(uint8_t * p_out, 688 | const uint8_t * p_out_end, 689 | const uint8_t * p_file, 690 | const read_context * p_ctx) 691 | { 692 | while(p_out < p_out_end) 693 | { 694 | *p_out++ = *(p_file + 2); 695 | *p_out++ = *(p_file + 1); 696 | *p_out++ = *(p_file ); 697 | if(p_ctx->out_channels == 4) 698 | *p_out++ = BMPREAD_DEFAULT_ALPHA; 699 | 700 | p_file += 3; 701 | } 702 | } 703 | 704 | /* Reads two bytes out of a memory buffer and converts it to a uint16_t. 705 | */ 706 | #define LoadLittleUint16(buf) (((uint16_t)(buf)[0] ) + \ 707 | ((uint16_t)(buf)[1] << 8)) 708 | 709 | /* Decodes 16-bit bitmap data by applying bitmasks. 710 | */ 711 | static void Decode16(uint8_t * p_out, 712 | const uint8_t * p_out_end, 713 | const uint8_t * p_file, 714 | const read_context * p_ctx) 715 | { 716 | const bitfield * bf = p_ctx->bitfields; 717 | 718 | while(p_out < p_out_end) 719 | { 720 | uint16_t value = LoadLittleUint16(p_file); 721 | 722 | *p_out++ = Make8Bits(ApplyBitfield(value, bf[0]), bf[0].span); 723 | *p_out++ = Make8Bits(ApplyBitfield(value, bf[1]), bf[1].span); 724 | *p_out++ = Make8Bits(ApplyBitfield(value, bf[2]), bf[2].span); 725 | if(p_ctx->out_channels == 4) 726 | { 727 | if(bf[3].span) 728 | *p_out++ = Make8Bits(ApplyBitfield(value, bf[3]), bf[3].span); 729 | else 730 | *p_out++ = BMPREAD_DEFAULT_ALPHA; 731 | } 732 | 733 | p_file += 2; 734 | } 735 | } 736 | 737 | /* Decodes 8-bit bitmap data by looking colors up in the palette. 738 | */ 739 | static void Decode8(uint8_t * p_out, 740 | const uint8_t * p_out_end, 741 | const uint8_t * p_file, 742 | const read_context * p_ctx) 743 | { 744 | while(p_out < p_out_end) { 745 | *p_out++ = p_ctx->palette[*p_file].red; 746 | *p_out++ = p_ctx->palette[*p_file].green; 747 | *p_out++ = p_ctx->palette[*p_file].blue; 748 | if(p_ctx->out_channels == 4) 749 | *p_out++ = BMPREAD_DEFAULT_ALPHA; 750 | 751 | p_file++; 752 | } 753 | } 754 | 755 | /* Decodes 4-bit bitmap data by looking colors up in the palette. 756 | */ 757 | static void Decode4(uint8_t * p_out, 758 | const uint8_t * p_out_end, 759 | const uint8_t * p_file, 760 | const read_context * p_ctx) 761 | { 762 | while(p_out < p_out_end) 763 | { 764 | unsigned int lookup = (*p_file & 0xf0U) >> 4; 765 | 766 | *p_out++ = p_ctx->palette[lookup].red; 767 | *p_out++ = p_ctx->palette[lookup].green; 768 | *p_out++ = p_ctx->palette[lookup].blue; 769 | if(p_ctx->out_channels == 4) 770 | *p_out++ = BMPREAD_DEFAULT_ALPHA; 771 | 772 | if(p_out < p_out_end) 773 | { 774 | lookup = *p_file++ & 0x0fU; 775 | 776 | *p_out++ = p_ctx->palette[lookup].red; 777 | *p_out++ = p_ctx->palette[lookup].green; 778 | *p_out++ = p_ctx->palette[lookup].blue; 779 | if(p_ctx->out_channels == 4) 780 | *p_out++ = BMPREAD_DEFAULT_ALPHA; 781 | } 782 | } 783 | } 784 | 785 | /* Decodes 1-bit bitmap data by looking colors up in the two-color palette. 786 | */ 787 | static void Decode1(uint8_t * p_out, 788 | const uint8_t * p_out_end, 789 | const uint8_t * p_file, 790 | const read_context * p_ctx) 791 | { 792 | while(p_out < p_out_end) 793 | { 794 | unsigned int bit; 795 | for(bit = 0; bit < 8 && p_out < p_out_end; bit++) 796 | { 797 | unsigned int lookup = (*p_file >> (7 - bit)) & 1; 798 | 799 | *p_out++ = p_ctx->palette[lookup].red; 800 | *p_out++ = p_ctx->palette[lookup].green; 801 | *p_out++ = p_ctx->palette[lookup].blue; 802 | if(p_ctx->out_channels == 4) 803 | *p_out++ = BMPREAD_DEFAULT_ALPHA; 804 | } 805 | 806 | p_file++; 807 | } 808 | } 809 | 810 | /* Selects an above decoder and runs it for each scan line of the file. 811 | * Returns 0 if there's an error or 1 if it's gravy. 812 | */ 813 | static int Decode(read_context * p_ctx) 814 | { 815 | void (* decoder)(uint8_t *, const uint8_t *, const uint8_t *, 816 | const read_context *); 817 | 818 | uint8_t * p_out; /* Pointer to current scan line in output buffer. */ 819 | uint8_t * p_out_end; /* End marker for output buffer. */ 820 | uint8_t * p_line_end; /* Pointer to end of current scan line in output. */ 821 | 822 | /* out_inc is an incrementor for p_out to advance it one scan line. I'm 823 | * not exactly sure what the correct type for it would be, perhaps ssize_t, 824 | * but that's not C standard. I went with ptrdiff_t because its value 825 | * will be equivalent to the difference between two pointers, whether it 826 | * was derived that way or not. 827 | */ 828 | ptrdiff_t out_inc; 829 | 830 | /* Double check this won't overflow. Who knows, man. */ 831 | #if SIZE_MAX > PTRDIFF_MAX 832 | if(p_ctx->out_line_len > PTRDIFF_MAX) return 0; 833 | #endif 834 | out_inc = p_ctx->out_line_len; 835 | 836 | if(!(p_ctx->info.height < 0) == !(p_ctx->flags & BMPREAD_TOP_DOWN)) 837 | { 838 | /* We're keeping scan lines in order. These and subsequent operations 839 | * have all been checked earlier. 840 | */ 841 | p_out = p_ctx->data_out; 842 | p_out_end = p_ctx->data_out + 843 | ((size_t)p_ctx->lines * p_ctx->out_line_len); 844 | } 845 | else /* We're reversing scan lines. */ 846 | { 847 | /* TODO: I'm not 100% sure about the legality, purely C spec-wise, of 848 | * this subtraction. 849 | */ 850 | p_out_end = p_ctx->data_out - p_ctx->out_line_len; 851 | p_out = p_ctx->data_out + 852 | (((size_t)p_ctx->lines - 1) * p_ctx->out_line_len); 853 | 854 | /* Always safe, given two's complement, since it was positive. */ 855 | out_inc = -out_inc; 856 | } 857 | 858 | p_line_end = p_out + (size_t)p_ctx->info.width * p_ctx->out_channels; 859 | 860 | switch(p_ctx->info.bits) 861 | { 862 | case 32: decoder = Decode32; break; 863 | case 24: decoder = Decode24; break; 864 | case 16: decoder = Decode16; break; 865 | case 8: decoder = Decode8; break; 866 | case 4: decoder = Decode4; break; 867 | case 1: decoder = Decode1; break; 868 | default: return 0; 869 | } 870 | 871 | if(!CanMakeLong(p_ctx->header.data_offset)) return 0; 872 | if(fseek(p_ctx->fp, p_ctx->header.data_offset, SEEK_SET)) return 0; 873 | 874 | while(p_out != p_out_end && 875 | fread(p_ctx->file_data, 1, p_ctx->file_line_len, p_ctx->fp) == 876 | p_ctx->file_line_len) 877 | { 878 | decoder(p_out, p_line_end, p_ctx->file_data, p_ctx); 879 | 880 | p_out += out_inc; 881 | p_line_end += out_inc; 882 | } 883 | 884 | return (p_out == p_out_end); 885 | } 886 | 887 | /* Frees resources allocated by various functions along the way. Only frees 888 | * data_out if !leave_data_out (if the bitmap loads successfully, you want the 889 | * data to remain until THEY free it). 890 | */ 891 | static void FreeContext(read_context * p_ctx, int leave_data_out) 892 | { 893 | if(p_ctx->fp) 894 | fclose(p_ctx->fp); 895 | if(p_ctx->palette) 896 | free(p_ctx->palette); 897 | if(p_ctx->file_data) 898 | free(p_ctx->file_data); 899 | 900 | if(!leave_data_out && p_ctx->data_out) 901 | free(p_ctx->data_out); 902 | } 903 | 904 | int bmpread(const char * bmp_file, unsigned int flags, bmpread_t * p_bmp_out) 905 | { 906 | int success = 0; 907 | 908 | read_context ctx; 909 | memset(&ctx, 0, sizeof(ctx)); 910 | 911 | do 912 | { 913 | if(!bmp_file) break; 914 | if(!p_bmp_out) break; 915 | memset(p_bmp_out, 0, sizeof(*p_bmp_out)); 916 | 917 | ctx.flags = flags; 918 | 919 | if(!(ctx.fp = fopen(bmp_file, "rb"))) break; 920 | if(!Validate(&ctx)) break; 921 | if(!Decode(&ctx)) break; 922 | 923 | /* Finally, make sure we can stuff these into ints. I feel like this 924 | * is slightly justified by how it keeps the header definition dead 925 | * simple (including, well, no #includes). I suppose this could also 926 | * be done way earlier and maybe save some disk reads, but I like 927 | * keeping the check with the code it's checking. 928 | */ 929 | #if INT32_MAX > INT_MAX 930 | if(ctx.info.width > INT_MAX) break; 931 | if(ctx.lines > INT_MAX) break; 932 | #endif 933 | 934 | p_bmp_out->width = ctx.info.width; 935 | p_bmp_out->height = ctx.lines; 936 | p_bmp_out->flags = ctx.flags; 937 | p_bmp_out->data = ctx.data_out; 938 | 939 | success = 1; 940 | } while(0); 941 | 942 | FreeContext(&ctx, success); 943 | 944 | return success; 945 | } 946 | 947 | void bmpread_free(bmpread_t * p_bmp) 948 | { 949 | if(p_bmp) 950 | { 951 | if(p_bmp->data) 952 | free(p_bmp->data); 953 | 954 | memset(p_bmp, 0, sizeof(*p_bmp)); 955 | } 956 | } -------------------------------------------------------------------------------- /src/m/bmpread.h: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * libbmpread - tiny, fast bitmap (.bmp) image file loader * 3 | * * 4 | * Copyright (C) 2005, 2012, 2016, 2018 Charles Lindsay * 5 | * * 6 | * This software is provided 'as-is', without any express or implied * 7 | * warranty. In no event will the authors be held liable for any damages * 8 | * arising from the use of this software. * 9 | * * 10 | * Permission is granted to anyone to use this software for any purpose, * 11 | * including commercial applications, and to alter it and redistribute it * 12 | * freely, subject to the following restrictions: * 13 | * * 14 | * 1. The origin of this software must not be misrepresented; you must not * 15 | * claim that you wrote the original software. If you use this software * 16 | * in a product, an acknowledgment in the product documentation would be * 17 | * appreciated but is not required. * 18 | * 2. Altered source versions must be plainly marked as such, and must not be * 19 | * misrepresented as being the original software. * 20 | * 3. This notice may not be removed or altered from any source distribution. * 21 | ******************************************************************************/ 22 | 23 | 24 | /* bmpread.h 25 | * version 3.0 26 | * 2018-02-02 27 | */ 28 | 29 | 30 | #ifndef __bmpread_h__ 31 | #define __bmpread_h__ 32 | 33 | #ifdef __cplusplus 34 | extern "C" 35 | { 36 | #endif 37 | 38 | 39 | /* Flags for bmpread() and bmpread_t (see below). Combine with bitwise OR. 40 | */ 41 | 42 | /* Output data as top line first (default is bottom line first). */ 43 | #define BMPREAD_TOP_DOWN 1u 44 | 45 | /* Don't pad lines to span a multiple of four bytes (default does pad). */ 46 | #define BMPREAD_BYTE_ALIGN 2u 47 | 48 | /* Allow loading of any size bitmap (default is bitmaps must be 2^n x 2^m). */ 49 | #define BMPREAD_ANY_SIZE 4u 50 | 51 | /* Load and output an alpha channel (default is just color channels). */ 52 | #define BMPREAD_ALPHA 8u 53 | 54 | 55 | /* The struct filled by bmpread(). Holds information about the image's pixels. 56 | */ 57 | typedef struct bmpread_t 58 | { 59 | int width; /* Width in pixels. */ 60 | int height; /* Height in pixels. */ 61 | 62 | /* BMPREAD_* flags, defined above, combined with bitwise OR, that affect 63 | * the format of data. These are set to the flags passed to bmpread(). 64 | */ 65 | unsigned int flags; 66 | 67 | /* A buffer holding the pixel data of the image. 68 | * 69 | * By default, each pixel spans three bytes: the red, green, and blue color 70 | * components in that order. However, with BMPREAD_ALPHA set in flags, 71 | * each pixel spans four bytes: the red, green, blue, and alpha components 72 | * in that order. 73 | * 74 | * Pixels are ordered left to right sequentially. By default, the bottom 75 | * line comes first, proceeding upward. However, with BMPREAD_TOP_DOWN set 76 | * in flags, the top line comes first, proceeding downward instead. 77 | * 78 | * Lines by default must span a multiple of four bytes. If the image width 79 | * and pixel span don't yield a multiple of four (a non-issue for 80 | * BMPREAD_ALPHA with four bytes per pixel), the end of each line is padded 81 | * with up to three unused bytes to meet the requirement. For example, 82 | * each line of an image three pixels wide, loaded without BMPREAD_ALPHA, 83 | * will span 12 bytes (3 pixels * 3 (RGB) channels per pixel = 9, padded 84 | * with 3 bytes up to the next multiple of 4). However, this behavior is 85 | * disabled with BMPREAD_BYTE_ALIGN set in flags, in which case all lines 86 | * span exactly width * pixel_span bytes. 87 | */ 88 | unsigned char * data; 89 | 90 | } bmpread_t; 91 | 92 | 93 | /* Loads the specified bitmap file from disk and fills out a bmpread_t struct 94 | * with data about it. 95 | * 96 | * Inputs: 97 | * bmp_file - The filename of the bitmap file to load. 98 | * flags - Any BMPREAD_* flags, defined above, combined with bitwise OR. 99 | * Specify 0 (or BMPREAD_ALPHA if you want an alpha channel) for 100 | * standard, OpenGL compliant behavior. 101 | * p_bmp_out - Pointer to a bmpread_t struct to fill with information. Its 102 | * contents on input are ignored. Must be freed with 103 | * bmpread_free() when no longer needed. 104 | * 105 | * Returns: 106 | * 0 if there's an error (file doesn't exist or is invalid, i/o error, etc.), 107 | * or nonzero if the file loaded ok. 108 | * 109 | * Notes: 110 | * The file must be a Windows 3 (not NT) or higher format bitmap file with any 111 | * valid bit depth (1, 4, 8, 16, 24, or 32), and must not be compressed (no 112 | * RLE). 113 | * 114 | * Default behavior is for bmpread() to return data in a format directly usable 115 | * by OpenGL texture functions, e.g. glTexImage2D, format GL_RGB (or GL_RGBA if 116 | * BMPREAD_ALPHA is in flags), type GL_UNSIGNED_BYTE. This implies a few 117 | * oddities: 118 | * - Lines are ordered bottom-first. To return data starting with the top 119 | * line like you might otherwise expect, pass BMPREAD_TOP_DOWN in flags. 120 | * - Lines are padded to span a multiple of four bytes. To return data with 121 | * no padding, pass BMPREAD_BYTE_ALIGN in flags. 122 | * - Images with a width or height that isn't a power of 2 will fail to load. 123 | * To allow loading images of any size, pass BMPREAD_ANY_SIZE in flags. 124 | * Note that passing any of these flags may cause the output to be unusable as 125 | * an OpenGL texture, which may or may not matter to you. 126 | * 127 | * Most bitmap files can't include an alpha channel, so the default behavior is 128 | * to ignore any alpha values present in the file. Pass BMPREAD_ALPHA in flags 129 | * to capture alpha values from the file; in case of an absent alpha channel, 130 | * alpha values are output as 255 (this can be changed by redefining 131 | * BMPREAD_DEFAULT_ALPHA in bmpread.c). This allows fully loading 16- and 132 | * 32-bit bitmaps, which *can* include an alpha channel. 133 | */ 134 | int bmpread(const char * bmp_file, unsigned int flags, bmpread_t * p_bmp_out); 135 | 136 | 137 | /* Frees memory allocated during bmpread(). Call bmpread_free() when you are 138 | * done using the bmpread_t struct (e.g. after you have passed the data on to 139 | * OpenGL). 140 | * 141 | * Inputs: 142 | * p_bmp - The pointer you previously passed to bmpread(). 143 | * 144 | * Returns: 145 | * void 146 | */ 147 | void bmpread_free(bmpread_t * p_bmp); 148 | 149 | 150 | #ifdef __cplusplus 151 | } 152 | #endif 153 | 154 | #endif -------------------------------------------------------------------------------- /src/m/gl_draw.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by MoeMod on 2019-06-07. 3 | // 4 | 5 | #include "gl_draw.h" 6 | 7 | #include 8 | 9 | #include "StudioModel.h" 10 | #include "ViewerSettings.h" 11 | 12 | void setupRenderMode (); 13 | 14 | // Replaces gluPerspective. Sets the frustum to perspective mode. 15 | // fovY - Field of vision in degrees in the y direction 16 | // aspect - Aspect ratio of the viewport 17 | // near - The near clipping distance 18 | // far - The far clipping distance 19 | static void perspectiveGL (GLfloat fovY, GLfloat aspect, GLfloat _near, GLfloat _far) 20 | { 21 | GLfloat w, h; 22 | 23 | h = tan (fovY / 360 * Q_PI) * _near * (480.0f / 640.0f); 24 | 25 | w = h * aspect; 26 | 27 | glFrustum (-w, w, -h, h, _near, _far); 28 | } 29 | 30 | static void drawFloor () 31 | { 32 | if (g_viewerSettings.use3dfx) 33 | { 34 | glBegin (GL_TRIANGLE_STRIP); 35 | glTexCoord2f (1.0f, 0.0f); 36 | glVertex3f (100.0f, 100.0f, 0.0f); 37 | 38 | glTexCoord2f (1.0f, 1.0f); 39 | glVertex3f (100.0f, -100.0f, 0.0f); 40 | 41 | glTexCoord2f (0.0f, 0.0f); 42 | glVertex3f (-100.0f, 100.0f, 0.0f); 43 | 44 | glTexCoord2f (0.0f, 1.0f); 45 | glVertex3f (-100.0f, -100.0f, 0.0f); 46 | 47 | glEnd (); 48 | } 49 | else 50 | { 51 | glBegin (GL_TRIANGLE_STRIP); 52 | glTexCoord2f (0.0f, 0.0f); 53 | glVertex3f (-100.0f, 100.0f, 0.0f); 54 | 55 | glTexCoord2f (0.0f, 1.0f); 56 | glVertex3f (-100.0f, -100.0f, 0.0f); 57 | 58 | glTexCoord2f (1.0f, 0.0f); 59 | glVertex3f (100.0f, 100.0f, 0.0f); 60 | 61 | glTexCoord2f (1.0f, 1.0f); 62 | glVertex3f (100.0f, -100.0f, 0.0f); 63 | 64 | glEnd (); 65 | } 66 | } 67 | void CHLMV_GL_Draw::Think() 68 | { 69 | g_studioModel.SetBlending (0, 0.0); 70 | g_studioModel.SetBlending (1, 0.0); 71 | 72 | auto CurrTime = std::chrono::high_resolution_clock::now(); 73 | 74 | if (!m_bStopPlaying) 75 | g_studioModel.AdvanceFrame (std::chrono::duration_cast>(CurrTime - m_PrevTime).count() * g_viewerSettings.speedScale); 76 | 77 | m_PrevTime = CurrTime; 78 | } 79 | 80 | void CHLMV_GL_Draw::Draw() 81 | { 82 | glClearColor (g_viewerSettings.bgColor[0], g_viewerSettings.bgColor[1], g_viewerSettings.bgColor[2], 0.0f); 83 | 84 | if (g_viewerSettings.useStencil) 85 | glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); 86 | else 87 | glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 88 | 89 | glViewport (0, 0, w2 (), h2 ()); 90 | 91 | glEnable(GL_BLEND); 92 | 93 | // 94 | // show textures 95 | // 96 | 97 | if (g_viewerSettings.showTexture) 98 | { 99 | glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 100 | glMatrixMode (GL_PROJECTION); 101 | glLoadIdentity (); 102 | 103 | glOrtho (0.0f, (float) w2 (), (float) h2 (), 0.0f, 1.0f, -1.0f); 104 | 105 | studiohdr_t *hdr = g_studioModel.getTextureHeader (); 106 | if (hdr) 107 | { 108 | mstudiotexture_t *ptextures = (mstudiotexture_t *) ((byte *) hdr + hdr->textureindex); 109 | float w = (float) ptextures[g_viewerSettings.texture].width * g_viewerSettings.textureScale; 110 | float h = (float) ptextures[g_viewerSettings.texture].height * g_viewerSettings.textureScale; 111 | 112 | glMatrixMode (GL_MODELVIEW); 113 | glPushMatrix (); 114 | glLoadIdentity (); 115 | 116 | glDisable (GL_CULL_FACE); 117 | glDisable (GL_DEPTH_TEST); 118 | 119 | glPolygonMode (GL_FRONT_AND_BACK, GL_FILL); 120 | float x = ((float) w2 () - w) / 2; 121 | float y = ((float) h2 () - h) / 2; 122 | 123 | glDisable (GL_TEXTURE_2D); 124 | if (g_viewerSettings.showUVMap) 125 | { 126 | if (g_viewerSettings.showUVMapOverlay) 127 | { 128 | glColor4f (0.0f, 0.0f, 1.0f, 1.0f); 129 | glRectf (x, y, x + w, y + h); 130 | glEnable (GL_TEXTURE_2D); 131 | glColor4f (1.0f, 1.0f, 1.0f, 1.0f); 132 | glBindTexture (GL_TEXTURE_2D, g_viewerSettings.texture + 3); //d_textureNames[0]); 133 | 134 | glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 135 | glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 136 | glBegin (GL_TRIANGLE_STRIP); 137 | 138 | glTexCoord2f (0, 0); 139 | glVertex2f (x, y); 140 | 141 | glTexCoord2f (1, 0); 142 | glVertex2f (x + w, y); 143 | 144 | glTexCoord2f (0, 1); 145 | glVertex2f (x, y + h); 146 | 147 | glTexCoord2f (1, 1); 148 | glVertex2f (x + w, y + h); 149 | 150 | glEnd (); 151 | 152 | } 153 | else 154 | { 155 | glColor4f (0.0f, 0.0f, 0.0f, 1.0f); 156 | glRectf (x, y, x + w, y + h); 157 | } 158 | 159 | hdr = g_studioModel.getStudioHeader (); 160 | 161 | if (hdr) 162 | { 163 | float s, t; 164 | int i, j, k, l; 165 | int index = -1; 166 | short *pskinref = (short *)((byte *)hdr + hdr->skinindex); 167 | 168 | for (k = 0; k < hdr->numbodyparts; k++) 169 | { 170 | mstudiobodyparts_t *pbodyparts = (mstudiobodyparts_t *) ((byte *) hdr + hdr->bodypartindex) + k; 171 | 172 | for (j = 0; j < pbodyparts->nummodels; j++) 173 | { 174 | mstudiomodel_t *pmodels = (mstudiomodel_t *) ((byte *) hdr + pbodyparts->modelindex) + j; 175 | 176 | for (l = 0; l < pmodels->nummesh; l++) 177 | { 178 | float s, t; 179 | short *ptricmds; 180 | int tri_type; 181 | mstudiomesh_t *pmesh = (mstudiomesh_t *) ((byte *) hdr + pmodels->meshindex) + l; 182 | 183 | ++index; 184 | if (pmesh->skinref != g_viewerSettings.texture 185 | || (g_viewerSettings.mesh != index && !g_viewerSettings.showAllMeshes)) 186 | continue; 187 | 188 | ptricmds = (short *)((byte *)hdr + pmesh->triindex); 189 | 190 | s = 1.0/(float)ptextures[pskinref[pmesh->skinref]].width; 191 | t = 1.0/(float)ptextures[pskinref[pmesh->skinref]].height; 192 | 193 | glColor4f (1.0f, 1.0f, 1.0f, 1.0f); 194 | glPolygonMode (GL_FRONT_AND_BACK, GL_LINE); 195 | glDisable (GL_TEXTURE_2D); 196 | glDisable (GL_CULL_FACE); 197 | glDisable (GL_DEPTH_TEST); 198 | if (g_viewerSettings.showSmoothLines) 199 | glEnable (GL_LINE_SMOOTH); 200 | else 201 | glDisable (GL_LINE_SMOOTH); 202 | glLineWidth (1.0); 203 | 204 | while ((i = *(ptricmds++))) 205 | { 206 | if (i < 0) 207 | { 208 | tri_type = GL_TRIANGLE_FAN; 209 | i = -i; 210 | } 211 | else 212 | { 213 | tri_type = GL_TRIANGLE_STRIP; 214 | } 215 | 216 | glBegin (tri_type); 217 | for ( ; i > 0; i--, ptricmds += 4) 218 | { 219 | glVertex3f (ptricmds[2] * s * w + x , ptricmds[3] * t * h + y , 1.0f); 220 | } 221 | glEnd ( ); 222 | glDisable (GL_LINE_SMOOTH); 223 | } 224 | } 225 | } 226 | } 227 | } 228 | } 229 | else 230 | { 231 | glColor4f (0.0f, 0.0f, 0.0f, 0.0f); 232 | glRectf (x, y, x + w, y + h); 233 | 234 | glEnable (GL_TEXTURE_2D); 235 | glColor4f (1.0f, 1.0f, 1.0f, 1.0f); 236 | glBindTexture (GL_TEXTURE_2D, g_viewerSettings.texture + 3); //d_textureNames[0]); 237 | 238 | glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 239 | glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 240 | 241 | glBegin (GL_TRIANGLE_STRIP); 242 | 243 | glTexCoord2f (0, 0); 244 | glVertex2f (x, y); 245 | 246 | glTexCoord2f (1, 0); 247 | glVertex2f (x + w, y); 248 | 249 | glTexCoord2f (0, 1); 250 | glVertex2f (x, y + h); 251 | 252 | glTexCoord2f (1, 1); 253 | glVertex2f (x + w, y + h); 254 | 255 | glEnd (); 256 | } 257 | glPopMatrix (); 258 | 259 | glClear (GL_DEPTH_BUFFER_BIT); 260 | glBindTexture (GL_TEXTURE_2D, 0); 261 | } 262 | return; 263 | } 264 | 265 | // 266 | // draw background 267 | // 268 | 269 | if (g_viewerSettings.showBackground && d_textureNames[0] && !g_viewerSettings.showTexture) 270 | { 271 | glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 272 | glMatrixMode (GL_PROJECTION); 273 | glLoadIdentity (); 274 | 275 | glOrtho (0.0f, 1.0f, 1.0f, 0.0f, 1.0f, -1.0f); 276 | 277 | glMatrixMode (GL_MODELVIEW); 278 | glPushMatrix (); 279 | glLoadIdentity (); 280 | 281 | glDisable (GL_CULL_FACE); 282 | glEnable (GL_TEXTURE_2D); 283 | 284 | glColor4f (1.0f, 1.0f, 1.0f, 1.0f); 285 | glPolygonMode (GL_FRONT_AND_BACK, GL_FILL); 286 | 287 | glBindTexture (GL_TEXTURE_2D, d_textureNames[0]); 288 | 289 | glBegin (GL_TRIANGLE_STRIP); 290 | 291 | glTexCoord2f (0, 0); 292 | glVertex2f (0, 0); 293 | 294 | glTexCoord2f (1, 0); 295 | glVertex2f (1, 0); 296 | 297 | glTexCoord2f (0, 1); 298 | glVertex2f (0, 1); 299 | 300 | glTexCoord2f (1, 1); 301 | glVertex2f (1, 1); 302 | 303 | glEnd (); 304 | 305 | glPopMatrix (); 306 | 307 | glClear (GL_DEPTH_BUFFER_BIT); 308 | glBindTexture (GL_TEXTURE_2D, 0); 309 | } 310 | 311 | glMatrixMode (GL_PROJECTION); 312 | glLoadIdentity (); 313 | if(!g_viewerSettings.vieworiginmode) 314 | { 315 | perspectiveGL (g_viewerSettings.yaw, (GLfloat) w () / (GLfloat) h (), 1.0f, 4096.0f); 316 | } 317 | else 318 | { 319 | if(g_viewerSettings.viewaspect > 0.0f) 320 | perspectiveGL (g_viewerSettings.viewfov, 1 / g_viewerSettings.viewaspect, 1.0f, 4096.0f); 321 | else 322 | perspectiveGL (g_viewerSettings.viewfov, (GLfloat) w () / (GLfloat) h (), 1.0f, 4096.0f); 323 | } 324 | 325 | glMatrixMode (GL_MODELVIEW); 326 | glPushMatrix (); 327 | glLoadIdentity (); 328 | 329 | float trans[3] = {g_viewerSettings.trans[0], g_viewerSettings.trans[1], g_viewerSettings.trans[2]}; 330 | float rot[3] = {g_viewerSettings.rot[0], g_viewerSettings.rot[1], g_viewerSettings.rot[2]}; 331 | 332 | if(g_viewerSettings.vieworiginmode) 333 | { 334 | float dx = g_viewerSettings.vieworigintrans[0]; 335 | float dy = g_viewerSettings.vieworigintrans[1]; 336 | float dz = g_viewerSettings.vieworigintrans[2]; 337 | 338 | trans[0] = -dx - 0.03f; 339 | trans[1] = dz + 1.0172f; 340 | trans[2] = dy - 0.02f; 341 | rot[0] = -90.0f; 342 | rot[1] = 90.0f; 343 | rot[2] = 0.0f; 344 | } 345 | 346 | glTranslatef(-trans[0], -trans[1], -trans[2]); 347 | glRotatef (rot[0], 1.0f, 0.0f, 0.0f); 348 | glRotatef (rot[1], 0.0f, 0.0f, 1.0f); 349 | 350 | // setup stencil buffer 351 | if (g_viewerSettings.useStencil) 352 | { 353 | /* Don't update color or depth. */ 354 | glDisable(GL_DEPTH_TEST); 355 | glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); 356 | 357 | /* Draw 1 into the stencil buffer. */ 358 | glEnable(GL_STENCIL_TEST); 359 | glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE); 360 | glStencilFunc(GL_ALWAYS, 1, 0xffffffff); 361 | 362 | /* Now render floor; floor pixels just get their stencil set to 1. */ 363 | drawFloor(); 364 | 365 | /* Re-enable update of color and depth. */ 366 | glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); 367 | glEnable(GL_DEPTH_TEST); 368 | 369 | /* Now, only render where stencil is set to 1. */ 370 | glStencilFunc(GL_EQUAL, 1, 0xffffffff); /* draw if ==1 */ 371 | glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); 372 | } 373 | 374 | g_vright[0] = g_vright[1] = trans[2]; 375 | 376 | if (g_viewerSettings.mirror) 377 | { 378 | glPushMatrix (); 379 | glScalef (1, 1, -1); 380 | glCullFace (GL_BACK); 381 | setupRenderMode (); 382 | g_studioModel.DrawModel (); 383 | glPopMatrix (); 384 | } 385 | 386 | if (g_viewerSettings.useStencil) 387 | glDisable (GL_STENCIL_TEST); 388 | 389 | setupRenderMode (); 390 | 391 | if (g_viewerSettings.vieworiginmode && g_viewerSettings.righthand) 392 | { 393 | glCullFace(GL_BACK); 394 | g_studioModel.DrawModel(); 395 | } 396 | else 397 | { 398 | glCullFace(GL_FRONT); 399 | g_studioModel.DrawModel(); 400 | } 401 | 402 | // 403 | // draw ground 404 | // 405 | 406 | if (g_viewerSettings.showGround) 407 | { 408 | glPolygonMode (GL_FRONT_AND_BACK, GL_FILL); 409 | glEnable (GL_DEPTH_TEST); 410 | glEnable (GL_CULL_FACE); 411 | 412 | if (g_viewerSettings.useStencil) 413 | glFrontFace (GL_CW); 414 | else 415 | glDisable (GL_CULL_FACE); 416 | 417 | glEnable (GL_BLEND); 418 | if (!d_textureNames[1]) 419 | { 420 | glDisable (GL_TEXTURE_2D); 421 | glColor4f (g_viewerSettings.gColor[0], g_viewerSettings.gColor[1], g_viewerSettings.gColor[2], 0.7f); 422 | glBindTexture (GL_TEXTURE_2D, 0); 423 | } 424 | else 425 | { 426 | glEnable (GL_TEXTURE_2D); 427 | glColor4f (1.0f, 1.0f, 1.0f, 0.6f); 428 | glBindTexture (GL_TEXTURE_2D, d_textureNames[1]); 429 | } 430 | 431 | glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 432 | 433 | drawFloor (); 434 | 435 | glDisable (GL_BLEND); 436 | 437 | if (g_viewerSettings.useStencil) 438 | { 439 | glCullFace (GL_BACK); 440 | glColor4f (0.1f, 0.1f, 0.1f, 1.0f); 441 | glBindTexture (GL_TEXTURE_2D, 0); 442 | drawFloor (); 443 | 444 | glFrontFace (GL_CCW); 445 | } 446 | else 447 | glEnable (GL_CULL_FACE); 448 | } 449 | 450 | if (g_viewerSettings.showCrosshair) 451 | { 452 | glMatrixMode (GL_PROJECTION); 453 | glPushMatrix (); 454 | glLoadIdentity (); 455 | glOrtho (0.0f, w2 (), h2 (), 0.0, 0.0, 1.0); 456 | glMatrixMode (GL_MODELVIEW); 457 | glLoadIdentity (); 458 | glDisable (GL_DEPTH_TEST); 459 | glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 460 | glEnable (GL_BLEND); 461 | glPolygonMode (GL_FRONT_AND_BACK, GL_FILL); 462 | float w = (w2 () - 27.0f) * 0.5f; 463 | float h = (h2 () - 27.0f) * 0.5f; 464 | glDisable (GL_TEXTURE_2D); 465 | glColor4f (g_viewerSettings.guColor[0], g_viewerSettings.guColor[1], g_viewerSettings.guColor[2], 1.0f); 466 | float w2 = w + 10.0f; 467 | float w3 = w + 12.0f; 468 | float w4 = w + 13.0f; 469 | float w5 = w + 14.0f; 470 | float w6 = w + 15.0f; 471 | float w7 = w + 17.0f; 472 | float w8 = w + 27.0f; 473 | 474 | float h2 = h + 10.0f; 475 | float h3 = h + 12.0f; 476 | float h4 = h + 13.0f; 477 | float h5 = h + 14.0f; 478 | float h6 = h + 15.0f; 479 | float h7 = h + 17.0f; 480 | float h8 = h + 27.0f; 481 | glRectf (w4, h, w5, h2); 482 | glRectf (w4, h7, w5, h8); 483 | glRectf (w, h4, w2, h5); 484 | glRectf (w7, h4, w8, h5); 485 | glRectf (w4, h4, w5, h5); 486 | glColor4f (g_viewerSettings.guColor[0], g_viewerSettings.guColor[1], g_viewerSettings.guColor[2], 0.25f); 487 | glRectf (w3, h, w4, h2); 488 | glRectf (w5, h, w6, h2); 489 | glRectf (w3, h7, w4, h8); 490 | glRectf (w5, h7, w6, h8); 491 | glRectf (w, h3, w2, h4); 492 | glRectf (w, h5, w2, h6); 493 | glRectf (w7, h3, w8, h4); 494 | glRectf (w7, h5, w8, h6); 495 | glRectf (w4, h3, w5, h4); 496 | glRectf (w4, h5, w5, h6); 497 | glRectf (w3, h4, w4, h5); 498 | glRectf (w5, h4, w6, h5); 499 | glColor4f (g_viewerSettings.guColor[0], g_viewerSettings.guColor[1], g_viewerSettings.guColor[2], 0.125f); 500 | glRectf (w3, h3, w4, h4); 501 | glRectf (w5, h3, w6, h4); 502 | glRectf (w3, h5, w4, h6); 503 | glRectf (w5, h5, w6, h6); 504 | } 505 | 506 | if (g_viewerSettings.showGuideLines) 507 | { 508 | glMatrixMode (GL_PROJECTION); 509 | glPushMatrix (); 510 | glLoadIdentity (); 511 | 512 | glOrtho (0.0f, w2 (), h2 (), 0.0f, 0.0f, 1.0f); 513 | 514 | glMatrixMode (GL_MODELVIEW); 515 | glLoadIdentity (); 516 | 517 | glDisable (GL_DEPTH_TEST); 518 | glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 519 | glEnable (GL_BLEND); 520 | 521 | glPolygonMode (GL_FRONT_AND_BACK, GL_FILL); 522 | glDisable (GL_TEXTURE_2D); 523 | 524 | glColor4f (g_viewerSettings.guColor[0], g_viewerSettings.guColor[1], g_viewerSettings.guColor[2], 0.5f); 525 | 526 | glEnable (GL_LINE_SMOOTH); 527 | glEnable (GL_LINE_STIPPLE); 528 | 529 | glLineWidth (1.0f); 530 | glLineStipple (2, 0xFAFA); 531 | 532 | float w = w2 () - 0.5f; 533 | glBegin (GL_LINES); 534 | glVertex2f (w, h2 () + 14.0f); 535 | glVertex2f (w, h2 ()); 536 | glEnd (); 537 | 538 | glDisable (GL_LINE_STIPPLE); 539 | glLineWidth (3.0f); 540 | 541 | glBegin (GL_LINES); 542 | w = w2 () * 0.085f; 543 | glVertex2f (w, 0.0f); 544 | glVertex2f (w, h2 ()); 545 | w = w2 () * 0.915f; 546 | glVertex2f (w, 0.0f); 547 | glVertex2f (w, h2 ()); 548 | glEnd (); 549 | 550 | glLineWidth (1.0f); 551 | glDisable (GL_LINE_SMOOTH); 552 | } 553 | 554 | glPopMatrix (); 555 | 556 | } 557 | -------------------------------------------------------------------------------- /src/m/gl_draw.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by MoeMod on 2019-06-07. 3 | // 4 | 5 | #ifndef HLMV_GL_DRAW_H 6 | #define HLMV_GL_DRAW_H 7 | 8 | #include 9 | 10 | 11 | class CHLMV_GL_Draw 12 | { 13 | public: 14 | CHLMV_GL_Draw() : m_bStopPlaying(false) {} 15 | 16 | public: 17 | void Think(); 18 | void Draw(); 19 | 20 | public: 21 | void SetSize(int w, int h, int w2, int h2) 22 | { 23 | m_w = w; 24 | m_h = h; 25 | m_w2 = w2; 26 | m_h2 = h2; 27 | } 28 | void SetStopPlaying(bool stop) 29 | { 30 | m_bStopPlaying = stop; 31 | } 32 | 33 | public: 34 | int w() const { return m_w; } 35 | int h() const { return m_h; } 36 | int w2() const { return m_w2; } 37 | int h2() const { return m_h2; } 38 | 39 | private: 40 | int m_w; 41 | int m_h; 42 | int m_w2; 43 | int m_h2; 44 | typename std::chrono::high_resolution_clock::time_point m_PrevTime; 45 | int d_textureNames[2]; 46 | bool m_bStopPlaying; 47 | }; 48 | 49 | 50 | #endif //HLMV_GL_DRAW_H 51 | -------------------------------------------------------------------------------- /src/m/mathlib.c: -------------------------------------------------------------------------------- 1 | /*** 2 | * 3 | * Copyright (c) 1998, Valve LLC. All rights reserved. 4 | * 5 | * This product contains software technology licensed from Id 6 | * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. 7 | * All Rights Reserved. 8 | * 9 | ****/ 10 | 11 | // mathlib.c -- math primitives 12 | 13 | #pragma warning( disable : 4244 ) 14 | #pragma warning( disable : 4237 ) 15 | #pragma warning( disable : 4305 ) 16 | 17 | 18 | /* #include "cmdlib.h" */ 19 | #ifndef true 20 | #define true 1 21 | #endif /* true */ 22 | #ifndef false 23 | #define false 0 24 | #endif /* false */ 25 | 26 | #include "mathlib.h" 27 | 28 | vec3_t vec3_origin = {0,0,0}; 29 | 30 | 31 | double VectorLength(vec3_t v) 32 | { 33 | int i; 34 | double length; 35 | 36 | length = 0; 37 | for (i=0 ; i< 3 ; i++) 38 | length += v[i]*v[i]; 39 | length = sqrt (length); // FIXME 40 | 41 | return length; 42 | } 43 | 44 | 45 | int VectorCompare (vec3_t v1, vec3_t v2) 46 | { 47 | int i; 48 | 49 | for (i=0 ; i<3 ; i++) 50 | if (fabs(v1[i]-v2[i]) > EQUAL_EPSILON) 51 | return false; 52 | 53 | return true; 54 | } 55 | 56 | vec_t Q_rint (vec_t in) 57 | { 58 | return floor (in + 0.5); 59 | } 60 | 61 | void VectorMA (vec3_t va, double scale, vec3_t vb, vec3_t vc) 62 | { 63 | vc[0] = va[0] + scale*vb[0]; 64 | vc[1] = va[1] + scale*vb[1]; 65 | vc[2] = va[2] + scale*vb[2]; 66 | } 67 | 68 | void CrossProduct (vec3_t v1, vec3_t v2, vec3_t cross) 69 | { 70 | cross[0] = v1[1]*v2[2] - v1[2]*v2[1]; 71 | cross[1] = v1[2]*v2[0] - v1[0]*v2[2]; 72 | cross[2] = v1[0]*v2[1] - v1[1]*v2[0]; 73 | } 74 | 75 | vec_t _DotProduct (vec3_t v1, vec3_t v2) 76 | { 77 | return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2]; 78 | } 79 | 80 | void _VectorSubtract (vec3_t va, vec3_t vb, vec3_t out) 81 | { 82 | out[0] = va[0]-vb[0]; 83 | out[1] = va[1]-vb[1]; 84 | out[2] = va[2]-vb[2]; 85 | } 86 | 87 | void _VectorAdd (vec3_t va, vec3_t vb, vec3_t out) 88 | { 89 | out[0] = va[0]+vb[0]; 90 | out[1] = va[1]+vb[1]; 91 | out[2] = va[2]+vb[2]; 92 | } 93 | 94 | void _VectorCopy (vec3_t in, vec3_t out) 95 | { 96 | out[0] = in[0]; 97 | out[1] = in[1]; 98 | out[2] = in[2]; 99 | } 100 | 101 | void _VectorScale (vec3_t v, vec_t scale, vec3_t out) 102 | { 103 | out[0] = v[0] * scale; 104 | out[1] = v[1] * scale; 105 | out[2] = v[2] * scale; 106 | } 107 | 108 | vec_t VectorNormalize (vec3_t v) 109 | { 110 | int i; 111 | double length; 112 | 113 | if ( fabs(v[1] - 0.000215956) < 0.0001) 114 | i=1; 115 | 116 | length = 0; 117 | for (i=0 ; i< 3 ; i++) 118 | length += v[i]*v[i]; 119 | length = sqrt (length); 120 | if (length == 0) 121 | return 0; 122 | 123 | for (i=0 ; i< 3 ; i++) 124 | v[i] /= length; 125 | 126 | return length; 127 | } 128 | 129 | void VectorInverse (vec3_t v) 130 | { 131 | v[0] = -v[0]; 132 | v[1] = -v[1]; 133 | v[2] = -v[2]; 134 | } 135 | 136 | void ClearBounds (vec3_t mins, vec3_t maxs) 137 | { 138 | mins[0] = mins[1] = mins[2] = 99999; 139 | maxs[0] = maxs[1] = maxs[2] = -99999; 140 | } 141 | 142 | void AddPointToBounds (vec3_t v, vec3_t mins, vec3_t maxs) 143 | { 144 | int i; 145 | vec_t val; 146 | 147 | for (i=0 ; i<3 ; i++) 148 | { 149 | val = v[i]; 150 | if (val < mins[i]) 151 | mins[i] = val; 152 | if (val > maxs[i]) 153 | maxs[i] = val; 154 | } 155 | } 156 | 157 | void AngleMatrix (const vec3_t angles, float (*matrix)[4] ) 158 | { 159 | float angle; 160 | float sr, sp, sy, cr, cp, cy; 161 | 162 | angle = angles[2] * (Q_PI*2 / 360); 163 | sy = sin(angle); 164 | cy = cos(angle); 165 | angle = angles[1] * (Q_PI*2 / 360); 166 | sp = sin(angle); 167 | cp = cos(angle); 168 | angle = angles[0] * (Q_PI*2 / 360); 169 | sr = sin(angle); 170 | cr = cos(angle); 171 | 172 | // matrix = (Z * Y) * X 173 | matrix[0][0] = cp*cy; 174 | matrix[1][0] = cp*sy; 175 | matrix[2][0] = -sp; 176 | matrix[0][1] = sr*sp*cy+cr*-sy; 177 | matrix[1][1] = sr*sp*sy+cr*cy; 178 | matrix[2][1] = sr*cp; 179 | matrix[0][2] = (cr*sp*cy+-sr*-sy); 180 | matrix[1][2] = (cr*sp*sy+-sr*cy); 181 | matrix[2][2] = cr*cp; 182 | matrix[0][3] = 0.0; 183 | matrix[1][3] = 0.0; 184 | matrix[2][3] = 0.0; 185 | } 186 | 187 | void AngleIMatrix (const vec3_t angles, float matrix[3][4] ) 188 | { 189 | float angle; 190 | float sr, sp, sy, cr, cp, cy; 191 | 192 | angle = angles[2] * (Q_PI*2 / 360); 193 | sy = sin(angle); 194 | cy = cos(angle); 195 | angle = angles[1] * (Q_PI*2 / 360); 196 | sp = sin(angle); 197 | cp = cos(angle); 198 | angle = angles[0] * (Q_PI*2 / 360); 199 | sr = sin(angle); 200 | cr = cos(angle); 201 | 202 | // matrix = (Z * Y) * X 203 | matrix[0][0] = cp*cy; 204 | matrix[0][1] = cp*sy; 205 | matrix[0][2] = -sp; 206 | matrix[1][0] = sr*sp*cy+cr*-sy; 207 | matrix[1][1] = sr*sp*sy+cr*cy; 208 | matrix[1][2] = sr*cp; 209 | matrix[2][0] = (cr*sp*cy+-sr*-sy); 210 | matrix[2][1] = (cr*sp*sy+-sr*cy); 211 | matrix[2][2] = cr*cp; 212 | matrix[0][3] = 0.0; 213 | matrix[1][3] = 0.0; 214 | matrix[2][3] = 0.0; 215 | } 216 | 217 | void R_ConcatTransforms (const float in1[3][4], const float in2[3][4], float out[3][4]) 218 | { 219 | out[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] + 220 | in1[0][2] * in2[2][0]; 221 | out[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] + 222 | in1[0][2] * in2[2][1]; 223 | out[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] + 224 | in1[0][2] * in2[2][2]; 225 | out[0][3] = in1[0][0] * in2[0][3] + in1[0][1] * in2[1][3] + 226 | in1[0][2] * in2[2][3] + in1[0][3]; 227 | out[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] + 228 | in1[1][2] * in2[2][0]; 229 | out[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] + 230 | in1[1][2] * in2[2][1]; 231 | out[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] + 232 | in1[1][2] * in2[2][2]; 233 | out[1][3] = in1[1][0] * in2[0][3] + in1[1][1] * in2[1][3] + 234 | in1[1][2] * in2[2][3] + in1[1][3]; 235 | out[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] + 236 | in1[2][2] * in2[2][0]; 237 | out[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] + 238 | in1[2][2] * in2[2][1]; 239 | out[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] + 240 | in1[2][2] * in2[2][2]; 241 | out[2][3] = in1[2][0] * in2[0][3] + in1[2][1] * in2[1][3] + 242 | in1[2][2] * in2[2][3] + in1[2][3]; 243 | } 244 | 245 | 246 | 247 | void VectorRotate (const vec3_t in1, const float in2[3][4], vec3_t out) 248 | { 249 | out[0] = DotProduct(in1, in2[0]); 250 | out[1] = DotProduct(in1, in2[1]); 251 | out[2] = DotProduct(in1, in2[2]); 252 | } 253 | 254 | 255 | // rotate by the inverse of the matrix 256 | void VectorIRotate (const vec3_t in1, const float in2[3][4], vec3_t out) 257 | { 258 | out[0] = in1[0]*in2[0][0] + in1[1]*in2[1][0] + in1[2]*in2[2][0]; 259 | out[1] = in1[0]*in2[0][1] + in1[1]*in2[1][1] + in1[2]*in2[2][1]; 260 | out[2] = in1[0]*in2[0][2] + in1[1]*in2[1][2] + in1[2]*in2[2][2]; 261 | } 262 | 263 | 264 | void VectorTransform (const vec3_t in1, const float in2[3][4], vec3_t out) 265 | { 266 | out[0] = DotProduct(in1, in2[0]) + in2[0][3]; 267 | out[1] = DotProduct(in1, in2[1]) + in2[1][3]; 268 | out[2] = DotProduct(in1, in2[2]) + in2[2][3]; 269 | } 270 | 271 | 272 | 273 | void AngleQuaternion( const vec3_t angles, vec4_t quaternion ) 274 | { 275 | float angle; 276 | float sr, sp, sy, cr, cp, cy; 277 | 278 | // FIXME: rescale the inputs to 1/2 angle 279 | angle = angles[2] * 0.5; 280 | sy = sin(angle); 281 | cy = cos(angle); 282 | angle = angles[1] * 0.5; 283 | sp = sin(angle); 284 | cp = cos(angle); 285 | angle = angles[0] * 0.5; 286 | sr = sin(angle); 287 | cr = cos(angle); 288 | 289 | quaternion[0] = sr*cp*cy-cr*sp*sy; // X 290 | quaternion[1] = cr*sp*cy+sr*cp*sy; // Y 291 | quaternion[2] = cr*cp*sy-sr*sp*cy; // Z 292 | quaternion[3] = cr*cp*cy+sr*sp*sy; // W 293 | } 294 | 295 | void QuaternionMatrix( const vec4_t quaternion, float (*matrix)[4] ) 296 | { 297 | 298 | matrix[0][0] = 1.0 - 2.0 * quaternion[1] * quaternion[1] - 2.0 * quaternion[2] * quaternion[2]; 299 | matrix[1][0] = 2.0 * quaternion[0] * quaternion[1] + 2.0 * quaternion[3] * quaternion[2]; 300 | matrix[2][0] = 2.0 * quaternion[0] * quaternion[2] - 2.0 * quaternion[3] * quaternion[1]; 301 | 302 | matrix[0][1] = 2.0 * quaternion[0] * quaternion[1] - 2.0 * quaternion[3] * quaternion[2]; 303 | matrix[1][1] = 1.0 - 2.0 * quaternion[0] * quaternion[0] - 2.0 * quaternion[2] * quaternion[2]; 304 | matrix[2][1] = 2.0 * quaternion[1] * quaternion[2] + 2.0 * quaternion[3] * quaternion[0]; 305 | 306 | matrix[0][2] = 2.0 * quaternion[0] * quaternion[2] + 2.0 * quaternion[3] * quaternion[1]; 307 | matrix[1][2] = 2.0 * quaternion[1] * quaternion[2] - 2.0 * quaternion[3] * quaternion[0]; 308 | matrix[2][2] = 1.0 - 2.0 * quaternion[0] * quaternion[0] - 2.0 * quaternion[1] * quaternion[1]; 309 | } 310 | 311 | void QuaternionSlerp( const vec4_t p, vec4_t q, float t, vec4_t qt ) 312 | { 313 | int i; 314 | float omega, cosom, sinom, sclp, sclq; 315 | 316 | // decide if one of the quaternions is backwards 317 | float a = 0; 318 | float b = 0; 319 | for (i = 0; i < 4; i++) { 320 | a += (p[i]-q[i])*(p[i]-q[i]); 321 | b += (p[i]+q[i])*(p[i]+q[i]); 322 | } 323 | if (a > b) { 324 | for (i = 0; i < 4; i++) { 325 | q[i] = -q[i]; 326 | } 327 | } 328 | 329 | cosom = p[0]*q[0] + p[1]*q[1] + p[2]*q[2] + p[3]*q[3]; 330 | 331 | if ((1.0 + cosom) > 0.00000001) { 332 | if ((1.0 - cosom) > 0.00000001) { 333 | omega = acos( cosom ); 334 | sinom = sin( omega ); 335 | sclp = sin( (1.0 - t)*omega) / sinom; 336 | sclq = sin( t*omega ) / sinom; 337 | } 338 | else { 339 | sclp = 1.0 - t; 340 | sclq = t; 341 | } 342 | for (i = 0; i < 4; i++) { 343 | qt[i] = sclp * p[i] + sclq * q[i]; 344 | } 345 | } 346 | else { 347 | qt[0] = -p[1]; 348 | qt[1] = p[0]; 349 | qt[2] = -p[3]; 350 | qt[3] = p[2]; 351 | sclp = sin( (1.0 - t) * 0.5 * Q_PI); 352 | sclq = sin( t * 0.5 * Q_PI); 353 | for (i = 0; i < 3; i++) { 354 | qt[i] = sclp * p[i] + sclq * qt[i]; 355 | } 356 | } 357 | } 358 | 359 | 360 | -------------------------------------------------------------------------------- /src/m/mathlib.h: -------------------------------------------------------------------------------- 1 | /*** 2 | * 3 | * Copyright (c) 1998, Valve LLC. All rights reserved. 4 | * 5 | * This product contains software technology licensed from Id 6 | * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. 7 | * All Rights Reserved. 8 | * 9 | ****/ 10 | 11 | #ifndef __MATHLIB__ 12 | #define __MATHLIB__ 13 | 14 | // mathlib.h 15 | 16 | #include 17 | 18 | #ifdef __cplusplus 19 | extern "C" { 20 | #endif 21 | 22 | #ifdef DOUBLEVEC_T 23 | typedef double vec_t; 24 | #else 25 | typedef float vec_t; 26 | #endif 27 | typedef vec_t vec3_t[3]; // x,y,z 28 | typedef vec_t vec4_t[4]; // x,y,z,w 29 | 30 | #define SIDE_FRONT 0 31 | #define SIDE_ON 2 32 | #define SIDE_BACK 1 33 | #define SIDE_CROSS -2 34 | 35 | #define Q_PI 3.14159265358979323846 36 | 37 | extern vec3_t vec3_origin; 38 | 39 | // Use this definition globally 40 | #define ON_EPSILON 0.01 41 | #define EQUAL_EPSILON 0.001 42 | 43 | int VectorCompare (vec3_t v1, vec3_t v2); 44 | 45 | #define DotProduct(x,y) ((x)[0]*(y)[0]+(x)[1]*(y)[1]+(x)[2]*(y)[2]) 46 | #define VectorFill(a,b) { (a)[0]=(b); (a)[1]=(b); (a)[2]=(b);} 47 | #define VectorAvg(a) ( ( (a)[0] + (a)[1] + (a)[2] ) / 3 ) 48 | #define VectorSubtract(a,b,c) {(c)[0]=(a)[0]-(b)[0];(c)[1]=(a)[1]-(b)[1];(c)[2]=(a)[2]-(b)[2];} 49 | #define VectorAdd(a,b,c) {(c)[0]=(a)[0]+(b)[0];(c)[1]=(a)[1]+(b)[1];(c)[2]=(a)[2]+(b)[2];} 50 | #define VectorCopy(a,b) {(b)[0]=(a)[0];(b)[1]=(a)[1];(b)[2]=(a)[2];} 51 | #define VectorScale(a,b,c) {(c)[0]=(b)*(a)[0];(c)[1]=(b)*(a)[1];(c)[2]=(b)*(a)[2];} 52 | 53 | vec_t Q_rint (vec_t in); 54 | vec_t _DotProduct (vec3_t v1, vec3_t v2); 55 | void _VectorSubtract (vec3_t va, vec3_t vb, vec3_t out); 56 | void _VectorAdd (vec3_t va, vec3_t vb, vec3_t out); 57 | void _VectorCopy (vec3_t in, vec3_t out); 58 | void _VectorScale (vec3_t v, vec_t scale, vec3_t out); 59 | 60 | double VectorLength(vec3_t v); 61 | 62 | void VectorMA (vec3_t va, double scale, vec3_t vb, vec3_t vc); 63 | 64 | void CrossProduct (vec3_t v1, vec3_t v2, vec3_t cross); 65 | vec_t VectorNormalize (vec3_t v); 66 | void VectorInverse (vec3_t v); 67 | 68 | void ClearBounds (vec3_t mins, vec3_t maxs); 69 | void AddPointToBounds (vec3_t v, vec3_t mins, vec3_t maxs); 70 | 71 | void AngleMatrix (const vec3_t angles, float matrix[3][4] ); 72 | void AngleIMatrix (const vec3_t angles, float matrix[3][4] ); 73 | void R_ConcatTransforms (const float in1[3][4], const float in2[3][4], float out[3][4]); 74 | 75 | void VectorIRotate (const vec3_t in1, const float in2[3][4], vec3_t out); 76 | void VectorRotate (const vec3_t in1, const float in2[3][4], vec3_t out); 77 | 78 | void VectorTransform (const vec3_t in1, const float in2[3][4], vec3_t out); 79 | 80 | void AngleQuaternion( const vec3_t angles, vec4_t quaternion ); 81 | void QuaternionMatrix( const vec4_t quaternion, float (*matrix)[4] ); 82 | void QuaternionSlerp( const vec4_t p, vec4_t q, float t, vec4_t qt ); 83 | 84 | 85 | #ifdef __cplusplus 86 | } 87 | #endif 88 | 89 | #endif 90 | -------------------------------------------------------------------------------- /src/m/mod_decryptor.cpp: -------------------------------------------------------------------------------- 1 | #include "mod_decryptor.h" 2 | #include "IceKey.hpp" 3 | #include "StudioModel.h" 4 | 5 | #include 6 | 7 | static const byte g_pDecryptorKey_20[32] = 8 | { 9 | 0x32, 0xA6, 0x21, 0xE0, 0xAB, 0x6B, 0xF4, 0x2C, 10 | 0x93, 0xC6, 0xF1, 0x96, 0xFB, 0x38, 0x75, 0x68, 11 | 0xBA, 0x70, 0x13, 0x86, 0xE0, 0xB3, 0x71, 0xF4, 12 | 0xE3, 0x9B, 0x07, 0x22, 0x0C, 0xFE, 0x88, 0x3A 13 | }; 14 | 15 | static const byte g_pDecryptorKey_21[32] = 16 | { 17 | 0x22, 0x7A, 0x19, 0x6F, 0x7B, 0x86, 0x7D, 0xE0, 18 | 0x8C, 0xC6, 0xF1, 0x96, 0xFB, 0x38, 0x75, 0x68, 19 | 0x88, 0x7A, 0x78, 0x86, 0x78, 0x86, 0x67, 0x70, 20 | 0xD9, 0x91, 0x07, 0x3A, 0x14, 0x74, 0xFE, 0x22 21 | }; 22 | 23 | CIceKey g_Decryptor(4); 24 | 25 | void DecryptChunk(byte *pData, size_t uDataSize) 26 | { 27 | if (!uDataSize) 28 | return; 29 | 30 | size_t uCount = (uDataSize + 7) >> 3; 31 | 32 | while (uCount) 33 | { 34 | g_Decryptor.Decrypt(pData, pData); 35 | pData += 8; 36 | uCount--; 37 | } 38 | } 39 | 40 | void DecryptData(byte *pData, size_t uDataSize) 41 | { 42 | if (!uDataSize) 43 | return; 44 | 45 | do 46 | { 47 | size_t uTempSize = uDataSize; 48 | 49 | if (uTempSize > 1024) 50 | uTempSize = 1024; 51 | 52 | if (uTempSize & 7) 53 | return; 54 | 55 | DecryptChunk(pData, uTempSize); 56 | pData += uTempSize; 57 | uDataSize -= uTempSize; 58 | } while (uDataSize); 59 | } 60 | 61 | bool Mod_IsCSOEncryptedModel(const studiohdr_t *studiohdr) 62 | { 63 | return studiohdr->version == 20 || studiohdr->version == 21; 64 | } 65 | 66 | void Mod_DecryptModel(const char *model_name, studiohdr_t *studiohdr) 67 | { 68 | byte *buffer = reinterpret_cast(studiohdr); 69 | 70 | if (!strncmp(model_name, "models/player", 13)) 71 | { 72 | if (studiohdr->numhitboxes == 21) 73 | studiohdr->numhitboxes = 20; 74 | } 75 | 76 | if (studiohdr->version == 20 || studiohdr->version == 21) 77 | { 78 | if (studiohdr->version == 20) 79 | g_Decryptor.SetKey(g_pDecryptorKey_20); 80 | else if (studiohdr->version == 21) 81 | g_Decryptor.SetKey(g_pDecryptorKey_21); 82 | 83 | mstudiotexture_t *ptexture = (mstudiotexture_t *)(buffer + studiohdr->textureindex); 84 | 85 | for (int i = 0; i < studiohdr->numtextures; i++) 86 | DecryptData(buffer + ptexture[i].index, (ptexture[i].width * ptexture[i].height) + (256 * 3)); 87 | 88 | mstudiobodyparts_t *pbodypart = (mstudiobodyparts_t *)(buffer + studiohdr->bodypartindex); 89 | 90 | for (int i = 0; i < studiohdr->numbodyparts; i++) 91 | { 92 | mstudiomodel_t *pmodel = (mstudiomodel_t *)(buffer + pbodypart[i].modelindex); 93 | 94 | for (int j = 0; j < pbodypart[i].nummodels; j++) 95 | { 96 | if (pmodel[j].numverts > 0) 97 | DecryptData(buffer + pmodel[j].vertindex, pmodel[j].numverts * sizeof(vec3_t)); 98 | } 99 | } 100 | 101 | studiohdr->version = 10; 102 | } 103 | } -------------------------------------------------------------------------------- /src/m/mod_decryptor.h: -------------------------------------------------------------------------------- 1 | #ifndef MOD_DECRYPTOR_H 2 | #define MOD_DECRYPTOR_H 3 | 4 | #include 5 | 6 | bool Mod_IsCSOEncryptedModel(const studiohdr_t *studiohdr); 7 | void Mod_DecryptModel(const char *model_name, studiohdr_t *studiohdr); 8 | 9 | 10 | #endif -------------------------------------------------------------------------------- /src/m/studio.h: -------------------------------------------------------------------------------- 1 | /*** 2 | * 3 | * Copyright (c) 1998, Valve LLC. All rights reserved. 4 | * 5 | * This product contains software technology licensed from Id 6 | * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. 7 | * All Rights Reserved. 8 | * 9 | ****/ 10 | 11 | 12 | 13 | 14 | #ifndef _STUDIO_H_ 15 | #define _STUDIO_H_ 16 | 17 | /* 18 | ============================================================================== 19 | 20 | STUDIO MODELS 21 | 22 | Studio models are position independent, so the cache manager can move them. 23 | ============================================================================== 24 | */ 25 | 26 | 27 | #define MAXSTUDIOTRIANGLES 20000 // TODO: tune this 28 | #define MAXSTUDIOVERTS 2048 // TODO: tune this 29 | #define MAXSTUDIOSEQUENCES 256 // total animation sequences 30 | #define MAXSTUDIOSKINS 100 // total textures 31 | #define MAXSTUDIOSRCBONES 512 // bones allowed at source movement 32 | #define MAXSTUDIOBONES 128 // total bones actually used 33 | #define MAXSTUDIOMODELS 32 // sub-models per model 34 | #define MAXSTUDIOBODYPARTS 32 35 | #define MAXSTUDIOGROUPS 4 36 | #define MAXSTUDIOANIMATIONS 512 // per sequence 37 | #define MAXSTUDIOMESHES 256 38 | #define MAXSTUDIOEVENTS 1024 39 | #define MAXSTUDIOPIVOTS 256 40 | #define MAXSTUDIOCONTROLLERS 8 41 | 42 | typedef struct 43 | { 44 | int id; 45 | int version; 46 | 47 | char name[64]; 48 | int length; 49 | 50 | vec3_t eyeposition; // ideal eye position 51 | vec3_t min; // ideal movement hull size 52 | vec3_t max; 53 | 54 | vec3_t bbmin; // clipping bounding box 55 | vec3_t bbmax; 56 | 57 | int flags; 58 | 59 | int numbones; // bones 60 | int boneindex; 61 | 62 | int numbonecontrollers; // bone controllers 63 | int bonecontrollerindex; 64 | 65 | int numhitboxes; // complex bounding boxes 66 | int hitboxindex; 67 | 68 | int numseq; // animation sequences 69 | int seqindex; 70 | 71 | int numseqgroups; // demand loaded sequences 72 | int seqgroupindex; 73 | 74 | int numtextures; // raw textures 75 | int textureindex; 76 | int texturedataindex; 77 | 78 | int numskinref; // replaceable textures 79 | int numskinfamilies; 80 | int skinindex; 81 | 82 | int numbodyparts; 83 | int bodypartindex; 84 | 85 | int numattachments; // queryable attachable points 86 | int attachmentindex; 87 | 88 | int soundtable; 89 | int soundindex; 90 | int soundgroups; 91 | int soundgroupindex; 92 | 93 | int numtransitions; // animation node to animation node transition graph 94 | int transitionindex; 95 | } studiohdr_t; 96 | 97 | // header for demand loaded sequence group data 98 | typedef struct 99 | { 100 | int id; 101 | int version; 102 | 103 | char name[64]; 104 | int length; 105 | } studioseqhdr_t; 106 | 107 | // bones 108 | typedef struct 109 | { 110 | char name[32]; // bone name for symbolic links 111 | int parent; // parent bone 112 | int flags; // ?? 113 | int bonecontroller[6]; // bone controller index, -1 == none 114 | float value[6]; // default DoF values 115 | float scale[6]; // scale for delta DoF values 116 | } mstudiobone_t; 117 | 118 | 119 | // bone controllers 120 | typedef struct 121 | { 122 | int bone; // -1 == 0 123 | int type; // X, Y, Z, XR, YR, ZR, M 124 | float start; 125 | float end; 126 | int rest; // byte index value at rest 127 | int index; // 0-3 user set controller, 4 mouth 128 | } mstudiobonecontroller_t; 129 | 130 | // intersection boxes 131 | typedef struct 132 | { 133 | int bone; 134 | int group; // intersection group 135 | vec3_t bbmin; // bounding box 136 | vec3_t bbmax; 137 | } mstudiobbox_t; 138 | 139 | // demand loaded sequence groups 140 | typedef struct 141 | { 142 | char label[32]; // textual name 143 | char name[64]; // file name 144 | int cache; // cache index pointer 145 | int data; // hack for group 0 146 | } mstudioseqgroup_t; 147 | 148 | // sequence descriptions 149 | typedef struct 150 | { 151 | char label[32]; // sequence label 152 | 153 | float fps; // frames per second 154 | int flags; // looping/non-looping flags 155 | 156 | int activity; 157 | int actweight; 158 | 159 | int numevents; 160 | int eventindex; 161 | 162 | int numframes; // number of frames per sequence 163 | 164 | int numpivots; // number of foot pivots 165 | int pivotindex; 166 | 167 | int motiontype; 168 | int motionbone; 169 | vec3_t linearmovement; 170 | int automoveposindex; 171 | int automoveangleindex; 172 | 173 | vec3_t bbmin; // per sequence bounding box 174 | vec3_t bbmax; 175 | 176 | int numblends; 177 | int animindex; // mstudioanim_t pointer relative to start of sequence group data 178 | // [blend][bone][X, Y, Z, XR, YR, ZR] 179 | 180 | int blendtype[2]; // X, Y, Z, XR, YR, ZR 181 | float blendstart[2]; // starting value 182 | float blendend[2]; // ending value 183 | int blendparent; 184 | 185 | int seqgroup; // sequence group for demand loading 186 | 187 | int entrynode; // transition node at entry 188 | int exitnode; // transition node at exit 189 | int nodeflags; // transition rules 190 | 191 | int nextseq; // auto advancing sequences 192 | } mstudioseqdesc_t; 193 | 194 | // events 195 | typedef struct 196 | { 197 | int frame; 198 | int event; 199 | int type; 200 | char options[64]; 201 | } mstudioevent_t; 202 | 203 | 204 | // pivots 205 | typedef struct 206 | { 207 | vec3_t org; // pivot point 208 | int start; 209 | int end; 210 | } mstudiopivot_t; 211 | 212 | // attachment 213 | typedef struct 214 | { 215 | char name[32]; 216 | int type; 217 | int bone; 218 | vec3_t org; // attachment point 219 | vec3_t vectors[3]; 220 | } mstudioattachment_t; 221 | 222 | typedef struct 223 | { 224 | unsigned short offset[6]; 225 | } mstudioanim_t; 226 | 227 | // animation frames 228 | typedef union 229 | { 230 | struct { 231 | byte valid; 232 | byte total; 233 | } num; 234 | short value; 235 | } mstudioanimvalue_t; 236 | 237 | 238 | 239 | // body part index 240 | typedef struct 241 | { 242 | char name[64]; 243 | int nummodels; 244 | int base; 245 | int modelindex; // index into models array 246 | } mstudiobodyparts_t; 247 | 248 | 249 | 250 | // skin info 251 | typedef struct 252 | { 253 | char name[64]; 254 | int flags; 255 | int width; 256 | int height; 257 | int index; 258 | } mstudiotexture_t; 259 | 260 | 261 | // skin families 262 | // short index[skinfamilies][skinref] 263 | 264 | // studio models 265 | typedef struct 266 | { 267 | char name[64]; 268 | 269 | int type; 270 | 271 | float boundingradius; 272 | 273 | int nummesh; 274 | int meshindex; 275 | 276 | int numverts; // number of unique vertices 277 | int vertinfoindex; // vertex bone info 278 | int vertindex; // vertex vec3_t 279 | int numnorms; // number of unique surface normals 280 | int norminfoindex; // normal bone info 281 | int normindex; // normal vec3_t 282 | 283 | int numgroups; // deformation groups 284 | int groupindex; 285 | } mstudiomodel_t; 286 | 287 | 288 | // vec3_t boundingbox[model][bone][2]; // complex intersection info 289 | 290 | 291 | // meshes 292 | typedef struct 293 | { 294 | int numtris; 295 | int triindex; 296 | int skinref; 297 | int numnorms; // per mesh normals 298 | int normindex; // normal vec3_t 299 | } mstudiomesh_t; 300 | 301 | // triangles 302 | #if 0 303 | typedef struct 304 | { 305 | short vertindex; // index into vertex array 306 | short normindex; // index into normal array 307 | short s,t; // s,t position on skin 308 | } mstudiotrivert_t; 309 | #endif 310 | 311 | // lighting options 312 | #define STUDIO_NF_FLATSHADE 0x0001 313 | #define STUDIO_NF_CHROME 0x0002 314 | #define STUDIO_NF_FULLBRIGHT 0x0004 315 | #define STUDIO_NF_ADDITIVE 0x0020 316 | #define STUDIO_NF_TRANSPARENT 0x0040 317 | 318 | 319 | // motion flags 320 | #define STUDIO_X 0x0001 321 | #define STUDIO_Y 0x0002 322 | #define STUDIO_Z 0x0004 323 | #define STUDIO_XR 0x0008 324 | #define STUDIO_YR 0x0010 325 | #define STUDIO_ZR 0x0020 326 | #define STUDIO_LX 0x0040 327 | #define STUDIO_LY 0x0080 328 | #define STUDIO_LZ 0x0100 329 | #define STUDIO_AX 0x0200 330 | #define STUDIO_AY 0x0400 331 | #define STUDIO_AZ 0x0800 332 | #define STUDIO_AXR 0x1000 333 | #define STUDIO_AYR 0x2000 334 | #define STUDIO_AZR 0x4000 335 | #define STUDIO_TYPES 0x7FFF 336 | #define STUDIO_RLOOP 0x8000 // controller that wraps shortest distance 337 | 338 | // sequence flags 339 | #define STUDIO_LOOPING 0x0001 340 | 341 | // bone flags 342 | #define STUDIO_HAS_NORMALS 0x0001 343 | #define STUDIO_HAS_VERTICES 0x0002 344 | #define STUDIO_HAS_BBOX 0x0004 345 | #define STUDIO_HAS_CHROME 0x0008 // if any of the textures have chrome on them 346 | 347 | #define RAD_TO_STUDIO (32768.0/M_PI) 348 | #define STUDIO_TO_RAD (M_PI/32768.0) 349 | 350 | #endif 351 | -------------------------------------------------------------------------------- /src/m/studio_utils.cpp: -------------------------------------------------------------------------------- 1 | /*** 2 | * 3 | * Copyright (c) 1998, Valve LLC. All rights reserved. 4 | * 5 | * This product contains software technology licensed from Id 6 | * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. 7 | * All Rights Reserved. 8 | * 9 | ****/ 10 | // updates: 11 | // 1-4-99 fixed file texture load and file read bug 12 | 13 | //////////////////////////////////////////////////////////////////////// 14 | #include 15 | #include 16 | #include 17 | #include 18 | //#include 19 | #include "StudioModel.h" 20 | #include "mod_decryptor.h" 21 | #include 22 | #include 23 | #include "TGAlib.h" 24 | #include "bmpread.h" 25 | 26 | 27 | #pragma warning( disable : 4244 ) // double to float 28 | 29 | //extern float g_bonetransform[MAXSTUDIOBONES][3][4]; 30 | 31 | 32 | StudioModel g_studioModel; 33 | 34 | //////////////////////////////////////////////////////////////////////// 35 | 36 | static int g_texnum = 3; 37 | 38 | //Mugsy - upped the maximum texture size to 512. All changes are the replacement of '256' 39 | //with this define, MAX_TEXTURE_DIMS 40 | #define MAX_TEXTURE_DIMS 512 41 | 42 | void StudioModel::UploadTexture(const mstudiotexture_t *ptexture, const byte *data, const byte *pal, int name) 43 | { 44 | // unsigned *in, int inwidth, int inheight, unsigned *out, int outwidth, int outheight; 45 | int i, j; 46 | int row1[MAX_TEXTURE_DIMS], row2[MAX_TEXTURE_DIMS], col1[MAX_TEXTURE_DIMS], col2[MAX_TEXTURE_DIMS]; 47 | const byte *pix1, *pix2, *pix3, *pix4; 48 | byte *tex, *out; 49 | 50 | // convert texture to power of 2 51 | int outwidth; 52 | for (outwidth = 1; outwidth < ptexture->width; outwidth <<= 1) 53 | ; 54 | 55 | if (outwidth > MAX_TEXTURE_DIMS) 56 | outwidth = MAX_TEXTURE_DIMS; 57 | 58 | int outheight; 59 | for (outheight = 1; outheight < ptexture->height; outheight <<= 1) 60 | ; 61 | 62 | if (outheight > MAX_TEXTURE_DIMS) 63 | outheight = MAX_TEXTURE_DIMS; 64 | 65 | tex = out = (byte *)malloc( outwidth * outheight * 4); 66 | if (!out) 67 | { 68 | return; 69 | } 70 | /* 71 | int k = 0; 72 | for (i = 0; i < ptexture->height; i++) 73 | { 74 | for (j = 0; j < ptexture->width; j++) 75 | { 76 | 77 | in[k++] = pal[data[i * ptexture->width + j] * 3 + 0]; 78 | in[k++] = pal[data[i * ptexture->width + j] * 3 + 1]; 79 | in[k++] = pal[data[i * ptexture->width + j] * 3 + 2]; 80 | in[k++] = 0xff;; 81 | } 82 | } 83 | 84 | gluScaleImage (GL_RGBA, ptexture->width, ptexture->height, GL_UNSIGNED_BYTE, in, outwidth, outheight, GL_UNSIGNED_BYTE, out); 85 | free (in); 86 | */ 87 | 88 | for (i = 0; i < outwidth; i++) 89 | { 90 | col1[i] = (int) ((i + 0.25) * (ptexture->width / (float)outwidth)); 91 | col2[i] = (int) ((i + 0.75) * (ptexture->width / (float)outwidth)); 92 | } 93 | 94 | for (i = 0; i < outheight; i++) 95 | { 96 | row1[i] = (int) ((i + 0.25) * (ptexture->height / (float)outheight)) * ptexture->width; 97 | row2[i] = (int) ((i + 0.75) * (ptexture->height / (float)outheight)) * ptexture->width; 98 | } 99 | 100 | // scale down and convert to 32bit RGB 101 | for (i=0 ; i>2; 111 | out[1] = (pix1[1] + pix2[1] + pix3[1] + pix4[1])>>2; 112 | out[2] = (pix1[2] + pix2[2] + pix3[2] + pix4[2])>>2; 113 | out[3] = 0xFF; 114 | } 115 | } 116 | 117 | glBindTexture( GL_TEXTURE_2D, name ); //g_texnum ); 118 | glTexImage2D( GL_TEXTURE_2D, 0, 3/*??*/, outwidth, outheight, 0, GL_RGBA, GL_UNSIGNED_BYTE, tex ); 119 | glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); 120 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 121 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 122 | 123 | // ptexture->width = outwidth; 124 | // ptexture->height = outheight; 125 | //ptexture->index = name; //g_texnum; 126 | 127 | free( tex ); 128 | } 129 | 130 | 131 | 132 | void 133 | StudioModel::FreeModel () 134 | { 135 | if (m_pstudiohdr) 136 | free (m_pstudiohdr); 137 | 138 | if (m_ptexturehdr && m_owntexmodel) 139 | free (m_ptexturehdr); 140 | 141 | m_pstudiohdr = m_ptexturehdr = 0; 142 | m_owntexmodel = false; 143 | 144 | int i; 145 | for (i = 0; i < 32; i++) 146 | { 147 | if (m_panimhdr[i]) 148 | { 149 | free (m_panimhdr[i]); 150 | m_panimhdr[i] = 0; 151 | } 152 | } 153 | 154 | // deleting textures 155 | g_texnum -= 3; 156 | int textures[MAXSTUDIOSKINS]; 157 | for (i = 0; i < g_texnum; i++) 158 | textures[i] = i + 3; 159 | 160 | glDeleteTextures (g_texnum, (const GLuint *) textures); 161 | 162 | g_texnum = 3; 163 | } 164 | 165 | 166 | 167 | studiohdr_t *StudioModel::LoadModel( const char *modelname ) 168 | { 169 | FILE *fp; 170 | long size; 171 | void *buffer; 172 | 173 | if (!modelname) 174 | return 0; 175 | 176 | // load the model 177 | if( (fp = fopen( modelname, "rb" )) == NULL) 178 | return 0; 179 | 180 | fseek( fp, 0, SEEK_END ); 181 | size = ftell( fp ); 182 | fseek( fp, 0, SEEK_SET ); 183 | 184 | buffer = malloc( size ); 185 | if (!buffer) 186 | { 187 | fclose (fp); 188 | return 0; 189 | } 190 | 191 | fread( buffer, size, 1, fp ); 192 | fclose( fp ); 193 | 194 | 195 | if (strncmp ((const char *) buffer, "IDST", 4) && 196 | strncmp ((const char *) buffer, "IDSQ", 4)) 197 | { 198 | free (buffer); 199 | return nullptr; 200 | } 201 | 202 | if (!strncmp ((const char *) buffer, "IDSQ", 4) && !m_pstudiohdr) 203 | { 204 | free (buffer); 205 | return nullptr; 206 | } 207 | 208 | return (studiohdr_t *)buffer; 209 | } 210 | 211 | void StudioModel::LoadModelTextures( const studiohdr_t *phdr) 212 | { 213 | const byte *pin = reinterpret_cast(phdr); 214 | const mstudiotexture_t *ptexture = reinterpret_cast(pin + phdr->textureindex); 215 | 216 | if (phdr->textureindex > 0 && phdr->numtextures <= MAXSTUDIOSKINS) 217 | { 218 | int n = phdr->numtextures; 219 | for (int i = 0; i < n; i++) 220 | { 221 | // strcpy( name, mod->name ); 222 | // strcpy( name, ptexture[i].name ); 223 | UploadTexture( &ptexture[i], pin + ptexture[i].index, pin + ptexture[i].width * ptexture[i].height + ptexture[i].index, g_texnum++ ); 224 | } 225 | } 226 | 227 | // UNDONE: free texture memory 228 | } 229 | 230 | void StudioModel::LoadModelTexturesCSO( studiohdr_t *phdr, const char *texturePath) 231 | { 232 | byte *pin = reinterpret_cast(phdr); 233 | mstudiotexture_t *ptexture = reinterpret_cast(pin + phdr->textureindex); 234 | 235 | if (phdr->textureindex > 0 && phdr->numtextures <= MAXSTUDIOSKINS) 236 | { 237 | int n = phdr->numtextures; 238 | for (int i = 0; i < n; i++) 239 | { 240 | // strcpy( name, mod->name ); 241 | // strcpy( name, ptexture[i].name ); 242 | if(isCSOExternalTexture(ptexture[i])) 243 | { 244 | std::string path = texturePath; 245 | path.push_back('/'); 246 | path += (ptexture[i].name); 247 | 248 | if(UploadTextureTGA(&ptexture[i], path.c_str(), g_texnum) || UploadTextureBMP(&ptexture[i], path.c_str(), g_texnum)) 249 | { 250 | // ... 251 | char buffer[64] = "*CSO* "; 252 | strcpy(ptexture[i].name, strcat(buffer, ptexture[i].name)); 253 | } 254 | else 255 | { 256 | UploadTexture( &ptexture[i], pin + ptexture[i].index, pin + ptexture[i].width * ptexture[i].height + ptexture[i].index, g_texnum ); 257 | } 258 | ++g_texnum; 259 | } 260 | else 261 | { 262 | UploadTexture( &ptexture[i], pin + ptexture[i].index, pin + ptexture[i].width * ptexture[i].height + ptexture[i].index, g_texnum++ ); 263 | } 264 | 265 | } 266 | } 267 | 268 | // UNDONE: free texture memory 269 | } 270 | 271 | bool StudioModel::PostLoadModel( studiohdr_t *phdr, const char *modelname ) 272 | { 273 | studiohdr_t *ptexturehdr = nullptr; 274 | bool owntexmodel = false; 275 | 276 | // preload textures 277 | if (phdr->numtextures == 0) 278 | { 279 | char texturename[256]; 280 | 281 | strcpy( texturename, modelname ); 282 | strcpy( &texturename[strlen(texturename) - 4], "T.mdl" ); 283 | 284 | ptexturehdr = LoadModel( texturename ); 285 | if (!ptexturehdr) 286 | { 287 | FreeModel (); 288 | return false; 289 | } 290 | owntexmodel = true; 291 | } 292 | else 293 | { 294 | ptexturehdr = phdr; 295 | owntexmodel = false; 296 | } 297 | 298 | // preload animations 299 | if (phdr->numseqgroups > 1) 300 | { 301 | for (int i = 1; i < phdr->numseqgroups; i++) 302 | { 303 | char seqgroupname[256]; 304 | 305 | strcpy( seqgroupname, modelname ); 306 | sprintf( &seqgroupname[strlen(seqgroupname) - 4], "%02d.mdl", i ); 307 | 308 | m_panimhdr[i] = LoadModel( seqgroupname ); 309 | if (!m_panimhdr[i]) 310 | { 311 | FreeModel (); 312 | return false; 313 | } 314 | } 315 | } 316 | 317 | m_pstudiohdr = phdr; 318 | m_ptexturehdr = ptexturehdr; 319 | m_owntexmodel = owntexmodel; 320 | 321 | SetSequence (0); 322 | SetController (0, 0.0f); 323 | SetController (1, 0.0f); 324 | SetController (2, 0.0f); 325 | SetController (3, 0.0f); 326 | SetMouth (0.0f); 327 | 328 | int n; 329 | for (n = 0; n < phdr->numbodyparts; n++) 330 | SetBodygroup (n, 0); 331 | 332 | SetSkin (0); 333 | /* 334 | vec3_t mins, maxs; 335 | ExtractBbox (mins, maxs); 336 | if (mins[2] < 5.0f) 337 | m_origin[2] = -mins[2]; 338 | */ 339 | 340 | return true; 341 | } 342 | 343 | 344 | 345 | bool StudioModel::SaveModel ( const char *modelname ) 346 | { 347 | if (!modelname) 348 | return false; 349 | 350 | if (!m_pstudiohdr) 351 | return false; 352 | 353 | FILE *file; 354 | 355 | file = fopen (modelname, "wb"); 356 | if (!file) 357 | return false; 358 | 359 | std::vector data_to_save(reinterpret_cast(m_pstudiohdr), reinterpret_cast(m_pstudiohdr) + m_pstudiohdr->length); 360 | 361 | { 362 | studiohdr_t *phdr = reinterpret_cast(data_to_save.data()); 363 | mstudiotexture_t *ptexture = reinterpret_cast(data_to_save.data() + phdr->textureindex); 364 | 365 | if (phdr->textureindex > 0 && phdr->numtextures <= MAXSTUDIOSKINS) { 366 | int n = phdr->numtextures; 367 | for (int i = 0; i < n; i++) { 368 | if(strncmp(ptexture[i].name, "*CSO* ", 5) != 0) 369 | continue; 370 | // TODO : convert and save external texture 371 | #if 0 372 | auto old_size = data_to_save.size(); 373 | { 374 | data_to_save.resize(old_size + ptexture[i].width * ptexture[i].height * 3 + 256); 375 | // due to vector resize, pointers may be expired 376 | phdr = reinterpret_cast(data_to_save.data()); 377 | ptexture = reinterpret_cast(data_to_save.data() + phdr->textureindex); 378 | } 379 | 380 | //glGetTexImage(GL_TEXTURE_2D, 0, GL_COLOR_INDEX, GL_UNSIGNED_BYTE, data_to_save.data() + old_size); 381 | ptexture[i].index = old_size; 382 | #endif 383 | 384 | { 385 | char szName[64]; 386 | strcpy(szName, ptexture[i].name + 5); 387 | strcpy(ptexture[i].name, szName); 388 | ptexture[i].width = 4; 389 | ptexture[i].height = 1; 390 | } 391 | } 392 | } 393 | } 394 | 395 | fwrite (data_to_save.data(), sizeof (byte), data_to_save.size(), file); 396 | fclose (file); 397 | 398 | // write texture model 399 | if (m_owntexmodel && m_ptexturehdr) 400 | { 401 | char texturename[256]; 402 | 403 | strcpy( texturename, modelname ); 404 | strcpy( &texturename[strlen(texturename) - 4], "T.mdl" ); 405 | 406 | file = fopen (texturename, "wb"); 407 | if (file) 408 | { 409 | fwrite (m_ptexturehdr, sizeof (byte), m_ptexturehdr->length, file); 410 | fclose (file); 411 | } 412 | } 413 | 414 | // write seq groups 415 | if (m_pstudiohdr->numseqgroups > 1) 416 | { 417 | for (int i = 1; i < m_pstudiohdr->numseqgroups; i++) 418 | { 419 | char seqgroupname[256]; 420 | 421 | strcpy( seqgroupname, modelname ); 422 | sprintf( &seqgroupname[strlen(seqgroupname) - 4], "%02d.mdl", i ); 423 | 424 | file = fopen (seqgroupname, "wb"); 425 | if (file) 426 | { 427 | fwrite (m_panimhdr[i], sizeof (byte), m_panimhdr[i]->length, file); 428 | fclose (file); 429 | } 430 | } 431 | } 432 | 433 | return true; 434 | } 435 | 436 | 437 | 438 | //////////////////////////////////////////////////////////////////////// 439 | 440 | int StudioModel::GetSequence( ) 441 | { 442 | return m_sequence; 443 | } 444 | 445 | int StudioModel::SetSequence( int iSequence ) 446 | { 447 | if (iSequence > m_pstudiohdr->numseq) 448 | return m_sequence; 449 | 450 | m_sequence = iSequence; 451 | m_frame = 0; 452 | 453 | return m_sequence; 454 | } 455 | 456 | 457 | void StudioModel::ExtractBbox( float *mins, float *maxs ) 458 | { 459 | mstudioseqdesc_t *pseqdesc; 460 | 461 | pseqdesc = (mstudioseqdesc_t *)((byte *)m_pstudiohdr + m_pstudiohdr->seqindex); 462 | 463 | mins[0] = pseqdesc[ m_sequence ].bbmin[0]; 464 | mins[1] = pseqdesc[ m_sequence ].bbmin[1]; 465 | mins[2] = pseqdesc[ m_sequence ].bbmin[2]; 466 | 467 | maxs[0] = pseqdesc[ m_sequence ].bbmax[0]; 468 | maxs[1] = pseqdesc[ m_sequence ].bbmax[1]; 469 | maxs[2] = pseqdesc[ m_sequence ].bbmax[2]; 470 | } 471 | 472 | 473 | 474 | void StudioModel::GetSequenceInfo( float *pflFrameRate, float *pflGroundSpeed ) 475 | { 476 | mstudioseqdesc_t *pseqdesc; 477 | 478 | pseqdesc = (mstudioseqdesc_t *)((byte *)m_pstudiohdr + m_pstudiohdr->seqindex) + (int)m_sequence; 479 | 480 | if (pseqdesc->numframes > 1) 481 | { 482 | *pflFrameRate = 256 * pseqdesc->fps / (pseqdesc->numframes - 1); 483 | *pflGroundSpeed = sqrt( pseqdesc->linearmovement[0]*pseqdesc->linearmovement[0]+ pseqdesc->linearmovement[1]*pseqdesc->linearmovement[1]+ pseqdesc->linearmovement[2]*pseqdesc->linearmovement[2] ); 484 | *pflGroundSpeed = *pflGroundSpeed * pseqdesc->fps / (pseqdesc->numframes - 1); 485 | } 486 | else 487 | { 488 | *pflFrameRate = 256.0; 489 | *pflGroundSpeed = 0.0; 490 | } 491 | } 492 | 493 | 494 | 495 | float StudioModel::SetController( int iController, float flValue ) 496 | { 497 | if (!m_pstudiohdr) 498 | return 0.0f; 499 | 500 | mstudiobonecontroller_t *pbonecontroller = (mstudiobonecontroller_t *)((byte *)m_pstudiohdr + m_pstudiohdr->bonecontrollerindex); 501 | 502 | // find first controller that matches the index 503 | int i; 504 | for (i = 0; i < m_pstudiohdr->numbonecontrollers; i++, pbonecontroller++) 505 | { 506 | if (pbonecontroller->index == iController) 507 | break; 508 | } 509 | if (i >= m_pstudiohdr->numbonecontrollers) 510 | return flValue; 511 | 512 | // wrap 0..360 if it's a rotational controller 513 | if (pbonecontroller->type & (STUDIO_XR | STUDIO_YR | STUDIO_ZR)) 514 | { 515 | // ugly hack, invert value if end < start 516 | if (pbonecontroller->end < pbonecontroller->start) 517 | flValue = -flValue; 518 | 519 | // does the controller not wrap? 520 | if (pbonecontroller->start + 359.0 >= pbonecontroller->end) 521 | { 522 | if (flValue > ((pbonecontroller->start + pbonecontroller->end) / 2.0) + 180) 523 | flValue = flValue - 360; 524 | if (flValue < ((pbonecontroller->start + pbonecontroller->end) / 2.0) - 180) 525 | flValue = flValue + 360; 526 | } 527 | else 528 | { 529 | if (flValue > 360) 530 | flValue = flValue - (int)(flValue / 360.0) * 360.0; 531 | else if (flValue < 0) 532 | flValue = flValue + (int)((flValue / -360.0) + 1) * 360.0; 533 | } 534 | } 535 | 536 | int setting = (int) (255 * (flValue - pbonecontroller->start) / 537 | (pbonecontroller->end - pbonecontroller->start)); 538 | 539 | if (setting < 0) setting = 0; 540 | if (setting > 255) setting = 255; 541 | m_controller[iController] = setting; 542 | 543 | return setting * (1.0 / 255.0) * (pbonecontroller->end - pbonecontroller->start) + pbonecontroller->start; 544 | } 545 | 546 | 547 | float StudioModel::SetMouth( float flValue ) 548 | { 549 | if (!m_pstudiohdr) 550 | return 0.0f; 551 | 552 | mstudiobonecontroller_t *pbonecontroller = (mstudiobonecontroller_t *)((byte *)m_pstudiohdr + m_pstudiohdr->bonecontrollerindex); 553 | 554 | // find first controller that matches the mouth 555 | for (int i = 0; i < m_pstudiohdr->numbonecontrollers; i++, pbonecontroller++) 556 | { 557 | if (pbonecontroller->index == 4) 558 | break; 559 | } 560 | 561 | // wrap 0..360 if it's a rotational controller 562 | if (pbonecontroller->type & (STUDIO_XR | STUDIO_YR | STUDIO_ZR)) 563 | { 564 | // ugly hack, invert value if end < start 565 | if (pbonecontroller->end < pbonecontroller->start) 566 | flValue = -flValue; 567 | 568 | // does the controller not wrap? 569 | if (pbonecontroller->start + 359.0 >= pbonecontroller->end) 570 | { 571 | if (flValue > ((pbonecontroller->start + pbonecontroller->end) / 2.0) + 180) 572 | flValue = flValue - 360; 573 | if (flValue < ((pbonecontroller->start + pbonecontroller->end) / 2.0) - 180) 574 | flValue = flValue + 360; 575 | } 576 | else 577 | { 578 | if (flValue > 360) 579 | flValue = flValue - (int)(flValue / 360.0) * 360.0; 580 | else if (flValue < 0) 581 | flValue = flValue + (int)((flValue / -360.0) + 1) * 360.0; 582 | } 583 | } 584 | 585 | int setting = (int) (64 * (flValue - pbonecontroller->start) / (pbonecontroller->end - pbonecontroller->start)); 586 | 587 | if (setting < 0) setting = 0; 588 | if (setting > 64) setting = 64; 589 | m_mouth = setting; 590 | 591 | return setting * (1.0 / 64.0) * (pbonecontroller->end - pbonecontroller->start) + pbonecontroller->start; 592 | } 593 | 594 | 595 | float StudioModel::SetBlending( int iBlender, float flValue ) 596 | { 597 | mstudioseqdesc_t *pseqdesc; 598 | 599 | if (!m_pstudiohdr) 600 | return 0.0f; 601 | 602 | pseqdesc = (mstudioseqdesc_t *)((byte *)m_pstudiohdr + m_pstudiohdr->seqindex) + (int)m_sequence; 603 | 604 | if (pseqdesc->blendtype[iBlender] == 0) 605 | return flValue; 606 | 607 | if (pseqdesc->blendtype[iBlender] & (STUDIO_XR | STUDIO_YR | STUDIO_ZR)) 608 | { 609 | // ugly hack, invert value if end < start 610 | if (pseqdesc->blendend[iBlender] < pseqdesc->blendstart[iBlender]) 611 | flValue = -flValue; 612 | 613 | // does the controller not wrap? 614 | if (pseqdesc->blendstart[iBlender] + 359.0 >= pseqdesc->blendend[iBlender]) 615 | { 616 | if (flValue > ((pseqdesc->blendstart[iBlender] + pseqdesc->blendend[iBlender]) / 2.0) + 180) 617 | flValue = flValue - 360; 618 | if (flValue < ((pseqdesc->blendstart[iBlender] + pseqdesc->blendend[iBlender]) / 2.0) - 180) 619 | flValue = flValue + 360; 620 | } 621 | } 622 | 623 | int setting = (int) (255 * (flValue - pseqdesc->blendstart[iBlender]) / (pseqdesc->blendend[iBlender] - pseqdesc->blendstart[iBlender])); 624 | 625 | if (setting < 0) setting = 0; 626 | if (setting > 255) setting = 255; 627 | 628 | m_blending[iBlender] = setting; 629 | 630 | return setting * (1.0 / 255.0) * (pseqdesc->blendend[iBlender] - pseqdesc->blendstart[iBlender]) + pseqdesc->blendstart[iBlender]; 631 | } 632 | 633 | 634 | 635 | int StudioModel::SetBodygroup( int iGroup, int iValue ) 636 | { 637 | if (!m_pstudiohdr) 638 | return 0; 639 | 640 | if (iGroup > m_pstudiohdr->numbodyparts) 641 | return -1; 642 | 643 | mstudiobodyparts_t *pbodypart = (mstudiobodyparts_t *)((byte *)m_pstudiohdr + m_pstudiohdr->bodypartindex) + iGroup; 644 | 645 | int iCurrent = (m_bodynum / pbodypart->base) % pbodypart->nummodels; 646 | 647 | if (iValue >= pbodypart->nummodels) 648 | return iCurrent; 649 | 650 | m_bodynum = (m_bodynum - (iCurrent * pbodypart->base) + (iValue * pbodypart->base)); 651 | 652 | return iValue; 653 | } 654 | 655 | 656 | int StudioModel::SetSkin( int iValue ) 657 | { 658 | if (!m_ptexturehdr) 659 | return 0; 660 | 661 | if (iValue >= m_ptexturehdr->numskinfamilies) 662 | { 663 | return m_skinnum; 664 | } 665 | 666 | m_skinnum = iValue; 667 | 668 | return iValue; 669 | } 670 | 671 | 672 | 673 | void StudioModel::scaleMeshes (float scale) 674 | { 675 | if (!m_pstudiohdr) 676 | return; 677 | 678 | int i, j, k; 679 | 680 | // scale verts 681 | int tmp = m_bodynum; 682 | for (i = 0; i < m_pstudiohdr->numbodyparts; i++) 683 | { 684 | mstudiobodyparts_t *pbodypart = (mstudiobodyparts_t *)((byte *)m_pstudiohdr + m_pstudiohdr->bodypartindex) + i; 685 | for (j = 0; j < pbodypart->nummodels; j++) 686 | { 687 | SetBodygroup (i, j); 688 | SetupModel (i); 689 | 690 | vec3_t *pstudioverts = (vec3_t *)((byte *)m_pstudiohdr + m_pmodel->vertindex); 691 | 692 | for (k = 0; k < m_pmodel->numverts; k++) 693 | VectorScale (pstudioverts[k], scale, pstudioverts[k]); 694 | } 695 | } 696 | 697 | m_bodynum = tmp; 698 | 699 | // scale complex hitboxes 700 | mstudiobbox_t *pbboxes = (mstudiobbox_t *) ((byte *) m_pstudiohdr + m_pstudiohdr->hitboxindex); 701 | for (i = 0; i < m_pstudiohdr->numhitboxes; i++) 702 | { 703 | VectorScale (pbboxes[i].bbmin, scale, pbboxes[i].bbmin); 704 | VectorScale (pbboxes[i].bbmax, scale, pbboxes[i].bbmax); 705 | } 706 | 707 | // scale bounding boxes 708 | mstudioseqdesc_t *pseqdesc = (mstudioseqdesc_t *)((byte *)m_pstudiohdr + m_pstudiohdr->seqindex); 709 | for (i = 0; i < m_pstudiohdr->numseq; i++) 710 | { 711 | VectorScale (pseqdesc[i].bbmin, scale, pseqdesc[i].bbmin); 712 | VectorScale (pseqdesc[i].bbmax, scale, pseqdesc[i].bbmax); 713 | } 714 | 715 | // maybe scale exeposition, pivots, attachments 716 | } 717 | 718 | 719 | 720 | void StudioModel::scaleBones (float scale) 721 | { 722 | if (!m_pstudiohdr) 723 | return; 724 | 725 | mstudiobone_t *pbones = (mstudiobone_t *) ((byte *) m_pstudiohdr + m_pstudiohdr->boneindex); 726 | for (int i = 0; i < m_pstudiohdr->numbones; i++) 727 | { 728 | for (int j = 0; j < 3; j++) 729 | { 730 | pbones[i].value[j] *= scale; 731 | pbones[i].scale[j] *= scale; 732 | } 733 | } 734 | } 735 | 736 | bool StudioModel::hasCSOTexture(const studiohdr_t *phdr) 737 | { 738 | const byte *pin = reinterpret_cast(phdr); 739 | const mstudiotexture_t *ptextures = reinterpret_cast(pin + phdr->textureindex); 740 | 741 | if (phdr->textureindex > 0 && phdr->numtextures <= MAXSTUDIOSKINS) 742 | { 743 | int n = phdr->numtextures; 744 | return std::any_of(ptextures, ptextures + n, isCSOExternalTexture); 745 | } 746 | 747 | return false; 748 | } 749 | bool StudioModel::isCSOExternalTexture(const mstudiotexture_t &texture) 750 | { 751 | return texture.width == 4 && texture.height == 1 && (texture.name[0] == '#' || texture.name[0] == '$'); 752 | } 753 | 754 | bool StudioModel::UploadTextureTGA(mstudiotexture_t *ptexture, const char *path, int name) 755 | { 756 | tImageTGA *tga = tgaLoad(path); 757 | if(tga->status != TGA_OK) 758 | return false; 759 | std::unique_ptr ptga(tga, tgaDestroy); 760 | 761 | glBindTexture( GL_TEXTURE_2D, name ); //g_texnum ); 762 | if(tga->pixelDepth == 32) 763 | glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA8, tga->width, tga->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, tga->imageData ); 764 | else 765 | glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB5_A1, tga->width, tga->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, tga->imageData ); 766 | glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); 767 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 768 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 769 | 770 | ptexture->width = tga->width; 771 | ptexture->height = tga->height; 772 | 773 | return true; 774 | } 775 | 776 | bool StudioModel::UploadTextureBMP(mstudiotexture_t *ptexture, const char *path, int name) 777 | { 778 | bmpread_t bitmap; 779 | if(!bmpread(path, BMPREAD_TOP_DOWN, &bitmap)) 780 | { 781 | return false; 782 | } 783 | std::unique_ptr pbmp(&bitmap, bmpread_free); 784 | 785 | /* At this point, bitmap.width and .height hold the pixel dimensions of the 786 | * file, and bitmap.data holds the raw pixel data in RGB triplets. 787 | */ 788 | 789 | glBindTexture(GL_TEXTURE_2D, name); 790 | 791 | glTexImage2D(GL_TEXTURE_2D, 0, 3, bitmap.width, bitmap.height, 0, GL_RGB, GL_UNSIGNED_BYTE, bitmap.data); 792 | 793 | glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); 794 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 795 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 796 | 797 | ptexture->width = bitmap.width; 798 | ptexture->height = bitmap.height; 799 | 800 | return true; 801 | } 802 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "hlmv_application.h" 2 | 3 | int main(int argc, char *argv[]) 4 | { 5 | return HLMVApplication(argc, argv).exec(); 6 | } 7 | -------------------------------------------------------------------------------- /src/v/hlmv.cpp: -------------------------------------------------------------------------------- 1 | #include "hlmv.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | QtGuiApplication1::QtGuiApplication1(QWidget *parent) 8 | : QMainWindow(parent) 9 | { 10 | this->setMinimumWidth(QApplication::primaryScreen()->availableVirtualSize().width() / 2); 11 | this->setMinimumHeight(width() / 3 * 2); 12 | 13 | ui.setupUi(this); 14 | 15 | connect(ui.actionOpen, &QAction::triggered, this, &QtGuiApplication1::OnActionOpen); 16 | connect(ui.actionSave, &QAction::triggered, this, &QtGuiApplication1::OnActionSave); 17 | connect(ui.actionClear, &QAction::triggered, this, &QtGuiApplication1::OnActionClear); 18 | connect(ui.actionAbout, &QAction::triggered, this, &QtGuiApplication1::OnActionAbout); 19 | updateStatusBar("Drag model file to open"); 20 | setAcceptDrops(true); 21 | 22 | SetupPlatformWindow(); 23 | } 24 | 25 | void QtGuiApplication1::OnActionOpen() 26 | { 27 | QString filename = QFileDialog::getOpenFileName(this, "Load model file", {}, tr( 28 | "Model file (*.mdl)" 29 | )); 30 | if(!filename.isEmpty()) 31 | { 32 | try 33 | { 34 | OpenFile(filename); 35 | } 36 | catch (const std::exception &e) 37 | { 38 | QMessageBox::warning(this, "Error", e.what()); 39 | } 40 | } 41 | } 42 | 43 | void QtGuiApplication1::OnActionSave() 44 | { 45 | QString filename = QFileDialog::getSaveFileName(this, "Save model file", {}, tr("Model file (*.mdl)")); 46 | if (!filename.isEmpty()) 47 | { 48 | try 49 | { 50 | SaveFile(filename); 51 | } 52 | catch (const std::exception &e) 53 | { 54 | QMessageBox::warning(this, "Error", e.what()); 55 | } 56 | } 57 | } 58 | 59 | void QtGuiApplication1::OnActionClear() 60 | { 61 | CloseFile(); 62 | } 63 | 64 | void QtGuiApplication1::OnActionAbout() 65 | { 66 | QMessageBox::about(this, "About", "Made by MoeMod in Summer 2019."); 67 | } 68 | 69 | -------------------------------------------------------------------------------- /src/v/hlmv.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "ui_hlmv.h" 5 | 6 | class QtGuiApplication1 : public QMainWindow 7 | { 8 | Q_OBJECT 9 | 10 | public: 11 | QtGuiApplication1(QWidget *parent = Q_NULLPTR); 12 | 13 | friend class MyOpenGLWidget; 14 | 15 | public slots: 16 | void OnActionOpen(); 17 | void OnActionSave(); 18 | void OnActionClear(); 19 | void OnActionAbout(); 20 | void OnTabChanged(int); 21 | 22 | void setSequence(int); 23 | void OnSetAnimationPlaying(bool); 24 | void OnSetAnimationCurrentFrame(int); 25 | void OnSetAnimationFPS(double); 26 | 27 | void setEventInfo(int index); 28 | void setMesh(int index); 29 | 30 | void setTextureCurrent(int index); 31 | void setTextureScale(int value); 32 | void updateTextureFlags(); 33 | void updateTextureUVDisplay(); 34 | void onImportTexture(); 35 | void onExportTexture(); 36 | 37 | void setBodypart (int index); 38 | void setSubmodel (int index); 39 | void setSkin (int index); 40 | void setBoneController (int index); 41 | void setBoneControllerCurrentValue (int value); 42 | 43 | void updateViewSettings(); 44 | void updateWeaponOriginSettings(); 45 | 46 | signals: 47 | void updateStatusBar(QString str); 48 | 49 | protected: 50 | void dragEnterEvent(QDragEnterEvent *e) override; 51 | void dropEvent(QDropEvent *e) override; 52 | 53 | public: 54 | void OpenFile(QString str) noexcept(false); 55 | void SaveFile(QString str) noexcept(false); 56 | void CloseFile(); 57 | void initSequences (); 58 | void setSequenceInfo (); 59 | void setEvent (int seq); 60 | void initTextures (); 61 | void initMeshList (int index); 62 | void initBodyparts(); 63 | void initBoneControllers (); 64 | void setBoneControllerValue (int index, float value); 65 | void initSkins (); 66 | void setModelInfo (); 67 | void centerView(); 68 | 69 | public: 70 | #ifdef Q_OS_MAC 71 | void SetupMacWindow(); 72 | void InstallTouchBar(); 73 | void SetupPlatformWindow() { SetupMacWindow(); InstallTouchBar(); } 74 | #elif defined(Q_OS_WIN32) 75 | void SetupAeroWindow(); 76 | void SetupPlatformWindow() { SetupAeroWindow(); } 77 | #else 78 | void SetupPlatformWindow() { } 79 | #endif 80 | 81 | public: 82 | Ui::QtGuiApplication1Class ui; 83 | }; 84 | -------------------------------------------------------------------------------- /src/v/hlmv.qrc: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/v/myopenglwidget.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "ViewerSettings.h" 14 | 15 | #include "gl_draw.h" 16 | #include "myopenglwidget.h" 17 | 18 | #include "qt_image.h" 19 | 20 | struct MyOpenGLWidget::impl_t 21 | { 22 | explicit impl_t(MyOpenGLWidget *p) : m_Timer(p) {} 23 | 24 | QPoint m_LastMousePos; 25 | 26 | QTimer m_Timer; 27 | CHLMV_GL_Draw m_gldraw; 28 | }; 29 | 30 | MyOpenGLWidget::MyOpenGLWidget(QWidget *parent) : 31 | pimpl(new impl_t(this)) 32 | { 33 | QObject::connect (&pimpl->m_Timer, SIGNAL (timeout ()), this, SLOT (idleEvent ())); 34 | pimpl->m_Timer.start (); 35 | 36 | grabGesture(Qt::PinchGesture); 37 | //QApplication::setAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents, false); 38 | //QApplication::setAttribute(Qt::AA_SynthesizeTouchForUnhandledMouseEvents, false); 39 | } 40 | 41 | MyOpenGLWidget::~MyOpenGLWidget() = default; 42 | 43 | void MyOpenGLWidget::idleEvent() 44 | { 45 | pimpl->m_gldraw.Think(); 46 | 47 | if (!g_viewerSettings.pause) 48 | this->updateGL (); 49 | } 50 | 51 | void MyOpenGLWidget::initializeGL() 52 | { 53 | 54 | } 55 | 56 | void MyOpenGLWidget::resizeGL(int w, int h) 57 | { 58 | pimpl->m_gldraw.SetSize(width(), height(), w, h); 59 | } 60 | 61 | void MyOpenGLWidget::paintGL() 62 | { 63 | pimpl->m_gldraw.Draw(); 64 | } 65 | 66 | bool MyOpenGLWidget::event(QEvent *event) 67 | { 68 | switch(event->type()){ 69 | case QEvent::Gesture: 70 | { 71 | gestureEvent(static_cast(event)); 72 | return true; 73 | } 74 | case QEvent::TouchBegin: 75 | case QEvent::TouchUpdate: 76 | case QEvent::TouchEnd: 77 | { 78 | touchEvent(static_cast(event)); 79 | return true; 80 | } 81 | default: 82 | break; 83 | } 84 | return QGLWidget::event(event); 85 | } 86 | 87 | void MyOpenGLWidget::gestureEvent(QGestureEvent *event) 88 | { 89 | // 2-fingers on both touchpad and touchscreen 90 | QPinchGesture *pinch = static_cast(event->gesture(Qt::PinchGesture)); 91 | if (pinch) 92 | { 93 | QPinchGesture::ChangeFlags changeFlags = pinch->changeFlags(); 94 | if (changeFlags & QPinchGesture::ScaleFactorChanged) 95 | { 96 | qreal scale_delta = pinch->scaleFactor(); 97 | g_viewerSettings.trans[2] /= scale_delta * scale_delta; 98 | } 99 | 100 | if(changeFlags & QPinchGesture::CenterPointChanged) 101 | { 102 | QPointF position_delta = pinch->centerPoint() - pinch->lastCenterPoint(); 103 | g_viewerSettings.trans[0] -= position_delta.x() / 4; 104 | g_viewerSettings.trans[1] += position_delta.y() / 4; 105 | } 106 | } 107 | 108 | // 3-fingers 109 | /* 110 | QPanGesture *pan = static_cast(event->gesture(Qt::PanGesture)); 111 | if (pan) 112 | { 113 | QPointF position_delta = pan->delta(); 114 | g_viewerSettings.trans[0] -= position_delta.x(); 115 | g_viewerSettings.trans[1] += position_delta.y(); 116 | } 117 | */ 118 | 119 | updateGL(); 120 | } 121 | 122 | void MyOpenGLWidget::touchEvent(QTouchEvent *event) 123 | { 124 | if (event->touchPoints().size() != 1) 125 | return; 126 | 127 | const QTouchEvent::TouchPoint &tp = event->touchPoints().first(); 128 | if (1) 129 | { 130 | auto delta = tp.pos() - tp.lastPos(); 131 | g_viewerSettings.rot[0] += delta.y(); 132 | g_viewerSettings.rot[1] += delta.x(); 133 | updateGL(); 134 | } 135 | } 136 | 137 | void MyOpenGLWidget::mousePressEvent(QMouseEvent *event) 138 | { 139 | if (event->source() != Qt::MouseEventNotSynthesized) 140 | return; 141 | pimpl->m_LastMousePos = event->pos(); 142 | } 143 | 144 | void MyOpenGLWidget::mouseMoveEvent(QMouseEvent *event) 145 | { 146 | if (event->source() != Qt::MouseEventNotSynthesized) 147 | return; 148 | 149 | int dx = event->x() - pimpl->m_LastMousePos.x(); 150 | int dy = event->y() - pimpl->m_LastMousePos.y(); 151 | 152 | if (event->buttons() & Qt::LeftButton) { 153 | 154 | g_viewerSettings.rot[0] += dy; 155 | g_viewerSettings.rot[1] += dx; 156 | 157 | updateGL(); 158 | } 159 | else if (event->buttons() & Qt::RightButton) { 160 | g_viewerSettings.trans[0] -= dx; 161 | g_viewerSettings.trans[1] += dy; 162 | 163 | updateGL(); 164 | } 165 | 166 | pimpl->m_LastMousePos = event->pos(); 167 | } 168 | 169 | void MyOpenGLWidget::wheelEvent(QWheelEvent *event) 170 | { 171 | if(event->source() == Qt::MouseEventSynthesizedBySystem) 172 | { 173 | // macOS touchpad 174 | auto delta = event->pixelDelta(); 175 | g_viewerSettings.trans[0] -= delta.x(); 176 | g_viewerSettings.trans[1] += delta.y(); 177 | } 178 | else 179 | { 180 | g_viewerSettings.trans[2] += event->delta(); 181 | } 182 | updateGL(); 183 | } 184 | 185 | void MyOpenGLWidget::setLight(bool b) 186 | { 187 | b ? glEnable(GL_LIGHTING) : glDisable(GL_LIGHTING); 188 | b ? glEnable(GL_LIGHT0) : glDisable(GL_LIGHT0); 189 | updateGL(); 190 | } 191 | 192 | void MyOpenGLWidget::onActionSnapshot() 193 | { 194 | const int w = pimpl->m_gldraw.w2(); 195 | const int h = pimpl->m_gldraw.h2(); 196 | 197 | QImage qi( w, h, QImage::Format::Format_RGB888); 198 | 199 | updateGL(); 200 | glReadPixels(0, 0, w, h, GL_RGB, GL_UNSIGNED_BYTE, qi.bits()); 201 | 202 | qi = std::move(qi).mirrored(); 203 | 204 | QClipboard *clipboard = QApplication::clipboard(); 205 | clipboard->setPixmap(QPixmap::fromImage(qi)); 206 | 207 | if(QMessageBox::question(this, 208 | "Save", 209 | "Image has been copied to clipboard.\nDo you want to save it to a file?", 210 | QMessageBox::StandardButtons(QMessageBox::Save | QMessageBox::Cancel) 211 | ) == QMessageBox::Save) 212 | { 213 | 214 | QString filename = getSaveFileImagePath(this, {}, {}); 215 | 216 | if(!filename.isEmpty()) 217 | try 218 | { 219 | saveImageTo(qi, filename); 220 | } 221 | catch(const std::exception &e) 222 | { 223 | QMessageBox::critical(this, "Error", e.what()), void(); 224 | } 225 | 226 | } 227 | } 228 | 229 | void MyOpenGLWidget::setPaused(bool x) 230 | { 231 | pimpl->m_gldraw.SetStopPlaying(x); 232 | } 233 | -------------------------------------------------------------------------------- /src/v/myopenglwidget.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | class QGestureEvent; 7 | class QTouchEvent; 8 | 9 | class MyOpenGLWidget : public QGLWidget 10 | { 11 | Q_OBJECT 12 | 13 | public: 14 | MyOpenGLWidget(QWidget *parent = Q_NULLPTR); 15 | ~MyOpenGLWidget(); 16 | 17 | void initializeGL() override; 18 | void resizeGL(int w, int h) override; 19 | void paintGL() override; 20 | 21 | bool event(QEvent *event) override; 22 | void mousePressEvent(QMouseEvent *event) override; 23 | void mouseMoveEvent(QMouseEvent *event) override; 24 | void wheelEvent(QWheelEvent *event) override; 25 | 26 | void gestureEvent(QGestureEvent *event); 27 | void touchEvent(QTouchEvent *event); 28 | 29 | signals: // signals 30 | 31 | public slots: 32 | void setLight(bool); 33 | void onActionSnapshot(); 34 | void idleEvent(); 35 | void setPaused(bool); 36 | 37 | private: 38 | struct impl_t; 39 | const std::unique_ptr pimpl; 40 | 41 | }; 42 | -------------------------------------------------------------------------------- /src/v/platform/macos/MacWindow.mm: -------------------------------------------------------------------------------- 1 | // 2 | // Created by 小白白 on 2019-06-06. 3 | // 4 | 5 | #include "hlmv.h" 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #import 12 | 13 | #include "ViewerSettings.h" 14 | 15 | @interface MyWindowDelegate : NSObject 16 | - (NSApplicationPresentationOptions)window:(NSWindow *)window willUseFullScreenPresentationOptions:(NSApplicationPresentationOptions)proposedOptions; 17 | - (BOOL)windowShouldClose:(id)sender; 18 | @end 19 | @implementation MyWindowDelegate 20 | - (NSApplicationPresentationOptions)window:(NSWindow *)window willUseFullScreenPresentationOptions:(NSApplicationPresentationOptions)proposedOptions; 21 | { 22 | return (NSApplicationPresentationFullScreen | 23 | NSApplicationPresentationHideDock | 24 | NSApplicationPresentationAutoHideMenuBar | 25 | NSApplicationPresentationAutoHideToolbar); 26 | } 27 | - (BOOL)windowShouldClose:(id)sender { 28 | exit(0); 29 | return YES; 30 | } 31 | - (void)windowDidResize:(NSNotification *)notification { 32 | NSWindow *nsw = notification.object; 33 | nsw.styleMask |= NSWindowStyleMaskUnifiedTitleAndToolbar; 34 | nsw.styleMask |= NSWindowStyleMaskFullSizeContentView; 35 | } 36 | @end 37 | 38 | void QtGuiApplication1::SetupMacWindow() 39 | { 40 | ui.statusBar->setVisible(false); 41 | 42 | NSApplication *nsa = NSApp; 43 | 44 | NSWindow* nsw = [reinterpret_cast(this->windowHandle()->winId()) window]; 45 | 46 | MyWindowDelegate *mwd = [[MyWindowDelegate alloc] init]; 47 | nsw.delegate = mwd; 48 | 49 | nsw.styleMask |= NSWindowStyleMaskUnifiedTitleAndToolbar; 50 | nsw.styleMask |= NSWindowStyleMaskFullSizeContentView; 51 | //nsw.titleVisibility = NSWindowTitleHidden; 52 | nsw.titlebarAppearsTransparent = TRUE; 53 | 54 | QMacToolBar *toolBar = new QMacToolBar(this); 55 | NSToolbar *nst = toolBar->nativeToolbar(); 56 | nst.displayMode = NSToolbarDisplayModeLabelOnly; 57 | nst.sizeMode = NSToolbarSizeModeRegular; 58 | 59 | // pages 60 | toolBar->addStandardItem(QMacToolBarItem::FlexibleSpace); 61 | toolBar->addStandardItem(QMacToolBarItem::Space); 62 | toolBar->addSeparator(); 63 | QTabWidget *tabWidget = ui.tabWidget; 64 | tabWidget->setVisible(false); 65 | tabWidget->tabBar()->setVisible(false); 66 | 67 | for(int i=0;icount();++i) 68 | { 69 | QMacToolBarItem *item = toolBar->addItem(tabWidget->tabIcon(i), tabWidget->tabText(i)); 70 | item->setSelectable(true); 71 | NSToolbarItem *nativeItem = item->nativeToolBarItem(); 72 | 73 | if(tabWidget->currentIndex() == i) 74 | { 75 | [nst setSelectedItemIdentifier:[nativeItem itemIdentifier]]; 76 | } 77 | 78 | auto act_lambda = [=]() { 79 | if(tabWidget->currentIndex() == i) { 80 | if(!tabWidget->isVisible() ) { 81 | tabWidget->setVisible(true); 82 | ui.statusBar->setVisible(true); 83 | } 84 | else { 85 | tabWidget->setVisible(false); 86 | ui.statusBar->setVisible(false); 87 | } 88 | 89 | //[nst setSelectedItemIdentifier:nil]; 90 | } 91 | else { 92 | tabWidget->setCurrentWidget(tabWidget->widget(i)); 93 | } 94 | }; 95 | 96 | connect(item,&QMacToolBarItem::activated, act_lambda); 97 | } 98 | toolBar->addSeparator(); 99 | toolBar->addStandardItem(QMacToolBarItem::FlexibleSpace); 100 | toolBar->addStandardItem(QMacToolBarItem::Space); 101 | 102 | 103 | toolBar->attachToWindow(this->window()->windowHandle()); 104 | 105 | 106 | NSButton *closeBtn = [nsw standardWindowButton:NSWindowCloseButton]; 107 | NSButton *miniaturizeBtn = [nsw standardWindowButton:NSWindowMiniaturizeButton]; 108 | NSButton *zoomBtn = [nsw standardWindowButton:NSWindowZoomButton]; 109 | 110 | closeBtn.translatesAutoresizingMaskIntoConstraints = NO; 111 | miniaturizeBtn.translatesAutoresizingMaskIntoConstraints = NO; 112 | zoomBtn.translatesAutoresizingMaskIntoConstraints = NO; 113 | 114 | NSLayoutConstraint *leftContraint1 = [NSLayoutConstraint constraintWithItem:closeBtn attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:closeBtn.superview attribute:NSLayoutAttributeLeft multiplier:1.0 constant:14.0]; 115 | NSLayoutConstraint *topContraint1 = [NSLayoutConstraint constraintWithItem:closeBtn attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:closeBtn.superview attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0]; 116 | leftContraint1.active = YES; 117 | topContraint1.active = YES; 118 | 119 | NSLayoutConstraint *leftContraint2 = [NSLayoutConstraint constraintWithItem:miniaturizeBtn attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:miniaturizeBtn.superview attribute:NSLayoutAttributeLeft multiplier:1.0 constant:33.0]; 120 | NSLayoutConstraint *topContraint2 = [NSLayoutConstraint constraintWithItem:miniaturizeBtn attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:miniaturizeBtn.superview attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0]; 121 | leftContraint2.active = YES; 122 | topContraint2.active = YES; 123 | 124 | NSLayoutConstraint *leftContraint3 = [NSLayoutConstraint constraintWithItem:zoomBtn attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:zoomBtn.superview attribute:NSLayoutAttributeLeft multiplier:1.0 constant:53.0]; 125 | NSLayoutConstraint *topContraint3 = [NSLayoutConstraint constraintWithItem:zoomBtn attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:zoomBtn.superview attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0]; 126 | leftContraint3.active = YES; 127 | topContraint3.active = YES; 128 | 129 | } 130 | 131 | -------------------------------------------------------------------------------- /src/v/platform/macos/Touchbar.mm: -------------------------------------------------------------------------------- 1 | // 2 | // Created by MoeMod on 2019-06-18. 3 | // 4 | 5 | #import 6 | #import 7 | #include "hlmv.h" 8 | 9 | // This example shows how to create and populate touch bars for Qt applications. 10 | // Two approaches are demonstrated: creating a global touch bar for the entire 11 | // application via the NSApplication delegate, and creating per-window touch bars 12 | // via the NSWindow delegate. Applications may use either or both of these, for example 13 | // to provide global base touch bar with window specific additions. Refer to the 14 | // NSTouchBar documentation for further details. 15 | 16 | static NSTouchBarItemIdentifier TextItemIdentifier = @"com.myapp.TextItemIdentifier"; 17 | 18 | @interface ViewModeScrubberDelegate : NSResponder 19 | - (instancetype) initWithHLMV:(QtGuiApplication1 *)hlmv; 20 | @property QtGuiApplication1 *hlmv; 21 | @property (strong) NSArray *validItems; 22 | @end 23 | @implementation ViewModeScrubberDelegate 24 | 25 | - (instancetype) initWithHLMV:(QtGuiApplication1 *)hlmv { 26 | [super init]; 27 | _hlmv = hlmv; 28 | NSWindow* nsw = [reinterpret_cast(self.hlmv->windowHandle()->winId()) window]; 29 | NSToolbar *nst = nsw.toolbar; 30 | 31 | NSArray *items = [nst.items copy]; 32 | NSMutableArray *new_items = [[NSMutableArray alloc] init]; 33 | 34 | int iSelected = 0; 35 | for(int i = 0; i < items.count; ++i) 36 | { 37 | NSToolbarItem * item = items[i]; 38 | if(item.action == nil) 39 | continue; 40 | [new_items addObject:item]; 41 | if([nst.selectedItemIdentifier isEqualToString:[item itemIdentifier]]) 42 | { 43 | 44 | } 45 | } 46 | 47 | 48 | _validItems = new_items; 49 | 50 | return self; 51 | } 52 | 53 | - (NSInteger)numberOfItemsForScrubber:(NSScrubber *)scrubber { 54 | return [_validItems count]; 55 | } 56 | 57 | - (NSScrubberItemView *)scrubber:(NSScrubber *)scrubber viewForItemAtIndex:(NSInteger)index { 58 | NSScrubberTextItemView *view = [scrubber makeItemWithIdentifier:TextItemIdentifier owner:nil]; 59 | 60 | view.textField.stringValue = _validItems[index].label; 61 | 62 | return view; 63 | } 64 | 65 | - (NSSize)scrubber:(NSScrubber *)scrubber layout:(NSScrubberFlowLayout *)layout sizeForItemAtIndex:(NSInteger)index { 66 | QTabWidget *tabWidget = self.hlmv->ui.tabWidget; 67 | NSString *string = tabWidget->tabText(static_cast(index)).toNSString(); 68 | NSRect bounds = [string boundingRectWithSize:NSMakeSize(CGFLOAT_MAX, CGFLOAT_MAX) 69 | options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading 70 | attributes:@{NSFontAttributeName: [NSFont systemFontOfSize:0]}]; 71 | 72 | return NSMakeSize(bounds.size.width + 50, 30); 73 | } 74 | 75 | - (void)didFinishInteractingWithScrubber:(NSScrubber *)scrubber { 76 | 77 | auto selectedIndex = scrubber.selectedIndex; 78 | QTabWidget *tabWidget = self.hlmv->ui.tabWidget; 79 | 80 | NSWindow* nsw = [reinterpret_cast(self.hlmv->windowHandle()->winId()) window]; 81 | NSToolbar *nst = nsw.toolbar; 82 | NSToolbarItem *nativeItem = _validItems[selectedIndex]; 83 | [nst setSelectedItemIdentifier:[nativeItem itemIdentifier]]; 84 | 85 | tabWidget->setCurrentWidget(tabWidget->widget(static_cast(selectedIndex))); 86 | 87 | } 88 | - (void)dealloc { 89 | [_validItems release]; 90 | [super dealloc]; 91 | } 92 | @end 93 | 94 | @interface SyncComboBoxScrubberDelegate : NSResponder 95 | - (instancetype) initWithComboBox:(QComboBox *)combo; 96 | @property QComboBox *combo; 97 | @end 98 | @implementation SyncComboBoxScrubberDelegate 99 | 100 | - (instancetype) initWithComboBox:(QComboBox *)combo { 101 | [super init]; 102 | _combo = combo; 103 | 104 | return self; 105 | } 106 | 107 | - (NSInteger)numberOfItemsForScrubber:(NSScrubber *)scrubber { 108 | return _combo->count(); 109 | } 110 | 111 | - (NSScrubberItemView *)scrubber:(NSScrubber *)scrubber viewForItemAtIndex:(NSInteger)index { 112 | NSScrubberTextItemView *view = [scrubber makeItemWithIdentifier:TextItemIdentifier owner:nil]; 113 | view.title = _combo->itemText(index).toNSString(); 114 | return view; 115 | } 116 | 117 | - (NSSize)scrubber:(NSScrubber *)scrubber layout:(NSScrubberFlowLayout *)layout sizeForItemAtIndex:(NSInteger)index { 118 | NSString *string = _combo->itemText(index).toNSString(); 119 | NSRect bounds = [string boundingRectWithSize:NSMakeSize(CGFLOAT_MAX, CGFLOAT_MAX) 120 | options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading 121 | attributes:@{NSFontAttributeName: [NSFont systemFontOfSize:0]}]; 122 | 123 | return NSMakeSize(bounds.size.width + 20, 30); 124 | } 125 | 126 | - (void)scrubber:(NSScrubber *)scrubber didSelectItemAtIndex:(NSInteger)selectedIndex { 127 | 128 | _combo->setCurrentIndex(selectedIndex); 129 | 130 | } 131 | @end 132 | 133 | @interface SyncComboBoxScrubberTouchBarItem: NSCustomTouchBarItem 134 | - (instancetype) initWithIdentifier:identifier ComboBox:(QComboBox *)combo; 135 | @property (strong) NSScrubber *scrubber; 136 | @property QComboBox *combo; 137 | @property QMetaObject::Connection connection; 138 | @end 139 | @implementation SyncComboBoxScrubberTouchBarItem 140 | 141 | - (instancetype) initWithIdentifier:identifier ComboBox:(QComboBox *)combo { 142 | self = [super initWithIdentifier:identifier]; 143 | NSScrubber *scrubber = [[[NSScrubber alloc] init] autorelease]; 144 | scrubber.scrubberLayout = [[NSScrubberFlowLayout alloc] init]; 145 | scrubber.mode = NSScrubberModeFree; 146 | scrubber.continuous = false; 147 | scrubber.selectionBackgroundStyle = [NSScrubberSelectionStyle outlineOverlayStyle]; 148 | 149 | SyncComboBoxScrubberDelegate *scrubberDelegate = [[SyncComboBoxScrubberDelegate alloc] initWithComboBox:combo]; 150 | scrubber.delegate = scrubberDelegate; 151 | scrubber.dataSource = scrubberDelegate; 152 | scrubber.floatsSelectionViews = false; 153 | scrubber.selectedIndex = combo->currentIndex(); 154 | 155 | [scrubber registerClass:[NSScrubberTextItemView class] forItemIdentifier:TextItemIdentifier]; 156 | 157 | self.view = _scrubber = scrubber; 158 | _combo = combo; 159 | _connection = QObject::connect(combo, 160 | static_cast(&QComboBox::currentIndexChanged), 161 | [self]{ _scrubber.selectedIndex =_combo->currentIndex(); } 162 | ); 163 | return self; 164 | } 165 | 166 | @end 167 | 168 | // The TouchBarProvider class implements the NSTouchBarDelegate protocol, as 169 | // well as app and window delegate protocols. 170 | @interface TouchBarProvider: NSResponder 171 | 172 | @property (strong) NSCustomTouchBarItem *touchBarItem1; 173 | @property (strong) NSCustomTouchBarItem *touchBarItem2; 174 | @property (strong) NSCustomTouchBarItem *touchBarItem3; 175 | @property (strong) NSCustomTouchBarItem *touchBarItem4; 176 | 177 | @property (strong) NSButton *touchBarButton1; 178 | @property (strong) NSButton *touchBarButton2; 179 | @property (strong) NSButton *touchBarButton3; 180 | @property (strong) NSButton *touchBarButton4; 181 | 182 | @property (strong) NSObject *qtDelegate; 183 | 184 | @property QtGuiApplication1 *hlmv; 185 | 186 | - (instancetype) initWithHLMVInstance:(QtGuiApplication1 *)p; 187 | 188 | @end 189 | 190 | // Create identifiers for two button items. 191 | static NSTouchBarItemIdentifier Button1Identifier = @"com.myapp.Button1Identifier"; 192 | static NSTouchBarItemIdentifier Button2Identifier = @"com.myapp.Button2Identifier"; 193 | static NSTouchBarItemIdentifier Button3Identifier = @"com.myapp.Button3Identifier"; 194 | static NSTouchBarItemIdentifier Button4Identifier = @"com.myapp.Button4Identifier"; 195 | static NSTouchBarItemIdentifier ViewModeScrubberItemIdentifier = @"com.hlmv.ViewModeScrubberItemIdentifier"; 196 | static NSTouchBarItemIdentifier ViewModeItemIdentifier = @"com.hlmv.ViewModeItemIdentifier"; 197 | static NSTouchBarItemIdentifier SequenceScrubberItemIdentifier = @"com.hlmv.SequenceScrubberItemIdentifier"; 198 | static NSTouchBarItemIdentifier TextureItemIdentifier = @"com.hlmv.TextureItemIdentifier"; 199 | static NSTouchBarItemIdentifier TextureScrubberItemIdentifier = @"com.hlmv.TextureScrubberItemIdentifier"; 200 | static NSTouchBarItemIdentifier BodyPartItemIdentifier = @"com.hlmv.BodyPartItemIdentifier"; 201 | static NSTouchBarItemIdentifier BodyPartScrubberItemIdentifier = @"com.hlmv.BodyPartScrubberItemIdentifier"; 202 | static NSTouchBarItemIdentifier SubModelItemIdentifier = @"com.hlmv.SubModelItemIdentifier"; 203 | static NSTouchBarItemIdentifier SubModelScrubberItemIdentifier = @"com.hlmv.SubModelScrubberItemIdentifier"; 204 | static NSTouchBarItemIdentifier SkinItemIdentifier = @"com.hlmv.SkinItemIdentifier"; 205 | static NSTouchBarItemIdentifier SkinScrubberItemIdentifier = @"com.hlmv.SkinScrubberItemIdentifier"; 206 | 207 | @implementation TouchBarProvider 208 | 209 | - (instancetype) initWithHLMVInstance:(QtGuiApplication1 *)p 210 | { 211 | self = [super init]; 212 | 213 | self.hlmv = p; 214 | 215 | return self; 216 | } 217 | 218 | - (NSTouchBar *)makeTouchBar 219 | { 220 | // Create the touch bar with this instance as its delegate 221 | NSTouchBar *bar = [[NSTouchBar alloc] init]; 222 | bar.delegate = self; 223 | 224 | // Add touch bar items: first, the very important emoji picker, followed 225 | // by two buttons. Note that no further handling of the emoji picker 226 | // is needed (emojii are automatically routed to any active text edit). Button 227 | // actions handlers are set up in makeItemForIdentifier below. 228 | if(_hlmv->ui.tabWidget->currentIndex() == 3 || _hlmv->ui.tabWidget->currentIndex() == 4) // view origin 229 | { 230 | bar.defaultItemIdentifiers = @[\ 231 | ViewModeItemIdentifier, 232 | NSTouchBarItemIdentifierFixedSpaceLarge, 233 | SequenceScrubberItemIdentifier 234 | ]; 235 | } 236 | else if(_hlmv->ui.tabWidget->currentIndex() == 2) // texture 237 | { 238 | bar.defaultItemIdentifiers = @[\ 239 | ViewModeItemIdentifier, 240 | NSTouchBarItemIdentifierFixedSpaceLarge, 241 | TextureItemIdentifier 242 | ]; 243 | } 244 | else if(_hlmv->ui.tabWidget->currentIndex() == 1) // body part 245 | { 246 | bar.defaultItemIdentifiers = @[\ 247 | ViewModeItemIdentifier, 248 | NSTouchBarItemIdentifierFixedSpaceLarge, 249 | BodyPartItemIdentifier, 250 | SubModelItemIdentifier, 251 | SkinItemIdentifier, 252 | ]; 253 | } 254 | else 255 | { 256 | bar.defaultItemIdentifiers = @[\ 257 | ViewModeScrubberItemIdentifier 258 | ]; 259 | } 260 | 261 | 262 | return bar; 263 | } 264 | 265 | - (NSTouchBarItem *)touchBar:(NSTouchBar *)touchBar makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier 266 | { 267 | Q_UNUSED(touchBar); 268 | 269 | // Create touch bar items as NSCustomTouchBarItems which can contain any NSView. 270 | if ([identifier isEqualToString:ViewModeItemIdentifier]) { 271 | NSPopoverTouchBarItem *item = [[NSPopoverTouchBarItem alloc] initWithIdentifier:identifier]; 272 | 273 | QTabBar *tabBar = _hlmv->ui.tabWidget->tabBar(); 274 | item.collapsedRepresentationLabel = tabBar->tabText(tabBar->currentIndex()).toNSString(); 275 | item.collapsedRepresentationImage = [NSImage imageNamed:NSImageNameTouchBarSidebarTemplate]; 276 | 277 | NSTouchBar *secondaryTouchBar = [[NSTouchBar alloc] init]; 278 | secondaryTouchBar.delegate = self; 279 | secondaryTouchBar.defaultItemIdentifiers = @[ViewModeScrubberItemIdentifier]; 280 | 281 | item.popoverTouchBar = secondaryTouchBar; 282 | 283 | return item; 284 | } 285 | else if ([identifier isEqualToString:ViewModeScrubberItemIdentifier]) { 286 | 287 | ViewModeScrubberDelegate *scrubberDelegate = [[ViewModeScrubberDelegate alloc] initWithHLMV:_hlmv]; 288 | //scrubberDelegate.hlmv = hlmv; 289 | 290 | NSScrubber *scrubber = [[[NSScrubber alloc] init] autorelease]; 291 | scrubber.scrubberLayout = [[NSScrubberFlowLayout alloc] init]; 292 | //scrubber.mode = NSScrubberModeFree; 293 | scrubber.continuous = true; 294 | scrubber.selectionBackgroundStyle = [NSScrubberSelectionStyle outlineOverlayStyle]; 295 | scrubber.delegate = scrubberDelegate; 296 | scrubber.dataSource = scrubberDelegate; 297 | scrubber.floatsSelectionViews = false; 298 | scrubber.selectedIndex = _hlmv->ui.tabWidget->currentIndex(); 299 | 300 | [scrubber registerClass:[NSScrubberTextItemView class] forItemIdentifier:TextItemIdentifier]; 301 | 302 | 303 | NSCustomTouchBarItem *item = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier]; 304 | item.view = scrubber; 305 | 306 | return item; 307 | } 308 | else if ([identifier isEqualToString:TextureItemIdentifier]) { 309 | NSPopoverTouchBarItem *item = [[NSPopoverTouchBarItem alloc] initWithIdentifier:identifier]; 310 | item.collapsedRepresentationLabel = _hlmv->ui.cTextures->currentText().toNSString(); 311 | item.collapsedRepresentationImage = [NSImage imageNamed:NSImageNameTouchBarQuickLookTemplate]; 312 | NSTouchBar *secondaryTouchBar = [[NSTouchBar alloc] init]; 313 | secondaryTouchBar.delegate = self; 314 | secondaryTouchBar.defaultItemIdentifiers = @[TextureScrubberItemIdentifier]; 315 | item.popoverTouchBar = secondaryTouchBar; 316 | return item; 317 | } 318 | else if ([identifier isEqualToString:TextureScrubberItemIdentifier]) { 319 | return [[SyncComboBoxScrubberTouchBarItem alloc] initWithIdentifier:identifier ComboBox:_hlmv->ui.cTextures]; 320 | } 321 | else if ([identifier isEqualToString:BodyPartItemIdentifier]) { 322 | NSPopoverTouchBarItem *item = [[NSPopoverTouchBarItem alloc] initWithIdentifier:identifier]; 323 | item.collapsedRepresentationLabel = _hlmv->ui.cBodypart->currentText().toNSString(); 324 | item.collapsedRepresentationImage = [NSImage imageNamed:NSImageNameTouchBarColorPickerFill]; 325 | NSTouchBar *secondaryTouchBar = [[NSTouchBar alloc] init]; 326 | secondaryTouchBar.delegate = self; 327 | secondaryTouchBar.defaultItemIdentifiers = @[BodyPartScrubberItemIdentifier]; 328 | item.popoverTouchBar = secondaryTouchBar; 329 | return item; 330 | } 331 | else if ([identifier isEqualToString:BodyPartScrubberItemIdentifier]) { 332 | return [[SyncComboBoxScrubberTouchBarItem alloc] initWithIdentifier:identifier ComboBox:_hlmv->ui.cBodypart]; 333 | } 334 | else if ([identifier isEqualToString:SubModelItemIdentifier]) { 335 | NSPopoverTouchBarItem *item = [[NSPopoverTouchBarItem alloc] initWithIdentifier:identifier]; 336 | item.collapsedRepresentationLabel = _hlmv->ui.cSubmodel->currentText().toNSString(); 337 | item.collapsedRepresentationImage = [NSImage imageNamed:NSImageNameTouchBarRecordStartTemplate]; 338 | NSTouchBar *secondaryTouchBar = [[NSTouchBar alloc] init]; 339 | secondaryTouchBar.delegate = self; 340 | secondaryTouchBar.defaultItemIdentifiers = @[SubModelScrubberItemIdentifier]; 341 | item.popoverTouchBar = secondaryTouchBar; 342 | return item; 343 | } 344 | else if ([identifier isEqualToString:SubModelScrubberItemIdentifier]) { 345 | return [[SyncComboBoxScrubberTouchBarItem alloc] initWithIdentifier:identifier ComboBox:_hlmv->ui.cSubmodel]; 346 | } 347 | else if ([identifier isEqualToString:SkinItemIdentifier]) { 348 | NSPopoverTouchBarItem *item = [[NSPopoverTouchBarItem alloc] initWithIdentifier:identifier]; 349 | item.collapsedRepresentationLabel = _hlmv->ui.cSkin->currentText().toNSString(); 350 | item.collapsedRepresentationImage = [NSImage imageNamed:NSImageNameTouchBarColorPickerStroke]; 351 | NSTouchBar *secondaryTouchBar = [[NSTouchBar alloc] init]; 352 | secondaryTouchBar.delegate = self; 353 | secondaryTouchBar.defaultItemIdentifiers = @[SkinScrubberItemIdentifier]; 354 | item.popoverTouchBar = secondaryTouchBar; 355 | return item; 356 | } 357 | else if ([identifier isEqualToString:SkinScrubberItemIdentifier]) { 358 | return [[SyncComboBoxScrubberTouchBarItem alloc] initWithIdentifier:identifier ComboBox:_hlmv->ui.cSkin]; 359 | } 360 | else if ([identifier isEqualToString:SequenceScrubberItemIdentifier]) { 361 | return [[SyncComboBoxScrubberTouchBarItem alloc] initWithIdentifier:identifier ComboBox:_hlmv->ui.cWpSequence]; 362 | } 363 | else if ([identifier isEqualToString:Button1Identifier]) { 364 | QString title = "Welcome"; 365 | self.touchBarItem1 = [[[NSCustomTouchBarItem alloc] initWithIdentifier:identifier] autorelease]; 366 | self.touchBarButton1 = [[NSButton buttonWithTitle:title.toNSString() target:self 367 | action:@selector(button1Clicked)] autorelease]; 368 | self.touchBarButton1.imageHugsTitle = true; 369 | self.touchBarItem1.view = self.touchBarButton1; 370 | return self.touchBarItem1; 371 | } 372 | else if ([identifier isEqualToString:Button2Identifier]) { 373 | QString title = "New Game"; 374 | self.touchBarItem2 = [[[NSCustomTouchBarItem alloc] initWithIdentifier:identifier] autorelease]; 375 | self.touchBarButton2 = [[NSButton buttonWithTitle:title.toNSString() target:self 376 | action:@selector(button2Clicked)] autorelease]; 377 | self.touchBarButton2.imageHugsTitle = true; 378 | self.touchBarItem2.view = self.touchBarButton2; 379 | return self.touchBarItem2; 380 | } 381 | else if ([identifier isEqualToString:Button3Identifier]) { 382 | QString title = "Servers"; 383 | self.touchBarItem3 = [[[NSCustomTouchBarItem alloc] initWithIdentifier:identifier] autorelease]; 384 | self.touchBarButton3 = [[NSButton buttonWithTitle:title.toNSString() target:self 385 | action:@selector(button3Clicked)] autorelease]; 386 | self.touchBarButton3.imageHugsTitle = true; 387 | self.touchBarItem3.view = self.touchBarButton3; 388 | return self.touchBarItem3; 389 | } 390 | else if ([identifier isEqualToString:Button4Identifier]) { 391 | QString title = "Settings"; 392 | self.touchBarItem4 = [[[NSCustomTouchBarItem alloc] initWithIdentifier:identifier] autorelease]; 393 | self.touchBarButton4 = [[NSButton buttonWithTitle:title.toNSString() target:self 394 | action:@selector(button4Clicked)] autorelease]; 395 | self.touchBarButton4.imageHugsTitle = true; 396 | self.touchBarItem4.view = self.touchBarButton4; 397 | return self.touchBarItem4; 398 | } 399 | return nil; 400 | } 401 | 402 | - (void)installAsDelegateForWindow:(NSWindow *)window 403 | { 404 | _qtDelegate = window.delegate; // Save current delegate for forwarding 405 | window.delegate = self; 406 | } 407 | 408 | - (void)installAsDelegateForApplication:(NSApplication *)application 409 | { 410 | _qtDelegate = application.delegate; // Save current delegate for forwarding 411 | application.delegate = self; 412 | } 413 | 414 | - (BOOL)respondsToSelector:(SEL)aSelector 415 | { 416 | // We want to forward to the qt delegate. Respond to selectors it 417 | // responds to in addition to selectors this instance resonds to. 418 | return [_qtDelegate respondsToSelector:aSelector] || [super respondsToSelector:aSelector]; 419 | } 420 | 421 | - (void)forwardInvocation:(NSInvocation *)anInvocation 422 | { 423 | // Forward to the existing delegate. This function is only called for selectors 424 | // this instance does not responds to, which means that the qt delegate 425 | // must respond to it (due to the respondsToSelector implementation above). 426 | [anInvocation invokeWithTarget:_qtDelegate]; 427 | } 428 | 429 | - (void)button1Clicked 430 | { 431 | [[NSApplication sharedApplication] toggleTouchBarCustomizationPalette:nil]; 432 | 433 | } 434 | 435 | - (void)button2Clicked 436 | { 437 | 438 | } 439 | 440 | - (void)button3Clicked 441 | { 442 | 443 | } 444 | 445 | - (void)button4Clicked 446 | { 447 | 448 | } 449 | 450 | @end 451 | 452 | void QtGuiApplication1::InstallTouchBar() 453 | { 454 | NSWindow* nsw = [reinterpret_cast(this->windowHandle()->winId()) window]; 455 | if(nsw.touchBar) 456 | { 457 | nsw.touchBar = nil; 458 | } 459 | else 460 | { 461 | // Install TouchBarProvider as application delegate 462 | TouchBarProvider *touchBarProvider = [[TouchBarProvider alloc] initWithHLMVInstance:this]; 463 | //[touchBarProvider installAsDelegateForApplication:[NSApplication sharedApplication]]; 464 | [touchBarProvider installAsDelegateForWindow:nsw]; 465 | } 466 | } 467 | -------------------------------------------------------------------------------- /src/v/platform/win32/AeroWindow.cpp: -------------------------------------------------------------------------------- 1 | #include "hlmv.h" 2 | #include 3 | #include 4 | 5 | void QtGuiApplication1::SetupAeroWindow() 6 | { 7 | //this->menuBar()->setVisible(false); 8 | //ui.centralWidget->layout()->addItem(ui.centralWidget->layout()->takeAt(0)); 9 | if (QOperatingSystemVersion::current() <= QOperatingSystemVersion::Windows7) 10 | { 11 | if (QtWin::isCompositionEnabled()) 12 | { 13 | QtWin::extendFrameIntoClientArea(this, -1, -1, -1, -1); 14 | setAttribute(Qt::WA_TranslucentBackground, true); 15 | setAttribute(Qt::WA_NoSystemBackground, false); 16 | } 17 | else 18 | { 19 | setAttribute(Qt::WA_TranslucentBackground, false); 20 | } 21 | } 22 | } --------------------------------------------------------------------------------