diff --git a/.gitignore b/.gitignore index 2c738e40b..4a64e58e0 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,7 @@ NifSkope.exe # VC Project *.cd *.filters +*.psess *.sln *.sln.docstates *.suo @@ -46,16 +47,23 @@ NifSkope.exe *.vcxproj.user *.vcproj *.vcproj.user +*.vsp *.vspscc *.vssscc +*.vspx + # VC Cache +.vs/ ipch/ *.aps *.cachefile *.ncb +*.opendb *.opensdf *.sdf +*.VC.db +*.VC.VC.opendb # VC Debug vc100.pdb diff --git a/.gitmodules b/.gitmodules index 1c3d4b553..56b84bd43 100644 --- a/.gitmodules +++ b/.gitmodules @@ -3,4 +3,7 @@ url = git://github.com/niftools/nifdocsys.git [submodule "qhull"] path = lib/qhull - url = git://gitorious.org/qhull/qhull.git + url = git@github.com:qhull/qhull.git +[submodule "lib/zlib"] + path = lib/zlib + url = git@github.com:madler/zlib.git diff --git a/CHANGELOG.md b/CHANGELOG.md index c983ad9bd..1e6435510 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ == CHANGELOG == +**NOTE: This changelog is not maintained for prerelease versions** + +You may view the changes since 1.1.3 here: https://github.com/jonwd7/nifskope/releases + + This is version 1.1.3 of NifSkope. changes since 1.1.2: diff --git a/DOXYGEN.md b/DOXYGEN.md new file mode 100644 index 000000000..e799ff843 --- /dev/null +++ b/DOXYGEN.md @@ -0,0 +1,110 @@ +Main Page {#mainpage} +========= + +[TOC] + + +Introduction {#intro} +======== + +%NifSkope is a graphical program that allows you to open NIF files, view their contents, edit them, and write them back out again. It is written in C++ using OpenGL and the Qt framework. + +The main application resides in the NifSkope class; rendering takes place via GLView. A central feature of Qt is the [Signals and Slots](http://doc.qt.io/qt-5/signalsandslots.html) mechanism, which is used extensively for communication between classes. A NIF is internally represented as a NifModel, and blocks are referenced by means of QModelIndex or QPersistentModelIndex. + +Various functions can be performed on a NIF via the Spell system; this is a good place to start if you want to learn about how a NIF is typically structured and how the blocks are manipulated. + + +Reading and parsing a NIF {#parsing} +======== + +The NIF specification is currently described by [nif.xml](https://github.com/niftools/nifxml) and parsed using NifXmlHandler. + + +Detailed Information {#detailed} +======== + +- @ref ui_programming +- @ref viewport_details + +[Signals and Slots]: http://doc.qt.io/qt-5/signalsandslots.html + + + + +@page ui_programming UI Programming + +[TOC] + +%NifSkope uses [Qt Designer] and [.ui files] to define the layout of most of its UI and uses [Signals and Slots] to allow communication between UI and non-UI code. %NifSkope makes partial use of QMetaObject's [auto-connection] features. + +The UI is styled with a Qt-specific subset of CSS dubbed [QSS]. Most of the style is defined in `style.qss` which is installed alongside NifSkope.exe, though stylesheets can also be defined in C++ using QWidget::setStyleSheet(). + + +See also: [Designing a UI]. + +[Designing a UI]: http://doc.qt.io/qt-5/gettingstartedqt.html#designing-a-ui +[Qt Designer]: http://doc.qt.io/qt-5/qtdesigner-manual.html +[.ui files]: http://doc.qt.io/qt-5/designer-using-a-ui-file.html +[Signals and Slots]: http://doc.qt.io/qt-5/signalsandslots.html +[auto-connection]: http://doc.qt.io/qt-5/designer-using-a-ui-file.html#widgets-and-dialogs-with-auto-connect +[QSS]: http://doc.qt.io/qt-5/stylesheet-reference.html + + + + +@page viewport_details Viewport + +[TOC] + +The main viewport class is GLView. Each frame is painted to the viewport via GLView::paintGL(). The viewport scenegraph is managed by a Scene class. A scene consists of: + +- Node, a physical object in the scene such as NiNode, BSFadeNode, etc. + - Mesh, e.g. NiTriShape + - Particles + - LODNode, BillboardNode +- Property, a property of a physical object in the scene. + +Nodes {#nodes} +======== + +The Node class is the base class for any physical object in a Scene. A Node can have children which are stored in a NodeList. [Properties] are stored with the Node via a PropertyList. + + +Properties {#properties} +======== + +The Property class is the %NifSkope analog to NiProperty blocks. Ideally anything that inherits NiProperty in nif.xml should have a Property subclass implementation. Not all Properties require any kind of manifestation in the scene but they are nevertheless encapsulated and tracked by a Property class. + + +Animation {#animation} +======== + +Anything that should animate must implement the IControllable interface. To animate an IControllable, you create a Controller. These Controllers are then made a `friend class` in the class which implements IControllable. There are currently very few Controllers implemented when it comes to reaching feature parity with the NIF specification: + +| %NifSkope class | NIF Block | +|--------------------------------|----------------------------------| +| KeyframeController | NiKeyframeController | +| TransformController | NiTransformController | +| MultiTargetTransformController | NiMultiTargetTransformController | +| VisibilityController | NiVisController | +| MorphController | NiGeomMorpherController | +| UVController | NiUVController | +| ParticleController | NiParticleSystemController | +| AlphaController | NiAlphaController | +| MaterialColorController | NiMaterialColorController | +| TexFlipController | NiFlipController | +| TexTransController | NiTextureTransformController | + +Newer Bethesda NIFs mostly use Bethesda proprietary (BS*) blocks, none of which are currently supported. This includes `BSKeyframeController, BSEffectShaderPropertyFloatController, BSEffectShaderPropertyColorController, BSLightingShaderPropertyFloatController, BSLightingShaderPropertyColorController` to name a few. + +To utilize [Controllers] a class must implement IControllable's methods: + +- IControllable::clear() +- IControllable::update() +- IControllable::transform() +- IControllable::setController() + +[Properties]: @ref Property +[Controllers]: @ref Controller + + diff --git a/NifSkope.pro b/NifSkope.pro index 84608175e..563d19f5a 100644 --- a/NifSkope.pro +++ b/NifSkope.pro @@ -7,22 +7,29 @@ TARGET = NifSkope QT += xml opengl network widgets +# Require Qt 5.5 or higher +contains(QT_VERSION, ^5\\.[0-4]\\..*) { + message("Cannot build NifSkope with Qt version $${QT_VERSION}") + error("Minimum required version is Qt 5.5") +} + # C++11 Support CONFIG += c++11 # Dependencies -CONFIG += fsengine nvtristrip qhull +CONFIG += nvtristrip qhull soil zlib lz4 +win32:CONFIG += fsengine # Debug/Release options CONFIG(debug, debug|release) { # Debug Options + BUILD = debug CONFIG += console } else { # Release Options + BUILD = release CONFIG -= console - DEFINES += QT_NO_DEBUG_OUPUT - # TODO: Clean up qWarnings first before using - #DEFINES += QT_NO_WARNING_OUTPUT + DEFINES += QT_NO_DEBUG_OUTPUT } # TODO: Get rid of this define # uncomment this if you want the text stats gl option @@ -38,7 +45,7 @@ TRANSLATIONS += \ DEFINES += \ QT_NO_CAST_FROM_BYTEARRAY \ # QByteArray deprecations QT_NO_URL_CAST_FROM_STRING \ # QUrl deprecations - QT_DISABLE_DEPRECATED_BEFORE=0x050200 #\ # Disable all functions deprecated as of 5.2 + QT_DISABLE_DEPRECATED_BEFORE=0x050300 #\ # Disable all functions deprecated as of 5.3 # Useful for tracking down strings not using # QObject::tr() for translations. @@ -46,37 +53,35 @@ DEFINES += \ # QT_NO_CAST_TO_ASCII -############################### -## Test for Shadow Build -############################### -# Qt Creator = shadow build -# Visual Studio = no shadow build - -SHADOWBUILD = true - -# Strips PWD (source) from OUT_PWD (build) to test if they are on the same path -# - contains() does not work -# - equals( PWD, $${OUT_PWD} ) is not sufficient -REP = $$replace(OUT_PWD, $${PWD}, "") - -# Build dir is inside Source dir -!equals( REP, $${OUT_PWD} ):SHADOWBUILD = false - -# Set OUT_PWD to ./bin so that qmake doesn't clutter PWD -!$$SHADOWBUILD:OUT_PWD = $${_PRO_FILE_PWD_}/bin -# Unfortunately w/ VS qmake still creates empty debug/release folders in PWD. -# They are never used but get auto-generated anyway. -# This setting does not help either: -# !$$SHADOWBUILD:DESTDIR = $${OUT_PWD} -unset(REP) - +VISUALSTUDIO = false +*msvc* { + ###################################### + ## Detect Visual Studio vs Qt Creator + ###################################### + # Qt Creator = shadow build + # Visual Studio = no shadow build + + # Strips PWD (source) from OUT_PWD (build) to test if they are on the same path + # - contains() does not work + # - equals( PWD, $${OUT_PWD} ) is not sufficient + REP = $$replace(OUT_PWD, $${PWD}, "") + + # Test if Build dir is outside Source dir + # if REP == OUT_PWD, not Visual Studio + !equals( REP, $${OUT_PWD} ):VISUALSTUDIO = true + unset(REP) + + # Set OUT_PWD to ./bin so that qmake doesn't clutter PWD + # Unfortunately w/ VS qmake still creates empty debug/release folders in PWD. + # They are never used but get auto-generated because of CONFIG += debug_and_release + $$VISUALSTUDIO:OUT_PWD = $${_PRO_FILE_PWD_}/bin +} ############################### -## INCLUDES +## FUNCTIONS ############################### include(NifSkope_functions.pri) -include(NifSkope_targets.pri) ############################### @@ -103,20 +108,17 @@ DEFINES += NIFSKOPE_VERSION=\\\"$${VER}\\\" # build_pass is necessary # Otherwise it will create empty .moc, .ui, etc. dirs on the drive root -build_pass { - Debug: bld = debug - Release: bld = release - - equals( SHADOWBUILD, true ) { - # Qt Creator - DESTDIR = $${OUT_PWD}/$${bld} +build_pass|!debug_and_release { + win32:equals( VISUALSTUDIO, true ) { + # Visual Studio + DESTDIR = $${_PRO_FILE_PWD_}/bin/$${BUILD} # INTERMEDIATE FILES - INTERMEDIATE = $${DESTDIR}/../GeneratedFiles/ + INTERMEDIATE = $${DESTDIR}/../GeneratedFiles/$${BUILD} } else { - # Visual Studio - DESTDIR = $${_PRO_FILE_PWD_}/bin/$${bld} + # Qt Creator + DESTDIR = $${OUT_PWD}/$${BUILD} # INTERMEDIATE FILES - INTERMEDIATE = $${DESTDIR}/../GeneratedFiles/$${bld} + INTERMEDIATE = $${DESTDIR}/../GeneratedFiles/ } UI_DIR = $${INTERMEDIATE}/.ui @@ -125,6 +127,12 @@ build_pass { OBJECTS_DIR = $${INTERMEDIATE}/.obj } +############################### +## TARGETS +############################### + +include(NifSkope_targets.pri) + ############################### ## PROJECT SCOPES @@ -142,7 +150,7 @@ HEADERS += \ src/gl/dds/Image.h \ src/gl/dds/PixelFormat.h \ src/gl/dds/Stream.h \ - src/gl/glcontrolable.h \ + src/gl/controllers.h \ src/gl/glcontroller.h \ src/gl/glmarker.h \ src/gl/glmesh.h \ @@ -153,11 +161,11 @@ HEADERS += \ src/gl/gltex.h \ src/gl/gltexloaders.h \ src/gl/gltools.h \ + src/gl/icontrollable.h \ src/gl/marker/constraints.h \ src/gl/marker/furniture.h \ src/gl/renderer.h \ src/glview.h \ - src/hacking.h \ src/importex/3ds.h \ src/kfmmodel.h \ src/message.h \ @@ -168,9 +176,9 @@ HEADERS += \ src/nifskope.h \ src/niftypes.h \ src/nifvalue.h \ - src/nvtristripwrapper.h \ - src/options.h \ + src/nvtristripwrapper.h \ src/qhull.h \ + src/settings.h \ src/spellbook.h \ src/spells/blocks.h \ src/spells/mesh.h \ @@ -181,7 +189,6 @@ HEADERS += \ src/spells/texture.h \ src/spells/transform.h \ src/widgets/colorwheel.h \ - src/widgets/copyfnam.h \ src/widgets/fileselect.h \ src/widgets/floatedit.h \ src/widgets/floatslider.h \ @@ -195,7 +202,13 @@ HEADERS += \ src/widgets/valueedit.h \ src/widgets/xmlcheck.h \ src/ui/about_dialog.h \ - src/version.h + src/ui/checkablemessagebox.h \ + src/ui/settingsdialog.h \ + src/version.h \ + lib/half.h \ + lib/dds.h \ + src/gl/bsshape.h \ + src/material.h SOURCES += \ src/basemodel.cpp \ @@ -205,6 +218,7 @@ SOURCES += \ src/gl/dds/DirectDrawSurface.cpp \ src/gl/dds/Image.cpp \ src/gl/dds/Stream.cpp \ + src/gl/controllers.cpp \ src/gl/glcontroller.cpp \ src/gl/glmarker.cpp \ src/gl/glmesh.cpp \ @@ -229,12 +243,13 @@ SOURCES += \ src/nifmodel.cpp \ src/nifproxy.cpp \ src/nifskope.cpp \ + src/nifskope_ui.cpp \ src/niftypes.cpp \ src/nifvalue.cpp \ src/nifxml.cpp \ - src/nvtristripwrapper.cpp \ - src/options.cpp \ + src/nvtristripwrapper.cpp \ src/qhull.cpp \ + src/settings.cpp \ src/spellbook.cpp \ src/spells/animation.cpp \ src/spells/blocks.cpp \ @@ -245,7 +260,7 @@ SOURCES += \ src/spells/havok.cpp \ src/spells/headerstring.cpp \ src/spells/light.cpp \ - src/spells/material.cpp \ + src/spells/materialedit.cpp \ src/spells/mesh.cpp \ src/spells/misc.cpp \ src/spells/moppcode.cpp \ @@ -260,7 +275,6 @@ SOURCES += \ src/spells/texture.cpp \ src/spells/transform.cpp \ src/widgets/colorwheel.cpp \ - src/widgets/copyfnam.cpp \ src/widgets/fileselect.cpp \ src/widgets/floatedit.cpp \ src/widgets/floatslider.cpp \ @@ -274,13 +288,24 @@ SOURCES += \ src/widgets/valueedit.cpp \ src/widgets/xmlcheck.cpp \ src/ui/about_dialog.cpp \ - src/version.cpp + src/ui/checkablemessagebox.cpp \ + src/ui/settingsdialog.cpp \ + src/version.cpp \ + lib/half.cpp \ + src/gl/bsshape.cpp \ + src/material.cpp RESOURCES += \ res/nifskope.qrc FORMS += \ - src/ui/about_dialog.ui + src/ui/about_dialog.ui \ + src/ui/checkablemessagebox.ui \ + src/ui/nifskope.ui \ + src/ui/settingsdialog.ui \ + src/ui/settingsgeneral.ui \ + src/ui/settingsrender.ui \ + src/ui/settingsresources.ui ############################### @@ -288,7 +313,6 @@ FORMS += \ ############################### fsengine { - DEFINES += FSENGINE INCLUDEPATH += lib/fsengine HEADERS += \ lib/fsengine/bsa.h \ @@ -328,6 +352,67 @@ qhull { lib/qhull/src/libqhull/user.h } +soil { + INCLUDEPATH += lib/soil + HEADERS += \ + lib/soil/image_DXT.h \ + lib/soil/image_helper.h \ + lib/soil/SOIL.h \ + lib/soil/stb_image_aug.h \ + lib/soil/stbi_DDS_aug.h \ + lib/soil/stbi_DDS_aug_c.h + SOURCES += \ + lib/soil/image_DXT.c \ + lib/soil/image_helper.c \ + lib/soil/SOIL.c \ + lib/soil/stb_image_aug.c +} + +zlib { + INCLUDEPATH += lib/zlib + + HEADERS += \ + lib/zlib/crc32.h \ + lib/zlib/deflate.h \ + lib/zlib/gzguts.h \ + lib/zlib/inffast.h \ + lib/zlib/inffixed.h \ + lib/zlib/inflate.h \ + lib/zlib/inftrees.h \ + lib/zlib/trees.h \ + lib/zlib/zconf.h \ + lib/zlib/zlib.h \ + lib/zlib/zutil.h + + SOURCES += \ + lib/zlib/adler32.c \ + lib/zlib/compress.c \ + lib/zlib/crc32.c \ + lib/zlib/deflate.c \ + lib/zlib/gzclose.c \ + lib/zlib/gzlib.c \ + lib/zlib/gzread.c \ + lib/zlib/gzwrite.c \ + lib/zlib/infback.c \ + lib/zlib/inffast.c \ + lib/zlib/inflate.c \ + lib/zlib/inftrees.c \ + lib/zlib/trees.c \ + lib/zlib/uncompr.c \ + lib/zlib/zutil.c +} + +lz4 { + DEFINES += LZ4_STATIC XXH_PRIVATE_API + + HEADERS += \ + lib/lz4frame.h \ + lib/xxhash.h + + SOURCES += \ + lib/lz4frame.c \ + lib/xxhash.c +} ############################### ## COMPILER SCOPES @@ -344,32 +429,31 @@ win32 { # MSVC # Both Visual Studio and Qt Creator -# Recommended: msvc2012 or higher -# (msvc2010 not tested) -*msvc201* { +# Required: msvc2013 or higher +*msvc* { + + # Grab _MSC_VER from the mkspecs that Qt was compiled with + # e.g. VS2013 = 1800, VS2012 = 1700, VS2010 = 1600 + _MSC_VER = $$find(QMAKE_COMPILER_DEFINES, "_MSC_VER") + _MSC_VER = $$split(_MSC_VER, =) + _MSC_VER = $$member(_MSC_VER, 1) + + # Reject unsupported MSVC versions + !isEmpty(_MSC_VER):lessThan(_MSC_VER, 1800) { + error("NifSkope only supports MSVC 2013 or later. If this is too prohibitive you may use Qt Creator with MinGW.") + } + # So VCProj Filters do not flatten headers/source CONFIG -= flat # COMPILER FLAGS # Optimization flags - QMAKE_CXXFLAGS_RELEASE *= -O2 + QMAKE_CXXFLAGS_RELEASE *= -O2 -arch:SSE2 # SSE2 is the default, but make it explicit # Multithreaded compiling for Visual Studio QMAKE_CXXFLAGS += -MP - - # LINKER FLAGS - # Manifest Embed - # msvc2012 only (when /MANIFEST:embed was introduced) - *msvc2012:$$SHADOWBUILD { - # Qt Creator only - # It gives occasional mt.exe errors post-link, so replicate /MANIFEST:embed like VS - # Check status of bug: https://bugreports.qt-project.org/browse/QTBUG-37363 - # https://codereview.qt-project.org/#change,80782 - # ... So that this may removed later. - CONFIG -= embed_manifest_exe - QMAKE_LFLAGS += /MANIFEST:embed /MANIFESTUAC - } + # LINKER FLAGS # Relocate .lib and .exp files to keep release dir clean QMAKE_LFLAGS += /IMPLIB:$$syspath($${INTERMEDIATE}/NifSkope.lib) @@ -382,12 +466,6 @@ win32 { QMAKE_POST_LINK += $$QMAKE_DEL_FILE $$syspath($${DESTDIR}/*.manifest) $$nt } -# MSVC < 2010 -*msvc200* { - # Throw up a warning - message( WARNING: Project file does not support MSVC 2008 or lower ) -} - # MinGW, GCC # Recommended: GCC 4.8.1+ @@ -396,9 +474,20 @@ win32 { # COMPILER FLAGS # Optimization flags - QMAKE_CXXFLAGS_RELEASE *= -O3 + QMAKE_CXXFLAGS_DEBUG -= -O0 -g + QMAKE_CXXFLAGS_DEBUG *= -Og -g3 + QMAKE_CXXFLAGS_RELEASE *= -O3 -mfpmath=sse + # C++11 Support QMAKE_CXXFLAGS_RELEASE *= -std=c++11 + + # Extension flags + QMAKE_CXXFLAGS_RELEASE *= -msse2 -msse +} + +win32 { + # GL libs for Qt 5.5+ + LIBS += -lopengl32 -lglu32 } unix:!macx { @@ -411,7 +500,7 @@ macx { # Pre/Post Link in build_pass only -build_pass { +build_pass|!debug_and_release { ############################### ## QMAKE_PRE_LINK @@ -431,7 +520,7 @@ build_pass { ## QMAKE_POST_LINK ############################### - DEP += \ + win32:DEP += \ dep/NifMopp.dll XML += \ @@ -444,8 +533,8 @@ build_pass { QHULLTXT += \ lib/qhull/COPYING.txt - LANG += \ - res/lang + #LANG += \ + # res/lang SHADERS += \ res/shaders @@ -459,8 +548,9 @@ build_pass { copyDirs( $$SHADERS, shaders ) - copyDirs( $$LANG, lang ) - copyFiles( $$XML $$DEP $$QSS ) + #copyDirs( $$LANG, lang ) + #copyFiles( $$XML $$QSS ) + win32:copyFiles( $$DEP ) # Copy Readmes and rename to TXT copyFiles( $$READMES,,,, md:txt ) @@ -477,8 +567,10 @@ build_pass { $$[QT_INSTALL_PLUGINS]/platforms/qwindows$${DLLEXT} imageformats += \ + $$[QT_INSTALL_PLUGINS]/imageformats/qdds$${DLLEXT} \ $$[QT_INSTALL_PLUGINS]/imageformats/qjpeg$${DLLEXT} \ - $$[QT_INSTALL_PLUGINS]/imageformats/qtga$${DLLEXT} + $$[QT_INSTALL_PLUGINS]/imageformats/qtga$${DLLEXT} \ + $$[QT_INSTALL_PLUGINS]/imageformats/qwebp$${DLLEXT} copyFiles( $$platforms, platforms, true ) copyFiles( $$imageformats, imageformats, true ) @@ -489,7 +581,7 @@ build_pass { # Build Messages # (Add `buildMessages` to CONFIG to use) -buildMessages:build_pass { +buildMessages:build_pass|buildMessages:!debug_and_release { CONFIG(debug, debug|release) { message("Debug Mode") } CONFIG(release, release|debug) { @@ -503,8 +595,8 @@ buildMessages:build_pass { message(build ________ $$OUT_PWD) message(Qt binaries __ $$[QT_INSTALL_BINS]) - build_pass:equals( SHADOWBUILD, true ) { - message(Shadow build __ Yes) + build_pass:equals( VISUALSTUDIO, true ) { + message(Visual Studio __ Yes) } #message($$CONFIG) diff --git a/NifSkope_functions.pri b/NifSkope_functions.pri index 5e285aefd..e13d0b4e5 100644 --- a/NifSkope_functions.pri +++ b/NifSkope_functions.pri @@ -167,9 +167,10 @@ defineReplace(QtBins) { } *-g++ { + # Copies libgcc*-*, libstdc++-*, libwinpthread-* + # Note: As of Qt 5.5, changed `lib*` to `lib*-*` in order to avoid unneeded libs. list += \ - $$[QT_INSTALL_BINS]/icu*.dll \ - $$[QT_INSTALL_BINS]/lib*.dll + $$[QT_INSTALL_BINS]/lib*-*.dll } return($$list) @@ -199,7 +200,8 @@ defineTest(copyFiles) { } ddir = $$syspath($${DESTDIR}$${QMAKE_DIR_SEP}$${subdir}) - QMAKE_POST_LINK += $$QMAKE_CHK_DIR_EXISTS $${ddir} $$QMAKE_MKDIR $${ddir} $$nt + unix:QMAKE_POST_LINK += $$QMAKE_MKDIR_CMD $${ddir} $$nt + else:QMAKE_POST_LINK += $$QMAKE_CHK_DIR_EXISTS $${ddir} $$QMAKE_MKDIR $${ddir} $$nt for(FILE, files) { fileabs = $${PWD}$${QMAKE_DIR_SEP}$${FILE} @@ -239,7 +241,8 @@ defineTest(copyDirs) { } ddir = $$syspath($${DESTDIR}$${QMAKE_DIR_SEP}$${subdir}) - QMAKE_POST_LINK += $$QMAKE_CHK_DIR_EXISTS $${ddir} $$QMAKE_MKDIR $${ddir} $$nt + unix:QMAKE_POST_LINK += $$QMAKE_MKDIR_CMD $${ddir} $$nt + else:QMAKE_POST_LINK += $$QMAKE_CHK_DIR_EXISTS $${ddir} $$QMAKE_MKDIR $${ddir} $$nt for(DIR, dirs) { dirabs = $${PWD}$${QMAKE_DIR_SEP}$${DIR} diff --git a/NifSkope_targets.pri b/NifSkope_targets.pri index 171724c68..ce93ef117 100644 --- a/NifSkope_targets.pri +++ b/NifSkope_targets.pri @@ -4,6 +4,38 @@ # Note: dir or file in build dir cannot be named the same as the target # e.g. "docs" target will fail if a "docs" folder is in OUT_PWD +win32:EXE = ".exe" +else:EXE = "" + +############################### +## lupdate / lrelease +############################### + +QMAKE_LUPDATE = $$[QT_INSTALL_BINS]/lupdate$${EXE} +exists($$QMAKE_LUPDATE) { + # Make target for Updating .ts + updatets.target = updatets + updatets.commands += cd $${_PRO_FILE_PWD_} $$nt + updatets.commands += $$[QT_INSTALL_BINS]/lupdate $${_PRO_FILE_} $$nt + updatets.CONFIG += no_check_exist no_link no_clean + + QMAKE_EXTRA_TARGETS += updatets +} else { + message("lupdate could not be found, ignoring make target") +} + +QMAKE_LRELEASE = $$[QT_INSTALL_BINS]/lrelease$${EXE} +exists($$QMAKE_LRELEASE) { + # Build Step for Releasing .ts->.qm + updateqm.input = TRANSLATIONS + updateqm.output = $$syspath($${DESTDIR}/lang/${QMAKE_FILE_BASE}.qm) + updateqm.commands = $$[QT_INSTALL_BINS]/lrelease ${QMAKE_FILE_IN} -qm $$syspath($${DESTDIR}/lang/${QMAKE_FILE_BASE}.qm) $$nt + updateqm.CONFIG += no_check_exist no_link no_clean target_predeps + + QMAKE_EXTRA_COMPILERS += updateqm +} else { + message("lrelease could not be found, ignoring build step") +} ############################### ## Docsys @@ -25,11 +57,7 @@ docs.target = docs # Vars docsys = $$syspath($${PWD}/build/docsys) indoc = doc$${QMAKE_DIR_SEP} -out = $$syspath($${OUT_PWD}) - -# Find out if release or debug because DESTDIR is blank -exists($$out/debug/NifSkope.exe):outdoc = $$syspath($${out}/debug/doc) -exists($$out/release/NifSkope.exe):outdoc = $$syspath($${out}/release/doc) +outdoc = $$syspath($${DESTDIR}/doc) # COMMANDS @@ -76,7 +104,7 @@ doxyfile = $$syspath($${OUT_PWD}/Doxyfile) doxyfilein = $$syspath($${PWD}/build/doxygen/Doxyfile.in) # Paths -qhgen = $$syspath($$[QT_INSTALL_BINS]/qhelpgenerator.exe) +qhgen = $$syspath($$[QT_INSTALL_BINS]/qhelpgenerator$${EXE}) dot = $$syspath(C:/Program Files (x86)/Graphviz2.37/bin) # TODO _7z = $$get7z() diff --git a/README.md b/README.md index d1051dbf4..69525d362 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ ================= - NifSkope 1.2.0a2.dev3 + NifSkope 2.0.dev6 ================= NifSkope is a tool for opening and editing the NetImmerse file format (NIF). NIF is used by video games such as Morrowind, Oblivion, Skyrim, Fallout 3, Fallout: New Vegas, Civilization IV, and more. diff --git a/build/VERSION b/build/VERSION index 6ad2126f9..277340063 100644 --- a/build/VERSION +++ b/build/VERSION @@ -1 +1 @@ -1.2.0a2.dev3 +2.0.dev6 diff --git a/build/doxygen/Doxyfile.in b/build/doxygen/Doxyfile.in index a21ed7824..974d67bc7 100644 --- a/build/doxygen/Doxyfile.in +++ b/build/doxygen/Doxyfile.in @@ -180,7 +180,7 @@ SHORT_NAMES = NO # description.) # The default value is: NO. -JAVADOC_AUTOBRIEF = NO +JAVADOC_AUTOBRIEF = YES # If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first # line (until the first dot) of a Qt-style comment as the brief description. If @@ -188,7 +188,7 @@ JAVADOC_AUTOBRIEF = NO # requiring an explicit \brief command for a brief description.) # The default value is: NO. -QT_AUTOBRIEF = NO +QT_AUTOBRIEF = YES # The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a # multi-line C++ special comment block (i.e. a block of //! or /// comments) as @@ -752,7 +752,8 @@ WARN_LOGFILE = INPUT = "@INPUT@" \ "@PWD@\lib\fsengine" \ - "@PWD@\lib\NvTriStrip" + "@PWD@\lib\NvTriStrip" \ + "@PWD@\DOXYGEN.md" # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses @@ -904,7 +905,7 @@ FILTER_SOURCE_PATTERNS = # (index.html). This can be useful if you have a project on for instance GitHub # and want to reuse the introduction page also for the doxygen output. -USE_MDFILE_AS_MAINPAGE = +USE_MDFILE_AS_MAINPAGE = "@PWD@\DOXYGEN.md" #--------------------------------------------------------------------------- # Configuration options related to source browsing @@ -1112,7 +1113,7 @@ HTML_STYLESHEET = # see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_EXTRA_STYLESHEET = +HTML_EXTRA_STYLESHEET = "@PWD@\build\doxygen\extra.css" # The HTML_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the HTML output directory. Note diff --git a/build/doxygen/extra.css b/build/doxygen/extra.css new file mode 100644 index 000000000..f93e172be --- /dev/null +++ b/build/doxygen/extra.css @@ -0,0 +1,9 @@ +code { + background-color: #f3f3f3; + border: 1px solid #eeeeee; + font-size: 90%; + padding: 2px 5px 2px 5px; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; +} \ No newline at end of file diff --git a/lib/NvTriStrip/NvTriStripObjects.cpp b/lib/NvTriStrip/NvTriStripObjects.cpp index d0e3b7e4b..d0fafa551 100644 --- a/lib/NvTriStrip/NvTriStripObjects.cpp +++ b/lib/NvTriStrip/NvTriStripObjects.cpp @@ -1,5 +1,7 @@ +#ifdef _MSC_VER #pragma warning( disable : 4786 ) +#endif #include #include diff --git a/lib/dds.h b/lib/dds.h new file mode 100644 index 000000000..245b6d80f --- /dev/null +++ b/lib/dds.h @@ -0,0 +1,153 @@ +//-------------------------------------------------------------------------------------- +// dds.h +// +// This header defines constants and structures that are useful when parsing +// DDS files. DDS files were originally designed to use several structures +// and constants that are native to DirectDraw and are defined in ddraw.h, +// such as DDSURFACEDESC2 and DDSCAPS2. This file defines similar +// (compatible) constants and structures so that one can use DDS files +// without needing to include ddraw.h. +//-------------------------------------------------------------------------------------- + +#ifndef _DDS_H_ +#define _DDS_H_ + +#include +#include + +#pragma pack(push,1) + +#define DDS_MAGIC 0x20534444 // "DDS " + +struct DDS_PIXELFORMAT +{ + DWORD dwSize; + DWORD dwFlags; + DWORD dwFourCC; + DWORD dwRGBBitCount; + DWORD dwRBitMask; + DWORD dwGBitMask; + DWORD dwBBitMask; + DWORD dwABitMask; +}; + +#define DDS_FOURCC 0x00000004 // DDPF_FOURCC +#define DDS_RGB 0x00000040 // DDPF_RGB +#define DDS_RGBA 0x00000041 // DDPF_RGB | DDPF_ALPHAPIXELS +#define DDS_LUMINANCE 0x00020000 // DDPF_LUMINANCE +#define DDS_ALPHA 0x00000002 // DDPF_ALPHA + +const DDS_PIXELFORMAT DDSPF_DXT1 = + { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, MAKEFOURCC('D','X','T','1'), 0, 0, 0, 0, 0 }; + +const DDS_PIXELFORMAT DDSPF_DXT2 = + { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, MAKEFOURCC('D','X','T','2'), 0, 0, 0, 0, 0 }; + +const DDS_PIXELFORMAT DDSPF_DXT3 = + { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, MAKEFOURCC('D','X','T','3'), 0, 0, 0, 0, 0 }; + +const DDS_PIXELFORMAT DDSPF_DXT4 = + { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, MAKEFOURCC('D','X','T','4'), 0, 0, 0, 0, 0 }; + +const DDS_PIXELFORMAT DDSPF_DXT5 = + { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, MAKEFOURCC('D','X','T','5'), 0, 0, 0, 0, 0 }; + +const DDS_PIXELFORMAT DDSPF_A8R8G8B8 = + { sizeof(DDS_PIXELFORMAT), DDS_RGBA, 0, 32, 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000 }; + +const DDS_PIXELFORMAT DDSPF_A1R5G5B5 = + { sizeof(DDS_PIXELFORMAT), DDS_RGBA, 0, 16, 0x00007c00, 0x000003e0, 0x0000001f, 0x00008000 }; + +const DDS_PIXELFORMAT DDSPF_A4R4G4B4 = + { sizeof(DDS_PIXELFORMAT), DDS_RGBA, 0, 16, 0x00000f00, 0x000000f0, 0x0000000f, 0x0000f000 }; + +const DDS_PIXELFORMAT DDSPF_R8G8B8 = + { sizeof(DDS_PIXELFORMAT), DDS_RGB, 0, 24, 0x00ff0000, 0x0000ff00, 0x000000ff, 0x00000000 }; + +const DDS_PIXELFORMAT DDSPF_R5G6B5 = + { sizeof(DDS_PIXELFORMAT), DDS_RGB, 0, 16, 0x0000f800, 0x000007e0, 0x0000001f, 0x00000000 }; + +// This indicates the DDS_HEADER_DXT10 extension is present (the format is in dxgiFormat) +const DDS_PIXELFORMAT DDSPF_DX10 = + { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, MAKEFOURCC('D','X','1','0'), 0, 0, 0, 0, 0 }; + +#define DDS_HEADER_FLAGS_TEXTURE 0x00001007 // DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT +#define DDS_HEADER_FLAGS_MIPMAP 0x00020000 // DDSD_MIPMAPCOUNT +#define DDS_HEADER_FLAGS_VOLUME 0x00800000 // DDSD_DEPTH +#define DDS_HEADER_FLAGS_PITCH 0x00000008 // DDSD_PITCH +#define DDS_HEADER_FLAGS_LINEARSIZE 0x00080000 // DDSD_LINEARSIZE + +#define DDS_SURFACE_FLAGS_TEXTURE 0x00001000 // DDSCAPS_TEXTURE +#define DDS_SURFACE_FLAGS_MIPMAP 0x00400008 // DDSCAPS_COMPLEX | DDSCAPS_MIPMAP +#define DDS_SURFACE_FLAGS_CUBEMAP 0x00000008 // DDSCAPS_COMPLEX + +#define DDS_CUBEMAP_POSITIVEX 0x00000600 // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEX +#define DDS_CUBEMAP_NEGATIVEX 0x00000a00 // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEX +#define DDS_CUBEMAP_POSITIVEY 0x00001200 // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEY +#define DDS_CUBEMAP_NEGATIVEY 0x00002200 // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEY +#define DDS_CUBEMAP_POSITIVEZ 0x00004200 // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEZ +#define DDS_CUBEMAP_NEGATIVEZ 0x00008200 // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEZ + +#define DDS_CUBEMAP_ALLFACES ( DDS_CUBEMAP_POSITIVEX | DDS_CUBEMAP_NEGATIVEX |\ + DDS_CUBEMAP_POSITIVEY | DDS_CUBEMAP_NEGATIVEY |\ + DDS_CUBEMAP_POSITIVEZ | DDS_CUBEMAP_NEGATIVEZ ) + +#define DDS_FLAGS_VOLUME 0x00200000 // DDSCAPS2_VOLUME + +// Subset here matches D3D10_RESOURCE_DIMENSION and D3D11_RESOURCE_DIMENSION +enum DDS_RESOURCE_DIMENSION +{ + DDS_DIMENSION_TEXTURE1D = 2, + DDS_DIMENSION_TEXTURE2D = 3, + DDS_DIMENSION_TEXTURE3D = 4, +}; + +// Subset here matches D3D10_RESOURCE_MISC_FLAG and D3D11_RESOURCE_MISC_FLAG +enum DDS_RESOURCE_MISC_FLAG +{ + DDS_RESOURCE_MISC_TEXTURECUBE = 0x4L, +}; + +enum DDS_MISC_FLAGS2 +{ + DDS_MISC_FLAGS2_ALPHA_MODE_MASK = 0x7L, +}; + +enum DDS_ALPHA_MODE +{ + DDS_ALPHA_MODE_UNKNOWN = 0, + DDS_ALPHA_MODE_STRAIGHT = 1, + DDS_ALPHA_MODE_PREMULTIPLIED = 2, + DDS_ALPHA_MODE_OPAQUE = 3, + DDS_ALPHA_MODE_CUSTOM = 4, +}; + +typedef struct +{ + DWORD dwSize; + DWORD dwHeaderFlags; + DWORD dwHeight; + DWORD dwWidth; + DWORD dwPitchOrLinearSize; + DWORD dwDepth; // only if DDS_HEADER_FLAGS_VOLUME is set in dwHeaderFlags + DWORD dwMipMapCount; + DWORD dwReserved1[11]; + DDS_PIXELFORMAT ddspf; + DWORD dwSurfaceFlags; + DWORD dwCubemapFlags; + DWORD dwReserved2[3]; +} DDS_HEADER; + +typedef struct +{ + DXGI_FORMAT dxgiFormat; + DDS_RESOURCE_DIMENSION resourceDimension; + DWORD miscFlag; // see DDS_RESOURCE_MISC_FLAG + DWORD arraySize; + DWORD miscFlags2; // see DDS_MISC_FLAGS2 +} DDS_HEADER_DXT10; + + +#pragma pack(pop) + +#endif // _DDS_H diff --git a/lib/fsengine/bsa.cpp b/lib/fsengine/bsa.cpp index 44c303fff..115a78584 100644 --- a/lib/fsengine/bsa.cpp +++ b/lib/fsengine/bsa.cpp @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -31,6 +31,9 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "bsa.h" +#include "dds.h" +#include "zlib/zlib.h" +#include "lz4frame.h" #include #include @@ -38,104 +41,18 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include -/* Default header data */ -#define MW_BSAHEADER_FILEID 0x00000100 //!< Magic for Morrowind BSA -#define OB_BSAHEADER_FILEID 0x00415342 //!< Magic for Oblivion BSA, the literal string "BSA\0". -#define OB_BSAHEADER_VERSION 0x67 //!< Version number of an Oblivion BSA -#define F3_BSAHEADER_VERSION 0x68 //!< Version number of a Fallout 3 BSA - -/* Archive flags */ -#define OB_BSAARCHIVE_PATHNAMES 0x0001 //!< Whether the BSA has names for paths -#define OB_BSAARCHIVE_FILENAMES 0x0002 //!< Whether the BSA has names for files -#define OB_BSAARCHIVE_COMPRESSFILES 0x0004 //!< Whether the files are compressed -#define F3_BSAARCHIVE_PREFIXFULLFILENAMES 0x0100 //!< Whether the name is prefixed to the data? - -/* File flags */ -#define OB_BSAFILE_NIF 0x0001 //!< Set when the BSA contains NIF files -#define OB_BSAFILE_DDS 0x0002 //!< Set when the BSA contains DDS files -#define OB_BSAFILE_XML 0x0004 //!< Set when the BSA contains XML files -#define OB_BSAFILE_WAV 0x0008 //!< Set when the BSA contains WAV files -#define OB_BSAFILE_MP3 0x0010 //!< Set when the BSA contains MP3 files -#define OB_BSAFILE_TXT 0x0020 //!< Set when the BSA contains TXT files -#define OB_BSAFILE_HTML 0x0020 //!< Set when the BSA contains HTML files -#define OB_BSAFILE_BAT 0x0020 //!< Set when the BSA contains BAT files -#define OB_BSAFILE_SCC 0x0020 //!< Set when the BSA contains SCC files -#define OB_BSAFILE_SPT 0x0040 //!< Set when the BSA contains SPT files -#define OB_BSAFILE_TEX 0x0080 //!< Set when the BSA contains TEX files -#define OB_BSAFILE_FNT 0x0080 //!< Set when the BSA contains FNT files -#define OB_BSAFILE_CTL 0x0100 //!< Set when the BSA contains CTL files - -/* Bitmasks for the size field in the header */ -#define OB_BSAFILE_SIZEMASK 0x3fffffff //!< Bit mask with OBBSAFileInfo::sizeFlags to get the size of the file - -/* Record flags */ -#define OB_BSAFILE_FLAG_COMPRESS 0xC0000000 //!< Bit mask with OBBSAFileInfo::sizeFlags to get the compression status - -//! \file bsa.cpp OBBSAHeader / \link OBBSAFileInfo FileInfo\endlink / \link OBBSAFolderInfo FolderInfo\endlink; MWBSAHeader, MWBSAFileSizeOffset - -//! The header of an Oblivion BSA. -/*! - * Follows OB_BSAHEADER_FILEID and OB_BSAHEADER_VERSION. - */ -struct OBBSAHeader -{ - quint32 FolderRecordOffset; //!< Offset of beginning of folder records - quint32 ArchiveFlags; //!< Archive flags - quint32 FolderCount; //!< Total number of folder records (OBBSAFolderInfo) - quint32 FileCount; //!< Total number of file records (OBBSAFileInfo) - quint32 FolderNameLength; //!< Total length of folder names - quint32 FileNameLength; //!< Total length of file names - quint32 FileFlags; //!< File flags - - friend QDebug operator<<( QDebug dbg, const OBBSAHeader & head ) - { - return dbg << "BSAHeader:" - << "\n folder offset" << head.FolderRecordOffset - << "\n archive flags" << head.ArchiveFlags - << "\n folder Count" << head.FolderCount - << "\n file Count" << head.FileCount - << "\n folder name length" << head.FolderNameLength - << "\n file name length" << head.FileNameLength - << "\n file flags" << head.FileFlags; - } - -}; - -//! Info for a file inside an Oblivion BSA -struct OBBSAFileInfo -{ - quint64 hash; //!< Hash of the filename - quint32 sizeFlags; //!< Size of the data, possibly with OB_BSAFILE_FLAG_COMPRESS set - quint32 offset; //!< Offset to raw file data -}; - -//! Info for a folder inside an Oblivion BSA -struct OBBSAFolderInfo -{ - quint64 hash; //!< Hash of the folder name - quint32 fileCount; //!< Number of files in folder - quint32 offset; //!< Offset to name of this folder -}; - -//! The header of a Morrowind BSA -struct MWBSAHeader -{ - quint32 HashOffset; //!< Offset of hash table minus header size (12) - quint32 FileCount; //!< Number of files in the archive -}; - -//! The file size and offset of an entry in a Morrowind BSA -struct MWBSAFileSizeOffset -{ - quint32 size; //!< The size of the file - quint32 offset; //!< The offset of the file -}; // see bsa.h quint32 BSA::BSAFile::size() const { - return sizeFlags & OB_BSAFILE_SIZEMASK; + if ( sizeFlags > 0 ) { + // Skyrim and earlier + return sizeFlags & OB_BSAFILE_SIZEMASK; + } + // TODO: Not correct for texture BA2s + return (packedLength == 0) ? unpackedLength : packedLength; } // see bsa.h @@ -159,7 +76,7 @@ static bool BSAReadSizedString( QFile & bsa, QString & s ) QByteArray b( len, char(0) ); if ( bsa.read( b.data(), len ) == len ) { - s = b; + s = QString::fromLatin1( b ); //qDebug() << "bailout on" << __FILE__ << "line" << __LINE__; return true; } @@ -170,13 +87,66 @@ static bool BSAReadSizedString( QFile & bsa, QString & s ) } } +QByteArray gUncompress( const QByteArray & data, const int size ) +{ + if ( data.size() <= 4 ) { + qWarning( "gUncompress: Input data is truncated" ); + return QByteArray(); + } + + QByteArray result; + + int ret; + z_stream strm; + static const int CHUNK_SIZE = 1024; + char out[CHUNK_SIZE]; + + /* allocate inflate state */ + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.avail_in = size; + strm.next_in = (Bytef*)(data.data()); + + ret = inflateInit2( &strm, 15 + 32 ); // gzip decoding + Q_ASSERT( ret == Z_OK ); + if ( ret != Z_OK ) + return QByteArray(); + + // run inflate() + do { + strm.avail_out = CHUNK_SIZE; + strm.next_out = (Bytef*)(out); + + ret = inflate( &strm, Z_NO_FLUSH ); + Q_ASSERT( ret != Z_STREAM_ERROR ); // state not clobbered + + switch ( ret ) { + case Z_NEED_DICT: + ret = Z_DATA_ERROR; // and fall through + case Z_DATA_ERROR: + case Z_MEM_ERROR: + (void)inflateEnd( &strm ); + return QByteArray(); + } + + result.append( out, CHUNK_SIZE - strm.avail_out ); + } while ( strm.avail_out == 0 ); + + // clean up and return + inflateEnd( &strm ); + return result; +} + // see bsa.h BSA::BSA( const QString & filename ) : FSArchiveFile(), bsa( filename ), bsaInfo( QFileInfo(filename) ), status( "initialized" ) { - bsaPath = bsaInfo.absolutePath() + QDir::separator() + bsaInfo.fileName(); + bsaPath = bsaInfo.absoluteFilePath(); bsaBase = bsaInfo.absolutePath(); bsaName = bsaInfo.fileName(); + + root = new BSAFolder; } // see bsa.h @@ -197,15 +167,19 @@ bool BSA::canOpen( const QString & fn ) return false; //qDebug() << "Magic:" << QString::number( magic, 16 ); - if ( magic == OB_BSAHEADER_FILEID ) - { + if ( magic == F4_BSAHEADER_FILEID ) { if ( f.read( (char *) & version, sizeof( version ) ) != 4 ) return false; - - return ( version == OB_BSAHEADER_VERSION || version == F3_BSAHEADER_VERSION ); - } - else + return version == F4_BSAHEADER_VERSION; + } else if ( magic == OB_BSAHEADER_FILEID ) { + if ( f.read( (char *)&version, sizeof( version ) ) != 4 ) + return false; + + return (version == OB_BSAHEADER_VERSION || version == F3_BSAHEADER_VERSION || version == SSE_BSAHEADER_VERSION); + } else { return magic == MW_BSAHEADER_FILEID; + } + } return false; @@ -221,15 +195,88 @@ bool BSA::open() if ( ! bsa.open( QIODevice::ReadOnly ) ) throw QString( "file open" ); - quint32 magic, version; + quint32 magic; bsa.read( (char*) &magic, sizeof( magic ) ); - - if ( magic == OB_BSAHEADER_FILEID ) + + if ( magic == F4_BSAHEADER_FILEID ) { + bsa.read( (char*)&version, sizeof( version ) ); + + if ( version != F4_BSAHEADER_VERSION ) + throw QString( "file version" ); + + F4BSAHeader header; + if ( bsa.read( (char *)&header, sizeof( header ) ) != sizeof( header ) ) + throw QString( "header size" ); + + numFiles = header.numFiles; + + auto offset = header.nameTableOffset; + + QVector filepaths; + if ( bsa.seek( offset ) ) { + for ( quint32 i = 0; i < numFiles; i++ ) { + quint8 length; + bsa.read( (char*)&length, 2 ); + + QByteArray strdata( length, char( 0 ) ); + bsa.read( strdata.data(), length ); + + filepaths.append( QString::fromLatin1( strdata ) ); + } + } + + QString h = QString::fromLatin1( header.type, 4 ); + if ( h == "GNRL" ) { + // General BA2 Format + if ( bsa.seek( sizeof( header ) + 8 ) ) { + for ( quint32 i = 0; i < numFiles; i++ ) { + F4GeneralInfo finfo; + bsa.read( (char*)&finfo, sizeof( F4GeneralInfo ) ); + + QString fullpath = filepaths[i]; + fullpath.replace( "\\", "/" ); + + QString filename = fullpath.right( fullpath.length() - fullpath.lastIndexOf( "/" ) - 1 ); + QString folderName = fullpath.left( fullpath.lastIndexOf( "/" ) ); + + insertFile( insertFolder( folderName ), filename, finfo.packedSize, finfo.unpackedSize, finfo.offset ); + } + } + } else if ( h == "DX10" ) { + // Texture BA2 Format + if ( bsa.seek( sizeof( header ) + 8 ) ) { + for ( quint32 i = 0; i < numFiles; i++ ) { + F4Tex tex; + bsa.read( (char*)&tex.header, 24 ); + + QVector texChunks; + for ( quint32 j = 0; j < tex.header.numChunks; j++ ) { + F4TexChunk texChunk; + bsa.read( (char*)&texChunk, 24 ); + texChunks.append( texChunk ); + } + + tex.chunks = texChunks; + + QString fullpath = filepaths[i]; + fullpath.replace( "\\", "/" ); + + QString filename = fullpath.right( fullpath.length() - fullpath.lastIndexOf( "/" ) - 1 ); + QString folderName = fullpath.left( fullpath.lastIndexOf( "/" ) ); + + F4TexChunk chunk = tex.chunks[0]; + + insertFile( insertFolder( folderName ), filename, chunk.packedSize, chunk.unpackedSize, chunk.offset, tex ); + } + } + } + } + else if ( magic == OB_BSAHEADER_FILEID ) { bsa.read( (char*) &version, sizeof( version ) ); - if ( version != OB_BSAHEADER_VERSION && version != F3_BSAHEADER_VERSION ) + if ( version != OB_BSAHEADER_VERSION && version != F3_BSAHEADER_VERSION && version != SSE_BSAHEADER_VERSION ) throw QString( "file version" ); OBBSAHeader header; @@ -237,20 +284,26 @@ bool BSA::open() if ( bsa.read( (char *) & header, sizeof( header ) ) != sizeof( header ) ) throw QString( "header size" ); - //qWarning() << bsaName << header; + numFiles = header.FileCount; + + //qDebug() << bsaName << header; if ( ( header.ArchiveFlags & OB_BSAARCHIVE_PATHNAMES ) == 0 || ( header.ArchiveFlags & OB_BSAARCHIVE_FILENAMES ) == 0 ) throw QString( "header flags" ); compressToggle = header.ArchiveFlags & OB_BSAARCHIVE_COMPRESSFILES; - if (version == F3_BSAHEADER_VERSION) { + if (version == F3_BSAHEADER_VERSION || version == SSE_BSAHEADER_VERSION) { namePrefix = header.ArchiveFlags & F3_BSAARCHIVE_PREFIXFULLFILENAMES; - } else { - namePrefix = false; } - if ( ! bsa.seek( header.FolderRecordOffset + header.FolderNameLength + header.FolderCount * ( 1 + sizeof( OBBSAFolderInfo ) ) + header.FileCount * sizeof( OBBSAFileInfo ) ) ) + int folderSize = 0; + if ( version != SSE_BSAHEADER_VERSION ) + folderSize = sizeof( OBBSAFolderInfo ); + else + folderSize = sizeof( SEBSAFolderInfo ); + + if ( !bsa.seek( header.FolderRecordOffset + header.FolderNameLength + header.FolderCount * (1 + folderSize) + header.FileCount * sizeof( OBBSAFileInfo ) ) ) throw QString( "file name seek" ); QByteArray fileNames( header.FileNameLength, char(0) ); @@ -262,23 +315,35 @@ bool BSA::open() if ( ! bsa.seek( header.FolderRecordOffset ) ) throw QString( "folder info seek" ); - - QVector folderInfos( header.FolderCount ); - if ( bsa.read( (char *) folderInfos.data(), header.FolderCount * sizeof( OBBSAFolderInfo ) ) != header.FolderCount * sizeof( OBBSAFolderInfo ) ) - throw QString( "folder info read" ); - + quint32 totalFileCount = 0; - - for ( const OBBSAFolderInfo folderInfo : folderInfos ) - { - // useless? - /* - qDebug() << __LINE__ << "position" << bsa.pos() << "offset" << folderInfo.offset; - if ( folderInfo.offset < header.FileNameLength || ! bsa.seek( folderInfo.offset - header.FileNameLength ) ) - throw QString( "folder content seek" ); - */ - - + bool ok = true; + + QVector folderInfos; + folderInfos.reserve( header.FolderCount ); + for ( quint32 i = 0; i < header.FolderCount; i++ ) { + BSAFolderInfo info = {}; + + // Hash + ok &= bsa.read( (char *)&info, 8 ) == 8; + // Filesize + ok &= bsa.read( (char *)&info + 8, 4 ) == 4; + if ( version == SSE_BSAHEADER_VERSION ) { + // Unknown value & Offset + ok &= bsa.read( (char *)&info + 12, 12 ) == 12; + } else { + // Offset + // Note: this is reading a uint32 into a uint64 whose memory must be zeroed. + ok &= bsa.read( (char *)&info + 16, 4 ) == 4; + } + + if ( !ok ) + throw QString( "folder info read" ); + + folderInfos << info; + } + + for ( const BSAFolderInfo folderInfo : folderInfos ) { QString folderName; if ( ! BSAReadSizedString( bsa, folderName ) || folderName.isEmpty() ) { @@ -286,7 +351,6 @@ bool BSA::open() throw QString( "folder name read" ); } - // qDebug() << folderName; BSAFolder * folder = insertFolder( folderName ); @@ -300,8 +364,8 @@ bool BSA::open() { if ( fileNameIndex >= header.FileNameLength ) throw QString( "file name size" ); - - QString fileName = ( fileNames.data() + fileNameIndex ); + + QString fileName = QString::fromLatin1( fileNames.data() + fileNameIndex ); fileNameIndex += fileName.length() + 1; insertFile( folder, fileName, fileInfo.sizeFlags, fileInfo.offset ); @@ -318,7 +382,7 @@ bool BSA::open() if ( bsa.read( (char *) & header, sizeof( header ) ) != sizeof( header ) ) throw QString( "header" ); - + numFiles = header.FileCount; compressToggle = false; namePrefix = false; @@ -381,11 +445,13 @@ void BSA::close() QMutexLocker lock( & bsaMutex ); bsa.close(); - qDeleteAll( root.children ); - qDeleteAll( root.files ); - root.children.clear(); - root.files.clear(); + qDeleteAll( root->children ); + qDeleteAll( root->files ); + root->children.clear(); + root->files.clear(); folders.clear(); + + delete root; } // see bsa.h @@ -413,22 +479,179 @@ bool BSA::fileContents( const QString & fn, QByteArray & content ) if (namePrefix) { char len; ok = bsa.read(&len, 1); - filesz -= len; + filesz -= len + 1; if (ok) ok = bsa.seek( file->offset + 1 + len ); } + + quint32 filesize = filesz; + if ( version == SSE_BSAHEADER_VERSION && file->sizeFlags > 0 && (file->compressed() ^ compressToggle) ) { + ok = bsa.read( (char*)&filesize, 4 ); + filesz -= 4; + } + content.resize( filesz ); - if ( ok && bsa.read( content.data(), filesz ) == filesz ) - { - if ( file->compressed() ^ compressToggle ) - { - quint8 x = content[0]; - content[0] = content[3]; - content[3] = x; - x = content[1]; - content[1] = content[2]; - content[2] = x; - content = qUncompress( content ); + if ( ok && bsa.read( content.data(), filesz ) == filesz ) { + if ( file->sizeFlags > 0 && (file->compressed() ^ compressToggle) ) { + // BSA + if ( version != SSE_BSAHEADER_VERSION ) { + content.remove( 0, 4 ); + QByteArray tmp3( content ); + + content = gUncompress( tmp3, filesz - 4 ); + } else { + QByteArray tmp; + tmp.resize( filesize ); + + LZ4F_decompressionContext_t dCtx = nullptr; + LZ4F_createDecompressionContext( &dCtx, LZ4F_VERSION ); + size_t dstSize = filesize; + size_t srcSize = content.size(); + + LZ4F_decompressOptions_t options = {}; + + LZ4F_decompress( dCtx, tmp.data(), &dstSize, content.data(), &srcSize, &options ); + LZ4F_errorCode_t error = LZ4F_freeDecompressionContext( dCtx ); + if ( error ) { + // TODO: Message logger + qDebug() << fn << "Error Code: " << error; + } + + content = tmp; + } + } else if ( file->packedLength > 0 && !file->tex.chunks.count() ) { + // General BA2 + QByteArray tmp( content ); + content.resize( file->unpackedLength ); + content = gUncompress( tmp, file->packedLength ); + } else if ( file->tex.chunks.count() ) { + // Fill DDS Header + DDS_HEADER ddsHeader = {}; + DDS_HEADER_DXT10 dx10Header = {}; + + bool dx10 = false; + + ddsHeader.dwSize = sizeof( ddsHeader ); + ddsHeader.dwHeaderFlags = DDS_HEADER_FLAGS_TEXTURE | DDS_HEADER_FLAGS_LINEARSIZE | DDS_HEADER_FLAGS_MIPMAP; + ddsHeader.dwHeight = file->tex.header.height; + ddsHeader.dwWidth = file->tex.header.width; + ddsHeader.dwMipMapCount = file->tex.header.numMips; + ddsHeader.ddspf.dwSize = sizeof( DDS_PIXELFORMAT ); + ddsHeader.dwSurfaceFlags = DDS_SURFACE_FLAGS_TEXTURE | DDS_SURFACE_FLAGS_MIPMAP; + + if ( file->tex.header.unk16 == 2049 ) + ddsHeader.dwCubemapFlags = DDS_CUBEMAP_ALLFACES; + + bool supported = true; + + switch ( file->tex.header.format ) { + case DXGI_FORMAT_BC1_UNORM: + ddsHeader.ddspf.dwFlags = DDS_FOURCC; + ddsHeader.ddspf.dwFourCC = MAKEFOURCC( 'D', 'X', 'T', '1' ); + ddsHeader.dwPitchOrLinearSize = file->tex.header.width * file->tex.header.height / 2; // 4bpp + break; + + case DXGI_FORMAT_BC2_UNORM: + ddsHeader.ddspf.dwFlags = DDS_FOURCC; + ddsHeader.ddspf.dwFourCC = MAKEFOURCC( 'D', 'X', 'T', '3' ); + ddsHeader.dwPitchOrLinearSize = file->tex.header.width * file->tex.header.height; // 8bpp + break; + + case DXGI_FORMAT_BC3_UNORM: + ddsHeader.ddspf.dwFlags = DDS_FOURCC; + ddsHeader.ddspf.dwFourCC = MAKEFOURCC( 'D', 'X', 'T', '5' ); + ddsHeader.dwPitchOrLinearSize = file->tex.header.width * file->tex.header.height; // 8bpp + break; + + case DXGI_FORMAT_BC5_UNORM: + ddsHeader.ddspf.dwFlags = DDS_FOURCC; + ddsHeader.ddspf.dwFourCC = MAKEFOURCC( 'A', 'T', 'I', '2' ); + ddsHeader.dwPitchOrLinearSize = file->tex.header.width * file->tex.header.height; // 8bpp + break; + + case DXGI_FORMAT_BC7_UNORM: + ddsHeader.ddspf.dwFlags = DDS_FOURCC; + ddsHeader.ddspf.dwFourCC = MAKEFOURCC( 'D', 'X', '1', '0' ); + ddsHeader.dwPitchOrLinearSize = file->tex.header.width * file->tex.header.height; // 8bpp + + dx10 = true; + dx10Header.dxgiFormat = DXGI_FORMAT_BC7_UNORM; + break; + + case DXGI_FORMAT_B8G8R8A8_UNORM: + ddsHeader.ddspf.dwFlags = DDS_RGBA; + ddsHeader.ddspf.dwRGBBitCount = 32; + ddsHeader.ddspf.dwRBitMask = 0x00FF0000; + ddsHeader.ddspf.dwGBitMask = 0x0000FF00; + ddsHeader.ddspf.dwBBitMask = 0x000000FF; + ddsHeader.ddspf.dwABitMask = 0xFF000000; + ddsHeader.dwPitchOrLinearSize = file->tex.header.width * file->tex.header.height * 4; // 32bpp + break; + + case DXGI_FORMAT_R8_UNORM: + ddsHeader.ddspf.dwFlags = DDS_RGB; + ddsHeader.ddspf.dwRGBBitCount = 8; + ddsHeader.ddspf.dwRBitMask = 0xFF; + ddsHeader.dwPitchOrLinearSize = file->tex.header.width * file->tex.header.height; // 8bpp + break; + + default: + supported = false; + break; + } + + if ( !supported ) + return false; + + char dds[sizeof( ddsHeader )]; + memcpy( dds, &ddsHeader, sizeof( ddsHeader ) ); + + int texSize = 0; // = file->unpackedLength; + int hdrSize = sizeof( ddsHeader ) + 4; + + content.clear(); + content.append( QByteArray::fromStdString( "DDS " ) ); + content.append( QByteArray::fromRawData( dds, sizeof( ddsHeader ) ) ); + Q_ASSERT( content.size() == hdrSize ); + + if ( dx10 ) { + dx10Header.resourceDimension = DDS_DIMENSION_TEXTURE2D; + dx10Header.miscFlag = 0; + dx10Header.arraySize = 1; + dx10Header.miscFlags2 = 0; + + char dds2[sizeof( dx10Header )]; + memcpy( dds2, &dx10Header, sizeof( dx10Header ) ); + content.append( QByteArray::fromRawData( dds2, sizeof( dx10Header ) ) ); + } + + // Start at 1st chunk now + for ( int i = 0; i < file->tex.chunks.count(); i++ ) { + F4TexChunk chunk = file->tex.chunks[i]; + if ( bsa.seek( chunk.offset ) ) { + QByteArray chunkData; + + if ( chunk.packedSize > 0 ) { + chunkData.resize( chunk.packedSize ); + if ( bsa.read( chunkData.data(), chunk.packedSize ) == chunk.packedSize ) { + chunkData = gUncompress( chunkData, chunk.packedSize ); + + if ( chunkData.size() != chunk.unpackedSize ) + qCritical() << "Size does not match at " << chunk.offset; + } + } else if ( !(bsa.read( chunkData.data(), chunk.unpackedSize ) == chunk.unpackedSize) ) { + qCritical() << "Size does not match at " << chunk.offset; + } + texSize += chunk.unpackedSize; + + content.append( chunkData ); + //Q_ASSERT( content.size() - hdrSize == texSize ); + } else { + qCritical() << "Seek error"; + } + } + } + return true; } } @@ -437,7 +660,7 @@ bool BSA::fileContents( const QString & fn, QByteArray & content ) } // see bsa.h -QString BSA::absoluteFilePath( const QString & fn ) const +QString BSA::getAbsoluteFilePath( const QString & fn ) const { if ( hasFile(fn) ) { return QFileInfo(this->bsaPath, fn).absoluteFilePath(); @@ -449,28 +672,23 @@ QString BSA::absoluteFilePath( const QString & fn ) const BSA::BSAFolder * BSA::insertFolder( QString name ) { if ( name.isEmpty() ) - return & root; + return root; name = name.replace( "\\", "/" ).toLower(); BSAFolder * folder = folders.value( name ); - if ( ! folder ) - { - // qDebug() << "inserting" << name; - + if ( !folder ) { folder = new BSAFolder; + folder->name = name; folders.insert( name, folder ); int p = name.lastIndexOf( "/" ); - if ( p >= 0 ) - { + if ( p >= 0 ) { folder->parent = insertFolder( name.left( p ) ); folder->parent->children.insert( name.right( name.length() - p - 1 ), folder ); - } - else - { - folder->parent = & root; - root.children.insert( name, folder ); + } else { + folder->parent = root; + root->children.insert( name, folder ); } } @@ -480,13 +698,25 @@ BSA::BSAFolder * BSA::insertFolder( QString name ) // see bsa.h BSA::BSAFile * BSA::insertFile( BSAFolder * folder, QString name, quint32 sizeFlags, quint32 offset ) { - name = name.toLower(); - BSAFile * file = new BSAFile; file->sizeFlags = sizeFlags; file->offset = offset; - folder->files.insert( name, file ); + + files.insert( QString( folder->name % "/" % name ).toLower(), file ); + return file; +} + +BSA::BSAFile * BSA::insertFile( BSAFolder * folder, QString name, quint32 packed, quint32 unpacked, quint64 offset, F4Tex dds ) +{ + BSAFile * file = new BSAFile; + file->tex = dds; + file->packedLength = packed; + file->unpackedLength = unpacked; + file->offset = offset; + folder->files.insert( name, file ); + + files.insert( QString( folder->name % "/" % name ).toLower(), file ); return file; } @@ -494,7 +724,7 @@ BSA::BSAFile * BSA::insertFile( BSAFolder * folder, QString name, quint32 sizeFl const BSA::BSAFolder * BSA::getFolder( QString fn ) const { if ( fn.isEmpty() ) - return & root; + return root; else return folders.value( fn ); } @@ -502,24 +732,7 @@ const BSA::BSAFolder * BSA::getFolder( QString fn ) const // see bsa.h const BSA::BSAFile * BSA::getFile( QString fn ) const { - QString folderName; - QString fileName = fn; - int p = fn.lastIndexOf( "/" ); - if ( p >= 0 ) - { - folderName = fn.left( p ); - fileName = fn.right( fn.length() - p - 1 ); - } - - // TODO: Multiple matches occur and user has no say which version gets loaded - // When it comes to the AUTO feature, should give preference to certain BSAs - // or take the newest and or highest quality version. - if ( const BSAFolder * folder = getFolder( folderName ) ) { - return folder->files.value( fileName ); - } - else { - return 0; - } + return files.value( fn.toLower() ); } // see bsa.h @@ -551,3 +764,145 @@ QDateTime BSA::fileTime( const QString & ) const { return bsaInfo.created( ); } + +bool BSA::scan( const BSA::BSAFolder * folder, QStandardItem * item, QString path ) +{ + if ( !folder || folder->children.count() == 0 ) + return false; + + QHash::const_iterator i; + for ( i = folder->children.begin(); i != folder->children.end(); i++ ) { + + if ( !i.value()->files.count() && !i.value()->children.count() ) + continue; + + auto folderItem = new QStandardItem( i.key() ); + auto pathDummy = new QStandardItem( "" ); + auto sizeDummy = new QStandardItem( "" ); + + item->appendRow( { folderItem, pathDummy, sizeDummy } ); + + // Recurse through folders + if ( i.value()->children.count() ) { + QString fullpath = ((path.isEmpty()) ? path : path % "/") % i.key(); + scan( i.value(), folderItem, fullpath ); + } + + // List files + if ( i.value()->files.count() ) { + QHash::const_iterator f; + for ( f = i.value()->files.begin(); f != i.value()->files.end(); f++ ) { + QString fullpath = path % "/" % i.key() % "/" % f.key(); + + int bytes = f.value()->size(); + QString filesize = (bytes > 1024) ? QString::number( bytes / 1024 ) + "KB" : QString::number( bytes ) + "B"; + + auto fileItem = new QStandardItem( f.key() ); + auto pathItem = new QStandardItem( fullpath ); + auto sizeItem = new QStandardItem( filesize ); + + folderItem->appendRow( { fileItem, pathItem, sizeItem } ); + } + } + } + + return true; +} + +bool BSA::fillModel( BSAModel * bsaModel, const QString & folder ) +{ + return scan( getFolder( folder ), bsaModel->invisibleRootItem(), folder ); +} + + +BSAModel::BSAModel( QObject * parent ) + : QStandardItemModel( parent ) +{ + +} + +void BSAModel::init() +{ + setColumnCount( 3 ); + setHorizontalHeaderLabels( { "File", "Path", "Size" } ); +} + +Qt::ItemFlags BSAModel::flags( const QModelIndex & index ) const +{ + return QStandardItemModel::flags( index ) ^ Qt::ItemIsEditable; +} + + +BSAProxyModel::BSAProxyModel( QObject * parent ) + : QSortFilterProxyModel( parent ) +{ + +} + +void BSAProxyModel::setFiletypes( QStringList types ) +{ + filetypes = types; +} + +void BSAProxyModel::setFilterByNameOnly( bool nameOnly ) +{ + filterByNameOnly = nameOnly; + + setFilterRegExp( filterRegExp() ); +} + +void BSAProxyModel::resetFilter() +{ + setFilterRegExp( QRegExp( "*", Qt::CaseInsensitive, QRegExp::Wildcard ) ); +} + +bool BSAProxyModel::filterAcceptsRow( int sourceRow, const QModelIndex & sourceParent ) const +{ + if ( !filterRegExp().isEmpty() ) { + + QModelIndex sourceIndex0 = sourceModel()->index( sourceRow, 0, sourceParent ); + QModelIndex sourceIndex1 = sourceModel()->index( sourceRow, 1, sourceParent ); + if ( sourceIndex0.isValid() ) { + // If children match, parent matches + int c = sourceModel()->rowCount( sourceIndex0 ); + for ( int i = 0; i < c; ++i ) { + if ( filterAcceptsRow( i, sourceIndex0 ) ) + return true; + } + + QString key0 = sourceModel()->data( sourceIndex0, filterRole() ).toString(); + QString key1 = sourceModel()->data( sourceIndex1, filterRole() ).toString(); + + bool typeMatch = true; + if ( filetypes.count() ) { + typeMatch = false; + for ( auto f : filetypes ) { + typeMatch |= key1.endsWith( f ); + } + } + + bool stringMatch = (filterByNameOnly) ? key0.contains( filterRegExp() ) : key1.contains( filterRegExp() ); + + return typeMatch && stringMatch; + } + } + + return QSortFilterProxyModel::filterAcceptsRow( sourceRow, sourceParent ); +} + +bool BSAProxyModel::lessThan( const QModelIndex & left, const QModelIndex & right ) const +{ + QString leftString = sourceModel()->data( left ).toString(); + QString rightString = sourceModel()->data( right ).toString(); + + QModelIndex leftChild = left.child( 0, 0 ); + QModelIndex rightChild = right.child( 0, 0 ); + + if ( !leftChild.isValid() && rightChild.isValid() ) + return false; + + if ( leftChild.isValid() && !rightChild.isValid() ) + return true; + + return leftString < rightString; +} diff --git a/lib/fsengine/bsa.h b/lib/fsengine/bsa.h index ea73b9079..7f220263f 100644 --- a/lib/fsengine/bsa.h +++ b/lib/fsengine/bsa.h @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -35,12 +35,194 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "fsengine.h" +#include +#include + +#include #include #include #include #include #include +#include + +using namespace std; + + +/* Default header data */ +#define MW_BSAHEADER_FILEID 0x00000100 //!< Magic for Morrowind BSA +#define OB_BSAHEADER_FILEID 0x00415342 //!< Magic for Oblivion BSA, the literal string "BSA\0". +#define F4_BSAHEADER_FILEID 0x58445442 //!< Magic for Fallout 4 BA2, the literal string "BTDX". +#define OB_BSAHEADER_VERSION 0x67 //!< Version number of an Oblivion BSA +#define F3_BSAHEADER_VERSION 0x68 //!< Version number of a Fallout 3 BSA +#define SSE_BSAHEADER_VERSION 0x69 //!< Version number of a Skyrim SE BSA +#define F4_BSAHEADER_VERSION 0x01 //!< Version number of a Fallout 4 BA2 + +/* Archive flags */ +#define OB_BSAARCHIVE_PATHNAMES 0x0001 //!< Whether the BSA has names for paths +#define OB_BSAARCHIVE_FILENAMES 0x0002 //!< Whether the BSA has names for files +#define OB_BSAARCHIVE_COMPRESSFILES 0x0004 //!< Whether the files are compressed +#define F3_BSAARCHIVE_PREFIXFULLFILENAMES 0x0100 //!< Whether the name is prefixed to the data? + +/* File flags */ +#define OB_BSAFILE_NIF 0x0001 //!< Set when the BSA contains NIF files +#define OB_BSAFILE_DDS 0x0002 //!< Set when the BSA contains DDS files +#define OB_BSAFILE_XML 0x0004 //!< Set when the BSA contains XML files +#define OB_BSAFILE_WAV 0x0008 //!< Set when the BSA contains WAV files +#define OB_BSAFILE_MP3 0x0010 //!< Set when the BSA contains MP3 files +#define OB_BSAFILE_TXT 0x0020 //!< Set when the BSA contains TXT files +#define OB_BSAFILE_HTML 0x0020 //!< Set when the BSA contains HTML files +#define OB_BSAFILE_BAT 0x0020 //!< Set when the BSA contains BAT files +#define OB_BSAFILE_SCC 0x0020 //!< Set when the BSA contains SCC files +#define OB_BSAFILE_SPT 0x0040 //!< Set when the BSA contains SPT files +#define OB_BSAFILE_TEX 0x0080 //!< Set when the BSA contains TEX files +#define OB_BSAFILE_FNT 0x0080 //!< Set when the BSA contains FNT files +#define OB_BSAFILE_CTL 0x0100 //!< Set when the BSA contains CTL files + +/* Bitmasks for the size field in the header */ +#define OB_BSAFILE_SIZEMASK 0x3fffffff //!< Bit mask with OBBSAFileInfo::sizeFlags to get the size of the file + +/* Record flags */ +#define OB_BSAFILE_FLAG_COMPRESS 0xC0000000 //!< Bit mask with OBBSAFileInfo::sizeFlags to get the compression status + +//! \file bsa.cpp OBBSAHeader / \link OBBSAFileInfo FileInfo\endlink / \link OBBSAFolderInfo FolderInfo\endlink; MWBSAHeader, MWBSAFileSizeOffset + +//! The header of an Oblivion BSA. +/*! +* Follows OB_BSAHEADER_FILEID and OB_BSAHEADER_VERSION. +*/ +struct OBBSAHeader +{ + quint32 FolderRecordOffset; //!< Offset of beginning of folder records + quint32 ArchiveFlags; //!< Archive flags + quint32 FolderCount; //!< Total number of folder records (OBBSAFolderInfo) + quint32 FileCount; //!< Total number of file records (OBBSAFileInfo) + quint32 FolderNameLength; //!< Total length of folder names + quint32 FileNameLength; //!< Total length of file names + quint32 FileFlags; //!< File flags + + friend QDebug operator<<(QDebug dbg, const OBBSAHeader & head) + { + return dbg << "BSAHeader:" + << "\n folder offset" << head.FolderRecordOffset + << "\n archive flags" << head.ArchiveFlags + << "\n folder Count" << head.FolderCount + << "\n file Count" << head.FileCount + << "\n folder name length" << head.FolderNameLength + << "\n file name length" << head.FileNameLength + << "\n file flags" << head.FileFlags; + } + +}; + +//! Info for a file inside an Oblivion BSA +struct OBBSAFileInfo +{ + quint64 hash; //!< Hash of the filename + quint32 sizeFlags; //!< Size of the data, possibly with OB_BSAFILE_FLAG_COMPRESS set + quint32 offset; //!< Offset to raw file data +}; + +//! Shared info format for a folder inside a BSA +struct BSAFolderInfo +{ + quint64 hash; //!< Hash of the folder name + quint32 fileCount; //!< Number of files in folder + quint32 unk; + quint64 offset; //!< Offset to name of this folder +}; + +//! Info for a folder inside an Oblivion BSA +struct OBBSAFolderInfo +{ + quint64 hash; //!< Hash of the folder name + quint32 fileCount; //!< Number of files in folder + quint32 offset; //!< Offset to name of this folder +}; + +//! Info for a folder inside an Skyrim SE BSA +struct SEBSAFolderInfo +{ + quint64 hash; //!< Hash of the folder name + quint32 fileCount; //!< Number of files in folder + quint32 unk; + quint64 offset; //!< Offset to name of this folder +}; + +//! The header of a Morrowind BSA +struct MWBSAHeader +{ + quint32 HashOffset; //!< Offset of hash table minus header size (12) + quint32 FileCount; //!< Number of files in the archive +}; + +//! The file size and offset of an entry in a Morrowind BSA +struct MWBSAFileSizeOffset +{ + quint32 size; //!< The size of the file + quint32 offset; //!< The offset of the file +}; + +#pragma pack(push, 4) +struct F4BSAHeader +{ + char type[4]; // 08 GNRL=General, DX10=Textures + quint32 numFiles; // 0C + quint64 nameTableOffset; // 10 - relative to start of file +}; + +// 24 +struct F4GeneralInfo +{ + quint32 nameHash; // 00 + char ext[4]; // 04 - extension + quint32 dirHash; // 08 + quint32 unk0C; // 0C - flags? 00100100 + quint64 offset; // 10 - relative to start of file + quint32 packedSize; // 18 - packed length (zlib) + quint32 unpackedSize; // 1C - unpacked length + quint32 unk20; // 20 - BAADF00D +}; +#pragma pack(pop) + +// 18 +struct F4TexInfo +{ + quint32 nameHash; // 00 + char ext[4]; // 04 + quint32 dirHash; // 08 + quint8 unk0C; // 0C + quint8 numChunks; // 0D + quint16 chunkHeaderSize;// 0E - size of one chunk header + quint16 height; // 10 + quint16 width; // 12 + quint8 numMips; // 14 + quint8 format; // 15 - DXGI_FORMAT + quint16 unk16; // 16 - 0800 +}; + +// 18 +struct F4TexChunk +{ + quint64 offset; // 00 + quint32 packedSize; // 08 + quint32 unpackedSize; // 0C + quint16 startMip; // 10 + quint16 endMip; // 12 + quint32 unk14; // 14 - BAADFOOD +}; + +struct F4Tex +{ + F4TexInfo header; + QVector chunks; +}; + + +class BSAModel; +class BSAProxyModel; + //! \file bsa.h BSA file, BSAIterator //! A Bethesda Software Archive file @@ -93,7 +275,7 @@ class BSA final : public FSArchiveFile //! See QFileInfo::created(). QDateTime fileTime( const QString & ) const override final; //! See QFileInfo::absoluteFilePath(). - QString absoluteFilePath( const QString & ) const override final; + QString getAbsoluteFilePath( const QString & ) const override final; //! Whether the given file can be opened as a %BSA or not static bool canOpen( const QString & ); @@ -101,17 +283,25 @@ class BSA final : public FSArchiveFile //! Returns BSA::status. QString statusText() const { return status; } -protected: //! A file inside a BSA struct BSAFile { - quint32 sizeFlags; //!< The size of the file in the BSA - quint32 offset; //!< The offset of the file in the BSA - + // Skyrim and earlier + quint32 sizeFlags = 0; //!< The size of the file in the BSA + + // Fallout 4 + quint32 packedLength = 0; + quint32 unpackedLength = 0; + + quint64 offset = 0; //!< The offset of the file in the BSA + //! The size of the file inside the BSA quint32 size() const; + //! Whether the file is compressed inside the BSA bool compressed() const; + + F4Tex tex; }; //! A folder inside a BSA @@ -121,7 +311,8 @@ class BSA final : public FSArchiveFile BSAFolder() : parent( 0 ) {} //! Destructor ~BSAFolder() { qDeleteAll( children ); qDeleteAll( files ); } - + + QString name; BSAFolder * parent; //!< The parent item QHash children; //!< A map of child folders QHash files; //!< A map of files inside the folder @@ -131,17 +322,26 @@ class BSA final : public FSArchiveFile BSAFolder * insertFolder( QString name ); //! Inserts a file into the structure of a %BSA BSAFile * insertFile( BSAFolder * folder, QString name, quint32 sizeFlags, quint32 offset ); + + BSAFile * insertFile( BSAFolder * folder, QString name, quint32 packed, quint32 unpacked, quint64 offset, F4Tex dds = F4Tex() ); //! Gets the specified folder, or the root folder if not found const BSAFolder * getFolder( QString fn ) const; //! Gets the specified file, or null if not found const BSAFile * getFile( QString fn ) const; + + bool scan( const BSA::BSAFolder *, QStandardItem *, QString ); + bool fillModel( BSAModel *, const QString & ); + +protected: //! The %BSA file QFile bsa; //! File info for the %BSA QFileInfo bsaInfo; + quint32 version; + //! Mutual exclusion handler QMutex bsaMutex; @@ -153,17 +353,60 @@ class BSA final : public FSArchiveFile QString bsaName; //! Map of folders inside a %BSA - QHash folders; + QHash folders; //! The root folder - BSAFolder root; + BSAFolder * root; + + //! Map of files inside a %BSA + QHash files; //! Error string for exception handling QString status; + + qint64 numFiles = 0; + int filesScanned = 0; //! Whether the %BSA is compressed - bool compressToggle; + bool compressToggle = false; //! Whether Fallout 3 names are prefixed with an extra string - bool namePrefix; + bool namePrefix = false; +}; + + +class BSAModel : public QStandardItemModel +{ + Q_OBJECT + +public: + BSAModel( QObject * parent = nullptr ); + + void init(); + + Qt::ItemFlags flags( const QModelIndex & index ) const override; +}; + + +class BSAProxyModel : public QSortFilterProxyModel +{ + Q_OBJECT + +public: + BSAProxyModel( QObject * parent = nullptr ); + + void setFiletypes( QStringList types ); + + void resetFilter(); + +public slots: + void setFilterByNameOnly( bool nameOnly ); + +protected: + bool filterAcceptsRow( int sourceRow, const QModelIndex & sourceParent ) const; + bool lessThan( const QModelIndex & left, const QModelIndex & right ) const; + +private: + QStringList filetypes; + bool filterByNameOnly = false; }; #endif diff --git a/lib/fsengine/fsengine.cpp b/lib/fsengine/fsengine.cpp index 861217e79..e8d7514e7 100644 --- a/lib/fsengine/fsengine.cpp +++ b/lib/fsengine/fsengine.cpp @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -39,22 +39,23 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include + //! \file fsengine.cpp File system engine implementations // see fsengine.h -FSArchiveHandler * FSArchiveHandler::openArchive( const QString & fn ) +std::shared_ptr FSArchiveHandler::openArchive( const QString & fn ) { if ( BSA::canOpen( fn ) ) { BSA * bsa = new BSA( fn ); if ( bsa->open() ) { //qDebug() << "BSA Open: " << fn; - return new FSArchiveHandler( bsa ); + return std::shared_ptr( new FSArchiveHandler( bsa ) ); } qDebug() << "fsengine error:" << fn << ":" << bsa->statusText(); delete bsa; } - return 0; + return nullptr; } // see fsengine.h diff --git a/lib/fsengine/fsengine.h b/lib/fsengine/fsengine.h index 303886d1c..909931340 100644 --- a/lib/fsengine/fsengine.h +++ b/lib/fsengine/fsengine.h @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -39,13 +39,14 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include +#include //! Provides a way to register an FSArchiveEngine with the application. class FSArchiveHandler { public: //! Opens a BSA for the specified file - static FSArchiveHandler * openArchive( const QString & ); + static std::shared_ptr openArchive( const QString & ); public: //! Constructor @@ -53,12 +54,18 @@ class FSArchiveHandler //! Destructor ~FSArchiveHandler(); + template T getArchive() const; FSArchiveFile * getArchive() { return archive; } protected: class FSArchiveFile * archive; }; +template inline T FSArchiveHandler::getArchive() const +{ + return static_cast(archive); +} + //! A file system archive class FSArchiveFile @@ -79,7 +86,7 @@ class FSArchiveFile virtual bool hasFile( const QString & ) const = 0; virtual qint64 fileSize( const QString & ) const = 0; virtual bool fileContents( const QString &, QByteArray & ) = 0; - virtual QString absoluteFilePath( const QString & ) const = 0; + virtual QString getAbsoluteFilePath( const QString & ) const = 0; virtual uint ownerId( const QString & ) const = 0; virtual QString owner( const QString & ) const = 0; diff --git a/lib/fsengine/fsmanager.cpp b/lib/fsengine/fsmanager.cpp index 38302597a..caec92afe 100644 --- a/lib/fsengine/fsmanager.cpp +++ b/lib/fsengine/fsmanager.cpp @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -43,23 +43,30 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -#include //! Global BSA file manager -static FSManager *theFSManager = NULL; +static FSManager * theFSManager = nullptr; // see fsmanager.h FSManager* FSManager::get() { - if (theFSManager == NULL) + if (!theFSManager) theFSManager = new FSManager(); return theFSManager; } +void FSManager::del() +{ + if ( theFSManager ) { + delete theFSManager; + theFSManager = nullptr; + } +} + // see fsmanager.h QList FSManager::archiveList() { QList archives; - for ( FSArchiveHandler* an : get()->archives.values() ) { + for ( std::shared_ptr an : get()->archives.values() ) { archives.append( an->getArchive() ); } return archives; @@ -69,26 +76,24 @@ QList FSManager::archiveList() FSManager::FSManager( QObject * parent ) : QObject( parent ), automatic( false ) { - QSettings cfg; - QStringList list = cfg.value( "FSEngine/Archives", QStringList() ).toStringList(); - - if ( list.size() == 1 && list.first() == "AUTO" ) - { - automatic = true; - list = autodetectArchives(); - } - - for ( const QString an : list ) - { - if ( FSArchiveHandler * a = FSArchiveHandler::openArchive( an ) ) - archives.insert( an, a ); - } + initialize(); } // see fsmanager.h FSManager::~FSManager() { - qDeleteAll( archives ); + archives.clear(); +} + +void FSManager::initialize() +{ + QSettings cfg; + QStringList list = cfg.value( "Settings/Resources/Archives", QStringList() ).toStringList(); + + for ( const QString an : list ) { + if ( auto a = FSArchiveHandler::openArchive( an ) ) + archives.insert( an, a ); + } } // see fsmanager.h @@ -103,15 +108,15 @@ QStringList FSManager::regPathBSAList( QString regKey, QString dataDir ) dataPath += "/"; dataPath += dataDir; QDir fs( dataPath ); - for ( const QString& fn : fs.entryList( { "*.bsa" }, QDir::Files ) ) + for ( const QString& fn : fs.entryList( { "*.bsa", "*.ba2" }, QDir::Files ) ) { - list << dataPath + QDir::separator() + fn; + list << QDir::fromNativeSeparators(dataPath + QDir::separator() + fn); } } return list; } -QStringList FSManager::autodetectArchives() +QStringList FSManager::autodetectArchives( const QString & folder ) { QStringList list; @@ -121,117 +126,29 @@ QStringList FSManager::autodetectArchives() list << regPathBSAList( "HKEY_LOCAL_MACHINE\\SOFTWARE\\Bethesda Softworks\\Fallout3", "Data" ); list << regPathBSAList( "HKEY_LOCAL_MACHINE\\SOFTWARE\\Bethesda Softworks\\FalloutNV", "Data" ); list << regPathBSAList( "HKEY_LOCAL_MACHINE\\SOFTWARE\\Bethesda Softworks\\Skyrim", "Data" ); + list << regPathBSAList( "HKEY_LOCAL_MACHINE\\SOFTWARE\\Bethesda Softworks\\Fallout4", "Data" ); + list << regPathBSAList( "HKEY_LOCAL_MACHINE\\SOFTWARE\\Bethesda Softworks\\Skyrim Special Edition", "Data" ); #endif - - return list; -} - -// see fsmanager.h -void FSManager::selectArchives() -{ - FSSelector select( this ); - select.exec(); -} - -// see fsmanager.h -FSSelector::FSSelector( FSManager * m ) - : QDialog(), manager( m ) -{ - model = new QStringListModel( this ); - model->setStringList( manager->archives.keys() ); - - view = new QListView( this ); - view->setModel( model ); - view->setEditTriggers( QListView::NoEditTriggers ); - - chkAuto = new QCheckBox( this ); - chkAuto->setText( "Automatic Selection" ); - chkAuto->setChecked( manager->automatic ); - connect( chkAuto, SIGNAL( toggled( bool ) ), this, SLOT( sltAuto( bool ) ) ); - - btAdd = new QPushButton( "Add", this ); - btAdd->setDisabled( manager->automatic ); - connect( btAdd, SIGNAL( clicked() ), this, SLOT( sltAdd() ) ); - - btDel = new QPushButton( "Remove", this ); - btDel->setDisabled( manager->automatic ); - connect( btDel, SIGNAL( clicked() ), this, SLOT( sltDel() ) ); - - btDelAll = new QPushButton( "Remove All", this ); - btDelAll->setDisabled( manager->automatic ); - connect( btDelAll, SIGNAL( clicked() ), this, SLOT( sltDelAll() ) ); - - QGridLayout * grid = new QGridLayout( this ); - grid->addWidget( chkAuto, 0, 0, 1, 2 ); - grid->addWidget( view, 1, 0, 1, 2 ); - grid->addWidget( btAdd, 2, 0, 1, 1 ); - grid->addWidget( btDel, 2, 1, 1, 1 ); - grid->addWidget( btDelAll, 2, 2, 1, 1 ); -} - -FSSelector::~FSSelector() -{ - QSettings cfg; - QStringList list( manager->automatic ? QStringList() << "AUTO" : manager->archives.keys() ); - cfg.setValue( "FSEngine/Archives", list ); - emit Options::get()->sigFlush3D(); -} - -void FSSelector::sltAuto( bool x ) -{ - if ( x ) - { - qDeleteAll( manager->archives ); - manager->archives.clear(); - - for ( const QString an : manager->autodetectArchives() ) - { - if ( FSArchiveHandler * a = FSArchiveHandler::openArchive( an ) ) - { - manager->archives.insert( an, a ); + if ( !folder.isEmpty() ) { + QStringList listCopy; + // Looking for a specific folder here + // Remove the BSAs that do not contain this folder + for ( auto f : list ) { + auto handler = FSArchiveHandler::openArchive( f ); + if ( handler ) { + auto bsa = handler->getArchive(); + if ( bsa ) { + auto rootFolder = bsa->getFolder( "" ); + if ( rootFolder->children.contains( folder ) ) { + listCopy.append( f ); + } + } } } - - model->setStringList( manager->archives.keys() ); - } - - manager->automatic = x; - - btAdd->setDisabled( x ); - btDel->setDisabled( x ); - btDelAll->setDisabled( x ); -} -void FSSelector::sltAdd() -{ - QStringList list = QFileDialog::getOpenFileNames( this, "Select resource files to add", QString(), "BSA (*.bsa)" ); - - for ( const QString an : list ) - { - if ( ! manager->archives.contains( an ) ) - if ( FSArchiveHandler * a = FSArchiveHandler::openArchive( an ) ) - manager->archives.insert( an, a ); + list = listCopy; } - - model->setStringList( manager->archives.keys() ); -} -void FSSelector::sltDel() -{ - QString an = view->currentIndex().data( Qt::DisplayRole ).toString(); - if ( FSArchiveHandler * a = manager->archives.take( an ) ) - { - delete a; - } - - model->setStringList( manager->archives.keys() ); -} - -void FSSelector::sltDelAll() -{ - qDeleteAll( manager->archives ); - manager->archives.clear(); - - model->setStringList( QStringList() ); + return list; } diff --git a/lib/fsengine/fsmanager.h b/lib/fsengine/fsmanager.h index 7a88f86e6..5b435fb3b 100644 --- a/lib/fsengine/fsmanager.h +++ b/lib/fsengine/fsmanager.h @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -39,6 +39,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +#include class FSArchiveHandler; class FSArchiveFile; @@ -50,59 +51,33 @@ class FSManager : public QObject public: //! Gets the global file system manager static FSManager * get(); + + //! Deletes the manager + static void del(); + //! Gets the list of globally registered BSA files static QList archiveList(); protected: //! Constructor - FSManager( QObject * parent = NULL ); + FSManager( QObject * parent = nullptr ); //! Destructor ~FSManager(); - -public slots: - //! Launches a FSSelector dialog - void selectArchives(); protected: - QMap archives; + QMap > archives; bool automatic; //! Builds a list of global BSAs on Windows platforms - static QStringList autodetectArchives(); + static QStringList autodetectArchives( const QString & folder = "" ); //! Helper function to build a list of BSAs static QStringList regPathBSAList( QString regKey, QString dataDir ); - - friend class FSSelector; -}; -//! Interface dialog for FSManager -class FSSelector : public QDialog -{ - Q_OBJECT -public: - //! Constructor - FSSelector( FSManager * m ); - //! Destructor - ~FSSelector(); + void initialize(); -protected slots: - void sltAuto( bool ); - void sltAdd(); - void sltDel(); - void sltDelAll(); - -protected: - FSManager * manager; - - class QStringListModel * model; - class QListView * view; - - class QCheckBox * chkAuto; - class QPushButton * btAdd; - class QPushButton * btDel; - class QPushButton * btDelAll; + friend class NifSkope; + friend class SettingsResources; }; - #endif diff --git a/lib/half.cpp b/lib/half.cpp new file mode 100644 index 000000000..ea00a5270 --- /dev/null +++ b/lib/half.cpp @@ -0,0 +1,719 @@ +// Branch-free implementation of half-precision (16 bit) floating point +// Copyright 2006 Mike Acton +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE +// +// Half-precision floating point format +// ------------------------------------ +// +// | Field | Last | First | Note +// |----------|------|-------|---------- +// | Sign | 15 | 15 | +// | Exponent | 14 | 10 | Bias = 15 +// | Mantissa | 9 | 0 | +// +// Compiling +// --------- +// +// Preferred compile flags for GCC: +// -O3 -fstrict-aliasing -std=c99 -pedantic -Wall -Wstrict-aliasing +// +// This file is a C99 source file, intended to be compiled with a C99 +// compliant compiler. However, for the moment it remains combatible +// with C++98. Therefore if you are using a compiler that poorly implements +// C standards (e.g. MSVC), it may be compiled as C++. This is not +// guaranteed for future versions. +// + +#include "half.h" + +// Load immediate +static inline uint32_t _uint32_li( uint32_t a ) +{ + return (a); +} + +// Decrement +static inline uint32_t _uint32_dec( uint32_t a ) +{ + return (a - 1); +} + +// Increment +static inline uint32_t _uint32_inc( uint32_t a ) +{ + return (a + 1); +} + +// Complement +static inline uint32_t _uint32_not( uint32_t a ) +{ + return (~a); +} + +// Negate +static inline uint32_t _uint32_neg( uint32_t a ) +{ + return (-a); +} + +// Extend sign +static inline uint32_t _uint32_ext( uint32_t a ) +{ + return (((int32_t)a)>>31); +} + +// And +static inline uint32_t _uint32_and( uint32_t a, uint32_t b ) +{ + return (a & b); +} + +// Exclusive Or +static inline uint32_t _uint32_xor( uint32_t a, uint32_t b ) +{ + return (a ^ b); +} + +// And with Complement +static inline uint32_t _uint32_andc( uint32_t a, uint32_t b ) +{ + return (a & ~b); +} + +// Or +static inline uint32_t _uint32_or( uint32_t a, uint32_t b ) +{ + return (a | b); +} + +// Shift Right Logical +static inline uint32_t _uint32_srl( uint32_t a, int sa ) +{ + return (a >> sa); +} + +// Shift Left Logical +static inline uint32_t _uint32_sll( uint32_t a, int sa ) +{ + return (a << sa); +} + +// Add +static inline uint32_t _uint32_add( uint32_t a, uint32_t b ) +{ + return (a + b); +} + +// Subtract +static inline uint32_t _uint32_sub( uint32_t a, uint32_t b ) +{ + return (a - b); +} + +// Multiply +static inline uint32_t _uint32_mul( uint32_t a, uint32_t b ) +{ + return (a * b); +} + +// Select on Sign bit +static inline uint32_t _uint32_sels( uint32_t test, uint32_t a, uint32_t b ) +{ + const uint32_t mask = _uint32_ext( test ); + const uint32_t sel_a = _uint32_and( a, mask ); + const uint32_t sel_b = _uint32_andc( b, mask ); + const uint32_t result = _uint32_or( sel_a, sel_b ); + + return (result); +} + +// Select Bits on mask +static inline uint32_t _uint32_selb( uint32_t mask, uint32_t a, uint32_t b ) +{ + const uint32_t sel_a = _uint32_and( a, mask ); + const uint32_t sel_b = _uint32_andc( b, mask ); + const uint32_t result = _uint32_or( sel_a, sel_b ); + + return (result); +} + +// Load Immediate +static inline uint16_t _uint16_li( uint16_t a ) +{ + return (a); +} + +// Extend sign +static inline uint16_t _uint16_ext( uint16_t a ) +{ + return (((int16_t)a)>>15); +} + +// Negate +static inline uint16_t _uint16_neg( uint16_t a ) +{ + return (-a); +} + +// Complement +static inline uint16_t _uint16_not( uint16_t a ) +{ + return (~a); +} + +// Decrement +static inline uint16_t _uint16_dec( uint16_t a ) +{ + return (a - 1); +} + +// Shift Left Logical +static inline uint16_t _uint16_sll( uint16_t a, int sa ) +{ + return (a << sa); +} + +// Shift Right Logical +static inline uint16_t _uint16_srl( uint16_t a, int sa ) +{ + return (a >> sa); +} + +// Add +static inline uint16_t _uint16_add( uint16_t a, uint16_t b ) +{ + return (a + b); +} + +// Subtract +static inline uint16_t _uint16_sub( uint16_t a, uint16_t b ) +{ + return (a - b); +} + +// And +static inline uint16_t _uint16_and( uint16_t a, uint16_t b ) +{ + return (a & b); +} + +// Or +static inline uint16_t _uint16_or( uint16_t a, uint16_t b ) +{ + return (a | b); +} + +// Exclusive Or +static inline uint16_t _uint16_xor( uint16_t a, uint16_t b ) +{ + return (a ^ b); +} + +// And with Complement +static inline uint16_t _uint16_andc( uint16_t a, uint16_t b ) +{ + return (a & ~b); +} + +// And then Shift Right Logical +static inline uint16_t _uint16_andsrl( uint16_t a, uint16_t b, int sa ) +{ + return ((a & b) >> sa); +} + +// Shift Right Logical then Mask +static inline uint16_t _uint16_srlm( uint16_t a, int sa, uint16_t mask ) +{ + return ((a >> sa) & mask); +} + +// Add then Mask +static inline uint16_t _uint16_addm( uint16_t a, uint16_t b, uint16_t mask ) +{ + return ((a + b) & mask); +} + + +// Select on Sign bit +static inline uint16_t _uint16_sels( uint16_t test, uint16_t a, uint16_t b ) +{ + const uint16_t mask = _uint16_ext( test ); + const uint16_t sel_a = _uint16_and( a, mask ); + const uint16_t sel_b = _uint16_andc( b, mask ); + const uint16_t result = _uint16_or( sel_a, sel_b ); + + return (result); +} + +// Count Leading Zeros +static inline uint32_t _uint32_cntlz( uint32_t x ) +{ +#ifdef __GNUC__ + /* NOTE: __builtin_clz is undefined for x == 0 */ + /* On PowerPC, this will map to insn: cntlzw */ + /* On Pentium, this will map to insn: clz */ + uint32_t is_x_nez_msb = _uint32_neg( x ); + uint32_t nlz = __builtin_clz( x ); + uint32_t result = _uint32_sels( is_x_nez_msb, nlz, 0x00000020 ); + return (result); +#else + const uint32_t x0 = _uint32_srl( x, 1 ); + const uint32_t x1 = _uint32_or( x, x0 ); + const uint32_t x2 = _uint32_srl( x1, 2 ); + const uint32_t x3 = _uint32_or( x1, x2 ); + const uint32_t x4 = _uint32_srl( x3, 4 ); + const uint32_t x5 = _uint32_or( x3, x4 ); + const uint32_t x6 = _uint32_srl( x5, 8 ); + const uint32_t x7 = _uint32_or( x5, x6 ); + const uint32_t x8 = _uint32_srl( x7, 16 ); + const uint32_t x9 = _uint32_or( x7, x8 ); + const uint32_t xA = _uint32_not( x9 ); + const uint32_t xB = _uint32_srl( xA, 1 ); + const uint32_t xC = _uint32_and( xB, 0x55555555 ); + const uint32_t xD = _uint32_sub( xA, xC ); + const uint32_t xE = _uint32_and( xD, 0x33333333 ); + const uint32_t xF = _uint32_srl( xD, 2 ); + const uint32_t x10 = _uint32_and( xF, 0x33333333 ); + const uint32_t x11 = _uint32_add( xE, x10 ); + const uint32_t x12 = _uint32_srl( x11, 4 ); + const uint32_t x13 = _uint32_add( x11, x12 ); + const uint32_t x14 = _uint32_and( x13, 0x0f0f0f0f ); + const uint32_t x15 = _uint32_srl( x14, 8 ); + const uint32_t x16 = _uint32_add( x14, x15 ); + const uint32_t x17 = _uint32_srl( x16, 16 ); + const uint32_t x18 = _uint32_add( x16, x17 ); + const uint32_t x19 = _uint32_and( x18, 0x0000003f ); + return ( x19 ); +#endif +} + +// Count Leading Zeros +static inline uint16_t _uint16_cntlz( uint16_t x ) +{ +#ifdef __GNUC__ + uint16_t nlz32 = (uint16_t)_uint32_cntlz( (uint32_t)x ); + uint32_t nlz = _uint32_sub( nlz32, 16 ); + return (nlz); +#else + const uint16_t x0 = _uint16_srl( x, 1 ); + const uint16_t x1 = _uint16_or( x, x0 ); + const uint16_t x2 = _uint16_srl( x1, 2 ); + const uint16_t x3 = _uint16_or( x1, x2 ); + const uint16_t x4 = _uint16_srl( x3, 4 ); + const uint16_t x5 = _uint16_or( x3, x4 ); + const uint16_t x6 = _uint16_srl( x5, 8 ); + const uint16_t x7 = _uint16_or( x5, x6 ); + const uint16_t x8 = _uint16_not( x7 ); + const uint16_t x9 = _uint16_srlm( x8, 1, 0x5555 ); + const uint16_t xA = _uint16_sub( x8, x9 ); + const uint16_t xB = _uint16_and( xA, 0x3333 ); + const uint16_t xC = _uint16_srlm( xA, 2, 0x3333 ); + const uint16_t xD = _uint16_add( xB, xC ); + const uint16_t xE = _uint16_srl( xD, 4 ); + const uint16_t xF = _uint16_addm( xD, xE, 0x0f0f ); + const uint16_t x10 = _uint16_srl( xF, 8 ); + const uint16_t x11 = _uint16_addm( xF, x10, 0x001f ); + return ( x11 ); +#endif +} + +uint16_t +half_from_float( uint32_t f ) +{ + const uint32_t one = _uint32_li( 0x00000001 ); + const uint32_t f_s_mask = _uint32_li( 0x80000000 ); + const uint32_t f_e_mask = _uint32_li( 0x7f800000 ); + const uint32_t f_m_mask = _uint32_li( 0x007fffff ); + const uint32_t f_m_hidden_bit = _uint32_li( 0x00800000 ); + const uint32_t f_m_round_bit = _uint32_li( 0x00001000 ); + const uint32_t f_snan_mask = _uint32_li( 0x7fc00000 ); + const uint32_t f_e_pos = _uint32_li( 0x00000017 ); + const uint32_t h_e_pos = _uint32_li( 0x0000000a ); + const uint32_t h_e_mask = _uint32_li( 0x00007c00 ); + const uint32_t h_snan_mask = _uint32_li( 0x00007e00 ); + const uint32_t h_e_mask_value = _uint32_li( 0x0000001f ); + const uint32_t f_h_s_pos_offset = _uint32_li( 0x00000010 ); + const uint32_t f_h_bias_offset = _uint32_li( 0x00000070 ); + const uint32_t f_h_m_pos_offset = _uint32_li( 0x0000000d ); + const uint32_t h_nan_min = _uint32_li( 0x00007c01 ); + const uint32_t f_h_e_biased_flag = _uint32_li( 0x0000008f ); + const uint32_t f_s = _uint32_and( f, f_s_mask ); + const uint32_t f_e = _uint32_and( f, f_e_mask ); + const uint16_t h_s = _uint32_srl( f_s, f_h_s_pos_offset ); + const uint32_t f_m = _uint32_and( f, f_m_mask ); + const uint16_t f_e_amount = _uint32_srl( f_e, f_e_pos ); + const uint32_t f_e_half_bias = _uint32_sub( f_e_amount, f_h_bias_offset ); + const uint32_t f_snan = _uint32_and( f, f_snan_mask ); + const uint32_t f_m_round_mask = _uint32_and( f_m, f_m_round_bit ); + const uint32_t f_m_round_offset = _uint32_sll( f_m_round_mask, one ); + const uint32_t f_m_rounded = _uint32_add( f_m, f_m_round_offset ); + const uint32_t f_m_denorm_sa = _uint32_sub( one, f_e_half_bias ); + const uint32_t f_m_with_hidden = _uint32_or( f_m_rounded, f_m_hidden_bit ); + const uint32_t f_m_denorm = _uint32_srl( f_m_with_hidden, f_m_denorm_sa ); + const uint32_t h_m_denorm = _uint32_srl( f_m_denorm, f_h_m_pos_offset ); + const uint32_t f_m_rounded_overflow = _uint32_and( f_m_rounded, f_m_hidden_bit ); + const uint32_t m_nan = _uint32_srl( f_m, f_h_m_pos_offset ); + const uint32_t h_em_nan = _uint32_or( h_e_mask, m_nan ); + const uint32_t h_e_norm_overflow_offset = _uint32_inc( f_e_half_bias ); + const uint32_t h_e_norm_overflow = _uint32_sll( h_e_norm_overflow_offset, h_e_pos ); + const uint32_t h_e_norm = _uint32_sll( f_e_half_bias, h_e_pos ); + const uint32_t h_m_norm = _uint32_srl( f_m_rounded, f_h_m_pos_offset ); + const uint32_t h_em_norm = _uint32_or( h_e_norm, h_m_norm ); + const uint32_t is_h_ndenorm_msb = _uint32_sub( f_h_bias_offset, f_e_amount ); + const uint32_t is_f_e_flagged_msb = _uint32_sub( f_h_e_biased_flag, f_e_half_bias ); + const uint32_t is_h_denorm_msb = _uint32_not( is_h_ndenorm_msb ); + const uint32_t is_f_m_eqz_msb = _uint32_dec( f_m ); + const uint32_t is_h_nan_eqz_msb = _uint32_dec( m_nan ); + const uint32_t is_f_inf_msb = _uint32_and( is_f_e_flagged_msb, is_f_m_eqz_msb ); + const uint32_t is_f_nan_underflow_msb = _uint32_and( is_f_e_flagged_msb, is_h_nan_eqz_msb ); + const uint32_t is_e_overflow_msb = _uint32_sub( h_e_mask_value, f_e_half_bias ); + const uint32_t is_h_inf_msb = _uint32_or( is_e_overflow_msb, is_f_inf_msb ); + const uint32_t is_f_nsnan_msb = _uint32_sub( f_snan, f_snan_mask ); + const uint32_t is_m_norm_overflow_msb = _uint32_neg( f_m_rounded_overflow ); + const uint32_t is_f_snan_msb = _uint32_not( is_f_nsnan_msb ); + const uint32_t h_em_overflow_result = _uint32_sels( is_m_norm_overflow_msb, h_e_norm_overflow, h_em_norm ); + const uint32_t h_em_nan_result = _uint32_sels( is_f_e_flagged_msb, h_em_nan, h_em_overflow_result ); + const uint32_t h_em_nan_underflow_result = _uint32_sels( is_f_nan_underflow_msb, h_nan_min, h_em_nan_result ); + const uint32_t h_em_inf_result = _uint32_sels( is_h_inf_msb, h_e_mask, h_em_nan_underflow_result ); + const uint32_t h_em_denorm_result = _uint32_sels( is_h_denorm_msb, h_m_denorm, h_em_inf_result ); + const uint32_t h_em_snan_result = _uint32_sels( is_f_snan_msb, h_snan_mask, h_em_denorm_result ); + const uint32_t h_result = _uint32_or( h_s, h_em_snan_result ); + + return (uint16_t)(h_result); +} + +uint32_t +half_to_float( uint16_t h ) +{ + const uint32_t h_e_mask = _uint32_li( 0x00007c00 ); + const uint32_t h_m_mask = _uint32_li( 0x000003ff ); + const uint32_t h_s_mask = _uint32_li( 0x00008000 ); + const uint32_t h_f_s_pos_offset = _uint32_li( 0x00000010 ); + const uint32_t h_f_e_pos_offset = _uint32_li( 0x0000000d ); + const uint32_t h_f_bias_offset = _uint32_li( 0x0001c000 ); + const uint32_t f_e_mask = _uint32_li( 0x7f800000 ); + const uint32_t f_m_mask = _uint32_li( 0x007fffff ); + const uint32_t h_f_e_denorm_bias = _uint32_li( 0x0000007e ); + const uint32_t h_f_m_denorm_sa_bias = _uint32_li( 0x00000008 ); + const uint32_t f_e_pos = _uint32_li( 0x00000017 ); + const uint32_t h_e_mask_minus_one = _uint32_li( 0x00007bff ); + const uint32_t h_e = _uint32_and( h, h_e_mask ); + const uint32_t h_m = _uint32_and( h, h_m_mask ); + const uint32_t h_s = _uint32_and( h, h_s_mask ); + const uint32_t h_e_f_bias = _uint32_add( h_e, h_f_bias_offset ); + const uint32_t h_m_nlz = _uint32_cntlz( h_m ); + const uint32_t f_s = _uint32_sll( h_s, h_f_s_pos_offset ); + const uint32_t f_e = _uint32_sll( h_e_f_bias, h_f_e_pos_offset ); + const uint32_t f_m = _uint32_sll( h_m, h_f_e_pos_offset ); + const uint32_t f_em = _uint32_or( f_e, f_m ); + const uint32_t h_f_m_sa = _uint32_sub( h_m_nlz, h_f_m_denorm_sa_bias ); + const uint32_t f_e_denorm_unpacked = _uint32_sub( h_f_e_denorm_bias, h_f_m_sa ); + const uint32_t h_f_m = _uint32_sll( h_m, h_f_m_sa ); + const uint32_t f_m_denorm = _uint32_and( h_f_m, f_m_mask ); + const uint32_t f_e_denorm = _uint32_sll( f_e_denorm_unpacked, f_e_pos ); + const uint32_t f_em_denorm = _uint32_or( f_e_denorm, f_m_denorm ); + const uint32_t f_em_nan = _uint32_or( f_e_mask, f_m ); + const uint32_t is_e_eqz_msb = _uint32_dec( h_e ); + const uint32_t is_m_nez_msb = _uint32_neg( h_m ); + const uint32_t is_e_flagged_msb = _uint32_sub( h_e_mask_minus_one, h_e ); + const uint32_t is_zero_msb = _uint32_andc( is_e_eqz_msb, is_m_nez_msb ); + const uint32_t is_inf_msb = _uint32_andc( is_e_flagged_msb, is_m_nez_msb ); + const uint32_t is_denorm_msb = _uint32_and( is_m_nez_msb, is_e_eqz_msb ); + const uint32_t is_nan_msb = _uint32_and( is_e_flagged_msb, is_m_nez_msb ); + const uint32_t is_zero = _uint32_ext( is_zero_msb ); + const uint32_t f_zero_result = _uint32_andc( f_em, is_zero ); + const uint32_t f_denorm_result = _uint32_sels( is_denorm_msb, f_em_denorm, f_zero_result ); + const uint32_t f_inf_result = _uint32_sels( is_inf_msb, f_e_mask, f_denorm_result ); + const uint32_t f_nan_result = _uint32_sels( is_nan_msb, f_em_nan, f_inf_result ); + const uint32_t f_result = _uint32_or( f_s, f_nan_result ); + + return (f_result); +} + +// half_add +// -------- +// +// (SUM) uint16_t z = half_add( x, y ); +// (DIFFERENCE) uint16_t z = half_add( x, -y ); +// +// * Difference of ZEROs is always +ZERO +// * Sum round with guard + round + sticky bit (grs) +// * QNaN + = QNaN +// * + +INF = +INF +// * - -INF = -INF +// * INF - INF = SNaN +// +// Will have exactly (0 ulps difference) the same result as: +// (Round up) +// +// union FLOAT_32 +// { +// float f32; +// uint32_t u32; +// }; +// +// union FLOAT_32 fx = { .u32 = half_to_float( x ) }; +// union FLOAT_32 fy = { .u32 = half_to_float( y ) }; +// union FLOAT_32 fz = { .f32 = fx.f32 + fy.f32 }; +// uint16_t z = float_to_half( fz ); +// +uint16_t +half_add( uint16_t x, uint16_t y ) +{ + const uint16_t one = _uint16_li( 0x0001 ); + const uint16_t msb_to_lsb_sa = _uint16_li( 0x000f ); + const uint16_t h_s_mask = _uint16_li( 0x8000 ); + const uint16_t h_e_mask = _uint16_li( 0x7c00 ); + const uint16_t h_m_mask = _uint16_li( 0x03ff ); + const uint16_t h_m_msb_mask = _uint16_li( 0x2000 ); + const uint16_t h_m_msb_sa = _uint16_li( 0x000d ); + const uint16_t h_m_hidden = _uint16_li( 0x0400 ); + const uint16_t h_e_pos = _uint16_li( 0x000a ); + const uint16_t h_e_bias_minus_one = _uint16_li( 0x000e ); + const uint16_t h_m_grs_carry = _uint16_li( 0x4000 ); + const uint16_t h_m_grs_carry_pos = _uint16_li( 0x000e ); + const uint16_t h_grs_size = _uint16_li( 0x0003 ); + const uint16_t h_snan = _uint16_li( 0xfe00 ); + const uint16_t h_e_mask_minus_one = _uint16_li( 0x7bff ); + const uint16_t h_grs_round_carry = _uint16_sll( one, h_grs_size ); + const uint16_t h_grs_round_mask = _uint16_sub( h_grs_round_carry, one ); + const uint16_t x_e = _uint16_and( x, h_e_mask ); + const uint16_t y_e = _uint16_and( y, h_e_mask ); + const uint16_t is_y_e_larger_msb = _uint16_sub( x_e, y_e ); + const uint16_t a = _uint16_sels( is_y_e_larger_msb, y, x); + const uint16_t a_s = _uint16_and( a, h_s_mask ); + const uint16_t a_e = _uint16_and( a, h_e_mask ); + const uint16_t a_m_no_hidden_bit = _uint16_and( a, h_m_mask ); + const uint16_t a_em_no_hidden_bit = _uint16_or( a_e, a_m_no_hidden_bit ); + const uint16_t b = _uint16_sels( is_y_e_larger_msb, x, y); + const uint16_t b_s = _uint16_and( b, h_s_mask ); + const uint16_t b_e = _uint16_and( b, h_e_mask ); + const uint16_t b_m_no_hidden_bit = _uint16_and( b, h_m_mask ); + const uint16_t b_em_no_hidden_bit = _uint16_or( b_e, b_m_no_hidden_bit ); + const uint16_t is_diff_sign_msb = _uint16_xor( a_s, b_s ); + const uint16_t is_a_inf_msb = _uint16_sub( h_e_mask_minus_one, a_em_no_hidden_bit ); + const uint16_t is_b_inf_msb = _uint16_sub( h_e_mask_minus_one, b_em_no_hidden_bit ); + const uint16_t is_undenorm_msb = _uint16_dec( a_e ); + const uint16_t is_undenorm = _uint16_ext( is_undenorm_msb ); + const uint16_t is_both_inf_msb = _uint16_and( is_a_inf_msb, is_b_inf_msb ); + const uint16_t is_invalid_inf_op_msb = _uint16_and( is_both_inf_msb, b_s ); + const uint16_t is_a_e_nez_msb = _uint16_neg( a_e ); + const uint16_t is_b_e_nez_msb = _uint16_neg( b_e ); + const uint16_t is_a_e_nez = _uint16_ext( is_a_e_nez_msb ); + const uint16_t is_b_e_nez = _uint16_ext( is_b_e_nez_msb ); + const uint16_t a_m_hidden_bit = _uint16_and( is_a_e_nez, h_m_hidden ); + const uint16_t b_m_hidden_bit = _uint16_and( is_b_e_nez, h_m_hidden ); + const uint16_t a_m_no_grs = _uint16_or( a_m_no_hidden_bit, a_m_hidden_bit ); + const uint16_t b_m_no_grs = _uint16_or( b_m_no_hidden_bit, b_m_hidden_bit ); + const uint16_t diff_e = _uint16_sub( a_e, b_e ); + const uint16_t a_e_unbias = _uint16_sub( a_e, h_e_bias_minus_one ); + const uint16_t a_m = _uint16_sll( a_m_no_grs, h_grs_size ); + const uint16_t a_e_biased = _uint16_srl( a_e, h_e_pos ); + const uint16_t m_sa_unbias = _uint16_srl( a_e_unbias, h_e_pos ); + const uint16_t m_sa_default = _uint16_srl( diff_e, h_e_pos ); + const uint16_t m_sa_unbias_mask = _uint16_andc( is_a_e_nez_msb, is_b_e_nez_msb ); + const uint16_t m_sa = _uint16_sels( m_sa_unbias_mask, m_sa_unbias, m_sa_default ); + const uint16_t b_m_no_sticky = _uint16_sll( b_m_no_grs, h_grs_size ); + const uint16_t sh_m = _uint16_srl( b_m_no_sticky, m_sa ); + const uint16_t sticky_overflow = _uint16_sll( one, m_sa ); + const uint16_t sticky_mask = _uint16_dec( sticky_overflow ); + const uint16_t sticky_collect = _uint16_and( b_m_no_sticky, sticky_mask ); + const uint16_t is_sticky_set_msb = _uint16_neg( sticky_collect ); + const uint16_t sticky = _uint16_srl( is_sticky_set_msb, msb_to_lsb_sa); + const uint16_t b_m = _uint16_or( sh_m, sticky ); + const uint16_t is_c_m_ab_pos_msb = _uint16_sub( b_m, a_m ); + const uint16_t c_inf = _uint16_or( a_s, h_e_mask ); + const uint16_t c_m_sum = _uint16_add( a_m, b_m ); + const uint16_t c_m_diff_ab = _uint16_sub( a_m, b_m ); + const uint16_t c_m_diff_ba = _uint16_sub( b_m, a_m ); + const uint16_t c_m_smag_diff = _uint16_sels( is_c_m_ab_pos_msb, c_m_diff_ab, c_m_diff_ba ); + const uint16_t c_s_diff = _uint16_sels( is_c_m_ab_pos_msb, a_s, b_s ); + const uint16_t c_s = _uint16_sels( is_diff_sign_msb, c_s_diff, a_s ); + const uint16_t c_m_smag_diff_nlz = _uint16_cntlz( c_m_smag_diff ); + const uint16_t diff_norm_sa = _uint16_sub( c_m_smag_diff_nlz, one ); + const uint16_t is_diff_denorm_msb = _uint16_sub( a_e_biased, diff_norm_sa ); + const uint16_t is_diff_denorm = _uint16_ext( is_diff_denorm_msb ); + const uint16_t is_a_or_b_norm_msb = _uint16_neg( a_e_biased ); + const uint16_t diff_denorm_sa = _uint16_dec( a_e_biased ); + const uint16_t c_m_diff_denorm = _uint16_sll( c_m_smag_diff, diff_denorm_sa ); + const uint16_t c_m_diff_norm = _uint16_sll( c_m_smag_diff, diff_norm_sa ); + const uint16_t c_e_diff_norm = _uint16_sub( a_e_biased, diff_norm_sa ); + const uint16_t c_m_diff_ab_norm = _uint16_sels( is_diff_denorm_msb, c_m_diff_denorm, c_m_diff_norm ); + const uint16_t c_e_diff_ab_norm = _uint16_andc( c_e_diff_norm, is_diff_denorm ); + const uint16_t c_m_diff = _uint16_sels( is_a_or_b_norm_msb, c_m_diff_ab_norm, c_m_smag_diff ); + const uint16_t c_e_diff = _uint16_sels( is_a_or_b_norm_msb, c_e_diff_ab_norm, a_e_biased ); + const uint16_t is_diff_eqz_msb = _uint16_dec( c_m_diff ); + const uint16_t is_diff_exactly_zero_msb = _uint16_and( is_diff_sign_msb, is_diff_eqz_msb ); + const uint16_t is_diff_exactly_zero = _uint16_ext( is_diff_exactly_zero_msb ); + const uint16_t c_m_added = _uint16_sels( is_diff_sign_msb, c_m_diff, c_m_sum ); + const uint16_t c_e_added = _uint16_sels( is_diff_sign_msb, c_e_diff, a_e_biased ); + const uint16_t c_m_carry = _uint16_and( c_m_added, h_m_grs_carry ); + const uint16_t is_c_m_carry_msb = _uint16_neg( c_m_carry ); + const uint16_t c_e_hidden_offset = _uint16_andsrl( c_m_added, h_m_grs_carry, h_m_grs_carry_pos ); + const uint16_t c_m_sub_hidden = _uint16_srl( c_m_added, one ); + const uint16_t c_m_no_hidden = _uint16_sels( is_c_m_carry_msb, c_m_sub_hidden, c_m_added ); + const uint16_t c_e_no_hidden = _uint16_add( c_e_added, c_e_hidden_offset ); + const uint16_t c_m_no_hidden_msb = _uint16_and( c_m_no_hidden, h_m_msb_mask ); + const uint16_t undenorm_m_msb_odd = _uint16_srl( c_m_no_hidden_msb, h_m_msb_sa ); + const uint16_t undenorm_fix_e = _uint16_and( is_undenorm, undenorm_m_msb_odd ); + const uint16_t c_e_fixed = _uint16_add( c_e_no_hidden, undenorm_fix_e ); + const uint16_t c_m_round_amount = _uint16_and( c_m_no_hidden, h_grs_round_mask ); + const uint16_t c_m_rounded = _uint16_add( c_m_no_hidden, c_m_round_amount ); + const uint16_t c_m_round_overflow = _uint16_andsrl( c_m_rounded, h_m_grs_carry, h_m_grs_carry_pos ); + const uint16_t c_e_rounded = _uint16_add( c_e_fixed, c_m_round_overflow ); + const uint16_t c_m_no_grs = _uint16_srlm( c_m_rounded, h_grs_size, h_m_mask ); + const uint16_t c_e = _uint16_sll( c_e_rounded, h_e_pos ); + const uint16_t c_em = _uint16_or( c_e, c_m_no_grs ); + const uint16_t c_normal = _uint16_or( c_s, c_em ); + const uint16_t c_inf_result = _uint16_sels( is_a_inf_msb, c_inf, c_normal ); + const uint16_t c_zero_result = _uint16_andc( c_inf_result, is_diff_exactly_zero ); + const uint16_t c_result = _uint16_sels( is_invalid_inf_op_msb, h_snan, c_zero_result ); + + return (c_result); +} + +// half_mul +// -------- +// +// May have 0 or 1 ulp difference from the following result: +// (Round to nearest) +// NOTE: Rounding mode differs between conversion and multiply +// +// union FLOAT_32 +// { +// float f32; +// uint32_t u32; +// }; +// +// union FLOAT_32 fx = { .u32 = half_to_float( x ) }; +// union FLOAT_32 fy = { .u32 = half_to_float( y ) }; +// union FLOAT_32 fz = { .f32 = fx.f32 * fy.f32 }; +// uint16_t z = float_to_half( fz ); +// +uint16_t +half_mul( uint16_t x, uint16_t y ) +{ + const uint32_t one = _uint32_li( 0x00000001 ); + const uint32_t h_s_mask = _uint32_li( 0x00008000 ); + const uint32_t h_e_mask = _uint32_li( 0x00007c00 ); + const uint32_t h_m_mask = _uint32_li( 0x000003ff ); + const uint32_t h_m_hidden = _uint32_li( 0x00000400 ); + const uint32_t h_e_pos = _uint32_li( 0x0000000a ); + const uint32_t h_e_bias = _uint32_li( 0x0000000f ); + const uint32_t h_m_bit_count = _uint32_li( 0x0000000a ); + const uint32_t h_m_bit_half_count = _uint32_li( 0x00000005 ); + const uint32_t h_nan_min = _uint32_li( 0x00007c01 ); + const uint32_t h_e_mask_minus_one = _uint32_li( 0x00007bff ); + const uint32_t h_snan = _uint32_li( 0x0000fe00 ); + const uint32_t m_round_overflow_bit = _uint32_li( 0x00000020 ); + const uint32_t m_hidden_bit = _uint32_li( 0x00100000 ); + const uint32_t a_s = _uint32_and( x, h_s_mask ); + const uint32_t b_s = _uint32_and( y, h_s_mask ); + const uint32_t c_s = _uint32_xor( a_s, b_s ); + const uint32_t x_e = _uint32_and( x, h_e_mask ); + const uint32_t x_e_eqz_msb = _uint32_dec( x_e ); + const uint32_t a = _uint32_sels( x_e_eqz_msb, y, x ); + const uint32_t b = _uint32_sels( x_e_eqz_msb, x, y ); + const uint32_t a_e = _uint32_and( a, h_e_mask ); + const uint32_t b_e = _uint32_and( b, h_e_mask ); + const uint32_t a_m = _uint32_and( a, h_m_mask ); + const uint32_t b_m = _uint32_and( b, h_m_mask ); + const uint32_t a_e_amount = _uint32_srl( a_e, h_e_pos ); + const uint32_t b_e_amount = _uint32_srl( b_e, h_e_pos ); + const uint32_t a_m_with_hidden = _uint32_or( a_m, h_m_hidden ); + const uint32_t b_m_with_hidden = _uint32_or( b_m, h_m_hidden ); + const uint32_t c_m_normal = _uint32_mul( a_m_with_hidden, b_m_with_hidden ); + const uint32_t c_m_denorm_biased = _uint32_mul( a_m_with_hidden, b_m ); + const uint32_t c_e_denorm_unbias_e = _uint32_sub( h_e_bias, a_e_amount ); + const uint32_t c_m_denorm_round_amount = _uint32_and( c_m_denorm_biased, h_m_mask ); + const uint32_t c_m_denorm_rounded = _uint32_add( c_m_denorm_biased, c_m_denorm_round_amount ); + const uint32_t c_m_denorm_inplace = _uint32_srl( c_m_denorm_rounded, h_m_bit_count ); + const uint32_t c_m_denorm_unbiased = _uint32_srl( c_m_denorm_inplace, c_e_denorm_unbias_e ); + const uint32_t c_m_denorm = _uint32_and( c_m_denorm_unbiased, h_m_mask ); + const uint32_t c_e_amount_biased = _uint32_add( a_e_amount, b_e_amount ); + const uint32_t c_e_amount_unbiased = _uint32_sub( c_e_amount_biased, h_e_bias ); + const uint32_t is_c_e_unbiased_underflow = _uint32_ext( c_e_amount_unbiased ); + const uint32_t c_e_underflow_half_sa = _uint32_neg( c_e_amount_unbiased ); + const uint32_t c_e_underflow_sa = _uint32_sll( c_e_underflow_half_sa, one ); + const uint32_t c_m_underflow = _uint32_srl( c_m_normal, c_e_underflow_sa ); + const uint32_t c_e_underflow_added = _uint32_andc( c_e_amount_unbiased, is_c_e_unbiased_underflow ); + const uint32_t c_m_underflow_added = _uint32_selb( is_c_e_unbiased_underflow, c_m_underflow, c_m_normal ); + const uint32_t is_mul_overflow_test = _uint32_and( c_e_underflow_added, m_round_overflow_bit ); + const uint32_t is_mul_overflow_msb = _uint32_neg( is_mul_overflow_test ); + const uint32_t c_e_norm_radix_corrected = _uint32_inc( c_e_underflow_added ); + const uint32_t c_m_norm_radix_corrected = _uint32_srl( c_m_underflow_added, one ); + const uint32_t c_m_norm_hidden_bit = _uint32_and( c_m_norm_radix_corrected, m_hidden_bit ); + const uint32_t is_c_m_norm_no_hidden_msb = _uint32_dec( c_m_norm_hidden_bit ); + const uint32_t c_m_norm_lo = _uint32_srl( c_m_norm_radix_corrected, h_m_bit_half_count ); + const uint32_t c_m_norm_lo_nlz = _uint16_cntlz( c_m_norm_lo ); + const uint32_t is_c_m_hidden_nunderflow_msb = _uint32_sub( c_m_norm_lo_nlz, c_e_norm_radix_corrected ); + const uint32_t is_c_m_hidden_underflow_msb = _uint32_not( is_c_m_hidden_nunderflow_msb ); + const uint32_t is_c_m_hidden_underflow = _uint32_ext( is_c_m_hidden_underflow_msb ); + const uint32_t c_m_hidden_underflow_normalized_sa = _uint32_srl( c_m_norm_lo_nlz, one ); + const uint32_t c_m_hidden_underflow_normalized = _uint32_sll( c_m_norm_radix_corrected, c_m_hidden_underflow_normalized_sa ); + const uint32_t c_m_hidden_normalized = _uint32_sll( c_m_norm_radix_corrected, c_m_norm_lo_nlz ); + const uint32_t c_e_hidden_normalized = _uint32_sub( c_e_norm_radix_corrected, c_m_norm_lo_nlz ); + const uint32_t c_e_hidden = _uint32_andc( c_e_hidden_normalized, is_c_m_hidden_underflow ); + const uint32_t c_m_hidden = _uint32_sels( is_c_m_hidden_underflow_msb, c_m_hidden_underflow_normalized, c_m_hidden_normalized ); + const uint32_t c_m_normalized = _uint32_sels( is_c_m_norm_no_hidden_msb, c_m_hidden, c_m_norm_radix_corrected ); + const uint32_t c_e_normalized = _uint32_sels( is_c_m_norm_no_hidden_msb, c_e_hidden, c_e_norm_radix_corrected ); + const uint32_t c_m_norm_round_amount = _uint32_and( c_m_normalized, h_m_mask ); + const uint32_t c_m_norm_rounded = _uint32_add( c_m_normalized, c_m_norm_round_amount ); + const uint32_t is_round_overflow_test = _uint32_and( c_e_normalized, m_round_overflow_bit ); + const uint32_t is_round_overflow_msb = _uint32_neg( is_round_overflow_test ); + const uint32_t c_m_norm_inplace = _uint32_srl( c_m_norm_rounded, h_m_bit_count ); + const uint32_t c_m = _uint32_and( c_m_norm_inplace, h_m_mask ); + const uint32_t c_e_norm_inplace = _uint32_sll( c_e_normalized, h_e_pos ); + const uint32_t c_e = _uint32_and( c_e_norm_inplace, h_e_mask ); + const uint32_t c_em_nan = _uint32_or( h_e_mask, a_m ); + const uint32_t c_nan = _uint32_or( a_s, c_em_nan ); + const uint32_t c_denorm = _uint32_or( c_s, c_m_denorm ); + const uint32_t c_inf = _uint32_or( c_s, h_e_mask ); + const uint32_t c_em_norm = _uint32_or( c_e, c_m ); + const uint32_t is_a_e_flagged_msb = _uint32_sub( h_e_mask_minus_one, a_e ); + const uint32_t is_b_e_flagged_msb = _uint32_sub( h_e_mask_minus_one, b_e ); + const uint32_t is_a_e_eqz_msb = _uint32_dec( a_e ); + const uint32_t is_a_m_eqz_msb = _uint32_dec( a_m ); + const uint32_t is_b_e_eqz_msb = _uint32_dec( b_e ); + const uint32_t is_b_m_eqz_msb = _uint32_dec( b_m ); + const uint32_t is_b_eqz_msb = _uint32_and( is_b_e_eqz_msb, is_b_m_eqz_msb ); + const uint32_t is_a_eqz_msb = _uint32_and( is_a_e_eqz_msb, is_a_m_eqz_msb ); + const uint32_t is_c_nan_via_a_msb = _uint32_andc( is_a_e_flagged_msb, is_b_e_flagged_msb ); + const uint32_t is_c_nan_via_b_msb = _uint32_andc( is_b_e_flagged_msb, is_b_m_eqz_msb ); + const uint32_t is_c_nan_msb = _uint32_or( is_c_nan_via_a_msb, is_c_nan_via_b_msb ); + const uint32_t is_c_denorm_msb = _uint32_andc( is_b_e_eqz_msb, is_a_e_flagged_msb ); + const uint32_t is_a_inf_msb = _uint32_and( is_a_e_flagged_msb, is_a_m_eqz_msb ); + const uint32_t is_c_snan_msb = _uint32_and( is_a_inf_msb, is_b_eqz_msb ); + const uint32_t is_c_nan_min_via_a_msb = _uint32_and( is_a_e_flagged_msb, is_b_eqz_msb ); + const uint32_t is_c_nan_min_via_b_msb = _uint32_and( is_b_e_flagged_msb, is_a_eqz_msb ); + const uint32_t is_c_nan_min_msb = _uint32_or( is_c_nan_min_via_a_msb, is_c_nan_min_via_b_msb ); + const uint32_t is_c_inf_msb = _uint32_or( is_a_e_flagged_msb, is_b_e_flagged_msb ); + const uint32_t is_overflow_msb = _uint32_or( is_round_overflow_msb, is_mul_overflow_msb ); + const uint32_t c_em_overflow_result = _uint32_sels( is_overflow_msb, h_e_mask, c_em_norm ); + const uint32_t c_common_result = _uint32_or( c_s, c_em_overflow_result ); + const uint32_t c_zero_result = _uint32_sels( is_b_eqz_msb, c_s, c_common_result ); + const uint32_t c_nan_result = _uint32_sels( is_c_nan_msb, c_nan, c_zero_result ); + const uint32_t c_nan_min_result = _uint32_sels( is_c_nan_min_msb, h_nan_min, c_nan_result ); + const uint32_t c_inf_result = _uint32_sels( is_c_inf_msb, c_inf, c_nan_min_result ); + const uint32_t c_denorm_result = _uint32_sels( is_c_denorm_msb, c_denorm, c_inf_result); + const uint32_t c_result = _uint32_sels( is_c_snan_msb, h_snan, c_denorm_result ); + + return (uint16_t)(c_result); +} diff --git a/lib/half.h b/lib/half.h new file mode 100644 index 000000000..75657d34c --- /dev/null +++ b/lib/half.h @@ -0,0 +1,18 @@ +#ifndef HALF_H +#define HALF_H + +#include + +uint32_t half_to_float( uint16_t h ); +uint16_t half_from_float( uint32_t f ); +uint16_t half_add( uint16_t arg0, uint16_t arg1 ); +uint16_t half_mul( uint16_t arg0, uint16_t arg1 ); + +static inline uint16_t +half_sub( uint16_t ha, uint16_t hb ) +{ + // (a-b) is the same as (a+(-b)) + return half_add( ha, hb ^ 0x8000 ); +} + +#endif /* HALF_H */ diff --git a/lib/lz4frame.c b/lib/lz4frame.c new file mode 100644 index 000000000..48520eb77 --- /dev/null +++ b/lib/lz4frame.c @@ -0,0 +1,3497 @@ +/* + LZ4 - Fast LZ compression algorithm + Copyright (C) 2011-2016, Yann Collet. + + BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + You can contact the author at : + - LZ4 homepage : http://www.lz4.org + - LZ4 source repository : https://github.com/Cyan4973/lz4 +*/ + +/*-************************************ +* Compiler Options +**************************************/ +#ifdef _MSC_VER /* Visual Studio */ +# define FORCE_INLINE static __forceinline +# include +# pragma warning(disable : 4127) /* disable: C4127: conditional expression is constant */ +# pragma warning(disable : 4293) /* disable: C4293: too large shift (32-bits) */ +#else +# if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */ +# if defined(__GNUC__) || defined(__clang__) +# define FORCE_INLINE static inline __attribute__((always_inline)) +# else +# define FORCE_INLINE static inline +# endif +# else +# define FORCE_INLINE static +# endif /* __STDC_VERSION__ */ +#endif /* _MSC_VER */ + +/* LZ4_GCC_VERSION is defined into lz4.h */ +#if (LZ4_GCC_VERSION >= 302) || (__INTEL_COMPILER >= 800) || defined(__clang__) +# define expect(expr,value) (__builtin_expect ((expr),(value)) ) +#else +# define expect(expr,value) (expr) +#endif + +#define likely(expr) expect((expr) != 0, 1) +#define unlikely(expr) expect((expr) != 0, 0) + + +/*-************************************ +* Memory routines +**************************************/ +#include /* malloc, calloc, free */ +#define ALLOCATOR(n,s) calloc(n,s) +#define ALLOCATORF(s) calloc(1,s) +#define FREEMEM free +#include /* memset, memcpy, memmove */ +#define MEM_INIT memset + + +/*-************************************ +* Includes +**************************************/ +#include "lz4frame.h" +//#include "lz4.h" +//#include "lz4hc.h" +#define XXH_STATIC_LINKING_ONLY +#include "xxhash.h" + + +/*-************************************ +* Basic Types +**************************************/ +#if !defined (__VMS) && (defined (__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) ) +# include + typedef uint8_t BYTE; + typedef uint16_t U16; + typedef uint32_t U32; + typedef int32_t S32; + typedef uint64_t U64; +#else + typedef unsigned char BYTE; + typedef unsigned short U16; + typedef unsigned int U32; + typedef signed int S32; + typedef unsigned long long U64; +#endif + + + /*-************************************ + * Tuning parameters + **************************************/ + /* + * HEAPMODE : + * Select how default compression functions will allocate memory for their hash table, + * in memory stack (0:default, fastest), or in memory heap (1:requires malloc()). + */ +#define HEAPMODE 0 +#define LZ4HC_HEAPMODE 0 + /* + * ACCELERATION_DEFAULT : + * Select "acceleration" for LZ4_compress_fast() when parameter value <= 0 + */ +#define ACCELERATION_DEFAULT 1 + + + /*-************************************ + * CPU Feature Detection + **************************************/ + /* LZ4_FORCE_MEMORY_ACCESS + * By default, access to unaligned memory is controlled by `memcpy()`, which is safe and portable. + * Unfortunately, on some target/compiler combinations, the generated assembly is sub-optimal. + * The below switch allow to select different access method for improved performance. + * Method 0 (default) : use `memcpy()`. Safe and portable. + * Method 1 : `__packed` statement. It depends on compiler extension (ie, not portable). + * This method is safe if your compiler supports it, and *generally* as fast or faster than `memcpy`. + * Method 2 : direct access. This method is portable but violate C standard. + * It can generate buggy code on targets which generate assembly depending on alignment. + * But in some circumstances, it's the only known way to get the most performance (ie GCC + ARMv6) + * See https://fastcompression.blogspot.fr/2015/08/accessing-unaligned-memory.html for details. + * Prefer these methods in priority order (0 > 1 > 2) + */ +#ifndef LZ4_FORCE_MEMORY_ACCESS /* can be defined externally, on command line for example */ +# if defined(__GNUC__) && ( defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) || defined(__ARM_ARCH_6K__) || defined(__ARM_ARCH_6Z__) || defined(__ARM_ARCH_6ZK__) || defined(__ARM_ARCH_6T2__) ) +# define LZ4_FORCE_MEMORY_ACCESS 2 +# elif defined(__INTEL_COMPILER) || \ + (defined(__GNUC__) && ( defined(__ARM_ARCH_7__) || defined(__ARM_ARCH_7A__) || defined(__ARM_ARCH_7R__) || defined(__ARM_ARCH_7M__) || defined(__ARM_ARCH_7S__) )) +# define LZ4_FORCE_MEMORY_ACCESS 1 +# endif +#endif + + /* + * LZ4_FORCE_SW_BITCOUNT + * Define this parameter if your target system or compiler does not support hardware bit count + */ +#if defined(_MSC_VER) && defined(_WIN32_WCE) /* Visual Studio for Windows CE does not support Hardware bit count */ +# define LZ4_FORCE_SW_BITCOUNT +#endif + + + /*-************************************ + * Reading and writing into memory + **************************************/ +#define STEPSIZE sizeof(size_t) + + static unsigned LZ4_64bits( void ) { return sizeof( void* ) == 8; } + + static unsigned LZ4_isLittleEndian( void ) + { + const union { U32 i; BYTE c[4]; } one = { 1 }; /* don't use static : performance detrimental */ + return one.c[0]; + } + + + static U16 LZ4_read16( const void* memPtr ) + { + U16 val; memcpy( &val, memPtr, sizeof( val ) ); return val; + } + + static U32 LZ4_read32( const void* memPtr ) + { + U32 val; memcpy( &val, memPtr, sizeof( val ) ); return val; + } + + static size_t LZ4_read_ARCH( const void* memPtr ) + { + size_t val; memcpy( &val, memPtr, sizeof( val ) ); return val; + } + + static void LZ4_write16( void* memPtr, U16 value ) + { + memcpy( memPtr, &value, sizeof( value ) ); + } + + static void LZ4_write32( void* memPtr, U32 value ) + { + memcpy( memPtr, &value, sizeof( value ) ); + } + + static U16 LZ4_readLE16( const void* memPtr ) + { + if ( LZ4_isLittleEndian() ) { + return LZ4_read16( memPtr ); + } else { + const BYTE* p = (const BYTE*)memPtr; + return (U16)((U16)p[0] + (p[1] << 8)); + } + } + + static void LZ4_writeLE16( void* memPtr, U16 value ) + { + if ( LZ4_isLittleEndian() ) { + LZ4_write16( memPtr, value ); + } else { + BYTE* p = (BYTE*)memPtr; + p[0] = (BYTE)value; + p[1] = (BYTE)(value >> 8); + } + } + + static void LZ4_copy8( void* dst, const void* src ) + { + memcpy( dst, src, 8 ); + } + + /* customized variant of memcpy, which can overwrite up to 7 bytes beyond dstEnd */ + static void LZ4_wildCopy( void* dstPtr, const void* srcPtr, void* dstEnd ) + { + BYTE* d = (BYTE*)dstPtr; + const BYTE* s = (const BYTE*)srcPtr; + BYTE* const e = (BYTE*)dstEnd; + +#if 0 + const size_t l2 = 8 - (((size_t)d) & (sizeof( void* ) - 1)); + LZ4_copy8( d, s ); if ( d>e - 9 ) return; + d += l2; s += l2; +#endif /* join to align */ + + do { LZ4_copy8( d, s ); d += 8; s += 8; } while ( d> 8); + dstPtr[2] = (BYTE)(value32 >> 16); + dstPtr[3] = (BYTE)(value32 >> 24); + } + + static U64 LZ4F_readLE64( const BYTE* srcPtr ) + { + U64 value64 = srcPtr[0]; + value64 += ((U64)srcPtr[1] << 8); + value64 += ((U64)srcPtr[2] << 16); + value64 += ((U64)srcPtr[3] << 24); + value64 += ((U64)srcPtr[4] << 32); + value64 += ((U64)srcPtr[5] << 40); + value64 += ((U64)srcPtr[6] << 48); + value64 += ((U64)srcPtr[7] << 56); + return value64; + } + + static void LZ4F_writeLE64( BYTE* dstPtr, U64 value64 ) + { + dstPtr[0] = (BYTE)value64; + dstPtr[1] = (BYTE)(value64 >> 8); + dstPtr[2] = (BYTE)(value64 >> 16); + dstPtr[3] = (BYTE)(value64 >> 24); + dstPtr[4] = (BYTE)(value64 >> 32); + dstPtr[5] = (BYTE)(value64 >> 40); + dstPtr[6] = (BYTE)(value64 >> 48); + dstPtr[7] = (BYTE)(value64 >> 56); + } + + + + /*-************************************ + * Common Constants + **************************************/ +#define MINMATCH 4 + +#define WILDCOPYLENGTH 8 +#define LASTLITERALS 5 +#define MFLIMIT (WILDCOPYLENGTH+MINMATCH) + static const int LZ4_minLength = (MFLIMIT + 1); + +#define KB *(1 <<10) +#define MB *(1 <<20) +#define GB *(1U<<30) + +#define MAXD_LOG 16 +#define MAX_DISTANCE ((1 << MAXD_LOG) - 1) + +#define ML_BITS 4 +#define ML_MASK ((1U<> 3); +# elif (defined(__clang__) || (LZ4_GCC_VERSION >= 304)) && !defined(LZ4_FORCE_SW_BITCOUNT) + return (__builtin_ctzll( (U64)val ) >> 3); +# else + static const int DeBruijnBytePos[64] = { 0, 0, 0, 0, 0, 1, 1, 2, 0, 3, 1, 3, 1, 4, 2, 7, 0, 2, 3, 6, 1, 5, 3, 5, 1, 3, 4, 4, 2, 5, 6, 7, 7, 0, 1, 2, 3, 3, 4, 6, 2, 6, 5, 5, 3, 4, 5, 6, 7, 1, 2, 4, 6, 4, 4, 5, 7, 2, 6, 5, 7, 6, 7, 7 }; + return DeBruijnBytePos[((U64)((val & -(long long)val) * 0x0218A392CDABBD3FULL)) >> 58]; +# endif + } else /* 32 bits */ { +# if defined(_MSC_VER) && !defined(LZ4_FORCE_SW_BITCOUNT) + unsigned long r; + _BitScanForward( &r, (U32)val ); + return (int)(r >> 3); +# elif (defined(__clang__) || (LZ4_GCC_VERSION >= 304)) && !defined(LZ4_FORCE_SW_BITCOUNT) + return (__builtin_ctz( (U32)val ) >> 3); +# else + static const int DeBruijnBytePos[32] = { 0, 0, 3, 0, 3, 1, 3, 0, 3, 2, 2, 1, 3, 2, 0, 1, 3, 3, 1, 2, 2, 2, 2, 0, 3, 1, 2, 0, 1, 0, 1, 1 }; + return DeBruijnBytePos[((U32)((val & -(S32)val) * 0x077CB531U)) >> 27]; +# endif + } + } else /* Big Endian CPU */ { + if ( LZ4_64bits() ) { +# if defined(_MSC_VER) && defined(_WIN64) && !defined(LZ4_FORCE_SW_BITCOUNT) + unsigned long r = 0; + _BitScanReverse64( &r, val ); + return (unsigned)(r >> 3); +# elif (defined(__clang__) || (LZ4_GCC_VERSION >= 304)) && !defined(LZ4_FORCE_SW_BITCOUNT) + return (__builtin_clzll( (U64)val ) >> 3); +# else + unsigned r; + if ( !(val >> 32) ) { r = 4; } else { r = 0; val >>= 32; } + if ( !(val >> 16) ) { r += 2; val >>= 8; } else { val >>= 24; } + r += (!val); + return r; +# endif + } else /* 32 bits */ { +# if defined(_MSC_VER) && !defined(LZ4_FORCE_SW_BITCOUNT) + unsigned long r = 0; + _BitScanReverse( &r, (unsigned long)val ); + return (unsigned)(r >> 3); +# elif (defined(__clang__) || (LZ4_GCC_VERSION >= 304)) && !defined(LZ4_FORCE_SW_BITCOUNT) + return (__builtin_clz( (U32)val ) >> 3); +# else + unsigned r; + if ( !(val >> 16) ) { r = 2; val >>= 8; } else { r = 0; val >>= 24; } + r += (!val); + return r; +# endif + } + } + } + + static unsigned LZ4_count( const BYTE* pIn, const BYTE* pMatch, const BYTE* pInLimit ) + { + const BYTE* const pStart = pIn; + + while ( likely( pIn compression run slower on incompressible data */ + + + /*-************************************ + * Local Structures and types + **************************************/ + typedef struct + { + U32 hashTable[HASH_SIZE_U32]; + U32 currentOffset; + U32 initCheck; + const BYTE* dictionary; + BYTE* bufferStart; /* obsolete, used for slideInputBuffer */ + U32 dictSize; + } LZ4_stream_t_internal; + + typedef enum { notLimited = 0, limitedOutput = 1 } limitedOutput_directive; + typedef enum { byPtr, byU32, byU16 } tableType_t; + + typedef enum { noDict = 0, withPrefix64k, usingExtDict } dict_directive; + typedef enum { noDictIssue = 0, dictSmall } dictIssue_directive; + + typedef enum { endOnOutputSize = 0, endOnInputSize = 1 } endCondition_directive; + typedef enum { full = 0, partial = 1 } earlyEnd_directive; + + + /*-************************************ + * Local Utils + **************************************/ + int LZ4_versionNumber( void ) { return LZ4_VERSION_NUMBER; } + int LZ4_compressBound( int isize ) { return LZ4_COMPRESSBOUND( isize ); } + int LZ4_sizeofState() { return LZ4_STREAMSIZE; } + + + /*-****************************** + * Compression functions + ********************************/ + static U32 LZ4_hashSequence( U32 sequence, tableType_t const tableType ) + { + if ( tableType == byU16 ) + return (((sequence) * 2654435761U) >> ((MINMATCH * 8) - (LZ4_HASHLOG + 1))); + else + return (((sequence) * 2654435761U) >> ((MINMATCH * 8) - LZ4_HASHLOG)); + } + + static const U64 prime5bytes = 889523592379ULL; + static U32 LZ4_hashSequence64( size_t sequence, tableType_t const tableType ) + { + const U32 hashLog = (tableType == byU16) ? LZ4_HASHLOG + 1 : LZ4_HASHLOG; + const U32 hashMask = (1 << hashLog) - 1; + return ((sequence * prime5bytes) >> (40 - hashLog)) & hashMask; + } + + static U32 LZ4_hashSequenceT( size_t sequence, tableType_t const tableType ) + { + if ( LZ4_64bits() ) + return LZ4_hashSequence64( sequence, tableType ); + return LZ4_hashSequence( (U32)sequence, tableType ); + } + + static U32 LZ4_hashPosition( const void* p, tableType_t tableType ) { return LZ4_hashSequenceT( LZ4_read_ARCH( p ), tableType ); } + + static void LZ4_putPositionOnHash( const BYTE* p, U32 h, void* tableBase, tableType_t const tableType, const BYTE* srcBase ) + { + switch ( tableType ) { + case byPtr: { const BYTE** hashTable = (const BYTE**)tableBase; hashTable[h] = p; return; } + case byU32: { U32* hashTable = (U32*)tableBase; hashTable[h] = (U32)(p - srcBase); return; } + case byU16: { U16* hashTable = (U16*)tableBase; hashTable[h] = (U16)(p - srcBase); return; } + } + } + + static void LZ4_putPosition( const BYTE* p, void* tableBase, tableType_t tableType, const BYTE* srcBase ) + { + U32 const h = LZ4_hashPosition( p, tableType ); + LZ4_putPositionOnHash( p, h, tableBase, tableType, srcBase ); + } + + static const BYTE* LZ4_getPositionOnHash( U32 h, void* tableBase, tableType_t tableType, const BYTE* srcBase ) + { + if ( tableType == byPtr ) { const BYTE** hashTable = (const BYTE**)tableBase; return hashTable[h]; } + if ( tableType == byU32 ) { const U32* const hashTable = (U32*)tableBase; return hashTable[h] + srcBase; } + { const U16* const hashTable = (U16*)tableBase; return hashTable[h] + srcBase; } /* default, to ensure a return */ + } + + static const BYTE* LZ4_getPosition( const BYTE* p, void* tableBase, tableType_t tableType, const BYTE* srcBase ) + { + U32 const h = LZ4_hashPosition( p, tableType ); + return LZ4_getPositionOnHash( h, tableBase, tableType, srcBase ); + } + + + /** LZ4_compress_generic() : + inlined, to ensure branches are decided at compilation time */ + FORCE_INLINE int LZ4_compress_generic( + void* const ctx, + const char* const source, + char* const dest, + const int inputSize, + const int maxOutputSize, + const limitedOutput_directive outputLimited, + const tableType_t tableType, + const dict_directive dict, + const dictIssue_directive dictIssue, + const U32 acceleration ) + { + LZ4_stream_t_internal* const dictPtr = (LZ4_stream_t_internal*)ctx; + + const BYTE* ip = (const BYTE*)source; + const BYTE* base; + const BYTE* lowLimit; + const BYTE* const lowRefLimit = ip - dictPtr->dictSize; + const BYTE* const dictionary = dictPtr->dictionary; + const BYTE* const dictEnd = dictionary + dictPtr->dictSize; + const size_t dictDelta = dictEnd - (const BYTE*)source; + const BYTE* anchor = (const BYTE*)source; + const BYTE* const iend = ip + inputSize; + const BYTE* const mflimit = iend - MFLIMIT; + const BYTE* const matchlimit = iend - LASTLITERALS; + + BYTE* op = (BYTE*)dest; + BYTE* const olimit = op + maxOutputSize; + + U32 forwardH; + size_t refDelta = 0; + + /* Init conditions */ + if ( (U32)inputSize > (U32)LZ4_MAX_INPUT_SIZE ) return 0; /* Unsupported inputSize, too large (or negative) */ + switch ( dict ) { + case noDict: + default: + base = (const BYTE*)source; + lowLimit = (const BYTE*)source; + break; + case withPrefix64k: + base = (const BYTE*)source - dictPtr->currentOffset; + lowLimit = (const BYTE*)source - dictPtr->dictSize; + break; + case usingExtDict: + base = (const BYTE*)source - dictPtr->currentOffset; + lowLimit = (const BYTE*)source; + break; + } + if ( (tableType == byU16) && (inputSize >= LZ4_64Klimit) ) return 0; /* Size too large (not within 64K limit) */ + if ( inputSize> LZ4_skipTrigger); + + if ( unlikely( forwardIp > mflimit ) ) goto _last_literals; + + match = LZ4_getPositionOnHash( h, ctx, tableType, base ); + if ( dict == usingExtDict ) { + if ( match < (const BYTE*)source ) { + refDelta = dictDelta; + lowLimit = dictionary; + } else { + refDelta = 0; + lowLimit = (const BYTE*)source; + } + } + forwardH = LZ4_hashPosition( forwardIp, tableType ); + LZ4_putPositionOnHash( ip, h, ctx, tableType, base ); + + } while ( ((dictIssue == dictSmall) ? (match < lowRefLimit) : 0) + || ((tableType == byU16) ? 0 : (match + MAX_DISTANCE < ip)) + || (LZ4_read32( match + refDelta ) != LZ4_read32( ip )) ); + } + + /* Catch up */ + while ( ((ip>anchor) & (match + refDelta > lowLimit)) && (unlikely( ip[-1] == match[refDelta - 1] )) ) { ip--; match--; } + + /* Encode Literals */ + { + unsigned const litLength = (unsigned)(ip - anchor); + token = op++; + if ( (outputLimited) && /* Check output buffer overflow */ + (unlikely( op + litLength + (2 + 1 + LASTLITERALS) + (litLength / 255) > olimit )) ) + return 0; + if ( litLength >= RUN_MASK ) { + int len = (int)litLength - RUN_MASK; + *token = (RUN_MASK << ML_BITS); + for ( ; len >= 255; len -= 255 ) *op++ = 255; + *op++ = (BYTE)len; + } else *token = (BYTE)(litLength << ML_BITS); + + /* Copy Literals */ + LZ4_wildCopy( op, anchor, op + litLength ); + op += litLength; + } + + _next_match: + /* Encode Offset */ + LZ4_writeLE16( op, (U16)(ip - match) ); op += 2; + + /* Encode MatchLength */ + { + unsigned matchCode; + + if ( (dict == usingExtDict) && (lowLimit == dictionary) ) { + const BYTE* limit; + match += refDelta; + limit = ip + (dictEnd - match); + if ( limit > matchlimit ) limit = matchlimit; + matchCode = LZ4_count( ip + MINMATCH, match + MINMATCH, limit ); + ip += MINMATCH + matchCode; + if ( ip == limit ) { + unsigned const more = LZ4_count( ip, (const BYTE*)source, matchlimit ); + matchCode += more; + ip += more; + } + } else { + matchCode = LZ4_count( ip + MINMATCH, match + MINMATCH, matchlimit ); + ip += MINMATCH + matchCode; + } + + if ( outputLimited && /* Check output buffer overflow */ + (unlikely( op + (1 + LASTLITERALS) + (matchCode >> 8) > olimit )) ) + return 0; + if ( matchCode >= ML_MASK ) { + *token += ML_MASK; + matchCode -= ML_MASK; + LZ4_write32( op, 0xFFFFFFFF ); + while ( matchCode >= 4 * 255 ) op += 4, LZ4_write32( op, 0xFFFFFFFF ), matchCode -= 4 * 255; + op += matchCode / 255; + *op++ = (BYTE)(matchCode % 255); + } else + *token += (BYTE)(matchCode); + } + + anchor = ip; + + /* Test end of chunk */ + if ( ip > mflimit ) break; + + /* Fill table */ + LZ4_putPosition( ip - 2, ctx, tableType, base ); + + /* Test next position */ + match = LZ4_getPosition( ip, ctx, tableType, base ); + if ( dict == usingExtDict ) { + if ( match < (const BYTE*)source ) { + refDelta = dictDelta; + lowLimit = dictionary; + } else { + refDelta = 0; + lowLimit = (const BYTE*)source; + } + } + LZ4_putPosition( ip, ctx, tableType, base ); + if ( ((dictIssue == dictSmall) ? (match >= lowRefLimit) : 1) + && (match + MAX_DISTANCE >= ip) + && (LZ4_read32( match + refDelta ) == LZ4_read32( ip )) ) { + token = op++; *token = 0; goto _next_match; + } + + /* Prepare next loop */ + forwardH = LZ4_hashPosition( ++ip, tableType ); + } + + _last_literals: + /* Encode Last Literals */ + { + const size_t lastRun = (size_t)(iend - anchor); + if ( (outputLimited) && /* Check output buffer overflow */ + ((op - (BYTE*)dest) + lastRun + 1 + ((lastRun + 255 - RUN_MASK) / 255) > (U32)maxOutputSize) ) + return 0; + if ( lastRun >= RUN_MASK ) { + size_t accumulator = lastRun - RUN_MASK; + *op++ = RUN_MASK << ML_BITS; + for ( ; accumulator >= 255; accumulator -= 255 ) *op++ = 255; + *op++ = (BYTE)accumulator; + } else { + *op++ = (BYTE)(lastRun << ML_BITS); + } + memcpy( op, anchor, lastRun ); + op += lastRun; + } + + /* End */ + return (int)(((char*)op) - dest); + } + + + int LZ4_compress_fast_extState( void* state, const char* source, char* dest, int inputSize, int maxOutputSize, int acceleration ) + { + LZ4_resetStream( (LZ4_stream_t*)state ); + if ( acceleration < 1 ) acceleration = ACCELERATION_DEFAULT; + + if ( maxOutputSize >= LZ4_compressBound( inputSize ) ) { + if ( inputSize < LZ4_64Klimit ) + return LZ4_compress_generic( state, source, dest, inputSize, 0, notLimited, byU16, noDict, noDictIssue, acceleration ); + else + return LZ4_compress_generic( state, source, dest, inputSize, 0, notLimited, LZ4_64bits() ? byU32 : byPtr, noDict, noDictIssue, acceleration ); + } else { + if ( inputSize < LZ4_64Klimit ) + return LZ4_compress_generic( state, source, dest, inputSize, maxOutputSize, limitedOutput, byU16, noDict, noDictIssue, acceleration ); + else + return LZ4_compress_generic( state, source, dest, inputSize, maxOutputSize, limitedOutput, LZ4_64bits() ? byU32 : byPtr, noDict, noDictIssue, acceleration ); + } + } + + + int LZ4_compress_fast( const char* source, char* dest, int inputSize, int maxOutputSize, int acceleration ) + { +#if (HEAPMODE) + void* ctxPtr = ALLOCATOR( 1, sizeof( LZ4_stream_t ) ); /* malloc-calloc always properly aligned */ +#else + LZ4_stream_t ctx; + void* ctxPtr = &ctx; +#endif + + int result = LZ4_compress_fast_extState( ctxPtr, source, dest, inputSize, maxOutputSize, acceleration ); + +#if (HEAPMODE) + FREEMEM( ctxPtr ); +#endif + return result; + } + + + int LZ4_compress_default( const char* source, char* dest, int inputSize, int maxOutputSize ) + { + return LZ4_compress_fast( source, dest, inputSize, maxOutputSize, 1 ); + } + + + /* hidden debug function */ + /* strangely enough, gcc generates faster code when this function is uncommented, even if unused */ + int LZ4_compress_fast_force( const char* source, char* dest, int inputSize, int maxOutputSize, int acceleration ) + { + LZ4_stream_t ctx; + LZ4_resetStream( &ctx ); + + if ( inputSize < LZ4_64Klimit ) + return LZ4_compress_generic( &ctx, source, dest, inputSize, maxOutputSize, limitedOutput, byU16, noDict, noDictIssue, acceleration ); + else + return LZ4_compress_generic( &ctx, source, dest, inputSize, maxOutputSize, limitedOutput, LZ4_64bits() ? byU32 : byPtr, noDict, noDictIssue, acceleration ); + } + + + /*-****************************** + * *_destSize() variant + ********************************/ + + static int LZ4_compress_destSize_generic( + void* const ctx, + const char* const src, + char* const dst, + int* const srcSizePtr, + const int targetDstSize, + const tableType_t tableType ) + { + const BYTE* ip = (const BYTE*)src; + const BYTE* base = (const BYTE*)src; + const BYTE* lowLimit = (const BYTE*)src; + const BYTE* anchor = ip; + const BYTE* const iend = ip + *srcSizePtr; + const BYTE* const mflimit = iend - MFLIMIT; + const BYTE* const matchlimit = iend - LASTLITERALS; + + BYTE* op = (BYTE*)dst; + BYTE* const oend = op + targetDstSize; + BYTE* const oMaxLit = op + targetDstSize - 2 /* offset */ - 8 /* because 8+MINMATCH==MFLIMIT */ - 1 /* token */; + BYTE* const oMaxMatch = op + targetDstSize - (LASTLITERALS + 1 /* token */); + BYTE* const oMaxSeq = oMaxLit - 1 /* token */; + + U32 forwardH; + + + /* Init conditions */ + if ( targetDstSize < 1 ) return 0; /* Impossible to store anything */ + if ( (U32)*srcSizePtr >( U32 )LZ4_MAX_INPUT_SIZE ) return 0; /* Unsupported input size, too large (or negative) */ + if ( (tableType == byU16) && (*srcSizePtr >= LZ4_64Klimit) ) return 0; /* Size too large (not within 64K limit) */ + if ( *srcSizePtr> LZ4_skipTrigger); + + if ( unlikely( forwardIp > mflimit ) ) goto _last_literals; + + match = LZ4_getPositionOnHash( h, ctx, tableType, base ); + forwardH = LZ4_hashPosition( forwardIp, tableType ); + LZ4_putPositionOnHash( ip, h, ctx, tableType, base ); + + } while ( ((tableType == byU16) ? 0 : (match + MAX_DISTANCE < ip)) + || (LZ4_read32( match ) != LZ4_read32( ip )) ); + } + + /* Catch up */ + while ( (ip>anchor) && (match > lowLimit) && (unlikely( ip[-1] == match[-1] )) ) { ip--; match--; } + + /* Encode Literal length */ + { + unsigned litLength = (unsigned)(ip - anchor); + token = op++; + if ( op + ((litLength + 240) / 255) + litLength > oMaxLit ) { + /* Not enough space for a last match */ + op--; + goto _last_literals; + } + if ( litLength >= RUN_MASK ) { + unsigned len = litLength - RUN_MASK; + *token = (RUN_MASK << ML_BITS); + for ( ; len >= 255; len -= 255 ) *op++ = 255; + *op++ = (BYTE)len; + } else *token = (BYTE)(litLength << ML_BITS); + + /* Copy Literals */ + LZ4_wildCopy( op, anchor, op + litLength ); + op += litLength; + } + + _next_match: + /* Encode Offset */ + LZ4_writeLE16( op, (U16)(ip - match) ); op += 2; + + /* Encode MatchLength */ + { + size_t matchLength = LZ4_count( ip + MINMATCH, match + MINMATCH, matchlimit ); + + if ( op + ((matchLength + 240) / 255) > oMaxMatch ) { + /* Match description too long : reduce it */ + matchLength = (15 - 1) + (oMaxMatch - op) * 255; + } + ip += MINMATCH + matchLength; + + if ( matchLength >= ML_MASK ) { + *token += ML_MASK; + matchLength -= ML_MASK; + while ( matchLength >= 255 ) { matchLength -= 255; *op++ = 255; } + *op++ = (BYTE)matchLength; + } else *token += (BYTE)(matchLength); + } + + anchor = ip; + + /* Test end of block */ + if ( ip > mflimit ) break; + if ( op > oMaxSeq ) break; + + /* Fill table */ + LZ4_putPosition( ip - 2, ctx, tableType, base ); + + /* Test next position */ + match = LZ4_getPosition( ip, ctx, tableType, base ); + LZ4_putPosition( ip, ctx, tableType, base ); + if ( (match + MAX_DISTANCE >= ip) + && (LZ4_read32( match ) == LZ4_read32( ip )) ) { + token = op++; *token = 0; goto _next_match; + } + + /* Prepare next loop */ + forwardH = LZ4_hashPosition( ++ip, tableType ); + } + + _last_literals: + /* Encode Last Literals */ + { + size_t lastRunSize = (size_t)(iend - anchor); + if ( op + 1 /* token */ + ((lastRunSize + 240) / 255) /* litLength */ + lastRunSize /* literals */ > oend ) { + /* adapt lastRunSize to fill 'dst' */ + lastRunSize = (oend - op) - 1; + lastRunSize -= (lastRunSize + 240) / 255; + } + ip = anchor + lastRunSize; + + if ( lastRunSize >= RUN_MASK ) { + size_t accumulator = lastRunSize - RUN_MASK; + *op++ = RUN_MASK << ML_BITS; + for ( ; accumulator >= 255; accumulator -= 255 ) *op++ = 255; + *op++ = (BYTE)accumulator; + } else { + *op++ = (BYTE)(lastRunSize << ML_BITS); + } + memcpy( op, anchor, lastRunSize ); + op += lastRunSize; + } + + /* End */ + *srcSizePtr = (int)(((const char*)ip) - src); + return (int)(((char*)op) - dst); + } + + + static int LZ4_compress_destSize_extState( void* state, const char* src, char* dst, int* srcSizePtr, int targetDstSize ) + { + LZ4_resetStream( (LZ4_stream_t*)state ); + + if ( targetDstSize >= LZ4_compressBound( *srcSizePtr ) ) { /* compression success is guaranteed */ + return LZ4_compress_fast_extState( state, src, dst, *srcSizePtr, targetDstSize, 1 ); + } else { + if ( *srcSizePtr < LZ4_64Klimit ) + return LZ4_compress_destSize_generic( state, src, dst, srcSizePtr, targetDstSize, byU16 ); + else + return LZ4_compress_destSize_generic( state, src, dst, srcSizePtr, targetDstSize, LZ4_64bits() ? byU32 : byPtr ); + } + } + + + int LZ4_compress_destSize( const char* src, char* dst, int* srcSizePtr, int targetDstSize ) + { +#if (HEAPMODE) + void* ctx = ALLOCATOR( 1, sizeof( LZ4_stream_t ) ); /* malloc-calloc always properly aligned */ +#else + LZ4_stream_t ctxBody; + void* ctx = &ctxBody; +#endif + + int result = LZ4_compress_destSize_extState( ctx, src, dst, srcSizePtr, targetDstSize ); + +#if (HEAPMODE) + FREEMEM( ctx ); +#endif + return result; + } + + + + /*-****************************** + * Streaming functions + ********************************/ + + LZ4_stream_t* LZ4_createStream( void ) + { + LZ4_stream_t* lz4s = (LZ4_stream_t*)ALLOCATOR( 8, LZ4_STREAMSIZE_U64 ); + LZ4_STATIC_ASSERT( LZ4_STREAMSIZE >= sizeof( LZ4_stream_t_internal ) ); /* A compilation error here means LZ4_STREAMSIZE is not large enough */ + LZ4_resetStream( lz4s ); + return lz4s; + } + + void LZ4_resetStream( LZ4_stream_t* LZ4_stream ) + { + MEM_INIT( LZ4_stream, 0, sizeof( LZ4_stream_t ) ); + } + + int LZ4_freeStream( LZ4_stream_t* LZ4_stream ) + { + FREEMEM( LZ4_stream ); + return (0); + } + + +#define HASH_UNIT sizeof(size_t) + int LZ4_loadDict( LZ4_stream_t* LZ4_dict, const char* dictionary, int dictSize ) + { + LZ4_stream_t_internal* dict = (LZ4_stream_t_internal*)LZ4_dict; + const BYTE* p = (const BYTE*)dictionary; + const BYTE* const dictEnd = p + dictSize; + const BYTE* base; + + if ( (dict->initCheck) || (dict->currentOffset > 1 GB) ) /* Uninitialized structure, or reuse overflow */ + LZ4_resetStream( LZ4_dict ); + + if ( dictSize < (int)HASH_UNIT ) { + dict->dictionary = NULL; + dict->dictSize = 0; + return 0; + } + + if ( (dictEnd - p) > 64 KB ) p = dictEnd - 64 KB; + dict->currentOffset += 64 KB; + base = p - dict->currentOffset; + dict->dictionary = p; + dict->dictSize = (U32)(dictEnd - p); + dict->currentOffset += dict->dictSize; + + while ( p <= dictEnd - HASH_UNIT ) { + LZ4_putPosition( p, dict->hashTable, byU32, base ); + p += 3; + } + + return dict->dictSize; + } + + + static void LZ4_renormDictT( LZ4_stream_t_internal* LZ4_dict, const BYTE* src ) + { + if ( (LZ4_dict->currentOffset > 0x80000000) || + ((size_t)LZ4_dict->currentOffset > (size_t)src) ) { /* address space overflow */ + /* rescale hash table */ + U32 const delta = LZ4_dict->currentOffset - 64 KB; + const BYTE* dictEnd = LZ4_dict->dictionary + LZ4_dict->dictSize; + int i; + for ( i = 0; ihashTable[i] < delta ) LZ4_dict->hashTable[i] = 0; + else LZ4_dict->hashTable[i] -= delta; + } + LZ4_dict->currentOffset = 64 KB; + if ( LZ4_dict->dictSize > 64 KB ) LZ4_dict->dictSize = 64 KB; + LZ4_dict->dictionary = dictEnd - LZ4_dict->dictSize; + } + } + + + int LZ4_compress_fast_continue( LZ4_stream_t* LZ4_stream, const char* source, char* dest, int inputSize, int maxOutputSize, int acceleration ) + { + LZ4_stream_t_internal* streamPtr = (LZ4_stream_t_internal*)LZ4_stream; + const BYTE* const dictEnd = streamPtr->dictionary + streamPtr->dictSize; + + const BYTE* smallest = (const BYTE*)source; + if ( streamPtr->initCheck ) return 0; /* Uninitialized structure detected */ + if ( (streamPtr->dictSize>0) && (smallest>dictEnd) ) smallest = dictEnd; + LZ4_renormDictT( streamPtr, smallest ); + if ( acceleration < 1 ) acceleration = ACCELERATION_DEFAULT; + + /* Check overlapping input/dictionary space */ + { + const BYTE* sourceEnd = (const BYTE*)source + inputSize; + if ( (sourceEnd > streamPtr->dictionary) && (sourceEnd < dictEnd) ) { + streamPtr->dictSize = (U32)(dictEnd - sourceEnd); + if ( streamPtr->dictSize > 64 KB ) streamPtr->dictSize = 64 KB; + if ( streamPtr->dictSize < 4 ) streamPtr->dictSize = 0; + streamPtr->dictionary = dictEnd - streamPtr->dictSize; + } + } + + /* prefix mode : source data follows dictionary */ + if ( dictEnd == (const BYTE*)source ) { + int result; + if ( (streamPtr->dictSize < 64 KB) && (streamPtr->dictSize < streamPtr->currentOffset) ) + result = LZ4_compress_generic( LZ4_stream, source, dest, inputSize, maxOutputSize, limitedOutput, byU32, withPrefix64k, dictSmall, acceleration ); + else + result = LZ4_compress_generic( LZ4_stream, source, dest, inputSize, maxOutputSize, limitedOutput, byU32, withPrefix64k, noDictIssue, acceleration ); + streamPtr->dictSize += (U32)inputSize; + streamPtr->currentOffset += (U32)inputSize; + return result; + } + + /* external dictionary mode */ + { + int result; + if ( (streamPtr->dictSize < 64 KB) && (streamPtr->dictSize < streamPtr->currentOffset) ) + result = LZ4_compress_generic( LZ4_stream, source, dest, inputSize, maxOutputSize, limitedOutput, byU32, usingExtDict, dictSmall, acceleration ); + else + result = LZ4_compress_generic( LZ4_stream, source, dest, inputSize, maxOutputSize, limitedOutput, byU32, usingExtDict, noDictIssue, acceleration ); + streamPtr->dictionary = (const BYTE*)source; + streamPtr->dictSize = (U32)inputSize; + streamPtr->currentOffset += (U32)inputSize; + return result; + } + } + + + /* Hidden debug function, to force external dictionary mode */ + int LZ4_compress_forceExtDict( LZ4_stream_t* LZ4_dict, const char* source, char* dest, int inputSize ) + { + LZ4_stream_t_internal* streamPtr = (LZ4_stream_t_internal*)LZ4_dict; + int result; + const BYTE* const dictEnd = streamPtr->dictionary + streamPtr->dictSize; + + const BYTE* smallest = dictEnd; + if ( smallest > (const BYTE*)source ) smallest = (const BYTE*)source; + LZ4_renormDictT( (LZ4_stream_t_internal*)LZ4_dict, smallest ); + + result = LZ4_compress_generic( LZ4_dict, source, dest, inputSize, 0, notLimited, byU32, usingExtDict, noDictIssue, 1 ); + + streamPtr->dictionary = (const BYTE*)source; + streamPtr->dictSize = (U32)inputSize; + streamPtr->currentOffset += (U32)inputSize; + + return result; + } + + + int LZ4_saveDict( LZ4_stream_t* LZ4_dict, char* safeBuffer, int dictSize ) + { + LZ4_stream_t_internal* dict = (LZ4_stream_t_internal*)LZ4_dict; + const BYTE* previousDictEnd = dict->dictionary + dict->dictSize; + + if ( (U32)dictSize > 64 KB ) dictSize = 64 KB; /* useless to define a dictionary > 64 KB */ + if ( (U32)dictSize > dict->dictSize ) dictSize = dict->dictSize; + + memmove( safeBuffer, previousDictEnd - dictSize, dictSize ); + + dict->dictionary = (const BYTE*)safeBuffer; + dict->dictSize = (U32)dictSize; + + return dictSize; + } + + + + /*-***************************** + * Decompression functions + *******************************/ + /*! LZ4_decompress_generic() : + * This generic decompression function cover all use cases. + * It shall be instantiated several times, using different sets of directives + * Note that it is important this generic function is really inlined, + * in order to remove useless branches during compilation optimization. + */ + FORCE_INLINE int LZ4_decompress_generic( + const char* const source, + char* const dest, + int inputSize, + int outputSize, /* If endOnInput==endOnInputSize, this value is the max size of Output Buffer. */ + + int endOnInput, /* endOnOutputSize, endOnInputSize */ + int partialDecoding, /* full, partial */ + int targetOutputSize, /* only used if partialDecoding==partial */ + int dict, /* noDict, withPrefix64k, usingExtDict */ + const BYTE* const lowPrefix, /* == dest when no prefix */ + const BYTE* const dictStart, /* only if dict==usingExtDict */ + const size_t dictSize /* note : = 0 if noDict */ + ) + { + /* Local Variables */ + const BYTE* ip = (const BYTE*)source; + const BYTE* const iend = ip + inputSize; + + BYTE* op = (BYTE*)dest; + BYTE* const oend = op + outputSize; + BYTE* cpy; + BYTE* oexit = op + targetOutputSize; + const BYTE* const lowLimit = lowPrefix - dictSize; + + const BYTE* const dictEnd = (const BYTE*)dictStart + dictSize; + const unsigned dec32table[] = { 4, 1, 2, 1, 4, 4, 4, 4 }; + const int dec64table[] = { 0, 0, 0, -1, 0, 1, 2, 3 }; + + const int safeDecode = (endOnInput == endOnInputSize); + const int checkOffset = ((safeDecode) && (dictSize < (int)(64 KB))); + + + /* Special cases */ + if ( (partialDecoding) && (oexit > oend - MFLIMIT) ) oexit = oend - MFLIMIT; /* targetOutputSize too high => decode everything */ + if ( (endOnInput) && (unlikely( outputSize == 0 )) ) return ((inputSize == 1) && (*ip == 0)) ? 0 : -1; /* Empty output buffer */ + if ( (!endOnInput) && (unlikely( outputSize == 0 )) ) return (*ip == 0 ? 1 : -1); + + /* Main Loop : decode sequences */ + while ( 1 ) { + unsigned token; + size_t length; + const BYTE* match; + size_t offset; + + /* get literal length */ + token = *ip++; + if ( (length = (token >> ML_BITS)) == RUN_MASK ) { + unsigned s; + do { + s = *ip++; + length += s; + } while ( likely( endOnInput ? ip(partialDecoding ? oexit : oend - MFLIMIT)) || (ip + length>iend - (2 + 1 + LASTLITERALS)))) + || ((!endOnInput) && (cpy>oend - WILDCOPYLENGTH)) ) { + if ( partialDecoding ) { + if ( cpy > oend ) goto _output_error; /* Error : write attempt beyond end of output buffer */ + if ( (endOnInput) && (ip + length > iend) ) goto _output_error; /* Error : read attempt beyond end of input buffer */ + } else { + if ( (!endOnInput) && (cpy != oend) ) goto _output_error; /* Error : block decoding must stop exactly there */ + if ( (endOnInput) && ((ip + length != iend) || (cpy > oend)) ) goto _output_error; /* Error : input must be consumed */ + } + memcpy( op, ip, length ); + ip += length; + op += length; + break; /* Necessarily EOF, due to parsing restrictions */ + } + LZ4_wildCopy( op, ip, cpy ); + ip += length; op = cpy; + + /* get offset */ + offset = LZ4_readLE16( ip ); ip += 2; + match = op - offset; + if ( (checkOffset) && (unlikely( match < lowLimit )) ) goto _output_error; /* Error : offset outside buffers */ + + /* get matchlength */ + length = token & ML_MASK; + if ( length == ML_MASK ) { + unsigned s; + do { + s = *ip++; + if ( (endOnInput) && (ip > iend - LASTLITERALS) ) goto _output_error; + length += s; + } while ( s == 255 ); + if ( (safeDecode) && unlikely( (size_t)(op + length)<(size_t)op ) ) goto _output_error; /* overflow detection */ + } + length += MINMATCH; + + /* check external dictionary */ + if ( (dict == usingExtDict) && (match < lowPrefix) ) { + if ( unlikely( op + length > oend - LASTLITERALS ) ) goto _output_error; /* doesn't respect parsing restriction */ + + if ( length <= (size_t)(lowPrefix - match) ) { + /* match can be copied as a single segment from external dictionary */ + memmove( op, dictEnd - (lowPrefix - match), length ); + op += length; + } else { + /* match encompass external dictionary and current block */ + size_t const copySize = (size_t)(lowPrefix - match); + size_t const restSize = length - copySize; + memcpy( op, dictEnd - copySize, copySize ); + op += copySize; + if ( restSize > (size_t)(op - lowPrefix) ) { /* overlap copy */ + BYTE* const endOfMatch = op + restSize; + const BYTE* copyFrom = lowPrefix; + while ( op < endOfMatch ) *op++ = *copyFrom++; + } else { + memcpy( op, lowPrefix, restSize ); + op += restSize; + } + } + continue; + } + + /* copy match within block */ + cpy = op + length; + if ( unlikely( offset<8 ) ) { + const int dec64 = dec64table[offset]; + op[0] = match[0]; + op[1] = match[1]; + op[2] = match[2]; + op[3] = match[3]; + match += dec32table[offset]; + memcpy( op + 4, match, 4 ); + match -= dec64; + } else { LZ4_copy8( op, match ); match += 8; } + op += 8; + + if ( unlikely( cpy>oend - 12 ) ) { + BYTE* const oCopyLimit = oend - (WILDCOPYLENGTH - 1); + if ( cpy > oend - LASTLITERALS ) goto _output_error; /* Error : last LASTLITERALS bytes must be literals (uncompressed) */ + if ( op < oCopyLimit ) { + LZ4_wildCopy( op, match, oCopyLimit ); + match += oCopyLimit - op; + op = oCopyLimit; + } + while ( op16 ) LZ4_wildCopy( op + 8, match + 8, cpy ); + } + op = cpy; /* correction */ + } + + /* end of decoding */ + if ( endOnInput ) + return (int)(((char*)op) - dest); /* Nb of output bytes decoded */ + else + return (int)(((const char*)ip) - source); /* Nb of input bytes read */ + + /* Overflow error detected */ + _output_error: + return (int)(-(((const char*)ip) - source)) - 1; + } + + + int LZ4_decompress_safe( const char* source, char* dest, int compressedSize, int maxDecompressedSize ) + { + return LZ4_decompress_generic( source, dest, compressedSize, maxDecompressedSize, endOnInputSize, full, 0, noDict, (BYTE*)dest, NULL, 0 ); + } + + int LZ4_decompress_safe_partial( const char* source, char* dest, int compressedSize, int targetOutputSize, int maxDecompressedSize ) + { + return LZ4_decompress_generic( source, dest, compressedSize, maxDecompressedSize, endOnInputSize, partial, targetOutputSize, noDict, (BYTE*)dest, NULL, 0 ); + } + + int LZ4_decompress_fast( const char* source, char* dest, int originalSize ) + { + return LZ4_decompress_generic( source, dest, 0, originalSize, endOnOutputSize, full, 0, withPrefix64k, (BYTE*)(dest - 64 KB), NULL, 64 KB ); + } + + + /*===== streaming decompression functions =====*/ + + typedef struct + { + const BYTE* externalDict; + size_t extDictSize; + const BYTE* prefixEnd; + size_t prefixSize; + } LZ4_streamDecode_t_internal; + + /* + * If you prefer dynamic allocation methods, + * LZ4_createStreamDecode() + * provides a pointer (void*) towards an initialized LZ4_streamDecode_t structure. + */ + LZ4_streamDecode_t* LZ4_createStreamDecode( void ) + { + LZ4_streamDecode_t* lz4s = (LZ4_streamDecode_t*)ALLOCATOR( 1, sizeof( LZ4_streamDecode_t ) ); + return lz4s; + } + + int LZ4_freeStreamDecode( LZ4_streamDecode_t* LZ4_stream ) + { + FREEMEM( LZ4_stream ); + return 0; + } + + /*! + * LZ4_setStreamDecode() : + * Use this function to instruct where to find the dictionary. + * This function is not necessary if previous data is still available where it was decoded. + * Loading a size of 0 is allowed (same effect as no dictionary). + * Return : 1 if OK, 0 if error + */ + int LZ4_setStreamDecode( LZ4_streamDecode_t* LZ4_streamDecode, const char* dictionary, int dictSize ) + { + LZ4_streamDecode_t_internal* lz4sd = (LZ4_streamDecode_t_internal*)LZ4_streamDecode; + lz4sd->prefixSize = (size_t)dictSize; + lz4sd->prefixEnd = (const BYTE*)dictionary + dictSize; + lz4sd->externalDict = NULL; + lz4sd->extDictSize = 0; + return 1; + } + + /* + *_continue() : + These decoding functions allow decompression of multiple blocks in "streaming" mode. + Previously decoded blocks must still be available at the memory position where they were decoded. + If it's not possible, save the relevant part of decoded data into a safe buffer, + and indicate where it stands using LZ4_setStreamDecode() + */ + int LZ4_decompress_safe_continue( LZ4_streamDecode_t* LZ4_streamDecode, const char* source, char* dest, int compressedSize, int maxOutputSize ) + { + LZ4_streamDecode_t_internal* lz4sd = (LZ4_streamDecode_t_internal*)LZ4_streamDecode; + int result; + + if ( lz4sd->prefixEnd == (BYTE*)dest ) { + result = LZ4_decompress_generic( source, dest, compressedSize, maxOutputSize, + endOnInputSize, full, 0, + usingExtDict, lz4sd->prefixEnd - lz4sd->prefixSize, lz4sd->externalDict, lz4sd->extDictSize ); + if ( result <= 0 ) return result; + lz4sd->prefixSize += result; + lz4sd->prefixEnd += result; + } else { + lz4sd->extDictSize = lz4sd->prefixSize; + lz4sd->externalDict = lz4sd->prefixEnd - lz4sd->extDictSize; + result = LZ4_decompress_generic( source, dest, compressedSize, maxOutputSize, + endOnInputSize, full, 0, + usingExtDict, (BYTE*)dest, lz4sd->externalDict, lz4sd->extDictSize ); + if ( result <= 0 ) return result; + lz4sd->prefixSize = result; + lz4sd->prefixEnd = (BYTE*)dest + result; + } + + return result; + } + + int LZ4_decompress_fast_continue( LZ4_streamDecode_t* LZ4_streamDecode, const char* source, char* dest, int originalSize ) + { + LZ4_streamDecode_t_internal* lz4sd = (LZ4_streamDecode_t_internal*)LZ4_streamDecode; + int result; + + if ( lz4sd->prefixEnd == (BYTE*)dest ) { + result = LZ4_decompress_generic( source, dest, 0, originalSize, + endOnOutputSize, full, 0, + usingExtDict, lz4sd->prefixEnd - lz4sd->prefixSize, lz4sd->externalDict, lz4sd->extDictSize ); + if ( result <= 0 ) return result; + lz4sd->prefixSize += originalSize; + lz4sd->prefixEnd += originalSize; + } else { + lz4sd->extDictSize = lz4sd->prefixSize; + lz4sd->externalDict = (BYTE*)dest - lz4sd->extDictSize; + result = LZ4_decompress_generic( source, dest, 0, originalSize, + endOnOutputSize, full, 0, + usingExtDict, (BYTE*)dest, lz4sd->externalDict, lz4sd->extDictSize ); + if ( result <= 0 ) return result; + lz4sd->prefixSize = originalSize; + lz4sd->prefixEnd = (BYTE*)dest + originalSize; + } + + return result; + } + + + /* + Advanced decoding functions : + *_usingDict() : + These decoding functions work the same as "_continue" ones, + the dictionary must be explicitly provided within parameters + */ + + FORCE_INLINE int LZ4_decompress_usingDict_generic( const char* source, char* dest, int compressedSize, int maxOutputSize, int safe, const char* dictStart, int dictSize ) + { + if ( dictSize == 0 ) + return LZ4_decompress_generic( source, dest, compressedSize, maxOutputSize, safe, full, 0, noDict, (BYTE*)dest, NULL, 0 ); + if ( dictStart + dictSize == dest ) { + if ( dictSize >= (int)(64 KB - 1) ) + return LZ4_decompress_generic( source, dest, compressedSize, maxOutputSize, safe, full, 0, withPrefix64k, (BYTE*)dest - 64 KB, NULL, 0 ); + return LZ4_decompress_generic( source, dest, compressedSize, maxOutputSize, safe, full, 0, noDict, (BYTE*)dest - dictSize, NULL, 0 ); + } + return LZ4_decompress_generic( source, dest, compressedSize, maxOutputSize, safe, full, 0, usingExtDict, (BYTE*)dest, (const BYTE*)dictStart, dictSize ); + } + + int LZ4_decompress_safe_usingDict( const char* source, char* dest, int compressedSize, int maxOutputSize, const char* dictStart, int dictSize ) + { + return LZ4_decompress_usingDict_generic( source, dest, compressedSize, maxOutputSize, 1, dictStart, dictSize ); + } + + int LZ4_decompress_fast_usingDict( const char* source, char* dest, int originalSize, const char* dictStart, int dictSize ) + { + return LZ4_decompress_usingDict_generic( source, dest, 0, originalSize, 0, dictStart, dictSize ); + } + + /* debug function */ + int LZ4_decompress_safe_forceExtDict( const char* source, char* dest, int compressedSize, int maxOutputSize, const char* dictStart, int dictSize ) + { + return LZ4_decompress_generic( source, dest, compressedSize, maxOutputSize, endOnInputSize, full, 0, usingExtDict, (BYTE*)dest, (const BYTE*)dictStart, dictSize ); + } + + + /*=************************************************* + * Obsolete Functions + ***************************************************/ + /* obsolete compression functions */ + int LZ4_compress_limitedOutput( const char* source, char* dest, int inputSize, int maxOutputSize ) { return LZ4_compress_default( source, dest, inputSize, maxOutputSize ); } + int LZ4_compress( const char* source, char* dest, int inputSize ) { return LZ4_compress_default( source, dest, inputSize, LZ4_compressBound( inputSize ) ); } + int LZ4_compress_limitedOutput_withState( void* state, const char* src, char* dst, int srcSize, int dstSize ) { return LZ4_compress_fast_extState( state, src, dst, srcSize, dstSize, 1 ); } + int LZ4_compress_withState( void* state, const char* src, char* dst, int srcSize ) { return LZ4_compress_fast_extState( state, src, dst, srcSize, LZ4_compressBound( srcSize ), 1 ); } + int LZ4_compress_limitedOutput_continue( LZ4_stream_t* LZ4_stream, const char* src, char* dst, int srcSize, int maxDstSize ) { return LZ4_compress_fast_continue( LZ4_stream, src, dst, srcSize, maxDstSize, 1 ); } + int LZ4_compress_continue( LZ4_stream_t* LZ4_stream, const char* source, char* dest, int inputSize ) { return LZ4_compress_fast_continue( LZ4_stream, source, dest, inputSize, LZ4_compressBound( inputSize ), 1 ); } + + /* + These function names are deprecated and should no longer be used. + They are only provided here for compatibility with older user programs. + - LZ4_uncompress is totally equivalent to LZ4_decompress_fast + - LZ4_uncompress_unknownOutputSize is totally equivalent to LZ4_decompress_safe + */ + int LZ4_uncompress( const char* source, char* dest, int outputSize ) { return LZ4_decompress_fast( source, dest, outputSize ); } + int LZ4_uncompress_unknownOutputSize( const char* source, char* dest, int isize, int maxOutputSize ) { return LZ4_decompress_safe( source, dest, isize, maxOutputSize ); } + + + /* Obsolete Streaming functions */ + + int LZ4_sizeofStreamState() { return LZ4_STREAMSIZE; } + + static void LZ4_init( LZ4_stream_t_internal* lz4ds, BYTE* base ) + { + MEM_INIT( lz4ds, 0, LZ4_STREAMSIZE ); + lz4ds->bufferStart = base; + } + + int LZ4_resetStreamState( void* state, char* inputBuffer ) + { + if ( (((size_t)state) & 3) != 0 ) return 1; /* Error : pointer is not aligned on 4-bytes boundary */ + LZ4_init( (LZ4_stream_t_internal*)state, (BYTE*)inputBuffer ); + return 0; + } + + void* LZ4_create( char* inputBuffer ) + { + void* lz4ds = ALLOCATOR( 8, LZ4_STREAMSIZE_U64 ); + LZ4_init( (LZ4_stream_t_internal*)lz4ds, (BYTE*)inputBuffer ); + return lz4ds; + } + + char* LZ4_slideInputBuffer( void* LZ4_Data ) + { + LZ4_stream_t_internal* ctx = (LZ4_stream_t_internal*)LZ4_Data; + int dictSize = LZ4_saveDict( (LZ4_stream_t*)LZ4_Data, (char*)ctx->bufferStart, 64 KB ); + return (char*)(ctx->bufferStart + dictSize); + } + + /* Obsolete streaming decompression functions */ + + int LZ4_decompress_safe_withPrefix64k( const char* source, char* dest, int compressedSize, int maxOutputSize ) + { + return LZ4_decompress_generic( source, dest, compressedSize, maxOutputSize, endOnInputSize, full, 0, withPrefix64k, (BYTE*)dest - 64 KB, NULL, 64 KB ); + } + + int LZ4_decompress_fast_withPrefix64k( const char* source, char* dest, int originalSize ) + { + return LZ4_decompress_generic( source, dest, 0, originalSize, endOnOutputSize, full, 0, withPrefix64k, (BYTE*)dest - 64 KB, NULL, 64 KB ); + } + + +/* + LZ4 HC - High Compression Mode of LZ4 + Copyright (C) 2011-2015, Yann Collet. + + BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + You can contact the author at : + - LZ4 source repository : https://github.com/Cyan4973/lz4 + - LZ4 public forum : https://groups.google.com/forum/#!forum/lz4c +*/ + + /* ************************************* + * Local Constants + ***************************************/ +#define DICTIONARY_LOGSIZE 16 +#define MAXD (1<> ((MINMATCH*8)-HASH_LOG)) + //#define DELTANEXTU16(p) chainTable[(p) & MAXD_MASK] /* flexible, MAXD dependent */ +#define DELTANEXTU16(p) chainTable[(U16)(p)] /* faster */ + + static U32 LZ4HC_hashPtr( const void* ptr ) { return HASH_FUNCTION( LZ4_read32( ptr ) ); } + + + + /************************************** + * HC Compression + **************************************/ + static void LZ4HC_init( LZ4HC_Data_Structure* hc4, const BYTE* start ) + { + MEM_INIT( (void*)hc4->hashTable, 0, sizeof( hc4->hashTable ) ); + MEM_INIT( hc4->chainTable, 0xFF, sizeof( hc4->chainTable ) ); + hc4->nextToUpdate = 64 KB; + hc4->base = start - 64 KB; + hc4->end = start; + hc4->dictBase = start - 64 KB; + hc4->dictLimit = 64 KB; + hc4->lowLimit = 64 KB; + } + + + /* Update chains up to ip (excluded) */ + FORCE_INLINE void LZ4HC_Insert( LZ4HC_Data_Structure* hc4, const BYTE* ip ) + { + U16* const chainTable = hc4->chainTable; + U32* const hashTable = hc4->hashTable; + const BYTE* const base = hc4->base; + U32 const target = (U32)(ip - base); + U32 idx = hc4->nextToUpdate; + + while ( idx < target ) { + U32 const h = LZ4HC_hashPtr( base + idx ); + size_t delta = idx - hashTable[h]; + if ( delta>MAX_DISTANCE ) delta = MAX_DISTANCE; + DELTANEXTU16( idx ) = (U16)delta; + hashTable[h] = idx; + idx++; + } + + hc4->nextToUpdate = target; + } + + + FORCE_INLINE int LZ4HC_InsertAndFindBestMatch( LZ4HC_Data_Structure* hc4, /* Index table will be updated */ + const BYTE* ip, const BYTE* const iLimit, + const BYTE** matchpos, + const int maxNbAttempts ) + { + U16* const chainTable = hc4->chainTable; + U32* const HashTable = hc4->hashTable; + const BYTE* const base = hc4->base; + const BYTE* const dictBase = hc4->dictBase; + const U32 dictLimit = hc4->dictLimit; + const U32 lowLimit = (hc4->lowLimit + 64 KB > (U32)(ip - base)) ? hc4->lowLimit : (U32)(ip - base) - (64 KB - 1); + U32 matchIndex; + const BYTE* match; + int nbAttempts = maxNbAttempts; + size_t ml = 0; + + /* HC4 match finder */ + LZ4HC_Insert( hc4, ip ); + matchIndex = HashTable[LZ4HC_hashPtr( ip )]; + + while ( (matchIndex >= lowLimit) && (nbAttempts) ) { + nbAttempts--; + if ( matchIndex >= dictLimit ) { + match = base + matchIndex; + if ( *(match + ml) == *(ip + ml) + && (LZ4_read32( match ) == LZ4_read32( ip )) ) { + size_t const mlt = LZ4_count( ip + MINMATCH, match + MINMATCH, iLimit ) + MINMATCH; + if ( mlt > ml ) { ml = mlt; *matchpos = match; } + } + } else { + match = dictBase + matchIndex; + if ( LZ4_read32( match ) == LZ4_read32( ip ) ) { + size_t mlt; + const BYTE* vLimit = ip + (dictLimit - matchIndex); + if ( vLimit > iLimit ) vLimit = iLimit; + mlt = LZ4_count( ip + MINMATCH, match + MINMATCH, vLimit ) + MINMATCH; + if ( (ip + mlt == vLimit) && (vLimit < iLimit) ) + mlt += LZ4_count( ip + mlt, base + dictLimit, iLimit ); + if ( mlt > ml ) { ml = mlt; *matchpos = base + matchIndex; } /* virtual matchpos */ + } + } + matchIndex -= DELTANEXTU16( matchIndex ); + } + + return (int)ml; + } + + + FORCE_INLINE int LZ4HC_InsertAndGetWiderMatch( + LZ4HC_Data_Structure* hc4, + const BYTE* const ip, + const BYTE* const iLowLimit, + const BYTE* const iHighLimit, + int longest, + const BYTE** matchpos, + const BYTE** startpos, + const int maxNbAttempts ) + { + U16* const chainTable = hc4->chainTable; + U32* const HashTable = hc4->hashTable; + const BYTE* const base = hc4->base; + const U32 dictLimit = hc4->dictLimit; + const BYTE* const lowPrefixPtr = base + dictLimit; + const U32 lowLimit = (hc4->lowLimit + 64 KB > (U32)(ip - base)) ? hc4->lowLimit : (U32)(ip - base) - (64 KB - 1); + const BYTE* const dictBase = hc4->dictBase; + U32 matchIndex; + int nbAttempts = maxNbAttempts; + int delta = (int)(ip - iLowLimit); + + + /* First Match */ + LZ4HC_Insert( hc4, ip ); + matchIndex = HashTable[LZ4HC_hashPtr( ip )]; + + while ( (matchIndex >= lowLimit) && (nbAttempts) ) { + nbAttempts--; + if ( matchIndex >= dictLimit ) { + const BYTE* matchPtr = base + matchIndex; + if ( *(iLowLimit + longest) == *(matchPtr - delta + longest) ) { + if ( LZ4_read32( matchPtr ) == LZ4_read32( ip ) ) { + int mlt = MINMATCH + LZ4_count( ip + MINMATCH, matchPtr + MINMATCH, iHighLimit ); + int back = 0; + + while ( (ip + back > iLowLimit) + && (matchPtr + back > lowPrefixPtr) + && (ip[back - 1] == matchPtr[back - 1]) ) + back--; + + mlt -= back; + + if ( mlt > longest ) { + longest = (int)mlt; + *matchpos = matchPtr + back; + *startpos = ip + back; + } + } + } + } else { + const BYTE* matchPtr = dictBase + matchIndex; + if ( LZ4_read32( matchPtr ) == LZ4_read32( ip ) ) { + size_t mlt; + int back = 0; + const BYTE* vLimit = ip + (dictLimit - matchIndex); + if ( vLimit > iHighLimit ) vLimit = iHighLimit; + mlt = LZ4_count( ip + MINMATCH, matchPtr + MINMATCH, vLimit ) + MINMATCH; + if ( (ip + mlt == vLimit) && (vLimit < iHighLimit) ) + mlt += LZ4_count( ip + mlt, base + dictLimit, iHighLimit ); + while ( (ip + back > iLowLimit) && (matchIndex + back > lowLimit) && (ip[back - 1] == matchPtr[back - 1]) ) back--; + mlt -= back; + if ( (int)mlt > longest ) { longest = (int)mlt; *matchpos = base + matchIndex + back; *startpos = ip + back; } + } + } + matchIndex -= DELTANEXTU16( matchIndex ); + } + + return longest; + } + + +#define LZ4HC_DEBUG 0 +#if LZ4HC_DEBUG + static unsigned debug = 0; +#endif + + FORCE_INLINE int LZ4HC_encodeSequence( + const BYTE** ip, + BYTE** op, + const BYTE** anchor, + int matchLength, + const BYTE* const match, + limitedOutput_directive limitedOutputBuffer, + BYTE* oend ) + { + int length; + BYTE* token; + +#if LZ4HC_DEBUG + if ( debug ) printf( "literal : %u -- match : %u -- offset : %u\n", (U32)(*ip - *anchor), (U32)matchLength, (U32)(*ip - match) ); +#endif + + /* Encode Literal length */ + length = (int)(*ip - *anchor); + token = (*op)++; + if ( (limitedOutputBuffer) && ((*op + (length >> 8) + length + (2 + 1 + LASTLITERALS)) > oend) ) return 1; /* Check output limit */ + if ( length >= (int)RUN_MASK ) { int len; *token = (RUN_MASK << ML_BITS); len = length - RUN_MASK; for ( ; len > 254; len -= 255 ) *(*op)++ = 255; *(*op)++ = (BYTE)len; } else *token = (BYTE)(length << ML_BITS); + + /* Copy Literals */ + LZ4_wildCopy( *op, *anchor, (*op) + length ); + *op += length; + + /* Encode Offset */ + LZ4_writeLE16( *op, (U16)(*ip - match) ); *op += 2; + + /* Encode MatchLength */ + length = (int)(matchLength - MINMATCH); + if ( (limitedOutputBuffer) && (*op + (length >> 8) + (1 + LASTLITERALS) > oend) ) return 1; /* Check output limit */ + if ( length >= (int)ML_MASK ) { + *token += ML_MASK; + length -= ML_MASK; + for ( ; length > 509; length -= 510 ) { *(*op)++ = 255; *(*op)++ = 255; } + if ( length > 254 ) { length -= 255; *(*op)++ = 255; } + *(*op)++ = (BYTE)length; + } else { + *token += (BYTE)(length); + } + + /* Prepare next loop */ + *ip += matchLength; + *anchor = *ip; + + return 0; + } + + + static int LZ4HC_compress_generic( + void* ctxvoid, + const char* source, + char* dest, + int inputSize, + int maxOutputSize, + int compressionLevel, + limitedOutput_directive limit + ) + { + LZ4HC_Data_Structure* ctx = (LZ4HC_Data_Structure*)ctxvoid; + const BYTE* ip = (const BYTE*)source; + const BYTE* anchor = ip; + const BYTE* const iend = ip + inputSize; + const BYTE* const mflimit = iend - MFLIMIT; + const BYTE* const matchlimit = (iend - LASTLITERALS); + + BYTE* op = (BYTE*)dest; + BYTE* const oend = op + maxOutputSize; + + unsigned maxNbAttempts; + int ml, ml2, ml3, ml0; + const BYTE* ref = NULL; + const BYTE* start2 = NULL; + const BYTE* ref2 = NULL; + const BYTE* start3 = NULL; + const BYTE* ref3 = NULL; + const BYTE* start0; + const BYTE* ref0; + + /* init */ + if ( compressionLevel > LZ4HC_MAX_CLEVEL ) compressionLevel = LZ4HC_MAX_CLEVEL; + if ( compressionLevel < 1 ) compressionLevel = LZ4HC_DEFAULT_CLEVEL; + maxNbAttempts = 1 << (compressionLevel - 1); + ctx->end += inputSize; + + ip++; + + /* Main Loop */ + while ( ip < mflimit ) { + ml = LZ4HC_InsertAndFindBestMatch( ctx, ip, matchlimit, (&ref), maxNbAttempts ); + if ( !ml ) { ip++; continue; } + + /* saved, in case we would skip too much */ + start0 = ip; + ref0 = ref; + ml0 = ml; + + _Search2: + if ( ip + ml < mflimit ) + ml2 = LZ4HC_InsertAndGetWiderMatch( ctx, ip + ml - 2, ip + 1, matchlimit, ml, &ref2, &start2, maxNbAttempts ); + else ml2 = ml; + + if ( ml2 == ml ) { /* No better match */ + if ( LZ4HC_encodeSequence( &ip, &op, &anchor, ml, ref, limit, oend ) ) return 0; + continue; + } + + if ( start0 < ip ) { + if ( start2 < ip + ml0 ) { /* empirical */ + ip = start0; + ref = ref0; + ml = ml0; + } + } + + /* Here, start0==ip */ + if ( (start2 - ip) < 3 ) { /* First Match too small : removed */ + ml = ml2; + ip = start2; + ref = ref2; + goto _Search2; + } + + _Search3: + /* + * Currently we have : + * ml2 > ml1, and + * ip1+3 <= ip2 (usually < ip1+ml1) + */ + if ( (start2 - ip) < OPTIMAL_ML ) { + int correction; + int new_ml = ml; + if ( new_ml > OPTIMAL_ML ) new_ml = OPTIMAL_ML; + if ( ip + new_ml > start2 + ml2 - MINMATCH ) new_ml = (int)(start2 - ip) + ml2 - MINMATCH; + correction = new_ml - (int)(start2 - ip); + if ( correction > 0 ) { + start2 += correction; + ref2 += correction; + ml2 -= correction; + } + } + /* Now, we have start2 = ip+new_ml, with new_ml = min(ml, OPTIMAL_ML=18) */ + + if ( start2 + ml2 < mflimit ) + ml3 = LZ4HC_InsertAndGetWiderMatch( ctx, start2 + ml2 - 3, start2, matchlimit, ml2, &ref3, &start3, maxNbAttempts ); + else ml3 = ml2; + + if ( ml3 == ml2 ) { /* No better match : 2 sequences to encode */ + /* ip & ref are known; Now for ml */ + if ( start2 < ip + ml ) ml = (int)(start2 - ip); + /* Now, encode 2 sequences */ + if ( LZ4HC_encodeSequence( &ip, &op, &anchor, ml, ref, limit, oend ) ) return 0; + ip = start2; + if ( LZ4HC_encodeSequence( &ip, &op, &anchor, ml2, ref2, limit, oend ) ) return 0; + continue; + } + + if ( start3 < ip + ml + 3 ) { /* Not enough space for match 2 : remove it */ + if ( start3 >= (ip + ml) ) { /* can write Seq1 immediately ==> Seq2 is removed, so Seq3 becomes Seq1 */ + if ( start2 < ip + ml ) { + int correction = (int)(ip + ml - start2); + start2 += correction; + ref2 += correction; + ml2 -= correction; + if ( ml2 < MINMATCH ) { + start2 = start3; + ref2 = ref3; + ml2 = ml3; + } + } + + if ( LZ4HC_encodeSequence( &ip, &op, &anchor, ml, ref, limit, oend ) ) return 0; + ip = start3; + ref = ref3; + ml = ml3; + + start0 = start2; + ref0 = ref2; + ml0 = ml2; + goto _Search2; + } + + start2 = start3; + ref2 = ref3; + ml2 = ml3; + goto _Search3; + } + + /* + * OK, now we have 3 ascending matches; let's write at least the first one + * ip & ref are known; Now for ml + */ + if ( start2 < ip + ml ) { + if ( (start2 - ip) < (int)ML_MASK ) { + int correction; + if ( ml > OPTIMAL_ML ) ml = OPTIMAL_ML; + if ( ip + ml > start2 + ml2 - MINMATCH ) ml = (int)(start2 - ip) + ml2 - MINMATCH; + correction = ml - (int)(start2 - ip); + if ( correction > 0 ) { + start2 += correction; + ref2 += correction; + ml2 -= correction; + } + } else { + ml = (int)(start2 - ip); + } + } + if ( LZ4HC_encodeSequence( &ip, &op, &anchor, ml, ref, limit, oend ) ) return 0; + + ip = start2; + ref = ref2; + ml = ml2; + + start2 = start3; + ref2 = ref3; + ml2 = ml3; + + goto _Search3; + } + + /* Encode Last Literals */ + { int lastRun = (int)(iend - anchor); + if ( (limit) && (((char*)op - dest) + lastRun + 1 + ((lastRun + 255 - RUN_MASK) / 255) > (U32)maxOutputSize) ) return 0; /* Check output limit */ + if ( lastRun >= (int)RUN_MASK ) { *op++ = (RUN_MASK << ML_BITS); lastRun -= RUN_MASK; for ( ; lastRun > 254; lastRun -= 255 ) *op++ = 255; *op++ = (BYTE)lastRun; } else *op++ = (BYTE)(lastRun << ML_BITS); + memcpy( op, anchor, iend - anchor ); + op += iend - anchor; + } + + /* End */ + return (int)(((char*)op) - dest); + } + + + int LZ4_sizeofStateHC( void ) { return sizeof( LZ4HC_Data_Structure ); } + + int LZ4_compress_HC_extStateHC( void* state, const char* src, char* dst, int srcSize, int maxDstSize, int compressionLevel ) + { + if ( ((size_t)(state)&(sizeof( void* ) - 1)) != 0 ) return 0; /* Error : state is not aligned for pointers (32 or 64 bits) */ + LZ4HC_init( (LZ4HC_Data_Structure*)state, (const BYTE*)src ); + if ( maxDstSize < LZ4_compressBound( srcSize ) ) + return LZ4HC_compress_generic( state, src, dst, srcSize, maxDstSize, compressionLevel, limitedOutput ); + else + return LZ4HC_compress_generic( state, src, dst, srcSize, maxDstSize, compressionLevel, notLimited ); + } + + int LZ4_compress_HC( const char* src, char* dst, int srcSize, int maxDstSize, int compressionLevel ) + { +#if LZ4HC_HEAPMODE==1 + LZ4HC_Data_Structure* statePtr = malloc( sizeof( LZ4HC_Data_Structure ) ); +#else + LZ4HC_Data_Structure state; + LZ4HC_Data_Structure* const statePtr = &state; +#endif + int cSize = LZ4_compress_HC_extStateHC( statePtr, src, dst, srcSize, maxDstSize, compressionLevel ); +#if LZ4HC_HEAPMODE==1 + free( statePtr ); +#endif + return cSize; + } + + + + /************************************** + * Streaming Functions + **************************************/ + /* allocation */ + LZ4_streamHC_t* LZ4_createStreamHC( void ) { return (LZ4_streamHC_t*)malloc( sizeof( LZ4_streamHC_t ) ); } + int LZ4_freeStreamHC( LZ4_streamHC_t* LZ4_streamHCPtr ) { free( LZ4_streamHCPtr ); return 0; } + + + /* initialization */ + void LZ4_resetStreamHC( LZ4_streamHC_t* LZ4_streamHCPtr, int compressionLevel ) + { + LZ4_STATIC_ASSERT( sizeof( LZ4HC_Data_Structure ) <= sizeof( LZ4_streamHC_t ) ); /* if compilation fails here, LZ4_STREAMHCSIZE must be increased */ + ((LZ4HC_Data_Structure*)LZ4_streamHCPtr)->base = NULL; + ((LZ4HC_Data_Structure*)LZ4_streamHCPtr)->compressionLevel = (unsigned)compressionLevel; + } + + int LZ4_loadDictHC( LZ4_streamHC_t* LZ4_streamHCPtr, const char* dictionary, int dictSize ) + { + LZ4HC_Data_Structure* ctxPtr = (LZ4HC_Data_Structure*)LZ4_streamHCPtr; + if ( dictSize > 64 KB ) { + dictionary += dictSize - 64 KB; + dictSize = 64 KB; + } + LZ4HC_init( ctxPtr, (const BYTE*)dictionary ); + if ( dictSize >= 4 ) LZ4HC_Insert( ctxPtr, (const BYTE*)dictionary + (dictSize - 3) ); + ctxPtr->end = (const BYTE*)dictionary + dictSize; + return dictSize; + } + + + /* compression */ + + static void LZ4HC_setExternalDict( LZ4HC_Data_Structure* ctxPtr, const BYTE* newBlock ) + { + if ( ctxPtr->end >= ctxPtr->base + 4 ) LZ4HC_Insert( ctxPtr, ctxPtr->end - 3 ); /* Referencing remaining dictionary content */ + /* Only one memory segment for extDict, so any previous extDict is lost at this stage */ + ctxPtr->lowLimit = ctxPtr->dictLimit; + ctxPtr->dictLimit = (U32)(ctxPtr->end - ctxPtr->base); + ctxPtr->dictBase = ctxPtr->base; + ctxPtr->base = newBlock - ctxPtr->dictLimit; + ctxPtr->end = newBlock; + ctxPtr->nextToUpdate = ctxPtr->dictLimit; /* match referencing will resume from there */ + } + + static int LZ4_compressHC_continue_generic( LZ4HC_Data_Structure* ctxPtr, + const char* source, char* dest, + int inputSize, int maxOutputSize, limitedOutput_directive limit ) + { + /* auto-init if forgotten */ + if ( ctxPtr->base == NULL ) LZ4HC_init( ctxPtr, (const BYTE*)source ); + + /* Check overflow */ + if ( (size_t)(ctxPtr->end - ctxPtr->base) > 2 GB ) { + size_t dictSize = (size_t)(ctxPtr->end - ctxPtr->base) - ctxPtr->dictLimit; + if ( dictSize > 64 KB ) dictSize = 64 KB; + LZ4_loadDictHC( (LZ4_streamHC_t*)ctxPtr, (const char*)(ctxPtr->end) - dictSize, (int)dictSize ); + } + + /* Check if blocks follow each other */ + if ( (const BYTE*)source != ctxPtr->end ) LZ4HC_setExternalDict( ctxPtr, (const BYTE*)source ); + + /* Check overlapping input/dictionary space */ + { + const BYTE* sourceEnd = (const BYTE*)source + inputSize; + const BYTE* const dictBegin = ctxPtr->dictBase + ctxPtr->lowLimit; + const BYTE* const dictEnd = ctxPtr->dictBase + ctxPtr->dictLimit; + if ( (sourceEnd > dictBegin) && ((const BYTE*)source < dictEnd) ) { + if ( sourceEnd > dictEnd ) sourceEnd = dictEnd; + ctxPtr->lowLimit = (U32)(sourceEnd - ctxPtr->dictBase); + if ( ctxPtr->dictLimit - ctxPtr->lowLimit < 4 ) ctxPtr->lowLimit = ctxPtr->dictLimit; + } + } + + return LZ4HC_compress_generic( ctxPtr, source, dest, inputSize, maxOutputSize, ctxPtr->compressionLevel, limit ); + } + + int LZ4_compress_HC_continue( LZ4_streamHC_t* LZ4_streamHCPtr, const char* source, char* dest, int inputSize, int maxOutputSize ) + { + if ( maxOutputSize < LZ4_compressBound( inputSize ) ) + return LZ4_compressHC_continue_generic( (LZ4HC_Data_Structure*)LZ4_streamHCPtr, source, dest, inputSize, maxOutputSize, limitedOutput ); + else + return LZ4_compressHC_continue_generic( (LZ4HC_Data_Structure*)LZ4_streamHCPtr, source, dest, inputSize, maxOutputSize, notLimited ); + } + + + /* dictionary saving */ + + int LZ4_saveDictHC( LZ4_streamHC_t* LZ4_streamHCPtr, char* safeBuffer, int dictSize ) + { + LZ4HC_Data_Structure* const streamPtr = (LZ4HC_Data_Structure*)LZ4_streamHCPtr; + int const prefixSize = (int)(streamPtr->end - (streamPtr->base + streamPtr->dictLimit)); + if ( dictSize > 64 KB ) dictSize = 64 KB; + if ( dictSize < 4 ) dictSize = 0; + if ( dictSize > prefixSize ) dictSize = prefixSize; + memmove( safeBuffer, streamPtr->end - dictSize, dictSize ); + { + U32 const endIndex = (U32)(streamPtr->end - streamPtr->base); + streamPtr->end = (const BYTE*)safeBuffer + dictSize; + streamPtr->base = streamPtr->end - endIndex; + streamPtr->dictLimit = endIndex - dictSize; + streamPtr->lowLimit = endIndex - dictSize; + if ( streamPtr->nextToUpdate < streamPtr->dictLimit ) streamPtr->nextToUpdate = streamPtr->dictLimit; + } + return dictSize; + } + + + /*********************************** + * Deprecated Functions + ***********************************/ + /* Deprecated compression functions */ + /* These functions are planned to start generate warnings by r131 approximately */ + int LZ4_compressHC( const char* src, char* dst, int srcSize ) { return LZ4_compress_HC( src, dst, srcSize, LZ4_compressBound( srcSize ), 0 ); } + int LZ4_compressHC_limitedOutput( const char* src, char* dst, int srcSize, int maxDstSize ) { return LZ4_compress_HC( src, dst, srcSize, maxDstSize, 0 ); } + int LZ4_compressHC2( const char* src, char* dst, int srcSize, int cLevel ) { return LZ4_compress_HC( src, dst, srcSize, LZ4_compressBound( srcSize ), cLevel ); } + int LZ4_compressHC2_limitedOutput( const char* src, char* dst, int srcSize, int maxDstSize, int cLevel ) { return LZ4_compress_HC( src, dst, srcSize, maxDstSize, cLevel ); } + int LZ4_compressHC_withStateHC( void* state, const char* src, char* dst, int srcSize ) { return LZ4_compress_HC_extStateHC( state, src, dst, srcSize, LZ4_compressBound( srcSize ), 0 ); } + int LZ4_compressHC_limitedOutput_withStateHC( void* state, const char* src, char* dst, int srcSize, int maxDstSize ) { return LZ4_compress_HC_extStateHC( state, src, dst, srcSize, maxDstSize, 0 ); } + int LZ4_compressHC2_withStateHC( void* state, const char* src, char* dst, int srcSize, int cLevel ) { return LZ4_compress_HC_extStateHC( state, src, dst, srcSize, LZ4_compressBound( srcSize ), cLevel ); } + int LZ4_compressHC2_limitedOutput_withStateHC( void* state, const char* src, char* dst, int srcSize, int maxDstSize, int cLevel ) { return LZ4_compress_HC_extStateHC( state, src, dst, srcSize, maxDstSize, cLevel ); } + int LZ4_compressHC_continue( LZ4_streamHC_t* ctx, const char* src, char* dst, int srcSize ) { return LZ4_compress_HC_continue( ctx, src, dst, srcSize, LZ4_compressBound( srcSize ) ); } + int LZ4_compressHC_limitedOutput_continue( LZ4_streamHC_t* ctx, const char* src, char* dst, int srcSize, int maxDstSize ) { return LZ4_compress_HC_continue( ctx, src, dst, srcSize, maxDstSize ); } + + + /* Deprecated streaming functions */ + /* These functions currently generate deprecation warnings */ + int LZ4_sizeofStreamStateHC( void ) { return LZ4_STREAMHCSIZE; } + + int LZ4_resetStreamStateHC( void* state, char* inputBuffer ) + { + if ( (((size_t)state) & (sizeof( void* ) - 1)) != 0 ) return 1; /* Error : pointer is not aligned for pointer (32 or 64 bits) */ + LZ4HC_init( (LZ4HC_Data_Structure*)state, (const BYTE*)inputBuffer ); + ((LZ4HC_Data_Structure*)state)->inputBuffer = (BYTE*)inputBuffer; + return 0; + } + + void* LZ4_createHC( char* inputBuffer ) + { + void* hc4 = ALLOCATOR( 1, sizeof( LZ4HC_Data_Structure ) ); + if ( hc4 == NULL ) return NULL; /* not enough memory */ + LZ4HC_init( (LZ4HC_Data_Structure*)hc4, (const BYTE*)inputBuffer ); + ((LZ4HC_Data_Structure*)hc4)->inputBuffer = (BYTE*)inputBuffer; + return hc4; + } + + int LZ4_freeHC( void* LZ4HC_Data ) + { + FREEMEM( LZ4HC_Data ); + return (0); + } + + int LZ4_compressHC2_continue( void* LZ4HC_Data, const char* source, char* dest, int inputSize, int compressionLevel ) + { + return LZ4HC_compress_generic( LZ4HC_Data, source, dest, inputSize, 0, compressionLevel, notLimited ); + } + + int LZ4_compressHC2_limitedOutput_continue( void* LZ4HC_Data, const char* source, char* dest, int inputSize, int maxOutputSize, int compressionLevel ) + { + return LZ4HC_compress_generic( LZ4HC_Data, source, dest, inputSize, maxOutputSize, compressionLevel, limitedOutput ); + } + + char* LZ4_slideInputBufferHC( void* LZ4HC_Data ) + { + LZ4HC_Data_Structure* hc4 = (LZ4HC_Data_Structure*)LZ4HC_Data; + int dictSize = LZ4_saveDictHC( (LZ4_streamHC_t*)LZ4HC_Data, (char*)(hc4->inputBuffer), 64 KB ); + return (char*)(hc4->inputBuffer + dictSize); + } + +/* +LZ4 auto-framing library +Copyright (C) 2011-2016, Yann Collet. + +BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +You can contact the author at : +- LZ4 homepage : http://www.lz4.org +- LZ4 source repository : https://github.com/Cyan4973/lz4 +*/ + +/* LZ4F is a stand-alone API to create LZ4-compressed Frames +* in full conformance with specification v1.5.0 +* All related operations, including memory management, are handled by the library. +* */ + +/*-************************************ +* Constants +**************************************/ + +#define _1BIT 0x01 +#define _2BITS 0x03 +#define _3BITS 0x07 +#define _4BITS 0x0F +#define _8BITS 0xFF + +#define LZ4F_MAGIC_SKIPPABLE_START 0x184D2A50U +#define LZ4F_MAGICNUMBER 0x184D2204U +#define LZ4F_BLOCKUNCOMPRESSED_FLAG 0x80000000U +#define LZ4F_BLOCKSIZEID_DEFAULT LZ4F_max64KB + +static const size_t minFHSize = 7; +static const size_t maxFHSize = 15; +static const size_t BHSize = 4; + + +/*-************************************ +* Structures and local types +**************************************/ +typedef struct LZ4F_cctx_s +{ + LZ4F_preferences_t prefs; + U32 version; + U32 cStage; + size_t maxBlockSize; + size_t maxBufferSize; + BYTE* tmpBuff; + BYTE* tmpIn; + size_t tmpInSize; + U64 totalInSize; + XXH32_state_t xxh; + void* lz4CtxPtr; + U32 lz4CtxLevel; /* 0: unallocated; 1: LZ4_stream_t; 3: LZ4_streamHC_t */ +} LZ4F_cctx_t; + +typedef struct LZ4F_dctx_s +{ + LZ4F_frameInfo_t frameInfo; + U32 version; + U32 dStage; + U64 frameRemainingSize; + size_t maxBlockSize; + size_t maxBufferSize; + const BYTE* srcExpect; + BYTE* tmpIn; + size_t tmpInSize; + size_t tmpInTarget; + BYTE* tmpOutBuffer; + const BYTE* dict; + size_t dictSize; + BYTE* tmpOut; + size_t tmpOutSize; + size_t tmpOutStart; + XXH32_state_t xxh; + BYTE header[16]; +} LZ4F_dctx_t; + + +/*-************************************ +* Error management +**************************************/ +#define LZ4F_GENERATE_STRING(STRING) #STRING, +static const char* LZ4F_errorStrings[] = { LZ4F_LIST_ERRORS(LZ4F_GENERATE_STRING) }; + + +unsigned LZ4F_isError(LZ4F_errorCode_t code) +{ + return (code > (LZ4F_errorCode_t)(-LZ4F_ERROR_maxCode)); +} + +const char* LZ4F_getErrorName(LZ4F_errorCode_t code) +{ + static const char* codeError = "Unspecified error code"; + if (LZ4F_isError(code)) return LZ4F_errorStrings[-(int)(code)]; + return codeError; +} + + +/*-************************************ +* Private functions +**************************************/ +static size_t LZ4F_getBlockSize(unsigned blockSizeID) +{ + static const size_t blockSizes[4] = { 64 KB, 256 KB, 1 MB, 4 MB }; + + if (blockSizeID == 0) blockSizeID = LZ4F_BLOCKSIZEID_DEFAULT; + blockSizeID -= 4; + if (blockSizeID > 3) return (size_t)-LZ4F_ERROR_maxBlockSize_invalid; + return blockSizes[blockSizeID]; +} + + +static BYTE LZ4F_headerChecksum (const void* header, size_t length) +{ + U32 xxh = XXH32(header, length, 0); + return (BYTE)(xxh >> 8); +} + + +/*-************************************ +* Simple compression functions +**************************************/ +static LZ4F_blockSizeID_t LZ4F_optimalBSID(const LZ4F_blockSizeID_t requestedBSID, const size_t srcSize) +{ + LZ4F_blockSizeID_t proposedBSID = LZ4F_max64KB; + size_t maxBlockSize = 64 KB; + while (requestedBSID > proposedBSID) + { + if (srcSize <= maxBlockSize) + return proposedBSID; + proposedBSID = (LZ4F_blockSizeID_t)((int)proposedBSID + 1); + maxBlockSize <<= 2; + } + return requestedBSID; +} + + +size_t LZ4F_compressFrameBound(size_t srcSize, const LZ4F_preferences_t* preferencesPtr) +{ + LZ4F_preferences_t prefs; + size_t headerSize; + size_t streamSize; + + if (preferencesPtr!=NULL) prefs = *preferencesPtr; + else memset(&prefs, 0, sizeof(prefs)); + + prefs.frameInfo.blockSizeID = LZ4F_optimalBSID(prefs.frameInfo.blockSizeID, srcSize); + prefs.autoFlush = 1; + + headerSize = maxFHSize; /* header size, including magic number and frame content size*/ + streamSize = LZ4F_compressBound(srcSize, &prefs); + + return headerSize + streamSize; +} + + +/*! LZ4F_compressFrame() : +* Compress an entire srcBuffer into a valid LZ4 frame, as defined by specification v1.5.0, in a single step. +* The most important rule is that dstBuffer MUST be large enough (dstMaxSize) to ensure compression completion even in worst case. +* You can get the minimum value of dstMaxSize by using LZ4F_compressFrameBound() +* If this condition is not respected, LZ4F_compressFrame() will fail (result is an errorCode) +* The LZ4F_preferences_t structure is optional : you can provide NULL as argument. All preferences will then be set to default. +* The result of the function is the number of bytes written into dstBuffer. +* The function outputs an error code if it fails (can be tested using LZ4F_isError()) +*/ +size_t LZ4F_compressFrame(void* dstBuffer, size_t dstMaxSize, const void* srcBuffer, size_t srcSize, const LZ4F_preferences_t* preferencesPtr) +{ + LZ4F_cctx_t cctxI; + LZ4_stream_t lz4ctx; + LZ4F_preferences_t prefs; + LZ4F_compressOptions_t options; + LZ4F_errorCode_t errorCode; + BYTE* const dstStart = (BYTE*) dstBuffer; + BYTE* dstPtr = dstStart; + BYTE* const dstEnd = dstStart + dstMaxSize; + + memset(&cctxI, 0, sizeof(cctxI)); /* works because no allocation */ + memset(&options, 0, sizeof(options)); + + cctxI.version = LZ4F_VERSION; + cctxI.maxBufferSize = 5 MB; /* mess with real buffer size to prevent allocation; works because autoflush==1 & stableSrc==1 */ + + if (preferencesPtr!=NULL) + prefs = *preferencesPtr; + else + memset(&prefs, 0, sizeof(prefs)); + if (prefs.frameInfo.contentSize != 0) + prefs.frameInfo.contentSize = (U64)srcSize; /* auto-correct content size if selected (!=0) */ + + if (prefs.compressionLevel < LZ4HC_MIN_CLEVEL) { + cctxI.lz4CtxPtr = &lz4ctx; + cctxI.lz4CtxLevel = 1; + } + + prefs.frameInfo.blockSizeID = LZ4F_optimalBSID(prefs.frameInfo.blockSizeID, srcSize); + prefs.autoFlush = 1; + if (srcSize <= LZ4F_getBlockSize(prefs.frameInfo.blockSizeID)) + prefs.frameInfo.blockMode = LZ4F_blockIndependent; /* no need for linked blocks */ + + options.stableSrc = 1; + + if (dstMaxSize < LZ4F_compressFrameBound(srcSize, &prefs)) + return (size_t)-LZ4F_ERROR_dstMaxSize_tooSmall; + + errorCode = LZ4F_compressBegin(&cctxI, dstBuffer, dstMaxSize, &prefs); /* write header */ + if (LZ4F_isError(errorCode)) return errorCode; + dstPtr += errorCode; /* header size */ + + errorCode = LZ4F_compressUpdate(&cctxI, dstPtr, dstEnd-dstPtr, srcBuffer, srcSize, &options); + if (LZ4F_isError(errorCode)) return errorCode; + dstPtr += errorCode; + + errorCode = LZ4F_compressEnd(&cctxI, dstPtr, dstEnd-dstPtr, &options); /* flush last block, and generate suffix */ + if (LZ4F_isError(errorCode)) return errorCode; + dstPtr += errorCode; + + if (prefs.compressionLevel >= LZ4HC_MIN_CLEVEL) /* no allocation necessary with lz4 fast */ + FREEMEM(cctxI.lz4CtxPtr); + + return (dstPtr - dstStart); +} + + +/*-********************************* +* Advanced compression functions +***********************************/ + +/* LZ4F_createCompressionContext() : +* The first thing to do is to create a compressionContext object, which will be used in all compression operations. +* This is achieved using LZ4F_createCompressionContext(), which takes as argument a version and an LZ4F_preferences_t structure. +* The version provided MUST be LZ4F_VERSION. It is intended to track potential version differences between different binaries. +* The function will provide a pointer to an allocated LZ4F_compressionContext_t object. +* If the result LZ4F_errorCode_t is not OK_NoError, there was an error during context creation. +* Object can release its memory using LZ4F_freeCompressionContext(); +*/ +LZ4F_errorCode_t LZ4F_createCompressionContext(LZ4F_compressionContext_t* LZ4F_compressionContextPtr, unsigned version) +{ + LZ4F_cctx_t* cctxPtr; + + cctxPtr = (LZ4F_cctx_t*)ALLOCATORF(sizeof(LZ4F_cctx_t)); + if (cctxPtr==NULL) return (LZ4F_errorCode_t)(-LZ4F_ERROR_allocation_failed); + + cctxPtr->version = version; + cctxPtr->cStage = 0; /* Next stage : write header */ + + *LZ4F_compressionContextPtr = (LZ4F_compressionContext_t)cctxPtr; + + return LZ4F_OK_NoError; +} + + +LZ4F_errorCode_t LZ4F_freeCompressionContext(LZ4F_compressionContext_t LZ4F_compressionContext) +{ + LZ4F_cctx_t* cctxPtr = (LZ4F_cctx_t*)LZ4F_compressionContext; + + if (cctxPtr != NULL) { /* null pointers can be safely provided to this function, like free() */ + FREEMEM(cctxPtr->lz4CtxPtr); + FREEMEM(cctxPtr->tmpBuff); + FREEMEM(LZ4F_compressionContext); + } + + return LZ4F_OK_NoError; +} + + +/*! LZ4F_compressBegin() : +* will write the frame header into dstBuffer. +* dstBuffer must be large enough to accommodate a header (dstMaxSize). Maximum header size is LZ4F_MAXHEADERFRAME_SIZE bytes. +* The result of the function is the number of bytes written into dstBuffer for the header +* or an error code (can be tested using LZ4F_isError()) +*/ +size_t LZ4F_compressBegin(LZ4F_compressionContext_t compressionContext, void* dstBuffer, size_t dstMaxSize, const LZ4F_preferences_t* preferencesPtr) +{ + LZ4F_preferences_t prefNull; + LZ4F_cctx_t* cctxPtr = (LZ4F_cctx_t*)compressionContext; + BYTE* const dstStart = (BYTE*)dstBuffer; + BYTE* dstPtr = dstStart; + BYTE* headerStart; + size_t requiredBuffSize; + + if (dstMaxSize < maxFHSize) return (size_t)-LZ4F_ERROR_dstMaxSize_tooSmall; + if (cctxPtr->cStage != 0) return (size_t)-LZ4F_ERROR_GENERIC; + memset(&prefNull, 0, sizeof(prefNull)); + if (preferencesPtr == NULL) preferencesPtr = &prefNull; + cctxPtr->prefs = *preferencesPtr; + + /* ctx Management */ + { U32 const tableID = (cctxPtr->prefs.compressionLevel < LZ4HC_MIN_CLEVEL) ? 1 : 2; /* 0:nothing ; 1:LZ4 table ; 2:HC tables */ + if (cctxPtr->lz4CtxLevel < tableID) { + FREEMEM(cctxPtr->lz4CtxPtr); + if (cctxPtr->prefs.compressionLevel < LZ4HC_MIN_CLEVEL) + cctxPtr->lz4CtxPtr = (void*)LZ4_createStream(); + else + cctxPtr->lz4CtxPtr = (void*)LZ4_createStreamHC(); + cctxPtr->lz4CtxLevel = tableID; + } + } + + /* Buffer Management */ + if (cctxPtr->prefs.frameInfo.blockSizeID == 0) cctxPtr->prefs.frameInfo.blockSizeID = LZ4F_BLOCKSIZEID_DEFAULT; + cctxPtr->maxBlockSize = LZ4F_getBlockSize(cctxPtr->prefs.frameInfo.blockSizeID); + + requiredBuffSize = cctxPtr->maxBlockSize + ((cctxPtr->prefs.frameInfo.blockMode == LZ4F_blockLinked) * 128 KB); + if (preferencesPtr->autoFlush) + requiredBuffSize = (cctxPtr->prefs.frameInfo.blockMode == LZ4F_blockLinked) * 64 KB; /* just needs dict */ + + if (cctxPtr->maxBufferSize < requiredBuffSize) { + cctxPtr->maxBufferSize = requiredBuffSize; + FREEMEM(cctxPtr->tmpBuff); + cctxPtr->tmpBuff = (BYTE*)ALLOCATORF(requiredBuffSize); + if (cctxPtr->tmpBuff == NULL) return (size_t)-LZ4F_ERROR_allocation_failed; + } + cctxPtr->tmpIn = cctxPtr->tmpBuff; + cctxPtr->tmpInSize = 0; + XXH32_reset( &(cctxPtr->xxh), 0 ); + if ( cctxPtr->prefs.compressionLevel < LZ4HC_MIN_CLEVEL ) + LZ4_resetStream( (LZ4_stream_t*)(cctxPtr->lz4CtxPtr) ); + else + LZ4_resetStreamHC( (LZ4_streamHC_t*)(cctxPtr->lz4CtxPtr), cctxPtr->prefs.compressionLevel ); + + /* Magic Number */ + LZ4F_writeLE32(dstPtr, LZ4F_MAGICNUMBER); + dstPtr += 4; + headerStart = dstPtr; + + /* FLG Byte */ + *dstPtr++ = (BYTE)(((1 & _2BITS) << 6) /* Version('01') */ + + ((cctxPtr->prefs.frameInfo.blockMode & _1BIT ) << 5) /* Block mode */ + + ((cctxPtr->prefs.frameInfo.contentChecksumFlag & _1BIT ) << 2) /* Frame checksum */ + + ((cctxPtr->prefs.frameInfo.contentSize > 0) << 3)); /* Frame content size */ + /* BD Byte */ + *dstPtr++ = (BYTE)((cctxPtr->prefs.frameInfo.blockSizeID & _3BITS) << 4); + /* Optional Frame content size field */ + if (cctxPtr->prefs.frameInfo.contentSize) { + LZ4F_writeLE64(dstPtr, cctxPtr->prefs.frameInfo.contentSize); + dstPtr += 8; + cctxPtr->totalInSize = 0; + } + /* CRC Byte */ + *dstPtr = LZ4F_headerChecksum(headerStart, dstPtr - headerStart); + dstPtr++; + + cctxPtr->cStage = 1; /* header written, now request input data block */ + + return (dstPtr - dstStart); +} + + +/* LZ4F_compressBound() : gives the size of Dst buffer given a srcSize to handle worst case situations. +* The LZ4F_frameInfo_t structure is optional : +* you can provide NULL as argument, preferences will then be set to cover worst case situations. +* */ +size_t LZ4F_compressBound(size_t srcSize, const LZ4F_preferences_t* preferencesPtr) +{ + LZ4F_preferences_t prefsNull; + memset(&prefsNull, 0, sizeof(prefsNull)); + prefsNull.frameInfo.contentChecksumFlag = LZ4F_contentChecksumEnabled; /* worst case */ + { const LZ4F_preferences_t* prefsPtr = (preferencesPtr==NULL) ? &prefsNull : preferencesPtr; + LZ4F_blockSizeID_t bid = prefsPtr->frameInfo.blockSizeID; + size_t blockSize = LZ4F_getBlockSize(bid); + unsigned nbBlocks = (unsigned)(srcSize / blockSize) + 1; + size_t lastBlockSize = prefsPtr->autoFlush ? srcSize % blockSize : blockSize; + size_t blockInfo = 4; /* default, without block CRC option */ + size_t frameEnd = 4 + (prefsPtr->frameInfo.contentChecksumFlag*4); + + return (blockInfo * nbBlocks) + (blockSize * (nbBlocks-1)) + lastBlockSize + frameEnd;; + } +} + + +typedef int (*compressFunc_t)(void* ctx, const char* src, char* dst, int srcSize, int dstSize, int level); + +static size_t LZ4F_compressBlock(void* dst, const void* src, size_t srcSize, compressFunc_t compress, void* lz4ctx, int level) +{ + /* compress one block */ + BYTE* cSizePtr = (BYTE*)dst; + U32 cSize; + cSize = (U32)compress(lz4ctx, (const char*)src, (char*)(cSizePtr+4), (int)(srcSize), (int)(srcSize-1), level); + LZ4F_writeLE32(cSizePtr, cSize); + if (cSize == 0) { /* compression failed */ + cSize = (U32)srcSize; + LZ4F_writeLE32(cSizePtr, cSize + LZ4F_BLOCKUNCOMPRESSED_FLAG); + memcpy(cSizePtr+4, src, srcSize); + } + return cSize + 4; +} + + +static int LZ4F_localLZ4_compress_limitedOutput_withState(void* ctx, const char* src, char* dst, int srcSize, int dstSize, int level) +{ + (void) level; + return LZ4_compress_limitedOutput_withState(ctx, src, dst, srcSize, dstSize); +} + +static int LZ4F_localLZ4_compress_limitedOutput_continue( void* ctx, const char* src, char* dst, int srcSize, int dstSize, int level ) +{ + (void)level; + return LZ4_compress_limitedOutput_continue( (LZ4_stream_t*)ctx, src, dst, srcSize, dstSize ); +} + +static int LZ4F_localLZ4_compressHC_limitedOutput_continue( void* ctx, const char* src, char* dst, int srcSize, int dstSize, int level ) +{ + (void)level; + return LZ4_compress_HC_continue( (LZ4_streamHC_t*)ctx, src, dst, srcSize, dstSize ); +} + +static compressFunc_t LZ4F_selectCompression( LZ4F_blockMode_t blockMode, int level ) +{ + if ( level < LZ4HC_MIN_CLEVEL ) { + if ( blockMode == LZ4F_blockIndependent ) return LZ4F_localLZ4_compress_limitedOutput_withState; + return LZ4F_localLZ4_compress_limitedOutput_continue; + } + if ( blockMode == LZ4F_blockIndependent ) return LZ4_compress_HC_extStateHC; + return LZ4F_localLZ4_compressHC_limitedOutput_continue; +} + +static int LZ4F_localSaveDict( LZ4F_cctx_t* cctxPtr ) +{ + if ( cctxPtr->prefs.compressionLevel < LZ4HC_MIN_CLEVEL ) + return LZ4_saveDict( (LZ4_stream_t*)(cctxPtr->lz4CtxPtr), (char*)(cctxPtr->tmpBuff), 64 KB ); + return LZ4_saveDictHC( (LZ4_streamHC_t*)(cctxPtr->lz4CtxPtr), (char*)(cctxPtr->tmpBuff), 64 KB ); +} + +typedef enum { notDone, fromTmpBuffer, fromSrcBuffer } LZ4F_lastBlockStatus; + +/*! LZ4F_compressUpdate() : +* LZ4F_compressUpdate() can be called repetitively to compress as much data as necessary. +* The most important rule is that dstBuffer MUST be large enough (dstMaxSize) to ensure compression completion even in worst case. +* If this condition is not respected, LZ4F_compress() will fail (result is an errorCode) +* You can get the minimum value of dstMaxSize by using LZ4F_compressBound() +* The LZ4F_compressOptions_t structure is optional : you can provide NULL as argument. +* The result of the function is the number of bytes written into dstBuffer : it can be zero, meaning input data was just buffered. +* The function outputs an error code if it fails (can be tested using LZ4F_isError()) +*/ +size_t LZ4F_compressUpdate(LZ4F_compressionContext_t compressionContext, void* dstBuffer, size_t dstMaxSize, const void* srcBuffer, size_t srcSize, const LZ4F_compressOptions_t* compressOptionsPtr) +{ + LZ4F_compressOptions_t cOptionsNull; + LZ4F_cctx_t* cctxPtr = (LZ4F_cctx_t*)compressionContext; + size_t blockSize = cctxPtr->maxBlockSize; + const BYTE* srcPtr = (const BYTE*)srcBuffer; + const BYTE* const srcEnd = srcPtr + srcSize; + BYTE* const dstStart = (BYTE*)dstBuffer; + BYTE* dstPtr = dstStart; + LZ4F_lastBlockStatus lastBlockCompressed = notDone; + compressFunc_t compress; + + + if (cctxPtr->cStage != 1) return (size_t)-LZ4F_ERROR_GENERIC; + if (dstMaxSize < LZ4F_compressBound(srcSize, &(cctxPtr->prefs))) return (size_t)-LZ4F_ERROR_dstMaxSize_tooSmall; + memset(&cOptionsNull, 0, sizeof(cOptionsNull)); + if (compressOptionsPtr == NULL) compressOptionsPtr = &cOptionsNull; + + /* select compression function */ + compress = LZ4F_selectCompression(cctxPtr->prefs.frameInfo.blockMode, cctxPtr->prefs.compressionLevel); + + /* complete tmp buffer */ + if (cctxPtr->tmpInSize > 0) { /* some data already within tmp buffer */ + size_t sizeToCopy = blockSize - cctxPtr->tmpInSize; + if (sizeToCopy > srcSize) { + /* add src to tmpIn buffer */ + memcpy(cctxPtr->tmpIn + cctxPtr->tmpInSize, srcBuffer, srcSize); + srcPtr = srcEnd; + cctxPtr->tmpInSize += srcSize; + /* still needs some CRC */ + } else { + /* complete tmpIn block and then compress it */ + lastBlockCompressed = fromTmpBuffer; + memcpy(cctxPtr->tmpIn + cctxPtr->tmpInSize, srcBuffer, sizeToCopy); + srcPtr += sizeToCopy; + + dstPtr += LZ4F_compressBlock(dstPtr, cctxPtr->tmpIn, blockSize, compress, cctxPtr->lz4CtxPtr, cctxPtr->prefs.compressionLevel); + + if (cctxPtr->prefs.frameInfo.blockMode==LZ4F_blockLinked) cctxPtr->tmpIn += blockSize; + cctxPtr->tmpInSize = 0; + } + } + + while ((size_t)(srcEnd - srcPtr) >= blockSize) { + /* compress full block */ + lastBlockCompressed = fromSrcBuffer; + dstPtr += LZ4F_compressBlock(dstPtr, srcPtr, blockSize, compress, cctxPtr->lz4CtxPtr, cctxPtr->prefs.compressionLevel); + srcPtr += blockSize; + } + + if ((cctxPtr->prefs.autoFlush) && (srcPtr < srcEnd)) { + /* compress remaining input < blockSize */ + lastBlockCompressed = fromSrcBuffer; + dstPtr += LZ4F_compressBlock(dstPtr, srcPtr, srcEnd - srcPtr, compress, cctxPtr->lz4CtxPtr, cctxPtr->prefs.compressionLevel); + srcPtr = srcEnd; + } + + /* preserve dictionary if necessary */ + if ((cctxPtr->prefs.frameInfo.blockMode==LZ4F_blockLinked) && (lastBlockCompressed==fromSrcBuffer)) { + if (compressOptionsPtr->stableSrc) { + cctxPtr->tmpIn = cctxPtr->tmpBuff; + } else { + int realDictSize = LZ4F_localSaveDict(cctxPtr); + if (realDictSize==0) return (size_t)-LZ4F_ERROR_GENERIC; + cctxPtr->tmpIn = cctxPtr->tmpBuff + realDictSize; + } + } + + /* keep tmpIn within limits */ + if ((cctxPtr->tmpIn + blockSize) > (cctxPtr->tmpBuff + cctxPtr->maxBufferSize) /* necessarily LZ4F_blockLinked && lastBlockCompressed==fromTmpBuffer */ + && !(cctxPtr->prefs.autoFlush)) + { + int realDictSize = LZ4F_localSaveDict(cctxPtr); + cctxPtr->tmpIn = cctxPtr->tmpBuff + realDictSize; + } + + /* some input data left, necessarily < blockSize */ + if (srcPtr < srcEnd) { + /* fill tmp buffer */ + size_t sizeToCopy = srcEnd - srcPtr; + memcpy(cctxPtr->tmpIn, srcPtr, sizeToCopy); + cctxPtr->tmpInSize = sizeToCopy; + } + + if (cctxPtr->prefs.frameInfo.contentChecksumFlag == LZ4F_contentChecksumEnabled) + XXH32_update(&(cctxPtr->xxh), srcBuffer, srcSize); + + cctxPtr->totalInSize += srcSize; + return dstPtr - dstStart; +} + + +/*! LZ4F_flush() : +* Should you need to create compressed data immediately, without waiting for a block to be filled, +* you can call LZ4_flush(), which will immediately compress any remaining data stored within compressionContext. +* The result of the function is the number of bytes written into dstBuffer +* (it can be zero, this means there was no data left within compressionContext) +* The function outputs an error code if it fails (can be tested using LZ4F_isError()) +* The LZ4F_compressOptions_t structure is optional : you can provide NULL as argument. +*/ +size_t LZ4F_flush(LZ4F_compressionContext_t compressionContext, void* dstBuffer, size_t dstMaxSize, const LZ4F_compressOptions_t* compressOptionsPtr) +{ + LZ4F_cctx_t* cctxPtr = (LZ4F_cctx_t*)compressionContext; + BYTE* const dstStart = (BYTE*)dstBuffer; + BYTE* dstPtr = dstStart; + compressFunc_t compress; + + + if (cctxPtr->tmpInSize == 0) return 0; /* nothing to flush */ + if (cctxPtr->cStage != 1) return (size_t)-LZ4F_ERROR_GENERIC; + if (dstMaxSize < (cctxPtr->tmpInSize + 8)) return (size_t)-LZ4F_ERROR_dstMaxSize_tooSmall; /* +8 : block header(4) + block checksum(4) */ + (void)compressOptionsPtr; /* not yet useful */ + + /* select compression function */ + compress = LZ4F_selectCompression(cctxPtr->prefs.frameInfo.blockMode, cctxPtr->prefs.compressionLevel); + + /* compress tmp buffer */ + dstPtr += LZ4F_compressBlock(dstPtr, cctxPtr->tmpIn, cctxPtr->tmpInSize, compress, cctxPtr->lz4CtxPtr, cctxPtr->prefs.compressionLevel); + if (cctxPtr->prefs.frameInfo.blockMode==LZ4F_blockLinked) cctxPtr->tmpIn += cctxPtr->tmpInSize; + cctxPtr->tmpInSize = 0; + + /* keep tmpIn within limits */ + if ((cctxPtr->tmpIn + cctxPtr->maxBlockSize) > (cctxPtr->tmpBuff + cctxPtr->maxBufferSize)) { /* necessarily LZ4F_blockLinked */ + int realDictSize = LZ4F_localSaveDict(cctxPtr); + cctxPtr->tmpIn = cctxPtr->tmpBuff + realDictSize; + } + + return dstPtr - dstStart; +} + + +/*! LZ4F_compressEnd() : +* When you want to properly finish the compressed frame, just call LZ4F_compressEnd(). +* It will flush whatever data remained within compressionContext (like LZ4_flush()) +* but also properly finalize the frame, with an endMark and a checksum. +* The result of the function is the number of bytes written into dstBuffer (necessarily >= 4 (endMark size)) +* The function outputs an error code if it fails (can be tested using LZ4F_isError()) +* The LZ4F_compressOptions_t structure is optional : you can provide NULL as argument. +* compressionContext can then be used again, starting with LZ4F_compressBegin(). The preferences will remain the same. +*/ +size_t LZ4F_compressEnd(LZ4F_compressionContext_t compressionContext, void* dstBuffer, size_t dstMaxSize, const LZ4F_compressOptions_t* compressOptionsPtr) +{ + LZ4F_cctx_t* cctxPtr = (LZ4F_cctx_t*)compressionContext; + BYTE* const dstStart = (BYTE*)dstBuffer; + BYTE* dstPtr = dstStart; + size_t errorCode; + + errorCode = LZ4F_flush(compressionContext, dstBuffer, dstMaxSize, compressOptionsPtr); + if (LZ4F_isError(errorCode)) return errorCode; + dstPtr += errorCode; + + LZ4F_writeLE32(dstPtr, 0); + dstPtr+=4; /* endMark */ + + if (cctxPtr->prefs.frameInfo.contentChecksumFlag == LZ4F_contentChecksumEnabled) { + U32 xxh = XXH32_digest(&(cctxPtr->xxh)); + LZ4F_writeLE32(dstPtr, xxh); + dstPtr+=4; /* content Checksum */ + } + + cctxPtr->cStage = 0; /* state is now re-usable (with identical preferences) */ + cctxPtr->maxBufferSize = 0; /* reuse HC context */ + + if (cctxPtr->prefs.frameInfo.contentSize) { + if (cctxPtr->prefs.frameInfo.contentSize != cctxPtr->totalInSize) + return (size_t)-LZ4F_ERROR_frameSize_wrong; + } + + return dstPtr - dstStart; +} + + +/*-*************************************************** +* Frame Decompression +*****************************************************/ + +/* Resource management */ + +/*! LZ4F_createDecompressionContext() : +* Create a decompressionContext object, which will track all decompression operations. +* Provides a pointer to a fully allocated and initialized LZ4F_decompressionContext object. +* Object can later be released using LZ4F_freeDecompressionContext(). +* @return : if != 0, there was an error during context creation. +*/ +LZ4F_errorCode_t LZ4F_createDecompressionContext(LZ4F_decompressionContext_t* LZ4F_decompressionContextPtr, unsigned versionNumber) +{ + LZ4F_dctx_t* const dctxPtr = (LZ4F_dctx_t*)ALLOCATORF(sizeof(LZ4F_dctx_t)); + if (dctxPtr==NULL) return (LZ4F_errorCode_t)-LZ4F_ERROR_GENERIC; + + dctxPtr->version = versionNumber; + *LZ4F_decompressionContextPtr = (LZ4F_decompressionContext_t)dctxPtr; + return LZ4F_OK_NoError; +} + +LZ4F_errorCode_t LZ4F_freeDecompressionContext(LZ4F_decompressionContext_t LZ4F_decompressionContext) +{ + LZ4F_errorCode_t result = LZ4F_OK_NoError; + LZ4F_dctx_t* const dctxPtr = (LZ4F_dctx_t*)LZ4F_decompressionContext; + if (dctxPtr != NULL) { /* can accept NULL input, like free() */ + result = (LZ4F_errorCode_t)dctxPtr->dStage; + FREEMEM(dctxPtr->tmpIn); + FREEMEM(dctxPtr->tmpOutBuffer); + FREEMEM(dctxPtr); + } + return result; +} + + +/* ******************************************************************** */ +/* ********************* Decompression ******************************** */ +/* ******************************************************************** */ + +typedef enum { dstage_getHeader=0, dstage_storeHeader, + dstage_getCBlockSize, dstage_storeCBlockSize, + dstage_copyDirect, + dstage_getCBlock, dstage_storeCBlock, + dstage_decodeCBlock, dstage_decodeCBlock_intoDst, + dstage_decodeCBlock_intoTmp, dstage_flushOut, + dstage_getSuffix, dstage_storeSuffix, + dstage_getSFrameSize, dstage_storeSFrameSize, + dstage_skipSkippable +} dStage_t; + + +/*! LZ4F_headerSize() : +* @return : size of frame header +* or an error code, which can be tested using LZ4F_isError() +*/ +static size_t LZ4F_headerSize(const void* src, size_t srcSize) +{ + /* minimal srcSize to determine header size */ + if (srcSize < 5) return (size_t)-LZ4F_ERROR_frameHeader_incomplete; + + /* special case : skippable frames */ + if ((LZ4F_readLE32(src) & 0xFFFFFFF0U) == LZ4F_MAGIC_SKIPPABLE_START) return 8; + + /* control magic number */ + if (LZ4F_readLE32(src) != LZ4F_MAGICNUMBER) return (size_t)-LZ4F_ERROR_frameType_unknown; + + /* Frame Header Size */ + { BYTE const FLG = ((const BYTE*)src)[4]; + U32 const contentSizeFlag = (FLG>>3) & _1BIT; + return contentSizeFlag ? maxFHSize : minFHSize; + } +} + + +/*! LZ4F_decodeHeader() : + input : `srcVoidPtr` points at the **beginning of the frame** + output : set internal values of dctx, such as + dctxPtr->frameInfo and dctxPtr->dStage. + Also allocates internal buffers. + @return : nb Bytes read from srcVoidPtr (necessarily <= srcSize) + or an error code (testable with LZ4F_isError()) +*/ +static size_t LZ4F_decodeHeader(LZ4F_dctx_t* dctxPtr, const void* srcVoidPtr, size_t srcSize) +{ + BYTE FLG, BD, HC; + unsigned version, blockMode, blockChecksumFlag, contentSizeFlag, contentChecksumFlag, blockSizeID; + size_t bufferNeeded; + size_t frameHeaderSize; + const BYTE* srcPtr = (const BYTE*)srcVoidPtr; + + /* need to decode header to get frameInfo */ + if (srcSize < minFHSize) return (size_t)-LZ4F_ERROR_frameHeader_incomplete; /* minimal frame header size */ + memset(&(dctxPtr->frameInfo), 0, sizeof(dctxPtr->frameInfo)); + + /* special case : skippable frames */ + if ((LZ4F_readLE32(srcPtr) & 0xFFFFFFF0U) == LZ4F_MAGIC_SKIPPABLE_START) { + dctxPtr->frameInfo.frameType = LZ4F_skippableFrame; + if (srcVoidPtr == (void*)(dctxPtr->header)) { + dctxPtr->tmpInSize = srcSize; + dctxPtr->tmpInTarget = 8; + dctxPtr->dStage = dstage_storeSFrameSize; + return srcSize; + } else { + dctxPtr->dStage = dstage_getSFrameSize; + return 4; + } + } + + /* control magic number */ + if (LZ4F_readLE32(srcPtr) != LZ4F_MAGICNUMBER) return (size_t)-LZ4F_ERROR_frameType_unknown; + dctxPtr->frameInfo.frameType = LZ4F_frame; + + /* Flags */ + FLG = srcPtr[4]; + version = (FLG>>6) & _2BITS; + blockMode = (FLG>>5) & _1BIT; + blockChecksumFlag = (FLG>>4) & _1BIT; + contentSizeFlag = (FLG>>3) & _1BIT; + contentChecksumFlag = (FLG>>2) & _1BIT; + + /* Frame Header Size */ + frameHeaderSize = contentSizeFlag ? maxFHSize : minFHSize; + + if (srcSize < frameHeaderSize) { + /* not enough input to fully decode frame header */ + if (srcPtr != dctxPtr->header) + memcpy(dctxPtr->header, srcPtr, srcSize); + dctxPtr->tmpInSize = srcSize; + dctxPtr->tmpInTarget = frameHeaderSize; + dctxPtr->dStage = dstage_storeHeader; + return srcSize; + } + + BD = srcPtr[5]; + blockSizeID = (BD>>4) & _3BITS; + + /* validate */ + if (version != 1) return (size_t)-LZ4F_ERROR_headerVersion_wrong; /* Version Number, only supported value */ + if (blockChecksumFlag != 0) return (size_t)-LZ4F_ERROR_blockChecksum_unsupported; /* Not supported for the time being */ + if (((FLG>>0)&_2BITS) != 0) return (size_t)-LZ4F_ERROR_reservedFlag_set; /* Reserved bits */ + if (((BD>>7)&_1BIT) != 0) return (size_t)-LZ4F_ERROR_reservedFlag_set; /* Reserved bit */ + if (blockSizeID < 4) return (size_t)-LZ4F_ERROR_maxBlockSize_invalid; /* 4-7 only supported values for the time being */ + if (((BD>>0)&_4BITS) != 0) return (size_t)-LZ4F_ERROR_reservedFlag_set; /* Reserved bits */ + + /* check */ + HC = LZ4F_headerChecksum(srcPtr+4, frameHeaderSize-5); + if (HC != srcPtr[frameHeaderSize-1]) return (size_t)-LZ4F_ERROR_headerChecksum_invalid; /* Bad header checksum error */ + + /* save */ + dctxPtr->frameInfo.blockMode = (LZ4F_blockMode_t)blockMode; + dctxPtr->frameInfo.contentChecksumFlag = (LZ4F_contentChecksum_t)contentChecksumFlag; + dctxPtr->frameInfo.blockSizeID = (LZ4F_blockSizeID_t)blockSizeID; + dctxPtr->maxBlockSize = LZ4F_getBlockSize(blockSizeID); + if (contentSizeFlag) + dctxPtr->frameRemainingSize = dctxPtr->frameInfo.contentSize = LZ4F_readLE64(srcPtr+6); + + /* init */ + if (contentChecksumFlag) XXH32_reset(&(dctxPtr->xxh), 0); + + /* alloc */ + bufferNeeded = dctxPtr->maxBlockSize + ((dctxPtr->frameInfo.blockMode==LZ4F_blockLinked) * 128 KB); + if (bufferNeeded > dctxPtr->maxBufferSize) { /* tmp buffers too small */ + FREEMEM(dctxPtr->tmpIn); + FREEMEM(dctxPtr->tmpOutBuffer); + dctxPtr->maxBufferSize = bufferNeeded; + dctxPtr->tmpIn = (BYTE*)ALLOCATORF(dctxPtr->maxBlockSize); + if (dctxPtr->tmpIn == NULL) return (size_t)-LZ4F_ERROR_GENERIC; + dctxPtr->tmpOutBuffer= (BYTE*)ALLOCATORF(dctxPtr->maxBufferSize); + if (dctxPtr->tmpOutBuffer== NULL) return (size_t)-LZ4F_ERROR_GENERIC; + } + dctxPtr->tmpInSize = 0; + dctxPtr->tmpInTarget = 0; + dctxPtr->dict = dctxPtr->tmpOutBuffer; + dctxPtr->dictSize = 0; + dctxPtr->tmpOut = dctxPtr->tmpOutBuffer; + dctxPtr->tmpOutStart = 0; + dctxPtr->tmpOutSize = 0; + + dctxPtr->dStage = dstage_getCBlockSize; + + return frameHeaderSize; +} + + +/*! LZ4F_getFrameInfo() : +* Decodes frame header information, such as blockSize. +* It is optional : you could start by calling directly LZ4F_decompress() instead. +* The objective is to extract header information without starting decompression, typically for allocation purposes. +* LZ4F_getFrameInfo() can also be used *after* starting decompression, on a valid LZ4F_decompressionContext_t. +* The number of bytes read from srcBuffer will be provided within *srcSizePtr (necessarily <= original value). +* You are expected to resume decompression from where it stopped (srcBuffer + *srcSizePtr) +* @return : hint of the better `srcSize` to use for next call to LZ4F_decompress, +* or an error code which can be tested using LZ4F_isError(). +*/ +LZ4F_errorCode_t LZ4F_getFrameInfo(LZ4F_decompressionContext_t dCtx, LZ4F_frameInfo_t* frameInfoPtr, + const void* srcBuffer, size_t* srcSizePtr) +{ + LZ4F_dctx_t* dctxPtr = (LZ4F_dctx_t*)dCtx; + + if (dctxPtr->dStage > dstage_storeHeader) { /* note : requires dstage_* header related to be at beginning of enum */ + /* frameInfo already decoded */ + size_t o=0, i=0; + *srcSizePtr = 0; + *frameInfoPtr = dctxPtr->frameInfo; + return LZ4F_decompress(dCtx, NULL, &o, NULL, &i, NULL); /* returns : recommended nb of bytes for LZ4F_decompress() */ + } else { + size_t nextSrcSize, o=0; + size_t const hSize = LZ4F_headerSize(srcBuffer, *srcSizePtr); + if (LZ4F_isError(hSize)) { *srcSizePtr=0; return hSize; } + if (*srcSizePtr < hSize) { *srcSizePtr=0; return (size_t)-LZ4F_ERROR_frameHeader_incomplete; } + + *srcSizePtr = hSize; + nextSrcSize = LZ4F_decompress(dCtx, NULL, &o, srcBuffer, srcSizePtr, NULL); + if (dctxPtr->dStage <= dstage_storeHeader) return (size_t)-LZ4F_ERROR_frameHeader_incomplete; /* should not happen, already checked */ + *frameInfoPtr = dctxPtr->frameInfo; + return nextSrcSize; + } +} + + +/* trivial redirector, for common prototype */ +static int LZ4F_decompress_safe (const char* source, char* dest, int compressedSize, int maxDecompressedSize, const char* dictStart, int dictSize) +{ + (void)dictStart; (void)dictSize; + return LZ4_decompress_safe (source, dest, compressedSize, maxDecompressedSize); +} + + +static void LZ4F_updateDict(LZ4F_dctx_t* dctxPtr, const BYTE* dstPtr, size_t dstSize, const BYTE* dstPtr0, unsigned withinTmp) +{ + if (dctxPtr->dictSize==0) + dctxPtr->dict = (const BYTE*)dstPtr; /* priority to dictionary continuity */ + + if (dctxPtr->dict + dctxPtr->dictSize == dstPtr) { /* dictionary continuity */ + dctxPtr->dictSize += dstSize; + return; + } + + if (dstPtr - dstPtr0 + dstSize >= 64 KB) { /* dstBuffer large enough to become dictionary */ + dctxPtr->dict = (const BYTE*)dstPtr0; + dctxPtr->dictSize = dstPtr - dstPtr0 + dstSize; + return; + } + + if ((withinTmp) && (dctxPtr->dict == dctxPtr->tmpOutBuffer)) { + /* assumption : dctxPtr->dict + dctxPtr->dictSize == dctxPtr->tmpOut + dctxPtr->tmpOutStart */ + dctxPtr->dictSize += dstSize; + return; + } + + if (withinTmp) { /* copy relevant dict portion in front of tmpOut within tmpOutBuffer */ + size_t preserveSize = dctxPtr->tmpOut - dctxPtr->tmpOutBuffer; + size_t copySize = 64 KB - dctxPtr->tmpOutSize; + const BYTE* oldDictEnd = dctxPtr->dict + dctxPtr->dictSize - dctxPtr->tmpOutStart; + if (dctxPtr->tmpOutSize > 64 KB) copySize = 0; + if (copySize > preserveSize) copySize = preserveSize; + + memcpy(dctxPtr->tmpOutBuffer + preserveSize - copySize, oldDictEnd - copySize, copySize); + + dctxPtr->dict = dctxPtr->tmpOutBuffer; + dctxPtr->dictSize = preserveSize + dctxPtr->tmpOutStart + dstSize; + return; + } + + if (dctxPtr->dict == dctxPtr->tmpOutBuffer) { /* copy dst into tmp to complete dict */ + if (dctxPtr->dictSize + dstSize > dctxPtr->maxBufferSize) { /* tmp buffer not large enough */ + size_t preserveSize = 64 KB - dstSize; /* note : dstSize < 64 KB */ + memcpy(dctxPtr->tmpOutBuffer, dctxPtr->dict + dctxPtr->dictSize - preserveSize, preserveSize); + dctxPtr->dictSize = preserveSize; + } + memcpy(dctxPtr->tmpOutBuffer + dctxPtr->dictSize, dstPtr, dstSize); + dctxPtr->dictSize += dstSize; + return; + } + + /* join dict & dest into tmp */ + { size_t preserveSize = 64 KB - dstSize; /* note : dstSize < 64 KB */ + if (preserveSize > dctxPtr->dictSize) preserveSize = dctxPtr->dictSize; + memcpy(dctxPtr->tmpOutBuffer, dctxPtr->dict + dctxPtr->dictSize - preserveSize, preserveSize); + memcpy(dctxPtr->tmpOutBuffer + preserveSize, dstPtr, dstSize); + dctxPtr->dict = dctxPtr->tmpOutBuffer; + dctxPtr->dictSize = preserveSize + dstSize; + } +} + + + +/*! LZ4F_decompress() : +* Call this function repetitively to regenerate data compressed within srcBuffer. +* The function will attempt to decode *srcSizePtr from srcBuffer, into dstBuffer of maximum size *dstSizePtr. +* +* The number of bytes regenerated into dstBuffer will be provided within *dstSizePtr (necessarily <= original value). +* +* The number of bytes effectively read from srcBuffer will be provided within *srcSizePtr (necessarily <= original value). +* If the number of bytes read is < number of bytes provided, then the decompression operation is not complete. +* You will have to call it again, continuing from where it stopped. +* +* The function result is an hint of the better srcSize to use for next call to LZ4F_decompress. +* Basically, it's the size of the current (or remaining) compressed block + header of next block. +* Respecting the hint provides some boost to performance, since it allows less buffer shuffling. +* Note that this is just a hint, you can always provide any srcSize you want. +* When a frame is fully decoded, the function result will be 0. +* If decompression failed, function result is an error code which can be tested using LZ4F_isError(). +*/ +size_t LZ4F_decompress(LZ4F_decompressionContext_t decompressionContext, + void* dstBuffer, size_t* dstSizePtr, + const void* srcBuffer, size_t* srcSizePtr, + const LZ4F_decompressOptions_t* decompressOptionsPtr) +{ + LZ4F_dctx_t* dctxPtr = (LZ4F_dctx_t*)decompressionContext; + LZ4F_decompressOptions_t optionsNull; + const BYTE* const srcStart = (const BYTE*)srcBuffer; + const BYTE* const srcEnd = srcStart + *srcSizePtr; + const BYTE* srcPtr = srcStart; + BYTE* const dstStart = (BYTE*)dstBuffer; + BYTE* const dstEnd = dstStart + *dstSizePtr; + BYTE* dstPtr = dstStart; + const BYTE* selectedIn = NULL; + unsigned doAnotherStage = 1; + size_t nextSrcSizeHint = 1; + + + memset(&optionsNull, 0, sizeof(optionsNull)); + if (decompressOptionsPtr==NULL) decompressOptionsPtr = &optionsNull; + *srcSizePtr = 0; + *dstSizePtr = 0; + + /* expect to continue decoding src buffer where it left previously */ + if (dctxPtr->srcExpect != NULL) { + if (srcStart != dctxPtr->srcExpect) return (size_t)-LZ4F_ERROR_srcPtr_wrong; + } + + /* programmed as a state machine */ + + while (doAnotherStage) { + + switch(dctxPtr->dStage) + { + + case dstage_getHeader: + if ((size_t)(srcEnd-srcPtr) >= maxFHSize) { /* enough to decode - shortcut */ + LZ4F_errorCode_t const hSize = LZ4F_decodeHeader(dctxPtr, srcPtr, srcEnd-srcPtr); + if (LZ4F_isError(hSize)) return hSize; + srcPtr += hSize; + break; + } + dctxPtr->tmpInSize = 0; + dctxPtr->tmpInTarget = minFHSize; /* minimum to attempt decode */ + dctxPtr->dStage = dstage_storeHeader; + /* pass-through */ + + case dstage_storeHeader: + { size_t sizeToCopy = dctxPtr->tmpInTarget - dctxPtr->tmpInSize; + if (sizeToCopy > (size_t)(srcEnd - srcPtr)) sizeToCopy = srcEnd - srcPtr; + memcpy(dctxPtr->header + dctxPtr->tmpInSize, srcPtr, sizeToCopy); + dctxPtr->tmpInSize += sizeToCopy; + srcPtr += sizeToCopy; + if (dctxPtr->tmpInSize < dctxPtr->tmpInTarget) { + nextSrcSizeHint = (dctxPtr->tmpInTarget - dctxPtr->tmpInSize) + BHSize; /* rest of header + nextBlockHeader */ + doAnotherStage = 0; /* not enough src data, ask for some more */ + break; + } + { LZ4F_errorCode_t const hSize = LZ4F_decodeHeader(dctxPtr, dctxPtr->header, dctxPtr->tmpInTarget); + if (LZ4F_isError(hSize)) return hSize; + } + break; + } + + case dstage_getCBlockSize: + if ((size_t)(srcEnd - srcPtr) >= BHSize) { + selectedIn = srcPtr; + srcPtr += BHSize; + } else { + /* not enough input to read cBlockSize field */ + dctxPtr->tmpInSize = 0; + dctxPtr->dStage = dstage_storeCBlockSize; + } + + if (dctxPtr->dStage == dstage_storeCBlockSize) /* can be skipped */ + case dstage_storeCBlockSize: + { + size_t sizeToCopy = BHSize - dctxPtr->tmpInSize; + if (sizeToCopy > (size_t)(srcEnd - srcPtr)) sizeToCopy = srcEnd - srcPtr; + memcpy(dctxPtr->tmpIn + dctxPtr->tmpInSize, srcPtr, sizeToCopy); + srcPtr += sizeToCopy; + dctxPtr->tmpInSize += sizeToCopy; + if (dctxPtr->tmpInSize < BHSize) { /* not enough input to get full cBlockSize; wait for more */ + nextSrcSizeHint = BHSize - dctxPtr->tmpInSize; + doAnotherStage = 0; + break; + } + selectedIn = dctxPtr->tmpIn; + } + + /* case dstage_decodeCBlockSize: */ /* no more direct access, to prevent scan-build warning */ + { size_t const nextCBlockSize = LZ4F_readLE32(selectedIn) & 0x7FFFFFFFU; + if (nextCBlockSize==0) { /* frameEnd signal, no more CBlock */ + dctxPtr->dStage = dstage_getSuffix; + break; + } + if (nextCBlockSize > dctxPtr->maxBlockSize) return (size_t)-LZ4F_ERROR_GENERIC; /* invalid cBlockSize */ + dctxPtr->tmpInTarget = nextCBlockSize; + if (LZ4F_readLE32(selectedIn) & LZ4F_BLOCKUNCOMPRESSED_FLAG) { + dctxPtr->dStage = dstage_copyDirect; + break; + } + dctxPtr->dStage = dstage_getCBlock; + if (dstPtr==dstEnd) { + nextSrcSizeHint = nextCBlockSize + BHSize; + doAnotherStage = 0; + } + break; + } + + case dstage_copyDirect: /* uncompressed block */ + { size_t sizeToCopy = dctxPtr->tmpInTarget; + if ((size_t)(srcEnd-srcPtr) < sizeToCopy) sizeToCopy = srcEnd - srcPtr; /* not enough input to read full block */ + if ((size_t)(dstEnd-dstPtr) < sizeToCopy) sizeToCopy = dstEnd - dstPtr; + memcpy(dstPtr, srcPtr, sizeToCopy); + if (dctxPtr->frameInfo.contentChecksumFlag) XXH32_update(&(dctxPtr->xxh), srcPtr, sizeToCopy); + if (dctxPtr->frameInfo.contentSize) dctxPtr->frameRemainingSize -= sizeToCopy; + + /* dictionary management */ + if (dctxPtr->frameInfo.blockMode==LZ4F_blockLinked) + LZ4F_updateDict(dctxPtr, dstPtr, sizeToCopy, dstStart, 0); + + srcPtr += sizeToCopy; + dstPtr += sizeToCopy; + if (sizeToCopy == dctxPtr->tmpInTarget) { /* all copied */ + dctxPtr->dStage = dstage_getCBlockSize; + break; + } + dctxPtr->tmpInTarget -= sizeToCopy; /* still need to copy more */ + nextSrcSizeHint = dctxPtr->tmpInTarget + BHSize; + doAnotherStage = 0; + break; + } + + case dstage_getCBlock: /* entry from dstage_decodeCBlockSize */ + if ((size_t)(srcEnd-srcPtr) < dctxPtr->tmpInTarget) { + dctxPtr->tmpInSize = 0; + dctxPtr->dStage = dstage_storeCBlock; + break; + } + selectedIn = srcPtr; + srcPtr += dctxPtr->tmpInTarget; + dctxPtr->dStage = dstage_decodeCBlock; + break; + + case dstage_storeCBlock: + { size_t sizeToCopy = dctxPtr->tmpInTarget - dctxPtr->tmpInSize; + if (sizeToCopy > (size_t)(srcEnd-srcPtr)) sizeToCopy = srcEnd-srcPtr; + memcpy(dctxPtr->tmpIn + dctxPtr->tmpInSize, srcPtr, sizeToCopy); + dctxPtr->tmpInSize += sizeToCopy; + srcPtr += sizeToCopy; + if (dctxPtr->tmpInSize < dctxPtr->tmpInTarget) { /* need more input */ + nextSrcSizeHint = (dctxPtr->tmpInTarget - dctxPtr->tmpInSize) + BHSize; + doAnotherStage=0; + break; + } + selectedIn = dctxPtr->tmpIn; + dctxPtr->dStage = dstage_decodeCBlock; + /* pass-through */ + } + + case dstage_decodeCBlock: + if ((size_t)(dstEnd-dstPtr) < dctxPtr->maxBlockSize) /* not enough place into dst : decode into tmpOut */ + dctxPtr->dStage = dstage_decodeCBlock_intoTmp; + else + dctxPtr->dStage = dstage_decodeCBlock_intoDst; + break; + + case dstage_decodeCBlock_intoDst: + { int (*decoder)(const char*, char*, int, int, const char*, int); + int decodedSize; + + if (dctxPtr->frameInfo.blockMode == LZ4F_blockLinked) + decoder = LZ4_decompress_safe_usingDict; + else + decoder = LZ4F_decompress_safe; + + decodedSize = decoder((const char*)selectedIn, (char*)dstPtr, (int)dctxPtr->tmpInTarget, (int)dctxPtr->maxBlockSize, (const char*)dctxPtr->dict, (int)dctxPtr->dictSize); + if (decodedSize < 0) return (size_t)-LZ4F_ERROR_GENERIC; /* decompression failed */ + if (dctxPtr->frameInfo.contentChecksumFlag) XXH32_update(&(dctxPtr->xxh), dstPtr, decodedSize); + if (dctxPtr->frameInfo.contentSize) dctxPtr->frameRemainingSize -= decodedSize; + + /* dictionary management */ + if (dctxPtr->frameInfo.blockMode==LZ4F_blockLinked) + LZ4F_updateDict(dctxPtr, dstPtr, decodedSize, dstStart, 0); + + dstPtr += decodedSize; + dctxPtr->dStage = dstage_getCBlockSize; + break; + } + + case dstage_decodeCBlock_intoTmp: + /* not enough place into dst : decode into tmpOut */ + { int (*decoder)(const char*, char*, int, int, const char*, int); + int decodedSize; + + if (dctxPtr->frameInfo.blockMode == LZ4F_blockLinked) + decoder = LZ4_decompress_safe_usingDict; + else + decoder = LZ4F_decompress_safe; + + /* ensure enough place for tmpOut */ + if (dctxPtr->frameInfo.blockMode == LZ4F_blockLinked) { + if (dctxPtr->dict == dctxPtr->tmpOutBuffer) { + if (dctxPtr->dictSize > 128 KB) { + memcpy(dctxPtr->tmpOutBuffer, dctxPtr->dict + dctxPtr->dictSize - 64 KB, 64 KB); + dctxPtr->dictSize = 64 KB; + } + dctxPtr->tmpOut = dctxPtr->tmpOutBuffer + dctxPtr->dictSize; + } else { /* dict not within tmp */ + size_t reservedDictSpace = dctxPtr->dictSize; + if (reservedDictSpace > 64 KB) reservedDictSpace = 64 KB; + dctxPtr->tmpOut = dctxPtr->tmpOutBuffer + reservedDictSpace; + } + } + + /* Decode */ + decodedSize = decoder((const char*)selectedIn, (char*)dctxPtr->tmpOut, (int)dctxPtr->tmpInTarget, (int)dctxPtr->maxBlockSize, (const char*)dctxPtr->dict, (int)dctxPtr->dictSize); + if (decodedSize < 0) return (size_t)-LZ4F_ERROR_decompressionFailed; /* decompression failed */ + if (dctxPtr->frameInfo.contentChecksumFlag) XXH32_update(&(dctxPtr->xxh), dctxPtr->tmpOut, decodedSize); + if (dctxPtr->frameInfo.contentSize) dctxPtr->frameRemainingSize -= decodedSize; + dctxPtr->tmpOutSize = decodedSize; + dctxPtr->tmpOutStart = 0; + dctxPtr->dStage = dstage_flushOut; + break; + } + + case dstage_flushOut: /* flush decoded data from tmpOut to dstBuffer */ + { size_t sizeToCopy = dctxPtr->tmpOutSize - dctxPtr->tmpOutStart; + if (sizeToCopy > (size_t)(dstEnd-dstPtr)) sizeToCopy = dstEnd-dstPtr; + memcpy(dstPtr, dctxPtr->tmpOut + dctxPtr->tmpOutStart, sizeToCopy); + + /* dictionary management */ + if (dctxPtr->frameInfo.blockMode==LZ4F_blockLinked) + LZ4F_updateDict(dctxPtr, dstPtr, sizeToCopy, dstStart, 1); + + dctxPtr->tmpOutStart += sizeToCopy; + dstPtr += sizeToCopy; + + /* end of flush ? */ + if (dctxPtr->tmpOutStart == dctxPtr->tmpOutSize) { + dctxPtr->dStage = dstage_getCBlockSize; + break; + } + nextSrcSizeHint = BHSize; + doAnotherStage = 0; /* still some data to flush */ + break; + } + + case dstage_getSuffix: + { size_t const suffixSize = dctxPtr->frameInfo.contentChecksumFlag * 4; + if (dctxPtr->frameRemainingSize) return (size_t)-LZ4F_ERROR_frameSize_wrong; /* incorrect frame size decoded */ + if (suffixSize == 0) { /* frame completed */ + nextSrcSizeHint = 0; + dctxPtr->dStage = dstage_getHeader; + doAnotherStage = 0; + break; + } + if ((srcEnd - srcPtr) < 4) { /* not enough size for entire CRC */ + dctxPtr->tmpInSize = 0; + dctxPtr->dStage = dstage_storeSuffix; + } else { + selectedIn = srcPtr; + srcPtr += 4; + } + } + + if (dctxPtr->dStage == dstage_storeSuffix) /* can be skipped */ + case dstage_storeSuffix: + { + size_t sizeToCopy = 4 - dctxPtr->tmpInSize; + if (sizeToCopy > (size_t)(srcEnd - srcPtr)) sizeToCopy = srcEnd - srcPtr; + memcpy(dctxPtr->tmpIn + dctxPtr->tmpInSize, srcPtr, sizeToCopy); + srcPtr += sizeToCopy; + dctxPtr->tmpInSize += sizeToCopy; + if (dctxPtr->tmpInSize < 4) { /* not enough input to read complete suffix */ + nextSrcSizeHint = 4 - dctxPtr->tmpInSize; + doAnotherStage=0; + break; + } + selectedIn = dctxPtr->tmpIn; + } + + /* case dstage_checkSuffix: */ /* no direct call, to avoid scan-build warning */ + { U32 const readCRC = LZ4F_readLE32(selectedIn); + U32 const resultCRC = XXH32_digest(&(dctxPtr->xxh)); + if (readCRC != resultCRC) return (size_t)-LZ4F_ERROR_contentChecksum_invalid; + nextSrcSizeHint = 0; + dctxPtr->dStage = dstage_getHeader; + doAnotherStage = 0; + break; + } + + case dstage_getSFrameSize: + if ((srcEnd - srcPtr) >= 4) { + selectedIn = srcPtr; + srcPtr += 4; + } else { + /* not enough input to read cBlockSize field */ + dctxPtr->tmpInSize = 4; + dctxPtr->tmpInTarget = 8; + dctxPtr->dStage = dstage_storeSFrameSize; + } + + if (dctxPtr->dStage == dstage_storeSFrameSize) + case dstage_storeSFrameSize: + { + size_t sizeToCopy = dctxPtr->tmpInTarget - dctxPtr->tmpInSize; + if (sizeToCopy > (size_t)(srcEnd - srcPtr)) sizeToCopy = srcEnd - srcPtr; + memcpy(dctxPtr->header + dctxPtr->tmpInSize, srcPtr, sizeToCopy); + srcPtr += sizeToCopy; + dctxPtr->tmpInSize += sizeToCopy; + if (dctxPtr->tmpInSize < dctxPtr->tmpInTarget) { /* not enough input to get full sBlockSize; wait for more */ + nextSrcSizeHint = dctxPtr->tmpInTarget - dctxPtr->tmpInSize; + doAnotherStage = 0; + break; + } + selectedIn = dctxPtr->header + 4; + } + + /* case dstage_decodeSFrameSize: */ /* no direct access */ + { size_t const SFrameSize = LZ4F_readLE32(selectedIn); + dctxPtr->frameInfo.contentSize = SFrameSize; + dctxPtr->tmpInTarget = SFrameSize; + dctxPtr->dStage = dstage_skipSkippable; + break; + } + + case dstage_skipSkippable: + { size_t skipSize = dctxPtr->tmpInTarget; + if (skipSize > (size_t)(srcEnd-srcPtr)) skipSize = srcEnd-srcPtr; + srcPtr += skipSize; + dctxPtr->tmpInTarget -= skipSize; + doAnotherStage = 0; + nextSrcSizeHint = dctxPtr->tmpInTarget; + if (nextSrcSizeHint) break; + dctxPtr->dStage = dstage_getHeader; + break; + } + } + } + + /* preserve dictionary within tmp if necessary */ + if ( (dctxPtr->frameInfo.blockMode==LZ4F_blockLinked) + &&(dctxPtr->dict != dctxPtr->tmpOutBuffer) + &&(!decompressOptionsPtr->stableDst) + &&((unsigned)(dctxPtr->dStage-1) < (unsigned)(dstage_getSuffix-1)) + ) + { + if (dctxPtr->dStage == dstage_flushOut) { + size_t preserveSize = dctxPtr->tmpOut - dctxPtr->tmpOutBuffer; + size_t copySize = 64 KB - dctxPtr->tmpOutSize; + const BYTE* oldDictEnd = dctxPtr->dict + dctxPtr->dictSize - dctxPtr->tmpOutStart; + if (dctxPtr->tmpOutSize > 64 KB) copySize = 0; + if (copySize > preserveSize) copySize = preserveSize; + + memcpy(dctxPtr->tmpOutBuffer + preserveSize - copySize, oldDictEnd - copySize, copySize); + + dctxPtr->dict = dctxPtr->tmpOutBuffer; + dctxPtr->dictSize = preserveSize + dctxPtr->tmpOutStart; + } else { + size_t newDictSize = dctxPtr->dictSize; + const BYTE* oldDictEnd = dctxPtr->dict + dctxPtr->dictSize; + if ((newDictSize) > 64 KB) newDictSize = 64 KB; + + memcpy(dctxPtr->tmpOutBuffer, oldDictEnd - newDictSize, newDictSize); + + dctxPtr->dict = dctxPtr->tmpOutBuffer; + dctxPtr->dictSize = newDictSize; + dctxPtr->tmpOut = dctxPtr->tmpOutBuffer + newDictSize; + } + } + + /* require function to be called again from position where it stopped */ + if (srcPtrsrcExpect = srcPtr; + else + dctxPtr->srcExpect = NULL; + + *srcSizePtr = (srcPtr - srcStart); + *dstSizePtr = (dstPtr - dstStart); + return nextSrcSizeHint; +} diff --git a/lib/lz4frame.h b/lib/lz4frame.h new file mode 100644 index 000000000..f448e4d64 --- /dev/null +++ b/lib/lz4frame.h @@ -0,0 +1,873 @@ +/* + LZ4 - Fast LZ compression algorithm + Header File + Copyright (C) 2011-2016, Yann Collet. + + BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + You can contact the author at : + - LZ4 homepage : http://www.lz4.org + - LZ4 source repository : https://github.com/Cyan4973/lz4 +*/ + +#pragma once + +#if defined (__cplusplus) +extern "C" { +#endif + +/*-************************************ +* Includes +**************************************/ +#include /* size_t */ + +/*-*************************************************************** +* Export parameters +*****************************************************************/ +/*! +* LZ4_DLL_EXPORT : +* Enable exporting of functions when building a Windows DLL +*/ +#ifndef LZ4_STATIC +#if defined(_WIN32) +# if defined(LZ4_DLL_EXPORT) && (LZ4_DLL_EXPORT==1) +# define LZ4FLIB_API __declspec(dllexport) +# else +# define LZ4FLIB_API __declspec(dllimport) +# endif +#endif +#else +# define LZ4FLIB_API +#endif + + + +/************************************** +* Error management +* ************************************/ +#define LZ4F_LIST_ERRORS(ITEM) \ + ITEM(OK_NoError) ITEM(ERROR_GENERIC) \ + ITEM(ERROR_maxBlockSize_invalid) ITEM(ERROR_blockMode_invalid) ITEM(ERROR_contentChecksumFlag_invalid) \ + ITEM(ERROR_compressionLevel_invalid) \ + ITEM(ERROR_headerVersion_wrong) ITEM(ERROR_blockChecksum_unsupported) ITEM(ERROR_reservedFlag_set) \ + ITEM(ERROR_allocation_failed) \ + ITEM(ERROR_srcSize_tooLarge) ITEM(ERROR_dstMaxSize_tooSmall) \ + ITEM(ERROR_frameHeader_incomplete) ITEM(ERROR_frameType_unknown) ITEM(ERROR_frameSize_wrong) \ + ITEM(ERROR_srcPtr_wrong) \ + ITEM(ERROR_decompressionFailed) \ + ITEM(ERROR_headerChecksum_invalid) ITEM(ERROR_contentChecksum_invalid) \ + ITEM(ERROR_maxCode) + +//#define LZ4F_DISABLE_OLD_ENUMS +#ifndef LZ4F_DISABLE_OLD_ENUMS +#define LZ4F_GENERATE_ENUM(ENUM) LZ4F_##ENUM, ENUM = LZ4F_##ENUM, +#else +#define LZ4F_GENERATE_ENUM(ENUM) LZ4F_##ENUM, +#endif + typedef enum { LZ4F_LIST_ERRORS( LZ4F_GENERATE_ENUM ) } LZ4F_errorCodes; /* enum is exposed, to handle specific errors; compare function result to -enum value */ + + + +/*-************************************ +* Version +**************************************/ +#define LZ4_VERSION_MAJOR 1 /* for breaking interface changes */ +#define LZ4_VERSION_MINOR 7 /* for new (non-breaking) interface capabilities */ +#define LZ4_VERSION_RELEASE 2 /* for tweaks, bug-fixes, or development */ + +#define LZ4_VERSION_NUMBER (LZ4_VERSION_MAJOR *100*100 + LZ4_VERSION_MINOR *100 + LZ4_VERSION_RELEASE) + LZ4FLIB_API int LZ4_versionNumber( void ); + +#define LZ4_LIB_VERSION LZ4_VERSION_MAJOR.LZ4_VERSION_MINOR.LZ4_VERSION_RELEASE +#define LZ4_QUOTE(str) #str +#define LZ4_EXPAND_AND_QUOTE(str) LZ4_QUOTE(str) +#define LZ4_VERSION_STRING LZ4_EXPAND_AND_QUOTE(LZ4_LIB_VERSION) + LZ4FLIB_API const char* LZ4_versionString( void ); + + + /*-************************************ + * Tuning parameter + **************************************/ + /* + * LZ4_MEMORY_USAGE : + * Memory usage formula : N->2^N Bytes (examples : 10 -> 1KB; 12 -> 4KB ; 16 -> 64KB; 20 -> 1MB; etc.) + * Increasing memory usage improves compression ratio + * Reduced memory usage can improve speed, due to cache effect + * Default value is 14, for 16KB, which nicely fits into Intel x86 L1 cache + */ +#define LZ4_MEMORY_USAGE 14 + + + /*-************************************ + * Simple Functions + **************************************/ + + LZ4FLIB_API int LZ4_compress_default( const char* source, char* dest, int sourceSize, int maxDestSize ); + LZ4FLIB_API int LZ4_decompress_safe( const char* source, char* dest, int compressedSize, int maxDecompressedSize ); + + /* + LZ4_compress_default() : + Compresses 'sourceSize' bytes from buffer 'source' + into already allocated 'dest' buffer of size 'maxDestSize'. + Compression is guaranteed to succeed if 'maxDestSize' >= LZ4_compressBound(sourceSize). + It also runs faster, so it's a recommended setting. + If the function cannot compress 'source' into a more limited 'dest' budget, + compression stops *immediately*, and the function result is zero. + As a consequence, 'dest' content is not valid. + This function never writes outside 'dest' buffer, nor read outside 'source' buffer. + sourceSize : Max supported value is LZ4_MAX_INPUT_VALUE + maxDestSize : full or partial size of buffer 'dest' (which must be already allocated) + return : the number of bytes written into buffer 'dest' (necessarily <= maxOutputSize) + or 0 if compression fails + + LZ4_decompress_safe() : + compressedSize : is the precise full size of the compressed block. + maxDecompressedSize : is the size of destination buffer, which must be already allocated. + return : the number of bytes decompressed into destination buffer (necessarily <= maxDecompressedSize) + If destination buffer is not large enough, decoding will stop and output an error code (<0). + If the source stream is detected malformed, the function will stop decoding and return a negative result. + This function is protected against buffer overflow exploits, including malicious data packets. + It never writes outside output buffer, nor reads outside input buffer. + */ + + + /*-************************************ + * Advanced Functions + **************************************/ +#define LZ4_MAX_INPUT_SIZE 0x7E000000 /* 2 113 929 216 bytes */ +#define LZ4_COMPRESSBOUND(isize) ((unsigned)(isize) > (unsigned)LZ4_MAX_INPUT_SIZE ? 0 : (isize) + ((isize)/255) + 16) + + /*! + LZ4_compressBound() : + Provides the maximum size that LZ4 compression may output in a "worst case" scenario (input data not compressible) + This function is primarily useful for memory allocation purposes (destination buffer size). + Macro LZ4_COMPRESSBOUND() is also provided for compilation-time evaluation (stack memory allocation for example). + Note that LZ4_compress_default() compress faster when dest buffer size is >= LZ4_compressBound(srcSize) + inputSize : max supported value is LZ4_MAX_INPUT_SIZE + return : maximum output size in a "worst case" scenario + or 0, if input size is too large ( > LZ4_MAX_INPUT_SIZE) + */ + LZ4FLIB_API int LZ4_compressBound( int inputSize ); + + /*! + LZ4_compress_fast() : + Same as LZ4_compress_default(), but allows to select an "acceleration" factor. + The larger the acceleration value, the faster the algorithm, but also the lesser the compression. + It's a trade-off. It can be fine tuned, with each successive value providing roughly +~3% to speed. + An acceleration value of "1" is the same as regular LZ4_compress_default() + Values <= 0 will be replaced by ACCELERATION_DEFAULT (see lz4.c), which is 1. + */ + LZ4FLIB_API int LZ4_compress_fast( const char* source, char* dest, int sourceSize, int maxDestSize, int acceleration ); + + + /*! + LZ4_compress_fast_extState() : + Same compression function, just using an externally allocated memory space to store compression state. + Use LZ4_sizeofState() to know how much memory must be allocated, + and allocate it on 8-bytes boundaries (using malloc() typically). + Then, provide it as 'void* state' to compression function. + */ + LZ4FLIB_API int LZ4_sizeofState( void ); + LZ4FLIB_API int LZ4_compress_fast_extState( void* state, const char* source, char* dest, int inputSize, int maxDestSize, int acceleration ); + + + /*! + LZ4_compress_destSize() : + Reverse the logic, by compressing as much data as possible from 'source' buffer + into already allocated buffer 'dest' of size 'targetDestSize'. + This function either compresses the entire 'source' content into 'dest' if it's large enough, + or fill 'dest' buffer completely with as much data as possible from 'source'. + *sourceSizePtr : will be modified to indicate how many bytes where read from 'source' to fill 'dest'. + New value is necessarily <= old value. + return : Nb bytes written into 'dest' (necessarily <= targetDestSize) + or 0 if compression fails + */ + LZ4FLIB_API int LZ4_compress_destSize( const char* source, char* dest, int* sourceSizePtr, int targetDestSize ); + + + /*! + LZ4_decompress_fast() : + originalSize : is the original and therefore uncompressed size + return : the number of bytes read from the source buffer (in other words, the compressed size) + If the source stream is detected malformed, the function will stop decoding and return a negative result. + Destination buffer must be already allocated. Its size must be a minimum of 'originalSize' bytes. + note : This function fully respect memory boundaries for properly formed compressed data. + It is a bit faster than LZ4_decompress_safe(). + However, it does not provide any protection against intentionally modified data stream (malicious input). + Use this function in trusted environment only (data to decode comes from a trusted source). + */ + LZ4FLIB_API int LZ4_decompress_fast( const char* source, char* dest, int originalSize ); + + /*! + LZ4_decompress_safe_partial() : + This function decompress a compressed block of size 'compressedSize' at position 'source' + into destination buffer 'dest' of size 'maxDecompressedSize'. + The function tries to stop decompressing operation as soon as 'targetOutputSize' has been reached, + reducing decompression time. + return : the number of bytes decoded in the destination buffer (necessarily <= maxDecompressedSize) + Note : this number can be < 'targetOutputSize' should the compressed block to decode be smaller. + Always control how many bytes were decoded. + If the source stream is detected malformed, the function will stop decoding and return a negative result. + This function never writes outside of output buffer, and never reads outside of input buffer. It is therefore protected against malicious data packets + */ + LZ4FLIB_API int LZ4_decompress_safe_partial( const char* source, char* dest, int compressedSize, int targetOutputSize, int maxDecompressedSize ); + + + /*-********************************************* + * Streaming Compression Functions + ***********************************************/ +#define LZ4_STREAMSIZE_U64 ((1 << (LZ4_MEMORY_USAGE-3)) + 4) +#define LZ4_STREAMSIZE (LZ4_STREAMSIZE_U64 * sizeof(long long)) + /* + * LZ4_stream_t : + * information structure to track an LZ4 stream. + * important : init this structure content before first use ! + * note : only allocated directly the structure if you are statically linking LZ4 + * If you are using liblz4 as a DLL, please use below construction methods instead. + */ + typedef struct { long long table[LZ4_STREAMSIZE_U64]; } LZ4_stream_t; + + /*! LZ4_resetStream() : + * Use this function to init an allocated `LZ4_stream_t` structure + */ + LZ4FLIB_API void LZ4_resetStream( LZ4_stream_t* streamPtr ); + + /*! LZ4_createStream() will allocate and initialize an `LZ4_stream_t` structure. + * LZ4_freeStream() releases its memory. + * In the context of a DLL (liblz4), please use these methods rather than the static struct. + * They are more future proof, in case of a change of `LZ4_stream_t` size. + */ + LZ4FLIB_API LZ4_stream_t* LZ4_createStream( void ); + LZ4FLIB_API int LZ4_freeStream( LZ4_stream_t* streamPtr ); + + /*! LZ4_loadDict() : + * Use this function to load a static dictionary into LZ4_stream. + * Any previous data will be forgotten, only 'dictionary' will remain in memory. + * Loading a size of 0 is allowed. + * Return : dictionary size, in bytes (necessarily <= 64 KB) + */ + LZ4FLIB_API int LZ4_loadDict( LZ4_stream_t* streamPtr, const char* dictionary, int dictSize ); + + /*! LZ4_compress_fast_continue() : + * Compress buffer content 'src', using data from previously compressed blocks as dictionary to improve compression ratio. + * Important : Previous data blocks are assumed to still be present and unmodified ! + * 'dst' buffer must be already allocated. + * If maxDstSize >= LZ4_compressBound(srcSize), compression is guaranteed to succeed, and runs faster. + * If not, and if compressed data cannot fit into 'dst' buffer size, compression stops, and function returns a zero. + */ + LZ4FLIB_API int LZ4_compress_fast_continue( LZ4_stream_t* streamPtr, const char* src, char* dst, int srcSize, int maxDstSize, int acceleration ); + + /*! LZ4_saveDict() : + * If previously compressed data block is not guaranteed to remain available at its memory location, + * save it into a safer place (char* safeBuffer). + * Note : you don't need to call LZ4_loadDict() afterwards, + * dictionary is immediately usable, you can therefore call LZ4_compress_fast_continue(). + * Return : saved dictionary size in bytes (necessarily <= dictSize), or 0 if error. + */ + LZ4FLIB_API int LZ4_saveDict( LZ4_stream_t* streamPtr, char* safeBuffer, int dictSize ); + + + /*-********************************************** + * Streaming Decompression Functions + ************************************************/ + +#define LZ4_STREAMDECODESIZE_U64 4 +#define LZ4_STREAMDECODESIZE (LZ4_STREAMDECODESIZE_U64 * sizeof(unsigned long long)) + typedef struct { unsigned long long table[LZ4_STREAMDECODESIZE_U64]; } LZ4_streamDecode_t; + /* + * LZ4_streamDecode_t + * information structure to track an LZ4 stream. + * init this structure content using LZ4_setStreamDecode or memset() before first use ! + * + * In the context of a DLL (liblz4) please prefer usage of construction methods below. + * They are more future proof, in case of a change of LZ4_streamDecode_t size in the future. + * LZ4_createStreamDecode will allocate and initialize an LZ4_streamDecode_t structure + * LZ4_freeStreamDecode releases its memory. + */ + LZ4FLIB_API LZ4_streamDecode_t* LZ4_createStreamDecode( void ); + LZ4FLIB_API int LZ4_freeStreamDecode( LZ4_streamDecode_t* LZ4_stream ); + + /*! LZ4_setStreamDecode() : + * Use this function to instruct where to find the dictionary. + * Setting a size of 0 is allowed (same effect as reset). + * @return : 1 if OK, 0 if error + */ + LZ4FLIB_API int LZ4_setStreamDecode( LZ4_streamDecode_t* LZ4_streamDecode, const char* dictionary, int dictSize ); + + /* + *_continue() : + These decoding functions allow decompression of multiple blocks in "streaming" mode. + Previously decoded blocks *must* remain available at the memory position where they were decoded (up to 64 KB) + In the case of a ring buffers, decoding buffer must be either : + - Exactly same size as encoding buffer, with same update rule (block boundaries at same positions) + In which case, the decoding & encoding ring buffer can have any size, including very small ones ( < 64 KB). + - Larger than encoding buffer, by a minimum of maxBlockSize more bytes. + maxBlockSize is implementation dependent. It's the maximum size you intend to compress into a single block. + In which case, encoding and decoding buffers do not need to be synchronized, + and encoding ring buffer can have any size, including small ones ( < 64 KB). + - _At least_ 64 KB + 8 bytes + maxBlockSize. + In which case, encoding and decoding buffers do not need to be synchronized, + and encoding ring buffer can have any size, including larger than decoding buffer. + Whenever these conditions are not possible, save the last 64KB of decoded data into a safe buffer, + and indicate where it is saved using LZ4_setStreamDecode() + */ + LZ4FLIB_API int LZ4_decompress_safe_continue( LZ4_streamDecode_t* LZ4_streamDecode, const char* source, char* dest, int compressedSize, int maxDecompressedSize ); + LZ4FLIB_API int LZ4_decompress_fast_continue( LZ4_streamDecode_t* LZ4_streamDecode, const char* source, char* dest, int originalSize ); + + + /* + Advanced decoding functions : + *_usingDict() : + These decoding functions work the same as + a combination of LZ4_setStreamDecode() followed by LZ4_decompress_x_continue() + They are stand-alone. They don't need nor update an LZ4_streamDecode_t structure. + */ + LZ4FLIB_API int LZ4_decompress_safe_usingDict( const char* source, char* dest, int compressedSize, int maxDecompressedSize, const char* dictStart, int dictSize ); + LZ4FLIB_API int LZ4_decompress_fast_usingDict( const char* source, char* dest, int originalSize, const char* dictStart, int dictSize ); + + + /*=************************************ + * Obsolete Functions + **************************************/ + /* Deprecate Warnings */ + /* Should these warnings messages be a problem, + it is generally possible to disable them, + with -Wno-deprecated-declarations for gcc + or _CRT_SECURE_NO_WARNINGS in Visual for example. + Otherwise, you can also define LZ4_DISABLE_DEPRECATE_WARNINGS */ +#define LZ4_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) +#ifdef LZ4_DISABLE_DEPRECATE_WARNINGS +# define LZ4_DEPRECATED() /* disable deprecation warnings */ +#else +# if (LZ4_GCC_VERSION >= 405) || defined(__clang__) +# define LZ4_DEPRECATED(message) __attribute__((deprecated(message))) +# elif (LZ4_GCC_VERSION >= 301) +# define LZ4_DEPRECATED(message) __attribute__((deprecated)) +# elif defined(_MSC_VER) +# define LZ4_DEPRECATED(message) __declspec(deprecated(message)) +# else +# pragma message("WARNING: You need to implement LZ4_DEPRECATED for this compiler") +# define LZ4_DEPRECATED(message) +# endif +#endif /* LZ4_DISABLE_DEPRECATE_WARNINGS */ + + /* Obsolete compression functions */ + /* These functions will generate warnings in a future release */ + LZ4FLIB_API int LZ4_compress( const char* source, char* dest, int sourceSize ); + LZ4FLIB_API int LZ4_compress_limitedOutput( const char* source, char* dest, int sourceSize, int maxOutputSize ); + LZ4FLIB_API int LZ4_compress_withState( void* state, const char* source, char* dest, int inputSize ); + LZ4FLIB_API int LZ4_compress_limitedOutput_withState( void* state, const char* source, char* dest, int inputSize, int maxOutputSize ); + LZ4FLIB_API int LZ4_compress_continue( LZ4_stream_t* LZ4_streamPtr, const char* source, char* dest, int inputSize ); + LZ4FLIB_API int LZ4_compress_limitedOutput_continue( LZ4_stream_t* LZ4_streamPtr, const char* source, char* dest, int inputSize, int maxOutputSize ); + + /* Obsolete decompression functions */ + /* These function names are completely deprecated and must no longer be used. + They are only provided in lz4.c for compatibility with older programs. + - LZ4_uncompress is the same as LZ4_decompress_fast + - LZ4_uncompress_unknownOutputSize is the same as LZ4_decompress_safe + These function prototypes are now disabled; uncomment them only if you really need them. + It is highly recommended to stop using these prototypes and migrate to maintained ones */ + /* int LZ4_uncompress (const char* source, char* dest, int outputSize); */ + /* int LZ4_uncompress_unknownOutputSize (const char* source, char* dest, int isize, int maxOutputSize); */ + + /* Obsolete streaming functions; use new streaming interface whenever possible */ + LZ4_DEPRECATED( "use LZ4_createStream() instead" ) void* LZ4_create( char* inputBuffer ); + LZ4_DEPRECATED( "use LZ4_createStream() instead" ) int LZ4_sizeofStreamState( void ); + LZ4_DEPRECATED( "use LZ4_resetStream() instead" ) int LZ4_resetStreamState( void* state, char* inputBuffer ); + LZ4_DEPRECATED( "use LZ4_saveDict() instead" ) char* LZ4_slideInputBuffer( void* state ); + + /* Obsolete streaming decoding functions */ + LZ4_DEPRECATED( "use LZ4_decompress_safe_usingDict() instead" ) int LZ4_decompress_safe_withPrefix64k( const char* src, char* dst, int compressedSize, int maxDstSize ); + LZ4_DEPRECATED( "use LZ4_decompress_fast_usingDict() instead" ) int LZ4_decompress_fast_withPrefix64k( const char* src, char* dst, int originalSize ); + +/* + LZ4 HC - High Compression Mode of LZ4 + Header File + Copyright (C) 2011-2015, Yann Collet. + BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + You can contact the author at : + - LZ4 source repository : https://github.com/Cyan4973/lz4 + - LZ4 public forum : https://groups.google.com/forum/#!forum/lz4c +*/ + +#define LZ4HC_MIN_CLEVEL 3 +#define LZ4HC_DEFAULT_CLEVEL 9 +#define LZ4HC_MAX_CLEVEL 16 + + /*-************************************ + * Block Compression + **************************************/ + /*! + LZ4_compress_HC() : + Destination buffer `dst` must be already allocated. + Compression success is guaranteed if `dst` buffer is sized to handle worst circumstances (data not compressible) + Worst size evaluation is provided by function LZ4_compressBound() (see "lz4.h") + `srcSize` : Max supported value is LZ4_MAX_INPUT_SIZE (see "lz4.h") + `compressionLevel` : Recommended values are between 4 and 9, although any value between 0 and LZ4HC_MAX_CLEVEL will work. + 0 means "use default value" (see lz4hc.c). + Values >LZ4HC_MAX_CLEVEL behave the same as 16. + @return : the number of bytes written into buffer 'dst' + or 0 if compression fails. + */ + LZ4FLIB_API int LZ4_compress_HC( const char* src, char* dst, int srcSize, int maxDstSize, int compressionLevel ); + + + /* Note : + Decompression functions are provided within "lz4.h" (BSD license) + */ + + + /*! + LZ4_compress_HC_extStateHC() : + Use this function if you prefer to manually allocate memory for compression tables. + To know how much memory must be allocated for the compression tables, use : + int LZ4_sizeofStateHC(); + + Allocated memory must be aligned on 8-bytes boundaries (which a normal malloc() will do properly). + + The allocated memory can then be provided to the compression functions using 'void* state' parameter. + LZ4_compress_HC_extStateHC() is equivalent to previously described function. + It just uses externally allocated memory for stateHC. + */ + LZ4FLIB_API int LZ4_compress_HC_extStateHC( void* state, const char* src, char* dst, int srcSize, int maxDstSize, int compressionLevel ); + LZ4FLIB_API int LZ4_sizeofStateHC( void ); + + + /*-************************************ + * Streaming Compression + **************************************/ +#define LZ4_STREAMHCSIZE 262192 +#define LZ4_STREAMHCSIZE_SIZET (LZ4_STREAMHCSIZE / sizeof(size_t)) + typedef struct { size_t table[LZ4_STREAMHCSIZE_SIZET]; } LZ4_streamHC_t; + /* + LZ4_streamHC_t + This structure allows static allocation of LZ4 HC streaming state. + State must then be initialized using LZ4_resetStreamHC() before first use. + + Static allocation should only be used in combination with static linking. + If you want to use LZ4 as a DLL, please use construction functions below, which are future-proof. + */ + + + /*! LZ4_createStreamHC() and LZ4_freeStreamHC() : + These functions create and release memory for LZ4 HC streaming state. + Newly created states are already initialized. + Existing state space can be re-used anytime using LZ4_resetStreamHC(). + If you use LZ4 as a DLL, use these functions instead of static structure allocation, + to avoid size mismatch between different versions. + */ + LZ4FLIB_API LZ4_streamHC_t* LZ4_createStreamHC( void ); + LZ4FLIB_API int LZ4_freeStreamHC( LZ4_streamHC_t* streamHCPtr ); + + LZ4FLIB_API void LZ4_resetStreamHC( LZ4_streamHC_t* streamHCPtr, int compressionLevel ); + LZ4FLIB_API int LZ4_loadDictHC( LZ4_streamHC_t* streamHCPtr, const char* dictionary, int dictSize ); + + LZ4FLIB_API int LZ4_compress_HC_continue( LZ4_streamHC_t* streamHCPtr, const char* src, char* dst, int srcSize, int maxDstSize ); + + LZ4FLIB_API int LZ4_saveDictHC( LZ4_streamHC_t* streamHCPtr, char* safeBuffer, int maxDictSize ); + + /* + These functions compress data in successive blocks of any size, using previous blocks as dictionary. + One key assumption is that previous blocks (up to 64 KB) remain read-accessible while compressing next blocks. + There is an exception for ring buffers, which can be smaller than 64 KB. + Such case is automatically detected and correctly handled by LZ4_compress_HC_continue(). + + Before starting compression, state must be properly initialized, using LZ4_resetStreamHC(). + A first "fictional block" can then be designated as initial dictionary, using LZ4_loadDictHC() (Optional). + + Then, use LZ4_compress_HC_continue() to compress each successive block. + It works like LZ4_compress_HC(), but use previous memory blocks as dictionary to improve compression. + Previous memory blocks (including initial dictionary when present) must remain accessible and unmodified during compression. + As a reminder, size 'dst' buffer to handle worst cases, using LZ4_compressBound(), to ensure success of compression operation. + + If, for any reason, previous data blocks can't be preserved unmodified in memory during next compression block, + you must save it to a safer memory space, using LZ4_saveDictHC(). + Return value of LZ4_saveDictHC() is the size of dictionary effectively saved into 'safeBuffer'. + */ + + + + /*-************************************ + * Deprecated Functions + **************************************/ + /* Deprecate Warnings */ + /* Should these warnings messages be a problem, + it is generally possible to disable them, + with -Wno-deprecated-declarations for gcc + or _CRT_SECURE_NO_WARNINGS in Visual for example. + You can also define LZ4_DEPRECATE_WARNING_DEFBLOCK. */ +#ifndef LZ4_DEPRECATE_WARNING_DEFBLOCK +# define LZ4_DEPRECATE_WARNING_DEFBLOCK +# define LZ4_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) +# if (LZ4_GCC_VERSION >= 405) || defined(__clang__) +# define LZ4_DEPRECATED(message) __attribute__((deprecated(message))) +# elif (LZ4_GCC_VERSION >= 301) +# define LZ4_DEPRECATED(message) __attribute__((deprecated)) +# elif defined(_MSC_VER) +# define LZ4_DEPRECATED(message) __declspec(deprecated(message)) +# else +# pragma message("WARNING: You need to implement LZ4_DEPRECATED for this compiler") +# define LZ4_DEPRECATED(message) +# endif +#endif /* LZ4_DEPRECATE_WARNING_DEFBLOCK */ + + /* deprecated compression functions */ + /* these functions will trigger warning messages in future releases */ + LZ4FLIB_API int LZ4_compressHC( const char* source, char* dest, int inputSize ); + LZ4FLIB_API int LZ4_compressHC_limitedOutput( const char* source, char* dest, int inputSize, int maxOutputSize ); + LZ4_DEPRECATED( "use LZ4_compress_HC() instead" ) int LZ4_compressHC2( const char* source, char* dest, int inputSize, int compressionLevel ); + LZ4_DEPRECATED( "use LZ4_compress_HC() instead" ) int LZ4_compressHC2_limitedOutput( const char* source, char* dest, int inputSize, int maxOutputSize, int compressionLevel ); + LZ4FLIB_API int LZ4_compressHC_withStateHC( void* state, const char* source, char* dest, int inputSize ); + LZ4FLIB_API int LZ4_compressHC_limitedOutput_withStateHC( void* state, const char* source, char* dest, int inputSize, int maxOutputSize ); + LZ4_DEPRECATED( "use LZ4_compress_HC_extStateHC() instead" ) int LZ4_compressHC2_withStateHC( void* state, const char* source, char* dest, int inputSize, int compressionLevel ); + LZ4_DEPRECATED( "use LZ4_compress_HC_extStateHC() instead" ) int LZ4_compressHC2_limitedOutput_withStateHC( void* state, const char* source, char* dest, int inputSize, int maxOutputSize, int compressionLevel ); + LZ4FLIB_API int LZ4_compressHC_continue( LZ4_streamHC_t* LZ4_streamHCPtr, const char* source, char* dest, int inputSize ); + LZ4FLIB_API int LZ4_compressHC_limitedOutput_continue( LZ4_streamHC_t* LZ4_streamHCPtr, const char* source, char* dest, int inputSize, int maxOutputSize ); + + /* Deprecated Streaming functions using older model; should no longer be used */ + LZ4_DEPRECATED( "use LZ4_createStreamHC() instead" ) void* LZ4_createHC( char* inputBuffer ); + LZ4_DEPRECATED( "use LZ4_saveDictHC() instead" ) char* LZ4_slideInputBufferHC( void* LZ4HC_Data ); + LZ4_DEPRECATED( "use LZ4_freeStreamHC() instead" ) int LZ4_freeHC( void* LZ4HC_Data ); + LZ4_DEPRECATED( "use LZ4_compress_HC_continue() instead" ) int LZ4_compressHC2_continue( void* LZ4HC_Data, const char* source, char* dest, int inputSize, int compressionLevel ); + LZ4_DEPRECATED( "use LZ4_compress_HC_continue() instead" ) int LZ4_compressHC2_limitedOutput_continue( void* LZ4HC_Data, const char* source, char* dest, int inputSize, int maxOutputSize, int compressionLevel ); + LZ4_DEPRECATED( "use LZ4_createStreamHC() instead" ) int LZ4_sizeofStreamStateHC( void ); + LZ4_DEPRECATED( "use LZ4_resetStreamHC() instead" ) int LZ4_resetStreamStateHC( void* state, char* inputBuffer ); + + +/* + LZ4 auto-framing library + Header File + Copyright (C) 2011-2015, Yann Collet. + BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + You can contact the author at : + - LZ4 source repository : https://github.com/Cyan4973/lz4 + - LZ4 public forum : https://groups.google.com/forum/#!forum/lz4c +*/ + +/* LZ4F is a stand-alone API to create LZ4-compressed frames + * conformant with specification v1.5.1. + * All related operations, including memory management, are handled internally by the library. + * You don't need lz4.h when using lz4frame.h. + * */ +/*-************************************ +* Error management +**************************************/ +typedef size_t LZ4F_errorCode_t; + +LZ4FLIB_API unsigned LZ4F_isError(LZ4F_errorCode_t code); +LZ4FLIB_API const char* LZ4F_getErrorName(LZ4F_errorCode_t code); /* return error code string; useful for debugging */ + + +/*-************************************ +* Frame compression types +**************************************/ +//#define LZ4F_DISABLE_OBSOLETE_ENUMS +#ifndef LZ4F_DISABLE_OBSOLETE_ENUMS +# define LZ4F_OBSOLETE_ENUM(x) ,x +#else +# define LZ4F_OBSOLETE_ENUM(x) +#endif + +typedef enum { + LZ4F_default=0, + LZ4F_max64KB=4, + LZ4F_max256KB=5, + LZ4F_max1MB=6, + LZ4F_max4MB=7 + LZ4F_OBSOLETE_ENUM(max64KB = LZ4F_max64KB) + LZ4F_OBSOLETE_ENUM(max256KB = LZ4F_max256KB) + LZ4F_OBSOLETE_ENUM(max1MB = LZ4F_max1MB) + LZ4F_OBSOLETE_ENUM(max4MB = LZ4F_max4MB) +} LZ4F_blockSizeID_t; + +typedef enum { + LZ4F_blockLinked=0, + LZ4F_blockIndependent + LZ4F_OBSOLETE_ENUM(blockLinked = LZ4F_blockLinked) + LZ4F_OBSOLETE_ENUM(blockIndependent = LZ4F_blockIndependent) +} LZ4F_blockMode_t; + +typedef enum { + LZ4F_noContentChecksum=0, + LZ4F_contentChecksumEnabled + LZ4F_OBSOLETE_ENUM(noContentChecksum = LZ4F_noContentChecksum) + LZ4F_OBSOLETE_ENUM(contentChecksumEnabled = LZ4F_contentChecksumEnabled) +} LZ4F_contentChecksum_t; + +typedef enum { + LZ4F_frame=0, + LZ4F_skippableFrame + LZ4F_OBSOLETE_ENUM(skippableFrame = LZ4F_skippableFrame) +} LZ4F_frameType_t; + +#ifndef LZ4F_DISABLE_OBSOLETE_ENUMS +typedef LZ4F_blockSizeID_t blockSizeID_t; +typedef LZ4F_blockMode_t blockMode_t; +typedef LZ4F_frameType_t frameType_t; +typedef LZ4F_contentChecksum_t contentChecksum_t; +#endif + +typedef struct { + LZ4F_blockSizeID_t blockSizeID; /* max64KB, max256KB, max1MB, max4MB ; 0 == default */ + LZ4F_blockMode_t blockMode; /* blockLinked, blockIndependent ; 0 == default */ + LZ4F_contentChecksum_t contentChecksumFlag; /* noContentChecksum, contentChecksumEnabled ; 0 == default */ + LZ4F_frameType_t frameType; /* LZ4F_frame, skippableFrame ; 0 == default */ + unsigned long long contentSize; /* Size of uncompressed (original) content ; 0 == unknown */ + unsigned reserved[2]; /* must be zero for forward compatibility */ +} LZ4F_frameInfo_t; + +typedef struct { + LZ4F_frameInfo_t frameInfo; + int compressionLevel; /* 0 == default (fast mode); values above 16 count as 16; values below 0 count as 0 */ + unsigned autoFlush; /* 1 == always flush (reduce need for tmp buffer) */ + unsigned reserved[4]; /* must be zero for forward compatibility */ +} LZ4F_preferences_t; + + +/*-********************************* +* Simple compression function +***********************************/ +LZ4FLIB_API size_t LZ4F_compressFrameBound(size_t srcSize, const LZ4F_preferences_t* preferencesPtr); + +/*!LZ4F_compressFrame() : + * Compress an entire srcBuffer into a valid LZ4 frame, as defined by specification v1.5.1 + * The most important rule is that dstBuffer MUST be large enough (dstMaxSize) to ensure compression completion even in worst case. + * You can get the minimum value of dstMaxSize by using LZ4F_compressFrameBound() + * If this condition is not respected, LZ4F_compressFrame() will fail (result is an errorCode) + * The LZ4F_preferences_t structure is optional : you can provide NULL as argument. All preferences will be set to default. + * The result of the function is the number of bytes written into dstBuffer. + * The function outputs an error code if it fails (can be tested using LZ4F_isError()) + */ +LZ4FLIB_API size_t LZ4F_compressFrame(void* dstBuffer, size_t dstMaxSize, const void* srcBuffer, size_t srcSize, const LZ4F_preferences_t* preferencesPtr); + + + +/*-*********************************** +* Advanced compression functions +*************************************/ +typedef struct LZ4F_cctx_s* LZ4F_compressionContext_t; /* must be aligned on 8-bytes */ + +typedef struct { + unsigned stableSrc; /* 1 == src content will remain available on future calls to LZ4F_compress(); avoid saving src content within tmp buffer as future dictionary */ + unsigned reserved[3]; +} LZ4F_compressOptions_t; + +/* Resource Management */ + +#define LZ4F_VERSION 100 +LZ4FLIB_API LZ4F_errorCode_t LZ4F_createCompressionContext(LZ4F_compressionContext_t* cctxPtr, unsigned version); +LZ4FLIB_API LZ4F_errorCode_t LZ4F_freeCompressionContext(LZ4F_compressionContext_t cctx); +/* LZ4F_createCompressionContext() : + * The first thing to do is to create a compressionContext object, which will be used in all compression operations. + * This is achieved using LZ4F_createCompressionContext(), which takes as argument a version and an LZ4F_preferences_t structure. + * The version provided MUST be LZ4F_VERSION. It is intended to track potential version differences between different binaries. + * The function will provide a pointer to a fully allocated LZ4F_compressionContext_t object. + * If the result LZ4F_errorCode_t is not zero, there was an error during context creation. + * Object can release its memory using LZ4F_freeCompressionContext(); + */ + + +/* Compression */ + +LZ4FLIB_API size_t LZ4F_compressBegin(LZ4F_compressionContext_t cctx, void* dstBuffer, size_t dstMaxSize, const LZ4F_preferences_t* prefsPtr); +/* LZ4F_compressBegin() : + * will write the frame header into dstBuffer. + * dstBuffer must be large enough to accommodate a header (dstMaxSize). Maximum header size is 15 bytes. + * The LZ4F_preferences_t structure is optional : you can provide NULL as argument, all preferences will then be set to default. + * The result of the function is the number of bytes written into dstBuffer for the header + * or an error code (can be tested using LZ4F_isError()) + */ + +LZ4FLIB_API size_t LZ4F_compressBound(size_t srcSize, const LZ4F_preferences_t* prefsPtr); +/* LZ4F_compressBound() : + * Provides the minimum size of Dst buffer given srcSize to handle worst case situations. + * Different preferences can produce different results. + * prefsPtr is optional : you can provide NULL as argument, all preferences will then be set to cover worst case. + * This function includes frame termination cost (4 bytes, or 8 if frame checksum is enabled) + */ + +LZ4FLIB_API size_t LZ4F_compressUpdate(LZ4F_compressionContext_t cctx, void* dstBuffer, size_t dstMaxSize, const void* srcBuffer, size_t srcSize, const LZ4F_compressOptions_t* cOptPtr); +/* LZ4F_compressUpdate() + * LZ4F_compressUpdate() can be called repetitively to compress as much data as necessary. + * The most important rule is that dstBuffer MUST be large enough (dstMaxSize) to ensure compression completion even in worst case. + * You can get the minimum value of dstMaxSize by using LZ4F_compressBound(). + * If this condition is not respected, LZ4F_compress() will fail (result is an errorCode). + * LZ4F_compressUpdate() doesn't guarantee error recovery, so you have to reset compression context when an error occurs. + * The LZ4F_compressOptions_t structure is optional : you can provide NULL as argument. + * The result of the function is the number of bytes written into dstBuffer : it can be zero, meaning input data was just buffered. + * The function outputs an error code if it fails (can be tested using LZ4F_isError()) + */ + +LZ4FLIB_API size_t LZ4F_flush(LZ4F_compressionContext_t cctx, void* dstBuffer, size_t dstMaxSize, const LZ4F_compressOptions_t* cOptPtr); +/* LZ4F_flush() + * Should you need to generate compressed data immediately, without waiting for the current block to be filled, + * you can call LZ4_flush(), which will immediately compress any remaining data buffered within cctx. + * Note that dstMaxSize must be large enough to ensure the operation will be successful. + * LZ4F_compressOptions_t structure is optional : you can provide NULL as argument. + * The result of the function is the number of bytes written into dstBuffer + * (it can be zero, this means there was no data left within cctx) + * The function outputs an error code if it fails (can be tested using LZ4F_isError()) + */ + +LZ4FLIB_API size_t LZ4F_compressEnd(LZ4F_compressionContext_t cctx, void* dstBuffer, size_t dstMaxSize, const LZ4F_compressOptions_t* cOptPtr); +/* LZ4F_compressEnd() + * When you want to properly finish the compressed frame, just call LZ4F_compressEnd(). + * It will flush whatever data remained within compressionContext (like LZ4_flush()) + * but also properly finalize the frame, with an endMark and a checksum. + * The result of the function is the number of bytes written into dstBuffer (necessarily >= 4 (endMark), or 8 if optional frame checksum is enabled) + * The function outputs an error code if it fails (can be tested using LZ4F_isError()) + * The LZ4F_compressOptions_t structure is optional : you can provide NULL as argument. + * A successful call to LZ4F_compressEnd() makes cctx available again for next compression task. + */ + + +/*-********************************* +* Decompression functions +***********************************/ + +typedef struct LZ4F_dctx_s* LZ4F_decompressionContext_t; /* must be aligned on 8-bytes */ + +typedef struct { + unsigned stableDst; /* guarantee that decompressed data will still be there on next function calls (avoid storage into tmp buffers) */ + unsigned reserved[3]; +} LZ4F_decompressOptions_t; + + +/* Resource management */ + +/*!LZ4F_createDecompressionContext() : + * Create an LZ4F_decompressionContext_t object, which will be used to track all decompression operations. + * The version provided MUST be LZ4F_VERSION. It is intended to track potential breaking differences between different versions. + * The function will provide a pointer to a fully allocated and initialized LZ4F_decompressionContext_t object. + * The result is an errorCode, which can be tested using LZ4F_isError(). + * dctx memory can be released using LZ4F_freeDecompressionContext(); + * The result of LZ4F_freeDecompressionContext() is indicative of the current state of decompressionContext when being released. + * That is, it should be == 0 if decompression has been completed fully and correctly. + */ +LZ4FLIB_API LZ4F_errorCode_t LZ4F_createDecompressionContext(LZ4F_decompressionContext_t* dctxPtr, unsigned version); +LZ4FLIB_API LZ4F_errorCode_t LZ4F_freeDecompressionContext(LZ4F_decompressionContext_t dctx); + + +/*====== Decompression ======*/ + +/*!LZ4F_getFrameInfo() : + * This function decodes frame header information (such as max blockSize, frame checksum, etc.). + * Its usage is optional. The objective is to extract frame header information, typically for allocation purposes. + * A header size is variable and can be from 7 to 15 bytes. It's also possible to input more bytes than that. + * The number of bytes read from srcBuffer will be updated within *srcSizePtr (necessarily <= original value). + * (note that LZ4F_getFrameInfo() can also be used anytime *after* starting decompression, in this case 0 input byte is enough) + * Frame header info is *copied into* an already allocated LZ4F_frameInfo_t structure. + * The function result is an hint about how many srcSize bytes LZ4F_decompress() expects for next call, + * or an error code which can be tested using LZ4F_isError() + * (typically, when there is not enough src bytes to fully decode the frame header) + * Decompression is expected to resume from where it stopped (srcBuffer + *srcSizePtr) + */ +LZ4FLIB_API size_t LZ4F_getFrameInfo(LZ4F_decompressionContext_t dctx, + LZ4F_frameInfo_t* frameInfoPtr, + const void* srcBuffer, size_t* srcSizePtr); + +/*!LZ4F_decompress() : + * Call this function repetitively to regenerate data compressed within srcBuffer. + * The function will attempt to decode *srcSizePtr bytes from srcBuffer, into dstBuffer of maximum size *dstSizePtr. + * + * The number of bytes regenerated into dstBuffer will be provided within *dstSizePtr (necessarily <= original value). + * + * The number of bytes read from srcBuffer will be provided within *srcSizePtr (necessarily <= original value). + * If number of bytes read is < number of bytes provided, then decompression operation is not completed. + * It typically happens when dstBuffer is not large enough to contain all decoded data. + * LZ4F_decompress() must be called again, starting from where it stopped (srcBuffer + *srcSizePtr) + * The function will check this condition, and refuse to continue if it is not respected. + * + * `dstBuffer` is expected to be flushed between each call to the function, its content will be overwritten. + * `dst` arguments can be changed at will at each consecutive call to the function. + * + * The function result is an hint of how many `srcSize` bytes LZ4F_decompress() expects for next call. + * Schematically, it's the size of the current (or remaining) compressed block + header of next block. + * Respecting the hint provides some boost to performance, since it does skip intermediate buffers. + * This is just a hint though, it's always possible to provide any srcSize. + * When a frame is fully decoded, the function result will be 0 (no more data expected). + * If decompression failed, function result is an error code, which can be tested using LZ4F_isError(). + * + * After a frame is fully decoded, dctx can be used again to decompress another frame. + */ +LZ4FLIB_API size_t LZ4F_decompress(LZ4F_decompressionContext_t dctx, + void* dstBuffer, size_t* dstSizePtr, + const void* srcBuffer, size_t* srcSizePtr, + const LZ4F_decompressOptions_t* dOptPtr); + + + +#if defined (__cplusplus) +} +#endif diff --git a/lib/qhull b/lib/qhull index 60d558197..9f0abe778 160000 --- a/lib/qhull +++ b/lib/qhull @@ -1 +1 @@ -Subproject commit 60d55819729d7b49391dde0271e15a56c70992b9 +Subproject commit 9f0abe778c8ae80951f14066dfc86e4d6d391177 diff --git a/lib/soil/SOIL.c b/lib/soil/SOIL.c new file mode 100644 index 000000000..af28c9565 --- /dev/null +++ b/lib/soil/SOIL.c @@ -0,0 +1,2039 @@ +/* + Jonathan Dummer + 2007-07-26-10.36 + + Simple OpenGL Image Library + + Public Domain + using Sean Barret's stb_image as a base + + Thanks to: + * Sean Barret - for the awesome stb_image + * Dan Venkitachalam - for finding some non-compliant DDS files, and patching some explicit casts + * everybody at gamedev.net +*/ + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4018 4100 4244) +#else +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wsign-compare" +#pragma GCC diagnostic ignored "-Wunused-parameter" +#endif + +#define SOIL_CHECK_FOR_GL_ERRORS 0 + +#ifdef WIN32 + #define WIN32_LEAN_AND_MEAN + #include + #include + #include +#elif defined(__APPLE__) || defined(__APPLE_CC__) + /* I can't test this Apple stuff! */ + #include + #include + #define APIENTRY +#else + #include + #include +#endif + +#include "SOIL.h" +#include "stb_image_aug.h" +#include "image_helper.h" +#include "image_DXT.h" + +#include +#include + +/* error reporting */ +char *result_string_pointer = "SOIL initialized"; + +/* for loading cube maps */ +enum{ + SOIL_CAPABILITY_UNKNOWN = -1, + SOIL_CAPABILITY_NONE = 0, + SOIL_CAPABILITY_PRESENT = 1 +}; +static int has_cubemap_capability = SOIL_CAPABILITY_UNKNOWN; +int query_cubemap_capability( void ); +#define SOIL_TEXTURE_WRAP_R 0x8072 +#define SOIL_CLAMP_TO_EDGE 0x812F +#define SOIL_NORMAL_MAP 0x8511 +#define SOIL_REFLECTION_MAP 0x8512 +#define SOIL_TEXTURE_CUBE_MAP 0x8513 +#define SOIL_TEXTURE_BINDING_CUBE_MAP 0x8514 +#define SOIL_TEXTURE_CUBE_MAP_POSITIVE_X 0x8515 +#define SOIL_TEXTURE_CUBE_MAP_NEGATIVE_X 0x8516 +#define SOIL_TEXTURE_CUBE_MAP_POSITIVE_Y 0x8517 +#define SOIL_TEXTURE_CUBE_MAP_NEGATIVE_Y 0x8518 +#define SOIL_TEXTURE_CUBE_MAP_POSITIVE_Z 0x8519 +#define SOIL_TEXTURE_CUBE_MAP_NEGATIVE_Z 0x851A +#define SOIL_PROXY_TEXTURE_CUBE_MAP 0x851B +#define SOIL_MAX_CUBE_MAP_TEXTURE_SIZE 0x851C +/* for non-power-of-two texture */ +static int has_NPOT_capability = SOIL_CAPABILITY_UNKNOWN; +int query_NPOT_capability( void ); +/* for texture rectangles */ +static int has_tex_rectangle_capability = SOIL_CAPABILITY_UNKNOWN; +int query_tex_rectangle_capability( void ); +#define SOIL_TEXTURE_RECTANGLE_ARB 0x84F5 +#define SOIL_MAX_RECTANGLE_TEXTURE_SIZE_ARB 0x84F8 +/* for using DXT compression */ +static int has_DXT_capability = SOIL_CAPABILITY_UNKNOWN; +int query_DXT_capability( void ); +#define SOIL_RGB_S3TC_DXT1 0x83F0 +#define SOIL_RGBA_S3TC_DXT1 0x83F1 +#define SOIL_RGBA_S3TC_DXT3 0x83F2 +#define SOIL_RGBA_S3TC_DXT5 0x83F3 +typedef void (APIENTRY * P_SOIL_GLCOMPRESSEDTEXIMAGE2DPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid * data); +P_SOIL_GLCOMPRESSEDTEXIMAGE2DPROC soilGlCompressedTexImage2D = NULL; +unsigned int SOIL_direct_load_DDS( + const char *filename, + unsigned int reuse_texture_ID, + int flags, + int loading_as_cubemap ); +unsigned int SOIL_direct_load_DDS_from_memory( + const unsigned char *const buffer, + int buffer_length, + unsigned int reuse_texture_ID, + int flags, + int loading_as_cubemap ); +/* other functions */ +unsigned int + SOIL_internal_create_OGL_texture + ( + const unsigned char *const data, + int width, int height, int channels, + unsigned int reuse_texture_ID, + unsigned int flags, + unsigned int opengl_texture_type, + unsigned int opengl_texture_target, + unsigned int texture_check_size_enum + ); + +/* and the code magic begins here [8^) */ +unsigned int + SOIL_load_OGL_texture + ( + const char *filename, + int force_channels, + unsigned int reuse_texture_ID, + unsigned int flags + ) +{ + /* variables */ + unsigned char* img; + int width, height, channels; + unsigned int tex_id; + /* does the user want direct uploading of the image as a DDS file? */ + if( flags & SOIL_FLAG_DDS_LOAD_DIRECT ) + { + /* 1st try direct loading of the image as a DDS file + note: direct uploading will only load what is in the + DDS file, no MIPmaps will be generated, the image will + not be flipped, etc. */ + tex_id = SOIL_direct_load_DDS( filename, reuse_texture_ID, flags, 0 ); + if( tex_id ) + { + /* hey, it worked!! */ + return tex_id; + } + } + /* try to load the image */ + img = SOIL_load_image( filename, &width, &height, &channels, force_channels ); + /* channels holds the original number of channels, which may have been forced */ + if( (force_channels >= 1) && (force_channels <= 4) ) + { + channels = force_channels; + } + if( NULL == img ) + { + /* image loading failed */ + result_string_pointer = stbi_failure_reason(); + return 0; + } + /* OK, make it a texture! */ + tex_id = SOIL_internal_create_OGL_texture( + img, width, height, channels, + reuse_texture_ID, flags, + GL_TEXTURE_2D, GL_TEXTURE_2D, + GL_MAX_TEXTURE_SIZE ); + /* and nuke the image data */ + SOIL_free_image_data( img ); + /* and return the handle, such as it is */ + return tex_id; +} + +unsigned int + SOIL_load_OGL_HDR_texture + ( + const char *filename, + int fake_HDR_format, + int rescale_to_max, + unsigned int reuse_texture_ID, + unsigned int flags + ) +{ + /* variables */ + unsigned char* img; + int width, height, channels; + unsigned int tex_id; + /* no direct uploading of the image as a DDS file */ + /* error check */ + if( (fake_HDR_format != SOIL_HDR_RGBE) && + (fake_HDR_format != SOIL_HDR_RGBdivA) && + (fake_HDR_format != SOIL_HDR_RGBdivA2) ) + { + result_string_pointer = "Invalid fake HDR format specified"; + return 0; + } + /* try to load the image (only the HDR type) */ + img = stbi_hdr_load_rgbe( filename, &width, &height, &channels, 4 ); + /* channels holds the original number of channels, which may have been forced */ + if( NULL == img ) + { + /* image loading failed */ + result_string_pointer = stbi_failure_reason(); + return 0; + } + /* the load worked, do I need to convert it? */ + if( fake_HDR_format == SOIL_HDR_RGBdivA ) + { + RGBE_to_RGBdivA( img, width, height, rescale_to_max ); + } else if( fake_HDR_format == SOIL_HDR_RGBdivA2 ) + { + RGBE_to_RGBdivA2( img, width, height, rescale_to_max ); + } + /* OK, make it a texture! */ + tex_id = SOIL_internal_create_OGL_texture( + img, width, height, channels, + reuse_texture_ID, flags, + GL_TEXTURE_2D, GL_TEXTURE_2D, + GL_MAX_TEXTURE_SIZE ); + /* and nuke the image data */ + SOIL_free_image_data( img ); + /* and return the handle, such as it is */ + return tex_id; +} + +unsigned int + SOIL_load_OGL_texture_from_memory + ( + const unsigned char *const buffer, + int buffer_length, + int force_channels, + unsigned int reuse_texture_ID, + unsigned int flags + ) +{ + /* variables */ + unsigned char* img; + int width, height, channels; + unsigned int tex_id; + /* does the user want direct uploading of the image as a DDS file? */ + if( flags & SOIL_FLAG_DDS_LOAD_DIRECT ) + { + /* 1st try direct loading of the image as a DDS file + note: direct uploading will only load what is in the + DDS file, no MIPmaps will be generated, the image will + not be flipped, etc. */ + tex_id = SOIL_direct_load_DDS_from_memory( + buffer, buffer_length, + reuse_texture_ID, flags, 0 ); + if( tex_id ) + { + /* hey, it worked!! */ + return tex_id; + } + } + /* try to load the image */ + img = SOIL_load_image_from_memory( + buffer, buffer_length, + &width, &height, &channels, + force_channels ); + /* channels holds the original number of channels, which may have been forced */ + if( (force_channels >= 1) && (force_channels <= 4) ) + { + channels = force_channels; + } + if( NULL == img ) + { + /* image loading failed */ + result_string_pointer = stbi_failure_reason(); + return 0; + } + /* OK, make it a texture! */ + tex_id = SOIL_internal_create_OGL_texture( + img, width, height, channels, + reuse_texture_ID, flags, + GL_TEXTURE_2D, GL_TEXTURE_2D, + GL_MAX_TEXTURE_SIZE ); + /* and nuke the image data */ + SOIL_free_image_data( img ); + /* and return the handle, such as it is */ + return tex_id; +} + +unsigned int + SOIL_load_OGL_cubemap + ( + const char *x_pos_file, + const char *x_neg_file, + const char *y_pos_file, + const char *y_neg_file, + const char *z_pos_file, + const char *z_neg_file, + int force_channels, + unsigned int reuse_texture_ID, + unsigned int flags + ) +{ + /* variables */ + unsigned char* img; + int width, height, channels; + unsigned int tex_id; + /* error checking */ + if( (x_pos_file == NULL) || + (x_neg_file == NULL) || + (y_pos_file == NULL) || + (y_neg_file == NULL) || + (z_pos_file == NULL) || + (z_neg_file == NULL) ) + { + result_string_pointer = "Invalid cube map files list"; + return 0; + } + /* capability checking */ + if( query_cubemap_capability() != SOIL_CAPABILITY_PRESENT ) + { + result_string_pointer = "No cube map capability present"; + return 0; + } + /* 1st face: try to load the image */ + img = SOIL_load_image( x_pos_file, &width, &height, &channels, force_channels ); + /* channels holds the original number of channels, which may have been forced */ + if( (force_channels >= 1) && (force_channels <= 4) ) + { + channels = force_channels; + } + if( NULL == img ) + { + /* image loading failed */ + result_string_pointer = stbi_failure_reason(); + return 0; + } + /* upload the texture, and create a texture ID if necessary */ + tex_id = SOIL_internal_create_OGL_texture( + img, width, height, channels, + reuse_texture_ID, flags, + SOIL_TEXTURE_CUBE_MAP, SOIL_TEXTURE_CUBE_MAP_POSITIVE_X, + SOIL_MAX_CUBE_MAP_TEXTURE_SIZE ); + /* and nuke the image data */ + SOIL_free_image_data( img ); + /* continue? */ + if( tex_id != 0 ) + { + /* 1st face: try to load the image */ + img = SOIL_load_image( x_neg_file, &width, &height, &channels, force_channels ); + /* channels holds the original number of channels, which may have been forced */ + if( (force_channels >= 1) && (force_channels <= 4) ) + { + channels = force_channels; + } + if( NULL == img ) + { + /* image loading failed */ + result_string_pointer = stbi_failure_reason(); + return 0; + } + /* upload the texture, but reuse the assigned texture ID */ + tex_id = SOIL_internal_create_OGL_texture( + img, width, height, channels, + tex_id, flags, + SOIL_TEXTURE_CUBE_MAP, SOIL_TEXTURE_CUBE_MAP_NEGATIVE_X, + SOIL_MAX_CUBE_MAP_TEXTURE_SIZE ); + /* and nuke the image data */ + SOIL_free_image_data( img ); + } + /* continue? */ + if( tex_id != 0 ) + { + /* 1st face: try to load the image */ + img = SOIL_load_image( y_pos_file, &width, &height, &channels, force_channels ); + /* channels holds the original number of channels, which may have been forced */ + if( (force_channels >= 1) && (force_channels <= 4) ) + { + channels = force_channels; + } + if( NULL == img ) + { + /* image loading failed */ + result_string_pointer = stbi_failure_reason(); + return 0; + } + /* upload the texture, but reuse the assigned texture ID */ + tex_id = SOIL_internal_create_OGL_texture( + img, width, height, channels, + tex_id, flags, + SOIL_TEXTURE_CUBE_MAP, SOIL_TEXTURE_CUBE_MAP_POSITIVE_Y, + SOIL_MAX_CUBE_MAP_TEXTURE_SIZE ); + /* and nuke the image data */ + SOIL_free_image_data( img ); + } + /* continue? */ + if( tex_id != 0 ) + { + /* 1st face: try to load the image */ + img = SOIL_load_image( y_neg_file, &width, &height, &channels, force_channels ); + /* channels holds the original number of channels, which may have been forced */ + if( (force_channels >= 1) && (force_channels <= 4) ) + { + channels = force_channels; + } + if( NULL == img ) + { + /* image loading failed */ + result_string_pointer = stbi_failure_reason(); + return 0; + } + /* upload the texture, but reuse the assigned texture ID */ + tex_id = SOIL_internal_create_OGL_texture( + img, width, height, channels, + tex_id, flags, + SOIL_TEXTURE_CUBE_MAP, SOIL_TEXTURE_CUBE_MAP_NEGATIVE_Y, + SOIL_MAX_CUBE_MAP_TEXTURE_SIZE ); + /* and nuke the image data */ + SOIL_free_image_data( img ); + } + /* continue? */ + if( tex_id != 0 ) + { + /* 1st face: try to load the image */ + img = SOIL_load_image( z_pos_file, &width, &height, &channels, force_channels ); + /* channels holds the original number of channels, which may have been forced */ + if( (force_channels >= 1) && (force_channels <= 4) ) + { + channels = force_channels; + } + if( NULL == img ) + { + /* image loading failed */ + result_string_pointer = stbi_failure_reason(); + return 0; + } + /* upload the texture, but reuse the assigned texture ID */ + tex_id = SOIL_internal_create_OGL_texture( + img, width, height, channels, + tex_id, flags, + SOIL_TEXTURE_CUBE_MAP, SOIL_TEXTURE_CUBE_MAP_POSITIVE_Z, + SOIL_MAX_CUBE_MAP_TEXTURE_SIZE ); + /* and nuke the image data */ + SOIL_free_image_data( img ); + } + /* continue? */ + if( tex_id != 0 ) + { + /* 1st face: try to load the image */ + img = SOIL_load_image( z_neg_file, &width, &height, &channels, force_channels ); + /* channels holds the original number of channels, which may have been forced */ + if( (force_channels >= 1) && (force_channels <= 4) ) + { + channels = force_channels; + } + if( NULL == img ) + { + /* image loading failed */ + result_string_pointer = stbi_failure_reason(); + return 0; + } + /* upload the texture, but reuse the assigned texture ID */ + tex_id = SOIL_internal_create_OGL_texture( + img, width, height, channels, + tex_id, flags, + SOIL_TEXTURE_CUBE_MAP, SOIL_TEXTURE_CUBE_MAP_NEGATIVE_Z, + SOIL_MAX_CUBE_MAP_TEXTURE_SIZE ); + /* and nuke the image data */ + SOIL_free_image_data( img ); + } + /* and return the handle, such as it is */ + return tex_id; +} + +unsigned int + SOIL_load_OGL_cubemap_from_memory + ( + const unsigned char *const x_pos_buffer, + int x_pos_buffer_length, + const unsigned char *const x_neg_buffer, + int x_neg_buffer_length, + const unsigned char *const y_pos_buffer, + int y_pos_buffer_length, + const unsigned char *const y_neg_buffer, + int y_neg_buffer_length, + const unsigned char *const z_pos_buffer, + int z_pos_buffer_length, + const unsigned char *const z_neg_buffer, + int z_neg_buffer_length, + int force_channels, + unsigned int reuse_texture_ID, + unsigned int flags + ) +{ + /* variables */ + unsigned char* img; + int width, height, channels; + unsigned int tex_id; + /* error checking */ + if( (x_pos_buffer == NULL) || + (x_neg_buffer == NULL) || + (y_pos_buffer == NULL) || + (y_neg_buffer == NULL) || + (z_pos_buffer == NULL) || + (z_neg_buffer == NULL) ) + { + result_string_pointer = "Invalid cube map buffers list"; + return 0; + } + /* capability checking */ + if( query_cubemap_capability() != SOIL_CAPABILITY_PRESENT ) + { + result_string_pointer = "No cube map capability present"; + return 0; + } + /* 1st face: try to load the image */ + img = SOIL_load_image_from_memory( + x_pos_buffer, x_pos_buffer_length, + &width, &height, &channels, force_channels ); + /* channels holds the original number of channels, which may have been forced */ + if( (force_channels >= 1) && (force_channels <= 4) ) + { + channels = force_channels; + } + if( NULL == img ) + { + /* image loading failed */ + result_string_pointer = stbi_failure_reason(); + return 0; + } + /* upload the texture, and create a texture ID if necessary */ + tex_id = SOIL_internal_create_OGL_texture( + img, width, height, channels, + reuse_texture_ID, flags, + SOIL_TEXTURE_CUBE_MAP, SOIL_TEXTURE_CUBE_MAP_POSITIVE_X, + SOIL_MAX_CUBE_MAP_TEXTURE_SIZE ); + /* and nuke the image data */ + SOIL_free_image_data( img ); + /* continue? */ + if( tex_id != 0 ) + { + /* 1st face: try to load the image */ + img = SOIL_load_image_from_memory( + x_neg_buffer, x_neg_buffer_length, + &width, &height, &channels, force_channels ); + /* channels holds the original number of channels, which may have been forced */ + if( (force_channels >= 1) && (force_channels <= 4) ) + { + channels = force_channels; + } + if( NULL == img ) + { + /* image loading failed */ + result_string_pointer = stbi_failure_reason(); + return 0; + } + /* upload the texture, but reuse the assigned texture ID */ + tex_id = SOIL_internal_create_OGL_texture( + img, width, height, channels, + tex_id, flags, + SOIL_TEXTURE_CUBE_MAP, SOIL_TEXTURE_CUBE_MAP_NEGATIVE_X, + SOIL_MAX_CUBE_MAP_TEXTURE_SIZE ); + /* and nuke the image data */ + SOIL_free_image_data( img ); + } + /* continue? */ + if( tex_id != 0 ) + { + /* 1st face: try to load the image */ + img = SOIL_load_image_from_memory( + y_pos_buffer, y_pos_buffer_length, + &width, &height, &channels, force_channels ); + /* channels holds the original number of channels, which may have been forced */ + if( (force_channels >= 1) && (force_channels <= 4) ) + { + channels = force_channels; + } + if( NULL == img ) + { + /* image loading failed */ + result_string_pointer = stbi_failure_reason(); + return 0; + } + /* upload the texture, but reuse the assigned texture ID */ + tex_id = SOIL_internal_create_OGL_texture( + img, width, height, channels, + tex_id, flags, + SOIL_TEXTURE_CUBE_MAP, SOIL_TEXTURE_CUBE_MAP_POSITIVE_Y, + SOIL_MAX_CUBE_MAP_TEXTURE_SIZE ); + /* and nuke the image data */ + SOIL_free_image_data( img ); + } + /* continue? */ + if( tex_id != 0 ) + { + /* 1st face: try to load the image */ + img = SOIL_load_image_from_memory( + y_neg_buffer, y_neg_buffer_length, + &width, &height, &channels, force_channels ); + /* channels holds the original number of channels, which may have been forced */ + if( (force_channels >= 1) && (force_channels <= 4) ) + { + channels = force_channels; + } + if( NULL == img ) + { + /* image loading failed */ + result_string_pointer = stbi_failure_reason(); + return 0; + } + /* upload the texture, but reuse the assigned texture ID */ + tex_id = SOIL_internal_create_OGL_texture( + img, width, height, channels, + tex_id, flags, + SOIL_TEXTURE_CUBE_MAP, SOIL_TEXTURE_CUBE_MAP_NEGATIVE_Y, + SOIL_MAX_CUBE_MAP_TEXTURE_SIZE ); + /* and nuke the image data */ + SOIL_free_image_data( img ); + } + /* continue? */ + if( tex_id != 0 ) + { + /* 1st face: try to load the image */ + img = SOIL_load_image_from_memory( + z_pos_buffer, z_pos_buffer_length, + &width, &height, &channels, force_channels ); + /* channels holds the original number of channels, which may have been forced */ + if( (force_channels >= 1) && (force_channels <= 4) ) + { + channels = force_channels; + } + if( NULL == img ) + { + /* image loading failed */ + result_string_pointer = stbi_failure_reason(); + return 0; + } + /* upload the texture, but reuse the assigned texture ID */ + tex_id = SOIL_internal_create_OGL_texture( + img, width, height, channels, + tex_id, flags, + SOIL_TEXTURE_CUBE_MAP, SOIL_TEXTURE_CUBE_MAP_POSITIVE_Z, + SOIL_MAX_CUBE_MAP_TEXTURE_SIZE ); + /* and nuke the image data */ + SOIL_free_image_data( img ); + } + /* continue? */ + if( tex_id != 0 ) + { + /* 1st face: try to load the image */ + img = SOIL_load_image_from_memory( + z_neg_buffer, z_neg_buffer_length, + &width, &height, &channels, force_channels ); + /* channels holds the original number of channels, which may have been forced */ + if( (force_channels >= 1) && (force_channels <= 4) ) + { + channels = force_channels; + } + if( NULL == img ) + { + /* image loading failed */ + result_string_pointer = stbi_failure_reason(); + return 0; + } + /* upload the texture, but reuse the assigned texture ID */ + tex_id = SOIL_internal_create_OGL_texture( + img, width, height, channels, + tex_id, flags, + SOIL_TEXTURE_CUBE_MAP, SOIL_TEXTURE_CUBE_MAP_NEGATIVE_Z, + SOIL_MAX_CUBE_MAP_TEXTURE_SIZE ); + /* and nuke the image data */ + SOIL_free_image_data( img ); + } + /* and return the handle, such as it is */ + return tex_id; +} + +unsigned int + SOIL_load_OGL_single_cubemap + ( + const char *filename, + const char face_order[6], + int force_channels, + unsigned int reuse_texture_ID, + unsigned int flags + ) +{ + /* variables */ + unsigned char* img; + int width, height, channels, i; + unsigned int tex_id = 0; + /* error checking */ + if( filename == NULL ) + { + result_string_pointer = "Invalid single cube map file name"; + return 0; + } + /* does the user want direct uploading of the image as a DDS file? */ + if( flags & SOIL_FLAG_DDS_LOAD_DIRECT ) + { + /* 1st try direct loading of the image as a DDS file + note: direct uploading will only load what is in the + DDS file, no MIPmaps will be generated, the image will + not be flipped, etc. */ + tex_id = SOIL_direct_load_DDS( filename, reuse_texture_ID, flags, 1 ); + if( tex_id ) + { + /* hey, it worked!! */ + return tex_id; + } + } + /* face order checking */ + for( i = 0; i < 6; ++i ) + { + if( (face_order[i] != 'N') && + (face_order[i] != 'S') && + (face_order[i] != 'W') && + (face_order[i] != 'E') && + (face_order[i] != 'U') && + (face_order[i] != 'D') ) + { + result_string_pointer = "Invalid single cube map face order"; + return 0; + }; + } + /* capability checking */ + if( query_cubemap_capability() != SOIL_CAPABILITY_PRESENT ) + { + result_string_pointer = "No cube map capability present"; + return 0; + } + /* 1st off, try to load the full image */ + img = SOIL_load_image( filename, &width, &height, &channels, force_channels ); + /* channels holds the original number of channels, which may have been forced */ + if( (force_channels >= 1) && (force_channels <= 4) ) + { + channels = force_channels; + } + if( NULL == img ) + { + /* image loading failed */ + result_string_pointer = stbi_failure_reason(); + return 0; + } + /* now, does this image have the right dimensions? */ + if( (width != 6*height) && + (6*width != height) ) + { + SOIL_free_image_data( img ); + result_string_pointer = "Single cubemap image must have a 6:1 ratio"; + return 0; + } + /* try the image split and create */ + tex_id = SOIL_create_OGL_single_cubemap( + img, width, height, channels, + face_order, reuse_texture_ID, flags + ); + /* nuke the temporary image data and return the texture handle */ + SOIL_free_image_data( img ); + return tex_id; +} + +unsigned int + SOIL_load_OGL_single_cubemap_from_memory + ( + const unsigned char *const buffer, + int buffer_length, + const char face_order[6], + int force_channels, + unsigned int reuse_texture_ID, + unsigned int flags + ) +{ + /* variables */ + unsigned char* img; + int width, height, channels, i; + unsigned int tex_id = 0; + /* error checking */ + if( buffer == NULL ) + { + result_string_pointer = "Invalid single cube map buffer"; + return 0; + } + /* does the user want direct uploading of the image as a DDS file? */ + if( flags & SOIL_FLAG_DDS_LOAD_DIRECT ) + { + /* 1st try direct loading of the image as a DDS file + note: direct uploading will only load what is in the + DDS file, no MIPmaps will be generated, the image will + not be flipped, etc. */ + tex_id = SOIL_direct_load_DDS_from_memory( + buffer, buffer_length, + reuse_texture_ID, flags, 1 ); + if( tex_id ) + { + /* hey, it worked!! */ + return tex_id; + } + } + /* face order checking */ + for( i = 0; i < 6; ++i ) + { + if( (face_order[i] != 'N') && + (face_order[i] != 'S') && + (face_order[i] != 'W') && + (face_order[i] != 'E') && + (face_order[i] != 'U') && + (face_order[i] != 'D') ) + { + result_string_pointer = "Invalid single cube map face order"; + return 0; + }; + } + /* capability checking */ + if( query_cubemap_capability() != SOIL_CAPABILITY_PRESENT ) + { + result_string_pointer = "No cube map capability present"; + return 0; + } + /* 1st off, try to load the full image */ + img = SOIL_load_image_from_memory( + buffer, buffer_length, + &width, &height, &channels, + force_channels ); + /* channels holds the original number of channels, which may have been forced */ + if( (force_channels >= 1) && (force_channels <= 4) ) + { + channels = force_channels; + } + if( NULL == img ) + { + /* image loading failed */ + result_string_pointer = stbi_failure_reason(); + return 0; + } + /* now, does this image have the right dimensions? */ + if( (width != 6*height) && + (6*width != height) ) + { + SOIL_free_image_data( img ); + result_string_pointer = "Single cubemap image must have a 6:1 ratio"; + return 0; + } + /* try the image split and create */ + tex_id = SOIL_create_OGL_single_cubemap( + img, width, height, channels, + face_order, reuse_texture_ID, flags + ); + /* nuke the temporary image data and return the texture handle */ + SOIL_free_image_data( img ); + return tex_id; +} + +unsigned int + SOIL_create_OGL_single_cubemap + ( + const unsigned char *const data, + int width, int height, int channels, + const char face_order[6], + unsigned int reuse_texture_ID, + unsigned int flags + ) +{ + /* variables */ + unsigned char* sub_img; + int dw, dh, sz, i; + unsigned int tex_id; + /* error checking */ + if( data == NULL ) + { + result_string_pointer = "Invalid single cube map image data"; + return 0; + } + /* face order checking */ + for( i = 0; i < 6; ++i ) + { + if( (face_order[i] != 'N') && + (face_order[i] != 'S') && + (face_order[i] != 'W') && + (face_order[i] != 'E') && + (face_order[i] != 'U') && + (face_order[i] != 'D') ) + { + result_string_pointer = "Invalid single cube map face order"; + return 0; + }; + } + /* capability checking */ + if( query_cubemap_capability() != SOIL_CAPABILITY_PRESENT ) + { + result_string_pointer = "No cube map capability present"; + return 0; + } + /* now, does this image have the right dimensions? */ + if( (width != 6*height) && + (6*width != height) ) + { + result_string_pointer = "Single cubemap image must have a 6:1 ratio"; + return 0; + } + /* which way am I stepping? */ + if( width > height ) + { + dw = height; + dh = 0; + } else + { + dw = 0; + dh = width; + } + sz = dw+dh; + sub_img = (unsigned char *)malloc( sz*sz*channels ); + /* do the splitting and uploading */ + tex_id = reuse_texture_ID; + for( i = 0; i < 6; ++i ) + { + int x, y, idx = 0; + unsigned int cubemap_target = 0; + /* copy in the sub-image */ + for( y = i*dh; y < i*dh+sz; ++y ) + { + for( x = i*dw*channels; x < (i*dw+sz)*channels; ++x ) + { + sub_img[idx++] = data[y*width*channels+x]; + } + } + /* what is my texture target? + remember, this coordinate system is + LHS if viewed from inside the cube! */ + switch( face_order[i] ) + { + case 'N': + cubemap_target = SOIL_TEXTURE_CUBE_MAP_POSITIVE_Z; + break; + case 'S': + cubemap_target = SOIL_TEXTURE_CUBE_MAP_NEGATIVE_Z; + break; + case 'W': + cubemap_target = SOIL_TEXTURE_CUBE_MAP_NEGATIVE_X; + break; + case 'E': + cubemap_target = SOIL_TEXTURE_CUBE_MAP_POSITIVE_X; + break; + case 'U': + cubemap_target = SOIL_TEXTURE_CUBE_MAP_POSITIVE_Y; + break; + case 'D': + cubemap_target = SOIL_TEXTURE_CUBE_MAP_NEGATIVE_Y; + break; + } + /* upload it as a texture */ + tex_id = SOIL_internal_create_OGL_texture( + sub_img, sz, sz, channels, + tex_id, flags, + SOIL_TEXTURE_CUBE_MAP, + cubemap_target, + SOIL_MAX_CUBE_MAP_TEXTURE_SIZE ); + } + /* and nuke the image and sub-image data */ + SOIL_free_image_data( sub_img ); + /* and return the handle, such as it is */ + return tex_id; +} + +unsigned int + SOIL_create_OGL_texture + ( + const unsigned char *const data, + int width, int height, int channels, + unsigned int reuse_texture_ID, + unsigned int flags + ) +{ + /* wrapper function for 2D textures */ + return SOIL_internal_create_OGL_texture( + data, width, height, channels, + reuse_texture_ID, flags, + GL_TEXTURE_2D, GL_TEXTURE_2D, + GL_MAX_TEXTURE_SIZE ); +} + +#if SOIL_CHECK_FOR_GL_ERRORS +void check_for_GL_errors( const char *calling_location ) +{ + /* check for errors */ + GLenum err_code = glGetError(); + while( GL_NO_ERROR != err_code ) + { + printf( "OpenGL Error @ %s: %i", calling_location, err_code ); + err_code = glGetError(); + } +} +#else +void check_for_GL_errors( const char *calling_location ) +{ + /* no check for errors */ +} +#endif + +unsigned int + SOIL_internal_create_OGL_texture + ( + const unsigned char *const data, + int width, int height, int channels, + unsigned int reuse_texture_ID, + unsigned int flags, + unsigned int opengl_texture_type, + unsigned int opengl_texture_target, + unsigned int texture_check_size_enum + ) +{ + /* variables */ + unsigned char* img; + unsigned int tex_id; + unsigned int internal_texture_format = 0, original_texture_format = 0; + int DXT_mode = SOIL_CAPABILITY_UNKNOWN; + int max_supported_size; + /* If the user wants to use the texture rectangle I kill a few flags */ + if( flags & SOIL_FLAG_TEXTURE_RECTANGLE ) + { + /* well, the user asked for it, can we do that? */ + if( query_tex_rectangle_capability() == SOIL_CAPABILITY_PRESENT ) + { + /* only allow this if the user in _NOT_ trying to do a cubemap! */ + if( opengl_texture_type == GL_TEXTURE_2D ) + { + /* clean out the flags that cannot be used with texture rectangles */ + flags &= ~( + SOIL_FLAG_POWER_OF_TWO | SOIL_FLAG_MIPMAPS | + SOIL_FLAG_TEXTURE_REPEATS + ); + /* and change my target */ + opengl_texture_target = SOIL_TEXTURE_RECTANGLE_ARB; + opengl_texture_type = SOIL_TEXTURE_RECTANGLE_ARB; + } else + { + /* not allowed for any other uses (yes, I'm looking at you, cubemaps!) */ + flags &= ~SOIL_FLAG_TEXTURE_RECTANGLE; + } + + } else + { + /* can't do it, and that is a breakable offense (uv coords use pixels instead of [0,1]!) */ + result_string_pointer = "Texture Rectangle extension unsupported"; + return 0; + } + } + /* create a copy the image data */ + img = (unsigned char*)malloc( width*height*channels ); + memcpy( img, data, width*height*channels ); + /* does the user want me to invert the image? */ + if( flags & SOIL_FLAG_INVERT_Y ) + { + int i, j; + for( j = 0; j*2 < height; ++j ) + { + int index1 = j * width * channels; + int index2 = (height - 1 - j) * width * channels; + for( i = width * channels; i > 0; --i ) + { + unsigned char temp = img[index1]; + img[index1] = img[index2]; + img[index2] = temp; + ++index1; + ++index2; + } + } + } + /* does the user want me to scale the colors into the NTSC safe RGB range? */ + if( flags & SOIL_FLAG_NTSC_SAFE_RGB ) + { + scale_image_RGB_to_NTSC_safe( img, width, height, channels ); + } + /* does the user want me to convert from straight to pre-multiplied alpha? + (and do we even _have_ alpha?) */ + if( flags & SOIL_FLAG_MULTIPLY_ALPHA ) + { + int i; + switch( channels ) + { + case 2: + for( i = 0; i < 2*width*height; i += 2 ) + { + img[i] = (img[i] * img[i+1] + 128) >> 8; + } + break; + case 4: + for( i = 0; i < 4*width*height; i += 4 ) + { + img[i+0] = (img[i+0] * img[i+3] + 128) >> 8; + img[i+1] = (img[i+1] * img[i+3] + 128) >> 8; + img[i+2] = (img[i+2] * img[i+3] + 128) >> 8; + } + break; + default: + /* no other number of channels contains alpha data */ + break; + } + } + /* if the user can't support NPOT textures, make sure we force the POT option */ + if( (query_NPOT_capability() == SOIL_CAPABILITY_NONE) && + !(flags & SOIL_FLAG_TEXTURE_RECTANGLE) ) + { + /* add in the POT flag */ + flags |= SOIL_FLAG_POWER_OF_TWO; + } + /* how large of a texture can this OpenGL implementation handle? */ + /* texture_check_size_enum will be GL_MAX_TEXTURE_SIZE or SOIL_MAX_CUBE_MAP_TEXTURE_SIZE */ + glGetIntegerv( texture_check_size_enum, &max_supported_size ); + /* do I need to make it a power of 2? */ + if( + (flags & SOIL_FLAG_POWER_OF_TWO) || /* user asked for it */ + (flags & SOIL_FLAG_MIPMAPS) || /* need it for the MIP-maps */ + (width > max_supported_size) || /* it's too big, (make sure it's */ + (height > max_supported_size) ) /* 2^n for later down-sampling) */ + { + int new_width = 1; + int new_height = 1; + while( new_width < width ) + { + new_width *= 2; + } + while( new_height < height ) + { + new_height *= 2; + } + /* still? */ + if( (new_width != width) || (new_height != height) ) + { + /* yep, resize */ + unsigned char *resampled = (unsigned char*)malloc( channels*new_width*new_height ); + up_scale_image( + img, width, height, channels, + resampled, new_width, new_height ); + /* OJO this is for debug only! */ + /* + SOIL_save_image( "\\showme.bmp", SOIL_SAVE_TYPE_BMP, + new_width, new_height, channels, + resampled ); + */ + /* nuke the old guy, then point it at the new guy */ + SOIL_free_image_data( img ); + img = resampled; + width = new_width; + height = new_height; + } + } + /* now, if it is too large... */ + if( (width > max_supported_size) || (height > max_supported_size) ) + { + /* I've already made it a power of two, so simply use the MIPmapping + code to reduce its size to the allowable maximum. */ + unsigned char *resampled; + int reduce_block_x = 1, reduce_block_y = 1; + int new_width, new_height; + if( width > max_supported_size ) + { + reduce_block_x = width / max_supported_size; + } + if( height > max_supported_size ) + { + reduce_block_y = height / max_supported_size; + } + new_width = width / reduce_block_x; + new_height = height / reduce_block_y; + resampled = (unsigned char*)malloc( channels*new_width*new_height ); + /* perform the actual reduction */ + mipmap_image( img, width, height, channels, + resampled, reduce_block_x, reduce_block_y ); + /* nuke the old guy, then point it at the new guy */ + SOIL_free_image_data( img ); + img = resampled; + width = new_width; + height = new_height; + } + /* does the user want us to use YCoCg color space? */ + if( flags & SOIL_FLAG_CoCg_Y ) + { + /* this will only work with RGB and RGBA images */ + convert_RGB_to_YCoCg( img, width, height, channels ); + /* + save_image_as_DDS( "CoCg_Y.dds", width, height, channels, img ); + */ + } + /* create the OpenGL texture ID handle + (note: allowing a forced texture ID lets me reload a texture) */ + tex_id = reuse_texture_ID; + if( tex_id == 0 ) + { + glGenTextures( 1, &tex_id ); + } + check_for_GL_errors( "glGenTextures" ); + /* Note: sometimes glGenTextures fails (usually no OpenGL context) */ + if( tex_id ) + { + /* and what type am I using as the internal texture format? */ + switch( channels ) + { + case 1: + original_texture_format = GL_LUMINANCE; + break; + case 2: + original_texture_format = GL_LUMINANCE_ALPHA; + break; + case 3: + original_texture_format = GL_RGB; + break; + case 4: + original_texture_format = GL_RGBA; + break; + } + internal_texture_format = original_texture_format; + /* does the user want me to, and can I, save as DXT? */ + if( flags & SOIL_FLAG_COMPRESS_TO_DXT ) + { + DXT_mode = query_DXT_capability(); + if( DXT_mode == SOIL_CAPABILITY_PRESENT ) + { + /* I can use DXT, whether I compress it or OpenGL does */ + if( (channels & 1) == 1 ) + { + /* 1 or 3 channels = DXT1 */ + internal_texture_format = SOIL_RGB_S3TC_DXT1; + } else + { + /* 2 or 4 channels = DXT5 */ + internal_texture_format = SOIL_RGBA_S3TC_DXT5; + } + } + } + /* bind an OpenGL texture ID */ + glBindTexture( opengl_texture_type, tex_id ); + check_for_GL_errors( "glBindTexture" ); + /* upload the main image */ + if( DXT_mode == SOIL_CAPABILITY_PRESENT ) + { + /* user wants me to do the DXT conversion! */ + int DDS_size; + unsigned char *DDS_data = NULL; + if( (channels & 1) == 1 ) + { + /* RGB, use DXT1 */ + DDS_data = convert_image_to_DXT1( img, width, height, channels, &DDS_size ); + } else + { + /* RGBA, use DXT5 */ + DDS_data = convert_image_to_DXT5( img, width, height, channels, &DDS_size ); + } + if( DDS_data ) + { + soilGlCompressedTexImage2D( + opengl_texture_target, 0, + internal_texture_format, width, height, 0, + DDS_size, DDS_data ); + check_for_GL_errors( "glCompressedTexImage2D" ); + SOIL_free_image_data( DDS_data ); + /* printf( "Internal DXT compressor\n" ); */ + } else + { + /* my compression failed, try the OpenGL driver's version */ + glTexImage2D( + opengl_texture_target, 0, + internal_texture_format, width, height, 0, + original_texture_format, GL_UNSIGNED_BYTE, img ); + check_for_GL_errors( "glTexImage2D" ); + /* printf( "OpenGL DXT compressor\n" ); */ + } + } else + { + /* user want OpenGL to do all the work! */ + glTexImage2D( + opengl_texture_target, 0, + internal_texture_format, width, height, 0, + original_texture_format, GL_UNSIGNED_BYTE, img ); + check_for_GL_errors( "glTexImage2D" ); + /*printf( "OpenGL DXT compressor\n" ); */ + } + /* are any MIPmaps desired? */ + if( flags & SOIL_FLAG_MIPMAPS ) + { + int MIPlevel = 1; + int MIPwidth = (width+1) / 2; + int MIPheight = (height+1) / 2; + unsigned char *resampled = (unsigned char*)malloc( channels*MIPwidth*MIPheight ); + while( ((1< 0; --i ) + { + unsigned char temp = pixel_data[index1]; + pixel_data[index1] = pixel_data[index2]; + pixel_data[index2] = temp; + ++index1; + ++index2; + } + } + + /* save the image */ + save_result = SOIL_save_image( filename, image_type, width, height, 3, pixel_data); + + /* And free the memory */ + SOIL_free_image_data( pixel_data ); + return save_result; +} + +unsigned char* + SOIL_load_image + ( + const char *filename, + int *width, int *height, int *channels, + int force_channels + ) +{ + unsigned char *result = stbi_load( filename, + width, height, channels, force_channels ); + if( result == NULL ) + { + result_string_pointer = stbi_failure_reason(); + } else + { + result_string_pointer = "Image loaded"; + } + return result; +} + +unsigned char* + SOIL_load_image_from_memory + ( + const unsigned char *const buffer, + int buffer_length, + int *width, int *height, int *channels, + int force_channels + ) +{ + unsigned char *result = stbi_load_from_memory( + buffer, buffer_length, + width, height, channels, + force_channels ); + if( result == NULL ) + { + result_string_pointer = stbi_failure_reason(); + } else + { + result_string_pointer = "Image loaded from memory"; + } + return result; +} + +int + SOIL_save_image + ( + const char *filename, + int image_type, + int width, int height, int channels, + const unsigned char *const data + ) +{ + int save_result; + + /* error check */ + if( (width < 1) || (height < 1) || + (channels < 1) || (channels > 4) || + (data == NULL) || + (filename == NULL) ) + { + return 0; + } + if( image_type == SOIL_SAVE_TYPE_BMP ) + { + save_result = stbi_write_bmp( filename, + width, height, channels, (void*)data ); + } else + if( image_type == SOIL_SAVE_TYPE_TGA ) + { + save_result = stbi_write_tga( filename, + width, height, channels, (void*)data ); + } else + if( image_type == SOIL_SAVE_TYPE_DDS ) + { + save_result = save_image_as_DDS( filename, + width, height, channels, (const unsigned char *const)data ); + } else + { + save_result = 0; + } + if( save_result == 0 ) + { + result_string_pointer = "Saving the image failed"; + } else + { + result_string_pointer = "Image saved"; + } + return save_result; +} + +void + SOIL_free_image_data + ( + unsigned char *img_data + ) +{ + free( (void*)img_data ); +} + +const char* + SOIL_last_result + ( + void + ) +{ + return result_string_pointer; +} + +unsigned int SOIL_direct_load_DDS_from_memory( + const unsigned char *const buffer, + int buffer_length, + unsigned int reuse_texture_ID, + int flags, + int loading_as_cubemap ) +{ + /* variables */ + DDS_header header; + unsigned int buffer_index = 0; + unsigned int tex_ID = 0; + /* file reading variables */ + unsigned int S3TC_type = 0; + unsigned char *DDS_data; + unsigned int DDS_main_size; + unsigned int DDS_full_size; + unsigned int width, height; + int mipmaps, cubemap, uncompressed, block_size = 16; + unsigned int flag; + unsigned int cf_target, ogl_target_start, ogl_target_end; + unsigned int opengl_texture_type; + int i; + /* 1st off, does the filename even exist? */ + if( NULL == buffer ) + { + /* we can't do it! */ + result_string_pointer = "NULL buffer"; + return 0; + } + if( buffer_length < sizeof( DDS_header ) ) + { + /* we can't do it! */ + result_string_pointer = "DDS file was too small to contain the DDS header"; + return 0; + } + /* try reading in the header */ + memcpy ( (void*)(&header), (const void *)buffer, sizeof( DDS_header ) ); + buffer_index = sizeof( DDS_header ); + /* guilty until proven innocent */ + result_string_pointer = "Failed to read a known DDS header"; + /* validate the header (warning, "goto"'s ahead, shield your eyes!!) */ + flag = ('D'<<0)|('D'<<8)|('S'<<16)|(' '<<24); + if( header.dwMagic != flag ) {goto quick_exit;} + if( header.dwSize != 124 ) {goto quick_exit;} + /* I need all of these */ + flag = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT; + if( (header.dwFlags & flag) != flag ) {goto quick_exit;} + /* According to the MSDN spec, the dwFlags should contain + DDSD_LINEARSIZE if it's compressed, or DDSD_PITCH if + uncompressed. Some DDS writers do not conform to the + spec, so I need to make my reader more tolerant */ + /* I need one of these */ + flag = DDPF_FOURCC | DDPF_RGB; + if( (header.sPixelFormat.dwFlags & flag) == 0 ) {goto quick_exit;} + if( header.sPixelFormat.dwSize != 32 ) {goto quick_exit;} + if( (header.sCaps.dwCaps1 & DDSCAPS_TEXTURE) == 0 ) {goto quick_exit;} + /* make sure it is a type we can upload */ + if( (header.sPixelFormat.dwFlags & DDPF_FOURCC) && + !( + (header.sPixelFormat.dwFourCC == (('D'<<0)|('X'<<8)|('T'<<16)|('1'<<24))) || + (header.sPixelFormat.dwFourCC == (('D'<<0)|('X'<<8)|('T'<<16)|('3'<<24))) || + (header.sPixelFormat.dwFourCC == (('D'<<0)|('X'<<8)|('T'<<16)|('5'<<24))) + ) ) + { + goto quick_exit; + } + /* OK, validated the header, let's load the image data */ + result_string_pointer = "DDS header loaded and validated"; + width = header.dwWidth; + height = header.dwHeight; + uncompressed = 1 - (header.sPixelFormat.dwFlags & DDPF_FOURCC) / DDPF_FOURCC; + cubemap = (header.sCaps.dwCaps2 & DDSCAPS2_CUBEMAP) / DDSCAPS2_CUBEMAP; + if( uncompressed ) + { + S3TC_type = GL_RGB; + block_size = 3; + if( header.sPixelFormat.dwFlags & DDPF_ALPHAPIXELS ) + { + S3TC_type = GL_RGBA; + block_size = 4; + } + DDS_main_size = width * height * block_size; + } else + { + /* can we even handle direct uploading to OpenGL DXT compressed images? */ + if( query_DXT_capability() != SOIL_CAPABILITY_PRESENT ) + { + /* we can't do it! */ + result_string_pointer = "Direct upload of S3TC images not supported by the OpenGL driver"; + return 0; + } + /* well, we know it is DXT1/3/5, because we checked above */ + switch( (header.sPixelFormat.dwFourCC >> 24) - '0' ) + { + case 1: + S3TC_type = SOIL_RGBA_S3TC_DXT1; + block_size = 8; + break; + case 3: + S3TC_type = SOIL_RGBA_S3TC_DXT3; + block_size = 16; + break; + case 5: + S3TC_type = SOIL_RGBA_S3TC_DXT5; + block_size = 16; + break; + } + DDS_main_size = ((width+3)>>2)*((height+3)>>2)*block_size; + } + if( cubemap ) + { + /* does the user want a cubemap? */ + if( !loading_as_cubemap ) + { + /* we can't do it! */ + result_string_pointer = "DDS image was a cubemap"; + return 0; + } + /* can we even handle cubemaps with the OpenGL driver? */ + if( query_cubemap_capability() != SOIL_CAPABILITY_PRESENT ) + { + /* we can't do it! */ + result_string_pointer = "Direct upload of cubemap images not supported by the OpenGL driver"; + return 0; + } + ogl_target_start = SOIL_TEXTURE_CUBE_MAP_POSITIVE_X; + ogl_target_end = SOIL_TEXTURE_CUBE_MAP_NEGATIVE_Z; + opengl_texture_type = SOIL_TEXTURE_CUBE_MAP; + } else + { + /* does the user want a non-cubemap? */ + if( loading_as_cubemap ) + { + /* we can't do it! */ + result_string_pointer = "DDS image was not a cubemap"; + return 0; + } + ogl_target_start = GL_TEXTURE_2D; + ogl_target_end = GL_TEXTURE_2D; + opengl_texture_type = GL_TEXTURE_2D; + } + if( (header.sCaps.dwCaps1 & DDSCAPS_MIPMAP) && (header.dwMipMapCount > 1) ) + { + int shift_offset; + mipmaps = header.dwMipMapCount - 1; + DDS_full_size = DDS_main_size; + if( uncompressed ) + { + /* uncompressed DDS, simple MIPmap size calculation */ + shift_offset = 0; + } else + { + /* compressed DDS, MIPmap size calculation is block based */ + shift_offset = 2; + } + for( i = 1; i <= mipmaps; ++ i ) + { + int w, h; + w = width >> (shift_offset + i); + h = height >> (shift_offset + i); + if( w < 1 ) + { + w = 1; + } + if( h < 1 ) + { + h = 1; + } + DDS_full_size += w*h*block_size; + } + } else + { + mipmaps = 0; + DDS_full_size = DDS_main_size; + } + DDS_data = (unsigned char*)malloc( DDS_full_size ); + /* got the image data RAM, create or use an existing OpenGL texture handle */ + tex_ID = reuse_texture_ID; + if( tex_ID == 0 ) + { + glGenTextures( 1, &tex_ID ); + } + /* bind an OpenGL texture ID */ + glBindTexture( opengl_texture_type, tex_ID ); + /* do this for each face of the cubemap! */ + for( cf_target = ogl_target_start; cf_target <= ogl_target_end; ++cf_target ) + { + if( buffer_index + DDS_full_size <= buffer_length ) + { + unsigned int byte_offset = DDS_main_size; + memcpy( (void*)DDS_data, (const void*)(&buffer[buffer_index]), DDS_full_size ); + buffer_index += DDS_full_size; + /* upload the main chunk */ + if( uncompressed ) + { + /* and remember, DXT uncompressed uses BGR(A), + so swap to RGB(A) for ALL MIPmap levels */ + for( i = 0; i < DDS_full_size; i += block_size ) + { + unsigned char temp = DDS_data[i]; + DDS_data[i] = DDS_data[i+2]; + DDS_data[i+2] = temp; + } + glTexImage2D( + cf_target, 0, + S3TC_type, width, height, 0, + S3TC_type, GL_UNSIGNED_BYTE, DDS_data ); + } else + { + soilGlCompressedTexImage2D( + cf_target, 0, + S3TC_type, width, height, 0, + DDS_main_size, DDS_data ); + } + /* upload the mipmaps, if we have them */ + for( i = 1; i <= mipmaps; ++i ) + { + int w, h, mip_size; + w = width >> i; + h = height >> i; + if( w < 1 ) + { + w = 1; + } + if( h < 1 ) + { + h = 1; + } + /* upload this mipmap */ + if( uncompressed ) + { + mip_size = w*h*block_size; + glTexImage2D( + cf_target, i, + S3TC_type, w, h, 0, + S3TC_type, GL_UNSIGNED_BYTE, &DDS_data[byte_offset] ); + } else + { + mip_size = ((w+3)/4)*((h+3)/4)*block_size; + soilGlCompressedTexImage2D( + cf_target, i, + S3TC_type, w, h, 0, + mip_size, &DDS_data[byte_offset] ); + } + /* and move to the next mipmap */ + byte_offset += mip_size; + } + /* it worked! */ + result_string_pointer = "DDS file loaded"; + } else + { + glDeleteTextures( 1, & tex_ID ); + tex_ID = 0; + cf_target = ogl_target_end + 1; + result_string_pointer = "DDS file was too small for expected image data"; + } + }/* end reading each face */ + SOIL_free_image_data( DDS_data ); + if( tex_ID ) + { + /* did I have MIPmaps? */ + if( mipmaps > 0 ) + { + /* instruct OpenGL to use the MIPmaps */ + glTexParameteri( opengl_texture_type, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + glTexParameteri( opengl_texture_type, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR ); + } else + { + /* instruct OpenGL _NOT_ to use the MIPmaps */ + glTexParameteri( opengl_texture_type, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + glTexParameteri( opengl_texture_type, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + } + /* does the user want clamping, or wrapping? */ + if( flags & SOIL_FLAG_TEXTURE_REPEATS ) + { + glTexParameteri( opengl_texture_type, GL_TEXTURE_WRAP_S, GL_REPEAT ); + glTexParameteri( opengl_texture_type, GL_TEXTURE_WRAP_T, GL_REPEAT ); + glTexParameteri( opengl_texture_type, SOIL_TEXTURE_WRAP_R, GL_REPEAT ); + } else + { + /* unsigned int clamp_mode = SOIL_CLAMP_TO_EDGE; */ + unsigned int clamp_mode = GL_CLAMP; + glTexParameteri( opengl_texture_type, GL_TEXTURE_WRAP_S, clamp_mode ); + glTexParameteri( opengl_texture_type, GL_TEXTURE_WRAP_T, clamp_mode ); + glTexParameteri( opengl_texture_type, SOIL_TEXTURE_WRAP_R, clamp_mode ); + } + } + +quick_exit: + /* report success or failure */ + return tex_ID; +} + +unsigned int SOIL_direct_load_DDS( + const char *filename, + unsigned int reuse_texture_ID, + int flags, + int loading_as_cubemap ) +{ + FILE *f; + unsigned char *buffer; + size_t buffer_length, bytes_read; + unsigned int tex_ID = 0; + /* error checks */ + if( NULL == filename ) + { + result_string_pointer = "NULL filename"; + return 0; + } + f = fopen( filename, "rb" ); + if( NULL == f ) + { + /* the file doesn't seem to exist (or be open-able) */ + result_string_pointer = "Can not find DDS file"; + return 0; + } + fseek( f, 0, SEEK_END ); + buffer_length = ftell( f ); + fseek( f, 0, SEEK_SET ); + buffer = (unsigned char *) malloc( buffer_length ); + if( NULL == buffer ) + { + result_string_pointer = "malloc failed"; + fclose( f ); + return 0; + } + bytes_read = fread( (void*)buffer, 1, buffer_length, f ); + fclose( f ); + if( bytes_read < buffer_length ) + { + /* huh? */ + buffer_length = bytes_read; + } + /* now try to do the loading */ + tex_ID = SOIL_direct_load_DDS_from_memory( + (const unsigned char *const)buffer, buffer_length, + reuse_texture_ID, flags, loading_as_cubemap ); + SOIL_free_image_data( buffer ); + return tex_ID; +} + +int query_NPOT_capability( void ) +{ + /* check for the capability */ + if( has_NPOT_capability == SOIL_CAPABILITY_UNKNOWN ) + { + /* we haven't yet checked for the capability, do so */ + if( + (NULL == strstr( (char const*)glGetString( GL_EXTENSIONS ), + "GL_ARB_texture_non_power_of_two" ) ) + ) + { + /* not there, flag the failure */ + has_NPOT_capability = SOIL_CAPABILITY_NONE; + } else + { + /* it's there! */ + has_NPOT_capability = SOIL_CAPABILITY_PRESENT; + } + } + /* let the user know if we can do non-power-of-two textures or not */ + return has_NPOT_capability; +} + +int query_tex_rectangle_capability( void ) +{ + /* check for the capability */ + if( has_tex_rectangle_capability == SOIL_CAPABILITY_UNKNOWN ) + { + /* we haven't yet checked for the capability, do so */ + if( + (NULL == strstr( (char const*)glGetString( GL_EXTENSIONS ), + "GL_ARB_texture_rectangle" ) ) + && + (NULL == strstr( (char const*)glGetString( GL_EXTENSIONS ), + "GL_EXT_texture_rectangle" ) ) + && + (NULL == strstr( (char const*)glGetString( GL_EXTENSIONS ), + "GL_NV_texture_rectangle" ) ) + ) + { + /* not there, flag the failure */ + has_tex_rectangle_capability = SOIL_CAPABILITY_NONE; + } else + { + /* it's there! */ + has_tex_rectangle_capability = SOIL_CAPABILITY_PRESENT; + } + } + /* let the user know if we can do texture rectangles or not */ + return has_tex_rectangle_capability; +} + +int query_cubemap_capability( void ) +{ + /* check for the capability */ + if( has_cubemap_capability == SOIL_CAPABILITY_UNKNOWN ) + { + /* we haven't yet checked for the capability, do so */ + if( + (NULL == strstr( (char const*)glGetString( GL_EXTENSIONS ), + "GL_ARB_texture_cube_map" ) ) + && + (NULL == strstr( (char const*)glGetString( GL_EXTENSIONS ), + "GL_EXT_texture_cube_map" ) ) + ) + { + /* not there, flag the failure */ + has_cubemap_capability = SOIL_CAPABILITY_NONE; + } else + { + /* it's there! */ + has_cubemap_capability = SOIL_CAPABILITY_PRESENT; + } + } + /* let the user know if we can do cubemaps or not */ + return has_cubemap_capability; +} + +int query_DXT_capability( void ) +{ + /* check for the capability */ + if( has_DXT_capability == SOIL_CAPABILITY_UNKNOWN ) + { + /* we haven't yet checked for the capability, do so */ + if( NULL == strstr( + (char const*)glGetString( GL_EXTENSIONS ), + "GL_EXT_texture_compression_s3tc" ) ) + { + /* not there, flag the failure */ + has_DXT_capability = SOIL_CAPABILITY_NONE; + } else + { + /* and find the address of the extension function */ + P_SOIL_GLCOMPRESSEDTEXIMAGE2DPROC ext_addr = NULL; + #ifdef WIN32 + ext_addr = (P_SOIL_GLCOMPRESSEDTEXIMAGE2DPROC) + wglGetProcAddress + ( + "glCompressedTexImage2DARB" + ); + #elif defined(__APPLE__) || defined(__APPLE_CC__) + /* I can't test this Apple stuff! */ + CFBundleRef bundle; + CFURLRef bundleURL = + CFURLCreateWithFileSystemPath( + kCFAllocatorDefault, + CFSTR("/System/Library/Frameworks/OpenGL.framework"), + kCFURLPOSIXPathStyle, + true ); + CFStringRef extensionName = + CFStringCreateWithCString( + kCFAllocatorDefault, + "glCompressedTexImage2DARB", + kCFStringEncodingASCII ); + bundle = CFBundleCreate( kCFAllocatorDefault, bundleURL ); + assert( bundle != NULL ); + ext_addr = (P_SOIL_GLCOMPRESSEDTEXIMAGE2DPROC) + CFBundleGetFunctionPointerForName + ( + bundle, extensionName + ); + CFRelease( bundleURL ); + CFRelease( extensionName ); + CFRelease( bundle ); + #else + ext_addr = (P_SOIL_GLCOMPRESSEDTEXIMAGE2DPROC) + glXGetProcAddressARB + ( + (const GLubyte *)"glCompressedTexImage2DARB" + ); + #endif + /* Flag it so no checks needed later */ + if( NULL == ext_addr ) + { + /* hmm, not good!! This should not happen, but does on my + laptop's VIA chipset. The GL_EXT_texture_compression_s3tc + spec requires that ARB_texture_compression be present too. + this means I can upload and have the OpenGL drive do the + conversion, but I can't use my own routines or load DDS files + from disk and upload them directly [8^( */ + has_DXT_capability = SOIL_CAPABILITY_NONE; + } else + { + /* all's well! */ + soilGlCompressedTexImage2D = ext_addr; + has_DXT_capability = SOIL_CAPABILITY_PRESENT; + } + } + } + /* let the user know if we can do DXT or not */ + return has_DXT_capability; +} + +#ifdef _MSC_VER +#pragma warning(pop) +#else +#pragma GCC diagnostic pop +#endif diff --git a/lib/soil/SOIL.h b/lib/soil/SOIL.h new file mode 100644 index 000000000..43f634f86 --- /dev/null +++ b/lib/soil/SOIL.h @@ -0,0 +1,433 @@ +/** + @mainpage SOIL + + Jonathan Dummer + 2007-07-26-10.36 + + Simple OpenGL Image Library + + A tiny c library for uploading images as + textures into OpenGL. Also saving and + loading of images is supported. + + I'm using Sean's Tool Box image loader as a base: + http://www.nothings.org/ + + I'm upgrading it to load TGA and DDS files, and a direct + path for loading DDS files straight into OpenGL textures, + when applicable. + + Image Formats: + - BMP load & save + - TGA load & save + - DDS load & save + - PNG load + - JPG load + + OpenGL Texture Features: + - resample to power-of-two sizes + - MIPmap generation + - compressed texture S3TC formats (if supported) + - can pre-multiply alpha for you, for better compositing + - can flip image about the y-axis (except pre-compressed DDS files) + + Thanks to: + * Sean Barret - for the awesome stb_image + * Dan Venkitachalam - for finding some non-compliant DDS files, and patching some explicit casts + * everybody at gamedev.net +**/ + +#ifndef HEADER_SIMPLE_OPENGL_IMAGE_LIBRARY +#define HEADER_SIMPLE_OPENGL_IMAGE_LIBRARY + +#ifdef __cplusplus +extern "C" { +#endif + +/** + The format of images that may be loaded (force_channels). + SOIL_LOAD_AUTO leaves the image in whatever format it was found. + SOIL_LOAD_L forces the image to load as Luminous (greyscale) + SOIL_LOAD_LA forces the image to load as Luminous with Alpha + SOIL_LOAD_RGB forces the image to load as Red Green Blue + SOIL_LOAD_RGBA forces the image to load as Red Green Blue Alpha +**/ +enum +{ + SOIL_LOAD_AUTO = 0, + SOIL_LOAD_L = 1, + SOIL_LOAD_LA = 2, + SOIL_LOAD_RGB = 3, + SOIL_LOAD_RGBA = 4 +}; + +/** + Passed in as reuse_texture_ID, will cause SOIL to + register a new texture ID using glGenTextures(). + If the value passed into reuse_texture_ID > 0 then + SOIL will just re-use that texture ID (great for + reloading image assets in-game!) +**/ +enum +{ + SOIL_CREATE_NEW_ID = 0 +}; + +/** + flags you can pass into SOIL_load_OGL_texture() + and SOIL_create_OGL_texture(). + (note that if SOIL_FLAG_DDS_LOAD_DIRECT is used + the rest of the flags with the exception of + SOIL_FLAG_TEXTURE_REPEATS will be ignored while + loading already-compressed DDS files.) + + SOIL_FLAG_POWER_OF_TWO: force the image to be POT + SOIL_FLAG_MIPMAPS: generate mipmaps for the texture + SOIL_FLAG_TEXTURE_REPEATS: otherwise will clamp + SOIL_FLAG_MULTIPLY_ALPHA: for using (GL_ONE,GL_ONE_MINUS_SRC_ALPHA) blending + SOIL_FLAG_INVERT_Y: flip the image vertically + SOIL_FLAG_COMPRESS_TO_DXT: if the card can display them, will convert RGB to DXT1, RGBA to DXT5 + SOIL_FLAG_DDS_LOAD_DIRECT: will load DDS files directly without _ANY_ additional processing + SOIL_FLAG_NTSC_SAFE_RGB: clamps RGB components to the range [16,235] + SOIL_FLAG_CoCg_Y: Google YCoCg; RGB=>CoYCg, RGBA=>CoCgAY + SOIL_FLAG_TEXTURE_RECTANGE: uses ARB_texture_rectangle ; pixel indexed & no repeat or MIPmaps or cubemaps +**/ +enum +{ + SOIL_FLAG_POWER_OF_TWO = 1, + SOIL_FLAG_MIPMAPS = 2, + SOIL_FLAG_TEXTURE_REPEATS = 4, + SOIL_FLAG_MULTIPLY_ALPHA = 8, + SOIL_FLAG_INVERT_Y = 16, + SOIL_FLAG_COMPRESS_TO_DXT = 32, + SOIL_FLAG_DDS_LOAD_DIRECT = 64, + SOIL_FLAG_NTSC_SAFE_RGB = 128, + SOIL_FLAG_CoCg_Y = 256, + SOIL_FLAG_TEXTURE_RECTANGLE = 512 +}; + +/** + The types of images that may be saved. + (TGA supports uncompressed RGB / RGBA) + (BMP supports uncompressed RGB) + (DDS supports DXT1 and DXT5) +**/ +enum +{ + SOIL_SAVE_TYPE_TGA = 0, + SOIL_SAVE_TYPE_BMP = 1, + SOIL_SAVE_TYPE_DDS = 2 +}; + +/** + Defines the order of faces in a DDS cubemap. + I recommend that you use the same order in single + image cubemap files, so they will be interchangeable + with DDS cubemaps when using SOIL. +**/ +#define SOIL_DDS_CUBEMAP_FACE_ORDER "EWUDNS" + +/** + The types of internal fake HDR representations + + SOIL_HDR_RGBE: RGB * pow( 2.0, A - 128.0 ) + SOIL_HDR_RGBdivA: RGB / A + SOIL_HDR_RGBdivA2: RGB / (A*A) +**/ +enum +{ + SOIL_HDR_RGBE = 0, + SOIL_HDR_RGBdivA = 1, + SOIL_HDR_RGBdivA2 = 2 +}; + +/** + Loads an image from disk into an OpenGL texture. + \param filename the name of the file to upload as a texture + \param force_channels 0-image format, 1-luminous, 2-luminous/alpha, 3-RGB, 4-RGBA + \param reuse_texture_ID 0-generate a new texture ID, otherwise reuse the texture ID (overwriting the old texture) + \param flags can be any of SOIL_FLAG_POWER_OF_TWO | SOIL_FLAG_MIPMAPS | SOIL_FLAG_TEXTURE_REPEATS | SOIL_FLAG_MULTIPLY_ALPHA | SOIL_FLAG_INVERT_Y | SOIL_FLAG_COMPRESS_TO_DXT | SOIL_FLAG_DDS_LOAD_DIRECT + \return 0-failed, otherwise returns the OpenGL texture handle +**/ +unsigned int + SOIL_load_OGL_texture + ( + const char *filename, + int force_channels, + unsigned int reuse_texture_ID, + unsigned int flags + ); + +/** + Loads 6 images from disk into an OpenGL cubemap texture. + \param x_pos_file the name of the file to upload as the +x cube face + \param x_neg_file the name of the file to upload as the -x cube face + \param y_pos_file the name of the file to upload as the +y cube face + \param y_neg_file the name of the file to upload as the -y cube face + \param z_pos_file the name of the file to upload as the +z cube face + \param z_neg_file the name of the file to upload as the -z cube face + \param force_channels 0-image format, 1-luminous, 2-luminous/alpha, 3-RGB, 4-RGBA + \param reuse_texture_ID 0-generate a new texture ID, otherwise reuse the texture ID (overwriting the old texture) + \param flags can be any of SOIL_FLAG_POWER_OF_TWO | SOIL_FLAG_MIPMAPS | SOIL_FLAG_TEXTURE_REPEATS | SOIL_FLAG_MULTIPLY_ALPHA | SOIL_FLAG_INVERT_Y | SOIL_FLAG_COMPRESS_TO_DXT | SOIL_FLAG_DDS_LOAD_DIRECT + \return 0-failed, otherwise returns the OpenGL texture handle +**/ +unsigned int + SOIL_load_OGL_cubemap + ( + const char *x_pos_file, + const char *x_neg_file, + const char *y_pos_file, + const char *y_neg_file, + const char *z_pos_file, + const char *z_neg_file, + int force_channels, + unsigned int reuse_texture_ID, + unsigned int flags + ); + +/** + Loads 1 image from disk and splits it into an OpenGL cubemap texture. + \param filename the name of the file to upload as a texture + \param face_order the order of the faces in the file, any combination of NSWEUD, for North, South, Up, etc. + \param force_channels 0-image format, 1-luminous, 2-luminous/alpha, 3-RGB, 4-RGBA + \param reuse_texture_ID 0-generate a new texture ID, otherwise reuse the texture ID (overwriting the old texture) + \param flags can be any of SOIL_FLAG_POWER_OF_TWO | SOIL_FLAG_MIPMAPS | SOIL_FLAG_TEXTURE_REPEATS | SOIL_FLAG_MULTIPLY_ALPHA | SOIL_FLAG_INVERT_Y | SOIL_FLAG_COMPRESS_TO_DXT | SOIL_FLAG_DDS_LOAD_DIRECT + \return 0-failed, otherwise returns the OpenGL texture handle +**/ +unsigned int + SOIL_load_OGL_single_cubemap + ( + const char *filename, + const char face_order[6], + int force_channels, + unsigned int reuse_texture_ID, + unsigned int flags + ); + +/** + Loads an HDR image from disk into an OpenGL texture. + \param filename the name of the file to upload as a texture + \param fake_HDR_format SOIL_HDR_RGBE, SOIL_HDR_RGBdivA, SOIL_HDR_RGBdivA2 + \param reuse_texture_ID 0-generate a new texture ID, otherwise reuse the texture ID (overwriting the old texture) + \param flags can be any of SOIL_FLAG_POWER_OF_TWO | SOIL_FLAG_MIPMAPS | SOIL_FLAG_TEXTURE_REPEATS | SOIL_FLAG_MULTIPLY_ALPHA | SOIL_FLAG_INVERT_Y | SOIL_FLAG_COMPRESS_TO_DXT + \return 0-failed, otherwise returns the OpenGL texture handle +**/ +unsigned int + SOIL_load_OGL_HDR_texture + ( + const char *filename, + int fake_HDR_format, + int rescale_to_max, + unsigned int reuse_texture_ID, + unsigned int flags + ); + +/** + Loads an image from RAM into an OpenGL texture. + \param buffer the image data in RAM just as if it were still in a file + \param buffer_length the size of the buffer in bytes + \param force_channels 0-image format, 1-luminous, 2-luminous/alpha, 3-RGB, 4-RGBA + \param reuse_texture_ID 0-generate a new texture ID, otherwise reuse the texture ID (overwriting the old texture) + \param flags can be any of SOIL_FLAG_POWER_OF_TWO | SOIL_FLAG_MIPMAPS | SOIL_FLAG_TEXTURE_REPEATS | SOIL_FLAG_MULTIPLY_ALPHA | SOIL_FLAG_INVERT_Y | SOIL_FLAG_COMPRESS_TO_DXT | SOIL_FLAG_DDS_LOAD_DIRECT + \return 0-failed, otherwise returns the OpenGL texture handle +**/ +unsigned int + SOIL_load_OGL_texture_from_memory + ( + const unsigned char *const buffer, + int buffer_length, + int force_channels, + unsigned int reuse_texture_ID, + unsigned int flags + ); + +/** + Loads 6 images from memory into an OpenGL cubemap texture. + \param x_pos_buffer the image data in RAM to upload as the +x cube face + \param x_pos_buffer_length the size of the above buffer + \param x_neg_buffer the image data in RAM to upload as the +x cube face + \param x_neg_buffer_length the size of the above buffer + \param y_pos_buffer the image data in RAM to upload as the +x cube face + \param y_pos_buffer_length the size of the above buffer + \param y_neg_buffer the image data in RAM to upload as the +x cube face + \param y_neg_buffer_length the size of the above buffer + \param z_pos_buffer the image data in RAM to upload as the +x cube face + \param z_pos_buffer_length the size of the above buffer + \param z_neg_buffer the image data in RAM to upload as the +x cube face + \param z_neg_buffer_length the size of the above buffer + \param force_channels 0-image format, 1-luminous, 2-luminous/alpha, 3-RGB, 4-RGBA + \param reuse_texture_ID 0-generate a new texture ID, otherwise reuse the texture ID (overwriting the old texture) + \param flags can be any of SOIL_FLAG_POWER_OF_TWO | SOIL_FLAG_MIPMAPS | SOIL_FLAG_TEXTURE_REPEATS | SOIL_FLAG_MULTIPLY_ALPHA | SOIL_FLAG_INVERT_Y | SOIL_FLAG_COMPRESS_TO_DXT | SOIL_FLAG_DDS_LOAD_DIRECT + \return 0-failed, otherwise returns the OpenGL texture handle +**/ +unsigned int + SOIL_load_OGL_cubemap_from_memory + ( + const unsigned char *const x_pos_buffer, + int x_pos_buffer_length, + const unsigned char *const x_neg_buffer, + int x_neg_buffer_length, + const unsigned char *const y_pos_buffer, + int y_pos_buffer_length, + const unsigned char *const y_neg_buffer, + int y_neg_buffer_length, + const unsigned char *const z_pos_buffer, + int z_pos_buffer_length, + const unsigned char *const z_neg_buffer, + int z_neg_buffer_length, + int force_channels, + unsigned int reuse_texture_ID, + unsigned int flags + ); + +/** + Loads 1 image from RAM and splits it into an OpenGL cubemap texture. + \param buffer the image data in RAM just as if it were still in a file + \param buffer_length the size of the buffer in bytes + \param face_order the order of the faces in the file, any combination of NSWEUD, for North, South, Up, etc. + \param force_channels 0-image format, 1-luminous, 2-luminous/alpha, 3-RGB, 4-RGBA + \param reuse_texture_ID 0-generate a new texture ID, otherwise reuse the texture ID (overwriting the old texture) + \param flags can be any of SOIL_FLAG_POWER_OF_TWO | SOIL_FLAG_MIPMAPS | SOIL_FLAG_TEXTURE_REPEATS | SOIL_FLAG_MULTIPLY_ALPHA | SOIL_FLAG_INVERT_Y | SOIL_FLAG_COMPRESS_TO_DXT | SOIL_FLAG_DDS_LOAD_DIRECT + \return 0-failed, otherwise returns the OpenGL texture handle +**/ +unsigned int + SOIL_load_OGL_single_cubemap_from_memory + ( + const unsigned char *const buffer, + int buffer_length, + const char face_order[6], + int force_channels, + unsigned int reuse_texture_ID, + unsigned int flags + ); + +/** + Creates a 2D OpenGL texture from raw image data. Note that the raw data is + _NOT_ freed after the upload (so the user can load various versions). + \param data the raw data to be uploaded as an OpenGL texture + \param width the width of the image in pixels + \param height the height of the image in pixels + \param channels the number of channels: 1-luminous, 2-luminous/alpha, 3-RGB, 4-RGBA + \param reuse_texture_ID 0-generate a new texture ID, otherwise reuse the texture ID (overwriting the old texture) + \param flags can be any of SOIL_FLAG_POWER_OF_TWO | SOIL_FLAG_MIPMAPS | SOIL_FLAG_TEXTURE_REPEATS | SOIL_FLAG_MULTIPLY_ALPHA | SOIL_FLAG_INVERT_Y | SOIL_FLAG_COMPRESS_TO_DXT + \return 0-failed, otherwise returns the OpenGL texture handle +**/ +unsigned int + SOIL_create_OGL_texture + ( + const unsigned char *const data, + int width, int height, int channels, + unsigned int reuse_texture_ID, + unsigned int flags + ); + +/** + Creates an OpenGL cubemap texture by splitting up 1 image into 6 parts. + \param data the raw data to be uploaded as an OpenGL texture + \param width the width of the image in pixels + \param height the height of the image in pixels + \param channels the number of channels: 1-luminous, 2-luminous/alpha, 3-RGB, 4-RGBA + \param face_order the order of the faces in the file, and combination of NSWEUD, for North, South, Up, etc. + \param reuse_texture_ID 0-generate a new texture ID, otherwise reuse the texture ID (overwriting the old texture) + \param flags can be any of SOIL_FLAG_POWER_OF_TWO | SOIL_FLAG_MIPMAPS | SOIL_FLAG_TEXTURE_REPEATS | SOIL_FLAG_MULTIPLY_ALPHA | SOIL_FLAG_INVERT_Y | SOIL_FLAG_COMPRESS_TO_DXT | SOIL_FLAG_DDS_LOAD_DIRECT + \return 0-failed, otherwise returns the OpenGL texture handle +**/ +unsigned int + SOIL_create_OGL_single_cubemap + ( + const unsigned char *const data, + int width, int height, int channels, + const char face_order[6], + unsigned int reuse_texture_ID, + unsigned int flags + ); + +/** + Captures the OpenGL window (RGB) and saves it to disk + \return 0 if it failed, otherwise returns 1 +**/ +int + SOIL_save_screenshot + ( + const char *filename, + int image_type, + int x, int y, + int width, int height + ); + +/** + Loads an image from disk into an array of unsigned chars. + Note that *channels return the original channel count of the + image. If force_channels was other than SOIL_LOAD_AUTO, + the resulting image has force_channels, but *channels may be + different (if the original image had a different channel + count). + \return 0 if failed, otherwise returns 1 +**/ +unsigned char* + SOIL_load_image + ( + const char *filename, + int *width, int *height, int *channels, + int force_channels + ); + +/** + Loads an image from memory into an array of unsigned chars. + Note that *channels return the original channel count of the + image. If force_channels was other than SOIL_LOAD_AUTO, + the resulting image has force_channels, but *channels may be + different (if the original image had a different channel + count). + \return 0 if failed, otherwise returns 1 +**/ +unsigned char* + SOIL_load_image_from_memory + ( + const unsigned char *const buffer, + int buffer_length, + int *width, int *height, int *channels, + int force_channels + ); + +/** + Saves an image from an array of unsigned chars (RGBA) to disk + \return 0 if failed, otherwise returns 1 +**/ +int + SOIL_save_image + ( + const char *filename, + int image_type, + int width, int height, int channels, + const unsigned char *const data + ); + +/** + Frees the image data (note, this is just C's "free()"...this function is + present mostly so C++ programmers don't forget to use "free()" and call + "delete []" instead [8^) +**/ +void + SOIL_free_image_data + ( + unsigned char *img_data + ); + +/** + This function resturn a pointer to a string describing the last thing + that happened inside SOIL. It can be used to determine why an image + failed to load. +**/ +const char* + SOIL_last_result + ( + void + ); + + +#ifdef __cplusplus +} +#endif + +#endif /* HEADER_SIMPLE_OPENGL_IMAGE_LIBRARY */ diff --git a/lib/soil/image_DXT.c b/lib/soil/image_DXT.c new file mode 100644 index 000000000..4206a1bbc --- /dev/null +++ b/lib/soil/image_DXT.c @@ -0,0 +1,632 @@ +/* + Jonathan Dummer + 2007-07-31-10.32 + + simple DXT compression / decompression code + + public domain +*/ + +#include "image_DXT.h" +#include +#include +#include +#include + +/* set this =1 if you want to use the covarince matrix method... + which is better than my method of using standard deviations + overall, except on the infintesimal chance that the power + method fails for finding the largest eigenvector */ +#define USE_COV_MAT 1 + +/********* Function Prototypes *********/ +/* + Takes a 4x4 block of pixels and compresses it into 8 bytes + in DXT1 format (color only, no alpha). Speed is valued + over prettyness, at least for now. +*/ +void compress_DDS_color_block( + int channels, + const unsigned char *const uncompressed, + unsigned char compressed[8] ); +/* + Takes a 4x4 block of pixels and compresses the alpha + component it into 8 bytes for use in DXT5 DDS files. + Speed is valued over prettyness, at least for now. +*/ +void compress_DDS_alpha_block( + const unsigned char *const uncompressed, + unsigned char compressed[8] ); + +/********* Actual Exposed Functions *********/ +int + save_image_as_DDS + ( + const char *filename, + int width, int height, int channels, + const unsigned char *const data + ) +{ + /* variables */ + FILE *fout; + unsigned char *DDS_data; + DDS_header header; + int DDS_size; + /* error check */ + if( (NULL == filename) || + (width < 1) || (height < 1) || + (channels < 1) || (channels > 4) || + (data == NULL ) ) + { + return 0; + } + /* Convert the image */ + if( (channels & 1) == 1 ) + { + /* no alpha, just use DXT1 */ + DDS_data = convert_image_to_DXT1( data, width, height, channels, &DDS_size ); + } else + { + /* has alpha, so use DXT5 */ + DDS_data = convert_image_to_DXT5( data, width, height, channels, &DDS_size ); + } + /* save it */ + memset( &header, 0, sizeof( DDS_header ) ); + header.dwMagic = ('D' << 0) | ('D' << 8) | ('S' << 16) | (' ' << 24); + header.dwSize = 124; + header.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT | DDSD_LINEARSIZE; + header.dwWidth = width; + header.dwHeight = height; + header.dwPitchOrLinearSize = DDS_size; + header.sPixelFormat.dwSize = 32; + header.sPixelFormat.dwFlags = DDPF_FOURCC; + if( (channels & 1) == 1 ) + { + header.sPixelFormat.dwFourCC = ('D' << 0) | ('X' << 8) | ('T' << 16) | ('1' << 24); + } else + { + header.sPixelFormat.dwFourCC = ('D' << 0) | ('X' << 8) | ('T' << 16) | ('5' << 24); + } + header.sCaps.dwCaps1 = DDSCAPS_TEXTURE; + /* write it out */ + fout = fopen( filename, "wb"); + fwrite( &header, sizeof( DDS_header ), 1, fout ); + fwrite( DDS_data, 1, DDS_size, fout ); + fclose( fout ); + /* done */ + free( DDS_data ); + return 1; +} + +unsigned char* convert_image_to_DXT1( + const unsigned char *const uncompressed, + int width, int height, int channels, + int *out_size ) +{ + unsigned char *compressed; + int i, j, x, y; + unsigned char ublock[16*3]; + unsigned char cblock[8]; + int index = 0, chan_step = 1; + int block_count = 0; + /* error check */ + *out_size = 0; + if( (width < 1) || (height < 1) || + (NULL == uncompressed) || + (channels < 1) || (channels > 4) ) + { + return NULL; + } + /* for channels == 1 or 2, I do not step forward for R,G,B values */ + if( channels < 3 ) + { + chan_step = 0; + } + /* get the RAM for the compressed image + (8 bytes per 4x4 pixel block) */ + *out_size = ((width+3) >> 2) * ((height+3) >> 2) * 8; + compressed = (unsigned char*)malloc( *out_size ); + /* go through each block */ + for( j = 0; j < height; j += 4 ) + { + for( i = 0; i < width; i += 4 ) + { + /* copy this block into a new one */ + int idx = 0; + int mx = 4, my = 4; + if( j+4 >= height ) + { + my = height - j; + } + if( i+4 >= width ) + { + mx = width - i; + } + for( y = 0; y < my; ++y ) + { + for( x = 0; x < mx; ++x ) + { + ublock[idx++] = uncompressed[(j+y)*width*channels+(i+x)*channels]; + ublock[idx++] = uncompressed[(j+y)*width*channels+(i+x)*channels+chan_step]; + ublock[idx++] = uncompressed[(j+y)*width*channels+(i+x)*channels+chan_step+chan_step]; + } + for( x = mx; x < 4; ++x ) + { + ublock[idx++] = ublock[0]; + ublock[idx++] = ublock[1]; + ublock[idx++] = ublock[2]; + } + } + for( y = my; y < 4; ++y ) + { + for( x = 0; x < 4; ++x ) + { + ublock[idx++] = ublock[0]; + ublock[idx++] = ublock[1]; + ublock[idx++] = ublock[2]; + } + } + /* compress the block */ + ++block_count; + compress_DDS_color_block( 3, ublock, cblock ); + /* copy the data from the block into the main block */ + for( x = 0; x < 8; ++x ) + { + compressed[index++] = cblock[x]; + } + } + } + return compressed; +} + +unsigned char* convert_image_to_DXT5( + const unsigned char *const uncompressed, + int width, int height, int channels, + int *out_size ) +{ + unsigned char *compressed; + int i, j, x, y; + unsigned char ublock[16*4]; + unsigned char cblock[8]; + int index = 0, chan_step = 1; + int block_count = 0, has_alpha; + /* error check */ + *out_size = 0; + if( (width < 1) || (height < 1) || + (NULL == uncompressed) || + (channels < 1) || ( channels > 4) ) + { + return NULL; + } + /* for channels == 1 or 2, I do not step forward for R,G,B vales */ + if( channels < 3 ) + { + chan_step = 0; + } + /* # channels = 1 or 3 have no alpha, 2 & 4 do have alpha */ + has_alpha = 1 - (channels & 1); + /* get the RAM for the compressed image + (16 bytes per 4x4 pixel block) */ + *out_size = ((width+3) >> 2) * ((height+3) >> 2) * 16; + compressed = (unsigned char*)malloc( *out_size ); + /* go through each block */ + for( j = 0; j < height; j += 4 ) + { + for( i = 0; i < width; i += 4 ) + { + /* local variables, and my block counter */ + int idx = 0; + int mx = 4, my = 4; + if( j+4 >= height ) + { + my = height - j; + } + if( i+4 >= width ) + { + mx = width - i; + } + for( y = 0; y < my; ++y ) + { + for( x = 0; x < mx; ++x ) + { + ublock[idx++] = uncompressed[(j+y)*width*channels+(i+x)*channels]; + ublock[idx++] = uncompressed[(j+y)*width*channels+(i+x)*channels+chan_step]; + ublock[idx++] = uncompressed[(j+y)*width*channels+(i+x)*channels+chan_step+chan_step]; + ublock[idx++] = + has_alpha * uncompressed[(j+y)*width*channels+(i+x)*channels+channels-1] + + (1-has_alpha)*255; + } + for( x = mx; x < 4; ++x ) + { + ublock[idx++] = ublock[0]; + ublock[idx++] = ublock[1]; + ublock[idx++] = ublock[2]; + ublock[idx++] = ublock[3]; + } + } + for( y = my; y < 4; ++y ) + { + for( x = 0; x < 4; ++x ) + { + ublock[idx++] = ublock[0]; + ublock[idx++] = ublock[1]; + ublock[idx++] = ublock[2]; + ublock[idx++] = ublock[3]; + } + } + /* now compress the alpha block */ + compress_DDS_alpha_block( ublock, cblock ); + /* copy the data from the compressed alpha block into the main buffer */ + for( x = 0; x < 8; ++x ) + { + compressed[index++] = cblock[x]; + } + /* then compress the color block */ + ++block_count; + compress_DDS_color_block( 4, ublock, cblock ); + /* copy the data from the compressed color block into the main buffer */ + for( x = 0; x < 8; ++x ) + { + compressed[index++] = cblock[x]; + } + } + } + return compressed; +} + +/********* Helper Functions *********/ +int convert_bit_range( int c, int from_bits, int to_bits ) +{ + int b = (1 << (from_bits - 1)) + c * ((1 << to_bits) - 1); + return (b + (b >> from_bits)) >> from_bits; +} + +int rgb_to_565( int r, int g, int b ) +{ + return + (convert_bit_range( r, 8, 5 ) << 11) | + (convert_bit_range( g, 8, 6 ) << 05) | + (convert_bit_range( b, 8, 5 ) << 00); +} + +void rgb_888_from_565( unsigned int c, int *r, int *g, int *b ) +{ + *r = convert_bit_range( (c >> 11) & 31, 5, 8 ); + *g = convert_bit_range( (c >> 05) & 63, 6, 8 ); + *b = convert_bit_range( (c >> 00) & 31, 5, 8 ); +} + +void compute_color_line_STDEV( + const unsigned char *const uncompressed, + int channels, + float point[3], float direction[3] ) +{ + const float inv_16 = 1.0f / 16.0f; + int i; + float sum_r = 0.0f, sum_g = 0.0f, sum_b = 0.0f; + float sum_rr = 0.0f, sum_gg = 0.0f, sum_bb = 0.0f; + float sum_rg = 0.0f, sum_rb = 0.0f, sum_gb = 0.0f; + /* calculate all data needed for the covariance matrix + ( to compare with _rygdxt code) */ + for( i = 0; i < 16*channels; i += channels ) + { + sum_r += uncompressed[i+0]; + sum_rr += uncompressed[i+0] * uncompressed[i+0]; + sum_g += uncompressed[i+1]; + sum_gg += uncompressed[i+1] * uncompressed[i+1]; + sum_b += uncompressed[i+2]; + sum_bb += uncompressed[i+2] * uncompressed[i+2]; + sum_rg += uncompressed[i+0] * uncompressed[i+1]; + sum_rb += uncompressed[i+0] * uncompressed[i+2]; + sum_gb += uncompressed[i+1] * uncompressed[i+2]; + } + /* convert the sums to averages */ + sum_r *= inv_16; + sum_g *= inv_16; + sum_b *= inv_16; + /* and convert the squares to the squares of the value - avg_value */ + sum_rr -= 16.0f * sum_r * sum_r; + sum_gg -= 16.0f * sum_g * sum_g; + sum_bb -= 16.0f * sum_b * sum_b; + sum_rg -= 16.0f * sum_r * sum_g; + sum_rb -= 16.0f * sum_r * sum_b; + sum_gb -= 16.0f * sum_g * sum_b; + /* the point on the color line is the average */ + point[0] = sum_r; + point[1] = sum_g; + point[2] = sum_b; + #if USE_COV_MAT + /* + The following idea was from ryg. + (https://mollyrocket.com/forums/viewtopic.php?t=392) + The method worked great (less RMSE than mine) most of + the time, but had some issues handling some simple + boundary cases, like full green next to full red, + which would generate a covariance matrix like this: + + | 1 -1 0 | + | -1 1 0 | + | 0 0 0 | + + For a given starting vector, the power method can + generate all zeros! So no starting with {1,1,1} + as I was doing! This kind of error is still a + slight posibillity, but will be very rare. + */ + /* use the covariance matrix directly + (1st iteration, don't use all 1.0 values!) */ + sum_r = 1.0f; + sum_g = 2.718281828f; + sum_b = 3.141592654f; + direction[0] = sum_r*sum_rr + sum_g*sum_rg + sum_b*sum_rb; + direction[1] = sum_r*sum_rg + sum_g*sum_gg + sum_b*sum_gb; + direction[2] = sum_r*sum_rb + sum_g*sum_gb + sum_b*sum_bb; + /* 2nd iteration, use results from the 1st guy */ + sum_r = direction[0]; + sum_g = direction[1]; + sum_b = direction[2]; + direction[0] = sum_r*sum_rr + sum_g*sum_rg + sum_b*sum_rb; + direction[1] = sum_r*sum_rg + sum_g*sum_gg + sum_b*sum_gb; + direction[2] = sum_r*sum_rb + sum_g*sum_gb + sum_b*sum_bb; + /* 3rd iteration, use results from the 2nd guy */ + sum_r = direction[0]; + sum_g = direction[1]; + sum_b = direction[2]; + direction[0] = sum_r*sum_rr + sum_g*sum_rg + sum_b*sum_rb; + direction[1] = sum_r*sum_rg + sum_g*sum_gg + sum_b*sum_gb; + direction[2] = sum_r*sum_rb + sum_g*sum_gb + sum_b*sum_bb; + #else + /* use my standard deviation method + (very robust, a tiny bit slower and less accurate) */ + direction[0] = sqrt( sum_rr ); + direction[1] = sqrt( sum_gg ); + direction[2] = sqrt( sum_bb ); + /* which has a greater component */ + if( sum_gg > sum_rr ) + { + /* green has greater component, so base the other signs off of green */ + if( sum_rg < 0.0f ) + { + direction[0] = -direction[0]; + } + if( sum_gb < 0.0f ) + { + direction[2] = -direction[2]; + } + } else + { + /* red has a greater component */ + if( sum_rg < 0.0f ) + { + direction[1] = -direction[1]; + } + if( sum_rb < 0.0f ) + { + direction[2] = -direction[2]; + } + } + #endif +} + +void LSE_master_colors_max_min( + int *cmax, int *cmin, + int channels, + const unsigned char *const uncompressed ) +{ + int i, j; + /* the master colors */ + int c0[3], c1[3]; + /* used for fitting the line */ + float sum_x[] = { 0.0f, 0.0f, 0.0f }; + float sum_x2[] = { 0.0f, 0.0f, 0.0f }; + float dot_max = 1.0f, dot_min = -1.0f; + float vec_len2 = 0.0f; + float dot; + /* error check */ + if( (channels < 3) || (channels > 4) ) + { + return; + } + compute_color_line_STDEV( uncompressed, channels, sum_x, sum_x2 ); + vec_len2 = 1.0f / ( 0.00001f + + sum_x2[0]*sum_x2[0] + sum_x2[1]*sum_x2[1] + sum_x2[2]*sum_x2[2] ); + /* finding the max and min vector values */ + dot_max = + ( + sum_x2[0] * uncompressed[0] + + sum_x2[1] * uncompressed[1] + + sum_x2[2] * uncompressed[2] + ); + dot_min = dot_max; + for( i = 1; i < 16; ++i ) + { + dot = + ( + sum_x2[0] * uncompressed[i*channels+0] + + sum_x2[1] * uncompressed[i*channels+1] + + sum_x2[2] * uncompressed[i*channels+2] + ); + if( dot < dot_min ) + { + dot_min = dot; + } else if( dot > dot_max ) + { + dot_max = dot; + } + } + /* and the offset (from the average location) */ + dot = sum_x2[0]*sum_x[0] + sum_x2[1]*sum_x[1] + sum_x2[2]*sum_x[2]; + dot_min -= dot; + dot_max -= dot; + /* post multiply by the scaling factor */ + dot_min *= vec_len2; + dot_max *= vec_len2; + /* OK, build the master colors */ + for( i = 0; i < 3; ++i ) + { + /* color 0 */ + c0[i] = (int)(0.5f + sum_x[i] + dot_max * sum_x2[i]); + if( c0[i] < 0 ) + { + c0[i] = 0; + } else if( c0[i] > 255 ) + { + c0[i] = 255; + } + /* color 1 */ + c1[i] = (int)(0.5f + sum_x[i] + dot_min * sum_x2[i]); + if( c1[i] < 0 ) + { + c1[i] = 0; + } else if( c1[i] > 255 ) + { + c1[i] = 255; + } + } + /* down_sample (with rounding?) */ + i = rgb_to_565( c0[0], c0[1], c0[2] ); + j = rgb_to_565( c1[0], c1[1], c1[2] ); + if( i > j ) + { + *cmax = i; + *cmin = j; + } else + { + *cmax = j; + *cmin = i; + } +} + +void + compress_DDS_color_block + ( + int channels, + const unsigned char *const uncompressed, + unsigned char compressed[8] + ) +{ + /* variables */ + int i; + int next_bit; + int enc_c0, enc_c1; + int c0[4], c1[4]; + float color_line[] = { 0.0f, 0.0f, 0.0f, 0.0f }; + float vec_len2 = 0.0f, dot_offset = 0.0f; + /* stupid order */ + int swizzle4[] = { 0, 2, 3, 1 }; + /* get the master colors */ + LSE_master_colors_max_min( &enc_c0, &enc_c1, channels, uncompressed ); + /* store the 565 color 0 and color 1 */ + compressed[0] = (enc_c0 >> 0) & 255; + compressed[1] = (enc_c0 >> 8) & 255; + compressed[2] = (enc_c1 >> 0) & 255; + compressed[3] = (enc_c1 >> 8) & 255; + /* zero out the compressed data */ + compressed[4] = 0; + compressed[5] = 0; + compressed[6] = 0; + compressed[7] = 0; + /* reconstitute the master color vectors */ + rgb_888_from_565( enc_c0, &c0[0], &c0[1], &c0[2] ); + rgb_888_from_565( enc_c1, &c1[0], &c1[1], &c1[2] ); + /* the new vector */ + vec_len2 = 0.0f; + for( i = 0; i < 3; ++i ) + { + color_line[i] = (float)(c1[i] - c0[i]); + vec_len2 += color_line[i] * color_line[i]; + } + if( vec_len2 > 0.0f ) + { + vec_len2 = 1.0f / vec_len2; + } + /* pre-proform the scaling */ + color_line[0] *= vec_len2; + color_line[1] *= vec_len2; + color_line[2] *= vec_len2; + /* compute the offset (constant) portion of the dot product */ + dot_offset = color_line[0]*c0[0] + color_line[1]*c0[1] + color_line[2]*c0[2]; + /* store the rest of the bits */ + next_bit = 8*4; + for( i = 0; i < 16; ++i ) + { + /* find the dot product of this color, to place it on the line + (should be [-1,1]) */ + int next_value = 0; + float dot_product = + color_line[0] * uncompressed[i*channels+0] + + color_line[1] * uncompressed[i*channels+1] + + color_line[2] * uncompressed[i*channels+2] - + dot_offset; + /* map to [0,3] */ + next_value = (int)( dot_product * 3.0f + 0.5f ); + if( next_value > 3 ) + { + next_value = 3; + } else if( next_value < 0 ) + { + next_value = 0; + } + /* OK, store this value */ + compressed[next_bit >> 3] |= swizzle4[ next_value ] << (next_bit & 7); + next_bit += 2; + } + /* done compressing to DXT1 */ +} + +void + compress_DDS_alpha_block + ( + const unsigned char *const uncompressed, + unsigned char compressed[8] + ) +{ + /* variables */ + int i; + int next_bit; + int a0, a1; + float scale_me; + /* stupid order */ + int swizzle8[] = { 1, 7, 6, 5, 4, 3, 2, 0 }; + /* get the alpha limits (a0 > a1) */ + a0 = a1 = uncompressed[3]; + for( i = 4+3; i < 16*4; i += 4 ) + { + if( uncompressed[i] > a0 ) + { + a0 = uncompressed[i]; + } else if( uncompressed[i] < a1 ) + { + a1 = uncompressed[i]; + } + } + /* store those limits, and zero the rest of the compressed dataset */ + compressed[0] = a0; + compressed[1] = a1; + /* zero out the compressed data */ + compressed[2] = 0; + compressed[3] = 0; + compressed[4] = 0; + compressed[5] = 0; + compressed[6] = 0; + compressed[7] = 0; + /* store the all of the alpha values */ + next_bit = 8*2; + scale_me = 7.9999f / (a0 - a1); + for( i = 3; i < 16*4; i += 4 ) + { + /* convert this alpha value to a 3 bit number */ + int svalue; + int value = (int)((uncompressed[i] - a1) * scale_me); + svalue = swizzle8[ value&7 ]; + /* OK, store this value, start with the 1st byte */ + compressed[next_bit >> 3] |= svalue << (next_bit & 7); + if( (next_bit & 7) > 5 ) + { + /* spans 2 bytes, fill in the start of the 2nd byte */ + compressed[1 + (next_bit >> 3)] |= svalue >> (8 - (next_bit & 7) ); + } + next_bit += 3; + } + /* done compressing to DXT1 */ +} diff --git a/lib/soil/image_DXT.h b/lib/soil/image_DXT.h new file mode 100644 index 000000000..75f604f42 --- /dev/null +++ b/lib/soil/image_DXT.h @@ -0,0 +1,123 @@ +/* + Jonathan Dummer + 2007-07-31-10.32 + + simple DXT compression / decompression code + + public domain +*/ + +#ifndef HEADER_IMAGE_DXT +#define HEADER_IMAGE_DXT + +/** + Converts an image from an array of unsigned chars (RGB or RGBA) to + DXT1 or DXT5, then saves the converted image to disk. + \return 0 if failed, otherwise returns 1 +**/ +int +save_image_as_DDS +( + const char *filename, + int width, int height, int channels, + const unsigned char *const data +); + +/** + take an image and convert it to DXT1 (no alpha) +**/ +unsigned char* +convert_image_to_DXT1 +( + const unsigned char *const uncompressed, + int width, int height, int channels, + int *out_size +); + +/** + take an image and convert it to DXT5 (with alpha) +**/ +unsigned char* +convert_image_to_DXT5 +( + const unsigned char *const uncompressed, + int width, int height, int channels, + int *out_size +); + +/** A bunch of DirectDraw Surface structures and flags **/ +typedef struct +{ + unsigned int dwMagic; + unsigned int dwSize; + unsigned int dwFlags; + unsigned int dwHeight; + unsigned int dwWidth; + unsigned int dwPitchOrLinearSize; + unsigned int dwDepth; + unsigned int dwMipMapCount; + unsigned int dwReserved1[ 11 ]; + + /* DDPIXELFORMAT */ + struct + { + unsigned int dwSize; + unsigned int dwFlags; + unsigned int dwFourCC; + unsigned int dwRGBBitCount; + unsigned int dwRBitMask; + unsigned int dwGBitMask; + unsigned int dwBBitMask; + unsigned int dwAlphaBitMask; + } + sPixelFormat; + + /* DDCAPS2 */ + struct + { + unsigned int dwCaps1; + unsigned int dwCaps2; + unsigned int dwDDSX; + unsigned int dwReserved; + } + sCaps; + unsigned int dwReserved2; +} +DDS_header ; + +/* the following constants were copied directly off the MSDN website */ + +/* The dwFlags member of the original DDSURFACEDESC2 structure + can be set to one or more of the following values. */ +#define DDSD_CAPS 0x00000001 +#define DDSD_HEIGHT 0x00000002 +#define DDSD_WIDTH 0x00000004 +#define DDSD_PITCH 0x00000008 +#define DDSD_PIXELFORMAT 0x00001000 +#define DDSD_MIPMAPCOUNT 0x00020000 +#define DDSD_LINEARSIZE 0x00080000 +#define DDSD_DEPTH 0x00800000 + +/* DirectDraw Pixel Format */ +#define DDPF_ALPHAPIXELS 0x00000001 +#define DDPF_FOURCC 0x00000004 +#define DDPF_RGB 0x00000040 + +/* The dwCaps1 member of the DDSCAPS2 structure can be + set to one or more of the following values. */ +#define DDSCAPS_COMPLEX 0x00000008 +#define DDSCAPS_TEXTURE 0x00001000 +#define DDSCAPS_MIPMAP 0x00400000 + +/* The dwCaps2 member of the DDSCAPS2 structure can be + set to one or more of the following values. */ +#define DDSCAPS2_CUBEMAP 0x00000200 +#define DDSCAPS2_CUBEMAP_POSITIVEX 0x00000400 +#define DDSCAPS2_CUBEMAP_NEGATIVEX 0x00000800 +#define DDSCAPS2_CUBEMAP_POSITIVEY 0x00001000 +#define DDSCAPS2_CUBEMAP_NEGATIVEY 0x00002000 +#define DDSCAPS2_CUBEMAP_POSITIVEZ 0x00004000 +#define DDSCAPS2_CUBEMAP_NEGATIVEZ 0x00008000 +#define DDSCAPS2_VOLUME 0x00200000 + +#endif /* HEADER_IMAGE_DXT */ diff --git a/lib/soil/image_helper.c b/lib/soil/image_helper.c new file mode 100644 index 000000000..8d3c7440c --- /dev/null +++ b/lib/soil/image_helper.c @@ -0,0 +1,444 @@ +/* + Jonathan Dummer + + image helper functions + + MIT license +*/ + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4244) +#endif + +#include "image_helper.h" +#include +#include + +/* Upscaling the image uses simple bilinear interpolation */ +int + up_scale_image + ( + const unsigned char* const orig, + int width, int height, int channels, + unsigned char* resampled, + int resampled_width, int resampled_height + ) +{ + float dx, dy; + int x, y, c; + + /* error(s) check */ + if ( (width < 1) || (height < 1) || + (resampled_width < 2) || (resampled_height < 2) || + (channels < 1) || + (NULL == orig) || (NULL == resampled) ) + { + /* signify badness */ + return 0; + } + /* + for each given pixel in the new map, find the exact location + from the original map which would contribute to this guy + */ + dx = (width - 1.0f) / (resampled_width - 1.0f); + dy = (height - 1.0f) / (resampled_height - 1.0f); + for ( y = 0; y < resampled_height; ++y ) + { + /* find the base y index and fractional offset from that */ + float sampley = y * dy; + int inty = (int)sampley; + /* if( inty < 0 ) { inty = 0; } else */ + if( inty > height - 2 ) { inty = height - 2; } + sampley -= inty; + for ( x = 0; x < resampled_width; ++x ) + { + float samplex = x * dx; + int intx = (int)samplex; + int base_index; + /* find the base x index and fractional offset from that */ + /* if( intx < 0 ) { intx = 0; } else */ + if( intx > width - 2 ) { intx = width - 2; } + samplex -= intx; + /* base index into the original image */ + base_index = (inty * width + intx) * channels; + for ( c = 0; c < channels; ++c ) + { + /* do the sampling */ + float value = 0.5f; + value += orig[base_index] + *(1.0f-samplex)*(1.0f-sampley); + value += orig[base_index+channels] + *(samplex)*(1.0f-sampley); + value += orig[base_index+width*channels] + *(1.0f-samplex)*(sampley); + value += orig[base_index+width*channels+channels] + *(samplex)*(sampley); + /* move to the next channel */ + ++base_index; + /* save the new value */ + resampled[y*resampled_width*channels+x*channels+c] = + (unsigned char)(value); + } + } + } + /* done */ + return 1; +} + +int + mipmap_image + ( + const unsigned char* const orig, + int width, int height, int channels, + unsigned char* resampled, + int block_size_x, int block_size_y + ) +{ + int mip_width, mip_height; + int i, j, c; + + /* error check */ + if( (width < 1) || (height < 1) || + (channels < 1) || (orig == NULL) || + (resampled == NULL) || + (block_size_x < 1) || (block_size_y < 1) ) + { + /* nothing to do */ + return 0; + } + mip_width = width / block_size_x; + mip_height = height / block_size_y; + if( mip_width < 1 ) + { + mip_width = 1; + } + if( mip_height < 1 ) + { + mip_height = 1; + } + for( j = 0; j < mip_height; ++j ) + { + for( i = 0; i < mip_width; ++i ) + { + for( c = 0; c < channels; ++c ) + { + const int index = (j*block_size_y)*width*channels + (i*block_size_x)*channels + c; + int sum_value; + int u,v; + int u_block = block_size_x; + int v_block = block_size_y; + int block_area; + /* do a bit of checking so we don't over-run the boundaries + (necessary for non-square textures!) */ + if( block_size_x * (i+1) > width ) + { + u_block = width - i*block_size_y; + } + if( block_size_y * (j+1) > height ) + { + v_block = height - j*block_size_y; + } + block_area = u_block*v_block; + /* for this pixel, see what the average + of all the values in the block are. + note: start the sum at the rounding value, not at 0 */ + sum_value = block_area >> 1; + for( v = 0; v < v_block; ++v ) + for( u = 0; u < u_block; ++u ) + { + sum_value += orig[index + v*width*channels + u*channels]; + } + resampled[j*mip_width*channels + i*channels + c] = sum_value / block_area; + } + } + } + return 1; +} + +int + scale_image_RGB_to_NTSC_safe + ( + unsigned char* orig, + int width, int height, int channels + ) +{ + const float scale_lo = 16.0f - 0.499f; + const float scale_hi = 235.0f + 0.499f; + int i, j; + int nc = channels; + unsigned char scale_LUT[256]; + /* error check */ + if( (width < 1) || (height < 1) || + (channels < 1) || (orig == NULL) ) + { + /* nothing to do */ + return 0; + } + /* set up the scaling Look Up Table */ + for( i = 0; i < 256; ++i ) + { + scale_LUT[i] = (unsigned char)((scale_hi - scale_lo) * i / 255.0f + scale_lo); + } + /* for channels = 2 or 4, ignore the alpha component */ + nc -= 1 - (channels & 1); + /* OK, go through the image and scale any non-alpha components */ + for( i = 0; i < width*height*channels; i += channels ) + { + for( j = 0; j < nc; ++j ) + { + orig[i+j] = scale_LUT[orig[i+j]]; + } + } + return 1; +} + +unsigned char clamp_byte( int x ) { return ( (x) < 0 ? (0) : ( (x) > 255 ? 255 : (x) ) ); } + +/* + This function takes the RGB components of the image + and converts them into YCoCg. 3 components will be + re-ordered to CoYCg (for optimum DXT1 compression), + while 4 components will be ordered CoCgAY (for DXT5 + compression). +*/ +int + convert_RGB_to_YCoCg + ( + unsigned char* orig, + int width, int height, int channels + ) +{ + int i; + /* error check */ + if( (width < 1) || (height < 1) || + (channels < 3) || (channels > 4) || + (orig == NULL) ) + { + /* nothing to do */ + return -1; + } + /* do the conversion */ + if( channels == 3 ) + { + for( i = 0; i < width*height*3; i += 3 ) + { + int r = orig[i+0]; + int g = (orig[i+1] + 1) >> 1; + int b = orig[i+2]; + int tmp = (2 + r + b) >> 2; + /* Co */ + orig[i+0] = clamp_byte( 128 + ((r - b + 1) >> 1) ); + /* Y */ + orig[i+1] = clamp_byte( g + tmp ); + /* Cg */ + orig[i+2] = clamp_byte( 128 + g - tmp ); + } + } else + { + for( i = 0; i < width*height*4; i += 4 ) + { + int r = orig[i+0]; + int g = (orig[i+1] + 1) >> 1; + int b = orig[i+2]; + unsigned char a = orig[i+3]; + int tmp = (2 + r + b) >> 2; + /* Co */ + orig[i+0] = clamp_byte( 128 + ((r - b + 1) >> 1) ); + /* Cg */ + orig[i+1] = clamp_byte( 128 + g - tmp ); + /* Alpha */ + orig[i+2] = a; + /* Y */ + orig[i+3] = clamp_byte( g + tmp ); + } + } + /* done */ + return 0; +} + +/* + This function takes the YCoCg components of the image + and converts them into RGB. See above. +*/ +int + convert_YCoCg_to_RGB + ( + unsigned char* orig, + int width, int height, int channels + ) +{ + int i; + /* error check */ + if( (width < 1) || (height < 1) || + (channels < 3) || (channels > 4) || + (orig == NULL) ) + { + /* nothing to do */ + return -1; + } + /* do the conversion */ + if( channels == 3 ) + { + for( i = 0; i < width*height*3; i += 3 ) + { + int co = orig[i+0] - 128; + int y = orig[i+1]; + int cg = orig[i+2] - 128; + /* R */ + orig[i+0] = clamp_byte( y + co - cg ); + /* G */ + orig[i+1] = clamp_byte( y + cg ); + /* B */ + orig[i+2] = clamp_byte( y - co - cg ); + } + } else + { + for( i = 0; i < width*height*4; i += 4 ) + { + int co = orig[i+0] - 128; + int cg = orig[i+1] - 128; + unsigned char a = orig[i+2]; + int y = orig[i+3]; + /* R */ + orig[i+0] = clamp_byte( y + co - cg ); + /* G */ + orig[i+1] = clamp_byte( y + cg ); + /* B */ + orig[i+2] = clamp_byte( y - co - cg ); + /* A */ + orig[i+3] = a; + } + } + /* done */ + return 0; +} + +float +find_max_RGBE +( + unsigned char *image, + int width, int height +) +{ + float max_val = 0.0f; + unsigned char *img = image; + int i, j; + for( i = width * height; i > 0; --i ) + { + /* float scale = powf( 2.0f, img[3] - 128.0f ) / 255.0f; */ + float scale = ldexp( 1.0f / 255.0f, (int)(img[3]) - 128 ); + for( j = 0; j < 3; ++j ) + { + if( img[j] * scale > max_val ) + { + max_val = img[j] * scale; + } + } + /* next pixel */ + img += 4; + } + return max_val; +} + +int +RGBE_to_RGBdivA +( + unsigned char *image, + int width, int height, + int rescale_to_max +) +{ + /* local variables */ + int i, iv; + unsigned char *img = image; + float scale = 1.0f; + /* error check */ + if( (!image) || (width < 1) || (height < 1) ) + { + return 0; + } + /* convert (note: no negative numbers, but 0.0 is possible) */ + if( rescale_to_max ) + { + scale = 255.0f / find_max_RGBE( image, width, height ); + } + for( i = width * height; i > 0; --i ) + { + /* decode this pixel, and find the max */ + float r,g,b,e, m; + /* e = scale * powf( 2.0f, img[3] - 128.0f ) / 255.0f; */ + e = scale * ldexp( 1.0f / 255.0f, (int)(img[3]) - 128 ); + r = e * img[0]; + g = e * img[1]; + b = e * img[2]; + m = (r > g) ? r : g; + m = (b > m) ? b : m; + /* and encode it into RGBdivA */ + iv = (m != 0.0f) ? (int)(255.0f / m) : 1.0f; + iv = (iv < 1) ? 1 : iv; + img[3] = (iv > 255) ? 255 : iv; + iv = (int)(img[3] * r + 0.5f); + img[0] = (iv > 255) ? 255 : iv; + iv = (int)(img[3] * g + 0.5f); + img[1] = (iv > 255) ? 255 : iv; + iv = (int)(img[3] * b + 0.5f); + img[2] = (iv > 255) ? 255 : iv; + /* and on to the next pixel */ + img += 4; + } + return 1; +} + +int +RGBE_to_RGBdivA2 +( + unsigned char *image, + int width, int height, + int rescale_to_max +) +{ + /* local variables */ + int i, iv; + unsigned char *img = image; + float scale = 1.0f; + /* error check */ + if( (!image) || (width < 1) || (height < 1) ) + { + return 0; + } + /* convert (note: no negative numbers, but 0.0 is possible) */ + if( rescale_to_max ) + { + scale = 255.0f * 255.0f / find_max_RGBE( image, width, height ); + } + for( i = width * height; i > 0; --i ) + { + /* decode this pixel, and find the max */ + float r,g,b,e, m; + /* e = scale * powf( 2.0f, img[3] - 128.0f ) / 255.0f; */ + e = scale * ldexp( 1.0f / 255.0f, (int)(img[3]) - 128 ); + r = e * img[0]; + g = e * img[1]; + b = e * img[2]; + m = (r > g) ? r : g; + m = (b > m) ? b : m; + /* and encode it into RGBdivA */ + iv = (m != 0.0f) ? (int)sqrtf( 255.0f * 255.0f / m ) : 1.0f; + iv = (iv < 1) ? 1 : iv; + img[3] = (iv > 255) ? 255 : iv; + iv = (int)(img[3] * img[3] * r / 255.0f + 0.5f); + img[0] = (iv > 255) ? 255 : iv; + iv = (int)(img[3] * img[3] * g / 255.0f + 0.5f); + img[1] = (iv > 255) ? 255 : iv; + iv = (int)(img[3] * img[3] * b / 255.0f + 0.5f); + img[2] = (iv > 255) ? 255 : iv; + /* and on to the next pixel */ + img += 4; + } + return 1; +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif diff --git a/lib/soil/image_helper.h b/lib/soil/image_helper.h new file mode 100644 index 000000000..3fa2662f0 --- /dev/null +++ b/lib/soil/image_helper.h @@ -0,0 +1,115 @@ +/* + Jonathan Dummer + + Image helper functions + + MIT license +*/ + +#ifndef HEADER_IMAGE_HELPER +#define HEADER_IMAGE_HELPER + +#ifdef __cplusplus +extern "C" { +#endif + +/** + This function upscales an image. + Not to be used to create MIPmaps, + but to make it square, + or to make it a power-of-two sized. +**/ +int + up_scale_image + ( + const unsigned char* const orig, + int width, int height, int channels, + unsigned char* resampled, + int resampled_width, int resampled_height + ); + +/** + This function downscales an image. + Used for creating MIPmaps, + the incoming image should be a + power-of-two sized. +**/ +int + mipmap_image + ( + const unsigned char* const orig, + int width, int height, int channels, + unsigned char* resampled, + int block_size_x, int block_size_y + ); + +/** + This function takes the RGB components of the image + and scales each channel from [0,255] to [16,235]. + This makes the colors "Safe" for display on NTSC + displays. Note that this is _NOT_ a good idea for + loading images like normal- or height-maps! +**/ +int + scale_image_RGB_to_NTSC_safe + ( + unsigned char* orig, + int width, int height, int channels + ); + +/** + This function takes the RGB components of the image + and converts them into YCoCg. 3 components will be + re-ordered to CoYCg (for optimum DXT1 compression), + while 4 components will be ordered CoCgAY (for DXT5 + compression). +**/ +int + convert_RGB_to_YCoCg + ( + unsigned char* orig, + int width, int height, int channels + ); + +/** + This function takes the YCoCg components of the image + and converts them into RGB. See above. +**/ +int + convert_YCoCg_to_RGB + ( + unsigned char* orig, + int width, int height, int channels + ); + +/** + Converts an HDR image from an array + of unsigned chars (RGBE) to RGBdivA + \return 0 if failed, otherwise returns 1 +**/ +int + RGBE_to_RGBdivA + ( + unsigned char *image, + int width, int height, + int rescale_to_max + ); + +/** + Converts an HDR image from an array + of unsigned chars (RGBE) to RGBdivA2 + \return 0 if failed, otherwise returns 1 +**/ +int + RGBE_to_RGBdivA2 + ( + unsigned char *image, + int width, int height, + int rescale_to_max + ); + +#ifdef __cplusplus +} +#endif + +#endif /* HEADER_IMAGE_HELPER */ diff --git a/lib/soil/stb_image_aug.c b/lib/soil/stb_image_aug.c new file mode 100644 index 000000000..cd598d6af --- /dev/null +++ b/lib/soil/stb_image_aug.c @@ -0,0 +1,3698 @@ +/* stbi-1.16 - public domain JPEG/PNG reader - http://nothings.org/stb_image.c + when you control the images you're loading + + QUICK NOTES: + Primarily of interest to game developers and other people who can + avoid problematic images and only need the trivial interface + + JPEG baseline (no JPEG progressive, no oddball channel decimations) + PNG non-interlaced + BMP non-1bpp, non-RLE + TGA (not sure what subset, if a subset) + PSD (composited view only, no extra channels) + HDR (radiance rgbE format) + writes BMP,TGA (define STBI_NO_WRITE to remove code) + decoded from memory or through stdio FILE (define STBI_NO_STDIO to remove code) + supports installable dequantizing-IDCT, YCbCr-to-RGB conversion (define STBI_SIMD) + + TODO: + stbi_info_* + + history: + 1.16 major bugfix - convert_format converted one too many pixels + 1.15 initialize some fields for thread safety + 1.14 fix threadsafe conversion bug; header-file-only version (#define STBI_HEADER_FILE_ONLY before including) + 1.13 threadsafe + 1.12 const qualifiers in the API + 1.11 Support installable IDCT, colorspace conversion routines + 1.10 Fixes for 64-bit (don't use "unsigned long") + optimized upsampling by Fabian "ryg" Giesen + 1.09 Fix format-conversion for PSD code (bad global variables!) + 1.08 Thatcher Ulrich's PSD code integrated by Nicolas Schulz + 1.07 attempt to fix C++ warning/errors again + 1.06 attempt to fix C++ warning/errors again + 1.05 fix TGA loading to return correct *comp and use good luminance calc + 1.04 default float alpha is 1, not 255; use 'void *' for stbi_image_free + 1.03 bugfixes to STBI_NO_STDIO, STBI_NO_HDR + 1.02 support for (subset of) HDR files, float interface for preferred access to them + 1.01 fix bug: possible bug in handling right-side up bmps... not sure + fix bug: the stbi_bmp_load() and stbi_tga_load() functions didn't work at all + 1.00 interface to zlib that skips zlib header + 0.99 correct handling of alpha in palette + 0.98 TGA loader by lonesock; dynamically add loaders (untested) + 0.97 jpeg errors on too large a file; also catch another malloc failure + 0.96 fix detection of invalid v value - particleman@mollyrocket forum + 0.95 during header scan, seek to markers in case of padding + 0.94 STBI_NO_STDIO to disable stdio usage; rename all #defines the same + 0.93 handle jpegtran output; verbose errors + 0.92 read 4,8,16,24,32-bit BMP files of several formats + 0.91 output 24-bit Windows 3.0 BMP files + 0.90 fix a few more warnings; bump version number to approach 1.0 + 0.61 bugfixes due to Marc LeBlanc, Christopher Lloyd + 0.60 fix compiling as c++ + 0.59 fix warnings: merge Dave Moore's -Wall fixes + 0.58 fix bug: zlib uncompressed mode len/nlen was wrong endian + 0.57 fix bug: jpg last huffman symbol before marker was >9 bits but less + than 16 available + 0.56 fix bug: zlib uncompressed mode len vs. nlen + 0.55 fix bug: restart_interval not initialized to 0 + 0.54 allow NULL for 'int *comp' + 0.53 fix bug in png 3->4; speedup png decoding + 0.52 png handles req_comp=3,4 directly; minor cleanup; jpeg comments + 0.51 obey req_comp requests, 1-component jpegs return as 1-component, + on 'test' only check type, not whether we support this variant +*/ + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4018 4100 4244) +#else +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wsign-compare" +#pragma GCC diagnostic ignored "-Wunused-parameter" +#pragma GCC diagnostic ignored "-Wunused-but-set-variable" +#endif + +#include "stb_image_aug.h" + +#ifndef STBI_NO_HDR +#include // ldexp +#include // strcmp +#endif + +#ifndef STBI_NO_STDIO +#include +#endif +#include +#include +#include +#include + +#ifndef _MSC_VER + #ifdef __cplusplus + #define __forceinline inline + #else + #define __forceinline + #endif +#endif + + +// implementation: +typedef unsigned char uint8; +typedef unsigned short uint16; +typedef signed short int16; +typedef unsigned int uint32; +typedef signed int int32; +typedef unsigned int uint; + +// should produce compiler error if size is wrong +typedef unsigned char validate_uint32[sizeof(uint32)==4]; + +#if defined(STBI_NO_STDIO) && !defined(STBI_NO_WRITE) +#define STBI_NO_WRITE +#endif + +#ifndef STBI_NO_DDS +#include "stbi_DDS_aug.h" +#endif + +// I (JLD) want full messages for SOIL +#define STBI_FAILURE_USERMSG 1 + +////////////////////////////////////////////////////////////////////////////// +// +// Generic API that works on all image types +// + +// this is not threadsafe +static char *failure_reason; + +char *stbi_failure_reason(void) +{ + return failure_reason; +} + +static int e(char *str) +{ + failure_reason = str; + return 0; +} + +#ifdef STBI_NO_FAILURE_STRINGS + #define e(x,y) 0 +#elif defined(STBI_FAILURE_USERMSG) + #define e(x,y) e(y) +#else + #define e(x,y) e(x) +#endif + +#define epf(x,y) ((float *) (e(x,y)?NULL:NULL)) +#define epuc(x,y) ((unsigned char *) (e(x,y)?NULL:NULL)) + +void stbi_image_free(void *retval_from_stbi_load) +{ + free(retval_from_stbi_load); +} + +#define MAX_LOADERS 32 +stbi_loader *loaders[MAX_LOADERS]; +static int max_loaders = 0; + +int stbi_register_loader(stbi_loader *loader) +{ + int i; + for (i=0; i < MAX_LOADERS; ++i) { + // already present? + if (loaders[i] == loader) + return 1; + // end of the list? + if (loaders[i] == NULL) { + loaders[i] = loader; + max_loaders = i+1; + return 1; + } + } + // no room for it + return 0; +} + +#ifndef STBI_NO_HDR +static float *ldr_to_hdr(stbi_uc *data, int x, int y, int comp); +static stbi_uc *hdr_to_ldr(float *data, int x, int y, int comp); +#endif + +#ifndef STBI_NO_STDIO +unsigned char *stbi_load(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = fopen(filename, "rb"); + unsigned char *result; + if (!f) return epuc("can't fopen", "Unable to open file"); + result = stbi_load_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +unsigned char *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + int i; + if (stbi_jpeg_test_file(f)) + return stbi_jpeg_load_from_file(f,x,y,comp,req_comp); + if (stbi_png_test_file(f)) + return stbi_png_load_from_file(f,x,y,comp,req_comp); + if (stbi_bmp_test_file(f)) + return stbi_bmp_load_from_file(f,x,y,comp,req_comp); + if (stbi_psd_test_file(f)) + return stbi_psd_load_from_file(f,x,y,comp,req_comp); + #ifndef STBI_NO_DDS + if (stbi_dds_test_file(f)) + return stbi_dds_load_from_file(f,x,y,comp,req_comp); + #endif + #ifndef STBI_NO_HDR + if (stbi_hdr_test_file(f)) { + float *hdr = stbi_hdr_load_from_file(f, x,y,comp,req_comp); + return hdr_to_ldr(hdr, *x, *y, req_comp ? req_comp : *comp); + } + #endif + for (i=0; i < max_loaders; ++i) + if (loaders[i]->test_file(f)) + return loaders[i]->load_from_file(f,x,y,comp,req_comp); + // test tga last because it's a crappy test! + if (stbi_tga_test_file(f)) + return stbi_tga_load_from_file(f,x,y,comp,req_comp); + return epuc("unknown image type", "Image not of any known type, or corrupt"); +} +#endif + +unsigned char *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + int i; + if (stbi_jpeg_test_memory(buffer,len)) + return stbi_jpeg_load_from_memory(buffer,len,x,y,comp,req_comp); + if (stbi_png_test_memory(buffer,len)) + return stbi_png_load_from_memory(buffer,len,x,y,comp,req_comp); + if (stbi_bmp_test_memory(buffer,len)) + return stbi_bmp_load_from_memory(buffer,len,x,y,comp,req_comp); + if (stbi_psd_test_memory(buffer,len)) + return stbi_psd_load_from_memory(buffer,len,x,y,comp,req_comp); + #ifndef STBI_NO_DDS + if (stbi_dds_test_memory(buffer,len)) + return stbi_dds_load_from_memory(buffer,len,x,y,comp,req_comp); + #endif + #ifndef STBI_NO_HDR + if (stbi_hdr_test_memory(buffer, len)) { + float *hdr = stbi_hdr_load_from_memory(buffer, len,x,y,comp,req_comp); + return hdr_to_ldr(hdr, *x, *y, req_comp ? req_comp : *comp); + } + #endif + for (i=0; i < max_loaders; ++i) + if (loaders[i]->test_memory(buffer,len)) + return loaders[i]->load_from_memory(buffer,len,x,y,comp,req_comp); + // test tga last because it's a crappy test! + if (stbi_tga_test_memory(buffer,len)) + return stbi_tga_load_from_memory(buffer,len,x,y,comp,req_comp); + return epuc("unknown image type", "Image not of any known type, or corrupt"); +} + +#ifndef STBI_NO_HDR + +#ifndef STBI_NO_STDIO +float *stbi_loadf(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = fopen(filename, "rb"); + float *result; + if (!f) return epf("can't fopen", "Unable to open file"); + result = stbi_loadf_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +float *stbi_loadf_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *data; + #ifndef STBI_NO_HDR + if (stbi_hdr_test_file(f)) + return stbi_hdr_load_from_file(f,x,y,comp,req_comp); + #endif + data = stbi_load_from_file(f, x, y, comp, req_comp); + if (data) + return ldr_to_hdr(data, *x, *y, req_comp ? req_comp : *comp); + return epf("unknown image type", "Image not of any known type, or corrupt"); +} +#endif + +float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi_uc *data; + #ifndef STBI_NO_HDR + if (stbi_hdr_test_memory(buffer, len)) + return stbi_hdr_load_from_memory(buffer, len,x,y,comp,req_comp); + #endif + data = stbi_load_from_memory(buffer, len, x, y, comp, req_comp); + if (data) + return ldr_to_hdr(data, *x, *y, req_comp ? req_comp : *comp); + return epf("unknown image type", "Image not of any known type, or corrupt"); +} +#endif + +// these is-hdr-or-not is defined independent of whether STBI_NO_HDR is +// defined, for API simplicity; if STBI_NO_HDR is defined, it always +// reports false! + +int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len) +{ + #ifndef STBI_NO_HDR + return stbi_hdr_test_memory(buffer, len); + #else + return 0; + #endif +} + +#ifndef STBI_NO_STDIO +extern int stbi_is_hdr (char const *filename) +{ + FILE *f = fopen(filename, "rb"); + int result=0; + if (f) { + result = stbi_is_hdr_from_file(f); + fclose(f); + } + return result; +} + +extern int stbi_is_hdr_from_file(FILE *f) +{ + #ifndef STBI_NO_HDR + return stbi_hdr_test_file(f); + #else + return 0; + #endif +} + +#endif + +// @TODO: get image dimensions & components without fully decoding +#ifndef STBI_NO_STDIO +extern int stbi_info (char const *filename, int *x, int *y, int *comp); +extern int stbi_info_from_file (FILE *f, int *x, int *y, int *comp); +#endif +extern int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp); + +#ifndef STBI_NO_HDR +static float h2l_gamma_i=1.0f/2.2f, h2l_scale_i=1.0f; +static float l2h_gamma=2.2f, l2h_scale=1.0f; + +void stbi_hdr_to_ldr_gamma(float gamma) { h2l_gamma_i = 1/gamma; } +void stbi_hdr_to_ldr_scale(float scale) { h2l_scale_i = 1/scale; } + +void stbi_ldr_to_hdr_gamma(float gamma) { l2h_gamma = gamma; } +void stbi_ldr_to_hdr_scale(float scale) { l2h_scale = scale; } +#endif + + +////////////////////////////////////////////////////////////////////////////// +// +// Common code used by all image loaders +// + +enum +{ + SCAN_load=0, + SCAN_type, + SCAN_header, +}; + +typedef struct +{ + uint32 img_x, img_y; + int img_n, img_out_n; + + #ifndef STBI_NO_STDIO + FILE *img_file; + #endif + uint8 *img_buffer, *img_buffer_end; +} stbi; + +#ifndef STBI_NO_STDIO +static void start_file(stbi *s, FILE *f) +{ + s->img_file = f; +} +#endif + +static void start_mem(stbi *s, uint8 const *buffer, int len) +{ +#ifndef STBI_NO_STDIO + s->img_file = NULL; +#endif + s->img_buffer = (uint8 *) buffer; + s->img_buffer_end = (uint8 *) buffer+len; +} + +__forceinline static int get8(stbi *s) +{ +#ifndef STBI_NO_STDIO + if (s->img_file) { + int c = fgetc(s->img_file); + return c == EOF ? 0 : c; + } +#endif + if (s->img_buffer < s->img_buffer_end) + return *s->img_buffer++; + return 0; +} + +__forceinline static int at_eof(stbi *s) +{ +#ifndef STBI_NO_STDIO + if (s->img_file) + return feof(s->img_file); +#endif + return s->img_buffer >= s->img_buffer_end; +} + +__forceinline static uint8 get8u(stbi *s) +{ + return (uint8) get8(s); +} + +static void skip(stbi *s, int n) +{ +#ifndef STBI_NO_STDIO + if (s->img_file) + fseek(s->img_file, n, SEEK_CUR); + else +#endif + s->img_buffer += n; +} + +static int get16(stbi *s) +{ + int z = get8(s); + return (z << 8) + get8(s); +} + +static uint32 get32(stbi *s) +{ + uint32 z = get16(s); + return (z << 16) + get16(s); +} + +static int get16le(stbi *s) +{ + int z = get8(s); + return z + (get8(s) << 8); +} + +static uint32 get32le(stbi *s) +{ + uint32 z = get16le(s); + return z + (get16le(s) << 16); +} + +static void getn(stbi *s, stbi_uc *buffer, int n) +{ +#ifndef STBI_NO_STDIO + if (s->img_file) { + fread(buffer, 1, n, s->img_file); + return; + } +#endif + memcpy(buffer, s->img_buffer, n); + s->img_buffer += n; +} + +////////////////////////////////////////////////////////////////////////////// +// +// generic converter from built-in img_n to req_comp +// individual types do this automatically as much as possible (e.g. jpeg +// does all cases internally since it needs to colorspace convert anyway, +// and it never has alpha, so very few cases ). png can automatically +// interleave an alpha=255 channel, but falls back to this for other cases +// +// assume data buffer is malloced, so malloc a new one and free that one +// only failure mode is malloc failing + +static uint8 compute_y(int r, int g, int b) +{ + return (uint8) (((r*77) + (g*150) + (29*b)) >> 8); +} + +static unsigned char *convert_format(unsigned char *data, int img_n, int req_comp, uint x, uint y) +{ + int i,j; + unsigned char *good; + + if (req_comp == img_n) return data; + assert(req_comp >= 1 && req_comp <= 4); + + good = (unsigned char *) malloc(req_comp * x * y); + if (good == NULL) { + free(data); + return epuc("outofmem", "Out of memory"); + } + + for (j=0; j < (int) y; ++j) { + unsigned char *src = data + j * x * img_n ; + unsigned char *dest = good + j * x * req_comp; + + #define COMBO(a,b) ((a)*8+(b)) + #define CASE(a,b) case COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + // convert source image with img_n components to one with req_comp components; + // avoid switch per pixel, so use switch per scanline and massive macros + switch(COMBO(img_n, req_comp)) { + CASE(1,2) dest[0]=src[0], dest[1]=255; break; + CASE(1,3) dest[0]=dest[1]=dest[2]=src[0]; break; + CASE(1,4) dest[0]=dest[1]=dest[2]=src[0], dest[3]=255; break; + CASE(2,1) dest[0]=src[0]; break; + CASE(2,3) dest[0]=dest[1]=dest[2]=src[0]; break; + CASE(2,4) dest[0]=dest[1]=dest[2]=src[0], dest[3]=src[1]; break; + CASE(3,4) dest[0]=src[0],dest[1]=src[1],dest[2]=src[2],dest[3]=255; break; + CASE(3,1) dest[0]=compute_y(src[0],src[1],src[2]); break; + CASE(3,2) dest[0]=compute_y(src[0],src[1],src[2]), dest[1] = 255; break; + CASE(4,1) dest[0]=compute_y(src[0],src[1],src[2]); break; + CASE(4,2) dest[0]=compute_y(src[0],src[1],src[2]), dest[1] = src[3]; break; + CASE(4,3) dest[0]=src[0],dest[1]=src[1],dest[2]=src[2]; break; + default: assert(0); + } + #undef CASE + } + + free(data); + return good; +} + +#ifndef STBI_NO_HDR +static float *ldr_to_hdr(stbi_uc *data, int x, int y, int comp) +{ + int i,k,n; + float *output = (float *) malloc(x * y * comp * sizeof(float)); + if (output == NULL) { free(data); return epf("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + output[i*comp + k] = (float) pow(data[i*comp+k]/255.0f, l2h_gamma) * l2h_scale; + } + if (k < comp) output[i*comp + k] = data[i*comp+k]/255.0f; + } + free(data); + return output; +} + +#define float2int(x) ((int) (x)) +static stbi_uc *hdr_to_ldr(float *data, int x, int y, int comp) +{ + int i,k,n; + stbi_uc *output = (stbi_uc *) malloc(x * y * comp); + if (output == NULL) { free(data); return epuc("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + float z = (float) pow(data[i*comp+k]*h2l_scale_i, h2l_gamma_i) * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = float2int(z); + } + if (k < comp) { + float z = data[i*comp+k] * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = float2int(z); + } + } + free(data); + return output; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// "baseline" JPEG/JFIF decoder (not actually fully baseline implementation) +// +// simple implementation +// - channel subsampling of at most 2 in each dimension +// - doesn't support delayed output of y-dimension +// - simple interface (only one output format: 8-bit interleaved RGB) +// - doesn't try to recover corrupt jpegs +// - doesn't allow partial loading, loading multiple at once +// - still fast on x86 (copying globals into locals doesn't help x86) +// - allocates lots of intermediate memory (full size of all components) +// - non-interleaved case requires this anyway +// - allows good upsampling (see next) +// high-quality +// - upsampled channels are bilinearly interpolated, even across blocks +// - quality integer IDCT derived from IJG's 'slow' +// performance +// - fast huffman; reasonable integer IDCT +// - uses a lot of intermediate memory, could cache poorly +// - load http://nothings.org/remote/anemones.jpg 3 times on 2.8Ghz P4 +// stb_jpeg: 1.34 seconds (MSVC6, default release build) +// stb_jpeg: 1.06 seconds (MSVC6, processor = Pentium Pro) +// IJL11.dll: 1.08 seconds (compiled by intel) +// IJG 1998: 0.98 seconds (MSVC6, makefile provided by IJG) +// IJG 1998: 0.95 seconds (MSVC6, makefile + proc=PPro) + +// huffman decoding acceleration +#define FAST_BITS 9 // larger handles more cases; smaller stomps less cache + +typedef struct +{ + uint8 fast[1 << FAST_BITS]; + // weirdly, repacking this into AoS is a 10% speed loss, instead of a win + uint16 code[256]; + uint8 values[256]; + uint8 size[257]; + unsigned int maxcode[18]; + int delta[17]; // old 'firstsymbol' - old 'firstcode' +} huffman; + +typedef struct +{ + #if STBI_SIMD + unsigned short dequant2[4][64]; + #endif + stbi s; + huffman huff_dc[4]; + huffman huff_ac[4]; + uint8 dequant[4][64]; + +// sizes for components, interleaved MCUs + int img_h_max, img_v_max; + int img_mcu_x, img_mcu_y; + int img_mcu_w, img_mcu_h; + +// definition of jpeg image component + struct + { + int id; + int h,v; + int tq; + int hd,ha; + int dc_pred; + + int x,y,w2,h2; + uint8 *data; + void *raw_data; + uint8 *linebuf; + } img_comp[4]; + + uint32 code_buffer; // jpeg entropy-coded buffer + int code_bits; // number of valid bits + unsigned char marker; // marker seen while filling entropy buffer + int nomore; // flag if we saw a marker so must stop + + int scan_n, order[4]; + int restart_interval, todo; +} jpeg; + +static int build_huffman(huffman *h, int *count) +{ + int i,j,k=0,code; + // build size list for each symbol (from JPEG spec) + for (i=0; i < 16; ++i) + for (j=0; j < count[i]; ++j) + h->size[k++] = (uint8) (i+1); + h->size[k] = 0; + + // compute actual symbols (from jpeg spec) + code = 0; + k = 0; + for(j=1; j <= 16; ++j) { + // compute delta to add to code to compute symbol id + h->delta[j] = k - code; + if (h->size[k] == j) { + while (h->size[k] == j) + h->code[k++] = (uint16) (code++); + if (code-1 >= (1 << j)) return e("bad code lengths","Corrupt JPEG"); + } + // compute largest code + 1 for this size, preshifted as needed later + h->maxcode[j] = code << (16-j); + code <<= 1; + } + h->maxcode[j] = 0xffffffff; + + // build non-spec acceleration table; 255 is flag for not-accelerated + memset(h->fast, 255, 1 << FAST_BITS); + for (i=0; i < k; ++i) { + int s = h->size[i]; + if (s <= FAST_BITS) { + int c = h->code[i] << (FAST_BITS-s); + int m = 1 << (FAST_BITS-s); + for (j=0; j < m; ++j) { + h->fast[c+j] = (uint8) i; + } + } + } + return 1; +} + +static void grow_buffer_unsafe(jpeg *j) +{ + do { + int b = j->nomore ? 0 : get8(&j->s); + if (b == 0xff) { + int c = get8(&j->s); + if (c != 0) { + j->marker = (unsigned char) c; + j->nomore = 1; + return; + } + } + j->code_buffer = (j->code_buffer << 8) | b; + j->code_bits += 8; + } while (j->code_bits <= 24); +} + +// (1 << n) - 1 +static uint32 bmask[17]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535}; + +// decode a jpeg huffman value from the bitstream +__forceinline static int decode(jpeg *j, huffman *h) +{ + unsigned int temp; + int c,k; + + if (j->code_bits < 16) grow_buffer_unsafe(j); + + // look at the top FAST_BITS and determine what symbol ID it is, + // if the code is <= FAST_BITS + c = (j->code_buffer >> (j->code_bits - FAST_BITS)) & ((1 << FAST_BITS)-1); + k = h->fast[c]; + if (k < 255) { + if (h->size[k] > j->code_bits) + return -1; + j->code_bits -= h->size[k]; + return h->values[k]; + } + + // naive test is to shift the code_buffer down so k bits are + // valid, then test against maxcode. To speed this up, we've + // preshifted maxcode left so that it has (16-k) 0s at the + // end; in other words, regardless of the number of bits, it + // wants to be compared against something shifted to have 16; + // that way we don't need to shift inside the loop. + if (j->code_bits < 16) + temp = (j->code_buffer << (16 - j->code_bits)) & 0xffff; + else + temp = (j->code_buffer >> (j->code_bits - 16)) & 0xffff; + for (k=FAST_BITS+1 ; ; ++k) + if (temp < h->maxcode[k]) + break; + if (k == 17) { + // error! code not found + j->code_bits -= 16; + return -1; + } + + if (k > j->code_bits) + return -1; + + // convert the huffman code to the symbol id + c = ((j->code_buffer >> (j->code_bits - k)) & bmask[k]) + h->delta[k]; + assert((((j->code_buffer) >> (j->code_bits - h->size[c])) & bmask[h->size[c]]) == h->code[c]); + + // convert the id to a symbol + j->code_bits -= k; + return h->values[c]; +} + +// combined JPEG 'receive' and JPEG 'extend', since baseline +// always extends everything it receives. +__forceinline static int extend_receive(jpeg *j, int n) +{ + unsigned int m = 1 << (n-1); + unsigned int k; + if (j->code_bits < n) grow_buffer_unsafe(j); + k = (j->code_buffer >> (j->code_bits - n)) & bmask[n]; + j->code_bits -= n; + // the following test is probably a random branch that won't + // predict well. I tried to table accelerate it but failed. + // maybe it's compiling as a conditional move? + if (k < m) + return (-1 << n) + k + 1; + else + return k; +} + +// given a value that's at position X in the zigzag stream, +// where does it appear in the 8x8 matrix coded as row-major? +static uint8 dezigzag[64+15] = +{ + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63, + // let corrupt input sample past end + 63, 63, 63, 63, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63 +}; + +// decode one 64-entry block-- +static int decode_block(jpeg *j, short data[64], huffman *hdc, huffman *hac, int b) +{ + int diff,dc,k; + int t = decode(j, hdc); + if (t < 0) return e("bad huffman code","Corrupt JPEG"); + + // 0 all the ac values now so we can do it 32-bits at a time + memset(data,0,64*sizeof(data[0])); + + diff = t ? extend_receive(j, t) : 0; + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + data[0] = (short) dc; + + // decode AC components, see JPEG spec + k = 1; + do { + int r,s; + int rs = decode(j, hac); + if (rs < 0) return e("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (rs != 0xf0) break; // end block + k += 16; + } else { + k += r; + // decode into unzigzag'd location + data[dezigzag[k++]] = (short) extend_receive(j,s); + } + } while (k < 64); + return 1; +} + +// take a -128..127 value and clamp it and convert to 0..255 +__forceinline static uint8 clamp(int x) +{ + x += 128; + // trick to use a single test to catch both cases + if ((unsigned int) x > 255) { + if (x < 0) return 0; + if (x > 255) return 255; + } + return (uint8) x; +} + +#define f2f(x) (int) (((x) * 4096 + 0.5)) +#define fsh(x) ((x) << 12) + +// derived from jidctint -- DCT_ISLOW +#define IDCT_1D(s0,s1,s2,s3,s4,s5,s6,s7) \ + int t0,t1,t2,t3,p1,p2,p3,p4,p5,x0,x1,x2,x3; \ + p2 = s2; \ + p3 = s6; \ + p1 = (p2+p3) * f2f(0.5411961f); \ + t2 = p1 + p3*f2f(-1.847759065f); \ + t3 = p1 + p2*f2f( 0.765366865f); \ + p2 = s0; \ + p3 = s4; \ + t0 = fsh(p2+p3); \ + t1 = fsh(p2-p3); \ + x0 = t0+t3; \ + x3 = t0-t3; \ + x1 = t1+t2; \ + x2 = t1-t2; \ + t0 = s7; \ + t1 = s5; \ + t2 = s3; \ + t3 = s1; \ + p3 = t0+t2; \ + p4 = t1+t3; \ + p1 = t0+t3; \ + p2 = t1+t2; \ + p5 = (p3+p4)*f2f( 1.175875602f); \ + t0 = t0*f2f( 0.298631336f); \ + t1 = t1*f2f( 2.053119869f); \ + t2 = t2*f2f( 3.072711026f); \ + t3 = t3*f2f( 1.501321110f); \ + p1 = p5 + p1*f2f(-0.899976223f); \ + p2 = p5 + p2*f2f(-2.562915447f); \ + p3 = p3*f2f(-1.961570560f); \ + p4 = p4*f2f(-0.390180644f); \ + t3 += p1+p4; \ + t2 += p2+p3; \ + t1 += p2+p4; \ + t0 += p1+p3; + +#if !STBI_SIMD +// .344 seconds on 3*anemones.jpg +static void idct_block(uint8 *out, int out_stride, short data[64], uint8 *dequantize) +{ + int i,val[64],*v=val; + uint8 *o,*dq = dequantize; + short *d = data; + + // columns + for (i=0; i < 8; ++i,++d,++dq, ++v) { + // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing + if (d[ 8]==0 && d[16]==0 && d[24]==0 && d[32]==0 + && d[40]==0 && d[48]==0 && d[56]==0) { + // no shortcut 0 seconds + // (1|2|3|4|5|6|7)==0 0 seconds + // all separate -0.047 seconds + // 1 && 2|3 && 4|5 && 6|7: -0.047 seconds + int dcterm = d[0] * dq[0] << 2; + v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm; + } else { + IDCT_1D(d[ 0]*dq[ 0],d[ 8]*dq[ 8],d[16]*dq[16],d[24]*dq[24], + d[32]*dq[32],d[40]*dq[40],d[48]*dq[48],d[56]*dq[56]) + // constants scaled things up by 1<<12; let's bring them back + // down, but keep 2 extra bits of precision + x0 += 512; x1 += 512; x2 += 512; x3 += 512; + v[ 0] = (x0+t3) >> 10; + v[56] = (x0-t3) >> 10; + v[ 8] = (x1+t2) >> 10; + v[48] = (x1-t2) >> 10; + v[16] = (x2+t1) >> 10; + v[40] = (x2-t1) >> 10; + v[24] = (x3+t0) >> 10; + v[32] = (x3-t0) >> 10; + } + } + + for (i=0, v=val, o=out; i < 8; ++i,v+=8,o+=out_stride) { + // no fast case since the first 1D IDCT spread components out + IDCT_1D(v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7]) + // constants scaled things up by 1<<12, plus we had 1<<2 from first + // loop, plus horizontal and vertical each scale by sqrt(8) so together + // we've got an extra 1<<3, so 1<<17 total we need to remove. + x0 += 65536; x1 += 65536; x2 += 65536; x3 += 65536; + o[0] = clamp((x0+t3) >> 17); + o[7] = clamp((x0-t3) >> 17); + o[1] = clamp((x1+t2) >> 17); + o[6] = clamp((x1-t2) >> 17); + o[2] = clamp((x2+t1) >> 17); + o[5] = clamp((x2-t1) >> 17); + o[3] = clamp((x3+t0) >> 17); + o[4] = clamp((x3-t0) >> 17); + } +} +#else +static void idct_block(uint8 *out, int out_stride, short data[64], unsigned short *dequantize) +{ + int i,val[64],*v=val; + uint8 *o; + unsigned short *dq = dequantize; + short *d = data; + + // columns + for (i=0; i < 8; ++i,++d,++dq, ++v) { + // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing + if (d[ 8]==0 && d[16]==0 && d[24]==0 && d[32]==0 + && d[40]==0 && d[48]==0 && d[56]==0) { + // no shortcut 0 seconds + // (1|2|3|4|5|6|7)==0 0 seconds + // all separate -0.047 seconds + // 1 && 2|3 && 4|5 && 6|7: -0.047 seconds + int dcterm = d[0] * dq[0] << 2; + v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm; + } else { + IDCT_1D(d[ 0]*dq[ 0],d[ 8]*dq[ 8],d[16]*dq[16],d[24]*dq[24], + d[32]*dq[32],d[40]*dq[40],d[48]*dq[48],d[56]*dq[56]) + // constants scaled things up by 1<<12; let's bring them back + // down, but keep 2 extra bits of precision + x0 += 512; x1 += 512; x2 += 512; x3 += 512; + v[ 0] = (x0+t3) >> 10; + v[56] = (x0-t3) >> 10; + v[ 8] = (x1+t2) >> 10; + v[48] = (x1-t2) >> 10; + v[16] = (x2+t1) >> 10; + v[40] = (x2-t1) >> 10; + v[24] = (x3+t0) >> 10; + v[32] = (x3-t0) >> 10; + } + } + + for (i=0, v=val, o=out; i < 8; ++i,v+=8,o+=out_stride) { + // no fast case since the first 1D IDCT spread components out + IDCT_1D(v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7]) + // constants scaled things up by 1<<12, plus we had 1<<2 from first + // loop, plus horizontal and vertical each scale by sqrt(8) so together + // we've got an extra 1<<3, so 1<<17 total we need to remove. + x0 += 65536; x1 += 65536; x2 += 65536; x3 += 65536; + o[0] = clamp((x0+t3) >> 17); + o[7] = clamp((x0-t3) >> 17); + o[1] = clamp((x1+t2) >> 17); + o[6] = clamp((x1-t2) >> 17); + o[2] = clamp((x2+t1) >> 17); + o[5] = clamp((x2-t1) >> 17); + o[3] = clamp((x3+t0) >> 17); + o[4] = clamp((x3-t0) >> 17); + } +} +static stbi_idct_8x8 stbi_idct_installed = idct_block; + +extern void stbi_install_idct(stbi_idct_8x8 func) +{ + stbi_idct_installed = func; +} +#endif + +#define MARKER_none 0xff +// if there's a pending marker from the entropy stream, return that +// otherwise, fetch from the stream and get a marker. if there's no +// marker, return 0xff, which is never a valid marker value +static uint8 get_marker(jpeg *j) +{ + uint8 x; + if (j->marker != MARKER_none) { x = j->marker; j->marker = MARKER_none; return x; } + x = get8u(&j->s); + if (x != 0xff) return MARKER_none; + while (x == 0xff) + x = get8u(&j->s); + return x; +} + +// in each scan, we'll have scan_n components, and the order +// of the components is specified by order[] +#define RESTART(x) ((x) >= 0xd0 && (x) <= 0xd7) + +// after a restart interval, reset the entropy decoder and +// the dc prediction +static void reset(jpeg *j) +{ + j->code_bits = 0; + j->code_buffer = 0; + j->nomore = 0; + j->img_comp[0].dc_pred = j->img_comp[1].dc_pred = j->img_comp[2].dc_pred = 0; + j->marker = MARKER_none; + j->todo = j->restart_interval ? j->restart_interval : 0x7fffffff; + // no more than 1<<31 MCUs if no restart_interal? that's plenty safe, + // since we don't even allow 1<<30 pixels +} + +static int parse_entropy_coded_data(jpeg *z) +{ + reset(z); + if (z->scan_n == 1) { + int i,j; + #if STBI_SIMD + __declspec(align(16)) + #endif + short data[64]; + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + if (!decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+z->img_comp[n].ha, n)) return 0; + #if STBI_SIMD + stbi_idct_installed(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data, z->dequant2[z->img_comp[n].tq]); + #else + idct_block(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data, z->dequant[z->img_comp[n].tq]); + #endif + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) grow_buffer_unsafe(z); + // if it's NOT a restart, then just bail, so we get corrupt data + // rather than no data + if (!RESTART(z->marker)) return 1; + reset(z); + } + } + } + } else { // interleaved! + int i,j,k,x,y; + short data[64]; + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x)*8; + int y2 = (j*z->img_comp[n].v + y)*8; + if (!decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+z->img_comp[n].ha, n)) return 0; + #if STBI_SIMD + stbi_idct_installed(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data, z->dequant2[z->img_comp[n].tq]); + #else + idct_block(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data, z->dequant[z->img_comp[n].tq]); + #endif + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) grow_buffer_unsafe(z); + // if it's NOT a restart, then just bail, so we get corrupt data + // rather than no data + if (!RESTART(z->marker)) return 1; + reset(z); + } + } + } + } + return 1; +} + +static int process_marker(jpeg *z, int m) +{ + int L; + switch (m) { + case MARKER_none: // no marker found + return e("expected marker","Corrupt JPEG"); + + case 0xC2: // SOF - progressive + return e("progressive jpeg","JPEG format not supported (progressive)"); + + case 0xDD: // DRI - specify restart interval + if (get16(&z->s) != 4) return e("bad DRI len","Corrupt JPEG"); + z->restart_interval = get16(&z->s); + return 1; + + case 0xDB: // DQT - define quantization table + L = get16(&z->s)-2; + while (L > 0) { + int q = get8(&z->s); + int p = q >> 4; + int t = q & 15,i; + if (p != 0) return e("bad DQT type","Corrupt JPEG"); + if (t > 3) return e("bad DQT table","Corrupt JPEG"); + for (i=0; i < 64; ++i) + z->dequant[t][dezigzag[i]] = get8u(&z->s); + #if STBI_SIMD + for (i=0; i < 64; ++i) + z->dequant2[t][i] = dequant[t][i]; + #endif + L -= 65; + } + return L==0; + + case 0xC4: // DHT - define huffman table + L = get16(&z->s)-2; + while (L > 0) { + uint8 *v; + int sizes[16],i,m=0; + int q = get8(&z->s); + int tc = q >> 4; + int th = q & 15; + if (tc > 1 || th > 3) return e("bad DHT header","Corrupt JPEG"); + for (i=0; i < 16; ++i) { + sizes[i] = get8(&z->s); + m += sizes[i]; + } + L -= 17; + if (tc == 0) { + if (!build_huffman(z->huff_dc+th, sizes)) return 0; + v = z->huff_dc[th].values; + } else { + if (!build_huffman(z->huff_ac+th, sizes)) return 0; + v = z->huff_ac[th].values; + } + for (i=0; i < m; ++i) + v[i] = get8u(&z->s); + L -= m; + } + return L==0; + } + // check for comment block or APP blocks + if ((m >= 0xE0 && m <= 0xEF) || m == 0xFE) { + skip(&z->s, get16(&z->s)-2); + return 1; + } + return 0; +} + +// after we see SOS +static int process_scan_header(jpeg *z) +{ + int i; + int Ls = get16(&z->s); + z->scan_n = get8(&z->s); + if (z->scan_n < 1 || z->scan_n > 4 || z->scan_n > (int) z->s.img_n) return e("bad SOS component count","Corrupt JPEG"); + if (Ls != 6+2*z->scan_n) return e("bad SOS len","Corrupt JPEG"); + for (i=0; i < z->scan_n; ++i) { + int id = get8(&z->s), which; + int q = get8(&z->s); + for (which = 0; which < z->s.img_n; ++which) + if (z->img_comp[which].id == id) + break; + if (which == z->s.img_n) return 0; + z->img_comp[which].hd = q >> 4; if (z->img_comp[which].hd > 3) return e("bad DC huff","Corrupt JPEG"); + z->img_comp[which].ha = q & 15; if (z->img_comp[which].ha > 3) return e("bad AC huff","Corrupt JPEG"); + z->order[i] = which; + } + if (get8(&z->s) != 0) return e("bad SOS","Corrupt JPEG"); + get8(&z->s); // should be 63, but might be 0 + if (get8(&z->s) != 0) return e("bad SOS","Corrupt JPEG"); + + return 1; +} + +static int process_frame_header(jpeg *z, int scan) +{ + stbi *s = &z->s; + int Lf,p,i,q, h_max=1,v_max=1,c; + Lf = get16(s); if (Lf < 11) return e("bad SOF len","Corrupt JPEG"); // JPEG + p = get8(s); if (p != 8) return e("only 8-bit","JPEG format not supported: 8-bit only"); // JPEG baseline + s->img_y = get16(s); if (s->img_y == 0) return e("no header height", "JPEG format not supported: delayed height"); // Legal, but we don't handle it--but neither does IJG + s->img_x = get16(s); if (s->img_x == 0) return e("0 width","Corrupt JPEG"); // JPEG requires + c = get8(s); + if (c != 3 && c != 1) return e("bad component count","Corrupt JPEG"); // JFIF requires + s->img_n = c; + for (i=0; i < c; ++i) { + z->img_comp[i].data = NULL; + z->img_comp[i].linebuf = NULL; + } + + if (Lf != 8+3*s->img_n) return e("bad SOF len","Corrupt JPEG"); + + for (i=0; i < s->img_n; ++i) { + z->img_comp[i].id = get8(s); + if (z->img_comp[i].id != i+1) // JFIF requires + if (z->img_comp[i].id != i) // some version of jpegtran outputs non-JFIF-compliant files! + return e("bad component ID","Corrupt JPEG"); + q = get8(s); + z->img_comp[i].h = (q >> 4); if (!z->img_comp[i].h || z->img_comp[i].h > 4) return e("bad H","Corrupt JPEG"); + z->img_comp[i].v = q & 15; if (!z->img_comp[i].v || z->img_comp[i].v > 4) return e("bad V","Corrupt JPEG"); + z->img_comp[i].tq = get8(s); if (z->img_comp[i].tq > 3) return e("bad TQ","Corrupt JPEG"); + } + + if (scan != SCAN_load) return 1; + + if ((1 << 30) / s->img_x / s->img_n < s->img_y) return e("too large", "Image too large to decode"); + + for (i=0; i < s->img_n; ++i) { + if (z->img_comp[i].h > h_max) h_max = z->img_comp[i].h; + if (z->img_comp[i].v > v_max) v_max = z->img_comp[i].v; + } + + // compute interleaved mcu info + z->img_h_max = h_max; + z->img_v_max = v_max; + z->img_mcu_w = h_max * 8; + z->img_mcu_h = v_max * 8; + z->img_mcu_x = (s->img_x + z->img_mcu_w-1) / z->img_mcu_w; + z->img_mcu_y = (s->img_y + z->img_mcu_h-1) / z->img_mcu_h; + + for (i=0; i < s->img_n; ++i) { + // number of effective pixels (e.g. for non-interleaved MCU) + z->img_comp[i].x = (s->img_x * z->img_comp[i].h + h_max-1) / h_max; + z->img_comp[i].y = (s->img_y * z->img_comp[i].v + v_max-1) / v_max; + // to simplify generation, we'll allocate enough memory to decode + // the bogus oversized data from using interleaved MCUs and their + // big blocks (e.g. a 16x16 iMCU on an image of width 33); we won't + // discard the extra data until colorspace conversion + z->img_comp[i].w2 = z->img_mcu_x * z->img_comp[i].h * 8; + z->img_comp[i].h2 = z->img_mcu_y * z->img_comp[i].v * 8; + z->img_comp[i].raw_data = malloc(z->img_comp[i].w2 * z->img_comp[i].h2+15); + if (z->img_comp[i].raw_data == NULL) { + for(--i; i >= 0; --i) { + free(z->img_comp[i].raw_data); + z->img_comp[i].data = NULL; + } + return e("outofmem", "Out of memory"); + } + // align blocks for installable-idct using mmx/sse + z->img_comp[i].data = (uint8*) (((size_t) z->img_comp[i].raw_data + 15) & ~15); + z->img_comp[i].linebuf = NULL; + } + + return 1; +} + +// use comparisons since in some cases we handle more than one case (e.g. SOF) +#define DNL(x) ((x) == 0xdc) +#define SOI(x) ((x) == 0xd8) +#define EOI(x) ((x) == 0xd9) +#define SOF(x) ((x) == 0xc0 || (x) == 0xc1) +#define SOS(x) ((x) == 0xda) + +static int decode_jpeg_header(jpeg *z, int scan) +{ + int m; + z->marker = MARKER_none; // initialize cached marker to empty + m = get_marker(z); + if (!SOI(m)) return e("no SOI","Corrupt JPEG"); + if (scan == SCAN_type) return 1; + m = get_marker(z); + while (!SOF(m)) { + if (!process_marker(z,m)) return 0; + m = get_marker(z); + while (m == MARKER_none) { + // some files have extra padding after their blocks, so ok, we'll scan + if (at_eof(&z->s)) return e("no SOF", "Corrupt JPEG"); + m = get_marker(z); + } + } + if (!process_frame_header(z, scan)) return 0; + return 1; +} + +static int decode_jpeg_image(jpeg *j) +{ + int m; + j->restart_interval = 0; + if (!decode_jpeg_header(j, SCAN_load)) return 0; + m = get_marker(j); + while (!EOI(m)) { + if (SOS(m)) { + if (!process_scan_header(j)) return 0; + if (!parse_entropy_coded_data(j)) return 0; + } else { + if (!process_marker(j, m)) return 0; + } + m = get_marker(j); + } + return 1; +} + +// static jfif-centered resampling (across block boundaries) + +typedef uint8 *(*resample_row_func)(uint8 *out, uint8 *in0, uint8 *in1, + int w, int hs); + +#define div4(x) ((uint8) ((x) >> 2)) + +static uint8 *resample_row_1(uint8 *out, uint8 *in_near, uint8 *in_far, int w, int hs) +{ + return in_near; +} + +static uint8* resample_row_v_2(uint8 *out, uint8 *in_near, uint8 *in_far, int w, int hs) +{ + // need to generate two samples vertically for every one in input + int i; + for (i=0; i < w; ++i) + out[i] = div4(3*in_near[i] + in_far[i] + 2); + return out; +} + +static uint8* resample_row_h_2(uint8 *out, uint8 *in_near, uint8 *in_far, int w, int hs) +{ + // need to generate two samples horizontally for every one in input + int i; + uint8 *input = in_near; + if (w == 1) { + // if only one sample, can't do any interpolation + out[0] = out[1] = input[0]; + return out; + } + + out[0] = input[0]; + out[1] = div4(input[0]*3 + input[1] + 2); + for (i=1; i < w-1; ++i) { + int n = 3*input[i]+2; + out[i*2+0] = div4(n+input[i-1]); + out[i*2+1] = div4(n+input[i+1]); + } + out[i*2+0] = div4(input[w-2]*3 + input[w-1] + 2); + out[i*2+1] = input[w-1]; + return out; +} + +#define div16(x) ((uint8) ((x) >> 4)) + +static uint8 *resample_row_hv_2(uint8 *out, uint8 *in_near, uint8 *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i,t0,t1; + if (w == 1) { + out[0] = out[1] = div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + out[0] = div4(t1+2); + for (i=1; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = div16(3*t0 + t1 + 8); + out[i*2 ] = div16(3*t1 + t0 + 8); + } + out[w*2-1] = div4(t1+2); + return out; +} + +static uint8 *resample_row_generic(uint8 *out, uint8 *in_near, uint8 *in_far, int w, int hs) +{ + // resample with nearest-neighbor + int i,j; + for (i=0; i < w; ++i) + for (j=0; j < hs; ++j) + out[i*hs+j] = in_near[i]; + return out; +} + +#define float2fixed(x) ((int) ((x) * 65536 + 0.5)) + +// 0.38 seconds on 3*anemones.jpg (0.25 with processor = Pro) +// VC6 without processor=Pro is generating multiple LEAs per multiply! +static void YCbCr_to_RGB_row(uint8 *out, uint8 *y, uint8 *pcb, uint8 *pcr, int count, int step) +{ + int i; + for (i=0; i < count; ++i) { + int y_fixed = (y[i] << 16) + 32768; // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr*float2fixed(1.40200f); + g = y_fixed - cr*float2fixed(0.71414f) - cb*float2fixed(0.34414f); + b = y_fixed + cb*float2fixed(1.77200f); + r >>= 16; + g >>= 16; + b >>= 16; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (uint8)r; + out[1] = (uint8)g; + out[2] = (uint8)b; + out[3] = 255; + out += step; + } +} + +#if STBI_SIMD +static stbi_YCbCr_to_RGB_run stbi_YCbCr_installed = YCbCr_to_RGB_row; + +void stbi_install_YCbCr_to_RGB(stbi_YCbCr_to_RGB_run func) +{ + stbi_YCbCr_installed = func; +} +#endif + + +// clean up the temporary component buffers +static void cleanup_jpeg(jpeg *j) +{ + int i; + for (i=0; i < j->s.img_n; ++i) { + if (j->img_comp[i].data) { + free(j->img_comp[i].raw_data); + j->img_comp[i].data = NULL; + } + if (j->img_comp[i].linebuf) { + free(j->img_comp[i].linebuf); + j->img_comp[i].linebuf = NULL; + } + } +} + +typedef struct +{ + resample_row_func resample; + uint8 *line0,*line1; + int hs,vs; // expansion factor in each axis + int w_lores; // horizontal pixels pre-expansion + int ystep; // how far through vertical expansion we are + int ypos; // which pre-expansion row we're on +} stbi_resample; + +static uint8 *load_jpeg_image(jpeg *z, int *out_x, int *out_y, int *comp, int req_comp) +{ + int n, decode_n; + // validate req_comp + if (req_comp < 0 || req_comp > 4) return epuc("bad req_comp", "Internal error"); + z->s.img_n = 0; + + // load a jpeg image from whichever source + if (!decode_jpeg_image(z)) { cleanup_jpeg(z); return NULL; } + + // determine actual number of components to generate + n = req_comp ? req_comp : z->s.img_n; + + if (z->s.img_n == 3 && n < 3) + decode_n = 1; + else + decode_n = z->s.img_n; + + // resample and color-convert + { + int k; + uint i,j; + uint8 *output; + uint8 *coutput[4]; + + stbi_resample res_comp[4]; + + for (k=0; k < decode_n; ++k) { + stbi_resample *r = &res_comp[k]; + + // allocate line buffer big enough for upsampling off the edges + // with upsample factor of 4 + z->img_comp[k].linebuf = (uint8 *) malloc(z->s.img_x + 3); + if (!z->img_comp[k].linebuf) { cleanup_jpeg(z); return epuc("outofmem", "Out of memory"); } + + r->hs = z->img_h_max / z->img_comp[k].h; + r->vs = z->img_v_max / z->img_comp[k].v; + r->ystep = r->vs >> 1; + r->w_lores = (z->s.img_x + r->hs-1) / r->hs; + r->ypos = 0; + r->line0 = r->line1 = z->img_comp[k].data; + + if (r->hs == 1 && r->vs == 1) r->resample = resample_row_1; + else if (r->hs == 1 && r->vs == 2) r->resample = resample_row_v_2; + else if (r->hs == 2 && r->vs == 1) r->resample = resample_row_h_2; + else if (r->hs == 2 && r->vs == 2) r->resample = resample_row_hv_2; + else r->resample = resample_row_generic; + } + + // can't error after this so, this is safe + output = (uint8 *) malloc(n * z->s.img_x * z->s.img_y + 1); + if (!output) { cleanup_jpeg(z); return epuc("outofmem", "Out of memory"); } + + // now go ahead and resample + for (j=0; j < z->s.img_y; ++j) { + uint8 *out = output + n * z->s.img_x * j; + for (k=0; k < decode_n; ++k) { + stbi_resample *r = &res_comp[k]; + int y_bot = r->ystep >= (r->vs >> 1); + coutput[k] = r->resample(z->img_comp[k].linebuf, + y_bot ? r->line1 : r->line0, + y_bot ? r->line0 : r->line1, + r->w_lores, r->hs); + if (++r->ystep >= r->vs) { + r->ystep = 0; + r->line0 = r->line1; + if (++r->ypos < z->img_comp[k].y) + r->line1 += z->img_comp[k].w2; + } + } + if (n >= 3) { + uint8 *y = coutput[0]; + if (z->s.img_n == 3) { + #if STBI_SIMD + stbi_YCbCr_installed(out, y, coutput[1], coutput[2], z->s.img_x, n); + #else + YCbCr_to_RGB_row(out, y, coutput[1], coutput[2], z->s.img_x, n); + #endif + } else + for (i=0; i < z->s.img_x; ++i) { + out[0] = out[1] = out[2] = y[i]; + out[3] = 255; // not used if n==3 + out += n; + } + } else { + uint8 *y = coutput[0]; + if (n == 1) + for (i=0; i < z->s.img_x; ++i) out[i] = y[i]; + else + for (i=0; i < z->s.img_x; ++i) *out++ = y[i], *out++ = 255; + } + } + cleanup_jpeg(z); + *out_x = z->s.img_x; + *out_y = z->s.img_y; + if (comp) *comp = z->s.img_n; // report original components, not output + return output; + } +} + +#ifndef STBI_NO_STDIO +unsigned char *stbi_jpeg_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + jpeg j; + start_file(&j.s, f); + return load_jpeg_image(&j, x,y,comp,req_comp); +} + +unsigned char *stbi_jpeg_load(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *data; + FILE *f = fopen(filename, "rb"); + if (!f) return NULL; + data = stbi_jpeg_load_from_file(f,x,y,comp,req_comp); + fclose(f); + return data; +} +#endif + +unsigned char *stbi_jpeg_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + jpeg j; + start_mem(&j.s, buffer,len); + return load_jpeg_image(&j, x,y,comp,req_comp); +} + +#ifndef STBI_NO_STDIO +int stbi_jpeg_test_file(FILE *f) +{ + int n,r; + jpeg j; + n = ftell(f); + start_file(&j.s, f); + r = decode_jpeg_header(&j, SCAN_type); + fseek(f,n,SEEK_SET); + return r; +} +#endif + +int stbi_jpeg_test_memory(stbi_uc const *buffer, int len) +{ + jpeg j; + start_mem(&j.s, buffer,len); + return decode_jpeg_header(&j, SCAN_type); +} + +// @TODO: +#ifndef STBI_NO_STDIO +extern int stbi_jpeg_info (char const *filename, int *x, int *y, int *comp); +extern int stbi_jpeg_info_from_file (FILE *f, int *x, int *y, int *comp); +#endif +extern int stbi_jpeg_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp); + +// public domain zlib decode v0.2 Sean Barrett 2006-11-18 +// simple implementation +// - all input must be provided in an upfront buffer +// - all output is written to a single output buffer (can malloc/realloc) +// performance +// - fast huffman + +// fast-way is faster to check than jpeg huffman, but slow way is slower +#define ZFAST_BITS 9 // accelerate all cases in default tables +#define ZFAST_MASK ((1 << ZFAST_BITS) - 1) + +// zlib-style huffman encoding +// (jpegs packs from left, zlib from right, so can't share code) +typedef struct +{ + uint16 fast[1 << ZFAST_BITS]; + uint16 firstcode[16]; + int maxcode[17]; + uint16 firstsymbol[16]; + uint8 size[288]; + uint16 value[288]; +} zhuffman; + +__forceinline static int bitreverse16(int n) +{ + n = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1); + n = ((n & 0xCCCC) >> 2) | ((n & 0x3333) << 2); + n = ((n & 0xF0F0) >> 4) | ((n & 0x0F0F) << 4); + n = ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8); + return n; +} + +__forceinline static int bit_reverse(int v, int bits) +{ + assert(bits <= 16); + // to bit reverse n bits, reverse 16 and shift + // e.g. 11 bits, bit reverse and shift away 5 + return bitreverse16(v) >> (16-bits); +} + +static int zbuild_huffman(zhuffman *z, uint8 *sizelist, int num) +{ + int i,k=0; + int code, next_code[16], sizes[17]; + + // DEFLATE spec for generating codes + memset(sizes, 0, sizeof(sizes)); + memset(z->fast, 255, sizeof(z->fast)); + for (i=0; i < num; ++i) + ++sizes[sizelist[i]]; + sizes[0] = 0; + for (i=1; i < 16; ++i) + assert(sizes[i] <= (1 << i)); + code = 0; + for (i=1; i < 16; ++i) { + next_code[i] = code; + z->firstcode[i] = (uint16) code; + z->firstsymbol[i] = (uint16) k; + code = (code + sizes[i]); + if (sizes[i]) + if (code-1 >= (1 << i)) return e("bad codelengths","Corrupt JPEG"); + z->maxcode[i] = code << (16-i); // preshift for inner loop + code <<= 1; + k += sizes[i]; + } + z->maxcode[16] = 0x10000; // sentinel + for (i=0; i < num; ++i) { + int s = sizelist[i]; + if (s) { + int c = next_code[s] - z->firstcode[s] + z->firstsymbol[s]; + z->size[c] = (uint8)s; + z->value[c] = (uint16)i; + if (s <= ZFAST_BITS) { + int k = bit_reverse(next_code[s],s); + while (k < (1 << ZFAST_BITS)) { + z->fast[k] = (uint16) c; + k += (1 << s); + } + } + ++next_code[s]; + } + } + return 1; +} + +// zlib-from-memory implementation for PNG reading +// because PNG allows splitting the zlib stream arbitrarily, +// and it's annoying structurally to have PNG call ZLIB call PNG, +// we require PNG read all the IDATs and combine them into a single +// memory buffer + +typedef struct +{ + uint8 *zbuffer, *zbuffer_end; + int num_bits; + uint32 code_buffer; + + char *zout; + char *zout_start; + char *zout_end; + int z_expandable; + + zhuffman z_length, z_distance; +} zbuf; + +__forceinline static int zget8(zbuf *z) +{ + if (z->zbuffer >= z->zbuffer_end) return 0; + return *z->zbuffer++; +} + +static void fill_bits(zbuf *z) +{ + do { + assert(z->code_buffer < (1U << z->num_bits)); + z->code_buffer |= zget8(z) << z->num_bits; + z->num_bits += 8; + } while (z->num_bits <= 24); +} + +__forceinline static unsigned int zreceive(zbuf *z, int n) +{ + unsigned int k; + if (z->num_bits < n) fill_bits(z); + k = z->code_buffer & ((1 << n) - 1); + z->code_buffer >>= n; + z->num_bits -= n; + return k; +} + +__forceinline static int zhuffman_decode(zbuf *a, zhuffman *z) +{ + int b,s,k; + if (a->num_bits < 16) fill_bits(a); + b = z->fast[a->code_buffer & ZFAST_MASK]; + if (b < 0xffff) { + s = z->size[b]; + a->code_buffer >>= s; + a->num_bits -= s; + return z->value[b]; + } + + // not resolved by fast table, so compute it the slow way + // use jpeg approach, which requires MSbits at top + k = bit_reverse(a->code_buffer, 16); + for (s=ZFAST_BITS+1; ; ++s) + if (k < z->maxcode[s]) + break; + if (s == 16) return -1; // invalid code! + // code size is s, so: + b = (k >> (16-s)) - z->firstcode[s] + z->firstsymbol[s]; + assert(z->size[b] == s); + a->code_buffer >>= s; + a->num_bits -= s; + return z->value[b]; +} + +static int expand(zbuf *z, int n) // need to make room for n bytes +{ + char *q; + int cur, limit; + if (!z->z_expandable) return e("output buffer limit","Corrupt PNG"); + cur = (int) (z->zout - z->zout_start); + limit = (int) (z->zout_end - z->zout_start); + while (cur + n > limit) + limit *= 2; + q = (char *) realloc(z->zout_start, limit); + if (q == NULL) return e("outofmem", "Out of memory"); + z->zout_start = q; + z->zout = q + cur; + z->zout_end = q + limit; + return 1; +} + +static int length_base[31] = { + 3,4,5,6,7,8,9,10,11,13, + 15,17,19,23,27,31,35,43,51,59, + 67,83,99,115,131,163,195,227,258,0,0 }; + +static int length_extra[31]= +{ 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 }; + +static int dist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, +257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0}; + +static int dist_extra[32] = +{ 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; + +static int parse_huffman_block(zbuf *a) +{ + for(;;) { + int z = zhuffman_decode(a, &a->z_length); + if (z < 256) { + if (z < 0) return e("bad huffman code","Corrupt PNG"); // error in huffman codes + if (a->zout >= a->zout_end) if (!expand(a, 1)) return 0; + *a->zout++ = (char) z; + } else { + uint8 *p; + int len,dist; + if (z == 256) return 1; + z -= 257; + len = length_base[z]; + if (length_extra[z]) len += zreceive(a, length_extra[z]); + z = zhuffman_decode(a, &a->z_distance); + if (z < 0) return e("bad huffman code","Corrupt PNG"); + dist = dist_base[z]; + if (dist_extra[z]) dist += zreceive(a, dist_extra[z]); + if (a->zout - a->zout_start < dist) return e("bad dist","Corrupt PNG"); + if (a->zout + len > a->zout_end) if (!expand(a, len)) return 0; + p = (uint8 *) (a->zout - dist); + while (len--) + *a->zout++ = *p++; + } + } +} + +static int compute_huffman_codes(zbuf *a) +{ + static uint8 length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; + static zhuffman z_codelength; // static just to save stack space + uint8 lencodes[286+32+137];//padding for maximum single op + uint8 codelength_sizes[19]; + int i,n; + + int hlit = zreceive(a,5) + 257; + int hdist = zreceive(a,5) + 1; + int hclen = zreceive(a,4) + 4; + + memset(codelength_sizes, 0, sizeof(codelength_sizes)); + for (i=0; i < hclen; ++i) { + int s = zreceive(a,3); + codelength_sizes[length_dezigzag[i]] = (uint8) s; + } + if (!zbuild_huffman(&z_codelength, codelength_sizes, 19)) return 0; + + n = 0; + while (n < hlit + hdist) { + int c = zhuffman_decode(a, &z_codelength); + assert(c >= 0 && c < 19); + if (c < 16) + lencodes[n++] = (uint8) c; + else if (c == 16) { + c = zreceive(a,2)+3; + memset(lencodes+n, lencodes[n-1], c); + n += c; + } else if (c == 17) { + c = zreceive(a,3)+3; + memset(lencodes+n, 0, c); + n += c; + } else { + assert(c == 18); + c = zreceive(a,7)+11; + memset(lencodes+n, 0, c); + n += c; + } + } + if (n != hlit+hdist) return e("bad codelengths","Corrupt PNG"); + if (!zbuild_huffman(&a->z_length, lencodes, hlit)) return 0; + if (!zbuild_huffman(&a->z_distance, lencodes+hlit, hdist)) return 0; + return 1; +} + +static int parse_uncompressed_block(zbuf *a) +{ + uint8 header[4]; + int len,nlen,k; + if (a->num_bits & 7) + zreceive(a, a->num_bits & 7); // discard + // drain the bit-packed data into header + k = 0; + while (a->num_bits > 0) { + header[k++] = (uint8) (a->code_buffer & 255); // wtf this warns? + a->code_buffer >>= 8; + a->num_bits -= 8; + } + assert(a->num_bits == 0); + // now fill header the normal way + while (k < 4) + header[k++] = (uint8) zget8(a); + len = header[1] * 256 + header[0]; + nlen = header[3] * 256 + header[2]; + if (nlen != (len ^ 0xffff)) return e("zlib corrupt","Corrupt PNG"); + if (a->zbuffer + len > a->zbuffer_end) return e("read past buffer","Corrupt PNG"); + if (a->zout + len > a->zout_end) + if (!expand(a, len)) return 0; + memcpy(a->zout, a->zbuffer, len); + a->zbuffer += len; + a->zout += len; + return 1; +} + +static int parse_zlib_header(zbuf *a) +{ + int cmf = zget8(a); + int cm = cmf & 15; + /* int cinfo = cmf >> 4; */ + int flg = zget8(a); + if ((cmf*256+flg) % 31 != 0) return e("bad zlib header","Corrupt PNG"); // zlib spec + if (flg & 32) return e("no preset dict","Corrupt PNG"); // preset dictionary not allowed in png + if (cm != 8) return e("bad compression","Corrupt PNG"); // DEFLATE required for png + // window = 1 << (8 + cinfo)... but who cares, we fully buffer output + return 1; +} + +// @TODO: should statically initialize these for optimal thread safety +static uint8 default_length[288], default_distance[32]; +static void init_defaults(void) +{ + int i; // use <= to match clearly with spec + for (i=0; i <= 143; ++i) default_length[i] = 8; + for ( ; i <= 255; ++i) default_length[i] = 9; + for ( ; i <= 279; ++i) default_length[i] = 7; + for ( ; i <= 287; ++i) default_length[i] = 8; + + for (i=0; i <= 31; ++i) default_distance[i] = 5; +} + +static int parse_zlib(zbuf *a, int parse_header) +{ + int final, type; + if (parse_header) + if (!parse_zlib_header(a)) return 0; + a->num_bits = 0; + a->code_buffer = 0; + do { + final = zreceive(a,1); + type = zreceive(a,2); + if (type == 0) { + if (!parse_uncompressed_block(a)) return 0; + } else if (type == 3) { + return 0; + } else { + if (type == 1) { + // use fixed code lengths + if (!default_distance[31]) init_defaults(); + if (!zbuild_huffman(&a->z_length , default_length , 288)) return 0; + if (!zbuild_huffman(&a->z_distance, default_distance, 32)) return 0; + } else { + if (!compute_huffman_codes(a)) return 0; + } + if (!parse_huffman_block(a)) return 0; + } + } while (!final); + return 1; +} + +static int do_zlib(zbuf *a, char *obuf, int olen, int exp, int parse_header) +{ + a->zout_start = obuf; + a->zout = obuf; + a->zout_end = obuf + olen; + a->z_expandable = exp; + + return parse_zlib(a, parse_header); +} + +char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen) +{ + zbuf a; + char *p = (char *) malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (uint8 *) buffer; + a.zbuffer_end = (uint8 *) buffer + len; + if (do_zlib(&a, p, initial_size, 1, 1)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + free(a.zout_start); + return NULL; + } +} + +char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen) +{ + return stbi_zlib_decode_malloc_guesssize(buffer, len, 16384, outlen); +} + +int stbi_zlib_decode_buffer(char *obuffer, int olen, char const *ibuffer, int ilen) +{ + zbuf a; + a.zbuffer = (uint8 *) ibuffer; + a.zbuffer_end = (uint8 *) ibuffer + ilen; + if (do_zlib(&a, obuffer, olen, 0, 1)) + return (int) (a.zout - a.zout_start); + else + return -1; +} + +char *stbi_zlib_decode_noheader_malloc(char const *buffer, int len, int *outlen) +{ + zbuf a; + char *p = (char *) malloc(16384); + if (p == NULL) return NULL; + a.zbuffer = (uint8 *) buffer; + a.zbuffer_end = (uint8 *) buffer+len; + if (do_zlib(&a, p, 16384, 1, 0)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + free(a.zout_start); + return NULL; + } +} + +int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen) +{ + zbuf a; + a.zbuffer = (uint8 *) ibuffer; + a.zbuffer_end = (uint8 *) ibuffer + ilen; + if (do_zlib(&a, obuffer, olen, 0, 0)) + return (int) (a.zout - a.zout_start); + else + return -1; +} + +// public domain "baseline" PNG decoder v0.10 Sean Barrett 2006-11-18 +// simple implementation +// - only 8-bit samples +// - no CRC checking +// - allocates lots of intermediate memory +// - avoids problem of streaming data between subsystems +// - avoids explicit window management +// performance +// - uses stb_zlib, a PD zlib implementation with fast huffman decoding + + +typedef struct +{ + uint32 length; + uint32 type; +} chunk; + +#define PNG_TYPE(a,b,c,d) (((a) << 24) + ((b) << 16) + ((c) << 8) + (d)) + +static chunk get_chunk_header(stbi *s) +{ + chunk c; + c.length = get32(s); + c.type = get32(s); + return c; +} + +static int check_png_header(stbi *s) +{ + static uint8 png_sig[8] = { 137,80,78,71,13,10,26,10 }; + int i; + for (i=0; i < 8; ++i) + if (get8(s) != png_sig[i]) return e("bad png sig","Not a PNG"); + return 1; +} + +typedef struct +{ + stbi s; + uint8 *idata, *expanded, *out; +} png; + + +enum { + F_none=0, F_sub=1, F_up=2, F_avg=3, F_paeth=4, + F_avg_first, F_paeth_first, +}; + +static uint8 first_row_filter[5] = +{ + F_none, F_sub, F_none, F_avg_first, F_paeth_first +}; + +static int paeth(int a, int b, int c) +{ + int p = a + b - c; + int pa = abs(p-a); + int pb = abs(p-b); + int pc = abs(p-c); + if (pa <= pb && pa <= pc) return a; + if (pb <= pc) return b; + return c; +} + +// create the png data from post-deflated data +static int create_png_image(png *a, uint8 *raw, uint32 raw_len, int out_n) +{ + stbi *s = &a->s; + uint32 i,j,stride = s->img_x*out_n; + int k; + int img_n = s->img_n; // copy it into a local for later + assert(out_n == s->img_n || out_n == s->img_n+1); + a->out = (uint8 *) malloc(s->img_x * s->img_y * out_n); + if (!a->out) return e("outofmem", "Out of memory"); + if (raw_len != (img_n * s->img_x + 1) * s->img_y) return e("not enough pixels","Corrupt PNG"); + for (j=0; j < s->img_y; ++j) { + uint8 *cur = a->out + stride*j; + uint8 *prior = cur - stride; + int filter = *raw++; + if (filter > 4) return e("invalid filter","Corrupt PNG"); + // if first row, use special filter that doesn't sample previous row + if (j == 0) filter = first_row_filter[filter]; + // handle first pixel explicitly + for (k=0; k < img_n; ++k) { + switch(filter) { + case F_none : cur[k] = raw[k]; break; + case F_sub : cur[k] = raw[k]; break; + case F_up : cur[k] = raw[k] + prior[k]; break; + case F_avg : cur[k] = raw[k] + (prior[k]>>1); break; + case F_paeth : cur[k] = (uint8) (raw[k] + paeth(0,prior[k],0)); break; + case F_avg_first : cur[k] = raw[k]; break; + case F_paeth_first: cur[k] = raw[k]; break; + } + } + if (img_n != out_n) cur[img_n] = 255; + raw += img_n; + cur += out_n; + prior += out_n; + // this is a little gross, so that we don't switch per-pixel or per-component + if (img_n == out_n) { + #define CASE(f) \ + case f: \ + for (i=s->img_x-1; i >= 1; --i, raw+=img_n,cur+=img_n,prior+=img_n) \ + for (k=0; k < img_n; ++k) + switch(filter) { + CASE(F_none) cur[k] = raw[k]; break; + CASE(F_sub) cur[k] = raw[k] + cur[k-img_n]; break; + CASE(F_up) cur[k] = raw[k] + prior[k]; break; + CASE(F_avg) cur[k] = raw[k] + ((prior[k] + cur[k-img_n])>>1); break; + CASE(F_paeth) cur[k] = (uint8) (raw[k] + paeth(cur[k-img_n],prior[k],prior[k-img_n])); break; + CASE(F_avg_first) cur[k] = raw[k] + (cur[k-img_n] >> 1); break; + CASE(F_paeth_first) cur[k] = (uint8) (raw[k] + paeth(cur[k-img_n],0,0)); break; + } + #undef CASE + } else { + assert(img_n+1 == out_n); + #define CASE(f) \ + case f: \ + for (i=s->img_x-1; i >= 1; --i, cur[img_n]=255,raw+=img_n,cur+=out_n,prior+=out_n) \ + for (k=0; k < img_n; ++k) + switch(filter) { + CASE(F_none) cur[k] = raw[k]; break; + CASE(F_sub) cur[k] = raw[k] + cur[k-out_n]; break; + CASE(F_up) cur[k] = raw[k] + prior[k]; break; + CASE(F_avg) cur[k] = raw[k] + ((prior[k] + cur[k-out_n])>>1); break; + CASE(F_paeth) cur[k] = (uint8) (raw[k] + paeth(cur[k-out_n],prior[k],prior[k-out_n])); break; + CASE(F_avg_first) cur[k] = raw[k] + (cur[k-out_n] >> 1); break; + CASE(F_paeth_first) cur[k] = (uint8) (raw[k] + paeth(cur[k-out_n],0,0)); break; + } + #undef CASE + } + } + return 1; +} + +static int compute_transparency(png *z, uint8 tc[3], int out_n) +{ + stbi *s = &z->s; + uint32 i, pixel_count = s->img_x * s->img_y; + uint8 *p = z->out; + + // compute color-based transparency, assuming we've + // already got 255 as the alpha value in the output + assert(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i=0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 255); + p += 2; + } + } else { + for (i=0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int expand_palette(png *a, uint8 *palette, int len, int pal_img_n) +{ + uint32 i, pixel_count = a->s.img_x * a->s.img_y; + uint8 *p, *temp_out, *orig = a->out; + + p = (uint8 *) malloc(pixel_count * pal_img_n); + if (p == NULL) return e("outofmem", "Out of memory"); + + // between here and free(out) below, exitting would leak + temp_out = p; + + if (pal_img_n == 3) { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p += 3; + } + } else { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p[3] = palette[n+3]; + p += 4; + } + } + free(a->out); + a->out = temp_out; + return 1; +} + +static int parse_png_file(png *z, int scan, int req_comp) +{ + uint8 palette[1024], pal_img_n=0; + uint8 has_trans=0, tc[3]; + uint32 ioff=0, idata_limit=0, i, pal_len=0; + int first=1,k; + stbi *s = &z->s; + + if (!check_png_header(s)) return 0; + + if (scan == SCAN_type) return 1; + + for(;;first=0) { + chunk c = get_chunk_header(s); + if (first && c.type != PNG_TYPE('I','H','D','R')) + return e("first not IHDR","Corrupt PNG"); + switch (c.type) { + case PNG_TYPE('I','H','D','R'): { + int depth,color,interlace,comp,filter; + if (!first) return e("multiple IHDR","Corrupt PNG"); + if (c.length != 13) return e("bad IHDR len","Corrupt PNG"); + s->img_x = get32(s); if (s->img_x > (1 << 24)) return e("too large","Very large image (corrupt?)"); + s->img_y = get32(s); if (s->img_y > (1 << 24)) return e("too large","Very large image (corrupt?)"); + depth = get8(s); if (depth != 8) return e("8bit only","PNG not supported: 8-bit only"); + color = get8(s); if (color > 6) return e("bad ctype","Corrupt PNG"); + if (color == 3) pal_img_n = 3; else if (color & 1) return e("bad ctype","Corrupt PNG"); + comp = get8(s); if (comp) return e("bad comp method","Corrupt PNG"); + filter= get8(s); if (filter) return e("bad filter method","Corrupt PNG"); + interlace = get8(s); if (interlace) return e("interlaced","PNG not supported: interlaced mode"); + if (!s->img_x || !s->img_y) return e("0-pixel image","Corrupt PNG"); + if (!pal_img_n) { + s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0); + if ((1 << 30) / s->img_x / s->img_n < s->img_y) return e("too large", "Image too large to decode"); + if (scan == SCAN_header) return 1; + } else { + // if paletted, then pal_n is our final components, and + // img_n is # components to decompress/filter. + s->img_n = 1; + if ((1 << 30) / s->img_x / 4 < s->img_y) return e("too large","Corrupt PNG"); + // if SCAN_header, have to scan to see if we have a tRNS + } + break; + } + + case PNG_TYPE('P','L','T','E'): { + if (c.length > 256*3) return e("invalid PLTE","Corrupt PNG"); + pal_len = c.length / 3; + if (pal_len * 3 != c.length) return e("invalid PLTE","Corrupt PNG"); + for (i=0; i < pal_len; ++i) { + palette[i*4+0] = get8u(s); + palette[i*4+1] = get8u(s); + palette[i*4+2] = get8u(s); + palette[i*4+3] = 255; + } + break; + } + + case PNG_TYPE('t','R','N','S'): { + if (z->idata) return e("tRNS after IDAT","Corrupt PNG"); + if (pal_img_n) { + if (scan == SCAN_header) { s->img_n = 4; return 1; } + if (pal_len == 0) return e("tRNS before PLTE","Corrupt PNG"); + if (c.length > pal_len) return e("bad tRNS len","Corrupt PNG"); + pal_img_n = 4; + for (i=0; i < c.length; ++i) + palette[i*4+3] = get8u(s); + } else { + if (!(s->img_n & 1)) return e("tRNS with alpha","Corrupt PNG"); + if (c.length != (uint32) s->img_n*2) return e("bad tRNS len","Corrupt PNG"); + has_trans = 1; + for (k=0; k < s->img_n; ++k) + tc[k] = (uint8) get16(s); // non 8-bit images will be larger + } + break; + } + + case PNG_TYPE('I','D','A','T'): { + if (pal_img_n && !pal_len) return e("no PLTE","Corrupt PNG"); + if (scan == SCAN_header) { s->img_n = pal_img_n; return 1; } + if (ioff + c.length > idata_limit) { + uint8 *p; + if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096; + while (ioff + c.length > idata_limit) + idata_limit *= 2; + p = (uint8 *) realloc(z->idata, idata_limit); if (p == NULL) return e("outofmem", "Out of memory"); + z->idata = p; + } + #ifndef STBI_NO_STDIO + if (s->img_file) + { + if (fread(z->idata+ioff,1,c.length,s->img_file) != c.length) return e("outofdata","Corrupt PNG"); + } + else + #endif + { + memcpy(z->idata+ioff, s->img_buffer, c.length); + s->img_buffer += c.length; + } + ioff += c.length; + break; + } + + case PNG_TYPE('I','E','N','D'): { + uint32 raw_len; + if (scan != SCAN_load) return 1; + if (z->idata == NULL) return e("no IDAT","Corrupt PNG"); + z->expanded = (uint8 *) stbi_zlib_decode_malloc((char *) z->idata, ioff, (int *) &raw_len); + if (z->expanded == NULL) return 0; // zlib should set error + free(z->idata); z->idata = NULL; + if ((req_comp == s->img_n+1 && req_comp != 3 && !pal_img_n) || has_trans) + s->img_out_n = s->img_n+1; + else + s->img_out_n = s->img_n; + if (!create_png_image(z, z->expanded, raw_len, s->img_out_n)) return 0; + if (has_trans) + if (!compute_transparency(z, tc, s->img_out_n)) return 0; + if (pal_img_n) { + // pal_img_n == 3 or 4 + s->img_n = pal_img_n; // record the actual colors we had + s->img_out_n = pal_img_n; + if (req_comp >= 3) s->img_out_n = req_comp; + if (!expand_palette(z, palette, pal_len, s->img_out_n)) + return 0; + } + free(z->expanded); z->expanded = NULL; + return 1; + } + + default: + // if critical, fail + if ((c.type & (1 << 29)) == 0) { + #ifndef STBI_NO_FAILURE_STRINGS + // not threadsafe + static char invalid_chunk[] = "XXXX chunk not known"; + invalid_chunk[0] = (uint8) (c.type >> 24); + invalid_chunk[1] = (uint8) (c.type >> 16); + invalid_chunk[2] = (uint8) (c.type >> 8); + invalid_chunk[3] = (uint8) (c.type >> 0); + #endif + return e(invalid_chunk, "PNG not supported: unknown chunk type"); + } + skip(s, c.length); + break; + } + // end of chunk, read and skip CRC + get32(s); + } +} + +static unsigned char *do_png(png *p, int *x, int *y, int *n, int req_comp) +{ + unsigned char *result=NULL; + p->expanded = NULL; + p->idata = NULL; + p->out = NULL; + if (req_comp < 0 || req_comp > 4) return epuc("bad req_comp", "Internal error"); + if (parse_png_file(p, SCAN_load, req_comp)) { + result = p->out; + p->out = NULL; + if (req_comp && req_comp != p->s.img_out_n) { + result = convert_format(result, p->s.img_out_n, req_comp, p->s.img_x, p->s.img_y); + p->s.img_out_n = req_comp; + if (result == NULL) return result; + } + *x = p->s.img_x; + *y = p->s.img_y; + if (n) *n = p->s.img_n; + } + free(p->out); p->out = NULL; + free(p->expanded); p->expanded = NULL; + free(p->idata); p->idata = NULL; + + return result; +} + +#ifndef STBI_NO_STDIO +unsigned char *stbi_png_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + png p; + start_file(&p.s, f); + return do_png(&p, x,y,comp,req_comp); +} + +unsigned char *stbi_png_load(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *data; + FILE *f = fopen(filename, "rb"); + if (!f) return NULL; + data = stbi_png_load_from_file(f,x,y,comp,req_comp); + fclose(f); + return data; +} +#endif + +unsigned char *stbi_png_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + png p; + start_mem(&p.s, buffer,len); + return do_png(&p, x,y,comp,req_comp); +} + +#ifndef STBI_NO_STDIO +int stbi_png_test_file(FILE *f) +{ + png p; + int n,r; + n = ftell(f); + start_file(&p.s, f); + r = parse_png_file(&p, SCAN_type,STBI_default); + fseek(f,n,SEEK_SET); + return r; +} +#endif + +int stbi_png_test_memory(stbi_uc const *buffer, int len) +{ + png p; + start_mem(&p.s, buffer, len); + return parse_png_file(&p, SCAN_type,STBI_default); +} + +// TODO: load header from png +#ifndef STBI_NO_STDIO +extern int stbi_png_info (char const *filename, int *x, int *y, int *comp); +extern int stbi_png_info_from_file (FILE *f, int *x, int *y, int *comp); +#endif +extern int stbi_png_info_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp); + +// Microsoft/Windows BMP image + +static int bmp_test(stbi *s) +{ + int sz; + if (get8(s) != 'B') return 0; + if (get8(s) != 'M') return 0; + get32le(s); // discard filesize + get16le(s); // discard reserved + get16le(s); // discard reserved + get32le(s); // discard data offset + sz = get32le(s); + if (sz == 12 || sz == 40 || sz == 56 || sz == 108) return 1; + return 0; +} + +#ifndef STBI_NO_STDIO +int stbi_bmp_test_file (FILE *f) +{ + stbi s; + int r,n = ftell(f); + start_file(&s,f); + r = bmp_test(&s); + fseek(f,n,SEEK_SET); + return r; +} +#endif + +int stbi_bmp_test_memory (stbi_uc const *buffer, int len) +{ + stbi s; + start_mem(&s, buffer, len); + return bmp_test(&s); +} + +// returns 0..31 for the highest set bit +static int high_bit(unsigned int z) +{ + int n=0; + if (z == 0) return -1; + if (z >= 0x10000) n += 16, z >>= 16; + if (z >= 0x00100) n += 8, z >>= 8; + if (z >= 0x00010) n += 4, z >>= 4; + if (z >= 0x00004) n += 2, z >>= 2; + if (z >= 0x00002) n += 1, z >>= 1; + return n; +} + +static int bitcount(unsigned int a) +{ + a = (a & 0x55555555) + ((a >> 1) & 0x55555555); // max 2 + a = (a & 0x33333333) + ((a >> 2) & 0x33333333); // max 4 + a = (a + (a >> 4)) & 0x0f0f0f0f; // max 8 per 4, now 8 bits + a = (a + (a >> 8)); // max 16 per 8 bits + a = (a + (a >> 16)); // max 32 per 8 bits + return a & 0xff; +} + +static int shiftsigned(int v, int shift, int bits) +{ + int result; + int z=0; + + if (shift < 0) v <<= -shift; + else v >>= shift; + result = v; + + z = bits; + while (z < 8) { + result += v >> z; + z += bits; + } + return result; +} + +static stbi_uc *bmp_load(stbi *s, int *x, int *y, int *comp, int req_comp) +{ + uint8 *out; + unsigned int mr=0,mg=0,mb=0,ma=0; + stbi_uc pal[256][4]; + int psize=0,i,j,compress=0,width; + int bpp, flip_vertically, pad, target, offset, hsz; + if (get8(s) != 'B' || get8(s) != 'M') return epuc("not BMP", "Corrupt BMP"); + get32le(s); // discard filesize + get16le(s); // discard reserved + get16le(s); // discard reserved + offset = get32le(s); + hsz = get32le(s); + if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108) return epuc("unknown BMP", "BMP type not supported: unknown"); + failure_reason = "bad BMP"; + if (hsz == 12) { + s->img_x = get16le(s); + s->img_y = get16le(s); + } else { + s->img_x = get32le(s); + s->img_y = get32le(s); + } + if (get16le(s) != 1) return 0; + bpp = get16le(s); + if (bpp == 1) return epuc("monochrome", "BMP type not supported: 1-bit"); + flip_vertically = ((int) s->img_y) > 0; + s->img_y = abs((int) s->img_y); + if (hsz == 12) { + if (bpp < 24) + psize = (offset - 14 - 24) / 3; + } else { + compress = get32le(s); + if (compress == 1 || compress == 2) return epuc("BMP RLE", "BMP type not supported: RLE"); + get32le(s); // discard sizeof + get32le(s); // discard hres + get32le(s); // discard vres + get32le(s); // discard colorsused + get32le(s); // discard max important + if (hsz == 40 || hsz == 56) { + if (hsz == 56) { + get32le(s); + get32le(s); + get32le(s); + get32le(s); + } + if (bpp == 16 || bpp == 32) { + mr = mg = mb = 0; + if (compress == 0) { + if (bpp == 32) { + mr = 0xff << 16; + mg = 0xff << 8; + mb = 0xff << 0; + } else { + mr = 31 << 10; + mg = 31 << 5; + mb = 31 << 0; + } + } else if (compress == 3) { + mr = get32le(s); + mg = get32le(s); + mb = get32le(s); + // not documented, but generated by photoshop and handled by mspaint + if (mr == mg && mg == mb) { + // ?!?!? + return NULL; + } + } else + return NULL; + } + } else { + assert(hsz == 108); + mr = get32le(s); + mg = get32le(s); + mb = get32le(s); + ma = get32le(s); + get32le(s); // discard color space + for (i=0; i < 12; ++i) + get32le(s); // discard color space parameters + } + if (bpp < 16) + psize = (offset - 14 - hsz) >> 2; + } + s->img_n = ma ? 4 : 3; + if (req_comp && req_comp >= 3) // we can directly decode 3 or 4 + target = req_comp; + else + target = s->img_n; // if they want monochrome, we'll post-convert + out = (stbi_uc *) malloc(target * s->img_x * s->img_y); + if (!out) return epuc("outofmem", "Out of memory"); + if (bpp < 16) { + int z=0; + if (psize == 0 || psize > 256) { free(out); return epuc("invalid", "Corrupt BMP"); } + for (i=0; i < psize; ++i) { + pal[i][2] = get8(s); + pal[i][1] = get8(s); + pal[i][0] = get8(s); + if (hsz != 12) get8(s); + pal[i][3] = 255; + } + skip(s, offset - 14 - hsz - psize * (hsz == 12 ? 3 : 4)); + if (bpp == 4) width = (s->img_x + 1) >> 1; + else if (bpp == 8) width = s->img_x; + else { free(out); return epuc("bad bpp", "Corrupt BMP"); } + pad = (-width)&3; + for (j=0; j < (int) s->img_y; ++j) { + for (i=0; i < (int) s->img_x; i += 2) { + int v=get8(s),v2=0; + if (bpp == 4) { + v2 = v & 15; + v >>= 4; + } + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + if (i+1 == (int) s->img_x) break; + v = (bpp == 8) ? get8(s) : v2; + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + } + skip(s, pad); + } + } else { + int rshift=0,gshift=0,bshift=0,ashift=0,rcount=0,gcount=0,bcount=0,acount=0; + int z = 0; + int easy=0; + skip(s, offset - 14 - hsz); + if (bpp == 24) width = 3 * s->img_x; + else if (bpp == 16) width = 2*s->img_x; + else /* bpp = 32 and pad = 0 */ width=0; + pad = (-width) & 3; + if (bpp == 24) { + easy = 1; + } else if (bpp == 32) { + if (mb == 0xff && mg == 0xff00 && mr == 0xff000000 && ma == 0xff000000) + easy = 2; + } + if (!easy) { + if (!mr || !mg || !mb) return epuc("bad masks", "Corrupt BMP"); + // right shift amt to put high bit in position #7 + rshift = high_bit(mr)-7; rcount = bitcount(mr); + gshift = high_bit(mg)-7; gcount = bitcount(mr); + bshift = high_bit(mb)-7; bcount = bitcount(mr); + ashift = high_bit(ma)-7; acount = bitcount(mr); + } + for (j=0; j < (int) s->img_y; ++j) { + if (easy) { + for (i=0; i < (int) s->img_x; ++i) { + int a; + out[z+2] = get8(s); + out[z+1] = get8(s); + out[z+0] = get8(s); + z += 3; + a = (easy == 2 ? get8(s) : 255); + if (target == 4) out[z++] = a; + } + } else { + for (i=0; i < (int) s->img_x; ++i) { + uint32 v = (bpp == 16 ? get16le(s) : get32le(s)); + int a; + out[z++] = shiftsigned(v & mr, rshift, rcount); + out[z++] = shiftsigned(v & mg, gshift, gcount); + out[z++] = shiftsigned(v & mb, bshift, bcount); + a = (ma ? shiftsigned(v & ma, ashift, acount) : 255); + if (target == 4) out[z++] = a; + } + } + skip(s, pad); + } + } + if (flip_vertically) { + stbi_uc t; + for (j=0; j < (int) s->img_y>>1; ++j) { + stbi_uc *p1 = out + j *s->img_x*target; + stbi_uc *p2 = out + (s->img_y-1-j)*s->img_x*target; + for (i=0; i < (int) s->img_x*target; ++i) { + t = p1[i], p1[i] = p2[i], p2[i] = t; + } + } + } + + if (req_comp && req_comp != target) { + out = convert_format(out, target, req_comp, s->img_x, s->img_y); + if (out == NULL) return out; // convert_format frees input on failure + } + + *x = s->img_x; + *y = s->img_y; + if (comp) *comp = target; + return out; +} + +#ifndef STBI_NO_STDIO +stbi_uc *stbi_bmp_load (char const *filename, int *x, int *y, int *comp, int req_comp) +{ + stbi_uc *data; + FILE *f = fopen(filename, "rb"); + if (!f) return NULL; + data = stbi_bmp_load_from_file(f, x,y,comp,req_comp); + fclose(f); + return data; +} + +stbi_uc *stbi_bmp_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi s; + start_file(&s, f); + return bmp_load(&s, x,y,comp,req_comp); +} +#endif + +stbi_uc *stbi_bmp_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi s; + start_mem(&s, buffer, len); + return bmp_load(&s, x,y,comp,req_comp); +} + +// Targa Truevision - TGA +// by Jonathan Dummer + +static int tga_test(stbi *s) +{ + int sz; + get8u(s); // discard Offset + sz = get8u(s); // color type + if( sz > 1 ) return 0; // only RGB or indexed allowed + sz = get8u(s); // image type + if( (sz != 1) && (sz != 2) && (sz != 3) && (sz != 9) && (sz != 10) && (sz != 11) ) return 0; // only RGB or grey allowed, +/- RLE + get16(s); // discard palette start + get16(s); // discard palette length + get8(s); // discard bits per palette color entry + get16(s); // discard x origin + get16(s); // discard y origin + if( get16(s) < 1 ) return 0; // test width + if( get16(s) < 1 ) return 0; // test height + sz = get8(s); // bits per pixel + if( (sz != 8) && (sz != 16) && (sz != 24) && (sz != 32) ) return 0; // only RGB or RGBA or grey allowed + return 1; // seems to have passed everything +} + +#ifndef STBI_NO_STDIO +int stbi_tga_test_file (FILE *f) +{ + stbi s; + int r,n = ftell(f); + start_file(&s, f); + r = tga_test(&s); + fseek(f,n,SEEK_SET); + return r; +} +#endif + +int stbi_tga_test_memory (stbi_uc const *buffer, int len) +{ + stbi s; + start_mem(&s, buffer, len); + return tga_test(&s); +} + +static stbi_uc *tga_load(stbi *s, int *x, int *y, int *comp, int req_comp) +{ + // read in the TGA header stuff + int tga_offset = get8u(s); + int tga_indexed = get8u(s); + int tga_image_type = get8u(s); + int tga_is_RLE = 0; + int tga_palette_start = get16le(s); + int tga_palette_len = get16le(s); + int tga_palette_bits = get8u(s); + int tga_x_origin = get16le(s); + int tga_y_origin = get16le(s); + int tga_width = get16le(s); + int tga_height = get16le(s); + int tga_bits_per_pixel = get8u(s); + int tga_inverted = get8u(s); + // image data + unsigned char *tga_data; + unsigned char *tga_palette = NULL; + int i, j; + unsigned char raw_data[4]; + unsigned char trans_data[] = { 0,0,0,0 }; + int RLE_count = 0; + int RLE_repeating = 0; + int read_next_pixel = 1; + // do a tiny bit of precessing + if( tga_image_type >= 8 ) + { + tga_image_type -= 8; + tga_is_RLE = 1; + } + /* int tga_alpha_bits = tga_inverted & 15; */ + tga_inverted = 1 - ((tga_inverted >> 5) & 1); + + // error check + if( //(tga_indexed) || + (tga_width < 1) || (tga_height < 1) || + (tga_image_type < 1) || (tga_image_type > 3) || + ((tga_bits_per_pixel != 8) && (tga_bits_per_pixel != 16) && + (tga_bits_per_pixel != 24) && (tga_bits_per_pixel != 32)) + ) + { + return NULL; + } + + // If I'm paletted, then I'll use the number of bits from the palette + if( tga_indexed ) + { + tga_bits_per_pixel = tga_palette_bits; + } + + // tga info + *x = tga_width; + *y = tga_height; + if( (req_comp < 1) || (req_comp > 4) ) + { + // just use whatever the file was + req_comp = tga_bits_per_pixel / 8; + *comp = req_comp; + } else + { + // force a new number of components + *comp = tga_bits_per_pixel/8; + } + tga_data = (unsigned char*)malloc( tga_width * tga_height * req_comp ); + + // skip to the data's starting position (offset usually = 0) + skip(s, tga_offset ); + // do I need to load a palette? + if( tga_indexed ) + { + // any data to skip? (offset usually = 0) + skip(s, tga_palette_start ); + // load the palette + tga_palette = (unsigned char*)malloc( tga_palette_len * tga_palette_bits / 8 ); + getn(s, tga_palette, tga_palette_len * tga_palette_bits / 8 ); + } + // load the data + for( i = 0; i < tga_width * tga_height; ++i ) + { + // if I'm in RLE mode, do I need to get a RLE chunk? + if( tga_is_RLE ) + { + if( RLE_count == 0 ) + { + // yep, get the next byte as a RLE command + int RLE_cmd = get8u(s); + RLE_count = 1 + (RLE_cmd & 127); + RLE_repeating = RLE_cmd >> 7; + read_next_pixel = 1; + } else if( !RLE_repeating ) + { + read_next_pixel = 1; + } + } else + { + read_next_pixel = 1; + } + // OK, if I need to read a pixel, do it now + if( read_next_pixel ) + { + // load however much data we did have + if( tga_indexed ) + { + // read in 1 byte, then perform the lookup + int pal_idx = get8u(s); + if( pal_idx >= tga_palette_len ) + { + // invalid index + pal_idx = 0; + } + pal_idx *= tga_bits_per_pixel / 8; + for( j = 0; j*8 < tga_bits_per_pixel; ++j ) + { + raw_data[j] = tga_palette[pal_idx+j]; + } + } else + { + // read in the data raw + for( j = 0; j*8 < tga_bits_per_pixel; ++j ) + { + raw_data[j] = get8u(s); + } + } + // convert raw to the intermediate format + switch( tga_bits_per_pixel ) + { + case 8: + // Luminous => RGBA + trans_data[0] = raw_data[0]; + trans_data[1] = raw_data[0]; + trans_data[2] = raw_data[0]; + trans_data[3] = 255; + break; + case 16: + // Luminous,Alpha => RGBA + trans_data[0] = raw_data[0]; + trans_data[1] = raw_data[0]; + trans_data[2] = raw_data[0]; + trans_data[3] = raw_data[1]; + break; + case 24: + // BGR => RGBA + trans_data[0] = raw_data[2]; + trans_data[1] = raw_data[1]; + trans_data[2] = raw_data[0]; + trans_data[3] = 255; + break; + case 32: + // BGRA => RGBA + trans_data[0] = raw_data[2]; + trans_data[1] = raw_data[1]; + trans_data[2] = raw_data[0]; + trans_data[3] = raw_data[3]; + break; + } + // clear the reading flag for the next pixel + read_next_pixel = 0; + } // end of reading a pixel + // convert to final format + switch( req_comp ) + { + case 1: + // RGBA => Luminance + tga_data[i*req_comp+0] = compute_y(trans_data[0],trans_data[1],trans_data[2]); + break; + case 2: + // RGBA => Luminance,Alpha + tga_data[i*req_comp+0] = compute_y(trans_data[0],trans_data[1],trans_data[2]); + tga_data[i*req_comp+1] = trans_data[3]; + break; + case 3: + // RGBA => RGB + tga_data[i*req_comp+0] = trans_data[0]; + tga_data[i*req_comp+1] = trans_data[1]; + tga_data[i*req_comp+2] = trans_data[2]; + break; + case 4: + // RGBA => RGBA + tga_data[i*req_comp+0] = trans_data[0]; + tga_data[i*req_comp+1] = trans_data[1]; + tga_data[i*req_comp+2] = trans_data[2]; + tga_data[i*req_comp+3] = trans_data[3]; + break; + } + // in case we're in RLE mode, keep counting down + --RLE_count; + } + // do I need to invert the image? + if( tga_inverted ) + { + for( j = 0; j*2 < tga_height; ++j ) + { + int index1 = j * tga_width * req_comp; + int index2 = (tga_height - 1 - j) * tga_width * req_comp; + for( i = tga_width * req_comp; i > 0; --i ) + { + unsigned char temp = tga_data[index1]; + tga_data[index1] = tga_data[index2]; + tga_data[index2] = temp; + ++index1; + ++index2; + } + } + } + // clear my palette, if I had one + if( tga_palette != NULL ) + { + free( tga_palette ); + } + // the things I do to get rid of an error message, and yet keep + // Microsoft's C compilers happy... [8^( + tga_palette_start = tga_palette_len = tga_palette_bits = + tga_x_origin = tga_y_origin = 0; + // OK, done + return tga_data; +} + +#ifndef STBI_NO_STDIO +stbi_uc *stbi_tga_load (char const *filename, int *x, int *y, int *comp, int req_comp) +{ + stbi_uc *data; + FILE *f = fopen(filename, "rb"); + if (!f) return NULL; + data = stbi_tga_load_from_file(f, x,y,comp,req_comp); + fclose(f); + return data; +} + +stbi_uc *stbi_tga_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi s; + start_file(&s, f); + return tga_load(&s, x,y,comp,req_comp); +} +#endif + +stbi_uc *stbi_tga_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi s; + start_mem(&s, buffer, len); + return tga_load(&s, x,y,comp,req_comp); +} + + +// ************************************************************************************************* +// Photoshop PSD loader -- PD by Thatcher Ulrich, integration by Nicholas Schulz, tweaked by STB + +static int psd_test(stbi *s) +{ + if (get32(s) != 0x38425053) return 0; // "8BPS" + else return 1; +} + +#ifndef STBI_NO_STDIO +int stbi_psd_test_file(FILE *f) +{ + stbi s; + int r,n = ftell(f); + start_file(&s, f); + r = psd_test(&s); + fseek(f,n,SEEK_SET); + return r; +} +#endif + +int stbi_psd_test_memory(stbi_uc const *buffer, int len) +{ + stbi s; + start_mem(&s, buffer, len); + return psd_test(&s); +} + +static stbi_uc *psd_load(stbi *s, int *x, int *y, int *comp, int req_comp) +{ + int pixelCount; + int channelCount, compression; + int channel, i, count, len; + int w,h; + uint8 *out; + + // Check identifier + if (get32(s) != 0x38425053) // "8BPS" + return epuc("not PSD", "Corrupt PSD image"); + + // Check file type version. + if (get16(s) != 1) + return epuc("wrong version", "Unsupported version of PSD image"); + + // Skip 6 reserved bytes. + skip(s, 6 ); + + // Read the number of channels (R, G, B, A, etc). + channelCount = get16(s); + if (channelCount < 0 || channelCount > 16) + return epuc("wrong channel count", "Unsupported number of channels in PSD image"); + + // Read the rows and columns of the image. + h = get32(s); + w = get32(s); + + // Make sure the depth is 8 bits. + if (get16(s) != 8) + return epuc("unsupported bit depth", "PSD bit depth is not 8 bit"); + + // Make sure the color mode is RGB. + // Valid options are: + // 0: Bitmap + // 1: Grayscale + // 2: Indexed color + // 3: RGB color + // 4: CMYK color + // 7: Multichannel + // 8: Duotone + // 9: Lab color + if (get16(s) != 3) + return epuc("wrong color format", "PSD is not in RGB color format"); + + // Skip the Mode Data. (It's the palette for indexed color; other info for other modes.) + skip(s,get32(s) ); + + // Skip the image resources. (resolution, pen tool paths, etc) + skip(s, get32(s) ); + + // Skip the reserved data. + skip(s, get32(s) ); + + // Find out if the data is compressed. + // Known values: + // 0: no compression + // 1: RLE compressed + compression = get16(s); + if (compression > 1) + return epuc("bad compression", "PSD has an unknown compression format"); + + // Create the destination image. + out = (stbi_uc *) malloc(4 * w*h); + if (!out) return epuc("outofmem", "Out of memory"); + pixelCount = w*h; + + // Initialize the data to zero. + //memset( out, 0, pixelCount * 4 ); + + // Finally, the image data. + if (compression) { + // RLE as used by .PSD and .TIFF + // Loop until you get the number of unpacked bytes you are expecting: + // Read the next source byte into n. + // If n is between 0 and 127 inclusive, copy the next n+1 bytes literally. + // Else if n is between -127 and -1 inclusive, copy the next byte -n+1 times. + // Else if n is 128, noop. + // Endloop + + // The RLE-compressed data is preceeded by a 2-byte data count for each row in the data, + // which we're going to just skip. + skip(s, h * channelCount * 2 ); + + // Read the RLE data by channel. + for (channel = 0; channel < 4; channel++) { + uint8 *p; + + p = out+channel; + if (channel >= channelCount) { + // Fill this channel with default data. + for (i = 0; i < pixelCount; i++) *p = (channel == 3 ? 255 : 0), p += 4; + } else { + // Read the RLE data. + count = 0; + while (count < pixelCount) { + len = get8(s); + if (len == 128) { + // No-op. + } else if (len < 128) { + // Copy next len+1 bytes literally. + len++; + count += len; + while (len) { + *p = get8(s); + p += 4; + len--; + } + } else if (len > 128) { + uint32 val; + // Next -len+1 bytes in the dest are replicated from next source byte. + // (Interpret len as a negative 8-bit int.) + len ^= 0x0FF; + len += 2; + val = get8(s); + count += len; + while (len) { + *p = val; + p += 4; + len--; + } + } + } + } + } + + } else { + // We're at the raw image data. It's each channel in order (Red, Green, Blue, Alpha, ...) + // where each channel consists of an 8-bit value for each pixel in the image. + + // Read the data by channel. + for (channel = 0; channel < 4; channel++) { + uint8 *p; + + p = out + channel; + if (channel > channelCount) { + // Fill this channel with default data. + for (i = 0; i < pixelCount; i++) *p = channel == 3 ? 255 : 0, p += 4; + } else { + // Read the data. + count = 0; + for (i = 0; i < pixelCount; i++) + *p = get8(s), p += 4; + } + } + } + + if (req_comp && req_comp != 4) { + out = convert_format(out, 4, req_comp, w, h); + if (out == NULL) return out; // convert_format frees input on failure + } + + if (comp) *comp = channelCount; + *y = h; + *x = w; + + return out; +} + +#ifndef STBI_NO_STDIO +stbi_uc *stbi_psd_load(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + stbi_uc *data; + FILE *f = fopen(filename, "rb"); + if (!f) return NULL; + data = stbi_psd_load_from_file(f, x,y,comp,req_comp); + fclose(f); + return data; +} + +stbi_uc *stbi_psd_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi s; + start_file(&s, f); + return psd_load(&s, x,y,comp,req_comp); +} +#endif + +stbi_uc *stbi_psd_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi s; + start_mem(&s, buffer, len); + return psd_load(&s, x,y,comp,req_comp); +} + + +// ************************************************************************************************* +// Radiance RGBE HDR loader +// originally by Nicolas Schulz +#ifndef STBI_NO_HDR +static int hdr_test(stbi *s) +{ + char *signature = "#?RADIANCE\n"; + int i; + for (i=0; signature[i]; ++i) + if (get8(s) != signature[i]) + return 0; + return 1; +} + +int stbi_hdr_test_memory(stbi_uc const *buffer, int len) +{ + stbi s; + start_mem(&s, buffer, len); + return hdr_test(&s); +} + +#ifndef STBI_NO_STDIO +int stbi_hdr_test_file(FILE *f) +{ + stbi s; + int r,n = ftell(f); + start_file(&s, f); + r = hdr_test(&s); + fseek(f,n,SEEK_SET); + return r; +} +#endif + +#define HDR_BUFLEN 1024 +static char *hdr_gettoken(stbi *z, char *buffer) +{ + int len=0; + //char *s = buffer, + char c = '\0'; + + c = get8(z); + + while (!at_eof(z) && c != '\n') { + buffer[len++] = c; + if (len == HDR_BUFLEN-1) { + // flush to end of line + while (!at_eof(z) && get8(z) != '\n') + ; + break; + } + c = get8(z); + } + + buffer[len] = 0; + return buffer; +} + +static void hdr_convert(float *output, stbi_uc *input, int req_comp) +{ + if( input[3] != 0 ) { + float f1; + // Exponent + f1 = (float) ldexp(1.0f, input[3] - (int)(128 + 8)); + if (req_comp <= 2) + output[0] = (input[0] + input[1] + input[2]) * f1 / 3; + else { + output[0] = input[0] * f1; + output[1] = input[1] * f1; + output[2] = input[2] * f1; + } + if (req_comp == 2) output[1] = 1; + if (req_comp == 4) output[3] = 1; + } else { + switch (req_comp) { + case 4: output[3] = 1; /* fallthrough */ + case 3: output[0] = output[1] = output[2] = 0; + break; + case 2: output[1] = 1; /* fallthrough */ + case 1: output[0] = 0; + break; + } + } +} + + +static float *hdr_load(stbi *s, int *x, int *y, int *comp, int req_comp) +{ + char buffer[HDR_BUFLEN]; + char *token; + int valid = 0; + int width, height; + stbi_uc *scanline; + float *hdr_data; + int len; + unsigned char count, value; + int i, j, k, c1,c2, z; + + + // Check identifier + if (strcmp(hdr_gettoken(s,buffer), "#?RADIANCE") != 0) + return epf("not HDR", "Corrupt HDR image"); + + // Parse header + while(1) { + token = hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) return epf("unsupported format", "Unsupported HDR format"); + + // Parse width and height + // can't use sscanf() if we're not using stdio! + token = hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) return epf("unsupported data layout", "Unsupported HDR format"); + token += 3; + height = strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) return epf("unsupported data layout", "Unsupported HDR format"); + token += 3; + width = strtol(token, NULL, 10); + + *x = width; + *y = height; + + *comp = 3; + if (req_comp == 0) req_comp = 3; + + // Read data + hdr_data = (float *) malloc(height * width * req_comp * sizeof(float)); + + // Load image data + // image data is stored as some number of sca + if( width < 8 || width >= 32768) { + // Read flat data + for (j=0; j < height; ++j) { + for (i=0; i < width; ++i) { + stbi_uc rgbe[4]; + main_decode_loop: + getn(s, rgbe, 4); + hdr_convert(hdr_data + j * width * req_comp + i * req_comp, rgbe, req_comp); + } + } + } else { + // Read RLE-encoded data + scanline = NULL; + + for (j = 0; j < height; ++j) { + c1 = get8(s); + c2 = get8(s); + len = get8(s); + if (c1 != 2 || c2 != 2 || (len & 0x80)) { + // not run-length encoded, so we have to actually use THIS data as a decoded + // pixel (note this can't be a valid pixel--one of RGB must be >= 128) + stbi_uc rgbe[4] = { c1,c2,len, get8(s) }; + hdr_convert(hdr_data, rgbe, req_comp); + i = 1; + j = 0; + free(scanline); + goto main_decode_loop; // yes, this is fucking insane; blame the fucking insane format + } + len <<= 8; + len |= get8(s); + if (len != width) { free(hdr_data); free(scanline); return epf("invalid decoded scanline length", "corrupt HDR"); } + if (scanline == NULL) scanline = (stbi_uc *) malloc(width * 4); + + for (k = 0; k < 4; ++k) { + i = 0; + while (i < width) { + count = get8(s); + if (count > 128) { + // Run + value = get8(s); + count -= 128; + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = value; + } else { + // Dump + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = get8(s); + } + } + } + for (i=0; i < width; ++i) + hdr_convert(hdr_data+(j*width + i)*req_comp, scanline + i*4, req_comp); + } + free(scanline); + } + + return hdr_data; +} + +static stbi_uc *hdr_load_rgbe(stbi *s, int *x, int *y, int *comp, int req_comp) +{ + char buffer[HDR_BUFLEN]; + char *token; + int valid = 0; + int width, height; + stbi_uc *scanline; + stbi_uc *rgbe_data; + int len; + unsigned char count, value; + int i, j, k, c1,c2, z; + + + // Check identifier + if (strcmp(hdr_gettoken(s,buffer), "#?RADIANCE") != 0) + return epuc("not HDR", "Corrupt HDR image"); + + // Parse header + while(1) { + token = hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) return epuc("unsupported format", "Unsupported HDR format"); + + // Parse width and height + // can't use sscanf() if we're not using stdio! + token = hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) return epuc("unsupported data layout", "Unsupported HDR format"); + token += 3; + height = strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) return epuc("unsupported data layout", "Unsupported HDR format"); + token += 3; + width = strtol(token, NULL, 10); + + *x = width; + *y = height; + + // RGBE _MUST_ come out as 4 components + *comp = 4; + req_comp = 4; + + // Read data + rgbe_data = (stbi_uc *) malloc(height * width * req_comp * sizeof(stbi_uc)); + // point to the beginning + scanline = rgbe_data; + + // Load image data + // image data is stored as some number of scan lines + if( width < 8 || width >= 32768) { + // Read flat data + for (j=0; j < height; ++j) { + for (i=0; i < width; ++i) { + main_decode_loop: + //getn(rgbe, 4); + getn(s,scanline, 4); + scanline += 4; + } + } + } else { + // Read RLE-encoded data + for (j = 0; j < height; ++j) { + c1 = get8(s); + c2 = get8(s); + len = get8(s); + if (c1 != 2 || c2 != 2 || (len & 0x80)) { + // not run-length encoded, so we have to actually use THIS data as a decoded + // pixel (note this can't be a valid pixel--one of RGB must be >= 128) + scanline[0] = c1; + scanline[1] = c2; + scanline[2] = len; + scanline[3] = get8(s); + scanline += 4; + i = 1; + j = 0; + goto main_decode_loop; // yes, this is insane; blame the insane format + } + len <<= 8; + len |= get8(s); + if (len != width) { free(rgbe_data); return epuc("invalid decoded scanline length", "corrupt HDR"); } + for (k = 0; k < 4; ++k) { + i = 0; + while (i < width) { + count = get8(s); + if (count > 128) { + // Run + value = get8(s); + count -= 128; + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = value; + } else { + // Dump + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = get8(s); + } + } + } + // move the scanline on + scanline += 4 * width; + } + } + + return rgbe_data; +} + +#ifndef STBI_NO_STDIO +float *stbi_hdr_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi s; + start_file(&s,f); + return hdr_load(&s,x,y,comp,req_comp); +} + +stbi_uc *stbi_hdr_load_rgbe_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi s; + start_file(&s,f); + return hdr_load_rgbe(&s,x,y,comp,req_comp); +} + +stbi_uc *stbi_hdr_load_rgbe (char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = fopen(filename, "rb"); + unsigned char *result; + if (!f) return epuc("can't fopen", "Unable to open file"); + result = stbi_hdr_load_rgbe_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} +#endif + +float *stbi_hdr_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi s; + start_mem(&s,buffer, len); + return hdr_load(&s,x,y,comp,req_comp); +} + +stbi_uc *stbi_hdr_load_rgbe_memory(stbi_uc *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi s; + start_mem(&s,buffer, len); + return hdr_load_rgbe(&s,x,y,comp,req_comp); +} + +#endif // STBI_NO_HDR + +/////////////////////// write image /////////////////////// + +#ifndef STBI_NO_WRITE + +static void write8(FILE *f, int x) { uint8 z = (uint8) x; fwrite(&z,1,1,f); } + +static void writefv(FILE *f, char *fmt, va_list v) +{ + while (*fmt) { + switch (*fmt++) { + case ' ': break; + case '1': { uint8 x = va_arg(v, int); write8(f,x); break; } + case '2': { int16 x = va_arg(v, int); write8(f,x); write8(f,x>>8); break; } + case '4': { int32 x = va_arg(v, int); write8(f,x); write8(f,x>>8); write8(f,x>>16); write8(f,x>>24); break; } + default: + assert(0); + va_end(v); + return; + } + } +} + +static void writef(FILE *f, char *fmt, ...) +{ + va_list v; + va_start(v, fmt); + writefv(f,fmt,v); + va_end(v); +} + +static void write_pixels(FILE *f, int rgb_dir, int vdir, int x, int y, int comp, void *data, int write_alpha, int scanline_pad) +{ + uint8 bg[3] = { 255, 0, 255}, px[3]; + uint32 zero = 0; + int i,j,k, j_end; + + if (vdir < 0) + j_end = -1, j = y-1; + else + j_end = y, j = 0; + + for (; j != j_end; j += vdir) { + for (i=0; i < x; ++i) { + uint8 *d = (uint8 *) data + (j*x+i)*comp; + if (write_alpha < 0) + fwrite(&d[comp-1], 1, 1, f); + switch (comp) { + case 1: + case 2: writef(f, "111", d[0],d[0],d[0]); + break; + case 4: + if (!write_alpha) { + for (k=0; k < 3; ++k) + px[k] = bg[k] + ((d[k] - bg[k]) * d[3])/255; + writef(f, "111", px[1-rgb_dir],px[1],px[1+rgb_dir]); + break; + } + /* FALLTHROUGH */ + case 3: + writef(f, "111", d[1-rgb_dir],d[1],d[1+rgb_dir]); + break; + } + if (write_alpha > 0) + fwrite(&d[comp-1], 1, 1, f); + } + fwrite(&zero,scanline_pad,1,f); + } +} + +static int outfile(char const *filename, int rgb_dir, int vdir, int x, int y, int comp, void *data, int alpha, int pad, char *fmt, ...) +{ + FILE *f = fopen(filename, "wb"); + if (f) { + va_list v; + va_start(v, fmt); + writefv(f, fmt, v); + va_end(v); + write_pixels(f,rgb_dir,vdir,x,y,comp,data,alpha,pad); + fclose(f); + } + return f != NULL; +} + +int stbi_write_bmp(char const *filename, int x, int y, int comp, void *data) +{ + int pad = (-x*3) & 3; + return outfile(filename,-1,-1,x,y,comp,data,0,pad, + "11 4 22 4" "4 44 22 444444", + 'B', 'M', 14+40+(x*3+pad)*y, 0,0, 14+40, // file header + 40, x,y, 1,24, 0,0,0,0,0,0); // bitmap header +} + +int stbi_write_tga(char const *filename, int x, int y, int comp, void *data) +{ + int has_alpha = !(comp & 1); + return outfile(filename, -1,-1, x, y, comp, data, has_alpha, 0, + "111 221 2222 11", 0,0,2, 0,0,0, 0,0,x,y, 24+8*has_alpha, 8*has_alpha); +} + +// any other image formats that do interleaved rgb data? +// PNG: requires adler32,crc32 -- significant amount of code +// PSD: no, channels output separately +// TIFF: no, stripwise-interleaved... i think + +#endif // STBI_NO_WRITE + +// add in my DDS loading support +#ifndef STBI_NO_DDS +#include "stbi_DDS_aug_c.h" +#endif + +#ifdef _MSC_VER +#pragma warning(pop) +#else +#pragma GCC diagnostic pop +#endif diff --git a/lib/soil/stb_image_aug.h b/lib/soil/stb_image_aug.h new file mode 100644 index 000000000..e59f2eb83 --- /dev/null +++ b/lib/soil/stb_image_aug.h @@ -0,0 +1,354 @@ +/* stbi-1.16 - public domain JPEG/PNG reader - http://nothings.org/stb_image.c + when you control the images you're loading + + QUICK NOTES: + Primarily of interest to game developers and other people who can + avoid problematic images and only need the trivial interface + + JPEG baseline (no JPEG progressive, no oddball channel decimations) + PNG non-interlaced + BMP non-1bpp, non-RLE + TGA (not sure what subset, if a subset) + PSD (composited view only, no extra channels) + HDR (radiance rgbE format) + writes BMP,TGA (define STBI_NO_WRITE to remove code) + decoded from memory or through stdio FILE (define STBI_NO_STDIO to remove code) + supports installable dequantizing-IDCT, YCbCr-to-RGB conversion (define STBI_SIMD) + + TODO: + stbi_info_* + + history: + 1.16 major bugfix - convert_format converted one too many pixels + 1.15 initialize some fields for thread safety + 1.14 fix threadsafe conversion bug; header-file-only version (#define STBI_HEADER_FILE_ONLY before including) + 1.13 threadsafe + 1.12 const qualifiers in the API + 1.11 Support installable IDCT, colorspace conversion routines + 1.10 Fixes for 64-bit (don't use "unsigned long") + optimized upsampling by Fabian "ryg" Giesen + 1.09 Fix format-conversion for PSD code (bad global variables!) + 1.08 Thatcher Ulrich's PSD code integrated by Nicolas Schulz + 1.07 attempt to fix C++ warning/errors again + 1.06 attempt to fix C++ warning/errors again + 1.05 fix TGA loading to return correct *comp and use good luminance calc + 1.04 default float alpha is 1, not 255; use 'void *' for stbi_image_free + 1.03 bugfixes to STBI_NO_STDIO, STBI_NO_HDR + 1.02 support for (subset of) HDR files, float interface for preferred access to them + 1.01 fix bug: possible bug in handling right-side up bmps... not sure + fix bug: the stbi_bmp_load() and stbi_tga_load() functions didn't work at all + 1.00 interface to zlib that skips zlib header + 0.99 correct handling of alpha in palette + 0.98 TGA loader by lonesock; dynamically add loaders (untested) + 0.97 jpeg errors on too large a file; also catch another malloc failure + 0.96 fix detection of invalid v value - particleman@mollyrocket forum + 0.95 during header scan, seek to markers in case of padding + 0.94 STBI_NO_STDIO to disable stdio usage; rename all #defines the same + 0.93 handle jpegtran output; verbose errors + 0.92 read 4,8,16,24,32-bit BMP files of several formats + 0.91 output 24-bit Windows 3.0 BMP files + 0.90 fix a few more warnings; bump version number to approach 1.0 + 0.61 bugfixes due to Marc LeBlanc, Christopher Lloyd + 0.60 fix compiling as c++ + 0.59 fix warnings: merge Dave Moore's -Wall fixes + 0.58 fix bug: zlib uncompressed mode len/nlen was wrong endian + 0.57 fix bug: jpg last huffman symbol before marker was >9 bits but less + than 16 available + 0.56 fix bug: zlib uncompressed mode len vs. nlen + 0.55 fix bug: restart_interval not initialized to 0 + 0.54 allow NULL for 'int *comp' + 0.53 fix bug in png 3->4; speedup png decoding + 0.52 png handles req_comp=3,4 directly; minor cleanup; jpeg comments + 0.51 obey req_comp requests, 1-component jpegs return as 1-component, + on 'test' only check type, not whether we support this variant +*/ + +#ifndef HEADER_STB_IMAGE_AUGMENTED +#define HEADER_STB_IMAGE_AUGMENTED + +//// begin header file //////////////////////////////////////////////////// +// +// Limitations: +// - no progressive/interlaced support (jpeg, png) +// - 8-bit samples only (jpeg, png) +// - not threadsafe +// - channel subsampling of at most 2 in each dimension (jpeg) +// - no delayed line count (jpeg) -- IJG doesn't support either +// +// Basic usage (see HDR discussion below): +// int x,y,n; +// unsigned char *data = stbi_load(filename, &x, &y, &n, 0); +// // ... process data if not NULL ... +// // ... x = width, y = height, n = # 8-bit components per pixel ... +// // ... replace '0' with '1'..'4' to force that many components per pixel +// stbi_image_free(data) +// +// Standard parameters: +// int *x -- outputs image width in pixels +// int *y -- outputs image height in pixels +// int *comp -- outputs # of image components in image file +// int req_comp -- if non-zero, # of image components requested in result +// +// The return value from an image loader is an 'unsigned char *' which points +// to the pixel data. The pixel data consists of *y scanlines of *x pixels, +// with each pixel consisting of N interleaved 8-bit components; the first +// pixel pointed to is top-left-most in the image. There is no padding between +// image scanlines or between pixels, regardless of format. The number of +// components N is 'req_comp' if req_comp is non-zero, or *comp otherwise. +// If req_comp is non-zero, *comp has the number of components that _would_ +// have been output otherwise. E.g. if you set req_comp to 4, you will always +// get RGBA output, but you can check *comp to easily see if it's opaque. +// +// An output image with N components has the following components interleaved +// in this order in each pixel: +// +// N=#comp components +// 1 grey +// 2 grey, alpha +// 3 red, green, blue +// 4 red, green, blue, alpha +// +// If image loading fails for any reason, the return value will be NULL, +// and *x, *y, *comp will be unchanged. The function stbi_failure_reason() +// can be queried for an extremely brief, end-user unfriendly explanation +// of why the load failed. Define STBI_NO_FAILURE_STRINGS to avoid +// compiling these strings at all, and STBI_FAILURE_USERMSG to get slightly +// more user-friendly ones. +// +// Paletted PNG and BMP images are automatically depalettized. +// +// +// =========================================================================== +// +// HDR image support (disable by defining STBI_NO_HDR) +// +// stb_image now supports loading HDR images in general, and currently +// the Radiance .HDR file format, although the support is provided +// generically. You can still load any file through the existing interface; +// if you attempt to load an HDR file, it will be automatically remapped to +// LDR, assuming gamma 2.2 and an arbitrary scale factor defaulting to 1; +// both of these constants can be reconfigured through this interface: +// +// stbi_hdr_to_ldr_gamma(2.2f); +// stbi_hdr_to_ldr_scale(1.0f); +// +// (note, do not use _inverse_ constants; stbi_image will invert them +// appropriately). +// +// Additionally, there is a new, parallel interface for loading files as +// (linear) floats to preserve the full dynamic range: +// +// float *data = stbi_loadf(filename, &x, &y, &n, 0); +// +// If you load LDR images through this interface, those images will +// be promoted to floating point values, run through the inverse of +// constants corresponding to the above: +// +// stbi_ldr_to_hdr_scale(1.0f); +// stbi_ldr_to_hdr_gamma(2.2f); +// +// Finally, given a filename (or an open file or memory block--see header +// file for details) containing image data, you can query for the "most +// appropriate" interface to use (that is, whether the image is HDR or +// not), using: +// +// stbi_is_hdr(char *filename); + +#ifndef STBI_NO_STDIO +#include +#endif + +#define STBI_VERSION 1 + +enum +{ + STBI_default = 0, // only used for req_comp + + STBI_grey = 1, + STBI_grey_alpha = 2, + STBI_rgb = 3, + STBI_rgb_alpha = 4, +}; + +typedef unsigned char stbi_uc; + +#ifdef __cplusplus +extern "C" { +#endif + +// WRITING API + +#if !defined(STBI_NO_WRITE) && !defined(STBI_NO_STDIO) +// write a BMP/TGA file given tightly packed 'comp' channels (no padding, nor bmp-stride-padding) +// (you must include the appropriate extension in the filename). +// returns TRUE on success, FALSE if couldn't open file, error writing file +extern int stbi_write_bmp (char const *filename, int x, int y, int comp, void *data); +extern int stbi_write_tga (char const *filename, int x, int y, int comp, void *data); +#endif + +// PRIMARY API - works on images of any type + +// load image by filename, open file, or memory buffer +#ifndef STBI_NO_STDIO +extern stbi_uc *stbi_load (char const *filename, int *x, int *y, int *comp, int req_comp); +extern stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); +extern int stbi_info_from_file (FILE *f, int *x, int *y, int *comp); +#endif +extern stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); +// for stbi_load_from_file, file pointer is left pointing immediately after image + +#ifndef STBI_NO_HDR +#ifndef STBI_NO_STDIO +extern float *stbi_loadf (char const *filename, int *x, int *y, int *comp, int req_comp); +extern float *stbi_loadf_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); +#endif +extern float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); + +extern void stbi_hdr_to_ldr_gamma(float gamma); +extern void stbi_hdr_to_ldr_scale(float scale); + +extern void stbi_ldr_to_hdr_gamma(float gamma); +extern void stbi_ldr_to_hdr_scale(float scale); + +#endif // STBI_NO_HDR + +// get a VERY brief reason for failure +// NOT THREADSAFE +extern char *stbi_failure_reason (void); + +// free the loaded image -- this is just free() +extern void stbi_image_free (void *retval_from_stbi_load); + +// get image dimensions & components without fully decoding +extern int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp); +extern int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len); +#ifndef STBI_NO_STDIO +extern int stbi_info (char const *filename, int *x, int *y, int *comp); +extern int stbi_is_hdr (char const *filename); +extern int stbi_is_hdr_from_file(FILE *f); +#endif + +// ZLIB client - used by PNG, available for other purposes + +extern char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen); +extern char *stbi_zlib_decode_malloc(const char *buffer, int len, int *outlen); +extern int stbi_zlib_decode_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + +extern char *stbi_zlib_decode_noheader_malloc(const char *buffer, int len, int *outlen); +extern int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + +// TYPE-SPECIFIC ACCESS + +// is it a jpeg? +extern int stbi_jpeg_test_memory (stbi_uc const *buffer, int len); +extern stbi_uc *stbi_jpeg_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); +extern int stbi_jpeg_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp); + +#ifndef STBI_NO_STDIO +extern stbi_uc *stbi_jpeg_load (char const *filename, int *x, int *y, int *comp, int req_comp); +extern int stbi_jpeg_test_file (FILE *f); +extern stbi_uc *stbi_jpeg_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); + +extern int stbi_jpeg_info (char const *filename, int *x, int *y, int *comp); +extern int stbi_jpeg_info_from_file (FILE *f, int *x, int *y, int *comp); +#endif + +// is it a png? +extern int stbi_png_test_memory (stbi_uc const *buffer, int len); +extern stbi_uc *stbi_png_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); +extern int stbi_png_info_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp); + +#ifndef STBI_NO_STDIO +extern stbi_uc *stbi_png_load (char const *filename, int *x, int *y, int *comp, int req_comp); +extern int stbi_png_info (char const *filename, int *x, int *y, int *comp); +extern int stbi_png_test_file (FILE *f); +extern stbi_uc *stbi_png_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); +extern int stbi_png_info_from_file (FILE *f, int *x, int *y, int *comp); +#endif + +// is it a bmp? +extern int stbi_bmp_test_memory (stbi_uc const *buffer, int len); + +extern stbi_uc *stbi_bmp_load (char const *filename, int *x, int *y, int *comp, int req_comp); +extern stbi_uc *stbi_bmp_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); +#ifndef STBI_NO_STDIO +extern int stbi_bmp_test_file (FILE *f); +extern stbi_uc *stbi_bmp_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); +#endif + +// is it a tga? +extern int stbi_tga_test_memory (stbi_uc const *buffer, int len); + +extern stbi_uc *stbi_tga_load (char const *filename, int *x, int *y, int *comp, int req_comp); +extern stbi_uc *stbi_tga_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); +#ifndef STBI_NO_STDIO +extern int stbi_tga_test_file (FILE *f); +extern stbi_uc *stbi_tga_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); +#endif + +// is it a psd? +extern int stbi_psd_test_memory (stbi_uc const *buffer, int len); + +extern stbi_uc *stbi_psd_load (char const *filename, int *x, int *y, int *comp, int req_comp); +extern stbi_uc *stbi_psd_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); +#ifndef STBI_NO_STDIO +extern int stbi_psd_test_file (FILE *f); +extern stbi_uc *stbi_psd_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); +#endif + +// is it an hdr? +extern int stbi_hdr_test_memory (stbi_uc const *buffer, int len); + +extern float * stbi_hdr_load (char const *filename, int *x, int *y, int *comp, int req_comp); +extern float * stbi_hdr_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); +extern stbi_uc *stbi_hdr_load_rgbe (char const *filename, int *x, int *y, int *comp, int req_comp); +extern float * stbi_hdr_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); +#ifndef STBI_NO_STDIO +extern int stbi_hdr_test_file (FILE *f); +extern float * stbi_hdr_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); +extern stbi_uc *stbi_hdr_load_rgbe_file (FILE *f, int *x, int *y, int *comp, int req_comp); +#endif + +// define new loaders +typedef struct +{ + int (*test_memory)(stbi_uc const *buffer, int len); + stbi_uc * (*load_from_memory)(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); + #ifndef STBI_NO_STDIO + int (*test_file)(FILE *f); + stbi_uc * (*load_from_file)(FILE *f, int *x, int *y, int *comp, int req_comp); + #endif +} stbi_loader; + +// register a loader by filling out the above structure (you must defined ALL functions) +// returns 1 if added or already added, 0 if not added (too many loaders) +// NOT THREADSAFE +extern int stbi_register_loader(stbi_loader *loader); + +// define faster low-level operations (typically SIMD support) +#if STBI_SIMD +typedef void (*stbi_idct_8x8)(uint8 *out, int out_stride, short data[64], unsigned short *dequantize); +// compute an integer IDCT on "input" +// input[x] = data[x] * dequantize[x] +// write results to 'out': 64 samples, each run of 8 spaced by 'out_stride' +// CLAMP results to 0..255 +typedef void (*stbi_YCbCr_to_RGB_run)(uint8 *output, uint8 const *y, uint8 const *cb, uint8 const *cr, int count, int step); +// compute a conversion from YCbCr to RGB +// 'count' pixels +// write pixels to 'output'; each pixel is 'step' bytes (either 3 or 4; if 4, write '255' as 4th), order R,G,B +// y: Y input channel +// cb: Cb input channel; scale/biased to be 0..255 +// cr: Cr input channel; scale/biased to be 0..255 + +extern void stbi_install_idct(stbi_idct_8x8 func); +extern void stbi_install_YCbCr_to_RGB(stbi_YCbCr_to_RGB_run func); +#endif // STBI_SIMD + +#ifdef __cplusplus +} +#endif + +// +// +//// end header file ///////////////////////////////////////////////////// +#endif // STBI_INCLUDE_STB_IMAGE_H diff --git a/lib/soil/stbi_DDS_aug.h b/lib/soil/stbi_DDS_aug.h new file mode 100644 index 000000000..c7da9f789 --- /dev/null +++ b/lib/soil/stbi_DDS_aug.h @@ -0,0 +1,21 @@ +/* + adding DDS loading support to stbi +*/ + +#ifndef HEADER_STB_IMAGE_DDS_AUGMENTATION +#define HEADER_STB_IMAGE_DDS_AUGMENTATION + +// is it a DDS file? +extern int stbi_dds_test_memory (stbi_uc const *buffer, int len); + +extern stbi_uc *stbi_dds_load (char *filename, int *x, int *y, int *comp, int req_comp); +extern stbi_uc *stbi_dds_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); +#ifndef STBI_NO_STDIO +extern int stbi_dds_test_file (FILE *f); +extern stbi_uc *stbi_dds_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); +#endif + +// +// +//// end header file ///////////////////////////////////////////////////// +#endif // HEADER_STB_IMAGE_DDS_AUGMENTATION diff --git a/lib/soil/stbi_DDS_aug_c.h b/lib/soil/stbi_DDS_aug_c.h new file mode 100644 index 000000000..f49407a7f --- /dev/null +++ b/lib/soil/stbi_DDS_aug_c.h @@ -0,0 +1,511 @@ + +/// DDS file support, does decoding, _not_ direct uploading +/// (use SOIL for that ;-) + +/// A bunch of DirectDraw Surface structures and flags +typedef struct { + unsigned int dwMagic; + unsigned int dwSize; + unsigned int dwFlags; + unsigned int dwHeight; + unsigned int dwWidth; + unsigned int dwPitchOrLinearSize; + unsigned int dwDepth; + unsigned int dwMipMapCount; + unsigned int dwReserved1[ 11 ]; + + // DDPIXELFORMAT + struct { + unsigned int dwSize; + unsigned int dwFlags; + unsigned int dwFourCC; + unsigned int dwRGBBitCount; + unsigned int dwRBitMask; + unsigned int dwGBitMask; + unsigned int dwBBitMask; + unsigned int dwAlphaBitMask; + } sPixelFormat; + + // DDCAPS2 + struct { + unsigned int dwCaps1; + unsigned int dwCaps2; + unsigned int dwDDSX; + unsigned int dwReserved; + } sCaps; + unsigned int dwReserved2; +} DDS_header ; + +// the following constants were copied directly off the MSDN website + +// The dwFlags member of the original DDSURFACEDESC2 structure +// can be set to one or more of the following values. +#define DDSD_CAPS 0x00000001 +#define DDSD_HEIGHT 0x00000002 +#define DDSD_WIDTH 0x00000004 +#define DDSD_PITCH 0x00000008 +#define DDSD_PIXELFORMAT 0x00001000 +#define DDSD_MIPMAPCOUNT 0x00020000 +#define DDSD_LINEARSIZE 0x00080000 +#define DDSD_DEPTH 0x00800000 + +// DirectDraw Pixel Format +#define DDPF_ALPHAPIXELS 0x00000001 +#define DDPF_FOURCC 0x00000004 +#define DDPF_RGB 0x00000040 + +// The dwCaps1 member of the DDSCAPS2 structure can be +// set to one or more of the following values. +#define DDSCAPS_COMPLEX 0x00000008 +#define DDSCAPS_TEXTURE 0x00001000 +#define DDSCAPS_MIPMAP 0x00400000 + +// The dwCaps2 member of the DDSCAPS2 structure can be +// set to one or more of the following values. +#define DDSCAPS2_CUBEMAP 0x00000200 +#define DDSCAPS2_CUBEMAP_POSITIVEX 0x00000400 +#define DDSCAPS2_CUBEMAP_NEGATIVEX 0x00000800 +#define DDSCAPS2_CUBEMAP_POSITIVEY 0x00001000 +#define DDSCAPS2_CUBEMAP_NEGATIVEY 0x00002000 +#define DDSCAPS2_CUBEMAP_POSITIVEZ 0x00004000 +#define DDSCAPS2_CUBEMAP_NEGATIVEZ 0x00008000 +#define DDSCAPS2_VOLUME 0x00200000 + +static int dds_test(stbi *s) +{ + // check the magic number + if (get8(s) != 'D') return 0; + if (get8(s) != 'D') return 0; + if (get8(s) != 'S') return 0; + if (get8(s) != ' ') return 0; + // check header size + if (get32le(s) != 124) return 0; + return 1; +} +#ifndef STBI_NO_STDIO +int stbi_dds_test_file (FILE *f) +{ + stbi s; + int r,n = ftell(f); + start_file(&s,f); + r = dds_test(&s); + fseek(f,n,SEEK_SET); + return r; +} +#endif + +int stbi_dds_test_memory (stbi_uc const *buffer, int len) +{ + stbi s; + start_mem(&s,buffer, len); + return dds_test(&s); +} + +// helper functions +int stbi_convert_bit_range( int c, int from_bits, int to_bits ) +{ + int b = (1 << (from_bits - 1)) + c * ((1 << to_bits) - 1); + return (b + (b >> from_bits)) >> from_bits; +} +void stbi_rgb_888_from_565( unsigned int c, int *r, int *g, int *b ) +{ + *r = stbi_convert_bit_range( (c >> 11) & 31, 5, 8 ); + *g = stbi_convert_bit_range( (c >> 05) & 63, 6, 8 ); + *b = stbi_convert_bit_range( (c >> 00) & 31, 5, 8 ); +} +void stbi_decode_DXT1_block( + unsigned char uncompressed[16*4], + unsigned char compressed[8] ) +{ + int next_bit = 4*8; + int i, r, g, b; + int c0, c1; + unsigned char decode_colors[4*4]; + // find the 2 primary colors + c0 = compressed[0] + (compressed[1] << 8); + c1 = compressed[2] + (compressed[3] << 8); + stbi_rgb_888_from_565( c0, &r, &g, &b ); + decode_colors[0] = r; + decode_colors[1] = g; + decode_colors[2] = b; + decode_colors[3] = 255; + stbi_rgb_888_from_565( c1, &r, &g, &b ); + decode_colors[4] = r; + decode_colors[5] = g; + decode_colors[6] = b; + decode_colors[7] = 255; + if( c0 > c1 ) + { + // no alpha, 2 interpolated colors + decode_colors[8] = (2*decode_colors[0] + decode_colors[4]) / 3; + decode_colors[9] = (2*decode_colors[1] + decode_colors[5]) / 3; + decode_colors[10] = (2*decode_colors[2] + decode_colors[6]) / 3; + decode_colors[11] = 255; + decode_colors[12] = (decode_colors[0] + 2*decode_colors[4]) / 3; + decode_colors[13] = (decode_colors[1] + 2*decode_colors[5]) / 3; + decode_colors[14] = (decode_colors[2] + 2*decode_colors[6]) / 3; + decode_colors[15] = 255; + } else + { + // 1 interpolated color, alpha + decode_colors[8] = (decode_colors[0] + decode_colors[4]) / 2; + decode_colors[9] = (decode_colors[1] + decode_colors[5]) / 2; + decode_colors[10] = (decode_colors[2] + decode_colors[6]) / 2; + decode_colors[11] = 255; + decode_colors[12] = 0; + decode_colors[13] = 0; + decode_colors[14] = 0; + decode_colors[15] = 0; + } + // decode the block + for( i = 0; i < 16*4; i += 4 ) + { + int idx = ((compressed[next_bit>>3] >> (next_bit & 7)) & 3) * 4; + next_bit += 2; + uncompressed[i+0] = decode_colors[idx+0]; + uncompressed[i+1] = decode_colors[idx+1]; + uncompressed[i+2] = decode_colors[idx+2]; + uncompressed[i+3] = decode_colors[idx+3]; + } + // done +} +void stbi_decode_DXT23_alpha_block( + unsigned char uncompressed[16*4], + unsigned char compressed[8] ) +{ + int i, next_bit = 0; + // each alpha value gets 4 bits + for( i = 3; i < 16*4; i += 4 ) + { + uncompressed[i] = stbi_convert_bit_range( + (compressed[next_bit>>3] >> (next_bit&7)) & 15, + 4, 8 ); + next_bit += 4; + } +} +void stbi_decode_DXT45_alpha_block( + unsigned char uncompressed[16*4], + unsigned char compressed[8] ) +{ + int i, next_bit = 8*2; + unsigned char decode_alpha[8]; + // each alpha value gets 3 bits, and the 1st 2 bytes are the range + decode_alpha[0] = compressed[0]; + decode_alpha[1] = compressed[1]; + if( decode_alpha[0] > decode_alpha[1] ) + { + // 6 step intermediate + decode_alpha[2] = (6*decode_alpha[0] + 1*decode_alpha[1]) / 7; + decode_alpha[3] = (5*decode_alpha[0] + 2*decode_alpha[1]) / 7; + decode_alpha[4] = (4*decode_alpha[0] + 3*decode_alpha[1]) / 7; + decode_alpha[5] = (3*decode_alpha[0] + 4*decode_alpha[1]) / 7; + decode_alpha[6] = (2*decode_alpha[0] + 5*decode_alpha[1]) / 7; + decode_alpha[7] = (1*decode_alpha[0] + 6*decode_alpha[1]) / 7; + } else + { + // 4 step intermediate, pluss full and none + decode_alpha[2] = (4*decode_alpha[0] + 1*decode_alpha[1]) / 5; + decode_alpha[3] = (3*decode_alpha[0] + 2*decode_alpha[1]) / 5; + decode_alpha[4] = (2*decode_alpha[0] + 3*decode_alpha[1]) / 5; + decode_alpha[5] = (1*decode_alpha[0] + 4*decode_alpha[1]) / 5; + decode_alpha[6] = 0; + decode_alpha[7] = 255; + } + for( i = 3; i < 16*4; i += 4 ) + { + int idx = 0, bit; + bit = (compressed[next_bit>>3] >> (next_bit&7)) & 1; + idx += bit << 0; + ++next_bit; + bit = (compressed[next_bit>>3] >> (next_bit&7)) & 1; + idx += bit << 1; + ++next_bit; + bit = (compressed[next_bit>>3] >> (next_bit&7)) & 1; + idx += bit << 2; + ++next_bit; + uncompressed[i] = decode_alpha[idx & 7]; + } + // done +} +void stbi_decode_DXT_color_block( + unsigned char uncompressed[16*4], + unsigned char compressed[8] ) +{ + int next_bit = 4*8; + int i, r, g, b; + int c0, c1; + unsigned char decode_colors[4*3]; + // find the 2 primary colors + c0 = compressed[0] + (compressed[1] << 8); + c1 = compressed[2] + (compressed[3] << 8); + stbi_rgb_888_from_565( c0, &r, &g, &b ); + decode_colors[0] = r; + decode_colors[1] = g; + decode_colors[2] = b; + stbi_rgb_888_from_565( c1, &r, &g, &b ); + decode_colors[3] = r; + decode_colors[4] = g; + decode_colors[5] = b; + // Like DXT1, but no choicees: + // no alpha, 2 interpolated colors + decode_colors[6] = (2*decode_colors[0] + decode_colors[3]) / 3; + decode_colors[7] = (2*decode_colors[1] + decode_colors[4]) / 3; + decode_colors[8] = (2*decode_colors[2] + decode_colors[5]) / 3; + decode_colors[9] = (decode_colors[0] + 2*decode_colors[3]) / 3; + decode_colors[10] = (decode_colors[1] + 2*decode_colors[4]) / 3; + decode_colors[11] = (decode_colors[2] + 2*decode_colors[5]) / 3; + // decode the block + for( i = 0; i < 16*4; i += 4 ) + { + int idx = ((compressed[next_bit>>3] >> (next_bit & 7)) & 3) * 3; + next_bit += 2; + uncompressed[i+0] = decode_colors[idx+0]; + uncompressed[i+1] = decode_colors[idx+1]; + uncompressed[i+2] = decode_colors[idx+2]; + } + // done +} +static stbi_uc *dds_load(stbi *s, int *x, int *y, int *comp, int req_comp) +{ + // all variables go up front + stbi_uc *dds_data = NULL; + stbi_uc block[16*4]; + stbi_uc compressed[8]; + int flags, DXT_family; + int has_alpha, has_mipmap; + int is_compressed, cubemap_faces; + int block_pitch, num_blocks; + DDS_header header; + int i, sz, cf; + // load the header + if( sizeof( DDS_header ) != 128 ) + { + return NULL; + } + getn( s, (stbi_uc*)(&header), 128 ); + // and do some checking + if( header.dwMagic != (('D' << 0) | ('D' << 8) | ('S' << 16) | (' ' << 24)) ) return NULL; + if( header.dwSize != 124 ) return NULL; + flags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT; + if( (header.dwFlags & flags) != flags ) return NULL; + /* According to the MSDN spec, the dwFlags should contain + DDSD_LINEARSIZE if it's compressed, or DDSD_PITCH if + uncompressed. Some DDS writers do not conform to the + spec, so I need to make my reader more tolerant */ + if( header.sPixelFormat.dwSize != 32 ) return NULL; + flags = DDPF_FOURCC | DDPF_RGB; + if( (header.sPixelFormat.dwFlags & flags) == 0 ) return NULL; + if( (header.sCaps.dwCaps1 & DDSCAPS_TEXTURE) == 0 ) return NULL; + // get the image data + s->img_x = header.dwWidth; + s->img_y = header.dwHeight; + s->img_n = 4; + is_compressed = (header.sPixelFormat.dwFlags & DDPF_FOURCC) / DDPF_FOURCC; + has_alpha = (header.sPixelFormat.dwFlags & DDPF_ALPHAPIXELS) / DDPF_ALPHAPIXELS; + has_mipmap = (header.sCaps.dwCaps1 & DDSCAPS_MIPMAP) && (header.dwMipMapCount > 1); + cubemap_faces = (header.sCaps.dwCaps2 & DDSCAPS2_CUBEMAP) / DDSCAPS2_CUBEMAP; + /* I need cubemaps to have square faces */ + cubemap_faces &= (s->img_x == s->img_y); + cubemap_faces *= 5; + cubemap_faces += 1; + block_pitch = (s->img_x+3) >> 2; + num_blocks = block_pitch * ((s->img_y+3) >> 2); + /* let the user know what's going on */ + *x = s->img_x; + *y = s->img_y; + *comp = s->img_n; + /* is this uncompressed? */ + if( is_compressed ) + { + /* compressed */ + // note: header.sPixelFormat.dwFourCC is something like (('D'<<0)|('X'<<8)|('T'<<16)|('1'<<24)) + DXT_family = 1 + (header.sPixelFormat.dwFourCC >> 24) - '1'; + if( (DXT_family < 1) || (DXT_family > 5) ) return NULL; + /* check the expected size...oops, nevermind... + those non-compliant writers leave + dwPitchOrLinearSize == 0 */ + // passed all the tests, get the RAM for decoding + sz = (s->img_x)*(s->img_y)*4*cubemap_faces; + dds_data = (unsigned char*)malloc( sz ); + /* do this once for each face */ + for( cf = 0; cf < cubemap_faces; ++ cf ) + { + // now read and decode all the blocks + for( i = 0; i < num_blocks; ++i ) + { + // where are we? + int bx, by, bw=4, bh=4; + int ref_x = 4 * (i % block_pitch); + int ref_y = 4 * (i / block_pitch); + // get the next block's worth of compressed data, and decompress it + if( DXT_family == 1 ) + { + // DXT1 + getn( s, compressed, 8 ); + stbi_decode_DXT1_block( block, compressed ); + } else if( DXT_family < 4 ) + { + // DXT2/3 + getn( s, compressed, 8 ); + stbi_decode_DXT23_alpha_block ( block, compressed ); + getn( s, compressed, 8 ); + stbi_decode_DXT_color_block ( block, compressed ); + } else + { + // DXT4/5 + getn( s, compressed, 8 ); + stbi_decode_DXT45_alpha_block ( block, compressed ); + getn( s, compressed, 8 ); + stbi_decode_DXT_color_block ( block, compressed ); + } + // is this a partial block? + if( ref_x + 4 > s->img_x ) + { + bw = s->img_x - ref_x; + } + if( ref_y + 4 > s->img_y ) + { + bh = s->img_y - ref_y; + } + // now drop our decompressed data into the buffer + for( by = 0; by < bh; ++by ) + { + int idx = 4*((ref_y+by+cf*s->img_x)*s->img_x + ref_x); + for( bx = 0; bx < bw*4; ++bx ) + { + + dds_data[idx+bx] = block[by*16+bx]; + } + } + } + /* done reading and decoding the main image... + skip MIPmaps if present */ + if( has_mipmap ) + { + int block_size = 16; + if( DXT_family == 1 ) + { + block_size = 8; + } + for( i = 1; i < header.dwMipMapCount; ++i ) + { + int mx = s->img_x >> (i + 2); + int my = s->img_y >> (i + 2); + if( mx < 1 ) + { + mx = 1; + } + if( my < 1 ) + { + my = 1; + } + skip( s, mx*my*block_size ); + } + } + }/* per cubemap face */ + } else + { + /* uncompressed */ + DXT_family = 0; + s->img_n = 3; + if( has_alpha ) + { + s->img_n = 4; + } + *comp = s->img_n; + sz = s->img_x*s->img_y*s->img_n*cubemap_faces; + dds_data = (unsigned char*)malloc( sz ); + /* do this once for each face */ + for( cf = 0; cf < cubemap_faces; ++ cf ) + { + /* read the main image for this face */ + getn( s, &dds_data[cf*s->img_x*s->img_y*s->img_n], s->img_x*s->img_y*s->img_n ); + /* done reading and decoding the main image... + skip MIPmaps if present */ + if( has_mipmap ) + { + for( i = 1; i < header.dwMipMapCount; ++i ) + { + int mx = s->img_x >> i; + int my = s->img_y >> i; + if( mx < 1 ) + { + mx = 1; + } + if( my < 1 ) + { + my = 1; + } + skip( s, mx*my*s->img_n ); + } + } + } + /* data was BGR, I need it RGB */ + for( i = 0; i < sz; i += s->img_n ) + { + unsigned char temp = dds_data[i]; + dds_data[i] = dds_data[i+2]; + dds_data[i+2] = temp; + } + } + /* finished decompressing into RGBA, + adjust the y size if we have a cubemap + note: sz is already up to date */ + s->img_y *= cubemap_faces; + *y = s->img_y; + // did the user want something else, or + // see if all the alpha values are 255 (i.e. no transparency) + has_alpha = 0; + if( s->img_n == 4) + { + for( i = 3; (i < sz) && (has_alpha == 0); i += 4 ) + { + has_alpha |= (dds_data[i] < 255); + } + } + if( (req_comp <= 4) && (req_comp >= 1) ) + { + // user has some requirements, meet them + if( req_comp != s->img_n ) + { + dds_data = convert_format( dds_data, s->img_n, req_comp, s->img_x, s->img_y ); + *comp = s->img_n; + } + } else + { + // user had no requirements, only drop to RGB is no alpha + if( (has_alpha == 0) && (s->img_n == 4) ) + { + dds_data = convert_format( dds_data, 4, 3, s->img_x, s->img_y ); + *comp = 3; + } + } + // OK, done + return dds_data; +} + +#ifndef STBI_NO_STDIO +stbi_uc *stbi_dds_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi s; + start_file(&s,f); + return dds_load(&s,x,y,comp,req_comp); +} + +stbi_uc *stbi_dds_load (char *filename, int *x, int *y, int *comp, int req_comp) +{ + stbi_uc *data; + FILE *f = fopen(filename, "rb"); + if (!f) return NULL; + data = stbi_dds_load_from_file(f,x,y,comp,req_comp); + fclose(f); + return data; +} +#endif + +stbi_uc *stbi_dds_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi s; + start_mem(&s,buffer, len); + return dds_load(&s,x,y,comp,req_comp); +} diff --git a/lib/xxhash.c b/lib/xxhash.c new file mode 100644 index 000000000..156b4847e --- /dev/null +++ b/lib/xxhash.c @@ -0,0 +1,868 @@ +/* +* xxHash - Fast Hash algorithm +* Copyright (C) 2012-2016, Yann Collet +* +* BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are +* met: +* +* * Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* * Redistributions in binary form must reproduce the above +* copyright notice, this list of conditions and the following disclaimer +* in the documentation and/or other materials provided with the +* distribution. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +* +* You can contact the author at : +* - xxHash homepage: http://www.xxhash.com +* - xxHash source repository : https://github.com/Cyan4973/xxHash +*/ + + +/* ************************************* +* Tuning parameters +***************************************/ +/*!XXH_FORCE_MEMORY_ACCESS : + * By default, access to unaligned memory is controlled by `memcpy()`, which is safe and portable. + * Unfortunately, on some target/compiler combinations, the generated assembly is sub-optimal. + * The below switch allow to select different access method for improved performance. + * Method 0 (default) : use `memcpy()`. Safe and portable. + * Method 1 : `__packed` statement. It depends on compiler extension (ie, not portable). + * This method is safe if your compiler supports it, and *generally* as fast or faster than `memcpy`. + * Method 2 : direct access. This method doesn't depend on compiler but violate C standard. + * It can generate buggy code on targets which do not support unaligned memory accesses. + * But in some circumstances, it's the only known way to get the most performance (ie GCC + ARMv6) + * See https://stackoverflow.com/a/32095106/646947 for details. + * Prefer these methods in priority order (0 > 1 > 2) + */ +#ifndef XXH_FORCE_MEMORY_ACCESS /* can be defined externally, on command line for example */ +# if defined(__GNUC__) && ( defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) || defined(__ARM_ARCH_6K__) || defined(__ARM_ARCH_6Z__) || defined(__ARM_ARCH_6ZK__) || defined(__ARM_ARCH_6T2__) ) +# define XXH_FORCE_MEMORY_ACCESS 2 +# elif defined(__INTEL_COMPILER) || \ + (defined(__GNUC__) && ( defined(__ARM_ARCH_7__) || defined(__ARM_ARCH_7A__) || defined(__ARM_ARCH_7R__) || defined(__ARM_ARCH_7M__) || defined(__ARM_ARCH_7S__) )) +# define XXH_FORCE_MEMORY_ACCESS 1 +# endif +#endif + +/*!XXH_ACCEPT_NULL_INPUT_POINTER : + * If the input pointer is a null pointer, xxHash default behavior is to trigger a memory access error, since it is a bad pointer. + * When this option is enabled, xxHash output for null input pointers will be the same as a null-length input. + * By default, this option is disabled. To enable it, uncomment below define : + */ +/* #define XXH_ACCEPT_NULL_INPUT_POINTER 1 */ + +/*!XXH_FORCE_NATIVE_FORMAT : + * By default, xxHash library provides endian-independant Hash values, based on little-endian convention. + * Results are therefore identical for little-endian and big-endian CPU. + * This comes at a performance cost for big-endian CPU, since some swapping is required to emulate little-endian format. + * Should endian-independance be of no importance for your application, you may set the #define below to 1, + * to improve speed for Big-endian CPU. + * This option has no impact on Little_Endian CPU. + */ +#ifndef XXH_FORCE_NATIVE_FORMAT /* can be defined externally */ +# define XXH_FORCE_NATIVE_FORMAT 0 +#endif + +/*!XXH_FORCE_ALIGN_CHECK : + * This is a minor performance trick, only useful with lots of very small keys. + * It means : check for aligned/unaligned input. + * The check costs one initial branch per hash; set to 0 when the input data + * is guaranteed to be aligned. + */ +#ifndef XXH_FORCE_ALIGN_CHECK /* can be defined externally */ +# if defined(__i386) || defined(_M_IX86) || defined(__x86_64__) || defined(_M_X64) +# define XXH_FORCE_ALIGN_CHECK 0 +# else +# define XXH_FORCE_ALIGN_CHECK 1 +# endif +#endif + + +/* ************************************* +* Includes & Memory related functions +***************************************/ +/* Modify the local functions below should you wish to use some other memory routines */ +/* for malloc(), free() */ +#include +static void* XXH_malloc(size_t s) { return malloc(s); } +static void XXH_free (void* p) { free(p); } +/* for memcpy() */ +#include +static void* XXH_memcpy(void* dest, const void* src, size_t size) { return memcpy(dest,src,size); } + +#define XXH_STATIC_LINKING_ONLY +#include "xxhash.h" + + +/* ************************************* +* Compiler Specific Options +***************************************/ +#ifdef _MSC_VER /* Visual Studio */ +# pragma warning(disable : 4127) /* disable: C4127: conditional expression is constant */ +# define FORCE_INLINE static __forceinline +#else +# if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L /* C99 */ +# ifdef __GNUC__ +# define FORCE_INLINE static inline __attribute__((always_inline)) +# else +# define FORCE_INLINE static inline +# endif +# else +# define FORCE_INLINE static +# endif /* __STDC_VERSION__ */ +#endif + + +/* ************************************* +* Basic Types +***************************************/ +#ifndef MEM_MODULE +# define MEM_MODULE +# if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L /* C99 */ +# include + typedef uint8_t BYTE; + typedef uint16_t U16; + typedef uint32_t U32; + typedef int32_t S32; + typedef uint64_t U64; +# else + typedef unsigned char BYTE; + typedef unsigned short U16; + typedef unsigned int U32; + typedef signed int S32; + typedef unsigned long long U64; +# endif +#endif + + +#if (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==2)) + +/* Force direct memory access. Only works on CPU which support unaligned memory access in hardware */ +static U32 XXH_read32(const void* memPtr) { return *(const U32*) memPtr; } +static U64 XXH_read64(const void* memPtr) { return *(const U64*) memPtr; } + +#elif (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==1)) + +/* __pack instructions are safer, but compiler specific, hence potentially problematic for some compilers */ +/* currently only defined for gcc and icc */ +typedef union { U32 u32; U64 u64; } __attribute__((packed)) unalign; + +static U32 XXH_read32(const void* ptr) { return ((const unalign*)ptr)->u32; } +static U64 XXH_read64(const void* ptr) { return ((const unalign*)ptr)->u64; } + +#else + +/* portable and safe solution. Generally efficient. + * see : https://stackoverflow.com/a/32095106/646947 + */ + +static U32 XXH_read32(const void* memPtr) +{ + U32 val; + memcpy(&val, memPtr, sizeof(val)); + return val; +} + +static U64 XXH_read64(const void* memPtr) +{ + U64 val; + memcpy(&val, memPtr, sizeof(val)); + return val; +} + +#endif /* XXH_FORCE_DIRECT_MEMORY_ACCESS */ + + +/* **************************************** +* Compiler-specific Functions and Macros +******************************************/ +#define GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) + +/* Note : although _rotl exists for minGW (GCC under windows), performance seems poor */ +#if defined(_MSC_VER) +# define XXH_rotl32(x,r) _rotl(x,r) +# define XXH_rotl64(x,r) _rotl64(x,r) +#else +# define XXH_rotl32(x,r) ((x << r) | (x >> (32 - r))) +# define XXH_rotl64(x,r) ((x << r) | (x >> (64 - r))) +#endif + +#if defined(_MSC_VER) /* Visual Studio */ +# define XXH_swap32 _byteswap_ulong +# define XXH_swap64 _byteswap_uint64 +#elif GCC_VERSION >= 403 +# define XXH_swap32 __builtin_bswap32 +# define XXH_swap64 __builtin_bswap64 +#else +static U32 XXH_swap32 (U32 x) +{ + return ((x << 24) & 0xff000000 ) | + ((x << 8) & 0x00ff0000 ) | + ((x >> 8) & 0x0000ff00 ) | + ((x >> 24) & 0x000000ff ); +} +static U64 XXH_swap64 (U64 x) +{ + return ((x << 56) & 0xff00000000000000ULL) | + ((x << 40) & 0x00ff000000000000ULL) | + ((x << 24) & 0x0000ff0000000000ULL) | + ((x << 8) & 0x000000ff00000000ULL) | + ((x >> 8) & 0x00000000ff000000ULL) | + ((x >> 24) & 0x0000000000ff0000ULL) | + ((x >> 40) & 0x000000000000ff00ULL) | + ((x >> 56) & 0x00000000000000ffULL); +} +#endif + + +/* ************************************* +* Architecture Macros +***************************************/ +typedef enum { XXH_bigEndian=0, XXH_littleEndian=1 } XXH_endianess; + +/* XXH_CPU_LITTLE_ENDIAN can be defined externally, for example on the compiler command line */ +#ifndef XXH_CPU_LITTLE_ENDIAN + static const int g_one = 1; +# define XXH_CPU_LITTLE_ENDIAN (*(const char*)(&g_one)) +#endif + + +/* *************************** +* Memory reads +*****************************/ +typedef enum { XXH_aligned, XXH_unaligned } XXH_alignment; + +FORCE_INLINE U32 XXH_readLE32_align(const void* ptr, XXH_endianess endian, XXH_alignment align) +{ + if (align==XXH_unaligned) + return endian==XXH_littleEndian ? XXH_read32(ptr) : XXH_swap32(XXH_read32(ptr)); + else + return endian==XXH_littleEndian ? *(const U32*)ptr : XXH_swap32(*(const U32*)ptr); +} + +FORCE_INLINE U32 XXH_readLE32(const void* ptr, XXH_endianess endian) +{ + return XXH_readLE32_align(ptr, endian, XXH_unaligned); +} + +static U32 XXH_readBE32(const void* ptr) +{ + return XXH_CPU_LITTLE_ENDIAN ? XXH_swap32(XXH_read32(ptr)) : XXH_read32(ptr); +} + +FORCE_INLINE U64 XXH_readLE64_align(const void* ptr, XXH_endianess endian, XXH_alignment align) +{ + if (align==XXH_unaligned) + return endian==XXH_littleEndian ? XXH_read64(ptr) : XXH_swap64(XXH_read64(ptr)); + else + return endian==XXH_littleEndian ? *(const U64*)ptr : XXH_swap64(*(const U64*)ptr); +} + +FORCE_INLINE U64 XXH_readLE64(const void* ptr, XXH_endianess endian) +{ + return XXH_readLE64_align(ptr, endian, XXH_unaligned); +} + +static U64 XXH_readBE64(const void* ptr) +{ + return XXH_CPU_LITTLE_ENDIAN ? XXH_swap64(XXH_read64(ptr)) : XXH_read64(ptr); +} + + +/* ************************************* +* Macros +***************************************/ +#define XXH_STATIC_ASSERT(c) { enum { XXH_static_assert = 1/(int)(!!(c)) }; } /* use only *after* variable declarations */ + + +/* ************************************* +* Constants +***************************************/ +static const U32 PRIME32_1 = 2654435761U; +static const U32 PRIME32_2 = 2246822519U; +static const U32 PRIME32_3 = 3266489917U; +static const U32 PRIME32_4 = 668265263U; +static const U32 PRIME32_5 = 374761393U; + +static const U64 PRIME64_1 = 11400714785074694791ULL; +static const U64 PRIME64_2 = 14029467366897019727ULL; +static const U64 PRIME64_3 = 1609587929392839161ULL; +static const U64 PRIME64_4 = 9650029242287828579ULL; +static const U64 PRIME64_5 = 2870177450012600261ULL; + +XXH_PUBLIC_API unsigned XXH_versionNumber (void) { return XXH_VERSION_NUMBER; } + + +/* ************************** +* Utils +****************************/ +XXH_PUBLIC_API void XXH32_copyState(XXH32_state_t* restrict dstState, const XXH32_state_t* restrict srcState) +{ + memcpy(dstState, srcState, sizeof(*dstState)); +} + +XXH_PUBLIC_API void XXH64_copyState(XXH64_state_t* restrict dstState, const XXH64_state_t* restrict srcState) +{ + memcpy(dstState, srcState, sizeof(*dstState)); +} + + +/* *************************** +* Simple Hash Functions +*****************************/ + +static U32 XXH32_round(U32 seed, U32 input) +{ + seed += input * PRIME32_2; + seed = XXH_rotl32(seed, 13); + seed *= PRIME32_1; + return seed; +} + +FORCE_INLINE U32 XXH32_endian_align(const void* input, size_t len, U32 seed, XXH_endianess endian, XXH_alignment align) +{ + const BYTE* p = (const BYTE*)input; + const BYTE* bEnd = p + len; + U32 h32; +#define XXH_get32bits(p) XXH_readLE32_align(p, endian, align) + +#ifdef XXH_ACCEPT_NULL_INPUT_POINTER + if (p==NULL) { + len=0; + bEnd=p=(const BYTE*)(size_t)16; + } +#endif + + if (len>=16) { + const BYTE* const limit = bEnd - 16; + U32 v1 = seed + PRIME32_1 + PRIME32_2; + U32 v2 = seed + PRIME32_2; + U32 v3 = seed + 0; + U32 v4 = seed - PRIME32_1; + + do { + v1 = XXH32_round(v1, XXH_get32bits(p)); p+=4; + v2 = XXH32_round(v2, XXH_get32bits(p)); p+=4; + v3 = XXH32_round(v3, XXH_get32bits(p)); p+=4; + v4 = XXH32_round(v4, XXH_get32bits(p)); p+=4; + } while (p<=limit); + + h32 = XXH_rotl32(v1, 1) + XXH_rotl32(v2, 7) + XXH_rotl32(v3, 12) + XXH_rotl32(v4, 18); + } else { + h32 = seed + PRIME32_5; + } + + h32 += (U32) len; + + while (p+4<=bEnd) { + h32 += XXH_get32bits(p) * PRIME32_3; + h32 = XXH_rotl32(h32, 17) * PRIME32_4 ; + p+=4; + } + + while (p> 15; + h32 *= PRIME32_2; + h32 ^= h32 >> 13; + h32 *= PRIME32_3; + h32 ^= h32 >> 16; + + return h32; +} + + +XXH_PUBLIC_API unsigned int XXH32 (const void* input, size_t len, unsigned int seed) +{ +#if 0 + /* Simple version, good for code maintenance, but unfortunately slow for small inputs */ + XXH32_CREATESTATE_STATIC(state); + XXH32_reset(state, seed); + XXH32_update(state, input, len); + return XXH32_digest(state); +#else + XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN; + + if (XXH_FORCE_ALIGN_CHECK) { + if ((((size_t)input) & 3) == 0) { /* Input is 4-bytes aligned, leverage the speed benefit */ + if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT) + return XXH32_endian_align(input, len, seed, XXH_littleEndian, XXH_aligned); + else + return XXH32_endian_align(input, len, seed, XXH_bigEndian, XXH_aligned); + } } + + if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT) + return XXH32_endian_align(input, len, seed, XXH_littleEndian, XXH_unaligned); + else + return XXH32_endian_align(input, len, seed, XXH_bigEndian, XXH_unaligned); +#endif +} + + +static U64 XXH64_round(U64 acc, U64 input) +{ + acc += input * PRIME64_2; + acc = XXH_rotl64(acc, 31); + acc *= PRIME64_1; + return acc; +} + +static U64 XXH64_mergeRound(U64 acc, U64 val) +{ + val = XXH64_round(0, val); + acc ^= val; + acc = acc * PRIME64_1 + PRIME64_4; + return acc; +} + +FORCE_INLINE U64 XXH64_endian_align(const void* input, size_t len, U64 seed, XXH_endianess endian, XXH_alignment align) +{ + const BYTE* p = (const BYTE*)input; + const BYTE* const bEnd = p + len; + U64 h64; +#define XXH_get64bits(p) XXH_readLE64_align(p, endian, align) + +#ifdef XXH_ACCEPT_NULL_INPUT_POINTER + if (p==NULL) { + len=0; + bEnd=p=(const BYTE*)(size_t)32; + } +#endif + + if (len>=32) { + const BYTE* const limit = bEnd - 32; + U64 v1 = seed + PRIME64_1 + PRIME64_2; + U64 v2 = seed + PRIME64_2; + U64 v3 = seed + 0; + U64 v4 = seed - PRIME64_1; + + do { + v1 = XXH64_round(v1, XXH_get64bits(p)); p+=8; + v2 = XXH64_round(v2, XXH_get64bits(p)); p+=8; + v3 = XXH64_round(v3, XXH_get64bits(p)); p+=8; + v4 = XXH64_round(v4, XXH_get64bits(p)); p+=8; + } while (p<=limit); + + h64 = XXH_rotl64(v1, 1) + XXH_rotl64(v2, 7) + XXH_rotl64(v3, 12) + XXH_rotl64(v4, 18); + h64 = XXH64_mergeRound(h64, v1); + h64 = XXH64_mergeRound(h64, v2); + h64 = XXH64_mergeRound(h64, v3); + h64 = XXH64_mergeRound(h64, v4); + + } else { + h64 = seed + PRIME64_5; + } + + h64 += (U64) len; + + while (p+8<=bEnd) { + U64 const k1 = XXH64_round(0, XXH_get64bits(p)); + h64 ^= k1; + h64 = XXH_rotl64(h64,27) * PRIME64_1 + PRIME64_4; + p+=8; + } + + if (p+4<=bEnd) { + h64 ^= (U64)(XXH_get32bits(p)) * PRIME64_1; + h64 = XXH_rotl64(h64, 23) * PRIME64_2 + PRIME64_3; + p+=4; + } + + while (p> 33; + h64 *= PRIME64_2; + h64 ^= h64 >> 29; + h64 *= PRIME64_3; + h64 ^= h64 >> 32; + + return h64; +} + + +XXH_PUBLIC_API unsigned long long XXH64 (const void* input, size_t len, unsigned long long seed) +{ +#if 0 + /* Simple version, good for code maintenance, but unfortunately slow for small inputs */ + XXH64_CREATESTATE_STATIC(state); + XXH64_reset(state, seed); + XXH64_update(state, input, len); + return XXH64_digest(state); +#else + XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN; + + if (XXH_FORCE_ALIGN_CHECK) { + if ((((size_t)input) & 7)==0) { /* Input is aligned, let's leverage the speed advantage */ + if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT) + return XXH64_endian_align(input, len, seed, XXH_littleEndian, XXH_aligned); + else + return XXH64_endian_align(input, len, seed, XXH_bigEndian, XXH_aligned); + } } + + if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT) + return XXH64_endian_align(input, len, seed, XXH_littleEndian, XXH_unaligned); + else + return XXH64_endian_align(input, len, seed, XXH_bigEndian, XXH_unaligned); +#endif +} + + +/* ************************************************** +* Advanced Hash Functions +****************************************************/ + +XXH_PUBLIC_API XXH32_state_t* XXH32_createState(void) +{ + return (XXH32_state_t*)XXH_malloc(sizeof(XXH32_state_t)); +} +XXH_PUBLIC_API XXH_errorcode XXH32_freeState(XXH32_state_t* statePtr) +{ + XXH_free(statePtr); + return XXH_OK; +} + +XXH_PUBLIC_API XXH64_state_t* XXH64_createState(void) +{ + return (XXH64_state_t*)XXH_malloc(sizeof(XXH64_state_t)); +} +XXH_PUBLIC_API XXH_errorcode XXH64_freeState(XXH64_state_t* statePtr) +{ + XXH_free(statePtr); + return XXH_OK; +} + + +/*** Hash feed ***/ + +XXH_PUBLIC_API XXH_errorcode XXH32_reset(XXH32_state_t* statePtr, unsigned int seed) +{ + XXH32_state_t state; /* using a local state to memcpy() in order to avoid strict-aliasing warnings */ + memset(&state, 0, sizeof(state)); + state.seed = seed; + state.v1 = seed + PRIME32_1 + PRIME32_2; + state.v2 = seed + PRIME32_2; + state.v3 = seed + 0; + state.v4 = seed - PRIME32_1; + memcpy(statePtr, &state, sizeof(state)); + return XXH_OK; +} + + +XXH_PUBLIC_API XXH_errorcode XXH64_reset(XXH64_state_t* statePtr, unsigned long long seed) +{ + XXH64_state_t state; /* using a local state to memcpy() in order to avoid strict-aliasing warnings */ + memset(&state, 0, sizeof(state)); + state.seed = seed; + state.v1 = seed + PRIME64_1 + PRIME64_2; + state.v2 = seed + PRIME64_2; + state.v3 = seed + 0; + state.v4 = seed - PRIME64_1; + memcpy(statePtr, &state, sizeof(state)); + return XXH_OK; +} + + +FORCE_INLINE XXH_errorcode XXH32_update_endian (XXH32_state_t* state, const void* input, size_t len, XXH_endianess endian) +{ + const BYTE* p = (const BYTE*)input; + const BYTE* const bEnd = p + len; + +#ifdef XXH_ACCEPT_NULL_INPUT_POINTER + if (input==NULL) return XXH_ERROR; +#endif + + state->total_len += len; + + if (state->memsize + len < 16) { /* fill in tmp buffer */ + XXH_memcpy((BYTE*)(state->mem32) + state->memsize, input, len); + state->memsize += (U32)len; + return XXH_OK; + } + + if (state->memsize) { /* some data left from previous update */ + XXH_memcpy((BYTE*)(state->mem32) + state->memsize, input, 16-state->memsize); + { const U32* p32 = state->mem32; + state->v1 = XXH32_round(state->v1, XXH_readLE32(p32, endian)); p32++; + state->v2 = XXH32_round(state->v2, XXH_readLE32(p32, endian)); p32++; + state->v3 = XXH32_round(state->v3, XXH_readLE32(p32, endian)); p32++; + state->v4 = XXH32_round(state->v4, XXH_readLE32(p32, endian)); p32++; + } + p += 16-state->memsize; + state->memsize = 0; + } + + if (p <= bEnd-16) { + const BYTE* const limit = bEnd - 16; + U32 v1 = state->v1; + U32 v2 = state->v2; + U32 v3 = state->v3; + U32 v4 = state->v4; + + do { + v1 = XXH32_round(v1, XXH_readLE32(p, endian)); p+=4; + v2 = XXH32_round(v2, XXH_readLE32(p, endian)); p+=4; + v3 = XXH32_round(v3, XXH_readLE32(p, endian)); p+=4; + v4 = XXH32_round(v4, XXH_readLE32(p, endian)); p+=4; + } while (p<=limit); + + state->v1 = v1; + state->v2 = v2; + state->v3 = v3; + state->v4 = v4; + } + + if (p < bEnd) { + XXH_memcpy(state->mem32, p, bEnd-p); + state->memsize = (int)(bEnd-p); + } + + return XXH_OK; +} + +XXH_PUBLIC_API XXH_errorcode XXH32_update (XXH32_state_t* state_in, const void* input, size_t len) +{ + XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN; + + if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT) + return XXH32_update_endian(state_in, input, len, XXH_littleEndian); + else + return XXH32_update_endian(state_in, input, len, XXH_bigEndian); +} + + + +FORCE_INLINE U32 XXH32_digest_endian (const XXH32_state_t* state, XXH_endianess endian) +{ + const BYTE * p = (const BYTE*)state->mem32; + const BYTE* const bEnd = (const BYTE*)(state->mem32) + state->memsize; + U32 h32; + + if (state->total_len >= 16) { + h32 = XXH_rotl32(state->v1, 1) + XXH_rotl32(state->v2, 7) + XXH_rotl32(state->v3, 12) + XXH_rotl32(state->v4, 18); + } else { + h32 = state->seed + PRIME32_5; + } + + h32 += (U32) state->total_len; + + while (p+4<=bEnd) { + h32 += XXH_readLE32(p, endian) * PRIME32_3; + h32 = XXH_rotl32(h32, 17) * PRIME32_4; + p+=4; + } + + while (p> 15; + h32 *= PRIME32_2; + h32 ^= h32 >> 13; + h32 *= PRIME32_3; + h32 ^= h32 >> 16; + + return h32; +} + + +XXH_PUBLIC_API unsigned int XXH32_digest (const XXH32_state_t* state_in) +{ + XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN; + + if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT) + return XXH32_digest_endian(state_in, XXH_littleEndian); + else + return XXH32_digest_endian(state_in, XXH_bigEndian); +} + + + +/* **** XXH64 **** */ + +FORCE_INLINE XXH_errorcode XXH64_update_endian (XXH64_state_t* state, const void* input, size_t len, XXH_endianess endian) +{ + const BYTE* p = (const BYTE*)input; + const BYTE* const bEnd = p + len; + +#ifdef XXH_ACCEPT_NULL_INPUT_POINTER + if (input==NULL) return XXH_ERROR; +#endif + + state->total_len += len; + + if (state->memsize + len < 32) { /* fill in tmp buffer */ + XXH_memcpy(((BYTE*)state->mem64) + state->memsize, input, len); + state->memsize += (U32)len; + return XXH_OK; + } + + if (state->memsize) { /* tmp buffer is full */ + XXH_memcpy(((BYTE*)state->mem64) + state->memsize, input, 32-state->memsize); + state->v1 = XXH64_round(state->v1, XXH_readLE64(state->mem64+0, endian)); + state->v2 = XXH64_round(state->v2, XXH_readLE64(state->mem64+1, endian)); + state->v3 = XXH64_round(state->v3, XXH_readLE64(state->mem64+2, endian)); + state->v4 = XXH64_round(state->v4, XXH_readLE64(state->mem64+3, endian)); + p += 32-state->memsize; + state->memsize = 0; + } + + if (p+32 <= bEnd) { + const BYTE* const limit = bEnd - 32; + U64 v1 = state->v1; + U64 v2 = state->v2; + U64 v3 = state->v3; + U64 v4 = state->v4; + + do { + v1 = XXH64_round(v1, XXH_readLE64(p, endian)); p+=8; + v2 = XXH64_round(v2, XXH_readLE64(p, endian)); p+=8; + v3 = XXH64_round(v3, XXH_readLE64(p, endian)); p+=8; + v4 = XXH64_round(v4, XXH_readLE64(p, endian)); p+=8; + } while (p<=limit); + + state->v1 = v1; + state->v2 = v2; + state->v3 = v3; + state->v4 = v4; + } + + if (p < bEnd) { + XXH_memcpy(state->mem64, p, bEnd-p); + state->memsize = (int)(bEnd-p); + } + + return XXH_OK; +} + +XXH_PUBLIC_API XXH_errorcode XXH64_update (XXH64_state_t* state_in, const void* input, size_t len) +{ + XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN; + + if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT) + return XXH64_update_endian(state_in, input, len, XXH_littleEndian); + else + return XXH64_update_endian(state_in, input, len, XXH_bigEndian); +} + + + +FORCE_INLINE U64 XXH64_digest_endian (const XXH64_state_t* state, XXH_endianess endian) +{ + const BYTE * p = (const BYTE*)state->mem64; + const BYTE* const bEnd = (const BYTE*)state->mem64 + state->memsize; + U64 h64; + + if (state->total_len >= 32) { + U64 const v1 = state->v1; + U64 const v2 = state->v2; + U64 const v3 = state->v3; + U64 const v4 = state->v4; + + h64 = XXH_rotl64(v1, 1) + XXH_rotl64(v2, 7) + XXH_rotl64(v3, 12) + XXH_rotl64(v4, 18); + h64 = XXH64_mergeRound(h64, v1); + h64 = XXH64_mergeRound(h64, v2); + h64 = XXH64_mergeRound(h64, v3); + h64 = XXH64_mergeRound(h64, v4); + } else { + h64 = state->seed + PRIME64_5; + } + + h64 += (U64) state->total_len; + + while (p+8<=bEnd) { + U64 const k1 = XXH64_round(0, XXH_readLE64(p, endian)); + h64 ^= k1; + h64 = XXH_rotl64(h64,27) * PRIME64_1 + PRIME64_4; + p+=8; + } + + if (p+4<=bEnd) { + h64 ^= (U64)(XXH_readLE32(p, endian)) * PRIME64_1; + h64 = XXH_rotl64(h64, 23) * PRIME64_2 + PRIME64_3; + p+=4; + } + + while (p> 33; + h64 *= PRIME64_2; + h64 ^= h64 >> 29; + h64 *= PRIME64_3; + h64 ^= h64 >> 32; + + return h64; +} + + +XXH_PUBLIC_API unsigned long long XXH64_digest (const XXH64_state_t* state_in) +{ + XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN; + + if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT) + return XXH64_digest_endian(state_in, XXH_littleEndian); + else + return XXH64_digest_endian(state_in, XXH_bigEndian); +} + + +/* ************************** +* Canonical representation +****************************/ + +/*! Default XXH result types are basic unsigned 32 and 64 bits. +* The canonical representation follows human-readable write convention, aka big-endian (large digits first). +* These functions allow transformation of hash result into and from its canonical format. +* This way, hash values can be written into a file or buffer, and remain comparable across different systems and programs. +*/ + +XXH_PUBLIC_API void XXH32_canonicalFromHash(XXH32_canonical_t* dst, XXH32_hash_t hash) +{ + XXH_STATIC_ASSERT(sizeof(XXH32_canonical_t) == sizeof(XXH32_hash_t)); + if (XXH_CPU_LITTLE_ENDIAN) hash = XXH_swap32(hash); + memcpy(dst, &hash, sizeof(*dst)); +} + +XXH_PUBLIC_API void XXH64_canonicalFromHash(XXH64_canonical_t* dst, XXH64_hash_t hash) +{ + XXH_STATIC_ASSERT(sizeof(XXH64_canonical_t) == sizeof(XXH64_hash_t)); + if (XXH_CPU_LITTLE_ENDIAN) hash = XXH_swap64(hash); + memcpy(dst, &hash, sizeof(*dst)); +} + +XXH_PUBLIC_API XXH32_hash_t XXH32_hashFromCanonical(const XXH32_canonical_t* src) +{ + return XXH_readBE32(src); +} + +XXH_PUBLIC_API XXH64_hash_t XXH64_hashFromCanonical(const XXH64_canonical_t* src) +{ + return XXH_readBE64(src); +} diff --git a/lib/xxhash.h b/lib/xxhash.h new file mode 100644 index 000000000..4d7feffcd --- /dev/null +++ b/lib/xxhash.h @@ -0,0 +1,297 @@ +/* + xxHash - Extremely Fast Hash algorithm + Header File + Copyright (C) 2012-2016, Yann Collet. + + BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + You can contact the author at : + - xxHash source repository : https://github.com/Cyan4973/xxHash +*/ + +/* Notice extracted from xxHash homepage : + +xxHash is an extremely fast Hash algorithm, running at RAM speed limits. +It also successfully passes all tests from the SMHasher suite. + +Comparison (single thread, Windows Seven 32 bits, using SMHasher on a Core 2 Duo @3GHz) + +Name Speed Q.Score Author +xxHash 5.4 GB/s 10 +CrapWow 3.2 GB/s 2 Andrew +MumurHash 3a 2.7 GB/s 10 Austin Appleby +SpookyHash 2.0 GB/s 10 Bob Jenkins +SBox 1.4 GB/s 9 Bret Mulvey +Lookup3 1.2 GB/s 9 Bob Jenkins +SuperFastHash 1.2 GB/s 1 Paul Hsieh +CityHash64 1.05 GB/s 10 Pike & Alakuijala +FNV 0.55 GB/s 5 Fowler, Noll, Vo +CRC32 0.43 GB/s 9 +MD5-32 0.33 GB/s 10 Ronald L. Rivest +SHA1-32 0.28 GB/s 10 + +Q.Score is a measure of quality of the hash function. +It depends on successfully passing SMHasher test set. +10 is a perfect score. + +A 64-bits version, named XXH64, is available since r35. +It offers much better speed, but for 64-bits applications only. +Name Speed on 64 bits Speed on 32 bits +XXH64 13.8 GB/s 1.9 GB/s +XXH32 6.8 GB/s 6.0 GB/s +*/ + +#ifndef XXHASH_H_5627135585666179 +#define XXHASH_H_5627135585666179 1 + +#if defined (__cplusplus) +extern "C" { +#endif + + +/* **************************** +* Definitions +******************************/ +#include /* size_t */ +typedef enum { XXH_OK=0, XXH_ERROR } XXH_errorcode; + + +/* **************************** +* API modifier +******************************/ +/** XXH_PRIVATE_API +* This is useful if you want to include xxhash functions in `static` mode +* in order to inline them, and remove their symbol from the public list. +* Methodology : +* #define XXH_PRIVATE_API +* #include "xxhash.h" +* `xxhash.c` is automatically included, so the file is still needed, +* but it's not useful to compile and link it anymore. +*/ +#ifdef XXH_PRIVATE_API +# ifndef XXH_STATIC_LINKING_ONLY +# define XXH_STATIC_LINKING_ONLY +# endif +# if defined(__GNUC__) +# define XXH_PUBLIC_API static __attribute__((unused)) +# elif defined (__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) +# define XXH_PUBLIC_API static inline +# elif defined(_MSC_VER) +# define XXH_PUBLIC_API static __inline +# else +# define XXH_PUBLIC_API static /* this version may generate warnings for unused static functions; disable the relevant warning */ +# endif +#else +# define XXH_PUBLIC_API /* do nothing */ +#endif /* XXH_PRIVATE_API */ + +/*!XXH_NAMESPACE, aka Namespace Emulation : + +If you want to include _and expose_ xxHash functions from within your own library, +but also want to avoid symbol collisions with another library which also includes xxHash, + +you can use XXH_NAMESPACE, to automatically prefix any public symbol from xxhash library +with the value of XXH_NAMESPACE (so avoid to keep it NULL and avoid numeric values). + +Note that no change is required within the calling program as long as it includes `xxhash.h` : +regular symbol name will be automatically translated by this header. +*/ +#ifdef XXH_NAMESPACE +# define XXH_CAT(A,B) A##B +# define XXH_NAME2(A,B) XXH_CAT(A,B) +# define XXH32 XXH_NAME2(XXH_NAMESPACE, XXH32) +# define XXH64 XXH_NAME2(XXH_NAMESPACE, XXH64) +# define XXH_versionNumber XXH_NAME2(XXH_NAMESPACE, XXH_versionNumber) +# define XXH32_createState XXH_NAME2(XXH_NAMESPACE, XXH32_createState) +# define XXH64_createState XXH_NAME2(XXH_NAMESPACE, XXH64_createState) +# define XXH32_freeState XXH_NAME2(XXH_NAMESPACE, XXH32_freeState) +# define XXH64_freeState XXH_NAME2(XXH_NAMESPACE, XXH64_freeState) +# define XXH32_reset XXH_NAME2(XXH_NAMESPACE, XXH32_reset) +# define XXH64_reset XXH_NAME2(XXH_NAMESPACE, XXH64_reset) +# define XXH32_update XXH_NAME2(XXH_NAMESPACE, XXH32_update) +# define XXH64_update XXH_NAME2(XXH_NAMESPACE, XXH64_update) +# define XXH32_digest XXH_NAME2(XXH_NAMESPACE, XXH32_digest) +# define XXH64_digest XXH_NAME2(XXH_NAMESPACE, XXH64_digest) +# define XXH32_copyState XXH_NAME2(XXH_NAMESPACE, XXH32_copyState) +# define XXH64_copyState XXH_NAME2(XXH_NAMESPACE, XXH64_copyState) +#endif + + +/* ************************************* +* Version +***************************************/ +#define XXH_VERSION_MAJOR 0 +#define XXH_VERSION_MINOR 6 +#define XXH_VERSION_RELEASE 1 +#define XXH_VERSION_NUMBER (XXH_VERSION_MAJOR *100*100 + XXH_VERSION_MINOR *100 + XXH_VERSION_RELEASE) +XXH_PUBLIC_API unsigned XXH_versionNumber (void); + + +/* **************************** +* Simple Hash Functions +******************************/ +typedef unsigned int XXH32_hash_t; +typedef unsigned long long XXH64_hash_t; + +XXH_PUBLIC_API XXH32_hash_t XXH32 (const void* input, size_t length, unsigned int seed); +XXH_PUBLIC_API XXH64_hash_t XXH64 (const void* input, size_t length, unsigned long long seed); + +/*! +XXH32() : + Calculate the 32-bits hash of sequence "length" bytes stored at memory address "input". + The memory between input & input+length must be valid (allocated and read-accessible). + "seed" can be used to alter the result predictably. + Speed on Core 2 Duo @ 3 GHz (single thread, SMHasher benchmark) : 5.4 GB/s +XXH64() : + Calculate the 64-bits hash of sequence of length "len" stored at memory address "input". + "seed" can be used to alter the result predictably. + This function runs 2x faster on 64-bits systems, but slower on 32-bits systems (see benchmark). +*/ + + +/* **************************** +* Streaming Hash Functions +******************************/ +typedef struct XXH32_state_s XXH32_state_t; /* incomplete type */ +typedef struct XXH64_state_s XXH64_state_t; /* incomplete type */ + +/*! State allocation, compatible with dynamic libraries */ + +XXH_PUBLIC_API XXH32_state_t* XXH32_createState(void); +XXH_PUBLIC_API XXH_errorcode XXH32_freeState(XXH32_state_t* statePtr); + +XXH_PUBLIC_API XXH64_state_t* XXH64_createState(void); +XXH_PUBLIC_API XXH_errorcode XXH64_freeState(XXH64_state_t* statePtr); + + +/* hash streaming */ + +XXH_PUBLIC_API XXH_errorcode XXH32_reset (XXH32_state_t* statePtr, unsigned int seed); +XXH_PUBLIC_API XXH_errorcode XXH32_update (XXH32_state_t* statePtr, const void* input, size_t length); +XXH_PUBLIC_API XXH32_hash_t XXH32_digest (const XXH32_state_t* statePtr); + +XXH_PUBLIC_API XXH_errorcode XXH64_reset (XXH64_state_t* statePtr, unsigned long long seed); +XXH_PUBLIC_API XXH_errorcode XXH64_update (XXH64_state_t* statePtr, const void* input, size_t length); +XXH_PUBLIC_API XXH64_hash_t XXH64_digest (const XXH64_state_t* statePtr); + +/* +These functions generate the xxHash of an input provided in multiple segments. +Note that, for small input, they are slower than single-call functions, due to state management. +For small input, prefer `XXH32()` and `XXH64()` . + +XXH state must first be allocated, using XXH*_createState() . + +Start a new hash by initializing state with a seed, using XXH*_reset(). + +Then, feed the hash state by calling XXH*_update() as many times as necessary. +Obviously, input must be allocated and read accessible. +The function returns an error code, with 0 meaning OK, and any other value meaning there is an error. + +Finally, a hash value can be produced anytime, by using XXH*_digest(). +This function returns the nn-bits hash as an int or long long. + +It's still possible to continue inserting input into the hash state after a digest, +and generate some new hashes later on, by calling again XXH*_digest(). + +When done, free XXH state space if it was allocated dynamically. +*/ + + +/* ************************** +* Utils +****************************/ +#if !(defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) /* ! C99 */ +# define restrict /* disable restrict */ +#endif + +XXH_PUBLIC_API void XXH32_copyState(XXH32_state_t* restrict dst_state, const XXH32_state_t* restrict src_state); +XXH_PUBLIC_API void XXH64_copyState(XXH64_state_t* restrict dst_state, const XXH64_state_t* restrict src_state); + + +/* ************************** +* Canonical representation +****************************/ +typedef struct { unsigned char digest[4]; } XXH32_canonical_t; +typedef struct { unsigned char digest[8]; } XXH64_canonical_t; + +XXH_PUBLIC_API void XXH32_canonicalFromHash(XXH32_canonical_t* dst, XXH32_hash_t hash); +XXH_PUBLIC_API void XXH64_canonicalFromHash(XXH64_canonical_t* dst, XXH64_hash_t hash); + +XXH_PUBLIC_API XXH32_hash_t XXH32_hashFromCanonical(const XXH32_canonical_t* src); +XXH_PUBLIC_API XXH64_hash_t XXH64_hashFromCanonical(const XXH64_canonical_t* src); + +/* Default result type for XXH functions are primitive unsigned 32 and 64 bits. +* The canonical representation uses human-readable write convention, aka big-endian (large digits first). +* These functions allow transformation of hash result into and from its canonical format. +* This way, hash values can be written into a file / memory, and remain comparable on different systems and programs. +*/ + + +#ifdef XXH_STATIC_LINKING_ONLY + +/* ================================================================================================ + This section contains definitions which are not guaranteed to remain stable. + They could change in a future version, becoming incompatible with a different version of the library. + They shall only be used with static linking. +=================================================================================================== */ + +/* These definitions allow allocating XXH state statically (on stack) */ + + struct XXH32_state_s { + unsigned long long total_len; + unsigned seed; + unsigned v1; + unsigned v2; + unsigned v3; + unsigned v4; + unsigned mem32[4]; /* buffer defined as U32 for alignment */ + unsigned memsize; + }; /* typedef'd to XXH32_state_t */ + + struct XXH64_state_s { + unsigned long long total_len; + unsigned long long seed; + unsigned long long v1; + unsigned long long v2; + unsigned long long v3; + unsigned long long v4; + unsigned long long mem64[4]; /* buffer defined as U64 for alignment */ + unsigned memsize; + }; /* typedef'd to XXH64_state_t */ + + +# ifdef XXH_PRIVATE_API +# include "xxhash.c" /* include xxhash functions as `static`, for inlining */ +# endif + +#endif /* XXH_STATIC_LINKING_ONLY */ + + +#if defined (__cplusplus) +} +#endif + +#endif /* XXHASH_H_5627135585666179 */ diff --git a/lib/zlib b/lib/zlib new file mode 160000 index 000000000..508932916 --- /dev/null +++ b/lib/zlib @@ -0,0 +1 @@ +Subproject commit 50893291621658f355bc5b4d450a8d06a563053d diff --git a/res/icon/axes.png b/res/icon/axes.png new file mode 100644 index 000000000..e7c442567 Binary files /dev/null and b/res/icon/axes.png differ diff --git a/res/icon/bulb-off.png b/res/icon/bulb-off.png new file mode 100644 index 000000000..08725194a Binary files /dev/null and b/res/icon/bulb-off.png differ diff --git a/res/icon/bulb.png b/res/icon/bulb.png new file mode 100644 index 000000000..0dd9e0898 Binary files /dev/null and b/res/icon/bulb.png differ diff --git a/res/icon/button_play.png b/res/icon/button_play.png deleted file mode 100644 index 1055fdc6e..000000000 Binary files a/res/icon/button_play.png and /dev/null differ diff --git a/res/icon/cloud.png b/res/icon/cloud.png new file mode 100644 index 000000000..1d33e064b Binary files /dev/null and b/res/icon/cloud.png differ diff --git a/res/icon/collapse.png b/res/icon/collapse.png new file mode 100644 index 000000000..d2cf8555f Binary files /dev/null and b/res/icon/collapse.png differ diff --git a/res/icon/collision-ball.png b/res/icon/collision-ball.png new file mode 100644 index 000000000..2d67d04ae Binary files /dev/null and b/res/icon/collision-ball.png differ diff --git a/res/icon/constraint.png b/res/icon/constraint.png new file mode 100644 index 000000000..e840a6ea5 Binary files /dev/null and b/res/icon/constraint.png differ diff --git a/res/icon/cube-front-16.png b/res/icon/cube-front-16.png new file mode 100644 index 000000000..e7175505b Binary files /dev/null and b/res/icon/cube-front-16.png differ diff --git a/res/icon/cube-side-16.png b/res/icon/cube-side-16.png new file mode 100644 index 000000000..2e8a859bd Binary files /dev/null and b/res/icon/cube-side-16.png differ diff --git a/res/icon/cube-top-16.png b/res/icon/cube-top-16.png new file mode 100644 index 000000000..6843f24f7 Binary files /dev/null and b/res/icon/cube-top-16.png differ diff --git a/res/icon/cubemap.png b/res/icon/cubemap.png new file mode 100644 index 000000000..8ec02121e Binary files /dev/null and b/res/icon/cubemap.png differ diff --git a/res/icon/expand.png b/res/icon/expand.png new file mode 100644 index 000000000..41d4650f3 Binary files /dev/null and b/res/icon/expand.png differ diff --git a/res/icon/flip.png b/res/icon/flip.png new file mode 100644 index 000000000..6fa4df48a Binary files /dev/null and b/res/icon/flip.png differ diff --git a/res/icon/glow.png b/res/icon/glow.png new file mode 100644 index 000000000..352231edb Binary files /dev/null and b/res/icon/glow.png differ diff --git a/res/icon/handle.png b/res/icon/handle.png new file mode 100644 index 000000000..97c022088 Binary files /dev/null and b/res/icon/handle.png differ diff --git a/res/icon/hidden-disabled.png b/res/icon/hidden-disabled.png new file mode 100644 index 000000000..4ba22aa03 Binary files /dev/null and b/res/icon/hidden-disabled.png differ diff --git a/res/icon/hidden.png b/res/icon/hidden.png new file mode 100644 index 000000000..ebfcca06c Binary files /dev/null and b/res/icon/hidden.png differ diff --git a/res/icon/light-rot-horizontal.png b/res/icon/light-rot-horizontal.png new file mode 100644 index 000000000..207ae784b Binary files /dev/null and b/res/icon/light-rot-horizontal.png differ diff --git a/res/icon/light-rot-vertical.png b/res/icon/light-rot-vertical.png new file mode 100644 index 000000000..7a48f5862 Binary files /dev/null and b/res/icon/light-rot-vertical.png differ diff --git a/res/icon/load-dark.png b/res/icon/load-dark.png new file mode 100644 index 000000000..8fad8d1aa Binary files /dev/null and b/res/icon/load-dark.png differ diff --git a/res/icon/load-view.png b/res/icon/load-view.png new file mode 100644 index 000000000..5e3888e3c Binary files /dev/null and b/res/icon/load-view.png differ diff --git a/res/icon/load-view2.png b/res/icon/load-view2.png new file mode 100644 index 000000000..85feda28f Binary files /dev/null and b/res/icon/load-view2.png differ diff --git a/res/icon/load.png b/res/icon/load.png new file mode 100644 index 000000000..5f64e2cce Binary files /dev/null and b/res/icon/load.png differ diff --git a/res/icon/marker.png b/res/icon/marker.png new file mode 100644 index 000000000..124cf2bd0 Binary files /dev/null and b/res/icon/marker.png differ diff --git a/res/icon/node.png b/res/icon/node.png new file mode 100644 index 000000000..99808a5b3 Binary files /dev/null and b/res/icon/node.png differ diff --git a/res/icon/normals.png b/res/icon/normals.png new file mode 100644 index 000000000..4d8cd0e89 Binary files /dev/null and b/res/icon/normals.png differ diff --git a/res/icon/orthographic.png b/res/icon/orthographic.png new file mode 100644 index 000000000..3fb658314 Binary files /dev/null and b/res/icon/orthographic.png differ diff --git a/res/icon/pause.png b/res/icon/pause.png new file mode 100644 index 000000000..c03c7ed8d Binary files /dev/null and b/res/icon/pause.png differ diff --git a/res/icon/perspective.png b/res/icon/perspective.png new file mode 100644 index 000000000..a40ed7f81 Binary files /dev/null and b/res/icon/perspective.png differ diff --git a/res/icon/play.png b/res/icon/play.png new file mode 100644 index 000000000..0ba5f48c9 Binary files /dev/null and b/res/icon/play.png differ diff --git a/res/icon/redo.png b/res/icon/redo.png new file mode 100644 index 000000000..2d86e0e36 Binary files /dev/null and b/res/icon/redo.png differ diff --git a/res/icon/repeat-all.png b/res/icon/repeat-all.png new file mode 100644 index 000000000..b9448252f Binary files /dev/null and b/res/icon/repeat-all.png differ diff --git a/res/icon/repeat-single.png b/res/icon/repeat-single.png new file mode 100644 index 000000000..485f5a51a Binary files /dev/null and b/res/icon/repeat-single.png differ diff --git a/res/icon/save-view.png b/res/icon/save-view.png new file mode 100644 index 000000000..9e3793dc8 Binary files /dev/null and b/res/icon/save-view.png differ diff --git a/res/icon/save-view2.png b/res/icon/save-view2.png new file mode 100644 index 000000000..b1da5ab99 Binary files /dev/null and b/res/icon/save-view2.png differ diff --git a/res/icon/save.png b/res/icon/save.png new file mode 100644 index 000000000..9bccc4adf Binary files /dev/null and b/res/icon/save.png differ diff --git a/res/icon/screenshot.png b/res/icon/screenshot.png new file mode 100644 index 000000000..9709a429b Binary files /dev/null and b/res/icon/screenshot.png differ diff --git a/res/icon/select-object.png b/res/icon/select-object.png new file mode 100644 index 000000000..f6fd10fda Binary files /dev/null and b/res/icon/select-object.png differ diff --git a/res/icon/select-verts.png b/res/icon/select-verts.png new file mode 100644 index 000000000..d4321c192 Binary files /dev/null and b/res/icon/select-verts.png differ diff --git a/res/icon/silhouette.png b/res/icon/silhouette.png new file mode 100644 index 000000000..ecf3b6767 Binary files /dev/null and b/res/icon/silhouette.png differ diff --git a/res/icon/sizegrip.png b/res/icon/sizegrip.png new file mode 100644 index 000000000..06346ccea Binary files /dev/null and b/res/icon/sizegrip.png differ diff --git a/res/icon/skinned.png b/res/icon/skinned.png new file mode 100644 index 000000000..928cfc70b Binary files /dev/null and b/res/icon/skinned.png differ diff --git a/res/icon/specular.png b/res/icon/specular.png new file mode 100644 index 000000000..355d563cb Binary files /dev/null and b/res/icon/specular.png differ diff --git a/res/icon/sun.png b/res/icon/sun.png new file mode 100644 index 000000000..c3c5292b7 Binary files /dev/null and b/res/icon/sun.png differ diff --git a/res/icon/textures.png b/res/icon/textures.png new file mode 100644 index 000000000..807d06270 Binary files /dev/null and b/res/icon/textures.png differ diff --git a/res/icon/undo.png b/res/icon/undo.png new file mode 100644 index 000000000..fc5741b22 Binary files /dev/null and b/res/icon/undo.png differ diff --git a/res/icon/vertex-colors.png b/res/icon/vertex-colors.png new file mode 100644 index 000000000..25686b1fe Binary files /dev/null and b/res/icon/vertex-colors.png differ diff --git a/res/icon/view-active.png b/res/icon/view-active.png new file mode 100644 index 000000000..d01b72e76 Binary files /dev/null and b/res/icon/view-active.png differ diff --git a/res/lang/NifSkope_de.ts b/res/lang/NifSkope_de.ts index a4a784fe5..9cb085a4b 100644 --- a/res/lang/NifSkope_de.ts +++ b/res/lang/NifSkope_de.ts @@ -39,52 +39,52 @@ Ungültige Größe für Array - + Name Name - + Type Typ - + Value Wert - + Argument Argument - + Array1 Array 1 - + Array2 Array 2 - + Condition Bedingung - + since seit - + until bis - + Version Condition @@ -196,177 +196,192 @@ GLView - + Top Top - + View from above Ansicht von oben - + Front Front - + View from the front Ansicht von vorne - + Side Seite - + View from the side Ansicht von der Seite - + Walk Gehen - + Enable walk mode "Gehen"-Modus aktivieren - + User Benutzer - + Restore the view as it was when Save User View was activated Die Einstellungen des "Benutzer"-Modus wiederherstellen - + Flip Umkehren - + Flip View from Front to Back, Top to Bottom, Side to Other Side Die Ansichten umkehren: Front zu Rückseite, Oben zu Unten, Seite zu anderer Seite - + Perspective Perspektivisch - + Perspective View Transformation or Orthogonal View Transformation Perspektivische Ansicht oder Orthogonale Ansicht - + Save User View Benutzer-Ansicht speichern - + Save current view rotation, position and distance Speichert die momentane Rotation, Position und Entfernung - + Save View To File... - + + Color Key Debug + + + + &Animations &Animationen - + enables evaluation of animation controllers Aktiviert die Verarbeitung von Animation Controllern - + &Play Ab&spielen - + &Loop Sch&leife - + &Switch &Wechseln - + Animation Animation - + Render View - + &Render &Rendern - - glview.cpp - GL ERROR (init) : + + NifSkope Directory - - glview.cpp - GL ERROR (paint): + + Save to NifSkope screenshots directory - - File + + NIF Directory - - Use View Size + + Save to NIF file directory - - Width + + Auto - - Height + + JPEG Quality - - Automatic + + Save - - Quality + + glview.cpp - GL ERROR (init) : - - OK + + glview.cpp - GL ERROR (paint): - + + Save View + + + + + File + + + + Cancel Abbrechen @@ -458,134 +473,45 @@ KfmModel - + version - + not supported yet - + this is not a KFM - + failed to load kfm file (%1) - + failed to write kfm file Fehler beim Schreiben von KFM in Datei - + array size mismatch - + error: couldn't open xml description file: %1 - - KfmXmlHandler - - - error maximum nesting level exceeded - - - - - error unknown element - - - - - this is not a niftoolsxml file - - - - - expected compound or version got %1 instead - - - - - invalid version string - - - - - compound %1 is already registered as internal type - - - - - version tag must not contain any sub tags - - - - - add needs at least name and type attributes - - - - - only add tags allowed in compound type declaration - - - - - error unhandled tag %1 in %2 - - - - - - mismatching end element tag for element - - - - - invalid %1 declaration: name is empty - - - - - compound type %1 referes to unknown type %2 - - - - - compound type %1 refers to unknown template type %2 - - - - - compound type %1 contains itself - - - - - Syntax error - - - - - XML parse error (line %1):<br> - - - NifBlockEditor - + Accept Ãœbernehmen @@ -593,14 +519,14 @@ NifIStream - + <string too long (0x%1)> - - - + + + <string too long> @@ -608,17 +534,17 @@ NifMatrix4Edit - + Translation Translation - + Rotation Rotation - + Scale Skalierung @@ -626,186 +552,186 @@ NifModel - + Unsupported 'Startup Version' %1 specified, reverting to 20.0.0.5 - + array %1 much too large. %2 bytes requested - + array %1 invalid - + unknown block %1 - - + + NifModel::reorderBlocks() - invalid argument - + unknown ancestor %1 - + <offset invalid> - + <palette not found> - - - + + + %1 - <index invalid> - + %1 <invalid> - + None - + dec: %1 hex: 0x%2 - + float: %1 hex: 0x%2 - + dec: %1 hex: 0x%2 bin: 0b%3 - + this is not a NIF - + version %1 (%2) is not supported yet - + invalid header string - + failed to load file header (version %1, %2) - - + + unexpected EOF during load - + non-zero block separator (%1) preceeding block %2 - - - + + + next block does not start with a NiString - - + + Unknown NiDataStream - - + + failed to load block number %1 (%2) previous block was %3 - + warning: block %1 (%2) not inserted! - - + + encountered unknown block (%1) - + device position incorrect after block number %1 (%2) at 0x%3 ended at 0x%4 (expected 0x%5) - + failed to reposition device at block number %1 (%2) previous block was %3 - + failed to load file footer - + failed to write block %1(%2) - + failed to open file - + failed to load file header (version %1) - - + + block %1 %2 array size mismatch - + infinite recursive link construct detected %1 -> %2 - + blocktype %1 and %2 are not related @@ -818,27 +744,27 @@ bin: 0b%3 NifProxyModel - + infinite recursing link construct detected - + NifProxyModel::mapTo() called with wrong model - + NifProxyModel::mapFrom() called with wrong model - + NifProxyModel::mapFrom() called with wrong ref model - + NifProxyModel::mapFrom() plural called with wrong model @@ -846,27 +772,27 @@ bin: 0b%3 NifSkope - + &Auto Sanitize before Save &Automatisches Validieren - + Reload &XML &XML neu einlesen - + &Reload XML + Nif XML + Nif neu &einlesen - + &New Window &Neues Fenster - + &Quit &Beenden @@ -879,39 +805,39 @@ bin: 0b%3 Hierarchie - + Select Font ... Schriftart wählen ... - + About &NifSkope Ãœber &NifSkope - + About &Qt Ãœber &Qt - + Resource Files Resource-Dateien - - + + Block List Blockliste - - + + Block Details Blockdetails - + KFM KFM @@ -920,22 +846,22 @@ bin: 0b%3 Laden und Speichern - + &Load... &Laden... - + &File &Datei - + &View &Ansicht - + &Toolbars &Toolbars @@ -952,7 +878,7 @@ bin: 0b%3 Fehler beim Lesen von KFM aus Datei - + loading nif... Nif wird geladen... @@ -961,12 +887,12 @@ bin: 0b%3 Fehler beim Lesen von Nif aus Datei - + failed to write kfm file Fehler beim Schreiben von KFM in Datei - + failed to write nif file Fehler beim Schreiben von Nif in Datei @@ -1003,674 +929,512 @@ bin: 0b%3 - + XML Checker - + Show Blocks in List - + Show Blocks in Tree - + Hide Version Mismatched Rows - + Realtime Row Version Updating (slow) - + NifSkope Documentation && &Tutorials - + NifSkope Help && Bug Report &Forum - + NifTools &Wiki - + NifTools &Downloads - + Interactive Help - + Inspect - + Load && Save - + &Save As... - + View - + tView - + Reset Block Details - + + tLOD + + + + Import Import - + Export Export - + &Help - + failed to load kfm from '%1' - + failed to load nif from '%1' - + NifSkope must be restarted for this setting to take full effect. - - NifXmlHandler - - - error maximum nesting level exceeded - - - - - error unknown element '%1' - - - - - this is not a niftoolsxml file - - - - - - failed to register alias %1 for type %2 - - - - - compound %1 is already registered as internal type - - - - - compound and niblocks must have a name - - - - - multiple declarations of %1 - - - - - forward declaration of block id %1 - - - - - basic definition must have a name and a nifskopetype - - - - - enum definition must have a name and a known storage type - - - - - failed to register alias %1 for enum type %2 - - - - - invalid version tag - - - - - expected basic, enum, compound, niobject or version got %1 instead - - - - - only add tags allowed in compound type declaration - - - - - add needs at least name and type attributes - - - - - only add tags allowed in block declaration - - - - - option defintion must have a name and a value - - - - - option value error (only integers please) - - - - - only option tags allowed in enum declaration - - - - - error unhandled tag %1 - - - - - - mismatching end element tag for element %1 - - - - - invalid %1 declaration: name is empty - - - - - failed to register enum option - - - - - compound type %1 refers to unknown type %2 - - - - - compound type %1 refers to unknown template type %2 - - - - - compound type %1 contains itself - - - - - niobject %1 inherits unknown ancestor %2 - - - - - niobject %1 inherits itself - - - - - niobject %1 refers to unknown type %2 - - - - - niobject %1 refers to unknown template type %2 - - - - - XML parse error (line %1):<br> - - - Options - + Settings - + Draw &Axes - + draw xyz-Axes - + Draw &Nodes - + draw bones/nodes - + Draw &Havok - + draw the havok shapes - + Draw &Constraints - + draw the havok constraints - + Draw &Furniture - + draw the furniture markers - + Show Hid&den - + always draw nodes and meshes - + Show S&tats - + display some statistics about the selected node - + &Settings... - + show the settings dialog - + General - + Regional and Language Settings - + Misc. Settings - + Startup Version - + This is the version that the initial 'blank' NIF file that is created when NifSkope opens will be. - + &Rendering - + Texture Folders - + Auto Detect - + Auto Detect Game Paths - + Custom - + Add Folder - + Remove Folder - + Move Up - + Move Down - + &Look for alternatives - + If a texture was nowhere to be found<br>NifSkope will start looking for alternatives.<p style='white-space:pre'>texture.dds does not exist -> use texture.bmp instead</p> - + Render - + &Anti Aliasing - + Enable anti aliasing and anisotropic texture filtering if available.<br>You'll need to restart NifSkope for this setting to take effect.<br> - + &Textures - + Enable textures - + &Shaders - + Enable Shaders - + Up Axis - + X - + Y - + Z - + Culling - + Cull &Non Textured - + Hide all meshes without textures - + &Cull Nodes by Name - + Enabling this option hides some special nodes and meshes - + Enter a regular expression. Nodes which names match the expression will be hidden - - + + Colors - + Light - - + + Ambient - - + + Diffuse - - + + Specular - + Frontal - + Lock light to camera position - + Position - + Declination - + Planar Angle - + Presets - + Sunny Day - + Dark Night - + Background - + Foreground - + Highlight - + Materials - + Material Overrides - + Emissive - + Enable Material Color Overrides - + Override colors used on Materials - + Export Export - + Export Settings - + Use 'Cull Nodes by Name' rendering option to cull nodes on export - - - - <p>The texture folder was not found.</p><p>This may be because you haven't extracted the archive files yet.<br><a href='http://cs.elderscrolls.com/constwiki/index.php/BSA_Unpacker_Tutorial'>Here</a>, it is explained how to do that.</p> - - - - + No supported games were detected. Your game may still work, you will just have to set the folders manually until an auto-detect routine is created. - + Successfully detected the following games: - + Choose a folder @@ -1686,7 +1450,7 @@ Your game may still work, you will just have to set the folders manually until a QMessageBox - + Fatal Error Schwerer Fehler @@ -1694,7 +1458,7 @@ Your game may still work, you will just have to set the folders manually until a Quat - + W %1 X %2 Y %3 @@ -1718,27 +1482,27 @@ Z %4 ScalingDialog - + Enter scaling factors - + Uniform scaling - + Enter translation amounts - + OK - + Cancel Abbrechen @@ -1752,8 +1516,8 @@ Z %4 - - + + Animation Animation @@ -1797,13 +1561,13 @@ Z %4 Die folgenden Controlled Nodes wurden in der Nif-Datei nicht gefunden: - - + + array %1 not found Array %1 nicht gefunden - + Convert Quat- to ZYX-Rotations Konvertiere Quat- nach ZYX-Rotation @@ -1858,7 +1622,7 @@ Z %4 - + @@ -1873,108 +1637,108 @@ Z %4 - + Edit UV UV bearbeiten - + Add Base Texture - + Add Dark Map - + Add Detail Map - + Add Glow Map - + Add Bump Map - + Add Decal 0 Map - + Add Decal 1 Map - + Add Decal 2 Map - + Add Decal 3 Map - + Export Template - + Multi Apply Mode - + Info - - + + Export texture - + Embed - + Add Textures - + Remove Texture - + OK - + Choose texture file(s) - + Edit Flip Controller - + Add Flip Controller @@ -2038,7 +1802,7 @@ Z %4 OBJ Mesh exportieren - + Export Export @@ -2060,23 +1824,23 @@ Z %4 Branch entfernen - + - - - - - - - - - - - + + + + + + + + + + + Block Block @@ -2115,18 +1879,18 @@ Z %4 - - - - + + + + - - - - - - - + + + + + + + Cancel Abbrechen @@ -2141,51 +1905,51 @@ Z %4 Tangent Space updaten - + Apply Transformation Transformation übernehmen - + On animated and or skinned nodes Apply Transformation most likely won't work the way you expected it. Für animierte und/oder geskinnte Nodes wird das Ãœbernehmen der Transformation wahrscheinlich fehlschlagen. - + Try anyway Trotzdem weiter - + Clear Abbrechen - - - - - + + + + + Transform Transformieren - + Insert - + Attach Property - + - + Node @@ -2211,13 +1975,13 @@ Z %4 - + Copy Kopieren - + Paste Einfügen @@ -2229,17 +1993,17 @@ Z %4 - + Nif versions differ!<br><br>Current File Version: %1<br>Clipboard Data Version: %2<br><br>The results will be unpredictable... - - - - + + + + Continue @@ -2251,121 +2015,121 @@ Z %4 - - + + Copy Branch - - + + parent link invalid - - + + parent unnamed - - + + failed to map parent link %1 %2 for block %3 %4; %5. - - + + failed to save block %1 %2. - - - + + + Paste Branch - - + + failed to map parent link %1 - + Paste At End - + Flatten Branch - - + + Move Up - - + + Move Down - + Remove By Id - + Remove Blocks by Id - + Enter a regular expression: - + Crop To Branch - + Convert - + Duplicate - - - - + + + + Duplicate Branch - + Sort By Name - + Attach Parent Node - + Edit Bearbeiten @@ -2395,7 +2159,7 @@ Z %4 - + Zero @@ -2445,519 +2209,519 @@ Z %4 - - - + + + Always - - - + + + Less - - - + + + Equal - - - + + + Less or Equal - - - + + + Greater - - - + + + Not Equal - - - + + + Greater or Equal - - - + + + Never - + Enable Blending - + Source Blend Mode - + Destination Blend Mode - + Enable Testing - + Alpha Test Function - + Alpha Test Threshold - + No Sorter - - - + + + Hidden - - - + + + None - - - + + + Triangles - - - + + + Bounding Box - - - + + + Collision Detection - + Skin Influence - - + + Active - - + + Cycle - - + + Reverse - - + + Clamp - - + + Loop Mode - + Linked - + No Collision - + Scaled - + Part Number - + Shadow - + Enable Z Buffer - + Z Buffer Read Only - + Z Buffer Test Function - - + + Set Flags also - + Enable Havok - + Enable Animation - + Bit %1 - + Always Face Camera - + Rotate About Up - + Rigid Face Camera - + Always Face Center - + Billboard Mode - + Rigid Face Center - + Stencil Enable - + Keep - + Replace - + Increment - + Decrement - + Invert - + Fail Action - + Z Fail Action - + Pass Action - + Counter clock wise or Both - + Draw counter clock wise - + Draw clock wise - + Draw Both - + Draw Mode - + Stencil Function - - + + Emissive - + Emissive + Ambient + Diffuse - + Lighting Mode - + Source Ignore - + Source Emissive - + Source Ambient/Diffuse - + Vertex Mode - + Ambient - + Diffuse - + Specular - + Target Color - + Clamp Both - + Clamp S Wrap T - + Wrap S Clamp T - + Wrap Both - + Clamp Mode - + FILTER_NEAREST - + FILTER_BILERP - + FILTER_TRILERP - + FILTER_NEAREST_MIPNEAREST - + FILTER_NEAREST_MIPLERP - + FILTER_BILERP_MIPNEAREST - + Filter Mode - + Accept Ãœbernehmen - + Enable Collision - + Is Skeleton Nif (?) - + FlameNodes Present - + EditorMarkers Present - + Pack Strips - - - - - + + + + + Havok - + Create Convex Shape - + Enter the maximum roundoff error to use - + Larger values will give a less precise but better performing hull - + Created hull with %1 vertices, %2 normals - + A -> B - + Calculate Spring Length - + no mesh data was found @@ -3005,247 +2769,253 @@ Z %4 - + Update MOPP Code - + unable to locate the NifMopp.dll library - + only bhkPackedNiTriStripsShape can be used with bhkMoppBvTreeShape Mopp code at this time - + need vertices and faces to calculate mopp code - + failed to generate mopp code - + Update All MOPP Code - - - - + + + + Batch - + Combine Properties - - - - + + + + Optimize - + Split Properties - + Remove Bogus Nodes - + removed %1 nodes - + Combine Shapes - + Fix Bip01 - + Scan Bip01 - + Make Skin Partition - + bad NiSkinData - vertex count does not match - + bad NiSkinData - some vertices have no weights at all - + reduced %1 vertices to %2 bone influences (maximum number of bones per vertex was %3) - + removed %1 bone influences - + Make All Skin Partitions - + did %1 partitions - + <b>Number of Bones per Vertex</b><br>Hint: Most games use 4 bones per vertex<br>Note: If the mesh contains vertices which are<br>influenced by more than x bones the number of<br>influences will be reduced for these vertices<br> - + <b>Number of Bones per Partition</b><br>Hint: Oblivion uses 18 bones pp<br>CivIV (non shader meshes) 4 bones pp<br>CivIV (shader enabled meshes) 18 bones pp<br>Note: To fit the triangles into the partitions<br>some bone influences may be removed again. - + <b>Whether or not to stripify the triangles in each partition.</b><br>Hint: Morrowind and Freedom force do not support strips.<br>Strips generally perform faster, if the game supports them. - + <b>Whether or not to pad partitions that will have fewer bones than specified above.</b><br>Hint: Freedom Force seems to require this, but it doesn't seem to affect other games. - + Mirror armature - + Mirror Armature - + Do you wish to flip or delete animation? - + Flip Umkehren - + Delete - - - - - - + + + + + + Ok - + Fix Bone Bounds - - - - + + + + Skeleton - + need vertices, normals, texture coordinates and faces to calculate tangents and bitangents - + Update All Tangent Spaces - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + Texture - + Scale Vertices - + Scale Normals - + Scale Skalierung - + Choose + Color + + + Set All + + Fix Geometry Data Names @@ -3253,11 +3023,11 @@ Z %4 - - - - - + + + + + Sanitize @@ -3267,8 +3037,8 @@ Z %4 - - + + Select a string or enter a new one @@ -3298,92 +3068,92 @@ Z %4 - + Reorder Link Arrays - + Collapse Link Arrays - + Adjust Texture Sources - + Reorder Blocks - + Check Links - + Edit String Offset - + Entries in the string palette - + Enter a pair of regular expressions to search and replace. - + See <a href='%1'>%2</a> for syntax. - + Search: - + Replace: - + Preview - + Replace Entries - + String Palette - + Cannot find string palette - + Updated %1 offsets in %2 sequences - + Edit String Palettes - + Select an animation sequence to edit the string palette for @@ -3491,7 +3261,7 @@ Z %4 - + %1 files in %2 seconds @@ -3499,17 +3269,17 @@ Z %4 TestThread - + invalid link - + link - + points to wrong block type @@ -3522,62 +3292,62 @@ Z %4 Der UV Editor konnte keine Texturdaten laden. - + UV Editor UV Editor - + Select &All - + Select &None - + Select &Faces - + Select &Connected - + &Scale and Translate Selected - + &Rotate Selected - + Texture Alpha Blending - + Select Coordinate Set - + Select Texture Slot - + Enter rotation angle - + Duplicate current @@ -3594,10 +3364,220 @@ length %4 Vector4 - + X %1 Y %2 Z %3 W %4 length %5 + + final + + + + error maximum nesting level exceeded + + + + + error unknown element + + + + + + this is not a niftoolsxml file + + + + + expected compound or version got %1 instead + + + + + invalid version string + + + + + + compound %1 is already registered as internal type + + + + + version tag must not contain any sub tags + + + + + + add needs at least name and type attributes + + + + + + only add tags allowed in compound type declaration + + + + + error unhandled tag %1 in %2 + + + + + + mismatching end element tag for element + + + + + + invalid %1 declaration: name is empty + + + + + compound type %1 referes to unknown type %2 + + + + + + compound type %1 refers to unknown template type %2 + + + + + + compound type %1 contains itself + + + + + Syntax error + + + + + + XML parse error (line %1):<br> + + + + + error unknown element '%1' + + + + + + failed to register alias %1 for type %2 + + + + + compound and niblocks must have a name + + + + + multiple declarations of %1 + + + + + forward declaration of block id %1 + + + + + basic definition must have a name and a nifskopetype + + + + + enum definition must have a name and a known storage type + + + + + failed to register alias %1 for enum type %2 + + + + + invalid version tag + + + + + expected basic, enum, compound, niobject or version got %1 instead + + + + + only add tags allowed in block declaration + + + + + option defintion must have a name and a value + + + + + option value error (only integers please) + + + + + only option tags allowed in enum declaration + + + + + error unhandled tag %1 + + + + + + mismatching end element tag for element %1 + + + + + failed to register enum option + + + + + compound type %1 refers to unknown type %2 + + + + + niobject %1 inherits unknown ancestor %2 + + + + + niobject %1 inherits itself + + + + + niobject %1 refers to unknown type %2 + + + + + niobject %1 refers to unknown template type %2 + + + diff --git a/res/lang/NifSkope_fr.ts b/res/lang/NifSkope_fr.ts index e0d1adfb7..f328d16db 100644 --- a/res/lang/NifSkope_fr.ts +++ b/res/lang/NifSkope_fr.ts @@ -39,52 +39,52 @@ Taille de tableau invalide - + Name Nom - + Type Type - + Value Valeur - + Argument Argument - + Array1 Tableau1 - + Array2 Tableau2 - + Condition Condition - + since depuis - + until jusqu'à - + Version Condition Condition de version @@ -196,172 +196,187 @@ GLView - + Top Dessus - + View from above Vue de dessus - + Front Face - + View from the front Vue de face - + Side Côté - + View from the side Vue de côté - + User Utilisateur - + Restore the view as it was when Save User View was activated Restaurer la vue utilisateur sauvegardée - + Walk Marcher - + Enable walk mode Activer le mode Marcher - + Flip Inverser - + Flip View from Front to Back, Top to Bottom, Side to Other Side Passer la vue de Face en vue de Dos, celle de Dessus en celle de Dessous et celle de Côté en celle du côté opposé - + Perspective Perspective - + Perspective View Transformation or Orthogonal View Transformation Transformation en Vue Perspective ou en Vue Orthogonale - + Save User View Sauvegarder la vue utilisateur - + Save current view rotation, position and distance Sauvegarder la rotation, la position et la distance de la vue actuelle - + Save View To File... - + + Color Key Debug + + + + &Animations &Animations - + enables evaluation of animation controllers Activer l'évaluation des contrôleurs d'animation - + &Play &Jouer - + &Loop &Boucler - + &Switch &Inverser - + Animation Animation - + Render View - + glview.cpp - GL ERROR (init) : - + glview.cpp - GL ERROR (paint): - + + Save View + + + + File - - Use View Size + + NifSkope Directory - - Width + + Save to NifSkope screenshots directory - - Height + + NIF Directory - - Automatic + + Save to NIF file directory - - Quality + + Auto - - OK + + JPEG Quality - + + Save + + + + Cancel Annuler @@ -370,7 +385,7 @@ Vue - + &Render &Rendu @@ -462,134 +477,45 @@ KfmModel - + version - + not supported yet - + this is not a KFM - + failed to load kfm file (%1) - + failed to write kfm file Impossible d'écrire dans le fichier KFM - + array size mismatch - + error: couldn't open xml description file: %1 - - KfmXmlHandler - - - error maximum nesting level exceeded - - - - - error unknown element - - - - - this is not a niftoolsxml file - - - - - expected compound or version got %1 instead - - - - - invalid version string - - - - - compound %1 is already registered as internal type - - - - - version tag must not contain any sub tags - - - - - add needs at least name and type attributes - - - - - only add tags allowed in compound type declaration - - - - - error unhandled tag %1 in %2 - - - - - - mismatching end element tag for element - - - - - invalid %1 declaration: name is empty - - - - - compound type %1 referes to unknown type %2 - - - - - compound type %1 refers to unknown template type %2 - - - - - compound type %1 contains itself - - - - - Syntax error - - - - - XML parse error (line %1):<br> - - - NifBlockEditor - + Accept Accepter @@ -597,14 +523,14 @@ NifIStream - + <string too long (0x%1)> - - - + + + <string too long> @@ -612,17 +538,17 @@ NifMatrix4Edit - + Translation Translation - + Rotation Rotation - + Scale Echelle @@ -630,186 +556,186 @@ NifModel - + Unsupported 'Startup Version' %1 specified, reverting to 20.0.0.5 - + array %1 much too large. %2 bytes requested - + array %1 invalid - + unknown block %1 - - + + NifModel::reorderBlocks() - invalid argument - + unknown ancestor %1 - + <offset invalid> - + <palette not found> - - - + + + %1 - <index invalid> - + %1 <invalid> - + None - + dec: %1 hex: 0x%2 - + float: %1 hex: 0x%2 - + dec: %1 hex: 0x%2 bin: 0b%3 - + this is not a NIF - + version %1 (%2) is not supported yet - + invalid header string - + failed to load file header (version %1, %2) - - + + unexpected EOF during load - + non-zero block separator (%1) preceeding block %2 - - - + + + next block does not start with a NiString - - + + Unknown NiDataStream - - + + failed to load block number %1 (%2) previous block was %3 - + warning: block %1 (%2) not inserted! - - + + encountered unknown block (%1) - + device position incorrect after block number %1 (%2) at 0x%3 ended at 0x%4 (expected 0x%5) - + failed to reposition device at block number %1 (%2) previous block was %3 - + failed to load file footer - + failed to write block %1(%2) - + failed to open file - + failed to load file header (version %1) - - + + block %1 %2 array size mismatch - + infinite recursive link construct detected %1 -> %2 - + blocktype %1 and %2 are not related @@ -822,27 +748,27 @@ bin: 0b%3 NifProxyModel - + infinite recursing link construct detected - + NifProxyModel::mapTo() called with wrong model - + NifProxyModel::mapFrom() called with wrong model - + NifProxyModel::mapFrom() called with wrong ref model - + NifProxyModel::mapFrom() plural called with wrong model @@ -878,129 +804,134 @@ bin: 0b%3 A propos de Nifskope - + &Auto Sanitize before Save Nettoyage &automatique avant sauvegarde - + Reload &XML Recharger &XML - + &Reload XML + Nif &Recharger XML + Nif - + &New Window &Nouvelle fenêtre - + XML Checker Vérificateur XML - + &Quit &Quitter - + Show Blocks in List Afficher les blocs en liste - + Show Blocks in Tree Afficher les blocs en arbre - + Hide Version Mismatched Rows - + Realtime Row Version Updating (slow) - + Select Font ... Choix d'une police ... - + NifSkope Documentation && &Tutorials Documentation && &Tutoriels de NifSkope - + NifSkope Help && Bug Report &Forum Aide de Nifskope && report de bug - + NifTools &Wiki &Wiki de NifTools - + NifTools &Downloads &Téléchargement de NifTools - + About &NifSkope A propos de &NifSkope - + About &Qt A propos de &Qt - + Resource Files Fichiers de ressource - + Interactive Help Aide interactive - - + + Block List Liste du bloc - - + + Block Details Détails du bloc - + KFM KFM - + Inspect Inspecter - + Load && Save - + + tLOD + + + + NifSkope must be restarted for this setting to take full effect. @@ -1009,644 +940,477 @@ bin: 0b%3 Charger & sauver - + &Load... &Charger... - + &Save As... Enregistrer &sous... - + View Vue - + tView - + Reset Block Details - + &File &Fichier - + Import Importer - + Export Exporter - + &View &Vue - + &Toolbars Barre d'ou&tils - + &Help &Aide - + failed to load kfm from '%1' Impossible de charger un KFM depuis '%1' - + loading nif... Chargement du nif... - + failed to load nif from '%1' Chargement de nif impossible depuis '%1' - + failed to write kfm file Impossible d'écrire dans le fichier KFM - + failed to write nif file Impossible d'écrire dans le fichier Nif - - NifXmlHandler - - - error maximum nesting level exceeded - - - - - error unknown element '%1' - - - - - this is not a niftoolsxml file - - - - - - failed to register alias %1 for type %2 - - - - - compound %1 is already registered as internal type - - - - - compound and niblocks must have a name - - - - - multiple declarations of %1 - - - - - forward declaration of block id %1 - - - - - basic definition must have a name and a nifskopetype - - - - - enum definition must have a name and a known storage type - - - - - failed to register alias %1 for enum type %2 - - - - - invalid version tag - - - - - expected basic, enum, compound, niobject or version got %1 instead - - - - - only add tags allowed in compound type declaration - - - - - add needs at least name and type attributes - - - - - only add tags allowed in block declaration - - - - - option defintion must have a name and a value - - - - - option value error (only integers please) - - - - - only option tags allowed in enum declaration - - - - - error unhandled tag %1 - - - - - - mismatching end element tag for element %1 - - - - - invalid %1 declaration: name is empty - - - - - failed to register enum option - - - - - compound type %1 refers to unknown type %2 - - - - - compound type %1 refers to unknown template type %2 - - - - - compound type %1 contains itself - - - - - niobject %1 inherits unknown ancestor %2 - - - - - niobject %1 inherits itself - - - - - niobject %1 refers to unknown type %2 - - - - - niobject %1 refers to unknown template type %2 - - - - - XML parse error (line %1):<br> - - - Options - + Settings - + Draw &Axes - + draw xyz-Axes - + Draw &Nodes - + draw bones/nodes - + Draw &Havok - + draw the havok shapes - + Draw &Constraints - + draw the havok constraints - + Draw &Furniture - + draw the furniture markers - + Show Hid&den - + always draw nodes and meshes - + Show S&tats - + display some statistics about the selected node - + &Settings... - + show the settings dialog - + General Général - + Regional and Language Settings - + Misc. Settings - + Startup Version - + This is the version that the initial 'blank' NIF file that is created when NifSkope opens will be. - + &Rendering - + Texture Folders - + Auto Detect - + Auto Detect Game Paths - + Custom - + Add Folder - + Remove Folder - + Move Up - + Move Down - + &Look for alternatives - + If a texture was nowhere to be found<br>NifSkope will start looking for alternatives.<p style='white-space:pre'>texture.dds does not exist -> use texture.bmp instead</p> - + Render - + &Anti Aliasing - + Enable anti aliasing and anisotropic texture filtering if available.<br>You'll need to restart NifSkope for this setting to take effect.<br> - + &Textures - + Enable textures - + &Shaders - + Enable Shaders - + Up Axis - + X - + Y - + Z - + Culling - + Cull &Non Textured - + Hide all meshes without textures - + &Cull Nodes by Name - + Enabling this option hides some special nodes and meshes - + Enter a regular expression. Nodes which names match the expression will be hidden - - + + Colors Couleurs - + Light - - + + Ambient - - + + Diffuse - - + + Specular - + Frontal - + Lock light to camera position - + Position - + Declination - + Planar Angle - + Presets - + Sunny Day - + Dark Night - + Background - + Foreground - + Highlight - + Materials Matériaux - + Material Overrides - + Emissive - + Enable Material Color Overrides - + Override colors used on Materials - + Export Exporter - + Export Settings - + Use 'Cull Nodes by Name' rendering option to cull nodes on export - - - - <p>The texture folder was not found.</p><p>This may be because you haven't extracted the archive files yet.<br><a href='http://cs.elderscrolls.com/constwiki/index.php/BSA_Unpacker_Tutorial'>Here</a>, it is explained how to do that.</p> - - - - + No supported games were detected. Your game may still work, you will just have to set the folders manually until an auto-detect routine is created. - + Successfully detected the following games: - + Choose a folder @@ -1662,7 +1426,7 @@ Your game may still work, you will just have to set the folders manually until a QMessageBox - + Fatal Error Erreur fatale @@ -1670,7 +1434,7 @@ Your game may still work, you will just have to set the folders manually until a Quat - + W %1 X %2 Y %3 @@ -1694,27 +1458,27 @@ Z %4 ScalingDialog - + Enter scaling factors - + Uniform scaling - + Enter translation amounts - + OK - + Cancel Annuler @@ -1756,8 +1520,8 @@ Z %4 - - + + Animation Animation @@ -1801,13 +1565,13 @@ Z %4 Les noeuds de contrôle suivants n'ont pas été trouvés dans le Nif: - - + + array %1 not found Tableau %1 introuvable - + Convert Quat- to ZYX-Rotations Conversion Quat- -> ZYX-Rotations @@ -1817,23 +1581,23 @@ Z %4 Supprimer la branche - + - - - - - - - - - - - + + + + + + + + + + + Block Bloc @@ -1850,7 +1614,7 @@ Z %4 - + Zero @@ -1900,460 +1664,460 @@ Z %4 - - - + + + Always - - - + + + Less - - - + + + Equal - - - + + + Less or Equal - - - + + + Greater - - - + + + Not Equal - - - + + + Greater or Equal - - - + + + Never - + Enable Blending - + Source Blend Mode - + Destination Blend Mode - + Enable Testing - + Alpha Test Function - + Alpha Test Threshold - + No Sorter - - - + + + Hidden - - - + + + None - - - + + + Triangles - - - + + + Bounding Box - - - - + + + + Collision Detection - + Skin Influence - - + + Active - - + + Cycle - - + + Reverse - - + + Clamp - - + + Loop Mode - + Linked - + No Collision - + Scaled - + Part Number - + Shadow - + Enable Z Buffer - + Z Buffer Read Only - + Z Buffer Test Function - - + + Set Flags also - + Enable Havok - + Enable Animation Animation activée - + Bit %1 - + Always Face Camera - + Rotate About Up - + Rigid Face Camera - + Always Face Center - + Billboard Mode - + Rigid Face Center - + Stencil Enable - + Keep - + Replace - + Increment - + Decrement - + Invert - + Fail Action - + Z Fail Action - + Pass Action - + Counter clock wise or Both - + Draw counter clock wise - + Draw clock wise - + Draw Both - + Draw Mode - + Stencil Function - - + + Emissive - + Emissive + Ambient + Diffuse - + Lighting Mode - + Source Ignore - + Source Emissive - + Source Ambient/Diffuse - + Vertex Mode - + Ambient - + Diffuse - + Specular - + Target Color - + Clamp Both - + Clamp S Wrap T - + Wrap S Clamp T - + Wrap Both - + Clamp Mode - + FILTER_NEAREST - + FILTER_BILERP - + FILTER_TRILERP - + FILTER_NEAREST_MIPNEAREST - + FILTER_NEAREST_MIPLERP - + FILTER_BILERP_MIPNEAREST - + Filter Mode - + Accept Accepter - + Enable Collision Collision activée - + Is Skeleton Nif (?) Est-ce un squelette Nif (?) @@ -2362,61 +2126,61 @@ Z %4 Drapeau non-identifié (?) - + FlameNodes Present Présence de FlameNodes - + EditorMarkers Present Présence de EditorMakers - + Pack Strips - - - - - + + + + + Havok Havok - + Create Convex Shape - + Enter the maximum roundoff error to use - + Larger values will give a less precise but better performing hull - + Created hull with %1 vertices, %2 normals - + A -> B - + Calculate Spring Length Calculer la longueur du ressort - + no mesh data was found Données de maillage introuvables @@ -2435,7 +2199,7 @@ Z %4 - + @@ -2528,7 +2292,7 @@ Z %4 Offset du fichier - + Update MOPP Code Mise-à-jour du code MOPP @@ -2537,35 +2301,35 @@ Z %4 Code - + unable to locate the NifMopp.dll library Bibliothèque NifMopp.dll introuvable - + only bhkPackedNiTriStripsShape can be used with bhkMoppBvTreeShape Mopp code at this time Seul un bhkPackedNiTriStripsShape peut être utilisé dans un code Mopp de bhkMoppBvTreeShape pour le moment - + need vertices and faces to calculate mopp code Le code Mopp nécessite des sommets et des faces - + failed to generate mopp code Impossible de générer le code Mopp - + Update All MOPP Code Mise-à-jour du code Mopp intégral - - - - + + + + Batch Batch @@ -2603,18 +2367,18 @@ Z %4 - - - - + + + + - - - - - - - + + + + + + + Cancel Annuler @@ -2624,159 +2388,159 @@ Z %4 Normaliser - + Combine Properties Combiner les propriétées - - - - + + + + Optimize Optimiser - + Split Properties Séparer les propriétées - + Remove Bogus Nodes Supprimer les noeuds à problème - + removed %1 nodes - + Combine Shapes Combiner les formes - + Fix Bip01 - + Scan Bip01 - + Make Skin Partition Partitionner la Skin - + bad NiSkinData - vertex count does not match Mauvais NiSkinData - le nombre de sommets ne correspond pas - + bad NiSkinData - some vertices have no weights at all Mauvais NiSkinData - certains sommets n'ont aucun poids - + reduced %1 vertices to %2 bone influences (maximum number of bones per vertex was %3) %1 sommets réduits à %2 liens d'os (le nombre maximal d'os par sommet était %3) - + removed %1 bone influences Suppression de %1 liens d'os - + Make All Skin Partitions Partitionner toute les Skins - + did %1 partitions %1 partitions faites - + <b>Number of Bones per Vertex</b><br>Hint: Most games use 4 bones per vertex<br>Note: If the mesh contains vertices which are<br>influenced by more than x bones the number of<br>influences will be reduced for these vertices<br> <b>Nombre d'os par sommet</b><br>Conseil: la plupart des jeux utilisent 4 os par sommet<br>Note: si le maillage contient des sommets qui sont<br>liés à plus que x os, le nombre de<br>liens sera réduit pour ces sommets<br> - + <b>Number of Bones per Partition</b><br>Hint: Oblivion uses 18 bones pp<br>CivIV (non shader meshes) 4 bones pp<br>CivIV (shader enabled meshes) 18 bones pp<br>Note: To fit the triangles into the partitions<br>some bone influences may be removed again. <b>Nombre d'os par partition</b><br>Conseil: Oblivion utilise 18 os par partition<br>CivIV (sans maillage de Shader) 4 os par partition<br>CivIV (avec maillage de Shader) 18 os par partition<br>Note: pour ajuster les triangles dans les partitions<br>certains liens d'os peuvent encore être supprimés. - + <b>Whether or not to stripify the triangles in each partition.</b><br>Hint: Morrowind and Freedom force do not support strips.<br>Strips generally perform faster, if the game supports them. <b>Stripifier ou non les triangles dans chaque partition.</b><br>Conseil: Morrowind et Freedom Force n'acceptent pas la Stripification.<br>En général, le Strip accélère le chargement pour les jeux qui le supporte. - + <b>Whether or not to pad partitions that will have fewer bones than specified above.</b><br>Hint: Freedom Force seems to require this, but it doesn't seem to affect other games. <b>Protéger ou non les partitions qui ont moins d'os que ce qui a été précisé.</b><br>Conseil: Freedom Force semble le demander mais il ne semble pas que cela affecte les autres jeux. - + Mirror armature - + Mirror Armature - + Do you wish to flip or delete animation? - + Flip Inverser - + Delete - - - - - - + + + + + + Ok Ok - + Fix Bone Bounds Fixer les limites des os - - - - + + + + Skeleton Squelette - + need vertices, normals, texture coordinates and faces to calculate tangents and bitangents Pour calculer les tangentes et les binormales, il faut des sommets, des normales, des faces et des coordonnées de texture - + Update All Tangent Spaces Mise-à-jour de tous les espaces tangents @@ -2786,183 +2550,183 @@ Z %4 Mise-à-jour de l'espace tangent - + Edit UV Editer UV - + Add Base Texture - + Add Dark Map - + Add Detail Map - + Add Glow Map - + Add Bump Map - + Add Decal 0 Map - + Add Decal 1 Map - + Add Decal 2 Map - + Add Decal 3 Map - + Export Template - + Multi Apply Mode - + Info - + Export Exporter - - + + Export texture - + Embed - + Add Textures - + Remove Texture - + OK - + Choose texture file(s) - + Edit Flip Controller - + Add Flip Controller - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + Texture Texture - + Apply Transformation Appliquer Transformation - + On animated and or skinned nodes Apply Transformation most likely won't work the way you expected it. Noeuds animés ou skinnés: appliquer les transformations imprévisibles. - + Try anyway Essayer - + Clear Effacer - - - - - + + + + + Transform Transformer - + Insert - + Attach Property - + - + Node @@ -2988,13 +2752,13 @@ Z %4 - + Copy Copier - + Paste Coller @@ -3006,17 +2770,17 @@ Z %4 - + Nif versions differ!<br><br>Current File Version: %1<br>Clipboard Data Version: %2<br><br>The results will be unpredictable... - - - - + + + + Continue @@ -3028,121 +2792,121 @@ Z %4 - - + + Copy Branch - - + + parent link invalid - - + + parent unnamed - - + + failed to map parent link %1 %2 for block %3 %4; %5. - - + + failed to save block %1 %2. - - - + + + Paste Branch - - + + failed to map parent link %1 - + Paste At End - + Flatten Branch - - + + Move Up - - + + Move Down - + Remove By Id - + Remove Blocks by Id - + Enter a regular expression: - + Crop To Branch - + Convert - + Duplicate - - - - + + + + Duplicate Branch - + Sort By Name - + Attach Parent Node - + Edit Editer @@ -3152,17 +2916,17 @@ Z %4 - + Scale Vertices Echelle des sommets - + Scale Normals - + Scale Echelle @@ -3173,15 +2937,21 @@ Z %4 - + Choose + Color + + + Set All + + Fix Geometry Data Names @@ -3189,11 +2959,11 @@ Z %4 - - - - - + + + + + Sanitize @@ -3203,8 +2973,8 @@ Z %4 - - + + Select a string or enter a new one @@ -3234,92 +3004,92 @@ Z %4 - + Reorder Link Arrays - + Collapse Link Arrays - + Adjust Texture Sources - + Reorder Blocks - + Check Links - + Edit String Offset - + Entries in the string palette - + Enter a pair of regular expressions to search and replace. - + See <a href='%1'>%2</a> for syntax. - + Search: - + Replace: - + Preview - + Replace Entries - + String Palette - + Cannot find string palette - + Updated %1 offsets in %2 sequences - + Edit String Palettes - + Select an animation sequence to edit the string palette for @@ -3427,7 +3197,7 @@ Z %4 - + %1 files in %2 seconds @@ -3435,17 +3205,17 @@ Z %4 TestThread - + invalid link - + link - + points to wrong block type @@ -3458,62 +3228,62 @@ Z %4 Impossible de charger les données de texture pour l'éditeur UV. - + UV Editor Editeur UV - + Select &All Sélectionner &Tout - + Select &None Sélectionner &Rien - + Select &Faces Sélectionner &Faces - + Select &Connected Sélectionner &Connexion - + &Scale and Translate Selected - + &Rotate Selected - + Texture Alpha Blending Mêler la texture alpha - + Select Coordinate Set - + Select Texture Slot - + Enter rotation angle - + Duplicate current @@ -3530,12 +3300,222 @@ length %4 Vector4 - + X %1 Y %2 Z %3 W %4 length %5 + + final + + + + error maximum nesting level exceeded + + + + + error unknown element + + + + + + this is not a niftoolsxml file + + + + + expected compound or version got %1 instead + + + + + invalid version string + + + + + + compound %1 is already registered as internal type + + + + + version tag must not contain any sub tags + + + + + + add needs at least name and type attributes + + + + + + only add tags allowed in compound type declaration + + + + + error unhandled tag %1 in %2 + + + + + + mismatching end element tag for element + + + + + + invalid %1 declaration: name is empty + + + + + compound type %1 referes to unknown type %2 + + + + + + compound type %1 refers to unknown template type %2 + + + + + + compound type %1 contains itself + + + + + Syntax error + + + + + + XML parse error (line %1):<br> + + + + + error unknown element '%1' + + + + + + failed to register alias %1 for type %2 + + + + + compound and niblocks must have a name + + + + + multiple declarations of %1 + + + + + forward declaration of block id %1 + + + + + basic definition must have a name and a nifskopetype + + + + + enum definition must have a name and a known storage type + + + + + failed to register alias %1 for enum type %2 + + + + + invalid version tag + + + + + expected basic, enum, compound, niobject or version got %1 instead + + + + + only add tags allowed in block declaration + + + + + option defintion must have a name and a value + + + + + option value error (only integers please) + + + + + only option tags allowed in enum declaration + + + + + error unhandled tag %1 + + + + + + mismatching end element tag for element %1 + + + + + failed to register enum option + + + + + compound type %1 refers to unknown type %2 + + + + + niobject %1 inherits unknown ancestor %2 + + + + + niobject %1 inherits itself + + + + + niobject %1 refers to unknown type %2 + + + + + niobject %1 refers to unknown template type %2 + + + spStiffSpringHelper diff --git a/res/nifskope.qrc b/res/nifskope.qrc index d0d695b00..ebf95edb1 100644 --- a/res/nifskope.qrc +++ b/res/nifskope.qrc @@ -1,28 +1,68 @@ - - - - nifskope.png - skel.dat - - - icon/arrow_left.png - icon/arrow_right.png + + + nifskope.png + skel.dat + + + icon/arrow_left.png + icon/arrow_right.png icon/img_update.png icon/img_link.png icon/img_flag.png - icon/havok_logo.png - icon/qhull_cone.gif + icon/havok_logo.png + icon/qhull_cone.gif + icon/handle.png + icon/collapse.png + icon/expand.png + icon/sizegrip.png - icon/button_play.png - icon/button_loop.png + icon/button_loop.png icon/button_switch.png - icon/button_view_top.png - icon/button_view_front.png - icon/button_view_side.png icon/button_view_walk.png - icon/button_view_user.png - icon/button_view_flip.png - icon/button_view_pers.png + icon/cube-top-16.png + icon/cube-front-16.png + icon/cube-side-16.png + icon/flip.png + icon/orthographic.png + icon/perspective.png + icon/axes.png + icon/collision-ball.png + icon/node.png + icon/constraint.png + icon/save-view.png + icon/load-view.png + icon/view-active.png + icon/screenshot.png + icon/save.png + icon/load.png + icon/save-view2.png + icon/load-view2.png + icon/load-dark.png + icon/bulb.png + icon/bulb-off.png + icon/textures.png + icon/vertex-colors.png + icon/pause.png + icon/play.png + icon/repeat-all.png + icon/repeat-single.png + icon/normals.png + icon/light-rot-horizontal.png + icon/light-rot-vertical.png + icon/silhouette.png + icon/specular.png + icon/glow.png + icon/marker.png + icon/undo.png + icon/redo.png + icon/sun.png + icon/cloud.png + icon/cubemap.png + icon/hidden.png + icon/hidden-disabled.png + icon/select-object.png + icon/select-verts.png + icon/skinned.png diff --git a/res/shaders/black.dds b/res/shaders/black.dds new file mode 100644 index 000000000..37a7341d4 Binary files /dev/null and b/res/shaders/black.dds differ diff --git a/res/shaders/blankdetailmap.dds b/res/shaders/blankdetailmap.dds new file mode 100644 index 000000000..d53ee9b5b Binary files /dev/null and b/res/shaders/blankdetailmap.dds differ diff --git a/res/shaders/default_n.dds b/res/shaders/default_n.dds new file mode 100644 index 000000000..66d9afa49 Binary files /dev/null and b/res/shaders/default_n.dds differ diff --git a/res/shaders/fo4_default.frag b/res/shaders/fo4_default.frag new file mode 100644 index 000000000..3463ab8f8 --- /dev/null +++ b/res/shaders/fo4_default.frag @@ -0,0 +1,314 @@ +#version 130 + +uniform sampler2D BaseMap; +uniform sampler2D NormalMap; +uniform sampler2D GlowMap; +uniform sampler2D BacklightMap; +uniform sampler2D SpecularMap; +uniform sampler2D GreyscaleMap; + +uniform vec3 specColor; +uniform float specStrength; +uniform float specGlossiness; // "Smoothness" in FO4; 0-1 +uniform float fresnelPower; + +uniform float paletteScale; + +uniform vec3 glowColor; +uniform float glowMult; + +uniform float alpha; + +uniform vec3 tintColor; + +uniform vec2 uvScale; +uniform vec2 uvOffset; + +uniform bool hasEmit; +uniform bool hasGlowMap; +uniform bool hasSoftlight; +uniform bool hasBacklight; +uniform bool hasRimlight; +uniform bool hasTintColor; +uniform bool hasSpecularMap; +uniform bool greyscaleColor; +uniform bool doubleSided; + +uniform float subsurfaceRolloff; +uniform float rimPower; +uniform float backlightPower; + +uniform mat4 worldMatrix; + +in vec3 LightDir; +in vec3 ViewDir; + +in vec4 A; +in vec4 C; +in vec4 D; + +in vec3 N; +in vec3 t; +in vec3 b; + +#ifndef M_PI + #define M_PI 3.1415926535897932384626433832795 +#endif + +#define FLT_EPSILON 1.192092896e-07F // smallest such that 1.0 + FLT_EPSILON != 1.0 + +float OrenNayar( vec3 L, vec3 V, vec3 N, float roughness, float NdotL ) +{ + //float NdotL = dot(N, L); + float NdotV = dot(N, V); + float LdotV = dot(L, V); + + float rough2 = roughness * roughness; + + float A = 1.0 - 0.5 * (rough2 / (rough2 + 0.57)); + float B = 0.45 * (rough2 / (rough2 + 0.09)); + + float a = min( NdotV, NdotL ); + float b = max( NdotV, NdotL ); + b = (sign(b) == 0.0) ? FLT_EPSILON : sign(b) * max( 0.01, abs(b) ); // For fudging the smoothness of C + float C = sqrt( (1.0 - a * a) * (1.0 - b * b) ) / b; + + float gamma = LdotV - NdotL * NdotV; + float L1 = A + B * max( gamma, FLT_EPSILON ) * C; + + return L1 * max( NdotL, FLT_EPSILON ); +} + +float OrenNayarFull( vec3 L, vec3 V, vec3 N, float roughness, float NdotL ) +{ + //float NdotL = dot(N, L); + float NdotV = dot(N, V); + float LdotV = dot(L, V); + + float angleVN = acos(max(NdotV, FLT_EPSILON)); + float angleLN = acos(max(NdotL, FLT_EPSILON)); + + float alpha = max(angleVN, angleLN); + float beta = min(angleVN, angleLN); + float gamma = LdotV - NdotL * NdotV; + + float roughnessSquared = roughness * roughness; + float roughnessSquared9 = (roughnessSquared / (roughnessSquared + 0.09)); + + // C1, C2, and C3 + float C1 = 1.0 - 0.5 * (roughnessSquared / (roughnessSquared + 0.33)); + float C2 = 0.45 * roughnessSquared9; + + if( gamma >= 0.0 ) { + C2 *= sin(alpha); + } else { + C2 *= (sin(alpha) - pow((2.0 * beta) / M_PI, 3.0)); + } + + float powValue = (4.0 * alpha * beta) / (M_PI * M_PI); + float C3 = 0.125 * roughnessSquared9 * powValue * powValue; + + // Avoid asymptote at pi/2 + float asym = M_PI / 2.0; + float lim1 = asym + 0.01; + float lim2 = asym - 0.01; + + float ab2 = (alpha + beta) / 2.0; + + if ( beta >= asym && beta < lim1 ) + beta = lim1; + else if ( beta < asym && beta >= lim2 ) + beta = lim2; + + if ( ab2 >= asym && ab2 < lim1 ) + ab2 = lim1; + else if ( ab2 < asym && ab2 >= lim2 ) + ab2 = lim2; + + // Reflection + float A = gamma * C2 * tan(beta); + float B = (1.0 - abs(gamma)) * C3 * tan(ab2); + + float L1 = max(FLT_EPSILON, NdotL) * (C1 + A + B); + + // Interreflection + float twoBetaPi = 2.0 * beta / M_PI; + float L2 = 0.17 * max(FLT_EPSILON, NdotL) * (roughnessSquared / (roughnessSquared + 0.13)) * (1.0 - gamma * twoBetaPi * twoBetaPi); + + return L1 + L2; +} + +// Schlick's Fresnel approximation +float fresnelSchlick( float VdotH, float F0 ) +{ + float base = 1.0 - VdotH; + float exp = pow( base, fresnelPower ); + return clamp( exp + F0 * (1.0 - exp), 0.0, 1.0 ); +} + +// The Torrance-Sparrow visibility factor, G +float VisibDiv( float NdotL, float NdotV, float VdotH, float NdotH ) +{ + float denom = max( VdotH, FLT_EPSILON ); + float numL = min( NdotV, NdotL ); + float numR = 2.0 * NdotH; + if ( denom >= (numL * numR) ) { + numL = (numL == NdotV) ? 1.0 : (NdotL / NdotV); + return (numL * numR) / denom; + } + return 1.0 / NdotV; +} + +// this is a normalized Phong model used in the Torrance-Sparrow model +vec3 TorranceSparrow(float NdotL, float NdotH, float NdotV, float VdotH, vec3 color, float power, float F0) +{ + // D: Normalized phong model + float D = ((power + 2.0) / (2.0 * M_PI)) * pow( NdotH, power ); + + // G: Torrance-Sparrow visibility term divided by NdotV + float G_NdotV = VisibDiv( NdotL, NdotV, VdotH, NdotH ); + + // F: Schlick's approximation + float F = fresnelSchlick( VdotH, F0 ); + + // Torrance-Sparrow: + // (F * G * D) / (4 * NdotL * NdotV) + // Division by NdotV is done in VisibDiv() + // and division by NdotL is removed since + // outgoing radiance is determined by: + // BRDF * NdotL * L() + float spec = (F * G_NdotV * D) / 4.0; + + return color * spec * M_PI; +} + +vec3 tonemap(vec3 x) +{ + float _A = 0.15; + float _B = 0.50; + float _C = 0.10; + float _D = 0.20; + float _E = 0.02; + float _F = 0.30; + + return ((x*(_A*x+_C*_B)+_D*_E)/(x*(_A*x+_B)+_D*_F))-_E/_F; +} + +vec4 colorLookup( float x, float y ) { + + return texture2D( GreyscaleMap, vec2( clamp(x, 0.0, 1.0), clamp(y, 0.0, 1.0) ) ); +} + +void main( void ) +{ + vec2 offset = gl_TexCoord[0].st * uvScale + uvOffset; + + vec4 baseMap = texture2D( BaseMap, offset ); + vec4 normalMap = texture2D( NormalMap, offset ); + vec4 specMap = texture2D( SpecularMap, offset ); + vec4 glowMap = texture2D( GlowMap, offset ); + + vec3 normal = normalize(normalMap.rgb * 2.0 - 1.0); + if ( !gl_FrontFacing && doubleSided ) { + normal *= -1.0; + } + // For _msn (Test with FSF1_Face) + //normal.z = sqrt( 1.0 - dot( normal.xy, normal.xy ) ); + + vec3 L = normalize(LightDir); + vec3 V = normalize(ViewDir); + vec3 R = reflect(-L, normal); + vec3 H = normalize( L + V ); + + float NdotL = dot(normal, L); + float NdotL0 = max( NdotL, FLT_EPSILON ); + float NdotH = max( dot(normal, H), FLT_EPSILON ); + float NdotV = max( dot(normal, V), FLT_EPSILON ); + float VdotH = max( dot(V, H), FLT_EPSILON ); + float NdotNegL = max( dot(normal, -L), FLT_EPSILON ); + + vec4 color; + vec3 albedo = baseMap.rgb * C.rgb; + vec3 diffuse = A.rgb + D.rgb * NdotL0; + if ( greyscaleColor ) { + vec4 luG = colorLookup( baseMap.g, paletteScale - (1 - C.r) ); + + albedo = luG.rgb; + } + + // Emissive + vec3 emissive = vec3(0.0); + if ( hasEmit ) { + emissive += glowColor * glowMult; + + if ( hasGlowMap ) { + emissive *= glowMap.rgb; + } + } + + // Specular + float g = 1.0; + float s = 1.0; + float smoothness = 1.0; + float roughness = 0.0; + float specMask = 1.0; + vec3 spec = vec3(0.0); + if ( hasSpecularMap ) { + g = specMap.r; + s = specMap.g; + smoothness = g * specGlossiness; + roughness = 1.0 - smoothness; + float fSpecularPower = exp2( smoothness * 10 + 1 ); + specMask = s * specStrength; + + spec = TorranceSparrow( NdotL0, NdotH, NdotV, VdotH, vec3(specMask), fSpecularPower, 0.2 ) * NdotL0 * D.rgb * specColor; + } + + vec3 backlight = vec3(0.0); + if ( backlightPower > 0.0 ) { + backlight = albedo * NdotNegL * clamp( backlightPower, 0.0, 1.0 ); + + emissive += backlight * D.rgb; + } + + vec3 rim = vec3(0.0); + if ( hasRimlight ) { + rim = vec3(pow((1.0 - NdotV), rimPower)); + rim *= smoothstep( -0.2, 1.0, dot(-L, V) ); + + //emissive += rim * D.rgb * specMask; + } + + // Diffuse + float diff = OrenNayarFull( L, V, normal, roughness, NdotL ); + diffuse = vec3(diff); + + vec3 soft = vec3(0.0); + float wrap = NdotL; + if ( hasSoftlight || subsurfaceRolloff > 0.0 ) { + wrap = (wrap + subsurfaceRolloff) / (1.0 + subsurfaceRolloff); + soft = albedo * max( 0.0, wrap ) * smoothstep( 1.0, 0.0, sqrt(diff) ); + + diffuse += soft; + } + + if ( hasTintColor ) { + albedo *= tintColor; + } + + // Diffuse + color.rgb = diffuse * albedo * D.rgb; + // Ambient + color.rgb += A.rgb * albedo; + // Specular + color.rgb += spec; + color.rgb += A.rgb * specMask * fresnelSchlick( VdotH, 0.2 ) * (1.0 - NdotV) * D.rgb; + // Emissive + color.rgb += emissive; + + color.rgb = tonemap( color.rgb ) / tonemap( vec3(1.0) ); + color.a = C.a * baseMap.a; + + gl_FragColor = color; + gl_FragColor.a *= alpha; +} diff --git a/res/shaders/fo4_default.prog b/res/shaders/fo4_default.prog new file mode 100644 index 000000000..7680325f0 --- /dev/null +++ b/res/shaders/fo4_default.prog @@ -0,0 +1,28 @@ +# default shader + +checkgroup begin or + # Fallout 4 and later + checkgroup begin and + check HEADER/Version >= 0x14020007 + check HEADER/User Version >= 12 + check HEADER/User Version 2 >= 130 + checkgroup end +checkgroup end + +checkgroup begin and + check BSLightingShaderProperty + check BSLightingShaderProperty/Skyrim Shader Type != 1 + check BSLightingShaderProperty/Skyrim Shader Type != 16 + check NiAVObject/Vertex Size >= 3 + checkgroup begin or + check BSTriShape + check BSSubIndexTriShape + check BSMeshLODTriShape + checkgroup end +checkgroup end + +texcoords 0 base +texcoords 1 tangents +texcoords 2 bitangents + +shaders fo4_default.vert fo4_default.frag diff --git a/res/shaders/fo4_default.vert b/res/shaders/fo4_default.vert new file mode 100644 index 000000000..62e989684 --- /dev/null +++ b/res/shaders/fo4_default.vert @@ -0,0 +1,38 @@ +#version 130 + +out vec3 LightDir; +out vec3 ViewDir; + +out vec3 N; +out vec3 t; +out vec3 b; +out vec3 v; + +out vec4 A; +out vec4 C; +out vec4 D; + + +void main( void ) +{ + gl_Position = ftransform(); + gl_TexCoord[0] = gl_MultiTexCoord0; + + N = normalize(gl_NormalMatrix * gl_Normal); + t = normalize(gl_NormalMatrix * gl_MultiTexCoord1.xyz); + b = normalize(gl_NormalMatrix * gl_MultiTexCoord2.xyz); + + // NOTE: b<->t + mat3 tbnMatrix = mat3(t.x, b.x, N.x, + t.y, b.y, N.y, + t.z, b.z, N.z); + + v = vec3(gl_ModelViewMatrix * gl_Vertex); + + ViewDir = tbnMatrix * -v.xyz; + LightDir = tbnMatrix * gl_LightSource[0].position.xyz; + + A = gl_LightSource[0].ambient; + C = gl_Color; + D = gl_LightSource[0].diffuse; +} diff --git a/res/shaders/fo4_effectshader.frag b/res/shaders/fo4_effectshader.frag new file mode 100644 index 000000000..e432426bf --- /dev/null +++ b/res/shaders/fo4_effectshader.frag @@ -0,0 +1,142 @@ +#version 130 + +uniform sampler2D SourceTexture; +uniform sampler2D GreyscaleMap; +uniform samplerCube CubeMap; +uniform sampler2D NormalMap; +uniform sampler2D SpecularMap; + +uniform bool doubleSided; + +uniform bool hasSourceTexture; +uniform bool hasGreyscaleMap; +uniform bool hasCubeMap; +uniform bool hasNormalMap; +uniform bool hasEnvMask; + +uniform bool greyscaleAlpha; +uniform bool greyscaleColor; + +uniform bool useFalloff; +uniform bool vertexColors; +uniform bool vertexAlpha; + +uniform bool hasWeaponBlood; + +uniform vec4 glowColor; +uniform float glowMult; + +uniform vec2 uvScale; +uniform vec2 uvOffset; + +uniform vec4 falloffParams; +uniform float falloffDepth; + +uniform float lightingInfluence; +uniform float envReflection; + +uniform mat4 worldMatrix; + +in vec3 LightDir; +in vec3 ViewDir; + +in vec4 A; +in vec4 C; +in vec4 D; + +in vec3 N; +in vec3 t; +in vec3 b; +in vec3 v; + +vec4 colorLookup( float x, float y ) { + + return texture2D( GreyscaleMap, vec2( clamp(x, 0.0, 1.0), clamp(y, 0.0, 1.0)) ); +} + +void main( void ) +{ + vec2 offset = gl_TexCoord[0].st * uvScale + uvOffset; + + vec4 baseMap = texture2D( SourceTexture, offset ); + vec4 normalMap = texture2D( NormalMap, offset ); + vec4 specMap = texture2D( SpecularMap, offset ); + + vec3 normal = normalize(normalMap.rgb * 2.0 - 1.0); + + if ( !gl_FrontFacing && doubleSided ) { + normal *= -1.0; + } + + vec3 L = normalize(LightDir); + vec3 V = normalize(ViewDir); + vec3 R = reflect(-L, normal); + vec3 H = normalize( L + V ); + + float NdotL = max( dot(normal, L), 0.000001 ); + float NdotH = max( dot(normal, H), 0.000001 ); + float NdotV = max( dot(normal, V), 0.000001 ); + float LdotH = max( dot(L, H), 0.000001 ); + float NdotNegL = max( dot(normal, -L), 0.000001 ); + + vec3 reflected = reflect( V, normal ); + vec3 reflectedVS = t * reflected.x + b * reflected.y + N * reflected.z; + vec3 reflectedWS = vec3( worldMatrix * (gl_ModelViewMatrixInverse * vec4( reflectedVS, 0.0 )) ); + + if ( greyscaleAlpha ) + baseMap.a = 1.0; + + vec4 baseColor = glowColor; + if ( !greyscaleColor ) + baseColor.rgb *= glowMult; + + // Falloff + float falloff = 1.0; + if ( useFalloff ) { + falloff = smoothstep( falloffParams.x, falloffParams.y, abs(dot(normal, V)) ); + falloff = mix( max(falloffParams.z, 0.0), min(falloffParams.w, 1.0), falloff ); + + baseMap.a *= falloff; + } + + float alphaMult = baseColor.a * baseColor.a; + + vec4 color; + color.rgb = baseMap.rgb * C.rgb * baseColor.rgb; + color.a = alphaMult * C.a * baseMap.a; + + if ( greyscaleColor ) { + vec4 luG = colorLookup( texture2D( SourceTexture, offset ).g, baseColor.r * C.r * falloff ); + + color.rgb = luG.rgb; + } + + if ( greyscaleAlpha ) { + vec4 luA = colorLookup( texture2D( SourceTexture, offset ).a, color.a ); + + color.a = luA.a; + } + + vec3 diffuse = A.rgb + (D.rgb * NdotL); + color.rgb = mix( color.rgb, color.rgb * D.rgb, lightingInfluence ); + + // Specular + float g = 1.0; + float s = 1.0; + if ( hasEnvMask ) { + g = specMap.r; + s = specMap.g; + } + + // Environment + vec4 cube = textureCube( CubeMap, reflectedWS ); + if ( hasCubeMap ) { + cube.rgb *= envReflection * s; + cube.rgb = mix( cube.rgb, cube.rgb * D.rgb, lightingInfluence ); + + color.rgb += cube.rgb * falloff; + } + + gl_FragColor.rgb = color.rgb; + gl_FragColor.a = color.a; +} diff --git a/res/shaders/fo4_effectshader.prog b/res/shaders/fo4_effectshader.prog new file mode 100644 index 000000000..355177175 --- /dev/null +++ b/res/shaders/fo4_effectshader.prog @@ -0,0 +1,23 @@ +# effect shader + +checkgroup begin or + # Fallout 4 and later + checkgroup begin and + check HEADER/Version >= 0x14020007 + check HEADER/User Version >= 12 + check HEADER/User Version 2 >= 130 + checkgroup end +checkgroup end + +checkgroup begin or + # Fallout 4 + checkgroup begin and + check BSEffectShaderProperty + checkgroup end +checkgroup end + +texcoords 0 base +texcoords 1 tangents +texcoords 2 bitangents + +shaders fo4_effectshader.vert fo4_effectshader.frag diff --git a/res/shaders/fo4_effectshader.vert b/res/shaders/fo4_effectshader.vert new file mode 100644 index 000000000..5776e7dae --- /dev/null +++ b/res/shaders/fo4_effectshader.vert @@ -0,0 +1,37 @@ +#version 130 + +out vec3 LightDir; +out vec3 ViewDir; + +out vec4 A; +out vec4 C; +out vec4 D; + +out vec3 N; +out vec3 t; +out vec3 b; +out vec3 v; + +void main( void ) +{ + gl_Position = ftransform(); + gl_TexCoord[0] = gl_MultiTexCoord0; + + N = normalize(gl_NormalMatrix * gl_Normal); + t = normalize(gl_NormalMatrix * gl_MultiTexCoord1.xyz); + b = normalize(gl_NormalMatrix * gl_MultiTexCoord2.xyz); + + // NOTE: b<->t + mat3 tbnMatrix = mat3(t.x, b.x, N.x, + t.y, b.y, N.y, + t.z, b.z, N.z); + + v = vec3(gl_ModelViewMatrix * gl_Vertex); + + ViewDir = tbnMatrix * -v.xyz; + LightDir = tbnMatrix * gl_LightSource[0].position.xyz; + + A = gl_LightSource[0].ambient; + C = gl_Color; + D = gl_LightSource[0].diffuse; +} \ No newline at end of file diff --git a/res/shaders/fo4_env.frag b/res/shaders/fo4_env.frag new file mode 100644 index 000000000..09cadb9b6 --- /dev/null +++ b/res/shaders/fo4_env.frag @@ -0,0 +1,329 @@ +#version 130 +#extension GL_ARB_shader_texture_lod : require + +uniform sampler2D BaseMap; +uniform sampler2D NormalMap; +//uniform sampler2D LightMask; +uniform sampler2D BacklightMap; +uniform sampler2D EnvironmentMap; +uniform sampler2D SpecularMap; +uniform sampler2D GreyscaleMap; +uniform samplerCube CubeMap; + +uniform vec3 specColor; +uniform float specStrength; +uniform float specGlossiness; // "Smoothness" in FO4; 0-1 +uniform float fresnelPower; + +uniform float paletteScale; + +uniform vec3 glowColor; +uniform float glowMult; + +uniform float alpha; + +uniform vec2 uvScale; +uniform vec2 uvOffset; + +uniform bool hasEmit; +uniform bool hasSoftlight; +uniform bool hasBacklight; +uniform bool hasRimlight; +uniform bool hasCubeMap; +uniform bool hasEnvMask; +uniform bool hasSpecularMap; +uniform bool greyscaleColor; +uniform bool doubleSided; + +uniform float subsurfaceRolloff; +uniform float rimPower; +uniform float backlightPower; + +uniform float envReflection; + +uniform mat4 worldMatrix; + +in vec3 LightDir; +in vec3 ViewDir; + +in vec4 A; +in vec4 C; +in vec4 D; + +in vec3 N; +in vec3 t; +in vec3 b; + +#ifndef M_PI + #define M_PI 3.1415926535897932384626433832795 +#endif + +#define FLT_EPSILON 1.192092896e-07F // smallest such that 1.0 + FLT_EPSILON != 1.0 + +float OrenNayar( vec3 L, vec3 V, vec3 N, float roughness, float NdotL ) +{ + //float NdotL = dot(N, L); + float NdotV = dot(N, V); + float LdotV = dot(L, V); + + float rough2 = roughness * roughness; + + float A = 1.0 - 0.5 * (rough2 / (rough2 + 0.57)); + float B = 0.45 * (rough2 / (rough2 + 0.09)); + + float a = min( NdotV, NdotL ); + float b = max( NdotV, NdotL ); + b = (sign(b) == 0.0) ? FLT_EPSILON : sign(b) * max( 0.01, abs(b) ); // For fudging the smoothness of C + float C = sqrt( (1.0 - a * a) * (1.0 - b * b) ) / b; + + float gamma = LdotV - NdotL * NdotV; + float L1 = A + B * max( gamma, FLT_EPSILON ) * C; + return L1 * max( NdotL, FLT_EPSILON ); +} + +float OrenNayarFull( vec3 L, vec3 V, vec3 N, float roughness, float NdotL ) +{ + //float NdotL = dot(N, L); + float NdotV = dot(N, V); + float LdotV = dot(L, V); + + float angleVN = acos(max(NdotV, FLT_EPSILON)); + float angleLN = acos(max(NdotL, FLT_EPSILON)); + + float alpha = max(angleVN, angleLN); + float beta = min(angleVN, angleLN); + float gamma = LdotV - NdotL * NdotV; + + float roughnessSquared = roughness * roughness; + float roughnessSquared9 = (roughnessSquared / (roughnessSquared + 0.09)); + + // C1, C2, and C3 + float C1 = 1.0 - 0.5 * (roughnessSquared / (roughnessSquared + 0.33)); + float C2 = 0.45 * roughnessSquared9; + + if( gamma >= 0.0 ) { + C2 *= sin(alpha); + } else { + C2 *= (sin(alpha) - pow((2.0 * beta) / M_PI, 3.0)); + } + + float powValue = (4.0 * alpha * beta) / (M_PI * M_PI); + float C3 = 0.125 * roughnessSquared9 * powValue * powValue; + + // Avoid asymptote at pi/2 + float asym = M_PI / 2.0; + float lim1 = asym + 0.01; + float lim2 = asym - 0.01; + + float ab2 = (alpha + beta) / 2.0; + + if ( beta >= asym && beta < lim1 ) + beta = lim1; + else if ( beta < asym && beta >= lim2 ) + beta = lim2; + + if ( ab2 >= asym && ab2 < lim1 ) + ab2 = lim1; + else if ( ab2 < asym && ab2 >= lim2 ) + ab2 = lim2; + + // Reflection + float A = gamma * C2 * tan(beta); + float B = (1.0 - abs(gamma)) * C3 * tan(ab2); + + float L1 = max(FLT_EPSILON, NdotL) * (C1 + A + B); + + // Interreflection + float twoBetaPi = 2.0 * beta / M_PI; + float L2 = 0.17 * max(FLT_EPSILON, NdotL) * (roughnessSquared / (roughnessSquared + 0.13)) * (1.0 - gamma * twoBetaPi * twoBetaPi); + + return L1 + L2; +} + +// Schlick's Fresnel approximation +float fresnelSchlick( float VdotH, float F0 ) +{ + float base = 1.0 - VdotH; + float exp = pow( base, fresnelPower ); + return clamp( exp + F0 * (1.0 - exp), 0.0, 1.0 ); +} + +// The Torrance-Sparrow visibility factor, G +float VisibDiv( float NdotL, float NdotV, float VdotH, float NdotH ) +{ + float denom = max( VdotH, FLT_EPSILON ); + float numL = min( NdotV, NdotL ); + float numR = 2.0 * NdotH; + if ( denom >= (numL * numR) ) { + numL = (numL == NdotV) ? 1.0 : (NdotL / NdotV); + return (numL * numR) / denom; + } + return 1.0 / NdotV; +} + +// this is a normalized Phong model used in the Torrance-Sparrow model +vec3 TorranceSparrow(float NdotL, float NdotH, float NdotV, float VdotH, vec3 color, float power, float F0) +{ + // D: Normalized phong model + float D = ((power + 2.0) / (2.0 * M_PI)) * pow( NdotH, power ); + + // G: Torrance-Sparrow visibility term divided by NdotV + float G_NdotV = VisibDiv( NdotL, NdotV, VdotH, NdotH ); + + // F: Schlick's approximation + float F = fresnelSchlick( VdotH, F0 ); + + // Torrance-Sparrow: + // (F * G * D) / (4 * NdotL * NdotV) + // Division by NdotV is done in VisibDiv() + // and division by NdotL is removed since + // outgoing radiance is determined by: + // BRDF * NdotL * L() + float spec = (F * G_NdotV * D) / 4.0; + + return color * spec * M_PI; +} + +vec3 tonemap(vec3 x) +{ + float _A = 0.15; + float _B = 0.50; + float _C = 0.10; + float _D = 0.20; + float _E = 0.02; + float _F = 0.30; + + return ((x*(_A*x+_C*_B)+_D*_E)/(x*(_A*x+_B)+_D*_F))-_E/_F; +} + +vec3 toGrayscale(vec3 color) +{ + return vec3(dot(vec3(0.3, 0.59, 0.11), color)); +} + +vec4 colorLookup( float x, float y ) { + + return texture2D( GreyscaleMap, vec2( clamp(x, 0.0, 1.0), clamp(y, 0.0, 1.0)) ); +} + +void main( void ) +{ + vec2 offset = gl_TexCoord[0].st * uvScale + uvOffset; + + vec4 baseMap = texture2D( BaseMap, offset ); + vec4 normalMap = texture2D( NormalMap, offset ); + vec4 specMap = texture2D( SpecularMap, offset ); + + vec3 normal = normalize(normalMap.rgb * 2.0 - 1.0); + if ( !gl_FrontFacing && doubleSided ) { + normal *= -1.0; + } + + vec3 L = normalize(LightDir); + vec3 V = normalize(ViewDir); + vec3 R = reflect(-L, normal); + vec3 H = normalize( L + V ); + + float NdotL = dot(normal, L); + float NdotL0 = max( NdotL, FLT_EPSILON ); + float NdotH = max( dot(normal, H), FLT_EPSILON ); + float NdotV = max( dot(normal, V), FLT_EPSILON ); + float VdotH = max( dot(V, H), FLT_EPSILON ); + float NdotNegL = max( dot(normal, -L), FLT_EPSILON ); + + vec3 reflected = reflect( V, normal ); + vec3 reflectedVS = t * reflected.x + b * reflected.y + N * reflected.z; + vec3 reflectedWS = vec3( worldMatrix * (gl_ModelViewMatrixInverse * vec4( reflectedVS, 0.0 )) ); + + + vec4 color; + vec3 albedo = baseMap.rgb * C.rgb; + vec3 diffuse = A.rgb + (D.rgb * NdotL0); + if ( greyscaleColor ) { + vec4 luG = colorLookup( baseMap.g, paletteScale - (1 - C.r) ); + + albedo = luG.rgb; + } + + // Emissive + vec3 emissive = vec3(0.0); + if ( hasEmit ) { + emissive += glowColor * glowMult; + } + + // Specular + float g = 1.0; + float s = 1.0; + float smoothness = 1.0; + float roughness = 0.0; + float specMask = 1.0; + vec3 spec = vec3(0.0); + if ( hasSpecularMap ) { + g = specMap.r; + s = specMap.g; + smoothness = g * specGlossiness; + roughness = 1.0 - smoothness; + float fSpecularPower = exp2( smoothness * 10 + 1 ); + specMask = s * specStrength; + + spec = TorranceSparrow( NdotL0, NdotH, NdotV, VdotH, vec3(specMask), fSpecularPower, 0.2 ) * NdotL0 * D.rgb * specColor; + } + + // Environment + vec4 cube = textureLod( CubeMap, reflectedWS, 8.0 - smoothness * 8.0 ); + vec4 env = texture2D( EnvironmentMap, offset ); + if ( hasCubeMap ) { + cube.rgb *= envReflection * specStrength; + if ( hasEnvMask ) { + cube.rgb *= env.r; + } else { + cube.rgb *= s; + } + + spec += cube.rgb * diffuse; + } + + vec3 backlight = vec3(0.0); + if ( backlightPower > 0.0 ) { + backlight = albedo * NdotNegL * clamp( backlightPower, 0.0, 1.0 ); + + emissive += backlight * D.rgb; + } + + vec3 rim = vec3(0.0); + if ( hasRimlight ) { + rim = vec3(pow((1.0 - NdotV), rimPower)) * specMask; + rim *= smoothstep( -0.2, 1.0, dot(-L, V) ); + + //emissive += rim * D.rgb; + } + + // Diffuse + float diff = OrenNayarFull( L, V, normal, roughness, NdotL ); + diffuse = vec3(diff); + + vec3 soft = vec3(0.0); + float wrap = NdotL; + if ( hasSoftlight || subsurfaceRolloff > 0.0 ) { + wrap = (wrap + subsurfaceRolloff) / (1.0 + subsurfaceRolloff); + soft = albedo * max( 0.0, wrap ) * smoothstep( 1.0, 0.0, sqrt(diff) ); + + diffuse += soft; + } + + // Diffuse + color.rgb = diffuse * albedo * D.rgb; + // Ambient + color.rgb += A.rgb * albedo; + // Specular + color.rgb += spec; + color.rgb += A.rgb * specMask * fresnelSchlick( VdotH, 0.2 ) * (1.0 - NdotV) * D.rgb; + // Emissive + color.rgb += emissive; + + color.rgb = tonemap( color.rgb ) / tonemap( vec3(1.0) ); + color.a = C.a * baseMap.a; + + gl_FragColor = color; + gl_FragColor.a *= alpha; +} diff --git a/res/shaders/fo4_env.prog b/res/shaders/fo4_env.prog new file mode 100644 index 000000000..b440605c3 --- /dev/null +++ b/res/shaders/fo4_env.prog @@ -0,0 +1,30 @@ +# environment + +checkgroup begin or + # Fallout 4 and later + checkgroup begin and + check HEADER/Version >= 0x14020007 + check HEADER/User Version >= 12 + check HEADER/User Version 2 >= 130 + checkgroup end +checkgroup end + +checkgroup begin and + check BSLightingShaderProperty + checkgroup begin or + check BSLightingShaderProperty/Skyrim Shader Type == 1 + check BSLightingShaderProperty/Skyrim Shader Type == 16 + checkgroup end + check NiAVObject/Vertex Size >= 3 + checkgroup begin or + check BSTriShape + check BSSubIndexTriShape + check BSMeshLODTriShape + checkgroup end +checkgroup end + +texcoords 0 base +texcoords 1 tangents +texcoords 2 bitangents + +shaders fo4_env.vert fo4_env.frag diff --git a/res/shaders/fo4_env.vert b/res/shaders/fo4_env.vert new file mode 100644 index 000000000..62e989684 --- /dev/null +++ b/res/shaders/fo4_env.vert @@ -0,0 +1,38 @@ +#version 130 + +out vec3 LightDir; +out vec3 ViewDir; + +out vec3 N; +out vec3 t; +out vec3 b; +out vec3 v; + +out vec4 A; +out vec4 C; +out vec4 D; + + +void main( void ) +{ + gl_Position = ftransform(); + gl_TexCoord[0] = gl_MultiTexCoord0; + + N = normalize(gl_NormalMatrix * gl_Normal); + t = normalize(gl_NormalMatrix * gl_MultiTexCoord1.xyz); + b = normalize(gl_NormalMatrix * gl_MultiTexCoord2.xyz); + + // NOTE: b<->t + mat3 tbnMatrix = mat3(t.x, b.x, N.x, + t.y, b.y, N.y, + t.z, b.z, N.z); + + v = vec3(gl_ModelViewMatrix * gl_Vertex); + + ViewDir = tbnMatrix * -v.xyz; + LightDir = tbnMatrix * gl_LightSource[0].position.xyz; + + A = gl_LightSource[0].ambient; + C = gl_Color; + D = gl_LightSource[0].diffuse; +} diff --git a/res/shaders/gray.dds b/res/shaders/gray.dds new file mode 100644 index 000000000..bf7ef8d22 Binary files /dev/null and b/res/shaders/gray.dds differ diff --git a/res/shaders/ob_glowmap.vert b/res/shaders/ob_glowmap.vert index 3a82e7805..4d0212eff 100644 --- a/res/shaders/ob_glowmap.vert +++ b/res/shaders/ob_glowmap.vert @@ -1,3 +1,4 @@ +#version 120 varying vec3 LightDir; varying vec3 ViewDir; @@ -6,27 +7,30 @@ varying vec3 HalfVector; varying vec4 ColorEA; varying vec4 ColorD; -vec3 normal; -vec3 tangent; -vec3 binormal; - -vec3 tspace( vec3 v ) -{ - return vec3( dot( v, binormal ), dot( v, tangent ), dot( v, normal ) ); -} +varying vec3 N; +varying vec3 t; +varying vec3 b; +varying vec3 v; void main( void ) { gl_Position = ftransform(); gl_TexCoord[0] = gl_MultiTexCoord0; - normal = gl_NormalMatrix * gl_Normal; - tangent = gl_NormalMatrix * gl_MultiTexCoord1.xyz; - binormal = gl_NormalMatrix * gl_MultiTexCoord2.xyz; + N = normalize(gl_NormalMatrix * gl_Normal); + t = normalize(gl_NormalMatrix * gl_MultiTexCoord1.xyz); + b = normalize(gl_NormalMatrix * gl_MultiTexCoord2.xyz); + + // NOTE: b<->t + mat3 tbnMatrix = mat3(b.x, t.x, N.x, + b.y, t.y, N.y, + b.z, t.z, N.z); + + v = vec3(gl_ModelViewMatrix * gl_Vertex); - ViewDir = tspace( ( gl_ModelViewMatrix * gl_Vertex ).xyz ); - LightDir = tspace( gl_LightSource[0].position.xyz ); // light 0 is directional - HalfVector = tspace( gl_LightSource[0].halfVector.xyz ); + ViewDir = tbnMatrix * -v.xyz; + LightDir = tbnMatrix * gl_LightSource[0].position.xyz; + HalfVector = tbnMatrix * gl_LightSource[0].halfVector.xyz; ColorEA = gl_FrontMaterial.ambient * gl_LightSource[0].ambient; ColorD = gl_FrontMaterial.diffuse * gl_LightSource[0].diffuse; diff --git a/res/shaders/ob_material_default.vert b/res/shaders/ob_material_default.vert index b9a18243b..58977d9b6 100644 --- a/res/shaders/ob_material_default.vert +++ b/res/shaders/ob_material_default.vert @@ -1,3 +1,4 @@ +#version 120 varying vec3 LightDir; varying vec3 ViewDir; @@ -6,28 +7,37 @@ varying vec3 HalfVector; varying vec4 ColorEA; varying vec4 ColorD; -vec3 normal; -vec3 tangent; -vec3 binormal; +varying vec3 N; +varying vec3 t; +varying vec3 b; +varying vec3 v; -vec3 tspace( vec3 v ) -{ - return vec3( dot( v, binormal ), dot( v, tangent ), dot( v, normal ) ); -} +varying vec4 A; +varying vec4 D; void main( void ) { gl_Position = ftransform(); gl_TexCoord[0] = gl_MultiTexCoord0; - normal = gl_NormalMatrix * gl_Normal; - tangent = gl_NormalMatrix * gl_MultiTexCoord1.xyz; - binormal = gl_NormalMatrix * gl_MultiTexCoord2.xyz; + N = normalize(gl_NormalMatrix * gl_Normal); + t = normalize(gl_NormalMatrix * gl_MultiTexCoord1.xyz); + b = normalize(gl_NormalMatrix * gl_MultiTexCoord2.xyz); - ViewDir = tspace( ( gl_ModelViewMatrix * gl_Vertex ).xyz ); - LightDir = tspace( gl_LightSource[0].position.xyz ); // light 0 is directional - HalfVector = tspace( gl_LightSource[0].halfVector.xyz ); + // NOTE: b<->t + mat3 tbnMatrix = mat3(b.x, t.x, N.x, + b.y, t.y, N.y, + b.z, t.z, N.z); + + v = vec3(gl_ModelViewMatrix * gl_Vertex); - ColorEA = gl_FrontMaterial.emission + gl_FrontMaterial.ambient * gl_LightSource[0].ambient; - ColorD = gl_FrontMaterial.diffuse * gl_LightSource[0].diffuse; + ViewDir = tbnMatrix * -v.xyz; + LightDir = tbnMatrix * gl_LightSource[0].position.xyz; + HalfVector = tbnMatrix * gl_LightSource[0].halfVector.xyz; + + A = gl_LightSource[0].ambient; + D = gl_LightSource[0].diffuse; + + ColorEA = gl_FrontMaterial.emission + gl_FrontMaterial.ambient * A; + ColorD = gl_FrontMaterial.diffuse * D; } diff --git a/res/shaders/ob_normal+glowmap.prog b/res/shaders/ob_normal+glowmap.prog index a4056e504..b046ee8eb 100644 --- a/res/shaders/ob_normal+glowmap.prog +++ b/res/shaders/ob_normal+glowmap.prog @@ -14,12 +14,6 @@ checkgroup begin or checkgroup end checkgroup begin or - # Skyrim - checkgroup begin and - check BSLightingShaderProperty/Skyrim Shader Type == 2 - check NiTriBasedGeomData/Has Vertex Colors == 0 - checkgroup end - checkgroup begin check NiTriBasedGeomData/Has Vertex Colors == 0 check NiVertexColorProperty/Vertex Mode == 1 diff --git a/res/shaders/ob_normal+glowmap_vcol.prog b/res/shaders/ob_normal+glowmap_vcol.prog index 110ad80f6..b046ee8eb 100644 --- a/res/shaders/ob_normal+glowmap_vcol.prog +++ b/res/shaders/ob_normal+glowmap_vcol.prog @@ -14,12 +14,6 @@ checkgroup begin or checkgroup end checkgroup begin or - # Skyrim - checkgroup begin and - check BSLightingShaderProperty/Skyrim Shader Type == 2 - check NiTriBasedGeomData/Has Vertex Colors == 1 - checkgroup end - checkgroup begin check NiTriBasedGeomData/Has Vertex Colors == 0 check NiVertexColorProperty/Vertex Mode == 1 diff --git a/res/shaders/ob_normalglowmap.frag b/res/shaders/ob_normalglowmap.frag index c15dc68b1..5e72e0229 100644 --- a/res/shaders/ob_normalglowmap.frag +++ b/res/shaders/ob_normalglowmap.frag @@ -1,34 +1,132 @@ +#version 120 + uniform sampler2D BaseMap; uniform sampler2D NormalMap; uniform sampler2D GlowMap; +uniform sampler2D LightMask; +uniform sampler2D BacklightMap; + +uniform bool hasGlowMap; +uniform vec3 glowColor; +uniform float glowMult; + +uniform vec3 specColor; +uniform float specStrength; +uniform float specGlossiness; + +uniform vec2 uvScale; +uniform vec2 uvOffset; + +uniform bool hasSoftlight; +uniform bool hasBacklight; +uniform bool hasRimlight; + +uniform float lightingEffect1; +uniform float lightingEffect2; varying vec3 LightDir; -varying vec3 HalfVector; -//varying vec3 ViewDir; +varying vec3 ViewDir; varying vec4 ColorEA; varying vec4 ColorD; +vec3 tonemap(vec3 x) +{ + float A = 0.15; + float B = 0.50; + float C = 0.10; + float D = 0.20; + float E = 0.02; + float F = 0.30; + + return ((x*(A*x+C*B)+D*E)/(x*(A*x+B)+D*F))-E/F; +} + void main( void ) { - vec4 color = ColorEA; + vec2 offset = gl_TexCoord[0].st * uvScale + uvOffset; - color += texture2D( GlowMap, gl_TexCoord[0].st ); - - vec4 normal = texture2D( NormalMap, gl_TexCoord[0].st ); - normal.rgb = normal.rgb * 2.0 - 1.0; + vec4 baseMap = texture2D( BaseMap, offset ); + vec4 nmap = texture2D( NormalMap, offset ); + + vec4 color; + color.rgb = baseMap.rgb; - float NdotL = max( dot( normal.rgb, normalize( LightDir ) ), 0.0 ); + vec3 normal = normalize(nmap.rgb * 2.0 - 1.0); + + float spec = 0.0; + + vec3 emissive = texture2D( GlowMap, offset ).rgb; + + // Skyrim + if ( hasGlowMap ) { + color.rgb += tonemap( baseMap.rgb * emissive.rgb * glowColor ) / tonemap( 1.0f / (vec3(glowMult) + 0.001f) ); + } - if ( NdotL > 0.0 ) - { - color += ColorD * NdotL; - float NdotHV = max( dot( normal.rgb, normalize( HalfVector ) ), 0.0 ); - color += normal.a * gl_FrontMaterial.specular * gl_LightSource[0].specular * pow( NdotHV, gl_FrontMaterial.shininess ); + vec3 L = normalize(LightDir); + vec3 E = normalize(ViewDir); + vec3 R = reflect(-L, normal); + //vec3 H = normalize( L + E ); + float NdotL = max(dot(normal, L), 0.0); + + color.rgb *= ColorEA.rgb + ColorD.rgb * NdotL; + color.a = ColorD.a * baseMap.a; + + if ( NdotL > 0.0 && specStrength > 0.0 ) { + float RdotE = max( dot( R, E ), 0.0 ); + + // TODO: Attenuation? + + if ( RdotE > 0.0 ) { + spec = nmap.a * gl_LightSource[0].specular.r * specStrength * pow(RdotE, 0.8*specGlossiness); + color.rgb += spec * specColor; + } + } + + vec3 backlight; + if ( hasBacklight ) { + backlight = texture2D( BacklightMap, offset ).rgb; + color.rgb += baseMap.rgb * backlight * (1.0 - NdotL) * 0.66; + } + + vec4 mask; + if ( hasRimlight || hasSoftlight ) { + mask = texture2D( LightMask, offset ); + } + + float facing = dot(-L, E); + + vec3 rim; + if ( hasRimlight ) { + rim = vec3(( 1.0 - NdotL ) * ( 1.0 - max(dot(normal, E), 0.0))); + //rim = smoothstep( 0.0, 1.0, rim ); + rim = mask.rgb * pow(rim, vec3(lightingEffect2)) * gl_LightSource[0].diffuse.rgb * vec3(0.66); + rim *= smoothstep( -0.5, 1.0, facing ); + color.rgb += rim; + } + + float wrap = dot(normal, -L); + + vec3 soft; + if ( hasSoftlight ) { + soft = vec3((1.0 - wrap) * (1.0 - NdotL)); + soft = smoothstep( -1.0, 1.0, soft ); + + // TODO: Very approximate, kind of arbitrary. There is surely a more correct way. + soft *= mask.rgb * pow(soft, vec3(4.0/(lightingEffect1*lightingEffect1))); // * gl_LightSource[0].ambient.rgb; + //soft *= smoothstep( -1.0, 0.0, soft ); + //soft = mix( soft, color.rgb, gl_LightSource[0].ambient.rgb ); + + //soft = smoothstep( 0.0, 1.0, soft ); + soft *= gl_LightSource[0].diffuse.rgb * gl_LightSource[0].ambient.rgb + (0.01 * lightingEffect1*lightingEffect1); + + //soft = clamp( soft, 0.0, 0.5 ); + //soft *= smoothstep( -0.5, 1.0, facing ); + //soft = mix( soft, color.rgb, gl_LightSource[0].diffuse.rgb ); + color.rgb += baseMap.rgb * soft; } - color = min( color, 1.0 ); - color *= texture2D( BaseMap, gl_TexCoord[0].st ); + //color = min( color, 1.0 ); gl_FragColor = color; } diff --git a/res/shaders/ob_normalmap.frag b/res/shaders/ob_normalmap.frag index 46d064d0a..94a55a8eb 100644 --- a/res/shaders/ob_normalmap.frag +++ b/res/shaders/ob_normalmap.frag @@ -1,31 +1,129 @@ +#version 120 + uniform sampler2D BaseMap; uniform sampler2D NormalMap; +uniform sampler2D LightMask; +uniform sampler2D BacklightMap; + +uniform vec3 specColor; +uniform float specStrength; +uniform float specGlossiness; + +uniform vec3 glowColor; +uniform float glowMult; + +uniform vec2 uvScale; +uniform vec2 uvOffset; + +uniform bool hasEmit; +uniform bool hasSoftlight; +uniform bool hasBacklight; +uniform bool hasRimlight; + +uniform float lightingEffect1; +uniform float lightingEffect2; varying vec3 LightDir; -varying vec3 HalfVector; -//varying vec3 ViewDir; +varying vec3 ViewDir; varying vec4 ColorEA; varying vec4 ColorD; +varying vec4 A; +varying vec4 D; + + +vec3 tonemap(vec3 x) +{ + float _A = 0.15; + float _B = 0.50; + float _C = 0.10; + float _D = 0.20; + float _E = 0.02; + float _F = 0.30; + + return ((x*(_A*x+_C*_B)+_D*_E)/(x*(_A*x+_B)+_D*_F))-_E/_F; +} + +vec3 toGrayscale(vec3 color) +{ + return vec3(dot(vec3(0.3, 0.59, 0.11), color)); +} + void main( void ) { - vec4 color = ColorEA; + vec2 offset = gl_TexCoord[0].st * uvScale + uvOffset; - vec4 normal = texture2D( NormalMap, gl_TexCoord[0].st ); - normal.rgb = normal.rgb * 2.0 - 1.0; + vec4 baseMap = texture2D( BaseMap, offset ); + vec4 normalMap = texture2D( NormalMap, offset ); + + vec3 normal = normalize(normalMap.rgb * 2.0 - 1.0); + - float NdotL = max( dot( normal.rgb, normalize( LightDir ) ), 0.0 ); + vec3 L = normalize(LightDir); + vec3 E = normalize(ViewDir); + vec3 R = reflect(-L, normal); - if ( NdotL > 0.0 ) - { - color += ColorD * NdotL; - float NdotHV = max( dot( normal.rgb, normalize( HalfVector ) ), 0.0 ); - color += normal.a * gl_FrontMaterial.specular * gl_LightSource[0].specular * pow( NdotHV, gl_FrontMaterial.shininess ); + float NdotL = max( dot(normal, L), 0.0 ); + float EdotN = max( dot(normal, E), 0.0 ); + float wrap = max( dot(normal, -L), 0.0 ); + float facing = max( dot(-L, E), 0.0 ); + + + vec4 color; + color.rgb = baseMap.rgb; + color.rgb *= ColorEA.rgb + (ColorD.rgb * NdotL); + color.a = ColorD.a * baseMap.a; + + + // Emissive + if ( hasEmit ) { + color.rgb += tonemap( baseMap.rgb * glowColor ) / tonemap( 1.0f / vec3(glowMult + 0.001f) ); + } + + // Specular + float spec = 0.0; + if ( NdotL > 0.0 && specStrength > 0.0 ) { + float RdotE = max( dot(R, E), 0.0 ); + if ( RdotE > 0.0 ) { + spec = normalMap.a * gl_LightSource[0].specular.r * specStrength * pow(RdotE, 0.8*specGlossiness); + color.rgb += spec * specColor; + } + } + + vec3 backlight; + if ( hasBacklight ) { + backlight = texture2D( BacklightMap, offset ).rgb; + color.rgb += baseMap.rgb * backlight * wrap * D.rgb; + } + + vec4 mask; + if ( hasRimlight || hasSoftlight ) { + mask = texture2D( LightMask, offset ); + } + + vec3 rim; + if ( hasRimlight ) { + rim = vec3((1.0 - NdotL) * (1.0 - EdotN)); + rim = mask.rgb * pow(rim, vec3(lightingEffect2)) * D.rgb * vec3(0.66); + rim *= smoothstep( -0.5, 1.0, facing ); + + color.rgb += rim; } - color = min( color, 1.0 ); - color *= texture2D( BaseMap, gl_TexCoord[0].st ); + vec3 soft; + if ( hasSoftlight ) { + soft = vec3((1.0 - wrap) * (1.0 - NdotL)); + soft = smoothstep( -1.0, 1.0, soft ); + + // TODO: Very approximate, kind of arbitrary. There is surely a more correct way. + soft *= mask.rgb * pow(soft, vec3(4.0/(lightingEffect1*lightingEffect1))); + soft *= D.rgb * A.rgb + (0.01 * lightingEffect1*lightingEffect1); + + color.rgb += baseMap.rgb * soft; + } + + color.rgb = tonemap( color.rgb ) / tonemap( vec3(1.0) ); gl_FragColor = color; } diff --git a/res/shaders/ob_normalmap.prog b/res/shaders/ob_normalmap.prog index c0c997ca4..34317a3a8 100644 --- a/res/shaders/ob_normalmap.prog +++ b/res/shaders/ob_normalmap.prog @@ -14,12 +14,6 @@ checkgroup begin or checkgroup end checkgroup begin or - # Skyrim - checkgroup begin and - check BSLightingShaderProperty/Skyrim Shader Type != 2 - check NiTriBasedGeomData/Has Vertex Colors == 0 - checkgroup end - # Fallout 3 checkgroup begin and check BSShaderPPLightingProperty diff --git a/res/shaders/ob_normalmap_vcol_ad.frag b/res/shaders/ob_normalmap_vcol_ad.frag deleted file mode 100644 index bfcef40b6..000000000 --- a/res/shaders/ob_normalmap_vcol_ad.frag +++ /dev/null @@ -1,32 +0,0 @@ -uniform sampler2D BaseMap; -uniform sampler2D NormalMap; - -varying vec3 LightDir; -varying vec3 HalfVector; -//varying vec3 ViewDir; - -varying vec4 ColorEA; -varying vec4 ColorD; - -void main( void ) -{ - vec4 color = ColorEA; - - vec4 normal = texture2D( NormalMap, gl_TexCoord[0].st ); - normal.rgb = normal.rgb * 2.0 - 1.0; - - float NdotL = max( dot( normal.rgb, normalize( LightDir ) ), 0.0 ); - - if ( NdotL > 0.0 ) - { - color += ColorD * NdotL; - float NdotHV = max( dot( normal.rgb, normalize( HalfVector ) ), 0.0 ); - color += normal.a * gl_FrontMaterial.specular * gl_LightSource[0].specular * pow( NdotHV, gl_FrontMaterial.shininess ); - } - - color = min( color, 1.0 ); - color.a = ColorD.a; - color *= texture2D( BaseMap, gl_TexCoord[0].st ); - - gl_FragColor = color; -} diff --git a/res/shaders/ob_normalmap_vcol_ad.prog b/res/shaders/ob_normalmap_vcol_ad.prog index 20c06bcc5..5530fd8ee 100644 --- a/res/shaders/ob_normalmap_vcol_ad.prog +++ b/res/shaders/ob_normalmap_vcol_ad.prog @@ -14,12 +14,6 @@ checkgroup begin or checkgroup end checkgroup begin or - # Skyrim - checkgroup begin and - check BSLightingShaderProperty/Skyrim Shader Type != 2 - check NiTriBasedGeomData/Has Vertex Colors == 1 - checkgroup end - # Fallout 3 checkgroup begin and check BSShaderPPLightingProperty @@ -36,4 +30,4 @@ texcoords 0 base texcoords 1 tangents texcoords 2 bitangents -shaders ob_vcolors_ad.vert ob_normalmap_vcol_ad.frag +shaders ob_vcolors_ad.vert ob_normalmap.frag diff --git a/res/shaders/ob_normalmap_vcol_e.prog b/res/shaders/ob_normalmap_vcol_e.prog index cbbcc6f0e..20f37f1be 100644 --- a/res/shaders/ob_normalmap_vcol_e.prog +++ b/res/shaders/ob_normalmap_vcol_e.prog @@ -15,11 +15,10 @@ checkgroup end checkgroup begin or # Skyrim - checkgroup begin and - check not NiAlphaProperty - check BSEffectShaderProperty/Emissive Multiple >= 1 - check NiTriBasedGeomData/Has Vertex Colors == 1 - checkgroup end + //checkgroup begin and + // check BSEffectShaderProperty + // check NiTriBasedGeomData/Has Vertex Colors == 1 + //checkgroup end checkgroup begin check NiTriBasedGeomData/Has Vertex Colors != 0 diff --git a/res/shaders/ob_parallax.frag b/res/shaders/ob_parallax.frag index b92e4b09b..10d80334b 100644 --- a/res/shaders/ob_parallax.frag +++ b/res/shaders/ob_parallax.frag @@ -1,3 +1,5 @@ +#version 120 + uniform sampler2D BaseMap; uniform sampler2D NormalMap; diff --git a/res/shaders/ob_parallaxglowmap.frag b/res/shaders/ob_parallaxglowmap.frag index c396fbdcc..30b99e52c 100644 --- a/res/shaders/ob_parallaxglowmap.frag +++ b/res/shaders/ob_parallaxglowmap.frag @@ -1,3 +1,5 @@ +#version 120 + uniform sampler2D BaseMap; uniform sampler2D NormalMap; uniform sampler2D GlowMap; diff --git a/res/shaders/ob_vcolors_ad.vert b/res/shaders/ob_vcolors_ad.vert index ad6cfa5bd..bee9676ed 100644 --- a/res/shaders/ob_vcolors_ad.vert +++ b/res/shaders/ob_vcolors_ad.vert @@ -1,3 +1,4 @@ +#version 120 varying vec3 LightDir; varying vec3 ViewDir; @@ -6,28 +7,37 @@ varying vec3 HalfVector; varying vec4 ColorEA; varying vec4 ColorD; -vec3 normal; -vec3 tangent; -vec3 binormal; +varying vec3 N; +varying vec3 t; +varying vec3 b; +varying vec3 v; -vec3 tspace( vec3 v ) -{ - return vec3( dot( v, binormal ), dot( v, tangent ), dot( v, normal ) ); -} +varying vec4 A; +varying vec4 D; void main( void ) { gl_Position = ftransform(); gl_TexCoord[0] = gl_MultiTexCoord0; - normal = gl_NormalMatrix * gl_Normal; - tangent = gl_NormalMatrix * gl_MultiTexCoord1.xyz; - binormal = gl_NormalMatrix * gl_MultiTexCoord2.xyz; + N = normalize(gl_NormalMatrix * gl_Normal); + t = normalize(gl_NormalMatrix * gl_MultiTexCoord1.xyz); + b = normalize(gl_NormalMatrix * gl_MultiTexCoord2.xyz); - ViewDir = tspace( ( gl_ModelViewMatrix * gl_Vertex ).xyz ); - LightDir = tspace( gl_LightSource[0].position.xyz ); // light 0 is directional - HalfVector = tspace( gl_LightSource[0].halfVector.xyz ); + // NOTE: b<->t + mat3 tbnMatrix = mat3(b.x, t.x, N.x, + b.y, t.y, N.y, + b.z, t.z, N.z); + + v = vec3(gl_ModelViewMatrix * gl_Vertex); - ColorEA = gl_FrontMaterial.emission + gl_Color * gl_LightSource[0].ambient; - ColorD = gl_Color * gl_LightSource[0].diffuse; + ViewDir = tbnMatrix * -v.xyz; + LightDir = tbnMatrix * gl_LightSource[0].position.xyz; + HalfVector = tbnMatrix * gl_LightSource[0].halfVector.xyz; + + A = gl_LightSource[0].ambient; + D = gl_LightSource[0].diffuse; + + ColorEA = gl_FrontMaterial.emission + gl_Color * A; + ColorD = gl_Color * D; } diff --git a/res/shaders/ob_vcolors_e.vert b/res/shaders/ob_vcolors_e.vert index 7dbc4c3cf..45bdea3db 100644 --- a/res/shaders/ob_vcolors_e.vert +++ b/res/shaders/ob_vcolors_e.vert @@ -1,3 +1,4 @@ +#version 120 varying vec3 LightDir; varying vec3 ViewDir; @@ -6,28 +7,37 @@ varying vec3 HalfVector; varying vec4 ColorEA; varying vec4 ColorD; -vec3 normal; -vec3 tangent; -vec3 binormal; +varying vec3 N; +varying vec3 t; +varying vec3 b; +varying vec3 v; -vec3 tspace( vec3 v ) -{ - return vec3( dot( v, binormal ), dot( v, tangent ), dot( v, normal ) ); -} +varying vec4 A; +varying vec4 D; void main( void ) { gl_Position = ftransform(); gl_TexCoord[0] = gl_MultiTexCoord0; - normal = gl_NormalMatrix * gl_Normal; - tangent = gl_NormalMatrix * gl_MultiTexCoord1.xyz; - binormal = gl_NormalMatrix * gl_MultiTexCoord2.xyz; + N = normalize(gl_NormalMatrix * gl_Normal); + t = normalize(gl_NormalMatrix * gl_MultiTexCoord1.xyz); + b = normalize(gl_NormalMatrix * gl_MultiTexCoord2.xyz); - ViewDir = tspace( ( gl_ModelViewMatrix * gl_Vertex ).xyz ); - LightDir = tspace( gl_LightSource[0].position.xyz ); // light 0 is directional - HalfVector = tspace( gl_LightSource[0].halfVector.xyz ); + // NOTE: b<->t + mat3 tbnMatrix = mat3(b.x, t.x, N.x, + b.y, t.y, N.y, + b.z, t.z, N.z); + + v = vec3(gl_ModelViewMatrix * gl_Vertex); + + ViewDir = tbnMatrix * -v.xyz; + LightDir = tbnMatrix * gl_LightSource[0].position.xyz; + HalfVector = tbnMatrix * gl_LightSource[0].halfVector.xyz; + + A = gl_LightSource[0].ambient; + D = gl_LightSource[0].diffuse; - ColorEA = gl_Color + gl_FrontMaterial.ambient * gl_LightSource[0].ambient; - ColorD = gl_FrontMaterial.diffuse * gl_LightSource[0].diffuse; + ColorEA = gl_Color + gl_FrontMaterial.ambient * A; + ColorD = gl_FrontMaterial.diffuse * D; } diff --git a/res/shaders/sk_default.frag b/res/shaders/sk_default.frag new file mode 100644 index 000000000..9167d7d2c --- /dev/null +++ b/res/shaders/sk_default.frag @@ -0,0 +1,133 @@ +#version 120 + +uniform sampler2D BaseMap; +uniform sampler2D NormalMap; +uniform sampler2D LightMask; +uniform sampler2D BacklightMap; + +uniform vec3 specColor; +uniform float specStrength; +uniform float specGlossiness; + +uniform vec3 glowColor; +uniform float glowMult; + +uniform float alpha; + +uniform vec3 tintColor; + +uniform vec2 uvScale; +uniform vec2 uvOffset; + +uniform bool hasEmit; +uniform bool hasSoftlight; +uniform bool hasBacklight; +uniform bool hasRimlight; +uniform bool hasTintColor; + +uniform float lightingEffect1; +uniform float lightingEffect2; + +varying vec3 LightDir; +varying vec3 ViewDir; + +varying vec4 A; +varying vec4 C; +varying vec4 D; + + +vec3 tonemap(vec3 x) +{ + float _A = 0.15; + float _B = 0.50; + float _C = 0.10; + float _D = 0.20; + float _E = 0.02; + float _F = 0.30; + + return ((x*(_A*x+_C*_B)+_D*_E)/(x*(_A*x+_B)+_D*_F))-_E/_F; +} + +vec3 toGrayscale(vec3 color) +{ + return vec3(dot(vec3(0.3, 0.59, 0.11), color)); +} + +void main( void ) +{ + vec2 offset = gl_TexCoord[0].st * uvScale + uvOffset; + + vec4 baseMap = texture2D( BaseMap, offset ); + vec4 normalMap = texture2D( NormalMap, offset ); + + vec3 normal = normalize(normalMap.rgb * 2.0 - 1.0); + + + vec3 L = normalize(LightDir); + vec3 E = normalize(ViewDir); + vec3 R = reflect(-L, normal); + vec3 H = normalize( L + E ); + + float NdotL = max( dot(normal, L), 0.0 ); + float NdotH = max( dot(normal, H), 0.0 ); + float EdotN = max( dot(normal, E), 0.0 ); + float NdotNegL = max( dot(normal, -L), 0.0 ); + + + vec4 color; + vec3 albedo = baseMap.rgb * C.rgb; + vec3 diffuse = A.rgb + (D.rgb * NdotL); + + + // Emissive + vec3 emissive = vec3(0.0); + if ( hasEmit ) { + emissive += glowColor * glowMult; + } + + // Specular + vec3 spec = clamp( specColor * specStrength * normalMap.a * pow(NdotH, specGlossiness), 0.0, 1.0 ); + spec *= D.rgb; + + vec3 backlight = vec3(0.0); + if ( hasBacklight ) { + backlight = texture2D( BacklightMap, offset ).rgb; + backlight *= NdotNegL; + + emissive += backlight * D.rgb; + } + + vec4 mask = vec4(0.0); + if ( hasRimlight || hasSoftlight ) { + mask = texture2D( LightMask, offset ); + } + + vec3 rim = vec3(0.0); + if ( hasRimlight ) { + rim = mask.rgb * pow(vec3((1.0 - EdotN)), vec3(lightingEffect2)); + rim *= smoothstep( -0.2, 1.0, dot(-L, E) ); + + emissive += rim * D.rgb; + } + + vec3 soft = vec3(0.0); + if ( hasSoftlight ) { + float wrap = (dot(normal, L) + lightingEffect1) / (1.0 + lightingEffect1); + + soft = max( wrap, 0.0 ) * mask.rgb * smoothstep( 1.0, 0.0, NdotL ); + soft *= sqrt( clamp( lightingEffect1, 0.0, 1.0 ) ); + + emissive += soft * D.rgb; + } + + if ( hasTintColor ) { + albedo *= tintColor; + } + + color.rgb = albedo * (diffuse + emissive) + spec; + color.rgb = tonemap( color.rgb ) / tonemap( vec3(1.0) ); + color.a = C.a * baseMap.a; + + gl_FragColor = color; + gl_FragColor.a *= alpha; +} diff --git a/res/shaders/sk_default.prog b/res/shaders/sk_default.prog new file mode 100644 index 000000000..9b064ad51 --- /dev/null +++ b/res/shaders/sk_default.prog @@ -0,0 +1,32 @@ +# default shader + +checkgroup begin or + # Fallout 3 and later + checkgroup begin and + check HEADER/Version >= 0x14020007 + check HEADER/User Version >= 11 + checkgroup end +checkgroup end + +checkgroup begin or + # Skyrim + checkgroup begin and + check BSLightingShaderProperty + check BSLightingShaderProperty/Skyrim Shader Type != 1 + check BSLightingShaderProperty/Skyrim Shader Type != 2 + check BSLightingShaderProperty/Skyrim Shader Type != 3 + check BSLightingShaderProperty/Skyrim Shader Type != 11 + check BSLightingShaderProperty/Skyrim Shader Type != 16 + #check NiTriBasedGeomData/Has Vertex Colors == 1 + checkgroup begin or + check NiTriBasedGeomData/Has Normals == 1 + check BSTriShape/VF & 128 + checkgroup end + checkgroup end +checkgroup end + +texcoords 0 base +texcoords 1 tangents +texcoords 2 bitangents + +shaders sk_default.vert sk_default.frag diff --git a/res/shaders/sk_default.vert b/res/shaders/sk_default.vert new file mode 100644 index 000000000..a3aa1b308 --- /dev/null +++ b/res/shaders/sk_default.vert @@ -0,0 +1,38 @@ +#version 120 + +varying vec3 LightDir; +varying vec3 ViewDir; + +varying vec3 N; +varying vec3 t; +varying vec3 b; +varying vec3 v; + +varying vec4 A; +varying vec4 C; +varying vec4 D; + + +void main( void ) +{ + gl_Position = ftransform(); + gl_TexCoord[0] = gl_MultiTexCoord0; + + N = normalize(gl_NormalMatrix * gl_Normal); + t = normalize(gl_NormalMatrix * gl_MultiTexCoord1.xyz); + b = normalize(gl_NormalMatrix * gl_MultiTexCoord2.xyz); + + // NOTE: b<->t + mat3 tbnMatrix = mat3(b.x, t.x, N.x, + b.y, t.y, N.y, + b.z, t.z, N.z); + + v = vec3(gl_ModelViewMatrix * gl_Vertex); + + ViewDir = tbnMatrix * -v.xyz; + LightDir = tbnMatrix * gl_LightSource[0].position.xyz; + + A = gl_LightSource[0].ambient; + C = gl_Color; + D = gl_LightSource[0].diffuse; +} diff --git a/res/shaders/sk_effectshader.frag b/res/shaders/sk_effectshader.frag new file mode 100644 index 000000000..8f3ff5098 --- /dev/null +++ b/res/shaders/sk_effectshader.frag @@ -0,0 +1,100 @@ +#version 120 + +uniform sampler2D SourceTexture; +uniform sampler2D GreyscaleMap; + +uniform bool doubleSided; + +uniform bool hasSourceTexture; +uniform bool hasGreyscaleMap; +uniform bool greyscaleAlpha; +uniform bool greyscaleColor; + +uniform bool useFalloff; +uniform bool vertexColors; +uniform bool vertexAlpha; + +uniform bool hasWeaponBlood; + +uniform vec4 glowColor; +uniform float glowMult; + +uniform vec2 uvScale; +uniform vec2 uvOffset; + +uniform vec4 falloffParams; +uniform float falloffDepth; + +varying vec3 LightDir; +varying vec3 ViewDir; + +varying vec4 C; + +varying vec3 N; +varying vec3 v; + +vec4 colorLookup( float x, float y ) { + + return texture2D( GreyscaleMap, vec2( clamp(x, 0.0, 1.0), clamp(y, 0.0, 1.0)) ); +} + +void main( void ) +{ + vec4 baseMap = texture2D( SourceTexture, gl_TexCoord[0].st * uvScale + uvOffset ); + + vec4 color; + + vec3 normal = N; + + // Reconstructed normal + //normal = normalize(cross(dFdy(v.xyz), dFdx(v.xyz))); + + //if ( !doubleSided && !gl_FrontFacing ) { return; } + + vec3 E = normalize(ViewDir); + + float tmp2 = falloffDepth; // Unused right now + + // Falloff + float falloff = 1.0; + if ( useFalloff ) { + float startO = min(falloffParams.z, 1.0); + float stopO = max(falloffParams.w, 0.0); + + // TODO: When X and Y are both 0.0 or both 1.0 the effect is reversed. + falloff = smoothstep( falloffParams.y, falloffParams.x, abs(E.b)); + + falloff = mix( max(falloffParams.w, 0.0), min(falloffParams.z, 1.0), falloff ); + } + + float alphaMult = glowColor.a * glowColor.a; + + color.rgb = baseMap.rgb; + color.a = baseMap.a; + + if ( hasWeaponBlood ) { + color.rgb = vec3( 1.0, 0.0, 0.0 ) * baseMap.r; + color.a = baseMap.a * baseMap.g; + } + + color.rgb *= C.rgb * glowColor.rgb; + color.a *= C.a * falloff * alphaMult; + + if ( greyscaleColor ) { + // Only Red emissive channel is used + float emRGB = glowColor.r; + + vec4 luG = colorLookup( baseMap.g, C.g * falloff * emRGB ); + + color.rgb = luG.rgb; + } + + if ( greyscaleAlpha ) { + vec4 luA = colorLookup( baseMap.a, C.a * falloff * alphaMult ); + + color.a = luA.a; + } + + gl_FragColor.rgb = color.rgb * glowMult; + gl_FragColor.a = color.a; +} diff --git a/res/shaders/sk_effectshader.prog b/res/shaders/sk_effectshader.prog new file mode 100644 index 000000000..1c0155242 --- /dev/null +++ b/res/shaders/sk_effectshader.prog @@ -0,0 +1,26 @@ +# normal mapping, vertex colors -> emissive + +checkgroup begin or + # Fallout 3 and later + checkgroup begin and + check HEADER/Version >= 0x14020007 + check HEADER/User Version >= 11 + check HEADER/User Version 2 != 130 + checkgroup end + +checkgroup end + +checkgroup begin or + # Skyrim + checkgroup begin and + #check not NiAlphaProperty + check BSEffectShaderProperty + checkgroup end + +checkgroup end + +texcoords 0 base +texcoords 1 tangents +texcoords 2 bitangents + +shaders sk_effectshader.vert sk_effectshader.frag diff --git a/res/shaders/sk_effectshader.vert b/res/shaders/sk_effectshader.vert new file mode 100644 index 000000000..afd493892 --- /dev/null +++ b/res/shaders/sk_effectshader.vert @@ -0,0 +1,33 @@ +#version 120 + +varying vec3 LightDir; +varying vec3 ViewDir; + +varying vec4 C; + +varying vec3 N; +varying vec3 t; +varying vec3 b; +varying vec3 v; + +void main( void ) +{ + gl_Position = ftransform(); + gl_TexCoord[0] = gl_MultiTexCoord0; + + N = normalize(gl_NormalMatrix * gl_Normal); + t = normalize(gl_NormalMatrix * gl_MultiTexCoord1.xyz); + b = normalize(gl_NormalMatrix * gl_MultiTexCoord2.xyz); + + // NOTE: b<->t + mat3 tbnMatrix = mat3(b.x, t.x, N.x, + b.y, t.y, N.y, + b.z, t.z, N.z); + + v = vec3(gl_ModelViewMatrix * gl_Vertex); + + ViewDir = tbnMatrix * -v.xyz; + LightDir = tbnMatrix * gl_LightSource[0].position.xyz; + + C = gl_Color; +} \ No newline at end of file diff --git a/res/shaders/sk_env.frag b/res/shaders/sk_env.frag new file mode 100644 index 000000000..b8df175df --- /dev/null +++ b/res/shaders/sk_env.frag @@ -0,0 +1,158 @@ +#version 120 + +uniform sampler2D BaseMap; +uniform sampler2D NormalMap; +uniform sampler2D LightMask; +uniform sampler2D BacklightMap; +uniform sampler2D EnvironmentMap; +uniform samplerCube CubeMap; + +uniform vec3 specColor; +uniform float specStrength; +uniform float specGlossiness; + +uniform vec3 glowColor; +uniform float glowMult; + +uniform float alpha; + +uniform vec2 uvScale; +uniform vec2 uvOffset; + +uniform bool hasEmit; +uniform bool hasSoftlight; +uniform bool hasBacklight; +uniform bool hasRimlight; +uniform bool hasCubeMap; +uniform bool hasEnvMask; + +uniform float lightingEffect1; +uniform float lightingEffect2; + +uniform float envReflection; + +uniform mat4 worldMatrix; + +varying vec3 LightDir; +varying vec3 ViewDir; + +varying vec4 A; +varying vec4 C; +varying vec4 D; + +varying vec3 N; +varying vec3 t; +varying vec3 b; + + +vec3 tonemap(vec3 x) +{ + float _A = 0.15; + float _B = 0.50; + float _C = 0.10; + float _D = 0.20; + float _E = 0.02; + float _F = 0.30; + + return ((x*(_A*x+_C*_B)+_D*_E)/(x*(_A*x+_B)+_D*_F))-_E/_F; +} + +vec3 toGrayscale(vec3 color) +{ + return vec3(dot(vec3(0.3, 0.59, 0.11), color)); +} + +void main( void ) +{ + vec2 offset = gl_TexCoord[0].st * uvScale + uvOffset; + + vec4 baseMap = texture2D( BaseMap, offset ); + vec4 normalMap = texture2D( NormalMap, offset ); + + vec3 normal = normalize(normalMap.rgb * 2.0 - 1.0); + + + vec3 L = normalize(LightDir); + vec3 E = normalize(ViewDir); + vec3 R = reflect(-L, normal); + vec3 H = normalize( L + E ); + + float NdotL = max( dot(normal, L), 0.0 ); + float NdotH = max( dot(normal, H), 0.0 ); + float EdotN = max( dot(normal, E), 0.0 ); + float NdotNegL = max( dot(normal, -L), 0.0 ); + + vec3 reflected = reflect( -E, normal ); + vec3 reflectedVS = b * reflected.x + t * reflected.y + N * reflected.z; + vec3 reflectedWS = vec3( worldMatrix * (gl_ModelViewMatrixInverse * vec4( reflectedVS, 0.0 )) ); + + + vec4 color; + vec3 albedo = baseMap.rgb * C.rgb; + vec3 diffuse = A.rgb + (D.rgb * NdotL); + + + // Environment + if ( hasCubeMap ) { + vec4 cube = textureCube( CubeMap, reflectedWS ); + cube.rgb *= envReflection; + + if ( hasEnvMask ) { + vec4 env = texture2D( EnvironmentMap, offset ); + cube.rgb *= env.r; + } else { + cube.rgb *= normalMap.a; + } + + + albedo += cube.rgb; + } + + // Emissive + vec3 emissive = vec3(0.0); + if ( hasEmit ) { + emissive += glowColor * glowMult; + } + + // Specular + vec3 spec = clamp( specColor * specStrength * normalMap.a * pow(NdotH, specGlossiness), 0.0, 1.0 ); + spec *= D.rgb; + + vec3 backlight = vec3(0.0); + if ( hasBacklight ) { + backlight = texture2D( BacklightMap, offset ).rgb; + backlight *= NdotNegL; + + emissive += backlight * D.rgb; + } + + vec4 mask = vec4(0.0); + if ( hasRimlight || hasSoftlight ) { + mask = texture2D( LightMask, offset ); + } + + vec3 rim = vec3(0.0); + if ( hasRimlight ) { + rim = mask.rgb * pow(vec3((1.0 - EdotN)), vec3(lightingEffect2)); + rim *= smoothstep( -0.2, 1.0, dot(-L, E) ); + + emissive += rim * D.rgb; + } + + vec3 soft = vec3(0.0); + if ( hasSoftlight ) { + float wrap = (dot(normal, L) + lightingEffect1) / (1.0 + lightingEffect1); + + soft = max( wrap, 0.0 ) * mask.rgb * smoothstep( 1.0, 0.0, NdotL ); + soft *= sqrt( clamp( lightingEffect1, 0.0, 1.0 ) ); + + emissive += soft * D.rgb; + } + + color.rgb = albedo * (diffuse + emissive) + spec; + color.rgb = tonemap( color.rgb ) / tonemap( vec3(1.0) ); + color.a = C.a * baseMap.a; + + gl_FragColor = color; + gl_FragColor.a *= alpha; +} diff --git a/res/shaders/sk_env.prog b/res/shaders/sk_env.prog new file mode 100644 index 000000000..f1cd419d4 --- /dev/null +++ b/res/shaders/sk_env.prog @@ -0,0 +1,30 @@ +# multi-layer parallax + +checkgroup begin or + # Fallout 3 and later + checkgroup begin and + check HEADER/Version >= 0x14020007 + check HEADER/User Version >= 11 + checkgroup end +checkgroup end + +checkgroup begin or + # Skyrim + checkgroup begin and + check BSLightingShaderProperty + checkgroup begin or + check NiTriBasedGeomData/Has Normals == 1 + check BSTriShape/VF & 128 + checkgroup end + checkgroup begin or + check BSLightingShaderProperty/Skyrim Shader Type == 1 + check BSLightingShaderProperty/Skyrim Shader Type == 16 + checkgroup end + checkgroup end +checkgroup end + +texcoords 0 base +texcoords 1 tangents +texcoords 2 bitangents + +shaders sk_env.vert sk_env.frag diff --git a/res/shaders/sk_env.vert b/res/shaders/sk_env.vert new file mode 100644 index 000000000..a3aa1b308 --- /dev/null +++ b/res/shaders/sk_env.vert @@ -0,0 +1,38 @@ +#version 120 + +varying vec3 LightDir; +varying vec3 ViewDir; + +varying vec3 N; +varying vec3 t; +varying vec3 b; +varying vec3 v; + +varying vec4 A; +varying vec4 C; +varying vec4 D; + + +void main( void ) +{ + gl_Position = ftransform(); + gl_TexCoord[0] = gl_MultiTexCoord0; + + N = normalize(gl_NormalMatrix * gl_Normal); + t = normalize(gl_NormalMatrix * gl_MultiTexCoord1.xyz); + b = normalize(gl_NormalMatrix * gl_MultiTexCoord2.xyz); + + // NOTE: b<->t + mat3 tbnMatrix = mat3(b.x, t.x, N.x, + b.y, t.y, N.y, + b.z, t.z, N.z); + + v = vec3(gl_ModelViewMatrix * gl_Vertex); + + ViewDir = tbnMatrix * -v.xyz; + LightDir = tbnMatrix * gl_LightSource[0].position.xyz; + + A = gl_LightSource[0].ambient; + C = gl_Color; + D = gl_LightSource[0].diffuse; +} diff --git a/res/shaders/sk_glowmap.frag b/res/shaders/sk_glowmap.frag new file mode 100644 index 000000000..a342d22d4 --- /dev/null +++ b/res/shaders/sk_glowmap.frag @@ -0,0 +1,133 @@ +#version 120 + +uniform sampler2D BaseMap; +uniform sampler2D NormalMap; +uniform sampler2D GlowMap; +uniform sampler2D LightMask; +uniform sampler2D BacklightMap; + +uniform bool hasGlowMap; +uniform vec3 glowColor; +uniform float glowMult; + +uniform vec3 specColor; +uniform float specStrength; +uniform float specGlossiness; + +uniform float alpha; + +uniform vec2 uvScale; +uniform vec2 uvOffset; + +uniform bool hasEmit; +uniform bool hasSoftlight; +uniform bool hasBacklight; +uniform bool hasRimlight; + +uniform float lightingEffect1; +uniform float lightingEffect2; + +varying vec3 LightDir; +varying vec3 ViewDir; + +varying vec4 A; +varying vec4 C; +varying vec4 D; + + +vec3 tonemap(vec3 x) +{ + float _A = 0.15; + float _B = 0.50; + float _C = 0.10; + float _D = 0.20; + float _E = 0.02; + float _F = 0.30; + + return ((x*(_A*x+_C*_B)+_D*_E)/(x*(_A*x+_B)+_D*_F))-_E/_F; +} + +vec3 toGrayscale(vec3 color) +{ + return vec3(dot(vec3(0.3, 0.59, 0.11), color)); +} + +void main( void ) +{ + vec2 offset = gl_TexCoord[0].st * uvScale + uvOffset; + + vec4 baseMap = texture2D( BaseMap, offset ); + vec4 normalMap = texture2D( NormalMap, offset ); + vec4 glowMap = texture2D( GlowMap, offset ); + + vec3 normal = normalize(normalMap.rgb * 2.0 - 1.0); + + + vec3 L = normalize(LightDir); + vec3 E = normalize(ViewDir); + vec3 R = reflect(-L, normal); + vec3 H = normalize( L + E ); + + float NdotL = max( dot(normal, L), 0.0 ); + float NdotH = max( dot(normal, H), 0.0 ); + float EdotN = max( dot(normal, E), 0.0 ); + float NdotNegL = max( dot(normal, -L), 0.0 ); + + + vec4 color; + vec3 albedo = baseMap.rgb * C.rgb; + vec3 diffuse = A.rgb + (D.rgb * NdotL); + + + // Emissive & Glow + vec3 emissive = vec3(0.0); + if ( hasEmit ) { + emissive += glowColor * glowMult; + + if ( hasGlowMap ) { + emissive *= glowMap.rgb; + } + } + + // Specular + vec3 spec = clamp( specColor * specStrength * normalMap.a * pow(NdotH, specGlossiness), 0.0, 1.0 ); + spec *= D.rgb; + + vec3 backlight = vec3(0.0); + if ( hasBacklight ) { + backlight = texture2D( BacklightMap, offset ).rgb; + backlight *= NdotNegL; + + emissive += backlight * D.rgb; + } + + vec4 mask = vec4(0.0); + if ( hasRimlight || hasSoftlight ) { + mask = texture2D( LightMask, offset ); + } + + vec3 rim = vec3(0.0); + if ( hasRimlight ) { + rim = mask.rgb * pow(vec3((1.0 - EdotN)), vec3(lightingEffect2)); + rim *= smoothstep( -0.2, 1.0, dot(-L, E) ); + + emissive += rim * D.rgb; + } + + vec3 soft = vec3(0.0); + if ( hasSoftlight ) { + float wrap = (dot(normal, L) + lightingEffect1) / (1.0 + lightingEffect1); + + soft = max( wrap, 0.0 ) * mask.rgb * smoothstep( 1.0, 0.0, NdotL ); + soft *= sqrt( clamp( lightingEffect1, 0.0, 1.0 ) ); + + emissive += soft * D.rgb; + } + + color.rgb = albedo * (diffuse + emissive) + spec; + color.rgb = tonemap( color.rgb ) / tonemap( vec3(1.0) ); + color.a = C.a * baseMap.a; + + gl_FragColor = color; + gl_FragColor.a *= alpha; +} diff --git a/res/shaders/sk_glowmap.prog b/res/shaders/sk_glowmap.prog new file mode 100644 index 000000000..aed124697 --- /dev/null +++ b/res/shaders/sk_glowmap.prog @@ -0,0 +1,27 @@ +# glow mapping + +checkgroup begin or + # Fallout 3 and later + checkgroup begin and + check HEADER/Version >= 0x14020007 + check HEADER/User Version >= 11 + checkgroup end +checkgroup end + +checkgroup begin or + # Skyrim + checkgroup begin and + check BSLightingShaderProperty + check BSLightingShaderProperty/Skyrim Shader Type == 2 + checkgroup begin or + check NiTriBasedGeomData/Has Normals == 1 + check BSTriShape/VF & 128 + checkgroup end + checkgroup end +checkgroup end + +texcoords 0 base +texcoords 1 tangents +texcoords 2 bitangents + +shaders sk_glowmap.vert sk_glowmap.frag diff --git a/res/shaders/sk_glowmap.vert b/res/shaders/sk_glowmap.vert new file mode 100644 index 000000000..88ee1720c --- /dev/null +++ b/res/shaders/sk_glowmap.vert @@ -0,0 +1,38 @@ +#version 120 + +varying vec3 LightDir; +varying vec3 ViewDir; + +varying vec3 N; +varying vec3 t; +varying vec3 b; +varying vec3 v; + +varying vec4 A; +varying vec4 C; +varying vec4 D; + + +void main( void ) +{ + gl_Position = ftransform(); + gl_TexCoord[0] = gl_MultiTexCoord0; + + N = normalize(gl_NormalMatrix * gl_Normal); + t = normalize(gl_NormalMatrix * gl_MultiTexCoord1.xyz); + b = normalize(gl_NormalMatrix * gl_MultiTexCoord2.xyz); + + // NOTE: b<->t + mat3 tbnMatrix = mat3(b.x, t.x, N.x, + b.y, t.y, N.y, + b.z, t.z, N.z); + + v = vec3(gl_ModelViewMatrix * gl_Vertex); + + ViewDir = tbnMatrix * -v.xyz; + LightDir = tbnMatrix * gl_LightSource[0].position.xyz; + + A = gl_LightSource[0].ambient; + C = gl_Color; + D = gl_LightSource[0].diffuse; +} diff --git a/res/shaders/sk_msn.frag b/res/shaders/sk_msn.frag new file mode 100644 index 000000000..2c6aca49a --- /dev/null +++ b/res/shaders/sk_msn.frag @@ -0,0 +1,195 @@ +#version 120 + +uniform sampler2D BaseMap; +uniform sampler2D NormalMap; +uniform sampler2D SpecularMap; +uniform sampler2D LightMask; +uniform sampler2D TintMask; +uniform sampler2D DetailMask; +uniform sampler2D BacklightMap; + +uniform vec3 specColor; +uniform float specStrength; +uniform float specGlossiness; + +uniform vec3 glowColor; +uniform float glowMult; + +uniform float alpha; + +uniform vec3 tintColor; + +uniform vec2 uvScale; +uniform vec2 uvOffset; + +uniform bool hasEmit; +uniform bool hasSoftlight; +uniform bool hasBacklight; +uniform bool hasRimlight; +uniform bool hasModelSpaceNormals; +uniform bool hasSpecularMap; +uniform bool hasDetailMask; +uniform bool hasTintMask; +uniform bool hasTintColor; + +uniform float lightingEffect1; +uniform float lightingEffect2; + +uniform mat4 viewMatrix; +uniform mat4 viewMatrixInverse; + +varying vec3 v; + +varying vec3 LightDir; +varying vec3 ViewDir; + +varying vec4 A; +varying vec4 C; +varying vec4 D; + + +vec3 tonemap(vec3 x) +{ + float _A = 0.15; + float _B = 0.50; + float _C = 0.10; + float _D = 0.20; + float _E = 0.02; + float _F = 0.30; + + return ((x*(_A*x+_C*_B)+_D*_E)/(x*(_A*x+_B)+_D*_F))-_E/_F; +} + +vec3 toGrayscale(vec3 color) +{ + return vec3(dot(vec3(0.3, 0.59, 0.11), color)); +} + +float overlay( float base, float blend ) +{ + float result; + if ( base < 0.5 ) { + result = 2.0 * base * blend; + } else { + result = 1.0 - 2.0 * (1.0 - blend) * (1.0 - base); + } + return result; +} + +vec3 overlay( vec3 ba, vec3 bl ) +{ + return vec3( overlay(ba.r, bl.r), overlay(ba.g, bl.g), overlay( ba.b, bl.b ) ); +} + +void main( void ) +{ + vec2 offset = gl_TexCoord[0].st * uvScale + uvOffset; + + vec4 baseMap = texture2D( BaseMap, offset ); + vec4 normalMap = texture2D( NormalMap, offset ); + + vec3 normal = normalMap.rgb * 2.0 - 1.0; + + // Convert model space to view space + // Swizzled G/B values! + normal = normalize( vec3( viewMatrix * vec4( normal.rbg, 0.0 ))); + + // Face Normals + //vec3 X = dFdx(v); + //vec3 Y = dFdy(v); + //vec3 constructedNormal = normalize(cross(X,Y)); + + + vec3 L = normalize(LightDir); + vec3 E = normalize(ViewDir); + vec3 R = reflect(-L, normal); + vec3 H = normalize( L + E ); + + float NdotL = max( dot(normal, L), 0.0 ); + float NdotH = max( dot(normal, H), 0.0 ); + float EdotN = max( dot(normal, E), 0.0 ); + float NdotNegL = max( dot(normal, -L), 0.0 ); + + + vec4 color; + vec3 albedo = baseMap.rgb * C.rgb; + vec3 diffuse = A.rgb + (D.rgb * NdotL); + + + // Emissive + vec3 emissive = vec3(0.0); + if ( hasEmit ) { + emissive += glowColor * glowMult; + } + + // Specular + + float s = texture2D( SpecularMap, offset ).r; + if ( !hasSpecularMap || hasBacklight ) { + s = normalMap.a; + } + + vec3 spec = clamp( specColor * specStrength * s * pow(NdotH, specGlossiness), 0.0, 1.0 ); + spec *= D.rgb; + + + vec3 backlight = vec3(0.0); + if ( hasBacklight ) { + backlight = texture2D( BacklightMap, offset ).rgb; + backlight *= NdotNegL; + + emissive += backlight * D.rgb; + } + + vec4 mask = vec4(0.0); + if ( hasRimlight || hasSoftlight ) { + mask = texture2D( LightMask, offset ); + } + + vec3 rim = vec3(0.0); + if ( hasRimlight ) { + rim = mask.rgb * pow(vec3((1.0 - EdotN)), vec3(lightingEffect2)); + rim *= smoothstep( -0.2, 1.0, dot(-L, E) ); + + emissive += rim * D.rgb; + } + + vec3 soft = vec3(0.0); + if ( hasSoftlight ) { + float wrap = (dot(normal, L) + lightingEffect1) / (1.0 + lightingEffect1); + + soft = max( wrap, 0.0 ) * mask.rgb * smoothstep( 1.0, 0.0, NdotL ); + soft *= sqrt( clamp( lightingEffect1, 0.0, 1.0 ) ); + + emissive += soft * D.rgb; + } + + vec3 detail = vec3(0.0); + if ( hasDetailMask ) { + detail = texture2D( DetailMask, offset ).rgb; + + albedo = overlay( albedo, detail ); + } + + vec3 tint = vec3(0.0); + if ( hasTintMask ) { + tint = texture2D( TintMask, offset ).rgb; + + albedo = overlay( albedo, tint ); + } + + if ( hasDetailMask ) { + albedo += albedo; + } + + if ( hasTintColor ) { + albedo *= tintColor; + } + + color.rgb = albedo * (diffuse + emissive) + spec; + color.rgb = tonemap( color.rgb ) / tonemap( vec3(1.0) ); + color.a = C.a * baseMap.a; + + gl_FragColor = color; + gl_FragColor.a *= alpha; +} diff --git a/res/shaders/sk_msn.prog b/res/shaders/sk_msn.prog new file mode 100644 index 000000000..66560dc3f --- /dev/null +++ b/res/shaders/sk_msn.prog @@ -0,0 +1,26 @@ +# model space normal mapping + +checkgroup begin or + # Fallout 3 and later + checkgroup begin and + check HEADER/Version >= 0x14020007 + check HEADER/User Version >= 11 + checkgroup end +checkgroup end + +checkgroup begin or + # Skyrim + checkgroup begin and + check BSLightingShaderProperty + checkgroup begin or + check NiTriBasedGeomData/Has Normals == 0 + check BSTriShape/VF !& 128 + checkgroup end + checkgroup end +checkgroup end + +texcoords 0 base +#texcoords 1 tangents +#texcoords 2 bitangents + +shaders sk_msn.vert sk_msn.frag \ No newline at end of file diff --git a/res/shaders/sk_msn.vert b/res/shaders/sk_msn.vert new file mode 100644 index 000000000..bdbd93bea --- /dev/null +++ b/res/shaders/sk_msn.vert @@ -0,0 +1,26 @@ +#version 120 + +varying vec3 LightDir; +varying vec3 ViewDir; + +varying vec3 v; + +varying vec4 A; +varying vec4 C; +varying vec4 D; + + +void main( void ) +{ + gl_Position = ftransform(); + gl_TexCoord[0] = gl_MultiTexCoord0; + + v = vec3(gl_ModelViewMatrix * gl_Vertex); + + ViewDir = -v.xyz; + LightDir = gl_LightSource[0].position.xyz; + + A = gl_LightSource[0].ambient; + C = gl_Color; + D = gl_LightSource[0].diffuse; +} diff --git a/res/shaders/sk_multilayer.frag b/res/shaders/sk_multilayer.frag new file mode 100644 index 000000000..2a4e85944 --- /dev/null +++ b/res/shaders/sk_multilayer.frag @@ -0,0 +1,214 @@ +#version 120 + +uniform sampler2D BaseMap; +uniform sampler2D NormalMap; +uniform sampler2D LightMask; +uniform sampler2D BacklightMap; +uniform sampler2D InnerMap; +uniform sampler2D EnvironmentMap; +uniform samplerCube CubeMap; + +uniform vec3 specColor; +uniform float specStrength; +uniform float specGlossiness; + +uniform vec3 glowColor; +uniform float glowMult; + +uniform float alpha; + +uniform vec2 uvScale; +uniform vec2 uvOffset; + +uniform bool hasEmit; +uniform bool hasSoftlight; +uniform bool hasBacklight; +uniform bool hasRimlight; +uniform bool hasCubeMap; +uniform bool hasEnvMask; + +uniform float lightingEffect1; +uniform float lightingEffect2; + +uniform vec2 innerScale; +uniform float innerThickness; +uniform float outerRefraction; +uniform float outerReflection; + +uniform mat4 worldMatrix; + +varying vec3 LightDir; +varying vec3 ViewDir; + +varying vec4 A; +varying vec4 C; +varying vec4 D; + +varying vec3 N; +varying vec3 t; +varying vec3 b; + + +vec3 tonemap(vec3 x) +{ + float _A = 0.15; + float _B = 0.50; + float _C = 0.10; + float _D = 0.20; + float _E = 0.02; + float _F = 0.30; + + return ((x*(_A*x+_C*_B)+_D*_E)/(x*(_A*x+_B)+_D*_F))-_E/_F; +} + +vec3 toGrayscale(vec3 color) +{ + return vec3(dot(vec3(0.3, 0.59, 0.11), color)); +} + +// Compute inner layer’s texture coordinate and transmission depth +// vTexCoord: Outer layer’s texture coordinate +// vInnerScale: Tiling of inner texture +// vViewTS: View vector in tangent space +// vNormalTS: Normal in tangent space (sampled normal map) +// fLayerThickness: Distance from outer layer to inner layer +vec3 ParallaxOffsetAndDepth( vec2 vTexCoord, vec2 vInnerScale, vec3 vViewTS, vec3 vNormalTS, float fLayerThickness ) +{ + // Tangent space reflection vector + vec3 vReflectionTS = reflect( -vViewTS, vNormalTS ); + // Tangent space transmission vector (reflect about surface plane) + vec3 vTransTS = vec3( vReflectionTS.xy, -vReflectionTS.z ); + + // Distance along transmission vector to intersect inner layer + float fTransDist = fLayerThickness / abs(vTransTS.z); + + // Texel size + // Bethesda's version does indeed seem to assume 1024, which is why they + // introduced the additional parameter. + vec2 vTexelSize = vec2( 1.0/(1024.0 * vInnerScale.x), 1.0/(1024.0 * vInnerScale.y) ); + + // Inner layer’s texture coordinate due to parallax + vec2 vOffset = vTexelSize * fTransDist * vTransTS.xy; + vec2 vOffsetTexCoord = vTexCoord + vOffset; + + // Return offset texture coordinate in xy and transmission dist in z + return vec3( vOffsetTexCoord, fTransDist ); +} + +void main( void ) +{ + vec2 offset = gl_TexCoord[0].st * uvScale + uvOffset; + + vec4 baseMap = texture2D( BaseMap, offset ); + vec4 normalMap = texture2D( NormalMap, offset ); + + vec3 normal = normalize(normalMap.rgb * 2.0 - 1.0); + + // Sample the non-parallax offset alpha channel of the inner map + // Used to modulate the innerThickness + float innerMapAlpha = texture2D( InnerMap, offset ).a; + + + vec3 L = normalize(LightDir); + vec3 E = normalize(ViewDir); + vec3 R = reflect(-L, normal); + vec3 H = normalize( L + E ); + + float NdotL = max( dot(normal, L), 0.0 ); + float NdotH = max( dot(normal, H), 0.0 ); + float EdotN = max( dot(normal, E), 0.0 ); + float NdotNegL = max( dot(normal, -L), 0.0 ); + + + // Mix between the face normal and the normal map based on the refraction scale + vec3 mixedNormal = mix( vec3(0.0, 0.0, 1.0), normal, clamp( outerRefraction, 0.0, 1.0 ) ); + vec3 parallax = ParallaxOffsetAndDepth( offset, innerScale, E, mixedNormal, innerThickness * innerMapAlpha ); + + // Sample the inner map at the offset coords + vec4 innerMap = texture2D( InnerMap, parallax.xy * innerScale ); + + vec3 reflected = reflect( -E, normal ); + vec3 reflectedVS = b * reflected.x + t * reflected.y + N * reflected.z; + vec3 reflectedWS = vec3( worldMatrix * (gl_ModelViewMatrixInverse * vec4( reflectedVS, 0.0 )) ); + + + vec4 color; + vec3 albedo; + vec3 diffuse = A.rgb + (D.rgb * NdotL); + vec3 inner = innerMap.rgb * C.rgb; + vec3 outer = baseMap.rgb * C.rgb; + + + // Mix inner/outer layer based on fresnel + float outerMix = max( 1.0 - EdotN, baseMap.a ); + albedo = mix( inner, outer, outerMix ); + + + // Environment + if ( hasCubeMap ) { + vec4 cube = textureCube( CubeMap, reflectedWS ); + cube.rgb *= outerReflection; + + if ( hasEnvMask ) { + vec4 env = texture2D( EnvironmentMap, offset ); + cube.rgb *= env.r; + } else { + cube.rgb *= normalMap.a; + } + + albedo += cube.rgb; + } + + // Specular + vec3 spec = clamp( specColor * specStrength * normalMap.a * pow(NdotH, specGlossiness), 0.0, 1.0 ); + spec *= D.rgb; + + // Emissive + // Mixed with outer map + vec3 emissive = vec3(0.0); + if ( hasEmit ) { + emissive += glowColor * glowMult; + } + + // Backlight + // Mixed with inner and outer map + vec3 backlight = vec3(0.0); + if ( hasBacklight ) { + backlight = texture2D( BacklightMap, offset ).rgb; + backlight *= NdotNegL; + + emissive += backlight * D.rgb; + } + + // TODO: Test rim and soft light mixing with inner/outer layer + + vec4 mask = vec4(0.0); + if ( hasRimlight || hasSoftlight ) { + mask = texture2D( LightMask, offset ); + } + + vec3 rim = vec3(0.0); + if ( hasRimlight ) { + rim = mask.rgb * pow(vec3((1.0 - EdotN)), vec3(lightingEffect2)); + rim *= smoothstep( -0.2, 1.0, dot(-L, E) ); + + emissive += rim * D.rgb; + } + + vec3 soft = vec3(0.0); + if ( hasSoftlight ) { + float wrap = (dot(normal, L) + lightingEffect1) / (1.0 + lightingEffect1); + + soft = max( wrap, 0.0 ) * mask.rgb * smoothstep( 1.0, 0.0, NdotL ); + soft *= sqrt( clamp( lightingEffect1, 0.0, 1.0 ) ); + + emissive += soft * D.rgb; + } + + color.rgb = albedo * (diffuse + emissive) + spec; + color.rgb = tonemap( color.rgb ) / tonemap( vec3(1.0) ); + color.a = C.a * baseMap.a; + + gl_FragColor = color; + gl_FragColor.a *= alpha; +} diff --git a/res/shaders/sk_multilayer.prog b/res/shaders/sk_multilayer.prog new file mode 100644 index 000000000..336e105d7 --- /dev/null +++ b/res/shaders/sk_multilayer.prog @@ -0,0 +1,27 @@ +# multi-layer parallax + +checkgroup begin or + # Fallout 3 and later + checkgroup begin and + check HEADER/Version >= 0x14020007 + check HEADER/User Version >= 11 + checkgroup end +checkgroup end + +checkgroup begin or + # Skyrim + checkgroup begin and + check BSLightingShaderProperty + check BSLightingShaderProperty/Skyrim Shader Type == 11 + checkgroup begin or + check NiTriBasedGeomData/Has Normals == 1 + check BSTriShape/VF & 128 + checkgroup end + checkgroup end +checkgroup end + +texcoords 0 base +texcoords 1 tangents +texcoords 2 bitangents + +shaders sk_multilayer.vert sk_multilayer.frag diff --git a/res/shaders/sk_multilayer.vert b/res/shaders/sk_multilayer.vert new file mode 100644 index 000000000..88ee1720c --- /dev/null +++ b/res/shaders/sk_multilayer.vert @@ -0,0 +1,38 @@ +#version 120 + +varying vec3 LightDir; +varying vec3 ViewDir; + +varying vec3 N; +varying vec3 t; +varying vec3 b; +varying vec3 v; + +varying vec4 A; +varying vec4 C; +varying vec4 D; + + +void main( void ) +{ + gl_Position = ftransform(); + gl_TexCoord[0] = gl_MultiTexCoord0; + + N = normalize(gl_NormalMatrix * gl_Normal); + t = normalize(gl_NormalMatrix * gl_MultiTexCoord1.xyz); + b = normalize(gl_NormalMatrix * gl_MultiTexCoord2.xyz); + + // NOTE: b<->t + mat3 tbnMatrix = mat3(b.x, t.x, N.x, + b.y, t.y, N.y, + b.z, t.z, N.z); + + v = vec3(gl_ModelViewMatrix * gl_Vertex); + + ViewDir = tbnMatrix * -v.xyz; + LightDir = tbnMatrix * gl_LightSource[0].position.xyz; + + A = gl_LightSource[0].ambient; + C = gl_Color; + D = gl_LightSource[0].diffuse; +} diff --git a/res/shaders/sk_parallax.frag b/res/shaders/sk_parallax.frag new file mode 100644 index 000000000..1435bd469 --- /dev/null +++ b/res/shaders/sk_parallax.frag @@ -0,0 +1,130 @@ +#version 120 + +uniform sampler2D BaseMap; +uniform sampler2D NormalMap; +uniform sampler2D HeightMap; +uniform sampler2D LightMask; +uniform sampler2D BacklightMap; + +uniform vec3 specColor; +uniform float specStrength; +uniform float specGlossiness; + +uniform vec3 glowColor; +uniform float glowMult; + +uniform float alpha; + +uniform vec2 uvScale; +uniform vec2 uvOffset; + +uniform bool hasEmit; +uniform bool hasSoftlight; +uniform bool hasBacklight; +uniform bool hasRimlight; + +uniform float lightingEffect1; +uniform float lightingEffect2; + +varying vec3 LightDir; +varying vec3 ViewDir; + +varying vec4 A; +varying vec4 C; +varying vec4 D; + + +vec3 tonemap(vec3 x) +{ + float _A = 0.15; + float _B = 0.50; + float _C = 0.10; + float _D = 0.20; + float _E = 0.02; + float _F = 0.30; + + return ((x*(_A*x+_C*_B)+_D*_E)/(x*(_A*x+_B)+_D*_F))-_E/_F; +} + +vec3 toGrayscale(vec3 color) +{ + return vec3(dot(vec3(0.3, 0.59, 0.11), color)); +} + +void main( void ) +{ + vec2 offset = gl_TexCoord[0].st * uvScale + uvOffset; + + vec3 E = normalize(ViewDir); + + float height = texture2D( HeightMap, offset ).r; + offset += E.xy * (height * 0.08 - 0.04); + + vec4 baseMap = texture2D( BaseMap, offset ); + vec4 normalMap = texture2D( NormalMap, offset ); + + vec3 normal = normalize(normalMap.rgb * 2.0 - 1.0); + + vec3 L = normalize(LightDir); + vec3 R = reflect(-L, normal); + vec3 H = normalize( L + E ); + + float NdotL = max( dot(normal, L), 0.0 ); + float NdotH = max( dot(normal, H), 0.0 ); + float EdotN = max( dot(normal, E), 0.0 ); + float NdotNegL = max( dot(normal, -L), 0.0 ); + + + vec4 color; + vec3 albedo = baseMap.rgb * C.rgb; + vec3 diffuse = A.rgb + (D.rgb * NdotL); + + + // Emissive + vec3 emissive = vec3(0.0); + if ( hasEmit ) { + emissive += glowColor * glowMult; + } + + // Specular + vec3 spec = clamp( specColor * specStrength * normalMap.a * pow(NdotH, specGlossiness), 0.0, 1.0 ); + spec *= D.rgb; + + vec3 backlight = vec3(0.0); + if ( hasBacklight ) { + backlight = texture2D( BacklightMap, offset ).rgb; + backlight *= NdotNegL; + + emissive += backlight * D.rgb; + } + + vec4 mask = vec4(0.0); + if ( hasRimlight || hasSoftlight ) { + mask = texture2D( LightMask, offset ); + } + + vec3 rim = vec3(0.0); + if ( hasRimlight ) { + rim = mask.rgb * pow(vec3((1.0 - EdotN)), vec3(lightingEffect2)); + rim *= smoothstep( -0.2, 1.0, dot(-L, E) ); + + emissive += rim * D.rgb; + } + + vec3 soft = vec3(0.0); + if ( hasSoftlight ) { + float wrap = (dot(normal, L) + lightingEffect1) / (1.0 + lightingEffect1); + + soft = max( wrap, 0.0 ) * mask.rgb * smoothstep( 1.0, 0.0, NdotL ); + soft *= sqrt( clamp( lightingEffect1, 0.0, 1.0 ) ); + + emissive += soft * D.rgb; + } + + color.rgb = albedo * (diffuse + emissive) + spec; + color.rgb = tonemap( color.rgb ) / tonemap( vec3(1.0) ); + color.a = C.a * baseMap.a; + + gl_FragColor = color; + gl_FragColor.a *= alpha; +} diff --git a/res/shaders/sk_parallax.prog b/res/shaders/sk_parallax.prog new file mode 100644 index 000000000..b6fcb9a8c --- /dev/null +++ b/res/shaders/sk_parallax.prog @@ -0,0 +1,27 @@ +# default shader + +checkgroup begin or + # Fallout 3 and later + checkgroup begin and + check HEADER/Version >= 0x14020007 + check HEADER/User Version >= 11 + checkgroup end +checkgroup end + +checkgroup begin or + # Skyrim + checkgroup begin and + check BSLightingShaderProperty + check BSLightingShaderProperty/Skyrim Shader Type == 3 + checkgroup begin or + check NiTriBasedGeomData/Has Normals == 1 + check BSTriShape/VF & 128 + checkgroup end + checkgroup end +checkgroup end + +texcoords 0 base +texcoords 1 tangents +texcoords 2 bitangents + +shaders sk_default.vert sk_parallax.frag diff --git a/res/shaders/white.dds b/res/shaders/white.dds new file mode 100644 index 000000000..505328636 Binary files /dev/null and b/res/shaders/white.dds differ diff --git a/src/basemodel.cpp b/src/basemodel.cpp index 3edcab4e9..f316b10cd 100644 --- a/src/basemodel.cpp +++ b/src/basemodel.cpp @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -31,7 +31,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "basemodel.h" -#include "options.h" +#include "settings.h" #include "niftypes.h" @@ -41,12 +41,18 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -//! \file basemodel.cpp BaseModel and BaseModelEval -BaseModel::BaseModel( QObject * parent ) : QAbstractItemModel( parent ) +//! @file basemodel.cpp Abstract base class for NIF data models + +/* + * BaseModel + */ + +BaseModel::BaseModel( QObject * p ) : QAbstractItemModel( p ) { - msgMode = EmitMessages; root = new NifItem( 0 ); + parentWindow = qobject_cast(p); + msgMode = TstMessage; } BaseModel::~BaseModel() @@ -54,19 +60,58 @@ BaseModel::~BaseModel() delete root; } -void BaseModel::msg( const Message & m ) const +QWidget * BaseModel::getWindow() { - switch ( msgMode ) { - case EmitMessages: - emit sigMessage( m ); - return; - case CollectMessages: - default: - messages.append( m ); - return; - } + return parentWindow; +} + +void BaseModel::setEmitChanges( bool e ) +{ + emitChanges = e; +} + +void BaseModel::setMessageMode( MsgMode mode ) +{ + msgMode = mode; } +void BaseModel::testMsg( const QString & m ) const +{ + messages.append( TestMessage() << m ); +} + +void BaseModel::beginInsertRows( const QModelIndex & parent, int first, int last ) +{ + setState( Inserting ); + QAbstractItemModel::beginInsertRows( parent, first, last ); +} + +void BaseModel::endInsertRows() +{ + QAbstractItemModel::endInsertRows(); + restoreState(); +} + +void BaseModel::beginRemoveRows( const QModelIndex & parent, int first, int last ) +{ + setState( Removing ); + QAbstractItemModel::beginRemoveRows( parent, first, last ); +} + +void BaseModel::endRemoveRows() +{ + QAbstractItemModel::endRemoveRows(); + restoreState(); +} + +bool BaseModel::getProcessingResult() +{ + bool result = changedWhileProcessing; + + changedWhileProcessing = false; + + return result; +} /* * array functions @@ -77,10 +122,15 @@ bool BaseModel::isArray( const QModelIndex & index ) const return !itemArr1( index ).isEmpty(); } +bool BaseModel::isArray( NifItem * item ) const +{ + return item->isArray() || (item->parent() && item->parent()->isMultiArray()); +} + int BaseModel::getArraySize( NifItem * array ) const { // shortcut for speed - if ( array->arr1().isEmpty() ) + if ( !isArray( array ) ) return 0; return evaluateInt( array, array->arr1expr() ); @@ -93,7 +143,7 @@ bool BaseModel::updateArray( const QModelIndex & array ) if ( !( array.isValid() && item && array.model() == this ) ) return false; - return updateArrayItem( item, false ); + return updateArrayItem( item ); } bool BaseModel::updateArray( const QModelIndex & parent, const QString & name ) @@ -275,12 +325,12 @@ QModelIndex BaseModel::parent( const QModelIndex & child ) const NifItem * childItem = static_cast( child.internalPointer() ); - if ( !childItem ) + if ( !childItem || childItem == root ) return QModelIndex(); NifItem * parentItem = childItem->parent(); - if ( parentItem == root || !parentItem ) + if ( !parentItem || parentItem == root ) return QModelIndex(); return createIndex( parentItem->row(), 0, parentItem ); @@ -382,11 +432,13 @@ QVariant BaseModel::data( const QModelIndex & index, int role ) const case NifValue::tBool: case NifValue::tInt: case NifValue::tUInt: + case NifValue::tULittle32: { quint32 i = item->value().toCount(); return QString( "dec: %1
hex: 0x%2" ).arg( i ).arg( i, 8, 16, QChar( '0' ) ); } case NifValue::tFloat: + case NifValue::tHfloat: { float f = item->value().toFloat(); quint32 i = item->value().toCount(); @@ -399,6 +451,10 @@ QVariant BaseModel::data( const QModelIndex & index, int role ) const } case NifValue::tVector3: return item->value().get().toHtml(); + case NifValue::tHalfVector3: + return item->value().get().toHtml(); + case NifValue::tByteVector3: + return item->value().get().toHtml(); case NifValue::tMatrix: return item->value().get().toHtml(); case NifValue::tQuat: @@ -409,6 +465,11 @@ QVariant BaseModel::data( const QModelIndex & index, int role ) const Color3 c = item->value().get(); return QString( "R %1
G %2
B %3" ).arg( c[0] ).arg( c[1] ).arg( c[2] ); } + case NifValue::tByteColor4: + { + Color4 c = item->value().get(); + return QString( "R %1
G %2
B %3
A %4" ).arg( c[0] ).arg( c[1] ).arg( c[2] ).arg( c[3] ); + } case NifValue::tColor4: { Color4 c = item->value().get(); @@ -451,7 +512,7 @@ bool BaseModel::setData( const QModelIndex & index, const QVariant & value, int item->setType( value.toString() ); break; case BaseModel::ValueCol: - item->value().fromVariant( value ); + item->value().setFromVariant( value ); break; case BaseModel::ArgCol: item->setArg( value.toString() ); @@ -478,7 +539,8 @@ bool BaseModel::setData( const QModelIndex & index, const QVariant & value, int return false; } - emit dataChanged( index, index ); + if ( state == Default ) + emit dataChanged( index, index ); return true; } @@ -528,23 +590,31 @@ Qt::ItemFlags BaseModel::flags( const QModelIndex & index ) const if ( !index.isValid() ) return Qt::ItemIsEnabled; - Qt::ItemFlags flags = Qt::ItemIsSelectable; + Qt::ItemFlags flags; + + auto item = static_cast(index.internalPointer()); + + bool condExpr; + if ( item ) + condExpr = item->condition(); + else + condExpr = evalCondition( index, true ); - if ( evalCondition( index, true ) ) - flags |= Qt::ItemIsEnabled; + if ( condExpr ) + flags = (Qt::ItemIsEnabled | Qt::ItemIsSelectable); switch ( index.column() ) { case TypeCol: return flags; + case NameCol: case ValueCol: - - if ( itemArr1( index ).isEmpty() ) + if ( condExpr ) return flags | Qt::ItemIsEditable; return flags; default: - return flags | Qt::ItemIsEditable; + return flags; } } @@ -555,14 +625,19 @@ Qt::ItemFlags BaseModel::flags( const QModelIndex & index ) const bool BaseModel::loadFromFile( const QString & file ) { QFile f( file ); - if ( f.open( QIODevice::ReadOnly ) && load( f ) ) { - fileinfo = QFileInfo( f ); - filename = fileinfo.baseName(); - folder = fileinfo.absolutePath(); + QFileInfo finfo( f ); + + setState( Loading ); + if ( f.exists() && finfo.isFile() && f.open( QIODevice::ReadOnly ) && load( f ) ) { + fileinfo = finfo; + filename = finfo.baseName(); + folder = finfo.absolutePath(); + resetState(); return true; } + resetState(); return false; } @@ -572,6 +647,13 @@ bool BaseModel::saveToFile( const QString & filename ) const return f.open( QIODevice::WriteOnly ) && save( f ); } +void BaseModel::refreshFileInfo( const QString & f ) +{ + fileinfo = QFileInfo( f ); + filename = fileinfo.baseName(); + folder = fileinfo.absolutePath(); +} + /* * searching */ @@ -579,7 +661,7 @@ bool BaseModel::saveToFile( const QString & filename ) const NifItem * BaseModel::getItem( NifItem * item, const QString & name ) const { if ( !item || item == root ) - return 0; + return nullptr; int slash = name.indexOf( "/" ); @@ -600,7 +682,7 @@ NifItem * BaseModel::getItem( NifItem * item, const QString & name ) const return child; } - return 0; + return nullptr; } /* @@ -634,7 +716,7 @@ NifItem * BaseModel::findItemX( NifItem * item, const QString & name ) const item = item->parent(); } - return 0; + return nullptr; } QModelIndex BaseModel::getIndex( const QModelIndex & parent, const QString & name ) const @@ -656,80 +738,6 @@ QModelIndex BaseModel::getIndex( const QModelIndex & parent, const QString & nam * conditions and version */ -//! Helper class for evaluating condition expressions -class BaseModelEval -{ -public: - //! Model - const BaseModel * model; - //! Item - const NifItem * item; - //! Constructor - BaseModelEval( const BaseModel * model, const NifItem * item ) - { - this->model = model; - this->item = item; - } - - //! Evaluation function - QVariant operator()( const QVariant & v ) const - { - if ( v.type() == QVariant::String ) { - QString left = v.toString(); - const NifItem * i = item; - - // resolve "ARG" - while ( left == "ARG" ) { - if ( !i->parent() ) - return false; - - i = i->parent(); - left = i->arg(); - } - - // resolve reference to sibling - const NifItem * sibling = model->getItem( i->parent(), left ); - - if ( sibling ) { - if ( sibling->value().isCount() ) { - return QVariant( sibling->value().toCount() ); - } else if ( sibling->value().isFileVersion() ) { - return QVariant( sibling->value().toFileVersion() ); - // this is tricky to understand - // we check whether the reference is an array - // if so, we get the current item's row number (i->row()) - // and get the sibling's child at that row number - // this is used for instance to describe array sizes of strips - } else if ( sibling->childCount() > 0 ) { - const NifItem * i2 = sibling->child( i->row() ); - - if ( i2 && i2->value().isCount() ) - return QVariant( i2->value().toCount() ); - } else { - qDebug() << ("can't convert " + left + " to a count"); - } - } - - // resolve reference to block type - // is the condition string a type? - if ( model->isAncestorOrNiBlock( left ) ) { - // get the type of the current block - const NifItem * block = i; - - while ( block->parent() && block->parent()->parent() ) { - block = block->parent(); - } - - return QVariant( model->inherits( block->name(), left ) ); - } - - return QVariant( 0 ); - } - - return v; - } -}; - int BaseModel::evaluateInt( NifItem * item, const Expression & expr ) const { if ( !item || item == root ) @@ -741,6 +749,9 @@ int BaseModel::evaluateInt( NifItem * item, const Expression & expr ) const bool BaseModel::evalCondition( NifItem * item, bool chkParents ) const { + if ( item->isConditionValid() ) + return item->condition(); + if ( !evalVersion( item, chkParents ) ) return false; @@ -759,12 +770,15 @@ bool BaseModel::evalCondition( NifItem * item, bool chkParents ) const return true; BaseModelEval functor( this, item ); - return item->condexpr().evaluateBool( functor ); + + item->setCondition( item->condexpr().evaluateBool( functor ) ); + + return item->condition(); } bool BaseModel::evalVersion( const QModelIndex & index, bool chkParents ) const { - NifItem * item = static_cast( index.internalPointer() ); + NifItem * item = static_cast(index.internalPointer()); if ( index.isValid() && index.model() == this && item ) return evalVersion( item, chkParents ); @@ -774,7 +788,7 @@ bool BaseModel::evalVersion( const QModelIndex & index, bool chkParents ) const bool BaseModel::evalCondition( const QModelIndex & index, bool chkParents ) const { - NifItem * item = static_cast( index.internalPointer() ); + NifItem * item = static_cast(index.internalPointer()); if ( index.isValid() && index.model() == this && item ) return evalCondition( item, chkParents ); @@ -782,3 +796,70 @@ bool BaseModel::evalCondition( const QModelIndex & index, bool chkParents ) cons return false; } + +/* + * BaseModelEval + */ + +BaseModelEval::BaseModelEval( const BaseModel * model, const NifItem * item ) +{ + this->model = model; + this->item = item; +} + +QVariant BaseModelEval::operator()(const QVariant & v) const +{ + if ( v.type() == QVariant::String ) { + QString left = v.toString(); + const NifItem * i = item; + + // resolve "ARG" + while ( left == "ARG" ) { + if ( !i->parent() ) + return false; + + i = i->parent(); + left = i->arg(); + } + + // resolve reference to sibling + const NifItem * sibling = model->getItem( i->parent(), left ); + + if ( sibling ) { + if ( sibling->value().isCount() ) { + return QVariant( sibling->value().toCount() ); + } else if ( sibling->value().isFileVersion() ) { + return QVariant( sibling->value().toFileVersion() ); + // this is tricky to understand + // we check whether the reference is an array + // if so, we get the current item's row number (i->row()) + // and get the sibling's child at that row number + // this is used for instance to describe array sizes of strips + } else if ( sibling->childCount() > 0 ) { + const NifItem * i2 = sibling->child( i->row() ); + + if ( i2 && i2->value().isCount() ) + return QVariant( i2->value().toCount() ); + } else { + qDebug() << ("can't convert " + left + " to a count"); + } + } + + // resolve reference to block type + // is the condition string a type? + if ( model->isAncestorOrNiBlock( left ) ) { + // get the type of the current block + const NifItem * block = i; + + while ( block->parent() && block->parent()->parent() ) { + block = block->parent(); + } + + return QVariant( model->inherits( block->name(), left ) ); + } + + return QVariant( 0 ); + } + + return v; +} diff --git a/src/basemodel.h b/src/basemodel.h index b98ee4235..cb24736bd 100644 --- a/src/basemodel.h +++ b/src/basemodel.h @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -40,17 +40,18 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include // Inherited #include #include +#include #include #include #include -class QAbstractItemDelegate; +//! @file basemodel.h BaseModel, BaseModelEval -//! \file basemodel.h BaseModel +class QAbstractItemDelegate; -//! Base class for nif and kfm models, which store files in memory. -/*! +/*! Base class for NIF and KFM models, which store files in memory. + * * This class serves as an abstract base class for NifModel and KfmModel * classes. */ @@ -58,85 +59,103 @@ class BaseModel : public QAbstractItemModel { Q_OBJECT + friend class NifIStream; + friend class NifOStream; + friend class BaseModelEval; + public: - //! Constructor - BaseModel( QObject * parent = 0 ); - //! Destructor + BaseModel( QObject * parent = nullptr ); ~BaseModel(); + //! Get parent window + QWidget * getWindow(); + //! Clear model data. virtual void clear() = 0; + //! Generic load from QIODevice. + virtual bool load( QIODevice & device ) = 0; + //! Generic save to QIODevice. + virtual bool save( QIODevice & device ) const = 0; + //! Get version as a string + virtual QString getVersion() const = 0; + //! Get version as a number + virtual quint32 getVersionNumber() const = 0; + + //! Get an item. + template T get( const QModelIndex & index ) const; + //! Get an item by name. + template T get( const QModelIndex & parent, const QString & name ) const; + //! Set an item. + template bool set( const QModelIndex & index, const T & d ); + //! Set an item by name. + template bool set( const QModelIndex & parent, const QString & name, const T & v ); + + //! Get a model index array as a QVector. + template QVector getArray( const QModelIndex & iArray ) const; + //! Get a model index array as a QVector by name. + template QVector getArray( const QModelIndex & iArray, const QString & name ) const; + //! Write a QVector to a model index array. + template void setArray( const QModelIndex & iArray, const QVector & array ); + //! Write a value to a model index array. + template void setArray( const QModelIndex & iArray, const T & val ); + //! Write a QVector to a model index array by name. + template void setArray( const QModelIndex & iArray, const QString & name, const QVector & array ); //! Load from file. bool loadFromFile( const QString & filename ); //! Save to file. bool saveToFile( const QString & filename ) const; - //! Generic load from QIODevice. - virtual bool load( QIODevice & device ) = 0; - //! Generic save to QIODevice. - virtual bool save( QIODevice & device ) const = 0; - - //! If the model was loaded from a file then getFolder returns the folder. - /*! + /*! If the model was loaded from a file then getFolder returns the folder. + * * This function is used to resolve external resources. - * \return The folder of the last file that was loaded with loadFromFile. + * @return The folder of the last file that was loaded with loadFromFile. */ QString getFolder() const { return folder; } - //! If the model was loaded from a file then getFilename returns the filename. - /*! - * This function is used to resolve external resources. - * \return The filename (without extension) of the last file that was loaded with loadFromFile. - */ + /*! If the model was loaded from a file then getFilename returns the filename. + * + * This function is used to resolve external resources. + * @return The filename (without extension) of the last file that was loaded with loadFromFile. + */ QString getFilename() const { return filename; } - //! If the model was loaded from a file then getFileInfo returns a QFileInfo object. - /*! - * This function is used to resolve external resources. - * \return The file info of the last file that was loaded with loadFromFile. - */ + /*! If the model was loaded from a file then getFileInfo returns a QFileInfo object. + * + * This function is used to resolve external resources. + * @return The file info of the last file that was loaded with loadFromFile. + */ QFileInfo getFileInfo() const { return fileinfo; } - //! Return true if the index pointed to is an array. - /*! - * \param array The index to check. - * \return true if the index is an array. + //! Updates stored file and folder information + void refreshFileInfo( const QString & ); + + /*! Return true if the index pointed to is an array. + * + * @param array The index to check. + * @return true if the index is an array. */ bool isArray( const QModelIndex & iArray ) const; - //! Update the size of an array (append or remove items). - /*! - * \param array The index of the array whose size to update. - * \return true if the update succeeded, false otherwise. + + /*! Return true if the item is an array. + * + * @param array The item to check. + * @return true if the index is an array. + */ + bool isArray( NifItem * item ) const; + + /*! Update the size of an array (append or remove items). + * + * @param array The index of the array whose size to update. + * @return true if the update succeeded, false otherwise. */ bool updateArray( const QModelIndex & iArray ); + //! Update the size of an array by name. bool updateArray( const QModelIndex & parent, const QString & name ); - //! Get an model index array as a QVector. - /*! - * \param array The index of the array to get. - * \return The array as QVector. - */ - template QVector getArray( const QModelIndex & iArray ) const; - //! Get an model index array as a QVector by name. - template QVector getArray( const QModelIndex & iArray, const QString & name ) const; - //! Write a QVector to a model index array. - template void setArray( const QModelIndex & iArray, const QVector & array ); - //! Write a QVector to a model index array by name. - template void setArray( const QModelIndex & iArray, const QString & name, const QVector & array ); - - //! Get an item. - template T get( const QModelIndex & index ) const; - //! Get an item by name. - template T get( const QModelIndex & parent, const QString & name ) const; - //! Set an item. - template bool set( const QModelIndex & index, const T & d ); - //! Set an item by name. - template bool set( const QModelIndex & parent, const QString & name, const T & v ); //! Get an item as a NifValue. NifValue getValue( const QModelIndex & index ) const; - /* Not implemented? */ // Get an item as a NifValue by name. //NifValue getValue( const QModelIndex & parent, const QString & name ) const; @@ -145,7 +164,6 @@ class BaseModel : public QAbstractItemModel //! Set an item from a NifValue by name. bool setValue( const QModelIndex & parent, const QString & name, const NifValue & v ); - // get item attributes //! Get the item name. QString itemName( const QModelIndex & index ) const; //! Get the item type string. @@ -183,10 +201,30 @@ class BaseModel : public QAbstractItemModel { return QObject::inherits( className ); } - //! Get version as a string - virtual QString getVersion() const = 0; - //! Get version as a number - virtual quint32 getVersionNumber() const = 0; + + enum ModelState + { + Default, + Loading, + Saving, + Inserting, + Removing, + Processing + }; + + //! Get the model's state + ModelState getState() const { return state; } + //! Set the model's state + void setState( ModelState s ) const { states.push( state ); state = s; } + //! Restore the model's state to the previous + void restoreState() const { state = states.pop(); } + //! Reset the model's state + void resetState() const { state = Default; states.clear(); } + //! Were there updates while batch processing (also clears the result) + bool getProcessingResult(); + + //! Get Messages collected + QList getMessages() const { QList lst = messages; messages.clear(); return lst; } //! Column names enum @@ -204,12 +242,13 @@ class BaseModel : public QAbstractItemModel NumColumns = 10, }; - // QAbstractModel interface - //! Creates a model index for the given row and column - /*! - * \see QAbstractItemModel::createIndex() + // QAbstractItemModel + + /*! Creates a model index for the given row and column + * @see QAbstractItemModel::createIndex() */ QModelIndex index( int row, int column, const QModelIndex & parent = QModelIndex() ) const override; + //! Finds the parent of the specified index QModelIndex parent( const QModelIndex & index ) const override; @@ -218,17 +257,18 @@ class BaseModel : public QAbstractItemModel //! Finds the number of columns int columnCount( const QModelIndex & parent = QModelIndex() ) const override { Q_UNUSED( parent ); return NumColumns; } - //! Finds the data associated with an index - /*! - * \param index The index to find data for - * \param role The Qt::ItemDataRole to get data for + /*! Finds the data associated with an index + * + * @param index The index to find data for + * @param role The Qt::ItemDataRole to get data for */ QVariant data( const QModelIndex & index, int role = Qt::DisplayRole ) const override; - //! Sets data associated with an index - /*! - * \param index The index to set data for - * \param value The data to set - * \param role The Qt::ItemDataRole to use + + /*! Sets data associated with an index + * + * @param index The index to set data for + * @param value The data to set + * @param role The Qt::ItemDataRole to use */ bool setData( const QModelIndex & index, const QVariant & value, int role = Qt::EditRole ) override; @@ -238,37 +278,46 @@ class BaseModel : public QAbstractItemModel //! Finds the flags for an index Qt::ItemFlags flags( const QModelIndex & index ) const override; - //! Message mode + // end QAbstractItemModel + + // Terrible hack for atomic-like operations + // Setting this to false will suspend dataChanged in set + // Doing so is necessary for performance reasons with setting thousands of rows at once + // e.g. Update Tangent Space with BSTriShapes + void setEmitChanges( bool e ); + enum MsgMode { - EmitMessages, CollectMessages + UserMessage, TstMessage }; - //! Set the Message mode - void setMessageMode( MsgMode m ) { msgMode = m; } - //! Get Messages collected - QList getMessages() const { QList lst = messages; messages.clear(); return lst; } + void setMessageMode( MsgMode mode ); signals: //! Messaging signal - void sigMessage( const Message & msg ) const; + void sigMessage( const TestMessage & msg ) const; //! Progress signal void sigProgress( int c, int m ) const; protected: - //! Update an array item - virtual bool updateArrayItem( NifItem * array, bool fast ) = 0; - //! Get the size of an array - int getArraySize( NifItem * array ) const; - //! Evaluate a string for an array - int evaluateInt( NifItem * item, const Expression & expr ) const; - //! Get an item virtual NifItem * getItem( NifItem * parent, const QString & name ) const; - //! Get an item by name - NifItem * getItemX( NifItem * item, const QString & name ) const; // find upwards - //! Find an item by name - NifItem * findItemX( NifItem * item, const QString & name ) const; + //! Set an item value + virtual bool setItemValue( NifItem * item, const NifValue & v ) = 0; + + //! Update an array item + virtual bool updateArrayItem( NifItem * array ) = 0; + + //! Convert a version number to a string + virtual QString ver2str( quint32 ) const = 0; + //! Convert a version string to a number + virtual quint32 str2ver( QString ) const = 0; + + //! Evaluate version + virtual bool evalVersion( NifItem * item, bool chkParents = false ) const = 0; + + //! Set the header string + virtual bool setHeaderString( const QString & ) = 0; //! Get an item by name template T get( NifItem * parent, const QString & name ) const; @@ -279,50 +328,79 @@ class BaseModel : public QAbstractItemModel template bool set( NifItem * parent, const QString & name, const T & d ); //! Set an item template bool set( NifItem * item, const T & d ); - //! Set an item value - virtual bool setItemValue( NifItem * item, const NifValue & v ) = 0; + + //! Get the size of an array + int getArraySize( NifItem * array ) const; + //! Evaluate a string for an array + int evaluateInt( NifItem * item, const Expression & expr ) const; + + //! Get an item by name + NifItem * getItemX( NifItem * item, const QString & name ) const; + //! Find an item by name + NifItem * findItemX( NifItem * item, const QString & name ) const; + //! Set an item value by name bool setItemValue( NifItem * parent, const QString & name, const NifValue & v ); - //! Evaluate version - virtual bool evalVersion( NifItem * item, bool chkParents = false ) const = 0; //! Evaluate conditions bool evalCondition( NifItem * item, bool chkParents = false ) const; - //! Convert a version number to a string - virtual QString ver2str( quint32 ) const = 0; - //! Convert a version string to a number - virtual quint32 str2ver( QString ) const = 0; + void beginInsertRows( const QModelIndex & parent, int first, int last ); + void endInsertRows(); - //! Set the header string - virtual bool setHeaderString( const QString & ) = 0; + void beginRemoveRows( const QModelIndex & parent, int first, int last ); + void endRemoveRows(); + + //! NifSkope window the model belongs to + QWidget * parentWindow; //! The root item NifItem * root; //! The filepath of the model QString folder; - //! The filename of the model QString filename; - //! The file info for the model QFileInfo fileinfo; - //! The messaging mode + // Whether or not to emit dataChanged() in set + bool emitChanges = true; + + //! A list of test messages + mutable QList messages; + //! Handle a test message + void testMsg( const QString & m ) const; + MsgMode msgMode; - //! A list of messages - mutable QList messages; - //! Handle a message - void msg( const Message & m ) const; + //! The model's state + mutable ModelState state = Default; + mutable QStack states; - friend class NifIStream; - friend class NifOStream; - friend class BaseModelEval; -}; // class BaseModel + //! Has any data changed while processing + bool changedWhileProcessing = false; +}; +//! Helper class for evaluating condition expressions +class BaseModelEval +{ +public: + //! Constructor + BaseModelEval( const BaseModel * model, const NifItem * item ); + + //! Evaluation function + QVariant operator()( const QVariant & v ) const; + +private: + const BaseModel * model; + const NifItem * item; +}; + + +// Templates + template inline T BaseModel::get( NifItem * parent, const QString & name ) const { NifItem * item = getItem( parent, name ); @@ -391,7 +469,11 @@ template inline T BaseModel::get( const QModelIndex & index ) const template inline bool BaseModel::set( NifItem * item, const T & d ) { if ( item->value().set( d ) ) { - emit dataChanged( createIndex( item->row(), ValueCol, item ), createIndex( item->row(), ValueCol, item ) ); + if ( state != Processing ) + emit dataChanged( createIndex( item->row(), ValueCol, item ), createIndex( item->row(), ValueCol, item ) ); + else + changedWhileProcessing = true; + return true; } @@ -436,6 +518,19 @@ template inline void BaseModel::setArray( const QModelIndex & iArra } } +template inline void BaseModel::setArray( const QModelIndex & iArray, const T & val ) +{ + NifItem * item = static_cast(iArray.internalPointer()); + + if ( isArray( iArray ) && item && iArray.model() == this ) { + item->setArray( val ); + int x = item->childCount() - 1; + + if ( x >= 0 ) + emit dataChanged( createIndex( 0, ValueCol, item->child( 0 ) ), createIndex( x, ValueCol, item->child( x ) ) ); + } +} + template inline void BaseModel::setArray( const QModelIndex & iParent, const QString & name, const QVector & array ) { setArray( getIndex( iParent, name ), array ); diff --git a/src/config.h b/src/config.h index 2b70687d3..210f6c728 100644 --- a/src/config.h +++ b/src/config.h @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/src/gl/bsshape.cpp b/src/gl/bsshape.cpp new file mode 100644 index 000000000..73d943b8f --- /dev/null +++ b/src/gl/bsshape.cpp @@ -0,0 +1,831 @@ +#include "bsshape.h" +#include "settings.h" + +#include "glscene.h" +#include "material.h" + + +BSShape::BSShape( Scene * s, const QModelIndex & b ) : Shape( s, b ) +{ + +} + +void BSShape::clear() +{ + Node::clear(); + + iSkin = iSkinData = iSkinPart = QModelIndex(); + bssp = nullptr; + bslsp = nullptr; + bsesp = nullptr; + + verts.clear(); + norms.clear(); + tangents.clear(); + bitangents.clear(); + triangles.clear(); + coords.clear(); + colors.clear(); + bones.clear(); + weights.clear(); +} + +void BSShape::update( const NifModel * nif, const QModelIndex & index ) +{ + Node::update( nif, index ); + + if ( !iBlock.isValid() || !index.isValid() ) + return; + + if ( iBlock != index && iSkin != index && iSkinData != index && !nif->inherits( index, "NiProperty" ) ) + return; + + nifVersion = nif->getUserVersion2(); + + auto vertexFlags = nif->get( iBlock, "VF" ); + bool isDynamic = nif->inherits( iBlock, "BSDynamicTriShape" ); + + bool isDataOnSkin = false; + bool isSkinned = vertexFlags & 0x400; + if ( nifVersion == 130 ) { + skinInstName = "BSSkin::Instance"; + skinDataName = "BSSkin::BoneData"; + } else { + skinInstName = "NiSkinInstance"; + skinDataName = "NiSkinData"; + } + + iSkin = nif->getBlock( nif->getLink( nif->getIndex( iBlock, "Skin" ) ), skinInstName ); + if ( !iSkin.isValid() ) + isSkinned = false; + + if ( isSkinned ) { + iSkinData = nif->getBlock( nif->getLink( iSkin, "Data" ), skinDataName ); + iSkinPart = nif->getBlock( nif->getLink( iSkin, "Skin Partition" ), "NiSkinPartition" ); + + if ( nifVersion == 100 ) + isDataOnSkin = true; + } + + updateData = true; + updateBounds |= updateData; + updateSkin = isSkinned; + transformRigid = !isSkinned; + + int dataSize = 0; + if ( !isDataOnSkin ) { + iVertData = nif->getIndex( iBlock, "Vertex Data" ); + iTriData = nif->getIndex( iBlock, "Triangles" ); + iData = iVertData; + if ( !iVertData.isValid() || !iTriData.isValid() ) + return; + + numVerts = std::min( nif->get( iBlock, "Num Vertices" ), nif->rowCount( iVertData ) ); + numTris = std::min( nif->get( iBlock, "Num Triangles" ), nif->rowCount( iTriData ) ); + + dataSize = nif->get( iBlock, "Data Size" ); + } else { + // For skinned geometry, the vertex data is stored on the NiSkinPartition + // The triangles are split up among the partitions + + iSkinPart = nif->getBlock( nif->getLink( iSkin, "Skin Partition" ), "NiSkinPartition" ); + if ( !iSkinPart.isValid() ) + return; + + iVertData = nif->getIndex( iSkinPart, "Vertex Data" ); + iTriData = QModelIndex(); + iData = iVertData; + + dataSize = nif->get( iSkinPart, "Data Size" ); + auto vertexSize = nif->get( iSkinPart, "Vertex Size" ); + if ( !iVertData.isValid() || dataSize == 0 || vertexSize == 0 ) + return; + + numVerts = dataSize / vertexSize; + } + + + auto bsphere = nif->getIndex( iBlock, "Bounding Sphere" ); + if ( bsphere.isValid() ) { + bsphereCenter = nif->get( bsphere, "Center" ); + bsphereRadius = nif->get( bsphere, "Radius" ); + } + + if ( iBlock == index && dataSize > 0 ) { + verts.clear(); + norms.clear(); + tangents.clear(); + bitangents.clear(); + triangles.clear(); + coords.clear(); + colors.clear(); + + // For compatibility with coords QList + QVector coordset; + + for ( int i = 0; i < numVerts; i++ ) { + auto idx = nif->index( i, 0, iVertData ); + + if ( !isDynamic ) + verts << nif->get( idx, "Vertex" ); + + coordset << nif->get( idx, "UV" ); + + // Bitangent X + auto bitX = nif->getValue( nif->getIndex( idx, "Bitangent X" ) ).toFloat(); + // Bitangent Y/Z + auto bitYi = nif->getValue( nif->getIndex( idx, "Bitangent Y" ) ).toCount(); + auto bitZi = nif->getValue( nif->getIndex( idx, "Bitangent Z" ) ).toCount(); + auto bitY = (double( bitYi ) / 255.0) * 2.0 - 1.0; + auto bitZ = (double( bitZi ) / 255.0) * 2.0 - 1.0; + + norms += nif->get( idx, "Normal" ); + tangents += nif->get( idx, "Tangent" ); + bitangents += Vector3( bitX, bitY, bitZ ); + + auto vcIdx = nif->getIndex( idx, "Vertex Colors" ); + if ( vcIdx.isValid() ) { + colors += nif->get( vcIdx ); + } + } + + if ( isDynamic ) { + auto dynVerts = nif->getArray( iBlock, "Vertices" ); + for ( const auto & v : dynVerts ) + verts << Vector3( v ); + } + + // Add coords as first set of QList + coords.append( coordset ); + + if ( !isDataOnSkin ) { + triangles = nif->getArray( iTriData ); + triangles = triangles.mid( 0, numTris ); + } else { + auto partIdx = nif->getIndex( iSkinPart, "Partition" ); + for ( int i = 0; i < nif->rowCount( partIdx ); i++ ) + triangles << nif->getArray( nif->index( i, 0, partIdx ), "Triangles" ); + } + } + + // Update shaders from this mesh's shader property + updateShaderProperties( nif ); + + if ( bssp ) + isVertexAlphaAnimation = bssp->hasSF2( ShaderFlags::SLSF2_Tree_Anim ); + + if ( isVertexAlphaAnimation ) { + for ( int i = 0; i < colors.count(); i++ ) + colors[i].setRGBA( colors[i].red(), colors[i].green(), colors[i].blue(), 1.0 ); + } +} + +QModelIndex BSShape::vertexAt( int idx ) const +{ + auto nif = static_cast(iBlock.model()); + if ( !nif ) + return QModelIndex(); + + // Vertices are on NiSkinPartition in version 100 + auto blk = iBlock; + if ( nifVersion < 130 && iSkinPart.isValid() ) { + if ( nif->inherits( blk, "BSDynamicTriShape" ) ) + return nif->getIndex( blk, "Vertices" ).child( idx, 0 ); + + blk = iSkinPart; + } + + return nif->getIndex( nif->getIndex( blk, "Vertex Data" ).child( idx, 0 ), "Vertex" ); +} + + +void BSShape::transform() +{ + if ( isHidden() ) + return; + + const NifModel * nif = static_cast(iBlock.model()); + if ( !nif || !iBlock.isValid() ) { + clear(); + return; + } + + if ( updateData ) { + updateData = false; + } + + if ( updateSkin ) { + updateSkin = false; + doSkinning = false; + + bones.clear(); + weights.clear(); + partitions.clear(); + + if ( iSkin.isValid() && iSkinData.isValid() ) { + skeletonRoot = nif->getLink( iSkin, "Skeleton Root" ); + if ( nifVersion < 130 ) + skeletonTrans = Transform( nif, iSkinData ); + + bones = nif->getLinkArray( iSkin, "Bones" ); + weights.fill( BoneWeights(), bones.count() ); + for ( int i = 0; i < bones.count(); i++ ) + weights[i].bone = bones[i]; + + for ( int i = 0; i < numVerts; i++ ) { + auto idx = nif->index( i, 0, iVertData ); + auto wts = nif->getArray( idx, "Bone Weights" ); + auto bns = nif->getArray( idx, "Bone Indices" ); + if ( wts.count() < 4 || bns.count() < 4 ) + continue; + + for ( int j = 0; j < 4; j++ ) { + if ( wts[j] > 0.0 ) + weights[bns[j]].weights << VertexWeight( i, wts[j] ); + } + } + + doSkinning = weights.count(); + } + } + + Node::transform(); +} + +void BSShape::transformShapes() +{ + if ( isHidden() ) + return; + + const NifModel * nif = static_cast(iBlock.model()); + if ( !nif || !iBlock.isValid() ) { + clear(); + return; + } + + Node::transformShapes(); + + transformRigid = true; + + if ( doSkinning && scene->options & Scene::DoSkinning ) { + transformRigid = false; + + transVerts.resize( verts.count() ); + transVerts.fill( Vector3() ); + transNorms.resize( verts.count() ); + transNorms.fill( Vector3() ); + transTangents.resize( verts.count() ); + transTangents.fill( Vector3() ); + transBitangents.resize( verts.count() ); + transBitangents.fill( Vector3() ); + + auto b = nif->getIndex( iSkinData, "Bone List" ); + for ( int i = 0; i < weights.count(); i++ ) + weights[i].setTransform( nif, b.child( i, 0 ) ); + + Node * root = findParent( 0 ); + for ( const BoneWeights & bw : weights ) { + Node * bone = root ? root->findChild( bw.bone ) : nullptr; + if ( bone ) { + Transform t = scene->view * bone->localTrans( 0 ) * bw.trans; + for ( const VertexWeight & w : bw.weights ) { + if ( w.vertex >= verts.count() ) + continue; + + transVerts[w.vertex] += t * verts[w.vertex] * w.weight; + transNorms[w.vertex] += t.rotation * norms[w.vertex] * w.weight; + transTangents[w.vertex] += t.rotation * tangents[w.vertex] * w.weight; + transBitangents[w.vertex] += t.rotation * bitangents[w.vertex] * w.weight; + } + } + } + + for ( int n = 0; n < verts.count(); n++ ) { + transNorms[n].normalize(); + transTangents[n].normalize(); + transBitangents[n].normalize(); + } + + boundSphere = BoundSphere( transVerts ); + boundSphere.applyInv( viewTrans() ); + updateBounds = false; + } else { + transVerts = verts; + transNorms = norms; + transTangents = tangents; + transBitangents = bitangents; + } + +} + +void BSShape::drawShapes( NodeList * secondPass, bool presort ) +{ + if ( isHidden() ) + return; + + glPointSize( 8.5 ); + + // TODO: Only run this if BSXFlags has "EditorMarkers present" flag + if ( !(scene->options & Scene::ShowMarkers) && name.startsWith( "EditorMarker" ) ) + return; + + if ( Node::SELECTING ) { + if ( scene->selMode & Scene::SelObject ) { + int s_nodeId = ID2COLORKEY( nodeId ); + glColor4ubv( (GLubyte *)&s_nodeId ); + } else { + glColor4f( 0, 0, 0, 1 ); + } + } + + // Draw translucent meshes in second pass + AlphaProperty * aprop = findProperty(); + Material * mat = (bssp) ? bssp->mat() : nullptr; + + drawSecond |= aprop && aprop->blend(); + drawSecond |= mat && mat->bDecal; + + if ( secondPass && drawSecond ) { + secondPass->add( this ); + return; + } + + if ( transformRigid ) { + glPushMatrix(); + glMultMatrix( viewTrans() ); + } + + // Render polygon fill slightly behind alpha transparency and wireframe + if ( !drawSecond ) { + glEnable( GL_POLYGON_OFFSET_FILL ); + glPolygonOffset( 1.0f, 2.0f ); + } + + glEnableClientState( GL_VERTEX_ARRAY ); + glVertexPointer( 3, GL_FLOAT, 0, transVerts.data() ); + + if ( !Node::SELECTING ) { + glEnableClientState( GL_NORMAL_ARRAY ); + glNormalPointer( GL_FLOAT, 0, transNorms.data() ); + + bool doVCs = (bssp && (bssp->getFlags2() & ShaderFlags::SLSF2_Vertex_Colors)); + + + Color4 * c = nullptr; + if ( colors.count() && (scene->options & Scene::DoVertexColors) && doVCs ) { + c = colors.data(); + } + + if ( c ) { + glEnableClientState( GL_COLOR_ARRAY ); + glColorPointer( 4, GL_FLOAT, 0, c ); + } else { + glColor( Color3( 1.0f, 1.0f, 1.0f ) ); + } + } + + + if ( !Node::SELECTING ) + shader = scene->renderer->setupProgram( this, shader ); + + if ( isDoubleSided ) { + glCullFace( GL_FRONT ); + glDrawElements( GL_TRIANGLES, triangles.count() * 3, GL_UNSIGNED_SHORT, triangles.data() ); + glCullFace( GL_BACK ); + } + + glDrawElements( GL_TRIANGLES, triangles.count() * 3, GL_UNSIGNED_SHORT, triangles.data() ); + + if ( !Node::SELECTING ) + scene->renderer->stopProgram(); + + glDisableClientState( GL_VERTEX_ARRAY ); + glDisableClientState( GL_NORMAL_ARRAY ); + glDisableClientState( GL_COLOR_ARRAY ); + + glDisable( GL_POLYGON_OFFSET_FILL ); + + + if ( scene->selMode & Scene::SelVertex ) { + drawVerts(); + } + + if ( transformRigid ) + glPopMatrix(); +} + +void BSShape::drawVerts() const +{ + glDisable( GL_LIGHTING ); + glNormalColor(); + + glBegin( GL_POINTS ); + + for ( int i = 0; i < numVerts; i++ ) { + if ( Node::SELECTING ) { + int id = ID2COLORKEY( ( shapeNumber << 16 ) + i ); + glColor4ubv( (GLubyte *)&id ); + } + glVertex( transVerts.value( i ) ); + } + + auto nif = static_cast(iBlock.model()); + if ( !nif ) + return; + + // Vertices are on NiSkinPartition in version 100 + bool selected = iBlock == scene->currentBlock; + if ( nifVersion < 130 && iSkinPart.isValid() ) { + selected |= iSkinPart == scene->currentBlock; + selected |= nif->inherits( iBlock, "BSDynamicTriShape" ); + } + + + // Highlight selected vertex + if ( !Node::SELECTING && selected ) { + auto idx = scene->currentIndex; + auto n = idx.data( Qt::DisplayRole ).toString(); + if ( n == "Vertex" || n == "Vertices" ) { + glHighlightColor(); + glVertex( transVerts.value( idx.parent().row() ) ); + } + } + + glEnd(); +} + +void BSShape::drawSelection() const +{ + if ( scene->options & Scene::ShowNodes ) + Node::drawSelection(); + + if ( isHidden() || !(scene->selMode & Scene::SelObject) ) + return; + + auto idx = scene->currentIndex; + auto blk = scene->currentBlock; + + // Is the current block extra data + bool extraData = false; + + auto nif = static_cast(idx.model()); + if ( !nif ) + return; + + // Set current block name and detect if extra data + auto blockName = nif->getBlockName( blk ); + if ( blockName == "BSPackedCombinedSharedGeomDataExtra" ) + extraData = true; + + // Don't do anything if this block is not the current block + // or if there is not extra data + if ( blk != iBlock && blk != iSkin && blk != iSkinData && blk != iSkinPart && !extraData ) + return; + + // Name of this index + auto n = idx.data( NifSkopeDisplayRole ).toString(); + // Name of this index's parent + auto p = idx.parent().data( NifSkopeDisplayRole ).toString(); + // Parent index + auto pBlock = nif->getBlock( nif->getParent( blk ) ); + + auto push = [this] ( const Transform & t ) { + if ( transformRigid ) { + glPushMatrix(); + glMultMatrix( t ); + } + }; + + auto pop = [this] () { + if ( transformRigid ) + glPopMatrix(); + }; + + push( viewTrans() ); + + glDepthFunc( GL_LEQUAL ); + + glDisable( GL_LIGHTING ); + glDisable( GL_COLOR_MATERIAL ); + glDisable( GL_TEXTURE_2D ); + glDisable( GL_NORMALIZE ); + glEnable( GL_DEPTH_TEST ); + glDepthMask( GL_FALSE ); + glEnable( GL_BLEND ); + glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + glDisable( GL_ALPHA_TEST ); + + glDisable( GL_CULL_FACE ); + + // TODO: User Settings + int lineWidth = 1.5; + int pointSize = 5.0; + + glLineWidth( lineWidth ); + glPointSize( pointSize ); + + glNormalColor(); + + glEnable( GL_POLYGON_OFFSET_FILL ); + glPolygonOffset( -1.0f, -2.0f ); + + float normalScale = bounds().radius / 20; + normalScale /= 2.0f; + + if ( normalScale < 0.1f ) + normalScale = 0.1f; + + + + // Draw All Verts lambda + auto allv = [this]( float size ) { + glPointSize( size ); + glBegin( GL_POINTS ); + + for ( int j = 0; j < transVerts.count(); j++ ) + glVertex( transVerts.value( j ) ); + + glEnd(); + }; + + if ( n == "Bounding Sphere" && !extraData ) { + auto sph = BoundSphere( nif, idx ); + if ( sph.radius > 0.0 ) { + glColor4f( 1, 1, 1, 0.33 ); + drawSphereSimple( sph.center, sph.radius, 72 ); + } + } + + if ( blockName == "BSPackedCombinedSharedGeomDataExtra" && pBlock == iBlock ) { + QVector idxs; + if ( n == "Bounding Sphere" ) { + idxs += idx; + } else if ( n == "BSPackedCombinedSharedGeomDataExtra" ) { + auto data = nif->getIndex( idx, "Object Data" ); + int dataCt = nif->rowCount( data ); + + for ( int i = 0; i < dataCt; i++ ) { + auto d = data.child( i, 0 ); + + int numC = nif->get( d, "Num Combined" ); + auto c = nif->getIndex( d, "Combined" ); + int cCt = nif->rowCount( c ); + + for ( int j = 0; j < cCt; j++ ) { + idxs += nif->getIndex( c.child( j, 0 ), "Bounding Sphere" ); + } + } + } + + if ( !idxs.count() ) { + glPopMatrix(); + return; + } + + Vector3 pTrans = nif->get( pBlock, "Translation" ); + auto iBSphere = nif->getIndex( pBlock, "Bounding Sphere" ); + Vector3 pbvC = nif->get( iBSphere.child( 0, 2 ) ); + float pbvR = nif->get( iBSphere.child( 1, 2 ) ); + + if ( pbvR > 0.0 ) { + glColor4f( 0, 1, 0, 0.33 ); + drawSphereSimple( pbvC, pbvR, 72 ); + } + + glPopMatrix(); + + for ( auto i : idxs ) { + Matrix mat = nif->get( i.parent(), "Rotation" ); + //auto trans = nif->get( idx.parent(), "Translation" ); + float scale = nif->get( i.parent(), "Scale" ); + + Vector3 bvC = nif->get( i, "Center" ); + float bvR = nif->get( i, "Radius" ); + + Transform t; + t.rotation = mat.inverted(); + t.translation = bvC; + t.scale = scale; + + glPushMatrix(); + glMultMatrix( scene->view * t ); + + if ( bvR > 0.0 ) { + glColor4f( 1, 1, 1, 0.33 ); + drawSphereSimple( Vector3( 0, 0, 0 ), bvR, 72 ); + } + + glPopMatrix(); + } + + glPushMatrix(); + glMultMatrix( viewTrans() ); + } + + if ( n == "Vertex Data" || n == "Vertex" || n == "Vertices" ) { + allv( 5.0 ); + + int s = -1; + if ( (n == "Vertex Data" && p == "Vertex Data") + || (n == "Vertices" && p == "Vertices") ) { + s = idx.row(); + } else if ( n == "Vertex" ) { + s = idx.parent().row(); + } + + if ( s >= 0 ) { + glPointSize( 10 ); + glDepthFunc( GL_ALWAYS ); + glHighlightColor(); + glBegin( GL_POINTS ); + glVertex( transVerts.value( s ) ); + glEnd(); + } + } + + glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); + + // Draw Lines lambda + auto lines = [this, &normalScale, &allv, &lineWidth]( const QVector & v ) { + allv( 7.0 ); + + int s = scene->currentIndex.parent().row(); + glBegin( GL_LINES ); + + for ( int j = 0; j < transVerts.count() && j < v.count(); j++ ) { + glVertex( transVerts.value( j ) ); + glVertex( transVerts.value( j ) + v.value( j ) * normalScale * 2 ); + glVertex( transVerts.value( j ) ); + glVertex( transVerts.value( j ) - v.value( j ) * normalScale / 2 ); + } + + glEnd(); + + if ( s >= 0 ) { + glDepthFunc( GL_ALWAYS ); + glHighlightColor(); + glLineWidth( 3.0 ); + glBegin( GL_LINES ); + glVertex( transVerts.value( s ) ); + glVertex( transVerts.value( s ) + v.value( s ) * normalScale * 2 ); + glVertex( transVerts.value( s ) ); + glVertex( transVerts.value( s ) - v.value( s ) * normalScale / 2 ); + glEnd(); + glLineWidth( lineWidth ); + } + }; + + // Draw Normals + if ( n.contains( "Normal" ) ) { + lines( transNorms ); + } + + // Draw Tangents + if ( n.contains( "Tangent" ) ) { + lines( transTangents ); + } + + // Draw Triangles + if ( n == "Triangles" ) { + int s = scene->currentIndex.row(); + if ( s >= 0 ) { + glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); + glHighlightColor(); + + Triangle tri = triangles.value( s ); + glBegin( GL_TRIANGLES ); + glVertex( transVerts.value( tri.v1() ) ); + glVertex( transVerts.value( tri.v2() ) ); + glVertex( transVerts.value( tri.v3() ) ); + glEnd(); + glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); + } + } + + // Draw Segments/Subsegments + if ( n == "Segment" || n == "Sub Segment" || n == "Num Primitives" ) { + auto sidx = idx; + int s; + if ( n != "Num Primitives" ) { + sidx = idx.child( 1, 0 ); + } + s = sidx.row(); + + auto nif = static_cast(sidx.model()); + + auto off = sidx.sibling( s - 1, 2 ).data().toInt() / 3; + auto cnt = sidx.sibling( s, 2 ).data().toInt(); + + auto numRec = sidx.sibling( s + 2, 2 ).data().toInt(); + + QVector cols = { { 255, 0, 0, 128 }, { 0, 255, 0, 128 }, { 0, 0, 255, 128 }, { 255, 255, 0, 128 }, + { 0, 255, 255, 128 }, { 255, 0, 255, 128 }, { 255, 255, 255, 128 } + }; + + glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); + + int maxTris = triangles.count(); + + if ( numRec > 0 ) { + auto recs = sidx.sibling( s + 3, 0 ); + for ( int i = 0; i < numRec; i++ ) { + auto subrec = recs.child( i, 0 ); + auto off = subrec.child( 0, 2 ).data().toInt() / 3; + auto cnt = subrec.child( 1, 2 ).data().toInt(); + + int j = off; + for ( j; j < cnt + off; j++ ) { + if ( j >= maxTris ) + continue; + + glColor( Color4(cols.value( i % 7 )) ); + Triangle tri = triangles[j]; + glBegin( GL_TRIANGLES ); + glVertex( transVerts.value( tri.v1() ) ); + glVertex( transVerts.value( tri.v2() ) ); + glVertex( transVerts.value( tri.v3() ) ); + glEnd(); + } + } + } else { + glColor( Color4(cols.value( idx.row() % 7 )) ); + + int i = off; + for ( i; i < cnt + off; i++ ) { + if ( i >= maxTris ) + continue; + + Triangle tri = triangles[i]; + glBegin( GL_TRIANGLES ); + glVertex( transVerts.value( tri.v1() ) ); + glVertex( transVerts.value( tri.v2() ) ); + glVertex( transVerts.value( tri.v3() ) ); + glEnd(); + } + } + pop(); + return; + } + + // Draw all bones' bounding spheres + if ( n == "NiSkinData" || n == "BSSkin::BoneData" ) { + // Get shape block + if ( nif->getBlock( nif->getParent( nif->getParent( blk ) ) ) == iBlock ) { + auto bones = nif->getIndex( blk, "Bone List" ); + int ct = nif->rowCount( bones ); + + for ( int i = 0; i < ct; i++ ) { + auto b = bones.child( i, 0 ); + boneSphere( nif, b ); + } + } + pop(); + return; + } + + // Draw bone bounding sphere + if ( n == "Bone List" ) { + if ( nif->isArray( idx ) ) { + for ( int i = 0; i < nif->rowCount( idx ); i++ ) + boneSphere( nif, idx.child( i, 0 ) ); + } else { + boneSphere( nif, idx ); + } + } + + // General wireframe + if ( blk == iBlock && idx != iVertData && p != "Vertex Data" && p != "Vertices" ) { + glLineWidth( 1.6f ); + glNormalColor(); + for ( const Triangle& tri : triangles ) { + glBegin( GL_TRIANGLES ); + glVertex( transVerts.value( tri.v1() ) ); + glVertex( transVerts.value( tri.v2() ) ); + glVertex( transVerts.value( tri.v3() ) ); + glEnd(); + } + } + + glDisable( GL_POLYGON_OFFSET_FILL ); + + pop(); +} + +BoundSphere BSShape::bounds() const +{ + if ( updateBounds ) { + updateBounds = false; + if ( verts.count() ) { + boundSphere = BoundSphere( verts ); + } else { + boundSphere = BoundSphere( bsphereCenter, bsphereRadius ); + } + } + + return worldTrans() * boundSphere; +} + +bool BSShape::isHidden() const +{ + return Node::isHidden(); +} diff --git a/src/gl/bsshape.h b/src/gl/bsshape.h new file mode 100644 index 000000000..e0ade1f25 --- /dev/null +++ b/src/gl/bsshape.h @@ -0,0 +1,57 @@ +#ifndef BSSHAPE_H +#define BSSHAPE_H + +#include "glmesh.h" +#include "glnode.h" +#include "gltools.h" + +class BSShape : public Shape +{ + +public: + BSShape( Scene * s, const QModelIndex & b ); + ~BSShape() { clear(); } + + // IControllable + + void clear() override; + void update( const NifModel * nif, const QModelIndex & ) override; + void transform() override; + + // end IControllable + + // Node + + void transformShapes() override; + + void drawShapes( NodeList * secondPass = nullptr, bool presort = false ) override; + void drawSelection() const override; + + BoundSphere bounds() const override; + + bool isHidden() const override; + //QString textStats() const override; + + // end Node + + // Shape + + void drawVerts() const override; + QModelIndex vertexAt( int ) const override; + +protected: + + QPersistentModelIndex iVertData; + QPersistentModelIndex iTriData; + + QString skinDataName; + QString skinInstName; + + int numVerts; + int numTris; + + Vector3 bsphereCenter; + float bsphereRadius = 0.0; +}; + +#endif // BSSHAPE_H diff --git a/src/gl/controllers.cpp b/src/gl/controllers.cpp new file mode 100644 index 000000000..4443ac618 --- /dev/null +++ b/src/gl/controllers.cpp @@ -0,0 +1,1176 @@ +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2015, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +3. The name of the NIF File Format Library and Tools project may not be +used to endorse or promote products derived from this software +without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +***** END LICENCE BLOCK *****/ + +#include "controllers.h" +#include "settings.h" + +#include "glmesh.h" +#include "glnode.h" +#include "glparticles.h" +#include "glproperty.h" +#include "glscene.h" + + +// `NiControllerManager` blocks + +ControllerManager::ControllerManager( Node * node, const QModelIndex & index ) + : Controller( index ), target( node ) +{ +} + +bool ControllerManager::update( const NifModel * nif, const QModelIndex & index ) +{ + if ( Controller::update( nif, index ) ) { + if ( target ) { + Scene * scene = target->scene; + QVector lSequences = nif->getLinkArray( index, "Controller Sequences" ); + for ( const auto l : lSequences ) { + QModelIndex iSeq = nif->getBlock( l, "NiControllerSequence" ); + + if ( iSeq.isValid() ) { + QString name = nif->get( iSeq, "Name" ); + + if ( !scene->animGroups.contains( name ) ) { + scene->animGroups.append( name ); + + QMap tags = scene->animTags[name]; + + QModelIndex iKeys = nif->getBlock( nif->getLink( iSeq, "Text Keys" ), "NiTextKeyExtraData" ); + QModelIndex iTags = nif->getIndex( iKeys, "Text Keys" ); + + for ( int r = 0; r < nif->rowCount( iTags ); r++ ) { + tags.insert( nif->get( iTags.child( r, 0 ), "Value" ), nif->get( iTags.child( r, 0 ), "Time" ) ); + } + + scene->animTags[name] = tags; + } + } + } + } + + return true; + } + + return false; +} + +void ControllerManager::setSequence( const QString & seqname ) +{ + const NifModel * nif = static_cast(iBlock.model()); + + if ( target && iBlock.isValid() && nif ) { + MultiTargetTransformController * multiTargetTransformer = 0; + for ( Controller * c : target->controllers ) { + if ( c->typeId() == "NiMultiTargetTransformController" ) { + multiTargetTransformer = static_cast(c); + break; + } + } + + QVector lSequences = nif->getLinkArray( iBlock, "Controller Sequences" ); + for ( const auto l : lSequences ) { + QModelIndex iSeq = nif->getBlock( l, "NiControllerSequence" ); + + if ( iSeq.isValid() && nif->get( iSeq, "Name" ) == seqname ) { + start = nif->get( iSeq, "Start Time" ); + stop = nif->get( iSeq, "Stop Time" ); + phase = nif->get( iSeq, "Phase" ); + frequency = nif->get( iSeq, "Frequency" ); + + QModelIndex iCtrlBlcks = nif->getIndex( iSeq, "Controlled Blocks" ); + + for ( int r = 0; r < nif->rowCount( iCtrlBlcks ); r++ ) { + QModelIndex iCB = iCtrlBlcks.child( r, 0 ); + + QModelIndex iInterpolator = nif->getBlock( nif->getLink( iCB, "Interpolator" ), "NiInterpolator" ); + + QModelIndex iController = nif->getBlock( nif->getLink( iCB, "Controller" ), "NiTimeController" ); + + QString nodename = nif->get( iCB, "Node Name" ); + + if ( nodename.isEmpty() ) { + QModelIndex idx = nif->getIndex( iCB, "Node Name Offset" ); + nodename = idx.sibling( idx.row(), NifModel::ValueCol ).data( NifSkopeDisplayRole ).toString(); + } + + QString proptype = nif->get( iCB, "Property Type" ); + + if ( proptype.isEmpty() ) { + QModelIndex idx = nif->getIndex( iCB, "Property Type Offset" ); + proptype = idx.sibling( idx.row(), NifModel::ValueCol ).data( NifSkopeDisplayRole ).toString(); + } + + QString ctrltype = nif->get( iCB, "Controller Type" ); + + if ( ctrltype.isEmpty() ) { + QModelIndex idx = nif->getIndex( iCB, "Controller Type Offset" ); + ctrltype = idx.sibling( idx.row(), NifModel::ValueCol ).data( NifSkopeDisplayRole ).toString(); + } + + QString var1 = nif->get( iCB, "Variable 1" ); + + if ( var1.isEmpty() ) { + QModelIndex idx = nif->getIndex( iCB, "Variable 1 Offset" ); + var1 = idx.sibling( idx.row(), NifModel::ValueCol ).data( NifSkopeDisplayRole ).toString(); + } + + QString var2 = nif->get( iCB, "Variable 2" ); + + if ( var2.isEmpty() ) { + QModelIndex idx = nif->getIndex( iCB, "Variable 2 Offset" ); + var2 = idx.sibling( idx.row(), NifModel::ValueCol ).data( NifSkopeDisplayRole ).toString(); + } + + Node * node = target->findChild( nodename ); + + if ( !node ) + continue; + + if ( ctrltype == "NiTransformController" && multiTargetTransformer ) { + if ( multiTargetTransformer->setInterpolator( node, iInterpolator ) ) { + multiTargetTransformer->start = start; + multiTargetTransformer->stop = stop; + multiTargetTransformer->phase = phase; + multiTargetTransformer->frequency = frequency; + continue; + } + } + + if ( ctrltype == "BSLightingShaderPropertyFloatController" + || ctrltype == "BSLightingShaderPropertyColorController" + || ctrltype == "BSEffectShaderPropertyFloatController" + || ctrltype == "BSEffectShaderPropertyColorController" + || ctrltype == "BSNiAlphaPropertyTestRefController" ) + { + //qDebug() << node->name; + + auto ctrl = node->findController( proptype, iController ); + if ( ctrl ) { + ctrl->setInterpolator( iInterpolator ); + } + continue; + } + + Controller * ctrl = node->findController( proptype, ctrltype, var1, var2 ); + + if ( ctrl ) { + ctrl->start = start; + ctrl->stop = stop; + ctrl->phase = phase; + ctrl->frequency = frequency; + + ctrl->setInterpolator( iInterpolator ); + } + } + } + } + } +} + + +// `NiKeyframeController` blocks + +KeyframeController::KeyframeController( Node * node, const QModelIndex & index ) + : Controller( index ), target( node ), lTrans( 0 ), lRotate( 0 ), lScale( 0 ) +{ +} + +void KeyframeController::updateTime( float time ) +{ + if ( !(active && target) ) + return; + + time = ctrlTime( time ); + + interpolate( target->local.rotation, iRotations, time, lRotate ); + interpolate( target->local.translation, iTranslations, time, lTrans ); + interpolate( target->local.scale, iScales, time, lScale ); +} + +bool KeyframeController::update( const NifModel * nif, const QModelIndex & index ) +{ + if ( Controller::update( nif, index ) ) { + iTranslations = nif->getIndex( iData, "Translations" ); + iRotations = nif->getIndex( iData, "Rotations" ); + + if ( !iRotations.isValid() ) + iRotations = iData; + + iScales = nif->getIndex( iData, "Scales" ); + return true; + } + + return false; +} + + +// `NiTransformController` blocks + +TransformController::TransformController( Node * node, const QModelIndex & index ) + : Controller( index ), target( node ) +{ +} + +void TransformController::updateTime( float time ) +{ + if ( !(active && target) ) + return; + + time = ctrlTime( time ); + + if ( interpolator ) { + interpolator->updateTransform( target->local, time ); + } +} + +void TransformController::setInterpolator( const QModelIndex & iBlock ) +{ + const NifModel * nif = static_cast(iBlock.model()); + + if ( nif && iBlock.isValid() ) { + if ( interpolator ) { + delete interpolator; + interpolator = 0; + } + + if ( nif->isNiBlock( iBlock, "NiBSplineCompTransformInterpolator" ) ) { + iInterpolator = iBlock; + interpolator = new BSplineTransformInterpolator( this ); + } else if ( nif->isNiBlock( iBlock, "NiTransformInterpolator" ) ) { + iInterpolator = iBlock; + interpolator = new TransformInterpolator( this ); + } + + if ( interpolator ) { + interpolator->update( nif, iInterpolator ); + } + } +} + + +// `NiMultiTargetTransformController` blocks + +MultiTargetTransformController::MultiTargetTransformController( Node * node, const QModelIndex & index ) + : Controller( index ), target( node ) +{ +} + +void MultiTargetTransformController::updateTime( float time ) +{ + if ( !(active && target) ) + return; + + time = ctrlTime( time ); + + for ( const TransformTarget& tt : extraTargets ) { + if ( tt.first && tt.second ) { + tt.second->updateTransform( tt.first->local, time ); + } + } +} + +bool MultiTargetTransformController::update( const NifModel * nif, const QModelIndex & index ) +{ + if ( Controller::update( nif, index ) ) { + if ( target ) { + Scene * scene = target->scene; + extraTargets.clear(); + + QVector lTargets = nif->getLinkArray( index, "Extra Targets" ); + for ( const auto l : lTargets ) { + Node * node = scene->getNode( nif, nif->getBlock( l ) ); + + if ( node ) { + extraTargets.append( TransformTarget( node, 0 ) ); + } + } + } + + return true; + } + + for ( const TransformTarget& tt : extraTargets ) { + // TODO: update the interpolators + } + + return false; +} + +bool MultiTargetTransformController::setInterpolator( Node * node, const QModelIndex & iInterpolator ) +{ + const NifModel * nif = static_cast(iInterpolator.model()); + + if ( !nif || !iInterpolator.isValid() ) + return false; + + QMutableListIterator it( extraTargets ); + + while ( it.hasNext() ) { + it.next(); + + if ( it.value().first == node ) { + if ( it.value().second ) { + delete it.value().second; + it.value().second = 0; + } + + if ( nif->isNiBlock( iInterpolator, "NiBSplineCompTransformInterpolator" ) ) { + it.value().second = new BSplineTransformInterpolator( this ); + } else if ( nif->isNiBlock( iInterpolator, "NiTransformInterpolator" ) ) { + it.value().second = new TransformInterpolator( this ); + } + + if ( it.value().second ) { + it.value().second->update( nif, iInterpolator ); + } + + return true; + } + } + + return false; +} + + +// `NiVisController` blocks + +VisibilityController::VisibilityController( Node * node, const QModelIndex & index ) + : Controller( index ), target( node ), visLast( 0 ) +{ +} + +void VisibilityController::updateTime( float time ) +{ + if ( !(active && target) ) + return; + + time = ctrlTime( time ); + + bool isVisible; + + if ( interpolate( isVisible, iData, "Data", time, visLast ) ) { + target->flags.node.hidden = !isVisible; + } +} + +bool VisibilityController::update( const NifModel * nif, const QModelIndex & index ) +{ + if ( Controller::update( nif, index ) ) { + return true; + } + + return false; +} + + +// `NiGeomMorpherController` blocks + +MorphController::MorphController( Shape * mesh, const QModelIndex & index ) + : Controller( index ), target( mesh ) +{ +} + +MorphController::~MorphController() +{ + qDeleteAll( morph ); +} + +void MorphController::updateTime( float time ) +{ + if ( !(target && iData.isValid() && active && morph.count() > 1) ) + return; + + time = ctrlTime( time ); + + if ( target->verts.count() != morph[0]->verts.count() ) + return; + + target->verts = morph[0]->verts; + + float x; + + for ( int i = 1; i < morph.count(); i++ ) { + MorphKey * key = morph[i]; + + if ( interpolate( x, key->iFrames, time, key->index ) ) { + if ( x < 0 ) + x = 0; + if ( x > 1 ) + x = 1; + + if ( x != 0 && target->verts.count() == key->verts.count() ) { + for ( int v = 0; v < target->verts.count(); v++ ) + target->verts[v] += key->verts[v] * x; + } + } + } + + target->updateBounds = true; +} + +bool MorphController::update( const NifModel * nif, const QModelIndex & index ) +{ + if ( Controller::update( nif, index ) ) { + qDeleteAll( morph ); + morph.clear(); + + QModelIndex midx = nif->getIndex( iData, "Morphs" ); + + for ( int r = 0; r < nif->rowCount( midx ); r++ ) { + QModelIndex iInterpolators, iInterpolatorWeights; + + if ( nif->checkVersion( 0, 0x14000005 ) ) { + iInterpolators = nif->getIndex( iBlock, "Interpolators" ); + } else if ( nif->checkVersion( 0x14010003, 0 ) ) { + iInterpolatorWeights = nif->getIndex( iBlock, "Interpolator Weights" ); + } + + QModelIndex iKey = midx.child( r, 0 ); + + MorphKey * key = new MorphKey; + key->index = 0; + + // this is ugly... + if ( iInterpolators.isValid() ) { + key->iFrames = nif->getIndex( nif->getBlock( nif->getLink( nif->getBlock( nif->getLink( iInterpolators.child( r, 0 ) ), "NiFloatInterpolator" ), "Data" ), "NiFloatData" ), "Data" ); + } else if ( iInterpolatorWeights.isValid() ) { + key->iFrames = nif->getIndex( nif->getBlock( nif->getLink( nif->getBlock( nif->getLink( iInterpolatorWeights.child( r, 0 ), "Interpolator" ), "NiFloatInterpolator" ), "Data" ), "NiFloatData" ), "Data" ); + } else { + key->iFrames = iKey; + } + + key->verts = nif->getArray( nif->getIndex( iKey, "Vectors" ) ); + + morph.append( key ); + } + + return true; + } + + return false; +} + + +// `NiUVController` blocks + +UVController::UVController( Shape * mesh, const QModelIndex & index ) + : Controller( index ), target( mesh ) +{ +} + +UVController::~UVController() +{ +} + +void UVController::updateTime( float time ) +{ + const NifModel * nif = static_cast(iData.model()); + QModelIndex uvGroups = nif->getIndex( iData, "UV Groups" ); + + // U trans, V trans, U scale, V scale + // see NiUVData compound in nif.xml + float val[4] = { 0.0, 0.0, 1.0, 1.0 }; + + if ( uvGroups.isValid() ) { + for ( int i = 0; i < 4 && i < nif->rowCount( uvGroups ); i++ ) { + interpolate( val[i], uvGroups.child( i, 0 ), ctrlTime( time ), luv ); + } + + // adjust coords; verified in SceneImmerse + for ( int i = 0; i < target->coords[0].size(); i++ ) { + // operating on pointers makes this too complicated, so we don't + Vector2 current = target->coords[0][i]; + // scaling/tiling applied before translation + // Note that scaling is relative to center! + current += Vector2( -0.5, -0.5 ); + current = Vector2( current[0] * val[2], current[1] * val[3] ); + current += Vector2( -val[0], val[1] ); + current += Vector2( 0.5, 0.5 ); + target->coords[0][i] = current; + } + } + + target->updateData = true; +} + +bool UVController::update( const NifModel * nif, const QModelIndex & index ) +{ + if ( Controller::update( nif, index ) ) { + // do stuff here + return true; + } + + return false; +} + + +float random( float r ) +{ + return r * rand() / RAND_MAX; +} + +Vector3 random( Vector3 v ) +{ + v[0] *= random( 1.0 ); + v[1] *= random( 1.0 ); + v[2] *= random( 1.0 ); + return v; +} + + +// `NiParticleSystemController` and other blocks + +ParticleController::ParticleController( Particles * particles, const QModelIndex & index ) + : Controller( index ), target( particles ) +{ +} + +bool ParticleController::update( const NifModel * nif, const QModelIndex & index ) +{ + if ( !target ) + return false; + + if ( Controller::update( nif, index ) || (index.isValid() && iExtras.contains( index )) ) { + emitNode = target->scene->getNode( nif, nif->getBlock( nif->getLink( iBlock, "Emitter" ) ) ); + emitStart = nif->get( iBlock, "Emit Start Time" ); + emitStop = nif->get( iBlock, "Emit Stop Time" ); + emitRate = nif->get( iBlock, "Emit Rate" ); + emitRadius = nif->get( iBlock, "Start Random" ); + emitAccu = 0; + emitLast = emitStart; + + spd = nif->get( iBlock, "Speed" ); + spdRnd = nif->get( iBlock, "Speed Random" ); + + ttl = nif->get( iBlock, "Lifetime" ); + ttlRnd = nif->get( iBlock, "Lifetime Random" ); + + inc = nif->get( iBlock, "Vertical Direction" ); + incRnd = nif->get( iBlock, "Vertical Angle" ); + + dec = nif->get( iBlock, "Horizontal Direction" ); + decRnd = nif->get( iBlock, "Horizontal Angle" ); + + size = nif->get( iBlock, "Size" ); + grow = 0.0; + fade = 0.0; + + list.clear(); + + QModelIndex iParticles = nif->getIndex( iBlock, "Particles" ); + + if ( iParticles.isValid() ) { + emitMax = nif->get( iBlock, "Num Particles" ); + int active = nif->get( iBlock, "Num Valid" ); + + //iParticles = nif->getIndex( iParticles, "Particles" ); + //if ( iParticles.isValid() ) + //{ + for ( int p = 0; p < active && p < nif->rowCount( iParticles ); p++ ) { + Particle particle; + particle.velocity = nif->get( iParticles.child( p, 0 ), "Velocity" ); + particle.lifetime = nif->get( iParticles.child( p, 0 ), "Lifetime" ); + particle.lifespan = nif->get( iParticles.child( p, 0 ), "Lifespan" ); + particle.lasttime = nif->get( iParticles.child( p, 0 ), "Timestamp" ); + particle.vertex = nif->get( iParticles.child( p, 0 ), "Vertex ID" ); + // Display saved particle start on initial load + list.append( particle ); + } + + //} + } + + if ( (nif->get( iBlock, "Emit Flags" ) & 1) == 0 ) { + emitRate = emitMax / (ttl + ttlRnd / 2); + } + + iExtras.clear(); + grav.clear(); + iColorKeys = QModelIndex(); + QModelIndex iExtra = nif->getBlock( nif->getLink( iBlock, "Particle Extra" ) ); + + while ( iExtra.isValid() ) { + iExtras.append( iExtra ); + + QString name = nif->itemName( iExtra ); + + if ( name == "NiParticleGrowFade" ) { + grow = nif->get( iExtra, "Grow" ); + fade = nif->get( iExtra, "Fade" ); + } else if ( name == "NiParticleColorModifier" ) { + iColorKeys = nif->getIndex( nif->getBlock( nif->getLink( iExtra, "Color Data" ), "NiColorData" ), "Data" ); + } else if ( name == "NiGravity" ) { + Gravity g; + g.force = nif->get( iExtra, "Force" ); + g.type = nif->get( iExtra, "Type" ); + g.position = nif->get( iExtra, "Position" ); + g.direction = nif->get( iExtra, "Direction" ); + grav.append( g ); + } + + iExtra = nif->getBlock( nif->getLink( iExtra, "Next Modifier" ) ); + } + + return true; + } + + return false; +} + +void ParticleController::updateTime( float time ) +{ + if ( !(target && active) ) + return; + + localtime = ctrlTime( time ); + + int n = 0; + + while ( n < list.count() ) { + Particle & p = list[n]; + + float deltaTime = (localtime > p.lasttime ? localtime - p.lasttime : 0); //( stop - start ) - p.lasttime + localtime ); + + p.lifetime += deltaTime; + + if ( p.lifetime < p.lifespan && p.vertex < target->verts.count() ) { + p.position = target->verts[p.vertex]; + + for ( int i = 0; i < 4; i++ ) + moveParticle( p, deltaTime / 4 ); + + p.lasttime = localtime; + n++; + } else { + list.remove( n ); + } + } + + if ( emitNode && emitNode->isVisible() && localtime >= emitStart && localtime <= emitStop ) { + float emitDelta = (localtime > emitLast ? localtime - emitLast : 0); + emitLast = localtime; + + emitAccu += emitDelta * emitRate; + + int num = int( emitAccu ); + + if ( num > 0 ) { + emitAccu -= num; + + while ( num-- > 0 && list.count() < target->verts.count() ) { + Particle p; + startParticle( p ); + list.append( p ); + } + } + } + + n = 0; + + while ( n < list.count() ) { + Particle & p = list[n]; + p.vertex = n; + target->verts[n] = p.position; + + if ( n < target->sizes.count() ) + sizeParticle( p, target->sizes[n] ); + + if ( n < target->colors.count() ) + colorParticle( p, target->colors[n] ); + + n++; + } + + target->active = list.count(); + target->size = size; +} + +void ParticleController::startParticle( Particle & p ) +{ + p.position = random( emitRadius * 2 ) - emitRadius; + p.position += target->worldTrans().rotation.inverted() * (emitNode->worldTrans().translation - target->worldTrans().translation); + + float i = inc + random( incRnd ); + float d = dec + random( decRnd ); + + p.velocity = Vector3( rand() & 1 ? sin( i ) : -sin( i ), 0, cos( i ) ); + + Matrix m; m.fromEuler( 0, 0, rand() & 1 ? d : -d ); + p.velocity = m * p.velocity; + + p.velocity = p.velocity * (spd + random( spdRnd )); + p.velocity = target->worldTrans().rotation.inverted() * emitNode->worldTrans().rotation * p.velocity; + + p.lifetime = 0; + p.lifespan = ttl + random( ttlRnd ); + p.lasttime = localtime; +} + +void ParticleController::moveParticle( Particle & p, float deltaTime ) +{ + for ( Gravity g : grav ) { + switch ( g.type ) { + case 0: + p.velocity += g.direction * (g.force * deltaTime); + break; + case 1: + { + Vector3 dir = (g.position - p.position); + dir.normalize(); + p.velocity += dir * (g.force * deltaTime); + } + break; + } + } + p.position += p.velocity * deltaTime; +} + +void ParticleController::sizeParticle( Particle & p, float & size ) +{ + size = 1.0; + + if ( grow > 0 && p.lifetime < grow ) + size *= p.lifetime / grow; + + if ( fade > 0 && p.lifespan - p.lifetime < fade ) + size *= (p.lifespan - p.lifetime) / fade; +} + +void ParticleController::colorParticle( Particle & p, Color4 & color ) +{ + if ( iColorKeys.isValid() ) { + int i = 0; + interpolate( color, iColorKeys, p.lifetime / p.lifespan, i ); + } +} + + +// `BSNiAlphaPropertyTestRefController` + +AlphaController::AlphaController( AlphaProperty * prop, const QModelIndex & index ) + : Controller( index ), alphaProp( prop ), lAlpha( 0 ) +{ +} + +// `NiAlphaController` + +AlphaController::AlphaController( MaterialProperty * prop, const QModelIndex & index ) + : Controller( index ), materialProp( prop ), lAlpha( 0 ) +{ +} + +void AlphaController::updateTime( float time ) +{ + if ( !(active) ) + return; + + if ( materialProp ) { + interpolate( materialProp->alpha, iData, "Data", ctrlTime( time ), lAlpha ); + + if ( materialProp->alpha < 0 ) + materialProp->alpha = 0; + + if ( materialProp->alpha > 1 ) + materialProp->alpha = 1; + } else if ( alphaProp ) { + float threshold; + + if ( interpolate( threshold, iData, "Data", ctrlTime( time ), lAlpha ) ) + alphaProp->setThreshold( threshold / 255.0 ); + } +} + + +// `NiMaterialColorController` + +MaterialColorController::MaterialColorController( MaterialProperty * prop, const QModelIndex & index ) + : Controller( index ), target( prop ), lColor( 0 ), tColor( tAmbient ) +{ +} + +void MaterialColorController::updateTime( float time ) +{ + if ( !(active && target) ) + return; + + Vector3 v3; + interpolate( v3, iData, "Data", ctrlTime( time ), lColor ); + + Color4 color( Color3( v3 ), 1.0 ); + + switch ( tColor ) { + case tAmbient: + target->ambient = color; + break; + case tDiffuse: + target->diffuse = color; + break; + case tSpecular: + target->specular = color; + break; + case tSelfIllum: + target->emissive = color; + break; + } +} + +bool MaterialColorController::update( const NifModel * nif, const QModelIndex & index ) +{ + if ( Controller::update( nif, index ) ) { + if ( nif->checkVersion( 0x0A010000, 0 ) ) { + tColor = nif->get( iBlock, "Target Color" ); + } else { + tColor = ((nif->get( iBlock, "Flags" ) >> 4) & 7); + } + + return true; + } + + return false; +} + + +// `NiFlipController` + +TexFlipController::TexFlipController( TexturingProperty * prop, const QModelIndex & index ) + : Controller( index ), target( prop ), flipDelta( 0 ), flipSlot( 0 ) +{ +} + +TexFlipController::TexFlipController( TextureProperty * prop, const QModelIndex & index ) + : Controller( index ), oldTarget( prop ), flipDelta( 0 ), flipSlot( 0 ) +{ +} + +void TexFlipController::updateTime( float time ) +{ + const NifModel * nif = static_cast(iSources.model()); + + if ( !((target || oldTarget) && active && iSources.isValid() && nif) ) + return; + + float r = 0; + + if ( iData.isValid() ) + interpolate( r, iData, "Data", ctrlTime( time ), flipLast ); + else if ( flipDelta > 0 ) + r = ctrlTime( time ) / flipDelta; + + // TexturingProperty + if ( target ) { + target->textures[flipSlot & 7].iSource = nif->getBlock( nif->getLink( iSources.child( (int)r, 0 ) ), "NiSourceTexture" ); + } else if ( oldTarget ) { + oldTarget->iImage = nif->getBlock( nif->getLink( iSources.child( (int)r, 0 ) ), "NiImage" ); + } +} + +bool TexFlipController::update( const NifModel * nif, const QModelIndex & index ) +{ + if ( Controller::update( nif, index ) ) { + flipDelta = nif->get( iBlock, "Delta" ); + flipSlot = nif->get( iBlock, "Texture Slot" ); + + if ( nif->checkVersion( 0x04000000, 0 ) ) { + iSources = nif->getIndex( iBlock, "Sources" ); + } else { + iSources = nif->getIndex( iBlock, "Images" ); + } + + return true; + } + + return false; +} + + +// `NiTextureTransformController` + +TexTransController::TexTransController( TexturingProperty * prop, const QModelIndex & index ) + : Controller( index ), target( prop ), texSlot( 0 ), texOP( 0 ) +{ +} + +void TexTransController::updateTime( float time ) +{ + if ( !(target && active) ) + return; + + TexturingProperty::TexDesc * tex = &target->textures[texSlot & 7]; + + float val; + + if ( interpolate( val, iData, "Data", ctrlTime( time ), lX ) ) { + // If desired, we could force display even if texture transform was disabled: + // tex->hasTransform = true; + // however "Has Texture Transform" doesn't exist until 10.1.0.0, and neither does + // NiTextureTransformController - so we won't bother + switch ( texOP ) { + case 0: + tex->translation[0] = val; + break; + case 1: + tex->translation[1] = val; + break; + case 2: + tex->rotation = val; + break; + case 3: + tex->tiling[0] = val; + break; + case 4: + tex->tiling[1] = val; + break; + } + } +} + +bool TexTransController::update( const NifModel * nif, const QModelIndex & index ) +{ + if ( Controller::update( nif, index ) ) { + texSlot = nif->get( iBlock, "Texture Slot" ); + texOP = nif->get( iBlock, "Operation" ); + return true; + } + + return false; +} + + + +EffectFloatController::EffectFloatController( BSEffectShaderProperty * prop, const QModelIndex & index ) + : Controller( index ), target( prop ) +{ +} + +void EffectFloatController::updateTime( float time ) +{ + if ( !(target && active) ) + return; + + float val; + + int lIdx; + + if ( interpolate( val, iData, "Data", ctrlTime( time ), lIdx ) ) { + switch ( variable ) { + case EffectFloat::Emissive_Multiple: + target->setEmissive( target->getEmissiveColor(), val ); + break; + case EffectFloat::Falloff_Start_Angle: + target->falloff.startAngle = val; + break; + case EffectFloat::Falloff_Stop_Angle: + target->falloff.stopAngle = val; + break; + case EffectFloat::Falloff_Start_Opacity: + target->falloff.startOpacity = val; + break; + case EffectFloat::Falloff_Stop_Opacity: + target->falloff.stopOpacity = val; + break; + case EffectFloat::Alpha: + { + auto c = target->getEmissiveColor(); + auto m = target->getEmissiveMult(); + + target->setEmissive( Color4( c.red(), c.green(), c.blue(), val ), m ); + } + break; + case EffectFloat::U_Offset: + target->setUvOffset( val, target->getUvOffset().y ); + break; + case EffectFloat::U_Scale: + target->setUvScale( val, target->getUvScale().y ); + break; + case EffectFloat::V_Offset: + target->setUvOffset( target->getUvOffset().x, val ); + break; + case EffectFloat::V_Scale: + target->setUvScale( target->getUvScale().x, val ); + break; + default: + break; + } + } +} + +bool EffectFloatController::update( const NifModel * nif, const QModelIndex & index ) +{ + if ( Controller::update( nif, index ) ) { + variable = EffectFloat::Variable( nif->get( iBlock, "Type of Controlled Variable" ) ); + return true; + } + + return false; +} + + +EffectColorController::EffectColorController( BSEffectShaderProperty * prop, const QModelIndex & index ) + : Controller( index ), target( prop ) +{ +} + +void EffectColorController::updateTime( float time ) +{ + if ( !(target && active) ) + return; + + Vector3 val; + + int lIdx; + + if ( interpolate( val, iData, "Data", ctrlTime( time ), lIdx ) ) { + switch ( variable ) { + case 0: + { + auto c = target->getEmissiveColor(); + auto m = target->getEmissiveMult(); + + target->setEmissive( Color4( val[0], val[1], val[2], c.alpha() ), m ); + break; + } + default: + break; + } + } +} + +bool EffectColorController::update( const NifModel * nif, const QModelIndex & index ) +{ + if ( Controller::update( nif, index ) ) { + variable = nif->get( iBlock, "Type of Controlled Color" ); + return true; + } + + return false; +} + + +LightingFloatController::LightingFloatController( BSLightingShaderProperty * prop, const QModelIndex & index ) + : Controller( index ), target( prop ) +{ +} + +void LightingFloatController::updateTime( float time ) +{ + if ( !(target && active) ) + return; + + float val; + + int lIdx; + + if ( interpolate( val, iData, "Data", ctrlTime( time ), lIdx ) ) { + switch ( variable ) { + case LightingFloat::Refraction_Strength: + break; + case LightingFloat::Reflection_Strength: + target->setEnvironmentReflection( val ); + break; + case LightingFloat::Glossiness: + target->setSpecular( target->getSpecularColor(), val, target->getSpecularStrength() ); + break; + case LightingFloat::Specular_Strength: + target->setSpecular( target->getSpecularColor(), target->getSpecularGloss(), val ); + break; + case LightingFloat::Emissive_Multiple: + target->setEmissive( target->getEmissiveColor(), val ); + break; + case LightingFloat::Alpha: + target->setAlpha( val ); + break; + case LightingFloat::U_Offset: + target->setUvOffset( val, target->getUvOffset().y ); + break; + case LightingFloat::U_Scale: + target->setUvScale( val, target->getUvScale().y ); + break; + case LightingFloat::V_Offset: + target->setUvOffset( target->getUvOffset().x, val ); + break; + case LightingFloat::V_Scale: + target->setUvScale( target->getUvScale().x, val ); + break; + default: + break; + } + } +} + +bool LightingFloatController::update( const NifModel * nif, const QModelIndex & index ) +{ + if ( Controller::update( nif, index ) ) { + variable = LightingFloat::Variable(nif->get( iBlock, "Type of Controlled Variable" )); + return true; + } + + return false; +} + + +LightingColorController::LightingColorController( BSLightingShaderProperty * prop, const QModelIndex & index ) + : Controller( index ), target( prop ) +{ +} + +void LightingColorController::updateTime( float time ) +{ + if ( !(target && active) ) + return; + + Vector3 val; + + int lIdx; + + if ( interpolate( val, iData, "Data", ctrlTime( time ), lIdx ) ) { + switch ( variable ) { + case 0: + target->setSpecular( { val[0], val[1], val[2] }, target->getSpecularGloss(), target->getSpecularStrength() ); + break; + case 1: + target->setEmissive( { val[0], val[1], val[2] }, target->getEmissiveMult() ); + break; + default: + break; + } + } +} + +bool LightingColorController::update( const NifModel * nif, const QModelIndex & index ) +{ + if ( Controller::update( nif, index ) ) { + variable = nif->get( iBlock, "Type of Controlled Color" ); + return true; + } + + return false; +} diff --git a/src/gl/controllers.h b/src/gl/controllers.h new file mode 100644 index 000000000..6ac824d6b --- /dev/null +++ b/src/gl/controllers.h @@ -0,0 +1,444 @@ +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2015, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +3. The name of the NIF File Format Library and Tools project may not be +used to endorse or promote products derived from this software +without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +***** END LICENCE BLOCK *****/ + +#ifndef CONTROLLERS_H +#define CONTROLLERS_H + +#include "glcontroller.h" // Inherited + + +//! @file controllers.h Controller subclasses + +class Shape; +class Node; + +//! Controller for `NiControllerManager` blocks +class ControllerManager final : public Controller +{ +public: + ControllerManager( Node * node, const QModelIndex & index ); + + void updateTime( float ) override final {} + + bool update( const NifModel * nif, const QModelIndex & index ) override final; + + void setSequence( const QString & seqname ) override final; + +protected: + QPointer target; +}; + + +//! Controller for `NiKeyframeController` blocks +class KeyframeController final : public Controller +{ +public: + KeyframeController( Node * node, const QModelIndex & index ); + + void updateTime( float time ) override final; + + bool update( const NifModel * nif, const QModelIndex & index ) override final; + +protected: + QPointer target; + + QPersistentModelIndex iTranslations, iRotations, iScales; + + int lTrans, lRotate, lScale; +}; + + +//! Controller for `NiTransformController` blocks +class TransformController final : public Controller +{ +public: + TransformController( Node * node, const QModelIndex & index ); + + void updateTime( float time ) override final; + + void setInterpolator( const QModelIndex & iBlock ) override final; + +protected: + QPointer target; + QPointer interpolator; +}; + + +//! Controller for `NiMultiTargetTransformController` blocks +class MultiTargetTransformController final : public Controller +{ + typedef QPair, QPointer > TransformTarget; + +public: + MultiTargetTransformController( Node * node, const QModelIndex & index ); + + void updateTime( float time ) override final; + + bool update( const NifModel * nif, const QModelIndex & index ) override final; + + bool setInterpolator( Node * node, const QModelIndex & iInterpolator ); + +protected: + QPointer target; + QList extraTargets; +}; + + +//! Controller for `NiVisController` blocks +class VisibilityController final : public Controller +{ +public: + VisibilityController( Node * node, const QModelIndex & index ); + + void updateTime( float time ) override final; + + bool update( const NifModel * nif, const QModelIndex & index ) override final; + +protected: + QPointer target; + + int visLast; +}; + + +//! Controller for `NiGeomMorpherController` blocks +class MorphController final : public Controller +{ + //! A representation of Mesh geometry morphs + struct MorphKey + { + QPersistentModelIndex iFrames; + QVector verts; + int index; + }; + +public: + MorphController( Shape * mesh, const QModelIndex & index ); + ~MorphController(); + + void updateTime( float time ) override final; + + bool update( const NifModel * nif, const QModelIndex & index ) override final; + +protected: + QPointer target; + QVector morph; +}; + + +//! Controller for `NiUVController` blocks +class UVController final : public Controller +{ +public: + UVController( Shape * mesh, const QModelIndex & index ); + + ~UVController(); + + void updateTime( float time ) override final; + + bool update( const NifModel * nif, const QModelIndex & index ) override final; + +protected: + QPointer target; + + int luv; +}; + + +class Particles; + +//! Controller for `NiParticleSystemController` and other blocks +class ParticleController final : public Controller +{ + struct Particle + { + Vector3 position; + Vector3 velocity; + Vector3 unknown; + float lifetime; + float lifespan; + float lasttime; + short y; + short vertex; + + Particle() : lifetime( 0 ), lifespan( 0 ) + { + } + }; + QVector list; + struct Gravity + { + float force; + int type; + Vector3 position; + Vector3 direction; + }; + QVector grav; + + QPointer target; + + float emitStart, emitStop, emitRate, emitLast, emitAccu, emitMax; + QPointer emitNode; + Vector3 emitRadius; + + float spd, spdRnd; + float ttl, ttlRnd; + + float inc, incRnd; + float dec, decRnd; + + float size; + float grow; + float fade; + + float localtime; + + QList iExtras; + QPersistentModelIndex iColorKeys; + +public: + ParticleController( Particles * particles, const QModelIndex & index ); + + bool update( const NifModel * nif, const QModelIndex & index ) override final; + + void updateTime( float time ) override final; + + void startParticle( Particle & p ); + + void moveParticle( Particle & p, float deltaTime ); + + void sizeParticle( Particle & p, float & size ); + + void colorParticle( Particle & p, Color4 & color ); +}; + + +class AlphaProperty; +class MaterialProperty; +class TexturingProperty; +class TextureProperty; +class BSEffectShaderProperty; +class BSLightingShaderProperty; + +//! Controller for alpha values in a MaterialProperty +class AlphaController final : public Controller +{ +public: + AlphaController( MaterialProperty * prop, const QModelIndex & index ); + + AlphaController( AlphaProperty * prop, const QModelIndex & index ); + + void updateTime( float time ) override final; + +protected: + QPointer materialProp; + QPointer alphaProp; + + int lAlpha; +}; + + +//! Controller for color values in a MaterialProperty +class MaterialColorController final : public Controller +{ +public: + MaterialColorController( MaterialProperty * prop, const QModelIndex & index ); + + void updateTime( float time ) override final; + + bool update( const NifModel * nif, const QModelIndex & index ) override final; + +protected: + QPointer target; //!< The MaterialProperty being controlled + + int lColor; //!< Last interpolation time + int tColor; //!< The color slot being controlled + + //! Color slots that can be controlled + enum + { + tAmbient = 0, + tDiffuse = 1, + tSpecular = 2, + tSelfIllum = 3 + }; +}; + + +//! Controller for source textures in a TexturingProperty +class TexFlipController final : public Controller +{ +public: + TexFlipController( TexturingProperty * prop, const QModelIndex & index ); + + TexFlipController( TextureProperty * prop, const QModelIndex & index ); + + void updateTime( float time ) override final; + + bool update( const NifModel * nif, const QModelIndex & index ) override final; + +protected: + QPointer target; + QPointer oldTarget; + + float flipDelta; + int flipSlot; + + int flipLast; + + QPersistentModelIndex iSources; +}; + + +//! Controller for transformations in a TexturingProperty +class TexTransController final : public Controller +{ +public: + TexTransController( TexturingProperty * prop, const QModelIndex & index ); + + void updateTime( float time ) override final; + + bool update( const NifModel * nif, const QModelIndex & index ) override final; + +protected: + QPointer target; + + int texSlot; + int texOP; + + int lX; +}; + +namespace EffectFloat +{ + enum Variable + { + Emissive_Multiple = 0, + Falloff_Start_Angle = 1, + Falloff_Stop_Angle = 2, + Falloff_Start_Opacity = 3, + Falloff_Stop_Opacity = 4, + Alpha = 5, + U_Offset = 6, + U_Scale = 7, + V_Offset = 8, + V_Scale = 9 + }; +} + + +//! Controller for float values in a BSEffectShaderProperty +class EffectFloatController final : public Controller +{ +public: + EffectFloatController( BSEffectShaderProperty * prop, const QModelIndex & index ); + + void updateTime( float time ) override final; + + bool update( const NifModel * nif, const QModelIndex & index ) override final; + +protected: + QPointer target; + + EffectFloat::Variable variable; +}; + + +//! Controller for color values in a BSEffectShaderProperty +class EffectColorController final : public Controller +{ +public: + EffectColorController( BSEffectShaderProperty * prop, const QModelIndex & index ); + + void updateTime( float time ) override final; + + bool update( const NifModel * nif, const QModelIndex & index ) override final; + +protected: + QPointer target; + + int variable; +}; + +namespace LightingFloat +{ + enum Variable + { + Refraction_Strength = 0, + Reflection_Strength = 8, + Glossiness = 9, + Specular_Strength = 10, + Emissive_Multiple = 11, + Alpha = 12, + U_Offset = 20, + U_Scale = 21, + V_Offset = 22, + V_Scale = 23 + }; +} + + +//! Controller for float values in a BSEffectShaderProperty +class LightingFloatController final : public Controller +{ +public: + LightingFloatController( BSLightingShaderProperty * prop, const QModelIndex & index ); + + void updateTime( float time ) override final; + + bool update( const NifModel * nif, const QModelIndex & index ) override final; + +protected: + QPointer target; + + LightingFloat::Variable variable; +}; + + +//! Controller for color values in a BSEffectShaderProperty +class LightingColorController final : public Controller +{ +public: + LightingColorController( BSLightingShaderProperty * prop, const QModelIndex & index ); + + void updateTime( float time ) override final; + + bool update( const NifModel * nif, const QModelIndex & index ) override final; + +protected: + QPointer target; + + int variable; +}; + + +#endif // CONTROLLERS_H diff --git a/src/gl/dds/BlockDXT.cpp b/src/gl/dds/BlockDXT.cpp index 87f867f75..64b0c1901 100644 --- a/src/gl/dds/BlockDXT.cpp +++ b/src/gl/dds/BlockDXT.cpp @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/src/gl/dds/BlockDXT.h b/src/gl/dds/BlockDXT.h index 40b18ce25..fb22a20b2 100644 --- a/src/gl/dds/BlockDXT.h +++ b/src/gl/dds/BlockDXT.h @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/src/gl/dds/Color.h b/src/gl/dds/Color.h index e27f7711d..2f2482aee 100644 --- a/src/gl/dds/Color.h +++ b/src/gl/dds/Color.h @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/src/gl/dds/ColorBlock.cpp b/src/gl/dds/ColorBlock.cpp index 30c4f874c..43ea88871 100644 --- a/src/gl/dds/ColorBlock.cpp +++ b/src/gl/dds/ColorBlock.cpp @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/src/gl/dds/ColorBlock.h b/src/gl/dds/ColorBlock.h index bfd925c6e..e64de4f6f 100644 --- a/src/gl/dds/ColorBlock.h +++ b/src/gl/dds/ColorBlock.h @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/src/gl/dds/Common.h b/src/gl/dds/Common.h index 1ed9cab3e..f3d24c4e9 100644 --- a/src/gl/dds/Common.h +++ b/src/gl/dds/Common.h @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/src/gl/dds/DirectDrawSurface.cpp b/src/gl/dds/DirectDrawSurface.cpp index a60d8f503..09d1b8b29 100644 --- a/src/gl/dds/DirectDrawSurface.cpp +++ b/src/gl/dds/DirectDrawSurface.cpp @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -86,6 +86,7 @@ static const uint FOURCC_DXT5 = MAKEFOURCC( 'D', 'X', 'T', '5' ); static const uint FOURCC_RXGB = MAKEFOURCC( 'R', 'X', 'G', 'B' ); static const uint FOURCC_ATI1 = MAKEFOURCC( 'A', 'T', 'I', '1' ); static const uint FOURCC_ATI2 = MAKEFOURCC( 'A', 'T', 'I', '2' ); +static const uint FOURCC_BC5U = MAKEFOURCC( 'B', 'C', '5', 'U' ); // 32 bit RGB formats. static const uint D3DFMT_R8G8B8 = 20; @@ -594,7 +595,8 @@ bool DirectDrawSurface::isSupported() const && header.pf.fourcc != FOURCC_DXT5 && header.pf.fourcc != FOURCC_RXGB && header.pf.fourcc != FOURCC_ATI1 - && header.pf.fourcc != FOURCC_ATI2 ) + && header.pf.fourcc != FOURCC_ATI2 + && header.pf.fourcc != FOURCC_BC5U ) { // Unknown fourcc code. return false; @@ -831,7 +833,7 @@ void DirectDrawSurface::readBlock( ColorBlock * rgba ) BlockATI1 block; mem_read( stream, block ); block.decodeBlock( rgba ); - } else if ( header.pf.fourcc == FOURCC_ATI2 ) { + } else if ( header.pf.fourcc == FOURCC_ATI2 || header.pf.fourcc == FOURCC_BC5U ) { BlockATI2 block; mem_read( stream, block ); block.decodeBlock( rgba ); @@ -839,7 +841,7 @@ void DirectDrawSurface::readBlock( ColorBlock * rgba ) // If normal flag set, convert to normal. if ( header.pf.flags & DDPF_NORMAL ) { - if ( header.pf.fourcc == FOURCC_ATI2 ) { + if ( header.pf.fourcc == FOURCC_ATI2 || header.pf.fourcc == FOURCC_BC5U ) { for ( int i = 0; i < 16; i++ ) { Color32 & c = rgba->color( i ); c = buildNormal( c.r, c.g ); @@ -866,6 +868,7 @@ uint DirectDrawSurface::blockSize() const case FOURCC_DXT5: case FOURCC_RXGB: case FOURCC_ATI2: + case FOURCC_BC5U: return 16; } diff --git a/src/gl/dds/DirectDrawSurface.h b/src/gl/dds/DirectDrawSurface.h index 5597594b4..fcdb58ca1 100644 --- a/src/gl/dds/DirectDrawSurface.h +++ b/src/gl/dds/DirectDrawSurface.h @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/src/gl/dds/Image.cpp b/src/gl/dds/Image.cpp index 28058389e..fa2bad0d5 100644 --- a/src/gl/dds/Image.cpp +++ b/src/gl/dds/Image.cpp @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/src/gl/dds/Image.h b/src/gl/dds/Image.h index 2bd5a5c70..584511e76 100644 --- a/src/gl/dds/Image.h +++ b/src/gl/dds/Image.h @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/src/gl/dds/PixelFormat.h b/src/gl/dds/PixelFormat.h index cb9c2ad6e..248113bb7 100644 --- a/src/gl/dds/PixelFormat.h +++ b/src/gl/dds/PixelFormat.h @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/src/gl/dds/Stream.cpp b/src/gl/dds/Stream.cpp index abafa4554..2385b26d6 100644 --- a/src/gl/dds/Stream.cpp +++ b/src/gl/dds/Stream.cpp @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/src/gl/dds/Stream.h b/src/gl/dds/Stream.h index 1e8ade6cf..42ea2bbb1 100644 --- a/src/gl/dds/Stream.h +++ b/src/gl/dds/Stream.h @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/src/gl/dds/dds_api.cpp b/src/gl/dds/dds_api.cpp index 1ff889ce0..c9cd75d0e 100644 --- a/src/gl/dds/dds_api.cpp +++ b/src/gl/dds/dds_api.cpp @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/src/gl/dds/dds_api.h b/src/gl/dds/dds_api.h index 74fbda50d..40d7e06a6 100644 --- a/src/gl/dds/dds_api.h +++ b/src/gl/dds/dds_api.h @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/src/gl/glcontroller.cpp b/src/gl/glcontroller.cpp index 01957a849..922103bb9 100644 --- a/src/gl/glcontroller.cpp +++ b/src/gl/glcontroller.cpp @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -31,25 +31,32 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "glcontroller.h" -#include "options.h" +#include "settings.h" #include "glscene.h" +//! @file glcontroller.cpp Controllable management, Interpolation management + /* - * Controllable + * IControllable */ -Controllable::Controllable( Scene * s, const QModelIndex & i ) : scene( s ), iBlock( i ) +IControllable::IControllable( Scene * s, const QModelIndex & i ) : scene( s ), iBlock( i ) { } -Controllable::~Controllable() +IControllable::~IControllable() { qDeleteAll( controllers ); } -void Controllable::clear() +QString IControllable::getName() const +{ + return name; +} + +void IControllable::clear() { name = QString(); @@ -57,17 +64,17 @@ void Controllable::clear() controllers.clear(); } -Controller * Controllable::findController( const QString & ctrltype, const QString & var1, const QString & var2 ) +Controller * IControllable::findController( const QString & ctrltype, const QString & var1, const QString & var2 ) { Q_UNUSED( var2 ); Q_UNUSED( var1 ); Controller * ctrl = nullptr; for ( Controller * c : controllers ) { if ( c->typeId() == ctrltype ) { - if ( ctrl == 0 ) { + if ( !ctrl ) { ctrl = c; } else { - ctrl = 0; + ctrl = nullptr; // TODO: eval var1 + var2 offset to determine which controller is targeted break; } @@ -77,8 +84,18 @@ Controller * Controllable::findController( const QString & ctrltype, const QStri return ctrl; } +Controller * IControllable::findController( const QModelIndex & index ) +{ + for ( Controller * c : controllers ) { + if ( c->index() == index ) + return c; + } + + return nullptr; +} + -void Controllable::update( const NifModel * nif, const QModelIndex & i ) +void IControllable::update( const NifModel * nif, const QModelIndex & i ) { if ( !iBlock.isValid() ) { clear(); @@ -123,16 +140,16 @@ void Controllable::update( const NifModel * nif, const QModelIndex & i ) } } -void Controllable::transform() +void IControllable::transform() { if ( scene->animate ) { for ( Controller * controller : controllers ) { - controller->update( scene->time ); + controller->updateTime( scene->time ); } } } -void Controllable::timeBounds( float & tmin, float & tmax ) +void IControllable::timeBounds( float & tmin, float & tmax ) { if ( controllers.isEmpty() ) return; @@ -148,7 +165,7 @@ void Controllable::timeBounds( float & tmin, float & tmax ) tmax = qMax( tmax, mx ); } -void Controllable::setSequence( const QString & seqname ) +void IControllable::setSequence( const QString & seqname ) { for ( Controller * ctrl : controllers ) { ctrl->setSequence( seqname ); @@ -199,6 +216,10 @@ bool Controller::update( const NifModel * nif, const QModelIndex & index ) active = flags & 0x08; extrapolation = (Extrapolation)( ( flags & 0x06 ) >> 1 ); + // TODO: Bit 4 (16) - Plays entire animation backwards. + // TODO: Bit 5 (32) - Generally only set when sequences are present. + // TODO: Bit 6 (64) - Always seems to be set on Skyrim NIFs, unknown function. + QModelIndex idx = nif->getBlock( nif->getLink( iBlock, "Interpolator" ) ); if ( idx.isValid() ) { @@ -312,6 +333,21 @@ bool Controller::timeIndex( float time, const NifModel * nif, const QModelIndex x = ( time - tI ) / ( tJ - tI ); + // Quadratic Bug Fix + + // Invert x + // Previously, this branch was causing x to decrement from 1.0. + // (This works fine for linear interpolation apparently) + x = 1.0 - x; + + // Swap I and J + // With x inverted, we must swap I and J or the animation will reverse. + auto tmpI = i; + i = j; + j = tmpI; + + // End Bug Fix + return true; } @@ -338,21 +374,33 @@ template bool interpolate( T & value, const QModelIndex & array, fl T v2 = nif->get( frames.child( next, 0 ), "Value" ); switch ( nif->get( array, "Interpolation" ) ) { - /* + case 2: { - float t1 = nif->get( frames.child( last, 0 ), "Forward" ); - float t2 = nif->get( frames.child( next, 0 ), "Backward" ); + // Quadratic + /* + In general, for keyframe values v1 = 0, v2 = 1 it appears that + setting v1's corresponding "Backward" value to 1 and v2's + corresponding "Forward" to 1 results in a linear interpolation. + */ - float x2 = x * x; - float x3 = x2 * x; + // Tangent 1 + float t1 = nif->get( frames.child( last, 0 ), "Backward" ); + // Tangent 2 + float t2 = nif->get( frames.child( next, 0 ), "Forward" ); + + float x2 = x * x; + float x3 = x2 * x; + + // Cubic Hermite spline + // x(t) = (2t^3 - 3t^2 + 1)P1 + (-2t^3 + 3t^2)P2 + (t^3 - 2t^2 + t)T1 + (t^3 - t^2)T2 + + value = v1 * (2.0f * x3 - 3.0f * x2 + 1.0f) + v2 * (-2.0f * x3 + 3.0f * x2) + t1 * (x3 - 2.0f * x2 + x) + t2 * (x3 - x2); - //x(t) = (2t^3 - 3t^2 + 1)*P1 + (-2t^3 + 3t^2)*P4 + (t^3 - 2t^2 + t)*R1 + (t^3 - t^2)*R4 - value = ( 2 * x3 - 3 * x2 + 1 ) * v1 + ( - 2 * x3 + 3 * x2 ) * v2 + ( x3 - 2 * x2 + x ) * t1 + ( x3 - x2 ) * t2; } return true; - */ + case 5: - + // Constant if ( x < 0.5 ) value = v1; else diff --git a/src/gl/glcontroller.h b/src/gl/glcontroller.h index fbdff9fc2..41fbb6ff3 100644 --- a/src/gl/glcontroller.h +++ b/src/gl/glcontroller.h @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -40,20 +40,15 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include -//! \file glcontroller.h Controller, Interpolator and subclasses - -class Scene; -class Node; -class Controller; -class Interpolator; +//! @file glcontroller.h Controller, Interpolator, TransformInterpolator, BSplineTransformInterpolator //! Something which can be attached to anything Controllable class Controller { + friend class Interpolator; + public: - //! Constructor Controller( const QModelIndex & index ); - //! Destructor virtual ~Controller() {} float start; @@ -69,36 +64,58 @@ class Controller bool active; - //! Set sequence name for animation groups - virtual void setSequence( const QString & seqname ); - - //! Update for specified time - virtual void update( float time ) = 0; - - //! Update for model and index - virtual bool update( const NifModel * nif, const QModelIndex & index ); + //! Find the model index of the controller + QModelIndex index() const { return iBlock; } //! Set the interpolator virtual void setInterpolator( const QModelIndex & iInterpolator ); - //! Find the model index of the controller - QModelIndex index() const { return iBlock; } + //! Set sequence name for animation groups + virtual void setSequence( const QString & seqname ); //! Find the type of the controller virtual QString typeId() const; + //! Update for model and index + virtual bool update( const NifModel * nif, const QModelIndex & index ); + + //! Update for specified time + virtual void updateTime( float time ) = 0; + //! Determine the controller time based on the specified time float ctrlTime( float time ) const; - //! Interpolate based on a value, data, an array name, the time, and the last index? - template static bool interpolate( T & value, const QModelIndex & data, const QString & arrayid, float time, int & lastindex ); - //! Interpolate based on a value, an array, the time, and the last index? + /*! Interpolate given the index of an array + * + * @param[out] value The value being interpolated + * @param[in] array The array index + * @param[in] time The scene time + * @param[out] lastIndex The last index + */ template static bool interpolate( T & value, const QModelIndex & array, float time, int & lastIndex ); - //! Unknown function - static bool timeIndex( float time, const NifModel * nif, const QModelIndex & array, int & i, int & j, float & x ); + + /*! Interpolate given an index and the array name + * + * @param[out] value The value being interpolated + * @param[in] data The index which houses the array + * @param[in] arrayid The name of the array + * @param[in] time The scene time + * @param[out] lastIndex The last index + */ + template static bool interpolate( T & value, const QModelIndex & data, const QString & arrayid, float time, int & lastindex ); + + /*! Returns the fraction of the way between two keyframes based on the scene time + * + * @param[in] inTime The scene time + * @param[in] nif The NIF + * @param[in] keysArray The Keys array in the interpolator + * @param[out] prevFrame The previous row in the Keys array + * @param[out] nextFrame The next row in the Keys array + * @param[out] fraction The current distance between the prev and next frame, as a fraction + */ + static bool timeIndex( float inTime, const NifModel * nif, const QModelIndex & keysArray, int & prevFrame, int & nextFrame, float & fraction ); protected: - friend class Interpolator; QPersistentModelIndex iBlock; QPersistentModelIndex iInterpolator; diff --git a/src/gl/glmarker.cpp b/src/gl/glmarker.cpp index 04ad46740..d0d72da05 100644 --- a/src/gl/glmarker.cpp +++ b/src/gl/glmarker.cpp @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/src/gl/glmarker.h b/src/gl/glmarker.h index ca08c3b37..d16d938fe 100644 --- a/src/gl/glmarker.h +++ b/src/gl/glmarker.h @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/src/gl/glmesh.cpp b/src/gl/glmesh.cpp index 7cc35c30f..28485a881 100644 --- a/src/gl/glmesh.cpp +++ b/src/gl/glmesh.cpp @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -32,9 +32,9 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "glmesh.h" #include "config.h" -#include "options.h" +#include "settings.h" -#include "glcontroller.h" // Inherited +#include "controllers.h" #include "glscene.h" #include "gltools.h" @@ -42,175 +42,20 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +#include -//! \file glmesh.cpp Mesh, MorphController, UVController -//! A Controller of Mesh geometry -class MorphController final : public Controller -{ - //! A representation of Mesh geometry morphs - struct MorphKey - { - QPersistentModelIndex iFrames; - QVector verts; - int index; - }; - -public: - MorphController( Mesh * mesh, const QModelIndex & index ) - : Controller( index ), target( mesh ) - { - } - - ~MorphController() - { - qDeleteAll( morph ); - } - - void update( float time ) override final - { - if ( !( target && iData.isValid() && active && morph.count() > 1 ) ) - return; - - time = ctrlTime( time ); - - if ( target->verts.count() != morph[0]->verts.count() ) - return; - - target->verts = morph[0]->verts; - - float x; - - for ( int i = 1; i < morph.count(); i++ ) { - MorphKey * key = morph[i]; - - if ( interpolate( x, key->iFrames, time, key->index ) ) { - if ( x < 0 ) - x = 0; - if ( x > 1 ) - x = 1; - - if ( x != 0 && target->verts.count() == key->verts.count() ) { - for ( int v = 0; v < target->verts.count(); v++ ) - target->verts[v] += key->verts[v] * x; - } - } - } - - target->upBounds = true; - } - - bool update( const NifModel * nif, const QModelIndex & index ) override final - { - if ( Controller::update( nif, index ) ) { - qDeleteAll( morph ); - morph.clear(); +//! @file glmesh.cpp Scene management for visible meshes such as NiTriShapes. - QModelIndex midx = nif->getIndex( iData, "Morphs" ); - for ( int r = 0; r < nif->rowCount( midx ); r++ ) { - QModelIndex iInterpolators, iInterpolatorWeights; - - if ( nif->checkVersion( 0, 0x14000005 ) ) { - iInterpolators = nif->getIndex( iBlock, "Interpolators" ); - } else if ( nif->checkVersion( 0x14010003, 0 ) ) { - iInterpolatorWeights = nif->getIndex( iBlock, "Interpolator Weights" ); - } - - QModelIndex iKey = midx.child( r, 0 ); - - MorphKey * key = new MorphKey; - key->index = 0; - - // this is ugly... - if ( iInterpolators.isValid() ) { - key->iFrames = nif->getIndex( nif->getBlock( nif->getLink( nif->getBlock( nif->getLink( iInterpolators.child( r, 0 ) ), "NiFloatInterpolator" ), "Data" ), "NiFloatData" ), "Data" ); - } else if ( iInterpolatorWeights.isValid() ) { - key->iFrames = nif->getIndex( nif->getBlock( nif->getLink( nif->getBlock( nif->getLink( iInterpolatorWeights.child( r, 0 ), "Interpolator" ), "NiFloatInterpolator" ), "Data" ), "NiFloatData" ), "Data" ); - } else { - key->iFrames = iKey; - } - - key->verts = nif->getArray( nif->getIndex( iKey, "Vectors" ) ); - - morph.append( key ); - } - - return true; - } - - return false; - } - -protected: - QPointer target; - QVector morph; -}; - -//! A Controller of UV data in a Mesh -class UVController final : public Controller +Shape::Shape( Scene * s, const QModelIndex & b ) : Node( s, b ) { -public: - UVController( Mesh * mesh, const QModelIndex & index ) - : Controller( index ), target( mesh ) - { - } - - ~UVController() - { - } - - void update( float time ) override final - { - const NifModel * nif = static_cast( iData.model() ); - QModelIndex uvGroups = nif->getIndex( iData, "UV Groups" ); - - // U trans, V trans, U scale, V scale - // see NiUVData compound in nif.xml - float val[4] = { 0.0, 0.0, 1.0, 1.0 }; - - if ( uvGroups.isValid() ) { - for ( int i = 0; i < 4 && i < nif->rowCount( uvGroups ); i++ ) { - interpolate( val[i], uvGroups.child( i, 0 ), ctrlTime( time ), luv ); - } - - // adjust coords; verified in SceneImmerse - for ( int i = 0; i < target->coords[0].size(); i++ ) { - // operating on pointers makes this too complicated, so we don't - Vector2 current = target->coords[0][i]; - // scaling/tiling applied before translation - // Note that scaling is relative to center! - current += Vector2( -0.5, -0.5 ); - current = Vector2( current[0] * val[2], current[1] * val[3] ); - current += Vector2( -val[0], val[1] ); - current += Vector2( 0.5, 0.5 ); - target->coords[0][i] = current; - } - } - - target->upData = true; - } - - bool update( const NifModel * nif, const QModelIndex & index ) override final - { - if ( Controller::update( nif, index ) ) { - // do stuff here - return true; - } - - return false; - } - -protected: - QPointer target; - - int luv; -}; - + shapeNumber = s->shapes.count(); +} -/* - * Mesh - */ +Mesh::Mesh( Scene * s, const QModelIndex & b ) : Shape( s, b ) +{ +} bool Mesh::isBSLODPresent = false; @@ -219,6 +64,9 @@ void Mesh::clear() Node::clear(); iData = iSkin = iSkinData = iSkinPart = iTangentData = QModelIndex(); + bssp = nullptr; + bslsp = nullptr; + bsesp = nullptr; verts.clear(); norms.clear(); @@ -235,11 +83,97 @@ void Mesh::clear() transVerts.clear(); transNorms.clear(); transColors.clear(); + transColorsNoAlpha.clear(); transTangents.clear(); transBitangents.clear(); isBSLODPresent = false; - double_sided = false; + isDoubleSided = false; +} + +void Shape::setController( const NifModel * nif, const QModelIndex & iController ) +{ + if ( nif->itemName( iController ) == "NiGeomMorpherController" ) { + Controller * ctrl = new MorphController( this, iController ); + ctrl->update( nif, iController ); + controllers.append( ctrl ); + } else if ( nif->itemName( iController ) == "NiUVController" ) { + Controller * ctrl = new UVController( this, iController ); + ctrl->update( nif, iController ); + controllers.append( ctrl ); + } else { + Node::setController( nif, iController ); + } +} + +void Shape::updateShaderProperties( const NifModel * nif ) +{ + QVector props = nif->getLinkArray( iBlock, "BS Properties" ); + if ( !props.count() ) + return; + + QModelIndex iLSP = nif->getBlock( props[0], "BSLightingShaderProperty" ); + QModelIndex iESP = nif->getBlock( props[0], "BSEffectShaderProperty" ); + + if ( iLSP.isValid() ) { + if ( !bslsp ) + bslsp = properties.get(); + + if ( !bslsp ) + return; + + bssp = bslsp; + + bslsp->updateParams( nif, iLSP ); + + // Mesh alpha override + translucent = (bslsp->getAlpha() < 1.0); + translucent |= bslsp->hasRefraction; + + } else if ( iESP.isValid() ) { + if ( !bsesp ) + bsesp = properties.get(); + + if ( !bsesp ) + return; + + bssp = bsesp; + + bsesp->updateParams( nif, iESP ); + + // Mesh alpha override + translucent = (bsesp->getAlpha() < 1.0) && !findProperty(); + } + + // Draw mesh second + drawSecond |= translucent; + + if ( !bssp ) + return; + + depthTest = bssp->getFlags1() & ShaderFlags::SLSF1_ZBuffer_Test; + depthWrite = bssp->getFlags2() & ShaderFlags::SLSF2_ZBuffer_Write; + isDoubleSided = bssp->getFlags2() & ShaderFlags::SLSF2_Double_Sided; + isVertexAlphaAnimation = bssp->getFlags2() & ShaderFlags::SLSF2_Tree_Anim; +} + +void Shape::boneSphere( const NifModel * nif, const QModelIndex & index ) const +{ + Node * root = findParent( 0 ); + Node * bone = root ? root->findChild( bones.value( index.row() ) ) : 0; + if ( !bone ) + return; + + Transform boneT = Transform( nif, index ); + Transform t = (scene->options & Scene::DoSkinning) ? viewTrans() : Transform(); + t = t * skeletonTrans * bone->localTrans( 0 ) * boneT; + + auto bSphere = BoundSphere( nif, nif->getIndex( index, "Bounding Sphere" ) ); + if ( bSphere.radius > 0.0 ) { + glColor4f( 1, 1, 1, 0.33 ); + auto pos = boneT.rotation.inverted() * (bSphere.center - boneT.translation); + drawSphereSimple( t * pos, bSphere.radius, 36 ); + } } void Mesh::update( const NifModel * nif, const QModelIndex & index ) @@ -253,16 +187,22 @@ void Mesh::update( const NifModel * nif, const QModelIndex & index ) if ( nif->isNiBlock( iBlock, "BSLODTriShape" ) ) { isBSLODPresent = true; - } emit nif->lodSliderChanged( isBSLODPresent ); } - upData |= ( iData == index ) || ( iTangentData == index ); - upSkin |= ( iSkin == index ); - upSkin |= ( iSkinData == index ); - upSkin |= ( iSkinPart == index ); + updateData |= ( iData == index ) || ( iTangentData == index ); + updateSkin |= ( iSkin == index ); + updateSkin |= ( iSkinData == index ); + updateSkin |= ( iSkinPart == index ); + + isVertexAlphaAnimation = false; + isDoubleSided = false; + + // Update shader from this mesh's shader property + if ( nif->checkVersion( 0x14020007, 0 ) && nif->inherits( iBlock, "NiTriBasedGeom" ) ) + updateShaderProperties( nif ); if ( iBlock == index ) { // NiMesh presents a problem because we are almost guaranteed to have multiple "data" blocks @@ -270,14 +210,14 @@ void Mesh::update( const NifModel * nif, const QModelIndex & index ) #ifndef QT_NO_DEBUG if ( nif->checkVersion( 0x14050000, 0 ) && nif->inherits( iBlock, "NiMesh" ) ) { - qWarning() << nif->get( iBlock, "Num Submeshes" ) << " submeshes"; + qDebug() << nif->get( iBlock, "Num Submeshes" ) << " submeshes"; iData = nif->getIndex( iBlock, "Datas" ); if ( iData.isValid() ) { - qWarning() << "Got " << nif->rowCount( iData ) << " rows of data"; - upData = true; + qDebug() << "Got " << nif->rowCount( iData ) << " rows of data"; + updateData = true; } else { - qWarning() << "Did not find data in NiMesh ???"; + qDebug() << "Did not find data in NiMesh ???"; } return; @@ -287,52 +227,51 @@ void Mesh::update( const NifModel * nif, const QModelIndex & index ) for ( const auto link : nif->getChildLinks( id() ) ) { QModelIndex iChild = nif->getBlock( link ); - if ( !iChild.isValid() ) continue; - QString name = nif->itemName( iChild ); - if ( nif->inherits( iChild, "NiTriShapeData" ) || nif->inherits( iChild, "NiTriStripsData" ) ) { if ( !iData.isValid() ) { iData = iChild; - upData = true; + updateData = true; } else if ( iData != iChild ) { - qWarning() << "shape block" << id() << "has multiple data blocks"; + Message::append( tr( "Warnings were generated while updating meshes." ), + tr( "Block %1 has multiple data blocks" ).arg( id() ) + ); } } else if ( nif->inherits( iChild, "NiSkinInstance" ) ) { if ( !iSkin.isValid() ) { iSkin = iChild; - upSkin = true; + updateSkin = true; } else if ( iSkin != iChild ) { - qWarning() << "shape block" << id() << "has multiple skin instances"; + Message::append( tr( "Warnings were generated while updating meshes." ), + tr( "Block %1 has multiple skin instances" ).arg( id() ) + ); } } } } - upBounds |= upData; + updateBounds |= updateData; } -void Mesh::setController( const NifModel * nif, const QModelIndex & iController ) +QModelIndex Mesh::vertexAt( int idx ) const { - if ( nif->itemName( iController ) == "NiGeomMorpherController" ) { - Controller * ctrl = new MorphController( this, iController ); - ctrl->update( nif, iController ); - controllers.append( ctrl ); - } else if ( nif->itemName( iController ) == "NiUVController" ) { - Controller * ctrl = new UVController( this, iController ); - ctrl->update( nif, iController ); - controllers.append( ctrl ); - } else { - Node::setController( nif, iController ); - } + auto nif = static_cast(iBlock.model()); + if ( !nif ) + return QModelIndex(); + + auto iVertexData = nif->getIndex( iData, "Vertices" ); + auto iVertex = iVertexData.child( idx, 0 ); + + return iVertex; } + bool Mesh::isHidden() const { return ( Node::isHidden() - || ( !Options::drawHidden() && Options::onlyTextured() + || ( !(scene->options & Scene::ShowHidden) /*&& Options::onlyTextured()*/ && !properties.get() && !properties.get() ) @@ -353,42 +292,42 @@ void Mesh::transform() return; } - if ( upData ) { - upData = false; + if ( updateData ) { + updateData = false; // update for NiMesh if ( nif->checkVersion( 0x14050000, 0 ) && nif->inherits( iBlock, "NiMesh" ) ) { #ifndef QT_NO_DEBUG // do stuff - qWarning() << "Entering NiMesh decoding..."; + qDebug() << "Entering NiMesh decoding..."; // mesh primitive type QString meshPrimitiveType = NifValue::enumOptionName( "MeshPrimitiveType", nif->get( iData, "Primitive Type" ) ); - qWarning() << "Mesh uses" << meshPrimitiveType; + qDebug() << "Mesh uses" << meshPrimitiveType; for ( int i = 0; i < nif->rowCount( iData ); i++ ) { // each data reference is to a single data stream quint32 stream = nif->getLink( iData.child( i, 0 ), "Stream" ); - qWarning() << "Data stream: " << stream; + qDebug() << "Data stream: " << stream; // can have multiple submeshes, unsure of exact meaning ushort numSubmeshes = nif->get( iData.child( i, 0 ), "Num Submeshes" ); - qWarning() << "Submeshes: " << numSubmeshes; + qDebug() << "Submeshes: " << numSubmeshes; QPersistentModelIndex submeshMap = nif->getIndex( iData.child( i, 0 ), "Submesh To Region Map" ); for ( int j = 0; j < numSubmeshes; j++ ) { - qWarning() << "Submesh" << j << "maps to region" << nif->get( submeshMap.child( j, 0 ) ); + qDebug() << "Submesh" << j << "maps to region" << nif->get( submeshMap.child( j, 0 ) ); } // each stream can have multiple components, and each has a starting index QMap componentIndexMap; int numComponents = nif->get( iData.child( i, 0 ), "Num Components" ); - qWarning() << "Components: " << numComponents; + qDebug() << "Components: " << numComponents; // semantics determine the usage QPersistentModelIndex componentSemantics = nif->getIndex( iData.child( i, 0 ), "Component Semantics" ); for ( int j = 0; j < numComponents; j++ ) { QString name = nif->get( componentSemantics.child( j, 0 ), "Name" ); uint index = nif->get( componentSemantics.child( j, 0 ), "Index" ); - qWarning() << "Component" << name << "at position" << index << "of component" << j << "in stream" << stream; + qDebug() << "Component" << name << "at position" << index << "of component" << j << "in stream" << stream; componentIndexMap.insert( j, QString( "%1 %2" ).arg( name ).arg( index ) ); } @@ -407,19 +346,19 @@ void Mesh::transform() quint32 totalIndices = 0; if ( regions.isValid() ) { - qWarning() << numRegions << " regions in this stream"; + qDebug() << numRegions << " regions in this stream"; for ( quint32 j = 0; j < numRegions; j++ ) { - qWarning() << "Start index: " << nif->get( regions.child( j, 0 ), "Start Index" ); - qWarning() << "Num indices: " << nif->get( regions.child( j, 0 ), "Num Indices" ); + qDebug() << "Start index: " << nif->get( regions.child( j, 0 ), "Start Index" ); + qDebug() << "Num indices: " << nif->get( regions.child( j, 0 ), "Num Indices" ); totalIndices += nif->get( regions.child( j, 0 ), "Num Indices" ); } - qWarning() << totalIndices << "total indices in" << numRegions << "regions"; + qDebug() << totalIndices << "total indices in" << numRegions << "regions"; } uint numStreamComponents = nif->get( dataStream, "Num Components" ); - qWarning() << "Stream has" << numStreamComponents << "components"; + qDebug() << "Stream has" << numStreamComponents << "components"; QPersistentModelIndex streamComponents = nif->getIndex( dataStream, "Component Formats" ); // stream components are interleaved, so we need to know their type before we read them QList typeList; @@ -427,21 +366,21 @@ void Mesh::transform() for ( uint j = 0; j < numStreamComponents; j++ ) { uint compFormat = nif->get( streamComponents.child( j, 0 ) ); QString compName = NifValue::enumOptionName( "ComponentFormat", compFormat ); - qWarning() << "Component format is" << compName; - qWarning() << "Stored as a" << compName.split( "_" )[1]; + qDebug() << "Component format is" << compName; + qDebug() << "Stored as a" << compName.split( "_" )[1]; typeList.append( compFormat - 1 ); // this can probably wait until we're reading the stream values QString compNameIndex = componentIndexMap.value( j ); QString compType = compNameIndex.split( " " )[0]; uint startIndex = compNameIndex.split( " " )[1].toUInt(); - qWarning() << "Component" << j << "contains" << compType << "starting at index" << startIndex; + qDebug() << "Component" << j << "contains" << compType << "starting at index" << startIndex; // try and sort out texcoords here... if ( compType == "TEXCOORD" ) { QVector tempCoords; coords.append( tempCoords ); - qWarning() << "Assigning coordinate set" << startIndex; + qDebug() << "Assigning coordinate set" << startIndex; } } @@ -455,7 +394,7 @@ void Mesh::transform() for ( uint k = 0; k < numStreamComponents; k++ ) { int typeLength = ( ( typeList[k] & 0x000F0000 ) >> 0x10 ); int typeSize = ( ( typeList[k] & 0x00000F00 ) >> 0x08 ); - qWarning() << "Reading" << typeLength << "values" << typeSize << "bytes"; + qDebug() << "Reading" << typeLength << "values" << typeSize << "bytes"; NifIStream tempInput( new NifModel, &streamBuffer ); QList values; @@ -480,11 +419,11 @@ void Mesh::transform() for ( int l = 0; l < typeLength; l++ ) { tempInput.read( tempValue ); values.append( tempValue ); - qWarning() << tempValue.toString(); + qDebug() << tempValue.toString(); } QString compType = componentIndexMap.value( k ).split( " " )[0]; - qWarning() << "Will store this value in" << compType; + qDebug() << "Will store this value in" << compType; // the mess begins... if ( NifValue::enumOptionName( "ComponentFormat", (typeList[k] + 1 ) ) == "F_FLOAT32_3" ) { @@ -500,7 +439,7 @@ void Mesh::transform() } else if ( compType == "TEXCOORD" ) { Vector2 tempVect2( values[0].toFloat(), values[1].toFloat() ); quint32 coordSet = componentIndexMap.value( k ).split( " " )[1].toUInt(); - qWarning() << "Need to append" << tempVect2 << "to texcoords" << coordSet; + qDebug() << "Need to append" << tempVect2 << "to texcoords" << coordSet; QVector currentSet = coords[coordSet]; currentSet.append( tempVect2 ); coords[coordSet] = currentSet; @@ -512,7 +451,7 @@ void Mesh::transform() if ( meshPrimitiveType == "MESH_PRIMITIVE_TRIANGLES" ) { for ( int k = 0; k < indices.size(); ) { Triangle tempTri( indices[k], indices[k + 1], indices[k + 2] ); - qWarning() << "Inserting triangle" << tempTri; + qDebug() << "Inserting triangle" << tempTri; triangles.append( tempTri ); k = k + 3; } @@ -521,62 +460,19 @@ void Mesh::transform() #endif } else { - // Handle some vertex color animation - the static part. - // The elegant way requires TODO: property system (glproperty.h, - // glproperty.cpp, renderer.cpp, etc.) complete refactoring. - // Refer to "nif.xml" for "SF_Vertex_Animation", "PROP_LightingShaderProperty" and "FLAG_ShaderFlags" -#define SF_Vertex_Animation 29 -#define SF_Double_Sided 4 -#define PROP_LightingShaderProperty "BSLightingShaderProperty" -#define PROP_BSEffectShaderProperty "BSEffectShaderProperty" -#define FLAG_ShaderFlags "Shader Flags 2" -#define FLAG_EffectShaderFlags1 "Effect Shader Flags 1" - bool alphaisanim = false; - double_sided = false; - double_sided_es = false; - - if ( nif->checkVersion( 0x14020007, 0 ) && nif->inherits( iBlock, "NiTriBasedGeom" ) ) { - QVector props = - nif->getLinkArray( iBlock, "Properties" ) - + nif->getLinkArray( iBlock, "BS Properties" ); - - for ( int i = 0; i < props.count(); i++ ) { - QModelIndex iProp = nif->getBlock( props[i], PROP_LightingShaderProperty ); - - if ( iProp.isValid() ) { - // TODO: check that it exists at all - unsigned int sf2 = nif->get( iProp, FLAG_ShaderFlags ); - // using nifvalue.cpp line ~211 - double_sided = sf2 & (1 << SF_Double_Sided); - - if ( sf2 & (1 << SF_Vertex_Animation) ) { - alphaisanim = true; - break; - } - } else { - // enable double_sided for BSEffectShaderProperty - iProp = nif->getBlock( props[i], PROP_BSEffectShaderProperty ); - - if ( iProp.isValid() ) { - unsigned int sf1 = nif->get( iProp, FLAG_EffectShaderFlags1 ); - double_sided_es = sf1 & (1 << SF_Double_Sided); - } - } - } - } - -#undef FLAG_EffectShaderFlags1 -#undef PROP_BSEffectShaderProperty -#undef PROP_LightingShaderProperty -#undef FLAG_ShaderFlags -#undef SF_Double_Sided -#undef SF_Vertex_Animation verts = nif->getArray( iData, "Vertices" ); norms = nif->getArray( iData, "Normals" ); colors = nif->getArray( iData, "Vertex Colors" ); - if ( alphaisanim ) { + // Detect if "Has Vertex Colors" is set to Yes in NiTriShape + // Used to compare against SLSF2_Vertex_Colors + hasVertexColors = true; + if ( colors.length() == 0 ) { + hasVertexColors = false; + } + + if ( isVertexAlphaAnimation ) { for ( int i = 0; i < colors.count(); i++ ) colors[i].setRGBA( colors[i].red(), colors[i].green(), colors[i].blue(), 1 ); } @@ -635,8 +531,9 @@ void Mesh::transform() if ( inv_cnt > 0 ) { int block_idx = nif->getBlockNumber( nif->getIndex( iData, "Triangles" ) ); - qWarning() << "Error: " << inv_cnt << " invalid index(es) in block #" - << block_idx << " NiTriShapeData.Triangles"; + Message::append( tr( "Warnings were generated while rendering mesh." ), + tr( "Block %1: %2 invalid indices in NiTriShapeData.Triangles" ).arg( block_idx ).arg( inv_cnt ) + ); } tristrips.clear(); @@ -648,7 +545,11 @@ void Mesh::transform() for ( int r = 0; r < nif->rowCount( points ); r++ ) tristrips.append( nif->getArray( points.child( r, 0 ) ) ); } else { - qWarning() << nif->itemName( iData ) << "(" << nif->getBlockNumber( iData ) << ") 'points' array not found"; + Message::append( tr( "Warnings were generated while rendering mesh." ), + tr( "Block %1: Invalid 'Points' array in %2" ) + .arg( nif->getBlockNumber( iData ) ) + .arg( nif->itemName( iData ) ) + ); } triangles.clear(); @@ -684,15 +585,15 @@ void Mesh::transform() } } - if ( upSkin ) { - upSkin = false; + if ( updateSkin ) { + updateSkin = false; weights.clear(); partitions.clear(); iSkinData = nif->getBlock( nif->getLink( iSkin, "Data" ), "NiSkinData" ); - skelRoot = nif->getLink( iSkin, "Skeleton Root" ); - skelTrans = Transform( nif, iSkinData ); + skeletonRoot = nif->getLink( iSkin, "Skeleton Root" ); + skeletonTrans = Transform( nif, iSkinData ); bones = nif->getLinkArray( iSkin, "Bones" ); @@ -726,14 +627,14 @@ void Mesh::transform() void Mesh::transformShapes() { - if ( isHidden() || !Options::drawMeshes() ) + if ( isHidden() ) return; Node::transformShapes(); transformRigid = true; - if ( weights.count() ) { + if ( weights.count() && (scene->options & Scene::DoSkinning) ) { transformRigid = false; transVerts.resize( verts.count() ); @@ -745,7 +646,7 @@ void Mesh::transformShapes() transBitangents.resize( bitangents.count() ); transBitangents.fill( Vector3() ); - Node * root = findParent( skelRoot ); + Node * root = findParent( skeletonRoot ); if ( partitions.count() ) { foreach ( const SkinPartition part, partitions ) { @@ -753,10 +654,10 @@ void Mesh::transformShapes() for ( int t = 0; t < boneTrans.count(); t++ ) { Node * bone = root ? root->findChild( bones.value( part.boneMap[t] ) ) : 0; - boneTrans[ t ] = viewTrans() * skelTrans; + boneTrans[ t ] = scene->view; if ( bone ) - boneTrans[ t ] = boneTrans[ t ] * bone->localTransFrom( skelRoot ) * weights.value( part.boneMap[t] ).trans; + boneTrans[ t ] = boneTrans[ t ] * bone->localTrans( skeletonRoot ) * weights.value( part.boneMap[t] ).trans; //if ( bone ) boneTrans[ t ] = bone->viewTrans() * weights.value( part.boneMap[t] ).trans; } @@ -790,11 +691,11 @@ void Mesh::transformShapes() } else { int x = 0; foreach ( const BoneWeights bw, weights ) { - Transform trans = viewTrans() * skelTrans; + Transform trans = viewTrans() * skeletonTrans; Node * bone = root ? root->findChild( bw.bone ) : 0; if ( bone ) - trans = trans * bone->localTransFrom( skelRoot ) * bw.trans; + trans = trans * bone->localTrans( skeletonRoot ) * bw.trans; if ( bone ) weights[x++].tcenter = bone->viewTrans() * bw.center; @@ -827,9 +728,9 @@ void Mesh::transformShapes() for ( int t = 0; t < transBitangents.count(); t++ ) transBitangents[t].normalize(); - bndSphere = BoundSphere( transVerts ); - bndSphere.applyInv( viewTrans() ); - upBounds = false; + boundSphere = BoundSphere( transVerts ); + boundSphere.applyInv( viewTrans() ); + updateBounds = false; } else { transVerts = verts; transNorms = norms; @@ -886,7 +787,6 @@ void Mesh::transformShapes() //} MaterialProperty * matprop = findProperty(); - if ( matprop && matprop->alphaValue() != 1.0 ) { float a = matprop->alphaValue(); transColors.resize( colors.count() ); @@ -896,39 +796,81 @@ void Mesh::transformShapes() } else { transColors = colors; } + + //if ( !bslsp ) + // bslsp = properties.get(); + + if ( bslsp ) { + transColorsNoAlpha.resize( colors.count() ); + if ( !(bslsp->getFlags1() & ShaderFlags::SLSF1_Vertex_Alpha) ) { + for ( int c = 0; c < colors.count(); c++ ) + transColorsNoAlpha[c] = Color4( transColors[c].red(), transColors[c].green(), transColors[c].blue(), 1.0f ); + } else { + transColorsNoAlpha.clear(); + } + } + + //if ( !bsesp ) + // bsesp = properties.get(); + + //if ( bsesp ) { + // transColorsNoAlpha.resize( colors.count() ); + // if ( !(bsesp->getFlags1() & ShaderFlags::SLSF1_Vertex_Alpha) ) { + // //qDebug() << "No VA" << name; + // for ( int c = 0; c < colors.count(); c++ ) + // transColorsNoAlpha[c] = Color4( transColors[c].red(), transColors[c].green(), transColors[c].blue(), 1.0f ); + // } else { + // transColorsNoAlpha.clear(); + // } + //} } BoundSphere Mesh::bounds() const { - if ( upBounds ) { - upBounds = false; - bndSphere = BoundSphere( verts ); + if ( updateBounds ) { + updateBounds = false; + boundSphere = BoundSphere( verts ); } - return worldTrans() * bndSphere; + return worldTrans() * boundSphere; } -void Mesh::drawShapes( NodeList * draw2nd ) +void Mesh::drawShapes( NodeList * secondPass, bool presort ) { - if ( isHidden() || !Options::drawMeshes() ) + if ( isHidden() ) + return; + + // TODO: Only run this if BSXFlags has "EditorMarkers present" flag + if ( !(scene->options & Scene::ShowMarkers) && name.startsWith( "EditorMarker" ) ) return; auto nif = static_cast(iBlock.model()); if ( Node::SELECTING ) { - int s_nodeId = ID2COLORKEY( nodeId ); - glColor4ubv( (GLubyte *)&s_nodeId ); + if ( scene->selMode & Scene::SelObject ) { + int s_nodeId = ID2COLORKEY( nodeId ); + glColor4ubv( (GLubyte *)&s_nodeId ); + } else { + glColor4f( 0, 0, 0, 1 ); + } } - // draw transparent meshes during second run + // BSOrderedNode + presorted |= presort; + + // Draw translucent meshes in second pass AlphaProperty * aprop = findProperty(); - if ( aprop && aprop->blend() && draw2nd ) { - draw2nd->add( this ); + drawSecond |= (aprop && aprop->blend()); + + if ( secondPass && drawSecond ) { + secondPass->add( this ); return; } + // TODO: Option to hide Refraction and other post effects + // rigid mesh? then pass the transformation on to the gl layer if ( transformRigid ) { @@ -936,6 +878,14 @@ void Mesh::drawShapes( NodeList * draw2nd ) glMultMatrix( viewTrans() ); } + //if ( !Node::SELECTING ) { + // qDebug() << viewTrans().translation; + //qDebug() << Vector3( nif->get( iBlock, "Translation" ) ); + //} + + // Debug axes + //drawAxes(Vector3(), 35.0); + // setup array pointers // Render polygon fill slightly behind alpha transparency and wireframe @@ -951,21 +901,31 @@ void Mesh::drawShapes( NodeList * draw2nd ) glNormalPointer( GL_FLOAT, 0, transNorms.data() ); } - if ( transColors.count() ) { + // Do VCs if legacy or if either bslsp or bsesp is set + bool doVCs = (!bssp) || (bssp && (bssp->getFlags2() & ShaderFlags::SLSF2_Vertex_Colors)); + + if ( transColors.count() + && ( scene->options & Scene::DoVertexColors ) + && doVCs ) + { glEnableClientState( GL_COLOR_ARRAY ); - glColorPointer( 4, GL_FLOAT, 0, transColors.data() ); + glColorPointer( 4, GL_FLOAT, 0, (transColorsNoAlpha.count()) ? transColorsNoAlpha.data() : transColors.data() ); } else { - glColor( Color3( 1.0f, 0.2f, 1.0f ) ); + if ( !hasVertexColors && (bslsp && bslsp->hasVertexColors) ) { + // Correctly blacken the mesh if SLSF2_Vertex_Colors is still on + // yet "Has Vertex Colors" is not. + glColor( Color3( 0.0f, 0.0f, 0.0f ) ); + } else { + glColor( Color3( 1.0f, 1.0f, 1.0f ) ); + } } } + // TODO: Hotspot. See about optimizing this. if ( !Node::SELECTING ) shader = scene->renderer->setupProgram( this, shader ); - if ( double_sided || double_sided_es ) { - if ( double_sided_es ) // TODO: reintroduce sorting if need be - glDepthMask( GL_FALSE ); - + if ( isDoubleSided ) { glDisable( GL_CULL_FACE ); } @@ -985,23 +945,20 @@ void Mesh::drawShapes( NodeList * draw2nd ) auto lod1tris = sortedTriangles.mid( lod0, lod1 ); auto lod2tris = sortedTriangles.mid( lod0 + lod1, lod2 ); - QSettings cfg; - int lodLevel = cfg.value( "GLView/LOD Level", 2 ).toInt(); - - // render level 0 (always visible) - if ( lod0tris.count() ) - glDrawElements( GL_TRIANGLES, lod0tris.count() * 3, GL_UNSIGNED_SHORT, lod0tris.data() ); - - if ( lodLevel > 0 ) { - // render level 1 + // If Level2, render all + // If Level1, also render Level0 + switch ( scene->lodLevel ) { + case Scene::Level2: + if ( lod2tris.count() ) + glDrawElements( GL_TRIANGLES, lod2tris.count() * 3, GL_UNSIGNED_SHORT, lod2tris.data() ); + case Scene::Level1: if ( lod1tris.count() ) glDrawElements( GL_TRIANGLES, lod1tris.count() * 3, GL_UNSIGNED_SHORT, lod1tris.data() ); - - if ( lodLevel > 1 ) { - // render level 2 - if ( lod2tris.count() ) - glDrawElements( GL_TRIANGLES, lod2tris.count() * 3, GL_UNSIGNED_SHORT, lod2tris.data() ); - } + case Scene::Level0: + default: + if ( lod0tris.count() ) + glDrawElements( GL_TRIANGLES, lod0tris.count() * 3, GL_UNSIGNED_SHORT, lod0tris.data() ); + break; } } @@ -1009,11 +966,8 @@ void Mesh::drawShapes( NodeList * draw2nd ) for ( int s = 0; s < tristrips.count(); s++ ) glDrawElements( GL_TRIANGLE_STRIP, tristrips[s].count(), GL_UNSIGNED_SHORT, tristrips[s].data() ); - if ( double_sided || double_sided_es ) { + if ( isDoubleSided ) { glEnable( GL_CULL_FACE ); - - if ( double_sided_es ) - glDepthMask( GL_TRUE ); } if ( !Node::SELECTING ) @@ -1025,24 +979,63 @@ void Mesh::drawShapes( NodeList * draw2nd ) glDisable( GL_POLYGON_OFFSET_FILL ); + glPointSize( 8.5 ); + if ( scene->selMode & Scene::SelVertex ) { + drawVerts(); + } + if ( transformRigid ) glPopMatrix(); } +void Mesh::drawVerts() const +{ + glDisable( GL_LIGHTING ); + glNormalColor(); + + glBegin( GL_POINTS ); + + for ( int i = 0; i < transVerts.count(); i++ ) { + if ( Node::SELECTING ) { + int id = ID2COLORKEY( (shapeNumber << 16) + i ); + glColor4ubv( (GLubyte *)&id ); + } + glVertex( transVerts.value( i ) ); + } + + // Highlight selected vertex + if ( !Node::SELECTING && iData == scene->currentBlock ) { + auto idx = scene->currentIndex; + if ( idx.data( Qt::DisplayRole ).toString() == "Vertices" ) { + glHighlightColor(); + glVertex( transVerts.value( idx.row() ) ); + } + } + + glEnd(); +} + void Mesh::drawSelection() const { - Node::drawSelection(); + if ( scene->options & Scene::ShowNodes ) + Node::drawSelection(); + + if ( isHidden() || !(scene->selMode & Scene::SelObject) ) + return; - if ( isHidden() || !Options::drawMeshes() ) + auto idx = scene->currentIndex; + auto blk = scene->currentBlock; + + auto nif = static_cast(idx.model()); + if ( !nif ) return; - if ( scene->currentBlock != iBlock && scene->currentBlock != iData && scene->currentBlock != iSkinPart - && ( !iTangentData.isValid() || scene->currentBlock != iTangentData ) ) + if ( blk != iBlock && blk != iData && blk != iSkinPart && blk != iSkinData + && ( !iTangentData.isValid() || blk != iTangentData ) ) { return; } - if ( transformRigid ) { glPushMatrix(); glMultMatrix( viewTrans() ); @@ -1066,27 +1059,26 @@ void Mesh::drawSelection() const QString n; int i = -1; - if ( scene->currentBlock == iBlock || scene->currentIndex == iData ) { + if ( blk == iBlock || idx == iData ) { n = "Faces"; - } else if ( scene->currentBlock == iData || scene->currentBlock == iSkinPart ) { - n = scene->currentIndex.data( NifSkopeDisplayRole ).toString(); - - QModelIndex iParent = scene->currentIndex.parent(); + } else if ( blk == iData || blk == iSkinPart ) { + n = idx.data( NifSkopeDisplayRole ).toString(); + QModelIndex iParent = idx.parent(); if ( iParent.isValid() && iParent != iData ) { n = iParent.data( NifSkopeDisplayRole ).toString(); - i = scene->currentIndex.row(); + i = idx.row(); } - } else if ( scene->currentBlock == iTangentData ) { + } else if ( blk == iTangentData ) { n = "TSpace"; + } else { + n = idx.data( NifSkopeDisplayRole ).toString(); } glDepthFunc( GL_LEQUAL ); glNormalColor(); glPolygonMode( GL_FRONT_AND_BACK, GL_POINT ); - glEnable( GL_POINT_SMOOTH ); - glHint( GL_POINT_SMOOTH_HINT, GL_NICEST ); if ( n == "Vertices" || n == "Normals" || n == "Vertex Colors" || n == "UV Sets" || n == "Tangents" || n == "Bitangents" ) @@ -1130,12 +1122,12 @@ void Mesh::drawSelection() const glBegin( GL_POINTS ); QModelIndex iPoints = points.child( i, 0 ); - if ( nif->isArray( scene->currentIndex ) ) { + if ( nif->isArray( idx ) ) { for ( int j = 0; j < nif->rowCount( iPoints ); j++ ) { glVertex( transVerts.value( nif->get( iPoints.child( j, 0 ) ) ) ); } } else { - iPoints = scene->currentIndex.parent(); + iPoints = idx.parent(); glVertex( transVerts.value( nif->get( iPoints.child( i, 0 ) ) ) ); } @@ -1144,8 +1136,10 @@ void Mesh::drawSelection() const } glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); - glEnable( GL_LINE_SMOOTH ); - glHint( GL_LINE_SMOOTH_HINT, GL_NICEST ); + + // TODO: Reenable as an alternative to MSAA when MSAA is not supported + //glEnable( GL_LINE_SMOOTH ); + //glHint( GL_LINE_SMOOTH_HINT, GL_NICEST ); if ( n == "Normals" || n == "TSpace" ) { float normalScale = bounds().radius / 20; @@ -1359,6 +1353,15 @@ void Mesh::drawSelection() const } } + if ( n == "Bone List" ) { + if ( nif->isArray( idx ) ) { + for ( int i = 0; i < nif->rowCount( idx ); i++ ) + boneSphere( nif, idx.child( i, 0 ) ); + } else { + boneSphere( nif, idx ); + } + } + if ( transformRigid ) glPopMatrix(); } diff --git a/src/gl/glmesh.h b/src/gl/glmesh.h index a356a8f0e..02893129f 100644 --- a/src/gl/glmesh.h +++ b/src/gl/glmesh.h @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -41,103 +41,159 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include -//! \file glmesh.h Mesh class +//! @file glmesh.h Mesh -//! A mesh -class Mesh : public Node +class Shape : public Node { -public: - Mesh( Scene * s, const QModelIndex & b ) : Node( s, b ) { double_sided = false; double_sided_es = false; } - ~Mesh() { clear(); } + friend class MorphController; + friend class UVController; + friend class Renderer; - void clear() override; - void update( const NifModel * nif, const QModelIndex & ) override; - void transform() override; +public: + Shape( Scene * s, const QModelIndex & b ); + ~Shape() { clear(); } - void transformShapes() override; + virtual void drawVerts() const {}; + virtual QModelIndex vertexAt( int ) const { return QModelIndex(); }; - void drawShapes( NodeList * draw2nd = nullptr ) override; - void drawSelection() const override; + int shapeNumber; - bool isHidden() const override; +protected: + //! Sets the Controller + void setController( const NifModel * nif, const QModelIndex & controller ) override; - //! The bounds of the mesh? - BoundSphere bounds() const override; + void updateShaderProperties( const NifModel * nif ); - QString textStats() const override; + void boneSphere( const NifModel * nif, const QModelIndex & index ) const; -protected: - void setController( const NifModel * nif, const QModelIndex & controller ) override; + int nifVersion = 0; //! Shape data QPersistentModelIndex iData; + //! Does the data need updating? + bool updateData = false; + //! Skin instance QPersistentModelIndex iSkin; //! Skin data QPersistentModelIndex iSkinData; //! Skin partition QPersistentModelIndex iSkinPart; - //! Tangent data - QPersistentModelIndex iTangentData; - //! Unsure - does the data need updating? - bool upData; - //! Unsure - does teh skin data need updating? - bool upSkin; //! Vertices QVector verts; //! Normals QVector norms; //! Vertex colors - QVector colors; + QVector colors; //! Tangents QVector tangents; //! Bitangents QVector bitangents; - //! UV coordinate sets - QList > coords; + QList> coords; + //! Triangles + QVector triangles; + //! Strip points + QList> tristrips; + //! Sorted triangles + QVector sortedTriangles; + //! Triangle indices + QVector indices; + //! Is the transform rigid or weighted? + bool transformRigid = true; //! Transformed vertices QVector transVerts; //! Transformed normals QVector transNorms; - //! Transformed colors (alpha-blended) + //! Transformed colors (alpha blended) QVector transColors; + //! Transformed colors (alpha removed) + QVector transColorsNoAlpha; //! Transformed tangents QVector transTangents; //! Transformed bitangents QVector transBitangents; - int skelRoot; - Transform skelTrans; + //! Does the skin data need updating? + bool updateSkin = false; + //! Toggle for skinning + bool doSkinning = false; + + int skeletonRoot; + Transform skeletonTrans; QVector bones; QVector weights; QVector partitions; - //! Triangles - QVector triangles; - //! Strip points - QList > tristrips; - //! Sorted triangles - QVector sortedTriangles; - //! Triangle indices - QVector indices; + //! Holds the name of the shader, or "fixed function pipeline" if no shader + QString shader; - bool transformRigid; + //! Shader property + BSShaderLightingProperty * bssp = nullptr; + //! Skyrim shader property + BSLightingShaderProperty * bslsp = nullptr; + //! Skyrim effect shader property + BSEffectShaderProperty * bsesp = nullptr; + //! Is shader set to double sided? + bool isDoubleSided = false; + //! Is shader set to animate using vertex alphas? + bool isVertexAlphaAnimation = false; + //! Is "Has Vertex Colors" set to Yes + bool hasVertexColors = false; + + bool depthTest = true; + bool depthWrite = true; + bool drawSecond = false; + bool translucent = false; + + mutable BoundSphere boundSphere; + mutable bool updateBounds; +}; - mutable BoundSphere bndSphere; - mutable bool upBounds; +//! A mesh +class Mesh : public Shape +{ - QString shader; +public: + Mesh( Scene * s, const QModelIndex & b ); + ~Mesh() { clear(); } - friend class MorphController; - friend class UVController; - friend class Renderer; + // IControllable + + void clear() override; + void update( const NifModel * nif, const QModelIndex & ) override; + void transform() override; + + // end IControllable + + // Node + + void transformShapes() override; + + void drawShapes( NodeList * secondPass = nullptr, bool presort = false ) override; + void drawSelection() const override; + + BoundSphere bounds() const override; + + bool isHidden() const override; + QString textStats() const override; + + // end Node + + // Shape + + void drawVerts() const override; + QModelIndex vertexAt( int ) const override; + +protected: + + //! Tangent data + QPersistentModelIndex iTangentData; static bool isBSLODPresent; - bool double_sided; - bool double_sided_es; }; + #endif diff --git a/src/gl/glnode.cpp b/src/gl/glnode.cpp index f2bab09bd..a2a026bf6 100644 --- a/src/gl/glnode.cpp +++ b/src/gl/glnode.cpp @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -31,9 +31,9 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "glnode.h" -#include "options.h" +#include "settings.h" -#include "glcontroller.h" // Inherited +#include "controllers.h" #include "glmarker.h" #include "glscene.h" @@ -42,9 +42,13 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "nvtristripwrapper.h" #include +#include #include // std::stable_sort + +//! @file glnode.cpp Scene management for visible NiNodes and their children. + #ifndef M_PI #define M_PI 3.1415926535897932385 #endif @@ -52,377 +56,8 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. int Node::SELECTING = 0; -class TransformController final : public Controller -{ -public: - TransformController( Node * node, const QModelIndex & index ) - : Controller( index ), target( node ) - { - } - - void update( float time ) override final - { - if ( !( active && target ) ) - return; - - time = ctrlTime( time ); - - if ( interpolator ) { - interpolator->updateTransform( target->local, time ); - } - } - - void setInterpolator( const QModelIndex & iBlock ) override final - { - const NifModel * nif = static_cast( iBlock.model() ); - - if ( nif && iBlock.isValid() ) { - if ( interpolator ) { - delete interpolator; - interpolator = 0; - } - - if ( nif->isNiBlock( iBlock, "NiBSplineCompTransformInterpolator" ) ) { - iInterpolator = iBlock; - interpolator = new BSplineTransformInterpolator( this ); - } else if ( nif->isNiBlock( iBlock, "NiTransformInterpolator" ) ) { - iInterpolator = iBlock; - interpolator = new TransformInterpolator( this ); - } - - if ( interpolator ) { - interpolator->update( nif, iInterpolator ); - } - } - } - -protected: - QPointer target; - QPointer interpolator; -}; - -class MultiTargetTransformController final : public Controller -{ - typedef QPair, QPointer > TransformTarget; - -public: - MultiTargetTransformController( Node * node, const QModelIndex & index ) - : Controller( index ), target( node ) - { - } - - void update( float time ) override final - { - if ( !( active && target ) ) - return; - - time = ctrlTime( time ); - - for ( const TransformTarget& tt : extraTargets ) { - if ( tt.first && tt.second ) { - tt.second->updateTransform( tt.first->local, time ); - } - } - } - - bool update( const NifModel * nif, const QModelIndex & index ) override final - { - if ( Controller::update( nif, index ) ) { - if ( target ) { - Scene * scene = target->scene; - extraTargets.clear(); - - QVector lTargets = nif->getLinkArray( index, "Extra Targets" ); - for ( const auto l : lTargets ) { - Node * node = scene->getNode( nif, nif->getBlock( l ) ); - - if ( node ) { - extraTargets.append( TransformTarget( node, 0 ) ); - } - } - } - - return true; - } - - for ( const TransformTarget& tt : extraTargets ) { - // TODO: update the interpolators - } - - return false; - } - - bool setInterpolator( Node * node, const QModelIndex & iInterpolator ) - { - const NifModel * nif = static_cast( iInterpolator.model() ); - - if ( !nif || !iInterpolator.isValid() ) - return false; - - QMutableListIterator it( extraTargets ); - - while ( it.hasNext() ) { - it.next(); - - if ( it.value().first == node ) { - if ( it.value().second ) { - delete it.value().second; - it.value().second = 0; - } - - if ( nif->isNiBlock( iInterpolator, "NiBSplineCompTransformInterpolator" ) ) { - it.value().second = new BSplineTransformInterpolator( this ); - } else if ( nif->isNiBlock( iInterpolator, "NiTransformInterpolator" ) ) { - it.value().second = new TransformInterpolator( this ); - } - - if ( it.value().second ) { - it.value().second->update( nif, iInterpolator ); - } - - return true; - } - } - - return false; - } - -protected: - QPointer target; - QList extraTargets; -}; - -class ControllerManager final : public Controller -{ -public: - ControllerManager( Node * node, const QModelIndex & index ) - : Controller( index ), target( node ) - { - } - - void update( float ) override final {} - - bool update( const NifModel * nif, const QModelIndex & index ) override final - { - if ( Controller::update( nif, index ) ) { - if ( target ) { - Scene * scene = target->scene; - QVector lSequences = nif->getLinkArray( index, "Controller Sequences" ); - for ( const auto l : lSequences ) { - QModelIndex iSeq = nif->getBlock( l, "NiControllerSequence" ); - - if ( iSeq.isValid() ) { - QString name = nif->get( iSeq, "Name" ); - - if ( !scene->animGroups.contains( name ) ) { - scene->animGroups.append( name ); - - QMap tags = scene->animTags[ name ]; - - QModelIndex iKeys = nif->getBlock( nif->getLink( iSeq, "Text Keys" ), "NiTextKeyExtraData" ); - QModelIndex iTags = nif->getIndex( iKeys, "Text Keys" ); - - for ( int r = 0; r < nif->rowCount( iTags ); r++ ) { - tags.insert( nif->get( iTags.child( r, 0 ), "Value" ), nif->get( iTags.child( r, 0 ), "Time" ) ); - } - - scene->animTags[ name ] = tags; - } - } - } - } - - return true; - } - - return false; - } - - void setSequence( const QString & seqname ) override final - { - const NifModel * nif = static_cast( iBlock.model() ); - - if ( target && iBlock.isValid() && nif ) { - MultiTargetTransformController * multiTargetTransformer = 0; - for ( Controller * c : target->controllers ) { - if ( c->typeId() == "NiMultiTargetTransformController" ) { - multiTargetTransformer = static_cast( c ); - break; - } - } - - QVector lSequences = nif->getLinkArray( iBlock, "Controller Sequences" ); - for ( const auto l : lSequences ) { - QModelIndex iSeq = nif->getBlock( l, "NiControllerSequence" ); - - if ( iSeq.isValid() && nif->get( iSeq, "Name" ) == seqname ) { - start = nif->get( iSeq, "Start Time" ); - stop = nif->get( iSeq, "Stop Time" ); - phase = nif->get( iSeq, "Phase" ); - frequency = nif->get( iSeq, "Frequency" ); - - QModelIndex iCtrlBlcks = nif->getIndex( iSeq, "Controlled Blocks" ); - - for ( int r = 0; r < nif->rowCount( iCtrlBlcks ); r++ ) { - QModelIndex iCB = iCtrlBlcks.child( r, 0 ); - - QModelIndex iInterpolator = nif->getBlock( nif->getLink( iCB, "Interpolator" ), "NiInterpolator" ); - - QString nodename = nif->get( iCB, "Node Name" ); - - if ( nodename.isEmpty() ) { - QModelIndex idx = nif->getIndex( iCB, "Node Name Offset" ); - nodename = idx.sibling( idx.row(), NifModel::ValueCol ).data( NifSkopeDisplayRole ).toString(); - } - - QString proptype = nif->get( iCB, "Property Type" ); - - if ( proptype.isEmpty() ) { - QModelIndex idx = nif->getIndex( iCB, "Property Type Offset" ); - proptype = idx.sibling( idx.row(), NifModel::ValueCol ).data( NifSkopeDisplayRole ).toString(); - } - - QString ctrltype = nif->get( iCB, "Controller Type" ); - - if ( ctrltype.isEmpty() ) { - QModelIndex idx = nif->getIndex( iCB, "Controller Type Offset" ); - ctrltype = idx.sibling( idx.row(), NifModel::ValueCol ).data( NifSkopeDisplayRole ).toString(); - } - - QString var1 = nif->get( iCB, "Variable 1" ); - - if ( var1.isEmpty() ) { - QModelIndex idx = nif->getIndex( iCB, "Variable 1 Offset" ); - var1 = idx.sibling( idx.row(), NifModel::ValueCol ).data( NifSkopeDisplayRole ).toString(); - } - - QString var2 = nif->get( iCB, "Variable 2" ); - - if ( var2.isEmpty() ) { - QModelIndex idx = nif->getIndex( iCB, "Variable 2 Offset" ); - var2 = idx.sibling( idx.row(), NifModel::ValueCol ).data( NifSkopeDisplayRole ).toString(); - } - - Node * node = target->findChild( nodename ); - - if ( !node ) - continue; - - if ( ctrltype == "NiTransformController" && multiTargetTransformer ) { - if ( multiTargetTransformer->setInterpolator( node, iInterpolator ) ) { - multiTargetTransformer->start = start; - multiTargetTransformer->stop = stop; - multiTargetTransformer->phase = phase; - multiTargetTransformer->frequency = frequency; - continue; - } - } - - Controller * ctrl = node->findController( proptype, ctrltype, var1, var2 ); - - if ( ctrl ) { - ctrl->start = start; - ctrl->stop = stop; - ctrl->phase = phase; - ctrl->frequency = frequency; - - ctrl->setInterpolator( iInterpolator ); - } - } - } - } - } - } - -protected: - QPointer target; -}; - -class KeyframeController final : public Controller -{ -public: - KeyframeController( Node * node, const QModelIndex & index ) - : Controller( index ), target( node ), lTrans( 0 ), lRotate( 0 ), lScale( 0 ) - { - } - - void update( float time ) - { - if ( !( active && target ) ) - return; - - time = ctrlTime( time ); - - interpolate( target->local.rotation, iRotations, time, lRotate ); - interpolate( target->local.translation, iTranslations, time, lTrans ); - interpolate( target->local.scale, iScales, time, lScale ); - } - - bool update( const NifModel * nif, const QModelIndex & index ) override final - { - if ( Controller::update( nif, index ) ) { - iTranslations = nif->getIndex( iData, "Translations" ); - iRotations = nif->getIndex( iData, "Rotations" ); - - if ( !iRotations.isValid() ) - iRotations = iData; - - iScales = nif->getIndex( iData, "Scales" ); - return true; - } - - return false; - } - -protected: - QPointer target; - - QPersistentModelIndex iTranslations, iRotations, iScales; - - int lTrans, lRotate, lScale; -}; - -class VisibilityController final : public Controller -{ -public: - VisibilityController( Node * node, const QModelIndex & index ) - : Controller( index ), target( node ), visLast( 0 ) - { - } - - void update( float time ) override final - { - if ( !( active && target ) ) - return; - - time = ctrlTime( time ); - - bool isVisible; - - if ( interpolate( isVisible, iData, time, visLast ) ) { - target->flags.node.hidden = !isVisible; - } - } - - bool update( const NifModel * nif, const QModelIndex & index ) override final - { - if ( Controller::update( nif, index ) ) { - // iData already points to the NiVisData - // note that nif.xml needs to have "Keys" not "Vis Keys" for interpolate() to work - //iKeys = nif->getIndex( iData, "Data" ); - return true; - } - - return false; - } - -protected: - QPointer target; - - //QPersistentModelIndex iKeys; - - int visLast; -}; +static QColor highlightColor; +static QColor wireframeColor; /* * Node list @@ -485,7 +120,7 @@ Node * NodeList::get( const QModelIndex & index ) const if ( n->index().isValid() && n->index() == index ) return n; } - return 0; + return nullptr; } void NodeList::validate() @@ -502,16 +137,39 @@ void NodeList::validate() bool compareNodes( const Node * node1, const Node * node2 ) { - // opaque meshes first (sorted from front to rear) - // then alpha enabled meshes (sorted from rear to front) + bool p1 = node1->isPresorted(); + bool p2 = node2->isPresorted(); + + // Presort meshes + if ( p1 && p2 ) { + return node1->id() < node2->id(); + } + + return p2; +} + +bool compareNodesAlpha( const Node * node1, const Node * node2 ) +{ + // Presorted meshes override other sorting + // Alpha enabled meshes on top (sorted from rear to front) + + bool p1 = node1->isPresorted(); + bool p2 = node2->isPresorted(); + + // Presort meshes + if ( p1 && p2 ) { + return node1->id() < node2->id(); + } + bool a1 = node1->findProperty(); bool a2 = node2->findProperty(); + float d1 = node1->viewDepth(); + float d2 = node2->viewDepth(); + + // Alpha sort meshes if ( a1 == a2 ) { - if ( a1 ) { - return ( node1->center()[2] < node2->center()[2] ); - } - return ( node1->center()[2] > node2->center()[2] ); + return (d1 < d2); } return a2; @@ -522,21 +180,68 @@ void NodeList::sort() std::stable_sort( nodes.begin(), nodes.end(), compareNodes ); } +void NodeList::alphaSort() +{ + std::stable_sort( nodes.begin(), nodes.end(), compareNodesAlpha ); +} /* * Node */ -Node::Node( Scene * s, const QModelIndex & index ) : Controllable( s, index ), parent( 0 ), ref( 0 ) +Node::Node( Scene * s, const QModelIndex & index ) : IControllable( s, index ), parent( 0 ), ref( 0 ) { nodeId = 0; flags.bits = 0; + + updateSettings(); + + connect( NifSkope::getOptions(), &SettingsDialog::saveSettings, this, &Node::updateSettings ); } + +void Node::updateSettings() +{ + QSettings settings; + settings.beginGroup( "Settings/Render/Colors/" ); + + cfg.background = settings.value( "Background", QColor( 0, 0, 0 ) ).value(); + cfg.highlight = settings.value( "Highlight", QColor( 255, 255, 0 ) ).value(); + cfg.wireframe = settings.value( "Wireframe", QColor( 0, 255, 0 ) ).value(); + + highlightColor = cfg.highlight; + wireframeColor = cfg.wireframe; + + settings.endGroup(); +} + +// Old Options API +// TODO: Move away from the GL-like naming +void glHighlightColor() +{ + glColor( Color4( highlightColor ) ); +} + +void glNormalColor() +{ + glColor( Color4( wireframeColor ) ); +} + +void Node::glHighlightColor() const +{ + glColor( Color4( cfg.highlight ) ); +} + +void Node::glNormalColor() const +{ + glColor( Color4( cfg.wireframe ) ); +} + + void Node::clear() { - Controllable::clear(); + IControllable::clear(); nodeId = 0; flags.bits = 0; @@ -554,15 +259,31 @@ Controller * Node::findController( const QString & proptype, const QString & ctr return prp->findController( ctrltype, var1, var2 ); } } - return 0; + return nullptr; + } + + return IControllable::findController( ctrltype, var1, var2 ); +} + +Controller * Node::findController( const QString & proptype, const QModelIndex & index ) +{ + Controller * c = nullptr; + + for ( Property * prp : properties.list() ) { + if ( prp->typeId() == proptype ) { + if ( c ) + break; + + c = prp->findController( index ); + } } - return Controllable::findController( ctrltype, var1, var2 ); + return c; } void Node::update( const NifModel * nif, const QModelIndex & index ) { - Controllable::update( nif, index ); + IControllable::update( nif, index ); if ( !iBlock.isValid() ) { clear(); @@ -686,7 +407,7 @@ const Transform & Node::worldTrans() const return scene->worldTrans[ nodeId ]; } -Transform Node::localTransFrom( int root ) const +Transform Node::localTrans( int root ) const { Transform trans; const Node * node = this; @@ -699,11 +420,16 @@ Transform Node::localTransFrom( int root ) const return trans; } -Vector3 Node::center() const +const Vector3 Node::center() const { return worldTrans().translation; } +float Node::viewDepth() const +{ + return viewTrans().translation[2]; +} + Node * Node::findParent( int id ) const { Node * node = parent; @@ -726,7 +452,7 @@ Node * Node::findChild( int id ) const return child; } } - return 0; + return nullptr; } Node * Node::findChild( const QString & name ) const @@ -740,23 +466,23 @@ Node * Node::findChild( const QString & name ) const if ( n ) return n; } - return 0; + return nullptr; } bool Node::isHidden() const { - if ( Options::drawHidden() ) + if ( scene->options & Scene::ShowHidden ) return false; if ( flags.node.hidden || ( parent && parent->isHidden() ) ) return true; - return !Options::cullExpression().pattern().isEmpty() && name.contains( Options::cullExpression() ); + return false; /*!Options::cullExpression().pattern().isEmpty() && name.contains( Options::cullExpression() );*/ } void Node::transform() { - Controllable::transform(); + IControllable::transform(); // if there's a rigid body attached, then calculate and cache the body's transform // (need this later in the drawing stage for the constraints) @@ -764,7 +490,7 @@ void Node::transform() if ( iBlock.isValid() && nif ) { // Scale up for Skyrim - float havokScale = ( nif->getUserVersion() >= 12 ) ? 10.0f : 1.0f; + float havokScale = (nif->checkVersion( 0x14020007, 0x14020007 ) && nif->getUserVersion() >= 12) ? 10.0f : 1.0f; QModelIndex iObject = nif->getBlock( nif->getLink( iBlock, "Collision Data" ) ); @@ -802,7 +528,10 @@ void Node::transformShapes() void Node::draw() { - if ( isHidden() ) + if ( isHidden() || iBlock == scene->currentBlock ) + return; + + if ( !(scene->selMode & Scene::SelObject) ) return; if ( Node::SELECTING ) { @@ -837,10 +566,16 @@ void Node::draw() glVertex( a ); glEnd(); - glBegin( GL_LINES ); - glVertex( a ); - glVertex( b ); - glEnd(); + if ( Node::SELECTING ) { + glBegin( GL_LINES ); + glVertex( a ); + glVertex( b ); + glEnd(); + } else { + auto c = cfg.wireframe; + glColor4f( c.redF(), c.greenF(), c.blueF(), c.alphaF() / 3.0 ); + drawDashLine( a, b, 144 ); + } for ( Node * node : children.list() ) { node->draw(); @@ -849,9 +584,23 @@ void Node::draw() void Node::drawSelection() const { - if ( scene->currentBlock != iBlock || !Options::drawNodes() ) + auto nif = static_cast(scene->currentIndex.model()); + if ( !nif ) + return; + + if ( !(scene->selMode & Scene::SelObject) ) + return; + + bool extraData = false; + auto currentBlock = nif->getBlockName( scene->currentBlock ); + if ( currentBlock == "BSConnectPoint::Parents" ) + extraData = nif->getBlockNumber( iBlock ) == 0; // Root Node only + + if ( scene->currentBlock != iBlock && !extraData ) return; + auto n = scene->currentIndex.data( NifSkopeDisplayRole ).toString(); + if ( Node::SELECTING ) { int s_nodeId = ID2COLORKEY( nodeId ); glColor4ubv( (GLubyte *)&s_nodeId ); @@ -874,6 +623,66 @@ void Node::drawSelection() const glPointSize( 8.5 ); + glPushMatrix(); + glMultMatrix( viewTrans() ); + + float sceneRadius = scene->bounds().radius; + float normalScale = (sceneRadius > 150.0) ? 1.0 : sceneRadius / 150.0; + + if ( currentBlock == "BSConnectPoint::Parents" ) { + glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); + + auto cp = nif->getIndex( scene->currentBlock, "Connect Points" ); + bool isChild = scene->currentIndex.parent().data( NifSkopeDisplayRole ).toString() == "Connect Points"; + + int sel = -1; + if ( n == "Connect Points" && !nif->isArray( scene->currentIndex ) ) { + sel = scene->currentIndex.row(); + } else if ( isChild ) { + sel = scene->currentIndex.parent().row(); + } + + int ct = nif->rowCount( cp ); + for ( int i = 0; i < ct; i++ ) { + auto p = cp.child( i, 0 ); + + auto trans = nif->get( p, "Translation" ); + auto rot = nif->get( p, "Rotation" ); + //auto scale = nif->get( p, "Scale" ); + + Transform t; + Matrix m; + m.fromQuat( rot ); + t.rotation = m; + t.translation = trans; + t.scale = normalScale * 16; + + if ( i == sel ) { + glHighlightColor(); + } else { + glNormalColor(); + } + + glPushMatrix(); + glMultMatrix( t ); + + auto pos = Vector3( 0, 0, 0 ); + + drawDashLine( pos, Vector3( 0, 1, 0 ), 15 ); + drawDashLine( pos, Vector3( 1, 0, 0 ), 15 ); + drawDashLine( pos, Vector3( 0, 0, 1 ), 15 ); + drawCircle( pos, Vector3( 0, 1, 0 ), 1, 64 ); + + glPopMatrix(); + } + + } + + glPopMatrix(); + + if ( extraData ) + return; + Vector3 a = viewTrans().translation; Vector3 b = a; @@ -884,10 +693,16 @@ void Node::drawSelection() const glVertex( a ); glEnd(); + auto c = cfg.highlight; + glColor4f( c.redF(), c.greenF(), c.blueF(), c.alphaF() * 0.8 ); glBegin( GL_LINES ); glVertex( a ); glVertex( b ); glEnd(); + + for ( Node * node : children.list() ) { + node->draw(); + } } void DrawVertexSelection( QVector & verts, int i ) @@ -935,12 +750,15 @@ void drawHvkShape( const NifModel * nif, const QModelIndex & iShape, QStackselMode & Scene::SelObject) ) + return; + stack.push( iShape ); // Scale up for Skyrim - float havokScale = ( nif->getUserVersion() >= 12 ) ? 10.0f : 1.0f; + float havokScale = (nif->checkVersion( 0x14020007, 0x14020007 ) && nif->getUserVersion() >= 12) ? 10.0f : 1.0f; - //qWarning() << "draw shape" << nif->getBlockNumber( iShape ) << nif->itemName( iShape ); + //qDebug() << "draw shape" << nif->getBlockNumber( iShape ) << nif->itemName( iShape ); QString name = nif->itemName( iShape ); @@ -1022,45 +840,15 @@ void drawHvkShape( const NifModel * nif, const QModelIndex & iShape, QStackgetIndex( iShape, "Strips Data" ); - - for ( int r = 0; r < nif->rowCount( iStrips ); r++ ) { - QModelIndex iStripData = nif->getBlock( nif->getLink( iStrips.child( r, 0 ) ), "NiTriStripsData" ); - - if ( iStripData.isValid() ) { - QVector verts = nif->getArray( iStripData, "Vertices" ); - - glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); - glDisable( GL_CULL_FACE ); - glBegin( GL_TRIANGLES ); + drawNiTSS( nif, iShape ); - QModelIndex iPoints = nif->getIndex( iStripData, "Points" ); - - for ( int r = 0; r < nif->rowCount( iPoints ); r++ ) { - // draw the strips like they appear in the tescs - // (use the unstich strips spell to avoid the spider web effect) - QVector strip = nif->getArray( iPoints.child( r, 0 ) ); - - if ( strip.count() >= 3 ) { - quint16 a = strip[0]; - quint16 b = strip[1]; - - for ( int x = 2; x < strip.size(); x++ ) { - quint16 c = strip[x]; - glVertex( verts.value( a ) ); - glVertex( verts.value( b ) ); - glVertex( verts.value( c ) ); - a = b; - b = c; - } - } - } - - glEnd(); - glEnable( GL_CULL_FACE ); - glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); - } - } + //if ( Options::getHavokState() == HAVOK_SOLID ) { + // QColor c = Options::hlColor(); + // c.setAlphaF( 0.3 ); + // glColor( Color4( c ) ); + // + // drawNiTSS( nif, iShape, true ); + //} glPopMatrix(); } else if ( name == "bhkConvexVerticesShape" ) { @@ -1069,7 +857,16 @@ void drawHvkShape( const NifModel * nif, const QModelIndex & iShape, QStackgetArray( iShape, "Vertices" ), nif->getArray( iShape, "Normals" ), havokScale ); + drawConvexHull( nif, iShape, havokScale ); + + //if ( Options::getHavokState() == HAVOK_SOLID ) { + // QColor c = Options::hlColor(); + // c.setAlphaF( 0.3 ); + // glColor( Color4( c ) ); + // + // drawConvexHull( nif, iShape, havokScale, true ); + //} + } else if ( name == "bhkMoppBvTreeShape" ) { if ( !Node::SELECTING ) { if ( scene->currentBlock == nif->getBlock( nif->getLink( iShape, "Shape" ) ) ) { @@ -1188,7 +985,7 @@ void drawHvkShape( const NifModel * nif, const QModelIndex & iShape, QStackgetBlock( nif->getParent( nif->getBlockNumber( iShape ) ) ); - Vector4 origin = Vector4( nif->get( iParent, "Origin" ), 0 ); - - QModelIndex iData = nif->getBlock( nif->getLink( iShape, "Data" ) ); - - if ( iData.isValid() ) { - QModelIndex iBigVerts = nif->getIndex( iData, "Big Verts" ); - QModelIndex iBigTris = nif->getIndex( iData, "Big Tris" ); - - QVector verts = nif->getArray( iBigVerts ); - - glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); - glDisable( GL_CULL_FACE ); + drawCMS( nif, iShape ); - for ( int r = 0; r < nif->rowCount( iBigTris ); r++ ) { - quint16 a = nif->get( iBigTris.child( r, 0 ), "Triangle 1" ); - quint16 b = nif->get( iBigTris.child( r, 0 ), "Triangle 2" ); - quint16 c = nif->get( iBigTris.child( r, 0 ), "Triangle 3" ); - - glBegin( GL_TRIANGLES ); - - glVertex( verts[a] * havokScale ); - glVertex( verts[b] * havokScale ); - glVertex( verts[c] * havokScale ); - - glEnd(); - } - - glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); - glEnable( GL_CULL_FACE ); - - QModelIndex iChunks = nif->getIndex( iData, "Chunks" ); - - for ( int r = 0; r < nif->rowCount( iChunks ); r++ ) { - Vector4 chunkOrigin = nif->get( iChunks.child( r, 0 ), "Translation" ); - quint32 numOffsets = nif->get( iChunks.child( r, 0 ), "Num Vertices" ); - quint32 numIndices = nif->get( iChunks.child( r, 0 ), "Num Indices" ); - quint32 numStrips = nif->get( iChunks.child( r, 0 ), "Num Strips" ); - QVector offsets = nif->getArray( iChunks.child( r, 0 ), "Vertices" ); - QVector indices = nif->getArray( iChunks.child( r, 0 ), "Indices" ); - QVector strips = nif->getArray( iChunks.child( r, 0 ), "Strips" ); - - QVector vertices( numOffsets / 3 ); - - int numStripVerts = 0; - int offset = 0; - - for ( int v = 0; v < (int)numStrips; v++ ) { - numStripVerts += strips[v]; - } - - for ( int n = 0; n < ( (int)numOffsets / 3 ); n++ ) { - vertices[n] = chunkOrigin + Vector4( offsets[3 * n], offsets[3 * n + 1], offsets[3 * n + 2], 0 ) / 1000.0f; - vertices[n] *= havokScale; - } - - glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); - glDisable( GL_CULL_FACE ); - - // Stripped tris - for ( int s = 0; s < (int)numStrips; s++ ) { - for ( int idx = 0; idx < strips[s] - 2; idx++ ) { - glBegin( GL_TRIANGLES ); - - glVertex( vertices[indices[offset + idx]] ); - glVertex( vertices[indices[offset + idx + 1]] ); - glVertex( vertices[indices[offset + idx + 2]] ); - - glEnd(); - } - - offset += strips[s]; - } - - // Non-stripped tris - for ( int f = 0; f < (int)(numIndices - offset); f += 3 ) { - glBegin( GL_TRIANGLES ); - - glVertex( vertices[indices[offset + f]] ); - glVertex( vertices[indices[offset + f + 1]] ); - glVertex( vertices[indices[offset + f + 2]] ); - - glEnd(); - } - - glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); - glEnable( GL_CULL_FACE ); - } - } + //if ( Options::getHavokState() == HAVOK_SOLID ) { + // QColor c = Options::hlColor(); + // c.setAlphaF( 0.3 ); + // glColor( Color4( c ) ); + // + // drawCMS( nif, iShape, true ); + //} glPopMatrix(); } @@ -1303,7 +1020,10 @@ void drawHvkShape( const NifModel * nif, const QModelIndex & iShape, QStackoptions & Scene::ShowConstraints) ) ) + return; + + if ( !(scene->selMode & Scene::SelObject) ) return; QList tBodies; @@ -1336,8 +1056,8 @@ void drawHvkConstraint( const NifModel * nif, const QModelIndex & iConstraint, c if ( scene->currentBlock == nif->getBlock( iConstraint ) ) { // fix: add selected visual to havok meshes glHighlightColor(); - color_a.fromQColor( Options::hlColor() ); - color_b.setRGB( Options::hlColor().blueF(), Options::hlColor().redF(), Options::hlColor().greenF() ); + color_a.fromQColor( highlightColor ); + color_b.setRGB( highlightColor.blueF(), highlightColor.redF(), highlightColor.greenF() ); } } @@ -1606,6 +1326,9 @@ void drawHvkConstraint( const NifModel * nif, const QModelIndex & iConstraint, c void Node::drawHavok() { + if ( !(scene->selMode & Scene::SelObject) ) + return; + // TODO: Why are all these here - "drawNodes", "drawFurn", "drawHavok"? // Idea: Make them go to their own classes in different cpp files for ( Node * node : children.list() ) { @@ -1749,7 +1472,7 @@ void Node::drawHavok() glMultMatrix( scene->bhkBodyTrans.value( nif->getBlockNumber( iBody ) ) ); - //qWarning() << "draw obj" << nif->getBlockNumber( iObject ) << nif->itemName( iObject ); + //qDebug() << "draw obj" << nif->getBlockNumber( iObject ) << nif->itemName( iObject ); if ( !Node::SELECTING ) { glEnable( GL_DEPTH_TEST ); @@ -1803,10 +1526,13 @@ void Node::drawHavok() int s_nodeId = ID2COLORKEY( nif->getBlockNumber( iBody ) ); glColor4ubv( (GLubyte *)&s_nodeId ); glDepthFunc( GL_ALWAYS ); - drawAxes( Vector3( nif->get( iBody, "Center" ) ), 0.2f ); + drawAxes( Vector3( nif->get( iBody, "Center" ) ), 2.0f ); glDepthFunc( GL_LEQUAL ); } else { - drawAxes( Vector3( nif->get( iBody, "Center" ) ), 0.2f ); + // Scale up for Skyrim + float havokScale = (nif->checkVersion( 0x14020007, 0x14020007 ) && nif->getUserVersion() >= 12) ? 10.0f : 1.0f; + + drawAxes( Vector3( nif->get( iBody, "Center" ) ) * havokScale, 2.0f ); } glPopMatrix(); @@ -1827,75 +1553,180 @@ void drawFurnitureMarker( const NifModel * nif, const QModelIndex & iPosition ) quint8 ref1 = nif->get( iPosition, "Position Ref 1" ); quint8 ref2 = nif->get( iPosition, "Position Ref 2" ); - if ( ref1 != ref2 ) { - qDebug() << "Position Ref 1 and 2 are not equal!"; - return; - } + const GLMarker * mark[5]; + Vector3 flip[5]; + Vector3 pos( 1, 1, 1 ); + Vector3 neg( -1, 1, 1 ); - Vector3 flip( 1, 1, 1 ); - const GLMarker * mark; + float xOffset = 0.0f; + float zOffset = 0.0f; + float yOffset = 0.0f; + float roll; - switch ( ref1 ) { - case 1: - mark = &FurnitureMarker01; - break; + int i = 0; - case 2: - flip[0] = -1; - mark = &FurnitureMarker01; - break; + if ( ref1 == 0 ) { + float heading = nif->get( iPosition, "Heading" ); + quint16 type = nif->get( iPosition, "Animation Type" ); + int entry = nif->get( iPosition, "Entry Properties" ); - case 3: - mark = &FurnitureMarker03; - break; + if ( type == 0 ) return; - case 4: - mark = &FurnitureMarker04; - break; + // Sit=1, Sleep=2, Lean=3 + // Front=1, Behind=2, Right=4, Left=8, Up=16(0x10) - case 11: - mark = &FurnitureMarker11; - break; + switch ( type ) { + case 1: + // Sit Type - case 12: - flip[0] = -1; - mark = &FurnitureMarker11; - break; + zOffset = -34.00f; - case 13: - mark = &FurnitureMarker13; - break; + if ( entry & 0x1 ) { + // Chair Front + flip[i] = pos; + mark[i] = &ChairFront; + i++; + } + if ( entry & 0x2 ) { + // Chair Behind + flip[i] = pos; + mark[i] = &ChairBehind; + i++; + } + if ( entry & 0x4 ) { + // Chair Right + flip[i] = neg; + mark[i] = &ChairLeft; + i++; + } + if ( entry & 0x8 ) { + // Chair Left + flip[i] = pos; + mark[i] = &ChairLeft; + i++; + } + break; + case 2: + // Sleep Type - case 14: - mark = &FurnitureMarker14; - break; + zOffset = -34.00f; - default: - qDebug() << "Unknown furniture marker " << ref1 << "!"; - return; - } + if ( entry & 0x1 ) { + // Bed Front + //flip[i] = pos; + //mark[i] = &FurnitureMarker03; + //i++; + } + if ( entry & 0x2 ) { + // Bed Behind + //flip[i] = pos; + //mark[i] = &FurnitureMarker04; + //i++; + } + if ( entry & 0x4 ) { + // Bed Right + flip[i] = neg; + mark[i] = &BedLeft; + i++; + } + if ( entry & 0x8 ) { + // Bed Left + flip[i] = pos; + mark[i] = &BedLeft; + i++; + } + if ( entry & 0x10 ) { + // Bed Up???? + // This is sometimes used as a real bed position + // Other times it is a dummy + flip[i] = neg; + mark[i] = &BedLeft; + i++; + } + break; + case 3: + break; + default: + break; + } + + roll = heading; + } else { + if ( ref1 != ref2 ) { + qDebug() << "Position Ref 1 and 2 are not equal"; + return; + } + + switch ( ref1 ) { + case 1: + mark[0] = &FurnitureMarker01; // Single Bed + break; + + case 2: + flip[0] = neg; + mark[0] = &FurnitureMarker01; + break; + + case 3: + mark[0] = &FurnitureMarker03; // Ground Bed? + break; + + case 4: + mark[0] = &FurnitureMarker04; // Ground Bed? Behind + break; + + case 11: + mark[0] = &FurnitureMarker11; // Chair Left + break; + + case 12: + flip[0] = neg; + mark[0] = &FurnitureMarker11; + break; - float roll = float(orient) / 6284.0 * 2.0 * (-M_PI); + case 13: + mark[0] = &FurnitureMarker13; // Chair Behind + break; + + case 14: + mark[0] = &FurnitureMarker14; // Chair Front + break; + + default: + qDebug() << "Unknown furniture marker " << ref1; + return; + } + + i = 1; + + // TODO: FIX: This makes no sense + roll = float( orient ) / 6284.0 * 2.0 * (-M_PI); + } if ( Node::SELECTING ) { - // TODO: not tested! need nif files what contain that GLint id = ( nif->getBlockNumber( iPosition ) & 0xffff ) | ( ( iPosition.row() & 0xffff ) << 16 ); int s_nodeId = ID2COLORKEY( id ); glColor4ubv( (GLubyte *)&s_nodeId ); } - glPushMatrix(); + for ( int n = 0; n < i; n++ ) { + glPushMatrix(); - Transform t; - t.rotation.fromEuler( 0, 0, roll ); - t.translation = offs; - glMultMatrix( t ); + Transform t; + t.rotation.fromEuler( 0, 0, roll ); + t.translation = offs; + t.translation[0] += xOffset; + t.translation[1] += yOffset; + t.translation[2] += zOffset; - glScale( flip ); + glMultMatrix( t ); - drawMarker( mark ); + glScale( flip[n] ); - glPopMatrix(); + drawMarker( mark[n] ); + + glPopMatrix(); + } } void Node::drawFurn() @@ -1909,6 +1740,9 @@ void Node::drawFurn() if ( !( iBlock.isValid() && nif ) ) return; + if ( !(scene->selMode & Scene::SelObject) ) + return; + QModelIndex iExtraDataList = nif->getIndex( iBlock, "Extra Data List" ); if ( !iExtraDataList.isValid() ) @@ -1962,13 +1796,23 @@ void Node::drawFurn() glPopMatrix(); } -void Node::drawShapes( NodeList * draw2nd ) +void Node::drawShapes( NodeList * secondPass, bool presort ) { if ( isHidden() ) return; + const NifModel * nif = static_cast(iBlock.model()); + + // BSOrderedNode support + // Only set if true (|=) so that it propagates to all children + presort |= nif->getBlock( iBlock, "BSOrderedNode" ).isValid(); + + presorted = presort; + if ( presorted ) + children.sort(); + for ( Node * node : children.list() ) { - node->drawShapes( draw2nd ); + node->drawShapes( secondPass, presort ); } } @@ -1995,8 +1839,10 @@ BoundSphere Node::bounds() const { BoundSphere boundsphere; + auto opts = scene->options; + // the node itself - if ( Options::drawNodes() || Options::drawHavok() ) { + if ( (opts & Scene::ShowNodes) || (opts & Scene::ShowCollision) ) { boundsphere |= BoundSphere( worldTrans().translation, 0 ); } diff --git a/src/gl/glnode.h b/src/gl/glnode.h index c9d924c6e..0337ee5ae 100644 --- a/src/gl/glnode.h +++ b/src/gl/glnode.h @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -33,7 +33,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef GLNODE_H #define GLNODE_H -#include "glcontrolable.h" // Inherited +#include "icontrollable.h" // Inherited #include "glproperty.h" #include @@ -41,6 +41,8 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include +//! @file glnode.h Node, NodeList + class Node; class NodeList final @@ -64,13 +66,22 @@ class NodeList final const QList & list() const { return nodes; } void sort(); + void alphaSort(); protected: QList nodes; }; -class Node : public Controllable +class Node : public IControllable { + friend class ControllerManager; + friend class KeyframeController; + friend class MultiTargetTransformController; + friend class TransformController; + friend class VisibilityController; + friend class NodeList; + friend class LODNode; + typedef union { quint16 bits; @@ -84,70 +95,85 @@ class Node : public Controllable public: Node( Scene * scene, const QModelIndex & block ); - // Inherited from Controllable + static int SELECTING; + + int id() const { return nodeId; } + + // IControllable + void clear() override; void update( const NifModel * nif, const QModelIndex & block ) override; void transform() override; + // end IControllable + virtual void transformShapes(); virtual void draw(); - virtual void drawShapes( NodeList * draw2nd = nullptr ); + virtual void drawShapes( NodeList * secondPass = nullptr, bool presort = false ); virtual void drawHavok(); virtual void drawFurn(); virtual void drawSelection() const; + virtual float viewDepth() const; + virtual class BoundSphere bounds() const; + virtual const Vector3 center() const; virtual const Transform & viewTrans() const; virtual const Transform & worldTrans() const; virtual const Transform & localTrans() const { return local; } - virtual Transform localTransFrom( int parentNode ) const; - virtual Vector3 center() const; + virtual Transform localTrans( int parentNode ) const; virtual bool isHidden() const; - bool isVisible() const { return !isHidden(); } - - int id() const { return nodeId; } + virtual QString textStats() const; - Node * findParent( int id ) const; + bool isVisible() const { return !isHidden(); } + bool isPresorted() const { return presorted; } + Node * findChild( int id ) const; Node * findChild( const QString & name ) const; + + Node * findParent( int id ) const; Node * parentNode() const { return parent; } void makeParent( Node * parent ); - virtual class BoundSphere bounds() const; - template T * findProperty() const; void activeProperties( PropertyList & list ) const; Controller * findController( const QString & proptype, const QString & ctrltype, const QString & var1, const QString & var2 ); - virtual QString textStats() const; + Controller * findController( const QString & proptype, const QModelIndex & index ); - static int SELECTING; +public slots: + void updateSettings(); protected: void setController( const NifModel * nif, const QModelIndex & controller ) override; + // Old Options API + // TODO: Move away from the GL-like naming + void glHighlightColor() const; + void glNormalColor() const; QPointer parent; - - int ref; - NodeList children; - PropertyList properties; - int nodeId; + PropertyList properties; Transform local; NodeFlags flags; - friend class KeyframeController; - friend class TransformController; - friend class ControllerManager; - friend class MultiTargetTransformController; - friend class VisibilityController; - friend class NodeList; - friend class LODNode; + struct Settings + { + QColor background; + QColor highlight; + QColor wireframe; + } cfg; + + + bool presorted = false; + + int nodeId; + int ref; }; template inline T * Node::findProperty() const @@ -160,7 +186,7 @@ template inline T * Node::findProperty() const if ( parent ) return parent->findProperty(); - return 0; + return nullptr; } //! A Node with levels of detail @@ -169,11 +195,14 @@ class LODNode : public Node public: LODNode( Scene * scene, const QModelIndex & block ); - // Inherited from Node, Controllable + // IControllable + void clear() override; void update( const NifModel * nif, const QModelIndex & block ) override; void transform() override; + // end IControllable + protected: QList > ranges; QPersistentModelIndex iData; @@ -187,9 +216,8 @@ class BillboardNode : public Node public: BillboardNode( Scene * scene, const QModelIndex & block ); - virtual const Transform & viewTrans() const; + const Transform & viewTrans() const override; }; -#endif - +#endif diff --git a/src/gl/glparticles.cpp b/src/gl/glparticles.cpp index a350b441e..cab04963a 100644 --- a/src/gl/glparticles.cpp +++ b/src/gl/glparticles.cpp @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -32,299 +32,13 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "glparticles.h" -#include "glcontroller.h" // Inherited + +#include "controllers.h" #include "glscene.h" #include -float random( float r ) -{ - return r * rand() / RAND_MAX; -} - -Vector3 random( Vector3 v ) -{ - v[0] *= random( 1.0 ); - v[1] *= random( 1.0 ); - v[2] *= random( 1.0 ); - return v; -} - -class ParticleController final : public Controller -{ - struct Particle - { - Vector3 position; - Vector3 velocity; - Vector3 unknown; - float lifetime; - float lifespan; - float lasttime; - short y; - short vertex; - - Particle() : lifetime( 0 ), lifespan( 0 ) - { - } - }; - QVector list; - struct Gravity - { - float force; - int type; - Vector3 position; - Vector3 direction; - }; - QVector grav; - - QPointer target; - - float emitStart, emitStop, emitRate, emitLast, emitAccu, emitMax; - QPointer emitNode; - Vector3 emitRadius; - - float spd, spdRnd; - float ttl, ttlRnd; - - float inc, incRnd; - float dec, decRnd; - - float size; - float grow; - float fade; - - float localtime; - - QList iExtras; - QPersistentModelIndex iColorKeys; - -public: - ParticleController( Particles * particles, const QModelIndex & index ) - : Controller( index ), target( particles ) - { - } - - bool update( const NifModel * nif, const QModelIndex & index ) override final - { - if ( !target ) - return false; - - if ( Controller::update( nif, index ) || ( index.isValid() && iExtras.contains( index ) ) ) { - emitNode = target->scene->getNode( nif, nif->getBlock( nif->getLink( iBlock, "Emitter" ) ) ); - emitStart = nif->get( iBlock, "Emit Start Time" ); - emitStop = nif->get( iBlock, "Emit Stop Time" ); - emitRate = nif->get( iBlock, "Emit Rate" ); - emitRadius = nif->get( iBlock, "Start Random" ); - emitAccu = 0; - emitLast = emitStart; - - spd = nif->get( iBlock, "Speed" ); - spdRnd = nif->get( iBlock, "Speed Random" ); - - ttl = nif->get( iBlock, "Lifetime" ); - ttlRnd = nif->get( iBlock, "Lifetime Random" ); - - inc = nif->get( iBlock, "Vertical Direction" ); - incRnd = nif->get( iBlock, "Vertical Angle" ); - - dec = nif->get( iBlock, "Horizontal Direction" ); - decRnd = nif->get( iBlock, "Horizontal Angle" ); - - size = nif->get( iBlock, "Size" ); - grow = 0.0; - fade = 0.0; - - list.clear(); - - QModelIndex iParticles = nif->getIndex( iBlock, "Particles" ); - - if ( iParticles.isValid() ) { - emitMax = nif->get( iBlock, "Num Particles" ); - int active = nif->get( iBlock, "Num Valid" ); - - //iParticles = nif->getIndex( iParticles, "Particles" ); - //if ( iParticles.isValid() ) - //{ - for ( int p = 0; p < active && p < nif->rowCount( iParticles ); p++ ) { - Particle particle; - particle.velocity = nif->get( iParticles.child( p, 0 ), "Velocity" ); - particle.lifetime = nif->get( iParticles.child( p, 0 ), "Lifetime" ); - particle.lifespan = nif->get( iParticles.child( p, 0 ), "Lifespan" ); - particle.lasttime = nif->get( iParticles.child( p, 0 ), "Timestamp" ); - particle.vertex = nif->get( iParticles.child( p, 0 ), "Vertex ID" ); - // Display saved particle start on initial load - list.append( particle ); - } - - //} - } - - if ( ( nif->get( iBlock, "Emit Flags" ) & 1 ) == 0 ) { - emitRate = emitMax / ( ttl + ttlRnd / 2 ); - } - - iExtras.clear(); - grav.clear(); - iColorKeys = QModelIndex(); - QModelIndex iExtra = nif->getBlock( nif->getLink( iBlock, "Particle Extra" ) ); - - while ( iExtra.isValid() ) { - iExtras.append( iExtra ); - - QString name = nif->itemName( iExtra ); - - if ( name == "NiParticleGrowFade" ) { - grow = nif->get( iExtra, "Grow" ); - fade = nif->get( iExtra, "Fade" ); - } else if ( name == "NiParticleColorModifier" ) { - iColorKeys = nif->getIndex( nif->getBlock( nif->getLink( iExtra, "Color Data" ), "NiColorData" ), "Data" ); - } else if ( name == "NiGravity" ) { - Gravity g; - g.force = nif->get( iExtra, "Force" ); - g.type = nif->get( iExtra, "Type" ); - g.position = nif->get( iExtra, "Position" ); - g.direction = nif->get( iExtra, "Direction" ); - grav.append( g ); - } - - iExtra = nif->getBlock( nif->getLink( iExtra, "Next Modifier" ) ); - } - - return true; - } - - return false; - } - - void update( float time ) override final - { - if ( !( target && active ) ) - return; - - localtime = ctrlTime( time ); - - int n = 0; - - while ( n < list.count() ) { - Particle & p = list[n]; - - float deltaTime = ( localtime > p.lasttime ? localtime - p.lasttime : 0 ); //( stop - start ) - p.lasttime + localtime ); - - p.lifetime += deltaTime; - - if ( p.lifetime < p.lifespan && p.vertex < target->verts.count() ) { - p.position = target->verts[ p.vertex ]; - - for ( int i = 0; i < 4; i++ ) - moveParticle( p, deltaTime / 4 ); - - p.lasttime = localtime; - n++; - } else { - list.remove( n ); - } - } - - if ( emitNode && emitNode->isVisible() && localtime >= emitStart && localtime <= emitStop ) { - float emitDelta = ( localtime > emitLast ? localtime - emitLast : 0 ); - emitLast = localtime; - - emitAccu += emitDelta * emitRate; - - int num = int(emitAccu); - - if ( num > 0 ) { - emitAccu -= num; - - while ( num-- > 0 && list.count() < target->verts.count() ) { - Particle p; - startParticle( p ); - list.append( p ); - } - } - } - - n = 0; - - while ( n < list.count() ) { - Particle & p = list[n]; - p.vertex = n; - target->verts[ n ] = p.position; - - if ( n < target->sizes.count() ) - sizeParticle( p, target->sizes[n] ); - - if ( n < target->colors.count() ) - colorParticle( p, target->colors[n] ); - - n++; - } - - target->active = list.count(); - target->size = size; - } - - void startParticle( Particle & p ) - { - p.position = random( emitRadius * 2 ) - emitRadius; - p.position += target->worldTrans().rotation.inverted() * ( emitNode->worldTrans().translation - target->worldTrans().translation ); - - float i = inc + random( incRnd ); - float d = dec + random( decRnd ); - - p.velocity = Vector3( rand() & 1 ? sin( i ) : -sin( i ), 0, cos( i ) ); - - Matrix m; m.fromEuler( 0, 0, rand() & 1 ? d : -d ); - p.velocity = m * p.velocity; - - p.velocity = p.velocity * ( spd + random( spdRnd ) ); - p.velocity = target->worldTrans().rotation.inverted() * emitNode->worldTrans().rotation * p.velocity; - - p.lifetime = 0; - p.lifespan = ttl + random( ttlRnd ); - p.lasttime = localtime; - } - - void moveParticle( Particle & p, float deltaTime ) - { - for ( Gravity g : grav ) { - switch ( g.type ) { - case 0: - p.velocity += g.direction * ( g.force * deltaTime ); - break; - case 1: - { - Vector3 dir = ( g.position - p.position ); - dir.normalize(); - p.velocity += dir * ( g.force * deltaTime ); - } - break; - } - } - p.position += p.velocity * deltaTime; - } - - void sizeParticle( Particle & p, float & size ) - { - size = 1.0; - - if ( grow > 0 && p.lifetime < grow ) - size *= p.lifetime / grow; - - if ( fade > 0 && p.lifespan - p.lifetime < fade ) - size *= ( p.lifespan - p.lifetime ) / fade; - } - - void colorParticle( Particle & p, Color4 & color ) - { - if ( iColorKeys.isValid() ) { - int i = 0; - interpolate( color, iColorKeys, p.lifetime / p.lifespan, i ); - } - } -}; - /* * Particle */ @@ -418,15 +132,17 @@ BoundSphere Particles::bounds() const return worldTrans() * sphere | Node::bounds(); } -void Particles::drawShapes( NodeList * draw2nd ) +void Particles::drawShapes( NodeList * secondPass, bool presort ) { + Q_UNUSED( presort ); + if ( isHidden() ) return; AlphaProperty * aprop = findProperty(); - if ( aprop && aprop->blend() && draw2nd ) { - draw2nd->add( this ); + if ( aprop && aprop->blend() && secondPass ) { + secondPass->add( this ); return; } diff --git a/src/gl/glparticles.h b/src/gl/glparticles.h index 42451903a..b98698cb9 100644 --- a/src/gl/glparticles.h +++ b/src/gl/glparticles.h @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -39,8 +39,12 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include +//! @file glparticles.h Particles + class Particles : public Node { + friend class ParticleController; + public: Particles( Scene * s, const QModelIndex & b ) : Node( s, b ) {} @@ -50,7 +54,7 @@ class Particles : public Node void transformShapes() override; - void drawShapes( NodeList * draw2nd = nullptr ) override; + void drawShapes( NodeList * secondPass = nullptr, bool presort = false ) override; BoundSphere bounds() const override; @@ -67,8 +71,7 @@ class Particles : public Node int active; float size; - - friend class ParticleController; }; + #endif diff --git a/src/gl/glproperty.cpp b/src/gl/glproperty.cpp index 70cfe29c4..1ad9702cb 100644 --- a/src/gl/glproperty.cpp +++ b/src/gl/glproperty.cpp @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -31,15 +31,15 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "glproperty.h" -#include "options.h" -#include "glcontroller.h" // Inherited +#include "controllers.h" #include "glscene.h" +#include "material.h" #include -//! \file glproperty.cpp Property, subclasses and controllers +//! @file glproperty.cpp Encapsulation of NiProperty blocks defined in nif.xml //! Helper function that checks texture sets bool checkSet( int s, const QList > & texcoords ) @@ -70,11 +70,13 @@ Property * Property::create( Scene * scene, const NifModel * nif, const QModelIn } else if ( nif->isNiBlock( index, "NiStencilProperty" ) ) { property = new StencilProperty( scene, index ); } else if ( nif->isNiBlock( index, "BSLightingShaderProperty" ) ) { - property = new BSShaderLightingProperty( scene, index ); + property = new BSLightingShaderProperty( scene, index ); } else if ( nif->isNiBlock( index, "BSShaderLightingProperty" ) ) { property = new BSShaderLightingProperty( scene, index ); } else if ( nif->isNiBlock( index, "BSEffectShaderProperty" ) ) { - property = new BSShaderLightingProperty( scene, index ); + property = new BSEffectShaderProperty( scene, index ); + } else if ( nif->isNiBlock( index, "BSWaterShaderProperty" ) ) { + property = new BSWaterShaderProperty( scene, index ); } else if ( nif->isNiBlock( index, "BSShaderNoLightingProperty" ) ) { property = new BSShaderLightingProperty( scene, index ); } else if ( nif->isNiBlock( index, "BSShaderPPLightingProperty" ) ) { @@ -83,9 +85,9 @@ Property * Property::create( Scene * scene, const NifModel * nif, const QModelIn NifItem * item = static_cast( index.internalPointer() ); if ( item ) - qWarning() << "Unknown property: " << item->name(); + qCWarning( nsNif ) << tr( "Unknown property: %1" ).arg( item->name() ); else - qWarning() << "Unknown property: I can't determine its name"; + qCWarning( nsNif ) << tr( "Unknown property: I can't determine its name" ); } if ( property ) @@ -150,7 +152,7 @@ void PropertyList::del( Property * p ) QHash::iterator i = properties.find( p->type() ); - while ( i != properties.end() && i.key() == p->type() ) { + while ( p && i != properties.end() && i.key() == p->type() ) { if ( i.value() == p ) { i = properties.erase( i ); @@ -222,27 +224,43 @@ void AlphaProperty::update( const NifModel * nif, const QModelIndex & block ) alphaThreshold = nif->get( iBlock, "Threshold" ) / 255.0; alphaSort = ( flags & 0x2000 ) == 0; + + // Temporary Weapon Blood fix for FO4 + if ( nif->getUserVersion2() == 130 ) + alphaTest |= (flags == 20547); } } +void AlphaProperty::setController( const NifModel * nif, const QModelIndex & controller ) +{ + if ( nif->itemName( controller ) == "BSNiAlphaPropertyTestRefController" ) { + Controller * ctrl = new AlphaController( this, controller ); + ctrl->update( nif, controller ); + controllers.append( ctrl ); + } +} + +void AlphaProperty::setThreshold( float threshold ) +{ + alphaThreshold = threshold; +} + void glProperty( AlphaProperty * p ) { - if ( p && p->alphaBlend && Options::blending() ) { + if ( p && p->alphaBlend && (p->scene->options & Scene::DoBlending) ) { + glDisable( GL_POLYGON_OFFSET_FILL ); glEnable( GL_BLEND ); glBlendFunc( p->alphaSrc, p->alphaDst ); } else { glDisable( GL_BLEND ); } - if ( p && p->alphaTest && Options::blending() ) { - //glEnable( GL_POLYGON_OFFSET_FILL ); - //glPolygonOffset( -1.0f, -1.0f ); + if ( p && p->alphaTest && (p->scene->options & Scene::DoBlending) ) { glDisable( GL_POLYGON_OFFSET_FILL ); glEnable( GL_ALPHA_TEST ); glAlphaFunc( p->alphaFunc, p->alphaThreshold ); } else { glDisable( GL_ALPHA_TEST ); - //glDisable( GL_POLYGON_OFFSET_FILL ); } } @@ -466,134 +484,6 @@ int TexturingProperty::coordSet( int id ) const return -1; } -//! Controller for source textures in a TexturingProperty -class TexFlipController final : public Controller -{ -public: - TexFlipController( TexturingProperty * prop, const QModelIndex & index ) - : Controller( index ), target( prop ), flipDelta( 0 ), flipSlot( 0 ) - { - } - - TexFlipController( TextureProperty * prop, const QModelIndex & index ) - : Controller( index ), oldTarget( prop ), flipDelta( 0 ), flipSlot( 0 ) - { - } - - void update( float time ) override final - { - const NifModel * nif = static_cast( iSources.model() ); - - if ( !( (target || oldTarget) && active && iSources.isValid() && nif ) ) - return; - - float r = 0; - - if ( iData.isValid() ) - interpolate( r, iData, "Data", ctrlTime( time ), flipLast ); - else if ( flipDelta > 0 ) - r = ctrlTime( time ) / flipDelta; - - // TexturingProperty - if ( target ) { - target->textures[flipSlot & 7 ].iSource = nif->getBlock( nif->getLink( iSources.child( (int)r, 0 ) ), "NiSourceTexture" ); - } else if ( oldTarget ) { - oldTarget->iImage = nif->getBlock( nif->getLink( iSources.child( (int)r, 0 ) ), "NiImage" ); - } - } - - bool update( const NifModel * nif, const QModelIndex & index ) override final - { - if ( Controller::update( nif, index ) ) { - flipDelta = nif->get( iBlock, "Delta" ); - flipSlot = nif->get( iBlock, "Texture Slot" ); - - if ( nif->checkVersion( 0x04000000, 0 ) ) { - iSources = nif->getIndex( iBlock, "Sources" ); - } else { - iSources = nif->getIndex( iBlock, "Images" ); - } - - return true; - } - - return false; - } - -protected: - QPointer target; - QPointer oldTarget; - - float flipDelta; - int flipSlot; - - int flipLast; - - QPersistentModelIndex iSources; -}; - -//! Controller for transformations in a TexturingProperty -class TexTransController final : public Controller -{ -public: - TexTransController( TexturingProperty * prop, const QModelIndex & index ) - : Controller( index ), target( prop ), texSlot( 0 ), texOP( 0 ) - { - } - - void update( float time ) override final - { - if ( !( target && active ) ) - return; - - TexturingProperty::TexDesc * tex = &target->textures[ texSlot & 7 ]; - - float val; - - if ( interpolate( val, iData, "Data", ctrlTime( time ), lX ) ) { - // If desired, we could force display even if texture transform was disabled: - // tex->hasTransform = true; - // however "Has Texture Transform" doesn't exist until 10.1.0.0, and neither does - // NiTextureTransformController - so we won't bother - switch ( texOP ) { - case 0: - tex->translation[0] = val; - break; - case 1: - tex->translation[1] = val; - break; - case 2: - tex->rotation = val; - break; - case 3: - tex->tiling[0] = val; - break; - case 4: - tex->tiling[1] = val; - break; - } - } - } - - bool update( const NifModel * nif, const QModelIndex & index ) override final - { - if ( Controller::update( nif, index ) ) { - texSlot = nif->get( iBlock, "Texture Slot" ); - texOP = nif->get( iBlock, "Operation" ); - return true; - } - - return false; - } - -protected: - QPointer target; - - int texSlot; - int texOP; - - int lX; -}; //! Set the appropriate Controller void TexturingProperty::setController( const NifModel * nif, const QModelIndex & iController ) @@ -629,7 +519,7 @@ int TexturingProperty::getId( const QString & texname ) void glProperty( TexturingProperty * p ) { - if ( p && Options::texturing() && p->bind( 0 ) ) { + if ( p && (p->scene->options & Scene::DoTexturing) && p->bind( 0 ) ) { glEnable( GL_TEXTURE_2D ); } } @@ -699,7 +589,7 @@ void TextureProperty::setController( const NifModel * nif, const QModelIndex & i void glProperty( TextureProperty * p ) { - if ( p && Options::texturing() && p->bind() ) { + if ( p && (p->scene->options & Scene::DoTexturing) && p->bind() ) { glEnable( GL_TEXTURE_2D ); } } @@ -728,120 +618,8 @@ void MaterialProperty::update( const NifModel * nif, const QModelIndex & index ) shininess = nif->get( index, "Glossiness" ); } - - // special case to force refresh of materials - bool overrideMaterials = Options::overrideMaterials(); - - if ( overridden && !overrideMaterials && iBlock.isValid() ) { - ambient = Color4( nif->get( iBlock, "Ambient Color" ) ); - diffuse = Color4( nif->get( iBlock, "Diffuse Color" ) ); - specular = Color4( nif->get( iBlock, "Specular Color" ) ); - emissive = Color4( nif->get( iBlock, "Emissive Color" ) ); - } else if ( overrideMaterials ) { - ambient = Color4( Options::overrideAmbient() ); - diffuse = Color4( Options::overrideDiffuse() ); - specular = Color4( Options::overrideSpecular() ); - emissive = Color4( Options::overrideEmissive() ); - } - - overridden = overrideMaterials; } -//! Controller for alpha values in a MaterialProperty -class AlphaController final : public Controller -{ -public: - AlphaController( MaterialProperty * prop, const QModelIndex & index ) - : Controller( index ), target( prop ), lAlpha( 0 ) - { - } - - void update( float time ) override final - { - if ( !( active && target ) ) - return; - - interpolate( target->alpha, iData, "Data", ctrlTime( time ), lAlpha ); - - if ( target->alpha < 0 ) - target->alpha = 0; - - if ( target->alpha > 1 ) - target->alpha = 1; - } - -protected: - QPointer target; - - int lAlpha; -}; - -//! Controller for color values in a MaterialProperty -class MaterialColorController final : public Controller -{ -public: - MaterialColorController( MaterialProperty * prop, const QModelIndex & index ) - : Controller( index ), target( prop ), lColor( 0 ), tColor( tAmbient ) - { - } - - void update( float time ) override final - { - if ( !( active && target ) ) - return; - - Vector3 v3; - interpolate( v3, iData, "Data", ctrlTime( time ), lColor ); - - Color4 color( Color3( v3 ), 1.0 ); - - switch ( tColor ) { - case tAmbient: - target->ambient = color; - break; - case tDiffuse: - target->diffuse = color; - break; - case tSpecular: - target->specular = color; - break; - case tSelfIllum: - target->emissive = color; - break; - } - } - - bool update( const NifModel * nif, const QModelIndex & index ) override final - { - if ( Controller::update( nif, index ) ) { - if ( nif->checkVersion( 0x0A010000, 0 ) ) { - tColor = nif->get( iBlock, "Target Color" ); - } else { - tColor = ( ( nif->get( iBlock, "Flags" ) >> 4 ) & 7 ); - } - - return true; - } - - return false; - } - -protected: - QPointer target; //!< The MaterialProperty being controlled - - int lColor; //!< Last interpolation time - int tColor; //!< The color slot being controlled - - //! Color slots that can be controlled - enum - { - tAmbient = 0, - tDiffuse = 1, - tSpecular = 2, - tSelfIllum = 3 - }; -}; - void MaterialProperty::setController( const NifModel * nif, const QModelIndex & iController ) { if ( nif->itemName( iController ) == "NiAlphaController" ) { @@ -855,6 +633,7 @@ void MaterialProperty::setController( const NifModel * nif, const QModelIndex & } } + void glProperty( MaterialProperty * p, SpecularProperty * s ) { if ( p ) { @@ -1037,17 +816,19 @@ void BSShaderLightingProperty::update( const NifModel * nif, const QModelIndex & // handle niobject name="BSEffectShaderProperty... if ( !iTextureSet.isValid() ) iSourceTexture = iBlock; + + iWetMaterial = nif->getIndex( iBlock, "Wet Material" ); } } void glProperty( BSShaderLightingProperty * p ) { - if ( p && Options::texturing() && p->bind( 0 ) ) { + if ( p && (p->scene->options & Scene::DoTexturing) && p->bind( 0 ) ) { glEnable( GL_TEXTURE_2D ); } } -bool BSShaderLightingProperty::bind( int id, const QString & fname ) +bool BSShaderLightingProperty::bind( int id, const QString & fname, TexClampMode mode ) { GLuint mipmaps = 0; @@ -1059,8 +840,32 @@ bool BSShaderLightingProperty::bind( int id, const QString & fname ) if ( mipmaps == 0 ) return false; - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT ); - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT ); + + switch ( mode ) + { + case TexClampMode::CLAMP_S_CLAMP_T: + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP ); + break; + case TexClampMode::CLAMP_S_WRAP_T: + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT ); + break; + case TexClampMode::WRAP_S_CLAMP_T: + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP ); + break; + case TexClampMode::MIRRORED_S_MIRRORED_T: + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT ); + break; + case TexClampMode::WRAP_S_WRAP_T: + default: + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT ); + break; + } + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, mipmaps > 1 ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR ); glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); @@ -1083,10 +888,79 @@ bool BSShaderLightingProperty::bind( int id, const QList > & te return false; } +bool BSShaderLightingProperty::bindCube( int id, const QString & fname ) +{ + Q_UNUSED( id ); + + GLuint result = 0; + + if ( !fname.isEmpty() ) + result = scene->bindTextureCube( fname ); + + if ( result == 0 ) + return false; + + glEnable( GL_TEXTURE_CUBE_MAP_SEAMLESS ); + + glTexParameteri( GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); + glTexParameteri( GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); + glTexParameteri( GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + glTexParameteri( GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR ); + glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + glMatrixMode( GL_TEXTURE ); + glLoadIdentity(); + glMatrixMode( GL_MODELVIEW ); + + return true; +} + QString BSShaderLightingProperty::fileName( int id ) const { - const NifModel * nif = qobject_cast( iTextureSet.model() ); + const NifModel * nif; + + // Fallout 4 + nif = qobject_cast(iWetMaterial.model()); + if ( nif ) { + // BSLSP + auto m = static_cast(material); + if ( m && m->isValid() ) { + auto tex = m->textures(); + if ( tex.count() == 9 ) { + switch ( id ) { + case 0: // Diffuse + if ( !tex[0].isEmpty() ) + return tex[0]; + break; + case 1: // Normal + if ( !tex[1].isEmpty() ) + return tex[1]; + break; + case 2: // Glow + if ( m->bGlowmap && !tex[5].isEmpty() ) + return tex[5]; + break; + case 3: // Greyscale + if ( m->bGrayscaleToPaletteColor && !tex[3].isEmpty() ) + return tex[3]; + break; + case 4: // Cubemap + if ( m->bEnvironmentMapping && !tex[4].isEmpty() ) + return tex[4]; + break; + case 5: // Env Mask + if ( m->bEnvironmentMapping && !tex[5].isEmpty() ) + return tex[5]; + break; + case 7: // Specular + if ( m->bSpecularEnabled && !tex[2].isEmpty() ) + return tex[2]; + break; + } + } + } + } + nif = qobject_cast(iTextureSet.model()); if ( nif && iTextureSet.isValid() ) { int nTextures = nif->get( iTextureSet, "Num Textures" ); QModelIndex iTextures = nif->getIndex( iTextureSet, "Textures" ); @@ -1095,10 +969,26 @@ QString BSShaderLightingProperty::fileName( int id ) const return nif->get( iTextures.child( id, 0 ) ); } else { // handle niobject name="BSEffectShaderProperty... - nif = qobject_cast( iSourceTexture.model() ); + auto m = static_cast(material); - if ( nif && iSourceTexture.isValid() ) - return nif->get( iSourceTexture, "Source Texture" ); + nif = qobject_cast(iSourceTexture.model()); + if ( !m && nif && iSourceTexture.isValid() ) { + switch ( id ) { + case 0: + return nif->get( iSourceTexture, "Source Texture" ); + case 1: + return nif->get( iSourceTexture, "Greyscale Texture" ); + case 2: + return nif->get( iSourceTexture, "Env Map Texture" ); + case 3: + return nif->get( iSourceTexture, "Normal Texture" ); + case 4: + return nif->get( iSourceTexture, "Env Mask Texture" ); + } + } else if ( m && m->isValid() ) { + auto tex = m->textures(); + return tex[id]; + } } return QString(); @@ -1120,3 +1010,581 @@ int BSShaderLightingProperty::getId( const QString & id ) return hash.value( id, -1 ); } +QPersistentModelIndex BSShaderLightingProperty::getTextureSet() const +{ + return iTextureSet; +} + +unsigned int BSShaderLightingProperty::getFlags1() const +{ + return (unsigned int)flags1; +} + +unsigned int BSShaderLightingProperty::getFlags2() const +{ + return (unsigned int)flags2; +} + +void BSShaderLightingProperty::setFlags1( unsigned int val ) +{ + flags1 = ShaderFlags::SF1( val ); +} + +void BSShaderLightingProperty::setFlags2( unsigned int val ) +{ + flags2 = ShaderFlags::SF2( val ); +} + +UVScale BSShaderLightingProperty::getUvScale() const +{ + return uvScale; +} + +UVOffset BSShaderLightingProperty::getUvOffset() const +{ + return uvOffset; +} + +void BSShaderLightingProperty::setUvScale( float x, float y ) +{ + uvScale.x = x; + uvScale.y = y; +} + +void BSShaderLightingProperty::setUvOffset( float x, float y ) +{ + uvOffset.x = x; + uvOffset.y = y; +} + +TexClampMode BSShaderLightingProperty::getClampMode() const +{ + return clampMode; +} + +void BSShaderLightingProperty::setClampMode( uint mode ) +{ + clampMode = TexClampMode( mode ); +} + +Material * BSShaderLightingProperty::mat() const +{ + return material; +} + +/* + BSLightingShaderProperty +*/ + +void BSLightingShaderProperty::update( const NifModel * nif, const QModelIndex & property ) +{ + BSShaderLightingProperty::update( nif, property ); + + if ( name.endsWith( ".bgsm", Qt::CaseInsensitive ) ) + material = new ShaderMaterial( name ); + + if ( material && !material->isValid() ) + material = nullptr; + + if ( material && name.isEmpty() ) { + delete material; + material = nullptr; + } + +} + +void BSLightingShaderProperty::updateParams( const NifModel * nif, const QModelIndex & prop ) +{ + ShaderMaterial * m = nullptr; + if ( mat() && mat()->isValid() ) + m = static_cast(mat()); + + auto stream = nif->getUserVersion2(); + auto textures = nif->getArray( getTextureSet(), "Textures" ); + + setShaderType( nif->get( prop, "Skyrim Shader Type" ) ); + setFlags1( nif->get( prop, "Shader Flags 1" ) ); + setFlags2( nif->get( prop, "Shader Flags 2" ) ); + + hasVertexAlpha = hasSF1( ShaderFlags::SLSF1_Vertex_Alpha ); + hasVertexColors = hasSF2( ShaderFlags::SLSF2_Vertex_Colors ); + + if ( !m ) { + alpha = nif->get( prop, "Alpha" ); + + auto uvScale = nif->get( prop, "UV Scale" ); + auto uvOffset = nif->get( prop, "UV Offset" ); + + setUvScale( uvScale[0], uvScale[1] ); + setUvOffset( uvOffset[0], uvOffset[1] ); + setClampMode( nif->get( prop, "Texture Clamp Mode" ) ); + + // Specular + if ( hasSF1( ShaderFlags::SLSF1_Specular ) ) { + auto spC = nif->get( prop, "Specular Color" ); + auto spG = nif->get( prop, "Glossiness" ); + auto spS = nif->get( prop, "Specular Strength" ); + setSpecular( spC, spG, spS ); + } else { + setSpecular( Color3( 0, 0, 0 ), 0, 0 ); + } + + // Emissive + setEmissive( nif->get( prop, "Emissive Color" ), nif->get( prop, "Emissive Multiple" ) ); + + hasEmittance = hasSF1( ShaderFlags::SLSF1_Own_Emit ); + if ( getShaderType() & ShaderFlags::ST_GlowShader ) + hasGlowMap = hasSF2( ShaderFlags::SLSF2_Glow_Map ) && !textures.value( 2, "" ).isEmpty(); + + // Version Dependent settings + if ( stream < 130 ) { + lightingEffect1 = nif->get( prop, "Lighting Effect 1" ); + lightingEffect2 = nif->get( prop, "Lighting Effect 2" ); + + innerThickness = nif->get( prop, "Parallax Inner Layer Thickness" ); + outerRefractionStrength = nif->get( prop, "Parallax Refraction Scale" ); + outerReflectionStrength = nif->get( prop, "Parallax Envmap Strength" ); + auto innerScale = nif->get( prop, "Parallax Inner Layer Texture Scale" ); + setInnerTextureScale( innerScale[0], innerScale[1] ); + + hasSpecularMap = hasSF1( ShaderFlags::SLSF1_Specular ) && !textures.value( 7, "" ).isEmpty(); + hasHeightMap = isST( ShaderFlags::ST_Heightmap ) && hasSF1( ShaderFlags::SLSF1_Parallax ) && !textures.value( 3, "" ).isEmpty(); + hasBacklight = hasSF2( ShaderFlags::SLSF2_Back_Lighting ); + hasRimlight = hasSF2( ShaderFlags::SLSF2_Rim_Lighting ); + hasSoftlight = hasSF2( ShaderFlags::SLSF2_Soft_Lighting ); + hasModelSpaceNormals = hasSF1( ShaderFlags::SLSF1_Model_Space_Normals ); + hasMultiLayerParallax = hasSF2( ShaderFlags::SLSF2_Multi_Layer_Parallax ); + + hasRefraction = hasSF1( ShaderFlags::SLSF1_Refraction ); + hasFireRefraction = hasSF1( ShaderFlags::SLSF1_Fire_Refraction ); + + hasTintColor = false; + hasTintMask = isST( ShaderFlags::ST_FaceTint ); + hasDetailMask = hasTintMask; + + QString tint; + if ( isST( ShaderFlags::ST_HairTint ) ) + tint = "Hair Tint Color"; + else if ( isST( ShaderFlags::ST_SkinTint ) ) + tint = "Skin Tint Color"; + + if ( !tint.isEmpty() ) { + hasTintColor = true; + setTintColor( nif->get( prop, tint ) ); + } + } else { + hasSpecularMap = hasSF1( ShaderFlags::SLSF1_Specular ); + greyscaleColor = hasSF1( ShaderFlags::SLSF1_Greyscale_To_PaletteColor ); + paletteScale = nif->get( prop, "Grayscale to Palette Scale" ); + lightingEffect1 = nif->get( prop, "Subsurface Rolloff" ); + backlightPower = nif->get( prop, "Backlight Power" ); + fresnelPower = nif->get( prop, "Fresnel Power" ); + } + + // Environment Map, Mask and Reflection Scale + hasEnvironmentMap = isST( ShaderFlags::ST_EnvironmentMap ) && hasSF1( ShaderFlags::SLSF1_Environment_Mapping ); + hasEnvironmentMap |= isST( ShaderFlags::ST_EyeEnvmap ) && hasSF1( ShaderFlags::SLSF1_Eye_Environment_Mapping ); + if ( stream == 100 ) + hasEnvironmentMap |= hasMultiLayerParallax; + + hasCubeMap = ( + isST( ShaderFlags::ST_EnvironmentMap ) + || isST( ShaderFlags::ST_EyeEnvmap ) + || isST( ShaderFlags::ST_MultiLayerParallax ) + ) + && hasEnvironmentMap + && !textures.value( 4, "" ).isEmpty(); + + useEnvironmentMask = hasEnvironmentMap && !textures.value( 5, "" ).isEmpty(); + + if ( isST( ShaderFlags::ST_EnvironmentMap ) ) + environmentReflection = nif->get( prop, "Environment Map Scale" ); + else if ( isST( ShaderFlags::ST_EyeEnvmap ) ) + environmentReflection = nif->get( prop, "Eye Cubemap Scale" ); + + } else { + alpha = m->fAlpha; + + setUvScale( m->fUScale, m->fVScale ); + setUvOffset( m->fUOffset, m->fVOffset ); + setSpecular( m->cSpecularColor, m->fSmoothness, m->fSpecularMult ); + setEmissive( m->cEmittanceColor, m->fEmittanceMult ); + + if ( m->bTileU && m->bTileV ) + clampMode = TexClampMode::WRAP_S_WRAP_T; + else if ( m->bTileU ) + clampMode = TexClampMode::WRAP_S_CLAMP_T; + else if ( m->bTileV ) + clampMode = TexClampMode::CLAMP_S_WRAP_T; + else + clampMode = TexClampMode::CLAMP_S_CLAMP_T; + + fresnelPower = m->fFresnelPower; + greyscaleColor = m->bGrayscaleToPaletteColor; + paletteScale = m->fGrayscaleToPaletteScale; + + hasSpecularMap = m->bSpecularEnabled && !m->textureList[2].isEmpty(); + hasGlowMap = m->bGlowmap; + hasEmittance = m->bEmitEnabled; + hasBacklight = m->bBackLighting; + hasRimlight = m->bRimLighting; + hasSoftlight = m->bSubsurfaceLighting; + rimPower = m->fRimPower; + backlightPower = m->fBacklightPower; + + + hasEnvironmentMap = m->bEnvironmentMapping; + hasCubeMap = m->bEnvironmentMapping && !m->textureList[4].isEmpty(); + useEnvironmentMask = hasEnvironmentMap && !m->bGlowmap && !m->textureList[5].isEmpty(); + environmentReflection = m->fEnvironmentMappingMaskScale; + + if ( hasSoftlight ) + setLightingEffect1( m->fSubsurfaceLightingRolloff ); + } +} + +void BSLightingShaderProperty::setController( const NifModel * nif, const QModelIndex & iController ) +{ + if ( nif->itemName( iController ) == "BSLightingShaderPropertyFloatController" ) { + Controller * ctrl = new LightingFloatController( this, iController ); + ctrl->update( nif, iController ); + controllers.append( ctrl ); + } else if ( nif->itemName( iController ) == "BSLightingShaderPropertyColorController" ) { + Controller * ctrl = new LightingColorController( this, iController ); + ctrl->update( nif, iController ); + controllers.append( ctrl ); + } +} + +void BSLightingShaderProperty::setShaderType( unsigned int t ) +{ + shaderType = ShaderFlags::ShaderType( t ); +} + +ShaderFlags::ShaderType BSLightingShaderProperty::getShaderType() +{ + return shaderType; +} + +void BSLightingShaderProperty::setEmissive( Color3 color, float mult ) +{ + emissiveColor = color; + emissiveMult = mult; +} + +void BSLightingShaderProperty::setSpecular( Color3 color, float gloss, float strength ) +{ + specularColor = color; + specularGloss = gloss; + specularStrength = strength; +} + +Color3 BSLightingShaderProperty::getEmissiveColor() const +{ + return emissiveColor; +} + +Color3 BSLightingShaderProperty::getSpecularColor() const +{ + return specularColor; +} + +float BSLightingShaderProperty::getEmissiveMult() const +{ + return emissiveMult; +} + +float BSLightingShaderProperty::getLightingEffect1() const +{ + return lightingEffect1; +} + +float BSLightingShaderProperty::getLightingEffect2() const +{ + return lightingEffect2; +} + +void BSLightingShaderProperty::setLightingEffect1( float val ) +{ + lightingEffect1 = val; +} + +void BSLightingShaderProperty::setLightingEffect2( float val ) +{ + lightingEffect2 = val; +} + +float BSLightingShaderProperty::getSpecularGloss() const +{ + return specularGloss; +} + +float BSLightingShaderProperty::getSpecularStrength() const +{ + return specularStrength; +} + +float BSLightingShaderProperty::getInnerThickness() const +{ + return innerThickness; +} + +UVScale BSLightingShaderProperty::getInnerTextureScale() const +{ + return innerTextureScale; +} + +float BSLightingShaderProperty::getOuterRefractionStrength() const +{ + return outerRefractionStrength; +} + +float BSLightingShaderProperty::getOuterReflectionStrength() const +{ + return outerReflectionStrength; +} + +void BSLightingShaderProperty::setInnerThickness( float thickness ) +{ + innerThickness = thickness; +} + +void BSLightingShaderProperty::setInnerTextureScale( float x, float y ) +{ + innerTextureScale.x = x; + innerTextureScale.y = y; +} + +void BSLightingShaderProperty::setOuterRefractionStrength( float strength ) +{ + outerRefractionStrength = strength; +} + +void BSLightingShaderProperty::setOuterReflectionStrength( float strength ) +{ + outerReflectionStrength = strength; +} + +float BSLightingShaderProperty::getEnvironmentReflection() const +{ + return environmentReflection; +} + +void BSLightingShaderProperty::setEnvironmentReflection( float strength ) +{ + environmentReflection = strength; +} + +float BSLightingShaderProperty::getAlpha() const +{ + return alpha; +} + +void BSLightingShaderProperty::setAlpha( float opacity ) +{ + alpha = opacity; +} + +Color3 BSLightingShaderProperty::getTintColor() const +{ + return tintColor; +} + +void BSLightingShaderProperty::setTintColor( Color3 c ) +{ + tintColor = c; +} + +/* + BSEffectShaderProperty +*/ + +void BSEffectShaderProperty::update( const NifModel * nif, const QModelIndex & property ) +{ + BSShaderLightingProperty::update( nif, property ); + + if ( name.endsWith( ".bgem", Qt::CaseInsensitive ) ) + material = new EffectMaterial( name ); + + if ( material && !material->isValid() ) + material = nullptr; + + if ( material && name.isEmpty() ) { + delete material; + material = nullptr; + } +} + +void BSEffectShaderProperty::updateParams( const NifModel * nif, const QModelIndex & prop ) +{ + EffectMaterial * m = nullptr; + if ( mat() && mat()->isValid() ) + m = static_cast(mat()); + + auto stream = nif->getUserVersion2(); + + setFlags1( nif->get( prop, "Shader Flags 1" ) ); + setFlags2( nif->get( prop, "Shader Flags 2" ) ); + + vertexAlpha = hasSF1( ShaderFlags::SLSF1_Vertex_Alpha ); + vertexColors = hasSF2( ShaderFlags::SLSF2_Vertex_Colors ); + + if ( !m ) { + setEmissive( nif->get( prop, "Emissive Color" ), nif->get( prop, "Emissive Multiple" ) ); + + hasSourceTexture = !nif->get( prop, "Source Texture" ).isEmpty(); + hasGreyscaleMap = !nif->get( prop, "Greyscale Texture" ).isEmpty(); + + greyscaleAlpha = hasSF1( ShaderFlags::SLSF1_Greyscale_To_PaletteAlpha ); + greyscaleColor = hasSF1( ShaderFlags::SLSF1_Greyscale_To_PaletteColor ); + useFalloff = hasSF1( ShaderFlags::SLSF1_Use_Falloff ); + + depthTest = hasSF1( ShaderFlags::SLSF1_ZBuffer_Test ); + depthWrite = hasSF2( ShaderFlags::SLSF2_ZBuffer_Write ); + isDoubleSided = hasSF2( ShaderFlags::SLSF2_Double_Sided ); + + if ( stream < 130 ) { + hasWeaponBlood = hasSF2( ShaderFlags::SLSF2_Weapon_Blood ); + } else { + hasEnvMap = !nif->get( prop, "Env Map Texture" ).isEmpty(); + hasNormalMap = !nif->get( prop, "Normal Texture" ).isEmpty(); + hasEnvMask = !nif->get( prop, "Env Mask Texture" ).isEmpty(); + + environmentReflection = nif->get( prop, "Environment Map Scale" ); + } + + auto uvScale = nif->get( prop, "UV Scale" ); + auto uvOffset = nif->get( prop, "UV Offset" ); + + setUvScale( uvScale[0], uvScale[1] ); + setUvOffset( uvOffset[0], uvOffset[1] ); + setClampMode( nif->get( prop, "Texture Clamp Mode" ) ); + + if ( hasSF2( ShaderFlags::SLSF2_Effect_Lighting ) ) + lightingInfluence = (float)nif->get( prop, "Lighting Influence" ) / 255.0; + + auto startA = nif->get( prop, "Falloff Start Angle" ); + auto stopA = nif->get( prop, "Falloff Stop Angle" ); + auto startO = nif->get( prop, "Falloff Start Opacity" ); + auto stopO = nif->get( prop, "Falloff Stop Opacity" ); + auto soft = nif->get( prop, "Soft Falloff Depth" ); + + setFalloff( startA, stopA, startO, stopO, soft ); + + } else { + + setEmissive( Color4( m->cBaseColor, m->fAlpha ), m->fBaseColorScale ); + + hasSourceTexture = !m->textureList[0].isEmpty(); + hasGreyscaleMap = !m->textureList[1].isEmpty(); + hasEnvMap = !m->textureList[2].isEmpty(); + hasNormalMap = !m->textureList[3].isEmpty(); + hasEnvMask = !m->textureList[4].isEmpty(); + + environmentReflection = m->fEnvironmentMappingMaskScale; + + greyscaleAlpha = m->bGrayscaleToPaletteAlpha; + greyscaleColor = m->bGrayscaleToPaletteColor; + useFalloff = m->bFalloffEnabled; + + depthTest = m->bZBufferTest; + depthWrite = m->bZBufferWrite; + isDoubleSided = m->bTwoSided; + + setUvScale( m->fUScale, m->fVScale ); + setUvOffset( m->fUOffset, m->fVOffset ); + + if ( m->bTileU && m->bTileV ) + clampMode = TexClampMode::WRAP_S_WRAP_T; + else if ( m->bTileU ) + clampMode = TexClampMode::WRAP_S_CLAMP_T; + else if ( m->bTileV ) + clampMode = TexClampMode::CLAMP_S_WRAP_T; + else + clampMode = TexClampMode::CLAMP_S_CLAMP_T; + + if ( m->bEffectLightingEnabled ) + lightingInfluence = m->fLightingInfluence; + + setFalloff( m->fFalloffStartAngle, m->fFalloffStopAngle, + m->fFalloffStartOpacity, m->fFalloffStopOpacity, m->fSoftDepth ); + } +} + +void BSEffectShaderProperty::setController( const NifModel * nif, const QModelIndex & iController ) +{ + if ( nif->itemName( iController ) == "BSEffectShaderPropertyFloatController" ) { + Controller * ctrl = new EffectFloatController( this, iController ); + ctrl->update( nif, iController ); + controllers.append( ctrl ); + } else if ( nif->itemName( iController ) == "BSEffectShaderPropertyColorController" ) { + Controller * ctrl = new EffectColorController( this, iController ); + ctrl->update( nif, iController ); + controllers.append( ctrl ); + } +} + +void BSEffectShaderProperty::setEmissive( Color4 color, float mult ) +{ + emissiveColor = color; + emissiveMult = mult; +} + +Color4 BSEffectShaderProperty::getEmissiveColor() const +{ + return emissiveColor; +} + +float BSEffectShaderProperty::getEmissiveMult() const +{ + return emissiveMult; +} + +float BSEffectShaderProperty::getAlpha() const +{ + return emissiveColor.alpha(); +} + +void BSEffectShaderProperty::setFalloff( float startA, float stopA, float startO, float stopO, float soft ) +{ + falloff.startAngle = startA; + falloff.stopAngle = stopA; + falloff.startOpacity = startO; + falloff.stopOpacity = stopO; + falloff.softDepth = soft; +} + +float BSEffectShaderProperty::getEnvironmentReflection() const +{ + return environmentReflection; +} + +void BSEffectShaderProperty::setEnvironmentReflection( float strength ) +{ + environmentReflection = strength; +} + +float BSEffectShaderProperty::getLightingInfluence() const +{ + return lightingInfluence; +} + +void BSEffectShaderProperty::setLightingInfluence( float strength ) +{ + lightingInfluence = strength; +} + +/* + BSWaterShaderProperty +*/ + +unsigned int BSWaterShaderProperty::getWaterShaderFlags() const +{ + return (unsigned int)waterShaderFlags; +} + +void BSWaterShaderProperty::setWaterShaderFlags( unsigned int val ) +{ + waterShaderFlags = WaterShaderFlags::SF1( val ); +} + diff --git a/src/gl/glproperty.h b/src/gl/glproperty.h index 7d7f43ab4..ad2bafa1a 100644 --- a/src/gl/glproperty.h +++ b/src/gl/glproperty.h @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -33,44 +33,46 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef GLPROPERTY_H #define GLPROPERTY_H -#include "glcontrolable.h" // Inherited +#include "icontrollable.h" // Inherited #include #include #include +//! @file glproperty.h Property, PropertyList + typedef unsigned int GLenum; typedef int GLint; typedef unsigned int GLuint; typedef float GLfloat; -//! \file glproperty.h Property classes + +class Material; //! Controllable properties attached to nodes and meshes -class Property : public Controllable +class Property : public IControllable { + friend class PropertyList; + protected: - //! Protected constructor; see Controllable() - Property( Scene * scene, const QModelIndex & index ) : Controllable( scene, index ), ref( 0 ) {} + //! Protected constructor; see IControllable() + Property( Scene * scene, const QModelIndex & index ) : IControllable( scene, index ), ref( 0 ) {} int ref; - //! List of properties - friend class PropertyList; - public: - //! Creates a Property based on the specified index of the specified model - /*! + /*! Creates a Property based on the specified index of the specified model + * * @param scene The Scene the property is in - * @param nif The model + * @param nif The model * @param index The index NiProperty */ static Property * create( Scene * scene, const NifModel * nif, const QModelIndex & index ); enum Type { - Alpha, ZBuffer, Material, Texturing, Texture, Specular, Wireframe, VertexColor, Stencil, ShaderLighting + Alpha, ZBuffer, MaterialProp, Texturing, Texture, Specular, Wireframe, VertexColor, Stencil, ShaderLighting }; virtual Type type() const = 0; @@ -82,14 +84,14 @@ class Property : public Controllable if ( type() == _type() ) return static_cast( this ); - return 0; + return nullptr; } }; //! Associate a Property subclass with a Property::Type #define REGISTER_PROPERTY( CLASSNAME, TYPENAME ) template <> inline Property::Type Property::_type() { return Property::TYPENAME; } -//! A list of \link Property Properties \endlink +//! A list of [Properties](@ref Property) class PropertyList final { public: @@ -127,7 +129,7 @@ template inline T * PropertyList::get() const if ( p ) return p->cast(); - return 0; + return nullptr; } template inline bool PropertyList::contains() const @@ -144,15 +146,20 @@ class AlphaProperty final : public Property Type type() const override final { return Alpha; } QString typeId() const override final { return "NiAlphaProperty"; } - void update( const NifModel * nif, const QModelIndex & block ); + void update( const NifModel * nif, const QModelIndex & block ) override final; bool blend() const { return alphaBlend; } bool test() const { return alphaTest; } bool sort() const { return alphaSort; } + float threshold() const { return alphaThreshold; } + + void setThreshold( float ); friend void glProperty( AlphaProperty * ); protected: + void setController( const NifModel * nif, const QModelIndex & controller ) override final; + bool alphaBlend, alphaTest, alphaSort; GLenum alphaSrc, alphaDst, alphaFunc; GLfloat alphaThreshold; @@ -169,7 +176,7 @@ class ZBufferProperty final : public Property Type type() const override final { return ZBuffer; } QString typeId() const override final { return "NiZBufferProperty"; } - void update( const NifModel * nif, const QModelIndex & block ); + void update( const NifModel * nif, const QModelIndex & block ) override final; bool test() const { return depthTest; } bool mask() const { return depthMask; } @@ -190,6 +197,9 @@ REGISTER_PROPERTY( ZBufferProperty, ZBuffer ) //! A Property that specifies (multi-)texturing class TexturingProperty final : public Property { + friend class TexFlipController; + friend class TexTransController; + //! The properties of each texture slot struct TexDesc { @@ -212,7 +222,7 @@ class TexturingProperty final : public Property Type type() const override final { return Texturing; } QString typeId() const override final { return "NiTexturingProperty"; } - void update( const NifModel * nif, const QModelIndex & block ); + void update( const NifModel * nif, const QModelIndex & block ) override final; friend void glProperty( TexturingProperty * ); @@ -229,10 +239,7 @@ class TexturingProperty final : public Property protected: TexDesc textures[numTextures]; - void setController( const NifModel * nif, const QModelIndex & controller ); - - friend class TexFlipController; - friend class TexTransController; + void setController( const NifModel * nif, const QModelIndex & controller ) override final; }; REGISTER_PROPERTY( TexturingProperty, Texturing ) @@ -240,13 +247,15 @@ REGISTER_PROPERTY( TexturingProperty, Texturing ) //! A Property that specifies a texture class TextureProperty final : public Property { + friend class TexFlipController; + public: TextureProperty( Scene * scene, const QModelIndex & index ) : Property( scene, index ) {} Type type() const override final { return Texture; } QString typeId() const override final { return "NiTextureProperty"; } - void update( const NifModel * nif, const QModelIndex & block ); + void update( const NifModel * nif, const QModelIndex & block ) override final; friend void glProperty( TextureProperty * ); @@ -258,9 +267,7 @@ class TextureProperty final : public Property protected: QPersistentModelIndex iImage; - void setController( const NifModel * nif, const QModelIndex & controller ); - - friend class TexFlipController; + void setController( const NifModel * nif, const QModelIndex & controller ) override final; }; REGISTER_PROPERTY( TextureProperty, Texture ) @@ -268,13 +275,16 @@ REGISTER_PROPERTY( TextureProperty, Texture ) //! A Property that specifies a material class MaterialProperty final : public Property { + friend class AlphaController; + friend class MaterialColorController; + public: MaterialProperty( Scene * scene, const QModelIndex & index ) : Property( scene, index ) {} - Type type() const override final { return Material; } + Type type() const override final { return MaterialProp; } QString typeId() const override final { return "NiMaterialProperty"; } - void update( const NifModel * nif, const QModelIndex & block ); + void update( const NifModel * nif, const QModelIndex & block ) override final; friend void glProperty( class MaterialProperty *, class SpecularProperty * ); @@ -285,13 +295,10 @@ class MaterialProperty final : public Property GLfloat shininess, alpha; bool overridden; - void setController( const NifModel * nif, const QModelIndex & controller ); - - friend class AlphaController; - friend class MaterialColorController; + void setController( const NifModel * nif, const QModelIndex & controller ) override final; }; -REGISTER_PROPERTY( MaterialProperty, Material ) +REGISTER_PROPERTY( MaterialProperty, MaterialProp ) //! A Property that specifies specularity class SpecularProperty final : public Property @@ -302,7 +309,7 @@ class SpecularProperty final : public Property Type type() const override final { return Specular; } QString typeId() const override final { return "NiSpecularProperty"; } - void update( const NifModel * nif, const QModelIndex & index ); + void update( const NifModel * nif, const QModelIndex & index ) override final; friend void glProperty( class MaterialProperty *, class SpecularProperty * ); @@ -321,7 +328,7 @@ class WireframeProperty final : public Property Type type() const override final { return Wireframe; } QString typeId() const override final { return "NiWireframeProperty"; } - void update( const NifModel * nif, const QModelIndex & index ); + void update( const NifModel * nif, const QModelIndex & index ) override final; friend void glProperty( WireframeProperty * ); @@ -340,7 +347,7 @@ class VertexColorProperty final : public Property Type type() const override final { return VertexColor; } QString typeId() const override final { return "NiVertexColorProperty"; } - void update( const NifModel * nif, const QModelIndex & index ); + void update( const NifModel * nif, const QModelIndex & index ) override final; friend void glProperty( VertexColorProperty *, bool vertexcolors ); @@ -360,7 +367,7 @@ class StencilProperty final : public Property Type type() const override final { return Stencil; } QString typeId() const override final { return "NiStencilProperty"; } - void update( const NifModel * nif, const QModelIndex & index ); + void update( const NifModel * nif, const QModelIndex & index ) override final; friend void glProperty( StencilProperty * ); @@ -381,35 +388,428 @@ class StencilProperty final : public Property REGISTER_PROPERTY( StencilProperty, Stencil ) + +namespace ShaderFlags +{ + enum SF1 : unsigned int + { + SLSF1_Specular = 1, // 0! + SLSF1_Skinned = 1 << 1, // 1 + SLSF1_Temp_Refraction = 1 << 2, // 2 + SLSF1_Vertex_Alpha = 1 << 3, // 3 + SLSF1_Greyscale_To_PaletteColor = 1 << 4, // 4 + SLSF1_Greyscale_To_PaletteAlpha = 1 << 5, // 5 + SLSF1_Use_Falloff = 1 << 6, // 6 + SLSF1_Environment_Mapping = 1 << 7, // 7 + SLSF1_Recieve_Shadows = 1 << 8, // 8 + SLSF1_Cast_Shadows = 1 << 9, // 9 + SLSF1_Facegen_Detail_Map = 1 << 10, // 10 + SLSF1_Parallax = 1 << 11, // 11 + SLSF1_Model_Space_Normals = 1 << 12, // 12 + SLSF1_Non_Projective_Shadows = 1 << 13, // 13 + SLSF1_Landscape = 1 << 14, // 14 + SLSF1_Refraction = 1 << 15, // 15! + SLSF1_Fire_Refraction = 1 << 16, // 16 + SLSF1_Eye_Environment_Mapping = 1 << 17, // 17 + SLSF1_Hair_Soft_Lighting = 1 << 18, // 18 + SLSF1_Screendoor_Alpha_Fade = 1 << 19, // 19 + SLSF1_Localmap_Hide_Secret = 1 << 20, // 20 + SLSF1_FaceGen_RGB_Tint = 1 << 21, // 21 + SLSF1_Own_Emit = 1 << 22, // 22 + SLSF1_Projected_UV = 1 << 23, // 23 + SLSF1_Multiple_Textures = 1 << 24, // 24 + SLSF1_Remappable_Textures = 1 << 25, // 25 + SLSF1_Decal = 1 << 26, // 26 + SLSF1_Dynamic_Decal = 1 << 27, // 27 + SLSF1_Parallax_Occlusion = 1 << 28, // 28 + SLSF1_External_Emittance = 1 << 29, // 29 + SLSF1_Soft_Effect = 1 << 30, // 30 + SLSF1_ZBuffer_Test = (unsigned int)(1 << 31), // 31 + }; + + enum SF2 : unsigned int + { + SLSF2_ZBuffer_Write = 1, // 0! + SLSF2_LOD_Landscape = 1 << 1, // 1 + SLSF2_LOD_Objects = 1 << 2, // 2 + SLSF2_No_Fade = 1 << 3, // 3 + SLSF2_Double_Sided = 1 << 4, // 4 + SLSF2_Vertex_Colors = 1 << 5, // 5 + SLSF2_Glow_Map = 1 << 6, // 6 + SLSF2_Assume_Shadowmask = 1 << 7, // 7 + SLSF2_Packed_Tangent = 1 << 8, // 8 + SLSF2_Multi_Index_Snow = 1 << 9, // 9 + SLSF2_Vertex_Lighting = 1 << 10, // 10 + SLSF2_Uniform_Scale = 1 << 11, // 11 + SLSF2_Fit_Slope = 1 << 12, // 12 + SLSF2_Billboard = 1 << 13, // 13 + SLSF2_No_LOD_Land_Blend = 1 << 14, // 14 + SLSF2_EnvMap_Light_Fade = 1 << 15, // 15! + SLSF2_Wireframe = 1 << 16, // 16 + SLSF2_Weapon_Blood = 1 << 17, // 17 + SLSF2_Hide_On_Local_Map = 1 << 18, // 18 + SLSF2_Premult_Alpha = 1 << 19, // 19 + SLSF2_Cloud_LOD = 1 << 20, // 20 + SLSF2_Anisotropic_Lighting = 1 << 21, // 21 + SLSF2_No_Transparency_Multisampling = 1 << 22, // 22 + SLSF2_Unused01 = 1 << 23, // 23 + SLSF2_Multi_Layer_Parallax = 1 << 24, // 24 + SLSF2_Soft_Lighting = 1 << 25, // 25 + SLSF2_Rim_Lighting = 1 << 26, // 26 + SLSF2_Back_Lighting = 1 << 27, // 27 + SLSF2_Unused02 = 1 << 28, // 28 + SLSF2_Tree_Anim = 1 << 29, // 29 + SLSF2_Effect_Lighting = 1 << 30, // 30 + SLSF2_HD_LOD_Objects = (unsigned int)(1 << 31), // 31 + }; + + enum ShaderType : unsigned int + { + ST_Default, + ST_EnvironmentMap, + ST_GlowShader, + ST_Heightmap, + ST_FaceTint, + ST_SkinTint, + ST_HairTint, + ST_ParallaxOccMaterial, + ST_WorldMultitexture, + ST_WorldMap1, + ST_Unknown10, + ST_MultiLayerParallax, + ST_Unknown12, + ST_WorldMap2, + ST_SparkleSnow, + ST_WorldMap3, + ST_EyeEnvmap, + ST_Unknown17, + ST_WorldMap4, + ST_WorldLODMultitexture + }; +} + +enum TexClampMode : unsigned int +{ + CLAMP_S_CLAMP_T = 0, + CLAMP_S_WRAP_T = 1, + WRAP_S_CLAMP_T = 2, + WRAP_S_WRAP_T = 3, + MIRRORED_S_MIRRORED_T = 4 +}; + + +struct UVScale +{ + float x = 1.0f; + float y = 1.0f; +}; + +struct UVOffset +{ + float x = 0.0f; + float y = 0.0f; +}; + + //! A Property that specifies shader lighting (Bethesda-specific) -class BSShaderLightingProperty final : public Property +class BSShaderLightingProperty : public Property { public: BSShaderLightingProperty( Scene * scene, const QModelIndex & index ) : Property( scene, index ) {} Type type() const override final { return ShaderLighting; } - QString typeId() const override final { return "BSShaderLightingProperty"; } + QString typeId() const override { return "BSShaderLightingProperty"; } - void update( const NifModel * nif, const QModelIndex & block ); + void update( const NifModel * nif, const QModelIndex & block ) override; friend void glProperty( BSShaderLightingProperty * ); - bool bind( int id, const QString & fname = QString() ); + bool bind( int id, const QString & fname = QString(), TexClampMode mode = TexClampMode::WRAP_S_WRAP_T ); bool bind( int id, const QList > & texcoords ); bool bind( int id, const QList > & texcoords, int stage ); + bool bindCube( int id, const QString & fname = QString() ); + QString fileName( int id ) const; //int coordSet( int id ) const; static int getId( const QString & id ); + QPersistentModelIndex getTextureSet() const; + + bool hasSF1( ShaderFlags::SF1 flag ) { return getFlags1() & flag; }; + bool hasSF2( ShaderFlags::SF2 flag ) { return getFlags2() & flag; }; + + unsigned int getFlags1() const; + unsigned int getFlags2() const; + + void setFlags1( unsigned int ); + void setFlags2( unsigned int ); + + UVScale getUvScale() const; + UVOffset getUvOffset() const; + + void setUvScale( float, float ); + void setUvOffset( float, float ); + + TexClampMode getClampMode() const; + + void setClampMode( uint mode ); + + bool getDepthTest() { return depthTest; } + bool getDepthWrite() { return depthWrite; } + bool getIsDoubleSided() { return isDoubleSided; } + bool getIsTranslucent() { return isTranslucent; } + + Material * mat() const; + protected: + ShaderFlags::SF1 flags1 = ShaderFlags::SLSF1_ZBuffer_Test; + ShaderFlags::SF2 flags2 = ShaderFlags::SLSF2_ZBuffer_Write; + //QVector textures; QPersistentModelIndex iTextureSet; QPersistentModelIndex iSourceTexture; + QPersistentModelIndex iWetMaterial; + + Material * material = nullptr; + + UVScale uvScale; + UVOffset uvOffset; + + TexClampMode clampMode; + + bool depthTest = false; + bool depthWrite = false; + bool isDoubleSided = false; + bool isTranslucent = false; }; REGISTER_PROPERTY( BSShaderLightingProperty, ShaderLighting ) +//! A Property that inherits BSShaderLightingProperty (Skyrim-specific) +class BSLightingShaderProperty final : public BSShaderLightingProperty +{ +public: + BSLightingShaderProperty( Scene * scene, const QModelIndex & index ) : BSShaderLightingProperty( scene, index ) + { + emissiveMult = 1.0f; + emissiveColor = Color3( 0, 0, 0 ); + + specularStrength = 1.0f; + specularColor = Color3( 1.0f, 1.0f, 1.0f ); + } + + QString typeId() const override final { return "BSLightingShaderProperty"; } + + void update( const NifModel * nif, const QModelIndex & block ) override; + void updateParams( const NifModel * nif, const QModelIndex & prop ); + + bool isST( ShaderFlags::ShaderType st ) { return getShaderType() == st; }; + + Color3 getEmissiveColor() const; + Color3 getSpecularColor() const; + + float getEmissiveMult() const; + + float getSpecularGloss() const; + float getSpecularStrength() const; + + float getLightingEffect1() const; + float getLightingEffect2() const; + + float getInnerThickness() const; + UVScale getInnerTextureScale() const; + float getOuterRefractionStrength() const; + float getOuterReflectionStrength() const; + + float getEnvironmentReflection() const; + + float getAlpha() const; + + void setShaderType( unsigned int ); + + void setEmissive( Color3 color, float mult = 1.0f ); + void setSpecular( Color3 color, float glossiness = 80.0f, float strength = 1.0f ); + + void setLightingEffect1( float ); + void setLightingEffect2( float ); + + void setInnerThickness( float ); + void setInnerTextureScale( float, float ); + void setOuterRefractionStrength( float ); + void setOuterReflectionStrength( float ); + + void setEnvironmentReflection( float ); + + void setAlpha( float ); + + Color3 getTintColor() const; + void setTintColor( Color3 ); + + bool hasVertexColors = false; + bool hasVertexAlpha = false; + bool hasGlowMap = false; + bool hasEmittance = false; + bool hasSoftlight = false; + bool hasBacklight = false; + bool hasRimlight = false; + bool hasModelSpaceNormals = false; + bool hasSpecularMap = false; + bool hasMultiLayerParallax = false; + bool hasCubeMap = false; + bool hasEnvironmentMap = false; + bool useEnvironmentMask = false; + bool hasHeightMap = false; + bool hasRefraction = false; + bool hasFireRefraction = false; + bool hasDetailMask = false; + bool hasTintMask = false; + bool hasTintColor = false; + bool greyscaleColor = false; + + ShaderFlags::ShaderType getShaderType(); + + float fresnelPower = 5.0; + float paletteScale = 1.0; + float rimPower = 2.0; + float backlightPower = 0.0; + +protected: + void setController( const NifModel * nif, const QModelIndex & controller ) override final; + + ShaderFlags::ShaderType shaderType; + + Color3 emissiveColor; + Color3 specularColor; + Color3 tintColor; + + float alpha = 1.0; + + float emissiveMult = 1.0; + + float specularGloss = 80.0; + float specularStrength = 1.0; + + float lightingEffect1 = 0.0; + float lightingEffect2 = 1.0; + + float environmentReflection = 0.0; + + // Multi-layer properties + float innerThickness; + UVScale innerTextureScale; + float outerRefractionStrength; + float outerReflectionStrength; +}; + +REGISTER_PROPERTY( BSLightingShaderProperty, ShaderLighting ) + + +//! A Property that inherits BSShaderLightingProperty (Skyrim-specific) +class BSEffectShaderProperty final : public BSShaderLightingProperty +{ +public: + BSEffectShaderProperty( Scene * scene, const QModelIndex & index ) : BSShaderLightingProperty( scene, index ) + { + } + + QString typeId() const override final { return "BSEffectShaderProperty"; } + + void update( const NifModel * nif, const QModelIndex & block ) override; + void updateParams( const NifModel * nif, const QModelIndex & prop ); + + Color4 getEmissiveColor() const; + float getEmissiveMult() const; + + float getAlpha() const; + + void setEmissive( Color4 color, float mult = 1.0f ); + void setFalloff( float, float, float, float, float ); + + float getEnvironmentReflection() const; + void setEnvironmentReflection( float ); + + float getLightingInfluence() const; + void setLightingInfluence( float ); + + bool hasSourceTexture = false; + bool hasGreyscaleMap = false; + bool hasEnvMap = false; + bool hasNormalMap = false; + bool hasEnvMask = false; + bool useFalloff = false; + + bool greyscaleColor = false; + bool greyscaleAlpha = false; + + bool vertexColors = false; + bool vertexAlpha = false; + + bool hasWeaponBlood = false; + + struct Falloff + { + float startAngle = 1.0f; + float stopAngle = 0.0f; + + float startOpacity = 1.0f; + float stopOpacity = 0.0f; + + float softDepth = 1.0f; + + }; + + Falloff falloff; + +protected: + void setController( const NifModel * nif, const QModelIndex & controller ) override final; + + Color4 emissiveColor; + float emissiveMult; + + float lightingInfluence = 0.0; + float environmentReflection = 0.0; +}; + +REGISTER_PROPERTY( BSEffectShaderProperty, ShaderLighting ) + + +namespace WaterShaderFlags +{ + enum SF1 : unsigned int + { + SWSF1_UNKNOWN0 = 1, + SWSF1_Bypass_Refraction_Map = 1 << 1, + SWSF1_Water_Toggle = 1 << 2, + SWSF1_UNKNOWN3 = 1 << 3, + SWSF1_UNKNOWN4 = 1 << 4, + SWSF1_UNKNOWN5 = 1 << 5, + SWSF1_Highlight_Layer_Toggle = 1 << 6, + SWSF1_Enabled = 1 << 7 + }; +} + +//! A Property that inherits BSShaderLightingProperty (Skyrim-specific) +class BSWaterShaderProperty final : public BSShaderLightingProperty +{ +public: + BSWaterShaderProperty( Scene * scene, const QModelIndex & index ) : BSShaderLightingProperty( scene, index ) + { + } + + QString typeId() const override final { return "BSWaterShaderProperty"; } + + unsigned int getWaterShaderFlags() const; + + void setWaterShaderFlags( unsigned int ); + +protected: + WaterShaderFlags::SF1 waterShaderFlags; +}; + +REGISTER_PROPERTY( BSWaterShaderProperty, ShaderLighting ) + + #endif diff --git a/src/gl/glscene.cpp b/src/gl/glscene.cpp index 3f1e5ba67..f9ea31044 100644 --- a/src/gl/glscene.cpp +++ b/src/gl/glscene.cpp @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -31,21 +31,25 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "glscene.h" -#include "options.h" +#include "settings.h" #include "glcontroller.h" #include "glmesh.h" +#include "bsshape.h" #include "glnode.h" #include "glparticles.h" #include "gltex.h" +#include #include #include +#include -//! \file glscene.cpp Scene management +//! \file glscene.cpp %Scene management -Scene::Scene( TexCache * texcache, QOpenGLContext * context, QOpenGLFunctions * functions ) +Scene::Scene( TexCache * texcache, QOpenGLContext * context, QOpenGLFunctions * functions, QObject * parent ) : + QObject( parent ) { renderer = new Renderer( context, functions ); @@ -56,6 +60,38 @@ Scene::Scene( TexCache * texcache, QOpenGLContext * context, QOpenGLFunctions * sceneBoundsValid = timeBoundsValid = false; textures = texcache; + + options = ( DoLighting | DoTexturing | DoMultisampling | DoBlending | DoVertexColors | DoSpecular | DoGlow | DoCubeMapping ); + + lodLevel = Level2; + + visMode = VisNone; + + selMode = SelObject; + + // Startup Defaults + + QSettings settings; + settings.beginGroup( "Settings/Render/General/Startup Defaults" ); + + if ( settings.value( "Show Axes", true ).toBool() ) + options |= ShowAxes; + if ( settings.value( "Show Grid", true ).toBool() ) + options |= ShowGrid; + if ( settings.value( "Show Collision" ).toBool() ) + options |= ShowCollision; + if ( settings.value( "Show Constraints" ).toBool() ) + options |= ShowConstraints; + if ( settings.value( "Show Markers" ).toBool() ) + options |= ShowMarkers; + if ( settings.value( "Show Nodes" ).toBool() ) + options |= ShowNodes; + if ( settings.value( "Show Hidden" ).toBool() ) + options |= ShowHidden; + if ( settings.value( "Do Skinning", true ).toBool() ) + options |= DoSkinning; + + settings.endGroup(); } Scene::~Scene() @@ -69,6 +105,7 @@ void Scene::clear( bool flushTextures ) nodes.clear(); properties.clear(); roots.clear(); + shapes.clear(); animGroups.clear(); animTags.clear(); @@ -127,6 +164,40 @@ void Scene::update( const NifModel * nif, const QModelIndex & index ) timeBoundsValid = false; } +void Scene::updateSceneOptions( bool checked ) +{ + Q_UNUSED( checked ); + + QAction * action = qobject_cast(sender()); + if ( action ) { + options ^= SceneOptions( action->data().toInt() ); + emit sceneUpdated(); + } +} + +void Scene::updateSceneOptionsGroup( QAction * action ) +{ + if ( !action ) + return; + + options ^= SceneOptions( action->data().toInt() ); + emit sceneUpdated(); +} + +void Scene::updateSelectMode( QAction * action ) +{ + if ( !action ) + return; + + selMode = SelMode( action->data().toInt() ); + emit sceneUpdated(); +} + +void Scene::updateLodLevel( int level ) +{ + lodLevel = LodLevel( level ); +} + void Scene::make( NifModel * nif, bool flushTextures ) { clear( flushTextures ); @@ -168,6 +239,7 @@ Node * Scene::getNode( const NifModel * nif, const QModelIndex & iNode ) || nif->inherits( iNode, "NiTriBasedGeom" ) ) { node = new Mesh( this, iNode ); + shapes += static_cast(node); } else if ( nif->checkVersion( 0x14050000, 0 ) && nif->itemName( iNode ) == "NiMesh" ) { @@ -177,6 +249,9 @@ Node * Scene::getNode( const NifModel * nif, const QModelIndex & iNode ) else if ( nif->inherits( iNode, "NiParticles" ) ) { // ... where did AParticleSystem go? node = new Particles( this, iNode ); + } else if ( nif->inherits( iNode, "BSTriShape" ) ) { + node = new BSShape( this, iNode ); + shapes += static_cast(node); } else if ( nif->inherits( iNode, "NiAVObject" ) ) { if ( nif->itemName( iNode ) == "BSTreeNode" ) node = new Node( this, iNode ); @@ -247,11 +322,11 @@ void Scene::draw() { drawShapes(); - if ( Options::drawNodes() ) + if ( options & ShowNodes ) drawNodes(); - if ( Options::drawHavok() ) + if ( options & ShowCollision ) drawHavok(); - if ( Options::drawFurn() ) + if ( options & ShowMarkers ) drawFurn(); drawSelection(); @@ -259,19 +334,19 @@ void Scene::draw() void Scene::drawShapes() { - if ( Options::blending() ) { - NodeList draw2nd; + if ( options & DoBlending ) { + NodeList secondPass; for ( Node * node : roots.list() ) { - node->drawShapes( &draw2nd ); + node->drawShapes( &secondPass ); } - if ( draw2nd.list().count() > 0 ) + if ( secondPass.list().count() > 0 ) drawSelection(); // for transparency pass - draw2nd.sort(); + secondPass.alphaSort(); - for ( Node * node : draw2nd.list() ) { + for ( Node * node : secondPass.list() ) { node->drawShapes(); } } else { @@ -381,7 +456,7 @@ QString Scene::textStats() int Scene::bindTexture( const QString & fname ) { - if ( !Options::texturing() || fname.isEmpty() ) + if ( !(options & DoTexturing) || fname.isEmpty() ) return 0; return textures->bind( fname ); @@ -389,10 +464,17 @@ int Scene::bindTexture( const QString & fname ) int Scene::bindTexture( const QModelIndex & iSource ) { - if ( !Options::texturing() || !iSource.isValid() ) + if ( !(options & DoTexturing) || !iSource.isValid() ) return 0; return textures->bind( iSource ); } +int Scene::bindTextureCube( const QString & fname ) +{ + if ( !(options & DoTexturing) || fname.isEmpty() ) + return 0; + + return textures->bindCube( fname ); +} diff --git a/src/gl/glscene.h b/src/gl/glscene.h index 82703f7bb..1fd70e76a 100644 --- a/src/gl/glscene.h +++ b/src/gl/glscene.h @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -41,6 +41,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "gltools.h" #include "renderer.h" +#include #include #include #include @@ -48,13 +49,16 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include +//! @file glscene.h Scene + class QOpenGLContext; class QOpenGLFunctions; -class Scene final +class Scene final : public QObject { + Q_OBJECT public: - Scene( TexCache * texcache, QOpenGLContext * context, QOpenGLFunctions * functions ); + Scene( TexCache * texcache, QOpenGLContext * context, QOpenGLFunctions * functions, QObject * parent = nullptr ); ~Scene(); void updateShaders() { renderer->updateShaders(); } @@ -81,9 +85,70 @@ class Scene final int bindTexture( const QString & fname ); int bindTexture( const QModelIndex & index ); + int bindTextureCube( const QString & fname ); + Node * getNode( const NifModel * nif, const QModelIndex & iNode ); Property * getProperty( const NifModel * nif, const QModelIndex & iProperty ); + enum SceneOption + { + None = 0x0, + ShowAxes = 0x1, + ShowGrid = 0x2, + ShowNodes = 0x4, + ShowCollision = 0x8, + ShowConstraints = 0x10, + ShowMarkers = 0x20, + DoDoubleSided = 0x40, + DoVertexColors = 0x80, + DoSpecular = 0x100, + DoGlow = 0x200, + DoTexturing = 0x400, + DoBlending = 0x800, + DoMultisampling = 0x1000, + DoLighting = 0x2000, + DoCubeMapping = 0x4000, + DisableShaders = 0x8000, + ShowHidden = 0x10000, + DoSkinning = 0x20000 + }; + Q_DECLARE_FLAGS( SceneOptions, SceneOption ); + + SceneOptions options; + + enum VisModes + { + VisNone = 0x0, + VisLightPos = 0x1, + VisNormalsOnly = 0x2, + VisSilhouette = 0x4 + }; + + Q_DECLARE_FLAGS( VisMode, VisModes ); + + VisMode visMode; + + enum SelModes + { + SelNone = 0, + SelObject = 1, + SelVertex = 2 + }; + + Q_DECLARE_FLAGS( SelMode, SelModes ); + + SelMode selMode; + + enum LodLevel + { + Level0 = 0, + Level1 = 1, + Level2 = 2 + }; + + LodLevel lodLevel; + + Renderer * renderer; NodeList nodes; @@ -110,10 +175,20 @@ class Scene final QPersistentModelIndex currentBlock; QPersistentModelIndex currentIndex; + QVector shapes; + BoundSphere bounds() const; float timeMin() const; float timeMax() const; +signals: + void sceneUpdated(); + +public slots: + void updateSceneOptions( bool checked ); + void updateSceneOptionsGroup( QAction * ); + void updateSelectMode( QAction * ); + void updateLodLevel( int ); protected: mutable bool sceneBoundsValid, timeBoundsValid; @@ -123,4 +198,8 @@ class Scene final void updateTimeBounds() const; }; +Q_DECLARE_OPERATORS_FOR_FLAGS( Scene::SceneOptions ) + +Q_DECLARE_OPERATORS_FOR_FLAGS( Scene::VisMode ) + #endif diff --git a/src/gl/gltex.cpp b/src/gl/gltex.cpp index e153399f9..e08176594 100644 --- a/src/gl/gltex.cpp +++ b/src/gl/gltex.cpp @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -31,15 +31,13 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "gltex.h" -#include "options.h" +#include "settings.h" #include "glscene.h" #include "gltexloaders.h" -#ifdef FSENGINE #include #include -#endif #include #include @@ -47,9 +45,17 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include + +#include + +//! @file gltex.cpp TexCache management -//! \file gltex.cpp TexCache management +#ifdef WIN32 +PFNGLACTIVETEXTUREARBPROC glActiveTextureARB; +PFNGLCLIENTACTIVETEXTUREARBPROC glClientActiveTextureARB; +#endif //! Number of texture units GLint num_texture_units = 0; @@ -59,82 +65,73 @@ float max_anisotropy = 1.0f; //! Accessor function for glProperty etc. float get_max_anisotropy() { - return max_anisotropy; + QSettings settings; + float af = settings.value( "Settings/Render/General/Anisotropic Filtering", 4.0 ).toFloat(); + + return std::min( float(pow( 2.0f, af )), max_anisotropy ); } void initializeTextureUnits( const QOpenGLContext * context ) { if ( context->hasExtension( "GL_ARB_multitexture" ) ) { - // TODO: This will always return 4 intentionally - // For shaders, should be GL_MAX_TEXTURE_IMAGE_UNITS_ARB, etc. - - glGetIntegerv( GL_MAX_TEXTURE_UNITS_ARB, &num_texture_units ); + glGetIntegerv( GL_MAX_TEXTURE_IMAGE_UNITS_ARB, &num_texture_units ); if ( num_texture_units < 1 ) num_texture_units = 1; - //qWarning() << "texture units" << num_texture_units; + //qDebug() << "texture units" << num_texture_units; } else { - qWarning( "multitexturing not supported" ); + qCWarning( nsGl ) << QObject::tr( "Multitexturing not supported." ); num_texture_units = 1; } - if ( Options::antialias() && context->hasExtension( "GL_EXT_texture_filter_anisotropic" ) ) { + if ( context->hasExtension( "GL_EXT_texture_filter_anisotropic" ) ) { glGetFloatv( GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &max_anisotropy ); - //qWarning() << "maximum anisotropy" << max_anisotropy; + //qDebug() << "maximum anisotropy" << max_anisotropy; } +#ifdef WIN32 + glActiveTextureARB = (PFNGLACTIVETEXTUREARBPROC)QOpenGLContext::currentContext()->getProcAddress( "glActiveTextureARB" ); + glClientActiveTextureARB = (PFNGLCLIENTACTIVETEXTUREARBPROC)QOpenGLContext::currentContext()->getProcAddress( "glClientActiveTextureARB" ); +#endif } bool activateTextureUnit( int stage ) { - PFNGLACTIVETEXTUREPROC glActiveTexture; - glActiveTexture = (PFNGLACTIVETEXTUREPROC)QOpenGLContext::currentContext()->getProcAddress( "glActiveTexture" ); - PFNGLCLIENTACTIVETEXTUREPROC glClientActiveTexture; - glClientActiveTexture = (PFNGLCLIENTACTIVETEXTUREPROC)QOpenGLContext::currentContext()->getProcAddress( "glClientActiveTexture" ); - if ( num_texture_units <= 1 ) return ( stage == 0 ); - // num_texture_units > 1 can only happen if GLEE_ARB_multitexture is true - // so glActiveTexture and glClientActiveTexture are supported if ( stage < num_texture_units ) { - glActiveTexture( GL_TEXTURE0 + stage ); - glClientActiveTexture( GL_TEXTURE0 + stage ); + + glActiveTextureARB( GL_TEXTURE0 + stage ); + glClientActiveTextureARB( GL_TEXTURE0 + stage ); return true; } return false; } -void resetTextureUnits() +void resetTextureUnits( int numTex ) { - PFNGLACTIVETEXTUREPROC glActiveTexture; - glActiveTexture = (PFNGLACTIVETEXTUREPROC)QOpenGLContext::currentContext()->getProcAddress( "glActiveTexture" ); - PFNGLCLIENTACTIVETEXTUREPROC glClientActiveTexture; - glClientActiveTexture = (PFNGLCLIENTACTIVETEXTUREPROC)QOpenGLContext::currentContext()->getProcAddress( "glClientActiveTexture" ); - if ( num_texture_units <= 1 ) { glDisable( GL_TEXTURE_2D ); return; } - // num_texture_units > 1 can only happen if GLEE_ARB_multitexture is true - // so glActiveTexture and glClientActiveTexture are supported - for ( int x = num_texture_units - 1; x >= 0; x-- ) { - glActiveTexture( GL_TEXTURE0 + x ); + for ( int x = numTex - 1; x >= 0; x-- ) { + glActiveTextureARB( GL_TEXTURE0 + x ); glDisable( GL_TEXTURE_2D ); glMatrixMode( GL_TEXTURE ); glLoadIdentity(); glMatrixMode( GL_MODELVIEW ); - glClientActiveTexture( GL_TEXTURE0 + x ); + glClientActiveTextureARB( GL_TEXTURE0 + x ); glDisableClientState( GL_TEXTURE_COORD_ARRAY ); } } /* - TexCache -*/ + * TexCache + */ TexCache::TexCache( QObject * parent ) : QObject( parent ) { @@ -157,22 +154,37 @@ QString TexCache::find( const QString & file, const QString & nifdir, QByteArray if ( file.isEmpty() ) return QString(); + if ( QFile( file ).exists() ) + return file; + + QSettings settings; + QString filename = QDir::toNativeSeparators( file ); - while ( filename.startsWith( "/" ) || filename.startsWith( "\\" ) ) - filename.remove( 0, 1 ); + if ( !filename.startsWith( "textures" ) ) { + QRegularExpression re( "textures[\\\\/]", QRegularExpression::CaseInsensitiveOption ); + int texIdx = filename.indexOf( re ); + if ( texIdx > 0 ) { + filename.remove( 0, texIdx ); + } else { + while ( filename.startsWith( "/" ) || filename.startsWith( "\\" ) ) + filename.remove( 0, 1 ); + + if ( !filename.startsWith( "textures", Qt::CaseInsensitive ) && !filename.startsWith( "shaders", Qt::CaseInsensitive ) ) + filename.prepend( "textures\\" ); + } + } QStringList extensions; - extensions << ".tga" << ".dds" << ".bmp" << ".nif" << ".texcache"; -#ifndef Q_OS_WIN - extensions << ".TGA" << ".DDS" << ".BMP" << ".NIF" << ".TEXCACHE"; -#endif + extensions << ".dds"; bool replaceExt = false; - if ( Options::textureAlternatives() ) { + bool textureAlternatives = settings.value( "Settings/Resources/Alternate Extensions", false ).toBool(); + if ( textureAlternatives ) { + extensions << ".tga" << ".bmp" << ".nif" << ".texcache"; for ( const QString ext : QStringList{ extensions } ) { - if ( filename.endsWith( ext ) ) { + if ( filename.endsWith( ext, Qt::CaseInsensitive ) ) { extensions.removeAll( ext ); extensions.prepend( ext ); filename = filename.left( filename.length() - ext.length() ); @@ -189,7 +201,24 @@ QString TexCache::find( const QString & file, const QString & nifdir, QByteArray filename += ext; } - for ( QString folder : Options::textureFolders() ) { + auto appdir = QDir::currentPath(); + + // First search NIF root + dir.setPath( nifdir ); + if ( dir.exists( filename ) ) { + return dir.filePath( filename ); + } + + // Next search NifSkope dir + dir.setPath( appdir ); + if ( dir.exists( filename ) ) { + return dir.filePath( filename ); + } + + + QStringList folders = settings.value( "Settings/Resources/Folders", QStringList() ).toStringList(); + + for ( QString folder : folders ) { // TODO: Always search nifdir without requiring a relative entry // in folders? Not too intuitive to require ".\" in your texture folder list // even if it is added by default. @@ -201,30 +230,28 @@ QString TexCache::find( const QString & file, const QString & nifdir, QByteArray if ( dir.exists( filename ) ) { filename = dir.filePath( filename ); + filename = QDir::toNativeSeparators( filename ); return filename; } } -#ifdef FSENGINE // Search through archives last, and load any requested textures into memory. for ( FSArchiveFile * archive : FSManager::archiveList() ) { if ( archive ) { filename = QDir::fromNativeSeparators( filename.toLower() ); - if ( archive->hasFile( filename ) ) { QByteArray outData; - //qDebug() << "Extracting " << filename; archive->fileContents( filename, outData ); if ( !outData.isEmpty() ) { data = outData; - - return file; + filename = QDir::toNativeSeparators( filename ); + return filename; } } } } -#endif + if ( !replaceExt ) break; @@ -253,7 +280,10 @@ QString TexCache::stripPath( const QString & filepath, const QString & nifFolder file = file.replace( "/", "\\" ).toLower(); QDir basePath; - for ( QString base : Options::textureFolders() ) { + QSettings settings; + QStringList folders = settings.value( "Settings/Resources/Folders", QStringList() ).toStringList(); + + for ( QString base : folders ) { if ( base.startsWith( "./" ) || base.startsWith( ".\\" ) ) { base = nifFolder + "/" + base; } @@ -293,6 +323,8 @@ void TexCache::fileChanged( const QString & filepath ) Tex * tx = it.value(); if ( tx && tx->filepath == filepath ) { + // Remove from watcher now to prevent multiple signals + watcher->removePath( tx->filepath ); if ( QFile::exists( tx->filepath ) ) { tx->reload = true; emit sigRefresh(); @@ -328,7 +360,7 @@ int TexCache::bind( const QString & fname ) if ( tx->filepath.isEmpty() || tx->reload ) tx->filepath = find( tx->filename, nifFolder, outData ); - if ( !outData.isEmpty() ) { + if ( !outData.isEmpty() || tx->reload ) { tx->data = outData; } @@ -385,6 +417,43 @@ int TexCache::bind( const QModelIndex & iSource ) return 0; } +int TexCache::bindCube( const QString & fname ) +{ + Tex * tx = textures.value( fname ); + + if ( !tx ) { + tx = new Tex; + tx->filename = fname; + tx->id = 0; + tx->data = QByteArray(); + tx->mipmaps = 0; + tx->reload = false; + + textures.insert( tx->filename, tx ); + } + + QByteArray outData; + + if ( tx->filepath.isEmpty() || tx->reload ) + tx->filepath = find( tx->filename, nifFolder, outData ); + + if ( !outData.isEmpty() ) { + tx->data = outData; + } + + if ( !tx->id || tx->reload ) { + if ( QFile::exists( tx->filepath ) && QFileInfo( tx->filepath ).isWritable() && (!watcher->files().contains( tx->filepath )) ) + watcher->addPath( tx->filepath ); + + tx->loadCube(); + } + + glBindTexture( GL_TEXTURE_CUBE_MAP, tx->id ); + glTexParameterf( GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_ANISOTROPY_EXT, get_max_anisotropy() ); + + return tx->mipmaps; +} + void TexCache::flush() { for ( Tex * tx : textures ) { @@ -466,7 +535,7 @@ bool TexCache::importFile( NifModel * nif, const QModelIndex & iSource, QModelIn if ( nif && iSource.isValid() ) { if ( nif->get( iSource, "Use External" ) == 1 ) { QString filename = nif->get( iSource, "File Name" ); - //qWarning() << "TexCache::importFile: Texture has filename (from NIF) " << filename; + //qDebug() << "TexCache::importFile: Texture has filename (from NIF) " << filename; Tex * tx = textures.value( filename ); return tx->savePixelData( nif, iSource, iData ); } @@ -476,8 +545,9 @@ bool TexCache::importFile( NifModel * nif, const QModelIndex & iSource, QModelIn } -////////////////////////////////////////////////////////////////////////// - +/* +* TexCache::Tex +*/ void TexCache::Tex::load() { @@ -500,6 +570,27 @@ void TexCache::Tex::load() } } +void TexCache::Tex::loadCube() +{ + if ( !id ) + glGenTextures( 1, &id ); + + width = height = mipmaps = 0; + reload = false; + status = QString(); + + glBindTexture( GL_TEXTURE_CUBE_MAP, id ); + + try + { + texLoadCube( filepath, format, width, height, mipmaps, data, id ); + } + catch ( QString e ) + { + status = e; + } +} + bool TexCache::Tex::saveAsFile( const QModelIndex & index, QString & savepath ) { texLoad( index, format, width, height, mipmaps ); @@ -515,6 +606,6 @@ bool TexCache::Tex::savePixelData( NifModel * nif, const QModelIndex & iSource, { Q_UNUSED( iSource ); // gltexloaders function goes here - //qWarning() << "TexCache::Tex:savePixelData: Packing" << iSource << "from file" << filepath << "to" << iData; + //qDebug() << "TexCache::Tex:savePixelData: Packing" << iSource << "from file" << filepath << "to" << iData; return texSaveNIF( nif, filepath, iData ); } diff --git a/src/gl/gltex.h b/src/gl/gltex.h index 045462dcc..b32d293ed 100644 --- a/src/gl/gltex.h +++ b/src/gl/gltex.h @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -42,6 +42,8 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include +//! @file gltex.h TexCache etc. header + class GroupBox; class QAction; @@ -50,10 +52,8 @@ class QOpenGLContext; typedef unsigned int GLuint; -//! \file gltex.h TexCache etc. header - -//! A class for handling OpenGL textures. -/*! +/*! A class for handling OpenGL textures. + * * This class stores information on all loaded textures, and watches the texture files. */ class TexCache final : public QObject @@ -87,6 +87,9 @@ class TexCache final : public QObject //! Load the texture void load(); + //! Load the texture + void loadCube(); + //! Save the texture as a file bool saveAsFile( const QModelIndex & index, QString & savepath ); //! Save the texture as pixel data @@ -94,9 +97,7 @@ class TexCache final : public QObject }; public: - //! Constructor TexCache( QObject * parent = nullptr ); - //! Destructor ~TexCache(); //! Bind a texture from filename @@ -104,6 +105,9 @@ class TexCache final : public QObject //! Bind a texture from pixel data int bind( const QModelIndex & iSource ); + //! Bind a texture from filename + int bindCube( const QString & fname ); + //! Debug function for getting info about a texture QString info( const QModelIndex & iSource ); @@ -126,8 +130,8 @@ class TexCache final : public QObject public slots: void flush(); - //! Set the folder to read textures from - /*! + /*! Set the folder to read textures from + * * If this is not set, relative paths won't resolve. The standard usage * is to give NifModel::getFolder() as the argument. */ @@ -149,6 +153,8 @@ float get_max_anisotropy(); void initializeTextureUnits( const QOpenGLContext * ); bool activateTextureUnit( int x ); -void resetTextureUnits(); +// TODO: The default of 8 is arbitrary because >8 causes GL paint errors +// This is a problem only if a mesh uses all 9 texture slots +void resetTextureUnits( int numTex = 8 ); #endif diff --git a/src/gl/gltexloaders.cpp b/src/gl/gltexloaders.cpp index eda6d074b..f59c991bc 100644 --- a/src/gl/gltexloaders.cpp +++ b/src/gl/gltexloaders.cpp @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -35,6 +35,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "nifmodel.h" #include "dds/dds_api.h" #include "dds/DirectDrawSurface.h" // unused? check if upstream has cleaner or documented API yet +#include "SOIL.h" #include #include @@ -46,8 +47,8 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include -/*! \file gltexloaders.cpp - * \brief Texture loading functions. +/*! @file gltexloaders.cpp + * @brief Texture loading functions. * * Textures can be loaded from a filename (in any supported format) or raw * pixel data. They can also be exported from raw pixel data to TGA or DDS. @@ -65,6 +66,10 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ +#define GL_COMPRESSED_LUMINANCE_ALPHA_3DC_ATI 0x8837 +#define FOURCC_ATI2 0x32495441 +#define FOURCC_BC5U 0x55354342 + //! Shift amounts for RGBA conversion static const int rgbashift[4] = { 0, 8, 16, 24 @@ -112,10 +117,10 @@ bool isPowerOfTwo( unsigned int x ) return ( x == 1 ); } -//! Completes mipmap sequence of the current active OpenGL texture. -/*! - * \param m Number of mipmaps that are already in the texture. - * \return Total number of mipmaps. +/*! Completes mipmap sequence of the current active OpenGL texture. + * + * @param m Number of mipmaps that are already in the texture. + * @return Total number of mipmaps. */ int generateMipMaps( int m ) { @@ -125,7 +130,7 @@ int generateMipMaps( int m ) glGetTexLevelParameteriv( GL_TEXTURE_2D, m - 1, GL_TEXTURE_WIDTH, &w ); glGetTexLevelParameteriv( GL_TEXTURE_2D, m - 1, GL_TEXTURE_HEIGHT, &h ); - //qWarning() << m-1 << w << h; + //qDebug() << m-1 << w << h; quint8 * data = (quint8 *)malloc( w * h * 4 ); glGetTexImage( GL_TEXTURE_2D, m - 1, GL_RGBA, GL_UNSIGNED_BYTE, data ); @@ -151,7 +156,7 @@ int generateMipMaps( int m ) if ( h == 0 ) h = 1; - //qWarning() << m << w << h; + //qDebug() << m << w << h; for ( int y = 0; y < h; y++ ) { for ( int x = 0; x < w; x++ ) { @@ -174,8 +179,8 @@ int generateMipMaps( int m ) return m; } -//! Converts RLE-encoded data into pixel data. -/*! +/*! Converts RLE-encoded data into pixel data. + * * TGA in particular uses the PackBits format described at * http://en.wikipedia.org/wiki/PackBits and in the TGA spec. */ @@ -193,7 +198,7 @@ bool uncompressRLE( QIODevice & f, int w, int h, int bytespp, quint8 * pixel ) if ( rl & 0x80 ) { // if RLE packet - quint8 px[8]; // pixel data in this packet (assume bytespp < 8) + quint8 px[8] = {}; // pixel data in this packet (assume bytespp < 8) for ( int b = 0; b < bytespp; b++ ) px[b] = data[o++]; @@ -218,16 +223,16 @@ bool uncompressRLE( QIODevice & f, int w, int h, int bytespp, quint8 * pixel ) return true; } -//! Convert pixels to RGBA -/*! - * \param data Pixels to convert - * \param w Width of the image - * \param h Height of the image - * \param bytespp Number of bytes per pixel - * \param mask Bitmask for pixel data - * \param flipV Whether to flip the data vertically - * \param flipH Whether to flip the data horizontally - * \param pixl Pixels to output +/*! Convert pixels to RGBA + * + * @param data Pixels to convert + * @param w Width of the image + * @param h Height of the image + * @param bytespp Number of bytes per pixel + * @param mask Bitmask for pixel data + * @param flipV Whether to flip the data vertically + * @param flipH Whether to flip the data horizontally + * @param pixl Pixels to output */ void convertToRGBA( const quint8 * data, int w, int h, int bytespp, const quint32 mask[], bool flipV, bool flipH, quint8 * pixl ) { @@ -569,18 +574,18 @@ void flipDXT( GLenum glFormat, int width, int height, unsigned char * image ) } } -//! Load a DXT compressed DDS texture from file -/*! - * \param f File to load from - * \param null Format - * \param null Block size - * \param null Width - * \param null Height - * \param mipmaps The number of mipmaps to read - * \param null Flip - * \return The total number of mipmaps +/*! Load a DXT compressed DDS texture from file + * + * @param f File to load from + * @param null Format + * @param null Block size + * @param null Width + * @param null Height + * @param mipmaps The number of mipmaps to read + * @param null Flip + * @return The total number of mipmaps */ -GLuint texLoadDXT( QIODevice & f, GLenum /*glFormat*/, int /*blockSize*/, quint32 /*width*/, quint32 /*height*/, quint32 mipmaps, bool /*flipV*/ = false ) +GLuint texLoadDXT( QIODevice & f, GLenum glFormat, int /*blockSize*/, quint32 /*width*/, quint32 /*height*/, quint32 mipmaps, bool /*flipV*/ = false ) { /* #ifdef WIN32 @@ -607,18 +612,31 @@ GLuint texLoadDXT( QIODevice & f, GLenum /*glFormat*/, int /*blockSize*/, quint3 Color32 * src = img->pixels(); GLubyte * dst = pixels; - //qWarning() << "flipV = " << flipV; - for ( quint32 y = 0; y < h; y++ ) { - for ( quint32 x = 0; x < w; x++ ) { - *dst++ = src->r; - *dst++ = src->g; - *dst++ = src->b; - *dst++ = src->a; - src++; + //qDebug() << "flipV = " << flipV; + if ( glFormat == GL_COMPRESSED_LUMINANCE_ALPHA_3DC_ATI ) { + for ( quint32 y = 0; y < h; y++ ) { + for ( quint32 x = 0; x < w; x++ ) { + *dst++ = src->g; + *dst++ = src->r; + *dst++ = 255 - src->b; + *dst++ = 255; + src++; + } + } + } else { + for ( quint32 y = 0; y < h; y++ ) { + for ( quint32 x = 0; x < w; x++ ) { + *dst++ = src->r; + *dst++ = src->g; + *dst++ = src->b; + *dst++ = src->a; + src++; + } } } delete img; + // load the texture into OpenGL glTexImage2D( GL_TEXTURE_2D, m++, 4, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels ); delete [] pixels; @@ -706,6 +724,12 @@ GLuint texLoadDDS( QIODevice & f, QString & texformat ) blockSize = 16; texformat += " (DXT5)"; break; + case FOURCC_ATI2: + case FOURCC_BC5U: + glFormat = GL_COMPRESSED_LUMINANCE_ALPHA_3DC_ATI; + blockSize = 16; + texformat += " (ATI2)"; + break; default: throw QString( "unknown texture compression" ); } @@ -740,12 +764,12 @@ GLuint texLoadDDS( QIODevice & f, QString & texformat ) } } -//! Load a DXT compressed texture -/*! - * \param hdr Description of the texture - * \param pixels The pixel data - * \param size The size of the texture - * \return The total number of mipmaps +/*! Load a DXT compressed texture + * + * @param hdr Description of the texture + * @param pixels The pixel data + * @param size The size of the texture + * @return The total number of mipmaps */ GLuint texLoadDXT( DDSFormat & hdr, const quint8 * pixels, uint size ) { @@ -765,7 +789,7 @@ GLuint texLoadDXT( DDSFormat & hdr, const quint8 * pixels, uint size ) Color32 * src = img->pixels(); GLubyte * dst = pixels; - //qWarning() << "flipV = " << flipV; + //qDebug() << "flipV = " << flipV; for ( quint32 y = 0; y < h; y++ ) { for ( quint32 x = 0; x < w; x++ ) { *dst++ = src->r; @@ -835,7 +859,7 @@ GLuint texLoadTGA( QIODevice & f, QString & texformat ) quint8 bits = hdr[7]; quint8 bytes = bits / 8; - //qWarning() << "COLORMAP" << "offset" << offset << "length" << length << "bits" << bits << "depth" << depth; + //qDebug() << "COLORMAP" << "offset" << offset << "length" << length << "bits" << bits << "depth" << depth; if ( bits != 32 && bits != 24 ) throw QString( "image sub format not supported" ); @@ -925,13 +949,13 @@ GLuint texLoadTGA( QIODevice & f, QString & texformat ) return 0; } -//! Return value as a 32-bit value; possibly replace with QtEndian functions? +//! Return value as a 32-bit value; possibly replace with QtEndian functions? quint32 get32( quint8 * x ) { return *( (quint32 *)x ); } -//! Return value as a 16-bit value; possibly replace with QtEndian functions? +//! Return value as a 16-bit value; possibly replace with QtEndian functions? quint16 get16( quint8 * x ) { return *( (quint16 *)x ); @@ -982,11 +1006,11 @@ GLuint texLoadBMP( QIODevice & f, QString & texformat ) throw QString( "unknown image sub format" ); /* - qWarning( "size %i,%i", width, height ); - qWarning( "plns %i", get16( &hdr[26] ) ); - qWarning( "bpp %i", bpp ); - qWarning( "cmpr %08x", compression ); - qWarning( "ofs %i", offset ); + qDebug( "size %i,%i", width, height ); + qDebug( "plns %i", get16( &hdr[26] ) ); + qDebug( "bpp %i", bpp ); + qDebug( "cmpr %08x", compression ); + qDebug( "ofs %i", offset ); */ return 0; } @@ -1175,12 +1199,12 @@ GLuint texLoadNIF( QIODevice & f, QString & texformat ) return mipmaps; } -// (public function, documented in gltexloaders.h) + bool texLoad( const QString & filepath, QString & format, GLuint & width, GLuint & height, GLuint & mipmaps ) { return texLoad( filepath, format, width, height, mipmaps, *(new QByteArray()) ); } -// (public function, documented in gltexloaders.h) + bool texLoad( const QString & filepath, QString & format, GLuint & width, GLuint & height, GLuint & mipmaps, QByteArray & data ) { width = height = mipmaps = 0; @@ -1216,6 +1240,7 @@ bool texLoad( const QString & filepath, QString & format, GLuint & width, GLuint throw QString( "unknown texture format" ); f.close(); + data.clear(); glGetTexLevelParameteriv( GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, (GLint *)&width ); glGetTexLevelParameteriv( GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, (GLint *)&height ); @@ -1223,7 +1248,67 @@ bool texLoad( const QString & filepath, QString & format, GLuint & width, GLuint return mipmaps > 0; } -// (public function, documented in gltexloaders.h) + +bool texLoadCube( const QString & filepath, QString & format, GLuint & width, GLuint & height, GLuint & mipmaps, QByteArray & data, GLuint id ) +{ + Q_UNUSED( format ); + + width = height = mipmaps = 0; + + if ( data.isEmpty() ) { + QFile tmpF( filepath ); + + if ( !tmpF.open( QIODevice::ReadOnly ) ) + throw QString( "could not open file" ); + + data = tmpF.readAll(); + + tmpF.close(); + + if ( data.isEmpty() ) + return false; + } + + bool success = false; + GLuint result; + + QBuffer f( &data ); + + if ( !f.open( QIODevice::ReadOnly ) ) + throw QString( "could not open buffer" ); + + if ( filepath.endsWith( ".dds", Qt::CaseInsensitive ) ) { + + result = SOIL_load_OGL_single_cubemap_from_memory( + (const unsigned char *)f.data().constData(), + f.data().size(), + SOIL_DDS_CUBEMAP_FACE_ORDER, + SOIL_LOAD_AUTO, + id, + SOIL_FLAG_MIPMAPS + ); + + if ( result == id ) { + success = true; + + // Just fudge the mipmaps number + mipmaps = 6; + } + } else { + throw QString( "unsupported texture format" ); + } + + + f.close(); + data.clear(); + + glGetTexLevelParameteriv( GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_TEXTURE_WIDTH, (GLint *)&width ); + glGetTexLevelParameteriv( GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_TEXTURE_HEIGHT, (GLint *)&height ); + + return success; +} + + bool texCanLoad( const QString & filepath ) { QFileInfo i( filepath ); @@ -1236,7 +1321,7 @@ bool texCanLoad( const QString & filepath ) ); } -// (public function, documented in gltexloaders.h) + bool texSaveDDS( const QModelIndex & index, const QString & filepath, GLuint & width, GLuint & height, GLuint & mipmaps ) { const NifModel * nif = qobject_cast( index.model() ); @@ -1244,7 +1329,7 @@ bool texSaveDDS( const QModelIndex & index, const QString & filepath, GLuint & w // can't dump palettised textures yet if ( format == 2 ) { - qWarning() << "Texture format not supported"; + qCCritical( nsIo ) << QObject::tr( "Texture format not supported" ); return false; } @@ -1277,14 +1362,14 @@ bool texSaveDDS( const QModelIndex & index, const QString & filepath, GLuint & w QFile f( filename ); if ( !f.open( QIODevice::WriteOnly ) ) { - qWarning() << "texSaveDDS(" << filename << ") : could not open file"; + qCCritical( nsIo ) << QObject::tr( "texSaveDDS: could not open %1" ).arg( filename ); return false; } qint64 writeBytes = f.write( (char *)"DDS ", 4 ); if ( writeBytes != 4 ) { - qWarning() << "texSaveDDS(" << filename << ") : could not open file"; + qCCritical( nsIo ) << QObject::tr( "texSaveDDS: could not open %1" ).arg( filename ); return false; } @@ -1488,7 +1573,7 @@ bool texSaveDDS( const QModelIndex & index, const QString & filepath, GLuint & w writeBytes = f.write( (char *)header, 124 ); if ( writeBytes != 124 ) { - qWarning() << "texSaveDDS(" << filename << ") : could not open file"; + qCCritical( nsIo ) << QObject::tr( "texSaveDDS: could not open %1" ).arg( filename ); return false; } @@ -1496,14 +1581,14 @@ bool texSaveDDS( const QModelIndex & index, const QString & filepath, GLuint & w writeBytes = f.write( buf.data().constData(), buf.size() ); if ( writeBytes != buf.size() ) { - qWarning() << "texSaveDDS(" << filename << ") : could not open file"; + qCCritical( nsIo ) << QObject::tr( "texSaveDDS: could not open %1" ).arg( filename ); return false; } return true; } -// (public function, documented in gltexloaders.h) + bool texSaveTGA( const QModelIndex & index, const QString & filepath, GLuint & width, GLuint & height ) { Q_UNUSED( index ); @@ -1553,7 +1638,7 @@ bool texSaveTGA( const QModelIndex & index, const QString & filepath, GLuint & w QFile f( filename ); if ( !f.open( QIODevice::WriteOnly ) ) { - qWarning() << "texSaveTGA(" << filename << ") : could not open file"; + qCCritical( nsIo ) << QObject::tr( "texSaveTGA: could not open %1" ).arg( filename ); free( data ); return false; } @@ -1590,7 +1675,7 @@ bool texSaveTGA( const QModelIndex & index, const QString & filepath, GLuint & w qint64 writeBytes = f.write( (char *)hdr, 18 ); if ( writeBytes != 18 ) { - qWarning() << "texSaveTGA(" << filename << ") : failed to write file"; + qCCritical( nsIo ) << QObject::tr( "texSaveTGA: could not write to %1" ).arg( filename ); free( data ); return false; } @@ -1598,7 +1683,7 @@ bool texSaveTGA( const QModelIndex & index, const QString & filepath, GLuint & w writeBytes = f.write( (char *)data, s ); if ( writeBytes != s ) { - qWarning() << "texSaveTGA(" << filename << ") : failed to write file"; + qCCritical( nsIo ) << QObject::tr( "texSaveTGA: could not write to %1" ).arg( filename ); free( data ); return false; } @@ -1606,7 +1691,7 @@ bool texSaveTGA( const QModelIndex & index, const QString & filepath, GLuint & w writeBytes = f.write( (char *)footer, 26 ); if ( writeBytes != 26 ) { - qWarning() << "texSaveTGA(" << filename << ") : failed to write file"; + qCCritical( nsIo ) << QObject::tr( "texSaveTGA: could not write to %1" ).arg( filename ); free( data ); return false; } @@ -1615,12 +1700,12 @@ bool texSaveTGA( const QModelIndex & index, const QString & filepath, GLuint & w return true; } -// (public function, documented in gltexloaders.h) + bool texSaveNIF( NifModel * nif, const QString & filepath, QModelIndex & iData ) { // Work out the extension and format // If DDS raw, DXT1 or DXT5, copy directly from texture - //qWarning() << "texSaveNIF: saving" << filepath << "to" << iData; + //qDebug() << "texSaveNIF: saving" << filepath << "to" << iData; QFile f( filepath ); @@ -1688,13 +1773,11 @@ bool texSaveNIF( NifModel * nif, const QString & filepath, QModelIndex & iData ) // copy channels for ( int i = 0; i < 4; i++ ) { -#ifndef QT_NO_DEBUG - qWarning() << "Channel" << i; - qWarning() << pix.get( srcChannels.child( i, 0 ), "Type" ); - qWarning() << pix.get( srcChannels.child( i, 0 ), "Convention" ); - qWarning() << pix.get( srcChannels.child( i, 0 ), "Bits Per Channel" ); - qWarning() << pix.get( srcChannels.child( i, 0 ), "Unknown Byte 1" ); -#endif + qDebug() << "Channel" << i; + qDebug() << pix.get( srcChannels.child( i, 0 ), "Type" ); + qDebug() << pix.get( srcChannels.child( i, 0 ), "Convention" ); + qDebug() << pix.get( srcChannels.child( i, 0 ), "Bits Per Channel" ); + qDebug() << pix.get( srcChannels.child( i, 0 ), "Unknown Byte 1" ); nif->set( destChannels.child( i, 0 ), "Type", pix.get( srcChannels.child( i, 0 ), "Type" ) ); nif->set( destChannels.child( i, 0 ), "Convention", pix.get( srcChannels.child( i, 0 ), "Convention" ) ); @@ -1704,7 +1787,7 @@ bool texSaveNIF( NifModel * nif, const QString & filepath, QModelIndex & iData ) nif->set( iData, "Num Faces", pix.get( iPixData, "Num Faces" ) ); } else { - qWarning() << "NIF versions are incompatible, cannot copy pixel data"; + qCCritical( nsNif ) << QObject::tr( "NIF versions are incompatible, cannot copy pixel data" ); return false; } @@ -1741,16 +1824,16 @@ bool texSaveNIF( NifModel * nif, const QString & filepath, QModelIndex & iData ) //nif->set<>( iData, "", pix.get<>( iPixData, "" ) ); //nif->set<>( iData, "", pix.get<>( iPixData, "" ) ); } else if ( filepath.endsWith( ".bmp", Qt::CaseInsensitive ) || filepath.endsWith( ".tga", Qt::CaseInsensitive ) ) { - //qWarning() << "Copying from GL buffer"; + //qDebug() << "Copying from GL buffer"; GLuint width, height, mipmaps; QString format; // fastest way to get parameters and ensure texture is active if ( texLoad( filepath, format, width, height, mipmaps ) ) { - //qWarning() << "Width" << width << "height" << height << "mipmaps" << mipmaps << "format" << format; + //qDebug() << "Width" << width << "height" << height << "mipmaps" << mipmaps << "format" << format; } else { - qWarning() << "Error importing texture" << filepath; + qCCritical( nsIo ) << QObject::tr( "Error importing %1" ).arg( filepath ); return false; } @@ -1807,11 +1890,11 @@ bool texSaveNIF( NifModel * nif, const QString & filepath, QModelIndex & iData ) mipmapData.resize( mipmapSize ); glGetTexImage( GL_TEXTURE_2D, i, GL_RGBA, GL_UNSIGNED_BYTE, mipmapData.data() ); - //qWarning() << "Copying mipmap" << i << "," << mipmapSize << "bytes"; + //qDebug() << "Copying mipmap" << i << "," << mipmapSize << "bytes"; pixelData.append( mipmapData ); - //qWarning() << "Now have" << pixelData.size() << "bytes of pixel data"; + //qDebug() << "Now have" << pixelData.size() << "bytes of pixel data"; // generate next offset, resize mipmapOffset += mipmapWidth * mipmapHeight * 4; @@ -1832,7 +1915,7 @@ bool texSaveNIF( NifModel * nif, const QString & filepath, QModelIndex & iData ) // return true once perfected //return false; } else if ( filepath.endsWith( ".dds", Qt::CaseInsensitive ) ) { - //qWarning() << "Will copy from DDS data"; + //qDebug() << "Will copy from DDS data"; DDSFormat ddsHeader; char tag[4]; f.read( &tag[0], 4 ); @@ -1840,29 +1923,27 @@ bool texSaveNIF( NifModel * nif, const QString & filepath, QModelIndex & iData ) if ( strncmp( tag, "DDS ", 4 ) != 0 || f.read( (char *)&ddsHeader, sizeof(DDSFormat) ) != sizeof( DDSFormat ) ) throw QString( "not a DDS file" ); -#ifndef QT_NO_DEBUG - qWarning() << "Size: " << ddsHeader.dwSize << "Flags" << ddsHeader.dwFlags << "Height" << ddsHeader.dwHeight << "Width" << ddsHeader.dwWidth; - qWarning() << "FourCC:" << ddsHeader.ddsPixelFormat.dwFourCC; -#endif + qDebug() << "Size: " << ddsHeader.dwSize << "Flags" << ddsHeader.dwFlags << "Height" << ddsHeader.dwHeight << "Width" << ddsHeader.dwWidth; + qDebug() << "FourCC:" << ddsHeader.ddsPixelFormat.dwFourCC; if ( ddsHeader.ddsPixelFormat.dwFlags & DDPF_FOURCC ) { switch ( ddsHeader.ddsPixelFormat.dwFourCC ) { case FOURCC_DXT1: - //qWarning() << "DXT1"; + //qDebug() << "DXT1"; nif->set( iData, "Pixel Format", 4 ); break; case FOURCC_DXT5: - //qWarning() << "DXT5"; + //qDebug() << "DXT5"; nif->set( iData, "Pixel Format", 5 ); break; default: // don't know how eg. DXT3 can be stored in NIF - qWarning() << "Unsupported DDS format:" << ddsHeader.ddsPixelFormat.dwFourCC << "FourCC"; + qCCritical( nsIo ) << QObject::tr( "Unsupported DDS format: %1 %2" ).arg( ddsHeader.ddsPixelFormat.dwFourCC ).arg( "FourCC" ); return false; break; } } else { - //qWarning() << "RAW"; + //qDebug() << "RAW"; // switch on BPP //nif->set( iData, "Pixel Format", 0 ); switch ( ddsHeader.ddsPixelFormat.dwBPP ) { @@ -1876,19 +1957,17 @@ bool texSaveNIF( NifModel * nif, const QString & filepath, QModelIndex & iData ) break; default: // theoretically could have a palettised DDS in 8bpp - qWarning() << "Unsupported DDS format:" << ddsHeader.ddsPixelFormat.dwBPP << "BPP"; + qCCritical( nsIo ) << QObject::tr( "Unsupported DDS format: %1 %2" ).arg( ddsHeader.ddsPixelFormat.dwBPP ).arg( "BPP" ); return false; break; } } -#ifndef QT_NO_DEBUG - qWarning() << "BPP:" << ddsHeader.ddsPixelFormat.dwBPP; - qWarning() << "RMask:" << ddsHeader.ddsPixelFormat.dwRMask; - qWarning() << "GMask:" << ddsHeader.ddsPixelFormat.dwGMask; - qWarning() << "BMask:" << ddsHeader.ddsPixelFormat.dwBMask; - qWarning() << "AMask:" << ddsHeader.ddsPixelFormat.dwAMask; -#endif + qDebug() << "BPP:" << ddsHeader.ddsPixelFormat.dwBPP; + qDebug() << "RMask:" << ddsHeader.ddsPixelFormat.dwRMask; + qDebug() << "GMask:" << ddsHeader.ddsPixelFormat.dwGMask; + qDebug() << "BMask:" << ddsHeader.ddsPixelFormat.dwBMask; + qDebug() << "AMask:" << ddsHeader.ddsPixelFormat.dwAMask; // Note that these might not match what's expected; hopefully the loader function is smart if ( nif->checkVersion( 0, 0x0A020000 ) ) { @@ -1936,13 +2015,13 @@ bool texSaveNIF( NifModel * nif, const QString & filepath, QModelIndex & iData ) // set RGB mask separately for ( int i = 0; i < 3; i++ ) { if ( ddsHeader.ddsPixelFormat.dwRMask == RGBA_INV_MASK[i] ) { - //qWarning() << "red channel" << i; + //qDebug() << "red channel" << i; nif->set( destChannels.child( i, 0 ), "Type", 0 ); } else if ( ddsHeader.ddsPixelFormat.dwGMask == RGBA_INV_MASK[i] ) { - //qWarning() << "green channel" << i; + //qDebug() << "green channel" << i; nif->set( destChannels.child( i, 0 ), "Type", 1 ); } else if ( ddsHeader.ddsPixelFormat.dwBMask == RGBA_INV_MASK[i] ) { - //qWarning() << "blue channel" << i; + //qDebug() << "blue channel" << i; nif->set( destChannels.child( i, 0 ), "Type", 2 ); } } @@ -1968,7 +2047,7 @@ bool texSaveNIF( NifModel * nif, const QString & filepath, QModelIndex & iData ) } // generate mipmap sizes and offsets - //qWarning() << "Mipmap count: " << ddsHeader.dwMipMapCount; + //qDebug() << "Mipmap count: " << ddsHeader.dwMipMapCount; nif->set( iData, "Num Mipmaps", ddsHeader.dwMipMapCount ); QModelIndex destMipMaps = nif->getIndex( iData, "Mipmaps" ); nif->updateArray( destMipMaps ); @@ -2010,13 +2089,13 @@ bool texSaveNIF( NifModel * nif, const QString & filepath, QModelIndex & iData ) nif->updateArray( iFaceData ); f.seek( 4 + ddsHeader.dwSize ); - //qWarning() << "Reading from " << f.pos(); + //qDebug() << "Reading from " << f.pos(); QByteArray ddsData = f.read( mipmapOffset ); - //qWarning() << "Read " << ddsData.size() << " bytes of" << f.size() << ", now at" << f.pos(); + //qDebug() << "Read " << ddsData.size() << " bytes of" << f.size() << ", now at" << f.pos(); if ( ddsData.size() != mipmapOffset ) { - qWarning() << "Unexpected EOF"; + qCCritical( nsIo ) << QObject::tr( "Unexpected EOF" ); return false; } @@ -2026,7 +2105,7 @@ bool texSaveNIF( NifModel * nif, const QString & filepath, QModelIndex & iData ) QByteArray result = nif->get( iFaceData, "Pixel Data" ); for ( int i = 0; i < 16; i++ ) { - qWarning() << "Comparing byte " << i << ": result " << (quint8)result[i] << ", original " << (quint8)ddsData[i]; + qDebug() << "Comparing byte " << i << ": result " << (quint8)result[i] << ", original " << (quint8)ddsData[i]; } */ diff --git a/src/gl/gltexloaders.h b/src/gl/gltexloaders.h index 51b5536d9..afbe1f795 100644 --- a/src/gl/gltexloaders.h +++ b/src/gl/gltexloaders.h @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -41,75 +41,77 @@ class QString; typedef unsigned int GLuint; -//! \file gltexloaders.h Texture loading functions header +//! @file gltexloaders.h Texture loading functions header -//! A function for loading textures. -/*! +/*! A function for loading textures. + * * Loads a texture pointed to by filepath. * Returns true on success, and throws a QString otherwise. * The parameters format, width, height and mipmaps will be filled with information about * the loaded texture. * - * \param filepath The full path to the texture that must be loaded. - * \param format Contain the format, for instance "DDS (DXT3)" or "TGA", on successful load. - * \param width Contains the texture width on successful load. - * \param height Contains the texture height on successful load. - * \param mipmaps Contains the number of mipmaps on successful load. - * \return true if the load was successful, false otherwise. + * @param filepath The full path to the texture that must be loaded. + * @param format Contain the format, for instance "DDS (DXT3)" or "TGA", on successful load. + * @param width Contains the texture width on successful load. + * @param height Contains the texture height on successful load. + * @param mipmaps Contains the number of mipmaps on successful load. + * @return True if the load was successful, false otherwise. */ extern bool texLoad( const QString & filepath, QString & format, GLuint & width, GLuint & height, GLuint & mipmaps ); extern bool texLoad( const QString & filepath, QString & format, GLuint & width, GLuint & height, GLuint & mipmaps, QByteArray & data ); -//! A function for loading textures. -/*! -* Loads a texture pointed to by model index. -* Returns true on success, and throws a QString otherwise. -* The parameters format, width, height and mipmaps will be filled with information about -* the loaded texture. -* -* \param iData Reference to pixel data block -* \param format Contain the format, for instance "DDS (DXT3)" or "TGA", on successful load. -* \param width Contains the texture width on successful load. -* \param height Contains the texture height on successful load. -* \param mipmaps Contains the number of mipmaps on successful load. -* \return true if the load was successful, false otherwise. -*/ +extern bool texLoadCube( const QString & filepath, QString & format, GLuint & width, GLuint & height, GLuint & mipmaps, QByteArray & data, GLuint id ); + +/*! A function for loading textures. + * + * Loads a texture pointed to by model index. + * Returns true on success, and throws a QString otherwise. + * The parameters format, width, height and mipmaps will be filled with information about + * the loaded texture. + * + * @param iData Reference to pixel data block + * @param format Contain the format, for instance "DDS (DXT3)" or "TGA", on successful load. + * @param width Contains the texture width on successful load. + * @param height Contains the texture height on successful load. + * @param mipmaps Contains the number of mipmaps on successful load. + * @return True if the load was successful, false otherwise. + */ extern bool texLoad( const QModelIndex & iData, QString & format, GLuint & width, GLuint & height, GLuint & mipmaps ); -//! A function which checks whether the given file can be loaded. -/*! +/*! A function which checks whether the given file can be loaded. + * * The function checks whether the file exists, is readable, and whether its extension * is that of a supported file format (dds, tga, or bmp). * - * \param filepath The full path to the texture that must be checked. + * @param filepath The full path to the texture that must be checked. */ extern bool texCanLoad( const QString & filepath ); -//! Save pixel data to a DDS file -/*! - * \param index Reference to pixel data - * \param filepath The filepath to write - * \param width The width of the texture - * \param height The height of the texture - * \param mipmaps The number of mipmaps present - * \return true if the save was successful, false otherwise +/*! Save pixel data to a DDS file + * + * @param index Reference to pixel data + * @param filepath The filepath to write + * @param width The width of the texture + * @param height The height of the texture + * @param mipmaps The number of mipmaps present + * @return True if the save was successful, false otherwise */ bool texSaveDDS( const QModelIndex & index, const QString & filepath, GLuint & width, GLuint & height, GLuint & mipmaps ); -//! Save pixel data to a TGA file -/*! - * \param index Reference to pixel data - * \param filepath The filepath to write - * \param width The width of the texture - * \param height The height of the texture - * \return true if the save was successful, false otherwise +/*! Save pixel data to a TGA file + * + * @param index Reference to pixel data + * @param filepath The filepath to write + * @param width The width of the texture + * @param height The height of the texture + * @return True if the save was successful, false otherwise */ bool texSaveTGA( const QModelIndex & index, const QString & filepath, GLuint & width, GLuint & height ); -//! Save a file to pixel data -/*! - * \param filepath The source texture to convert - * \param iData The pixel data to write +/*! Save a file to pixel data + * + * @param filepath The source texture to convert + * @param iData The pixel data to write */ bool texSaveNIF( class NifModel * nif, const QString & filepath, QModelIndex & iData ); diff --git a/src/gl/gltools.cpp b/src/gl/gltools.cpp index 1639109aa..3e2abd1ee 100644 --- a/src/gl/gltools.cpp +++ b/src/gl/gltools.cpp @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -32,6 +32,15 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "gltools.h" +#include +#include +#include +#include + +#include +#include +#include + #include "nifmodel.h" @@ -40,8 +49,9 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. BoneWeights::BoneWeights( const NifModel * nif, const QModelIndex & index, int b, int vcnt ) { trans = Transform( nif, index ); - center = nif->get( index, "Center" ); - radius = nif->get( index, "Radius" ); + auto sph = BoundSphere( nif, index ); + center = sph.center; + radius = sph.radius; bone = b; QModelIndex idxWeights = nif->getIndex( index, "Vertex Weights" ); @@ -60,6 +70,14 @@ BoneWeights::BoneWeights( const NifModel * nif, const QModelIndex & index, int b } +void BoneWeights::setTransform( const NifModel * nif, const QModelIndex & index ) +{ + trans = Transform( nif, index ); + auto sph = BoundSphere( nif, index ); + center = sph.center; + radius = sph.radius; +} + SkinPartition::SkinPartition( const NifModel * nif, const QModelIndex & index ) { @@ -121,6 +139,17 @@ BoundSphere::BoundSphere( const BoundSphere & other ) operator=( other ); } +BoundSphere::BoundSphere( const NifModel * nif, const QModelIndex & index ) +{ + auto idx = index; + auto sph = nif->getIndex( idx, "Bounding Sphere" ); + if ( sph.isValid() ) + idx = sph; + + center = nif->get( idx, "Center" ); + radius = nif->get( idx, "Radius" ); +} + BoundSphere::BoundSphere( const QVector & verts ) { if ( verts.isEmpty() ) { @@ -251,6 +280,94 @@ void drawAxes( Vector3 c, float axis ) glPopMatrix(); } +QVector sortAxes( QVector axesDots ) +{ + QVector dotsSorted = axesDots; + std::stable_sort( dotsSorted.begin(), dotsSorted.end() ); + + // Retrieve position of X, Y, Z axes in sorted list + auto x = axesDots.indexOf( dotsSorted[0] ); + auto y = axesDots.indexOf( dotsSorted[1] ); + auto z = axesDots.indexOf( dotsSorted[2] ); + + // When z == 1.0, x and y both == 0 + if ( axesDots[2] == 1.0 ) { + x = 0; + y = 1; + } + + return{ x, y, z }; +} + +void drawAxesOverlay( Vector3 c, float axis, QVector axesOrder ) +{ + glPushMatrix(); + glTranslate( c ); + GLfloat arrow = axis / 36.0; + + glDisable( GL_LIGHTING ); + glDepthFunc( GL_ALWAYS ); + glLineWidth( 2.0f ); + glBegin( GL_LINES ); + + // Render the X axis + std::function xAxis = [axis, arrow]() { + glColor3f( 1.0, 0.0, 0.0 ); + glVertex3f( 0, 0, 0 ); + glVertex3f( +axis, 0, 0 ); + glVertex3f( +axis, 0, 0 ); + glVertex3f( +axis - 3 * arrow, +arrow, +arrow ); + glVertex3f( +axis, 0, 0 ); + glVertex3f( +axis - 3 * arrow, -arrow, +arrow ); + glVertex3f( +axis, 0, 0 ); + glVertex3f( +axis - 3 * arrow, +arrow, -arrow ); + glVertex3f( +axis, 0, 0 ); + glVertex3f( +axis - 3 * arrow, -arrow, -arrow ); + }; + + // Render the Y axis + std::function yAxis = [axis, arrow]() { + glColor3f( 0.0, 1.0, 0.0 ); + glVertex3f( 0, 0, 0 ); + glVertex3f( 0, +axis, 0 ); + glVertex3f( 0, +axis, 0 ); + glVertex3f( +arrow, +axis - 3 * arrow, +arrow ); + glVertex3f( 0, +axis, 0 ); + glVertex3f( -arrow, +axis - 3 * arrow, +arrow ); + glVertex3f( 0, +axis, 0 ); + glVertex3f( +arrow, +axis - 3 * arrow, -arrow ); + glVertex3f( 0, +axis, 0 ); + glVertex3f( -arrow, +axis - 3 * arrow, -arrow ); + }; + + // Render the Z axis + std::function zAxis = [axis, arrow]() { + glColor3f( 0.0, 0.0, 1.0 ); + glVertex3f( 0, 0, 0 ); + glVertex3f( 0, 0, +axis ); + glVertex3f( 0, 0, +axis ); + glVertex3f( +arrow, +arrow, +axis - 3 * arrow ); + glVertex3f( 0, 0, +axis ); + glVertex3f( -arrow, +arrow, +axis - 3 * arrow ); + glVertex3f( 0, 0, +axis ); + glVertex3f( +arrow, -arrow, +axis - 3 * arrow ); + glVertex3f( 0, 0, +axis ); + glVertex3f( -arrow, -arrow, +axis - 3 * arrow ); + }; + + // List of the lambdas + QVector> axes = { xAxis, yAxis, zAxis }; + + // Render the axes in the given order + // e.g. {2, 1, 0} = zAxis(); yAxis(); xAxis(); + for ( auto i : axesOrder ) { + axes[i](); + } + + glEnd(); + glPopMatrix(); +} + void drawBox( Vector3 a, Vector3 b ) { glBegin( GL_LINE_STRIP ); @@ -279,6 +396,35 @@ void drawBox( Vector3 a, Vector3 b ) glEnd(); } +void drawGrid( int s /* grid size */, int line /* line spacing */, int sub /* # subdivisions */ ) +{ + glEnable( GL_BLEND ); + glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + glLineWidth( 1.0f ); + glColor4f( 1.0f, 1.0f, 1.0f, 0.2f ); + + glBegin( GL_LINES ); + for ( int i = -s; i <= s; i += line ) { + glVertex3f( i, -s, 0.0f ); + glVertex3f( i, s, 0.0f ); + glVertex3f( -s, i, 0.0f ); + glVertex3f( s, i, 0.0f ); + } + glEnd(); + + glColor4f( 1.0f, 1.0f, 1.0f, 0.1f ); + glLineWidth( 0.25f ); + glBegin( GL_LINES ); + for ( int i = -s; i <= s; i += line/sub ) { + glVertex3f( i, -s, 0.0f ); + glVertex3f( i, s, 0.0f ); + glVertex3f( -s, i, 0.0f ); + glVertex3f( s, i, 0.0f ); + } + glEnd(); + glDisable( GL_BLEND ); +} + void drawCircle( Vector3 c, Vector3 n, float r, int sd ) { Vector3 x = Vector3::crossproduct( n, Vector3( n[1], n[2], n[0] ) ); @@ -480,6 +626,13 @@ void drawSolidArc( Vector3 c, Vector3 n, Vector3 x, Vector3 y, float an, float a glEnable( GL_CULL_FACE ); } +void drawSphereSimple( Vector3 c, float r, int sd ) +{ + drawCircle( c, Vector3( 0, 0, 1 ), r, sd ); + drawCircle( c, Vector3( 0, 1, 0 ), r, sd ); + drawCircle( c, Vector3( 1, 0, 0 ), r, sd ); +} + void drawSphere( Vector3 c, float r, int sd ) { for ( int j = -sd; j <= sd; j++ ) { @@ -588,30 +741,258 @@ void drawDashLine( Vector3 a, Vector3 b, int sd ) glEnd(); } -void drawConvexHull( QVector vertices, QVector normals, float scale ) +//! Find the dot product of two vectors +static float dotproduct( const Vector3 & v1, const Vector3 & v2 ) { - glBegin( GL_LINES ); + return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2]; +} +//! Find the cross product of two vectors +static Vector3 crossproduct( const Vector3 & a, const Vector3 & b ) +{ + return Vector3( a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0] ); +} + +//! Generate triangles for convex hull +static QVector generateTris( const NifModel * nif, const QModelIndex & iShape, float scale ) +{ + QVector vertices = nif->getArray( iShape, "Vertices" ); + //QVector normals = nif->getArray( iShape, "Normals" ); + + if ( vertices.isEmpty() ) + return QVector(); + + Vector3 A, B, C, N, V; + float D; + int L, prev, eps; + bool good; + + L = vertices.count(); + QVector P( L ); + QVector tris; + + // Convert Vector4 to Vector3 + for ( int v = 0; v < L; v++ ) { + P[v] = Vector3( vertices[v] ); + } + + for ( int i = 0; i < L - 2; i++ ) { + A = P[i]; + + for ( int j = i + 1; j < L - 1; j++ ) { + B = P[j]; + + for ( int k = j + 1; k < L; k++ ) { + C = P[k]; + + prev = 0; + good = true; + + N = crossproduct( (B - A), (C - A) ); + + for ( int p = 0; p < L; p++ ) { + V = P[p]; - for ( int i = 1; i < vertices.count(); i++ ) { - for ( int j = 0; j < i; j++ ) { - glVertex( vertices[i] * scale ); - glVertex( vertices[j] * scale ); + if ( (V == A) || (V == B) || (V == C) ) continue; + + D = dotproduct( (V - A), N ); + + if ( D == 0 ) continue; + + eps = (D > 0) ? 1 : -1; + + if ( eps + prev == 0 ) { + good = false; + continue; + } + + prev = eps; + } + + if ( good ) { + // Append ABC + tris << (A*scale) << (B*scale) << (C*scale); + } + } } } - /* - Vector3 c; - foreach ( Vector4 v, vertices ) - c += Vector3( v ); - if ( vertices.count() ) - c /= vertices.count(); - foreach ( Vector4 n, normals ) - { - glVertex( c + Vector3( n ) * ( n[3] ) ); - glVertex( c + Vector3( n ) * ( - 1 + n[3] ) ); + return tris; +} + +void drawConvexHull( const NifModel * nif, const QModelIndex & iShape, float scale, bool solid ) +{ + static QMap> shapes; + QVector shape; + + shape = shapes[iShape]; + + if ( shape.empty() ) { + shape = generateTris( nif, iShape, scale ); + shapes[iShape] = shape; + } + + glPolygonMode( GL_FRONT_AND_BACK, solid ? GL_FILL : GL_LINE ); + glDisable( GL_CULL_FACE ); + glBegin( GL_TRIANGLES ); + + for ( int i = 0; i < shape.count(); i += 3 ) { + // DRAW ABC + glVertex( shape[i] ); + glVertex( shape[i+1] ); + glVertex( shape[i+2] ); } - */ + glEnd(); + glPolygonMode( GL_FRONT_AND_BACK, solid ? GL_LINE : GL_FILL ); + glEnable( GL_CULL_FACE ); +} + +void drawNiTSS( const NifModel * nif, const QModelIndex & iShape, bool solid ) +{ + QModelIndex iStrips = nif->getIndex( iShape, "Strips Data" ); + for ( int r = 0; r < nif->rowCount( iStrips ); r++ ) { + QModelIndex iStripData = nif->getBlock( nif->getLink( iStrips.child( r, 0 ) ), "NiTriStripsData" ); + if ( iStripData.isValid() ) { + QVector verts = nif->getArray( iStripData, "Vertices" ); + + glPolygonMode( GL_FRONT_AND_BACK, solid ? GL_FILL : GL_LINE ); + glDisable( GL_CULL_FACE ); + glBegin( GL_TRIANGLES ); + + QModelIndex iPoints = nif->getIndex( iStripData, "Points" ); + for ( int r = 0; r < nif->rowCount( iPoints ); r++ ) { // draw the strips like they appear in the tescs + // (use the unstich strips spell to avoid the spider web effect) + QVector strip = nif->getArray( iPoints.child( r, 0 ) ); + if ( strip.count() >= 3 ) { + quint16 a = strip[0]; + quint16 b = strip[1]; + + for ( int x = 2; x < strip.size(); x++ ) { + quint16 c = strip[x]; + glVertex( verts.value( a ) ); + glVertex( verts.value( b ) ); + glVertex( verts.value( c ) ); + a = b; + b = c; + } + } + } + + glEnd(); + glEnable( GL_CULL_FACE ); + glPolygonMode( GL_FRONT_AND_BACK, solid ? GL_LINE : GL_FILL ); + } + } +} + +void drawCMS( const NifModel * nif, const QModelIndex & iShape, bool solid ) +{ + // Scale up for Skyrim + float havokScale = (nif->checkVersion( 0x14020007, 0x14020007 ) && nif->getUserVersion() >= 12) ? 10.0f : 1.0f; + + //QModelIndex iParent = nif->getBlock( nif->getParent( nif->getBlockNumber( iShape ) ) ); + //Vector4 origin = Vector4( nif->get( iParent, "Origin" ), 0 ); + + QModelIndex iData = nif->getBlock( nif->getLink( iShape, "Data" ) ); + if ( iData.isValid() ) { + QModelIndex iBigVerts = nif->getIndex( iData, "Big Verts" ); + QModelIndex iBigTris = nif->getIndex( iData, "Big Tris" ); + QModelIndex iChunkTrans = nif->getIndex( iData, "Chunk Transforms" ); + + QVector verts = nif->getArray( iBigVerts ); + + glPolygonMode( GL_FRONT_AND_BACK, solid ? GL_FILL : GL_LINE ); + glDisable( GL_CULL_FACE ); + + for ( int r = 0; r < nif->rowCount( iBigTris ); r++ ) { + quint16 a = nif->get( iBigTris.child( r, 0 ), "Triangle 1" ); + quint16 b = nif->get( iBigTris.child( r, 0 ), "Triangle 2" ); + quint16 c = nif->get( iBigTris.child( r, 0 ), "Triangle 3" ); + + glBegin( GL_TRIANGLES ); + + glVertex( verts[a] * havokScale ); + glVertex( verts[b] * havokScale ); + glVertex( verts[c] * havokScale ); + + glEnd(); + } + + glPolygonMode( GL_FRONT_AND_BACK, solid ? GL_LINE : GL_FILL ); + glEnable( GL_CULL_FACE ); + + QModelIndex iChunks = nif->getIndex( iData, "Chunks" ); + for ( int r = 0; r < nif->rowCount( iChunks ); r++ ) { + Vector4 chunkOrigin = nif->get( iChunks.child( r, 0 ), "Translation" ); + + quint32 transformIndex = nif->get( iChunks.child( r, 0 ), "Transform Index" ); + QModelIndex chunkTransform = iChunkTrans.child( transformIndex, 0 ); + Vector4 chunkTranslation = nif->get( chunkTransform.child( 0, 0 ) ); + Quat chunkRotation = nif->get( chunkTransform.child( 1, 0 ) ); + + quint32 numOffsets = nif->get( iChunks.child( r, 0 ), "Num Vertices" ); + quint32 numIndices = nif->get( iChunks.child( r, 0 ), "Num Indices" ); + quint32 numStrips = nif->get( iChunks.child( r, 0 ), "Num Strips" ); + QVector offsets = nif->getArray( iChunks.child( r, 0 ), "Vertices" ); + QVector indices = nif->getArray( iChunks.child( r, 0 ), "Indices" ); + QVector strips = nif->getArray( iChunks.child( r, 0 ), "Strips" ); + + QVector vertices( numOffsets / 3 ); + + int numStripVerts = 0; + int offset = 0; + + for ( int v = 0; v < (int)numStrips; v++ ) { + numStripVerts += strips[v]; + } + + for ( int n = 0; n < ((int)numOffsets / 3); n++ ) { + vertices[n] = chunkOrigin + chunkTranslation + Vector4( offsets[3 * n], offsets[3 * n + 1], offsets[3 * n + 2], 0 ) / 1000.0f; + vertices[n] *= havokScale; + } + + glPolygonMode( GL_FRONT_AND_BACK, solid ? GL_FILL : GL_LINE ); + glDisable( GL_CULL_FACE ); + + Transform trans; + trans.rotation.fromQuat( chunkRotation ); + + // Stripped tris + for ( int s = 0; s < (int)numStrips; s++ ) { + + for ( int idx = 0; idx < strips[s] - 2; idx++ ) { + + glBegin( GL_TRIANGLES ); + + glVertex( trans.rotation * Vector3( vertices[indices[offset + idx]] ) ); + glVertex( trans.rotation * Vector3( vertices[indices[offset + idx + 1]] ) ); + glVertex( trans.rotation * Vector3( vertices[indices[offset + idx + 2]] ) ); + + glEnd(); + + } + + offset += strips[s]; + + } + + // Non-stripped tris + for ( int f = 0; f < (int)(numIndices - offset); f += 3 ) { + glBegin( GL_TRIANGLES ); + + glVertex( trans.rotation * Vector3( vertices[indices[offset + f]] ) ); + glVertex( trans.rotation * Vector3( vertices[indices[offset + f + 1]] ) ); + glVertex( trans.rotation * Vector3( vertices[indices[offset + f + 2]] ) ); + + glEnd(); + + } + + glPolygonMode( GL_FRONT_AND_BACK, solid ? GL_LINE : GL_FILL ); + glEnable( GL_CULL_FACE ); + + } + } } // Renders text using the font initialized in the primary view class diff --git a/src/gl/gltools.h b/src/gl/gltools.h index bb2ce95f1..fc54940d9 100644 --- a/src/gl/gltools.h +++ b/src/gl/gltools.h @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -38,7 +38,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include -//! \file gltools.h BoundSphere, VertexWeight, BoneWeights, SkinPartition +//! @file gltools.h BoundSphere, VertexWeight, BoneWeights, SkinPartition //! A bounding sphere for an object, typically a Mesh class BoundSphere final @@ -46,6 +46,7 @@ class BoundSphere final public: BoundSphere(); BoundSphere( const BoundSphere & ); + BoundSphere( const NifModel * nif, const QModelIndex & ); BoundSphere( const Vector3 & center, float radius ); BoundSphere( const QVector & vertices ); @@ -83,6 +84,8 @@ class BoneWeights final BoneWeights() { bone = 0; } BoneWeights( const NifModel * nif, const QModelIndex & index, int b, int vcnt = 0 ); + void setTransform( const NifModel * nif, const QModelIndex & index ); + Transform trans; Vector3 center; float radius; Vector3 tcenter; @@ -107,17 +110,24 @@ class SkinPartition final QList > tristrips; }; +QVector sortAxes( QVector axesDots ); + void drawAxes( Vector3 c, float axis ); +void drawAxesOverlay( Vector3 c, float axis, QVector axesOrder = {2, 1, 0} ); +void drawGrid( int s, int line, int sub ); void drawBox( Vector3 a, Vector3 b ); void drawCircle( Vector3 c, Vector3 n, float r, int sd = 16 ); void drawArc( Vector3 c, Vector3 x, Vector3 y, float an, float ax, int sd = 8 ); void drawSolidArc( Vector3 c, Vector3 n, Vector3 x, Vector3 y, float an, float ax, float r, int sd = 8 ); void drawCone( Vector3 c, Vector3 n, float a, int sd = 16 ); void drawRagdollCone( Vector3 pivot, Vector3 twist, Vector3 plane, float coneAngle, float minPlaneAngle, float maxPlaneAngle, int sd = 16 ); +void drawSphereSimple( Vector3 c, float r, int sd = 36 ); void drawSphere( Vector3 c, float r, int sd = 8 ); void drawCapsule( Vector3 a, Vector3 b, float r, int sd = 5 ); void drawDashLine( Vector3 a, Vector3 b, int sd = 15 ); -void drawConvexHull( QVector vertices, QVector normals, float scale = 1.0f ); +void drawConvexHull( const NifModel * nif, const QModelIndex & iShape, float scale, bool solid = false ); +void drawNiTSS( const NifModel * nif, const QModelIndex & iShape, bool solid = false ); +void drawCMS( const NifModel * nif, const QModelIndex & iShape, bool solid = false ); void drawSpring( Vector3 a, Vector3 b, float stiffness, int sd = 16, bool solid = false ); void drawRail( const Vector3 & a, const Vector3 & b ); diff --git a/src/gl/glcontrolable.h b/src/gl/icontrollable.h similarity index 86% rename from src/gl/glcontrolable.h rename to src/gl/icontrollable.h index 39bb1311d..9861a071e 100644 --- a/src/gl/glcontrolable.h +++ b/src/gl/icontrollable.h @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -30,8 +30,8 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ -#ifndef GLCONTROLABLE_H -#define GLCONTROLABLE_H +#ifndef ICONTROLLABLE_H +#define ICONTROLLABLE_H #include "nifmodel.h" @@ -41,22 +41,28 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include +//! @file icontrollable.h IControllable interface + class Controller; class Scene; //! Anything capable of having a Controller -class Controllable : public QObject +class IControllable : public QObject { + Q_OBJECT + + friend class ControllerManager; + public: - Controllable( Scene * Scene, const QModelIndex & index ); - virtual ~Controllable(); + IControllable( Scene * Scene, const QModelIndex & index ); + virtual ~IControllable(); QModelIndex index() const { return iBlock; } virtual bool isValid() const { return iBlock.isValid(); } virtual void clear(); - virtual void update( const NifModel * nif, const QModelIndex & index ); + virtual void update( const NifModel * nif, const QModelIndex & index ) = 0; virtual void transform(); @@ -65,6 +71,10 @@ class Controllable : public QObject void setSequence( const QString & seqname ); Controller * findController( const QString & ctrltype, const QString & var1, const QString & var2 ); + Controller * findController( const QModelIndex & index ); + + QString getName() const; + protected: //! Sets the Controller virtual void setController( const NifModel * nif, const QModelIndex & iController ) { Q_UNUSED( nif ); Q_UNUSED( iController ); } @@ -76,8 +86,6 @@ class Controllable : public QObject QList controllers; QString name; - - friend class ControllerManager; }; #endif diff --git a/src/gl/marker/constraints.h b/src/gl/marker/constraints.h index 3cdb105ad..49e27e1f9 100644 --- a/src/gl/marker/constraints.h +++ b/src/gl/marker/constraints.h @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/src/gl/marker/furniture.h b/src/gl/marker/furniture.h index f76aaa103..0797bf0b4 100644 --- a/src/gl/marker/furniture.h +++ b/src/gl/marker/furniture.h @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -1083,3 +1083,758 @@ static const unsigned short FurnitureMarker14Faces[] = static const GLMarker FurnitureMarker14 = { 108, 76, FurnitureMarker14Verts, FurnitureMarker14Faces }; + +static const float BedLeftVerts[] = +{ + -65.7945f, -12.1604f, 59.4863f, + -65.7945f, 12.3047f, 59.4863f, + -65.7945f, -12.1604f, 103.8379f, + -65.7945f, 12.3047f, 103.8379f, + -74.1415f, -12.1572f, 59.5118f, + -74.1414f, 12.2701f, 59.5118f, + -74.1297f, -12.1604f, 103.8379f, + -74.1297f, 12.3047f, 103.8379f, + -72.8216f, -12.1682f, 53.8481f, + -67.4439f, -12.1682f, 53.8481f, + -70.1328f, -16.8254f, 53.8481f, + -72.8216f, -12.1682f, 103.8481f, + -67.4439f, -12.1682f, 103.8481f, + -70.1328f, -16.8254f, 98.4131f, + -72.8216f, 12.2981f, 53.8481f, + -67.4439f, 12.2981f, 53.8481f, + -70.1328f, 16.9553f, 53.8481f, + -72.8216f, 12.2981f, 103.8481f, + -67.4439f, 12.2981f, 103.8481f, + -70.1328f, 16.9553f, 98.4131f, + -65.7729f, 7.3971f, 0.0000f, + -74.1533f, 2.5587f, -0.0000f, + -74.1532f, 12.2355f, 0.0000f, + -65.7729f, 7.3971f, 59.5373f, + -74.1533f, 2.5587f, 59.5373f, + -65.7729f, -7.3155f, -0.0000f, + -74.1533f, -12.1539f, -0.0000f, + -74.1532f, -2.4771f, -0.0000f, + -65.7729f, -7.3155f, 59.5373f, + -74.1532f, -2.4771f, 59.5373f, + -74.2853f, -6.6657f, 113.7122f, + -74.2853f, 6.6853f, 113.7122f, + -63.5249f, -6.6657f, 113.7122f, + -63.5249f, 6.6853f, 113.7122f, + -74.2853f, -6.6657f, 126.7122f, + -74.2853f, 6.6853f, 126.7122f, + -63.5249f, -6.6657f, 126.7122f, + -63.5249f, 6.6853f, 126.7122f, + -12.2286f, 19.9788f, 46.4090f, + 12.2365f, 19.9788f, 46.4090f, + -12.2286f, -24.3728f, 46.4090f, + 12.2365f, -24.3728f, 46.4090f, + -12.2254f, 19.9533f, 38.0621f, + 12.2019f, 19.9533f, 38.0621f, + -12.2286f, -24.3728f, 38.0738f, + 12.2365f, -24.3728f, 38.0739f, + -12.2364f, 25.6171f, 39.3819f, + -12.2364f, 25.6171f, 44.7596f, + -16.8937f, 25.6171f, 42.0707f, + -12.2364f, -24.3829f, 39.3819f, + -12.2364f, -24.3829f, 44.7596f, + -16.8937f, -18.9480f, 42.0707f, + 12.2299f, 25.6171f, 39.3819f, + 12.2299f, 25.6171f, 44.7596f, + 16.8871f, 25.6171f, 42.0708f, + 12.2299f, -24.3829f, 39.3819f, + 12.2299f, -24.3829f, 44.7596f, + 16.8871f, -18.9480f, 42.0708f, + 7.3288f, 79.4651f, 46.4306f, + 2.4905f, 79.4651f, 38.0503f, + 12.1672f, 79.4651f, 38.0503f, + 7.3289f, 19.9278f, 46.4306f, + 2.4905f, 19.9278f, 38.0503f, + -7.3837f, 79.4651f, 46.4306f, + -12.2221f, 79.4651f, 38.0503f, + -2.5453f, 79.4651f, 38.0503f, + -7.3837f, 19.9278f, 46.4306f, + -2.5453f, 19.9278f, 38.0503f, + -6.7339f, -34.2471f, 37.9182f, + 6.6171f, -34.2471f, 37.9182f, + -6.7339f, -34.2471f, 48.6786f, + 6.6171f, -34.2471f, 48.6786f, + -6.7339f, -47.2471f, 37.9182f, + 6.6171f, -47.2471f, 37.9182f, + -6.7339f, -47.2471f, 48.6786f, + 6.6171f, -47.2471f, 48.6786f, + -36.3600f, 100.1841f, 38.0476f, + -36.3600f, -66.4381f, 38.0476f, + 34.4878f, 100.1841f, 38.0476f, + 34.4878f, -66.4381f, 38.0476f, + -36.3600f, 100.1841f, 0.0000f, + -36.3600f, 100.1841f, 38.0476f, + 34.4878f, 100.1841f, 0.0000f, + 34.4878f, 100.1841f, 38.0476f, + -36.3600f, -66.4381f, 0.0000f, + -36.3600f, -66.4381f, 38.0476f, + -36.3600f, 100.1841f, 0.0000f, + -36.3600f, 100.1841f, 38.0476f, + 34.4878f, -66.4381f, 0.0000f, + 34.4878f, -66.4381f, 38.0476f, + -36.3600f, -66.4381f, 0.0000f, + -36.3600f, -66.4381f, 38.0476f, + 34.4878f, 100.1841f, 0.0000f, + 34.4878f, 100.1841f, 38.0476f, + 34.4878f, -66.4381f, 0.0000f, + 34.4878f, -66.4381f, 38.0476f, +}; + +static const unsigned short BedLeftFaces[] = +{ + 3, 2, 0, + 0, 1, 3, + 7, 5, 4, + 4, 6, 7, + 13, 10, 9, + 12, 13, 9, + 11, 8, 10, + 13, 11, 10, + 16, 19, 15, + 19, 18, 15, + 14, 17, 16, + 17, 19, 16, + 24, 21, 20, + 23, 24, 20, + 23, 20, 22, + 5, 23, 22, + 4, 26, 25, + 28, 4, 25, + 28, 25, 27, + 29, 28, 27, + 35, 31, 30, + 30, 34, 35, + 37, 33, 31, + 31, 35, 37, + 36, 32, 33, + 33, 37, 36, + 34, 30, 32, + 32, 36, 34, + 38, 40, 41, + 41, 39, 38, + 42, 43, 45, + 45, 44, 42, + 47, 48, 51, + 47, 51, 50, + 48, 46, 49, + 48, 49, 51, + 53, 57, 54, + 53, 56, 57, + 54, 55, 52, + 54, 57, 55, + 58, 59, 62, + 58, 62, 61, + 60, 58, 61, + 60, 61, 43, + 63, 64, 42, + 63, 42, 66, + 65, 63, 66, + 65, 66, 67, + 68, 69, 73, + 73, 72, 68, + 69, 71, 75, + 75, 73, 69, + 71, 70, 74, + 74, 75, 71, + 70, 68, 72, + 72, 74, 70, + 76, 77, 78, + 77, 79, 78, + 80, 81, 82, + 81, 83, 82, + 84, 85, 86, + 85, 87, 86, + 88, 89, 90, + 89, 91, 90, + 92, 93, 94, + 93, 95, 94 +}; + +static const GLMarker BedLeft = { 96, 66, BedLeftVerts, BedLeftFaces }; + +static const float ChairLeftVerts[] = +{ + -48.2526f, -12.1604f, 59.4863f, + -48.2526f, 12.3047f, 59.4863f, + -48.2526f, -12.1604f, 103.8379f, + -48.2526f, 12.3047f, 103.8379f, + -56.5996f, -12.1572f, 59.5118f, + -56.5996f, 12.2701f, 59.5118f, + -56.5878f, -12.1604f, 103.8379f, + -56.5878f, 12.3047f, 103.8379f, + -55.2798f, -12.1682f, 53.8481f, + -49.9021f, -12.1682f, 53.8481f, + -52.5909f, -16.8254f, 53.8481f, + -55.2798f, -12.1682f, 103.8481f, + -49.9021f, -12.1682f, 103.8481f, + -52.5909f, -16.8254f, 98.4131f, + -55.2798f, 12.2981f, 53.8481f, + -49.9020f, 12.2981f, 53.8481f, + -52.5909f, 16.9553f, 53.8481f, + -55.2798f, 12.2981f, 103.8481f, + -49.9020f, 12.2981f, 103.8481f, + -52.5909f, 16.9553f, 98.4131f, + -48.2311f, 7.3971f, 0.0000f, + -56.6114f, 2.5587f, -0.0000f, + -56.6114f, 12.2355f, 0.0000f, + -48.2311f, 7.3971f, 59.5373f, + -56.6114f, 2.5587f, 59.5373f, + -48.2311f, -7.3155f, -0.0000f, + -56.6114f, -12.1539f, -0.0000f, + -56.6114f, -2.4771f, -0.0000f, + -48.2311f, -7.3155f, 59.5373f, + -56.6114f, -2.4771f, 59.5373f, + -56.7435f, -6.6656f, 113.7122f, + -56.7435f, 6.6853f, 113.7122f, + -45.9830f, -6.6657f, 113.7122f, + -45.9830f, 6.6853f, 113.7122f, + -56.7435f, -6.6657f, 126.7122f, + -56.7435f, 6.6853f, 126.7122f, + -45.9831f, -6.6657f, 126.7122f, + -45.9830f, 6.6853f, 126.7122f, + 12.4389f, 5.2796f, 32.1170f, + -12.0262f, 5.2796f, 32.1170f, + 12.4389f, 5.2796f, 89.7628f, + -12.0262f, 5.2796f, 89.7628f, + 12.4357f, -3.1702f, 32.1276f, + -11.9916f, -3.1691f, 32.1307f, + 12.4389f, -3.0555f, 89.7628f, + -12.0262f, -3.0555f, 89.7628f, + 12.4467f, -1.7475f, 39.7729f, + 12.4467f, 3.6302f, 39.7729f, + 17.1039f, 0.9414f, 39.7729f, + 12.4467f, -1.7475f, 89.7729f, + 12.4467f, 3.6302f, 89.7729f, + 17.1039f, 0.9414f, 84.3379f, + -12.0196f, -1.7475f, 39.7729f, + -12.0196f, 3.6302f, 39.7729f, + -16.6768f, 0.9414f, 39.7729f, + -12.0196f, -1.7475f, 89.7729f, + -12.0196f, 3.6302f, 89.7729f, + -16.6768f, 0.9414f, 84.3379f, + -7.1186f, 34.6358f, 40.4681f, + -2.2802f, 26.2996f, 32.1189f, + -11.9570f, 26.2996f, 32.1189f, + -7.1186f, 5.2776f, 40.4992f, + -2.2802f, 5.2776f, 32.1189f, + 7.5940f, 34.6358f, 40.4681f, + 12.4324f, 26.2985f, 32.1158f, + 2.7556f, 26.2985f, 32.1158f, + 7.5940f, 5.2765f, 40.4961f, + 2.7556f, 5.2765f, 32.1158f, + 6.9442f, -3.2112f, 101.6667f, + -6.4068f, -3.2112f, 101.6667f, + 6.9442f, 7.5492f, 101.6667f, + -6.4068f, 7.5492f, 101.6667f, + 6.9442f, -3.2112f, 114.6667f, + -6.4068f, -3.2112f, 114.6667f, + 6.9441f, 7.5492f, 114.6667f, + -6.4068f, 7.5492f, 114.6667f, + -7.1186f, 39.1596f, -0.1588f, + -2.2802f, 30.7792f, -0.1588f, + -11.9570f, 30.7792f, -0.1588f, + 7.5940f, 39.1596f, -0.1588f, + 12.4324f, 30.7792f, -0.1588f, + 2.7556f, 30.7792f, -0.1588f, + 12.4357f, 5.3020f, 32.1276f, + -11.9916f, 5.3031f, 32.1307f, + 16.4333f, -15.9701f, 0.0000f, + -16.5005f, -15.9701f, 0.0000f, + 16.4333f, 15.8041f, 0.0000f, + -16.5005f, 15.8041f, 0.0000f, + -16.5005f, 15.8041f, 32.0000f, + -16.5005f, -15.9701f, 32.0000f, + 16.4333f, 15.8041f, 32.0000f, + 16.4333f, -15.9701f, 32.0000f, + -16.5005f, 15.8041f, 0.0000f, + -16.5005f, 15.8041f, 32.0000f, + 16.4333f, 15.8041f, 0.0000f, + 16.4333f, 15.8041f, 32.0000f, + -16.5005f, -15.9701f, 0.0000f, + -16.5005f, -15.9701f, 32.0000f, + -16.5005f, 15.8041f, 0.0000f, + -16.5005f, 15.8041f, 32.0000f, + 16.4333f, -15.9701f, 0.0000f, + 16.4333f, -15.9701f, 32.0000f, + -16.5005f, -15.9701f, 0.0000f, + -16.5005f, -15.9701f, 32.0000f, + 16.4333f, 15.8041f, 0.0000f, + 16.4333f, 15.8041f, 32.0000f, + 16.4333f, -15.9701f, 0.0000f, + 16.4333f, -15.9701f, 32.0000f, +}; + +static const unsigned short ChairLeftFaces[] = +{ + 3, 2, 0, + 0, 1, 3, + 7, 5, 4, + 4, 6, 7, + 13, 10, 9, + 12, 13, 9, + 11, 8, 10, + 13, 11, 10, + 16, 19, 15, + 19, 18, 15, + 14, 17, 16, + 17, 19, 16, + 24, 21, 20, + 23, 24, 20, + 23, 20, 22, + 5, 23, 22, + 4, 26, 25, + 28, 4, 25, + 28, 25, 27, + 29, 28, 27, + 35, 31, 30, + 30, 34, 35, + 37, 33, 31, + 31, 35, 37, + 36, 32, 33, + 33, 37, 36, + 34, 30, 32, + 32, 36, 34, + 41, 40, 38, + 38, 39, 41, + 45, 43, 42, + 42, 44, 45, + 51, 48, 47, + 50, 51, 47, + 49, 46, 48, + 51, 49, 48, + 54, 57, 53, + 57, 56, 53, + 52, 55, 54, + 55, 57, 54, + 62, 59, 58, + 61, 62, 58, + 61, 58, 60, + 83, 61, 60, + 82, 64, 63, + 66, 82, 63, + 66, 63, 65, + 67, 66, 65, + 73, 69, 68, + 68, 72, 73, + 75, 71, 69, + 69, 73, 75, + 74, 70, 71, + 71, 75, 74, + 72, 68, 70, + 70, 74, 72, + 59, 77, 76, + 58, 59, 76, + 58, 76, 78, + 60, 58, 78, + 64, 80, 79, + 63, 64, 79, + 63, 79, 81, + 65, 63, 81, + 84, 85, 86, + 85, 87, 86, + 88, 89, 90, + 89, 91, 90, + 92, 93, 94, + 93, 95, 94, + 96, 97, 98, + 97, 99, 98, + 100, 101, 102, + 101, 103, 102, + 104, 105, 106, + 105, 107, 106 +}; + +static const GLMarker ChairLeft = { 108, 76, ChairLeftVerts, ChairLeftFaces }; + +static const float ChairFrontVerts[] = +{ + -12.1604f, 51.7602f, 59.4863f, + 12.3047f, 51.7602f, 59.4863f, + -12.1604f, 51.7602f, 103.8379f, + 12.3047f, 51.7602f, 103.8379f, + -12.1572f, 60.1072f, 59.5118f, + 12.2701f, 60.1072f, 59.5118f, + -12.1604f, 60.0954f, 103.8379f, + 12.3047f, 60.0954f, 103.8379f, + -12.1682f, 58.7873f, 53.8481f, + -12.1682f, 53.4096f, 53.8481f, + -16.8254f, 56.0985f, 53.8481f, + -12.1682f, 58.7873f, 103.8481f, + -12.1682f, 53.4096f, 103.8481f, + -16.8254f, 56.0985f, 98.4131f, + 12.2981f, 58.7873f, 53.8481f, + 12.2981f, 53.4096f, 53.8481f, + 16.9553f, 56.0985f, 53.8481f, + 12.2981f, 58.7873f, 103.8481f, + 12.2981f, 53.4096f, 103.8481f, + 16.9553f, 56.0985f, 98.4131f, + 7.3971f, 51.7386f, 0.0000f, + 2.5587f, 60.1190f, -0.0000f, + 12.2355f, 60.1190f, 0.0000f, + 7.3971f, 51.7386f, 59.5373f, + 2.5587f, 60.1190f, 59.5373f, + -7.3155f, 51.7386f, -0.0000f, + -12.1539f, 60.1190f, -0.0000f, + -2.4771f, 60.1190f, -0.0000f, + -7.3155f, 51.7386f, 59.5373f, + -2.4771f, 60.1190f, 59.5373f, + -6.6656f, 60.2511f, 113.7122f, + 6.6853f, 60.2511f, 113.7122f, + -6.6657f, 49.4906f, 113.7122f, + 6.6853f, 49.4906f, 113.7122f, + -6.6657f, 60.2511f, 126.7122f, + 6.6853f, 60.2511f, 126.7122f, + -6.6657f, 49.4906f, 126.7122f, + 6.6853f, 49.4906f, 126.7122f, + 11.9130f, 3.2433f, 32.1170f, + -12.5522f, 3.2433f, 32.1170f, + 11.9130f, 3.2433f, 89.7628f, + -12.5522f, 3.2433f, 89.7628f, + 11.9097f, -5.2066f, 32.1276f, + -12.5175f, -5.2055f, 32.1307f, + 11.9130f, -5.0919f, 89.7628f, + -12.5522f, -5.0919f, 89.7628f, + 11.9208f, -3.7838f, 39.7729f, + 11.9208f, 1.5939f, 39.7729f, + 16.5780f, -1.0950f, 39.7729f, + 11.9208f, -3.7838f, 89.7729f, + 11.9208f, 1.5939f, 89.7729f, + 16.5780f, -1.0950f, 84.3379f, + -12.5455f, -3.7838f, 39.7729f, + -12.5455f, 1.5939f, 39.7729f, + -17.2028f, -1.0950f, 39.7729f, + -12.5455f, -3.7838f, 89.7729f, + -12.5455f, 1.5939f, 89.7729f, + -17.2028f, -1.0950f, 84.3379f, + -7.6445f, 32.5994f, 40.4681f, + -2.8061f, 24.2633f, 32.1189f, + -12.4829f, 24.2633f, 32.1189f, + -7.6445f, 3.2412f, 40.4992f, + -2.8061f, 3.2412f, 32.1189f, + 7.0681f, 32.5994f, 40.4681f, + 11.9065f, 24.2621f, 32.1158f, + 2.2297f, 24.2621f, 32.1158f, + 7.0681f, 3.2401f, 40.4961f, + 2.2297f, 3.2401f, 32.1158f, + 6.4182f, -5.2476f, 101.6667f, + -6.9327f, -5.2476f, 101.6667f, + 6.4182f, 5.5129f, 101.6667f, + -6.9327f, 5.5129f, 101.6667f, + 6.4182f, -5.2476f, 114.6667f, + -6.9327f, -5.2476f, 114.6667f, + 6.4182f, 5.5129f, 114.6667f, + -6.9327f, 5.5129f, 114.6667f, + -7.6445f, 37.1232f, -0.1588f, + -2.8061f, 28.7429f, -0.1588f, + -12.4829f, 28.7429f, -0.1588f, + 7.0681f, 37.1232f, -0.1588f, + 11.9065f, 28.7429f, -0.1588f, + 2.2297f, 28.7429f, -0.1588f, + 11.9097f, 3.2656f, 32.1276f, + -12.5175f, 3.2667f, 32.1307f, + -16.5321f, 15.8488f, -0.0000f, + 16.4017f, 15.8488f, -0.0000f, + -16.5321f, -15.9254f, -0.0000f, + 16.4017f, -15.9254f, -0.0000f, + 16.4017f, -15.9254f, 32.0000f, + 16.4017f, 15.8488f, 32.0000f, + -16.5321f, -15.9254f, 32.0000f, + -16.5321f, 15.8488f, 32.0000f, + 16.4017f, -15.9254f, -0.0000f, + 16.4017f, -15.9254f, 32.0000f, + -16.5321f, -15.9254f, -0.0000f, + -16.5321f, -15.9254f, 32.0000f, + 16.4017f, 15.8488f, -0.0000f, + 16.4017f, 15.8488f, 32.0000f, + 16.4017f, -15.9254f, -0.0000f, + 16.4017f, -15.9254f, 32.0000f, + -16.5321f, 15.8488f, -0.0000f, + -16.5321f, 15.8488f, 32.0000f, + 16.4017f, 15.8488f, -0.0000f, + 16.4017f, 15.8488f, 32.0000f, + -16.5321f, -15.9254f, -0.0000f, + -16.5321f, -15.9254f, 32.0000f, + -16.5321f, 15.8488f, -0.0000f, + -16.5321f, 15.8488f, 32.0000f, +}; + +static const unsigned short ChairFrontFaces[] = +{ + 3, 2, 0, + 0, 1, 3, + 7, 5, 4, + 4, 6, 7, + 13, 10, 9, + 12, 13, 9, + 11, 8, 10, + 13, 11, 10, + 16, 19, 15, + 19, 18, 15, + 14, 17, 16, + 17, 19, 16, + 24, 21, 20, + 23, 24, 20, + 23, 20, 22, + 5, 23, 22, + 4, 26, 25, + 28, 4, 25, + 28, 25, 27, + 29, 28, 27, + 35, 31, 30, + 30, 34, 35, + 37, 33, 31, + 31, 35, 37, + 36, 32, 33, + 33, 37, 36, + 34, 30, 32, + 32, 36, 34, + 41, 40, 38, + 38, 39, 41, + 45, 43, 42, + 42, 44, 45, + 51, 48, 47, + 50, 51, 47, + 49, 46, 48, + 51, 49, 48, + 54, 57, 53, + 57, 56, 53, + 52, 55, 54, + 55, 57, 54, + 62, 59, 58, + 61, 62, 58, + 61, 58, 60, + 83, 61, 60, + 82, 64, 63, + 66, 82, 63, + 66, 63, 65, + 67, 66, 65, + 73, 69, 68, + 68, 72, 73, + 75, 71, 69, + 69, 73, 75, + 74, 70, 71, + 71, 75, 74, + 72, 68, 70, + 70, 74, 72, + 59, 77, 76, + 58, 59, 76, + 58, 76, 78, + 60, 58, 78, + 64, 80, 79, + 63, 64, 79, + 63, 79, 81, + 65, 63, 81, + 84, 85, 86, + 85, 87, 86, + 88, 89, 90, + 89, 91, 90, + 92, 93, 94, + 93, 95, 94, + 96, 97, 98, + 97, 99, 98, + 100, 101, 102, + 101, 103, 102, + 104, 105, 106, + 105, 107, 106 +}; + +static const GLMarker ChairFront = { 108, 76, ChairFrontVerts, ChairFrontFaces }; + + +static const float ChairBehindVerts[] = +{ + 12.1604f, -51.9449f, 59.4863f, + -12.3047f, -51.9449f, 59.4863f, + 12.1604f, -51.9449f, 103.8379f, + -12.3047f, -51.9449f, 103.8379f, + 12.1572f, -60.2919f, 59.5118f, + -12.2701f, -60.2919f, 59.5118f, + 12.1604f, -60.2801f, 103.8379f, + -12.3047f, -60.2801f, 103.8379f, + 12.1682f, -58.9720f, 53.8481f, + 12.1682f, -53.5943f, 53.8481f, + 16.8254f, -56.2832f, 53.8481f, + 12.1682f, -58.9720f, 103.8481f, + 12.1682f, -53.5943f, 103.8481f, + 16.8255f, -56.2832f, 98.4131f, + -12.2981f, -58.9720f, 53.8481f, + -12.2981f, -53.5943f, 53.8481f, + -16.9553f, -56.2832f, 53.8481f, + -12.2981f, -58.9720f, 103.8481f, + -12.2981f, -53.5943f, 103.8481f, + -16.9553f, -56.2832f, 98.4131f, + -7.3971f, -51.9233f, 0.0000f, + -2.5587f, -60.3037f, -0.0000f, + -12.2355f, -60.3037f, 0.0000f, + -7.3971f, -51.9233f, 59.5373f, + -2.5587f, -60.3037f, 59.5373f, + 7.3155f, -51.9233f, -0.0000f, + 12.1539f, -60.3037f, -0.0000f, + 2.4771f, -60.3037f, -0.0000f, + 7.3155f, -51.9233f, 59.5373f, + 2.4771f, -60.3037f, 59.5373f, + 6.6657f, -60.4357f, 113.7122f, + -6.6853f, -60.4357f, 113.7122f, + 6.6657f, -49.6753f, 113.7122f, + -6.6853f, -49.6753f, 113.7122f, + 6.6657f, -60.4357f, 126.7122f, + -6.6853f, -60.4357f, 126.7122f, + 6.6657f, -49.6753f, 126.7122f, + -6.6853f, -49.6753f, 126.7122f, + 12.5377f, 5.2163f, 32.1170f, + -11.9274f, 5.2163f, 32.1170f, + 12.5377f, 5.2163f, 89.7628f, + -11.9274f, 5.2163f, 89.7628f, + 12.5345f, -3.2336f, 32.1276f, + -11.8928f, -3.2325f, 32.1307f, + 12.5377f, -3.1189f, 89.7628f, + -11.9274f, -3.1189f, 89.7628f, + 12.5455f, -1.8109f, 39.7729f, + 12.5455f, 3.5669f, 39.7729f, + 17.2028f, 0.8780f, 39.7729f, + 12.5455f, -1.8109f, 89.7729f, + 12.5455f, 3.5669f, 89.7729f, + 17.2028f, 0.8780f, 84.3379f, + -11.9208f, -1.8109f, 39.7729f, + -11.9208f, 3.5669f, 39.7729f, + -16.5780f, 0.8780f, 39.7729f, + -11.9208f, -1.8109f, 89.7729f, + -11.9208f, 3.5669f, 89.7729f, + -16.5780f, 0.8780f, 84.3379f, + -7.0198f, 34.5724f, 40.4681f, + -2.1814f, 26.2362f, 32.1189f, + -11.8581f, 26.2363f, 32.1189f, + -7.0198f, 5.2142f, 40.4992f, + -2.1814f, 5.2142f, 32.1189f, + 7.6928f, 34.5724f, 40.4681f, + 12.5312f, 26.2351f, 32.1158f, + 2.8544f, 26.2351f, 32.1158f, + 7.6928f, 5.2131f, 40.4961f, + 2.8544f, 5.2131f, 32.1158f, + 7.0430f, -3.2746f, 101.6667f, + -6.3080f, -3.2746f, 101.6667f, + 7.0430f, 7.4859f, 101.6667f, + -6.3080f, 7.4859f, 101.6667f, + 7.0430f, -3.2746f, 114.6667f, + -6.3080f, -3.2746f, 114.6667f, + 7.0430f, 7.4859f, 114.6667f, + -6.3080f, 7.4859f, 114.6667f, + -7.0198f, 39.0962f, -0.1588f, + -2.1814f, 30.7159f, -0.1588f, + -11.8581f, 30.7159f, -0.1588f, + 7.6928f, 39.0962f, -0.1588f, + 12.5312f, 30.7159f, -0.1588f, + 2.8544f, 30.7159f, -0.1588f, + 12.5345f, 5.2386f, 32.1276f, + -11.8928f, 5.2397f, 32.1307f, + 16.5321f, -16.0335f, -0.0000f, + -16.4017f, -16.0335f, -0.0000f, + 16.5321f, 15.7407f, -0.0000f, + -16.4017f, 15.7407f, -0.0000f, + -16.4017f, 15.7407f, 32.0000f, + -16.4017f, -16.0335f, 32.0000f, + 16.5321f, 15.7407f, 32.0000f, + 16.5321f, -16.0335f, 32.0000f, + -16.4017f, 15.7407f, -0.0000f, + -16.4017f, 15.7407f, 32.0000f, + 16.5321f, 15.7407f, -0.0000f, + 16.5321f, 15.7407f, 32.0000f, + -16.4017f, -16.0335f, -0.0000f, + -16.4017f, -16.0335f, 32.0000f, + -16.4017f, 15.7407f, -0.0000f, + -16.4017f, 15.7407f, 32.0000f, + 16.5321f, -16.0335f, -0.0000f, + 16.5321f, -16.0335f, 32.0000f, + -16.4017f, -16.0335f, -0.0000f, + -16.4017f, -16.0335f, 32.0000f, + 16.5321f, 15.7407f, -0.0000f, + 16.5321f, 15.7407f, 32.0000f, + 16.5321f, -16.0335f, -0.0000f, + 16.5321f, -16.0335f, 32.0000f, +}; + +static const unsigned short ChairBehindFaces[] = +{ + 3, 2, 0, + 0, 1, 3, + 7, 5, 4, + 4, 6, 7, + 13, 10, 9, + 12, 13, 9, + 11, 8, 10, + 13, 11, 10, + 16, 19, 15, + 19, 18, 15, + 14, 17, 16, + 17, 19, 16, + 24, 21, 20, + 23, 24, 20, + 23, 20, 22, + 5, 23, 22, + 4, 26, 25, + 28, 4, 25, + 28, 25, 27, + 29, 28, 27, + 35, 31, 30, + 30, 34, 35, + 37, 33, 31, + 31, 35, 37, + 36, 32, 33, + 33, 37, 36, + 34, 30, 32, + 32, 36, 34, + 41, 40, 38, + 38, 39, 41, + 45, 43, 42, + 42, 44, 45, + 51, 48, 47, + 50, 51, 47, + 49, 46, 48, + 51, 49, 48, + 54, 57, 53, + 57, 56, 53, + 52, 55, 54, + 55, 57, 54, + 62, 59, 58, + 61, 62, 58, + 61, 58, 60, + 83, 61, 60, + 82, 64, 63, + 66, 82, 63, + 66, 63, 65, + 67, 66, 65, + 73, 69, 68, + 68, 72, 73, + 75, 71, 69, + 69, 73, 75, + 74, 70, 71, + 71, 75, 74, + 72, 68, 70, + 70, 74, 72, + 59, 77, 76, + 58, 59, 76, + 58, 76, 78, + 60, 58, 78, + 64, 80, 79, + 63, 64, 79, + 63, 79, 81, + 65, 63, 81, + 84, 85, 86, + 85, 87, 86, + 88, 89, 90, + 89, 91, 90, + 92, 93, 94, + 93, 95, 94, + 96, 97, 98, + 97, 99, 98, + 100, 101, 102, + 101, 103, 102, + 104, 105, 106, + 105, 107, 106 +}; + +static const GLMarker ChairBehind = { 108, 76, ChairBehindVerts, ChairBehindFaces }; diff --git a/src/gl/renderer.cpp b/src/gl/renderer.cpp index 5c5231970..2693136ae 100644 --- a/src/gl/renderer.cpp +++ b/src/gl/renderer.cpp @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -31,12 +31,13 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "renderer.h" -#include "options.h" +#include "settings.h" #include "glmesh.h" #include "glproperty.h" #include "glscene.h" #include "gltex.h" +#include "material.h" #include #include @@ -44,30 +45,29 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include #include +//! @file renderer.cpp Renderer and child classes implementation + bool shader_initialized = false; -bool shader_ready = false; +bool shader_ready = true; bool Renderer::initialize() { if ( !shader_initialized ) { -#ifdef DISABLE_SHADERS - shader_ready = false; -#else // check for OpenGL 2.0 // (we don't use the extension API but the 2.0 API for shaders) - if ( fn->hasOpenGLFeature( QOpenGLFunctions::Shaders ) ) { + if ( cfg.useShaders && fn->hasOpenGLFeature( QOpenGLFunctions::Shaders ) ) { shader_ready = true; + shader_initialized = true; } else { shader_ready = false; } -#endif - //qWarning() << "shader support" << shader_ready; - shader_initialized = true; + //qDebug() << "shader support" << shader_ready; } return shader_ready; @@ -85,7 +85,8 @@ const QHash Renderer::ConditionSingle: { GE, " >= " }, { LT, " < " }, { GT, " > " }, - { AND, " & " } + { AND, " & " }, + { NAND, " !& " } }; Renderer::ConditionSingle::ConditionSingle( const QString & line, bool neg ) : invert( neg ) @@ -120,7 +121,7 @@ QModelIndex Renderer::ConditionSingle::getIndex( const NifModel * nif, const QLi QString childid; if ( blkid.startsWith( "HEADER/" ) ) - return nif->getIndex( nif->getHeader(), blkid ); + return nif->getIndex( nif->getHeader(), blkid.remove( "HEADER/" ) ); int pos = blkid.indexOf( "/" ); @@ -190,7 +191,7 @@ void Renderer::ConditionGroup::addCondition( Condition * c ) } Renderer::Shader::Shader( const QString & n, GLenum t, QOpenGLFunctions * fn ) - : name( n ), id( 0 ), status( false ), type( t ), f( fn ) + : f( fn ), name( n ), id( 0 ), status( false ), type( t ) { id = f->glCreateShader( type ); } @@ -233,7 +234,7 @@ bool Renderer::Shader::load( const QString & filepath ) catch ( QString err ) { status = false; - qWarning() << "error loading shader" << name << ":\r\n" << err.toLatin1().data(); + Message::append( QObject::tr( "There were errors during shader compilation" ), QString( "%1:\r\n\r\n%2" ).arg( name ).arg( err ) ); return false; } status = true; @@ -242,7 +243,7 @@ bool Renderer::Shader::load( const QString & filepath ) Renderer::Program::Program( const QString & n, QOpenGLFunctions * fn ) - : name( n ), id( 0 ), status( false ), f( fn ) + : f( fn ), name( n ), id( 0 ) { id = f->glCreateProgram(); } @@ -354,7 +355,7 @@ bool Renderer::Program::load( const QString & filepath, Renderer * renderer ) catch ( QString x ) { status = false; - qWarning() << "error loading shader program " << name << ":\r\n" << x.toLatin1().data(); + Message::append( QObject::tr( "There were errors during shader compilation" ), QString( "%1:\r\n\r\n%2" ).arg( name ).arg( x ) ); return false; } status = true; @@ -364,6 +365,9 @@ bool Renderer::Program::load( const QString & filepath, Renderer * renderer ) Renderer::Renderer( QOpenGLContext * c, QOpenGLFunctions * f ) : cx( c ), fn( f ) { + updateSettings(); + + connect( NifSkope::getOptions(), &SettingsDialog::saveSettings, this, &Renderer::updateSettings ); } Renderer::~Renderer() @@ -371,6 +375,22 @@ Renderer::~Renderer() releaseShaders(); } + +void Renderer::updateSettings() +{ + QSettings settings; + + cfg.useShaders = settings.value( "Settings/Render/General/Use Shaders", true ).toBool(); + + bool prevStatus = shader_ready; + + shader_ready = cfg.useShaders && fn->hasOpenGLFeature( QOpenGLFunctions::Shaders ); + if ( !shader_initialized && shader_ready && !prevStatus ) { + updateShaders(); + shader_initialized = true; + } +} + void Renderer::updateShaders() { if ( !shader_ready ) @@ -424,12 +444,12 @@ void Renderer::releaseShaders() shaders.clear(); } -QString Renderer::setupProgram( Mesh * mesh, const QString & hint ) +QString Renderer::setupProgram( Shape * mesh, const QString & hint ) { PropertyList props; mesh->activeProperties( props ); - if ( !shader_ready || !Options::shaders() ) { + if ( !shader_ready || (mesh->scene->options & Scene::DisableShaders) || (mesh->scene->visMode & Scene::VisSilhouette) ) { setupFixedFunction( mesh, props ); return QString( "fixed function pipeline" ); } @@ -467,7 +487,7 @@ void Renderer::stopProgram() resetTextureUnits(); } -bool Renderer::setupProgram( Program * prog, Mesh * mesh, const PropertyList & props, const QList & iBlocks ) +bool Renderer::setupProgram( Program * prog, Shape * mesh, const PropertyList & props, const QList & iBlocks ) { const NifModel * nif = qobject_cast( mesh->index().model() ); @@ -479,13 +499,38 @@ bool Renderer::setupProgram( Program * prog, Mesh * mesh, const PropertyList & p fn->glUseProgram( prog->id ); + auto opts = mesh->scene->options; + auto vis = mesh->scene->visMode; + + Material * mat = nullptr; + if ( mesh->bslsp && mesh->bslsp->mat() ) + mat = mesh->bslsp->mat(); + else if ( mesh->bsesp && mesh->bsesp->mat() ) + mat = mesh->bsesp->mat(); + + QString diff; + + if ( (opts & Scene::DoLighting) && (vis & Scene::VisNormalsOnly) ) + diff = "shaders/white.dds"; + // texturing TexturingProperty * texprop = props.get(); BSShaderLightingProperty * bsprop = props.get(); + // TODO: BSLSP has been split off from BSShaderLightingProperty so it needs + // to be accessible from here + + TexClampMode clamp = TexClampMode::WRAP_S_WRAP_T; + + if ( mesh->bslsp ) { + clamp = mesh->bslsp->getClampMode(); + } + int texunit = 0; + //GLint baseWidth, baseHeight; + GLint uniBaseMap = fn->glGetUniformLocation( prog->id, "BaseMap" ); if ( uniBaseMap >= 0 ) { @@ -495,9 +540,12 @@ bool Renderer::setupProgram( Program * prog, Mesh * mesh, const PropertyList & p if ( !activateTextureUnit( texunit ) ) return false; - if ( (texprop && !texprop->bind( 0 )) || (bsprop && !bsprop->bind( 0 )) ) + if ( (texprop && !texprop->bind( 0 )) || (bsprop && !bsprop->bind( 0, diff, clamp )) ) return false; + //glGetTexLevelParameteriv( GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, (GLint *)&baseWidth ); + //glGetTexLevelParameteriv( GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, (GLint *)&baseHeight ); + fn->glUniform1i( uniBaseMap, texunit++ ); } @@ -514,7 +562,7 @@ bool Renderer::setupProgram( Program * prog, Mesh * mesh, const PropertyList & p if ( pos >= 0 ) fname = fname.left( pos ) + "_n.dds"; - else if ( ( pos = fname.lastIndexOf( "." ) ) >= 0 ) + else if ( (pos = fname.lastIndexOf( "." )) >= 0 ) fname = fname.insert( pos, "_n" ); if ( !activateTextureUnit( texunit ) || !texprop->bind( 0, fname ) ) @@ -522,7 +570,10 @@ bool Renderer::setupProgram( Program * prog, Mesh * mesh, const PropertyList & p } else if ( bsprop ) { QString fname = bsprop->fileName( 1 ); - if ( !fname.isEmpty() && (!activateTextureUnit( texunit ) || !bsprop->bind( 1, fname )) ) + if ( !(opts & Scene::DoLighting) ) + fname = "shaders/default_n.dds"; + + if ( !fname.isEmpty() && (!activateTextureUnit( texunit ) || !bsprop->bind( 1, fname, clamp )) ) return false; } @@ -542,7 +593,7 @@ bool Renderer::setupProgram( Program * prog, Mesh * mesh, const PropertyList & p if ( pos >= 0 ) fname = fname.left( pos ) + "_g.dds"; - else if ( ( pos = fname.lastIndexOf( "." ) ) >= 0 ) + else if ( (pos = fname.lastIndexOf( "." )) >= 0 ) fname = fname.insert( pos, "_g" ); if ( !activateTextureUnit( texunit ) || !texprop->bind( 0, fname ) ) @@ -550,13 +601,347 @@ bool Renderer::setupProgram( Program * prog, Mesh * mesh, const PropertyList & p } else if ( bsprop ) { QString fname = bsprop->fileName( 2 ); - if ( !fname.isEmpty() && (!activateTextureUnit( texunit ) || !bsprop->bind( 2, fname )) ) + if ( !fname.isEmpty() && (!activateTextureUnit( texunit ) || !bsprop->bind( 2, fname, clamp )) ) return false; } fn->glUniform1i( uniGlowMap, texunit++ ); } + + // Sets a float + auto uni1f = [this, prog, mesh]( const char * var, float x ) { + GLint uni = fn->glGetUniformLocation( prog->id, var ); + if ( uni >= 0 ) + fn->glUniform1f( uni, x ); + }; + + // Sets a vec2 (two floats) + auto uni2f = [this, prog, mesh]( const char * var, float x, float y ) { + GLint uni = fn->glGetUniformLocation( prog->id, var ); + if ( uni >= 0 ) + fn->glUniform2f( uni, x, y ); + }; + + // Sets a vec3 (three floats) + auto uni3f = [this, prog, mesh]( const char * var, float x, float y, float z ) { + GLint uni = fn->glGetUniformLocation( prog->id, var ); + if ( uni >= 0 ) + fn->glUniform3f( uni, x, y, z ); + }; + + // Sets a vec4 (four floats) + auto uni4f = [this, prog, mesh]( const char * var, float x, float y, float z, float w ) { + GLint uni = fn->glGetUniformLocation( prog->id, var ); + if ( uni >= 0 ) + fn->glUniform4f( uni, x, y, z, w ); + }; + + // Sets an integer or boolean + auto uni1i = [this, prog, mesh]( const char * var, int val ) { + GLint uni = fn->glGetUniformLocation( prog->id, var ); + if ( uni >= 0 ) + fn->glUniform1i( uni, val ); + }; + + // Sets a mat3 (3x3 matrix) + auto uni3m = [this, prog, mesh]( const char * var, Matrix val ) { + GLint uni = fn->glGetUniformLocation( prog->id, var ); + if ( uni >= 0 ) { + fn->glUniformMatrix3fv( uni, 1, 0, val.data() ); + } + }; + + // Sets a mat4 (4x4 matrix) + auto uni4m = [this, prog, mesh]( const char * var, Matrix4 val ) { + GLint uni = fn->glGetUniformLocation( prog->id, var ); + if ( uni >= 0 ) { + fn->glUniformMatrix4fv( uni, 1, 0, val.data() ); + } + }; + + // Sets a sampler2D (texture sampler) + auto uniSampler = [this, prog, bsprop, &texunit]( const char * var, int textureSlot, QString alternate, TexClampMode clamp ) { + GLint uniSamp = fn->glGetUniformLocation( prog->id, var ); + if ( uniSamp >= 0 ) { + + QString fname = bsprop->fileName( textureSlot ); + if ( fname.isEmpty() ) + fname = alternate; + + if ( !fname.isEmpty() && (!activateTextureUnit( texunit ) || !bsprop->bind( textureSlot, fname, clamp )) ) + return false; + + fn->glUniform1i( uniSamp, texunit++ ); + + return true; + } + + return true; + }; + + QString white = "shaders/white.dds"; + QString black = "shaders/black.dds"; + QString default_n = "shaders/default_n.dds"; + + + // BSLightingShaderProperty + if ( mesh->bslsp ) { + uni1f( "lightingEffect1", mesh->bslsp->getLightingEffect1() ); + uni1f( "lightingEffect2", mesh->bslsp->getLightingEffect2() ); + + uni1f( "alpha", mesh->bslsp->getAlpha() ); + + auto uvS = mesh->bslsp->getUvScale(); + uni2f( "uvScale", uvS.x, uvS.y ); + + auto uvO = mesh->bslsp->getUvOffset(); + uni2f( "uvOffset", uvO.x, uvO.y ); + + uni4m( "viewMatrix", mesh->viewTrans().toMatrix4() ); + uni4m( "viewMatrixInverse", mesh->viewTrans().toMatrix4().inverted() ); + + uni4m( "localMatrix", mesh->localTrans().toMatrix4() ); + uni4m( "localMatrixInverse", mesh->localTrans().toMatrix4().inverted() ); + + uni4m( "worldMatrix", mesh->worldTrans().toMatrix4() ); + uni4m( "worldMatrixInverse", mesh->worldTrans().toMatrix4().inverted() ); + + uni1i( "greyscaleColor", mesh->bslsp->greyscaleColor ); + if ( mesh->bslsp->greyscaleColor ) { + if ( !uniSampler( "GreyscaleMap", 3, "", TexClampMode::MIRRORED_S_MIRRORED_T ) ) + return false; + } + + uni1i( "hasTintColor", mesh->bslsp->hasTintColor ); + if ( mesh->bslsp->hasTintColor ) { + auto tC = mesh->bslsp->getTintColor(); + uni3f( "tintColor", tC.red(), tC.green(), tC.blue() ); + } + + uni1i( "hasDetailMask", mesh->bslsp->hasDetailMask ); + if ( mesh->bslsp->hasDetailMask ) { + if ( !uniSampler( "DetailMask", 3, "shaders/blankdetailmap.dds", clamp ) ) + return false; + } + + uni1i( "hasTintMask", mesh->bslsp->hasTintMask ); + if ( mesh->bslsp->hasTintMask ) { + if ( !uniSampler( "TintMask", 6, "shaders/gray.dds", clamp ) ) + return false; + } + + // Rim & Soft params + + uni1i( "hasSoftlight", mesh->bslsp->hasSoftlight ); + uni1i( "hasRimlight", mesh->bslsp->hasRimlight ); + + if ( nif->getUserVersion2() < 130 && (mesh->bslsp->hasSoftlight || mesh->bslsp->hasRimlight) ) { + + if ( !uniSampler( "LightMask", 2, default_n, clamp ) ) + return false; + } + + // Backlight params + + uni1i( "hasBacklight", mesh->bslsp->hasBacklight ); + + if ( nif->getUserVersion2() < 130 && mesh->bslsp->hasBacklight ) { + + if ( !uniSampler( "BacklightMap", 7, default_n, clamp ) ) + return false; + } + + // Glow params + + if ( (opts & Scene::DoGlow) && (opts & Scene::DoLighting) && mesh->bslsp->hasEmittance ) + uni1f( "glowMult", mesh->bslsp->getEmissiveMult() ); + else + uni1f( "glowMult", 0 ); + + uni1i( "hasEmit", mesh->bslsp->hasEmittance ); + uni1i( "hasGlowMap", mesh->bslsp->hasGlowMap ); + auto emC = mesh->bslsp->getEmissiveColor(); + uni3f( "glowColor", emC.red(), emC.green(), emC.blue() ); + + // Specular params + + if ( (opts & Scene::DoSpecular) && (opts & Scene::DoLighting) ) + uni1f( "specStrength", mesh->bslsp->getSpecularStrength() ); + else + uni1f( "specStrength", 0 ); + + // Assure specular power does not break the shaders + auto gloss = mesh->bslsp->getSpecularGloss(); + uni1f( "specGlossiness", gloss ); + + auto spec = mesh->bslsp->getSpecularColor(); + uni3f( "specColor", spec.red(), spec.green(), spec.blue() ); + + uni1i( "hasSpecularMap", mesh->bslsp->hasSpecularMap ); + + if ( mesh->bslsp->hasSpecularMap && (nif->getUserVersion2() == 130 || !mesh->bslsp->hasBacklight) ) { + if ( !uniSampler( "SpecularMap", 7, white, clamp ) ) + return false; + } + + if ( nif->getUserVersion2() == 130 ) { + uni1i( "doubleSided", mesh->isDoubleSided ); + uni1f( "paletteScale", mesh->bslsp->paletteScale ); + uni1f( "subsurfaceRolloff", mesh->bslsp->getLightingEffect1() ); + uni1f( "fresnelPower", mesh->bslsp->fresnelPower ); + uni1f( "rimPower", mesh->bslsp->rimPower ); + uni1f( "backlightPower", mesh->bslsp->backlightPower ); + } + + // Multi-Layer + + if ( mesh->bslsp->hasMultiLayerParallax ) { + + auto inS = mesh->bslsp->getInnerTextureScale(); + uni2f( "innerScale", inS.x, inS.y ); + + uni1f( "innerThickness", mesh->bslsp->getInnerThickness() ); + + uni1f( "outerRefraction", mesh->bslsp->getOuterRefractionStrength() ); + uni1f( "outerReflection", mesh->bslsp->getOuterReflectionStrength() ); + + if ( !uniSampler( "InnerMap", 6, default_n, clamp ) ) + return false; + } + + // Environment Mapping + + uni1i( "hasCubeMap", mesh->bslsp->hasCubeMap ); + uni1i( "hasEnvMask", mesh->bslsp->useEnvironmentMask ); + + if ( mesh->bslsp->hasCubeMap && mesh->bslsp->hasEnvironmentMap && (opts & Scene::DoCubeMapping) && (opts & Scene::DoLighting) ) { + + uni1f( "envReflection", mesh->bslsp->getEnvironmentReflection() ); + + // Do not test useEnvironmentMask here, always pass white.dds as fallback + if ( !uniSampler( "EnvironmentMap", 5, white, clamp ) ) + return false; + + GLint uniCubeMap = fn->glGetUniformLocation( prog->id, "CubeMap" ); + if ( uniCubeMap >= 0 ) { + + QString fname = bsprop->fileName( 4 ); + + if ( !fname.isEmpty() && (!activateTextureUnit( texunit ) || !bsprop->bindCube( 4, fname )) ) + return false; + + fn->glUniform1i( uniCubeMap, texunit++ ); + } + } else { + // In the case that the cube texture has already been bound, + // but SLSF1_Environment_Mapping is not set, assure that it + // removes reflections. + uni1f( "envReflection", 0 ); + } + + // Parallax + + if ( mesh->bslsp->hasHeightMap ) { + if ( !uniSampler( "HeightMap", 3, "shaders/gray.dds", clamp ) ) + return false; + } + } + + + // BSEffectShaderProperty + if ( mesh->bsesp ) { + + uni4m( "worldMatrix", mesh->worldTrans().toMatrix4() ); + + clamp = mesh->bsesp->getClampMode(); + clamp = TexClampMode(clamp); + + if ( !uniSampler( "SourceTexture", 0, white, clamp ) ) + return false; + + uni1i( "doubleSided", mesh->bsesp->getIsDoubleSided() ); + + auto uvS = mesh->bsesp->getUvScale(); + uni2f( "uvScale", uvS.x, uvS.y ); + + auto uvO = mesh->bsesp->getUvOffset(); + uni2f( "uvOffset", uvO.x, uvO.y ); + + uni1i( "hasSourceTexture", mesh->bsesp->hasSourceTexture ); + uni1i( "hasGreyscaleMap", mesh->bsesp->hasGreyscaleMap ); + + uni1i( "greyscaleAlpha", mesh->bsesp->greyscaleAlpha ); + uni1i( "greyscaleColor", mesh->bsesp->greyscaleColor ); + + + uni1i( "useFalloff", mesh->bsesp->useFalloff ); + uni1i( "vertexAlpha", mesh->bsesp->vertexAlpha ); + uni1i( "vertexColors", mesh->bsesp->vertexColors ); + + uni1i( "hasWeaponBlood", mesh->bsesp->hasWeaponBlood ); + + // Glow params + + auto emC = mesh->bsesp->getEmissiveColor(); + uni4f( "glowColor", emC.red(), emC.green(), emC.blue(), emC.alpha() ); + uni1f( "glowMult", mesh->bsesp->getEmissiveMult() ); + + // Falloff params + + uni4f( "falloffParams", + mesh->bsesp->falloff.startAngle, mesh->bsesp->falloff.stopAngle, + mesh->bsesp->falloff.startOpacity, mesh->bsesp->falloff.stopOpacity + ); + + uni1f( "falloffDepth", mesh->bsesp->falloff.softDepth ); + + // BSEffectShader textures + if ( mesh->bsesp->hasGreyscaleMap ) { + if ( !uniSampler( "GreyscaleMap", 1, "", TexClampMode::MIRRORED_S_MIRRORED_T ) ) + return false; + } + + if ( nif->getUserVersion2() == 130 ) { + + uni1f( "lightingInfluence", mesh->bsesp->getLightingInfluence() ); + + uni1i( "hasNormalMap", mesh->bsesp->hasNormalMap && (opts & Scene::DoLighting) ); + + uniSampler( "NormalMap", 3, default_n, clamp ); + + uni1i( "hasCubeMap", mesh->bsesp->hasEnvMap ); + uni1i( "hasEnvMask", mesh->bsesp->hasEnvMask ); + + if ( mesh->bsesp->hasEnvMap && (opts & Scene::DoCubeMapping) && (opts & Scene::DoLighting) ) { + uni1f( "envReflection", mesh->bsesp->getEnvironmentReflection() ); + + if ( mesh->bsesp->hasEnvMask && !uniSampler( "SpecularMap", 4, white, clamp ) ) + return false; + + GLint uniCubeMap = fn->glGetUniformLocation( prog->id, "CubeMap" ); + if ( uniCubeMap >= 0 ) { + + QString fname = bsprop->fileName( 2 ); + + if ( !fname.isEmpty() && (!activateTextureUnit( texunit ) || !bsprop->bindCube( 2, fname )) ) + return false; + + fn->glUniform1i( uniCubeMap, texunit++ ); + } + } else { + uni1f( "envReflection", 0 ); + } + + } + } + + // Defaults for uniforms in older meshes + if ( !mesh->bsesp && !mesh->bslsp ) { + uni2f( "uvScale", 1.0, 1.0 ); + uni2f( "uvOffset", 0.0, 0.0 ); + } + QMapIterator itx( prog->texcoords ); while ( itx.hasNext() ) { @@ -566,17 +951,26 @@ bool Renderer::setupProgram( Program * prog, Mesh * mesh, const PropertyList & p return false; if ( itx.value() == "tangents" ) { - if ( !mesh->transTangents.count() ) + if ( mesh->transTangents.count() ) { + glEnableClientState( GL_TEXTURE_COORD_ARRAY ); + glTexCoordPointer( 3, GL_FLOAT, 0, mesh->transTangents.data() ); + } else if ( mesh->tangents.count() ) { + glEnableClientState( GL_TEXTURE_COORD_ARRAY ); + glTexCoordPointer( 3, GL_FLOAT, 0, mesh->tangents.data() ); + } else { return false; + } - glEnableClientState( GL_TEXTURE_COORD_ARRAY ); - glTexCoordPointer( 3, GL_FLOAT, 0, mesh->transTangents.data() ); } else if ( itx.value() == "bitangents" ) { - if ( !mesh->transBitangents.count() ) + if ( mesh->transBitangents.count() ) { + glEnableClientState( GL_TEXTURE_COORD_ARRAY ); + glTexCoordPointer( 3, GL_FLOAT, 0, mesh->transBitangents.data() ); + } else if ( mesh->bitangents.count() ) { + glEnableClientState( GL_TEXTURE_COORD_ARRAY ); + glTexCoordPointer( 3, GL_FLOAT, 0, mesh->bitangents.data() ); + } else { return false; - - glEnableClientState( GL_TEXTURE_COORD_ARRAY ); - glTexCoordPointer( 3, GL_FLOAT, 0, mesh->transBitangents.data() ); + } } else if ( texprop ) { int txid = TexturingProperty::getId( itx.value() ); if ( txid < 0 ) @@ -612,6 +1006,43 @@ bool Renderer::setupProgram( Program * prog, Mesh * mesh, const PropertyList & p glProperty( props.get() ); + if ( mat && (mesh->scene->options & Scene::DoBlending) ) { + static const GLenum blendMap[11] = { + GL_ONE, GL_ZERO, GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR, + GL_DST_COLOR, GL_ONE_MINUS_DST_COLOR, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, + GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA, GL_SRC_ALPHA_SATURATE + }; + + if ( mat && mat->bAlphaBlend ) { + glDisable( GL_POLYGON_OFFSET_FILL ); + glEnable( GL_BLEND ); + glBlendFunc( blendMap[mat->iAlphaSrc], blendMap[mat->iAlphaDst] ); + } else { + glDisable( GL_BLEND ); + } + + if ( mat && mat->bAlphaTest ) { + glDisable( GL_POLYGON_OFFSET_FILL ); + glEnable( GL_ALPHA_TEST ); + glAlphaFunc( GL_GREATER, float( mat->iAlphaTestRef ) / 255.0 ); + } else { + glDisable( GL_ALPHA_TEST ); + } + + if ( mat && mat->bDecal ) { + glEnable( GL_POLYGON_OFFSET_FILL ); + glPolygonOffset( -1.0f, -1.0f ); + } + } + + // BSESP/BSLSP do not always need an NiAlphaProperty, and appear to override it at times + if ( !mat && mesh->translucent ) { + glEnable( GL_BLEND ); + glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + // If mesh is alpha tested, override threshold + glAlphaFunc( GL_GREATER, 0.1f ); + } + // setup vertex colors //glProperty( props.get< VertexColorProperty >(), glIsEnabled( GL_COLOR_ARRAY ) ); @@ -625,6 +1056,14 @@ bool Renderer::setupProgram( Program * prog, Mesh * mesh, const PropertyList & p glProperty( props.get() ); + if ( !mesh->depthTest ) { + glDisable( GL_DEPTH_TEST ); + } + + if ( !mesh->depthWrite || mesh->translucent ) { + glDepthMask( GL_FALSE ); + } + // setup stencil glProperty( props.get() ); @@ -636,12 +1075,18 @@ bool Renderer::setupProgram( Program * prog, Mesh * mesh, const PropertyList & p return true; } -void Renderer::setupFixedFunction( Mesh * mesh, const PropertyList & props ) +void Renderer::setupFixedFunction( Shape * mesh, const PropertyList & props ) { // setup lighting glEnable( GL_LIGHTING ); + // Disable specular because it washes out vertex colors + // at perpendicular viewing angles + float color[4] = { 0, 0, 0, 0 }; + glMaterialfv( GL_FRONT_AND_BACK, GL_SPECULAR, color ); + glLightfv( GL_LIGHT0, GL_SPECULAR, color ); + // setup blending glProperty( props.get() ); @@ -662,6 +1107,14 @@ void Renderer::setupFixedFunction( Mesh * mesh, const PropertyList & props ) glProperty( props.get() ); + if ( !mesh->depthTest ) { + glDisable( GL_DEPTH_TEST ); + } + + if ( !mesh->depthWrite ) { + glDepthMask( GL_FALSE ); + } + // setup stencil glProperty( props.get() ); diff --git a/src/gl/renderer.h b/src/gl/renderer.h index dc193e569..f597d6c33 100644 --- a/src/gl/renderer.h +++ b/src/gl/renderer.h @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -36,7 +36,10 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "nifmodel.h" +//! @file renderer.h Renderer, Renderer::ConditionSingle, Renderer::ConditionGroup, Renderer::Shader, Renderer::Program + class Mesh; +class Shape; class PropertyList; class QOpenGLContext; @@ -45,18 +48,20 @@ class QOpenGLFunctions; typedef unsigned int GLenum; typedef unsigned int GLuint; -//! Manages rendering and shaders? -class Renderer +//! Manages rendering and shaders +class Renderer : public QObject { + Q_OBJECT + + friend class Program; + public: - //! Constructor Renderer( QOpenGLContext * c, QOpenGLFunctions * f ); - //! Destructor ~Renderer(); - //! Init from context? + //! Set up shaders bool initialize(); - //! Whether the shaders are available + //! Whether shader support is available bool hasShaderSupport(); //! Updates shaders @@ -69,12 +74,16 @@ class Renderer //! Context Functions QOpenGLFunctions * fn; - //! Sets up rendering? - QString setupProgram( Mesh *, const QString & hint = QString() ); - //! Stops rendering? + //! Set up shader program + QString setupProgram( Shape *, const QString & hint = QString() ); + //! Stop shader program void stopProgram(); +public slots: + void updateSettings(); + protected: + //! Base Condition class for shader programs class Condition { public: @@ -84,6 +93,7 @@ class Renderer virtual bool eval( const NifModel * nif, const QList & iBlocks ) const = 0; }; + //! Condition class for single conditions class ConditionSingle final : public Condition { public: @@ -95,7 +105,7 @@ class Renderer QString left, right; enum Type { - NONE, EQ, NE, LE, GE, LT, GT, AND + NONE, EQ, NE, LE, GE, LT, GT, AND, NAND }; Type comp; const static QHash compStrs; @@ -106,6 +116,7 @@ class Renderer template bool compare( T a, T b ) const; }; + //! Condition class for grouped conditions (OR or AND) class ConditionGroup final : public Condition { public: @@ -123,6 +134,7 @@ class Renderer bool _or; }; + //! Parsing and loading of .frag or .vert files class Shader { public: @@ -140,6 +152,7 @@ class Renderer GLenum type; }; + //! Parsing and loading of .prog files class Program { public: @@ -151,7 +164,7 @@ class Renderer QOpenGLFunctions * f; QString name; GLuint id; - bool status; + bool status = false; ConditionGroup conditions; QMap texcoords; @@ -160,12 +173,18 @@ class Renderer QMap shaders; QMap programs; - friend class Program; + bool setupProgram( Program *, Shape *, const PropertyList &, const QList & iBlocks ); + void setupFixedFunction( Shape *, const PropertyList & ); - bool setupProgram( Program *, Mesh *, const PropertyList &, const QList & iBlocks ); - void setupFixedFunction( Mesh *, const PropertyList & ); + struct Settings + { + bool useShaders = true; + } cfg; }; + +// Templates + template inline bool Renderer::ConditionSingle::compare( T a, T b ) const { switch ( comp ) { @@ -183,6 +202,8 @@ template inline bool Renderer::ConditionSingle::compare( T a, T b ) return a > b; case AND: return a & b; + case NAND: + return !(a & b); default: return true; } diff --git a/src/glview.cpp b/src/glview.cpp index 2498d5b58..40ecfbef8 100644 --- a/src/glview.cpp +++ b/src/glview.cpp @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -32,9 +32,12 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "glview.h" #include "config.h" -#include "options.h" +#include "settings.h" +#include "ui_nifskope.h" +#include "nifskope.h" #include "nifmodel.h" +#include "gl/glmesh.h" #include "gl/glscene.h" #include "gl/gltex.h" #include "widgets/fileselect.h" @@ -47,6 +50,8 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include +#include #include #include #include @@ -73,12 +78,14 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #endif -// TODO: Make these options user configurable. -// TODO: Make this platform independent (Half monitor refresh rate) -#define FPS 60 -#define FOV 45.0 -#define MOV_SPD 350 -#define ROT_SPD 45 + +// NOTE: The FPS define is a frame limiter, +// NOT the guaranteed FPS in the viewport. +// Also the QTimer is integer milliseconds +// so 60 will give you 1000/60 = 16, not 16.666 +// therefore it's really 62.5FPS +#define FPS 144 + #define ZOOM_MIN 1.0 #define ZOOM_MAX 1000.0 @@ -87,10 +94,24 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #endif -//! \file glview.cpp GLView implementation +//! @file glview.cpp GLView implementation + + +GLGraphicsView::GLGraphicsView( QWidget * parent ) : QGraphicsView() +{ + setContextMenuPolicy( Qt::CustomContextMenu ); + setFocusPolicy( Qt::ClickFocus ); + setAcceptDrops( true ); + + installEventFilter( parent ); +} + +GLGraphicsView::~GLGraphicsView() {} + -GLView * GLView::create() +GLView * GLView::create( NifSkope * window ) { + QGLFormat fmt; static QList > views; QGLWidget * share = nullptr; @@ -99,28 +120,51 @@ GLView * GLView::create() share = v; } - QGLFormat fmt; - fmt.setDoubleBuffer( true ); - fmt.setRgba( true ); - fmt.setSamples( Options::antialias() ? 16 : 0 ); + QSettings settings; + int aa = settings.value( "Settings/Render/General/Antialiasing", 4 ).toInt(); - if ( share ) + // All new windows after the first window will share a format + if ( share ) { fmt = share->format(); - else - fmt.setSampleBuffers( Options::antialias() ); - + } else { + fmt.setSampleBuffers( aa > 0 ); + } + + // OpenGL version fmt.setVersion( 2, 1 ); // Ignored if version < 3.2 //fmt.setProfile(QGLFormat::CoreProfile); - views.append( QPointer( new GLView( fmt, share ) ) ); + // V-Sync + fmt.setSwapInterval( 1 ); + fmt.setDoubleBuffer( true ); + + fmt.setSamples( pow( 2, aa ) ); + + fmt.setDirectRendering( true ); + fmt.setRgba( true ); + + views.append( QPointer( new GLView( fmt, window, share ) ) ); return views.last(); } -GLView::GLView( const QGLFormat & format, const QGLWidget * shareWidget ) - : QGLWidget( format, 0, shareWidget ) +GLView::GLView( const QGLFormat & format, QWidget * p, const QGLWidget * shareWidget ) + : QGLWidget( format, p, shareWidget ) { + setFocusPolicy( Qt::ClickFocus ); + //setAttribute( Qt::WA_PaintOnScreen ); + //setAttribute( Qt::WA_NoSystemBackground ); + setAutoFillBackground( false ); + setAcceptDrops( true ); + setContextMenuPolicy( Qt::CustomContextMenu ); + + // Manually handle the buffer swap + // Fixes bug with QGraphicsView and double buffering + // Input becomes sluggish and CPU usage doubles when putting GLView + // inside a QGraphicsView. + setAutoBufferSwap( false ); + // Make the context current on this window makeCurrent(); @@ -131,202 +175,77 @@ GLView::GLView( const QGLFormat & format, const QGLWidget * shareWidget ) glFuncs = glContext->functions(); if ( !glFuncs ) { - qWarning( "Could not obtain OpenGL functions" ); + Message::critical( this, tr( "Could not obtain OpenGL functions" ) ); exit( 1 ); } glFuncs->initializeOpenGLFunctions(); - setFocusPolicy( Qt::ClickFocus ); - setAttribute( Qt::WA_NoSystemBackground ); - setAcceptDrops( true ); - setContextMenuPolicy( Qt::CustomContextMenu ); + view = ViewDefault; + animState = AnimEnabled; + debugMode = DbgNone; Zoom = 1.0; - zInc = 1; doCenter = false; doCompile = false; - doMultisampling = Options::antialias(); model = nullptr; time = 0.0; lastTime = QTime::currentTime(); - fpsact = 0.0; - fpsacc = 0.0; - fpscnt = 0; - textures = new TexCache( this ); + updateSettings(); + scene = new Scene( textures, glContext, glFuncs ); connect( textures, &TexCache::sigRefresh, this, static_cast(&GLView::update) ); + connect( scene, &Scene::sceneUpdated, this, static_cast(&GLView::update) ); timer = new QTimer( this ); timer->setInterval( 1000 / FPS ); timer->start(); connect( timer, &QTimer::timeout, this, &GLView::advanceGears ); + lightVisTimeout = 1500; + lightVisTimer = new QTimer( this ); + lightVisTimer->setSingleShot( true ); + connect( lightVisTimer, &QTimer::timeout, [this]() { setVisMode( Scene::VisLightPos, false ); update(); } ); - grpView = new QActionGroup( this ); - grpView->setExclusive( false ); - connect( grpView, &QActionGroup::triggered, this, &GLView::viewAction ); - - aViewTop = new QAction( QIcon( ":/btn/viewTop" ), tr( "Top" ), grpView ); - aViewTop->setToolTip( tr( "View from above" ) ); - aViewTop->setCheckable( true ); - aViewTop->setShortcut( Qt::Key_F5 ); - grpView->addAction( aViewTop ); - - aViewFront = new QAction( QIcon( ":/btn/viewFront" ), tr( "Front" ), grpView ); - aViewFront->setToolTip( tr( "View from the front" ) ); - aViewFront->setCheckable( true ); - aViewFront->setChecked( true ); - aViewFront->setShortcut( Qt::Key_F6 ); - grpView->addAction( aViewFront ); - - aViewSide = new QAction( QIcon( ":/btn/viewSide" ), tr( "Side" ), grpView ); - aViewSide->setToolTip( tr( "View from the side" ) ); - aViewSide->setCheckable( true ); - aViewSide->setShortcut( Qt::Key_F7 ); - grpView->addAction( aViewSide ); - - aViewUser = new QAction( QIcon( ":/btn/viewUser" ), tr( "User" ), grpView ); - aViewUser->setToolTip( tr( "Restore the view as it was when Save User View was activated" ) ); - aViewUser->setCheckable( true ); - aViewUser->setShortcut( Qt::Key_F8 ); - grpView->addAction( aViewUser ); - - aViewWalk = new QAction( QIcon( ":/btn/viewWalk" ), tr( "Walk" ), grpView ); - aViewWalk->setToolTip( tr( "Enable walk mode" ) ); - aViewWalk->setCheckable( true ); - aViewWalk->setShortcut( Qt::Key_F9 ); - grpView->addAction( aViewWalk ); - - aViewFlip = new QAction( QIcon( ":/btn/viewFlip" ), tr( "Flip" ), this ); - aViewFlip->setToolTip( tr( "Flip View from Front to Back, Top to Bottom, Side to Other Side" ) ); - aViewFlip->setCheckable( true ); - aViewFlip->setShortcut( Qt::Key_F11 ); - grpView->addAction( aViewFlip ); - - aViewPerspective = new QAction( QIcon( ":/btn/viewPers" ), tr( "Perspective" ), this ); - aViewPerspective->setToolTip( tr( "Perspective View Transformation or Orthogonal View Transformation" ) ); - aViewPerspective->setCheckable( true ); - aViewPerspective->setShortcut( Qt::Key_F10 ); - grpView->addAction( aViewPerspective ); - - aViewUserSave = new QAction( tr( "Save User View" ), this ); - aViewUserSave->setToolTip( tr( "Save current view rotation, position and distance" ) ); - aViewUserSave->setShortcut( Qt::CTRL + Qt::Key_F9 ); - connect( aViewUserSave, &QAction::triggered, this, &GLView::sltSaveUserView ); - - aPrintView = new QAction( tr( "Save View To File..." ), this ); - connect( aPrintView, &QAction::triggered, this, &GLView::saveImage ); + connect( NifSkope::getOptions(), &SettingsDialog::flush3D, textures, &TexCache::flush ); + connect( NifSkope::getOptions(), &SettingsDialog::update3D, [this]() { + updateSettings(); + qglClearColor( cfg.background ); + update(); + } ); +} -#ifndef QT_NO_DEBUG - aColorKeyDebug = new QAction( tr( "Color Key Debug" ), this ); - aColorKeyDebug->setCheckable( true ); - aColorKeyDebug->setChecked( false ); -#endif +GLView::~GLView() +{ + flush(); - aAnimate = new QAction( tr( "&Animations" ), this ); - aAnimate->setToolTip( tr( "enables evaluation of animation controllers" ) ); - aAnimate->setCheckable( true ); - aAnimate->setChecked( true ); - connect( aAnimate, &QAction::toggled, this, &GLView::checkActions ); - addAction( aAnimate ); - - aAnimPlay = new QAction( QIcon( ":/btn/play" ), tr( "&Play" ), this ); - aAnimPlay->setCheckable( true ); - aAnimPlay->setChecked( true ); - connect( aAnimPlay, &QAction::toggled, this, &GLView::checkActions ); - - aAnimLoop = new QAction( QIcon( ":/btn/loop" ), tr( "&Loop" ), this ); - aAnimLoop->setCheckable( true ); - aAnimLoop->setChecked( true ); - - aAnimSwitch = new QAction( QIcon( ":/btn/switch" ), tr( "&Switch" ), this ); - aAnimSwitch->setCheckable( true ); - aAnimSwitch->setChecked( true ); - - // animation tool bar - - tAnim = new QToolBar( tr( "Animation" ) ); - tAnim->setObjectName( "AnimTool" ); - tAnim->setAllowedAreas( Qt::TopToolBarArea | Qt::BottomToolBarArea ); - tAnim->setIconSize( QSize( 16, 16 ) ); - - connect( aAnimate, &QAction::toggled, tAnim->toggleViewAction(), &QAction::setChecked ); - connect( aAnimate, &QAction::toggled, tAnim, &QToolBar::setVisible ); - connect( tAnim->toggleViewAction(), &QAction::toggled, aAnimate, &QAction::setChecked ); - - tAnim->addAction( aAnimPlay ); - - sldTime = new FloatSlider( Qt::Horizontal, true, true ); - sldTime->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Maximum ); - connect( this, &GLView::sigTime, sldTime, &FloatSlider::set ); - connect( sldTime, &FloatSlider::valueChanged, this, &GLView::sltTime ); - tAnim->addWidget( sldTime ); - - FloatEdit * edtTime = new FloatEdit; - edtTime->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Maximum ); - - connect( this, &GLView::sigTime, edtTime, &FloatEdit::set ); - connect( edtTime, static_cast(&FloatEdit::sigEdited), this, &GLView::sltTime ); - connect( sldTime, &FloatSlider::valueChanged, edtTime, &FloatEdit::setValue ); - connect( edtTime, static_cast(&FloatEdit::sigEdited), sldTime, &FloatSlider::setValue ); - sldTime->addEditor( edtTime ); - - tAnim->addAction( aAnimLoop ); - - tAnim->addAction( aAnimSwitch ); - - animGroups = new QComboBox(); - animGroups->setMinimumWidth( 100 ); - connect( animGroups, static_cast(&QComboBox::activated), this, &GLView::sltSequence ); - tAnim->addWidget( animGroups ); - - // TODO: Connect to a less general signal for when texture paths are edited - // or things that affect textures. Not *any* Options update. - // This makes editing various options sluggish, like lighting color, background color - // and even some LineEdits like "Cull Nodes by Name" - connect( Options::get(), &Options::sigFlush3D, textures, &TexCache::flush ); - connect( Options::get(), &Options::sigChanged, this, static_cast(&GLView::update) ); - connect( Options::get(), &Options::materialOverridesChanged, this, &GLView::sceneUpdate ); - -#ifdef Q_OS_LINUX - // extra whitespace for linux - QWidget * extraspace = new QWidget(); - extraspace->setFixedWidth( 5 ); - tAnim->addWidget( extraspace ); -#endif + delete textures; + delete scene; +} - tView = new QToolBar( tr( "Render View" ) ); - tView->setObjectName( "ViewTool" ); - tView->setAllowedAreas( Qt::TopToolBarArea | Qt::BottomToolBarArea ); - tView->setIconSize( QSize( 16, 16 ) ); - - tView->addAction( aViewTop ); - tView->addAction( aViewFront ); - tView->addAction( aViewSide ); - tView->addAction( aViewUser ); - tView->addAction( aViewWalk ); - tView->addSeparator(); - tView->addAction( aViewFlip ); - tView->addAction( aViewPerspective ); - -#ifdef Q_OS_LINUX - // extra whitespace for linux - extraspace = new QWidget(); - extraspace->setFixedWidth( 5 ); - tView->addWidget( extraspace ); -#endif +void GLView::updateSettings() +{ + QSettings settings; + settings.beginGroup( "Settings/Render" ); + + cfg.background = settings.value( "Colors/Background", QColor( 0, 0, 0 ) ).value(); + cfg.fov = settings.value( "General/Camera/Field Of View" ).toFloat(); + cfg.moveSpd = settings.value( "General/Camera/Movement Speed" ).toFloat(); + cfg.rotSpd = settings.value( "General/Camera/Rotation Speed" ).toFloat(); + cfg.upAxis = settings.value( "General/Up Axis", 2 ).toInt(); + + settings.endGroup(); } -GLView::~GLView() +QColor GLView::clearColor() const { - delete scene; + return cfg.background; } @@ -339,12 +258,30 @@ Scene * GLView::getScene() return scene; } -void GLView::sceneUpdate() +void GLView::updateScene() { scene->update( model, QModelIndex() ); update(); } +void GLView::updateAnimationState( bool checked ) +{ + QAction * action = qobject_cast(sender()); + if ( action ) { + auto opt = AnimationState( action->data().toInt() ); + + if ( checked ) + animState |= opt; + else + animState &= ~opt; + + scene->animate = (animState & AnimEnabled); + lastTime = QTime::currentTime(); + + update(); + } +} + /* * OpenGL @@ -354,10 +291,10 @@ void GLView::initializeGL() { GLenum err; - if ( Options::antialias() ) { + if ( scene->options & Scene::DoMultisampling ) { if ( !glContext->hasExtension( "GL_EXT_framebuffer_multisample" ) ) { - doMultisampling = false; - //qWarning() << "System does not support multisampling"; + scene->options &= ~Scene::DoMultisampling; + //qDebug() << "System does not support multisampling"; } /* else { GLint maxSamples; glGetIntegerv( GL_MAX_SAMPLES, &maxSamples ); @@ -370,6 +307,12 @@ void GLView::initializeGL() if ( scene->renderer->initialize() ) updateShaders(); + // Initial viewport values + // Made viewport and aspect member variables. + // They were being updated every single frame instead of only when resizing. + //glGetIntegerv( GL_VIEWPORT, viewport ); + aspect = (GLdouble)width() / (GLdouble)height(); + // Check for errors while ( ( err = glGetError() ) != GL_NO_ERROR ) qDebug() << tr( "glview.cpp - GL ERROR (init) : " ) << (const char *)gluErrorString( err ); @@ -386,22 +329,21 @@ void GLView::glProjection( int x, int y ) { Q_UNUSED( x ); Q_UNUSED( y ); - GLint viewport[4]; - glGetIntegerv( GL_VIEWPORT, viewport ); - GLdouble aspect = (GLdouble)viewport[2] / (GLdouble)viewport[3]; - glMatrixMode( GL_PROJECTION ); glLoadIdentity(); BoundSphere bs = scene->view * scene->bounds(); - if ( Options::drawAxes() ) + if ( scene->options & Scene::ShowAxes ) { bs |= BoundSphere( scene->view * Vector3(), axis ); + } + + float bounds = (bs.radius > 1024.0) ? bs.radius : 1024.0; - GLdouble nr = fabs( bs.center[2] ) - bs.radius * 1.2; - GLdouble fr = fabs( bs.center[2] ) + bs.radius * 1.2; + GLdouble nr = fabs( bs.center[2] ) - bounds * 1.5; + GLdouble fr = fabs( bs.center[2] ) + bounds * 1.5; - if ( aViewPerspective->isChecked() || aViewWalk->isChecked() ) { + if ( perspectiveMode || (view == ViewWalk) ) { // Perspective View if ( nr < 1.0 ) nr = 1.0; @@ -421,7 +363,7 @@ void GLView::glProjection( int x, int y ) fr = 2.0; } - GLdouble h2 = tan( ( FOV / Zoom ) / 360 * M_PI ) * nr; + GLdouble h2 = tan( ( cfg.fov / Zoom ) / 360 * M_PI ) * nr; GLdouble w2 = h2 * aspect; glFrustum( -w2, +w2, -h2, +h2, nr, fr ); } else { @@ -435,6 +377,7 @@ void GLView::glProjection( int x, int y ) glLoadIdentity(); } + #ifdef USE_GL_QPAINTER void GLView::paintEvent( QPaintEvent * event ) { @@ -447,6 +390,8 @@ void GLView::paintEvent( QPaintEvent * event ) void GLView::paintGL() { #endif + + // Save GL state glPushAttrib( GL_ALL_ATTRIB_BITS ); glMatrixMode( GL_PROJECTION ); @@ -455,45 +400,48 @@ void GLView::paintGL() glPushMatrix(); // Clear Viewport - glViewport( 0, 0, width(), height() ); - qglClearColor( Options::bgColor() ); - glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); - + if ( scene->visMode & Scene::VisSilhouette ) { + qglClearColor( QColor( 255, 255, 255, 255 ) ); + } + //glViewport( 0, 0, width(), height() ); + glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT ); + + // Compile the model if ( doCompile ) { textures->setNifFolder( model->getFolder() ); scene->make( model ); scene->transform( Transform(), scene->timeMin() ); - axis = scene->bounds().radius < 0 ? 1 : scene->bounds().radius * 1.4; // fix: the axis appearance when there is no scene yet - - if ( axis == 0 ) - axis = 1; + axis = (scene->bounds().radius <= 0) ? 1024.0 : scene->bounds().radius; - if ( time < scene->timeMin() || time > scene->timeMax() ) - time = scene->timeMin(); - - emit sigTime( time, scene->timeMin(), scene->timeMax() ); + if ( scene->timeMin() != scene->timeMax() ) { + if ( time < scene->timeMin() || time > scene->timeMax() ) + time = scene->timeMin(); - animGroups->clear(); - animGroups->addItems( scene->animGroups ); - animGroups->setCurrentIndex( scene->animGroups.indexOf( scene->animGroup ) ); + emit sequencesUpdated(); + } else if ( scene->timeMax() == 0 ) { + // No Animations in this NIF + emit sequencesDisabled( true ); + } + emit sceneTimeChanged( time, scene->timeMin(), scene->timeMax() ); doCompile = false; } // Center the model if ( doCenter ) { - viewAction( checkedViewAction() ); + setCenter(); doCenter = false; } // Transform the scene Matrix ap; - if ( Options::upAxis() == Options::YAxis ) { + + if ( cfg.upAxis == YAxis ) { ap( 0, 0 ) = 0; ap( 0, 1 ) = 0; ap( 0, 2 ) = 1; ap( 1, 0 ) = 1; ap( 1, 1 ) = 0; ap( 1, 2 ) = 0; ap( 2, 0 ) = 0; ap( 2, 1 ) = 1; ap( 2, 2 ) = 0; - } else if ( Options::upAxis() == Options::XAxis ) { + } else if ( cfg.upAxis == XAxis ) { ap( 0, 0 ) = 0; ap( 0, 1 ) = 1; ap( 0, 2 ) = 0; ap( 1, 0 ) = 0; ap( 1, 1 ) = 0; ap( 1, 2 ) = 1; ap( 2, 0 ) = 1; ap( 2, 1 ) = 0; ap( 2, 2 ) = 0; @@ -504,7 +452,7 @@ void GLView::paintGL() viewTrans.rotation = viewTrans.rotation * ap; viewTrans.translation = viewTrans.rotation * Pos; - if ( !aViewWalk->isChecked() ) + if ( view != ViewWalk ) viewTrans.translation[2] -= Dist * 2; scene->transform( viewTrans, time ); @@ -513,8 +461,8 @@ void GLView::paintGL() glProjection(); glLoadIdentity(); - // Draw the axes - if ( Options::drawAxes() ) { + // Draw the grid + if ( scene->options & Scene::ShowGrid ) { glDisable( GL_ALPHA_TEST ); glDisable( GL_BLEND ); glDisable( GL_LIGHTING ); @@ -526,44 +474,135 @@ void GLView::paintGL() glDisable( GL_NORMALIZE ); glLineWidth( 2.0f ); + // Keep the grid "grounded" regardless of Up Axis + Transform gridTrans = viewTrans; + if ( cfg.upAxis != ZAxis ) + gridTrans.rotation = viewTrans.rotation * ap.inverted(); + glPushMatrix(); - glLoadMatrix( viewTrans ); + glLoadMatrix( gridTrans ); - drawAxes( Vector3(), axis ); + // TODO: Configurable grid in Settings + // 1024 game units, major lines every 128, minor lines every 64 + drawGrid( 1024, 128, 2 ); glPopMatrix(); } - // Setup light - Vector4 lightDir( 0.0, 0.0, 1.0, 0.0 ); +#ifndef QT_NO_DEBUG + // Debug scene bounds + glEnable( GL_DEPTH_TEST ); + glDepthMask( GL_TRUE ); + glDepthFunc( GL_LESS ); + glPushMatrix(); + glLoadMatrix( viewTrans ); + if ( debugMode == DbgBounds ) { + BoundSphere bs = scene->bounds(); + bs |= BoundSphere( Vector3(), axis ); + drawSphere( bs.center, bs.radius ); + } + glPopMatrix(); +#endif + + GLfloat mat_spec[] = { 0.0f, 0.0f, 0.0f, 1.0f }; + + if ( scene->options & Scene::DoLighting ) { + // Setup light + Vector4 lightDir( 0.0, 0.0, 1.0, 0.0 ); + + if ( !frontalLight ) { + float decl = declination / 180.0 * PI; + Vector3 v( sin( decl ), 0, cos( decl ) ); + Matrix m; m.fromEuler( 0, 0, planarAngle / 180.0 * PI ); + v = m * v; + lightDir = Vector4( viewTrans.rotation * v, 0.0 ); + + if ( scene->visMode & Scene::VisLightPos ) { + glEnable( GL_BLEND ); + glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + glEnable( GL_DEPTH_TEST ); + glDepthMask( GL_TRUE ); + glDepthFunc( GL_LESS ); + + glPushMatrix(); + glLoadMatrix( viewTrans ); + + glLineWidth( 2.0f ); + glColor4f( 1.0f, 1.0f, 1.0f, 0.5f ); + + // Scale the distance a bit + float l = axis + 64.0; + l = (l < 128) ? axis * 1.5 : l; + l = (l > 2048) ? axis * 0.66 : l; + l = (l > 1024) ? axis * 0.75 : l; + + drawDashLine( Vector3( 0, 0, 0 ), v * l, 30 ); + drawSphere( v * l, axis / 10 ); + glPopMatrix(); + glDisable( GL_BLEND ); + } + } + + float amb = ambient; + if ( (scene->visMode & Scene::VisNormalsOnly) + && (scene->options & Scene::DoTexturing) + && !(scene->options & Scene::DisableShaders) ) + { + amb = 0.1f; + } + + GLfloat mat_amb[] = { amb, amb, amb, 1.0f }; + GLfloat mat_diff[] = { brightness, brightness, brightness, 1.0f }; + + + glShadeModel( GL_SMOOTH ); + glEnable( GL_LIGHTING ); + glEnable( GL_LIGHT0 ); + glLightfv( GL_LIGHT0, GL_AMBIENT, mat_amb ); + glLightfv( GL_LIGHT0, GL_DIFFUSE, mat_diff ); + glLightfv( GL_LIGHT0, GL_SPECULAR, mat_diff ); + glLightfv( GL_LIGHT0, GL_POSITION, lightDir.data() ); + + // Necessary? + glLightModeli( GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE ); + } else { + float amb = 0.5f; + if ( scene->options & Scene::DisableShaders ) { + amb = 0.0f; + } - if ( !Options::lightFrontal() ) { - float decl = Options::lightDeclination() / 180.0 * PI; - Vector3 v( sin( decl ), 0, cos( decl ) ); - Matrix m; m.fromEuler( 0, 0, Options::lightPlanarAngle() / 180.0 * PI ); - v = m * v; - lightDir = Vector4( viewTrans.rotation * v, 0.0 ); + GLfloat mat_amb[] = { amb, amb, amb, 1.0f }; + GLfloat mat_diff[] = { 1.0f, 1.0f, 1.0f, 1.0f }; + + + glShadeModel( GL_SMOOTH ); + glEnable( GL_LIGHTING ); + glEnable( GL_LIGHT0 ); + glLightfv( GL_LIGHT0, GL_AMBIENT, mat_amb ); + glLightfv( GL_LIGHT0, GL_DIFFUSE, mat_diff ); + glLightfv( GL_LIGHT0, GL_SPECULAR, mat_spec ); } - glShadeModel( GL_SMOOTH ); - glLightfv( GL_LIGHT0, GL_POSITION, lightDir.data() ); - glLightfv( GL_LIGHT0, GL_AMBIENT, Color4( Options::ambient() ).data() ); - glLightfv( GL_LIGHT0, GL_DIFFUSE, Color4( Options::diffuse() ).data() ); - glLightfv( GL_LIGHT0, GL_SPECULAR, Color4( Options::specular() ).data() ); - glLightModeli( GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE ); - glEnable( GL_LIGHT0 ); - glEnable( GL_LIGHTING ); + if ( scene->visMode & Scene::VisSilhouette ) { + GLfloat mat_diff[] = { 0.0f, 0.0f, 0.0f, 1.0f }; + GLfloat mat_amb[] = { 0.0f, 0.0f, 0.0f, 1.0f }; - if ( Options::antialias() && doMultisampling ) - glEnable( GL_MULTISAMPLE_ARB ); + glShadeModel( GL_FLAT ); + glEnable( GL_LIGHTING ); + glEnable( GL_LIGHT0 ); + glLightModelfv( GL_LIGHT_MODEL_AMBIENT, mat_diff ); + glMaterialfv( GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, mat_diff ); + glLightfv( GL_LIGHT0, GL_AMBIENT, mat_amb ); + glLightfv( GL_LIGHT0, GL_DIFFUSE, mat_diff ); + glLightfv( GL_LIGHT0, GL_SPECULAR, mat_spec ); + } - // Initialize Rendering Font - // TODO: Seek alternative to fontDisplayListBase or determine if code is actually necessary - //glListBase(fontDisplayListBase(QFont(), 2000)); + if ( scene->options & Scene::DoMultisampling ) + glEnable( GL_MULTISAMPLE_ARB ); #ifndef QT_NO_DEBUG // Color Key debug - if ( aColorKeyDebug->isChecked() ) { + if ( debugMode == DbgColorPicker ) { glDisable( GL_MULTISAMPLE ); glDisable( GL_LINE_SMOOTH ); glDisable( GL_TEXTURE_2D ); @@ -585,6 +624,54 @@ void GLView::paintGL() // Draw the model scene->draw(); + if ( scene->options & Scene::ShowAxes ) { + // Resize viewport to small corner of screen + int axesSize = std::min( width() / 10, 125 ); + glViewport( 0, 0, axesSize, axesSize ); + + // Reset matrices + glMatrixMode( GL_PROJECTION ); + glLoadIdentity(); + + // Square frustum + auto nr = 1.0; + auto fr = 250.0; + GLdouble h2 = tan( cfg.fov / 360 * M_PI ) * nr; + GLdouble w2 = h2; + glFrustum( -w2, +w2, -h2, +h2, nr, fr ); + + // Reset matrices + glMatrixMode( GL_MODELVIEW ); + glLoadIdentity(); + + glPushMatrix(); + + // Store and reset viewTrans translation + auto viewTransOrig = viewTrans.translation; + + // Zoom out slightly + viewTrans.translation = { 0, 0, -150.0 }; + + // Load modified viewTrans + glLoadMatrix( viewTrans ); + + // Restore original viewTrans translation + viewTrans.translation = viewTransOrig; + + // Find direction of axes + auto vtr = viewTrans.rotation; + QVector axesDots = { vtr( 2, 0 ), vtr( 2, 1 ), vtr( 2, 2 ) }; + + drawAxesOverlay( { 0, 0, 0 }, 50.0, sortAxes( axesDots ) ); + + glPopMatrix(); + + // Restore viewport size + glViewport( 0, 0, width(), height() ); + // Restore matrices + glProjection(); + } + // Restore GL state glPopAttrib(); glMatrixMode( GL_MODELVIEW ); @@ -596,52 +683,88 @@ void GLView::paintGL() GLenum err; while ( ( err = glGetError() ) != GL_NO_ERROR ) qDebug() << tr( "glview.cpp - GL ERROR (paint): " ) << (const char *)gluErrorString( err ); - - // Update FPS counter - if ( fpsacc > 1.0 && fpscnt ) { - fpsacc /= fpscnt; - - if ( fpsacc > 0.0001 ) - fpsact = 1.0 / fpsacc; - else - fpsact = 10000; - - fpsacc = 0; - fpscnt = 0; - } emit paintUpdate(); + // Manually handle the buffer swap + swapBuffers(); + #ifdef USE_GL_QPAINTER - // draw text on top using QPainter + painter.end(); +#endif +} - if ( Options::benchmark() || Options::drawStats() ) { - int ls = QFontMetrics( font() ).lineSpacing(); - int y = 1; - painter.setPen( Options::hlColor() ); +void GLView::resizeGL( int width, int height ) +{ + resize( width, height ); - if ( Options::benchmark() ) { - painter.drawText( 10, y++ *ls, QString( "FPS %1" ).arg( int(fpsact) ) ); - y++; - } + makeCurrent(); + aspect = (GLdouble)width / (GLdouble)height; + glViewport( 0, 0, width, height ); + qglClearColor( cfg.background ); - if ( Options::drawStats() ) { - QString stats = scene->textStats(); - QStringList lines = stats.split( "\n" ); - for ( const QString& line : lines ) { - painter.drawText( 10, y++ *ls, line ); - } - } + update(); +} + +void GLView::resizeEvent( QResizeEvent * e ) +{ + Q_UNUSED( e ); + // This function should never be called. + // Moved to NifSkope::eventFilter() +} + +void GLView::setFrontalLight( bool frontal ) +{ + frontalLight = frontal; + update(); +} + +void GLView::setBrightness( int value ) +{ + if ( value > 900 ) { + value += pow(value - 900, 1.5); } - painter.end(); -#endif + brightness = float(value) / 720.0; + update(); } -void GLView::resizeGL( int width, int height ) +void GLView::setAmbient( int value ) { - glViewport( 0, 0, width, height ); + ambient = float( value ) / 1440.0; + update(); +} + +void GLView::setDeclination( int decl ) +{ + declination = float(decl) / 4; // Divide by 4 because sliders are -720 <-> 720 + lightVisTimer->start( lightVisTimeout ); + setVisMode( Scene::VisLightPos, true ); + update(); +} + +void GLView::setPlanarAngle( int angle ) +{ + planarAngle = float(angle) / 4; // Divide by 4 because sliders are -720 <-> 720 + lightVisTimer->start( lightVisTimeout ); + setVisMode( Scene::VisLightPos, true ); + update(); +} + +void GLView::setDebugMode( DebugMode mode ) +{ + debugMode = mode; +} + +void GLView::setVisMode( Scene::VisMode mode, bool checked ) +{ + if ( checked ) + scene->visMode |= mode; + else + scene->visMode &= ~mode; + + update(); } typedef void (Scene::* DrawFunc)( void ); @@ -685,6 +808,7 @@ int indexAt( /*GLuint *buffer,*/ NifModel * model, Scene * scene, QList df; - if ( Options::drawHavok() ) + if ( scene->options & Scene::ShowCollision ) df << &Scene::drawHavok; - if ( Options::drawNodes() ) + if ( scene->options & Scene::ShowNodes ) df << &Scene::drawNodes; - if ( Options::drawFurn() ) + if ( scene->options & Scene::ShowMarkers ) df << &Scene::drawFurn; df << &Scene::drawShapes; @@ -770,7 +894,15 @@ QModelIndex GLView::indexAt( const QPoint & pos, int cycle ) QModelIndex chooseIndex; - if ( choose != -1 ) { + if ( scene->selMode & Scene::SelVertex ) { + // Vertex + int block = choose >> 16; + int vert = choose - (block << 16); + + auto shape = scene->shapes.value( block ); + if ( shape ) + chooseIndex = shape->vertexAt( vert ); + } else if ( choose != -1 ) { // Block Index chooseIndex = model->getBlock( choose ); @@ -792,13 +924,14 @@ void GLView::center() void GLView::move( float x, float y, float z ) { Pos += Matrix::euler( Rot[0] / 180 * PI, Rot[1] / 180 * PI, Rot[2] / 180 * PI ).inverted() * Vector3( x, y, z ); + updateViewpoint(); update(); } void GLView::rotate( float x, float y, float z ) { Rot += Vector3( x, y, z ); - uncheckViewAction(); + updateViewpoint(); update(); } @@ -815,6 +948,35 @@ void GLView::zoom( float z ) update(); } +void GLView::setCenter() +{ + Node * node = scene->getNode( model, scene->currentBlock ); + + if ( node ) { + // Center on selected node + BoundSphere bs = node->bounds(); + + this->setPosition( -bs.center ); + + if ( bs.radius > 0 ) { + setDistance( bs.radius * 1.2 ); + } + } else { + // Center on entire mesh + BoundSphere bs = scene->bounds(); + + if ( bs.radius < 1 ) + bs.radius = 1024.0; + + setDistance( bs.radius * 1.2 ); + setZoom( 1.0 ); + + setPosition( -bs.center ); + + setOrientation( view ); + } +} + void GLView::setDistance( float x ) { Dist = x; @@ -833,6 +995,12 @@ void GLView::setPosition( Vector3 v ) update(); } +void GLView::setProjection( bool isPersp ) +{ + perspectiveMode = isPersp; + update(); +} + void GLView::setRotation( float x, float y, float z ) { Rot = { x, y, z }; @@ -845,6 +1013,100 @@ void GLView::setZoom( float z ) update(); } + +void GLView::flipOrientation() +{ + ViewState tmp = ViewDefault; + + switch ( view ) { + case ViewTop: + tmp = ViewBottom; + break; + case ViewBottom: + tmp = ViewTop; + break; + case ViewLeft: + tmp = ViewRight; + break; + case ViewRight: + tmp = ViewLeft; + break; + case ViewFront: + tmp = ViewBack; + break; + case ViewBack: + tmp = ViewFront; + break; + case ViewUser: + default: + { + // TODO: Flip any other view also? + } + break; + } + + setOrientation( tmp, false ); +} + +void GLView::setOrientation( GLView::ViewState state, bool recenter ) +{ + if ( state == view ) + return; + + switch ( state ) { + case ViewBottom: + setRotation( 180, 0, 0 ); // Bottom + break; + case ViewTop: + setRotation( 0, 0, 0 ); // Top + break; + case ViewBack: + setRotation( -90, 0, 0 ); // Back + break; + case ViewFront: + setRotation( -90, 0, 180 ); // Front + break; + case ViewRight: + setRotation( -90, 0, 90 ); // Right + break; + case ViewLeft: + setRotation( -90, 0, -90 ); // Left + break; + default: + break; + } + + view = state; + + // Recenter + if ( recenter ) + center(); +} + +void GLView::updateViewpoint() +{ + switch ( view ) { + case ViewTop: + case ViewBottom: + case ViewLeft: + case ViewRight: + case ViewFront: + case ViewBack: + case ViewUser: + emit viewpointChanged(); + break; + default: + break; + } +} + +void GLView::flush() +{ + if ( textures ) + textures->flush(); +} + + /* * NifModel */ @@ -926,20 +1188,26 @@ void GLView::dataChanged( const QModelIndex & idx, const QModelIndex & xdi ) void GLView::modelChanged() { + if ( doCompile ) + return; + doCompile = true; - doCenter = true; + //doCenter = true; update(); } void GLView::modelLinked() { + if ( doCompile ) + return; + doCompile = true; //scene->update( model, QModelIndex() ); update(); } void GLView::modelDestroyed() { - setNif( 0 ); + setNif( nullptr ); } @@ -947,133 +1215,30 @@ void GLView::modelDestroyed() * UI */ -QMenu * GLView::createMenu() const -{ - QMenu * m = new QMenu( tr( "&Render" ) ); - m->addAction( aViewTop ); - m->addAction( aViewFront ); - m->addAction( aViewSide ); - m->addAction( aViewWalk ); - m->addAction( aViewUser ); - m->addSeparator(); - m->addAction( aViewFlip ); - m->addAction( aViewPerspective ); - m->addAction( aViewUserSave ); - m->addSeparator(); - m->addAction( aPrintView ); -#ifndef QT_NO_DEBUG - m->addAction( aColorKeyDebug ); -#endif - m->addSeparator(); - m->addActions( Options::actions() ); - return m; -} - -QList GLView::toolbars() const -{ - return{ tView, tAnim }; -} - -void GLView::viewAction( QAction * act ) -{ - BoundSphere bs = scene->bounds(); - - if ( Options::drawAxes() ) - bs |= BoundSphere( Vector3(), axis ); - - if ( bs.radius < 1 ) - bs.radius = 1; - - setDistance( bs.radius ); - setZoom( 1.0 ); - - if ( act == aViewWalk ) { - setRotation( -90, 0, 0 ); - setPosition( Vector3() - scene->bounds().center ); - setZoom( 1.0 ); - aViewWalk->setChecked( true ); - aViewTop->setChecked( false ); - aViewFront->setChecked( false ); - aViewSide->setChecked( false ); - aViewUser->setChecked( false ); - } - - if ( !act || act == aViewFlip ) { - act = checkedViewAction(); - } - - if ( act != aViewWalk ) - setPosition( Vector3() - bs.center ); - - if ( act == aViewTop ) { - if ( aViewFlip->isChecked() ) - setRotation( 180, 0, 0 ); - else - setRotation( 0, 0, 0 ); - - aViewWalk->setChecked( false ); - aViewTop->setChecked( true ); - aViewFront->setChecked( false ); - aViewSide->setChecked( false ); - aViewUser->setChecked( false ); - } else if ( act == aViewFront ) { - if ( aViewFlip->isChecked() ) - setRotation( -90, 0, 180 ); - else - setRotation( -90, 0, 0 ); - - aViewWalk->setChecked( false ); - aViewTop->setChecked( false ); - aViewFront->setChecked( true ); - aViewSide->setChecked( false ); - aViewUser->setChecked( false ); - } else if ( act == aViewSide ) { - if ( aViewFlip->isChecked() ) - setRotation( -90, 0, -90 ); - else - setRotation( -90, 0, 90 ); - - aViewWalk->setChecked( false ); - aViewTop->setChecked( false ); - aViewFront->setChecked( false ); - aViewSide->setChecked( true ); - aViewUser->setChecked( false ); - } else if ( act == aViewUser ) { - QSettings cfg; - cfg.beginGroup( "GLView" ); - cfg.beginGroup( "User View" ); - setRotation( cfg.value( "RotX" ).toDouble(), cfg.value( "RotY" ).toDouble(), cfg.value( "RotZ" ).toDouble() ); - setPosition( cfg.value( "PosX" ).toDouble(), cfg.value( "PosY" ).toDouble(), cfg.value( "PosZ" ).toDouble() ); - setDistance( cfg.value( "Dist" ).toDouble() ); - aViewWalk->setChecked( false ); - aViewTop->setChecked( false ); - aViewFront->setChecked( false ); - aViewSide->setChecked( false ); - aViewUser->setChecked( true ); - cfg.endGroup(); - cfg.endGroup(); - } - - update(); -} - -void GLView::sltTime( float t ) +void GLView::setSceneTime( float t ) { time = t; update(); - emit sigTime( time, scene->timeMin(), scene->timeMax() ); + emit sceneTimeChanged( time, scene->timeMin(), scene->timeMax() ); } -void GLView::sltSequence( const QString & seqname ) +void GLView::setSceneSequence( const QString & seqname ) { - animGroups->setCurrentIndex( scene->animGroups.indexOf( seqname ) ); + // Update UI + QAction * action = qobject_cast(sender()); + if ( !action ) { + // Called from self and not UI + emit sequenceChanged( seqname ); + } + scene->setSequence( seqname ); time = scene->timeMin(); - emit sigTime( time, scene->timeMin(), scene->timeMax() ); + emit sceneTimeChanged( time, scene->timeMin(), scene->timeMax() ); update(); } -void GLView::sltSaveUserView() +// TODO: Multiple user views, ala Recent Files +void GLView::saveUserView() { QSettings cfg; cfg.beginGroup( "GLView" ); @@ -1085,90 +1250,80 @@ void GLView::sltSaveUserView() cfg.setValue( "PosY", Pos[1] ); cfg.setValue( "PosZ", Pos[2] ); cfg.setValue( "Dist", Dist ); - viewAction( aViewUser ); cfg.endGroup(); cfg.endGroup(); } -QAction * GLView::checkedViewAction() const -{ - if ( aViewTop->isChecked() ) - return aViewTop; - else if ( aViewFront->isChecked() ) - return aViewFront; - else if ( aViewSide->isChecked() ) - return aViewSide; - else if ( aViewWalk->isChecked() ) - return aViewWalk; - else if ( aViewUser->isChecked() ) - return aViewUser; - - return 0; -} - -void GLView::uncheckViewAction() +void GLView::loadUserView() { - QAction * act = checkedViewAction(); - - if ( act && act != aViewWalk ) - act->setChecked( false ); + QSettings cfg; + cfg.beginGroup( "GLView" ); + cfg.beginGroup( "User View" ); + setRotation( cfg.value( "RotX" ).toDouble(), cfg.value( "RotY" ).toDouble(), cfg.value( "RotZ" ).toDouble() ); + setPosition( cfg.value( "PosX" ).toDouble(), cfg.value( "PosY" ).toDouble(), cfg.value( "PosZ" ).toDouble() ); + setDistance( cfg.value( "Dist" ).toDouble() ); + cfg.endGroup(); + cfg.endGroup(); } void GLView::advanceGears() { QTime t = QTime::currentTime(); float dT = lastTime.msecsTo( t ) / 1000.0; - - if ( Options::benchmark() ) { - fpsacc += dT; - fpscnt++; - update(); - } - - if ( dT < 0 ) - dT = 0; - - if ( dT > 1.0 ) - dT = 1.0; + dT = (dT < 0) ? 0 : ((dT > 1.0) ? 1.0 : dT); lastTime = t; if ( !isVisible() ) return; - if ( aAnimate->isChecked() && aAnimPlay->isChecked() && scene->timeMin() != scene->timeMax() ) { + if ( ( animState & AnimEnabled ) && ( animState & AnimPlay ) + && scene->timeMin() != scene->timeMax() ) + { time += dT; if ( time > scene->timeMax() ) { - if ( aAnimSwitch->isChecked() && !scene->animGroups.isEmpty() ) { + if ( ( animState & AnimSwitch ) && !scene->animGroups.isEmpty() ) { int ix = scene->animGroups.indexOf( scene->animGroup ); - + if ( ++ix >= scene->animGroups.count() ) ix -= scene->animGroups.count(); - - sltSequence( scene->animGroups.value( ix ) ); - } else if ( aAnimLoop->isChecked() ) { + + setSceneSequence( scene->animGroups.value( ix ) ); + } else if ( animState & AnimLoop ) { + time = scene->timeMin(); + } else { + // Animation has completed and is not looping + // or cycling through animations. + // Reset time and state and then inform UI it has stopped. time = scene->timeMin(); + animState &= ~AnimPlay; + emit sequenceStopped(); } + } else { + // Animation is not done yet } - emit sigTime( time, scene->timeMin(), scene->timeMax() ); + emit sceneTimeChanged( time, scene->timeMin(), scene->timeMax() ); update(); } + // TODO: Some kind of input class for choosing the appropriate + // keys based on user preferences of what app they would like to + // emulate for the control scheme // Rotation - if ( kbd[ Qt::Key_Up ] ) rotate( -ROT_SPD * dT, 0, 0 ); - if ( kbd[ Qt::Key_Down ] ) rotate( +ROT_SPD * dT, 0, 0 ); - if ( kbd[ Qt::Key_Left ] ) rotate( 0, 0, -ROT_SPD * dT ); - if ( kbd[ Qt::Key_Right ] ) rotate( 0, 0, +ROT_SPD * dT ); + if ( kbd[ Qt::Key_Up ] ) rotate( -cfg.rotSpd * dT, 0, 0 ); + if ( kbd[ Qt::Key_Down ] ) rotate( +cfg.rotSpd * dT, 0, 0 ); + if ( kbd[ Qt::Key_Left ] ) rotate( 0, 0, -cfg.rotSpd * dT ); + if ( kbd[ Qt::Key_Right ] ) rotate( 0, 0, +cfg.rotSpd * dT ); // Movement - if ( kbd[ Qt::Key_A ] ) move( +MOV_SPD * dT, 0, 0 ); - if ( kbd[ Qt::Key_D ] ) move( -MOV_SPD * dT, 0, 0 ); - if ( kbd[ Qt::Key_W ] ) move( 0, 0, +MOV_SPD * dT ); - if ( kbd[ Qt::Key_S ] ) move( 0, 0, -MOV_SPD * dT ); - if ( kbd[ Qt::Key_F ] ) move( 0, +MOV_SPD * dT, 0 ); - if ( kbd[ Qt::Key_R ] ) move( 0, -MOV_SPD * dT, 0 ); + if ( kbd[ Qt::Key_A ] ) move( +cfg.moveSpd * dT, 0, 0 ); + if ( kbd[ Qt::Key_D ] ) move( -cfg.moveSpd * dT, 0, 0 ); + if ( kbd[ Qt::Key_W ] ) move( 0, 0, +cfg.moveSpd * dT ); + if ( kbd[ Qt::Key_S ] ) move( 0, 0, -cfg.moveSpd * dT ); + //if ( kbd[ Qt::Key_F ] ) move( 0, +MOV_SPD * dT, 0 ); + //if ( kbd[ Qt::Key_R ] ) move( 0, -MOV_SPD * dT, 0 ); // Zoom if ( kbd[ Qt::Key_Q ] ) setDistance( Dist * 1.0 / 1.1 ); @@ -1189,64 +1344,21 @@ void GLView::advanceGears() } } -void GLView::checkActions() -{ - scene->animate = aAnimate->isChecked(); - - lastTime = QTime::currentTime(); - - if ( Options::benchmark() ) - timer->setInterval( 0 ); - else - timer->setInterval( 1000 / FPS ); - - update(); -} - - -/* - * Settings - */ - -void GLView::save( QSettings & settings ) -{ - settings.setValue( "GLView/Enable Animations", aAnimate->isChecked() ); - settings.setValue( "GLView/Play Animation", aAnimPlay->isChecked() ); - settings.setValue( "GLView/Loop Animation", aAnimLoop->isChecked() ); - settings.setValue( "GLView/Switch Animation", aAnimSwitch->isChecked() ); - settings.setValue( "GLView/Perspective", aViewPerspective->isChecked() ); - - if ( checkedViewAction() ) - settings.setValue( "GLView/View Action", checkedViewAction()->text() ); -} - -void GLView::restore( const QSettings & settings ) -{ - aAnimate->setChecked( settings.value( "GLView/Enable Animations", true ).toBool() ); - aAnimPlay->setChecked( settings.value( "GLView/Play Animation", true ).toBool() ); - aAnimLoop->setChecked( settings.value( "GLView/Loop Animation", true ).toBool() ); - aAnimSwitch->setChecked( settings.value( "GLView/Switch Animation", true ).toBool() ); - aViewPerspective->setChecked( settings.value( "GLView/Perspective", true ).toBool() ); - checkActions(); - QString viewAct = settings.value( "GLView/View Action", "Front" ).toString(); - for ( QAction * act : grpView->actions() ) { - if ( act->text() == viewAct ) - viewAction( act ); - } -} +// TODO: Separate widget void GLView::saveImage() { - QDialog dlg; - QGridLayout * lay = new QGridLayout; - dlg.setWindowTitle( tr( "Save View" ) ); - dlg.setLayout( lay ); - dlg.setMinimumWidth( 400 ); + auto dlg = new QDialog( qApp->activeWindow() ); + QGridLayout * lay = new QGridLayout( dlg ); + dlg->setWindowTitle( tr( "Save View" ) ); + dlg->setLayout( lay ); + dlg->setMinimumWidth( 400 ); QString date = QDateTime::currentDateTime().toString( "yyyyMMdd_HH-mm-ss" ); QString name = model->getFilename(); QString nifFolder = model->getFolder(); + // TODO: Default extension in Settings QString filename = name + (!name.isEmpty() ? "_" : "") + date + ".jpg"; // Default: NifSkope directory @@ -1256,55 +1368,110 @@ void GLView::saveImage() QString nifPath = nifFolder + (!nifFolder.isEmpty() ? "/" : "") + filename; FileSelector * file = new FileSelector( FileSelector::SaveFile, tr( "File" ), QBoxLayout::LeftToRight ); - file->setFilter( { "Images (*.jpg *.png *.bmp)", "JPEG (*.jpg)", "PNG (*.png)", "BMP (*.bmp)" } ); + file->setParent( dlg ); + // TODO: Default extension in Settings + file->setFilter( { "Images (*.jpg *.png *.webp *.bmp)", "JPEG (*.jpg)", "PNG (*.png)", "WebP (*.webp)", "BMP (*.bmp)" } ); file->setFile( nifskopePath ); lay->addWidget( file, 0, 0, 1, -1 ); + + auto grpDir = new QButtonGroup( dlg ); - QRadioButton * nifskopeDir = new QRadioButton( tr( "NifSkope Directory" ), this ); + QRadioButton * nifskopeDir = new QRadioButton( tr( "NifSkope Directory" ), dlg ); nifskopeDir->setChecked( true ); nifskopeDir->setToolTip( tr( "Save to NifSkope screenshots directory" ) ); - QRadioButton * niffileDir = new QRadioButton( tr( "NIF Directory" ), this ); + QRadioButton * niffileDir = new QRadioButton( tr( "NIF Directory" ), dlg ); niffileDir->setChecked( false ); niffileDir->setDisabled( nifFolder.isEmpty() ); niffileDir->setToolTip( tr( "Save to NIF file directory" ) ); + grpDir->addButton( nifskopeDir ); + grpDir->addButton( niffileDir ); + grpDir->setExclusive( true ); + lay->addWidget( nifskopeDir, 1, 0, 1, 1 ); lay->addWidget( niffileDir, 1, 1, 1, 1 ); // Save JPEG Quality QSettings cfg; - int jpegQuality = cfg.value( "JPEG/Quality", 91 ).toInt(); + int jpegQuality = cfg.value( "JPEG/Quality", 90 ).toInt(); cfg.setValue( "JPEG/Quality", jpegQuality ); QHBoxLayout * pixBox = new QHBoxLayout; pixBox->setAlignment( Qt::AlignRight ); - QSpinBox * pixQuality = new QSpinBox; + QSpinBox * pixQuality = new QSpinBox( dlg ); pixQuality->setRange( -1, 100 ); pixQuality->setSingleStep( 10 ); pixQuality->setValue( jpegQuality ); pixQuality->setSpecialValueText( tr( "Auto" ) ); pixQuality->setMaximumWidth( pixQuality->minimumSizeHint().width() ); - pixBox->addWidget( new QLabel( tr( "JPEG Quality" ) ) ); + pixBox->addWidget( new QLabel( tr( "JPEG Quality" ), dlg ) ); pixBox->addWidget( pixQuality ); lay->addLayout( pixBox, 1, 2, Qt::AlignRight ); - QHBoxLayout * hBox = new QHBoxLayout; - QPushButton * btnOk = new QPushButton( tr( "Save" ) ); - QPushButton * btnCancel = new QPushButton( tr( "Cancel" ) ); + + // Image Size radio button lambda + auto btnSize = [dlg]( const QString & name ) { + auto btn = new QRadioButton( name, dlg ); + btn->setCheckable( true ); + + return btn; + }; + + // Get max viewport size for platform + GLint dims; + glGetIntegerv( GL_MAX_VIEWPORT_DIMS, &dims ); + int maxSize = dims; + + // Default size + auto btnOneX = btnSize( "1x" ); + btnOneX->setChecked( true ); + // Disable any of these that would exceed the max viewport size of the platform + auto btnTwoX = btnSize( "2x" ); + btnTwoX->setDisabled( (width() * 2) > maxSize || (height() * 2) > maxSize ); + auto btnFourX = btnSize( "4x" ); + btnFourX->setDisabled( (width() * 4) > maxSize || (height() * 4) > maxSize ); + auto btnEightX = btnSize( "8x" ); + btnEightX->setDisabled( (width() * 8) > maxSize || (height() * 8) > maxSize ); + + + auto grpBox = new QGroupBox( tr( "Image Size" ), dlg ); + auto grpBoxLayout = new QHBoxLayout; + grpBoxLayout->addWidget( btnOneX ); + grpBoxLayout->addWidget( btnTwoX ); + grpBoxLayout->addWidget( btnFourX ); + grpBoxLayout->addWidget( btnEightX ); + grpBoxLayout->addWidget( new QLabel( "Caution:
4x and 8x may be memory intensive.", dlg ) ); + grpBoxLayout->addStretch( 1 ); + grpBox->setLayout( grpBoxLayout ); + + auto grpSize = new QButtonGroup( dlg ); + grpSize->addButton( btnOneX, 1 ); + grpSize->addButton( btnTwoX, 2 ); + grpSize->addButton( btnFourX, 4 ); + grpSize->addButton( btnEightX, 8 ); + + grpSize->setExclusive( true ); + + lay->addWidget( grpBox, 2, 0, 1, -1 ); + + + QHBoxLayout * hBox = new QHBoxLayout; + QPushButton * btnOk = new QPushButton( tr( "Save" ), dlg ); + QPushButton * btnCancel = new QPushButton( tr( "Cancel" ), dlg ); hBox->addWidget( btnOk ); hBox->addWidget( btnCancel ); - lay->addLayout( hBox, 2, 0, 1, -1 ); + lay->addLayout( hBox, 3, 0, 1, -1 ); // Set FileSelector to NifSkope dir (relative) - connect( nifskopeDir, &QRadioButton::clicked, [&]() + connect( nifskopeDir, &QRadioButton::clicked, [=]() { file->setText( nifskopePath ); file->setFile( nifskopePath ); } ); // Set FileSelector to NIF File dir (absolute) - connect( niffileDir, &QRadioButton::clicked, [&]() + connect( niffileDir, &QRadioButton::clicked, [=]() { file->setText( nifPath ); file->setFile( nifPath ); @@ -1315,6 +1482,7 @@ void GLView::saveImage() connect( btnOk, &QPushButton::clicked, [&]() { // Save JPEG Quality + QSettings cfg; cfg.setValue( "JPEG/Quality", pixQuality->value() ); // TODO: Set up creation of screenshots directory in Options @@ -1323,18 +1491,74 @@ void GLView::saveImage() workingDir.mkpath( "screenshots" ); } - QImage img = grabFrameBuffer(); - if ( img.save( file->file(), 0, pixQuality->value() ) ) { - dlg.accept(); + // Supersampling + int ss = grpSize->checkedId(); + + int w, h; + + w = width(); + h = height(); + + // Resize viewport for supersampling + if ( ss > 1 ) { + w *= ss; + h *= ss; + + resizeGL( w, h ); + } + + QOpenGLFramebufferObjectFormat fboFmt; + fboFmt.setTextureTarget( GL_TEXTURE_2D ); + fboFmt.setInternalTextureFormat( GL_RGB ); + fboFmt.setMipmap( false ); + fboFmt.setAttachment( QOpenGLFramebufferObject::Attachment::Depth ); + fboFmt.setSamples( 16 / ss ); + + QOpenGLFramebufferObject fbo( w, h, fboFmt ); + fbo.bind(); + + update(); + updateGL(); + + fbo.release(); + + QImage * img = new QImage(fbo.toImage()); + + // Return viewport to original size + if ( ss > 1 ) + resizeGL( width(), height() ); + + + QImageWriter writer( file->file() ); + + // Set Compression for formats that can use it + writer.setCompression( 1 ); + + // Handle JPEG/WebP Quality exclusively + // PNG will not use compression if Quality is set + if ( file->file().endsWith( ".jpg", Qt::CaseInsensitive ) ) { + writer.setFormat( "jpg" ); + writer.setQuality( 50 + pixQuality->value() / 2 ); + } else if ( file->file().endsWith( ".webp", Qt::CaseInsensitive ) ) { + writer.setFormat( "webp" ); + writer.setQuality( 75 + pixQuality->value() / 4 ); + } + + if ( writer.write( *img ) ) { + dlg->accept(); } else { - qWarning() << "Could not save to file. Please check the filepath and extension are valid."; + Message::critical( this, tr( "Could not save %1" ).arg( file->file() ) ); } + + delete img; + img = nullptr; } ); - connect( btnCancel, &QPushButton::clicked, &dlg, &QDialog::reject ); + connect( btnCancel, &QPushButton::clicked, dlg, &QDialog::reject ); - if ( dlg.exec() != QDialog::Accepted ) + if ( dlg->exec() != QDialog::Accepted ) { return; + } } @@ -1431,8 +1655,8 @@ void GLView::keyPressEvent( QKeyEvent * event ) case Qt::Key_D: case Qt::Key_W: case Qt::Key_S: - case Qt::Key_R: - case Qt::Key_F: + //case Qt::Key_R: + //case Qt::Key_F: case Qt::Key_Q: case Qt::Key_E: kbd[event->key()] = true; @@ -1440,26 +1664,11 @@ void GLView::keyPressEvent( QKeyEvent * event ) case Qt::Key_Escape: doCompile = true; - if ( !aViewWalk->isChecked() ) + if ( view == ViewWalk ) doCenter = true; update(); break; - case Qt::Key_C: - { - Node * node = scene->getNode( model, scene->currentBlock ); - - if ( node != 0 ) { - BoundSphere bs = node->bounds(); - - this->setPosition( -bs.center ); - - if ( bs.radius > 0 ) { - setDistance( bs.radius * 1.5f ); - } - } - } - break; default: event->ignore(); break; @@ -1479,8 +1688,8 @@ void GLView::keyReleaseEvent( QKeyEvent * event ) case Qt::Key_D: case Qt::Key_W: case Qt::Key_S: - case Qt::Key_R: - case Qt::Key_F: + //case Qt::Key_R: + //case Qt::Key_F: case Qt::Key_Q: case Qt::Key_E: kbd[event->key()] = false; @@ -1520,6 +1729,11 @@ void GLView::mouseMoveEvent( QMouseEvent * event ) void GLView::mousePressEvent( QMouseEvent * event ) { + if ( event->button() == Qt::ForwardButton || event->button() == Qt::BackButton ) { + event->ignore(); + return; + } + lastPos = event->pos(); if ( (pressPos - event->pos()).manhattanLength() <= 3 ) @@ -1535,12 +1749,47 @@ void GLView::mouseReleaseEvent( QMouseEvent * event ) if ( !(model && (pressPos - event->pos()).manhattanLength() <= 3) ) return; - QModelIndex idx = indexAt( event->pos(), cycleSelect ); - scene->currentBlock = model->getBlock( idx ); - scene->currentIndex = idx.sibling( idx.row(), 0 ); + if ( event->button() == Qt::ForwardButton || event->button() == Qt::BackButton || event->button() == Qt::MiddleButton ) { + event->ignore(); + return; + } + + auto mods = event->modifiers(); + + if ( !(mods & Qt::AltModifier) ) { + QModelIndex idx = indexAt( event->pos(), cycleSelect ); + scene->currentBlock = model->getBlock( idx ); + scene->currentIndex = idx.sibling( idx.row(), 0 ); + + if ( idx.isValid() ) { + emit clicked( QModelIndex() ); // HACK: To get Block Details to update + emit clicked( idx ); + } + + } else { + // Color Picker / Eyedrop tool + QOpenGLFramebufferObjectFormat fboFmt; + fboFmt.setTextureTarget( GL_TEXTURE_2D ); + fboFmt.setInternalTextureFormat( GL_RGB ); + fboFmt.setMipmap( false ); + fboFmt.setAttachment( QOpenGLFramebufferObject::Attachment::Depth ); + + QOpenGLFramebufferObject fbo( width(), height(), fboFmt ); + fbo.bind(); - if ( idx.isValid() ) { - emit clicked( idx ); + update(); + updateGL(); + + fbo.release(); + + QImage * img = new QImage( fbo.toImage() ); + + auto what = img->pixel( event->pos() ); + + qglClearColor( QColor( what ) ); + // qDebug() << QColor( what ); + + delete img; } update(); @@ -1548,8 +1797,186 @@ void GLView::mouseReleaseEvent( QMouseEvent * event ) void GLView::wheelEvent( QWheelEvent * event ) { - if ( aViewWalk->isChecked() ) + if ( view == ViewWalk ) mouseMov += Vector3( 0, 0, event->delta() ); else setDistance( Dist * (event->delta() < 0 ? 1.0 / 0.8 : 0.8) ); } + + +void GLGraphicsView::setupViewport( QWidget * viewport ) +{ + GLView * glWidget = qobject_cast(viewport); + if ( glWidget ) { + //glWidget->installEventFilter( this ); + } + + QGraphicsView::setupViewport( viewport ); +} + +bool GLGraphicsView::eventFilter( QObject * o, QEvent * e ) +{ + //GLView * glWidget = qobject_cast(o); + //if ( glWidget ) { + // + //} + + return QGraphicsView::eventFilter( o, e ); +} + +//void GLGraphicsView::paintEvent( QPaintEvent * e ) +//{ +// GLView * glWidget = qobject_cast(viewport()); +// if ( glWidget ) { +// // glWidget->paintEvent( e ); +// } +// +// QGraphicsView::paintEvent( e ); +//} + +void GLGraphicsView::drawForeground( QPainter * painter, const QRectF & rect ) +{ + QGraphicsView::drawForeground( painter, rect ); +} + +void GLGraphicsView::drawBackground( QPainter * painter, const QRectF & rect ) +{ + Q_UNUSED( painter ); Q_UNUSED( rect ); + + GLView * glWidget = qobject_cast(viewport()); + if ( glWidget ) { + glWidget->updateGL(); + } + + //QGraphicsView::drawBackground( painter, rect ); +} + +void GLGraphicsView::dragEnterEvent( QDragEnterEvent * e ) +{ + // Intercept NIF files + if ( e->mimeData()->hasUrls() ) { + QList urls = e->mimeData()->urls(); + for ( auto url : urls ) { + if ( url.scheme() == "file" ) { + QString fn = url.toLocalFile(); + QFileInfo finfo( fn ); + if ( finfo.exists() && NifSkope::fileExtensions().contains( finfo.suffix(), Qt::CaseInsensitive ) ) { + draggedNifs << finfo.absoluteFilePath(); + } + } + } + + if ( !draggedNifs.isEmpty() ) { + e->accept(); + return; + } + } + + // Pass event on to viewport for any texture drag/drops + GLView * glWidget = qobject_cast(viewport()); + if ( glWidget ) { + glWidget->dragEnterEvent( e ); + } +} +void GLGraphicsView::dragLeaveEvent( QDragLeaveEvent * e ) +{ + if ( !draggedNifs.isEmpty() ) { + draggedNifs.clear(); + e->ignore(); + return; + } + + // Pass event on to viewport for any texture drag/drops + GLView * glWidget = qobject_cast(viewport()); + if ( glWidget ) { + glWidget->dragLeaveEvent( e ); + } +} +void GLGraphicsView::dragMoveEvent( QDragMoveEvent * e ) +{ + if ( !draggedNifs.isEmpty() ) { + e->accept(); + return; + } + + // Pass event on to viewport for any texture drag/drops + GLView * glWidget = qobject_cast(viewport()); + if ( glWidget ) { + glWidget->dragMoveEvent( e ); + } +} +void GLGraphicsView::dropEvent( QDropEvent * e ) +{ + if ( !draggedNifs.isEmpty() ) { + auto ns = qobject_cast(parentWidget()); + if ( ns ) { + ns->openFiles( draggedNifs ); + } + + draggedNifs.clear(); + e->accept(); + return; + } + + // Pass event on to viewport for any texture drag/drops + GLView * glWidget = qobject_cast(viewport()); + if ( glWidget ) { + glWidget->dropEvent( e ); + } +} +void GLGraphicsView::focusOutEvent( QFocusEvent * e ) +{ + GLView * glWidget = qobject_cast(viewport()); + if ( glWidget ) { + glWidget->focusOutEvent( e ); + } +} +void GLGraphicsView::keyPressEvent( QKeyEvent * e ) +{ + GLView * glWidget = qobject_cast(viewport()); + if ( glWidget ) { + glWidget->keyPressEvent( e ); + } +} +void GLGraphicsView::keyReleaseEvent( QKeyEvent * e ) +{ + GLView * glWidget = qobject_cast(viewport()); + if ( glWidget ) { + glWidget->keyReleaseEvent( e ); + } +} +void GLGraphicsView::mouseDoubleClickEvent( QMouseEvent * e ) +{ + GLView * glWidget = qobject_cast(viewport()); + if ( glWidget ) { + glWidget->mouseDoubleClickEvent( e ); + } +} +void GLGraphicsView::mouseMoveEvent( QMouseEvent * e ) +{ + GLView * glWidget = qobject_cast(viewport()); + if ( glWidget ) { + glWidget->mouseMoveEvent( e ); + } +} +void GLGraphicsView::mousePressEvent( QMouseEvent * e ) +{ + GLView * glWidget = qobject_cast(viewport()); + if ( glWidget ) { + glWidget->mousePressEvent( e ); + } +} +void GLGraphicsView::mouseReleaseEvent( QMouseEvent * e ) +{ + GLView * glWidget = qobject_cast(viewport()); + if ( glWidget ) { + glWidget->mouseReleaseEvent( e ); + } +} +void GLGraphicsView::wheelEvent( QWheelEvent * e ) +{ + GLView * glWidget = qobject_cast(viewport()); + if ( glWidget ) { + glWidget->wheelEvent( e ); + } +} diff --git a/src/glview.h b/src/glview.h index 7618503a9..425e8de13 100644 --- a/src/glview.h +++ b/src/glview.h @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -34,18 +34,22 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define GLVIEW #include "nifmodel.h" +#include "gl/glscene.h" #include "widgets/floatedit.h" #include "widgets/floatslider.h" #include // Inherited +#include #include #include #include -//! \file glview.h GLView class +//! @file glview.h GLView, GLGraphicsView +class NifSkope; +class GLGraphicsView; class Scene; class QAction; @@ -59,93 +63,140 @@ class QSettings; class QToolBar; class QTimer; -//! The model view window + +//! The main [Viewport](@ref viewport_details) class class GLView final : public QGLWidget { Q_OBJECT - //! Constructor - GLView( const QGLFormat & format, const QGLWidget * shareWidget = 0 ); - //! Destructor + friend class NifSkope; + friend class GLGraphicsView; + +private: + GLView( const QGLFormat & format, QWidget * parent, const QGLWidget * shareWidget = 0 ); ~GLView(); public: //! Static instance - static GLView * create(); + static GLView * create( NifSkope * ); QOpenGLContext * glContext; QOpenGLFunctions * glFuncs; + float brightness = 1.0; + float ambient = 0.375; + float declination = 0; + float planarAngle = 0; + bool frontalLight = true; + + enum AnimationStates + { + AnimDisabled = 0x0, + AnimEnabled = 0x1, + AnimPlay = 0x2, + AnimLoop = 0x4, + AnimSwitch = 0x8 + }; + Q_DECLARE_FLAGS( AnimationState, AnimationStates ); + + AnimationState animState; + + enum ViewState + { + ViewDefault, + ViewTop, + ViewBottom, + ViewLeft, + ViewRight, + ViewFront, + ViewBack, + ViewWalk, + ViewUser + }; + + enum DebugMode + { + DbgNone = 0, + DbgColorPicker = 1, + DbgBounds = 2 + }; + + enum UpAxis + { + XAxis = 0, + YAxis = 1, + ZAxis = 2 + }; + + void setNif( NifModel * ); + Scene * getScene(); void updateShaders(); + void updateViewpoint(); + + void flush(); void center(); void move( float, float, float ); void rotate( float, float, float ); void zoom( float ); + void setCenter(); void setDistance( float ); void setPosition( float, float, float ); void setPosition( Vector3 ); + void setProjection( bool ); void setRotation( float, float, float ); void setZoom( float ); - QModelIndex indexAt( const QPoint & p, int cycle = 0 ); - - // UI + void setOrientation( GLView::ViewState, bool recenter = true ); + void flipOrientation(); - QActionGroup * grpView; + void setDebugMode( DebugMode ); - QAction * aViewWalk; - QAction * aViewTop; - QAction * aViewFront; - QAction * aViewSide; - QAction * aViewFlip; - QAction * aViewPerspective; - QAction * aViewUser; - QAction * aViewUserSave; - QAction * aPrintView; - QAction * aColorKeyDebug; - QAction * aAnimate; - QAction * aAnimPlay; - QAction * aAnimLoop; - QAction * aAnimSwitch; + QColor clearColor() const; - QToolBar * tAnim; - QToolBar * tView; - QComboBox * animGroups; - FloatSlider * sldTime; + QModelIndex indexAt( const QPoint & p, int cycle = 0 ); - QMenu * createMenu() const; - QList toolbars() const; + // UI QSize minimumSizeHint() const override final { return { 50, 50 }; } QSize sizeHint() const override final { return { 400, 400 }; } - // Settings - - void save( QSettings & ); - void restore( const QSettings & ); - public slots: - void setNif( NifModel * ); void setCurrentIndex( const QModelIndex & ); - - void sltTime( float ); - void sltSequence( const QString & ); - void sltSaveUserView(); + void setSceneTime( float ); + void setSceneSequence( const QString & ); + void saveUserView(); + void loadUserView(); + void setBrightness( int ); + void setAmbient( int ); + void setDeclination( int ); + void setPlanarAngle( int ); + void setFrontalLight( bool ); + void updateScene(); + void updateAnimationState( bool checked ); + void setVisMode( Scene::VisMode, bool checked = true ); + void updateSettings(); signals: void clicked( const QModelIndex & ); void paintUpdate(); - void sigTime( float t, float mn, float mx ); + void sceneTimeChanged( float t, float mn, float mx ); + void viewpointChanged(); + + void sequenceStopped(); + void sequenceChanged( const QString & ); + void sequencesUpdated(); + void sequencesDisabled( bool ); protected: //! Sets up the OpenGL rendering context, defines display lists, etc. void initializeGL() override final; //! Sets up the OpenGL viewport, projection, etc. void resizeGL( int width, int height ) override final; + void resizeEvent( QResizeEvent * event ) override final; #ifdef USE_GL_QPAINTER void paintEvent( QPaintEvent * ) override final; #else @@ -176,23 +227,25 @@ protected slots: NifModel * model; Scene * scene; + ViewState view; + DebugMode debugMode; + bool perspectiveMode; + class TexCache * textures; float time; QTime lastTime; QTimer * timer; - int fpscnt; - float fpsact; - float fpsacc; - float Dist; Vector3 Pos; Vector3 Rot; GLdouble Zoom; GLdouble axis; + GLdouble grid; Transform viewTrans; - int zInc; + + GLdouble aspect; QHash kbd; QPoint lastPos; @@ -206,22 +259,66 @@ protected slots: bool doCompile; bool doCenter; - bool doMultisampling; - QAction * checkedViewAction() const; - void uncheckViewAction(); + QTimer * lightVisTimer; + int lightVisTimeout; + + struct Settings + { + QColor background; + float fov = 45.0; + float moveSpd = 350; + float rotSpd = 45; + + int upAxis; + } cfg; private slots: void advanceGears(); - void checkActions(); - void viewAction( QAction * ); void dataChanged( const QModelIndex &, const QModelIndex & ); void modelChanged(); void modelLinked(); void modelDestroyed(); +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS( GLView::AnimationState ) + + +class GLGraphicsView : public QGraphicsView +{ + Q_OBJECT + +public: + GLGraphicsView( QWidget * parent ); + ~GLGraphicsView(); + +protected slots: + virtual void setupViewport( QWidget * viewport ); + +protected: + bool eventFilter( QObject * o, QEvent * e ) override final; + void dragEnterEvent( QDragEnterEvent * ) override final; + void dragLeaveEvent( QDragLeaveEvent * ) override final; + void dragMoveEvent( QDragMoveEvent * ) override final; + void dropEvent( QDropEvent * ) override final; + void focusOutEvent( QFocusEvent * ) override final; + void keyPressEvent( QKeyEvent * ) override final; + void keyReleaseEvent( QKeyEvent * ) override final; + void mouseDoubleClickEvent( QMouseEvent * ) override final; + void mouseMoveEvent( QMouseEvent * ) override final; + void mousePressEvent( QMouseEvent * ) override final; + void mouseReleaseEvent( QMouseEvent * ) override final; + void wheelEvent( QWheelEvent * ) override final; + + //void paintEvent( QPaintEvent * ) override final; + void drawBackground( QPainter * painter, const QRectF & rect ) override final; + void drawForeground( QPainter * painter, const QRectF & rect ) override final; + +private: + + QStringList draggedNifs; - void sceneUpdate(); }; #endif diff --git a/src/hacking.h b/src/hacking.h deleted file mode 100644 index 88eb09efa..000000000 --- a/src/hacking.h +++ /dev/null @@ -1,42 +0,0 @@ -// Please run doxygen and view apidocs/html/index.html - -/*! - * \mainpage NifSkope API Documentation - * - * A concise description of NifSkope's inner workings should come here, with - * pointers to get people started. - * - * The class list and - * file list will eventually contain brief summaries - * of what every class is used for, and what every file contains, respectively. - * - * - \ref intro - * - \ref parsing - * - * \page intro Introduction - * - * %NifSkope is a graphical program that allows you to open NIF files, view - * their contents, edit them, and write them back out again. It is written in - * C++ using OpenGL and the Qt framework, and designed using the - * Model/View - * Programming paradigm. - * - * The main application is present in the NifSkope class; rendering takes place - * via GLView. A central feature of Qt is the - * Signals and Slots - * mechanism, which is used extensively to build the GUI. A NIF is internally represented - * as a NifModel, and blocks are referenced by means of QModelIndex and - * QPersistentModelIndex. - * - * Various "magic" functions can be performed on a NIF via the Spell system; - * this is probably a good place to start if you want to learn about how a NIF - * is typically structured and how the blocks are manipulated. - * - *
Next: \ref parsing
- * - * \page parsing Reading and parsing a NIF - * - * The NIF specification is currently described by nif.xml and parsed using NifXmlHandler. - * - */ - diff --git a/src/importex/3ds.cpp b/src/importex/3ds.cpp index 546acabe9..ef97eabd0 100644 --- a/src/importex/3ds.cpp +++ b/src/importex/3ds.cpp @@ -1,3 +1,10 @@ +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-variable" +#pragma GCC diagnostic ignored "-Wunused-but-set-variable" +#endif + + #include "3ds.h" #include "nvtristripwrapper.h" @@ -252,28 +259,28 @@ void import3ds( NifModel * nif, const QModelIndex & index ) QFile fobj( fname ); if ( !fobj.open( QIODevice::ReadOnly ) ) { - qWarning() << tr( "Could not open %1 for read access" ).arg( fobj.fileName() ); + qCCritical( nsIo ) << tr( "Failed to read %1" ).arg( fobj.fileName() ); return; } Chunk * FileChunk = Chunk::LoadFile( &fobj ); if ( !FileChunk ) { - qWarning() << tr( "Could not get 3ds data" ); + qCCritical( nsIo ) << tr( "Could not get 3ds data" ); return; } Chunk * Model = FileChunk->getChild( M3DMAGIC ); if ( !Model ) { - qWarning() << tr( "Could not get 3ds model" ); + qCCritical( nsIo ) << tr( "Could not get 3ds model" ); return; } Chunk * ModelData = Model->getChild( MDATA ); if ( !ModelData ) { - qWarning() << tr( "Could not get 3ds model data" ); + qCCritical( nsIo ) << tr( "Could not get 3ds model data" ); return; } @@ -590,7 +597,8 @@ void import3ds( NifModel * nif, const QModelIndex & index ) for ( int i = 0; i < mesh->matfaces.size(); i++ ) { if ( !ObjMaterials.contains( mesh->matfaces[i].matName ) ) { - qWarning() << tr( "Material '%1' not found in list!" ).arg( mesh->matfaces[i].matName ); + Message::append( tr( "Warnings were generated during 3ds import." ), + tr( "Material '%1' not found in list." ).arg( mesh->matfaces[i].matName ) ); } objMaterial * mat = &ObjMaterials[mesh->matfaces[i].matName]; @@ -751,3 +759,7 @@ void import3ds( NifModel * nif, const QModelIndex & index ) nif->reset(); return; } + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif diff --git a/src/importex/3ds.h b/src/importex/3ds.h index 61017870e..7389782d0 100644 --- a/src/importex/3ds.h +++ b/src/importex/3ds.h @@ -267,6 +267,7 @@ class Chunk } private: + QFile * f; ChunkHeader h; ChunkPos p; ChunkDataFlag df; @@ -274,7 +275,6 @@ class Chunk ChunkDataLength dl; ChunkDataCount dc; - QFile * f; QMap c; void subproc() diff --git a/src/importex/col.cpp b/src/importex/col.cpp index ef26ed41a..6447c09e4 100644 --- a/src/importex/col.cpp +++ b/src/importex/col.cpp @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -31,7 +31,6 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "config.h" -#include "options.h" #include "nifmodel.h" #include "nvtristripwrapper.h" @@ -963,8 +962,8 @@ void attachNiNode ( const NifModel * nif, QDomElement parentNode, int idx ) void exportCol( const NifModel * nif, QFileInfo fileInfo ) { - culling = Options::get()->exportCullEnabled(); - cullRegExp = Options::get()->cullExpression(); + //culling = Options::get()->exportCullEnabled(); + //cullRegExp = Options::get()->cullExpression(); QList roots = nif->getRootLinks(); QString question; @@ -983,7 +982,7 @@ void exportCol( const NifModel * nif, QFileInfo fileInfo ) QFile fobj( fname + ".dae" ); if ( !fobj.open( QIODevice::WriteOnly ) ) { - qWarning() << "could not open " << fobj.fileName() << " for write access"; + qCCritical( nsIo ) << tr( "Failed to write %1" ).arg( fobj.fileName() ); return; } diff --git a/src/importex/importex.cpp b/src/importex/importex.cpp index ae5b0f035..88a667d6f 100644 --- a/src/importex/importex.cpp +++ b/src/importex/importex.cpp @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -50,7 +50,7 @@ void import3ds( NifModel * nif, const QModelIndex & index ); void NifSkope::fillImportExportMenus() { mExport->addAction( tr( "Export .OBJ" ) ); - mExport->addAction( tr( "Export .DAE" ) ); + //mExport->addAction( tr( "Export .DAE" ) ); mImport->addAction( tr( "Import .3DS" ) ); mImport->addAction( tr( "Import .OBJ" ) ); } @@ -82,5 +82,5 @@ void NifSkope::sltImportExport( QAction * a ) else if ( a->text() == tr( "Import .3DS" ) ) import3ds( nif, index ); else if ( a->text() == tr( "Export .DAE" ) ) - exportCol( nif, this->getLoadFileName() ); + exportCol( nif, currentFile ); } diff --git a/src/importex/obj.cpp b/src/importex/obj.cpp index fcc9bcda5..523faee8a 100644 --- a/src/importex/obj.cpp +++ b/src/importex/obj.cpp @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -30,8 +30,6 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ -#include "options.h" - #include "nifmodel.h" #include "nvtristripwrapper.h" #include "gl/gltex.h" @@ -61,74 +59,138 @@ static void writeData( const NifModel * nif, const QModelIndex & iData, QTextStr { // copy vertices - QVector verts = nif->getArray( iData, "Vertices" ); - foreach ( Vector3 v, verts ) { - v = t * v; - obj << "v " << qSetRealNumberPrecision( 17 ) << v[0] << " " << v[1] << " " << v[2] << "\r\n"; - } + if ( nif->getUserVersion2() < 130 ) { + QVector verts = nif->getArray( iData, "Vertices" ); + foreach( Vector3 v, verts ) + { + v = t * v; + obj << "v " << qSetRealNumberPrecision( 17 ) << v[0] << " " << v[1] << " " << v[2] << "\r\n"; + } - // copy texcoords + // copy texcoords - QModelIndex iUV = nif->getIndex( iData, "UV Sets" ); + QModelIndex iUV = nif->getIndex( iData, "UV Sets" ); - if ( !iUV.isValid() ) - iUV = nif->getIndex( iData, "UV Sets 2" ); + if ( !iUV.isValid() ) + iUV = nif->getIndex( iData, "UV Sets 2" ); - QVector texco = nif->getArray( iUV.child( 0, 0 ) ); - foreach ( Vector2 t, texco ) { - obj << "vt " << t[0] << " " << 1.0 - t[1] << "\r\n"; - } + QVector texco = nif->getArray( iUV.child( 0, 0 ) ); + foreach( Vector2 t, texco ) + { + obj << "vt " << t[0] << " " << 1.0 - t[1] << "\r\n"; + } - // copy normals + // copy normals - QVector norms = nif->getArray( iData, "Normals" ); - foreach ( Vector3 n, norms ) { - n = t.rotation * n; - obj << "vn " << n[0] << " " << n[1] << " " << n[2] << "\r\n"; - } + QVector norms = nif->getArray( iData, "Normals" ); + foreach( Vector3 n, norms ) + { + n = t.rotation * n; + obj << "vn " << n[0] << " " << n[1] << " " << n[2] << "\r\n"; + } + + // get the triangles - // get the triangles + QVector tris; - QVector tris; + QModelIndex iPoints = nif->getIndex( iData, "Points" ); - QModelIndex iPoints = nif->getIndex( iData, "Points" ); + if ( iPoints.isValid() ) { + QList > strips; - if ( iPoints.isValid() ) { - QList > strips; + for ( int r = 0; r < nif->rowCount( iPoints ); r++ ) + strips.append( nif->getArray( iPoints.child( r, 0 ) ) ); - for ( int r = 0; r < nif->rowCount( iPoints ); r++ ) - strips.append( nif->getArray( iPoints.child( r, 0 ) ) ); + tris = triangulate( strips ); + } else { + tris = nif->getArray( iData, "Triangles" ); + } + + // write the triangles + + foreach( Triangle t, tris ) + { + obj << "f"; + + for ( int p = 0; p < 3; p++ ) { + obj << " " << ofs[0] + t[p]; + + if ( norms.count() ) + if ( texco.count() ) + obj << "/" << ofs[1] + t[p] << "/" << ofs[2] + t[p]; + else + obj << "//" << ofs[2] + t[p]; + + + else if ( texco.count() ) + obj << "/" << ofs[1] + t[p]; + } - tris = triangulate( strips ); + obj << "\r\n"; + } + + ofs[0] += verts.count(); + ofs[1] += texco.count(); + ofs[2] += norms.count(); } else { - tris = nif->getArray( iData, "Triangles" ); - } + auto iVertData = nif->getIndex( iData, "Vertex Data" ); + if ( !iVertData.isValid() ) + return; - // write the triangles + QVector verts; + QVector coords; + QVector norms; - foreach ( Triangle t, tris ) { - obj << "f"; + auto numVerts = nif->get( iData, "Num Vertices" ); + for ( int i = 0; i < numVerts; i++ ) { + auto idx = nif->index( i, 0, iVertData ); - for ( int p = 0; p < 3; p++ ) { - obj << " " << ofs[0] + t[p]; + verts += nif->get( idx, "Vertex" ); + coords += nif->get( idx, "UV" ); + norms += nif->get( idx, "Normal" ); + } - if ( norms.count() ) - if ( texco.count() ) - obj << "/" << ofs[1] + t[p] << "/" << ofs[2] + t[p]; - else - obj << "//" << ofs[2] + t[p]; + for ( Vector3 & v : verts ) { + v = t * v; + obj << "v " << qSetRealNumberPrecision( 17 ) << v[0] << " " << v[1] << " " << v[2] << "\r\n"; + } + for ( Vector2 & c : coords ) { + obj << "vt " << c[0] << " " << 1.0 - c[1] << "\r\n"; + } - else if ( texco.count() ) - obj << "/" << ofs[1] + t[p]; + for ( Vector3 & n : norms ) { + n = t.rotation * n; + obj << "vn " << n[0] << " " << n[1] << " " << n[2] << "\r\n"; } - obj << "\r\n"; - } + auto tris = nif->getArray( iData, "Triangles" ); + + for ( const Triangle & t : tris ) { + obj << "f"; + + for ( int p = 0; p < 3; p++ ) { + obj << " " << ofs[0] + t[p]; + + if ( norms.count() ) + if ( coords.count() ) + obj << "/" << ofs[1] + t[p] << "/" << ofs[2] + t[p]; + else + obj << "//" << ofs[2] + t[p]; - ofs[0] += verts.count(); - ofs[1] += texco.count(); - ofs[2] += norms.count(); + + else if ( coords.count() ) + obj << "/" << ofs[1] + t[p]; + } + + obj << "\r\n"; + } + + ofs[0] += verts.count(); + ofs[1] += coords.count(); + ofs[2] += norms.count(); + } + } static void writeShape( const NifModel * nif, const QModelIndex & iShape, QTextStream & obj, QTextStream & mtl, int ofs[], Transform t ) @@ -173,16 +235,9 @@ static void writeShape( const NifModel * nif, const QModelIndex & iShape, QTextS "obj format does not support skinning. This mesh will be " "exported statically in its bind pose, without skin weights." ) ); - } else if ( nif->isNiBlock( iProp, "BSShaderNoLightingProperty" ) - || nif->isNiBlock( iProp, "SkyShaderProperty" ) - || nif->isNiBlock( iProp, "TileShaderProperty" ) - ) - { + } else if ( nif->isNiBlock( iProp, { "BSShaderNoLightingProperty", "SkyShaderProperty", "TileShaderProperty" } ) ) { map_Kd = TexCache::find( nif->get( iProp, "File Name" ), nif->getFolder() ); - } else if ( nif->isNiBlock( iProp, "BSShaderPPLightingProperty" ) - || nif->isNiBlock( iProp, "Lighting30ShaderProperty" ) - ) - { + } else if ( nif->isNiBlock( iProp, { "BSShaderPPLightingProperty", "Lighting30ShaderProperty" } ) ) { QModelIndex iArray = nif->getIndex( nif->getBlock( nif->getLink( iProp, "Texture Set" ) ), "Textures" ); map_Kd = TexCache::find( nif->get( iArray.child( 0, 0 ) ), nif->getFolder() ); } @@ -212,7 +267,10 @@ static void writeShape( const NifModel * nif, const QModelIndex & iShape, QTextS obj << "\r\n# " << name << "\r\n\r\ng " << name << "\r\n" << "usemtl " << matn << "\r\n\r\n"; - writeData( nif, nif->getBlock( nif->getLink( iShape, "Data" ) ), obj, ofs, t ); + if ( nif->getUserVersion2() < 130 ) + writeData( nif, nif->getBlock( nif->getLink( iShape, "Data" ) ), obj, ofs, t ); + else + writeData( nif, iShape, obj, ofs, t ); } static void writeParent( const NifModel * nif, const QModelIndex & iNode, QTextStream & obj, QTextStream & mtl, int ofs[], Transform t ) @@ -227,7 +285,9 @@ static void writeParent( const NifModel * nif, const QModelIndex & iNode, QTextS if ( nif->inherits( iChild, "NiNode" ) ) writeParent( nif, iChild, obj, mtl, ofs, t ); - else if ( nif->isNiBlock( iChild, "NiTriShape" ) || nif->isNiBlock( iChild, "NiTriStrips" ) ) + else if ( nif->isNiBlock( iChild, { "NiTriShape", "NiTriStrips" } ) ) + writeShape( nif, iChild, obj, mtl, ofs, t * Transform( nif, iChild ) ); + else if ( nif->isNiBlock( iChild, { "BSTriShape", "BSSubIndexTriShape", "BSMeshLODTriShape" } ) ) writeShape( nif, iChild, obj, mtl, ofs, t * Transform( nif, iChild ) ); else if ( nif->inherits( iChild, "NiCollisionObject" ) ) { QModelIndex iBody = nif->getBlock( nif->getLink( iChild, "Body" ) ); @@ -298,8 +358,8 @@ static void writeParent( const NifModel * nif, const QModelIndex & iNode, QTextS void exportObj( const NifModel * nif, const QModelIndex & index ) { - objCulling = Options::get()->exportCullEnabled(); - objCullRegExp = Options::get()->cullExpression(); + //objCulling = Options::get()->exportCullEnabled(); + //objCullRegExp = Options::get()->cullExpression(); //--Determine how the file will export, and be sure the user wants to continue--// QList roots; @@ -345,14 +405,14 @@ void exportObj( const NifModel * nif, const QModelIndex & index ) QFile fobj( fname + ".obj" ); if ( !fobj.open( QIODevice::WriteOnly ) ) { - qWarning() << "could not open " << fobj.fileName() << " for write access"; + qCCritical( nsIo ) << tr( "Failed to write %1" ).arg( fobj.fileName() ); return; } QFile fmtl( fname + ".mtl" ); if ( !fmtl.open( QIODevice::WriteOnly ) ) { - qWarning() << "could not open " << fmtl.fileName() << " for write access"; + qCCritical( nsIo ) << tr( "Failed to write %1" ).arg( fmtl.fileName() ); return; } @@ -377,7 +437,7 @@ void exportObj( const NifModel * nif, const QModelIndex & index ) if ( nif->inherits( iBlock, "NiNode" ) ) writeParent( nif, iBlock, sobj, smtl, ofs, Transform() ); - else if ( nif->isNiBlock( iBlock, "NiTriShape" ) || nif->isNiBlock( iBlock, "NiTriStrips" ) ) + else if ( nif->isNiBlock( iBlock, { "NiTriShape", "NiTriStrips" } ) ) writeShape( nif, iBlock, sobj, smtl, ofs, Transform() ); } @@ -425,7 +485,7 @@ static void readMtlLib( const QString & fname, QMap & omat QFile file( fname ); if ( !file.open( QIODevice::ReadOnly ) ) { - qWarning() << "failed to open" << fname; + qCCritical( nsIo ) << tr( "Failed to read %1" ).arg( fname ); return; } @@ -571,7 +631,7 @@ void importObj( NifModel * nif, const QModelIndex & index ) QFile fobj( fname ); if ( !fobj.open( QIODevice::ReadOnly ) ) { - qWarning() << tr( "could not open " ) << fobj.fileName() << tr( " for read access" ); + qCCritical( nsIo ) << tr( "Failed to read %1" ).arg( fobj.fileName() ); return; } @@ -615,7 +675,7 @@ void importObj( NifModel * nif, const QModelIndex & index ) onorms.append( Vector3( t.value( 1 ).toDouble(), t.value( 2 ).toDouble(), t.value( 3 ).toDouble() ) ); } else if ( t.value( 0 ) == "f" ) { if ( t.count() > 5 ) { - qWarning() << "please triangulate your mesh before import"; + qCCritical( nsNif ) << tr( "Please triangulate your mesh before import." ); return; } @@ -686,8 +746,10 @@ void importObj( NifModel * nif, const QModelIndex & index ) addLink( nif, iNode, "Children", nif->getBlockNumber( iShape ) ); } - if ( !omaterials.contains( it.key() ) ) - qWarning() << "material" << it.key() << "not found in mtllib"; + if ( !omaterials.contains( it.key() ) ) { + Message::append( tr( "Warnings were generated during OBJ import." ), + tr( "Material '%1' not found in mtllib." ).arg( it.key() ) ); + } ObjMaterial mtl = omaterials.value( it.key() ); diff --git a/src/kfmmodel.cpp b/src/kfmmodel.cpp index 9e8ed8518..691da978e 100644 --- a/src/kfmmodel.cpp +++ b/src/kfmmodel.cpp @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -33,6 +33,8 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "kfmmodel.h" +//! @file kfmmodel.cpp KfmModel + KfmModel::KfmModel( QObject * parent ) : BaseModel( parent ) { clear(); @@ -101,11 +103,10 @@ void KfmModel::clear() insertType( root, NifData( "Kfm", "Kfm" ) ); kfmroot = root->child( 0 ); version = 0x0200000b; + endResetModel(); if ( kfmroot ) set( kfmroot, "Header String", ";Gamebryo KFM File Version 2.0.0.0b" ); - - endResetModel(); } /* @@ -123,7 +124,7 @@ static QString parentPrefix( const QString & x ) return x; } -bool KfmModel::updateArrayItem( NifItem * array, bool fast ) +bool KfmModel::updateArrayItem( NifItem * array ) { if ( array->arr1().isEmpty() ) return false; @@ -131,7 +132,12 @@ bool KfmModel::updateArrayItem( NifItem * array, bool fast ) int d1 = getArraySize( array ); if ( d1 > 1024 * 1024 * 8 ) { - msg( Message() << "array" << array->name() << "much too large" ); + auto m = tr( "array %1 much too large. %2 bytes requested" ).arg( array->name() ).arg( d1 ); + if ( msgMode == UserMessage ) { + Message::append( nullptr, tr( "Could not update array item." ), m, QMessageBox::Critical ); + } else { + testMsg( m ); + } return false; } @@ -140,26 +146,22 @@ bool KfmModel::updateArrayItem( NifItem * array, bool fast ) if ( d1 > rows ) { NifData data( array->name(), array->type(), array->temp(), NifValue( NifValue::type( array->type() ) ), parentPrefix( array->arg() ), parentPrefix( array->arr2() ), QString(), QString(), 0, 0 ); - if ( !fast ) - beginInsertRows( createIndex( array->row(), 0, array ), rows, d1 - 1 ); + beginInsertRows( createIndex( array->row(), 0, array ), rows, d1 - 1 ); array->prepareInsert( d1 - rows ); for ( int c = rows; c < d1; c++ ) insertType( array, data ); - if ( !fast ) - endInsertRows(); + endInsertRows(); } if ( d1 < rows ) { - if ( !fast ) - beginRemoveRows( createIndex( array->row(), 0, array ), d1, rows - 1 ); + beginRemoveRows( createIndex( array->row(), 0, array ), d1, rows - 1 ); array->removeChildren( d1, rows - d1 ); - if ( !fast ) - endRemoveRows(); + endRemoveRows(); } return true; @@ -175,12 +177,12 @@ void KfmModel::insertType( NifItem * parent, const NifData & data, int at ) NifItem * array = insertBranch( parent, data, at ); if ( evalCondition( array ) ) - updateArrayItem( array, true ); + updateArrayItem( array ); return; } - NifBlock * compound = compounds.value( data.type() ); + NifBlockPtr compound = compounds.value( data.type() ); if ( compound ) { NifItem * branch = insertBranch( parent, data, at ); @@ -243,18 +245,17 @@ bool KfmModel::setItemValue( NifItem * item, const NifValue & val ) bool KfmModel::setHeaderString( const QString & s ) { - //msg( DbgMsg() << s << s.right( s.length() - 27 ) ); if ( s.startsWith( ";Gamebryo KFM File Version " ) ) { version = version2number( s.right( s.length() - 27 ) ); if ( isVersionSupported( version ) ) { return true; } else { - msg( Message() << tr( "version" ) << version2string( version ) << tr( "not supported yet" ) ); + Message::critical( nullptr, tr( "Version %1 is not supported." ).arg( version2string( version ) ) ); return false; } } - msg( Message() << tr( "this is not a KFM" ) ); + Message::critical( nullptr, tr( "Could not open %1 because it is not a supported type." ).arg( fileinfo.fileName() ) ); return false; } @@ -264,8 +265,10 @@ bool KfmModel::load( QIODevice & device ) NifIStream stream( this, &device ); - if ( !kfmroot || !load( kfmroot, stream, true ) ) { - msg( Message() << tr( "failed to load kfm file (%1)" ).arg( version ) ); + if ( !kfmroot || !load( kfmroot, stream ) ) { + Message::critical( nullptr, tr( "The file could not be read. See Details for more information." ), + tr( "failed to load kfm file (%1)" ).arg( version2string( version ) ) + ); return false; } @@ -282,14 +285,14 @@ bool KfmModel::save( QIODevice & device ) const NifOStream stream( this, &device ); if ( !kfmroot || save( kfmroot, stream ) ) { - msg( Message() << tr( "failed to write kfm file" ) ); + Message::critical( nullptr, tr( "Failed to write KFM file." ) ); return false; } return true; } -bool KfmModel::load( NifItem * parent, NifIStream & stream, bool fast ) +bool KfmModel::load( NifItem * parent, NifIStream & stream ) { if ( !parent ) return false; @@ -299,13 +302,13 @@ bool KfmModel::load( NifItem * parent, NifIStream & stream, bool fast ) if ( evalCondition( child ) ) { if ( !child->arr1().isEmpty() ) { - if ( !updateArrayItem( child, fast ) ) + if ( !updateArrayItem( child ) ) return false; - if ( !load( child, stream, fast ) ) + if ( !load( child, stream ) ) return false; } else if ( child->childCount() > 0 ) { - if ( !load( child, stream, fast ) ) + if ( !load( child, stream ) ) return false; } else { if ( !stream.read( child->value() ) ) @@ -327,8 +330,11 @@ bool KfmModel::save( NifItem * parent, NifOStream & stream ) const if ( evalCondition( child ) ) { if ( !child->arr1().isEmpty() || !child->arr2().isEmpty() || child->childCount() > 0 ) { - if ( !child->arr1().isEmpty() && child->childCount() != getArraySize( child ) ) - msg( Message() << child->name() << tr( "array size mismatch" ) ); + if ( !child->arr1().isEmpty() && child->childCount() != getArraySize( child ) ) { + Message::append( tr( "Warnings were generated while reading the blocks." ), + tr( "%1 array size mismatch" ).arg( child->name() ) + ); + } if ( !save( child, stream ) ) return false; diff --git a/src/kfmmodel.h b/src/kfmmodel.h index 89c78c632..9d2770aac 100644 --- a/src/kfmmodel.h +++ b/src/kfmmodel.h @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -39,6 +39,11 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +#include + +using NifBlockPtr = std::shared_ptr; + +//! @file kfmmodel.h KfmModel class KfmModel final : public BaseModel { @@ -78,15 +83,15 @@ class KfmModel final : public BaseModel QString getVersion() const override final { return version2string( version ); } quint32 getVersionNumber() const override final { return version; } - static QAbstractItemDelegate * createDelegate(); + static QAbstractItemDelegate * createDelegate( QObject * parent ); protected: void insertType( NifItem * parent, const NifData & data, int row = -1 ); NifItem * insertBranch( NifItem * parent, const NifData & data, int row = -1 ); - bool updateArrayItem( NifItem * array, bool fast ) override final; + bool updateArrayItem( NifItem * array ) override final; - bool load( NifItem * parent, NifIStream & stream, bool fast = true ); + bool load( NifItem * parent, NifIStream & stream ); bool save( NifItem * parent, NifOStream & stream ) const; bool setItemValue( NifItem * item, const NifValue & v ) override final; @@ -106,7 +111,7 @@ class KfmModel final : public BaseModel // XML structures static QList supportedVersions; - static QHash compounds; + static QHash compounds; static QString parseXmlDescription( const QString & filename ); diff --git a/src/kfmxml.cpp b/src/kfmxml.cpp index 53a29377c..3f7cad869 100644 --- a/src/kfmxml.cpp +++ b/src/kfmxml.cpp @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -30,6 +30,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ +#include "message.h" #include "kfmmodel.h" #include // QXmlDefaultHandler Inherited @@ -41,7 +42,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. QReadWriteLock KfmModel::XMLlock; QList KfmModel::supportedVersions; -QHash KfmModel::compounds; +QHash KfmModel::compounds; class KfmXmlHandler final : public QXmlDefaultHandler { @@ -59,7 +60,7 @@ class KfmXmlHandler final : public QXmlDefaultHandler QStringList elements; QString errorStr; - NifBlock * blk; + NifBlockPtr blk; int current() const { @@ -118,7 +119,7 @@ class KfmXmlHandler final : public QXmlDefaultHandler err( tr( "compound %1 is already registered as internal type" ).arg( list.value( "name" ) ) ); if ( !blk ) - blk = new NifBlock; + blk = NifBlockPtr( new NifBlock ); blk->id = list.value( "name" ); break; @@ -141,10 +142,12 @@ class KfmXmlHandler final : public QXmlDefaultHandler list.value( "arr2" ), list.value( "cond" ), KfmModel::version2number( list.value( "ver1" ) ), - KfmModel::version2number( list.value( "ver2" ) ), - ( list.value( "abstract" ) == "1" ) + KfmModel::version2number( list.value( "ver2" ) ) ); + if ( list.value( "abstract" ) == "1" ) + data.setAbstract( true ); + if ( data.name().isEmpty() || data.type().isEmpty() ) err( tr( "add needs at least name and type attributes" ) ); @@ -184,10 +187,9 @@ class KfmXmlHandler final : public QXmlDefaultHandler KfmModel::compounds.insert( blk->id, blk ); break; } - blk = 0; + blk = nullptr; } else { - delete blk; - blk = 0; + blk = nullptr; err( tr( "invalid %1 declaration: name is empty" ).arg( elements.value( x ) ) ); } } @@ -212,7 +214,7 @@ class KfmXmlHandler final : public QXmlDefaultHandler { // make a rough check of the maps for ( const QString& key : KfmModel::compounds.keys() ) { - NifBlock * c = KfmModel::compounds.value( key ); + NifBlockPtr c = KfmModel::compounds.value( key ); for ( const NifData& data : c->types ) { if ( !checkType( data ) ) err( tr( "compound type %1 referes to unknown type %2" ).arg( key, data.type() ) ); @@ -236,7 +238,7 @@ class KfmXmlHandler final : public QXmlDefaultHandler if ( errorStr.isEmpty() ) errorStr = tr( "Syntax error" ); - errorStr.prepend( tr( "XML parse error (line %1):
" ).arg( exception.lineNumber() ) ); + errorStr.prepend( tr( "%1 XML parse error (line %2): " ).arg( "KFM" ).arg( exception.lineNumber() ) ); return false; } }; @@ -260,7 +262,7 @@ bool KfmModel::loadXML() QString result = KfmModel::parseXmlDescription( fname ); if ( !result.isEmpty() ) { - QMessageBox::critical( 0, "NifSkope", result ); + Message::append( tr( "Error loading XML
You will need to reinstall the XML and restart the application." ), result, QMessageBox::Critical ); return false; } @@ -271,13 +273,16 @@ QString KfmModel::parseXmlDescription( const QString & filename ) { QWriteLocker lck( &XMLlock ); - qDeleteAll( compounds ); compounds.clear(); + compounds.clear(); supportedVersions.clear(); QFile f( filename ); + if ( !f.exists() ) + return tr( "kfm.xml could not be found. Please install it and restart the application." ); + if ( !f.open( QIODevice::ReadOnly | QIODevice::Text ) ) - return tr( "error: couldn't open xml description file: %1" ).arg( filename ); + return tr( "Couldn't open KFM XML description file: %1" ).arg( filename ); KfmXmlHandler handler; QXmlSimpleReader reader; @@ -287,7 +292,7 @@ QString KfmModel::parseXmlDescription( const QString & filename ) reader.parse( source ); if ( !handler.errorString().isEmpty() ) { - qDeleteAll( compounds ); compounds.clear(); + compounds.clear(); supportedVersions.clear(); } diff --git a/src/material.cpp b/src/material.cpp new file mode 100644 index 000000000..383254903 --- /dev/null +++ b/src/material.cpp @@ -0,0 +1,268 @@ +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2015, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +3. The name of the NIF File Format Library and Tools project may not be +used to endorse or promote products derived from this software +without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +***** END LICENCE BLOCK *****/ + +#include "material.h" + +#include +#include + +#include +#include +#include +#include +#include + +#define BGSM 0x4D534742 +#define BGEM 0x4D454742 + +Material::Material( QString name ) +{ + localPath = toLocalPath( name.replace( "\\", "/" ) ); + if ( localPath.startsWith( "data/", Qt::CaseInsensitive ) ) { + localPath.remove( 0, 5 ); + } + data = find( localPath ); + + fileExists = !data.isEmpty(); +} + +bool Material::readFile() +{ + if ( data.isEmpty() ) + return false; + + QBuffer f( &data ); + if ( f.open( QIODevice::ReadOnly ) ) { + in.setDevice( &f ); + in.setByteOrder( QDataStream::LittleEndian ); + in.setFloatingPointPrecision( QDataStream::SinglePrecision ); + + quint32 magic; + in >> magic; + + if ( magic != BGSM && magic != BGEM ) + return false; + + in >> version >> tileFlags; + + bTileU = (tileFlags & 0x2) != 0; + bTileV = (tileFlags & 0x1) != 0; + + in >> fUOffset >> fVOffset >> fUScale >> fVScale; + in >> fAlpha; + in >> bAlphaBlend >> iAlphaSrc >> iAlphaDst; + in >> iAlphaTestRef; + in >> bAlphaTest >> bZBufferWrite >> bZBufferTest; + in >> bScreenSpaceReflections >> bWetnessControl_ScreenSpaceReflections; + in >> bDecal >> bTwoSided >> bDecalNoFade >> bNonOccluder; + in >> bRefraction >> bRefractionFalloff >> fRefractionPower; + in >> bEnvironmentMapping >> fEnvironmentMappingMaskScale; + in >> bGrayscaleToPaletteColor; + + return in.status() == QDataStream::Ok; + } + + return false; +} + +QByteArray Material::find( QString path ) +{ + QSettings settings; + QStringList folders = settings.value( "Settings/Resources/Folders", QStringList() ).toStringList(); + + QString filename; + QDir dir; + for ( QString folder : folders ) { + dir.setPath( folder ); + + if ( dir.exists( path ) ) { + filename = QDir::fromNativeSeparators( dir.filePath( path ) ); + + QFile f( filename ); + if ( f.open( QIODevice::ReadOnly ) ) + return f.readAll(); + } + } + + for ( FSArchiveFile * archive : FSManager::archiveList() ) { + if ( archive ) { + filename = QDir::fromNativeSeparators( path.toLower() ); + if ( archive->hasFile( filename ) ) { + QByteArray outData; + archive->fileContents( filename, outData ); + + if ( !outData.isEmpty() ) { + return outData; + } + } + } + } + + return QByteArray(); +} + +QString Material::toLocalPath( QString path ) const +{ + QFileInfo finfo( path ); + + QString p = path; + if ( finfo.isAbsolute() ) { + int idx = path.indexOf( "materials", 0, Qt::CaseInsensitive ); + + p = path.right( path.length() - idx ); + } + + return p; +} + +bool Material::isValid() const +{ + return readable && !data.isEmpty(); +} + +QStringList Material::textures() const +{ + return textureList; +} + +QString Material::getPath() const +{ + return localPath; +} + + +ShaderMaterial::ShaderMaterial( QString name ) : Material( name ) +{ + if ( fileExists ) + readable = readFile(); +} + +bool ShaderMaterial::readFile() +{ + if ( data.isEmpty() || !Material::readFile() ) + return false; + + QBuffer f( &data ); + if ( f.open( QIODevice::ReadOnly ) ) { + in.setDevice( &f ); + in.setByteOrder( QDataStream::LittleEndian ); + in.setFloatingPointPrecision( QDataStream::SinglePrecision ); + + in.skipRawData( 63 ); + + for ( int i = 0; i < 9; i++ ) { + char * str; + in >> str; + textureList << QString( str ); + } + + in >> bEnableEditorAlphaRef >> bRimLighting; + in >> fRimPower >> fBacklightPower; + in >> bSubsurfaceLighting >> fSubsurfaceLightingRolloff; + in >> bSpecularEnabled; + in >> specR >> specG >> specB; + cSpecularColor.setRGB( specR, specG, specB ); + in >> fSpecularMult >> fSmoothness >> fFresnelPower; + in >> fWetnessControl_SpecScale >> fWetnessControl_SpecPowerScale >> fWetnessControl_SpecMinvar; + in >> fWetnessControl_EnvMapScale >> fWetnessControl_FresnelPower >> fWetnessControl_Metalness; + + char * rootMaterialStr; + in >> rootMaterialStr; + sRootMaterialPath = QString( rootMaterialStr ); + + in >> bAnisoLighting >> bEmitEnabled; + + if ( bEmitEnabled ) + in >> emitR >> emitG >> emitB; + cEmittanceColor.setRGB( emitR, emitG, emitB ); + + in >> fEmittanceMult >> bModelSpaceNormals; + in >> bExternalEmittance >> bBackLighting; + in >> bReceiveShadows >> bHideSecret >> bCastShadows; + in >> bDissolveFade >> bAssumeShadowmask >> bGlowmap; + in >> bEnvironmentMappingWindow >> bEnvironmentMappingEye; + in >> bHair >> hairR >> hairG >> hairB; + cHairTintColor.setRGB( hairR, hairG, hairB ); + + in >> bTree >> bFacegen >> bSkinTint >> bTessellate; + in >> fDisplacementTextureBias >> fDisplacementTextureScale; + in >> fTessellationPnScale >> fTessellationBaseFactor >> fTessellationFadeDistance; + in >> fGrayscaleToPaletteScale >> bSkewSpecularAlpha; + + return in.status() == QDataStream::Ok; + } + + return false; +} + +EffectMaterial::EffectMaterial( QString name ) : Material( name ) +{ + if ( fileExists ) + readable = readFile(); +} + +bool EffectMaterial::readFile() +{ + if ( data.isEmpty() || !Material::readFile() ) + return false; + + QBuffer f( &data ); + if ( f.open( QIODevice::ReadOnly ) ) { + in.setDevice( &f ); + in.setByteOrder( QDataStream::LittleEndian ); + in.setFloatingPointPrecision( QDataStream::SinglePrecision ); + + in.skipRawData( 63 ); + + for ( int i = 0; i < 5; i++ ) { + char * str; + in >> str; + textureList << QString( str ); + } + + in >> bBloodEnabled >> bEffectLightingEnabled; + in >> bFalloffEnabled >> bFalloffColorEnabled; + in >> bGrayscaleToPaletteAlpha >> bSoftEnabled; + in >> baseR >> baseG >> baseB; + + cBaseColor.setRGB( baseR, baseG, baseB ); + + in >> fBaseColorScale; + in >> fFalloffStartAngle >> fFalloffStopAngle; + in >> fFalloffStartOpacity >> fFalloffStopOpacity; + in >> fLightingInfluence >> iEnvmapMinLOD >> fSoftDepth; + + return in.status() == QDataStream::Ok; + } + + return false; +} diff --git a/src/material.h b/src/material.h new file mode 100644 index 000000000..55d0981a7 --- /dev/null +++ b/src/material.h @@ -0,0 +1,214 @@ +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2015, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +3. The name of the NIF File Format Library and Tools project may not be +used to endorse or promote products derived from this software +without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +***** END LICENCE BLOCK *****/ + +#ifndef MATERIAL_H +#define MATERIAL_H + +#include "niftypes.h" + +#include +#include +#include +#include + + +class Material : public QObject +{ + Q_OBJECT + + friend class Renderer; + friend class BSShape; + friend class BSShaderLightingProperty; + friend class BSLightingShaderProperty; + friend class BSEffectShaderProperty; + +public: + Material( QString name ); + + bool isValid() const; + QStringList textures() const; + QString getPath() const; + +protected: + virtual bool readFile(); + QByteArray find( QString path ); + QString toLocalPath( QString path ) const; + + + QStringList textureList; + + bool fileExists = false; + QString localPath; + //QString absolutePath; + + QDataStream in; + QByteArray data; + + // Is not JSON format or otherwise unreadable + bool readable; + + // BGSM & BGEM shared variables + + quint32 version = 0; + quint32 tileFlags = 0; + bool bTileU = false; + bool bTileV = false; + float fUOffset; + float fVOffset; + float fUScale; + float fVScale; + float fAlpha; + quint8 bAlphaBlend; + quint32 iAlphaSrc; + quint32 iAlphaDst; + quint8 iAlphaTestRef; + quint8 bAlphaTest; + quint8 bZBufferWrite; + quint8 bZBufferTest; + quint8 bScreenSpaceReflections; + quint8 bWetnessControl_ScreenSpaceReflections; + quint8 bDecal; + quint8 bTwoSided; + quint8 bDecalNoFade; + quint8 bNonOccluder; + quint8 bRefraction; + quint8 bRefractionFalloff; + float fRefractionPower; + quint8 bEnvironmentMapping; + float fEnvironmentMappingMaskScale; + quint8 bGrayscaleToPaletteColor; +}; + + +class ShaderMaterial : public Material +{ + Q_OBJECT + + friend class Renderer; + friend class BSShape; + friend class BSShaderLightingProperty; + friend class BSLightingShaderProperty; + +public: + ShaderMaterial( QString name ); + +protected: + bool readFile() override final; + + quint8 bEnableEditorAlphaRef; + quint8 bRimLighting; + float fRimPower; + float fBacklightPower; + quint8 bSubsurfaceLighting; + float fSubsurfaceLightingRolloff; + quint8 bSpecularEnabled; + float specR, specG, specB; + Color3 cSpecularColor; + float fSpecularMult; + float fSmoothness; + float fFresnelPower; + float fWetnessControl_SpecScale; + float fWetnessControl_SpecPowerScale; + float fWetnessControl_SpecMinvar; + float fWetnessControl_EnvMapScale; + float fWetnessControl_FresnelPower; + float fWetnessControl_Metalness; + QString sRootMaterialPath; + quint8 bAnisoLighting; + quint8 bEmitEnabled; + float emitR = 0, emitG = 0, emitB = 0; + Color3 cEmittanceColor; + float fEmittanceMult; + quint8 bModelSpaceNormals; + quint8 bExternalEmittance; + quint8 bBackLighting; + quint8 bReceiveShadows; + quint8 bHideSecret; + quint8 bCastShadows; + quint8 bDissolveFade; + quint8 bAssumeShadowmask; + quint8 bGlowmap; + quint8 bEnvironmentMappingWindow; + quint8 bEnvironmentMappingEye; + quint8 bHair; + float hairR, hairG, hairB; + Color3 cHairTintColor; + quint8 bTree; + quint8 bFacegen; + quint8 bSkinTint; + quint8 bTessellate; + float fDisplacementTextureBias; + float fDisplacementTextureScale; + float fTessellationPnScale; + float fTessellationBaseFactor; + float fTessellationFadeDistance; + float fGrayscaleToPaletteScale; + quint8 bSkewSpecularAlpha; + +}; + + +class EffectMaterial : public Material +{ + Q_OBJECT + + friend class Renderer; + friend class BSShape; + friend class BSShaderLightingProperty; + friend class BSEffectShaderProperty; + +public: + EffectMaterial( QString name ); + +protected: + bool readFile() override final; + + quint8 bBloodEnabled; + quint8 bEffectLightingEnabled; + quint8 bFalloffEnabled; + quint8 bFalloffColorEnabled; + quint8 bGrayscaleToPaletteAlpha; + quint8 bSoftEnabled; + float baseR = 0, baseG = 0, baseB = 0; + Color3 cBaseColor; + float fBaseColorScale; + float fFalloffStartAngle; + float fFalloffStopAngle; + float fFalloffStartOpacity; + float fFalloffStopOpacity; + float fLightingInfluence; + quint8 iEnvmapMinLOD; + float fSoftDepth; +}; + + +#endif // MATERIAL_H diff --git a/src/message.cpp b/src/message.cpp index a653a0a16..5d588dda1 100644 --- a/src/message.cpp +++ b/src/message.cpp @@ -1,5 +1,189 @@ #include "message.h" +#include +#include +#include + + +Q_LOGGING_CATEGORY( ns, "nifskope" ) +Q_LOGGING_CATEGORY( nsGl, "nifskope.gl" ) +Q_LOGGING_CATEGORY( nsIo, "nifskope.io" ) +Q_LOGGING_CATEGORY( nsNif, "nifskope.nif" ) +Q_LOGGING_CATEGORY( nsSpell, "nifskope.spell" ) + + +Message::Message() : QObject( nullptr ) +{ + +} + +Message::~Message() +{ + +} + +//! Static helper for message box without detail text +void Message::message( QWidget * parent, const QString & str, QMessageBox::Icon icon ) +{ + auto msgBox = new QMessageBox( parent ); + + // Keep message box on top if it does not have a parent + //if ( !parent ) + msgBox->setWindowFlags( msgBox->windowFlags() | Qt::WindowStaysOnTopHint | Qt::Tool ); + + msgBox->setText( str ); + msgBox->setIcon( icon ); + + msgBox->open(); + + //if ( !parent ) + msgBox->activateWindow(); +} + +//! Static helper for message box with detail text +void Message::message( QWidget * parent, const QString & str, const QString & err, QMessageBox::Icon icon ) +{ + if ( !parent ) + parent = qApp->activeWindow(); + + auto msgBox = new QMessageBox( parent ); + + // Keep message box on top if it does not have a parent + //if ( !parent ) + msgBox->setWindowFlags( msgBox->windowFlags() | Qt::WindowStaysOnTopHint | Qt::Tool ); + + msgBox->setText( str ); + msgBox->setIcon( icon ); + msgBox->setDetailedText( err ); + + msgBox->open(); + + //if ( !parent ) + msgBox->activateWindow(); +} + +//! Static helper for installed message handler +void Message::message( QWidget * parent, const QString & str, const QMessageLogContext * context, QMessageBox::Icon icon ) +{ + +#ifdef QT_NO_DEBUG + if ( !QString( context->category ).startsWith( "nifskope", Qt::CaseInsensitive ) ) { + + // TODO: Log these into a log table widget like Qt Creator's issues tab + + return; + } +#endif + + QString d; + d.append( QString( "%1: %2\n" ).arg( "File" ).arg( context->file ) ); + d.append( QString( "%1: %2\n" ).arg( "Function" ).arg( context->function ) ); + d.append( QString( "%1: %2\n" ).arg( "Line" ).arg( context->line ) ); + d.append( QString( "%1: %2\n" ).arg( "Category" ).arg( context->category ) ); + d.append( QString( "%1:\n\n%2" ).arg( "Message" ).arg( str ) ); + + message( parent, str, d, icon ); +} + +/* +* Critical message boxes +* +*/ + +void Message::critical( QWidget * parent, const QString & str ) +{ + message( parent, str, QMessageBox::Critical ); +} + +void Message::critical( QWidget * parent, const QString & str, const QString & err ) +{ + message( parent, str, err, QMessageBox::Critical ); +} + +/* +* Warning message boxes +* +*/ + +void Message::warning( QWidget * parent, const QString & str ) +{ + message( parent, str, QMessageBox::Warning ); +} + +void Message::warning( QWidget * parent, const QString & str, const QString & err ) +{ + message( parent, str, err, QMessageBox::Warning ); +} + +/* +* Info message boxes +* +*/ + +void Message::info( QWidget * parent, const QString & str ) +{ + message( parent, str, QMessageBox::Information ); +} + +void Message::info( QWidget * parent, const QString & str, const QString & err ) +{ + message( parent, str, err, QMessageBox::Information ); +} + +/* +* Accumulate messages in detailed text area +* +*/ + +static QMap messageBoxes; + +void Message::append( QWidget * parent, const QString & str, const QString & err, QMessageBox::Icon icon ) +{ + if ( !parent ) + parent = qApp->activeWindow(); + + // Create one box per error string, accumulate messages + auto box = messageBoxes[str]; + if ( box ) { + // Append strings to existing message box's Detailed Text + // Show box if it has been closed before + box->show(); + box->setDetailedText( box->detailedText().append( err + "\n" ) ); + } else { + // Create new message box + auto msgBox = new QMessageBox( parent ); + + // Keep message box on top if it does not have a parent + //if ( !parent ) + msgBox->setWindowFlags( msgBox->windowFlags() | Qt::WindowStaysOnTopHint | Qt::Tool ); + + msgBox->setText( str ); + msgBox->setIcon( icon ); + msgBox->setDetailedText( err + "\n" ); + msgBox->open(); + + //if ( !parent ) + msgBox->activateWindow(); + + messageBoxes[str] = msgBox; + + // Clear Detailed Text with each confirmation + connect( msgBox, &QMessageBox::buttonClicked, [msgBox]( QAbstractButton * button ) { + Q_UNUSED( button ); + msgBox->setDetailedText( "" ); + } ); + } +} +void Message::append( const QString & str, const QString & err, QMessageBox::Icon icon ) +{ + append( nullptr, str, err, icon ); +} + + + +/* + Old message class +*/ inline void space( QString & s ) { @@ -7,49 +191,49 @@ inline void space( QString & s ) s += " "; } -template <> Message & Message::operator<<( const char * x ) +template <> TestMessage & TestMessage::operator<<(const char * x) { space( s ); s += x; return *this; } -template <> Message & Message::operator<<( QString x ) +template <> TestMessage & TestMessage::operator<<(QString x) { space( s ); s += "\"" + x + "\""; return *this; } -template <> Message & Message::operator<<( QByteArray x ) +template <> TestMessage & TestMessage::operator<<(QByteArray x) { space( s ); s += "\"" + x + "\""; return *this; } -template <> Message & Message::operator<<( int x ) +template <> TestMessage & TestMessage::operator<<(int x) { space( s ); s += QString::number( x ); return *this; } -template <> Message & Message::operator<<( unsigned int x ) +template <> TestMessage & TestMessage::operator<<(unsigned int x) { space( s ); s += QString::number( x ); return *this; } -template <> Message & Message::operator<<( double x ) +template <> TestMessage & TestMessage::operator<<(double x) { space( s ); s += QString::number( x ); return *this; } -template <> Message & Message::operator<<( float x ) +template <> TestMessage & TestMessage::operator<<(float x) { space( s ); s += QString::number( x ); diff --git a/src/message.h b/src/message.h index fa03a960c..68b5a3d51 100644 --- a/src/message.h +++ b/src/message.h @@ -1,16 +1,50 @@ #ifndef MESSAGE_H #define MESSAGE_H +#include +#include #include #include +Q_DECLARE_LOGGING_CATEGORY( ns ) +Q_DECLARE_LOGGING_CATEGORY( nsGl ) +Q_DECLARE_LOGGING_CATEGORY( nsIo ) +Q_DECLARE_LOGGING_CATEGORY( nsNif ) +Q_DECLARE_LOGGING_CATEGORY( nsSpell ) -class Message + +class QMessageLogContext; + +class Message : QObject { + Q_OBJECT + Message(); + ~Message(); + public: - Message( QtMsgType t = QtWarningMsg ) : typ( t ) {} + static void message( QWidget *, const QString &, QMessageBox::Icon ); + static void message( QWidget *, const QString &, const QString &, QMessageBox::Icon ); + static void message( QWidget *, const QString &, const QMessageLogContext *, QMessageBox::Icon ); + + static void append( const QString &, const QString &, QMessageBox::Icon = QMessageBox::Warning ); + static void append( QWidget *, const QString &, const QString &, QMessageBox::Icon = QMessageBox::Warning ); - template Message & operator<<( T ); + static void critical( QWidget *, const QString & ); + static void critical( QWidget *, const QString &, const QString & ); + + static void warning( QWidget *, const QString & ); + static void warning( QWidget *, const QString &, const QString & ); + + static void info( QWidget *, const QString & ); + static void info( QWidget *, const QString &, const QString & ); +}; + +class TestMessage +{ +public: + TestMessage( QtMsgType t = QtWarningMsg ) : typ( t ) {} + + template TestMessage & operator<<(T); operator QString() const { return s; } @@ -19,8 +53,7 @@ class Message protected: QString s; QtMsgType typ; -}; -#define DbgMsg() Message( QtDebugMsg ) +}; #endif diff --git a/src/nifdelegate.cpp b/src/nifdelegate.cpp index baad771a0..c2e8842d2 100644 --- a/src/nifdelegate.cpp +++ b/src/nifdelegate.cpp @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -30,8 +30,9 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ -#include "options.h" +#include "settings.h" +#include "glview.h" #include "kfmmodel.h" #include "nifmodel.h" #include "nifproxy.h" @@ -47,6 +48,8 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include +//! @file nifdelegate.cpp NifDelegate + extern void qt_format_text( const QFont & font, const QRectF & _r, int tf, const QString & str, QRectF * brect, int tabstops, int * tabarray, int tabarraylen, @@ -54,10 +57,10 @@ extern void qt_format_text( const QFont & font, const QRectF & _r, class NifDelegate final : public QItemDelegate { - SpellBook * book; + SpellBookPtr book; public: - NifDelegate( SpellBook * sb = 0 ) : QItemDelegate(), book( sb ) + NifDelegate( QObject * p, SpellBookPtr sb = 0 ) : QItemDelegate( p ), book( sb ) { } @@ -66,6 +69,11 @@ class NifDelegate final : public QItemDelegate Q_ASSERT( event ); Q_ASSERT( model ); + // Ignore indices which are not editable + // since this QItemDelegate bypasses the flags. + if ( !(model->flags( index ) & Qt::ItemIsEditable) ) + return false; + switch ( event->type() ) { case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: @@ -73,9 +81,10 @@ class NifDelegate final : public QItemDelegate if ( static_cast(event)->button() == Qt::LeftButton && decoRect( option ).contains( static_cast(event)->pos() ) ) { - Spell * spell = SpellBook::lookup( model->data( index, Qt::UserRole ).toString() ); - + // Spell Icons in Value column + SpellPtr spell = SpellBook::lookup( model->data( index, Qt::UserRole ).toString() ); if ( spell && !spell->icon().isNull() ) { + // Spell Icon click if ( event->type() == QEvent::MouseButtonRelease ) { NifModel * nif = 0; QModelIndex buddy = index; @@ -88,6 +97,7 @@ class NifDelegate final : public QItemDelegate buddy = proxy->mapTo( index ); } + // Cast Spell for icon which was clicked if ( nif && spell->isApplicable( nif, buddy ) ) { if ( book ) book->cast( nif, buddy, spell ); @@ -109,6 +119,7 @@ class NifDelegate final : public QItemDelegate if ( v.canConvert() ) { NifValue nv = v.value(); + // Yes/No toggle for bool types if ( nv.type() == NifValue::tBool ) { nv.set( !nv.get() ); model->setData( index, nv.toVariant(), Qt::EditRole ); @@ -136,8 +147,8 @@ class NifDelegate final : public QItemDelegate QIcon icon; if ( !user.isEmpty() ) { - Spell * spell = SpellBook::lookup( user ); - + // Find the icon for this Spell if one exists + SpellPtr spell = SpellBook::lookup( user ); if ( spell ) icon = spell->icon(); } @@ -155,8 +166,9 @@ class NifDelegate final : public QItemDelegate opt.state |= QStyle::State_Active; QPalette::ColorGroup cg = ( opt.state & QStyle::State_Enabled ) ? QPalette::Normal : QPalette::Disabled; + // Color the field background if the value type is a color + // Otherwise normal behavior QVariant color = index.data( Qt::BackgroundColorRole ); - if ( color.canConvert() ) painter->fillRect( option.rect, color.value() ); else if ( option.state & QStyle::State_Selected ) @@ -189,7 +201,7 @@ class NifDelegate final : public QItemDelegate QWidget * createEditor( QWidget * parent, const QStyleOptionViewItem &, const QModelIndex & index ) const override final { if ( !index.isValid() ) - return 0; + return nullptr; QVariant v = index.data( Qt::EditRole ); QWidget * w = 0; @@ -278,6 +290,18 @@ class NifDelegate final : public QItemDelegate if ( vedit ) { v.setValue( vedit->getValue() ); + + // Value is unchanged, do not push to Undo Stack or call setData() + if ( v == index.data( Qt::EditRole ) ) + return; + + if ( model->inherits( "NifModel" ) ) { + auto valueType = model->sibling( index.row(), 0, index ).data().toString(); + + auto nif = static_cast(model); + nif->undoStack->push( new ChangeValueCommand( index, v, vedit->getValue().toString(), valueType, nif ) ); + } + model->setData( index, v, Qt::EditRole ); } else if ( cedit ) { QString t = index.sibling( index.row(), NifModel::TypeCol ).data( NifSkopeDisplayRole ).toString(); @@ -292,6 +316,18 @@ class NifDelegate final : public QItemDelegate NifValue nv = v.value(); nv.setCount( x ); v.setValue( nv ); + + // Value is unchanged, do not push to Undo Stack or call setData() + if ( v == index.data( Qt::EditRole ) ) + return; + + if ( model->inherits( "NifModel" ) ) { + auto valueType = model->sibling( index.row(), 0, index ).data().toString(); + + auto nif = static_cast(model); + nif->undoStack->push( new ToggleCheckBoxListCommand( index, v, valueType, nif ) ); + } + model->setData( index, v, Qt::EditRole ); } } else if ( ledit ) { @@ -314,12 +350,12 @@ class NifDelegate final : public QItemDelegate } }; -QAbstractItemDelegate * NifModel::createDelegate( SpellBook * book ) +QAbstractItemDelegate * NifModel::createDelegate( QObject * parent, SpellBookPtr book ) { - return new NifDelegate( book ); + return new NifDelegate( parent, book ); } -QAbstractItemDelegate * KfmModel::createDelegate() +QAbstractItemDelegate * KfmModel::createDelegate( QObject * p ) { - return new NifDelegate; + return new NifDelegate( p ); } diff --git a/src/nifexpr.cpp b/src/nifexpr.cpp index f5a6e26f1..e462a99ca 100644 --- a/src/nifexpr.cpp +++ b/src/nifexpr.cpp @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -37,25 +37,29 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. //#include "basemodel.h" -static bool matchGroup( const QString & cond, int offset, int & startpos, int & endpos ) +//! @file nifexpr.cpp Expression parsing for conditions defined in nif.xml. + +static bool matchGroup( const std::string & cond, int offset, int & startpos, int & endpos ) { int scandepth = 0; startpos = -1; endpos = -1; for ( int scanpos = offset, len = cond.length(); scanpos != len; ++scanpos ) { - QChar c = cond[scanpos]; - - if ( c == '(' ) { + switch ( cond[scanpos] ) + { + case '(': if ( startpos == -1 ) startpos = scanpos; ++scandepth; - } else if ( c == ')' ) { + break; + case ')': if ( --scandepth == 0 ) { endpos = scanpos; return true; } + break; } } @@ -136,6 +140,10 @@ Expression::Operator Expression::operatorFromString( const QString & str ) return Expression::e_add; else if ( str == "-" ) return Expression::e_sub; + else if ( str == "#DIV#" ) + return Expression::e_div; + else if ( str == "#MUL#" ) + return Expression::e_mul; else if ( str == "&&" ) return Expression::e_bool_and; else if ( str == "||" ) @@ -149,7 +157,7 @@ void Expression::partition( const QString & cond, int offset /*= 0*/ ) int pos; if ( cond.isEmpty() ) { - this->opcode = Expression::e_nop; + opcode = Expression::e_nop; return; } @@ -157,143 +165,67 @@ void Expression::partition( const QString & cond, int offset /*= 0*/ ) QRegularExpression reUnary( "^\\s*!(.*)" ); QRegularExpressionMatch reUnaryMatch = reUnary.match( cond, offset ); pos = reUnaryMatch.capturedStart(); - -#ifndef QT_NO_DEBUG - int oldPos; - QRegExp reUnaryOld( "^\\s*!(.*)" ); - oldPos = reUnaryOld.indexIn( cond, offset, QRegExp::CaretAtOffset ); - //qDebug() << reUnaryOld.capturedTexts(); - //qDebug() << reUnaryMatch.capturedTexts(); - Q_ASSERT( pos == oldPos ); - //Q_ASSERT( reUnaryOld.capturedTexts() == reUnaryMatch.capturedTexts() ); -#endif - if ( pos != -1 ) { Expression e( reUnaryMatch.captured( 1 ).trimmed() ); - this->opcode = Expression::e_not; - this->rhs = QVariant::fromValue( e ); -#ifndef QT_NO_DEBUG - Expression e2( reUnaryOld.cap( 1 ).trimmed() ); - Q_ASSERT( e.toString() == e2.toString() ); -#endif + opcode = Expression::e_not; + rhs = QVariant::fromValue( e ); return; } - // Check for left group - int lstartpos = -1, lendpos = -1, ostartpos = -1, oendpos = -1, rstartpos = -1, rendpos = -1; - //QRegExp tokens("\b(!=|==|>=|<=|>|<|\\&|\+|-|\\&\\&|\\|\\||\(|\)|[a-zA-Z0-9][a-zA-Z0-9_ \\?]*[a-zA-Z0-9_\\?]?)\b"); + int lstartpos = -1, lendpos = -1, // Left Start/End + ostartpos = -1, oendpos = -1, // Operator Start/End + rstartpos = -1, rendpos = -1; // Right Start/End - // TODO: Do we want single & and single | in here? Staring with - // Qt5 in QRegularExpression, I had to put the && and || before - // the single ones in order to get the correct match - QRegularExpression reOps( "(!=|==|>=|<=|>|<|\\+|-|\\&\\&|\\|\\||\\&|\\|)" ); + QRegularExpression reOps( "(!=|==|>=|<=|>|<|\\+|-|#DIV#|#MUL#|\\&\\&|\\|\\||\\&|\\|)" ); QRegularExpression reLParen( "^\\s*\\(.*" ); QRegularExpressionMatch reLParenMatch = reLParen.match( cond, offset ); - pos = reLParenMatch.capturedStart(); - -#ifndef QT_NO_DEBUG - QRegExp reOpsOld( "(!=|==|>=|<=|>|<|\\&|\\||\\+|-|\\&\\&|\\|\\|)" ); - QRegExp reLParenOld( "^\\s*\\(.*" ); - oldPos = reLParenOld.indexIn( cond, offset, QRegExp::CaretAtOffset ); - //QRegularExpression testOps( R"rx((!=|==|>=|<=|>|<|\+|-|\&\&|\|\||\&|\|))rx" ); - //auto testStr = "what what && what what"; - //auto testMatch = testOps.match( testStr, 0 ); - //Q_ASSERT( testMatch.hasMatch() ); - //Q_ASSERT( testMatch.captured( 0 ) == "&&" ); - //qDebug() << "reLParenMatch captured: " << reLParenMatch.captured(); - //qDebug() << reLParenMatch.hasMatch(); - Q_ASSERT( pos == oldPos ); -#endif + // Check for left group + pos = reLParenMatch.capturedStart(); if ( pos != -1 ) { - matchGroup( cond, pos, lstartpos, lendpos ); + // Get start/end pos for lparen + matchGroup( cond.toStdString(), pos, lstartpos, lendpos ); + // Find operator in group QRegularExpressionMatch reOpsMatch = reOps.match( cond, lendpos + 1 ); pos = reOpsMatch.capturedStart(); - -#ifndef QT_NO_DEBUG - //qDebug() << "Condition: " << cond; - //qDebug() << "Start Pos: " << lstartpos; - //qDebug() << "End Pos: " << lendpos; - oldPos = reOpsOld.indexIn( cond, lendpos + 1, QRegExp::CaretAtOffset ); - //qDebug() << "reOpsOld captured: " << reOpsOld.capturedTexts(); - //qDebug() << "cond length: " << cond.length(); - //qDebug() << "reOpsMatch captured: " << reOpsMatch.captured(); - //qDebug() << "reOpsMatch match: " << reOpsMatch.hasMatch(); - Q_ASSERT( pos == oldPos ); -#endif - + // Move positions inward ++lstartpos, --lendpos; if ( pos != -1 ) { ostartpos = pos; oendpos = ostartpos + reOpsMatch.captured( 0 ).length(); -#ifndef QT_NO_DEBUG - int oendpos2 = ostartpos + reOpsOld.cap( 0 ).length(); - //qDebug() << reOpsOld.capturedTexts(); - //qDebug() << reOpsOld.cap( 0 ); - //qDebug() << reOpsOld.cap( 0 ).length(); - //qDebug() << reOpsMatch.capturedTexts(); - //qDebug() << reOpsMatch.captured( 0 ); - //qDebug() << reOpsMatch.captured( 0 ).length(); - Q_ASSERT( oendpos == oendpos2 ); -#endif } else { partition( cond.mid( lstartpos, lendpos - lstartpos + 1 ) ); return; } } else { + // Check for expression without parens QRegularExpressionMatch reOpsMatch = reOps.match( cond, offset ); pos = reOpsMatch.capturedStart(); -#ifndef QT_NO_DEBUG - oldPos = reOpsOld.indexIn( cond, offset, QRegExp::CaretAtOffset ); - Q_ASSERT( pos == oldPos ); -#endif if ( pos != -1 ) { lstartpos = offset; lendpos = pos - 1; ostartpos = pos; oendpos = ostartpos + reOpsMatch.captured( 0 ).length(); -#ifndef QT_NO_DEBUG - int oendpos2 = ostartpos + reOpsOld.cap( 0 ).length(); - Q_ASSERT( oendpos == oendpos2 ); -#endif } else { -#ifndef QT_NO_DEBUG - static QRegExp reIntOld( "[-+]?[0-9]+" ); - static QRegExp reUIntOld( "0[xX][0-9]+" ); - static QRegExp reFloatOld( "^[-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?$" ); - static QRegExp reVersionOld( "[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+" ); -#endif static QRegularExpression reInt( "\\A(?:[-+]?[0-9]+)\\z" ); - static QRegularExpression reUInt( "\\A(?:0[xX][0-9]+)\\z" ); + static QRegularExpression reUInt( "\\A(?:0[xX][0-9a-fA-F]+)\\z" ); static QRegularExpression reFloat( "^[-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?$" ); static QRegularExpression reVersion( "\\A(?:[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+)\\z" ); // termination - this->lhs.setValue( cond ); + lhs.setValue( cond ); if ( reUInt.match( cond ).hasMatch() ) { -#ifndef QT_NO_DEBUG - //qDebug() << "reUInt exact match"; - Q_ASSERT( reUIntOld.exactMatch( cond ) ); -#endif - this->lhs.convert( QVariant::UInt ); + lhs.convert( QVariant::UInt ); } else if ( reInt.match( cond ).hasMatch() ) { -#ifndef QT_NO_DEBUG - //qDebug() << "reInt exact match"; - Q_ASSERT( reIntOld.exactMatch( cond ) ); -#endif - this->lhs.convert( QVariant::Int ); + lhs.convert( QVariant::Int ); } else if ( reVersion.match( cond ).hasMatch() ) { -#ifndef QT_NO_DEBUG - //qDebug() << "reVersion exact match"; - Q_ASSERT( reVersionOld.exactMatch( cond ) ); -#endif - this->lhs.setValue( version2number( cond ) ); + lhs.setValue( version2number( cond ) ); } - this->opcode = Expression::e_nop; + opcode = Expression::e_nop; return; } } @@ -305,17 +237,17 @@ void Expression::partition( const QString & cond, int offset /*= 0*/ ) Expression rhsexp( cond.mid( rstartpos, rendpos - rstartpos + 1 ).trimmed() ); if ( lhsexp.opcode == Expression::e_nop ) { - this->lhs = lhsexp.lhs; + lhs = lhsexp.lhs; } else { - this->lhs = QVariant::fromValue( lhsexp ); + lhs = QVariant::fromValue( lhsexp ); } - this->opcode = operatorFromString( cond.mid( ostartpos, oendpos - ostartpos ) ); + opcode = operatorFromString( cond.mid( ostartpos, oendpos - ostartpos ) ); if ( rhsexp.opcode == Expression::e_nop ) { - this->rhs = rhsexp.lhs; + rhs = rhsexp.lhs; } else { - this->rhs = QVariant::fromValue( rhsexp ); + rhs = QVariant::fromValue( rhsexp ); } } @@ -353,6 +285,10 @@ QString Expression::toString() const return QString( "(%1 + %2)" ).arg( l, r ); case Expression::e_sub: return QString( "(%1 - %2)" ).arg( l, r ); + case Expression::e_div: + return QString( "(%1 / %2)" ).arg( l, r ); + case Expression::e_mul: + return QString( "(%1 * %2)" ).arg( l, r ); case Expression::e_bool_and: return QString( "(%1 && %2)" ).arg( l, r ); case Expression::e_bool_or: diff --git a/src/nifexpr.h b/src/nifexpr.h index 182423478..2870df957 100644 --- a/src/nifexpr.h +++ b/src/nifexpr.h @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -39,12 +39,14 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include +//! @file nifexpr.h Expression + class Expression final { enum Operator { e_nop, e_not_eq, e_eq, e_gte, e_lte, e_gt, e_lt, e_bit_and, e_bit_or, - e_add, e_sub, e_bool_and, e_bool_or, e_not, + e_add, e_sub, e_div, e_mul, e_bool_and, e_bool_or, e_not, }; QVariant lhs; QVariant rhs; @@ -101,6 +103,10 @@ class Expression final return QVariant::fromValue( l.toUInt() + r.toUInt() ); case Expression::e_sub: return QVariant::fromValue( l.toUInt() - r.toUInt() ); + case Expression::e_div: + return QVariant::fromValue( l.toUInt() / r.toUInt() ); + case Expression::e_mul: + return QVariant::fromValue( l.toUInt() * r.toUInt() ); case Expression::e_bool_and: return QVariant::fromValue( l.toBool() && r.toBool() ); case Expression::e_bool_or: diff --git a/src/nifitem.h b/src/nifitem.h index b2c310a25..1c64ba151 100644 --- a/src/nifitem.h +++ b/src/nifitem.h @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -42,33 +42,48 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include -//! \file nifitem.h NifItem, NifBlock, NifData, NifSharedData +//! @file nifitem.h NifItem, NifBlock, NifData, NifSharedData -//! Shared data for NifData. -/** - * See QSharedDataPointer for details on data sharing in Qt; - * shared classes - * give pointer efficiency to classes. +/*! Shared data for NifData. + * + * @see QSharedDataPointer */ class NifSharedData final : public QSharedData { friend class NifData; - //! Constructor. - NifSharedData( const QString & n, const QString & t, const QString & tt, const QString & a, const QString & a1, const QString & a2, const QString & c, quint32 v1, quint32 v2, bool abs ) - : QSharedData(), name( n ), type( t ), temp( tt ), arg( a ), arr1( a1 ), arr2( a2 ), cond( c ), ver1( v1 ), ver2( v2 ), condexpr( c ), arr1expr( a1 ), isAbstract( abs ) {} +public: + enum DataFlag + { + None = 0x0, + Abstract = 0x1, + Binary = 0x2, + Templated = 0x4, + Compound = 0x8, + Array = 0x10, + MultiArray = 0x20, + Conditionless = 0x40 + }; + + typedef QFlags DataFlags; + +private: + + NifSharedData( const QString & n, const QString & t, const QString & tt, const QString & a, const QString & a1, + const QString & a2, const QString & c, quint32 v1, quint32 v2, NifSharedData::DataFlags f ) + : QSharedData(), name( n ), type( t ), temp( tt ), arg( a ), arr1( a1 ), arr2( a2 ), + cond( c ), ver1( v1 ), ver2( v2 ), condexpr( c ), arr1expr( a1 ), flags( f ) + { + } - //! Constructor. NifSharedData( const QString & n, const QString & t ) - : QSharedData(), name( n ), type( t ), ver1( 0 ), ver2( 0 ), isAbstract( false ) {} + : QSharedData(), name( n ), type( t ) {} - //! Constructor. NifSharedData( const QString & n, const QString & t, const QString & txt ) - : QSharedData(), name( n ), type( t ), ver1( 0 ), ver2( 0 ), text( txt ), isAbstract( false ) {} + : QSharedData(), name( n ), type( t ), text( txt ) {} - //! Constructor. NifSharedData() - : QSharedData(), ver1( 0 ), ver2( 0 ), isAbstract( false ) {} + : QSharedData() {} //! Name. QString name; @@ -85,9 +100,9 @@ class NifSharedData final : public QSharedData //! Condition. QString cond; //! Earliest version. - quint32 ver1; + quint32 ver1 = 0; //! Latest version. - quint32 ver2; + quint32 ver2 = 0; //! Description text. QString text; //! Condition as an expression. @@ -98,23 +113,25 @@ class NifSharedData final : public QSharedData QString vercond; //! Version condition as an expression. Expression verexpr; - //! Abstract flag. - bool isAbstract; + + DataFlags flags = None; }; +Q_DECLARE_OPERATORS_FOR_FLAGS( NifSharedData::DataFlags ); + //! The data and NifValue stored by a NifItem class NifData { public: - //! Constructor. - NifData( const QString & name, const QString & type, const QString & temp, const NifValue & val, const QString & arg, const QString & arr1, const QString & arr2, const QString & cond, quint32 ver1, quint32 ver2, bool isAbstract = false ) - : d( new NifSharedData( name, type, temp, arg, arr1, arr2, cond, ver1, ver2, isAbstract ) ), value( val ) {} + NifData( const QString & name, const QString & type, const QString & temp, const NifValue & val, const QString & arg, + const QString & arr1 = QString(), const QString & arr2 = QString(), const QString & cond = QString(), + quint32 ver1 = 0, quint32 ver2 = 0, NifSharedData::DataFlags flag = NifSharedData::None ) + : d( new NifSharedData( name, type, temp, arg, arr1, arr2, cond, ver1, ver2, flag ) ), value( val ) + {} - //! Constructor. NifData( const QString & name, const QString & type = QString(), const QString & text = QString() ) : d( new NifSharedData( name, type, text ) ) {} - //! Constructor. NifData() : d( new NifSharedData() ) {} @@ -147,7 +164,19 @@ class NifData //! Get the version condition attribute of the data, as an expression. inline const Expression & verexpr() const { return d->verexpr; } //! Get the abstract attribute of the data. - inline const bool & isAbstract() const { return d->isAbstract; } + inline bool isAbstract() const { return d->flags & NifSharedData::Abstract; } + //! Is the data binary. Binary means the data is being treated as one blob. + inline bool isBinary() const { return d->flags & NifSharedData::Binary; } + //! Is the data templated. Templated means the type is dynamic. + inline bool isTemplated() const { return d->flags & NifSharedData::Templated; } + //! Is the data a compound. Compound means the data type is a compound block. + inline bool isCompound() const { return d->flags & NifSharedData::Compound; } + //! Is the data an array. Array means the data on this row repeats. + inline bool isArray() const { return d->flags & NifSharedData::Array; } + //! Is the data a multi-array. Multi-array means the item's children are also arrays. + inline bool isMultiArray() const { return d->flags & NifSharedData::MultiArray; } + //! Is the data conditionless. Conditionless means no expression evaluation is necessary. + inline bool isConditionless() const { return d->flags & NifSharedData::Conditionless; } //! Sets the name of the data. void setName( const QString & name ) { d->name = name; } @@ -183,8 +212,25 @@ class NifData d->vercond = cond; d->verexpr = Expression( cond ); } + + inline void setFlag( NifSharedData::DataFlags flag, bool val ) + { + (val) ? d->flags |= flag : d->flags &= ~flag; + } //! Sets the abstract attribute of the data. - void setAbstract( bool & isAbstract ) { d->isAbstract = isAbstract; } + inline void setAbstract( bool flag ) { setFlag( NifSharedData::Abstract, flag ); } + //! Sets the binary data flag. Binary means the data is being treated as one blob. + inline void setBinary( bool flag ) { setFlag( NifSharedData::Binary, flag ); } + //! Sets the templated data flag. Templated means the type is dynamic. + inline void setTemplated( bool flag ) { setFlag( NifSharedData::Templated, flag ); } + //! Sets the compound data flag. Compound means the data type is a compound block. + inline void setIsCompound( bool flag ) { setFlag( NifSharedData::Compound, flag ); } + //! Sets the array data flag. Array means the data on this row repeats. + inline void setIsArray( bool flag ) { setFlag( NifSharedData::Array, flag ); } + //! Sets the multi-array data flag. Multi-array means the item's children are also arrays. + inline void setIsMultiArray( bool flag ) { setFlag( NifSharedData::MultiArray, flag ); } + //! Sets the conditionless data flag. Conditionless means no expression evaluation is necessary. + inline void setIsConditionless( bool flag ) { setFlag( NifSharedData::Conditionless, flag ); } protected: //! The internal shared data. @@ -205,7 +251,7 @@ struct NifBlock //! Description text. QString text; //! Abstract flag. - bool abstract; + bool abstract = false; //! Data present. QList types; }; @@ -214,15 +260,12 @@ struct NifBlock class NifItem { public: - //! Constructor. NifItem( NifItem * parent ) : parentItem( parent ) {} - //! Constructor. NifItem( const NifData & data, NifItem * parent ) : itemData( data ), parentItem( parent ) {} - //! Destructor. ~NifItem() { qDeleteAll( childItems ); @@ -237,66 +280,108 @@ class NifItem //! Return the row that this item is at. int row() const { - if ( parentItem ) - return parentItem->childItems.indexOf( const_cast(this) ); + if ( !parentItem ) + return 0; + + if ( rowIdx < 0 ) + rowIdx = parentItem->childItems.indexOf( const_cast(this) ); - return 0; + return rowIdx; } - //! Allocate memory to insert child items - /*! - * \param e The number of items to be inserted + /*! Allocate memory to insert child items + * + * @param e The number of items to be inserted */ void prepareInsert( int e ) { childItems.reserve( childItems.count() + e ); } - //! Insert child data item - /*! - * \param data The data to insert - * \param at The position to insert at; append if not specified - * \return An item containing the inserted data + //! Get child items + const QVector & children() + { + return childItems; + } + + /*! Insert child data item + * + * @param data The data to insert + * @param at The position to insert at; append if not specified + * @return An item containing the inserted data */ NifItem * insertChild( const NifData & data, int at = -1 ) { NifItem * item = new NifItem( data, this ); - if ( at < 0 || at > childItems.count() ) + if ( data.isConditionless() ) + item->setCondition( true ); + + if ( at < 0 || at > childItems.count() ) { childItems.append( item ); - else + } else { + invalidateRowCounts(); childItems.insert( at, item ); + } + + populateLinksUp( item ); return item; } - //! Insert child item - /*! - * \param child The data to insert - * \param at The position to insert at; append if not specified - * \return The row the child was inserted at + /*! Insert child item + * + * @param child The data to insert + * @param at The position to insert at; append if not specified + * @return The row the child was inserted at */ int insertChild( NifItem * child, int at = -1 ) { child->parentItem = this; - if ( at < 0 || at > childItems.count() ) + if ( at < 0 || at > childItems.count() ) { childItems.append( child ); - else + } else { + invalidateRowCounts(); childItems.insert( at, child ); + } + populateLinksUp( child ); + return child->row(); } - //! Take child item at row - /*! - * \param row The row to take the item from - * \return The child item that was removed + //! Inform the parent and its ancestors of any links + void populateLinksUp( NifItem * item ) + { + if ( item->value().type() == NifValue::tLink || item->value().type() == NifValue::tUpLink ) { + // Add this child's row to the item's link vector + linkRows << item->row(); + + // Inform the parent that this item's rows have links + auto p = parentItem; + auto c = this; + while ( p ) { + // Add this item's row to the parent item + if ( !p->linkAncestorRows.contains( c->row() ) ) + p->linkAncestorRows << c->row(); + + // Recurse up + c = p; + p = p->parentItem; + } + } + } + + /*! Take child item at row + * + * @param row The row to take the item from + * @return The child item that was removed */ NifItem * takeChild( int row ) { NifItem * item = child( row ); - + invalidateRowCounts(); if ( item ) { childItems.remove( row ); item->parentItem = 0; @@ -305,30 +390,30 @@ class NifItem return item; } - //! Remove child item at row - /*! - * \param row The row to remove the item from + /*! Remove child item at row + * + * @param row The row to remove the item from */ void removeChild( int row ) { NifItem * item = child( row ); - + invalidateRowCounts(); if ( item ) { childItems.remove( row ); delete item; } } - //! Remove several child items - /*! - * \param row The row to start from - * \param count The number of rows to delete + /*! Remove several child items + * + * @param row The row to start from + * @param count The number of rows to delete */ void removeChildren( int row, int count ) { + invalidateRowCounts(); for ( int c = row; c < row + count; c++ ) { NifItem * item = childItems.value( c ); - if ( item ) delete item; } @@ -355,7 +440,7 @@ class NifItem if ( child->name() == name ) return child; } - return 0; + return nullptr; } //! Return the child item with the specified name @@ -365,7 +450,7 @@ class NifItem if ( child->name() == name ) return child; } - return 0; + return nullptr; } //! Return a count of the number of child items @@ -381,6 +466,125 @@ class NifItem childItems.clear(); } + const QVector & getLinkAncestorRows() const + { + return linkAncestorRows; + } + + const QVector & getLinkRows() const + { + return linkRows; + } + + //! Conditions for each child in the array (if fixed) + const QVector & arrayConditions() + { + return arrConds; + } + + //! Reset array conditions based on size of children + void resetArrayConditions() + { + if ( childItems.isEmpty() ) + return; + + arrConds.clear(); + arrConds.resize( childItems.at( 0 )->childCount() ); + arrConds.fill( false ); + } + + //! Reset array conditions based on provided size + void resetArrayConditions( int size ) + { + arrConds.clear(); + arrConds.resize( size ); + arrConds.fill( false ); + } + + //! Update array condition at specified index + void updateArrayCondition( bool cond, int at ) + { + if ( arrConds.count() > at ) + arrConds[at] = cond; + } + + //! Cached result of cond expression + bool condition() + { + return conditionStatus == 1; + } + + //! Cached result of vercond expression + bool versionCondition() + { + return vercondStatus == 1; + } + + //! Has the condition been cached + bool isConditionValid() + { + return conditionStatus != -1; + } + + //! Has the version condition been cached + bool isVercondValid() + { + return vercondStatus != -1; + } + + //! Cache the cond expression + void setCondition( bool status ) + { + conditionStatus = status; + } + + //! Cache the vercond expression + void setVersionCondition( bool status ) + { + vercondStatus = status; + } + + //! Invalidate the cached cond expression + void invalidateCondition() + { + conditionStatus = -1; + } + + //! Invalidate the cached vercond expression + void invalidateVersionCondition() + { + vercondStatus = -1; + } + + //! Invalidate the cached row index + void invalidateRow() + { + rowIdx = -1; + } + + //! Invalidate the cached row index for this item and its children + void invalidateRowCounts() + { + invalidateRow(); + for ( NifItem * c : childItems ) { + c->invalidateRow(); + } + } + + //! Invalidate the cached row index for this item and its children starting at the given index + void invalidateRowCounts( int at ) + { + if ( at < childCount() ) { + invalidateRow(); + for ( int i = at; i < childCount(); i++ ) { + childItems.value( i )->invalidateRow(); + } + } else { + invalidateRowCounts(); + } + + } + //! Return the value of the item data (const version) inline const NifValue & value() const { return itemData.value; } //! Return the value of the item data @@ -416,7 +620,19 @@ class NifItem //! Return the version condition attribute of the data, as an expression inline const Expression & verexpr() const { return itemData.verexpr(); } //! Return the abstract attribute of the data - inline const bool & isAbstract() const { return itemData.isAbstract(); } + inline bool isAbstract() const { return itemData.isAbstract(); } + //! Is the item data binary. Binary means the data is being treated as one blob. + inline bool isBinary() const { return itemData.isBinary(); } + //! Is the item data templated. Templated means the type is dynamic. + inline bool isTemplated() const { return itemData.isTemplated(); } + //! Is the item data a compound. Compound means the data type is a compound block. + inline bool isCompound() const { return itemData.isCompound(); } + //! Is the item data an array. Array means the data on this row repeats. + inline bool isArray() const { return itemData.isArray(); } + //! Is the item data a multi-array. Multi-array means the item's children are also arrays. + inline bool isMultiArray() const { return itemData.isMultiArray(); } + //! Is the item data conditionless. Conditionless means no expression evaluation is necessary. + inline bool isConditionless() const { return itemData.isConditionless(); } //! Set the name inline void setName( const QString & name ) { itemData.setName( name ); } @@ -468,13 +684,35 @@ class NifItem } } + //! Set the child items from a single value + template void setArray( const T & val ) + { + for ( NifItem * child : childItems ) { + child->itemData.value.set( val ); + } + } + private: //! The data held by the item NifData itemData; //! The parent of this item - NifItem * parentItem; + NifItem * parentItem = nullptr; //! The child items QVector childItems; + + //! Rows which have links under them at any level + QVector linkAncestorRows; + //! Rows which are links + QVector linkRows; + + //! Item's condition status, -1 is invalid, otherwise 0/1 + int conditionStatus = -1; + //! Item's vercond status, -1 is invalid, otherwise 0/1 + int vercondStatus = -1; + //! Item's row index, -1 is invalid, otherwise 0+ + mutable int rowIdx = -1; + //! If item is array with fixed compounds, the conditions are stored here for reuse + QVector arrConds; }; #endif diff --git a/src/nifmodel.cpp b/src/nifmodel.cpp index ff394143e..0b8defc66 100644 --- a/src/nifmodel.cpp +++ b/src/nifmodel.cpp @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -32,7 +32,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "nifmodel.h" #include "config.h" -#include "options.h" +#include "settings.h" #include "niftypes.h" #include "spellbook.h" @@ -46,13 +46,28 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include -//! \file nifmodel.cpp NifModel implementation, NifModelEval +//! @file nifmodel.cpp The NIF data model. NifModel::NifModel( QObject * parent ) : BaseModel( parent ) { + updateSettings(); + clear(); } +void NifModel::updateSettings() +{ + QSettings settings; + + settings.beginGroup( "Settings/NIF/Startup Defaults" ); + + cfg.startupVersion = settings.value( "Version", "20.0.0.5" ).toString(); + cfg.userVersion = settings.value( "User Version", "11" ).toInt(); + cfg.userVersion2 = settings.value( "User Version 2", "11" ).toInt(); + + settings.endGroup(); +} + QString NifModel::version2string( quint32 v ) { if ( v == 0 ) @@ -132,41 +147,11 @@ quint32 NifModel::version2number( const QString & s ) return ( i == 0xffffffff ? 0 : i ); } -// Helper class for evaluating condition expressions -class NifModelEval -{ -public: - const NifModel * model; - const NifItem * item; - NifModelEval( const NifModel * model, const NifItem * item ) - { - this->model = model; - this->item = item; - } - - QVariant operator()( const QVariant & v ) const - { - if ( v.type() == QVariant::String ) { - QString left = v.toString(); - NifItem * i = const_cast(item); - i = model->getItem( i, left ); - - if ( i ) { - if ( i->value().isCount() ) - return QVariant( i->value().toCount() ); - else if ( i->value().isFileVersion() ) - return QVariant( i->value().toFileVersion() ); - } - - return QVariant( 0 ); - } - - return v; - } -}; - bool NifModel::evalVersion( NifItem * item, bool chkParents ) const { + if ( item->isVercondValid() ) + return item->versionCondition(); + if ( item == root ) return true; @@ -185,7 +170,11 @@ bool NifModel::evalVersion( NifItem * item, bool chkParents ) const return true; NifModelEval functor( this, getHeaderItem() ); - return item->verexpr().evaluateBool( functor ); + bool expr = item->verexpr().evaluateBool( functor ); + + item->setVersionCondition( expr ); + + return expr; } void NifModel::clear() @@ -195,14 +184,23 @@ void NifModel::clear() filename = QString(); folder = QString(); root->killChildren(); - insertType( root, NifData( "NiHeader", "Header" ) ); - insertType( root, NifData( "NiFooter", "Footer" ) ); - version = version2number( Options::startupVersion() ); - if ( !isVersionSupported( version ) ) { - msg( Message() << tr( "Unsupported 'Startup Version' %1 specified, reverting to 20.0.0.5" ).arg( Options::startupVersion() ).toLatin1() ); + NifData headerData = NifData( "NiHeader", "Header" ); + NifData footerData = NifData( "NiFooter", "Footer" ); + headerData.setIsCompound( true ); + headerData.setIsConditionless( true ); + footerData.setIsCompound( true ); + footerData.setIsConditionless( true ); + + insertType( root, headerData ); + insertType( root, footerData ); + version = version2number( cfg.startupVersion ); + + if ( !supportedVersions.isEmpty() && !isVersionSupported( version ) ) { + Message::warning( nullptr, tr( "Unsupported 'Startup Version' %1 specified, reverting to 20.0.0.5" ).arg( cfg.startupVersion ) ); version = 0x14000005; } + endResetModel(); NifItem * item = getItem( getHeaderItem(), "Version" ); @@ -221,10 +219,9 @@ void NifModel::clear() set( getHeaderItem(), "Header String", header_string ); - if ( version == 0x14000005 ) { - //Just set this if version is 20.0.0.5 for now. Probably should be a separate option. - set( getHeaderItem(), "User Version", 11 ); - set( getHeaderItem(), "User Version 2", 11 ); + if ( version >= 0x14000005 ) { + set( getHeaderItem(), "User Version", cfg.userVersion ); + set( getHeaderItem(), "User Version 2", cfg.userVersion2 ); } //set( getHeaderItem(), "Unknown Int 3", 11 ); @@ -240,7 +237,6 @@ void NifModel::clear() lockUpdates = false; needUpdates = utNone; - endResetModel(); } /* @@ -280,7 +276,7 @@ void NifModel::updateFooter() return; set( footer, "Num Roots", rootLinks.count() ); - updateArrayItem( roots, false ); + updateArrayItem( roots ); for ( int r = 0; r < roots->childCount(); r++ ) roots->child( r )->value().setLink( rootLinks.value( r ) ); @@ -303,6 +299,8 @@ NifItem * NifModel::getHeaderItem() const void NifModel::updateHeader() { + emit beginUpdateHeader(); + if ( lockUpdates ) { needUpdates = UpdateType( needUpdates | utHeader ); return; @@ -315,65 +313,52 @@ void NifModel::updateHeader() NifItem * idxBlockTypeIndices = getItem( header, "Block Type Index" ); NifItem * idxBlockSize = getItem( header, "Block Size" ); + // Update Block Types, Block Type Index, and Block Size if ( idxBlockTypes && idxBlockTypeIndices ) { - QStringList blocktypes; - QList blocktypeindices; + QVector blocktypes; + QVector blocktypeindices; + QVector blocksizes; for ( int r = 1; r < root->childCount() - 1; r++ ) { NifItem * block = root->child( r ); - /* - if ( ! blocktypes.contains( block->name() ) ) - blocktypes.append( block->name() ); - blocktypeindices.append( blocktypes.indexOf( block->name() ) ); - */ - // NiMesh hack QString blockName = block->name(); -#ifndef QT_NO_DEBUG - qWarning() << "Updating header with " << blockName; -#endif - if ( blockName == "NiDataStream" ) { blockName = QString( "NiDataStream\x01%1\x01%2" ).arg( block->child( "Usage" )->value().get() ).arg( block->child( "Access" )->value().get() ); -#ifndef QT_NO_DEBUG - qWarning() << "Changing blockname to " << blockName; -#endif + qDebug() << "Changing blockname to " << blockName; } - if ( !blocktypes.contains( blockName ) ) + int bTypeIdx = blocktypes.indexOf( blockName ); + if ( bTypeIdx < 0 ) { blocktypes.append( blockName ); + bTypeIdx = blocktypes.count() - 1; + } + + blocktypeindices.append( bTypeIdx ); - blocktypeindices.append( blocktypes.indexOf( blockName ) ); + if ( version >= 0x14020000 && idxBlockSize ) { + updateArrays( block ); + blocksizes.append( blockSize( block ) ); + } - if ( version >= 0x14020000 && idxBlockSize ) - updateArrays( block, false ); } set( header, "Num Block Types", blocktypes.count() ); - // Setting fast update to true to workaround bug where deleting blocks - // causes a crash. This probably means anyone listening to updates - // for these two arrays will not work. - updateArrayItem( idxBlockTypes, false ); - updateArrayItem( idxBlockTypeIndices, true ); - - if ( version >= 0x14020000 && idxBlockSize ) { - updateArrayItem( idxBlockSize, false ); - } - - for ( int r = 0; r < idxBlockTypes->childCount(); r++ ) - set( idxBlockTypes->child( r ), blocktypes.value( r ) ); - - for ( int r = 0; r < idxBlockTypeIndices->childCount(); r++ ) - set( idxBlockTypeIndices->child( r ), blocktypeindices.value( r ) ); + updateArrayItem( idxBlockTypes ); + updateArrayItem( idxBlockTypeIndices ); - // for version 20.2.0.? and above the block size is stored in the header if ( version >= 0x14020000 && idxBlockSize ) { - for ( int r = 0; r < idxBlockSize->childCount(); r++ ) - set( idxBlockSize->child( r ), blockSize( getBlockItem( r ) ) ); + updateArrayItem( idxBlockSize ); } + setState( Processing ); + idxBlockTypes->setArray( blocktypes ); + idxBlockTypeIndices->setArray( blocktypeindices ); + if ( blocksizes.count() ) + idxBlockSize->setArray( blocksizes ); + restoreState(); // For 20.1 and above strings are saved in the header. Max String Length must be updated. if ( version >= 0x14010003 ) { @@ -401,32 +386,29 @@ void NifModel::updateHeader() NifItem * NifModel::getItem( NifItem * item, const QString & name ) const { - if ( name.startsWith( "HEADER/" ) ) - return getItem( getHeaderItem(), name.right( name.length() - 7 ) ); - if ( !item || item == root ) - return 0; + return nullptr; - int slash = name.indexOf( "/" ); + if ( item->isArray() || item->parent()->isArray() ) { + int slash = name.indexOf( "/" ); + if ( slash > 0 ) { + QString left = name.left( slash ); + QString right = name.right( name.length() - slash - 1 ); - if ( slash > 0 ) { - QString left = name.left( slash ); - QString right = name.right( name.length() - slash - 1 ); + // Resolve ../ for arr1, arr2, arg passing + if ( left == ".." ) + return getItem( item->parent(), right ); - if ( left == ".." ) - return getItem( item->parent(), right ); - - return getItem( getItem( item, left ), right ); + return getItem( getItem( item, left ), right ); + } } - for ( int c = 0; c < item->childCount(); c++ ) { - NifItem * child = item->child( c ); - + for ( auto child : item->children() ) { if ( child && child->name() == name && evalCondition( child ) ) return child; } - return 0; + return nullptr; } /* @@ -443,162 +425,136 @@ static QString parentPrefix( const QString & x ) return x; } -bool NifModel::updateByteArrayItem( NifItem * array, bool fast ) +bool NifModel::updateByteArrayItem( NifItem * array ) { - const int ArrayConvertSize = 0; // Currently disabled. Use nifskopetype="blob" instead - - int calcRows = getArraySize( array ); - int rows = array->childCount(); - - // exit early and let default handling delete all rows - if ( calcRows == 0 || !array->arr2().isEmpty() ) + // New row count + int rows = getArraySize( array ); + if ( rows == 0 ) return false; - // Transition from large array to smaller array which now requires real rows - if ( calcRows < ArrayConvertSize ) { - if ( rows == 1 ) { - if ( NifItem * child = array->child( 0 ) ) { - if ( child->value().type() == NifValue::tBlob ) { - QByteArray bm = get( child ); - - // Delete the blob row after grabbing the data - if ( !fast ) - beginRemoveRows( createIndex( array->row(), 0, array ), 0, rows - 1 ); - - array->removeChildren( 0, rows ); - - if ( !fast ) - endRemoveRows(); - - // Now insert approprate rows and replace data from byte array to preserve some of the data. - if ( calcRows > 0 ) { - NifData data( array->name(), array->type(), array->temp(), NifValue( NifValue::type( array->type() ) ), parentPrefix( array->arg() ), parentPrefix( array->arr2() ), QString(), QString(), 0, 0 ); - - if ( !fast ) - beginInsertRows( createIndex( array->row(), 0, array ), 0, calcRows - 1 ); - - array->prepareInsert( calcRows ); + // Create byte array for holding blob data + QByteArray bytes; + bytes.resize( rows ); - for ( int i = 0; i < calcRows; i++ ) { - insertType( array, data ); - - if ( NifItem * c = array->child( i ) ) - c->value().set( bm[i] ); - } - - if ( !fast ) - endInsertRows(); - } - } - } - } - - return false; - } - - - // Create dummy row for holding the blob data - QByteArray bytes; bytes.resize( calcRows ); + // Previous row count + int itemRows = array->childCount(); // Grab data from existing rows if appropriate and then purge - if ( rows > 1 ) { - for ( int i = 0; i < rows; i++ ) { + if ( itemRows > 1 ) { + for ( int i = 0; i < itemRows; i++ ) { if ( NifItem * child = array->child( 0 ) ) { bytes[i] = get( child ); } } - if ( !fast ) - beginRemoveRows( createIndex( array->row(), 0, array ), 0, rows - 1 ); - - array->removeChildren( 0, rows ); - - if ( !fast ) - endRemoveRows(); + beginRemoveRows( createIndex( array->row(), 0, array ), 0, itemRows - 1 ); + array->removeChildren( 0, itemRows ); + endRemoveRows(); - rows = 0; + itemRows = 0; } // Create the dummy row for holding the byte array - if ( rows == 0 ) { - NifData data( array->name(), array->type(), array->temp(), NifValue( NifValue::tBlob ), parentPrefix( array->arg() ), QString(), QString(), QString(), 0, 0 ); + if ( itemRows == 0 ) { + NifData data( array->name(), array->type(), array->temp(), NifValue( NifValue::tBlob ), parentPrefix( array->arg() ) ); + data.setBinary( true ); - if ( !fast ) - beginInsertRows( createIndex( array->row(), 0, array ), 0, 1 ); + beginInsertRows( createIndex( array->row(), 0, array ), 0, 1 ); array->prepareInsert( 1 ); insertType( array, data ); - if ( !fast ) - endInsertRows(); + endInsertRows(); } + // Update the byte array if ( NifItem * child = array->child( 0 ) ) { - QByteArray * bm = (child->value().type() == NifValue::tBlob) ? get( child ) : NULL; - - if ( bm == NULL ) { + QByteArray * bm = (child->isBinary()) ? get( child ) : nullptr; + if ( !bm ) { set( child, bytes ); } else if ( bm->size() == 0 ) { *bm = bytes; } else { - bm->resize( calcRows ); + bm->resize( rows ); } } return true; } -bool NifModel::updateArrayItem( NifItem * array, bool fast ) +bool NifModel::updateArrayItem( NifItem * array ) { - if ( array->arr1().isEmpty() ) + if ( !isArray( array ) ) return false; - int d1 = getArraySize( array ); + // New row count + int rows = getArraySize( array ); - - // Special case for very large arrays that are opaque in nature. - // Typical array handling has very poor performance with these arrays - if ( NifValue::type( array->type() ) == NifValue::tBlob ) { - if ( updateByteArrayItem( array, fast ) ) + // Binary array handling + if ( array->isBinary() ) { + if ( updateByteArrayItem( array ) ) return true; } - if ( d1 > 1024 * 1024 * 8 ) { - msg( Message() << tr( "array %1 much too large. %2 bytes requested" ).arg( array->name() ).arg( d1 ) ); + // Error handling + if ( rows > 1024 * 1024 * 8 ) { + auto m = tr( "array %1 much too large. %2 bytes requested" ).arg( array->name() ).arg( rows ); + if ( msgMode == UserMessage ) { + Message::append( nullptr, tr( "Could not update array item." ), m, QMessageBox::Critical ); + } else { + testMsg( m ); + } + return false; - } else if ( d1 < 0 ) { - msg( Message() << tr( "array %1 invalid" ).arg( array->name() ) ); + } else if ( rows < 0 ) { + auto m = tr( "array %1 invalid" ).arg( array->name() ); + if ( msgMode == UserMessage ) { + Message::append( nullptr, tr( "Could not update array item." ), m, QMessageBox::Critical ); + } else { + testMsg( m ); + } + return false; } - int rows = array->childCount(); + // Previous row count + int itemRows = array->childCount(); - if ( d1 > rows ) { - NifData data( array->name(), array->type(), array->temp(), NifValue( NifValue::type( array->type() ) ), parentPrefix( array->arg() ), parentPrefix( array->arr2() ), QString(), QString(), 0, 0 ); + // Add item children + if ( rows > itemRows ) { + NifData data( array->name(), + array->type(), + array->temp(), + NifValue( NifValue::type( array->type() ) ), + parentPrefix( array->arg() ), + parentPrefix( array->arr2() ) // arr1 in children is parent arr2 + ); - if ( !fast ) - beginInsertRows( createIndex( array->row(), 0, array ), rows, d1 - 1 ); + // Fill data flags + data.setIsConditionless( true ); + data.setIsCompound( array->isCompound() ); + data.setIsArray( array->isMultiArray() ); - array->prepareInsert( d1 - rows ); + beginInsertRows( createIndex( array->row(), 0, array ), itemRows, rows - 1 ); - for ( int c = rows; c < d1; c++ ) + array->prepareInsert( rows - itemRows ); + + for ( int c = itemRows; c < rows; c++ ) insertType( array, data ); - if ( !fast ) - endInsertRows(); + endInsertRows(); } - if ( d1 < rows ) { - if ( !fast ) - beginRemoveRows( createIndex( array->row(), 0, array ), d1, rows - 1 ); + // Remove item children + if ( rows < itemRows ) { + beginRemoveRows( createIndex( array->row(), 0, array ), rows, itemRows - 1 ); - array->removeChildren( d1, rows - d1 ); + array->removeChildren( rows, itemRows - rows ); - if ( !fast ) - endRemoveRows(); + endRemoveRows(); } - if ( !fast && d1 != rows && ( isCompound( array->type() ) || NifValue::isLink( NifValue::type( array->type() ) ) ) ) { + if ( (state != Loading) && (rows != itemRows) && (isCompound( array->type() ) || NifValue::isLink( NifValue::type( array->type() ) )) ) { NifItem * parent = array; while ( parent->parent() && parent->parent() != root ) @@ -614,23 +570,18 @@ bool NifModel::updateArrayItem( NifItem * array, bool fast ) return true; } -bool NifModel::updateArrays( NifItem * parent, bool fast ) +bool NifModel::updateArrays( NifItem * parent ) { if ( !parent ) return false; - for ( int row = 0; row < parent->childCount(); row++ ) { - NifItem * child = parent->child( row ); - + for ( auto child : parent->children() ) { if ( evalCondition( child ) ) { - if ( !child->arr1().isEmpty() ) { - if ( !updateArrayItem( child, fast ) ) - return false; - - if ( !updateArrays( child, false ) ) + if ( isArray( child ) ) { + if ( !updateArrayItem( child ) || !updateArrays( child ) ) return false; } else if ( child->childCount() > 0 ) { - if ( !updateArrays( child, fast ) ) + if ( !updateArrays( child ) ) return false; } } @@ -643,9 +594,9 @@ bool NifModel::updateArrays( NifItem * parent, bool fast ) * block functions */ -QModelIndex NifModel::insertNiBlock( const QString & identifier, int at, bool fast ) +QModelIndex NifModel::insertNiBlock( const QString & identifier, int at ) { - NifBlock * block = blocks.value( identifier ); + NifBlockPtr block = blocks.value( identifier ); if ( block ) { if ( at < 0 || at > getBlockCount() ) @@ -659,13 +610,12 @@ QModelIndex NifModel::insertNiBlock( const QString & identifier, int at, bool fa else at = getBlockCount() + 1; - if ( !fast ) - beginInsertRows( QModelIndex(), at, at ); + beginInsertRows( QModelIndex(), at, at ); NifItem * branch = insertBranch( root, NifData( identifier, "NiBlock", block->text ), at ); + branch->setCondition( true ); - if ( !fast ) - endInsertRows(); + endInsertRows(); if ( !block->ancestor.isEmpty() ) insertAncestor( branch, block->ancestor ); @@ -676,7 +626,7 @@ QModelIndex NifModel::insertNiBlock( const QString & identifier, int at, bool fa insertType( branch, data ); } - if ( !fast ) { + if ( state != Loading ) { updateHeader(); updateLinks(); updateFooter(); @@ -686,7 +636,12 @@ QModelIndex NifModel::insertNiBlock( const QString & identifier, int at, bool fa return createIndex( branch->row(), 0, branch ); } - msg( Message() << tr( "unknown block %1" ).arg( identifier ) ); + if ( msgMode == UserMessage ) { + Message::critical( nullptr, tr( "Could not insert NiBlock." ), tr( "unknown block %1" ).arg( identifier ) ); + } else { + testMsg( tr( "unknown block %1" ) ); + } + return QModelIndex(); } @@ -700,7 +655,6 @@ void NifModel::removeNiBlock( int blocknum ) beginRemoveRows( QModelIndex(), blocknum + 1, blocknum + 1 ); root->removeChild( blocknum + 1 ); endRemoveRows(); - updateHeader(); updateLinks(); updateFooter(); emit linksChanged(); @@ -745,7 +699,7 @@ void NifModel::moveNiBlock( int src, int dst ) void NifModel::updateStrings( NifModel * src, NifModel * tgt, NifItem * item ) { - if ( NULL == item ) + if ( !item ) return; NifValue::Type vt = item->value().type(); @@ -755,8 +709,8 @@ void NifModel::updateStrings( NifModel * src, NifModel * tgt, NifItem * item ) tgt->assignString( tgt->createIndex( 0, 0, item ), str, false ); } - for ( int i = 0; i < item->childCount(); ++i ) { - updateStrings( src, tgt, item->child( i ) ); + for ( auto child : item->children() ) { + updateStrings( src, tgt, child ); } } @@ -806,8 +760,14 @@ void NifModel::reorderBlocks( const QVector & order ) if ( getBlockCount() <= 1 ) return; + QString err = tr( "NifModel::reorderBlocks() - invalid argument" ); + if ( order.count() != getBlockCount() ) { - msg( Message() << tr( "NifModel::reorderBlocks() - invalid argument" ) ); + if ( msgMode == UserMessage ) { + Message::critical( nullptr, err ); + } else { + testMsg( err ); + } return; } @@ -816,7 +776,12 @@ void NifModel::reorderBlocks( const QVector & order ) for ( qint32 n = 0; n < order.count(); n++ ) { if ( blockMap.contains( order[n] ) || order[n] < 0 || order[n] >= getBlockCount() ) { - msg( Message() << tr( "NifModel::reorderBlocks() - invalid argument" ) ); + if ( msgMode == UserMessage ) { + Message::critical( nullptr, err ); + } else { + testMsg( err ); + } + return; } @@ -965,10 +930,20 @@ bool NifModel::isNiBlock( const QModelIndex & index, const QString & name ) cons return false; } +bool NifModel::isNiBlock( const QModelIndex & index, const QStringList & names ) const +{ + for ( const QString & name : names ) { + if ( isNiBlock( index, name ) ) + return true; + } + + return false; +} + NifItem * NifModel::getBlockItem( int x ) const { if ( x < 0 || x >= getBlockCount() ) - return 0; + return nullptr; return root->child( x + 1 ); } @@ -985,8 +960,10 @@ int NifModel::getBlockCount() const void NifModel::insertAncestor( NifItem * parent, const QString & identifier, int at ) { + setState( Inserting ); + Q_UNUSED( at ); - NifBlock * ancestor = blocks.value( identifier ); + NifBlockPtr ancestor = blocks.value( identifier ); if ( ancestor ) { if ( !ancestor->ancestor.isEmpty() ) @@ -998,8 +975,15 @@ void NifModel::insertAncestor( NifItem * parent, const QString & identifier, int insertType( parent, data ); } } else { - msg( Message() << tr( "unknown ancestor %1" ).arg( identifier ) ); + if ( msgMode == UserMessage ) { + Message::warning( nullptr, tr( "Cannot insert parent." ), tr( "unknown parent %1" ).arg( identifier ) ); + } else { + testMsg( tr( "unknown parent %1" ).arg( identifier ) ); + } + } + + restoreState(); } bool NifModel::inherits( const QString & name, const QString & aunty ) const @@ -1007,7 +991,7 @@ bool NifModel::inherits( const QString & name, const QString & aunty ) const if ( name == aunty ) return true; - NifBlock * type = blocks.value( name ); + NifBlockPtr type = blocks.value( name ); if ( type && ( type->ancestor == aunty || inherits( type->ancestor, aunty ) ) ) return true; @@ -1040,54 +1024,54 @@ void NifModel::insertType( const QModelIndex & parent, const NifData & data, int void NifModel::insertType( NifItem * parent, const NifData & data, int at ) { - if ( !data.arr1().isEmpty() ) { - NifItem * array = insertBranch( parent, data, at ); - - if ( evalCondition( array ) ) - updateArrayItem( array, true ); - - return; - } + setState( Inserting ); - NifBlock * compound = compounds.value( data.type() ); - - if ( compound ) { + if ( data.isArray() ) { + NifItem * item = insertBranch( parent, data, at ); + } else if ( data.isCompound() ) { + NifBlockPtr compound = compounds.value( data.type() ); + if ( !compound ) + return; NifItem * branch = insertBranch( parent, data, at ); branch->prepareInsert( compound->types.count() ); - for ( const NifData& d : compound->types ) { + const auto & types = compound->types; + for ( const NifData & d : types ) { insertType( branch, d ); } - } else { - if ( data.type() == "TEMPLATE" || data.temp() == "TEMPLATE" ) { - QString tmp = parent->temp(); - NifItem * tItem = parent; + } else if ( data.isTemplated() ) { + QLatin1String tmpl( "TEMPLATE" ); + QString tmp = parent->temp(); + NifItem * tItem = parent; - while ( tmp == "TEMPLATE" && tItem->parent() ) { - tItem = tItem->parent(); - tmp = tItem->temp(); - } + while ( tmp == tmpl && tItem->parent() ) { + tItem = tItem->parent(); + tmp = tItem->temp(); + } - NifData d( data ); + NifData d( data ); - if ( d.type() == "TEMPLATE" ) { - d.value.changeType( NifValue::type( tmp ) ); - d.setType( tmp ); - } + if ( d.type() == tmpl ) { + d.value.changeType( NifValue::type( tmp ) ); + d.setType( tmp ); + // The templates are now filled + d.setTemplated( false ); + } - if ( d.temp() == "TEMPLATE" ) - d.setTemp( tmp ); + if ( d.temp() == tmpl ) + d.setTemp( tmp ); - insertType( parent, d, at ); - } else { - NifItem * item = parent->insertChild( data, at ); + insertType( parent, d, at ); + } else { + NifItem * item = parent->insertChild( data, at ); - // Kludge for string conversion. - // Ensure that the string type is correct for the nif version - if ( item->value().type() == NifValue::tString || item->value().type() == NifValue::tFilePath ) { - item->value().changeType( version < 0x14010003 ? NifValue::tSizedString : NifValue::tStringIndex ); - } + // Kludge for string conversion. + // Ensure that the string type is correct for the nif version + if ( item->value().type() == NifValue::tString || item->value().type() == NifValue::tFilePath ) { + item->value().changeType( version < 0x14010003 ? NifValue::tSizedString : NifValue::tStringIndex ); } } + + restoreState(); } @@ -1370,14 +1354,14 @@ QVariant NifModel::data( const QModelIndex & idx, int role ) const switch ( column ) { case NameCol: { - if ( item->parent() && !item->parent()->arr1().isEmpty() ) { + if ( item->parent() && isArray( item->parent() ) ) { return QString( "array index: %1" ).arg( item->row() ); } else { QString tip = QString( "

%1

%2

" ) .arg( item->name() ) .arg( QString( item->text() ).replace( "<", "<" ).replace( "\n", "
" ) ); - if ( NifBlock * blk = blocks.value( item->name() ) ) { + if ( NifBlockPtr blk = blocks.value( item->name() ) ) { tip += "

Ancestors:

    "; while ( blocks.contains( blk->ancestor ) ) { @@ -1403,12 +1387,14 @@ QVariant NifModel::data( const QModelIndex & idx, int role ) const case NifValue::tBool: case NifValue::tInt: case NifValue::tUInt: + case NifValue::tULittle32: { return tr( "dec: %1\nhex: 0x%2" ) .arg( item->value().toString() ) .arg( item->value().toCount(), 8, 16, QChar( '0' ) ); } case NifValue::tFloat: + case NifValue::tHfloat: { return tr( "float: %1\nhex: 0x%2" ) .arg( NumOrMinMax( item->value().toFloat(), 'g', 8 ) ) @@ -1430,6 +1416,10 @@ QVariant NifModel::data( const QModelIndex & idx, int role ) const .arg( item->value().toCount(), 8, 16, QChar( '0' ) ); case NifValue::tVector3: return item->value().get().toHtml(); + case NifValue::tHalfVector3: + return item->value().get().toHtml(); + case NifValue::tByteVector3: + return item->value().get().toHtml(); case NifValue::tMatrix: return item->value().get().toHtml(); case NifValue::tMatrix4: @@ -1445,6 +1435,15 @@ QVariant NifModel::data( const QModelIndex & idx, int role ) const .arg( c[1] ) .arg( c[2] ); } + case NifValue::tByteColor4: + { + Color4 c = item->value().get(); + return QString( "R %1\nG %2\nB %3\nA %4" ) + .arg( c[0] ) + .arg( c[1] ) + .arg( c[2] ) + .arg( c[3] ); + } case NifValue::tColor4: { Color4 c = item->value().get(); @@ -1472,7 +1471,7 @@ QVariant NifModel::data( const QModelIndex & idx, int role ) const NifItem * nv = findItemX( item, "Num Vertices" ); if ( !nv ) { - qWarning() << "Num Vertices is null"; + qDebug() << "Num Vertices is null"; return QVariant(); } @@ -1491,7 +1490,7 @@ QVariant NifModel::data( const QModelIndex & idx, int role ) const case Qt::UserRole: { if ( column == ValueCol ) { - Spell * spell = SpellBook::instant( this, index ); + SpellPtr spell = SpellBook::instant( this, index ); if ( spell ) return spell->page() + "/" + spell->name(); @@ -1548,7 +1547,7 @@ bool NifModel::setData( const QModelIndex & index, const QVariant & value, int r val.changeType( version < 0x14010003 ? NifValue::tSizedString : NifValue::tStringIndex ); assignString( index, value.toString(), true ); } else { - item->value().fromVariant( value ); + item->value().setFromVariant( value ); if ( isLink( index ) && getBlockOrHeader( index ) != getFooter() ) { updateLinks(); @@ -1601,8 +1600,12 @@ bool NifModel::setData( const QModelIndex & index, const QVariant & value, int r } } - // update original index - emit dataChanged( index, index ); + if ( state == Default ) { + // Reassess conditions for reliant data only when modifying value + invalidateDependentConditions( item ); + // update original index + emit dataChanged( index, index ); + } return true; } @@ -1610,6 +1613,7 @@ bool NifModel::setData( const QModelIndex & index, const QVariant & value, int r void NifModel::reset() { beginResetModel(); + resetState(); updateLinks(); endResetModel(); } @@ -1650,14 +1654,19 @@ bool NifModel::removeRows( int row, int count, const QModelIndex & parent ) bool NifModel::setHeaderString( const QString & s ) { - //msg( DbgMsg() << s ); if ( !( s.startsWith( "NetImmerse File Format" ) || s.startsWith( "Gamebryo" ) // official || s.startsWith( "NDSNIF" ) // altantica || s.startsWith( "NS" ) // neosteam || s.startsWith( "Joymaster HS1 Object Format - (JMI)" ) // howling sword, uses .jmi extension ) ) { - msg( Message() << tr( "this is not a NIF" ) ); + auto m = tr( "Could not open %1 because it is not a supported type." ).arg( fileinfo.fileName() ); + if ( msgMode == UserMessage ) { + Message::critical( nullptr, m ); + } else { + testMsg( m ); + } + return false; } @@ -1679,7 +1688,13 @@ bool NifModel::setHeaderString( const QString & s ) version = version2number( v ); if ( !isVersionSupported( version ) ) { - msg( Message() << tr( "version %1 (%2) is not supported yet" ).arg( version2string( version ), v ) ); + auto m = tr( "Version %1 (%2) is not supported." ).arg( version2string( version ), v ); + if ( msgMode == UserMessage ) { + Message::critical( nullptr, m ); + } else { + testMsg( m ); + } + return false; } @@ -1690,7 +1705,12 @@ bool NifModel::setHeaderString( const QString & s ) return true; } - msg( Message() << tr( "invalid header string" ) ); + if ( msgMode == UserMessage ) { + Message::critical( nullptr, tr( "Invalid header string" ) ); + } else { + testMsg( tr( "Invalid header string" ) ); + } + return false; } @@ -1704,20 +1724,21 @@ bool NifModel::load( QIODevice & device ) NifIStream stream( this, &device ); + if ( state != Loading ) + setState( Loading ); + // read header - NifItem * header = NULL; + NifItem * header = nullptr; header = getHeaderItem(); + if ( !header || !loadHeader( header, stream ) ) { + auto m = tr( "failed to load file header (version %1, %2)" ).arg( version, 0, 16 ).arg( version2string( version ) ); + if ( msgMode == UserMessage ) { + Message::critical( nullptr, tr( "The file could not be read. See Details for more information." ), m ); + } else { + testMsg( m ); + } - // bugfix: force user versions to zero (if the template was version - // 20.0.0.5 then they have been set to non-zero, and the read function - // will not reset them to zero on older files...) - if ( header ) { - set( header, "User Version 2", 0 ); - set( header, "User Version", 0 ); - } - - if ( !header || !load( header, stream, true ) ) { - msg( Message() << tr( "failed to load file header (version %1, %2)" ).arg( version, 0, 16 ).arg( version2string( version ) ) ); + resetState(); return false; } @@ -1726,7 +1747,7 @@ bool NifModel::load( QIODevice & device ) //qDebug( "numblocks %i", numblocks ); emit sigProgress( 0, numblocks ); - QTime t = QTime::currentTime(); + //QTime t = QTime::currentTime(); qint64 curpos = 0; try @@ -1760,8 +1781,14 @@ bool NifModel::load( QIODevice & device ) int dummy; device.read( (char *)&dummy, 4 ); - if ( dummy != 0 ) - msg( Message() << tr( "non-zero block separator (%1) preceeding block %2" ).arg( dummy ).arg( blktyp ) ); + if ( dummy != 0 ) { + auto m = tr( "non-zero block separator (%1) preceeding block %2" ).arg( dummy ).arg( blktyp ); + if ( msgMode == UserMessage ) { + Message::append( tr( "Warnings were generated while reading NIF file." ), m ); + } else { + testMsg( m ); + } + } } // for version 20.2.0.? and above the block size is stored in the header @@ -1797,16 +1824,14 @@ bool NifModel::load( QIODevice & device ) throw tr( "Unknown NiDataStream" ); } -#ifndef QT_NO_DEBUG - qWarning() << "Loaded NiDataStream with usage " << dataStreamUsage << " access " << dataStreamAccess; -#endif + qDebug() << "Loaded NiDataStream with usage " << dataStreamUsage << " access " << dataStreamAccess; } if ( isNiBlock( blktyp ) ) { - //msg( DbgMsg() << "loading block" << c << ":" << blktyp ); - QModelIndex newBlock = insertNiBlock( blktyp, -1, true ); + //qDebug() << "loading block" << c << ":" << blktyp ); + QModelIndex newBlock = insertNiBlock( blktyp, -1 ); - if ( !load( root->child( c + 1 ), stream, true ) ) { + if ( !loadItem( root->child( c + 1 ), stream ) ) { NifItem * child = root->child( c ); throw tr( "failed to load block number %1 (%2) previous block was %3" ).arg( c ).arg( blktyp ).arg( child ? child->name() : prevblktyp ); } @@ -1817,7 +1842,13 @@ bool NifModel::load( QIODevice & device ) set( newBlock, "Access", dataStreamAccess ); } } else { - msg( Message() << tr( "warning: block %1 (%2) not inserted!" ).arg( c ).arg( blktyp ) ); + auto m = tr( "warning: block %1 (%2) not inserted!" ).arg( c ).arg( blktyp ); + if ( msgMode == UserMessage ) { + Message::append( tr( "Warnings were generated while reading NIF file." ), m ); + } else { + testMsg( m ); + } + throw tr( "encountered unknown block (%1)" ).arg( blktyp ); } } @@ -1835,11 +1866,24 @@ bool NifModel::load( QIODevice & device ) if ( (curpos + size) != pos ) { // unable to seek to location... abort - if ( device.seek( curpos + size ) ) - msg( Message() << tr( "device position incorrect after block number %1 (%2) at 0x%3 ended at 0x%4 (expected 0x%5)" ).arg( c ).arg( blktyp ).arg( QString::number( curpos, 16 ) ).arg( QString::number( pos, 16 ) ).arg( QString::number( curpos + size, 16 ) ).toLatin1() ); - else + if ( device.seek( curpos + size ) ) { + auto m = tr( "device position incorrect after block number %1 (%2) at 0x%3 ended at 0x%4 (expected 0x%5)" ) + .arg( c ) + .arg( blktyp ) + .arg( QString::number( curpos, 16 ) ) + .arg( QString::number( pos, 16 ) ) + .arg( QString::number( curpos + size, 16 ) + ); + + if ( msgMode == UserMessage ) { + Message::append( tr( "Warnings were generated while reading NIF file." ), m ); + } else { + testMsg( m ); + } + } + else { throw tr( "failed to reposition device at block number %1 (%2) previous block was %3" ).arg( c ).arg( blktyp ).arg( root->child( c )->name() ); - + } curpos = device.pos(); } else { curpos = pos; @@ -1850,8 +1894,11 @@ bool NifModel::load( QIODevice & device ) } // read in the footer - if ( !load( getFooterItem(), stream, true ) ) - throw tr( "failed to load file footer" ); + // Disabling the throw because it hinders decoding when the XML is wrong, + // and prevents any data whatsoever from loading. + loadItem( getFooterItem(), stream ); + //if ( !loadItem( getFooterItem(), stream ) ) + // throw tr( "failed to load file footer" ); } else { // versions below 3.3.0.13 QMap linkMap; @@ -1890,10 +1937,10 @@ bool NifModel::load( QIODevice & device ) linkMap.insert( p, c ); if ( isNiBlock( blktyp ) ) { - //msg( DbgMsg() << "loading block" << c << ":" << blktyp ); - insertNiBlock( blktyp, -1, true ); + //qDebug() << "loading block" << c << ":" << blktyp ); + insertNiBlock( blktyp, -1 ); - if ( !load( root->child( c + 1 ), stream, true ) ) + if ( !loadItem( root->child( c + 1 ), stream ) ) throw tr( "failed to load block number %1 (%2) previous block was %3" ).arg( c ).arg( blktyp ).arg( root->child( c )->name() ); } else { throw tr( "encountered unknown block (%1)" ).arg( blktyp ); @@ -1914,11 +1961,17 @@ bool NifModel::load( QIODevice & device ) } catch ( QString err ) { - msg( Message() << err.toLatin1().constData() ); + if ( msgMode == UserMessage ) { + Message::critical( nullptr, tr( "The NIF file could not be read. See Details for more information." ), err ); + } else { + testMsg( err ); + } + reset(); return false; } - //msg( Message() << t.msecsTo( QTime::currentTime() ) ); + + //qDebug() << t.msecsTo( QTime::currentTime() ); reset(); // notify model views that a significant change to the data structure has occurded return true; } @@ -1927,6 +1980,8 @@ bool NifModel::save( QIODevice & device ) const { NifOStream stream( this, &device ); + setState( Saving ); + // Force update header and footer prior to save if ( NifModel * mdl = const_cast(this) ) { mdl->updateHeader(); @@ -1938,10 +1993,7 @@ bool NifModel::save( QIODevice & device ) const for ( int c = 0; c < rowCount( QModelIndex() ); c++ ) { emit sigProgress( c + 1, rowCount( QModelIndex() ) ); - //msg( DbgMsg() << "saving block" << c << ":" << itemName( index( c, 0 ) ) ); -#ifndef QT_NO_DEBUG - qWarning() << "saving block " << c << ": " << itemName( index( c, 0 ) ); -#endif + //qDebug() << "saving block " << c << ": " << itemName( index( c, 0 ) ); if ( itemType( index( c, 0 ) ) == "NiBlock" ) { if ( version > 0x0a000000 ) { @@ -1970,8 +2022,9 @@ bool NifModel::save( QIODevice & device ) const } } - if ( !save( root->child( c ), stream ) ) { - msg( Message() << tr( "failed to write block %1(%2)" ).arg( itemName( index( c, 0 ) ) ).arg( c - 1 ) ); + if ( !saveItem( root->child( c ), stream ) ) { + Message::critical( nullptr, tr( "Failed to write block %1 (%2)." ).arg( itemName( index( c, 0 ) ) ).arg( c - 1 ) ); + resetState(); return false; } } @@ -1983,16 +2036,17 @@ bool NifModel::save( QIODevice & device ) const device.write( string.toLatin1().constData(), len ); } + resetState(); return true; } -bool NifModel::load( QIODevice & device, const QModelIndex & index ) +bool NifModel::loadIndex( QIODevice & device, const QModelIndex & index ) { NifItem * item = static_cast( index.internalPointer() ); if ( item && index.isValid() && index.model() == this ) { NifIStream stream( this, &device ); - bool ok = load( item, stream, false ); + bool ok = loadItem( item, stream ); updateLinks(); updateFooter(); emit linksChanged(); @@ -2009,7 +2063,7 @@ bool NifModel::loadAndMapLinks( QIODevice & device, const QModelIndex & index, c if ( item && index.isValid() && index.model() == this ) { NifIStream stream( this, &device ); - bool ok = load( item, stream, false ); + bool ok = loadItem( item, stream ); mapLinks( item, map ); updateLinks(); updateFooter(); @@ -2028,7 +2082,7 @@ bool NifModel::loadHeaderOnly( const QString & fname ) QFile f( fname ); if ( !f.open( QIODevice::ReadOnly ) ) { - msg( Message() << tr( "failed to open file" ) << fname ); + Message::critical( nullptr, tr( "Failed to open %1" ).arg( fname ) ); return false; } @@ -2037,8 +2091,12 @@ bool NifModel::loadHeaderOnly( const QString & fname ) // read header NifItem * header = getHeaderItem(); - if ( !header || !load( header, stream, true ) ) { - msg( Message() << tr( "failed to load file header (version %1)" ).arg( version ) ); + if ( !header || !loadHeader( header, stream ) ) { + if ( msgMode == UserMessage ) { + Message::critical( nullptr, tr( "Failed to load file header." ), tr( "Version %1" ).arg( version ) ); + } else { + testMsg( tr( "Failed to load file header version %1" ).arg( version ) ); + } return false; } @@ -2067,7 +2125,8 @@ bool NifModel::earlyRejection( const QString & filepath, const QString & blockId if ( blockId.isEmpty() == true || version < 0x0A000100 ) { blk_match = true; } else { - for ( const QString& s : nif.getArray( nif.getHeader(), "Block Types" ) ) { + const auto & types = nif.getArray( nif.getHeader(), "Block Types" ); + for ( const QString& s : types ) { if ( inherits( s, blockId ) ) { blk_match = true; break; @@ -2078,11 +2137,11 @@ bool NifModel::earlyRejection( const QString & filepath, const QString & blockId return (ver_match && blk_match); } -bool NifModel::save( QIODevice & device, const QModelIndex & index ) const +bool NifModel::saveIndex( QIODevice & device, const QModelIndex & index ) const { NifOStream stream( this, &device ); NifItem * item = static_cast( index.internalPointer() ); - return ( item && index.isValid() && index.model() == this && save( item, stream ) ); + return ( item && index.isValid() && index.model() == this && saveItem( item, stream ) ); } int NifModel::fileOffset( const QModelIndex & index ) const @@ -2147,19 +2206,22 @@ int NifModel::blockSize( NifItem * parent, NifSStream & stream ) const NifItem * child = parent->child( row ); if ( child->isAbstract() ) { -#ifndef QT_NO_DEBUG - // qWarning() << "Not counting abstract item " << child->name(); -#endif + //qDebug() << "Not counting abstract item " << child->name(); continue; } if ( evalCondition( child ) ) { - if ( !child->arr1().isEmpty() || !child->arr2().isEmpty() || child->childCount() > 0 ) { - if ( !child->arr1().isEmpty() && child->childCount() != getArraySize( child ) ) { - if ( ( NifValue::type( child->type() ) == NifValue::tBlob ) ) { + if ( isArray( child ) || !child->arr2().isEmpty() || child->childCount() > 0 ) { + if ( isArray( child ) && child->childCount() != getArraySize( child ) ) { + if ( child->isBinary() ) { // special byte } else { - msg( Message() << tr( "block %1 %2 array size mismatch" ).arg( getBlockNumber( parent ) ).arg( child->name() ) ); + auto m = tr( "block %1 %2 array size mismatch" ).arg( getBlockNumber( parent ) ).arg( child->name() ); + if ( msgMode == UserMessage ) { + Message::append( tr( "Warnings were generated while reading the blocks." ), m ); + } else { + testMsg( m ); + } } } @@ -2173,74 +2235,76 @@ int NifModel::blockSize( NifItem * parent, NifSStream & stream ) const return size; } -bool NifModel::load( NifItem * parent, NifIStream & stream, bool fast ) +bool NifModel::loadItem( NifItem * parent, NifIStream & stream ) { if ( !parent ) return false; - for ( int row = 0; row < parent->childCount(); row++ ) { - NifItem * child = parent->child( row ); + for ( auto child : parent->children() ) { + if ( !child->isConditionless() ) + child->invalidateCondition(); if ( child->isAbstract() ) { -#ifndef QT_NO_DEBUG - // qWarning() << "Not loading abstract item " << child->name(); -#endif + //qDebug() << "Not loading abstract item " << child->name(); continue; } if ( evalCondition( child ) ) { - if ( !child->arr1().isEmpty() ) { - if ( !updateArrayItem( child, fast ) ) - return false; - - if ( !load( child, stream, fast ) ) + if ( isArray( child ) ) { + if ( !updateArrayItem( child ) || !loadItem( child, stream ) ) return false; } else if ( child->childCount() > 0 ) { - if ( !load( child, stream, fast ) ) + if ( !loadItem( child, stream ) ) return false; } else { if ( !stream.read( child->value() ) ) return false; } } - - // these values are always little-endian - if ( (child->name() == "Num Blocks") || (child->name() == "User Version") || (child->name() == "User Version 2") ) { - if ( version >= 0x14000004 && get( getHeaderItem(), "Endian Type" ) == 0 ) { - child->value().setCount( qFromBigEndian( child->value().toCount() ) ); - } - } } return true; } -bool NifModel::save( NifItem * parent, NifOStream & stream ) const +bool NifModel::loadHeader( NifItem * header, NifIStream & stream ) { - if ( !parent ) + // Load header separately and invalidate conditions before reading + // Compensates for < 20.0.0.5 User Version/User Version 2 program defaults issue + if ( !header ) return false; - for ( int row = 0; row < parent->childCount(); row++ ) { - NifItem * child = parent->child( row ); + set( header, "User Version", 0 ); + set( header, "User Version 2", 0 ); + + invalidateConditions( header, false ); + + return loadItem( header, stream ); +} +bool NifModel::saveItem( NifItem * parent, NifOStream & stream ) const +{ + if ( !parent ) + return false; + + for ( auto child : parent->children() ) { if ( child->isAbstract() ) { -#ifndef QT_NO_DEBUG - qWarning() << "Not saving abstract item " << child->name(); -#endif + qDebug() << "Not saving abstract item " << child->name(); continue; } if ( evalCondition( child ) ) { - if ( !child->arr1().isEmpty() || !child->arr2().isEmpty() || child->childCount() > 0 ) { - if ( !child->arr1().isEmpty() && child->childCount() != getArraySize( child ) ) { - if ( ( NifValue::type( child->type() ) == NifValue::tBlob ) ) { + if ( isArray( child ) || !child->arr2().isEmpty() || child->childCount() > 0 ) { + if ( isArray( child ) && child->childCount() != getArraySize( child ) ) { + if ( child->isBinary() ) { // special byte } else { - msg( Message() << tr( "block %1 %2 array size mismatch" ).arg( getBlockNumber( parent ) ).arg( child->name() ) ); + Message::append( tr( "Warnings were generated while reading the blocks." ), + tr( "block %1 %2 array size mismatch" ).arg( getBlockNumber( parent ) ).arg( child->name() ) + ); } } - if ( !save( child, stream ) ) + if ( !saveItem( child, stream ) ) return false; } else { if ( !stream.write( child->value() ) ) @@ -2257,14 +2321,12 @@ bool NifModel::fileOffset( NifItem * parent, NifItem * target, NifSStream & stre if ( parent == target ) return true; - for ( int row = 0; row < parent->childCount(); row++ ) { - NifItem * child = parent->child( row ); - + for ( auto child : parent->children() ) { if ( child == target ) return true; if ( evalCondition( child ) ) { - if ( !child->arr1().isEmpty() || !child->arr2().isEmpty() || child->childCount() > 0 ) { + if ( isArray( child ) || !child->arr2().isEmpty() || child->childCount() > 0 ) { if ( fileOffset( child, target, stream, ofs ) ) return true; } else { @@ -2283,6 +2345,103 @@ NifItem * NifModel::insertBranch( NifItem * parentItem, const NifData & data, in return item; } +bool NifModel::evalCondition( NifItem * item, bool chkParents ) const +{ + if ( item->isConditionValid() ) + return item->condition(); + + // Early reject + if ( item->isConditionless() ) { + item->setCondition( true ); + return item->condition(); + } + + // Store row conditions for certain compound arrays + NifItem * parent = item->parent(); + if ( isFixedCompound( parent->type() ) ) { + NifItem * arrRoot = parent->parent(); + if ( isArray( arrRoot ) ) { + // This is a compound in the compound array + if ( parent->row() == 0 ) { + // This is the first compound in the compound array + if ( item->row() == 0 ) { + // First row of first compound, initialize condition cache + arrRoot->resetArrayConditions(); + } + // Cache condition on array root + arrRoot->updateArrayCondition( BaseModel::evalCondition( item ), item->row() ); + } + auto arrCond = arrRoot->arrayConditions(); + auto itemRow = item->row(); + if ( arrCond.count() > itemRow ) + item->setCondition( arrCond.at( itemRow ) ); + return item->condition(); + } + } + + item->setCondition( BaseModel::evalCondition( item, chkParents ) ); + + return item->condition(); +} + +void NifModel::invalidateConditions( NifItem * item, bool refresh ) +{ + for ( NifItem * c : item->children() ) { + c->invalidateCondition(); + c->invalidateVersionCondition(); + if ( refresh ) + c->setCondition( BaseModel::evalCondition( c ) ); + + if ( isArray( c ) || c->childCount() > 0 ) { + invalidateConditions( c ); + } + } + + // Reset conditions cached on array root for fixed condition compounds + if ( isArray( item ) && isFixedCompound( item->type() ) ) { + item->resetArrayConditions(); + } +} + +void NifModel::invalidateConditions( const QModelIndex & index, bool refresh ) +{ + auto item = static_cast(index.internalPointer()); + if ( item ) + invalidateConditions( item, refresh ); +} + +void NifModel::invalidateDependentConditions( NifItem * item ) +{ + if ( !item ) + return; + + NifItem * p = item->parent(); + if ( !p || p == root ) + return; + + QString name = item->name(); + for ( int i = item->row(); i < p->childCount(); i++ ) { + auto c = p->children().at( i ); + // String check for Name in cond or arg + // Note: May cause some false positives but this is OK + if ( c->cond().contains( name ) ) { + c->invalidateCondition(); + c->setCondition( BaseModel::evalCondition( c ) ); + } + + if ( (c->cond().contains( name ) || c->arg().contains( name )) && c->childCount() > 0 ) + invalidateConditions( c, true ); + } +} + +void NifModel::invalidateDependentConditions( const QModelIndex & index ) +{ + auto item = static_cast(index.internalPointer()); + if ( item ) + invalidateDependentConditions( item ); +} + + /* * link functions */ @@ -2303,9 +2462,11 @@ void NifModel::updateLinks( int block ) childLinks.clear(); parentLinks.clear(); + // Run updateLinks() for each block for ( int c = 0; c < getBlockCount(); c++ ) updateLinks( c ); + // Run checkLinks() for each block for ( int c = 0; c < getBlockCount(); c++ ) { QStack stack; checkLinks( c, stack ); @@ -2334,28 +2495,35 @@ void NifModel::updateLinks( int block, NifItem * parent ) if ( !parent ) return; - for ( int r = 0; r < parent->childCount(); r++ ) { - NifItem * child = parent->child( r ); - - bool ischild; - bool islink = itemIsLink( child, &ischild ); - - if ( child->childCount() > 0 ) { - updateLinks( block, child ); - } else if ( islink ) { - int l = child->value().toLink(); - - if ( l >= 0 && child->arr1().isEmpty() ) { - if ( ischild ) { - if ( !childLinks[block].contains( l ) ) - childLinks[block].append( l ); - } else { - if ( !parentLinks[block].contains( l ) ) - parentLinks[block].append( l ); - } + auto links = parent->getLinkRows(); + for ( int l : links ) { + NifItem * c = parent->child( l ); + if ( !c ) + continue; + + if ( c->childCount() > 0 ) { + updateLinks( block, c ); + continue; + } + + int i = c->value().toLink(); + if ( i >= 0 ) { + if ( c->value().type() == NifValue::tUpLink ) { + if ( !parentLinks[block].contains( i ) ) + parentLinks[block].append( i ); + } else { + if ( !childLinks[block].contains( i ) ) + childLinks[block].append( i ); } } } + + auto linkparents = parent->getLinkAncestorRows(); + for ( int p : linkparents ) { + NifItem * c = parent->child( p ); + if ( c && c->childCount() > 0 ) + updateLinks( block, c ); + } } void NifModel::checkLinks( int block, QStack & parents ) @@ -2363,7 +2531,13 @@ void NifModel::checkLinks( int block, QStack & parents ) parents.push( block ); foreach ( const auto child, childLinks.value( block ) ) { if ( parents.contains( child ) ) { - msg( Message() << tr( "infinite recursive link construct detected %1 -> %2" ).arg( block ).arg( child ) ); + auto m = tr( "infinite recursive link construct detected %1 -> %2" ).arg( block ).arg( child ); + if ( msgMode == UserMessage ) { + Message::append( tr( "Warnings were generated while reading NIF file." ), m ); + } else { + testMsg( m ); + } + childLinks[block].removeAll( child ); } else { checkLinks( child, parents ); @@ -2378,8 +2552,8 @@ void NifModel::adjustLinks( NifItem * parent, int block, int delta ) return; if ( parent->childCount() > 0 ) { - for ( int c = 0; c < parent->childCount(); c++ ) - adjustLinks( parent->child( c ), block, delta ); + for ( auto child : parent->children() ) + adjustLinks( child, block, delta ); } else { int l = parent->value().toLink(); @@ -2398,8 +2572,8 @@ void NifModel::mapLinks( NifItem * parent, const QMap & map ) return; if ( parent->childCount() > 0 ) { - for ( int c = 0; c < parent->childCount(); c++ ) - mapLinks( parent->child( c ), map ); + for ( auto child : parent->children() ) + mapLinks( child, map ); } else { int l = parent->value().toLink(); @@ -2441,9 +2615,9 @@ QVector NifModel::getLinkArray( const QModelIndex & iArray ) const NifItem * item = static_cast( iArray.internalPointer() ); if ( isArray( iArray ) && item && iArray.model() == this ) { - for ( int c = 0; c < item->childCount(); c++ ) { - if ( itemIsLink( item->child( c ) ) ) { - links.append( item->child( c )->value().toLink() ); + for ( auto child : item->children() ) { + if ( itemIsLink( child ) ) { + links.append( child->value().toLink() ); } else { links.clear(); break; @@ -2576,6 +2750,11 @@ int NifModel::getParent( int block ) const return parent; } +int NifModel::getParent( const QModelIndex & index ) const +{ + return getParent( getBlockNumber( index ) ); +} + QString NifModel::string( const QModelIndex & index, bool extraInfo ) const { NifValue v = getValue( index ); @@ -2720,7 +2899,7 @@ bool NifModel::assignString( const QModelIndex & index, const QString & name, co // convert a block from one type to another -void NifModel::convertNiBlock( const QString & identifier, const QModelIndex & index, bool fast ) +void NifModel::convertNiBlock( const QString & identifier, const QModelIndex & index ) { QString btype = getBlockName( index ); @@ -2728,13 +2907,13 @@ void NifModel::convertNiBlock( const QString & identifier, const QModelIndex & i return; if ( !inherits( btype, identifier ) && !inherits( identifier, btype ) ) { - msg( Message() << tr( "blocktype %1 and %2 are not related" ).arg( btype, identifier ) ); + Message::critical( nullptr, tr( "Cannot convert NiBlock." ), tr( "blocktype %1 and %2 are not related" ).arg( btype, identifier ) ); return; } NifItem * branch = static_cast( index.internalPointer() ); - NifBlock * srcBlock = blocks.value( btype ); - NifBlock * dstBlock = blocks.value( identifier ); + NifBlockPtr srcBlock = blocks.value( btype ); + NifBlockPtr dstBlock = blocks.value( identifier ); if ( srcBlock && dstBlock && branch ) { branch->setName( identifier ); @@ -2742,7 +2921,7 @@ void NifModel::convertNiBlock( const QString & identifier, const QModelIndex & i if ( inherits( btype, identifier ) ) { // Remove any level between the two types for ( QString ancestor = btype; !ancestor.isNull() && ancestor != identifier; ) { - NifBlock * block = blocks.value( ancestor ); + NifBlockPtr block = blocks.value( ancestor ); if ( !block ) break; @@ -2759,7 +2938,7 @@ void NifModel::convertNiBlock( const QString & identifier, const QModelIndex & i QStringList types; for ( QString ancestor = identifier; !ancestor.isNull() && ancestor != btype; ) { - NifBlock * block = blocks.value( ancestor ); + NifBlockPtr block = blocks.value( ancestor ); if ( !block ) break; @@ -2769,7 +2948,7 @@ void NifModel::convertNiBlock( const QString & identifier, const QModelIndex & i } for ( const QString& ancestor : types ) { - NifBlock * block = blocks.value( ancestor ); + NifBlockPtr block = blocks.value( ancestor ); if ( !block ) break; @@ -2780,7 +2959,8 @@ void NifModel::convertNiBlock( const QString & identifier, const QModelIndex & i if ( n > 0 ) { beginInsertRows( index, cn, cn + n - 1 ); branch->prepareInsert( n ); - for ( const NifData& data : block->types ) { + const auto & types = block->types; + for ( const NifData& data : types ) { insertType( branch, data ); } endInsertRows(); @@ -2788,7 +2968,7 @@ void NifModel::convertNiBlock( const QString & identifier, const QModelIndex & i } } - if ( !fast ) { + if ( state != Loading ) { updateHeader(); updateLinks(); updateFooter(); @@ -2829,3 +3009,103 @@ void NifModel::updateModel( UpdateType value ) emit linksChanged(); } + +/* + * NifModelEval + */ + +NifModelEval::NifModelEval( const NifModel * model, const NifItem * item ) +{ + this->model = model; + this->item = item; +} + +QVariant NifModelEval::operator()( const QVariant & v ) const +{ + if ( v.type() == QVariant::String ) { + QString left = v.toString(); + NifItem * i = const_cast(item); + i = model->getItem( i, left ); + + if ( i ) { + if ( i->value().isCount() ) + return QVariant( i->value().toCount() ); + else if ( i->value().isFileVersion() ) + return QVariant( i->value().toFileVersion() ); + } + + return QVariant( 0 ); + } + + return v; +} + + +/* + * ChangeValueCommand + */ + +ChangeValueCommand::ChangeValueCommand( const QModelIndex & index, + const QVariant & value, const QString & valueString, const QString & valueType, NifModel * model ) + : QUndoCommand(), nif( model ), idx( index ) +{ + oldValue = index.data( Qt::EditRole ); + newValue = value; + + auto oldTxt = index.data( Qt::DisplayRole ).toString(); + auto newTxt = valueString; + + if ( !newTxt.isEmpty() ) + setText( QApplication::translate( "ChangeValueCommand", "Set %1 to %2" ).arg( valueType ).arg( newTxt ) ); + else + setText( QApplication::translate( "ChangeValueCommand", "Modify %1" ).arg( valueType ) ); +} + +void ChangeValueCommand::redo() +{ + //qDebug() << "Redoing"; + nif->setData( idx, newValue, Qt::EditRole ); + + //qDebug() << nif->data( idx ).toString(); +} + +void ChangeValueCommand::undo() +{ + //qDebug() << "Undoing"; + nif->setData( idx, oldValue, Qt::EditRole ); + + //qDebug() << nif->data( idx ).toString(); +} + + +/* + * ToggleCheckBoxListCommand + */ + +ToggleCheckBoxListCommand::ToggleCheckBoxListCommand( const QModelIndex & index, + const QVariant & value, const QString & valueType, NifModel * model ) + : QUndoCommand(), nif( model ), idx( index ) +{ + oldValue = index.data( Qt::EditRole ); + newValue = value; + + auto oldTxt = index.data( Qt::DisplayRole ).toString(); + + setText( QApplication::translate( "ToggleCheckBoxListCommand", "Modify %1" ).arg( valueType ) ); +} + +void ToggleCheckBoxListCommand::redo() +{ + //qDebug() << "Redoing"; + nif->setData( idx, newValue, Qt::EditRole ); + + //qDebug() << nif->data( idx ).toString(); +} + +void ToggleCheckBoxListCommand::undo() +{ + //qDebug() << "Undoing"; + nif->setData( idx, oldValue, Qt::EditRole ); + + //qDebug() << nif->data( idx ).toString(); +} diff --git a/src/nifmodel.h b/src/nifmodel.h index 84fe49a95..308507a78 100644 --- a/src/nifmodel.h +++ b/src/nifmodel.h @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -39,37 +39,77 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include +#include +#include -//! \file nifmodel.h NifModel -//! Base class for nif models. +class SpellBook; + +using NifBlockPtr = std::shared_ptr; +using SpellBookPtr = std::shared_ptr; + +//! @file nifmodel.h NifModel, NifModelEval, ChangeValueCommand, ToggleCheckBoxListCommand + +//! The main data model for the NIF file. class NifModel final : public BaseModel { Q_OBJECT + friend class NifXmlHandler; + friend class NifModelEval; + friend class NifOStream; + public: - //! Constructor NifModel( QObject * parent = 0 ); - // call this once on startup to load the XML descriptions //! Find and parse the XML file static bool loadXML(); - // when creating NifModels from outside the main thread protect them with a QReadLocker (see the XML check spell for an example) + //! When creating NifModels from outside the main thread protect them with a QReadLocker static QReadWriteLock XMLlock; - // clear model data; implements BaseModel - void clear() override final; + // QAbstractItemModel + + QVariant data( const QModelIndex & index, int role = Qt::DisplayRole ) const override final; + bool setData( const QModelIndex & index, const QVariant & value, int role = Qt::EditRole ) override final; + bool removeRows( int row, int count, const QModelIndex & parent ) override final; - // generic load and save to and from QIODevice; implements BaseModel + // end QAbstractItemModel + + // BaseModel + + void clear() override final; bool load( QIODevice & device ) override final; bool save( QIODevice & device ) const override final; + QString getVersion() const override final { return version2string( version ); } + quint32 getVersionNumber() const override final { return version; } + + template T get( const QModelIndex & index ) const; + template bool set( const QModelIndex & index, const T & d ); + + template T get( const QModelIndex & parent, const QString & name ) const; + template bool set( const QModelIndex & parent, const QString & name, const T & v ); + + // end BaseModel + //! Load from QIODevice and index - bool load( QIODevice & device, const QModelIndex & ); + bool loadIndex( QIODevice & device, const QModelIndex & ); //! Save to QIODevice and index - bool save( QIODevice & device, const QModelIndex & ) const; + bool saveIndex( QIODevice & device, const QModelIndex & ) const; + //! Resets the model to its original state in any attached views. + void reset(); + + //! Evaluate the condition and version expressions for a NifItem + bool evalCondition( NifItem * item, bool chkParents = false ) const; + //! Invalidate the conditions of the item and its children recursively + void invalidateConditions( NifItem * item, bool refresh = true ); + void invalidateConditions( const QModelIndex & index, bool refresh = true ); + //! Invalidate only the conditions of the items dependent on this item + void invalidateDependentConditions( NifItem * item ); + void invalidateDependentConditions( const QModelIndex & index ); //! Loads a model and maps links bool loadAndMapLinks( QIODevice & device, const QModelIndex &, const QMap & map ); @@ -86,13 +126,13 @@ class NifModel final : public BaseModel //! Returns the estimated file size of the stream int blockSize( NifItem * parent, NifSStream & stream ) const; - //! Checks if the specified file contains the specified block ID in its header and is of the specified version - /*! + /*! Checks if the specified file contains the specified block ID in its header and is of the specified version + * * Note that it will not open the full file to look for block types, only the header * - * \param filepath The nif to check - * \param blockId The block to check for - * \param The version to check for + * @param filepath The NIF to check + * @param blockId The block to check for + * @param version The version to check for */ bool earlyRejection( const QString & filepath, const QString & blockId, quint32 version ); @@ -110,7 +150,7 @@ class NifModel final : public BaseModel bool holdUpdates( bool value ); //! Insert or append ( row == -1 ) a new NiBlock - QModelIndex insertNiBlock( const QString & identifier, int row = -1, bool fast = false ); + QModelIndex insertNiBlock( const QString & identifier, int row = -1 ); //! Remove a block from the list void removeNiBlock( int blocknum ); //! Move a block in the list @@ -121,29 +161,41 @@ class NifModel final : public BaseModel QString getBlockType( const QModelIndex & ) const; //! Return the block number int getBlockNumber( const QModelIndex & ) const; - //! Returns the parent block - /** - * \param The model index to get the parent of - * \param name Optional: the type to check for - * \return The index of the parent block + + /*! Get the NiBlock at a given index + * + * @param idx The model index to get the parent of + * @param name Optional: the type to check for */ QModelIndex getBlock( const QModelIndex & idx, const QString & name = QString() ) const; - //! Returns the parent block or header - QModelIndex getBlockOrHeader( const QModelIndex & ) const; - //! Get the NiBlock at a given index - /** - * \param x The index to get the block for - * \param name Optional: the type to check for + + /*! Get the NiBlock at a given index + * + * @param x The integer index of the block + * @param name Optional: the type to check for */ QModelIndex getBlock( int x, const QString & name = QString() ) const; + + //! Returns the parent block or header + QModelIndex getBlockOrHeader( const QModelIndex & ) const; + //! Get the number of NiBlocks int getBlockCount() const; - //! Check if a given index is a NiBlock - /** - * \param index The index to check - * \param name Optional: the type to check for + + /*! Check if a given index is a NiBlock + * + * @param index The index to check + * @param name Optional: the type to check for */ bool isNiBlock( const QModelIndex & index, const QString & name = QString() ) const; + + /*! Check if a given index is a NiBlock + * + * @param index The index to check + * @param names A list of names to check + */ + bool isNiBlock( const QModelIndex & index, const QStringList & names ) const; + //! Returns a list with all known NiXXX ids () static QStringList allNiBlocks(); //! Determine if a value is a NiBlock identifier (). @@ -153,26 +205,33 @@ class NifModel final : public BaseModel //! Moves all niblocks from this nif to another nif, returns a map which maps old block numbers to new block numbers QMap moveAllNiBlocks( NifModel * targetnif, bool update = true ); //! Convert a block from one type to another - void convertNiBlock( const QString & identifier, const QModelIndex & index, bool fast = false ); + void convertNiBlock( const QString & identifier, const QModelIndex & index ); void insertType( const QModelIndex & parent, const NifData & data, int atRow ); - // return the root blocks QList getRootLinks() const; - // return the list of block links QList getChildLinks( int block ) const; QList getParentLinks( int block ) const; - // return the parent block number or none (-1) if there is no parent or if there are multiple parents + + /*! Get parent + * @return Parent block number or -1 if there are zero or multiple parents. + */ int getParent( int block ) const; - // is it a child or parent link? + /*! Get parent + * @return Parent block number or -1 if there are zero or multiple parents. + */ + int getParent( const QModelIndex & index ) const; + + //! Is it a child or parent link? bool isLink( const QModelIndex & index, bool * ischildLink = 0 ) const; //! Return a block number if the index is a valid link qint32 getLink( const QModelIndex & index ) const; - //! Get the block number of a link - /** - * @param parent The parent of the link - * @param name The name of the link + + /*! Get the block number of a link + * + * @param parent The parent of the link + * @param name The name of the link */ int getLink( const QModelIndex & parent, const QString & name ) const; QVector getLinkArray( const QModelIndex & array ) const; @@ -186,37 +245,28 @@ class NifModel final : public BaseModel //! Is name a compound type? static bool isCompound( const QString & name ); + //! Is compound of fixed size/condition? (Array optimization) + static bool isFixedCompound( const QString & name ); //! Is name an ancestor identifier ()? static bool isAncestor( const QString & name ); //! Is name a NiBlock identifier ( or )? - bool isAncestorOrNiBlock( const QString & name ) const override final; // virtual so not static + bool isAncestorOrNiBlock( const QString & name ) const override final; //! Returns true if name inherits ancestor. - bool inherits( const QString & name, const QString & ancestor ) const override final; // virtual so not static + bool inherits( const QString & name, const QString & ancestor ) const override final; // returns true if the block containing index inherits ancestor bool inherits( const QModelIndex & index, const QString & ancestor ) const; - // is this version supported ? + //! Is this version supported? static bool isVersionSupported( quint32 ); - // version conversion static QString version2string( quint32 ); static quint32 version2number( const QString & ); - //! Check whether the current nif file version lies in the range [since, until] + //! Check whether the current NIF file version lies in the range [since, until] bool checkVersion( quint32 since, quint32 until ) const; - QString getVersion() const { return version2string( version ); } - quint32 getVersionNumber() const { return version; } quint32 getUserVersion() const { return get( getHeader(), "User Version" ); } - - // QAbstractModel interface - QVariant data( const QModelIndex & index, int role = Qt::DisplayRole ) const override final; - bool setData( const QModelIndex & index, const QVariant & value, int role = Qt::EditRole ) override final; - //! Resets the model to its original state in any attached views. - void reset(); - - // removes an item from the model - bool removeRows( int row, int count, const QModelIndex & parent ) override final; + quint32 getUserVersion2() const { return get( getHeader(), "User Version 2" ); } QString string( const QModelIndex & index, bool extraInfo = false ) const; QString string( const QModelIndex & index, const QString & name, bool extraInfo = false ) const; @@ -224,51 +274,71 @@ class NifModel final : public BaseModel bool assignString( const QModelIndex & index, const QString & string, bool replace = false ); bool assignString( const QModelIndex & index, const QString & name, const QString & string, bool replace = false ); + //! Create and return delegate for SpellBook + static QAbstractItemDelegate * createDelegate( QObject * parent, SpellBookPtr book ); - // BaseModel Overrides - template T get( const QModelIndex & index ) const; - template bool set( const QModelIndex & index, const T & d ); + //! Undo Stack for changes to NifModel + QUndoStack * undoStack; - template T get( const QModelIndex & parent, const QString & name ) const; - template bool set( const QModelIndex & parent, const QString & name, const T & v ); - - - static QAbstractItemDelegate * createDelegate( class SpellBook * ); +public slots: + void updateSettings(); signals: void linksChanged(); void lodSliderChanged( bool ) const; + void beginUpdateHeader(); protected: - void insertAncestor( NifItem * parent, const QString & identifier, int row = -1 ); - void insertType( NifItem * parent, const NifData & data, int row = -1 ); - NifItem * insertBranch( NifItem * parent, const NifData & data, int row = -1 ); - - bool updateArrayItem( NifItem * array, bool fast ) override final; - bool updateByteArrayItem( NifItem * array, bool fast ); - bool updateArrays( NifItem * parent, bool fast ); + // BaseModel - NifItem * getHeaderItem() const; - NifItem * getFooterItem() const; - NifItem * getBlockItem( int ) const; NifItem * getItem( NifItem * parent, const QString & name ) const override final; - bool load( NifItem * parent, NifIStream & stream, bool fast = true ); - bool save( NifItem * parent, NifOStream & stream ) const; - bool fileOffset( NifItem * parent, NifItem * target, NifSStream & stream, int & ofs ) const; - bool setItemValue( NifItem * item, const NifValue & v ) override final; - bool itemIsLink( NifItem * item, bool * ischildLink = 0 ) const; - int getBlockNumber( NifItem * item ) const; - - bool setHeaderString( const QString & ) override final; + bool updateArrayItem( NifItem * array ) override final; QString ver2str( quint32 v ) const override final { return version2string( v ); } quint32 str2ver( QString s ) const override final { return version2number( s ); } bool evalVersion( NifItem * item, bool chkParents = false ) const override final; + bool setHeaderString( const QString & ) override final; + + template T get( NifItem * parent, const QString & name ) const; + template T get( NifItem * item ) const; + template bool set( NifItem * parent, const QString & name, const T & d ); + template bool set( NifItem * item, const T & d ); + + // end BaseModel + + bool loadItem( NifItem * parent, NifIStream & stream ); + bool loadHeader( NifItem * parent, NifIStream & stream ); + bool saveItem( NifItem * parent, NifOStream & stream ) const; + bool fileOffset( NifItem * parent, NifItem * target, NifSStream & stream, int & ofs ) const; + + NifItem * getHeaderItem() const; + NifItem * getFooterItem() const; + NifItem * getBlockItem( int ) const; + + int getBlockNumber( NifItem * item ) const; + bool itemIsLink( NifItem * item, bool * ischildLink = 0 ) const; + + void insertAncestor( NifItem * parent, const QString & identifier, int row = -1 ); + void insertType( NifItem * parent, const NifData & data, int row = -1 ); + NifItem * insertBranch( NifItem * parent, const NifData & data, int row = -1 ); + + bool updateByteArrayItem( NifItem * array ); + bool updateArrays( NifItem * parent ); + + void updateLinks( int block = -1 ); + void updateLinks( int block, NifItem * parent ); + void checkLinks( int block, QStack & parents ); + void adjustLinks( NifItem * parent, int block, int delta ); + void mapLinks( NifItem * parent, const QMap & map ); + + static void updateStrings( NifModel * src, NifModel * tgt, NifItem * item ); + bool assignString( NifItem * parent, const QString & string, bool replace = false ); + //! NIF file version quint32 version; @@ -288,42 +358,73 @@ class NifModel final : public BaseModel }; UpdateType needUpdates; - void updateLinks( int block = -1 ); - void updateLinks( int block, NifItem * parent ); - void checkLinks( int block, QStack & parents ); - void adjustLinks( NifItem * parent, int block, int delta ); - void mapLinks( NifItem * parent, const QMap & map ); void updateModel( UpdateType value = utAll ); - static void updateStrings( NifModel * src, NifModel * tgt, NifItem * item ); - bool assignString( NifItem * parent, const QString & string, bool replace = false ); - + //! Parse the XML file using a NifXmlHandler + static QString parseXmlDescription( const QString & filename ); // XML structures static QList supportedVersions; + static QHash compounds; + static QHash fixedCompounds; + static QHash blocks; - static QHash compounds; - static QHash blocks; +private: + struct Settings + { + QString startupVersion; + int userVersion; + int userVersion2; + } cfg; +}; - //! Parse the XML file using a NifXmlHandler - static QString parseXmlDescription( const QString & filename ); - // Get and Set template overloads from base model - template T get( NifItem * parent, const QString & name ) const; - template T get( NifItem * item ) const; - template bool set( NifItem * parent, const QString & name, const T & d ); - template bool set( NifItem * item, const T & d ); +//! Helper class for evaluating condition expressions +class NifModelEval +{ +public: + NifModelEval( const NifModel * model, const NifItem * item ); - friend class NifXmlHandler; - friend class NifModelEval; - friend class NifOStream; -}; // class NifModel + QVariant operator()( const QVariant & v ) const; +private: + const NifModel * model; + const NifItem * item; +}; + + +class ChangeValueCommand : public QUndoCommand +{ +public: + ChangeValueCommand( const QModelIndex & index, const QVariant & value, const QString & valueString, const QString & valueType, NifModel * model ); + void redo() override; + void undo() override; +private: + NifModel * nif; + QVariant newValue, oldValue; + QModelIndex idx; +}; + + +class ToggleCheckBoxListCommand : public QUndoCommand +{ +public: + ToggleCheckBoxListCommand( const QModelIndex & index, const QVariant & value, const QString & valueType, NifModel * model ); + void redo() override; + void undo() override; +private: + NifModel * nif; + QVariant newValue, oldValue; + QModelIndex idx; +}; + + +// Inlines inline QStringList NifModel::allNiBlocks() { QStringList lst; - for ( NifBlock * blk : blocks ) { + for ( NifBlockPtr blk : blocks ) { if ( !blk->abstract ) lst.append( blk->id ); } @@ -337,13 +438,13 @@ inline bool NifModel::isAncestorOrNiBlock( const QString & name ) const inline bool NifModel::isNiBlock( const QString & name ) { - NifBlock * blk = blocks.value( name ); + NifBlockPtr blk = blocks.value( name ); return blk && !blk->abstract; } inline bool NifModel::isAncestor( const QString & name ) { - NifBlock * blk = blocks.value( name ); + NifBlockPtr blk = blocks.value( name ); return blk && blk->abstract; } @@ -352,6 +453,11 @@ inline bool NifModel::isCompound( const QString & name ) return compounds.contains( name ); } +inline bool NifModel::isFixedCompound( const QString & name ) +{ + return fixedCompounds.contains( name ); +} + inline bool NifModel::isVersionSupported( quint32 v ) { return supportedVersions.contains( v ); @@ -382,13 +488,13 @@ inline bool NifModel::itemIsLink( NifItem * item, bool * isChildLink ) const inline bool NifModel::checkVersion( quint32 since, quint32 until ) const { - return ( ( since == 0 || since <= version ) - && ( until == 0 || version <= until ) - ); + return (( since == 0 || since <= version ) && ( until == 0 || version <= until )); } -// Overrides for get and set templates. +// Templates + + template inline T NifModel::get( const QModelIndex & index ) const { return BaseModel::get( index ); @@ -411,26 +517,36 @@ template inline T NifModel::get( const QModelIndex & parent, const template inline bool NifModel::set( const QModelIndex & index, const T & d ) { - return BaseModel::set( index, d ); + bool result = BaseModel::set( index, d ); + if ( result ) + invalidateDependentConditions( index ); + return result; } template inline bool NifModel::set( NifItem * item, const T & d ) { - return BaseModel::set( item, d ); + bool result = BaseModel::set( item, d ); + if ( result ) + invalidateDependentConditions( item ); + return result; } template inline bool NifModel::set( const QModelIndex & parent, const QString & name, const T & d ) { - return BaseModel::set( parent, name, d ); + bool result = BaseModel::set( parent, name, d ); + if ( result ) + invalidateDependentConditions( getIndex( parent, name ) ); + return result; } template inline bool NifModel::set( NifItem * parent, const QString & name, const T & d ) { - return BaseModel::set( parent, name, d ); + bool result = BaseModel::set( parent, name, d ); + if ( result ) + invalidateDependentConditions( getItem( parent, name ) ); + return result; } - -// QString overloads for the get and set templates template <> inline QString NifModel::get( const QModelIndex & index ) const { return this->string( index ); diff --git a/src/nifproxy.cpp b/src/nifproxy.cpp index 2293b6633..e0150b03c 100644 --- a/src/nifproxy.cpp +++ b/src/nifproxy.cpp @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -57,7 +57,7 @@ class NifProxyItem if ( item->block() == link ) return item; } - return 0; + return nullptr; } int rowLink( int link ) @@ -176,7 +176,7 @@ class NifProxyItem return x; } - return 0; + return nullptr; } void findAllItems( int b, QList & list ) @@ -262,7 +262,9 @@ void NifProxyModel::updateRoot( bool fast ) //qDebug() << "proxy update top level"; - for ( NifProxyItem * item : root->childItems ) { + // Make a copy to iterate over + auto items = root->childItems; + for ( NifProxyItem * item : items ) { if ( !nif->getRootLinks().contains( item->block() ) ) { int at = root->rowLink( item->block() ); @@ -330,7 +332,9 @@ void NifProxyModel::updateItem( NifProxyItem * item, bool fast ) if ( !parents.contains( child->block() ) ) { updateItem( child, fast ); } else { - qWarning() << tr( "infinite recursing link construct detected" ) << item->block() << "->" << child->block(); + Message::append( tr( "Warnings were generated while reading NIF file." ), + tr( "infinite recursive link construct detected %1 -> %2" ).arg( item->block() ).arg( child->block() ) + ); } } for ( const auto l : nif->getParentLinks( item->block() ) ) { diff --git a/src/nifproxy.h b/src/nifproxy.h index c96fe22dc..686d17fa9 100644 --- a/src/nifproxy.h +++ b/src/nifproxy.h @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/src/nifskope.cpp b/src/nifskope.cpp index 8f7cfa2d2..43e615976 100644 --- a/src/nifskope.cpp +++ b/src/nifskope.cpp @@ -1,8 +1,8 @@ -/***** BEGIN LICENSE BLOCK ***** +/***** BEGIN LICENSE BLOCK ***** BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -32,52 +32,53 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "nifskope.h" #include "version.h" -#include "options.h" +#include "settings.h" + +#include "ui_nifskope.h" +#include "ui/about_dialog.h" #include "glview.h" +#include "gl/glscene.h" #include "kfmmodel.h" #include "nifmodel.h" #include "nifproxy.h" #include "spellbook.h" -#include "widgets/copyfnam.h" #include "widgets/fileselect.h" #include "widgets/nifview.h" #include "widgets/refrbrowser.h" #include "widgets/inspect.h" -#include "widgets/xmlcheck.h" + #include #include +#include #include -#include +#include +#include #include #include #include -#include -#include #include #include -#include #include -#include -#include +#include #include +#include #include -#include #include #include #include #include #include #include +#include #include #include +#include -#ifdef FSENGINE +#include #include -FSManager * fsmanager = nullptr; -#endif #ifdef WIN32 # define WINDOWS_LEAN_AND_MEAN @@ -85,7 +86,56 @@ FSManager * fsmanager = nullptr; #endif -//! \file nifskope.cpp The main file for NifSkope +//! @file nifskope.cpp The main file for %NifSkope + +SettingsDialog * NifSkope::options; + +const QList> NifSkope::filetypes = { + // NIF types + { "NIF", "nif" }, { "Bethesda Terrain", "btr" }, { "Bethesda Terrain Object", "bto" }, + // KF types + { "Keyframe", "kf" }, { "Keyframe Animation", "kfa" }, { "Keyframe Motion", "kfm" }, + // Miscellaneous NIF types + { "NIFCache", "nifcache" }, { "TEXCache", "texcache" }, { "PCPatch", "pcpatch" }, { "JMI", "jmi" } +}; + +QStringList NifSkope::fileExtensions() +{ + QStringList fileExts; + for ( int i = 0; i < filetypes.size(); i++ ) { + fileExts << filetypes.at( i ).second; + } + + return fileExts; +} + +QString NifSkope::fileFilter( const QString & ext ) +{ + QString filter; + + for ( int i = 0; i < filetypes.size(); i++ ) { + if ( filetypes.at( i ).second == ext ) { + filter = QString( "%1 (*.%2)" ).arg( filetypes.at( i ).first ).arg( filetypes.at( i ).second ); + } + } + + return filter; +} + +QString NifSkope::fileFilters( bool allFiles ) +{ + QStringList filters; + + if ( allFiles ) { + filters << QString( "All Files (*.%1)" ).arg( fileExtensions().join( " *." ) ); + } + + for ( int i = 0; i < filetypes.size(); i++ ) { + filters << QString( "%1 (*.%2)" ).arg( filetypes.at( i ).first ).arg( filetypes.at( i ).second ); + } + + return filters.join( ";;" ); +} /* @@ -93,526 +143,251 @@ FSManager * fsmanager = nullptr; */ NifSkope::NifSkope() - : QMainWindow(), selecting( false ), initialShowEvent( true ) + : QMainWindow(), ui( new Ui::MainWindow ) { - // init UI parts + // Init UI + ui->setupUi( this ); + + qApp->installEventFilter( this ); + + // Init Dialogs aboutDialog = new AboutDialog( this ); + if ( !options ) + options = new SettingsDialog; - // migrate settings from older versions of NifSkope + // Migrate settings from older versions of NifSkope migrateSettings(); - // create a new nif - nif = new NifModel( this ); - connect( nif, &NifModel::sigMessage, this, &NifSkope::dispatchMessage ); + // Update Settings struct from registry + updateSettings(); - SpellBook * book = new SpellBook( nif, QModelIndex(), this, SLOT( select( const QModelIndex & ) ) ); + // Create models + /* ********************** */ - // create a new hierarchical proxy nif + nif = new NifModel( this ); proxy = new NifProxyModel( this ); proxy->setModel( nif ); - // create a new kfm model + nifEmpty = new NifModel( this ); + proxyEmpty = new NifProxyModel( this ); + + nif->setMessageMode( BaseModel::UserMessage ); + + // Setup QUndoStack + nif->undoStack = new QUndoStack( this ); + + indexStack = new QUndoStack( this ); + + // Setup Window Modified on data change + connect( nif, &NifModel::dataChanged, [this]( const QModelIndex &, const QModelIndex & ) { + // Only if UI is enabled (prevents asterisk from flashing during save/load) + if ( !windowTitle().isEmpty() && isEnabled() ) + setWindowModified( true ); + } ); + kfm = new KfmModel( this ); - connect( kfm, &KfmModel::sigMessage, this, &NifSkope::dispatchMessage ); + kfmEmpty = new KfmModel( this ); + + book = SpellBookPtr( new SpellBook( nif, QModelIndex(), this, SLOT( select( const QModelIndex & ) ) ) ); + // Setup Views + /* ********************** */ - // this view shows the block list - list = new NifTreeView; + // Block List + list = ui->list; list->setModel( proxy ); - list->setItemDelegate( nif->createDelegate( book ) ); - list->header()->setStretchLastSection( true ); - list->header()->setMinimumSectionSize( 100 ); + list->setSortingEnabled( false ); + list->setItemDelegate( nif->createDelegate( this, book ) ); list->installEventFilter( this ); - connect( list, &NifTreeView::sigCurrentIndexChanged, this, &NifSkope::select ); - connect( list, &NifTreeView::customContextMenuRequested, this, &NifSkope::contextMenu ); - - // this view shows the whole nif file or the block details - tree = new NifTreeView; + // Block Details + tree = ui->tree; tree->setModel( nif ); - tree->setItemDelegate( nif->createDelegate( book ) ); - tree->header()->setStretchLastSection( false ); + tree->setSortingEnabled( false ); + tree->setItemDelegate( nif->createDelegate( this, book ) ); tree->installEventFilter( this ); + tree->header()->moveSection( 1, 2 ); - connect( tree, &NifTreeView::sigCurrentIndexChanged, this, &NifSkope::select ); - connect( tree, &NifTreeView::customContextMenuRequested, this, &NifSkope::contextMenu ); - + // Header Details + header = ui->header; + header->setModel( nif ); + header->setItemDelegate( nif->createDelegate( this, book ) ); + header->installEventFilter( this ); + header->header()->moveSection( 1, 2 ); - // this view shows the whole kfm file - kfmtree = new NifTreeView; + // KFM + kfmtree = ui->kfmtree; kfmtree->setModel( kfm ); - kfmtree->setItemDelegate( kfm->createDelegate() ); - kfmtree->header()->setStretchLastSection( false ); + kfmtree->setItemDelegate( kfm->createDelegate( this ) ); kfmtree->installEventFilter( this ); - connect( kfmtree, &NifTreeView::customContextMenuRequested, this, &NifSkope::contextMenu ); + // Help Browser + refrbrwsr = ui->refrBrowser; + refrbrwsr->setNifModel( nif ); - // this browser shows the reference of current node - refrbrwsr = new ReferenceBrowser; + // Archive Browser + bsaView = ui->bsaView; + connect( bsaView, &QTreeView::doubleClicked, this, &NifSkope::openArchiveFile ); - refrbrwsr->setNifModel( nif ); - connect( tree, &NifTreeView::sigCurrentIndexChanged, refrbrwsr, &ReferenceBrowser::browse ); + bsaModel = new BSAModel( this ); + bsaProxyModel = new BSAProxyModel( this ); -#ifdef EDIT_ON_ACTIVATE - connect( list, &NifTreeView::activated, - list, static_cast(&NifTreeView::edit) ); - connect( tree, &NifTreeView::activated, - tree, static_cast(&NifTreeView::edit) ); - connect( kfmtree, &NifTreeView::activated, - kfmtree, static_cast(&NifTreeView::edit) ); -#endif + // Empty Model for swapping out before model fill + emptyModel = new QStandardItemModel( this ); + // Connect models with views + /* ********************** */ - // open gl - setCentralWidget( ogl = GLView::create() ); + connect( list, &NifTreeView::sigCurrentIndexChanged, this, &NifSkope::select ); + connect( list, &NifTreeView::customContextMenuRequested, this, &NifSkope::contextMenu ); + connect( tree, &NifTreeView::sigCurrentIndexChanged, this, &NifSkope::select ); + connect( tree, &NifTreeView::customContextMenuRequested, this, &NifSkope::contextMenu ); + connect( tree, &NifTreeView::sigCurrentIndexChanged, refrbrwsr, &ReferenceBrowser::browse ); + connect( header, &NifTreeView::customContextMenuRequested, this, &NifSkope::contextMenu ); + connect( kfmtree, &NifTreeView::customContextMenuRequested, this, &NifSkope::contextMenu ); + + // Create GLView + /* ********************** */ + + ogl = GLView::create( this ); + ogl->setObjectName( "OGL1" ); ogl->setNif( nif ); - connect( ogl, &GLView::clicked, this, &NifSkope::select ); - connect( ogl, &GLView::customContextMenuRequested, this, &NifSkope::contextMenu ); + ogl->installEventFilter( this ); -#ifndef DISABLE_INSPECTIONVIEWER - // this browser shows the state of the current selected item - // currently for showing transform state of nodes at current time + // Create InspectView + /* ********************** */ + inspect = new InspectView; inspect->setNifModel( nif ); inspect->setScene( ogl->getScene() ); - connect( tree, &NifTreeView::sigCurrentIndexChanged, inspect, &InspectView::updateSelection); - connect( ogl, &GLView::sigTime, inspect, &InspectView::updateTime ); - connect( ogl, &GLView::paintUpdate, inspect, &InspectView::refresh ); -#endif - // actions - - aSanitize = new QAction( tr( "&Auto Sanitize before Save" ), this ); - aSanitize->setCheckable( true ); - aSanitize->setChecked( true ); - aLoadXML = new QAction( tr( "Reload &XML" ), this ); - connect( aLoadXML, &QAction::triggered, this, &NifSkope::loadXML ); - aReload = new QAction( tr( "&Reload XML + Nif" ), this ); - aReload->setShortcut( Qt::ALT + Qt::Key_X ); - connect( aReload, &QAction::triggered, this, &NifSkope::reload ); - aWindow = new QAction( tr( "&New Window" ), this ); - aWindow->setShortcut( QKeySequence::New ); - connect( aWindow, &QAction::triggered, this, &NifSkope::sltWindow ); - aShredder = new QAction( tr( "XML Checker" ), this ); - connect( aShredder, &QAction::triggered, this, &NifSkope::sltShredder ); - aQuit = new QAction( tr( "&Quit" ), this ); - connect( aQuit, &QAction::triggered, this, &NifSkope::close ); - - aList = new QAction( tr( "Show Blocks in List" ), this ); - aList->setCheckable( true ); - aList->setChecked( list->model() == nif ); - - aHierarchy = new QAction( tr( "Show Blocks in Tree" ), this ); - aHierarchy->setCheckable( true ); - aHierarchy->setChecked( list->model() == proxy ); - - gListMode = new QActionGroup( this ); - connect( gListMode, &QActionGroup::triggered, this, &NifSkope::setListMode ); - gListMode->addAction( aList ); - gListMode->addAction( aHierarchy ); - gListMode->setExclusive( true ); - - aCondition = new QAction( tr( "Hide Version Mismatched Rows" ), this ); - aCondition->setCheckable( true ); - aCondition->setChecked( false ); - - aRCondition = new QAction( tr( "Realtime Row Version Updating (slow)" ), this ); - aRCondition->setCheckable( true ); - aRCondition->setChecked( false ); - aRCondition->setEnabled( false ); - - connect( aCondition, &QAction::toggled, aRCondition, &QAction::setEnabled ); - connect( aRCondition, &QAction::toggled, tree, &NifTreeView::setRealTime ); - connect( aRCondition, &QAction::toggled, kfmtree, &NifTreeView::setRealTime ); - - // use toggled to enable startup values to take effect - connect( aCondition, &QAction::toggled, tree, &NifTreeView::setEvalConditions ); - connect( aCondition, &QAction::toggled, kfmtree, &NifTreeView::setEvalConditions ); - - aSelectFont = new QAction( tr( "Select Font ..." ), this ); - connect( aSelectFont, &QAction::triggered, this, &NifSkope::sltSelectFont ); - - - /* help menu */ - - aHelpWebsite = new QAction( tr( "NifSkope Documentation && &Tutorials" ), this ); - aHelpWebsite->setData( QUrl( "http://niftools.sourceforge.net/wiki/index.php/NifSkope" ) ); - connect( aHelpWebsite, &QAction::triggered, this, &NifSkope::openURL ); - - aHelpForum = new QAction( tr( "NifSkope Help && Bug Report &Forum" ), this ); - aHelpForum->setData( QUrl( "http://niftools.sourceforge.net/forum/viewforum.php?f=24" ) ); - connect( aHelpForum, &QAction::triggered, this, &NifSkope::openURL ); - - aNifToolsWebsite = new QAction( tr( "NifTools &Wiki" ), this ); - aNifToolsWebsite->setData( QUrl( "http://niftools.sourceforge.net" ) ); - connect( aNifToolsWebsite, &QAction::triggered, this, &NifSkope::openURL ); - - aNifToolsDownloads = new QAction( tr( "NifTools &Downloads" ), this ); - aNifToolsDownloads->setData( QUrl( "http://sourceforge.net/project/showfiles.php?group_id=149157" ) ); - connect( aNifToolsDownloads, &QAction::triggered, this, &NifSkope::openURL ); - - aNifSkope = new QAction( tr( "About &NifSkope" ), this ); - // TODO: Can't seem to figure out correct cast for new signal syntax - connect( aNifSkope, SIGNAL( triggered() ), aboutDialog, SLOT( open() ) ); - - aAboutQt = new QAction( tr( "About &Qt" ), this ); - connect( aAboutQt, &QAction::triggered, qApp, &QApplication::aboutQt ); - -#ifdef FSENGINE - - if ( fsmanager ) { - aResources = new QAction( tr( "Resource Files" ), this ); - connect( aResources, &QAction::triggered, fsmanager, &FSManager::selectArchives ); - } else { - aResources = nullptr; - } - -#endif - - - // dock widgets - - dRefr = new QDockWidget( tr( "Interactive Help" ) ); - dRefr->setObjectName( "RefrDock" ); - dRefr->setWidget( refrbrwsr ); - dRefr->toggleViewAction()->setShortcut( Qt::Key_F1 ); - dRefr->toggleViewAction()->setChecked( false ); - dRefr->setVisible( false ); - - dList = new QDockWidget( tr( "Block List" ) ); - dList->setObjectName( "ListDock" ); - dList->setWidget( list ); - dList->toggleViewAction()->setShortcut( Qt::Key_F2 ); - connect( dList->toggleViewAction(), &QAction::triggered, tree, &NifTreeView::clearRootIndex ); - - dTree = new QDockWidget( tr( "Block Details" ) ); - dTree->setObjectName( "TreeDock" ); - dTree->setWidget( tree ); - dTree->toggleViewAction()->setShortcut( Qt::Key_F3 ); - - dKfm = new QDockWidget( tr( "KFM" ) ); - dKfm->setObjectName( "KfmDock" ); - dKfm->setWidget( kfmtree ); - dKfm->toggleViewAction()->setShortcut( Qt::Key_F4 ); - dKfm->toggleViewAction()->setChecked( false ); - dKfm->setVisible( false ); - -#ifndef DISABLE_INSPECTIONVIEWER - dInsp = new QDockWidget( tr( "Inspect" ) ); - dInsp->setObjectName( "InspectDock" ); - dInsp->setWidget( inspect ); - //dInsp->toggleViewAction()->setShortcut( Qt::ALT + Qt::Key_Enter ); - dInsp->toggleViewAction()->setChecked( false ); - dInsp->setVisible( false ); -#endif - - addDockWidget( Qt::BottomDockWidgetArea, dRefr ); - addDockWidget( Qt::LeftDockWidgetArea, dList ); - addDockWidget( Qt::BottomDockWidgetArea, dTree ); - addDockWidget( Qt::RightDockWidgetArea, dKfm ); - -#ifndef DISABLE_INSPECTIONVIEWER - addDockWidget( Qt::RightDockWidgetArea, dInsp, Qt::Vertical ); -#endif - /* ******** */ - - // tool bars - - // begin Load & Save toolbar - tool = new QToolBar( tr( "Load && Save" ) ); - tool->setObjectName( "toolbar" ); - tool->setAllowedAreas( Qt::TopToolBarArea | Qt::BottomToolBarArea ); - - QStringList fileExtensions{ - "All Files (*.nif *.kf *.kfa *.kfm *.nifcache *.texcache *.pcpatch *.jmi)", - "NIF (*.nif)", "Keyframe (*.kf)", "Keyframe Animation (*.kfa)", "Keyframe Motion (*.kfm)", - "NIFCache (*.nifcache)", "TEXCache (*.texcache)", "PCPatch (*.pcpatch)", "JMI (*.jmi)" - }; - - // create the load portion of the toolbar - aLineLoad = tool->addWidget( lineLoad = new FileSelector( FileSelector::LoadFile, tr( "&Load..." ), QBoxLayout::RightToLeft, QKeySequence::Open ) ); - lineLoad->setFilter( fileExtensions ); - connect( lineLoad, &FileSelector::sigActivated, this, static_cast(&NifSkope::load) ); - - // add the Load<=>Save filename copy widget - CopyFilename * cpFilename = new CopyFilename( this ); - cpFilename->setObjectName( "fileCopyWidget" ); - connect( cpFilename, &CopyFilename::leftTriggered, this, &NifSkope::copyFileNameSaveLoad ); - connect( cpFilename, &CopyFilename::rightTriggered, this, &NifSkope::copyFileNameLoadSave ); - aCpFileName = tool->addWidget( cpFilename ); - - // create the save portion of the toolbar - aLineSave = tool->addWidget( lineSave = new FileSelector( FileSelector::SaveFile, tr( "&Save As..." ), QBoxLayout::LeftToRight, QKeySequence::Save ) ); - lineSave->setFilter( fileExtensions ); - connect( lineSave, &FileSelector::sigActivated, this, static_cast(&NifSkope::save) ); - -#ifdef Q_OS_LINUX - // extra whitespace for linux - QWidget * extraspace = new QWidget(); - extraspace->setFixedWidth( 5 ); - tool->addWidget( extraspace ); -#endif - - addToolBar( Qt::TopToolBarArea, tool ); - // end Load & Save toolbar + // Create Progress Bar + /* ********************** */ + progress = new QProgressBar( ui->statusbar ); + progress->setMaximumSize( 200, 18 ); + progress->setVisible( false ); + + // Process progress events + connect( nif, &NifModel::sigProgress, [this]( int c, int m ) { + progress->setRange( 0, m ); + progress->setValue( c ); + qApp->processEvents(); + } ); + + /* + * UI Init + * ********************** + */ + + // Init Scene and View + graphicsScene = new QGraphicsScene( this ); + graphicsView = new GLGraphicsView( this ); + graphicsView->setScene( graphicsScene ); + graphicsView->setRenderHint( QPainter::Antialiasing ); + graphicsView->setRenderHint( QPainter::SmoothPixmapTransform ); + graphicsView->setCacheMode( QGraphicsView::CacheNone ); + graphicsView->setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); + graphicsView->setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); + //graphicsView->setOptimizationFlags( QGraphicsView::DontSavePainterState | QGraphicsView::DontAdjustForAntialiasing ); + + // Set central widget and viewport + setCentralWidget( graphicsView ); + graphicsView->setViewport( ogl ); + graphicsView->setViewportUpdateMode( QGraphicsView::FullViewportUpdate ); + + setContextMenuPolicy( Qt::NoContextMenu ); + + // Resize timer for eventFilter() + isResizing = false; + resizeTimer = new QTimer( this ); + resizeTimer->setSingleShot( true ); + connect( resizeTimer, &QTimer::timeout, this, &NifSkope::resizeDone ); + + // Set Actions + initActions(); + + // Dock Widgets + initDockWidgets(); + + // Toolbars + initToolBars(); + + // Menus + initMenu(); + + // Connections (that are required to load after all other inits) + initConnections(); + + connect( options, &SettingsDialog::saveSettings, this, &NifSkope::updateSettings ); + connect( options, &SettingsDialog::localeChanged, this, &NifSkope::sltLocaleChanged ); + + connect( qApp, &QApplication::lastWindowClosed, this, &NifSkope::exitRequested ); +} - // begin OpenGL toolbars - for ( QToolBar * tb : ogl->toolbars() ) { - addToolBar( Qt::TopToolBarArea, tb ); - } - // end OpenGL toolbars - - // begin View toolbar - QToolBar * tView = new QToolBar( tr( "View" ) ); - tView->setObjectName( tr( "tView" ) ); - tView->setAllowedAreas( Qt::TopToolBarArea | Qt::BottomToolBarArea ); - QAction * aResetBlockDetails = new QAction( tr( "Reset Block Details" ), this ); - connect( aResetBlockDetails, &QAction::triggered, this, &NifSkope::sltResetBlockDetails ); - tView->addAction( aResetBlockDetails ); - tView->addSeparator(); - tView->addAction( dRefr->toggleViewAction() ); - tView->addAction( dList->toggleViewAction() ); - tView->addAction( dTree->toggleViewAction() ); - tView->addAction( dKfm->toggleViewAction() ); - tView->addAction( dInsp->toggleViewAction() ); - addToolBar( Qt::TopToolBarArea, tView ); - // end View toolbars - - // LOD Toolbar - QToolBar * tLOD = new QToolBar( "LOD" ); - tLOD->setObjectName( tr( "tLOD" ) ); - tLOD->setAllowedAreas( Qt::TopToolBarArea | Qt::BottomToolBarArea ); +void NifSkope::exitRequested() +{ + qApp->removeEventFilter( this ); + // Must disconnect from this signal as it's set once for each widget for some reason + disconnect( qApp, &QApplication::lastWindowClosed, this, &NifSkope::exitRequested ); - QSettings cfg; - int lodLevel = cfg.value( "GLView/LOD Level", 2 ).toInt(); - cfg.setValue( "GLView/LOD Level", lodLevel ); - - QSlider * lodSlider = new QSlider( Qt::Horizontal ); - lodSlider->setFocusPolicy( Qt::StrongFocus ); - lodSlider->setTickPosition( QSlider::TicksBelow ); - lodSlider->setTickInterval( 1 ); - lodSlider->setSingleStep( 1 ); - lodSlider->setMinimum( 0 ); - lodSlider->setMaximum( 2 ); - lodSlider->setValue( lodLevel ); - - tLOD->addWidget( lodSlider ); - tLOD->setEnabled( false ); - - connect( lodSlider, &QSlider::valueChanged, []( int value ) - { - QSettings cfg; - cfg.setValue( "GLView/LOD Level", value ); - } - ); - connect( lodSlider, &QSlider::valueChanged, Options::get(), &Options::sigChanged ); - connect( nif, &NifModel::lodSliderChanged, [tLOD]( bool enabled ) { tLOD->setEnabled( enabled ); } ); - - addToolBar( Qt::TopToolBarArea, tLOD ); - - /* ********* */ - - // menu - - // assemble the File menu - QMenu * mFile = new QMenu( tr( "&File" ) ); - mFile->addActions( lineLoad->actions() ); - mFile->addActions( lineSave->actions() ); - mFile->addSeparator(); - mFile->addMenu( mImport = new QMenu( tr( "Import" ) ) ); - mFile->addMenu( mExport = new QMenu( tr( "Export" ) ) ); - mFile->addSeparator(); - mFile->addAction( aSanitize ); - mFile->addSeparator(); - mFile->addAction( aWindow ); - mFile->addSeparator(); - mFile->addAction( aLoadXML ); - mFile->addAction( aReload ); - mFile->addAction( aShredder ); - -#ifdef FSENGINE - - if ( aResources ) { - mFile->addSeparator(); - mFile->addAction( aResources ); - } + FSManager::del(); -#endif - mFile->addSeparator(); - mFile->addAction( aQuit ); - - QMenu * mView = new QMenu( tr( "&View" ) ); - mView->addActions( tView->actions() ); - mView->addSeparator(); - QMenu * mTools = new QMenu( tr( "&Toolbars" ) ); - mView->addMenu( mTools ); - for ( QObject * o : children() ) { - QToolBar * tb = qobject_cast( o ); - - if ( tb ) - mTools->addAction( tb->toggleViewAction() ); - } - mView->addSeparator(); - QMenu * mBlockList = new QMenu( tr( "Block List" ) ); - mView->addMenu( mBlockList ); - mBlockList->addAction( aHierarchy ); - mBlockList->addAction( aList ); - QMenu * mBlockDetails = new QMenu( tr( "Block Details" ) ); - mView->addMenu( mBlockDetails ); - mBlockDetails->addAction( aCondition ); - mBlockDetails->addAction( aRCondition ); - mBlockDetails->addAction( aResetBlockDetails ); - mView->addSeparator(); - mView->addAction( aSelectFont ); - - QMenu * mAbout = new QMenu( tr( "&Help" ) ); - mAbout->addAction( dRefr->toggleViewAction() ); - mAbout->addAction( aHelpWebsite ); - mAbout->addAction( aHelpForum ); - mAbout->addSeparator(); - mAbout->addAction( aNifToolsWebsite ); - mAbout->addAction( aNifToolsDownloads ); - mAbout->addSeparator(); - mAbout->addAction( aAboutQt ); - mAbout->addAction( aNifSkope ); - - menuBar()->addMenu( mFile ); - menuBar()->addMenu( mView ); - menuBar()->addMenu( ogl->createMenu() ); - menuBar()->addMenu( book ); - menuBar()->addMenu( mAbout ); - - fillImportExportMenus(); - connect( mExport, &QMenu::triggered, this, &NifSkope::sltImportExport ); - connect( mImport, &QMenu::triggered, this, &NifSkope::sltImportExport ); - - connect( Options::get(), &Options::sigLocaleChanged, this, &NifSkope::sltLocaleChanged ); + if ( options ) + delete options; } NifSkope::~NifSkope() { + delete ui; } -void NifSkope::closeEvent( QCloseEvent * e ) -{ - QSettings settings; - save( settings ); - - QMainWindow::closeEvent( e ); -} - -//! Resize views from settings -void restoreHeader( const QString & name, const QSettings & settings, QHeaderView * header ) +void NifSkope::swapModels() { - QByteArray b = settings.value( name ).value(); - - if ( b.isEmpty() ) - return; - - QDataStream d( &b, QIODevice::ReadOnly ); - int s; - d >> s; - - if ( s != header->count() ) - return; - - for ( int c = 0; c < header->count(); c++ ) { - d >> s; - header->resizeSection( c, s ); + // Swap out the models with empty versions while loading the file + // This is so that the views do not update while loading the file + if ( tree->model() == nif ) { + list->setModel( proxyEmpty ); + tree->setModel( nifEmpty ); + header->setModel( nifEmpty ); + kfmtree->setModel( kfmEmpty ); + } else { + list->setModel( proxy ); + tree->setModel( nif ); + header->setModel( nif ); + kfmtree->setModel( kfm ); } } -void NifSkope::restore( const QSettings & settings ) +void NifSkope::updateSettings() { - restoreGeometry( settings.value( "UI/Window Geometry" ).toByteArray() ); - restoreState( settings.value( "UI/Window State" ).toByteArray(), 0x073 ); - - lineLoad->setText( settings.value( "File/Last Load", QString( "" ) ).toString() ); - lineSave->setText( settings.value( "File/Last Save", QString( "" ) ).toString() ); - aSanitize->setChecked( settings.value( "File/Auto Sanitize", true ).toBool() ); - - if ( settings.value( "UI/List Mode", "hierarchy" ).toString() == "list" ) - aList->setChecked( true ); - else - aHierarchy->setChecked( true ); - - setListMode(); - - aCondition->setChecked( settings.value( "UI/Hide Mismatched Rows", false ).toBool() ); - aRCondition->setChecked( settings.value( "UI/Realtime Condition Updating", false ).toBool() ); - restoreHeader( "UI/List Sizes", settings, list->header() ); - restoreHeader( "UI/Tree Sizes", settings, tree->header() ); - restoreHeader( "UI/Kfmtree Sizes", settings, kfmtree->header() ); + QSettings settings; - ogl->restore( settings ); + settings.beginGroup( "Settings" ); - QVariant fontVar = settings.value( "UI/View Font" ); + cfg.locale = settings.value( "Locale", "en" ).toLocale(); + cfg.suppressSaveConfirm = settings.value( "UI/Suppress Save Confirmation", false ).toBool(); - if ( fontVar.canConvert() ) - setViewFont( fontVar.value() ); + settings.endGroup(); } -//! Save view sizes to settings -void saveHeader( const QString & name, QSettings & settings, QHeaderView * header ) +SettingsDialog * NifSkope::getOptions() { - QByteArray b; - QDataStream d( &b, QIODevice::WriteOnly ); - d << header->count(); - - for ( int c = 0; c < header->count(); c++ ) - d << header->sectionSize( c ); - - settings.setValue( name, b ); + return options; } -void NifSkope::save( QSettings & settings ) const -{ - settings.setValue( "UI/Window State", saveState( 0x073 ) ); - settings.setValue( "UI/Window Geometry", saveGeometry() ); - - settings.setValue( "File/Last Load", lineLoad->text() ); - settings.setValue( "File/Last Save", lineSave->text() ); - settings.setValue( "File/Auto Sanitize", aSanitize->isChecked() ); - - settings.setValue( "UI/List Mode", ( gListMode->checkedAction() == aList ? "list" : "hierarchy" ) ); - settings.setValue( "UI/Hide Mismatched Rows", aCondition->isChecked() ); - settings.setValue( "UI/Realtime Condition Updating", aRCondition->isChecked() ); - - saveHeader( "UI/List Sizes", settings, list->header() ); - saveHeader( "UI/Tree Sizes", settings, tree->header() ); - saveHeader( "UI/Kfmtree Sizes", settings, kfmtree->header() ); - ogl->save( settings ); - Options::get()->save(); -} - -void NifSkope::contextMenu( const QPoint & pos ) +void NifSkope::closeEvent( QCloseEvent * e ) { - QModelIndex idx; - QPoint p = pos; - - if ( sender() == tree ) { - idx = tree->indexAt( pos ); - p = tree->mapToGlobal( pos ); - } else if ( sender() == list ) { - idx = list->indexAt( pos ); - p = list->mapToGlobal( pos ); - } else if ( sender() == ogl ) { - idx = ogl->indexAt( pos ); - p = ogl->mapToGlobal( pos ); - } else { - return; - } + saveUi(); - while ( idx.model() && idx.model()->inherits( "NifProxyModel" ) ) { - idx = qobject_cast( idx.model() )->mapTo( idx ); - } - - SpellBook book( nif, idx, this, SLOT( select( const QModelIndex & ) ) ); - book.exec( p ); + if ( saveConfirm() ) + e->accept(); + else + e->ignore(); } + void NifSkope::select( const QModelIndex & index ) { if ( selecting ) @@ -626,16 +401,75 @@ void NifSkope::select( const QModelIndex & index ) if ( idx.isValid() && idx.model() != nif ) return; + QModelIndex prevIdx = currentIdx; + currentIdx = idx; + selecting = true; + // Push to index stack only if there is a sender + // Must also come AFTER selecting=true + // Both of these things prevent infinite recursion + if ( sender() && !currentIdx.parent().isValid() ) { + // Skips index selection in Block Details + // NOTE: QUndoStack::push() calls the redo() command which calls NifSkope::select() + // therefore infinite recursion is possible. + indexStack->push( new SelectIndexCommand( this, currentIdx, prevIdx ) ); + } + + // TEST: Cast sender to GLView + //auto s = qobject_cast(sender()); + //if ( s ) + // qDebug() << sender()->objectName(); + if ( sender() != ogl ) { ogl->setCurrentIndex( idx ); } + if ( sender() == ogl ) { + if ( dList->isVisible() ) + dList->raise(); + } + + // Switch to Block Details tab if not selecting inside Header tab + if ( sender() != header ) { + if ( dTree->isVisible() ) + dTree->raise(); + } + if ( sender() != list ) { if ( list->model() == proxy ) { - QModelIndex pidx = proxy->mapFrom( nif->getBlock( idx ), list->currentIndex() ); - list->setCurrentIndex( pidx ); + QModelIndex idxProxy = proxy->mapFrom( nif->getBlock( idx ), list->currentIndex() ); + + // Fix for NiDefaultAVObjectPalette (et al.) bug + // mapFrom() stops at the first result for the given block number, + // thus when clicking in the viewport, the actual NiTriShape is not selected + // but the reference to it in NiDefaultAVObjectPalette or other non-NiAVObjects. + + // The true parent of the NIF block + QModelIndex blockParent = nif->index( nif->getParent( idx ) + 1, 0 ); + QModelIndex blockParentProxy = proxy->mapFrom( blockParent, list->currentIndex() ); + QString blockParentString = blockParentProxy.data( Qt::DisplayRole ).toString(); + + // The parent string for the proxy result (possibly incorrect) + QString proxyIdxParentString = idxProxy.parent().data( Qt::DisplayRole ).toString(); + + // Determine if proxy result is incorrect + if ( proxyIdxParentString != blockParentString ) { + // Find ALL QModelIndex which match the display string + for ( const QModelIndex & i : list->model()->match( list->model()->index( 0, 0 ), Qt::DisplayRole, idxProxy.data( Qt::DisplayRole ), + 100, Qt::MatchRecursive ) ) + { + // Skip if child of NiDefaultAVObjectPalette, et al. + if ( i.parent().data( Qt::DisplayRole ).toString() != blockParentString ) + continue; + + list->setCurrentIndex( i ); + } + } else { + // Proxy parent is already an ancestor of NiAVObject + list->setCurrentIndex( idxProxy ); + } + } else if ( list->model() == nif ) { list->setCurrentIndex( nif->getBlockOrHeader( idx ) ); } @@ -713,362 +547,542 @@ void NifSkope::setListMode() } } -void NifSkope::load( const QString & filepath ) +// 'Recent Files' Helpers + +QString strippedName( const QString & fullFileName ) { - lineLoad->setText( filepath ); - QTimer::singleShot( 0, this, SLOT( load() ) ); + return QFileInfo( fullFileName ).fileName(); } -void NifSkope::load() +int updateRecentActions( QAction * acts[], const QStringList & files ) { - setEnabled( false ); + int numRecentFiles = std::min( files.size(), (int)NifSkope::NumRecentFiles ); + + for ( int i = 0; i < numRecentFiles; ++i ) { + QString text = QString( "&%1 %2" ).arg( i + 1 ).arg( strippedName( files[i] ) ); + acts[i]->setText( text ); + acts[i]->setData( files[i] ); + acts[i]->setStatusTip( files[i] ); + acts[i]->setVisible( true ); + } + for ( int j = numRecentFiles; j < NifSkope::NumRecentFiles; ++j ) + acts[j]->setVisible( false ); - QFileInfo niffile( QDir::fromNativeSeparators( lineLoad->text() ) ); - niffile.makeAbsolute(); + return numRecentFiles; +} - if ( niffile.suffix().compare( "kfm", Qt::CaseInsensitive ) == 0 ) { - lineLoad->rstState(); - lineSave->rstState(); +void updateRecentFiles( QStringList & files, const QString & file ) +{ + files.removeAll( file ); + files.prepend( file ); + while ( files.size() > NifSkope::NumRecentFiles ) + files.removeLast(); +} +// End Helpers - if ( !kfm->loadFromFile( niffile.filePath() ) ) { - qWarning() << tr( "failed to load kfm from '%1'" ).arg( niffile.filePath() ); - lineLoad->setState( FileSelector::stError ); - } else { - lineLoad->setState( FileSelector::stSuccess ); - lineLoad->setText( niffile.filePath() ); - lineSave->setText( niffile.filePath() ); - } - niffile.setFile( kfm->getFolder(), - kfm->get( kfm->getKFMroot(), "NIF File Name" ) ); - } +void NifSkope::updateRecentFileActions() +{ + QSettings settings; + QStringList files = settings.value( "File/Recent File List" ).toStringList(); - ogl->tAnim->setEnabled( false ); + int numRecentFiles = ::updateRecentActions( recentFileActs, files ); - if ( !niffile.isFile() ) { - nif->clear(); - lineLoad->setState( FileSelector::stError ); - } else { - ProgDlg prog; - prog.setLabelText( tr( "loading nif..." ) ); - prog.setRange( 0, 1 ); - prog.setValue( 0 ); - prog.setMinimumDuration( 2100 ); - connect( nif, &NifModel::sigProgress, &prog, &ProgDlg::sltProgress ); - - lineLoad->rstState(); - lineSave->rstState(); - - if ( !nif->loadFromFile( niffile.filePath() ) ) { - qWarning() << tr( "failed to load nif from '%1'" ).arg( niffile.filePath() ); - lineLoad->setState( FileSelector::stError ); - } else { - lineLoad->setState( FileSelector::stSuccess ); - lineLoad->setText( niffile.filePath() ); - lineSave->setText( niffile.filePath() ); - } + aRecentFilesSeparator->setVisible( numRecentFiles > 0 ); + ui->mRecentFiles->setEnabled( numRecentFiles > 0 ); +} - setWindowTitle( niffile.fileName() ); +void NifSkope::updateAllRecentFileActions() +{ + for ( QWidget * widget : QApplication::topLevelWidgets() ) { + NifSkope * win = qobject_cast(widget); + if ( win ) { + win->updateRecentFileActions(); + win->updateRecentArchiveActions(); + win->updateRecentArchiveFileActions(); + } } +} + +QString NifSkope::getCurrentFile() const +{ + return currentFile; +} + +void NifSkope::setCurrentFile( const QString & filename ) +{ + currentFile = QDir::fromNativeSeparators( filename ); - ogl->tAnim->setEnabled( true ); - ogl->center(); + nif->refreshFileInfo( currentFile ); - // Expand BSShaderTextureSet by default - auto indices = nif->match( nif->index( 0, 0 ), Qt::DisplayRole, "Textures", -1, Qt::MatchRecursive ); - for ( auto i : indices ) { - tree->expand( i ); + setWindowFilePath( currentFile ); + + // Avoid adding files opened from BSAs to Recent Files + QFileInfo file( currentFile ); + if ( !file.exists() && !file.isAbsolute() ) { + setCurrentArchiveFile( filename ); + return; } - // Scroll panel back to top - tree->scrollTo( nif->index( 0, 0 ) ); + QSettings settings; + QStringList files = settings.value( "File/Recent File List" ).toStringList(); + ::updateRecentFiles( files, currentFile ); + + settings.setValue( "File/Recent File List", files ); - setEnabled( true ); + updateAllRecentFileActions(); } -void ProgDlg::sltProgress( int x, int y ) +void NifSkope::setCurrentArchiveFile( const QString & filepath ) { - setRange( 0, y ); - setValue( x ); - qApp->processEvents(); + QString bsa = filepath.split( "/" ).first(); + if ( !bsa.endsWith( ".bsa", Qt::CaseInsensitive ) && !bsa.endsWith( ".ba2", Qt::CaseInsensitive ) ) + return; + + // Strip BSA name from beginning of path + QString path = filepath; + path.replace( bsa + "/", "" ); + + QSettings settings; + QHash hash = settings.value( "File/Recent Archive Files" ).toHash(); + + // Retrieve and update existing Recent Files for BSA + QStringList filepaths = hash.value( bsa ).toStringList(); + ::updateRecentFiles( filepaths, path ); + + // Replace BSA's Recent Files + hash[bsa] = filepaths; + + settings.setValue( "File/Recent Archive Files", hash ); + + updateAllRecentFileActions(); } -void NifSkope::sltResetBlockDetails() +void NifSkope::clearCurrentFile() { - if ( tree ) - tree->clearRootIndex(); + QSettings settings; + QStringList files = settings.value( "File/Recent File List" ).toStringList(); + files.removeAll( currentFile ); + settings.setValue( "File/Recent File List", files ); + + updateAllRecentFileActions(); } -void NifSkope::save() +void NifSkope::setCurrentArchive( BSA * bsa ) { - // write to file - setEnabled( false ); + currentArchive = bsa; - QString nifname = lineSave->text(); + QString file = currentArchive->path(); - if ( nifname.endsWith( ".KFM", Qt::CaseInsensitive ) ) { - lineSave->rstState(); + QSettings settings; + QStringList files = settings.value( "File/Recent Archive List" ).toStringList(); + ::updateRecentFiles( files, file ); - if ( !kfm->saveToFile( nifname ) ) { - qWarning() << tr( "failed to write kfm file" ) << nifname; - lineSave->setState( FileSelector::stError ); - } else { - lineSave->setState( FileSelector::stSuccess ); - } - } else { - lineSave->rstState(); + settings.setValue( "File/Recent Archive List", files ); - if ( aSanitize->isChecked() ) { - QModelIndex idx = SpellBook::sanitize( nif ); - - if ( idx.isValid() ) - select( idx ); - } + updateAllRecentFileActions(); +} - if ( !nif->saveToFile( nifname ) ) { - qWarning() << tr( "failed to write nif file " ) << nifname; - lineSave->setState( FileSelector::stError ); - } else { - lineSave->setState( FileSelector::stSuccess ); - } +void NifSkope::clearCurrentArchive() +{ + QSettings settings; + QStringList files = settings.value( "File/Recent Archive List" ).toStringList(); - // TODO: nif->getFileInfo() returns stale data - // Instead create tmp QFileInfo from lineSave text - // Future: updating file info stored in nif - QFileInfo finfo( nifname ); - setWindowTitle( finfo.fileName() ); - } + files.removeAll( currentArchive->path() ); + settings.setValue( "File/Recent Archive List", files ); - setEnabled( true ); + updateAllRecentFileActions(); } -void NifSkope::copyFileNameLoadSave() +void NifSkope::updateRecentArchiveActions() { - if ( lineLoad->text().isEmpty() ) { - return; - } + QSettings settings; + QStringList files = settings.value( "File/Recent Archive List" ).toStringList(); + + int numRecentFiles = ::updateRecentActions( recentArchiveActs, files ); - lineSave->replaceText( lineLoad->text() ); + ui->mRecentArchives->setEnabled( numRecentFiles > 0 ); } -void NifSkope::copyFileNameSaveLoad() +void NifSkope::updateRecentArchiveFileActions() { - if ( lineSave->text().isEmpty() ) { + QSettings settings; + QHash hash = settings.value( "File/Recent Archive Files" ).toHash(); + + if ( !currentArchive ) return; - } - lineLoad->replaceText( lineSave->text() ); + QString key = currentArchive->name(); + + QStringList files = hash.value( key ).toStringList(); + + int numRecentFiles = ::updateRecentActions( recentArchiveFileActs, files ); + + mRecentArchiveFiles->setEnabled( numRecentFiles > 0 ); } -void NifSkope::sltWindow() +QByteArray fileChecksum( const QString &fileName, QCryptographicHash::Algorithm hashAlgorithm ) { - createWindow(); + QFile f( fileName ); + if ( f.open( QFile::ReadOnly ) ) { + QCryptographicHash hash( hashAlgorithm ); + if ( hash.addData( &f ) ) { + return hash.result(); + } + } + return QByteArray(); } -void NifSkope::sltShredder() +void NifSkope::checkFile( QFileInfo fInfo, QByteArray filehash ) { - TestShredder::create(); + QString fname = fInfo.fileName(); + QString fpath = fInfo.filePath(); + QDir::temp().mkdir( "NifSkope" ); + QString tmpDir = QDir::tempPath() + "/NifSkope"; + QDir tmp( tmpDir ); + QString tmpFile = tmpDir + "/" + fInfo.fileName(); + + emit beginSave(); + bool saved = nif->saveToFile( tmpFile ); + if ( saved ) { + auto filehash2 = fileChecksum( tmpFile, QCryptographicHash::Md5 ); + + if ( filehash == filehash2 ) { + tmp.remove( fname ); + } else { + QString err = "An MD5 hash comparison indicates this file will not be 100% identical upon saving. This could indicate underlying issues with the data in this file."; + Message::warning( this, err, fpath ); +#ifdef QT_NO_DEBUG + tmp.remove( fname ); +#endif + } + } + emit completeSave( saved, fpath ); } -void NifSkope::openURL() +void NifSkope::openArchive( const QString & archive ) { - if ( !sender() ) + // Clear memory from previously opened archives + bsaModel->clear(); + bsaProxyModel->clear(); + bsaProxyModel->setSourceModel( emptyModel ); + bsaView->setModel( emptyModel ); + bsaView->setSortingEnabled( false ); + + archiveHandler.reset(); + + archiveHandler = FSArchiveHandler::openArchive( archive ); + if ( !archiveHandler ) { + qCWarning( nsIo ) << "The BSA could not be opened."; return; + } - QAction * aURL = qobject_cast( sender() ); + auto bsa = archiveHandler->getArchive(); + if ( bsa ) { - if ( !aURL ) - return; + setCurrentArchive( bsa ); - QUrl URL = aURL->data().toUrl(); + // Models + bsaModel->init(); - if ( !URL.isValid() ) - return; + // Populate model from BSA + bsa->fillModel( bsaModel, "meshes" ); - QDesktopServices::openUrl( URL ); + if ( bsaModel->rowCount() == 0 ) { + qCWarning( nsIo ) << "The BSA does not contain any meshes."; + clearCurrentArchive(); + return; + } + + // Set proxy and view only after filling source model + bsaProxyModel->setSourceModel( bsaModel ); + bsaView->setModel( bsaProxyModel ); + bsaView->setSortingEnabled( true ); + + bsaView->hideColumn( 1 ); + bsaView->setColumnWidth( 0, 300 ); + bsaView->setColumnWidth( 2, 50 ); + + // Sort proxy after model/view is populated + bsaProxyModel->sort( 0, Qt::AscendingOrder ); + bsaProxyModel->setFiletypes( { ".nif", ".bto", ".btr" } ); + bsaProxyModel->resetFilter(); + + // Set filename label + ui->bsaName->setText( currentArchive->name() ); + + ui->bsaFilter->setEnabled( true ); + ui->bsaFilenameOnly->setEnabled( true ); + + // Bring tab to front + dBrowser->raise(); + + // Filter + auto filterTimer = new QTimer( this ); + filterTimer->setSingleShot( true ); + + connect( ui->bsaFilter, &QLineEdit::textChanged, [filterTimer]() { filterTimer->start( 300 ); } ); + connect( filterTimer, &QTimer::timeout, [this]() { + auto text = ui->bsaFilter->text(); + + bsaProxyModel->setFilterRegExp( QRegExp( text, Qt::CaseInsensitive, QRegExp::Wildcard ) ); + bsaView->expandAll(); + + if ( text.isEmpty() ) { + bsaView->collapseAll(); + bsaProxyModel->resetFilter(); + } + + } ); + + connect( ui->bsaFilenameOnly, &QCheckBox::toggled, bsaProxyModel, &BSAProxyModel::setFilterByNameOnly ); + + // Update filter when switching open archives + filterTimer->start( 0 ); + } } +void NifSkope::openArchiveFile( const QModelIndex & index ) +{ + QString filepath = index.sibling( index.row(), 1 ).data( Qt::EditRole ).toString(); + + if ( !filepath.isEmpty() ) + openArchiveFileString( currentArchive, filepath ); +} -NifSkope * NifSkope::createWindow( const QString & fname ) +void NifSkope::openArchiveFileString( BSA * bsa, const QString & filepath ) { - NifSkope * skope = new NifSkope; - skope->setAttribute( Qt::WA_DeleteOnClose ); - QSettings settings; - skope->restore( settings ); - skope->show(); + if ( bsa->hasFile( filepath ) ) { + if ( !saveConfirm() ) + return; + + // Read data from BSA + QByteArray data; + bsa->fileContents( filepath, data ); + + // Format like "BSANAME.BSA/path/to/file.nif" + QString path = bsa->name() + "/" + filepath; + + QBuffer buf; + buf.setData( data ); + if ( buf.open( QBuffer::ReadOnly ) ) { + + emit beginLoading(); + + bool loaded = nif->load( buf ); + if ( loaded ) + setCurrentFile( path ); - skope->raise(); + emit completeLoading( loaded, path ); - if ( !fname.isEmpty() ) { - skope->lineLoad->setFile( fname ); - QTimer::singleShot( 0, skope, SLOT( load() ) ); + //if ( loaded ) { + // QCryptographicHash hash( QCryptographicHash::Md5 ); + // hash.addData( data ); + // filehash = hash.result(); + // + // QFileInfo f( path ); + // + // checkFile( f, filehash ); + //} + + buf.close(); + } } +} + + +void NifSkope::openFile( QString & file ) +{ + if ( !saveConfirm() ) + return; - return skope; + loadFile( file ); } -void NifSkope::loadXML() +void NifSkope::openRecentFile() { - NifModel::loadXML(); - KfmModel::loadXML(); + if ( !saveConfirm() ) + return; + + QAction * action = qobject_cast(sender()); + if ( action ) + loadFile( action->data().toString() ); } -void NifSkope::reload() +void NifSkope::openRecentArchive() +{ + QAction * action = qobject_cast(sender()); + if ( action ) + openArchive( action->data().toString() ); +} + +void NifSkope::openRecentArchiveFile() { - if ( NifModel::loadXML() ) { - load(); + QAction * action = qobject_cast(sender()); + if ( action ) + openArchiveFileString( currentArchive, action->data().toString() ); +} + + +void NifSkope::openFiles( QStringList & files ) +{ + // Open first file in current window if blank + // or only one file selected. + if ( getCurrentFile().isEmpty() || files.count() == 1 ) { + QString first = files.takeFirst(); + if ( !first.isEmpty() ) + loadFile( first ); + } + + for ( const QString & file : files ) { + NifSkope::createWindow( file ); } } -void NifSkope::sltSelectFont() +void NifSkope::saveFile( const QString & filename ) { - bool ok; - QFont fnt = QFontDialog::getFont( &ok, list->font(), this ); + setCurrentFile( filename ); + save(); +} - if ( !ok ) - return; +void NifSkope::loadFile( const QString & filename ) +{ + QApplication::setOverrideCursor( Qt::WaitCursor ); - setViewFont( fnt ); - QSettings settings; - settings.setValue( "UI/View Font", fnt ); + setCurrentFile( filename ); + QTimer::singleShot( 0, this, SLOT( load() ) ); } -void NifSkope::setViewFont( const QFont & font ) +void NifSkope::reload() { - list->setFont( font ); - QFontMetrics metrics( list->font() ); - list->setIconSize( QSize( metrics.width( "000" ), metrics.lineSpacing() ) ); - tree->setFont( font ); - tree->setIconSize( QSize( metrics.width( "000" ), metrics.lineSpacing() ) ); - kfmtree->setFont( font ); - kfmtree->setIconSize( QSize( metrics.width( "000" ), metrics.lineSpacing() ) ); - ogl->setFont( font ); + QTimer::singleShot( 0, this, SLOT( load() ) ); } -bool NifSkope::eventFilter( QObject * o, QEvent * e ) +void NifSkope::load() { - if ( e->type() == QEvent::Polish ) { - QTimer::singleShot( 0, this, SLOT( overrideViewFont() ) ); + emit beginLoading(); + + QFileInfo f( QDir::fromNativeSeparators( currentFile ) ); + f.makeAbsolute(); + + QString fname = f.filePath(); + + // TODO: This is rather poor in terms of file validation + + if ( f.suffix().compare( "kfm", Qt::CaseInsensitive ) == 0 ) { + emit completeLoading( kfm->loadFromFile( fname ), fname ); + + f.setFile( kfm->getFolder(), kfm->get( kfm->getKFMroot(), "NIF File Name" ) ); } - return QMainWindow::eventFilter( o, e ); + bool loaded = nif->loadFromFile( fname ); + + emit completeLoading( loaded, fname ); + + //if ( loaded ) { + // filehash = fileChecksum( fname, QCryptographicHash::Md5 ); + // + // checkFile( f, filehash ); + //} } -void NifSkope::overrideViewFont() +void NifSkope::save() { - QSettings settings; - QVariant var = settings.value( "UI/View Font" ); + // Assure file path is absolute + // If not absolute, it is loaded from a BSA + QFileInfo curFile( currentFile ); + if ( !curFile.isAbsolute() ) { + saveAsDlg(); + return; + } + + emit beginSave(); - if ( var.canConvert() ) { - setViewFont( var.value() ); + QString fname = currentFile; + + // TODO: This is rather poor in terms of file validation + + if ( fname.endsWith( ".KFM", Qt::CaseInsensitive ) ) { + emit completeSave( kfm->saveToFile( fname ), fname ); + } else { + if ( aSanitize->isChecked() ) { + QModelIndex idx = SpellBook::sanitize( nif ); + if ( idx.isValid() ) + select( idx ); + } + + emit completeSave( nif->saveToFile( fname ), fname ); } } -void NifSkope::dispatchMessage( const Message & msg ) + +//! Opens website links using the QAction's tooltip text +void NifSkope::openURL() { - switch ( msg.type() ) { - case QtCriticalMsg: - qCritical() << msg; - break; - case QtFatalMsg: - qFatal( QString( msg ).toLatin1().data() ); - break; - case QtWarningMsg: - qWarning() << msg; - break; - case QtDebugMsg: - default: - qDebug() << msg; - break; - } + // Note: This method may appear unused but this slot is + // utilized in the nifskope.ui file. + + if ( !sender() ) + return; + + QAction * aURL = qobject_cast( sender() ); + if ( !aURL ) + return; + + // Sender is an action, grab URL from tooltip + QUrl URL(aURL->toolTip()); + if ( !URL.isValid() ) + return; + + QDesktopServices::openUrl( URL ); } -QTextEdit * msgtarget = nullptr; +/* + * SelectIndexCommand + * Manages cycling between previously selected indices like a browser Back/Forward button + */ -#ifdef Q_OS_WIN32 -//! Windows mutex handling -class QDefaultHandlerCriticalSection +SelectIndexCommand::SelectIndexCommand( NifSkope * wnd, const QModelIndex & cur, const QModelIndex & prev ) { - CRITICAL_SECTION cs; + nifskope = wnd; -public: - QDefaultHandlerCriticalSection() { InitializeCriticalSection( &cs ); } - ~QDefaultHandlerCriticalSection() { DeleteCriticalSection( &cs ); } - void lock() { EnterCriticalSection( &cs ); } - void unlock() { LeaveCriticalSection( &cs ); } -}; + curIdx = cur; + prevIdx = prev; +} -//! Application-wide debug and warning message handler internals -static void qDefaultMsgHandler( QtMsgType t, const char * str ) +void SelectIndexCommand::redo() { - Q_UNUSED( t ); - // OutputDebugString is not threadsafe. - // cannot use QMutex here, because qWarning()s in the QMutex - // implementation may cause this function to recurse - static QDefaultHandlerCriticalSection staticCriticalSection; - - if ( !str ) - str = "(null)"; - - staticCriticalSection.lock(); - QString s( QString::fromLocal8Bit( str ) ); - s += QLatin1String( "\n" ); - OutputDebugStringW( (TCHAR *)s.utf16() ); - staticCriticalSection.unlock(); + nifskope->select( curIdx ); } -#else -// Doxygen won't find this unless you undef Q_OS_WIN32 -//! Application-wide debug and warning message handler internals -void qDefaultMsgHandler( QtMsgType t, const char * str ) -{ - if ( !str ) - str = "(null)"; - printf( "%s\n", str ); +void SelectIndexCommand::undo() +{ + nifskope->select( prevIdx ); } -#endif + //! Application-wide debug and warning message handler -void myMessageOutput( QtMsgType type, const QMessageLogContext &, const QString & str ) +void myMessageOutput( QtMsgType type, const QMessageLogContext & context, const QString & str ) { - QByteArray msg = str.toLocal8Bit(); - static const QString editFailed( "edit: editing failed" ); - static const QString accessWidgetRect( "QAccessibleWidget::rect" ); - switch ( type ) { case QtDebugMsg: - qDefaultMsgHandler( type, msg.constData() ); + fprintf( stderr, "[Debug] %s\n", qPrintable( str ) ); break; case QtWarningMsg: - - // workaround for Qt 4.2.2 - if ( editFailed == msg ) - return; - else if ( QString( msg ).startsWith( accessWidgetRect ) ) - return; - + fprintf( stderr, "[Warning] %s\n", qPrintable( str ) ); + Message::message( qApp->activeWindow(), str, &context, QMessageBox::Warning ); + break; case QtCriticalMsg: - - if ( !msgtarget ) { - msgtarget = new QTextEdit; - msgtarget->setWindowFlags( Qt::Tool | Qt::WindowStaysOnTopHint ); - } - - if ( !msgtarget->isVisible() ) { - msgtarget->clear(); - msgtarget->show(); - } - - msgtarget->append( msg ); - qDefaultMsgHandler( type, msg.constData() ); + fprintf( stderr, "[Critical] %s\n", qPrintable( str ) ); + Message::message( qApp->activeWindow(), str, &context, QMessageBox::Critical ); break; case QtFatalMsg: - qDefaultMsgHandler( type, msg.constData() ); - QMessageBox::critical( 0, QMessageBox::tr( "Fatal Error" ), msg ); - // TODO: the above causes stack overflow when - // "ASSERT: "testAttribute(Qt::WA_WState_Created)" in file kernel\qapplication_win.cpp, line 3699" - abort(); + fprintf( stderr, "[Fatal] %s\n", qPrintable( str ) ); + break; + case QtInfoMsg: + fprintf( stderr, "[Info] %s\n", qPrintable( str ) ); + break; } } @@ -1077,35 +1091,28 @@ void myMessageOutput( QtMsgType type, const QMessageLogContext &, const QString * IPC socket */ -IPCsocket * IPCsocket::create() +IPCsocket * IPCsocket::create( int port ) { QUdpSocket * udp = new QUdpSocket(); - if ( udp->bind( QHostAddress( QHostAddress::LocalHost ), NIFSKOPE_IPC_PORT, QUdpSocket::DontShareAddress ) ) { + if ( udp->bind( QHostAddress( QHostAddress::LocalHost ), port, QUdpSocket::DontShareAddress ) ) { IPCsocket * ipc = new IPCsocket( udp ); QDesktopServices::setUrlHandler( "nif", ipc, "openNif" ); return ipc; } - return 0; + return nullptr; } -void IPCsocket::sendCommand( const QString & cmd ) +void IPCsocket::sendCommand( const QString & cmd, int port ) { QUdpSocket udp; - udp.writeDatagram( (const char *)cmd.data(), cmd.length() * sizeof( QChar ), QHostAddress( QHostAddress::LocalHost ), NIFSKOPE_IPC_PORT ); + udp.writeDatagram( (const char *)cmd.data(), cmd.length() * sizeof( QChar ), QHostAddress( QHostAddress::LocalHost ), port ); } IPCsocket::IPCsocket( QUdpSocket * s ) : QObject(), socket( s ) { QObject::connect( socket, &QUdpSocket::readyRead, this, &IPCsocket::processDatagram ); - -#ifdef FSENGINE - - if ( !fsmanager ) - fsmanager = FSManager::get(); - -#endif } IPCsocket::~IPCsocket() @@ -1138,6 +1145,14 @@ void IPCsocket::execCommand( const QString & cmd ) } } +void IPCsocket::openNif( const QUrl & url ) +{ + auto file = url.toString(); + file.remove( 0, 4 ); + + openNif( file ); +} + void IPCsocket::openNif( const QString & url ) { NifSkope::createWindow( url ); @@ -1146,12 +1161,12 @@ void IPCsocket::openNif( const QString & url ) // TODO: This class was not used. QSystemLocale became private in Qt 5. // It appears this class was going to handle display of numbers. -/*//! System locale override +//! System locale override /** * Qt does not use the System Locale consistency so this basically forces all floating * numbers into C format but leaves all other local specific settings. - *//* -class NifSystemLocale : QSystemLocale + */ +/*class NifSystemLocale : QSystemLocale { virtual QVariant query(QueryType type, QVariant in) const { @@ -1199,11 +1214,13 @@ static void SetAppLocale( QLocale curLocale ) mTranslator->load( fileName ); } + + QLocale::setDefault( QLocale::C ); } void NifSkope::sltLocaleChanged() { - SetAppLocale( Options::get()->translationLocale() ); + SetAppLocale( cfg.locale ); QMessageBox mb( "NifSkope", tr( "NifSkope must be restarted for this setting to take full effect." ), @@ -1212,13 +1229,24 @@ void NifSkope::sltLocaleChanged() ); mb.setIconPixmap( QPixmap( ":/res/nifskope.png" ) ); mb.exec(); + + // TODO: Retranslate dynamically + //ui->retranslateUi( this ); } -QString NifSkope::getLoadFileName() +QCoreApplication * createApplication( int &argc, char *argv[] ) { - return lineLoad->text(); + // Iterate over args + for ( int i = 1; i < argc; ++i ) { + // -no-gui: start as core app without all the GUI overhead + if ( !qstrcmp( argv[i], "-no-gui" ) ) { + return new QCoreApplication( argc, argv ); + } + } + return new QApplication( argc, argv ); } + /* * main */ @@ -1226,138 +1254,138 @@ QString NifSkope::getLoadFileName() //! The main program int main( int argc, char * argv[] ) { - // set up the Qt Application - QApplication app( argc, argv ); - app.setOrganizationName( "NifTools" ); - app.setOrganizationDomain( "niftools.org" ); - app.setApplicationName( "NifSkope " + NifSkopeVersion::rawToMajMin( NIFSKOPE_VERSION ) ); - app.setApplicationVersion( NIFSKOPE_VERSION ); - app.setApplicationDisplayName( "NifSkope " + NifSkopeVersion::rawToDisplay( NIFSKOPE_VERSION, true ) ); - - // install message handler - qRegisterMetaType( "Message" ); -#ifdef QT_NO_DEBUG - qInstallMessageHandler( myMessageOutput ); -#endif + QScopedPointer app( createApplication( argc, argv ) ); + + if ( auto a = qobject_cast(app.data()) ) { + + a->setOrganizationName( "NifTools" ); + a->setOrganizationDomain( "niftools.org" ); + a->setApplicationName( "NifSkope " + NifSkopeVersion::rawToMajMin( NIFSKOPE_VERSION ) ); + a->setApplicationVersion( NIFSKOPE_VERSION ); + a->setApplicationDisplayName( "NifSkope " + NifSkopeVersion::rawToDisplay( NIFSKOPE_VERSION, true ) ); + + // Must set current directory or this causes issues with several features + QDir::setCurrent( qApp->applicationDirPath() ); + + // Register message handler + //qRegisterMetaType( "Message" ); + qInstallMessageHandler( myMessageOutput ); + + // Register types + qRegisterMetaType( "NifValue" ); + QMetaType::registerComparators(); - // if there is a style sheet present then load it - QDir qssDir( QApplication::applicationDirPath() ); - QStringList qssList( QStringList() - << "style.qss" + // Find stylesheet + QDir qssDir( QApplication::applicationDirPath() ); + QStringList qssList( QStringList() + << "style.qss" #ifdef Q_OS_LINUX - << "/usr/share/nifskope/style.qss" + << "/usr/share/nifskope/style.qss" #endif - ); - QString qssName; - for ( const QString& str : qssList ) { - if ( qssDir.exists( str ) ) { - qssName = qssDir.filePath( str ); - break; + ); + QString qssName; + for ( const QString& str : qssList ) { + if ( qssDir.exists( str ) ) { + qssName = qssDir.filePath( str ); + break; + } } - } - // load the style sheet if present - if ( !qssName.isEmpty() ) { - QFile style( qssName ); + // Load stylesheet + if ( !qssName.isEmpty() ) { + QFile style( qssName ); - if ( style.open( QFile::ReadOnly ) ) { - app.setStyleSheet( style.readAll() ); - style.close(); + if ( style.open( QFile::ReadOnly ) ) { + a->setStyleSheet( style.readAll() ); + style.close(); + } } - } - QSettings cfg; - cfg.beginGroup( "Settings" ); - SetAppLocale( cfg.value( "Language", "en" ).toLocale() ); - cfg.endGroup(); + // Set locale + QSettings cfg; + cfg.beginGroup( "Settings" ); + SetAppLocale( cfg.value( "Locale", "en" ).toLocale() ); + cfg.endGroup(); - NifModel::loadXML(); - KfmModel::loadXML(); + // Load XML files + NifModel::loadXML(); + KfmModel::loadXML(); - QStack fnames; - bool reuseSession = true; + int port = NIFSKOPE_IPC_PORT; - // EXE is being passed arguments - for ( int i = 1; i < argc; ++i ) { - char * arg = argv[i]; - - if ( arg && arg[0] == '-' ) { - // Command line arguments - // TODO: See QCommandLineParser for future - // expansion of command line abilities. - switch ( arg[1] ) { - case 'i': - case 'I': - // TODO: Figure out the point of this - reuseSession = false; - break; - } - } else { - //qDebug() << "arg " << i << ": " << arg; + QStack fnames; + + // Command Line setup + QCommandLineParser parser; + parser.addHelpOption(); + parser.addVersionOption(); + + // Add port option + QCommandLineOption portOption( {"p", "port"}, "Port NifSkope listens on", "port" ); + parser.addOption( portOption ); + + // Process options + parser.process( *a ); + + // Override port value + if ( parser.isSet( portOption ) ) + port = parser.value( portOption ).toInt(); + + // Files were passed to NifSkope + for ( const QString & arg : parser.positionalArguments() ) { QString fname = QDir::current().filePath( arg ); if ( QFileInfo( fname ).exists() ) { fnames.push( fname ); } } - } - - // EXE is being opened directly - if ( fnames.isEmpty() ) { - fnames.push( QString() ); - } - if ( fnames.count() > 0 ) { - - // TODO: Figure out the point of this - if ( !reuseSession ) { - //qDebug() << "NifSkope createWindow"; - NifSkope::createWindow( fnames.pop() ); - return app.exec(); + // No files were passed to NifSkope, push empty string + if ( fnames.isEmpty() ) { + fnames.push( QString() ); } - - if ( IPCsocket * ipc = IPCsocket::create() ) { + + if ( IPCsocket * ipc = IPCsocket::create( port ) ) { //qDebug() << "IPCSocket exec"; ipc->execCommand( QString( "NifSkope::open %1" ).arg( fnames.pop() ) ); while ( !fnames.isEmpty() ) { - IPCsocket::sendCommand( QString( "NifSkope::open %1" ).arg( fnames.pop() ) ); + IPCsocket::sendCommand( QString( "NifSkope::open %1" ).arg( fnames.pop() ), port ); } - return app.exec(); + return a->exec(); } else { //qDebug() << "IPCSocket send"; - IPCsocket::sendCommand( QString( "NifSkope::open %1" ).arg( fnames.pop() ) ); + while ( !fnames.isEmpty() ) { + IPCsocket::sendCommand( QString( "NifSkope::open %1" ).arg( fnames.pop() ), port ); + } return 0; } - + } else { + // Future command line batch tools here } + + return 0; } void NifSkope::migrateSettings() const { - // IMPORTANT: - // Do not make any calls to Options:: until after all migration code. - // Static calls to Options:: still create the options instance and inits - // the various widgets with incorrect values. Once you close the app, - // the settings you migrated get overwritten with the default values. - // Load current NifSkope settings QSettings cfg; // Load pre-1.2 NifSkope settings QSettings cfg1_1( "NifTools", "NifSkope" ); + // Load NifSkope 1.2 settings + QSettings cfg1_2( "NifTools", "NifSkope 1.2" ); // Current version strings QString curVer = NIFSKOPE_VERSION; QString curQtVer = QT_VERSION_STR; QString curDisplayVer = NifSkopeVersion::rawToDisplay( NIFSKOPE_VERSION, true ); - bool doMigration = false; - // New Install, no need to migrate anything if ( !cfg.value( "Version" ).isValid() && !cfg1_1.value( "version" ).isValid() ) { - // QSettings constructor creates an empty folder, so clear it. + // QSettings constructor creates an empty folder, so clear it. cfg1_1.clear(); // Set version values @@ -1368,57 +1396,120 @@ void NifSkope::migrateSettings() const return; } - // Forward dec for prevVer which either comes from `cfg` or `cfg1_1` - QString prevVer; + QString prevVer = curVer; QString prevQtVer = cfg.value( "Qt Version" ).toString(); QString prevDisplayVer = cfg.value( "Display Version" ).toString(); // Set full granularity for version comparisons NifSkopeVersion::setNumParts( 7 ); - // Check for Existing 1.1 Migration - if ( !cfg1_1.value( "migrated" ).isValid() ) { - // Old install has not been migrated yet + // Test migration lambda + // Note: Sets value of prevVer + auto testMigration = [&prevVer]( QSettings & migrateFrom, const char * migrateTo ) { + if ( migrateFrom.value( "version" ).isValid() && !migrateFrom.value( "migrated" ).isValid() ) { + prevVer = migrateFrom.value( "version" ).toString(); - // Get prevVer from pre-1.2 settings - prevVer = cfg1_1.value( "version" ).toString(); - - NifSkopeVersion tmp( prevVer ); - if ( tmp < "1.2.0" ) - doMigration = true; - } else { - // Get prevVer from post-1.2 settings - prevVer = cfg.value( "Version" ).toString(); - } - - NifSkopeVersion oldVersion( prevVer ); - NifSkopeVersion newVersion( curVer ); + NifSkopeVersion tmp( prevVer ); + if ( tmp < migrateTo ) + return true; + } + return false; + }; - // Migrate from 1.1.x to 1.2 - if ( doMigration && (oldVersion < "1.2.0") ) { - // Port old key values to new key names + // Migrate lambda + // Using a QHash of registry keys (stored in version.h), migrates from one version to another. + auto migrate = []( QSettings & migrateFrom, QSettings & migrateTo, const QHash migration ) { QHash::const_iterator i; - for ( i = migrateTo1_2.begin(); i != migrateTo1_2.end(); ++i ) { - QVariant val = cfg1_1.value( i.key() ); + for ( i = migration.begin(); i != migration.end(); ++i ) { + QVariant val = migrateFrom.value( i.key() ); if ( val.isValid() ) { - cfg.setValue( i.value(), val ); + migrateTo.setValue( i.value(), val ); } } - // Set `migrated` flag in legacy QSettings - cfg1_1.setValue( "migrated", true ); + migrateFrom.setValue( "migrated", true ); + }; + + // NOTE: These set `prevVer` and must come before setting `oldVersion` + bool migrateFrom1_1 = testMigration( cfg1_1, "1.2.0" ); + bool migrateFrom1_2 = testMigration( cfg1_2, "2.0" ); + + if ( !migrateFrom1_1 && !migrateFrom1_2 ) { + prevVer = cfg.value( "Version" ).toString(); } + NifSkopeVersion oldVersion( prevVer ); + NifSkopeVersion newVersion( curVer ); + // Check NifSkope Version // Assure full granularity here NifSkopeVersion::setNumParts( 7 ); if ( oldVersion != newVersion ) { + + // Migrate from 1.1.x to 1.2 + if ( migrateFrom1_1 ) { + qDebug() << "Migrating from 1.1 to 1.2"; + migrate( cfg1_1, cfg1_2, migrateTo1_2 ); + } + + // Migrate from 1.2.x to 2.0 + if ( migrateFrom1_2 ) { + qDebug() << "Migrating from 1.2 to 2.0"; + migrate( cfg1_2, cfg, migrateTo2_0 ); + } + // Set new Version cfg.setValue( "Version", curVer ); if ( prevDisplayVer != curDisplayVer ) cfg.setValue( "Display Version", curDisplayVer ); + + // Migrate to new Settings + if ( oldVersion <= NifSkopeVersion( "2.0.dev1" ) ) { + qDebug() << "Migrating to new Settings"; + + // Sanitize backslashes + auto sanitize = []( QVariant oldVal ) { + QStringList sanitized; + for ( const QString & archive : oldVal.toStringList() ) { + if ( archive == "AUTO" ) { + sanitized.append( FSManager::autodetectArchives() ); + continue; + } + + sanitized.append( QDir::fromNativeSeparators( archive ) ); + } + + return sanitized; + }; + + QVariant foldersVal = cfg.value( "Settings/Resources/Folders" ); + if ( foldersVal.toStringList().isEmpty() ) { + QVariant oldVal = cfg.value( "Render Settings/Texture Folders" ); + if ( !oldVal.isNull() ) { + cfg.setValue( "Settings/Resources/Folders", sanitize( oldVal ) ); + } + } + + QVariant archivesVal = cfg.value( "Settings/Resources/Archives" ); + if ( archivesVal.toStringList().isEmpty() ) { + QVariant oldVal = cfg.value( "FSEngine/Archives" ); + if ( !oldVal.isNull() ) { + cfg.setValue( "Settings/Resources/Archives", sanitize( oldVal ) ); + } + } + + // Update archive handler + FSManager::get()->initialize(); + + // Remove old keys + + cfg.remove( "FSEngine" ); + cfg.remove( "Render Settings" ); + cfg.remove( "Settings/Language" ); + cfg.remove( "Settings/Startup Version" ); + } } // Check Qt Version diff --git a/src/nifskope.h b/src/nifskope.h index 8104c9316..ec6059d38 100644 --- a/src/nifskope.h +++ b/src/nifskope.h @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -34,109 +34,187 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define NIFSKOPE_H #include "message.h" -#include "ui/about_dialog.h" #include // Inherited #include // Inherited -#include // Inherited +#include +#include +#include +#include + +#if QT_NO_DEBUG #define NIFSKOPE_IPC_PORT 12583 +#else +#define NIFSKOPE_IPC_PORT 12584 +#endif +namespace Ui { + class MainWindow; +} class FileSelector; class GLView; +class GLGraphicsView; class InspectView; class KfmModel; class NifModel; class NifProxyModel; class NifTreeView; class ReferenceBrowser; +class SettingsDialog; +class SpellBook; +class FSArchiveHandler; +class BSA; +class BSAModel; +class BSAProxyModel; +class QStandardItemModel; class QAction; class QActionGroup; +class QComboBox; +class QGraphicsScene; class QLocale; class QModelIndex; -class QSettings; -class QSlider; -class QSpinBox; -class QTextEdit; -class QTranslator; +class QProgressBar; +class QStringList; +class QTimer; +class QTreeView; class QUdpSocket; -//! \file nifskope.h The main header for NifSkope -//! The main application class for NifSkope. -/*! +//! @file nifskope.h NifSkope, IPCsocket + +/*! The main application class for NifSkope. + * * This class encapsulates the main NifSkope window. It has members for saving - * and restoring settings, loading and saving nif files, loading an xml - * description, widgets for the various subwindows, menu's, and a socket by - * which NifSkope can communicate with itself. + * and restoring settings, loading and saving NIF files, loading an XML + * description, widgets for the various subwindows, menus, and a UDP socket + * with which NifSkope can communicate with itself. */ class NifSkope final : public QMainWindow { Q_OBJECT public: - //! Constructor NifSkope(); - //! Destructor ~NifSkope(); - //! Create and initialize a new NifSkope application window. - /*! - * \param fname The name of the file to load in the new NifSkope window. - * \return The newly created NifSkope instance. - */ - static NifSkope * createWindow( const QString & fname = QString() ); + Ui::MainWindow * ui; + //! Save Confirm dialog + bool saveConfirm(); //! Save NifSkope application settings. - /*! - * \param settings The QSettings object used to store the settings. - */ - void save( QSettings & settings ) const; + void saveUi() const; + //! Restore NifSkope UI settings. + void restoreUi(); - //! Restore NifSkope application settings. - /*! - * \param settings The QSettings object to restore the settings from. + //! Returns path of currently open file + QString getCurrentFile() const; + + /*! Create and initialize a new NifSkope application window. + * + * @param fname The name of the file to load in the new NifSkope window. + * @return The newly created NifSkope instance. */ - void restore( const QSettings & settings ); - //! Get Loaded filename - /*! - * \return QString of loaded filename + static NifSkope * createWindow( const QString & fname = QString() ); + + static SettingsDialog * getOptions(); + + //! List of all supported file extensions + static QStringList fileExtensions(); + + //! Return a file filter for a single extension + static QString fileFilter( const QString & ); + + /*! Return a file filter for all supported extensions. + * + * @param allFiles If true, file filter will be prepended with "All Files (*.nif *.btr ...)" + * so that all supported files will show at once. Used for Open File dialog. */ - QString getLoadFileName(); + static QString fileFilters( bool allFiles = true ); + + //! A map of all the currently support filetypes to their file extensions. + static const QList> filetypes; + + enum { NumRecentFiles = 10 }; + +signals: + void beginLoading(); + void completeLoading( bool, QString & ); + void beginSave(); + void completeSave( bool, QString & ); public slots: - //! Set the lineLoad string and load a nif, kf, or kfm file. - /*! - * \param filepath The file to load. - */ - void load( const QString & filepath ); + void openFile( QString & ); + void openFiles( QStringList & ); - //! Load a nif, kf, or kfm file, taking the file path from the lineLoad widget. - void load(); + void openArchive( const QString & ); + void openArchiveFile( const QModelIndex & ); + void openArchiveFileString( BSA *, const QString & ); - //! Save a nif, kf, or kfm file, taking the file path from the lineSave widget. - void save(); + void enableUi(); + + void updateSettings(); + + //! Select a NIF index + void select( const QModelIndex & ); + + // Automatic slots //! Reparse the nif.xml and kfm.xml files. - void loadXML(); + void on_aLoadXML_triggered(); //! Reparse the nif.xml and kfm.xml files and reload the current file. - void reload(); + void on_aReload_triggered(); //! A slot that creates a new NifSkope application window. - void sltWindow(); + void on_aWindow_triggered(); //! A slot for starting the XML checker. - void sltShredder(); + void on_aShredder_triggered(); //! Reset "block details" - void sltResetBlockDetails(); + void on_aHeader_triggered(); + + //! Select the font to use + void on_aSelectFont_triggered(); + + void on_tRender_actionTriggered( QAction * ); + + void on_aViewTop_triggered( bool ); + void on_aViewFront_triggered( bool ); + void on_aViewLeft_triggered( bool ); + + void on_aViewCenter_triggered(); + void on_aViewFlip_triggered( bool ); + void on_aViewPerspective_toggled( bool ); + void on_aViewWalk_triggered( bool ); + + void on_aViewUser_toggled( bool ); + void on_aViewUserSave_triggered( bool ); + + void on_aSettings_triggered(); + protected slots: - //! Select a NIF index - void select( const QModelIndex & ); + void openDlg(); + void saveAsDlg(); + + void archiveDlg(); + + void load(); + void save(); + + void reload(); + + void exitRequested(); + + void onLoadBegin(); + void onSaveBegin(); + + void onLoadComplete( bool, QString & ); + void onSaveComplete( bool, QString & ); //! Display a context menu at the specified position void contextMenu( const QPoint & pos ); @@ -144,23 +222,12 @@ protected slots: //! Set the list mode void setListMode(); - //! Select the font to use - void sltSelectFont(); - - //! Send a Message - void dispatchMessage( const Message & msg ); - //! Override the view font void overrideViewFont(); - //! Copy file name from load to save - void copyFileNameLoadSave(); - //! Copy file name from save to load - void copyFileNameSaveLoad(); - - //! Sets Import/Export menus - /*! - * see importex/importex.cpp + /*! Sets Import/Export menus + * + * @see importex/importex.cpp */ void fillImportExportMenus(); //! Perform Import or Export @@ -172,8 +239,12 @@ protected slots: //! Change system locale and notify user that restart may be required void sltLocaleChanged(); + //! Called after window resizing has stopped + void resizeDone(); + protected: void closeEvent( QCloseEvent * e ) override final; + //void resizeEvent( QResizeEvent * event ) override final; bool eventFilter( QObject * o, QEvent * e ) override final; private: @@ -181,29 +252,75 @@ protected slots: void initDockWidgets(); void initToolBars(); void initMenu(); + void initConnections(); - //! "About NifSkope" dialog. - QWidget * aboutDialog; + void loadFile( const QString & ); + void saveFile( const QString & ); + void checkFile( QFileInfo fInfo, QByteArray filehash ); + + void openRecentFile(); + void setCurrentFile( const QString & ); + void clearCurrentFile(); + void updateRecentFileActions(); + void updateAllRecentFileActions(); + + void openRecentArchive(); + void openRecentArchiveFile(); + void setCurrentArchive( BSA * ); + void setCurrentArchiveFile( const QString & ); + void clearCurrentArchive(); + void updateRecentArchiveActions(); + void updateRecentArchiveFileActions(); + + //! Disconnect and reconnect the models to the views + void swapModels(); + + QMenu * lightingWidget(); + QWidget * filePathWidget( QWidget * ); void setViewFont( const QFont & ); //! Migrate settings from older versions of NifSkope. void migrateSettings() const; - //! Stores the nif file in memory. + //! "About NifSkope" dialog. + QWidget * aboutDialog; + + QString currentFile; + BSA * currentArchive = nullptr; + + QByteArray filehash; + + //! Stores the NIF file in memory. NifModel * nif; - //! A hierarchical proxy for the nif file. + //! A hierarchical proxy for the NIF file. NifProxyModel * proxy; - //! Stores the kfm file in memory. + //! Stores the KFM file in memory. KfmModel * kfm; + NifModel * nifEmpty; + NifProxyModel * proxyEmpty; + KfmModel * kfmEmpty; + //! This view shows the block list. NifTreeView * list; - //! This view shows the whole nif file or the block details. + //! This view shows the block details. NifTreeView * tree; - //! This view shows the KFM file, if any + //! This view shows the file header. + NifTreeView * header; + //! This view shows the archive browser files. + QTreeView * bsaView; + + //! This view shows the KFM file, if any. NifTreeView * kfmtree; + //! Spellbook instance + std::shared_ptr book; + + std::shared_ptr archiveHandler; + + static SettingsDialog * options; + //! Help browser ReferenceBrowser * refrbrwsr; @@ -213,34 +330,35 @@ protected slots: //! The main window GLView * ogl; - bool selecting; - bool initialShowEvent; + QGraphicsScene * graphicsScene; + GLGraphicsView * graphicsView; - FileSelector * lineLoad; - FileSelector * lineSave; + QComboBox * animGroups; + QAction * animGroupsAction; + + bool selecting = false; + bool initialShowEvent = true; + + QProgressBar * progress = nullptr; QDockWidget * dList; QDockWidget * dTree; + QDockWidget * dHeader; QDockWidget * dKfm; QDockWidget * dRefr; QDockWidget * dInsp; + QDockWidget * dBrowser; QToolBar * tool; QAction * aSanitize; - QAction * aLoadXML; - QAction * aReload; - QAction * aWindow; - QAction * aShredder; - QAction * aQuit; - - QAction * aLineLoad; - QAction * aLineSave; - QAction * aCpFileName; - -#ifdef FSENGINE - QAction * aResources; -#endif + + QAction * undoAction; + QAction * redoAction; + + QActionGroup * selectActions; + QActionGroup * showActions; + QActionGroup * shadingActions; QActionGroup * gListMode; QAction * aList; @@ -250,18 +368,53 @@ protected slots: QAction * aSelectFont; - QAction * aHelpWebsite; - QAction * aHelpForum; - QAction * aNifToolsWebsite; - QAction * aNifToolsDownloads; - - QAction * aNifSkope; - QAction * aAboutQt; - QMenu * mExport; QMenu * mImport; + + QAction * aRecentFilesSeparator; + + QAction * recentFileActs[NumRecentFiles]; + QAction * recentArchiveActs[NumRecentFiles]; + QAction * recentArchiveFileActs[NumRecentFiles]; + + bool isResizing; + QTimer * resizeTimer; + QImage buf; + + struct Settings + { + QLocale locale; + bool suppressSaveConfirm; + } cfg; + + //! The currently selected index + QModelIndex currentIdx; + + QUndoStack * indexStack; + //QAction * idxForwardAction; + //QAction * idxBackAction; + + BSAModel * bsaModel; + BSAProxyModel * bsaProxyModel; + QStandardItemModel * emptyModel; + + QMenu * mRecentArchiveFiles; +}; + + +class SelectIndexCommand : public QUndoCommand +{ +public: + SelectIndexCommand( NifSkope *, const QModelIndex &, const QModelIndex & ); + void redo() override; + void undo() override; +private: + QModelIndex curIdx, prevIdx; + + NifSkope * nifskope; }; + //! UDP communication between instances class IPCsocket final : public QObject { @@ -269,15 +422,17 @@ class IPCsocket final : public QObject public: //! Creates a socket - static IPCsocket * create(); + static IPCsocket * create( int port ); //! Sends a command - static void sendCommand( const QString & cmd ); + static void sendCommand( const QString & cmd, int port ); public slots: //! Acts on a command void execCommand( const QString & cmd ); + void openNif( const QUrl & ); + //! Opens a NIF from a URL void openNif( const QString & ); @@ -291,22 +446,4 @@ protected slots: QUdpSocket * socket; }; -//! Progress dialog -class ProgDlg final : public QProgressDialog -{ - Q_OBJECT - -public: - //! Constructor - ProgDlg() {} - -public slots: - //! Update progress - /*! - * \param x The amount done - * \param y The total amount - */ - void sltProgress( int x, int y ); -}; - #endif diff --git a/src/nifskope_ui.cpp b/src/nifskope_ui.cpp new file mode 100644 index 000000000..8cfb20b20 --- /dev/null +++ b/src/nifskope_ui.cpp @@ -0,0 +1,1354 @@ +/***** BEGIN LICENSE BLOCK ***** + +BSD License + +Copyright (c) 2005-2015, NIF File Format Library and Tools +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +3. The name of the NIF File Format Library and Tools project may not be +used to endorse or promote products derived from this software +without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +***** END LICENCE BLOCK *****/ + +#include "nifskope.h" +#include "version.h" +#include "settings.h" + +#include "ui_nifskope.h" +#include "ui/about_dialog.h" +#include "ui/settingsdialog.h" + +#include "glview.h" +#include "gl/glscene.h" +#include "kfmmodel.h" +#include "nifmodel.h" +#include "nifproxy.h" +#include "spellbook.h" +#include "widgets/fileselect.h" +#include "widgets/floatslider.h" +#include "widgets/floatedit.h" +#include "widgets/nifview.h" +#include "widgets/refrbrowser.h" +#include "widgets/inspect.h" +#include "widgets/xmlcheck.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + + +//! @file nifskope_ui.cpp UI logic for %NifSkope's main window. + +NifSkope * NifSkope::createWindow( const QString & fname ) +{ + NifSkope * skope = new NifSkope; + skope->setAttribute( Qt::WA_DeleteOnClose ); + skope->restoreUi(); + skope->show(); + skope->raise(); + + // Example Dark style + //QApplication::setStyle( QStyleFactory::create( "Fusion" ) ); + //QPalette darkPalette; + //darkPalette.setColor( QPalette::Window, QColor( 53, 53, 53 ) ); + //darkPalette.setColor( QPalette::WindowText, Qt::white ); + //darkPalette.setColor( QPalette::Base, QColor( 25, 25, 25 ) ); + //darkPalette.setColor( QPalette::AlternateBase, QColor( 53, 53, 53 ) ); + //darkPalette.setColor( QPalette::ToolTipBase, Qt::white ); + //darkPalette.setColor( QPalette::ToolTipText, Qt::white ); + //darkPalette.setColor( QPalette::Text, Qt::white ); + //darkPalette.setColor( QPalette::Button, QColor( 53, 53, 53 ) ); + //darkPalette.setColor( QPalette::ButtonText, Qt::white ); + //darkPalette.setColor( QPalette::BrightText, Qt::red ); + //darkPalette.setColor( QPalette::Link, QColor( 42, 130, 218 ) ); + //darkPalette.setColor( QPalette::Highlight, QColor( 42, 130, 218 ) ); + //darkPalette.setColor( QPalette::HighlightedText, Qt::black ); + //qApp->setPalette( darkPalette ); + //qApp->setStyleSheet( "QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }" ); + + if ( !fname.isEmpty() ) { + skope->loadFile( fname ); + } + + return skope; +} + +void NifSkope::initActions() +{ + aSanitize = ui->aSanitize; + aList = ui->aList; + aHierarchy = ui->aHierarchy; + aCondition = ui->aCondition; + aRCondition = ui->aRCondition; + aSelectFont = ui->aSelectFont; + + // Undo/Redo + undoAction = nif->undoStack->createUndoAction( this, tr( "&Undo" ) ); + undoAction->setShortcut( QKeySequence::Undo ); + undoAction->setIcon( QIcon( ":btn/undo" ) ); + redoAction = nif->undoStack->createRedoAction( this, tr( "&Redo" ) ); + redoAction->setShortcut( QKeySequence::Redo ); + redoAction->setIcon( QIcon( ":btn/redo" ) ); + + // TODO: Back/Forward button in Block List + //idxForwardAction = indexStack->createRedoAction( this ); + //idxBackAction = indexStack->createUndoAction( this ); + + ui->tFile->addAction( undoAction ); + ui->tFile->addAction( redoAction ); + + connect( undoAction, &QAction::triggered, [this]( bool ) { + ogl->update(); + } ); + + connect( redoAction, &QAction::triggered, [this]( bool ) { + ogl->update(); + } ); + + ui->aSave->setShortcut( QKeySequence::Save ); + ui->aSaveAs->setShortcut( { "Ctrl+Alt+S" } ); + ui->aWindow->setShortcut( QKeySequence::New ); + + connect( ui->aBrowseArchive, &QAction::triggered, this, &NifSkope::archiveDlg ); + connect( ui->aOpen, &QAction::triggered, this, &NifSkope::openDlg ); + connect( ui->aSave, &QAction::triggered, this, &NifSkope::save ); + connect( ui->aSaveAs, &QAction::triggered, this, &NifSkope::saveAsDlg ); + + // TODO: Assure Actions and Scene state are synced + // Set Data for Actions to pass onto Scene when clicking + /* + ShowAxes = 0x1, + ShowGrid = 0x2, + ShowNodes = 0x4, + ShowCollision = 0x8, + ShowConstraints = 0x10, + ShowMarkers = 0x20, + DoDoubleSided = 0x40, // Not implemented + DoVertexColors = 0x80, + DoSpecular = 0x100, + DoGlow = 0x200, + DoTexturing = 0x400, + DoBlending = 0x800, // Not implemented + DoMultisampling = 0x1000, // Not implemented + DoLighting = 0x2000, + DoCubeMapping = 0x4000, + DisableShaders = 0x8000, + ShowHidden = 0x10000 + */ + + ui->aShowAxes->setData( Scene::ShowAxes ); + ui->aShowGrid->setData( Scene::ShowGrid ); + ui->aShowNodes->setData( Scene::ShowNodes ); + ui->aShowCollision->setData( Scene::ShowCollision ); + ui->aShowConstraints->setData( Scene::ShowConstraints ); + ui->aShowMarkers->setData( Scene::ShowMarkers ); + ui->aShowHidden->setData( Scene::ShowHidden ); + ui->aDoSkinning->setData( Scene::DoSkinning ); + + ui->aTextures->setData( Scene::DoTexturing ); + ui->aVertexColors->setData( Scene::DoVertexColors ); + ui->aSpecular->setData( Scene::DoSpecular ); + ui->aGlow->setData( Scene::DoGlow ); + ui->aCubeMapping->setData( Scene::DoCubeMapping ); + ui->aLighting->setData( Scene::DoLighting ); + ui->aDisableShading->setData( Scene::DisableShaders ); + + ui->aSelectObject->setData( Scene::SelObject ); + ui->aSelectVertex->setData( Scene::SelVertex ); + + auto agroup = [this]( QVector actions, bool exclusive ) { + QActionGroup * ag = new QActionGroup( this ); + for ( auto a : actions ) { + ag->addAction( a ); + } + + ag->setExclusive( exclusive ); + + return ag; + }; + + selectActions = agroup( { ui->aSelectObject, ui->aSelectVertex }, true ); + connect( selectActions, &QActionGroup::triggered, ogl->getScene(), &Scene::updateSelectMode ); + + showActions = agroup( { ui->aShowAxes, ui->aShowGrid, ui->aShowNodes, ui->aShowCollision, + ui->aShowConstraints, ui->aShowMarkers, ui->aShowHidden, ui->aDoSkinning + }, false ); + connect( showActions, &QActionGroup::triggered, ogl->getScene(), &Scene::updateSceneOptionsGroup ); + + shadingActions = agroup( { ui->aTextures, ui->aVertexColors, ui->aSpecular, ui->aGlow, ui->aCubeMapping, ui->aLighting, ui->aDisableShading }, false ); + connect( shadingActions, &QActionGroup::triggered, ogl->getScene(), &Scene::updateSceneOptionsGroup ); + + auto testActions = agroup( { ui->aTest1Dbg, ui->aTest2Dbg, ui->aTest3Dbg }, true ); + connect( testActions, &QActionGroup::triggered, ogl->getScene(), &Scene::updateSceneOptionsGroup ); + + // Sync actions to Scene state + for ( auto a : showActions->actions() ) { + a->setChecked( ogl->scene->options & a->data().toInt() ); + } + + // Sync actions to Scene state + for ( auto a : shadingActions->actions() ) { + a->setChecked( ogl->scene->options & a->data().toInt() ); + } + + // Setup blank QActions for Recent Files menus + for ( int i = 0; i < NumRecentFiles; ++i ) { + recentFileActs[i] = new QAction( this ); + recentArchiveActs[i] = new QAction( this ); + recentArchiveFileActs[i] = new QAction( this ); + + recentFileActs[i]->setVisible( false ); + recentArchiveActs[i]->setVisible( false ); + recentArchiveFileActs[i]->setVisible( false ); + + connect( recentFileActs[i], &QAction::triggered, this, &NifSkope::openRecentFile ); + connect( recentArchiveActs[i], &QAction::triggered, this, &NifSkope::openRecentArchive ); + connect( recentArchiveFileActs[i], &QAction::triggered, this, &NifSkope::openRecentArchiveFile ); + } + + aList->setChecked( list->model() == nif ); + aHierarchy->setChecked( list->model() == proxy ); + + // Allow only List or Tree view to be selected at once + gListMode = new QActionGroup( this ); + gListMode->addAction( aList ); + gListMode->addAction( aHierarchy ); + gListMode->setExclusive( true ); + connect( gListMode, &QActionGroup::triggered, this, &NifSkope::setListMode ); + + connect( aCondition, &QAction::toggled, tree, &NifTreeView::setRowHiding ); + connect( aCondition, &QAction::toggled, kfmtree, &NifTreeView::setRowHiding ); + + connect( ui->aAboutNifSkope, &QAction::triggered, aboutDialog, &AboutDialog::show ); + connect( ui->aAboutQt, &QAction::triggered, qApp, &QApplication::aboutQt ); + + connect( ui->aPrintView, &QAction::triggered, ogl, &GLView::saveImage ); + +#ifdef QT_NO_DEBUG + ui->aColorKeyDebug->setDisabled( true ); + ui->aColorKeyDebug->setVisible( false ); + ui->aBoundsDebug->setDisabled( true ); + ui->aBoundsDebug->setVisible( false ); +#else + QAction * debugNone = new QAction( this ); + + QActionGroup * debugActions = agroup( { debugNone, ui->aColorKeyDebug, ui->aBoundsDebug }, false ); + connect( ui->aColorKeyDebug, &QAction::triggered, [this]( bool checked ) { + if ( checked ) + ogl->setDebugMode( GLView::DbgColorPicker ); + else + ogl->setDebugMode( GLView::DbgNone ); + + ogl->update(); + } ); + + connect( ui->aBoundsDebug, &QAction::triggered, [this]( bool checked ) { + if ( checked ) + ogl->setDebugMode( GLView::DbgBounds ); + else + ogl->setDebugMode( GLView::DbgNone ); + + ogl->update(); + } ); + + connect( debugActions, &QActionGroup::triggered, [=]( QAction * action ) { + for ( auto a : debugActions->actions() ) { + if ( a == action ) + continue; + + a->setChecked( false ); + } + } ); +#endif + + connect( ui->aSilhouette, &QAction::triggered, [this]( bool checked ) { + //ui->aDisableShading->setChecked( checked ); + ogl->setVisMode( Scene::VisSilhouette, checked ); + } ); + + connect( ui->aVisNormals, &QAction::triggered, [this]( bool checked ) { + ogl->setVisMode( Scene::VisNormalsOnly, checked ); + } ); + + connect( ogl, &GLView::clicked, this, &NifSkope::select ); + connect( ogl, &GLView::sceneTimeChanged, inspect, &InspectView::updateTime ); + connect( ogl, &GLView::paintUpdate, inspect, &InspectView::refresh ); + connect( ogl, &GLView::viewpointChanged, [this]() { + ui->aViewTop->setChecked( false ); + ui->aViewFront->setChecked( false ); + ui->aViewLeft->setChecked( false ); + ui->aViewUser->setChecked( false ); + + ogl->setOrientation( GLView::ViewDefault, false ); + } ); + + connect( graphicsView, &GLGraphicsView::customContextMenuRequested, this, &NifSkope::contextMenu ); + + // Update Inspector widget with current index + connect( tree, &NifTreeView::sigCurrentIndexChanged, inspect, &InspectView::updateSelection ); +} + +void NifSkope::initDockWidgets() +{ + dRefr = ui->RefrDock; + dList = ui->ListDock; + dTree = ui->TreeDock; + dHeader = ui->HeaderDock; + dInsp = ui->InspectDock; + dKfm = ui->KfmDock; + dBrowser = ui->BrowserDock; + + // Tabify List and Header + tabifyDockWidget( dList, dHeader ); + tabifyDockWidget( dHeader, dBrowser ); + + // Raise List above Header + dList->raise(); + + // Hide certain docks by default + dRefr->toggleViewAction()->setChecked( false ); + dInsp->toggleViewAction()->setChecked( false ); + dKfm->toggleViewAction()->setChecked( false ); + + dRefr->setVisible( false ); + dInsp->setVisible( false ); + dKfm->setVisible( false ); + + // Set Inspect widget + dInsp->setWidget( inspect ); + + connect( dList->toggleViewAction(), &QAction::triggered, tree, &NifTreeView::clearRootIndex ); + +} + +void NifSkope::initMenu() +{ + // Disable without NIF loaded + ui->mRender->setEnabled( false ); + + // Populate Toolbars menu with all enabled toolbars + for ( QObject * o : children() ) { + QToolBar * tb = qobject_cast(o); + if ( tb && tb->objectName() != "tFile" ) { + // Do not add tFile to the list + ui->mToolbars->addAction( tb->toggleViewAction() ); + } + } + + // Insert SpellBook class before Help + ui->menubar->insertMenu( ui->menubar->actions().at( 3 ), book.get() ); + + // Insert Import/Export menus + mExport = ui->menuExport; + mImport = ui->menuImport; + + fillImportExportMenus(); + connect( mExport, &QMenu::triggered, this, &NifSkope::sltImportExport ); + connect( mImport, &QMenu::triggered, this, &NifSkope::sltImportExport ); + + // BSA Recent Files + mRecentArchiveFiles = new QMenu( this ); + mRecentArchiveFiles->setObjectName( "mRecentArchiveFiles" ); + + for ( int i = 0; i < NumRecentFiles; ++i ) { + ui->mRecentFiles->addAction( recentFileActs[i] ); + ui->mRecentArchives->addAction( recentArchiveActs[i] ); + mRecentArchiveFiles->addAction( recentArchiveFileActs[i] ); + } + + // Load & Save + QMenu * mSave = new QMenu( this ); + mSave->setObjectName( "mSave" ); + + mSave->addAction( ui->aSave ); + mSave->addAction( ui->aSaveAs ); + + QMenu * mOpen = new QMenu( this ); + mOpen->setObjectName( "mOpen" ); + + mOpen->addAction( ui->aOpen ); + mOpen->addAction( ui->aBrowseArchive ); + + aRecentFilesSeparator = mOpen->addSeparator(); + for ( int i = 0; i < NumRecentFiles; ++i ) + mOpen->addAction( recentFileActs[i] ); + + // Append Menu to tFile actions + for ( auto child : ui->tFile->findChildren() ) { + + if ( child->defaultAction() == ui->aSaveMenu ) { + child->setMenu( mSave ); + child->setPopupMode( QToolButton::InstantPopup ); + child->setStyleSheet( "padding-left: 2px; padding-right: 10px;" ); + } + + if ( child->defaultAction() == ui->aOpenMenu ) { + child->setMenu( mOpen ); + child->setPopupMode( QToolButton::InstantPopup ); + child->setStyleSheet( "padding-left: 2px; padding-right: 10px;" ); + } + } + + updateRecentFileActions(); + updateRecentArchiveActions(); + //updateRecentArchiveFileActions(); + + // Lighting Menu + // TODO: Split off into own widget + auto mLight = lightingWidget(); + + // Append Menu to tFile actions + for ( auto child : ui->tRender->findChildren() ) { + + if ( child->defaultAction() == ui->aLightMenu ) { + child->setMenu( mLight ); + child->setPopupMode( QToolButton::InstantPopup ); + child->setStyleSheet( "padding-left: 2px; padding-right: 10px;" ); + } + } + + + // BSA Recent Archives + auto tRecentArchives = new QToolButton( this ); + tRecentArchives->setObjectName( "tRecentArchives" ); + tRecentArchives->setText( "Recent Archives" ); + tRecentArchives->setStyleSheet( "padding-right: 10px;" ); + tRecentArchives->setMenu( ui->mRecentArchives ); + tRecentArchives->setPopupMode( QToolButton::InstantPopup ); + + // BSA Recent Files + auto tRecentArchiveFiles = new QToolButton( this ); + tRecentArchiveFiles->setObjectName( "tRecentArchiveFiles" ); + tRecentArchiveFiles->setText( "Recent Files" ); + tRecentArchiveFiles->setStyleSheet( "padding-right: 10px;" ); + tRecentArchiveFiles->setMenu( mRecentArchiveFiles ); + tRecentArchiveFiles->setPopupMode( QToolButton::InstantPopup ); + + ui->bsaTitleBar->layout()->addWidget( tRecentArchives ); + ui->bsaTitleBar->layout()->addWidget( tRecentArchiveFiles ); +} + + +void NifSkope::initToolBars() +{ + // Disable without NIF loaded + ui->tRender->setEnabled( false ); + ui->tRender->setContextMenuPolicy( Qt::ActionsContextMenu ); + + // Status Bar + ui->statusbar->setContentsMargins( 0, 0, 0, 0 ); + ui->statusbar->addPermanentWidget( progress ); + + // TODO: Split off into own widget + ui->statusbar->addPermanentWidget( filePathWidget( this ) ); + + + // Render + + QActionGroup * grpView = new QActionGroup( this ); + grpView->addAction( ui->aViewTop ); + grpView->addAction( ui->aViewFront ); + grpView->addAction( ui->aViewLeft ); + grpView->addAction( ui->aViewWalk ); + grpView->setExclusive( true ); + + + // Animate + connect( ui->aAnimate, &QAction::toggled, ui->tAnim, &QToolBar::setVisible ); + connect( ui->tAnim, &QToolBar::visibilityChanged, ui->aAnimate, &QAction::setChecked ); + + /*enum AnimationStates + { + AnimDisabled = 0x0, + AnimEnabled = 0x1, + AnimPlay = 0x2, + AnimLoop = 0x4, + AnimSwitch = 0x8 + };*/ + + ui->aAnimate->setData( GLView::AnimEnabled ); + ui->aAnimPlay->setData( GLView::AnimPlay ); + ui->aAnimLoop->setData( GLView::AnimLoop ); + ui->aAnimSwitch->setData( GLView::AnimSwitch ); + + connect( ui->aAnimate, &QAction::toggled, ogl, &GLView::updateAnimationState ); + connect( ui->aAnimPlay, &QAction::triggered, ogl, &GLView::updateAnimationState ); + connect( ui->aAnimLoop, &QAction::toggled, ogl, &GLView::updateAnimationState ); + connect( ui->aAnimSwitch, &QAction::toggled, ogl, &GLView::updateAnimationState ); + + // Animation timeline slider + auto animSlider = new FloatSlider( Qt::Horizontal, true, true ); + auto animSliderEdit = new FloatEdit( ui->tAnim ); + + animSlider->addEditor( animSliderEdit ); + animSlider->setParent( ui->tAnim ); + animSlider->setMinimumWidth( 200 ); + animSlider->setMaximumWidth( 500 ); + animSlider->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::MinimumExpanding ); + + connect( ogl, &GLView::sceneTimeChanged, animSlider, &FloatSlider::set ); + connect( ogl, &GLView::sceneTimeChanged, animSliderEdit, &FloatEdit::set ); + connect( animSlider, &FloatSlider::valueChanged, ogl, &GLView::setSceneTime ); + connect( animSlider, &FloatSlider::valueChanged, animSliderEdit, &FloatEdit::setValue ); + connect( animSliderEdit, static_cast(&FloatEdit::sigEdited), ogl, &GLView::setSceneTime ); + connect( animSliderEdit, static_cast(&FloatEdit::sigEdited), animSlider, &FloatSlider::setValue ); + + // Animations + animGroups = new QComboBox( ui->tAnim ); + animGroups->setMinimumWidth( 60 ); + animGroups->setSizeAdjustPolicy( QComboBox::AdjustToContents ); + animGroups->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Minimum ); + connect( animGroups, static_cast(&QComboBox::activated), ogl, &GLView::setSceneSequence ); + + ui->tAnim->addWidget( animSlider ); + animGroupsAction = ui->tAnim->addWidget( animGroups ); + + connect( ogl, &GLView::sequencesDisabled, ui->tAnim, &QToolBar::hide ); + connect( ogl, &GLView::sequenceStopped, ui->aAnimPlay, &QAction::toggle ); + connect( ogl, &GLView::sequenceChanged, [this]( const QString & seqname ) { + animGroups->setCurrentIndex( ogl->getScene()->animGroups.indexOf( seqname ) ); + } ); + connect( ogl, &GLView::sequencesUpdated, [this]() { + ui->tAnim->show(); + + animGroups->clear(); + animGroups->addItems( ogl->getScene()->animGroups ); + animGroups->setCurrentIndex( ogl->getScene()->animGroups.indexOf( ogl->getScene()->animGroup ) ); + + if ( animGroups->count() == 0 ) { + animGroupsAction->setVisible( false ); + ui->aAnimSwitch->setVisible( false ); + } else { + ui->aAnimSwitch->setVisible( animGroups->count() != 1 ); + animGroupsAction->setVisible( true ); + animGroups->adjustSize(); + } + } ); + + + // LOD Toolbar + QToolBar * tLOD = ui->tLOD; + + QSettings cfg; + int lodLevel = cfg.value( "GLView/LOD Level", 2 ).toInt(); + cfg.setValue( "GLView/LOD Level", lodLevel ); + + QSlider * lodSlider = new QSlider( Qt::Horizontal ); + lodSlider->setFocusPolicy( Qt::StrongFocus ); + lodSlider->setTickPosition( QSlider::TicksBelow ); + lodSlider->setTickInterval( 1 ); + lodSlider->setSingleStep( 1 ); + lodSlider->setMinimum( 0 ); + lodSlider->setMaximum( 2 ); + lodSlider->setValue( lodLevel ); + + tLOD->addWidget( lodSlider ); + tLOD->setEnabled( false ); + + connect( lodSlider, &QSlider::valueChanged, ogl->getScene(), &Scene::updateLodLevel ); + connect( lodSlider, &QSlider::valueChanged, ogl, &GLView::updateGL ); + connect( nif, &NifModel::lodSliderChanged, [tLOD]( bool enabled ) { tLOD->setEnabled( enabled ); tLOD->setVisible( enabled ); } ); +} + +void NifSkope::initConnections() +{ + connect( nif, &NifModel::beginUpdateHeader, this, &NifSkope::enableUi ); + + connect( this, &NifSkope::beginLoading, this, &NifSkope::onLoadBegin ); + connect( this, &NifSkope::beginSave, this, &NifSkope::onSaveBegin ); + + connect( this, &NifSkope::completeLoading, this, &NifSkope::onLoadComplete ); + connect( this, &NifSkope::completeSave, this, &NifSkope::onSaveComplete ); +} + + +QMenu * NifSkope::lightingWidget() +{ + QMenu * mLight = new QMenu( this ); + mLight->setObjectName( "mLight" ); + + mLight->setStyleSheet( + R"qss(#mLight { background: #f5f5f5; padding: 8px; border: 1px solid #CCC; })qss" + ); + + auto onOffWidget = new QWidget; + onOffWidget->setContentsMargins( 0, 0, 0, 0 ); + auto onOffLayout = new QHBoxLayout; + onOffLayout->setContentsMargins( 0, 0, 0, 0 ); + onOffWidget->setLayout( onOffLayout ); + + auto chkLighting = new QToolButton( mLight ); + chkLighting->setObjectName( "chkLighting" ); + chkLighting->setCheckable( true ); + chkLighting->setChecked( true ); + chkLighting->setDefaultAction( ui->aLighting ); + chkLighting->setIconSize( QSize( 18, 18 ) ); + chkLighting->setToolButtonStyle( Qt::ToolButtonTextBesideIcon ); + //chkLighting->setText( " " + ui->aLighting->text() ); // Resets during toggle + //chkLighting->setStatusTip( ui->aLighting->statusTip() ); Doesn't work + chkLighting->setStyleSheet( R"qss( + #chkLighting { padding: 5px; } + )qss" ); + + auto lightingOptionsWidget = new QWidgetAction( mLight ); + + auto optionsWidget = new QWidget; + optionsWidget->setContentsMargins( 0, 0, 0, 0 ); + auto optionsLayout = new QHBoxLayout; + optionsLayout->setContentsMargins( 0, 0, 0, 0 ); + optionsWidget->setLayout( optionsLayout ); + + + auto chkFrontal = new QToolButton( mLight ); + chkFrontal->setObjectName( "chkFrontal" ); + chkFrontal->setCheckable( true ); + chkFrontal->setChecked( true ); + chkFrontal->setDefaultAction( ui->aFrontalLight ); + //chkFrontal->setIconSize( QSize( 16, 16 ) ); + //chkFrontal->setToolButtonStyle( Qt::ToolButtonTextBesideIcon ); + //chkFrontal->setStatusTip( ui->aFrontalLight->statusTip() ); Doesn't work + chkFrontal->setStyleSheet( R"qss( #chkFrontal { padding: 5px; } )qss" ); + + onOffLayout->addWidget( chkLighting ); + onOffLayout->addWidget( chkFrontal ); + + // Slider lambda + auto sld = [this]( QWidget * parent, const QString & img, int min, int max, int val ) { + + auto slider = new QSlider( Qt::Horizontal, parent ); + slider->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Maximum ); + slider->setRange( min, max ); + slider->setSingleStep( max / 4 ); + slider->setTickInterval( max / 2 ); + slider->setTickPosition( QSlider::TicksBelow ); + slider->setValue( val ); + slider->setStyleSheet( "background: transparent url(" + img + ");" + R"qss( + background-repeat: no-repeat; + background-position: left; + background-origin: margin; + background-clip: margin; + margin-left: 20px; + )qss" + ); + + return slider; + }; + + // Lighting position, uses -720 to 720 and GLView divides it by 4 + // because QSlider uses integers and we'd like less choppy motion + auto sldDeclination = sld( chkFrontal, ":/btn/lightVertical", -720, 720, 0 ); + auto sldPlanarAngle = sld( chkFrontal, ":/btn/lightHorizontal", -720, 720, 0 ); + auto sldBrightness = sld( chkFrontal, ":/btn/sun", 0, 1440, 720 ); + auto sldAmbient = sld( chkFrontal, ":/btn/cloud", 0, 1440, 540 ); + + sldDeclination->setDisabled( true ); + sldPlanarAngle->setDisabled( true ); + + // Button lambda + auto btn = [this]( QAction * act, const QString & name, QMenu * menu ) { + + auto button = new QToolButton( menu ); + button->setObjectName( name ); + button->setCheckable( true ); + button->setChecked( true ); + button->setDefaultAction( act ); + button->setIconSize( QSize( 16, 16 ) ); + //button->setStatusTip( ui->aTextures->statusTip() ); Doesn't work + button->setStyleSheet( R"qss( padding: 3px 2px 2px 3px; )qss" ); + + return button; + }; + + auto chkTextures = btn( ui->aTextures, "chkTextures", mLight ); + auto chkVertexColors = btn( ui->aVertexColors, "chkVertexColors", mLight ); + auto chkNormalsOnly = btn( ui->aVisNormals, "chkNormalsOnly", mLight ); + + optionsLayout->addWidget( chkTextures ); + optionsLayout->addWidget( chkVertexColors ); + optionsLayout->addWidget( chkNormalsOnly ); + + lightingOptionsWidget->setDefaultWidget( optionsWidget ); + + auto lightingGroup = new QGroupBox( mLight ); + lightingGroup->setObjectName( "lightingGroup" ); + lightingGroup->setContentsMargins( 0, 0, 0, 0 ); + lightingGroup->setStyleSheet( R"qss( #lightingGroup { padding: 0; border: none; } )qss" ); + //lightingGroup->setDisabled( true ); + + auto lightingGroupVbox = new QVBoxLayout; + lightingGroupVbox->setContentsMargins( 0, 0, 0, 0 ); + lightingGroupVbox->setSpacing( 0 ); + lightingGroup->setLayout( lightingGroupVbox ); + + lightingGroupVbox->addWidget( sldBrightness ); + lightingGroupVbox->addWidget( sldAmbient ); + lightingGroupVbox->addWidget( sldDeclination ); + lightingGroupVbox->addWidget( sldPlanarAngle ); + + // Disable lighting sliders when Frontal + connect( chkFrontal, &QToolButton::toggled, sldDeclination, &QSlider::setDisabled ); + connect( chkFrontal, &QToolButton::toggled, sldPlanarAngle, &QSlider::setDisabled ); + + // Disable Frontal checkbox (and sliders) when no lighting + connect( chkLighting, &QToolButton::toggled, chkFrontal, &QToolButton::setEnabled ); + connect( chkLighting, &QToolButton::toggled, ui->aVisNormals, &QAction::setEnabled ); + connect( chkLighting, &QToolButton::toggled, [sldDeclination, sldPlanarAngle, chkFrontal]( bool checked ) { + if ( !chkFrontal->isChecked() ) { + // Don't enable the sliders if Frontal is checked + sldDeclination->setEnabled( checked ); + sldDeclination->setEnabled( checked ); + } + } ); + + // Inform ogl of changes + //connect( chkLighting, &QCheckBox::toggled, ogl, &GLView::lightingToggled ); + connect( sldBrightness, &QSlider::valueChanged, ogl, &GLView::setBrightness ); + connect( sldAmbient, &QSlider::valueChanged, ogl, &GLView::setAmbient ); + connect( sldDeclination, &QSlider::valueChanged, ogl, &GLView::setDeclination ); + connect( sldPlanarAngle, &QSlider::valueChanged, ogl, &GLView::setPlanarAngle ); + connect( chkFrontal, &QToolButton::toggled, ogl, &GLView::setFrontalLight ); + + + // Set up QWidgetActions so they can be added to a QMenu + auto lightingWidget = new QWidgetAction( mLight ); + lightingWidget->setDefaultWidget( onOffWidget ); + + auto lightingAngleWidget = new QWidgetAction( mLight ); + lightingAngleWidget->setDefaultWidget( lightingGroup ); + + mLight->addAction( lightingWidget ); + mLight->addAction( lightingAngleWidget ); + mLight->addAction( lightingOptionsWidget ); + + return mLight; +} + + +QWidget * NifSkope::filePathWidget( QWidget * parent ) +{ + // Show Filepath of loaded NIF + auto filepathWidget = new QWidget( parent ); + filepathWidget->setObjectName( "filepathStatusbarWidget" ); + auto filepathLayout = new QHBoxLayout( filepathWidget ); + filepathWidget->setLayout( filepathLayout ); + filepathLayout->setContentsMargins( 0, 0, 0, 0 ); + auto labelFilepath = new QLabel( "", filepathWidget ); + labelFilepath->setMinimumHeight( 16 ); + + filepathLayout->addWidget( labelFilepath ); + + // Navigate to Filepath + auto navigateToFilepath = new QPushButton( "", filepathWidget ); + navigateToFilepath->setFlat( true ); + navigateToFilepath->setIcon( QIcon( ":btn/loadDark" ) ); + navigateToFilepath->setIconSize( QSize( 16, 16 ) ); + navigateToFilepath->setStatusTip( tr( "Show in Explorer" ) ); + + filepathLayout->addWidget( navigateToFilepath ); + + filepathWidget->setVisible( false ); + + // Show Filepath on successful NIF load + connect( this, &NifSkope::completeLoading, [this, filepathWidget, labelFilepath, navigateToFilepath]( bool success, QString & fname ) { + filepathWidget->setVisible( success ); + labelFilepath->setText( fname ); + + if ( QFileInfo( fname ).exists() ) { + navigateToFilepath->show(); + } else { + navigateToFilepath->hide(); + } + } ); + + // Change Filepath on successful NIF save + connect( this, &NifSkope::completeSave, [this, filepathWidget, labelFilepath, navigateToFilepath]( bool success, QString & fname ) { + filepathWidget->setVisible( success ); + labelFilepath->setText( fname ); + + if ( QFileInfo( fname ).exists() ) { + navigateToFilepath->show(); + } else { + navigateToFilepath->hide(); + } + } ); + + // Navigate to NIF in Explorer + connect( navigateToFilepath, &QPushButton::clicked, [this]() { +#ifdef Q_OS_WIN + QStringList args; + args << "/select," << QDir::toNativeSeparators( currentFile ); + QProcess::startDetached( "explorer", args ); +#endif + } ); + + + return filepathWidget; +} + + +void NifSkope::archiveDlg() +{ + QString file = QFileDialog::getOpenFileName( this, tr( "Open Archive" ), "", "Archives (*.bsa *.ba2)" ); + if ( !file.isEmpty() ) + openArchive( file ); +} + +void NifSkope::openDlg() +{ + // Grab most recent filepath if blank window + auto path = nif->getFileInfo().absolutePath(); + path = (path.isEmpty()) ? recentFileActs[0]->data().toString() : path; + + if ( !saveConfirm() ) + return; + + QStringList files = QFileDialog::getOpenFileNames( this, tr( "Open File" ), path, fileFilters() ); + if ( !files.isEmpty() ) + openFiles( files ); +} + +void NifSkope::onLoadBegin() +{ + // Disconnect the models from the views + swapModels(); + + setEnabled( false ); + ui->tAnim->setEnabled( false ); + + progress->setVisible( true ); + progress->reset(); +} + +void NifSkope::onLoadComplete( bool success, QString & fname ) +{ + QApplication::restoreOverrideCursor(); + + // Reconnect the models to the views + swapModels(); + + // Re-enable window + setEnabled( true ); // IMPORTANT! + + int timeout = 2500; + if ( success ) { + // Scroll panel back to top + tree->scrollTo( nif->index( 0, 0 ) ); + + select( nif->getHeader() ); + + header->setRootIndex( nif->getHeader() ); + ogl->setOrientation( GLView::ViewFront ); + + enableUi(); + + } else { + // File failed to load + Message::critical( this, tr( "Failed to load %1" ).arg( fname ) ); + + nif->clear(); + kfm->clear(); + timeout = 0; + + // Remove from Current Files + clearCurrentFile(); + + // Reset + currentFile = ""; + setWindowFilePath( "" ); + progress->reset(); + } + + // Mark window as unmodified + setWindowModified( false ); + nif->undoStack->clear(); + indexStack->clear(); + + // Center the model on load + ogl->center(); + + // Hide Progress Bar + QTimer::singleShot( timeout, progress, SLOT( hide() ) ); +} + + +void NifSkope::saveAsDlg() +{ + QString filename = QFileDialog::getSaveFileName( this, tr( "Save File" ), nif->getFileInfo().absoluteFilePath(), + fileFilters( false ), + new QString( fileFilter( nif->getFileInfo().suffix() ) ) + ); + + if ( filename.isEmpty() ) + return; + + saveFile( filename ); +} + +void NifSkope::onSaveBegin() +{ + setEnabled( false ); +} + +void NifSkope::onSaveComplete( bool success, QString & fname ) +{ + setEnabled( true ); + + if ( success ) { + // Update if Save As results in filename change + setWindowFilePath( fname ); + // Mark window as unmodified + nif->undoStack->setClean(); + setWindowModified( false ); + } +} + +bool NifSkope::saveConfirm() +{ + if ( !cfg.suppressSaveConfirm && (isWindowModified() || !nif->undoStack->isClean()) ) { + QMessageBox::StandardButton response; + response = QMessageBox::question( this, + tr( "Save Confirmation" ), + tr( "

    You have unsaved changes to %1.

    Would you like to save them now?" ).arg( nif->getFileInfo().completeBaseName() ), + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::No ); + + if ( response == QMessageBox::Yes ) { + saveAsDlg(); + return true; + } else if ( response == QMessageBox::No ) { + return true; + } else if ( response == QMessageBox::Cancel ) { + return false; + } + } + + return true; +} + +void NifSkope::enableUi() +{ + // Re-enable toolbars, actions, and menus + ui->aSaveMenu->setEnabled( true ); + ui->aSave->setEnabled( true ); + ui->aSaveAs->setEnabled( true ); + ui->aHeader->setEnabled( true ); + + ui->mRender->setEnabled( true ); + ui->tAnim->setEnabled( true ); + animGroups->clear(); + + + ui->tRender->setEnabled( true ); + + // We only need to enable the UI once, disconnect + disconnect( nif, &NifModel::beginUpdateHeader, this, &NifSkope::enableUi ); +} + +void NifSkope::saveUi() const +{ + QSettings settings; + // TODO: saveState takes a version number which can be incremented between releases if necessary + settings.setValue( "UI/Window State", saveState( 0x073 ) ); + settings.setValue( "UI/Window Geometry", saveGeometry() ); + + settings.setValue( "File/Auto Sanitize", aSanitize->isChecked() ); + + settings.setValue( "UI/List Mode", (gListMode->checkedAction() == aList ? "list" : "hierarchy") ); + settings.setValue( "UI/Show Non-applicable Rows", aCondition->isChecked() ); + + settings.setValue( "UI/List Header", list->header()->saveState() ); + settings.setValue( "UI/Tree Header", tree->header()->saveState() ); + settings.setValue( "UI/Header Header", header->header()->saveState() ); + settings.setValue( "UI/Kfmtree Header", kfmtree->header()->saveState() ); + + settings.setValue( "GLView/Enable Animations", ui->aAnimate->isChecked() ); + //settings.setValue( "GLView/Play Animation", ui->aAnimPlay->isChecked() ); + //settings.setValue( "GLView/Loop Animation", ui->aAnimLoop->isChecked() ); + //settings.setValue( "GLView/Switch Animation", ui->aAnimSwitch->isChecked() ); + settings.setValue( "GLView/Perspective", ui->aViewPerspective->isChecked() ); +} + + +void NifSkope::restoreUi() +{ + QSettings settings; + restoreGeometry( settings.value( "UI/Window Geometry" ).toByteArray() ); + restoreState( settings.value( "UI/Window State" ).toByteArray(), 0x073 ); + + aSanitize->setChecked( settings.value( "File/Auto Sanitize", true ).toBool() ); + + if ( settings.value( "UI/List Mode", "hierarchy" ).toString() == "list" ) + aList->setChecked( true ); + else + aHierarchy->setChecked( true ); + + setListMode(); + + aCondition->setChecked( settings.value( "UI/Show Non-applicable Rows", false ).toBool() ); + + list->header()->restoreState( settings.value( "UI/List Header" ).toByteArray() ); + tree->header()->restoreState( settings.value( "UI/Tree Header" ).toByteArray() ); + header->header()->restoreState( settings.value( "UI/Header Header" ).toByteArray() ); + kfmtree->header()->restoreState( settings.value( "UI/Kfmtree Header" ).toByteArray() ); + + auto hideSections = []( NifTreeView * tree, bool hidden ) { + tree->header()->setSectionHidden( NifModel::ArgCol, hidden ); + tree->header()->setSectionHidden( NifModel::Arr1Col, hidden ); + tree->header()->setSectionHidden( NifModel::Arr2Col, hidden ); + tree->header()->setSectionHidden( NifModel::CondCol, hidden ); + tree->header()->setSectionHidden( NifModel::Ver1Col, hidden ); + tree->header()->setSectionHidden( NifModel::Ver2Col, hidden ); + tree->header()->setSectionHidden( NifModel::VerCondCol, hidden ); + }; + + // Hide advanced metadata loaded from nif.xml as it's not useful or necessary for editing + if ( settings.value( "Settings/Nif/Hide metadata columns", true ).toBool() ) { + hideSections( tree, true ); + hideSections( header, true ); + } else { + // Unhide here, or header()->restoreState() will keep them perpetually hidden + hideSections( tree, false ); + hideSections( header, false ); + } + + ui->aAnimate->setChecked( settings.value( "GLView/Enable Animations", true ).toBool() ); + //ui->aAnimPlay->setChecked( settings.value( "GLView/Play Animation", true ).toBool() ); + //ui->aAnimLoop->setChecked( settings.value( "GLView/Loop Animation", true ).toBool() ); + //ui->aAnimSwitch->setChecked( settings.value( "GLView/Switch Animation", true ).toBool() ); + + auto isPersp = settings.value( "GLView/Perspective", true ).toBool(); + ui->aViewPerspective->setChecked( isPersp ); + + ogl->setProjection( isPersp ); + + QVariant fontVar = settings.value( "UI/View Font" ); + + if ( fontVar.canConvert() ) + setViewFont( fontVar.value() ); + + // Modify UI settings that cannot be set in Designer + tabifyDockWidget( ui->InspectDock, ui->KfmDock ); +} + +void NifSkope::setViewFont( const QFont & font ) +{ + list->setFont( font ); + QFontMetrics metrics( list->font() ); + list->setIconSize( QSize( metrics.width( "000" ), metrics.lineSpacing() ) ); + tree->setFont( font ); + tree->setIconSize( QSize( metrics.width( "000" ), metrics.lineSpacing() ) ); + header->setFont( font ); + header->setIconSize( QSize( metrics.width( "000" ), metrics.lineSpacing() ) ); + kfmtree->setFont( font ); + kfmtree->setIconSize( QSize( metrics.width( "000" ), metrics.lineSpacing() ) ); + ogl->setFont( font ); +} + +void NifSkope::resizeDone() +{ + isResizing = false; + + // Unhide GLView, update GLGraphicsView + ogl->show(); + graphicsScene->setSceneRect( graphicsView->rect() ); + graphicsView->fitInView( graphicsScene->sceneRect() ); + + ogl->setUpdatesEnabled( true ); + ogl->setDisabled( false ); + ogl->getScene()->animate = true; + ogl->update(); + ogl->resizeGL( centralWidget()->width(), centralWidget()->height() ); +} + + +bool NifSkope::eventFilter( QObject * o, QEvent * e ) +{ + // TODO: This doesn't seem to be doing anything extra + //if ( e->type() == QEvent::Polish ) { + // QTimer::singleShot( 0, this, SLOT( overrideViewFont() ) ); + //} + + // Global mouse press + if ( o->isWindowType() && e->type() == QEvent::MouseButtonPress ) { + //qDebug() << "Mouse Press"; + } + // Global mouse release + if ( o->isWindowType() && e->type() == QEvent::MouseButtonRelease ) { + //qDebug() << "Mouse Release"; + + // Back/Forward button support for cycling through indices + auto mouseEvent = static_cast(e); + if ( mouseEvent ) { + if ( mouseEvent->button() == Qt::ForwardButton ) { + mouseEvent->accept(); + indexStack->redo(); + } + + if ( mouseEvent->button() == Qt::BackButton ) { + mouseEvent->accept(); + indexStack->undo(); + } + } + } + + // Filter GLGraphicsView + auto obj = qobject_cast(o); + if ( !obj || obj != graphicsView ) + return QMainWindow::eventFilter( o, e ); + + // Turn off animation + // Grab framebuffer + // Begin resize timer + // Block all Resize Events to GLView + if ( e->type() == QEvent::Resize ) { + // Hide GLView + ogl->hide(); + + if ( !isResizing && !resizeTimer->isActive() ) { + ogl->getScene()->animate = false; + ogl->updateGL(); + + if ( buf.isNull() ) { + // Init initial buffer with solid color + // Otherwise becomes random colors on release builds + buf = QImage( 10, 10, QImage::Format_ARGB32 ); + buf.fill( ogl->clearColor() ); + } else { + buf = ogl->grabFrameBuffer(); + } + + ogl->setUpdatesEnabled( false ); + ogl->setDisabled( true ); + + isResizing = true; + resizeTimer->start( 300 ); + } + + return true; + } + + // Paint stored framebuffer over GLGraphicsView while resizing + if ( !buf.isNull() && isResizing && e->type() == QEvent::Paint ) { + QPainter painter; + painter.begin( graphicsView ); + painter.drawImage( QRect( 0, 0, painter.device()->width(), painter.device()->height() ), buf ); + painter.end(); + + return true; + } + + return QMainWindow::eventFilter( o, e ); +} + + +/* +* Slots +* ********************** +*/ + + +void NifSkope::contextMenu( const QPoint & pos ) +{ + QModelIndex idx; + QPoint p = pos; + + if ( sender() == tree ) { + idx = tree->indexAt( pos ); + p = tree->mapToGlobal( pos ); + } else if ( sender() == list ) { + idx = list->indexAt( pos ); + p = list->mapToGlobal( pos ); + } else if ( sender() == header ) { + idx = header->indexAt( pos ); + p = header->mapToGlobal( pos ); + } else if ( sender() == graphicsView ) { + idx = ogl->indexAt( pos ); + p = graphicsView->mapToGlobal( pos ); + } else { + return; + } + + while ( idx.model() && idx.model()->inherits( "NifProxyModel" ) ) { + idx = qobject_cast(idx.model())->mapTo( idx ); + } + + SpellBook contextBook( nif, idx, this, SLOT( select( const QModelIndex & ) ) ); + + if ( !idx.isValid() || nif->flags( idx ) & Qt::ItemIsEditable ) + contextBook.exec( p ); +} + +void NifSkope::overrideViewFont() +{ + QSettings settings; + QVariant var = settings.value( "UI/View Font" ); + + if ( var.canConvert() ) { + setViewFont( var.value() ); + } +} + + +/* +* Automatic Slots +* ********************** +*/ + + +void NifSkope::on_aLoadXML_triggered() +{ + NifModel::loadXML(); + KfmModel::loadXML(); +} + +void NifSkope::on_aReload_triggered() +{ + if ( NifModel::loadXML() ) { + reload(); + } +} + +void NifSkope::on_aSelectFont_triggered() +{ + bool ok; + QFont fnt = QFontDialog::getFont( &ok, list->font(), this ); + + if ( !ok ) + return; + + setViewFont( fnt ); + QSettings settings; + settings.setValue( "UI/View Font", fnt ); +} + +void NifSkope::on_aWindow_triggered() +{ + createWindow(); +} + +void NifSkope::on_aShredder_triggered() +{ + TestShredder::create(); +} + +void NifSkope::on_aHeader_triggered() +{ + if ( tree ) + tree->clearRootIndex(); + + select( nif->getHeader() ); +} + + +void NifSkope::on_tRender_actionTriggered( QAction * action ) +{ + Q_UNUSED( action ); +} + +void NifSkope::on_aViewTop_triggered( bool checked ) +{ + if ( checked ) { + ogl->setOrientation( GLView::ViewTop ); + } +} + +void NifSkope::on_aViewFront_triggered( bool checked ) +{ + if ( checked ) { + ogl->setOrientation( GLView::ViewFront ); + } +} + +void NifSkope::on_aViewLeft_triggered( bool checked ) +{ + if ( checked ) { + ogl->setOrientation( GLView::ViewLeft ); + } +} + +void NifSkope::on_aViewCenter_triggered() +{ + ogl->center(); +} + +void NifSkope::on_aViewFlip_triggered( bool checked ) +{ + Q_UNUSED( checked ); + ogl->flipOrientation(); +} + +void NifSkope::on_aViewPerspective_toggled( bool checked ) +{ + ogl->setProjection( checked ); +} + +void NifSkope::on_aViewWalk_triggered( bool checked ) +{ + if ( checked ) { + ogl->setOrientation( GLView::ViewWalk ); + } +} + + +void NifSkope::on_aViewUserSave_triggered( bool checked ) +{ + Q_UNUSED( checked ); + ogl->saveUserView(); + ui->aViewUser->setChecked( true ); +} + + +void NifSkope::on_aViewUser_toggled( bool checked ) +{ + if ( checked ) { + ogl->setOrientation( GLView::ViewUser, false ); + ogl->loadUserView(); + } +} + +void NifSkope::on_aSettings_triggered() +{ + options->show(); + options->raise(); + options->activateWindow(); +} diff --git a/src/niftypes.cpp b/src/niftypes.cpp index 57e4a9b18..d7d034ef5 100644 --- a/src/niftypes.cpp +++ b/src/niftypes.cpp @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -37,6 +37,8 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include +//! @file niftypes.cpp Type functions + const float Quat::identity[4] = { 1.0, 0.0, 0.0, 0.0 }; @@ -295,6 +297,16 @@ QString Matrix::toHtml() const + QString( "" ); } +QString Matrix::toRaw() const +{ + return QString( "%1, %2, %3\r\n" ) + .arg( m[0][0], 0, 'f', 4 ).arg( m[0][1], 0, 'f', 4 ).arg( m[0][2], 0, 'f', 4 ) + + QString( "%1, %2, %3\r\n" ) + .arg( m[1][0], 0, 'f', 4 ).arg( m[1][1], 0, 'f', 4 ).arg( m[1][2], 0, 'f', 4 ) + + QString( "%1, %2, %3\r\n" ) + .arg( m[2][0], 0, 'f', 4 ).arg( m[2][1], 0, 'f', 4 ).arg( m[2][2], 0, 'f', 4 ); +} + QString Matrix4::toHtml() const { return QString( "" ) @@ -328,7 +340,7 @@ void Matrix4::decompose( Vector3 & trans, Matrix & rot, Vector3 & scale ) const Matrix mtx = rot * rotT; - scale = { sqrt(mtx( 0, 0 )), sqrt(mtx( 1, 1 )), sqrt(mtx( 2, 2 )) }; + scale = { (float)sqrt(mtx( 0, 0 )), (float)sqrt(mtx( 1, 1 )), (float)sqrt(mtx( 2, 2 )) }; for ( int i = 0; i < 3; i++ ) { for ( int j = 0; j < 3; j++ ) @@ -350,9 +362,164 @@ void Matrix4::compose( const Vector3 & trans, const Matrix & rot, const Vector3 for ( int i = 0; i < 3; i++ ) { for ( int j = 0; j < 3; j++ ) - m[ i ][ j ] = rot( j, i ) * scale[ j ]; + m[i][j] = rot( j, i ) * scale[j]; + } +} + +bool gluInvertMatrix( const float m[16], float invOut[16] ) +{ + float inv[16], det; + int i; + + inv[0] = m[5] * m[10] * m[15] - + m[5] * m[11] * m[14] - + m[9] * m[6] * m[15] + + m[9] * m[7] * m[14] + + m[13] * m[6] * m[11] - + m[13] * m[7] * m[10]; + + inv[4] = -m[4] * m[10] * m[15] + + m[4] * m[11] * m[14] + + m[8] * m[6] * m[15] - + m[8] * m[7] * m[14] - + m[12] * m[6] * m[11] + + m[12] * m[7] * m[10]; + + inv[8] = m[4] * m[9] * m[15] - + m[4] * m[11] * m[13] - + m[8] * m[5] * m[15] + + m[8] * m[7] * m[13] + + m[12] * m[5] * m[11] - + m[12] * m[7] * m[9]; + + inv[12] = -m[4] * m[9] * m[14] + + m[4] * m[10] * m[13] + + m[8] * m[5] * m[14] - + m[8] * m[6] * m[13] - + m[12] * m[5] * m[10] + + m[12] * m[6] * m[9]; + + inv[1] = -m[1] * m[10] * m[15] + + m[1] * m[11] * m[14] + + m[9] * m[2] * m[15] - + m[9] * m[3] * m[14] - + m[13] * m[2] * m[11] + + m[13] * m[3] * m[10]; + + inv[5] = m[0] * m[10] * m[15] - + m[0] * m[11] * m[14] - + m[8] * m[2] * m[15] + + m[8] * m[3] * m[14] + + m[12] * m[2] * m[11] - + m[12] * m[3] * m[10]; + + inv[9] = -m[0] * m[9] * m[15] + + m[0] * m[11] * m[13] + + m[8] * m[1] * m[15] - + m[8] * m[3] * m[13] - + m[12] * m[1] * m[11] + + m[12] * m[3] * m[9]; + + inv[13] = m[0] * m[9] * m[14] - + m[0] * m[10] * m[13] - + m[8] * m[1] * m[14] + + m[8] * m[2] * m[13] + + m[12] * m[1] * m[10] - + m[12] * m[2] * m[9]; + + inv[2] = m[1] * m[6] * m[15] - + m[1] * m[7] * m[14] - + m[5] * m[2] * m[15] + + m[5] * m[3] * m[14] + + m[13] * m[2] * m[7] - + m[13] * m[3] * m[6]; + + inv[6] = -m[0] * m[6] * m[15] + + m[0] * m[7] * m[14] + + m[4] * m[2] * m[15] - + m[4] * m[3] * m[14] - + m[12] * m[2] * m[7] + + m[12] * m[3] * m[6]; + + inv[10] = m[0] * m[5] * m[15] - + m[0] * m[7] * m[13] - + m[4] * m[1] * m[15] + + m[4] * m[3] * m[13] + + m[12] * m[1] * m[7] - + m[12] * m[3] * m[5]; + + inv[14] = -m[0] * m[5] * m[14] + + m[0] * m[6] * m[13] + + m[4] * m[1] * m[14] - + m[4] * m[2] * m[13] - + m[12] * m[1] * m[6] + + m[12] * m[2] * m[5]; + + inv[3] = -m[1] * m[6] * m[11] + + m[1] * m[7] * m[10] + + m[5] * m[2] * m[11] - + m[5] * m[3] * m[10] - + m[9] * m[2] * m[7] + + m[9] * m[3] * m[6]; + + inv[7] = m[0] * m[6] * m[11] - + m[0] * m[7] * m[10] - + m[4] * m[2] * m[11] + + m[4] * m[3] * m[10] + + m[8] * m[2] * m[7] - + m[8] * m[3] * m[6]; + + inv[11] = -m[0] * m[5] * m[11] + + m[0] * m[7] * m[9] + + m[4] * m[1] * m[11] - + m[4] * m[3] * m[9] - + m[8] * m[1] * m[7] + + m[8] * m[3] * m[5]; + + inv[15] = m[0] * m[5] * m[10] - + m[0] * m[6] * m[9] - + m[4] * m[1] * m[10] + + m[4] * m[2] * m[9] + + m[8] * m[1] * m[6] - + m[8] * m[2] * m[5]; + + det = m[0] * inv[0] + m[1] * inv[4] + m[2] * inv[8] + m[3] * inv[12]; + + if ( det == 0 ) + return false; + + det = 1.0 / det; + + for ( i = 0; i < 16; i++ ) + invOut[i] = inv[i] * det; + + return true; +} + +Matrix4 Matrix4::inverted() const +{ + float mat[16]; + float matInv[16]; + + for ( int i = 0; i < 4; i++ ) { + mat[4*i] = m[i][0]; + mat[4*i+1] = m[i][1]; + mat[4*i+2] = m[i][2]; + mat[4*i+3] = m[i][3]; + } + + gluInvertMatrix( mat, matInv ); + + Matrix4 inv; + + for ( int i = 0; i < 4; i++ ) { + inv( i, 0 ) = matInv[4*i]; + inv( i, 1 ) = matInv[4*i + 1]; + inv( i, 2 ) = matInv[4*i + 2]; + inv( i, 3 ) = matInv[4*i + 3]; } + return inv; } void Quat::fromAxisAngle( Vector3 axis, float angle ) diff --git a/src/niftypes.h b/src/niftypes.h index f6bf9b46d..ac06fb064 100644 --- a/src/niftypes.h +++ b/src/niftypes.h @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -35,6 +35,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +#include #include #include @@ -51,7 +52,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #endif -//! \file niftypes.h Type definitions and functions +//! @file niftypes.h Matrix, Matrix4, Triangle, Vector2, Vector3, Vector4, Color3, Color4, Quat class NifModel; class QDataStream; @@ -183,11 +184,26 @@ class Vector2 friend QDataStream & operator>>( QDataStream & ds, Vector2 & v ); }; -//! An attempt at providing a QDebug stream operator for Vector2 -/** - * See Qt Debugging - * for how this is supposed to work. - */ +class HalfVector2 : public Vector2 +{ +public: + //! Default constructor + HalfVector2() + { + xy[0] = xy[1] = 0.0; + } + //! Constructor + HalfVector2( float x, float y ) : Vector2( x, y ) + { + } + + HalfVector2( Vector2 v ) : Vector2( v ) + { + } +}; + + +//! QDebug stream operator for Vector2 inline QDebug & operator<<( QDebug dbg, Vector2 v ) { dbg.nospace() << "(" << v[0] << ", " << v[1] << ")"; @@ -226,10 +242,10 @@ class Vector3 xyz[1] = v2[1]; xyz[2] = z; } - //! Constructor from a Vector4 - /*! + + /*! Constructor from a Vector4 + * * Construct a Vector3 from a Vector4 by truncating. - * Doxygen can't document this due to a circular dependency? */ explicit Vector3( const class Vector4 & ); @@ -271,6 +287,15 @@ class Vector3 Vector3 w( *this ); return w += v; } + + Vector3 & operator+( float s ) + { + xyz[0] += s; + xyz[1] += s; + xyz[2] += s; + return *this; + } + //! Minus operator Vector3 operator-( Vector3 v ) const { @@ -423,11 +448,43 @@ class Vector3 friend QDataStream & operator>>( QDataStream & ds, Vector3 & v ); }; -//! An attempt at providing a QDebug stream operator for Vector3 -/** - * See Qt Debugging - * for how this is supposed to work. - */ +class HalfVector3 : public Vector3 +{ +public: + //! Default constructor + HalfVector3() + { + xyz[0] = xyz[1] = xyz[2] = 0.0; + } + //! Constructor + HalfVector3( float x, float y, float z ) : Vector3( x, y, z ) + { + } + + HalfVector3( Vector3 v ) : Vector3( v ) + { + } +}; + +class ByteVector3 : public Vector3 +{ +public: + //! Default constructor + ByteVector3() + { + xyz[0] = xyz[1] = xyz[2] = 0.0; + } + //! Constructor + ByteVector3( float x, float y, float z ) : Vector3( x, y, z ) + { + } + + ByteVector3( Vector3 v ) : Vector3( v ) + { + } +}; + +//! QDebug stream operator for Vector3 inline QDebug & operator<<( QDebug dbg, Vector3 v ) { dbg.nospace() << "(" << v[0] << ", " << v[1] << ", " << v[2] << ")"; @@ -647,11 +704,7 @@ class Vector4 friend QDataStream & operator>>( QDataStream & ds, Vector4 & v ); }; -//! An attempt at providing a QDebug stream operator for Vector2 -/** - * See Qt Debugging - * for how this is supposed to work. - */ +//! QDebug stream operator for Vector2 inline QDebug & operator<<( QDebug dbg, Vector4 v ) { dbg.nospace() << "(" << v[0] << ", " << v[1] << ", " << v[2] << ", " << v[3] << ")"; @@ -665,7 +718,6 @@ inline QDataStream & operator>>( QDataStream & ds, Vector4 & v ) return ds; } -// This doesn't seem to document properly in Doxygen. inline Vector3::Vector3( const Vector4 & v4 ) { xyz[0] = v4[0]; @@ -755,6 +807,12 @@ class Quat Quat r( *this ); return ( r += q ); } + //! Equality operator + bool operator==( const Quat & other ) const + { + return (wxyz[0] == other.wxyz[0]) && (wxyz[1] == other.wxyz[1]) && (wxyz[2] == other.wxyz[2]) && (wxyz[3] == other.wxyz[3]); + } + //! Find the dot product of two quaternions static float dotproduct( const Quat & q1, const Quat & q2 ) { @@ -844,6 +902,17 @@ class Matrix Q_ASSERT( c < 3 && d < 3 ); return m[c][d]; } + //! Equality operator + bool operator==( const Matrix & other ) const + { + for ( int i = 0; i < 3; i++ ) { + for ( int j = 0; j < 3; j++ ) { + if ( m[i][j] != other.m[i][j] ) + return false; + } + } + return true; + } //! Find the inverted form Matrix inverted() const; @@ -868,6 +937,12 @@ class Matrix //! Format as HTML QString toHtml() const; + //! Format as raw text + QString toRaw() const; + + //! %Data accessor + const float * data() const { return (float *)m; } + protected: float m[3][3]; static const float identity[9]; @@ -922,6 +997,17 @@ class Matrix4 Q_ASSERT( c < 4 && d < 4 ); return m[c][d]; } + //! Equality operator + bool operator==( const Matrix4 & other ) const + { + for ( int i = 0; i < 4; i++ ) { + for ( int j = 0; j < 4; j++ ) { + if ( m[i][j] != other.m[i][j] ) + return false; + } + } + return true; + } // Not implemented; use decompose() instead /* @@ -935,7 +1021,7 @@ class Matrix4 //! Compose from translation, rotation and scale void compose( const Vector3 & trans, const Matrix & rot, const Vector3 & scale ); - //Matrix44 inverted() const; + Matrix4 inverted() const; //! Format as HTML QString toHtml() const; @@ -955,12 +1041,10 @@ class Matrix4 class Transform { public: - //! Constructor - /*! - * Creates a transform from a NIF index. + /*! Creates a transform from a NIF index. * - * \param nif The model - * \param transform The index to create the transform from + * @param nif The model. + * @param transform The index to create the transform from. */ Transform( const NifModel * nif, const QModelIndex & transform ); //! Default constructor @@ -1034,8 +1118,8 @@ class Triangle //! Gets the third vertex inline quint16 v3() const { return v[2]; } - //! Flips the triangle face - /*! + /*! Flips the triangle face + * * Triangles are usually drawn anticlockwise(?); by changing the order of * the vertices the triangle is flipped. */ @@ -1051,6 +1135,12 @@ class Triangle return t; } + //! Equality operator + bool operator==( const Triangle & other ) const + { + return (v[0] == other.v[0]) && (v[1] == other.v[1]) && (v[2] == other.v[2]); + } + protected: quint16 v[3]; friend class NifIStream; @@ -1059,11 +1149,7 @@ class Triangle friend QDataStream & operator>>( QDataStream & ds, Triangle & t ); }; -//! An attempt at providing a QDebug stream operator for Triangle -/** - * See Qt Debugging - * for how this is supposed to work. - */ +//! QDebug stream operator for Triangle inline QDebug & operator<<( QDebug dbg, Triangle t ) { dbg.nospace() << "(" << t[0] << "," << t[1] << "," << t[2] << ")"; @@ -1101,10 +1187,10 @@ class Color3 explicit Color3( const QColor & c ) { fromQColor( c ); } //! Constructor explicit Color3( const Vector3 & v ) { fromVector3( v ); } - //! Constructor - /*! - * Construct a Color3 from a Color4 by truncating. - * Doxygen can't document this due to a circular dependency? + + /*! Construct a Color3 from a Color4 by truncating. + * + * @param c4 The Color4. */ explicit Color3( const class Color4 & c4 ); @@ -1157,6 +1243,15 @@ class Color3 return ( c += o ); } + Color3 operator+( float x ) const + { + Color3 c( *this ); + c.rgb[0] += x; + c.rgb[1] += x; + c.rgb[2] += x; + return c; + } + //! Minus operator Color3 operator-( const Color3 & o ) const { @@ -1276,6 +1371,16 @@ class Color4 return ( c += o ); } + Color4 operator+(float x) const + { + Color4 c( *this ); + c.rgba[0] += x; + c.rgba[1] += x; + c.rgba[2] += x; + c.rgba[3] += x; + return c; + } + //! Minus operator Color4 operator-( const Color4 & o ) const { @@ -1345,7 +1450,14 @@ class Color4 friend QDataStream & operator>>( QDataStream & ds, Color4 & c ); }; -// This refuses to document properly in doxygen. +class ByteColor4 : public Color4 +{ +public: + //! Default constructor + ByteColor4() { rgba[0] = rgba[1] = rgba[2] = rgba[3] = 1.0; } +}; + + inline Color3::Color3( const Color4 & c4 ) { rgb[0] = c4[0]; @@ -1353,6 +1465,18 @@ inline Color3::Color3( const Color4 & c4 ) rgb[2] = c4[2]; } +inline QDebug & operator<<( QDebug dbg, Color3 c ) +{ + dbg.nospace() << "(" << c[0] << ", " << c[1] << ", " << c[2] << ")"; + return dbg.space(); +} + +inline QDebug & operator<<( QDebug dbg, Color4 c ) +{ + dbg.nospace() << "(" << c[0] << ", " << c[1] << ", " << c[2] << ", " << c[3] << ")"; + return dbg.space(); +} + //! A stream operator for reading in a Color4 inline QDataStream & operator>>( QDataStream & ds, Color4 & c ) { @@ -1360,8 +1484,8 @@ inline QDataStream & operator>>( QDataStream & ds, Color4 & c ) return ds; } -//! A fixed length vector of type T. -/*! +/*! A fixed length vector of type T. + * * %Data is allocated into a vector portion and the data section. * The vector simply points to appropriate places in the data section. * @param T Type of Vector @@ -1374,8 +1498,8 @@ class FixedMatrix FixedMatrix() : v_( nullptr ), len0( 0 ), len1( 0 ) {} - //! Size Constructor - /*! + /*! Size Constructor + * * Allocate the requested number of elements. */ FixedMatrix( int length1, int length2 ) @@ -1450,8 +1574,8 @@ class FixedMatrix return v_[ calcindex( index1, index2 ) ]; } - //! Calculates row? of element - /*! + /*! Calculates row? of element + * * @param[in] index1 i value of element * @param[in] index2 j value of element * @return position of element? @@ -1475,20 +1599,20 @@ class FixedMatrix //! Start of the array portion of the vector T * data() const { return v_; } - //! Assign a string to vector at specified index. - /*! - * @param[in] index1 Index (i) in array to assign - * @param[in] index2 Index (j) in array to assign - * @param[in] value Value to copy into string + /*! Assign a string to vector at specified index. + * + * @param [in] index1 Index (i) in array to assign. + * @param [in] index2 Index (j) in array to assign. + * @param [in] value Value to copy into string. */ void assign( int index1, int index2, T value ) { element( index1, index2 ) = value; } - //! Swap contents with another FixedMatrix - /*! - * @param[in,out] other Other vector to swap with + /*! Swap contents with another FixedMatrix. + * + * @param [in,out] other Other vector to swap with. */ void swap( FixedMatrix & other ) { @@ -1503,7 +1627,6 @@ class FixedMatrix int len1; //!< Length in second dimension }; -//! typedef FixedMatrix ByteMatrix; #endif diff --git a/src/nifvalue.cpp b/src/nifvalue.cpp index fa5fe0b50..f52c5898f 100644 --- a/src/nifvalue.cpp +++ b/src/nifvalue.cpp @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -32,8 +32,8 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "nifvalue.h" #include "config.h" -//#include "options.h" +#include "half.h" #include "nifmodel.h" #include @@ -41,17 +41,32 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include -//! \file nifvalue.cpp NifValue, NifIStream, NifOStream, NifSStream - -/* - * NifValue - */ +//! @file nifvalue.cpp NifValue, NifIStream, NifOStream, NifSStream QHash NifValue::typeMap; QHash NifValue::typeTxt; QHash NifValue::enumMap; QHash NifValue::aliasMap; +/* + * NifValue + */ + +NifValue::NifValue( Type t ) +{ + changeType( t ); +} + +NifValue::NifValue( const NifValue & other ) +{ + operator=(other); +} + +NifValue::~NifValue() +{ + clear(); +} + void NifValue::initialize() { typeMap.clear(); @@ -59,43 +74,52 @@ void NifValue::initialize() typeMap.insert( "bool", NifValue::tBool ); typeMap.insert( "byte", NifValue::tByte ); + typeMap.insert( "char", NifValue::tByte ); typeMap.insert( "word", NifValue::tWord ); typeMap.insert( "short", NifValue::tShort ); typeMap.insert( "int", NifValue::tInt ); - typeMap.insert( "flags", NifValue::tFlags ); + typeMap.insert( "Flags", NifValue::tFlags ); typeMap.insert( "ushort", NifValue::tWord ); typeMap.insert( "uint", NifValue::tUInt ); - typeMap.insert( "link", NifValue::tLink ); - typeMap.insert( "uplink", NifValue::tUpLink ); + typeMap.insert( "ulittle32", NifValue::tULittle32 ); + typeMap.insert( "Ref", NifValue::tLink ); + typeMap.insert( "Ptr", NifValue::tUpLink ); typeMap.insert( "float", NifValue::tFloat ); - typeMap.insert( "sizedstring", NifValue::tSizedString ); - typeMap.insert( "text", NifValue::tText ); - typeMap.insert( "shortstring", NifValue::tShortString ); - typeMap.insert( "color3", NifValue::tColor3 ); - typeMap.insert( "color4", NifValue::tColor4 ); - typeMap.insert( "vector4", NifValue::tVector4 ); - typeMap.insert( "vector3", NifValue::tVector3 ); - typeMap.insert( "quat", NifValue::tQuat ); - typeMap.insert( "quaternion", NifValue::tQuat ); - typeMap.insert( "quaternion_wxyz", NifValue::tQuat ); - typeMap.insert( "quaternion_xyzw", NifValue::tQuatXYZW ); - typeMap.insert( "matrix33", NifValue::tMatrix ); - typeMap.insert( "matrix44", NifValue::tMatrix4 ); - typeMap.insert( "vector2", NifValue::tVector2 ); - typeMap.insert( "triangle", NifValue::tTriangle ); - typeMap.insert( "bytearray", NifValue::tByteArray ); - typeMap.insert( "bytematrix", NifValue::tByteMatrix ); - typeMap.insert( "fileversion", NifValue::tFileVersion ); - typeMap.insert( "headerstring", NifValue::tHeaderString ); - typeMap.insert( "linestring", NifValue::tLineString ); - typeMap.insert( "stringpalette", NifValue::tStringPalette ); - typeMap.insert( "stringoffset", NifValue::tStringOffset ); - typeMap.insert( "stringindex", NifValue::tStringIndex ); - typeMap.insert( "blocktypeindex", NifValue::tBlockTypeIndex ); + typeMap.insert( "SizedString", NifValue::tSizedString ); + typeMap.insert( "Text", NifValue::tText ); + typeMap.insert( "ShortString", NifValue::tShortString ); + typeMap.insert( "Color3", NifValue::tColor3 ); + typeMap.insert( "Color4", NifValue::tColor4 ); + typeMap.insert( "Vector4", NifValue::tVector4 ); + typeMap.insert( "Vector3", NifValue::tVector3 ); + typeMap.insert( "TBC", NifValue::tVector3 ); + typeMap.insert( "Quaternion", NifValue::tQuat ); + typeMap.insert( "QuaternionWXYZ", NifValue::tQuat ); + typeMap.insert( "QuaternionXYZW", NifValue::tQuatXYZW ); + typeMap.insert( "Matrix33", NifValue::tMatrix ); + typeMap.insert( "Matrix44", NifValue::tMatrix4 ); + typeMap.insert( "Vector2", NifValue::tVector2 ); + typeMap.insert( "TexCoord", NifValue::tVector2 ); + typeMap.insert( "Triangle", NifValue::tTriangle ); + typeMap.insert( "ByteArray", NifValue::tByteArray ); + typeMap.insert( "ByteMatrix", NifValue::tByteMatrix ); + typeMap.insert( "FileVersion", NifValue::tFileVersion ); + typeMap.insert( "HeaderString", NifValue::tHeaderString ); + typeMap.insert( "LineString", NifValue::tLineString ); + typeMap.insert( "StringPalette", NifValue::tStringPalette ); + typeMap.insert( "StringOffset", NifValue::tStringOffset ); + typeMap.insert( "StringIndex", NifValue::tStringIndex ); + typeMap.insert( "BlockTypeIndex", NifValue::tBlockTypeIndex ); typeMap.insert( "char8string", NifValue::tChar8String ); typeMap.insert( "string", NifValue::tString ); - typeMap.insert( "filepath", NifValue::tFilePath ); + typeMap.insert( "FilePath", NifValue::tFilePath ); typeMap.insert( "blob", NifValue::tBlob ); + typeMap.insert( "hfloat", NifValue::tHfloat ); + typeMap.insert( "HalfVector3", NifValue::tHalfVector3 ); + typeMap.insert( "ByteVector3", NifValue::tByteVector3 ); + typeMap.insert( "HalfVector2", NifValue::tHalfVector2 ); + typeMap.insert( "HalfTexCoord", NifValue::tHalfVector2 ); + typeMap.insert( "ByteColor4", NifValue::tByteColor4 ); enumMap.clear(); } @@ -200,7 +224,7 @@ bool NifValue::registerEnumType( const QString & eid, EnumType eTyp ) NifValue::EnumType NifValue::enumType( const QString & eid ) { - return enumMap.value( eid, { EnumType::eNone } ).t; + return (enumMap.contains( eid )) ? enumMap[eid].t : EnumType::eNone; } QString NifValue::enumOptionName( const QString & eid, quint32 val ) @@ -314,31 +338,19 @@ const NifValue::EnumOptions & NifValue::enumOptionData( const QString & eid ) return enumMap[eid]; } -NifValue::NifValue( Type t ) : typ( tNone ), abstract( false ) -{ - changeType( t ); -} - -NifValue::NifValue( const NifValue & other ) : typ( tNone ) -{ - operator=( other ); -} - -NifValue::~NifValue() -{ - clear(); -} - void NifValue::clear() { switch ( typ ) { case tVector4: - delete static_cast( val.data ); + delete static_cast( val.data ); break; case tVector3: + case tHalfVector3: + case tByteVector3: delete static_cast( val.data ); break; case tVector2: + case tHalfVector2: delete static_cast( val.data ); break; case tMatrix: @@ -374,6 +386,7 @@ void NifValue::clear() delete static_cast( val.data ); break; case tColor4: + case tByteColor4: delete static_cast( val.data ); break; case tBlob: @@ -401,6 +414,8 @@ void NifValue::changeType( Type t ) val.i32 = -1; return; case tVector3: + case tHalfVector3: + case tByteVector3: val.data = new Vector3(); break; case tVector4: @@ -417,6 +432,7 @@ void NifValue::changeType( Type t ) val.data = new Quat(); return; case tVector2: + case tHalfVector2: val.data = new Vector2(); return; case tTriangle: @@ -435,6 +451,7 @@ void NifValue::changeType( Type t ) val.data = new Color3(); return; case tColor4: + case tByteColor4: val.data = new Color4(); return; case tByteArray: @@ -464,6 +481,8 @@ void NifValue::operator=( const NifValue & other ) switch ( typ ) { case tVector3: + case tHalfVector3: + case tByteVector3: *static_cast( val.data ) = *static_cast( other.val.data ); return; case tVector4: @@ -480,6 +499,7 @@ void NifValue::operator=( const NifValue & other ) *static_cast( val.data ) = *static_cast( other.val.data ); return; case tVector2: + case tHalfVector2: *static_cast( val.data ) = *static_cast( other.val.data ); return; case tString: @@ -495,6 +515,7 @@ void NifValue::operator=( const NifValue & other ) *static_cast( val.data ) = *static_cast( other.val.data ); return; case tColor4: + case tByteColor4: *static_cast( val.data ) = *static_cast( other.val.data ); return; case tByteArray: @@ -516,6 +537,183 @@ void NifValue::operator=( const NifValue & other ) } } +bool NifValue::operator==( const NifValue & other ) const +{ + switch ( typ ) { + case tByte: + return val.u08 == other.val.u08; + + case tWord: + case tFlags: + case tStringOffset: + case tBlockTypeIndex: + case tShort: + return val.u16 == other.val.u16; + + case tBool: + case tUInt: + case tULittle32: + case tStringIndex: + case tFileVersion: + return val.u32 == other.val.u32; + + case tInt: + case tLink: + case tUpLink: + return val.i32 == other.val.i32; + + case tFloat: + case tHfloat: + return val.f32 == other.val.f32; + case tString: + case tSizedString: + case tText: + case tShortString: + case tHeaderString: + case tLineString: + case tChar8String: + case tFilePath: + { + QString * s1 = static_cast(val.data); + QString * s2 = static_cast(other.val.data); + + if ( !s1 || !s2 ) + return false; + + return *s1 == *s2; + } + + case tColor3: + { + Color3 * c1 = static_cast(val.data); + Color3 * c2 = static_cast(other.val.data); + + if ( !c1 || !c2 ) + return false; + + return c1->toQColor() == c2->toQColor(); + } + + case tColor4: + case tByteColor4: + { + Color4 * c1 = static_cast(val.data); + Color4 * c2 = static_cast(other.val.data); + + if ( !c1 || !c2 ) + return false; + + return c1->toQColor() == c2->toQColor(); + } + + case tVector2: + case tHalfVector2: + { + Vector2 * vec1 = static_cast(val.data); + Vector2 * vec2 = static_cast(other.val.data); + + if ( !vec1 || !vec2 ) + return false; + + return *vec1 == *vec2; + } + + case tVector3: + case tHalfVector3: + case tByteVector3: + { + Vector3 * vec1 = static_cast(val.data); + Vector3 * vec2 = static_cast(other.val.data); + + if ( !vec1 || !vec2 ) + return false; + + return *vec1 == *vec2; + } + + case tVector4: + { + Vector4 * vec1 = static_cast(val.data); + Vector4 * vec2 = static_cast(other.val.data); + + if ( !vec1 || !vec2 ) + return false; + + return *vec1 == *vec2; + } + + case tQuat: + case tQuatXYZW: + { + Quat * quat1 = static_cast(val.data); + Quat * quat2 = static_cast(other.val.data); + + if ( !quat1 || !quat2 ) + return false; + + return *quat1 == *quat2; + } + + case tTriangle: + { + Triangle * tri1 = static_cast(val.data); + Triangle * tri2 = static_cast(other.val.data); + + if ( !tri1 || !tri2 ) + return false; + + return *tri1 == *tri2; + } + + case tByteArray: + case tByteMatrix: + case tStringPalette: + case tBlob: + { + QByteArray * a1 = static_cast(val.data); + QByteArray * a2 = static_cast(other.val.data); + + if ( a1->isNull() || a2->isNull() ) + return false; + + return *a1 == *a2; + } + + case tMatrix: + { + Matrix * m1 = static_cast(val.data); + Matrix * m2 = static_cast(other.val.data); + + if ( !m1 || !m2 ) + return false; + + return *m1 == *m2; + } + case tMatrix4: + { + Matrix4 * m1 = static_cast(val.data); + Matrix4 * m2 = static_cast(other.val.data); + + if ( !m1 || !m2 ) + return false; + + return *m1 == *m2; + } + case tNone: + default: + return false; + } + + return false; +} + +bool NifValue::operator<( const NifValue & other ) const +{ + Q_UNUSED( other ); + return false; +} + + QVariant NifValue::toVariant() const { QVariant v; @@ -523,7 +721,7 @@ QVariant NifValue::toVariant() const return v; } -bool NifValue::fromVariant( const QVariant & var ) +bool NifValue::setFromVariant( const QVariant & var ) { if ( var.canConvert() ) { operator=( var.value() ); @@ -535,7 +733,7 @@ bool NifValue::fromVariant( const QVariant & var ) return false; } -bool NifValue::fromString( const QString & s ) +bool NifValue::setFromString( const QString & s ) { bool ok; @@ -566,6 +764,7 @@ bool NifValue::fromString( const QString & s ) val.i32 = s.toInt( &ok, 0 ); return ok; case tUInt: + case tULittle32: val.u32 = s.toUInt( &ok, 0 ); return ok; case tStringIndex: @@ -578,6 +777,9 @@ bool NifValue::fromString( const QString & s ) case tFloat: val.f32 = s.toDouble( &ok ); return ok; + case tHfloat: + val.f32 = s.toDouble( &ok ); + return ok; case tString: case tSizedString: case tText: @@ -591,6 +793,7 @@ bool NifValue::fromString( const QString & s ) static_cast( val.data )->fromQColor( QColor( s ) ); return true; case tColor4: + case tByteColor4: static_cast( val.data )->fromQColor( QColor( s ) ); return true; case tFileVersion: @@ -636,6 +839,7 @@ QString NifValue::toString() const case tStringOffset: case tBlockTypeIndex: case tUInt: + case tULittle32: return QString::number( val.u32 ); case tStringIndex: return QString::number( val.u32 ); @@ -647,7 +851,9 @@ QString NifValue::toString() const case tUpLink: return QString::number( val.i32 ); case tFloat: - return NumOrMinMax( val.f32, 'f', 4 ); + return NumOrMinMax( val.f32, 'f', 6 ); + case tHfloat: + return QString::number( val.f32, 'f', 4 ); case tString: case tSizedString: case tText: @@ -665,6 +871,7 @@ QString NifValue::toString() const .arg( (int)( col->blue() * 0xff ), 2, 16, QChar( '0' ) ); } case tColor4: + case tByteColor4: { Color4 * col = static_cast( val.data ); return QString( "#%1%2%3%4" ) @@ -674,6 +881,7 @@ QString NifValue::toString() const .arg( (int)( col->alpha() * 0xff ), 2, 16, QChar( '0' ) ); } case tVector2: + case tHalfVector2: { Vector2 * v = static_cast( val.data ); @@ -682,6 +890,8 @@ QString NifValue::toString() const .arg( NumOrMinMax( (*v)[1], 'f', VECTOR_DECIMALS ) ); } case tVector3: + case tHalfVector3: + case tByteVector3: { Vector3 * v = static_cast( val.data ); @@ -797,17 +1007,28 @@ QColor NifValue::toColor() const { if ( type() == tColor3 ) return static_cast( val.data )->toQColor(); - else if ( type() == tColor4 ) + else if ( type() == tColor4 || type() == tByteColor4 ) return static_cast( val.data )->toQColor(); return QColor(); } -void NifOStream::init() +/* + * NifIStream + */ + +void NifIStream::init() { - bool32bit = ( model->inherits( "NifModel" ) && model->getVersionNumber() <= 0x04000002 ); - linkAdjust = ( model->inherits( "NifModel" ) && model->getVersionNumber() < 0x0303000D ); - stringAdjust = ( model->inherits( "NifModel" ) && model->getVersionNumber() >= 0x14010003 ); + bool32bit = (model->inherits( "NifModel" ) && model->getVersionNumber() <= 0x04000002); + linkAdjust = (model->inherits( "NifModel" ) && model->getVersionNumber() < 0x0303000D); + stringAdjust = (model->inherits( "NifModel" ) && model->getVersionNumber() >= 0x14010003); + bigEndian = false; // set when tFileVersion is read + + dataStream = std::unique_ptr( new QDataStream( device ) ); + dataStream->setByteOrder( QDataStream::LittleEndian ); + dataStream->setFloatingPointPrecision( QDataStream::SinglePrecision ); + + maxLength = 0x8000; } bool NifIStream::read( NifValue & val ) @@ -846,6 +1067,18 @@ bool NifIStream::read( NifValue & val ) *dataStream >> val.val.u32; return ( dataStream->status() == QDataStream::Ok ); } + case NifValue::tULittle32: + { + if ( bigEndian ) + dataStream->setByteOrder( QDataStream::LittleEndian ); + + *dataStream >> val.val.u32; + + if ( bigEndian ) + dataStream->setByteOrder( QDataStream::BigEndian ); + + return (dataStream->status() == QDataStream::Ok); + } case NifValue::tStringIndex: { *dataStream >> val.val.u32; @@ -864,6 +1097,65 @@ bool NifIStream::read( NifValue & val ) case NifValue::tFloat: { *dataStream >> val.val.f32; + return ( dataStream->status() == QDataStream::Ok ); + } + case NifValue::tHfloat: + { + uint16_t half; + *dataStream >> half; + val.val.u32 = half_to_float( half ); + return (dataStream->status() == QDataStream::Ok); + } + case NifValue::tByteVector3: + { + quint8 x, y, z; + float xf, yf, zf; + + *dataStream >> x; + *dataStream >> y; + *dataStream >> z; + + xf = (double(x) / 255.0) * 2.0 - 1.0; + yf = (double(y) / 255.0) * 2.0 - 1.0; + zf = (double(z) / 255.0) * 2.0 - 1.0; + + Vector3 * v = static_cast(val.val.data); + v->xyz[0] = xf; v->xyz[1] = yf; v->xyz[2] = zf; + + return (dataStream->status() == QDataStream::Ok); + } + case NifValue::tHalfVector3: + { + uint16_t x, y, z; + union { float f; uint32_t i; } xu, yu, zu; + + *dataStream >> x; + *dataStream >> y; + *dataStream >> z; + + xu.i = half_to_float( x ); + yu.i = half_to_float( y ); + zu.i = half_to_float( z ); + + Vector3 * v = static_cast(val.val.data); + v->xyz[0] = xu.f; v->xyz[1] = yu.f; v->xyz[2] = zu.f; + + return ( dataStream->status() == QDataStream::Ok ); + } + case NifValue::tHalfVector2: + { + uint16_t x, y; + union { float f; uint32_t i; } xu, yu; + + *dataStream >> x; + *dataStream >> y; + + xu.i = half_to_float( x ); + yu.i = half_to_float( y ); + + Vector2 * v = static_cast(val.val.data); + v->xy[0] = xu.f; v->xy[1] = yu.f; + return ( dataStream->status() == QDataStream::Ok ); } case NifValue::tVector3: @@ -907,6 +1199,19 @@ bool NifIStream::read( NifValue & val ) } case NifValue::tColor3: return device->read( (char *)static_cast( val.val.data )->rgb, 12 ) == 12; + case NifValue::tByteColor4: + { + quint8 r, g, b, a; + *dataStream >> r; + *dataStream >> g; + *dataStream >> b; + *dataStream >> a; + + Color4 * c = static_cast(val.val.data); + c->setRGBA( (float)r / 255.0, (float)g / 255.0, (float)b / 255.0, (float)a / 255.0 ); + + return (dataStream->status() == QDataStream::Ok); + } case NifValue::tColor4: { Color4 * c = static_cast( val.val.data ); @@ -944,7 +1249,7 @@ bool NifIStream::read( NifValue & val ) //string.replace( "\r", "\\r" ); //string.replace( "\n", "\\n" ); - *static_cast( val.val.data ) = QString( string ); + *static_cast( val.val.data ) = QString::fromLocal8Bit( string ); } return true; case NifValue::tText: @@ -1140,20 +1445,16 @@ bool NifIStream::read( NifValue & val ) return false; } -void NifIStream::init() -{ - bool32bit = ( model->inherits( "NifModel" ) && model->getVersionNumber() <= 0x04000002 ); - linkAdjust = ( model->inherits( "NifModel" ) && model->getVersionNumber() < 0x0303000D ); - stringAdjust = ( model->inherits( "NifModel" ) && model->getVersionNumber() >= 0x14010003 ); - bigEndian = false; // set when tFileVersion is read - dataStream = new QDataStream( device ); - dataStream->setByteOrder( QDataStream::LittleEndian ); - dataStream->setFloatingPointPrecision( QDataStream::SinglePrecision ); +/* + * NifOStream + */ - QSettings cfg; - maxLength = cfg.value( "maximum string length", 0x8000 ).toInt(); - //maxLength = Options::maxStringLength(); +void NifOStream::init() +{ + bool32bit = (model->inherits( "NifModel" ) && model->getVersionNumber() <= 0x04000002); + linkAdjust = (model->inherits( "NifModel" ) && model->getVersionNumber() < 0x0303000D); + stringAdjust = (model->inherits( "NifModel" ) && model->getVersionNumber() >= 0x14010003); } bool NifOStream::write( const NifValue & val ) @@ -1176,6 +1477,7 @@ bool NifOStream::write( const NifValue & val ) case NifValue::tStringOffset: case NifValue::tInt: case NifValue::tUInt: + case NifValue::tULittle32: case NifValue::tStringIndex: return device->write( (char *)&val.val.u32, 4 ) == 4; case NifValue::tFileVersion: @@ -1208,6 +1510,60 @@ bool NifOStream::write( const NifValue & val ) case NifValue::tFloat: return device->write( (char *)&val.val.f32, 4 ) == 4; + case NifValue::tHfloat: + { + uint16_t half = half_from_float( val.val.u32 ); + return device->write( (char *)&half, 2 ) == 2; + } + case NifValue::tByteVector3: + { + Vector3 * vec = static_cast(val.val.data); + if ( !vec ) + return false; + + uint8_t v[3]; + v[0] = round( ((vec->xyz[0] + 1.0) / 2.0) * 255.0 ); + v[1] = round( ((vec->xyz[1] + 1.0) / 2.0) * 255.0 ); + v[2] = round( ((vec->xyz[2] + 1.0) / 2.0) * 255.0 ); + + return device->write( (char*)v, 3 ) == 3; + } + case NifValue::tHalfVector3: + { + Vector3 * vec = static_cast(val.val.data); + if ( !vec ) + return false; + + union { float f; uint32_t i; } xu, yu, zu; + + xu.f = vec->xyz[0]; + yu.f = vec->xyz[1]; + zu.f = vec->xyz[2]; + + uint16_t v[3]; + v[0] = half_from_float( xu.i ); + v[1] = half_from_float( yu.i ); + v[2] = half_from_float( zu.i ); + + return device->write( (char*)v, 6 ) == 6; + } + case NifValue::tHalfVector2: + { + Vector2 * vec = static_cast(val.val.data); + if ( !vec ) + return false; + + union { float f; uint32_t i; } xu, yu; + + xu.f = vec->xy[0]; + yu.f = vec->xy[1]; + + uint16_t v[2]; + v[0] = half_from_float( xu.i ); + v[1] = half_from_float( yu.i ); + + return device->write( (char*)v, 4 ) == 4; + } case NifValue::tVector3: return device->write( (char *)static_cast( val.val.data )->xyz, 12 ) == 12; case NifValue::tVector4: @@ -1229,6 +1585,21 @@ bool NifOStream::write( const NifValue & val ) return device->write( (char *)static_cast( val.val.data )->xy, 8 ) == 8; case NifValue::tColor3: return device->write( (char *)static_cast( val.val.data )->rgb, 12 ) == 12; + case NifValue::tByteColor4: + { + Color4 * color = static_cast(val.val.data); + if ( !color ) + return false; + + quint8 c[4]; + + auto cF = color->rgba; + for ( int i = 0; i < 4; i++ ) { + c[i] = round( cF[i] * 255.0f ); + } + + return device->write( (char*)c, 4 ) == 4; + } case NifValue::tColor4: return device->write( (char *)static_cast( val.val.data )->rgba, 16 ) == 16; case NifValue::tSizedString: @@ -1245,7 +1616,7 @@ bool NifOStream::write( const NifValue & val ) } case NifValue::tShortString: { - QByteArray string = static_cast( val.val.data )->toLatin1(); + QByteArray string = static_cast( val.val.data )->toLocal8Bit(); string.replace( "\\r", "\r" ); string.replace( "\\n", "\n" ); @@ -1377,6 +1748,11 @@ bool NifOStream::write( const NifValue & val ) return false; } + +/* + * NifSStream + */ + void NifSStream::init() { bool32bit = ( model->inherits( "NifModel" ) && model->getVersionNumber() <= 0x04000002 ); @@ -1403,12 +1779,21 @@ int NifSStream::size( const NifValue & val ) case NifValue::tStringOffset: case NifValue::tInt: case NifValue::tUInt: + case NifValue::tULittle32: case NifValue::tStringIndex: case NifValue::tFileVersion: case NifValue::tLink: case NifValue::tUpLink: case NifValue::tFloat: return 4; + case NifValue::tHfloat: + return 2; + case NifValue::tByteVector3: + return 3; + case NifValue::tHalfVector3: + return 6; + case NifValue::tHalfVector2: + return 4; case NifValue::tVector3: return 12; case NifValue::tVector4: @@ -1426,6 +1811,8 @@ int NifSStream::size( const NifValue & val ) return 8; case NifValue::tColor3: return 12; + case NifValue::tByteColor4: + return 4; case NifValue::tColor4: return 16; case NifValue::tSizedString: diff --git a/src/nifvalue.h b/src/nifvalue.h index a929eae73..797708d43 100644 --- a/src/nifvalue.h +++ b/src/nifvalue.h @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -41,15 +41,14 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +#include -class QDataStream; -class QIODevice; -//! \file nifvalue.h NifValue, NifIStream, NifOStream, NifSStream +//! @file nifvalue.h NifValue, NifIStream, NifOStream, NifSStream // if there is demand for it, consider moving these into Options //! Number of decimals when editing vector types (Vector2, Vector3, Vector4) -#define VECTOR_DECIMALS 4 +#define VECTOR_DECIMALS 6 //! Maximum/minimum range when editing vector types (Vector2, Vector3, Vector4) #define VECTOR_RANGE 100000000 //! Number of decimals when editing Quat rotations in Euler mode @@ -61,17 +60,22 @@ class QIODevice; //! Increment when editing color types (Color3, Color4) #define COLOR_STEP 0.01 -//! A generic class used for storing a value of any type. -/*! + +/*! A generic class used for storing a value of any type. + * * The NifValue::Type enum lists all supported types. */ class NifValue { Q_DECLARE_TR_FUNCTIONS( NifValue ) + friend class NifIStream; + friend class NifOStream; + friend class NifSStream; + public: - //! List of all types implemented internally by NifSkope. - /*! + /*! List of all types implemented internally by NifSkope. + * * To add a new type, add a new enumerant to Type, and update NifValue::initialize() * to reflect the name of the type as used in the xml. */ @@ -87,13 +91,14 @@ class NifValue tBlockTypeIndex = 6, tInt = 7, tShort = 8, - tUInt = 9, + tULittle32 = 9, + tUInt = 10, // - tLink = 10, - tUpLink = 11, - tFloat = 12, + tLink = 11, + tUpLink = 12, + tFloat = 13, // all string types should come between tSizedString and tChar8String - tSizedString = 13, + tSizedString = 14, tText = 15, tShortString = 16, tHeaderString = 18, @@ -117,7 +122,11 @@ class NifValue tFilePath = 35, //!< not a string: requires special handling for slash/backslash etc. tByteMatrix = 36, tBlob = 37, - + tHfloat = 38, + tHalfVector3 = 39, + tByteVector3 = 40, + tHalfVector2 = 41, + tByteColor4 = 42, tNone = 0xff }; @@ -128,27 +137,59 @@ class NifValue eFlags, //!< bitflag enum }; + //! Constructor - initialize the value to nothing, type tNone. + NifValue() {} + //! Constructor - initialize the value to a default value of the specified type. + NifValue( Type t ); + //! Copy constructor. + NifValue( const NifValue & other ); + //! Destructor. + ~NifValue(); + + + //! Assignment. Performs a deep copy of the data. + void operator=(const NifValue & other); + //! Custom comparator for QVariant::operator==() + bool operator==(const NifValue & other) const; + //! Necessary for QMetaType::registerComparators(), but unused + bool operator<(const NifValue &) const; + + //! Clear the data, setting its type to tNone. + void clear(); + + //! Get the type. + Type type() const { return typ; } + + /*! Change the type of data stored. + * + * Clears existing data, changes its type, and then reinitializes the data to its default. + * Note that if Type is the same as originally, then the data is not cleared. + */ + void changeType( Type ); + // *** apparently not used *** //template static Type typeId(); - //! Initialize the class data - /*! + /*! Initialize the class data + * * Sets typeMap. Clears typeTxt and enumMap (which will be filled later during xml parsing). */ static void initialize(); - //! Get the Type corresponding to a string typId, as stored in the typeMap. - /*! - * \param typId The type string (as used in the xml). - * \return The Type corresponding to the string, or tNone if the type is not found. + /*! Get the Type corresponding to a string typId, as stored in the typeMap. + * + * @param typId The type string (as used in the xml). + * @return The Type corresponding to the string, or tNone if the type is not found. */ static Type type( const QString & typId ); + //! Get a html formatted description of the type. static QString typeDescription( const QString & typId ); //! Update the typeTxt map with the type description. Newline characters are replaced by html line break tags. static void setTypeDescription( const QString & typId, const QString & txt ); - //! Register an alias for a type. - /*! + + /*! Register an alias for a type. + * * This is done by updating the typeMap and maps the alias string to the type * corresponding to the internal string. */ @@ -163,26 +204,30 @@ class NifValue //! Register an enum type. static bool registerEnumType( const QString & eid, EnumType eTyp ); - //! Register an option for an enum type. - /*! - * \param eid The name of the enum type. - * \param oid The name of the option of that type to add. - * \param oval The value of that option. - * \param otxt The documentation string for the option. - * \return true if successful, false if the option value was already registered. + + /*! Register an option for an enum type. + * + * @param eid The name of the enum type. + * @param oid The name of the option of that type to add. + * @param oval The value of that option. + * @param otxt The documentation string for the option. + * @return True if successful, false if the option value was already registered. */ static bool registerEnumOption( const QString & eid, const QString & oid, quint32 oval, const QString & otxt ); + //! Get the name of an option from its value. static QString enumOptionName( const QString & eid, quint32 oval ); //! Get the documentation string of an option from its value. static QString enumOptionText( const QString & eid, quint32 oval ); - //! Get an option from an option string. - /*! - * \param eid The name of the enum type. - * \param oid The name of the option. - * \param ok Is set to true if successful, is set to false if the option string was not found. + + /*! Get an option from an option string. + * + * @param eid The name of the enum type. + * @param oid The name of the option. + * @param ok Is set to true if successful, is set to false if the option string was not found. */ static quint32 enumOptionValue( const QString & eid, const QString & oid, bool * ok = 0 ); + //! Get list of all options that have been registered for the given enum type. static QStringList enumOptions( const QString & eid ); //! Get type of enum for given enum type @@ -190,35 +235,6 @@ class NifValue //! Get list of all options that have been registered for the given enum type. static const EnumOptions & enumOptionData( const QString & eid ); - //! Constructor - initialize the value to nothing, type tNone. - NifValue() { typ = tNone; abstract = false; } - //! Constructor - initialize the value to a default value of the specified type. - NifValue( Type t ); - //! Copy constructor. - NifValue( const NifValue & other ); - //! Destructor. - ~NifValue(); - - //! Clear the data, setting its type to tNone. - void clear(); - //! Change the type of data stored. - /*! - * Clears existing data, changes its type, and then reinitializes the data to its default. - * Note that if Type is the same as originally, then the data is not cleared. - */ - void changeType( Type ); - - //! Get the abstract flag on this value. Does not seem to be reliably initialised yet. - inline bool isAbstract() { return abstract; } - - //! Set the abstract flag on this value. - inline void setAbstract( bool flag ) { abstract = flag; } - - //! Assignment. Performs a deep copy of the data. - void operator=( const NifValue & other ); - - //! Get the type. - Type type() const { return typ; } //! Check if the type is not tNone. static bool isValid( Type t ) { return t != tNone; } @@ -228,13 +244,13 @@ class NifValue //! Check if the type of the data is not tNone. bool isValid() const { return typ != tNone; } //! Check if the type of the data is a color type (Color3 or Color4 in xml). - bool isColor() const { return typ == tColor3 || typ == tColor4; } + bool isColor() const { return typ == tColor3 || typ == tColor4 || typ == tByteColor4; } //! Check if the type of the data is a count. bool isCount() const { return (typ >= tBool && typ <= tUInt); } //! Check if the type of the data is a flag type (Flags in xml). bool isFlags() const { return typ == tFlags; } //! Check if the type of the data is a float type (Float in xml). - bool isFloat() const { return typ == tFloat; } + bool isFloat() const { return (typ == tFloat) || (typ == tHfloat); } //! Check if the type of the data is of a link type (Ref or Ptr in xml). bool isLink() const { return typ == tLink || typ == tUpLink; } //! Check if the type of the data is a 3x3 matrix type (Matrix33 in xml). @@ -249,6 +265,12 @@ class NifValue bool isVector4() const { return typ == tVector4; } //! Check if the type of the data is a Vector 3. bool isVector3() const { return typ == tVector3; } + //! Check if the type of the data is a Half Vector3. + bool isHalfVector3() const { return typ == tHalfVector3; } + //! Check if the type of the data is a Byte Vector3. + bool isByteVector3() const { return typ == tByteVector3; } + //! Check if the type of the data is a HalfVector2. + bool isHalfVector2() const { return typ == tHalfVector2; } //! Check if the type of the data is a Vector 2. bool isVector2() const { return typ == tVector2; } //! Check if the type of the data is a triangle type. @@ -276,41 +298,41 @@ class NifValue //! See the documentation of QVariant for details. QVariant toVariant() const; - //! Set this value to a count. - /** - * \return True if applicable, false otherwise + /*! Set this value to a count. + * + * @return True if applicable, false otherwise */ bool setCount( quint32 ); - //! Set this value to a float. - /** - * \return True if applicable, false otherwise + /*! Set this value to a float. + * + * @return True if applicable, false otherwise */ bool setFloat( float ); - //! Set this value to a link. - /** - * \return True if applicable, false otherwise + /*! Set this value to a link. + * + * @return True if applicable, false otherwise */ bool setLink( int ); - //! Set this value to a file version. - /** - * \return True if applicable, false otherwise + /*! Set this value to a file version. + * + * @return True if applicable, false otherwise */ bool setFileVersion( quint32 ); - //! Set this value from a string. - /** - * \return True if applicable, false otherwise + /*! Set this value from a string. + * + * @return True if applicable, false otherwise */ - bool fromString( const QString & ); + bool setFromString( const QString & ); - //! Set this value from a QVariant. - /** - * \return True if applicable, false otherwise + /*! Set this value from a QVariant. + * + * @return True if applicable, false otherwise */ - bool fromVariant( const QVariant & ); + bool setFromVariant( const QVariant & ); //! Check whether the data is of type T. template bool ask( T * t = 0 ) const; @@ -321,7 +343,7 @@ class NifValue protected: //! The type of this data. - Type typ; + Type typ = tNone; //! The structure containing the data. union Value @@ -337,17 +359,15 @@ class NifValue //! The data value. Value val; - //! If the value represents an abstract field. Does not seem to be reliably initialised yet. - bool abstract; - - //! Get the data as an object of type T. - /*! + /*! Get the data as an object of type T. + * * If the type t is not equal to the actual type of the data, then return T(). Serves * as a helper function for get, intended for internal use only. */ template T getType( Type t ) const; - //! Set the data from an object of type T. - /*! + + /*! Set the data from an object of type T. + * * If the type t is not equal to the actual type of the data, then return false, else * return true. Helper function for set, intended for internal use only. */ @@ -356,28 +376,130 @@ class NifValue //! A dictionary yielding the Type from a type string. static QHash typeMap; - //! A dictionary yielding the enumeration dictionary from a string. - /*! + /*! A dictionary yielding the enumeration dictionary from a string. + * * Enums are stored as mappings from quint32 to pairs of strings, where * the first string in the pair is the enumerant string, and the second * is the enumerant documentation string. For example, * enumMap["AlphaFormat"][1] = QPair<"ALPHA_BINARY", "Texture is either fully transparent or fully opaque."> */ static QHash enumMap; + //! A dictionary yielding the documentation string of a type string. static QHash typeTxt; - //! A dictionary yielding the underlying type string from an alias string. - /*! + + /*! A dictionary yielding the underlying type string from an alias string. + * * Enums are stored as an underlying type (not always uint) which is normally not visible. * This dictionary allows that type to be exposed, eg. for NifValue::typeDescription(). */ static QHash aliasMap; +}; - friend class NifIStream; - friend class NifOStream; - friend class NifSStream; +Q_DECLARE_METATYPE( NifValue ) + + +class BaseModel; +class NifItem; + +class QDataStream; +class QIODevice; + +//! An input stream that reads a file into a model. +class NifIStream final +{ + Q_DECLARE_TR_FUNCTIONS( NifIStream ) + +public: + NifIStream( BaseModel * m, QIODevice * d ) : model( m ), device( d ) + { + init(); + } + + //! Reads a NifValue from the underlying device. Returns true if successful. + bool read( NifValue & ); + +private: + //! The model that data is being read into. + BaseModel * model; + //! The underlying device that data is being read from. + QIODevice * device; + //! The data stream that is wrapped around the device (simplifies endian conversion) + std::unique_ptr dataStream; + + //! Initialises the stream. + void init(); + + //! Whether a boolean is 32-bit. + bool bool32bit; + //! Whether link adjustment is required. + bool linkAdjust; + //! Whether string adjustment is required. + bool stringAdjust; + //! Whether the model is big-endian + bool bigEndian; + + //! The maximum length of a string that can be read. + int maxLength; +}; + + +//! An output stream that writes a model to a file. +class NifOStream final +{ + Q_DECLARE_TR_FUNCTIONS( NifOStream ) + +public: + NifOStream( const BaseModel * n, QIODevice * d ) : model( n ), device( d ) { init(); } + + //! Writes a NifValue to the underlying device. Returns true if successful. + bool write( const NifValue & ); + +private: + //! The model that data is being read from. + const BaseModel * model; + //! The underlying device that data is being written to. + QIODevice * device; + + //! Initialises the stream. + void init(); + + //! Whether a boolean is 32-bit. + bool bool32bit; + //! Whether link adjustment is required. + bool linkAdjust; + //! Whether string adjustment is required. + bool stringAdjust; + //! Whether the model is big-endian + bool bigEndian; +}; + + +//! A stream that determines the size of values in a model. +class NifSStream final +{ +public: + NifSStream( const BaseModel * n ) : model( n ) { init(); } + + //! Determine the size of a given NifValue. + int size( const NifValue & ); + +private: + //! The model that values are being sized for. + const BaseModel * model; + + //! Initialises the stream. + void init(); + + //! Whether booleans are 32-bit or not. + bool bool32bit; + //! Whether string adjustment is required. + bool stringAdjust; }; + +// Inlines + // documented above; should this really be inlined? // GCC only allows type punning via union (http://gcc.gnu.org/onlinedocs/gcc-4.2.1/gcc/Optimize-Options.html#index-fstrict_002daliasing-550) // This also works on GCC 3.4.5 @@ -388,7 +510,7 @@ inline quint32 NifValue::toCount() const return 0; } -// documented above + inline float NifValue::toFloat() const { if ( isFloat() ) @@ -396,7 +518,7 @@ inline float NifValue::toFloat() const return 0.0; } -// documented above + inline qint32 NifValue::toLink() const { if ( isLink() ) @@ -404,7 +526,7 @@ inline qint32 NifValue::toLink() const return -1; } -// documented above + inline quint32 NifValue::toFileVersion() const { if ( isFileVersion() ) @@ -413,7 +535,6 @@ inline quint32 NifValue::toFileVersion() const return 0; } -// documented above inline bool NifValue::setCount( quint32 c ) { if ( isCount() ) { @@ -422,7 +543,7 @@ inline bool NifValue::setCount( quint32 c ) return false; } -// documented above + inline bool NifValue::setFloat( float f ) { if ( isFloat() ) { @@ -431,7 +552,7 @@ inline bool NifValue::setFloat( float f ) return false; } -// documented above + inline bool NifValue::setLink( int l ) { if ( isLink() ) { @@ -440,7 +561,7 @@ inline bool NifValue::setLink( int l ) return false; } -// documented above + inline bool NifValue::setFileVersion( quint32 v ) { if ( isFileVersion() ) { @@ -450,6 +571,9 @@ inline bool NifValue::setFileVersion( quint32 v ) return false; } + +// Templates + template inline T NifValue::getType( Type t ) const { if ( typ == t ) @@ -519,16 +643,38 @@ template <> inline Vector4 NifValue::get() const } template <> inline Vector3 NifValue::get() const { - return getType( tVector3 ); + if ( typ == tVector3 || typ == tHalfVector3 ) + return *static_cast(val.data); + + return Vector3(); +} +template <> inline HalfVector3 NifValue::get() const +{ + return getType( tHalfVector3 ); +} +template <> inline ByteVector3 NifValue::get() const +{ + return getType( tByteVector3 ); +} +template <> inline HalfVector2 NifValue::get() const +{ + return getType( tHalfVector2 ); } template <> inline Vector2 NifValue::get() const { - return getType( tVector2 ); + if ( typ == tVector2 || typ == tHalfVector2 ) + return *static_cast(val.data); + + return Vector2(); } template <> inline Color3 NifValue::get() const { return getType( tColor3 ); } +template <> inline ByteColor4 NifValue::get() const +{ + return getType( tByteColor4 ); +} template <> inline Color4 NifValue::get() const { return getType( tColor4 ); @@ -556,7 +702,7 @@ template <> inline QByteArray * NifValue::get() const if ( isByteArray() ) return static_cast( val.data ); - return NULL; + return nullptr; } template <> inline Quat NifValue::get() const { @@ -570,7 +716,7 @@ template <> inline ByteMatrix * NifValue::get() const if ( isByteMatrix() ) return static_cast( val.data ); - return NULL; + return nullptr; } //! Set the data from a boolean. Return true if successful. @@ -628,6 +774,21 @@ template <> inline bool NifValue::set( const Vector3 & x ) { return setType( tVector3, x ); } +//! Set the data from a HalfVector3. Return true if successful. +template <> inline bool NifValue::set( const HalfVector3 & x ) +{ + return setType( tHalfVector3, x ); +} +//! Set the data from a ByteVector3. Return true if successful. +template <> inline bool NifValue::set( const ByteVector3 & x ) +{ + return setType( tByteVector3, x ); +} +//! Set the data from a HalfVector2. Return true if successful. +template <> inline bool NifValue::set( const HalfVector2 & x ) +{ + return setType( tHalfVector2, x ); +} //! Set the data from a Vector2. Return true if successful. template <> inline bool NifValue::set( const Vector2 & x ) { @@ -638,6 +799,11 @@ template <> inline bool NifValue::set( const Color3 & x ) { return setType( tColor3, x ); } +//! Set the data from a ByteColor4. Return true if successful. +template <> inline bool NifValue::set( const ByteColor4 & x ) +{ + return setType( tByteColor4, x ); +} //! Set the data from a Color4. Return true if successful. template <> inline bool NifValue::set( const Color4 & x ) { @@ -648,13 +814,11 @@ template <> inline bool NifValue::set( const Triangle & x ) { return setType( tTriangle, x ); } - -// should this really be inlined? //! Set the data from a string. Return true if successful. template <> inline bool NifValue::set( const QString & x ) { if ( isString() ) { - if ( val.data == NULL ) { + if ( !val.data ) { val.data = new QString; } @@ -664,8 +828,6 @@ template <> inline bool NifValue::set( const QString & x ) return false; } - -// should this really be inlined? //! Set the data from a byte array. Return true if successful. template <> inline bool NifValue::set( const QByteArray & x ) { @@ -676,8 +838,6 @@ template <> inline bool NifValue::set( const QByteArray & x ) return false; } - -// should this really be inlined? //! Set the data from a quaternion. Return true if successful. template <> inline bool NifValue::set( const Quat & x ) { @@ -734,6 +894,21 @@ template <> inline bool NifValue::ask( Vector3 * ) const { return type() == tVector3; } +//! Check whether the data is a HalfVector3. +template <> inline bool NifValue::ask( HalfVector3 * ) const +{ + return type() == tHalfVector3; +} +//! Check whether the data is a ByteVector3. +template <> inline bool NifValue::ask( ByteVector3 * ) const +{ + return type() == tByteVector3; +} +//! Check whether the data is a Vector2. +template <> inline bool NifValue::ask( HalfVector2 * ) const +{ + return type() == tHalfVector2; +} //! Check whether the data is a Vector2. template <> inline bool NifValue::ask( Vector2 * ) const { @@ -744,6 +919,11 @@ template <> inline bool NifValue::ask( Color3 * ) const { return type() == tColor3; } +//! Check whether the data is a ByteColor4. +template <> inline bool NifValue::ask( ByteColor4 * ) const +{ + return type() == tByteColor4; +} //! Check whether the data is a Color4. template <> inline bool NifValue::ask( Color4 * ) const { @@ -765,103 +945,4 @@ template <> inline bool NifValue::ask( QByteArray * ) const return isByteArray(); } -class BaseModel; -class NifItem; - -//! An input stream that reads a file into a model. -class NifIStream final -{ - Q_DECLARE_TR_FUNCTIONS( NifIStream ) - -public: - //! Constructor. - NifIStream( BaseModel * m, QIODevice * d ) : model( m ), device( d ) - { - init(); - } - - //! Reads a NifValue from the underlying device. Returns true if successful. - bool read( NifValue & ); - -private: - //! The model that data is being read into. - BaseModel * model; - //! The underlying device that data is being read from. - QIODevice * device; - //! The data stream that is wrapped around the device (simplifies endian conversion) - QDataStream * dataStream; - - //! Initialises the stream. - void init(); - - //! Whether a boolean is 32-bit. - bool bool32bit; - //! Whether link adjustment is required. - bool linkAdjust; - //! Whether string adjustment is required. - bool stringAdjust; - //! Whether the model is big-endian - bool bigEndian; - - //! The maximum length of a string that can be read. - int maxLength; -}; - -//! An output stream that writes a model to a file. -class NifOStream final -{ - Q_DECLARE_TR_FUNCTIONS( NifOStream ) - -public: - //! Constructor. - NifOStream( const BaseModel * n, QIODevice * d ) : model( n ), device( d ) { init(); } - - //! Writes a NifValue to the underlying device. Returns true if successful. - bool write( const NifValue & ); - -private: - //! The model that data is being read from. - const BaseModel * model; - //! The underlying device that data is being written to. - QIODevice * device; - - //! Initialises the stream. - void init(); - - //! Whether a boolean is 32-bit. - bool bool32bit; - //! Whether link adjustment is required. - bool linkAdjust; - //! Whether string adjustment is required. - bool stringAdjust; - //! Whether the model is big-endian - bool bigEndian; -}; - -//! A stream that determines the size of values in a model. -class NifSStream final -{ -public: - //! Constructor. - NifSStream( const BaseModel * n ) : model( n ) { init(); } - - //! Determine the size of a given NifValue. - int size( const NifValue & ); - -private: - //! The model that values are being sized for. - const BaseModel * model; - - //! Initialises the stream. - void init(); - - //! Whether booleans are 32-bit or not. - bool bool32bit; - //! Whether string adjustment is required. - bool stringAdjust; -}; - - -Q_DECLARE_METATYPE( NifValue ) - #endif diff --git a/src/nifxml.cpp b/src/nifxml.cpp index 425c3fefc..ec48389b0 100644 --- a/src/nifxml.cpp +++ b/src/nifxml.cpp @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -30,6 +30,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ +#include "message.h" #include "nifmodel.h" #include "niftypes.h" @@ -45,8 +46,9 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. QReadWriteLock NifModel::XMLlock; QList NifModel::supportedVersions; -QHash NifModel::compounds; -QHash NifModel::blocks; +QHash NifModel::compounds; +QHash NifModel::fixedCompounds; +QHash NifModel::blocks; //! Parses nif.xml class NifXmlHandler final : public QXmlDefaultHandler @@ -115,7 +117,7 @@ class NifXmlHandler final : public QXmlDefaultHandler QString optTxt; //! Block - NifBlock * blk; + NifBlockPtr blk; //! Data NifData data; @@ -168,19 +170,14 @@ class NifXmlHandler final : public QXmlDefaultHandler case tagCompound: case tagBlock: { - if ( !list.value( "nifskopetype" ).isEmpty() ) { - QString alias = list.value( "name" ); - QString type = list.value( "nifskopetype" ); + QString name = list.value( "name" ); - if ( alias != type ) { - if ( !NifValue::registerAlias( alias, type ) ) - err( tr( "failed to register alias %1 for type %2" ).arg( alias, type ) ); - } - - typId = alias; + if ( NifValue::type( name ) != NifValue::tNone ) { + // Internal Type + typId = name; typTxt = QString(); } else { - QString id = list.value( "name" ); + QString id = name; if ( x == tagCompound && NifValue::isValid( NifValue::type( id ) ) ) err( tr( "compound %1 is already registered as internal type" ).arg( list.value( "name" ) ) ); @@ -192,7 +189,7 @@ class NifXmlHandler final : public QXmlDefaultHandler err( tr( "multiple declarations of %1" ).arg( id ) ); if ( !blk ) - blk = new NifBlock; + blk = NifBlockPtr( new NifBlock ); blk->id = id; blk->abstract = ( list.value( "abstract" ) == "1" ); @@ -205,23 +202,22 @@ class NifXmlHandler final : public QXmlDefaultHandler err( tr( "forward declaration of block id %1" ).arg( blk->ancestor ) ); } } + + QString externalCond = list.value( "externalcond" ); + if ( externalCond == "1" ) { + NifModel::fixedCompounds.insert( blk->id, blk ); + } } } break; case tagBasic: { - QString alias = list.value( "name" ); - QString type = list.value( "nifskopetype" ); + QString name = list.value( "name" ); - if ( alias.isEmpty() || type.isEmpty() ) - err( tr( "basic definition must have a name and a nifskopetype" ) ); + if ( NifValue::type( name ) == NifValue::tNone ) + err( tr( "basic definition %1 must have an internal NifSkope type" ).arg( name ) ); - if ( alias != type ) { - if ( !NifValue::registerAlias( alias, type ) ) - err( tr( "failed to register alias %1 for type %2" ).arg( alias, type ) ); - } - - typId = alias; + typId = name; typTxt = QString(); } break; @@ -271,46 +267,42 @@ class NifXmlHandler final : public QXmlDefaultHandler switch ( x ) { case tagAdd: { - // ns type optimizers come here - // we really shouldn't be doing this - // but it will work for now until we find a better solution QString type = list.value( "type" ); - QString nstype = list.value( "nifskopetype" ); - - if ( !nstype.isEmpty() && nstype != type ) { - if ( NifValue::type( nstype ) == NifValue::tNone ) - err( "failed to locate alias " + nstype ); - - type = nstype; - } - - if ( type == "KeyArray" ) - type = "ns keyarray"; - else if ( type == "VectorKeyArray" ) - type = "ns keyvecarray"; - else if ( type == "TypedVectorKeyArray" ) - type = "ns keyvecarraytyp"; - else if ( type == "RotationKeyArray" ) - type = "ns keyrotarray"; + QString tmpl = list.value( "template" ); + QString arr1 = list.value( "arr1" ); + QString arr2 = list.value( "arr2" ); + QString cond = list.value( "cond" ); + QString ver1 = list.value( "ver1" ); + QString ver2 = list.value( "ver2" ); + QString abs = list.value( "abstract" ); + QString bin = list.value( "binary" ); + + bool isTemplated = (type == "TEMPLATE" || tmpl == "TEMPLATE"); + bool isCompound = NifModel::compounds.contains( type ); + bool isArray = !arr1.isEmpty(); + bool isMultiArray = !arr2.isEmpty(); // now allocate data = NifData( list.value( "name" ), type, - list.value( "template" ), + tmpl, NifValue( NifValue::type( type ) ), list.value( "arg" ), - list.value( "arr1" ), - list.value( "arr2" ), - list.value( "cond" ), - NifModel::version2number( list.value( "ver1" ) ), - NifModel::version2number( list.value( "ver2" ) ), - ( list.value( "abstract" ) == "1" ) + arr1, + arr2, + cond, + NifModel::version2number( ver1 ), + NifModel::version2number( ver2 ) ); - if ( data.isAbstract() ) { - data.value.setAbstract( true ); - } + // Set data flags + data.setAbstract( abs == "1" ); + data.setBinary( bin == "1" ); + data.setTemplated( isTemplated ); + data.setIsCompound( isCompound ); + data.setIsArray( isArray ); + data.setIsMultiArray( isMultiArray ); QString defval = list.value( "default" ); @@ -321,7 +313,7 @@ class NifXmlHandler final : public QXmlDefaultHandler if ( ok ) { data.value.setCount( enumVal ); } else { - data.value.fromString( defval ); + data.value.setFromString( defval ); } } @@ -348,6 +340,10 @@ class NifXmlHandler final : public QXmlDefaultHandler data.setVerCond( vercond ); } + // Set conditionless flag on data + if ( cond.isEmpty() && vercond.isEmpty() && ver1.isEmpty() && ver2.isEmpty() ) + data.setIsConditionless( true ); + if ( data.name().isEmpty() || data.type().isEmpty() ) err( tr( "add needs at least name and type attributes" ) ); } @@ -416,8 +412,7 @@ class NifXmlHandler final : public QXmlDefaultHandler case tagBlock: if ( blk ) { if ( blk->id.isEmpty() ) { - delete blk; - blk = 0; + blk = nullptr; err( tr( "invalid %1 declaration: name is empty" ).arg( tagid ) ); } @@ -521,7 +516,7 @@ class NifXmlHandler final : public QXmlDefaultHandler { // make a rough check of the maps for ( const QString& key : NifModel::compounds.keys() ) { - NifBlock * c = NifModel::compounds.value( key ); + NifBlockPtr c = NifModel::compounds.value( key ); for ( NifData data :c->types ) { if ( !checkType( data ) ) err( tr( "compound type %1 refers to unknown type %2" ).arg( key, data.type() ) ); @@ -535,7 +530,7 @@ class NifXmlHandler final : public QXmlDefaultHandler } for ( const QString& key : NifModel::blocks.keys() ) { - NifBlock * blk = NifModel::blocks.value( key ); + NifBlockPtr blk = NifModel::blocks.value( key ); if ( !blk->ancestor.isEmpty() && !NifModel::blocks.contains( blk->ancestor ) ) err( tr( "niobject %1 inherits unknown ancestor %2" ).arg( key, blk->ancestor ) ); @@ -566,7 +561,7 @@ class NifXmlHandler final : public QXmlDefaultHandler if ( errorStr.isEmpty() ) errorStr = "Syntax error"; - errorStr.prepend( tr( "XML parse error (line %1):
    " ).arg( exception.lineNumber() ) ); + errorStr.prepend( tr( "%1 XML parse error (line %2): " ).arg( "NIF" ).arg( exception.lineNumber() ) ); return false; } }; @@ -591,7 +586,7 @@ bool NifModel::loadXML() QString result = NifModel::parseXmlDescription( fname ); if ( !result.isEmpty() ) { - QMessageBox::critical( 0, "NifSkope", result ); + Message::append( tr( "Error loading XML
    You will need to reinstall the XML and restart the application." ), result, QMessageBox::Critical ); return false; } @@ -603,8 +598,8 @@ QString NifModel::parseXmlDescription( const QString & filename ) { QWriteLocker lck( &XMLlock ); - qDeleteAll( compounds ); compounds.clear(); - qDeleteAll( blocks ); blocks.clear(); + compounds.clear(); + blocks.clear(); supportedVersions.clear(); @@ -612,8 +607,11 @@ QString NifModel::parseXmlDescription( const QString & filename ) QFile f( filename ); + if ( !f.exists() ) + return tr( "nif.xml could not be found. Please install it and restart the application." ); + if ( !f.open( QIODevice::ReadOnly | QIODevice::Text ) ) - return tr( "error: couldn't open xml description file: " ) + filename; + return tr( "Couldn't open NIF XML description file: %1" ).arg( filename ); NifXmlHandler handler; QXmlSimpleReader reader; @@ -623,12 +621,8 @@ QString NifModel::parseXmlDescription( const QString & filename ) reader.parse( source ); if ( !handler.errorString().isEmpty() ) { - qDeleteAll( compounds ); compounds.clear(); - - qDeleteAll( blocks ); blocks.clear(); - supportedVersions.clear(); } diff --git a/src/options.cpp b/src/options.cpp deleted file mode 100644 index ec72e524c..000000000 --- a/src/options.cpp +++ /dev/null @@ -1,1134 +0,0 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2012, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. -3. The name of the NIF File Format Library and Tools project may not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -***** END LICENCE BLOCK *****/ - -#include "options.h" -#include "config.h" -#include "version.h" - -#include "widgets/colorwheel.h" -#include "widgets/fileselect.h" -#include "widgets/floatslider.h" -#include "widgets/groupbox.h" - -#include // Inherited -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -//! \file options.cpp SmallListView and Options implementation - -//! Helper class for Options::TexFolderView -class SmallListView final : public QListView -{ -public: - QSize sizeHint() const override final { return minimumSizeHint(); } -}; - -Options::Options() -{ - version = new NifSkopeVersion( NIFSKOPE_VERSION ); - - QSettings cfg; - cfg.beginGroup( "Render Settings" ); - - showMeshes = cfg.value( "Draw Meshes", true ).toBool(); - - // Cast QTimer slot - auto tStart = static_cast(&QTimer::start); - // Cast QButtonGroup signal - auto bgClicked = static_cast(&QButtonGroup::buttonClicked); - - tSave = new QTimer( this ); - tSave->setInterval( 5000 ); - tSave->setSingleShot( true ); - connect( this, &Options::sigChanged, tSave, tStart ); - connect( tSave, &QTimer::timeout, this, &Options::save ); - - tEmit = new QTimer( this ); - tEmit->setInterval( 500 ); - tEmit->setSingleShot( true ); - connect( tEmit, &QTimer::timeout, this, &Options::sigChanged ); - connect( this, &Options::sigChanged, tEmit, &QTimer::stop ); - - dialog = new GroupBox( "", Qt::Vertical ); - dialog->setWindowTitle( tr( "Settings" ) ); - dialog->setWindowFlags( Qt::Tool | Qt::WindowStaysOnTopHint ); - dialog->installEventFilter( this ); - - aDrawAxes = new QAction( tr( "Draw &Axes" ), this ); - aDrawAxes->setToolTip( tr( "draw xyz-Axes" ) ); - aDrawAxes->setCheckable( true ); - aDrawAxes->setChecked( cfg.value( "Draw Axes", true ).toBool() ); - connect( aDrawAxes, &QAction::toggled, this, &Options::sigChanged ); - - aDrawNodes = new QAction( tr( "Draw &Nodes" ), this ); - aDrawNodes->setToolTip( tr( "draw bones/nodes" ) ); - aDrawNodes->setCheckable( true ); - aDrawNodes->setChecked( cfg.value( "Draw Nodes", true ).toBool() ); - connect( aDrawNodes, &QAction::toggled, this, &Options::sigChanged ); - - aDrawHavok = new QAction( tr( "Draw &Havok" ), this ); - aDrawHavok->setToolTip( tr( "draw the havok shapes" ) ); - aDrawHavok->setCheckable( true ); - aDrawHavok->setChecked( cfg.value( "Draw Collision Geometry", true ).toBool() ); - connect( aDrawHavok, &QAction::toggled, this, &Options::sigChanged ); - - aDrawConstraints = new QAction( tr( "Draw &Constraints" ), this ); - aDrawConstraints->setToolTip( tr( "draw the havok constraints" ) ); - aDrawConstraints->setCheckable( true ); - aDrawConstraints->setChecked( cfg.value( "Draw Constraints", true ).toBool() ); - connect( aDrawConstraints, &QAction::toggled, this, &Options::sigChanged ); - - aDrawFurn = new QAction( tr( "Draw &Furniture" ), this ); - aDrawFurn->setToolTip( tr( "draw the furniture markers" ) ); - aDrawFurn->setCheckable( true ); - aDrawFurn->setChecked( cfg.value( "Draw Furniture Markers", true ).toBool() ); - connect( aDrawFurn, &QAction::toggled, this, &Options::sigChanged ); - - aDrawHidden = new QAction( tr( "Show Hid&den" ), this ); - aDrawHidden->setToolTip( tr( "always draw nodes and meshes" ) ); - aDrawHidden->setCheckable( true ); - aDrawHidden->setChecked( cfg.value( "Show Hidden Objects", false ).toBool() ); - connect( aDrawHidden, &QAction::toggled, this, &Options::sigChanged ); - - aDrawStats = new QAction( tr( "Show S&tats" ), this ); - aDrawStats->setToolTip( tr( "display some statistics about the selected node" ) ); - aDrawStats->setCheckable( true ); - aDrawStats->setChecked( cfg.value( "Show Stats", false ).toBool() ); - connect( aDrawStats, &QAction::toggled, this, &Options::sigChanged ); - - aSettings = new QAction( tr( "&Settings..." ), this ); - aSettings->setToolTip( tr( "show the settings dialog" ) ); - connect( aSettings, &QAction::triggered, dialog, &GroupBox::show ); - - cfg.endGroup(); - - GroupBox * texPage; - tab = new QTabWidget; - - if ( GroupBox * genPage = new GroupBox( Qt::Vertical ) ) { - tab->addTab( genPage, tr( "General" ) ); - - genPage->pushLayout( Qt::Vertical ); - cfg.beginGroup( "Settings" ); - - // Locale Settings - genPage->pushLayout( tr( "Regional and Language Settings" ), Qt::Vertical, 1 ); - genPage->pushLayout( Qt::Horizontal ); - - genPage->addWidget( new QLabel( "Language:" ) ); - RegionOpt = new QComboBox; - genPage->addWidget( RegionOpt ); - - QLocale localeInvariant( "en" ); - QString txtLang = QLocale::languageToString( localeInvariant.language() ); - - if ( localeInvariant.country() != QLocale::AnyCountry ) - txtLang.append( " (" ).append( QLocale::countryToString( localeInvariant.country() ) ).append( ")" ); - - RegionOpt->addItem( txtLang, localeInvariant ); - - QDir directory( QApplication::applicationDirPath() ); - - if ( !directory.cd( "lang" ) ) { -#ifdef Q_OS_LINUX - - if ( !directory.cd( "/usr/share/nifskope/lang" ) ) { - } - -#endif - } - - QRegularExpression fileRe( "NifSkope_(.*)\\.ts", QRegularExpression::CaseInsensitiveOption ); - - foreach ( const QString file, directory.entryList( QStringList( "NifSkope_*.ts" ), QDir::Files | QDir::NoSymLinks ) ) { - QRegularExpressionMatch fileReMatch = fileRe.match( file ); - if ( fileReMatch.hasMatch() ) { - QString localeText = fileReMatch.capturedTexts()[1]; - QLocale fileLocale( localeText ); - - if ( RegionOpt->findData( fileLocale ) < 0 ) { - QString txtLang = QLocale::languageToString( fileLocale.language() ); - - if ( fileLocale.country() != QLocale::AnyCountry ) - txtLang.append( " (" ).append( QLocale::countryToString( fileLocale.country() ) ).append( ")" ); - - RegionOpt->addItem( txtLang, fileLocale ); - } - } - } - QLocale locale = cfg.value( "Language", "en" ).toLocale(); - int localeIdx = RegionOpt->findData( locale ); - RegionOpt->setCurrentIndex( localeIdx > 0 ? localeIdx : 0 ); - - connect( RegionOpt, static_cast(&QComboBox::currentIndexChanged),this, &Options::sigChanged ); - connect( RegionOpt, static_cast(&QComboBox::currentIndexChanged), this, &Options::sigLocaleChanged ); - - genPage->popLayout(); - genPage->popLayout(); - - //Misc Options - genPage->pushLayout( tr( "Misc. Settings" ), Qt::Vertical, 1 ); - genPage->pushLayout( Qt::Horizontal ); - - genPage->addWidget( new QLabel( tr( "Startup Version" ) ) ); - genPage->addWidget( StartVer = new QLineEdit( cfg.value( "Startup Version", "20.0.0.5" ).toString() ) ); - StartVer->setToolTip( tr( "This is the version that the initial 'blank' NIF file that is created when NifSkope opens will be." ) ); - connect( StartVer, &QLineEdit::textChanged, this, &Options::sigChanged ); - - genPage->popLayout(); - - /* if we want to make max string length more accessible - genPage->pushLayout( Qt::Horizontal ); - genPage->addWidget( new QLabel( tr("Maximum String Length") ) ); - genPage->addWidget( StringLength = new QSpinBox ); - StringLength->setRange( 0, INT_MAX ); - StringLength->setValue( cfg.value( "Maximum String Length", 0x8000).toInt() ); - connect( StringLength, SIGNAL( valueChanged( int ) ), this, SIGNAL( sigChanged() ) ); - */ - - - //More Misc options can be added here. - genPage->popLayout(); - - cfg.endGroup(); - genPage->popLayout(); - } - - - tab->addTab( texPage = new GroupBox( Qt::Vertical ), tr( "&Rendering" ) ); - { - cfg.beginGroup( "Render Settings" ); - - texPage->pushLayout( tr( "Texture Folders" ), Qt::Vertical ); - texPage->pushLayout( Qt::Horizontal ); - -#ifdef Q_OS_WIN32 - texPage->pushLayout( tr( "Auto Detect" ), Qt::Vertical ); - QButtonGroup * tfgamegrp = new QButtonGroup( this ); - connect( tfgamegrp, bgClicked, this, &Options::textureFolderAutoDetect ); - QPushButton * bt = new QPushButton( tr( "Auto Detect\nGame Paths" ) ); - tfgamegrp->addButton( bt ); - texPage->addWidget( bt ); - - texPage->popLayout(); -#endif - - texPage->pushLayout( tr( "Custom" ), Qt::Vertical ); - texPage->pushLayout( Qt::Horizontal ); - - QButtonGroup * tfactgrp = new QButtonGroup( this ); - connect( tfactgrp, bgClicked, this, &Options::textureFolderAction ); - int tfaid = 0; - for ( const QString& tfaname : QStringList{ tr( "Add Folder" ), tr( "Remove Folder" ), tr( "Move Up" ), tr( "Move Down" ) } ) - { - QPushButton * bt = new QPushButton( tfaname ); - TexFolderButtons[tfaid] = bt; - tfactgrp->addButton( bt, tfaid++ ); - texPage->addWidget( bt ); - } - - texPage->popLayout(); - - - - TexFolderModel = new QStringListModel( this ); - TexFolderModel->setStringList( cfg.value( "Texture Folders" ).toStringList() ); - - connect( TexFolderModel, &QStringListModel::rowsInserted, tEmit, tStart ); - connect( TexFolderModel, &QStringListModel::rowsRemoved, tEmit, tStart ); - connect( TexFolderModel, &QStringListModel::dataChanged, tEmit, tStart ); - connect( TexFolderModel, &QStringListModel::modelReset, tEmit, tStart ); - connect( TexFolderModel, &QStringListModel::rowsInserted, this, &Options::sigFlush3D ); - connect( TexFolderModel, &QStringListModel::rowsRemoved, this, &Options::sigFlush3D ); - connect( TexFolderModel, &QStringListModel::dataChanged, this, &Options::sigFlush3D ); - connect( TexFolderModel, &QStringListModel::modelReset, this, &Options::sigFlush3D ); - - - TexFolderView = new SmallListView; - texPage->addWidget( TexFolderView, 0 ); - TexFolderView->setMinimumHeight( 105 ); - TexFolderView->setModel( TexFolderModel ); - TexFolderView->setCurrentIndex( TexFolderModel->index( 0, 0, QModelIndex() ) ); - - texPage->pushLayout( Qt::Horizontal ); - - TexFolderSelect = new FileSelector( FileSelector::Folder, "Folder", QBoxLayout::RightToLeft ); - texPage->addWidget( TexFolderSelect ); - - QDataWidgetMapper * TexFolderMapper = new QDataWidgetMapper( this ); - TexFolderMapper->setModel( TexFolderModel ); - TexFolderMapper->addMapping( TexFolderSelect, 0 ); - TexFolderMapper->setCurrentModelIndex( TexFolderView->currentIndex() ); - connect( TexFolderView->selectionModel(), &QItemSelectionModel::currentChanged, - TexFolderMapper, &QDataWidgetMapper::setCurrentModelIndex ); - connect( TexFolderSelect, &FileSelector::sigActivated, TexFolderMapper, &QDataWidgetMapper::submit ); - - connect( TexFolderView->selectionModel(), &QItemSelectionModel::currentChanged, - this, &Options::textureFolderIndex ); - textureFolderIndex( TexFolderView->currentIndex() ); - - texPage->addWidget( TexAlternatives = new QCheckBox( tr( "&Look for alternatives" ) ) ); - TexAlternatives->setToolTip( tr( "If a texture was nowhere to be found
    NifSkope will start looking for alternatives.

    texture.dds does not exist -> use texture.bmp instead

    " ) ); - TexAlternatives->setChecked( cfg.value( "Texture Alternatives", true ).toBool() ); - connect( TexAlternatives, &QCheckBox::toggled, this, &Options::sigChanged ); - connect( TexAlternatives, &QCheckBox::toggled, this, &Options::sigFlush3D ); - - texPage->popLayout(); - texPage->popLayout(); - texPage->popLayout(); - texPage->popLayout(); - texPage->pushLayout( Qt::Horizontal ); - texPage->pushLayout( tr( "Render" ), Qt::Vertical ); - - - texPage->addWidget( AntiAlias = new QCheckBox( tr( "&Anti Aliasing" ) ) ); - AntiAlias->setToolTip( tr( "Enable anti aliasing and anisotropic texture filtering if available.
    You'll need to restart NifSkope for this setting to take effect.
    " ) ); - AntiAlias->setChecked( cfg.value( "Anti Aliasing", true ).toBool() ); - connect( AntiAlias, &QCheckBox::toggled, this, &Options::sigChanged ); - - texPage->addWidget( Textures = new QCheckBox( tr( "&Textures" ) ) ); - Textures->setToolTip( tr( "Enable textures" ) ); - Textures->setChecked( cfg.value( "Texturing", true ).toBool() ); - connect( Textures, &QCheckBox::toggled, this, &Options::sigChanged ); - connect( Textures, &QCheckBox::toggled, this, &Options::sigFlush3D ); - - texPage->addWidget( Shaders = new QCheckBox( tr( "&Shaders" ) ) ); - Shaders->setToolTip( tr( "Enable Shaders" ) ); - Shaders->setChecked( cfg.value( "Enable Shaders", true ).toBool() ); - connect( Shaders, &QCheckBox::toggled, this, &Options::sigChanged ); - connect( Shaders, &QCheckBox::toggled, this, &Options::sigFlush3D ); - - texPage->popLayout(); - texPage->pushLayout( tr( "Up Axis" ), Qt::Vertical ); - - - texPage->addWidget( AxisX = new QRadioButton( tr( "X" ) ) ); - texPage->addWidget( AxisY = new QRadioButton( tr( "Y" ) ) ); - texPage->addWidget( AxisZ = new QRadioButton( tr( "Z" ) ) ); - - QButtonGroup * btgrp = new QButtonGroup( this ); - btgrp->addButton( AxisX ); - btgrp->addButton( AxisY ); - btgrp->addButton( AxisZ ); - btgrp->setExclusive( true ); - - QString upax = cfg.value( "Up Axis", "Z" ).toString(); - - if ( upax == "X" ) - AxisX->setChecked( true ); - else if ( upax == "Y" ) - AxisY->setChecked( true ); - else - AxisZ->setChecked( true ); - - connect( AxisX, &QRadioButton::toggled, this, &Options::sigChanged ); - connect( AxisY, &QRadioButton::toggled, this, &Options::sigChanged ); - connect( AxisZ, &QRadioButton::toggled, this, &Options::sigChanged ); - - - texPage->popLayout(); - texPage->pushLayout( tr( "Culling" ), Qt::Vertical, 1 ); - - texPage->addWidget( CullNoTex = new QCheckBox( tr( "Cull &Non Textured" ) ) ); - CullNoTex->setToolTip( tr( "Hide all meshes without textures" ) ); - CullNoTex->setChecked( cfg.value( "Cull Non Textured", false ).toBool() ); - connect( CullNoTex, &QCheckBox::toggled, this, &Options::sigChanged ); - - texPage->addWidget( CullByID = new QCheckBox( tr( "&Cull Nodes by Name" ) ) ); - CullByID->setToolTip( tr( "Enabling this option hides some special nodes and meshes" ) ); - CullByID->setChecked( cfg.value( "Cull Nodes By Name", false ).toBool() ); - connect( CullByID, &QCheckBox::toggled, this, &Options::sigChanged ); - - texPage->addWidget( CullExpr = new QLineEdit( cfg.value( "Cull Expression", "^collidee|^shadowcaster|^\\!LoD_cullme|^footprint" ).toString() ) ); - CullExpr->setToolTip( tr( "Enter a regular expression. Nodes which names match the expression will be hidden" ) ); -// leave RegExp input open as both rendering and collada cull sharing this -// CullExpr->setEnabled( CullByID->isChecked() ); - CullExpr->setEnabled( true ); - connect( CullExpr, &QLineEdit::textChanged, this, &Options::sigChanged ); -// connect( CullByID, SIGNAL( toggled( bool ) ), CullExpr, SLOT( setEnabled( bool ) ) ); - - texPage->popLayout(); - texPage->popLayout(); - - cfg.endGroup(); - } - - GroupBox * colorPage; - tab->addTab( colorPage = new GroupBox( Qt::Vertical ), tr( "Colors" ) ); - { - cfg.beginGroup( "Render Settings" ); - - colorPage->pushLayout( tr( "Light" ), Qt::Vertical ); - colorPage->pushLayout( Qt::Horizontal ); - - - cfg.beginGroup( "Light0" ); - - QStringList lightNames{ tr( "Ambient" ), tr( "Diffuse" ), tr( "Specular" ) }; - QList lightDefaults{ - QColor::fromRgbF( .4, .4, .4 ), - QColor::fromRgbF( .8, .8, .8 ), - QColor::fromRgbF( 1, 1, 1 ) - }; - - for ( int l = 0; l < 3; l++ ) { - ColorWheel * wheel = new ColorWheel( cfg.value( lightNames[l], lightDefaults[l] ).value() ); - wheel->setSizeHint( QSize( 105, 105 ) ); - wheel->setAlpha( false ); - connect( wheel, &ColorWheel::sigColorEdited, this, &Options::sigChanged ); - LightColor[l] = wheel; - - colorPage->pushLayout( lightNames[l], Qt::Vertical ); - colorPage->addWidget( wheel ); - colorPage->popLayout(); - } - - colorPage->popLayout(); - colorPage->pushLayout( Qt::Horizontal ); - - colorPage->addWidget( LightFrontal = new QCheckBox( tr( "Frontal" ) ), 0 ); - LightFrontal->setToolTip( tr( "Lock light to camera position" ) ); - LightFrontal->setChecked( cfg.value( "Frontal", true ).toBool() ); - connect( LightFrontal, &QCheckBox::toggled, this, &Options::sigChanged ); - - QWidget * pos = colorPage->pushLayout( tr( "Position" ), Qt::Horizontal, 1 ); - pos->setDisabled( LightFrontal->isChecked() ); - connect( LightFrontal, &QCheckBox::toggled, pos, &QWidget::setDisabled ); - - colorPage->addWidget( new QLabel( tr( "Declination" ) ) ); - colorPage->addWidget( LightDeclination = new QSpinBox, 1 ); - LightDeclination->setMinimum( -180 ); - LightDeclination->setMaximum( +180 ); - LightDeclination->setSingleStep( 5 ); - LightDeclination->setWrapping( true ); - LightDeclination->setValue( cfg.value( "Declination", 0 ).toInt() ); - connect( LightDeclination, static_cast(&QSpinBox::valueChanged), this, &Options::sigChanged ); - - colorPage->addWidget( new QLabel( tr( "Planar Angle" ) ) ); - colorPage->addWidget( LightPlanarAngle = new QSpinBox, 1 ); - LightPlanarAngle->setMinimum( -180 ); - LightPlanarAngle->setMaximum( +180 ); - LightPlanarAngle->setSingleStep( 5 ); - LightPlanarAngle->setWrapping( true ); - LightPlanarAngle->setValue( cfg.value( "Planar Angle", 0 ).toInt() ); - connect( LightPlanarAngle, static_cast(&QSpinBox::valueChanged), this, &Options::sigChanged ); - - cfg.endGroup(); - - colorPage->popLayout(); - colorPage->pushLayout( tr( "Presets" ), Qt::Horizontal ); - - QButtonGroup * grp = new QButtonGroup( this ); - connect( grp, bgClicked, this, &Options::activateLightPreset ); - int psid = 0; - for ( const QString& psname : QStringList{ tr( "Sunny Day" ), tr( "Dark Night" ) } ) - { - QPushButton * bt = new QPushButton( psname ); - grp->addButton( bt, psid++ ); - colorPage->addWidget( bt ); - } - - colorPage->popLayout(); - colorPage->popLayout(); - colorPage->popLayout(); - colorPage->pushLayout( tr( "Colors" ), Qt::Horizontal ); - - - QStringList colorNames{ tr( "Background" ), tr( "Foreground" ), tr( "Highlight" ) }; - QList colorDefaults{ - QColor::fromRgb( 0, 0, 0 ), - QColor::fromRgb( 255, 255, 255 ), - QColor::fromRgb( 255, 255, 0 ) - }; - - for ( int c = 0; c < 3; c++ ) { - colorPage->pushLayout( colorNames[c], Qt::Horizontal ); - - ColorWheel * wheel = new ColorWheel( cfg.value( colorNames[c], colorDefaults[c] ).value() ); - wheel->setSizeHint( QSize( 105, 105 ) ); - connect( wheel, &ColorWheel::sigColorEdited, this, &Options::sigChanged ); - colors[ c ] = wheel; - colorPage->addWidget( wheel ); - - if ( c != 0 ) { - alpha[ c ] = new AlphaSlider( Qt::Vertical ); - alpha[ c ]->setValue( cfg.value( colorNames[c], colorDefaults[c] ).value().alphaF() ); - alpha[ c ]->setColor( wheel->getColor() ); - connect( alpha[c], &AlphaSlider::valueChanged, this, &Options::sigChanged ); - connect( wheel, &ColorWheel::sigColor, alpha[ c ], &AlphaSlider::setColor ); - connect( alpha[c], &AlphaSlider::valueChanged, wheel, &ColorWheel::setAlphaValue ); - colorPage->addWidget( alpha[ c ] ); - } else { - alpha[c] = nullptr; - wheel->setAlpha( false ); - } - - colorPage->popLayout(); - } - - colorPage->popLayout(); - - cfg.endGroup(); - } - GroupBox * matPage; - tab->addTab( matPage = new GroupBox( Qt::Vertical ), tr( "Materials" ) ); - { - cfg.beginGroup( "Render Settings" ); - - QWidget * overrideBox = matPage->pushLayout( tr( "Material Overrides" ), Qt::Vertical ); - overrideBox->setMaximumHeight( 200 ); - - matPage->pushLayout( Qt::Horizontal ); - - cfg.beginGroup( "MatOver" ); - - QStringList names{ tr( "Ambient" ), tr( "Diffuse" ), tr( "Specular" ), tr( "Emissive" ) }; - QList defaults{ - QColor::fromRgbF( 1, 1, 1 ), - QColor::fromRgbF( 1, 1, 1 ), - QColor::fromRgbF( 1, 1, 1 ), - QColor::fromRgbF( 1, 1, 1 ) - }; - - for ( int l = 0; l < 4; l++ ) { - ColorWheel * wheel = new ColorWheel( cfg.value( names[l], defaults[l] ).value() ); - wheel->setSizeHint( QSize( 105, 105 ) ); - wheel->setAlpha( false ); - connect( wheel, &ColorWheel::sigColorEdited, this, &Options::materialOverridesChanged ); - matColors[l] = wheel; - - QWidget * box = matPage->pushLayout( names[l], Qt::Vertical ); - box->setMaximumSize( 150, 150 ); - matPage->addWidget( wheel ); - matPage->popLayout(); - } - - matPage->popLayout(); - matPage->pushLayout( Qt::Horizontal ); - - matPage->addWidget( overrideMatCheck = new QCheckBox( tr( "Enable Material Color Overrides" ) ), 0 ); - overrideMatCheck->setToolTip( tr( "Override colors used on Materials" ) ); - //overrideMaterials->setChecked( cfg.value( "Override", true ).toBool() ); - connect( overrideMatCheck, &QCheckBox::toggled, this, &Options::materialOverridesChanged ); - - cfg.endGroup(); - - matPage->popLayout(); - matPage->popLayout(); - - cfg.endGroup(); - } - - GroupBox * exportPage; - tab->addTab( exportPage = new GroupBox( Qt::Vertical ), tr( "Export" ) ); - { - cfg.beginGroup( "Export Settings" ); - exportPage->pushLayout( tr( "Export Settings" ), Qt::Vertical, 1 ); - exportPage->addWidget( exportCull = new QCheckBox( tr( "Use 'Cull Nodes by Name' rendering option to cull nodes on export" ) ), 1, Qt::AlignTop ); - exportCull->setChecked( cfg.value( "Export Culling", false ).toBool() ); - connect( exportCull, &QCheckBox::toggled, this, &Options::sigChanged ); - exportPage->popLayout(); - cfg.endGroup(); - } - - // set render page as default - tab->setCurrentWidget( texPage ); - - dialog->pushLayout( Qt::Vertical ); - - dialog->addWidget( tab ); - - dialog->popLayout(); -} - -Options::~Options() -{ - if ( tSave->isActive() ) - save(); -} - -bool Options::eventFilter( QObject * o, QEvent * e ) -{ - if ( o == dialog && e->type() == QEvent::Close && tSave->isActive() ) { - save(); - } - - return false; -} - -Options * Options::get() -{ - static Options * options = new Options(); - return options; -} - -QList Options::actions() -{ - Options * opts = get(); - return { opts->aDrawAxes, - opts->aDrawNodes, - opts->aDrawHavok , - opts->aDrawConstraints, - opts->aDrawFurn, - opts->aDrawHidden, -#ifdef USE_GL_QPAINTER - opts->aDrawStats, -#endif - opts->aSettings - }; -} - -void Options::save() -{ - tSave->stop(); - - QSettings cfg; - - cfg.beginGroup( "Render Settings" ); - - cfg.setValue( "Texture Folders", textureFolders() ); - cfg.setValue( "Texture Alternatives", textureAlternatives() ); - - cfg.setValue( "Draw Axes", drawAxes() ); - cfg.setValue( "Draw Nodes", drawNodes() ); - cfg.setValue( "Draw Collision Geometry", drawHavok() ); - cfg.setValue( "Draw Constraints", drawConstraints() ); - cfg.setValue( "Draw Furniture Markers", drawFurn() ); - cfg.setValue( "Show Hidden Objects", drawHidden() ); - cfg.setValue( "Show Stats", drawStats() ); - - cfg.setValue( "Background", bgColor() ); - cfg.setValue( "Foreground", nlColor() ); - cfg.setValue( "Highlight", hlColor() ); - - cfg.setValue( "Anti Aliasing", antialias() ); - cfg.setValue( "Texturing", texturing() ); - cfg.setValue( "Enable Shaders", shaders() ); - - cfg.setValue( "Cull Nodes By Name", CullByID->isChecked() ); - cfg.setValue( "Cull Expression", CullExpr->text() ); - cfg.setValue( "Cull Non Textured", onlyTextured() ); - - cfg.setValue( "Up Axis", AxisX->isChecked() ? "X" : AxisY->isChecked() ? "Y" : "Z" ); - - // Light0 group - cfg.setValue( "Light0/Ambient", ambient() ); - cfg.setValue( "Light0/Diffuse", diffuse() ); - cfg.setValue( "Light0/Specular", specular() ); - cfg.setValue( "Light0/Frontal", lightFrontal() ); - cfg.setValue( "Light0/Declination", lightDeclination() ); - cfg.setValue( "Light0/Planar Angle", lightPlanarAngle() ); - - // MatOver group - cfg.setValue( "MatOver/Ambient", overrideAmbient() ); - cfg.setValue( "MatOver/Diffuse", overrideDiffuse() ); - cfg.setValue( "MatOver/Specular", overrideSpecular() ); - cfg.setValue( "MatOver/Emissive", overrideEmissive() ); - - cfg.endGroup(); // Render Settings - - // Settings group - cfg.setValue( "Settings/Language", translationLocale() ); - cfg.setValue( "Settings/Startup Version", startupVersion() ); - // If we want to make this more accessible - //cfg.setValue( "Settings/Maximum String Length", maxStringLength() ); - - // Export Settings group - cfg.setValue( "Export Settings/Export Culling", exportCullEnabled() ); -} - -bool regTexturePath( QStringList & gamePaths, QString & gameList, // Out Params - const QString & regPath, const QString & regValue, - const QString & gameFolder, const QString & gameName, - QStringList gameSubDirs = QStringList(), - QStringList gameArchiveFilters = QStringList(), - QString confirmPath = QString() ) -{ - QSettings reg( regPath, QSettings::NativeFormat ); - QDir dir( reg.value( regValue ).toString() ); - - if ( dir.exists() && dir.cd( gameFolder ) ) { - gameList.append( gameName + "\n" ); - - gamePaths.append( dir.path() ); - if ( !gameSubDirs.isEmpty() ) { - foreach ( QString sd, gameSubDirs ) { - gamePaths.append( dir.path() + sd ); - } - } - - if ( !gameArchiveFilters.isEmpty() ) { - dir.setNameFilters( gameArchiveFilters ); - dir.setFilter( QDir::Dirs ); - foreach( QString dn, dir.entryList() ) { - gamePaths << dir.filePath( dn ); - - if ( !gameSubDirs.isEmpty() ) { - foreach ( QString sd, gameSubDirs ) { - gamePaths << dir.filePath( dn ) + sd; - } - } - } - } - return true; - } - return false; -} - -void Options::textureFolderAutoDetect() -{ - //List to hold all games paths that were detected - QStringList list; - - //Generic "same directory" path should always be added - list.append( "./" ); - - //String to hold the message box message - QString game_list; - -#ifdef Q_OS_WIN32 - - // Skyrim - { - regTexturePath( list, game_list, - "HKEY_LOCAL_MACHINE\\SOFTWARE\\Bethesda Softworks\\Skyrim", - "Installed Path", - "Data", - "TES V: Skyrim", - {}, /* No subdirs */ - { ".bsa" }, - "Textures" /* Confirm Textures if no FSENGINE */ - ); - } - - // Fallout: New Vegas - { - regTexturePath( list, game_list, - "HKEY_LOCAL_MACHINE\\SOFTWARE\\Bethesda Softworks\\FalloutNV", - "Installed Path", - "Data", - "Fallout: New Vegas", - {}, /* No subdirs */ - { ".bsa" }, - "Textures" /* Confirm Textures if no FSENGINE */ - ); - } - - // Fallout 3 - { - regTexturePath( list, game_list, - "HKEY_LOCAL_MACHINE\\SOFTWARE\\Bethesda Softworks\\Fallout3", - "Installed Path", - "Data", - "Fallout 3", - {}, /* No subdirs */ - { ".bsa" }, - "Textures" /* Confirm Textures if no FSENGINE */ - ); - } - - // Oblivion - { - regTexturePath( list, game_list, - "HKEY_LOCAL_MACHINE\\SOFTWARE\\Bethesda Softworks\\Oblivion", - "Installed Path", - "Data", - "TES IV: Oblivion", - {}, /* No subdirs */ - { ".bsa" }, - "Textures" /* Confirm Textures if no FSENGINE */ - ); - } - - // Morrowind - { - regTexturePath( list, game_list, - "HKEY_LOCAL_MACHINE\\SOFTWARE\\Bethesda Softworks\\Morrowind", - "Installed Path", - "Data", - "TES III: Morrowind", - { "/Textures" }, - { ".bsa" } - ); - } - - // CIV IV - { - regTexturePath( list, game_list, - "HKEY_LOCAL_MACHINE\\SOFTWARE\\Firaxis Games\\Sid Meier's Civilization 4", - "INSTALLDIR", - "Assets/Art/shared", - "Sid Meier's Civilization IV" - ); - } - - // Freedom Force - { - QStringList ffSubDirs{ "./textures", "./skins/standard" }; - - regTexturePath( list, game_list, - "HKEY_LOCAL_MACHINE\\SOFTWARE\\Irrational Games\\FFVTTR", - "InstallDir", - "Data/Art/library/area_specific/_textures", - "Freedom Force vs. the Third Reich", - ffSubDirs - ); - - regTexturePath( list, game_list, - "HKEY_LOCAL_MACHINE\\SOFTWARE\\Irrational Games\\Freedom Force", - "InstallDir", - "Data/Art/library/area_specific/_textures", - "Freedom Force", - ffSubDirs - ); - - regTexturePath( list, game_list, - "HKEY_LOCAL_MACHINE\\SOFTWARE\\Irrational Games\\Freedom Force Demo", - "InstallDir", - "Data/Art/library/area_specific/_textures", - "Freedom Force Demo", - ffSubDirs - ); - } - -#endif - //Set folder list box to contain the newly detected textures, along with the ones the user has already defined, ignoring any duplicates - list.removeDuplicates(); - - TexFolderModel->setStringList( list ); - TexAlternatives->setChecked( false ); - TexFolderView->setCurrentIndex( TexFolderModel->index( 0, 0, QModelIndex() ) ); - - //Announce result to user - if ( game_list.size() == 0 ) { - game_list = tr( "No supported games were detected.\nYour game may still work, you will just have to set the folders manually until an auto-detect routine is created." ); - } else { - game_list = tr( "Successfully detected the following games:\n" ) + game_list; - } - - QMessageBox::information( dialog, "NifSkope", game_list ); -} - -void Options::textureFolderAction( int id ) -{ - QModelIndex idx = TexFolderView->currentIndex(); - - switch ( id ) { - case 0: - // add folder - TexFolderModel->insertRow( 0, QModelIndex() ); - TexFolderModel->setData( TexFolderModel->index( 0, 0, QModelIndex() ), tr( "Choose a folder" ), Qt::EditRole ); - TexFolderView->setCurrentIndex( TexFolderModel->index( 0, 0, QModelIndex() ) ); - break; - case 1: - - if ( idx.isValid() ) { - // remove folder - TexFolderModel->removeRow( idx.row(), QModelIndex() ); - } - break; - case 2: - - if ( idx.isValid() && idx.row() > 0 ) { - // move up - QModelIndex xdi = idx.sibling( idx.row() - 1, 0 ); - QVariant v = TexFolderModel->data( idx, Qt::EditRole ); - TexFolderModel->setData( idx, TexFolderModel->data( xdi, Qt::EditRole ), Qt::EditRole ); - TexFolderModel->setData( xdi, v, Qt::EditRole ); - TexFolderView->setCurrentIndex( xdi ); - } - break; - case 3: - - if ( idx.isValid() && idx.row() < TexFolderModel->rowCount() - 1 ) { - // move down - QModelIndex xdi = idx.sibling( idx.row() + 1, 0 ); - QVariant v = TexFolderModel->data( idx, Qt::EditRole ); - TexFolderModel->setData( idx, TexFolderModel->data( xdi, Qt::EditRole ), Qt::EditRole ); - TexFolderModel->setData( xdi, v, Qt::EditRole ); - TexFolderView->setCurrentIndex( xdi ); - } - break; - } -} - -void Options::textureFolderIndex( const QModelIndex & idx ) -{ - TexFolderSelect->setEnabled( idx.isValid() ); - TexFolderButtons[0]->setEnabled( true ); - TexFolderButtons[1]->setEnabled( idx.isValid() ); - TexFolderButtons[2]->setEnabled( idx.isValid() && ( idx.row() > 0 ) ); - TexFolderButtons[3]->setEnabled( idx.isValid() && ( idx.row() < TexFolderModel->rowCount() - 1 ) ); -} - -void Options::activateLightPreset( int id ) -{ - switch ( id ) { - case 0: // sunny day - LightColor[0]->setColor( QColor::fromRgbF( 0.4, 0.4, 0.4 ) ); - LightColor[1]->setColor( QColor::fromRgbF( 0.8, 0.8, 0.8 ) ); - LightColor[2]->setColor( QColor::fromRgbF( 1.0, 1.0, 1.0 ) ); - break; - case 1: // dark night - LightColor[0]->setColor( QColor::fromRgbF( 0.2, 0.2, 0.25 ) ); - LightColor[1]->setColor( QColor::fromRgbF( 0.1, 0.1, 0.1 ) ); - LightColor[2]->setColor( QColor::fromRgbF( 0.1, 0.1, 0.1 ) ); - break; - default: - return; - } - - emit sigChanged(); -} - -QStringList Options::textureFolders() -{ - return get()->TexFolderModel->stringList(); -} - -bool Options::textureAlternatives() -{ - return get()->TexAlternatives->isChecked(); -} - -Options::Axis Options::upAxis() -{ - return get()->AxisX->isChecked() ? XAxis : get()->AxisY->isChecked() ? YAxis : ZAxis; -} - -bool Options::antialias() -{ - return get()->AntiAlias->isChecked(); -} - -bool Options::texturing() -{ - return get()->Textures->isChecked(); -} - -bool Options::shaders() -{ - return get()->Shaders->isChecked(); -} - - -bool Options::drawAxes() -{ - return get()->aDrawAxes->isChecked(); -} - -bool Options::drawNodes() -{ - return get()->aDrawNodes->isChecked(); -} - -bool Options::drawHavok() -{ - return get()->aDrawHavok->isChecked(); -} - -bool Options::drawConstraints() -{ - return get()->aDrawConstraints->isChecked(); -} - -bool Options::drawFurn() -{ - return get()->aDrawFurn->isChecked(); -} - -bool Options::drawHidden() -{ - return get()->aDrawHidden->isChecked(); -} - -bool Options::drawStats() -{ - return get()->aDrawStats->isChecked(); -} - -bool Options::benchmark() -{ - return false; -} - - -QColor Options::bgColor() -{ - return get()->colors[ 0 ]->getColor(); -} - -QColor Options::nlColor() -{ - QColor c = get()->colors[ 1 ]->getColor(); - c.setAlphaF( get()->alpha[ 1 ]->value() ); - return c; -} - -QColor Options::hlColor() -{ - QColor c = get()->colors[ 2 ]->getColor(); - c.setAlphaF( get()->alpha[ 2 ]->value() ); - return c; -} - - -QRegularExpression Options::cullExpression() -{ - return get()->CullByID->isChecked() ? QRegularExpression( get()->CullExpr->text() ) : QRegularExpression(); -} - -bool Options::onlyTextured() -{ - return get()->CullNoTex->isChecked(); -} - - -QColor Options::ambient() -{ - return get()->LightColor[ 0 ]->getColor(); -} - -QColor Options::diffuse() -{ - return get()->LightColor[ 1 ]->getColor(); -} - -QColor Options::specular() -{ - return get()->LightColor[ 2 ]->getColor(); -} - -bool Options::lightFrontal() -{ - return get()->LightFrontal->isChecked(); -} - -int Options::lightDeclination() -{ - return get()->LightDeclination->value(); -} - -int Options::lightPlanarAngle() -{ - return get()->LightPlanarAngle->value(); -} - -QString Options::startupVersion() -{ - return get()->StartVer->text(); -}; - -bool Options::overrideMaterials() -{ - return get()->overrideMatCheck->isChecked(); -} - -QColor Options::overrideAmbient() -{ - return get()->matColors[0]->getColor(); -} - -QColor Options::overrideDiffuse() -{ - return get()->matColors[1]->getColor(); -} - -QColor Options::overrideSpecular() -{ - return get()->matColors[2]->getColor(); -} - -QColor Options::overrideEmissive() -{ - return get()->matColors[3]->getColor(); -} - -/*! - * This option is hidden in the registry and disabled by setting the key - * Render Settings/Draw Meshes to false - */ -bool Options::drawMeshes() -{ - return get()->showMeshes; -} - -QLocale Options::translationLocale() -{ - int idx = get()->RegionOpt->currentIndex(); - - if ( idx >= 0 ) { - return get()->RegionOpt->itemData( idx ).toLocale(); - } - - return QLocale::system(); -} - -/* if we want to make this more accessible -int Options::maxStringLength() -{ - return get()->StringLength->value(); -} -*/ - -bool Options::exportCullEnabled() -{ - return get()->exportCull->isChecked(); -} - - -QString Options::getDisplayVersion() -{ - return get()->version->displayVersion; -} diff --git a/src/options.h b/src/options.h deleted file mode 100644 index f60ff65b6..000000000 --- a/src/options.h +++ /dev/null @@ -1,284 +0,0 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2012, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. -3. The name of the NIF File Format Library and Tools project may not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -***** END LICENCE BLOCK *****/ - -#ifndef OPTIONS_H -#define OPTIONS_H - -#include "widgets/groupbox.h" - -#include // Inherited - -#define NifSkopeDisplayRole (Qt::UserRole + 42) - - -//! \file options.h Options class - -class AlphaSlider; -class ColorWheel; -class FileSelector; -class GroupBox; - -class QAbstractButton; -class QAction; -class QColor; -class QComboBox; -class QCheckBox; -class QDialog; -class QLineEdit; -class QListView; -class QModelIndex; -class QRadioButton; -class QSpinBox; -class QStringListModel; -class QTabWidget; -class QTimer; - -class NifSkopeVersion; - -//! Gets the color normally used for drawing from Options::nlColor() -#define glNormalColor() glColor( Color4( Options::nlColor() ) ) -//! Gets the color used for highlighting from Options::hlColor() -#define glHighlightColor() glColor( Color4( Options::hlColor() ) ) - -//! Global options menu and dialog -class Options final : public QObject -{ - Q_OBJECT - -public: - //! Global instance - static Options * get(); - //! The list of currently enabled actions - static QList actions(); - - static QString getDisplayVersion(); - - //! Texture folders - static QStringList textureFolders(); - //! Whether to use alternative textures - static bool textureAlternatives(); - - //! Whether to enable antialiasing - static bool antialias(); - //! Whether to enable texturing - static bool texturing(); - //! Whether to enable shaders - static bool shaders(); - - //! Whether to enable blending - static bool blending() { return true; } - - //! The background color of the main view window - static QColor bgColor(); - //! The colour to normally use for drawing - static QColor nlColor(); - //! The colour to use when highlighting - static QColor hlColor(); - - //! Regular expression to use for culling - static QRegularExpression cullExpression(); - //! Whether to only draw textured shapes - static bool onlyTextured(); - - //! Whether to draw the axes - static bool drawAxes(); - //! Whether to draw nodes - static bool drawNodes(); - //! Whether to draw Havok shapes - static bool drawHavok(); - //! Whether to draw constraints - static bool drawConstraints(); - //! Whether to draw furniture markers - static bool drawFurn(); - //! Whether to draw hidden shapes - static bool drawHidden(); - //! Whether to draw stats - static bool drawStats(); - //! Whether to draw meshes - static bool drawMeshes(); - - //! Whether to benchmark FPS - static bool benchmark(); - - //! The possible axes - typedef enum - { - ZAxis, YAxis, XAxis - } Axis; - - //! The axis defined as up - static Axis upAxis(); - - //! The ambient lighting color - static QColor ambient(); - //! The diffuse lighting color - static QColor diffuse(); - //! The specular lighting color - static QColor specular(); - - //! Whether to use frontal lighting - static bool lightFrontal(); - //! The angle between the Z axis and the light - static int lightDeclination(); - //! The angle between the X axis and the light - static int lightPlanarAngle(); - - //! Whether to override material colors - static bool overrideMaterials(); - //! The ambient color to override materials with - static QColor overrideAmbient(); - //! The diffuse color to override materials with - static QColor overrideDiffuse(); - //! The specular color to override materials with - static QColor overrideSpecular(); - //! The emissive color to override materials with - static QColor overrideEmissive(); - - //! The NIF version to use at start - static QString startupVersion(); - //! The current translation locale - static QLocale translationLocale(); - // Maximum string length (see NifIStream::init for the current usage) - //static int maxStringLength(); - //! status of Collada Cull setting - static bool exportCullEnabled(); - -signals: - //! Signal emitted when a value changes - void sigChanged(); - //! Signal emitted when a value changes and needs to flush 3D data - void sigFlush3D(); - //! Signal emitted when material overrides change - void materialOverridesChanged(); - //! Signal emitted when the locale changes - void sigLocaleChanged(); - -protected slots: - //! Texture folder button actions - void textureFolderAction( int ); - //! Per-index texture folder options - void textureFolderIndex( const QModelIndex & ); - //! Automatic detection of texture folders - void textureFolderAutoDetect(); - //! Set lighting presets - void activateLightPreset( int ); - -public slots: - void save(); - -protected: - friend class TexturesPage; - friend class ColorsOptionPage; - friend class MaterialOverrideOptionPage; - - Options(); - ~Options(); - - bool eventFilter( QObject * o, QEvent * e ) override final; - - // NifSkope Version formatted for display - NifSkopeVersion * version; - - ////////////////////////////////////////////////////////////////////////// - // Menu - - QAction * aDrawAxes; - QAction * aDrawNodes; - QAction * aDrawHavok; - QAction * aDrawConstraints; - QAction * aDrawFurn; - QAction * aDrawHidden; - QAction * aDrawStats; - - QAction * aSettings; - - QTimer * tSave, * tEmit; - - ////////////////////////////////////////////////////////////////////////// - // General Settings page - - QComboBox * RegionOpt; - QLineEdit * StartVer; - //QSpinBox * StringLength; - - ////////////////////////////////////////////////////////////////////////// - // Rendering Settings page - - QStringListModel * TexFolderModel; - QListView * TexFolderView; - FileSelector * TexFolderSelect; - QCheckBox * TexAlternatives; - QAbstractButton * TexFolderButtons[4]; - - QCheckBox * AntiAlias; - QCheckBox * Textures; - QCheckBox * Shaders; - - QCheckBox * CullNoTex; - QCheckBox * CullByID; - QLineEdit * CullExpr; - - QRadioButton * AxisX; - QRadioButton * AxisY; - QRadioButton * AxisZ; - - ////////////////////////////////////////////////////////////////////////// - // Colors Settings page - - ColorWheel * colors[3]; - AlphaSlider * alpha[3]; - - ColorWheel * LightColor[3]; - - QCheckBox * LightFrontal; - QSpinBox * LightDeclination; - QSpinBox * LightPlanarAngle; - - ////////////////////////////////////////////////////////////////////////// - // Materials Settings page - - QCheckBox * overrideMatCheck; - ColorWheel * matColors[4]; - - ////////////////////////////////////////////////////////////////////////// - // Export Settings page - QCheckBox * exportCull; - - ////////////////////////////////////////////////////////////////////////// - - GroupBox * dialog; - QTabWidget * tab; - - bool showMeshes; -}; - -#endif diff --git a/src/qhull.cpp b/src/qhull.cpp index 1e017e562..584e332fb 100644 --- a/src/qhull.cpp +++ b/src/qhull.cpp @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -40,15 +40,16 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // Disable compiler warning #pragma warning(disable: 4005) // Disable Code Analysis warnings -#pragma warning(disable: 6001) -#pragma warning(disable: 6011) -#pragma warning(disable: 6031) -#pragma warning(disable: 6305) -#pragma warning(disable: 6387) -#pragma warning(disable: 28182) -#pragma warning(disable: 28183) +#pragma warning(disable: 6001 6011 6031 6305 6387) +#pragma warning(disable: 28182 28183) // ALL_CODE_ANALYSIS_WARNINGS seems // to no longer work as of msvc2013 +#else +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wclobbered" +#pragma GCC diagnostic ignored "-Wunused-but-set-variable" +#pragma GCC diagnostic ignored "-Wunused-but-set-parameter" +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" #endif extern "C" { @@ -74,6 +75,8 @@ extern "C" } #ifdef _MSC_VER #pragma warning(pop) +#else +#pragma GCC diagnostic pop #endif #include diff --git a/src/qhull.h b/src/qhull.h index 574d3a4a6..264246f36 100644 --- a/src/qhull.h +++ b/src/qhull.h @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/src/settings.cpp b/src/settings.cpp new file mode 100644 index 000000000..31dca46f3 --- /dev/null +++ b/src/settings.cpp @@ -0,0 +1,774 @@ +#include "settings.h" + +#include "widgets/colorwheel.h" +#include "widgets/floatslider.h" +#include "ui/settingsdialog.h" + +#include "ui_settingsgeneral.h" +#include "ui_settingsrender.h" +#include "ui_settingsresources.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +SettingsPane::SettingsPane( QWidget * parent ) : + QWidget( parent ) +{ + dlg = qobject_cast(parent); + if ( dlg ) { + connect( dlg, &SettingsDialog::loadSettings, this, &SettingsPane::read ); + connect( dlg, &SettingsDialog::saveSettings, this, &SettingsPane::write ); + connect( this, &SettingsPane::paneModified, dlg, &SettingsDialog::modified ); + } + + QSettings settings; + QVariant settingsVersion = settings.value( "Settings/Version" ); + if ( settingsVersion.isNull() ) { + // First time install + setModified( true ); + } +} + +SettingsPane::~SettingsPane() +{ +} + +bool SettingsPane::isModified() +{ + return modified; +} + +void SettingsPane::setModified( bool m ) +{ + modified = m; +} + +void SettingsPane::modifyPane() +{ + if ( !modified ) { + emit paneModified(); + + modified = true; + } +} + +QString humanize( QString str ) +{ + str = str.replace( QRegularExpression( "(?<=[A-Z])(?=[A-Z][a-z])|(?<=[^A-Z])(?=[A-Z])|(?<=[A-Za-z])(?=[^A-Za-z])" ), " " ); + str[0] = str[0].toUpper(); + + return str; +} + + +void SettingsPane::readPane( QWidget * w, QSettings & settings ) +{ + for ( auto c : w->children() ) { + + auto f = qobject_cast(c); + if ( f ) { + readPane( f, settings ); + } + + auto grp = qobject_cast(c); + if ( grp ) { + QString name = grp->title(); + if ( !name.isEmpty() && !grp->isFlat() ) + settings.beginGroup( name ); + + readPane( grp, settings ); + + if ( !name.isEmpty() && !grp->isFlat() ) + settings.endGroup(); + } + + auto chk = qobject_cast(c); + if ( chk ) { + QString name = (!chk->text().isEmpty()) ? chk->text() : ::humanize( chk->objectName() ); + QVariant val = settings.value( name ); + if ( !val.isNull() ) + chk->setChecked( val.toBool() ); + + connect( chk, &QCheckBox::stateChanged, this, &SettingsPane::modifyPane ); + } + + auto cmb = qobject_cast(c); + if ( cmb ) { + QVariant val = settings.value( ::humanize( cmb->objectName() ) ); + if ( !val.isNull() ) + cmb->setCurrentIndex( val.toInt() ); + + connect( cmb, &QComboBox::currentTextChanged, this, &SettingsPane::modifyPane ); + } + + auto btn = qobject_cast(c); + if ( btn ) { + QString name = (!btn->text().isEmpty()) ? btn->text() : ::humanize( btn->objectName() ); + QVariant val = settings.value( name ); + if ( !val.isNull() ) + btn->setChecked( val.toBool() ); + + connect( btn, &QRadioButton::clicked, this, &SettingsPane::modifyPane ); + } + + auto edt = qobject_cast(c); + if ( edt ) { + QVariant val = settings.value( ::humanize( edt->objectName() ) ); + if ( !val.isNull() ) + edt->setText( val.toString() ); + + auto tEmit = new QTimer( this ); + tEmit->setInterval( 500 ); + tEmit->setSingleShot( true ); + connect( tEmit, &QTimer::timeout, this, &SettingsPane::modifyPane ); + connect( edt, &QLineEdit::textEdited, [this, tEmit]() { + if ( tEmit->isActive() ) { + tEmit->stop(); + } + + tEmit->start(); + } ); + } + + auto clr = qobject_cast(c); + if ( clr ) { + QVariant val = settings.value( ::humanize( clr->objectName() ) ); + if ( !val.isNull() ) + clr->setColor( val.value() ); + } + + auto spn = qobject_cast(c); + if ( spn ) { + QVariant val = settings.value( ::humanize( spn->objectName() ) ); + if ( !val.isNull() ) + spn->setValue( val.toInt() ); + + connect( spn, static_cast(&QSpinBox::valueChanged), this, &SettingsPane::modifyPane ); + } + + auto dbl = qobject_cast(c); + if ( dbl ) { + QVariant val = settings.value( ::humanize( dbl->objectName() ) ); + if ( !val.isNull() ) + dbl->setValue( val.toDouble() ); + + connect( dbl, static_cast(&QDoubleSpinBox::valueChanged), this, &SettingsPane::modifyPane ); + } + } +} + +void SettingsPane::writePane( QWidget * w, QSettings & settings ) +{ + for ( auto c : w->children() ) { + + auto f = qobject_cast(c); + if ( f ) { + writePane( f, settings ); + } + + auto grp = qobject_cast(c); + if ( grp ) { + QString name = grp->title(); + if ( !name.isEmpty() && !grp->isFlat() ) + settings.beginGroup( name ); + + writePane( grp, settings ); + + if ( !name.isEmpty() && !grp->isFlat() ) + settings.endGroup(); + } + + auto chk = qobject_cast(c); + if ( chk ) { + QString name = (!chk->text().isEmpty()) ? chk->text() : ::humanize( chk->objectName() ); + settings.setValue( name, chk->isChecked() ); + } + + auto cmb = qobject_cast(c); + if ( cmb ) { + settings.setValue( ::humanize( cmb->objectName() ), cmb->currentIndex() ); + } + + auto btn = qobject_cast(c); + if ( btn ) { + QString name = (!btn->text().isEmpty()) ? btn->text() : ::humanize( btn->objectName() ); + settings.setValue( name, btn->isChecked() ); + } + + auto edt = qobject_cast(c); + if ( edt ) { + settings.setValue( ::humanize( edt->objectName() ), edt->text() ); + } + + auto clr = qobject_cast(c); + if ( clr ) { + settings.setValue( ::humanize( clr->objectName() ), clr->getColor() ); + } + + auto spn = qobject_cast(c); + if ( spn ) { + settings.setValue( ::humanize( spn->objectName() ), spn->value() ); + } + + auto dbl = qobject_cast(c); + if ( dbl ) { + settings.setValue( ::humanize( dbl->objectName() ), dbl->value() ); + } + } +} + + +/* + * General + */ + +SettingsGeneral::SettingsGeneral( QWidget * parent ) : + SettingsPane( parent ), + ui( new Ui::SettingsGeneral ) +{ + ui->setupUi( this ); + SettingsDialog::registerPage( parent, ui->name->text() ); + + QLocale locale( "en" ); + QString txtLang = QLocale::languageToString( locale.language() ); + + if ( locale.country() != QLocale::AnyCountry ) + txtLang.append( " (" ).append( QLocale::countryToString( locale.country() ) ).append( ")" ); + + ui->language->addItem( txtLang, locale ); + ui->language->setCurrentIndex( 0 ); + + QDir directory( QApplication::applicationDirPath() ); + + if ( !directory.cd( "lang" ) ) { +#ifdef Q_OS_LINUX + directory.cd( "/usr/share/nifskope/lang" ); +#endif + } + + QRegularExpression fileRe( "NifSkope_(.*)\\.qm", QRegularExpression::CaseInsensitiveOption ); + + for ( const QString file : directory.entryList( QStringList( "NifSkope_*.qm" ), QDir::Files | QDir::NoSymLinks ) ) + { + QRegularExpressionMatch fileReMatch = fileRe.match( file ); + if ( fileReMatch.hasMatch() ) { + QLocale fileLocale( fileReMatch.capturedTexts()[1] ); + if ( ui->language->findData( fileLocale ) < 0 ) { + QString txtLang = QLocale::languageToString( fileLocale.language() ); + + if ( fileLocale.country() != QLocale::AnyCountry ) + txtLang.append( " (" + QLocale::countryToString( fileLocale.country() ) + ")" ); + + ui->language->addItem( txtLang, fileLocale ); + } + } + } +} + +SettingsGeneral::~SettingsGeneral() +{ + +} + +void SettingsGeneral::read() +{ + QSettings settings; + + settings.beginGroup( "Settings" ); + + for ( int i = 0; i < ui->general->count(); i++ ) { + + auto w = ui->general->widget( i ); + + settings.beginGroup( ::humanize( w->objectName() ) ); + readPane( w, settings ); + settings.endGroup(); + } + + settings.endGroup(); + + setModified( false ); +} + +void SettingsGeneral::write() +{ + if ( !isModified() ) + return; + + QSettings settings; + + settings.beginGroup( "Settings" ); + + int langPrev = settings.value( "UI/Language", 0 ).toInt(); + + for ( int i = 0; i < ui->general->count(); i++ ) { + + auto w = ui->general->widget( i ); + + settings.beginGroup( ::humanize( w->objectName() ) ); + writePane( w, settings ); + settings.endGroup(); + } + + int langCur = settings.value( "UI/Language", 0 ).toInt(); + + settings.endGroup(); + + // Set Locale + auto idx = ui->language->currentIndex(); + settings.setValue( "Settings/Locale", ui->language->itemData( idx ).toLocale() ); + + if ( langPrev != langCur ) { + emit dlg->localeChanged(); + } + + setModified( false ); +} + +void SettingsGeneral::setDefault() +{ + read(); +} + + +/* + * Render + */ + +SettingsRender::SettingsRender( QWidget * parent ) : + SettingsPane( parent ), + ui( new Ui::SettingsRender ) +{ + ui->setupUi( this ); + SettingsDialog::registerPage( parent, ui->name->text() ); + + auto color = [this]( const QString & str, ColorWheel * w, ColorLineEdit * e, const QColor & color ) { + e->setWheel( w, str ); + w->setColor( color ); + + connect( w, &ColorWheel::sigColorEdited, this, &SettingsPane::modifyPane ); + }; + + color( "Background", ui->colorBackground, ui->background, QColor( 0, 0, 0 ) ); + color( "Wireframe", ui->colorWireframe, ui->wireframe, QColor( 0, 255, 0 ) ); + color( "Highlight", ui->colorHighlight, ui->highlight, QColor( 255, 255, 0 ) ); + + + auto alphaSlider = [this]( ColorWheel * c, ColorLineEdit * e, QHBoxLayout * l ) { + auto alpha = new AlphaSlider( Qt::Vertical ); + alpha->setParent( this ); + + c->setAlpha( true ); + e->setAlpha( 1.0 ); + l->addWidget( alpha ); + + connect( c, &ColorWheel::sigColor, alpha, &AlphaSlider::setColor ); + connect( alpha, &AlphaSlider::valueChanged, c, &ColorWheel::setAlphaValue ); + connect( alpha, &AlphaSlider::valueChanged, e, &ColorLineEdit::setAlpha ); + connect( alpha, &AlphaSlider::valueChanged, this, &SettingsPane::modifyPane ); + + alpha->setColor( e->getColor() ); + }; + + alphaSlider( ui->colorWireframe, ui->wireframe, ui->layAlphaWire ); + alphaSlider( ui->colorHighlight, ui->highlight, ui->layAlphaHigh ); +} + +void SettingsRender::read() +{ + QSettings settings; + + settings.beginGroup( "Settings/Render" ); + + for ( int i = 0; i < ui->render->count(); i++ ) { + + auto w = ui->render->widget( i ); + + settings.beginGroup( ::humanize( w->objectName() ) ); + readPane( w, settings ); + settings.endGroup(); + } + + settings.endGroup(); + + setModified( false ); +} + +void SettingsRender::write() +{ + if ( !isModified() ) + return; + + QSettings settings; + + settings.beginGroup( "Settings/Render" ); + + for ( int i = 0; i < ui->render->count(); i++ ) { + + auto w = ui->render->widget( i ); + + settings.beginGroup( ::humanize( w->objectName() ) ); + writePane( w, settings ); + settings.endGroup(); + } + + settings.endGroup(); + + setModified( false ); +} + +SettingsRender::~SettingsRender() +{ +} + +void SettingsRender::setDefault() +{ + read(); +} + + +bool regFolderPath( QStringList & gamePaths, const QString & regPath, const QString & regValue, const QString & gameFolder, + QStringList gameSubDirs = QStringList(), QStringList gameArchiveFilters = QStringList() ) +{ + QSettings reg( regPath, QSettings::NativeFormat ); + QDir dir( reg.value( regValue ).toString() ); + + if ( dir.exists() && dir.cd( gameFolder ) ) { + gamePaths.append( dir.path() ); + if ( !gameSubDirs.isEmpty() ) { + for ( QString & sd : gameSubDirs ) { + gamePaths.append( dir.path() + sd ); + } + } + + if ( !gameArchiveFilters.isEmpty() ) { + dir.setNameFilters( gameArchiveFilters ); + dir.setFilter( QDir::Dirs ); + for ( QString & dn : dir.entryList() ) { + gamePaths << dir.filePath( dn ); + + if ( !gameSubDirs.isEmpty() ) { + for ( QString & sd : gameSubDirs ) { + gamePaths << dir.filePath( dn ) + sd; + } + } + } + } + return true; + } + return false; +} + +bool regFolderPaths( QStringList & gamePaths, const QStringList & regPaths, const QString & regValue, const QString & gameFolder, + QStringList gameSubDirs = QStringList(), QStringList gameArchiveFilters = QStringList() ) +{ + bool result = false; + for ( const QString & path : regPaths ) { + result |= ::regFolderPath( gamePaths, path, regValue, gameFolder, gameSubDirs, gameArchiveFilters ); + } + return result; +} + +/* + * Resources + */ + +SettingsResources::SettingsResources( QWidget * parent ) : + SettingsPane( parent ), + ui( new Ui::SettingsResources ) +{ + ui->setupUi( this ); + SettingsDialog::registerPage( parent, ui->name->text() ); + + archiveMgr = FSManager::get(); + + folders = new QStringListModel( this ); + archives = new QStringListModel( this ); + + ui->foldersList->setModel( folders ); + ui->archivesList->setModel( archives ); + +#ifndef Q_OS_WIN32 + ui->btnArchiveAutoDetect->setHidden( true ); + ui->btnFolderAutoDetect->setHidden( true ); +#endif + + // Move Up / Move Down Behavior + connect( ui->foldersList->selectionModel(), &QItemSelectionModel::currentChanged, + [this]( const QModelIndex & idx, const QModelIndex & last ) + { + Q_UNUSED( last ); + + ui->btnFolderUp->setEnabled( idx.row() > 0 ); + ui->btnFolderDown->setEnabled( idx.row() < folders->rowCount() - 1 ); + } + ); + + // Move Up / Move Down Behavior + connect( ui->archivesList->selectionModel(), &QItemSelectionModel::currentChanged, + [this]( const QModelIndex & idx, const QModelIndex & last ) + { + Q_UNUSED( last ); + + ui->btnArchiveUp->setEnabled( idx.row() > 0 ); + ui->btnArchiveDown->setEnabled( idx.row() < archives->rowCount() - 1 ); + } + ); +} + +SettingsResources::~SettingsResources() +{ +} + +void SettingsResources::read() +{ + QSettings settings; + + QVariant foldersVal = settings.value( "Settings/Resources/Folders", QStringList() ); + folders->setStringList( foldersVal.toStringList() ); + + QVariant archivesVal = settings.value( "Settings/Resources/Archives", QStringList() ); + archives->setStringList( archivesVal.toStringList() ); + + ui->foldersList->setCurrentIndex( folders->index( 0, 0 ) ); + ui->archivesList->setCurrentIndex( archives->index( 0, 0 ) ); + + ui->chkAlternateExt->setChecked( settings.value( "Settings/Resources/Alternate Extensions", true ).toBool() ); + + setModified( false ); +} + +void SettingsResources::write() +{ + if ( !isModified() ) + return; + + QSettings settings; + + settings.setValue( "Settings/Resources/Folders", folders->stringList() ); + settings.setValue( "Settings/Resources/Archives", archives->stringList() ); + + // Sync FSManager to Archives list + archiveMgr->archives.clear(); + for ( const QString an : archives->stringList() ) { + if ( !archiveMgr->archives.contains( an ) ) + if ( auto a = FSArchiveHandler::openArchive( an ) ) + archiveMgr->archives.insert( an, a ); + } + + settings.setValue( "Settings/Resources/Alternate Extensions", ui->chkAlternateExt->isChecked() ); + + setModified( false ); + + emit dlg->flush3D(); +} + +void SettingsResources::setDefault() +{ + read(); +} + + +void moveIdxDown( QListView * view ) +{ + QModelIndex idx = view->currentIndex(); + auto model = view->model(); + + if ( idx.isValid() && idx.row() < model->rowCount() - 1 ) { + QModelIndex downIdx = idx.sibling( idx.row() + 1, 0 ); + QVariant v = model->data( idx, Qt::EditRole ); + model->setData( idx, model->data( downIdx, Qt::EditRole ) ); + model->setData( downIdx, v ); + view->setCurrentIndex( downIdx ); + } +} + +void moveIdxUp( QListView * view ) +{ + QModelIndex idx = view->currentIndex(); + auto model = view->model(); + + if ( idx.isValid() && idx.row() > 0 ) { + QModelIndex upIdx = idx.sibling( idx.row() - 1, 0 ); + QVariant v = model->data( idx, Qt::EditRole ); + model->setData( idx, model->data( upIdx, Qt::EditRole ) ); + model->setData( upIdx, v ); + view->setCurrentIndex( upIdx ); + } +} + +void SettingsResources::on_btnFolderAdd_clicked() +{ + QFileDialog dialog( this ); + dialog.setFileMode( QFileDialog::Directory ); + + QString path; + if ( dialog.exec() ) + path = dialog.selectedFiles().at( 0 ); + + folders->insertRow( 0 ); + folders->setData( folders->index( 0, 0 ), path ); + ui->foldersList->setCurrentIndex( folders->index( 0, 0 ) ); + modifyPane(); +} + +void SettingsResources::on_btnFolderRemove_clicked() +{ + ui->foldersList->model()->removeRow( ui->foldersList->currentIndex().row() ); + modifyPane(); +} + +void SettingsResources::on_btnFolderDown_clicked() +{ + moveIdxDown( ui->foldersList ); + modifyPane(); +} + +void SettingsResources::on_btnFolderUp_clicked() +{ + moveIdxUp( ui->foldersList ); + modifyPane(); +} + +void SettingsResources::on_btnFolderAutoDetect_clicked() +{ + // List to hold all games paths that were detected + QStringList list; + + QString beth = "HKEY_LOCAL_MACHINE\\SOFTWARE\\Bethesda Softworks\\%1"; + + // Fallout 4 + { + regFolderPath( list, beth.arg( "Fallout4" ), + "Installed Path", + "Data", + { "/Textures" }, + { ".ba2" } + ); + } + + // Skyrim, Fallout, Oblivion + { + regFolderPaths( list, + { beth.arg( "Skyrim Special Edition" ), beth.arg( "Skyrim" ), beth.arg( "FalloutNV" ), beth.arg( "Fallout3" ), beth.arg( "Oblivion" ) }, + "Installed Path", + "Data", + {}, /* No subdirs */ + { ".bsa" } + ); + } + + // Morrowind + { + regFolderPath( list, + "HKEY_LOCAL_MACHINE\\SOFTWARE\\Bethesda Softworks\\Morrowind", + "Installed Path", + "Data", + { "/Textures" }, + { ".bsa" } + ); + } + + // CIV IV + { + regFolderPath( list, + "HKEY_LOCAL_MACHINE\\SOFTWARE\\Firaxis Games\\Sid Meier's Civilization 4", + "INSTALLDIR", + "Assets/Art/shared" + ); + } + + // Freedom Force + { + QString ff = "HKEY_LOCAL_MACHINE\\SOFTWARE\\Irrational Games\\%1"; + QStringList ffSubDirs{ "./textures", "./skins/standard" }; + + regFolderPaths( list, + { ff.arg( "FFVTTR" ), ff.arg( "Freedom Force" ) }, + "InstallDir", + "Data/Art/library/area_specific/_textures", + ffSubDirs + ); + } + + QStringList archivesNew = folders->stringList() + list; + archivesNew.removeDuplicates(); + + folders->setStringList( archivesNew ); + ui->foldersList->setCurrentIndex( folders->index( 0, 0, QModelIndex() ) ); + + modifyPane(); +} + + +void SettingsResources::on_btnArchiveAdd_clicked() +{ + QStringList files = QFileDialog::getOpenFileNames( + this, + "Select one or more archives", + "", + "Archive (*.bsa *.ba2)" + ); + + for ( int i = 0; i < files.count(); i++ ) { + archives->insertRow( i ); + archives->setData( archives->index( i, 0 ), files.at( i ) ); + } + + ui->archivesList->setCurrentIndex( archives->index( 0, 0 ) ); + modifyPane(); +} + +void SettingsResources::on_btnArchiveRemove_clicked() +{ + ui->archivesList->model()->removeRow( ui->archivesList->currentIndex().row() ); + modifyPane(); +} + +void SettingsResources::on_btnArchiveDown_clicked() +{ + moveIdxDown( ui->archivesList ); + modifyPane(); +} + +void SettingsResources::on_btnArchiveUp_clicked() +{ + moveIdxUp( ui->archivesList ); + modifyPane(); +} + +void SettingsResources::on_btnArchiveAutoDetect_clicked() +{ + QStringList archivesNew = archives->stringList(); + + QStringList autoList = FSManager::autodetectArchives( "textures" ); + // Fallout 4 Materials + autoList += FSManager::autodetectArchives( "materials" ); + for ( const QString & archive : autoList ) { + if ( !archivesNew.contains( archive, Qt::CaseInsensitive ) ) { + archivesNew.append( archive ); + } + } + + archivesNew.removeDuplicates(); + + archives->setStringList( archivesNew ); + + ui->archivesList->setCurrentIndex( archives->index( 0, 0 ) ); + modifyPane(); +} diff --git a/src/settings.h b/src/settings.h new file mode 100644 index 000000000..aaeb0a19d --- /dev/null +++ b/src/settings.h @@ -0,0 +1,120 @@ +#ifndef SETTINGSPANE_H +#define SETTINGSPANE_H + +#include "ui/settingsdialog.h" +#include "nifskope.h" + +#include +#include + + +#define NifSkopeDisplayRole (Qt::UserRole + 42) + +class FSManager; + +class QListWidgetItem; +class QStringListModel; + +namespace Ui { +class SettingsGeneral; +class SettingsRender; +class SettingsResources; +} + +class SettingsPane : public QWidget +{ + Q_OBJECT + +public: + explicit SettingsPane( QWidget * parent = nullptr ); + virtual ~SettingsPane(); + + virtual void read() = 0; + virtual void write() = 0; + virtual void setDefault() = 0; + +public slots: + virtual void modifyPane(); + +signals: + void paneModified(); + +protected: + void readPane( QWidget * w, QSettings & settings ); + void writePane( QWidget * w, QSettings & settings ); + bool isModified(); + void setModified( bool ); + + SettingsDialog * dlg; + + bool modified = false; +}; + +class SettingsGeneral : public SettingsPane +{ + Q_OBJECT + +public: + explicit SettingsGeneral( QWidget * parent = nullptr ); + ~SettingsGeneral(); + + void read() override final; + void write() override final; + void setDefault() override final; + +private: + std::unique_ptr ui; +}; + +class SettingsRender : public SettingsPane +{ + Q_OBJECT + +public: + explicit SettingsRender( QWidget * parent = nullptr ); + ~SettingsRender(); + + void read() override final; + void write() override final; + void setDefault() override final; + +private: + std::unique_ptr ui; +}; + +class SettingsResources : public SettingsPane +{ + Q_OBJECT + +public: + explicit SettingsResources( QWidget * parent = nullptr ); + ~SettingsResources(); + + void read() override final; + void write() override final; + void setDefault() override final; + +public slots: + void on_btnFolderAdd_clicked(); + void on_btnFolderRemove_clicked(); + void on_btnFolderDown_clicked(); + void on_btnFolderUp_clicked(); + void on_btnFolderAutoDetect_clicked(); + + void on_btnArchiveAdd_clicked(); + void on_btnArchiveRemove_clicked(); + void on_btnArchiveDown_clicked(); + void on_btnArchiveUp_clicked(); + void on_btnArchiveAutoDetect_clicked(); + +private: + std::unique_ptr ui; + + FSManager * archiveMgr; + + QStringListModel * folders; + QStringListModel * archives; +}; + + +#endif // SETTINGSPANE_H diff --git a/src/spellbook.cpp b/src/spellbook.cpp index 1b6a308a4..85617cded 100644 --- a/src/spellbook.cpp +++ b/src/spellbook.cpp @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -32,41 +32,44 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "spellbook.h" +#include "ui/checkablemessagebox.h" + #include #include +#include + //! \file spellbook.cpp SpellBook implementation -QList & SpellBook::spells() +QList & SpellBook::spells() { - // construct-on-first-use wrapper - static QList * _spells = new QList(); - return *_spells; + static QList _spells = QList(); + return _spells; } QList & SpellBook::books() { - static QList * _books = new QList(); - return *_books; + static QList _books = QList(); + return _books; } -QMultiHash & SpellBook::hash() +QMultiHash & SpellBook::hash() { - static QMultiHash * _hash = new QMultiHash(); - return *_hash; + static QMultiHash _hash = QMultiHash(); + return _hash; } -QList & SpellBook::instants() +QList & SpellBook::instants() { - static QList * _instants = new QList(); - return *_instants; + static QList _instants = QList(); + return _instants; } -QList & SpellBook::sanitizers() +QList & SpellBook::sanitizers() { - static QList * _sanitizers = new QList(); - return *_sanitizers; + static QList _sanitizers = QList(); + return _sanitizers; } SpellBook::SpellBook( NifModel * nif, const QModelIndex & index, QObject * receiver, const char * member ) : QMenu(), Nif( 0 ) @@ -80,7 +83,7 @@ SpellBook::SpellBook( NifModel * nif, const QModelIndex & index, QObject * recei sltNif( nif ); // fill in the known spells - for ( Spell * spell : spells() ) { + for ( SpellPtr spell : spells() ) { newSpellRegistered( spell ); } @@ -98,15 +101,46 @@ SpellBook::~SpellBook() books().removeAll( this ); } -void SpellBook::cast( NifModel * nif, const QModelIndex & index, Spell * spell ) +void SpellBook::cast( NifModel * nif, const QModelIndex & index, SpellPtr spell ) { - if ( spell && spell->isApplicable( nif, index ) ) - emit sigIndex( spell->cast( nif, index ) ); + QSettings cfg; + + bool suppressConfirm = cfg.value( "Settings/Suppress Undoable Confirmation", false ).toBool(); + bool accepted = false; + + QDialogButtonBox::StandardButton response = QDialogButtonBox::Yes; + + if ( !suppressConfirm ) { + response = CheckableMessageBox::question( this, "Confirmation", "This action cannot currently be undone. Do you want to continue?", "Do not ask me again", &accepted ); + + if ( accepted ) + cfg.setValue( "Settings/Suppress Undoable Confirmation", true ); + } + + if ( (response == QDialogButtonBox::Yes) && spell && spell->isApplicable( nif, index ) ) { + bool noSignals = spell->batch(); + if ( noSignals ) + nif->setState( BaseModel::Processing ); + // Cast the spell and return index + auto idx = spell->cast( nif, index ); + if ( noSignals ) + nif->resetState(); + + // Refresh the header + nif->invalidateConditions( nif->getHeader(), true ); + nif->updateHeader(); + + if ( noSignals && nif->getProcessingResult() ) { + emit nif->dataChanged( idx, idx ); + } + + emit sigIndex( idx ); + } } void SpellBook::sltSpellTriggered( QAction * action ) { - Spell * spell = Map.value( action ); + SpellPtr spell = Map.value( action ); cast( Nif, Index, spell ); } @@ -146,7 +180,7 @@ void SpellBook::checkActions( QMenu * menu, const QString & page ) menuEnable |= action->menu()->isEnabled(); action->setVisible( action->menu()->isEnabled() ); } else { - for ( Spell * spell : spells() ) { + for ( SpellPtr spell : spells() ) { if ( action->text() == spell->name() && page == spell->page() ) { bool actionEnable = Nif && spell->isApplicable( Nif, Index ); action->setVisible( actionEnable ); @@ -159,7 +193,7 @@ void SpellBook::checkActions( QMenu * menu, const QString & page ) menu->setEnabled( menuEnable ); } -void SpellBook::newSpellRegistered( Spell * spell ) +void SpellBook::newSpellRegistered( SpellPtr spell ) { if ( spell->page().isEmpty() ) { Map.insert( addAction( spell->icon(), spell->name() ), spell ); @@ -173,7 +207,7 @@ void SpellBook::newSpellRegistered( Spell * spell ) } if ( !menu ) { - menu = new QMenu( spell->page() ); + menu = new QMenu( spell->page(), this ); addMenu( menu ); } @@ -183,7 +217,7 @@ void SpellBook::newSpellRegistered( Spell * spell ) } } -void SpellBook::registerSpell( Spell * spell ) +void SpellBook::registerSpell( SpellPtr spell ) { spells().append( spell ); hash().insertMulti( spell->name(), spell ); @@ -199,10 +233,10 @@ void SpellBook::registerSpell( Spell * spell ) } } -Spell * SpellBook::lookup( const QString & id ) +SpellPtr SpellBook::lookup( const QString & id ) { if ( id.isEmpty() ) - return 0; + return nullptr; QString page; QString name = id; @@ -213,41 +247,41 @@ Spell * SpellBook::lookup( const QString & id ) name = split.value( 1 ); } - for ( Spell * spell : hash().values( name ) ) { + for ( SpellPtr spell : hash().values( name ) ) { if ( spell->page() == page ) return spell; } - return 0; + return nullptr; } -Spell * SpellBook::lookup( const QKeySequence & hotkey ) +SpellPtr SpellBook::lookup( const QKeySequence & hotkey ) { if ( hotkey.isEmpty() ) - return 0; + return nullptr; - for ( Spell * spell : spells() ) { + for ( SpellPtr spell : spells() ) { if ( spell->hotkey() == hotkey ) return spell; } - return 0; + return nullptr; } -Spell * SpellBook::instant( const NifModel * nif, const QModelIndex & index ) +SpellPtr SpellBook::instant( const NifModel * nif, const QModelIndex & index ) { - for ( Spell * spell : instants() ) { + for ( SpellPtr spell : instants() ) { if ( spell->isApplicable( nif, index ) ) return spell; } - return 0; + return nullptr; } QModelIndex SpellBook::sanitize( NifModel * nif ) { QPersistentModelIndex ridx; - for ( Spell * spell : sanitizers() ) { + for ( SpellPtr spell : sanitizers() ) { if ( spell->isApplicable( nif, QModelIndex() ) ) { QModelIndex idx = spell->cast( nif, QModelIndex() ); @@ -264,5 +298,5 @@ QAction * SpellBook::exec( const QPoint & pos, QAction * act ) if ( isEnabled() ) return QMenu::exec( pos, act ); - return 0; + return nullptr; } diff --git a/src/spellbook.h b/src/spellbook.h index c04f9bc28..9a3fa9588 100644 --- a/src/spellbook.h +++ b/src/spellbook.h @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -33,6 +33,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef SPELLBOOK_H #define SPELLBOOK_H +#include "message.h" #include "nifmodel.h" #include // Inherited @@ -45,6 +46,10 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +#include + + +using QIconPtr = std::shared_ptr; //! \file spellbook.h Spell, SpellBook and Librarian @@ -72,6 +77,8 @@ class Spell virtual bool instant() const { return false; } //! Whether the spell performs a sanitizing function virtual bool sanity() const { return false; } + //! Whether the spell has a high processing cost + virtual bool batch() const { return (page() == "Batch") || (page() == "Block") || (page() == "Mesh"); } //! Hotkey sequence virtual QKeySequence hotkey() const { return QKeySequence(); } @@ -99,6 +106,8 @@ class Spell static inline QString tr( const char * key, const char * comment = 0 ) { return QCoreApplication::translate( "Spell", key, comment ); } }; +using SpellPtr = std::shared_ptr; + //! Spell menu class SpellBook final : public QMenu { @@ -114,14 +123,14 @@ class SpellBook final : public QMenu QAction * exec( const QPoint & pos, QAction * act = 0 ); //! Register spell with appropriate books - static void registerSpell( Spell * spell ); + static void registerSpell( SpellPtr spell ); //! Locate spell by name - static Spell * lookup( const QString & id ); + static SpellPtr lookup( const QString & id ); //! Locate spell by hotkey - static Spell * lookup( const QKeySequence & hotkey ); + static SpellPtr lookup( const QKeySequence & hotkey ); //! Locate instant spells by datatype - static Spell * instant( const NifModel * nif, const QModelIndex & index ); + static SpellPtr instant( const NifModel * nif, const QModelIndex & index ); //! Cast all sanitizing spells static QModelIndex sanitize( NifModel * nif ); @@ -131,7 +140,7 @@ public slots: void sltIndex( const QModelIndex & index ); - void cast( NifModel * nif, const QModelIndex & index, Spell * spell ); + void cast( NifModel * nif, const QModelIndex & index, SpellPtr spell ); void checkActions(); @@ -144,17 +153,17 @@ protected slots: protected: NifModel * Nif; QPersistentModelIndex Index; - QMap Map; + QMap Map; - void newSpellRegistered( Spell * spell ); + void newSpellRegistered( SpellPtr spell ); void checkActions( QMenu * menu, const QString & page ); private: - static QList & spells(); + static QList & spells(); static QList & books(); - static QMultiHash & hash(); - static QList & instants(); - static QList & sanitizers(); + static QMultiHash & hash(); + static QList & instants(); + static QList & sanitizers(); }; //! SpellBook manager @@ -169,7 +178,7 @@ class Librarian final */ Librarian( Spell * spell ) { - SpellBook::registerSpell( spell ); + SpellBook::registerSpell( SpellPtr( spell ) ); } }; diff --git a/src/spells/animation.cpp b/src/spells/animation.cpp index 5ad53ef1b..706016fdb 100644 --- a/src/spells/animation.cpp +++ b/src/spells/animation.cpp @@ -1,5 +1,5 @@ #include "spellbook.h" -#include "options.h" +#include "settings.h" #include @@ -125,7 +125,7 @@ class spAttachKf final : public Spell QPersistentModelIndex iObjPalette = nif->getBlock( nif->getLink( iCtrlManager, "Object Palette" ), "NiDefaultAVObjectPalette" ); if ( !iObjPalette.isValid() ) { - iObjPalette = nif->insertNiBlock( "NiDefaultAVObjectPalette", nif->getBlockNumber( iCtrlManager ) + 1, true ); + iObjPalette = nif->insertNiBlock( "NiDefaultAVObjectPalette", nif->getBlockNumber( iCtrlManager ) + 1 ); nif->setLink( iCtrlManager, "Object Palette", nif->getBlockNumber( iObjPalette ) ); } @@ -162,9 +162,8 @@ class spAttachKf final : public Spell nif->holdUpdates( false ); if ( !missingNodes.isEmpty() ) { - qWarning() << Spell::tr( "The following controlled nodes were not found in the nif:" ); for ( const QString& nn : missingNodes ) { - qWarning() << nn; + Message::append( Spell::tr( "Errors occurred while attaching .KF" ), nn ); } } @@ -172,7 +171,7 @@ class spAttachKf final : public Spell } catch ( QString e ) { - qWarning( e.toLatin1().constData() ); + Message::append( Spell::tr( "Errors occurred while attaching .KF" ), e ); } } return index; @@ -230,7 +229,7 @@ class spAttachKf final : public Spell static QModelIndex attachController( NifModel * nif, const QPersistentModelIndex & iNode, const QString & ctrltype, bool fast = false ) { - QModelIndex iCtrl = nif->insertNiBlock( ctrltype, nif->getBlockNumber( iNode ) + 1, fast ); + QModelIndex iCtrl = nif->insertNiBlock( ctrltype, nif->getBlockNumber( iNode ) + 1 ); if ( !iCtrl.isValid() ) return QModelIndex(); diff --git a/src/spells/blocks.cpp b/src/spells/blocks.cpp index dfb6559c5..b6015dbed 100644 --- a/src/spells/blocks.cpp +++ b/src/spells/blocks.cpp @@ -166,17 +166,20 @@ static void populateBlocks( QList & blocks, NifModel * nif, qint32 block //! Remove the children from the specified block static void removeChildren( NifModel * nif, const QPersistentModelIndex & iBlock ) { - QList iChildren; + // Build list of child links + QVector iChildren; for ( const auto link : nif->getChildLinks( nif->getBlockNumber( iBlock ) ) ) { iChildren.append( nif->getBlock( link ) ); } + // Remove children of child links for ( const QPersistentModelIndex& iChild : iChildren ) { if ( iChild.isValid() && nif->getBlockNumber( iBlock ) == nif->getParent( nif->getBlockNumber( iChild ) ) ) { removeChildren( nif, iChild ); } } + // Remove children for ( const QPersistentModelIndex& iChild : iChildren ) { if ( iChild.isValid() && nif->getBlockNumber( iBlock ) == nif->getParent( nif->getBlockNumber( iChild ) ) ) { nif->removeNiBlock( nif->getBlockNumber( iChild ) ); @@ -293,7 +296,7 @@ class spAttachProperty final : public Spell if ( !addLink( nif, iParent, "Properties", nif->getBlockNumber( iProperty ) ) ) { // try Skyrim if ( !addLink( nif, iParent, "BS Properties", nif->getBlockNumber( iProperty ) ) ) { - qWarning() << "failed to attach property block; perhaps the array is full?"; + qCWarning( nsSpell ) << Spell::tr( "failed to attach property block; perhaps the array is full?" ); } } @@ -466,6 +469,7 @@ class spCopyBlock final : public Spell public: QString name() const override final { return Spell::tr( "Copy" ); } QString page() const override final { return Spell::tr( "Block" ); } + QKeySequence hotkey() const { return{ Qt::CTRL + Qt::SHIFT + Qt::Key_C }; } bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { @@ -477,7 +481,7 @@ class spCopyBlock final : public Spell QByteArray data; QBuffer buffer( &data ); - if ( buffer.open( QIODevice::WriteOnly ) && nif->save( buffer, index ) ) { + if ( buffer.open( QIODevice::WriteOnly ) && nif->saveIndex( buffer, index ) ) { QMimeData * mime = new QMimeData; mime->setData( QString( "nifskope/niblock/%1/%2" ).arg( nif->itemName( index ), nif->getVersion() ), data ); QApplication::clipboard()->setMimeData( mime ); @@ -541,7 +545,7 @@ class spPasteBlock final : public Spell if ( buffer.open( QIODevice::ReadOnly ) ) { QModelIndex block = nif->insertNiBlock( blockType( form ), nif->getBlockCount() ); - nif->load( buffer, block ); + nif->loadIndex( buffer, block ); blockLink( nif, index, block ); return block; } @@ -561,6 +565,7 @@ class spPasteOverBlock final : public Spell public: QString name() const override final { return Spell::tr( "Paste Over" ); } QString page() const override final { return Spell::tr( "Block" ); } + QKeySequence hotkey() const { return{ Qt::CTRL + Qt::SHIFT + Qt::Key_V }; } QString acceptFormat( const QString & format, const NifModel * nif, const QModelIndex & block ) { @@ -599,7 +604,7 @@ class spPasteOverBlock final : public Spell QBuffer buffer( &data ); if ( buffer.open( QIODevice::ReadOnly ) ) { - nif->load( buffer, index ); + nif->loadIndex( buffer, index ); return index; } } @@ -653,7 +658,7 @@ class spCopyBranch final : public Spell } } - QMessageBox::information( 0, Spell::tr( "Copy Branch" ), Spell::tr( "failed to map parent link %1 %2 for block %3 %4; %5." ) + Message::critical( nullptr, Spell::tr( "%1 failed with errors." ).arg( name() ), Spell::tr( "failed to map parent link %1 %2 for block %3 %4; %5." ) .arg( link ) .arg( nif->itemName( nif->getBlock( link ) ) ) .arg( block ) @@ -676,8 +681,8 @@ class spCopyBranch final : public Spell for ( const auto block : blocks ) { ds << nif->itemName( nif->getBlock( block ) ); - if ( !nif->save( buffer, nif->getBlock( block ) ) ) { - QMessageBox::information( 0, Spell::tr( "Copy Branch" ), Spell::tr( "failed to save block %1 %2." ) + if ( !nif->saveIndex( buffer, nif->getBlock( block ) ) ) { + Message::critical( nullptr, Spell::tr( "%1 failed with errors." ).arg( name() ), Spell::tr( "failed to save block %1 %2." ) .arg( block ) .arg( nif->itemName( nif->getBlock( block ) ) ) ); @@ -780,7 +785,7 @@ class spPasteBranch final : public Spell if ( block >= 0 ) { blockMap.insert( ipm.key(), block ); } else { - QMessageBox::information( 0, Spell::tr( "Paste Branch" ), Spell::tr( "failed to map parent link %1" ) + Message::critical( nullptr, Spell::tr( "%1 failed with errors." ).arg( name() ), Spell::tr( "failed to map parent link %1" ) .arg( ipm.value() ) ); return index; @@ -1059,7 +1064,7 @@ class spCropToBranch final : public Spell { // construct list of block numbers of all blocks in this branch of index QList branch = getBranch( nif, nif->getBlockNumber( index ) ); - //qWarning() << branch; // DEBUG + //qDebug() << branch; // remove non-branch blocks int n = 0; // tracks the current block number in the new system (after some blocks have been removed already) int m = 0; // tracks the block number in the old system i.e. as they are numbered in the branch list @@ -1149,6 +1154,7 @@ class spDuplicateBlock final : public Spell public: QString name() const override final { return Spell::tr( "Duplicate" ); } QString page() const override final { return Spell::tr( "Block" ); } + QKeySequence hotkey() const { return{ Qt::CTRL + Qt::SHIFT + Qt::Key_D }; } bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { @@ -1162,11 +1168,11 @@ class spDuplicateBlock final : public Spell QBuffer buffer( &data ); // Opening in ReadWrite doesn't work - race condition? - if ( buffer.open( QIODevice::WriteOnly ) && nif->save( buffer, index ) ) { + if ( buffer.open( QIODevice::WriteOnly ) && nif->saveIndex( buffer, index ) ) { // from spPasteBlock if ( buffer.open( QIODevice::ReadOnly ) ) { QModelIndex block = nif->insertNiBlock( nif->getBlockName( index ), nif->getBlockCount() ); - nif->load( buffer, block ); + nif->loadIndex( buffer, block ); blockLink( nif, nif->getBlock( nif->getParent( nif->getBlockNumber( index ) ) ), block ); return block; } @@ -1184,6 +1190,7 @@ class spDuplicateBranch final : public Spell public: QString name() const override final { return Spell::tr( "Duplicate Branch" ); } QString page() const override final { return Spell::tr( "Block" ); } + QKeySequence hotkey() const { return{ Qt::CTRL + Qt::Key_D }; } bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { @@ -1219,7 +1226,7 @@ class spDuplicateBranch final : public Spell } } - QMessageBox::information( 0, Spell::tr( "Duplicate Branch" ), Spell::tr( "failed to map parent link %1 %2 for block %3 %4; %5." ) + Message::critical( nullptr, Spell::tr( "%1 failed with errors." ).arg( name() ), Spell::tr( "failed to map parent link %1 %2 for block %3 %4; %5." ) .arg( link ) .arg( nif->itemName( nif->getBlock( link ) ) ) .arg( block ) @@ -1242,8 +1249,8 @@ class spDuplicateBranch final : public Spell for ( const auto block : blocks ) { ds << nif->itemName( nif->getBlock( block ) ); - if ( !nif->save( buffer, nif->getBlock( block ) ) ) { - QMessageBox::information( 0, Spell::tr( "Duplicate Branch" ), Spell::tr( "failed to save block %1 %2." ) + if ( !nif->saveIndex( buffer, nif->getBlock( block ) ) ) { + Message::critical( nullptr, Spell::tr( "%1 failed with errors." ).arg( name() ), Spell::tr( "failed to save block %1 %2." ) .arg( block ) .arg( nif->itemName( nif->getBlock( block ) ) ) ); @@ -1280,7 +1287,7 @@ class spDuplicateBranch final : public Spell if ( block >= 0 ) { blockMap.insert( ipm.key(), block ); } else { - QMessageBox::information( 0, Spell::tr( "Duplicate Branch" ), Spell::tr( "failed to map parent link %1" ) + Message::critical( nullptr, Spell::tr( "%1 failed with errors." ).arg( name() ), Spell::tr( "failed to map parent link %1" ) .arg( ipm.value() ) ); return index; diff --git a/src/spells/color.cpp b/src/spells/color.cpp index da918378c..c77d32fc5 100644 --- a/src/spells/color.cpp +++ b/src/spells/color.cpp @@ -26,10 +26,16 @@ class spChooseColor final : public Spell QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final { - if ( nif->getValue( index ).type() == NifValue::tColor3 ) + auto typ = nif->getValue( index ).type(); + if ( typ == NifValue::tColor3 ) { nif->set( index, ColorWheel::choose( nif->get( index ) ) ); - else if ( nif->getValue( index ).type() == NifValue::tColor4 ) + } else if ( typ == NifValue::tColor4 ) { nif->set( index, ColorWheel::choose( nif->get( index ) ) ); + } else if ( typ == NifValue::tByteColor4 ) { + auto col = ColorWheel::choose( nif->get( index ) ); + nif->set( index, *static_cast(&col) ); + } + return index; } @@ -37,3 +43,32 @@ class spChooseColor final : public Spell REGISTER_SPELL( spChooseColor ) +//! Set an array of Colors +class spSetAllColor final : public Spell +{ +public: + QString name() const override final { return Spell::tr( "Set All" ); } + QString page() const override final { return Spell::tr( "Color" ); } + QIcon icon() const { return ColorWheel::getIcon(); } + bool instant() const { return true; } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final + { + return nif->isArray( index ) && nif->getValue( index.child( 0, 0 ) ).isColor(); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final + { + QModelIndex colorIdx = (nif->isArray( index )) ? index.child( 0, 0 ) : index; + + auto typ = nif->getValue( colorIdx ).type(); + if ( typ == NifValue::tColor3 ) + nif->setArray( index, ColorWheel::choose( nif->get( colorIdx ) ) ); + else if ( typ == NifValue::tColor4 ) + nif->setArray( index, ColorWheel::choose( nif->get( colorIdx ) ) ); + + return index; + } +}; + +REGISTER_SPELL( spSetAllColor ) diff --git a/src/spells/flags.cpp b/src/spells/flags.cpp index 58d0721c5..c4950d9b8 100644 --- a/src/spells/flags.cpp +++ b/src/spells/flags.cpp @@ -39,6 +39,7 @@ class spEditFlags final : public Spell VertexColor, ZBuffer, BSX, + NiAVObject, None }; @@ -86,11 +87,11 @@ class spEditFlags final : public Spell } return Controller; - } else if ( name == "NiNode" ) { + } else if ( name == "NiNode" && nif->getVersionNumber() != 0x14020007 ) { return Node; } else if ( name == "bhkRigidBody" || name == "bhkRigidBodyT" ) { return RigidBody; - } else if ( name == "NiTriShape" || name == "NiTriStrips" ) { + } else if ( (name == "NiTriShape" || name == "NiTriStrips") && nif->getVersionNumber() != 0x14020007 ) { return Shape; } else if ( name == "NiStencilProperty" ) { return Stencil; @@ -102,6 +103,8 @@ class spEditFlags final : public Spell return ZBuffer; } else if ( name == "BSXFlags" ) { return BSX; + } else if ( nif->inherits( index.parent(), "NiAVObject" ) && nif->getVersionNumber() == 0x14020007 ) { + return NiAVObject; } } @@ -154,6 +157,9 @@ class spEditFlags final : public Spell case BSX: bsxFlags( nif, iFlags ); break; + case NiAVObject: + niavFlags( nif, iFlags ); + break; default: break; } @@ -460,12 +466,20 @@ class spEditFlags final : public Spell QVBoxLayout * vbox = new QVBoxLayout( &dlg ); QStringList flagNames{ - Spell::tr( "Enable Havok" ), // 1 - Spell::tr( "Enable Collision" ), // 2 - Spell::tr( "Is Skeleton Nif (?)" ), // 4 - Spell::tr( "Enable Animation" ), // 8 - Spell::tr( "FlameNodes Present" ), // 16 - Spell::tr( "EditorMarkers Present" ) // 32 + Spell::tr( "Animated" ), // 1 + Spell::tr( "Havok" ), // 2 + Spell::tr( "Ragdoll" ), // 4 + Spell::tr( "Complex" ), // 8 + Spell::tr( "Addon" ), // 16 + Spell::tr( "Editor Marker" ), // 32 + Spell::tr( "Dynamic" ), // 64 + Spell::tr( "Articulated" ), // 128 + Spell::tr( "Needs Transform Updates" ), // 256 + Spell::tr( "External Emit" ), // 512 + //Spell::tr( "Magic Shader Particles" ), // 1024 + //Spell::tr( "Lights" ), // 2048 + //Spell::tr( "Breakable" ), // 4096 + //Spell::tr( "Searched Breakable (Runtime Only?)" ) // 8192 }; QList chkBoxes; @@ -488,6 +502,71 @@ class spEditFlags final : public Spell } } + //! Set NiAVObject flags + void niavFlags( NifModel * nif, const QModelIndex & index ) + { + quint32 flags = nif->get( index ); + + QDialog dlg; + QVBoxLayout * vbox = new QVBoxLayout( &dlg ); + + QStringList flagNames{ + Spell::tr( "Hidden" ), // 1 + Spell::tr( "Selective Update" ), // 2 + Spell::tr( "Selective Update Transforms" ), // 4 + Spell::tr( "Selective Update Controller" ), // 8 + Spell::tr( "Selective Update Rigid" ), // 16 + Spell::tr( "Display (UI?) Object" ), // 32 + Spell::tr( "Disable Sorting" ), // 64 + Spell::tr( "Sel. Upd. Transforms Override" ), // 128 + Spell::tr( "" ), // 256 + Spell::tr( "Save External Geom Data" ), // 512 + Spell::tr( "No Decals" ), // 1024 + Spell::tr( "Always Draw" ), // 2048 + Spell::tr( "Mesh LOD" ), // 4096 + Spell::tr( "Fixed Bound" ), // 8192 + Spell::tr( "Top Fade Node" ), // 16384 + Spell::tr( "Ignore Fade" ), // 32768 + Spell::tr( "No Anim Sync (X)" ), // 65536 + Spell::tr( "No Anim Sync (Y)" ), // 1 << 17 + Spell::tr( "No Anim Sync (Z)" ), // 1 << 18 + Spell::tr( "No Anim Sync (S)" ), // 1 << 19 + Spell::tr( "No Dismember" ), // 1 << 20 + Spell::tr( "No Dismember Validity" ), // 1 << 21 + Spell::tr( "Render Use" ), // 1 << 22 + Spell::tr( "Materials Applied" ), // 1 << 23 + Spell::tr( "High Detail" ), // 1 << 24 + Spell::tr( "Force Update" ), // 1 << 25 + Spell::tr( "Pre-Processed Node" ), // 1 << 26 + Spell::tr( "Bit 27" ), // 1 << 27 + Spell::tr( "Bit 28" ), // 1 << 28 + Spell::tr( "Bit 29" ), // 1 << 29 + Spell::tr( "Bit 30" ), // 1 << 30 + Spell::tr( "Bit 31" ), // 1 << 31 + + }; + + QList chkBoxes; + int x = 0; + for ( const QString& flagName : flagNames ) { + chkBoxes << dlgCheck( vbox, QString( "%1: %2 (%3)" ).arg( Spell::tr( "Bit %1" ).arg( x ) ) + .arg( flagName ).arg( (uint)(1 << x) ) ); + chkBoxes.last()->setChecked( flags & (1 << x) ); + x++; + } + + dlgButtons( &dlg, vbox ); + + if ( dlg.exec() == QDialog::Accepted ) { + x = 0; + for ( QCheckBox * chk : chkBoxes ) { + flags = (flags & (~(1 << x))) | (chk->isChecked() ? 1 << x : 0); + x++; + } + nif->set( index, flags ); + } + } + //! Set flags on a BillboardNode void billboardFlags( NifModel * nif, const QModelIndex & index ) { diff --git a/src/spells/havok.cpp b/src/spells/havok.cpp index ffbb4c229..ccb64f6d2 100644 --- a/src/spells/havok.cpp +++ b/src/spells/havok.cpp @@ -48,11 +48,7 @@ class spCreateCVS final : public Spell if ( !iData.isValid() ) return index; - float havokScale = 1.0f; - - if ( nif->getUserVersion() >= 12 ) { - havokScale = 10.0f; - } + float havokScale = (nif->checkVersion( 0x14020007, 0x14020007 ) && nif->getUserVersion() >= 12) ? 10.0f : 1.0f; havokScale *= havokConst; @@ -61,6 +57,13 @@ class spCreateCVS final : public Spell /* get the verts of our mesh */ QVector verts = nif->getArray( iData, "Vertices" ); + QVector vertsTrans; + + // Offset by translation of NiTriShape + Vector3 trans = nif->get( index, "Translation" ); + for ( auto v : verts ) { + vertsTrans.append( v + trans ); + } // to store results QVector hullVerts, hullNorms; @@ -80,6 +83,15 @@ class spCreateCVS final : public Spell precSpin->setValue( 0.25 ); vbox->addWidget( precSpin ); + vbox->addWidget( new QLabel( Spell::tr( "Collision Radius" ) ) ); + + QDoubleSpinBox * spnRadius = new QDoubleSpinBox; + spnRadius->setRange( 0, 0.5 ); + spnRadius->setDecimals( 4 ); + spnRadius->setSingleStep( 0.001 ); + spnRadius->setValue( 0.05 ); + vbox->addWidget( spnRadius ); + QHBoxLayout * hbox = new QHBoxLayout; vbox->addLayout( hbox ); @@ -99,7 +111,7 @@ class spCreateCVS final : public Spell } /* make a convex hull from it */ - compute_convex_hull( verts, hullVerts, hullNorms, (float)precSpin->value() ); + compute_convex_hull( vertsTrans, hullVerts, hullNorms, (float)precSpin->value() ); // sort and remove duplicate vertices QList sortedVerts; @@ -150,7 +162,7 @@ class spCreateCVS final : public Spell // radius is always 0.1? // TODO: Figure out if radius is not arbitrarily set in vanilla NIFs - nif->set( iCVS, "Radius", 0.1f ); + nif->set( iCVS, "Radius", spnRadius->value() ); // for arrow detection: [0, 0, -0, 0, 0, -0] nif->set( nif->getIndex( iCVS, "Unknown 6 Floats" ).child( 2, 0 ), -0.0 ); @@ -189,7 +201,7 @@ class spCreateCVS final : public Spell nif->removeNiBlock( nif->getBlockNumber( shape ) ); } - QMessageBox::information( 0, "NifSkope", Spell::tr( "Created hull with %1 vertices, %2 normals" ).arg( convex_verts.count() ).arg( convex_norms.count() ) ); + Message::info( nullptr, Spell::tr( "Created hull with %1 vertices, %2 normals" ).arg( convex_verts.count() ).arg( convex_norms.count() ) ); // returning iCVS here can crash NifSkope if a child array is selected return index; @@ -207,12 +219,10 @@ class spConstraintHelper final : public Spell bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { - return nif && ( - nif->isNiBlock( nif->getBlock( index ), "bhkMalleableConstraint" ) - || nif->isNiBlock( nif->getBlock( index ), "bhkRagdollConstraint" ) - || nif->isNiBlock( nif->getBlock( index ), "bhkLimitedHingeConstraint" ) - || nif->isNiBlock( nif->getBlock( index ), "bhkHingeConstraint" ) - || nif->isNiBlock( nif->getBlock( index ), "bhkPrismaticConstraint" ) ); + return nif && + nif->isNiBlock( nif->getBlock( index ), + { "bhkMalleableConstraint", "bhkRagdollConstraint", "bhkLimitedHingeConstraint", "bhkHingeConstraint", "bhkPrismaticConstraint" } + ); } QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final @@ -234,7 +244,7 @@ class spConstraintHelper final : public Spell QModelIndex iBodyB = nif->getBlock( nif->getLink( nif->getIndex( iConstraint, "Entities" ).child( 1, 0 ) ), "bhkRigidBody" ); if ( !iBodyA.isValid() || !iBodyB.isValid() ) { - qWarning() << "coudn't find the bodies for this constraint"; + Message::warning( nullptr, Spell::tr( "Couldn't find the bodies for this constraint." ) ); return index; } @@ -243,53 +253,46 @@ class spConstraintHelper final : public Spell if ( name == "bhkLimitedHingeConstraint" ) { iConstraint = nif->getIndex( iConstraint, "Limited Hinge" ); - - if ( !iConstraint.isValid() ) - return index; - } - - if ( name == "bhkRagdollConstraint" ) { + } else if ( name == "bhkRagdollConstraint" ) { iConstraint = nif->getIndex( iConstraint, "Ragdoll" ); - - if ( !iConstraint.isValid() ) - return index; - } - - if ( name == "bhkHingeConstraint" ) { + } else if ( name == "bhkHingeConstraint" ) { iConstraint = nif->getIndex( iConstraint, "Hinge" ); - - if ( !iConstraint.isValid() ) - return index; } + if ( !iConstraint.isValid() ) + return index; + Vector3 pivot = Vector3( nif->get( iConstraint, "Pivot A" ) ) * havokConst; pivot = transA * pivot; pivot = transB.rotation.inverted() * ( pivot - transB.translation ) / transB.scale / havokConst; nif->set( iConstraint, "Pivot B", { pivot[0], pivot[1], pivot[2], 0 } ); + // TODO: bhkHingeConstraint + QString axleA, axleB, twistA, twistB; if ( name == "bhkLimitedHingeConstraint" ) { - Vector3 axle = Vector3( nif->get( iConstraint, "Axle A" ) ); - axle = transA.rotation * axle; - axle = transB.rotation.inverted() * axle; - nif->set( iConstraint, "Axle B", { axle[0], axle[1], axle[2], 0 } ); - - axle = Vector3( nif->get( iConstraint, "Perp2 Axle In A2" ) ); - axle = transA.rotation * axle; - axle = transB.rotation.inverted() * axle; - nif->set( iConstraint, "Perp2 Axle In B2", { axle[0], axle[1], axle[2], 0 } ); + axleA = "Axle A"; + axleB = "Axle B"; + twistA = "Perp2 Axle In A2"; + twistB = "Perp2 Axle In B2"; + } else if ( name == "bhkRagdollConstraint" ) { + axleA = "Plane A"; + axleB = "Plane B"; + twistA = "Twist A"; + twistB = "Twist B"; } - if ( name == "bhkRagdollConstraint" ) { - Vector3 axle = Vector3( nif->get( iConstraint, "Plane A" ) ); - axle = transA.rotation * axle; - axle = transB.rotation.inverted() * axle; - nif->set( iConstraint, "Plane B", { axle[0], axle[1], axle[2], 0 } ); + if ( axleA.isEmpty() || axleB.isEmpty() || twistA.isEmpty() || twistB.isEmpty() ) + return index; + + Vector3 axle = Vector3( nif->get( iConstraint, axleA ) ); + axle = transA.rotation * axle; + axle = transB.rotation.inverted() * axle; + nif->set( iConstraint, axleB, { axle[0], axle[1], axle[2], 0 } ); - axle = Vector3( nif->get( iConstraint, "Twist A" ) ); - axle = transA.rotation * axle; - axle = transB.rotation.inverted() * axle; - nif->set( iConstraint, "Twist B", { axle[0], axle[1], axle[2], 0 } ); - } + axle = Vector3( nif->get( iConstraint, twistA ) ); + axle = transA.rotation * axle; + axle = transB.rotation.inverted() * axle; + nif->set( iConstraint, twistB, { axle[0], axle[1], axle[2], 0 } ); return index; } @@ -338,7 +341,7 @@ class spStiffSpringHelper final : public Spell QModelIndex iBodyB = nif->getBlock( nif->getLink( nif->getIndex( iConstraint, "Entities" ).child( 1, 0 ) ), "bhkRigidBody" ); if ( !iBodyA.isValid() || !iBodyB.isValid() ) { - qWarning() << "coudn't find the bodies for this constraint"; + Message::warning( nullptr, Spell::tr( "Couldn't find the bodies for this constraint" ) ); return idx; } @@ -417,7 +420,7 @@ class spPackHavokStrips final : public Spell } if ( vertices.isEmpty() || triangles.isEmpty() ) { - qWarning() << Spell::tr( "no mesh data was found" ); + Message::warning( nullptr, Spell::tr( "No mesh data was found." ) ); return iShape; } diff --git a/src/spells/headerstring.cpp b/src/spells/headerstring.cpp index b68d17924..a6cd022b5 100644 --- a/src/spells/headerstring.cpp +++ b/src/spells/headerstring.cpp @@ -60,7 +60,7 @@ static char const * txt_xpm[] = { " " }; -static QIcon * txt_xpm_icon = nullptr; +static QIconPtr txt_xpm_icon = nullptr; //! Edit the index of a header string class spEditStringIndex final : public Spell @@ -71,7 +71,7 @@ class spEditStringIndex final : public Spell QIcon icon() const { if ( !txt_xpm_icon ) - txt_xpm_icon = new QIcon( QPixmap( txt_xpm ) ); + txt_xpm_icon = QIconPtr( new QIcon(QPixmap( txt_xpm )) ); return *txt_xpm_icon; } diff --git a/src/spells/light.cpp b/src/spells/light.cpp index d6829f658..9dc0b7a99 100644 --- a/src/spells/light.cpp +++ b/src/spells/light.cpp @@ -49,7 +49,7 @@ static char const * light42_xpm[] = { " .... " }; -QIcon * light42_xpm_icon = nullptr; +static QIconPtr light42_xpm_icon = nullptr; //! Edit the parameters of a light object class spLightEdit final : public Spell @@ -61,7 +61,7 @@ class spLightEdit final : public Spell QIcon icon() const { if ( !light42_xpm_icon ) - light42_xpm_icon = new QIcon( QPixmap( light42_xpm ) ); + light42_xpm_icon = QIconPtr( new QIcon(QPixmap( light42_xpm )) ); return *light42_xpm_icon; } diff --git a/src/spells/material.cpp b/src/spells/materialedit.cpp similarity index 98% rename from src/spells/material.cpp rename to src/spells/materialedit.cpp index 8e583fec5..ae874f386 100644 --- a/src/spells/material.cpp +++ b/src/spells/materialedit.cpp @@ -89,7 +89,7 @@ static char const * mat42_xpm[] = { " " }; -QIcon * mat42_xpm_icon = nullptr; +static QIconPtr mat42_xpm_icon = nullptr; //! Edit a material class spMaterialEdit final : public Spell @@ -101,7 +101,7 @@ class spMaterialEdit final : public Spell QIcon icon() const { if ( !mat42_xpm_icon ) - mat42_xpm_icon = new QIcon( QPixmap( mat42_xpm ) ); + mat42_xpm_icon = QIconPtr( new QIcon(QPixmap( mat42_xpm )) ); return *mat42_xpm_icon; } diff --git a/src/spells/mesh.cpp b/src/spells/mesh.cpp index 7dba14b6c..b1a45ac2c 100644 --- a/src/spells/mesh.cpp +++ b/src/spells/mesh.cpp @@ -1,4 +1,5 @@ #include "mesh.h" +#include "gl/gltools.h" #include #include @@ -21,7 +22,7 @@ static QModelIndex getShape( const NifModel * nif, const QModelIndex & index ) if ( nif->isNiBlock( iShape, "NiTriBasedGeomData" ) ) iShape = nif->getBlock( nif->getParent( nif->getBlockNumber( iShape ) ) ); - if ( nif->isNiBlock( iShape, "NiTriShape" ) || nif->isNiBlock( index, "BSLODTriShape" ) || nif->isNiBlock( index, "NiTriStrips" ) ) + if ( nif->isNiBlock( iShape, { "NiTriShape", "BSLODTriShape", "NiTriStrips" } ) ) if ( nif->getBlock( nif->getLink( iShape, "Data" ), "NiTriBasedGeomData" ).isValid() ) return iShape; @@ -39,7 +40,7 @@ static QModelIndex getTriShapeData( const NifModel * nif, const QModelIndex & in { QModelIndex iData = nif->getBlock( index ); - if ( nif->isNiBlock( index, "NiTriShape" ) || nif->isNiBlock( index, "BSLODTriShape" ) ) + if ( nif->isNiBlock( index, { "NiTriShape", "BSLODTriShape" } ) ) iData = nif->getBlock( nif->getLink( index, "Data" ) ); if ( nif->isNiBlock( iData, "NiTriShapeData" ) ) @@ -67,7 +68,7 @@ static void removeWasteVertices( NifModel * nif, const QModelIndex & iData, cons QVector verts = nif->getArray( iData, "Vertices" ); if ( !verts.count() ) { - throw QString( Spell::tr( "no vertices?" ) ); + throw QString( Spell::tr( "No vertices" ) ); } QVector norms = nif->getArray( iData, "Normals" ); @@ -79,7 +80,7 @@ static void removeWasteVertices( NifModel * nif, const QModelIndex & iData, cons texco << nif->getArray( iUVSets.child( r, 0 ) ); if ( texco.last().count() != verts.count() ) - throw QString( "uv array size differs" ); + throw QString( Spell::tr( "UV array size differs" ) ); } int numVerts = verts.count(); @@ -88,7 +89,7 @@ static void removeWasteVertices( NifModel * nif, const QModelIndex & iData, cons || ( norms.count() && norms.count() != numVerts ) || ( colors.count() && colors.count() != numVerts ) ) { - throw QString( "vertex array size differs" ); + throw QString( Spell::tr( "Vertex array size differs" ) ); } // detect unused vertices @@ -114,7 +115,7 @@ static void removeWasteVertices( NifModel * nif, const QModelIndex & iData, cons // remove them - qWarning() << "removing" << verts.count() - used.count() << "vertices"; + Message::info( nullptr, Spell::tr( "Removed %1 vertices" ).arg( verts.count() - used.count() ) ); if ( verts.count() == used.count() ) return; @@ -226,12 +227,12 @@ static void removeWasteVertices( NifModel * nif, const QModelIndex & iData, cons if ( iSkinPart.isValid() ) { nif->removeNiBlock( nif->getBlockNumber( iSkinPart ) ); - qWarning() << "the skin partition was removed, please add it again with the skin partition spell"; + Message::warning( nullptr, Spell::tr( "The skin partition was removed, please regenerate it with the skin partition spell" ) ); } } catch ( QString e ) { - qWarning() << e.toLatin1().data(); + Message::warning( nullptr, Spell::tr( "There were errors during the operation" ), e ); } } @@ -441,7 +442,7 @@ class spPruneRedundantTriangles final : public Spell } if ( cnt > 0 ) { - qWarning() << QString( Spell::tr( "%1 triangles removed" ) ).arg( cnt ); + Message::info( nullptr, Spell::tr( "Removed %1 triangles" ).arg( cnt ) ); nif->set( iData, "Num Triangles", tris.count() ); nif->set( iData, "Num Triangle Points", tris.count() * 3 ); nif->updateArray( iData, "Triangles" ); @@ -478,7 +479,7 @@ class spRemoveDuplicateVertices final : public Spell QVector verts = nif->getArray( iData, "Vertices" ); if ( !verts.count() ) - throw QString( "no vertices?" ); + throw QString( Spell::tr( "No vertices" ) ); QVector norms = nif->getArray( iData, "Normals" ); QVector colors = nif->getArray( iData, "Vertex Colors" ); @@ -489,7 +490,7 @@ class spRemoveDuplicateVertices final : public Spell texco << nif->getArray( iUVSets.child( r, 0 ) ); if ( texco.last().count() != verts.count() ) - throw QString( Spell::tr( "uv array size differs" ) ); + throw QString( Spell::tr( "UV array size differs" ) ); } int numVerts = verts.count(); @@ -498,7 +499,7 @@ class spRemoveDuplicateVertices final : public Spell || ( norms.count() && norms.count() != numVerts ) || ( colors.count() && colors.count() != numVerts ) ) { - throw QString( Spell::tr( "vertex array size differs" ) ); + throw QString( Spell::tr( "Vertex array size differs" ) ); } // detect the dublicates @@ -532,7 +533,7 @@ class spRemoveDuplicateVertices final : public Spell } } - //qWarning() << QString( Spell::tr("detected % duplicates") ).arg( map.count() ); + //qDebug() << QString( Spell::tr("detected % duplicates") ).arg( map.count() ); // adjust the faces @@ -573,7 +574,7 @@ class spRemoveDuplicateVertices final : public Spell } catch ( QString e ) { - qWarning() << e.toLatin1().data(); + Message::warning( nullptr, Spell::tr( "There were errors during the operation" ), e ); } return index; @@ -676,3 +677,83 @@ QModelIndex spUpdateCenterRadius::cast( NifModel * nif, const QModelIndex & inde } REGISTER_SPELL( spUpdateCenterRadius ) + +//! Updates Bounds of BSTriShape +class spUpdateBounds final : public Spell +{ +public: + QString name() const override final { return Spell::tr( "Update Bounds" ); } + QString page() const override final { return Spell::tr( "Mesh" ); } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final + { + return nif->inherits( index, "BSTriShape" ) && nif->getIndex( index, "Vertex Data" ).isValid(); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final + { + auto vertData = nif->getIndex( index, "Vertex Data" ); + + // Retrieve the verts + QVector verts; + for ( int i = 0; i < nif->rowCount( vertData ); i++ ) { + verts << nif->get( vertData.child( i, 0 ), "Vertex" ); + } + + if ( verts.isEmpty() ) + return index; + + // Creating a bounding sphere from the verts + BoundSphere bounds = BoundSphere( verts ); + + // Update the bounding sphere + auto boundsIdx = nif->getIndex( index, "Bounding Sphere" ); + nif->set( boundsIdx, "Center", bounds.center ); + nif->set( boundsIdx, "Radius", bounds.radius ); + + return index; + } +}; + +REGISTER_SPELL( spUpdateBounds ) + + +class spUpdateAllBounds final : public Spell +{ +public: + QString name() const override final { return Spell::tr( "Update All Bounds" ); } + QString page() const override final { return Spell::tr( "Batch" ); } + + bool isApplicable( const NifModel * nif, const QModelIndex & idx ) override final + { + if ( !nif || idx.isValid() ) + return false; + + if ( nif->getUserVersion2() == 130 ) + return true; + + return false; + } + + QModelIndex cast( NifModel * nif, const QModelIndex & ) override final + { + QList indices; + + spUpdateBounds updBounds; + + for ( int n = 0; n < nif->getBlockCount(); n++ ) { + QModelIndex idx = nif->getBlock( n ); + + if ( updBounds.isApplicable( nif, idx ) ) + indices << idx; + } + + for ( const QModelIndex& idx : indices ) { + updBounds.castIfApplicable( nif, idx ); + } + + return QModelIndex(); + } +}; + +REGISTER_SPELL( spUpdateAllBounds ) diff --git a/src/spells/misc.cpp b/src/spells/misc.cpp index df4a10a10..6f2f5d8fc 100644 --- a/src/spells/misc.cpp +++ b/src/spells/misc.cpp @@ -1,5 +1,6 @@ #include "misc.h" +#include // Brief description is deliberately not autolinked to class Spell /*! \file misc.cpp @@ -19,7 +20,7 @@ class spUpdateArray final : public Spell bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { - if ( nif->isArray( index ) && nif->evalCondition( index ) ) { + if ( nif->isArray( index ) ) { //Check if array is of fixed size NifItem * item = static_cast( index.internalPointer() ); bool static1 = true; @@ -138,17 +139,116 @@ class spFileOffset final : public Spell QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final { int ofs = nif->fileOffset( index ); - qWarning( QString( "estimated file offset is %1 (0x%2)" ).arg( ofs ).arg( ofs, 0, 16 ).toLatin1().constData() ); + Message::info( nif->getWindow(), + Spell::tr( "Estimated file offset is %1 (0x%2)" ).arg( ofs ).arg( ofs, 0, 16 ), + Spell::tr( "Block: %1\nOffset: %2 (0x%3)" ).arg( index.data( Qt::DisplayRole ).toString() ).arg( ofs ).arg( ofs, 0, 16 ) + ); return index; } }; REGISTER_SPELL( spFileOffset ) +//! Exports the binary data of a binary row to a file +class spExportBinary final : public Spell +{ +public: + QString name() const override final { return Spell::tr( "Export Binary" ); } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final + { + NifItem * item = static_cast(index.internalPointer()); + + return item && (item->value().isByteArray() || (item->isBinary() && item->isArray())) && index.isValid(); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final + { + NifItem * item = static_cast(index.internalPointer()); + + QByteArray data; + + NifItem * dataItem = item; + if ( item->isArray() && item->isBinary() ) { + dataItem = item->child( 0 ); + } + + if ( dataItem && dataItem->value().isByteArray() ) { + auto bytes = dataItem->value().get(); + data.append( *bytes ); + } + + // Get parent block name and number + int blockNum = nif->getBlockNumber( index ); + QString suffix = QString( "%1_%2" ).arg( nif->getBlockName( nif->getBlock( blockNum ) ) ).arg( blockNum ); + QString filestring = QString( "%1-%2" ).arg( nif->getFilename() ).arg( suffix ); + + QString filename = QFileDialog::getSaveFileName( qApp->activeWindow(), tr( "Export Binary File" ), + filestring, "*.*" ); + QFile file( filename ); + if ( file.open( QIODevice::WriteOnly ) ) { + file.write( data ); + file.close(); + } + + return index; + } +}; + +REGISTER_SPELL( spExportBinary ) + +//! Imports the binary data of a file to a binary row +class spImportBinary final : public Spell +{ +public: + QString name() const override final { return Spell::tr( "Import Binary" ); } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final + { + NifItem * item = static_cast(index.internalPointer()); + + return item && (item->value().isByteArray() || (item->isBinary() && item->isArray())) && index.isValid(); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final + { + NifItem * item = static_cast(index.internalPointer()); + NifItem * parent = item->parent(); + auto iParent = index.parent(); + + auto idx = index; + if ( item->isArray() && item->isBinary() ) { + parent = item; + iParent = index; + idx = index.child( 0, 0 ); + } + + QString filename = QFileDialog::getOpenFileName( qApp->activeWindow(), tr( "Import Binary File" ), "", "*.*" ); + QFile file( filename ); + if ( file.open( QIODevice::ReadOnly ) ) { + QByteArray data = file.readAll(); + + if ( parent->isArray() && parent->isBinary() ) { + // NOTE: This will only work on byte arrays where the array length is not an expression + nif->set( iParent.parent(), parent->arr1(), data.count() ); + nif->updateArray( iParent ); + } + + nif->set( idx, data ); + + file.close(); + } + + return index; + } +}; + +REGISTER_SPELL( spImportBinary ) + // definitions for spCollapseArray moved to misc.h bool spCollapseArray::isApplicable( const NifModel * nif, const QModelIndex & index ) { - if ( nif->isArray( index ) && nif->evalCondition( index ) && index.isValid() + if ( nif->isArray( index ) && index.isValid() && ( nif->getBlockType( index ) == "Ref" || nif->getBlockType( index ) == "Ptr" ) ) { // copy from spUpdateArray when that changes diff --git a/src/spells/moppcode.cpp b/src/spells/moppcode.cpp index 4e46d7aa5..7e2cce2fa 100644 --- a/src/spells/moppcode.cpp +++ b/src/spells/moppcode.cpp @@ -138,7 +138,7 @@ class spMoppCode final : public Spell return false; if ( TheHavokCode.Initialize() ) { - QModelIndex iData = nif->getBlock( nif->getLink( index, "Data" ) ); + //QModelIndex iData = nif->getBlock( nif->getLink( index, "Data" ) ); if ( nif->isNiBlock( index, "bhkMoppBvTreeShape" ) ) { return ( nif->checkVersion( 0x14000004, 0x14000005 ) @@ -152,7 +152,7 @@ class spMoppCode final : public Spell QModelIndex cast( NifModel * nif, const QModelIndex & iBlock ) override final { if ( !TheHavokCode.Initialize() ) { - qWarning() << Spell::tr( "unable to locate the NifMopp.dll library" ); + Message::critical( nullptr, Spell::tr( "Unable to locate NifMopp.dll" ) ); return iBlock; } @@ -161,7 +161,7 @@ class spMoppCode final : public Spell QModelIndex ibhkPackedNiTriStripsShape = nif->getBlock( nif->getLink( ibhkMoppBvTreeShape, "Shape" ) ); if ( !nif->isNiBlock( ibhkPackedNiTriStripsShape, "bhkPackedNiTriStripsShape" ) ) { - qWarning() << Spell::tr( "only bhkPackedNiTriStripsShape can be used with bhkMoppBvTreeShape Mopp code at this time" ); + Message::warning( nullptr, Spell::tr( "Only bhkPackedNiTriStripsShape is supported at this time." ) ); return iBlock; } @@ -202,7 +202,9 @@ class spMoppCode final : public Spell } if ( verts.isEmpty() || triangles.isEmpty() ) { - qWarning() << Spell::tr( "need vertices and faces to calculate mopp code" ); + Message::critical( nullptr, Spell::tr( "Insufficient data to calculate MOPP code" ), + Spell::tr("Vertices: %1, Triangles: %2").arg( !verts.isEmpty() ).arg( !triangles.isEmpty() ) + ); return iBlock; } @@ -211,7 +213,7 @@ class spMoppCode final : public Spell QByteArray moppcode = TheHavokCode.CalculateMoppCode( subshapeVerts, verts, triangles, &origin, &scale ); if ( moppcode.size() == 0 ) { - qWarning() << Spell::tr( "failed to generate mopp code" ); + Message::critical( nullptr, Spell::tr( "Failed to generate MOPP code" ) ); } else { QModelIndex iCodeOrigin = nif->getIndex( ibhkMoppBvTreeShape, "Origin" ); nif->set( iCodeOrigin, origin ); diff --git a/src/spells/morphctrl.cpp b/src/spells/morphctrl.cpp index 1566e9a9b..176129b73 100644 --- a/src/spells/morphctrl.cpp +++ b/src/spells/morphctrl.cpp @@ -47,7 +47,7 @@ class spMorphFrameSave final : public Spell int selFrame = frameList.indexOf( act->text() ); if ( selFrame == 0 ) { - qWarning() << "overriding base key frame, all other frames will be cleared"; + qCWarning( nsSpell ) << Spell::tr( "overriding base key frame, all other frames will be cleared" ); nif->set( iMorphData, "Num Vertices", nif->get( iMeshData, "Num Vertices" ) ); QVector verts = nif->getArray( iMeshData, "Vertices" ); nif->updateArray( iFrames.child( 0, 0 ), "Vectors" ); diff --git a/src/spells/normals.cpp b/src/spells/normals.cpp index 1c8742967..02a49f8f5 100644 --- a/src/spells/normals.cpp +++ b/src/spells/normals.cpp @@ -27,12 +27,15 @@ class spFaceNormals final : public Spell { QModelIndex iData = nif->getBlock( index ); - if ( nif->isNiBlock( index, "NiTriShape" ) || nif->isNiBlock( index, "BSLODTriShape" ) || nif->isNiBlock( index, "NiTriStrips" ) ) + if ( nif->isNiBlock( index, { "NiTriShape", "BSLODTriShape", "NiTriStrips" } ) ) iData = nif->getBlock( nif->getLink( index, "Data" ) ); - if ( nif->isNiBlock( iData, "NiTriShapeData" ) || nif->isNiBlock( iData, "NiTriStripsData" ) ) + if ( nif->isNiBlock( iData, { "NiTriShapeData", "NiTriStripsData" } ) ) return iData; + if ( nif->isNiBlock( index, { "BSTriShape", "BSMeshLODTriShape", "BSSubIndexTriShape" } ) ) + return nif->getIndex( index, "Vertex Data" ); + return QModelIndex(); } @@ -45,41 +48,73 @@ class spFaceNormals final : public Spell { QModelIndex iData = getShapeData( nif, index ); - QVector verts = nif->getArray( iData, "Vertices" ); - QVector triangles; - QModelIndex iPoints = nif->getIndex( iData, "Points" ); + auto faceNormals = []( const QVector & verts, const QVector & triangles, QVector & norms ) { + for ( const Triangle & tri : triangles ) { + Vector3 a = verts[tri[0]]; + Vector3 b = verts[tri[1]]; + Vector3 c = verts[tri[2]]; + + Vector3 fn = Vector3::crossproduct( b - a, c - a ); + norms[tri[0]] += fn; + norms[tri[1]] += fn; + norms[tri[2]] += fn; + } + + for ( int n = 0; n < norms.count(); n++ ) { + norms[n].normalize(); + } + }; - if ( iPoints.isValid() ) { - QList > strips; + if ( nif->getUserVersion2() < 130 ) { + QVector verts = nif->getArray( iData, "Vertices" ); + QVector triangles; + QModelIndex iPoints = nif->getIndex( iData, "Points" ); - for ( int r = 0; r < nif->rowCount( iPoints ); r++ ) - strips.append( nif->getArray( iPoints.child( r, 0 ) ) ); + if ( iPoints.isValid() ) { + QList > strips; - triangles = triangulate( strips ); + for ( int r = 0; r < nif->rowCount( iPoints ); r++ ) + strips.append( nif->getArray( iPoints.child( r, 0 ) ) ); + + triangles = triangulate( strips ); + } else { + triangles = nif->getArray( iData, "Triangles" ); + } + + + QVector norms( verts.count() ); + + faceNormals( verts, triangles, norms ); + + nif->set( iData, "Has Normals", 1 ); + nif->updateArray( iData, "Normals" ); + nif->setArray( iData, "Normals", norms ); } else { - triangles = nif->getArray( iData, "Triangles" ); - } + int numVerts = nif->get( index, "Num Vertices" ); + QVector verts; + QVector norms( numVerts ); + QVector triangles = nif->getArray( index, "Triangles" ); + for ( int i = 0; i < numVerts; i++ ) { + auto idx = nif->index( i, 0, iData ); - QVector norms( verts.count() ); - for ( const Triangle& tri : triangles ) { - Vector3 a = verts[ tri[0] ]; - Vector3 b = verts[ tri[1] ]; - Vector3 c = verts[ tri[2] ]; + verts += nif->get( idx, "Vertex" ); + } - Vector3 fn = Vector3::crossproduct( b - a, c - a ); - norms[ tri[0] ] += fn; - norms[ tri[1] ] += fn; - norms[ tri[2] ] += fn; - } + faceNormals( verts, triangles, norms ); - for ( int n = 0; n < norms.count(); n++ ) { - norms[ n ].normalize(); - } + // Pause updates between model/view + nif->setEmitChanges( false ); + for ( int i = 0; i < numVerts; i++ ) { + // Unpause updates if last + if ( i == numVerts - 1 ) + nif->setEmitChanges( true ); - nif->set( iData, "Has Normals", 1 ); - nif->updateArray( iData, "Normals" ); - nif->setArray( iData, "Normals", norms ); + auto idx = nif->index( i, 0, iData ); + + nif->set( idx, "Normal", *static_cast(&norms[i]) ); + } + } return index; } @@ -133,8 +168,21 @@ class spSmoothNormals final : public Spell { QModelIndex iData = spFaceNormals::getShapeData( nif, index ); - QVector verts = nif->getArray( iData, "Vertices" ); - QVector norms = nif->getArray( iData, "Normals" ); + QVector verts; + QVector norms; + + if ( nif->getUserVersion2() < 130 ) { + verts = nif->getArray( iData, "Vertices" ); + norms = nif->getArray( iData, "Normals" ); + } else { + int numVerts = nif->get( index, "Num Vertices" ); + for ( int i = 0; i < numVerts; i++ ) { + auto idx = nif->index( i, 0, iData ); + + verts += nif->get( idx, "Vertex" ); + norms += nif->get( idx, "Normal" ); + } + } if ( verts.isEmpty() || verts.count() != norms.count() ) return index; @@ -203,7 +251,23 @@ class spSmoothNormals final : public Spell for ( int i = 0; i < verts.count(); i++ ) snorms[i].normalize(); - nif->setArray( iData, "Normals", snorms ); + if ( nif->getUserVersion2() < 130 ) { + nif->setArray( iData, "Normals", snorms ); + } else { + int numVerts = nif->get( index, "Num Vertices" ); + // Pause updates between model/view + nif->setEmitChanges( false ); + for ( int i = 0; i < numVerts; i++ ) { + // Unpause updates if last + if ( i == numVerts - 1 ) + nif->setEmitChanges( true ); + + auto idx = nif->index( i, 0, iData ); + + nif->set( idx, "Normal", *static_cast(&snorms[i]) ); + } + } + return index; } diff --git a/src/spells/optimize.cpp b/src/spells/optimize.cpp index 9d622f2af..e53d4f2b9 100644 --- a/src/spells/optimize.cpp +++ b/src/spells/optimize.cpp @@ -70,7 +70,7 @@ class spCombiProps final : public Spell QBuffer data; data.open( QBuffer::WriteOnly ); data.write( nif->itemName( iBlock ).toLatin1() ); - nif->save( data, iBlock ); + nif->saveIndex( data, iBlock ); props.insert( b, data.buffer() ); } @@ -108,7 +108,7 @@ class spCombiProps final : public Spell } } while ( !map.isEmpty() ); - QMessageBox::information( 0, "NifSkope", QString( "removed %1 properties" ).arg( numRemoved ) ); + Message::info( nullptr, Spell::tr( "Removed %1 properties" ).arg( numRemoved ) ); return QModelIndex(); } }; @@ -155,10 +155,10 @@ class spUniqueProps final : public Spell QModelIndex iSrc2 = nif->insertNiBlock( "NiSourceTexture", nif->getBlockCount() + 1 ); QBuffer buffer; buffer.open( QBuffer::WriteOnly ); - nif->save( buffer, iSrc ); + nif->saveIndex( buffer, iSrc ); buffer.close(); buffer.open( QBuffer::ReadOnly ); - nif->load( buffer, iSrc2 ); + nif->loadIndex( buffer, iSrc2 ); map[ sl ] = nif->getBlockNumber( iSrc2 ); } } @@ -167,7 +167,7 @@ class spUniqueProps final : public Spell QModelIndex iProp2 = nif->insertNiBlock( nif->itemName( iProp ), nif->getBlockCount() + 1 ); QBuffer buffer; buffer.open( QBuffer::WriteOnly ); - nif->save( buffer, iProp ); + nif->saveIndex( buffer, iProp ); buffer.close(); buffer.open( QBuffer::ReadOnly ); nif->loadAndMapLinks( buffer, iProp2, map ); @@ -244,7 +244,7 @@ class spRemoveBogusNodes final : public Spell } while ( removed ); if ( cnt > 0 ) - QMessageBox::information( 0, "NifSkope", QString( Spell::tr( "removed %1 nodes" ) ).arg( cnt ) ); + Message::info( nullptr, Spell::tr( "Removed %1 nodes" ).arg( cnt ) ); return QModelIndex(); } @@ -284,7 +284,7 @@ class spCombiTris final : public Spell if ( nif->getParent( lChild ) == nif->getBlockNumber( iParent ) ) { QModelIndex iChild = nif->getBlock( lChild ); - if ( nif->isNiBlock( iChild, "NiTriShape" ) || nif->isNiBlock( iChild, "NiTriStrips" ) ) + if ( nif->isNiBlock( iChild, { "NiTriShape", "NiTriStrips" } ) ) lTris << lChild; } } @@ -368,7 +368,11 @@ class spCombiTris final : public Spell if ( nif->isNiBlock( iBlock, "NiBinaryExtraData" ) && nif->get( iBlock, "Name" ) == "Tangent space (binormal & tangent vectors)" ) continue; - qWarning() << "Attached " << nif->itemName( iBlock ) << " prevents " << nif->get( iTriA, "Name" ) << " and " << nif->get( iTriB, "Name" ) << " from matching."; + qCWarning( nsSpell ) << Spell::tr( "Attached %1 prevents %2 and %3 from matching." ) + .arg( nif->itemName( iBlock ) ) + .arg( nif->get( iTriA, "Name" ) ) + .arg( nif->get( iTriB, "Name" ) ); + return false; } @@ -385,7 +389,11 @@ class spCombiTris final : public Spell if ( nif->isNiBlock( iBlock, "NiBinaryExtraData" ) && nif->get( iBlock, "Name" ) == "Tangent space (binormal & tangent vectors)" ) continue; - qWarning() << "Attached " << nif->itemName( iBlock ) << " prevents " << nif->get( iTriA, "Name" ) << " and " << nif->get( iTriB, "Name" ) << " from matching."; + qCWarning( nsSpell ) << Spell::tr( "Attached %1 prevents %2 and %3 from matching." ) + .arg( nif->itemName( iBlock ) ) + .arg( nif->get( iTriA, "Name" ) ) + .arg( nif->get( iTriB, "Name" ) ); + return false; } @@ -486,3 +494,91 @@ class spCombiTris final : public Spell }; REGISTER_SPELL( spCombiTris ) + + +void scan( QModelIndex idx, NifModel * nif, QMap & usedStrings, bool hasCED ) +{ + for ( int i = 0; i < nif->rowCount( idx ); i++ ) { + auto child = idx.child( i, 2 ); + if ( nif->rowCount( child ) > 0 ) { + scan( child, nif, usedStrings, hasCED ); + continue; + } + + NifValue val; + val.setFromVariant( child.data( Qt::EditRole ) ); + if ( val.type() == NifValue::tStringIndex ) { + if ( nif->get( child ) == -1 ) + continue; + + auto str = nif->get( child ); + if ( !usedStrings.contains( str ) ) + usedStrings.insert( str, usedStrings.size() ); + + qint32 value = usedStrings[str]; + if ( hasCED && value > 0 ) + value++; + + nif->set( child, value ); + } + } +} + +//! Removes unused strings from the header +class spRemoveUnusedStrings final : public Spell +{ +public: + QString name() const override final { return Spell::tr( "Remove Unused Strings" ); } + QString page() const override final { return Spell::tr( "Optimize" ); } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final + { + return nif && !index.isValid(); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & ) override final + { + auto originalStrings = nif->getArray( nif->getHeader(), "Strings" ); + + // FO4 workaround for apparently unused but necessary BSClothExtraData string + int cedIdx = originalStrings.indexOf( "CED" ); + + bool hasCED = cedIdx >= 0; + + QMap usedStrings; + for ( qint32 b = 0; b < nif->getBlockCount(); b++ ) + scan( nif->getBlock( b ), nif, usedStrings, hasCED ); + + QVector newStrings( usedStrings.size() ); + for ( auto kv : usedStrings.toStdMap() ) + newStrings[kv.second] = kv.first; + + int newSize = newStrings.size(); + + if ( hasCED ) { + newStrings.insert( cedIdx, 1, "CED" ); + newSize++; + } + + nif->set( nif->getHeader(), "Num Strings", newSize ); + nif->updateArray( nif->getHeader(), "Strings" ); + nif->setArray( nif->getHeader(), "Strings", newStrings ); + nif->updateHeader(); + + // Remove new from original to see what was removed + for ( const auto & s : newStrings ) + originalStrings.removeAll( s ); + + QString msg; + if ( originalStrings.size() ) + msg = "Removed:\r\n" + QStringList::fromVector( originalStrings ).join( "\r\n" ); + + Message::info( nullptr, Spell::tr( "Strings Removed: %1. New string table has %2 entries." ) + .arg( originalStrings.size() ).arg( newSize ), msg + ); + + return QModelIndex(); + } +}; + +REGISTER_SPELL( spRemoveUnusedStrings ) diff --git a/src/spells/sanitize.cpp b/src/spells/sanitize.cpp index 0c1358b1f..a073a01cd 100644 --- a/src/spells/sanitize.cpp +++ b/src/spells/sanitize.cpp @@ -171,7 +171,9 @@ class spSanitizeBlockOrder final : public Spell public: QString name() const override final { return Spell::tr( "Reorder Blocks" ); } QString page() const override final { return Spell::tr( "Sanitize" ); } - bool sanity() const { return true; } + // Prevent this from running during auto-sanitize for the time being + // Can really only cause issues with rendering and textureset overrides via the CK + bool sanity() const { return false; } bool isApplicable( const NifModel *, const QModelIndex & index ) override final { @@ -246,7 +248,7 @@ class spSanitizeBlockOrder final : public Spell // check whether all blocks have been added if ( nif->getBlockCount() != newblocks.size() ) { - qWarning() << "failed to sanitize blocks order, corrupt nif tree?"; + qCCritical( nsSpell ) << Spell::tr( "failed to sanitize blocks order, corrupt nif tree?" ); return QModelIndex(); } @@ -255,8 +257,7 @@ class spSanitizeBlockOrder final : public Spell for ( qint32 n = 0; n < newblocks.size(); n++ ) { order[newblocks[n]] = n; - // DEBUG - //qWarning() << n << newblocks[n]; + //qDebug() << n << newblocks[n]; } // reorder the blocks @@ -308,12 +309,12 @@ class spSanityCheckLinks final : public Spell /* if ( ! child ) { - qWarning() << "unassigned parent link"; + qDebug() << "unassigned parent link"; return idx; } */ } else if ( l >= nif->getBlockCount() ) { - qWarning() << "invalid link"; + qCCritical( nsSpell ) << Spell::tr( "Invalid link '%1'." ).arg( QString::number(l) ); return idx; } else { QString tmplt = nif->itemTmplt( idx ); @@ -322,7 +323,7 @@ class spSanityCheckLinks final : public Spell QModelIndex iBlock = nif->getBlock( l ); if ( !nif->inherits( iBlock, tmplt ) ) { - qWarning() << "link points to wrong block type"; + qCCritical( nsSpell ) << Spell::tr( "Link '%1' points to wrong block type." ).arg( QString::number(l) ); return idx; } } @@ -343,3 +344,167 @@ class spSanityCheckLinks final : public Spell REGISTER_SPELL( spSanityCheckLinks ) +//! Fixes invalid block names +class spFixInvalidNames final : public Spell +{ +public: + QString name() const override final { return Spell::tr( "Fix Invalid Block Names" ); } + QString page() const override final { return Spell::tr( "Sanitize" ); } + bool sanity() const { return true; } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final + { + return nif && nif->getIndex( nif->getHeader(), "Num Strings" ).isValid() && !index.isValid(); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & ) override final + { + QVector stringsToAdd; + QVector shapeNames; + QMap modifiedBlocks; + + auto iHeader = nif->getHeader(); + auto numStrings = nif->get( iHeader, "Num Strings" ); + auto strings = nif->getArray( iHeader, "Strings" ); + + // Provides a string index for the desired string + auto rename = [&strings, &stringsToAdd, numStrings] ( int & newIdx, const QString & str ) { + newIdx = strings.indexOf( str ); + if ( newIdx < 0 ) { + bool inNew = stringsToAdd.contains( str ); + newIdx = (inNew) ? stringsToAdd.indexOf( str ) : stringsToAdd.count(); + newIdx += numStrings; + + if ( !inNew ) + stringsToAdd << str; + } + }; + + // Provides a block name using a base string and the block number, + // incrementing the number if necessary to avoid duplicate names + auto autoRename = [&strings, &stringsToAdd, numStrings] ( int & newIdx, const QString & parentName, int blockNum ) { + QString newName; + int j = 0; + do { + newName = QString( "%1:%2" ).arg( parentName ).arg( blockNum + j ); + j++; + } while ( strings.contains( newName ) || stringsToAdd.contains( newName ) ); + + newIdx = numStrings + stringsToAdd.count(); + stringsToAdd << newName; + }; + + for ( int i = 0; i < nif->getBlockCount(); i++ ) { + QModelIndex iBlock = nif->getBlock( i ); + if ( !(nif->inherits( iBlock, "NiObjectNET" ) || nif->inherits( iBlock, "NiExtraData" )) ) + continue; + + auto nameIdx = nif->get( iBlock, "Name" ); + auto nameString = nif->get( iBlock, "Name" ); + + QModelIndex iBlockParent = nif->getBlock( nif->getParent( i ) ); + auto parentNameString = nif->get( iBlockParent, "Name" ); + if ( !iBlockParent.isValid() ) { + parentNameString = nif->getFilename(); + } + + int newIdx = -1; + + bool isOutOfBounds = nameIdx >= numStrings; + bool isProp = nif->isNiBlock( iBlock, + { "BSLightingShaderProperty", "BSEffectShaderProperty", "NiAlphaProperty" } ); + bool isNiAV = nif->inherits( iBlock, "NiAVObject" ); + + // Fix 'BSX' strings + if ( nif->isNiBlock( iBlock, "BSXFlags" ) ) { + if ( nameString == "BSX" ) + continue; + rename( newIdx, "BSX" ); + } else if ( nameString == "BSX" ) { + if ( isProp ) + rename( newIdx,"" ); + else + autoRename( newIdx, parentNameString, i ); + } + + // Fix duplicate shape names + if ( isNiAV && !isOutOfBounds ) { + if ( shapeNames.contains( nameString ) ) + autoRename( newIdx, parentNameString, i ); + + shapeNames << nameString; + } + + // Fix "Wet Material" field + if ( isProp && nif->getIndex( iBlock, "Wet Material" ).isValid() ) { + auto wetIdx = nif->get( iBlock, "Wet Material" ); + auto wetString = nif->get( iBlock, "Wet Material" ); + + int newWetIdx = -1; + + bool invalidString = !wetString.isEmpty() && !wetString.endsWith( ".bgsm", Qt::CaseInsensitive ); + if ( wetIdx >= numStrings || invalidString ) { + rename( newWetIdx, "" ); + } + + if ( newWetIdx > -1 ) { + nif->set( iBlock, "Wet Material", newWetIdx ); + modifiedBlocks.insert( nif->getIndex( iBlock, "Wet Material" ), "Wet Material" ); + } + } + + // Fix "Name" field + if ( isOutOfBounds ) { + // Fix out of bounds string indices + // Rename scene objects, or blank out property names + if ( !isProp ) + autoRename( newIdx, parentNameString, i ); + else + rename( newIdx, "" ); + } else if ( isProp ) { + if ( nameString.isEmpty() ) + continue; + + // Fix invalid property names + if ( nif->isNiBlock( iBlock, "NiAlphaProperty" ) ) { + rename( newIdx, "" ); + } else { + auto ci = Qt::CaseInsensitive; + if ( !(nameString.endsWith( ".bgsm", ci ) || nameString.endsWith( ".bgem", ci )) ) { + rename( newIdx, "" ); + } + } + } + + if ( newIdx > -1 ) { + nif->set( iBlock, "Name", newIdx ); + modifiedBlocks.insert( nif->getIndex( iBlock, "Name" ), "Name" ); + } + } + + if ( modifiedBlocks.count() < 1 ) + return QModelIndex(); + + // Append new strings to header strings + strings << stringsToAdd; + + // Update header + nif->set( iHeader, "Num Strings", strings.count() ); + nif->updateArray( iHeader, "Strings" ); + nif->setArray( iHeader, "Strings", strings ); + + nif->updateHeader(); + + for ( const auto & b : modifiedBlocks.toStdMap() ) { + auto blockName = b.first.parent().data( Qt::DisplayRole ).toString(); + + Message::append( Spell::tr( "One or more blocks have had their Name sanitized." ), + QString( "%1 (%2) = '%3'" ).arg( blockName ).arg( b.second ).arg( nif->get( b.first ) ) + ); + } + + return QModelIndex(); + } +}; + +REGISTER_SPELL( spFixInvalidNames ) diff --git a/src/spells/skeleton.cpp b/src/spells/skeleton.cpp index 5fffeab5c..3f02a01c1 100644 --- a/src/spells/skeleton.cpp +++ b/src/spells/skeleton.cpp @@ -220,7 +220,7 @@ class spScanSkeleton final : public Spell if ( name.startsWith( "Bip01" ) ) { Transform local( nif, index ); stream << name << local << tparent * local; - qWarning() << name; + qDebug() << name; for ( const auto link : nif->getChildLinks( nif->getBlockNumber( index ) ) ) { QModelIndex iChild = nif->getBlock( link, "NiNode" ); @@ -265,7 +265,7 @@ class spSkinPartition final : public Spell bool isApplicable( const NifModel * nif, const QModelIndex & iShape ) override final { - if ( nif->isNiBlock( iShape, "NiTriShape" ) || nif->isNiBlock( iShape, "NiTriStrips" ) ) { + if ( nif->isNiBlock( iShape, { "NiTriShape", "NiTriStrips" } ) ) { QModelIndex iSkinInst = nif->getBlock( nif->getLink( iShape, "Skin Instance" ), "NiSkinInstance" ); if ( iSkinInst.isValid() ) { @@ -421,7 +421,10 @@ class spSkinPartition final : public Spell } } - qWarning() << QString( Spell::tr( "reduced %1 vertices to %2 bone influences (maximum number of bones per vertex was %3)" ) ).arg( c ).arg( maxBonesPerVertex ).arg( maxBones ); + qCWarning( nsSpell ) << Spell::tr( "Reduced %1 vertices to %2 bone influences (maximum number of bones per vertex was %3)" ) + .arg( c ) + .arg( maxBonesPerVertex ) + .arg( maxBones ); } maxBones = maxBonesPerVertex; @@ -635,7 +638,7 @@ class spSkinPartition final : public Spell } if ( cnt > 0 ) - qWarning() << QString( Spell::tr( "removed %1 bone influences" ) ).arg( cnt ); + qCWarning( nsSpell ) << Spell::tr( "Removed %1 bone influences" ).arg( cnt ); // split the triangles into partitions @@ -750,7 +753,7 @@ class spSkinPartition final : public Spell parts.append( part ); } - //qWarning() << parts.count() << "small partitions"; + //qDebug() << parts.count() << "small partitions"; // merge partitions @@ -773,7 +776,7 @@ class spSkinPartition final : public Spell } } - //qWarning() << parts.count() << "partitions"; + //qDebug() << parts.count() << "partitions"; // create the NiSkinPartition if it doesn't exist yet @@ -796,7 +799,7 @@ class spSkinPartition final : public Spell // why is QList.count() signed? cast to squash warning if ( nparts != (quint32)parts.count() ) { - qWarning() << "BSDismemberSkinInstance partition count does not match Skin Partition count. Adjusting to fit."; + qCWarning( nsSpell ) << "BSDismemberSkinInstance partition count does not match Skin Partition count. Adjusting to fit."; nif->set( iSkinInst, "Num Partitions", parts.count() ); nif->updateArray( iSkinInst, "Partitions" ); } @@ -1030,7 +1033,7 @@ class spAllSkinPartitions final : public Spell Partitioner.cast( nif, idx, mbpp, mbpv, make_strips ); } - qWarning() << QString( Spell::tr( "did %1 partitions" ) ).arg( indices.count() ); + qCWarning( nsSpell ) << Spell::tr( "did %1 partitions" ).arg( indices.count() ); return QModelIndex(); } @@ -1053,7 +1056,7 @@ SkinPartitionDialog::SkinPartitionDialog( int ) : QDialog() spnPart->setValue( 18 ); ckTStrip = new QCheckBox( "&Stripify Triangles" ); - ckTStrip->setChecked( true ); + ckTStrip->setChecked( false ); ckTStrip->setToolTip( "Determines whether the triangles in each partition will be arranged into strips or as a list of individual triangles. Different gaems work best with one or the other." ); connect( ckTStrip, &QCheckBox::clicked, this, &SkinPartitionDialog::changed ); @@ -1207,8 +1210,10 @@ class spFixBoneBounds final : public Spell center = ( mn + mx ) / 2; radius = qMax( ( mn - center ).length(), ( mx - center ).length() ); - nif->set( iBoneDataList.child( b, 0 ), "Bounding Sphere Offset", center ); - nif->set( iBoneDataList.child( b, 0 ), "Bounding Sphere Radius", radius ); + auto sphIdx = nif->getIndex( iBoneDataList.child( b, 0 ) , "Bounding Sphere" ); + + nif->set( sphIdx, "Bounding Sphere Offset", center ); + nif->set( sphIdx, "Bounding Sphere Radius", radius ); } return iSkinData; @@ -1308,7 +1313,7 @@ class spMirrorSkeleton final : public Spell nif->set( iChild, "Name", childName ); - //qWarning() << "Checking child: " << iChild; + //qDebug() << "Checking child: " << iChild; if ( iChild.isValid() ) { if ( nif->itemName( iChild ) == "NiNode" ) { // repeat @@ -1327,7 +1332,7 @@ class spMirrorSkeleton final : public Spell void doShapes( NifModel * nif, const QModelIndex & index ) { - //qWarning() << "Entering doShapes"; + //qDebug() << "Entering doShapes"; QModelIndex iData = nif->getBlock( nif->getLink( index, "Data" ) ); QModelIndex iSkinInstance = nif->getBlock( nif->getLink( index, "Skin Instance" ), "NiSkinInstance" ); diff --git a/src/spells/stringpalette.cpp b/src/spells/stringpalette.cpp index d36997551..2357f1222 100644 --- a/src/spells/stringpalette.cpp +++ b/src/spells/stringpalette.cpp @@ -65,7 +65,7 @@ static char const * txt_xpm[] = { " " }; -QIcon * txt_xpm_icon = nullptr; +static QIconPtr txt_xpm_icon = nullptr; //! Edit a single offset into a string palette. class spEditStringOffset final : public Spell @@ -76,7 +76,7 @@ class spEditStringOffset final : public Spell QIcon icon() const { if ( !txt_xpm_icon ) - txt_xpm_icon = new QIcon( QPixmap( txt_xpm ) ); + txt_xpm_icon = QIconPtr( new QIcon(QPixmap( txt_xpm )) ); return *txt_xpm_icon; } @@ -305,15 +305,13 @@ class spEditStringEntries final : public Spell // a single palette could be share by multiple NiSequences QPersistentModelIndex iPalette = nif->getBlock( nif->getLink( index, "String Palette" ) ); -#ifndef QT_NO_DEBUG - qWarning() << "This block uses " << iPalette; -#endif + qDebug() << "This block uses " << iPalette; if ( !iPalette.isValid() ) { iPalette = nif->getBlock( nif->getLink( index.parent(), "String Palette" ) ); if ( !iPalette.isValid() ) { - qWarning() << Spell::tr( "Cannot find string palette" ); + qCWarning( nsSpell ) << Spell::tr( "Cannot find string palette" ); return QModelIndex(); } } @@ -348,7 +346,7 @@ class spEditStringEntries final : public Spell // get replaced entries QStringList newEntries = sprd->getStringList(); - //qWarning() << newEntries; + //qDebug() << newEntries; // rebuild palette bytes.clear(); @@ -381,9 +379,7 @@ class spEditStringEntries final : public Spell } } -#ifndef QT_NO_DEBUG - qWarning() << "Found sequences " << sequenceList; -#endif + qDebug() << "Found sequences " << sequenceList; // find their string palettes QList sequenceUpdateList; @@ -393,9 +389,9 @@ class spEditStringEntries final : public Spell QPersistentModelIndex temp = sequenceListIterator.next(); QPersistentModelIndex tempPalette = nif->getBlock( nif->getLink( temp, "String Palette" ) ); - //qWarning() << "Sequence " << temp << " uses " << tempPalette; + //qDebug() << "Sequence " << temp << " uses " << tempPalette; if ( iPalette == tempPalette ) { - //qWarning() << "Identical to this sequence palette!"; + //qDebug() << "Identical to this sequence palette!"; sequenceUpdateList.append( temp ); } } @@ -406,7 +402,7 @@ class spEditStringEntries final : public Spell while ( sequenceUpdateIterator.hasNext() ) { QPersistentModelIndex nextBlock = sequenceUpdateIterator.next(); - //qWarning() << "Need to update " << nextBlock; + //qDebug() << "Need to update " << nextBlock; QPersistentModelIndex blocks = nif->getIndex( nextBlock, "Controlled Blocks" ); @@ -418,13 +414,12 @@ class spEditStringEntries final : public Spell // we shouldn't ever exceed the limit of an int, even though the type // is properly a uint int oldValue = nif->get( thisBlock.child( j, 0 ) ); -#ifndef QT_NO_DEBUG - qWarning() << "Index " << thisBlock.child( j, 0 ) + qDebug() << "Index " << thisBlock.child( j, 0 ) << " is a string offset with name " << nif->itemName( thisBlock.child( j, 0 ) ) << " and value " << nif->get( thisBlock.child( j, 0 ) ); -#endif + if ( oldValue != -1 ) { int newValue = offsetMap.value( oldValue ); @@ -439,8 +434,7 @@ class spEditStringEntries final : public Spell // update the palette itself nif->set( iPalette, "Palette", bytes ); - QMessageBox::information( 0, "NifSkope", - Spell::tr( "Updated %1 offsets in %2 sequences" ).arg( numRefsUpdated ).arg( sequenceUpdateList.size() ) ); + Message::info( nullptr, Spell::tr( "Updated %1 offsets in %2 sequences" ).arg( numRefsUpdated ).arg( sequenceUpdateList.size() ) ); return index; } diff --git a/src/spells/strippify.cpp b/src/spells/strippify.cpp index ee2321ee6..bc7c0b5bf 100644 --- a/src/spells/strippify.cpp +++ b/src/spells/strippify.cpp @@ -58,7 +58,7 @@ class spStrippify final : public Spell skip++; } - //qWarning() << "num triangles" << triangles.count() << "skipped" << skip; + //qDebug() << "num triangles" << triangles.count() << "skipped" << skip; QList > strips = stripify( triangles ); @@ -87,6 +87,7 @@ class spStrippify final : public Spell copyValue( nif, iStripData, iData, "Has UV" ); copyValue( nif, iStripData, iData, "Num UV Sets" ); + copyValue( nif, iStripData, iData, "Vector Flags" ); copyValue( nif, iStripData, iData, "BS Num UV Sets" ); copyValue( nif, iStripData, iData, "Num UV Sets 2" ); QModelIndex iDstUV = nif->getIndex( iStripData, "UV Sets" ); @@ -243,6 +244,7 @@ class spTriangulate final : public Spell copyValue( nif, iTriData, iStripData, "Has UV" ); copyValue( nif, iTriData, iStripData, "Num UV Sets" ); copyValue( nif, iTriData, iStripData, "BS Num UV Sets" ); + copyValue( nif, iTriData, iStripData, "Vector Flags" ); copyValue( nif, iTriData, iStripData, "Num UV Sets 2" ); QModelIndex iDstUV = nif->getIndex( iTriData, "UV Sets" ); QModelIndex iSrcUV = nif->getIndex( iStripData, "UV Sets" ); @@ -293,6 +295,38 @@ class spTriangulate final : public Spell REGISTER_SPELL( spTriangulate ) +class spTriangulateAll final : public Spell +{ +public: + QString name() const override final { return Spell::tr( "Triangulate All Strips" ); } + QString page() const override final { return Spell::tr( "Batch" ); } + + bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final + { + return !index.isValid(); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & ) override final + { + QList triStrips; + + for ( int l = 0; l < nif->getBlockCount(); l++ ) { + QModelIndex idx = nif->getBlock( l, "NiTriStrips" ); + if ( idx.isValid() ) + triStrips << idx; + } + + spTriangulate tri; + for ( const QModelIndex& idx : triStrips ) + tri.castIfApplicable( nif, idx ); + + return QModelIndex(); + } +}; + +REGISTER_SPELL( spTriangulateAll ) + + class spStichStrips final : public Spell { public: diff --git a/src/spells/tangentspace.cpp b/src/spells/tangentspace.cpp index 13872a8e7..efcaa7789 100644 --- a/src/spells/tangentspace.cpp +++ b/src/spells/tangentspace.cpp @@ -7,6 +7,12 @@ bool spTangentSpace::isApplicable( const NifModel * nif, const QModelIndex & ind { QModelIndex iData = nif->getBlock( nif->getLink( index, "Data" ) ); + if ( nif->isNiBlock( index, "BSTriShape" ) || nif->isNiBlock( index, "BSSubIndexTriShape" ) + || nif->isNiBlock( index, "BSMeshLODTriShape" ) ) { + // TODO: Check vertex flags to verify mesh has normals and space for tangents/bitangents + return true; + } + if ( !( nif->isNiBlock( index, "NiTriShape" ) && nif->isNiBlock( iData, "NiTriShapeData" ) ) && !( nif->isNiBlock( index, "BSLODTriShape" ) && nif->isNiBlock( iData, "NiTriShapeData" ) ) && !( nif->isNiBlock( index, "NiTriStrips" ) && nif->isNiBlock( iData, "NiTriStripsData" ) ) ) @@ -36,21 +42,44 @@ QModelIndex spTangentSpace::cast( NifModel * nif, const QModelIndex & iBlock ) { QPersistentModelIndex iShape = iBlock; - QModelIndex iData = nif->getBlock( nif->getLink( iShape, "Data" ) ); + QModelIndex iData; + if ( nif->getUserVersion2() < 130 ) + iData = nif->getBlock( nif->getLink( iShape, "Data" ) ); + else + iData = nif->getIndex( iShape, "Vertex Data" ); + + QVector verts; + QVector norms; + QVector texco; + + if ( nif->getUserVersion2() < 130 ) { + verts = nif->getArray( iData, "Vertices" ); + norms = nif->getArray( iData, "Normals" ); + } else { + int numVerts = nif->get( iShape, "Num Vertices" ); - QVector verts = nif->getArray( iData, "Vertices" ); - QVector norms = nif->getArray( iData, "Normals" ); + for ( int i = 0; i < numVerts; i++ ) { + auto idx = nif->index( i, 0, iData ); + verts += nif->get( idx, "Vertex" ); + norms += nif->get( idx, "Normal" );; + texco += nif->get( idx, "UV" ); + } + } QVector vxcol = nif->getArray( iData, "Vertex Colors" ); int numUVSets = nif->get( iData, "Num UV Sets" ); int tspaceFlags = nif->get( iData, "TSpace Flag" ); - QModelIndex iTexCo = nif->getIndex( iData, "UV Sets" ); - if ( !iTexCo.isValid() ) - iTexCo = nif->getIndex( iData, "UV Sets 2" ); + if ( nif->getUserVersion2() < 130 ) { + QModelIndex iTexCo = nif->getIndex( iData, "UV Sets" ); + + if ( !iTexCo.isValid() ) + iTexCo = nif->getIndex( iData, "UV Sets 2" ); + + iTexCo = iTexCo.child( 0, 0 ); + texco = nif->getArray( iTexCo ); + } - iTexCo = iTexCo.child( 0, 0 ); - QVector texco = nif->getArray( iTexCo ); QVector triangles; QModelIndex iPoints = nif->getIndex( iData, "Points" ); @@ -62,12 +91,21 @@ QModelIndex spTangentSpace::cast( NifModel * nif, const QModelIndex & iBlock ) strips.append( nif->getArray( iPoints.child( r, 0 ) ) ); triangles = triangulate( strips ); - } else { + } else if ( nif->getUserVersion2() < 130 ) { triangles = nif->getArray( iData, "Triangles" ); + } else if ( nif->getUserVersion2() == 130 ) { + triangles = nif->getArray( iShape, "Triangles" ); } if ( verts.isEmpty() || norms.count() != verts.count() || texco.count() != verts.count() || triangles.isEmpty() ) { - qWarning() << Spell::tr( "need vertices, normals, texture coordinates and faces to calculate tangents and bitangents" ); + Message::append( tr( "Update Tangent Spaces failed on one or more blocks." ), + tr( "Block %1: Insufficient information to calculate tangents and bitangents. V: %2, N: %3, Tex: %4, Tris: %5" ) + .arg( nif->getBlockNumber( iBlock ) ) + .arg( verts.count() ) + .arg( norms.count() ) + .arg( texco.count() ) + .arg( triangles.count() ) + ); return iBlock; } @@ -108,7 +146,7 @@ QModelIndex spTangentSpace::cast( NifModel * nif, const QModelIndex & iBlock ) if ( fabs( r ) <= 10e-10 ) { //if ( skptricnt++ < 3 ) - // qWarning() << t; + // qDebug() << t; continue; } @@ -142,7 +180,7 @@ QModelIndex spTangentSpace::cast( NifModel * nif, const QModelIndex & iBlock ) } } - //qWarning() << "skipped triangles" << skptricnt; + //qDebug() << "skipped triangles" << skptricnt; //int cnt = 0; @@ -159,7 +197,7 @@ QModelIndex spTangentSpace::cast( NifModel * nif, const QModelIndex & iBlock ) t[0] = n[1]; t[1] = n[2]; t[2] = n[0]; b = Vector3::crossproduct( n, t ); //if ( cnt++ < 3 ) - // qWarning() << i; + // qDebug() << i; } else { t.normalize(); t = ( t - n * Vector3::dotproduct( n, t ) ); @@ -177,7 +215,7 @@ QModelIndex spTangentSpace::cast( NifModel * nif, const QModelIndex & iBlock ) //qDebug() << ""; } - //qWarning() << "unassigned vertices" << cnt; + //qDebug() << "unassigned vertices" << cnt; bool isOblivion = false; @@ -210,7 +248,7 @@ QModelIndex spTangentSpace::cast( NifModel * nif, const QModelIndex & iBlock ) } nif->set( iTSpace, "Binary Data", QByteArray( (const char *)tan.data(), tan.count() * sizeof( Vector3 ) ) + QByteArray( (const char *)bin.data(), bin.count() * sizeof( Vector3 ) ) ); - } else { + } else if ( nif->getUserVersion2() < 130 ) { if ( tspaceFlags == 0 ) tspaceFlags = 0x10; @@ -222,6 +260,21 @@ QModelIndex spTangentSpace::cast( NifModel * nif, const QModelIndex & iBlock ) nif->updateArray( iTangents ); nif->setArray( iBinorms, bin ); nif->setArray( iTangents, tan ); + } else if ( nif->getUserVersion2() == 130 ) { + + int numVerts = nif->get( iShape, "Num Vertices" ); + for ( int i = 0; i < numVerts; i++ ) { + auto idx = nif->index( i, 0, iData ); + + nif->set( idx, "Tangent", tan[i] ); + nif->set( idx, "Bitangent X", bin[i][0] ); + + auto bitYi = round( ((bin[i][1] + 1.0) / 2.0) * 255.0 ); + auto bitZi = round( ((bin[i][2] + 1.0) / 2.0) * 255.0 ); + + nif->set( idx, "Bitangent Y", bitYi ); + nif->set( idx, "Bitangent Z", bitZi ); + } } return iShape; @@ -274,3 +327,51 @@ class spAllTangentSpaces final : public Spell REGISTER_SPELL( spAllTangentSpaces ) + +class spAddAllTangentSpaces final : public Spell +{ +public: + QString name() const override final { return Spell::tr( "Add Tangent Spaces and Update" ); } + QString page() const override final { return Spell::tr( "Batch" ); } + + bool isApplicable( const NifModel * nif, const QModelIndex & idx ) override final + { + return nif && !idx.isValid() && nif->checkVersion( 0x0A010000, 0 ); + } + + QModelIndex cast( NifModel * nif, const QModelIndex & ) override final + { + QVector blks; + for ( int l = 0; l < nif->getBlockCount(); l++ ) { + QModelIndex idx = nif->getBlock( l, "NiTriShape" ); + if ( !idx.isValid() ) + continue; + + // NiTriShapeData + auto iData = nif->getBlock( nif->getLink( idx, "Data" ) ); + + // Do not do anything without proper UV/Vert/Tri data + auto numVerts = nif->get( iData, "Num Vertices" ); + auto numTris = nif->get( iData, "Num Triangles" ); + bool hasUVs = nif->get( iData, "Vector Flags" ) & 1; + if ( !hasUVs || !numVerts || !numTris ) + continue; + + nif->set( iData, "Vector Flags", 4097 ); + nif->updateArray( iData, "Tangents" ); + nif->updateArray( iData, "Bitangents" ); + + // Add NiTriShape for spTangentSpace + blks << idx; + } + + spTangentSpace update; + for ( auto& b : blks ) + update.cast( nif, b ); + + return QModelIndex(); + } +}; + +REGISTER_SPELL( spAddAllTangentSpaces ) + diff --git a/src/spells/texture.cpp b/src/spells/texture.cpp index f5368a507..ff7ff2360 100644 --- a/src/spells/texture.cpp +++ b/src/spells/texture.cpp @@ -126,15 +126,15 @@ static char const * tex42_xpm[] = { " " }; -QIcon * tex42_xpm_icon = nullptr; +static QIconPtr tex42_xpm_icon = nullptr; //! Find the reference to shape data from a model and index QModelIndex getData( const NifModel * nif, const QModelIndex & index ) { - if ( nif->isNiBlock( index, "NiTriShape" ) || nif->isNiBlock( index, "NiTriStrips" ) || nif->isNiBlock( index, "BSLODTriShape" ) ) + if ( nif->isNiBlock( index, { "NiTriShape", "NiTriStrips", "BSLODTriShape" } ) ) return nif->getBlock( nif->getLink( index, "Data" ) ); - if ( nif->isNiBlock( index, "NiTriShapeData" ) || nif->isNiBlock( index, "NiTriStripsData" ) ) + if ( nif->isNiBlock( index, { "NiTriShapeData", "NiTriStripsData" } ) ) return index; return QModelIndex(); @@ -163,7 +163,7 @@ class spChooseTexture final : public Spell QIcon icon() const { if ( !tex42_xpm_icon ) - tex42_xpm_icon = new QIcon( QPixmap( tex42_xpm ) ); + tex42_xpm_icon = QIconPtr( new QIcon(QPixmap( tex42_xpm )) ); return *tex42_xpm_icon; } @@ -172,7 +172,7 @@ class spChooseTexture final : public Spell { QModelIndex iBlock = nif->getBlock( idx ); - if ( ( nif->isNiBlock( iBlock, "NiSourceTexture" ) || nif->isNiBlock( iBlock, "NiImage" ) ) + if ( nif->isNiBlock( iBlock, { "NiSourceTexture", "NiImage" } ) && ( iBlock == idx.sibling( idx.row(), 0 ) || nif->itemName( idx ) == "File Name" ) ) return true; else if ( nif->isNiBlock( iBlock, "BSShaderNoLightingProperty" ) && nif->itemName( idx ) == "File Name" ) @@ -193,7 +193,7 @@ class spChooseTexture final : public Spell QModelIndex iFile; bool setExternal = false; - if ( ( nif->isNiBlock( iBlock, "NiSourceTexture" ) || nif->isNiBlock( iBlock, "NiImage" ) ) + if ( nif->isNiBlock( iBlock, { "NiSourceTexture", "NiImage" } ) && ( iBlock == idx.sibling( idx.row(), 0 ) || nif->itemName( idx ) == "File Name" ) ) { iFile = nif->getIndex( iBlock, "File Name" ); @@ -262,10 +262,7 @@ class spEditTexCoords final : public Spell bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final { - return ( nif->itemName( index ) == "NiTriShape" || nif->itemName( index ) == "NiTriStrips" || nif->itemName( index ) == "BSLODTriShape" ); - - //QModelIndex iUVs = getUV( nif, index ); - //return iUVs.isValid() && nif->rowCount( iUVs ) >= 1; + return nif->inherits( index, "NiTriBasedGeom" ) || nif->inherits( index, "BSTriShape" ); } QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final @@ -832,7 +829,7 @@ class spTexInfo final : public Spell tex->bind( index ); } - qWarning() << tex->info( index ); + qDebug() << tex->info( index ); return QModelIndex(); } }; @@ -950,14 +947,14 @@ class spEmbedTexture final : public Spell tex->setNifFolder( nif->getFolder() ); if ( tex->bind( index ) ) { - //qWarning() << "spEmbedTexture: Embedding texture " << index; + //qDebug() << "spEmbedTexture: Embedding texture " << index; int blockNum = nif->getBlockNumber( index ); nif->insertNiBlock( "NiPixelData", blockNum + 1 ); QPersistentModelIndex iSourceTexture = nif->getBlock( blockNum, "NiSourceTexture" ); QModelIndex iPixelData = nif->getBlock( blockNum + 1, "NiPixelData" ); - //qWarning() << "spEmbedTexture: Block number" << blockNum << "holds source" << iSourceTexture << "Pixel data will be stored in" << iPixelData; + //qDebug() << "spEmbedTexture: Block number" << blockNum << "holds source" << iSourceTexture << "Pixel data will be stored in" << iPixelData; // finish writing this function if ( tex->importFile( nif, iSourceTexture, iPixelData ) ) { @@ -973,7 +970,7 @@ class spEmbedTexture final : public Spell nif->set( index, "Name", tempFileName ); } } else { - qWarning() << "Could not save texture"; + qCWarning( nsSpell ) << tr( "Could not save texture." ); // delete block? /* nif->removeNiBlock( blockNum+1 ); @@ -1100,7 +1097,7 @@ void TexFlipDialog::listFromNif() QModelIndex sources = nif->getIndex( baseIndex, "Sources" ); if ( nif->rowCount( sources ) != numSources ) { - qWarning() << "Number of sources does not match!"; + qCWarning( nsSpell ) << tr( "'Num Sources' does not match!" ); return; } @@ -1148,13 +1145,15 @@ class spEditFlipper final : public Spell // TODO: use a map here to delete missing textures and preserve existing properties QModelIndex sources = nif->getIndex( flipController, "Sources" ); + int size = flipNames.size(); - if ( nif->get( flipController, "Num Sources" ) > flipNames.size() ) { + if ( nif->get( flipController, "Num Sources" ) > size ) { // delete blocks - qWarning() << "Found" << flipNames.size() << "textures, have" << nif->get( flipController, "Num Sources" ); - - for ( int i = flipNames.size(); i < nif->get( flipController, "Num Sources" ); i++ ) { - qWarning() << "Deleting" << nif->getLink( sources.child( i, 0 ) ); + int num = nif->get( flipController, "Num Sources" ); + for ( int i = size; i < num; i++ ) { + Message::append( tr( "Found %1 textures, have %2" ).arg( size ).arg( num ), + tr( "Deleting %1" ).arg( nif->getLink( sources.child( i, 0 ) ) ), QMessageBox::Information + ); nif->removeNiBlock( nif->getLink( sources.child( i, 0 ) ) ); } } diff --git a/src/spells/transform.cpp b/src/spells/transform.cpp index f2760326f..3ca710035 100644 --- a/src/spells/transform.cpp +++ b/src/spells/transform.cpp @@ -283,7 +283,7 @@ class spPasteTransformation final : public Spell REGISTER_SPELL( spPasteTransformation ) -QIcon * transform_xpm_icon = 0; +static QIconPtr transform_xpm_icon = nullptr; class spEditTransformation final : public Spell { @@ -294,7 +294,7 @@ class spEditTransformation final : public Spell QIcon icon() const { if ( !transform_xpm_icon ) - transform_xpm_icon = new QIcon( QPixmap( transform_xpm ) ); + transform_xpm_icon = QIconPtr( new QIcon(QPixmap( transform_xpm )) ); return *transform_xpm_icon; } diff --git a/src/ui/about_dialog.cpp b/src/ui/about_dialog.cpp index b971904d6..5cd2ccff7 100644 --- a/src/ui/about_dialog.cpp +++ b/src/ui/about_dialog.cpp @@ -9,30 +9,37 @@ AboutDialog::AboutDialog( QWidget * parent ) #else this->setWindowTitle( tr( "About NifSkope %1" ).arg( NIFSKOPE_VERSION ) ); #endif - QString text = tr( - "

    NifSkope is a tool for analyzing and editing NetImmerse/Gamebryo '.nif' files.

    " - "

    NifSkope is based on NifTool's XML file format specification. " - "For more information visit our site at http://niftools.sourceforge.net

    " - "

    NifSkope is free software available under a BSD license. " - "The source is available via git " - "(clone) on SourceForge. " - "Instructions on compiling NifSkope are available on the NifTools wiki.

    " - "

    The most recent version of NifSkope can always be downloaded from the " - "NifTools SourceForge Project page.

    " -// only the windows build uses havok -// (Q_OS_WIN32 is also defined on win64) -#ifdef Q_OS_WIN32 - "
    " - "

    NifSkope uses Havok(R) for the generation of mopp code. " - "(C)Copyright 1999-2008 Havok.com Inc. (and its Licensors). " - "All Rights Reserved. " - "See www.havok.com for details.

    " -#endif - "
    " - "

    NifSkope uses Qhull for the generation of convex hulls. " - "Copyright(c) 1993-2010 C.B. Barber and The Geometry Center. " - "Qhull is free software and may be obtained from www.qhull.org. " - "See Qhull_COPYING.txt for details." - ); + QString text = tr( R"rhtml( +

    NifSkope is a tool for opening and editing the NetImmerse file format (NIF).

    + +

    NifSkope is free software available under a BSD license. + The source is available via GitHub

    + +

    For more information visit our forum.
    + To receive support for NifSkope please use the + NifSkope Help subforum.

    + +

    The most recent stable version of NifSkope can be downloaded from the + official GitHub release page.

    + +

    A detailed changelog and the latest developmental builds of NifSkope + can be found here.

    + +

    For the decompression of BSA (Version 105) files, NifSkope uses LZ4:
    + LZ4 Library
    + Copyright (c) 2011-2015, Yann Collet
    + All rights reserved.

    + +

    For the generation of mopp code on Windows builds, NifSkope uses Havok(R):
    + Copyright (c) 1999-2008 Havok.com Inc. (and its Licensors).
    + All Rights Reserved.

    + +

    For the generation of convex hulls, NifSkope uses Qhull:
    + Copyright (c) 1993-2015 C.B. Barber and The Geometry Center.
    + Qhull is free software and may be obtained via http from www.qhull.org or GitHub. + See Qhull_COPYING.txt for details.

    + + )rhtml" ); + ui.text->setText( text ); } diff --git a/src/ui/about_dialog.ui b/src/ui/about_dialog.ui index e83d0a743..9514f5d3a 100644 --- a/src/ui/about_dialog.ui +++ b/src/ui/about_dialog.ui @@ -72,7 +72,7 @@ - :/res/nifskope.png + :/res/nifskope.png @@ -133,7 +133,7 @@ - + diff --git a/src/ui/checkablemessagebox.cpp b/src/ui/checkablemessagebox.cpp new file mode 100644 index 000000000..c8698eb5d --- /dev/null +++ b/src/ui/checkablemessagebox.cpp @@ -0,0 +1,147 @@ + +#include "checkablemessagebox.h" +#include "ui_checkablemessagebox.h" + +#include + + +struct CheckableMessageBoxPrivate { + CheckableMessageBoxPrivate() : clickedButton(0) {} + + Ui::CheckableMessageBox ui; + QAbstractButton * clickedButton; +}; + +CheckableMessageBox::CheckableMessageBox( QWidget * parent ) : QDialog( parent ), + m_d( new CheckableMessageBoxPrivate ) +{ + setModal( true ); + setWindowFlags( windowFlags() & ~Qt::WindowContextHelpButtonHint ); + m_d->ui.setupUi( this ); + m_d->ui.pixmapLabel->setVisible( false ); + connect( m_d->ui.buttonBox, &QDialogButtonBox::accepted, this, &CheckableMessageBox::accept ); + connect( m_d->ui.buttonBox, &QDialogButtonBox::rejected, this, &CheckableMessageBox::reject ); + connect( m_d->ui.buttonBox, &QDialogButtonBox::clicked, this, &CheckableMessageBox::slotClicked ); +} + +CheckableMessageBox::~CheckableMessageBox() +{ + delete m_d; +} + +void CheckableMessageBox::slotClicked( QAbstractButton * b ) +{ + m_d->clickedButton = b; +} + +QAbstractButton *CheckableMessageBox::clickedButton() const +{ + return m_d->clickedButton; +} + +QDialogButtonBox::StandardButton CheckableMessageBox::clickedStandardButton() const +{ + if ( m_d->clickedButton ) + return m_d->ui.buttonBox->standardButton( m_d->clickedButton ); + return QDialogButtonBox::NoButton; +} + +QString CheckableMessageBox::text() const +{ + return m_d->ui.messageLabel->text(); +} + +void CheckableMessageBox::setText( const QString & t ) +{ + m_d->ui.messageLabel->setText( t ); +} + +QPixmap CheckableMessageBox::iconPixmap() const +{ + if ( const QPixmap * p = m_d->ui.pixmapLabel->pixmap() ) + return QPixmap( *p ); + return QPixmap(); +} + +void CheckableMessageBox::setIconPixmap( const QPixmap & p ) +{ + m_d->ui.pixmapLabel->setPixmap( p ); + m_d->ui.pixmapLabel->setVisible( !p.isNull() ); +} + +bool CheckableMessageBox::isChecked() const +{ + return m_d->ui.checkBox->isChecked(); +} + +void CheckableMessageBox::setChecked( bool s ) +{ + m_d->ui.checkBox->setChecked( s ); +} + +QString CheckableMessageBox::checkBoxText() const +{ + return m_d->ui.checkBox->text(); +} + +void CheckableMessageBox::setCheckBoxText( const QString & t ) +{ + m_d->ui.checkBox->setText( t ); +} + +QDialogButtonBox::StandardButtons CheckableMessageBox::standardButtons() const +{ + return m_d->ui.buttonBox->standardButtons(); +} + +void CheckableMessageBox::setStandardButtons( QDialogButtonBox::StandardButtons s ) +{ + m_d->ui.buttonBox->setStandardButtons( s ); +} + +QDialogButtonBox::StandardButton CheckableMessageBox::defaultButton() const +{ + for ( QAbstractButton * b : m_d->ui.buttonBox->buttons() ) { + if ( QPushButton * pb = qobject_cast(b) ) { + if ( pb->isDefault() ) + return m_d->ui.buttonBox->standardButton( pb ); + } + } + + return QDialogButtonBox::NoButton; +} + +void CheckableMessageBox::setDefaultButton( QDialogButtonBox::StandardButton s ) +{ + if ( QPushButton * b = m_d->ui.buttonBox->button( s ) ) { + b->setDefault( true ); + b->setFocus(); + } +} + +QDialogButtonBox::StandardButton + CheckableMessageBox::question( QWidget * parent, + const QString & title, + const QString & question, + const QString & checkBoxText, + bool * checkBoxSetting, + QDialogButtonBox::StandardButtons buttons, + QDialogButtonBox::StandardButton defaultButton ) +{ + CheckableMessageBox mb( parent ); + mb.setWindowTitle( title ); + mb.setIconPixmap( QMessageBox::standardIcon( QMessageBox::Question ) ); + mb.setText( question ); + mb.setCheckBoxText( checkBoxText ); + mb.setChecked( *checkBoxSetting ); + mb.setStandardButtons( buttons ); + mb.setDefaultButton( defaultButton ); + mb.exec(); + *checkBoxSetting = mb.isChecked(); + return mb.clickedStandardButton(); +} + +QMessageBox::StandardButton CheckableMessageBox::dialogButtonBoxToMessageBoxButton( QDialogButtonBox::StandardButton db ) +{ + return static_cast( int(db) ); +} diff --git a/src/ui/checkablemessagebox.h b/src/ui/checkablemessagebox.h new file mode 100644 index 000000000..a47fd5095 --- /dev/null +++ b/src/ui/checkablemessagebox.h @@ -0,0 +1,67 @@ +#ifndef CHECKABLEMESSAGEBOX_H +#define CHECKABLEMESSAGEBOX_H + + +#include +#include +#include + + +struct CheckableMessageBoxPrivate; + +class CheckableMessageBox : public QDialog +{ + Q_OBJECT + Q_PROPERTY(QString text READ text WRITE setText) + Q_PROPERTY(QPixmap iconPixmap READ iconPixmap WRITE setIconPixmap) + Q_PROPERTY(bool isChecked READ isChecked WRITE setChecked) + Q_PROPERTY(QString checkBoxText READ checkBoxText WRITE setCheckBoxText) + Q_PROPERTY(QDialogButtonBox::StandardButtons buttons READ standardButtons WRITE setStandardButtons) + Q_PROPERTY(QDialogButtonBox::StandardButton defaultButton READ defaultButton WRITE setDefaultButton) +public: + explicit CheckableMessageBox( QWidget * parent ); + virtual ~CheckableMessageBox(); + + static QDialogButtonBox::StandardButton + question( QWidget * parent, + const QString & title, + const QString & question, + const QString & checkBoxText, + bool * checkBoxSetting, + QDialogButtonBox::StandardButtons buttons = QDialogButtonBox::Yes|QDialogButtonBox::No, + QDialogButtonBox::StandardButton defaultButton = QDialogButtonBox::No ); + + QString text() const; + void setText( const QString & ); + + bool isChecked() const; + void setChecked( bool s ); + + QString checkBoxText() const; + void setCheckBoxText( const QString & ); + + QDialogButtonBox::StandardButtons standardButtons() const; + void setStandardButtons( QDialogButtonBox::StandardButtons s ); + + QDialogButtonBox::StandardButton defaultButton() const; + void setDefaultButton( QDialogButtonBox::StandardButton s ); + + QPixmap iconPixmap() const; + void setIconPixmap ( const QPixmap & p ); + + // Query the result + QAbstractButton * clickedButton() const; + QDialogButtonBox::StandardButton clickedStandardButton() const; + + // Conversion convenience + static QMessageBox::StandardButton dialogButtonBoxToMessageBoxButton( QDialogButtonBox::StandardButton ); + +private slots: + void slotClicked( QAbstractButton * b ); + +private: + CheckableMessageBoxPrivate * m_d; +}; + + +#endif // CHECKABLEMESSAGEBOX_H diff --git a/src/ui/checkablemessagebox.ui b/src/ui/checkablemessagebox.ui new file mode 100644 index 000000000..4a6db706b --- /dev/null +++ b/src/ui/checkablemessagebox.ui @@ -0,0 +1,154 @@ + + + CheckableMessageBox + + + + 0 + 0 + 195 + 107 + + + + Dialog + + + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::MinimumExpanding + + + + 0 + 5 + + + + + + + + + + TextLabel + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + CheckBox + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Minimum + + + + 0 + 5 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + true + + + + + + + + + buttonBox + accepted() + CheckableMessageBox + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + CheckableMessageBox + reject() + + + 316 + 260 + + + 286 + 274 + + + + + \ No newline at end of file diff --git a/src/ui/nifskope.ui b/src/ui/nifskope.ui new file mode 100644 index 000000000..490350fe0 --- /dev/null +++ b/src/ui/nifskope.ui @@ -0,0 +1,2596 @@ + + + MainWindow + + + + 0 + 0 + 1280 + 960 + + + + + false + + + + + + 0 + 0 + + + + + 0 + 24 + + + + + 16777215 + 24 + + + + + 10 + + + + false + + + #statusbar { +padding: 0 0 0 0; +margin: 0 0 0 0; +border: 0px solid transparent; +/*background: transparent; +color: #0099ff; +background: #0099ff; +color: white;*/ +color: white; +background: qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 rgba(67, 67, 67, 255), stop:0.039823 rgba(85, 85, 85, 255), stop:0.103245 rgba(102, 102, 102, 255), stop:0.171091 rgba(106, 106, 106, 255)); +} + +#statusbar QProgressBar { +margin: 0px; +padding: 0px; +border: 0px solid transparent; +text-align: center; +color: white; +font: 12px normal "Segoe UI"; +background: transparent; +} + +QProgressBar::chunk { +background-color: #05B8CC; +width: 20px; +} + +QProgressBar::text { +padding-bottom: 2px; +margin-top: -2px; +} + +#statusbar QSizeGrip { +background: url(":/img/sizeGrip") no-repeat; +width: 10px; height: 10px; +} + +#statusbar::text { +padding: 0 0 10px 0; +margin: -3px 0 0 0; + +} + +#statusbar::item { +border: 0px solid transparent; +} +#statusbar #filepathStatusbarWidget QLabel { +/*color: #333333;*/ +color: white; +padding: 0 0 1px 0; +margin: -1px 0 0 0; +font: 12px normal "Segoe UI"; +} + +#statusbar QPushButton { +padding: 0px; border: none; margin: 0px; +} + +#statusbar #filepathStatusbarWidget QPushButton:pressed { +margin-top: 1px; margin-left: 1px; +background: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0.5, stop:0.139963 rgba(255, 255, 255, 255), stop:1 rgba(255, 255, 255, 0)); +} + +#statusbar #filepathStatusbarWidget QPushButton:hover { +background: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0.5, stop:0.139963 rgba(255, 255, 255, 255), stop:1 rgba(255, 255, 255, 0)); +} + + + + + + 0 + 0 + + + + Qt::PreventContextMenu + + + File + + + false + + + Qt::TopToolBarArea + + + + 18 + 18 + + + + false + + + TopToolBarArea + + + false + + + + + + + Render + + + Qt::BottomToolBarArea|Qt::TopToolBarArea + + + + 18 + 18 + + + + TopToolBarArea + + + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + View + + + Qt::BottomToolBarArea|Qt::TopToolBarArea + + + TopToolBarArea + + + false + + + + + + + + + + + false + + + Animation + + + Qt::BottomToolBarArea|Qt::TopToolBarArea + + + + 18 + 18 + + + + TopToolBarArea + + + false + + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 80 + 16777215 + + + + LOD + + + Qt::BottomToolBarArea|Qt::TopToolBarArea + + + + 16 + 16 + + + + TopToolBarArea + + + false + + + + + + + 0 + 0 + 1280 + 21 + + + + + &File + + + + false + + + Import + + + + + true + + + Export + + + + + Recent Files + + + + + + Recent Archives + + + + + + + + + + + + + + + + + + + + + + + + + + + View + + + + &Toolbars + + + + + + Block List + + + + + + + Block Details + + + + + + + Show + + + + + + + + + + + + + + + + + + + Render + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Help + + + + + + + + + + + + + + + Options + + + + + + + + + + + + + true + + + Qt::BottomDockWidgetArea|Qt::LeftDockWidgetArea|Qt::RightDockWidgetArea + + + Inspect + + + 2 + + + + + + + 400 + 240 + + + + Qt::BottomDockWidgetArea|Qt::LeftDockWidgetArea|Qt::RightDockWidgetArea + + + Block List + + + 1 + + + + + 3 + + + 5 + + + 4 + + + 5 + + + 0 + + + + + + 0 + 0 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 20 + 20 + + + + Expand All + + + + + + + :/img/expand:/img/expand + + + + 16 + 16 + + + + true + + + + + + + + 20 + 20 + + + + Collapse All + + + + + + + :/img/collapse:/img/collapse + + + + 16 + 16 + + + + true + + + + + + + + + + Qt::CustomContextMenu + + + QFrame::NoFrame + + + QAbstractScrollArea::AdjustToContentsOnFirstShow + + + true + + + true + + + 100 + + + + + + + + + + 400 + 240 + + + + Qt::BottomDockWidgetArea|Qt::LeftDockWidgetArea|Qt::RightDockWidgetArea + + + Block Details + + + 1 + + + + + 3 + + + 5 + + + 4 + + + 5 + + + 0 + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 20 + 20 + + + + Expand All + + + + + + + + + + :/img/expand:/img/expand + + + + 16 + 16 + + + + true + + + + + + + + 20 + 20 + + + + Collapse All + + + + + + + :/img/collapse:/img/collapse + + + + 16 + 16 + + + + true + + + + + + + + + + Qt::CustomContextMenu + + + QFrame::NoFrame + + + QAbstractScrollArea::AdjustToContentsOnFirstShow + + + true + + + true + + + + + + + + + + 0 + 0 + + + + Qt::BottomDockWidgetArea|Qt::LeftDockWidgetArea|Qt::RightDockWidgetArea + + + Interactive Help + + + 8 + + + + + 0 + + + 3 + + + 2 + + + 3 + + + 0 + + + + + QFrame::NoFrame + + + + + + + + + Qt::BottomDockWidgetArea|Qt::LeftDockWidgetArea|Qt::RightDockWidgetArea + + + KFM + + + 2 + + + + + 3 + + + 5 + + + 4 + + + 5 + + + 0 + + + + + QFrame::NoFrame + + + true + + + true + + + false + + + + + + + + + Qt::BottomDockWidgetArea|Qt::LeftDockWidgetArea|Qt::RightDockWidgetArea + + + Header + + + 8 + + + + + 3 + + + 5 + + + 4 + + + 5 + + + 0 + + + + + Qt::CustomContextMenu + + + QFrame::NoFrame + + + true + + + true + + + + + + + + + Qt::BottomDockWidgetArea|Qt::LeftDockWidgetArea|Qt::RightDockWidgetArea + + + Archive Browser + + + 8 + + + + + 3 + + + 5 + + + 4 + + + 5 + + + 0 + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 6 + + + 3 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + font-size: 14px; + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 3 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + false + + + + + + + false + + + Filename only + + + + + + + + + + QFrame::NoFrame + + + true + + + 16 + + + true + + + true + + + + + + + + + true + + + false + + + Auto Sanitize before Save + + + + + New Window + + + + + Reload XML + + + + + Reload + + + Reload XML && NIF + + + Alt+X + + + + + XML Checker + + + + + Quit + + + + + false + + + Header + + + Header + + + + + true + + + + :/btn/cubeTop16:/btn/cubeTop16 + + + Top + + + Top View + + + Top View + + + T + + + + + true + + + true + + + + :/btn/cubeFront16:/btn/cubeFront16 + + + Front + + + Front View + + + Front View + + + F + + + + + true + + + + :/btn/cubeSide16:/btn/cubeSide16 + + + Left + + + Left View + + + Left View + + + L + + + + + true + + + + :/btn/loadView2 + :/btn/userViewActive:/btn/loadView2 + + + Load View + + + Load View + + + Load View + + + F8 + + + + + true + + + + :/btn/viewWalk:/btn/viewWalk + + + Walk + + + Walk Mode + + + Walk Mode + + + F9 + + + + + + :/btn/viewFlip:/btn/viewFlip + + + Flip + + + Flip View + + + Flip View + + + F11 + + + + + true + + + + :/btn/cubeOrtho + :/btn/cubePersp:/btn/cubeOrtho + + + Perspective + + + Perspective or Orthogonal View + + + Perspective or Orthogonal View + + + P + + + + + + :/btn/saveView2:/btn/saveView2 + + + Save View + + + Save View + + + Save View + + + Ctrl+F9 + + + + + true + + + Show Blocks in Tree + + + + + true + + + Show Blocks in List + + + + + true + + + true + + + Show Non-applicable Rows + + + + + true + + + false + + + Realtime Row Version Updating (slow) + + + + + Select Font... + + + + + true + + + Interactive Help + + + + + true + + + true + + + Block List + + + Block List + + + + + true + + + true + + + true + + + Block Details + + + Block Details + + + + + true + + + KFM + + + KFM + + + + + true + + + Inspect + + + Inspect + + + + + NifSkope Documentation && &Tutorials + + + http://niftools.sourceforge.net/wiki/index.php/NifSkope + + + + + NifSkope Help && Bug Report &Forum + + + http://niftools.sourceforge.net/forum/viewforum.php?f=24 + + + + + NifTools &Wiki + + + http://niftools.sourceforge.net + + + + + NifTools &Downloads + + + http://sourceforge.net/project/showfiles.php?group_id=149157 + + + + + About &NifSkope + + + + + About &Qt + + + + + + :/btn/screenshot:/btn/screenshot + + + Screenshot + + + Screenshot + + + Screenshot + + + + + true + + + Color Key Debug + + + + + true + + + true + + + &Animations + + + Enables Animation + + + + + true + + + false + + + + :/btn/play + :/btn/pause:/btn/play + + + &Play + + + Play Animation + + + Play Animation + + + + + true + + + false + + + + :/btn/repeatSingle:/btn/repeatSingle + + + &Loop + + + Loop Animation + + + Loop Animation + + + + + true + + + false + + + + :/btn/repeatAll:/btn/repeatAll + + + &Switch + + + Switch Animation + + + Switch Animation + + + + + LOD Dummy + + + false + + + + + true + + + + :/btn/showCollision:/btn/showCollision + + + Show Collision + + + Show Collision + + + Show Collision + + + + + true + + + + :/btn/showAxes:/btn/showAxes + + + Show Axes + + + Show Axes + + + Show Axes + + + + + true + + + + :/btn/showNodes:/btn/showNodes + + + Show Nodes + + + Show Nodes + + + Show Nodes + + + + + true + + + + :/btn/showConstraints:/btn/showConstraints + + + Show Constraints + + + Show Constraints + + + Show Constraints + + + + + Center + + + Center Viewport on object + + + Center Viewport on object + + + Z + + + + + Settings... + + + + + + :/btn/load:/btn/load + + + Open... + + + Open... + + + + + false + + + + :/btn/save:/btn/save + + + Save... + + + Save... + + + + + false + + + Save + + + Save File + + + + + false + + + Save As... + + + Save File As + + + + + Open... + + + Open File + + + + + aDummyRecentFiles + + + false + + + + + + :/btn/bulb:/btn/bulb + + + + + + + + + Lighting Options + + + + + true + + + true + + + + :/btn/textures + + + + Textures + + + Show Textures + + + Show Textures + + + Alt+T + + + + + true + + + true + + + + :/btn/vertexColors + + + + Vertex Colors + + + Show Vertex Colors + + + Show Vertex Colors + + + Alt+V + + + + + true + + + true + + + + :/btn/bulbOff + :/btn/bulb:/btn/bulbOff + + + Lighting + + + Lighting + + + Turn Lighting On/Off + + + + + true + + + true + + + Frontal + + + Frontal Lighting + + + Alt+F + + + + + true + + + + :/btn/normals:/btn/normals + + + Lighting Only + + + Lighting Only + + + Alt+L + + + + + true + + + Disable Shading + + + Disable Shading + + + Alt+S + + + + + true + + + + :/btn/silhouette:/btn/silhouette + + + Silhouette + + + Silhouette Visualization + + + Alt+D + + + + + true + + + true + + + + :/btn/specular:/btn/specular + + + Specular + + + Show Specular Highlights + + + Alt+H + + + + + true + + + true + + + + :/btn/glow:/btn/glow + + + Glow + + + Show Glow Mapping + + + Alt+G + + + + + true + + + + :/btn/showMarkers:/btn/showMarkers + + + Show Markers + + + Show Markers + + + Show Markers + + + + + true + + + true + + + Header + + + + + true + + + true + + + + :/btn/cubemap:/btn/cubemap + + + Cube Mapping + + + Cube Mapping + + + Alt+R + + + + + true + + + Bounds Debug + + + + + true + + + Show Grid + + + G + + + + + true + + + + :/btn/hiddenDisabled + :/btn/hidden:/btn/hiddenDisabled + + + Show Hidden + + + Show Hidden + + + + + Browse Archive... + + + + + true + + + true + + + Archive Browser + + + + + Test 1 Debug + + + Alt+, + + + false + + + + + Test 2 Debug + + + Alt+. + + + false + + + + + Test 3 Debug + + + Alt+/ + + + false + + + + + true + + + true + + + + :/btn/selectObject:/btn/selectObject + + + Select Object + + + Select Object + + + + + true + + + + :/btn/selectVerts:/btn/selectVerts + + + Select Vertex + + + Select Vertex + + + + + true + + + + :/btn/skinned + :/btn/skinned:/btn/skinned + + + Do Skinning + + + Do Skinning + + + Do Skinning + + + + + + NifTreeView + QTreeView +
    widgets/nifview.h
    +
    + + ReferenceBrowser + QTextBrowser +
    widgets/refrbrowser.h
    +
    +
    + + + + + + aToggleHelp + triggered(bool) + RefrDock + setVisible(bool) + + + -1 + -1 + + + 908 + 630 + + + + + aToggleBlockList + triggered(bool) + ListDock + setVisible(bool) + + + -1 + -1 + + + 99 + 327 + + + + + aToggleBlockDetails + triggered(bool) + TreeDock + setVisible(bool) + + + -1 + -1 + + + 282 + 743 + + + + + aToggleKfm + triggered(bool) + KfmDock + setVisible(bool) + + + -1 + -1 + + + 885 + 311 + + + + + aToggleInspect + triggered(bool) + InspectDock + setVisible(bool) + + + -1 + -1 + + + 885 + 76 + + + + + InspectDock + visibilityChanged(bool) + aToggleInspect + setChecked(bool) + + + 885 + 76 + + + -1 + -1 + + + + + ListDock + visibilityChanged(bool) + aToggleBlockList + setChecked(bool) + + + 99 + 327 + + + -1 + -1 + + + + + TreeDock + visibilityChanged(bool) + aToggleBlockDetails + setChecked(bool) + + + 282 + 743 + + + -1 + -1 + + + + + RefrDock + visibilityChanged(bool) + aToggleHelp + setChecked(bool) + + + 908 + 630 + + + -1 + -1 + + + + + KfmDock + visibilityChanged(bool) + aToggleKfm + setChecked(bool) + + + 885 + 311 + + + -1 + -1 + + + + + aNifToolsWebsite + triggered() + MainWindow + openURL() + + + -1 + -1 + + + 511 + 383 + + + + + aNifToolsDownloads + triggered() + MainWindow + openURL() + + + -1 + -1 + + + 511 + 383 + + + + + aHelpForum + triggered() + MainWindow + openURL() + + + -1 + -1 + + + 511 + 383 + + + + + aHelpWebsite + triggered() + MainWindow + openURL() + + + -1 + -1 + + + 511 + 383 + + + + + aQuit + triggered() + MainWindow + close() + + + -1 + -1 + + + 511 + 383 + + + + + aToggleKfm + triggered() + KfmDock + raise() + + + -1 + -1 + + + 885 + 349 + + + + + aToggleInspect + triggered() + InspectDock + raise() + + + -1 + -1 + + + 885 + 111 + + + + + aToggleHelp + triggered() + RefrDock + raise() + + + -1 + -1 + + + 908 + 633 + + + + + aToggleBlockList + triggered() + ListDock + raise() + + + -1 + -1 + + + 99 + 330 + + + + + aToggleBlockDetails + triggered() + TreeDock + raise() + + + -1 + -1 + + + 282 + 743 + + + + + bExpandAllTree + clicked() + tree + scrollToTop() + + + 19 + 573 + + + 277 + 743 + + + + + bExpandAllTree + clicked() + tree + expandAll() + + + 19 + 573 + + + 277 + 743 + + + + + bExpandAllList + clicked() + list + expandAll() + + + 19 + 88 + + + 94 + 351 + + + + + bExpandAllList + clicked() + list + scrollToTop() + + + 19 + 88 + + + 94 + 351 + + + + + bCollapseAllList + clicked() + list + collapseAll() + + + 41 + 83 + + + 94 + 332 + + + + + bCollapseAllList + clicked() + list + scrollToTop() + + + 41 + 83 + + + 94 + 332 + + + + + bCollapseAllTree + clicked() + tree + collapseAll() + + + 50 + 573 + + + 277 + 743 + + + + + bCollapseAllTree + clicked() + tree + scrollToTop() + + + 50 + 573 + + + 277 + 743 + + + + + aToggleHeader + triggered(bool) + HeaderDock + setVisible(bool) + + + -1 + -1 + + + 979 + 635 + + + + + aToggleHeader + triggered() + HeaderDock + raise() + + + -1 + -1 + + + 979 + 635 + + + + + aToggleArchiveBrowser + triggered(bool) + BrowserDock + setVisible(bool) + + + -1 + -1 + + + 883 + 634 + + + + + aToggleArchiveBrowser + triggered() + BrowserDock + raise() + + + -1 + -1 + + + 883 + 634 + + + + + BrowserDock + visibilityChanged(bool) + aToggleArchiveBrowser + setChecked(bool) + + + 883 + 634 + + + -1 + -1 + + + + + + openURL() + +
    diff --git a/src/ui/settingsdialog.cpp b/src/ui/settingsdialog.cpp new file mode 100644 index 000000000..177ee5345 --- /dev/null +++ b/src/ui/settingsdialog.cpp @@ -0,0 +1,125 @@ +#include "settingsdialog.h" +#include "ui_settingsdialog.h" + +#include "settings.h" + +#include +#include +#include +#include + + +SettingsDialog::SettingsDialog( QWidget * parent ) : + QDialog( parent ), + ui( new Ui::SettingsDialog ) +{ + ui->setupUi( this ); + + content = ui->content; + categories = ui->categoryList; + + setWindowTitle( tr( "Settings" ) ); + setWindowFlags( Qt::Tool | Qt::WindowStaysOnTopHint ); + installEventFilter( this ); + + content->addWidget( new SettingsGeneral( this ) ); + content->addWidget( new SettingsRender( this ) ); + content->addWidget( new SettingsResources( this ) ); + + categories->setCurrentRow( 0 ); + + btnSave = ui->submit->button( QDialogButtonBox::Save ); + btnSave->setEnabled( false ); + + btnApply = ui->submit->button( QDialogButtonBox::Apply ); + btnApply->setEnabled( false ); + + QSettings settings; + + settingsVersion = settings.value( "Settings/Version" ); + if ( settingsVersion.isNull() ) { + // First time install, save defaults to registry + save(); + settings.setValue( "Settings/Version", 1.0 ); + } + + connect( ui->categoryList, &QListWidget::currentItemChanged, this, &SettingsDialog::changePage ); + connect( ui->submit, &QDialogButtonBox::accepted, this, &SettingsDialog::save ); + connect( ui->submit, &QDialogButtonBox::clicked, [this]( QAbstractButton * btn ) { + if ( btn == btnApply ) + apply(); + } ); + connect( ui->submit, &QDialogButtonBox::rejected, this, &SettingsDialog::cancel ); + connect( ui->restoreAllDefaults, &QPushButton::clicked, this, &SettingsDialog::restoreDefaults ); +} + +SettingsDialog::~SettingsDialog() +{ +} + + +void SettingsDialog::registerPage( QWidget * parent, const QString & text ) +{ + auto p = qobject_cast(parent); + if ( p ) + p->categories->addItem( text ); +} + +void SettingsDialog::apply() +{ + emit saveSettings(); + emit update3D(); + + btnSave->setEnabled( false ); + btnApply->setEnabled( false ); +} + +void SettingsDialog::save() +{ + apply(); + close(); +} + +void SettingsDialog::cancel() +{ + emit loadSettings(); + + close(); + + btnSave->setEnabled( false ); + btnApply->setEnabled( false ); +} + +void SettingsDialog::restoreDefaults() +{ + auto tmpDlg = std::unique_ptr( new SettingsDialog ); + tmpDlg->save(); + + loadSettings(); +} + +void SettingsDialog::modified() +{ + btnSave->setEnabled( true ); + btnApply->setEnabled( true ); +} + +void SettingsDialog::changePage( QListWidgetItem * current, QListWidgetItem * previous ) +{ + if ( !current ) + current = previous; + + content->setCurrentIndex( categories->row( current ) ); +} + +bool SettingsDialog::eventFilter( QObject * o, QEvent * e ) +{ + return QDialog::eventFilter( o, e ); +} + +void SettingsDialog::showEvent( QShowEvent * e ) +{ + emit loadSettings(); + + QDialog::showEvent( e ); +} diff --git a/src/ui/settingsdialog.h b/src/ui/settingsdialog.h new file mode 100644 index 000000000..52e8c6676 --- /dev/null +++ b/src/ui/settingsdialog.h @@ -0,0 +1,60 @@ +#ifndef SETTINGSDIALOG_H +#define SETTINGSDIALOG_H + +#include +#include + +#include + +class QListWidget; +class QListWidgetItem; +class QPushButton; +class QStackedWidget; + +namespace Ui { +class SettingsDialog; +} + + +class SettingsDialog : public QDialog +{ + Q_OBJECT + +public: + explicit SettingsDialog( QWidget * parent = nullptr ); + ~SettingsDialog(); + + + static void registerPage( QWidget *, const QString & ); + + QStackedWidget * content; + QListWidget * categories; + + QVariant settingsVersion; + +public slots: + void save(); + void apply(); + void cancel(); + void changePage( QListWidgetItem * current, QListWidgetItem * previous ); + void restoreDefaults(); + void modified(); + +signals: + void loadSettings(); + void saveSettings(); + void localeChanged(); + void update3D(); + void flush3D(); + +private: + std::unique_ptr ui; + + QPushButton * btnSave; + QPushButton * btnApply; + + bool eventFilter( QObject *, QEvent * ) override final; + void showEvent( QShowEvent * ) override final; +}; + +#endif // SETTINGSDIALOG_H diff --git a/src/ui/settingsdialog.ui b/src/ui/settingsdialog.ui new file mode 100644 index 000000000..f9d9e07da --- /dev/null +++ b/src/ui/settingsdialog.ui @@ -0,0 +1,198 @@ + + + SettingsDialog + + + + 0 + 0 + 882 + 655 + + + + Dialog + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 200 + 16777215 + + + + + Segoe UI Light + 14 + + + + QListWidget { outline: none; } +QListWidget::item { height: 20px; padding: 10px; color: #555555; border: 1px solid transparent; } +QListWidget::item::text { padding-left: 10px; background: transparent; border: 1px solid transparent; } +QListWidget::item::text:focus { border: 1px solid transparent; } +QListWidget::item:focus { border: 1px solid transparent; color: black; } + +QListWidget::item:hover { background: rgba(0, 153, 255, 32); border: 1px solid transparent; } +QListWidget::item:selected { background: rgba(0, 153, 255, 128); border: 1px solid transparent; } + + + QFrame::NoFrame + + + QFrame::Plain + + + true + + + + + + + + Segoe UI Light + 10 + + + + #restoreAllDefaults { background: transparent; border: 1px solid transparent; padding: 5px; } + +#restoreAllDefaults:hover:pressed { background: rgba(255, 255, 255, 255); } +#restoreAllDefaults:hover:!pressed { background: rgba(0, 153, 255, 255); } + + + Restore All Defaults + + + false + + + + + + + + + + + 0 + 0 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 6 + + + 6 + + + 0 + + + 0 + + + + + -1 + + + + + + + QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Save + + + + + + + 0 + + + 1 + + + 1 + + + 1 + + + 1 + + + + + + + + + + + + + + diff --git a/src/ui/settingsgeneral.ui b/src/ui/settingsgeneral.ui new file mode 100644 index 000000000..9828a1f30 --- /dev/null +++ b/src/ui/settingsgeneral.ui @@ -0,0 +1,239 @@ + + + SettingsGeneral + + + + 0 + 0 + 800 + 600 + + + + + 0 + 0 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + Segoe UI Semibold + 12 + 75 + true + + + + General + + + + + + + 0 + + + + User Interface + + + + + + + 0 + 0 + + + + Region && Language + + + true + + + + + + false + + + + 150 + 0 + + + + + + + + + + + Misc + + + true + + + + + + Suppress Save Confirmation + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + NIF + + + + + + + 0 + 0 + + + + Startup Defaults + + + + + + Version + + + version + + + + + + + 20.0.0.5 + + + + + + + User Version + + + userVersion + + + + + + + 11 + + + + + + + User Version 2 + + + userVersion2 + + + + + + + 11 + + + + + + + + + + nif.xml + + + true + + + + + + Hide metadata columns + + + true + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + diff --git a/src/ui/settingsrender.ui b/src/ui/settingsrender.ui new file mode 100644 index 000000000..0dfaeae84 --- /dev/null +++ b/src/ui/settingsrender.ui @@ -0,0 +1,632 @@ + + + SettingsRender + + + + 0 + 0 + 800 + 600 + + + + + 0 + 0 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + Segoe UI Semibold + 12 + 75 + true + + + + Render + + + + + + + + 0 + 0 + + + + 0 + + + + General + + + + + + + 0 + 0 + + + + QFrame::NoFrame + + + QFrame::Plain + + + 0 + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + Rendering + + + true + + + + + + Antialiasing + + + antialiasing + + + + + + + + 60 + 16777215 + + + + 4 + + + + Off + + + + + 2x + + + + + 4x + + + + + 8x + + + + + 16x + + + + + + + + Anisotropic Filtering + + + anisotropicFiltering + + + + + + + + 60 + 16777215 + + + + 4 + + + + 1x + + + + + 2x + + + + + 4x + + + + + 8x + + + + + 16x + + + + + + + + + + + true + + + + + + + Use Shaders + + + useShaders + + + + + + + + + + + 0 + 0 + + + + Model + + + true + + + + + + Up Axis + + + upAxis + + + + + + + 2 + + + + X + + + + + Y + + + + + Z + + + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 0 + 20 + + + + + + + + + 0 + 0 + + + + Camera + + + + + + Field of View + + + fieldOfView + + + + + + + 45 + + + 120 + + + 5 + + + 60 + + + + + + + Rotation Speed + + + rotationSpeed + + + + + + + 15 + + + 1500 + + + 15 + + + 45 + + + + + + + Movement Speed + + + movementSpeed + + + + + + + 0 + + + 7000 + + + 50 + + + 350 + + + + + + + false + + + 1 + + + + +X + + + + + +Y + + + + + +Z + + + + + -X + + + + + -Y + + + + + -Z + + + + + + + + false + + + Startup Direction + + + startupDirection + + + + + + + + + + + 0 + 0 + + + + Startup Defaults + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + Show Axes + + + true + + + + + + + Show Nodes + + + + + + + Show Grid + + + true + + + + + + + Show Collision + + + + + + + Show Hidden + + + + + + + Show Constraints + + + + + + + Show Markers + + + + + + + Do Skinning + + + true + + + + + + + + + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + Colors + + + + + + + 0 + 0 + + + + + + + + + + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + + + + + + + + + + + + + + + + + + ColorWheel + QWidget +
    widgets/colorwheel.h
    + 1 +
    + + ColorLineEdit + QWidget +
    widgets/colorwheel.h
    + 1 +
    +
    + + +
    diff --git a/src/ui/settingsresources.ui b/src/ui/settingsresources.ui new file mode 100644 index 000000000..6159389bc --- /dev/null +++ b/src/ui/settingsresources.ui @@ -0,0 +1,288 @@ + + + SettingsResources + + + + 0 + 0 + 800 + 600 + + + + + 0 + 0 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + Segoe UI Semibold + 12 + 75 + true + + + + Resources + + + + + + + + 0 + 0 + + + + 0 + + + + Paths + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + + + + + + QPushButton { padding: 5px 10px; } + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Add Folder + + + + + + + Remove Folder + + + + + + + Move Up + + + + + + + Move Down + + + + + + + Qt::Vertical + + + + 20 + 20 + + + + + + + + Auto Detect +Game Paths + + + + + + + + + + + + + Load alternate file extensions + + + + + + + + Archives + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + + + + + + QPushButton { padding: 5px 10px; } + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Add Archives + + + + + + + Remove Archive + + + + + + + Move Up + + + + + + + Move Down + + + + + + + Qt::Vertical + + + + 20 + 20 + + + + + + + + Auto Detect +Archives + + + + + + + + + + + + + + + + + + diff --git a/src/version.cpp b/src/version.cpp index 7a87afabf..484695b4e 100644 --- a/src/version.cpp +++ b/src/version.cpp @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2014, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -35,8 +35,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "QDebug" -//! \file version.cpp - +//! @file version.cpp %NifSkope version number management // Number of version parts (Default = 3) int NifSkopeVersion::numParts = 3; diff --git a/src/version.h b/src/version.h index 01937a192..a9cd5acc1 100644 --- a/src/version.h +++ b/src/version.h @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2014, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -39,13 +39,12 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include -/** - * \file version.h - * \brief Encapsulation of application version strings. - * \author jonwd7 - * \date 2014-06-06 +/*! + * @file version.h NifSkopeVersion + * @author jonwd7 + * @date 2014-06-06 * - * \see Github: https://github.com/niftools/nifskope/issues/61 + * @see Github: https://github.com/niftools/nifskope/issues/61 */ @@ -57,10 +56,8 @@ class QDebug; #define NIFSKOPE_VERSION_HEX NifSkopeVersion::hexVersion( NIFSKOPE_VERSION ) -/** - * \class NifSkopeVersion - * \brief Encapsulates application version strings into comparable objects. - * Also provides static convenience functions for raw strings. +/*! Encapsulates application version strings into comparable objects and provides static convenience + * functions for raw strings. * * For comparison purposes, such as for migrating QSettings between versions, * or removing deprecated QSettings. @@ -87,7 +84,7 @@ class NifSkopeVersion final //! Instance version of NifSkopeVersion::hexVersion() int hex() const; - /** \brief Compare two strings beyond MAJ.MIN.REV granularity + /*! Compare two strings beyond MAJ.MIN.REV granularity * * Max of 7 parts: * 0 = Major @@ -98,11 +95,11 @@ class NifSkopeVersion final * 5 = Dev Code (dev, post) * 6 = Dev Version * - * \param parts Number of parts - * \return void + * @param parts Number of parts + * @return void * - * \note This sets NifSkopeVersion::numParts (static) for *all* NifSkopeVersion objects. - * \code + * @note This sets NifSkopeVersion::numParts (static) for *all* NifSkopeVersion objects. + * @code * // Default numParts = 3 * NifSkopeVersion a( "1.2.0" ); * NifSkopeVersion b( "1.2.0a1" ); @@ -112,71 +109,71 @@ class NifSkopeVersion final * NifSkopeVersion::setNumParts( 5 ); * * qDebug() << (a > b); // True - * \endcode + * @endcode */ static void setNumParts( int num ); - /** \brief Integer representation of version string + /*! Integer representation of version string * represented in hex e.g. `"1.2.1" -> 0x010201 -> 66049` */ static int hexVersion( const QString ); static int hexVersion( const QList ); - /** \brief Compare two strings beyond MAJ.MIN.REV granularity + /*! Compare two strings beyond MAJ.MIN.REV granularity * - * \param[in] ver1 Version 1 - * \param[in] ver2 Version 2 - * \param parts Number of parts to compare - * \return `{-1, 0, 1}` meaning: `{ver1 < ver2, ver1 == ver2, ver1 > ver2}` + * @param[in] ver1 Version 1 + * @param[in] ver2 Version 2 + * @param parts Number of parts to compare + * @return `{-1, 0, 1}` meaning: `{ver1 < ver2, ver1 == ver2, ver1 > ver2}` */ static int compare( const QString & ver1, const QString & ver2, int parts ); static int compare( const QString & ver1, const QString & ver2 ); - /** \brief Compare two strings beyond MAJ.MIN.REV granularity - * - * \param[in] ver1 Version 1 - * \param[in] ver2 Version 2 - * \param parts Number of parts to compare - * \return True if `ver1 > ver2` - */ + /*! Compare two strings beyond MAJ.MIN.REV granularity + * + * @param[in] ver1 Version 1 + * @param[in] ver2 Version 2 + * @param parts Number of parts to compare + * @return True if `ver1 > ver2` + */ static bool compareGreater( const QString & ver1, const QString & ver2, int parts ); static bool compareGreater( const QString & ver1, const QString & ver2 ); - /** \brief Compare two strings beyond MAJ.MIN.REV granularity - * - * \param[in] ver1 Version 1 - * \param[in] ver2 Version 2 - * \param parts Number of parts to compare - * \return True if `ver1 < ver2` - */ + /*! Compare two strings beyond MAJ.MIN.REV granularity + * + * @param[in] ver1 Version 1 + * @param[in] ver2 Version 2 + * @param parts Number of parts to compare + * @return True if `ver1 < ver2` + */ static bool compareLess( const QString & ver1, const QString & ver2, int parts ); static bool compareLess( const QString & ver1, const QString & ver2 ); - /** \brief Version string for display + /*! Version string for display * - * \param[in] ver Version string - * \param showStage (false) Whether to show release stage e.g. "1.2.0 Alpha 1" - * \param showDev (false) Whether to show dev release e.g. "1.2.0 Alpha 1 Dev 1" - * \return Display string for `ver`, optionally formatted with stage/dev information + * @param[in] ver Version string + * @param showStage (false) Whether to show release stage e.g. "1.2.0 Alpha 1" + * @param showDev (false) Whether to show dev release e.g. "1.2.0 Alpha 1 Dev 1" + * @return Display string for `ver`, optionally formatted with stage/dev information */ static QString rawToDisplay( const QString & ver, bool showStage = false, bool showDev = false ); - /** \brief Version string for MAJ.MIN format + /*! Version string for MAJ.MIN format * - * \param[in] ver Version string - * \return `ver` formatted to MAJ.MIN format, e.g. 1.2 + * @param[in] ver Version string + * @return `ver` formatted to MAJ.MIN format, e.g. 1.2 */ static QString rawToMajMin( const QString & ver ); - /** \brief Version parts for any version string + /*! Version parts for any version string * - * \param[in] ver Version string - * \param parts (7) Number of parts to return - * \return Version parts of length `parts` + * @param[in] ver Version string + * @param parts (7) Number of parts to return + * @return Version parts of length `parts` */ static QList versionParts( const QString & ver, int parts = 7 ); - /** \brief Pass by reference a QStringList for the version parts. + /*! Pass by reference a QStringList for the version parts. * * Some examples of version strings: * @@ -194,10 +191,10 @@ class NifSkopeVersion final * "1.3.0a", * "1.4.0rc" * - * \param[in] ver Version string - * \param[out] verNums Version string encoded as a list of integers - * \param parts (3) Number of parts to return - * \return True if valid string, false if invalid string + * @param[in] ver Version string + * @param[out] verNums Version string encoded as a list of integers + * @param parts (3) Number of parts to return + * @return True if valid string, false if invalid string */ static bool formatVersion( const QString & ver, QList & verNums, int parts = 3 ); @@ -224,11 +221,10 @@ class NifSkopeVersion final static int numParts; }; -/** \brief QDebug operator for NifSkopeVersion +/*! QDebug operator for NifSkopeVersion * * Prints rawVersion, displayVersion, parts() * e.g. "1.2.0a1.dev19" "1.2.0 Alpha 1" (1, 2, 0, 1, 1, 0, 19) - * */ QDebug operator<<(QDebug dbg, const NifSkopeVersion & ver); @@ -249,12 +245,7 @@ static const QHash migrateTo1_2 = { { "Render Settings/Draw Collision Geometry", "Render Settings/Draw Collision Geometry" }, { "Render Settings/Draw Constraints", "Render Settings/Draw Constraints" }, { "Render Settings/Draw Furniture Markers", "Render Settings/Draw Furniture Markers" }, { "Render Settings/Draw Nodes", "Render Settings/Draw Nodes" }, { "Render Settings/Enable Shaders", "Render Settings/Enable Shaders" }, { "Render Settings/Foreground", "Render Settings/Foreground" }, - { "Render Settings/Handle Length", "Render Settings/Handle Length" }, { "Render Settings/Highlight", "Render Settings/Highlight" }, - { "Render Settings/Light0/Ambient", "Render Settings/Light0/Ambient" }, { "Render Settings/Light0/Declination", "Render Settings/Light0/Declination" }, - { "Render Settings/Light0/Diffuse", "Render Settings/Light0/Diffuse" }, { "Render Settings/Light0/Frontal", "Render Settings/Light0/Frontal" }, - { "Render Settings/Light0/Planar Angle", "Render Settings/Light0/Planar Angle" }, { "Render Settings/Light0/Specular", "Render Settings/Light0/Specular" }, - { "Render Settings/MatOver/Ambient", "Render Settings/MatOver/Ambient" }, { "Render Settings/MatOver/Diffuse", "Render Settings/MatOver/Diffuse" }, - { "Render Settings/MatOver/Emmissive", "Render Settings/MatOver/Emissive" }, { "Render Settings/MatOver/Specular", "Render Settings/MatOver/Specular" }, + { "Render Settings/Highlight", "Render Settings/Highlight" }, { "Render Settings/Show Hidden Objects", "Render Settings/Show Hidden Objects" }, { "Render Settings/Show Stats", "Render Settings/Show Stats" }, { "Render Settings/Texture Alternatives", "Render Settings/Texture Alternatives" }, { "Render Settings/Texture Folders", "Render Settings/Texture Folders" }, { "Render Settings/Texturing", "Render Settings/Texturing" }, { "Render Settings/Up Axis", "Render Settings/Up Axis" }, @@ -275,5 +266,23 @@ static const QHash migrateTo1_2 = { { "version", "Version" } }; +static const QHash migrateTo2_0 = { + { "Export Settings/Export Culling", "Export Settings/Export Culling" }, + { "File/Recent File List", "File/Recent File List" }, + { "File/Auto Sanitize", "File/Auto Sanitize" }, + { "File/Last Load", "File/Last Load" }, { "File/Last Save", "File/Last Save" }, + { "FSEngine/Archives", "FSEngine/Archives" }, + { "Render Settings/Anti Aliasing", "Render Settings/Anti Aliasing" }, + { "Render Settings/Texturing", "Render Settings/Texturing" }, + { "Render Settings/Enable Shaders", "Render Settings/Enable Shaders" }, + { "Render Settings/Background", "Render Settings/Background" }, + { "Render Settings/Foreground", "Render Settings/Foreground" }, + { "Render Settings/Highlight", "Render Settings/Highlight" }, + { "Render Settings/Texture Alternatives", "Render Settings/Texture Alternatives" }, + { "Render Settings/Texture Folders", "Render Settings/Texture Folders" }, + { "Render Settings/Up Axis", "Render Settings/Up Axis" }, + { "Settings/Language", "Settings/Language" }, { "Settings/Startup Version", "Settings/Startup Version" }, +}; + #endif diff --git a/src/widgets/colorwheel.cpp b/src/widgets/colorwheel.cpp index c223c1f14..5005e278e 100644 --- a/src/widgets/colorwheel.cpp +++ b/src/widgets/colorwheel.cpp @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -31,6 +31,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "colorwheel.h" +#include "spellbook.h" #include "floatslider.h" #include "niftypes.h" @@ -40,6 +41,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include #include #include #include @@ -96,7 +98,7 @@ static const char * const hsv42_xpm[] = { #include -QIcon * ColorWheel::icon = nullptr; +static QIconPtr icon = nullptr; ColorWheel::ColorWheel( QWidget * parent ) : QWidget( parent ) { @@ -123,7 +125,7 @@ ColorWheel::ColorWheel( const QColor & c, QWidget * parent ) : QWidget( parent ) QIcon ColorWheel::getIcon() { if ( !icon ) - icon = new QIcon( QPixmap( hsv42_xpm ) ); + icon = QIconPtr( new QIcon(QPixmap( hsv42_xpm )) ); return *icon; } @@ -167,6 +169,8 @@ void ColorWheel::setAlpha( const bool & b ) void ColorWheel::setAlphaValue( const float & f ) { A = f; + + setColor( QColor::fromHsvF( H, S, V, A ) ); } QSize ColorWheel::sizeHint() const @@ -174,7 +178,7 @@ QSize ColorWheel::sizeHint() const if ( sHint.isValid() ) return sHint; - return { 250, 250 }; + return { 200, 200 }; } void ColorWheel::setSizeHint( const QSize & s ) @@ -207,6 +211,8 @@ void ColorWheel::paintEvent( QPaintEvent * e ) QPainter p( this ); p.translate( width() / 2, height() / 2 ); + p.setRenderHint( QPainter::Antialiasing ); + p.setRenderHint( QPainter::HighQualityAntialiasing ); p.setPen( Qt::NoPen ); @@ -477,3 +483,115 @@ void ColorWheel::chooseHex() emit sigColorEdited( temp ); } } + +ColorLineEdit::ColorLineEdit( QWidget * parent ) : QWidget( parent ) +{ + QHBoxLayout * layout = new QHBoxLayout( this ); + + setLayout( layout ); + + title = new QLabel( this ); + title->setText( "Color" ); + title->setSizePolicy( QSizePolicy( QSizePolicy::Maximum, QSizePolicy::MinimumExpanding ) ); + + lblColor = new QLabel( this ); + + color = new QLineEdit( this ); + color->setText( "#FFFFFF" ); + color->setMaxLength( 7 ); + color->setAlignment( Qt::AlignCenter ); + color->setMaximumWidth( 60 ); + + alpha = new QDoubleSpinBox( this ); + alpha->setDecimals( 4 ); + alpha->setMinimum( 0.0 ); + alpha->setMaximum( 1.0 ); + alpha->setSingleStep( 0.01 ); + alpha->setVisible( false ); + alpha->setHidden( true ); + + layout->setAlignment( Qt::AlignLeft ); + layout->addWidget( title ); + layout->addWidget( lblColor ); + layout->addWidget( color ); + layout->addWidget( alpha ); +} + +QColor ColorLineEdit::getColor() const +{ + QColor c = QColor( color->text() ); + if ( hasAlpha ) + c.setAlphaF( alpha->value() ); + + return c; +} + +void ColorLineEdit::setWheel( ColorWheel * cw, const QString & str ) +{ + wheel = cw; + + if ( !str.isEmpty() ) + setTitle( str ); + + connect( wheel, &ColorWheel::sigColor, this, &ColorLineEdit::setColor ); + connect( wheel, &ColorWheel::sigColor, [this]() { + lblColor->setStyleSheet( "background-color: " + wheel->getColor().name( QColor::HexArgb ) + ";" ); + } ); + + connect( color, &QLineEdit::cursorPositionChanged, [this]() { + if ( color->cursorPosition() == 0 ) { + color->setCursorPosition( 1 ); + } + } ); + + connect( color, &QLineEdit::textEdited, [this]() { + QString colorTxt = color->text(); + + // Prevent "#" deletion + if ( !color->text().startsWith( "#" ) ) { + color->setText( "#" + color->text() ); + } + + QColor c = QColor( colorTxt ); + if ( hasAlpha ) + c.setAlphaF( alpha->value() ); + + if ( (color->text().length() % 2 == 0) || !QColor::isValidColor( colorTxt ) ) + return; + + QColor wc = wheel->getColor(); + if ( c.toRgb() != wc.toRgb() ) + wheel->setColor( c ); + } ); +} + +void ColorLineEdit::setTitle( const QString & str ) +{ + title->setText( str ); +} + +void ColorLineEdit::setColor( const QColor & c ) +{ + color->setText( c.name( QColor::HexRgb ) ); + + if ( hasAlpha ) + alpha->setValue( c.alphaF() ); + + QColor wc = wheel->getColor(); + + // Sync color wheel + // Do NOT compare entire QColor, will create + // infinite loop between their ::setColor() + if ( hasAlpha && c.alphaF() != wc.alphaF() ) + wheel->setColor( c ); + + if ( c.red() != wc.red() || c.green() != wc.green() || c.blue() != wc.blue() ) + wheel->setColor( c ); +} + +void ColorLineEdit::setAlpha( float a ) +{ + hasAlpha = true; + + alpha->setValue( a ); +} diff --git a/src/widgets/colorwheel.h b/src/widgets/colorwheel.h index 2b42a9277..d97725d2e 100644 --- a/src/widgets/colorwheel.h +++ b/src/widgets/colorwheel.h @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -33,8 +33,9 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef COLORWHEEL_H #define COLORWHEEL_H -#include // Inherited -#include // Inherited +#include // Inherited +#include // Inherited + #include #include #include @@ -101,8 +102,38 @@ public slots: } pressed; QSize sHint; +}; + +class QLabel; +class QLineEdit; +class QDoubleSpinBox; +class QPushButton; + +class ColorLineEdit final : public QWidget +{ + Q_OBJECT + +public: + ColorLineEdit( QWidget * parent = nullptr ); - static QIcon * icon; + QColor getColor() const; + + void setWheel( ColorWheel *, const QString & = "" ); + void setTitle( const QString & ); + void setColor( const QColor & ); + +public slots: + void setAlpha( float ); + +private: + ColorWheel * wheel; + QLabel * title; + QLabel * lblColor; + QLineEdit * color; + QDoubleSpinBox * alpha; + QPushButton * btn; + + bool hasAlpha = false; }; class ColorSpinBox final : public QSpinBox diff --git a/src/widgets/copyfnam.cpp b/src/widgets/copyfnam.cpp deleted file mode 100644 index a4e614752..000000000 --- a/src/widgets/copyfnam.cpp +++ /dev/null @@ -1,84 +0,0 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2012, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. -3. The name of the NIF File Format Library and Tools project may not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -***** END LICENCE BLOCK *****/ - -#include "copyfnam.h" - -#include -#include - - -CopyFilename::CopyFilename( QWidget * parent ) - : QWidget( parent ) -{ - this->leftArrow.load( ":/img/ArrowLeft" ); - this->rightArrow.load( ":/img/ArrowRight" ); - - this->setMinimumWidth( qMax( leftArrow.width(), this->rightArrow.width() ) + 4 ); - this->setMinimumHeight( this->leftArrow.width() + this->rightArrow.width() + 4 ); -} - -void CopyFilename::paintEvent( QPaintEvent * e ) -{ - Q_UNUSED( e ); - int w = this->width(); - int h = this->height() / 2; - - // right arrow position, top - this->rightPos = this->rightArrow.rect(); - this->rightPos.moveTo( - ( w - this->rightArrow.width() ) / 2, - ( h - this->rightArrow.height() ) / 2 - ); - - // left arrow position, bottom - this->leftPos = this->leftArrow.rect(); - this->leftPos.moveTo( - ( w - this->leftArrow.width() ) / 2, - ( h - this->leftArrow.height() ) / 2 + h - ); - - // now paint the widget - QPainter p( this ); - - // draw arrows - p.drawImage( this->rightPos, this->rightArrow, this->rightArrow.rect() ); - p.drawImage( this->leftPos, this->leftArrow, this->leftArrow.rect() ); -} - -void CopyFilename::mousePressEvent( QMouseEvent * e ) -{ - if ( e->y() < ( this->height() / 2 ) ) { - emit( this->rightTriggered() ); - } else { - emit( this->leftTriggered() ); - } -} diff --git a/src/widgets/copyfnam.h b/src/widgets/copyfnam.h deleted file mode 100644 index ce5ffd011..000000000 --- a/src/widgets/copyfnam.h +++ /dev/null @@ -1,66 +0,0 @@ -/***** BEGIN LICENSE BLOCK ***** - -BSD License - -Copyright (c) 2005-2012, NIF File Format Library and Tools -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. -3. The name of the NIF File Format Library and Tools project may not be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -***** END LICENCE BLOCK *****/ - -#ifndef COPYFNAM_H -#define COPYFNAM_H - -#include // Inherited -#include -#include - - -class CopyFilename final : public QWidget -{ - Q_OBJECT - -public: - CopyFilename( QWidget * parent = nullptr ); - -signals: - void leftTriggered(); - void rightTriggered(); - -protected: - void paintEvent( QPaintEvent * e ) override final; - void mousePressEvent( QMouseEvent * e ) override final; - - // QPushButton * leftBtnCopyFilename; - // QPushButton * rightBtnCopyFilename; - -private: - QImage leftArrow; - QImage rightArrow; - QRect leftPos; - QRect rightPos; -}; - -#endif diff --git a/src/widgets/fileselect.cpp b/src/widgets/fileselect.cpp index da6a2b2ac..4ef610e82 100644 --- a/src/widgets/fileselect.cpp +++ b/src/widgets/fileselect.cpp @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -51,6 +51,8 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define FEEDBACK_TIME 1200 +QAction * FileSelector::completionAction; + CompletionAction::CompletionAction( QObject * parent ) : QAction( "Completion of Filenames", parent ) { QSettings cfg; @@ -102,8 +104,10 @@ FileSelector::FileSelector( Modes mode, const QString & buttonText, QBoxLayout:: line->installEventFilter( this ); - connect( completionAction(), &QAction::toggled, this, &FileSelector::setCompletionEnabled ); - setCompletionEnabled( completionAction()->isChecked() ); + completionAction = new CompletionAction( this ); + + connect( completionAction, &QAction::toggled, this, &FileSelector::setCompletionEnabled ); + setCompletionEnabled( completionAction->isChecked() ); timer = new QTimer( this ); timer->setSingleShot( true ); @@ -111,11 +115,6 @@ FileSelector::FileSelector( Modes mode, const QString & buttonText, QBoxLayout:: connect( timer, &QTimer::timeout, this, &FileSelector::rstState ); } -QAction * FileSelector::completionAction() -{ - static QAction * action = new CompletionAction; - return action; -} void FileSelector::setCompletionEnabled( bool x ) { @@ -272,7 +271,7 @@ bool FileSelector::eventFilter( QObject * o, QEvent * e ) QMenu * menu = line->createStandardContextMenu(); menu->addSeparator(); - menu->addAction( completionAction() ); + menu->addAction( completionAction ); menu->exec( event->globalPos() ); delete menu; return true; diff --git a/src/widgets/fileselect.h b/src/widgets/fileselect.h index c99ac1a5b..38c0b6f40 100644 --- a/src/widgets/fileselect.h +++ b/src/widgets/fileselect.h @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -109,7 +109,7 @@ protected slots: protected: bool eventFilter( QObject * o, QEvent * e ) override final; - QAction * completionAction(); + static QAction * completionAction; Modes Mode; States State; diff --git a/src/widgets/floatedit.cpp b/src/widgets/floatedit.cpp index 0db9276ac..17508e4a9 100644 --- a/src/widgets/floatedit.cpp +++ b/src/widgets/floatedit.cpp @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -75,7 +75,7 @@ FloatEdit::FloatEdit( QWidget * parent ) val = 0.0f; setValidator( validator = new FloatValidator( this ) ); - validator->setDecimals( 4 ); + validator->setDecimals( 6 ); connect( this, &FloatEdit::editingFinished, this, &FloatEdit::edited ); diff --git a/src/widgets/floatedit.h b/src/widgets/floatedit.h index 36308b76b..61bb71797 100644 --- a/src/widgets/floatedit.h +++ b/src/widgets/floatedit.h @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/src/widgets/floatslider.cpp b/src/widgets/floatslider.cpp index 05c7d6d09..f19d49f93 100644 --- a/src/widgets/floatslider.cpp +++ b/src/widgets/floatslider.cpp @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -239,15 +239,16 @@ QStyleOptionSlider FloatSlider::getStyleOption() const opt.rect.adjust( (6 * w) / 10, VAL_HEIGHT, (-6 * w) / 10, 0 ); } - opt.maximum = INT_MAX - 1; + opt.maximum = INT_MAX; opt.minimum = 0; opt.orientation = ori; opt.pageStep = 10; opt.singleStep = 1; - opt.sliderValue = ( max != min ) ? int(1.0f * ( val - min ) / ( max - min ) * opt.maximum) : 0; + opt.sliderValue = ( max != min ) ? int(1.0f * ( val - min ) / ( max - min ) * opt.maximum) - 1 : 0; opt.sliderPosition = opt.sliderValue; opt.tickPosition = QSlider::NoTicks; opt.direction = Qt::LeftToRight; + opt.dialWrapping = false; /* upside down for vertical slider; zero at bottom position */ opt.upsideDown = (ori == Qt::Vertical); diff --git a/src/widgets/floatslider.h b/src/widgets/floatslider.h index 9fe76fcad..d837925ce 100644 --- a/src/widgets/floatslider.h +++ b/src/widgets/floatslider.h @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/src/widgets/groupbox.cpp b/src/widgets/groupbox.cpp index 2eb8f7b34..94dd3edb8 100644 --- a/src/widgets/groupbox.cpp +++ b/src/widgets/groupbox.cpp @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/src/widgets/groupbox.h b/src/widgets/groupbox.h index e3e8643af..051285369 100644 --- a/src/widgets/groupbox.h +++ b/src/widgets/groupbox.h @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/src/widgets/inspect.cpp b/src/widgets/inspect.cpp index 62407b9e1..9dcd60acd 100644 --- a/src/widgets/inspect.cpp +++ b/src/widgets/inspect.cpp @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -36,12 +36,16 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "gl/glscene.h" #include "gl/glnode.h" +#include +#include #include +#include #include #include #include #include #include +#include #include #include @@ -53,6 +57,10 @@ class InspectViewInternal { public: ~InspectViewInternal(); + + Transform transform; + QPushButton * btnCopyTransform; + bool needUpdate; float time; QLabel * nameLabel; @@ -176,6 +184,10 @@ InspectView::InspectView( QWidget * parent, Qt::WindowFlags f ) impl->localCheck->setCheckState( Qt::Unchecked ); impl->localCheck->setText( tr( "Show Local Transform" ) ); + impl->btnCopyTransform = new QPushButton( this ); + impl->btnCopyTransform->setText( tr( "Copy Transform to Clipboard" ) ); + impl->btnCopyTransform->setFocus(); + impl->posGroup = new QGroupBox( this ); impl->posGroup->setTitle( tr( "Position:" ) ); impl->posXLabel = new QLabel( this ); @@ -291,6 +303,7 @@ InspectView::InspectView( QWidget * parent, Qt::WindowFlags f ) grid->addWidget( impl->timeLabel, 2, 0 ); grid->addWidget( impl->timeText, 2, 1 ); grid->addWidget( impl->localCheck, 3, 0, 1, 2, Qt::AlignLeft | Qt::AlignAbsolute ); + grid->addWidget( impl->btnCopyTransform, 3, 1, 1, 2, Qt::AlignRight | Qt::AlignAbsolute ); grid->addWidget( impl->posGroup, 4, 0, 1, 2, Qt::AlignLeft | Qt::AlignAbsolute ); grid->addWidget( impl->invertCheck, 5, 0, 1, 2, Qt::AlignLeft | Qt::AlignAbsolute ); //grid->addWidget( impl->rotGroup, 6, 0, 1, 2, Qt::AlignLeft | Qt::AlignAbsolute ); @@ -304,6 +317,7 @@ InspectView::InspectView( QWidget * parent, Qt::WindowFlags f ) connect( impl->localCheck, &QCheckBox::stateChanged, this, &InspectView::update ); connect( impl->invertCheck, &QCheckBox::stateChanged, this, &InspectView::update ); connect( impl->refreshBtn, &QPushButton::clicked, this, &InspectView::update ); + connect( impl->btnCopyTransform, &QPushButton::clicked, this, &InspectView::copyTransformToMimedata ); } InspectView::~InspectView() @@ -377,6 +391,8 @@ void InspectView::update() Transform tm = ( impl->localCheck->isChecked() ) ? node->localTrans() : node->worldTrans(); Matrix mat = tm.rotation; + impl->transform = tm; + if ( impl->invertCheck->isChecked() ) mat = mat.inverted(); @@ -443,3 +459,17 @@ void InspectView::setVisible( bool visible ) impl->needUpdate = visible; QDialog::setVisible( visible ); } + +void InspectView::copyTransformToMimedata() +{ + QByteArray data; + QBuffer buffer( &data ); + if ( buffer.open( QIODevice::WriteOnly ) ) { + QDataStream ds( &buffer ); + ds << impl->transform; + + QMimeData * mime = new QMimeData; + mime->setData( QString( "nifskope/transform" ), data ); + QApplication::clipboard()->setMimeData( mime ); + } +} diff --git a/src/widgets/inspect.h b/src/widgets/inspect.h index 3bb538907..3b6e4cb0f 100644 --- a/src/widgets/inspect.h +++ b/src/widgets/inspect.h @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -68,6 +68,7 @@ public slots: void refresh(); void updateTime( float t, float mn, float mx ); void update(); + void copyTransformToMimedata(); private: InspectViewInternal * impl; diff --git a/src/widgets/nifcheckboxlist.cpp b/src/widgets/nifcheckboxlist.cpp index 4759ca136..467d61caa 100644 --- a/src/widgets/nifcheckboxlist.cpp +++ b/src/widgets/nifcheckboxlist.cpp @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -31,7 +31,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "nifcheckboxlist.h" -#include "options.h" +#include "settings.h" #include #include @@ -193,6 +193,7 @@ NifCheckBoxList::~NifCheckBoxList() void NifCheckBoxList::sltDataChanged( const QModelIndex &, const QModelIndex & ) { + emit dataChanged(); updateText(); } diff --git a/src/widgets/nifcheckboxlist.h b/src/widgets/nifcheckboxlist.h index a45cdc631..0f7dc9105 100644 --- a/src/widgets/nifcheckboxlist.h +++ b/src/widgets/nifcheckboxlist.h @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -134,6 +134,9 @@ class NifCheckBoxList : public CheckBoxList NifCheckBoxList( QWidget * widget = nullptr ); virtual ~NifCheckBoxList(); +signals: + void dataChanged(); + protected: void updateText() override final; void parseText( const QString & text ); diff --git a/src/widgets/nifeditors.cpp b/src/widgets/nifeditors.cpp index 9c9ad9077..fea049040 100644 --- a/src/widgets/nifeditors.cpp +++ b/src/widgets/nifeditors.cpp @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -55,11 +55,6 @@ NifBlockEditor::NifBlockEditor( NifModel * n, const QModelIndex & i, bool fireAn setLayout( layout ); layouts.push( layout ); - QModelIndex iName = nif->getIndex( iBlock, "Name" ); - - if ( iName.isValid() ) - add( new NifLineEdit( nif, iName ) ); - timer = new QTimer( this ); connect( timer, &QTimer::timeout, this, &NifBlockEditor::updateData ); timer->setInterval( 0 ); @@ -183,7 +178,7 @@ QLayout * NifEditBox::getLayout() return lay; } -void NifEditBox::applyData() +void NifEditBox::sltApplyData() { if ( nif && index.isValid() ) { applyData( nif ); @@ -196,8 +191,7 @@ NifFloatSlider::NifFloatSlider( NifModel * nif, const QModelIndex & index, float { getLayout()->addWidget( slider = new FloatSlider( Qt::Horizontal, true, false ) ); slider->setRange( min, max ); - // TODO: Can't seem to figure out correct cast for new signal syntax - connect( slider, SIGNAL( valueChanged( float ) ), this, SLOT( applyData() ) ); + connect( slider, &FloatSlider::valueChanged, this, &NifFloatSlider::sltApplyData ); } void NifFloatSlider::updateData( NifModel * nif ) @@ -216,8 +210,10 @@ NifFloatEdit::NifFloatEdit( NifModel * nif, const QModelIndex & index, float min getLayout()->addWidget( spinbox = new QDoubleSpinBox() ); spinbox->setRange( min, max ); spinbox->setDecimals( 4 ); - // TODO: Can't seem to figure out correct cast for new signal syntax - connect( spinbox, SIGNAL( valueChanged( double ) ), this, SLOT( applyData() ) ); + // Cast QDoubleSpinBox slot + auto dsbValueChanged = static_cast(&QDoubleSpinBox::valueChanged); + + connect( spinbox, dsbValueChanged, this, &NifFloatEdit::sltApplyData ); } void NifFloatEdit::updateData( NifModel * nif ) @@ -234,8 +230,7 @@ NifLineEdit::NifLineEdit( NifModel * nif, const QModelIndex & index ) : NifEditBox( nif, index ) { getLayout()->addWidget( line = new QLineEdit() ); - // TODO: Can't seem to figure out correct cast for new signal syntax - connect( line, SIGNAL( textEdited( const QString & ) ), this, SLOT( applyData() ) ); + connect( line, &QLineEdit::textEdited, this, &NifLineEdit::sltApplyData ); } void NifLineEdit::updateData( NifModel * nif ) @@ -254,13 +249,12 @@ NifColorEdit::NifColorEdit( NifModel * nif, const QModelIndex & index ) { getLayout()->addWidget( color = new ColorWheel() ); color->setSizeHint( QSize( 140, 140 ) ); - // TODO: Can't seem to figure out correct cast for new signal syntax - connect( color, SIGNAL( sigColor( const QColor & ) ), this, SLOT( applyData() ) ); + connect( color, &ColorWheel::sigColor, this, &NifColorEdit::sltApplyData ); - if ( nif->getValue( index ).type() == NifValue::tColor4 ) { + auto typ = nif->getValue( index ).type(); + if ( typ == NifValue::tColor4 || typ == NifValue::tByteColor4 ) { getLayout()->addWidget( alpha = new AlphaSlider() ); - // TODO: Can't seem to figure out correct cast for new signal syntax - connect( alpha, SIGNAL( valueChanged( float ) ), this, SLOT( applyData() ) ); + connect( alpha, &AlphaSlider::valueChanged, this, &NifColorEdit::sltApplyData ); } else { alpha = nullptr; } @@ -294,8 +288,7 @@ NifVectorEdit::NifVectorEdit( NifModel * nif, const QModelIndex & index ) : NifEditBox( nif, index ) { getLayout()->addWidget( vector = new VectorEdit() ); - // TODO: Can't seem to figure out correct cast for new signal syntax - connect( vector, SIGNAL( sigEdited() ), this, SLOT( applyData() ) ); + connect( vector, &VectorEdit::sigEdited, this, &NifVectorEdit::sltApplyData ); } void NifVectorEdit::updateData( NifModel * nif ) @@ -322,8 +315,7 @@ NifRotationEdit::NifRotationEdit( NifModel * nif, const QModelIndex & index ) : NifEditBox( nif, index ) { getLayout()->addWidget( rotation = new RotationEdit() ); - // TODO: Can't seem to figure out correct cast for new signal syntax - connect( rotation, SIGNAL( sigEdited() ), this, SLOT( applyData() ) ); + connect( rotation, &RotationEdit::sigEdited, this, &NifRotationEdit::sltApplyData ); } void NifRotationEdit::updateData( NifModel * nif ) @@ -357,24 +349,21 @@ NifMatrix4Edit::NifMatrix4Edit( NifModel * nif, const QModelIndex & index ) group->setTitle( tr( "Translation" ) ); group->setLayout( new QHBoxLayout ); group->layout()->addWidget( translation = new VectorEdit() ); - // TODO: Can't seem to figure out correct cast for new signal syntax - connect( translation, SIGNAL( sigEdited() ), this, SLOT( applyData() ) ); + connect( translation, &VectorEdit::sigEdited, this, &NifMatrix4Edit::sltApplyData ); group = new QGroupBox; vbox->addWidget( group ); group->setTitle( tr( "Rotation" ) ); group->setLayout( new QHBoxLayout ); group->layout()->addWidget( rotation = new RotationEdit() ); - // TODO: Can't seem to figure out correct cast for new signal syntax - connect( rotation, SIGNAL( sigEdited() ), this, SLOT( applyData() ) ); + connect( rotation, &RotationEdit::sigEdited, this, &NifMatrix4Edit::sltApplyData ); group = new QGroupBox; vbox->addWidget( group ); group->setTitle( tr( "Scale" ) ); group->setLayout( new QHBoxLayout ); group->layout()->addWidget( scale = new VectorEdit() ); - // TODO: Can't seem to figure out correct cast for new signal syntax - connect( scale, SIGNAL( sigEdited() ), this, SLOT( applyData() ) ); + connect( scale, &VectorEdit::sigEdited, this, &NifMatrix4Edit::sltApplyData ); } void NifMatrix4Edit::updateData( NifModel * nif ) diff --git a/src/widgets/nifeditors.h b/src/widgets/nifeditors.h index 283acc509..76c79807c 100644 --- a/src/widgets/nifeditors.h +++ b/src/widgets/nifeditors.h @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -55,7 +55,7 @@ class NifEditBox : public QGroupBox QModelIndex getIndex() const { return index; } protected slots: - void applyData(); + void sltApplyData(); protected: QLayout * getLayout(); diff --git a/src/widgets/nifview.cpp b/src/widgets/nifview.cpp index fccd65999..5715cc7c2 100644 --- a/src/widgets/nifview.cpp +++ b/src/widgets/nifview.cpp @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -38,16 +38,11 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include -NifTreeView::NifTreeView() : QTreeView() +NifTreeView::NifTreeView( QWidget * parent, Qt::WindowFlags flags ) : QTreeView() { - nif = nullptr; - EvalConditions = false; - RealTimeEval = false; + Q_UNUSED( flags ); - setUniformRowHeights( true ); - setAlternatingRowColors( true ); - setContextMenuPolicy( Qt::CustomContextMenu ); - setHorizontalScrollBarPolicy( Qt::ScrollBarAsNeeded ); + setParent( parent ); connect( this, &NifTreeView::expanded, this, &NifTreeView::scrollExpand ); } @@ -65,8 +60,9 @@ void NifTreeView::setModel( QAbstractItemModel * model ) QTreeView::setModel( model ); - if ( nif && EvalConditions && RealTimeEval ) + if ( nif && doRowHiding ) { connect( nif, &BaseModel::dataChanged, this, &NifTreeView::updateConditions ); + } } void NifTreeView::setRootIndex( const QModelIndex & index ) @@ -84,16 +80,16 @@ void NifTreeView::clearRootIndex() setRootIndex( QModelIndex() ); } -void NifTreeView::setEvalConditions( bool c ) +void NifTreeView::setRowHiding( bool show ) { - if ( EvalConditions == c ) + if ( doRowHiding != show ) return; - EvalConditions = c; + doRowHiding = !show; - if ( nif && EvalConditions && RealTimeEval ) { + if ( nif && doRowHiding ) { connect( nif, &BaseModel::dataChanged, this, &NifTreeView::updateConditions ); - } else { + } else if ( nif ) { disconnect( nif, &BaseModel::dataChanged, this, &NifTreeView::updateConditions ); } @@ -102,26 +98,14 @@ void NifTreeView::setEvalConditions( bool c ) doItemsLayout(); } -void NifTreeView::setRealTime( bool c ) -{ - if ( RealTimeEval == c ) - return; - - RealTimeEval = c; - - if ( nif && EvalConditions && RealTimeEval ) { - connect( nif, &BaseModel::dataChanged, this, &NifTreeView::updateConditions ); - } else { - disconnect( nif, &BaseModel::dataChanged, this, &NifTreeView::updateConditions ); - } - - updateConditionRecurse( rootIndex() ); - doItemsLayout(); -} bool NifTreeView::isRowHidden( int r, const QModelIndex & index ) const { - return ( EvalConditions && index.isValid() && nif && index.model() == nif && !nif->evalCondition( index.child( r, 0 ) ) ); + NifItem * item = static_cast(index.internalPointer()); + if ( !item || !doRowHiding ) + return false; + + return !item->condition(); } void NifTreeView::setAllExpanded( const QModelIndex & index, bool e ) @@ -154,6 +138,9 @@ void NifTreeView::drawBranches( QPainter * painter, const QRect & rect, const QM void NifTreeView::updateConditions( const QModelIndex & topLeft, const QModelIndex & bottomRight ) { + if ( nif->getState() != BaseModel::Default ) + return; + Q_UNUSED( bottomRight ); updateConditionRecurse( topLeft.parent() ); doItemsLayout(); @@ -161,26 +148,24 @@ void NifTreeView::updateConditions( const QModelIndex & topLeft, const QModelInd void NifTreeView::updateConditionRecurse( const QModelIndex & index ) { - /* - if ( model()->rowCount( index ) == 0 ) - { - setRowHidden( index.row(), index.parent(), (EvalConditions && ! nif->evalVersion( index, true ) ) ); - } - */ - //else - //{ + if ( nif->getState() != BaseModel::Default ) + return; + + NifItem * item = static_cast(index.internalPointer()); + if ( !item ) + return; + for ( int r = 0; r < model()->rowCount( index ); r++ ) { QModelIndex child = model()->index( r, 0, index ); updateConditionRecurse( child ); } - setRowHidden( index.row(), index.parent(), ( EvalConditions && !nif->evalVersion( index, false ) ) ); - //} + setRowHidden( index.row(), index.parent(), doRowHiding && !item->condition() ); } void NifTreeView::keyPressEvent( QKeyEvent * e ) { - Spell * spell = SpellBook::lookup( QKeySequence( e->modifiers() + e->key() ) ); + SpellPtr spell = SpellBook::lookup( QKeySequence( e->modifiers() + e->key() ) ); if ( spell ) { NifModel * nif = nullptr; @@ -200,7 +185,21 @@ void NifTreeView::keyPressEvent( QKeyEvent * e ) if ( nif && spell->isApplicable( nif, oldidx ) ) { selectionModel()->setCurrentIndex( QModelIndex(), QItemSelectionModel::Clear | QItemSelectionModel::Rows ); + bool noSignals = spell->batch(); + if ( noSignals ) + nif->setState( BaseModel::Processing ); + // Cast the spell and return index QModelIndex newidx = spell->cast( nif, oldidx ); + if ( noSignals ) + nif->resetState(); + + // Refresh the header + nif->invalidateConditions( nif->getHeader(), true ); + nif->updateHeader(); + + if ( noSignals && nif->getProcessingResult() ) { + emit nif->dataChanged( newidx, newidx ); + } if ( proxy ) newidx = proxy->mapFrom( newidx, oldidx ); @@ -230,8 +229,16 @@ void NifTreeView::currentChanged( const QModelIndex & current, const QModelIndex { QTreeView::currentChanged( current, last ); - if ( nif && EvalConditions ) { - updateConditionRecurse( rootIndex() ); + if ( nif && doRowHiding ) { + updateConditionRecurse( current ); + } + + auto mdl = static_cast( nif ); + if ( mdl ) { + // Auto-Expand Textures + if ( mdl->inherits( current, "BSShaderTextureSet" ) ) { + expand( current.child( 1, 0 ) ); + } } emit sigCurrentIndexChanged( currentIndex() ); diff --git a/src/widgets/nifview.h b/src/widgets/nifview.h index 49525dc4e..328b513cf 100644 --- a/src/widgets/nifview.h +++ b/src/widgets/nifview.h @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -43,7 +43,7 @@ class NifTreeView final : public QTreeView public: //! Constructor - NifTreeView(); + NifTreeView( QWidget * parent = 0, Qt::WindowFlags flags = 0 ); //! Destructor ~NifTreeView(); @@ -53,7 +53,7 @@ class NifTreeView final : public QTreeView void setAllExpanded( const QModelIndex & index, bool e ); //! Accessor for EvalConditions - bool evalConditions() const { return EvalConditions; } + bool evalConditions() const { return doRowHiding; } //! Is a row hidden? bool isRowHidden( int row, const QModelIndex & parent ) const; @@ -72,10 +72,8 @@ public slots: //! Clear the root index; probably conncted to NifSkope::dList void clearRootIndex(); - //! Sets version evaluation conditions - void setEvalConditions( bool ); - //! Sets real-time version condition evalutation (slow) - void setRealTime( bool ); + //! Sets Hiding of non-applicable rows + void setRowHiding( bool ); protected slots: //! Updates version conditions (connect to dataChanged) @@ -94,10 +92,9 @@ protected slots: QStyleOptionViewItem viewOptions() const override final; - bool EvalConditions; - bool RealTimeEval; + bool doRowHiding = true; - class BaseModel * nif; + class BaseModel * nif = nullptr; }; diff --git a/src/widgets/refrbrowser.cpp b/src/widgets/refrbrowser.cpp index 1a353e471..ce93352df 100644 --- a/src/widgets/refrbrowser.cpp +++ b/src/widgets/refrbrowser.cpp @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/src/widgets/refrbrowser.h b/src/widgets/refrbrowser.h index 22d450bce..7b6aeceac 100644 --- a/src/widgets/refrbrowser.h +++ b/src/widgets/refrbrowser.h @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/src/widgets/uvedit.cpp b/src/widgets/uvedit.cpp index 753087747..cbd5b9f26 100644 --- a/src/widgets/uvedit.cpp +++ b/src/widgets/uvedit.cpp @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -31,7 +31,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***** END LICENCE BLOCK *****/ #include "uvedit.h" -#include "options.h" +#include "settings.h" #include "nifmodel.h" #include "niftypes.h" @@ -77,7 +77,7 @@ UVWidget * UVWidget::createEditor( NifModel * nif, const QModelIndex & idx ) uvw->setAttribute( Qt::WA_DeleteOnClose ); if ( !uvw->setNifData( nif, idx ) ) { - qWarning() << tr( "Could not load texture data for UV editor." ); + qCWarning( nsSpell ) << tr( "Could not load texture data for UV editor." ); delete uvw; return nullptr; } @@ -99,17 +99,17 @@ static GLshort texArray[4][2] = { static GLdouble glUnit = ( 1.0 / BASESIZE ); static GLdouble glGridD = GRIDSIZE * glUnit; +QStringList UVWidget::texnames = { + "Base Texture", "Dark Texture", "Detail Texture", + "Gloss Texture", "Glow Texture", "Bump Map Texture", + "Decal 0 Texture", "Decal 1 Texture", "Decal 2 Texture", + "Decal 3 Texture" +}; + + UVWidget::UVWidget( QWidget * parent ) : QGLWidget( QGLFormat( QGL::SampleBuffers ), parent, 0, Qt::Tool | Qt::WindowStaysOnTopHint ), undoStack( new QUndoStack( this ) ) { - // these are not translated since they are pulled from nif.xml - texnames = { - "Base Texture", "Dark Texture", "Detail Texture", - "Gloss Texture", "Glow Texture", "Bump Map Texture", - "Decal 0 Texture", "Decal 1 Texture", "Decal 2 Texture", - "Decal 3 Texture" - }; - setWindowTitle( tr( "UV Editor" ) ); setFocusPolicy( Qt::StrongFocus ); @@ -176,23 +176,10 @@ UVWidget::UVWidget( QWidget * parent ) connect( aTextureBlend, &QAction::toggled, this, &UVWidget::updateGL ); addAction( aTextureBlend ); - coordSetGroup = new QActionGroup( this ); - connect( coordSetGroup, &QActionGroup::triggered, this, &UVWidget::selectCoordSet ); - - coordSetSelect = new QMenu( tr( "Select Coordinate Set" ) ); - addAction( coordSetSelect->menuAction() ); - connect( coordSetSelect, &QMenu::aboutToShow, this, &UVWidget::getCoordSets ); + updateSettings(); - texSlotGroup = new QActionGroup( this ); - connect( texSlotGroup, &QActionGroup::triggered, this, &UVWidget::selectTexSlot ); - - menuTexSelect = new QMenu( tr( "Select Texture Slot" ) ); - addAction( menuTexSelect->menuAction() ); - connect( menuTexSelect, &QMenu::aboutToShow, this, &UVWidget::getTexSlots ); - - currentTexSlot = 0; - - connect( Options::get(), &Options::sigChanged, this, &UVWidget::updateGL ); + connect( NifSkope::getOptions(), &SettingsDialog::saveSettings, this, &UVWidget::updateSettings ); + connect( NifSkope::getOptions(), &SettingsDialog::update3D, this, &UVWidget::updateGL ); } UVWidget::~UVWidget() @@ -201,6 +188,18 @@ UVWidget::~UVWidget() nif = nullptr; } +void UVWidget::updateSettings() +{ + QSettings settings; + settings.beginGroup( "Settings/Render/Colors/" ); + + cfg.background = settings.value( "Background" ).value(); + cfg.highlight = settings.value( "Highlight" ).value(); + cfg.wireframe = settings.value( "Wireframe" ).value(); + + settings.endGroup(); +} + void UVWidget::initializeGL() { glMatrixMode( GL_MODELVIEW ); @@ -219,7 +218,7 @@ void UVWidget::initializeGL() glEnable( GL_MULTISAMPLE ); glDisable( GL_LIGHTING ); - qglClearColor( Options::bgColor() ); + qglClearColor( cfg.background ); if ( !texfile.isEmpty() ) bindTexture( texfile ); @@ -259,7 +258,7 @@ void UVWidget::paintGL() glPushMatrix(); glLoadIdentity(); - qglClearColor( Options::bgColor() ); + qglClearColor( cfg.background ); glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); glDisable( GL_DEPTH_TEST ); @@ -376,7 +375,7 @@ void UVWidget::paintGL() if ( !selectRect.isNull() ) { glLoadIdentity(); - glHighlightColor(); + glColor( Color4( cfg.highlight ) ); glBegin( GL_LINE_LOOP ); glVertex( mapToContents( selectRect.topLeft() ) ); glVertex( mapToContents( selectRect.topRight() ) ); @@ -387,7 +386,7 @@ void UVWidget::paintGL() if ( !selectPoly.isEmpty() ) { glLoadIdentity(); - glHighlightColor(); + glColor( Color4( cfg.highlight ) ); glBegin( GL_LINE_LOOP ); for ( const QPoint& p : selectPoly ) { glVertex( mapToContents( p ) ); @@ -414,9 +413,9 @@ void UVWidget::drawTexCoords() glScalef( 1.0f, 1.0f, 1.0f ); glTranslatef( -0.5f, -0.5f, 0.0f ); - Color4 nlColor( Options::nlColor() ); + Color4 nlColor( cfg.wireframe ); nlColor.setAlpha( 0.5f ); - Color4 hlColor( Options::hlColor() ); + Color4 hlColor( cfg.highlight ); hlColor.setAlpha( 0.5f ); glLineWidth( 1.0f ); @@ -762,6 +761,24 @@ bool UVWidget::setNifData( NifModel * nifModel, const QModelIndex & nifIndex ) nif = nifModel; iShape = nifIndex; + isDataOnSkin = false; + + // Version dependent actions + if ( nif && nif->getVersionNumber() != 0x14020007 ) { + coordSetGroup = new QActionGroup( this ); + connect( coordSetGroup, &QActionGroup::triggered, this, &UVWidget::selectCoordSet ); + + coordSetSelect = new QMenu( tr( "Select Coordinate Set" ) ); + addAction( coordSetSelect->menuAction() ); + connect( coordSetSelect, &QMenu::aboutToShow, this, &UVWidget::getCoordSets ); + + texSlotGroup = new QActionGroup( this ); + connect( texSlotGroup, &QActionGroup::triggered, this, &UVWidget::selectTexSlot ); + + menuTexSelect = new QMenu( tr( "Select Texture Slot" ) ); + addAction( menuTexSelect->menuAction() ); + connect( menuTexSelect, &QMenu::aboutToShow, this, &UVWidget::getTexSlots ); + } if ( nif ) { connect( nif, &NifModel::modelReset, this, &UVWidget::close ); @@ -770,9 +787,29 @@ bool UVWidget::setNifData( NifModel * nifModel, const QModelIndex & nifIndex ) connect( nif, &NifModel::rowsRemoved, this, &UVWidget::nifDataChanged ); } + if ( !nif ) + return false; + textures->setNifFolder( nif->getFolder() ); iShapeData = nif->getBlock( nif->getLink( iShape, "Data" ) ); + if ( nif->getVersionNumber() == 0x14020007 && nif->getUserVersion2() >= 100 ) { + iShapeData = nif->getIndex( iShape, "Vertex Data" ); + + auto vf = nif->get( iShape, "VF" ); + if ( (vf & 0x400) && nif->getUserVersion2() == 100 ) { + // Skinned SSE + auto skinID = nif->getLink( nif->getIndex( iShape, "Skin" ) ); + auto partID = nif->getLink( nif->getBlock( skinID, "NiSkinInstance" ), "Skin Partition" ); + iPartBlock = nif->getBlock( partID, "NiSkinPartition" ); + if ( !iPartBlock.isValid() ) + return false; + + isDataOnSkin = true; + + iShapeData = nif->getIndex( iPartBlock, "Vertex Data" ); + } + } if ( nif->inherits( iShapeData, "NiTriBasedGeomData" ) ) { iTexCoords = nif->getIndex( iShapeData, "UV Sets" ).child( 0, 0 ); @@ -784,6 +821,22 @@ bool UVWidget::setNifData( NifModel * nifModel, const QModelIndex & nifIndex ) if ( !setTexCoords() ) { return false; } + } else if ( nif->inherits( iShape, "BSTriShape" ) ) { + int numVerts = 0; + if ( !isDataOnSkin ) + numVerts = nif->get( iShape, "Num Vertices" ); + else + numVerts = nif->get( iPartBlock, "Data Size" ) / nif->get( iPartBlock, "Vertex Size" ); + + for ( int i = 0; i < numVerts; i++ ) { + texcoords << nif->get( nif->index( i, 0, iShapeData ), "UV" ); + } + + // Fake index so that isValid() checks do not fail + iTexCoords = iShape; + + if ( !setTexCoords() ) + return false; } for ( const auto l : @@ -864,7 +917,8 @@ bool UVWidget::setNifData( NifModel * nifModel, const QModelIndex & nifIndex ) bool UVWidget::setTexCoords() { - texcoords = nif->getArray( iTexCoords ); + if ( nif->inherits( iShape, "NiTriBasedGeom" ) ) + texcoords = nif->getArray( iTexCoords ); QVector tris; @@ -879,6 +933,16 @@ bool UVWidget::setTexCoords() for ( int r = 0; r < nif->rowCount( iPoints ); r++ ) { tris += triangulate( nif->getArray( iPoints.child( r, 0 ) ) ); } + } else if ( nif->inherits( iShape, "BSTriShape" ) ) { + if ( !isDataOnSkin ) { + tris = nif->getArray( iShape, "Triangles" ); + } else { + auto partIdx = nif->getIndex( iPartBlock, "Partition" ); + for ( int i = 0; i < nif->rowCount( partIdx ); i++ ) { + tris << nif->getArray( nif->index( i, 0, partIdx ), "Triangles" ); + } + } + } if ( tris.isEmpty() ) @@ -904,7 +968,25 @@ void UVWidget::updateNif() { if ( nif && iTexCoords.isValid() ) { disconnect( nif, &NifModel::dataChanged, this, &UVWidget::nifDataChanged ); - nif->setArray( iTexCoords, texcoords ); + nif->setState( BaseModel::Processing ); + + if ( nif->inherits( iShapeData, "NiTriBasedGeomData" ) ) { + nif->setArray( iTexCoords, texcoords ); + } else if ( nif->inherits( iShape, "BSTriShape" ) ) { + int numVerts = 0; + if ( !isDataOnSkin ) + numVerts = nif->get( iShape, "Num Vertices" ); + else + numVerts = nif->get( iPartBlock, "Data Size" ) / nif->get( iPartBlock, "Vertex Size" ); + + for ( int i = 0; i < numVerts; i++ ) { + nif->set( nif->index( i, 0, iShapeData ), "UV", HalfVector2( texcoords.value( i ) ) ); + } + + nif->dataChanged( iShape, iShape ); + } + + nif->restoreState(); connect( nif, &NifModel::dataChanged, this, &UVWidget::nifDataChanged ); } } @@ -1448,6 +1530,7 @@ void UVWidget::getCoordSets() { coordSetSelect->clear(); + // TODO: Broken for newer nif.xml where NiGeometryData has been corrected quint8 numUvSets = nif->get( iShapeData, "Num UV Sets" ); for ( int i = 0; i < numUvSets; i++ ) { diff --git a/src/widgets/uvedit.h b/src/widgets/uvedit.h index 80852a2eb..3630d5296 100644 --- a/src/widgets/uvedit.h +++ b/src/widgets/uvedit.h @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -99,9 +99,7 @@ class UVWidget final : public QGLWidget void mouseMoveEvent( QMouseEvent * e ) override final; void wheelEvent( QWheelEvent * e ) override final; -// should this be public slots? - -protected slots: +public slots: //! Does the selection contain this vertex? bool isSelected( int index ); //! Select a vertex @@ -123,6 +121,8 @@ protected slots: //! Rotate the selection void rotateSelection(); + void updateSettings(); + protected slots: void nifDataChanged( const QModelIndex & ); //! Build the texture slots menu @@ -185,7 +185,10 @@ protected slots: void updateNif(); NifModel * nif; - QPersistentModelIndex iShape, iShapeData, iTexCoords, iTex; + QPersistentModelIndex iShape, iShapeData, iTexCoords, iTex, iPartBlock; + + //! If mesh is skinned, different behavior is required for stream version 100 + bool isDataOnSkin = false; //! Submenu for texture slot selection QMenu * menuTexSelect; @@ -195,13 +198,14 @@ protected slots: QStringList validTexs; //! Names of texture slots - QStringList texnames; + static QStringList texnames; + //! Texture slot currently being operated on - int currentTexSlot; + int currentTexSlot = 0; //! Read texcoords from the nif bool setTexCoords(); //! Coordinate set currently in use - int currentCoordSet; + int currentCoordSet = 0; //! Submenu for coordinate set selection QMenu * coordSetSelect; @@ -226,6 +230,13 @@ protected slots: friend class UVWRotateCommand; QAction * aTextureBlend; + + struct Settings + { + QColor background; + QColor highlight; + QColor wireframe; + } cfg; }; //! Dialog for getting scaling factors diff --git a/src/widgets/valueedit.cpp b/src/widgets/valueedit.cpp index 0f9464520..eb33f2992 100644 --- a/src/widgets/valueedit.cpp +++ b/src/widgets/valueedit.cpp @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -57,13 +57,15 @@ ValueEdit::ValueEdit( QWidget * parent ) : QWidget( parent ), typ( NifValue::tNo bool ValueEdit::canEdit( NifValue::Type t ) { return t == NifValue::tByte || t == NifValue::tWord || t == NifValue::tInt || t == NifValue::tFlags - || t == NifValue::tLink || t == NifValue::tUpLink || t == NifValue::tFloat || t == NifValue::tText - || t == NifValue::tSizedString || t == NifValue::tLineString || t == NifValue::tChar8String - || t == NifValue::tShortString || t == NifValue::tStringIndex || t == NifValue::tString - || t == NifValue::tVector4 || t == NifValue::tVector3 || t == NifValue::tVector2 - || t == NifValue::tColor3 || t == NifValue::tColor4 - || t == NifValue::tMatrix || t == NifValue::tQuat || t == NifValue::tQuatXYZW - || t == NifValue::tTriangle || t == NifValue::tShort || t == NifValue::tUInt; + || t == NifValue::tLink || t == NifValue::tUpLink || t == NifValue::tFloat || t == NifValue::tText + || t == NifValue::tSizedString || t == NifValue::tLineString || t == NifValue::tChar8String + || t == NifValue::tShortString || t == NifValue::tStringIndex || t == NifValue::tString + || t == NifValue::tVector4 || t == NifValue::tVector3 || t == NifValue::tVector2 + || t == NifValue::tColor3 || t == NifValue::tColor4 || t == NifValue::tByteColor4 + || t == NifValue::tMatrix || t == NifValue::tQuat || t == NifValue::tQuatXYZW + || t == NifValue::tTriangle || t == NifValue::tShort || t == NifValue::tUInt || t == NifValue::tULittle32 + || t == NifValue::tHfloat || t == NifValue::tHalfVector3 || t == NifValue::tByteVector3 + || t == NifValue::tHalfVector2; } class CenterLabel final : public QLabel @@ -73,32 +75,75 @@ class CenterLabel final : public QLabel CenterLabel() : QLabel() { setAlignment( Qt::AlignCenter ); } }; -class UIntSpinBox final : public QSpinBox + +UnsignedValidator::UnsignedValidator( QObject * parent ) + : QValidator( parent ) { -public: - UIntSpinBox( QWidget * parent ) : QSpinBox( parent ) { setRange( INT_MIN, INT_MAX ); } +} -protected: - QString textFromValue( int i ) const override final - { - return QString::number( (unsigned int)i ); - } +QValidator::State UnsignedValidator::validate( QString & input, int & pos ) const +{ + if ( input.trimmed().isEmpty() || input.trimmed() == QLatin1String( "0x" ) ) + return Intermediate; + + bool ok; + uint val = input.toUInt( &ok, 0 ); + + if ( !ok || val > max ) + return Invalid; + else if ( val < min ) + return Intermediate; + + return Acceptable; +} + +void UnsignedValidator::setRange( uint minimum, uint maximum ) +{ + min = minimum; + max = maximum; +} + + +UIntSpinBox::UIntSpinBox( QWidget * parent ) : QSpinBox( parent ) +{ + validator = new UnsignedValidator( this ); +} + +QValidator::State UIntSpinBox::validate( QString & text, int & pos ) const +{ + return validator->validate( text, pos ); +} - int valueFromText( const QString & text ) const override final - { - // until we convert to a QLineEdit, this lets us put in numbers between - // INT_MAX and 2*INT_MAX by entering them as a signed value - return text.toLong(); +void UIntSpinBox::setValue( uint value ) +{ + QSpinBox::setValue( toInt( value ) ); +} + +QString UIntSpinBox::textFromValue( int value ) const +{ + return QString::number( toUInt( value ) ); +} + +int UIntSpinBox::valueFromText( const QString & text ) const +{ + bool ok; + QString txt = text; + uint newVal = txt.toUInt( &ok, 0 ); + + if ( !ok && !(prefix().isEmpty() && suffix().isEmpty()) ) { + newVal = cleanText().toUInt( &ok, 0 ); } -}; + + return toInt( newVal ); +} + void ValueEdit::setValue( const NifValue & v ) { typ = v.type(); if ( edit ) { - // segfaults with Qt 4.5: - //delete edit; + delete edit; edit = nullptr; resize( this->baseSize() ); } @@ -151,9 +196,11 @@ void ValueEdit::setValue( const NifValue & v ) } break; case NifValue::tUInt: + case NifValue::tULittle32: { - QSpinBox * ie = new UIntSpinBox( this ); + UIntSpinBox * ie = new UIntSpinBox( this ); ie->setFrame( false ); + ie->setRange( INT_MIN, INT_MAX ); ie->setValue( v.toCount() ); edit = ie; } @@ -172,6 +219,7 @@ void ValueEdit::setValue( const NifValue & v ) } break; case NifValue::tFloat: + case NifValue::tHfloat: { FloatEdit * fe = new FloatEdit( this ); /* @@ -208,6 +256,13 @@ void ValueEdit::setValue( const NifValue & v ) // te->setBaseSize( width(), height() * 5); // edit = te; //} break; + case NifValue::tByteColor4: + { + ColorEdit * ce = new ColorEdit( this ); + ce->setColor4( v.get() ); + edit = ce; + } + break; case NifValue::tColor4: { ColorEdit * ce = new ColorEdit( this ); @@ -229,6 +284,20 @@ void ValueEdit::setValue( const NifValue & v ) edit = ve; } break; + case NifValue::tByteVector3: + { + VectorEdit * ve = new VectorEdit( this ); + ve->setVector3( v.get() ); + edit = ve; + } + break; + case NifValue::tHalfVector3: + { + VectorEdit * ve = new VectorEdit( this ); + ve->setVector3( v.get() ); + edit = ve; + } + break; case NifValue::tVector3: { VectorEdit * ve = new VectorEdit( this ); @@ -236,6 +305,13 @@ void ValueEdit::setValue( const NifValue & v ) edit = ve; } break; + case NifValue::tHalfVector2: + { + VectorEdit * ve = new VectorEdit( this ); + ve->setVector2( v.get() ); + edit = ve; + } + break; case NifValue::tVector2: { VectorEdit * ve = new VectorEdit( this ); @@ -315,6 +391,7 @@ NifValue ValueEdit::getValue() const case NifValue::tFlags: case NifValue::tInt: case NifValue::tUInt: + case NifValue::tULittle32: case NifValue::tStringIndex: val.setCount( qobject_cast( edit )->value() ); break; @@ -333,17 +410,24 @@ NifValue ValueEdit::getValue() const } break; case NifValue::tFloat: + case NifValue::tHfloat: val.setFloat( qobject_cast( edit )->value() ); break; case NifValue::tLineString: case NifValue::tShortString: case NifValue::tChar8String: - val.fromString( qobject_cast( edit )->text() ); + val.setFromString( qobject_cast( edit )->text() ); break; case NifValue::tSizedString: case NifValue::tText: - val.fromString( qobject_cast( edit )->toPlainText() ); + val.setFromString( qobject_cast( edit )->toPlainText() ); break; + case NifValue::tByteColor4: + { + auto col = qobject_cast(edit)->getColor4(); + val.set( *static_cast(&col) ); + break; + } case NifValue::tColor4: val.set( qobject_cast( edit )->getColor4() ); break; @@ -353,9 +437,27 @@ NifValue ValueEdit::getValue() const case NifValue::tVector4: val.set( qobject_cast( edit )->getVector4() ); break; + case NifValue::tByteVector3: + { + auto vec = qobject_cast(edit)->getVector3(); + val.set( *static_cast(&vec) ); + break; + } + case NifValue::tHalfVector3: + { + auto vec = qobject_cast(edit)->getVector3(); + val.set( *static_cast(&vec) ); + break; + } case NifValue::tVector3: val.set( qobject_cast( edit )->getVector3() ); break; + case NifValue::tHalfVector2: + { + auto vec = qobject_cast(edit)->getVector2(); + val.set( *static_cast(&vec) ); + break; + } case NifValue::tVector2: val.set( qobject_cast( edit )->getVector2() ); break; diff --git a/src/widgets/valueedit.h b/src/widgets/valueedit.h index 47c1ad461..44c35d9a8 100644 --- a/src/widgets/valueedit.h +++ b/src/widgets/valueedit.h @@ -2,7 +2,7 @@ BSD License -Copyright (c) 2005-2012, NIF File Format Library and Tools +Copyright (c) 2005-2015, NIF File Format Library and Tools All rights reserved. Redistribution and use in source and binary forms, with or without @@ -37,6 +37,10 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include // Inherited #include // Inherited +#include // Inherited +#include // Inherited + +#include //! \file valueedit.h ValueEdit and other widgets @@ -246,4 +250,65 @@ public slots: void keyPressEvent( QKeyEvent * e ) override final; }; + +class UnsignedValidator : public QValidator +{ + Q_OBJECT + +public: + UnsignedValidator( QObject * parent ); + ~UnsignedValidator() {} + + QValidator::State validate( QString &, int & ) const; + + void setRange( uint min, uint max ); + + uint minimum() const { return min; } + uint maximum() const { return max; } + +private: + uint min = 0, max = 0xffffffff; +}; + + +class UIntSpinBox final : public QSpinBox +{ + Q_OBJECT + +public: + UIntSpinBox( QWidget * parent ); + + ~UIntSpinBox() + { + delete validator; + } + + QValidator::State validate( QString & text, int & pos ) const override; + +public slots: + void setValue( uint value ); + +protected: + QString textFromValue( int value ) const; + + int valueFromText( const QString & text ) const; + + uint toUInt( int i ) const + { + uint ui; + std::memcpy( &ui, &i, sizeof( ui ) ); + return ui; + } + + int toInt( uint ui ) const + { + int i; + std::memcpy( &i, &ui, sizeof( i ) ); + return i; + } + +private: + UnsignedValidator * validator; +}; + #endif diff --git a/src/widgets/xmlcheck.cpp b/src/widgets/xmlcheck.cpp index bf4e796d1..3ba5010d2 100644 --- a/src/widgets/xmlcheck.cpp +++ b/src/widgets/xmlcheck.cpp @@ -207,11 +207,11 @@ void TestShredder::run() QStringList extensions; if ( chkNif->isChecked() ) - extensions << "NIF (*.nif)" << "NIFCache (*.nifcache)" << "TEXCache (*.texcache)" << "PCPatch (*.pcpatch)"; + extensions << "*.nif" << "*.nifcache" << "*.texcache" << "*.pcpatch"; if ( chkKf->isChecked() ) - extensions << "Keyframe (*.kf)" << "Keyframe Animation (*.kfa)"; + extensions << "*.kf" << "*.kfa"; if ( chkKfm->isChecked() ) - extensions << "Keyframe Motion (*.kfm)"; + extensions << "*.kfm"; queue.init( directory->text(), extensions, recursive->isChecked() ); @@ -372,9 +372,7 @@ TestThread::~TestThread() void TestThread::run() { NifModel nif; - nif.setMessageMode( BaseModel::CollectMessages ); KfmModel kfm; - kfm.setMessageMode( BaseModel::CollectMessages ); QString filepath = queue->dequeue(); @@ -399,7 +397,7 @@ void TestThread::run() bool loaded = model->loadFromFile( filepath ); QString result = QString( "%1 (%2)" ).arg( filepath, model->getVersion() ); - QList messages = model->getMessages(); + QList messages = model->getMessages(); bool blk_match = false; @@ -418,7 +416,7 @@ void TestThread::run() // Don't show anything if block match is on but the requested type wasn't found & we're in block match mode if ( blockMatch.isEmpty() == true || blk_match == true ) { - for ( const Message& msg : messages ) { + for ( const TestMessage& msg : messages ) { if ( msg.type() != QtDebugMsg ) { result += "
    " + msg; rep |= true; @@ -452,9 +450,9 @@ static QString linkId( const NifModel * nif, QModelIndex idx ) return id; } -QList TestThread::checkLinks( const NifModel * nif, const QModelIndex & iParent, bool kf ) +QList TestThread::checkLinks( const NifModel * nif, const QModelIndex & iParent, bool kf ) { - QList messages; + QList messages; for ( int r = 0; r < nif->rowCount( iParent ); r++ ) { QModelIndex idx = iParent.child( r, 0 ); @@ -468,7 +466,7 @@ QList TestThread::checkLinks( const NifModel * nif, const QModelIndex & // if ( ! child && ! kf ) // messages.append( Message() << tr("unassigned parent link") << linkId( nif, idx ) ); } else if ( l >= nif->getBlockCount() ) { - messages.append( Message() << tr( "invalid link" ) << linkId( nif, idx ) ); + messages.append( TestMessage() << tr( "invalid link" ) << linkId( nif, idx ) ); } else { QString tmplt = nif->itemTmplt( idx ); @@ -476,7 +474,7 @@ QList TestThread::checkLinks( const NifModel * nif, const QModelIndex & QModelIndex iBlock = nif->getBlock( l ); if ( !nif->inherits( iBlock, tmplt ) ) - messages.append( Message() << tr( "link" ) << linkId( nif, idx ) << tr( "points to wrong block type" ) << nif->itemName( iBlock ) ); + messages.append( TestMessage() << tr( "link" ) << linkId( nif, idx ) << tr( "points to wrong block type" ) << nif->itemName( iBlock ) ); } } } diff --git a/src/widgets/xmlcheck.h b/src/widgets/xmlcheck.h index 8679567ec..bf474e55a 100644 --- a/src/widgets/xmlcheck.h +++ b/src/widgets/xmlcheck.h @@ -63,7 +63,7 @@ class TestThread final : public QThread protected: void run() override final; - QList checkLinks( const class NifModel * nif, const class QModelIndex & iParent, bool kf ); + QList checkLinks( const class NifModel * nif, const class QModelIndex & iParent, bool kf ); FileQueue * queue;