mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-01-27 03:35:27 +00:00
Merge branch openmw:master into mwdialogue-bindings
This commit is contained in:
commit
0986b103c5
@ -533,7 +533,7 @@ macOS14_Xcode15_arm64:
|
||||
|
||||
.Windows_Ninja_Base:
|
||||
tags:
|
||||
- windows
|
||||
- saas-windows-medium-amd64
|
||||
rules:
|
||||
- if: $CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||
before_script:
|
||||
@ -570,10 +570,10 @@ macOS14_Xcode15_arm64:
|
||||
- $env:CCACHE_BASEDIR = Get-Location
|
||||
- $env:CCACHE_DIR = "$(Get-Location)\ccache"
|
||||
- New-Item -Type Directory -Force -Path $env:CCACHE_DIR
|
||||
- New-Item -Type File -Force -Path MSVC2019_64_Ninja\.cmake\api\v1\query\codemodel-v2
|
||||
- sh CI/before_script.msvc.sh -c $config -p Win64 -v 2019 -k -V -N -b -t -C $multiview -E
|
||||
- New-Item -Type File -Force -Path MSVC2022_64_Ninja\.cmake\api\v1\query\codemodel-v2
|
||||
- sh CI/before_script.msvc.sh -c $config -p Win64 -v 2022 -k -V -N -b -t -C $multiview -E
|
||||
- Get-Volume
|
||||
- cd MSVC2019_64_Ninja
|
||||
- cd MSVC2022_64_Ninja
|
||||
- .\ActivateMSVC.ps1
|
||||
- cmake --build . --config $config
|
||||
- ccache --show-stats -v
|
||||
@ -583,47 +583,41 @@ macOS14_Xcode15_arm64:
|
||||
- Get-ChildItem -Recurse *.ilk | Remove-Item
|
||||
- |
|
||||
if (Get-ChildItem -Recurse *.pdb) {
|
||||
7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" '*.pdb' CI-ID.txt
|
||||
7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" '*.pdb' CI-ID.txt
|
||||
if (Test-Path env:AWS_ACCESS_KEY_ID) {
|
||||
aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp "..\..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" s3://openmw-artifacts/${artifactDirectory}
|
||||
aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp "..\..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" s3://openmw-artifacts/${artifactDirectory}
|
||||
}
|
||||
Push-Location ..
|
||||
..\CI\Store-Symbols.ps1
|
||||
if (Test-Path env:AWS_ACCESS_KEY_ID) {
|
||||
aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp --recursive --exclude * --include *.ex_ --include *.dl_ --include *.pd_ .\SymStore s3://openmw-sym
|
||||
}
|
||||
7z a -tzip "..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_sym_store.zip"))" '.\SymStore\*' $config\CI-ID.txt
|
||||
7z a -tzip "..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_sym_store.zip"))" '.\SymStore\*' $config\CI-ID.txt
|
||||
Pop-Location
|
||||
Get-ChildItem -Recurse *.pdb | Remove-Item
|
||||
}
|
||||
- 7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}.zip"))" '*'
|
||||
- 7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}.zip"))" '*'
|
||||
- |
|
||||
if (Test-Path env:AWS_ACCESS_KEY_ID) {
|
||||
aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp "..\..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}.zip"))" s3://openmw-artifacts/${artifactDirectory}
|
||||
aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp "..\..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}.zip"))" s3://openmw-artifacts/${artifactDirectory}
|
||||
}
|
||||
- if ($executables) { foreach ($exe in $executables.Split(',')) { & .\$exe } }
|
||||
after_script:
|
||||
- Get-Volume
|
||||
- Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log
|
||||
cache:
|
||||
key: ninja-v8
|
||||
key: ninja-2022-v9
|
||||
paths:
|
||||
- ccache
|
||||
- deps
|
||||
- MSVC2019_64_Ninja/deps/Qt
|
||||
- MSVC2022_64_Ninja/deps/Qt
|
||||
artifacts:
|
||||
when: always
|
||||
paths:
|
||||
- "*.zip"
|
||||
- "*.log"
|
||||
- MSVC2019_64_Ninja/*.log
|
||||
- MSVC2019_64_Ninja/*/*.log
|
||||
- MSVC2019_64_Ninja/*/*/*.log
|
||||
- MSVC2019_64_Ninja/*/*/*/*.log
|
||||
- MSVC2019_64_Ninja/*/*/*/*/*.log
|
||||
- MSVC2019_64_Ninja/*/*/*/*/*/*.log
|
||||
- MSVC2019_64_Ninja/*/*/*/*/*/*/*.log
|
||||
- MSVC2019_64_Ninja/*/*/*/*/*/*/*/*.log
|
||||
- MSVC2022_64_Ninja/*.log
|
||||
- MSVC2022_64_Ninja/**/*.log
|
||||
# When CCache doesn't exist (e.g. first build on a fork), build takes more than 1h, which is the default for forks.
|
||||
timeout: 2h
|
||||
|
||||
@ -656,7 +650,7 @@ macOS14_Xcode15_arm64:
|
||||
|
||||
.Windows_MSBuild_Base:
|
||||
tags:
|
||||
- windows
|
||||
- saas-windows-medium-amd64
|
||||
rules:
|
||||
- if: $CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||
before_script:
|
||||
@ -688,9 +682,9 @@ macOS14_Xcode15_arm64:
|
||||
- $time = (Get-Date -Format "HH:mm:ss")
|
||||
- echo ${time}
|
||||
- echo "started by ${GITLAB_USER_NAME}"
|
||||
- New-Item -Type File -Force -Path MSVC2019_64\.cmake\api\v1\query\codemodel-v2
|
||||
- sh CI/before_script.msvc.sh -c $config -p Win64 -v 2019 -k -V -b -t -C $multiview -E
|
||||
- cd MSVC2019_64
|
||||
- New-Item -Type File -Force -Path MSVC2022_64\.cmake\api\v1\query\codemodel-v2
|
||||
- sh CI/before_script.msvc.sh -c $config -p Win64 -v 2022 -k -V -b -t -C $multiview -E
|
||||
- cd MSVC2022_64
|
||||
- Get-Volume
|
||||
- cmake --build . --config $config
|
||||
- cd $config
|
||||
@ -699,46 +693,40 @@ macOS14_Xcode15_arm64:
|
||||
- Get-ChildItem -Recurse *.ilk | Remove-Item
|
||||
- |
|
||||
if (Get-ChildItem -Recurse *.pdb) {
|
||||
7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" '*.pdb' CI-ID.txt
|
||||
7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" '*.pdb' CI-ID.txt
|
||||
if (Test-Path env:AWS_ACCESS_KEY_ID) {
|
||||
aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp "..\..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" s3://openmw-artifacts/${artifactDirectory}
|
||||
aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp "..\..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" s3://openmw-artifacts/${artifactDirectory}
|
||||
}
|
||||
Push-Location ..
|
||||
..\CI\Store-Symbols.ps1
|
||||
if (Test-Path env:AWS_ACCESS_KEY_ID) {
|
||||
aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp --recursive --exclude * --include *.ex_ --include *.dl_ --include *.pd_ .\SymStore s3://openmw-sym
|
||||
}
|
||||
7z a -tzip "..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_sym_store.zip"))" '.\SymStore\*' $config\CI-ID.txt
|
||||
7z a -tzip "..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_sym_store.zip"))" '.\SymStore\*' $config\CI-ID.txt
|
||||
Pop-Location
|
||||
Get-ChildItem -Recurse *.pdb | Remove-Item
|
||||
}
|
||||
- 7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}.zip"))" '*'
|
||||
- 7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}.zip"))" '*'
|
||||
- |
|
||||
if (Test-Path env:AWS_ACCESS_KEY_ID) {
|
||||
aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp "..\..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}.zip"))" s3://openmw-artifacts/${artifactDirectory}
|
||||
aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp "..\..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}.zip"))" s3://openmw-artifacts/${artifactDirectory}
|
||||
}
|
||||
- if ($executables) { foreach ($exe in $executables.Split(',')) { & .\$exe } }
|
||||
after_script:
|
||||
- Get-Volume
|
||||
- Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log
|
||||
cache:
|
||||
key: msbuild-v8
|
||||
key: msbuild-2022-v9
|
||||
paths:
|
||||
- deps
|
||||
- MSVC2019_64/deps/Qt
|
||||
- MSVC2022_64/deps/Qt
|
||||
artifacts:
|
||||
when: always
|
||||
paths:
|
||||
- "*.zip"
|
||||
- "*.log"
|
||||
- MSVC2019_64/*.log
|
||||
- MSVC2019_64/*/*.log
|
||||
- MSVC2019_64/*/*/*.log
|
||||
- MSVC2019_64/*/*/*/*.log
|
||||
- MSVC2019_64/*/*/*/*/*.log
|
||||
- MSVC2019_64/*/*/*/*/*/*.log
|
||||
- MSVC2019_64/*/*/*/*/*/*/*.log
|
||||
- MSVC2019_64/*/*/*/*/*/*/*/*.log
|
||||
- MSVC2022_64/*.log
|
||||
- MSVC2022_64/**/*.log
|
||||
# When CCache doesn't exist (e.g. first build on a fork), build takes more than 1h, which is the default for forks.
|
||||
timeout: 2h
|
||||
|
||||
|
@ -46,6 +46,7 @@
|
||||
Bug #6657: Distant terrain tiles become black when using FWIW mod
|
||||
Bug #6661: Saved games that have no preview screenshot cause issues or crashes
|
||||
Bug #6716: mwscript comparison operator handling is too restrictive
|
||||
Bug #6723: "Turn to movement direction" makes the player rotate wildly with COLLADA
|
||||
Bug #6754: Beast to Non-beast transformation mod is not working on OpenMW
|
||||
Bug #6758: Main menu background video can be stopped by opening the options menu
|
||||
Bug #6807: Ultimate Galleon is not working properly
|
||||
@ -169,6 +170,7 @@
|
||||
Bug #7899: Editor: Doors can't be unlocked
|
||||
Bug #7901: Editor: Teleport-related fields shouldn't be editable if a ref does not teleport
|
||||
Bug #7908: Key bindings names in the settings menu are layout-specific
|
||||
Bug #7943: Using "addSoulGem" and "dropSoulGem" commands to creatures works only with "Weapon & Shield" flagged ones
|
||||
Feature #2566: Handle NAM9 records for manual cell references
|
||||
Feature #3537: Shader-based water ripples
|
||||
Feature #5173: Support for NiFogProperty
|
||||
@ -218,6 +220,7 @@
|
||||
Feature #7652: Sort inactive post processing shaders list properly
|
||||
Feature #7698: Implement sAbsorb, sDamage, sDrain, sFortify and sRestore
|
||||
Feature #7709: Improve resolution selection in Launcher
|
||||
Feature #7777: Support external Bethesda material files (BGSM/BGEM)
|
||||
Feature #7792: Support Timescale Clouds
|
||||
Feature #7795: Support MaxNumberRipples INI setting
|
||||
Feature #7805: Lua Menu context
|
||||
|
@ -357,6 +357,16 @@ add_qt_image_dlls() {
|
||||
QT_IMAGEFORMATS[$CONFIG]="${QT_IMAGEFORMATS[$CONFIG]} $@"
|
||||
}
|
||||
|
||||
declare -A QT_ICONENGINES
|
||||
QT_ICONENGINES["Release"]=""
|
||||
QT_ICONENGINES["Debug"]=""
|
||||
QT_ICONENGINES["RelWithDebInfo"]=""
|
||||
add_qt_icon_dlls() {
|
||||
local CONFIG=$1
|
||||
shift
|
||||
QT_ICONENGINES[$CONFIG]="${QT_ICONENGINES[$CONFIG]} $@"
|
||||
}
|
||||
|
||||
if [ -z $PLATFORM ]; then
|
||||
PLATFORM="$(uname -m)"
|
||||
fi
|
||||
@ -563,7 +573,7 @@ ICU_VER="70_1"
|
||||
LUAJIT_VER="v2.1.0-beta3-452-g7a0cf5fd"
|
||||
LZ4_VER="1.9.2"
|
||||
OPENAL_VER="1.23.0"
|
||||
QT_VER="5.15.2"
|
||||
QT_VER="6.6.2"
|
||||
|
||||
OSG_ARCHIVE_NAME="OSGoS 3.6.5"
|
||||
OSG_ARCHIVE="OSGoS-3.6.5-123-g68c5c573d-msvc${OSG_MSVC_YEAR}-win${BITS}"
|
||||
@ -894,7 +904,7 @@ printf "Qt ${QT_VER}... "
|
||||
printf "Exists. "
|
||||
elif [ -z $SKIP_EXTRACT ]; then
|
||||
pushd "$DEPS" > /dev/null
|
||||
AQT_VERSION="v3.1.7"
|
||||
AQT_VERSION="v3.1.12"
|
||||
if ! [ -f "aqt_x64-${AQT_VERSION}.exe" ]; then
|
||||
download "aqt ${AQT_VERSION}"\
|
||||
"https://github.com/miurahr/aqtinstall/releases/download/${AQT_VERSION}/aqt_x64.exe" \
|
||||
@ -915,6 +925,9 @@ printf "Qt ${QT_VER}... "
|
||||
echo Done.
|
||||
fi
|
||||
|
||||
QT_MAJOR_VER=$(echo "${QT_VER}" | awk -F '[.]' '{printf "%d", $1}')
|
||||
QT_MINOR_VER=$(echo "${QT_VER}" | awk -F '[.]' '{printf "%d", $2}')
|
||||
|
||||
cd $QT_SDK
|
||||
for CONFIGURATION in ${CONFIGURATIONS[@]}; do
|
||||
if [ $CONFIGURATION == "Debug" ]; then
|
||||
@ -922,14 +935,24 @@ printf "Qt ${QT_VER}... "
|
||||
else
|
||||
DLLSUFFIX=""
|
||||
fi
|
||||
if [ "${QT_VER:0:1}" -eq "6" ]; then
|
||||
add_runtime_dlls $CONFIGURATION "$(pwd)/bin/Qt${QT_VER:0:1}"{Core,Gui,Network,OpenGL,OpenGLWidgets,Widgets,Svg}${DLLSUFFIX}.dll
|
||||
|
||||
if [ "${QT_MAJOR_VER}" -eq 6 ]; then
|
||||
add_runtime_dlls $CONFIGURATION "$(pwd)/bin/Qt${QT_MAJOR_VER}"{Core,Gui,Network,OpenGL,OpenGLWidgets,Widgets,Svg}${DLLSUFFIX}.dll
|
||||
|
||||
# Since Qt 6.7.0 plugin is called "qmodernwindowsstyle"
|
||||
if [ "${QT_MINOR_VER}" -ge 7 ]; then
|
||||
add_qt_style_dlls $CONFIGURATION "$(pwd)/plugins/styles/qmodernwindowsstyle${DLLSUFFIX}.dll"
|
||||
else
|
||||
add_qt_style_dlls $CONFIGURATION "$(pwd)/plugins/styles/qwindowsvistastyle${DLLSUFFIX}.dll"
|
||||
fi
|
||||
else
|
||||
add_runtime_dlls $CONFIGURATION "$(pwd)/bin/Qt${QT_VER:0:1}"{Core,Gui,Network,OpenGL,Widgets,Svg}${DLLSUFFIX}.dll
|
||||
add_runtime_dlls $CONFIGURATION "$(pwd)/bin/Qt${QT_MAJOR_VER}"{Core,Gui,Network,OpenGL,Widgets,Svg}${DLLSUFFIX}.dll
|
||||
add_qt_style_dlls $CONFIGURATION "$(pwd)/plugins/styles/qwindowsvistastyle${DLLSUFFIX}.dll"
|
||||
fi
|
||||
|
||||
add_qt_platform_dlls $CONFIGURATION "$(pwd)/plugins/platforms/qwindows${DLLSUFFIX}.dll"
|
||||
add_qt_style_dlls $CONFIGURATION "$(pwd)/plugins/styles/qwindowsvistastyle${DLLSUFFIX}.dll"
|
||||
add_qt_image_dlls $CONFIGURATION "$(pwd)/plugins/imageformats/qsvg${DLLSUFFIX}.dll"
|
||||
add_qt_icon_dlls $CONFIGURATION "$(pwd)/plugins/iconengines/qsvgicon${DLLSUFFIX}.dll"
|
||||
done
|
||||
echo Done.
|
||||
}
|
||||
@ -1123,7 +1146,7 @@ fi
|
||||
echo " $(basename $DLL)"
|
||||
cp "$DLL" "${DLL_PREFIX}styles"
|
||||
done
|
||||
|
||||
echo
|
||||
echo "- Qt Image Format DLLs..."
|
||||
mkdir -p ${DLL_PREFIX}imageformats
|
||||
for DLL in ${QT_IMAGEFORMATS[$CONFIGURATION]}; do
|
||||
@ -1131,6 +1154,13 @@ fi
|
||||
cp "$DLL" "${DLL_PREFIX}imageformats"
|
||||
done
|
||||
echo
|
||||
echo "- Qt Icon Engine DLLs..."
|
||||
mkdir -p ${DLL_PREFIX}iconengines
|
||||
for DLL in ${QT_ICONENGINES[$CONFIGURATION]}; do
|
||||
echo " $(basename $DLL)"
|
||||
cp "$DLL" "${DLL_PREFIX}iconengines"
|
||||
done
|
||||
echo
|
||||
done
|
||||
#fi
|
||||
|
||||
|
@ -825,6 +825,18 @@ if (OPENMW_OSX_DEPLOYMENT AND APPLE)
|
||||
get_filename_component(QT_QMACSTYLE_PLUGIN_NAME "${QT_QMACSTYLE_PLUGIN_PATH}" NAME)
|
||||
configure_file("${QT_QMACSTYLE_PLUGIN_PATH}" "${APP_BUNDLE_DIR}/Contents/PlugIns/${QT_QMACSTYLE_PLUGIN_GROUP}/${QT_QMACSTYLE_PLUGIN_NAME}" COPYONLY)
|
||||
|
||||
get_property(QT_QSVG_PLUGIN_PATH TARGET Qt${QT_VERSION_MAJOR}::QSvgPlugin PROPERTY LOCATION_RELEASE)
|
||||
get_filename_component(QT_QSVG_PLUGIN_DIR "${QT_QSVG_PLUGIN_PATH}" DIRECTORY)
|
||||
get_filename_component(QT_QSVG_PLUGIN_GROUP "${QT_QSVG_PLUGIN_DIR}" NAME)
|
||||
get_filename_component(QT_QSVG_PLUGIN_NAME "${QT_QSVG_PLUGIN_PATH}" NAME)
|
||||
configure_file("${QT_QSVG_PLUGIN_PATH}" "${APP_BUNDLE_DIR}/Contents/PlugIns/${QT_QSVG_PLUGIN_GROUP}/${QT_QSVG_PLUGIN_NAME}" COPYONLY)
|
||||
|
||||
get_property(QT_QSVG_ICON_PLUGIN_PATH TARGET Qt${QT_VERSION_MAJOR}::QSvgIconPlugin PROPERTY LOCATION_RELEASE)
|
||||
get_filename_component(QT_QSVG_ICON_PLUGIN_DIR "${QT_QSVG_ICON_PLUGIN_PATH}" DIRECTORY)
|
||||
get_filename_component(QT_QSVG_ICON_PLUGIN_GROUP "${QT_QSVG_ICON_PLUGIN_DIR}" NAME)
|
||||
get_filename_component(QT_QSVG_ICON_PLUGIN_NAME "${QT_QSVG_ICON_PLUGIN_PATH}" NAME)
|
||||
configure_file("${QT_QSVG_ICON_PLUGIN_PATH}" "${APP_BUNDLE_DIR}/Contents/PlugIns/${QT_QSVG_ICON_PLUGIN_GROUP}/${QT_QSVG_ICON_PLUGIN_NAME}" COPYONLY)
|
||||
|
||||
configure_file("${OpenMW_SOURCE_DIR}/files/mac/qt.conf" "${APP_BUNDLE_DIR}/Contents/Resources/qt.conf" COPYONLY)
|
||||
|
||||
if (BUILD_OPENCS)
|
||||
@ -832,13 +844,9 @@ if (OPENMW_OSX_DEPLOYMENT AND APPLE)
|
||||
set(OPENCS_BUNDLE_NAME "${OPENCS_BUNDLE_NAME_TMP}.app")
|
||||
configure_file("${QT_COCOA_PLUGIN_PATH}" "${OPENCS_BUNDLE_NAME}/Contents/PlugIns/${QT_COCOA_PLUGIN_GROUP}/${QT_COCOA_PLUGIN_NAME}" COPYONLY)
|
||||
configure_file("${QT_QMACSTYLE_PLUGIN_PATH}" "${OPENCS_BUNDLE_NAME}/Contents/PlugIns/${QT_QMACSTYLE_PLUGIN_GROUP}/${QT_QMACSTYLE_PLUGIN_NAME}" COPYONLY)
|
||||
configure_file("${OpenMW_SOURCE_DIR}/files/mac/qt.conf" "${OPENCS_BUNDLE_NAME}/Contents/Resources/qt.conf" COPYONLY)
|
||||
|
||||
get_property(QT_QSVG_PLUGIN_PATH TARGET Qt${QT_VERSION_MAJOR}::QSvgPlugin PROPERTY LOCATION_RELEASE)
|
||||
get_filename_component(QT_QSVG_PLUGIN_DIR "${QT_QSVG_PLUGIN_PATH}" DIRECTORY)
|
||||
get_filename_component(QT_QSVG_PLUGIN_GROUP "${QT_QSVG_PLUGIN_DIR}" NAME)
|
||||
get_filename_component(QT_QSVG_PLUGIN_NAME "${QT_QSVG_PLUGIN_PATH}" NAME)
|
||||
configure_file("${QT_QSVG_PLUGIN_PATH}" "${OPENCS_BUNDLE_NAME}/Contents/PlugIns/${QT_QSVG_PLUGIN_GROUP}/${QT_QSVG_PLUGIN_NAME}" COPYONLY)
|
||||
configure_file("${QT_QSVG_ICON_PLUGIN_PATH}" "${OPENCS_BUNDLE_NAME}/Contents/PlugIns/${QT_QSVG_ICON_PLUGIN_GROUP}/${QT_QSVG_ICON_PLUGIN_NAME}" COPYONLY)
|
||||
configure_file("${OpenMW_SOURCE_DIR}/files/mac/qt.conf" "${OPENCS_BUNDLE_NAME}/Contents/Resources/qt.conf" COPYONLY)
|
||||
endif ()
|
||||
|
||||
install(DIRECTORY "${APP_BUNDLE_DIR}" USE_SOURCE_PERMISSIONS DESTINATION "." COMPONENT Runtime)
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include <components/files/multidircollection.hpp>
|
||||
#include <components/misc/strings/conversion.hpp>
|
||||
#include <components/platform/platform.hpp>
|
||||
#include <components/resource/bgsmfilemanager.hpp>
|
||||
#include <components/resource/bulletshape.hpp>
|
||||
#include <components/resource/bulletshapemanager.hpp>
|
||||
#include <components/resource/foreachbulletobject.hpp>
|
||||
@ -173,7 +174,8 @@ namespace
|
||||
constexpr double expiryDelay = 0;
|
||||
Resource::ImageManager imageManager(&vfs, expiryDelay);
|
||||
Resource::NifFileManager nifFileManager(&vfs, &encoder.getStatelessEncoder());
|
||||
Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager, expiryDelay);
|
||||
Resource::BgsmFileManager bgsmFileManager(&vfs, expiryDelay);
|
||||
Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager, &bgsmFileManager, expiryDelay);
|
||||
Resource::BulletShapeManager bulletShapeManager(&vfs, &sceneManager, &nifFileManager, expiryDelay);
|
||||
|
||||
Resource::forEachBulletObject(
|
||||
|
@ -83,7 +83,7 @@ target_link_libraries(openmw-launcher
|
||||
components_qt
|
||||
)
|
||||
|
||||
target_link_libraries(openmw-launcher Qt::Widgets Qt::Core)
|
||||
target_link_libraries(openmw-launcher Qt::Widgets Qt::Core Qt::Svg)
|
||||
|
||||
if (BUILD_WITH_CODE_COVERAGE)
|
||||
target_compile_options(openmw-launcher PRIVATE --coverage)
|
||||
|
@ -19,6 +19,7 @@
|
||||
#include <components/files/conversion.hpp>
|
||||
#include <components/files/multidircollection.hpp>
|
||||
#include <components/platform/platform.hpp>
|
||||
#include <components/resource/bgsmfilemanager.hpp>
|
||||
#include <components/resource/bulletshapemanager.hpp>
|
||||
#include <components/resource/imagemanager.hpp>
|
||||
#include <components/resource/niffilemanager.hpp>
|
||||
@ -220,7 +221,8 @@ namespace NavMeshTool
|
||||
|
||||
Resource::ImageManager imageManager(&vfs, expiryDelay);
|
||||
Resource::NifFileManager nifFileManager(&vfs, &encoder.getStatelessEncoder());
|
||||
Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager, expiryDelay);
|
||||
Resource::BgsmFileManager bgsmFileManager(&vfs, expiryDelay);
|
||||
Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager, &bgsmFileManager, expiryDelay);
|
||||
Resource::BulletShapeManager bulletShapeManager(&vfs, &sceneManager, &nifFileManager, expiryDelay);
|
||||
DetourNavigator::RecastGlobalAllocator::init();
|
||||
DetourNavigator::Settings navigatorSettings = DetourNavigator::makeSettingsFromSettingsManager();
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <components/bgsm/file.hpp>
|
||||
#include <components/files/configurationmanager.hpp>
|
||||
#include <components/files/constrainedfilestream.hpp>
|
||||
#include <components/files/conversion.hpp>
|
||||
@ -25,7 +26,7 @@
|
||||
namespace bpo = boost::program_options;
|
||||
|
||||
/// See if the file has the named extension
|
||||
bool hasExtension(const std::filesystem::path& filename, const std::string& extensionToFind)
|
||||
bool hasExtension(const std::filesystem::path& filename, std::string_view extensionToFind)
|
||||
{
|
||||
const auto extension = Files::pathToUnicodeString(filename.extension());
|
||||
return Misc::StringUtils::ciEqual(extension, extensionToFind);
|
||||
@ -36,6 +37,13 @@ bool isNIF(const std::filesystem::path& filename)
|
||||
{
|
||||
return hasExtension(filename, ".nif") || hasExtension(filename, ".kf");
|
||||
}
|
||||
|
||||
/// Check if the file is a material file.
|
||||
bool isMaterial(const std::filesystem::path& filename)
|
||||
{
|
||||
return hasExtension(filename, ".bgem") || hasExtension(filename, ".bgsm");
|
||||
}
|
||||
|
||||
/// See if the file has the "bsa" extension.
|
||||
bool isBSA(const std::filesystem::path& filename)
|
||||
{
|
||||
@ -51,16 +59,17 @@ std::unique_ptr<VFS::Archive> makeArchive(const std::filesystem::path& path)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void readNIF(
|
||||
void readFile(
|
||||
const std::filesystem::path& source, const std::filesystem::path& path, const VFS::Manager* vfs, bool quiet)
|
||||
{
|
||||
const std::string pathStr = Files::pathToUnicodeString(path);
|
||||
const bool isNif = isNIF(path);
|
||||
if (!quiet)
|
||||
{
|
||||
if (hasExtension(path, ".kf"))
|
||||
std::cout << "Reading KF file '" << pathStr << "'";
|
||||
if (isNif)
|
||||
std::cout << "Reading " << (hasExtension(path, ".nif") ? "NIF" : "KF") << " file '" << pathStr << "'";
|
||||
else
|
||||
std::cout << "Reading NIF file '" << pathStr << "'";
|
||||
std::cout << "Reading " << (hasExtension(path, ".bgsm") ? "BGSM" : "BGEM") << " file '" << pathStr << "'";
|
||||
if (!source.empty())
|
||||
std::cout << " from '" << Files::pathToUnicodeString(isBSA(source) ? source.filename() : source) << "'";
|
||||
std::cout << std::endl;
|
||||
@ -68,12 +77,22 @@ void readNIF(
|
||||
const std::filesystem::path fullPath = !source.empty() ? source / path : path;
|
||||
try
|
||||
{
|
||||
Nif::NIFFile file(Files::pathToUnicodeString(fullPath));
|
||||
Nif::Reader reader(file, nullptr);
|
||||
if (vfs != nullptr)
|
||||
reader.parse(vfs->get(pathStr));
|
||||
if (isNif)
|
||||
{
|
||||
Nif::NIFFile file(Files::pathToUnicodeString(fullPath));
|
||||
Nif::Reader reader(file, nullptr);
|
||||
if (vfs != nullptr)
|
||||
reader.parse(vfs->get(pathStr));
|
||||
else
|
||||
reader.parse(Files::openConstrainedFileStream(fullPath));
|
||||
}
|
||||
else
|
||||
reader.parse(Files::openConstrainedFileStream(fullPath));
|
||||
{
|
||||
if (vfs != nullptr)
|
||||
Bgsm::parse(vfs->get(pathStr));
|
||||
else
|
||||
Bgsm::parse(Files::openConstrainedFileStream(fullPath));
|
||||
}
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
@ -95,11 +114,11 @@ void readVFS(std::unique_ptr<VFS::Archive>&& archive, const std::filesystem::pat
|
||||
vfs.addArchive(std::move(archive));
|
||||
vfs.buildIndex();
|
||||
|
||||
for (const auto& name : vfs.getRecursiveDirectoryIterator(""))
|
||||
for (const auto& name : vfs.getRecursiveDirectoryIterator())
|
||||
{
|
||||
if (isNIF(name.value()))
|
||||
if (isNIF(name.value()) || isMaterial(name.value()))
|
||||
{
|
||||
readNIF(archivePath, name.value(), &vfs, quiet);
|
||||
readFile(archivePath, name.value(), &vfs, quiet);
|
||||
}
|
||||
}
|
||||
|
||||
@ -129,10 +148,10 @@ void readVFS(std::unique_ptr<VFS::Archive>&& archive, const std::filesystem::pat
|
||||
bool parseOptions(int argc, char** argv, Files::PathContainer& files, Files::PathContainer& archives,
|
||||
bool& writeDebugLog, bool& quiet)
|
||||
{
|
||||
bpo::options_description desc(R"(Ensure that OpenMW can use the provided NIF, KF and BSA/BA2 files
|
||||
bpo::options_description desc(R"(Ensure that OpenMW can use the provided NIF, KF, BGEM/BGSM and BSA/BA2 files
|
||||
|
||||
Usages:
|
||||
niftest <nif files, kf files, BSA/BA2 files, or directories>
|
||||
niftest <nif files, kf files, bgem/bgsm files, BSA/BA2 files, or directories>
|
||||
Scan the file or directories for NIF errors.
|
||||
|
||||
Allowed options)");
|
||||
@ -221,9 +240,9 @@ int main(int argc, char** argv)
|
||||
const std::string pathStr = Files::pathToUnicodeString(path);
|
||||
try
|
||||
{
|
||||
if (isNIF(path))
|
||||
if (isNIF(path) || isMaterial(path))
|
||||
{
|
||||
readNIF({}, path, vfs.get(), quiet);
|
||||
readFile({}, path, vfs.get(), quiet);
|
||||
}
|
||||
else if (auto archive = makeArchive(path))
|
||||
{
|
||||
@ -231,7 +250,7 @@ int main(int argc, char** argv)
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "Error: '" << pathStr << "' is not a NIF/KF file, BSA/BA2 archive, or directory"
|
||||
std::cerr << "Error: '" << pathStr << "' is not a NIF/KF/BGEM/BGSM file, BSA/BA2 archive, or directory"
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
|
@ -257,6 +257,7 @@ endif()
|
||||
|
||||
if (WIN32)
|
||||
target_link_libraries(openmw-cs-lib ${Boost_LOCALE_LIBRARY})
|
||||
target_sources(openmw-cs PRIVATE ${CMAKE_SOURCE_DIR}/files/windows/openmw-cs.exe.manifest)
|
||||
endif()
|
||||
|
||||
if (WIN32 AND BUILD_OPENCS)
|
||||
|
@ -324,7 +324,6 @@ namespace CSMWorld
|
||||
{ ColumnId_MaxAttack, "Max Attack" },
|
||||
{ ColumnId_CreatureMisc, "Creature Misc" },
|
||||
|
||||
{ ColumnId_Idle1, "Idle 1" },
|
||||
{ ColumnId_Idle2, "Idle 2" },
|
||||
{ ColumnId_Idle3, "Idle 3" },
|
||||
{ ColumnId_Idle4, "Idle 4" },
|
||||
@ -332,6 +331,7 @@ namespace CSMWorld
|
||||
{ ColumnId_Idle6, "Idle 6" },
|
||||
{ ColumnId_Idle7, "Idle 7" },
|
||||
{ ColumnId_Idle8, "Idle 8" },
|
||||
{ ColumnId_Idle9, "Idle 9" },
|
||||
|
||||
{ ColumnId_RegionWeather, "Weather" },
|
||||
{ ColumnId_WeatherName, "Type" },
|
||||
|
@ -310,14 +310,14 @@ namespace CSMWorld
|
||||
ColumnId_MaxAttack = 284,
|
||||
ColumnId_CreatureMisc = 285,
|
||||
|
||||
ColumnId_Idle1 = 286,
|
||||
ColumnId_Idle2 = 287,
|
||||
ColumnId_Idle3 = 288,
|
||||
ColumnId_Idle4 = 289,
|
||||
ColumnId_Idle5 = 290,
|
||||
ColumnId_Idle6 = 291,
|
||||
ColumnId_Idle7 = 292,
|
||||
ColumnId_Idle8 = 293,
|
||||
ColumnId_Idle2 = 286,
|
||||
ColumnId_Idle3 = 287,
|
||||
ColumnId_Idle4 = 288,
|
||||
ColumnId_Idle5 = 289,
|
||||
ColumnId_Idle6 = 290,
|
||||
ColumnId_Idle7 = 291,
|
||||
ColumnId_Idle8 = 292,
|
||||
ColumnId_Idle9 = 293,
|
||||
|
||||
ColumnId_RegionWeather = 294,
|
||||
ColumnId_WeatherName = 295,
|
||||
|
@ -210,7 +210,6 @@ CSMWorld::RefIdCollection::RefIdCollection()
|
||||
mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_AiDuration, CSMWorld::ColumnBase::Display_Integer));
|
||||
mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_AiWanderToD, CSMWorld::ColumnBase::Display_Integer));
|
||||
|
||||
mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_Idle1, CSMWorld::ColumnBase::Display_Integer));
|
||||
mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_Idle2, CSMWorld::ColumnBase::Display_Integer));
|
||||
mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_Idle3, CSMWorld::ColumnBase::Display_Integer));
|
||||
mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_Idle4, CSMWorld::ColumnBase::Display_Integer));
|
||||
@ -218,6 +217,7 @@ CSMWorld::RefIdCollection::RefIdCollection()
|
||||
mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_Idle6, CSMWorld::ColumnBase::Display_Integer));
|
||||
mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_Idle7, CSMWorld::ColumnBase::Display_Integer));
|
||||
mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_Idle8, CSMWorld::ColumnBase::Display_Integer));
|
||||
mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_Idle9, CSMWorld::ColumnBase::Display_Integer));
|
||||
|
||||
mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_AiWanderRepeat, CSMWorld::ColumnBase::Display_Boolean));
|
||||
mColumns.back().addColumn(
|
||||
|
@ -28,7 +28,7 @@ void CSMWorld::Resources::recreate(const VFS::Manager* vfs, const char* const* e
|
||||
|
||||
size_t baseSize = mBaseDirectory.size();
|
||||
|
||||
for (const auto& filepath : vfs->getRecursiveDirectoryIterator(""))
|
||||
for (const auto& filepath : vfs->getRecursiveDirectoryIterator())
|
||||
{
|
||||
const std::string_view view = filepath.view();
|
||||
if (view.size() < baseSize + 1 || !view.starts_with(mBaseDirectory) || view[baseSize] != '/')
|
||||
|
@ -10,7 +10,7 @@
|
||||
#include <QScreen>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
QPushButton* CSVDoc::StartupDialogue::addButton(const QString& label, const QIcon& icon)
|
||||
QPushButton* CSVDoc::StartupDialogue::addButton(const QString& label, const QString& icon)
|
||||
{
|
||||
int column = mColumn--;
|
||||
|
||||
@ -39,13 +39,13 @@ QWidget* CSVDoc::StartupDialogue::createButtons()
|
||||
mLayout = new QGridLayout(widget);
|
||||
|
||||
/// \todo add icons
|
||||
QPushButton* loadDocument = addButton("Edit A Content File", QIcon(":startup/edit-content"));
|
||||
QPushButton* loadDocument = addButton("Edit A Content File", ":startup/edit-content");
|
||||
connect(loadDocument, &QPushButton::clicked, this, &StartupDialogue::loadDocument);
|
||||
|
||||
QPushButton* createAddon = addButton("Create A New Addon", QIcon(":startup/create-addon"));
|
||||
QPushButton* createAddon = addButton("Create A New Addon", ":startup/create-addon");
|
||||
connect(createAddon, &QPushButton::clicked, this, &StartupDialogue::createAddon);
|
||||
|
||||
QPushButton* createGame = addButton("Create A New Game", QIcon(":startup/create-game"));
|
||||
QPushButton* createGame = addButton("Create A New Game", ":startup/create-game");
|
||||
connect(createGame, &QPushButton::clicked, this, &StartupDialogue::createGame);
|
||||
|
||||
for (int i = 0; i < 3; ++i)
|
||||
|
@ -20,7 +20,7 @@ namespace CSVDoc
|
||||
int mColumn;
|
||||
QGridLayout* mLayout;
|
||||
|
||||
QPushButton* addButton(const QString& label, const QIcon& icon);
|
||||
QPushButton* addButton(const QString& label, const QString& icon);
|
||||
|
||||
QWidget* createButtons();
|
||||
|
||||
|
@ -1110,7 +1110,16 @@ void CSVDoc::View::updateWidth(bool isGrowLimit, int minSubViewWidth)
|
||||
{
|
||||
QRect rect;
|
||||
if (isGrowLimit)
|
||||
rect = QApplication::screenAt(pos())->geometry();
|
||||
{
|
||||
// Widget position can be negative, we should clamp it.
|
||||
QPoint position = pos();
|
||||
if (position.x() <= 0)
|
||||
position.setX(0);
|
||||
if (position.y() <= 0)
|
||||
position.setY(0);
|
||||
|
||||
rect = QApplication::screenAt(position)->geometry();
|
||||
}
|
||||
else
|
||||
rect = desktopRect();
|
||||
|
||||
|
@ -62,7 +62,7 @@ namespace CSVRender
|
||||
|
||||
osg::ref_ptr<osg::PositionAttitudeTransform> mBaseNode;
|
||||
SceneUtil::Skeleton* mSkeleton;
|
||||
SceneUtil::NodeMapVisitor::NodeMap mNodeMap;
|
||||
SceneUtil::NodeMap mNodeMap;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,7 @@
|
||||
class QWidget;
|
||||
|
||||
CSVRender::InstanceMoveMode::InstanceMoveMode(QWidget* parent)
|
||||
: ModeButton(QIcon(QPixmap(":scenetoolbar/transform-move")),
|
||||
: ModeButton(QIcon(":scenetoolbar/transform-move"),
|
||||
"Move selected instances"
|
||||
"<ul><li>Use {scene-edit-primary} to move instances around freely</li>"
|
||||
"<li>Use {scene-edit-secondary} to move instances around within the grid</li>"
|
||||
|
@ -94,7 +94,7 @@ void CSVWidget::SceneToolMode::showPanel(const QPoint& position)
|
||||
|
||||
void CSVWidget::SceneToolMode::addButton(const std::string& icon, const std::string& id, const QString& tooltip)
|
||||
{
|
||||
ModeButton* button = new ModeButton(QIcon(QPixmap(icon.c_str())), tooltip, mPanel);
|
||||
ModeButton* button = new ModeButton(QIcon(icon.c_str()), tooltip, mPanel);
|
||||
addButton(button, id);
|
||||
}
|
||||
|
||||
|
@ -60,10 +60,10 @@ CSVWidget::ShapeBrushWindow::ShapeBrushWindow(CSMDoc::Document& document, QWidge
|
||||
: QFrame(parent, Qt::Popup)
|
||||
, mDocument(document)
|
||||
{
|
||||
mButtonPoint = new QPushButton(QIcon(QPixmap(":scenetoolbar/brush-point")), "", this);
|
||||
mButtonSquare = new QPushButton(QIcon(QPixmap(":scenetoolbar/brush-square")), "", this);
|
||||
mButtonCircle = new QPushButton(QIcon(QPixmap(":scenetoolbar/brush-circle")), "", this);
|
||||
mButtonCustom = new QPushButton(QIcon(QPixmap(":scenetoolbar/brush-custom")), "", this);
|
||||
mButtonPoint = new QPushButton(QIcon(":scenetoolbar/brush-point"), "", this);
|
||||
mButtonSquare = new QPushButton(QIcon(":scenetoolbar/brush-square"), "", this);
|
||||
mButtonCircle = new QPushButton(QIcon(":scenetoolbar/brush-circle"), "", this);
|
||||
mButtonCustom = new QPushButton(QIcon(":scenetoolbar/brush-custom"), "", this);
|
||||
|
||||
mSizeSliders = new ShapeBrushSizeControls("Brush size", this);
|
||||
|
||||
@ -201,25 +201,25 @@ void CSVWidget::SceneToolShapeBrush::setButtonIcon(CSVWidget::BrushShape brushSh
|
||||
{
|
||||
case BrushShape_Point:
|
||||
|
||||
setIcon(QIcon(QPixmap(":scenetoolbar/brush-point")));
|
||||
setIcon(QIcon(":scenetoolbar/brush-point"));
|
||||
tooltip += mShapeBrushWindow->toolTipPoint;
|
||||
break;
|
||||
|
||||
case BrushShape_Square:
|
||||
|
||||
setIcon(QIcon(QPixmap(":scenetoolbar/brush-square")));
|
||||
setIcon(QIcon(":scenetoolbar/brush-square"));
|
||||
tooltip += mShapeBrushWindow->toolTipSquare;
|
||||
break;
|
||||
|
||||
case BrushShape_Circle:
|
||||
|
||||
setIcon(QIcon(QPixmap(":scenetoolbar/brush-circle")));
|
||||
setIcon(QIcon(":scenetoolbar/brush-circle"));
|
||||
tooltip += mShapeBrushWindow->toolTipCircle;
|
||||
break;
|
||||
|
||||
case BrushShape_Custom:
|
||||
|
||||
setIcon(QIcon(QPixmap(":scenetoolbar/brush-custom")));
|
||||
setIcon(QIcon(":scenetoolbar/brush-custom"));
|
||||
tooltip += mShapeBrushWindow->toolTipCustom;
|
||||
break;
|
||||
}
|
||||
|
@ -90,10 +90,10 @@ CSVWidget::TextureBrushWindow::TextureBrushWindow(CSMDoc::Document& document, QW
|
||||
mSelectedBrush = new QLabel(QString::fromStdString(mBrushTextureLabel));
|
||||
}
|
||||
|
||||
mButtonPoint = new QPushButton(QIcon(QPixmap(":scenetoolbar/brush-point")), "", this);
|
||||
mButtonSquare = new QPushButton(QIcon(QPixmap(":scenetoolbar/brush-square")), "", this);
|
||||
mButtonCircle = new QPushButton(QIcon(QPixmap(":scenetoolbar/brush-circle")), "", this);
|
||||
mButtonCustom = new QPushButton(QIcon(QPixmap(":scenetoolbar/brush-custom")), "", this);
|
||||
mButtonPoint = new QPushButton(QIcon(":scenetoolbar/brush-point"), "", this);
|
||||
mButtonSquare = new QPushButton(QIcon(":scenetoolbar/brush-square"), "", this);
|
||||
mButtonCircle = new QPushButton(QIcon(":scenetoolbar/brush-circle"), "", this);
|
||||
mButtonCustom = new QPushButton(QIcon(":scenetoolbar/brush-custom"), "", this);
|
||||
|
||||
mSizeSliders = new BrushSizeControls("Brush size", this);
|
||||
|
||||
@ -282,25 +282,25 @@ void CSVWidget::SceneToolTextureBrush::setButtonIcon(CSVWidget::BrushShape brush
|
||||
{
|
||||
case BrushShape_Point:
|
||||
|
||||
setIcon(QIcon(QPixmap(":scenetoolbar/brush-point")));
|
||||
setIcon(QIcon(":scenetoolbar/brush-point"));
|
||||
tooltip += mTextureBrushWindow->toolTipPoint;
|
||||
break;
|
||||
|
||||
case BrushShape_Square:
|
||||
|
||||
setIcon(QIcon(QPixmap(":scenetoolbar/brush-square")));
|
||||
setIcon(QIcon(":scenetoolbar/brush-square"));
|
||||
tooltip += mTextureBrushWindow->toolTipSquare;
|
||||
break;
|
||||
|
||||
case BrushShape_Circle:
|
||||
|
||||
setIcon(QIcon(QPixmap(":scenetoolbar/brush-circle")));
|
||||
setIcon(QIcon(":scenetoolbar/brush-circle"));
|
||||
tooltip += mTextureBrushWindow->toolTipCircle;
|
||||
break;
|
||||
|
||||
case BrushShape_Custom:
|
||||
|
||||
setIcon(QIcon(QPixmap(":scenetoolbar/brush-custom")));
|
||||
setIcon(QIcon(":scenetoolbar/brush-custom"));
|
||||
tooltip += mTextureBrushWindow->toolTipCustom;
|
||||
break;
|
||||
}
|
||||
|
@ -88,7 +88,7 @@ void CSVWidget::SceneToolToggle2::addButton(
|
||||
stream << mSingleIcon << id;
|
||||
|
||||
PushButton* button = new PushButton(
|
||||
QIcon(QPixmap(stream.str().c_str())), PushButton::Type_Toggle, tooltip.isEmpty() ? name : tooltip, mPanel);
|
||||
QIcon(stream.str().c_str()), PushButton::Type_Toggle, tooltip.isEmpty() ? name : tooltip, mPanel);
|
||||
|
||||
button->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
|
||||
button->setIconSize(QSize(mIconSize, mIconSize));
|
||||
|
@ -29,7 +29,7 @@ void CSVWorld::DragRecordTable::startDragFromTable(const CSVWorld::DragRecordTab
|
||||
mime->setIndexAtDragStart(index);
|
||||
QDrag* drag = new QDrag(this);
|
||||
drag->setMimeData(mime);
|
||||
drag->setPixmap(QString::fromUtf8(mime->getIcon().c_str()));
|
||||
drag->setPixmap(QIcon(mime->getIcon().c_str()).pixmap(QSize(16, 16)));
|
||||
drag->exec(Qt::CopyAction);
|
||||
}
|
||||
|
||||
|
@ -78,8 +78,16 @@ CSVWorld::TableSubView::TableSubView(
|
||||
widget->setLayout(layout);
|
||||
|
||||
setWidget(widget);
|
||||
|
||||
// Widget position can be negative, we should clamp it.
|
||||
QPoint position = pos();
|
||||
if (position.x() <= 0)
|
||||
position.setX(0);
|
||||
if (position.y() <= 0)
|
||||
position.setY(0);
|
||||
|
||||
// prefer height of the screen and full width of the table
|
||||
const QRect rect = QApplication::screenAt(pos())->geometry();
|
||||
const QRect rect = QApplication::screenAt(position)->geometry();
|
||||
int frameHeight = 40; // set a reasonable default
|
||||
QWidget* topLevel = QApplication::topLevelAt(pos());
|
||||
if (topLevel)
|
||||
|
@ -64,6 +64,7 @@
|
||||
#include "mwscript/interpretercontext.hpp"
|
||||
#include "mwscript/scriptmanagerimp.hpp"
|
||||
|
||||
#include "mwsound/constants.hpp"
|
||||
#include "mwsound/soundmanagerimp.hpp"
|
||||
|
||||
#include "mwworld/class.hpp"
|
||||
@ -987,9 +988,8 @@ void OMW::Engine::go()
|
||||
// start in main menu
|
||||
mWindowManager->pushGuiMode(MWGui::GM_MainMenu);
|
||||
|
||||
std::string titlefile = "music/special/morrowind title.mp3";
|
||||
if (mVFS->exists(titlefile))
|
||||
mSoundManager->streamMusic(titlefile, MWSound::MusicType::Special);
|
||||
if (mVFS->exists(MWSound::titleMusic))
|
||||
mSoundManager->streamMusic(MWSound::titleMusic, MWSound::MusicType::Special);
|
||||
else
|
||||
Log(Debug::Warning) << "Title music not found";
|
||||
|
||||
|
@ -117,7 +117,7 @@ namespace MWBase
|
||||
virtual void stopMusic() = 0;
|
||||
///< Stops music if it's playing
|
||||
|
||||
virtual void streamMusic(const std::string& filename, MWSound::MusicType type, float fade = 1.f) = 0;
|
||||
virtual void streamMusic(VFS::Path::NormalizedView filename, MWSound::MusicType type, float fade = 1.f) = 0;
|
||||
///< Play a soundifle
|
||||
/// \param filename name of a sound file in the data directory.
|
||||
/// \param type music type.
|
||||
@ -126,7 +126,7 @@ namespace MWBase
|
||||
virtual bool isMusicPlaying() = 0;
|
||||
///< Returns true if music is playing
|
||||
|
||||
virtual void playPlaylist(const std::string& playlist) = 0;
|
||||
virtual void playPlaylist(VFS::Path::NormalizedView playlist) = 0;
|
||||
///< Start playing music from the selected folder
|
||||
/// \param name of the folder that contains the playlist
|
||||
/// Title music playlist is predefined
|
||||
|
@ -22,6 +22,8 @@
|
||||
#include "../mwmechanics/creaturestats.hpp"
|
||||
#include "../mwmechanics/npcstats.hpp"
|
||||
|
||||
#include "../mwsound/constants.hpp"
|
||||
|
||||
#include "class.hpp"
|
||||
|
||||
namespace
|
||||
@ -216,8 +218,7 @@ namespace MWGui
|
||||
center();
|
||||
|
||||
// Play LevelUp Music
|
||||
MWBase::Environment::get().getSoundManager()->streamMusic(
|
||||
"Music/Special/MW_Triumph.mp3", MWSound::MusicType::Special);
|
||||
MWBase::Environment::get().getSoundManager()->streamMusic(MWSound::triumphMusic, MWSound::MusicType::Special);
|
||||
}
|
||||
|
||||
void LevelupDialog::onOkButtonClicked(MyGUI::Widget* sender)
|
||||
|
@ -67,7 +67,8 @@ namespace MWGui
|
||||
!= supported_extensions.end();
|
||||
};
|
||||
|
||||
for (const auto& name : mResourceSystem->getVFS()->getRecursiveDirectoryIterator("Splash/"))
|
||||
constexpr VFS::Path::NormalizedView splash("splash/");
|
||||
for (const auto& name : mResourceSystem->getVFS()->getRecursiveDirectoryIterator(splash))
|
||||
{
|
||||
if (isSupportedExtension(Misc::getFileExtension(name)))
|
||||
mSplashScreens.push_back(name);
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
#include <components/settings/values.hpp>
|
||||
#include <components/vfs/manager.hpp>
|
||||
#include <components/vfs/pathutil.hpp>
|
||||
#include <components/widgets/imagebutton.hpp>
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
@ -37,7 +38,9 @@ namespace MWGui
|
||||
getWidget(mVersionText, "VersionText");
|
||||
mVersionText->setCaption(versionDescription);
|
||||
|
||||
mHasAnimatedMenu = mVFS->exists("video/menu_background.bik");
|
||||
constexpr VFS::Path::NormalizedView menuBackgroundVideo("video/menu_background.bik");
|
||||
|
||||
mHasAnimatedMenu = mVFS->exists(menuBackgroundVideo);
|
||||
|
||||
updateMenu();
|
||||
}
|
||||
|
@ -402,7 +402,8 @@ namespace MWGui
|
||||
|
||||
std::vector<std::string> availableLanguages;
|
||||
const VFS::Manager* vfs = MWBase::Environment::get().getResourceSystem()->getVFS();
|
||||
for (const auto& path : vfs->getRecursiveDirectoryIterator("l10n/"))
|
||||
constexpr VFS::Path::NormalizedView l10n("l10n/");
|
||||
for (const auto& path : vfs->getRecursiveDirectoryIterator(l10n))
|
||||
{
|
||||
if (Misc::getFileExtension(path) == "yaml")
|
||||
{
|
||||
|
@ -7,7 +7,6 @@
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/windowmanager.hpp"
|
||||
|
||||
#include <components/settings/values.hpp>
|
||||
#include <components/widgets/imagebutton.hpp>
|
||||
|
||||
#include "draganddrop.hpp"
|
||||
@ -80,12 +79,12 @@ void WindowBase::center()
|
||||
|
||||
void WindowBase::clampWindowCoordinates(MyGUI::Window* window)
|
||||
{
|
||||
auto minSize = window->getMinSize();
|
||||
minSize.height = static_cast<int>(minSize.height * Settings::gui().mScalingFactor);
|
||||
minSize.width = static_cast<int>(minSize.width * Settings::gui().mScalingFactor);
|
||||
MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize();
|
||||
if (window->getLayer())
|
||||
viewSize = window->getLayer()->getSize();
|
||||
|
||||
// Window's minimum size is larger than the screen size, can not clamp coordinates
|
||||
MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize();
|
||||
auto minSize = window->getMinSize();
|
||||
if (minSize.width > viewSize.width || minSize.height > viewSize.height)
|
||||
return;
|
||||
|
||||
|
@ -356,7 +356,8 @@ namespace MWGui
|
||||
mWindows.push_back(std::move(console));
|
||||
trackWindow(mConsole, makeConsoleWindowSettingValues());
|
||||
|
||||
bool questList = mResourceSystem->getVFS()->exists("textures/tx_menubook_options_over.dds");
|
||||
constexpr VFS::Path::NormalizedView menubookOptionsOverTexture("textures/tx_menubook_options_over.dds");
|
||||
const bool questList = mResourceSystem->getVFS()->exists(menubookOptionsOverTexture);
|
||||
auto journal = JournalWindow::create(JournalViewModel::create(), questList, mEncoding);
|
||||
mGuiModeStates[GM_Journal] = GuiModeState(journal.get());
|
||||
mWindows.push_back(std::move(journal));
|
||||
|
@ -141,7 +141,7 @@ namespace MWLua
|
||||
api["streamMusic"] = [](std::string_view fileName, const sol::optional<sol::table>& options) {
|
||||
auto args = getStreamMusicArgs(options);
|
||||
MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager();
|
||||
sndMgr->streamMusic(std::string(fileName), MWSound::MusicType::Scripted, args.mFade);
|
||||
sndMgr->streamMusic(VFS::Path::Normalized(fileName), MWSound::MusicType::Scripted, args.mFade);
|
||||
};
|
||||
|
||||
api["say"]
|
||||
|
@ -39,6 +39,8 @@
|
||||
|
||||
#include "../mwrender/vismask.hpp"
|
||||
|
||||
#include "../mwsound/constants.hpp"
|
||||
|
||||
#include "actor.hpp"
|
||||
#include "actorutil.hpp"
|
||||
#include "aicombataction.hpp"
|
||||
@ -1798,7 +1800,7 @@ namespace MWMechanics
|
||||
MWBase::Environment::get().getStateManager()->askLoadRecent();
|
||||
// Play Death Music if it was the player dying
|
||||
MWBase::Environment::get().getSoundManager()->streamMusic(
|
||||
"Music/Special/MW_Death.mp3", MWSound::MusicType::Special);
|
||||
MWSound::deathMusic, MWSound::MusicType::Special);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -29,6 +29,8 @@
|
||||
#include "../mwbase/windowmanager.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
|
||||
#include "../mwsound/constants.hpp"
|
||||
|
||||
#include "actor.hpp"
|
||||
#include "actors.hpp"
|
||||
#include "actorutil.hpp"
|
||||
@ -1678,12 +1680,12 @@ namespace MWMechanics
|
||||
if (mMusicType != MWSound::MusicType::Explore && !hasHostiles
|
||||
&& !(player.getClass().getCreatureStats(player).isDead() && musicPlaying))
|
||||
{
|
||||
MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Explore"));
|
||||
MWBase::Environment::get().getSoundManager()->playPlaylist(MWSound::explorePlaylist);
|
||||
mMusicType = MWSound::MusicType::Explore;
|
||||
}
|
||||
else if (mMusicType != MWSound::MusicType::Battle && hasHostiles)
|
||||
{
|
||||
MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Battle"));
|
||||
MWBase::Environment::get().getSoundManager()->playPlaylist(MWSound::battlePlaylist);
|
||||
mMusicType = MWSound::MusicType::Battle;
|
||||
}
|
||||
}
|
||||
|
@ -530,6 +530,7 @@ namespace MWRender
|
||||
, mHasMagicEffects(false)
|
||||
, mAlpha(1.f)
|
||||
, mPlayScriptedOnly(false)
|
||||
, mRequiresBoneMap(false)
|
||||
{
|
||||
for (size_t i = 0; i < sNumBlendMasks; i++)
|
||||
mAnimationTimePtr[i] = std::make_shared<AnimationTime>();
|
||||
@ -964,8 +965,17 @@ namespace MWRender
|
||||
{
|
||||
if (!mNodeMapCreated && mObjectRoot)
|
||||
{
|
||||
SceneUtil::NodeMapVisitor visitor(mNodeMap);
|
||||
mObjectRoot->accept(visitor);
|
||||
// If the base of this animation is an osgAnimation, we should map the bones not matrix transforms
|
||||
if (mRequiresBoneMap)
|
||||
{
|
||||
SceneUtil::NodeMapVisitorBoneOnly visitor(mNodeMap);
|
||||
mObjectRoot->accept(visitor);
|
||||
}
|
||||
else
|
||||
{
|
||||
SceneUtil::NodeMapVisitor visitor(mNodeMap);
|
||||
mObjectRoot->accept(visitor);
|
||||
}
|
||||
mNodeMapCreated = true;
|
||||
}
|
||||
return mNodeMap;
|
||||
@ -1479,6 +1489,10 @@ namespace MWRender
|
||||
mInsert->addChild(mObjectRoot);
|
||||
}
|
||||
|
||||
// osgAnimation formats with skeletons should have their nodemap be bone instances
|
||||
// FIXME: better way to detect osgAnimation here instead of relying on extension?
|
||||
mRequiresBoneMap = mSkeleton != nullptr && !Misc::StringUtils::ciEndsWith(model, ".nif");
|
||||
|
||||
if (previousStateset)
|
||||
mObjectRoot->setStateSet(previousStateset);
|
||||
|
||||
|
@ -246,6 +246,7 @@ namespace MWRender
|
||||
osg::ref_ptr<SceneUtil::LightListCallback> mLightListCallback;
|
||||
|
||||
bool mPlayScriptedOnly;
|
||||
bool mRequiresBoneMap;
|
||||
|
||||
const NodeMap& getNodeMap() const;
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
#include "rotatecontroller.hpp"
|
||||
|
||||
#include <osg/MatrixTransform>
|
||||
#include <osgAnimation/Bone>
|
||||
|
||||
namespace MWRender
|
||||
{
|
||||
@ -43,6 +44,17 @@ namespace MWRender
|
||||
|
||||
node->setMatrix(matrix);
|
||||
|
||||
// If we are linked to a bone we must call setMatrixInSkeletonSpace
|
||||
osgAnimation::Bone* b = dynamic_cast<osgAnimation::Bone*>(node);
|
||||
if (b)
|
||||
{
|
||||
osgAnimation::Bone* parent = b->getBoneParent();
|
||||
if (parent)
|
||||
matrix *= parent->getMatrixInSkeletonSpace();
|
||||
|
||||
b->setMatrixInSkeletonSpace(matrix);
|
||||
}
|
||||
|
||||
traverse(node, nv);
|
||||
}
|
||||
|
||||
|
@ -715,8 +715,6 @@ namespace MWRender
|
||||
|
||||
osg::ref_ptr<osg::Texture2D> normalMap(
|
||||
new osg::Texture2D(mResourceSystem->getImageManager()->getImage("textures/omw/water_nm.png")));
|
||||
if (normalMap->getImage())
|
||||
normalMap->getImage()->flipVertical();
|
||||
normalMap->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT);
|
||||
normalMap->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT);
|
||||
normalMap->setMaxAnisotropy(16);
|
||||
|
@ -631,7 +631,7 @@ namespace MWScript
|
||||
ESM::RefId gem = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger));
|
||||
runtime.pop();
|
||||
|
||||
if (!ptr.getClass().hasInventoryStore(ptr))
|
||||
if (!ptr.getClass().isActor())
|
||||
return;
|
||||
|
||||
const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore();
|
||||
@ -664,10 +664,10 @@ namespace MWScript
|
||||
for (unsigned int i = 0; i < arg0; ++i)
|
||||
runtime.pop();
|
||||
|
||||
if (!ptr.getClass().hasInventoryStore(ptr))
|
||||
if (!ptr.getClass().isActor())
|
||||
return;
|
||||
|
||||
MWWorld::InventoryStore& store = ptr.getClass().getInventoryStore(ptr);
|
||||
MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr);
|
||||
for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it)
|
||||
{
|
||||
if (it->getCellRef().getSoul() == soul)
|
||||
@ -780,10 +780,10 @@ namespace MWScript
|
||||
ESM::RefId soul = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger));
|
||||
runtime.pop();
|
||||
|
||||
if (!ptr.getClass().hasInventoryStore(ptr))
|
||||
if (!ptr.getClass().isActor())
|
||||
return;
|
||||
|
||||
MWWorld::InventoryStore& store = ptr.getClass().getInventoryStore(ptr);
|
||||
MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr);
|
||||
|
||||
for (MWWorld::ContainerStoreIterator iter(store.begin()); iter != store.end(); ++iter)
|
||||
{
|
||||
|
@ -63,7 +63,7 @@ namespace MWScript
|
||||
public:
|
||||
void execute(Interpreter::Runtime& runtime) override
|
||||
{
|
||||
std::string music{ runtime.getStringLiteral(runtime[0].mInteger) };
|
||||
const VFS::Path::Normalized music(runtime.getStringLiteral(runtime[0].mInteger));
|
||||
runtime.pop();
|
||||
|
||||
MWBase::Environment::get().getSoundManager()->streamMusic(
|
||||
|
15
apps/openmw/mwsound/constants.hpp
Normal file
15
apps/openmw/mwsound/constants.hpp
Normal file
@ -0,0 +1,15 @@
|
||||
#ifndef OPENMW_APPS_OPENMW_MWSOUND_CONSTANTS_H
|
||||
#define OPENMW_APPS_OPENMW_MWSOUND_CONSTANTS_H
|
||||
|
||||
#include <components/vfs/pathutil.hpp>
|
||||
|
||||
namespace MWSound
|
||||
{
|
||||
constexpr VFS::Path::NormalizedView battlePlaylist("battle");
|
||||
constexpr VFS::Path::NormalizedView explorePlaylist("explore");
|
||||
constexpr VFS::Path::NormalizedView titleMusic("music/special/morrowind title.mp3");
|
||||
constexpr VFS::Path::NormalizedView triumphMusic("music/special/mw_triumph.mp3");
|
||||
constexpr VFS::Path::NormalizedView deathMusic("music/special/mw_death.mp3");
|
||||
}
|
||||
|
||||
#endif
|
@ -208,7 +208,7 @@ namespace MWSound
|
||||
return dec;
|
||||
}
|
||||
|
||||
void FFmpeg_Decoder::open(const std::string& fname)
|
||||
void FFmpeg_Decoder::open(VFS::Path::NormalizedView fname)
|
||||
{
|
||||
close();
|
||||
mDataStream = mResourceMgr->get(fname);
|
||||
@ -224,7 +224,7 @@ namespace MWSound
|
||||
formatCtx->pb = ioCtx.get();
|
||||
|
||||
// avformat_open_input frees user supplied AVFormatContext on failure
|
||||
if (avformat_open_input(&formatCtx, fname.c_str(), nullptr, nullptr) != 0)
|
||||
if (avformat_open_input(&formatCtx, fname.value().data(), nullptr, nullptr) != 0)
|
||||
throw std::runtime_error("Failed to open input");
|
||||
|
||||
AVFormatContextPtr formatCtxPtr(std::exchange(formatCtx, nullptr));
|
||||
|
@ -93,7 +93,7 @@ namespace MWSound
|
||||
bool getAVAudioData();
|
||||
size_t readAVAudioData(void* data, size_t length);
|
||||
|
||||
void open(const std::string& fname) override;
|
||||
void open(VFS::Path::NormalizedView fname) override;
|
||||
void close() override;
|
||||
|
||||
std::string getName() override;
|
||||
|
@ -24,8 +24,10 @@ namespace MWSound
|
||||
private:
|
||||
MWSound::MovieAudioDecoder* mDecoder;
|
||||
|
||||
void open(const std::string& fname) override;
|
||||
void close() override;
|
||||
void open(VFS::Path::NormalizedView fname) override { throw std::runtime_error("Method not implemented"); }
|
||||
|
||||
void close() override {}
|
||||
|
||||
std::string getName() override;
|
||||
void getInfo(int* samplerate, ChannelConfig* chans, SampleType* type) override;
|
||||
size_t read(char* buffer, size_t bytes) override;
|
||||
@ -92,12 +94,6 @@ namespace MWSound
|
||||
std::shared_ptr<MWSoundDecoderBridge> mDecoderBridge;
|
||||
};
|
||||
|
||||
void MWSoundDecoderBridge::open(const std::string& fname)
|
||||
{
|
||||
throw std::runtime_error("Method not implemented");
|
||||
}
|
||||
void MWSoundDecoderBridge::close() {}
|
||||
|
||||
std::string MWSoundDecoderBridge::getName()
|
||||
{
|
||||
return mDecoder->getStreamName();
|
||||
|
@ -1,6 +1,8 @@
|
||||
#ifndef GAME_SOUND_SOUND_DECODER_H
|
||||
#define GAME_SOUND_SOUND_DECODER_H
|
||||
|
||||
#include <components/vfs/pathutil.hpp>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@ -36,7 +38,7 @@ namespace MWSound
|
||||
{
|
||||
const VFS::Manager* mResourceMgr;
|
||||
|
||||
virtual void open(const std::string& fname) = 0;
|
||||
virtual void open(VFS::Path::NormalizedView fname) = 0;
|
||||
virtual void close() = 0;
|
||||
|
||||
virtual std::string getName() = 0;
|
||||
|
@ -26,14 +26,14 @@
|
||||
|
||||
#include "../mwmechanics/actorutil.hpp"
|
||||
|
||||
#include "constants.hpp"
|
||||
#include "ffmpeg_decoder.hpp"
|
||||
#include "openal_output.hpp"
|
||||
#include "sound.hpp"
|
||||
#include "sound_buffer.hpp"
|
||||
#include "sound_decoder.hpp"
|
||||
#include "sound_output.hpp"
|
||||
|
||||
#include "ffmpeg_decoder.hpp"
|
||||
#include "openal_output.hpp"
|
||||
|
||||
namespace MWSound
|
||||
{
|
||||
namespace
|
||||
@ -252,13 +252,13 @@ namespace MWSound
|
||||
}
|
||||
}
|
||||
|
||||
void SoundManager::streamMusicFull(const std::string& filename)
|
||||
void SoundManager::streamMusicFull(VFS::Path::NormalizedView filename)
|
||||
{
|
||||
if (!mOutput->isInitialized())
|
||||
return;
|
||||
|
||||
stopMusic();
|
||||
if (filename.empty())
|
||||
if (filename.value().empty())
|
||||
return;
|
||||
|
||||
Log(Debug::Info) << "Playing \"" << filename << "\"";
|
||||
@ -269,7 +269,7 @@ namespace MWSound
|
||||
{
|
||||
decoder->open(filename);
|
||||
}
|
||||
catch (std::exception& e)
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
Log(Debug::Error) << "Failed to load audio from \"" << filename << "\": " << e.what();
|
||||
return;
|
||||
@ -285,7 +285,7 @@ namespace MWSound
|
||||
mOutput->streamSound(std::move(decoder), mMusic.get());
|
||||
}
|
||||
|
||||
void SoundManager::advanceMusic(const std::string& filename, float fadeOut)
|
||||
void SoundManager::advanceMusic(VFS::Path::NormalizedView filename, float fadeOut)
|
||||
{
|
||||
if (!isMusicPlaying())
|
||||
{
|
||||
@ -300,13 +300,16 @@ namespace MWSound
|
||||
|
||||
void SoundManager::startRandomTitle()
|
||||
{
|
||||
const std::vector<std::string>& filelist = mMusicFiles[mCurrentPlaylist];
|
||||
if (filelist.empty())
|
||||
const auto playlist = mMusicFiles.find(mCurrentPlaylist);
|
||||
|
||||
if (playlist == mMusicFiles.end() || playlist->second.empty())
|
||||
{
|
||||
advanceMusic(std::string());
|
||||
advanceMusic(VFS::Path::NormalizedView());
|
||||
return;
|
||||
}
|
||||
|
||||
const std::vector<VFS::Path::Normalized>& filelist = playlist->second;
|
||||
|
||||
auto& tracklist = mMusicToPlay[mCurrentPlaylist];
|
||||
|
||||
// Do a Fisher-Yates shuffle
|
||||
@ -335,7 +338,7 @@ namespace MWSound
|
||||
return mMusic && mOutput->isStreamPlaying(mMusic.get());
|
||||
}
|
||||
|
||||
void SoundManager::streamMusic(const std::string& filename, MusicType type, float fade)
|
||||
void SoundManager::streamMusic(VFS::Path::NormalizedView filename, MusicType type, float fade)
|
||||
{
|
||||
const auto mechanicsManager = MWBase::Environment::get().getMechanicsManager();
|
||||
|
||||
@ -344,35 +347,36 @@ namespace MWSound
|
||||
&& type != MusicType::Special)
|
||||
return;
|
||||
|
||||
std::string normalizedName = VFS::Path::normalizeFilename(filename);
|
||||
|
||||
mechanicsManager->setMusicType(type);
|
||||
advanceMusic(normalizedName, fade);
|
||||
advanceMusic(filename, fade);
|
||||
if (type == MWSound::MusicType::Battle)
|
||||
mCurrentPlaylist = "Battle";
|
||||
mCurrentPlaylist = battlePlaylist;
|
||||
else if (type == MWSound::MusicType::Explore)
|
||||
mCurrentPlaylist = "Explore";
|
||||
mCurrentPlaylist = explorePlaylist;
|
||||
}
|
||||
|
||||
void SoundManager::playPlaylist(const std::string& playlist)
|
||||
void SoundManager::playPlaylist(VFS::Path::NormalizedView playlist)
|
||||
{
|
||||
if (mCurrentPlaylist == playlist)
|
||||
return;
|
||||
|
||||
if (mMusicFiles.find(playlist) == mMusicFiles.end())
|
||||
auto it = mMusicFiles.find(playlist);
|
||||
|
||||
if (it == mMusicFiles.end())
|
||||
{
|
||||
std::vector<std::string> filelist;
|
||||
auto playlistPath = Misc::ResourceHelpers::correctMusicPath(playlist) + '/';
|
||||
for (const auto& name : mVFS->getRecursiveDirectoryIterator(playlistPath))
|
||||
std::vector<VFS::Path::Normalized> filelist;
|
||||
const VFS::Path::Normalized playlistPath
|
||||
= Misc::ResourceHelpers::correctMusicPath(playlist) / VFS::Path::NormalizedView();
|
||||
for (const auto& name : mVFS->getRecursiveDirectoryIterator(VFS::Path::NormalizedView(playlistPath)))
|
||||
filelist.push_back(name);
|
||||
|
||||
mMusicFiles[playlist] = std::move(filelist);
|
||||
it = mMusicFiles.emplace_hint(it, playlist, std::move(filelist));
|
||||
}
|
||||
|
||||
// No Battle music? Use Explore playlist
|
||||
if (playlist == "Battle" && mMusicFiles[playlist].empty())
|
||||
if (playlist == battlePlaylist && it->second.empty())
|
||||
{
|
||||
playPlaylist("Explore");
|
||||
playPlaylist(explorePlaylist);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1019,7 +1023,7 @@ namespace MWSound
|
||||
mTimePassed = 0.0f;
|
||||
|
||||
// Make sure music is still playing
|
||||
if (!isMusicPlaying() && !mCurrentPlaylist.empty())
|
||||
if (!isMusicPlaying() && !mCurrentPlaylist.value().empty())
|
||||
startRandomTitle();
|
||||
|
||||
Environment env = Env_Normal;
|
||||
@ -1137,10 +1141,10 @@ namespace MWSound
|
||||
if (!mMusic || !mMusic->updateFade(duration) || !mOutput->isStreamPlaying(mMusic.get()))
|
||||
{
|
||||
stopMusic();
|
||||
if (!mNextMusic.empty())
|
||||
if (!mNextMusic.value().empty())
|
||||
{
|
||||
streamMusicFull(mNextMusic);
|
||||
mNextMusic.clear();
|
||||
mNextMusic = VFS::Path::Normalized();
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -1160,9 +1164,8 @@ namespace MWSound
|
||||
|
||||
if (isMainMenu && !isMusicPlaying())
|
||||
{
|
||||
std::string titlefile = "music/special/morrowind title.mp3";
|
||||
if (mVFS->exists(titlefile))
|
||||
streamMusic(titlefile, MWSound::MusicType::Special);
|
||||
if (mVFS->exists(MWSound::titleMusic))
|
||||
streamMusic(MWSound::titleMusic, MWSound::MusicType::Special);
|
||||
}
|
||||
|
||||
updateSounds(duration);
|
||||
|
@ -9,6 +9,7 @@
|
||||
|
||||
#include <components/fallback/fallback.hpp>
|
||||
#include <components/misc/objectpool.hpp>
|
||||
#include <components/misc/strings/algorithm.hpp>
|
||||
#include <components/settings/settings.hpp>
|
||||
|
||||
#include "../mwbase/soundmanager.hpp"
|
||||
@ -52,9 +53,10 @@ namespace MWSound
|
||||
std::unique_ptr<Sound_Output> mOutput;
|
||||
|
||||
// Caches available music tracks by <playlist name, (sound files) >
|
||||
std::unordered_map<std::string, std::vector<std::string>> mMusicFiles;
|
||||
std::unordered_map<VFS::Path::Normalized, std::vector<VFS::Path::Normalized>, VFS::Path::Hash, std::equal_to<>>
|
||||
mMusicFiles;
|
||||
std::unordered_map<std::string, std::vector<int>> mMusicToPlay; // A list with music files not yet played
|
||||
std::string mLastPlayedMusic; // The music file that was last played
|
||||
VFS::Path::Normalized mLastPlayedMusic; // The music file that was last played
|
||||
|
||||
WaterSoundUpdater mWaterSoundUpdater;
|
||||
|
||||
@ -90,7 +92,7 @@ namespace MWSound
|
||||
TrackList mActiveTracks;
|
||||
|
||||
StreamPtr mMusic;
|
||||
std::string mCurrentPlaylist;
|
||||
VFS::Path::Normalized mCurrentPlaylist;
|
||||
|
||||
bool mListenerUnderwater;
|
||||
osg::Vec3f mListenerPos;
|
||||
@ -102,7 +104,7 @@ namespace MWSound
|
||||
Sound* mUnderwaterSound;
|
||||
Sound* mNearWaterSound;
|
||||
|
||||
std::string mNextMusic;
|
||||
VFS::Path::Normalized mNextMusic;
|
||||
bool mPlaybackPaused;
|
||||
|
||||
RegionSoundSelector mRegionSoundSelector;
|
||||
@ -123,8 +125,8 @@ namespace MWSound
|
||||
|
||||
StreamPtr playVoice(DecoderPtr decoder, const osg::Vec3f& pos, bool playlocal);
|
||||
|
||||
void streamMusicFull(const std::string& filename);
|
||||
void advanceMusic(const std::string& filename, float fadeOut = 1.f);
|
||||
void streamMusicFull(VFS::Path::NormalizedView filename);
|
||||
void advanceMusic(VFS::Path::NormalizedView filename, float fadeOut = 1.f);
|
||||
void startRandomTitle();
|
||||
|
||||
void cull3DSound(SoundBase* sound);
|
||||
@ -174,7 +176,7 @@ namespace MWSound
|
||||
void stopMusic() override;
|
||||
///< Stops music if it's playing
|
||||
|
||||
void streamMusic(const std::string& filename, MWSound::MusicType type, float fade = 1.f) override;
|
||||
void streamMusic(VFS::Path::NormalizedView filename, MWSound::MusicType type, float fade = 1.f) override;
|
||||
///< Play a soundifle
|
||||
/// \param filename name of a sound file in the data directory.
|
||||
/// \param type music type.
|
||||
@ -183,7 +185,7 @@ namespace MWSound
|
||||
bool isMusicPlaying() override;
|
||||
///< Returns true if music is playing
|
||||
|
||||
void playPlaylist(const std::string& playlist) override;
|
||||
void playPlaylist(VFS::Path::NormalizedView playlist) override;
|
||||
///< Start playing music from the selected folder
|
||||
/// \param name of the folder that contains the playlist
|
||||
/// Title music playlist is predefined
|
||||
|
@ -190,7 +190,8 @@ namespace
|
||||
{
|
||||
const DetourNavigator::AgentBounds agentBounds = world.getPathfindingAgentBounds(ptr);
|
||||
if (!navigator.addAgent(agentBounds))
|
||||
Log(Debug::Warning) << "Agent bounds are not supported by navigator: " << agentBounds;
|
||||
Log(Debug::Warning) << "Agent bounds are not supported by navigator for " << ptr.toString() << ": "
|
||||
<< agentBounds;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -90,6 +90,8 @@
|
||||
#include "../mwphysics/object.hpp"
|
||||
#include "../mwphysics/physicssystem.hpp"
|
||||
|
||||
#include "../mwsound/constants.hpp"
|
||||
|
||||
#include "actionteleport.hpp"
|
||||
#include "cellstore.hpp"
|
||||
#include "containerstore.hpp"
|
||||
@ -394,7 +396,7 @@ namespace MWWorld
|
||||
{
|
||||
// Make sure that we do not continue to play a Title music after a new game video.
|
||||
MWBase::Environment::get().getSoundManager()->stopMusic();
|
||||
MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Explore"));
|
||||
MWBase::Environment::get().getSoundManager()->playPlaylist(MWSound::explorePlaylist);
|
||||
MWBase::Environment::get().getWindowManager()->playVideo(video, true);
|
||||
}
|
||||
}
|
||||
|
@ -100,6 +100,8 @@ file(GLOB UNITTEST_SRC_FILES
|
||||
resource/testobjectcache.cpp
|
||||
|
||||
vfs/testpathutil.cpp
|
||||
|
||||
sceneutil/osgacontroller.cpp
|
||||
)
|
||||
|
||||
source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES})
|
||||
|
@ -8,19 +8,22 @@ namespace
|
||||
TEST(CorrectSoundPath, wav_files_not_overridden_with_mp3_in_vfs_are_not_corrected)
|
||||
{
|
||||
std::unique_ptr<VFS::Manager> mVFS = TestingOpenMW::createTestVFS({ { "sound/bar.wav", nullptr } });
|
||||
EXPECT_EQ(correctSoundPath("sound/bar.wav", *mVFS), "sound/bar.wav");
|
||||
constexpr VFS::Path::NormalizedView path("sound/bar.wav");
|
||||
EXPECT_EQ(correctSoundPath(path, *mVFS), "sound/bar.wav");
|
||||
}
|
||||
|
||||
TEST(CorrectSoundPath, wav_files_overridden_with_mp3_in_vfs_are_corrected)
|
||||
{
|
||||
std::unique_ptr<VFS::Manager> mVFS = TestingOpenMW::createTestVFS({ { "sound/foo.mp3", nullptr } });
|
||||
EXPECT_EQ(correctSoundPath("sound/foo.wav", *mVFS), "sound/foo.mp3");
|
||||
constexpr VFS::Path::NormalizedView path("sound/foo.wav");
|
||||
EXPECT_EQ(correctSoundPath(path, *mVFS), "sound/foo.mp3");
|
||||
}
|
||||
|
||||
TEST(CorrectSoundPath, corrected_path_does_not_check_existence_in_vfs)
|
||||
{
|
||||
std::unique_ptr<VFS::Manager> mVFS = TestingOpenMW::createTestVFS({});
|
||||
EXPECT_EQ(correctSoundPath("sound/foo.wav", *mVFS), "sound/foo.mp3");
|
||||
constexpr VFS::Path::NormalizedView path("sound/foo.wav");
|
||||
EXPECT_EQ(correctSoundPath(path, *mVFS), "sound/foo.mp3");
|
||||
}
|
||||
|
||||
namespace
|
||||
|
@ -3,6 +3,7 @@
|
||||
#include <components/nif/node.hpp>
|
||||
#include <components/nif/property.hpp>
|
||||
#include <components/nifosg/nifloader.hpp>
|
||||
#include <components/resource/bgsmfilemanager.hpp>
|
||||
#include <components/resource/imagemanager.hpp>
|
||||
#include <components/sceneutil/serialize.hpp>
|
||||
#include <components/vfs/manager.hpp>
|
||||
@ -29,6 +30,7 @@ namespace
|
||||
{
|
||||
VFS::Manager mVfs;
|
||||
Resource::ImageManager mImageManager{ &mVfs, 0 };
|
||||
Resource::BgsmFileManager mMaterialManager{ &mVfs, 0 };
|
||||
const osgDB::ReaderWriter* mReaderWriter = osgDB::Registry::instance()->getReaderWriterForExtension("osgt");
|
||||
osg::ref_ptr<osgDB::Options> mOptions = new osgDB::Options;
|
||||
|
||||
@ -70,7 +72,7 @@ namespace
|
||||
init(node);
|
||||
Nif::NIFFile file("test.nif");
|
||||
file.mRoots.push_back(&node);
|
||||
auto result = Loader::load(file, &mImageManager);
|
||||
auto result = Loader::load(file, &mImageManager, &mMaterialManager);
|
||||
EXPECT_EQ(serialize(*result), R"(
|
||||
osg::Group {
|
||||
UniqueID 1
|
||||
@ -259,7 +261,7 @@ osg::Group {
|
||||
node.mProperties.push_back(Nif::RecordPtrT<Nif::NiProperty>(&property));
|
||||
Nif::NIFFile file("test.nif");
|
||||
file.mRoots.push_back(&node);
|
||||
auto result = Loader::load(file, &mImageManager);
|
||||
auto result = Loader::load(file, &mImageManager, &mMaterialManager);
|
||||
EXPECT_EQ(serialize(*result), formatOsgNodeForBSShaderProperty(GetParam().mExpectedShaderPrefix));
|
||||
}
|
||||
|
||||
@ -289,7 +291,7 @@ osg::Group {
|
||||
node.mProperties.push_back(Nif::RecordPtrT<Nif::NiProperty>(&property));
|
||||
Nif::NIFFile file("test.nif");
|
||||
file.mRoots.push_back(&node);
|
||||
auto result = Loader::load(file, &mImageManager);
|
||||
auto result = Loader::load(file, &mImageManager, &mMaterialManager);
|
||||
EXPECT_EQ(serialize(*result), formatOsgNodeForBSLightingShaderProperty(GetParam().mExpectedShaderPrefix));
|
||||
}
|
||||
|
||||
|
131
apps/openmw_test_suite/sceneutil/osgacontroller.cpp
Normal file
131
apps/openmw_test_suite/sceneutil/osgacontroller.cpp
Normal file
@ -0,0 +1,131 @@
|
||||
#include <components/sceneutil/osgacontroller.hpp>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <osgAnimation/Channel>
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
|
||||
namespace
|
||||
{
|
||||
using namespace SceneUtil;
|
||||
|
||||
static const std::string ROOT_BONE_NAME = "bip01";
|
||||
|
||||
// Creates a merged anim track with a single root channel with two start/end matrix transforms
|
||||
osg::ref_ptr<Resource::Animation> createMergedAnimationTrack(std::string name, osg::Matrixf startTransform,
|
||||
osg::Matrixf endTransform, float startTime = 0.0f, float endTime = 1.0f)
|
||||
{
|
||||
osg::ref_ptr<Resource::Animation> mergedAnimationTrack = new Resource::Animation;
|
||||
mergedAnimationTrack->setName(name);
|
||||
|
||||
osgAnimation::MatrixKeyframeContainer* cbCntr = new osgAnimation::MatrixKeyframeContainer;
|
||||
cbCntr->push_back(osgAnimation::MatrixKeyframe(startTime, startTransform));
|
||||
cbCntr->push_back(osgAnimation::MatrixKeyframe(endTime, endTransform));
|
||||
|
||||
osg::ref_ptr<osgAnimation::MatrixLinearChannel> rootChannel = new osgAnimation::MatrixLinearChannel;
|
||||
rootChannel->setName("transform");
|
||||
rootChannel->setTargetName(ROOT_BONE_NAME);
|
||||
rootChannel->getOrCreateSampler()->setKeyframeContainer(cbCntr);
|
||||
mergedAnimationTrack->addChannel(rootChannel);
|
||||
return mergedAnimationTrack;
|
||||
}
|
||||
|
||||
TEST(OsgAnimationControllerTest, getTranslationShouldReturnSampledChannelTranslationForBip01)
|
||||
{
|
||||
std::vector<EmulatedAnimation> emulatedAnimations;
|
||||
emulatedAnimations.push_back({ 0.0f, 1.0f, "test1" }); // should sample this
|
||||
emulatedAnimations.push_back({ 1.1f, 2.0f, "test2" }); // should ignore this
|
||||
|
||||
OsgAnimationController controller;
|
||||
controller.setEmulatedAnimations(emulatedAnimations);
|
||||
|
||||
osg::Matrixf startTransform = osg::Matrixf::identity();
|
||||
osg::Matrixf endTransform = osg::Matrixf::identity();
|
||||
osg::Matrixf endTransform2 = osg::Matrixf::identity();
|
||||
endTransform.setTrans(1.0f, 1.0f, 1.0f);
|
||||
controller.addMergedAnimationTrack(createMergedAnimationTrack("test1", startTransform, endTransform));
|
||||
endTransform2.setTrans(2.0f, 2.0f, 2.0f);
|
||||
controller.addMergedAnimationTrack(
|
||||
createMergedAnimationTrack("test2", endTransform, endTransform2, 0.1f, 0.9f));
|
||||
|
||||
// should be halfway between 0,0,0 and 1,1,1
|
||||
osg::Vec3f translation = controller.getTranslation(0.5f);
|
||||
EXPECT_EQ(translation, osg::Vec3f(0.5f, 0.5f, 0.5f));
|
||||
}
|
||||
|
||||
TEST(OsgAnimationControllerTest, getTranslationShouldReturnZeroVectorIfNotFound)
|
||||
{
|
||||
std::vector<EmulatedAnimation> emulatedAnimations;
|
||||
emulatedAnimations.push_back({ 0.0f, 1.0f, "test1" });
|
||||
|
||||
OsgAnimationController controller;
|
||||
controller.setEmulatedAnimations(emulatedAnimations);
|
||||
|
||||
osg::Matrixf startTransform = osg::Matrixf::identity();
|
||||
osg::Matrixf endTransform = osg::Matrixf::identity();
|
||||
endTransform.setTrans(1.0f, 1.0f, 1.0f);
|
||||
controller.addMergedAnimationTrack(createMergedAnimationTrack("test1", startTransform, endTransform));
|
||||
|
||||
// Has no emulated animation at time so will return 0,0,0
|
||||
osg::Vec3f translation = controller.getTranslation(100.0f);
|
||||
EXPECT_EQ(translation, osg::Vec3f(0.0f, 0.0f, 0.0f));
|
||||
}
|
||||
|
||||
TEST(OsgAnimationControllerTest, getTranslationShouldReturnZeroVectorIfNoMergedTracks)
|
||||
{
|
||||
std::vector<EmulatedAnimation> emulatedAnimations;
|
||||
emulatedAnimations.push_back({ 0.0f, 1.0f, "test1" });
|
||||
|
||||
OsgAnimationController controller;
|
||||
controller.setEmulatedAnimations(emulatedAnimations);
|
||||
|
||||
// Has no merged tracks so will return 0,0,0
|
||||
osg::Vec3f translation = controller.getTranslation(0.5);
|
||||
EXPECT_EQ(translation, osg::Vec3f(0.0f, 0.0f, 0.0f));
|
||||
}
|
||||
|
||||
TEST(OsgAnimationControllerTest, getTransformShouldReturnIdentityIfNotFound)
|
||||
{
|
||||
std::vector<EmulatedAnimation> emulatedAnimations;
|
||||
emulatedAnimations.push_back({ 0.0f, 1.0f, "test1" });
|
||||
|
||||
OsgAnimationController controller;
|
||||
controller.setEmulatedAnimations(emulatedAnimations);
|
||||
|
||||
osg::Matrixf startTransform = osg::Matrixf::identity();
|
||||
osg::Matrixf endTransform = osg::Matrixf::identity();
|
||||
endTransform.setTrans(1.0f, 1.0f, 1.0f);
|
||||
controller.addMergedAnimationTrack(createMergedAnimationTrack("test1", startTransform, endTransform));
|
||||
|
||||
// Has no emulated animation at time so will return identity
|
||||
EXPECT_EQ(controller.getTransformForNode(100.0f, ROOT_BONE_NAME), osg::Matrixf::identity());
|
||||
|
||||
// Has no bone animation at time so will return identity
|
||||
EXPECT_EQ(controller.getTransformForNode(0.5f, "wrongbone"), osg::Matrixf::identity());
|
||||
}
|
||||
|
||||
TEST(OsgAnimationControllerTest, getTransformShouldReturnSampledAnimMatrixAtTime)
|
||||
{
|
||||
std::vector<EmulatedAnimation> emulatedAnimations;
|
||||
emulatedAnimations.push_back({ 0.0f, 1.0f, "test1" }); // should sample this
|
||||
emulatedAnimations.push_back({ 1.1f, 2.0f, "test2" }); // should ignore this
|
||||
|
||||
OsgAnimationController controller;
|
||||
controller.setEmulatedAnimations(emulatedAnimations);
|
||||
|
||||
osg::Matrixf startTransform = osg::Matrixf::identity();
|
||||
osg::Matrixf endTransform = osg::Matrixf::identity();
|
||||
endTransform.setTrans(1.0f, 1.0f, 1.0f);
|
||||
controller.addMergedAnimationTrack(createMergedAnimationTrack("test1", startTransform, endTransform));
|
||||
osg::Matrixf endTransform2 = osg::Matrixf::identity();
|
||||
endTransform2.setTrans(2.0f, 2.0f, 2.0f);
|
||||
controller.addMergedAnimationTrack(
|
||||
createMergedAnimationTrack("test2", endTransform, endTransform2, 0.1f, 0.9f));
|
||||
|
||||
EXPECT_EQ(controller.getTransformForNode(0.0f, ROOT_BONE_NAME), startTransform); // start of test1
|
||||
EXPECT_EQ(controller.getTransformForNode(1.0f, ROOT_BONE_NAME), endTransform); // end of test1
|
||||
EXPECT_EQ(controller.getTransformForNode(1.1f, ROOT_BONE_NAME), endTransform); // start of test2
|
||||
EXPECT_EQ(controller.getTransformForNode(2.0f, ROOT_BONE_NAME), endTransform2); // end of test2
|
||||
}
|
||||
}
|
@ -39,7 +39,7 @@ namespace VFS::Path
|
||||
|
||||
TEST(NormalizedTest, shouldSupportConstructorFromNormalizedView)
|
||||
{
|
||||
const NormalizedView view = "foo/bar/baz";
|
||||
const NormalizedView view("foo/bar/baz");
|
||||
const Normalized value(view);
|
||||
EXPECT_EQ(value.view(), "foo/bar/baz");
|
||||
}
|
||||
|
@ -85,7 +85,7 @@ target_link_libraries(openmw-wizard
|
||||
components_qt
|
||||
)
|
||||
|
||||
target_link_libraries(openmw-wizard Qt::Widgets Qt::Core)
|
||||
target_link_libraries(openmw-wizard Qt::Widgets Qt::Core Qt::Svg)
|
||||
|
||||
if (OPENMW_USE_UNSHIELD)
|
||||
target_link_libraries(openmw-wizard ${LIBUNSHIELD_LIBRARIES})
|
||||
|
@ -19,6 +19,8 @@ Wizard::InstallationTargetPage::InstallationTargetPage(QWidget* parent, const Fi
|
||||
|
||||
setupUi(this);
|
||||
|
||||
folderIconLabel->setPixmap(QIcon(":folder").pixmap(QSize(48, 48)));
|
||||
|
||||
registerField(QLatin1String("installation.path*"), targetLineEdit);
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,8 @@ Wizard::LanguageSelectionPage::LanguageSelectionPage(QWidget* parent)
|
||||
|
||||
setupUi(this);
|
||||
|
||||
flagIconLabel->setPixmap(QIcon(":preferences-desktop-locale").pixmap(QSize(48, 48)));
|
||||
|
||||
registerField(QLatin1String("installation.language"), languageComboBox, "currentData", "currentDataChanged");
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,10 @@ Wizard::MethodSelectionPage::MethodSelectionPage(QWidget* parent)
|
||||
|
||||
setupUi(this);
|
||||
|
||||
installerIconLabel->setPixmap(QIcon(":system-installer").pixmap(QSize(48, 48)));
|
||||
folderIconLabel->setPixmap(QIcon(":folder").pixmap(QSize(48, 48)));
|
||||
buyIconLabel->setPixmap(QIcon(":dollar").pixmap(QSize(48, 48)));
|
||||
|
||||
#ifndef OPENMW_USE_UNSHIELD
|
||||
retailDiscRadioButton->setEnabled(false);
|
||||
existingLocationRadioButton->setChecked(true);
|
||||
|
@ -30,9 +30,6 @@
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string><html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html></string>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::RichText</enum>
|
||||
</property>
|
||||
|
@ -30,9 +30,6 @@
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string><html><head/><body><p><img src=":/icons/tango/48x48/preferences-desktop-locale.png"/></p></body></html></string>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::RichText</enum>
|
||||
</property>
|
||||
|
@ -59,9 +59,6 @@
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string><html><head/><body><p><img src=":/icons/tango/48x48/system-installer.png"/></p></body></html></string>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::RichText</enum>
|
||||
</property>
|
||||
@ -124,9 +121,6 @@
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string><html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html></string>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::RichText</enum>
|
||||
</property>
|
||||
@ -191,9 +185,6 @@
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string><html><head/><body><p><img src=":/icons/tango/48x48/dollar.png"/></p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
|
@ -107,6 +107,10 @@ add_component_dir (settings
|
||||
windowmode
|
||||
)
|
||||
|
||||
add_component_dir (bgsm
|
||||
stream file
|
||||
)
|
||||
|
||||
add_component_dir (bsa
|
||||
bsa_file compressedbsafile ba2gnrlfile ba2dx10file ba2file memorystream
|
||||
)
|
||||
@ -125,7 +129,7 @@ add_component_dir (vfs
|
||||
|
||||
add_component_dir (resource
|
||||
scenemanager keyframemanager imagemanager bulletshapemanager bulletshape niffilemanager objectcache multiobjectcache resourcesystem
|
||||
resourcemanager stats animation foreachbulletobject errormarker cachestats
|
||||
resourcemanager stats animation foreachbulletobject errormarker cachestats bgsmfilemanager
|
||||
)
|
||||
|
||||
add_component_dir (shader
|
||||
@ -511,7 +515,7 @@ set (ESM_UI ${CMAKE_CURRENT_SOURCE_DIR}/contentselector/contentselector.ui
|
||||
if (USE_QT)
|
||||
add_component_qt_dir (contentselector
|
||||
model/modelitem model/esmfile
|
||||
model/naturalsort model/contentmodel
|
||||
model/contentmodel
|
||||
model/loadordererror
|
||||
view/combobox view/contentselector
|
||||
)
|
||||
|
236
components/bgsm/file.cpp
Normal file
236
components/bgsm/file.cpp
Normal file
@ -0,0 +1,236 @@
|
||||
#include "file.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "stream.hpp"
|
||||
|
||||
namespace Bgsm
|
||||
{
|
||||
MaterialFilePtr parse(Files::IStreamPtr&& inputStream)
|
||||
{
|
||||
std::shared_ptr<Bgsm::MaterialFile> file;
|
||||
BGSMStream stream(std::move(inputStream));
|
||||
|
||||
std::array<char, 4> signature;
|
||||
stream.readArray(signature);
|
||||
std::string shaderType(signature.data(), 4);
|
||||
if (shaderType == "BGEM")
|
||||
{
|
||||
file = std::make_shared<BGEMFile>();
|
||||
file->mShaderType = Bgsm::ShaderType::Effect;
|
||||
}
|
||||
else if (shaderType == "BGSM")
|
||||
{
|
||||
file = std::make_shared<BGSMFile>();
|
||||
file->mShaderType = Bgsm::ShaderType::Lighting;
|
||||
}
|
||||
else
|
||||
throw std::runtime_error("Invalid material file");
|
||||
|
||||
file->read(stream);
|
||||
return file;
|
||||
}
|
||||
|
||||
void MaterialFile::read(BGSMStream& stream)
|
||||
{
|
||||
stream.read(mVersion);
|
||||
stream.read(mClamp);
|
||||
stream.read(mUVOffset);
|
||||
stream.read(mUVScale);
|
||||
stream.read(mTransparency);
|
||||
stream.read(mAlphaBlend);
|
||||
stream.read(mSourceBlendMode);
|
||||
stream.read(mDestinationBlendMode);
|
||||
stream.read(mAlphaTestThreshold);
|
||||
stream.read(mAlphaTest);
|
||||
stream.read(mDepthWrite);
|
||||
stream.read(mDepthTest);
|
||||
stream.read(mSSR);
|
||||
stream.read(mWetnessControlSSR);
|
||||
stream.read(mDecal);
|
||||
stream.read(mTwoSided);
|
||||
stream.read(mDecalNoFade);
|
||||
stream.read(mNonOccluder);
|
||||
stream.read(mRefraction);
|
||||
stream.read(mRefractionFalloff);
|
||||
stream.read(mRefractionPower);
|
||||
if (mVersion < 10)
|
||||
{
|
||||
stream.read(mEnvMapEnabled);
|
||||
stream.read(mEnvMapMaskScale);
|
||||
}
|
||||
else
|
||||
{
|
||||
stream.read(mDepthBias);
|
||||
}
|
||||
stream.read(mGrayscaleToPaletteColor);
|
||||
if (mVersion >= 6)
|
||||
stream.read(mMaskWrites);
|
||||
}
|
||||
|
||||
void BGSMFile::read(BGSMStream& stream)
|
||||
{
|
||||
MaterialFile::read(stream);
|
||||
|
||||
stream.read(mDiffuseMap);
|
||||
stream.read(mNormalMap);
|
||||
stream.read(mSmoothSpecMap);
|
||||
stream.read(mGrayscaleMap);
|
||||
if (mVersion >= 3)
|
||||
{
|
||||
stream.read(mGlowMap);
|
||||
stream.read(mWrinkleMap);
|
||||
stream.read(mSpecularMap);
|
||||
stream.read(mLightingMap);
|
||||
stream.read(mFlowMap);
|
||||
if (mVersion >= 17)
|
||||
stream.read(mDistanceFieldAlphaMap);
|
||||
}
|
||||
else
|
||||
{
|
||||
stream.read(mEnvMap);
|
||||
stream.read(mGlowMap);
|
||||
stream.read(mInnerLayerMap);
|
||||
stream.read(mWrinkleMap);
|
||||
stream.read(mDisplacementMap);
|
||||
}
|
||||
stream.read(mEnableEditorAlphaThreshold);
|
||||
if (mVersion >= 8)
|
||||
{
|
||||
stream.read(mTranslucency);
|
||||
stream.read(mTranslucencyThickObject);
|
||||
stream.read(mTranslucencyMixAlbedoWithSubsurfaceColor);
|
||||
stream.read(mTranslucencySubsurfaceColor);
|
||||
stream.read(mTranslucencyTransmissiveScale);
|
||||
stream.read(mTranslucencyTurbulence);
|
||||
}
|
||||
else
|
||||
{
|
||||
stream.read(mRimLighting);
|
||||
stream.read(mRimPower);
|
||||
stream.read(mBackLightPower);
|
||||
stream.read(mSubsurfaceLighting);
|
||||
stream.read(mSubsurfaceLightingRolloff);
|
||||
}
|
||||
stream.read(mSpecularEnabled);
|
||||
stream.read(mSpecularColor);
|
||||
stream.read(mSpecularMult);
|
||||
stream.read(mSmoothness);
|
||||
stream.read(mFresnelPower);
|
||||
stream.read(mWetnessControlSpecScale);
|
||||
stream.read(mWetnessControlSpecPowerScale);
|
||||
stream.read(mWetnessControlSpecMinvar);
|
||||
if (mVersion < 10)
|
||||
stream.read(mWetnessControlEnvMapScale);
|
||||
stream.read(mWetnessControlFresnelPower);
|
||||
stream.read(mWetnessControlMetalness);
|
||||
if (mVersion >= 3)
|
||||
{
|
||||
stream.read(mPBR);
|
||||
if (mVersion >= 9)
|
||||
{
|
||||
stream.read(mCustomPorosity);
|
||||
stream.read(mPorosityValue);
|
||||
}
|
||||
}
|
||||
stream.read(mRootMaterialPath);
|
||||
stream.read(mAnisoLighting);
|
||||
stream.read(mEmitEnabled);
|
||||
if (mEmitEnabled)
|
||||
stream.read(mEmittanceColor);
|
||||
stream.read(mEmittanceMult);
|
||||
stream.read(mModelSpaceNormals);
|
||||
stream.read(mExternalEmittance);
|
||||
if (mVersion >= 12)
|
||||
{
|
||||
stream.read(mLumEmittance);
|
||||
if (mVersion >= 13)
|
||||
{
|
||||
stream.read(mUseAdaptiveEmissive);
|
||||
stream.read(mAdaptiveEmissiveExposureParams);
|
||||
}
|
||||
}
|
||||
else if (mVersion < 8)
|
||||
{
|
||||
stream.read(mBackLighting);
|
||||
}
|
||||
stream.read(mReceiveShadows);
|
||||
stream.read(mHideSecret);
|
||||
stream.read(mCastShadows);
|
||||
stream.read(mDissolveFade);
|
||||
stream.read(mAssumeShadowmask);
|
||||
stream.read(mGlowMapEnabled);
|
||||
if (mVersion < 7)
|
||||
{
|
||||
stream.read(mEnvMapWindow);
|
||||
stream.read(mEnvMapEye);
|
||||
}
|
||||
stream.read(mHair);
|
||||
stream.read(mHairTintColor);
|
||||
stream.read(mTree);
|
||||
stream.read(mFacegen);
|
||||
stream.read(mSkinTint);
|
||||
stream.read(mTessellate);
|
||||
if (mVersion < 3)
|
||||
{
|
||||
stream.read(mDisplacementMapParams);
|
||||
stream.read(mTessellationParams);
|
||||
}
|
||||
stream.read(mGrayscaleToPaletteScale);
|
||||
if (mVersion >= 1)
|
||||
{
|
||||
stream.read(mSkewSpecularAlpha);
|
||||
stream.read(mTerrain);
|
||||
if (mTerrain)
|
||||
{
|
||||
if (mVersion == 3)
|
||||
stream.skip(4); // Unknown
|
||||
|
||||
stream.read(mTerrainParams);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BGEMFile::read(BGSMStream& stream)
|
||||
{
|
||||
MaterialFile::read(stream);
|
||||
|
||||
stream.read(mBaseMap);
|
||||
stream.read(mGrayscaleMap);
|
||||
stream.read(mEnvMap);
|
||||
stream.read(mNormalMap);
|
||||
stream.read(mEnvMapMask);
|
||||
if (mVersion >= 11)
|
||||
{
|
||||
stream.read(mSpecularMap);
|
||||
stream.read(mLightingMap);
|
||||
stream.read(mGlowMap);
|
||||
}
|
||||
if (mVersion >= 10)
|
||||
{
|
||||
stream.read(mEnvMapEnabled);
|
||||
stream.read(mEnvMapMaskScale);
|
||||
}
|
||||
stream.read(mBlood);
|
||||
stream.read(mEffectLighting);
|
||||
stream.read(mFalloff);
|
||||
stream.read(mFalloffColor);
|
||||
stream.read(mGrayscaleToPaletteAlpha);
|
||||
stream.read(mSoft);
|
||||
stream.read(mBaseColor);
|
||||
stream.read(mBaseColorScale);
|
||||
stream.read(mFalloffParams);
|
||||
stream.read(mLightingInfluence);
|
||||
stream.read(mEnvmapMinLOD);
|
||||
stream.read(mSoftDepth);
|
||||
if (mVersion >= 11)
|
||||
stream.read(mEmittanceColor);
|
||||
if (mVersion >= 15)
|
||||
stream.read(mAdaptiveEmissiveExposureParams);
|
||||
if (mVersion >= 16)
|
||||
stream.read(mGlowMapEnabled);
|
||||
if (mVersion >= 20)
|
||||
stream.read(mEffectPbrSpecular);
|
||||
}
|
||||
}
|
167
components/bgsm/file.hpp
Normal file
167
components/bgsm/file.hpp
Normal file
@ -0,0 +1,167 @@
|
||||
#ifndef OPENMW_COMPONENTS_BGSM_FILE_HPP
|
||||
#define OPENMW_COMPONENTS_BGSM_FILE_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include <osg/Vec2f>
|
||||
#include <osg/Vec3f>
|
||||
#include <osg/Vec4f>
|
||||
|
||||
#include <components/files/istreamptr.hpp>
|
||||
|
||||
namespace Bgsm
|
||||
{
|
||||
class BGSMStream;
|
||||
|
||||
enum class ShaderType
|
||||
{
|
||||
Lighting,
|
||||
Effect,
|
||||
};
|
||||
|
||||
struct MaterialFile
|
||||
{
|
||||
ShaderType mShaderType;
|
||||
std::uint32_t mVersion;
|
||||
std::uint32_t mClamp;
|
||||
osg::Vec2f mUVOffset, mUVScale;
|
||||
float mTransparency;
|
||||
bool mAlphaBlend;
|
||||
std::uint32_t mSourceBlendMode;
|
||||
std::uint32_t mDestinationBlendMode;
|
||||
std::uint8_t mAlphaTestThreshold;
|
||||
bool mAlphaTest;
|
||||
bool mDepthWrite, mDepthTest;
|
||||
bool mSSR;
|
||||
bool mWetnessControlSSR;
|
||||
bool mDecal;
|
||||
bool mTwoSided;
|
||||
bool mDecalNoFade;
|
||||
bool mNonOccluder;
|
||||
bool mRefraction;
|
||||
bool mRefractionFalloff;
|
||||
float mRefractionPower;
|
||||
bool mEnvMapEnabled;
|
||||
float mEnvMapMaskScale;
|
||||
bool mDepthBias;
|
||||
bool mGrayscaleToPaletteColor;
|
||||
std::uint8_t mMaskWrites;
|
||||
|
||||
MaterialFile() = default;
|
||||
virtual void read(BGSMStream& stream);
|
||||
virtual ~MaterialFile() = default;
|
||||
};
|
||||
|
||||
struct BGSMFile : MaterialFile
|
||||
{
|
||||
std::string mDiffuseMap;
|
||||
std::string mNormalMap;
|
||||
std::string mSmoothSpecMap;
|
||||
std::string mGrayscaleMap;
|
||||
std::string mGlowMap;
|
||||
std::string mWrinkleMap;
|
||||
std::string mSpecularMap;
|
||||
std::string mLightingMap;
|
||||
std::string mFlowMap;
|
||||
std::string mDistanceFieldAlphaMap;
|
||||
std::string mEnvMap;
|
||||
std::string mInnerLayerMap;
|
||||
std::string mDisplacementMap;
|
||||
bool mEnableEditorAlphaThreshold;
|
||||
bool mTranslucency;
|
||||
bool mTranslucencyThickObject;
|
||||
bool mTranslucencyMixAlbedoWithSubsurfaceColor;
|
||||
osg::Vec3f mTranslucencySubsurfaceColor;
|
||||
float mTranslucencyTransmissiveScale;
|
||||
float mTranslucencyTurbulence;
|
||||
bool mRimLighting;
|
||||
float mRimPower;
|
||||
float mBackLightPower;
|
||||
bool mSubsurfaceLighting;
|
||||
float mSubsurfaceLightingRolloff;
|
||||
bool mSpecularEnabled;
|
||||
osg::Vec3f mSpecularColor;
|
||||
float mSpecularMult;
|
||||
float mSmoothness;
|
||||
float mFresnelPower;
|
||||
float mWetnessControlSpecScale;
|
||||
float mWetnessControlSpecPowerScale;
|
||||
float mWetnessControlSpecMinvar;
|
||||
float mWetnessControlEnvMapScale;
|
||||
float mWetnessControlFresnelPower;
|
||||
float mWetnessControlMetalness;
|
||||
bool mPBR;
|
||||
bool mCustomPorosity;
|
||||
float mPorosityValue;
|
||||
std::string mRootMaterialPath;
|
||||
bool mAnisoLighting;
|
||||
bool mEmitEnabled;
|
||||
osg::Vec3f mEmittanceColor;
|
||||
float mEmittanceMult;
|
||||
bool mModelSpaceNormals;
|
||||
bool mExternalEmittance;
|
||||
float mLumEmittance;
|
||||
bool mUseAdaptiveEmissive;
|
||||
osg::Vec3f mAdaptiveEmissiveExposureParams;
|
||||
bool mBackLighting;
|
||||
bool mReceiveShadows;
|
||||
bool mHideSecret;
|
||||
bool mCastShadows;
|
||||
bool mDissolveFade;
|
||||
bool mAssumeShadowmask;
|
||||
bool mGlowMapEnabled;
|
||||
bool mEnvMapWindow;
|
||||
bool mEnvMapEye;
|
||||
bool mHair;
|
||||
osg::Vec3f mHairTintColor;
|
||||
bool mTree;
|
||||
bool mFacegen;
|
||||
bool mSkinTint;
|
||||
bool mTessellate;
|
||||
osg::Vec2f mDisplacementMapParams;
|
||||
osg::Vec3f mTessellationParams;
|
||||
float mGrayscaleToPaletteScale;
|
||||
bool mSkewSpecularAlpha;
|
||||
bool mTerrain;
|
||||
osg::Vec3f mTerrainParams;
|
||||
|
||||
void read(BGSMStream& stream) override;
|
||||
};
|
||||
|
||||
struct BGEMFile : MaterialFile
|
||||
{
|
||||
std::string mBaseMap;
|
||||
std::string mGrayscaleMap;
|
||||
std::string mEnvMap;
|
||||
std::string mNormalMap;
|
||||
std::string mEnvMapMask;
|
||||
std::string mSpecularMap;
|
||||
std::string mLightingMap;
|
||||
std::string mGlowMap;
|
||||
bool mBlood;
|
||||
bool mEffectLighting;
|
||||
bool mFalloff;
|
||||
bool mFalloffColor;
|
||||
bool mGrayscaleToPaletteAlpha;
|
||||
bool mSoft;
|
||||
osg::Vec3f mBaseColor;
|
||||
float mBaseColorScale;
|
||||
osg::Vec4f mFalloffParams;
|
||||
float mLightingInfluence;
|
||||
std::uint8_t mEnvmapMinLOD;
|
||||
float mSoftDepth;
|
||||
osg::Vec3f mEmittanceColor;
|
||||
osg::Vec3f mAdaptiveEmissiveExposureParams;
|
||||
bool mGlowMapEnabled;
|
||||
bool mEffectPbrSpecular;
|
||||
|
||||
void read(BGSMStream& stream) override;
|
||||
};
|
||||
|
||||
using MaterialFilePtr = std::shared_ptr<const Bgsm::MaterialFile>;
|
||||
MaterialFilePtr parse(Files::IStreamPtr&& stream);
|
||||
}
|
||||
#endif
|
39
components/bgsm/stream.cpp
Normal file
39
components/bgsm/stream.cpp
Normal file
@ -0,0 +1,39 @@
|
||||
#include "stream.hpp"
|
||||
|
||||
namespace Bgsm
|
||||
{
|
||||
template <>
|
||||
void BGSMStream::read<osg::Vec2f>(osg::Vec2f& vec)
|
||||
{
|
||||
readBufferOfType(mStream, vec._v);
|
||||
}
|
||||
|
||||
template <>
|
||||
void BGSMStream::read<osg::Vec3f>(osg::Vec3f& vec)
|
||||
{
|
||||
readBufferOfType(mStream, vec._v);
|
||||
}
|
||||
|
||||
template <>
|
||||
void BGSMStream::read<osg::Vec4f>(osg::Vec4f& vec)
|
||||
{
|
||||
readBufferOfType(mStream, vec._v);
|
||||
}
|
||||
|
||||
template <>
|
||||
void BGSMStream::read<std::string>(std::string& str)
|
||||
{
|
||||
std::uint32_t length;
|
||||
read(length);
|
||||
// Prevent potential memory allocation freezes; strings this long are not expected in BGSM
|
||||
if (length > 1024)
|
||||
throw std::runtime_error("Requested string length is too large: " + std::to_string(length));
|
||||
str = std::string(length, '\0');
|
||||
mStream->read(str.data(), length);
|
||||
if (mStream->bad())
|
||||
throw std::runtime_error("Failed to read sized string of " + std::to_string(length) + " chars");
|
||||
std::size_t end = str.find('\0');
|
||||
if (end != std::string::npos)
|
||||
str.erase(end);
|
||||
}
|
||||
}
|
77
components/bgsm/stream.hpp
Normal file
77
components/bgsm/stream.hpp
Normal file
@ -0,0 +1,77 @@
|
||||
#ifndef OPENMW_COMPONENTS_BGSM_STREAM_HPP
|
||||
#define OPENMW_COMPONENTS_BGSM_STREAM_HPP
|
||||
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <istream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
#include <components/files/istreamptr.hpp>
|
||||
#include <components/misc/endianness.hpp>
|
||||
|
||||
#include <osg/Vec2f>
|
||||
#include <osg/Vec3f>
|
||||
#include <osg/Vec4f>
|
||||
|
||||
namespace Bgsm
|
||||
{
|
||||
template <std::size_t numInstances, typename T>
|
||||
inline void readBufferOfType(Files::IStreamPtr& pIStream, T* dest)
|
||||
{
|
||||
static_assert(std::is_arithmetic_v<T>, "Buffer element type is not arithmetic");
|
||||
pIStream->read(reinterpret_cast<char*>(dest), numInstances * sizeof(T));
|
||||
if (pIStream->bad())
|
||||
throw std::runtime_error("Failed to read typed (" + std::string(typeid(T).name()) + ") buffer of "
|
||||
+ std::to_string(numInstances) + " instances");
|
||||
if constexpr (Misc::IS_BIG_ENDIAN)
|
||||
for (std::size_t i = 0; i < numInstances; i++)
|
||||
Misc::swapEndiannessInplace(dest[i]);
|
||||
}
|
||||
|
||||
template <std::size_t numInstances, typename T>
|
||||
inline void readBufferOfType(Files::IStreamPtr& pIStream, T (&dest)[numInstances])
|
||||
{
|
||||
readBufferOfType<numInstances>(pIStream, static_cast<T*>(dest));
|
||||
}
|
||||
|
||||
class BGSMStream
|
||||
{
|
||||
Files::IStreamPtr mStream;
|
||||
|
||||
public:
|
||||
explicit BGSMStream(Files::IStreamPtr&& stream)
|
||||
: mStream(std::move(stream))
|
||||
{
|
||||
}
|
||||
|
||||
void skip(size_t size) { mStream->ignore(size); }
|
||||
|
||||
/// Read into a single instance of type
|
||||
template <class T>
|
||||
void read(T& data)
|
||||
{
|
||||
readBufferOfType<1>(mStream, &data);
|
||||
}
|
||||
|
||||
/// Read multiple instances of type into an array
|
||||
template <class T, size_t size>
|
||||
void readArray(std::array<T, size>& arr)
|
||||
{
|
||||
readBufferOfType<size>(mStream, arr.data());
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
void BGSMStream::read<osg::Vec2f>(osg::Vec2f& vec);
|
||||
template <>
|
||||
void BGSMStream::read<osg::Vec3f>(osg::Vec3f& vec);
|
||||
template <>
|
||||
void BGSMStream::read<osg::Vec4f>(osg::Vec4f& vec);
|
||||
template <>
|
||||
void BGSMStream::read<std::string>(std::string& str);
|
||||
}
|
||||
|
||||
#endif
|
@ -1,112 +0,0 @@
|
||||
/*
|
||||
* This file contains code found in the QtGui module of the Qt Toolkit.
|
||||
* See Qt's qfilesystemmodel source files for more information
|
||||
*/
|
||||
|
||||
#include "naturalsort.hpp"
|
||||
|
||||
static inline QChar getNextChar(const QString& s, int location)
|
||||
{
|
||||
return (location < s.length()) ? s.at(location) : QChar();
|
||||
}
|
||||
|
||||
/*!
|
||||
* Natural number sort, skips spaces.
|
||||
*
|
||||
* Examples:
|
||||
* 1, 2, 10, 55, 100
|
||||
* 01.jpg, 2.jpg, 10.jpg
|
||||
*
|
||||
* Note on the algorithm:
|
||||
* Only as many characters as necessary are looked at and at most they all
|
||||
* are looked at once.
|
||||
*
|
||||
* Slower then QString::compare() (of course)
|
||||
*/
|
||||
int naturalCompare(const QString& s1, const QString& s2, Qt::CaseSensitivity cs)
|
||||
{
|
||||
for (int l1 = 0, l2 = 0; l1 <= s1.count() && l2 <= s2.count(); ++l1, ++l2)
|
||||
{
|
||||
// skip spaces, tabs and 0's
|
||||
QChar c1 = getNextChar(s1, l1);
|
||||
while (c1.isSpace())
|
||||
c1 = getNextChar(s1, ++l1);
|
||||
QChar c2 = getNextChar(s2, l2);
|
||||
while (c2.isSpace())
|
||||
c2 = getNextChar(s2, ++l2);
|
||||
|
||||
if (c1.isDigit() && c2.isDigit())
|
||||
{
|
||||
while (c1.digitValue() == 0)
|
||||
c1 = getNextChar(s1, ++l1);
|
||||
while (c2.digitValue() == 0)
|
||||
c2 = getNextChar(s2, ++l2);
|
||||
|
||||
int lookAheadLocation1 = l1;
|
||||
int lookAheadLocation2 = l2;
|
||||
int currentReturnValue = 0;
|
||||
// find the last digit, setting currentReturnValue as we go if it isn't equal
|
||||
for (QChar lookAhead1 = c1, lookAhead2 = c2;
|
||||
(lookAheadLocation1 <= s1.length() && lookAheadLocation2 <= s2.length());
|
||||
lookAhead1 = getNextChar(s1, ++lookAheadLocation1), lookAhead2 = getNextChar(s2, ++lookAheadLocation2))
|
||||
{
|
||||
bool is1ADigit = !lookAhead1.isNull() && lookAhead1.isDigit();
|
||||
bool is2ADigit = !lookAhead2.isNull() && lookAhead2.isDigit();
|
||||
if (!is1ADigit && !is2ADigit)
|
||||
break;
|
||||
if (!is1ADigit)
|
||||
return -1;
|
||||
if (!is2ADigit)
|
||||
return 1;
|
||||
if (currentReturnValue == 0)
|
||||
{
|
||||
if (lookAhead1 < lookAhead2)
|
||||
{
|
||||
currentReturnValue = -1;
|
||||
}
|
||||
else if (lookAhead1 > lookAhead2)
|
||||
{
|
||||
currentReturnValue = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (currentReturnValue != 0)
|
||||
return currentReturnValue;
|
||||
}
|
||||
|
||||
if (cs == Qt::CaseInsensitive)
|
||||
{
|
||||
if (!c1.isLower())
|
||||
c1 = c1.toLower();
|
||||
if (!c2.isLower())
|
||||
c2 = c2.toLower();
|
||||
}
|
||||
int r = QString::localeAwareCompare(c1, c2);
|
||||
if (r < 0)
|
||||
return -1;
|
||||
if (r > 0)
|
||||
return 1;
|
||||
}
|
||||
// The two strings are the same (02 == 2) so fall back to the normal sort
|
||||
return QString::compare(s1, s2, cs);
|
||||
}
|
||||
|
||||
bool naturalSortLessThanCS(const QString& left, const QString& right)
|
||||
{
|
||||
return (naturalCompare(left, right, Qt::CaseSensitive) < 0);
|
||||
}
|
||||
|
||||
bool naturalSortLessThanCI(const QString& left, const QString& right)
|
||||
{
|
||||
return (naturalCompare(left, right, Qt::CaseInsensitive) < 0);
|
||||
}
|
||||
|
||||
bool naturalSortGreaterThanCS(const QString& left, const QString& right)
|
||||
{
|
||||
return (naturalCompare(left, right, Qt::CaseSensitive) > 0);
|
||||
}
|
||||
|
||||
bool naturalSortGreaterThanCI(const QString& left, const QString& right)
|
||||
{
|
||||
return (naturalCompare(left, right, Qt::CaseInsensitive) > 0);
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
#ifndef NATURALSORT_H
|
||||
#define NATURALSORT_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
bool naturalSortLessThanCS(const QString& left, const QString& right);
|
||||
bool naturalSortLessThanCI(const QString& left, const QString& right);
|
||||
bool naturalSortGreaterThanCS(const QString& left, const QString& right);
|
||||
bool naturalSortGreaterThanCI(const QString& left, const QString& right);
|
||||
|
||||
#endif
|
@ -31,7 +31,7 @@ ContentSelectorView::ContentSelector::~ContentSelector() = default;
|
||||
|
||||
void ContentSelectorView::ContentSelector::buildContentModel(bool showOMWScripts)
|
||||
{
|
||||
QIcon warningIcon(ui->addonView->style()->standardIcon(QStyle::SP_MessageBoxWarning).pixmap(QSize(16, 15)));
|
||||
QIcon warningIcon(ui->addonView->style()->standardIcon(QStyle::SP_MessageBoxWarning));
|
||||
mContentModel = new ContentSelectorModel::ContentModel(this, warningIcon, showOMWScripts);
|
||||
}
|
||||
|
||||
|
@ -173,6 +173,11 @@ std::string Misc::ResourceHelpers::correctActorModelPath(const std::string& resP
|
||||
return mdlname;
|
||||
}
|
||||
|
||||
std::string Misc::ResourceHelpers::correctMaterialPath(std::string_view resPath, const VFS::Manager* vfs)
|
||||
{
|
||||
return correctResourcePath({ { "materials" } }, resPath, vfs);
|
||||
}
|
||||
|
||||
std::string Misc::ResourceHelpers::correctMeshPath(std::string_view resPath)
|
||||
{
|
||||
std::string res = "meshes\\";
|
||||
@ -186,9 +191,10 @@ VFS::Path::Normalized Misc::ResourceHelpers::correctSoundPath(VFS::Path::Normali
|
||||
return prefix / resPath;
|
||||
}
|
||||
|
||||
std::string Misc::ResourceHelpers::correctMusicPath(const std::string& resPath)
|
||||
VFS::Path::Normalized Misc::ResourceHelpers::correctMusicPath(VFS::Path::NormalizedView resPath)
|
||||
{
|
||||
return "music\\" + resPath;
|
||||
static constexpr VFS::Path::NormalizedView prefix("music");
|
||||
return prefix / resPath;
|
||||
}
|
||||
|
||||
std::string_view Misc::ResourceHelpers::meshPathForESM3(std::string_view resPath)
|
||||
|
@ -34,15 +34,16 @@ namespace Misc
|
||||
/// Use "xfoo.nif" instead of "foo.nif" if "xfoo.kf" is available
|
||||
/// Note that if "xfoo.nif" is actually unavailable, we can't fall back to "foo.nif". :(
|
||||
std::string correctActorModelPath(const std::string& resPath, const VFS::Manager* vfs);
|
||||
std::string correctMaterialPath(std::string_view resPath, const VFS::Manager* vfs);
|
||||
|
||||
// Adds "meshes\\".
|
||||
std::string correctMeshPath(std::string_view resPath);
|
||||
|
||||
// Adds "sound\\".
|
||||
// Prepends "sound/".
|
||||
VFS::Path::Normalized correctSoundPath(VFS::Path::NormalizedView resPath);
|
||||
|
||||
// Adds "music\\".
|
||||
std::string correctMusicPath(const std::string& resPath);
|
||||
// Prepends "music/".
|
||||
VFS::Path::Normalized correctMusicPath(VFS::Path::NormalizedView resPath);
|
||||
|
||||
// Removes "meshes\\".
|
||||
std::string_view meshPathForESM3(std::string_view resPath);
|
||||
|
@ -15,6 +15,13 @@ namespace Misc::StringUtils
|
||||
bool operator()(char x, char y) const { return toLower(x) < toLower(y); }
|
||||
};
|
||||
|
||||
inline std::string underscoresToSpaces(const std::string_view oldName)
|
||||
{
|
||||
std::string newName(oldName);
|
||||
std::replace(newName.begin(), newName.end(), '_', ' ');
|
||||
return newName;
|
||||
}
|
||||
|
||||
inline bool ciLess(std::string_view x, std::string_view y)
|
||||
{
|
||||
return std::lexicographical_compare(x.begin(), x.end(), y.begin(), y.end(), CiCharLess());
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include <components/misc/strings/algorithm.hpp>
|
||||
#include <components/misc/strings/lower.hpp>
|
||||
#include <components/nif/parent.hpp>
|
||||
#include <components/resource/bgsmfilemanager.hpp>
|
||||
#include <components/resource/imagemanager.hpp>
|
||||
|
||||
// particle
|
||||
@ -42,6 +43,7 @@
|
||||
#include <osg/TexEnvCombine>
|
||||
#include <osg/Texture2D>
|
||||
|
||||
#include <components/bgsm/file.hpp>
|
||||
#include <components/nif/effect.hpp>
|
||||
#include <components/nif/exception.hpp>
|
||||
#include <components/nif/extra.hpp>
|
||||
@ -247,6 +249,8 @@ namespace NifOsg
|
||||
}
|
||||
std::filesystem::path mFilename;
|
||||
unsigned int mVersion, mUserVersion, mBethVersion;
|
||||
Resource::BgsmFileManager* mMaterialManager{ nullptr };
|
||||
Resource::ImageManager* mImageManager{ nullptr };
|
||||
|
||||
size_t mFirstRootTextureIndex{ ~0u };
|
||||
bool mFoundFirstRootTexturingProperty = false;
|
||||
@ -339,7 +343,6 @@ namespace NifOsg
|
||||
struct HandleNodeArgs
|
||||
{
|
||||
unsigned int mNifVersion;
|
||||
Resource::ImageManager* mImageManager;
|
||||
SceneUtil::TextKeyMap* mTextKeys;
|
||||
std::vector<unsigned int> mBoundTextures = {};
|
||||
int mAnimFlags = 0;
|
||||
@ -349,7 +352,7 @@ namespace NifOsg
|
||||
osg::Node* mRootNode = nullptr;
|
||||
};
|
||||
|
||||
osg::ref_ptr<osg::Node> load(Nif::FileView nif, Resource::ImageManager* imageManager)
|
||||
osg::ref_ptr<osg::Node> load(Nif::FileView nif)
|
||||
{
|
||||
const size_t numRoots = nif.numRoots();
|
||||
std::vector<const Nif::NiAVObject*> roots;
|
||||
@ -371,10 +374,8 @@ namespace NifOsg
|
||||
created->setDataVariance(osg::Object::STATIC);
|
||||
for (const Nif::NiAVObject* root : roots)
|
||||
{
|
||||
auto node = handleNode(root, nullptr, nullptr,
|
||||
{ .mNifVersion = nif.getVersion(),
|
||||
.mImageManager = imageManager,
|
||||
.mTextKeys = &textkeys->mTextKeys });
|
||||
auto node = handleNode(
|
||||
root, nullptr, nullptr, { .mNifVersion = nif.getVersion(), .mTextKeys = &textkeys->mTextKeys });
|
||||
created->addChild(node);
|
||||
}
|
||||
if (mHasNightDayLabel)
|
||||
@ -405,8 +406,7 @@ namespace NifOsg
|
||||
}
|
||||
|
||||
void applyNodeProperties(const Nif::NiAVObject* nifNode, osg::Node* applyTo,
|
||||
SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager,
|
||||
std::vector<unsigned int>& boundTextures, int animflags)
|
||||
SceneUtil::CompositeStateSetUpdater* composite, std::vector<unsigned int>& boundTextures, int animflags)
|
||||
{
|
||||
bool hasStencilProperty = false;
|
||||
|
||||
@ -444,8 +444,7 @@ namespace NifOsg
|
||||
if (property.getPtr()->recIndex == mFirstRootTextureIndex)
|
||||
applyTo->setUserValue("overrideFx", 1);
|
||||
}
|
||||
handleProperty(property.getPtr(), applyTo, composite, imageManager, boundTextures, animflags,
|
||||
hasStencilProperty);
|
||||
handleProperty(property.getPtr(), applyTo, composite, boundTextures, animflags, hasStencilProperty);
|
||||
}
|
||||
}
|
||||
|
||||
@ -457,8 +456,7 @@ namespace NifOsg
|
||||
shaderprop = static_cast<const Nif::BSTriShape*>(nifNode)->mShaderProperty;
|
||||
|
||||
if (!shaderprop.empty())
|
||||
handleProperty(shaderprop.getPtr(), applyTo, composite, imageManager, boundTextures, animflags,
|
||||
hasStencilProperty);
|
||||
handleProperty(shaderprop.getPtr(), applyTo, composite, boundTextures, animflags, hasStencilProperty);
|
||||
}
|
||||
|
||||
static void setupController(const Nif::NiTimeController* ctrl, SceneUtil::Controller* toSetup, int animflags)
|
||||
@ -522,32 +520,21 @@ namespace NifOsg
|
||||
sequenceNode->setMode(osg::Sequence::START);
|
||||
}
|
||||
|
||||
osg::ref_ptr<osg::Image> handleSourceTexture(
|
||||
const Nif::NiSourceTexture* st, Resource::ImageManager* imageManager)
|
||||
osg::ref_ptr<osg::Image> handleSourceTexture(const Nif::NiSourceTexture* st) const
|
||||
{
|
||||
if (!st)
|
||||
return nullptr;
|
||||
if (st)
|
||||
{
|
||||
if (st->mExternal)
|
||||
return getTextureImage(st->mFile);
|
||||
|
||||
osg::ref_ptr<osg::Image> image;
|
||||
if (st->mExternal)
|
||||
{
|
||||
std::string filename = Misc::ResourceHelpers::correctTexturePath(st->mFile, imageManager->getVFS());
|
||||
image = imageManager->getImage(filename);
|
||||
if (!st->mData.empty())
|
||||
return handleInternalTexture(st->mData.getPtr());
|
||||
}
|
||||
else if (!st->mData.empty())
|
||||
{
|
||||
image = handleInternalTexture(st->mData.getPtr());
|
||||
}
|
||||
return image;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void handleTextureWrapping(osg::Texture2D* texture, bool wrapS, bool wrapT)
|
||||
{
|
||||
texture->setWrap(osg::Texture::WRAP_S, wrapS ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE);
|
||||
texture->setWrap(osg::Texture::WRAP_T, wrapT ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE);
|
||||
}
|
||||
|
||||
bool handleEffect(const Nif::NiAVObject* nifNode, osg::StateSet* stateset, Resource::ImageManager* imageManager)
|
||||
bool handleEffect(const Nif::NiAVObject* nifNode, osg::StateSet* stateset)
|
||||
{
|
||||
if (nifNode->recType != Nif::RC_NiTextureEffect)
|
||||
{
|
||||
@ -590,16 +577,12 @@ namespace NifOsg
|
||||
return false;
|
||||
}
|
||||
|
||||
osg::ref_ptr<osg::Image> image(handleSourceTexture(textureEffect->mTexture.getPtr(), imageManager));
|
||||
osg::ref_ptr<osg::Texture2D> texture2d(new osg::Texture2D(image));
|
||||
if (image)
|
||||
texture2d->setTextureSize(image->s(), image->t());
|
||||
texture2d->setName("envMap");
|
||||
handleTextureWrapping(texture2d, textureEffect->wrapS(), textureEffect->wrapT());
|
||||
|
||||
int texUnit = 3; // FIXME
|
||||
|
||||
stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON);
|
||||
const unsigned int uvSet = 0;
|
||||
const unsigned int texUnit = 3; // FIXME
|
||||
std::vector<unsigned int> boundTextures;
|
||||
boundTextures.resize(3); // Dummy vector for attachNiSourceTexture
|
||||
attachNiSourceTexture("envMap", textureEffect->mTexture.getPtr(), textureEffect->wrapS(),
|
||||
textureEffect->wrapT(), uvSet, stateset, boundTextures);
|
||||
stateset->setTextureAttributeAndModes(texUnit, texGen, osg::StateAttribute::ON);
|
||||
stateset->setTextureAttributeAndModes(texUnit, createEmissiveTexEnv(), osg::StateAttribute::ON);
|
||||
|
||||
@ -761,7 +744,7 @@ namespace NifOsg
|
||||
|
||||
osg::ref_ptr<SceneUtil::CompositeStateSetUpdater> composite = new SceneUtil::CompositeStateSetUpdater;
|
||||
|
||||
applyNodeProperties(nifNode, node, composite, args.mImageManager, args.mBoundTextures, args.mAnimFlags);
|
||||
applyNodeProperties(nifNode, node, composite, args.mBoundTextures, args.mAnimFlags);
|
||||
|
||||
const bool isNiGeometry = isTypeNiGeometry(nifNode->recType);
|
||||
const bool isBSGeometry = isTypeBSGeometry(nifNode->recType);
|
||||
@ -769,7 +752,7 @@ namespace NifOsg
|
||||
|
||||
if (isGeometry && !args.mSkipMeshes)
|
||||
{
|
||||
bool skip;
|
||||
bool skip = false;
|
||||
if (args.mNifVersion <= Nif::NIFFile::NIFVersion::VER_MW)
|
||||
{
|
||||
skip = (args.mHasMarkers && Misc::StringUtils::ciStartsWith(nifNode->mName, "tri editormarker"))
|
||||
@ -777,7 +760,11 @@ namespace NifOsg
|
||||
|| Misc::StringUtils::ciStartsWith(nifNode->mName, "tri shadow");
|
||||
}
|
||||
else
|
||||
skip = args.mHasMarkers && Misc::StringUtils::ciStartsWith(nifNode->mName, "EditorMarker");
|
||||
{
|
||||
if (args.mHasMarkers)
|
||||
skip = Misc::StringUtils::ciStartsWith(nifNode->mName, "EditorMarker")
|
||||
|| Misc::StringUtils::ciStartsWith(nifNode->mName, "VisibilityEditorMarker");
|
||||
}
|
||||
if (!skip)
|
||||
{
|
||||
if (isNiGeometry)
|
||||
@ -859,7 +846,7 @@ namespace NifOsg
|
||||
if (!effect.empty())
|
||||
{
|
||||
osg::ref_ptr<osg::StateSet> effectStateSet = new osg::StateSet;
|
||||
if (handleEffect(effect.getPtr(), effectStateSet, args.mImageManager))
|
||||
if (handleEffect(effect.getPtr(), effectStateSet))
|
||||
for (unsigned int i = 0; i < currentNode->getNumChildren(); ++i)
|
||||
currentNode->getChild(i)->getOrCreateStateSet()->merge(*effectStateSet);
|
||||
}
|
||||
@ -1025,9 +1012,56 @@ namespace NifOsg
|
||||
}
|
||||
}
|
||||
|
||||
osg::ref_ptr<osg::Image> getTextureImage(std::string_view path) const
|
||||
{
|
||||
if (!mImageManager)
|
||||
return nullptr;
|
||||
|
||||
std::string filename = Misc::ResourceHelpers::correctTexturePath(path, mImageManager->getVFS());
|
||||
return mImageManager->getImage(filename);
|
||||
}
|
||||
|
||||
osg::ref_ptr<osg::Texture2D> attachTexture(const std::string& name, osg::ref_ptr<osg::Image> image, bool wrapS,
|
||||
bool wrapT, unsigned int uvSet, osg::StateSet* stateset, std::vector<unsigned int>& boundTextures) const
|
||||
{
|
||||
osg::ref_ptr<osg::Texture2D> texture2d = new osg::Texture2D(image);
|
||||
if (image)
|
||||
texture2d->setTextureSize(image->s(), image->t());
|
||||
texture2d->setWrap(osg::Texture::WRAP_S, wrapS ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE);
|
||||
texture2d->setWrap(osg::Texture::WRAP_T, wrapT ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE);
|
||||
unsigned int texUnit = boundTextures.size();
|
||||
if (stateset)
|
||||
stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON);
|
||||
texture2d->setName(name);
|
||||
boundTextures.emplace_back(uvSet);
|
||||
return texture2d;
|
||||
}
|
||||
|
||||
osg::ref_ptr<osg::Texture2D> attachExternalTexture(const std::string& name, const std::string& path, bool wrapS,
|
||||
bool wrapT, unsigned int uvSet, osg::StateSet* stateset, std::vector<unsigned int>& boundTextures) const
|
||||
{
|
||||
return attachTexture(name, getTextureImage(path), wrapS, wrapT, uvSet, stateset, boundTextures);
|
||||
}
|
||||
|
||||
osg::ref_ptr<osg::Texture2D> attachNiSourceTexture(const std::string& name, const Nif::NiSourceTexture* st,
|
||||
bool wrapS, bool wrapT, unsigned int uvSet, osg::StateSet* stateset,
|
||||
std::vector<unsigned int>& boundTextures) const
|
||||
{
|
||||
return attachTexture(name, handleSourceTexture(st), wrapS, wrapT, uvSet, stateset, boundTextures);
|
||||
}
|
||||
|
||||
static void clearBoundTextures(osg::StateSet* stateset, std::vector<unsigned int>& boundTextures)
|
||||
{
|
||||
if (!boundTextures.empty())
|
||||
{
|
||||
for (unsigned int i = 0; i < boundTextures.size(); ++i)
|
||||
stateset->setTextureMode(i, GL_TEXTURE_2D, osg::StateAttribute::OFF);
|
||||
boundTextures.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void handleTextureControllers(const Nif::NiProperty* texProperty,
|
||||
SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager,
|
||||
osg::StateSet* stateset, int animflags)
|
||||
SceneUtil::CompositeStateSetUpdater* composite, osg::StateSet* stateset, int animflags)
|
||||
{
|
||||
for (Nif::NiTimeControllerPtr ctrl = texProperty->mController; !ctrl.empty(); ctrl = ctrl->mNext)
|
||||
{
|
||||
@ -1056,17 +1090,16 @@ namespace NifOsg
|
||||
wrapT = inherit->getWrap(osg::Texture2D::WRAP_T);
|
||||
}
|
||||
|
||||
const unsigned int uvSet = 0;
|
||||
std::vector<unsigned int> boundTextures; // Dummy list for attachTexture
|
||||
for (const auto& source : flipctrl->mSources)
|
||||
{
|
||||
if (source.empty())
|
||||
continue;
|
||||
|
||||
osg::ref_ptr<osg::Image> image(handleSourceTexture(source.getPtr(), imageManager));
|
||||
osg::ref_ptr<osg::Texture2D> texture(new osg::Texture2D(image));
|
||||
if (image)
|
||||
texture->setTextureSize(image->s(), image->t());
|
||||
texture->setWrap(osg::Texture::WRAP_S, wrapS);
|
||||
texture->setWrap(osg::Texture::WRAP_T, wrapT);
|
||||
// NB: not changing the stateset
|
||||
osg::ref_ptr<osg::Texture2D> texture
|
||||
= attachNiSourceTexture({}, source.getPtr(), wrapS, wrapT, uvSet, nullptr, boundTextures);
|
||||
textures.push_back(texture);
|
||||
}
|
||||
osg::ref_ptr<FlipController> callback(new FlipController(flipctrl, textures));
|
||||
@ -1811,7 +1844,7 @@ namespace NifOsg
|
||||
}
|
||||
}
|
||||
|
||||
osg::ref_ptr<osg::Image> handleInternalTexture(const Nif::NiPixelData* pixelData)
|
||||
osg::ref_ptr<osg::Image> handleInternalTexture(const Nif::NiPixelData* pixelData) const
|
||||
{
|
||||
if (pixelData->mMipmaps.empty())
|
||||
return nullptr;
|
||||
@ -1946,7 +1979,7 @@ namespace NifOsg
|
||||
return image;
|
||||
}
|
||||
|
||||
osg::ref_ptr<osg::TexEnvCombine> createEmissiveTexEnv()
|
||||
static osg::ref_ptr<osg::TexEnvCombine> createEmissiveTexEnv()
|
||||
{
|
||||
osg::ref_ptr<osg::TexEnvCombine> texEnv(new osg::TexEnvCombine);
|
||||
// Sum the previous colour and the emissive colour.
|
||||
@ -1977,33 +2010,42 @@ namespace NifOsg
|
||||
|
||||
void handleTextureProperty(const Nif::NiTexturingProperty* texprop, const std::string& nodeName,
|
||||
osg::StateSet* stateset, SceneUtil::CompositeStateSetUpdater* composite,
|
||||
Resource::ImageManager* imageManager, std::vector<unsigned int>& boundTextures, int animflags)
|
||||
std::vector<unsigned int>& boundTextures, int animflags)
|
||||
{
|
||||
if (!boundTextures.empty())
|
||||
{
|
||||
// overriding a parent NiTexturingProperty, so remove what was previously bound
|
||||
for (unsigned int i = 0; i < boundTextures.size(); ++i)
|
||||
stateset->setTextureMode(i, GL_TEXTURE_2D, osg::StateAttribute::OFF);
|
||||
boundTextures.clear();
|
||||
}
|
||||
// overriding a parent NiTexturingProperty, so remove what was previously bound
|
||||
clearBoundTextures(stateset, boundTextures);
|
||||
|
||||
// If this loop is changed such that the base texture isn't guaranteed to end up in texture unit 0, the
|
||||
// shadow casting shader will need to be updated accordingly.
|
||||
for (size_t i = 0; i < texprop->mTextures.size(); ++i)
|
||||
{
|
||||
if (texprop->mTextures[i].mEnabled
|
||||
|| (i == Nif::NiTexturingProperty::BaseTexture && !texprop->mController.empty()))
|
||||
const Nif::NiTexturingProperty::Texture& tex = texprop->mTextures[i];
|
||||
if (tex.mEnabled || (i == Nif::NiTexturingProperty::BaseTexture && !texprop->mController.empty()))
|
||||
{
|
||||
std::string textureName;
|
||||
switch (i)
|
||||
{
|
||||
// These are handled later on
|
||||
case Nif::NiTexturingProperty::BaseTexture:
|
||||
textureName = "diffuseMap";
|
||||
break;
|
||||
case Nif::NiTexturingProperty::GlowTexture:
|
||||
textureName = "glowMap";
|
||||
break;
|
||||
case Nif::NiTexturingProperty::DarkTexture:
|
||||
textureName = "darkMap";
|
||||
break;
|
||||
case Nif::NiTexturingProperty::BumpTexture:
|
||||
textureName = "bumpMap";
|
||||
break;
|
||||
case Nif::NiTexturingProperty::DetailTexture:
|
||||
textureName = "detailMap";
|
||||
break;
|
||||
case Nif::NiTexturingProperty::DecalTexture:
|
||||
textureName = "decalMap";
|
||||
break;
|
||||
case Nif::NiTexturingProperty::GlossTexture:
|
||||
textureName = "glossMap";
|
||||
break;
|
||||
default:
|
||||
{
|
||||
@ -2013,12 +2055,9 @@ namespace NifOsg
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int uvSet = 0;
|
||||
// create a new texture, will later attempt to share using the SharedStateManager
|
||||
osg::ref_ptr<osg::Texture2D> texture2d;
|
||||
if (texprop->mTextures[i].mEnabled)
|
||||
const unsigned int texUnit = boundTextures.size();
|
||||
if (tex.mEnabled)
|
||||
{
|
||||
const Nif::NiTexturingProperty::Texture& tex = texprop->mTextures[i];
|
||||
if (tex.mSourceTexture.empty() && texprop->mController.empty())
|
||||
{
|
||||
if (i == 0)
|
||||
@ -2028,32 +2067,18 @@ namespace NifOsg
|
||||
}
|
||||
|
||||
if (!tex.mSourceTexture.empty())
|
||||
{
|
||||
const Nif::NiSourceTexture* st = tex.mSourceTexture.getPtr();
|
||||
osg::ref_ptr<osg::Image> image = handleSourceTexture(st, imageManager);
|
||||
texture2d = new osg::Texture2D(image);
|
||||
if (image)
|
||||
texture2d->setTextureSize(image->s(), image->t());
|
||||
}
|
||||
attachNiSourceTexture(textureName, tex.mSourceTexture.getPtr(), tex.wrapS(), tex.wrapT(),
|
||||
tex.mUVSet, stateset, boundTextures);
|
||||
else
|
||||
texture2d = new osg::Texture2D;
|
||||
|
||||
handleTextureWrapping(texture2d, tex.wrapS(), tex.wrapT());
|
||||
|
||||
uvSet = tex.mUVSet;
|
||||
attachTexture(
|
||||
textureName, nullptr, tex.wrapS(), tex.wrapT(), tex.mUVSet, stateset, boundTextures);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Texture only comes from NiFlipController, so tex is ignored, set defaults
|
||||
texture2d = new osg::Texture2D;
|
||||
handleTextureWrapping(texture2d, true, true);
|
||||
uvSet = 0;
|
||||
attachTexture(textureName, nullptr, true, true, 0, stateset, boundTextures);
|
||||
}
|
||||
|
||||
unsigned int texUnit = boundTextures.size();
|
||||
|
||||
stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON);
|
||||
|
||||
if (i == Nif::NiTexturingProperty::GlowTexture)
|
||||
{
|
||||
stateset->setTextureAttributeAndModes(texUnit, createEmissiveTexEnv(), osg::StateAttribute::ON);
|
||||
@ -2121,51 +2146,165 @@ namespace NifOsg
|
||||
texEnv->setOperand0_Alpha(osg::TexEnvCombine::SRC_ALPHA);
|
||||
stateset->setTextureAttributeAndModes(texUnit, texEnv, osg::StateAttribute::ON);
|
||||
}
|
||||
|
||||
switch (i)
|
||||
{
|
||||
case Nif::NiTexturingProperty::BaseTexture:
|
||||
texture2d->setName("diffuseMap");
|
||||
break;
|
||||
case Nif::NiTexturingProperty::BumpTexture:
|
||||
texture2d->setName("bumpMap");
|
||||
break;
|
||||
case Nif::NiTexturingProperty::GlowTexture:
|
||||
texture2d->setName("emissiveMap");
|
||||
break;
|
||||
case Nif::NiTexturingProperty::DarkTexture:
|
||||
texture2d->setName("darkMap");
|
||||
break;
|
||||
case Nif::NiTexturingProperty::DetailTexture:
|
||||
texture2d->setName("detailMap");
|
||||
break;
|
||||
case Nif::NiTexturingProperty::DecalTexture:
|
||||
texture2d->setName("decalMap");
|
||||
break;
|
||||
case Nif::NiTexturingProperty::GlossTexture:
|
||||
texture2d->setName("glossMap");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
boundTextures.push_back(uvSet);
|
||||
}
|
||||
}
|
||||
handleTextureControllers(texprop, composite, imageManager, stateset, animflags);
|
||||
handleTextureControllers(texprop, composite, stateset, animflags);
|
||||
}
|
||||
|
||||
void handleTextureSet(const Nif::BSShaderTextureSet* textureSet, unsigned int clamp,
|
||||
const std::string& nodeName, osg::StateSet* stateset, Resource::ImageManager* imageManager,
|
||||
std::vector<unsigned int>& boundTextures)
|
||||
static Bgsm::MaterialFilePtr getShaderMaterial(
|
||||
std::string_view path, Resource::BgsmFileManager* materialManager)
|
||||
{
|
||||
if (!boundTextures.empty())
|
||||
if (!materialManager)
|
||||
return nullptr;
|
||||
|
||||
if (!Misc::StringUtils::ciEndsWith(path, ".bgem") && !Misc::StringUtils::ciEndsWith(path, ".bgsm"))
|
||||
return nullptr;
|
||||
|
||||
std::string normalizedPath = Misc::ResourceHelpers::correctMaterialPath(path, materialManager->getVFS());
|
||||
try
|
||||
{
|
||||
for (unsigned int i = 0; i < boundTextures.size(); ++i)
|
||||
stateset->setTextureMode(i, GL_TEXTURE_2D, osg::StateAttribute::OFF);
|
||||
boundTextures.clear();
|
||||
return materialManager->get(VFS::Path::Normalized(normalizedPath));
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
Log(Debug::Error) << "Failed to load shader material: " << e.what();
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void handleShaderMaterialNodeProperties(
|
||||
Bgsm::MaterialFilePtr material, osg::StateSet* stateset, std::vector<unsigned int>& boundTextures)
|
||||
{
|
||||
const unsigned int uvSet = 0;
|
||||
const bool wrapS = (material->mClamp >> 1) & 0x1;
|
||||
const bool wrapT = material->mClamp & 0x1;
|
||||
if (material->mShaderType == Bgsm::ShaderType::Lighting)
|
||||
{
|
||||
const Bgsm::BGSMFile* bgsm = static_cast<const Bgsm::BGSMFile*>(material.get());
|
||||
|
||||
if (!bgsm->mDiffuseMap.empty())
|
||||
attachExternalTexture(
|
||||
"diffuseMap", bgsm->mDiffuseMap, wrapS, wrapT, uvSet, stateset, boundTextures);
|
||||
|
||||
if (!bgsm->mNormalMap.empty())
|
||||
attachExternalTexture("normalMap", bgsm->mNormalMap, wrapS, wrapT, uvSet, stateset, boundTextures);
|
||||
|
||||
if (bgsm->mGlowMapEnabled && !bgsm->mGlowMap.empty())
|
||||
attachExternalTexture("emissiveMap", bgsm->mGlowMap, wrapS, wrapT, uvSet, stateset, boundTextures);
|
||||
|
||||
if (bgsm->mTree)
|
||||
stateset->addUniform(new osg::Uniform("useTreeAnim", true));
|
||||
}
|
||||
else if (material->mShaderType == Bgsm::ShaderType::Effect)
|
||||
{
|
||||
const Bgsm::BGEMFile* bgem = static_cast<const Bgsm::BGEMFile*>(material.get());
|
||||
|
||||
if (!bgem->mBaseMap.empty())
|
||||
attachExternalTexture("diffuseMap", bgem->mBaseMap, wrapS, wrapT, uvSet, stateset, boundTextures);
|
||||
|
||||
bool useFalloff = bgem->mFalloff;
|
||||
stateset->addUniform(new osg::Uniform("useFalloff", useFalloff));
|
||||
if (useFalloff)
|
||||
stateset->addUniform(new osg::Uniform("falloffParams", bgem->mFalloffParams));
|
||||
}
|
||||
|
||||
if (material->mTwoSided)
|
||||
stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF);
|
||||
handleDepthFlags(stateset, material->mDepthTest, material->mDepthWrite);
|
||||
}
|
||||
|
||||
void handleDecal(bool enabled, bool hasSortAlpha, osg::Node& node)
|
||||
{
|
||||
if (!enabled)
|
||||
return;
|
||||
osg::ref_ptr<osg::StateSet> stateset = node.getOrCreateStateSet();
|
||||
osg::ref_ptr<osg::PolygonOffset> polygonOffset(new osg::PolygonOffset);
|
||||
polygonOffset->setUnits(SceneUtil::AutoDepth::isReversed() ? 1.f : -1.f);
|
||||
polygonOffset->setFactor(SceneUtil::AutoDepth::isReversed() ? 0.65f : -0.65f);
|
||||
polygonOffset = shareAttribute(polygonOffset);
|
||||
stateset->setAttributeAndModes(polygonOffset, osg::StateAttribute::ON);
|
||||
if (!mPushedSorter && !hasSortAlpha)
|
||||
stateset->setRenderBinDetails(1, "SORT_BACK_TO_FRONT");
|
||||
}
|
||||
|
||||
void handleAlphaTesting(
|
||||
bool enabled, osg::AlphaFunc::ComparisonFunction function, int threshold, osg::Node& node)
|
||||
{
|
||||
if (enabled)
|
||||
{
|
||||
osg::ref_ptr<osg::AlphaFunc> alphaFunc(new osg::AlphaFunc(function, threshold / 255.f));
|
||||
alphaFunc = shareAttribute(alphaFunc);
|
||||
node.getOrCreateStateSet()->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON);
|
||||
}
|
||||
else if (osg::StateSet* stateset = node.getStateSet())
|
||||
{
|
||||
stateset->removeAttribute(osg::StateAttribute::ALPHAFUNC);
|
||||
stateset->removeMode(GL_ALPHA_TEST);
|
||||
}
|
||||
}
|
||||
|
||||
void handleAlphaBlending(
|
||||
bool enabled, int sourceMode, int destMode, bool sort, bool& hasSortAlpha, osg::Node& node)
|
||||
{
|
||||
if (enabled)
|
||||
{
|
||||
osg::ref_ptr<osg::StateSet> stateset = node.getOrCreateStateSet();
|
||||
osg::ref_ptr<osg::BlendFunc> blendFunc(
|
||||
new osg::BlendFunc(getBlendMode(sourceMode), getBlendMode(destMode)));
|
||||
// on AMD hardware, alpha still seems to be stored with an RGBA framebuffer with OpenGL.
|
||||
// This might be mandated by the OpenGL 2.1 specification section 2.14.9, or might be a bug.
|
||||
// Either way, D3D8.1 doesn't do that, so adapt the destination factor.
|
||||
if (blendFunc->getDestination() == GL_DST_ALPHA)
|
||||
blendFunc->setDestination(GL_ONE);
|
||||
blendFunc = shareAttribute(blendFunc);
|
||||
stateset->setAttributeAndModes(blendFunc, osg::StateAttribute::ON);
|
||||
|
||||
if (sort)
|
||||
{
|
||||
hasSortAlpha = true;
|
||||
if (!mPushedSorter)
|
||||
stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
|
||||
}
|
||||
else if (!mPushedSorter)
|
||||
{
|
||||
stateset->setRenderBinToInherit();
|
||||
}
|
||||
}
|
||||
else if (osg::ref_ptr<osg::StateSet> stateset = node.getStateSet())
|
||||
{
|
||||
stateset->removeAttribute(osg::StateAttribute::BLENDFUNC);
|
||||
stateset->removeMode(GL_BLEND);
|
||||
if (!mPushedSorter)
|
||||
stateset->setRenderBinToInherit();
|
||||
}
|
||||
}
|
||||
|
||||
void handleShaderMaterialDrawableProperties(
|
||||
Bgsm::MaterialFilePtr shaderMat, osg::ref_ptr<osg::Material> mat, osg::Node& node, bool& hasSortAlpha)
|
||||
{
|
||||
mat->setAlpha(osg::Material::FRONT_AND_BACK, shaderMat->mTransparency);
|
||||
handleAlphaTesting(shaderMat->mAlphaTest, osg::AlphaFunc::GREATER, shaderMat->mAlphaTestThreshold, node);
|
||||
handleAlphaBlending(shaderMat->mAlphaBlend, shaderMat->mSourceBlendMode, shaderMat->mDestinationBlendMode,
|
||||
true, hasSortAlpha, node);
|
||||
handleDecal(shaderMat->mDecal, hasSortAlpha, node);
|
||||
if (shaderMat->mShaderType == Bgsm::ShaderType::Lighting)
|
||||
{
|
||||
auto bgsm = static_cast<const Bgsm::BGSMFile*>(shaderMat.get());
|
||||
mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(bgsm->mEmittanceColor, 1.f));
|
||||
mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(bgsm->mSpecularColor, 1.f));
|
||||
}
|
||||
else if (shaderMat->mShaderType == Bgsm::ShaderType::Effect)
|
||||
{
|
||||
auto bgem = static_cast<const Bgsm::BGEMFile*>(shaderMat.get());
|
||||
mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(bgem->mEmittanceColor, 1.f));
|
||||
if (bgem->mSoft)
|
||||
SceneUtil::setupSoftEffect(node, bgem->mSoftDepth, true, bgem->mSoftDepth);
|
||||
}
|
||||
}
|
||||
|
||||
void handleTextureSet(const Nif::BSShaderTextureSet* textureSet, bool wrapS, bool wrapT,
|
||||
const std::string& nodeName, osg::StateSet* stateset, std::vector<unsigned int>& boundTextures)
|
||||
{
|
||||
const unsigned int uvSet = 0;
|
||||
|
||||
for (size_t i = 0; i < textureSet->mTextures.size(); ++i)
|
||||
@ -2175,8 +2314,16 @@ namespace NifOsg
|
||||
switch (static_cast<Nif::BSShaderTextureSet::TextureType>(i))
|
||||
{
|
||||
case Nif::BSShaderTextureSet::TextureType::Base:
|
||||
attachExternalTexture(
|
||||
"diffuseMap", textureSet->mTextures[i], wrapS, wrapT, uvSet, stateset, boundTextures);
|
||||
break;
|
||||
case Nif::BSShaderTextureSet::TextureType::Normal:
|
||||
attachExternalTexture(
|
||||
"normalMap", textureSet->mTextures[i], wrapS, wrapT, uvSet, stateset, boundTextures);
|
||||
break;
|
||||
case Nif::BSShaderTextureSet::TextureType::Glow:
|
||||
attachExternalTexture(
|
||||
"emissiveMap", textureSet->mTextures[i], wrapS, wrapT, uvSet, stateset, boundTextures);
|
||||
break;
|
||||
default:
|
||||
{
|
||||
@ -2185,31 +2332,6 @@ namespace NifOsg
|
||||
continue;
|
||||
}
|
||||
}
|
||||
std::string filename
|
||||
= Misc::ResourceHelpers::correctTexturePath(textureSet->mTextures[i], imageManager->getVFS());
|
||||
osg::ref_ptr<osg::Image> image = imageManager->getImage(filename);
|
||||
osg::ref_ptr<osg::Texture2D> texture2d = new osg::Texture2D(image);
|
||||
if (image)
|
||||
texture2d->setTextureSize(image->s(), image->t());
|
||||
handleTextureWrapping(texture2d, (clamp >> 1) & 0x1, clamp & 0x1);
|
||||
unsigned int texUnit = boundTextures.size();
|
||||
stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON);
|
||||
// BSShaderTextureSet presence means there's no need for FFP support for the affected node
|
||||
switch (static_cast<Nif::BSShaderTextureSet::TextureType>(i))
|
||||
{
|
||||
case Nif::BSShaderTextureSet::TextureType::Base:
|
||||
texture2d->setName("diffuseMap");
|
||||
break;
|
||||
case Nif::BSShaderTextureSet::TextureType::Normal:
|
||||
texture2d->setName("normalMap");
|
||||
break;
|
||||
case Nif::BSShaderTextureSet::TextureType::Glow:
|
||||
texture2d->setName("emissiveMap");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
boundTextures.emplace_back(uvSet);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2269,8 +2391,8 @@ namespace NifOsg
|
||||
}
|
||||
|
||||
void handleProperty(const Nif::NiProperty* property, osg::Node* node,
|
||||
SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager,
|
||||
std::vector<unsigned int>& boundTextures, int animflags, bool hasStencilProperty)
|
||||
SceneUtil::CompositeStateSetUpdater* composite, std::vector<unsigned int>& boundTextures, int animflags,
|
||||
bool hasStencilProperty)
|
||||
{
|
||||
switch (property->recType)
|
||||
{
|
||||
@ -2352,8 +2474,7 @@ namespace NifOsg
|
||||
{
|
||||
const Nif::NiTexturingProperty* texprop = static_cast<const Nif::NiTexturingProperty*>(property);
|
||||
osg::StateSet* stateset = node->getOrCreateStateSet();
|
||||
handleTextureProperty(
|
||||
texprop, node->getName(), stateset, composite, imageManager, boundTextures, animflags);
|
||||
handleTextureProperty(texprop, node->getName(), stateset, composite, boundTextures, animflags);
|
||||
node->setUserValue("applyMode", static_cast<int>(texprop->mApplyMode));
|
||||
break;
|
||||
}
|
||||
@ -2364,13 +2485,13 @@ namespace NifOsg
|
||||
node->setUserValue("shaderPrefix", std::string(getBSShaderPrefix(texprop->mType)));
|
||||
node->setUserValue("shaderRequired", shaderRequired);
|
||||
osg::StateSet* stateset = node->getOrCreateStateSet();
|
||||
clearBoundTextures(stateset, boundTextures);
|
||||
const bool wrapS = (texprop->mClamp >> 1) & 0x1;
|
||||
const bool wrapT = texprop->mClamp & 0x1;
|
||||
if (!texprop->mTextureSet.empty())
|
||||
{
|
||||
auto textureSet = texprop->mTextureSet.getPtr();
|
||||
handleTextureSet(
|
||||
textureSet, texprop->mClamp, node->getName(), stateset, imageManager, boundTextures);
|
||||
}
|
||||
handleTextureControllers(texprop, composite, imageManager, stateset, animflags);
|
||||
texprop->mTextureSet.getPtr(), wrapS, wrapT, node->getName(), stateset, boundTextures);
|
||||
handleTextureControllers(texprop, composite, stateset, animflags);
|
||||
if (texprop->refraction())
|
||||
SceneUtil::setupDistortion(*node, texprop->mRefraction.mStrength);
|
||||
break;
|
||||
@ -2383,34 +2504,20 @@ namespace NifOsg
|
||||
node->setUserValue("shaderPrefix", std::string(getBSShaderPrefix(texprop->mType)));
|
||||
node->setUserValue("shaderRequired", shaderRequired);
|
||||
osg::StateSet* stateset = node->getOrCreateStateSet();
|
||||
clearBoundTextures(stateset, boundTextures);
|
||||
if (!texprop->mFilename.empty())
|
||||
{
|
||||
if (!boundTextures.empty())
|
||||
{
|
||||
for (unsigned int i = 0; i < boundTextures.size(); ++i)
|
||||
stateset->setTextureMode(i, GL_TEXTURE_2D, osg::StateAttribute::OFF);
|
||||
boundTextures.clear();
|
||||
}
|
||||
std::string filename
|
||||
= Misc::ResourceHelpers::correctTexturePath(texprop->mFilename, imageManager->getVFS());
|
||||
osg::ref_ptr<osg::Image> image = imageManager->getImage(filename);
|
||||
osg::ref_ptr<osg::Texture2D> texture2d = new osg::Texture2D(image);
|
||||
texture2d->setName("diffuseMap");
|
||||
if (image)
|
||||
texture2d->setTextureSize(image->s(), image->t());
|
||||
handleTextureWrapping(texture2d, texprop->wrapS(), texprop->wrapT());
|
||||
const unsigned int texUnit = 0;
|
||||
const unsigned int uvSet = 0;
|
||||
stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON);
|
||||
boundTextures.push_back(uvSet);
|
||||
if (mBethVersion >= 27)
|
||||
{
|
||||
useFalloff = true;
|
||||
stateset->addUniform(new osg::Uniform("falloffParams", texprop->mFalloffParams));
|
||||
}
|
||||
attachExternalTexture("diffuseMap", texprop->mFilename, texprop->wrapS(), texprop->wrapT(),
|
||||
uvSet, stateset, boundTextures);
|
||||
}
|
||||
if (mBethVersion >= 27)
|
||||
{
|
||||
useFalloff = true;
|
||||
stateset->addUniform(new osg::Uniform("falloffParams", texprop->mFalloffParams));
|
||||
}
|
||||
stateset->addUniform(new osg::Uniform("useFalloff", useFalloff));
|
||||
handleTextureControllers(texprop, composite, imageManager, stateset, animflags);
|
||||
handleTextureControllers(texprop, composite, stateset, animflags);
|
||||
handleDepthFlags(stateset, texprop->depthTest(), texprop->depthWrite());
|
||||
break;
|
||||
}
|
||||
@ -2421,10 +2528,18 @@ namespace NifOsg
|
||||
node->setUserValue("shaderPrefix", std::string(getBSLightingShaderPrefix(texprop->mType)));
|
||||
node->setUserValue("shaderRequired", shaderRequired);
|
||||
osg::StateSet* stateset = node->getOrCreateStateSet();
|
||||
clearBoundTextures(stateset, boundTextures);
|
||||
if (Bgsm::MaterialFilePtr material = getShaderMaterial(texprop->mName, mMaterialManager))
|
||||
{
|
||||
handleShaderMaterialNodeProperties(material, stateset, boundTextures);
|
||||
break;
|
||||
}
|
||||
const bool wrapS = (texprop->mClamp >> 1) & 0x1;
|
||||
const bool wrapT = texprop->mClamp & 0x1;
|
||||
if (!texprop->mTextureSet.empty())
|
||||
handleTextureSet(texprop->mTextureSet.getPtr(), texprop->mClamp, node->getName(), stateset,
|
||||
imageManager, boundTextures);
|
||||
handleTextureControllers(texprop, composite, imageManager, stateset, animflags);
|
||||
handleTextureSet(
|
||||
texprop->mTextureSet.getPtr(), wrapS, wrapT, node->getName(), stateset, boundTextures);
|
||||
handleTextureControllers(texprop, composite, stateset, animflags);
|
||||
if (texprop->doubleSided())
|
||||
stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF);
|
||||
if (texprop->treeAnim())
|
||||
@ -2442,27 +2557,20 @@ namespace NifOsg
|
||||
node->setUserValue("shaderPrefix", std::string("bs/nolighting"));
|
||||
node->setUserValue("shaderRequired", shaderRequired);
|
||||
osg::StateSet* stateset = node->getOrCreateStateSet();
|
||||
clearBoundTextures(stateset, boundTextures);
|
||||
if (Bgsm::MaterialFilePtr material = getShaderMaterial(texprop->mName, mMaterialManager))
|
||||
{
|
||||
handleShaderMaterialNodeProperties(material, stateset, boundTextures);
|
||||
break;
|
||||
}
|
||||
if (!texprop->mSourceTexture.empty())
|
||||
{
|
||||
if (!boundTextures.empty())
|
||||
{
|
||||
for (unsigned int i = 0; i < boundTextures.size(); ++i)
|
||||
stateset->setTextureMode(i, GL_TEXTURE_2D, osg::StateAttribute::OFF);
|
||||
boundTextures.clear();
|
||||
}
|
||||
std::string filename = Misc::ResourceHelpers::correctTexturePath(
|
||||
texprop->mSourceTexture, imageManager->getVFS());
|
||||
osg::ref_ptr<osg::Image> image = imageManager->getImage(filename);
|
||||
osg::ref_ptr<osg::Texture2D> texture2d = new osg::Texture2D(image);
|
||||
texture2d->setName("diffuseMap");
|
||||
if (image)
|
||||
texture2d->setTextureSize(image->s(), image->t());
|
||||
handleTextureWrapping(texture2d, (texprop->mClamp >> 1) & 0x1, texprop->mClamp & 0x1);
|
||||
const unsigned int texUnit = 0;
|
||||
const unsigned int uvSet = 0;
|
||||
stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON);
|
||||
boundTextures.push_back(uvSet);
|
||||
|
||||
const bool wrapS = (texprop->mClamp >> 1) & 0x1;
|
||||
const bool wrapT = texprop->mClamp & 0x1;
|
||||
unsigned int texUnit = boundTextures.size();
|
||||
attachExternalTexture(
|
||||
"diffuseMap", texprop->mSourceTexture, wrapS, wrapT, uvSet, stateset, boundTextures);
|
||||
{
|
||||
osg::ref_ptr<osg::TexMat> texMat(new osg::TexMat);
|
||||
// This handles 20.2.0.7 UV settings like 4.0.0.2 UV settings (see NifOsg::UVController)
|
||||
@ -2484,7 +2592,7 @@ namespace NifOsg
|
||||
stateset->addUniform(new osg::Uniform("useFalloff", useFalloff));
|
||||
if (useFalloff)
|
||||
stateset->addUniform(new osg::Uniform("falloffParams", texprop->mFalloffParams));
|
||||
handleTextureControllers(texprop, composite, imageManager, stateset, animflags);
|
||||
handleTextureControllers(texprop, composite, stateset, animflags);
|
||||
if (texprop->doubleSided())
|
||||
stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF);
|
||||
handleDepthFlags(stateset, texprop->depthTest(), texprop->depthWrite());
|
||||
@ -2566,12 +2674,9 @@ namespace NifOsg
|
||||
|
||||
bool hasMatCtrl = false;
|
||||
bool hasSortAlpha = false;
|
||||
osg::StateSet* blendFuncStateSet = nullptr;
|
||||
|
||||
auto setBin_Transparent = [](osg::StateSet* ss) { ss->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); };
|
||||
auto setBin_BackToFront = [](osg::StateSet* ss) { ss->setRenderBinDetails(0, "SORT_BACK_TO_FRONT"); };
|
||||
auto setBin_Traversal = [](osg::StateSet* ss) { ss->setRenderBinDetails(2, "TraversalOrderBin"); };
|
||||
auto setBin_Inherit = [](osg::StateSet* ss) { ss->setRenderBinToInherit(); };
|
||||
|
||||
auto lightmode = Nif::NiVertexColorProperty::LightMode::LightMode_EmiAmbDif;
|
||||
float emissiveMult = 1.f;
|
||||
@ -2657,52 +2762,10 @@ namespace NifOsg
|
||||
case Nif::RC_NiAlphaProperty:
|
||||
{
|
||||
const Nif::NiAlphaProperty* alphaprop = static_cast<const Nif::NiAlphaProperty*>(property);
|
||||
if (alphaprop->useAlphaBlending())
|
||||
{
|
||||
osg::ref_ptr<osg::BlendFunc> blendFunc(
|
||||
new osg::BlendFunc(getBlendMode(alphaprop->sourceBlendMode()),
|
||||
getBlendMode(alphaprop->destinationBlendMode())));
|
||||
// on AMD hardware, alpha still seems to be stored with an RGBA framebuffer with OpenGL.
|
||||
// This might be mandated by the OpenGL 2.1 specification section 2.14.9, or might be a bug.
|
||||
// Either way, D3D8.1 doesn't do that, so adapt the destination factor.
|
||||
if (blendFunc->getDestination() == GL_DST_ALPHA)
|
||||
blendFunc->setDestination(GL_ONE);
|
||||
blendFunc = shareAttribute(blendFunc);
|
||||
node->getOrCreateStateSet()->setAttributeAndModes(blendFunc, osg::StateAttribute::ON);
|
||||
|
||||
if (!alphaprop->noSorter())
|
||||
{
|
||||
hasSortAlpha = true;
|
||||
if (!mPushedSorter)
|
||||
setBin_Transparent(node->getStateSet());
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!mPushedSorter)
|
||||
setBin_Inherit(node->getStateSet());
|
||||
}
|
||||
}
|
||||
else if (osg::StateSet* stateset = node->getStateSet())
|
||||
{
|
||||
stateset->removeAttribute(osg::StateAttribute::BLENDFUNC);
|
||||
stateset->removeMode(GL_BLEND);
|
||||
blendFuncStateSet = stateset;
|
||||
if (!mPushedSorter)
|
||||
blendFuncStateSet->setRenderBinToInherit();
|
||||
}
|
||||
|
||||
if (alphaprop->useAlphaTesting())
|
||||
{
|
||||
osg::ref_ptr<osg::AlphaFunc> alphaFunc(new osg::AlphaFunc(
|
||||
getTestMode(alphaprop->alphaTestMode()), alphaprop->mThreshold / 255.f));
|
||||
alphaFunc = shareAttribute(alphaFunc);
|
||||
node->getOrCreateStateSet()->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON);
|
||||
}
|
||||
else if (osg::StateSet* stateset = node->getStateSet())
|
||||
{
|
||||
stateset->removeAttribute(osg::StateAttribute::ALPHAFUNC);
|
||||
stateset->removeMode(GL_ALPHA_TEST);
|
||||
}
|
||||
handleAlphaBlending(alphaprop->useAlphaBlending(), alphaprop->sourceBlendMode(),
|
||||
alphaprop->destinationBlendMode(), !alphaprop->noSorter(), hasSortAlpha, *node);
|
||||
handleAlphaTesting(alphaprop->useAlphaTesting(), getTestMode(alphaprop->alphaTestMode()),
|
||||
alphaprop->mThreshold, *node);
|
||||
break;
|
||||
}
|
||||
case Nif::RC_BSShaderPPLightingProperty:
|
||||
@ -2714,6 +2777,18 @@ namespace NifOsg
|
||||
case Nif::RC_BSLightingShaderProperty:
|
||||
{
|
||||
auto shaderprop = static_cast<const Nif::BSLightingShaderProperty*>(property);
|
||||
if (Bgsm::MaterialFilePtr shaderMat = getShaderMaterial(shaderprop->mName, mMaterialManager))
|
||||
{
|
||||
handleShaderMaterialDrawableProperties(shaderMat, mat, *node, hasSortAlpha);
|
||||
if (shaderMat->mShaderType == Bgsm::ShaderType::Lighting)
|
||||
{
|
||||
auto bgsm = static_cast<const Bgsm::BGSMFile*>(shaderMat.get());
|
||||
specEnabled = false; // bgsm->mSpecularEnabled; TODO: PBR specular lighting
|
||||
specStrength = 1.f; // bgsm->mSpecularMult;
|
||||
emissiveMult = bgsm->mEmittanceMult;
|
||||
}
|
||||
break;
|
||||
}
|
||||
mat->setAlpha(osg::Material::FRONT_AND_BACK, shaderprop->mAlpha);
|
||||
mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(shaderprop->mEmissive, 1.f));
|
||||
mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(shaderprop->mSpecular, 1.f));
|
||||
@ -2722,31 +2797,18 @@ namespace NifOsg
|
||||
emissiveMult = shaderprop->mEmissiveMult;
|
||||
specStrength = shaderprop->mSpecStrength;
|
||||
specEnabled = shaderprop->specular();
|
||||
if (shaderprop->decal())
|
||||
{
|
||||
osg::StateSet* stateset = node->getOrCreateStateSet();
|
||||
if (!mPushedSorter && !hasSortAlpha)
|
||||
stateset->setRenderBinDetails(1, "SORT_BACK_TO_FRONT");
|
||||
osg::ref_ptr<osg::PolygonOffset> polygonOffset(new osg::PolygonOffset);
|
||||
polygonOffset->setUnits(SceneUtil::AutoDepth::isReversed() ? 1.f : -1.f);
|
||||
polygonOffset->setFactor(SceneUtil::AutoDepth::isReversed() ? 0.65f : -0.65f);
|
||||
stateset->setAttributeAndModes(polygonOffset, osg::StateAttribute::ON);
|
||||
}
|
||||
handleDecal(shaderprop->decal(), hasSortAlpha, *node);
|
||||
break;
|
||||
}
|
||||
case Nif::RC_BSEffectShaderProperty:
|
||||
{
|
||||
auto shaderprop = static_cast<const Nif::BSEffectShaderProperty*>(property);
|
||||
if (shaderprop->decal())
|
||||
if (Bgsm::MaterialFilePtr shaderMat = getShaderMaterial(shaderprop->mName, mMaterialManager))
|
||||
{
|
||||
osg::StateSet* stateset = node->getOrCreateStateSet();
|
||||
if (!mPushedSorter && !hasSortAlpha)
|
||||
stateset->setRenderBinDetails(1, "SORT_BACK_TO_FRONT");
|
||||
osg::ref_ptr<osg::PolygonOffset> polygonOffset(new osg::PolygonOffset);
|
||||
polygonOffset->setUnits(SceneUtil::AutoDepth::isReversed() ? 1.f : -1.f);
|
||||
polygonOffset->setFactor(SceneUtil::AutoDepth::isReversed() ? 0.65f : -0.65f);
|
||||
stateset->setAttributeAndModes(polygonOffset, osg::StateAttribute::ON);
|
||||
handleShaderMaterialDrawableProperties(shaderMat, mat, *node, hasSortAlpha);
|
||||
break;
|
||||
}
|
||||
handleDecal(shaderprop->decal(), hasSortAlpha, *node);
|
||||
if (shaderprop->softEffect())
|
||||
SceneUtil::setupSoftEffect(
|
||||
*node, shaderprop->mFalloffDepth, true, shaderprop->mFalloffDepth);
|
||||
@ -2860,10 +2922,13 @@ namespace NifOsg
|
||||
}
|
||||
};
|
||||
|
||||
osg::ref_ptr<osg::Node> Loader::load(Nif::FileView file, Resource::ImageManager* imageManager)
|
||||
osg::ref_ptr<osg::Node> Loader::load(
|
||||
Nif::FileView file, Resource::ImageManager* imageManager, Resource::BgsmFileManager* materialManager)
|
||||
{
|
||||
LoaderImpl impl(file.getFilename(), file.getVersion(), file.getUserVersion(), file.getBethVersion());
|
||||
return impl.load(file, imageManager);
|
||||
impl.mMaterialManager = materialManager;
|
||||
impl.mImageManager = imageManager;
|
||||
return impl.load(file);
|
||||
}
|
||||
|
||||
void Loader::loadKf(Nif::FileView kf, SceneUtil::KeyframeHolder& target)
|
||||
|
@ -18,6 +18,7 @@ namespace osg
|
||||
namespace Resource
|
||||
{
|
||||
class ImageManager;
|
||||
class BgsmFileManager;
|
||||
}
|
||||
|
||||
namespace NifOsg
|
||||
@ -30,7 +31,8 @@ namespace NifOsg
|
||||
public:
|
||||
/// Create a scene graph for the given NIF. Auto-detects when skinning is used and wraps the graph in a Skeleton
|
||||
/// if so.
|
||||
static osg::ref_ptr<osg::Node> load(Nif::FileView file, Resource::ImageManager* imageManager);
|
||||
static osg::ref_ptr<osg::Node> load(
|
||||
Nif::FileView file, Resource::ImageManager* imageManager, Resource::BgsmFileManager* materialManager);
|
||||
|
||||
/// Load keyframe controllers from the given kf file.
|
||||
static void loadKf(Nif::FileView kf, SceneUtil::KeyframeHolder& target);
|
||||
|
55
components/resource/bgsmfilemanager.cpp
Normal file
55
components/resource/bgsmfilemanager.cpp
Normal file
@ -0,0 +1,55 @@
|
||||
#include "bgsmfilemanager.hpp"
|
||||
|
||||
#include <osg/Object>
|
||||
|
||||
#include <components/vfs/manager.hpp>
|
||||
|
||||
#include "objectcache.hpp"
|
||||
|
||||
namespace Resource
|
||||
{
|
||||
|
||||
class BgsmFileHolder : public osg::Object
|
||||
{
|
||||
public:
|
||||
BgsmFileHolder(const Bgsm::MaterialFilePtr& file)
|
||||
: mBgsmFile(file)
|
||||
{
|
||||
}
|
||||
BgsmFileHolder(const BgsmFileHolder& copy, const osg::CopyOp& copyop)
|
||||
: mBgsmFile(copy.mBgsmFile)
|
||||
{
|
||||
}
|
||||
|
||||
BgsmFileHolder() = default;
|
||||
|
||||
META_Object(Resource, BgsmFileHolder)
|
||||
|
||||
Bgsm::MaterialFilePtr mBgsmFile;
|
||||
};
|
||||
|
||||
BgsmFileManager::BgsmFileManager(const VFS::Manager* vfs, double expiryDelay)
|
||||
: ResourceManager(vfs, expiryDelay)
|
||||
{
|
||||
}
|
||||
|
||||
Bgsm::MaterialFilePtr BgsmFileManager::get(VFS::Path::NormalizedView name)
|
||||
{
|
||||
osg::ref_ptr<osg::Object> obj = mCache->getRefFromObjectCache(name);
|
||||
if (obj)
|
||||
return static_cast<BgsmFileHolder*>(obj.get())->mBgsmFile;
|
||||
else
|
||||
{
|
||||
Bgsm::MaterialFilePtr file = Bgsm::parse(mVFS->get(name));
|
||||
obj = new BgsmFileHolder(file);
|
||||
mCache->addEntryToObjectCache(name.value(), obj);
|
||||
return file;
|
||||
}
|
||||
}
|
||||
|
||||
void BgsmFileManager::reportStats(unsigned int frameNumber, osg::Stats* stats) const
|
||||
{
|
||||
Resource::reportStats("BSShader Material", frameNumber, mCache->getStats(), *stats);
|
||||
}
|
||||
|
||||
}
|
27
components/resource/bgsmfilemanager.hpp
Normal file
27
components/resource/bgsmfilemanager.hpp
Normal file
@ -0,0 +1,27 @@
|
||||
#ifndef OPENMW_COMPONENTS_RESOURCE_BGSMFILEMANAGER_H
|
||||
#define OPENMW_COMPONENTS_RESOURCE_BGSMFILEMANAGER_H
|
||||
|
||||
#include <components/bgsm/file.hpp>
|
||||
|
||||
#include "resourcemanager.hpp"
|
||||
|
||||
namespace Resource
|
||||
{
|
||||
|
||||
/// @brief Handles caching of material files.
|
||||
/// @note May be used from any thread.
|
||||
class BgsmFileManager : public ResourceManager
|
||||
{
|
||||
public:
|
||||
BgsmFileManager(const VFS::Manager* vfs, double expiryDelay);
|
||||
~BgsmFileManager() = default;
|
||||
|
||||
/// Retrieve a material file from the cache or load it from the VFS if not cached yet.
|
||||
Bgsm::MaterialFilePtr get(VFS::Path::NormalizedView name);
|
||||
|
||||
void reportStats(unsigned int frameNumber, osg::Stats* stats) const override;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -37,11 +37,11 @@ namespace Resource
|
||||
|
||||
bool RetrieveAnimationsVisitor::belongsToLeftUpperExtremity(const std::string& name)
|
||||
{
|
||||
static const std::array boneNames = { "bip01_l_clavicle", "left_clavicle", "bip01_l_upperarm", "left_upper_arm",
|
||||
"bip01_l_forearm", "bip01_l_hand", "left_hand", "left_wrist", "shield_bone", "bip01_l_pinky1",
|
||||
"bip01_l_pinky2", "bip01_l_pinky3", "bip01_l_ring1", "bip01_l_ring2", "bip01_l_ring3", "bip01_l_middle1",
|
||||
"bip01_l_middle2", "bip01_l_middle3", "bip01_l_pointer1", "bip01_l_pointer2", "bip01_l_pointer3",
|
||||
"bip01_l_thumb1", "bip01_l_thumb2", "bip01_l_thumb3", "left_forearm" };
|
||||
static const std::array boneNames = { "bip01 l clavicle", "left clavicle", "bip01 l upperarm", "left upper arm",
|
||||
"bip01 l forearm", "bip01 l hand", "left hand", "left wrist", "shield bone", "bip01 l pinky1",
|
||||
"bip01 l pinky2", "bip01 l pinky3", "bip01 l ring1", "bip01 l ring2", "bip01 l ring3", "bip01 l middle1",
|
||||
"bip01 l middle2", "bip01 l middle3", "bip01 l pointer1", "bip01 l pointer2", "bip01 l pointer3",
|
||||
"bip01 l thumb1", "bip01 l thumb2", "bip01 l thumb3", "left forearm" };
|
||||
|
||||
if (std::find(boneNames.begin(), boneNames.end(), name) != boneNames.end())
|
||||
return true;
|
||||
@ -51,11 +51,11 @@ namespace Resource
|
||||
|
||||
bool RetrieveAnimationsVisitor::belongsToRightUpperExtremity(const std::string& name)
|
||||
{
|
||||
static const std::array boneNames = { "bip01_r_clavicle", "right_clavicle", "bip01_r_upperarm",
|
||||
"right_upper_arm", "bip01_r_forearm", "bip01_r_hand", "right_hand", "right_wrist", "bip01_r_thumb1",
|
||||
"bip01_r_thumb2", "bip01_r_thumb3", "weapon_bone", "bip01_r_pinky1", "bip01_r_pinky2", "bip01_r_pinky3",
|
||||
"bip01_r_ring1", "bip01_r_ring2", "bip01_r_ring3", "bip01_r_middle1", "bip01_r_middle2", "bip01_r_middle3",
|
||||
"bip01_r_pointer1", "bip01_r_pointer2", "bip01_r_pointer3", "right_forearm" };
|
||||
static const std::array boneNames = { "bip01 r clavicle", "right clavicle", "bip01 r upperarm",
|
||||
"right upper arm", "bip01 r forearm", "bip01 r hand", "right hand", "right wrist", "bip01 r thumb1",
|
||||
"bip01 r thumb2", "bip01 r thumb3", "weapon bone", "bip01 r pinky1", "bip01 r pinky2", "bip01 r pinky3",
|
||||
"bip01 r ring1", "bip01 r ring2", "bip01 r ring3", "bip01 r middle1", "bip01 r middle2", "bip01 r middle3",
|
||||
"bip01 r pointer1", "bip01 r pointer2", "bip01 r pointer3", "right forearm" };
|
||||
|
||||
if (std::find(boneNames.begin(), boneNames.end(), name) != boneNames.end())
|
||||
return true;
|
||||
@ -66,7 +66,7 @@ namespace Resource
|
||||
bool RetrieveAnimationsVisitor::belongsToTorso(const std::string& name)
|
||||
{
|
||||
static const std::array boneNames
|
||||
= { "bip01_spine1", "bip01_spine2", "bip01_neck", "bip01_head", "head", "neck", "chest", "groin" };
|
||||
= { "bip01 spine1", "bip01 spine2", "bip01 neck", "bip01 head", "head", "neck", "chest", "groin" };
|
||||
|
||||
if (std::find(boneNames.begin(), boneNames.end(), name) != boneNames.end())
|
||||
return true;
|
||||
@ -99,6 +99,9 @@ namespace Resource
|
||||
const osgAnimation::ChannelList& channels = animation->getChannels();
|
||||
for (const auto& channel : channels)
|
||||
{
|
||||
// Replace channel target name to match the renamed bones/transforms
|
||||
channel->setTargetName(Misc::StringUtils::underscoresToSpaces(channel->getTargetName()));
|
||||
|
||||
if (name == "Bip01 R Clavicle")
|
||||
{
|
||||
if (!belongsToRightUpperExtremity(channel->getTargetName()))
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "bgsmfilemanager.hpp"
|
||||
#include "imagemanager.hpp"
|
||||
#include "keyframemanager.hpp"
|
||||
#include "niffilemanager.hpp"
|
||||
@ -15,11 +16,14 @@ namespace Resource
|
||||
: mVFS(vfs)
|
||||
{
|
||||
mNifFileManager = std::make_unique<NifFileManager>(vfs, encoder);
|
||||
mBgsmFileManager = std::make_unique<BgsmFileManager>(vfs, expiryDelay);
|
||||
mImageManager = std::make_unique<ImageManager>(vfs, expiryDelay);
|
||||
mSceneManager = std::make_unique<SceneManager>(vfs, mImageManager.get(), mNifFileManager.get(), expiryDelay);
|
||||
mSceneManager = std::make_unique<SceneManager>(
|
||||
vfs, mImageManager.get(), mNifFileManager.get(), mBgsmFileManager.get(), expiryDelay);
|
||||
mKeyframeManager = std::make_unique<KeyframeManager>(vfs, mSceneManager.get(), expiryDelay, encoder);
|
||||
|
||||
addResourceManager(mNifFileManager.get());
|
||||
addResourceManager(mBgsmFileManager.get());
|
||||
addResourceManager(mKeyframeManager.get());
|
||||
// note, scene references images so add images afterwards for correct implementation of updateCache()
|
||||
addResourceManager(mSceneManager.get());
|
||||
@ -43,6 +47,11 @@ namespace Resource
|
||||
return mImageManager.get();
|
||||
}
|
||||
|
||||
BgsmFileManager* ResourceSystem::getBgsmFileManager()
|
||||
{
|
||||
return mBgsmFileManager.get();
|
||||
}
|
||||
|
||||
NifFileManager* ResourceSystem::getNifFileManager()
|
||||
{
|
||||
return mNifFileManager.get();
|
||||
|
@ -25,6 +25,7 @@ namespace Resource
|
||||
|
||||
class SceneManager;
|
||||
class ImageManager;
|
||||
class BgsmFileManager;
|
||||
class NifFileManager;
|
||||
class KeyframeManager;
|
||||
class BaseResourceManager;
|
||||
@ -41,6 +42,7 @@ namespace Resource
|
||||
|
||||
SceneManager* getSceneManager();
|
||||
ImageManager* getImageManager();
|
||||
BgsmFileManager* getBgsmFileManager();
|
||||
NifFileManager* getNifFileManager();
|
||||
KeyframeManager* getKeyframeManager();
|
||||
|
||||
@ -74,6 +76,7 @@ namespace Resource
|
||||
private:
|
||||
std::unique_ptr<SceneManager> mSceneManager;
|
||||
std::unique_ptr<ImageManager> mImageManager;
|
||||
std::unique_ptr<BgsmFileManager> mBgsmFileManager;
|
||||
std::unique_ptr<NifFileManager> mNifFileManager;
|
||||
std::unique_ptr<KeyframeManager> mKeyframeManager;
|
||||
|
||||
|
@ -9,7 +9,10 @@
|
||||
#include <osg/Node>
|
||||
#include <osg/UserDataContainer>
|
||||
|
||||
#include <osgAnimation/Bone>
|
||||
#include <osgAnimation/RigGeometry>
|
||||
#include <osgAnimation/Skeleton>
|
||||
#include <osgAnimation/UpdateBone>
|
||||
|
||||
#include <osgParticle/ParticleSystem>
|
||||
|
||||
@ -52,6 +55,7 @@
|
||||
#include <components/files/hash.hpp>
|
||||
#include <components/files/memorystream.hpp>
|
||||
|
||||
#include "bgsmfilemanager.hpp"
|
||||
#include "errormarker.hpp"
|
||||
#include "imagemanager.hpp"
|
||||
#include "niffilemanager.hpp"
|
||||
@ -268,6 +272,11 @@ namespace Resource
|
||||
|
||||
void apply(osg::Node& node) override
|
||||
{
|
||||
// If an osgAnimation bone/transform, ensure underscores in name are replaced with spaces
|
||||
// this is for compatibility reasons
|
||||
if (dynamic_cast<osgAnimation::Bone*>(&node))
|
||||
node.setName(Misc::StringUtils::underscoresToSpaces(node.getName()));
|
||||
|
||||
if (osg::StateSet* stateset = node.getStateSet())
|
||||
{
|
||||
if (stateset->getRenderingHint() == osg::StateSet::TRANSPARENT_BIN)
|
||||
@ -353,8 +362,55 @@ namespace Resource
|
||||
std::vector<osg::ref_ptr<SceneUtil::RigGeometryHolder>> mRigGeometryHolders;
|
||||
};
|
||||
|
||||
void updateVertexInfluenceMap(osgAnimation::RigGeometry& rig)
|
||||
{
|
||||
osgAnimation::VertexInfluenceMap* vertexInfluenceMap = rig.getInfluenceMap();
|
||||
if (!vertexInfluenceMap)
|
||||
return;
|
||||
|
||||
std::vector<std::string> renameList;
|
||||
for (const auto& [boneName, unused] : *vertexInfluenceMap)
|
||||
{
|
||||
if (boneName.find('_') != std::string::npos)
|
||||
renameList.push_back(boneName);
|
||||
}
|
||||
|
||||
for (const std::string& oldName : renameList)
|
||||
{
|
||||
const std::string newName = Misc::StringUtils::underscoresToSpaces(oldName);
|
||||
if (vertexInfluenceMap->find(newName) == vertexInfluenceMap->end())
|
||||
(*vertexInfluenceMap)[newName] = std::move((*vertexInfluenceMap)[oldName]);
|
||||
vertexInfluenceMap->erase(oldName);
|
||||
}
|
||||
}
|
||||
|
||||
class RenameAnimCallbacksVisitor : public osg::NodeVisitor
|
||||
{
|
||||
public:
|
||||
RenameAnimCallbacksVisitor()
|
||||
: osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN)
|
||||
{
|
||||
}
|
||||
|
||||
void apply(osg::MatrixTransform& node) override
|
||||
{
|
||||
// osgAnimation update callback name must match bone name/channel targets
|
||||
osg::Callback* cb = node.getUpdateCallback();
|
||||
while (cb)
|
||||
{
|
||||
auto animCb = dynamic_cast<osgAnimation::AnimationUpdateCallback<osg::NodeCallback>*>(cb);
|
||||
if (animCb)
|
||||
animCb->setName(Misc::StringUtils::underscoresToSpaces(animCb->getName()));
|
||||
|
||||
cb = cb->getNestedCallback();
|
||||
}
|
||||
|
||||
traverse(node);
|
||||
}
|
||||
};
|
||||
|
||||
SceneManager::SceneManager(const VFS::Manager* vfs, Resource::ImageManager* imageManager,
|
||||
Resource::NifFileManager* nifFileManager, double expiryDelay)
|
||||
Resource::NifFileManager* nifFileManager, Resource::BgsmFileManager* bgsmFileManager, double expiryDelay)
|
||||
: ResourceManager(vfs, expiryDelay)
|
||||
, mShaderManager(new Shader::ShaderManager)
|
||||
, mForceShaders(false)
|
||||
@ -369,6 +425,7 @@ namespace Resource
|
||||
, mSharedStateManager(new SharedStateManager)
|
||||
, mImageManager(imageManager)
|
||||
, mNifFileManager(nifFileManager)
|
||||
, mBgsmFileManager(bgsmFileManager)
|
||||
, mMinFilter(osg::Texture::LINEAR_MIPMAP_LINEAR)
|
||||
, mMagFilter(osg::Texture::LINEAR)
|
||||
, mMaxAnisotropy(1)
|
||||
@ -556,6 +613,7 @@ namespace Resource
|
||||
VFS::Path::NormalizedView normalizedFilename, std::istream& model, Resource::ImageManager* imageManager)
|
||||
{
|
||||
const std::string_view ext = Misc::getFileExtension(normalizedFilename.value());
|
||||
const bool isColladaFile = ext == "dae";
|
||||
osgDB::ReaderWriter* reader = osgDB::Registry::instance()->getReaderWriterForExtension(std::string(ext));
|
||||
if (!reader)
|
||||
{
|
||||
@ -571,7 +629,7 @@ namespace Resource
|
||||
// findFileCallback would be necessary. but findFileCallback does not support virtual files, so we can't
|
||||
// implement it.
|
||||
options->setReadFileCallback(new ImageReadCallback(imageManager));
|
||||
if (ext == "dae")
|
||||
if (isColladaFile)
|
||||
options->setOptionString("daeUseSequencedTextureUnits");
|
||||
|
||||
const std::array<std::uint64_t, 2> fileHash = Files::getHash(normalizedFilename.value(), model);
|
||||
@ -599,9 +657,13 @@ namespace Resource
|
||||
node->accept(rigFinder);
|
||||
for (osg::Node* foundRigNode : rigFinder.mFoundNodes)
|
||||
{
|
||||
if (foundRigNode->libraryName() == std::string("osgAnimation"))
|
||||
if (foundRigNode->libraryName() == std::string_view("osgAnimation"))
|
||||
{
|
||||
osgAnimation::RigGeometry* foundRigGeometry = static_cast<osgAnimation::RigGeometry*>(foundRigNode);
|
||||
|
||||
if (isColladaFile)
|
||||
Resource::updateVertexInfluenceMap(*foundRigGeometry);
|
||||
|
||||
osg::ref_ptr<SceneUtil::RigGeometryHolder> newRig
|
||||
= new SceneUtil::RigGeometryHolder(*foundRigGeometry, osg::CopyOp::DEEP_COPY_ALL);
|
||||
|
||||
@ -616,13 +678,18 @@ namespace Resource
|
||||
}
|
||||
}
|
||||
|
||||
if (ext == "dae")
|
||||
if (isColladaFile)
|
||||
{
|
||||
Resource::ColladaDescriptionVisitor colladaDescriptionVisitor;
|
||||
node->accept(colladaDescriptionVisitor);
|
||||
|
||||
if (colladaDescriptionVisitor.mSkeleton)
|
||||
{
|
||||
// Collada bones may have underscores in place of spaces due to a collada limitation
|
||||
// we should rename the bones and update callbacks here at load time
|
||||
Resource::RenameAnimCallbacksVisitor renameBoneVisitor;
|
||||
node->accept(renameBoneVisitor);
|
||||
|
||||
if (osg::Group* group = dynamic_cast<osg::Group*>(node))
|
||||
{
|
||||
group->removeChildren(0, group->getNumChildren());
|
||||
@ -730,11 +797,12 @@ namespace Resource
|
||||
}
|
||||
|
||||
osg::ref_ptr<osg::Node> load(VFS::Path::NormalizedView normalizedFilename, const VFS::Manager* vfs,
|
||||
Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager)
|
||||
Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager,
|
||||
Resource::BgsmFileManager* materialMgr)
|
||||
{
|
||||
const std::string_view ext = Misc::getFileExtension(normalizedFilename.value());
|
||||
if (ext == "nif")
|
||||
return NifOsg::Loader::load(*nifFileManager->get(normalizedFilename), imageManager);
|
||||
return NifOsg::Loader::load(*nifFileManager->get(normalizedFilename), imageManager, materialMgr);
|
||||
else if (ext == "spt")
|
||||
{
|
||||
Log(Debug::Warning) << "Ignoring SpeedTree data file " << normalizedFilename;
|
||||
@ -856,7 +924,7 @@ namespace Resource
|
||||
{
|
||||
path.changeExtension(meshType);
|
||||
if (mVFS->exists(path))
|
||||
return load(path, mVFS, mImageManager, mNifFileManager);
|
||||
return load(path, mVFS, mImageManager, mNifFileManager, mBgsmFileManager);
|
||||
}
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
@ -865,7 +933,8 @@ namespace Resource
|
||||
<< ", using embedded marker_error instead";
|
||||
}
|
||||
Files::IMemStream file(ErrorMarker::sValue.data(), ErrorMarker::sValue.size());
|
||||
return loadNonNif("error_marker.osgt", file, mImageManager);
|
||||
constexpr VFS::Path::NormalizedView errorMarker("error_marker.osgt");
|
||||
return loadNonNif(errorMarker, file, mImageManager);
|
||||
}
|
||||
|
||||
osg::ref_ptr<osg::Node> SceneManager::cloneErrorMarker()
|
||||
@ -888,7 +957,7 @@ namespace Resource
|
||||
osg::ref_ptr<osg::Node> loaded;
|
||||
try
|
||||
{
|
||||
loaded = load(normalized, mVFS, mImageManager, mNifFileManager);
|
||||
loaded = load(normalized, mVFS, mImageManager, mNifFileManager, mBgsmFileManager);
|
||||
|
||||
SceneUtil::ProcessExtraDataVisitor extraDataVisitor(this);
|
||||
loaded->accept(extraDataVisitor);
|
||||
|
@ -32,6 +32,7 @@ namespace Resource
|
||||
{
|
||||
class ImageManager;
|
||||
class NifFileManager;
|
||||
class BgsmFileManager;
|
||||
class SharedStateManager;
|
||||
}
|
||||
|
||||
@ -90,7 +91,7 @@ namespace Resource
|
||||
{
|
||||
public:
|
||||
explicit SceneManager(const VFS::Manager* vfs, Resource::ImageManager* imageManager,
|
||||
Resource::NifFileManager* nifFileManager, double expiryDelay);
|
||||
Resource::NifFileManager* nifFileManager, Resource::BgsmFileManager* bgsmFileManager, double expiryDelay);
|
||||
~SceneManager();
|
||||
|
||||
Shader::ShaderManager& getShaderManager();
|
||||
@ -259,6 +260,7 @@ namespace Resource
|
||||
|
||||
Resource::ImageManager* mImageManager;
|
||||
Resource::NifFileManager* mNifFileManager;
|
||||
Resource::BgsmFileManager* mBgsmFileManager;
|
||||
|
||||
osg::Texture::FilterMode mMinFilter;
|
||||
osg::Texture::FilterMode mMagFilter;
|
||||
|
@ -87,6 +87,7 @@ namespace Resource
|
||||
"Image",
|
||||
"Nif",
|
||||
"Keyframe",
|
||||
"BSShader Material",
|
||||
"Groundcover Chunk",
|
||||
"Object Chunk",
|
||||
"Terrain Chunk",
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include <yaml-cpp/yaml.h>
|
||||
|
||||
#include <components/misc/osguservalues.hpp>
|
||||
#include <components/misc/strings/algorithm.hpp>
|
||||
#include <components/resource/scenemanager.hpp>
|
||||
#include <components/sceneutil/depth.hpp>
|
||||
#include <components/shader/shadermanager.hpp>
|
||||
|
@ -1,5 +1,6 @@
|
||||
#include <components/sceneutil/osgacontroller.hpp>
|
||||
|
||||
#include <osg/MatrixTransform>
|
||||
#include <osg/Node>
|
||||
#include <osg/NodeVisitor>
|
||||
#include <osg/ref_ptr>
|
||||
@ -9,6 +10,7 @@
|
||||
#include <osgAnimation/Sampler>
|
||||
#include <osgAnimation/UpdateMatrixTransform>
|
||||
|
||||
#include <components/misc/strings/algorithm.hpp>
|
||||
#include <components/misc/strings/lower.hpp>
|
||||
#include <components/resource/animation.hpp>
|
||||
#include <components/sceneutil/controller.hpp>
|
||||
@ -24,6 +26,10 @@ namespace SceneUtil
|
||||
|
||||
void LinkVisitor::link(osgAnimation::UpdateMatrixTransform* umt)
|
||||
{
|
||||
// If osgAnimation had underscores, we should update the umt name also
|
||||
// otherwise the animation channel and updates wont be applied
|
||||
umt->setName(Misc::StringUtils::underscoresToSpaces(umt->getName()));
|
||||
|
||||
const osgAnimation::ChannelList& channels = mAnimation->getChannels();
|
||||
for (const auto& channel : channels)
|
||||
{
|
||||
@ -85,9 +91,8 @@ namespace SceneUtil
|
||||
}
|
||||
}
|
||||
|
||||
osg::Vec3f OsgAnimationController::getTranslation(float time) const
|
||||
osg::Matrixf OsgAnimationController::getTransformForNode(float time, const std::string_view name) const
|
||||
{
|
||||
osg::Vec3f translationValue;
|
||||
std::string animationName;
|
||||
float newTime = time;
|
||||
|
||||
@ -98,10 +103,11 @@ namespace SceneUtil
|
||||
{
|
||||
newTime = time - emulatedAnimation.mStartTime;
|
||||
animationName = emulatedAnimation.mName;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Find the root transform track in animation
|
||||
// Find the bone's transform track in animation
|
||||
for (const auto& mergedAnimationTrack : mMergedAnimationTracks)
|
||||
{
|
||||
if (mergedAnimationTrack->getName() != animationName)
|
||||
@ -111,7 +117,7 @@ namespace SceneUtil
|
||||
|
||||
for (const auto& channel : channels)
|
||||
{
|
||||
if (channel->getTargetName() != "bip01" || channel->getName() != "transform")
|
||||
if (!Misc::StringUtils::ciEqual(name, channel->getTargetName()) || channel->getName() != "transform")
|
||||
continue;
|
||||
|
||||
if (osgAnimation::MatrixLinearSampler* templateSampler
|
||||
@ -119,13 +125,17 @@ namespace SceneUtil
|
||||
{
|
||||
osg::Matrixf matrix;
|
||||
templateSampler->getValueAt(newTime, matrix);
|
||||
translationValue = matrix.getTrans();
|
||||
return osg::Vec3f(translationValue[0], translationValue[1], translationValue[2]);
|
||||
return matrix;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return osg::Vec3f();
|
||||
return osg::Matrixf::identity();
|
||||
}
|
||||
|
||||
osg::Vec3f OsgAnimationController::getTranslation(float time) const
|
||||
{
|
||||
return getTransformForNode(time, "bip01").getTrans();
|
||||
}
|
||||
|
||||
void OsgAnimationController::update(float time, const std::string& animationName)
|
||||
@ -162,6 +172,12 @@ namespace SceneUtil
|
||||
update(time - emulatedAnimation.mStartTime, emulatedAnimation.mName);
|
||||
}
|
||||
}
|
||||
|
||||
// Reset the transform of this node to whats in the animation
|
||||
// we force this here because downstream some code relies on the bone having a non-modified transform
|
||||
// as this is how the NIF controller behaves. RotationController is a good example of this.
|
||||
// Without this here, it causes osgAnimation skeletons to spin wildly
|
||||
static_cast<osg::MatrixTransform*>(node)->setMatrix(getTransformForNode(time, node->getName()));
|
||||
}
|
||||
|
||||
traverse(node, nv);
|
||||
|
@ -59,6 +59,9 @@ namespace SceneUtil
|
||||
/// @brief Handles the location of the instance
|
||||
osg::Vec3f getTranslation(float time) const override;
|
||||
|
||||
/// @brief Handles finding bone position in the animation
|
||||
osg::Matrixf getTransformForNode(float time, const std::string_view name) const;
|
||||
|
||||
/// @brief Calls animation track update()
|
||||
void update(float time, const std::string& animationName);
|
||||
|
||||
|
@ -5,6 +5,8 @@
|
||||
|
||||
#include <osgParticle/ParticleSystem>
|
||||
|
||||
#include <osgAnimation/Bone>
|
||||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
#include <components/misc/strings/algorithm.hpp>
|
||||
|
||||
@ -13,7 +15,6 @@
|
||||
|
||||
namespace SceneUtil
|
||||
{
|
||||
|
||||
bool FindByNameVisitor::checkGroup(osg::Group& group)
|
||||
{
|
||||
if (Misc::StringUtils::ciEqual(group.getName(), mNameToFind))
|
||||
@ -22,35 +23,13 @@ namespace SceneUtil
|
||||
return true;
|
||||
}
|
||||
|
||||
// FIXME: can the nodes/bones be renamed at loading stage rather than each time?
|
||||
// Convert underscores to whitespaces as a workaround for Collada (OpenMW's animation system uses
|
||||
// whitespace-separated names)
|
||||
std::string nodeName = group.getName();
|
||||
std::replace(nodeName.begin(), nodeName.end(), '_', ' ');
|
||||
if (Misc::StringUtils::ciEqual(nodeName, mNameToFind))
|
||||
{
|
||||
mFoundNode = &group;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void FindByClassVisitor::apply(osg::Node& node)
|
||||
{
|
||||
if (Misc::StringUtils::ciEqual(node.className(), mNameToFind))
|
||||
{
|
||||
mFoundNodes.push_back(&node);
|
||||
}
|
||||
else
|
||||
{
|
||||
// FIXME: can the nodes/bones be renamed at loading stage rather than each time?
|
||||
// Convert underscores to whitespaces as a workaround for Collada (OpenMW's animation system uses
|
||||
// whitespace-separated names)
|
||||
std::string nodeName = node.className();
|
||||
std::replace(nodeName.begin(), nodeName.end(), '_', ' ');
|
||||
if (Misc::StringUtils::ciEqual(nodeName, mNameToFind))
|
||||
mFoundNodes.push_back(&node);
|
||||
}
|
||||
|
||||
traverse(node);
|
||||
}
|
||||
@ -69,23 +48,19 @@ namespace SceneUtil
|
||||
|
||||
void FindByNameVisitor::apply(osg::Geometry&) {}
|
||||
|
||||
void NodeMapVisitorBoneOnly::apply(osg::MatrixTransform& trans)
|
||||
{
|
||||
// Choose first found bone in file
|
||||
if (dynamic_cast<osgAnimation::Bone*>(&trans) != nullptr)
|
||||
mMap.emplace(trans.getName(), &trans);
|
||||
|
||||
traverse(trans);
|
||||
}
|
||||
|
||||
void NodeMapVisitor::apply(osg::MatrixTransform& trans)
|
||||
{
|
||||
// Choose first found node in file
|
||||
|
||||
if (trans.libraryName() == std::string_view("osgAnimation"))
|
||||
{
|
||||
std::string nodeName = trans.getName();
|
||||
|
||||
// FIXME: can the nodes/bones be renamed at loading stage rather than each time?
|
||||
// Convert underscores to whitespaces as a workaround for Collada (OpenMW's animation system uses
|
||||
// whitespace-separated names)
|
||||
std::replace(nodeName.begin(), nodeName.end(), '_', ' ');
|
||||
mMap.emplace(nodeName, &trans);
|
||||
}
|
||||
else
|
||||
mMap.emplace(trans.getName(), &trans);
|
||||
|
||||
mMap.emplace(trans.getName(), &trans);
|
||||
traverse(trans);
|
||||
}
|
||||
|
||||
|
@ -50,14 +50,14 @@ namespace SceneUtil
|
||||
std::vector<osg::Node*> mFoundNodes;
|
||||
};
|
||||
|
||||
typedef std::unordered_map<std::string, osg::ref_ptr<osg::MatrixTransform>, Misc::StringUtils::CiHash,
|
||||
Misc::StringUtils::CiEqual>
|
||||
NodeMap;
|
||||
|
||||
/// Maps names to nodes
|
||||
class NodeMapVisitor : public osg::NodeVisitor
|
||||
{
|
||||
public:
|
||||
typedef std::unordered_map<std::string, osg::ref_ptr<osg::MatrixTransform>, Misc::StringUtils::CiHash,
|
||||
Misc::StringUtils::CiEqual>
|
||||
NodeMap;
|
||||
|
||||
NodeMapVisitor(NodeMap& map)
|
||||
: osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN)
|
||||
, mMap(map)
|
||||
@ -70,6 +70,22 @@ namespace SceneUtil
|
||||
NodeMap& mMap;
|
||||
};
|
||||
|
||||
/// Maps names to bone nodes
|
||||
class NodeMapVisitorBoneOnly : public osg::NodeVisitor
|
||||
{
|
||||
public:
|
||||
NodeMapVisitorBoneOnly(NodeMap& map)
|
||||
: osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN)
|
||||
, mMap(map)
|
||||
{
|
||||
}
|
||||
|
||||
void apply(osg::MatrixTransform& trans) override;
|
||||
|
||||
private:
|
||||
NodeMap& mMap;
|
||||
};
|
||||
|
||||
/// @brief Base class for visitors that remove nodes from a scene graph.
|
||||
/// Subclasses need to fill the mToRemove vector.
|
||||
/// To use, node->accept(removeVisitor); removeVisitor.remove();
|
||||
|
@ -99,4 +99,21 @@ namespace VFS
|
||||
++normalized.back();
|
||||
return { it, mIndex.lower_bound(normalized) };
|
||||
}
|
||||
|
||||
RecursiveDirectoryRange Manager::getRecursiveDirectoryIterator(VFS::Path::NormalizedView path) const
|
||||
{
|
||||
if (path.value().empty())
|
||||
return { mIndex.begin(), mIndex.end() };
|
||||
const auto it = mIndex.lower_bound(path);
|
||||
if (it == mIndex.end() || !it->first.view().starts_with(path.value()))
|
||||
return { it, it };
|
||||
std::string copy(path.value());
|
||||
++copy.back();
|
||||
return { it, mIndex.lower_bound(copy) };
|
||||
}
|
||||
|
||||
RecursiveDirectoryRange Manager::getRecursiveDirectoryIterator() const
|
||||
{
|
||||
return { mIndex.begin(), mIndex.end() };
|
||||
}
|
||||
}
|
||||
|
@ -65,6 +65,10 @@ namespace VFS
|
||||
/// @note May be called from any thread once the index has been built.
|
||||
RecursiveDirectoryRange getRecursiveDirectoryIterator(std::string_view path) const;
|
||||
|
||||
RecursiveDirectoryRange getRecursiveDirectoryIterator(VFS::Path::NormalizedView path) const;
|
||||
|
||||
RecursiveDirectoryRange getRecursiveDirectoryIterator() const;
|
||||
|
||||
/// Retrieve the absolute path to the file
|
||||
/// @note Throws an exception if the file can not be found.
|
||||
/// @note May be called from any thread once the index has been built.
|
||||
|
@ -79,7 +79,7 @@ namespace VFS::Path
|
||||
public:
|
||||
constexpr NormalizedView() noexcept = default;
|
||||
|
||||
constexpr NormalizedView(const char* value)
|
||||
constexpr explicit NormalizedView(const char* value)
|
||||
: mValue(value)
|
||||
{
|
||||
if (!isNormalized(mValue))
|
||||
@ -179,6 +179,12 @@ namespace VFS::Path
|
||||
return true;
|
||||
}
|
||||
|
||||
Normalized& operator=(NormalizedView value)
|
||||
{
|
||||
mValue = value.value();
|
||||
return *this;
|
||||
}
|
||||
|
||||
Normalized& operator/=(NormalizedView value)
|
||||
{
|
||||
mValue.reserve(mValue.size() + value.value().size() + 1);
|
||||
@ -258,6 +264,22 @@ namespace VFS::Path
|
||||
result /= rhs;
|
||||
return result;
|
||||
}
|
||||
|
||||
struct Hash
|
||||
{
|
||||
using is_transparent = void;
|
||||
|
||||
[[nodiscard]] std::size_t operator()(std::string_view sv) const { return std::hash<std::string_view>{}(sv); }
|
||||
|
||||
[[nodiscard]] std::size_t operator()(const std::string& s) const { return std::hash<std::string>{}(s); }
|
||||
|
||||
[[nodiscard]] std::size_t operator()(const Normalized& s) const { return std::hash<std::string>{}(s.value()); }
|
||||
|
||||
[[nodiscard]] std::size_t operator()(NormalizedView s) const
|
||||
{
|
||||
return std::hash<std::string_view>{}(s.value());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
|
95
files/lang/components_sv.ts
Normal file
95
files/lang/components_sv.ts
Normal file
@ -0,0 +1,95 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS>
|
||||
<TS version="2.1" language="sv_SE">
|
||||
<context>
|
||||
<name>ContentSelector</name>
|
||||
<message>
|
||||
<source>Select language used by ESM/ESP content files to allow OpenMW to detect their encoding. </source>
|
||||
<translation>Välj språk som används av ESM/ESP-innehållsfiler så att OpenMW kan hitta deras kodning. </translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ContentSelectorModel::ContentModel</name>
|
||||
<message>
|
||||
<source>Unable to find dependent file: %1</source>
|
||||
<translation>Kunde inte hitta beroende fil: %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Dependent file needs to be active: %1</source>
|
||||
<translation>Beroende fil måste vara aktiv: %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>This file needs to load after %1</source>
|
||||
<translation>Denna fil måste laddas efter %1</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ContentSelectorModel::EsmFile</name>
|
||||
<message>
|
||||
<source><b>Author:</b> %1<br/><b>Format version:</b> %2<br/><b>Modified:</b> %3<br/><b>Path:</b><br/>%4<br/><br/><b>Description:</b><br/>%5<br/><br/><b>Dependencies: </b>%6<br/></source>
|
||||
<translation><b>Skapare:</b> %1<br/><b>Formatversion:</b> %2<br/><b>Ändrad:</b> %3<br/><b>Sökväg:</b><br/>%4<br/><br/><b>Beskrivning:</b><br/>%5<br/><br/><b>Beroenden: </b>%6<br/></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><br/><b>This content file cannot be disabled because it is part of OpenMW.</b><br/></source>
|
||||
<translation><br/><b>Denna innehållsfil kan inte inaktiveras då den är en del av OpenMW.</b><br/></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><br/><b>This content file cannot be disabled because it is enabled in a config file other than the user one.</b><br/></source>
|
||||
<translation><br/><b>Denna innehållsfil kan inte inaktiveras då den är en aktiverad i en annan konfigurationsfil än användarens.</b><br/></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ContentSelectorView::ContentSelector</name>
|
||||
<message>
|
||||
<source>&Check Selected</source>
|
||||
<translation>&Bocka i markerade</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>&Uncheck Selected</source>
|
||||
<translation>&Bocka ur markerade</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>&Copy Path(s) to Clipboard</source>
|
||||
<translation>&Kopiera sökväg(ar) till klippbordet</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><No game file></source>
|
||||
<translation><Ingen spelfil></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Process::ProcessInvoker</name>
|
||||
<message>
|
||||
<source>Error starting executable</source>
|
||||
<translation>Kunde inte starta körbara filen</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Error running executable</source>
|
||||
<translation>Fel när körbara filen skulle köras</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>
|
||||
Arguments:
|
||||
</source>
|
||||
<translation>
|
||||
Argument:
|
||||
</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><html><head/><body><p><b>Could not find %1</b></p><p>The application is not found.</p><p>Please make sure OpenMW is installed correctly and try again.</p></body></html></source>
|
||||
<translation><html><head/><body><p><b>Kunde inte hitta %1</b></p><p>Applikationen hittas inte.</p><p>Se till att OpenMW är korrekt installerat och försök igen.</p></body></html></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><html><head/><body><p><b>Could not start %1</b></p><p>The application is not executable.</p><p>Please make sure you have the right permissions and try again.</p></body></html></source>
|
||||
<translation><html><head/><body><p><b>Kunde inte starta %1</b></p><p>Applikationen är inte körbar.</p><p>Se till att du har rätt behörigheter och försök igen.</p></body></html></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><html><head/><body><p><b>Could not start %1</b></p><p>An error occurred while starting %1.</p><p>Press "Show Details..." for more information.</p></body></html></source>
|
||||
<translation><html><head/><body><p><b>Kunde inte starta %1</b></p><p>Ett fel uppstod när %1 skulle startas.</p><p>Tryck på "Visa detaljer..." för mer information.</p></body></html></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><html><head/><body><p><b>Executable %1 returned an error</b></p><p>An error occurred while running %1.</p><p>Press "Show Details..." for more information.</p></body></html></source>
|
||||
<translation><html><head/><body><p><b>Körbara filen %1 gav ett felmeddelande</b></p><p>Ett fel uppstod när %1 kördes.</p><p>Tryck på "Visa detaljer..." för mer information.</p></body></html></translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
1494
files/lang/launcher_sv.ts
Normal file
1494
files/lang/launcher_sv.ts
Normal file
File diff suppressed because it is too large
Load Diff
@ -128,10 +128,6 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov
|
||||
<source>Where should Morrowind be installed?</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html></source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Morrowind will be installed to the following location. </source>
|
||||
<translation type="unfinished"></translation>
|
||||
@ -170,10 +166,6 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov
|
||||
<source>What is the language of the Morrowind installation?</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><html><head/><body><p><img src=":/icons/tango/48x48/preferences-desktop-locale.png"/></p></body></html></source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Select the language of the Morrowind installation.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
@ -197,10 +189,6 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov
|
||||
<source>Retail CD/DVD</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><html><head/><body><p><img src=":/icons/tango/48x48/system-installer.png"/></p></body></html></source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Install from a retail disc to a new location.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
@ -209,10 +197,6 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov
|
||||
<source>Existing Installation</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html></source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Select an existing installation.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
@ -221,10 +205,6 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov
|
||||
<source>Don't have a copy?</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><html><head/><body><p><img src=":/icons/tango/48x48/dollar.png"/></p></body></html></source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Buy the game</source>
|
||||
<translation type="unfinished"></translation>
|
||||
|
@ -128,10 +128,6 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov
|
||||
<source>Where should Morrowind be installed?</source>
|
||||
<translation>À quel emplacement Morrowind doit-il être installé ?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html></source>
|
||||
<translation><html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Morrowind will be installed to the following location. </source>
|
||||
<translation>Morrowind sera installé à l'emplacement suivant :</translation>
|
||||
@ -170,10 +166,6 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov
|
||||
<source>What is the language of the Morrowind installation?</source>
|
||||
<translation>Dans quelle langue est cette installation de Morrowind ?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><html><head/><body><p><img src=":/icons/tango/48x48/preferences-desktop-locale.png"/></p></body></html></source>
|
||||
<translation><html><head/><body><p><img src=":/icons/tango/48x48/preferences-desktop-locale.png"/></p></body></html></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Select the language of the Morrowind installation.</source>
|
||||
<translation>Sélectionnez la langue de cette installation de Morrowind.</translation>
|
||||
@ -197,10 +189,6 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov
|
||||
<source>Retail CD/DVD</source>
|
||||
<translation>Copie CD/DVD physique</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><html><head/><body><p><img src=":/icons/tango/48x48/system-installer.png"/></p></body></html></source>
|
||||
<translation><html><head/><body><p><img src=":/icons/tango/48x48/system-installer.png"/></p></body></html></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Install from a retail disc to a new location.</source>
|
||||
<translation>Installer depuis un CD/DVD et choisir la destination sur l'ordinateur.</translation>
|
||||
@ -209,10 +197,6 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov
|
||||
<source>Existing Installation</source>
|
||||
<translation>Installation existante</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html></source>
|
||||
<translation><html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Select an existing installation.</source>
|
||||
<translation>Sélectionnez une installation existante.</translation>
|
||||
@ -221,10 +205,6 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov
|
||||
<source>Don't have a copy?</source>
|
||||
<translation>Vous n'avez pas de copie du jeu ?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><html><head/><body><p><img src=":/icons/tango/48x48/dollar.png"/></p></body></html></source>
|
||||
<translation><html><head/><body><p><img src=":/icons/tango/48x48/dollar.png"/></p></body></html></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Buy the game</source>
|
||||
<translation>Achetez le jeu.</translation>
|
||||
|
@ -130,10 +130,6 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov
|
||||
<source>Where should Morrowind be installed?</source>
|
||||
<translation>Куда нужно установить Morrowind?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html></source>
|
||||
<translation><html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Morrowind will be installed to the following location. </source>
|
||||
<translation>Morrowind будет установлен в следующее место. </translation>
|
||||
@ -172,10 +168,6 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov
|
||||
<source>What is the language of the Morrowind installation?</source>
|
||||
<translation>Какой язык использует ваша копия Morrowind?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><html><head/><body><p><img src=":/icons/tango/48x48/preferences-desktop-locale.png"/></p></body></html></source>
|
||||
<translation>><html><head/><body><p><img src=":/icons/tango/48x48/preferences-desktop-locale.png"/></p></body></html></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Select the language of the Morrowind installation.</source>
|
||||
<translation>Выберите язык, используемый вашей копией Morrowind.</translation>
|
||||
@ -199,10 +191,6 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov
|
||||
<source>Retail CD/DVD</source>
|
||||
<translation>CD/DVD-диск</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><html><head/><body><p><img src=":/icons/tango/48x48/system-installer.png"/></p></body></html></source>
|
||||
<translation><html><head/><body><p><img src=":/icons/tango/48x48/system-installer.png"/></p></body></html></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Install from a retail disc to a new location.</source>
|
||||
<translation>Установить игру с диска.</translation>
|
||||
@ -211,10 +199,6 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov
|
||||
<source>Existing Installation</source>
|
||||
<translation>Установленная копия игры</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html></source>
|
||||
<translation><html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Select an existing installation.</source>
|
||||
<translation>Выбрать установленную копию игры.</translation>
|
||||
@ -223,10 +207,6 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov
|
||||
<source>Don't have a copy?</source>
|
||||
<translation>Нет копии игры?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><html><head/><body><p><img src=":/icons/tango/48x48/dollar.png"/></p></body></html></source>
|
||||
<translation><html><head/><body><p><img src=":/icons/tango/48x48/dollar.png"/></p></body></html></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Buy the game</source>
|
||||
<translation>Купить игру</translation>
|
||||
|
658
files/lang/wizard_sv.ts
Normal file
658
files/lang/wizard_sv.ts
Normal file
@ -0,0 +1,658 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS>
|
||||
<TS version="2.1" language="sv_SE">
|
||||
<context>
|
||||
<name>ComponentSelectionPage</name>
|
||||
<message>
|
||||
<source>WizardPage</source>
|
||||
<translation>WizardPage</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Select Components</source>
|
||||
<translation>Välj komponenter</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Which components should be installed?</source>
|
||||
<translation>Vilka komponenter ska installeras?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><html><head/><body><p>Select which official Morrowind expansions should be installed. For best results, it is recommended to have both expansions installed.</p><p><span style=" font-weight:bold;">Note:</span> It is possible to install expansions later by re-running this Wizard.<br/></p></body></html></source>
|
||||
<translation><html><head/><body><p>Välj vilka officiella Morrowindexpansioner som ska installeras. För bäst resultat rekommenderas att båda expansionerna installeras.</p><p><span style=" font-weight:bold;">Note:</span> Det är möjligt att installera expansioner senare genom att köra denna guide igen.<br/></p></body></html></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Selected components:</source>
|
||||
<translation>Valda komponenter:</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ConclusionPage</name>
|
||||
<message>
|
||||
<source>WizardPage</source>
|
||||
<translation>WizardPage</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Completing the OpenMW Wizard</source>
|
||||
<translation>Färdigställer OpenMWs installationsguide</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Placeholder</source>
|
||||
<translation>Placeholder</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ExistingInstallationPage</name>
|
||||
<message>
|
||||
<source>WizardPage</source>
|
||||
<translation>WizardPage</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Select Existing Installation</source>
|
||||
<translation>Välj befintlig installation</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Select an existing installation for OpenMW to use or modify.</source>
|
||||
<translation>Välj en befintlig installation som OpenMW kan använda eller modifiera.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Detected installations:</source>
|
||||
<translation>Hittade installationer:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Browse...</source>
|
||||
<translation>Bläddra...</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ImportPage</name>
|
||||
<message>
|
||||
<source>WizardPage</source>
|
||||
<translation>WizardPage</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Import Settings</source>
|
||||
<translation>Importera inställningar</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Import settings from the Morrowind installation.</source>
|
||||
<translation>Importera inställningar från Morrowindinstallationen.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><html><head/><body><p>OpenMW needs to import settings from the Morrowind configuration file in order to function properly.</p><p><span style=" font-weight:bold;">Note:</span> It is possible to import settings later by re-running this Wizard.</p><p/></body></html></source>
|
||||
<translation><html><head/><body><p>OpenMW behöver importera inställningar från Morrowinds konfigurationsfil för att fungera korrekt.</p><p><span style=" font-weight:bold;">Notera:</span> Det är möjligt att importera inställningarna senare genom att köra denna guide igen.</p><p/></body></html></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Import Settings From Morrowind.ini</source>
|
||||
<translation>Importera inställningar från Morrowind.ini</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Import Add-on and Plugin Selection</source>
|
||||
<translation>Importera tillägg- och pluginmarkering</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Import Bitmap Fonts Setup From Morrowind.ini</source>
|
||||
<translation>Importera bitmapfonter från Morrowind.ini</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Fonts shipped with the original engine are blurry with UI scaling and support only a small amount of characters,
|
||||
so OpenMW provides another set of fonts to avoid these issues. These fonts use TrueType technology and are quite similar
|
||||
to default Morrowind fonts. Check this box if you still prefer original fonts over OpenMW ones or if you use custom bitmap fonts.</source>
|
||||
<translation>Fonter som kommer med ordinarie spelmotor är suddiga med gränssnittsuppskalning och stödjer bara ett litet antal tecken,
|
||||
så OpenMW tillhandahåller därför andra fonter för att undvika dessa problem. Dessa fonter använder TrueType-teknologi och är ganska lika
|
||||
de ordinarie fonterna i Morrowind. Bocka i denna ruta om du ändå föredrar ordinarie fonter istället för OpenMWs, alternativt om du använder egna bitmapfonter.</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>InstallationPage</name>
|
||||
<message>
|
||||
<source>WizardPage</source>
|
||||
<translation>WizardPage</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Installing</source>
|
||||
<translation>Installerar</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Please wait while Morrowind is installed on your computer.</source>
|
||||
<translation>Vänta medan Morrowind installeras på din dator.</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>InstallationTargetPage</name>
|
||||
<message>
|
||||
<source>WizardPage</source>
|
||||
<translation>WizardPage</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Select Installation Destination</source>
|
||||
<translation>Välj installationsplats</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Where should Morrowind be installed?</source>
|
||||
<translation>Var ska Morrowind installeras?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Morrowind will be installed to the following location. </source>
|
||||
<translation>Morrowind kommer installeras på följande plats. </translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Browse...</source>
|
||||
<translation>Bläddra...</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>IntroPage</name>
|
||||
<message>
|
||||
<source>WizardPage</source>
|
||||
<translation>WizardPage</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Welcome to the OpenMW Wizard</source>
|
||||
<translation>Välkommen till OpenMWs installationsguide</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>This Wizard will help you install Morrowind and its add-ons for OpenMW to use.</source>
|
||||
<translation>Denna guide hjälper dig att installera Morrowind och expansionerna för OpenMW.</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>LanguageSelectionPage</name>
|
||||
<message>
|
||||
<source>WizardPage</source>
|
||||
<translation>WizardPage</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Select Morrowind Language</source>
|
||||
<translation>Välj Morrowinds språk</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>What is the language of the Morrowind installation?</source>
|
||||
<translation>Vad är språket på Morrowindinstallationen?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Select the language of the Morrowind installation.</source>
|
||||
<translation>Välj språket på Morrowindinstallationen.</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MethodSelectionPage</name>
|
||||
<message>
|
||||
<source>WizardPage</source>
|
||||
<translation>WizardPage</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Select Installation Method</source>
|
||||
<translation>Välj installationsmetod</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><html><head/><body><p>Select how you would like to install <i>The Elder Scrolls III: Morrowind</i>.</p></body></html></source>
|
||||
<translation><html><head/><body><p>Välj hur du vill installera <i>The Elder Scrolls III: Morrowind</i>.</p></body></html></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Retail CD/DVD</source>
|
||||
<translation>Köpt CD/DVD</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Install from a retail disc to a new location.</source>
|
||||
<translation>Installera från en köpt skiva till en ny plats.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Existing Installation</source>
|
||||
<translation>Befintlig installation</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Select an existing installation.</source>
|
||||
<translation>Välj en befintlig installation.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Don't have a copy?</source>
|
||||
<translation>Äger du inte spelet?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Buy the game</source>
|
||||
<translation>Köp spelet</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>QObject</name>
|
||||
<message>
|
||||
<source><br><b>Could not find Morrowind.ini</b><br><br>The Wizard needs to update settings in this file.<br><br>Press "Browse..." to specify the location manually.<br></source>
|
||||
<translation><br><b>Kunde inte hitta Morrowind.ini</b><br><br>Guiden behöver uppdatera inställningarna i denna fil.<br><br>Tryck på "Bläddra..." för att specificera filens plats manuellt.<br></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>B&rowse...</source>
|
||||
<translation>B&läddra...</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Select configuration file</source>
|
||||
<translation>Välj konfigurationsfil</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><b>Morrowind.bsa</b> is missing!<br>Make sure your Morrowind installation is complete.</source>
|
||||
<translation><b>Morrowind.bsa</b> saknas!<br>Se till att din Morrowindinstallation är komplett.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><br><b>There may be a more recent version of Morrowind available.</b><br><br>Do you wish to continue anyway?<br></source>
|
||||
<translation><br><b>Det kan finnas nyare version av Morrowind tillgänglig.</b><br><br>Vill du fortsätta ändå?<br></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Most recent Morrowind not detected</source>
|
||||
<translation>Senaste versionen av Morrowind hittades inte</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Select a valid %1 installation media.<br><b>Hint</b>: make sure that it contains at least one <b>.cab</b> file.</source>
|
||||
<translation>Välj ett giltigt %1 installationsmedium.<br><b>Tips</b>: säkerställ att det finns åtminstone en <b>.cab</b>-fil.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>There may be a more recent version of Morrowind available.<br><br>Do you wish to continue anyway?</source>
|
||||
<translation>Det kan finnas en mer uppdaterad version av Morrowind tillgänglig.<br><br>Vill du fortsätta ändå?</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Wizard::ComponentSelectionPage</name>
|
||||
<message>
|
||||
<source>&Install</source>
|
||||
<translation>&Installera</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>&Skip</source>
|
||||
<translation>&Hoppa över</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Morrowind (installed)</source>
|
||||
<translation>Morrowind (installerat)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Morrowind</source>
|
||||
<translation>Morrowind</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Tribunal (installed)</source>
|
||||
<translation>Tribunal (installerat)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Tribunal</source>
|
||||
<translation>Tribunal</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Bloodmoon (installed)</source>
|
||||
<translation>Bloodmoon (installerat)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Bloodmoon</source>
|
||||
<translation>Bloodmoon</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>About to install Tribunal after Bloodmoon</source>
|
||||
<translation>På väg att installera Tribunal efter Bloodmoon</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><html><head/><body><p><b>You are about to install Tribunal</b></p><p>Bloodmoon is already installed on your computer.</p><p>However, it is recommended that you install Tribunal before Bloodmoon.</p><p>Would you like to re-install Bloodmoon?</p></body></html></source>
|
||||
<translation><html><head/><body><p><b>Du håller på att installera Tribunal</b></p><p>Bloodmoon finns redan installerat på din dator.</p><p>Det är dock rekommenderat att du installerar Tribunal före Bloodmoon.</p><p>Vill du ominstallera Bloodmoon?</p></body></html></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Re-install &Bloodmoon</source>
|
||||
<translation>Ominstallera &Bloodmoon</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Wizard::ConclusionPage</name>
|
||||
<message>
|
||||
<source><html><head/><body><p>The OpenMW Wizard successfully installed Morrowind on your computer.</p></body></html></source>
|
||||
<translation><html><head/><body><p>OpenMWs Installationsguide har installerat Morrowind på din dator.</p></body></html></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><html><head/><body><p>The OpenMW Wizard successfully modified your existing Morrowind installation.</body></html></source>
|
||||
<translation><html><head/><body><p>OpenMWs installationsguide har justerat din befintliga Morrowindinstallation.</body></html></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><html><head/><body><p>The OpenMW Wizard failed to install Morrowind on your computer.</p><p>Please report any bugs you might have encountered to our <a href="https://gitlab.com/OpenMW/openmw/issues">bug tracker</a>.<br/>Make sure to include the installation log.</p><br/></body></html></source>
|
||||
<translation><html><head/><body><p>OpenMWs installationsguide misslyckades med att installera Morrowind på din dator.</p><p>Vänligen rapportera eventuella buggar på vår <a href="https://gitlab.com/OpenMW/openmw/issues">bug tracker</a>.<br/>Se till att du inkluderar installationsloggen.</p><br/></body></html></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Wizard::ExistingInstallationPage</name>
|
||||
<message>
|
||||
<source>No existing installations detected</source>
|
||||
<translation>Inga befintliga installationer hittades</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Error detecting Morrowind configuration</source>
|
||||
<translation>Kunde inte hitta Morrowind-konfigurationsfil</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Morrowind configuration file (*.ini)</source>
|
||||
<translation>Morrowind konfigurationsfil (*.ini)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Select Morrowind.esm (located in Data Files)</source>
|
||||
<translation>Markera Morrowind.esm (hittas i Data Files)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Morrowind master file (Morrowind.esm)</source>
|
||||
<translation>Morrowind masterfil (Morrowind.esm)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Error detecting Morrowind files</source>
|
||||
<translation>Kunde inte hitta Morrowindfiler</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Wizard::InstallationPage</name>
|
||||
<message>
|
||||
<source><p>Attempting to install component %1.</p></source>
|
||||
<translation><p>Försöker installera komponent %1.</p></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Attempting to install component %1.</source>
|
||||
<translation>Försöker installera komponent %1.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>%1 Installation</source>
|
||||
<translation>%1 Installation</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Select %1 installation media</source>
|
||||
<translation>Välj %1 installationsmedia</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><p><br/><span style="color:red;"><b>Error: The installation was aborted by the user</b></span></p></source>
|
||||
<translation><p><br/><span style="color:red;"><b>Fel: Installationen avbröts av användaren</b></span></p></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><p>Detected old version of component Morrowind.</p></source>
|
||||
<translation><p>Hittade gammal version av Morrowind.</p></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Detected old version of component Morrowind.</source>
|
||||
<translation>Hittade gammal version av komponenten Morrowind.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Morrowind Installation</source>
|
||||
<translation>Installation av Morrowind</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Installation finished</source>
|
||||
<translation>Installationen klar</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Installation completed successfully!</source>
|
||||
<translation>Installationen slutfördes!</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Installation failed!</source>
|
||||
<translation>Installationen misslyckades!</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><p><br/><span style="color:red;"><b>Error: %1</b></p></source>
|
||||
<translation><p><br/><span style="color:red;"><b>Fel: %1</b></p></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><p><span style="color:red;"><b>%1</b></p></source>
|
||||
<translation><p><span style="color:red;"><b>%1</b></p></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><html><head/><body><p><b>The Wizard has encountered an error</b></p><p>The error reported was:</p><p>%1</p><p>Press &quot;Show Details...&quot; for more information.</p></body></html></source>
|
||||
<translation><html><head/><body><p><b>Guiden har stött på ett fel</b></p><p>Felet som rapporterades var:</p><p>%1</p><p>Tryck på &quot;Visa detaljer...&quot; för mer information.</p></body></html></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>An error occurred</source>
|
||||
<translation>Ett fel uppstod</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Wizard::InstallationTargetPage</name>
|
||||
<message>
|
||||
<source>Error creating destination</source>
|
||||
<translation>Fel när målkatalogen skulle skapas</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><html><head/><body><p><b>Could not create the destination directory</b></p><p>Please make sure you have the right permissions and try again, or specify a different location.</p></body></html></source>
|
||||
<translation><html><head/><body><p><b>Kunde inte skapa målkatalogen</b></p><p>Se till att du har rätt behörigheter och försök igen, eller specificera en annan katalog.</p></body></html></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><html><head/><body><p><b>Could not write to the destination directory</b></p><p>Please make sure you have the right permissions and try again, or specify a different location.</p></body></html></source>
|
||||
<translation><html><head/><body><p><b>Kunde inte skriva till målkatalogen</b></p><p>Se till att du har rätt behörigheter och försök igen, eller specificera en annan katalog.</p></body></html></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><html><head/><body><p><b>The destination directory is not empty</b></p><p>An existing Morrowind installation is present in the specified location.</p><p>Please specify a different location, or go back and select the location as an existing installation.</p></body></html></source>
|
||||
<translation><html><head/><body><p><b>Målkatalogen är inte tom</b></p><p>Det finns en befintlig Morrowindinstallation i den specificerade katalogen.</p><p>Välj en annan katalog eller gå tillbaka och välj katalogen som en befintlig installation.</p></body></html></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Insufficient permissions</source>
|
||||
<translation>Otillräckliga behörigheter</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Destination not empty</source>
|
||||
<translation>Platsen är inte tom</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Select where to install Morrowind</source>
|
||||
<translation>Välj där Morrowind ska installeras</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Wizard::LanguageSelectionPage</name>
|
||||
<message>
|
||||
<source>English</source>
|
||||
<translation>Engelska</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>French</source>
|
||||
<translation>Franska</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>German</source>
|
||||
<translation>Tyska</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Italian</source>
|
||||
<translation>Italienska</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Polish</source>
|
||||
<translation>Polska</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Russian</source>
|
||||
<translation>Ryska</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Spanish</source>
|
||||
<translation>Spanska</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Wizard::MainWizard</name>
|
||||
<message>
|
||||
<source>OpenMW Wizard</source>
|
||||
<translation>OpenMW installationsguide</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Error opening Wizard log file</source>
|
||||
<translation>Kunde inte öppna guidens loggfil</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><html><head/><body><p><b>Could not open %1 for writing</b></p><p>Please make sure you have the right permissions and try again.</p></body></html></source>
|
||||
<translation><html><head/><body><p><b>Kunde inte öppna %1 för att skriva</b></p><p>Se till att du har rätt behörigheter och försök igen.</p></body></html></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><html><head/><body><p><b>Could not open %1 for reading</b></p><p>Please make sure you have the right permissions and try again.</p></body></html></source>
|
||||
<translation><html><head/><body><p><b>Kunde inte öppna %1 för läsning</b></p><p>Se till att du har rätt behörigheter och försök igen.</p></body></html></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Error opening OpenMW configuration file</source>
|
||||
<translation>Fel när OpenMW-konfigurationsfil skulle öppnas</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Quit Wizard</source>
|
||||
<translation>Avsluta guiden</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Are you sure you want to exit the Wizard?</source>
|
||||
<translation>Är du säker på att du vill avsluta guiden?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Error creating OpenMW configuration directory</source>
|
||||
<translation>Fel vid skapande av OpenMW-konfigurationskatalog</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><html><head/><body><p><b>Could not create %1</b></p><p>Please make sure you have the right permissions and try again.</p></body></html></source>
|
||||
<translation><html><head/><body><p><b>Kunde inte skapa %1</b></p><p>Se till att du har rätt behörigheter och försök igen.</p></body></html></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Error writing OpenMW configuration file</source>
|
||||
<translation>Kunde inte skriva OpenMW-konfigurationsfil</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Wizard::UnshieldWorker</name>
|
||||
<message>
|
||||
<source>Failed to open Morrowind configuration file!</source>
|
||||
<translation>Kunde inte öppna en Morrowind-konfigurationsfil!</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Opening %1 failed: %2.</source>
|
||||
<translation>Öppnar %1 misslyckad: %2.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Failed to write Morrowind configuration file!</source>
|
||||
<translation>Kunde inte skriva en Morrowind-konfigurationsfil!</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Writing to %1 failed: %2.</source>
|
||||
<translation>Skriver till %1 misslyckad: %2.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Installing: %1</source>
|
||||
<translation>Installerar: %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Installing: %1 directory</source>
|
||||
<translation>Installerar: %1 katalog</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Installation finished!</source>
|
||||
<translation>Installationen slutförd!</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Component parameter is invalid!</source>
|
||||
<translation>Komponentparametern är ogiltig!</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>An invalid component parameter was supplied.</source>
|
||||
<translation>En ogiltig komponentparameter angavs.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Failed to find a valid archive containing %1.bsa! Retrying.</source>
|
||||
<translation>Misslyckades att hitta ett giltigt arkiv som innehåller %1.bsa! Försöker igen.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Installing %1</source>
|
||||
<translation>Installerar %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Installation media path not set!</source>
|
||||
<translation>Sökväg till installationsmedia inte inställd!</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>The source path for %1 was not set.</source>
|
||||
<translation>Källsökvägen för %1 ställdes inte in.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot create temporary directory!</source>
|
||||
<translation>Kan inte skapa temporär katalog!</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Failed to create %1.</source>
|
||||
<translation>Kunde inte skapa %1.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot move into temporary directory!</source>
|
||||
<translation>Kan inte flytta till temporär katalog!</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Failed to move into %1.</source>
|
||||
<translation>Misslyckades att flytta till %1.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Moving installation files</source>
|
||||
<translation>Flyttar installationsfiler</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Could not install directory!</source>
|
||||
<translation>Kunde inte installera katalog!</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Installing %1 to %2 failed.</source>
|
||||
<translation>Installation %1 till %2 misslyckades.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Could not install translation file!</source>
|
||||
<translation>Kunde inte installera översättningsfil!</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Failed to install *%1 files.</source>
|
||||
<translation>Kunde inte installera *%1 filer.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Could not install Morrowind data file!</source>
|
||||
<translation>Kunde inte installera Morrowind-datafil!</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Failed to install %1.</source>
|
||||
<translation>Misslyckades att installera %1.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Could not install Morrowind configuration file!</source>
|
||||
<translation>Kunde inte installera Morrowind-konfigurationsfil!</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Installing: Sound directory</source>
|
||||
<translation>Installerar: Ljudkatalog</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Could not find Tribunal data file!</source>
|
||||
<translation>Tribunal-datafil hittades inte!</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Failed to find %1.</source>
|
||||
<translation>Misslyckades att hitta %1.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Could not find Tribunal patch file!</source>
|
||||
<translation>Tribunal-patchfil hittades inte!</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Could not find Bloodmoon data file!</source>
|
||||
<translation>Bloodmoon-datafil hittades inte!</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Updating Morrowind configuration file</source>
|
||||
<translation>Uppdaterar Morrowind-konfigurationsfil</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>%1 installation finished!</source>
|
||||
<translation>%1 installation klar!</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Extracting: %1</source>
|
||||
<translation>Extraherar: %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Failed to open InstallShield Cabinet File.</source>
|
||||
<translation>Misslyckades att öppna en InstallShield Cabinet-fil.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Opening %1 failed.</source>
|
||||
<translation>Misslyckades att öppna %1.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Failed to extract %1.</source>
|
||||
<translation>Misslyckades att extrahera %1.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Complete path: %1</source>
|
||||
<translation>Färdigställ sökväg: %1</translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
501
files/opencs/configure.svg
Normal file
501
files/opencs/configure.svg
Normal file
@ -0,0 +1,501 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 12.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 51448) -->
|
||||
|
||||
<svg
|
||||
version="1.0"
|
||||
id="Livello_1"
|
||||
width="128"
|
||||
height="128.00018"
|
||||
viewBox="0 0 119.999 120.061"
|
||||
overflow="visible"
|
||||
enable-background="new 0 0 119.999 120.061"
|
||||
xml:space="preserve"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:ns="http://ns.adobe.com/SaveForWeb/1.0/"><defs
|
||||
id="defs127"><linearGradient
|
||||
id="linearGradient3291"><stop
|
||||
style="stop-color:black;stop-opacity:1"
|
||||
offset="0"
|
||||
id="stop3293" /><stop
|
||||
style="stop-color:black;stop-opacity:0"
|
||||
offset="1"
|
||||
id="stop3295" /></linearGradient><radialGradient
|
||||
xlink:href="#linearGradient3291"
|
||||
id="radialGradient3336"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1,0,0,0.197802,0,92.82166)"
|
||||
cx="63.912209"
|
||||
cy="115.70919"
|
||||
fx="33.953373"
|
||||
fy="115.70919"
|
||||
r="63.912209" /><linearGradient
|
||||
xlink:href="#XMLID_11_"
|
||||
id="linearGradient9291"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1.1250507,0.1245433,0.1244801,1.1244802,-2.8257128,-11.708873)"
|
||||
x1="41.188999"
|
||||
y1="80.101601"
|
||||
x2="27.6451"
|
||||
y2="66.557701" /><linearGradient
|
||||
id="linearGradient6141"><stop
|
||||
style="stop-color:#000000;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop6143" /><stop
|
||||
style="stop-color:#ffffff;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop6145" /></linearGradient><linearGradient
|
||||
gradientTransform="translate(-3.0255781e-2,3.056501e-4)"
|
||||
id="linearGradient5166"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="77.542931"
|
||||
y1="41.868668"
|
||||
x2="88.108376"
|
||||
y2="31.303225"><stop
|
||||
offset="0"
|
||||
style="stop-color:#323232;stop-opacity:1;"
|
||||
id="stop5168" /><stop
|
||||
offset="0.75739998"
|
||||
style="stop-color:#ffffff;stop-opacity:0;"
|
||||
id="stop5170" /></linearGradient><linearGradient
|
||||
y2="31.303225"
|
||||
x2="88.108376"
|
||||
y1="41.868668"
|
||||
x1="77.542931"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="XMLID_16_"
|
||||
gradientTransform="translate(-3.0255781e-2,3.056501e-4)"><stop
|
||||
id="stop92"
|
||||
style="stop-color:#ffffff;stop-opacity:1;"
|
||||
offset="0" /><stop
|
||||
id="stop106"
|
||||
style="stop-color:#ffffff;stop-opacity:0.01898734;"
|
||||
offset="0.75739998" /></linearGradient><linearGradient
|
||||
xlink:href="#XMLID_18_"
|
||||
id="linearGradient3295"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0.7074656,-0.7074656,0.7071068,0.7071068,-18.749172,36.539651)"
|
||||
x1="-6.165"
|
||||
y1="112.5049"
|
||||
x2="11.0181"
|
||||
y2="112.5049" /><linearGradient
|
||||
xlink:href="#XMLID_17_"
|
||||
id="linearGradient3298"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1.0002442,-2.5378062e-4,-2.5378062e-4,1.0002442,8.6347844,-24.632129)"
|
||||
x1="38.757801"
|
||||
y1="7.4277"
|
||||
x2="63.923302"
|
||||
y2="7.4277" /><linearGradient
|
||||
xlink:href="#XMLID_16_"
|
||||
id="linearGradient3301"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0.7074656,-0.7074656,0.7071068,0.7071068,-24.896055,39.032512)"
|
||||
x1="76.281174"
|
||||
y1="43.060352"
|
||||
x2="86.611"
|
||||
y2="31.303225" /><linearGradient
|
||||
xlink:href="#XMLID_15_"
|
||||
id="linearGradient3304"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0.7074656,-0.7074656,0.7071068,0.7071068,-24.895791,39.125846)"
|
||||
x1="76.919403"
|
||||
y1="43.1436"
|
||||
x2="100.6411"
|
||||
y2="19.4214" /><linearGradient
|
||||
xlink:href="#XMLID_14_"
|
||||
id="linearGradient3307"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0.7074656,-0.7074656,0.7071068,0.7071068,-18.749172,36.539651)"
|
||||
x1="67.880402"
|
||||
y1="6.4331002"
|
||||
x2="105.3924"
|
||||
y2="43.945599" /><linearGradient
|
||||
xlink:href="#XMLID_13_"
|
||||
id="linearGradient3310"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(-0.7074656,0.7074656,-0.7071068,-0.7071068,101.2385,185.78751)"
|
||||
x1="-0.47799999"
|
||||
y1="105.0586"
|
||||
x2="6.3157001"
|
||||
y2="111.8523" /><linearGradient
|
||||
xlink:href="#XMLID_12_"
|
||||
id="linearGradient3313"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(-0.7074656,0.7074656,-0.7071068,-0.7071068,107.38512,183.20132)"
|
||||
x1="1.3501"
|
||||
y1="103.2324"
|
||||
x2="8.1437998"
|
||||
y2="110.0261" /><linearGradient
|
||||
id="XMLID_10_"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="61.9692"
|
||||
y1="88.430702"
|
||||
x2="40.815899"
|
||||
y2="88.430702"
|
||||
gradientTransform="matrix(0.7074589,0.7071,-0.7074589,0.7071,68.76314,-21.373694)"><stop
|
||||
offset="0.0533"
|
||||
style="stop-color:#555555"
|
||||
id="stop10" /><stop
|
||||
offset="0.21113414"
|
||||
style="stop-color:#BBBBBB"
|
||||
id="stop12" /><stop
|
||||
offset="0.36265489"
|
||||
style="stop-color:#ffffff;stop-opacity:1;"
|
||||
id="stop14" /><stop
|
||||
offset="0.60322773"
|
||||
style="stop-color:#ffffff;stop-opacity:1;"
|
||||
id="stop16" /><stop
|
||||
offset="0.84976584"
|
||||
style="stop-color:#e9e9e9;stop-opacity:1;"
|
||||
id="stop18" /><stop
|
||||
offset="1"
|
||||
style="stop-color:#555555"
|
||||
id="stop20" /></linearGradient><linearGradient
|
||||
xlink:href="#XMLID_10_"
|
||||
id="linearGradient10168"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1.5628683,-3.9649986e-4,-3.9652886e-4,1.562754,-20.23462,-103.35726)"
|
||||
x1="61.9692"
|
||||
y1="88.430702"
|
||||
x2="40.815899"
|
||||
y2="88.430702" /><linearGradient
|
||||
xlink:href="#XMLID_10_"
|
||||
id="linearGradient10170"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1.0663866,2.7056218e-4,-2.7056218e-4,-1.0663866,9.2387294,154.26114)"
|
||||
x1="61.9692"
|
||||
y1="88.430702"
|
||||
x2="40.815899"
|
||||
y2="88.430702" /><linearGradient
|
||||
xlink:href="#XMLID_17_"
|
||||
id="linearGradient10203"
|
||||
x1="56.03125"
|
||||
y1="121.5"
|
||||
x2="72.031251"
|
||||
y2="121.5"
|
||||
gradientUnits="userSpaceOnUse" /><linearGradient
|
||||
xlink:href="#XMLID_17_"
|
||||
id="linearGradient10205"
|
||||
x1="56.03125"
|
||||
y1="110.5"
|
||||
x2="72.031251"
|
||||
y2="110.5"
|
||||
gradientUnits="userSpaceOnUse" /><linearGradient
|
||||
xlink:href="#XMLID_10_"
|
||||
id="linearGradient10219"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1.0663866,2.7056218e-4,-2.7056218e-4,-1.0663866,9.2387294,154.26114)"
|
||||
x1="61.9692"
|
||||
y1="88.430702"
|
||||
x2="40.815899"
|
||||
y2="88.430702" /><linearGradient
|
||||
xlink:href="#XMLID_17_"
|
||||
id="linearGradient10221"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="56.03125"
|
||||
y1="110.5"
|
||||
x2="72.031251"
|
||||
y2="110.5" /><linearGradient
|
||||
xlink:href="#XMLID_14_"
|
||||
id="linearGradient10223"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0.7074656,-0.7074656,0.7071068,0.7071068,-18.749172,36.539651)"
|
||||
x1="67.880402"
|
||||
y1="6.4331002"
|
||||
x2="105.3924"
|
||||
y2="43.945599" /><linearGradient
|
||||
xlink:href="#XMLID_15_"
|
||||
id="linearGradient10225"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0.7074656,-0.7074656,0.7071068,0.7071068,-24.895791,39.125846)"
|
||||
x1="76.919403"
|
||||
y1="43.1436"
|
||||
x2="100.6411"
|
||||
y2="19.4214" /><linearGradient
|
||||
xlink:href="#XMLID_16_"
|
||||
id="linearGradient10227"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0.7074656,-0.7074656,0.7071068,0.7071068,-24.895791,39.125846)"
|
||||
x1="76.281174"
|
||||
y1="43.060352"
|
||||
x2="86.611"
|
||||
y2="31.303225" /><linearGradient
|
||||
xlink:href="#XMLID_17_"
|
||||
id="linearGradient10229"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1.0002442,-2.5378062e-4,-2.5378062e-4,1.0002442,8.6347844,-24.632129)"
|
||||
x1="38.757801"
|
||||
y1="7.4277"
|
||||
x2="63.923302"
|
||||
y2="7.4277" /><linearGradient
|
||||
xlink:href="#XMLID_17_"
|
||||
id="linearGradient10231"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="56.03125"
|
||||
y1="121.5"
|
||||
x2="72.031251"
|
||||
y2="121.5" /><linearGradient
|
||||
xlink:href="#XMLID_18_"
|
||||
id="linearGradient10233"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0.7074656,-0.7074656,0.7071068,0.7071068,-18.749172,36.539651)"
|
||||
x1="-6.165"
|
||||
y1="112.5049"
|
||||
x2="11.0181"
|
||||
y2="112.5049" /><linearGradient
|
||||
xlink:href="#XMLID_18_"
|
||||
id="linearGradient10236"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1.0005075,0,0,1.0000001,6.1427973,2.5178157)"
|
||||
x1="-6.165"
|
||||
y1="112.5049"
|
||||
x2="11.0181"
|
||||
y2="112.5049" /><linearGradient
|
||||
xlink:href="#XMLID_17_"
|
||||
id="linearGradient10239"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="56.03125"
|
||||
y1="121.5"
|
||||
x2="72.031251"
|
||||
y2="121.5"
|
||||
gradientTransform="matrix(0.6632486,0.6632486,-0.6632486,0.6632486,45.21604,-10.083813)" /><linearGradient
|
||||
xlink:href="#XMLID_17_"
|
||||
id="linearGradient10242"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0.7074589,0.7071,-0.7074589,0.7071,68.761161,-21.373784)"
|
||||
x1="38.757801"
|
||||
y1="7.4277"
|
||||
x2="63.923302"
|
||||
y2="7.4277" /><linearGradient
|
||||
xlink:href="#XMLID_16_"
|
||||
id="linearGradient10245"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1.0005075,0,0,1.0000001,-3.223487e-2,2.1565487e-4)"
|
||||
x1="76.281174"
|
||||
y1="43.060352"
|
||||
x2="86.611"
|
||||
y2="31.303225" /><linearGradient
|
||||
xlink:href="#XMLID_15_"
|
||||
id="linearGradient10248"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1.0005075,0,0,1.0000001,-3.223487e-2,2.1565487e-4)"
|
||||
x1="76.919403"
|
||||
y1="43.1436"
|
||||
x2="100.6411"
|
||||
y2="19.4214" /><linearGradient
|
||||
xlink:href="#XMLID_14_"
|
||||
id="linearGradient10251"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1.0005075,0,0,1.0000001,6.1427973,2.5178157)"
|
||||
x1="67.880402"
|
||||
y1="6.4331002"
|
||||
x2="105.3924"
|
||||
y2="43.945599" /><linearGradient
|
||||
xlink:href="#XMLID_17_"
|
||||
id="linearGradient10254"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="56.03125"
|
||||
y1="110.5"
|
||||
x2="72.031251"
|
||||
y2="110.5"
|
||||
gradientTransform="matrix(0.6632486,0.6632486,-0.6632486,0.6632486,45.21604,-10.083813)" /><linearGradient
|
||||
xlink:href="#XMLID_10_"
|
||||
id="linearGradient10257"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0.7071,0.7074589,0.7071,-0.7074589,-50.969871,98.357246)"
|
||||
x1="61.9692"
|
||||
y1="88.430702"
|
||||
x2="40.815899"
|
||||
y2="88.430702" /><linearGradient
|
||||
xlink:href="#XMLID_17_"
|
||||
id="linearGradient10260"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0.7074589,0.7071,-0.7074589,0.7071,68.761161,-21.373784)"
|
||||
x1="51.339096"
|
||||
y1="30.159527"
|
||||
x2="51.326984"
|
||||
y2="-17.582758" /><linearGradient
|
||||
xlink:href="#linearGradient6141"
|
||||
id="linearGradient6147"
|
||||
x1="7.2493796"
|
||||
y1="120.2508"
|
||||
x2="57.625931"
|
||||
y2="69.874252"
|
||||
gradientUnits="userSpaceOnUse" /></defs><metadata
|
||||
id="metadata3"><ns:sfw><ns:slices /><ns:sliceSourceBounds
|
||||
y="3.97"
|
||||
x="4"
|
||||
height="120.061"
|
||||
width="119.999"
|
||||
bottomLeftOrigin="true" /></ns:sfw><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><linearGradient
|
||||
id="XMLID_11_"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="41.188999"
|
||||
y1="80.101601"
|
||||
x2="27.6451"
|
||||
y2="66.557701"
|
||||
gradientTransform="translate(8.7075442,3.5814057)"><stop
|
||||
offset="0.40830001"
|
||||
style="stop-color:#f0f0f0;stop-opacity:1;"
|
||||
id="stop25" /><stop
|
||||
offset="0.50628459"
|
||||
style="stop-color:#C3C3C3"
|
||||
id="stop27" /><stop
|
||||
offset="0.56008232"
|
||||
style="stop-color:#8b8b8b;stop-opacity:1;"
|
||||
id="stop29" /><stop
|
||||
id="stop12212"
|
||||
style="stop-color:#393939;stop-opacity:1;"
|
||||
offset="0.67944592" /></linearGradient><linearGradient
|
||||
id="XMLID_12_"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="1.3501"
|
||||
y1="103.2324"
|
||||
x2="8.1437998"
|
||||
y2="110.0261"
|
||||
gradientTransform="matrix(-1,0,0,-1,18.150444,221.94371)"><stop
|
||||
offset="0"
|
||||
style="stop-color:#EEEEEE"
|
||||
id="stop36" /><stop
|
||||
offset="1"
|
||||
style="stop-color:#BBBBBB"
|
||||
id="stop38" /></linearGradient><linearGradient
|
||||
id="XMLID_13_"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="-0.47799999"
|
||||
y1="105.0586"
|
||||
x2="6.3157001"
|
||||
y2="111.8523"
|
||||
gradientTransform="matrix(-1,0,0,-1,11.978544,219.42611)"><stop
|
||||
offset="0"
|
||||
style="stop-color:#BBBBBB"
|
||||
id="stop43" /><stop
|
||||
offset="1"
|
||||
style="stop-color:#555555"
|
||||
id="stop45" /></linearGradient><linearGradient
|
||||
id="XMLID_14_"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="67.880402"
|
||||
y1="6.4331002"
|
||||
x2="105.3924"
|
||||
y2="43.945599"
|
||||
gradientTransform="translate(6.1416442,2.5179057)"><stop
|
||||
offset="0.0947"
|
||||
style="stop-color:#666666;stop-opacity:1;"
|
||||
id="stop50" /><stop
|
||||
id="stop3330"
|
||||
style="stop-color:#b7b7b7;stop-opacity:1;"
|
||||
offset="0.40963161" /><stop
|
||||
offset="0.50530702"
|
||||
style="stop-color:#ffffff;stop-opacity:1;"
|
||||
id="stop56" /><stop
|
||||
id="stop3328"
|
||||
style="stop-color:#a3a3a3;stop-opacity:1;"
|
||||
offset="0.63043118" /><stop
|
||||
offset="0.93489999"
|
||||
style="stop-color:#646464;stop-opacity:1;"
|
||||
id="stop62" /></linearGradient><linearGradient
|
||||
id="XMLID_15_"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="76.919403"
|
||||
y1="43.1436"
|
||||
x2="100.6411"
|
||||
y2="19.4214"
|
||||
gradientTransform="translate(-3.0255781e-2,3.056501e-4)"><stop
|
||||
offset="0"
|
||||
style="stop-color:#aeaeae;stop-opacity:1;"
|
||||
id="stop67" /><stop
|
||||
offset="0.0602"
|
||||
style="stop-color:#b2b2b2;stop-opacity:1;"
|
||||
id="stop69" /><stop
|
||||
offset="0.18103883"
|
||||
style="stop-color:#d9d9d9;stop-opacity:1;"
|
||||
id="stop71" /><stop
|
||||
offset="0.3195"
|
||||
style="stop-color:#ffffff;stop-opacity:1;"
|
||||
id="stop73" /><stop
|
||||
offset="0.43529999"
|
||||
style="stop-color:#f4f4f4;stop-opacity:1;"
|
||||
id="stop75" /><stop
|
||||
offset="0.6028"
|
||||
style="stop-color:#D4D4D4"
|
||||
id="stop77" /><stop
|
||||
offset="0.7633"
|
||||
style="stop-color:#BBBBBB"
|
||||
id="stop79" /><stop
|
||||
offset="0.80790001"
|
||||
style="stop-color:#b9bcc1;stop-opacity:1;"
|
||||
id="stop81" /><stop
|
||||
offset="0.92970002"
|
||||
style="stop-color:#bdc0c3;stop-opacity:1;"
|
||||
id="stop83" /><stop
|
||||
offset="1"
|
||||
style="stop-color:#b5b9bb;stop-opacity:1;"
|
||||
id="stop85" /></linearGradient><linearGradient
|
||||
id="XMLID_17_"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="38.757801"
|
||||
y1="7.4277"
|
||||
x2="63.923302"
|
||||
y2="7.4277"
|
||||
gradientTransform="matrix(0.7071,0.7071,-0.7071,0.7071,68.728244,-21.373694)"><stop
|
||||
offset="0"
|
||||
style="stop-color:#3a3a3a;stop-opacity:1;"
|
||||
id="stop111" /><stop
|
||||
offset="0.60194176"
|
||||
style="stop-color:#ffffff;stop-opacity:1;"
|
||||
id="stop113" /><stop
|
||||
offset="1"
|
||||
style="stop-color:#888888"
|
||||
id="stop115" /></linearGradient><linearGradient
|
||||
id="XMLID_18_"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="-6.165"
|
||||
y1="112.5049"
|
||||
x2="11.0181"
|
||||
y2="112.5049"
|
||||
gradientTransform="translate(6.1416442,2.5179057)"><stop
|
||||
offset="0"
|
||||
style="stop-color:#BBBBBB"
|
||||
id="stop120" /><stop
|
||||
offset="1"
|
||||
style="stop-color:#000000"
|
||||
id="stop122" /></linearGradient><path
|
||||
style="fill:url(#linearGradient10257)"
|
||||
d="M 76.409451,31.804481 C 75.443086,30.924606 74.357677,30.748097 72.740857,32.364097 L 2.6023174,103.87059 C 2.5703077,103.90258 2.569153,103.93922 2.5401379,103.97422 C -0.95363288,107.73022 -0.88972357,113.60492 2.7681296,117.25992 C 6.4259848,120.91592 12.29792,120.98291 16.053828,117.48791 C 16.088848,117.45991 16.126446,117.45673 16.157461,117.42573 L 87.72613,47.307917 C 90.960771,44.076918 86.953486,42.972159 84.679332,40.696158 L 79.373343,35.390169 C 78.234266,34.251669 77.375816,32.684356 76.409451,31.804481 z M 16.053828,104.01567 C 18.984982,106.94689 18.986335,111.69819 16.053828,114.62765 C 13.124457,117.55553 8.3745634,117.55731 5.4418505,114.62765 C 2.5091357,111.69799 2.5109065,106.94669 5.4418505,104.01567 C 8.3727812,101.08623 13.121121,101.086 16.053828,104.01567 z "
|
||||
id="path10166" /><path
|
||||
style="fill:url(#linearGradient10254)"
|
||||
d="M 17.380325,102.68918 C 14.447618,99.759506 9.6992784,99.759734 6.7683477,102.68918 C 6.5428061,102.91472 6.3373933,103.14849 6.1465522,103.39388 C 9.0905761,101.11028 13.348454,101.3131 16.053828,104.01567 C 18.759425,106.72134 18.965866,110.98021 16.675624,113.92295 C 16.920967,113.73233 17.15491,113.52645 17.380325,113.30115 C 20.312833,110.3717 20.31148,105.6204 17.380325,102.68918 z "
|
||||
id="path10186" /><path
|
||||
d="M 118.731,16.620217 L 103.19512,32.150217 C 99.80841,35.534217 93.549236,33.235217 90.160517,29.850217 C 86.776801,26.465216 84.475632,20.211217 87.86035,16.825217 L 103.40023,1.2962158 C 92.523716,-2.8617843 79.067889,3.9452161 74.82974,8.1812158 C 63.636062,19.367217 62.837657,34.722217 74.065352,45.942217 C 85.286043,57.160217 100.64883,56.490217 111.84151,45.303218 C 116.07966,41.066217 122.89111,27.489217 118.731,16.620217 z "
|
||||
id="path64"
|
||||
style="fill:url(#linearGradient10251)" /><path
|
||||
d="M 77.659175,11.009216 C 72.74068,15.925216 70.031306,21.867217 70.031306,27.741217 L 70.030305,27.742217 C 70.031306,33.311217 72.404509,38.626217 76.894787,43.113217 C 81.422083,47.639217 86.7778,50.031218 92.381644,50.031217 L 92.381644,50.031217 C 98.22961,50.031218 104.1346,47.348218 109.01107,42.474217 C 111.8225,39.664217 115.5714,32.245217 115.93458,25.071217 C 111.35626,29.647217 106.02356,34.977217 106.02356,34.977217 C 100.96199,40.037217 92.379643,37.724217 87.331082,32.680217 C 84.259524,29.607217 82.198478,25.228217 82.198477,21.129217 C 82.199478,18.494217 83.050911,15.977216 85.028913,13.997217 C 85.029914,13.996217 90.524701,8.5042158 94.982962,4.049216 C 87.471153,4.3972165 80.050388,8.6212158 77.659175,11.009216 z "
|
||||
id="path87"
|
||||
style="fill:url(#linearGradient10248);fill-opacity:1.0" /><path
|
||||
d="M 85.387095,16.818216 C 81.792271,20.411216 84.661727,27.477217 88.592721,31.406217 C 92.527718,35.337217 99.594302,38.205217 103.19012,34.613217 C 103.19012,34.613217 118.96412,17.234216 118.73,16.620217 L 103.19412,32.150217 C 99.80741,35.534218 93.548235,33.235217 90.159515,29.850217 C 86.775799,26.465216 84.474633,20.211217 87.85935,16.825217 L 103.39923,1.2962158 L 85.387095,16.818216 z "
|
||||
id="path117"
|
||||
style="fill:url(#linearGradient10260)" /><path
|
||||
style="fill:url(#linearGradient10239)"
|
||||
d="M 4.820055,104.72037 C 4.5726725,104.91226 4.3425471,105.11509 4.1153533,105.34217 C 1.1844093,108.27319 1.1826385,113.02448 4.1153533,115.95415 C 7.0480662,118.88381 11.79796,118.88203 14.727331,115.95415 C 14.952993,115.72872 15.158195,115.49477 15.349126,115.24945 C 12.406133,117.53606 8.148893,117.33187 5.4418505,114.62765 C 2.734806,111.92343 2.5308464,107.66392 4.820055,104.72037 z "
|
||||
id="path10190" /><path
|
||||
d="M 2.7691837,116.26123 C 1.0242958,114.51723 0.10383016,112.26923 -0.011225313,109.98622 C -0.15229878,112.60123 0.76716563,115.26323 2.7691816,117.26123 C 6.4270358,120.91723 12.302016,120.99023 16.057925,117.49522 C 16.092943,117.46722 16.134962,117.44623 16.165977,117.41523 L 17.166488,116.41622 C 13.410582,119.90922 6.427038,119.91723 2.7691837,116.26123 z "
|
||||
id="path124"
|
||||
style="fill:url(#linearGradient10236)" /><path
|
||||
style="fill:url(#linearGradient6147);fill-opacity:1.0;fill-rule:nonzero;opacity:0.5"
|
||||
d="M 43.21875 69.5625 L 2.8125 110.75 C 2.7783735 110.78411 2.7809338 110.80644 2.75 110.84375 C -0.97480066 114.84812 -0.93098327 121.10331 2.96875 125 C 6.8684854 128.89776 13.151978 128.97611 17.15625 125.25 C 17.193586 125.22015 17.216934 125.22055 17.25 125.1875 L 57.3125 85.9375 C 53.66929 79.550692 48.861858 73.979783 43.21875 69.5625 z M 11.5 108.5625 C 13.547005 108.56247 15.592932 109.34455 17.15625 110.90625 C 20.281231 114.0313 20.282673 119.09558 17.15625 122.21875 C 14.033171 125.34024 8.9703922 125.34214 5.84375 122.21875 C 2.7171056 119.09537 2.7189937 114.03109 5.84375 110.90625 C 7.406121 109.34467 9.4529953 108.56253 11.5 108.5625 z "
|
||||
id="path2236"
|
||||
transform="matrix(0.9379752,0,0,0.9379752,-3.0914126e-2,0)" /><path
|
||||
d="M 15.537923,96.062539 C 14.713663,96.978699 14.388162,98.22441 14.530816,99.52305 C 14.682682,100.89492 15.361817,102.33182 16.533505,103.50003 C 18.808146,105.77775 22.084895,106.19602 23.991153,104.45332 C 24.002148,104.47231 67.492075,61.01118 77.726913,50.782827 C 77.294045,50.416137 69.737251,42.875414 69.288707,42.347047 C 66.356033,45.277862 15.537923,96.062539 15.537923,96.062539 z "
|
||||
id="path33"
|
||||
style="fill:url(#linearGradient9291);opacity:0.5" /><path
|
||||
style="fill:url(#linearGradient3301)"
|
||||
id="path108"
|
||||
d="M 94.304331,48.937972 C 88.699488,48.937972 83.344772,46.545972 78.817475,42.019972 C 74.327198,37.532972 71.953994,32.217972 71.952993,26.648972 L 71.953994,26.647972 C 71.953994,20.773972 74.663368,14.831973 79.581863,9.9159745 C 80.407282,9.0909731 81.84201,8.0489732 83.661933,7.0219732 C 80.923544,8.3389732 78.759446,9.8199731 77.661889,10.915974 C 72.743394,15.831973 70.034021,21.773972 70.034021,27.647972 L 70.033021,27.648972 C 70.034021,33.217972 72.407224,38.532972 76.897502,43.019972 C 81.424798,47.545972 86.780515,49.937972 92.384357,49.937972 C 96.062223,49.937972 99.7631,48.874972 103.21585,46.874972 C 100.32739,48.224972 97.308855,48.937972 94.304331,48.937972 z " /></svg>
|
After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 505 KiB After Width: | Height: | Size: 505 KiB |
@ -89,23 +89,82 @@
|
||||
x2="76.100182"
|
||||
y2="249"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient
|
||||
id="Main-61">
|
||||
<stop
|
||||
style="stop-color:#4d4d4d;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop2082-1" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
xlink:href="#Main-6"
|
||||
id="linearGradient2098"
|
||||
x1="52.082603"
|
||||
y1="61.780205"
|
||||
x2="53.181248"
|
||||
y2="61.780205"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(3.7795278,0,0,3.7795278,-189.44141,-219.41797)" />
|
||||
<linearGradient
|
||||
xlink:href="#Main-6"
|
||||
id="linearGradient2096"
|
||||
x1="50.281166"
|
||||
y1="62.838539"
|
||||
x2="52.926998"
|
||||
y2="62.838539"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient
|
||||
xlink:href="#Main-6"
|
||||
id="linearGradient2098-8"
|
||||
x1="52.082603"
|
||||
y1="61.780205"
|
||||
x2="53.181248"
|
||||
y2="61.780205"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient
|
||||
xlink:href="#Main-6"
|
||||
id="linearGradient2096-5"
|
||||
x1="50.286324"
|
||||
y1="62.972603"
|
||||
x2="52.024643"
|
||||
y2="62.967445"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(-0.27492098,-0.52917219)" />
|
||||
<linearGradient
|
||||
xlink:href="#Main-6"
|
||||
id="linearGradient2098-4"
|
||||
x1="52.082603"
|
||||
y1="61.780205"
|
||||
x2="53.181248"
|
||||
y2="61.780205"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(-1.3228271,-0.79375592)" />
|
||||
<linearGradient
|
||||
xlink:href="#Main-6"
|
||||
id="linearGradient2092"
|
||||
x1="45.243748"
|
||||
y1="62.838539"
|
||||
x2="47.88958"
|
||||
y2="62.838539"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(-0.26458362)" />
|
||||
<linearGradient
|
||||
xlink:href="#Main-6"
|
||||
id="linearGradient2094"
|
||||
x1="44.989498"
|
||||
y1="61.780205"
|
||||
x2="46.088139"
|
||||
y2="61.780205"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(0.79373763)" />
|
||||
</defs>
|
||||
<g
|
||||
id="menu-reload"
|
||||
transform="matrix(3.7795278,0,0,3.7795278,-67.999997,-247.99997)"
|
||||
transform="matrix(3.7795278,0,0,3.7795278,-187.99999,-227.99997)"
|
||||
style="display:inline">
|
||||
<rect
|
||||
style="opacity:0;fill:#a51d2d;stroke-width:0.264583px;paint-order:fill markers stroke;stop-color:#000000"
|
||||
id="rect1553"
|
||||
width="4.2333331"
|
||||
height="4.2333331"
|
||||
x="17.991665"
|
||||
y="65.616653"
|
||||
rx="1.9824198e-15" />
|
||||
<path
|
||||
id="circle2129"
|
||||
style="font-variation-settings:normal;opacity:1;vector-effect:none;fill:url(#linearGradient2146);fill-opacity:1;stroke:none;stroke-width:0.999999px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;stop-color:#000000;stop-opacity:1"
|
||||
d="m 69.046875,241.90039 a 7.0999998,7.0999998 0 0 0 -3.628906,0.9707 l 3.210937,3.21094 0.800782,-0.80273 -1.892579,-1.89258 A 5.7999999,5.7999999 0 0 1 69,243.19922 5.7999999,5.7999999 0 0 1 74.800781,249 a 5.7999999,5.7999999 0 0 1 -0.697265,2.75586 l 1.144531,0.61719 a 7.0999998,7.0999998 0 0 0 -1.316406,-8.48047 7.0999998,7.0999998 0 0 0 -4.884766,-1.99219 z m -6.294922,3.72656 a 7.0999998,7.0999998 0 0 0 1.316406,8.48047 7.0999998,7.0999998 0 0 0 4.884766,1.99219 7.0999998,7.0999998 0 0 0 3.628906,-0.9707 l -3.210937,-3.21094 -0.800782,0.80273 1.892579,1.89258 A 5.7999999,5.7999999 0 0 1 69,254.80078 5.7999999,5.7999999 0 0 1 63.199219,249 a 5.7999999,5.7999999 0 0 1 0.697265,-2.75586 z"
|
||||
transform="matrix(0.26458333,0,0,0.26458333,1.8520833,1.8520833)" />
|
||||
d="m 52.8211,61.122306 -2.004035,-0.0036 0.217103,-0.201119 c 0.158097,-0.150209 -0.07905,-0.375524 -0.237144,-0.225315 l -0.50101,0.476016 c -0.06587,0.06207 -0.06587,0.163247 0,0.225315 l 0.50101,0.476016 c 0.06533,0.06258 0.171817,0.06258 0.237144,0 0.06587,-0.06207 0.06585,-0.163246 -1.8e-5,-0.225315 L 50.817065,61.383321 H 52.8211 c 0.143894,-0.0027 0.357404,0.162378 0.360144,0.299094 v 0.391921 c 0,0.211563 0.264583,0.211563 0.264583,0 v -0.391921 c -0.0028,-0.311974 -0.296373,-0.566316 -0.624727,-0.563677 z m 0.07432,1.891352 c -0.158096,-0.149435 -0.394425,0.07511 -0.237143,0.225315 l 0.2171,0.261015 h -2.004033 c -0.143895,0.0027 -0.333193,-0.162379 -0.335933,-0.299097 v -0.39192 c -5.38e-4,-0.21105 -0.264046,-0.21105 -0.264584,0 v 0.39192 c 0.0028,0.311975 0.272162,0.56632 0.600517,0.56368 h 2.004035 l -0.217101,0.20112 c -0.06587,0.06207 -0.06587,0.163245 0,0.225314 0.06533,0.06258 0.171816,0.06258 0.237144,0 l 0.501007,-0.476016 c 0.06587,-0.06207 0.06587,-0.163245 0,-0.225314 z"
|
||||
id="path1"
|
||||
style="display:inline;overflow:hidden;fill:#4d4d4d;fill-opacity:1;stroke-width:0.00381524" />
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 8.1 KiB |
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user