1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2026-01-04 19:43:13 +00:00

Merge branch openmw:master into master

This commit is contained in:
Igilq 2025-07-26 08:02:24 +00:00
commit 0d6c9c2303
543 changed files with 42338 additions and 7765 deletions

View file

@ -13,3 +13,7 @@ HeaderFilterRegex: '(apps|components)/'
CheckOptions:
- key: readability-identifier-naming.ConceptCase
value: CamelCase
- key: readability-identifier-naming.NamespaceCase
value: CamelCase
- key: readability-identifier-naming.NamespaceIgnoredRegexp
value: 'osg(DB|FX|Particle|Shadow|Viewer|Util)?'

View file

@ -73,7 +73,7 @@ jobs:
- uses: actions/checkout@v4
- name: Install Building Dependencies
run: CI/before_install.osx.sh
run: CI/before_install.macos.sh
- name: Prime ccache
uses: hendrikmuhs/ccache-action@v1
@ -82,11 +82,9 @@ jobs:
max-size: 1000M
- name: Configure
run: CI/before_script.osx.sh
run: CI/before_script.macos.sh
- name: Build
run: |
cd build
make -j $(sysctl -n hw.logicalcpu) package
run: CI/macos/build.sh
Output-Envs:
name: Read .env file and expose it as output
@ -106,7 +104,6 @@ jobs:
fail-fast: true
matrix:
image:
- "2019"
- "2022"
uses: ./.github/workflows/windows.yml

View file

@ -4,9 +4,13 @@ on:
workflow_call:
inputs:
image:
description: MSVC image (2019/2022)
description: Window Server image
required: true
type: string
msvc:
description: MSVC version (2019/2022)
default: "2022"
type: string
vcpkg-deps-tag:
description: Git tag of our deps
required: true
@ -45,7 +49,7 @@ jobs:
- name: Download prebuilt vcpkg packages
working-directory: ${{ github.workspace }}/deps
run: |
$MANIFEST = "vcpkg-x64-${{ inputs.image }}-${{ inputs.vcpkg-deps-tag }}.txt"
$MANIFEST = "vcpkg-x64-${{ inputs.msvc }}-${{ inputs.vcpkg-deps-tag }}.txt"
curl --fail --retry 3 -L -o "$MANIFEST" "https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/$MANIFEST"
$lines = Get-Content "$MANIFEST"
$URL = $lines[0]
@ -61,7 +65,7 @@ jobs:
- name: Extract archived prebuilt vcpkg packages
working-directory: ${{ github.workspace }}/deps
run: 7z x -y -ovcpkg-x64-${{ inputs.image }}-${{ inputs.vcpkg-deps-tag }} $env:archive
run: 7z x -y -ovcpkg-x64-${{ inputs.msvc }}-${{ inputs.vcpkg-deps-tag }} $env:archive
- name: Cache Qt
id: qt-cache
@ -94,12 +98,12 @@ jobs:
-B ${{ github.workspace }}/build
-G Ninja
-D CMAKE_BUILD_TYPE=${{ inputs.build-type }}
-D CMAKE_TOOLCHAIN_FILE='${{ github.workspace }}/deps/vcpkg-x64-${{ inputs.image }}-${{ inputs.vcpkg-deps-tag }}/scripts/buildsystems/vcpkg.cmake'
-D CMAKE_TOOLCHAIN_FILE='${{ github.workspace }}/deps/vcpkg-x64-${{ inputs.msvc }}-${{ inputs.vcpkg-deps-tag }}/scripts/buildsystems/vcpkg.cmake'
-D CMAKE_PREFIX_PATH='${{ github.workspace }}/deps/Qt/6.6.3/msvc2019_64'
${{ inputs.package && '-D CMAKE_CXX_FLAGS_RELEASE="/O2 /Ob2 /DNDEBUG /Zi"' || '' }}
${{ inputs.package && '-D "CMAKE_EXE_LINKER_FLAGS_RELEASE=/DEBUG /INCREMENTAL:NO"' || '' }}
-D LuaJit_INCLUDE_DIR='${{ github.workspace }}/deps/vcpkg-x64-${{ inputs.image }}-${{ inputs.vcpkg-deps-tag }}/installed/x64-windows/include/luajit'
-D LuaJit_LIBRARY='${{ github.workspace }}/deps/vcpkg-x64-${{ inputs.image }}-${{ inputs.vcpkg-deps-tag }}/installed/x64-windows/lib/lua51.lib'
-D LuaJit_INCLUDE_DIR='${{ github.workspace }}/deps/vcpkg-x64-${{ inputs.msvc }}-${{ inputs.vcpkg-deps-tag }}/installed/x64-windows/include/luajit'
-D LuaJit_LIBRARY='${{ github.workspace }}/deps/vcpkg-x64-${{ inputs.msvc }}-${{ inputs.vcpkg-deps-tag }}/installed/x64-windows/lib/lua51.lib'
-D BUILD_BENCHMARKS=${{ ! inputs.package }}
-D BUILD_COMPONENTS_TESTS=${{ ! inputs.package }}
-D BUILD_OPENMW_TESTS=${{ ! inputs.package }}
@ -114,9 +118,9 @@ jobs:
- name: Copy missing DLLs
run: |
cp ${{ github.workspace }}/deps/vcpkg-x64-${{ inputs.image }}-${{ inputs.vcpkg-deps-tag }}/installed/x64-windows/bin/Release/MyGUIEngine.dll ${{ github.workspace }}/build
cp -Filter *.dll -Recurse ${{ github.workspace }}/deps/vcpkg-x64-${{ inputs.image }}-${{ inputs.vcpkg-deps-tag }}/installed/x64-windows/bin/osgPlugins-3.6.5 ${{ github.workspace }}/build
cp ${{ github.workspace }}/deps/vcpkg-x64-${{ inputs.image }}-${{ inputs.vcpkg-deps-tag }}/installed/x64-windows/bin/*.dll ${{ github.workspace }}/build
cp ${{ github.workspace }}/deps/vcpkg-x64-${{ inputs.msvc }}-${{ inputs.vcpkg-deps-tag }}/installed/x64-windows/bin/Release/MyGUIEngine.dll ${{ github.workspace }}/build
cp -Filter *.dll -Recurse ${{ github.workspace }}/deps/vcpkg-x64-${{ inputs.msvc }}-${{ inputs.vcpkg-deps-tag }}/installed/x64-windows/bin/osgPlugins-3.6.5 ${{ github.workspace }}/build
cp ${{ github.workspace }}/deps/vcpkg-x64-${{ inputs.msvc }}-${{ inputs.vcpkg-deps-tag }}/installed/x64-windows/bin/*.dll ${{ github.workspace }}/build
- name: Copy Qt DLLs
working-directory: ${{ github.workspace }}/deps/Qt/6.6.3/msvc2019_64
@ -172,7 +176,7 @@ jobs:
env:
GH_TOKEN: ${{ github.token }}
run: |
job_url=$(gh run --repo ${{ github.repository }} view ${{ github.run_id }} --json jobs --jq '.jobs[] | select(.name == "windows-${{ inputs.image }}") | .url')
job_url=$(gh run --repo ${{ github.repository }} view ${{ github.run_id }} --json jobs --jq '.jobs[] | select(.name == "windows-${{ inputs.msvc }}") | .url')
printf "Ref ${{ github.ref }}\nJob ${job_url}\nCommit ${{ github.sha }}\n" > install/CI-ID.txt
cp install/CI-ID.txt pdb/CI-ID.txt
cp install/CI-ID.txt SymStore/CI-ID.txt
@ -180,19 +184,19 @@ jobs:
- name: Store OpenMW archived pdb files
uses: actions/upload-artifact@v4
with:
name: openmw-windows-${{ inputs.image }}-pdb-${{ github.sha }}
name: openmw-windows-${{ inputs.msvc }}-pdb-${{ github.sha }}
path: ${{ github.workspace }}/pdb/*
- name: Store OpenMW build artifacts
uses: actions/upload-artifact@v4
with:
name: openmw-windows-${{ inputs.image }}-${{ github.sha }}
name: openmw-windows-${{ inputs.msvc }}-${{ github.sha }}
path: ${{ github.workspace }}/install/*
- name: Store symbol server artifacts
uses: actions/upload-artifact@v4
with:
name: openmw-windows-${{ inputs.image }}-sym-store-${{ github.sha }}
name: openmw-windows-${{ inputs.msvc }}-sym-store-${{ github.sha }}
path: ${{ github.workspace }}/SymStore/*
- name: Upload to symbol server

View file

@ -46,6 +46,9 @@ Ubuntu_GCC_preprocess:
- pip3 install --user click termtables
script:
- CI/ubuntu_gcc_preprocess.sh
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_PIPELINE_SOURCE == "push" && $CI_PROJECT_ID != "7107382"
.Ubuntu:
extends: .Ubuntu_Image
@ -77,7 +80,7 @@ Ubuntu_GCC_preprocess:
- if [[ "${BUILD_TESTS_ONLY}" && ! "${BUILD_WITH_CODE_COVERAGE}" ]]; then ./openmw_detournavigator_navmeshtilescache_benchmark; fi
- if [[ "${BUILD_TESTS_ONLY}" && ! "${BUILD_WITH_CODE_COVERAGE}" ]]; then ./openmw_esm_refid_benchmark; fi
- if [[ "${BUILD_TESTS_ONLY}" && ! "${BUILD_WITH_CODE_COVERAGE}" ]]; then ./openmw_settings_access_benchmark; fi
- ccache -s
- ccache -svv
- df -h
- if [[ "${BUILD_WITH_CODE_COVERAGE}" ]]; then gcovr --xml-pretty --exclude-unreachable-branches --print-summary --root "${CI_PROJECT_DIR}" -j $(nproc) -o ../coverage.xml; fi
- ls | grep -v -e '^extern$' -e '^install$' -e '^components-tests.xml$' -e '^openmw-tests.xml$' -e '^openmw-cs-tests.xml$' | xargs -I '{}' rm -rf './{}'
@ -124,7 +127,7 @@ Coverity:
- cov-analysis-linux64-*/bin/cov-configure --template --comptype prefix --compiler ccache
# Remove the specific targets and build everything once we can do it under 3h
- cov-analysis-linux64-*/bin/cov-build --dir cov-int cmake --build build -- -j $(nproc)
- ccache -s
- ccache -svv
after_script:
- tar cfz cov-int.tar.gz cov-int
- curl https://scan.coverity.com/builds?project=$COVERITY_SCAN_PROJECT_NAME
@ -268,6 +271,9 @@ Ubuntu_GCC_tests_asan:
when: always
reports:
junit: build/*-tests.xml
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_PIPELINE_SOURCE == "push" && $CI_PROJECT_ID != "7107382"
Ubuntu_GCC_tests_ubsan:
extends: Ubuntu_GCC
@ -285,6 +291,9 @@ Ubuntu_GCC_tests_ubsan:
when: always
reports:
junit: build/*-tests.xml
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_PIPELINE_SOURCE == "push" && $CI_PROJECT_ID != "7107382"
.Ubuntu_GCC_tests_tsan:
extends: Ubuntu_GCC
@ -322,6 +331,9 @@ Ubuntu_GCC_tests_coverage:
coverage_format: cobertura
path: coverage.xml
junit: build/*-tests.xml
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_PIPELINE_SOURCE == "push" && $CI_PROJECT_ID != "7107382"
.Ubuntu_Static_Deps:
extends: Ubuntu_Clang
@ -396,12 +408,15 @@ Ubuntu_Clang:
- cd build
- find . -name *.o -exec touch {} \;
- cmake --build . -- -j $(nproc) ${BUILD_TARGETS}
- ccache -s
- ccache -svv
artifacts:
paths:
- build/
expire_in: 12h
timeout: 3h
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_PIPELINE_SOURCE == "push" && $CI_PROJECT_ID != "7107382"
Ubuntu_Clang_Tidy_components:
extends: .Ubuntu_Clang_Tidy_Base
@ -414,7 +429,7 @@ Ubuntu_Clang_Tidy_openmw:
needs:
- Ubuntu_Clang_Tidy_components
variables:
BUILD_TARGETS: openmw
BUILD_TARGETS: openmw openmw-tests
timeout: 3h
Ubuntu_Clang_Tidy_openmw-cs:
@ -430,7 +445,7 @@ Ubuntu_Clang_Tidy_other:
needs:
- Ubuntu_Clang_Tidy_components
variables:
BUILD_TARGETS: bsatool esmtool openmw-launcher openmw-iniimporter openmw-essimporter openmw-wizard niftest components-tests openmw-tests openmw-cs-tests openmw-navmeshtool openmw-bulletobjecttool
BUILD_TARGETS: components-tests bsatool esmtool openmw-launcher openmw-iniimporter openmw-essimporter openmw-wizard niftest openmw-navmeshtool openmw-bulletobjecttool
timeout: 3h
.Ubuntu_Clang_tests:
@ -467,7 +482,7 @@ Ubuntu_Clang_tests_Debug:
stage: test
variables:
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
EXAMPLE_SUITE_REVISION: f51b832e033429a7cdc520e0e48d7dfdb9141caa
EXAMPLE_SUITE_REVISION: 599987b52bd2d064e26299d3317b11049499f32b
cache:
paths:
- .cache/pip
@ -502,19 +517,27 @@ Ubuntu_GCC_integration_tests_asan:
.MacOS:
stage: build
rules:
- if: $CI_PROJECT_ID == "7107382"
- if: $CI_PROJECT_ID != "7107382"
when: never
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
when: manual
- if: $CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "schedule"
image: macos-15-xcode-16
tags:
- saas-macos-medium-m1
cache:
paths:
- ccache/
script:
- CI/before_install.osx.sh
- CI/before_install.macos.sh
- export CCACHE_BASEDIR="$(pwd)"
- export CCACHE_DIR="$(pwd)/ccache"
- mkdir -pv "${CCACHE_DIR}"
- ccache -z -M "${CCACHE_SIZE}"
- CI/before_script.osx.sh
- cd build; make -j $(sysctl -n hw.logicalcpu) package
- for dmg in *.dmg; do mv "$dmg" "${dmg%.dmg}_${CI_COMMIT_REF_NAME##*/}.dmg"; done
- CI/macos/ccache_prep.sh
- CI/before_script.macos.sh
- CI/macos/build.sh
- cd build
- for dmg in *.dmg; do mv "$dmg" "${dmg%.dmg}_${DMG_IDENTIFIER}_${CI_COMMIT_REF_NAME##*/}.dmg"; done
- |
if [[ -n "${AWS_ACCESS_KEY_ID}" ]]; then
echo "[default]" > ~/.s3cfg
@ -529,20 +552,33 @@ Ubuntu_GCC_integration_tests_asan:
s3cmd put "${dmg}" s3://openmw-artifacts/${artifactDirectory}
done
fi
- ccache -s
- ../CI/macos/ccache_save.sh
artifacts:
paths:
- build/OpenMW-*.dmg
macOS14_Xcode15_arm64:
macOS15_Xcode16_amd64:
extends: .MacOS
image: macos-14-xcode-15
tags:
- saas-macos-medium-m1
cache:
key: macOS14_Xcode15_arm64.v1
key: macOS15_Xcode16_amd64.v1
variables:
CCACHE_SIZE: 3G
DMG_IDENTIFIER: amd64
MACOS_AMD64: true
HOMEBREW_NO_AUTO_UPDATE: 1
HOMEBREW_NO_EMOJI: true
HOMEBREW_NO_INSTALL_CLEANUP: true
macOS15_Xcode16_arm64:
extends: .MacOS
cache:
key: macOS15_Xcode16_arm64.v1
variables:
DMG_IDENTIFIER: arm64
CCACHE_SIZE: 3G
HOMEBREW_NO_AUTO_UPDATE: 1
HOMEBREW_NO_EMOJI: true
HOMEBREW_NO_INSTALL_CLEANUP: true
.Compress_And_Upload_Symbols_Base:
extends: .Ubuntu_Image
@ -667,7 +703,7 @@ macOS14_Xcode15_arm64:
- Get-Volume
- Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log
cache:
key: ninja-2022-v12
key: ninja-2022-v13
paths:
- ccache
- deps
@ -825,7 +861,7 @@ macOS14_Xcode15_arm64:
- Get-Volume
- Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log
cache:
key: msbuild-2022-v12
key: msbuild-2022-v13
paths:
- deps
- MSVC2022_64/deps/Qt
@ -926,7 +962,7 @@ Windows_MSBuild_CacheInit:
- cd build
- cmake --build . -- -j $(nproc)
# - cmake --install . # no one uses builds anyway, disable until 'no space left' is resolved
- ccache -s
- ccache -svv
- df -h
- ls | grep -v -e '^extern$' -e '^install$' | xargs -I '{}' rm -rf './{}'
- cd ..
@ -968,7 +1004,7 @@ Windows_MSBuild_CacheInit:
- flatpak build-bundle ./repo openmw.flatpak org.openmw.OpenMW.devel
cache:
key: flatpak
paths:
paths:
- ".flatpak-builder"
artifacts:
untracked: false

View file

@ -10,4 +10,4 @@ python:
build:
os: ubuntu-22.04
tools:
python: "3.8"
python: "3.9"

View file

@ -10,7 +10,8 @@ If you feel your name is missing from this list, please add it to `AUTHORS.md`.
Programmers
-----------
Bret Curtis (psi29a) - Project leader 2019-present
Alexey Dobrokhotov (Capo) - Project leader 2025-present
Bret Curtis (psi29a) - Project leader 2019-2025
Marc Zinnschlag (Zini) - Project leader 2010-2018
Nicolay Korslund - Project leader 2008-2010
scrawl - Top contributor
@ -49,10 +50,10 @@ Programmers
Berulacks
Bo Svensson
Britt Mathis (galdor557)
Capostrophic
Carl Maxwell
cc9cii
Cédric Mocquillon
Charles Horn
Chris Boyce (slothlife)
Chris Robinson (KittyCat)
Chris Vigil
@ -196,7 +197,7 @@ Programmers
Qlonever
Radu-Marius Popovici (rpopovici)
Rafael Moura (dhustkoder)
Randy Davin (Kindi)
Randy Davin (Kuyondo)
rdimesio
rexelion
riothamus

View file

@ -1,3 +1,65 @@
0.50.0
------
Bug #2967: Inventory windows don't update when changing items by script
Bug #5331: Pathfinding works incorrectly when actor is moved from one interior cell to another
Bug #6039: Next Spell keybind fails while selected enchanted item has multiple copies
Bug #6573: Editor: Selection behaves incorrectly on high-DPI displays
Bug #6792: Birth sign info box has no line breaks
Bug #7371: Equipping item from inventory does not play a Down sound when equipping fails
Bug #7622: Player's marksman weapons don't work on close actors underwater
Bug #7649: The sound and vfx of resisted enchanted items' magic still play
Bug #7740: Magic items in the HUD aren't composited correctly
Bug #7799: Picking up ingredients while object paging active grid is on may cause a hiccup
Bug #7871: Kwama Queen doesn't start combat with player
Bug #8245: The console command ShowVars does not list global mwscripts
Bug #8265: Topics are linked incorrectly
Bug #8303: On target spells cast by non-actors should fire underwater
Bug #8318: Missing global variables are not handled gracefully in dialogue conditions
Bug #8333: Quest status subrecords should not actually cause parsing to skip remaining data
Bug #8340: Multi-effect enchantments are too expensive
Bug #8341: Repeat shader visitor passes discard parallax
Bug #8349: Travel to non-existent cell causes persistent black screen
Bug #8359: Some quick keys menu related issues
Bug #8371: Silence affects powers
Bug #8375: Moon phase cycle doesn't match Morrowind
Bug #8383: Casting bound helm or boots on beast races doesn't cleanup properly
Bug #8385: Russian encoding broken with locale parameters and calendar
Bug #8408: OpenMW doesn't report all the potential resting hindrances
Bug #8414: Waterwalking works when collision is disabled
Bug #8431: Behaviour of removed items from a container is buggy
Bug #8432: Changing to and from an interior cell doesn't update collision
Bug #8436: Spell selection in a pinned spellbook window doesn't update
Bug #8437: Pinned inventory window's pin button doesn't look pressed
Bug #8446: Travel prices are strangely inconsistent
Bug #8459: Changing magic effect base cost doesn't change spell price
Bug #8466: Showmap "" reveals nameless cells
Bug #8485: Witchwither disease and probably other common diseases don't work correctly
Bug #8490: Normals on Water disappear when Water Shader is Enabled but Refraction is Disabled
Bug #8500: OpenMW Alarm behaviour doesn't match morrowind.exe
Bug #8519: Multiple bounty is sometimes assigned to player when detected during a pickpocketing action
Bug #8585: Dialogue topic list doesn't have enough padding
Bug #8587: Minor INI importer problems
Bug #8593: Render targets do not generate mipmaps
Bug #8598: Post processing shaders don't interact with the vfs correctly
Bug #8599: Non-ASCII paths in BSA files don't work
Bug #8609: The crosshair is too large
Bug #8610: Terrain normal maps using NormalGL format instead of NormalDX
Bug #8612: Using aiactivate on an ingredient when graphical herbalism is enabled triggers non-stop pickup sounds
Bug #8615: Rest/wait time progress speed is different from vanilla
Feature #2522: Support quick item transfer
Feature #3769: Allow GetSpellEffects on enchantments
Feature #8112: Expose landscape record data to Lua
Feature #8113: Support extended selection in autodetected subdirectory dialog
Feature #8139: Editor: Redesign the selection markers
Feature #8285: Expose list of active shaders in postprocessing API
Feature #8313: Show the character name in the savegame details
Feature #8320: Add access mwscript source text to lua api
Feature #8334: Lua: AddTopic equivalent
Feature #8355: Lua: Window visibility checking in interfaces.UI
Feature #8580: Sort characters in the save loading menu
Feature #8597: Lua: Add more built-in event handlers
0.49.0
------
@ -236,6 +298,8 @@
Bug #8465: Blue screen w/ antialiasing and post-processing on macOS
Bug #8503: Camera does not handle NaN gracefully
Bug #8541: Lua: util.color:asHex produces wrong output for some colors
Bug #8567: Token replacement does not work via CLI and relative paths passed via the command line are not relative to the CWD
Bug #8576: Crash on exit when unresolving containers with scripted items
Feature #1415: Infinite fall failsafe
Feature #2566: Handle NAM9 records for manual cell references
Feature #3501: OpenMW-CS: Instance Editing - Shortcuts for axial locking

7
CI/before_install.macos.sh Executable file
View file

@ -0,0 +1,7 @@
#!/bin/sh -ex
if [[ "${MACOS_AMD64}" ]]; then
./CI/macos/before_install.amd64.sh
else
./CI/macos/before_install.arm64.sh
fi

View file

@ -1,30 +0,0 @@
#!/bin/sh -ex
export HOMEBREW_NO_EMOJI=1
export HOMEBREW_NO_INSTALL_CLEANUP=1
export HOMEBREW_AUTOREMOVE=1
brew tap --repair
brew update --quiet
brew install curl xquartz gd fontconfig freetype harfbuzz brotli s3cmd
command -v ccache >/dev/null 2>&1 || brew install ccache
command -v cmake >/dev/null 2>&1 || brew install cmake
command -v qmake >/dev/null 2>&1 || brew install qt@5
export PATH="/opt/homebrew/opt/qt@5/bin:$PATH"
# Install deps
brew install openal-soft icu4c yaml-cpp sqlite
ccache --version
cmake --version
qmake --version
if [[ "${MACOS_AMD64}" ]]; then
curl -fSL -R -J https://gitlab.com/OpenMW/openmw-deps/-/raw/main/macos/openmw-deps-20240802.zip -o ~/openmw-deps.zip
unzip -o ~/openmw-deps.zip -d /tmp > /dev/null
else
curl -fSL -R -J https://gitlab.com/OpenMW/openmw-deps/-/raw/main/macos/openmw-deps-20240818-arm64.tar.xz -o ~/openmw-deps.tar.xz
tar xf ~/openmw-deps.tar.xz -C /tmp > /dev/null
fi

77
CI/before_script.macos.sh Executable file
View file

@ -0,0 +1,77 @@
#!/bin/sh -e
# Silence a git warning
git config --global advice.detachedHead false
rm -fr build
mkdir build
cd build
DEPENDENCIES_ROOT="/tmp/openmw-deps"
if [[ "${MACOS_AMD64}" ]]; then
QT_PATH=$(arch -x86_64 /usr/local/bin/brew --prefix qt@6)
ICU_PATH=$(arch -x86_64 /usr/local/bin/brew --prefix icu4c)
OPENAL_PATH=$(arch -x86_64 /usr/local/bin/brew --prefix openal-soft)
CCACHE_EXECUTABLE=$(arch -x86_64 /usr/local/bin/brew --prefix ccache)/bin/ccache
else
QT_PATH=$(brew --prefix qt@6)
ICU_PATH=$(brew --prefix icu4c)
OPENAL_PATH=$(brew --prefix openal-soft)
CCACHE_EXECUTABLE=$(brew --prefix ccache)/bin/ccache
fi
declare -a CMAKE_CONF_OPTS=(
-D CMAKE_PREFIX_PATH="$DEPENDENCIES_ROOT;$QT_PATH;$OPENAL_PATH"
-D CMAKE_C_COMPILER_LAUNCHER="$CCACHE_EXECUTABLE"
-D CMAKE_CXX_COMPILER_LAUNCHER="$CCACHE_EXECUTABLE"
-D CMAKE_CXX_FLAGS="-stdlib=libc++"
-D CMAKE_C_COMPILER="clang"
-D CMAKE_CXX_COMPILER="clang++"
-D CMAKE_OSX_DEPLOYMENT_TARGET="13.6"
-D OPENMW_USE_SYSTEM_RECASTNAVIGATION=TRUE
-D Boost_INCLUDE_DIR="$DEPENDENCIES_ROOT/include"
-D OSGPlugins_LIB_DIR="$DEPENDENCIES_ROOT/lib/osgPlugins-3.6.5"
-D ICU_ROOT="$ICU_PATH"
-D OPENMW_OSX_DEPLOYMENT=TRUE
)
declare -a BUILD_OPTS=(
-D BUILD_OPENMW=TRUE
-D BUILD_OPENCS=TRUE
-D BUILD_ESMTOOL=TRUE
-D BUILD_BSATOOL=TRUE
-D BUILD_ESSIMPORTER=TRUE
-D BUILD_NIFTEST=TRUE
-D BUILD_NAVMESHTOOL=TRUE
-D BUILD_BULLETOBJECTTOOL=TRUE
-G"Unix Makefiles"
)
if [[ "${MACOS_AMD64}" ]]; then
CMAKE_CONF_OPTS+=(
-D CMAKE_OSX_ARCHITECTURES="x86_64"
)
fi
if [[ "${CMAKE_BUILD_TYPE}" ]]; then
CMAKE_CONF_OPTS+=(
-D CMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
)
else
CMAKE_CONF_OPTS+=(
-D CMAKE_BUILD_TYPE=RelWithDebInfo
)
fi
if [[ "${MACOS_AMD64}" ]]; then
arch -x86_64 cmake \
"${CMAKE_CONF_OPTS[@]}" \
"${BUILD_OPTS[@]}" \
..
else
cmake \
"${CMAKE_CONF_OPTS[@]}" \
"${BUILD_OPTS[@]}" \
..
fi

View file

@ -377,6 +377,8 @@ case $VS_VERSION in
MSVC_DISPLAY_YEAR="2022"
QT_MSVC_YEAR="2019"
VCPKG_TRIPLET="x64-windows"
;;
16|16.0|2019 )
@ -386,6 +388,8 @@ case $VS_VERSION in
MSVC_DISPLAY_YEAR="2019"
QT_MSVC_YEAR="2019"
VCPKG_TRIPLET="x64-windows-2019"
;;
15|15.0|2017 )
@ -546,7 +550,7 @@ fi
QT_VER='6.6.3'
AQT_VERSION='v3.1.15'
VCPKG_TAG="2024-11-10"
VCPKG_TAG="2025-07-23"
VCPKG_PATH="vcpkg-x64-${VS_VERSION:?}-${VCPKG_TAG:?}"
VCPKG_PDB_PATH="vcpkg-x64-${VS_VERSION:?}-pdb-${VCPKG_TAG:?}"
VCPKG_MANIFEST="${VCPKG_PATH:?}.txt"
@ -633,16 +637,16 @@ printf "vcpkg packages ${VCPKG_TAG:?}... "
fi
add_cmake_opts -DCMAKE_TOOLCHAIN_FILE="$(real_pwd)/${VCPKG_PATH:?}/scripts/buildsystems/vcpkg.cmake"
add_cmake_opts -DLuaJit_INCLUDE_DIR="$(real_pwd)/${VCPKG_PATH:?}/installed/x64-windows/include/luajit"
add_cmake_opts -DLuaJit_LIBRARY="$(real_pwd)/${VCPKG_PATH:?}/installed/x64-windows/lib/lua51.lib"
add_cmake_opts -DLuaJit_INCLUDE_DIR="$(real_pwd)/${VCPKG_PATH:?}/installed/${VCPKG_TRIPLET}/include/luajit"
add_cmake_opts -DLuaJit_LIBRARY="$(real_pwd)/${VCPKG_PATH:?}/installed/${VCPKG_TRIPLET}/lib/lua51.lib"
for CONFIGURATION in ${CONFIGURATIONS[@]}; do
if [[ ${CONFIGURATION:?} == "Debug" ]]; then
VCPKG_DLL_BIN="$(pwd)/${VCPKG_PATH:?}/installed/x64-windows/debug/bin"
VCPKG_DLL_BIN="$(pwd)/${VCPKG_PATH:?}/installed/${VCPKG_TRIPLET}/debug/bin"
add_runtime_dlls ${CONFIGURATION:?} "${VCPKG_DLL_BIN:?}/Debug/MyGUIEngine_d.dll"
else
VCPKG_DLL_BIN="$(pwd)/${VCPKG_PATH:?}/installed/x64-windows/bin"
VCPKG_DLL_BIN="$(pwd)/${VCPKG_PATH:?}/installed/${VCPKG_TRIPLET}/bin"
add_runtime_dlls ${CONFIGURATION:?} "${VCPKG_DLL_BIN:?}/Release/MyGUIEngine.dll"
fi

View file

@ -1,53 +0,0 @@
#!/bin/sh -e
# Silence a git warning
git config --global advice.detachedHead false
rm -fr build
mkdir build
cd build
DEPENDENCIES_ROOT="/tmp/openmw-deps"
QT_PATH=$(brew --prefix qt@5)
ICU_PATH=$(brew --prefix icu4c)
OPENAL_PATH=$(brew --prefix openal-soft)
CCACHE_EXECUTABLE=$(brew --prefix ccache)/bin/ccache
declare -a CMAKE_CONF_OPTS=(
-D CMAKE_PREFIX_PATH="$DEPENDENCIES_ROOT;$QT_PATH;$OPENAL_PATH"
-D CMAKE_C_COMPILER_LAUNCHER="$CCACHE_EXECUTABLE"
-D CMAKE_CXX_COMPILER_LAUNCHER="$CCACHE_EXECUTABLE"
-D CMAKE_CXX_FLAGS="-stdlib=libc++"
-D CMAKE_C_COMPILER="clang"
-D CMAKE_CXX_COMPILER="clang++"
-D CMAKE_OSX_DEPLOYMENT_TARGET="13.6"
-D OPENMW_USE_SYSTEM_RECASTNAVIGATION=TRUE
-D Boost_INCLUDE_DIR="$DEPENDENCIES_ROOT/include"
-D OSGPlugins_LIB_DIR="$DEPENDENCIES_ROOT/lib/osgPlugins-3.6.5"
-D ICU_ROOT="$ICU_PATH"
-D OPENMW_OSX_DEPLOYMENT=TRUE
)
if [[ "${CMAKE_BUILD_TYPE}" ]]; then
CMAKE_CONF_OPTS+=(
-D CMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
)
else
CMAKE_CONF_OPTS+=(
-D CMAKE_BUILD_TYPE=RelWithDebInfo
)
fi
cmake \
"${CMAKE_CONF_OPTS[@]}" \
-D BUILD_OPENMW=TRUE \
-D BUILD_OPENCS=TRUE \
-D BUILD_ESMTOOL=TRUE \
-D BUILD_BSATOOL=TRUE \
-D BUILD_ESSIMPORTER=TRUE \
-D BUILD_NIFTEST=TRUE \
-D BUILD_NAVMESHTOOL=TRUE \
-D BUILD_BULLETOBJECTTOOL=TRUE \
-G"Unix Makefiles" \
..

View file

@ -1,7 +1,6 @@
#!/bin/bash -ex
git ls-files -- ':(exclude)extern/' '*.cpp' '*.hpp' '*.h' |
grep -vP '/[a-z0-9]+\.(cpp|hpp|h)$' |
grep -vFf CI/file_name_exceptions.txt &&
grep -vP '/[a-z0-9]+\.(cpp|hpp|h)$' &&
( echo 'File names do not follow the naming convention, see https://wiki.openmw.org/index.php?title=Naming_Conventions#Files'; exit -1 )
exit 0

View file

@ -1,48 +0,0 @@
apps/openmw/android_main.cpp
apps/openmw/mwsound/efx-presets.h
apps/openmw/mwsound/ffmpeg_decoder.cpp
apps/openmw/mwsound/ffmpeg_decoder.hpp
apps/openmw/mwsound/openal_output.cpp
apps/openmw/mwsound/openal_output.hpp
apps/openmw/mwsound/sound_buffer.cpp
apps/openmw/mwsound/sound_buffer.hpp
apps/openmw/mwsound/sound_decoder.hpp
apps/openmw/mwsound/sound_output.hpp
apps/components_tests/esm/test_fixed_string.cpp
apps/components_tests/files/conversion_tests.cpp
apps/components_tests/lua/test_async.cpp
apps/components_tests/lua/test_configuration.cpp
apps/components_tests/lua/test_l10n.cpp
apps/components_tests/lua/test_lua.cpp
apps/components_tests/lua/test_scriptscontainer.cpp
apps/components_tests/lua/test_serialization.cpp
apps/components_tests/lua/test_storage.cpp
apps/components_tests/lua/test_ui_content.cpp
apps/components_tests/lua/test_utilpackage.cpp
apps/components_tests/lua/test_inputactions.cpp
apps/components_tests/lua/test_yaml.cpp
apps/components_tests/misc/test_endianness.cpp
apps/components_tests/misc/test_resourcehelpers.cpp
apps/components_tests/misc/test_stringops.cpp
apps/openmw_tests/mwdialogue/test_keywordsearch.cpp
apps/openmw_tests/mwscript/test_scripts.cpp
apps/openmw_tests/mwscript/test_utils.hpp
apps/openmw_tests/mwworld/test_store.cpp
components/bsa/bsa_file.cpp
components/bsa/bsa_file.hpp
components/crashcatcher/windows_crashcatcher.cpp
components/crashcatcher/windows_crashcatcher.hpp
components/crashcatcher/windows_crashmonitor.cpp
components/crashcatcher/windows_crashmonitor.hpp
components/crashcatcher/windows_crashshm.hpp
components/fx/lexer_types.hpp
components/fx/parse_constants.hpp
components/platform/file.posix.cpp
components/platform/file.stdio.cpp
components/platform/file.win32.cpp
components/sdlutil/gl4es_init.cpp
components/sdlutil/gl4es_init.h
components/to_utf8/gen_iconv.cpp
components/to_utf8/tables_gen.hpp
components/to_utf8/to_utf8.cpp
components/to_utf8/to_utf8.hpp

View file

@ -0,0 +1,8 @@
#!/bin/sh -ex
arch -x86_64 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
arch -x86_64 /usr/local/bin/brew install curl xquartz gd fontconfig freetype harfbuzz brotli s3cmd ccache cmake qt@6 openal-soft icu4c yaml-cpp sqlite
curl -fSL -R -J https://gitlab.com/OpenMW/openmw-deps/-/raw/main/macos/openmw-deps-20240802.zip -o ~/openmw-deps.zip
unzip -o ~/openmw-deps.zip -d /tmp > /dev/null

View file

@ -0,0 +1,9 @@
#!/bin/sh -ex
brew tap --repair
brew update --quiet
brew install curl xquartz gd fontconfig freetype harfbuzz brotli s3cmd ccache cmake qt@6 openal-soft icu4c yaml-cpp sqlite
curl -fSL -R -J https://gitlab.com/OpenMW/openmw-deps/-/raw/main/macos/openmw-deps-20240818-arm64.tar.xz -o ~/openmw-deps.tar.xz
tar xf ~/openmw-deps.tar.xz -C /tmp > /dev/null

9
CI/macos/build.sh Executable file
View file

@ -0,0 +1,9 @@
#!/bin/sh -ex
cd build
if [[ "${MACOS_AMD64}" ]]; then
arch -x86_64 make -j $(sysctl -n hw.logicalcpu) package
else
make -j $(sysctl -n hw.logicalcpu) package
fi

7
CI/macos/ccache_prep.sh Executable file
View file

@ -0,0 +1,7 @@
#!/bin/sh -ex
if [[ "${MACOS_AMD64}" ]]; then
arch -x86_64 ccache -z -M "${CCACHE_SIZE}"
else
ccache -z -M "${CCACHE_SIZE}"
fi

7
CI/macos/ccache_save.sh Executable file
View file

@ -0,0 +1,7 @@
#!/bin/sh -ex
if [[ "${MACOS_AMD64}" ]]; then
arch -x86_64 ccache -svv
else
ccache -svv
fi

View file

@ -80,10 +80,10 @@ endif()
message(STATUS "Configuring OpenMW...")
set(OPENMW_VERSION_MAJOR 0)
set(OPENMW_VERSION_MINOR 49)
set(OPENMW_VERSION_MINOR 50)
set(OPENMW_VERSION_RELEASE 0)
set(OPENMW_LUA_API_REVISION 76)
set(OPENMW_POSTPROCESSING_API_REVISION 2)
set(OPENMW_LUA_API_REVISION 82)
set(OPENMW_POSTPROCESSING_API_REVISION 3)
set(OPENMW_VERSION_COMMITHASH "")
set(OPENMW_VERSION_TAGHASH "")
@ -466,7 +466,7 @@ find_package(Boost 1.70.0 CONFIG REQUIRED COMPONENTS ${BOOST_COMPONENTS} OPTIONA
if(OPENMW_USE_SYSTEM_MYGUI)
find_package(MyGUI 3.4.3 REQUIRED)
endif()
find_package(SDL2 2.0.10 REQUIRED)
find_package(SDL2 2.0.20 REQUIRED)
find_package(OpenAL REQUIRED)
find_package(ZLIB REQUIRED)
@ -590,30 +590,10 @@ if(OPENMW_LTO_BUILD)
endif()
endif()
if (CMAKE_CXX_COMPILER_ID STREQUAL GNU OR CMAKE_CXX_COMPILER_ID STREQUAL Clang)
set(OPENMW_CXX_FLAGS "-Wall -Wextra -Wundef -Wextra-semi -Wno-unused-parameter -pedantic -Wno-long-long -Wnon-virtual-dtor -Wunused ${OPENMW_CXX_FLAGS}")
if (CMAKE_CXX_COMPILER_ID STREQUAL GNU)
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105438
set(OPENMW_CXX_FLAGS "-Wno-array-bounds ${OPENMW_CXX_FLAGS}")
endif()
if (APPLE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -stdlib=libc++")
endif()
if (CMAKE_CXX_COMPILER_ID STREQUAL Clang AND NOT APPLE)
if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 3.6 OR CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL 3.6)
set(OPENMW_CXX_FLAGS "${OPENMW_CXX_FLAGS} -Wno-potentially-evaluated-expression")
endif ()
endif()
if (CMAKE_CXX_COMPILER_ID STREQUAL GNU)
set(OPENMW_CXX_FLAGS "${OPENMW_CXX_FLAGS} -Wno-unused-but-set-parameter -Wduplicated-branches -Wduplicated-cond -Wlogical-op")
endif()
endif (CMAKE_CXX_COMPILER_ID STREQUAL GNU OR CMAKE_CXX_COMPILER_ID STREQUAL Clang)
if (APPLE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -stdlib=libc++")
endif()
# Extern
@ -624,8 +604,42 @@ if (BUILD_OPENCS OR BUILD_OPENCS_TESTS)
add_subdirectory (extern/osgQt)
endif()
if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" OR CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
add_compile_options("/W4")
set(WARNINGS_DISABLE
4100 # Unreferenced formal parameter (-Wunused-parameter)
4127 # Conditional expression is constant
4996 # Function was declared deprecated
5054 # Deprecated operations between enumerations of different types caused by Qt headers
)
foreach(d ${WARNINGS_DISABLE})
add_compile_options("/wd${d}")
endforeach(d)
if(OPENMW_MSVC_WERROR)
add_compile_options("/WX")
endif()
else ()
add_compile_options("-Wall" "-Wextra" "-Wundef" "-Wextra-semi" "-Wno-unused-parameter" "-pedantic" "-Wno-long-long" "-Wnon-virtual-dtor" "-Wunused")
if (CMAKE_CXX_COMPILER_ID STREQUAL Clang AND NOT APPLE)
if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 3.6 OR CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL 3.6)
add_compile_options("-Wno-potentially-evaluated-expression")
endif ()
endif()
if (CMAKE_CXX_COMPILER_ID STREQUAL GNU)
add_compile_options("-Wno-unused-but-set-parameter" "-Wduplicated-branches" "-Wduplicated-cond" "-Wlogical-op")
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105438
add_compile_options("-Wno-array-bounds")
endif()
endif ()
if (OPENMW_CXX_FLAGS)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OPENMW_CXX_FLAGS}")
separate_arguments(OPENMW_CXX_FLAGS NATIVE_COMMAND "${OPENMW_CXX_FLAGS}")
add_compile_options(${OPENMW_CXX_FLAGS})
endif()
# Components
@ -715,87 +729,9 @@ if (WIN32)
set_target_properties(openmw PROPERTIES LINK_FLAGS_MINSIZEREL "/SUBSYSTEM:WINDOWS")
endif()
# Play a bit with the warning levels
set(WARNINGS "/W4")
set(WARNINGS_DISABLE
4100 # Unreferenced formal parameter (-Wunused-parameter)
4127 # Conditional expression is constant
4996 # Function was declared deprecated
5054 # Deprecated operations between enumerations of different types caused by Qt headers
)
foreach(d ${WARNINGS_DISABLE})
list(APPEND WARNINGS "/wd${d}")
endforeach(d)
if(OPENMW_MSVC_WERROR)
list(APPEND WARNINGS "/WX")
endif()
target_compile_options(components PRIVATE ${WARNINGS})
target_compile_options(osg-ffmpeg-videoplayer PRIVATE ${WARNINGS})
if (MSVC_VERSION GREATER_EQUAL 1915 AND MSVC_VERSION LESS 1920)
target_compile_definitions(components INTERFACE _ENABLE_EXTENDED_ALIGNED_STORAGE)
endif()
if (BUILD_BSATOOL)
target_compile_options(bsatool PRIVATE ${WARNINGS})
endif()
if (BUILD_ESMTOOL)
target_compile_options(esmtool PRIVATE ${WARNINGS})
endif()
if (BUILD_ESSIMPORTER)
target_compile_options(openmw-essimporter PRIVATE ${WARNINGS})
endif()
if (BUILD_LAUNCHER)
target_compile_options(openmw-launcher PRIVATE ${WARNINGS})
endif()
if (BUILD_MWINIIMPORTER)
target_compile_options(openmw-iniimporter PRIVATE ${WARNINGS})
endif()
if (BUILD_OPENCS)
target_compile_options(openmw-cs PRIVATE ${WARNINGS})
endif()
if (BUILD_OPENMW)
target_compile_options(openmw PRIVATE ${WARNINGS})
endif()
if (BUILD_WIZARD)
target_compile_options(openmw-wizard PRIVATE ${WARNINGS})
endif()
if (BUILD_COMPONENTS_TESTS)
target_compile_options(components-tests PRIVATE ${WARNINGS})
endif()
if (BUILD_BENCHMARKS)
target_compile_options(openmw_detournavigator_navmeshtilescache_benchmark PRIVATE ${WARNINGS})
endif()
if (BUILD_NAVMESHTOOL)
target_compile_options(openmw-navmeshtool PRIVATE ${WARNINGS})
endif()
if (BUILD_BULLETOBJECTTOOL)
target_compile_options(openmw-bulletobjecttool PRIVATE ${WARNINGS} ${MT_BUILD})
endif()
if (BUILD_OPENCS_TESTS)
target_compile_options(openmw-cs-tests PRIVATE ${WARNINGS})
endif()
if (BUILD_OPENMW_TESTS)
target_compile_options(openmw-tests PRIVATE ${WARNINGS})
endif()
endif(MSVC)
# TODO: At some point release builds should not use the console but rather write to a log file

View file

@ -5,7 +5,7 @@ OpenMW is an open-source open-world RPG game engine that supports playing Morrow
OpenMW also comes with OpenMW-CS, a replacement for Bethesda's Construction Set.
* Version: 0.49.0
* Version: 0.50.0
* License: GPLv3 (see [LICENSE](https://gitlab.com/OpenMW/openmw/-/raw/master/LICENSE) for more information)
* Website: https://www.openmw.org
* IRC: #openmw on irc.libera.chat

View file

@ -20,7 +20,7 @@
#include <components/resource/niffilemanager.hpp>
#include <components/resource/scenemanager.hpp>
#include <components/settings/settings.hpp>
#include <components/to_utf8/to_utf8.hpp>
#include <components/toutf8/toutf8.hpp>
#include <components/version/version.hpp>
#include <components/vfs/manager.hpp>
#include <components/vfs/registerarchives.hpp>
@ -125,6 +125,7 @@ namespace
}
Files::ConfigurationManager config;
config.processPaths(variables, std::filesystem::current_path());
config.readConfiguration(variables, desc);
Debug::setupLogging(config.getLogPath(), applicationName);
@ -154,7 +155,7 @@ namespace
VFS::Manager vfs;
VFS::registerArchives(&vfs, fileCollections, archives, true);
VFS::registerArchives(&vfs, fileCollections, archives, true, &encoder.getStatelessEncoder());
Settings::Manager::load(config);

View file

@ -4,29 +4,28 @@ include_directories(SYSTEM ${GMOCK_INCLUDE_DIRS})
file(GLOB UNITTEST_SRC_FILES
main.cpp
esm/test_fixed_string.cpp
esm/variant.cpp
esm/testfixedstring.cpp
esm/testrefid.cpp
esm/variant.cpp
lua/test_lua.cpp
lua/test_scriptscontainer.cpp
lua/test_utilpackage.cpp
lua/test_serialization.cpp
lua/test_configuration.cpp
lua/test_l10n.cpp
lua/test_storage.cpp
lua/test_async.cpp
lua/test_inputactions.cpp
lua/test_yaml.cpp
lua/test_ui_content.cpp
lua/testasync.cpp
lua/testconfiguration.cpp
lua/testinputactions.cpp
lua/testl10n.cpp
lua/testlua.cpp
lua/testscriptscontainer.cpp
lua/testserialization.cpp
lua/teststorage.cpp
lua/testuicontent.cpp
lua/testutilpackage.cpp
lua/testyaml.cpp
misc/compression.cpp
misc/progressreporter.cpp
misc/test_endianness.cpp
misc/test_resourcehelpers.cpp
misc/test_stringops.cpp
misc/testendianness.cpp
misc/testmathutil.cpp
misc/testresourcehelpers.cpp
misc/teststringops.cpp
nifloader/testbulletnifloader.cpp
@ -64,8 +63,8 @@ file(GLOB UNITTEST_SRC_FILES
esmloader/esmdata.cpp
esmloader/record.cpp
files/conversiontests.cpp
files/hash.cpp
files/conversion_tests.cpp
toutf8/toutf8.cpp

View file

@ -723,7 +723,7 @@ namespace ESM
TEST_P(Esm3SaveLoadRecordTest, landShouldNotChange)
{
LandRecordData data;
std::iota(data.mHeights.begin(), data.mHeights.end(), 1);
std::iota(data.mHeights.begin(), data.mHeights.end(), 1.0f);
std::for_each(data.mHeights.begin(), data.mHeights.end(), [](float& v) { v *= Land::sHeightScale; });
data.mMinHeight = *std::min_element(data.mHeights.begin(), data.mHeights.end());
data.mMaxHeight = *std::max_element(data.mHeights.begin(), data.mHeights.end());

View file

@ -10,7 +10,7 @@
#include <components/esmloader/load.hpp>
#include <components/files/collections.hpp>
#include <components/files/multidircollection.hpp>
#include <components/to_utf8/to_utf8.hpp>
#include <components/toutf8/toutf8.hpp>
#include <gtest/gtest.h>

View file

@ -5,7 +5,7 @@
namespace
{
using namespace testing;
using namespace fx::Lexer;
using namespace Fx::Lexer;
struct LexerTest : Test
{

View file

@ -91,7 +91,7 @@ namespace
)" };
using namespace testing;
using namespace fx;
using namespace Fx;
struct TechniqueTest : Test
{
@ -113,7 +113,8 @@ namespace
void compile(const std::string& name)
{
mTechnique = std::make_unique<Technique>(*mVFS.get(), mImageManager, name, 1, 1, true, true);
mTechnique = std::make_unique<Technique>(
*mVFS.get(), mImageManager, Technique::makeFileName(name), name, 1, 1, true, true);
mTechnique->compile();
}
};

View file

@ -24,6 +24,8 @@ namespace
constexpr VFS::Path::NormalizedView test2EnPath("l10n/test2/en.yaml");
constexpr VFS::Path::NormalizedView test3EnPath("l10n/test3/en.yaml");
constexpr VFS::Path::NormalizedView test3DePath("l10n/test3/de.yaml");
constexpr VFS::Path::NormalizedView test4RuPath("l10n/test4/ru.yaml");
constexpr VFS::Path::NormalizedView test4EnPath("l10n/test4/en.yaml");
VFSTestFile invalidScript("not a script");
VFSTestFile incorrectScript(
@ -69,6 +71,16 @@ currency: "You have {money, number, currency}"
VFSTestFile test2En(R"X(
good_morning: "Morning!"
you_have_arrows: "Arrows count: {count}"
)X");
VFSTestFile test4Ru(R"X(
skill_increase: "Ваш навык {навык} увеличился до {value}"
acrobatics: "Акробатика"
)X");
VFSTestFile test4En(R"X(
stat_increase: "Your {stat} has increased to {value}"
speed: "Speed"
)X");
struct LuaL10nTest : Test
@ -80,6 +92,8 @@ you_have_arrows: "Arrows count: {count}"
{ test2EnPath, &test2En },
{ test3EnPath, &test1En },
{ test3DePath, &test1De },
{ test4RuPath, &test4Ru },
{ test4EnPath, &test4En },
});
LuaUtil::ScriptsConfiguration mCfg;
@ -91,7 +105,7 @@ you_have_arrows: "Arrows count: {count}"
lua.protectedCall([&](LuaUtil::LuaView& view) {
sol::state_view& l = view.sol();
internal::CaptureStdout();
l10n::Manager l10nManager(mVFS.get());
L10n::Manager l10nManager(mVFS.get());
l10nManager.setPreferredLocales({ "de", "en" });
EXPECT_THAT(internal::GetCapturedStdout(), "Preferred locales: gmst de en\n");
@ -169,6 +183,18 @@ you_have_arrows: "Arrows count: {count}"
l.safe_script("t3 = l10n('Test3', 'de')");
l10nManager.setPreferredLocales({ "en" });
EXPECT_EQ(get<std::string>(l, "t3('Hello {name}!', {name='World'})"), "Hallo World!");
// Test that formatting arguments use a correct encoding
l.safe_script("t4 = l10n('Test4', 'ru')");
l10nManager.setPreferredLocales({ "ru", "en" });
EXPECT_EQ(get<std::string>(l, "t4('skill_increase', {навык='Акробатика', value=100})"),
"Ваш навык Акробатика увеличился до 100");
EXPECT_EQ(get<std::string>(l, "t4('skill_increase', {навык=t4('acrobatics'), value=100})"),
"Ваш навык Акробатика увеличился до 100");
EXPECT_EQ(get<std::string>(l, "t4('stat_increase', {stat='Speed', value=100})"),
"Your Speed has increased to 100");
EXPECT_EQ(get<std::string>(l, "t4('stat_increase', {stat=t4('speed'), value=100})"),
"Your Speed has increased to 100");
});
}
}

View file

@ -9,22 +9,33 @@ namespace
{
using namespace testing;
struct LuaUtilPackageTest : Test
{
LuaUtil::LuaState mLuaState{ nullptr, nullptr };
LuaUtilPackageTest()
{
mLuaState.addInternalLibSearchPath(
std::filesystem::path{ OPENMW_PROJECT_SOURCE_DIR } / "components" / "lua");
sol::state_view sol = mLuaState.unsafeState();
sol["util"] = LuaUtil::initUtilPackage(sol);
}
};
template <typename T>
T get(sol::state& lua, const std::string& luaCode)
T get(sol::state_view& lua, const std::string& luaCode)
{
return lua.safe_script("return " + luaCode).get<T>();
}
std::string getAsString(sol::state& lua, std::string luaCode)
std::string getAsString(sol::state_view& lua, std::string luaCode)
{
return LuaUtil::toString(lua.safe_script("return " + luaCode));
}
TEST(LuaUtilPackageTest, Vector2)
TEST_F(LuaUtilPackageTest, Vector2)
{
sol::state lua;
lua.open_libraries(sol::lib::base, sol::lib::math, sol::lib::string);
lua["util"] = LuaUtil::initUtilPackage(lua);
sol::state_view lua = mLuaState.unsafeState();
lua.safe_script("v = util.vector2(3, 4)");
EXPECT_FLOAT_EQ(get<float>(lua, "v.x"), 3);
EXPECT_FLOAT_EQ(get<float>(lua, "v.y"), 4);
@ -55,11 +66,9 @@ namespace
EXPECT_TRUE(get<bool>(lua, "swizzle['01'] == util.vector2(0, 1) and swizzle['0y'] == util.vector2(0, 2)"));
}
TEST(LuaUtilPackageTest, Vector3)
TEST_F(LuaUtilPackageTest, Vector3)
{
sol::state lua;
lua.open_libraries(sol::lib::base, sol::lib::math, sol::lib::string);
lua["util"] = LuaUtil::initUtilPackage(lua);
sol::state_view lua = mLuaState.unsafeState();
lua.safe_script("v = util.vector3(5, 12, 13)");
EXPECT_FLOAT_EQ(get<float>(lua, "v.x"), 5);
EXPECT_FLOAT_EQ(get<float>(lua, "v.y"), 12);
@ -94,11 +103,9 @@ namespace
get<bool>(lua, "swizzle['001'] == util.vector3(0, 0, 1) and swizzle['0yx'] == util.vector3(0, 2, 1)"));
}
TEST(LuaUtilPackageTest, Vector4)
TEST_F(LuaUtilPackageTest, Vector4)
{
sol::state lua;
lua.open_libraries(sol::lib::base, sol::lib::math, sol::lib::string);
lua["util"] = LuaUtil::initUtilPackage(lua);
sol::state_view lua = mLuaState.unsafeState();
lua.safe_script("v = util.vector4(5, 12, 13, 15)");
EXPECT_FLOAT_EQ(get<float>(lua, "v.x"), 5);
EXPECT_FLOAT_EQ(get<float>(lua, "v.y"), 12);
@ -136,11 +143,9 @@ namespace
lua, "swizzle['0001'] == util.vector4(0, 0, 0, 1) and swizzle['0yx1'] == util.vector4(0, 2, 1, 1)"));
}
TEST(LuaUtilPackageTest, Color)
TEST_F(LuaUtilPackageTest, Color)
{
sol::state lua;
lua.open_libraries(sol::lib::base, sol::lib::math, sol::lib::string);
lua["util"] = LuaUtil::initUtilPackage(lua);
sol::state_view lua = mLuaState.unsafeState();
lua.safe_script("brown = util.color.rgba(0.75, 0.25, 0, 1)");
EXPECT_EQ(get<std::string>(lua, "tostring(brown)"), "(0.75, 0.25, 0, 1)");
lua.safe_script("blue = util.color.rgb(0, 1, 0, 1)");
@ -155,11 +160,9 @@ namespace
EXPECT_TRUE(get<bool>(lua, "red:asRgb() == util.vector3(1, 0, 0)"));
}
TEST(LuaUtilPackageTest, Transform)
TEST_F(LuaUtilPackageTest, Transform)
{
sol::state lua;
lua.open_libraries(sol::lib::base, sol::lib::math, sol::lib::string);
lua["util"] = LuaUtil::initUtilPackage(lua);
sol::state_view lua = mLuaState.unsafeState();
lua["T"] = lua["util"]["transform"];
lua["v"] = lua["util"]["vector3"];
EXPECT_ERROR(lua.safe_script("T.identity = nil"), "attempt to index");
@ -191,11 +194,9 @@ namespace
EXPECT_LT(get<float>(lua, "(rz_move_rx:inverse() * v(0, 1, 2) - v(1, 2, 3)):length()"), 1e-6);
}
TEST(LuaUtilPackageTest, UtilityFunctions)
TEST_F(LuaUtilPackageTest, UtilityFunctions)
{
sol::state lua;
lua.open_libraries(sol::lib::base, sol::lib::math, sol::lib::string);
lua["util"] = LuaUtil::initUtilPackage(lua);
sol::state_view lua = mLuaState.unsafeState();
lua.safe_script("v = util.vector2(1, 0):rotate(math.rad(120))");
EXPECT_FLOAT_EQ(get<float>(lua, "v.x"), -0.5f);
EXPECT_FLOAT_EQ(get<float>(lua, "v.y"), 0.86602539f);
@ -203,6 +204,10 @@ namespace
EXPECT_FLOAT_EQ(get<float>(lua, "util.clamp(0.1, 0, 1.5)"), 0.1f);
EXPECT_FLOAT_EQ(get<float>(lua, "util.clamp(-0.1, 0, 1.5)"), 0);
EXPECT_FLOAT_EQ(get<float>(lua, "util.clamp(2.1, 0, 1.5)"), 1.5f);
EXPECT_FLOAT_EQ(get<float>(lua, "util.round(2.1)"), 2.0f);
EXPECT_FLOAT_EQ(get<float>(lua, "util.round(-2.1)"), -2.0f);
EXPECT_FLOAT_EQ(get<float>(lua, "util.remap(5, 0, 10, 0, 100)"), 50.0f);
EXPECT_FLOAT_EQ(get<float>(lua, "util.remap(-5, 0, 10, 0, 100)"), -50.0f);
lua.safe_script("t = util.makeReadOnly({x = 1})");
EXPECT_FLOAT_EQ(get<float>(lua, "t.x"), 1);
EXPECT_ERROR(lua.safe_script("t.y = 2"), "userdata value");

View file

@ -26,6 +26,15 @@ namespace
std::unique_ptr<VFS::Manager> mVFS = TestingOpenMW::createTestVFS({});
constexpr VFS::Path::NormalizedView path("sound/foo.wav");
EXPECT_EQ(correctSoundPath(path, *mVFS), "sound/foo.mp3");
auto correctESM4SoundPath = [](auto path, auto* vfs) {
return Misc::ResourceHelpers::correctResourcePath({ { "sound" } }, path, vfs, ".mp3");
};
EXPECT_EQ(correctESM4SoundPath("foo.WAV", mVFS.get()), "sound\\foo.mp3");
EXPECT_EQ(correctESM4SoundPath("SOUND/foo.WAV", mVFS.get()), "sound\\foo.mp3");
EXPECT_EQ(correctESM4SoundPath("DATA\\SOUND\\foo.WAV", mVFS.get()), "sound\\foo.mp3");
EXPECT_EQ(correctESM4SoundPath("\\Data/Sound\\foo.WAV", mVFS.get()), "sound\\foo.mp3");
}
namespace

View file

@ -1,5 +1,5 @@
#include <components/misc/strings/conversion.hpp>
#include <components/to_utf8/to_utf8.hpp>
#include <components/toutf8/toutf8.hpp>
#include <gtest/gtest.h>

View file

@ -15,7 +15,7 @@
#include <components/esm4/readerutils.hpp>
#include <components/esm4/records.hpp>
#include <components/esm4/typetraits.hpp>
#include <components/to_utf8/to_utf8.hpp>
#include <components/toutf8/toutf8.hpp>
namespace EsmTool
{

View file

@ -249,7 +249,7 @@ namespace ESSImport
{
ESM::InventoryState& invState = mContext->mPlayer.mObject.mInventory;
for (size_t i = 0; i < invState.mItems.size(); ++i)
for (uint32_t i = 0; i < static_cast<uint32_t>(invState.mItems.size()); ++i)
{
// FIXME: in case of conflict (multiple items with this refID) use the already equipped one?
if (invState.mItems[i].mRef.mRefID == refr.mActorData.mSelectedEnchantItem)

View file

@ -25,7 +25,7 @@
#include <components/misc/constants.hpp>
#include <components/to_utf8/to_utf8.hpp>
#include <components/toutf8/toutf8.hpp>
#include "importercontext.hpp"

View file

@ -40,6 +40,7 @@ Allowed options)");
bpo::notify(variables);
Files::ConfigurationManager cfgManager(true);
cfgManager.processPaths(variables, std::filesystem::current_path());
cfgManager.readConfiguration(variables, desc);
const auto& essFile = variables["mwsave"].as<Files::MaybeQuotedPath>();

View file

@ -39,8 +39,6 @@
#include "utils/profilescombobox.hpp"
#include "utils/textinputdialog.hpp"
#include "ui_directorypicker.h"
const char* Launcher::DataFilesPage::mDefaultContentListName = "Default";
namespace
@ -155,6 +153,7 @@ namespace Launcher
Launcher::DataFilesPage::DataFilesPage(const Files::ConfigurationManager& cfg, Config::GameSettings& gameSettings,
Config::LauncherSettings& launcherSettings, MainDialog* parent)
: QWidget(parent)
, mDirectoryPickerDialog(new QDialog(this))
, mMainDialog(parent)
, mCfgMgr(cfg)
, mGameSettings(gameSettings)
@ -163,6 +162,7 @@ Launcher::DataFilesPage::DataFilesPage(const Files::ConfigurationManager& cfg, C
, mReloadCellsThread(&DataFilesPage::reloadCells, this)
{
ui.setupUi(this);
mDirectoryPicker.setupUi(mDirectoryPickerDialog);
setObjectName("DataFilesPage");
mSelector = new ContentSelectorView::ContentSelector(ui.contentSelectorWidget, /*showOMWScripts=*/true);
const QString encoding = mGameSettings.value("encoding", { "win1252" }).value;
@ -266,6 +266,7 @@ void Launcher::DataFilesPage::buildView()
buildArchiveContextMenu();
buildDataFilesContextMenu();
buildDirectoryPickerContextMenu();
}
void Launcher::DataFilesPage::slotCopySelectedItemsPaths()
@ -298,8 +299,10 @@ void Launcher::DataFilesPage::buildArchiveContextMenu()
&DataFilesPage::slotShowArchiveContextMenu);
mArchiveContextMenu = new QMenu(ui.archiveListWidget);
mArchiveContextMenu->addAction(tr("&Check Selected"), this, SLOT(slotCheckMultiSelectedItems()));
mArchiveContextMenu->addAction(tr("&Uncheck Selected"), this, SLOT(slotUncheckMultiSelectedItems()));
mArchiveContextMenu->addAction(tr("&Check Selected"), this,
[this]() { setCheckStateForMultiSelectedItems(ui.archiveListWidget, Qt::Checked); });
mArchiveContextMenu->addAction(tr("&Uncheck Selected"), this,
[this]() { setCheckStateForMultiSelectedItems(ui.archiveListWidget, Qt::Unchecked); });
}
void Launcher::DataFilesPage::buildDataFilesContextMenu()
@ -314,6 +317,18 @@ void Launcher::DataFilesPage::buildDataFilesContextMenu()
tr("&Open Path in File Explorer"), this, &Launcher::DataFilesPage::slotOpenSelectedItemsPaths);
}
void Launcher::DataFilesPage::buildDirectoryPickerContextMenu()
{
connect(mDirectoryPicker.dirListWidget, &QListWidget::customContextMenuRequested, this,
&DataFilesPage::slotShowDirectoryPickerContextMenu);
mDirectoryPickerMenu = new QMenu(mDirectoryPicker.dirListWidget);
mDirectoryPickerMenu->addAction(tr("&Check Selected"), this,
[this]() { setCheckStateForMultiSelectedItems(mDirectoryPicker.dirListWidget, Qt::Checked); });
mDirectoryPickerMenu->addAction(tr("&Uncheck Selected"), this,
[this]() { setCheckStateForMultiSelectedItems(mDirectoryPicker.dirListWidget, Qt::Unchecked); });
}
bool Launcher::DataFilesPage::loadSettings()
{
ui.navMeshMaxSizeSpinBox->setValue(getMaxNavMeshDbFileSizeMiB());
@ -821,28 +836,22 @@ void Launcher::DataFilesPage::addSubdirectories(bool append)
return;
}
QDialog dialog;
Ui::SelectSubdirs select;
select.setupUi(&dialog);
mDirectoryPicker.dirListWidget->clear();
for (const auto& dir : subdirs)
{
if (!ui.directoryListWidget->findItems(dir, Qt::MatchFixedString).isEmpty())
continue;
const auto lastRow = select.dirListWidget->count();
select.dirListWidget->addItem(dir);
select.dirListWidget->item(lastRow)->setCheckState(Qt::Unchecked);
QListWidgetItem* newDir = new QListWidgetItem(dir, mDirectoryPicker.dirListWidget);
newDir->setCheckState(Qt::Unchecked);
}
dialog.show();
if (dialog.exec() == QDialog::Rejected)
if (mDirectoryPickerDialog->exec() == QDialog::Rejected)
return;
for (int i = 0; i < select.dirListWidget->count(); ++i)
for (int i = 0; i < mDirectoryPicker.dirListWidget->count(); ++i)
{
const auto* dir = select.dirListWidget->item(i);
const auto* dir = mDirectoryPicker.dirListWidget->item(i);
if (dir->checkState() == Qt::Checked)
{
ui.directoryListWidget->insertItem(selectedRow, dir->text());
@ -893,38 +902,35 @@ void Launcher::DataFilesPage::removeDirectory()
refreshDataFilesView();
}
void Launcher::DataFilesPage::showContextMenu(QMenu* menu, QListWidget* list, const QPoint& pos)
{
QPoint globalPos = list->viewport()->mapToGlobal(pos);
menu->exec(globalPos);
}
void Launcher::DataFilesPage::slotShowArchiveContextMenu(const QPoint& pos)
{
QPoint globalPos = ui.archiveListWidget->viewport()->mapToGlobal(pos);
mArchiveContextMenu->exec(globalPos);
showContextMenu(mArchiveContextMenu, ui.archiveListWidget, pos);
}
void Launcher::DataFilesPage::slotShowDataFilesContextMenu(const QPoint& pos)
{
QPoint globalPos = ui.directoryListWidget->viewport()->mapToGlobal(pos);
mDataFilesContextMenu->exec(globalPos);
showContextMenu(mDataFilesContextMenu, ui.directoryListWidget, pos);
}
void Launcher::DataFilesPage::setCheckStateForMultiSelectedItems(bool checked)
void Launcher::DataFilesPage::slotShowDirectoryPickerContextMenu(const QPoint& pos)
{
Qt::CheckState checkState = checked ? Qt::Checked : Qt::Unchecked;
showContextMenu(mDirectoryPickerMenu, mDirectoryPicker.dirListWidget, pos);
}
for (QListWidgetItem* selectedItem : ui.archiveListWidget->selectedItems())
void Launcher::DataFilesPage::setCheckStateForMultiSelectedItems(QListWidget* list, Qt::CheckState checkState)
{
for (QListWidgetItem* selectedItem : list->selectedItems())
{
selectedItem->setCheckState(checkState);
}
}
void Launcher::DataFilesPage::slotUncheckMultiSelectedItems()
{
setCheckStateForMultiSelectedItems(false);
}
void Launcher::DataFilesPage::slotCheckMultiSelectedItems()
{
setCheckStateForMultiSelectedItems(true);
}
void Launcher::DataFilesPage::moveSources(QListWidget* sourceList, int step)
{
const QList<QPair<int, QListWidgetItem*>> sortedItems = sortedSelectedItems(sourceList, step > 0);

View file

@ -2,6 +2,7 @@
#define DATAFILESPAGE_H
#include "ui_datafilespage.h"
#include "ui_directorypicker.h"
#include <components/process/processinvoker.hpp>
@ -46,8 +47,11 @@ namespace Launcher
ContentSelectorView::ContentSelector* mSelector;
Ui::DataFilesPage ui;
QDialog* mDirectoryPickerDialog;
Ui::SelectSubdirs mDirectoryPicker;
QMenu* mArchiveContextMenu;
QMenu* mDataFilesContextMenu;
QMenu* mDirectoryPickerMenu;
public:
explicit DataFilesPage(const Files::ConfigurationManager& cfg, Config::GameSettings& gameSettings,
@ -87,8 +91,7 @@ namespace Launcher
void slotShowArchiveContextMenu(const QPoint& pos);
void slotShowDataFilesContextMenu(const QPoint& pos);
void slotCheckMultiSelectedItems();
void slotUncheckMultiSelectedItems();
void slotShowDirectoryPickerContextMenu(const QPoint& pos);
void on_newProfileAction_triggered();
void on_cloneProfileAction_triggered();
@ -145,7 +148,9 @@ namespace Launcher
void buildView();
void buildArchiveContextMenu();
void buildDataFilesContextMenu();
void setCheckStateForMultiSelectedItems(bool checked);
void buildDirectoryPickerContextMenu();
void showContextMenu(QMenu* menu, QListWidget* list, const QPoint& pos);
void setCheckStateForMultiSelectedItems(QListWidget* list, Qt::CheckState checkState);
void setProfile(int index, bool savePrevious);
void setProfile(const QString& previous, const QString& current, bool savePrevious);
void removeProfile(const QString& profile);

View file

@ -242,11 +242,36 @@ void Launcher::GraphicsPage::handleWindowModeChange(Settings::WindowMode mode)
{
if (mode == Settings::WindowMode::Fullscreen || mode == Settings::WindowMode::WindowedFullscreen)
{
QString customSizeMessage = tr("Custom window size is available only in Windowed mode.");
QString windowBorderMessage = tr("Window border is available only in Windowed mode.");
standardRadioButton->toggle();
customRadioButton->setEnabled(false);
customWidthSpinBox->setEnabled(false);
customHeightSpinBox->setEnabled(false);
windowBorderCheckBox->setEnabled(false);
windowBorderCheckBox->setToolTip(windowBorderMessage);
customWidthSpinBox->setToolTip(customSizeMessage);
customHeightSpinBox->setToolTip(customSizeMessage);
customRadioButton->setToolTip(customSizeMessage);
}
if (mode == Settings::WindowMode::Fullscreen)
{
resolutionComboBox->setEnabled(true);
resolutionComboBox->setToolTip("");
standardRadioButton->setToolTip("");
}
else if (mode == Settings::WindowMode::WindowedFullscreen)
{
QString fullScreenMessage = tr("Windowed Fullscreen mode always uses the native display resolution.");
resolutionComboBox->setEnabled(false);
resolutionComboBox->setToolTip(fullScreenMessage);
standardRadioButton->setToolTip(fullScreenMessage);
// Assume that a first item is a native screen resolution
resolutionComboBox->setCurrentIndex(0);
}
else
{
@ -254,6 +279,13 @@ void Launcher::GraphicsPage::handleWindowModeChange(Settings::WindowMode mode)
customWidthSpinBox->setEnabled(true);
customHeightSpinBox->setEnabled(true);
windowBorderCheckBox->setEnabled(true);
resolutionComboBox->setEnabled(true);
resolutionComboBox->setToolTip("");
standardRadioButton->setToolTip("");
windowBorderCheckBox->setToolTip("");
customWidthSpinBox->setToolTip("");
customHeightSpinBox->setToolTip("");
customRadioButton->setToolTip("");
}
}

View file

@ -42,7 +42,7 @@ int runLauncher(int argc, char* argv[])
resourcesPath = Files::pathToQString(variables["resources"].as<Files::MaybeQuotedPath>().u8string());
}
l10n::installQtTranslations(app, "launcher", resourcesPath);
L10n::installQtTranslations(app, "launcher", resourcesPath);
Launcher::MainDialog mainWin(configurationManager);

View file

@ -25,7 +25,14 @@
</widget>
</item>
<item row="0" column="0">
<widget class="QListWidget" name="dirListWidget"/>
<widget class="QListWidget" name="dirListWidget">
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
</widget>
</item>
</layout>
</widget>

View file

@ -11,6 +11,23 @@
namespace sfs = std::filesystem;
namespace
{
// from configfileparser.cpp
std::string trim_ws(const std::string& s)
{
std::string::size_type n, n2;
n = s.find_first_not_of(" \t\r\n");
if (n == std::string::npos)
return std::string();
else
{
n2 = s.find_last_not_of(" \t\r\n");
return s.substr(n, n2 - n + 1);
}
}
}
MwIniImporter::MwIniImporter()
: mVerbose(false)
, mEncoding(ToUTF8::WINDOWS_1250)
@ -352,12 +369,10 @@ MwIniImporter::multistrmap MwIniImporter::loadCfgFile(const std::filesystem::pat
std::string line;
while (std::getline(file, line))
{
// we cant say comment by only looking at first char anymore
int comment_pos = static_cast<int>(line.find('#'));
if (comment_pos > 0)
// ignore comments - keep in sync with configfileparser.cpp
if (line.find('#') == line.find_first_not_of(" \t\r\n"))
{
line = line.substr(0, comment_pos);
continue;
}
if (line.empty())
@ -373,6 +388,8 @@ MwIniImporter::multistrmap MwIniImporter::loadCfgFile(const std::filesystem::pat
std::string key(line.substr(0, pos));
std::string value(line.substr(pos + 1));
key = trim_ws(key);
value = trim_ws(value);
if (map.find(key) == map.end())
{

View file

@ -8,7 +8,7 @@
#include <string>
#include <vector>
#include <components/to_utf8/to_utf8.hpp>
#include <components/toutf8/toutf8.hpp>
class MwIniImporter
{

View file

@ -126,12 +126,20 @@ int wmain(int argc, wchar_t* wargv[])
MwIniImporter importer;
importer.setVerbose(vm.count("verbose") != 0);
MwIniImporter::multistrmap cfg = importer.loadCfgFile(cfgFile);
// Font encoding settings
std::string encoding(vm["encoding"].as<std::string>());
std::string encoding;
if (vm["encoding"].defaulted() && cfg.contains("encoding") && !cfg["encoding"].empty())
encoding = cfg["encoding"].back();
else
{
encoding = vm["encoding"].as<std::string>();
cfg["encoding"] = { encoding };
}
importer.setInputEncoding(ToUTF8::calculateEncoding(encoding));
MwIniImporter::multistrmap ini = importer.loadIniFile(iniFile);
MwIniImporter::multistrmap cfg = importer.loadCfgFile(cfgFile);
if (!vm.count("fonts"))
{

View file

@ -25,7 +25,7 @@
#include <components/resource/niffilemanager.hpp>
#include <components/resource/scenemanager.hpp>
#include <components/settings/values.hpp>
#include <components/to_utf8/to_utf8.hpp>
#include <components/toutf8/toutf8.hpp>
#include <components/version/version.hpp>
#include <components/vfs/manager.hpp>
#include <components/vfs/registerarchives.hpp>
@ -143,6 +143,7 @@ namespace NavMeshTool
}
Files::ConfigurationManager config;
config.processPaths(variables, std::filesystem::current_path());
config.readConfiguration(variables, desc);
Debug::setupLogging(config.getLogPath(), applicationName);
@ -188,7 +189,7 @@ namespace NavMeshTool
VFS::Manager vfs;
VFS::registerArchives(&vfs, fileCollections, archives, true);
VFS::registerArchives(&vfs, fileCollections, archives, true, &encoder.getStatelessEncoder());
Settings::Manager::load(config);

View file

@ -201,8 +201,8 @@ namespace NavMeshTool
{
if (!land.has_value() || osg::Vec2i(land->mX, land->mY) != cellPosition
|| (land->mDataTypes & ESM::Land::DATA_VHGT) == 0)
return { HeightfieldPlane{ ESM::Land::DEFAULT_HEIGHT }, ESM::Land::DEFAULT_HEIGHT,
ESM::Land::DEFAULT_HEIGHT };
return { HeightfieldPlane{ static_cast<float>(ESM::Land::DEFAULT_HEIGHT) },
static_cast<float>(ESM::Land::DEFAULT_HEIGHT), static_cast<float>(ESM::Land::DEFAULT_HEIGHT) };
ESM::Land::LandData& landData = *landDatas.emplace_back(std::make_unique<ESM::Land::LandData>());
land->loadData(ESM::Land::DATA_VHGT, landData);

View file

@ -113,7 +113,7 @@ bool isBSA(const std::filesystem::path& path)
std::unique_ptr<VFS::Archive> makeArchive(const std::filesystem::path& path)
{
if (isBSA(path))
return VFS::makeBsaArchive(path);
return VFS::makeBsaArchive(path, nullptr);
if (std::filesystem::is_directory(path))
return std::make_unique<VFS::FileSystemArchive>(path);
return nullptr;
@ -198,7 +198,7 @@ void readVFS(std::unique_ptr<VFS::Archive>&& archive, const std::filesystem::pat
{
try
{
readVFS(VFS::makeBsaArchive(file.second), file.second, quiet);
readVFS(VFS::makeBsaArchive(file.second, nullptr), file.second, quiet);
}
catch (const std::exception& e)
{

View file

@ -88,7 +88,7 @@ opencs_units (view/render
scenewidget worldspacewidget pagedworldspacewidget unpagedworldspacewidget
previewwidget editmode instancemode instanceselectionmode instancemovemode
orbitcameramode pathgridmode selectionmode pathgridselectionmode cameracontroller
cellwater terraintexturemode actor terrainselection terrainshapemode brushdraw commands
cellwater terraintexturemode actor terrainselection terrainshapemode brushdraw commands objectmarker
)
opencs_units (view/render

View file

@ -34,7 +34,7 @@
#include <components/misc/rng.hpp>
#include <components/nifosg/nifloader.hpp>
#include <components/settings/settings.hpp>
#include <components/to_utf8/to_utf8.hpp>
#include <components/toutf8/toutf8.hpp>
#include "view/doc/viewmanager.hpp"

View file

@ -12,7 +12,7 @@
#include <apps/opencs/model/world/universalid.hpp>
#include <components/files/multidircollection.hpp>
#include <components/to_utf8/to_utf8.hpp>
#include <components/toutf8/toutf8.hpp>
#include "../world/data.hpp"
#include "../world/idcompletionmanager.hpp"

View file

@ -9,7 +9,7 @@
#include <vector>
#include <components/files/multidircollection.hpp>
#include <components/to_utf8/to_utf8.hpp>
#include <components/toutf8/toutf8.hpp>
#include "loader.hpp"

View file

@ -2,11 +2,8 @@
#include <utility>
#if defined(Q_OS_MAC)
#include <QCoreApplication>
#include <QDir>
#endif
#include <QProcess>
#include <QString>
#include <QStringList>
@ -55,16 +52,17 @@ void CSMDoc::Runner::start(bool delayed)
QString path = "openmw";
#ifdef Q_OS_WIN
path.append(QString(".exe"));
#elif defined(Q_OS_MAC)
QDir dir(QCoreApplication::applicationDirPath());
dir.cdUp();
dir.cdUp();
dir.cdUp();
path = dir.absoluteFilePath(path.prepend("OpenMW.app/Contents/MacOS/"));
#else
path.prepend(QString("./"));
path.append(QLatin1String(".exe"));
#endif
QDir dir(QCoreApplication::applicationDirPath());
#ifdef Q_OS_MAC
// the CS and engine are in separate .app directories
dir.cdUp();
dir.cdUp();
dir.cdUp();
path.prepend("OpenMW.app/Contents/MacOS/");
#endif
path = dir.absoluteFilePath(path);
mStartup = new QTemporaryFile(this);
mStartup->open();

View file

@ -3,7 +3,7 @@
#include <QObject>
#include <components/to_utf8/to_utf8.hpp>
#include <components/toutf8/toutf8.hpp>
#include "operation.hpp"
#include "savingstate.hpp"

View file

@ -9,7 +9,7 @@
#include <components/esm3/esmwriter.hpp>
#include <components/misc/algorithm.hpp>
#include <components/to_utf8/to_utf8.hpp>
#include <components/toutf8/toutf8.hpp>
namespace CSMDoc
{

View file

@ -78,7 +78,7 @@ namespace CSMPrefs
}
}
bool ShortcutManager::getModifier(const std::string& name, int& modifier) const
bool ShortcutManager::getModifier(std::string_view name, int& modifier) const
{
ModifierMap::const_iterator item = mModifiers.find(name);
if (item != mModifiers.end())
@ -175,14 +175,14 @@ namespace CSMPrefs
return concat;
}
void ShortcutManager::convertFromString(const std::string& data, QKeySequence& sequence) const
void ShortcutManager::convertFromString(std::string_view data, QKeySequence& sequence) const
{
const int MaxKeys = 4; // A limitation of QKeySequence
size_t end = data.find(';');
size_t size = std::min(end, data.size());
std::string value = data.substr(0, size);
std::string_view value = data.substr(0, size);
size_t start = 0;
int keyPos = 0;
@ -195,7 +195,7 @@ namespace CSMPrefs
end = data.find('+', start);
end = std::min(end, value.size());
std::string name = value.substr(start, end - start);
std::string_view name = value.substr(start, end - start);
if (name == "Ctrl")
{
@ -242,12 +242,12 @@ namespace CSMPrefs
sequence = QKeySequence(keys[0], keys[1], keys[2], keys[3]);
}
void ShortcutManager::convertFromString(const std::string& data, int& modifier) const
void ShortcutManager::convertFromString(std::string_view data, int& modifier) const
{
size_t start = data.find(';') + 1;
start = std::min(start, data.size());
std::string name = data.substr(start);
std::string_view name = data.substr(start);
KeyMap::const_iterator searchResult = mKeys.find(name);
if (searchResult != mKeys.end())
{
@ -259,7 +259,7 @@ namespace CSMPrefs
}
}
void ShortcutManager::convertFromString(const std::string& data, QKeySequence& sequence, int& modifier) const
void ShortcutManager::convertFromString(std::string_view data, QKeySequence& sequence, int& modifier) const
{
convertFromString(data, sequence);
convertFromString(data, modifier);

View file

@ -31,7 +31,7 @@ namespace CSMPrefs
bool getSequence(std::string_view name, QKeySequence& sequence) const;
void setSequence(std::string_view name, const QKeySequence& sequence);
bool getModifier(const std::string& name, int& modifier) const;
bool getModifier(std::string_view name, int& modifier) const;
void setModifier(std::string_view name, int modifier);
std::string convertToString(const QKeySequence& sequence) const;
@ -39,10 +39,10 @@ namespace CSMPrefs
std::string convertToString(const QKeySequence& sequence, int modifier) const;
void convertFromString(const std::string& data, QKeySequence& sequence) const;
void convertFromString(const std::string& data, int& modifier) const;
void convertFromString(std::string_view data, QKeySequence& sequence) const;
void convertFromString(std::string_view data, int& modifier) const;
void convertFromString(const std::string& data, QKeySequence& sequence, int& modifier) const;
void convertFromString(std::string_view data, QKeySequence& sequence, int& modifier) const;
/// Replaces "{sequence-name}" or "{modifier-name}" with the appropriate text
QString processToolTip(const QString& toolTip) const;
@ -53,7 +53,7 @@ namespace CSMPrefs
typedef std::map<std::string, QKeySequence, std::less<>> SequenceMap;
typedef std::map<std::string, int, std::less<>> ModifierMap;
typedef std::map<int, std::string> NameMap;
typedef std::map<std::string, int> KeyMap;
typedef std::map<std::string, int, std::less<>> KeyMap;
ShortcutMap mShortcuts;
SequenceMap mSequences;

View file

@ -180,7 +180,10 @@ void CSMPrefs::State::declare()
declareInt(mValues->mRendering.mCameraOrthoSize, "Orthographic Projection Size Parameter")
.setTooltip("Size of the orthographic frustum, greater value will allow the camera to see more of the world.")
.setRange(10, 10000);
declareDouble(mValues->mRendering.mObjectMarkerAlpha, "Object Marker Transparency").setPrecision(2).setRange(0, 1);
declareDouble(mValues->mRendering.mObjectMarkerScale, "Object Marker Scale Factor")
.setPrecision(2)
.setRange(.01f, 100.f)
.setTooltip("Multiplier for the size of object selection markers.");
declareBool(mValues->mRendering.mSceneUseGradient, "Use Gradient Background");
declareColour(mValues->mRendering.mSceneDayBackgroundColour, "Day Background Colour");
declareColour(mValues->mRendering.mSceneDayGradientColour, "Day Gradient Colour")
@ -376,6 +379,7 @@ void CSMPrefs::State::declare()
declareShortcut(mValues->mKeyBindings.mSceneScaleSubmode, "Scale Object Submode");
declareShortcut(mValues->mKeyBindings.mSceneRotateSubmode, "Rotate Object Submode");
declareShortcut(mValues->mKeyBindings.mSceneCameraCycle, "Cycle Camera Mode");
declareShortcut(mValues->mKeyBindings.mSceneToggleMarker, "Toggle Selection Marker");
declareSubcategory("1st/Free Camera");
declareShortcut(mValues->mKeyBindings.mFreeForward, "Forward");
@ -498,7 +502,7 @@ CSMPrefs::ShortcutSetting& CSMPrefs::State::declareShortcut(
// Setup with actual data
QKeySequence sequence;
getShortcutManager().convertFromString(value, sequence);
getShortcutManager().convertFromString(value.get(), sequence);
getShortcutManager().setSequence(value.mName, sequence);
CSMPrefs::ShortcutSetting* setting

View file

@ -258,7 +258,7 @@ namespace CSMPrefs
Settings::SettingValue<int> mCameraFov{ mIndex, sName, "camera-fov", 90 };
Settings::SettingValue<bool> mCameraOrtho{ mIndex, sName, "camera-ortho", false };
Settings::SettingValue<int> mCameraOrthoSize{ mIndex, sName, "camera-ortho-size", 100 };
Settings::SettingValue<double> mObjectMarkerAlpha{ mIndex, sName, "object-marker-alpha", 0.5 };
Settings::SettingValue<double> mObjectMarkerScale{ mIndex, sName, "object-marker-scale", 5.0 };
Settings::SettingValue<bool> mSceneUseGradient{ mIndex, sName, "scene-use-gradient", true };
Settings::SettingValue<std::string> mSceneDayBackgroundColour{ mIndex, sName, "scene-day-background-colour",
"#6e7880" };
@ -491,7 +491,7 @@ namespace CSMPrefs
Settings::SettingValue<std::string> mSceneScaleSubmode{ mIndex, sName, "scene-submode-scale", "V" };
Settings::SettingValue<std::string> mSceneRotateSubmode{ mIndex, sName, "scene-submode-rotate", "R" };
Settings::SettingValue<std::string> mSceneCameraCycle{ mIndex, sName, "scene-cam-cycle", "Tab" };
Settings::SettingValue<std::string> mSceneToggleMarkers{ mIndex, sName, "scene-toggle-markers", "F4" };
Settings::SettingValue<std::string> mSceneToggleMarker{ mIndex, sName, "scene-toggle-marker", "F4" };
Settings::SettingValue<std::string> mFreeForward{ mIndex, sName, "free-forward", "W" };
Settings::SettingValue<std::string> mFreeBackward{ mIndex, sName, "free-backward", "S" };
Settings::SettingValue<std::string> mFreeLeft{ mIndex, sName, "free-left", "A" };

View file

@ -3,7 +3,7 @@
#include <memory>
#include <components/to_utf8/to_utf8.hpp>
#include <components/toutf8/toutf8.hpp>
#include "../doc/operation.hpp"

View file

@ -9,7 +9,7 @@
#include <apps/opencs/model/world/idcollection.hpp>
#include <apps/opencs/model/world/record.hpp>
#include <components/to_utf8/to_utf8.hpp>
#include <components/toutf8/toutf8.hpp>
#include "../doc/stage.hpp"

View file

@ -6,7 +6,7 @@
#include <apps/opencs/model/world/universalid.hpp>
#include <components/to_utf8/to_utf8.hpp>
#include <components/toutf8/toutf8.hpp>
#include <QObject>

View file

@ -143,7 +143,7 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data
, mArchives(archives)
, mVFS(std::make_unique<VFS::Manager>())
{
VFS::registerArchives(mVFS.get(), Files::Collections(mDataPaths), mArchives, true);
VFS::registerArchives(mVFS.get(), Files::Collections(mDataPaths), mArchives, true, &mEncoder.getStatelessEncoder());
mResourcesManager.setVFS(mVFS.get());
@ -1465,7 +1465,7 @@ std::vector<ESM::RefId> CSMWorld::Data::getIds(bool listDeleted) const
void CSMWorld::Data::assetsChanged()
{
mVFS.get()->reset();
VFS::registerArchives(mVFS.get(), Files::Collections(mDataPaths), mArchives, true);
VFS::registerArchives(mVFS.get(), Files::Collections(mDataPaths), mArchives, true, &mEncoder.getStatelessEncoder());
const UniversalId assetTableIds[] = { UniversalId::Type_Meshes, UniversalId::Type_Icons, UniversalId::Type_Musics,
UniversalId::Type_SoundsRes, UniversalId::Type_Textures, UniversalId::Type_Videos };

View file

@ -38,7 +38,7 @@
#include <components/esm3/selectiongroup.hpp>
#include <components/files/multidircollection.hpp>
#include <components/misc/algorithm.hpp>
#include <components/to_utf8/to_utf8.hpp>
#include <components/toutf8/toutf8.hpp>
#include "cell.hpp"
#include "idcollection.hpp"

View file

@ -625,8 +625,6 @@ bool CSMWorld::ConstInfoSelectWrapper::conditionIsAlwaysTrue(
default:
throw std::logic_error("InfoCondition: operator can not be used to compare");
}
return false;
}
template <typename T1, typename T2>
@ -651,8 +649,6 @@ bool CSMWorld::ConstInfoSelectWrapper::conditionIsNeverTrue(
default:
throw std::logic_error("InfoCondition: operator can not be used to compare");
}
return false;
}
QVariant CSMWorld::ConstInfoSelectWrapper::getValue() const

View file

@ -753,7 +753,6 @@ void CSMWorld::RefIdCollection::cloneRecord(
bool CSMWorld::RefIdCollection::touchRecord(const ESM::RefId& id)
{
throw std::runtime_error("RefIdCollection::touchRecord is unimplemented");
return false;
}
void CSMWorld::RefIdCollection::appendRecord(std::unique_ptr<RecordBase> record, UniversalId::Type type)

View file

@ -25,9 +25,10 @@
#include "cellwater.hpp"
#include "instancedragmodes.hpp"
#include "mask.hpp"
#include "object.hpp"
#include "objectmarker.hpp"
#include "pathgrid.hpp"
#include "terrainstorage.hpp"
#include "worldspacewidget.hpp"
#include <apps/opencs/model/world/cell.hpp>
#include <apps/opencs/model/world/cellcoordinates.hpp>
@ -107,9 +108,6 @@ bool CSVRender::Cell::addObjects(int start, int end)
auto object = std::make_unique<Object>(mData, mCellNode, id, false);
if (mSubModeElementMask & Mask_Reference)
object->setSubMode(mSubMode);
mObjects.insert(std::make_pair(id, object.release()));
modified = true;
}
@ -168,9 +166,10 @@ void CSVRender::Cell::unloadLand()
mCellBorder.reset();
}
CSVRender::Cell::Cell(
CSMDoc::Document& document, osg::Group* rootNode, const std::string& id, bool deleted, bool isExterior)
: mData(document.getData())
CSVRender::Cell::Cell(CSMDoc::Document& document, ObjectMarker* selectionMarker, osg::Group* rootNode,
const std::string& id, bool deleted, bool isExterior)
: mSelectionMarker(selectionMarker)
, mData(document.getData())
, mId(ESM::RefId::stringRefId(id))
, mDeleted(deleted)
, mSubMode(0)
@ -466,7 +465,10 @@ void CSVRender::Cell::setSelection(int elementMask, Selection mode)
}
iter->second->setSelected(selected);
if (selected)
mSelectionMarker->addToSelectionHistory(iter->second->getReferenceId(), false);
}
mSelectionMarker->updateSelectionMarker();
}
if (mPathgrid && elementMask & Mask_Pathgrid)
{
@ -506,8 +508,10 @@ void CSVRender::Cell::selectAllWithSameParentId(int elementMask)
if (!iter->second->getSelected() && ids.find(iter->second->getReferenceableId()) != ids.end())
{
iter->second->setSelected(true);
mSelectionMarker->addToSelectionHistory(iter->second->getReferenceId(), false);
}
}
mSelectionMarker->updateSelectionMarker();
}
void CSVRender::Cell::handleSelectDrag(Object* object, DragMode dragMode)
@ -520,6 +524,9 @@ void CSVRender::Cell::handleSelectDrag(Object* object, DragMode dragMode)
else if (dragMode == DragMode_Select_Invert)
object->setSelected(!object->getSelected());
if (object->getSelected())
mSelectionMarker->addToSelectionHistory(object->getReferenceId(), false);
}
void CSVRender::Cell::selectInsideCube(const osg::Vec3d& pointA, const osg::Vec3d& pointB, DragMode dragMode)
@ -542,6 +549,8 @@ void CSVRender::Cell::selectInsideCube(const osg::Vec3d& pointA, const osg::Vec3
}
}
}
mSelectionMarker->updateSelectionMarker();
}
void CSVRender::Cell::selectWithinDistance(const osg::Vec3d& point, float distance, DragMode dragMode)
@ -555,6 +564,8 @@ void CSVRender::Cell::selectWithinDistance(const osg::Vec3d& point, float distan
if (distanceFromObject < distance)
handleSelectDrag(object.second, dragMode);
}
mSelectionMarker->updateSelectionMarker();
}
void CSVRender::Cell::setCellArrows(int mask)
@ -625,9 +636,11 @@ void CSVRender::Cell::selectFromGroup(const std::vector<std::string>& group)
if (objectName == object->getReferenceId())
{
object->setSelected(true, osg::Vec4f(1, 0, 1, 1));
mSelectionMarker->addToSelectionHistory(object->getReferenceId(), false);
}
}
}
mSelectionMarker->updateSelectionMarker();
}
void CSVRender::Cell::unhideAll()
@ -673,8 +686,7 @@ void CSVRender::Cell::setSubMode(int subMode, unsigned int elementMask)
mSubModeElementMask = elementMask;
if (elementMask & Mask_Reference)
for (std::map<std::string, Object*>::const_iterator iter(mObjects.begin()); iter != mObjects.end(); ++iter)
iter->second->setSubMode(subMode);
mSelectionMarker->setSubMode(subMode);
}
void CSVRender::Cell::reset(unsigned int elementMask)
@ -685,3 +697,11 @@ void CSVRender::Cell::reset(unsigned int elementMask)
if (mPathgrid && elementMask & Mask_Pathgrid)
mPathgrid->resetIndicators();
}
CSVRender::Object* CSVRender::Cell::getObjectByReferenceId(const std::string& referenceId)
{
if (auto iter = mObjects.find(Misc::StringUtils::lowerCase(referenceId)); iter != mObjects.end())
return iter->second;
else
return nullptr;
}

View file

@ -9,9 +9,9 @@
#include <osg/Vec3d>
#include <osg/ref_ptr>
#include "../../model/doc/document.hpp"
#include "../../model/world/cellcoordinates.hpp"
#include "instancedragmodes.hpp"
#include "worldspacewidget.hpp"
#include <components/esm/refid.hpp>
#include <components/misc/algorithm.hpp>
@ -44,8 +44,11 @@ namespace CSVRender
class CellBorder;
class CellMarker;
class ObjectMarker;
class Cell
{
ObjectMarker* const mSelectionMarker;
CSMWorld::Data& mData;
ESM::RefId mId;
osg::ref_ptr<osg::Group> mCellNode;
@ -90,8 +93,8 @@ namespace CSVRender
public:
/// \note Deleted covers both cells that are deleted and cells that don't exist in
/// the first place.
Cell(CSMDoc::Document& document, osg::Group* rootNode, const std::string& id, bool deleted = false,
bool isExterior = false);
Cell(CSMDoc::Document& document, ObjectMarker* selectionMarker, osg::Group* rootNode, const std::string& id,
bool deleted = false, bool isExterior = false);
~Cell();
@ -182,6 +185,8 @@ namespace CSVRender
/// true state.
void reset(unsigned int elementMask);
CSVRender::Object* getObjectByReferenceId(const std::string& referenceId);
friend class CellNodeCallback;
};
}

View file

@ -171,16 +171,18 @@ osg::Vec3f CSVRender::InstanceMode::getProjectionSpaceCoords(const osg::Vec3f& p
osg::Vec3f CSVRender::InstanceMode::getMousePlaneCoords(const QPoint& point, const osg::Vec3d& dragStart)
{
osg::Matrix viewMatrix;
viewMatrix.invert(getWorldspaceWidget().getCamera()->getViewMatrix());
osg::Matrix projMatrix;
projMatrix.invert(getWorldspaceWidget().getCamera()->getProjectionMatrix());
osg::Matrix combined = projMatrix * viewMatrix;
const osg::Matrix viewMatrix = getWorldspaceWidget().getCamera()->getViewMatrix();
const osg::Matrix projMatrix = getWorldspaceWidget().getCamera()->getProjectionMatrix();
const osg::Matrix combined = osg::Matrix::inverse(viewMatrix * projMatrix);
/* calculate viewport normalized coordinates
note: is there a reason to use getCamera()->getViewport()->computeWindowMatrix() instead? */
float x = (point.x() * 2) / getWorldspaceWidget().getCamera()->getViewport()->width() - 1.0f;
float y = 1.0f - (point.y() * 2) / getWorldspaceWidget().getCamera()->getViewport()->height();
const float scale = getWorldspaceWidget().devicePixelRatioF();
const osg::Viewport* viewport = getWorldspaceWidget().getCamera()->getViewport();
float x = point.x() * scale / viewport->width();
float y = point.y() * scale / viewport->height();
x = x * 2.0f - 1.0f;
y = 1.0f - y * 2.0f;
osg::Vec3f mousePlanePoint = osg::Vec3f(x, y, dragStart.z()) * combined;
@ -360,7 +362,29 @@ CSVRender::InstanceMode::InstanceMode(
for (const char axis : "xyz")
connect(new CSMPrefs::Shortcut(std::string("scene-axis-") + axis, worldspaceWidget),
qOverload<>(&CSMPrefs::Shortcut::activated), this, [this, axis] { this->setDragAxis(axis); });
qOverload<>(&CSMPrefs::Shortcut::activated), this, [this, axis] {
this->setDragAxis(axis);
std::string axisStr(1, toupper(axis));
switch (getSubMode())
{
case (Object::Mode_Move):
axisStr += "_Axis";
break;
case (Object::Mode_Rotate):
axisStr += "_Axis_Rot";
break;
case (Object::Mode_Scale):
axisStr += "_Axis_Scale";
break;
}
auto selectionMarker = getWorldspaceWidget().getSelectionMarker();
if (mDragAxis != -1)
selectionMarker->updateMarkerHighlight(axisStr, axis - 'x');
else
selectionMarker->resetMarkerHighlight();
});
}
void CSVRender::InstanceMode::activate(CSVWidget::SceneToolbar* toolbar)
@ -458,52 +482,58 @@ void CSVRender::InstanceMode::secondaryEditPressed(const WorldspaceHitResult& hi
void CSVRender::InstanceMode::primarySelectPressed(const WorldspaceHitResult& hit)
{
getWorldspaceWidget().clearSelection(Mask_Reference);
auto& worldspaceWidget = getWorldspaceWidget();
if (hit.tag)
worldspaceWidget.clearSelection(Mask_Reference);
if (!hit.tag)
return;
if (CSVRender::ObjectTag* objectTag = dynamic_cast<CSVRender::ObjectTag*>(hit.tag.get()))
{
if (CSVRender::ObjectTag* objectTag = dynamic_cast<CSVRender::ObjectTag*>(hit.tag.get()))
{
// hit an Object, select it
CSVRender::Object* object = objectTag->mObject;
object->setSelected(true);
return;
}
// hit an Object, select it
CSVRender::Object* object = objectTag->mObject;
object->setSelected(true);
worldspaceWidget.getSelectionMarker()->addToSelectionHistory(object->getReferenceId());
}
}
void CSVRender::InstanceMode::secondarySelectPressed(const WorldspaceHitResult& hit)
{
if (hit.tag)
if (!hit.tag)
return;
if (CSVRender::ObjectTag* objectTag = dynamic_cast<CSVRender::ObjectTag*>(hit.tag.get()))
{
if (CSVRender::ObjectTag* objectTag = dynamic_cast<CSVRender::ObjectTag*>(hit.tag.get()))
{
// hit an Object, toggle its selection state
CSVRender::Object* object = objectTag->mObject;
object->setSelected(!object->getSelected());
return;
}
// hit an Object, toggle its selection state
CSVRender::Object* object = objectTag->mObject;
object->setSelected(!object->getSelected());
const auto selectionMarker = getWorldspaceWidget().getSelectionMarker();
if (object->getSelected())
selectionMarker->addToSelectionHistory(object->getReferenceId(), false);
selectionMarker->updateSelectionMarker();
}
}
void CSVRender::InstanceMode::tertiarySelectPressed(const WorldspaceHitResult& hit)
{
auto* snapTarget = dynamic_cast<CSVRender::ObjectTag*>(getWorldspaceWidget().getSnapTarget(Mask_Reference).get());
if (snapTarget)
if (auto* snapTarget
= dynamic_cast<CSVRender::ObjectTag*>(getWorldspaceWidget().getSnapTarget(Mask_Reference).get()))
{
snapTarget->mObject->setSnapTarget(false);
}
if (hit.tag)
if (!hit.tag)
return;
if (CSVRender::ObjectTag* objectTag = dynamic_cast<CSVRender::ObjectTag*>(hit.tag.get()))
{
if (CSVRender::ObjectTag* objectTag = dynamic_cast<CSVRender::ObjectTag*>(hit.tag.get()))
{
// hit an Object, toggle its selection state
CSVRender::Object* object = objectTag->mObject;
object->setSnapTarget(!object->getSnapTarget());
return;
}
// hit an Object, toggle its selection state
CSVRender::Object* object = objectTag->mObject;
object->setSnapTarget(!object->getSnapTarget());
}
}
@ -512,23 +542,26 @@ bool CSVRender::InstanceMode::primaryEditStartDrag(const QPoint& pos)
if (mDragMode != DragMode_None || mLocked)
return false;
WorldspaceHitResult hit = getWorldspaceWidget().mousePick(pos, getWorldspaceWidget().getInteractionMask());
auto& worldspaceWidget = getWorldspaceWidget();
std::vector<osg::ref_ptr<TagBase>> selection = getWorldspaceWidget().getSelection(Mask_Reference);
WorldspaceHitResult hit = worldspaceWidget.mousePick(pos, worldspaceWidget.getInteractionMask());
std::vector<osg::ref_ptr<TagBase>> selection = worldspaceWidget.getSelection(Mask_Reference);
if (selection.empty())
{
// Only change selection at the start of drag if no object is already selected
if (hit.tag && CSMPrefs::get()["3D Scene Input"]["context-select"].isTrue())
{
getWorldspaceWidget().clearSelection(Mask_Reference);
worldspaceWidget.clearSelection(Mask_Reference);
if (CSVRender::ObjectTag* objectTag = dynamic_cast<CSVRender::ObjectTag*>(hit.tag.get()))
{
CSVRender::Object* object = objectTag->mObject;
object->setSelected(true);
worldspaceWidget.getSelectionMarker()->addToSelectionHistory(object->getReferenceId());
}
}
selection = getWorldspaceWidget().getSelection(Mask_Reference);
selection = worldspaceWidget.getSelection(Mask_Reference);
if (selection.empty())
return false;
}
@ -589,23 +622,26 @@ bool CSVRender::InstanceMode::secondaryEditStartDrag(const QPoint& pos)
if (mDragMode != DragMode_None || mLocked)
return false;
WorldspaceHitResult hit = getWorldspaceWidget().mousePick(pos, getWorldspaceWidget().getInteractionMask());
auto& worldspaceWidget = getWorldspaceWidget();
std::vector<osg::ref_ptr<TagBase>> selection = getWorldspaceWidget().getSelection(Mask_Reference);
WorldspaceHitResult hit = worldspaceWidget.mousePick(pos, worldspaceWidget.getInteractionMask());
std::vector<osg::ref_ptr<TagBase>> selection = worldspaceWidget.getSelection(Mask_Reference);
if (selection.empty())
{
// Only change selection at the start of drag if no object is already selected
if (hit.tag && CSMPrefs::get()["3D Scene Input"]["context-select"].isTrue())
{
getWorldspaceWidget().clearSelection(Mask_Reference);
worldspaceWidget.clearSelection(Mask_Reference);
if (CSVRender::ObjectTag* objectTag = dynamic_cast<CSVRender::ObjectTag*>(hit.tag.get()))
{
CSVRender::Object* object = objectTag->mObject;
object->setSelected(true);
worldspaceWidget.getSelectionMarker()->addToSelectionHistory(object->getReferenceId());
}
}
selection = getWorldspaceWidget().getSelection(Mask_Reference);
selection = worldspaceWidget.getSelection(Mask_Reference);
if (selection.empty())
return false;
}
@ -639,10 +675,10 @@ bool CSVRender::InstanceMode::secondaryEditStartDrag(const QPoint& pos)
mDragMode = DragMode_Scale_Snap;
// Calculate scale factor
std::vector<osg::ref_ptr<TagBase>> editedSelection = getWorldspaceWidget().getEdited(Mask_Reference);
std::vector<osg::ref_ptr<TagBase>> editedSelection = worldspaceWidget.getEdited(Mask_Reference);
osg::Vec3f center = getScreenCoords(getSelectionCenter(editedSelection));
int widgetHeight = getWorldspaceWidget().height();
int widgetHeight = worldspaceWidget.height();
float dx = pos.x() - center.x();
float dy = (widgetHeight - pos.y()) - center.y();

View file

@ -18,25 +18,11 @@
#include <apps/opencs/model/world/universalid.hpp>
#include <apps/opencs/view/render/tagbase.hpp>
#include <osg/Array>
#include <osg/BoundingSphere>
#include <osg/GL>
#include <osg/Geometry>
#include <osg/Group>
#include <osg/Math>
#include <osg/Node>
#include <osg/PositionAttitudeTransform>
#include <osg/PrimitiveSet>
#include <osg/Quat>
#include <osg/Shape>
#include <osg/ShapeDrawable>
#include <osg/StateAttribute>
#include <osg/StateSet>
#include <osg/Vec3>
#include <osgFX/Scribe>
#include "../../model/prefs/state.hpp"
#include "../../model/world/cellcoordinates.hpp"
#include "../../model/world/commandmacro.hpp"
#include "../../model/world/commands.hpp"
@ -63,11 +49,6 @@ namespace ESM
struct Light;
}
const float CSVRender::Object::MarkerShaftWidth = 30;
const float CSVRender::Object::MarkerShaftBaseLength = 70;
const float CSVRender::Object::MarkerHeadWidth = 50;
const float CSVRender::Object::MarkerHeadLength = 50;
namespace
{
@ -95,12 +76,6 @@ QString CSVRender::ObjectTag::getToolTip(bool /*hideBasics*/, const WorldspaceHi
return QString::fromUtf8(mObject->getReferenceableId().c_str());
}
CSVRender::ObjectMarkerTag::ObjectMarkerTag(Object* object, int axis)
: ObjectTag(object)
, mAxis(axis)
{
}
void CSVRender::Object::clear() {}
void CSVRender::Object::update()
@ -204,238 +179,6 @@ const CSMWorld::CellRef& CSVRender::Object::getReference() const
return mData.getReferences().getRecord(mReferenceId).get();
}
void CSVRender::Object::updateMarker()
{
for (int i = 0; i < 3; ++i)
{
if (mMarker[i])
{
mRootNode->removeChild(mMarker[i]);
mMarker[i] = osg::ref_ptr<osg::Node>();
}
if (mSelected)
{
if (mSubMode == 0)
{
mMarker[i] = makeMoveOrScaleMarker(i);
mMarker[i]->setUserData(new ObjectMarkerTag(this, i));
mRootNode->addChild(mMarker[i]);
}
else if (mSubMode == 1)
{
mMarker[i] = makeRotateMarker(i);
mMarker[i]->setUserData(new ObjectMarkerTag(this, i));
mRootNode->addChild(mMarker[i]);
}
else if (mSubMode == 2)
{
mMarker[i] = makeMoveOrScaleMarker(i);
mMarker[i]->setUserData(new ObjectMarkerTag(this, i));
mRootNode->addChild(mMarker[i]);
}
}
}
}
osg::ref_ptr<osg::Node> CSVRender::Object::makeMoveOrScaleMarker(int axis)
{
osg::ref_ptr<osg::Geometry> geometry(new osg::Geometry);
float shaftLength = MarkerShaftBaseLength + mBaseNode->getBound().radius();
// shaft
osg::Vec3Array* vertices = new osg::Vec3Array;
for (int i = 0; i < 2; ++i)
{
float length = i ? shaftLength : MarkerShaftWidth;
vertices->push_back(getMarkerPosition(-MarkerShaftWidth / 2, -MarkerShaftWidth / 2, length, axis));
vertices->push_back(getMarkerPosition(-MarkerShaftWidth / 2, MarkerShaftWidth / 2, length, axis));
vertices->push_back(getMarkerPosition(MarkerShaftWidth / 2, MarkerShaftWidth / 2, length, axis));
vertices->push_back(getMarkerPosition(MarkerShaftWidth / 2, -MarkerShaftWidth / 2, length, axis));
}
// head backside
vertices->push_back(getMarkerPosition(-MarkerHeadWidth / 2, -MarkerHeadWidth / 2, shaftLength, axis));
vertices->push_back(getMarkerPosition(-MarkerHeadWidth / 2, MarkerHeadWidth / 2, shaftLength, axis));
vertices->push_back(getMarkerPosition(MarkerHeadWidth / 2, MarkerHeadWidth / 2, shaftLength, axis));
vertices->push_back(getMarkerPosition(MarkerHeadWidth / 2, -MarkerHeadWidth / 2, shaftLength, axis));
// head
vertices->push_back(getMarkerPosition(0, 0, shaftLength + MarkerHeadLength, axis));
geometry->setVertexArray(vertices);
osg::DrawElementsUShort* primitives = new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLES, 0);
// shaft
for (int i = 0; i < 4; ++i)
{
int i2 = i == 3 ? 0 : i + 1;
primitives->push_back(i);
primitives->push_back(4 + i);
primitives->push_back(i2);
primitives->push_back(4 + i);
primitives->push_back(4 + i2);
primitives->push_back(i2);
}
// cap
primitives->push_back(0);
primitives->push_back(1);
primitives->push_back(2);
primitives->push_back(2);
primitives->push_back(3);
primitives->push_back(0);
// head, backside
primitives->push_back(0 + 8);
primitives->push_back(1 + 8);
primitives->push_back(2 + 8);
primitives->push_back(2 + 8);
primitives->push_back(3 + 8);
primitives->push_back(0 + 8);
for (int i = 0; i < 4; ++i)
{
primitives->push_back(12);
primitives->push_back(8 + (i == 3 ? 0 : i + 1));
primitives->push_back(8 + i);
}
geometry->addPrimitiveSet(primitives);
osg::Vec4Array* colours = new osg::Vec4Array;
for (int i = 0; i < 8; ++i)
colours->push_back(
osg::Vec4f(axis == 0 ? 1.0f : 0.2f, axis == 1 ? 1.0f : 0.2f, axis == 2 ? 1.0f : 0.2f, mMarkerTransparency));
for (int i = 8; i < 8 + 4 + 1; ++i)
colours->push_back(
osg::Vec4f(axis == 0 ? 1.0f : 0.0f, axis == 1 ? 1.0f : 0.0f, axis == 2 ? 1.0f : 0.0f, mMarkerTransparency));
geometry->setColorArray(colours, osg::Array::BIND_PER_VERTEX);
setupCommonMarkerState(geometry);
osg::ref_ptr<osg::Group> group(new osg::Group);
group->addChild(geometry);
return group;
}
osg::ref_ptr<osg::Node> CSVRender::Object::makeRotateMarker(int axis)
{
const float InnerRadius = std::max(MarkerShaftBaseLength, mBaseNode->getBound().radius());
const float OuterRadius = InnerRadius + MarkerShaftWidth;
const float SegmentDistance = 100.f;
const size_t SegmentCount = std::clamp<int>(OuterRadius * 2 * osg::PI / SegmentDistance, 24, 64);
const size_t VerticesPerSegment = 4;
const size_t IndicesPerSegment = 24;
const size_t VertexCount = SegmentCount * VerticesPerSegment;
const size_t IndexCount = SegmentCount * IndicesPerSegment;
const float Angle = 2 * osg::PI / SegmentCount;
const unsigned short IndexPattern[IndicesPerSegment]
= { 0, 4, 5, 0, 5, 1, 2, 6, 4, 2, 4, 0, 3, 7, 6, 3, 6, 2, 1, 5, 7, 1, 7, 3 };
osg::ref_ptr<osg::Geometry> geometry = new osg::Geometry();
osg::ref_ptr<osg::Vec3Array> vertices = new osg::Vec3Array(VertexCount);
osg::ref_ptr<osg::Vec4Array> colors = new osg::Vec4Array(1);
osg::ref_ptr<osg::DrawElementsUShort> primitives
= new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLES, IndexCount);
// prevent some depth collision issues from overlaps
osg::Vec3f offset = getMarkerPosition(0, MarkerShaftWidth / 4, 0, axis);
for (size_t i = 0; i < SegmentCount; ++i)
{
size_t index = i * VerticesPerSegment;
float innerX = InnerRadius * std::cos(i * Angle);
float innerY = InnerRadius * std::sin(i * Angle);
float outerX = OuterRadius * std::cos(i * Angle);
float outerY = OuterRadius * std::sin(i * Angle);
vertices->at(index++) = getMarkerPosition(innerX, innerY, MarkerShaftWidth / 2, axis) + offset;
vertices->at(index++) = getMarkerPosition(innerX, innerY, -MarkerShaftWidth / 2, axis) + offset;
vertices->at(index++) = getMarkerPosition(outerX, outerY, MarkerShaftWidth / 2, axis) + offset;
vertices->at(index++) = getMarkerPosition(outerX, outerY, -MarkerShaftWidth / 2, axis) + offset;
}
colors->at(0)
= osg::Vec4f(axis == 0 ? 1.0f : 0.2f, axis == 1 ? 1.0f : 0.2f, axis == 2 ? 1.0f : 0.2f, mMarkerTransparency);
for (size_t i = 0; i < SegmentCount; ++i)
{
size_t indices[IndicesPerSegment];
for (size_t j = 0; j < IndicesPerSegment; ++j)
{
indices[j] = i * VerticesPerSegment + j;
if (indices[j] >= VertexCount)
indices[j] -= VertexCount;
}
size_t elementOffset = i * IndicesPerSegment;
for (size_t j = 0; j < IndicesPerSegment; ++j)
{
primitives->setElement(elementOffset++, indices[IndexPattern[j]]);
}
}
geometry->setVertexArray(vertices);
geometry->setColorArray(colors, osg::Array::BIND_OVERALL);
geometry->addPrimitiveSet(primitives);
setupCommonMarkerState(geometry);
osg::ref_ptr<osg::Group> group = new osg::Group();
group->addChild(geometry);
return group;
}
void CSVRender::Object::setupCommonMarkerState(osg::ref_ptr<osg::Geometry> geometry)
{
osg::ref_ptr<osg::StateSet> state = geometry->getOrCreateStateSet();
state->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
state->setMode(GL_BLEND, osg::StateAttribute::ON);
state->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
}
osg::Vec3f CSVRender::Object::getMarkerPosition(float x, float y, float z, int axis)
{
switch (axis)
{
case 2:
return osg::Vec3f(x, y, z);
case 0:
return osg::Vec3f(z, x, y);
case 1:
return osg::Vec3f(y, z, x);
default:
throw std::logic_error("invalid axis for marker geometry");
}
}
CSVRender::Object::Object(
CSMWorld::Data& data, osg::Group* parentNode, const std::string& id, bool referenceable, bool forceBaseToZero)
: mData(data)
@ -446,8 +189,6 @@ CSVRender::Object::Object(
, mForceBaseToZero(forceBaseToZero)
, mScaleOverride(1)
, mOverrideFlags(0)
, mSubMode(-1)
, mMarkerTransparency(0.5f)
{
mRootNode = new osg::PositionAttitudeTransform;
@ -476,7 +217,6 @@ CSVRender::Object::Object(
adjustTransform();
update();
updateMarker();
}
CSVRender::Object::~Object()
@ -506,9 +246,6 @@ void CSVRender::Object::setSelected(bool selected, const osg::Vec4f& color)
}
else
mRootNode->addChild(mBaseNode);
mMarkerTransparency = CSMPrefs::get()["Rendering"]["object-marker-alpha"].toDouble();
updateMarker();
}
bool CSVRender::Object::getSelected() const
@ -536,9 +273,6 @@ void CSVRender::Object::setSnapTarget(bool isSnapTarget)
}
else
mRootNode->addChild(mBaseNode);
mMarkerTransparency = CSMPrefs::get()["Rendering"]["object-marker-alpha"].toDouble();
updateMarker();
}
bool CSVRender::Object::getSnapTarget() const
@ -566,7 +300,6 @@ bool CSVRender::Object::referenceableDataChanged(const QModelIndex& topLeft, con
{
adjustTransform();
update();
updateMarker();
return true;
}
@ -614,7 +347,6 @@ bool CSVRender::Object::referenceDataChanged(const QModelIndex& topLeft, const Q
= ESM::RefId::stringRefId(references.getData(index, columnIndex).toString().toUtf8().constData());
update();
updateMarker();
}
return true;
@ -626,7 +358,6 @@ bool CSVRender::Object::referenceDataChanged(const QModelIndex& topLeft, const Q
void CSVRender::Object::reloadAssets()
{
update();
updateMarker();
}
std::string CSVRender::Object::getReferenceId() const
@ -720,12 +451,6 @@ void CSVRender::Object::setScale(float scale)
adjustTransform();
}
void CSVRender::Object::setMarkerTransparency(float value)
{
mMarkerTransparency = value;
updateMarker();
}
void CSVRender::Object::apply(CSMWorld::CommandMacro& commands)
{
const CSMWorld::RefCollection& collection = mData.getReferences();
@ -796,18 +521,8 @@ void CSVRender::Object::apply(CSMWorld::CommandMacro& commands)
mOverrideFlags = 0;
}
void CSVRender::Object::setSubMode(int subMode)
{
if (subMode != mSubMode)
{
mSubMode = subMode;
updateMarker();
}
}
void CSVRender::Object::reset()
{
mOverrideFlags = 0;
adjustTransform();
updateMarker();
}

View file

@ -58,14 +58,6 @@ namespace CSVRender
QString getToolTip(bool hideBasics, const WorldspaceHitResult& hit) const override;
};
class ObjectMarkerTag : public ObjectTag
{
public:
ObjectMarkerTag(Object* object, int axis);
int mAxis;
};
class Object
{
public:
@ -76,12 +68,22 @@ namespace CSVRender
Override_Scale = 4
};
private:
static const float MarkerShaftWidth;
static const float MarkerShaftBaseLength;
static const float MarkerHeadWidth;
static const float MarkerHeadLength;
enum SubMode
{
Mode_Move,
Mode_Rotate,
Mode_Scale,
Mode_None,
};
enum Axis
{
Axis_X,
Axis_Y,
Axis_Z
};
private:
CSMWorld::Data& mData;
ESM::RefId mReferenceId;
ESM::RefId mReferenceableId;
@ -96,9 +98,6 @@ namespace CSVRender
ESM::Position mPositionOverride;
float mScaleOverride;
int mOverrideFlags;
osg::ref_ptr<osg::Node> mMarker[3];
int mSubMode;
float mMarkerTransparency;
std::unique_ptr<Actor> mActor;
/// Not implemented
@ -120,16 +119,6 @@ namespace CSVRender
/// Throws an exception if *this was constructed with referenceable
const CSMWorld::CellRef& getReference() const;
void updateMarker();
osg::ref_ptr<osg::Node> makeMoveOrScaleMarker(int axis);
osg::ref_ptr<osg::Node> makeRotateMarker(int axis);
/// Sets up a stateset with properties common to all marker types.
void setupCommonMarkerState(osg::ref_ptr<osg::Geometry> geometry);
osg::Vec3f getMarkerPosition(float x, float y, float z, int axis);
public:
Object(CSMWorld::Data& data, osg::Group* cellNode, const std::string& id, bool referenceable,
bool forceBaseToZero = false);
@ -199,8 +188,6 @@ namespace CSVRender
/// Apply override changes via command and end edit mode
void apply(CSMWorld::CommandMacro& commands);
void setSubMode(int subMode);
/// Erase all overrides and restore the visual representation of the object to its
/// true state.
void reset();

View file

@ -0,0 +1,307 @@
#include <unordered_set>
#include <QFile>
#include <osg/ClipPlane>
#include <osg/Material>
#include <osg/PositionAttitudeTransform>
#include <osgUtil/CullVisitor>
#include <components/resource/resourcesystem.hpp>
#include <components/resource/scenemanager.hpp>
#include <components/sceneutil/visitor.hpp>
#include "../../model/prefs/state.hpp"
#include "objectmarker.hpp"
#include "worldspacewidget.hpp"
namespace
{
class FindMaterialVisitor : public osg::NodeVisitor
{
public:
FindMaterialVisitor(CSVRender::NodeMap& map)
: osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN)
, mMap(map)
{
}
void apply(osg::Geometry& node) override
{
osg::StateSet* state = node.getStateSet();
if (state->getAttribute(osg::StateAttribute::MATERIAL))
mMap.emplace(node.getName(), &node);
traverse(node);
}
private:
CSVRender::NodeMap& mMap;
};
class ToCamera : public SceneUtil::NodeCallback<ToCamera, osg::Node*, osgUtil::CullVisitor*>
{
public:
ToCamera(osg::ref_ptr<osg::ClipPlane> clipPlane)
: mClipPlane(std::move(clipPlane))
{
}
void operator()(osg::Node* node, osgUtil::CullVisitor* cv)
{
osg::Vec3f normal = cv->getEyePoint();
mClipPlane->setClipPlane(normal.x(), normal.y(), normal.z(), 0);
traverse(node, cv);
}
private:
osg::ref_ptr<osg::ClipPlane> mClipPlane;
};
auto addTagToActiveMarkerNodes = [](CSVRender::NodeMap& mMarkerNodes, CSVRender::Object* object,
std::initializer_list<std::string> suffixes) {
for (const auto& markerSuffix : suffixes)
{
for (char axis = 'X'; axis <= 'Z'; ++axis)
mMarkerNodes[axis + markerSuffix]->setUserData(new CSVRender::ObjectMarkerTag(object, axis - 'X'));
}
};
}
namespace CSVRender
{
ObjectMarkerTag::ObjectMarkerTag(Object* object, int axis)
: ObjectTag(object)
, mAxis(axis)
{
}
ObjectMarker::ObjectMarker(WorldspaceWidget* worldspaceWidget, Resource::ResourceSystem* resourceSystem)
: mWorldspaceWidget(worldspaceWidget)
, mResourceSystem(resourceSystem)
, mMarkerScale(CSMPrefs::get()["Rendering"]["object-marker-scale"].toDouble())
, mSubMode(Object::Mode_None)
{
mBaseNode = new osg::PositionAttitudeTransform;
mBaseNode->setNodeMask(Mask_Reference);
mBaseNode->setScale(osg::Vec3f(mMarkerScale, mMarkerScale, mMarkerScale));
mRootNode = new osg::PositionAttitudeTransform;
mRootNode->addChild(mBaseNode);
worldspaceWidget->setSelectionMarkerRoot(mRootNode);
QFile file(":render/selection-marker");
if (!file.open(QIODevice::ReadOnly))
throw std::runtime_error("Failed to open selection marker file");
auto markerData = file.readAll();
mResourceSystem->getSceneManager()->loadSelectionMarker(mBaseNode, markerData.data(), markerData.size());
osg::ref_ptr<osg::StateSet> baseNodeState = mBaseNode->getOrCreateStateSet();
baseNodeState->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE);
baseNodeState->setRenderBinDetails(1000, "RenderBin");
FindMaterialVisitor matMapper(mMarkerNodes);
mBaseNode->accept(matMapper);
for (const auto& [name, node] : mMarkerNodes)
{
osg::StateSet* state = node->getStateSet();
osg::Material* mat = static_cast<osg::Material*>(state->getAttribute(osg::StateAttribute::MATERIAL));
osg::Vec4f emis = mat->getEmission(osg::Material::FRONT_AND_BACK);
mat->setEmission(osg::Material::FRONT_AND_BACK, emis / 4);
mOriginalColors.emplace(name, emis);
}
SceneUtil::NodeMap sceneNodes;
SceneUtil::NodeMapVisitor nodeMapper(sceneNodes);
mBaseNode->accept(nodeMapper);
mMarkerNodes.insert(sceneNodes.begin(), sceneNodes.end());
osg::ref_ptr<osg::Node> rotateMarkers = mMarkerNodes["rotateMarkers"];
osg::ClipPlane* clip = new osg::ClipPlane(0);
rotateMarkers->setCullCallback(new ToCamera(clip));
rotateMarkers->getStateSet()->setAttributeAndModes(clip, osg::StateAttribute::ON);
}
void ObjectMarker::toggleVisibility()
{
bool isVisible = mBaseNode->getNodeMask() == Mask_Reference;
mBaseNode->setNodeMask(isVisible ? Mask_Hidden : Mask_Reference);
}
void ObjectMarker::updateScale(const float scale)
{
mMarkerScale = scale;
mBaseNode->setScale(osg::Vec3f(scale, scale, scale));
}
void ObjectMarker::setSubMode(const int subMode)
{
if (subMode == mSubMode)
return;
mSubMode = subMode;
resetMarkerHighlight();
updateSelectionMarker();
}
bool ObjectMarker::hitBehindMarker(const osg::Vec3d& hitPos, osg::ref_ptr<osg::Camera> camera)
{
if (mSubMode != Object::Mode_Rotate)
return false;
osg::Vec3d center, eye, forwardVector, _;
std::vector<osg::Node*> rotMark = mMarkerNodes["rotateMarkers"]->getParentalNodePaths()[0];
const osg::Vec3f markerPos = osg::computeLocalToWorld(rotMark).getTrans();
camera->getViewMatrixAsLookAt(eye, center, _);
forwardVector = center - eye;
forwardVector.normalize();
return (hitPos - markerPos) * forwardVector > 0;
}
bool ObjectMarker::attachMarker(const std::string& refId)
{
const auto& object = mWorldspaceWidget->getObjectByReferenceId(refId);
if (!object)
removeFromSelectionHistory(refId);
if (!object || !object->getSelected())
return false;
if (!object->getRootNode()->addChild(mRootNode))
throw std::runtime_error("Failed to add marker to object");
std::string parentMarkerNode;
switch (mSubMode)
{
case (Object::Mode_Rotate):
parentMarkerNode = "rotateMarkers";
addTagToActiveMarkerNodes(mMarkerNodes, object, { "_Axis_Rot" });
break;
case (Object::Mode_Scale):
parentMarkerNode = "scaleMarkers";
addTagToActiveMarkerNodes(mMarkerNodes, object, { "_Axis_Scale", "_Wall_Scale" });
break;
case (Object::Mode_Move):
default:
parentMarkerNode = "moveMarkers";
addTagToActiveMarkerNodes(mMarkerNodes, object, { "_Axis", "_Wall" });
break;
}
mMarkerNodes[parentMarkerNode]->asGroup()->setNodeMask(Mask_Reference);
return true;
}
void ObjectMarker::detachMarker()
{
for (std::size_t index = mRootNode->getNumParents(); index > 0;)
mRootNode->getParent(--index)->removeChild(mRootNode);
osg::ref_ptr<osg::Group> widgetRoot = mMarkerNodes["unitArrows"]->asGroup();
for (std::size_t index = widgetRoot->getNumChildren(); index > 0;)
widgetRoot->getChild(--index)->setNodeMask(Mask_Hidden);
}
void ObjectMarker::addToSelectionHistory(const std::string& refId, bool update)
{
auto foundObject = std::find_if(mSelectionHistory.begin(), mSelectionHistory.end(),
[&refId](const std::string& objId) { return objId == refId; });
if (foundObject == mSelectionHistory.end())
mSelectionHistory.push_back(refId);
else
std::rotate(foundObject, foundObject + 1, mSelectionHistory.end());
if (update)
updateSelectionMarker(refId);
}
void ObjectMarker::removeFromSelectionHistory(const std::string& refId)
{
mSelectionHistory.erase(std::remove_if(mSelectionHistory.begin(), mSelectionHistory.end(),
[&refId](const std::string& objId) { return objId == refId; }),
mSelectionHistory.end());
}
void ObjectMarker::updateSelectionMarker(const std::string& refId)
{
if (mSelectionHistory.empty())
return;
detachMarker();
if (refId.empty())
{
for (std::size_t index = mSelectionHistory.size(); index > 0;)
if (attachMarker(mSelectionHistory[--index]))
break;
}
else
attachMarker(refId);
}
void ObjectMarker::resetMarkerHighlight()
{
if (mLastHighlightedNodes.empty())
return;
for (const auto& [nodeName, mat] : mLastHighlightedNodes)
mat->setEmission(osg::Material::FRONT_AND_BACK, mat->getEmission(osg::Material::FRONT_AND_BACK) / 4);
mLastHighlightedNodes.clear();
mLastHitNode.clear();
}
void ObjectMarker::updateMarkerHighlight(const std::string_view hitNode, const int axis)
{
if (hitNode == mLastHitNode)
return;
resetMarkerHighlight();
std::string colorName;
switch (axis)
{
case Object::Axis_X:
colorName = "red";
break;
case Object::Axis_Y:
colorName = "green";
break;
case Object::Axis_Z:
colorName = "blue";
break;
default:
throw std::runtime_error("Invalid axis for highlighting: " + std::to_string(axis));
}
std::vector<std::string> targetMaterials = { colorName + "-material" };
if (mSubMode != Object::Mode_Rotate)
targetMaterials.emplace_back(colorName + "_alpha-material");
for (const auto& materialNodeName : targetMaterials)
{
osg::ref_ptr<osg::Node> matNode = mMarkerNodes[materialNodeName];
osg::StateSet* state = matNode->getStateSet();
osg::StateAttribute* matAttr = state->getAttribute(osg::StateAttribute::MATERIAL);
osg::Material* mat = static_cast<osg::Material*>(matAttr);
mat->setEmission(osg::Material::FRONT_AND_BACK, mOriginalColors[materialNodeName]);
mLastHighlightedNodes.emplace(std::make_pair(matNode->getName(), mat));
}
mLastHitNode = hitNode;
}
}

View file

@ -0,0 +1,77 @@
#ifndef OPENCS_VIEW_OBJECT_MARKER_H
#define OPENCS_VIEW_OBJECT_MARKER_H
#include "object.hpp"
namespace osg
{
class Camera;
class Material;
}
namespace CSVRender
{
using NodeMap = std::unordered_map<std::string, osg::ref_ptr<osg::Node>>;
class WorldspaceWidget;
class ObjectMarkerTag : public ObjectTag
{
public:
ObjectMarkerTag(Object* object, int axis);
int mAxis;
};
class ObjectMarker
{
friend class WorldspaceWidget;
WorldspaceWidget* mWorldspaceWidget;
Resource::ResourceSystem* mResourceSystem;
NodeMap mMarkerNodes;
osg::ref_ptr<osg::PositionAttitudeTransform> mBaseNode;
osg::ref_ptr<osg::PositionAttitudeTransform> mRootNode;
std::unordered_map<std::string, osg::Vec4f> mOriginalColors;
std::vector<std::string> mSelectionHistory;
std::string mLastHitNode;
std::unordered_map<std::string, osg::Material*> mLastHighlightedNodes;
float mMarkerScale;
int mSubMode;
ObjectMarker(WorldspaceWidget* worldspaceWidget, Resource::ResourceSystem* resourceSystem);
static std::unique_ptr<ObjectMarker> create(WorldspaceWidget* widget, Resource::ResourceSystem* resourceSystem)
{
return std::unique_ptr<ObjectMarker>(new ObjectMarker(widget, resourceSystem));
}
bool attachMarker(const std::string& refId);
void removeFromSelectionHistory(const std::string& refId);
public:
ObjectMarker(ObjectMarker&) = delete;
ObjectMarker(ObjectMarker&&) = delete;
ObjectMarker& operator=(const ObjectMarker&) = delete;
ObjectMarker& operator=(ObjectMarker&&) = delete;
void toggleVisibility();
bool hitBehindMarker(const osg::Vec3d& hitPos, osg::ref_ptr<osg::Camera> camera);
void detachMarker();
void addToSelectionHistory(const std::string& refId, bool update = true);
void updateSelectionMarker(const std::string& refId = std::string());
void resetMarkerHighlight();
void updateMarkerHighlight(const std::string_view hitNode, const int axis);
void setSubMode(const int subMode);
void updateScale(const float scale);
};
}
#endif // OPENCS_VIEW_OBJECT_MARKER_H

View file

@ -86,8 +86,8 @@ bool CSVRender::PagedWorldspaceWidget::adjustCells()
{
modified = true;
auto cell
= std::make_unique<Cell>(mDocument, mRootNode, iter->first.getId(mWorldspace), deleted, true);
auto cell = std::make_unique<Cell>(getDocument(), mSelectionMarker.get(), mRootNode,
iter->first.getId(mWorldspace), deleted, true);
delete iter->second;
iter->second = cell.release();
@ -465,7 +465,8 @@ void CSVRender::PagedWorldspaceWidget::addCellToScene(const CSMWorld::CellCoordi
bool deleted = index == -1 || cells.getRecord(index).mState == CSMWorld::RecordBase::State_Deleted;
auto cell = std::make_unique<Cell>(mDocument, mRootNode, coordinates.getId(mWorldspace), deleted, true);
auto cell = std::make_unique<Cell>(
getDocument(), mSelectionMarker.get(), mRootNode, coordinates.getId(mWorldspace), deleted, true);
EditMode* editMode = getEditMode();
cell->setSubMode(editMode->getSubMode(), editMode->getInteractionMask());
@ -750,6 +751,7 @@ void CSVRender::PagedWorldspaceWidget::clearSelection(int elementMask)
iter->second->setSelection(elementMask, Cell::Selection_Clear);
flagAsModified();
mSelectionMarker->detachMarker();
}
void CSVRender::PagedWorldspaceWidget::invertSelection(int elementMask)
@ -907,6 +909,7 @@ void CSVRender::PagedWorldspaceWidget::setSubMode(int subMode, unsigned int elem
{
for (std::map<CSMWorld::CellCoordinates, Cell*>::const_iterator iter = mCells.begin(); iter != mCells.end(); ++iter)
iter->second->setSubMode(subMode, elementMask);
mSelectionMarker->updateSelectionMarker();
}
void CSVRender::PagedWorldspaceWidget::reset(unsigned int elementMask)
@ -986,3 +989,12 @@ void CSVRender::PagedWorldspaceWidget::loadSouthCell()
{
addCellToSceneFromCamera(0, -1);
}
CSVRender::Object* CSVRender::PagedWorldspaceWidget::getObjectByReferenceId(const std::string& referenceId)
{
for (const auto& [_, cell] : mCells)
if (const auto& object = cell->getObjectByReferenceId(referenceId))
return object;
return nullptr;
}

View file

@ -174,6 +174,8 @@ namespace CSVRender
/// Erase all overrides and restore the visual representation to its true state.
void reset(unsigned int elementMask) override;
CSVRender::Object* getObjectByReferenceId(const std::string& referenceId) override;
protected:
void addVisibilitySelectorButtons(CSVWidget::SceneToolToggle2* tool) override;

View file

@ -445,6 +445,32 @@ namespace CSVRender
mCurrentCamControl->setup(mRootNode, Mask_Reference | Mask_Terrain, CameraController::WorldUp);
mCamPositionSet = true;
}
if (mSelectionMarkerNode)
{
osg::MatrixList worldMats = mSelectionMarkerNode->getWorldMatrices();
if (!worldMats.empty())
{
osg::Matrixd markerWorldMat = worldMats[0];
osg::Vec3f eye, _;
mView->getCamera()->getViewMatrix().getLookAt(eye, _, _);
osg::Vec3f cameraLocalPos = eye * osg::Matrixd::inverse(markerWorldMat);
bool isInFrontRightQuadrant = (cameraLocalPos.x() > 0.1f) && (cameraLocalPos.y() > 0.1f);
bool isSignificantlyBehind = (cameraLocalPos.x() < 1.f) && (cameraLocalPos.y() < 1.f);
if (!isInFrontRightQuadrant && isSignificantlyBehind)
{
osg::Quat current = mSelectionMarkerNode->getAttitude();
mSelectionMarkerNode->setAttitude(current * osg::Quat(osg::PI, osg::Vec3f(0, 0, 1)));
}
float distance = (markerWorldMat.getTrans() - eye).length();
float scale = std::max(distance / 75.0f, 1.0f);
mSelectionMarkerNode->setScale(osg::Vec3(scale, scale, scale));
}
}
}
void SceneWidget::settingChanged(const CSMPrefs::Setting* setting)

View file

@ -9,6 +9,7 @@
#include <QTimer>
#include <QWidget>
#include <osg/PositionAttitudeTransform>
#include <osg/Vec4f>
#include <osg/ref_ptr>
@ -105,6 +106,11 @@ namespace CSVRender
void setExterior(bool isExterior);
void setSelectionMarkerRoot(osg::ref_ptr<osg::PositionAttitudeTransform> selectionMarker)
{
mSelectionMarkerNode = selectionMarker;
}
protected:
void setLighting(Lighting* lighting);
///< \attention The ownership of \a lighting is not transferred to *this.
@ -122,6 +128,7 @@ namespace CSVRender
Lighting* mLighting;
osg::ref_ptr<osg::PositionAttitudeTransform> mSelectionMarkerNode;
osg::ref_ptr<osg::Camera> mGradientCamera;
osg::Vec4f mDefaultAmbient;
bool mHasDefaultAmbient;

View file

@ -79,7 +79,7 @@ CSVRender::UnpagedWorldspaceWidget::UnpagedWorldspaceWidget(
update();
mCell = std::make_unique<Cell>(document, mRootNode, mCellId);
mCell = std::make_unique<Cell>(document, mSelectionMarker.get(), mRootNode, mCellId);
}
void CSVRender::UnpagedWorldspaceWidget::cellDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight)
@ -127,7 +127,7 @@ bool CSVRender::UnpagedWorldspaceWidget::handleDrop(
mCellId = universalIdData.begin()->getId();
mCell = std::make_unique<Cell>(getDocument(), mRootNode, mCellId);
mCell = std::make_unique<Cell>(getDocument(), mSelectionMarker.get(), mRootNode, mCellId);
mCamPositionSet = false;
mOrbitCamControl->reset();
@ -141,6 +141,7 @@ void CSVRender::UnpagedWorldspaceWidget::clearSelection(int elementMask)
{
mCell->setSelection(elementMask, Cell::Selection_Clear);
flagAsModified();
mSelectionMarker->detachMarker();
}
void CSVRender::UnpagedWorldspaceWidget::invertSelection(int elementMask)
@ -218,6 +219,7 @@ std::vector<osg::ref_ptr<CSVRender::TagBase>> CSVRender::UnpagedWorldspaceWidget
void CSVRender::UnpagedWorldspaceWidget::setSubMode(int subMode, unsigned int elementMask)
{
mCell->setSubMode(subMode, elementMask);
mSelectionMarker->updateSelectionMarker();
}
void CSVRender::UnpagedWorldspaceWidget::reset(unsigned int elementMask)
@ -383,3 +385,8 @@ CSVRender::WorldspaceWidget::dropRequirments CSVRender::UnpagedWorldspaceWidget:
return ignored;
}
}
CSVRender::Object* CSVRender::UnpagedWorldspaceWidget::getObjectByReferenceId(const std::string& referenceId)
{
return mCell->getObjectByReferenceId(referenceId);
}

View file

@ -104,6 +104,8 @@ namespace CSVRender
/// Erase all overrides and restore the visual representation to its true state.
void reset(unsigned int elementMask) override;
CSVRender::Object* getObjectByReferenceId(const std::string& id) override;
private:
void referenceableDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) override;

View file

@ -52,7 +52,6 @@
#include "cameracontroller.hpp"
#include "instancemode.hpp"
#include "mask.hpp"
#include "object.hpp"
#include "pathgridmode.hpp"
CSVRender::WorldspaceWidget::WorldspaceWidget(CSMDoc::Document& document, QWidget* parent)
@ -74,8 +73,8 @@ CSVRender::WorldspaceWidget::WorldspaceWidget(CSMDoc::Document& document, QWidge
, mToolTipPos(-1, -1)
, mShowToolTips(false)
, mToolTipDelay(0)
, mInConstructor(true)
, mSelectedNavigationMode(0)
, mSelectionMarker(ObjectMarker::create(this, document.getData().getResourceSystem().get()))
{
setAcceptDrops(true);
@ -145,13 +144,14 @@ CSVRender::WorldspaceWidget::WorldspaceWidget(CSMDoc::Document& document, QWidge
&WorldspaceWidget::unhideAll);
connect(new CSMPrefs::Shortcut("scene-clear-selection", this), qOverload<>(&CSMPrefs::Shortcut::activated), this,
[this] { this->clearSelection(Mask_Reference); });
[this] { clearSelection(Mask_Reference); });
CSMPrefs::Shortcut* switchPerspectiveShortcut = new CSMPrefs::Shortcut("scene-cam-cycle", this);
connect(switchPerspectiveShortcut, qOverload<>(&CSMPrefs::Shortcut::activated), this,
&WorldspaceWidget::cycleNavigationMode);
mInConstructor = false;
connect(new CSMPrefs::Shortcut("scene-toggle-marker", this), qOverload<>(&CSMPrefs::Shortcut::activated), this,
[this]() { mSelectionMarker->toggleVisibility(); });
}
void CSVRender::WorldspaceWidget::settingChanged(const CSMPrefs::Setting* setting)
@ -162,17 +162,8 @@ void CSVRender::WorldspaceWidget::settingChanged(const CSMPrefs::Setting* settin
mDragWheelFactor = setting->toDouble();
else if (*setting == "3D Scene Input/drag-shift-factor")
mDragShiftFactor = setting->toDouble();
else if (*setting == "Rendering/object-marker-alpha" && !mInConstructor)
{
float alpha = setting->toDouble();
// getSelection is virtual, thus this can not be called from the constructor
auto selection = getSelection(Mask_Reference);
for (osg::ref_ptr<TagBase> tag : selection)
{
if (auto objTag = dynamic_cast<ObjectTag*>(tag.get()))
objTag->mObject->setMarkerTransparency(alpha);
}
}
else if (*setting == "Rendering/object-marker-scale")
mSelectionMarker->updateScale(setting->toDouble());
else if (*setting == "Tooltips/scene-delay")
mToolTipDelay = setting->toInt();
else if (*setting == "Tooltips/scene")
@ -396,8 +387,29 @@ CSMDoc::Document& CSVRender::WorldspaceWidget::getDocument()
return mDocument;
}
CSVRender::WorldspaceHitResult CSVRender::WorldspaceWidget::mousePick(
const QPoint& localPos, unsigned int interactionMask) const
template <typename Tag>
std::optional<CSVRender::WorldspaceHitResult> CSVRender::WorldspaceWidget::checkTag(
const osgUtil::LineSegmentIntersector::Intersection& intersection) const
{
for (auto* node : intersection.nodePath)
{
if (auto* tag = dynamic_cast<Tag*>(node->getUserData()))
{
WorldspaceHitResult hit = { true, tag, 0, 0, 0, intersection.getWorldIntersectPoint() };
if (intersection.indexList.size() >= 3)
{
hit.index0 = intersection.indexList[0];
hit.index1 = intersection.indexList[1];
hit.index2 = intersection.indexList[2];
}
return hit;
}
}
return std::nullopt;
}
std::tuple<osg::Vec3d, osg::Vec3d, osg::Vec3d> CSVRender::WorldspaceWidget::getStartEndDirection(
int pointX, int pointY) const
{
// may be okay to just use devicePixelRatio() directly
QScreen* screen = SceneWidget::windowHandle() && SceneWidget::windowHandle()->screen()
@ -405,8 +417,8 @@ CSVRender::WorldspaceHitResult CSVRender::WorldspaceWidget::mousePick(
: QGuiApplication::primaryScreen();
// (0,0) is considered the lower left corner of an OpenGL window
int x = localPos.x() * screen->devicePixelRatio();
int y = height() * screen->devicePixelRatio() - localPos.y() * screen->devicePixelRatio();
int x = pointX * screen->devicePixelRatio();
int y = height() * screen->devicePixelRatio() - pointY * screen->devicePixelRatio();
// Convert from screen space to world space
osg::Matrixd wpvMat;
@ -418,6 +430,13 @@ CSVRender::WorldspaceHitResult CSVRender::WorldspaceWidget::mousePick(
osg::Vec3d start = wpvMat.preMult(osg::Vec3d(x, y, 0));
osg::Vec3d end = wpvMat.preMult(osg::Vec3d(x, y, 1));
osg::Vec3d direction = end - start;
return { start, end, direction };
}
CSVRender::WorldspaceHitResult CSVRender::WorldspaceWidget::mousePick(
const QPoint& localPos, unsigned int interactionMask) const
{
auto [start, end, direction] = getStartEndDirection(localPos.x(), localPos.y());
// Get intersection
osg::ref_ptr<osgUtil::LineSegmentIntersector> intersector(
@ -430,51 +449,46 @@ CSVRender::WorldspaceHitResult CSVRender::WorldspaceWidget::mousePick(
mView->getCamera()->accept(visitor);
// Get relevant data
for (osgUtil::LineSegmentIntersector::Intersections::iterator it = intersector->getIntersections().begin();
it != intersector->getIntersections().end(); ++it)
{
osgUtil::LineSegmentIntersector::Intersection intersection = *it;
auto intersections = intersector->getIntersections();
// reject back-facing polygons
if (direction * intersection.getWorldIntersectNormal() > 0)
{
continue;
}
std::vector<osgUtil::LineSegmentIntersector::Intersection> validIntersections
= { intersections.begin(), intersections.end() };
for (std::vector<osg::Node*>::iterator nodeIter = intersection.nodePath.begin();
nodeIter != intersection.nodePath.end(); ++nodeIter)
{
osg::Node* node = *nodeIter;
if (osg::ref_ptr<CSVRender::TagBase> tag = dynamic_cast<CSVRender::TagBase*>(node->getUserData()))
{
WorldspaceHitResult hit = { true, std::move(tag), 0, 0, 0, intersection.getWorldIntersectPoint() };
if (intersection.indexList.size() >= 3)
{
hit.index0 = intersection.indexList[0];
hit.index1 = intersection.indexList[1];
hit.index2 = intersection.indexList[2];
}
return hit;
}
}
const auto& removeBackfaces = [direction = direction](const osgUtil::LineSegmentIntersector::Intersection& i) {
return direction * i.getWorldIntersectNormal() > 0;
};
// Something untagged, probably terrain
WorldspaceHitResult hit = { true, nullptr, 0, 0, 0, intersection.getWorldIntersectPoint() };
if (intersection.indexList.size() >= 3)
{
hit.index0 = intersection.indexList[0];
hit.index1 = intersection.indexList[1];
hit.index2 = intersection.indexList[2];
}
return hit;
}
validIntersections.erase(std::remove_if(validIntersections.begin(), validIntersections.end(), removeBackfaces),
validIntersections.end());
// Default placement
direction.normalize();
direction *= CSMPrefs::get()["3D Scene Editing"]["distance"].toInt();
WorldspaceHitResult hit = { false, nullptr, 0, 0, 0, start + direction };
if (validIntersections.empty())
return WorldspaceHitResult{ false, nullptr, 0, 0, 0, start + direction };
const auto& firstHit = validIntersections.front();
for (const auto& hit : validIntersections)
if (const auto& markerHit = checkTag<ObjectMarkerTag>(hit))
{
if (mSelectionMarker->hitBehindMarker(markerHit->worldPos, mView->getCamera()))
return WorldspaceHitResult{ false, nullptr, 0, 0, 0, start + direction };
else
return *markerHit;
}
if (auto hit = checkTag<TagBase>(firstHit))
return *hit;
// Something untagged, probably terrain
WorldspaceHitResult hit = { true, nullptr, 0, 0, 0, firstHit.getWorldIntersectPoint() };
if (firstHit.indexList.size() >= 3)
{
hit.index0 = firstHit.indexList[0];
hit.index1 = firstHit.indexList[1];
hit.index2 = firstHit.indexList[2];
}
return hit;
}
@ -632,6 +646,41 @@ void CSVRender::WorldspaceWidget::elementSelectionChanged()
void CSVRender::WorldspaceWidget::updateOverlay() {}
void CSVRender::WorldspaceWidget::handleMarkerHighlight(const int x, const int y)
{
auto [start, end, _] = getStartEndDirection(x, y);
osg::ref_ptr<osgUtil::LineSegmentIntersector> intersector(
new osgUtil::LineSegmentIntersector(osgUtil::Intersector::MODEL, start, end));
intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::NO_LIMIT);
osgUtil::IntersectionVisitor visitor(intersector);
visitor.setTraversalMask(Mask_Reference);
mView->getCamera()->accept(visitor);
bool hitMarker = false;
for (const auto& intersection : intersector->getIntersections())
{
if (mSelectionMarker->hitBehindMarker(intersection.getWorldIntersectPoint(), mView->getCamera()))
continue;
for (const auto& node : intersection.nodePath)
{
if (const auto& marker = dynamic_cast<ObjectMarkerTag*>(node->getUserData()))
{
hitMarker = true;
mSelectionMarker->updateMarkerHighlight(node->getName(), marker->mAxis);
break;
}
}
}
if (!hitMarker)
mSelectionMarker->resetMarkerHighlight();
}
void CSVRender::WorldspaceWidget::mouseMoveEvent(QMouseEvent* event)
{
dynamic_cast<CSVRender::EditMode&>(*mEditMode->getCurrent()).mouseMoveEvent(event);
@ -685,6 +734,8 @@ void CSVRender::WorldspaceWidget::mouseMoveEvent(QMouseEvent* event)
}
}
const QPointF& pos = event->localPos();
handleMarkerHighlight(pos.x(), pos.y());
SceneWidget::mouseMoveEvent(event);
}
}

View file

@ -13,6 +13,7 @@
#include <apps/opencs/view/render/tagbase.hpp>
#include "instancedragmodes.hpp"
#include "objectmarker.hpp"
#include "scenewidget.hpp"
class QDragEnterEvent;
@ -89,7 +90,6 @@ namespace CSVRender
QPoint mToolTipPos;
bool mShowToolTips;
int mToolTipDelay;
bool mInConstructor;
int mSelectedNavigationMode;
public:
@ -186,6 +186,12 @@ namespace CSVRender
virtual void selectWithinDistance(const osg::Vec3d& point, float distance, DragMode dragMode) = 0;
template <typename Tag>
std::optional<WorldspaceHitResult> checkTag(
const osgUtil::LineSegmentIntersector::Intersection& intersection) const;
std::tuple<osg::Vec3d, osg::Vec3d, osg::Vec3d> getStartEndDirection(int pointX, int pointY) const;
/// Return the next intersection with scene elements matched by
/// \a interactionMask based on \a localPos and the camera vector.
/// If there is no such intersection, instead a point "in front" of \a localPos will be
@ -216,7 +222,14 @@ namespace CSVRender
EditMode* getEditMode();
virtual CSVRender::Object* getObjectByReferenceId(const std::string& id) = 0;
ObjectMarker* getSelectionMarker() { return mSelectionMarker.get(); }
const ObjectMarker* getSelectionMarker() const { return mSelectionMarker.get(); }
protected:
const std::unique_ptr<CSVRender::ObjectMarker> mSelectionMarker;
/// Visual elements in a scene
/// @note do not change the enumeration values, they are used in pre-existing button file names!
enum ButtonId
@ -252,6 +265,10 @@ namespace CSVRender
void cycleNavigationMode();
private:
bool hitBehindMarker(const osg::Vec3d& hitPos) const;
void handleMarkerHighlight(const int x, const int y);
void dragEnterEvent(QDragEnterEvent* event) override;
void dropEvent(QDropEvent* event) override;

View file

@ -15,7 +15,7 @@ set(OPENMW_HEADERS
profile.hpp
)
source_group(apps/openmw FILES main.cpp android_main.cpp ${OPENMW_SOURCES} ${OPENMW_HEADERS} ${OPENMW_RESOURCES})
source_group(apps/openmw FILES main.cpp androidmain.cpp ${OPENMW_SOURCES} ${OPENMW_HEADERS} ${OPENMW_RESOURCES})
add_openmw_dir (mwrender
actors objects renderingmanager animation rotatecontroller sky skyutil npcanimation esm4npcanimation vismask
@ -44,7 +44,7 @@ add_openmw_dir (mwgui
tradeitemmodel companionitemmodel pickpocketitemmodel controllers savegamedialog
recharge mode videowidget backgroundimage itemwidget screenfader debugwindow spellmodel spellview
draganddrop timeadvancer jailscreen itemchargeview keyboardnavigation textcolours statswatcher
postprocessorhud settings
postprocessorhud settings worlditemmodel itemtransfer
)
add_openmw_dir (mwdialogue
@ -60,9 +60,9 @@ add_openmw_dir (mwscript
add_openmw_dir (mwlua
luamanagerimp object objectlists userdataserializer luaevents engineevents objectvariant
context menuscripts globalscripts localscripts playerscripts luabindings objectbindings cellbindings
context menuscripts globalscripts localscripts playerscripts luabindings objectbindings cellbindings coremwscriptbindings
mwscriptbindings camerabindings vfsbindings uibindings soundbindings inputbindings nearbybindings dialoguebindings
postprocessingbindings stats recordstore debugbindings corebindings worldbindings worker magicbindings factionbindings
postprocessingbindings stats recordstore debugbindings corebindings worldbindings worker landbindings magicbindings factionbindings
classbindings itemdata inputprocessor animationbindings birthsignbindings racebindings markupbindings
types/types types/door types/item types/actor types/container types/lockable types/weapon types/npc
types/creature types/player types/activator types/book types/lockpick types/probe types/apparatus
@ -71,8 +71,8 @@ add_openmw_dir (mwlua
)
add_openmw_dir (mwsound
soundmanagerimp openal_output ffmpeg_decoder sound sound_buffer sound_decoder sound_output
loudness movieaudiofactory alext efx efx-presets regionsoundselector watersoundupdater
soundmanagerimp openaloutput ffmpegdecoder sound soundbuffer sounddecoder soundoutput
loudness movieaudiofactory alext efx efxpresets regionsoundselector watersoundupdater
)
add_openmw_dir (mwworld
@ -127,7 +127,7 @@ if(BUILD_OPENMW)
if (ANDROID)
add_library(openmw SHARED
main.cpp
android_main.cpp
androidmain.cpp
)
else()
openmw_add_executable(openmw
@ -138,6 +138,12 @@ if(BUILD_OPENMW)
endif()
target_link_libraries(openmw openmw-lib)
# Workaround necessary to ensure osgAnimation::MatrixLinearSampler dynamic casts work under Clang
# NOTE: it's unclear whether the broken behavior is spec-compliant
if (CMAKE_CXX_COMPILER_ID STREQUAL Clang)
set_target_properties(openmw PROPERTIES ENABLE_EXPORTS ON)
endif()
endif()
# Sound stuff - here so CMake doesn't stupidly recompile EVERYTHING

View file

@ -404,8 +404,8 @@ OMW::Engine::~Engine()
mMechanicsManager = nullptr;
mDialogueManager = nullptr;
mJournal = nullptr;
mScriptManager = nullptr;
mWindowManager = nullptr;
mScriptManager = nullptr;
mWorld = nullptr;
mStereoManager = nullptr;
mSoundManager = nullptr;
@ -433,6 +433,8 @@ OMW::Engine::~Engine()
}
SDL_Quit();
Log(Debug::Info) << "Quitting peacefully.";
}
// Set data dir
@ -729,7 +731,7 @@ void OMW::Engine::prepareEngine()
mVFS = std::make_unique<VFS::Manager>();
VFS::registerArchives(mVFS.get(), mFileCollections, mArchives, true);
VFS::registerArchives(mVFS.get(), mFileCollections, mArchives, true, &mEncoder.get()->getStatelessEncoder());
mResourceSystem = std::make_unique<Resource::ResourceSystem>(
mVFS.get(), Settings::cells().mCacheExpiryDelay, &mEncoder.get()->getStatelessEncoder());
@ -753,7 +755,7 @@ void OMW::Engine::prepareEngine()
mViewer->addEventHandler(mScreenCaptureHandler);
mL10nManager = std::make_unique<l10n::Manager>(mVFS.get());
mL10nManager = std::make_unique<L10n::Manager>(mVFS.get());
mL10nManager->setPreferredLocales(Settings::general().mPreferredLocales, Settings::general().mGmstOverridesL10n);
mEnvironment.setL10nManager(*mL10nManager);
@ -1069,8 +1071,6 @@ void OMW::Engine::go()
Settings::Manager::saveUser(mCfgMgr.getUserConfigPath() / "settings.cfg");
Settings::ShaderManager::get().save();
mLuaManager->savePermanentStorage(mCfgMgr.getUserConfigPath());
Log(Debug::Info) << "Quitting peacefully.";
}
void OMW::Engine::setCompileAll(bool all)

View file

@ -113,7 +113,7 @@ namespace MWDialogue
class Journal;
}
namespace l10n
namespace L10n
{
class Manager;
}
@ -141,7 +141,7 @@ namespace OMW
std::unique_ptr<MWState::StateManager> mStateManager;
std::unique_ptr<MWLua::LuaManager> mLuaManager;
std::unique_ptr<MWLua::Worker> mLuaWorker;
std::unique_ptr<l10n::Manager> mL10nManager;
std::unique_ptr<L10n::Manager> mL10nManager;
MWBase::Environment mEnvironment;
ToUTF8::FromType mEncoding;
std::unique_ptr<ToUTF8::Utf8Encoder> mEncoder;

View file

@ -61,6 +61,8 @@ bool parseOptions(int argc, char** argv, OMW::Engine& engine, Files::Configurati
return false;
}
cfgMgr.processPaths(variables, std::filesystem::current_path());
cfgMgr.readConfiguration(variables, desc);
Debug::setupLogging(cfgMgr.getLogPath(), "OpenMW");

View file

@ -10,7 +10,7 @@ namespace Resource
class ResourceSystem;
}
namespace l10n
namespace L10n
{
class Manager;
}
@ -57,7 +57,7 @@ namespace MWBase
StateManager* mStateManager = nullptr;
LuaManager* mLuaManager = nullptr;
Resource::ResourceSystem* mResourceSystem = nullptr;
l10n::Manager* mL10nManager = nullptr;
L10n::Manager* mL10nManager = nullptr;
float mFrameRateLimit = 0;
float mFrameDuration = 0;
@ -95,7 +95,7 @@ namespace MWBase
void setResourceSystem(Resource::ResourceSystem& value) { mResourceSystem = &value; }
void setL10nManager(l10n::Manager& value) { mL10nManager = &value; }
void setL10nManager(L10n::Manager& value) { mL10nManager = &value; }
Misc::NotNullPtr<World> getWorld() const { return mWorld; }
Misc::NotNullPtr<MWWorld::WorldModel> getWorldModel() const { return mWorldModel; }
@ -122,7 +122,7 @@ namespace MWBase
Misc::NotNullPtr<Resource::ResourceSystem> getResourceSystem() const { return mResourceSystem; }
Misc::NotNullPtr<l10n::Manager> getL10nManager() const { return mL10nManager; }
Misc::NotNullPtr<L10n::Manager> getL10nManager() const { return mL10nManager; }
float getFrameRateLimit() const { return mFrameRateLimit; }

View file

@ -88,6 +88,8 @@ namespace MWBase
virtual void executeAction(int action) = 0;
virtual bool controlsDisabled() = 0;
virtual void saveBindings() = 0;
};
}

View file

@ -1,6 +1,7 @@
#ifndef GAME_MWBASE_LUAMANAGER_H
#define GAME_MWBASE_LUAMANAGER_H
#include <filesystem>
#include <map>
#include <string>
#include <variant>
@ -75,6 +76,7 @@ namespace MWBase
virtual void questUpdated(const ESM::RefId& questId, int stage) = 0;
// `arg` is either forwarded from MWGui::pushGuiMode or empty
virtual void uiModeChanged(const MWWorld::Ptr& arg) = 0;
virtual void savePermanentStorage(const std::filesystem::path& userConfigPath) = 0;
// TODO: notify LuaManager about other events
// virtual void objectOnHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object,

Some files were not shown because too many files have changed in this diff Show more