diff --git a/.appveyor.yml b/.appveyor.yml deleted file mode 100644 index 84d82a06f..000000000 --- a/.appveyor.yml +++ /dev/null @@ -1,125 +0,0 @@ -# specify version format -version: "{build}" - -image: - - Visual Studio 2017 - - ubuntu1804 - - macos - -platform: - - x64 - -# specify custom environment variables -environment: - APPVEYOR_SAVE_CACHE_ON_ERROR: true - -# build configuration, i.e. Debug, Release, etc. -configuration: - - Debug - - Release - -# branches to build -branches: - # blacklist - except: - - gh-pages - -for: -- - #------------------ - # Windows 10 - #------------------ - matrix: - only: - - image: Visual Studio 2017 - # scripts that are called at very beginning, before repo cloning - init: - - ver - - cmake --version - - msbuild /version - # scripts that run after cloning repository - install: - # update vcpkg - - cd C:\tools\vcpkg - - git pull - - .\bootstrap-vcpkg.bat - - if "%platform%"=="Win32" set VCPKG_ARCH=x86-windows - - if "%platform%"=="x64" set VCPKG_ARCH=x64-windows - # remove outdated versions - - vcpkg remove --outdated --recurse - # install dependencies - - vcpkg install --recurse --triplet %VCPKG_ARCH% zlib boost-iostreams boost-program-options boost-system boost-serialization eigen3 cgal[core] opencv glew glfw3 - - vcpkg integrate install - - cd "%APPVEYOR_BUILD_FOLDER%" - # preserve contents of selected directories and files across project builds - cache: - - 'C:\tools\vcpkg\installed' - build_script: - - git clone https://github.com/cdcseacave/VCG.git - - if "%platform%"=="Win32" set CMAKE_GENERATOR=-G"Visual Studio 15 2017" - - if "%platform%"=="x64" set CMAKE_GENERATOR=-G"Visual Studio 15 2017 Win64" - - mkdir bin && cd bin - - cmake %CMAKE_GENERATOR% -DCMAKE_BUILD_TYPE=%Configuration% -DCMAKE_TOOLCHAIN_FILE="C:\tools\vcpkg\scripts\buildsystems\vcpkg.cmake" -DVCG_ROOT="%APPVEYOR_BUILD_FOLDER%\VCG" .. - - cmake --build . --target ALL_BUILD --config %Configuration% -- /maxcpucount:4 -- - #------------------ - # Ubuntu 18.04 LTS - #------------------ - matrix: - only: - - image: ubuntu1804 - # scripts that are called at very beginning, before repo cloning - init: - - lsb_release -a - - cmake --version - - gcc -v - # scripts that run after cloning repository - install: - - sudo apt-get update -qq && sudo apt-get install -qq - - sudo apt-get -y install build-essential git cmake libpng-dev libjpeg-dev libtiff-dev libglu1-mesa-dev libxmu-dev libxi-dev - - sudo apt-get -y install libboost-iostreams-dev libboost-program-options-dev libboost-system-dev libboost-serialization-dev - - sudo apt-get -y install libopencv-dev libcgal-dev libcgal-qt5-dev libatlas-base-dev - - sudo apt-get -y install freeglut3-dev libglew-dev libglfw3-dev - # preserve contents of selected directories and files across project builds - cache: - - '/usr/lib/x86_64-linux-gnu/' - build_script: - - git clone https://github.com/cdcseacave/VCG.git - - git clone --single-branch --branch 3.2 https://gitlab.com/libeigen/eigen.git - - mkdir eigen_build && cd eigen_build - - cmake . ../eigen - - make && sudo make install - - cd .. - - mkdir bin && cd bin - - cmake -DCMAKE_BUILD_TYPE=$Configuration -DVCG_ROOT="$APPVEYOR_BUILD_FOLDER/VCG" .. - - make -- - #------------------ - # MacOS - #------------------ - matrix: - only: - - image: macos - # scripts that are called at very beginning, before repo cloning - init: - - system_profiler SPSoftwareDataType - - cmake --version - - gcc -v - # scripts that run after cloning repository - install: - - brew update - - printf "#%s/bin/bash\nbrew install libomp boost eigen opencv cgal glew glfw3\nexit 0\n" "!" > install.sh - - chmod +x install.sh - - ./install.sh - # preserve contents of selected directories and files across project builds - cache: - - '/usr/local/Cellar/' - build_script: - - git clone https://github.com/cdcseacave/VCG.git - - mkdir bin && cd bin - - cmake -DCMAKE_BUILD_TYPE=$CONFIGURATION -DVCG_ROOT="$APPVEYOR_BUILD_FOLDER/VCG" .. - - make - -on_success: - - cmd: 7z a OpenMVS_x64.7z "C:\projects\openmvs\bin\bin\vc15\x64\%Configuration%\*.exe" "C:\projects\openmvs\bin\bin\vc15\x64\%Configuration%\*.dll" - - cmd: appveyor PushArtifact OpenMVS_x64.7z diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 000000000..11a6d0e10 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,39 @@ +FROM ubuntu:22.04 + +ARG USERNAME=openmvs +ARG USER_UID=1000 +ARG USER_GID=$USER_UID +ARG INSTALL_ZSH="true" + +# Prepare and empty machine for building: +RUN apt-get update -yq + +COPY .devcontainer/library-scripts/*.sh /tmp/library-scripts/ +RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ + && /bin/bash /tmp/library-scripts/common-debian.sh "${INSTALL_ZSH}" "${USERNAME}" "${USER_UID}" "${USER_GID}" "${UPGRADE_PACKAGES}" "true" "true"\ + # + # **************************************************************************** + # * TODO: Add any additional OS packages you want included in the definition * + # * here. We want to do this before cleanup to keep the "layer" small. * + # **************************************************************************** + # && apt-get -y install --no-install-recommends \ + # + && apt-get -y install --no-install-recommends build-essential git cmake libpng-dev libjpeg-dev libtiff-dev libglu1-mesa-dev libglew-dev libglfw3-dev \ + # Boost + libboost-iostreams-dev libboost-program-options-dev libboost-system-dev libboost-serialization-dev \ + # OpenCV + libopencv-dev \ + # CGAL + libcgal-dev libcgal-qt5-dev \ + && apt-get autoremove -y && apt-get clean -y && rm -rf /var/lib/apt/lists/* /tmp/library-scripts + +# Eigen +RUN git clone https://gitlab.com/libeigen/eigen --branch 3.4 +RUN mkdir eigen_build +RUN cd eigen_build &&\ + cmake . ../eigen &&\ + make && make install &&\ + cd .. + +# VCGLib +RUN git clone https://github.com/cdcseacave/VCG.git vcglib diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 000000000..f9b245244 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,42 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/cpp +{ + "name": "OpenMVS", + "build": { + "dockerfile": "Dockerfile", + "context": ".." + }, + // Features to add to the dev container. More info: https://containers.dev/features. + // "features": {}, + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + // Use 'postCreateCommand' to run commands after the container is created. + "postCreateCommand": "./.devcontainer/postCreateCommand.sh", + // Configure tool-specific properties. + "customizations": { + "vscode": { + "extensions": [ + "ms-vscode.cpptools", + "ms-vscode.cmake-tools", + "twxs.cmake", + "josetr.cmake-language-support-vscode" + ] + } + }, + "containerEnv": { + "DISPLAY": "unix:0" + }, + "remoteEnv": { + "PATH": "/usr/local/bin/OpenMVS:${containerEnv:PATH}" + }, + "mounts": [ + "source=/tmp/.X11-unix,target=/tmp/.X11-unix,type=bind,consistency=cached" + ], + "features": { + "ghcr.io/devcontainers/features/sshd:1": { + "version": "latest" + } + }, + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + "remoteUser": "openmvs" +} \ No newline at end of file diff --git a/.devcontainer/library-scripts/common-debian.sh b/.devcontainer/library-scripts/common-debian.sh new file mode 100644 index 000000000..bf1f9e2ed --- /dev/null +++ b/.devcontainer/library-scripts/common-debian.sh @@ -0,0 +1,454 @@ +#!/usr/bin/env bash +#------------------------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information. +#------------------------------------------------------------------------------------------------------------- +# +# Docs: https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/common.md +# Maintainer: The VS Code and Codespaces Teams +# +# Syntax: ./common-debian.sh [install zsh flag] [username] [user UID] [user GID] [upgrade packages flag] [install Oh My Zsh! flag] [Add non-free packages] + +set -e + +INSTALL_ZSH=${1:-"true"} +USERNAME=${2:-"automatic"} +USER_UID=${3:-"automatic"} +USER_GID=${4:-"automatic"} +UPGRADE_PACKAGES=${5:-"true"} +INSTALL_OH_MYS=${6:-"true"} +ADD_NON_FREE_PACKAGES=${7:-"false"} +SCRIPT_DIR="$(cd $(dirname "${BASH_SOURCE[0]}") && pwd)" +MARKER_FILE="/usr/local/etc/vscode-dev-containers/common" + +if [ "$(id -u)" -ne 0 ]; then + echo -e 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.' + exit 1 +fi + +# Ensure that login shells get the correct path if the user updated the PATH using ENV. +rm -f /etc/profile.d/00-restore-env.sh +echo "export PATH=${PATH//$(sh -lc 'echo $PATH')/\$PATH}" > /etc/profile.d/00-restore-env.sh +chmod +x /etc/profile.d/00-restore-env.sh + +# If in automatic mode, determine if a user already exists, if not use vscode +if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then + USERNAME="" + POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)") + for CURRENT_USER in ${POSSIBLE_USERS[@]}; do + if id -u ${CURRENT_USER} > /dev/null 2>&1; then + USERNAME=${CURRENT_USER} + break + fi + done + if [ "${USERNAME}" = "" ]; then + USERNAME=vscode + fi +elif [ "${USERNAME}" = "none" ]; then + USERNAME=root + USER_UID=0 + USER_GID=0 +fi + +# Load markers to see which steps have already run +if [ -f "${MARKER_FILE}" ]; then + echo "Marker file found:" + cat "${MARKER_FILE}" + source "${MARKER_FILE}" +fi + +# Ensure apt is in non-interactive to avoid prompts +export DEBIAN_FRONTEND=noninteractive + +# Function to call apt-get if needed +apt_get_update_if_needed() +{ + if [ ! -d "/var/lib/apt/lists" ] || [ "$(ls /var/lib/apt/lists/ | wc -l)" = "0" ]; then + echo "Running apt-get update..." + apt-get update + else + echo "Skipping apt-get update." + fi +} + +# Run install apt-utils to avoid debconf warning then verify presence of other common developer tools and dependencies +if [ "${PACKAGES_ALREADY_INSTALLED}" != "true" ]; then + + package_list="apt-utils \ + openssh-client \ + gnupg2 \ + dirmngr \ + iproute2 \ + procps \ + lsof \ + htop \ + net-tools \ + psmisc \ + curl \ + wget \ + rsync \ + ca-certificates \ + unzip \ + zip \ + nano \ + vim-tiny \ + less \ + jq \ + lsb-release \ + apt-transport-https \ + dialog \ + libc6 \ + libgcc1 \ + libkrb5-3 \ + libgssapi-krb5-2 \ + libicu[0-9][0-9] \ + liblttng-ust[0-9] \ + libstdc++6 \ + zlib1g \ + locales \ + sudo \ + ncdu \ + man-db \ + strace \ + manpages \ + manpages-dev \ + init-system-helpers" + + # Needed for adding manpages-posix and manpages-posix-dev which are non-free packages in Debian + if [ "${ADD_NON_FREE_PACKAGES}" = "true" ]; then + # Bring in variables from /etc/os-release like VERSION_CODENAME + . /etc/os-release + sed -i -E "s/deb http:\/\/(deb|httpredir)\.debian\.org\/debian ${VERSION_CODENAME} main/deb http:\/\/\1\.debian\.org\/debian ${VERSION_CODENAME} main contrib non-free/" /etc/apt/sources.list + sed -i -E "s/deb-src http:\/\/(deb|httredir)\.debian\.org\/debian ${VERSION_CODENAME} main/deb http:\/\/\1\.debian\.org\/debian ${VERSION_CODENAME} main contrib non-free/" /etc/apt/sources.list + sed -i -E "s/deb http:\/\/(deb|httpredir)\.debian\.org\/debian ${VERSION_CODENAME}-updates main/deb http:\/\/\1\.debian\.org\/debian ${VERSION_CODENAME}-updates main contrib non-free/" /etc/apt/sources.list + sed -i -E "s/deb-src http:\/\/(deb|httpredir)\.debian\.org\/debian ${VERSION_CODENAME}-updates main/deb http:\/\/\1\.debian\.org\/debian ${VERSION_CODENAME}-updates main contrib non-free/" /etc/apt/sources.list + sed -i "s/deb http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}\/updates main/deb http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}\/updates main contrib non-free/" /etc/apt/sources.list + sed -i "s/deb-src http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}\/updates main/deb http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}\/updates main contrib non-free/" /etc/apt/sources.list + sed -i "s/deb http:\/\/deb\.debian\.org\/debian ${VERSION_CODENAME}-backports main/deb http:\/\/deb\.debian\.org\/debian ${VERSION_CODENAME}-backports main contrib non-free/" /etc/apt/sources.list + sed -i "s/deb-src http:\/\/deb\.debian\.org\/debian ${VERSION_CODENAME}-backports main/deb http:\/\/deb\.debian\.org\/debian ${VERSION_CODENAME}-backports main contrib non-free/" /etc/apt/sources.list + # Handle bullseye location for security https://www.debian.org/releases/bullseye/amd64/release-notes/ch-information.en.html + sed -i "s/deb http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}-security main/deb http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}-security main contrib non-free/" /etc/apt/sources.list + sed -i "s/deb-src http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}-security main/deb http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}-security main contrib non-free/" /etc/apt/sources.list + echo "Running apt-get update..." + apt-get update + package_list="${package_list} manpages-posix manpages-posix-dev" + else + apt_get_update_if_needed + fi + + # Install libssl1.1 if available + if [[ ! -z $(apt-cache --names-only search ^libssl1.1$) ]]; then + package_list="${package_list} libssl1.1" + fi + + # Install appropriate version of libssl1.0.x if available + libssl_package=$(dpkg-query -f '${db:Status-Abbrev}\t${binary:Package}\n' -W 'libssl1\.0\.?' 2>&1 || echo '') + if [ "$(echo "$LIlibssl_packageBSSL" | grep -o 'libssl1\.0\.[0-9]:' | uniq | sort | wc -l)" -eq 0 ]; then + if [[ ! -z $(apt-cache --names-only search ^libssl1.0.2$) ]]; then + # Debian 9 + package_list="${package_list} libssl1.0.2" + elif [[ ! -z $(apt-cache --names-only search ^libssl1.0.0$) ]]; then + # Ubuntu 18.04, 16.04, earlier + package_list="${package_list} libssl1.0.0" + fi + fi + + echo "Packages to verify are installed: ${package_list}" + apt-get -y install --no-install-recommends ${package_list} 2> >( grep -v 'debconf: delaying package configuration, since apt-utils is not installed' >&2 ) + + # Install git if not already installed (may be more recent than distro version) + if ! type git > /dev/null 2>&1; then + apt-get -y install --no-install-recommends git + fi + + PACKAGES_ALREADY_INSTALLED="true" +fi + +# Get to latest versions of all packages +if [ "${UPGRADE_PACKAGES}" = "true" ]; then + apt_get_update_if_needed + apt-get -y upgrade --no-install-recommends + apt-get autoremove -y +fi + +# Ensure at least the en_US.UTF-8 UTF-8 locale is available. +# Common need for both applications and things like the agnoster ZSH theme. +if [ "${LOCALE_ALREADY_SET}" != "true" ] && ! grep -o -E '^\s*en_US.UTF-8\s+UTF-8' /etc/locale.gen > /dev/null; then + echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen + locale-gen + LOCALE_ALREADY_SET="true" +fi + +# Create or update a non-root user to match UID/GID. +group_name="${USERNAME}" +if id -u ${USERNAME} > /dev/null 2>&1; then + # User exists, update if needed + if [ "${USER_GID}" != "automatic" ] && [ "$USER_GID" != "$(id -g $USERNAME)" ]; then + group_name="$(id -gn $USERNAME)" + groupmod --gid $USER_GID ${group_name} + usermod --gid $USER_GID $USERNAME + fi + if [ "${USER_UID}" != "automatic" ] && [ "$USER_UID" != "$(id -u $USERNAME)" ]; then + usermod --uid $USER_UID $USERNAME + fi +else + # Create user + if [ "${USER_GID}" = "automatic" ]; then + groupadd $USERNAME + else + groupadd --gid $USER_GID $USERNAME + fi + if [ "${USER_UID}" = "automatic" ]; then + useradd -s /bin/bash --gid $USERNAME -m $USERNAME + else + useradd -s /bin/bash --uid $USER_UID --gid $USERNAME -m $USERNAME + fi +fi + +# Add sudo support for non-root user +if [ "${USERNAME}" != "root" ] && [ "${EXISTING_NON_ROOT_USER}" != "${USERNAME}" ]; then + echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME + chmod 0440 /etc/sudoers.d/$USERNAME + EXISTING_NON_ROOT_USER="${USERNAME}" +fi + +# ** Shell customization section ** +if [ "${USERNAME}" = "root" ]; then + user_rc_path="/root" +else + user_rc_path="/home/${USERNAME}" +fi + +# Restore user .bashrc defaults from skeleton file if it doesn't exist or is empty +if [ ! -f "${user_rc_path}/.bashrc" ] || [ ! -s "${user_rc_path}/.bashrc" ] ; then + cp /etc/skel/.bashrc "${user_rc_path}/.bashrc" +fi + +# Restore user .profile defaults from skeleton file if it doesn't exist or is empty +if [ ! -f "${user_rc_path}/.profile" ] || [ ! -s "${user_rc_path}/.profile" ] ; then + cp /etc/skel/.profile "${user_rc_path}/.profile" +fi + +# .bashrc/.zshrc snippet +rc_snippet="$(cat << 'EOF' + +if [ -z "${USER}" ]; then export USER=$(whoami); fi +if [[ "${PATH}" != *"$HOME/.local/bin"* ]]; then export PATH="${PATH}:$HOME/.local/bin"; fi + +# Display optional first run image specific notice if configured and terminal is interactive +if [ -t 1 ] && [[ "${TERM_PROGRAM}" = "vscode" || "${TERM_PROGRAM}" = "codespaces" ]] && [ ! -f "$HOME/.config/vscode-dev-containers/first-run-notice-already-displayed" ]; then + if [ -f "/usr/local/etc/vscode-dev-containers/first-run-notice.txt" ]; then + cat "/usr/local/etc/vscode-dev-containers/first-run-notice.txt" + elif [ -f "/workspaces/.codespaces/shared/first-run-notice.txt" ]; then + cat "/workspaces/.codespaces/shared/first-run-notice.txt" + fi + mkdir -p "$HOME/.config/vscode-dev-containers" + # Mark first run notice as displayed after 10s to avoid problems with fast terminal refreshes hiding it + ((sleep 10s; touch "$HOME/.config/vscode-dev-containers/first-run-notice-already-displayed") &) +fi + +# Set the default git editor if not already set +if [ -z "$(git config --get core.editor)" ] && [ -z "${GIT_EDITOR}" ]; then + if [ "${TERM_PROGRAM}" = "vscode" ]; then + if [[ -n $(command -v code-insiders) && -z $(command -v code) ]]; then + export GIT_EDITOR="code-insiders --wait" + else + export GIT_EDITOR="code --wait" + fi + fi +fi + +EOF +)" + +# code shim, it fallbacks to code-insiders if code is not available +cat << 'EOF' > /usr/local/bin/code +#!/bin/sh + +get_in_path_except_current() { + which -a "$1" | grep -A1 "$0" | grep -v "$0" +} + +code="$(get_in_path_except_current code)" + +if [ -n "$code" ]; then + exec "$code" "$@" +elif [ "$(command -v code-insiders)" ]; then + exec code-insiders "$@" +else + echo "code or code-insiders is not installed" >&2 + exit 127 +fi +EOF +chmod +x /usr/local/bin/code + +# systemctl shim - tells people to use 'service' if systemd is not running +cat << 'EOF' > /usr/local/bin/systemctl +#!/bin/sh +set -e +if [ -d "/run/systemd/system" ]; then + exec /bin/systemctl "$@" +else + echo '\n"systemd" is not running in this container due to its overhead.\nUse the "service" command to start services instead. e.g.: \n\nservice --status-all' +fi +EOF +chmod +x /usr/local/bin/systemctl + +# Codespaces bash and OMZ themes - partly inspired by https://github.com/ohmyzsh/ohmyzsh/blob/master/themes/robbyrussell.zsh-theme +codespaces_bash="$(cat \ +<<'EOF' + +# Codespaces bash prompt theme +__bash_prompt() { + local userpart='`export XIT=$? \ + && [ ! -z "${GITHUB_USER}" ] && echo -n "\[\033[0;32m\]@${GITHUB_USER} " || echo -n "\[\033[0;32m\]\u " \ + && [ "$XIT" -ne "0" ] && echo -n "\[\033[1;31m\]➜" || echo -n "\[\033[0m\]➜"`' + local gitbranch='`\ + if [ "$(git config --get codespaces-theme.hide-status 2>/dev/null)" != 1 ]; then \ + export BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || git rev-parse --short HEAD 2>/dev/null); \ + if [ "${BRANCH}" != "" ]; then \ + echo -n "\[\033[0;36m\](\[\033[1;31m\]${BRANCH}" \ + && if git ls-files --error-unmatch -m --directory --no-empty-directory -o --exclude-standard ":/*" > /dev/null 2>&1; then \ + echo -n " \[\033[1;33m\]✗"; \ + fi \ + && echo -n "\[\033[0;36m\]) "; \ + fi; \ + fi`' + local lightblue='\[\033[1;34m\]' + local removecolor='\[\033[0m\]' + PS1="${userpart} ${lightblue}\w ${gitbranch}${removecolor}\$ " + unset -f __bash_prompt +} +__bash_prompt + +EOF +)" + +codespaces_zsh="$(cat \ +<<'EOF' +# Codespaces zsh prompt theme +__zsh_prompt() { + local prompt_username + if [ ! -z "${GITHUB_USER}" ]; then + prompt_username="@${GITHUB_USER}" + else + prompt_username="%n" + fi + PROMPT="%{$fg[green]%}${prompt_username} %(?:%{$reset_color%}➜ :%{$fg_bold[red]%}➜ )" # User/exit code arrow + PROMPT+='%{$fg_bold[blue]%}%(5~|%-1~/…/%3~|%4~)%{$reset_color%} ' # cwd + PROMPT+='$([ "$(git config --get codespaces-theme.hide-status 2>/dev/null)" != 1 ] && git_prompt_info)' # Git status + PROMPT+='%{$fg[white]%}$ %{$reset_color%}' + unset -f __zsh_prompt +} +ZSH_THEME_GIT_PROMPT_PREFIX="%{$fg_bold[cyan]%}(%{$fg_bold[red]%}" +ZSH_THEME_GIT_PROMPT_SUFFIX="%{$reset_color%} " +ZSH_THEME_GIT_PROMPT_DIRTY=" %{$fg_bold[yellow]%}✗%{$fg_bold[cyan]%})" +ZSH_THEME_GIT_PROMPT_CLEAN="%{$fg_bold[cyan]%})" +__zsh_prompt + +EOF +)" + +# Add RC snippet and custom bash prompt +if [ "${RC_SNIPPET_ALREADY_ADDED}" != "true" ]; then + echo "${rc_snippet}" >> /etc/bash.bashrc + echo "${codespaces_bash}" >> "${user_rc_path}/.bashrc" + echo 'export PROMPT_DIRTRIM=4' >> "${user_rc_path}/.bashrc" + if [ "${USERNAME}" != "root" ]; then + echo "${codespaces_bash}" >> "/root/.bashrc" + echo 'export PROMPT_DIRTRIM=4' >> "/root/.bashrc" + fi + chown ${USERNAME}:${group_name} "${user_rc_path}/.bashrc" + RC_SNIPPET_ALREADY_ADDED="true" +fi + +# Optionally install and configure zsh and Oh My Zsh! +if [ "${INSTALL_ZSH}" = "true" ]; then + if ! type zsh > /dev/null 2>&1; then + apt_get_update_if_needed + apt-get install -y zsh + fi + if [ "${ZSH_ALREADY_INSTALLED}" != "true" ]; then + echo "${rc_snippet}" >> /etc/zsh/zshrc + ZSH_ALREADY_INSTALLED="true" + fi + + # Adapted, simplified inline Oh My Zsh! install steps that adds, defaults to a codespaces theme. + # See https://github.com/ohmyzsh/ohmyzsh/blob/master/tools/install.sh for official script. + oh_my_install_dir="${user_rc_path}/.oh-my-zsh" + if [ ! -d "${oh_my_install_dir}" ] && [ "${INSTALL_OH_MYS}" = "true" ]; then + template_path="${oh_my_install_dir}/templates/zshrc.zsh-template" + user_rc_file="${user_rc_path}/.zshrc" + umask g-w,o-w + mkdir -p ${oh_my_install_dir} + git clone --depth=1 \ + -c core.eol=lf \ + -c core.autocrlf=false \ + -c fsck.zeroPaddedFilemode=ignore \ + -c fetch.fsck.zeroPaddedFilemode=ignore \ + -c receive.fsck.zeroPaddedFilemode=ignore \ + "https://github.com/ohmyzsh/ohmyzsh" "${oh_my_install_dir}" 2>&1 + echo -e "$(cat "${template_path}")\nDISABLE_AUTO_UPDATE=true\nDISABLE_UPDATE_PROMPT=true" > ${user_rc_file} + sed -i -e 's/ZSH_THEME=.*/ZSH_THEME="codespaces"/g' ${user_rc_file} + + mkdir -p ${oh_my_install_dir}/custom/themes + echo "${codespaces_zsh}" > "${oh_my_install_dir}/custom/themes/codespaces.zsh-theme" + # Shrink git while still enabling updates + cd "${oh_my_install_dir}" + git repack -a -d -f --depth=1 --window=1 + # Copy to non-root user if one is specified + if [ "${USERNAME}" != "root" ]; then + cp -rf "${user_rc_file}" "${oh_my_install_dir}" /root + chown -R ${USERNAME}:${group_name} "${user_rc_path}" + fi + fi +fi + +# Persist image metadata info, script if meta.env found in same directory +meta_info_script="$(cat << 'EOF' +#!/bin/sh +. /usr/local/etc/vscode-dev-containers/meta.env + +# Minimal output +if [ "$1" = "version" ] || [ "$1" = "image-version" ]; then + echo "${VERSION}" + exit 0 +elif [ "$1" = "release" ]; then + echo "${GIT_REPOSITORY_RELEASE}" + exit 0 +elif [ "$1" = "content" ] || [ "$1" = "content-url" ] || [ "$1" = "contents" ] || [ "$1" = "contents-url" ]; then + echo "${CONTENTS_URL}" + exit 0 +fi + +#Full output +echo +echo "Development container image information" +echo +if [ ! -z "${VERSION}" ]; then echo "- Image version: ${VERSION}"; fi +if [ ! -z "${DEFINITION_ID}" ]; then echo "- Definition ID: ${DEFINITION_ID}"; fi +if [ ! -z "${VARIANT}" ]; then echo "- Variant: ${VARIANT}"; fi +if [ ! -z "${GIT_REPOSITORY}" ]; then echo "- Source code repository: ${GIT_REPOSITORY}"; fi +if [ ! -z "${GIT_REPOSITORY_RELEASE}" ]; then echo "- Source code release/branch: ${GIT_REPOSITORY_RELEASE}"; fi +if [ ! -z "${BUILD_TIMESTAMP}" ]; then echo "- Timestamp: ${BUILD_TIMESTAMP}"; fi +if [ ! -z "${CONTENTS_URL}" ]; then echo && echo "More info: ${CONTENTS_URL}"; fi +echo +EOF +)" +if [ -f "${SCRIPT_DIR}/meta.env" ]; then + mkdir -p /usr/local/etc/vscode-dev-containers/ + cp -f "${SCRIPT_DIR}/meta.env" /usr/local/etc/vscode-dev-containers/meta.env + echo "${meta_info_script}" > /usr/local/bin/devcontainer-info + chmod +x /usr/local/bin/devcontainer-info +fi + +# Write marker file +mkdir -p "$(dirname "${MARKER_FILE}")" +echo -e "\ + PACKAGES_ALREADY_INSTALLED=${PACKAGES_ALREADY_INSTALLED}\n\ + LOCALE_ALREADY_SET=${LOCALE_ALREADY_SET}\n\ + EXISTING_NON_ROOT_USER=${EXISTING_NON_ROOT_USER}\n\ + RC_SNIPPET_ALREADY_ADDED=${RC_SNIPPET_ALREADY_ADDED}\n\ + ZSH_ALREADY_INSTALLED=${ZSH_ALREADY_INSTALLED}" > "${MARKER_FILE}" + +echo "Done!" \ No newline at end of file diff --git a/.devcontainer/postCreateCommand.sh b/.devcontainer/postCreateCommand.sh new file mode 100755 index 000000000..3185a858b --- /dev/null +++ b/.devcontainer/postCreateCommand.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +rm -rf openMVS_build && mkdir openMVS_build + +cd openMVS_build &&\ + cmake .. -DCMAKE_BUILD_TYPE=Release -DVCG_ROOT=/vcglib + +# add below args for CUDA, refer docker/buildInDocker.sh for base container and additional stuff required +# -DOpenMVS_USE_CUDA=ON -DCMAKE_LIBRARY_PATH=/usr/local/cuda/lib64/stubs/ -DCUDA_TOOLKIT_ROOT_DIR=/usr/local/cuda/ -DCUDA_INCLUDE_DIRS=/usr/local/cuda/include/ -DCUDA_CUDART_LIBRARY=/usr/local/cuda/lib64 -DCUDA_NVCC_EXECUTABLE=/usr/local/cuda/bin/ + +# Install OpenMVS library +make -j && sudo make install \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..c0a459940 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +# Ensure shell scripts uses the correct line ending. +Dockerfile eol=lf +*.sh eol=lf +*.bat eol=crlf +*.cmd eol=crlf diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..c626e4ba5 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: [cdcseacave] diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml new file mode 100644 index 000000000..861a054dd --- /dev/null +++ b/.github/workflows/continuous_integration.yml @@ -0,0 +1,87 @@ +name: Continuous Integration +run-name: ${{ github.actor }} is building OpenMVS + +on: + push: + branches: [master, develop] + pull_request: + branches: [master, develop] + # Allows to run this workflow manually from the Actions tab + workflow_dispatch: + +env: + CTEST_OUTPUT_ON_FAILURE: 1 + +defaults: + run: + shell: bash + +jobs: + build-tests: + name: Build on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macOS-latest, windows-latest] + include: + - os: windows-latest + triplet: x64-windows-release + build-type: Release + - os: ubuntu-latest + triplet: x64-linux-release + build-type: Release + - os: macos-latest + triplet: arm64-osx + build-type: Release + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Restore artifacts, or setup vcpkg for building artifacts + uses: lukka/run-vcpkg@v11 + with: + vcpkgDirectory: '${{ github.workspace }}/vcpkg' + vcpkgGitCommitId: '1de2026f28ead93ff1773e6e680387643e914ea1' + + - name: Install Ubuntu dependencies + if: matrix.os == 'ubuntu-latest' + run: | + sudo apt-get update -y + sudo apt-get install -y autoconf-archive libxmu-dev libdbus-1-dev libxtst-dev libxi-dev libxinerama-dev libxcursor-dev xorg-dev libgl-dev libglu1-mesa-dev pkg-config ninja-build + + - name: Install macOS dependencies + if: matrix.os == 'macOS-latest' + run: | + brew install automake autoconf autoconf-archive libtool ninja + + - name: Configure CMake for Windows + if: matrix.os == 'windows-latest' + run: | + cmake -S . -B make -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} -DVCPKG_TARGET_TRIPLET=${{ matrix.triplet }} -DOpenMVS_USE_CUDA=OFF + + - name: Configure CMake for Ubuntu and macOS + if: matrix.os != 'windows-latest' + run: | + cmake -S . -B make -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} -DVCPKG_TARGET_TRIPLET=${{ matrix.triplet }} -DOpenMVS_USE_CUDA=OFF -G Ninja + + - name: Build + working-directory: ./make + run: | + rm -rf ../vcpkg/buildtrees + rm -rf ../vcpkg/downloads + cmake --build . --config ${{ matrix.build-type }} --parallel $(nproc) + + - name: Unit Tests + working-directory: ./make + run: | + ctest -j$(nproc) --build-config ${{ matrix.build-type }} + + - name: Deploy Windows release + if: matrix.os == 'windows-latest' + uses: actions/upload-artifact@v3 + with: + name: OpenMVS_Windows_Release_x64 + path: | + ${{ github.workspace }}/make/bin/**/x64 + !${{ github.workspace }}/make/bin/**/*.exp diff --git a/.gitignore b/.gitignore index 1880d2e51..c301e5a4e 100644 --- a/.gitignore +++ b/.gitignore @@ -35,5 +35,5 @@ CMakeSettings.json .idea/ .vscode/ out/ -bin/ -binaries/ +bin*/ +make*/ diff --git a/BUILD.md b/BUILD.md index 52e4dc0b8..ffc29becf 100644 --- a/BUILD.md +++ b/BUILD.md @@ -2,14 +2,15 @@ Dependencies ------------ -*OpenMVS* relies on a number of open source libraries, some of which are optional. For details on customizing the build process, see the compilation instructions. -* [Eigen](http://eigen.tuxfamily.org) version 3.2 (or higher on Windows only) +*OpenMVS* relies on a number of open source libraries, some optional, which are managed automatically by [vcpkg](https://github.com/Microsoft/vcpkg). For details on customizing the build process, see the build instructions. +* [Eigen](http://eigen.tuxfamily.org) version 3.4 or higher * [OpenCV](http://opencv.org) version 2.4 or higher -* [Ceres](http://ceres-solver.org) version 1.10 or higher +* [Ceres](http://ceres-solver.org) version 1.10 or higher (optional) * [CGAL](http://www.cgal.org) version 4.2 or higher * [Boost](http://www.boost.org) version 1.56 or higher * [VCG](http://vcg.isti.cnr.it/vcglib) -* [GLFW](http://www.glfw.org) +* [CUDA](https://developer.nvidia.com/cuda-downloads) (optional) +* [GLFW](http://www.glfw.org) (optional) ------------------ Build instructions @@ -18,121 +19,90 @@ Build instructions Required tools: * [CMake](http://www.cmake.org) * [git](https://git-scm.com) -* C/C++ compiler like Visual Studio or GCC +* C/C++ compiler like Visual Studio 2019, GCC or Clang -------------------- -Windows compilation -------------------- +The dependencies can be fetched and built automatically using `vcpkg` on all major platform, by setting the environment variable `VCPKG_ROOT` to point to its path or by using the `cmake` parameter `-DCMAKE_TOOLCHAIN_FILE=[path to vcpkg]/scripts/buildsystems/vcpkg.cmake`. -Visual Studio 2008 or newer are supported. Please note that the development is done mainly on Windows, so this platform build is well tested. The latest pre-built binaries for fast testing can be download from [here](https://github.com/cdcseacave/openMVS_sample/releases/latest). Visual Studio 2017 and dependencies automation tool [vcpkg](https://github.com/Microsoft/vcpkg) are used in this example. +The latest pre-built stable binaries can be download from [here](https://github.com/cdcseacave/openMVS_sample/releases/latest). ``` -#Make a toplevel directory for deps & build & src somewhere: -mkdir OpenMVS -cd OpenMVS - -#Get and install dependencies using vcpkg; -#choose the desired triplet, like "x64-windows", by setting the VCPKG_DEFAULT_TRIPLET environment variable or by specifying it after each package: -vcpkg install zlib boost-iostreams boost-program-options boost-system boost-serialization eigen3 cgal[core] opencv glew glfw3 - -#Get VCGLib (Required): -git clone https://github.com/cdcseacave/VCG.git - -#Get and unpack OpenMVS in OpenMVS/src: -git clone https://github.com/cdcseacave/openMVS.git src +#Clone OpenMVS +git clone --recurse-submodules https://github.com/cdcseacave/openMVS.git #Make build directory: -mkdir build -cd build - -#Run CMake, where VCPKG_ROOT environment variable points to the root of vcpkg installation: -cmake . ..\src -G "Visual Studio 15 2017 Win64" -DCMAKE_TOOLCHAIN_FILE=%VCPKG_ROOT%\scripts\buildsystems\vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows -DVCG_ROOT="..\VCG" - -#Open the solution in MSVC and build it -``` - ------------------ -Linux compilation ------------------ - -[Ubuntu](http://www.ubuntu.com) 16.04 is used next as the example linux distribution. - -``` -#Prepare and empty machine for building: -sudo apt-get update -qq && sudo apt-get install -qq -sudo apt-get -y install git cmake libpng-dev libjpeg-dev libtiff-dev libglu1-mesa-dev -main_path=`pwd` - -#Eigen (Required) -git clone https://gitlab.com/libeigen/eigen.git --branch 3.2 -mkdir eigen_build && cd eigen_build -cmake . ../eigen -make && sudo make install -cd .. - -#Boost (Required) -sudo apt-get -y install libboost-iostreams-dev libboost-program-options-dev libboost-system-dev libboost-serialization-dev - -#OpenCV (Required) -sudo apt-get -y install libopencv-dev - -#CGAL (Required) -sudo apt-get -y install libcgal-dev libcgal-qt5-dev +cd openMVS +mkdir make +cd make -#VCGLib (Required) -git clone https://github.com/cdcseacave/VCG.git vcglib +#Run CMake: +cmake .. -#Ceres (optional) -sudo apt-get -y install libatlas-base-dev libsuitesparse-dev -git clone https://ceres-solver.googlesource.com/ceres-solver ceres-solver -mkdir ceres_build && cd ceres_build -cmake . ../ceres-solver/ -DMINIGLOG=ON -DBUILD_TESTING=OFF -DBUILD_EXAMPLES=OFF -make -j2 && sudo make install -cd .. - -#GLFW3 (Optional) -sudo apt-get -y install freeglut3-dev libglew-dev libglfw3-dev - -#OpenMVS -git clone https://github.com/cdcseacave/openMVS.git openMVS -mkdir openMVS_build && cd openMVS_build -cmake . ../openMVS -DCMAKE_BUILD_TYPE=Release -DVCG_ROOT="$main_path/vcglib" - -#If you want to use OpenMVS as shared library, add to the CMake command: --DBUILD_SHARED_LIBS=ON +#Build: +cmake --build . -j4 #Install OpenMVS library (optional): -make -j2 && sudo make install +cmake --install . ``` --------------------- -Mac OS X compilation --------------------- - -Install dependencies, run CMake and make. +------------------- +Library usage +------------------- +In order to use *OpenMVS* as a third-party library in your project, first compile it as described above or simply use `vcpgk`: +``` +vcpkg install openmvs ``` -#Install dependencies -brew update -brew install boost eigen opencv cgal -main_path=`pwd` - -#GLFW3 (Optional) -brew install glew glfw3 - -#VCGLib (Required) -git clone https://github.com/cdcseacave/VCG.git vcglib - -#Getting the OpenMVS sources: -git clone https://github.com/cdcseacave/openMVS.git -#Build OpenMVS -mkdir openMVS_build && cd openMVS_build -cmake . ../openMVS -DCMAKE_BUILD_TYPE=Release -DVCG_DIR="$main_path/vcglib" +Inside your project CMake script, use: +``` +find_package(OpenMVS) +if(OpenMVS_FOUND) + include_directories(${OpenMVS_INCLUDE_DIRS}) + add_definitions(${OpenMVS_DEFINITIONS}) +endif() + +add_executable(your_project source_code.cpp) +target_link_libraries(your_project PRIVATE OpenMVS::MVS) +``` -#If you want to use OpenMVS as shared library, add to the CMake command: --DBUILD_SHARED_LIBS=ON +------------------- +Python API +------------------- -#Install OpenMVS library (optional): -make && sudo make install +The Python API can be enable by setting the `OpenMVS_USE_PYTHON` option to `ON` when running `cmake`. The Python API is built as a shared library and can be used in any Python project. Example: +``` +import pyOpenMVS + +def run_mvs(): + # set the working folder; all files used next are relative to this folder (optional) + pyOpenMVS.set_working_folder("folder/containing/the/scene") + # create an empty scene + scene = pyOpenMVS.Scene() + # load a MVS scene from a file + if not scene.load("scene.mvs"): + print("ERROR: scene could not be loaded") + return + # estimate depth-maps and fuse them into a point-cloud + if not scene.dense_reconstruction(): + print("ERROR: could not dense reconstruct the scene") + return + scene.save_pointcloud("pointcloud.ply") + # reconstruct a mesh from the point-cloud + if not scene.reconstruct_mesh(): + print("ERROR: could not reconstruct the mesh for this scene") + return + scene.save_mesh("mesh.ply") + # refine the mesh using gradient descent optimization (optional) + if not scene.refine_mesh(): + print("ERROR: could not refine the mesh for this scene") + return + scene.save_mesh("refined_mesh.ply") + # texture the mesh using the input images + if not scene.texture_mesh(): + print("ERROR: could not texture the mesh for this scene") + return + scene.save_mesh("textured_mesh.ply") + +if __name__ == "__main__": + run_mvs() ``` diff --git a/CMakeLists.txt b/CMakeLists.txt index fff705bda..b9d301cba 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,40 +3,72 @@ ######################################################################## # # Project-wide settings -CMAKE_MINIMUM_REQUIRED(VERSION 3.8.0) -if(POLICY CMP0011) - cmake_policy(SET CMP0011 NEW) -endif() -if(POLICY CMP0074) - cmake_policy(SET CMP0074 NEW) -endif() +CMAKE_MINIMUM_REQUIRED(VERSION 3.10) +IF(POLICY CMP0011) + CMAKE_POLICY(SET CMP0011 NEW) +ENDIF() +IF(POLICY CMP0072) + CMAKE_POLICY(SET CMP0072 NEW) +ENDIF() +IF(POLICY CMP0074) + CMAKE_POLICY(SET CMP0074 NEW) +ENDIF() +IF(POLICY CMP0104) + CMAKE_POLICY(SET CMP0104 NEW) +ENDIF() +IF(POLICY CMP0146) + CMAKE_POLICY(SET CMP0146 OLD) # not required, but forced by OpenCV old support for CUDA +ENDIF() + +# List configuration options +SET(OpenMVS_BUILD_TOOLS ON CACHE BOOL "Build example applications") +SET(OpenMVS_USE_OPENMP ON CACHE BOOL "Enable OpenMP library") +SET(OpenMVS_USE_OPENGL ON CACHE BOOL "Enable OpenGL library") +SET(OpenMVS_USE_BREAKPAD ON CACHE BOOL "Enable BreakPad library") +SET(OpenMVS_USE_PYTHON ON CACHE BOOL "Enable Python library bindings") +SET(OpenMVS_USE_CERES OFF CACHE BOOL "Enable CERES optimization library") +SET(OpenMVS_USE_CUDA ON CACHE BOOL "Enable CUDA library") +SET(OpenMVS_USE_FAST_FLOAT2INT OFF CACHE BOOL "Use an optimized code to convert real numbers to int") +SET(OpenMVS_USE_FAST_INVSQRT OFF CACHE BOOL "Use an optimized code to compute the inverse square root (slower in fact on modern compilers)") +SET(OpenMVS_USE_FAST_CBRT OFF CACHE BOOL "Use an optimized code to compute the cubic root") +SET(OpenMVS_USE_SSE ON CACHE BOOL "Enable SSE optimizations") +SET(OpenMVS_MAX_CUDA_COMPATIBILITY OFF CACHE BOOL "Build for maximum CUDA device compatibility") +SET(OpenMVS_ENABLE_TESTS ON CACHE BOOL "Enable test code") + +# Load automatically VCPKG toolchain if available +IF(NOT DEFINED CMAKE_TOOLCHAIN_FILE AND DEFINED ENV{VCPKG_ROOT}) + SET(CMAKE_TOOLCHAIN_FILE "$ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" CACHE STRING "") + IF(NOT DEFINED VCPKG_TARGET_TRIPLET AND DEFINED ENV{VCPKG_DEFAULT_TRIPLET}) + SET(VCPKG_TARGET_TRIPLET "$ENV{VCPKG_DEFAULT_TRIPLET}" CACHE STRING "") + ENDIF() +ENDIF() +IF(OpenMVS_USE_PYTHON) + LIST(APPEND VCPKG_MANIFEST_FEATURES "python") + SET(PARTIAL_BUILD_SHARED_LIBS ON) +ENDIF() +IF(OpenMVS_USE_CUDA) + LIST(APPEND VCPKG_MANIFEST_FEATURES "cuda") +ENDIF() # Name of the project. # # CMake files in this project can refer to the root source directory # as ${OpenMVS_SOURCE_DIR} and to the root binary directory as # ${OpenMVS_BINARY_DIR}. -PROJECT(OpenMVS) +PROJECT(OpenMVS LANGUAGES CXX) -set(OpenMVS_MAJOR_VERSION 1) -set(OpenMVS_MINOR_VERSION 1) -set(OpenMVS_PATCH_VERSION 1) -set(OpenMVS_VERSION ${OpenMVS_MAJOR_VERSION}.${OpenMVS_MINOR_VERSION}.${OpenMVS_PATCH_VERSION}) +SET(OpenMVS_MAJOR_VERSION 2) +SET(OpenMVS_MINOR_VERSION 3) +SET(OpenMVS_PATCH_VERSION 0) +SET(OpenMVS_VERSION ${OpenMVS_MAJOR_VERSION}.${OpenMVS_MINOR_VERSION}.${OpenMVS_PATCH_VERSION}) -# Find dependencies: -SET(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/build/Modules) - -# fix CMake IntDir variable -if(MSVC AND "${MSVC_VERSION}" STRGREATER "1500") - SET(CMAKE_CFG_INTDIR "$(Platform)/$(Configuration)") -endif() -SET(COTIRE_INTDIR "cotire") +# Disable SSE on unsupported platforms +IF(CMAKE_SYSTEM_PROCESSOR MATCHES "^(arm|ARM|aarch64|AARCH64)") + SET(OpenMVS_USE_SSE OFF) +ENDIF() # Define helper functions and macros. INCLUDE(build/Utils.cmake) -if(ENABLE_PRECOMPILED_HEADERS) - INCLUDE(build/Cotire.cmake) -endif() # Init session with macros defined in Utils.cmake GetOperatingSystemArchitectureBitness(SYSTEM) @@ -44,29 +76,18 @@ ComposePackageLibSuffix() ConfigCompilerAndLinker() ConfigLibrary() -# List configuration options -SET(OpenMVS_USE_NONFREE ON CACHE BOOL "Build non-free (patented) functionality") -SET(OpenMVS_USE_CERES OFF CACHE BOOL "Enable CERES optimization library") -SET(OpenMVS_USE_FAST_FLOAT2INT ON CACHE BOOL "Use an optimized code to convert real numbers to int") -SET(OpenMVS_USE_FAST_INVSQRT OFF CACHE BOOL "Use an optimized code to compute the inverse square root (slower in fact on modern compilers)") -SET(OpenMVS_USE_FAST_CBRT ON CACHE BOOL "Use an optimized code to compute the cubic root") -SET(OpenMVS_USE_SSE ON CACHE BOOL "Enable SSE optimizations") -SET(OpenMVS_USE_OPENMP ON CACHE BOOL "Enable OpenMP library") -SET(OpenMVS_USE_OPENGL ON CACHE BOOL "Enable OpenGL library") -SET(OpenMVS_USE_CUDA ON CACHE BOOL "Enable CUDA library") -SET(OpenMVS_USE_BREAKPAD ON CACHE BOOL "Enable BreakPad library") -SET(OpenMVS_CONFIG_INCLUDE_DIR "${CMAKE_BINARY_DIR}/" CACHE PATH "Where to create the build/platform specific header") - -INCLUDE_DIRECTORIES("${OpenMVS_SOURCE_DIR}") +# Find dependencies: +SET(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/build/Modules) # Find required packages +SET(OpenMVS_EXTRA_INCLUDES "") SET(OpenMVS_EXTRA_LIBS "") + if(OpenMVS_USE_OPENMP) SET(OpenMP_LIBS "") FIND_PACKAGE(OpenMP) if(OPENMP_FOUND) SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") - ADD_DEFINITIONS(-D_USE_OPENMP) SET(_USE_OPENMP TRUE) #cmake only check for separate OpenMP library on AppleClang 7+ #https://github.com/Kitware/CMake/blob/42212f7539040139ecec092547b7d58ef12a4d72/Modules/FindOpenMP.cmake#L252 @@ -75,18 +96,15 @@ if(OpenMVS_USE_OPENMP) LIST(APPEND OpenMVS_EXTRA_LIBS ${OpenMP_LIBS}) endif() else() - message("-- Can't find OpenMP. Continuing without it.") + MESSAGE("-- Can't find OpenMP. Continuing without it.") endif() endif() if(OpenMVS_USE_OPENGL) - if(POLICY CMP0072) - cmake_policy(SET CMP0072 NEW) - endif() FIND_PACKAGE(OpenGL) if(OPENGL_FOUND) INCLUDE_DIRECTORIES(${OPENGL_INCLUDE_DIR}) - ADD_DEFINITIONS(${OpenGL_DEFINITIONS} -D_USE_OPENGL) + ADD_DEFINITIONS(${OpenGL_DEFINITIONS}) SET(_USE_OPENGL TRUE) else() MESSAGE("-- Can't find OpenGL. Continuing without it.") @@ -94,11 +112,62 @@ if(OpenMVS_USE_OPENGL) endif() if(OpenMVS_USE_CUDA) - FIND_PACKAGE(CUDA) - if(CUDA_FOUND) + INCLUDE(CheckLanguage) + CHECK_LANGUAGE(CUDA) + if(CMAKE_CUDA_COMPILER) + SET(CUDA_FOUND TRUE) + if(NOT DEFINED CMAKE_CUDA_ARCHITECTURES) + SET(CMAKE_CUDA_ARCHITECTURES "native") + endif() + ENABLE_LANGUAGE(CUDA) + # not required, but forced by OpenCV old support for CUDA + FIND_PACKAGE(CUDA REQUIRED) + # CUDA-11.x can not be compiled using C++14 standard on Windows + string(REGEX MATCH "^[0-9]+" CUDA_VERSION_MAJOR ${CMAKE_CUDA_COMPILER_VERSION}) + string(REGEX REPLACE "([0-9]+)\\.([0-9]+).*" "\\2" CUDA_VERSION_MINOR ${CMAKE_CUDA_COMPILER_VERSION}) + if(${CUDA_VERSION_MAJOR} GREATER 10) + SET(CMAKE_CUDA_STANDARD 17) + endif() + EXECUTE_PROCESS(COMMAND "${CMAKE_CUDA_COMPILER}" --list-gpu-arch + OUTPUT_VARIABLE LIST_GPU_ARCH + ERROR_QUIET) + if(NOT LIST_GPU_ARCH AND OpenMVS_MAX_CUDA_COMPATIBILITY) + message(WARNING "Cannot compile for max CUDA compatibility, nvcc does not support --list-gpu-arch") + SET(OpenMVS_MAX_CUDA_COMPATIBILITY OFF) + endif() + if(NOT OpenMVS_MAX_CUDA_COMPATIBILITY) + if(NOT DEFINED CMAKE_CUDA_ARCHITECTURES) + SET(CMAKE_CUDA_ARCHITECTURES 75) + endif() + else() + # Build for maximum compatibility + # https://arnon.dk/matching-sm-architectures-arch-and-gencode-for-various-nvidia-cards/ + + # Extract list of arch and gencodes + STRING(REPLACE "\r" "" LIST_GPU_ARCH ${LIST_GPU_ARCH}) + STRING(REPLACE "\n" ";" LIST_GPU_ARCH ${LIST_GPU_ARCH}) + + EXECUTE_PROCESS(COMMAND "${CMAKE_CUDA_COMPILER}" --list-gpu-code + OUTPUT_VARIABLE LIST_GPU_CODE + ERROR_QUIET) + STRING(REPLACE "\r" "" LIST_GPU_CODE ${LIST_GPU_CODE}) + STRING(REPLACE "\n" ";" LIST_GPU_CODE ${LIST_GPU_CODE}) + + LIST(GET LIST_GPU_CODE 0 TARGET_GPU_CODE) + SET(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -arch=${TARGET_GPU_CODE}") + + SET(IDX 0) + foreach(GPU_ARCH ${LIST_GPU_ARCH}) + LIST(GET LIST_GPU_CODE ${IDX} GPU_CODE) + SET(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -gencode=arch=${GPU_ARCH},code=${GPU_CODE}") + MATH(EXPR IDX "${IDX}+1") + endforeach() + MESSAGE("-- Set CUDA flags: " ${CMAKE_CUDA_FLAGS}) + endif() + SET(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} --expt-relaxed-constexpr") INCLUDE_DIRECTORIES(${CUDA_INCLUDE_DIRS}) - ADD_DEFINITIONS(-D_USE_CUDA) SET(_USE_CUDA TRUE) + MESSAGE("-- Found CUDA: " ${CMAKE_CUDA_COMPILER_VERSION} " with GPU arch: " ${CMAKE_CUDA_ARCHITECTURES}) else() SET(CUDA_CUDA_LIBRARY "") MESSAGE("-- Can't find CUDA. Continuing without it.") @@ -111,7 +180,7 @@ if(OpenMVS_USE_BREAKPAD) FIND_PACKAGE(BREAKPAD) if(BREAKPAD_FOUND) INCLUDE_DIRECTORIES(${BREAKPAD_INCLUDE_DIRS}) - ADD_DEFINITIONS(${BREAKPAD_DEFINITIONS} -D_USE_BREAKPAD) + ADD_DEFINITIONS(${BREAKPAD_DEFINITIONS}) SET(_USE_BREAKPAD TRUE) LIST(APPEND OpenMVS_EXTRA_LIBS ${BREAKPAD_LIBS}) else() @@ -119,23 +188,48 @@ if(OpenMVS_USE_BREAKPAD) endif() endif() -FIND_PACKAGE(Boost ${SYSTEM_PACKAGE_REQUIRED} COMPONENTS iostreams program_options system serialization) +SET(Boost_EXTRA_COMPONENTS "") +if(OpenMVS_USE_PYTHON) + FIND_PACKAGE(Python3 COMPONENTS Interpreter Development REQUIRED) + if(Python3_FOUND) + INCLUDE_DIRECTORIES(${Python3_INCLUDE_DIRS}) + LIST(APPEND OpenMVS_EXTRA_INCLUDES ${Python3_INCLUDE_DIRS}) + LIST(APPEND OpenMVS_EXTRA_LIBS ${Python3_LIBRARIES}) + LIST(APPEND Boost_EXTRA_COMPONENTS python${Python3_VERSION_MAJOR}${Python3_VERSION_MINOR}) + MESSAGE(STATUS "Python ${Python3_VERSION_MAJOR}.${Python3_VERSION_MINOR} found (include: ${Python3_INCLUDE_DIRS})") + else() + MESSAGE("-- Can't find Python. Continuing without it.") + endif() +endif() + +FIND_PACKAGE(Boost REQUIRED COMPONENTS iostreams program_options system serialization OPTIONAL_COMPONENTS ${Boost_EXTRA_COMPONENTS}) if(Boost_FOUND) + LIST(APPEND OpenMVS_EXTRA_INCLUDES ${Boost_INCLUDE_DIRS}) INCLUDE_DIRECTORIES(${Boost_INCLUDE_DIRS}) - ADD_DEFINITIONS(${Boost_DEFINITIONS} -D_USE_BOOST) + ADD_DEFINITIONS(${Boost_DEFINITIONS}) LINK_DIRECTORIES(${Boost_LIBRARY_DIRS}) + if(NOT MSVC AND DEFINED CMAKE_TOOLCHAIN_FILE) + # work around this missing library link in vcpkg + LIST(APPEND Boost_LIBRARIES zstd) + endif() SET(_USE_BOOST TRUE) + if(OpenMVS_USE_PYTHON AND Boost_${Boost_EXTRA_COMPONENTS}_FOUND) + SET(_USE_BOOST_PYTHON TRUE) + endif() endif() -FIND_PACKAGE(Eigen ${SYSTEM_PACKAGE_REQUIRED}) -if(EIGEN_FOUND) - INCLUDE_DIRECTORIES(${EIGEN_INCLUDE_DIRS}) - ADD_DEFINITIONS(${EIGEN_DEFINITIONS} -D_USE_EIGEN) +FIND_PACKAGE(Eigen3 3.4 REQUIRED) +if(EIGEN3_FOUND) + LIST(APPEND OpenMVS_EXTRA_INCLUDES ${EIGEN3_INCLUDE_DIR}) + INCLUDE_DIRECTORIES(${EIGEN3_INCLUDE_DIR}) + ADD_DEFINITIONS(${EIGEN3_DEFINITIONS}) SET(_USE_EIGEN TRUE) + MESSAGE(STATUS "Eigen ${EIGEN3_VERSION} found (include: ${EIGEN3_INCLUDE_DIR})") endif() -FIND_PACKAGE(OpenCV ${SYSTEM_PACKAGE_REQUIRED}) +FIND_PACKAGE(OpenCV REQUIRED) if(OpenCV_FOUND) + LIST(APPEND OpenMVS_EXTRA_INCLUDES ${OpenCV_INCLUDE_DIRS}) INCLUDE_DIRECTORIES(${OpenCV_INCLUDE_DIRS}) ADD_DEFINITIONS(${OpenCV_DEFINITIONS}) SET(_USE_OPENCV TRUE) @@ -144,76 +238,79 @@ else() MESSAGE("-- Can't find OpenCV. Please specify OpenCV directory using OpenCV_DIR variable") endif() +LIST(REMOVE_DUPLICATES OpenMVS_EXTRA_INCLUDES) +LIST(REMOVE_DUPLICATES OpenMVS_EXTRA_LIBS) + # Set defines -SET(OpenMVS_DEFINITIONS "") -if(OpenMVS_USE_NONFREE) - LIST(APPEND OpenMVS_DEFINITIONS -D_USE_NONFREE) - SET(_USE_NONFREE TRUE) -endif() if(OpenMVS_USE_FAST_FLOAT2INT) - LIST(APPEND OpenMVS_DEFINITIONS -D_USE_FAST_FLOAT2INT) SET(_USE_FAST_FLOAT2INT TRUE) endif() if(OpenMVS_USE_FAST_INVSQRT) - LIST(APPEND OpenMVS_DEFINITIONS -D_USE_FAST_INVSQRT) SET(_USE_FAST_INVSQRT TRUE) endif() if(OpenMVS_USE_FAST_CBRT) - LIST(APPEND OpenMVS_DEFINITIONS -D_USE_FAST_CBRT) SET(_USE_FAST_CBRT TRUE) endif() if(OpenMVS_USE_SSE) - LIST(APPEND OpenMVS_DEFINITIONS -D_USE_SSE) SET(_USE_SSE TRUE) endif() -ADD_DEFINITIONS(${OpenMVS_DEFINITIONS}) +if(OpenMVS_USE_CERES) + SET(_USE_CERES TRUE) +endif() + +INCLUDE_DIRECTORIES("${OpenMVS_SOURCE_DIR}") +INCLUDE_DIRECTORIES("${CMAKE_BINARY_DIR}") # Add modules ADD_SUBDIRECTORY(libs) -ADD_SUBDIRECTORY(apps) +if(OpenMVS_BUILD_TOOLS) + ADD_SUBDIRECTORY(apps) +endif() ADD_SUBDIRECTORY(docs) -if(OpenMVS_USE_CERES) - SET(_USE_CERES TRUE) +if (OpenMVS_ENABLE_TESTS) + # enable testing functionality + ENABLE_TESTING() + # define tests + ADD_TEST(NAME UnitTests COMMAND $ "0") + ADD_TEST(NAME PipelineTest COMMAND $ "1") endif() -# Set configuration file -CONFIGURE_FILE("${OpenMVS_SOURCE_DIR}/build/Templates/ConfigLocal.h.in" "${OpenMVS_CONFIG_INCLUDE_DIR}/ConfigLocal.h") -INSTALL(FILES "${OpenMVS_CONFIG_INCLUDE_DIR}/ConfigLocal.h" DESTINATION "${INSTALL_INCLUDE_DIR}") -INCLUDE_DIRECTORIES(${OpenMVS_CONFIG_INCLUDE_DIR}) - -# Add all targets to the build-tree export set -export(TARGETS Common IO Math MVS FILE "${PROJECT_BINARY_DIR}/OpenMVSTargets.cmake") - # Export the package for use from the build-tree # (this registers the build-tree with a global CMake-registry) -export(PACKAGE OpenMVS) - +EXPORT(PACKAGE OpenMVS) + +# Install the export set for use with the install-tree +INSTALL(EXPORT OpenMVSTargets + NAMESPACE OpenMVS:: + DESTINATION "${INSTALL_CMAKE_DIR}") + +# Install configuration file +CONFIGURE_FILE("${CMAKE_CURRENT_SOURCE_DIR}/build/Templates/ConfigLocal.h.in" "${CMAKE_BINARY_DIR}/ConfigLocal.h") +INSTALL(FILES "${CMAKE_BINARY_DIR}/ConfigLocal.h" DESTINATION "${INSTALL_INCLUDE_DIR}") + # Create the OpenMVSConfig.cmake and OpenMVSConfigVersion files -file(RELATIVE_PATH REL_INCLUDE_DIR "${INSTALL_CMAKE_DIR}" "${INSTALL_INCLUDE_DIR}") -# ... for the build tree -set(CONF_INCLUDE_DIRS "${PROJECT_SOURCE_DIR}" "${PROJECT_BINARY_DIR}") -configure_file("${CMAKE_CURRENT_SOURCE_DIR}/build/OpenMVSConfig.cmake.in" "${PROJECT_BINARY_DIR}/OpenMVSConfig.cmake" @ONLY) -# ... for the install tree -set(CONF_INCLUDE_DIRS "${INSTALL_CMAKE_DIR}/${REL_INCLUDE_DIR}") -configure_file("${CMAKE_CURRENT_SOURCE_DIR}/build/OpenMVSConfig.cmake.in" "${PROJECT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/OpenMVSConfig.cmake" @ONLY) -# ... for both -configure_file("${CMAKE_CURRENT_SOURCE_DIR}/build/OpenMVSConfigVersion.cmake.in" "${PROJECT_BINARY_DIR}/OpenMVSConfigVersion.cmake" @ONLY) - +INCLUDE(CMakePackageConfigHelpers) +write_basic_package_version_file("${PROJECT_BINARY_DIR}/OpenMVSConfigVersion.cmake" + VERSION ${OpenMVS_VERSION} + COMPATIBILITY AnyNewerVersion) +SET(INSTALL_INCLUDE_DIR_IN ${INSTALL_INCLUDE_DIR_PREFIX} ${OpenMVS_EXTRA_INCLUDES}) +SET(INSTALL_CMAKE_DIR_IN ${INSTALL_CMAKE_DIR_PREFIX}) +configure_package_config_file("${CMAKE_CURRENT_SOURCE_DIR}/build/Templates/OpenMVSConfig.cmake.in" + "${PROJECT_BINARY_DIR}/OpenMVSConfig.cmake" + INSTALL_DESTINATION ${PROJECT_BINARY_DIR} + NO_SET_AND_CHECK_MACRO) # Install the OpenMVSConfig.cmake and OpenMVSConfigVersion.cmake -install(FILES - "${PROJECT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/OpenMVSConfig.cmake" +INSTALL(FILES + "${PROJECT_BINARY_DIR}/OpenMVSConfig.cmake" "${PROJECT_BINARY_DIR}/OpenMVSConfigVersion.cmake" - DESTINATION "${INSTALL_CMAKE_DIR}" COMPONENT dev) - -# Install the export set for use with the install-tree -install(EXPORT OpenMVSTargets DESTINATION "${INSTALL_CMAKE_DIR}" COMPONENT dev) + DESTINATION "${INSTALL_CMAKE_DIR}") # uninstall target -configure_file( - "${CMAKE_CURRENT_SOURCE_DIR}/build/cmake_uninstall.cmake.in" +CONFIGURE_FILE( + "${CMAKE_CURRENT_SOURCE_DIR}/build/Templates/cmake_uninstall.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" IMMEDIATE @ONLY) -add_custom_target(uninstall +ADD_CUSTOM_TARGET(uninstall COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake) diff --git a/COPYRIGHT.md b/COPYRIGHT.md index 81d77966f..5f48c453f 100644 --- a/COPYRIGHT.md +++ b/COPYRIGHT.md @@ -13,28 +13,14 @@ This program includes works distributed under the terms of another license(s) an Copyright (c) 2007 SEACAVE SRL. Licensed under a [Boost license](http://www.boost.org/users/license.html). -* __easyexif__
- [https://github.com/mayanklahiri/easyexif](https://github.com/mayanklahiri/easyexif) - Copyright (c) 2010 Mayank Lahiri. - Distributed under the [New BSD License](http://opensource.org/licenses/BSD-3-Clause). - * __histogram__
Copyright (c) Jansson Consulting & Pierre Moulon. Licensed under the [MPL2 license](http://opensource.org/licenses/MPL-2.0). -* __htmlDoc__
- Copyright (c) Pierre Moulon. - Licensed under the [MPL2 license](http://opensource.org/licenses/MPL-2.0). - * __ACRANSAC__
Copyright (c) Pierre Moulon. Licensed under the [MPL2 license](http://opensource.org/licenses/MPL-2.0). -* __stlplus3__
- [http://stlplus.sourceforge.net](http://stlplus.sourceforge.net) - Copyright (c) 1999-2004 Southampton University, 2004 onwards Andy Rushton. All rights reserved. - Licensed under the [BSD license](http://opensource.org/licenses/bsd-license.php). - * __rectangle-bin-packing__
[http://clb.demon.fi/projects/rectangle-bin-packing](http://clb.demon.fi/projects/rectangle-bin-packing) Copyright (c) Jukka Jylänki. @@ -50,11 +36,6 @@ This program includes works distributed under the terms of another license(s) an Copyright (c) Joachim Wuttke. Licensed under the [FreeBSD license](http://opensource.org/licenses/BSD-2-Clause). -* __TRWS__
- [http://pub.ist.ac.at/~vnk/software.html](http://pub.ist.ac.at/~vnk/software.html) - Copyright (c) Vladimir Kolmogorov. - Licensed under the [MSR-SSLA license](http://research.microsoft.com/en-us/um/people/antr/vrr/vrr/license.htm). - * __ibfs__
[http://www.cs.tau.ac.il/~sagihed/ibfs](http://www.cs.tau.ac.il/~sagihed/ibfs) Copyright (c) Haim Kaplan and Sagi Hed. diff --git a/MvgMvsPipeline.py b/MvgMvsPipeline.py deleted file mode 100644 index 106857f7e..000000000 --- a/MvgMvsPipeline.py +++ /dev/null @@ -1,368 +0,0 @@ -#!/usr/bin/python3 -# -*- encoding: utf-8 -*- -# -# Created by @FlachyJoe -""" -This script is for an easy use of OpenMVG and OpenMVS - -usage: MvgMvs_Pipeline.py [-h] [--steps STEPS [STEPS ...]] [--preset PRESET] - [--0 0 [0 ...]] [--1 1 [1 ...]] [--2 2 [2 ...]] - [--3 3 [3 ...]] [--4 4 [4 ...]] [--5 5 [5 ...]] - [--6 6 [6 ...]] [--7 7 [7 ...]] [--8 8 [8 ...]] - [--9 9 [9 ...]] [--10 10 [10 ...]] [--11 11 [11 ...]] - [--12 12 [12 ...]] [--13 13 [13 ...]] - [--14 14 [14 ...]] [--15 15 [15 ...]] - input_dir output_dir - -Photogrammetry reconstruction with these steps: - 0. Intrinsics analysis openMVG_main_SfMInit_ImageListing - 1. Compute features openMVG_main_ComputeFeatures - 2. Compute matches openMVG_main_ComputeMatches - 3. Incremental reconstruction openMVG_main_IncrementalSfM - 4. Global reconstruction openMVG_main_GlobalSfM - 5. Colorize Structure openMVG_main_ComputeSfM_DataColor - 6. Structure from Known Poses openMVG_main_ComputeStructureFromKnownPoses - 7. Colorized robust triangulation openMVG_main_ComputeSfM_DataColor - 8. Control Points Registration ui_openMVG_control_points_registration - 9. Export to openMVS openMVG_main_openMVG2openMVS - 10. Densify point-cloud DensifyPointCloud - 11. Reconstruct the mesh ReconstructMesh - 12. Refine the mesh RefineMesh - 13. Texture the mesh TextureMesh - 14. Estimate disparity-maps DensifyPointCloud - 15. Fuse disparity-maps DensifyPointCloud - -positional arguments: - input_dir the directory wich contains the pictures set. - output_dir the directory wich will contain the resulting files. - -optional arguments: - -h, --help show this help message and exit - --steps STEPS [STEPS ...] steps to process - --preset PRESET steps list preset in - SEQUENTIAL = [0, 1, 2, 3, 9, 10, 11, 12, 13] - GLOBAL = [0, 1, 2, 4, 9, 10, 11, 12, 13] - MVG_SEQ = [0, 1, 2, 3, 5, 6, 7] - MVG_GLOBAL = [0, 1, 2, 4, 5, 6, 7] - MVS_SGM = [14, 15] - default : SEQUENTIAL - -Passthrough: - Option to be passed to command lines (remove - in front of option names) - e.g. --1 p ULTRA to use the ULTRA preset in openMVG_main_ComputeFeatures -""" - -import os -import subprocess -import sys -import argparse - -DEBUG = False - -if sys.platform.startswith('win'): - PATH_DELIM = ';' -else: - PATH_DELIM = ':' - -# add this script's directory to PATH -os.environ['PATH'] += PATH_DELIM + os.path.dirname(os.path.abspath(__file__)) - -# add current directory to PATH -os.environ['PATH'] += PATH_DELIM + os.getcwd() - - -def whereis(afile): - """ - return directory in which afile is, None if not found. Look in PATH - """ - if sys.platform.startswith('win'): - cmd = "where" - else: - cmd = "which" - try: - ret = subprocess.run([cmd, afile], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, check=True) - return os.path.split(ret.stdout.decode())[0] - except subprocess.CalledProcessError: - return None - - -def find(afile): - """ - As whereis look only for executable on linux, this find look for all file type - """ - for d in os.environ['PATH'].split(PATH_DELIM): - if os.path.isfile(os.path.join(d, afile)): - return d - return None - - -# Try to find openMVG and openMVS binaries in PATH -OPENMVG_BIN = whereis("openMVG_main_SfMInit_ImageListing") -OPENMVS_BIN = whereis("ReconstructMesh") - -# Try to find openMVG camera sensor database -CAMERA_SENSOR_DB_FILE = "sensor_width_camera_database.txt" -CAMERA_SENSOR_DB_DIRECTORY = find(CAMERA_SENSOR_DB_FILE) - -# Ask user for openMVG and openMVS directories if not found -if not OPENMVG_BIN: - OPENMVG_BIN = input("openMVG binary folder?\n") -if not OPENMVS_BIN: - OPENMVS_BIN = input("openMVS binary folder?\n") -if not CAMERA_SENSOR_DB_DIRECTORY: - CAMERA_SENSOR_DB_DIRECTORY = input("openMVG camera database (%s) folder?\n" % CAMERA_SENSOR_DB_FILE) - - -PRESET = {'SEQUENTIAL': [0, 1, 2, 3, 9, 10, 11, 12, 13], - 'GLOBAL': [0, 1, 2, 4, 9, 10, 11, 12, 13], - 'MVG_SEQ': [0, 1, 2, 3, 5, 6, 7], - 'MVG_GLOBAL': [0, 1, 2, 4, 5, 6, 7], - 'MVS_SGM': [14, 15]} - -PRESET_DEFAULT = 'SEQUENTIAL' - -# HELPERS for terminal colors -BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8) -NO_EFFECT, BOLD, UNDERLINE, BLINK, INVERSE, HIDDEN = (0, 1, 4, 5, 7, 8) - - -# from Python cookbook, #475186 -def has_colours(stream): - ''' - Return stream colours capability - ''' - if not hasattr(stream, "isatty"): - return False - if not stream.isatty(): - return False # auto color only on TTYs - try: - import curses - curses.setupterm() - return curses.tigetnum("colors") > 2 - except Exception: - # guess false in case of error - return False - -HAS_COLOURS = has_colours(sys.stdout) - - -def printout(text, colour=WHITE, background=BLACK, effect=NO_EFFECT): - """ - print() with colour - """ - if HAS_COLOURS: - seq = "\x1b[%d;%d;%dm" % (effect, 30+colour, 40+background) + text + "\x1b[0m" - sys.stdout.write(seq+'\r\n') - else: - sys.stdout.write(text+'\r\n') - - -# OBJECTS to store config and data in -class ConfContainer: - """ - Container for all the config variables - """ - def __init__(self): - pass - - -class AStep: - """ Represents a process step to be run """ - def __init__(self, info, cmd, opt): - self.info = info - self.cmd = cmd - self.opt = opt - - -class StepsStore: - """ List of steps with facilities to configure them """ - def __init__(self): - self.steps_data = [ - ["Intrinsics analysis", # 0 - os.path.join(OPENMVG_BIN, "openMVG_main_SfMInit_ImageListing"), - ["-i", "%input_dir%", "-o", "%matches_dir%", "-d", "%camera_file_params%"]], - ["Compute features", # 1 - os.path.join(OPENMVG_BIN, "openMVG_main_ComputeFeatures"), - ["-i", "%matches_dir%/sfm_data.json", "-o", "%matches_dir%", "-m", "SIFT", "-n", "4"]], - ["Compute matches", # 2 - os.path.join(OPENMVG_BIN, "openMVG_main_ComputeMatches"), - ["-i", "%matches_dir%/sfm_data.json", "-o", "%matches_dir%", "-n", "HNSWL2", "-r", ".8"]], - ["Incremental reconstruction", # 3 - os.path.join(OPENMVG_BIN, "openMVG_main_IncrementalSfM"), - ["-i", "%matches_dir%/sfm_data.json", "-m", "%matches_dir%", "-o", "%reconstruction_dir%"]], - ["Global reconstruction", # 4 - os.path.join(OPENMVG_BIN, "openMVG_main_GlobalSfM"), - ["-i", "%matches_dir%/sfm_data.json", "-m", "%matches_dir%", "-o", "%reconstruction_dir%"]], - ["Colorize Structure", # 5 - os.path.join(OPENMVG_BIN, "openMVG_main_ComputeSfM_DataColor"), - ["-i", "%reconstruction_dir%/sfm_data.bin", "-o", "%reconstruction_dir%/colorized.ply"]], - ["Structure from Known Poses", # 6 - os.path.join(OPENMVG_BIN, "openMVG_main_ComputeStructureFromKnownPoses"), - ["-i", "%reconstruction_dir%/sfm_data.bin", "-m", "%matches_dir%", "-f", "%matches_dir%/matches.f.bin", "-o", "%reconstruction_dir%/robust.bin"]], - ["Colorized robust triangulation", # 7 - os.path.join(OPENMVG_BIN, "openMVG_main_ComputeSfM_DataColor"), - ["-i", "%reconstruction_dir%/robust.bin", "-o", "%reconstruction_dir%/robust_colorized.ply"]], - ["Control Points Registration", # 8 - os.path.join(OPENMVG_BIN, "ui_openMVG_control_points_registration"), - ["-i", "%reconstruction_dir%/sfm_data.bin"]], - ["Export to openMVS", # 9 - os.path.join(OPENMVG_BIN, "openMVG_main_openMVG2openMVS"), - ["-i", "%reconstruction_dir%/sfm_data.bin", "-o", "%mvs_dir%/scene.mvs", "-d", "%mvs_dir%/images"]], - ["Densify point cloud", # 10 - os.path.join(OPENMVS_BIN, "DensifyPointCloud"), - ["scene.mvs", "--dense-config-file", "Densify.ini", "--resolution-level", "1", "-w", "%mvs_dir%"]], - ["Reconstruct the mesh", # 11 - os.path.join(OPENMVS_BIN, "ReconstructMesh"), - ["scene_dense.mvs", "-w", "%mvs_dir%"]], - ["Refine the mesh", # 12 - os.path.join(OPENMVS_BIN, "RefineMesh"), - ["scene_dense_mesh.mvs", "--scales", "2", "-w", "%mvs_dir%"]], - ["Texture the mesh", # 13 - os.path.join(OPENMVS_BIN, "TextureMesh"), - ["scene_dense_mesh_refine.mvs", "--decimate", "0.5", "-w", "%mvs_dir%"]], - ["Estimate disparity-maps", # 14 - os.path.join(OPENMVS_BIN, "DensifyPointCloud"), - ["scene.mvs", "--dense-config-file", "Densify.ini", "--fusion-mode", "-1", "-w", "%mvs_dir%"]], - ["Fuse disparity-maps", # 15 - os.path.join(OPENMVS_BIN, "DensifyPointCloud"), - ["scene.mvs", "--dense-config-file", "Densify.ini", "--fusion-mode", "-2", "-w", "%mvs_dir%"]] - ] - - def __getitem__(self, indice): - return AStep(*self.steps_data[indice]) - - def length(self): - return len(self.steps_data) - - def apply_conf(self, conf): - """ replace each %var% per conf.var value in steps data """ - for s in self.steps_data: - o2 = [] - for o in s[2]: - co = o.replace("%input_dir%", conf.input_dir) - co = co.replace("%output_dir%", conf.output_dir) - co = co.replace("%matches_dir%", conf.matches_dir) - co = co.replace("%reconstruction_dir%", conf.reconstruction_dir) - co = co.replace("%mvs_dir%", conf.mvs_dir) - co = co.replace("%camera_file_params%", conf.camera_file_params) - o2.append(co) - s[2] = o2 - - -CONF = ConfContainer() -STEPS = StepsStore() - -# ARGS -PARSER = argparse.ArgumentParser( - formatter_class=argparse.RawTextHelpFormatter, - description="Photogrammetry reconstruction with these steps: \r\n" + - "\r\n".join(("\t%i. %s\t %s" % (t, STEPS[t].info, STEPS[t].cmd) for t in range(STEPS.length()))) - ) -PARSER.add_argument('input_dir', - help="the directory wich contains the pictures set.") -PARSER.add_argument('output_dir', - help="the directory wich will contain the resulting files.") -PARSER.add_argument('--steps', - type=int, - nargs="+", - help="steps to process") -PARSER.add_argument('--preset', - help="steps list preset in \r\n" + - " \r\n".join([k + " = " + str(PRESET[k]) for k in PRESET]) + - " \r\ndefault : " + PRESET_DEFAULT) - -GROUP = PARSER.add_argument_group('Passthrough', description="Option to be passed to command lines (remove - in front of option names)\r\ne.g. --1 p ULTRA to use the ULTRA preset in openMVG_main_ComputeFeatures") -for n in range(STEPS.length()): - GROUP.add_argument('--'+str(n), nargs='+') - -PARSER.parse_args(namespace=CONF) # store args in the ConfContainer - - -# FOLDERS - -def mkdir_ine(dirname): - """Create the folder if not presents""" - if not os.path.exists(dirname): - os.mkdir(dirname) - - -# Absolute path for input and ouput dirs -CONF.input_dir = os.path.abspath(CONF.input_dir) -CONF.output_dir = os.path.abspath(CONF.output_dir) - -if not os.path.exists(CONF.input_dir): - sys.exit("%s: path not found" % CONF.input_dir) - -CONF.reconstruction_dir = os.path.join(CONF.output_dir, "sfm") -CONF.matches_dir = os.path.join(CONF.reconstruction_dir, "matches") -CONF.mvs_dir = os.path.join(CONF.output_dir, "mvs") -CONF.camera_file_params = os.path.join(CAMERA_SENSOR_DB_DIRECTORY, CAMERA_SENSOR_DB_FILE) - -mkdir_ine(CONF.output_dir) -mkdir_ine(CONF.reconstruction_dir) -mkdir_ine(CONF.matches_dir) -mkdir_ine(CONF.mvs_dir) - -# Update directories in steps commandlines -STEPS.apply_conf(CONF) - -# PRESET -if CONF.steps and CONF.preset: - sys.exit("Steps and preset arguments can't be set together.") -elif CONF.preset: - try: - CONF.steps = PRESET[CONF.preset] - except KeyError: - sys.exit("Unkown preset %s, choose %s" % (CONF.preset, ' or '.join([s for s in PRESET]))) -elif not CONF.steps: - CONF.steps = PRESET[PRESET_DEFAULT] - -# WALK -print("# Using input dir: %s" % CONF.input_dir) -print("# output dir: %s" % CONF.output_dir) -print("# Steps: %s" % str(CONF.steps)) - -if 2 in CONF.steps: # ComputeMatches - if 4 in CONF.steps: # GlobalReconstruction - # Set the geometric_model of ComputeMatches to Essential - STEPS[2].opt.extend(["-g", "e"]) - -for cstep in CONF.steps: - printout("#%i. %s" % (cstep, STEPS[cstep].info), effect=INVERSE) - - # Retrieve "passthrough" commandline options - opt = getattr(CONF, str(cstep)) - if opt: - # add - sign to short options and -- to long ones - for o in range(0, len(opt), 2): - if len(opt[o]) > 1: - opt[o] = '-' + opt[o] - opt[o] = '-' + opt[o] - else: - opt = [] - - # Remove STEPS[cstep].opt options now defined in opt - for anOpt in STEPS[cstep].opt: - if anOpt in opt: - idx = STEPS[cstep].opt.index(anOpt) - if DEBUG: - print('#\tRemove ' + str(anOpt) + ' from defaults options at id ' + str(idx)) - del STEPS[cstep].opt[idx:idx+2] - - # create a commandline for the current step - cmdline = [STEPS[cstep].cmd] + STEPS[cstep].opt + opt - print('Cmd: ' + ' '.join(cmdline)) - - if not DEBUG: - # Launch the current step - try: - pStep = subprocess.Popen(cmdline) - pStep.wait() - if pStep.returncode != 0: - break - except KeyboardInterrupt: - sys.exit('\r\nProcess canceled by user, all files remains') - else: - print('\t'.join(cmdline)) - -printout("# Pipeline end #", effect=INVERSE) diff --git a/README.md b/README.md index ac9a7cfad..9cad520bb 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # OpenMVS: open Multi-View Stereo reconstruction library +[![Watch reconstruction](https://i.imgur.com/S4L0E6r.jpeg)](https://sketchfab.com/models/3aedcdd22c31447199c18dd9aec5d952/embed) + ## Introduction [OpenMVS (Multi-View Stereo)](http://cdcseacave.github.io/openMVS) is a library for computer-vision scientists and especially targeted to the Multi-View Stereo reconstruction community. While there are mature and complete open-source projects targeting Structure-from-Motion pipelines (like [OpenMVG](https://github.com/openMVG/openMVG)) which recover camera poses and a sparse 3D point-cloud from an input set of images, there are none addressing the last part of the photogrammetry chain-flow. *OpenMVS* aims at filling that gap by providing a complete set of algorithms to recover the full surface of the scene to be reconstructed. The input is a set of camera poses plus the sparse point-cloud and the output is a textured mesh. The main topics covered by this project are: @@ -13,8 +15,8 @@ See the complete [documentation](https://github.com/cdcseacave/openMVS/wiki) on ## Build -See the [building](https://github.com/cdcseacave/openMVS/wiki/Building) wiki page. Windows and Ubuntu x64 continuous integration status [![Build Status](https://ci.appveyor.com/api/projects/status/github/cdcseacave/openmvs?branch=master&svg=true)](https://ci.appveyor.com/project/cdcseacave/openmvs) -Automatic Windows x64 binary builds can be found for each commit on its Appveyor Artifacts page. +See the [building](https://github.com/cdcseacave/openMVS/wiki/Building) wiki page. Windows, Ubuntu and MacOS x64 continuous integration status [![Continuous Integration](https://github.com/cdcseacave/openMVS/actions/workflows/continuous_integration.yml/badge.svg)](https://github.com/cdcseacave/openMVS/actions/workflows/continuous_integration.yml) +Automatic Windows x64 binary builds can be found for each commit on its Artifacts page. ## Example diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt index dfc4f88f8..3bb00c6c6 100644 --- a/apps/CMakeLists.txt +++ b/apps/CMakeLists.txt @@ -1,8 +1,14 @@ # Add applications ADD_SUBDIRECTORY(InterfaceCOLMAP) -ADD_SUBDIRECTORY(InterfaceVisualSFM) +ADD_SUBDIRECTORY(InterfaceMetashape) +ADD_SUBDIRECTORY(InterfaceMVSNet) +ADD_SUBDIRECTORY(InterfacePolycam) ADD_SUBDIRECTORY(DensifyPointCloud) ADD_SUBDIRECTORY(ReconstructMesh) ADD_SUBDIRECTORY(RefineMesh) ADD_SUBDIRECTORY(TextureMesh) +ADD_SUBDIRECTORY(TransformScene) ADD_SUBDIRECTORY(Viewer) +if(OpenMVS_ENABLE_TESTS) + ADD_SUBDIRECTORY(Tests) +endif() diff --git a/apps/DensifyPointCloud/CMakeLists.txt b/apps/DensifyPointCloud/CMakeLists.txt index 5dddebee4..38e27310b 100644 --- a/apps/DensifyPointCloud/CMakeLists.txt +++ b/apps/DensifyPointCloud/CMakeLists.txt @@ -5,7 +5,7 @@ else() endif() FILE(GLOB LIBRARY_FILES_H "*.h" "*.inl") -cxx_executable_with_flags_no_pch(DensifyPointCloud "Apps" "${cxx_default}" "MVS;${OpenMVS_EXTRA_LIBS}" ${LIBRARY_FILES_C} ${LIBRARY_FILES_H}) +cxx_executable_with_flags(DensifyPointCloud "Apps" "${cxx_default}" "MVS;${OpenMVS_EXTRA_LIBS}" ${LIBRARY_FILES_C} ${LIBRARY_FILES_H}) # Install INSTALL(TARGETS DensifyPointCloud diff --git a/apps/DensifyPointCloud/DensifyPointCloud.cpp b/apps/DensifyPointCloud/DensifyPointCloud.cpp index d82c07294..7637e60bb 100644 --- a/apps/DensifyPointCloud/DensifyPointCloud.cpp +++ b/apps/DensifyPointCloud/DensifyPointCloud.cpp @@ -43,14 +43,30 @@ using namespace MVS; // S T R U C T S /////////////////////////////////////////////////// +namespace { + namespace OPT { String strInputFileName; +String strPointCloudFileName; String strOutputFileName; +String strViewNeighborsFileName; +String strOutputViewNeighborsFileName; String strMeshFileName; +String strExportROIFileName; +String strImportROIFileName; String strDenseConfigFileName; +String strExportDepthMapsName; +String strMaskPath; +float fMaxSubsceneArea; float fSampleMesh; -int thFilterPointCloud; +float fBorderROI; +bool bCrop2ROI; +int nEstimateROI; +int nTowerMode; int nFusionMode; +float fEstimateScale; +int thFilterPointCloud; +int nExportNumViews; int nArchiveType; int nProcessPriority; unsigned nMaxThreads; @@ -58,8 +74,17 @@ String strConfigFileName; boost::program_options::variables_map vm; } // namespace OPT +class Application { +public: + Application() {} + ~Application() { Finalize(); } + + bool Initialize(size_t argc, LPCTSTR* argv); + void Finalize(); +}; // Application + // initialize and parse the command line parameters -bool Initialize(size_t argc, LPCTSTR* argv) +bool Application::Initialize(size_t argc, LPCTSTR* argv) { // initialize log and console OPEN_LOG(); @@ -71,7 +96,7 @@ bool Initialize(size_t argc, LPCTSTR* argv) ("help,h", "produce this help message") ("working-folder,w", boost::program_options::value(&WORKING_FOLDER), "working directory (default current directory)") ("config-file,c", boost::program_options::value(&OPT::strConfigFileName)->default_value(APPNAME _T(".cfg")), "file name containing program options") - ("archive-type", boost::program_options::value(&OPT::nArchiveType)->default_value(2), "project archive type: 0-text, 1-binary, 2-compressed binary") + ("archive-type", boost::program_options::value(&OPT::nArchiveType)->default_value(ARCHIVE_MVS), "project archive type: -1-interface, 0-text, 1-binary, 2-compressed binary") ("process-priority", boost::program_options::value(&OPT::nProcessPriority)->default_value(-1), "process priority (below normal by default)") ("max-threads", boost::program_options::value(&OPT::nMaxThreads)->default_value(0), "maximum number of threads (0 for using all available cores)") #if TD_VERBOSE != TD_VERBOSE_OFF @@ -83,37 +108,76 @@ bool Initialize(size_t argc, LPCTSTR* argv) #endif ), "verbosity level") #endif + #ifdef _USE_CUDA + ("cuda-device", boost::program_options::value(&SEACAVE::CUDA::desiredDeviceID)->default_value(-1), "CUDA device number to be used for depth-map estimation (-2 - CPU processing, -1 - best GPU, >=0 - device index)") + #endif ; // group of options allowed both on command line and in config file + #ifdef _USE_CUDA + const unsigned nNumViewsDefault(8); + const unsigned numIters(4); + #else + const unsigned nNumViewsDefault(5); + const unsigned numIters(3); + #endif unsigned nResolutionLevel; unsigned nMaxResolution; unsigned nMinResolution; unsigned nNumViews; unsigned nMinViewsFuse; + unsigned nSubResolutionLevels; + unsigned nEstimationIters; + unsigned nEstimationGeometricIters; unsigned nEstimateColors; unsigned nEstimateNormals; + unsigned nFuseFilter; + unsigned nOptimize; + int nIgnoreMaskLabel; + bool bRemoveDmaps; boost::program_options::options_description config("Densify options"); config.add_options() ("input-file,i", boost::program_options::value(&OPT::strInputFileName), "input filename containing camera poses and image list") - ("output-file,o", boost::program_options::value(&OPT::strOutputFileName), "output filename for storing the dense point-cloud") + ("pointcloud-file,p", boost::program_options::value(&OPT::strPointCloudFileName), "sparse point-cloud with views file name to densify (overwrite existing point-cloud)") + ("output-file,o", boost::program_options::value(&OPT::strOutputFileName), "output filename for storing the dense point-cloud (optional)") + ("view-neighbors-file", boost::program_options::value(&OPT::strViewNeighborsFileName), "input filename containing the list of views and their neighbors (optional)") + ("output-view-neighbors-file", boost::program_options::value(&OPT::strOutputViewNeighborsFileName), "output filename containing the generated list of views and their neighbors") ("resolution-level", boost::program_options::value(&nResolutionLevel)->default_value(1), "how many times to scale down the images before point cloud computation") - ("max-resolution", boost::program_options::value(&nMaxResolution)->default_value(3200), "do not scale images higher than this resolution") + ("max-resolution", boost::program_options::value(&nMaxResolution)->default_value(2560), "do not scale images higher than this resolution") ("min-resolution", boost::program_options::value(&nMinResolution)->default_value(640), "do not scale images lower than this resolution") - ("number-views", boost::program_options::value(&nNumViews)->default_value(5), "number of views used for depth-map estimation (0 - all neighbor views available)") - ("number-views-fuse", boost::program_options::value(&nMinViewsFuse)->default_value(3), "minimum number of images that agrees with an estimate during fusion in order to consider it inlier") + ("sub-resolution-levels", boost::program_options::value(&nSubResolutionLevels)->default_value(2), "number of patch-match sub-resolution iterations (0 - disabled)") + ("number-views", boost::program_options::value(&nNumViews)->default_value(nNumViewsDefault), "number of views used for depth-map estimation (0 - all neighbor views available)") + ("number-views-fuse", boost::program_options::value(&nMinViewsFuse)->default_value(2), "minimum number of images that agrees with an estimate during fusion in order to consider it inlier (<2 - only merge depth-maps)") + ("ignore-mask-label", boost::program_options::value(&nIgnoreMaskLabel)->default_value(-1), "label value to ignore in the image mask, stored in the MVS scene or next to each image with '.mask.png' extension (<0 - disabled)") + ("mask-path", boost::program_options::value(&OPT::strMaskPath), "path to folder containing mask images with '.mask.png' extension") + ("iters", boost::program_options::value(&nEstimationIters)->default_value(numIters), "number of patch-match iterations") + ("geometric-iters", boost::program_options::value(&nEstimationGeometricIters)->default_value(2), "number of geometric consistent patch-match iterations (0 - disabled)") ("estimate-colors", boost::program_options::value(&nEstimateColors)->default_value(2), "estimate the colors for the dense point-cloud (0 - disabled, 1 - final, 2 - estimate)") ("estimate-normals", boost::program_options::value(&nEstimateNormals)->default_value(2), "estimate the normals for the dense point-cloud (0 - disabled, 1 - final, 2 - estimate)") + ("estimate-scale", boost::program_options::value(&OPT::fEstimateScale)->default_value(0.f), "estimate the point-scale for the dense point-cloud (scale multiplier, 0 - disabled)") + ("sub-scene-area", boost::program_options::value(&OPT::fMaxSubsceneArea)->default_value(0.f), "split the scene in sub-scenes such that each sub-scene surface does not exceed the given maximum sampling area (0 - disabled)") ("sample-mesh", boost::program_options::value(&OPT::fSampleMesh)->default_value(0.f), "uniformly samples points on a mesh (0 - disabled, <0 - number of points, >0 - sample density per square unit)") + ("fusion-mode", boost::program_options::value(&OPT::nFusionMode)->default_value(0), "depth-maps fusion mode (-2 - fuse disparity-maps, -1 - export disparity-maps only, 0 - depth-maps & fusion, 1 - export depth-maps only)") + ("fusion-filter", boost::program_options::value(&nFuseFilter)->default_value(2), "filter used to fuse the depth-maps (0 - merge, 1 - fuse, 2 - dense-fuse)") + ("postprocess-dmaps", boost::program_options::value(&nOptimize)->default_value(7), "flags used to filter the depth-maps after estimation (0 - disabled, 1 - remove-speckles, 2 - fill-gaps, 4 - adjust-filter)") ("filter-point-cloud", boost::program_options::value(&OPT::thFilterPointCloud)->default_value(0), "filter dense point-cloud based on visibility (0 - disabled)") - ("fusion-mode", boost::program_options::value(&OPT::nFusionMode)->default_value(0), "depth map fusion mode (-2 - fuse disparity-maps, -1 - export disparity-maps only, 0 - depth-maps & fusion, 1 - export depth-maps only)") + ("export-number-views", boost::program_options::value(&OPT::nExportNumViews)->default_value(0), "export points with >= number of views (0 - disabled, <0 - save MVS project too)") + ("roi-border", boost::program_options::value(&OPT::fBorderROI)->default_value(0), "add a border to the region-of-interest when cropping the scene (0 - disabled, >0 - percentage, <0 - absolute)") + ("estimate-roi", boost::program_options::value(&OPT::nEstimateROI)->default_value(2), "estimate and set region-of-interest (0 - disabled, 1 - enabled, 2 - adaptive)") + ("crop-to-roi", boost::program_options::value(&OPT::bCrop2ROI)->default_value(true), "crop scene using the region-of-interest") + ("remove-dmaps", boost::program_options::value(&bRemoveDmaps)->default_value(false), "remove depth-maps after fusion") + ("tower-mode", boost::program_options::value(&OPT::nTowerMode)->default_value(4), "add a cylinder of points in the center of ROI; scene assume to be Z-up oriented (0 - disabled, 1 - replace, 2 - append, 3 - select neighbors, 4 - select neighbors & append, <0 - force tower mode)") ; // hidden options, allowed both on command line and // in config file, but will not be shown to the user boost::program_options::options_description hidden("Hidden options"); hidden.add_options() + ("mesh-file", boost::program_options::value(&OPT::strMeshFileName), "mesh file name used for image pair overlap estimation") + ("export-roi-file", boost::program_options::value(&OPT::strExportROIFileName), "ROI file name to be exported form the scene") + ("import-roi-file", boost::program_options::value(&OPT::strImportROIFileName), "ROI file name to be imported into the scene") ("dense-config-file", boost::program_options::value(&OPT::strDenseConfigFileName), "optional configuration file for the densifier (overwritten by the command line options)") + ("export-depth-maps-name", boost::program_options::value(&OPT::strExportDepthMapsName), "render given mesh and save the depth-map for every image to this file name base (empty - disabled)") ; boost::program_options::options_description cmdline_options; @@ -147,27 +211,31 @@ bool Initialize(size_t argc, LPCTSTR* argv) // print application details: version and command line Util::LogBuild(); - LOG(_T("Command line:%s"), Util::CommandLineToString(argc, argv).c_str()); + LOG(_T("Command line: ") APPNAME _T("%s"), Util::CommandLineToString(argc, argv).c_str()); // validate input Util::ensureValidPath(OPT::strInputFileName); - Util::ensureUnifySlash(OPT::strInputFileName); - if (OPT::vm.count("help") || OPT::strInputFileName.IsEmpty()) { + if (OPT::vm.count("help") || OPT::strInputFileName.empty()) { boost::program_options::options_description visible("Available options"); visible.add(generic).add(config); GET_LOG() << visible; } - if (OPT::strInputFileName.IsEmpty()) + if (OPT::strInputFileName.empty()) return false; // initialize optional options + Util::ensureValidPath(OPT::strPointCloudFileName); Util::ensureValidPath(OPT::strOutputFileName); - Util::ensureUnifySlash(OPT::strOutputFileName); - if (OPT::strOutputFileName.IsEmpty()) + Util::ensureValidPath(OPT::strViewNeighborsFileName); + Util::ensureValidPath(OPT::strOutputViewNeighborsFileName); + Util::ensureValidPath(OPT::strMeshFileName); + Util::ensureValidPath(OPT::strExportROIFileName); + Util::ensureValidPath(OPT::strImportROIFileName); + if (OPT::strOutputFileName.empty()) OPT::strOutputFileName = Util::getFileFullName(OPT::strInputFileName) + _T("_dense.mvs"); // init dense options - if (!OPT::strDenseConfigFileName.IsEmpty()) + if (!OPT::strDenseConfigFileName.empty()) OPT::strDenseConfigFileName = MAKE_PATH_SAFE(OPT::strDenseConfigFileName); OPTDENSE::init(); const bool bValidConfig(OPTDENSE::oConfig.Load(OPT::strDenseConfigFileName)); @@ -175,42 +243,36 @@ bool Initialize(size_t argc, LPCTSTR* argv) OPTDENSE::nResolutionLevel = nResolutionLevel; OPTDENSE::nMaxResolution = nMaxResolution; OPTDENSE::nMinResolution = nMinResolution; + OPTDENSE::nSubResolutionLevels = nSubResolutionLevels; OPTDENSE::nNumViews = nNumViews; OPTDENSE::nMinViewsFuse = nMinViewsFuse; + OPTDENSE::nEstimationIters = nEstimationIters; + OPTDENSE::nEstimationGeometricIters = nEstimationGeometricIters; OPTDENSE::nEstimateColors = nEstimateColors; OPTDENSE::nEstimateNormals = nEstimateNormals; - if (!bValidConfig && !OPT::strDenseConfigFileName.IsEmpty()) + OPTDENSE::nFuseFilter = nFuseFilter; + OPTDENSE::nOptimize = nOptimize; + OPTDENSE::nIgnoreMaskLabel = nIgnoreMaskLabel; + OPTDENSE::bRemoveDmaps = bRemoveDmaps; + if (!bValidConfig && !OPT::strDenseConfigFileName.empty()) OPTDENSE::oConfig.Save(OPT::strDenseConfigFileName); - // initialize global options - Process::setCurrentProcessPriority((Process::Priority)OPT::nProcessPriority); - #ifdef _USE_OPENMP - if (OPT::nMaxThreads != 0) - omp_set_num_threads(OPT::nMaxThreads); - #endif - - #ifdef _USE_BREAKPAD - // start memory dumper - MiniDumper::Create(APPNAME, WORKING_FOLDER); - #endif - - Util::Init(); + MVS::Initialize(APPNAME, OPT::nMaxThreads, OPT::nProcessPriority); return true; } // finalize application instance -void Finalize() +void Application::Finalize() { - #if TD_VERBOSE != TD_VERBOSE_OFF - // print memory statistics - Util::LogMemoryInfo(); - #endif + MVS::Finalize(); CLOSE_LOGFILE(); CLOSE_LOGCONSOLE(); CLOSE_LOG(); } +} // unnamed namespace + int main(int argc, LPCTSTR* argv) { #ifdef _DEBUGINFO @@ -218,63 +280,157 @@ int main(int argc, LPCTSTR* argv) _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);// | _CRTDBG_CHECK_ALWAYS_DF); #endif - if (!Initialize(argc, argv)) + Application application; + if (!application.Initialize(argc, argv)) return EXIT_FAILURE; Scene scene(OPT::nMaxThreads); if (OPT::fSampleMesh != 0) { // sample input mesh and export the obtained point-cloud - if (!scene.mesh.Load(MAKE_PATH_SAFE(OPT::strInputFileName))) + if (!scene.Load(MAKE_PATH_SAFE(OPT::strInputFileName), true) || scene.mesh.IsEmpty()) return EXIT_FAILURE; TD_TIMER_START(); PointCloud pointcloud; if (OPT::fSampleMesh > 0) scene.mesh.SamplePoints(OPT::fSampleMesh, 0, pointcloud); else - scene.mesh.SamplePoints((unsigned)ROUND2INT(-OPT::fSampleMesh), pointcloud); + scene.mesh.SamplePoints(ROUND2INT(-OPT::fSampleMesh), pointcloud); VERBOSE("Sample mesh completed: %u points (%s)", pointcloud.GetSize(), TD_TIMER_GET_FMT().c_str()); pointcloud.Save(MAKE_PATH_SAFE(Util::getFileFullName(OPT::strOutputFileName))+_T(".ply")); - Finalize(); return EXIT_SUCCESS; } // load and estimate a dense point-cloud - if (!scene.Load(MAKE_PATH_SAFE(OPT::strInputFileName))) + const Scene::SCENE_TYPE sceneType(scene.Load(MAKE_PATH_SAFE(OPT::strInputFileName))); + if (sceneType == Scene::SCENE_NA) return EXIT_FAILURE; - if (scene.pointcloud.IsEmpty()) { - VERBOSE("error: empty initial point-cloud"); + if (!OPT::strPointCloudFileName.empty() && !scene.pointcloud.Load(MAKE_PATH_SAFE(OPT::strPointCloudFileName))) { + VERBOSE("error: cannot load point-cloud file"); return EXIT_FAILURE; } + if (!OPT::strMaskPath.empty()) { + Util::ensureValidFolderPath(OPT::strMaskPath); + for (Image& image : scene.images) { + if (!image.maskName.empty()) { + VERBOSE("error: Image %s has non-empty maskName %s", image.name.c_str(), image.maskName.c_str()); + return EXIT_FAILURE; + } + image.maskName = OPT::strMaskPath + Util::getFileName(image.name) + ".mask.png"; + if (!File::access(image.maskName)) { + VERBOSE("error: Mask image %s not found", image.maskName.c_str()); + return EXIT_FAILURE; + } + } + } + if (!OPT::strImportROIFileName.empty()) { + std::ifstream fs(MAKE_PATH_SAFE(OPT::strImportROIFileName)); + if (!fs) + return EXIT_FAILURE; + fs >> scene.obb; + scene.Save(MAKE_PATH_SAFE(Util::getFileFullName(OPT::strOutputFileName))+_T(".mvs"), (ARCHIVE_TYPE)OPT::nArchiveType); + return EXIT_SUCCESS; + } + if (!scene.IsBounded()) + scene.EstimateROI(OPT::nEstimateROI, 1.1f); + if (!OPT::strExportROIFileName.empty() && scene.IsBounded()) { + std::ofstream fs(MAKE_PATH_SAFE(OPT::strExportROIFileName)); + if (!fs) + return EXIT_FAILURE; + fs << scene.obb; + return EXIT_SUCCESS; + } + if (OPT::nTowerMode!=0) + scene.InitTowerScene(OPT::nTowerMode); + if (!OPT::strMeshFileName.empty()) + scene.mesh.Load(MAKE_PATH_SAFE(OPT::strMeshFileName)); + if (!OPT::strViewNeighborsFileName.empty()) + scene.LoadViewNeighbors(MAKE_PATH_SAFE(OPT::strViewNeighborsFileName)); + if (!OPT::strOutputViewNeighborsFileName.empty()) { + if (!scene.ImagesHaveNeighbors()) { + VERBOSE("error: neighbor views not computed yet"); + return EXIT_FAILURE; + } + scene.SaveViewNeighbors(MAKE_PATH_SAFE(OPT::strOutputViewNeighborsFileName)); + return EXIT_SUCCESS; + } + if (!OPT::strExportDepthMapsName.empty() && !scene.mesh.IsEmpty()) { + // project mesh onto each image and save the resulted depth-maps + TD_TIMER_START(); + if (!scene.ExportMeshToDepthMaps(MAKE_PATH_SAFE(OPT::strExportDepthMapsName))) + return EXIT_FAILURE; + VERBOSE("Mesh projection completed: %u depth-maps (%s)", scene.images.size(), TD_TIMER_GET_FMT().c_str()); + return EXIT_SUCCESS; + } + if (OPT::fMaxSubsceneArea > 0) { + // split the scene in sub-scenes by maximum sampling area + Scene::ImagesChunkArr chunks; + scene.Split(chunks, OPT::fMaxSubsceneArea); + scene.ExportChunks(chunks, GET_PATH_FULL(OPT::strOutputFileName), (ARCHIVE_TYPE)OPT::nArchiveType); + return EXIT_SUCCESS; + } if (OPT::thFilterPointCloud < 0) { // filter point-cloud based on camera-point visibility intersections scene.PointCloudFilter(OPT::thFilterPointCloud); const String baseFileName(MAKE_PATH_SAFE(Util::getFileFullName(OPT::strOutputFileName))+_T("_filtered")); scene.Save(baseFileName+_T(".mvs"), (ARCHIVE_TYPE)OPT::nArchiveType); scene.pointcloud.Save(baseFileName+_T(".ply")); - Finalize(); return EXIT_SUCCESS; } - if ((ARCHIVE_TYPE)OPT::nArchiveType != ARCHIVE_MVS) { + if (OPT::nExportNumViews && scene.pointcloud.IsValid()) { + // export point-cloud containing only points with N+ views + const String baseFileName(MAKE_PATH_SAFE(Util::getFileFullName(OPT::strOutputFileName))+ + String::FormatString(_T("_%dviews"), ABS(OPT::nExportNumViews))); + if (OPT::nExportNumViews > 0) { + // export point-cloud containing only points with N+ views + scene.pointcloud.SaveNViews(baseFileName+_T(".ply"), (IIndex)OPT::nExportNumViews); + } else { + // save scene and export point-cloud containing only points with N+ views + scene.pointcloud.RemoveMinViews((IIndex)-OPT::nExportNumViews); + scene.Save(baseFileName+_T(".mvs"), (ARCHIVE_TYPE)OPT::nArchiveType); + scene.pointcloud.Save(baseFileName+_T(".ply")); + } + return EXIT_SUCCESS; + } + if (OPT::fEstimateScale > 0 && !scene.pointcloud.IsEmpty() && !scene.images.empty()) { + // simply export existing point-cloud with scale + if (scene.pointcloud.normals.empty()) { + if (!scene.pointcloud.IsValid()) { + VERBOSE("error: can not estimate normals as the point-cloud is not valid"); + return EXIT_FAILURE; + } + EstimatePointNormals(scene.images, scene.pointcloud); + } + const String baseFileName(MAKE_PATH_SAFE(Util::getFileFullName(OPT::strOutputFileName))); + scene.pointcloud.SaveWithScale(baseFileName+_T("_scale.ply"), scene.images, OPT::fEstimateScale); + return EXIT_SUCCESS; + } + PointCloud sparsePointCloud; + if ((ARCHIVE_TYPE)OPT::nArchiveType != ARCHIVE_MVS || sceneType == Scene::SCENE_INTERFACE) { + #if TD_VERBOSE != TD_VERBOSE_OFF + if (VERBOSITY_LEVEL > 1 && !scene.pointcloud.IsEmpty()) + scene.pointcloud.PrintStatistics(scene.images.data(), &scene.obb); + #endif + if ((ARCHIVE_TYPE)OPT::nArchiveType == ARCHIVE_MVS) + sparsePointCloud = scene.pointcloud; TD_TIMER_START(); - if (!scene.DenseReconstruction(OPT::nFusionMode)) { + if (!scene.DenseReconstruction(OPT::nFusionMode, OPT::bCrop2ROI, OPT::fBorderROI)) { if (ABS(OPT::nFusionMode) != 1) return EXIT_FAILURE; VERBOSE("Depth-maps estimated (%s)", TD_TIMER_GET_FMT().c_str()); - Finalize(); return EXIT_SUCCESS; } VERBOSE("Densifying point-cloud completed: %u points (%s)", scene.pointcloud.GetSize(), TD_TIMER_GET_FMT().c_str()); } - // save the final mesh + // save the final point-cloud const String baseFileName(MAKE_PATH_SAFE(Util::getFileFullName(OPT::strOutputFileName))); - scene.Save(baseFileName+_T(".mvs"), (ARCHIVE_TYPE)OPT::nArchiveType); - scene.pointcloud.Save(baseFileName+_T(".ply")); + scene.pointcloud.Save(baseFileName+_T(".ply"), (ARCHIVE_TYPE)OPT::nArchiveType == ARCHIVE_MVS); #if TD_VERBOSE != TD_VERBOSE_OFF if (VERBOSITY_LEVEL > 2) scene.ExportCamerasMLP(baseFileName+_T(".mlp"), baseFileName+_T(".ply")); #endif - - Finalize(); + if ((ARCHIVE_TYPE)OPT::nArchiveType == ARCHIVE_MVS) + scene.pointcloud.Swap(sparsePointCloud); + scene.Save(baseFileName+_T(".mvs"), (ARCHIVE_TYPE)OPT::nArchiveType); return EXIT_SUCCESS; } /*----------------------------------------------------------------*/ diff --git a/apps/InterfaceCOLMAP/CMakeLists.txt b/apps/InterfaceCOLMAP/CMakeLists.txt index 0a5b0fc0e..5d48c79ec 100644 --- a/apps/InterfaceCOLMAP/CMakeLists.txt +++ b/apps/InterfaceCOLMAP/CMakeLists.txt @@ -5,7 +5,7 @@ else() endif() FILE(GLOB LIBRARY_FILES_H "*.h" "*.inl") -cxx_executable_with_flags_no_pch(InterfaceCOLMAP "Apps" "${cxx_default}" "MVS;${OpenMVS_EXTRA_LIBS}" ${LIBRARY_FILES_C} ${LIBRARY_FILES_H}) +cxx_executable_with_flags(InterfaceCOLMAP "Apps" "${cxx_default}" "MVS;${OpenMVS_EXTRA_LIBS}" ${LIBRARY_FILES_C} ${LIBRARY_FILES_H}) # Install INSTALL(TARGETS InterfaceCOLMAP diff --git a/apps/InterfaceCOLMAP/InterfaceCOLMAP.cpp b/apps/InterfaceCOLMAP/InterfaceCOLMAP.cpp index dbe796ca6..2a8ded5a2 100644 --- a/apps/InterfaceCOLMAP/InterfaceCOLMAP.cpp +++ b/apps/InterfaceCOLMAP/InterfaceCOLMAP.cpp @@ -31,8 +31,6 @@ #include "../../libs/MVS/Common.h" #include "../../libs/MVS/Scene.h" -#define _USE_OPENCV -#include "../../libs/MVS/Interface.h" #include #include "endian.h" @@ -63,10 +61,14 @@ using namespace MVS; // S T R U C T S /////////////////////////////////////////////////// +namespace { + namespace OPT { -bool b3Dnovator2COLMAP; // conversion direction +bool bFromOpenMVS; // conversion direction bool bNormalizeIntrinsics; +bool bForceSparsePointCloud; String strInputFileName; +String strPointCloudFileName; String strOutputFileName; String strImageFolder; unsigned nArchiveType; @@ -76,8 +78,17 @@ String strConfigFileName; boost::program_options::variables_map vm; } // namespace OPT +class Application { +public: + Application() {} + ~Application() { Finalize(); } + + bool Initialize(size_t argc, LPCTSTR* argv); + void Finalize(); +}; // Application + // initialize and parse the command line parameters -bool Initialize(size_t argc, LPCTSTR* argv) +bool Application::Initialize(size_t argc, LPCTSTR* argv) { // initialize log and console OPEN_LOG(); @@ -86,10 +97,10 @@ bool Initialize(size_t argc, LPCTSTR* argv) // group of options allowed only on command line boost::program_options::options_description generic("Generic options"); generic.add_options() - ("help,h", "produce this help message") + ("help,h", "imports SfM or MVS scene stored in COLMAP undistoreted format OR exports MVS scene to COLMAP format") ("working-folder,w", boost::program_options::value(&WORKING_FOLDER), "working directory (default current directory)") ("config-file,c", boost::program_options::value(&OPT::strConfigFileName)->default_value(APPNAME _T(".cfg")), "file name containing program options") - ("archive-type", boost::program_options::value(&OPT::nArchiveType)->default_value(2), "project archive type: 0-text, 1-binary, 2-compressed binary") + ("archive-type", boost::program_options::value(&OPT::nArchiveType)->default_value(ARCHIVE_MVS), "project archive type: -1-interface, 0-text, 1-binary, 2-compressed binary") ("process-priority", boost::program_options::value(&OPT::nProcessPriority)->default_value(-1), "process priority (below normal by default)") ("max-threads", boost::program_options::value(&OPT::nMaxThreads)->default_value(0), "maximum number of threads (0 for using all available cores)") #if TD_VERBOSE != TD_VERBOSE_OFF @@ -107,9 +118,11 @@ bool Initialize(size_t argc, LPCTSTR* argv) boost::program_options::options_description config("Main options"); config.add_options() ("input-file,i", boost::program_options::value(&OPT::strInputFileName), "input COLMAP folder containing cameras, images and points files OR input MVS project file") + ("pointcloud-file,p", boost::program_options::value(&OPT::strPointCloudFileName), "point-cloud with views file name (overwrite existing point-cloud)") ("output-file,o", boost::program_options::value(&OPT::strOutputFileName), "output filename for storing the MVS project") ("image-folder", boost::program_options::value(&OPT::strImageFolder)->default_value(COLMAP_IMAGES_FOLDER), "folder to the undistorted images") - ("normalize,f", boost::program_options::value(&OPT::bNormalizeIntrinsics)->default_value(true), "normalize intrinsics while exporting to MVS format") + ("normalize,f", boost::program_options::value(&OPT::bNormalizeIntrinsics)->default_value(false), "normalize intrinsics while exporting to MVS format") + ("force-points,e", boost::program_options::value(&OPT::bForceSparsePointCloud)->default_value(false), "force exporting point-cloud as sparse points also even if dense point-cloud detected") ; boost::program_options::options_description cmdline_options; @@ -147,8 +160,7 @@ bool Initialize(size_t argc, LPCTSTR* argv) // validate input Util::ensureValidPath(OPT::strInputFileName); - const String strInputFileNameExt(Util::getFileExt(OPT::strInputFileName).ToLower()); - OPT::b3Dnovator2COLMAP = (strInputFileNameExt == MVS_EXT); + Util::ensureValidPath(OPT::strPointCloudFileName); const bool bInvalidCommand(OPT::strInputFileName.empty()); if (OPT::vm.count("help") || bInvalidCommand) { boost::program_options::options_description visible("Available options"); @@ -166,40 +178,26 @@ bool Initialize(size_t argc, LPCTSTR* argv) // initialize optional options Util::ensureValidFolderPath(OPT::strImageFolder); Util::ensureValidPath(OPT::strOutputFileName); - if (OPT::b3Dnovator2COLMAP) { + OPT::strImageFolder = MAKE_PATH_FULL(WORKING_FOLDER_FULL, OPT::strImageFolder); + const String strInputFileNameExt(Util::getFileExt(OPT::strInputFileName).ToLower()); + OPT::bFromOpenMVS = (strInputFileNameExt == MVS_EXT); + if (OPT::bFromOpenMVS) { if (OPT::strOutputFileName.empty()) OPT::strOutputFileName = Util::getFilePath(OPT::strInputFileName); } else { Util::ensureFolderSlash(OPT::strInputFileName); if (OPT::strOutputFileName.empty()) OPT::strOutputFileName = OPT::strInputFileName + _T("scene") MVS_EXT; - else - OPT::strImageFolder = Util::getRelativePath(Util::getFilePath(OPT::strOutputFileName), OPT::strInputFileName+OPT::strImageFolder); } - // initialize global options - Process::setCurrentProcessPriority((Process::Priority)OPT::nProcessPriority); - #ifdef _USE_OPENMP - if (OPT::nMaxThreads != 0) - omp_set_num_threads(OPT::nMaxThreads); - #endif - - #ifdef _USE_BREAKPAD - // start memory dumper - MiniDumper::Create(APPNAME, WORKING_FOLDER); - #endif - - Util::Init(); + MVS::Initialize(APPNAME, OPT::nMaxThreads, OPT::nProcessPriority); return true; } // finalize application instance -void Finalize() +void Application::Finalize() { - #if TD_VERBOSE != TD_VERBOSE_OFF - // print memory statistics - Util::LogMemoryInfo(); - #endif + MVS::Finalize(); CLOSE_LOGFILE(); CLOSE_LOGCONSOLE(); @@ -217,22 +215,19 @@ typedef uint64_t image_pair_t; typedef uint32_t point2D_t; typedef uint64_t point3D_t; -typedef std::unordered_map CameraModelMap; -CameraModelMap mapCameraModel; - -void DefineCameraModels() { - COLMAP::mapCameraModel.emplace(0, "SIMPLE_PINHOLE"); - COLMAP::mapCameraModel.emplace(1, "PINHOLE"); - COLMAP::mapCameraModel.emplace(2, "SIMPLE_RADIAL"); - COLMAP::mapCameraModel.emplace(3, "RADIAL"); - COLMAP::mapCameraModel.emplace(4, "OPENCV"); - COLMAP::mapCameraModel.emplace(5, "OPENCV_FISHEYE"); - COLMAP::mapCameraModel.emplace(6, "FULL_OPENCV"); - COLMAP::mapCameraModel.emplace(7, "FOV"); - COLMAP::mapCameraModel.emplace(8, "SIMPLE_RADIAL_FISHEYE"); - COLMAP::mapCameraModel.emplace(9, "RADIAL_FISHEYE"); - COLMAP::mapCameraModel.emplace(10, "THIN_PRISM_FISHEYE"); -} +const std::vector mapCameraModel = { + "SIMPLE_PINHOLE", + "PINHOLE", + "SIMPLE_RADIAL", + "RADIAL", + "OPENCV", + "OPENCV_FISHEYE", + "FULL_OPENCV", + "FOV", + "SIMPLE_RADIAL_FISHEYE", + "RADIAL_FISHEYE", + "THIN_PRISM_FISHEYE" +}; // tools bool NextLine(std::istream& stream, std::istringstream& in, bool bIgnoreEmpty=true) { @@ -253,7 +248,7 @@ struct Camera { String model; // camera model name uint32_t width, height; // camera resolution std::vector params; // camera parameters - bool parsedNumCameras = false; + uint64_t numCameras{0}; // only for binary format Camera() {} Camera(uint32_t _ID) : ID(_ID) {} @@ -261,13 +256,12 @@ struct Camera { struct CameraHash { size_t operator()(const Camera& camera) const { - const size_t h1(std::hash()(camera.model)); - const size_t h2(std::hash()(camera.width)); - const size_t h3(std::hash()(camera.height)); - size_t h(h1 ^ ((h2 ^ (h3 << 1)) << 1)); + size_t seed = std::hash()(camera.model); + std::hash_combine(seed, camera.width); + std::hash_combine(seed, camera.height); for (REAL p: camera.params) - h = std::hash()(p) ^ (h << 1); - return h; + std::hash_combine(seed, p); + return seed; } }; struct CameraEqualTo { @@ -279,13 +273,17 @@ struct Camera { }; bool Read(std::istream& stream, bool binary) { - if (binary) { + if (binary) return ReadBIN(stream); - } else { - return ReadTXT(stream); - } + return ReadTXT(stream); } + bool Write(std::ostream& stream, bool binary) { + if (binary) + return WriteBIN(stream); + return WriteTXT(stream); + } + // Camera list with one line of data per camera: // CAMERA_ID, MODEL, WIDTH, HEIGHT, PARAMS[] bool ReadTXT(std::istream& stream) { @@ -295,6 +293,8 @@ struct Camera { in >> ID >> model >> width >> height; if (in.fail()) return false; + ASSERT(ID > 0); + --ID; if (model != _T("PINHOLE")) return false; params.resize(4); @@ -305,20 +305,20 @@ struct Camera { // See: colmap/src/base/reconstruction.cc // void Reconstruction::ReadCamerasBinary(const std::string& path) bool ReadBIN(std::istream& stream) { - if (stream.peek() == EOF) return false; - if (!parsedNumCameras) { + if (numCameras == 0) { // Read the first entry in the binary file - ReadBinaryLittleEndian(&stream); - parsedNumCameras = true; + numCameras = ReadBinaryLittleEndian(&stream); } ID = ReadBinaryLittleEndian(&stream); - model = mapCameraModel.at(ReadBinaryLittleEndian(&stream)); + model = mapCameraModel[ReadBinaryLittleEndian(&stream)]; width = (uint32_t)ReadBinaryLittleEndian(&stream); height = (uint32_t)ReadBinaryLittleEndian(&stream); + ASSERT(ID > 0); + --ID; if (model != _T("PINHOLE")) return false; params.resize(4); @@ -326,7 +326,7 @@ struct Camera { return true; } - bool Write(std::ostream& out) const { + bool WriteTXT(std::ostream& out) const { out << ID+1 << _T(" ") << model << _T(" ") << width << _T(" ") << height; if (out.fail()) return false; @@ -338,6 +338,23 @@ struct Camera { out << std::endl; return true; } + + bool WriteBIN(std::ostream& stream) { + if (numCameras != 0) { + // Write the first entry in the binary file + WriteBinaryLittleEndian(&stream, numCameras); + numCameras = 0; + } + + WriteBinaryLittleEndian(&stream, ID+1); + const int64 modelId(std::distance(mapCameraModel.begin(), std::find(mapCameraModel.begin(), mapCameraModel.end(), model))); + WriteBinaryLittleEndian(&stream, (int)modelId); + WriteBinaryLittleEndian(&stream, width); + WriteBinaryLittleEndian(&stream, height); + for (REAL param: params) + WriteBinaryLittleEndian(&stream, param); + return !stream.fail(); + } }; typedef std::vector Cameras; // structure describing an image @@ -352,18 +369,22 @@ struct Image { uint32_t idCamera; // ID of the associated camera String name; // image file name std::vector projs; // known image projections - bool parsedNumRegImages = false; + uint64_t numRegImages{0}; // only for binary format Image() {} Image(uint32_t _ID) : ID(_ID) {} bool operator < (const Image& rhs) const { return ID < rhs.ID; } bool Read(std::istream& stream, bool binary) { - if (binary) { + if (binary) return ReadBIN(stream); - } else { - return ReadTXT(stream); - } + return ReadTXT(stream); + } + + bool Write(std::ostream& stream, bool binary) { + if (binary) + return WriteBIN(stream); + return WriteTXT(stream); } // Image list with two lines of data per image: @@ -379,6 +400,8 @@ struct Image { >> idCamera >> name; if (in.fail()) return false; + ASSERT(ID > 0 && idCamera > 0); + --ID; --idCamera; Util::ensureValidPath(name); if (!NextLine(stream, in, false)) return false; @@ -388,7 +411,9 @@ struct Image { in >> proj.p(0) >> proj.p(1) >> (int&)proj.idPoint; if (in.fail()) break; - projs.push_back(proj); + ASSERT(proj.idPoint > 0); + --proj.idPoint; + projs.emplace_back(proj); } return true; } @@ -396,14 +421,12 @@ struct Image { // See: colmap/src/base/reconstruction.cc // void Reconstruction::ReadImagesBinary(const std::string& path) bool ReadBIN(std::istream& stream) { - if (stream.peek() == EOF) return false; - if (!parsedNumRegImages) { + if (!numRegImages) { // Read the first entry in the binary file - ReadBinaryLittleEndian(&stream); - parsedNumRegImages = true; + numRegImages = ReadBinaryLittleEndian(&stream); } ID = ReadBinaryLittleEndian(&stream); @@ -415,15 +438,17 @@ struct Image { t(1) = ReadBinaryLittleEndian(&stream); t(2) = ReadBinaryLittleEndian(&stream); idCamera = ReadBinaryLittleEndian(&stream); + ASSERT(ID > 0 && idCamera > 0); + --ID; --idCamera; name = ""; - char nameChar; - do { + while (true) { + char nameChar; stream.read(&nameChar, 1); - if (nameChar != '\0') { - name += nameChar; - } - } while (nameChar != '\0'); + if (nameChar == '\0') + break; + name += nameChar; + } Util::ensureValidPath(name); const size_t numPoints2D = ReadBinaryLittleEndian(&stream); @@ -433,12 +458,14 @@ struct Image { proj.p(0) = (float)ReadBinaryLittleEndian(&stream); proj.p(1) = (float)ReadBinaryLittleEndian(&stream); proj.idPoint = (uint32_t)ReadBinaryLittleEndian(&stream); - projs.push_back(proj); + ASSERT(proj.idPoint > 0); + --proj.idPoint; + projs.emplace_back(proj); } return true; } - bool Write(std::ostream& out) const { + bool WriteTXT(std::ostream& out) const { out << ID+1 << _T(" ") << q.w() << _T(" ") << q.x() << _T(" ") << q.y() << _T(" ") << q.z() << _T(" ") << t(0) << _T(" ") << t(1) << _T(" ") << t(2) << _T(" ") @@ -452,6 +479,37 @@ struct Image { out << std::endl; return !out.fail(); } + + bool WriteBIN(std::ostream& stream) { + if (numRegImages != 0) { + // Write the first entry in the binary file + WriteBinaryLittleEndian(&stream, numRegImages); + numRegImages = 0; + } + + WriteBinaryLittleEndian(&stream, ID+1); + + WriteBinaryLittleEndian(&stream, q.w()); + WriteBinaryLittleEndian(&stream, q.x()); + WriteBinaryLittleEndian(&stream, q.y()); + WriteBinaryLittleEndian(&stream, q.z()); + + WriteBinaryLittleEndian(&stream, t(0)); + WriteBinaryLittleEndian(&stream, t(1)); + WriteBinaryLittleEndian(&stream, t(2)); + + WriteBinaryLittleEndian(&stream, idCamera+1); + + stream.write(name.c_str(), name.size()+1); + + WriteBinaryLittleEndian(&stream, projs.size()); + for (const Proj& proj: projs) { + WriteBinaryLittleEndian(&stream, proj.p(0)); + WriteBinaryLittleEndian(&stream, proj.p(1)); + WriteBinaryLittleEndian(&stream, proj.idPoint+1); + } + return !stream.fail(); + } }; typedef std::vector Images; // structure describing a 3D point @@ -465,18 +523,22 @@ struct Point { Interface::Col3 c; // BGR color float e; // error std::vector tracks; // point track - bool parsedNumPoints3D = false; + uint64_t numPoints3D{0}; // only for binary format Point() {} Point(uint32_t _ID) : ID(_ID) {} bool operator < (const Image& rhs) const { return ID < rhs.ID; } bool Read(std::istream& stream, bool binary) { - if (binary) { + if (binary) return ReadBIN(stream); - } else { - return ReadTXT(stream); - } + return ReadTXT(stream); + } + + bool Write(std::ostream& stream, bool binary) { + if (binary) + return WriteBIN(stream); + return WriteTXT(stream); } // 3D point list with one line of data per point: @@ -495,13 +557,17 @@ struct Point { c.z = CLAMP(r,0,255); if (in.fail()) return false; + ASSERT(ID > 0); + --ID; tracks.clear(); while (true) { Track track; in >> track.idImage >> track.idProj; if (in.fail()) break; - tracks.push_back(track); + ASSERT(track.idImage > 0 && track.idProj > 0); + --track.idImage; --track.idProj; + tracks.emplace_back(track); } return !tracks.empty(); } @@ -509,14 +575,12 @@ struct Point { // See: colmap/src/base/reconstruction.cc // void Reconstruction::ReadPoints3DBinary(const std::string& path) bool ReadBIN(std::istream& stream) { - if (stream.peek() == EOF) return false; - if (!parsedNumPoints3D) { + if (!numPoints3D) { // Read the first entry in the binary file - ReadBinaryLittleEndian(&stream); - parsedNumPoints3D = true; + numPoints3D = ReadBinaryLittleEndian(&stream); } int r,g,b; @@ -531,19 +595,23 @@ struct Point { c.x = CLAMP(b,0,255); c.y = CLAMP(g,0,255); c.z = CLAMP(r,0,255); - + ASSERT(ID > 0); + --ID; + const size_t trackLength = ReadBinaryLittleEndian(&stream); tracks.clear(); for (size_t j = 0; j < trackLength; ++j) { Track track; track.idImage = ReadBinaryLittleEndian(&stream); track.idProj = ReadBinaryLittleEndian(&stream); - tracks.push_back(track); - } + ASSERT(track.idImage > 0 && track.idProj > 0); + --track.idImage; --track.idProj; + tracks.emplace_back(track); + } return !tracks.empty(); } - bool Write(std::ostream& out) const { + bool WriteTXT(std::ostream& out) const { ASSERT(!tracks.empty()); const int r(c.z),g(c.y),b(c.x); out << ID+1 << _T(" ") @@ -558,8 +626,71 @@ struct Point { out << std::endl; return !out.fail(); } + + bool WriteBIN(std::ostream& stream) { + ASSERT(!tracks.empty()); + if (numPoints3D != 0) { + // Write the first entry in the binary file + WriteBinaryLittleEndian(&stream, numPoints3D); + numPoints3D = 0; + } + + WriteBinaryLittleEndian(&stream, ID+1); + WriteBinaryLittleEndian(&stream, p.x); + WriteBinaryLittleEndian(&stream, p.y); + WriteBinaryLittleEndian(&stream, p.z); + WriteBinaryLittleEndian(&stream, c.z); + WriteBinaryLittleEndian(&stream, c.y); + WriteBinaryLittleEndian(&stream, c.x); + WriteBinaryLittleEndian(&stream, e); + + WriteBinaryLittleEndian(&stream, tracks.size()); + for (const Track& track: tracks) { + WriteBinaryLittleEndian(&stream, track.idImage+1); + WriteBinaryLittleEndian(&stream, track.idProj+1); + } + return !stream.fail(); + } }; typedef std::vector Points; +// structure describing an 2D dynamic matrix +template +struct Mat { + size_t width_ = 0; + size_t height_ = 0; + size_t depth_ = 0; + std::vector data_; + + size_t GetNumBytes() const { + return data_.size() * sizeof(T); + } + const T* GetChannelPtr(size_t c) const { + return data_.data()+width_*height_*c; + } + + // See: colmap/src/mvs/mat.h + void Read(const std::string& path) { + std::streampos pos; { + std::fstream text_file(path, std::ios::in | std::ios::binary); + char unused_char; + text_file >> width_ >> unused_char >> height_ >> unused_char >> depth_ >> + unused_char; + pos = text_file.tellg(); + } + data_.resize(width_ * height_ * depth_); + std::fstream binary_file(path, std::ios::in | std::ios::binary); + binary_file.seekg(pos); + ReadBinaryLittleEndian(&binary_file, &data_); + } + void Write(const std::string& path) const { + { + std::fstream text_file(path, std::ios::out); + text_file << width_ << "&" << height_ << "&" << depth_ << "&"; + } + std::fstream binary_file(path, std::ios::out | std::ios::binary | std::ios::app); + WriteBinaryLittleEndian(&binary_file, data_); + } +}; } // namespace COLMAP typedef Eigen::Matrix EMat33d; @@ -586,9 +717,8 @@ bool DetermineInputSource(const String& filenameTXT, const String& filenameBIN, } -bool ImportScene(const String& strFolder, Interface& scene) +bool ImportScene(const String& strFolder, const String& strOutFolder, Interface& scene, PointCloud& pointcloud) { - COLMAP::DefineCameraModels(); // read camera list typedef std::unordered_map CamerasMap; CamerasMap mapCameras; @@ -619,25 +749,22 @@ bool ImportScene(const String& strFolder, Interface& scene) Interface::Platform::Camera camera; camera.name = colmapCamera.model; camera.K = Interface::Mat33d::eye(); + // account for different pixel center conventions as COLMAP uses pixel center at (0.5,0.5) camera.K(0,0) = colmapCamera.params[0]; camera.K(1,1) = colmapCamera.params[1]; - camera.K(0,2) = colmapCamera.params[2]; - camera.K(1,2) = colmapCamera.params[3]; + camera.K(0,2) = colmapCamera.params[2]-REAL(0.5); + camera.K(1,2) = colmapCamera.params[3]-REAL(0.5); camera.R = Interface::Mat33d::eye(); camera.C = Interface::Pos3d(0,0,0); if (OPT::bNormalizeIntrinsics) { // normalize camera intrinsics - const REAL fScale(REAL(1)/Camera::GetNormalizationScale(colmapCamera.width, colmapCamera.height)); - camera.K(0,0) *= fScale; - camera.K(1,1) *= fScale; - camera.K(0,2) *= fScale; - camera.K(1,2) *= fScale; + camera.K = Camera::ScaleK(camera.K, 1.0/Camera::GetNormalizationScale(colmapCamera.width, colmapCamera.height)); } else { camera.width = colmapCamera.width; camera.height = colmapCamera.height; } - platform.cameras.push_back(camera); - scene.platforms.push_back(platform); + platform.cameras.emplace_back(camera); + scene.platforms.emplace_back(platform); } } if (mapCameras.empty()) { @@ -667,21 +794,21 @@ bool ImportScene(const String& strFolder, Interface& scene) EnsureRotationMatrix((Matrix3x3d&)pose.R); Eigen::Map(&pose.C.x) = -(imageColmap.q.inverse() * imageColmap.t); Interface::Image image; - image.name = OPT::strImageFolder+imageColmap.name; + image.name = MAKE_PATH_REL(strOutFolder,OPT::strImageFolder+imageColmap.name); image.platformID = mapCameras.at(imageColmap.idCamera); image.cameraID = 0; image.ID = imageColmap.ID; Interface::Platform& platform = scene.platforms[image.platformID]; image.poseID = (uint32_t)platform.poses.size(); - platform.poses.push_back(pose); - scene.images.push_back(image); + platform.poses.emplace_back(pose); + scene.images.emplace_back(image); } } // read points list const String filenameDensePoints(strFolder+COLMAP_DENSE_POINTS); const String filenameDenseVisPoints(strFolder+COLMAP_DENSE_POINTS_VISIBILITY); - if (!File::access(filenameDensePoints) || !File::access(filenameDenseVisPoints)) { + { // parse sparse point-cloud const String filenamePointsTXT(strFolder+COLMAP_POINTS_TXT); const String filenamePointsBIN(strFolder+COLMAP_POINTS_BIN); @@ -691,8 +818,7 @@ bool ImportScene(const String& strFolder, Interface& scene) if (!DetermineInputSource(filenamePointsTXT, filenamePointsBIN, file, filenamePoints, binary)) { return false; } - LOG_OUT() << "Reading images: " << filenamePoints << std::endl; - + LOG_OUT() << "Reading points: " << filenamePoints << std::endl; COLMAP::Point point; while (file.good() && point.Read(file, binary)) { Interface::Vertex vertex; @@ -708,10 +834,11 @@ bool ImportScene(const String& strFolder, Interface& scene) scene.vertices.emplace_back(std::move(vertex)); scene.verticesColor.emplace_back(Interface::Color{point.c}); } - } else { + } + pointcloud.Release(); + if (File::access(filenameDensePoints) && File::access(filenameDenseVisPoints)) { // parse dense point-cloud LOG_OUT() << "Reading points: " << filenameDensePoints << " and " << filenameDenseVisPoints << std::endl; - PointCloud pointcloud; if (!pointcloud.Load(filenameDensePoints)) { VERBOSE("error: unable to open file '%s'", filenameDensePoints.c_str()); return false; @@ -727,29 +854,154 @@ bool ImportScene(const String& strFolder, Interface& scene) VERBOSE("error: point-cloud and visibility have different size"); return false; } + pointcloud.pointViews.resize(numPoints); for (size_t i=0; isecond]; + CLISTDEF2(String) neighborNames; + Util::strSplit(neighbors, _T(','), neighborNames); + FOREACH(i, neighborNames) { + String& neighborName = neighborNames[i]; + Util::strTrim(neighborName, _T(" ")); + const ImagesMap::const_iterator it_neighbor = std::find_if(mapImages.begin(), mapImages.end(), + [&neighborName](const ImagesMap::value_type& image) { + return image.first.name == neighborName; + }); + if (it_neighbor == mapImages.end()) { + if (i == 0) + break; + continue; + } + imageNeighbors.emplace_back(scene.images[it_neighbor->second].ID); + } + } + } + LOG_OUT() << "Reading depth-maps/normal-maps: " << pathDepthMaps << " and " << pathNormalMaps << std::endl; + Util::ensureFolder(strOutFolder); + const String strType[] = {".geometric.bin", ".photometric.bin"}; + FOREACH(idx, scene.images) { + const Interface::Image& image = scene.images[idx]; + COLMAP::Mat colDepthMap, colNormalMap; + const String filenameImage(Util::getFileNameExt(image.name)); + for (int i=0; i<2; ++i) { + const String filenameDepthMaps(pathDepthMaps+filenameImage+strType[i]); + if (File::isFile(filenameDepthMaps)) { + colDepthMap.Read(filenameDepthMaps); + const String filenameNormalMaps(pathNormalMaps+filenameImage+strType[i]); + if (File::isFile(filenameNormalMaps)) { + colNormalMap.Read(filenameNormalMaps); + } + break; + } + } + if (!colDepthMap.data_.empty()) { + IIndexArr IDs = {image.ID}; + IDs.Join(imagesNeighbors[(IIndex)idx]); + const Interface::Platform& platform = scene.platforms[image.platformID]; + const Interface::Platform::Pose pose(platform.GetPose(image.cameraID, image.poseID)); + const Interface::Mat33d K(platform.GetFullK(image.cameraID, (uint32_t)colDepthMap.width_, (uint32_t)colDepthMap.height_)); + MVS::DepthMap depthMap((int)colDepthMap.height_, (int)colDepthMap.width_); + memcpy(depthMap.getData(), colDepthMap.data_.data(), colDepthMap.GetNumBytes()); + MVS::NormalMap normalMap; + if (!colNormalMap.data_.empty()) { + normalMap.create((int)colNormalMap.height_, (int)colNormalMap.width_); + cv::merge(std::vector{ + cv::Mat((int)colNormalMap.height_, (int)colNormalMap.width_, CV_32F, (void*)colNormalMap.GetChannelPtr(0)), + cv::Mat((int)colNormalMap.height_, (int)colNormalMap.width_, CV_32F, (void*)colNormalMap.GetChannelPtr(1)), + cv::Mat((int)colNormalMap.height_, (int)colNormalMap.width_, CV_32F, (void*)colNormalMap.GetChannelPtr(2)) + }, normalMap); + } + MVS::ConfidenceMap confMap; + MVS::ViewsMap viewsMap; + const auto depthMM(std::minmax_element(colDepthMap.data_.cbegin(), colDepthMap.data_.cend())); + const MVS::Depth dMin(*depthMM.first), dMax(*depthMM.second); + if (!ExportDepthDataRaw(strOutFolder+String::FormatString("depth%04u.dmap", image.ID), MAKE_PATH_FULL(strOutFolder, image.name), IDs, depthMap.size(), K, pose.R, pose.C, dMin, dMax, depthMap, normalMap, confMap, viewsMap)) + return false; } - std::sort(vertex.views.begin(), vertex.views.end(), - [](const Interface::Vertex::View& view0, const Interface::Vertex::View& view1) { return view0.imageID < view1.imageID; }); - scene.vertices.emplace_back(std::move(vertex)); - scene.verticesNormal.emplace_back(Interface::Normal{pointcloud.normals[i]}); - scene.verticesColor.emplace_back(Interface::Color{pointcloud.colors[i]}); } } return true; } -bool ExportScene(const String& strFolder, const Interface& scene) +bool ImportPointCloud(const String& strPointCloudFileName, Interface& scene) +{ + PointCloud pointcloud; + if (!pointcloud.Load(strPointCloudFileName)) { + VERBOSE("error: cannot load point-cloud file"); + return false; + } + if (!pointcloud.IsValid()) { + VERBOSE("error: loaded point-cloud does not have visibility information"); + return false; + } + // replace scene point-cloud with the loaded one + scene.vertices.clear(); + scene.verticesColor.clear(); + scene.verticesNormal.clear(); + scene.vertices.reserve(pointcloud.points.size()); + if (!pointcloud.colors.empty()) + scene.verticesColor.reserve(pointcloud.points.size()); + if (!pointcloud.normals.empty()) + scene.verticesNormal.reserve(pointcloud.points.size()); + FOREACH(i, pointcloud.points) { + Interface::Vertex vertex; + vertex.X = pointcloud.points[i]; + vertex.views.reserve(pointcloud.pointViews[i].size()); + FOREACH(j, pointcloud.pointViews[i]) { + Interface::Vertex::View& view = vertex.views.emplace_back(); + view.imageID = pointcloud.pointViews[i][j]; + view.confidence = (pointcloud.pointWeights.empty() ? 0.f : pointcloud.pointWeights[i][j]); + } + scene.vertices.emplace_back(std::move(vertex)); + if (!pointcloud.colors.empty()) { + const Pixel8U& c = pointcloud.colors[i]; + scene.verticesColor.emplace_back(Interface::Color{Interface::Col3{c.b, c.g, c.r}}); + } + if (!pointcloud.normals.empty()) + scene.verticesNormal.emplace_back(Interface::Normal{pointcloud.normals[i]}); + } + return true; +} + +bool ExportScene(const String& strFolder, const Interface& scene, bool bForceSparsePointCloud = false, bool binary = true) { Util::ensureFolder(strFolder+COLMAP_SPARSE_FOLDER); @@ -757,16 +1009,22 @@ bool ExportScene(const String& strFolder, const Interface& scene) CLISTDEF0IDX(KMatrix,uint32_t) Ks; CLISTDEF0IDX(COLMAP::Camera,uint32_t) cams; { - const String filenameCameras(strFolder+COLMAP_CAMERAS_TXT); + const String filenameCameras(strFolder+(binary?COLMAP_CAMERAS_BIN:COLMAP_CAMERAS_TXT)); LOG_OUT() << "Writing cameras: " << filenameCameras << std::endl; - std::ofstream file(filenameCameras); + std::ofstream file(filenameCameras, binary ? std::ios::trunc|std::ios::binary : std::ios::trunc); if (!file.good()) { VERBOSE("error: unable to open file '%s'", filenameCameras.c_str()); return false; } - file << _T("# Camera list with one line of data per camera:") << std::endl; - file << _T("# CAMERA_ID, MODEL, WIDTH, HEIGHT, PARAMS[]") << std::endl; COLMAP::Camera cam; + if (binary) { + cam.numCameras = 0; + for (const Interface::Platform& platform: scene.platforms) + cam.numCameras += (uint32_t)platform.cameras.size(); + } else { + file << _T("# Camera list with one line of data per camera:") << std::endl; + file << _T("# CAMERA_ID, MODEL, WIDTH, HEIGHT, PARAMS[]") << std::endl; + } cam.model = _T("PINHOLE"); cam.params.resize(4); for (uint32_t ID=0; ID<(uint32_t)scene.platforms.size(); ++ID) { @@ -774,10 +1032,7 @@ bool ExportScene(const String& strFolder, const Interface& scene) ASSERT(platform.cameras.size() == 1); // only one camera per platform supported const Interface::Platform::Camera& camera = platform.cameras[0]; cam.ID = ID; - cam.params[0] = camera.K(0,0); - cam.params[1] = camera.K(1,1); - cam.params[2] = camera.K(0,2); - cam.params[3] = camera.K(1,2); + KMatrix K; if (camera.width == 0 || camera.height == 0) { // find one image using this camera const Interface::Image* pImage(NULL); @@ -792,29 +1047,26 @@ bool ExportScene(const String& strFolder, const Interface& scene) LOG("error: no image using camera %u of platform %u", 0, ID); continue; } - IMAGEPTR ptrImage(Image::ReadImageHeader(MAKE_PATH_SAFE(pImage->name.c_str()))); + IMAGEPTR ptrImage(Image::ReadImageHeader(MAKE_PATH_SAFE(pImage->name))); if (ptrImage == NULL) return false; cam.width = ptrImage->GetWidth(); cam.height = ptrImage->GetHeight(); - // denormalize camera intrinsics - const double fScale(MVS::Camera::GetNormalizationScale(cam.width, cam.height)); - cam.params[0] *= fScale; - cam.params[1] *= fScale; - cam.params[2] *= fScale; - cam.params[3] *= fScale; + // unnormalize camera intrinsics + K = platform.GetFullK(0, cam.width, cam.height); } else { cam.width = camera.width; cam.height = camera.height; + K = camera.K; } - if (!cam.Write(file)) + // account for different pixel center conventions as COLMAP uses pixel center at (0.5,0.5) + cam.params[0] = K(0,0); + cam.params[1] = K(1,1); + cam.params[2] = K(0,2)+REAL(0.5); + cam.params[3] = K(1,2)+REAL(0.5); + if (!cam.Write(file, binary)) return false; - KMatrix& K = Ks.AddEmpty(); - K = KMatrix::IDENTITY; - K(0,0) = cam.params[0]; - K(1,1) = cam.params[1]; - K(0,2) = cam.params[2]; - K(1,2) = cam.params[3]; + Ks.emplace_back(K); cams.emplace_back(cam); } } @@ -828,7 +1080,7 @@ bool ExportScene(const String& strFolder, const Interface& scene) const uint32_t avgPointsPerSmallView(3000), avgPointsPerLargeView(12000); { images.resize(scene.images.size()); - cameras.resize(scene.images.size()); + cameras.resize((unsigned)scene.images.size()); for (uint32_t ID=0; ID<(uint32_t)scene.images.size(); ++ID) { const Interface::Image& image = scene.images[ID]; if (image.poseID == NO_ID) @@ -841,7 +1093,7 @@ bool ExportScene(const String& strFolder, const Interface& scene) img.q = Eigen::Quaterniond(Eigen::Map(pose.R.val)); img.t = -(img.q * Eigen::Map(&pose.C.x)); img.idCamera = image.platformID; - img.name = MAKE_PATH_REL(OPT::strImageFolder, image.name); + img.name = MAKE_PATH_REL(OPT::strImageFolder, MAKE_PATH_FULL(WORKING_FOLDER_FULL, image.name)); Camera& camera = cameras[ID]; camera.K = Ks[image.platformID]; camera.R = pose.R; @@ -856,18 +1108,23 @@ bool ExportScene(const String& strFolder, const Interface& scene) // auto-select dense or sparse mode based on number of points const bool bSparsePointCloud(scene.vertices.size() < (size_t)maxNumPointsSparse); - if (bSparsePointCloud) { + if (bSparsePointCloud || bForceSparsePointCloud) { // write points list { - const String filenamePoints(strFolder+COLMAP_POINTS_TXT); + const String filenamePoints(strFolder+(binary?COLMAP_POINTS_BIN:COLMAP_POINTS_TXT)); LOG_OUT() << "Writing points: " << filenamePoints << std::endl; - std::ofstream file(filenamePoints); + std::ofstream file(filenamePoints, binary ? std::ios::trunc|std::ios::binary : std::ios::trunc); if (!file.good()) { VERBOSE("error: unable to open file '%s'", filenamePoints.c_str()); return false; } - file << _T("# 3D point list with one line of data per point:") << std::endl; - file << _T("# POINT3D_ID, X, Y, Z, R, G, B, ERROR, TRACK[] as (IMAGE_ID, POINT2D_IDX)") << std::endl; + uint64_t numPoints3D = 0; + if (binary) { + numPoints3D = scene.vertices.size(); + } else { + file << _T("# 3D point list with one line of data per point:") << std::endl; + file << _T("# POINT3D_ID, X, Y, Z, R, G, B, ERROR, TRACK[] as (IMAGE_ID, POINT2D_IDX)") << std::endl; + } for (uint32_t ID=0; ID<(uint32_t)scene.vertices.size(); ++ID) { const Interface::Vertex& vertex = scene.vertices[ID]; COLMAP::Point point; @@ -875,19 +1132,23 @@ bool ExportScene(const String& strFolder, const Interface& scene) point.p = vertex.X; for (const Interface::Vertex::View& view: vertex.views) { COLMAP::Image& img = images[view.imageID]; - COLMAP::Point::Track track; - track.idImage = view.imageID; - track.idProj = (uint32_t)img.projs.size(); - point.tracks.push_back(track); + point.tracks.emplace_back(COLMAP::Point::Track{view.imageID, (uint32_t)img.projs.size()}); COLMAP::Image::Proj proj; proj.idPoint = ID; const Point3 X(vertex.X); ProjectVertex_3x4_3_2(cameras[view.imageID].P.val, X.ptr(), proj.p.data()); - img.projs.push_back(proj); + // account for different pixel center conventions as COLMAP uses pixel center at (0.5,0.5) + proj.p[0] += REAL(0.5); + proj.p[1] += REAL(0.5); + img.projs.emplace_back(proj); } point.c = scene.verticesColor.empty() ? Interface::Col3(255,255,255) : scene.verticesColor[ID].c; point.e = 0; - if (!point.Write(file)) + if (numPoints3D != 0) { + point.numPoints3D = numPoints3D; + numPoints3D = 0; + } + if (!point.Write(file, binary)) return false; } } @@ -936,7 +1197,8 @@ bool ExportScene(const String& strFolder, const Interface& scene) Util::ensureFolder(strFolder+COLMAP_STEREO_CONSISTENCYGRAPHS_FOLDER); Util::ensureFolder(strFolder+COLMAP_STEREO_DEPTHMAPS_FOLDER); Util::ensureFolder(strFolder+COLMAP_STEREO_NORMALMAPS_FOLDER); - } else { + } + if (!bSparsePointCloud) { // export dense point-cloud const String filenameDensePoints(strFolder+COLMAP_DENSE_POINTS); const String filenameDenseVisPoints(strFolder+COLMAP_DENSE_POINTS_VISIBILITY); @@ -963,7 +1225,7 @@ bool ExportScene(const String& strFolder, const Interface& scene) file.write(&view.imageID, sizeof(uint32_t)); } } - if (!pointcloud.Save(filenameDensePoints, true)) { + if (!pointcloud.Save(filenameDensePoints, false, true)) { VERBOSE("error: unable to write file '%s'", filenameDensePoints.c_str()); return false; } @@ -971,18 +1233,33 @@ bool ExportScene(const String& strFolder, const Interface& scene) // write images list { - const String filenameImages(strFolder+COLMAP_IMAGES_TXT); + const String filenameImages(strFolder+(binary?COLMAP_IMAGES_BIN:COLMAP_IMAGES_TXT)); LOG_OUT() << "Writing images: " << filenameImages << std::endl; - std::ofstream file(filenameImages); + std::ofstream file(filenameImages, binary ? std::ios::trunc|std::ios::binary : std::ios::trunc); if (!file.good()) { VERBOSE("error: unable to open file '%s'", filenameImages.c_str()); return false; } - file << _T("# Image list with two lines of data per image:") << std::endl; - file << _T("# IMAGE_ID, QW, QX, QY, QZ, TX, TY, TZ, CAMERA_ID, NAME") << std::endl; - file << _T("# POINTS2D[] as (X, Y, POINT3D_ID)") << std::endl; - for (const COLMAP::Image& img: images) { - if ((bSparsePointCloud && img.projs.empty()) || !img.Write(file)) + uint64_t numRegImages = 0; + if (binary) { + for (const COLMAP::Image& img: images) { + if (bSparsePointCloud && img.projs.empty()) + continue; + ++numRegImages; + } + } else { + file << _T("# Image list with two lines of data per image:") << std::endl; + file << _T("# IMAGE_ID, QW, QX, QY, QZ, TX, TY, TZ, CAMERA_ID, NAME") << std::endl; + file << _T("# POINTS2D[] as (X, Y, POINT3D_ID)") << std::endl; + } + for (COLMAP::Image& img: images) { + if (bSparsePointCloud && img.projs.empty()) + continue; + if (numRegImages != 0) { + img.numRegImages = numRegImages; + numRegImages = 0; + } + if (!img.Write(file, binary)) return false; } } @@ -990,6 +1267,53 @@ bool ExportScene(const String& strFolder, const Interface& scene) } +// export camera intrinsics in txt format +bool ExportIntrinsicsTxt(const String& fileName, const Interface& scene) +{ + LOG_OUT() << "Writing intrinsics: " << fileName << std::endl; + uint32_t idxValidK(NO_ID); + for (uint32_t ID=0; ID<(uint32_t)scene.images.size(); ++ID) { + const Interface::Image& image = scene.images[ID]; + if (!image.IsValid()) + continue; + if (idxValidK == NO_ID) { + idxValidK = ID; + continue; + } + if (scene.GetK(idxValidK) != scene.GetK(ID)) { + VERBOSE("error: multiple camera models"); + return false; + } + } + if (idxValidK == NO_ID) + return false; + const Interface::Image& image = scene.images[idxValidK]; + String imagefileName(image.name); + Util::ensureValidPath(imagefileName); + imagefileName = MAKE_PATH_FULL(WORKING_FOLDER_FULL, imagefileName); + IMAGEPTR pImage = Image::ReadImageHeader(imagefileName); + if (!pImage) { + VERBOSE("error: unable to open image file '%s'", imagefileName.c_str()); + return false; + } + const Interface::Mat33d& K = scene.platforms[image.platformID].GetFullK(image.cameraID, pImage->GetWidth(), pImage->GetHeight()); + Eigen::Matrix4d K4(Eigen::Matrix4d::Identity()); + K4.topLeftCorner<3,3>() = Eigen::Map(K.val); + Util::ensureFolder(fileName); + std::ofstream out(fileName); + if (!out.good()) { + VERBOSE("error: unable to open file '%s'", fileName.c_str()); + return false; + } + out << std::setprecision(12); + out << K4(0,0) << _T(" ") << K4(0,1) << _T(" ") << K4(0,2) << _T(" ") << K4(0,3) << _T("\n"); + out << K4(1,0) << _T(" ") << K4(1,1) << _T(" ") << K4(1,2) << _T(" ") << K4(1,3) << _T("\n"); + out << K4(2,0) << _T(" ") << K4(2,1) << _T(" ") << K4(2,2) << _T(" ") << K4(2,3) << _T("\n"); + out << K4(3,0) << _T(" ") << K4(3,1) << _T(" ") << K4(3,2) << _T(" ") << K4(3,3) << _T("\n"); + return !out.fail(); +} + + // export image poses in log format // see: http://redwood-data.org/indoor/fileformat.html // to support: https://www.tanksandtemples.org/tutorial @@ -1003,7 +1327,12 @@ bool ExportImagesLog(const String& fileName, const Interface& scene) return false; } out << std::setprecision(12); - for (uint32_t ID=0; ID<(uint32_t)scene.images.size(); ++ID) { + IIndexArr orderedImages((uint32_t)scene.images.size()); + std::iota(orderedImages.begin(), orderedImages.end(), 0u); + orderedImages.Sort([&scene](IIndex i, IIndex j) { + return scene.images[i].ID < scene.images[j].ID; + }); + for (IIndex ID: orderedImages) { const Interface::Image& image = scene.images[ID]; Eigen::Matrix3d R(Eigen::Matrix3d::Identity()); Eigen::Vector3d t(Eigen::Vector3d::Zero()); @@ -1025,6 +1354,67 @@ bool ExportImagesLog(const String& fileName, const Interface& scene) return !out.fail(); } + +// export poses in Strecha camera format: +// Strecha model is P = K[R^T|-R^T t] +// our model is P = K[R|t], t = -RC +bool ExportImagesCamera(const String& pathName, const Interface& scene) +{ + LOG_OUT() << "Writing poses: " << pathName << std::endl; + Util::ensureFolder(pathName); + for (uint32_t ID=0; ID<(uint32_t)scene.images.size(); ++ID) { + const Interface::Image& image = scene.images[ID]; + String imageFileName(image.name); + Util::ensureValidPath(imageFileName); + const String fileName(pathName+Util::getFileNameExt(imageFileName)+".camera"); + std::ofstream out(fileName); + if (!out.good()) { + VERBOSE("error: unable to open file '%s'", fileName.c_str()); + return false; + } + out << std::setprecision(12); + KMatrix K(KMatrix::IDENTITY); + RMatrix R(RMatrix::IDENTITY); + CMatrix t(CMatrix::ZERO); + unsigned width(0), height(0); + if (image.platformID != NO_ID && image.cameraID != NO_ID) { + const Interface::Platform& platform = scene.platforms[image.platformID]; + const Interface::Platform::Camera& camera = platform.cameras[image.cameraID]; + if (camera.HasResolution()) { + width = camera.width; + height = camera.height; + K = camera.K; + } else { + IMAGEPTR pImage = Image::ReadImageHeader(image.name); + width = pImage->GetWidth(); + height = pImage->GetHeight(); + K = platform.GetFullK(image.cameraID, width, height); + } + if (image.poseID != NO_ID) { + const Interface::Platform::Pose& pose = platform.poses[image.poseID]; + R = pose.R.t(); + t = pose.C; + } + } + out << K(0,0) << _T(" ") << K(0,1) << _T(" ") << K(0,2) << _T("\n"); + out << K(1,0) << _T(" ") << K(1,1) << _T(" ") << K(1,2) << _T("\n"); + out << K(2,0) << _T(" ") << K(2,1) << _T(" ") << K(2,2) << _T("\n"); + out << _T("0 0 0") << _T("\n"); + out << R(0,0) << _T(" ") << R(0,1) << _T(" ") << R(0,2) << _T("\n"); + out << R(1,0) << _T(" ") << R(1,1) << _T(" ") << R(1,2) << _T("\n"); + out << R(2,0) << _T(" ") << R(2,1) << _T(" ") << R(2,2) << _T("\n"); + out << t.x << _T(" ") << t.y << _T(" ") << t.z << _T("\n"); + out << width << _T(" ") << height << _T("\n"); + if (out.fail()) { + VERBOSE("error: unable to write file '%s'", fileName.c_str()); + return false; + } + } + return true; +} + +} // unnamed namespace + int main(int argc, LPCTSTR* argv) { #ifdef _DEBUGINFO @@ -1032,38 +1422,51 @@ int main(int argc, LPCTSTR* argv) _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);// | _CRTDBG_CHECK_ALWAYS_DF); #endif - if (!Initialize(argc, argv)) + Application application; + if (!application.Initialize(argc, argv)) return EXIT_FAILURE; TD_TIMER_START(); - if (OPT::b3Dnovator2COLMAP) { + if (OPT::bFromOpenMVS) { // read MVS input data Interface scene; if (!ARCHIVE::SerializeLoad(scene, MAKE_PATH_SAFE(OPT::strInputFileName))) return EXIT_FAILURE; if (Util::getFileExt(OPT::strOutputFileName) == _T(".log")) { // write poses in log format + ExportIntrinsicsTxt(MAKE_PATH_FULL(WORKING_FOLDER_FULL, String("intrinsics.txt")), scene); ExportImagesLog(MAKE_PATH_SAFE(OPT::strOutputFileName), scene); + } else + if (Util::getFileExt(OPT::strOutputFileName) == _T(".camera")) { + // write poses in Strecha camera format + ExportImagesCamera((OPT::strOutputFileName=Util::getFileFullName(MAKE_PATH_FULL(WORKING_FOLDER_FULL, OPT::strOutputFileName)))+PATH_SEPARATOR, scene); } else { // write COLMAP input data + if (!OPT::strPointCloudFileName.empty() && !ImportPointCloud(MAKE_PATH_SAFE(OPT::strPointCloudFileName), scene)) + return EXIT_FAILURE; Util::ensureFolderSlash(OPT::strOutputFileName); - ExportScene(MAKE_PATH_SAFE(OPT::strOutputFileName), scene); + ExportScene(MAKE_PATH_SAFE(OPT::strOutputFileName), scene, OPT::bForceSparsePointCloud); } VERBOSE("Input data exported: %u images & %u vertices (%s)", scene.images.size(), scene.vertices.size(), TD_TIMER_GET_FMT().c_str()); } else { // read COLMAP input data Interface scene; - if (!ImportScene(MAKE_PATH_SAFE(OPT::strInputFileName), scene)) + const String strOutFolder(Util::getFilePath(MAKE_PATH_FULL(WORKING_FOLDER_FULL, OPT::strOutputFileName))); + PointCloud pointcloud; + if (!ImportScene(MAKE_PATH_SAFE(OPT::strInputFileName), strOutFolder, scene, pointcloud)) return EXIT_FAILURE; // write MVS input data - Util::ensureFolder(Util::getFullPath(MAKE_PATH_FULL(WORKING_FOLDER_FULL, OPT::strOutputFileName))); - if (!ARCHIVE::SerializeSave(scene, MAKE_PATH_SAFE(OPT::strOutputFileName), (uint32_t)OPT::bNormalizeIntrinsics?0:1)) + Util::ensureFolder(strOutFolder); + if (!ARCHIVE::SerializeSave(scene, MAKE_PATH_SAFE(OPT::strOutputFileName))) + return EXIT_FAILURE; + if (!pointcloud.IsEmpty() && !pointcloud.Save(MAKE_PATH_SAFE(Util::getFileFullName(OPT::strOutputFileName)) + _T(".ply"), true)) return EXIT_FAILURE; - VERBOSE("Exported data: %u images & %u vertices (%s)", scene.images.size(), scene.vertices.size(), TD_TIMER_GET_FMT().c_str()); + VERBOSE("Exported data: %u images, %u points%s (%s)", + scene.images.size(), scene.vertices.size(), pointcloud.IsEmpty()?"":String::FormatString(", %d dense points", pointcloud.GetSize()).c_str(), + TD_TIMER_GET_FMT().c_str()); } - Finalize(); return EXIT_SUCCESS; } /*----------------------------------------------------------------*/ diff --git a/apps/InterfacePhotoScan/CMakeLists.txt b/apps/InterfaceMVSNet/CMakeLists.txt similarity index 57% rename from apps/InterfacePhotoScan/CMakeLists.txt rename to apps/InterfaceMVSNet/CMakeLists.txt index e030ce189..e38a82ced 100644 --- a/apps/InterfacePhotoScan/CMakeLists.txt +++ b/apps/InterfaceMVSNet/CMakeLists.txt @@ -5,9 +5,9 @@ else() endif() FILE(GLOB LIBRARY_FILES_H "*.h" "*.inl") -cxx_executable_with_flags_no_pch(InterfacePhotoScan "Apps" "${cxx_default}" "MVS;${OpenMVS_EXTRA_LIBS}" ${LIBRARY_FILES_C} ${LIBRARY_FILES_H}) +cxx_executable_with_flags(InterfaceMVSNet "Apps" "${cxx_default}" "MVS;${OpenMVS_EXTRA_LIBS}" ${LIBRARY_FILES_C} ${LIBRARY_FILES_H}) # Install -INSTALL(TARGETS InterfacePhotoScan +INSTALL(TARGETS InterfaceMVSNet EXPORT OpenMVSTargets RUNTIME DESTINATION "${INSTALL_BIN_DIR}" COMPONENT bin) diff --git a/apps/InterfaceMVSNet/InterfaceMVSNet.cpp b/apps/InterfaceMVSNet/InterfaceMVSNet.cpp new file mode 100644 index 000000000..5db3f8528 --- /dev/null +++ b/apps/InterfaceMVSNet/InterfaceMVSNet.cpp @@ -0,0 +1,706 @@ +/* + * InterfaceMVSNet.cpp + * + * Copyright (c) 2014-2021 SEACAVE + * + * Author(s): + * + * cDc + * + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * + * Additional Terms: + * + * You are required to preserve legal notices and author attributions in + * that material or in the Appropriate Legal Notices displayed by works + * containing it. + */ + +#include "../../libs/MVS/Common.h" +#include "../../libs/MVS/Scene.h" +#define JSON_NOEXCEPTION +#include "../../libs/IO/json.hpp" +#include + +using namespace MVS; + + +// D E F I N E S /////////////////////////////////////////////////// + +// uncomment to enable multi-threading based on OpenMP +#ifdef _USE_OPENMP +#define MVSNET_USE_OPENMP +#endif + +#define APPNAME _T("InterfaceMVSNet") +#define MVS_EXT _T(".mvs") +#define MVSNET_IMAGES_FOLDER _T("images") +#define MVSNET_CAMERAS_FOLDER _T("cams") +#define MVSNET_IMAGES_EXT _T(".jpg") +#define MVSNET_CAMERAS_NAME _T("_cam.txt") +#define RTMV_CAMERAS_EXT _T(".json") +#define NERFSTUDIO_TRANSFORMS _T("transforms.json") + + +// S T R U C T S /////////////////////////////////////////////////// + +namespace { + +namespace OPT { + String strInputFileName; + String strOutputFileName; + unsigned nArchiveType; + int nProcessPriority; + unsigned nMaxThreads; + String strConfigFileName; + boost::program_options::variables_map vm; +} // namespace OPT + +class Application { +public: + Application() {} + ~Application() { Finalize(); } + + bool Initialize(size_t argc, LPCTSTR* argv); + void Finalize(); +}; // Application + +// initialize and parse the command line parameters +bool Application::Initialize(size_t argc, LPCTSTR* argv) +{ + // initialize log and console + OPEN_LOG(); + OPEN_LOGCONSOLE(); + + // group of options allowed only on command line + boost::program_options::options_description generic("Generic options"); + generic.add_options() + ("help,h", "produce this help message") + ("working-folder,w", boost::program_options::value(&WORKING_FOLDER), "working directory (default current directory)") + ("config-file,c", boost::program_options::value(&OPT::strConfigFileName)->default_value(APPNAME _T(".cfg")), "file name containing program options") + ("archive-type", boost::program_options::value(&OPT::nArchiveType)->default_value(ARCHIVE_MVS), "project archive type: -1-interface, 0-text, 1-binary, 2-compressed binary") + ("process-priority", boost::program_options::value(&OPT::nProcessPriority)->default_value(-1), "process priority (below normal by default)") + ("max-threads", boost::program_options::value(&OPT::nMaxThreads)->default_value(0), "maximum number of threads (0 for using all available cores)") + #if TD_VERBOSE != TD_VERBOSE_OFF + ("verbosity,v", boost::program_options::value(&g_nVerbosityLevel)->default_value( + #if TD_VERBOSE == TD_VERBOSE_DEBUG + 3 + #else + 2 + #endif + ), "verbosity level") + #endif + ; + + // group of options allowed both on command line and in config file + boost::program_options::options_description config("Main options"); + config.add_options() + ("input-file,i", boost::program_options::value(&OPT::strInputFileName), "input filename containing camera poses and image list") + ("output-file,o", boost::program_options::value(&OPT::strOutputFileName), "output filename for storing the mesh") + ; + + boost::program_options::options_description cmdline_options; + cmdline_options.add(generic).add(config); + + boost::program_options::options_description config_file_options; + config_file_options.add(config); + + boost::program_options::positional_options_description p; + p.add("input-file", -1); + + try { + // parse command line options + boost::program_options::store(boost::program_options::command_line_parser((int)argc, argv).options(cmdline_options).positional(p).run(), OPT::vm); + boost::program_options::notify(OPT::vm); + INIT_WORKING_FOLDER; + // parse configuration file + std::ifstream ifs(MAKE_PATH_SAFE(OPT::strConfigFileName)); + if (ifs) { + boost::program_options::store(parse_config_file(ifs, config_file_options), OPT::vm); + boost::program_options::notify(OPT::vm); + } + } + catch (const std::exception& e) { + LOG(e.what()); + return false; + } + + // initialize the log file + OPEN_LOGFILE(MAKE_PATH(APPNAME _T("-") + Util::getUniqueName(0) + _T(".log")).c_str()); + + // print application details: version and command line + Util::LogBuild(); + LOG(_T("Command line: ") APPNAME _T("%s"), Util::CommandLineToString(argc, argv).c_str()); + + // validate input + Util::ensureValidPath(OPT::strInputFileName); + const String strInputFileNameExt(Util::getFileExt(OPT::strInputFileName).ToLower()); + const bool bInvalidCommand(OPT::strInputFileName.empty()); + if (OPT::vm.count("help") || bInvalidCommand) { + boost::program_options::options_description visible("Available options"); + visible.add(generic).add(config); + GET_LOG() << visible; + } + if (bInvalidCommand) + return false; + + // initialize optional options + Util::ensureValidPath(OPT::strOutputFileName); + if (OPT::strOutputFileName.empty()) + OPT::strOutputFileName = Util::getFileName(OPT::strInputFileName) + "scene" MVS_EXT; + + MVS::Initialize(APPNAME, OPT::nMaxThreads, OPT::nProcessPriority); + return true; +} + +// finalize application instance +void Application::Finalize() +{ + MVS::Finalize(); + + CLOSE_LOGFILE(); + CLOSE_LOGCONSOLE(); + CLOSE_LOG(); +} + +void ImageListParse(const LPSTR* argv, Point3& C) +{ + // read position vector + C.x = String::FromString(argv[0]); + C.y = String::FromString(argv[1]); + C.z = String::FromString(argv[2]); +} + +void ImageListParse(const LPSTR* argv, Matrix3x3& R) +{ + // read rotation matrix + R(0, 0) = String::FromString(argv[0]); + R(0, 1) = String::FromString(argv[1]); + R(0, 2) = String::FromString(argv[2]); + R(1, 0) = String::FromString(argv[3]); + R(1, 1) = String::FromString(argv[4]); + R(1, 2) = String::FromString(argv[5]); + R(2, 0) = String::FromString(argv[6]); + R(2, 1) = String::FromString(argv[7]); + R(2, 2) = String::FromString(argv[8]); +} + +void ImageListParse(const LPSTR* argv, Matrix3x4& P) +{ + // read projection matrix + P(0, 0) = String::FromString(argv[0]); + P(0, 1) = String::FromString(argv[1]); + P(0, 2) = String::FromString(argv[2]); + P(0, 3) = String::FromString(argv[3]); + P(1, 0) = String::FromString(argv[4]); + P(1, 1) = String::FromString(argv[5]); + P(1, 2) = String::FromString(argv[6]); + P(1, 3) = String::FromString(argv[7]); + P(2, 0) = String::FromString(argv[8]); + P(2, 1) = String::FromString(argv[9]); + P(2, 2) = String::FromString(argv[10]); + P(2, 3) = String::FromString(argv[11]); +} + +// convert a range-map to depth-map +void RangeToDepthMap(const Image32F& rangeMap, const Camera& camera, DepthMap& depthMap) +{ + depthMap.create(rangeMap.size()); + for (int y = 0; y < depthMap.rows; ++y) { + const float* const rangeRow = rangeMap.ptr(y); + float* const depthRow = depthMap.ptr(y); + for (int x = 0; x < depthMap.cols; ++x) { + const float range = rangeRow[x]; + depthRow[x] = (Depth)(range <= 0 ? 0 : normalized(camera.TransformPointI2C(Point2(x,y))).z*range); + } + } +} + +// parse scene stored in MVSNet format composed of undistorted images and camera poses +// for example see GigaVision benchmark (http://gigamvs.net): +// |--sceneX +// |--images +// |--xxx.jpg +// |--xxx.jpg +// .... +// |--cams +// |--xxx_cam.txt +// |--xxx_cam.txt +// .... +// |--render_cams +// |--xxx_cam.txt +// |--xxx_cam.txt +// .... +// +// where the camera parameter of one image stored in a cam.txt file contains the camera +// extrinsic E = [R|t], intrinsic K and the depth range: +// extrinsic +// E00 E01 E02 E03 +// E10 E11 E12 E13 +// E20 E21 E22 E23 +// E30 E31 E32 E33 +// +// intrinsic +// K00 K01 K02 +// K10 K11 K12 +// K20 K21 K22 +// +// DEPTH_MIN DEPTH_INTERVAL (DEPTH_NUM DEPTH_MAX) +bool ParseSceneMVSNet(Scene& scene, const String& strPath) +{ + #if defined(_SUPPORT_CPP17) && (!defined(__GNUC__) || (__GNUC__ > 7)) + IIndex prevPlatformID = NO_ID; + for (const std::filesystem::directory_entry& entry : std::filesystem::directory_iterator((strPath + MVSNET_IMAGES_FOLDER).c_str())) { + if (entry.path().extension() != MVSNET_IMAGES_EXT) + continue; + // parse camera + const std::string strCamFileName(strPath + MVSNET_CAMERAS_FOLDER PATH_SEPARATOR_STR + entry.path().stem().string().c_str() + MVSNET_CAMERAS_NAME); + std::ifstream fcam(strCamFileName); + if (!fcam) + continue; + String line; + do { + std::getline(fcam, line); + } while (line != "extrinsic" && fcam.good()); + String strP; + for (int i = 0; i < 3; ++i) { + std::getline(fcam, line); + strP += ' ' + line; + } + if (!fcam.good()) + continue; + size_t argc; + CAutoPtrArr argv; + argv = Util::CommandLineToArgvA(strP, argc); + if (argc != 12) + continue; + Matrix3x4 P; + ImageListParse(argv, P); + do { + std::getline(fcam, line); + } while (line != "intrinsic" && fcam.good()); + strP.clear(); + for (int i = 0; i < 3; ++i) { + std::getline(fcam, line); + strP += ' ' + line; + } + if (!fcam.good()) + continue; + argv = Util::CommandLineToArgvA(strP, argc); + if (argc != 9) + continue; + Matrix3x3 K; + ImageListParse(argv, K); + // setup camera + IIndex platformID; + if (prevPlatformID == NO_ID || !K.IsEqual(scene.platforms[prevPlatformID].cameras[0].K, 1e-3)) { + prevPlatformID = platformID = scene.platforms.size(); + Platform& platform = scene.platforms.emplace_back(); + Platform::Camera& camera = platform.cameras.emplace_back(); + camera.K = K; + camera.R = RMatrix::IDENTITY; + camera.C = CMatrix::ZERO; + } else { + platformID = prevPlatformID; + } + Platform& platform = scene.platforms[platformID]; + // setup image + const IIndex ID = scene.images.size(); + Image& imageData = scene.images.emplace_back(); + imageData.platformID = platformID; + imageData.cameraID = 0; // only one camera per platform supported by this format + imageData.poseID = NO_ID; + imageData.ID = ID; + imageData.name = entry.path().string(); + Util::ensureUnifySlash(imageData.name); + imageData.name = MAKE_PATH_FULL(strPath, imageData.name); + // set image resolution + IMAGEPTR pimage = Image::ReadImageHeader(imageData.name); + imageData.width = pimage->GetWidth(); + imageData.height = pimage->GetHeight(); + imageData.scale = 1; + // set camera pose + imageData.poseID = platform.poses.size(); + Platform::Pose& pose = platform.poses.emplace_back(); + DecomposeProjectionMatrix(P, pose.R, pose.C); + imageData.camera = platform.GetCamera(imageData.cameraID, imageData.poseID); + } + if (scene.images.size() < 2) + return false; + scene.nCalibratedImages = (unsigned)scene.images.size(); + return true; + #else + VERBOSE("error: C++17 is required to parse MVSNet format"); + return false; + #endif // _SUPPORT_CPP17 +} + +// RTMV scene format: http://www.cs.umd.edu/~mmeshry/projects/rtmv +// |--sceneX +// |--images +// |--xxx.jpg +// |--xxx.jpg +// .... +// |--outputs (optional) +// |--depthxxxx.exr +// |--normalxxxx.exr +// .... +// |--transforms.json +bool ParseSceneNerfstudio(Scene& scene, const String& strPath) +{ + const nlohmann::json data = nlohmann::json::parse(std::ifstream(strPath + NERFSTUDIO_TRANSFORMS)); + if (data.empty()) + return false; + // parse camera + const cv::Size resolution(data["w"].get(), data["h"].get()); + const IIndex platformID = scene.platforms.size(); + Platform& platform = scene.platforms.emplace_back(); + Platform::Camera& camera = platform.cameras.emplace_back(); + camera.K = KMatrix::IDENTITY; + camera.R = RMatrix::IDENTITY; + camera.C = CMatrix::ZERO; + camera.K(0,0) = data["fl_x"].get(); + camera.K(1,1) = data["fl_y"].get(); + camera.K(0,2) = data["cx"].get(); + camera.K(1,2) = data["cy"].get(); + const String cameraModel = data["camera_model"].get(); + if (cameraModel == "SIMPLE_PINHOLE") { + } else + // check ZERO radial distortion for all "PERSPECTIVE" type cameras + if (cameraModel == "PINHOLE" || cameraModel == "SIMPLE_RADIAL" || cameraModel == "RADIAL" || cameraModel == "OPENCV") { + const REAL k1 = data["k1"].get(); + const REAL k2 = data["k2"].get(); + const REAL p1 = data["p1"].get(); + const REAL p2 = data["p2"].get(); + if (k1 != 0 || k2 != 0 || p1 != 0 || p2 != 0) { + VERBOSE("error: radial distortion not supported"); + return false; + } + } else { + VERBOSE("error: camera model not supported"); + return false; + } + // parse images + const nlohmann::json& frames = data["frames"]; + for (const nlohmann::json& frame: frames) { + // set image + // frames expected to be ordered in JSON + const IIndex imageID = scene.images.size(); + const String strFileName(strPath + frame["file_path"].get().c_str()); + Image& imageData = scene.images.emplace_back(); + imageData.platformID = platformID; + imageData.cameraID = 0; // only one camera per platform supported by this format + imageData.poseID = NO_ID; + imageData.ID = imageID; + imageData.name = strFileName; + ASSERT(Util::isFullPath(imageData.name)); + // set image resolution + imageData.width = resolution.width; + imageData.height = resolution.height; + imageData.scale = 1; + // load camera pose + imageData.poseID = platform.poses.size(); + Platform::Pose& pose = platform.poses.emplace_back(); + const auto Ps = frame["transform_matrix"].get>>(); + Eigen::Matrix4d P{ + {Ps[0][0], Ps[0][1], Ps[0][2], Ps[0][3]}, + {Ps[1][0], Ps[1][1], Ps[1][2], Ps[1][3]}, + {Ps[2][0], Ps[2][1], Ps[2][2], Ps[2][3]}, + {Ps[3][0], Ps[3][1], Ps[3][2], Ps[3][3]} + }; + // revert nerfstudio conversion: + // convert from COLMAP's camera coordinate system (OpenCV) to ours (OpenGL) + // c2w[0:3, 1:3] *= -1 + // c2w = c2w[np.array([1, 0, 2, 3]), :] + // c2w[2, :] *= -1 + P.row(2) *= -1; + P.row(0).swap(P.row(1)); + P.col(2) *= -1; + P.col(1) *= -1; + // set camera pose + pose.R = P.topLeftCorner<3, 3>().transpose().eval(); + pose.R.EnforceOrthogonality(); + pose.C = P.topRightCorner<3, 1>().eval(); + imageData.camera = platform.GetCamera(imageData.cameraID, imageData.poseID); + // try reading depth-map and normal-map + DepthMap depthMap; { + const String depthPath(strPath + String::FormatString("outputs/depth%04u.exr", imageID)); + const Image32F rangeMap = cv::imread(depthPath, cv::IMREAD_UNCHANGED); + if (rangeMap.empty()) { + VERBOSE("Unable to load depthmap %s.", depthPath.c_str()); + continue; + } + RangeToDepthMap(rangeMap, imageData.camera, depthMap); + } + NormalMap normalMap; { + const String normalPath(strPath + String::FormatString("outputs/normal%04u.exr", imageID)); + normalMap = cv::imread(normalPath, cv::IMREAD_UNCHANGED); + if (normalMap.empty()) { + VERBOSE("Unable to load normalMap %s.", normalPath.c_str()); + continue; + } + } + const ConfidenceMap confMap; + const ViewsMap viewsMap; + const IIndexArr IDs = {imageID}; + double dMin, dMax; + cv::minMaxIdx(depthMap, &dMin, &dMax, NULL, NULL, depthMap > 0); + const String dmapPath(strPath + String::FormatString("depth%04u.dmap", imageID)); + if (!ExportDepthDataRaw(dmapPath, + imageData.name, IDs, resolution, + camera.K, pose.R, pose.C, + (float)dMin, (float)dMax, + depthMap, normalMap, confMap, viewsMap)) + { + VERBOSE("Unable to save dmap: %s", dmapPath.c_str()); + continue; + } + } + if (scene.images.size() < 2) + return false; + scene.nCalibratedImages = (unsigned)scene.images.size(); + return true; +} + +// RTMV scene format: http://www.cs.umd.edu/~mmeshry/projects/rtmv +// |--sceneX +// |--xxx.exr +// |--xxx.seg.exr +// |--xxx.depth.exr +// |--xxx.json +// .... +bool ParseSceneRTMV(Scene& scene, const String& strPath) +{ + const String strImagePath(strPath + "images/"); + Util::ensureFolder(strImagePath); + std::vector strImageNames; + #if defined(_SUPPORT_CPP17) && (!defined(__GNUC__) || (__GNUC__ > 7)) + for (const std::filesystem::directory_entry& entry : std::filesystem::directory_iterator(strPath.c_str())) { + if (entry.path().extension() != RTMV_CAMERAS_EXT) + continue; + strImageNames.emplace_back(entry.path().stem().string()); + } + #else + VERBOSE("error: C++17 is required to parse RTMV format"); + return false; + #endif // _SUPPORT_CPP17 + IIndex prevPlatformID = NO_ID; + scene.images.resize((IIndex)strImageNames.size()); + scene.platforms.reserve((IIndex)strImageNames.size()); + #ifdef MVSNET_USE_OPENMP + #pragma omp parallel for schedule(dynamic) + for (int_t i=0; i<(int_t)strImageNames.size(); ++i) { + #else + FOREACH(i, strImageNames) { + #endif + const IIndex imageID((IIndex)i); + const String& strImageName(strImageNames[imageID]); + // parse camera + const String strFileName(strPath + strImageName); + const nlohmann::json dataCamera = nlohmann::json::parse(std::ifstream(strFileName+RTMV_CAMERAS_EXT)); + if (dataCamera.empty()) + continue; + const nlohmann::json& data = dataCamera["camera_data"]; + const cv::Size resolution(data["width"].get(), data["height"].get()); + // set platform + Matrix3x3 K = Matrix3x3::IDENTITY; + K(0,0) = data["intrinsics"]["fx"].get(); + K(1,1) = data["intrinsics"]["fy"].get(); + K(0,2) = data["intrinsics"]["cx"].get(); + K(1,2) = data["intrinsics"]["cy"].get(); + IIndex platformID; + if (prevPlatformID == NO_ID || !K.IsEqual(scene.platforms[prevPlatformID].cameras[0].K, 1e-3)) { + #ifdef MVSNET_USE_OPENMP + #pragma omp critical + #endif + { + prevPlatformID = platformID = scene.platforms.size(); + Platform& platform = scene.platforms.emplace_back(); + Platform::Camera& camera = platform.cameras.emplace_back(); + platform.poses.reserve((IIndex)strImageNames.size()); + camera.K = K; + camera.R = RMatrix::IDENTITY; + camera.C = CMatrix::ZERO; + } + } else { + platformID = prevPlatformID; + } + Platform& platform = scene.platforms[platformID]; + // set image + Image& imageData = scene.images[imageID]; + imageData.platformID = platformID; + imageData.cameraID = 0; // only one camera per platform supported by this format + imageData.poseID = NO_ID; + imageData.ID = imageID; + imageData.name = strImagePath+strImageName+".jpg"; + ASSERT(Util::isFullPath(imageData.name)); + { + cv::Mat image = cv::imread(strFileName+".exr", cv::IMREAD_UNCHANGED); + ASSERT(image.type() == CV_32FC4); + std::vector channels; + cv::split(image, channels); + cv::merge(std::vector{channels[0], channels[1], channels[2]}, image); + image.convertTo(imageData.image, CV_8UC3, 255); + } + ASSERT(resolution == imageData.image.size()); + if (imageData.image.empty()) { + VERBOSE("Unable to load image %s.", (strFileName+".exr").c_str()); + continue; + } + cv::imwrite(imageData.name, imageData.image); + imageData.ReleaseImage(); + // set image resolution + imageData.width = resolution.width; + imageData.height = resolution.height; + imageData.scale = 1; + // load camera pose + #ifdef MVSNET_USE_OPENMP + #pragma omp critical + #endif + { + imageData.poseID = platform.poses.size(); + platform.poses.emplace_back(); + } + Platform::Pose& pose = platform.poses[imageData.poseID]; + const auto Ps = data["cam2world"].get>>(); + Eigen::Matrix4d P{ + {Ps[0][0], Ps[1][0], Ps[2][0], Ps[3][0]}, + {Ps[0][1], Ps[1][1], Ps[2][1], Ps[3][1]}, + {Ps[0][2], Ps[1][2], Ps[2][2], Ps[3][2]}, + {Ps[0][3], Ps[1][3], Ps[2][3], Ps[3][3]} + }; + // apply the same transforms as nerfstudio converter + P.row(2) *= -1; + P.row(0).swap(P.row(1)); + P.col(2) *= -1; + P.col(1) *= -1; + // set camera pose + pose.R = P.topLeftCorner<3, 3>().transpose().eval(); + pose.R.EnforceOrthogonality(); + pose.C = P.topRightCorner<3, 1>().eval(); + imageData.camera = platform.GetCamera(imageData.cameraID, imageData.poseID); + // try reading the segmentation mask + { + cv::Mat imgMask = cv::imread(strFileName+".seg.exr", cv::IMREAD_UNCHANGED); + if (imgMask.empty()) { + VERBOSE("Unable to load segmentation mask %s.", (strFileName+".seg.exr").c_str()); + continue; + } + ASSERT(imgMask.type() == CV_32FC4); + ASSERT(resolution == imgMask.size()); + std::vector channels; + cv::split(imgMask, channels); + channels[0].convertTo(imgMask, CV_16U); + imageData.maskName = strImagePath+strImageName+".mask.png"; + cv::imwrite(imageData.maskName, imgMask); + } + // try reading the depth-map + DepthMap depthMap; { + const cv::Mat imgDepthMap = cv::imread(strFileName+".depth.exr", cv::IMREAD_UNCHANGED); + if (imgDepthMap.empty()) { + VERBOSE("Unable to load depthmap %s.", (strFileName+".depth.exr").c_str()); + continue; + } + ASSERT(imgDepthMap.type() == CV_32FC4); + ASSERT(resolution == imgDepthMap.size()); + std::vector channels; + cv::split(imgDepthMap, channels); + RangeToDepthMap(channels[0], imageData.camera, depthMap); + } + const NormalMap normalMap; + const ConfidenceMap confMap; + const ViewsMap viewsMap; + const IIndexArr IDs = {imageID}; + double dMin, dMax; + cv::minMaxIdx(depthMap, &dMin, &dMax, NULL, NULL, depthMap > 0); + const String dmapPath(strPath + String::FormatString("depth%04u.dmap", imageID)); + if (!ExportDepthDataRaw(dmapPath, + imageData.name, IDs, resolution, + K, pose.R, pose.C, + (float)dMin, (float)dMax, + depthMap, normalMap, confMap, viewsMap)) + { + VERBOSE("Unable to save dmap: %s", dmapPath.c_str()); + continue; + } + } + if (scene.images.size() < 2) + return false; + scene.nCalibratedImages = (unsigned)scene.images.size(); + return true; +} + +bool ParseScene(Scene& scene) +{ + #if defined(_SUPPORT_CPP17) && (!defined(__GNUC__) || (__GNUC__ > 7)) + String strPath(MAKE_PATH_FULL(WORKING_FOLDER_FULL, OPT::strInputFileName)); + Util::ensureValidFolderPath(strPath); + const std::filesystem::path path(static_cast(strPath)); + enum Type { + MVSNet = 0, + NERFSTUDIO, + RTMV, + } sceneType = MVSNet; + for (const std::filesystem::directory_entry& entry : std::filesystem::directory_iterator(path)) { + if (entry.path().extension() == RTMV_CAMERAS_EXT) { + if (entry.path().filename() == NERFSTUDIO_TRANSFORMS) { + sceneType = NERFSTUDIO; + break; + } + sceneType = RTMV; + } + } + switch (sceneType) { + case NERFSTUDIO: return ParseSceneNerfstudio(scene, strPath); + case RTMV: return ParseSceneRTMV(scene, strPath); + default: return ParseSceneMVSNet(scene, strPath); + } + #else + VERBOSE("error: C++17 is required to parse MVSNet format"); + return false; + #endif // _SUPPORT_CPP17 +} + +} // unnamed namespace + +int main(int argc, LPCTSTR* argv) +{ + #ifdef _DEBUGINFO + // set _crtBreakAlloc index to stop in at allocation + _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);// | _CRTDBG_CHECK_ALWAYS_DF); + #endif + + Application application; + if (!application.Initialize(argc, argv)) + return EXIT_FAILURE; + + TD_TIMER_START(); + + Scene scene(OPT::nMaxThreads); + + // convert data from MVSNet format to OpenMVS + if (!ParseScene(scene)) + return EXIT_FAILURE; + + // write OpenMVS input data + scene.Save(MAKE_PATH_SAFE(OPT::strOutputFileName), (ARCHIVE_TYPE)OPT::nArchiveType); + + VERBOSE("Imported data: %u platforms, %u images, %u vertices (%s)", + scene.platforms.size(), scene.images.size(), scene.pointcloud.GetSize(), + TD_TIMER_GET_FMT().c_str()); + return EXIT_SUCCESS; +} +/*----------------------------------------------------------------*/ diff --git a/apps/InterfaceMetashape/CMakeLists.txt b/apps/InterfaceMetashape/CMakeLists.txt new file mode 100644 index 000000000..100e829f5 --- /dev/null +++ b/apps/InterfaceMetashape/CMakeLists.txt @@ -0,0 +1,13 @@ +if(MSVC) + FILE(GLOB LIBRARY_FILES_C "*.cpp" "*.rc") +else() + FILE(GLOB LIBRARY_FILES_C "*.cpp") +endif() +FILE(GLOB LIBRARY_FILES_H "*.h" "*.inl") + +cxx_executable_with_flags(InterfaceMetashape "Apps" "${cxx_default}" "MVS;${OpenMVS_EXTRA_LIBS}" ${LIBRARY_FILES_C} ${LIBRARY_FILES_H}) + +# Install +INSTALL(TARGETS InterfaceMetashape + EXPORT OpenMVSTargets + RUNTIME DESTINATION "${INSTALL_BIN_DIR}" COMPONENT bin) diff --git a/apps/InterfaceMetashape/InterfaceMetashape.cpp b/apps/InterfaceMetashape/InterfaceMetashape.cpp new file mode 100644 index 000000000..03a80ce87 --- /dev/null +++ b/apps/InterfaceMetashape/InterfaceMetashape.cpp @@ -0,0 +1,887 @@ +/* + * InterfaceMetashape.cpp + * + * Copyright (c) 2014-2021 SEACAVE + * + * Author(s): + * + * cDc + * + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * + * Additional Terms: + * + * You are required to preserve legal notices and author attributions in + * that material or in the Appropriate Legal Notices displayed by works + * containing it. + */ + +#include "../../libs/MVS/Common.h" +#include "../../libs/MVS/Scene.h" +#include "../../libs/IO/TinyXML2.h" +#include + +using namespace MVS; + + +// D E F I N E S /////////////////////////////////////////////////// + +#define APPNAME _T("InterfaceMetashape") // previously PhotoScan +#define MVS_EXT _T(".mvs") +#define XML_EXT _T(".xml") +#define PLY_EXT _T(".ply") + + +// S T R U C T S /////////////////////////////////////////////////// + +namespace { + +namespace OPT { +String strInputFileName; +String strPointsFileName; +String strOutputFileName; +String strOutputImageFolder; +unsigned nArchiveType; +int nProcessPriority; +unsigned nMaxThreads; +String strConfigFileName; +boost::program_options::variables_map vm; +} // namespace OPT + +class Application { +public: + Application() {} + ~Application() { Finalize(); } + + bool Initialize(size_t argc, LPCTSTR* argv); + void Finalize(); +}; // Application + +// initialize and parse the command line parameters +bool Application::Initialize(size_t argc, LPCTSTR* argv) +{ + // initialize log and console + OPEN_LOG(); + OPEN_LOGCONSOLE(); + + // group of options allowed only on command line + boost::program_options::options_description generic("Generic options"); + generic.add_options() + ("help,h", "imports SfM scene stored either in Metashape Agisoft/BlocksExchange or ContextCapture BlocksExchange XML format") + ("working-folder,w", boost::program_options::value(&WORKING_FOLDER), "working directory (default current directory)") + ("config-file,c", boost::program_options::value(&OPT::strConfigFileName)->default_value(APPNAME _T(".cfg")), "file name containing program options") + ("archive-type", boost::program_options::value(&OPT::nArchiveType)->default_value(ARCHIVE_MVS), "project archive type: -1-interface, 0-text, 1-binary, 2-compressed binary") + ("process-priority", boost::program_options::value(&OPT::nProcessPriority)->default_value(-1), "process priority (below normal by default)") + ("max-threads", boost::program_options::value(&OPT::nMaxThreads)->default_value(0), "maximum number of threads (0 for using all available cores)") + #if TD_VERBOSE != TD_VERBOSE_OFF + ("verbosity,v", boost::program_options::value(&g_nVerbosityLevel)->default_value( + #if TD_VERBOSE == TD_VERBOSE_DEBUG + 3 + #else + 2 + #endif + ), "verbosity level") + #endif + ; + + // group of options allowed both on command line and in config file + boost::program_options::options_description config("Main options"); + config.add_options() + ("input-file,i", boost::program_options::value(&OPT::strInputFileName), "input filename containing camera poses and image list") + ("points-file,p", boost::program_options::value(&OPT::strPointsFileName), "input filename containing the 3D points") + ("output-file,o", boost::program_options::value(&OPT::strOutputFileName), "output filename for storing the scene") + ("output-image-folder", boost::program_options::value(&OPT::strOutputImageFolder)->default_value("undistorted_images"), "output folder to store undistorted images") + ; + + boost::program_options::options_description cmdline_options; + cmdline_options.add(generic).add(config); + + boost::program_options::options_description config_file_options; + config_file_options.add(config); + + boost::program_options::positional_options_description p; + p.add("input-file", -1); + + try { + // parse command line options + boost::program_options::store(boost::program_options::command_line_parser((int)argc, argv).options(cmdline_options).positional(p).run(), OPT::vm); + boost::program_options::notify(OPT::vm); + INIT_WORKING_FOLDER; + // parse configuration file + std::ifstream ifs(MAKE_PATH_SAFE(OPT::strConfigFileName)); + if (ifs) { + boost::program_options::store(parse_config_file(ifs, config_file_options), OPT::vm); + boost::program_options::notify(OPT::vm); + } + } + catch (const std::exception& e) { + LOG(e.what()); + return false; + } + + // initialize the log file + OPEN_LOGFILE(MAKE_PATH(APPNAME _T("-")+Util::getUniqueName(0)+_T(".log")).c_str()); + + // print application details: version and command line + Util::LogBuild(); + LOG(_T("Command line: ") APPNAME _T("%s"), Util::CommandLineToString(argc, argv).c_str()); + + // validate input + Util::ensureValidPath(OPT::strPointsFileName); + Util::ensureValidPath(OPT::strInputFileName); + Util::ensureValidFolderPath(OPT::strOutputImageFolder); + const bool bInvalidCommand(OPT::strInputFileName.empty()); + if (OPT::vm.count("help") || bInvalidCommand) { + boost::program_options::options_description visible("Available options"); + visible.add(generic).add(config); + GET_LOG() << visible; + } + if (bInvalidCommand) + return false; + + // initialize optional options + Util::ensureValidPath(OPT::strOutputFileName); + if (OPT::strOutputFileName.empty()) + OPT::strOutputFileName = Util::getFileName(OPT::strInputFileName) + MVS_EXT; + + MVS::Initialize(APPNAME, OPT::nMaxThreads, OPT::nProcessPriority); + return true; +} + +// finalize application instance +void Application::Finalize() +{ + MVS::Finalize(); + + CLOSE_LOGFILE(); + CLOSE_LOGCONSOLE(); + CLOSE_LOG(); +} + +struct DistCoeff { + union { + REAL coeff[8]; + struct { + REAL k1, k2, p1, p2, k3, k4, k5, k6; + }; + }; + DistCoeff() : k1(0), k2(0), p1(0), p2(0), k3(0), k4(0), k5(0), k6(0) {} + bool HasDistortion() const { return k1 != 0 || k2 != 0 || k3 != 0 || k4 != 0 || k5 != 0 || k6 != 0; } +}; +typedef cList DistCoeffs; +typedef cList PlatformDistCoeffs; + +void ImageListParseC(const LPSTR* argv, Point3& C) +{ + // read position vector + C.x = String::FromString(argv[0]); + C.y = String::FromString(argv[1]); + C.z = String::FromString(argv[2]); +} + +void ImageListParseR(const LPSTR* argv, Matrix3x3& R) +{ + // read rotation matrix + R(0, 0) = String::FromString(argv[0]); + R(0, 1) = String::FromString(argv[1]); + R(0, 2) = String::FromString(argv[2]); + R(1, 0) = String::FromString(argv[3]); + R(1, 1) = String::FromString(argv[4]); + R(1, 2) = String::FromString(argv[5]); + R(2, 0) = String::FromString(argv[6]); + R(2, 1) = String::FromString(argv[7]); + R(2, 2) = String::FromString(argv[8]); +} + +void ImageListParseP(const LPSTR* argv, Matrix3x4& P) +{ + // read projection matrix + P(0, 0) = String::FromString(argv[0]); + P(0, 1) = String::FromString(argv[1]); + P(0, 2) = String::FromString(argv[2]); + P(0, 3) = String::FromString(argv[3]); + P(1, 0) = String::FromString(argv[4]); + P(1, 1) = String::FromString(argv[5]); + P(1, 2) = String::FromString(argv[6]); + P(1, 3) = String::FromString(argv[7]); + P(2, 0) = String::FromString(argv[8]); + P(2, 1) = String::FromString(argv[9]); + P(2, 2) = String::FromString(argv[10]); + P(2, 3) = String::FromString(argv[11]); +} + +// parse images list containing calibration and pose information +// and load the corresponding 3D point-cloud +bool ParseImageListXML(tinyxml2::XMLDocument& doc, Scene& scene, PlatformDistCoeffs& pltDistCoeffs, size_t& nCameras, size_t& nPoses) +{ + String strInputFileName(MAKE_PATH_FULL(WORKING_FOLDER_FULL, OPT::strInputFileName)); + Util::ensureValidPath(strInputFileName); + tinyxml2::XMLElement* elem; + if (doc.ErrorID() != tinyxml2::XML_SUCCESS) + goto InvalidDocument; + { + tinyxml2::XMLElement* document = doc.FirstChildElement(_T("document"))->FirstChildElement(_T("chunk")); + if (document == NULL) + goto InvalidDocument; + { + bool bMetashapeFile(false); + CLISTDEF0(cv::Size) resolutions; + std::unordered_map mapPlatformID; + std::unordered_map mapImageID; + + // parse platform and camera models + { + tinyxml2::XMLElement* sensors = document->FirstChildElement(_T("sensors")); + if (sensors == NULL) + goto InvalidDocument; + { + for (tinyxml2::XMLElement* sensor=sensors->FirstChildElement(); sensor!=NULL; sensor=sensor->NextSiblingElement()) { + unsigned ID; + if (0 != _tcsicmp(sensor->Value(), _T("sensor")) || sensor->QueryUnsignedAttribute(_T("id"), &ID) != tinyxml2::XML_SUCCESS) + goto InvalidDocument; + { + // add new camera + enum CameraModel {METASHAPE=0, VSFM}; + int model(METASHAPE); + sensor->QueryIntAttribute(_T("model"), &model); + mapPlatformID.emplace(ID, scene.platforms.size()); + Platform& platform = scene.platforms.AddEmpty(); + LPCTSTR name; + if ((name=sensor->Attribute(_T("label"))) != NULL) + platform.name = name; + // parse intrinsics + tinyxml2::XMLElement* calibration = sensor->FirstChildElement(_T("calibration")); + if (calibration == NULL) + goto InvalidDocument; + { + if ((elem=calibration->FirstChildElement(_T("resolution"))) != NULL) { + resolutions.emplace_back( + elem->UnsignedAttribute(_T("width")), + elem->UnsignedAttribute(_T("height")) + ); + ASSERT(model == METASHAPE); + bMetashapeFile = true; + } + Platform::Camera& camera = platform.cameras.AddEmpty(); + camera.K = KMatrix::IDENTITY; + camera.R = RMatrix::IDENTITY; + camera.C = CMatrix::ZERO; + DistCoeff& dc = pltDistCoeffs.AddEmpty().AddEmpty(); + for (elem=calibration->FirstChildElement(); elem!=NULL; elem=elem->NextSiblingElement()) { + if (0 == _tcsicmp(elem->Value(), _T("f"))) { + camera.K(0,0) = camera.K(1,1) = String::FromString(elem->GetText()); + } else + if (0 == _tcsicmp(elem->Value(), _T("fx"))) { + elem->QueryDoubleText(&camera.K(0,0)); + } else + if (0 == _tcsicmp(elem->Value(), _T("fy"))) { + elem->QueryDoubleText(&camera.K(1,1)); + } else + if (0 == _tcsicmp(elem->Value(), _T("cx"))) { + elem->QueryDoubleText(&camera.K(0,2)); + } else + if (0 == _tcsicmp(elem->Value(), _T("cy"))) { + elem->QueryDoubleText(&camera.K(1,2)); + } else + if (0 == _tcsicmp(elem->Value(), _T("k1"))) { + elem->QueryDoubleText(&dc.k1); + } else + if (0 == _tcsicmp(elem->Value(), _T("k2"))) { + elem->QueryDoubleText(&dc.k2); + } else + if (0 == _tcsicmp(elem->Value(), _T("k3"))) { + elem->QueryDoubleText(&dc.k3); + } else + if (0 == _tcsicmp(elem->Value(), _T("p1"))) { + elem->QueryDoubleText(&dc.p1); + } else + if (0 == _tcsicmp(elem->Value(), _T("p2"))) { + elem->QueryDoubleText(&dc.p2); + } else + if (0 == _tcsicmp(elem->Value(), _T("k4"))) { + elem->QueryDoubleText(&dc.k4); + } else + if (0 == _tcsicmp(elem->Value(), _T("k5"))) { + elem->QueryDoubleText(&dc.k5); + } else + if (0 == _tcsicmp(elem->Value(), _T("k6"))) { + elem->QueryDoubleText(&dc.k6); + } + } + if (bMetashapeFile) { + const cv::Size& resolution = resolutions.back(); + camera.K(0,2) += resolution.width*REAL(0.5); + camera.K(1,2) += resolution.height*REAL(0.5); + camera.K = camera.GetScaledK(REAL(1)/Camera::GetNormalizationScale(resolution.width, resolution.height)); + std::swap(dc.p1, dc.p2); + } + ++nCameras; + } + } + } + } + } + + // parse poses + { + tinyxml2::XMLElement* cameras = document->FirstChildElement(_T("cameras")); + if (cameras == NULL) + goto InvalidDocument; + { + PMatrix P; + size_t argc; + const String strPath(Util::getFilePath(strInputFileName)); + for (tinyxml2::XMLElement* camera=cameras->FirstChildElement(); camera!=NULL; camera=camera->NextSiblingElement()) { + unsigned ID; + if (0 != _tcsicmp(camera->Value(), _T("camera")) || camera->QueryUnsignedAttribute(_T("id"), &ID) != tinyxml2::XML_SUCCESS) + goto InvalidDocument; + { + // add new image + mapImageID.emplace(ID, scene.images.size()); + Image& imageData = scene.images.AddEmpty(); + LPCTSTR name; + if ((name=camera->Attribute(_T("type"))) != NULL && _tcsicmp(name, _T("frame")) != 0) { + DEBUG_EXTRA("warning: unsupported camera calibration '%s'", name); + continue; + } + if ((name=camera->Attribute(_T("label"))) != NULL) + imageData.name = name; + Util::ensureUnifySlash(imageData.name); + if (Util::getFileExt(imageData.name).empty()) + imageData.name += _T(".jpg"); + imageData.name = MAKE_PATH_FULL(strPath, imageData.name); + imageData.platformID = mapPlatformID.at(camera->UnsignedAttribute(_T("sensor_id"))); + imageData.cameraID = 0; // only one camera per platform supported by this format + imageData.ID = mapImageID.at(ID); + const cv::Size& resolution = resolutions[imageData.platformID]; + imageData.width = resolution.width; + imageData.height = resolution.height; + imageData.scale = 1; + if (!bMetashapeFile && !camera->BoolAttribute(_T("enabled"))) { + imageData.poseID = NO_ID; + DEBUG_EXTRA("warning: uncalibrated image '%s'", name); + continue; + } + // set pose + CAutoPtrArr argv; + if ((elem=camera->FirstChildElement(_T("transform"))) == NULL || + (argv=Util::CommandLineToArgvA(elem->GetText(), argc)) == NULL || + (argc != (bMetashapeFile ? 16 : 12))) + { + VERBOSE("Invalid image list camera: %u", ID); + continue; + } + Platform& platform = scene.platforms[imageData.platformID]; + imageData.poseID = platform.poses.size(); + Platform::Pose& pose = platform.poses.AddEmpty(); + ImageListParseP(argv, P); + DecomposeProjectionMatrix(P, pose.R, pose.C); + if (bMetashapeFile) { + pose.C = pose.R*(-pose.C); + pose.R = pose.R.t(); + } + imageData.camera = platform.GetCamera(imageData.cameraID, imageData.poseID); + ++nPoses; + } + } + scene.nCalibratedImages = (unsigned)nPoses; + } + + // parse bounding-box + { + tinyxml2::XMLElement* region = document->FirstChildElement(_T("region")); + if (region == NULL) + goto InvalidDocument; + { + size_t argc; + CAutoPtrArr argv; + Point3 C, E; Matrix3x3 R; + if ((elem=region->FirstChildElement(_T("center"))) == NULL || + (argv=Util::CommandLineToArgvA(elem->GetText(), argc)) == NULL || + argc != 3) + { + VERBOSE("Invalid image list region: %s", elem->GetText()); + goto InvalidDocument; + } + ImageListParseC(argv, C); + if ((elem=region->FirstChildElement(_T("size"))) == NULL || + (argv=Util::CommandLineToArgvA(elem->GetText(), argc)) == NULL || + argc != 3) + { + VERBOSE("Invalid image list region: %s", elem->GetText()); + goto InvalidDocument; + } + ImageListParseC(argv, E); + E *= REAL(0.5); + if ((elem=region->FirstChildElement(_T("R"))) == NULL || + (argv=Util::CommandLineToArgvA(elem->GetText(), argc)) == NULL || + argc != 9) + { + VERBOSE("Invalid image list region: %s", elem->GetText()); + goto InvalidDocument; + } + ImageListParseR(argv, R); + scene.obb.m_rot = Cast(R); + scene.obb.m_pos = Cast(C); + scene.obb.m_ext = Cast(E); + } + } + } + } + } + + return true; + InvalidDocument: + VERBOSE("Invalid camera list"); + return false; +} + +// parse scene stored in ContextCapture BlocksExchange format containing cameras, images and sparse point-cloud +bool ParseBlocksExchangeXML(tinyxml2::XMLDocument& doc, Scene& scene, PlatformDistCoeffs& pltDistCoeffs, size_t& nCameras, size_t& nPoses) { + String strInputFileName(MAKE_PATH_FULL(WORKING_FOLDER_FULL, OPT::strInputFileName)); + Util::ensureValidPath(strInputFileName); + const tinyxml2::XMLElement* blocksExchange; + const tinyxml2::XMLElement* document; + const tinyxml2::XMLElement* photogroups; + if (doc.ErrorID() != tinyxml2::XML_SUCCESS || + (blocksExchange=doc.FirstChildElement("BlocksExchange")) == NULL || + (document=blocksExchange->FirstChildElement("Block")) == NULL || + (photogroups=document->FirstChildElement("Photogroups")) == NULL) { + VERBOSE("error: invalid scene file"); + return false; + } + CLISTDEF0(cv::Size) resolutions; + std::unordered_map mapImageID; + const String strPath(Util::getFilePath(strInputFileName)); + const tinyxml2::XMLElement* elem; + for (const tinyxml2::XMLElement* photogroup=photogroups->FirstChildElement(); photogroup!=NULL; photogroup=photogroup->NextSiblingElement()) { + if ((elem=photogroup->FirstChildElement("CameraModelType")) == NULL || + std::strcmp(elem->GetText(), "Perspective") != 0) + continue; + if ((elem=photogroup->FirstChildElement("ImageDimensions")) == NULL) + continue; + const IIndex platformID = scene.platforms.size(); + Platform& platform = scene.platforms.AddEmpty(); + platform.name = photogroup->FirstChildElement("Name")->GetText(); + resolutions.emplace_back( + elem->FirstChildElement("Width")->UnsignedText(), + elem->FirstChildElement("Height")->UnsignedText() + ); + // parse camera + Platform::Camera& camera = platform.cameras.AddEmpty(); + camera.K = KMatrix::IDENTITY; + camera.R = RMatrix::IDENTITY; + camera.C = CMatrix::ZERO; + const float resolutionScale = Camera::GetNormalizationScale(resolutions.back().width, resolutions.back().height); + if ((elem=photogroup->FirstChildElement("FocalLengthPixels")) != NULL) { + camera.K(0,0) = camera.K(1,1) = photogroup->FirstChildElement("FocalLengthPixels")->DoubleText(); + } else { + camera.K(0,0) = camera.K(1,1) = photogroup->FirstChildElement("FocalLength")->DoubleText() * resolutionScale / photogroup->FirstChildElement("SensorSize")->DoubleText(); + } + if ((elem=photogroup->FirstChildElement("PrincipalPoint")) != NULL) { + camera.K(0,2) = elem->FirstChildElement("x")->DoubleText(); + camera.K(1,2) = elem->FirstChildElement("y")->DoubleText(); + } else { + camera.K(0,2) = resolutions.back().width*REAL(0.5); + camera.K(1,2) = resolutions.back().height*REAL(0.5); + } + if ((elem=photogroup->FirstChildElement("AspectRatio")) != NULL) + camera.K(1,1) *= elem->DoubleText(); + if ((elem=photogroup->FirstChildElement("Skew")) != NULL) + camera.K(0,1) = elem->DoubleText(); + camera.K = camera.GetScaledK(REAL(1)/resolutionScale); + // parse distortion parameters + DistCoeff& dc = pltDistCoeffs.AddEmpty().AddEmpty(); { + const tinyxml2::XMLElement* distortion=photogroup->FirstChildElement("Distortion"); + if (distortion) { + if ((elem=distortion->FirstChildElement("K1")) != NULL) + dc.k1 = elem->DoubleText(); + if ((elem=distortion->FirstChildElement("K2")) != NULL) + dc.k2 = elem->DoubleText(); + if ((elem=distortion->FirstChildElement("K3")) != NULL) + dc.k3 = elem->DoubleText(); + if ((elem=distortion->FirstChildElement("P1")) != NULL) + dc.p2 = elem->DoubleText(); + if ((elem=distortion->FirstChildElement("P2")) != NULL) + dc.p1 = elem->DoubleText(); + } + } + ++nCameras; + for (const tinyxml2::XMLElement* photo=photogroup->FirstChildElement("Photo"); photo!=NULL; photo=photo->NextSiblingElement()) { + const IIndex idxImage = scene.images.size(); + Image& imageData = scene.images.AddEmpty(); + imageData.platformID = platformID; + imageData.cameraID = 0; // only one camera per platform supported by this format + imageData.poseID = NO_ID; + imageData.ID = photo->FirstChildElement("Id")->UnsignedText(); + imageData.name = photo->FirstChildElement("ImagePath")->GetText(); + Util::ensureUnifySlash(imageData.name); + imageData.name = MAKE_PATH_FULL(strPath, imageData.name); + mapImageID.emplace(imageData.ID, idxImage); + // set image resolution + const cv::Size& resolution = resolutions[imageData.platformID]; + imageData.width = resolution.width; + imageData.height = resolution.height; + imageData.scale = 1; + // set camera pose + const tinyxml2::XMLElement* photoPose = photo->FirstChildElement("Pose"); + if (photoPose == NULL) + continue; + if ((elem=photoPose->FirstChildElement("Rotation")) == NULL) + continue; + imageData.poseID = platform.poses.size(); + Platform::Pose& pose = platform.poses.AddEmpty(); + pose.R = Matrix3x3( + elem->FirstChildElement("M_00")->DoubleText(), + elem->FirstChildElement("M_01")->DoubleText(), + elem->FirstChildElement("M_02")->DoubleText(), + elem->FirstChildElement("M_10")->DoubleText(), + elem->FirstChildElement("M_11")->DoubleText(), + elem->FirstChildElement("M_12")->DoubleText(), + elem->FirstChildElement("M_20")->DoubleText(), + elem->FirstChildElement("M_21")->DoubleText(), + elem->FirstChildElement("M_22")->DoubleText()); + if ((elem=photoPose->FirstChildElement("Center")) == NULL) + continue; + pose.C = Point3( + elem->FirstChildElement("x")->DoubleText(), + elem->FirstChildElement("y")->DoubleText(), + elem->FirstChildElement("z")->DoubleText()); + imageData.camera = platform.GetCamera(imageData.cameraID, imageData.poseID); + // set depth stats + if ((elem=photo->FirstChildElement("MedianDepth")) != NULL) + imageData.avgDepth = (float)elem->DoubleText(); + else if (photo->FirstChildElement("NearDepth") != NULL && photo->FirstChildElement("FarDepth") != NULL) + imageData.avgDepth = (float)((photo->FirstChildElement("NearDepth")->DoubleText() + photo->FirstChildElement("FarDepth")->DoubleText())/2); + else + imageData.avgDepth = 0; + ++nPoses; + } + } + if (scene.images.size() < 2) + return false; + scene.nCalibratedImages = (unsigned)nPoses; + // transform poses to a local coordinate system + const bool bLocalCoords(document->FirstChildElement("SRSId") == NULL || + ((elem=blocksExchange->FirstChildElement("SpatialReferenceSystems")) != NULL && (elem=elem->FirstChildElement("SRS")) != NULL && (elem=elem->FirstChildElement("Name")) != NULL && _tcsncmp(elem->GetText(), "Local Coordinates", 17) == 0)); + Point3 center = Point3::ZERO; + if (!bLocalCoords) { + for (const Image& imageData : scene.images) + center += imageData.camera.C; + center /= scene.images.size(); + for (Platform& platform : scene.platforms) + for (Platform::Pose& pose : platform.poses) + pose.C -= center; + } + // try to read also the sparse point-cloud + const tinyxml2::XMLElement* tiepoints = document->FirstChildElement("TiePoints"); + if (tiepoints == NULL) + return true; + for (const tinyxml2::XMLElement* tiepoint=tiepoints->FirstChildElement(); tiepoint!=NULL; tiepoint=tiepoint->NextSiblingElement()) { + if ((elem=tiepoint->FirstChildElement("Position")) == NULL) + continue; + scene.pointcloud.points.emplace_back( + (float)elem->FirstChildElement("x")->DoubleText(), + (float)elem->FirstChildElement("y")->DoubleText(), + (float)elem->FirstChildElement("z")->DoubleText()); + if (!bLocalCoords) + scene.pointcloud.points.back() -= Cast(center); + if ((elem=tiepoint->FirstChildElement("Color")) != NULL) + scene.pointcloud.colors.emplace_back( + (uint8_t)CLAMP(elem->FirstChildElement("Red")->DoubleText()*255, 0.0, 255.0), + (uint8_t)CLAMP(elem->FirstChildElement("Green")->DoubleText()*255, 0.0, 255.0), + (uint8_t)CLAMP(elem->FirstChildElement("Blue")->DoubleText()*255, 0.0, 255.0)); + PointCloud::ViewArr views; + for (const tinyxml2::XMLElement* view=tiepoint->FirstChildElement("Measurement"); view!=NULL; view=view->NextSiblingElement()) + views.emplace_back(mapImageID.at(view->FirstChildElement("PhotoId")->UnsignedText())); + scene.pointcloud.pointViews.emplace_back(std::move(views)); + } + return true; +} + +// parse scene stored either in Metashape images list format or ContextCapture BlocksExchange format +bool ParseSceneXML(Scene& scene, PlatformDistCoeffs& pltDistCoeffs, size_t& nCameras, size_t& nPoses) +{ + // parse XML file + const String strInputFileName(MAKE_PATH_SAFE(OPT::strInputFileName)); + tinyxml2::XMLDocument doc; { + ISTREAMPTR pStream(new File(strInputFileName, File::READ, File::OPEN)); + if (!((File*)(ISTREAM*)pStream)->isOpen()) { + VERBOSE("error: failed opening the input scene file"); + return false; + } + const size_t nLen(pStream->getSize()); + String str; str.resize(nLen); + pStream->read(&str[0], nLen); + doc.Parse(str.c_str(), nLen); + } + if (doc.ErrorID() != tinyxml2::XML_SUCCESS) { + VERBOSE("error: invalid XML file"); + return false; + } + // parse scene + if (doc.FirstChildElement("BlocksExchange") == NULL) + return ParseImageListXML(doc, scene, pltDistCoeffs, nCameras, nPoses); + return ParseBlocksExchangeXML(doc, scene, pltDistCoeffs, nCameras, nPoses); +} + +// undistort image using Brown's model +bool UndistortBrown(Image& imageData, uint32_t ID, const DistCoeff& dc, const String& pathData) +{ + // do we need to undistort? + if (!dc.HasDistortion()) + return true; + + // load image pixels + if (!imageData.ReloadImage()) + return false; + + // initialize intrinsics + const cv::Vec& distCoeffs = *reinterpret_cast*>(dc.coeff); + const KMatrix prevK(imageData.camera.GetK(imageData.width, imageData.height)); + #if 1 + const KMatrix& K(prevK); + #else + const KMatrix K(cv::getOptimalNewCameraMatrix(prevK, distCoeffs, imageData.size(), 0.0, cv::Size(), NULL, true)); + ASSERT(K(0,2) == Camera::ComposeK(prevK(0,0), prevK(1,1), imageData.width(), imageData.height())(0,2)); + ASSERT(K(1,2) == Camera::ComposeK(prevK(0,0), prevK(1,1), imageData.width(), imageData.height())(1,2)); + if (K.IsEqual(prevK)) { + int i(0); + while (distCoeffs(i++) == 0.0) { + if (i == 8) + return true; // nothing to do + } + } + #endif + + // undistort image + Image8U3 imgUndist; + cv::undistort(imageData.image, imgUndist, prevK, distCoeffs, K); + imageData.ReleaseImage(); + + // save undistorted image + imageData.image = imgUndist; + imageData.name = pathData + String::FormatString(_T("%05u.jpg"), ID); + Util::ensureFolder(imageData.name); + return imageData.image.Save(imageData.name); +} + +// project all points in this image and keep those looking at the camera and are most in front +void AssignPoints(const Image& imageData, uint32_t ID, PointCloud& pointcloud) +{ + ASSERT(pointcloud.IsValid()); + const int CHalfSize(1); + const int FHalfSize(5); + const Depth thCloseDepth(0.1f); + + // sort points by depth + IndexScoreArr points(0, pointcloud.points.size()); + FOREACH(p, pointcloud.points) { + const PointCloud::Point& X(pointcloud.points[p]); + const float d((float)imageData.camera.PointDepth(X)); + if (d <= 0) + continue; + points.emplace_back((uint32_t)p, d); + } + points.Sort(); + + // project all points to this view + DepthMap depthMap(imageData.GetSize()); + TImage pointMap(imageData.GetSize()); + depthMap.fill(FLT_MAX); + pointMap.memset((uint8_t)NO_ID); + RFOREACHPTR(pPD, points) { + const Point3 X(pointcloud.points[pPD->idx]); + const Point3f Xc(imageData.camera.TransformPointW2C(X)); + // (also the view to point vector cause the face is in camera view space) + // point skip already in the previous step if the (cos) angle between + // the view to point vector and the view direction is negative + ASSERT(Xc.z > 0); + // skip point if the (cos) angle between + // its normal and the point to view vector is negative + if (!pointcloud.normals.empty() && Xc.dot(pointcloud.normals[pPD->idx]) > 0) + continue; + const Point2f x(imageData.camera.TransformPointC2I(Xc)); + const ImageRef ir(ROUND2INT(x)); + if (!depthMap.isInside(ir)) + continue; + // skip point if the there is a very near by point closer + for (int i=-CHalfSize; i<=CHalfSize; ++i) { + const int rw(ir.y+i); + for (int j=-CHalfSize; j<=CHalfSize; ++j) { + const int cw(ir.x+j); + if (!depthMap.isInside(ImageRef(cw,rw))) + continue; + if (depthMap(rw,cw) < Xc.z) + goto NEXT_POINT; + } + } + // skip the point if there is a near by point much closer + for (int i=-FHalfSize; i<=FHalfSize; ++i) { + const int rw(ir.y+i); + for (int j=-FHalfSize; j<=FHalfSize; ++j) { + const int cw(ir.x+j); + if (!depthMap.isInside(ImageRef(cw,rw))) + continue; + const Depth depth(depthMap(rw,cw)); + if (depth < Xc.z && !IsDepthSimilar(depth, Xc.z, thCloseDepth)) + goto NEXT_POINT; + } + } + // store this point + depthMap(ir) = Xc.z; + pointMap(ir) = pPD->idx; + NEXT_POINT:; + } + + // add all points viewed by this camera + const int HalfSize(1); + const int RowsEnd(pointMap.rows-HalfSize); + const int ColsEnd(pointMap.cols-HalfSize); + unsigned nNumPoints(0); + #ifdef _USE_OPENMP + #pragma omp critical + #endif + for (int r=HalfSize; r at allocation + _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);// | _CRTDBG_CHECK_ALWAYS_DF); + #endif + + Application application; + if (!application.Initialize(argc, argv)) + return EXIT_FAILURE; + + TD_TIMER_START(); + + Scene scene(OPT::nMaxThreads); + + // convert data from Metashape format to OpenMVS + PlatformDistCoeffs pltDistCoeffs; + size_t nCameras(0), nPoses(0); + if (!ParseSceneXML(scene, pltDistCoeffs, nCameras, nPoses)) + return EXIT_FAILURE; + + // read the 3D point-cloud if available + if (!OPT::strPointsFileName.empty() && !scene.pointcloud.Load(MAKE_PATH_SAFE(OPT::strPointsFileName))) + return EXIT_FAILURE; + const bool bAssignPoints(!scene.pointcloud.IsEmpty() && !scene.pointcloud.IsValid()); + if (bAssignPoints) + scene.pointcloud.pointViews.resize(scene.pointcloud.GetSize()); + + // undistort images + const String pathData(MAKE_PATH_FULL(WORKING_FOLDER_FULL, OPT::strOutputImageFolder)); + Util::Progress progress(_T("Processed images"), scene.images.size()); + GET_LOGCONSOLE().Pause(); + #ifdef _USE_OPENMP + bool bAbort(false); + #pragma omp parallel for shared(bAbort) schedule(dynamic) + for (int ID=0; ID<(int)scene.images.size(); ++ID) { + #pragma omp flush (bAbort) + if (bAbort) + continue; + #else + FOREACH(ID, scene.images) { + #endif + ++progress; + Image& imageData = scene.images[ID]; + if (!imageData.IsValid()) + continue; + if (!UndistortBrown(imageData, ID, pltDistCoeffs[imageData.platformID][imageData.cameraID], pathData)) { + #ifdef _USE_OPENMP + bAbort = true; + #pragma omp flush (bAbort) + continue; + #else + return EXIT_FAILURE; + #endif + } + imageData.UpdateCamera(scene.platforms); + if (bAssignPoints) + AssignPoints(imageData, ID, scene.pointcloud); + } + GET_LOGCONSOLE().Play(); + #ifdef _USE_OPENMP + if (bAbort) + return EXIT_FAILURE; + #endif + progress.close(); + + if (scene.pointcloud.IsValid()) { + // filter invalid points + RFOREACH(i, scene.pointcloud.points) + if (scene.pointcloud.pointViews[i].size() < 2) + scene.pointcloud.RemovePoint(i); + // compute average scene depth per image + if (!std::any_of(scene.images.begin(), scene.images.end(), [](const Image& imageData) { return imageData.avgDepth > 0; })) { + std::vector avgDepths(scene.images.size(), 0.f); + std::vector numDepths(scene.images.size(), 0u); + FOREACH(idxPoint, scene.pointcloud.points) { + const Point3 X(scene.pointcloud.points[idxPoint]); + for (const PointCloud::View& idxImage: scene.pointcloud.pointViews[idxPoint]) { + const Image& imageData = scene.images[idxImage]; + const float depth((float)imageData.camera.PointDepth(X)); + if (depth > 0) { + avgDepths[idxImage] += depth; + ++numDepths[idxImage]; + } + } + } + FOREACH(idxImage, scene.images) { + Image& imageData = scene.images[idxImage]; + if (numDepths[idxImage] > 0) + imageData.avgDepth = avgDepths[idxImage] / numDepths[idxImage]; + } + } + } + + // print average scene depth per image stats + MeanStdMinMax acc; + for (const Image& imageData: scene.images) + if (imageData.avgDepth > 0) + acc.Update(imageData.avgDepth); + + // write OpenMVS input data + scene.Save(MAKE_PATH_SAFE(OPT::strOutputFileName), (ARCHIVE_TYPE)OPT::nArchiveType); + + VERBOSE("Exported data: %u platforms, %u cameras, %u poses, %u images, %u vertices, %g min / %g mean (%g std) / %g max average scene depth per image (%s)", + scene.platforms.size(), nCameras, nPoses, scene.images.size(), scene.pointcloud.GetSize(), + acc.minVal, acc.GetMean(), acc.GetStdDev(), acc.maxVal, + TD_TIMER_GET_FMT().c_str()); + return EXIT_SUCCESS; +} +/*----------------------------------------------------------------*/ diff --git a/apps/InterfaceOpenMVG/CMakeLists.txt b/apps/InterfaceOpenMVG/CMakeLists.txt index 5dc56a0d9..6377497ea 100644 --- a/apps/InterfaceOpenMVG/CMakeLists.txt +++ b/apps/InterfaceOpenMVG/CMakeLists.txt @@ -15,7 +15,7 @@ else() endif() FILE(GLOB LIBRARY_FILES_H "*.h" "*.inl") -cxx_executable_with_flags_no_pch(InterfaceOpenMVG "Apps" "${cxx_default}" "${LIBS_DEPEND};${OpenMVS_EXTRA_LIBS}" ${LIBRARY_FILES_C} ${LIBRARY_FILES_H}) +cxx_executable_with_flags(InterfaceOpenMVG "Apps" "${cxx_default}" "${LIBS_DEPEND};${OpenMVS_EXTRA_LIBS}" ${LIBRARY_FILES_C} ${LIBRARY_FILES_H}) # Install INSTALL(TARGETS InterfaceOpenMVG diff --git a/apps/InterfaceOpenMVG/InterfaceOpenMVG.cpp b/apps/InterfaceOpenMVG/InterfaceOpenMVG.cpp index c410d0d75..fa3dc6b33 100644 --- a/apps/InterfaceOpenMVG/InterfaceOpenMVG.cpp +++ b/apps/InterfaceOpenMVG/InterfaceOpenMVG.cpp @@ -53,6 +53,8 @@ // S T R U C T S /////////////////////////////////////////////////// +namespace { + namespace openMVS { namespace MVS_IO { @@ -337,8 +339,17 @@ String strConfigFileName; boost::program_options::variables_map vm; } // namespace OPT +class Application { +public: + Application() {} + ~Application() { Finalize(); } + + bool Initialize(size_t argc, LPCTSTR* argv); + void Finalize(); +}; // Application + // initialize and parse the command line parameters -bool Initialize(size_t argc, LPCTSTR* argv) +bool Application::Initialize(size_t argc, LPCTSTR* argv) { // initialize log and console OPEN_LOG(); @@ -350,11 +361,11 @@ bool Initialize(size_t argc, LPCTSTR* argv) ("help,h", "produce this help message") ("working-folder,w", boost::program_options::value(&WORKING_FOLDER), "working directory (default current directory)") ("config-file,c", boost::program_options::value(&OPT::strConfigFileName)->default_value(APPNAME _T(".cfg")), "file name containing program options") - ("archive-type", boost::program_options::value(&OPT::nArchiveType)->default_value(2), "project archive type: 0-text, 1-binary, 2-compressed binary") - ("process-priority", boost::program_options::value(&OPT::nProcessPriority)->default_value(-1), "process priority (below normal by default)") - ("max-threads", boost::program_options::value(&OPT::nMaxThreads)->default_value(0), "maximum number of threads (0 for using all available cores)") + ("archive-type", boost::program_options::value(&OPT::nArchiveType)->default_value(ARCHIVE_DEFAULT), "project archive type: 0-text, 1-binary, 2-compressed binary") + ("process-priority", boost::program_options::value(&OPT::nProcessPriority)->default_value(-1), "process priority (below normal by default)") + ("max-threads", boost::program_options::value(&OPT::nMaxThreads)->default_value(0), "maximum number of threads (0 for using all available cores)") #if TD_VERBOSE != TD_VERBOSE_OFF - ("verbosity,v", boost::program_options::value(&g_nVerbosityLevel)->default_value( + ("verbosity,v", boost::program_options::value(&g_nVerbosityLevel)->default_value( #if TD_VERBOSE == TD_VERBOSE_DEBUG 3 #else @@ -371,7 +382,7 @@ bool Initialize(size_t argc, LPCTSTR* argv) ("input-file,i", boost::program_options::value(&OPT::strInputFileName), "input filename containing camera poses and image list") ("output-file,o", boost::program_options::value(&OPT::strOutputFileName), "output filename for storing the mesh") ("output-image-folder", boost::program_options::value(&OPT::strOutputImageFolder)->default_value("undistorted_images"), "output folder to store undistorted images") - ("normalize,f", boost::program_options::value(&OPT::bNormalizeIntrinsics)->default_value(true), "normalize intrinsics while exporting to OpenMVS format") + ("normalize,f", boost::program_options::value(&OPT::bNormalizeIntrinsics)->default_value(true), "normalize intrinsics while exporting to OpenMVS format") ; boost::program_options::options_description cmdline_options; @@ -405,7 +416,7 @@ bool Initialize(size_t argc, LPCTSTR* argv) // print application details: version and command line Util::LogBuild(); - LOG(_T("Command line:%s"), Util::CommandLineToString(argc, argv).c_str()); + LOG(_T("Command line: ") APPNAME _T("%s"), Util::CommandLineToString(argc, argv).c_str()); // validate input Util::ensureValidPath(OPT::strListFileName); @@ -413,7 +424,7 @@ bool Initialize(size_t argc, LPCTSTR* argv) Util::ensureValidPath(OPT::strInputFileName); Util::ensureUnifySlash(OPT::strInputFileName); Util::ensureUnifySlash(OPT::strOutputImageFolder); - Util::ensureDirectorySlash(OPT::strOutputImageFolder); + Util::ensureFolderSlash(OPT::strOutputImageFolder); const String strInputFileNameExt(Util::getFileExt(OPT::strInputFileName).ToLower()); OPT::bOpenMVS2OpenMVG = (strInputFileNameExt == MVS_EXT); #ifdef _USE_OPENMVG @@ -438,39 +449,28 @@ bool Initialize(size_t argc, LPCTSTR* argv) Util::ensureUnifySlash(OPT::strOutputFileName); if (OPT::bOpenMVS2OpenMVG) { if (OPT::strOutputFileName.IsEmpty()) - OPT::strOutputFileName = Util::getFullFileName(OPT::strInputFileName); + OPT::strOutputFileName = Util::getFileFullName(OPT::strInputFileName); } else { if (OPT::strOutputFileName.IsEmpty()) - OPT::strOutputFileName = Util::getFullFileName(OPT::strInputFileName) + MVS_EXT; + OPT::strOutputFileName = Util::getFileFullName(OPT::strInputFileName) + MVS_EXT; } - // initialize global options - Process::setCurrentProcessPriority((Process::Priority)OPT::nProcessPriority); - #ifdef _USE_OPENMP - if (OPT::nMaxThreads != 0) - omp_set_num_threads(OPT::nMaxThreads); - #endif - - #ifdef _USE_BREAKPAD - // start memory dumper - MiniDumper::Create(APPNAME, WORKING_FOLDER); - #endif + MVS::Initialize(APPNAME, OPT::nMaxThreads, OPT::nProcessPriority); return true; } // finalize application instance -void Finalize() +void Application::Finalize() { - #if TD_VERBOSE != TD_VERBOSE_OFF - // print memory statistics - Util::LogMemoryInfo(); - #endif + MVS::Finalize(); CLOSE_LOGFILE(); CLOSE_LOGCONSOLE(); CLOSE_LOG(); } +} // unnamed namespace + int main(int argc, LPCTSTR* argv) { #ifdef _DEBUGINFO @@ -478,7 +478,8 @@ int main(int argc, LPCTSTR* argv) _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);// | _CRTDBG_CHECK_ALWAYS_DF); #endif - if (!Initialize(argc, argv)) + Application application; + if (!application.Initialize(argc, argv)) return EXIT_FAILURE; TD_TIMER_START(); @@ -598,7 +599,7 @@ int main(int argc, LPCTSTR* argv) image.name = view.second->s_Img_path; Util::ensureUnifySlash(image.name); Util::strTrim(image.name, PATH_SEPARATOR_STR); - String pathRoot(sfm_data.s_root_path); Util::ensureDirectorySlash(pathRoot); + String pathRoot(sfm_data.s_root_path); Util::ensureFolderSlash(pathRoot); const String srcImage(MAKE_PATH_FULL(WORKING_FOLDER_FULL, pathRoot+image.name)); image.name = MAKE_PATH_FULL(WORKING_FOLDER_FULL, OPT::strOutputImageFolder+image.name); Util::ensureDirectory(image.name); @@ -749,7 +750,6 @@ int main(int argc, LPCTSTR* argv) TD_TIMER_GET_FMT().c_str()); } - Finalize(); return EXIT_SUCCESS; } /*----------------------------------------------------------------*/ diff --git a/apps/InterfacePhotoScan/InterfacePhotoScan.cpp b/apps/InterfacePhotoScan/InterfacePhotoScan.cpp deleted file mode 100644 index 0b5f1effa..000000000 --- a/apps/InterfacePhotoScan/InterfacePhotoScan.cpp +++ /dev/null @@ -1,597 +0,0 @@ -/* - * InterfacePhotoScan.cpp - * - * Copyright (c) 2014-2015 SEACAVE - * - * Author(s): - * - * cDc - * - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - * - * Additional Terms: - * - * You are required to preserve legal notices and author attributions in - * that material or in the Appropriate Legal Notices displayed by works - * containing it. - */ - -#include "../../libs/MVS/Common.h" -#include "../../libs/MVS/Scene.h" -#include - - -// D E F I N E S /////////////////////////////////////////////////// - -#define APPNAME _T("InterfacePhotoScan") -#define MVS_EXT _T(".mvs") -#define XML_EXT _T(".xml") -#define PLY_EXT _T(".ply") - - -// S T R U C T S /////////////////////////////////////////////////// - -namespace OPT { -String strPointsFileName; -String strInputFileName; -String strOutputFileName; -String strOutputImageFolder; -unsigned nArchiveType; -int nProcessPriority; -unsigned nMaxThreads; -String strConfigFileName; -boost::program_options::variables_map vm; -} // namespace OPT - -// initialize and parse the command line parameters -bool Initialize(size_t argc, LPCTSTR* argv) -{ - // initialize log and console - OPEN_LOG(); - OPEN_LOGCONSOLE(); - - // group of options allowed only on command line - boost::program_options::options_description generic("Generic options"); - generic.add_options() - ("help,h", "produce this help message") - ("working-folder,w", boost::program_options::value(&WORKING_FOLDER), "working directory (default current directory)") - ("config-file,c", boost::program_options::value(&OPT::strConfigFileName)->default_value(APPNAME _T(".cfg")), "file name containing program options") - ("archive-type", boost::program_options::value(&OPT::nArchiveType)->default_value(2), "project archive type: 0-text, 1-binary, 2-compressed binary") - ("process-priority", boost::program_options::value(&OPT::nProcessPriority)->default_value(-1), "process priority (below normal by default)") - ("max-threads", boost::program_options::value(&OPT::nMaxThreads)->default_value(0), "maximum number of threads (0 for using all available cores)") - #if TD_VERBOSE != TD_VERBOSE_OFF - ("verbosity,v", boost::program_options::value(&g_nVerbosityLevel)->default_value( - #if TD_VERBOSE == TD_VERBOSE_DEBUG - 3 - #else - 2 - #endif - ), "verbosity level") - #endif - ; - - // group of options allowed both on command line and in config file - boost::program_options::options_description config("Main options"); - config.add_options() - ("points-file,p", boost::program_options::value(&OPT::strPointsFileName), "input filename containing the 3D points") - ("input-file,i", boost::program_options::value(&OPT::strInputFileName), "input filename containing camera poses and image list") - ("output-file,o", boost::program_options::value(&OPT::strOutputFileName), "output filename for storing the mesh") - ("output-image-folder", boost::program_options::value(&OPT::strOutputImageFolder)->default_value("undistorted_images"), "output folder to store undistorted images") - ; - - boost::program_options::options_description cmdline_options; - cmdline_options.add(generic).add(config); - - boost::program_options::options_description config_file_options; - config_file_options.add(config); - - boost::program_options::positional_options_description p; - p.add("input-file", -1); - - try { - // parse command line options - boost::program_options::store(boost::program_options::command_line_parser((int)argc, argv).options(cmdline_options).positional(p).run(), OPT::vm); - boost::program_options::notify(OPT::vm); - INIT_WORKING_FOLDER; - // parse configuration file - std::ifstream ifs(MAKE_PATH_SAFE(OPT::strConfigFileName)); - if (ifs) { - boost::program_options::store(parse_config_file(ifs, config_file_options), OPT::vm); - boost::program_options::notify(OPT::vm); - } - } - catch (const std::exception& e) { - LOG(e.what()); - return false; - } - - // initialize the log file - OPEN_LOGFILE(MAKE_PATH(APPNAME _T("-")+Util::getUniqueName(0)+_T(".log")).c_str()); - - // print application details: version and command line - Util::LogBuild(); - LOG(_T("Command line:%s"), Util::CommandLineToString(argc, argv).c_str()); - - // validate input - Util::ensureValidPath(OPT::strPointsFileName); - Util::ensureUnifySlash(OPT::strPointsFileName); - Util::ensureValidPath(OPT::strInputFileName); - Util::ensureUnifySlash(OPT::strInputFileName); - Util::ensureUnifySlash(OPT::strOutputImageFolder); - Util::ensureDirectorySlash(OPT::strOutputImageFolder); - const String strInputFileNameExt(Util::getFileExt(OPT::strInputFileName).ToLower()); - const bool bInvalidCommand(OPT::strInputFileName.IsEmpty() || OPT::strPointsFileName.IsEmpty()); - if (OPT::vm.count("help") || bInvalidCommand) { - boost::program_options::options_description visible("Available options"); - visible.add(generic).add(config); - GET_LOG() << visible; - } - if (bInvalidCommand) - return false; - - // initialize optional options - Util::ensureValidPath(OPT::strOutputFileName); - Util::ensureUnifySlash(OPT::strOutputFileName); - if (OPT::strOutputFileName.IsEmpty()) - OPT::strOutputFileName = Util::getFullFileName(OPT::strInputFileName) + MVS_EXT; - - // initialize global options - Process::setCurrentProcessPriority((Process::Priority)OPT::nProcessPriority); - #ifdef _USE_OPENMP - if (OPT::nMaxThreads != 0) - omp_set_num_threads(OPT::nMaxThreads); - #endif - - #ifdef _USE_BREAKPAD - // start memory dumper - MiniDumper::Create(APPNAME, WORKING_FOLDER); - #endif - return true; -} - -// finalize application instance -void Finalize() -{ - #if TD_VERBOSE != TD_VERBOSE_OFF - // print memory statistics - Util::LogMemoryInfo(); - #endif - - CLOSE_LOGFILE(); - CLOSE_LOGCONSOLE(); - CLOSE_LOG(); -} - -struct DistCoeff { - REAL k1, k2, k3, p1, p2, k4, k5, k6; - DistCoeff() : k1(0), k2(0), k3(0), p1(0), p2(0), k4(0), k5(0), k6(0) {} -}; -typedef cList DistCoeffs; -typedef cList PlatformDistCoeffs; - -void ImageListParseP(const LPSTR* argv, Matrix3x4& P) -{ - // read projection matrix - P(0, 0) = String::FromString(argv[0]); - P(0, 1) = String::FromString(argv[1]); - P(0, 2) = String::FromString(argv[2]); - P(0, 3) = String::FromString(argv[3]); - P(1, 0) = String::FromString(argv[4]); - P(1, 1) = String::FromString(argv[5]); - P(1, 2) = String::FromString(argv[6]); - P(1, 3) = String::FromString(argv[7]); - P(2, 0) = String::FromString(argv[8]); - P(2, 1) = String::FromString(argv[9]); - P(2, 2) = String::FromString(argv[10]); - P(2, 3) = String::FromString(argv[11]); -} - -// parse images list containing calibration and pose information -// and load the corresponding 3D point-cloud -bool ParseImageListXML(MVS::Scene& scene, PlatformDistCoeffs& pltDistCoeffs, size_t& nCameras, size_t& nPoses) -{ - using namespace MVS; - - // open image list - nCameras = nPoses = 0; - const String strInputFileName(MAKE_PATH_SAFE(OPT::strInputFileName)); - ISTREAMPTR pStream(new File(strInputFileName, File::READ, File::OPEN)); - if (!((File*)(ISTREAM*)pStream)->isOpen()) { - LOG(_T("error: failed opening the input image list")); - return false; - } - - // parse camera list - tinyxml2::XMLElement* elem; - const size_t nLen(pStream->getSize()); - String strCameras; strCameras.resize(nLen); - pStream->read(&strCameras[0], nLen); - tinyxml2::XMLDocument doc; - doc.Parse(strCameras.c_str(), nLen); - if (doc.ErrorID() != tinyxml2::XML_SUCCESS) - goto InvalidDocument; - tinyxml2::XMLElement* document = doc.FirstChildElement(_T("document"))->FirstChildElement(_T("chunk")); - if (document == NULL) - goto InvalidDocument; - bool bPhotoScanFile(false); - - // parse platform and camera models - { - tinyxml2::XMLElement* sensors = document->FirstChildElement(_T("sensors")); - if (sensors == NULL) - goto InvalidDocument; - { - for (tinyxml2::XMLElement* sensor=sensors->FirstChildElement(); sensor!=NULL; sensor=sensor->NextSiblingElement()) { - unsigned ID; - if (0 != _tcsicmp(sensor->Value(), _T("sensor")) || sensor->QueryUnsignedAttribute(_T("id"), &ID) != tinyxml2::XML_SUCCESS) - goto InvalidDocument; - { - // add new camera - enum CameraModel {PHOTOSCAN=0, VSFM}; - int model(PHOTOSCAN); - sensor->QueryIntAttribute(_T("model"), &model); - ASSERT(scene.platforms.GetSize() == ID); - Platform& platform = scene.platforms.AddEmpty(); - LPCTSTR name; - if ((name=sensor->Attribute(_T("label"))) != NULL) - platform.name = name; - // parse intrinsics - tinyxml2::XMLElement* calibration = sensor->FirstChildElement(_T("calibration")); - if (calibration == NULL) - goto InvalidDocument; - { - REAL scale(1); - if ((elem=calibration->FirstChildElement(_T("resolution"))) != NULL) { - scale = REAL(1)/(REAL)Camera::GetNormalizationScale(elem->UnsignedAttribute(_T("width")), elem->UnsignedAttribute(_T("height"))); - ASSERT(model == PHOTOSCAN); - bPhotoScanFile = true; - } - Platform::Camera& camera = platform.cameras.AddEmpty(); - camera.K = KMatrix::IDENTITY; - camera.R = RMatrix::IDENTITY; - camera.C = CMatrix::ZERO; - DistCoeff& dc = pltDistCoeffs.AddEmpty().AddEmpty(); - for (elem=calibration->FirstChildElement(); elem!=NULL; elem=elem->NextSiblingElement()) { - if (0 == _tcsicmp(elem->Value(), _T("f"))) { - camera.K(0,0) = camera.K(1,1) = String::FromString(elem->GetText())*scale; - } else - if (0 == _tcsicmp(elem->Value(), _T("fx"))) { - elem->QueryDoubleText(&camera.K(0,0)); - camera.K(0,0) *= scale; - } else - if (0 == _tcsicmp(elem->Value(), _T("fy"))) { - elem->QueryDoubleText(&camera.K(1,1)); - camera.K(1,1) *= scale; - } else - if (0 == _tcsicmp(elem->Value(), _T("cx"))) { - elem->QueryDoubleText(&camera.K(0,2)); - camera.K(0,2) *= scale; - } else - if (0 == _tcsicmp(elem->Value(), _T("cy"))) { - elem->QueryDoubleText(&camera.K(1,2)); - camera.K(1,2) *= scale; - } else - if (0 == _tcsicmp(elem->Value(), _T("k1"))) { - elem->QueryDoubleText(&dc.k1); - } else - if (0 == _tcsicmp(elem->Value(), _T("k2"))) { - elem->QueryDoubleText(&dc.k2); - } else - if (0 == _tcsicmp(elem->Value(), _T("k3"))) { - elem->QueryDoubleText(&dc.k3); - } else - if (0 == _tcsicmp(elem->Value(), _T("p1"))) { - elem->QueryDoubleText(&dc.p1); - } else - if (0 == _tcsicmp(elem->Value(), _T("p2"))) { - elem->QueryDoubleText(&dc.p2); - } else - if (0 == _tcsicmp(elem->Value(), _T("k4"))) { - elem->QueryDoubleText(&dc.k4); - } else - if (0 == _tcsicmp(elem->Value(), _T("k5"))) { - elem->QueryDoubleText(&dc.k5); - } else - if (0 == _tcsicmp(elem->Value(), _T("k6"))) { - elem->QueryDoubleText(&dc.k6); - } - } - ++nCameras; - } - } - } - } - } - - // parse poses - { - tinyxml2::XMLElement* cameras = document->FirstChildElement(_T("cameras")); - if (cameras == NULL) - goto InvalidDocument; - { - PMatrix P; - size_t argc; - const String strPath(GET_PATH_FULL(strInputFileName)); - for (tinyxml2::XMLElement* camera=cameras->FirstChildElement(); camera!=NULL; camera=camera->NextSiblingElement()) { - unsigned ID; - if (0 != _tcsicmp(camera->Value(), _T("camera")) || camera->QueryUnsignedAttribute(_T("id"), &ID) != tinyxml2::XML_SUCCESS) - goto InvalidDocument; - { - // add new image - ASSERT(scene.images.GetSize() == ID); - Image& imageData = scene.images.AddEmpty(); - LPCTSTR name; - if ((name=camera->Attribute(_T("type"))) != NULL && _tcsicmp(name, _T("frame")) != 0) { - DEBUG_EXTRA("warning: unsupported camera calibration '%s'", name); - continue; - } - if ((name=camera->Attribute(_T("label"))) != NULL) - imageData.name = name; - Util::ensureUnifySlash(imageData.name); - imageData.name = MAKE_PATH_FULL(strPath, imageData.name); - imageData.platformID = camera->UnsignedAttribute(_T("sensor_id")); - imageData.cameraID = 0; // only one camera per platform supported by this format - imageData.ID = ID; - if (!camera->BoolAttribute(_T("enabled"))) { - imageData.poseID = NO_ID; - DEBUG_EXTRA("warning: uncalibrated image '%s'", name); - continue; - } - // set pose - CAutoPtrArr argv; - if ((elem=camera->FirstChildElement(_T("transform"))) == NULL || - (argv=Util::CommandLineToArgvA(elem->GetText(), argc)) == NULL || - (argc != (bPhotoScanFile ? 16 : 12))) - { - VERBOSE("Invalid image list camera: %u", ID); - continue; - } - Platform& platform = scene.platforms[imageData.platformID]; - imageData.poseID = platform.poses.GetSize(); - Platform::Pose& pose = platform.poses.AddEmpty(); - ImageListParseP(argv, P); - DecomposeProjectionMatrix(P, pose.R, pose.C); - if (bPhotoScanFile) { - pose.C = pose.R*(-pose.C); - pose.R = pose.R.t(); - } - imageData.camera = platform.GetCamera(imageData.cameraID, imageData.poseID); - ++nPoses; - } - } - } - } - - return true; - InvalidDocument: - VERBOSE("Invalid camera list"); - return false; -} - -// undistort image using Brown's model -bool UndistortBrown(MVS::Image& imageData, uint32_t ID, const DistCoeff& dc, const String& pathData) -{ - using namespace MVS; - - // load image pixels - if (!imageData.ReloadImage()) - return false; - - // initialize intrinsics - cv::Vec distCoeffs; - distCoeffs(0) = dc.k1; - distCoeffs(1) = dc.k2; - distCoeffs(2) = dc.p1; - distCoeffs(3) = dc.p2; - distCoeffs(4) = dc.k3; - distCoeffs(5) = dc.k4; - distCoeffs(6) = dc.k5; - distCoeffs(7) = dc.k6; - const KMatrix prevK(imageData.camera.GetK(imageData.width, imageData.height)); - #if 1 - const KMatrix& K(prevK); - #else - const KMatrix K(cv::getOptimalNewCameraMatrix(prevK, distCoeffs, imageData.GetSize(), 0.0, cv::Size(), NULL, true)); - ASSERT(K(0,2) == Camera::ComposeK(prevK(0,0), prevK(1,1), imageData.width(), imageData.height())(0,2)); - ASSERT(K(1,2) == Camera::ComposeK(prevK(0,0), prevK(1,1), imageData.width(), imageData.height())(1,2)); - if (K.IsEqual(prevK)) { - int i(0); - while (distCoeffs(i++) == 0.0) { - if (i == 8) - return true; // nothing to do - } - } - #endif - - // undistort image - Image8U3 imgUndist; - cv::undistort(imageData.image, imgUndist, prevK, distCoeffs, K); - imageData.ReleaseImage(); - - // save undistorted image - imageData.image = imgUndist; - imageData.name = pathData + String::FormatString(_T("%05u.png"), ID); - Util::ensureDirectory(imageData.name); - return imageData.image.Save(imageData.name); -} - -// project all points in this image and keep those looking at the camera and are most in front -void AssignPoints(const MVS::Image& imageData, uint32_t ID, MVS::PointCloud& pointcloud) -{ - using namespace MVS; - - ASSERT(pointcloud.IsValid()); - const int CHalfSize(1); - const int FHalfSize(5); - const Depth thCloseDepth(0.1f); - - // sort points by depth - IndexScoreArr points(0, pointcloud.points.GetSize()); - FOREACH(p, pointcloud.points) { - const PointCloud::Point& X(pointcloud.points[p]); - const float d((float)imageData.camera.PointDepth(X)); - if (d <= 0) - continue; - points.AddConstruct((uint32_t)p, d); - } - points.Sort(); - - // project all points to this view - DepthMap depthMap(imageData.GetSize()); - TImage pointMap(imageData.GetSize()); - depthMap.fill(FLT_MAX); - pointMap.memset((uint8_t)NO_ID); - RFOREACHPTR(pPD, points) { - const Point3 X(pointcloud.points[pPD->idx]); - const Point3f Xc(imageData.camera.TransformPointW2C(X)); - // (also the view to point vector cause the face is in camera view space) - // point skip already in the previous step if the (cos) angle between - // the view to point vector and the view direction is negative - ASSERT(Xc.z > 0); - // skip point if the (cos) angle between - // its normal and the point to view vector is negative - if (!pointcloud.normals.IsEmpty() && Xc.dot(pointcloud.normals[pPD->idx]) > 0) - continue; - const Point2f x(imageData.camera.TransformPointC2I(Xc)); - const ImageRef ir(ROUND2INT(x)); - if (!depthMap.isInside(ir)) - continue; - // skip point if the there is a very near by point closer - for (int i=-CHalfSize; i<=CHalfSize; ++i) { - const int rw(ir.y+i); - for (int j=-CHalfSize; j<=CHalfSize; ++j) { - const int cw(ir.x+j); - if (!depthMap.isInside(ImageRef(cw,rw))) - continue; - if (depthMap(rw,cw) < Xc.z) - goto NEXT_POINT; - } - } - // skip the point if there is a near by point much closer - for (int i=-FHalfSize; i<=FHalfSize; ++i) { - const int rw(ir.y+i); - for (int j=-FHalfSize; j<=FHalfSize; ++j) { - const int cw(ir.x+j); - if (!depthMap.isInside(ImageRef(cw,rw))) - continue; - const Depth depth(depthMap(rw,cw)); - if (depth < Xc.z && !IsDepthSimilar(depth, Xc.z, thCloseDepth)) - goto NEXT_POINT; - } - } - // store this point - depthMap(ir) = Xc.z; - pointMap(ir) = pPD->idx; - NEXT_POINT:; - } - - // add all points viewed by this camera - const int HalfSize(1); - const int RowsEnd(pointMap.rows-HalfSize); - const int ColsEnd(pointMap.cols-HalfSize); - unsigned nNumPoints(0); - #ifdef _USE_OPENMP - #pragma omp critical - #endif - for (int r=HalfSize; r at allocation - _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);// | _CRTDBG_CHECK_ALWAYS_DF); - #endif - - if (!Initialize(argc, argv)) - return EXIT_FAILURE; - - TD_TIMER_START(); - - // read the 3D point-cloud - MVS::Scene scene(OPT::nMaxThreads); - if (!scene.pointcloud.Load(MAKE_PATH_SAFE(OPT::strPointsFileName))) - return EXIT_FAILURE; - ASSERT(!scene.pointcloud.IsValid()); - scene.pointcloud.pointViews.Resize(scene.pointcloud.points.GetSize()); - - // convert data from PhotoScan format to OpenMVS - PlatformDistCoeffs pltDistCoeffs; - size_t nCameras, nPoses; - if (!ParseImageListXML(scene, pltDistCoeffs, nCameras, nPoses)) - return EXIT_FAILURE; - - // undistort images - const String pathData(MAKE_PATH_FULL(WORKING_FOLDER_FULL, OPT::strOutputImageFolder)); - Util::Progress progress(_T("Processed images"), scene.images.GetSize()); - GET_LOGCONSOLE().Pause(); - #ifdef _USE_OPENMP - bool bAbort(false); - #pragma omp parallel for shared(bAbort) schedule(dynamic) - for (int ID=0; ID<(int)scene.images.GetSize(); ++ID) { - #pragma omp flush (bAbort) - if (bAbort) - continue; - #else - FOREACH(ID, scene.images) { - #endif - ++progress; - MVS::Image& imageData = scene.images[ID]; - if (!UndistortBrown(imageData, ID, pltDistCoeffs[imageData.platformID][imageData.cameraID], pathData)) { - #ifdef _USE_OPENMP - bAbort = true; - #pragma omp flush (bAbort) - continue; - #else - return false; - #endif - } - imageData.UpdateCamera(scene.platforms); - AssignPoints(imageData, ID, scene.pointcloud); - } - GET_LOGCONSOLE().Play(); - #ifdef _USE_OPENMP - if (bAbort) - return EXIT_SUCCESS; - #endif - progress.close(); - - // write OpenMVS input data - scene.Save(MAKE_PATH_SAFE(OPT::strOutputFileName), (ARCHIVE_TYPE)OPT::nArchiveType); - - VERBOSE("Exported data: %u platforms, %u cameras, %u poses, %u images, %u vertices (%s)", - scene.platforms.GetSize(), nCameras, nPoses, scene.images.GetSize(), scene.pointcloud.GetSize(), - TD_TIMER_GET_FMT().c_str()); - - Finalize(); - return EXIT_SUCCESS; -} -/*----------------------------------------------------------------*/ diff --git a/apps/InterfacePolycam/CMakeLists.txt b/apps/InterfacePolycam/CMakeLists.txt new file mode 100644 index 000000000..de8fda125 --- /dev/null +++ b/apps/InterfacePolycam/CMakeLists.txt @@ -0,0 +1,13 @@ +if(MSVC) + FILE(GLOB LIBRARY_FILES_C "*.cpp" "*.rc") +else() + FILE(GLOB LIBRARY_FILES_C "*.cpp") +endif() +FILE(GLOB LIBRARY_FILES_H "*.h" "*.inl") + +cxx_executable_with_flags(InterfacePolycam "Apps" "${cxx_default}" "MVS;${OpenMVS_EXTRA_LIBS}" ${LIBRARY_FILES_C} ${LIBRARY_FILES_H}) + +# Install +INSTALL(TARGETS InterfacePolycam + EXPORT OpenMVSTargets + RUNTIME DESTINATION "${INSTALL_BIN_DIR}" COMPONENT bin) diff --git a/apps/InterfacePolycam/InterfacePolycam.cpp b/apps/InterfacePolycam/InterfacePolycam.cpp new file mode 100644 index 000000000..ee7b3f159 --- /dev/null +++ b/apps/InterfacePolycam/InterfacePolycam.cpp @@ -0,0 +1,364 @@ +/* + * InterfacePolycam.cpp + * + * Copyright (c) 2014-2023 SEACAVE + * + * Author(s): + * + * cDc + * + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * + * Additional Terms: + * + * You are required to preserve legal notices and author attributions in + * that material or in the Appropriate Legal Notices displayed by works + * containing it. + */ + +#include "../../libs/MVS/Common.h" +#include "../../libs/MVS/Scene.h" +#define JSON_NOEXCEPTION +#include "../../libs/IO/json.hpp" +#include + +using namespace MVS; + + +// D E F I N E S /////////////////////////////////////////////////// + +#define APPNAME _T("InterfacePolycam") +#define MVS_EXT _T(".mvs") +#define JSON_EXT _T(".json") +#define DEPTH_EXT _T(".png") + + +// S T R U C T S /////////////////////////////////////////////////// + +namespace { + +namespace OPT { +String strInputFileName; +String strOutputFileName; +unsigned nArchiveType; +int nProcessPriority; +unsigned nMaxThreads; +String strConfigFileName; +boost::program_options::variables_map vm; +} // namespace OPT + +class Application { +public: + Application() {} + ~Application() { Finalize(); } + + bool Initialize(size_t argc, LPCTSTR* argv); + void Finalize(); +}; // Application + +// initialize and parse the command line parameters +bool Application::Initialize(size_t argc, LPCTSTR* argv) +{ + // initialize log and console + OPEN_LOG(); + OPEN_LOGCONSOLE(); + + // group of options allowed only on command line + boost::program_options::options_description generic("Generic options"); + generic.add_options() + ("help,h", "imports SfM scene stored Polycam format") + ("working-folder,w", boost::program_options::value(&WORKING_FOLDER), "working directory (default current directory)") + ("config-file,c", boost::program_options::value(&OPT::strConfigFileName)->default_value(APPNAME _T(".cfg")), "file name containing program options") + ("archive-type", boost::program_options::value(&OPT::nArchiveType)->default_value(ARCHIVE_MVS), "project archive type: -1-interface, 0-text, 1-binary, 2-compressed binary") + ("process-priority", boost::program_options::value(&OPT::nProcessPriority)->default_value(-1), "process priority (below normal by default)") + ("max-threads", boost::program_options::value(&OPT::nMaxThreads)->default_value(0), "maximum number of threads (0 for using all available cores)") + #if TD_VERBOSE != TD_VERBOSE_OFF + ("verbosity,v", boost::program_options::value(&g_nVerbosityLevel)->default_value( + #if TD_VERBOSE == TD_VERBOSE_DEBUG + 3 + #else + 2 + #endif + ), "verbosity level") + #endif + ; + + // group of options allowed both on command line and in config file + boost::program_options::options_description config("Main options"); + config.add_options() + ("input-file,i", boost::program_options::value(&OPT::strInputFileName), "input folder containing Polycam camera poses, images and depth-maps") + ("output-file,o", boost::program_options::value(&OPT::strOutputFileName), "output filename for storing the scene") + ; + + boost::program_options::options_description cmdline_options; + cmdline_options.add(generic).add(config); + + boost::program_options::options_description config_file_options; + config_file_options.add(config); + + boost::program_options::positional_options_description p; + p.add("input-file", -1); + + try { + // parse command line options + boost::program_options::store(boost::program_options::command_line_parser((int)argc, argv).options(cmdline_options).positional(p).run(), OPT::vm); + boost::program_options::notify(OPT::vm); + INIT_WORKING_FOLDER; + // parse configuration file + std::ifstream ifs(MAKE_PATH_SAFE(OPT::strConfigFileName)); + if (ifs) { + boost::program_options::store(parse_config_file(ifs, config_file_options), OPT::vm); + boost::program_options::notify(OPT::vm); + } + } + catch (const std::exception& e) { + LOG(e.what()); + return false; + } + + // initialize the log file + OPEN_LOGFILE(MAKE_PATH(APPNAME _T("-")+Util::getUniqueName(0)+_T(".log")).c_str()); + + // print application details: version and command line + Util::LogBuild(); + LOG(_T("Command line: ") APPNAME _T("%s"), Util::CommandLineToString(argc, argv).c_str()); + + // validate input + const bool bInvalidCommand(OPT::strInputFileName.empty()); + Util::ensureValidFolderPath(OPT::strInputFileName); + if (OPT::vm.count("help") || bInvalidCommand) { + boost::program_options::options_description visible("Available options"); + visible.add(generic).add(config); + GET_LOG() << visible; + } + if (bInvalidCommand) + return false; + + // initialize optional options + Util::ensureValidFolderPath(OPT::strOutputFileName); + if (OPT::strOutputFileName.empty()) + OPT::strOutputFileName = "scene" MVS_EXT; + + MVS::Initialize(APPNAME, OPT::nMaxThreads, OPT::nProcessPriority); + return true; +} + +// finalize application instance +void Application::Finalize() +{ + MVS::Finalize(); + + CLOSE_LOGFILE(); + CLOSE_LOGCONSOLE(); + CLOSE_LOG(); +} + +// parse image containing calibration, pose, and depth-map information +bool ParseImage(Scene& scene, const String& imagePath, const String& cameraPath, const String& depthPath, + const std::unordered_map& mapImageName) +{ + nlohmann::json data = nlohmann::json::parse(std::ifstream(cameraPath)); + if (data.empty()) + return false; + const cv::Size resolution(data["width"].get(), data["height"].get()); + // set platform + const IIndex platformID = scene.platforms.size(); + Platform& platform = scene.platforms.AddEmpty(); + Platform::Camera& camera = platform.cameras.AddEmpty(); + camera.K = KMatrix::IDENTITY; + camera.R = RMatrix::IDENTITY; + camera.C = CMatrix::ZERO; + camera.K(0,0) = data["fx"].get(); + camera.K(1,1) = data["fy"].get(); + camera.K(0,2) = data["cx"].get(); + camera.K(1,2) = data["cy"].get(); + // set image + const IIndex imageID = scene.images.size(); + Image& imageData = scene.images.AddEmpty(); + imageData.platformID = platformID; + imageData.cameraID = 0; // only one camera per platform supported by this format + imageData.poseID = NO_ID; + imageData.ID = imageID; + imageData.name = imagePath; + ASSERT(Util::isFullPath(imageData.name)); + // set image resolution + imageData.width = resolution.width; + imageData.height = resolution.height; + imageData.scale = 1; + // set camera pose + imageData.poseID = platform.poses.size(); + Platform::Pose& pose = platform.poses.AddEmpty(); + const Eigen::Matrix3d R_session_arkitcam{ + {data["t_00"].get(), data["t_01"].get(), data["t_02"].get()}, + {data["t_10"].get(), data["t_11"].get(), data["t_12"].get()}, + {data["t_20"].get(), data["t_21"].get(), data["t_22"].get()} + }; + const Eigen::Vector3d t_session_arkitcam{ + data["t_03"].get(), + data["t_13"].get(), + data["t_23"].get() + }; + const Eigen::Affine3d T_session_arkitcam{ + Eigen::Affine3d(Eigen::Translation3d(t_session_arkitcam)) * Eigen::Affine3d(Eigen::AngleAxisd(R_session_arkitcam)) + }; + const Eigen::Affine3d T_cam_arkitcam{ + Eigen::AngleAxisd(M_PI, Eigen::Vector3d::UnitX()) + }; + const Eigen::Matrix4d P{ + (T_cam_arkitcam * T_session_arkitcam.inverse()).matrix() + }; + pose.R = P.topLeftCorner<3, 3>().eval(); + pose.R.EnforceOrthogonality(); + const Point3d t = P.topRightCorner<3, 1>().eval(); + pose.C = pose.R.t() * (-t); + imageData.camera = platform.GetCamera(imageData.cameraID, imageData.poseID); + ++scene.nCalibratedImages; + // set image neighbors if available + nlohmann::json::const_iterator itNeighbors = data.find("neighbors"); + if (itNeighbors != data.end()) { + const std::vector neighborTimestamps = itNeighbors->get>(); + for (uint64_t timestamp: neighborTimestamps) { + const String neighborName = std::to_string(timestamp); + const IIndex neighborID = mapImageName.at(neighborName); + if (neighborID != imageData.ID) + imageData.neighbors.emplace_back(ViewScore{neighborID, 0, 1.f, FD2R(15.f), 0.5f, 3.f}); + } + } + // load and convert depth-map + if (!depthPath.empty()) { + DepthMap depthMap; { + constexpr double depthScale{1000.0}; + const cv::Mat imgDepthMap = cv::imread(depthPath, cv::IMREAD_ANYDEPTH); + if (imgDepthMap.empty()) + return false; + imgDepthMap.convertTo(depthMap, CV_32FC1, 1.0/depthScale); + } + IIndexArr IDs = {imageData.ID}; + IDs.JoinFunctor(imageData.neighbors.size(), [&imageData](IIndex i) { + return imageData.neighbors[i].ID; + }); + double dMin, dMax; + cv::minMaxIdx(depthMap, &dMin, &dMax, NULL, NULL, depthMap > 0); + const NormalMap normalMap; + const ConfidenceMap confMap; + const ViewsMap viewsMap; + if (!ExportDepthDataRaw(MAKE_PATH(String::FormatString("depth%04u.dmap", imageData.ID)), + imageData.name, IDs, resolution, + camera.K, pose.R, pose.C, + (float)dMin, (float)dMax, + depthMap, normalMap, confMap, viewsMap)) + return false; + } + return true; +} + +// parse scene stored in Polycam format +bool ParseScene(Scene& scene, const String& scenePath) +{ + #if defined(_SUPPORT_CPP17) && (!defined(__GNUC__) || (__GNUC__ > 7)) + size_t numCorrectedFolders(0), numCorrectedDepthFolders(0), numFolders(0), numDepthFolders(0); + for (const auto& file: std::filesystem::directory_iterator(scenePath.c_str())) { + if (file.path().stem() == "corrected_cameras" || + file.path().stem() == "corrected_images") + ++numCorrectedFolders; + else if (file.path().stem() == "corrected_depth") + ++numCorrectedDepthFolders; + else if (file.path().stem() == "cameras" || + file.path().stem() == "images") + ++numFolders; + else if (file.path().stem() == "depth") + ++numDepthFolders; + } + if (numFolders != 2) { + VERBOSE("Invalid scene folder"); + return false; + } + scene.nCalibratedImages = 0; + if (numCorrectedFolders == 2) { + // corrected data + CLISTDEFIDX(String, IIndex) imagePaths; + for (const auto& file: std::filesystem::directory_iterator((scenePath + "corrected_images").c_str())) + imagePaths.emplace_back(file.path().string()); + VERBOSE("Parsing corrected data: %u...", imagePaths.size()); + std::unordered_map mapImageName; + mapImageName.reserve(imagePaths.size()); + for (String& imagePath: imagePaths) { + Util::ensureValidPath(imagePath); + mapImageName.emplace(Util::getFileName(imagePath), static_cast(mapImageName.size())); + } + for (const String& imagePath: imagePaths) { + const String imageName = Util::getFileName(imagePath); + const String cameraPath(scenePath + "corrected_cameras" + PATH_SEPARATOR_STR + imageName + JSON_EXT); + const String depthPath(numCorrectedDepthFolders ? scenePath + "corrected_depth" + PATH_SEPARATOR_STR + imageName + DEPTH_EXT : String()); + if (!ParseImage(scene, imagePath, cameraPath, depthPath, mapImageName)) + return false; + } + } else { + // raw data + CLISTDEFIDX(String, IIndex) imagePaths; + for (const auto& file: std::filesystem::directory_iterator((scenePath + "images").c_str())) + imagePaths.emplace_back(file.path().string()); + VERBOSE("Parsing raw data: %u...", imagePaths.size()); + std::unordered_map mapImageName; + mapImageName.reserve(imagePaths.size()); + for (String& imagePath: imagePaths) { + Util::ensureValidPath(imagePath); + mapImageName.emplace(Util::getFileName(imagePath), static_cast(mapImageName.size())); + } + for (const String& imagePath: imagePaths) { + const String imageName = Util::getFileName(imagePath); + const String cameraPath(scenePath + "cameras" + PATH_SEPARATOR_STR + imageName + JSON_EXT); + const String depthPath(numDepthFolders ? scenePath + "depth" + PATH_SEPARATOR_STR + imageName + DEPTH_EXT : String()); + if (!ParseImage(scene, imagePath, cameraPath, depthPath, mapImageName)) + return false; + } + } + return true; + #else + return false; + #endif +} + +} // unnamed namespace + +int main(int argc, LPCTSTR* argv) +{ + #ifdef _DEBUGINFO + // set _crtBreakAlloc index to stop in at allocation + _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);// | _CRTDBG_CHECK_ALWAYS_DF); + #endif + + Application application; + if (!application.Initialize(argc, argv)) + return EXIT_FAILURE; + + TD_TIMER_START(); + + Scene scene(OPT::nMaxThreads); + + // convert data from Polycam format to OpenMVS + if (!ParseScene(scene, MAKE_PATH_FULL(WORKING_FOLDER_FULL, OPT::strInputFileName))) + return EXIT_FAILURE; + + // write OpenMVS input data + scene.Save(MAKE_PATH_SAFE(OPT::strOutputFileName), (ARCHIVE_TYPE)OPT::nArchiveType); + + VERBOSE("Exported data: %u platforms, %u cameras, %u poses, %u images (%s)", + scene.platforms.size(), scene.images.size(), scene.images.size(), scene.images.size(), + TD_TIMER_GET_FMT().c_str()); + return EXIT_SUCCESS; +} +/*----------------------------------------------------------------*/ diff --git a/apps/InterfaceVisualSFM/CMakeLists.txt b/apps/InterfaceVisualSFM/CMakeLists.txt index 9bd8bdedf..a1b16af7b 100644 --- a/apps/InterfaceVisualSFM/CMakeLists.txt +++ b/apps/InterfaceVisualSFM/CMakeLists.txt @@ -5,7 +5,7 @@ else() endif() FILE(GLOB LIBRARY_FILES_H "*.h" "*.inl") -cxx_executable_with_flags_no_pch(InterfaceVisualSFM "Apps" "${cxx_default}" "MVS;${OpenMVS_EXTRA_LIBS}" ${LIBRARY_FILES_C} ${LIBRARY_FILES_H}) +cxx_executable_with_flags(InterfaceVisualSFM "Apps" "${cxx_default}" "MVS;${OpenMVS_EXTRA_LIBS}" ${LIBRARY_FILES_C} ${LIBRARY_FILES_H}) # Install INSTALL(TARGETS InterfaceVisualSFM diff --git a/apps/InterfaceVisualSFM/InterfaceVisualSFM.cpp b/apps/InterfaceVisualSFM/InterfaceVisualSFM.cpp index c686abf8b..0f3d832d4 100644 --- a/apps/InterfaceVisualSFM/InterfaceVisualSFM.cpp +++ b/apps/InterfaceVisualSFM/InterfaceVisualSFM.cpp @@ -48,10 +48,13 @@ // S T R U C T S /////////////////////////////////////////////////// +namespace { + namespace OPT { String strInputFileName; String strOutputFileName; String strOutputImageFolder; +bool bFromOpenMVS; // conversion direction unsigned nArchiveType; int nProcessPriority; unsigned nMaxThreads; @@ -59,8 +62,17 @@ String strConfigFileName; boost::program_options::variables_map vm; } // namespace OPT +class Application { +public: + Application() {} + ~Application() { Finalize(); } + + bool Initialize(size_t argc, LPCTSTR* argv); + void Finalize(); +}; // Application + // initialize and parse the command line parameters -bool Initialize(size_t argc, LPCTSTR* argv) +bool Application::Initialize(size_t argc, LPCTSTR* argv) { // initialize log and console OPEN_LOG(); @@ -72,7 +84,7 @@ bool Initialize(size_t argc, LPCTSTR* argv) ("help,h", "produce this help message") ("working-folder,w", boost::program_options::value(&WORKING_FOLDER), "working directory (default current directory)") ("config-file,c", boost::program_options::value(&OPT::strConfigFileName)->default_value(APPNAME _T(".cfg")), "file name containing program options") - ("archive-type", boost::program_options::value(&OPT::nArchiveType)->default_value(2), "project archive type: 0-text, 1-binary, 2-compressed binary") + ("archive-type", boost::program_options::value(&OPT::nArchiveType)->default_value(ARCHIVE_DEFAULT), "project archive type: 0-text, 1-binary, 2-compressed binary") ("process-priority", boost::program_options::value(&OPT::nProcessPriority)->default_value(-1), "process priority (below normal by default)") ("max-threads", boost::program_options::value(&OPT::nMaxThreads)->default_value(0), "maximum number of threads (0 for using all available cores)") #if TD_VERBOSE != TD_VERBOSE_OFF @@ -125,7 +137,7 @@ bool Initialize(size_t argc, LPCTSTR* argv) // print application details: version and command line Util::LogBuild(); - LOG(_T("Command line:%s"), Util::CommandLineToString(argc, argv).c_str()); + LOG(_T("Command line: ") APPNAME _T("%s"), Util::CommandLineToString(argc, argv).c_str()); // validate input Util::ensureValidPath(OPT::strInputFileName); @@ -145,38 +157,34 @@ bool Initialize(size_t argc, LPCTSTR* argv) Util::ensureUnifySlash(OPT::strOutputFileName); Util::ensureUnifySlash(OPT::strOutputImageFolder); Util::ensureFolderSlash(OPT::strOutputImageFolder); - if (OPT::strOutputFileName.IsEmpty()) - OPT::strOutputFileName = Util::getFileFullName(OPT::strInputFileName) + MVS_EXT; - - // initialize global options - Process::setCurrentProcessPriority((Process::Priority)OPT::nProcessPriority); - #ifdef _USE_OPENMP - if (OPT::nMaxThreads != 0) - omp_set_num_threads(OPT::nMaxThreads); - #endif - - #ifdef _USE_BREAKPAD - // start memory dumper - MiniDumper::Create(APPNAME, WORKING_FOLDER); - #endif + const String strInputFileNameExt(Util::getFileExt(OPT::strInputFileName).ToLower()); + OPT::bFromOpenMVS = (strInputFileNameExt == MVS_EXT); + if (OPT::bFromOpenMVS) { + if (OPT::strOutputFileName.empty()) + OPT::strOutputFileName = Util::getFilePath(OPT::strInputFileName); + } else { + if (OPT::strOutputFileName.empty()) + OPT::strOutputFileName = Util::getFilePath(OPT::strInputFileName) + _T("scene") MVS_EXT; + else + OPT::strOutputImageFolder = Util::getRelativePath(Util::getFilePath(OPT::strOutputFileName), Util::getFilePath(OPT::strInputFileName)+OPT::strOutputImageFolder); + } - Util::Init(); + MVS::Initialize(APPNAME, OPT::nMaxThreads, OPT::nProcessPriority); return true; } // finalize application instance -void Finalize() +void Application::Finalize() { - #if TD_VERBOSE != TD_VERBOSE_OFF - // print memory statistics - Util::LogMemoryInfo(); - #endif + MVS::Finalize(); CLOSE_LOGFILE(); CLOSE_LOGCONSOLE(); CLOSE_LOG(); } +} // unnamed namespace + #define PBA_PRECISION float namespace PBA { @@ -256,9 +264,9 @@ void UndistortImage(const Camera& camera, const REAL& k1, const Image8U3 imgIn, for (int u=0; u cameras; + std::vector vertices; + std::vector measurements; // the array of 2D projections (only inliers) + std::vector correspondingPoint; // 3D point index corresponding to each 2D projection + std::vector correspondingView; // and camera index + std::vector names; + std::vector ptc; + cameras.reserve(scene.images.size()); + names.reserve(scene.images.size()); + MVS::IIndexArr mapIdx(scene.images.size()); + bool bFocalWarning(false), bPrincipalpointWarning(false); + FOREACH(idx, scene.images) { + const MVS::Image& image = scene.images[idx]; + if (!image.IsValid()) { + mapIdx[idx] = NO_ID; + continue; + } + if (!bFocalWarning && !ISEQUAL(image.camera.K(0, 0), image.camera.K(1, 1))) { + DEBUG("warning: fx != fy and NVM format does not support it"); + bFocalWarning = true; + } + if (!bPrincipalpointWarning && (!ISEQUAL(REAL(image.width-1)*0.5, image.camera.K(0, 2)) || !ISEQUAL(REAL(image.height-1)*0.5, image.camera.K(1, 2)))) { + DEBUG("warning: cx, cy are not the image center and NVM format does not support it"); + bPrincipalpointWarning = true; + } + PBA::Camera cameraNVM; + cameraNVM.SetFocalLength((image.camera.K(0, 0) + image.camera.K(1, 1)) * 0.5); + cameraNVM.SetMatrixRotation(image.camera.R.val); + cameraNVM.SetCameraCenterAfterRotation(image.camera.C.ptr()); + mapIdx[idx] = static_cast(cameras.size()); + cameras.emplace_back(cameraNVM); + names.emplace_back(MAKE_PATH_REL(WORKING_FOLDER_FULL, image.name)); + } + vertices.reserve(scene.pointcloud.points.size()); + measurements.reserve(scene.pointcloud.pointViews.size()); + correspondingPoint.reserve(scene.pointcloud.pointViews.size()); + correspondingView.reserve(scene.pointcloud.pointViews.size()); + FOREACH(idx, scene.pointcloud.points) { + const MVS::PointCloud::Point& X = scene.pointcloud.points[idx]; + const MVS::PointCloud::ViewArr& views = scene.pointcloud.pointViews[idx]; + const size_t prevMeasurements(measurements.size()); + for (MVS::IIndex idxView: views) { + const MVS::Image& image = scene.images[idxView]; + const Point2f pt(image.camera.TransformPointW2I(Cast(X))); + if (pt.x < 0 || pt.y < 0 || pt.x > image.width-1 || pt.y > image.height-1) + continue; + measurements.emplace_back(pt.x, pt.y); + correspondingView.emplace_back(static_cast(mapIdx[idxView])); + correspondingPoint.emplace_back(static_cast(vertices.size())); + } + if (prevMeasurements < measurements.size()) + vertices.emplace_back(PBA::Point3D{X.x, X.y, X.z}); + } + if (!scene.pointcloud.colors.empty()) { + ptc.reserve(scene.pointcloud.colors.size()*3); + FOREACH(idx, scene.pointcloud.points) { + const MVS::PointCloud::Color& c = scene.pointcloud.colors[idx]; + ptc.emplace_back(c.r); + ptc.emplace_back(c.g); + ptc.emplace_back(c.b); + } + } + PBA::SaveModelFile(MAKE_PATH_SAFE(OPT::strOutputFileName), cameras, vertices, measurements, correspondingPoint, correspondingView, names, ptc); + + VERBOSE("Input data exported: %u images & %u points (%s)", scene.images.size(), scene.pointcloud.GetSize(), TD_TIMER_GET_FMT().c_str()); + return true; +} + + +bool ImportSceneVSFM() { TD_TIMER_START(); @@ -315,11 +402,7 @@ int ImportSceneVSFM() camera.R = RMatrix::IDENTITY; camera.C = CMatrix::ZERO; // normalize camera intrinsics - const REAL fScale(REAL(1)/MVS::Camera::GetNormalizationScale(image.width, image.height)); - camera.K(0,0) *= fScale; - camera.K(1,1) *= fScale; - camera.K(0,2) *= fScale; - camera.K(1,2) *= fScale; + camera.K = camera.GetScaledK(REAL(1)/MVS::Camera::GetNormalizationScale(image.width, image.height)); // set pose image.poseID = platform.poses.GetSize(); MVS::Platform::Pose& pose = platform.poses.AddEmpty(); @@ -333,16 +416,16 @@ int ImportSceneVSFM() const PBA::Point3D& X = vertices[idx]; scene.pointcloud.points.AddConstruct(X.xyz[0], X.xyz[1], X.xyz[2]); } - if (ptc.size() == vertices.size()*3) { - scene.pointcloud.colors.Reserve(ptc.size()); - for (size_t idx=0; idx& camera_data, std::vector

& c camidx.push_back(cidx); //camera index ptidx.push_back(i); //point index - //add a measurment to the vector + //add a measurement to the vector measurements.push_back(Point2D(imx, -imy)); nproj ++; } @@ -638,7 +638,7 @@ bool RemoveInvisiblePoints( std::vector& camera_data, std::vector cv(camera_data.size(), 0); //should any cameras be removed ? - int min_observation = 20; //cameras should see at leat 20 points + int min_observation = 20; //cameras should see at least 20 points do { diff --git a/apps/ReconstructMesh/CMakeLists.txt b/apps/ReconstructMesh/CMakeLists.txt index 86cac3de4..4b6aac728 100644 --- a/apps/ReconstructMesh/CMakeLists.txt +++ b/apps/ReconstructMesh/CMakeLists.txt @@ -5,7 +5,7 @@ else() endif() FILE(GLOB LIBRARY_FILES_H "*.h" "*.inl") -cxx_executable_with_flags_no_pch(ReconstructMesh "Apps" "${cxx_default}" "MVS;${OpenMVS_EXTRA_LIBS}" ${LIBRARY_FILES_C} ${LIBRARY_FILES_H}) +cxx_executable_with_flags(ReconstructMesh "Apps" "${cxx_default}" "MVS;${OpenMVS_EXTRA_LIBS}" ${LIBRARY_FILES_C} ${LIBRARY_FILES_H}) # Install INSTALL(TARGETS ReconstructMesh diff --git a/apps/ReconstructMesh/ReconstructMesh.cpp b/apps/ReconstructMesh/ReconstructMesh.cpp index 8651715e0..7d3e9853a 100644 --- a/apps/ReconstructMesh/ReconstructMesh.cpp +++ b/apps/ReconstructMesh/ReconstructMesh.cpp @@ -48,21 +48,32 @@ using namespace MVS; // S T R U C T S /////////////////////////////////////////////////// +namespace { + namespace OPT { String strInputFileName; +String strPointCloudFileName; String strOutputFileName; String strMeshFileName; +String strImportROIFileName; +String strImagePointsFileName; bool bMeshExport; float fDistInsert; +bool bUseOnlyROI; bool bUseConstantWeight; bool bUseFreeSpaceSupport; float fThicknessFactor; float fQualityFactor; float fDecimateMesh; +unsigned nTargetFaceNum; float fRemoveSpurious; bool bRemoveSpikes; unsigned nCloseHoles; unsigned nSmoothMesh; +float fEdgeLength; +bool bCrop2ROI; +float fBorderROI; +float fSplitMaxArea; unsigned nArchiveType; int nProcessPriority; unsigned nMaxThreads; @@ -71,8 +82,17 @@ String strConfigFileName; boost::program_options::variables_map vm; } // namespace OPT +class Application { +public: + Application() {} + ~Application() { Finalize(); } + + bool Initialize(size_t argc, LPCTSTR* argv); + void Finalize(); +}; // Application + // initialize and parse the command line parameters -bool Initialize(size_t argc, LPCTSTR* argv) +bool Application::Initialize(size_t argc, LPCTSTR* argv) { // initialize log and console OPEN_LOG(); @@ -85,11 +105,11 @@ bool Initialize(size_t argc, LPCTSTR* argv) ("working-folder,w", boost::program_options::value(&WORKING_FOLDER), "working directory (default current directory)") ("config-file,c", boost::program_options::value(&OPT::strConfigFileName)->default_value(APPNAME _T(".cfg")), "file name containing program options") ("export-type", boost::program_options::value(&OPT::strExportType)->default_value(_T("ply")), "file type used to export the 3D scene (ply or obj)") - ("archive-type", boost::program_options::value(&OPT::nArchiveType)->default_value(2), "project archive type: 0-text, 1-binary, 2-compressed binary") - ("process-priority", boost::program_options::value(&OPT::nProcessPriority)->default_value(-1), "process priority (below normal by default)") - ("max-threads", boost::program_options::value(&OPT::nMaxThreads)->default_value(0), "maximum number of threads (0 for using all available cores)") + ("archive-type", boost::program_options::value(&OPT::nArchiveType)->default_value(ARCHIVE_MVS), "project archive type: -1-interface, 0-text, 1-binary, 2-compressed binary") + ("process-priority", boost::program_options::value(&OPT::nProcessPriority)->default_value(-1), "process priority (below normal by default)") + ("max-threads", boost::program_options::value(&OPT::nMaxThreads)->default_value(0), "maximum number of threads (0 for using all available cores)") #if TD_VERBOSE != TD_VERBOSE_OFF - ("verbosity,v", boost::program_options::value(&g_nVerbosityLevel)->default_value( + ("verbosity,v", boost::program_options::value(&g_nVerbosityLevel)->default_value( #if TD_VERBOSE == TD_VERBOSE_DEBUG 3 #else @@ -97,26 +117,35 @@ bool Initialize(size_t argc, LPCTSTR* argv) #endif ), "verbosity level") #endif + #ifdef _USE_CUDA + ("cuda-device", boost::program_options::value(&SEACAVE::CUDA::desiredDeviceID)->default_value(-1), "CUDA device number to be used to reconstruct the mesh (-2 - CPU processing, -1 - best GPU, >=0 - device index)") + #endif ; // group of options allowed both on command line and in config file boost::program_options::options_description config_main("Reconstruct options"); config_main.add_options() ("input-file,i", boost::program_options::value(&OPT::strInputFileName), "input filename containing camera poses and image list") + ("pointcloud-file,p", boost::program_options::value(&OPT::strPointCloudFileName), "dense point-cloud with views file name to reconstruct (overwrite existing point-cloud)") ("output-file,o", boost::program_options::value(&OPT::strOutputFileName), "output filename for storing the mesh") - ("min-point-distance,d", boost::program_options::value(&OPT::fDistInsert)->default_value(2.5f), "minimum distance in pixels between the projection of two 3D points to consider them different while triangulating (0 - disabled)") - ("constant-weight", boost::program_options::value(&OPT::bUseConstantWeight)->default_value(true), "considers all view weights 1 instead of the available weight") - ("free-space-support,f", boost::program_options::value(&OPT::bUseFreeSpaceSupport)->default_value(false), "exploits the free-space support in order to reconstruct weakly-represented surfaces") - ("thickness-factor", boost::program_options::value(&OPT::fThicknessFactor)->default_value(1.f), "multiplier adjusting the minimum thickness considered during visibility weighting") - ("quality-factor", boost::program_options::value(&OPT::fQualityFactor)->default_value(1.f), "multiplier adjusting the quality weight considered during graph-cut") + ("min-point-distance,d", boost::program_options::value(&OPT::fDistInsert)->default_value(2.5f), "minimum distance in pixels between the projection of two 3D points to consider them different while triangulating (0 - disabled)") + ("integrate-only-roi", boost::program_options::value(&OPT::bUseOnlyROI)->default_value(false), "use only the points inside the ROI") + ("constant-weight", boost::program_options::value(&OPT::bUseConstantWeight)->default_value(true), "considers all view weights 1 instead of the available weight") + ("free-space-support,f", boost::program_options::value(&OPT::bUseFreeSpaceSupport)->default_value(false), "exploits the free-space support in order to reconstruct weakly-represented surfaces") + ("thickness-factor", boost::program_options::value(&OPT::fThicknessFactor)->default_value(1.f), "multiplier adjusting the minimum thickness considered during visibility weighting") + ("quality-factor", boost::program_options::value(&OPT::fQualityFactor)->default_value(1.f), "multiplier adjusting the quality weight considered during graph-cut") ; boost::program_options::options_description config_clean("Clean options"); config_clean.add_options() - ("decimate", boost::program_options::value(&OPT::fDecimateMesh)->default_value(1.f), "decimation factor in range (0..1] to be applied to the reconstructed surface (1 - disabled)") - ("remove-spurious", boost::program_options::value(&OPT::fRemoveSpurious)->default_value(20.f), "spurious factor for removing faces with too long edges or isolated components (0 - disabled)") - ("remove-spikes", boost::program_options::value(&OPT::bRemoveSpikes)->default_value(true), "flag controlling the removal of spike faces") - ("close-holes", boost::program_options::value(&OPT::nCloseHoles)->default_value(30), "try to close small holes in the reconstructed surface (0 - disabled)") - ("smooth", boost::program_options::value(&OPT::nSmoothMesh)->default_value(2), "number of iterations to smooth the reconstructed surface (0 - disabled)") + ("decimate", boost::program_options::value(&OPT::fDecimateMesh)->default_value(1.f), "decimation factor in range (0..1] to be applied to the reconstructed surface (1 - disabled)") + ("target-face-num", boost::program_options::value(&OPT::nTargetFaceNum)->default_value(0), "target number of faces to be applied to the reconstructed surface. (0 - disabled)") + ("remove-spurious", boost::program_options::value(&OPT::fRemoveSpurious)->default_value(20.f), "spurious factor for removing faces with too long edges or isolated components (0 - disabled)") + ("remove-spikes", boost::program_options::value(&OPT::bRemoveSpikes)->default_value(true), "flag controlling the removal of spike faces") + ("close-holes", boost::program_options::value(&OPT::nCloseHoles)->default_value(30), "try to close small holes in the reconstructed surface (0 - disabled)") + ("smooth", boost::program_options::value(&OPT::nSmoothMesh)->default_value(2), "number of iterations to smooth the reconstructed surface (0 - disabled)") + ("edge-length", boost::program_options::value(&OPT::fEdgeLength)->default_value(0.f), "remesh such that the average edge length is this size (0 - disabled)") + ("roi-border", boost::program_options::value(&OPT::fBorderROI)->default_value(0), "add a border to the region-of-interest when cropping the scene (0 - disabled, >0 - percentage, <0 - absolute)") + ("crop-to-roi", boost::program_options::value(&OPT::bCrop2ROI)->default_value(true), "crop scene using the region-of-interest") ; // hidden options, allowed both on command line and @@ -124,7 +153,10 @@ bool Initialize(size_t argc, LPCTSTR* argv) boost::program_options::options_description hidden("Hidden options"); hidden.add_options() ("mesh-file", boost::program_options::value(&OPT::strMeshFileName), "mesh file name to clean (skips the reconstruction step)") - ("mesh-export", boost::program_options::value(&OPT::bMeshExport)->default_value(false), "just export the mesh contained in loaded project") + ("mesh-export", boost::program_options::value(&OPT::bMeshExport)->default_value(false), "just export the mesh contained in loaded project") + ("split-max-area", boost::program_options::value(&OPT::fSplitMaxArea)->default_value(0.f), "maximum surface area that a sub-mesh can contain (0 - disabled)") + ("import-roi-file", boost::program_options::value(&OPT::strImportROIFileName), "ROI file name to be imported into the scene") + ("image-points-file", boost::program_options::value(&OPT::strImagePointsFileName), "input filename containing the list of points from an image to project on the mesh (optional)") ; boost::program_options::options_description cmdline_options; @@ -158,55 +190,149 @@ bool Initialize(size_t argc, LPCTSTR* argv) // print application details: version and command line Util::LogBuild(); - LOG(_T("Command line:%s"), Util::CommandLineToString(argc, argv).c_str()); + LOG(_T("Command line: ") APPNAME _T("%s"), Util::CommandLineToString(argc, argv).c_str()); // validate input Util::ensureValidPath(OPT::strInputFileName); - Util::ensureUnifySlash(OPT::strInputFileName); - if (OPT::vm.count("help") || OPT::strInputFileName.IsEmpty()) { + if (OPT::vm.count("help") || OPT::strInputFileName.empty()) { boost::program_options::options_description visible("Available options"); visible.add(generic).add(config_main).add(config_clean); GET_LOG() << visible; } - if (OPT::strInputFileName.IsEmpty()) + if (OPT::strInputFileName.empty()) return false; OPT::strExportType = OPT::strExportType.ToLower() == _T("obj") ? _T(".obj") : _T(".ply"); // initialize optional options + Util::ensureValidPath(OPT::strPointCloudFileName); Util::ensureValidPath(OPT::strOutputFileName); - Util::ensureUnifySlash(OPT::strOutputFileName); - if (OPT::strOutputFileName.IsEmpty()) + Util::ensureValidPath(OPT::strImportROIFileName); + Util::ensureValidPath(OPT::strImagePointsFileName); + Util::ensureValidPath(OPT::strMeshFileName); + if (OPT::strPointCloudFileName.empty() && (ARCHIVE_TYPE)OPT::nArchiveType == ARCHIVE_MVS) + OPT::strPointCloudFileName = Util::getFileFullName(OPT::strInputFileName) + _T(".ply"); + if (OPT::strOutputFileName.empty()) OPT::strOutputFileName = Util::getFileFullName(OPT::strInputFileName) + _T("_mesh.mvs"); - // initialize global options - Process::setCurrentProcessPriority((Process::Priority)OPT::nProcessPriority); - #ifdef _USE_OPENMP - if (OPT::nMaxThreads != 0) - omp_set_num_threads(OPT::nMaxThreads); - #endif - - #ifdef _USE_BREAKPAD - // start memory dumper - MiniDumper::Create(APPNAME, WORKING_FOLDER); - #endif - - Util::Init(); + MVS::Initialize(APPNAME, OPT::nMaxThreads, OPT::nProcessPriority); return true; } // finalize application instance -void Finalize() +void Application::Finalize() { - #if TD_VERBOSE != TD_VERBOSE_OFF - // print memory statistics - Util::LogMemoryInfo(); - #endif + MVS::Finalize(); CLOSE_LOGFILE(); CLOSE_LOGCONSOLE(); CLOSE_LOG(); } +} // unnamed namespace + + +// export 3D coordinates corresponding to 2D coordinates provided by inputFileName: +// parse image point list; first line is the name of the image to project, +// each consequent line store the xy coordinates to project: +// +// +// +// ... +// +// for example: +// N01.JPG 3 +// 3090 2680 +// 3600 2100 +// 3640 2190 +bool Export3DProjections(Scene& scene, const String& inputFileName) { + SML smlPointList(_T("ImagePoints")); + smlPointList.Load(inputFileName); + ASSERT(smlPointList.GetArrChildren().size() <= 1); + IDX idx(0); + + // read image name + size_t argc; + CAutoPtrArr argv; + while (true) { + argv = Util::CommandLineToArgvA(smlPointList.GetValue(idx).val, argc); + if (argc > 0 && argv[0][0] != _T('#')) + break; + if (++idx == smlPointList.size()) + return false; + } + if (argc < 2) + return false; + String imgName(argv[0]); + IIndex imgID(NO_ID); + for (const Image& imageData : scene.images) { + if (!imageData.IsValid()) + continue; + if (imageData.name.substr(imageData.name.size() - imgName.size()) == imgName) { + imgID = imageData.ID; + break; + } + } + if (imgID == NO_ID) { + VERBOSE("Unable to find image named: %s", imgName.c_str()); + return false; + } + + // read image points + std::vector imagePoints; + while (++idx != smlPointList.size()) { + // parse image element + const String& line(smlPointList.GetValue(idx).val); + argv = Util::CommandLineToArgvA(line, argc); + if (argc > 0 && argv[0][0] == _T('#')) + continue; + if (argc < 2) { + VERBOSE("Invalid image coordinates: %s", line.c_str()); + continue; + } + const Point2f pt( + String::FromString(argv[0], -1), + String::FromString(argv[1], -1)); + if (pt.x > 0 && pt.y > 0) + imagePoints.emplace_back(pt); + } + if (imagePoints.empty()) { + VERBOSE("Unable to read image points from: %s", imgName.c_str()); + return false; + } + + // prepare output file + String outFileName(Util::insertBeforeFileExt(inputFileName, "_3D")); + File oStream(outFileName, File::WRITE, File::CREATE | File::TRUNCATE); + if (!oStream.isOpen()) { + VERBOSE("Unable to open output file: %s", outFileName.c_str()); + return false; + } + + // print image name + oStream.print("%s %u\n", imgName.c_str(), imagePoints.size()); + + // init mesh octree + const Mesh::Octree octree(scene.mesh.vertices, [](Mesh::Octree::IDX_TYPE size, Mesh::Octree::Type /*radius*/) { + return size > 256; + }); + scene.mesh.ListIncidentFaces(); + + // save 3D coord in the output file + const Image& imgToExport = scene.images[imgID]; + for (const Point2f& pt : imagePoints) { + // define ray from camera center to each x,y image coord + const Ray3 ray(imgToExport.camera.C, normalized(imgToExport.camera.RayPoint(pt))); + // find ray intersection with the mesh + const IntersectRayMesh intRay(octree, ray, scene.mesh); + if (intRay.pick.IsValid()) { + const Point3d ptHit(ray.GetPoint(intRay.pick.dist)); + oStream.print("%.7f %.7f %.7f\n", ptHit.x, ptHit.y, ptHit.z); + } else + oStream.print("NA\n"); + } + return true; +} + int main(int argc, LPCTSTR* argv) { #ifdef _DEBUGINFO @@ -214,13 +340,57 @@ int main(int argc, LPCTSTR* argv) _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);// | _CRTDBG_CHECK_ALWAYS_DF); #endif - if (!Initialize(argc, argv)) + Application application; + if (!application.Initialize(argc, argv)) return EXIT_FAILURE; Scene scene(OPT::nMaxThreads); // load project - if (!scene.Load(MAKE_PATH_SAFE(OPT::strInputFileName))) + const Scene::SCENE_TYPE sceneType(scene.Load(MAKE_PATH_SAFE(OPT::strInputFileName), + OPT::fSplitMaxArea > 0 || OPT::fDecimateMesh < 1 || OPT::nTargetFaceNum > 0 || !OPT::strImportROIFileName.empty())); + if (sceneType == Scene::SCENE_NA) return EXIT_FAILURE; + if (!OPT::strPointCloudFileName.empty() && (File::isFile(MAKE_PATH_SAFE(OPT::strPointCloudFileName)) ? + !scene.pointcloud.Load(MAKE_PATH_SAFE(OPT::strPointCloudFileName)) : + !scene.pointcloud.IsValid())) { + VERBOSE("error: cannot load point-cloud file"); + return EXIT_FAILURE; + } + if (!OPT::strMeshFileName.empty() && !scene.mesh.Load(MAKE_PATH_SAFE(OPT::strMeshFileName))) { + VERBOSE("error: cannot load mesh file"); + return EXIT_FAILURE; + } + const String baseFileName(MAKE_PATH_SAFE(Util::getFileFullName(OPT::strOutputFileName))); + if (OPT::fSplitMaxArea > 0) { + // split mesh using max-area constraint + Mesh::FacesChunkArr chunks; + if (scene.mesh.Split(chunks, OPT::fSplitMaxArea)) + scene.mesh.Save(chunks, baseFileName); + return EXIT_SUCCESS; + } + + if (!OPT::strImportROIFileName.empty()) { + std::ifstream fs(MAKE_PATH_SAFE(OPT::strImportROIFileName)); + if (!fs) + return EXIT_FAILURE; + fs >> scene.obb; + if (OPT::bCrop2ROI && !scene.mesh.IsEmpty() && !scene.IsValid()) { + TD_TIMER_START(); + const size_t numVertices = scene.mesh.vertices.size(); + const size_t numFaces = scene.mesh.faces.size(); + scene.mesh.RemoveFacesOutside(scene.obb); + VERBOSE("Mesh trimmed to ROI: %u vertices and %u faces removed (%s)", + numVertices-scene.mesh.vertices.size(), numFaces-scene.mesh.faces.size(), TD_TIMER_GET_FMT().c_str()); + scene.mesh.Save(baseFileName+OPT::strExportType); + return EXIT_SUCCESS; + } + } + + if (!OPT::strImagePointsFileName.empty() && !scene.mesh.IsEmpty()) { + Export3DProjections(scene, MAKE_PATH_SAFE(OPT::strImagePointsFileName)); + return EXIT_SUCCESS; + } + if (OPT::bMeshExport) { // check there is a mesh to export if (scene.mesh.IsEmpty()) @@ -230,16 +400,21 @@ int main(int argc, LPCTSTR* argv) scene.mesh.Save(fileName); #if TD_VERBOSE != TD_VERBOSE_OFF if (VERBOSITY_LEVEL > 2) - scene.ExportCamerasMLP(Util::getFileFullName(OPT::strOutputFileName)+_T(".mlp"), fileName); + scene.ExportCamerasMLP(baseFileName+_T(".mlp"), fileName); #endif } else { - if (OPT::strMeshFileName.IsEmpty()) { + const OBB3f initialOBB(scene.obb); + if (OPT::fBorderROI > 0) + scene.obb.EnlargePercent(OPT::fBorderROI); + else if (OPT::fBorderROI < 0) + scene.obb.Enlarge(-OPT::fBorderROI); + if (OPT::strMeshFileName.empty() && scene.mesh.IsEmpty()) { // reset image resolution to the original size and // make sure the image neighbors are initialized before deleting the point-cloud #ifdef RECMESH_USE_OPENMP bool bAbort(false); #pragma omp parallel for - for (int_t idx=0; idx<(int_t)scene.images.GetSize(); ++idx) { + for (int_t idx=0; idx<(int_t)scene.images.size(); ++idx) { #pragma omp flush (bAbort) if (bAbort) continue; @@ -262,7 +437,7 @@ int main(int argc, LPCTSTR* argv) } imageData.UpdateCamera(scene.platforms); // select neighbor views - if (imageData.neighbors.IsEmpty()) { + if (imageData.neighbors.empty()) { IndexArr points; scene.SelectNeighborViews(idxImage, points); } @@ -275,36 +450,49 @@ int main(int argc, LPCTSTR* argv) TD_TIMER_START(); if (OPT::bUseConstantWeight) scene.pointcloud.pointWeights.Release(); - if (!scene.ReconstructMesh(OPT::fDistInsert, OPT::bUseFreeSpaceSupport, 4, OPT::fThicknessFactor, OPT::fQualityFactor)) + if (!scene.ReconstructMesh(OPT::fDistInsert, OPT::bUseFreeSpaceSupport, OPT::bUseOnlyROI, 4, OPT::fThicknessFactor, OPT::fQualityFactor)) return EXIT_FAILURE; VERBOSE("Mesh reconstruction completed: %u vertices, %u faces (%s)", scene.mesh.vertices.GetSize(), scene.mesh.faces.GetSize(), TD_TIMER_GET_FMT().c_str()); #if TD_VERBOSE != TD_VERBOSE_OFF if (VERBOSITY_LEVEL > 2) { // dump raw mesh - scene.mesh.Save(MAKE_PATH_SAFE(Util::getFileFullName(OPT::strOutputFileName))+_T("_raw")+OPT::strExportType); + scene.mesh.Save(baseFileName+_T("_raw")+OPT::strExportType); } #endif - } else { + } else if (!OPT::strMeshFileName.empty()) { // load existing mesh to clean scene.mesh.Load(MAKE_PATH_SAFE(OPT::strMeshFileName)); } // clean the mesh - scene.mesh.Clean(OPT::fDecimateMesh, OPT::fRemoveSpurious, OPT::bRemoveSpikes, OPT::nCloseHoles, OPT::nSmoothMesh, false); - scene.mesh.Clean(1.f, 0.f, OPT::bRemoveSpikes, OPT::nCloseHoles, 0, false); // extra cleaning trying to close more holes - scene.mesh.Clean(1.f, 0.f, false, 0, 0, true); // extra cleaning to remove non-manifold problems created by closing holes + if (OPT::bCrop2ROI && scene.IsBounded()) { + TD_TIMER_START(); + const size_t numVertices = scene.mesh.vertices.size(); + const size_t numFaces = scene.mesh.faces.size(); + scene.mesh.RemoveFacesOutside(scene.obb); + VERBOSE("Mesh trimmed to ROI: %u vertices and %u faces removed (%s)", + numVertices-scene.mesh.vertices.size(), numFaces-scene.mesh.faces.size(), TD_TIMER_GET_FMT().c_str()); + } + const float fDecimate(OPT::nTargetFaceNum ? static_cast(OPT::nTargetFaceNum) / scene.mesh.faces.size() : OPT::fDecimateMesh); + scene.mesh.Clean(fDecimate, OPT::fRemoveSpurious, OPT::bRemoveSpikes, OPT::nCloseHoles, OPT::nSmoothMesh, OPT::fEdgeLength, false); + scene.mesh.Clean(1.f, 0.f, OPT::bRemoveSpikes, OPT::nCloseHoles, 0u, 0.f, false); // extra cleaning trying to close more holes + scene.mesh.Clean(1.f, 0.f, false, 0u, 0u, 0.f, true); // extra cleaning to remove non-manifold problems created by closing holes + scene.obb = initialOBB; // save the final mesh - const String baseFileName(MAKE_PATH_SAFE(Util::getFileFullName(OPT::strOutputFileName))); - scene.Save(baseFileName+_T(".mvs"), (ARCHIVE_TYPE)OPT::nArchiveType); scene.mesh.Save(baseFileName+OPT::strExportType); #if TD_VERBOSE != TD_VERBOSE_OFF if (VERBOSITY_LEVEL > 2) scene.ExportCamerasMLP(baseFileName+_T(".mlp"), baseFileName+OPT::strExportType); #endif + if ((ARCHIVE_TYPE)OPT::nArchiveType != ARCHIVE_MVS || sceneType != Scene::SCENE_INTERFACE) + scene.Save(baseFileName+_T(".mvs"), (ARCHIVE_TYPE)OPT::nArchiveType); } - Finalize(); + if (!OPT::strImagePointsFileName.empty()) { + Export3DProjections(scene, MAKE_PATH_SAFE(OPT::strImagePointsFileName)); + return EXIT_SUCCESS; + } return EXIT_SUCCESS; } /*----------------------------------------------------------------*/ diff --git a/apps/RefineMesh/CMakeLists.txt b/apps/RefineMesh/CMakeLists.txt index eaffec244..26a6f84ed 100644 --- a/apps/RefineMesh/CMakeLists.txt +++ b/apps/RefineMesh/CMakeLists.txt @@ -5,7 +5,7 @@ else() endif() FILE(GLOB LIBRARY_FILES_H "*.h" "*.inl") -cxx_executable_with_flags_no_pch(RefineMesh "Apps" "${cxx_default}" "MVS;${OpenMVS_EXTRA_LIBS}" ${LIBRARY_FILES_C} ${LIBRARY_FILES_H}) +cxx_executable_with_flags(RefineMesh "Apps" "${cxx_default}" "MVS;${OpenMVS_EXTRA_LIBS}" ${LIBRARY_FILES_C} ${LIBRARY_FILES_H}) # Install INSTALL(TARGETS RefineMesh diff --git a/apps/RefineMesh/RefineMesh.cpp b/apps/RefineMesh/RefineMesh.cpp index 1cbd6e227..88c2dc05b 100644 --- a/apps/RefineMesh/RefineMesh.cpp +++ b/apps/RefineMesh/RefineMesh.cpp @@ -43,10 +43,12 @@ using namespace MVS; // S T R U C T S /////////////////////////////////////////////////// +namespace { + namespace OPT { String strInputFileName; -String strOutputFileName; String strMeshFileName; +String strOutputFileName; unsigned nResolutionLevel; unsigned nMinResolution; unsigned nMaxViews; @@ -62,9 +64,6 @@ float fRatioRigidityElasticity; unsigned nMaxFaceArea; float fPlanarVertexRatio; float fGradientStep; -#ifdef _USE_CUDA -bool bUseCUDA; -#endif unsigned nArchiveType; int nProcessPriority; unsigned nMaxThreads; @@ -73,8 +72,17 @@ String strConfigFileName; boost::program_options::variables_map vm; } // namespace OPT +class Application { +public: + Application() {} + ~Application() { Finalize(); } + + bool Initialize(size_t argc, LPCTSTR* argv); + void Finalize(); +}; // Application + // initialize and parse the command line parameters -bool Initialize(size_t argc, LPCTSTR* argv) +bool Application::Initialize(size_t argc, LPCTSTR* argv) { // initialize log and console OPEN_LOG(); @@ -87,11 +95,11 @@ bool Initialize(size_t argc, LPCTSTR* argv) ("working-folder,w", boost::program_options::value(&WORKING_FOLDER), "working directory (default current directory)") ("config-file,c", boost::program_options::value(&OPT::strConfigFileName)->default_value(APPNAME _T(".cfg")), "file name containing program options") ("export-type", boost::program_options::value(&OPT::strExportType)->default_value(_T("ply")), "file type used to export the 3D scene (ply or obj)") - ("archive-type", boost::program_options::value(&OPT::nArchiveType)->default_value(2), "project archive type: 0-text, 1-binary, 2-compressed binary") - ("process-priority", boost::program_options::value(&OPT::nProcessPriority)->default_value(-1), "process priority (below normal by default)") - ("max-threads", boost::program_options::value(&OPT::nMaxThreads)->default_value(0), "maximum number of threads (0 for using all available cores)") + ("archive-type", boost::program_options::value(&OPT::nArchiveType)->default_value(ARCHIVE_MVS), "project archive type: -1-interface, 0-text, 1-binary, 2-compressed binary") + ("process-priority", boost::program_options::value(&OPT::nProcessPriority)->default_value(-1), "process priority (below normal by default)") + ("max-threads", boost::program_options::value(&OPT::nMaxThreads)->default_value(0), "maximum number of threads (0 for using all available cores)") #if TD_VERBOSE != TD_VERBOSE_OFF - ("verbosity,v", boost::program_options::value(&g_nVerbosityLevel)->default_value( + ("verbosity,v", boost::program_options::value(&g_nVerbosityLevel)->default_value( #if TD_VERBOSE == TD_VERBOSE_DEBUG 3 #else @@ -99,45 +107,39 @@ bool Initialize(size_t argc, LPCTSTR* argv) #endif ), "verbosity level") #endif + #ifdef _USE_CUDA + ("cuda-device", boost::program_options::value(&SEACAVE::CUDA::desiredDeviceID)->default_value(-2), "CUDA device number to be used for mesh refinement (-2 - CPU processing, -1 - best GPU, >=0 - device index)") + #endif ; // group of options allowed both on command line and in config file boost::program_options::options_description config("Refine options"); config.add_options() ("input-file,i", boost::program_options::value(&OPT::strInputFileName), "input filename containing camera poses and image list") + ("mesh-file,m", boost::program_options::value(&OPT::strMeshFileName), "mesh file name to refine (overwrite existing mesh)") ("output-file,o", boost::program_options::value(&OPT::strOutputFileName), "output filename for storing the mesh") - ("resolution-level", boost::program_options::value(&OPT::nResolutionLevel)->default_value(0), "how many times to scale down the images before mesh refinement") - ("min-resolution", boost::program_options::value(&OPT::nMinResolution)->default_value(640), "do not scale images lower than this resolution") - ("max-views", boost::program_options::value(&OPT::nMaxViews)->default_value(8), "maximum number of neighbor images used to refine the mesh") - ("decimate", boost::program_options::value(&OPT::fDecimateMesh)->default_value(0.f), "decimation factor in range [0..1] to be applied to the input surface before refinement (0 - auto, 1 - disabled)") - ("close-holes", boost::program_options::value(&OPT::nCloseHoles)->default_value(30), "try to close small holes in the input surface (0 - disabled)") - ("ensure-edge-size", boost::program_options::value(&OPT::nEnsureEdgeSize)->default_value(1), "ensure edge size and improve vertex valence of the input surface (0 - disabled, 1 - auto, 2 - force)") - ("max-face-area", boost::program_options::value(&OPT::nMaxFaceArea)->default_value(64), "maximum face area projected in any pair of images that is not subdivided (0 - disabled)") - ("scales", boost::program_options::value(&OPT::nScales)->default_value(3), "how many iterations to run mesh optimization on multi-scale images") - ("scale-step", boost::program_options::value(&OPT::fScaleStep)->default_value(0.5f), "image scale factor used at each mesh optimization step") - ("reduce-memory", boost::program_options::value(&OPT::nReduceMemory)->default_value(1), "recompute some data in order to reduce memory requirements") - ("alternate-pair", boost::program_options::value(&OPT::nAlternatePair)->default_value(0), "refine mesh using an image pair alternatively as reference (0 - both, 1 - alternate, 2 - only left, 3 - only right)") - ("regularity-weight", boost::program_options::value(&OPT::fRegularityWeight)->default_value(0.2f), "scalar regularity weight to balance between photo-consistency and regularization terms during mesh optimization") - ("rigidity-elasticity-ratio", boost::program_options::value(&OPT::fRatioRigidityElasticity)->default_value(0.9f), "scalar ratio used to compute the regularity gradient as a combination of rigidity and elasticity") - ("gradient-step", boost::program_options::value(&OPT::fGradientStep)->default_value(45.05f), "gradient step to be used instead (0 - auto)") - ("planar-vertex-ratio", boost::program_options::value(&OPT::fPlanarVertexRatio)->default_value(0.f), "threshold used to remove vertices on planar patches (0 - disabled)") - #ifdef _USE_CUDA - ("use-cuda", boost::program_options::value(&OPT::bUseCUDA)->default_value(true), "refine mesh using CUDA") - #endif - ; - - // hidden options, allowed both on command line and - // in config file, but will not be shown to the user - boost::program_options::options_description hidden("Hidden options"); - hidden.add_options() - ("mesh-file", boost::program_options::value(&OPT::strMeshFileName), "mesh file name to refine (overwrite the existing mesh)") + ("resolution-level", boost::program_options::value(&OPT::nResolutionLevel)->default_value(0), "how many times to scale down the images before mesh refinement") + ("min-resolution", boost::program_options::value(&OPT::nMinResolution)->default_value(640), "do not scale images lower than this resolution") + ("max-views", boost::program_options::value(&OPT::nMaxViews)->default_value(8), "maximum number of neighbor images used to refine the mesh") + ("decimate", boost::program_options::value(&OPT::fDecimateMesh)->default_value(0.f), "decimation factor in range [0..1] to be applied to the input surface before refinement (0 - auto, 1 - disabled)") + ("close-holes", boost::program_options::value(&OPT::nCloseHoles)->default_value(30), "try to close small holes in the input surface (0 - disabled)") + ("ensure-edge-size", boost::program_options::value(&OPT::nEnsureEdgeSize)->default_value(1), "ensure edge size and improve vertex valence of the input surface (0 - disabled, 1 - auto, 2 - force)") + ("max-face-area", boost::program_options::value(&OPT::nMaxFaceArea)->default_value(32), "maximum face area projected in any pair of images that is not subdivided (0 - disabled)") + ("scales", boost::program_options::value(&OPT::nScales)->default_value(2), "how many iterations to run mesh optimization on multi-scale images") + ("scale-step", boost::program_options::value(&OPT::fScaleStep)->default_value(0.5f), "image scale factor used at each mesh optimization step") + ("alternate-pair", boost::program_options::value(&OPT::nAlternatePair)->default_value(0), "refine mesh using an image pair alternatively as reference (0 - both, 1 - alternate, 2 - only left, 3 - only right)") + ("regularity-weight", boost::program_options::value(&OPT::fRegularityWeight)->default_value(0.2f), "scalar regularity weight to balance between photo-consistency and regularization terms during mesh optimization") + ("rigidity-elasticity-ratio", boost::program_options::value(&OPT::fRatioRigidityElasticity)->default_value(0.9f), "scalar ratio used to compute the regularity gradient as a combination of rigidity and elasticity") + ("gradient-step", boost::program_options::value(&OPT::fGradientStep)->default_value(45.05f), "gradient step to be used instead (0 - auto)") + ("planar-vertex-ratio", boost::program_options::value(&OPT::fPlanarVertexRatio)->default_value(0.f), "threshold used to remove vertices on planar patches (0 - disabled)") + ("reduce-memory", boost::program_options::value(&OPT::nReduceMemory)->default_value(1), "recompute some data in order to reduce memory requirements") ; boost::program_options::options_description cmdline_options; - cmdline_options.add(generic).add(config).add(hidden); + cmdline_options.add(generic).add(config); boost::program_options::options_description config_file_options; - config_file_options.add(config).add(hidden); + config_file_options.add(config); boost::program_options::positional_options_description p; p.add("input-file", -1); @@ -164,55 +166,43 @@ bool Initialize(size_t argc, LPCTSTR* argv) // print application details: version and command line Util::LogBuild(); - LOG(_T("Command line:%s"), Util::CommandLineToString(argc, argv).c_str()); + LOG(_T("Command line: ") APPNAME _T("%s"), Util::CommandLineToString(argc, argv).c_str()); // validate input Util::ensureValidPath(OPT::strInputFileName); - Util::ensureUnifySlash(OPT::strInputFileName); - if (OPT::vm.count("help") || OPT::strInputFileName.IsEmpty()) { + if (OPT::vm.count("help") || OPT::strInputFileName.empty()) { boost::program_options::options_description visible("Available options"); visible.add(generic).add(config); GET_LOG() << visible; } - if (OPT::strInputFileName.IsEmpty()) + if (OPT::strInputFileName.empty()) return false; OPT::strExportType = OPT::strExportType.ToLower() == _T("obj") ? _T(".obj") : _T(".ply"); // initialize optional options + Util::ensureValidPath(OPT::strMeshFileName); Util::ensureValidPath(OPT::strOutputFileName); - Util::ensureUnifySlash(OPT::strOutputFileName); - if (OPT::strOutputFileName.IsEmpty()) + if (OPT::strMeshFileName.empty() && (ARCHIVE_TYPE)OPT::nArchiveType == ARCHIVE_MVS) + OPT::strMeshFileName = Util::getFileFullName(OPT::strInputFileName) + _T(".ply"); + if (OPT::strOutputFileName.empty()) OPT::strOutputFileName = Util::getFileFullName(OPT::strInputFileName) + _T("_refine.mvs"); - // initialize global options - Process::setCurrentProcessPriority((Process::Priority)OPT::nProcessPriority); - #ifdef _USE_OPENMP - if (OPT::nMaxThreads != 0) - omp_set_num_threads(OPT::nMaxThreads); - #endif - - #ifdef _USE_BREAKPAD - // start memory dumper - MiniDumper::Create(APPNAME, WORKING_FOLDER); - #endif - - Util::Init(); + MVS::Initialize(APPNAME, OPT::nMaxThreads, OPT::nProcessPriority); return true; } // finalize application instance -void Finalize() +void Application::Finalize() { - #if TD_VERBOSE != TD_VERBOSE_OFF - // print memory statistics - Util::LogMemoryInfo(); - #endif + MVS::Finalize(); CLOSE_LOGFILE(); CLOSE_LOGCONSOLE(); CLOSE_LOG(); } +} // unnamed namespace + int main(int argc, LPCTSTR* argv) { #ifdef _DEBUGINFO @@ -220,16 +210,18 @@ int main(int argc, LPCTSTR* argv) _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);// | _CRTDBG_CHECK_ALWAYS_DF); #endif - if (!Initialize(argc, argv)) + Application application; + if (!application.Initialize(argc, argv)) return EXIT_FAILURE; Scene scene(OPT::nMaxThreads); // load and refine the coarse mesh - if (!scene.Load(MAKE_PATH_SAFE(OPT::strInputFileName))) + const Scene::SCENE_TYPE sceneType(scene.Load(MAKE_PATH_SAFE(OPT::strInputFileName))); + if (sceneType == Scene::SCENE_NA) + return EXIT_FAILURE; + if (!OPT::strMeshFileName.empty() && !scene.mesh.Load(MAKE_PATH_SAFE(OPT::strMeshFileName))) { + VERBOSE("error: cannot load mesh file"); return EXIT_FAILURE; - if (!OPT::strMeshFileName.IsEmpty()) { - // load given coarse mesh - scene.mesh.Load(MAKE_PATH_SAFE(OPT::strMeshFileName)); } if (scene.mesh.IsEmpty()) { VERBOSE("error: empty initial mesh"); @@ -237,7 +229,7 @@ int main(int argc, LPCTSTR* argv) } TD_TIMER_START(); #ifdef _USE_CUDA - if (!OPT::bUseCUDA || + if (SEACAVE::CUDA::desiredDeviceID < -1 || !scene.RefineMeshCUDA(OPT::nResolutionLevel, OPT::nMinResolution, OPT::nMaxViews, OPT::fDecimateMesh, OPT::nCloseHoles, OPT::nEnsureEdgeSize, OPT::nMaxFaceArea, @@ -251,24 +243,24 @@ int main(int argc, LPCTSTR* argv) OPT::fDecimateMesh, OPT::nCloseHoles, OPT::nEnsureEdgeSize, OPT::nMaxFaceArea, OPT::nScales, OPT::fScaleStep, - OPT::nReduceMemory, OPT::nAlternatePair, + OPT::nAlternatePair, OPT::fRegularityWeight, OPT::fRatioRigidityElasticity, + OPT::fGradientStep, OPT::fPlanarVertexRatio, - OPT::fGradientStep)) + OPT::nReduceMemory)) return EXIT_FAILURE; VERBOSE("Mesh refinement completed: %u vertices, %u faces (%s)", scene.mesh.vertices.GetSize(), scene.mesh.faces.GetSize(), TD_TIMER_GET_FMT().c_str()); // save the final mesh const String baseFileName(MAKE_PATH_SAFE(Util::getFileFullName(OPT::strOutputFileName))); - scene.Save(baseFileName+_T(".mvs"), (ARCHIVE_TYPE)OPT::nArchiveType); scene.mesh.Save(baseFileName+OPT::strExportType); #if TD_VERBOSE != TD_VERBOSE_OFF if (VERBOSITY_LEVEL > 2) scene.ExportCamerasMLP(baseFileName+_T(".mlp"), baseFileName+OPT::strExportType); #endif - - Finalize(); + if ((ARCHIVE_TYPE)OPT::nArchiveType != ARCHIVE_MVS || sceneType != Scene::SCENE_INTERFACE) + scene.Save(baseFileName+_T(".mvs"), (ARCHIVE_TYPE)OPT::nArchiveType); return EXIT_SUCCESS; } /*----------------------------------------------------------------*/ diff --git a/apps/Tests/CMakeLists.txt b/apps/Tests/CMakeLists.txt new file mode 100644 index 000000000..192178b32 --- /dev/null +++ b/apps/Tests/CMakeLists.txt @@ -0,0 +1,15 @@ +if(MSVC) + FILE(GLOB LIBRARY_FILES_C "*.cpp" "*.rc") +else() + FILE(GLOB LIBRARY_FILES_C "*.cpp") +endif() +FILE(GLOB LIBRARY_FILES_H "*.h" "*.inl") + +ADD_DEFINITIONS(-D_DATA_PATH="${CMAKE_CURRENT_SOURCE_DIR}/data/") + +cxx_executable_with_flags(Tests "Apps" "${cxx_default}" "MVS;${OpenMVS_EXTRA_LIBS}" ${LIBRARY_FILES_C} ${LIBRARY_FILES_H}) + +# Install +INSTALL(TARGETS Tests + EXPORT OpenMVSTargets + RUNTIME DESTINATION "${INSTALL_BIN_DIR}" COMPONENT bin) diff --git a/apps/Tests/Tests.cpp b/apps/Tests/Tests.cpp new file mode 100644 index 000000000..ebc730cbd --- /dev/null +++ b/apps/Tests/Tests.cpp @@ -0,0 +1,137 @@ +/* + * Tests.cpp + * + * Copyright (c) 2014-2021 SEACAVE + * + * Author(s): + * + * cDc + * + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * + * Additional Terms: + * + * You are required to preserve legal notices and author attributions in + * that material or in the Appropriate Legal Notices displayed by works + * containing it. + */ + +#include "../../libs/MVS/Common.h" +#include "../../libs/MVS/Scene.h" + +using namespace MVS; + + +// D E F I N E S /////////////////////////////////////////////////// + +#define APPNAME _T("Tests") + + +// S T R U C T S /////////////////////////////////////////////////// + +// test various algorithms independently +bool UnitTests() +{ + TD_TIMER_START(); + if (!SEACAVE::cListTest(100)) { + VERBOSE("ERROR: cListTest failed!"); + return false; + } + if (!SEACAVE::OctreeTest(100)) { + VERBOSE("ERROR: OctreeTest failed!"); + return false; + } + if (!SEACAVE::OctreeTest(100)) { + VERBOSE("ERROR: OctreeTest failed!"); + return false; + } + if (!SEACAVE::TestRayTriangleIntersection(1000)) { + VERBOSE("ERROR: TestRayTriangleIntersection failed!"); + return false; + } + if (!SEACAVE::TestRayTriangleIntersection(1000)) { + VERBOSE("ERROR: TestRayTriangleIntersection failed!"); + return false; + } + VERBOSE("All unit tests passed (%s)", TD_TIMER_GET_FMT().c_str()); + return true; +} + + +// test MVS stages on a small sample dataset +bool PipelineTest(bool verbose=false) +{ + TD_TIMER_START(); + #if 0 && defined(_USE_CUDA) + // force CPU for testing even if CUDA is available + SEACAVE::CUDA::desiredDeviceID = -2; + #endif + Scene scene; + if (!scene.Load(MAKE_PATH("scene.mvs"))) { + VERBOSE("ERROR: TestDataset failed loading the scene!"); + return false; + } + OPTDENSE::init(); + OPTDENSE::bRemoveDmaps = true; + if (!scene.DenseReconstruction() || scene.pointcloud.GetSize() < 50000u) { + VERBOSE("ERROR: TestDataset failed estimating dense point cloud!"); + return false; + } + if (verbose) + scene.pointcloud.Save(MAKE_PATH("scene_dense.ply")); + if (!scene.ReconstructMesh() || scene.mesh.faces.size() < 25000u) { + VERBOSE("ERROR: TestDataset failed reconstructing the mesh!"); + return false; + } + if (verbose) + scene.mesh.Save(MAKE_PATH("scene_dense_mesh.ply")); + constexpr float decimate = 0.7f; + scene.mesh.Clean(decimate); + if (!ISINSIDE(scene.mesh.faces.size(), 20000u, 30000u)) { + VERBOSE("ERROR: TestDataset failed cleaning the mesh!"); + return false; + } + if (!scene.TextureMesh(0, 0) || !scene.mesh.HasTexture()) { + VERBOSE("ERROR: TestDataset failed texturing the mesh!"); + return false; + } + if (verbose) + scene.mesh.Save(MAKE_PATH("scene_dense_mesh_texture.ply")); + VERBOSE("All pipeline stages passed (%s)", TD_TIMER_GET_FMT().c_str()); + return true; +} + +// test OpenMVS functionality +int main(int argc, LPCTSTR* argv) +{ + OPEN_LOG(); + OPEN_LOGCONSOLE(); + MVS::Initialize(APPNAME); + WORKING_FOLDER = _DATA_PATH; + INIT_WORKING_FOLDER; + if (argc < 2 || std::atoi(argv[1]) == 0) { + if (!UnitTests()) + return EXIT_FAILURE; + } else { + if (!PipelineTest()) + return EXIT_FAILURE; + } + MVS::Finalize(); + CLOSE_LOGCONSOLE(); + CLOSE_LOG(); + return EXIT_SUCCESS; +} +/*----------------------------------------------------------------*/ diff --git a/apps/Tests/data/images/00000.jpg b/apps/Tests/data/images/00000.jpg new file mode 100644 index 000000000..2ec67cb9a Binary files /dev/null and b/apps/Tests/data/images/00000.jpg differ diff --git a/apps/Tests/data/images/00001.jpg b/apps/Tests/data/images/00001.jpg new file mode 100644 index 000000000..a5b6f9545 Binary files /dev/null and b/apps/Tests/data/images/00001.jpg differ diff --git a/apps/Tests/data/images/00002.jpg b/apps/Tests/data/images/00002.jpg new file mode 100644 index 000000000..d4caa0bd8 Binary files /dev/null and b/apps/Tests/data/images/00002.jpg differ diff --git a/apps/Tests/data/images/00003.jpg b/apps/Tests/data/images/00003.jpg new file mode 100644 index 000000000..504155048 Binary files /dev/null and b/apps/Tests/data/images/00003.jpg differ diff --git a/apps/Tests/data/scene.mvs b/apps/Tests/data/scene.mvs new file mode 100644 index 000000000..b6bd8f94b Binary files /dev/null and b/apps/Tests/data/scene.mvs differ diff --git a/apps/TextureMesh/CMakeLists.txt b/apps/TextureMesh/CMakeLists.txt index b53e2f560..bef488eeb 100644 --- a/apps/TextureMesh/CMakeLists.txt +++ b/apps/TextureMesh/CMakeLists.txt @@ -5,7 +5,7 @@ else() endif() FILE(GLOB LIBRARY_FILES_H "*.h" "*.inl") -cxx_executable_with_flags_no_pch(TextureMesh "Apps" "${cxx_default}" "MVS;${OpenMVS_EXTRA_LIBS}" ${LIBRARY_FILES_C} ${LIBRARY_FILES_H}) +cxx_executable_with_flags(TextureMesh "Apps" "${cxx_default}" "MVS;${OpenMVS_EXTRA_LIBS}" ${LIBRARY_FILES_C} ${LIBRARY_FILES_H}) # Install INSTALL(TARGETS TextureMesh diff --git a/apps/TextureMesh/TextureMesh.cpp b/apps/TextureMesh/TextureMesh.cpp index 23f1421d1..75cb0f50d 100644 --- a/apps/TextureMesh/TextureMesh.cpp +++ b/apps/TextureMesh/TextureMesh.cpp @@ -43,14 +43,18 @@ using namespace MVS; // S T R U C T S /////////////////////////////////////////////////// +namespace { + namespace OPT { String strInputFileName; -String strOutputFileName; String strMeshFileName; +String strOutputFileName; +String strViewsFileName; float fDecimateMesh; unsigned nCloseHoles; unsigned nResolutionLevel; unsigned nMinResolution; +unsigned minCommonCameras; float fOutlierThreshold; float fRatioDataSmoothness; bool bGlobalSeamLeveling; @@ -58,17 +62,29 @@ bool bLocalSeamLeveling; unsigned nTextureSizeMultiple; unsigned nRectPackingHeuristic; uint32_t nColEmpty; +float fSharpnessWeight; +int nIgnoreMaskLabel; unsigned nOrthoMapResolution; unsigned nArchiveType; int nProcessPriority; unsigned nMaxThreads; +int nMaxTextureSize; String strExportType; String strConfigFileName; boost::program_options::variables_map vm; } // namespace OPT +class Application { +public: + Application() {} + ~Application() { Finalize(); } + + bool Initialize(size_t argc, LPCTSTR* argv); + void Finalize(); +}; // Application + // initialize and parse the command line parameters -bool Initialize(size_t argc, LPCTSTR* argv) +bool Application::Initialize(size_t argc, LPCTSTR* argv) { // initialize log and console OPEN_LOG(); @@ -80,12 +96,12 @@ bool Initialize(size_t argc, LPCTSTR* argv) ("help,h", "produce this help message") ("working-folder,w", boost::program_options::value(&WORKING_FOLDER), "working directory (default current directory)") ("config-file,c", boost::program_options::value(&OPT::strConfigFileName)->default_value(APPNAME _T(".cfg")), "file name containing program options") - ("export-type", boost::program_options::value(&OPT::strExportType)->default_value(_T("ply")), "file type used to export the 3D scene (ply or obj)") - ("archive-type", boost::program_options::value(&OPT::nArchiveType)->default_value(2), "project archive type: 0-text, 1-binary, 2-compressed binary") - ("process-priority", boost::program_options::value(&OPT::nProcessPriority)->default_value(-1), "process priority (below normal by default)") - ("max-threads", boost::program_options::value(&OPT::nMaxThreads)->default_value(0), "maximum number of threads (0 for using all available cores)") + ("export-type", boost::program_options::value(&OPT::strExportType)->default_value(_T("ply")), "file type used to export the 3D scene (ply, obj, glb or gltf)") + ("archive-type", boost::program_options::value(&OPT::nArchiveType)->default_value(ARCHIVE_MVS), "project archive type: -1-interface, 0-text, 1-binary, 2-compressed binary") + ("process-priority", boost::program_options::value(&OPT::nProcessPriority)->default_value(-1), "process priority (below normal by default)") + ("max-threads", boost::program_options::value(&OPT::nMaxThreads)->default_value(0), "maximum number of threads (0 for using all available cores)") #if TD_VERBOSE != TD_VERBOSE_OFF - ("verbosity,v", boost::program_options::value(&g_nVerbosityLevel)->default_value( + ("verbosity,v", boost::program_options::value(&g_nVerbosityLevel)->default_value( #if TD_VERBOSE == TD_VERBOSE_DEBUG 3 #else @@ -93,32 +109,40 @@ bool Initialize(size_t argc, LPCTSTR* argv) #endif ), "verbosity level") #endif + #ifdef _USE_CUDA + ("cuda-device", boost::program_options::value(&SEACAVE::CUDA::desiredDeviceID)->default_value(-1), "CUDA device number to be used to texture the mesh (-2 - CPU processing, -1 - best GPU, >=0 - device index)") + #endif ; // group of options allowed both on command line and in config file boost::program_options::options_description config("Texture options"); config.add_options() ("input-file,i", boost::program_options::value(&OPT::strInputFileName), "input filename containing camera poses and image list") + ("mesh-file,m", boost::program_options::value(&OPT::strMeshFileName), "mesh file name to texture (overwrite existing mesh)") ("output-file,o", boost::program_options::value(&OPT::strOutputFileName), "output filename for storing the mesh") - ("decimate", boost::program_options::value(&OPT::fDecimateMesh)->default_value(1.f), "decimation factor in range [0..1] to be applied to the input surface before refinement (0 - auto, 1 - disabled)") - ("close-holes", boost::program_options::value(&OPT::nCloseHoles)->default_value(30), "try to close small holes in the input surface (0 - disabled)") - ("resolution-level", boost::program_options::value(&OPT::nResolutionLevel)->default_value(0), "how many times to scale down the images before mesh refinement") - ("min-resolution", boost::program_options::value(&OPT::nMinResolution)->default_value(640), "do not scale images lower than this resolution") - ("outlier-threshold", boost::program_options::value(&OPT::fOutlierThreshold)->default_value(6e-2f), "threshold used to find and remove outlier face textures (0 - disabled)") - ("cost-smoothness-ratio", boost::program_options::value(&OPT::fRatioDataSmoothness)->default_value(0.1f), "ratio used to adjust the preference for more compact patches (1 - best quality/worst compactness, ~0 - worst quality/best compactness)") - ("global-seam-leveling", boost::program_options::value(&OPT::bGlobalSeamLeveling)->default_value(true), "generate uniform texture patches using global seam leveling") - ("local-seam-leveling", boost::program_options::value(&OPT::bLocalSeamLeveling)->default_value(true), "generate uniform texture patch borders using local seam leveling") - ("texture-size-multiple", boost::program_options::value(&OPT::nTextureSizeMultiple)->default_value(0), "texture size should be a multiple of this value (0 - power of two)") - ("patch-packing-heuristic", boost::program_options::value(&OPT::nRectPackingHeuristic)->default_value(3), "specify the heuristic used when deciding where to place a new patch (0 - best fit, 3 - good speed, 100 - best speed)") - ("empty-color", boost::program_options::value(&OPT::nColEmpty)->default_value(0x00FF7F27), "color used for faces not covered by any image") - ("orthographic-image-resolution", boost::program_options::value(&OPT::nOrthoMapResolution)->default_value(0), "orthographic image resolution to be generated from the textured mesh - the mesh is expected to be already geo-referenced or at least properly oriented (0 - disabled)") + ("decimate", boost::program_options::value(&OPT::fDecimateMesh)->default_value(1.f), "decimation factor in range [0..1] to be applied to the input surface before refinement (0 - auto, 1 - disabled)") + ("close-holes", boost::program_options::value(&OPT::nCloseHoles)->default_value(30), "try to close small holes in the input surface (0 - disabled)") + ("resolution-level", boost::program_options::value(&OPT::nResolutionLevel)->default_value(0), "how many times to scale down the images before mesh refinement") + ("min-resolution", boost::program_options::value(&OPT::nMinResolution)->default_value(640), "do not scale images lower than this resolution") + ("outlier-threshold", boost::program_options::value(&OPT::fOutlierThreshold)->default_value(6e-2f), "threshold used to find and remove outlier face textures (0 - disabled)") + ("cost-smoothness-ratio", boost::program_options::value(&OPT::fRatioDataSmoothness)->default_value(0.1f), "ratio used to adjust the preference for more compact patches (1 - best quality/worst compactness, ~0 - worst quality/best compactness)") + ("virtual-face-images", boost::program_options::value(&OPT::minCommonCameras)->default_value(0), "generate texture patches using virtual faces composed of coplanar triangles sharing at least this number of views (0 - disabled, 3 - good value)") + ("global-seam-leveling", boost::program_options::value(&OPT::bGlobalSeamLeveling)->default_value(true), "generate uniform texture patches using global seam leveling") + ("local-seam-leveling", boost::program_options::value(&OPT::bLocalSeamLeveling)->default_value(true), "generate uniform texture patch borders using local seam leveling") + ("texture-size-multiple", boost::program_options::value(&OPT::nTextureSizeMultiple)->default_value(0), "texture size should be a multiple of this value (0 - power of two)") + ("patch-packing-heuristic", boost::program_options::value(&OPT::nRectPackingHeuristic)->default_value(3), "specify the heuristic used when deciding where to place a new patch (0 - best fit, 3 - good speed, 100 - best speed)") + ("empty-color", boost::program_options::value(&OPT::nColEmpty)->default_value(0x00FF7F27), "color used for faces not covered by any image") + ("sharpness-weight", boost::program_options::value(&OPT::fSharpnessWeight)->default_value(0.5f), "amount of sharpness to be applied on the texture (0 - disabled)") + ("orthographic-image-resolution", boost::program_options::value(&OPT::nOrthoMapResolution)->default_value(0), "orthographic image resolution to be generated from the textured mesh - the mesh is expected to be already geo-referenced or at least properly oriented (0 - disabled)") + ("ignore-mask-label", boost::program_options::value(&OPT::nIgnoreMaskLabel)->default_value(-1), "label value to ignore in the image mask, stored in the MVS scene or next to each image with '.mask.png' extension (-1 - auto estimate mask for lens distortion, -2 - disabled)") + ("max-texture-size", boost::program_options::value(&OPT::nMaxTextureSize)->default_value(8192), "maximum texture size, split it in multiple textures of this size if needed (0 - unbounded)") ; // hidden options, allowed both on command line and // in config file, but will not be shown to the user boost::program_options::options_description hidden("Hidden options"); hidden.add_options() - ("mesh-file", boost::program_options::value(&OPT::strMeshFileName), "mesh file name to texture (overwrite the existing mesh)") + ("views-file", boost::program_options::value(&OPT::strViewsFileName), "file name containing the list of views to be used for texturing (optional)") ; boost::program_options::options_description cmdline_options; @@ -152,55 +176,88 @@ bool Initialize(size_t argc, LPCTSTR* argv) // print application details: version and command line Util::LogBuild(); - LOG(_T("Command line:%s"), Util::CommandLineToString(argc, argv).c_str()); + LOG(_T("Command line: ") APPNAME _T("%s"), Util::CommandLineToString(argc, argv).c_str()); // validate input Util::ensureValidPath(OPT::strInputFileName); - Util::ensureUnifySlash(OPT::strInputFileName); - if (OPT::vm.count("help") || OPT::strInputFileName.IsEmpty()) { + if (OPT::vm.count("help") || OPT::strInputFileName.empty()) { boost::program_options::options_description visible("Available options"); visible.add(generic).add(config); GET_LOG() << visible; } - if (OPT::strInputFileName.IsEmpty()) + if (OPT::strInputFileName.empty()) return false; - OPT::strExportType = OPT::strExportType.ToLower() == _T("obj") ? _T(".obj") : _T(".ply"); + OPT::strExportType = OPT::strExportType.ToLower(); + if (OPT::strExportType == _T("obj")) + OPT::strExportType = _T(".obj"); + else + if (OPT::strExportType == _T("glb")) + OPT::strExportType = _T(".glb"); + else + if (OPT::strExportType == _T("gltf")) + OPT::strExportType = _T(".gltf"); + else + OPT::strExportType = _T(".ply"); // initialize optional options + Util::ensureValidPath(OPT::strMeshFileName); Util::ensureValidPath(OPT::strOutputFileName); - Util::ensureUnifySlash(OPT::strOutputFileName); - if (OPT::strOutputFileName.IsEmpty()) + Util::ensureValidPath(OPT::strViewsFileName); + if (OPT::strMeshFileName.empty() && (ARCHIVE_TYPE)OPT::nArchiveType == ARCHIVE_MVS) + OPT::strMeshFileName = Util::getFileFullName(OPT::strInputFileName) + _T(".ply"); + if (OPT::strOutputFileName.empty()) OPT::strOutputFileName = Util::getFileFullName(OPT::strInputFileName) + _T("_texture.mvs"); - // initialize global options - Process::setCurrentProcessPriority((Process::Priority)OPT::nProcessPriority); - #ifdef _USE_OPENMP - if (OPT::nMaxThreads != 0) - omp_set_num_threads(OPT::nMaxThreads); - #endif - - #ifdef _USE_BREAKPAD - // start memory dumper - MiniDumper::Create(APPNAME, WORKING_FOLDER); - #endif - - Util::Init(); + MVS::Initialize(APPNAME, OPT::nMaxThreads, OPT::nProcessPriority); return true; } // finalize application instance -void Finalize() +void Application::Finalize() { - #if TD_VERBOSE != TD_VERBOSE_OFF - // print memory statistics - Util::LogMemoryInfo(); - #endif + MVS::Finalize(); CLOSE_LOGFILE(); CLOSE_LOGCONSOLE(); CLOSE_LOG(); } +} // unnamed namespace + +IIndexArr ParseViewsFile(const String& filename, const Scene& scene) { + IIndexArr views; + std::ifstream file(filename); + if (!file.good()) { + VERBOSE("error: unable to open views file '%s'", filename.c_str()); + return views; + } + while (true) { + String imageName; + std::getline(file, imageName); + if (file.fail() || imageName.empty()) + break; + LPTSTR endIdx; + const IDX idx(strtoul(imageName, &endIdx, 10)); + const size_t szIndex(*endIdx == '\0' ? size_t(0) : imageName.size()); + FOREACH(idxImage, scene.images) { + const Image& image = scene.images[idxImage]; + if (szIndex == 0) { + // try to match by index + if (image.ID != idx) + continue; + } else { + // try to match by file name + const String name(Util::getFileNameExt(image.name)); + if (name.size() < szIndex || _tcsnicmp(name, imageName, szIndex) != 0) + continue; + } + views.emplace_back(idxImage); + break; + } + } + return views; +} + int main(int argc, LPCTSTR* argv) { #ifdef _DEBUGINFO @@ -208,23 +265,25 @@ int main(int argc, LPCTSTR* argv) _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);// | _CRTDBG_CHECK_ALWAYS_DF); #endif - if (!Initialize(argc, argv)) + Application application; + if (!application.Initialize(argc, argv)) return EXIT_FAILURE; Scene scene(OPT::nMaxThreads); // load and texture the mesh - if (!scene.Load(MAKE_PATH_SAFE(OPT::strInputFileName))) + const Scene::SCENE_TYPE sceneType(scene.Load(MAKE_PATH_SAFE(OPT::strInputFileName))); + if (sceneType == Scene::SCENE_NA) + return EXIT_FAILURE; + if (!OPT::strMeshFileName.empty() && !scene.mesh.Load(MAKE_PATH_SAFE(OPT::strMeshFileName))) { + VERBOSE("error: cannot load mesh file"); return EXIT_FAILURE; - if (!OPT::strMeshFileName.IsEmpty()) { - // load given mesh - scene.mesh.Load(MAKE_PATH_SAFE(OPT::strMeshFileName)); } if (scene.mesh.IsEmpty()) { VERBOSE("error: empty initial mesh"); return EXIT_FAILURE; } const String baseFileName(MAKE_PATH_SAFE(Util::getFileFullName(OPT::strOutputFileName))); - if (OPT::nOrthoMapResolution && !scene.mesh.textureDiffuse.empty()) { + if (OPT::nOrthoMapResolution && !scene.mesh.HasTexture()) { // the input mesh is already textured and an orthographic projection was requested goto ProjectOrtho; } @@ -233,27 +292,34 @@ int main(int argc, LPCTSTR* argv) // decimate to the desired resolution if (OPT::fDecimateMesh < 1.f) { ASSERT(OPT::fDecimateMesh > 0.f); - scene.mesh.Clean(OPT::fDecimateMesh, 0.f, false, OPT::nCloseHoles, 0u, false); - scene.mesh.Clean(1.f, 0.f, false, 0, 0u, true); // extra cleaning to remove non-manifold problems created by closing holes + scene.mesh.Clean(OPT::fDecimateMesh, 0.f, false, OPT::nCloseHoles, 0u, 0.f, false); + scene.mesh.Clean(1.f, 0.f, false, 0u, 0u, 0.f, true); // extra cleaning to remove non-manifold problems created by closing holes #if TD_VERBOSE != TD_VERBOSE_OFF if (VERBOSITY_LEVEL > 3) scene.mesh.Save(baseFileName +_T("_decim")+OPT::strExportType); #endif } + // fetch list of views to be used for texturing + IIndexArr views; + if (!OPT::strViewsFileName.empty()) + views = ParseViewsFile(MAKE_PATH_SAFE(OPT::strViewsFileName), scene); // compute mesh texture TD_TIMER_START(); - if (!scene.TextureMesh(OPT::nResolutionLevel, OPT::nMinResolution, OPT::fOutlierThreshold, OPT::fRatioDataSmoothness, OPT::bGlobalSeamLeveling, OPT::bLocalSeamLeveling, OPT::nTextureSizeMultiple, OPT::nRectPackingHeuristic, Pixel8U(OPT::nColEmpty))) + if (!scene.TextureMesh(OPT::nResolutionLevel, OPT::nMinResolution, OPT::minCommonCameras, OPT::fOutlierThreshold, OPT::fRatioDataSmoothness, + OPT::bGlobalSeamLeveling, OPT::bLocalSeamLeveling, OPT::nTextureSizeMultiple, OPT::nRectPackingHeuristic, Pixel8U(OPT::nColEmpty), + OPT::fSharpnessWeight, OPT::nIgnoreMaskLabel, OPT::nMaxTextureSize, views)) return EXIT_FAILURE; VERBOSE("Mesh texturing completed: %u vertices, %u faces (%s)", scene.mesh.vertices.GetSize(), scene.mesh.faces.GetSize(), TD_TIMER_GET_FMT().c_str()); // save the final mesh - scene.Save(baseFileName+_T(".mvs"), (ARCHIVE_TYPE)OPT::nArchiveType); scene.mesh.Save(baseFileName+OPT::strExportType); #if TD_VERBOSE != TD_VERBOSE_OFF if (VERBOSITY_LEVEL > 2) scene.ExportCamerasMLP(baseFileName+_T(".mlp"), baseFileName+OPT::strExportType); #endif + if ((ARCHIVE_TYPE)OPT::nArchiveType != ARCHIVE_MVS || sceneType != Scene::SCENE_INTERFACE) + scene.Save(baseFileName+_T(".mvs"), (ARCHIVE_TYPE)OPT::nArchiveType); } if (OPT::nOrthoMapResolution) { @@ -272,7 +338,6 @@ int main(int argc, LPCTSTR* argv) sml.Save(baseFileName+_T("_orthomap.txt")); } - Finalize(); return EXIT_SUCCESS; } /*----------------------------------------------------------------*/ diff --git a/apps/TransformScene/CMakeLists.txt b/apps/TransformScene/CMakeLists.txt new file mode 100644 index 000000000..2ea8a7c97 --- /dev/null +++ b/apps/TransformScene/CMakeLists.txt @@ -0,0 +1,13 @@ +if(MSVC) + FILE(GLOB LIBRARY_FILES_C "*.cpp" "*.rc") +else() + FILE(GLOB LIBRARY_FILES_C "*.cpp") +endif() +FILE(GLOB LIBRARY_FILES_H "*.h" "*.inl") + +cxx_executable_with_flags(TransformScene "Apps" "${cxx_default}" "MVS;${OpenMVS_EXTRA_LIBS}" ${LIBRARY_FILES_C} ${LIBRARY_FILES_H}) + +# Install +INSTALL(TARGETS TransformScene + EXPORT OpenMVSTargets + RUNTIME DESTINATION "${INSTALL_BIN_DIR}" COMPONENT bin) diff --git a/apps/TransformScene/TransformScene.cpp b/apps/TransformScene/TransformScene.cpp new file mode 100644 index 000000000..31b2fd2dd --- /dev/null +++ b/apps/TransformScene/TransformScene.cpp @@ -0,0 +1,339 @@ +/* + * TransformScene.cpp + * + * Copyright (c) 2014-2021 SEACAVE + * + * Author(s): + * + * cDc + * + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * + * Additional Terms: + * + * You are required to preserve legal notices and author attributions in + * that material or in the Appropriate Legal Notices displayed by works + * containing it. + */ + +#include "../../libs/MVS/Common.h" +#include "../../libs/MVS/Scene.h" +#include + +using namespace MVS; + + +// D E F I N E S /////////////////////////////////////////////////// + +#define APPNAME _T("TransformScene") +#define MVS_EXT _T(".mvs") + + +// S T R U C T S /////////////////////////////////////////////////// + +namespace { + +namespace OPT { + String strInputFileName; + String strPointCloudFileName; + String strMeshFileName; + String strOutputFileName; + String strAlignFileName; + String strTransformFileName; + String strTransferTextureFileName; + String strIndicesFileName; + bool bComputeVolume; + float fEpsNoisePosition; + float fEpsNoiseRotation; + float fPlaneThreshold; + float fSampleMesh; + unsigned nMaxResolution; + unsigned nUpAxis; + unsigned nArchiveType; + int nProcessPriority; + unsigned nMaxThreads; + String strExportType; + String strConfigFileName; + boost::program_options::variables_map vm; +} // namespace OPT + +class Application { +public: + Application() {} + ~Application() { Finalize(); } + + bool Initialize(size_t argc, LPCTSTR* argv); + void Finalize(); +}; // Application + +// initialize and parse the command line parameters +bool Application::Initialize(size_t argc, LPCTSTR* argv) +{ + // initialize log and console + OPEN_LOG(); + OPEN_LOGCONSOLE(); + + // group of options allowed only on command line + boost::program_options::options_description generic("Generic options"); + generic.add_options() + ("help,h", "produce this help message") + ("working-folder,w", boost::program_options::value(&WORKING_FOLDER), "working directory (default current directory)") + ("config-file,c", boost::program_options::value(&OPT::strConfigFileName)->default_value(APPNAME _T(".cfg")), "file name containing program options") + ("export-type", boost::program_options::value(&OPT::strExportType)->default_value(_T("ply")), "file type used to export the 3D scene (ply, obj, glb or gltf)") + ("archive-type", boost::program_options::value(&OPT::nArchiveType)->default_value(ARCHIVE_MVS), "project archive type: -1-interface, 0-text, 1-binary, 2-compressed binary") + ("process-priority", boost::program_options::value(&OPT::nProcessPriority)->default_value(-1), "process priority (below normal by default)") + ("max-threads", boost::program_options::value(&OPT::nMaxThreads)->default_value(0), "maximum number of threads (0 for using all available cores)") + #if TD_VERBOSE != TD_VERBOSE_OFF + ("verbosity,v", boost::program_options::value(&g_nVerbosityLevel)->default_value( + #if TD_VERBOSE == TD_VERBOSE_DEBUG + 3 + #else + 2 + #endif + ), "verbosity level") + #endif + ; + + // group of options allowed both on command line and in config file + boost::program_options::options_description config("Main options"); + config.add_options() + ("input-file,i", boost::program_options::value(&OPT::strInputFileName), "input scene filename") + ("pointcloud-file,p", boost::program_options::value(&OPT::strPointCloudFileName), "dense point-cloud with views file name to transform (overwrite existing point-cloud)") + ("mesh-file,m", boost::program_options::value(&OPT::strMeshFileName), "mesh file name to transform (overwrite existing mesh)") + ("output-file,o", boost::program_options::value(&OPT::strOutputFileName), "output filename for storing the scene") + ("align-file,a", boost::program_options::value(&OPT::strAlignFileName), "input scene filename to which the scene will be cameras aligned") + ("transform-file,t", boost::program_options::value(&OPT::strTransformFileName), "input transform filename by which the scene will transformed") + ("transfer-texture-file", boost::program_options::value(&OPT::strTransferTextureFileName), "input mesh filename to which the texture of the scene's mesh will be transfered to (the two meshes should be aligned and the new mesh to have UV-map)") + ("indices-file", boost::program_options::value(&OPT::strIndicesFileName), "input indices filename to be used with ex. texture transfer to select a subset of the scene's mesh") + ("compute-volume", boost::program_options::value(&OPT::bComputeVolume)->default_value(false), "compute the volume of the given watertight mesh, or else try to estimate the ground plane and assume the mesh is bounded by it") + ("eps-noise-position", boost::program_options::value(&OPT::fEpsNoisePosition)->default_value(0.f), "add noise to camera positions (0 - disabled)") + ("eps-noise-rotation", boost::program_options::value(&OPT::fEpsNoiseRotation)->default_value(0.f), "add noise to camera rotations (0 - disabled)") + ("plane-threshold", boost::program_options::value(&OPT::fPlaneThreshold)->default_value(0.f), "threshold used to estimate the ground plane (<0 - disabled, 0 - auto, >0 - desired threshold)") + ("sample-mesh", boost::program_options::value(&OPT::fSampleMesh)->default_value(-300000.f), "uniformly samples points on a mesh (0 - disabled, <0 - number of points, >0 - sample density per square unit)") + ("max-resolution", boost::program_options::value(&OPT::nMaxResolution)->default_value(0), "make sure image resolution are not not larger than this (0 - disabled)") + ("up-axis", boost::program_options::value(&OPT::nUpAxis)->default_value(2), "scene axis considered to point upwards (0 - x, 1 - y, 2 - z)") + ; + + boost::program_options::options_description cmdline_options; + cmdline_options.add(generic).add(config); + + boost::program_options::options_description config_file_options; + config_file_options.add(config); + + boost::program_options::positional_options_description p; + p.add("input-file", -1); + + try { + // parse command line options + boost::program_options::store(boost::program_options::command_line_parser((int)argc, argv).options(cmdline_options).positional(p).run(), OPT::vm); + boost::program_options::notify(OPT::vm); + INIT_WORKING_FOLDER; + // parse configuration file + std::ifstream ifs(MAKE_PATH_SAFE(OPT::strConfigFileName)); + if (ifs) { + boost::program_options::store(parse_config_file(ifs, config_file_options), OPT::vm); + boost::program_options::notify(OPT::vm); + } + } + catch (const std::exception& e) { + LOG(e.what()); + return false; + } + + // initialize the log file + OPEN_LOGFILE(MAKE_PATH(APPNAME _T("-") + Util::getUniqueName(0) + _T(".log")).c_str()); + + // print application details: version and command line + Util::LogBuild(); + LOG(_T("Command line: ") APPNAME _T("%s"), Util::CommandLineToString(argc, argv).c_str()); + + // validate input + Util::ensureValidPath(OPT::strInputFileName); + Util::ensureValidPath(OPT::strAlignFileName); + Util::ensureValidPath(OPT::strTransformFileName); + Util::ensureValidPath(OPT::strTransferTextureFileName); + Util::ensureValidPath(OPT::strIndicesFileName); + const String strInputFileNameExt(Util::getFileExt(OPT::strInputFileName).ToLower()); + const bool bInvalidCommand(OPT::strInputFileName.empty() || + (OPT::strAlignFileName.empty() && OPT::strTransformFileName.empty() && OPT::strTransferTextureFileName.empty() && !OPT::bComputeVolume)); + if (OPT::vm.count("help") || bInvalidCommand) { + boost::program_options::options_description visible("Available options"); + visible.add(generic).add(config); + GET_LOG() << visible; + } + if (bInvalidCommand) + return false; + OPT::strExportType = OPT::strExportType.ToLower(); + if (OPT::strExportType == _T("obj")) + OPT::strExportType = _T(".obj"); + else + if (OPT::strExportType == _T("glb")) + OPT::strExportType = _T(".glb"); + else + if (OPT::strExportType == _T("gltf")) + OPT::strExportType = _T(".gltf"); + else + OPT::strExportType = _T(".ply"); + + // initialize optional options + Util::ensureValidPath(OPT::strPointCloudFileName); + Util::ensureValidPath(OPT::strMeshFileName); + Util::ensureValidPath(OPT::strOutputFileName); + if (OPT::strMeshFileName.empty() && (ARCHIVE_TYPE)OPT::nArchiveType == ARCHIVE_MVS && strInputFileNameExt == MVS_EXT) + OPT::strMeshFileName = Util::getFileFullName(OPT::strInputFileName) + _T(".ply"); + if (OPT::strOutputFileName.empty()) + OPT::strOutputFileName = Util::getFileName(OPT::strInputFileName) + _T("_transformed") MVS_EXT; + + MVS::Initialize(APPNAME, OPT::nMaxThreads, OPT::nProcessPriority); + return true; +} + +// finalize application instance +void Application::Finalize() +{ + MVS::Finalize(); + + CLOSE_LOGFILE(); + CLOSE_LOGCONSOLE(); + CLOSE_LOG(); +} + +} // unnamed namespace + +int main(int argc, LPCTSTR* argv) +{ + #ifdef _DEBUGINFO + // set _crtBreakAlloc index to stop in at allocation + _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);// | _CRTDBG_CHECK_ALWAYS_DF); + #endif + + Application application; + if (!application.Initialize(argc, argv)) + return EXIT_FAILURE; + + TD_TIMER_START(); + + Scene scene(OPT::nMaxThreads); + + // load given scene + const Scene::SCENE_TYPE sceneType(scene.Load(MAKE_PATH_SAFE(OPT::strInputFileName), + !OPT::strTransformFileName.empty() || !OPT::strTransferTextureFileName.empty() || OPT::bComputeVolume)); + if (sceneType == Scene::SCENE_NA) + return EXIT_FAILURE; + if (OPT::fEpsNoisePosition > 0 || OPT::fEpsNoiseRotation > 0) { + scene.pointcloud.Release(); + scene.AddNoiseCameraPoses(OPT::fEpsNoisePosition, FD2R(OPT::fEpsNoiseRotation)); + scene.Save(MAKE_PATH_SAFE(Util::getFileFullName(OPT::strOutputFileName)) + _T(".mvs"), (ARCHIVE_TYPE)OPT::nArchiveType); + return EXIT_SUCCESS; + } + if (!OPT::strPointCloudFileName.empty() && !scene.pointcloud.Load(MAKE_PATH_SAFE(OPT::strPointCloudFileName))) { + VERBOSE("error: cannot load point-cloud file"); + return EXIT_FAILURE; + } + if (!OPT::strMeshFileName.empty() && !scene.mesh.Load(MAKE_PATH_SAFE(OPT::strMeshFileName))) { + VERBOSE("error: cannot load mesh file"); + return EXIT_FAILURE; + } + const String baseFileName(MAKE_PATH_SAFE(Util::getFileFullName(OPT::strOutputFileName))); + + if (!OPT::strAlignFileName.empty()) { + // transform this scene such that it best aligns with the given scene based on the camera positions + Scene sceneRef(OPT::nMaxThreads); + if (!sceneRef.Load(MAKE_PATH_SAFE(OPT::strAlignFileName))) + return EXIT_FAILURE; + if (!scene.AlignTo(sceneRef)) + return EXIT_FAILURE; + VERBOSE("Scene aligned to the given reference scene (%s)", TD_TIMER_GET_FMT().c_str()); + } + + if (!OPT::strTransformFileName.empty()) { + // transform this scene by the given transform matrix + std::ifstream file(MAKE_PATH_SAFE(OPT::strTransformFileName)); + std::string value; + std::vector transformValues; + while (file >> value) { + double v; + try { + v = std::stod(value); + } + catch (...) { + continue; + } + transformValues.push_back(v); + } + if (transformValues.size() != 12 && + (transformValues.size() != 16 || transformValues[12] != 0 || transformValues[13] != 0 || transformValues[14] != 0 || transformValues[15] != 1)) { + VERBOSE("error: invalid transform"); + return EXIT_FAILURE; + } + Matrix3x4 transform; + for (unsigned i=0; i<12; ++i) + transform[i] = transformValues[i]; + scene.Transform(transform); + VERBOSE("Scene transformed by the given transformation matrix (%s)", TD_TIMER_GET_FMT().c_str()); + } + + if (!OPT::strTransferTextureFileName.empty()) { + // transfer the texture of the scene's mesh to the new mesh; + // the two meshes should be aligned and the new mesh to have UV-coordinates + Mesh newMesh; + if (!newMesh.Load(MAKE_PATH_SAFE(OPT::strTransferTextureFileName))) + return EXIT_FAILURE; + Mesh::FaceIdxArr faceSubsetIndices; + if (!OPT::strIndicesFileName.empty()) { + std::ifstream in(OPT::strIndicesFileName.c_str()); + while (true) { + String index; + in >> index; + if (!in.good()) + break; + faceSubsetIndices.emplace_back(index.From()); + } + } + if (!scene.mesh.TransferTexture(newMesh, faceSubsetIndices)) + return EXIT_FAILURE; + newMesh.Save(baseFileName + OPT::strExportType); + VERBOSE("Texture transfered (%s)", TD_TIMER_GET_FMT().c_str()); + return EXIT_SUCCESS; + } + + if (OPT::nMaxResolution > 0) { + // scale scene images + const String folderName(Util::getFilePath(MAKE_PATH_FULL(WORKING_FOLDER_FULL, OPT::strInputFileName)) + String::FormatString("images%u" PATH_SEPARATOR_STR, OPT::nMaxResolution)); + if (!scene.ScaleImages(OPT::nMaxResolution, 0, folderName)) { + DEBUG("error: can not scale scene images to '%s'", folderName.c_str()); + return EXIT_FAILURE; + } + } + + if (OPT::bComputeVolume && !scene.mesh.IsEmpty()) { + // compute the mesh volume + const REAL volume(scene.ComputeLeveledVolume(OPT::fPlaneThreshold, OPT::fSampleMesh, OPT::nUpAxis)); + VERBOSE("Mesh volume: %g (%s)", volume, TD_TIMER_GET_FMT().c_str()); + } + + // write transformed scene + if (scene.IsValid()) + scene.Save(MAKE_PATH_SAFE(OPT::strOutputFileName), (ARCHIVE_TYPE)OPT::nArchiveType); + if (!scene.IsValid() || (ARCHIVE_TYPE)OPT::nArchiveType == ARCHIVE_MVS) { + if (!scene.pointcloud.IsEmpty()) + scene.pointcloud.Save(baseFileName + _T(".ply"), (ARCHIVE_TYPE)OPT::nArchiveType == ARCHIVE_MVS); + if (!scene.mesh.IsEmpty()) + scene.mesh.Save(baseFileName + OPT::strExportType); + } + return EXIT_SUCCESS; +} +/*----------------------------------------------------------------*/ diff --git a/apps/Viewer/CMakeLists.txt b/apps/Viewer/CMakeLists.txt index c519040d1..fe0920242 100644 --- a/apps/Viewer/CMakeLists.txt +++ b/apps/Viewer/CMakeLists.txt @@ -16,32 +16,17 @@ else() MESSAGE("-- Can't find GLEW. Continuing without it.") RETURN() endif() -if(CMAKE_COMPILER_IS_GNUCXX) - FIND_PACKAGE(PkgConfig QUIET) - pkg_search_module(GLFW QUIET glfw3) - if(GLFW_FOUND) - INCLUDE_DIRECTORIES(${GLFW_INCLUDE_DIRS}) - ADD_DEFINITIONS(${GLFW_DEFINITIONS}) - MESSAGE(STATUS "GLFW3 ${GLFW_VERSION} found (include: ${GLFW_INCLUDE_DIRS})") - else() - MESSAGE("-- Can't find GLFW3. Continuing without it.") - RETURN() - endif() +FIND_PACKAGE(glfw3 QUIET) +if(glfw3_FOUND) + INCLUDE_DIRECTORIES(${glfw3_INCLUDE_DIRS}) + ADD_DEFINITIONS(${glfw3_DEFINITIONS}) + MESSAGE(STATUS "GLFW3 ${glfw3_VERSION} found (include: ${glfw3_INCLUDE_DIRS})") else() - FIND_PACKAGE(glfw3 QUIET) - if(glfw3_FOUND) - INCLUDE_DIRECTORIES(${glfw3_INCLUDE_DIRS}) - ADD_DEFINITIONS(${glfw3_DEFINITIONS}) - MESSAGE(STATUS "GLFW3 ${glfw3_VERSION} found (include: ${glfw3_INCLUDE_DIRS})") - else() - MESSAGE("-- Can't find GLFW3. Continuing without it.") - RETURN() - endif() + MESSAGE("-- Can't find GLFW3. Continuing without it.") + RETURN() endif() # List sources files -FILE(GLOB PCH_C "Common.cpp") - if(MSVC) FILE(GLOB LIBRARY_FILES_C "*.cpp" "*.rc") else() @@ -49,15 +34,12 @@ else() endif() FILE(GLOB LIBRARY_FILES_H "*.h" "*.inl") -# Place Common.cpp as the first file in the list -# needed by cotire when setting PCH manually -LIST(REMOVE_ITEM LIBRARY_FILES_C ${PCH_C}) -SET(LIBRARY_FILES_C "${PCH_C};${LIBRARY_FILES_C}") - -cxx_executable_with_flags_no_pch(${VIEWER_NAME} "Apps" "${cxx_default}" "MVS;${OPENGL_LIBRARIES};${GLEW_LIBRARY};${GLFW_STATIC_LIBRARIES};GLEW::GLEW;${glfw3_LIBRARY};${GLFW3_LIBRARY};glfw;${OpenMVS_EXTRA_LIBS}" ${LIBRARY_FILES_C} ${LIBRARY_FILES_H}) +cxx_executable_with_flags(${VIEWER_NAME} "Apps" "${cxx_default}" "MVS;${OPENGL_LIBRARIES};${GLEW_LIBRARY};${GLFW_STATIC_LIBRARIES};GLEW::GLEW;${glfw3_LIBRARY};${GLFW3_LIBRARY};glfw;${OpenMVS_EXTRA_LIBS}" ${LIBRARY_FILES_C} ${LIBRARY_FILES_H}) # Manually set Common.h as the precompiled header -set_target_pch(${VIEWER_NAME} Common.h) +IF(CMAKE_VERSION VERSION_GREATER_EQUAL 3.16.0) + TARGET_PRECOMPILE_HEADERS(${VIEWER_NAME} PRIVATE "Common.h") +endif() # Install INSTALL(TARGETS ${VIEWER_NAME} diff --git a/apps/Viewer/Camera.cpp b/apps/Viewer/Camera.cpp index b217c2d6b..e6cb75044 100644 --- a/apps/Viewer/Camera.cpp +++ b/apps/Viewer/Camera.cpp @@ -40,122 +40,136 @@ using namespace VIEWER; // S T R U C T S /////////////////////////////////////////////////// -Camera::Camera(const AABB3d& _box, double _fov) +Camera::Camera(const AABB3d& _box, const Point3d& _center, float _scaleF, float _fov) : - box(_box), - width(0), height(0), + boxScene(_box), + centerScene(_center), rotation(Eigen::Quaterniond::Identity()), center(Eigen::Vector3d::Zero()), - dist(0), radius(100), fov(_fov), - scaleF(1.f), + dist(0), radius(100), + fovDef(_fov), scaleFDef(_scaleF), prevCamID(NO_ID), currentCamID(NO_ID), maxCamID(0) { Reset(); } -void Camera::CopyOf(const Camera& rhs) -{ - rotation = rhs.rotation; - center = rhs.center; - dist = rhs.dist; - radius = rhs.radius; - fov = rhs.fov; -} - - -void Camera::Init(const AABB3d& _box) -{ - box = _box; - Reset(); -} - void Camera::Reset() { - center = box.GetCenter(); - radius = box.GetSize().norm()*0.5; + if (boxScene.IsEmpty()) { + center = Point3d::ZERO; + radius = 1; + } else { + center = centerScene; + radius = boxScene.GetSize().norm()*0.5; + } rotation = Eigen::Quaterniond::Identity(); - scaleF = 1.f; + scaleF = scaleFDef; prevCamID = currentCamID = NO_ID; - fov = 40; - dist = radius * 0.5 / SIN(D2R(fov)); - Resize(width, height); + fov = fovDef; + dist = radius*0.5 / SIN(D2R((double)fov)); + if (size.area()) + Resize(size); } -void Camera::Resize(int _width, int _height) +void Camera::Resize(const cv::Size& _size) { + ASSERT(MINF(_size.width, _size.height) > 0); + size = _size; glMatrixMode(GL_PROJECTION); glLoadIdentity(); - const GLfloat zNear = 1e-2f; - const GLfloat zFar = 1e5f; - width = _width; height = _height; - GLfloat aspect = float(width)/float(height); - GLfloat fH = TAN(FD2R((float)fov)) * zNear; - GLfloat fW = fH * aspect; - glFrustum(-fW, fW, -fH, fH, zNear, zFar); + const GLfloat zNear = 1e-3f; + const GLfloat zFar = (float)boxScene.GetSize().norm()*10; + const GLfloat aspect = float(size.width)/float(size.height); + if (fov == 5.f) { + // orthographic projection + const GLfloat fH = (float)boxScene.GetSize().norm()*0.5f; + const GLfloat fW = fH * aspect; + glOrtho(-fW, fW, -fH, fH, zNear, zFar); + } else { + // perspective projection + const GLfloat fH = TAN(FD2R(fov)) * zNear; + const GLfloat fW = fH * aspect; + glFrustum(-fW, fW, -fH, fH, zNear, zFar); + } } -void Camera::SetFOV(double _fov) +void Camera::SetFOV(float _fov) { - fov = _fov; - Resize(width, height); + fov = MAXF(_fov, 5.f); + Resize(size); } Eigen::Vector3d Camera::GetPosition() const { - const Eigen::Matrix3d R(rotation.toRotationMatrix()); - const Eigen::Vector3d eye(0, 0, dist); - return R * eye + center; + const Eigen::Matrix3d R(GetRotation()); + return center + R.col(2) * dist; +} + +Eigen::Matrix3d Camera::GetRotation() const +{ + return rotation.toRotationMatrix(); } Eigen::Matrix4d Camera::GetLookAt() const { - const Eigen::Matrix3d R(rotation.toRotationMatrix()); - const Eigen::Vector3d eye(R.col(2) * dist + center); + const Eigen::Matrix3d R(GetRotation()); + const Eigen::Vector3d eye(center + R.col(2) * dist); const Eigen::Vector3d up(R.col(1)); - + const Eigen::Vector3d n((center-eye).normalized()); const Eigen::Vector3d s(n.cross(up)); const Eigen::Vector3d v(s.cross(n)); - - Eigen::Matrix4d m; - m << - s(0), s(1), s(2), -eye.dot(s), - v(0), v(1), v(2), -eye.dot(v), - -n(0), -n(1), -n(2), eye.dot(n), - 0.0, 0.0, 0.0, 1.0; + + Eigen::Matrix4d m; m << + s(0), s(1), s(2), -eye.dot(s), + v(0), v(1), v(2), -eye.dot(v), + -n(0), -n(1), -n(2), eye.dot(n), + 0.0, 0.0, 0.0, 1.0; return m; } void Camera::GetLookAt(Eigen::Vector3d& _eye, Eigen::Vector3d& _center, Eigen::Vector3d& _up) const { - const Eigen::Matrix3d R(rotation.toRotationMatrix()); - const Eigen::Vector3d eye(0, 0, dist); - const Eigen::Vector3d up(0, 1, 0); - - _eye = R * eye + center; + const Eigen::Matrix3d R(GetRotation()); + _eye = center + R.col(2) * dist; _center = center; - _up = R * up; + _up = R.col(1); } + void Camera::Rotate(const Eigen::Vector2d& pos, const Eigen::Vector2d& prevPos) { - if (pos.isApprox(prevPos, ZERO_TOLERANCE)) + if (pos.isApprox(prevPos, ZEROTOLERANCE())) return; Eigen::Vector3d oldp(prevPos.x(), prevPos.y(), 0); Eigen::Vector3d newp(pos.x(), pos.y(), 0); - const double radius_virtual_sphere(0.9); - Project2Sphere(radius_virtual_sphere, oldp); - Project2Sphere(radius_virtual_sphere, newp); - Eigen::Quaterniond dr; - dr.setFromTwoVectors(newp, oldp); - rotation *= dr; + const double radiusSphere(0.9); + ProjectOnSphere(radiusSphere, oldp); + ProjectOnSphere(radiusSphere, newp); + rotation *= Eigen::Quaterniond().setFromTwoVectors(newp, oldp); + + // disable camera view mode + prevCamID = currentCamID; +} + +void Camera::Translate(const Eigen::Vector2d& pos, const Eigen::Vector2d& prevPos) +{ + if (pos.isApprox(prevPos, ZEROTOLERANCE())) + return; + + Eigen::Matrix P, V; + glGetDoublev(GL_MODELVIEW_MATRIX, V.data()); + glGetDoublev(GL_PROJECTION_MATRIX, P.data()); + Eigen::Vector3d centerScreen((P*V*center.homogeneous().eval()).hnormalized()); + centerScreen.head<2>() += prevPos - pos; + center = (V.inverse()*P.inverse()*centerScreen.homogeneous().eval()).hnormalized(); // disable camera view mode prevCamID = currentCamID; } -void Camera::Project2Sphere(double radius, Eigen::Vector3d& p) const +void Camera::ProjectOnSphere(double radius, Eigen::Vector3d& p) const { p.z() = 0; const double d = p.x()* p.x()+ p.y() * p.y(); diff --git a/apps/Viewer/Camera.h b/apps/Viewer/Camera.h index 37d982386..d83880a3e 100644 --- a/apps/Viewer/Camera.h +++ b/apps/Viewer/Camera.h @@ -48,34 +48,38 @@ class Camera public: EIGEN_MAKE_ALIGNED_OPERATOR_NEW - AABB3d box; - int width, height; + cv::Size size; + AABB3d boxScene; + Eigen::Vector3d centerScene; Eigen::Quaterniond rotation; Eigen::Vector3d center; - double dist; - double radius; - double fov; - float scaleF; + double dist, radius; + float fov, fovDef; + float scaleF, scaleFDef; MVS::IIndex prevCamID, currentCamID, maxCamID; public: - explicit Camera(const AABB3d& _box=AABB3d(true), double _fov=40); - void CopyOf(const Camera&); + Camera(const AABB3d& _box=AABB3d(true), const Point3d& _center=Point3d::ZERO, float _scaleF=1, float _fov=40); - void Init(const AABB3d&); void Reset(); - void Resize(int _width, int _height); - void SetFOV(double _fov); + void Resize(const cv::Size&); + void SetFOV(float _fov); + + const cv::Size& GetSize() const { return size; } Eigen::Vector3d GetPosition() const; + Eigen::Matrix3d GetRotation() const; Eigen::Matrix4d GetLookAt() const; + void GetLookAt(Eigen::Vector3d& eye, Eigen::Vector3d& center, Eigen::Vector3d& up) const; void Rotate(const Eigen::Vector2d& pos, const Eigen::Vector2d& prevPos); + void Translate(const Eigen::Vector2d& pos, const Eigen::Vector2d& prevPos); + + bool IsCameraViewMode() const { return prevCamID != currentCamID && currentCamID != NO_ID; } protected: - void Project2Sphere(double radius, Eigen::Vector3d& p) const; + void ProjectOnSphere(double radius, Eigen::Vector3d& p) const; }; -typedef CSharedPtr CameraPtr; /*----------------------------------------------------------------*/ } // namespace VIEWER diff --git a/apps/Viewer/Common.h b/apps/Viewer/Common.h index d1f1912b5..f3387834e 100644 --- a/apps/Viewer/Common.h +++ b/apps/Viewer/Common.h @@ -59,16 +59,19 @@ using namespace SEACAVE; namespace VIEWER { // the conversion matrix from OpenGL default coordinate system -// to the camera coordinate system: +// to the camera coordinate system (NADIR orientation): // [ 1 0 0 0] * [ x ] = [ x ] // 0 -1 0 0 y -y // 0 0 -1 0 z -z // 0 0 0 1 1 1 -static const GLfloat gs_convert[4][4] = { - {1.f, 0.f, 0.f, 0.f}, - {0.f, -1.f, 0.f, 0.f}, - {0.f, 0.f, -1.f, 0.f}, - {0.f, 0.f, 0.f, 1.f}}; +static const Eigen::Matrix4d gs_convert = [] { + Eigen::Matrix4d tmp; tmp << + 1, 0, 0, 0, + 0, -1, 0, 0, + 0, 0, -1, 0, + 0, 0, 0, 1; + return tmp; +}(); /// given rotation matrix R and translation vector t, /// column-major matrix m is equal to: diff --git a/apps/Viewer/Scene.cpp b/apps/Viewer/Scene.cpp index b40cb1bf2..8053bb5cc 100644 --- a/apps/Viewer/Scene.cpp +++ b/apps/Viewer/Scene.cpp @@ -40,103 +40,6 @@ using namespace VIEWER; #define IMAGE_MAX_RESOLUTION 1024 -// S T R U C T S /////////////////////////////////////////////////// - -struct IndexDist { - IDX idx; - REAL dist; - - inline IndexDist() : dist(REAL(FLT_MAX)) {} - inline bool IsValid() const { return dist < REAL(FLT_MAX); } -}; - -struct IntersectRayPoints { - typedef MVS::PointCloud Scene; - typedef VIEWER::Scene::OctreePoints Octree; - typedef typename Octree::IDX_TYPE IDX; - typedef TCone Cone3; - typedef TConeIntersect Cone3Intersect; - - const Scene& scene; - const Cone3 cone; - const Cone3Intersect coneIntersect; - const unsigned minViews; - IndexDist pick; - - IntersectRayPoints(const Octree& octree, const Ray3& _ray, const Scene& _scene, unsigned _minViews) - : scene(_scene), cone(_ray, D2R(REAL(0.5))), coneIntersect(cone), minViews(_minViews) - { - octree.Collect(*this, *this); - } - - inline bool Intersects(const typename Octree::POINT_TYPE& center, typename Octree::Type radius) const { - return coneIntersect(Sphere3(center.cast(), REAL(radius)*SQRT_3)); - } - - void operator () (const IDX* idices, IDX size) { - // test ray-point intersection and keep the closest - FOREACHRAWPTR(pIdx, idices, size) { - const MVS::PointCloud::Index idx(*pIdx); - if (!scene.pointViews.IsEmpty() && scene.pointViews[idx].size() < minViews) - continue; - const MVS::PointCloud::Point& X = scene.points[idx]; - REAL dist; - if (coneIntersect.Classify(Cast(X), dist) == VISIBLE) { - ASSERT(dist >= 0); - if (pick.dist > dist) { - pick.dist = dist; - pick.idx = idx; - } - } - } - } -}; - -struct IntersectRayMesh { - typedef MVS::Mesh Scene; - typedef VIEWER::Scene::OctreeMesh Octree; - typedef typename Octree::IDX_TYPE IDX; - - const Scene& scene; - const Ray3& ray; - IndexDist pick; - - IntersectRayMesh(const Octree& octree, const Ray3& _ray, const Scene& _scene) - : scene(_scene), ray(_ray) - { - octree.Collect(*this, *this); - } - - inline bool Intersects(const typename Octree::POINT_TYPE& center, typename Octree::Type radius) const { - return ray.Intersects(AABB3f(center, radius)); - } - - void operator () (const IDX* idices, IDX size) { - // store all intersected faces only once - typedef std::unordered_set FaceSet; - FaceSet set; - FOREACHRAWPTR(pIdx, idices, size) { - const MVS::Mesh::VIndex idxVertex((MVS::Mesh::VIndex)*pIdx); - const MVS::Mesh::FaceIdxArr& faces = scene.vertexFaces[idxVertex]; - set.insert(faces.begin(), faces.end()); - } - // test face intersection and keep the closest - for (MVS::Mesh::FIndex idxFace: set) { - const MVS::Mesh::Face& face = scene.faces[idxFace]; - REAL dist; - if (ray.Intersects(Triangle3(Cast(scene.vertices[face[0]]), Cast(scene.vertices[face[1]]), Cast(scene.vertices[face[2]])), &dist)) { - ASSERT(dist >= 0); - if (pick.dist > dist) { - pick.dist = dist; - pick.idx = idxFace; - } - } - } - } -}; -/*----------------------------------------------------------------*/ - - // S T R U C T S /////////////////////////////////////////////////// enum EVENT_TYPE { @@ -178,12 +81,16 @@ class EVTComputeOctree : public Event bool Run(void*) { MVS::Scene& scene = pScene->scene; if (!scene.mesh.IsEmpty()) { - Scene::OctreeMesh octMesh(scene.mesh.vertices); - scene.mesh.ListIncidenteFaces(); + Scene::OctreeMesh octMesh(scene.mesh.vertices, [](Scene::OctreeMesh::IDX_TYPE size, Scene::OctreeMesh::Type /*radius*/) { + return size > 256; + }); + scene.mesh.ListIncidentFaces(); pScene->octMesh.Swap(octMesh); } else if (!scene.pointcloud.IsEmpty()) { - Scene::OctreePoints octPoints(scene.pointcloud.points); + Scene::OctreePoints octPoints(scene.pointcloud.points, [](Scene::OctreePoints::IDX_TYPE size, Scene::OctreePoints::Type /*radius*/) { + return size > 512; + }); pScene->octPoints.Swap(octPoints); } return true; @@ -215,10 +122,10 @@ void* Scene::ThreadWorker(void*) { SEACAVE::EventQueue Scene::events; SEACAVE::Thread Scene::thread; -Scene::Scene() +Scene::Scene(ARCHIVE_TYPE _nArchiveType) : - listPointCloud(0), - listMesh(0) + nArchiveType(_nArchiveType), + listPointCloud(0) { } Scene::~Scene() @@ -230,13 +137,22 @@ void Scene::Empty() { ReleasePointCloud(); ReleaseMesh(); + obbPoints.Release(); + if (window.IsValid()) { + window.ReleaseClbk(); + window.Reset(); + window.SetName(_T("(empty)")); + } textures.Release(); images.Release(); scene.Release(); sceneName.clear(); + geometryName.clear(); } void Scene::Release() { + if (window.IsValid()) + window.SetVisible(false); if (!thread.isRunning()) { events.AddEvent(new EVTClose()); thread.join(); @@ -254,29 +170,26 @@ void Scene::ReleasePointCloud() } void Scene::ReleaseMesh() { - if (listMesh) { - glDeleteLists(listMesh, 1); - listMesh = 0; + if (!listMeshes.empty()) { + for (GLuint listMesh: listMeshes) + glDeleteLists(listMesh, 1); + listMeshes.Release(); } } -bool Scene::Init(int width, int height, LPCTSTR windowName, LPCTSTR fileName, LPCTSTR meshFileName) +bool Scene::Init(const cv::Size& size, LPCTSTR windowName, LPCTSTR fileName, LPCTSTR geometryFileName) { ASSERT(scene.IsEmpty()); // init window if (glfwInit() == GL_FALSE) return false; - if (!window.Init(width, height, windowName)) + if (!window.Init(size, windowName)) return false; if (glewInit() != GLEW_OK) return false; name = windowName; window.clbkOpenScene = DELEGATEBINDCLASS(Window::ClbkOpenScene, &Scene::Open, this); - window.clbkExportScene = DELEGATEBINDCLASS(Window::ClbkExportScene, &Scene::Export, this); - window.clbkRayScene = DELEGATEBINDCLASS(Window::ClbkRayScene, &Scene::CastRay, this); - window.clbkCompilePointCloud = DELEGATEBINDCLASS(Window::ClbkCompilePointCloud, &Scene::CompilePointCloud, this); - window.clbkCompileMesh = DELEGATEBINDCLASS(Window::ClbkCompileMesh, &Scene::CompileMesh, this); // init OpenGL glPolygonMode(GL_FRONT, GL_FILL); @@ -301,13 +214,13 @@ bool Scene::Init(int width, int height, LPCTSTR windowName, LPCTSTR fileName, LP thread.start(ThreadWorker); // open scene or init empty scene - if (fileName == NULL || !Open(fileName, meshFileName)) - window.SetCamera(CameraPtr(new Camera())); - + window.SetCamera(Camera()); + if (fileName != NULL) + Open(fileName, geometryFileName); window.SetVisible(true); return true; } -bool Scene::Open(LPCTSTR fileName, LPCTSTR meshFileName) +bool Scene::Open(LPCTSTR fileName, LPCTSTR geometryFileName) { ASSERT(fileName); DEBUG_EXTRA("Loading: '%s'", Util::getFileNameExt(fileName).c_str()); @@ -319,53 +232,83 @@ bool Scene::Open(LPCTSTR fileName, LPCTSTR meshFileName) INIT_WORKING_FOLDER; if (!scene.Load(fileName, true)) return false; - if (meshFileName) { - // load given mesh - scene.mesh.Load(meshFileName); + if (geometryFileName) { + // try to load given mesh + MVS::Mesh mesh; + MVS::PointCloud pointcloud; + if (mesh.Load(geometryFileName)) { + scene.mesh.Swap(mesh); + geometryName = geometryFileName; + geometryMesh = true; + } else + // try to load as a point-cloud + if (pointcloud.Load(geometryFileName)) { + scene.pointcloud.Swap(pointcloud); + geometryName = geometryFileName; + geometryMesh = false; + } } - if (scene.IsEmpty()) - return false; + if (!scene.pointcloud.IsEmpty()) + scene.pointcloud.PrintStatistics(scene.images.data(), &scene.obb); #if 1 // create octree structure used to accelerate selection functionality - events.AddEvent(new EVTComputeOctree(this)); + if (!scene.IsEmpty()) + events.AddEvent(new EVTComputeOctree(this)); #endif // init scene AABB3d bounds(true); - if (!scene.pointcloud.IsEmpty()) { - bounds = scene.pointcloud.GetAABB(MINF(3u,scene.nCalibratedImages)); - if (bounds.IsEmpty()) - bounds = scene.pointcloud.GetAABB(); - } - if (!scene.mesh.IsEmpty()) { - scene.mesh.ComputeNormalFaces(); - bounds.Insert(scene.mesh.GetAABB()); + Point3d center(Point3d::INF); + if (scene.IsBounded()) { + bounds = AABB3d(scene.obb.GetAABB()); + center = bounds.GetCenter(); + } else { + if (!scene.pointcloud.IsEmpty()) { + bounds = scene.pointcloud.GetAABB(MINF(3u,scene.nCalibratedImages)); + if (bounds.IsEmpty()) + bounds = scene.pointcloud.GetAABB(); + center = scene.pointcloud.GetCenter(); + } + if (!scene.mesh.IsEmpty()) { + scene.mesh.ComputeNormalFaces(); + bounds.Insert(scene.mesh.GetAABB()); + center = scene.mesh.GetCenter(); + } } // init images + AABB3d imageBounds(true); images.Reserve(scene.images.size()); FOREACH(idxImage, scene.images) { const MVS::Image& imageData = scene.images[idxImage]; if (!imageData.IsValid()) continue; - images.AddConstruct(idxImage); + images.emplace_back(idxImage); + imageBounds.InsertFull(imageData.camera.C); } + if (imageBounds.IsEmpty()) + imageBounds.Enlarge(0.5); + if (bounds.IsEmpty()) + bounds = imageBounds; // init and load texture if (scene.mesh.HasTexture()) { - Image& image = textures.AddEmpty(); - ASSERT(image.idx == NO_ID); - #if 0 - cv::flip(scene.mesh.textureDiffuse, scene.mesh.textureDiffuse, 0); - image.SetImage(scene.mesh.textureDiffuse); - scene.mesh.textureDiffuse.release(); - #else // preserve texture, used only to be able to export the mesh - Image8U3 textureDiffuse; - cv::flip(scene.mesh.textureDiffuse, textureDiffuse, 0); - image.SetImage(textureDiffuse); - #endif - image.GenerateMipmap(); + FOREACH(i, scene.mesh.texturesDiffuse) { + Image& image = textures.emplace_back(); + ASSERT(image.idx == NO_ID); + #if 0 + Image8U3& textureDiffuse = scene.mesh.texturesDiffuse[i]; + cv::flip(textureDiffuse, textureDiffuse, 0); + image.SetImage(textureDiffuse); + textureDiffuse.release(); + #else // preserve texture, used only to be able to export the mesh + Image8U3 textureDiffuse; + cv::flip(scene.mesh.texturesDiffuse[i], textureDiffuse, 0); + image.SetImage(textureDiffuse); + #endif + image.GenerateMipmap(); + } } // init display lists @@ -373,17 +316,74 @@ bool Scene::Open(LPCTSTR fileName, LPCTSTR meshFileName) CompilePointCloud(); // compile mesh CompileMesh(); + // compile bounding-box + CompileBounds(); // init camera - window.SetCamera(CameraPtr(new Camera(bounds))); - window.camera->maxCamID = images.size(); + window.SetCamera(Camera(bounds, + center == Point3d::INF ? Point3d(bounds.GetCenter()) : center, + images.size()<2?1.f:(float)imageBounds.EnlargePercent(REAL(1)/images.size()).GetSize().norm())); + window.camera.maxCamID = images.size(); window.SetName(String::FormatString((name + _T(": %s")).c_str(), Util::getFileName(fileName).c_str())); - window.Reset(MINF(2u, images.size())); + window.clbkSaveScene = DELEGATEBINDCLASS(Window::ClbkSaveScene, &Scene::Save, this); + window.clbkExportScene = DELEGATEBINDCLASS(Window::ClbkExportScene, &Scene::Export, this); + window.clbkCenterScene = DELEGATEBINDCLASS(Window::ClbkCenterScene, &Scene::Center, this); + window.clbkCompilePointCloud = DELEGATEBINDCLASS(Window::ClbkCompilePointCloud, &Scene::CompilePointCloud, this); + window.clbkCompileMesh = DELEGATEBINDCLASS(Window::ClbkCompileMesh, &Scene::CompileMesh, this); + window.clbkTogleSceneBox = DELEGATEBINDCLASS(Window::ClbkTogleSceneBox, &Scene::TogleSceneBox, this); + window.clbkCropToBounds = DELEGATEBINDCLASS(Window::ClbkCropToBounds, &Scene::CropToBounds, this); + if (scene.IsBounded()) + window.clbkCompileBounds = DELEGATEBINDCLASS(Window::ClbkCompileBounds, &Scene::CompileBounds, this); + if (!scene.IsEmpty()) + window.clbkRayScene = DELEGATEBINDCLASS(Window::ClbkRayScene, &Scene::CastRay, this); + window.Reset(!scene.pointcloud.IsEmpty()&&!scene.mesh.IsEmpty()?Window::SPR_NONE:Window::SPR_ALL, + MINF(2u,images.size())); + return true; +} + +// export the scene +bool Scene::Save(LPCTSTR _fileName, bool bRescaleImages) +{ + if (!IsOpen()) + return false; + REAL imageScale = 0; + if (bRescaleImages) { + window.SetVisible(false); + std::cout << "Enter image resolution scale: "; + String strScale; + std::cin >> strScale; + window.SetVisible(true); + imageScale = strScale.From(0); + } + const String fileName(_fileName != NULL ? String(_fileName) : Util::insertBeforeFileExt(sceneName, _T("_new"))); + MVS::Mesh mesh; + if (!scene.mesh.IsEmpty() && !geometryName.empty() && geometryMesh) + mesh.Swap(scene.mesh); + MVS::PointCloud pointcloud; + if (!scene.pointcloud.IsEmpty() && !geometryName.empty() && !geometryMesh) + pointcloud.Swap(scene.pointcloud); + if (imageScale > 0 && imageScale < 1) { + // scale and save images + const String folderName(Util::getFilePath(MAKE_PATH_FULL(WORKING_FOLDER_FULL, fileName)) + String::FormatString("images%d" PATH_SEPARATOR_STR, ROUND2INT(imageScale*100))); + if (!scene.ScaleImages(0, imageScale, folderName)) { + DEBUG("error: can not scale scene images to '%s'", folderName.c_str()); + return false; + } + } + if (!scene.Save(fileName, nArchiveType)) { + DEBUG("error: can not save scene to '%s'", fileName.c_str()); + return false; + } + if (!mesh.IsEmpty()) + scene.mesh.Swap(mesh); + if (!pointcloud.IsEmpty()) + scene.pointcloud.Swap(pointcloud); + sceneName = fileName; return true; } // export the scene -bool Scene::Export(LPCTSTR _fileName, LPCTSTR exportType, bool losslessTexture) const +bool Scene::Export(LPCTSTR _fileName, LPCTSTR exportType) const { if (!IsOpen()) return false; @@ -391,13 +391,31 @@ bool Scene::Export(LPCTSTR _fileName, LPCTSTR exportType, bool losslessTexture) String lastFileName; const String fileName(_fileName != NULL ? String(_fileName) : sceneName); const String baseFileName(Util::getFileFullName(fileName)); - const bool bPoints(scene.pointcloud.Save(lastFileName=(baseFileName+_T("_pointcloud.ply")))); - const bool bMesh(scene.mesh.Save(lastFileName=(baseFileName+_T("_mesh")+(exportType?exportType:(Util::getFileExt(fileName)==_T(".obj")?_T(".obj"):_T(".ply")))), true, losslessTexture)); + const bool bPoints(scene.pointcloud.Save(lastFileName=(baseFileName+_T("_pointcloud.ply")), nArchiveType==ARCHIVE_MVS)); + const bool bMesh(scene.mesh.Save(lastFileName=(baseFileName+_T("_mesh")+(exportType?exportType:(Util::getFileExt(fileName)==_T(".obj")?_T(".obj"):_T(".ply")))), cList(), true)); #if TD_VERBOSE != TD_VERBOSE_OFF if (VERBOSITY_LEVEL > 2 && (bPoints || bMesh)) scene.ExportCamerasMLP(Util::getFileFullName(lastFileName)+_T(".mlp"), lastFileName); #endif - return (bPoints || bMesh); + AABB3f aabb(true); + if (scene.IsBounded()) { + std::ofstream fs(baseFileName+_T("_roi.txt")); + if (fs) + fs << scene.obb; + aabb = scene.obb.GetAABB(); + } else + if (!scene.pointcloud.IsEmpty()) { + aabb = scene.pointcloud.GetAABB(); + } else + if (!scene.mesh.IsEmpty()) { + aabb = scene.mesh.GetAABB(); + } + if (!aabb.IsEmpty()) { + std::ofstream fs(baseFileName+_T("_roi_box.txt")); + if (fs) + fs << aabb; + } + return bPoints || bMesh; } void Scene::CompilePointCloud() @@ -409,15 +427,15 @@ void Scene::CompilePointCloud() glNewList(listPointCloud, GL_COMPILE); ASSERT((window.sparseType&(Window::SPR_POINTS|Window::SPR_LINES)) != 0); // compile point-cloud - if (!scene.pointcloud.IsEmpty() && (window.sparseType&Window::SPR_POINTS) != 0) { + if ((window.sparseType&Window::SPR_POINTS) != 0) { ASSERT_ARE_SAME_TYPE(float, MVS::PointCloud::Point::Type); glBegin(GL_POINTS); glColor3f(1.f,1.f,1.f); FOREACH(i, scene.pointcloud.points) { - if (!scene.pointcloud.pointViews.IsEmpty() && + if (!scene.pointcloud.pointViews.empty() && scene.pointcloud.pointViews[i].size() < window.minViews) continue; - if (!scene.pointcloud.colors.IsEmpty()) { + if (!scene.pointcloud.colors.empty()) { const MVS::PointCloud::Color& c = scene.pointcloud.colors[i]; glColor3ub(c.r,c.g,c.b); } @@ -434,29 +452,75 @@ void Scene::CompileMesh() if (scene.mesh.IsEmpty()) return; ReleaseMesh(); - listMesh = glGenLists(1); - glNewList(listMesh, GL_COMPILE); - // compile mesh - ASSERT_ARE_SAME_TYPE(float, MVS::Mesh::Vertex::Type); - ASSERT_ARE_SAME_TYPE(float, MVS::Mesh::Normal::Type); - ASSERT_ARE_SAME_TYPE(float, MVS::Mesh::TexCoord::Type); - glColor3f(1.f, 1.f, 1.f); - glBegin(GL_TRIANGLES); - FOREACH(i, scene.mesh.faces) { - const MVS::Mesh::Face& face = scene.mesh.faces[i]; - const MVS::Mesh::Normal& n = scene.mesh.faceNormals[i]; - glNormal3fv(n.ptr()); - for (int j = 0; j < 3; ++j) { - if (!scene.mesh.faceTexcoords.IsEmpty() && window.bRenderTexture) { - const MVS::Mesh::TexCoord& t = scene.mesh.faceTexcoords[i * 3 + j]; - glTexCoord2fv(t.ptr()); + if (scene.mesh.faceNormals.empty()) + scene.mesh.ComputeNormalFaces(); + // translate, normalize and flip Y axis of the texture coordinates + MVS::Mesh::TexCoordArr normFaceTexcoords; + if (scene.mesh.HasTexture() && window.bRenderTexture) + scene.mesh.FaceTexcoordsNormalize(normFaceTexcoords, true); + MVS::Mesh::TexIndex texIdx(0); + do { + GLuint& listMesh = listMeshes.emplace_back(glGenLists(1)); + listMesh = glGenLists(1); + glNewList(listMesh, GL_COMPILE); + // compile mesh + ASSERT_ARE_SAME_TYPE(float, MVS::Mesh::Vertex::Type); + ASSERT_ARE_SAME_TYPE(float, MVS::Mesh::Normal::Type); + ASSERT_ARE_SAME_TYPE(float, MVS::Mesh::TexCoord::Type); + glColor3f(1.f, 1.f, 1.f); + glBegin(GL_TRIANGLES); + FOREACH(idxFace, scene.mesh.faces) { + if (!scene.mesh.faceTexindices.empty() && scene.mesh.faceTexindices[idxFace] != texIdx) + continue; + const MVS::Mesh::Face& face = scene.mesh.faces[idxFace]; + const MVS::Mesh::Normal& n = scene.mesh.faceNormals[idxFace]; + glNormal3fv(n.ptr()); + for (int j = 0; j < 3; ++j) { + if (!normFaceTexcoords.empty()) { + const MVS::Mesh::TexCoord& t = normFaceTexcoords[idxFace*3 + j]; + glTexCoord2fv(t.ptr()); + } + const MVS::Mesh::Vertex& p = scene.mesh.vertices[face[j]]; + glVertex3fv(p.ptr()); } - const MVS::Mesh::Vertex& p = scene.mesh.vertices[face[j]]; - glVertex3fv(p.ptr()); + } + glEnd(); + glEndList(); + } while (++texIdx < scene.mesh.texturesDiffuse.size()); +} + +void Scene::CompileBounds() +{ + obbPoints.Release(); + if (!scene.IsBounded()) { + window.bRenderBounds = false; + return; + } + window.bRenderBounds = !window.bRenderBounds; + if (window.bRenderBounds) { + static const uint8_t indices[12*2] = { + 0,2, 2,3, 3,1, 1,0, + 0,6, 2,4, 3,5, 1,7, + 6,4, 4,5, 5,7, 7,6 + }; + OBB3f::POINT corners[OBB3f::numCorners]; + scene.obb.GetCorners(corners); + for (int i=0; i<12; ++i) { + obbPoints.emplace_back(corners[indices[i*2+0]]); + obbPoints.emplace_back(corners[indices[i*2+1]]); } } - glEnd(); - glEndList(); +} + +void Scene::CropToBounds() +{ + if (!IsOpen()) + return; + if (!scene.IsBounded()) + return; + scene.pointcloud.RemovePointsOutside(scene.obb); + scene.mesh.RemoveFacesOutside(scene.obb); + Center(); } void Scene::Draw() @@ -464,47 +528,39 @@ void Scene::Draw() glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glPointSize(window.pointSize); - window.UpdateView(images, scene.images); - // render point-cloud if (listPointCloud) { glDisable(GL_TEXTURE_2D); glCallList(listPointCloud); } // render mesh - if (listMesh) { + if (!listMeshes.empty()) { glEnable(GL_DEPTH_TEST); glEnable(GL_CULL_FACE); - if (!scene.mesh.faceTexcoords.IsEmpty() && window.bRenderTexture) { + if (!scene.mesh.faceTexcoords.empty() && window.bRenderTexture) { glEnable(GL_TEXTURE_2D); - textures.First().Bind(); - glCallList(listMesh); + FOREACH(i, listMeshes) { + textures[i].Bind(); + glCallList(listMeshes[i]); + } glDisable(GL_TEXTURE_2D); } else { glEnable(GL_LIGHTING); - glCallList(listMesh); + for (GLuint listMesh: listMeshes) + glCallList(listMesh); glDisable(GL_LIGHTING); } } // render cameras if (window.bRenderCameras) { glDisable(GL_CULL_FACE); + const Point3* ptrPrevC(NULL); FOREACH(idx, images) { Image& image = images[idx]; const MVS::Image& imageData = scene.images[image.idx]; const MVS::Camera& camera = imageData.camera; - // change coordinates system to the camera space - glPushMatrix(); - glMultMatrixd((GLdouble*)TransL2W((const Matrix3x3::EMat)camera.R, -(const Point3::EVec)camera.C).data()); - glPointSize(window.pointSize+1.f); - glDisable(GL_TEXTURE_2D); - // draw camera position and image center - const double scaleFocal(window.camera->scaleF); - glBegin(GL_POINTS); - glColor3f(1,0,0); glVertex3f(0,0,0); // camera position - glColor3f(0,1,0); glVertex3f(0,0,(float)scaleFocal); // image center - glEnd(); // cache image corner coordinates + const double scaleFocal(window.camera.scaleF); const Point2d pp(camera.GetPrincipalPoint()); const double focal(camera.GetFocalLength()/scaleFocal); const double cx(-pp.x/focal); @@ -515,8 +571,19 @@ void Scene::Draw() const Point3d ic2(cx, py, scaleFocal); const Point3d ic3(px, py, scaleFocal); const Point3d ic4(px, cy, scaleFocal); + // change coordinates system to the camera space + glPushMatrix(); + glMultMatrixd((GLdouble*)TransL2W((const Matrix3x3::EMat)camera.R, -(const Point3::EVec)camera.C).data()); + glPointSize(window.pointSize+1.f); + glDisable(GL_TEXTURE_2D); + // draw camera position and image center + glBegin(GL_POINTS); + glColor3f(1,0,0); glVertex3f(0,0,0); // camera position + glColor3f(0,1,0); glVertex3f(0,0,(float)scaleFocal); // image center + glColor3f(0,0,1); glVertex3d((0.5*imageData.width-pp.x)/focal, cy, scaleFocal); // image up + glEnd(); // draw image thumbnail - const bool bSelectedImage(idx == window.camera->currentCamID); + const bool bSelectedImage(idx == window.camera.currentCamID); if (bSelectedImage) { if (image.IsValid()) { // render image @@ -561,8 +628,39 @@ void Scene::Draw() glEnd(); // restore coordinate system glPopMatrix(); + // render image visibility info + if (window.bRenderImageVisibility && idx != NO_ID && idx==window.camera.currentCamID) { + if (scene.pointcloud.IsValid()) { + const Image& image = images[idx]; + glPointSize(window.pointSize*1.1f); + glDisable(GL_DEPTH_TEST); + glBegin(GL_POINTS); + glColor3f(1.f,0.f,0.f); + FOREACH(i, scene.pointcloud.points) { + ASSERT(!scene.pointcloud.pointViews[i].empty()); + if (scene.pointcloud.pointViews[i].size() < window.minViews) + continue; + if (scene.pointcloud.pointViews[i].FindFirst(image.idx) == MVS::PointCloud::ViewArr::NO_INDEX) + continue; + glVertex3fv(scene.pointcloud.points[i].ptr()); + } + glEnd(); + glEnable(GL_DEPTH_TEST); + glPointSize(window.pointSize); + } + } + // render camera trajectory + if (window.bRenderCameraTrajectory && ptrPrevC) { + glBegin(GL_LINES); + glColor3f(1.f,0.5f,0.f); + glVertex3dv(ptrPrevC->ptr()); + glVertex3dv(camera.C.ptr()); + glEnd(); + } + ptrPrevC = &camera.C; } } + // render selection if (window.selectionType != Window::SEL_NA) { glPointSize(window.pointSize+4); glDisable(GL_DEPTH_TEST); @@ -573,26 +671,75 @@ void Scene::Draw() glColor3f(0,0,1); glVertex3fv(window.selectionPoints[2].ptr()); } glEnd(); + if (window.bRenderViews && window.selectionType == Window::SEL_POINT) { + if (!scene.pointcloud.pointViews.empty()) { + glBegin(GL_LINES); + const MVS::PointCloud::ViewArr& views = scene.pointcloud.pointViews[(MVS::PointCloud::Index)window.selectionIdx]; + ASSERT(!views.empty()); + for (MVS::PointCloud::View idxImage: views) { + const MVS::Image& imageData = scene.images[idxImage]; + glVertex3dv(imageData.camera.C.ptr()); + glVertex3fv(window.selectionPoints[0].ptr()); + } + glEnd(); + } + } glEnable(GL_DEPTH_TEST); glPointSize(window.pointSize); } + // render oriented-bounding-box + if (!obbPoints.empty()) { + glDepthMask(GL_FALSE); + glBegin(GL_LINES); + glColor3f(0.5f,0.1f,0.8f); + for (IDX i=0; iRotate(window.pos, window.prevPos); -} - void Scene::Loop() { while (!glfwWindowShouldClose(window.GetWindow())) { - ProcessEvents(); + window.UpdateView(images, scene.images); Draw(); + glfwWaitEvents(); + } +} + + +void Scene::Center() +{ + if (!IsOpen()) + return; + scene.Center(); + CompilePointCloud(); + CompileMesh(); + if (scene.IsBounded()) { + window.bRenderBounds = false; + CompileBounds(); } + events.AddEvent(new EVTComputeOctree(this)); +} + +void Scene::TogleSceneBox() +{ + if (!IsOpen()) + return; + const auto EnlargeAABB = [](AABB3f aabb) { + return aabb.Enlarge(aabb.GetSize().maxCoeff()*0.03f); + }; + if (scene.IsBounded()) + scene.obb = OBB3f(true); + else if (!scene.mesh.IsEmpty()) + scene.obb.Set(EnlargeAABB(scene.mesh.GetAABB())); + else if (!scene.pointcloud.IsEmpty()) + scene.obb.Set(EnlargeAABB(scene.pointcloud.GetAABB(window.minViews))); + CompileBounds(); } @@ -616,21 +763,21 @@ void Scene::CastRay(const Ray3& ray, int action) if (window.selectionType != Window::SEL_NA && now-window.selectionTime < timeDblClick) { // this is a double click, center scene at the selected point - window.camera->center = Point3d(window.selectionPoints[3]); - window.camera->dist *= 0.7; + window.CenterCamera(window.selectionPoints[3]); window.selectionTime = now; } else if (!octMesh.IsEmpty()) { // find ray intersection with the mesh - const IntersectRayMesh intRay(octMesh, ray, scene.mesh); + const MVS::IntersectRayMesh intRay(octMesh, ray, scene.mesh); if (intRay.pick.IsValid()) { const MVS::Mesh::Face& face = scene.mesh.faces[(MVS::Mesh::FIndex)intRay.pick.idx]; window.selectionPoints[0] = scene.mesh.vertices[face[0]]; window.selectionPoints[1] = scene.mesh.vertices[face[1]]; window.selectionPoints[2] = scene.mesh.vertices[face[2]]; - window.selectionPoints[3] = (ray.m_pOrig + ray.m_vDir*intRay.pick.dist).cast(); + window.selectionPoints[3] = ray.GetPoint(intRay.pick.dist).cast(); window.selectionType = Window::SEL_TRIANGLE; window.selectionTime = now; + window.selectionIdx = intRay.pick.idx; DEBUG("Face selected:\n\tindex: %u\n\tvertex 1: %u (%g %g %g)\n\tvertex 2: %u (%g %g %g)\n\tvertex 3: %u (%g %g %g)", intRay.pick.idx, face[0], window.selectionPoints[0].x, window.selectionPoints[0].y, window.selectionPoints[0].z, @@ -643,11 +790,12 @@ void Scene::CastRay(const Ray3& ray, int action) } else if (!octPoints.IsEmpty()) { // find ray intersection with the points - const IntersectRayPoints intRay(octPoints, ray, scene.pointcloud, window.minViews); + const MVS::IntersectRayPoints intRay(octPoints, ray, scene.pointcloud, window.minViews); if (intRay.pick.IsValid()) { window.selectionPoints[0] = window.selectionPoints[3] = scene.pointcloud.points[intRay.pick.idx]; window.selectionType = Window::SEL_POINT; window.selectionTime = now; + window.selectionIdx = intRay.pick.idx; DEBUG("Point selected:\n\tindex: %u (%g %g %g)%s", intRay.pick.idx, window.selectionPoints[0].x, window.selectionPoints[0].y, window.selectionPoints[0].z, @@ -666,7 +814,7 @@ void Scene::CastRay(const Ray3& ray, int action) }().c_str() ); } else { - window.selectionType = Window::SEL_POINT; + window.selectionType = Window::SEL_NA; } } break; } diff --git a/apps/Viewer/Scene.h b/apps/Viewer/Scene.h index 0d31fa076..9315b35e8 100644 --- a/apps/Viewer/Scene.h +++ b/apps/Viewer/Scene.h @@ -48,13 +48,16 @@ namespace VIEWER { class Scene { public: - typedef TOctree OctreePoints; - typedef TOctree OctreeMesh; + typedef MVS::PointCloud::Octree OctreePoints; + typedef MVS::Mesh::Octree OctreeMesh; public: + ARCHIVE_TYPE nArchiveType; String name; String sceneName; + String geometryName; + bool geometryMesh; MVS::Scene scene; Window window; ImageArr images; // scene photos @@ -62,16 +65,17 @@ class Scene OctreePoints octPoints; OctreeMesh octMesh; + Point3fArr obbPoints; GLuint listPointCloud; - GLuint listMesh; + CLISTDEF0IDX(GLuint,MVS::Mesh::TexIndex) listMeshes; // multi-threading static SEACAVE::EventQueue events; // internal events queue (processed by the working threads) static SEACAVE::Thread thread; // worker thread public: - Scene(); + explicit Scene(ARCHIVE_TYPE _nArchiveType = ARCHIVE_MVS); ~Scene(); void Empty(); @@ -82,16 +86,20 @@ class Scene inline bool IsOpen() const { return IsValid() && !scene.IsEmpty(); } inline bool IsOctreeValid() const { return !octPoints.IsEmpty() || !octMesh.IsEmpty(); } - bool Init(int width, int height, LPCTSTR windowName, LPCTSTR fileName=NULL, LPCTSTR meshFileName=NULL); - bool Open(LPCTSTR fileName, LPCTSTR meshFileName=NULL); - bool Export(LPCTSTR fileName, LPCTSTR exportType=NULL, bool losslessTexture=false) const; + bool Init(const cv::Size&, LPCTSTR windowName, LPCTSTR fileName=NULL, LPCTSTR geometryFileName=NULL); + bool Open(LPCTSTR fileName, LPCTSTR geometryFileName=NULL); + bool Save(LPCTSTR fileName=NULL, bool bRescaleImages=false); + bool Export(LPCTSTR fileName, LPCTSTR exportType=NULL) const; void CompilePointCloud(); void CompileMesh(); + void CompileBounds(); + void CropToBounds(); void Draw(); - void ProcessEvents(); void Loop(); + void Center(); + void TogleSceneBox(); void CastRay(const Ray3&, int); protected: static void* ThreadWorker(void*); diff --git a/apps/Viewer/Viewer.cpp b/apps/Viewer/Viewer.cpp index 2470e3abf..5bff3c2bf 100644 --- a/apps/Viewer/Viewer.cpp +++ b/apps/Viewer/Viewer.cpp @@ -44,11 +44,12 @@ using namespace VIEWER; // S T R U C T S /////////////////////////////////////////////////// +namespace { + namespace OPT { String strInputFileName; +String strGeometryFileName; String strOutputFileName; -String strMeshFileName; -bool bLosslessTexture; unsigned nArchiveType; int nProcessPriority; unsigned nMaxThreads; @@ -61,8 +62,17 @@ bool bLogFile; boost::program_options::variables_map vm; } // namespace OPT +class Application { +public: + Application() {} + ~Application() { Finalize(); } + + bool Initialize(size_t argc, LPCTSTR* argv); + void Finalize(); +}; // Application + // initialize and parse the command line parameters -bool Initialize(size_t argc, LPCTSTR* argv) +bool Application::Initialize(size_t argc, LPCTSTR* argv) { // initialize log and console OPEN_LOG(); @@ -75,13 +85,13 @@ bool Initialize(size_t argc, LPCTSTR* argv) ("working-folder,w", boost::program_options::value(&WORKING_FOLDER), "working directory (default current directory)") ("config-file,c", boost::program_options::value(&OPT::strConfigFileName)->default_value(APPNAME _T(".cfg")), "file name containing program options") ("export-type", boost::program_options::value(&OPT::strExportType), "file type used to export the 3D scene (ply or obj)") - ("archive-type", boost::program_options::value(&OPT::nArchiveType)->default_value(2), "project archive type: 0-text, 1-binary, 2-compressed binary") - ("process-priority", boost::program_options::value(&OPT::nProcessPriority)->default_value(0), "process priority (normal by default)") - ("max-threads", boost::program_options::value(&OPT::nMaxThreads)->default_value(0), "maximum number of threads that this process should use (0 - use all available cores)") - ("max-memory", boost::program_options::value(&OPT::nMaxMemory)->default_value(0), "maximum amount of memory in MB that this process should use (0 - use all available memory)") + ("archive-type", boost::program_options::value(&OPT::nArchiveType)->default_value(ARCHIVE_MVS), "project archive type: -1-interface, 0-text, 1-binary, 2-compressed binary") + ("process-priority", boost::program_options::value(&OPT::nProcessPriority)->default_value(0), "process priority (normal by default)") + ("max-threads", boost::program_options::value(&OPT::nMaxThreads)->default_value(0), "maximum number of threads that this process should use (0 - use all available cores)") + ("max-memory", boost::program_options::value(&OPT::nMaxMemory)->default_value(0), "maximum amount of memory in MB that this process should use (0 - use all available memory)") #if TD_VERBOSE != TD_VERBOSE_OFF - ("log-file", boost::program_options::value(&OPT::bLogFile)->default_value(false), "dump log to a file") - ("verbosity,v", boost::program_options::value(&g_nVerbosityLevel)->default_value( + ("log-file", boost::program_options::value(&OPT::bLogFile)->default_value(false), "dump log to a file") + ("verbosity,v", boost::program_options::value(&g_nVerbosityLevel)->default_value( #if TD_VERBOSE == TD_VERBOSE_DEBUG 3 #else @@ -95,22 +105,15 @@ bool Initialize(size_t argc, LPCTSTR* argv) boost::program_options::options_description config("Viewer options"); config.add_options() ("input-file,i", boost::program_options::value(&OPT::strInputFileName), "input project filename containing camera poses and scene (point-cloud/mesh)") + ("geometry-file,g", boost::program_options::value(&OPT::strGeometryFileName), "mesh or point-cloud with views file name (overwrite existing geometry)") ("output-file,o", boost::program_options::value(&OPT::strOutputFileName), "output filename for storing the mesh") - ("texture-lossless", boost::program_options::value(&OPT::bLosslessTexture)->default_value(false), "export texture using a lossless image format") - ; - - // hidden options, allowed both on command line and - // in config file, but will not be shown to the user - boost::program_options::options_description hidden("Hidden options"); - hidden.add_options() - ("mesh-file", boost::program_options::value(&OPT::strMeshFileName), "mesh file name to texture (overwrite the existing mesh)") ; boost::program_options::options_description cmdline_options; - cmdline_options.add(generic).add(config).add(hidden); + cmdline_options.add(generic).add(config); boost::program_options::options_description config_file_options; - config_file_options.add(config).add(hidden); + config_file_options.add(config); boost::program_options::positional_options_description p; p.add("input-file", -1); @@ -140,7 +143,7 @@ bool Initialize(size_t argc, LPCTSTR* argv) // print application details: version and command line Util::LogBuild(); - LOG(_T("Command line:%s"), Util::CommandLineToString(argc, argv).c_str()); + LOG(_T("Command line: ") APPNAME _T("%s"), Util::CommandLineToString(argc, argv).c_str()); // validate input Util::ensureValidPath(OPT::strInputFileName); @@ -152,10 +155,18 @@ bool Initialize(size_t argc, LPCTSTR* argv) "Keys:\n" "\tE: export scene\n" "\tR: reset scene\n" + "\tB: render bounds\n" + "\tB + Shift: togle bounds\n" "\tC: render cameras\n" + "\tC + Shift: render camera trajectory\n" + "\tC + Ctrl: center scene\n" "\tLeft/Right: select next camera to view the scene\n" - "\tW: render wire-frame mesh\n" + "\tS: save scene\n" + "\tS + Shift: rescale images and save scene\n" "\tT: render mesh texture\n" + "\tW: render wire-frame mesh\n" + "\tV: render view rays to the selected point\n" + "\tV + Shift: render points seen by the current view\n" "\tUp/Down: adjust point size\n" "\tUp/Down + Shift: adjust minimum number of views accepted when displaying a point or line\n" "\t+/-: adjust camera thumbnail transparency\n" @@ -163,36 +174,21 @@ bool Initialize(size_t argc, LPCTSTR* argv) "\n") << visible; } - if (!OPT::strExportType.IsEmpty()) + if (!OPT::strExportType.empty()) OPT::strExportType = OPT::strExportType.ToLower() == _T("obj") ? _T(".obj") : _T(".ply"); // initialize optional options + Util::ensureValidPath(OPT::strGeometryFileName); Util::ensureValidPath(OPT::strOutputFileName); - Util::ensureValidPath(OPT::strMeshFileName); - // initialize global options - Process::setCurrentProcessPriority((Process::Priority)OPT::nProcessPriority); - #ifdef _USE_OPENMP - if (OPT::nMaxThreads != 0) - omp_set_num_threads(OPT::nMaxThreads); - #endif - - #ifdef _USE_BREAKPAD - // start memory dumper - MiniDumper::Create(APPNAME, WORKING_FOLDER); - #endif - - Util::Init(); + MVS::Initialize(APPNAME, OPT::nMaxThreads, OPT::nProcessPriority); return true; } // finalize application instance -void Finalize() +void Application::Finalize() { - #if TD_VERBOSE != TD_VERBOSE_OFF - // print memory statistics - Util::LogMemoryInfo(); - #endif + MVS::Finalize(); if (OPT::bLogFile) CLOSE_LOGFILE(); @@ -200,6 +196,8 @@ void Finalize() CLOSE_LOG(); } +} // unnamed namespace + int main(int argc, LPCTSTR* argv) { #ifdef _DEBUGINFO @@ -207,23 +205,22 @@ int main(int argc, LPCTSTR* argv) _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);// | _CRTDBG_CHECK_ALWAYS_DF); #endif - if (!Initialize(argc, argv)) + Application application; + if (!application.Initialize(argc, argv)) return EXIT_FAILURE; // create viewer Scene viewer; - if (!viewer.Init(1280, 720, APPNAME, - OPT::strInputFileName.IsEmpty() ? NULL : MAKE_PATH_SAFE(OPT::strInputFileName).c_str(), - OPT::strMeshFileName.IsEmpty() ? NULL : MAKE_PATH_SAFE(OPT::strMeshFileName).c_str())) + if (!viewer.Init(cv::Size(1280, 720), APPNAME, + OPT::strInputFileName.empty() ? NULL : MAKE_PATH_SAFE(OPT::strInputFileName).c_str(), + OPT::strGeometryFileName.empty() ? NULL : MAKE_PATH_SAFE(OPT::strGeometryFileName).c_str())) return EXIT_FAILURE; - if (viewer.IsOpen() && !OPT::strOutputFileName.IsEmpty()) { + if (viewer.IsOpen() && !OPT::strOutputFileName.empty()) { // export the scene - viewer.Export(MAKE_PATH_SAFE(OPT::strOutputFileName), OPT::strExportType.IsEmpty()?LPCTSTR(NULL):OPT::strExportType.c_str(), OPT::bLosslessTexture); + viewer.Export(MAKE_PATH_SAFE(OPT::strOutputFileName), OPT::strExportType.empty()?LPCTSTR(NULL):OPT::strExportType.c_str()); } // enter viewer loop viewer.Loop(); - - Finalize(); return EXIT_SUCCESS; } /*----------------------------------------------------------------*/ diff --git a/apps/Viewer/Window.cpp b/apps/Viewer/Window.cpp index 4cb3eeef4..cfd2f12ef 100644 --- a/apps/Viewer/Window.cpp +++ b/apps/Viewer/Window.cpp @@ -57,39 +57,57 @@ Window::~Window() void Window::Release() { if (IsValid()) { + #ifdef _USE_NUKLEAR + nk_glfw3_shutdown(); + #endif glfwDestroyWindow(window); window = NULL; } clbkOpenScene.reset(); + ReleaseClbk(); +} + +void Window::ReleaseClbk() +{ + clbkSaveScene.reset(); clbkExportScene.reset(); + clbkCenterScene.reset(); clbkRayScene.reset(); clbkCompilePointCloud.reset(); clbkCompileMesh.reset(); + clbkCompileBounds.reset(); + clbkTogleSceneBox.reset(); + clbkCropToBounds.reset(); } -bool Window::Init(int width, int height, LPCTSTR name) +bool Window::Init(const cv::Size& _size, LPCTSTR name) { + sizeScale = 1; + size = _size; + glfwDefaultWindowHints(); glfwWindowHint(GLFW_VISIBLE, 0); - window = glfwCreateWindow(width, height, name, NULL, NULL); + window = glfwCreateWindow(size.width, size.height, name, NULL, NULL); if (!window) return false; glfwMakeContextCurrent(window); glfwSetFramebufferSizeCallback(window, Window::Resize); glfwSetKeyCallback(window, Window::Key); glfwSetMouseButtonCallback(window, Window::MouseButton); + glfwSetCursorPosCallback(window, Window::MouseMove); glfwSetScrollCallback(window, Window::Scroll); glfwSetDropCallback(window, Window::Drop); g_mapWindows[window] = this; + Reset(); return true; } -void Window::SetCamera(CameraPtr cam) +void Window::SetCamera(const Camera& cam) { camera = cam; - int width, height; - glfwGetWindowSize(window, &width, &height); - Resize(width, height); + cv::Size _size; + glfwGetFramebufferSize(window, &_size.width, &_size.height); + Resize(_size); } void Window::SetName(LPCTSTR name) { @@ -102,18 +120,27 @@ void Window::SetVisible(bool v) else glfwHideWindow(window); } -void Window::Reset(uint32_t _minViews) +bool Window::IsVisible() const +{ + return glfwGetWindowAttrib(window, GLFW_VISIBLE) != 0; +} +void Window::Reset(SPARSE _sparseType, unsigned _minViews) { - if (camera) - camera->Reset(); - sparseType = SPR_ALL; + camera.Reset(); + inputType = INP_NA; + sparseType = _sparseType; minViews = _minViews; pointSize = 2.f; cameraBlend = 0.5f; bRenderCameras = true; + bRenderCameraTrajectory = true; + bRenderImageVisibility = false; + bRenderViews = true; bRenderSolid = true; bRenderTexture = true; + bRenderBounds = false; selectionType = SEL_NA; + selectionIdx = NO_IDX; if (clbkCompilePointCloud != NULL) clbkCompilePointCloud(); if (clbkCompileMesh != NULL) @@ -121,46 +148,76 @@ void Window::Reset(uint32_t _minViews) glfwPostEmptyEvent(); } -void Window::UpdateView(const ImageArr& images, const MVS::ImageArr& sceneImages) + +void Window::CenterCamera(const Point3& pos) { - glMatrixMode(GL_MODELVIEW); - if (camera->prevCamID != camera->currentCamID && camera->currentCamID != NO_ID) { - // enable camera view mode - // apply current camera transform - const Image& image = images[camera->currentCamID]; - const MVS::Image& imageData = sceneImages[image.idx]; - const MVS::Camera& camera = imageData.camera; - const Eigen::Matrix4d trans(TransW2L((const Matrix3x3::EMat)camera.R, camera.GetT())); - glLoadMatrixf((GLfloat*)gs_convert); - glMultMatrixd((GLdouble*)trans.data()); + camera.center = pos; + camera.dist *= 0.7; +} + + +void Window::UpdateView(const ImageArr& images, const MVS::ImageArr& sceneImagesMVS) +{ + if (camera.IsCameraViewMode()) { + // enable camera view mode and apply current camera transform + const Image& image = images[camera.currentCamID]; + const MVS::Camera& camera = sceneImagesMVS[image.idx].camera; + UpdateView((const Matrix3x3::EMat)camera.R, camera.GetT()); } else { // apply view point transform - const Eigen::Matrix4d trans(camera->GetLookAt()); + glMatrixMode(GL_MODELVIEW); + const Eigen::Matrix4d trans(camera.GetLookAt()); glLoadMatrixd((GLdouble*)trans.data()); } } -void Window::UpdateMousePosition() +void Window::UpdateView(const Eigen::Matrix3d& R, const Eigen::Vector3d& t) +{ + glMatrixMode(GL_MODELVIEW); + transform = gs_convert * TransW2L(R, t); + glLoadMatrixd((GLdouble*)transform.data()); +} + +void Window::UpdateMousePosition(double xpos, double ypos) { prevPos = pos; - // get current position - glfwGetCursorPos(window, &pos.x(), &pos.y()); + pos.x() = xpos; + pos.y() = ypos; // normalize position to [-1:1] range - const int w(camera->width); - const int h(camera->height); + const int w(camera.size.width); + const int h(camera.size.height); pos.x() = (2.0 * pos.x() - w) / w; pos.y() = (h - 2.0 * pos.y()) / h; } -void Window::Resize(int width, int height) + +void Window::GetFrame(Image8U3& image) const +{ + image.create(GetSize()); + glReadPixels(0, 0, image.width(), image.height(), GL_BGR_EXT, GL_UNSIGNED_BYTE, image.ptr()); + cv::flip(image, image, 0); +} + + +cv::Size Window::GetSize() const +{ + cv::Size _size; + glfwGetWindowSize(window, &_size.width, &_size.height); + return _size; +} +void Window::Resize(const cv::Size& _size) { + // detect scaled window + sizeScale = (double)GetSize().width/_size.width; + size = _size; + // update resolution glfwMakeContextCurrent(window); - glViewport(0, 0, (GLint)width, (GLint)height); - camera->Resize(width, height); + glViewport(0, 0, size.width, size.height); + camera.Resize(cv::Size(ROUND2INT(size.width*sizeScale), ROUND2INT(size.height*sizeScale))); } void Window::Resize(GLFWwindow* window, int width, int height) { - g_mapWindows[window]->Resize(width, height); + g_mapWindows[window]->Resize(cv::Size(width, height)); } void Window::Key(int k, int /*scancode*/, int action, int mod) @@ -196,41 +253,67 @@ void Window::Key(int k, int /*scancode*/, int action, int mod) break; case GLFW_KEY_LEFT: if (action != GLFW_RELEASE) { - camera->prevCamID = camera->currentCamID; - camera->currentCamID--; - if (camera->currentCamID < NO_ID && camera->currentCamID >= camera->maxCamID) - camera->currentCamID = camera->maxCamID-1; + camera.prevCamID = camera.currentCamID; + camera.currentCamID--; + if (camera.currentCamID < NO_ID && camera.currentCamID >= camera.maxCamID) + camera.currentCamID = camera.maxCamID-1; } break; case GLFW_KEY_RIGHT: if (action != GLFW_RELEASE) { - camera->prevCamID = camera->currentCamID; - camera->currentCamID++; - if (camera->currentCamID >= camera->maxCamID) - camera->currentCamID = NO_ID; + camera.prevCamID = camera.currentCamID; + camera.currentCamID++; + if (camera.currentCamID >= camera.maxCamID) + camera.currentCamID = NO_ID; + } + break; + case GLFW_KEY_B: + if (action == GLFW_RELEASE) { + if (mod & GLFW_MOD_CONTROL) { + if (clbkCropToBounds != NULL) + clbkCropToBounds(); + } else if (mod & GLFW_MOD_SHIFT) { + if (clbkTogleSceneBox != NULL) + clbkTogleSceneBox(); + } else { + if (clbkCompileBounds != NULL) + clbkCompileBounds(); + } + } + break; + case GLFW_KEY_C: + if (action == GLFW_RELEASE) { + if (mod & GLFW_MOD_SHIFT) { + bRenderCameraTrajectory = !bRenderCameraTrajectory; + } else if (mod & GLFW_MOD_CONTROL) { + if (clbkCenterScene != NULL) + clbkCenterScene(); + } else { + bRenderCameras = !bRenderCameras; + } } break; case GLFW_KEY_E: if (action == GLFW_RELEASE && clbkExportScene != NULL) - clbkExportScene(NULL, NULL, false); + clbkExportScene(NULL, NULL); + break; + case GLFW_KEY_P: + switch (sparseType) { + case SPR_POINTS: sparseType = SPR_LINES; break; + case SPR_LINES: sparseType = SPR_ALL; break; + case SPR_ALL: sparseType = SPR_POINTS; break; + } + if (clbkCompilePointCloud != NULL) + clbkCompilePointCloud(); break; case GLFW_KEY_R: if (action == GLFW_RELEASE) Reset(); break; - case GLFW_KEY_C: - if (action == GLFW_RELEASE) - bRenderCameras = !bRenderCameras; - break; - case GLFW_KEY_W: + case GLFW_KEY_S: if (action == GLFW_RELEASE) { - if (bRenderSolid) { - bRenderSolid = false; - glPolygonMode(GL_FRONT, GL_LINE); - } else { - bRenderSolid = true; - glPolygonMode(GL_FRONT, GL_FILL); - } + if (clbkSaveScene != NULL) + clbkSaveScene(NULL, (mod & GLFW_MOD_SHIFT) != 0); } break; case GLFW_KEY_T: @@ -240,21 +323,32 @@ void Window::Key(int k, int /*scancode*/, int action, int mod) clbkCompileMesh(); } break; - case GLFW_KEY_P: - switch (sparseType) { - case SPR_POINTS: sparseType = SPR_LINES; break; - case SPR_LINES: sparseType = SPR_ALL; break; - case SPR_ALL: sparseType = SPR_POINTS; break; + case GLFW_KEY_V: + if (action == GLFW_RELEASE) { + if (mod & GLFW_MOD_SHIFT) { + bRenderImageVisibility = !bRenderImageVisibility; + } else { + bRenderViews = !bRenderViews; + } + } + break; + case GLFW_KEY_W: + if (action == GLFW_RELEASE) { + if (bRenderSolid) { + bRenderSolid = false; + glPolygonMode(GL_FRONT, GL_LINE); + } else { + bRenderSolid = true; + glPolygonMode(GL_FRONT, GL_FILL); + } } - if (clbkCompilePointCloud != NULL) - clbkCompilePointCloud(); break; case GLFW_KEY_KP_SUBTRACT: if (action == GLFW_RELEASE) { if (mod & GLFW_MOD_CONTROL) - camera->SetFOV(MAXF(camera->fov-5, 5.0)); + camera.SetFOV(camera.fov-5.f); else if (mod & GLFW_MOD_SHIFT) - camera->scaleF *= 0.9f; + camera.scaleF *= 0.9f; else cameraBlend = MAXF(cameraBlend-0.1f, 0.f); } @@ -262,9 +356,9 @@ void Window::Key(int k, int /*scancode*/, int action, int mod) case GLFW_KEY_KP_ADD: if (action == GLFW_RELEASE) { if (mod & GLFW_MOD_CONTROL) - camera->SetFOV(camera->fov+5); + camera.SetFOV(camera.fov+5.f); else if (mod & GLFW_MOD_SHIFT) - camera->scaleF *= 1.11f; + camera.scaleF *= 1.1111f; else cameraBlend = MINF(cameraBlend+0.1f, 1.f); } @@ -278,24 +372,53 @@ void Window::Key(GLFWwindow* window, int k, int scancode, int action, int mod) void Window::MouseButton(int button, int action, int /*mods*/) { - if (clbkRayScene != NULL && button == GLFW_MOUSE_BUTTON_LEFT) { - typedef Eigen::Matrix Mat4; - Mat4 P, V; - glGetDoublev(GL_MODELVIEW_MATRIX, V.data()); - glGetDoublev(GL_PROJECTION_MATRIX, P.data()); - // 4d Homogeneous Clip Coordinates - const Eigen::Vector4d ray_clip(pos.x(), pos.y(), -1.0, 1.0); - // 4d Eye (Camera) Coordinates - Eigen::Vector4d ray_eye(P.inverse()*ray_clip); - ray_eye.z() = -1.0; - ray_eye.w() = 0.0; - // 4d World Coordinates - const Mat4 invV(V.inverse()); - ASSERT(ISEQUAL(invV(3,3),1.0)); - const Eigen::Vector3d start(invV.topRightCorner<3,1>()); - const Eigen::Vector4d ray_wor(invV*ray_eye); - const Eigen::Vector3d dir(ray_wor.topRows<3>().normalized()); - clbkRayScene(Ray3d(start, dir), action); + switch (button) { + case GLFW_MOUSE_BUTTON_LEFT: { + if (action == GLFW_PRESS) { + inputType.set(INP_MOUSE_LEFT); + } else + if (action == GLFW_RELEASE) { + inputType.unset(INP_MOUSE_LEFT); + glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL); + } + if (clbkRayScene != NULL) { + typedef Eigen::Matrix Mat4; + Mat4 P, V; + glGetDoublev(GL_MODELVIEW_MATRIX, V.data()); + glGetDoublev(GL_PROJECTION_MATRIX, P.data()); + // 4d Homogeneous Clip Coordinates + const Eigen::Vector4d ray_clip(pos.x(), pos.y(), -1.0, 1.0); + // 4d Eye (Camera) Coordinates + Eigen::Vector4d ray_eye(P.inverse()*ray_clip); + ray_eye.z() = -1.0; + ray_eye.w() = 0.0; + // 4d World Coordinates + const Mat4 invV(V.inverse()); + ASSERT(ISEQUAL(invV(3,3),1.0)); + const Eigen::Vector3d start(invV.topRightCorner<3,1>()); + const Eigen::Vector4d ray_wor(invV*ray_eye); + const Eigen::Vector3d dir(ray_wor.topRows<3>().normalized()); + clbkRayScene(Ray3d(start, dir), action); + } + } break; + case GLFW_MOUSE_BUTTON_MIDDLE: { + if (action == GLFW_PRESS) { + inputType.set(INP_MOUSE_MIDDLE); + glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); + } else + if (action == GLFW_RELEASE) { + inputType.unset(INP_MOUSE_MIDDLE); + glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL); + } + } break; + case GLFW_MOUSE_BUTTON_RIGHT: { + if (action == GLFW_PRESS) { + inputType.set(INP_MOUSE_RIGHT); + } else + if (action == GLFW_RELEASE) { + inputType.unset(INP_MOUSE_RIGHT); + } + } } } void Window::MouseButton(GLFWwindow* window, int button, int action, int mods) @@ -303,9 +426,25 @@ void Window::MouseButton(GLFWwindow* window, int button, int action, int mods) g_mapWindows[window]->MouseButton(button, action, mods); } +void Window::MouseMove(double xpos, double ypos) +{ + UpdateMousePosition(xpos, ypos); + if (inputType.isSet(INP_MOUSE_LEFT)) { + glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); + camera.Rotate(pos, prevPos); + } else + if (inputType.isSet(INP_MOUSE_MIDDLE)) { + camera.Translate(pos, prevPos); + } +} +void Window::MouseMove(GLFWwindow* window, double xpos, double ypos) +{ + g_mapWindows[window]->MouseMove(xpos, ypos); +} + void Window::Scroll(double /*xoffset*/, double yoffset) { - camera->dist *= (yoffset>0 ? POW(1.11,yoffset) : POW(0.9,-yoffset)); + camera.dist *= (yoffset>0 ? POW(1.11,yoffset) : POW(0.9,-yoffset)); } void Window::Scroll(GLFWwindow* window, double xoffset, double yoffset) { @@ -319,9 +458,9 @@ void Window::Drop(int count, const char** paths) String fileName(paths[0]); Util::ensureUnifySlash(fileName); if (count > 1) { - String meshFileName(paths[1]); - Util::ensureUnifySlash(meshFileName); - clbkOpenScene(fileName, meshFileName); + String geometryFileName(paths[1]); + Util::ensureUnifySlash(geometryFileName); + clbkOpenScene(fileName, geometryFileName); } else { clbkOpenScene(fileName, NULL); } diff --git a/apps/Viewer/Window.h b/apps/Viewer/Window.h index 0d9fe5034..ad9a957d0 100644 --- a/apps/Viewer/Window.h +++ b/apps/Viewer/Window.h @@ -50,10 +50,22 @@ class Window { public: GLFWwindow* window; // window handle - CameraPtr camera; // current camera (always valid) + Camera camera; // current camera Eigen::Vector2d pos, prevPos; // current and previous mouse position (normalized) + Eigen::Matrix4d transform; // view matrix corresponding to the currently selected image + cv::Size size; // resolution in pixels, sometimes not equal to window resolution, ex. on Retina display + double sizeScale; // window/screen resolution scale + + enum INPUT : unsigned { + INP_NA = 0, + INP_MOUSE_LEFT = (1 << 0), + INP_MOUSE_MIDDLE = (1 << 1), + INP_MOUSE_RIGHT = (1 << 2), + }; + Flags inputType; enum SPARSE { + SPR_NONE = 0, SPR_POINTS = (1 << 0), SPR_LINES = (1 << 1), SPR_ALL = SPR_POINTS|SPR_LINES @@ -63,8 +75,12 @@ class Window float pointSize; float cameraBlend; bool bRenderCameras; + bool bRenderCameraTrajectory; + bool bRenderImageVisibility; + bool bRenderViews; bool bRenderSolid; bool bRenderTexture; + bool bRenderBounds; enum SELECTION { SEL_NA = 0, @@ -74,17 +90,28 @@ class Window SELECTION selectionType; Point3f selectionPoints[4]; double selectionTimeClick, selectionTime; + IDX selectionIdx; typedef DELEGATE ClbkOpenScene; ClbkOpenScene clbkOpenScene; - typedef DELEGATE ClbkExportScene; + typedef DELEGATE ClbkSaveScene; + ClbkSaveScene clbkSaveScene; + typedef DELEGATE ClbkExportScene; ClbkExportScene clbkExportScene; + typedef DELEGATE ClbkCenterScene; + ClbkCenterScene clbkCenterScene; typedef DELEGATE ClbkRayScene; ClbkRayScene clbkRayScene; typedef DELEGATE ClbkCompilePointCloud; ClbkCompilePointCloud clbkCompilePointCloud; typedef DELEGATE ClbkCompileMesh; ClbkCompileMesh clbkCompileMesh; + typedef DELEGATE ClbkCompileBounds; + ClbkCompileBounds clbkCompileBounds; + typedef DELEGATE ClbkTogleSceneBox; + ClbkTogleSceneBox clbkTogleSceneBox; + typedef DELEGATE ClbkCropToBounds; + ClbkCropToBounds clbkCropToBounds; typedef std::unordered_map WindowsMap; static WindowsMap g_mapWindows; @@ -94,25 +121,35 @@ class Window ~Window(); void Release(); + void ReleaseClbk(); inline bool IsValid() const { return window != NULL; } - bool Init(int width, int height, LPCTSTR name); - void SetCamera(CameraPtr); + inline GLFWwindow* GetWindow() { return window; } + + bool Init(const cv::Size&, LPCTSTR name); + void SetCamera(const Camera&); void SetName(LPCTSTR); void SetVisible(bool); - void Reset(uint32_t minViews=2); + bool IsVisible() const; + void Reset(SPARSE sparseType=SPR_ALL, unsigned minViews=2); - inline GLFWwindow* GetWindow() { return window; } + void CenterCamera(const Point3&); void UpdateView(const ImageArr&, const MVS::ImageArr&); - void UpdateMousePosition(); + void UpdateView(const Eigen::Matrix3d& R, const Eigen::Vector3d& t); + void UpdateMousePosition(double xpos, double ypos); + + void GetFrame(Image8U3&) const; - void Resize(int width, int height); + cv::Size GetSize() const; + void Resize(const cv::Size&); static void Resize(GLFWwindow* window, int width, int height); void Key(int k, int scancode, int action, int mod); static void Key(GLFWwindow* window, int k, int scancode, int action, int mod); void MouseButton(int button, int action, int mods); static void MouseButton(GLFWwindow* window, int button, int action, int mods); + void MouseMove(double xpos, double ypos); + static void MouseMove(GLFWwindow* window, double xpos, double ypos); void Scroll(double xoffset, double yoffset); static void Scroll(GLFWwindow* window, double xoffset, double yoffset); void Drop(int count, const char** paths); diff --git a/build/Cotire.cmake b/build/Cotire.cmake deleted file mode 100644 index 97275d649..000000000 --- a/build/Cotire.cmake +++ /dev/null @@ -1,4190 +0,0 @@ -# - cotire (compile time reducer) -# -# See the cotire manual for usage hints. -# -#============================================================================= -# Copyright 2012-2018 Sascha Kratky -# -# Permission is hereby granted, free of charge, to any person -# obtaining a copy of this software and associated documentation -# files (the "Software"), to deal in the Software without -# restriction, including without limitation the rights to use, -# copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the -# Software is furnished to do so, subject to the following -# conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -# OTHER DEALINGS IN THE SOFTWARE. -#============================================================================= - -if(__COTIRE_INCLUDED) - return() -endif() -set(__COTIRE_INCLUDED TRUE) - -# call cmake_minimum_required, but prevent modification of the CMake policy stack in include mode -# cmake_minimum_required also sets the policy version as a side effect, which we have to avoid -if (NOT CMAKE_SCRIPT_MODE_FILE) - cmake_policy(PUSH) -endif() -cmake_minimum_required(VERSION 2.8.12) -if (NOT CMAKE_SCRIPT_MODE_FILE) - cmake_policy(POP) -endif() - -set (COTIRE_CMAKE_MODULE_FILE "${CMAKE_CURRENT_LIST_FILE}") -set (COTIRE_CMAKE_MODULE_VERSION "1.8.0") - -# activate select policies -if (POLICY CMP0025) - # Compiler id for Apple Clang is now AppleClang - cmake_policy(SET CMP0025 NEW) -endif() - -if (POLICY CMP0026) - # disallow use of the LOCATION target property - cmake_policy(SET CMP0026 NEW) -endif() - -if (POLICY CMP0038) - # targets may not link directly to themselves - cmake_policy(SET CMP0038 NEW) -endif() - -if (POLICY CMP0039) - # utility targets may not have link dependencies - cmake_policy(SET CMP0039 NEW) -endif() - -if (POLICY CMP0040) - # target in the TARGET signature of add_custom_command() must exist - cmake_policy(SET CMP0040 NEW) -endif() - -if (POLICY CMP0045) - # error on non-existent target in get_target_property - cmake_policy(SET CMP0045 NEW) -endif() - -if (POLICY CMP0046) - # error on non-existent dependency in add_dependencies - cmake_policy(SET CMP0046 NEW) -endif() - -if (POLICY CMP0049) - # do not expand variables in target source entries - cmake_policy(SET CMP0049 NEW) -endif() - -if (POLICY CMP0050) - # disallow add_custom_command SOURCE signatures - cmake_policy(SET CMP0050 NEW) -endif() - -if (POLICY CMP0051) - # include TARGET_OBJECTS expressions in a target's SOURCES property - cmake_policy(SET CMP0051 NEW) -endif() - -if (POLICY CMP0053) - # simplify variable reference and escape sequence evaluation - cmake_policy(SET CMP0053 NEW) -endif() - -if (POLICY CMP0054) - # only interpret if() arguments as variables or keywords when unquoted - cmake_policy(SET CMP0054 NEW) -endif() - -if (POLICY CMP0055) - # strict checking for break() command - cmake_policy(SET CMP0055 NEW) -endif() - -include(CMakeParseArguments) -include(ProcessorCount) - -function (cotire_get_configuration_types _configsVar) - set (_configs "") - if (CMAKE_CONFIGURATION_TYPES) - list (APPEND _configs ${CMAKE_CONFIGURATION_TYPES}) - endif() - if (CMAKE_BUILD_TYPE) - list (APPEND _configs "${CMAKE_BUILD_TYPE}") - endif() - if (_configs) - list (REMOVE_DUPLICATES _configs) - set (${_configsVar} ${_configs} PARENT_SCOPE) - else() - set (${_configsVar} "None" PARENT_SCOPE) - endif() -endfunction() - -function (cotire_get_source_file_extension _sourceFile _extVar) - # get_filename_component returns extension from first occurrence of . in file name - # this function computes the extension from last occurrence of . in file name - string (FIND "${_sourceFile}" "." _index REVERSE) - if (_index GREATER -1) - math (EXPR _index "${_index} + 1") - string (SUBSTRING "${_sourceFile}" ${_index} -1 _sourceExt) - else() - set (_sourceExt "") - endif() - set (${_extVar} "${_sourceExt}" PARENT_SCOPE) -endfunction() - -macro (cotire_check_is_path_relative_to _path _isRelativeVar) - set (${_isRelativeVar} FALSE) - if (IS_ABSOLUTE "${_path}") - foreach (_dir ${ARGN}) - file (RELATIVE_PATH _relPath "${_dir}" "${_path}") - if (NOT _relPath OR (NOT IS_ABSOLUTE "${_relPath}" AND NOT "${_relPath}" MATCHES "^\\.\\.")) - set (${_isRelativeVar} TRUE) - break() - endif() - endforeach() - endif() -endmacro() - -function (cotire_filter_language_source_files _language _target _sourceFilesVar _excludedSourceFilesVar _cotiredSourceFilesVar) - if (CMAKE_${_language}_SOURCE_FILE_EXTENSIONS) - set (_languageExtensions "${CMAKE_${_language}_SOURCE_FILE_EXTENSIONS}") - else() - set (_languageExtensions "") - endif() - if (CMAKE_${_language}_IGNORE_EXTENSIONS) - set (_ignoreExtensions "${CMAKE_${_language}_IGNORE_EXTENSIONS}") - else() - set (_ignoreExtensions "") - endif() - if (COTIRE_UNITY_SOURCE_EXCLUDE_EXTENSIONS) - set (_excludeExtensions "${COTIRE_UNITY_SOURCE_EXCLUDE_EXTENSIONS}") - else() - set (_excludeExtensions "") - endif() - if (COTIRE_DEBUG AND _languageExtensions) - message (STATUS "${_language} source file extensions: ${_languageExtensions}") - endif() - if (COTIRE_DEBUG AND _ignoreExtensions) - message (STATUS "${_language} ignore extensions: ${_ignoreExtensions}") - endif() - if (COTIRE_DEBUG AND _excludeExtensions) - message (STATUS "${_language} exclude extensions: ${_excludeExtensions}") - endif() - if (CMAKE_VERSION VERSION_LESS "3.1.0") - set (_allSourceFiles ${ARGN}) - else() - # as of CMake 3.1 target sources may contain generator expressions - # since we cannot obtain required property information about source files added - # through generator expressions at configure time, we filter them out - string (GENEX_STRIP "${ARGN}" _allSourceFiles) - endif() - set (_filteredSourceFiles "") - set (_excludedSourceFiles "") - foreach (_sourceFile ${_allSourceFiles}) - get_source_file_property(_sourceIsHeaderOnly "${_sourceFile}" HEADER_FILE_ONLY) - get_source_file_property(_sourceIsExternal "${_sourceFile}" EXTERNAL_OBJECT) - get_source_file_property(_sourceIsSymbolic "${_sourceFile}" SYMBOLIC) - if (NOT _sourceIsHeaderOnly AND NOT _sourceIsExternal AND NOT _sourceIsSymbolic) - cotire_get_source_file_extension("${_sourceFile}" _sourceExt) - if (_sourceExt) - list (FIND _ignoreExtensions "${_sourceExt}" _ignoreIndex) - if (_ignoreIndex LESS 0) - list (FIND _excludeExtensions "${_sourceExt}" _excludeIndex) - if (_excludeIndex GREATER -1) - list (APPEND _excludedSourceFiles "${_sourceFile}") - else() - list (FIND _languageExtensions "${_sourceExt}" _sourceIndex) - if (_sourceIndex GREATER -1) - # consider source file unless it is excluded explicitly - get_source_file_property(_sourceIsExcluded "${_sourceFile}" COTIRE_EXCLUDED) - if (_sourceIsExcluded) - list (APPEND _excludedSourceFiles "${_sourceFile}") - else() - list (APPEND _filteredSourceFiles "${_sourceFile}") - endif() - else() - get_source_file_property(_sourceLanguage "${_sourceFile}" LANGUAGE) - if ("${_sourceLanguage}" STREQUAL "${_language}") - # add to excluded sources, if file is not ignored and has correct language without having the correct extension - list (APPEND _excludedSourceFiles "${_sourceFile}") - endif() - endif() - endif() - endif() - endif() - endif() - endforeach() - # separate filtered source files from already cotired ones - # the COTIRE_TARGET property of a source file may be set while a target is being processed by cotire - set (_sourceFiles "") - set (_cotiredSourceFiles "") - foreach (_sourceFile ${_filteredSourceFiles}) - get_source_file_property(_sourceIsCotired "${_sourceFile}" COTIRE_TARGET) - if (_sourceIsCotired) - list (APPEND _cotiredSourceFiles "${_sourceFile}") - else() - get_source_file_property(_sourceCompileFlags "${_sourceFile}" COMPILE_FLAGS) - if (_sourceCompileFlags) - # add to excluded sources, if file has custom compile flags - list (APPEND _excludedSourceFiles "${_sourceFile}") - else() - get_source_file_property(_sourceCompileOptions "${_sourceFile}" COMPILE_OPTIONS) - if (_sourceCompileOptions) - # add to excluded sources, if file has list of custom compile options - list (APPEND _excludedSourceFiles "${_sourceFile}") - else() - list (APPEND _sourceFiles "${_sourceFile}") - endif() - endif() - endif() - endforeach() - if (COTIRE_DEBUG) - if (_sourceFiles) - message (STATUS "Filtered ${_target} ${_language} sources: ${_sourceFiles}") - endif() - if (_excludedSourceFiles) - message (STATUS "Excluded ${_target} ${_language} sources: ${_excludedSourceFiles}") - endif() - if (_cotiredSourceFiles) - message (STATUS "Cotired ${_target} ${_language} sources: ${_cotiredSourceFiles}") - endif() - endif() - set (${_sourceFilesVar} ${_sourceFiles} PARENT_SCOPE) - set (${_excludedSourceFilesVar} ${_excludedSourceFiles} PARENT_SCOPE) - set (${_cotiredSourceFilesVar} ${_cotiredSourceFiles} PARENT_SCOPE) -endfunction() - -function (cotire_get_objects_with_property_on _filteredObjectsVar _property _type) - set (_filteredObjects "") - foreach (_object ${ARGN}) - get_property(_isSet ${_type} "${_object}" PROPERTY ${_property} SET) - if (_isSet) - get_property(_propertyValue ${_type} "${_object}" PROPERTY ${_property}) - if (_propertyValue) - list (APPEND _filteredObjects "${_object}") - endif() - endif() - endforeach() - set (${_filteredObjectsVar} ${_filteredObjects} PARENT_SCOPE) -endfunction() - -function (cotire_get_objects_with_property_off _filteredObjectsVar _property _type) - set (_filteredObjects "") - foreach (_object ${ARGN}) - get_property(_isSet ${_type} "${_object}" PROPERTY ${_property} SET) - if (_isSet) - get_property(_propertyValue ${_type} "${_object}" PROPERTY ${_property}) - if (NOT _propertyValue) - list (APPEND _filteredObjects "${_object}") - endif() - endif() - endforeach() - set (${_filteredObjectsVar} ${_filteredObjects} PARENT_SCOPE) -endfunction() - -function (cotire_get_source_file_property_values _valuesVar _property) - set (_values "") - foreach (_sourceFile ${ARGN}) - get_source_file_property(_propertyValue "${_sourceFile}" ${_property}) - if (_propertyValue) - list (APPEND _values "${_propertyValue}") - endif() - endforeach() - set (${_valuesVar} ${_values} PARENT_SCOPE) -endfunction() - -function (cotire_resolve_config_properties _configurations _propertiesVar) - set (_properties "") - foreach (_property ${ARGN}) - if ("${_property}" MATCHES "") - foreach (_config ${_configurations}) - string (TOUPPER "${_config}" _upperConfig) - string (REPLACE "" "${_upperConfig}" _configProperty "${_property}") - list (APPEND _properties ${_configProperty}) - endforeach() - else() - list (APPEND _properties ${_property}) - endif() - endforeach() - set (${_propertiesVar} ${_properties} PARENT_SCOPE) -endfunction() - -function (cotire_copy_set_properties _configurations _type _source _target) - cotire_resolve_config_properties("${_configurations}" _properties ${ARGN}) - foreach (_property ${_properties}) - get_property(_isSet ${_type} ${_source} PROPERTY ${_property} SET) - if (_isSet) - get_property(_propertyValue ${_type} ${_source} PROPERTY ${_property}) - set_property(${_type} ${_target} PROPERTY ${_property} "${_propertyValue}") - endif() - endforeach() -endfunction() - -function (cotire_get_target_usage_requirements _target _config _targetRequirementsVar) - set (_targetRequirements "") - get_target_property(_librariesToProcess ${_target} LINK_LIBRARIES) - while (_librariesToProcess) - # remove from head - list (GET _librariesToProcess 0 _library) - list (REMOVE_AT _librariesToProcess 0) - if (_library MATCHES "^\\$<\\$:([A-Za-z0-9_:-]+)>$") - set (_library "${CMAKE_MATCH_1}") - elseif (_config STREQUAL "None" AND _library MATCHES "^\\$<\\$:([A-Za-z0-9_:-]+)>$") - set (_library "${CMAKE_MATCH_1}") - endif() - if (TARGET ${_library}) - list (FIND _targetRequirements ${_library} _index) - if (_index LESS 0) - list (APPEND _targetRequirements ${_library}) - # BFS traversal of transitive libraries - get_target_property(_libraries ${_library} INTERFACE_LINK_LIBRARIES) - if (_libraries) - list (APPEND _librariesToProcess ${_libraries}) - list (REMOVE_DUPLICATES _librariesToProcess) - endif() - endif() - endif() - endwhile() - set (${_targetRequirementsVar} ${_targetRequirements} PARENT_SCOPE) -endfunction() - -function (cotire_filter_compile_flags _language _flagFilter _matchedOptionsVar _unmatchedOptionsVar) - if (WIN32 AND CMAKE_${_language}_COMPILER_ID MATCHES "MSVC|Intel") - set (_flagPrefix "[/-]") - else() - set (_flagPrefix "--?") - endif() - set (_optionFlag "") - set (_matchedOptions "") - set (_unmatchedOptions "") - foreach (_compileFlag ${ARGN}) - if (_compileFlag) - if (_optionFlag AND NOT "${_compileFlag}" MATCHES "^${_flagPrefix}") - # option with separate argument - list (APPEND _matchedOptions "${_compileFlag}") - set (_optionFlag "") - elseif ("${_compileFlag}" MATCHES "^(${_flagPrefix})(${_flagFilter})$") - # remember option - set (_optionFlag "${CMAKE_MATCH_2}") - elseif ("${_compileFlag}" MATCHES "^(${_flagPrefix})(${_flagFilter})(.+)$") - # option with joined argument - list (APPEND _matchedOptions "${CMAKE_MATCH_3}") - set (_optionFlag "") - else() - # flush remembered option - if (_optionFlag) - list (APPEND _matchedOptions "${_optionFlag}") - set (_optionFlag "") - endif() - # add to unfiltered options - list (APPEND _unmatchedOptions "${_compileFlag}") - endif() - endif() - endforeach() - if (_optionFlag) - list (APPEND _matchedOptions "${_optionFlag}") - endif() - if (COTIRE_DEBUG AND _matchedOptions) - message (STATUS "Filter ${_flagFilter} matched: ${_matchedOptions}") - endif() - if (COTIRE_DEBUG AND _unmatchedOptions) - message (STATUS "Filter ${_flagFilter} unmatched: ${_unmatchedOptions}") - endif() - set (${_matchedOptionsVar} ${_matchedOptions} PARENT_SCOPE) - set (${_unmatchedOptionsVar} ${_unmatchedOptions} PARENT_SCOPE) -endfunction() - -function (cotire_is_target_supported _target _isSupportedVar) - if (NOT TARGET "${_target}") - set (${_isSupportedVar} FALSE PARENT_SCOPE) - return() - endif() - get_target_property(_imported ${_target} IMPORTED) - if (_imported) - set (${_isSupportedVar} FALSE PARENT_SCOPE) - return() - endif() - get_target_property(_targetType ${_target} TYPE) - if (NOT _targetType MATCHES "EXECUTABLE|(STATIC|SHARED|MODULE|OBJECT)_LIBRARY") - set (${_isSupportedVar} FALSE PARENT_SCOPE) - return() - endif() - set (${_isSupportedVar} TRUE PARENT_SCOPE) -endfunction() - -function (cotire_get_target_compile_flags _config _language _target _flagsVar) - string (TOUPPER "${_config}" _upperConfig) - # collect options from CMake language variables - set (_compileFlags "") - if (CMAKE_${_language}_FLAGS) - set (_compileFlags "${_compileFlags} ${CMAKE_${_language}_FLAGS}") - endif() - if (CMAKE_${_language}_FLAGS_${_upperConfig}) - set (_compileFlags "${_compileFlags} ${CMAKE_${_language}_FLAGS_${_upperConfig}}") - endif() - if (_target) - # add target compile flags - get_target_property(_targetflags ${_target} COMPILE_FLAGS) - if (_targetflags) - set (_compileFlags "${_compileFlags} ${_targetflags}") - endif() - endif() - if (UNIX) - separate_arguments(_compileFlags UNIX_COMMAND "${_compileFlags}") - elseif(WIN32) - separate_arguments(_compileFlags WINDOWS_COMMAND "${_compileFlags}") - else() - separate_arguments(_compileFlags) - endif() - # target compile options - if (_target) - get_target_property(_targetOptions ${_target} COMPILE_OPTIONS) - if (_targetOptions) - list (APPEND _compileFlags ${_targetOptions}) - endif() - endif() - # interface compile options from linked library targets - if (_target) - set (_linkedTargets "") - cotire_get_target_usage_requirements(${_target} ${_config} _linkedTargets) - foreach (_linkedTarget ${_linkedTargets}) - get_target_property(_targetOptions ${_linkedTarget} INTERFACE_COMPILE_OPTIONS) - if (_targetOptions) - list (APPEND _compileFlags ${_targetOptions}) - endif() - endforeach() - endif() - # handle language standard properties - if (CMAKE_${_language}_STANDARD_DEFAULT) - # used compiler supports language standard levels - if (_target) - get_target_property(_targetLanguageStandard ${_target} ${_language}_STANDARD) - if (_targetLanguageStandard) - set (_type "EXTENSION") - get_property(_isSet TARGET ${_target} PROPERTY ${_language}_EXTENSIONS SET) - if (_isSet) - get_target_property(_targetUseLanguageExtensions ${_target} ${_language}_EXTENSIONS) - if (NOT _targetUseLanguageExtensions) - set (_type "STANDARD") - endif() - endif() - if (CMAKE_${_language}${_targetLanguageStandard}_${_type}_COMPILE_OPTION) - list (APPEND _compileFlags "${CMAKE_${_language}${_targetLanguageStandard}_${_type}_COMPILE_OPTION}") - endif() - endif() - endif() - endif() - # handle the POSITION_INDEPENDENT_CODE target property - if (_target) - get_target_property(_targetPIC ${_target} POSITION_INDEPENDENT_CODE) - if (_targetPIC) - get_target_property(_targetType ${_target} TYPE) - if (_targetType STREQUAL "EXECUTABLE" AND CMAKE_${_language}_COMPILE_OPTIONS_PIE) - list (APPEND _compileFlags "${CMAKE_${_language}_COMPILE_OPTIONS_PIE}") - elseif (CMAKE_${_language}_COMPILE_OPTIONS_PIC) - list (APPEND _compileFlags "${CMAKE_${_language}_COMPILE_OPTIONS_PIC}") - endif() - endif() - endif() - # handle visibility target properties - if (_target) - get_target_property(_targetVisibility ${_target} ${_language}_VISIBILITY_PRESET) - if (_targetVisibility AND CMAKE_${_language}_COMPILE_OPTIONS_VISIBILITY) - list (APPEND _compileFlags "${CMAKE_${_language}_COMPILE_OPTIONS_VISIBILITY}${_targetVisibility}") - endif() - get_target_property(_targetVisibilityInlines ${_target} VISIBILITY_INLINES_HIDDEN) - if (_targetVisibilityInlines AND CMAKE_${_language}_COMPILE_OPTIONS_VISIBILITY_INLINES_HIDDEN) - list (APPEND _compileFlags "${CMAKE_${_language}_COMPILE_OPTIONS_VISIBILITY_INLINES_HIDDEN}") - endif() - endif() - # platform specific flags - if (APPLE) - get_target_property(_architectures ${_target} OSX_ARCHITECTURES_${_upperConfig}) - if (NOT _architectures) - get_target_property(_architectures ${_target} OSX_ARCHITECTURES) - endif() - if (_architectures) - foreach (_arch ${_architectures}) - list (APPEND _compileFlags "-arch" "${_arch}") - endforeach() - endif() - if (CMAKE_OSX_SYSROOT) - if (CMAKE_${_language}_SYSROOT_FLAG) - list (APPEND _compileFlags "${CMAKE_${_language}_SYSROOT_FLAG}" "${CMAKE_OSX_SYSROOT}") - else() - list (APPEND _compileFlags "-isysroot" "${CMAKE_OSX_SYSROOT}") - endif() - endif() - if (CMAKE_OSX_DEPLOYMENT_TARGET) - if (CMAKE_${_language}_OSX_DEPLOYMENT_TARGET_FLAG) - list (APPEND _compileFlags "${CMAKE_${_language}_OSX_DEPLOYMENT_TARGET_FLAG}${CMAKE_OSX_DEPLOYMENT_TARGET}") - else() - list (APPEND _compileFlags "-mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET}") - endif() - endif() - endif() - if (COTIRE_DEBUG AND _compileFlags) - message (STATUS "Target ${_target} compile flags: ${_compileFlags}") - endif() - set (${_flagsVar} ${_compileFlags} PARENT_SCOPE) -endfunction() - -function (cotire_get_target_include_directories _config _language _target _includeDirsVar _systemIncludeDirsVar) - set (_includeDirs "") - set (_systemIncludeDirs "") - # default include dirs - if (CMAKE_INCLUDE_CURRENT_DIR) - list (APPEND _includeDirs "${CMAKE_CURRENT_BINARY_DIR}") - list (APPEND _includeDirs "${CMAKE_CURRENT_SOURCE_DIR}") - endif() - set (_targetFlags "") - cotire_get_target_compile_flags("${_config}" "${_language}" "${_target}" _targetFlags) - # parse additional include directories from target compile flags - if (CMAKE_INCLUDE_FLAG_${_language}) - string (STRIP "${CMAKE_INCLUDE_FLAG_${_language}}" _includeFlag) - string (REGEX REPLACE "^[-/]+" "" _includeFlag "${_includeFlag}") - if (_includeFlag) - set (_dirs "") - cotire_filter_compile_flags("${_language}" "${_includeFlag}" _dirs _ignore ${_targetFlags}) - if (_dirs) - list (APPEND _includeDirs ${_dirs}) - endif() - endif() - endif() - # parse additional system include directories from target compile flags - if (CMAKE_INCLUDE_SYSTEM_FLAG_${_language}) - string (STRIP "${CMAKE_INCLUDE_SYSTEM_FLAG_${_language}}" _includeFlag) - string (REGEX REPLACE "^[-/]+" "" _includeFlag "${_includeFlag}") - if (_includeFlag) - set (_dirs "") - cotire_filter_compile_flags("${_language}" "${_includeFlag}" _dirs _ignore ${_targetFlags}) - if (_dirs) - list (APPEND _systemIncludeDirs ${_dirs}) - endif() - endif() - endif() - # target include directories - get_directory_property(_dirs DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" INCLUDE_DIRECTORIES) - if (_target) - get_target_property(_targetDirs ${_target} INCLUDE_DIRECTORIES) - if (_targetDirs) - list (APPEND _dirs ${_targetDirs}) - endif() - get_target_property(_targetDirs ${_target} INTERFACE_SYSTEM_INCLUDE_DIRECTORIES) - if (_targetDirs) - list (APPEND _systemIncludeDirs ${_targetDirs}) - endif() - endif() - # interface include directories from linked library targets - if (_target) - set (_linkedTargets "") - cotire_get_target_usage_requirements(${_target} ${_config} _linkedTargets) - foreach (_linkedTarget ${_linkedTargets}) - get_target_property(_linkedTargetType ${_linkedTarget} TYPE) - if (CMAKE_INCLUDE_CURRENT_DIR_IN_INTERFACE AND NOT CMAKE_VERSION VERSION_LESS "3.4.0" AND - _linkedTargetType MATCHES "(STATIC|SHARED|MODULE|OBJECT)_LIBRARY") - # CMAKE_INCLUDE_CURRENT_DIR_IN_INTERFACE refers to CMAKE_CURRENT_BINARY_DIR and CMAKE_CURRENT_SOURCE_DIR - # at the time, when the target was created. These correspond to the target properties BINARY_DIR and SOURCE_DIR - # which are only available with CMake 3.4 or later. - get_target_property(_targetDirs ${_linkedTarget} BINARY_DIR) - if (_targetDirs) - list (APPEND _dirs ${_targetDirs}) - endif() - get_target_property(_targetDirs ${_linkedTarget} SOURCE_DIR) - if (_targetDirs) - list (APPEND _dirs ${_targetDirs}) - endif() - endif() - get_target_property(_targetDirs ${_linkedTarget} INTERFACE_INCLUDE_DIRECTORIES) - if (_targetDirs) - list (APPEND _dirs ${_targetDirs}) - endif() - get_target_property(_targetDirs ${_linkedTarget} INTERFACE_SYSTEM_INCLUDE_DIRECTORIES) - if (_targetDirs) - list (APPEND _systemIncludeDirs ${_targetDirs}) - endif() - endforeach() - endif() - if (dirs) - list (REMOVE_DUPLICATES _dirs) - endif() - list (LENGTH _includeDirs _projectInsertIndex) - foreach (_dir ${_dirs}) - if (CMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE) - cotire_check_is_path_relative_to("${_dir}" _isRelative "${CMAKE_SOURCE_DIR}" "${CMAKE_BINARY_DIR}") - if (_isRelative) - list (LENGTH _includeDirs _len) - if (_len EQUAL _projectInsertIndex) - list (APPEND _includeDirs "${_dir}") - else() - list (INSERT _includeDirs _projectInsertIndex "${_dir}") - endif() - math (EXPR _projectInsertIndex "${_projectInsertIndex} + 1") - else() - list (APPEND _includeDirs "${_dir}") - endif() - else() - list (APPEND _includeDirs "${_dir}") - endif() - endforeach() - list (REMOVE_DUPLICATES _includeDirs) - list (REMOVE_DUPLICATES _systemIncludeDirs) - if (CMAKE_${_language}_IMPLICIT_INCLUDE_DIRECTORIES) - list (REMOVE_ITEM _includeDirs ${CMAKE_${_language}_IMPLICIT_INCLUDE_DIRECTORIES}) - endif() - if (WIN32 AND NOT MINGW) - # convert Windows paths in include directories to CMake paths - if (_includeDirs) - set (_paths "") - foreach (_dir ${_includeDirs}) - file (TO_CMAKE_PATH "${_dir}" _path) - list (APPEND _paths "${_path}") - endforeach() - set (_includeDirs ${_paths}) - endif() - if (_systemIncludeDirs) - set (_paths "") - foreach (_dir ${_systemIncludeDirs}) - file (TO_CMAKE_PATH "${_dir}" _path) - list (APPEND _paths "${_path}") - endforeach() - set (_systemIncludeDirs ${_paths}) - endif() - endif() - if (COTIRE_DEBUG AND _includeDirs) - message (STATUS "Target ${_target} include dirs: ${_includeDirs}") - endif() - set (${_includeDirsVar} ${_includeDirs} PARENT_SCOPE) - if (COTIRE_DEBUG AND _systemIncludeDirs) - message (STATUS "Target ${_target} system include dirs: ${_systemIncludeDirs}") - endif() - set (${_systemIncludeDirsVar} ${_systemIncludeDirs} PARENT_SCOPE) -endfunction() - -function (cotire_get_target_export_symbol _target _exportSymbolVar) - set (_exportSymbol "") - get_target_property(_targetType ${_target} TYPE) - get_target_property(_enableExports ${_target} ENABLE_EXPORTS) - if (_targetType MATCHES "(SHARED|MODULE)_LIBRARY" OR - (_targetType STREQUAL "EXECUTABLE" AND _enableExports)) - get_target_property(_exportSymbol ${_target} DEFINE_SYMBOL) - if (NOT _exportSymbol) - set (_exportSymbol "${_target}_EXPORTS") - endif() - string (MAKE_C_IDENTIFIER "${_exportSymbol}" _exportSymbol) - endif() - set (${_exportSymbolVar} ${_exportSymbol} PARENT_SCOPE) -endfunction() - -function (cotire_get_target_compile_definitions _config _language _target _definitionsVar) - string (TOUPPER "${_config}" _upperConfig) - set (_configDefinitions "") - # CMAKE_INTDIR for multi-configuration build systems - if (NOT "${CMAKE_CFG_INTDIR}" STREQUAL ".") - list (APPEND _configDefinitions "CMAKE_INTDIR=\"${_config}\"") - endif() - # target export define symbol - cotire_get_target_export_symbol("${_target}" _defineSymbol) - if (_defineSymbol) - list (APPEND _configDefinitions "${_defineSymbol}") - endif() - # directory compile definitions - get_directory_property(_definitions DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" COMPILE_DEFINITIONS) - if (_definitions) - list (APPEND _configDefinitions ${_definitions}) - endif() - get_directory_property(_definitions DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" COMPILE_DEFINITIONS_${_upperConfig}) - if (_definitions) - list (APPEND _configDefinitions ${_definitions}) - endif() - # target compile definitions - get_target_property(_definitions ${_target} COMPILE_DEFINITIONS) - if (_definitions) - list (APPEND _configDefinitions ${_definitions}) - endif() - get_target_property(_definitions ${_target} COMPILE_DEFINITIONS_${_upperConfig}) - if (_definitions) - list (APPEND _configDefinitions ${_definitions}) - endif() - # interface compile definitions from linked library targets - set (_linkedTargets "") - cotire_get_target_usage_requirements(${_target} ${_config} _linkedTargets) - foreach (_linkedTarget ${_linkedTargets}) - get_target_property(_definitions ${_linkedTarget} INTERFACE_COMPILE_DEFINITIONS) - if (_definitions) - list (APPEND _configDefinitions ${_definitions}) - endif() - endforeach() - # parse additional compile definitions from target compile flags - # and do not look at directory compile definitions, which we already handled - set (_targetFlags "") - cotire_get_target_compile_flags("${_config}" "${_language}" "${_target}" _targetFlags) - cotire_filter_compile_flags("${_language}" "D" _definitions _ignore ${_targetFlags}) - if (_definitions) - list (APPEND _configDefinitions ${_definitions}) - endif() - list (REMOVE_DUPLICATES _configDefinitions) - if (COTIRE_DEBUG AND _configDefinitions) - message (STATUS "Target ${_target} compile definitions: ${_configDefinitions}") - endif() - set (${_definitionsVar} ${_configDefinitions} PARENT_SCOPE) -endfunction() - -function (cotire_get_target_compiler_flags _config _language _target _compilerFlagsVar) - # parse target compile flags omitting compile definitions and include directives - set (_targetFlags "") - cotire_get_target_compile_flags("${_config}" "${_language}" "${_target}" _targetFlags) - set (_flagFilter "D") - if (CMAKE_INCLUDE_FLAG_${_language}) - string (STRIP "${CMAKE_INCLUDE_FLAG_${_language}}" _includeFlag) - string (REGEX REPLACE "^[-/]+" "" _includeFlag "${_includeFlag}") - if (_includeFlag) - set (_flagFilter "${_flagFilter}|${_includeFlag}") - endif() - endif() - if (CMAKE_INCLUDE_SYSTEM_FLAG_${_language}) - string (STRIP "${CMAKE_INCLUDE_SYSTEM_FLAG_${_language}}" _includeFlag) - string (REGEX REPLACE "^[-/]+" "" _includeFlag "${_includeFlag}") - if (_includeFlag) - set (_flagFilter "${_flagFilter}|${_includeFlag}") - endif() - endif() - set (_compilerFlags "") - cotire_filter_compile_flags("${_language}" "${_flagFilter}" _ignore _compilerFlags ${_targetFlags}) - if (COTIRE_DEBUG AND _compilerFlags) - message (STATUS "Target ${_target} compiler flags: ${_compilerFlags}") - endif() - set (${_compilerFlagsVar} ${_compilerFlags} PARENT_SCOPE) -endfunction() - -function (cotire_add_sys_root_paths _pathsVar) - if (APPLE) - if (CMAKE_OSX_SYSROOT AND CMAKE_${_language}_HAS_ISYSROOT) - foreach (_path IN LISTS ${_pathsVar}) - if (IS_ABSOLUTE "${_path}") - get_filename_component(_path "${CMAKE_OSX_SYSROOT}/${_path}" ABSOLUTE) - if (EXISTS "${_path}") - list (APPEND ${_pathsVar} "${_path}") - endif() - endif() - endforeach() - endif() - endif() - set (${_pathsVar} ${${_pathsVar}} PARENT_SCOPE) -endfunction() - -function (cotire_get_source_extra_properties _sourceFile _pattern _resultVar) - set (_extraProperties ${ARGN}) - set (_result "") - if (_extraProperties) - list (FIND _extraProperties "${_sourceFile}" _index) - if (_index GREATER -1) - math (EXPR _index "${_index} + 1") - list (LENGTH _extraProperties _len) - math (EXPR _len "${_len} - 1") - foreach (_index RANGE ${_index} ${_len}) - list (GET _extraProperties ${_index} _value) - if (_value MATCHES "${_pattern}") - list (APPEND _result "${_value}") - else() - break() - endif() - endforeach() - endif() - endif() - set (${_resultVar} ${_result} PARENT_SCOPE) -endfunction() - -function (cotire_get_source_compile_definitions _config _language _sourceFile _definitionsVar) - set (_compileDefinitions "") - if (NOT CMAKE_SCRIPT_MODE_FILE) - string (TOUPPER "${_config}" _upperConfig) - get_source_file_property(_definitions "${_sourceFile}" COMPILE_DEFINITIONS) - if (_definitions) - list (APPEND _compileDefinitions ${_definitions}) - endif() - get_source_file_property(_definitions "${_sourceFile}" COMPILE_DEFINITIONS_${_upperConfig}) - if (_definitions) - list (APPEND _compileDefinitions ${_definitions}) - endif() - endif() - cotire_get_source_extra_properties("${_sourceFile}" "^[a-zA-Z0-9_]+(=.*)?$" _definitions ${ARGN}) - if (_definitions) - list (APPEND _compileDefinitions ${_definitions}) - endif() - if (COTIRE_DEBUG AND _compileDefinitions) - message (STATUS "Source ${_sourceFile} compile definitions: ${_compileDefinitions}") - endif() - set (${_definitionsVar} ${_compileDefinitions} PARENT_SCOPE) -endfunction() - -function (cotire_get_source_files_compile_definitions _config _language _definitionsVar) - set (_configDefinitions "") - foreach (_sourceFile ${ARGN}) - cotire_get_source_compile_definitions("${_config}" "${_language}" "${_sourceFile}" _sourceDefinitions) - if (_sourceDefinitions) - list (APPEND _configDefinitions "${_sourceFile}" ${_sourceDefinitions} "-") - endif() - endforeach() - set (${_definitionsVar} ${_configDefinitions} PARENT_SCOPE) -endfunction() - -function (cotire_get_source_undefs _sourceFile _property _sourceUndefsVar) - set (_sourceUndefs "") - if (NOT CMAKE_SCRIPT_MODE_FILE) - get_source_file_property(_undefs "${_sourceFile}" ${_property}) - if (_undefs) - list (APPEND _sourceUndefs ${_undefs}) - endif() - endif() - cotire_get_source_extra_properties("${_sourceFile}" "^[a-zA-Z0-9_]+$" _undefs ${ARGN}) - if (_undefs) - list (APPEND _sourceUndefs ${_undefs}) - endif() - if (COTIRE_DEBUG AND _sourceUndefs) - message (STATUS "Source ${_sourceFile} ${_property} undefs: ${_sourceUndefs}") - endif() - set (${_sourceUndefsVar} ${_sourceUndefs} PARENT_SCOPE) -endfunction() - -function (cotire_get_source_files_undefs _property _sourceUndefsVar) - set (_sourceUndefs "") - foreach (_sourceFile ${ARGN}) - cotire_get_source_undefs("${_sourceFile}" ${_property} _undefs) - if (_undefs) - list (APPEND _sourceUndefs "${_sourceFile}" ${_undefs} "-") - endif() - endforeach() - set (${_sourceUndefsVar} ${_sourceUndefs} PARENT_SCOPE) -endfunction() - -macro (cotire_set_cmd_to_prologue _cmdVar) - set (${_cmdVar} "${CMAKE_COMMAND}") - if (COTIRE_DEBUG) - list (APPEND ${_cmdVar} "--warn-uninitialized") - endif() - list (APPEND ${_cmdVar} "-DCOTIRE_BUILD_TYPE:STRING=$") - if (XCODE) - list (APPEND ${_cmdVar} "-DXCODE:BOOL=TRUE") - endif() - if (COTIRE_VERBOSE) - list (APPEND ${_cmdVar} "-DCOTIRE_VERBOSE:BOOL=ON") - elseif("${CMAKE_GENERATOR}" MATCHES "Makefiles") - list (APPEND ${_cmdVar} "-DCOTIRE_VERBOSE:BOOL=$(VERBOSE)") - endif() -endmacro() - -function (cotire_init_compile_cmd _cmdVar _language _compilerLauncher _compilerExe _compilerArg1) - if (NOT _compilerLauncher) - set (_compilerLauncher ${CMAKE_${_language}_COMPILER_LAUNCHER}) - endif() - if (NOT _compilerExe) - set (_compilerExe "${CMAKE_${_language}_COMPILER}") - endif() - if (NOT _compilerArg1) - set (_compilerArg1 ${CMAKE_${_language}_COMPILER_ARG1}) - endif() - if (WIN32) - file (TO_NATIVE_PATH "${_compilerExe}" _compilerExe) - endif() - string (STRIP "${_compilerArg1}" _compilerArg1) - if ("${CMAKE_GENERATOR}" MATCHES "Make|Ninja") - # compiler launcher is only supported for Makefile and Ninja - set (${_cmdVar} ${_compilerLauncher} "${_compilerExe}" ${_compilerArg1} PARENT_SCOPE) - else() - set (${_cmdVar} "${_compilerExe}" ${_compilerArg1} PARENT_SCOPE) - endif() -endfunction() - -macro (cotire_add_definitions_to_cmd _cmdVar _language) - foreach (_definition ${ARGN}) - if (WIN32 AND CMAKE_${_language}_COMPILER_ID MATCHES "MSVC|Intel") - list (APPEND ${_cmdVar} "/D${_definition}") - else() - list (APPEND ${_cmdVar} "-D${_definition}") - endif() - endforeach() -endmacro() - -function (cotire_add_includes_to_cmd _cmdVar _language _includesVar _systemIncludesVar) - set (_includeDirs ${${_includesVar}} ${${_systemIncludesVar}}) - if (_includeDirs) - list (REMOVE_DUPLICATES _includeDirs) - foreach (_include ${_includeDirs}) - if (WIN32 AND CMAKE_${_language}_COMPILER_ID MATCHES "MSVC|Intel") - file (TO_NATIVE_PATH "${_include}" _include) - list (APPEND ${_cmdVar} "${CMAKE_INCLUDE_FLAG_${_language}}${CMAKE_INCLUDE_FLAG_SEP_${_language}}${_include}") - else() - set (_index -1) - if ("${CMAKE_INCLUDE_SYSTEM_FLAG_${_language}}" MATCHES ".+") - list (FIND ${_systemIncludesVar} "${_include}" _index) - endif() - if (_index GREATER -1) - list (APPEND ${_cmdVar} "${CMAKE_INCLUDE_SYSTEM_FLAG_${_language}}${CMAKE_INCLUDE_FLAG_SEP_${_language}}${_include}") - else() - list (APPEND ${_cmdVar} "${CMAKE_INCLUDE_FLAG_${_language}}${CMAKE_INCLUDE_FLAG_SEP_${_language}}${_include}") - endif() - endif() - endforeach() - endif() - set (${_cmdVar} ${${_cmdVar}} PARENT_SCOPE) -endfunction() - -function (cotire_add_frameworks_to_cmd _cmdVar _language _includesVar _systemIncludesVar) - if (APPLE) - set (_frameworkDirs "") - foreach (_include ${${_includesVar}}) - if (IS_ABSOLUTE "${_include}" AND _include MATCHES "\\.framework$") - get_filename_component(_frameworkDir "${_include}" DIRECTORY) - list (APPEND _frameworkDirs "${_frameworkDir}") - endif() - endforeach() - set (_systemFrameworkDirs "") - foreach (_include ${${_systemIncludesVar}}) - if (IS_ABSOLUTE "${_include}" AND _include MATCHES "\\.framework$") - get_filename_component(_frameworkDir "${_include}" DIRECTORY) - list (APPEND _systemFrameworkDirs "${_frameworkDir}") - endif() - endforeach() - if (_systemFrameworkDirs) - list (APPEND _frameworkDirs ${_systemFrameworkDirs}) - endif() - if (_frameworkDirs) - list (REMOVE_DUPLICATES _frameworkDirs) - foreach (_frameworkDir ${_frameworkDirs}) - set (_index -1) - if ("${CMAKE_${_language}_SYSTEM_FRAMEWORK_SEARCH_FLAG}" MATCHES ".+") - list (FIND _systemFrameworkDirs "${_frameworkDir}" _index) - endif() - if (_index GREATER -1) - list (APPEND ${_cmdVar} "${CMAKE_${_language}_SYSTEM_FRAMEWORK_SEARCH_FLAG}${_frameworkDir}") - else() - list (APPEND ${_cmdVar} "${CMAKE_${_language}_FRAMEWORK_SEARCH_FLAG}${_frameworkDir}") - endif() - endforeach() - endif() - endif() - set (${_cmdVar} ${${_cmdVar}} PARENT_SCOPE) -endfunction() - -macro (cotire_add_compile_flags_to_cmd _cmdVar) - foreach (_flag ${ARGN}) - list (APPEND ${_cmdVar} "${_flag}") - endforeach() -endmacro() - -function (cotire_check_file_up_to_date _fileIsUpToDateVar _file) - if (EXISTS "${_file}") - set (_triggerFile "") - foreach (_dependencyFile ${ARGN}) - if (EXISTS "${_dependencyFile}") - # IS_NEWER_THAN returns TRUE if both files have the same timestamp - # thus we do the comparison in both directions to exclude ties - if ("${_dependencyFile}" IS_NEWER_THAN "${_file}" AND - NOT "${_file}" IS_NEWER_THAN "${_dependencyFile}") - set (_triggerFile "${_dependencyFile}") - break() - endif() - endif() - endforeach() - if (_triggerFile) - if (COTIRE_VERBOSE) - get_filename_component(_fileName "${_file}" NAME) - message (STATUS "${_fileName} update triggered by ${_triggerFile} change.") - endif() - set (${_fileIsUpToDateVar} FALSE PARENT_SCOPE) - else() - if (COTIRE_VERBOSE) - get_filename_component(_fileName "${_file}" NAME) - message (STATUS "${_fileName} is up-to-date.") - endif() - set (${_fileIsUpToDateVar} TRUE PARENT_SCOPE) - endif() - else() - if (COTIRE_VERBOSE) - get_filename_component(_fileName "${_file}" NAME) - message (STATUS "${_fileName} does not exist yet.") - endif() - set (${_fileIsUpToDateVar} FALSE PARENT_SCOPE) - endif() -endfunction() - -macro (cotire_find_closest_relative_path _headerFile _includeDirs _relPathVar) - set (${_relPathVar} "") - foreach (_includeDir ${_includeDirs}) - if (IS_DIRECTORY "${_includeDir}") - file (RELATIVE_PATH _relPath "${_includeDir}" "${_headerFile}") - if (NOT IS_ABSOLUTE "${_relPath}" AND NOT "${_relPath}" MATCHES "^\\.\\.") - string (LENGTH "${${_relPathVar}}" _closestLen) - string (LENGTH "${_relPath}" _relLen) - if (_closestLen EQUAL 0 OR _relLen LESS _closestLen) - set (${_relPathVar} "${_relPath}") - endif() - endif() - elseif ("${_includeDir}" STREQUAL "${_headerFile}") - # if path matches exactly, return short non-empty string - set (${_relPathVar} "1") - break() - endif() - endforeach() -endmacro() - -macro (cotire_check_header_file_location _headerFile _insideIncludeDirs _outsideIncludeDirs _headerIsInside) - # check header path against ignored and honored include directories - cotire_find_closest_relative_path("${_headerFile}" "${_insideIncludeDirs}" _insideRelPath) - if (_insideRelPath) - # header is inside, but could be become outside if there is a shorter outside match - cotire_find_closest_relative_path("${_headerFile}" "${_outsideIncludeDirs}" _outsideRelPath) - if (_outsideRelPath) - string (LENGTH "${_insideRelPath}" _insideRelPathLen) - string (LENGTH "${_outsideRelPath}" _outsideRelPathLen) - if (_outsideRelPathLen LESS _insideRelPathLen) - set (${_headerIsInside} FALSE) - else() - set (${_headerIsInside} TRUE) - endif() - else() - set (${_headerIsInside} TRUE) - endif() - else() - # header is outside - set (${_headerIsInside} FALSE) - endif() -endmacro() - -macro (cotire_check_ignore_header_file_path _headerFile _headerIsIgnoredVar) - if (NOT EXISTS "${_headerFile}") - set (${_headerIsIgnoredVar} TRUE) - elseif (IS_DIRECTORY "${_headerFile}") - set (${_headerIsIgnoredVar} TRUE) - elseif ("${_headerFile}" MATCHES "\\.\\.|[_-]fixed" AND "${_headerFile}" MATCHES "\\.h$") - # heuristic: ignore C headers with embedded parent directory references or "-fixed" or "_fixed" in path - # these often stem from using GCC #include_next tricks, which may break the precompiled header compilation - # with the error message "error: no include path in which to search for header.h" - set (${_headerIsIgnoredVar} TRUE) - else() - set (${_headerIsIgnoredVar} FALSE) - endif() -endmacro() - -macro (cotire_check_ignore_header_file_ext _headerFile _ignoreExtensionsVar _headerIsIgnoredVar) - # check header file extension - cotire_get_source_file_extension("${_headerFile}" _headerFileExt) - set (${_headerIsIgnoredVar} FALSE) - if (_headerFileExt) - list (FIND ${_ignoreExtensionsVar} "${_headerFileExt}" _index) - if (_index GREATER -1) - set (${_headerIsIgnoredVar} TRUE) - endif() - endif() -endmacro() - -macro (cotire_parse_line _line _headerFileVar _headerDepthVar) - if (MSVC) - # cl.exe /showIncludes produces different output, depending on the language pack used, e.g.: - # English: "Note: including file: C:\directory\file" - # German: "Hinweis: Einlesen der Datei: C:\directory\file" - # We use a very general regular expression, relying on the presence of the : characters - if (_line MATCHES "( +)([a-zA-Z]:[^:]+)$") - string (LENGTH "${CMAKE_MATCH_1}" ${_headerDepthVar}) - get_filename_component(${_headerFileVar} "${CMAKE_MATCH_2}" ABSOLUTE) - else() - set (${_headerFileVar} "") - set (${_headerDepthVar} 0) - endif() - else() - if (_line MATCHES "^(\\.+) (.*)$") - # GCC like output - string (LENGTH "${CMAKE_MATCH_1}" ${_headerDepthVar}) - if (IS_ABSOLUTE "${CMAKE_MATCH_2}") - set (${_headerFileVar} "${CMAKE_MATCH_2}") - else() - get_filename_component(${_headerFileVar} "${CMAKE_MATCH_2}" REALPATH) - endif() - else() - set (${_headerFileVar} "") - set (${_headerDepthVar} 0) - endif() - endif() -endmacro() - -function (cotire_parse_includes _language _scanOutput _ignoredIncludeDirs _honoredIncludeDirs _ignoredExtensions _selectedIncludesVar _unparsedLinesVar) - if (WIN32) - # prevent CMake macro invocation errors due to backslash characters in Windows paths - string (REPLACE "\\" "/" _scanOutput "${_scanOutput}") - endif() - # canonize slashes - string (REPLACE "//" "/" _scanOutput "${_scanOutput}") - # prevent semicolon from being interpreted as a line separator - string (REPLACE ";" "\\;" _scanOutput "${_scanOutput}") - # then separate lines - string (REGEX REPLACE "\n" ";" _scanOutput "${_scanOutput}") - list (LENGTH _scanOutput _len) - # remove duplicate lines to speed up parsing - list (REMOVE_DUPLICATES _scanOutput) - list (LENGTH _scanOutput _uniqueLen) - if (COTIRE_VERBOSE OR COTIRE_DEBUG) - message (STATUS "Scanning ${_uniqueLen} unique lines of ${_len} for includes") - if (_ignoredExtensions) - message (STATUS "Ignored extensions: ${_ignoredExtensions}") - endif() - if (_ignoredIncludeDirs) - message (STATUS "Ignored paths: ${_ignoredIncludeDirs}") - endif() - if (_honoredIncludeDirs) - message (STATUS "Included paths: ${_honoredIncludeDirs}") - endif() - endif() - set (_sourceFiles ${ARGN}) - set (_selectedIncludes "") - set (_unparsedLines "") - # stack keeps track of inside/outside project status of processed header files - set (_headerIsInsideStack "") - foreach (_line IN LISTS _scanOutput) - if (_line) - cotire_parse_line("${_line}" _headerFile _headerDepth) - if (_headerFile) - cotire_check_header_file_location("${_headerFile}" "${_ignoredIncludeDirs}" "${_honoredIncludeDirs}" _headerIsInside) - if (COTIRE_DEBUG) - message (STATUS "${_headerDepth}: ${_headerFile} ${_headerIsInside}") - endif() - # update stack - list (LENGTH _headerIsInsideStack _stackLen) - if (_headerDepth GREATER _stackLen) - math (EXPR _stackLen "${_stackLen} + 1") - foreach (_index RANGE ${_stackLen} ${_headerDepth}) - list (APPEND _headerIsInsideStack ${_headerIsInside}) - endforeach() - else() - foreach (_index RANGE ${_headerDepth} ${_stackLen}) - list (REMOVE_AT _headerIsInsideStack -1) - endforeach() - list (APPEND _headerIsInsideStack ${_headerIsInside}) - endif() - if (COTIRE_DEBUG) - message (STATUS "${_headerIsInsideStack}") - endif() - # header is a candidate if it is outside project - if (NOT _headerIsInside) - # get parent header file's inside/outside status - if (_headerDepth GREATER 1) - math (EXPR _index "${_headerDepth} - 2") - list (GET _headerIsInsideStack ${_index} _parentHeaderIsInside) - else() - set (_parentHeaderIsInside TRUE) - endif() - # select header file if parent header file is inside project - # (e.g., a project header file that includes a standard header file) - if (_parentHeaderIsInside) - cotire_check_ignore_header_file_path("${_headerFile}" _headerIsIgnored) - if (NOT _headerIsIgnored) - cotire_check_ignore_header_file_ext("${_headerFile}" _ignoredExtensions _headerIsIgnored) - if (NOT _headerIsIgnored) - list (APPEND _selectedIncludes "${_headerFile}") - else() - # fix header's inside status on stack, it is ignored by extension now - list (REMOVE_AT _headerIsInsideStack -1) - list (APPEND _headerIsInsideStack TRUE) - endif() - endif() - if (COTIRE_DEBUG) - message (STATUS "${_headerFile} ${_ignoredExtensions} ${_headerIsIgnored}") - endif() - endif() - endif() - else() - if (MSVC) - # for cl.exe do not keep unparsed lines which solely consist of a source file name - string (FIND "${_sourceFiles}" "${_line}" _index) - if (_index LESS 0) - list (APPEND _unparsedLines "${_line}") - endif() - else() - list (APPEND _unparsedLines "${_line}") - endif() - endif() - endif() - endforeach() - list (REMOVE_DUPLICATES _selectedIncludes) - set (${_selectedIncludesVar} ${_selectedIncludes} PARENT_SCOPE) - set (${_unparsedLinesVar} ${_unparsedLines} PARENT_SCOPE) -endfunction() - -function (cotire_scan_includes _includesVar) - set(_options "") - set(_oneValueArgs COMPILER_ID COMPILER_EXECUTABLE COMPILER_ARG1 COMPILER_VERSION LANGUAGE UNPARSED_LINES SCAN_RESULT) - set(_multiValueArgs COMPILE_DEFINITIONS COMPILE_FLAGS INCLUDE_DIRECTORIES SYSTEM_INCLUDE_DIRECTORIES - IGNORE_PATH INCLUDE_PATH IGNORE_EXTENSIONS INCLUDE_PRIORITY_PATH COMPILER_LAUNCHER) - cmake_parse_arguments(_option "${_options}" "${_oneValueArgs}" "${_multiValueArgs}" ${ARGN}) - set (_sourceFiles ${_option_UNPARSED_ARGUMENTS}) - if (NOT _option_LANGUAGE) - set (_option_LANGUAGE "CXX") - endif() - if (NOT _option_COMPILER_ID) - set (_option_COMPILER_ID "${CMAKE_${_option_LANGUAGE}_ID}") - endif() - if (NOT _option_COMPILER_VERSION) - set (_option_COMPILER_VERSION "${CMAKE_${_option_LANGUAGE}_COMPILER_VERSION}") - endif() - cotire_init_compile_cmd(_cmd "${_option_LANGUAGE}" "${_option_COMPILER_LAUNCHER}" "${_option_COMPILER_EXECUTABLE}" "${_option_COMPILER_ARG1}") - cotire_add_definitions_to_cmd(_cmd "${_option_LANGUAGE}" ${_option_COMPILE_DEFINITIONS}) - cotire_add_compile_flags_to_cmd(_cmd ${_option_COMPILE_FLAGS}) - cotire_add_includes_to_cmd(_cmd "${_option_LANGUAGE}" _option_INCLUDE_DIRECTORIES _option_SYSTEM_INCLUDE_DIRECTORIES) - cotire_add_frameworks_to_cmd(_cmd "${_option_LANGUAGE}" _option_INCLUDE_DIRECTORIES _option_SYSTEM_INCLUDE_DIRECTORIES) - cotire_add_makedep_flags("${_option_LANGUAGE}" "${_option_COMPILER_ID}" "${_option_COMPILER_VERSION}" _cmd) - # only consider existing source files for scanning - set (_existingSourceFiles "") - foreach (_sourceFile ${_sourceFiles}) - if (EXISTS "${_sourceFile}") - list (APPEND _existingSourceFiles "${_sourceFile}") - endif() - endforeach() - if (NOT _existingSourceFiles) - set (${_includesVar} "" PARENT_SCOPE) - return() - endif() - # add source files to be scanned - if (WIN32) - foreach (_sourceFile ${_existingSourceFiles}) - file (TO_NATIVE_PATH "${_sourceFile}" _sourceFileNative) - list (APPEND _cmd "${_sourceFileNative}") - endforeach() - else() - list (APPEND _cmd ${_existingSourceFiles}) - endif() - if (COTIRE_VERBOSE) - message (STATUS "execute_process: ${_cmd}") - endif() - if (MSVC_IDE OR _option_COMPILER_ID MATCHES "MSVC") - # cl.exe messes with the output streams unless the environment variable VS_UNICODE_OUTPUT is cleared - unset (ENV{VS_UNICODE_OUTPUT}) - endif() - execute_process( - COMMAND ${_cmd} - WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" - RESULT_VARIABLE _result - OUTPUT_QUIET - ERROR_VARIABLE _output) - if (_result) - message (STATUS "Result ${_result} scanning includes of ${_existingSourceFiles}.") - endif() - cotire_parse_includes( - "${_option_LANGUAGE}" "${_output}" - "${_option_IGNORE_PATH}" "${_option_INCLUDE_PATH}" - "${_option_IGNORE_EXTENSIONS}" - _includes _unparsedLines - ${_sourceFiles}) - if (_option_INCLUDE_PRIORITY_PATH) - set (_sortedIncludes "") - foreach (_priorityPath ${_option_INCLUDE_PRIORITY_PATH}) - foreach (_include ${_includes}) - string (FIND ${_include} ${_priorityPath} _position) - if (_position GREATER -1) - list (APPEND _sortedIncludes ${_include}) - endif() - endforeach() - endforeach() - if (_sortedIncludes) - list (INSERT _includes 0 ${_sortedIncludes}) - list (REMOVE_DUPLICATES _includes) - endif() - endif() - set (${_includesVar} ${_includes} PARENT_SCOPE) - if (_option_UNPARSED_LINES) - set (${_option_UNPARSED_LINES} ${_unparsedLines} PARENT_SCOPE) - endif() - if (_option_SCAN_RESULT) - set (${_option_SCAN_RESULT} ${_result} PARENT_SCOPE) - endif() -endfunction() - -macro (cotire_append_undefs _contentsVar) - set (_undefs ${ARGN}) - if (_undefs) - list (REMOVE_DUPLICATES _undefs) - foreach (_definition ${_undefs}) - list (APPEND ${_contentsVar} "#undef ${_definition}") - endforeach() - endif() -endmacro() - -macro (cotire_comment_str _language _commentText _commentVar) - if ("${_language}" STREQUAL "CMAKE") - set (${_commentVar} "# ${_commentText}") - else() - set (${_commentVar} "/* ${_commentText} */") - endif() -endmacro() - -function (cotire_write_file _language _file _contents _force) - get_filename_component(_moduleName "${COTIRE_CMAKE_MODULE_FILE}" NAME) - cotire_comment_str("${_language}" "${_moduleName} ${COTIRE_CMAKE_MODULE_VERSION} generated file" _header1) - cotire_comment_str("${_language}" "${_file}" _header2) - set (_contents "${_header1}\n${_header2}\n${_contents}") - if (COTIRE_DEBUG) - message (STATUS "${_contents}") - endif() - if (_force OR NOT EXISTS "${_file}") - file (WRITE "${_file}" "${_contents}") - else() - file (READ "${_file}" _oldContents) - if (NOT "${_oldContents}" STREQUAL "${_contents}") - file (WRITE "${_file}" "${_contents}") - else() - if (COTIRE_DEBUG) - message (STATUS "${_file} unchanged") - endif() - endif() - endif() -endfunction() - -function (cotire_generate_unity_source _unityFile) - set(_options "") - set(_oneValueArgs LANGUAGE) - set(_multiValueArgs - DEPENDS SOURCES_COMPILE_DEFINITIONS - PRE_UNDEFS SOURCES_PRE_UNDEFS POST_UNDEFS SOURCES_POST_UNDEFS PROLOGUE EPILOGUE) - cmake_parse_arguments(_option "${_options}" "${_oneValueArgs}" "${_multiValueArgs}" ${ARGN}) - if (_option_DEPENDS) - cotire_check_file_up_to_date(_unityFileIsUpToDate "${_unityFile}" ${_option_DEPENDS}) - if (_unityFileIsUpToDate) - return() - endif() - endif() - set (_sourceFiles ${_option_UNPARSED_ARGUMENTS}) - if (NOT _option_PRE_UNDEFS) - set (_option_PRE_UNDEFS "") - endif() - if (NOT _option_SOURCES_PRE_UNDEFS) - set (_option_SOURCES_PRE_UNDEFS "") - endif() - if (NOT _option_POST_UNDEFS) - set (_option_POST_UNDEFS "") - endif() - if (NOT _option_SOURCES_POST_UNDEFS) - set (_option_SOURCES_POST_UNDEFS "") - endif() - set (_contents "") - if (_option_PROLOGUE) - list (APPEND _contents ${_option_PROLOGUE}) - endif() - if (_option_LANGUAGE AND _sourceFiles) - if ("${_option_LANGUAGE}" STREQUAL "CXX") - list (APPEND _contents "#ifdef __cplusplus") - elseif ("${_option_LANGUAGE}" STREQUAL "C") - list (APPEND _contents "#ifndef __cplusplus") - endif() - endif() - set (_compileUndefinitions "") - foreach (_sourceFile ${_sourceFiles}) - cotire_get_source_compile_definitions( - "${_option_CONFIGURATION}" "${_option_LANGUAGE}" "${_sourceFile}" _compileDefinitions - ${_option_SOURCES_COMPILE_DEFINITIONS}) - cotire_get_source_undefs("${_sourceFile}" COTIRE_UNITY_SOURCE_PRE_UNDEFS _sourcePreUndefs ${_option_SOURCES_PRE_UNDEFS}) - cotire_get_source_undefs("${_sourceFile}" COTIRE_UNITY_SOURCE_POST_UNDEFS _sourcePostUndefs ${_option_SOURCES_POST_UNDEFS}) - if (_option_PRE_UNDEFS) - list (APPEND _compileUndefinitions ${_option_PRE_UNDEFS}) - endif() - if (_sourcePreUndefs) - list (APPEND _compileUndefinitions ${_sourcePreUndefs}) - endif() - if (_compileUndefinitions) - cotire_append_undefs(_contents ${_compileUndefinitions}) - set (_compileUndefinitions "") - endif() - if (_sourcePostUndefs) - list (APPEND _compileUndefinitions ${_sourcePostUndefs}) - endif() - if (_option_POST_UNDEFS) - list (APPEND _compileUndefinitions ${_option_POST_UNDEFS}) - endif() - foreach (_definition ${_compileDefinitions}) - if (_definition MATCHES "^([a-zA-Z0-9_]+)=(.+)$") - list (APPEND _contents "#define ${CMAKE_MATCH_1} ${CMAKE_MATCH_2}") - list (INSERT _compileUndefinitions 0 "${CMAKE_MATCH_1}") - else() - list (APPEND _contents "#define ${_definition}") - list (INSERT _compileUndefinitions 0 "${_definition}") - endif() - endforeach() - # use absolute path as source file location - get_filename_component(_sourceFileLocation "${_sourceFile}" ABSOLUTE) - if (WIN32) - file (TO_NATIVE_PATH "${_sourceFileLocation}" _sourceFileLocation) - endif() - list (APPEND _contents "#include \"${_sourceFileLocation}\"") - endforeach() - if (_compileUndefinitions) - cotire_append_undefs(_contents ${_compileUndefinitions}) - set (_compileUndefinitions "") - endif() - if (_option_LANGUAGE AND _sourceFiles) - list (APPEND _contents "#endif") - endif() - if (_option_EPILOGUE) - list (APPEND _contents ${_option_EPILOGUE}) - endif() - list (APPEND _contents "") - string (REPLACE ";" "\n" _contents "${_contents}") - if (COTIRE_VERBOSE) - message ("${_contents}") - endif() - cotire_write_file("${_option_LANGUAGE}" "${_unityFile}" "${_contents}" TRUE) -endfunction() - -function (cotire_generate_prefix_header _prefixFile) - set(_options "") - set(_oneValueArgs LANGUAGE COMPILER_EXECUTABLE COMPILER_ARG1 COMPILER_ID COMPILER_VERSION) - set(_multiValueArgs DEPENDS COMPILE_DEFINITIONS COMPILE_FLAGS - INCLUDE_DIRECTORIES SYSTEM_INCLUDE_DIRECTORIES IGNORE_PATH INCLUDE_PATH - IGNORE_EXTENSIONS INCLUDE_PRIORITY_PATH COMPILER_LAUNCHER) - cmake_parse_arguments(_option "${_options}" "${_oneValueArgs}" "${_multiValueArgs}" ${ARGN}) - if (NOT _option_COMPILER_ID) - set (_option_COMPILER_ID "${CMAKE_${_option_LANGUAGE}_ID}") - endif() - if (NOT _option_COMPILER_VERSION) - set (_option_COMPILER_VERSION "${CMAKE_${_option_LANGUAGE}_COMPILER_VERSION}") - endif() - if (_option_DEPENDS) - cotire_check_file_up_to_date(_prefixFileIsUpToDate "${_prefixFile}" ${_option_DEPENDS}) - if (_prefixFileIsUpToDate) - # create empty log file - set (_unparsedLinesFile "${_prefixFile}.log") - file (WRITE "${_unparsedLinesFile}" "") - return() - endif() - endif() - set (_prologue "") - set (_epilogue "") - if (_option_COMPILER_ID MATCHES "Clang") - set (_prologue "#pragma clang system_header") - elseif (_option_COMPILER_ID MATCHES "GNU") - set (_prologue "#pragma GCC system_header") - elseif (_option_COMPILER_ID MATCHES "MSVC") - set (_prologue "#pragma warning(push, 0)") - set (_epilogue "#pragma warning(pop)") - elseif (_option_COMPILER_ID MATCHES "Intel") - # Intel compiler requires hdrstop pragma to stop generating PCH file - set (_epilogue "#pragma hdrstop") - endif() - set (_sourceFiles ${_option_UNPARSED_ARGUMENTS}) - cotire_scan_includes(_selectedHeaders ${_sourceFiles} - LANGUAGE "${_option_LANGUAGE}" - COMPILER_LAUNCHER "${_option_COMPILER_LAUNCHER}" - COMPILER_EXECUTABLE "${_option_COMPILER_EXECUTABLE}" - COMPILER_ARG1 "${_option_COMPILER_ARG1}" - COMPILER_ID "${_option_COMPILER_ID}" - COMPILER_VERSION "${_option_COMPILER_VERSION}" - COMPILE_DEFINITIONS ${_option_COMPILE_DEFINITIONS} - COMPILE_FLAGS ${_option_COMPILE_FLAGS} - INCLUDE_DIRECTORIES ${_option_INCLUDE_DIRECTORIES} - SYSTEM_INCLUDE_DIRECTORIES ${_option_SYSTEM_INCLUDE_DIRECTORIES} - IGNORE_PATH ${_option_IGNORE_PATH} - INCLUDE_PATH ${_option_INCLUDE_PATH} - IGNORE_EXTENSIONS ${_option_IGNORE_EXTENSIONS} - INCLUDE_PRIORITY_PATH ${_option_INCLUDE_PRIORITY_PATH} - UNPARSED_LINES _unparsedLines - SCAN_RESULT _scanResult) - cotire_generate_unity_source("${_prefixFile}" - PROLOGUE ${_prologue} EPILOGUE ${_epilogue} LANGUAGE "${_option_LANGUAGE}" ${_selectedHeaders}) - set (_unparsedLinesFile "${_prefixFile}.log") - if (_unparsedLines) - if (COTIRE_VERBOSE OR _scanResult OR NOT _selectedHeaders) - list (LENGTH _unparsedLines _skippedLineCount) - if (WIN32) - file (TO_NATIVE_PATH "${_unparsedLinesFile}" _unparsedLinesLogPath) - else() - set (_unparsedLinesLogPath "${_unparsedLinesFile}") - endif() - message (STATUS "${_skippedLineCount} line(s) skipped, see ${_unparsedLinesLogPath}") - endif() - string (REPLACE ";" "\n" _unparsedLines "${_unparsedLines}") - endif() - file (WRITE "${_unparsedLinesFile}" "${_unparsedLines}\n") -endfunction() - -function (cotire_add_makedep_flags _language _compilerID _compilerVersion _flagsVar) - set (_flags ${${_flagsVar}}) - if (_compilerID MATCHES "MSVC") - # cl.exe options used - # /nologo suppresses display of sign-on banner - # /TC treat all files named on the command line as C source files - # /TP treat all files named on the command line as C++ source files - # /EP preprocess to stdout without #line directives - # /showIncludes list include files - set (_sourceFileTypeC "/TC") - set (_sourceFileTypeCXX "/TP") - if (_flags) - # append to list - list (APPEND _flags /nologo "${_sourceFileType${_language}}" /EP /showIncludes) - else() - # return as a flag string - set (_flags "${_sourceFileType${_language}} /EP /showIncludes") - endif() - elseif (_compilerID MATCHES "GNU") - # GCC options used - # -H print the name of each header file used - # -E invoke preprocessor - # -fdirectives-only do not expand macros, requires GCC >= 4.3 - if (_flags) - # append to list - list (APPEND _flags -H -E) - if (NOT "${_compilerVersion}" VERSION_LESS "4.3.0") - list (APPEND _flags -fdirectives-only) - endif() - else() - # return as a flag string - set (_flags "-H -E") - if (NOT "${_compilerVersion}" VERSION_LESS "4.3.0") - set (_flags "${_flags} -fdirectives-only") - endif() - endif() - elseif (_compilerID MATCHES "Clang") - if (UNIX) - # Clang options used - # -H print the name of each header file used - # -E invoke preprocessor - # -fno-color-diagnostics do not print diagnostics in color - # -Eonly just run preprocessor, no output - if (_flags) - # append to list - list (APPEND _flags -H -E -fno-color-diagnostics -Xclang -Eonly) - else() - # return as a flag string - set (_flags "-H -E -fno-color-diagnostics -Xclang -Eonly") - endif() - elseif (WIN32) - # Clang-cl.exe options used - # /TC treat all files named on the command line as C source files - # /TP treat all files named on the command line as C++ source files - # /EP preprocess to stdout without #line directives - # -H print the name of each header file used - # -fno-color-diagnostics do not print diagnostics in color - # -Eonly just run preprocessor, no output - set (_sourceFileTypeC "/TC") - set (_sourceFileTypeCXX "/TP") - if (_flags) - # append to list - list (APPEND _flags "${_sourceFileType${_language}}" /EP -fno-color-diagnostics -Xclang -H -Xclang -Eonly) - else() - # return as a flag string - set (_flags "${_sourceFileType${_language}} /EP -fno-color-diagnostics -Xclang -H -Xclang -Eonly") - endif() - endif() - elseif (_compilerID MATCHES "Intel") - if (WIN32) - # Windows Intel options used - # /nologo do not display compiler version information - # /QH display the include file order - # /EP preprocess to stdout, omitting #line directives - # /TC process all source or unrecognized file types as C source files - # /TP process all source or unrecognized file types as C++ source files - set (_sourceFileTypeC "/TC") - set (_sourceFileTypeCXX "/TP") - if (_flags) - # append to list - list (APPEND _flags /nologo "${_sourceFileType${_language}}" /EP /QH) - else() - # return as a flag string - set (_flags "${_sourceFileType${_language}} /EP /QH") - endif() - else() - # Linux / Mac OS X Intel options used - # -H print the name of each header file used - # -EP preprocess to stdout, omitting #line directives - # -Kc++ process all source or unrecognized file types as C++ source files - if (_flags) - # append to list - if ("${_language}" STREQUAL "CXX") - list (APPEND _flags -Kc++) - endif() - list (APPEND _flags -H -EP) - else() - # return as a flag string - if ("${_language}" STREQUAL "CXX") - set (_flags "-Kc++ ") - endif() - set (_flags "${_flags}-H -EP") - endif() - endif() - else() - message (FATAL_ERROR "cotire: unsupported ${_language} compiler ${_compilerID} version ${_compilerVersion}.") - endif() - set (${_flagsVar} ${_flags} PARENT_SCOPE) -endfunction() - -function (cotire_add_pch_compilation_flags _language _compilerID _compilerVersion _prefixFile _pchFile _hostFile _flagsVar) - set (_flags ${${_flagsVar}}) - if (_compilerID MATCHES "MSVC") - file (TO_NATIVE_PATH "${_prefixFile}" _prefixFileNative) - file (TO_NATIVE_PATH "${_pchFile}" _pchFileNative) - file (TO_NATIVE_PATH "${_hostFile}" _hostFileNative) - # cl.exe options used - # /Yc creates a precompiled header file - # /Fp specifies precompiled header binary file name - # /FI forces inclusion of file - # /TC treat all files named on the command line as C source files - # /TP treat all files named on the command line as C++ source files - # /Zs syntax check only - # /Zm precompiled header memory allocation scaling factor - set (_sourceFileTypeC "/TC") - set (_sourceFileTypeCXX "/TP") - if (_flags) - # append to list - list (APPEND _flags /nologo "${_sourceFileType${_language}}" - "/Yc${_prefixFileNative}" "/Fp${_pchFileNative}" "/FI${_prefixFileNative}" /Zs "${_hostFileNative}") - if (COTIRE_PCH_MEMORY_SCALING_FACTOR) - list (APPEND _flags "/Zm${COTIRE_PCH_MEMORY_SCALING_FACTOR}") - endif() - else() - # return as a flag string - set (_flags "/Yc\"${_prefixFileNative}\" /Fp\"${_pchFileNative}\" /FI\"${_prefixFileNative}\"") - if (COTIRE_PCH_MEMORY_SCALING_FACTOR) - set (_flags "${_flags} /Zm${COTIRE_PCH_MEMORY_SCALING_FACTOR}") - endif() - endif() - elseif (_compilerID MATCHES "GNU") - # GCC options used - # -x specify the source language - # -c compile but do not link - # -o place output in file - # note that we cannot use -w to suppress all warnings upon pre-compiling, because turning off a warning may - # alter compile flags as a side effect (e.g., -Wwrite-string implies -fconst-strings) - set (_xLanguage_C "c-header") - set (_xLanguage_CXX "c++-header") - if (_flags) - # append to list - list (APPEND _flags -x "${_xLanguage_${_language}}" -c "${_prefixFile}" -o "${_pchFile}") - else() - # return as a flag string - set (_flags "-x ${_xLanguage_${_language}} -c \"${_prefixFile}\" -o \"${_pchFile}\"") - endif() - elseif (_compilerID MATCHES "Clang") - if (UNIX) - # Clang options used - # -x specify the source language - # -c compile but do not link - # -o place output in file - # -fno-pch-timestamp disable inclusion of timestamp in precompiled headers (clang 4.0.0+) - set (_xLanguage_C "c-header") - set (_xLanguage_CXX "c++-header") - if (_flags) - # append to list - list (APPEND _flags -x "${_xLanguage_${_language}}" -c "${_prefixFile}" -o "${_pchFile}") - if (NOT "${_compilerVersion}" VERSION_LESS "4.0.0") - list (APPEND _flags -Xclang -fno-pch-timestamp) - endif() - else() - # return as a flag string - set (_flags "-x ${_xLanguage_${_language}} -c \"${_prefixFile}\" -o \"${_pchFile}\"") - if (NOT "${_compilerVersion}" VERSION_LESS "4.0.0") - set (_flags "${_flags} -Xclang -fno-pch-timestamp") - endif() - endif() - elseif (WIN32) - # Clang-cl.exe options used - # /Yc creates a precompiled header file - # /Fp specifies precompiled header binary file name - # /FI forces inclusion of file - # /Zs syntax check only - # /TC treat all files named on the command line as C source files - # /TP treat all files named on the command line as C++ source files - set (_sourceFileTypeC "/TC") - set (_sourceFileTypeCXX "/TP") - if (_flags) - # append to list - list (APPEND _flags "${_sourceFileType${_language}}" - "/Yc${_prefixFile}" "/Fp${_pchFile}" "/FI${_prefixFile}" /Zs "${_hostFile}") - else() - # return as a flag string - set (_flags "/Yc\"${_prefixFile}\" /Fp\"${_pchFile}\" /FI\"${_prefixFile}\"") - endif() - endif() - elseif (_compilerID MATCHES "Intel") - if (WIN32) - file (TO_NATIVE_PATH "${_prefixFile}" _prefixFileNative) - file (TO_NATIVE_PATH "${_pchFile}" _pchFileNative) - file (TO_NATIVE_PATH "${_hostFile}" _hostFileNative) - # Windows Intel options used - # /nologo do not display compiler version information - # /Yc create a precompiled header (PCH) file - # /Fp specify a path or file name for precompiled header files - # /FI tells the preprocessor to include a specified file name as the header file - # /TC process all source or unrecognized file types as C source files - # /TP process all source or unrecognized file types as C++ source files - # /Zs syntax check only - # /Wpch-messages enable diagnostics related to pre-compiled headers (requires Intel XE 2013 Update 2) - set (_sourceFileTypeC "/TC") - set (_sourceFileTypeCXX "/TP") - if (_flags) - # append to list - list (APPEND _flags /nologo "${_sourceFileType${_language}}" - "/Yc" "/Fp${_pchFileNative}" "/FI${_prefixFileNative}" /Zs "${_hostFileNative}") - if (NOT "${_compilerVersion}" VERSION_LESS "13.1.0") - list (APPEND _flags "/Wpch-messages") - endif() - else() - # return as a flag string - set (_flags "/Yc /Fp\"${_pchFileNative}\" /FI\"${_prefixFileNative}\"") - if (NOT "${_compilerVersion}" VERSION_LESS "13.1.0") - set (_flags "${_flags} /Wpch-messages") - endif() - endif() - else() - # Linux / Mac OS X Intel options used - # -pch-dir location for precompiled header files - # -pch-create name of the precompiled header (PCH) to create - # -Kc++ process all source or unrecognized file types as C++ source files - # -fsyntax-only check only for correct syntax - # -Wpch-messages enable diagnostics related to pre-compiled headers (requires Intel XE 2013 Update 2) - get_filename_component(_pchDir "${_pchFile}" DIRECTORY) - get_filename_component(_pchName "${_pchFile}" NAME) - set (_xLanguage_C "c-header") - set (_xLanguage_CXX "c++-header") - set (_pchSuppressMessages FALSE) - if ("${CMAKE_${_language}_FLAGS}" MATCHES ".*-Wno-pch-messages.*") - set(_pchSuppressMessages TRUE) - endif() - if (_flags) - # append to list - if ("${_language}" STREQUAL "CXX") - list (APPEND _flags -Kc++) - endif() - list (APPEND _flags -include "${_prefixFile}" -pch-dir "${_pchDir}" -pch-create "${_pchName}" -fsyntax-only "${_hostFile}") - if (NOT "${_compilerVersion}" VERSION_LESS "13.1.0") - if (NOT _pchSuppressMessages) - list (APPEND _flags -Wpch-messages) - endif() - endif() - else() - # return as a flag string - set (_flags "-include \"${_prefixFile}\" -pch-dir \"${_pchDir}\" -pch-create \"${_pchName}\"") - if (NOT "${_compilerVersion}" VERSION_LESS "13.1.0") - if (NOT _pchSuppressMessages) - set (_flags "${_flags} -Wpch-messages") - endif() - endif() - endif() - endif() - else() - message (FATAL_ERROR "cotire: unsupported ${_language} compiler ${_compilerID} version ${_compilerVersion}.") - endif() - set (${_flagsVar} ${_flags} PARENT_SCOPE) -endfunction() - -function (cotire_add_prefix_pch_inclusion_flags _language _compilerID _compilerVersion _prefixFile _pchFile _flagsVar) - set (_flags ${${_flagsVar}}) - if (_compilerID MATCHES "MSVC") - file (TO_NATIVE_PATH "${_prefixFile}" _prefixFileNative) - # cl.exe options used - # /Yu uses a precompiled header file during build - # /Fp specifies precompiled header binary file name - # /FI forces inclusion of file - # /Zm precompiled header memory allocation scaling factor - if (_pchFile) - file (TO_NATIVE_PATH "${_pchFile}" _pchFileNative) - if (_flags) - # append to list - list (APPEND _flags "/Yu${_prefixFileNative}" "/Fp${_pchFileNative}" "/FI${_prefixFileNative}") - if (COTIRE_PCH_MEMORY_SCALING_FACTOR) - list (APPEND _flags "/Zm${COTIRE_PCH_MEMORY_SCALING_FACTOR}") - endif() - else() - # return as a flag string - set (_flags "/Yu\"${_prefixFileNative}\" /Fp\"${_pchFileNative}\" /FI\"${_prefixFileNative}\"") - if (COTIRE_PCH_MEMORY_SCALING_FACTOR) - set (_flags "${_flags} /Zm${COTIRE_PCH_MEMORY_SCALING_FACTOR}") - endif() - endif() - else() - # no precompiled header, force inclusion of prefix header - if (_flags) - # append to list - list (APPEND _flags "/FI${_prefixFileNative}") - else() - # return as a flag string - set (_flags "/FI\"${_prefixFileNative}\"") - endif() - endif() - elseif (_compilerID MATCHES "GNU") - # GCC options used - # -include process include file as the first line of the primary source file - # -Winvalid-pch warns if precompiled header is found but cannot be used - # note: ccache requires the -include flag to be used in order to process precompiled header correctly - if (_flags) - # append to list - list (APPEND _flags -Winvalid-pch -include "${_prefixFile}") - else() - # return as a flag string - set (_flags "-Winvalid-pch -include \"${_prefixFile}\"") - endif() - elseif (_compilerID MATCHES "Clang") - if (UNIX) - # Clang options used - # -include process include file as the first line of the primary source file - # note: ccache requires the -include flag to be used in order to process precompiled header correctly - if (_flags) - # append to list - list (APPEND _flags -include "${_prefixFile}") - else() - # return as a flag string - set (_flags "-include \"${_prefixFile}\"") - endif() - elseif (WIN32) - # Clang-cl.exe options used - # /Yu uses a precompiled header file during build - # /Fp specifies precompiled header binary file name - # /FI forces inclusion of file - if (_pchFile) - if (_flags) - # append to list - list (APPEND _flags "/Yu${_prefixFile}" "/Fp${_pchFile}" "/FI${_prefixFile}") - else() - # return as a flag string - set (_flags "/Yu\"${_prefixFile}\" /Fp\"${_pchFile}\" /FI\"${_prefixFile}\"") - endif() - else() - # no precompiled header, force inclusion of prefix header - if (_flags) - # append to list - list (APPEND _flags "/FI${_prefixFile}") - else() - # return as a flag string - set (_flags "/FI\"${_prefixFile}\"") - endif() - endif() - endif() - elseif (_compilerID MATCHES "Intel") - if (WIN32) - file (TO_NATIVE_PATH "${_prefixFile}" _prefixFileNative) - # Windows Intel options used - # /Yu use a precompiled header (PCH) file - # /Fp specify a path or file name for precompiled header files - # /FI tells the preprocessor to include a specified file name as the header file - # /Wpch-messages enable diagnostics related to pre-compiled headers (requires Intel XE 2013 Update 2) - if (_pchFile) - file (TO_NATIVE_PATH "${_pchFile}" _pchFileNative) - if (_flags) - # append to list - list (APPEND _flags "/Yu" "/Fp${_pchFileNative}" "/FI${_prefixFileNative}") - if (NOT "${_compilerVersion}" VERSION_LESS "13.1.0") - list (APPEND _flags "/Wpch-messages") - endif() - else() - # return as a flag string - set (_flags "/Yu /Fp\"${_pchFileNative}\" /FI\"${_prefixFileNative}\"") - if (NOT "${_compilerVersion}" VERSION_LESS "13.1.0") - set (_flags "${_flags} /Wpch-messages") - endif() - endif() - else() - # no precompiled header, force inclusion of prefix header - if (_flags) - # append to list - list (APPEND _flags "/FI${_prefixFileNative}") - else() - # return as a flag string - set (_flags "/FI\"${_prefixFileNative}\"") - endif() - endif() - else() - # Linux / Mac OS X Intel options used - # -pch-dir location for precompiled header files - # -pch-use name of the precompiled header (PCH) to use - # -include process include file as the first line of the primary source file - # -Wpch-messages enable diagnostics related to pre-compiled headers (requires Intel XE 2013 Update 2) - if (_pchFile) - get_filename_component(_pchDir "${_pchFile}" DIRECTORY) - get_filename_component(_pchName "${_pchFile}" NAME) - set (_pchSuppressMessages FALSE) - if ("${CMAKE_${_language}_FLAGS}" MATCHES ".*-Wno-pch-messages.*") - set(_pchSuppressMessages TRUE) - endif() - if (_flags) - # append to list - list (APPEND _flags -include "${_prefixFile}" -pch-dir "${_pchDir}" -pch-use "${_pchName}") - if (NOT "${_compilerVersion}" VERSION_LESS "13.1.0") - if (NOT _pchSuppressMessages) - list (APPEND _flags -Wpch-messages) - endif() - endif() - else() - # return as a flag string - set (_flags "-include \"${_prefixFile}\" -pch-dir \"${_pchDir}\" -pch-use \"${_pchName}\"") - if (NOT "${_compilerVersion}" VERSION_LESS "13.1.0") - if (NOT _pchSuppressMessages) - set (_flags "${_flags} -Wpch-messages") - endif() - endif() - endif() - else() - # no precompiled header, force inclusion of prefix header - if (_flags) - # append to list - list (APPEND _flags -include "${_prefixFile}") - else() - # return as a flag string - set (_flags "-include \"${_prefixFile}\"") - endif() - endif() - endif() - else() - message (FATAL_ERROR "cotire: unsupported ${_language} compiler ${_compilerID} version ${_compilerVersion}.") - endif() - set (${_flagsVar} ${_flags} PARENT_SCOPE) -endfunction() - -function (cotire_precompile_prefix_header _prefixFile _pchFile _hostFile) - set(_options "") - set(_oneValueArgs COMPILER_EXECUTABLE COMPILER_ARG1 COMPILER_ID COMPILER_VERSION LANGUAGE) - set(_multiValueArgs COMPILE_DEFINITIONS COMPILE_FLAGS INCLUDE_DIRECTORIES SYSTEM_INCLUDE_DIRECTORIES SYS COMPILER_LAUNCHER) - cmake_parse_arguments(_option "${_options}" "${_oneValueArgs}" "${_multiValueArgs}" ${ARGN}) - if (NOT _option_LANGUAGE) - set (_option_LANGUAGE "CXX") - endif() - if (NOT _option_COMPILER_ID) - set (_option_COMPILER_ID "${CMAKE_${_option_LANGUAGE}_ID}") - endif() - if (NOT _option_COMPILER_VERSION) - set (_option_COMPILER_VERSION "${CMAKE_${_option_LANGUAGE}_COMPILER_VERSION}") - endif() - cotire_init_compile_cmd(_cmd "${_option_LANGUAGE}" "${_option_COMPILER_LAUNCHER}" "${_option_COMPILER_EXECUTABLE}" "${_option_COMPILER_ARG1}") - cotire_add_definitions_to_cmd(_cmd "${_option_LANGUAGE}" ${_option_COMPILE_DEFINITIONS}) - cotire_add_compile_flags_to_cmd(_cmd ${_option_COMPILE_FLAGS}) - cotire_add_includes_to_cmd(_cmd "${_option_LANGUAGE}" _option_INCLUDE_DIRECTORIES _option_SYSTEM_INCLUDE_DIRECTORIES) - cotire_add_frameworks_to_cmd(_cmd "${_option_LANGUAGE}" _option_INCLUDE_DIRECTORIES _option_SYSTEM_INCLUDE_DIRECTORIES) - cotire_add_pch_compilation_flags( - "${_option_LANGUAGE}" "${_option_COMPILER_ID}" "${_option_COMPILER_VERSION}" - "${_prefixFile}" "${_pchFile}" "${_hostFile}" _cmd) - if (COTIRE_VERBOSE) - message (STATUS "execute_process: ${_cmd}") - endif() - if (MSVC_IDE OR _option_COMPILER_ID MATCHES "MSVC") - # cl.exe messes with the output streams unless the environment variable VS_UNICODE_OUTPUT is cleared - unset (ENV{VS_UNICODE_OUTPUT}) - elseif (_option_COMPILER_ID MATCHES "Clang" AND _option_COMPILER_VERSION VERSION_LESS "4.0.0") - if (_option_COMPILER_LAUNCHER MATCHES "ccache" OR - _option_COMPILER_EXECUTABLE MATCHES "ccache") - # Newer versions of Clang embed a compilation timestamp into the precompiled header binary, - # which results in "file has been modified since the precompiled header was built" errors if ccache is used. - # We work around the problem by disabling ccache upon pre-compiling the prefix header. - set (ENV{CCACHE_DISABLE} "true") - endif() - endif() - execute_process( - COMMAND ${_cmd} - WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" - RESULT_VARIABLE _result) - if (_result) - message (FATAL_ERROR "cotire: error ${_result} precompiling ${_prefixFile}.") - endif() -endfunction() - -function (cotire_check_precompiled_header_support _language _target _msgVar) - set (_unsupportedCompiler - "Precompiled headers not supported for ${_language} compiler ${CMAKE_${_language}_COMPILER_ID}") - if (CMAKE_${_language}_COMPILER_ID MATCHES "MSVC") - # PCH supported since Visual Studio C++ 6.0 - # and CMake does not support an earlier version - set (${_msgVar} "" PARENT_SCOPE) - elseif (CMAKE_${_language}_COMPILER_ID MATCHES "GNU") - # GCC PCH support requires version >= 3.4 - if ("${CMAKE_${_language}_COMPILER_VERSION}" VERSION_LESS "3.4.0") - set (${_msgVar} "${_unsupportedCompiler} version ${CMAKE_${_language}_COMPILER_VERSION}." PARENT_SCOPE) - else() - set (${_msgVar} "" PARENT_SCOPE) - endif() - elseif (CMAKE_${_language}_COMPILER_ID MATCHES "Clang") - if (UNIX) - # all Unix Clang versions have PCH support - set (${_msgVar} "" PARENT_SCOPE) - elseif (WIN32) - # only clang-cl is supported under Windows - get_filename_component(_compilerName "${CMAKE_${_language}_COMPILER}" NAME_WE) - if (NOT _compilerName MATCHES "cl$") - set (${_msgVar} "${_unsupportedCompiler} version ${CMAKE_${_language}_COMPILER_VERSION}. Use clang-cl instead." PARENT_SCOPE) - endif() - endif() - elseif (CMAKE_${_language}_COMPILER_ID MATCHES "Intel") - # Intel PCH support requires version >= 8.0.0 - if ("${CMAKE_${_language}_COMPILER_VERSION}" VERSION_LESS "8.0.0") - set (${_msgVar} "${_unsupportedCompiler} version ${CMAKE_${_language}_COMPILER_VERSION}." PARENT_SCOPE) - else() - set (${_msgVar} "" PARENT_SCOPE) - endif() - else() - set (${_msgVar} "${_unsupportedCompiler}." PARENT_SCOPE) - endif() - # check if ccache is used as a compiler launcher - get_target_property(_launcher ${_target} ${_language}_COMPILER_LAUNCHER) - get_filename_component(_realCompilerExe "${CMAKE_${_language}_COMPILER}" REALPATH) - if (_realCompilerExe MATCHES "ccache" OR _launcher MATCHES "ccache") - # verify that ccache configuration is compatible with precompiled headers - # always check environment variable CCACHE_SLOPPINESS, because earlier versions of ccache - # do not report the "sloppiness" setting correctly upon printing ccache configuration - if (DEFINED ENV{CCACHE_SLOPPINESS}) - if (NOT "$ENV{CCACHE_SLOPPINESS}" MATCHES "pch_defines" OR - NOT "$ENV{CCACHE_SLOPPINESS}" MATCHES "time_macros") - set (${_msgVar} - "ccache requires the environment variable CCACHE_SLOPPINESS to be set to \"pch_defines,time_macros\"." - PARENT_SCOPE) - endif() - else() - if (_realCompilerExe MATCHES "ccache") - set (_ccacheExe "${_realCompilerExe}") - else() - set (_ccacheExe "${_launcher}") - endif() - execute_process( - COMMAND "${_ccacheExe}" "--print-config" - WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" - RESULT_VARIABLE _result - OUTPUT_VARIABLE _ccacheConfig OUTPUT_STRIP_TRAILING_WHITESPACE - ERROR_QUIET) - if (_result) - set (${_msgVar} "ccache configuration cannot be determined." PARENT_SCOPE) - elseif (NOT _ccacheConfig MATCHES "sloppiness.*=.*time_macros" OR - NOT _ccacheConfig MATCHES "sloppiness.*=.*pch_defines") - set (${_msgVar} - "ccache requires configuration setting \"sloppiness\" to be set to \"pch_defines,time_macros\"." - PARENT_SCOPE) - endif() - endif() - endif() - if (APPLE) - # PCH compilation not supported by GCC / Clang for multi-architecture builds (e.g., i386, x86_64) - cotire_get_configuration_types(_configs) - foreach (_config ${_configs}) - set (_targetFlags "") - cotire_get_target_compile_flags("${_config}" "${_language}" "${_target}" _targetFlags) - cotire_filter_compile_flags("${_language}" "arch" _architectures _ignore ${_targetFlags}) - list (LENGTH _architectures _numberOfArchitectures) - if (_numberOfArchitectures GREATER 1) - string (REPLACE ";" ", " _architectureStr "${_architectures}") - set (${_msgVar} - "Precompiled headers not supported on Darwin for multi-architecture builds (${_architectureStr})." - PARENT_SCOPE) - break() - endif() - endforeach() - endif() -endfunction() - -macro (cotire_get_intermediate_dir _cotireDir) - # ${CMAKE_CFG_INTDIR} may reference a build-time variable when using a generator which supports configuration types - get_filename_component(${_cotireDir} "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/${COTIRE_INTDIR}" ABSOLUTE) -endmacro() - -macro (cotire_setup_file_extension_variables) - set (_unityFileExt_C ".c") - set (_unityFileExt_CXX ".cxx") - set (_prefixFileExt_C ".h") - set (_prefixFileExt_CXX ".hxx") - set (_prefixSourceFileExt_C ".c") - set (_prefixSourceFileExt_CXX ".cxx") -endmacro() - -function (cotire_make_single_unity_source_file_path _language _target _unityFileVar) - cotire_setup_file_extension_variables() - if (NOT DEFINED _unityFileExt_${_language}) - set (${_unityFileVar} "" PARENT_SCOPE) - return() - endif() - set (_unityFileBaseName "${_target}_${_language}${COTIRE_UNITY_SOURCE_FILENAME_SUFFIX}") - set (_unityFileName "${_unityFileBaseName}${_unityFileExt_${_language}}") - cotire_get_intermediate_dir(_baseDir) - set (_unityFile "${_baseDir}/${_unityFileName}") - set (${_unityFileVar} "${_unityFile}" PARENT_SCOPE) -endfunction() - -function (cotire_make_unity_source_file_paths _language _target _maxIncludes _unityFilesVar) - cotire_setup_file_extension_variables() - if (NOT DEFINED _unityFileExt_${_language}) - set (${_unityFileVar} "" PARENT_SCOPE) - return() - endif() - set (_unityFileBaseName "${_target}_${_language}${COTIRE_UNITY_SOURCE_FILENAME_SUFFIX}") - cotire_get_intermediate_dir(_baseDir) - set (_startIndex 0) - set (_index 0) - set (_unityFiles "") - set (_sourceFiles ${ARGN}) - foreach (_sourceFile ${_sourceFiles}) - get_source_file_property(_startNew "${_sourceFile}" COTIRE_START_NEW_UNITY_SOURCE) - math (EXPR _unityFileCount "${_index} - ${_startIndex}") - if (_startNew OR (_maxIncludes GREATER 0 AND NOT _unityFileCount LESS _maxIncludes)) - if (_index GREATER 0) - # start new unity file segment - math (EXPR _endIndex "${_index} - 1") - set (_unityFileName "${_unityFileBaseName}_${_startIndex}_${_endIndex}${_unityFileExt_${_language}}") - list (APPEND _unityFiles "${_baseDir}/${_unityFileName}") - endif() - set (_startIndex ${_index}) - endif() - math (EXPR _index "${_index} + 1") - endforeach() - list (LENGTH _sourceFiles _numberOfSources) - if (_startIndex EQUAL 0) - # there is only a single unity file - cotire_make_single_unity_source_file_path(${_language} ${_target} _unityFiles) - elseif (_startIndex LESS _numberOfSources) - # end with final unity file segment - math (EXPR _endIndex "${_index} - 1") - set (_unityFileName "${_unityFileBaseName}_${_startIndex}_${_endIndex}${_unityFileExt_${_language}}") - list (APPEND _unityFiles "${_baseDir}/${_unityFileName}") - endif() - set (${_unityFilesVar} ${_unityFiles} PARENT_SCOPE) - if (COTIRE_DEBUG AND _unityFiles) - message (STATUS "unity files: ${_unityFiles}") - endif() -endfunction() - -function (cotire_unity_to_prefix_file_path _language _target _unityFile _prefixFileVar) - cotire_setup_file_extension_variables() - if (NOT DEFINED _unityFileExt_${_language}) - set (${_prefixFileVar} "" PARENT_SCOPE) - return() - endif() - set (_unityFileBaseName "${_target}_${_language}${COTIRE_UNITY_SOURCE_FILENAME_SUFFIX}") - set (_prefixFileBaseName "${_target}_${_language}${COTIRE_PREFIX_HEADER_FILENAME_SUFFIX}") - string (REPLACE "${_unityFileBaseName}" "${_prefixFileBaseName}" _prefixFile "${_unityFile}") - string (REGEX REPLACE "${_unityFileExt_${_language}}$" "${_prefixFileExt_${_language}}" _prefixFile "${_prefixFile}") - set (${_prefixFileVar} "${_prefixFile}" PARENT_SCOPE) -endfunction() - -function (cotire_prefix_header_to_source_file_path _language _prefixHeaderFile _prefixSourceFileVar) - cotire_setup_file_extension_variables() - if (NOT DEFINED _prefixSourceFileExt_${_language}) - set (${_prefixSourceFileVar} "" PARENT_SCOPE) - return() - endif() - string (REGEX REPLACE "${_prefixFileExt_${_language}}$" "${_prefixSourceFileExt_${_language}}" _prefixSourceFile "${_prefixHeaderFile}") - set (${_prefixSourceFileVar} "${_prefixSourceFile}" PARENT_SCOPE) -endfunction() - -function (cotire_make_prefix_file_name _language _target _prefixFileBaseNameVar _prefixFileNameVar) - cotire_setup_file_extension_variables() - if (NOT _language) - set (_prefixFileBaseName "${_target}${COTIRE_PREFIX_HEADER_FILENAME_SUFFIX}") - set (_prefixFileName "${_prefixFileBaseName}${_prefixFileExt_C}") - elseif (DEFINED _prefixFileExt_${_language}) - set (_prefixFileBaseName "${_target}_${_language}${COTIRE_PREFIX_HEADER_FILENAME_SUFFIX}") - set (_prefixFileName "${_prefixFileBaseName}${_prefixFileExt_${_language}}") - else() - set (_prefixFileBaseName "") - set (_prefixFileName "") - endif() - set (${_prefixFileBaseNameVar} "${_prefixFileBaseName}" PARENT_SCOPE) - set (${_prefixFileNameVar} "${_prefixFileName}" PARENT_SCOPE) -endfunction() - -function (cotire_make_prefix_file_path _language _target _prefixFileVar) - cotire_make_prefix_file_name("${_language}" "${_target}" _prefixFileBaseName _prefixFileName) - set (${_prefixFileVar} "" PARENT_SCOPE) - if (_prefixFileName) - if (NOT _language) - set (_language "C") - endif() - if (CMAKE_${_language}_COMPILER_ID MATCHES "GNU|Clang|Intel|MSVC") - cotire_get_intermediate_dir(_baseDir) - set (${_prefixFileVar} "${_baseDir}/${_prefixFileName}" PARENT_SCOPE) - endif() - endif() -endfunction() - -function (cotire_make_pch_file_path _language _target _pchFileVar) - cotire_make_prefix_file_name("${_language}" "${_target}" _prefixFileBaseName _prefixFileName) - set (${_pchFileVar} "" PARENT_SCOPE) - if (_prefixFileBaseName AND _prefixFileName) - cotire_check_precompiled_header_support("${_language}" "${_target}" _msg) - if (NOT _msg) - if (XCODE) - # For Xcode, we completely hand off the compilation of the prefix header to the IDE - return() - endif() - cotire_get_intermediate_dir(_baseDir) - if (CMAKE_${_language}_COMPILER_ID MATCHES "MSVC") - # MSVC uses the extension .pch added to the prefix header base name - set (${_pchFileVar} "${_baseDir}/${_prefixFileBaseName}.pch" PARENT_SCOPE) - elseif (CMAKE_${_language}_COMPILER_ID MATCHES "Clang") - # Clang looks for a precompiled header corresponding to the prefix header with the extension .pch appended - set (${_pchFileVar} "${_baseDir}/${_prefixFileName}.pch" PARENT_SCOPE) - elseif (CMAKE_${_language}_COMPILER_ID MATCHES "GNU") - # GCC looks for a precompiled header corresponding to the prefix header with the extension .gch appended - set (${_pchFileVar} "${_baseDir}/${_prefixFileName}.gch" PARENT_SCOPE) - elseif (CMAKE_${_language}_COMPILER_ID MATCHES "Intel") - # Intel uses the extension .pchi added to the prefix header base name - set (${_pchFileVar} "${_baseDir}/${_prefixFileBaseName}.pchi" PARENT_SCOPE) - endif() - endif() - endif() -endfunction() - -function (cotire_select_unity_source_files _unityFile _sourcesVar) - set (_sourceFiles ${ARGN}) - if (_sourceFiles AND "${_unityFile}" MATCHES "${COTIRE_UNITY_SOURCE_FILENAME_SUFFIX}_([0-9]+)_([0-9]+)") - set (_startIndex ${CMAKE_MATCH_1}) - set (_endIndex ${CMAKE_MATCH_2}) - list (LENGTH _sourceFiles _numberOfSources) - if (NOT _startIndex LESS _numberOfSources) - math (EXPR _startIndex "${_numberOfSources} - 1") - endif() - if (NOT _endIndex LESS _numberOfSources) - math (EXPR _endIndex "${_numberOfSources} - 1") - endif() - set (_files "") - foreach (_index RANGE ${_startIndex} ${_endIndex}) - list (GET _sourceFiles ${_index} _file) - list (APPEND _files "${_file}") - endforeach() - else() - set (_files ${_sourceFiles}) - endif() - set (${_sourcesVar} ${_files} PARENT_SCOPE) -endfunction() - -function (cotire_get_unity_source_dependencies _language _target _dependencySourcesVar) - set (_dependencySources "") - # depend on target's generated source files - get_target_property(_targetSourceFiles ${_target} SOURCES) - cotire_get_objects_with_property_on(_generatedSources GENERATED SOURCE ${_targetSourceFiles}) - if (_generatedSources) - # but omit all generated source files that have the COTIRE_EXCLUDED property set to true - cotire_get_objects_with_property_on(_excludedGeneratedSources COTIRE_EXCLUDED SOURCE ${_generatedSources}) - if (_excludedGeneratedSources) - list (REMOVE_ITEM _generatedSources ${_excludedGeneratedSources}) - endif() - # and omit all generated source files that have the COTIRE_DEPENDENCY property set to false explicitly - cotire_get_objects_with_property_off(_excludedNonDependencySources COTIRE_DEPENDENCY SOURCE ${_generatedSources}) - if (_excludedNonDependencySources) - list (REMOVE_ITEM _generatedSources ${_excludedNonDependencySources}) - endif() - if (_generatedSources) - list (APPEND _dependencySources ${_generatedSources}) - endif() - endif() - if (COTIRE_DEBUG AND _dependencySources) - message (STATUS "${_language} ${_target} unity source dependencies: ${_dependencySources}") - endif() - set (${_dependencySourcesVar} ${_dependencySources} PARENT_SCOPE) -endfunction() - -function (cotire_get_prefix_header_dependencies _language _target _dependencySourcesVar) - set (_dependencySources "") - # depend on target source files marked with custom COTIRE_DEPENDENCY property - get_target_property(_targetSourceFiles ${_target} SOURCES) - cotire_get_objects_with_property_on(_dependencySources COTIRE_DEPENDENCY SOURCE ${_targetSourceFiles}) - if (COTIRE_DEBUG AND _dependencySources) - message (STATUS "${_language} ${_target} prefix header dependencies: ${_dependencySources}") - endif() - set (${_dependencySourcesVar} ${_dependencySources} PARENT_SCOPE) -endfunction() - -function (cotire_generate_target_script _language _configurations _target _targetScriptVar _targetConfigScriptVar) - set (_targetSources ${ARGN}) - cotire_get_prefix_header_dependencies(${_language} ${_target} COTIRE_TARGET_PREFIX_DEPENDS ${_targetSources}) - cotire_get_unity_source_dependencies(${_language} ${_target} COTIRE_TARGET_UNITY_DEPENDS ${_targetSources}) - # set up variables to be configured - set (COTIRE_TARGET_LANGUAGE "${_language}") - get_target_property(COTIRE_TARGET_IGNORE_PATH ${_target} COTIRE_PREFIX_HEADER_IGNORE_PATH) - cotire_add_sys_root_paths(COTIRE_TARGET_IGNORE_PATH) - get_target_property(COTIRE_TARGET_INCLUDE_PATH ${_target} COTIRE_PREFIX_HEADER_INCLUDE_PATH) - cotire_add_sys_root_paths(COTIRE_TARGET_INCLUDE_PATH) - get_target_property(COTIRE_TARGET_PRE_UNDEFS ${_target} COTIRE_UNITY_SOURCE_PRE_UNDEFS) - get_target_property(COTIRE_TARGET_POST_UNDEFS ${_target} COTIRE_UNITY_SOURCE_POST_UNDEFS) - get_target_property(COTIRE_TARGET_MAXIMUM_NUMBER_OF_INCLUDES ${_target} COTIRE_UNITY_SOURCE_MAXIMUM_NUMBER_OF_INCLUDES) - get_target_property(COTIRE_TARGET_INCLUDE_PRIORITY_PATH ${_target} COTIRE_PREFIX_HEADER_INCLUDE_PRIORITY_PATH) - cotire_get_source_files_undefs(COTIRE_UNITY_SOURCE_PRE_UNDEFS COTIRE_TARGET_SOURCES_PRE_UNDEFS ${_targetSources}) - cotire_get_source_files_undefs(COTIRE_UNITY_SOURCE_POST_UNDEFS COTIRE_TARGET_SOURCES_POST_UNDEFS ${_targetSources}) - set (COTIRE_TARGET_CONFIGURATION_TYPES "${_configurations}") - foreach (_config ${_configurations}) - string (TOUPPER "${_config}" _upperConfig) - cotire_get_target_include_directories( - "${_config}" "${_language}" "${_target}" COTIRE_TARGET_INCLUDE_DIRECTORIES_${_upperConfig} COTIRE_TARGET_SYSTEM_INCLUDE_DIRECTORIES_${_upperConfig}) - cotire_get_target_compile_definitions( - "${_config}" "${_language}" "${_target}" COTIRE_TARGET_COMPILE_DEFINITIONS_${_upperConfig}) - cotire_get_target_compiler_flags( - "${_config}" "${_language}" "${_target}" COTIRE_TARGET_COMPILE_FLAGS_${_upperConfig}) - cotire_get_source_files_compile_definitions( - "${_config}" "${_language}" COTIRE_TARGET_SOURCES_COMPILE_DEFINITIONS_${_upperConfig} ${_targetSources}) - endforeach() - get_target_property(COTIRE_TARGET_${_language}_COMPILER_LAUNCHER ${_target} ${_language}_COMPILER_LAUNCHER) - # set up COTIRE_TARGET_SOURCES - set (COTIRE_TARGET_SOURCES "") - foreach (_sourceFile ${_targetSources}) - get_source_file_property(_generated "${_sourceFile}" GENERATED) - if (_generated) - # use absolute paths for generated files only, retrieving the LOCATION property is an expensive operation - get_source_file_property(_sourceLocation "${_sourceFile}" LOCATION) - list (APPEND COTIRE_TARGET_SOURCES "${_sourceLocation}") - else() - list (APPEND COTIRE_TARGET_SOURCES "${_sourceFile}") - endif() - endforeach() - # copy variable definitions to cotire target script - get_cmake_property(_vars VARIABLES) - string (REGEX MATCHALL "COTIRE_[A-Za-z0-9_]+" _matchVars "${_vars}") - # omit COTIRE_*_INIT variables - string (REGEX MATCHALL "COTIRE_[A-Za-z0-9_]+_INIT" _initVars "${_matchVars}") - if (_initVars) - list (REMOVE_ITEM _matchVars ${_initVars}) - endif() - # omit COTIRE_VERBOSE which is passed as a CMake define on command line - list (REMOVE_ITEM _matchVars COTIRE_VERBOSE) - set (_contents "") - set (_contentsHasGeneratorExpressions FALSE) - foreach (_var IN LISTS _matchVars ITEMS - XCODE MSVC CMAKE_GENERATOR CMAKE_BUILD_TYPE CMAKE_CONFIGURATION_TYPES - CMAKE_${_language}_COMPILER_ID CMAKE_${_language}_COMPILER_VERSION - CMAKE_${_language}_COMPILER_LAUNCHER CMAKE_${_language}_COMPILER CMAKE_${_language}_COMPILER_ARG1 - CMAKE_INCLUDE_FLAG_${_language} CMAKE_INCLUDE_FLAG_SEP_${_language} - CMAKE_INCLUDE_SYSTEM_FLAG_${_language} - CMAKE_${_language}_FRAMEWORK_SEARCH_FLAG - CMAKE_${_language}_SYSTEM_FRAMEWORK_SEARCH_FLAG - CMAKE_${_language}_SOURCE_FILE_EXTENSIONS) - if (DEFINED ${_var}) - string (REPLACE "\"" "\\\"" _value "${${_var}}") - set (_contents "${_contents}set (${_var} \"${_value}\")\n") - if (NOT _contentsHasGeneratorExpressions) - if ("${_value}" MATCHES "\\$<.*>") - set (_contentsHasGeneratorExpressions TRUE) - endif() - endif() - endif() - endforeach() - # generate target script file - get_filename_component(_moduleName "${COTIRE_CMAKE_MODULE_FILE}" NAME) - set (_targetCotireScript "${CMAKE_CURRENT_BINARY_DIR}/${_target}_${_language}_${_moduleName}") - cotire_write_file("CMAKE" "${_targetCotireScript}" "${_contents}" FALSE) - if (_contentsHasGeneratorExpressions) - # use file(GENERATE ...) to expand generator expressions in the target script at CMake generate-time - set (_configNameOrNoneGeneratorExpression "$<$:None>$<$>:$>") - set (_targetCotireConfigScript "${CMAKE_CURRENT_BINARY_DIR}/${_target}_${_language}_${_configNameOrNoneGeneratorExpression}_${_moduleName}") - file (GENERATE OUTPUT "${_targetCotireConfigScript}" INPUT "${_targetCotireScript}") - else() - set (_targetCotireConfigScript "${_targetCotireScript}") - endif() - set (${_targetScriptVar} "${_targetCotireScript}" PARENT_SCOPE) - set (${_targetConfigScriptVar} "${_targetCotireConfigScript}" PARENT_SCOPE) -endfunction() - -function (cotire_setup_pch_file_compilation _language _target _targetScript _prefixFile _pchFile _hostFile) - set (_sourceFiles ${ARGN}) - if (CMAKE_${_language}_COMPILER_ID MATCHES "MSVC|Intel" OR - (WIN32 AND CMAKE_${_language}_COMPILER_ID MATCHES "Clang")) - # for MSVC, Intel and Clang-cl, we attach the precompiled header compilation to the host file - # the remaining files include the precompiled header, see cotire_setup_pch_file_inclusion - if (_sourceFiles) - set (_flags "") - cotire_add_pch_compilation_flags( - "${_language}" "${CMAKE_${_language}_COMPILER_ID}" "${CMAKE_${_language}_COMPILER_VERSION}" - "${_prefixFile}" "${_pchFile}" "${_hostFile}" _flags) - set_property (SOURCE ${_hostFile} APPEND_STRING PROPERTY COMPILE_FLAGS " ${_flags} ") - set_property (SOURCE ${_hostFile} APPEND PROPERTY OBJECT_OUTPUTS "${_pchFile}") - # make object file generated from host file depend on prefix header - set_property (SOURCE ${_hostFile} APPEND PROPERTY OBJECT_DEPENDS "${_prefixFile}") - # mark host file as cotired to prevent it from being used in another cotired target - set_property (SOURCE ${_hostFile} PROPERTY COTIRE_TARGET "${_target}") - endif() - elseif ("${CMAKE_GENERATOR}" MATCHES "Make|Ninja") - # for makefile based generator, we add a custom command to precompile the prefix header - if (_targetScript) - cotire_set_cmd_to_prologue(_cmds) - list (APPEND _cmds -P "${COTIRE_CMAKE_MODULE_FILE}" "precompile" "${_targetScript}" "${_prefixFile}" "${_pchFile}" "${_hostFile}") - if (MSVC_IDE) - file (TO_NATIVE_PATH "${_pchFile}" _pchFileLogPath) - else() - file (RELATIVE_PATH _pchFileLogPath "${CMAKE_BINARY_DIR}" "${_pchFile}") - endif() - # make precompiled header compilation depend on the actual compiler executable used to force - # re-compilation when the compiler executable is updated. This prevents "created by a different GCC executable" - # warnings when the precompiled header is included. - get_filename_component(_realCompilerExe "${CMAKE_${_language}_COMPILER}" ABSOLUTE) - if (COTIRE_DEBUG) - message (STATUS "add_custom_command: OUTPUT ${_pchFile} ${_cmds} DEPENDS ${_prefixFile} ${_realCompilerExe} IMPLICIT_DEPENDS ${_language} ${_prefixFile}") - endif() - set_property (SOURCE "${_pchFile}" PROPERTY GENERATED TRUE) - add_custom_command( - OUTPUT "${_pchFile}" - COMMAND ${_cmds} - DEPENDS "${_prefixFile}" "${_realCompilerExe}" - IMPLICIT_DEPENDS ${_language} "${_prefixFile}" - WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" - COMMENT "Building ${_language} precompiled header ${_pchFileLogPath}" - VERBATIM) - endif() - endif() -endfunction() - -function (cotire_setup_pch_file_inclusion _language _target _wholeTarget _prefixFile _pchFile _hostFile) - if (CMAKE_${_language}_COMPILER_ID MATCHES "MSVC|Intel" OR - (WIN32 AND CMAKE_${_language}_COMPILER_ID MATCHES "Clang")) - # for MSVC, Intel and clang-cl, we include the precompiled header in all but the host file - # the host file does the precompiled header compilation, see cotire_setup_pch_file_compilation - set (_sourceFiles ${ARGN}) - list (LENGTH _sourceFiles _numberOfSourceFiles) - if (_numberOfSourceFiles GREATER 0) - # mark sources as cotired to prevent them from being used in another cotired target - set_source_files_properties(${_sourceFiles} PROPERTIES COTIRE_TARGET "${_target}") - set (_flags "") - cotire_add_prefix_pch_inclusion_flags( - "${_language}" "${CMAKE_${_language}_COMPILER_ID}" "${CMAKE_${_language}_COMPILER_VERSION}" - "${_prefixFile}" "${_pchFile}" _flags) - set_property (SOURCE ${_sourceFiles} APPEND_STRING PROPERTY COMPILE_FLAGS " ${_flags} ") - # make object files generated from source files depend on precompiled header - set_property (SOURCE ${_sourceFiles} APPEND PROPERTY OBJECT_DEPENDS "${_pchFile}") - endif() - elseif ("${CMAKE_GENERATOR}" MATCHES "Make|Ninja") - set (_sourceFiles ${_hostFile} ${ARGN}) - if (NOT _wholeTarget) - # for makefile based generator, we force the inclusion of the prefix header for a subset - # of the source files, if this is a multi-language target or has excluded files - set (_flags "") - cotire_add_prefix_pch_inclusion_flags( - "${_language}" "${CMAKE_${_language}_COMPILER_ID}" "${CMAKE_${_language}_COMPILER_VERSION}" - "${_prefixFile}" "${_pchFile}" _flags) - set_property (SOURCE ${_sourceFiles} APPEND_STRING PROPERTY COMPILE_FLAGS " ${_flags} ") - # mark sources as cotired to prevent them from being used in another cotired target - set_source_files_properties(${_sourceFiles} PROPERTIES COTIRE_TARGET "${_target}") - endif() - # make object files generated from source files depend on precompiled header - set_property (SOURCE ${_sourceFiles} APPEND PROPERTY OBJECT_DEPENDS "${_pchFile}") - endif() -endfunction() - -function (cotire_setup_prefix_file_inclusion _language _target _prefixFile) - set (_sourceFiles ${ARGN}) - # force the inclusion of the prefix header for the given source files - set (_flags "") - set (_pchFile "") - cotire_add_prefix_pch_inclusion_flags( - "${_language}" "${CMAKE_${_language}_COMPILER_ID}" "${CMAKE_${_language}_COMPILER_VERSION}" - "${_prefixFile}" "${_pchFile}" _flags) - set_property (SOURCE ${_sourceFiles} APPEND_STRING PROPERTY COMPILE_FLAGS " ${_flags} ") - # mark sources as cotired to prevent them from being used in another cotired target - set_source_files_properties(${_sourceFiles} PROPERTIES COTIRE_TARGET "${_target}") - # make object files generated from source files depend on prefix header - set_property (SOURCE ${_sourceFiles} APPEND PROPERTY OBJECT_DEPENDS "${_prefixFile}") -endfunction() - -function (cotire_get_first_set_property_value _propertyValueVar _type _object) - set (_properties ${ARGN}) - foreach (_property ${_properties}) - get_property(_propertyValue ${_type} "${_object}" PROPERTY ${_property}) - if (_propertyValue) - set (${_propertyValueVar} ${_propertyValue} PARENT_SCOPE) - return() - endif() - endforeach() - set (${_propertyValueVar} "" PARENT_SCOPE) -endfunction() - -function (cotire_setup_combine_command _language _targetScript _joinedFile _cmdsVar) - set (_files ${ARGN}) - set (_filesPaths "") - foreach (_file ${_files}) - get_filename_component(_filePath "${_file}" ABSOLUTE) - list (APPEND _filesPaths "${_filePath}") - endforeach() - cotire_set_cmd_to_prologue(_prefixCmd) - list (APPEND _prefixCmd -P "${COTIRE_CMAKE_MODULE_FILE}" "combine") - if (_targetScript) - list (APPEND _prefixCmd "${_targetScript}") - endif() - list (APPEND _prefixCmd "${_joinedFile}" ${_filesPaths}) - if (COTIRE_DEBUG) - message (STATUS "add_custom_command: OUTPUT ${_joinedFile} COMMAND ${_prefixCmd} DEPENDS ${_files}") - endif() - set_property (SOURCE "${_joinedFile}" PROPERTY GENERATED TRUE) - if (MSVC_IDE) - file (TO_NATIVE_PATH "${_joinedFile}" _joinedFileLogPath) - else() - file (RELATIVE_PATH _joinedFileLogPath "${CMAKE_BINARY_DIR}" "${_joinedFile}") - endif() - get_filename_component(_joinedFileBaseName "${_joinedFile}" NAME_WE) - get_filename_component(_joinedFileExt "${_joinedFile}" EXT) - if (_language AND _joinedFileBaseName MATCHES "${COTIRE_UNITY_SOURCE_FILENAME_SUFFIX}$") - set (_comment "Generating ${_language} unity source ${_joinedFileLogPath}") - elseif (_language AND _joinedFileBaseName MATCHES "${COTIRE_PREFIX_HEADER_FILENAME_SUFFIX}$") - if (_joinedFileExt MATCHES "^\\.c") - set (_comment "Generating ${_language} prefix source ${_joinedFileLogPath}") - else() - set (_comment "Generating ${_language} prefix header ${_joinedFileLogPath}") - endif() - else() - set (_comment "Generating ${_joinedFileLogPath}") - endif() - add_custom_command( - OUTPUT "${_joinedFile}" - COMMAND ${_prefixCmd} - DEPENDS ${_files} - COMMENT "${_comment}" - WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" - VERBATIM) - list (APPEND ${_cmdsVar} COMMAND ${_prefixCmd}) - set (${_cmdsVar} ${${_cmdsVar}} PARENT_SCOPE) -endfunction() - -function (cotire_setup_target_pch_usage _languages _target _wholeTarget) - if (XCODE) - # for Xcode, we attach a pre-build action to generate the unity sources and prefix headers - set (_prefixFiles "") - foreach (_language ${_languages}) - get_property(_prefixFile TARGET ${_target} PROPERTY COTIRE_${_language}_PREFIX_HEADER) - if (_prefixFile) - list (APPEND _prefixFiles "${_prefixFile}") - endif() - endforeach() - set (_cmds ${ARGN}) - list (LENGTH _prefixFiles _numberOfPrefixFiles) - if (_numberOfPrefixFiles GREATER 1) - # we also generate a generic, single prefix header which includes all language specific prefix headers - set (_language "") - set (_targetScript "") - cotire_make_prefix_file_path("${_language}" ${_target} _prefixHeader) - cotire_setup_combine_command("${_language}" "${_targetScript}" "${_prefixHeader}" _cmds ${_prefixFiles}) - else() - set (_prefixHeader "${_prefixFiles}") - endif() - if (COTIRE_DEBUG) - message (STATUS "add_custom_command: TARGET ${_target} PRE_BUILD ${_cmds}") - endif() - # because CMake PRE_BUILD command does not support dependencies, - # we check dependencies explicity in cotire script mode when the pre-build action is run - add_custom_command( - TARGET "${_target}" - PRE_BUILD ${_cmds} - WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" - COMMENT "Updating target ${_target} prefix headers" - VERBATIM) - # make Xcode precompile the generated prefix header with ProcessPCH and ProcessPCH++ - set_target_properties(${_target} PROPERTIES XCODE_ATTRIBUTE_GCC_PRECOMPILE_PREFIX_HEADER "YES") - set_target_properties(${_target} PROPERTIES XCODE_ATTRIBUTE_GCC_PREFIX_HEADER "${_prefixHeader}") - elseif ("${CMAKE_GENERATOR}" MATCHES "Make|Ninja") - # for makefile based generator, we force inclusion of the prefix header for all target source files - # if this is a single-language target without any excluded files - if (_wholeTarget) - set (_language "${_languages}") - # for MSVC, Intel and clang-cl, precompiled header inclusion is always done on the source file level - # see cotire_setup_pch_file_inclusion - if (NOT CMAKE_${_language}_COMPILER_ID MATCHES "MSVC|Intel" AND NOT - (WIN32 AND CMAKE_${_language}_COMPILER_ID MATCHES "Clang")) - get_property(_prefixFile TARGET ${_target} PROPERTY COTIRE_${_language}_PREFIX_HEADER) - if (_prefixFile) - get_property(_pchFile TARGET ${_target} PROPERTY COTIRE_${_language}_PRECOMPILED_HEADER) - set (_options COMPILE_OPTIONS) - cotire_add_prefix_pch_inclusion_flags( - "${_language}" "${CMAKE_${_language}_COMPILER_ID}" "${CMAKE_${_language}_COMPILER_VERSION}" - "${_prefixFile}" "${_pchFile}" _options) - set_property(TARGET ${_target} APPEND PROPERTY ${_options}) - endif() - endif() - endif() - endif() -endfunction() - -function (cotire_setup_unity_generation_commands _language _target _targetScript _targetConfigScript _unityFiles _cmdsVar) - set (_dependencySources "") - cotire_get_unity_source_dependencies(${_language} ${_target} _dependencySources ${ARGN}) - foreach (_unityFile ${_unityFiles}) - set_property (SOURCE "${_unityFile}" PROPERTY GENERATED TRUE) - # set up compiled unity source dependencies via OBJECT_DEPENDS - # this ensures that missing source files are generated before the unity file is compiled - if (COTIRE_DEBUG AND _dependencySources) - message (STATUS "${_unityFile} OBJECT_DEPENDS ${_dependencySources}") - endif() - if (_dependencySources) - # the OBJECT_DEPENDS property requires a list of full paths - set (_objectDependsPaths "") - foreach (_sourceFile ${_dependencySources}) - get_source_file_property(_sourceLocation "${_sourceFile}" LOCATION) - list (APPEND _objectDependsPaths "${_sourceLocation}") - endforeach() - set_property (SOURCE "${_unityFile}" PROPERTY OBJECT_DEPENDS ${_objectDependsPaths}) - endif() - if (WIN32 AND CMAKE_${_language}_COMPILER_ID MATCHES "MSVC|Intel") - # unity file compilation results in potentially huge object file, - # thus use /bigobj by default unter cl.exe and Windows Intel - set_property (SOURCE "${_unityFile}" APPEND_STRING PROPERTY COMPILE_FLAGS "/bigobj") - endif() - cotire_set_cmd_to_prologue(_unityCmd) - list (APPEND _unityCmd -P "${COTIRE_CMAKE_MODULE_FILE}" "unity" "${_targetConfigScript}" "${_unityFile}") - if (CMAKE_VERSION VERSION_LESS "3.1.0") - set (_unityCmdDepends "${_targetScript}") - else() - # CMake 3.1.0 supports generator expressions in arguments to DEPENDS - set (_unityCmdDepends "${_targetConfigScript}") - endif() - if (MSVC_IDE) - file (TO_NATIVE_PATH "${_unityFile}" _unityFileLogPath) - else() - file (RELATIVE_PATH _unityFileLogPath "${CMAKE_BINARY_DIR}" "${_unityFile}") - endif() - if (COTIRE_DEBUG) - message (STATUS "add_custom_command: OUTPUT ${_unityFile} COMMAND ${_unityCmd} DEPENDS ${_unityCmdDepends}") - endif() - add_custom_command( - OUTPUT "${_unityFile}" - COMMAND ${_unityCmd} - DEPENDS ${_unityCmdDepends} - COMMENT "Generating ${_language} unity source ${_unityFileLogPath}" - WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" - VERBATIM) - list (APPEND ${_cmdsVar} COMMAND ${_unityCmd}) - endforeach() - set (${_cmdsVar} ${${_cmdsVar}} PARENT_SCOPE) -endfunction() - -function (cotire_setup_prefix_generation_command _language _target _targetScript _prefixFile _unityFiles _cmdsVar) - set (_sourceFiles ${ARGN}) - set (_dependencySources "") - cotire_get_prefix_header_dependencies(${_language} ${_target} _dependencySources ${_sourceFiles}) - cotire_set_cmd_to_prologue(_prefixCmd) - list (APPEND _prefixCmd -P "${COTIRE_CMAKE_MODULE_FILE}" "prefix" "${_targetScript}" "${_prefixFile}" ${_unityFiles}) - set_property (SOURCE "${_prefixFile}" PROPERTY GENERATED TRUE) - # make prefix header generation depend on the actual compiler executable used to force - # re-generation when the compiler executable is updated. This prevents "file not found" - # errors for compiler version specific system header files. - get_filename_component(_realCompilerExe "${CMAKE_${_language}_COMPILER}" ABSOLUTE) - if (COTIRE_DEBUG) - message (STATUS "add_custom_command: OUTPUT ${_prefixFile} COMMAND ${_prefixCmd} DEPENDS ${_unityFile} ${_dependencySources} ${_realCompilerExe}") - endif() - if (MSVC_IDE) - file (TO_NATIVE_PATH "${_prefixFile}" _prefixFileLogPath) - else() - file (RELATIVE_PATH _prefixFileLogPath "${CMAKE_BINARY_DIR}" "${_prefixFile}") - endif() - get_filename_component(_prefixFileExt "${_prefixFile}" EXT) - if (_prefixFileExt MATCHES "^\\.c") - set (_comment "Generating ${_language} prefix source ${_prefixFileLogPath}") - else() - set (_comment "Generating ${_language} prefix header ${_prefixFileLogPath}") - endif() - # prevent pre-processing errors upon generating the prefix header when a target's generated include file does not yet exist - # we do not add a file-level dependency for the target's generated files though, because we only want to depend on their existence - # thus we make the prefix header generation depend on a custom helper target which triggers the generation of the files - set (_preTargetName "${_target}${COTIRE_PCH_TARGET_SUFFIX}_pre") - if (TARGET ${_preTargetName}) - # custom helper target has already been generated while processing a different language - list (APPEND _dependencySources ${_preTargetName}) - else() - get_target_property(_targetSourceFiles ${_target} SOURCES) - cotire_get_objects_with_property_on(_generatedSources GENERATED SOURCE ${_targetSourceFiles}) - if (_generatedSources) - add_custom_target("${_preTargetName}" DEPENDS ${_generatedSources}) - cotire_init_target("${_preTargetName}") - list (APPEND _dependencySources ${_preTargetName}) - endif() - endif() - add_custom_command( - OUTPUT "${_prefixFile}" "${_prefixFile}.log" - COMMAND ${_prefixCmd} - DEPENDS ${_unityFiles} ${_dependencySources} "${_realCompilerExe}" - COMMENT "${_comment}" - WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" - VERBATIM) - list (APPEND ${_cmdsVar} COMMAND ${_prefixCmd}) - set (${_cmdsVar} ${${_cmdsVar}} PARENT_SCOPE) -endfunction() - -function (cotire_setup_prefix_generation_from_unity_command _language _target _targetScript _prefixFile _unityFiles _cmdsVar) - set (_sourceFiles ${ARGN}) - if (CMAKE_${_language}_COMPILER_ID MATCHES "GNU|Clang") - # GNU and Clang require indirect compilation of the prefix header to make them honor the system_header pragma - cotire_prefix_header_to_source_file_path(${_language} "${_prefixFile}" _prefixSourceFile) - else() - set (_prefixSourceFile "${_prefixFile}") - endif() - cotire_setup_prefix_generation_command( - ${_language} ${_target} "${_targetScript}" - "${_prefixSourceFile}" "${_unityFiles}" ${_cmdsVar} ${_sourceFiles}) - if (CMAKE_${_language}_COMPILER_ID MATCHES "GNU|Clang") - # set up generation of a prefix source file which includes the prefix header - cotire_setup_combine_command(${_language} "${_targetScript}" "${_prefixFile}" _cmds ${_prefixSourceFile}) - endif() - set (${_cmdsVar} ${${_cmdsVar}} PARENT_SCOPE) -endfunction() - -function (cotire_setup_prefix_generation_from_provided_command _language _target _targetScript _prefixFile _cmdsVar) - set (_prefixHeaderFiles ${ARGN}) - if (CMAKE_${_language}_COMPILER_ID MATCHES "GNU|Clang") - # GNU and Clang require indirect compilation of the prefix header to make them honor the system_header pragma - cotire_prefix_header_to_source_file_path(${_language} "${_prefixFile}" _prefixSourceFile) - else() - set (_prefixSourceFile "${_prefixFile}") - endif() - cotire_setup_combine_command(${_language} "${_targetScript}" "${_prefixSourceFile}" _cmds ${_prefixHeaderFiles}) - if (CMAKE_${_language}_COMPILER_ID MATCHES "GNU|Clang") - # set up generation of a prefix source file which includes the prefix header - cotire_setup_combine_command(${_language} "${_targetScript}" "${_prefixFile}" _cmds ${_prefixSourceFile}) - endif() - set (${_cmdsVar} ${${_cmdsVar}} PARENT_SCOPE) -endfunction() - -function (cotire_init_cotire_target_properties _target) - get_property(_isSet TARGET ${_target} PROPERTY COTIRE_ENABLE_PRECOMPILED_HEADER SET) - if (NOT _isSet) - set_property(TARGET ${_target} PROPERTY COTIRE_ENABLE_PRECOMPILED_HEADER TRUE) - endif() - get_property(_isSet TARGET ${_target} PROPERTY COTIRE_ADD_UNITY_BUILD SET) - if (NOT _isSet) - set_property(TARGET ${_target} PROPERTY COTIRE_ADD_UNITY_BUILD TRUE) - endif() - get_property(_isSet TARGET ${_target} PROPERTY COTIRE_ADD_CLEAN SET) - if (NOT _isSet) - set_property(TARGET ${_target} PROPERTY COTIRE_ADD_CLEAN FALSE) - endif() - get_property(_isSet TARGET ${_target} PROPERTY COTIRE_PREFIX_HEADER_IGNORE_PATH SET) - if (NOT _isSet) - set_property(TARGET ${_target} PROPERTY COTIRE_PREFIX_HEADER_IGNORE_PATH "${CMAKE_SOURCE_DIR}") - cotire_check_is_path_relative_to("${CMAKE_BINARY_DIR}" _isRelative "${CMAKE_SOURCE_DIR}") - if (NOT _isRelative) - set_property(TARGET ${_target} APPEND PROPERTY COTIRE_PREFIX_HEADER_IGNORE_PATH "${CMAKE_BINARY_DIR}") - endif() - endif() - get_property(_isSet TARGET ${_target} PROPERTY COTIRE_PREFIX_HEADER_INCLUDE_PATH SET) - if (NOT _isSet) - set_property(TARGET ${_target} PROPERTY COTIRE_PREFIX_HEADER_INCLUDE_PATH "") - endif() - get_property(_isSet TARGET ${_target} PROPERTY COTIRE_PREFIX_HEADER_INCLUDE_PRIORITY_PATH SET) - if (NOT _isSet) - set_property(TARGET ${_target} PROPERTY COTIRE_PREFIX_HEADER_INCLUDE_PRIORITY_PATH "") - endif() - get_property(_isSet TARGET ${_target} PROPERTY COTIRE_UNITY_SOURCE_PRE_UNDEFS SET) - if (NOT _isSet) - set_property(TARGET ${_target} PROPERTY COTIRE_UNITY_SOURCE_PRE_UNDEFS "") - endif() - get_property(_isSet TARGET ${_target} PROPERTY COTIRE_UNITY_SOURCE_POST_UNDEFS SET) - if (NOT _isSet) - set_property(TARGET ${_target} PROPERTY COTIRE_UNITY_SOURCE_POST_UNDEFS "") - endif() - get_property(_isSet TARGET ${_target} PROPERTY COTIRE_UNITY_LINK_LIBRARIES_INIT SET) - if (NOT _isSet) - set_property(TARGET ${_target} PROPERTY COTIRE_UNITY_LINK_LIBRARIES_INIT "COPY_UNITY") - endif() - get_property(_isSet TARGET ${_target} PROPERTY COTIRE_UNITY_SOURCE_MAXIMUM_NUMBER_OF_INCLUDES SET) - if (NOT _isSet) - if (COTIRE_MAXIMUM_NUMBER_OF_UNITY_INCLUDES) - set_property(TARGET ${_target} PROPERTY COTIRE_UNITY_SOURCE_MAXIMUM_NUMBER_OF_INCLUDES "${COTIRE_MAXIMUM_NUMBER_OF_UNITY_INCLUDES}") - else() - set_property(TARGET ${_target} PROPERTY COTIRE_UNITY_SOURCE_MAXIMUM_NUMBER_OF_INCLUDES "") - endif() - endif() -endfunction() - -function (cotire_make_target_message _target _languages _disableMsg _targetMsgVar) - get_target_property(_targetUsePCH ${_target} COTIRE_ENABLE_PRECOMPILED_HEADER) - get_target_property(_targetAddSCU ${_target} COTIRE_ADD_UNITY_BUILD) - string (REPLACE ";" " " _languagesStr "${_languages}") - math (EXPR _numberOfExcludedFiles "${ARGC} - 4") - if (_numberOfExcludedFiles EQUAL 0) - set (_excludedStr "") - elseif (COTIRE_VERBOSE OR _numberOfExcludedFiles LESS 4) - string (REPLACE ";" ", " _excludedStr "excluding ${ARGN}") - else() - set (_excludedStr "excluding ${_numberOfExcludedFiles} files") - endif() - set (_targetMsg "") - if (NOT _languages) - set (_targetMsg "Target ${_target} cannot be cotired.") - if (_disableMsg) - set (_targetMsg "${_targetMsg} ${_disableMsg}") - endif() - elseif (NOT _targetUsePCH AND NOT _targetAddSCU) - set (_targetMsg "${_languagesStr} target ${_target} cotired without unity build and precompiled header.") - if (_disableMsg) - set (_targetMsg "${_targetMsg} ${_disableMsg}") - endif() - elseif (NOT _targetUsePCH) - if (_excludedStr) - set (_targetMsg "${_languagesStr} target ${_target} cotired without precompiled header ${_excludedStr}.") - else() - set (_targetMsg "${_languagesStr} target ${_target} cotired without precompiled header.") - endif() - if (_disableMsg) - set (_targetMsg "${_targetMsg} ${_disableMsg}") - endif() - elseif (NOT _targetAddSCU) - if (_excludedStr) - set (_targetMsg "${_languagesStr} target ${_target} cotired without unity build ${_excludedStr}.") - else() - set (_targetMsg "${_languagesStr} target ${_target} cotired without unity build.") - endif() - if (_disableMsg) - set (_targetMsg "${_targetMsg} ${_disableMsg}") - endif() - else() - if (_excludedStr) - set (_targetMsg "${_languagesStr} target ${_target} cotired ${_excludedStr}.") - else() - set (_targetMsg "${_languagesStr} target ${_target} cotired.") - endif() - endif() - set (${_targetMsgVar} "${_targetMsg}" PARENT_SCOPE) -endfunction() - -function (cotire_choose_target_languages _target _targetLanguagesVar _wholeTargetVar) - set (_languages ${ARGN}) - set (_allSourceFiles "") - set (_allExcludedSourceFiles "") - set (_allCotiredSourceFiles "") - set (_targetLanguages "") - set (_pchEligibleTargetLanguages "") - get_target_property(_targetType ${_target} TYPE) - get_target_property(_targetSourceFiles ${_target} SOURCES) - get_target_property(_targetUsePCH ${_target} COTIRE_ENABLE_PRECOMPILED_HEADER) - get_target_property(_targetAddSCU ${_target} COTIRE_ADD_UNITY_BUILD) - set (_disableMsg "") - foreach (_language ${_languages}) - get_target_property(_prefixHeader ${_target} COTIRE_${_language}_PREFIX_HEADER) - get_target_property(_unityBuildFile ${_target} COTIRE_${_language}_UNITY_SOURCE) - if (_prefixHeader OR _unityBuildFile) - message (STATUS "cotire: target ${_target} has already been cotired.") - set (${_targetLanguagesVar} "" PARENT_SCOPE) - return() - endif() - if (_targetUsePCH AND "${_language}" MATCHES "^C|CXX$" AND DEFINED CMAKE_${_language}_COMPILER_ID) - if (CMAKE_${_language}_COMPILER_ID) - cotire_check_precompiled_header_support("${_language}" "${_target}" _disableMsg) - if (_disableMsg) - set (_targetUsePCH FALSE) - endif() - endif() - endif() - set (_sourceFiles "") - set (_excludedSources "") - set (_cotiredSources "") - cotire_filter_language_source_files(${_language} ${_target} _sourceFiles _excludedSources _cotiredSources ${_targetSourceFiles}) - if (_sourceFiles OR _excludedSources OR _cotiredSources) - list (APPEND _targetLanguages ${_language}) - endif() - if (_sourceFiles) - list (APPEND _allSourceFiles ${_sourceFiles}) - endif() - list (LENGTH _sourceFiles _numberOfSources) - if (NOT _numberOfSources LESS ${COTIRE_MINIMUM_NUMBER_OF_TARGET_SOURCES}) - list (APPEND _pchEligibleTargetLanguages ${_language}) - endif() - if (_excludedSources) - list (APPEND _allExcludedSourceFiles ${_excludedSources}) - endif() - if (_cotiredSources) - list (APPEND _allCotiredSourceFiles ${_cotiredSources}) - endif() - endforeach() - set (_targetMsgLevel STATUS) - if (NOT _targetLanguages) - string (REPLACE ";" " or " _languagesStr "${_languages}") - set (_disableMsg "No ${_languagesStr} source files.") - set (_targetUsePCH FALSE) - set (_targetAddSCU FALSE) - endif() - if (_targetUsePCH) - if (_allCotiredSourceFiles) - cotire_get_source_file_property_values(_cotireTargets COTIRE_TARGET ${_allCotiredSourceFiles}) - list (REMOVE_DUPLICATES _cotireTargets) - string (REPLACE ";" ", " _cotireTargetsStr "${_cotireTargets}") - set (_disableMsg "Target sources already include a precompiled header for target(s) ${_cotireTargets}.") - set (_disableMsg "${_disableMsg} Set target property COTIRE_ENABLE_PRECOMPILED_HEADER to FALSE for targets ${_target},") - set (_disableMsg "${_disableMsg} ${_cotireTargetsStr} to get a workable build system.") - set (_targetMsgLevel SEND_ERROR) - set (_targetUsePCH FALSE) - elseif (NOT _pchEligibleTargetLanguages) - set (_disableMsg "Too few applicable sources.") - set (_targetUsePCH FALSE) - elseif (XCODE AND _allExcludedSourceFiles) - # for Xcode, we cannot apply the precompiled header to individual sources, only to the whole target - set (_disableMsg "Exclusion of source files not supported for generator Xcode.") - set (_targetUsePCH FALSE) - elseif (XCODE AND "${_targetType}" STREQUAL "OBJECT_LIBRARY") - # for Xcode, we cannot apply the required PRE_BUILD action to generate the prefix header to an OBJECT_LIBRARY target - set (_disableMsg "Required PRE_BUILD action not supported for OBJECT_LIBRARY targets for generator Xcode.") - set (_targetUsePCH FALSE) - endif() - endif() - if (_targetAddSCU) - # disable unity builds if automatic Qt processing is used - get_target_property(_targetAutoMoc ${_target} AUTOMOC) - get_target_property(_targetAutoUic ${_target} AUTOUIC) - get_target_property(_targetAutoRcc ${_target} AUTORCC) - if (_targetAutoMoc OR _targetAutoUic OR _targetAutoRcc) - if (_disableMsg) - set (_disableMsg "${_disableMsg} Target uses automatic CMake Qt processing.") - else() - set (_disableMsg "Target uses automatic CMake Qt processing.") - endif() - set (_targetAddSCU FALSE) - endif() - endif() - set_property(TARGET ${_target} PROPERTY COTIRE_ENABLE_PRECOMPILED_HEADER ${_targetUsePCH}) - set_property(TARGET ${_target} PROPERTY COTIRE_ADD_UNITY_BUILD ${_targetAddSCU}) - cotire_make_target_message(${_target} "${_targetLanguages}" "${_disableMsg}" _targetMsg ${_allExcludedSourceFiles}) - if (_targetMsg) - if (NOT DEFINED COTIREMSG_${_target}) - set (COTIREMSG_${_target} "") - endif() - if (COTIRE_VERBOSE OR NOT "${_targetMsgLevel}" STREQUAL "STATUS" OR - NOT "${COTIREMSG_${_target}}" STREQUAL "${_targetMsg}") - # cache message to avoid redundant messages on re-configure - set (COTIREMSG_${_target} "${_targetMsg}" CACHE INTERNAL "${_target} cotire message.") - message (${_targetMsgLevel} "${_targetMsg}") - endif() - endif() - list (LENGTH _targetLanguages _numberOfLanguages) - if (_numberOfLanguages GREATER 1 OR _allExcludedSourceFiles) - set (${_wholeTargetVar} FALSE PARENT_SCOPE) - else() - set (${_wholeTargetVar} TRUE PARENT_SCOPE) - endif() - set (${_targetLanguagesVar} ${_targetLanguages} PARENT_SCOPE) -endfunction() - -function (cotire_compute_unity_max_number_of_includes _target _maxIncludesVar) - set (_sourceFiles ${ARGN}) - get_target_property(_maxIncludes ${_target} COTIRE_UNITY_SOURCE_MAXIMUM_NUMBER_OF_INCLUDES) - if (_maxIncludes MATCHES "(-j|--parallel|--jobs) ?([0-9]*)") - if (DEFINED CMAKE_MATCH_2) - set (_numberOfThreads "${CMAKE_MATCH_2}") - else() - set (_numberOfThreads "") - endif() - if (NOT _numberOfThreads) - # use all available cores - ProcessorCount(_numberOfThreads) - endif() - list (LENGTH _sourceFiles _numberOfSources) - math (EXPR _maxIncludes "(${_numberOfSources} + ${_numberOfThreads} - 1) / ${_numberOfThreads}") - elseif (NOT _maxIncludes MATCHES "[0-9]+") - set (_maxIncludes 0) - endif() - if (COTIRE_DEBUG) - message (STATUS "${_target} unity source max includes: ${_maxIncludes}") - endif() - set (${_maxIncludesVar} ${_maxIncludes} PARENT_SCOPE) -endfunction() - -function (cotire_process_target_language _language _configurations _target _wholeTarget _cmdsVar) - set (${_cmdsVar} "" PARENT_SCOPE) - get_target_property(_targetSourceFiles ${_target} SOURCES) - set (_sourceFiles "") - set (_excludedSources "") - set (_cotiredSources "") - cotire_filter_language_source_files(${_language} ${_target} _sourceFiles _excludedSources _cotiredSources ${_targetSourceFiles}) - if (NOT _sourceFiles AND NOT _cotiredSources) - return() - endif() - set (_cmds "") - # check for user provided unity source file list - get_property(_unitySourceFiles TARGET ${_target} PROPERTY COTIRE_${_language}_UNITY_SOURCE_INIT) - if (NOT _unitySourceFiles) - set (_unitySourceFiles ${_sourceFiles} ${_cotiredSources}) - endif() - cotire_generate_target_script( - ${_language} "${_configurations}" ${_target} _targetScript _targetConfigScript ${_unitySourceFiles}) - # set up unity files for parallel compilation - cotire_compute_unity_max_number_of_includes(${_target} _maxIncludes ${_unitySourceFiles}) - cotire_make_unity_source_file_paths(${_language} ${_target} ${_maxIncludes} _unityFiles ${_unitySourceFiles}) - list (LENGTH _unityFiles _numberOfUnityFiles) - if (_numberOfUnityFiles EQUAL 0) - return() - elseif (_numberOfUnityFiles GREATER 1) - cotire_setup_unity_generation_commands( - ${_language} ${_target} "${_targetScript}" "${_targetConfigScript}" "${_unityFiles}" _cmds ${_unitySourceFiles}) - endif() - # set up single unity file for prefix header generation - cotire_make_single_unity_source_file_path(${_language} ${_target} _unityFile) - cotire_setup_unity_generation_commands( - ${_language} ${_target} "${_targetScript}" "${_targetConfigScript}" "${_unityFile}" _cmds ${_unitySourceFiles}) - cotire_make_prefix_file_path(${_language} ${_target} _prefixFile) - # set up prefix header - if (_prefixFile) - # check for user provided prefix header files - get_property(_prefixHeaderFiles TARGET ${_target} PROPERTY COTIRE_${_language}_PREFIX_HEADER_INIT) - if (_prefixHeaderFiles) - cotire_setup_prefix_generation_from_provided_command( - ${_language} ${_target} "${_targetConfigScript}" "${_prefixFile}" _cmds ${_prefixHeaderFiles}) - else() - cotire_setup_prefix_generation_from_unity_command( - ${_language} ${_target} "${_targetConfigScript}" "${_prefixFile}" "${_unityFile}" _cmds ${_unitySourceFiles}) - endif() - # check if selected language has enough sources at all - list (LENGTH _sourceFiles _numberOfSources) - if (_numberOfSources LESS ${COTIRE_MINIMUM_NUMBER_OF_TARGET_SOURCES}) - set (_targetUsePCH FALSE) - else() - get_target_property(_targetUsePCH ${_target} COTIRE_ENABLE_PRECOMPILED_HEADER) - endif() - if (_targetUsePCH) - cotire_make_pch_file_path(${_language} ${_target} _pchFile) - if (_pchFile) - # first file in _sourceFiles is passed as the host file - cotire_setup_pch_file_compilation( - ${_language} ${_target} "${_targetConfigScript}" "${_prefixFile}" "${_pchFile}" ${_sourceFiles}) - cotire_setup_pch_file_inclusion( - ${_language} ${_target} ${_wholeTarget} "${_prefixFile}" "${_pchFile}" ${_sourceFiles}) - endif() - elseif (_prefixHeaderFiles) - # user provided prefix header must be included unconditionally - cotire_setup_prefix_file_inclusion(${_language} ${_target} "${_prefixFile}" ${_sourceFiles}) - endif() - endif() - # mark target as cotired for language - set_property(TARGET ${_target} PROPERTY COTIRE_${_language}_UNITY_SOURCE "${_unityFiles}") - if (_prefixFile) - set_property(TARGET ${_target} PROPERTY COTIRE_${_language}_PREFIX_HEADER "${_prefixFile}") - if (_targetUsePCH AND _pchFile) - set_property(TARGET ${_target} PROPERTY COTIRE_${_language}_PRECOMPILED_HEADER "${_pchFile}") - endif() - endif() - set (${_cmdsVar} ${_cmds} PARENT_SCOPE) -endfunction() - -function (cotire_setup_clean_target _target) - set (_cleanTargetName "${_target}${COTIRE_CLEAN_TARGET_SUFFIX}") - if (NOT TARGET "${_cleanTargetName}") - cotire_set_cmd_to_prologue(_cmds) - get_filename_component(_outputDir "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}" ABSOLUTE) - list (APPEND _cmds -P "${COTIRE_CMAKE_MODULE_FILE}" "cleanup" "${_outputDir}" "${COTIRE_INTDIR}" "${_target}") - add_custom_target(${_cleanTargetName} - COMMAND ${_cmds} - WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" - COMMENT "Cleaning up target ${_target} cotire generated files" - VERBATIM) - cotire_init_target("${_cleanTargetName}") - endif() -endfunction() - -function (cotire_setup_pch_target _languages _configurations _target) - if ("${CMAKE_GENERATOR}" MATCHES "Make|Ninja") - # for makefile based generators, we add a custom target to trigger the generation of the cotire related files - set (_dependsFiles "") - foreach (_language ${_languages}) - set (_props COTIRE_${_language}_PREFIX_HEADER COTIRE_${_language}_UNITY_SOURCE) - if (NOT CMAKE_${_language}_COMPILER_ID MATCHES "MSVC|Intel" AND NOT - (WIN32 AND CMAKE_${_language}_COMPILER_ID MATCHES "Clang")) - # MSVC, Intel and clang-cl only create precompiled header as a side effect - list (INSERT _props 0 COTIRE_${_language}_PRECOMPILED_HEADER) - endif() - cotire_get_first_set_property_value(_dependsFile TARGET ${_target} ${_props}) - if (_dependsFile) - list (APPEND _dependsFiles "${_dependsFile}") - endif() - endforeach() - if (_dependsFiles) - set (_pchTargetName "${_target}${COTIRE_PCH_TARGET_SUFFIX}") - add_custom_target("${_pchTargetName}" DEPENDS ${_dependsFiles}) - cotire_init_target("${_pchTargetName}") - cotire_add_to_pch_all_target(${_pchTargetName}) - endif() - else() - # for other generators, we add the "clean all" target to clean up the precompiled header - cotire_setup_clean_all_target() - endif() -endfunction() - -function (cotire_filter_object_libraries _target _objectLibrariesVar) - set (_objectLibraries "") - foreach (_source ${ARGN}) - if (_source MATCHES "^\\$$") - list (APPEND _objectLibraries "${_source}") - endif() - endforeach() - set (${_objectLibrariesVar} ${_objectLibraries} PARENT_SCOPE) -endfunction() - -function (cotire_collect_unity_target_sources _target _languages _unityTargetSourcesVar) - get_target_property(_targetSourceFiles ${_target} SOURCES) - set (_unityTargetSources ${_targetSourceFiles}) - foreach (_language ${_languages}) - get_property(_unityFiles TARGET ${_target} PROPERTY COTIRE_${_language}_UNITY_SOURCE) - if (_unityFiles) - # remove source files that are included in the unity source - set (_sourceFiles "") - set (_excludedSources "") - set (_cotiredSources "") - cotire_filter_language_source_files(${_language} ${_target} _sourceFiles _excludedSources _cotiredSources ${_targetSourceFiles}) - if (_sourceFiles OR _cotiredSources) - list (REMOVE_ITEM _unityTargetSources ${_sourceFiles} ${_cotiredSources}) - endif() - # add unity source files instead - list (APPEND _unityTargetSources ${_unityFiles}) - endif() - endforeach() - # handle object libraries which are part of the target's sources - get_target_property(_linkLibrariesStrategy ${_target} COTIRE_UNITY_LINK_LIBRARIES_INIT) - if ("${_linkLibrariesStrategy}" MATCHES "^COPY_UNITY$") - cotire_filter_object_libraries(${_target} _objectLibraries ${_targetSourceFiles}) - if (_objectLibraries) - cotire_map_libraries("${_linkLibrariesStrategy}" _unityObjectLibraries ${_objectLibraries}) - list (REMOVE_ITEM _unityTargetSources ${_objectLibraries}) - list (APPEND _unityTargetSources ${_unityObjectLibraries}) - endif() - endif() - set (${_unityTargetSourcesVar} ${_unityTargetSources} PARENT_SCOPE) -endfunction() - -function (cotire_setup_unity_target_pch_usage _languages _target) - foreach (_language ${_languages}) - get_property(_unityFiles TARGET ${_target} PROPERTY COTIRE_${_language}_UNITY_SOURCE) - if (_unityFiles) - get_property(_userPrefixFile TARGET ${_target} PROPERTY COTIRE_${_language}_PREFIX_HEADER_INIT) - get_property(_prefixFile TARGET ${_target} PROPERTY COTIRE_${_language}_PREFIX_HEADER) - if (_userPrefixFile AND _prefixFile) - # user provided prefix header must be included unconditionally by unity sources - cotire_setup_prefix_file_inclusion(${_language} ${_target} "${_prefixFile}" ${_unityFiles}) - endif() - endif() - endforeach() -endfunction() - -function (cotire_setup_unity_build_target _languages _configurations _target) - get_target_property(_unityTargetName ${_target} COTIRE_UNITY_TARGET_NAME) - if (NOT _unityTargetName) - set (_unityTargetName "${_target}${COTIRE_UNITY_BUILD_TARGET_SUFFIX}") - endif() - # determine unity target sub type - get_target_property(_targetType ${_target} TYPE) - if ("${_targetType}" STREQUAL "EXECUTABLE") - set (_unityTargetSubType "") - elseif (_targetType MATCHES "(STATIC|SHARED|MODULE|OBJECT)_LIBRARY") - set (_unityTargetSubType "${CMAKE_MATCH_1}") - else() - message (WARNING "cotire: target ${_target} has unknown target type ${_targetType}.") - return() - endif() - # determine unity target sources - set (_unityTargetSources "") - cotire_collect_unity_target_sources(${_target} "${_languages}" _unityTargetSources) - # prevent AUTOMOC, AUTOUIC and AUTORCC properties from being set when the unity target is created - set (CMAKE_AUTOMOC OFF) - set (CMAKE_AUTOUIC OFF) - set (CMAKE_AUTORCC OFF) - if (COTIRE_DEBUG) - message (STATUS "add target ${_targetType} ${_unityTargetName} ${_unityTargetSubType} EXCLUDE_FROM_ALL ${_unityTargetSources}") - endif() - # generate unity target - if ("${_targetType}" STREQUAL "EXECUTABLE") - add_executable(${_unityTargetName} ${_unityTargetSubType} EXCLUDE_FROM_ALL ${_unityTargetSources}) - else() - add_library(${_unityTargetName} ${_unityTargetSubType} EXCLUDE_FROM_ALL ${_unityTargetSources}) - endif() - # copy output location properties - set (_outputDirProperties - ARCHIVE_OUTPUT_DIRECTORY ARCHIVE_OUTPUT_DIRECTORY_ - LIBRARY_OUTPUT_DIRECTORY LIBRARY_OUTPUT_DIRECTORY_ - RUNTIME_OUTPUT_DIRECTORY RUNTIME_OUTPUT_DIRECTORY_) - if (COTIRE_UNITY_OUTPUT_DIRECTORY) - set (_setDefaultOutputDir TRUE) - if (IS_ABSOLUTE "${COTIRE_UNITY_OUTPUT_DIRECTORY}") - set (_outputDir "${COTIRE_UNITY_OUTPUT_DIRECTORY}") - else() - # append relative COTIRE_UNITY_OUTPUT_DIRECTORY to target's actual output directory - cotire_copy_set_properties("${_configurations}" TARGET ${_target} ${_unityTargetName} ${_outputDirProperties}) - cotire_resolve_config_properties("${_configurations}" _properties ${_outputDirProperties}) - foreach (_property ${_properties}) - get_property(_outputDir TARGET ${_target} PROPERTY ${_property}) - if (_outputDir) - get_filename_component(_outputDir "${_outputDir}/${COTIRE_UNITY_OUTPUT_DIRECTORY}" ABSOLUTE) - set_property(TARGET ${_unityTargetName} PROPERTY ${_property} "${_outputDir}") - set (_setDefaultOutputDir FALSE) - endif() - endforeach() - if (_setDefaultOutputDir) - get_filename_component(_outputDir "${CMAKE_CURRENT_BINARY_DIR}/${COTIRE_UNITY_OUTPUT_DIRECTORY}" ABSOLUTE) - endif() - endif() - if (_setDefaultOutputDir) - set_target_properties(${_unityTargetName} PROPERTIES - ARCHIVE_OUTPUT_DIRECTORY "${_outputDir}" - LIBRARY_OUTPUT_DIRECTORY "${_outputDir}" - RUNTIME_OUTPUT_DIRECTORY "${_outputDir}") - endif() - else() - cotire_copy_set_properties("${_configurations}" TARGET ${_target} ${_unityTargetName} - ${_outputDirProperties}) - endif() - # copy output name - cotire_copy_set_properties("${_configurations}" TARGET ${_target} ${_unityTargetName} - ARCHIVE_OUTPUT_NAME ARCHIVE_OUTPUT_NAME_ - LIBRARY_OUTPUT_NAME LIBRARY_OUTPUT_NAME_ - OUTPUT_NAME OUTPUT_NAME_ - RUNTIME_OUTPUT_NAME RUNTIME_OUTPUT_NAME_ - PREFIX _POSTFIX SUFFIX - IMPORT_PREFIX IMPORT_SUFFIX) - # copy compile stuff - cotire_copy_set_properties("${_configurations}" TARGET ${_target} ${_unityTargetName} - COMPILE_DEFINITIONS COMPILE_DEFINITIONS_ - COMPILE_FLAGS COMPILE_OPTIONS - Fortran_FORMAT Fortran_MODULE_DIRECTORY - INCLUDE_DIRECTORIES - INTERPROCEDURAL_OPTIMIZATION INTERPROCEDURAL_OPTIMIZATION_ - POSITION_INDEPENDENT_CODE - C_COMPILER_LAUNCHER CXX_COMPILER_LAUNCHER - C_INCLUDE_WHAT_YOU_USE CXX_INCLUDE_WHAT_YOU_USE - C_VISIBILITY_PRESET CXX_VISIBILITY_PRESET VISIBILITY_INLINES_HIDDEN - C_CLANG_TIDY CXX_CLANG_TIDY) - # copy compile features - cotire_copy_set_properties("${_configurations}" TARGET ${_target} ${_unityTargetName} - C_EXTENSIONS C_STANDARD C_STANDARD_REQUIRED - CXX_EXTENSIONS CXX_STANDARD CXX_STANDARD_REQUIRED - COMPILE_FEATURES) - # copy interface stuff - cotire_copy_set_properties("${_configurations}" TARGET ${_target} ${_unityTargetName} - COMPATIBLE_INTERFACE_BOOL COMPATIBLE_INTERFACE_NUMBER_MAX COMPATIBLE_INTERFACE_NUMBER_MIN - COMPATIBLE_INTERFACE_STRING - INTERFACE_COMPILE_DEFINITIONS INTERFACE_COMPILE_FEATURES INTERFACE_COMPILE_OPTIONS - INTERFACE_INCLUDE_DIRECTORIES INTERFACE_SOURCES - INTERFACE_POSITION_INDEPENDENT_CODE INTERFACE_SYSTEM_INCLUDE_DIRECTORIES - INTERFACE_AUTOUIC_OPTIONS NO_SYSTEM_FROM_IMPORTED) - # copy link stuff - cotire_copy_set_properties("${_configurations}" TARGET ${_target} ${_unityTargetName} - BUILD_WITH_INSTALL_RPATH BUILD_WITH_INSTALL_NAME_DIR - INSTALL_RPATH INSTALL_RPATH_USE_LINK_PATH SKIP_BUILD_RPATH - LINKER_LANGUAGE LINK_DEPENDS LINK_DEPENDS_NO_SHARED - LINK_FLAGS LINK_FLAGS_ - LINK_INTERFACE_LIBRARIES LINK_INTERFACE_LIBRARIES_ - LINK_INTERFACE_MULTIPLICITY LINK_INTERFACE_MULTIPLICITY_ - LINK_SEARCH_START_STATIC LINK_SEARCH_END_STATIC - STATIC_LIBRARY_FLAGS STATIC_LIBRARY_FLAGS_ - NO_SONAME SOVERSION VERSION - LINK_WHAT_YOU_USE BUILD_RPATH) - # copy cmake stuff - cotire_copy_set_properties("${_configurations}" TARGET ${_target} ${_unityTargetName} - IMPLICIT_DEPENDS_INCLUDE_TRANSFORM RULE_LAUNCH_COMPILE RULE_LAUNCH_CUSTOM RULE_LAUNCH_LINK) - # copy Apple platform specific stuff - cotire_copy_set_properties("${_configurations}" TARGET ${_target} ${_unityTargetName} - BUNDLE BUNDLE_EXTENSION FRAMEWORK FRAMEWORK_VERSION INSTALL_NAME_DIR - MACOSX_BUNDLE MACOSX_BUNDLE_INFO_PLIST MACOSX_FRAMEWORK_INFO_PLIST MACOSX_RPATH - OSX_ARCHITECTURES OSX_ARCHITECTURES_ PRIVATE_HEADER PUBLIC_HEADER RESOURCE XCTEST - IOS_INSTALL_COMBINED XCODE_EXPLICIT_FILE_TYPE XCODE_PRODUCT_TYPE) - # copy Windows platform specific stuff - cotire_copy_set_properties("${_configurations}" TARGET ${_target} ${_unityTargetName} - GNUtoMS - COMPILE_PDB_NAME COMPILE_PDB_NAME_ - COMPILE_PDB_OUTPUT_DIRECTORY COMPILE_PDB_OUTPUT_DIRECTORY_ - PDB_NAME PDB_NAME_ PDB_OUTPUT_DIRECTORY PDB_OUTPUT_DIRECTORY_ - VS_DESKTOP_EXTENSIONS_VERSION VS_DOTNET_REFERENCES VS_DOTNET_TARGET_FRAMEWORK_VERSION - VS_GLOBAL_KEYWORD VS_GLOBAL_PROJECT_TYPES VS_GLOBAL_ROOTNAMESPACE - VS_IOT_EXTENSIONS_VERSION VS_IOT_STARTUP_TASK - VS_KEYWORD VS_MOBILE_EXTENSIONS_VERSION - VS_SCC_AUXPATH VS_SCC_LOCALPATH VS_SCC_PROJECTNAME VS_SCC_PROVIDER - VS_WINDOWS_TARGET_PLATFORM_MIN_VERSION - VS_WINRT_COMPONENT VS_WINRT_EXTENSIONS VS_WINRT_REFERENCES - WIN32_EXECUTABLE WINDOWS_EXPORT_ALL_SYMBOLS - DEPLOYMENT_REMOTE_DIRECTORY VS_CONFIGURATION_TYPE - VS_SDK_REFERENCES VS_USER_PROPS VS_DEBUGGER_WORKING_DIRECTORY) - # copy Android platform specific stuff - cotire_copy_set_properties("${_configurations}" TARGET ${_target} ${_unityTargetName} - ANDROID_API ANDROID_API_MIN ANDROID_GUI - ANDROID_ANT_ADDITIONAL_OPTIONS ANDROID_ARCH ANDROID_ASSETS_DIRECTORIES - ANDROID_JAR_DEPENDENCIES ANDROID_JAR_DIRECTORIES ANDROID_JAVA_SOURCE_DIR - ANDROID_NATIVE_LIB_DEPENDENCIES ANDROID_NATIVE_LIB_DIRECTORIES - ANDROID_PROCESS_MAX ANDROID_PROGUARD ANDROID_PROGUARD_CONFIG_PATH - ANDROID_SECURE_PROPS_PATH ANDROID_SKIP_ANT_STEP ANDROID_STL_TYPE) - # copy CUDA platform specific stuff - cotire_copy_set_properties("${_configurations}" TARGET ${_target} ${_unityTargetName} - CUDA_PTX_COMPILATION CUDA_SEPARABLE_COMPILATION CUDA_RESOLVE_DEVICE_SYMBOLS - CUDA_EXTENSIONS CUDA_STANDARD CUDA_STANDARD_REQUIRED) - # use output name from original target - get_target_property(_targetOutputName ${_unityTargetName} OUTPUT_NAME) - if (NOT _targetOutputName) - set_property(TARGET ${_unityTargetName} PROPERTY OUTPUT_NAME "${_target}") - endif() - # use export symbol from original target - cotire_get_target_export_symbol("${_target}" _defineSymbol) - if (_defineSymbol) - set_property(TARGET ${_unityTargetName} PROPERTY DEFINE_SYMBOL "${_defineSymbol}") - if ("${_targetType}" STREQUAL "EXECUTABLE") - set_property(TARGET ${_unityTargetName} PROPERTY ENABLE_EXPORTS TRUE) - endif() - endif() - # enable parallel compilation for MSVC - if (MSVC AND "${CMAKE_GENERATOR}" MATCHES "Visual Studio") - list (LENGTH _unityTargetSources _numberOfUnityTargetSources) - if (_numberOfUnityTargetSources GREATER 1) - set_property(TARGET ${_unityTargetName} APPEND PROPERTY COMPILE_OPTIONS "/MP") - endif() - endif() - cotire_init_target(${_unityTargetName}) - cotire_add_to_unity_all_target(${_unityTargetName}) - set_property(TARGET ${_target} PROPERTY COTIRE_UNITY_TARGET_NAME "${_unityTargetName}") -endfunction(cotire_setup_unity_build_target) - -function (cotire_target _target) - set(_options "") - set(_oneValueArgs "") - set(_multiValueArgs LANGUAGES CONFIGURATIONS) - cmake_parse_arguments(_option "${_options}" "${_oneValueArgs}" "${_multiValueArgs}" ${ARGN}) - if (NOT _option_LANGUAGES) - get_property (_option_LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES) - endif() - if (NOT _option_CONFIGURATIONS) - cotire_get_configuration_types(_option_CONFIGURATIONS) - endif() - # check if cotire can be applied to target at all - cotire_is_target_supported(${_target} _isSupported) - if (NOT _isSupported) - get_target_property(_imported ${_target} IMPORTED) - get_target_property(_targetType ${_target} TYPE) - if (_imported) - message (WARNING "cotire: imported ${_targetType} target ${_target} cannot be cotired.") - else() - message (STATUS "cotire: ${_targetType} target ${_target} cannot be cotired.") - endif() - return() - endif() - # resolve alias - get_target_property(_aliasName ${_target} ALIASED_TARGET) - if (_aliasName) - if (COTIRE_DEBUG) - message (STATUS "${_target} is an alias. Applying cotire to aliased target ${_aliasName} instead.") - endif() - set (_target ${_aliasName}) - endif() - # check if target needs to be cotired for build type - # when using configuration types, the test is performed at build time - cotire_init_cotire_target_properties(${_target}) - if (NOT CMAKE_CONFIGURATION_TYPES) - if (CMAKE_BUILD_TYPE) - list (FIND _option_CONFIGURATIONS "${CMAKE_BUILD_TYPE}" _index) - else() - list (FIND _option_CONFIGURATIONS "None" _index) - endif() - if (_index EQUAL -1) - if (COTIRE_DEBUG) - message (STATUS "CMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} not cotired (${_option_CONFIGURATIONS})") - endif() - return() - endif() - endif() - # when not using configuration types, immediately create cotire intermediate dir - if (NOT CMAKE_CONFIGURATION_TYPES) - cotire_get_intermediate_dir(_baseDir) - file (MAKE_DIRECTORY "${_baseDir}") - endif() - # choose languages that apply to the target - cotire_choose_target_languages("${_target}" _targetLanguages _wholeTarget ${_option_LANGUAGES}) - if (NOT _targetLanguages) - return() - endif() - set (_cmds "") - foreach (_language ${_targetLanguages}) - cotire_process_target_language("${_language}" "${_option_CONFIGURATIONS}" ${_target} ${_wholeTarget} _cmd) - if (_cmd) - list (APPEND _cmds ${_cmd}) - endif() - endforeach() - get_target_property(_targetAddSCU ${_target} COTIRE_ADD_UNITY_BUILD) - if (_targetAddSCU) - cotire_setup_unity_build_target("${_targetLanguages}" "${_option_CONFIGURATIONS}" ${_target}) - endif() - get_target_property(_targetUsePCH ${_target} COTIRE_ENABLE_PRECOMPILED_HEADER) - if (_targetUsePCH) - cotire_setup_target_pch_usage("${_targetLanguages}" ${_target} ${_wholeTarget} ${_cmds}) - cotire_setup_pch_target("${_targetLanguages}" "${_option_CONFIGURATIONS}" ${_target}) - if (_targetAddSCU) - cotire_setup_unity_target_pch_usage("${_targetLanguages}" ${_target}) - endif() - endif() - get_target_property(_targetAddCleanTarget ${_target} COTIRE_ADD_CLEAN) - if (_targetAddCleanTarget) - cotire_setup_clean_target(${_target}) - endif() -endfunction(cotire_target) - -function (cotire_map_libraries _strategy _mappedLibrariesVar) - set (_mappedLibraries "") - foreach (_library ${ARGN}) - if (_library MATCHES "^\\$$") - set (_libraryName "${CMAKE_MATCH_1}") - set (_linkOnly TRUE) - set (_objectLibrary FALSE) - elseif (_library MATCHES "^\\$$") - set (_libraryName "${CMAKE_MATCH_1}") - set (_linkOnly FALSE) - set (_objectLibrary TRUE) - else() - set (_libraryName "${_library}") - set (_linkOnly FALSE) - set (_objectLibrary FALSE) - endif() - if ("${_strategy}" MATCHES "COPY_UNITY") - cotire_is_target_supported(${_libraryName} _isSupported) - if (_isSupported) - # use target's corresponding unity target, if available - get_target_property(_libraryUnityTargetName ${_libraryName} COTIRE_UNITY_TARGET_NAME) - if (TARGET "${_libraryUnityTargetName}") - if (_linkOnly) - list (APPEND _mappedLibraries "$") - elseif (_objectLibrary) - list (APPEND _mappedLibraries "$") - else() - list (APPEND _mappedLibraries "${_libraryUnityTargetName}") - endif() - else() - list (APPEND _mappedLibraries "${_library}") - endif() - else() - list (APPEND _mappedLibraries "${_library}") - endif() - else() - list (APPEND _mappedLibraries "${_library}") - endif() - endforeach() - list (REMOVE_DUPLICATES _mappedLibraries) - set (${_mappedLibrariesVar} ${_mappedLibraries} PARENT_SCOPE) -endfunction() - -function (cotire_target_link_libraries _target) - cotire_is_target_supported(${_target} _isSupported) - if (NOT _isSupported) - return() - endif() - get_target_property(_unityTargetName ${_target} COTIRE_UNITY_TARGET_NAME) - if (TARGET "${_unityTargetName}") - get_target_property(_linkLibrariesStrategy ${_target} COTIRE_UNITY_LINK_LIBRARIES_INIT) - if (COTIRE_DEBUG) - message (STATUS "unity target ${_unityTargetName} link strategy: ${_linkLibrariesStrategy}") - endif() - if ("${_linkLibrariesStrategy}" MATCHES "^(COPY|COPY_UNITY)$") - get_target_property(_linkLibraries ${_target} LINK_LIBRARIES) - if (_linkLibraries) - cotire_map_libraries("${_linkLibrariesStrategy}" _unityLinkLibraries ${_linkLibraries}) - set_target_properties(${_unityTargetName} PROPERTIES LINK_LIBRARIES "${_unityLinkLibraries}") - if (COTIRE_DEBUG) - message (STATUS "unity target ${_unityTargetName} link libraries: ${_unityLinkLibraries}") - endif() - endif() - get_target_property(_interfaceLinkLibraries ${_target} INTERFACE_LINK_LIBRARIES) - if (_interfaceLinkLibraries) - cotire_map_libraries("${_linkLibrariesStrategy}" _unityLinkInterfaceLibraries ${_interfaceLinkLibraries}) - set_target_properties(${_unityTargetName} PROPERTIES INTERFACE_LINK_LIBRARIES "${_unityLinkInterfaceLibraries}") - if (COTIRE_DEBUG) - message (STATUS "unity target ${_unityTargetName} interface link libraries: ${_unityLinkInterfaceLibraries}") - endif() - endif() - get_target_property(_manualDependencies ${_target} MANUALLY_ADDED_DEPENDENCIES) - if (_manualDependencies) - cotire_map_libraries("${_linkLibrariesStrategy}" _unityManualDependencies ${_manualDependencies}) - if (_unityManualDependencies) - add_dependencies("${_unityTargetName}" ${_unityManualDependencies}) - endif() - endif() - endif() - endif() -endfunction(cotire_target_link_libraries) - -function (cotire_cleanup _binaryDir _cotireIntermediateDirName _targetName) - if (_targetName) - file (GLOB_RECURSE _cotireFiles "${_binaryDir}/${_targetName}*.*") - else() - file (GLOB_RECURSE _cotireFiles "${_binaryDir}/*.*") - endif() - # filter files in intermediate directory - set (_filesToRemove "") - foreach (_file ${_cotireFiles}) - get_filename_component(_dir "${_file}" DIRECTORY) - get_filename_component(_dirName "${_dir}" NAME) - if ("${_dirName}" STREQUAL "${_cotireIntermediateDirName}") - list (APPEND _filesToRemove "${_file}") - endif() - endforeach() - if (_filesToRemove) - if (COTIRE_VERBOSE) - message (STATUS "cleaning up ${_filesToRemove}") - endif() - file (REMOVE ${_filesToRemove}) - endif() -endfunction() - -function (cotire_init_target _targetName) - if (COTIRE_TARGETS_FOLDER) - set_target_properties(${_targetName} PROPERTIES FOLDER "${COTIRE_TARGETS_FOLDER}") - endif() - set_target_properties(${_targetName} PROPERTIES EXCLUDE_FROM_ALL TRUE) - if (MSVC_IDE) - set_target_properties(${_targetName} PROPERTIES EXCLUDE_FROM_DEFAULT_BUILD TRUE) - endif() -endfunction() - -function (cotire_add_to_pch_all_target _pchTargetName) - set (_targetName "${COTIRE_PCH_ALL_TARGET_NAME}") - if (NOT TARGET "${_targetName}") - add_custom_target("${_targetName}" - WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" - VERBATIM) - cotire_init_target("${_targetName}") - endif() - cotire_setup_clean_all_target() - add_dependencies(${_targetName} ${_pchTargetName}) -endfunction() - -function (cotire_add_to_unity_all_target _unityTargetName) - set (_targetName "${COTIRE_UNITY_BUILD_ALL_TARGET_NAME}") - if (NOT TARGET "${_targetName}") - add_custom_target("${_targetName}" - WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" - VERBATIM) - cotire_init_target("${_targetName}") - endif() - cotire_setup_clean_all_target() - add_dependencies(${_targetName} ${_unityTargetName}) -endfunction() - -function (cotire_setup_clean_all_target) - set (_targetName "${COTIRE_CLEAN_ALL_TARGET_NAME}") - if (NOT TARGET "${_targetName}") - cotire_set_cmd_to_prologue(_cmds) - list (APPEND _cmds -P "${COTIRE_CMAKE_MODULE_FILE}" "cleanup" "${CMAKE_BINARY_DIR}" "${COTIRE_INTDIR}") - add_custom_target(${_targetName} - COMMAND ${_cmds} - WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" - COMMENT "Cleaning up all cotire generated files" - VERBATIM) - cotire_init_target("${_targetName}") - endif() -endfunction() - -function (cotire) - set(_options "") - set(_oneValueArgs "") - set(_multiValueArgs LANGUAGES CONFIGURATIONS) - cmake_parse_arguments(_option "${_options}" "${_oneValueArgs}" "${_multiValueArgs}" ${ARGN}) - set (_targets ${_option_UNPARSED_ARGUMENTS}) - foreach (_target ${_targets}) - if (TARGET ${_target}) - cotire_target(${_target} LANGUAGES ${_option_LANGUAGES} CONFIGURATIONS ${_option_CONFIGURATIONS}) - else() - message (WARNING "cotire: ${_target} is not a target.") - endif() - endforeach() - foreach (_target ${_targets}) - if (TARGET ${_target}) - cotire_target_link_libraries(${_target}) - endif() - endforeach() -endfunction() - -if (CMAKE_SCRIPT_MODE_FILE) - - # cotire is being run in script mode - # locate -P on command args - set (COTIRE_ARGC -1) - foreach (_index RANGE ${CMAKE_ARGC}) - if (COTIRE_ARGC GREATER -1) - set (COTIRE_ARGV${COTIRE_ARGC} "${CMAKE_ARGV${_index}}") - math (EXPR COTIRE_ARGC "${COTIRE_ARGC} + 1") - elseif ("${CMAKE_ARGV${_index}}" STREQUAL "-P") - set (COTIRE_ARGC 0) - endif() - endforeach() - - # include target script if available - if ("${COTIRE_ARGV2}" MATCHES "\\.cmake$") - # the included target scripts sets up additional variables relating to the target (e.g., COTIRE_TARGET_SOURCES) - include("${COTIRE_ARGV2}") - endif() - - if (COTIRE_DEBUG) - message (STATUS "${COTIRE_ARGV0} ${COTIRE_ARGV1} ${COTIRE_ARGV2} ${COTIRE_ARGV3} ${COTIRE_ARGV4} ${COTIRE_ARGV5}") - endif() - - if (NOT COTIRE_BUILD_TYPE) - set (COTIRE_BUILD_TYPE "None") - endif() - string (TOUPPER "${COTIRE_BUILD_TYPE}" _upperConfig) - set (_includeDirs ${COTIRE_TARGET_INCLUDE_DIRECTORIES_${_upperConfig}}) - set (_systemIncludeDirs ${COTIRE_TARGET_SYSTEM_INCLUDE_DIRECTORIES_${_upperConfig}}) - set (_compileDefinitions ${COTIRE_TARGET_COMPILE_DEFINITIONS_${_upperConfig}}) - set (_compileFlags ${COTIRE_TARGET_COMPILE_FLAGS_${_upperConfig}}) - # check if target has been cotired for actual build type COTIRE_BUILD_TYPE - list (FIND COTIRE_TARGET_CONFIGURATION_TYPES "${COTIRE_BUILD_TYPE}" _index) - if (_index GREATER -1) - set (_sources ${COTIRE_TARGET_SOURCES}) - set (_sourcesDefinitions ${COTIRE_TARGET_SOURCES_COMPILE_DEFINITIONS_${_upperConfig}}) - else() - if (COTIRE_DEBUG) - message (STATUS "COTIRE_BUILD_TYPE=${COTIRE_BUILD_TYPE} not cotired (${COTIRE_TARGET_CONFIGURATION_TYPES})") - endif() - set (_sources "") - set (_sourcesDefinitions "") - endif() - set (_targetPreUndefs ${COTIRE_TARGET_PRE_UNDEFS}) - set (_targetPostUndefs ${COTIRE_TARGET_POST_UNDEFS}) - set (_sourcesPreUndefs ${COTIRE_TARGET_SOURCES_PRE_UNDEFS}) - set (_sourcesPostUndefs ${COTIRE_TARGET_SOURCES_POST_UNDEFS}) - - if ("${COTIRE_ARGV1}" STREQUAL "unity") - - if (XCODE) - # executing pre-build action under Xcode, check dependency on target script - set (_dependsOption DEPENDS "${COTIRE_ARGV2}") - else() - # executing custom command, no need to re-check for dependencies - set (_dependsOption "") - endif() - - cotire_select_unity_source_files("${COTIRE_ARGV3}" _sources ${_sources}) - - cotire_generate_unity_source( - "${COTIRE_ARGV3}" ${_sources} - LANGUAGE "${COTIRE_TARGET_LANGUAGE}" - SOURCES_COMPILE_DEFINITIONS ${_sourcesDefinitions} - PRE_UNDEFS ${_targetPreUndefs} - POST_UNDEFS ${_targetPostUndefs} - SOURCES_PRE_UNDEFS ${_sourcesPreUndefs} - SOURCES_POST_UNDEFS ${_sourcesPostUndefs} - ${_dependsOption}) - - elseif ("${COTIRE_ARGV1}" STREQUAL "prefix") - - if (XCODE) - # executing pre-build action under Xcode, check dependency on unity file and prefix dependencies - set (_dependsOption DEPENDS "${COTIRE_ARGV4}" ${COTIRE_TARGET_PREFIX_DEPENDS}) - else() - # executing custom command, no need to re-check for dependencies - set (_dependsOption "") - endif() - - set (_files "") - foreach (_index RANGE 4 ${COTIRE_ARGC}) - if (COTIRE_ARGV${_index}) - list (APPEND _files "${COTIRE_ARGV${_index}}") - endif() - endforeach() - - cotire_generate_prefix_header( - "${COTIRE_ARGV3}" ${_files} - COMPILER_LAUNCHER "${COTIRE_TARGET_${COTIRE_TARGET_LANGUAGE}_COMPILER_LAUNCHER}" - COMPILER_EXECUTABLE "${CMAKE_${COTIRE_TARGET_LANGUAGE}_COMPILER}" - COMPILER_ARG1 ${CMAKE_${COTIRE_TARGET_LANGUAGE}_COMPILER_ARG1} - COMPILER_ID "${CMAKE_${COTIRE_TARGET_LANGUAGE}_COMPILER_ID}" - COMPILER_VERSION "${CMAKE_${COTIRE_TARGET_LANGUAGE}_COMPILER_VERSION}" - LANGUAGE "${COTIRE_TARGET_LANGUAGE}" - IGNORE_PATH "${COTIRE_TARGET_IGNORE_PATH};${COTIRE_ADDITIONAL_PREFIX_HEADER_IGNORE_PATH}" - INCLUDE_PATH ${COTIRE_TARGET_INCLUDE_PATH} - IGNORE_EXTENSIONS "${CMAKE_${COTIRE_TARGET_LANGUAGE}_SOURCE_FILE_EXTENSIONS};${COTIRE_ADDITIONAL_PREFIX_HEADER_IGNORE_EXTENSIONS}" - INCLUDE_PRIORITY_PATH ${COTIRE_TARGET_INCLUDE_PRIORITY_PATH} - INCLUDE_DIRECTORIES ${_includeDirs} - SYSTEM_INCLUDE_DIRECTORIES ${_systemIncludeDirs} - COMPILE_DEFINITIONS ${_compileDefinitions} - COMPILE_FLAGS ${_compileFlags} - ${_dependsOption}) - - elseif ("${COTIRE_ARGV1}" STREQUAL "precompile") - - set (_files "") - foreach (_index RANGE 5 ${COTIRE_ARGC}) - if (COTIRE_ARGV${_index}) - list (APPEND _files "${COTIRE_ARGV${_index}}") - endif() - endforeach() - - cotire_precompile_prefix_header( - "${COTIRE_ARGV3}" "${COTIRE_ARGV4}" "${COTIRE_ARGV5}" - COMPILER_LAUNCHER "${COTIRE_TARGET_${COTIRE_TARGET_LANGUAGE}_COMPILER_LAUNCHER}" - COMPILER_EXECUTABLE "${CMAKE_${COTIRE_TARGET_LANGUAGE}_COMPILER}" - COMPILER_ARG1 ${CMAKE_${COTIRE_TARGET_LANGUAGE}_COMPILER_ARG1} - COMPILER_ID "${CMAKE_${COTIRE_TARGET_LANGUAGE}_COMPILER_ID}" - COMPILER_VERSION "${CMAKE_${COTIRE_TARGET_LANGUAGE}_COMPILER_VERSION}" - LANGUAGE "${COTIRE_TARGET_LANGUAGE}" - INCLUDE_DIRECTORIES ${_includeDirs} - SYSTEM_INCLUDE_DIRECTORIES ${_systemIncludeDirs} - COMPILE_DEFINITIONS ${_compileDefinitions} - COMPILE_FLAGS ${_compileFlags}) - - elseif ("${COTIRE_ARGV1}" STREQUAL "combine") - - if (COTIRE_TARGET_LANGUAGE) - set (_combinedFile "${COTIRE_ARGV3}") - set (_startIndex 4) - else() - set (_combinedFile "${COTIRE_ARGV2}") - set (_startIndex 3) - endif() - set (_files "") - foreach (_index RANGE ${_startIndex} ${COTIRE_ARGC}) - if (COTIRE_ARGV${_index}) - list (APPEND _files "${COTIRE_ARGV${_index}}") - endif() - endforeach() - - if (XCODE) - # executing pre-build action under Xcode, check dependency on files to be combined - set (_dependsOption DEPENDS ${_files}) - else() - # executing custom command, no need to re-check for dependencies - set (_dependsOption "") - endif() - - if (COTIRE_TARGET_LANGUAGE) - cotire_generate_unity_source( - "${_combinedFile}" ${_files} - LANGUAGE "${COTIRE_TARGET_LANGUAGE}" - ${_dependsOption}) - else() - cotire_generate_unity_source("${_combinedFile}" ${_files} ${_dependsOption}) - endif() - - elseif ("${COTIRE_ARGV1}" STREQUAL "cleanup") - - cotire_cleanup("${COTIRE_ARGV2}" "${COTIRE_ARGV3}" "${COTIRE_ARGV4}") - - else() - message (FATAL_ERROR "cotire: unknown command \"${COTIRE_ARGV1}\".") - endif() - -else() - - # cotire is being run in include mode - # set up all variable and property definitions - - if (NOT DEFINED COTIRE_DEBUG_INIT) - if (DEFINED COTIRE_DEBUG) - set (COTIRE_DEBUG_INIT ${COTIRE_DEBUG}) - else() - set (COTIRE_DEBUG_INIT FALSE) - endif() - endif() - option (COTIRE_DEBUG "Enable cotire debugging output?" ${COTIRE_DEBUG_INIT}) - - if (NOT DEFINED COTIRE_VERBOSE_INIT) - if (DEFINED COTIRE_VERBOSE) - set (COTIRE_VERBOSE_INIT ${COTIRE_VERBOSE}) - else() - set (COTIRE_VERBOSE_INIT FALSE) - endif() - endif() - option (COTIRE_VERBOSE "Enable cotire verbose output?" ${COTIRE_VERBOSE_INIT}) - - set (COTIRE_ADDITIONAL_PREFIX_HEADER_IGNORE_EXTENSIONS "inc;inl;ipp" CACHE STRING - "Ignore headers with the listed file extensions from the generated prefix header.") - - set (COTIRE_ADDITIONAL_PREFIX_HEADER_IGNORE_PATH "" CACHE STRING - "Ignore headers from these directories when generating the prefix header.") - - set (COTIRE_UNITY_SOURCE_EXCLUDE_EXTENSIONS "m;mm" CACHE STRING - "Ignore sources with the listed file extensions from the generated unity source.") - - set (COTIRE_MINIMUM_NUMBER_OF_TARGET_SOURCES "2" CACHE STRING - "Minimum number of sources in target required to enable use of precompiled header.") - - if (NOT DEFINED COTIRE_MAXIMUM_NUMBER_OF_UNITY_INCLUDES_INIT) - if (DEFINED COTIRE_MAXIMUM_NUMBER_OF_UNITY_INCLUDES) - set (COTIRE_MAXIMUM_NUMBER_OF_UNITY_INCLUDES_INIT ${COTIRE_MAXIMUM_NUMBER_OF_UNITY_INCLUDES}) - elseif ("${CMAKE_GENERATOR}" MATCHES "JOM|Ninja|Visual Studio") - # enable parallelization for generators that run multiple jobs by default - set (COTIRE_MAXIMUM_NUMBER_OF_UNITY_INCLUDES_INIT "-j") - else() - set (COTIRE_MAXIMUM_NUMBER_OF_UNITY_INCLUDES_INIT "0") - endif() - endif() - set (COTIRE_MAXIMUM_NUMBER_OF_UNITY_INCLUDES "${COTIRE_MAXIMUM_NUMBER_OF_UNITY_INCLUDES_INIT}" CACHE STRING - "Maximum number of source files to include in a single unity source file.") - - if (NOT COTIRE_PREFIX_HEADER_FILENAME_SUFFIX) - set (COTIRE_PREFIX_HEADER_FILENAME_SUFFIX "_prefix") - endif() - if (NOT COTIRE_UNITY_SOURCE_FILENAME_SUFFIX) - set (COTIRE_UNITY_SOURCE_FILENAME_SUFFIX "_unity") - endif() - if (NOT COTIRE_INTDIR) - set (COTIRE_INTDIR "cotire") - endif() - if (NOT COTIRE_PCH_ALL_TARGET_NAME) - set (COTIRE_PCH_ALL_TARGET_NAME "all_pch") - endif() - if (NOT COTIRE_UNITY_BUILD_ALL_TARGET_NAME) - set (COTIRE_UNITY_BUILD_ALL_TARGET_NAME "all_unity") - endif() - if (NOT COTIRE_CLEAN_ALL_TARGET_NAME) - set (COTIRE_CLEAN_ALL_TARGET_NAME "clean_cotire") - endif() - if (NOT COTIRE_CLEAN_TARGET_SUFFIX) - set (COTIRE_CLEAN_TARGET_SUFFIX "_clean_cotire") - endif() - if (NOT COTIRE_PCH_TARGET_SUFFIX) - set (COTIRE_PCH_TARGET_SUFFIX "_pch") - endif() - if (MSVC) - # MSVC default PCH memory scaling factor of 100 percent (75 MB) is too small for template heavy C++ code - # use a bigger default factor of 170 percent (128 MB) - if (NOT DEFINED COTIRE_PCH_MEMORY_SCALING_FACTOR) - set (COTIRE_PCH_MEMORY_SCALING_FACTOR "170") - endif() - endif() - if (NOT COTIRE_UNITY_BUILD_TARGET_SUFFIX) - set (COTIRE_UNITY_BUILD_TARGET_SUFFIX "_unity") - endif() - if (NOT DEFINED COTIRE_TARGETS_FOLDER) - set (COTIRE_TARGETS_FOLDER "cotire") - endif() - if (NOT DEFINED COTIRE_UNITY_OUTPUT_DIRECTORY) - if ("${CMAKE_GENERATOR}" MATCHES "Ninja") - # generated Ninja build files do not work if the unity target produces the same output file as the cotired target - set (COTIRE_UNITY_OUTPUT_DIRECTORY "unity") - else() - set (COTIRE_UNITY_OUTPUT_DIRECTORY "") - endif() - endif() - - # define cotire cache variables - - define_property( - CACHED_VARIABLE PROPERTY "COTIRE_ADDITIONAL_PREFIX_HEADER_IGNORE_PATH" - BRIEF_DOCS "Ignore headers from these directories when generating the prefix header." - FULL_DOCS - "The variable can be set to a semicolon separated list of include directories." - "If a header file is found in one of these directories or sub-directories, it will be excluded from the generated prefix header." - "If not defined, defaults to empty list." - ) - - define_property( - CACHED_VARIABLE PROPERTY "COTIRE_ADDITIONAL_PREFIX_HEADER_IGNORE_EXTENSIONS" - BRIEF_DOCS "Ignore includes with the listed file extensions from the generated prefix header." - FULL_DOCS - "The variable can be set to a semicolon separated list of file extensions." - "If a header file extension matches one in the list, it will be excluded from the generated prefix header." - "Includes with an extension in CMAKE__SOURCE_FILE_EXTENSIONS are always ignored." - "If not defined, defaults to inc;inl;ipp." - ) - - define_property( - CACHED_VARIABLE PROPERTY "COTIRE_UNITY_SOURCE_EXCLUDE_EXTENSIONS" - BRIEF_DOCS "Exclude sources with the listed file extensions from the generated unity source." - FULL_DOCS - "The variable can be set to a semicolon separated list of file extensions." - "If a source file extension matches one in the list, it will be excluded from the generated unity source file." - "Source files with an extension in CMAKE__IGNORE_EXTENSIONS are always excluded." - "If not defined, defaults to m;mm." - ) - - define_property( - CACHED_VARIABLE PROPERTY "COTIRE_MINIMUM_NUMBER_OF_TARGET_SOURCES" - BRIEF_DOCS "Minimum number of sources in target required to enable use of precompiled header." - FULL_DOCS - "The variable can be set to an integer > 0." - "If a target contains less than that number of source files, cotire will not enable the use of the precompiled header for the target." - "If not defined, defaults to 2." - ) - - define_property( - CACHED_VARIABLE PROPERTY "COTIRE_MAXIMUM_NUMBER_OF_UNITY_INCLUDES" - BRIEF_DOCS "Maximum number of source files to include in a single unity source file." - FULL_DOCS - "This may be set to an integer >= 0." - "If 0, cotire will only create a single unity source file." - "If a target contains more than that number of source files, cotire will create multiple unity source files for it." - "Can be set to \"-j\" to optimize the count of unity source files for the number of available processor cores." - "Can be set to \"-j jobs\" to optimize the number of unity source files for the given number of simultaneous jobs." - "Is used to initialize the target property COTIRE_UNITY_SOURCE_MAXIMUM_NUMBER_OF_INCLUDES." - "Defaults to \"-j\" for the generators Visual Studio, JOM or Ninja. Defaults to 0 otherwise." - ) - - # define cotire directory properties - - define_property( - DIRECTORY PROPERTY "COTIRE_ENABLE_PRECOMPILED_HEADER" - BRIEF_DOCS "Modify build command of cotired targets added in this directory to make use of the generated precompiled header." - FULL_DOCS - "See target property COTIRE_ENABLE_PRECOMPILED_HEADER." - ) - - define_property( - DIRECTORY PROPERTY "COTIRE_ADD_UNITY_BUILD" - BRIEF_DOCS "Add a new target that performs a unity build for cotired targets added in this directory." - FULL_DOCS - "See target property COTIRE_ADD_UNITY_BUILD." - ) - - define_property( - DIRECTORY PROPERTY "COTIRE_ADD_CLEAN" - BRIEF_DOCS "Add a new target that cleans all cotire generated files for cotired targets added in this directory." - FULL_DOCS - "See target property COTIRE_ADD_CLEAN." - ) - - define_property( - DIRECTORY PROPERTY "COTIRE_PREFIX_HEADER_IGNORE_PATH" - BRIEF_DOCS "Ignore headers from these directories when generating the prefix header." - FULL_DOCS - "See target property COTIRE_PREFIX_HEADER_IGNORE_PATH." - ) - - define_property( - DIRECTORY PROPERTY "COTIRE_PREFIX_HEADER_INCLUDE_PATH" - BRIEF_DOCS "Honor headers from these directories when generating the prefix header." - FULL_DOCS - "See target property COTIRE_PREFIX_HEADER_INCLUDE_PATH." - ) - - define_property( - DIRECTORY PROPERTY "COTIRE_PREFIX_HEADER_INCLUDE_PRIORITY_PATH" - BRIEF_DOCS "Header paths matching one of these directories are put at the top of the prefix header." - FULL_DOCS - "See target property COTIRE_PREFIX_HEADER_INCLUDE_PRIORITY_PATH." - ) - - define_property( - DIRECTORY PROPERTY "COTIRE_UNITY_SOURCE_PRE_UNDEFS" - BRIEF_DOCS "Preprocessor undefs to place in the generated unity source file before the inclusion of each source file." - FULL_DOCS - "See target property COTIRE_UNITY_SOURCE_PRE_UNDEFS." - ) - - define_property( - DIRECTORY PROPERTY "COTIRE_UNITY_SOURCE_POST_UNDEFS" - BRIEF_DOCS "Preprocessor undefs to place in the generated unity source file after the inclusion of each source file." - FULL_DOCS - "See target property COTIRE_UNITY_SOURCE_POST_UNDEFS." - ) - - define_property( - DIRECTORY PROPERTY "COTIRE_UNITY_SOURCE_MAXIMUM_NUMBER_OF_INCLUDES" - BRIEF_DOCS "Maximum number of source files to include in a single unity source file." - FULL_DOCS - "See target property COTIRE_UNITY_SOURCE_MAXIMUM_NUMBER_OF_INCLUDES." - ) - - define_property( - DIRECTORY PROPERTY "COTIRE_UNITY_LINK_LIBRARIES_INIT" - BRIEF_DOCS "Define strategy for setting up the unity target's link libraries." - FULL_DOCS - "See target property COTIRE_UNITY_LINK_LIBRARIES_INIT." - ) - - # define cotire target properties - - define_property( - TARGET PROPERTY "COTIRE_ENABLE_PRECOMPILED_HEADER" INHERITED - BRIEF_DOCS "Modify this target's build command to make use of the generated precompiled header." - FULL_DOCS - "If this property is set to TRUE, cotire will modify the build command to make use of the generated precompiled header." - "Irrespective of the value of this property, cotire will setup custom commands to generate the unity source and prefix header for the target." - "For makefile based generators cotire will also set up a custom target to manually invoke the generation of the precompiled header." - "The target name will be set to this target's name with the suffix _pch appended." - "Inherited from directory." - "Defaults to TRUE." - ) - - define_property( - TARGET PROPERTY "COTIRE_ADD_UNITY_BUILD" INHERITED - BRIEF_DOCS "Add a new target that performs a unity build for this target." - FULL_DOCS - "If this property is set to TRUE, cotire creates a new target of the same type that uses the generated unity source file instead of the target sources." - "Most of the relevant target properties will be copied from this target to the new unity build target." - "Target dependencies and linked libraries have to be manually set up for the new unity build target." - "The unity target name will be set to this target's name with the suffix _unity appended." - "Inherited from directory." - "Defaults to TRUE." - ) - - define_property( - TARGET PROPERTY "COTIRE_ADD_CLEAN" INHERITED - BRIEF_DOCS "Add a new target that cleans all cotire generated files for this target." - FULL_DOCS - "If this property is set to TRUE, cotire creates a new target that clean all files (unity source, prefix header, precompiled header)." - "The clean target name will be set to this target's name with the suffix _clean_cotire appended." - "Inherited from directory." - "Defaults to FALSE." - ) - - define_property( - TARGET PROPERTY "COTIRE_PREFIX_HEADER_IGNORE_PATH" INHERITED - BRIEF_DOCS "Ignore headers from these directories when generating the prefix header." - FULL_DOCS - "The property can be set to a list of directories." - "If a header file is found in one of these directories or sub-directories, it will be excluded from the generated prefix header." - "Inherited from directory." - "If not set, this property is initialized to \${CMAKE_SOURCE_DIR};\${CMAKE_BINARY_DIR}." - ) - - define_property( - TARGET PROPERTY "COTIRE_PREFIX_HEADER_INCLUDE_PATH" INHERITED - BRIEF_DOCS "Honor headers from these directories when generating the prefix header." - FULL_DOCS - "The property can be set to a list of directories." - "If a header file is found in one of these directories or sub-directories, it will be included in the generated prefix header." - "If a header file is both selected by COTIRE_PREFIX_HEADER_IGNORE_PATH and COTIRE_PREFIX_HEADER_INCLUDE_PATH," - "the option which yields the closer relative path match wins." - "Inherited from directory." - "If not set, this property is initialized to the empty list." - ) - - define_property( - TARGET PROPERTY "COTIRE_PREFIX_HEADER_INCLUDE_PRIORITY_PATH" INHERITED - BRIEF_DOCS "Header paths matching one of these directories are put at the top of prefix header." - FULL_DOCS - "The property can be set to a list of directories." - "Header file paths matching one of these directories will be inserted at the beginning of the generated prefix header." - "Header files are sorted according to the order of the directories in the property." - "If not set, this property is initialized to the empty list." - ) - - define_property( - TARGET PROPERTY "COTIRE_UNITY_SOURCE_PRE_UNDEFS" INHERITED - BRIEF_DOCS "Preprocessor undefs to place in the generated unity source file before the inclusion of each target source file." - FULL_DOCS - "This may be set to a semicolon-separated list of preprocessor symbols." - "cotire will add corresponding #undef directives to the generated unit source file before each target source file." - "Inherited from directory." - "Defaults to empty string." - ) - - define_property( - TARGET PROPERTY "COTIRE_UNITY_SOURCE_POST_UNDEFS" INHERITED - BRIEF_DOCS "Preprocessor undefs to place in the generated unity source file after the inclusion of each target source file." - FULL_DOCS - "This may be set to a semicolon-separated list of preprocessor symbols." - "cotire will add corresponding #undef directives to the generated unit source file after each target source file." - "Inherited from directory." - "Defaults to empty string." - ) - - define_property( - TARGET PROPERTY "COTIRE_UNITY_SOURCE_MAXIMUM_NUMBER_OF_INCLUDES" INHERITED - BRIEF_DOCS "Maximum number of source files to include in a single unity source file." - FULL_DOCS - "This may be set to an integer > 0." - "If a target contains more than that number of source files, cotire will create multiple unity build files for it." - "If not set, cotire will only create a single unity source file." - "Inherited from directory." - "Defaults to empty." - ) - - define_property( - TARGET PROPERTY "COTIRE__UNITY_SOURCE_INIT" - BRIEF_DOCS "User provided unity source file to be used instead of the automatically generated one." - FULL_DOCS - "If set, cotire will only add the given file(s) to the generated unity source file." - "If not set, cotire will add all the target source files to the generated unity source file." - "The property can be set to a user provided unity source file." - "Defaults to empty." - ) - - define_property( - TARGET PROPERTY "COTIRE__PREFIX_HEADER_INIT" - BRIEF_DOCS "User provided prefix header file to be used instead of the automatically generated one." - FULL_DOCS - "If set, cotire will add the given header file(s) to the generated prefix header file." - "If not set, cotire will generate a prefix header by tracking the header files included by the unity source file." - "The property can be set to a user provided prefix header file (e.g., stdafx.h)." - "Defaults to empty." - ) - - define_property( - TARGET PROPERTY "COTIRE_UNITY_LINK_LIBRARIES_INIT" INHERITED - BRIEF_DOCS "Define strategy for setting up unity target's link libraries." - FULL_DOCS - "If this property is empty or set to NONE, the generated unity target's link libraries have to be set up manually." - "If this property is set to COPY, the unity target's link libraries will be copied from this target." - "If this property is set to COPY_UNITY, the unity target's link libraries will be copied from this target with considering existing unity targets." - "Inherited from directory." - "Defaults to empty." - ) - - define_property( - TARGET PROPERTY "COTIRE__UNITY_SOURCE" - BRIEF_DOCS "Read-only property. The generated unity source file(s)." - FULL_DOCS - "cotire sets this property to the path of the generated single computation unit source file for the target." - "Defaults to empty string." - ) - - define_property( - TARGET PROPERTY "COTIRE__PREFIX_HEADER" - BRIEF_DOCS "Read-only property. The generated prefix header file." - FULL_DOCS - "cotire sets this property to the full path of the generated language prefix header for the target." - "Defaults to empty string." - ) - - define_property( - TARGET PROPERTY "COTIRE__PRECOMPILED_HEADER" - BRIEF_DOCS "Read-only property. The generated precompiled header file." - FULL_DOCS - "cotire sets this property to the full path of the generated language precompiled header binary for the target." - "Defaults to empty string." - ) - - define_property( - TARGET PROPERTY "COTIRE_UNITY_TARGET_NAME" - BRIEF_DOCS "The name of the generated unity build target corresponding to this target." - FULL_DOCS - "This property can be set to the desired name of the unity target that will be created by cotire." - "If not set, the unity target name will be set to this target's name with the suffix _unity appended." - "After this target has been processed by cotire, the property is set to the actual name of the generated unity target." - "Defaults to empty string." - ) - - # define cotire source properties - - define_property( - SOURCE PROPERTY "COTIRE_EXCLUDED" - BRIEF_DOCS "Do not modify source file's build command." - FULL_DOCS - "If this property is set to TRUE, the source file's build command will not be modified to make use of the precompiled header." - "The source file will also be excluded from the generated unity source file." - "Source files that have their COMPILE_FLAGS property set will be excluded by default." - "Defaults to FALSE." - ) - - define_property( - SOURCE PROPERTY "COTIRE_DEPENDENCY" - BRIEF_DOCS "Add this source file to dependencies of the automatically generated prefix header file." - FULL_DOCS - "If this property is set to TRUE, the source file is added to dependencies of the generated prefix header file." - "If the file is modified, cotire will re-generate the prefix header source upon build." - "Defaults to FALSE." - ) - - define_property( - SOURCE PROPERTY "COTIRE_UNITY_SOURCE_PRE_UNDEFS" - BRIEF_DOCS "Preprocessor undefs to place in the generated unity source file before the inclusion of this source file." - FULL_DOCS - "This may be set to a semicolon-separated list of preprocessor symbols." - "cotire will add corresponding #undef directives to the generated unit source file before this file is included." - "Defaults to empty string." - ) - - define_property( - SOURCE PROPERTY "COTIRE_UNITY_SOURCE_POST_UNDEFS" - BRIEF_DOCS "Preprocessor undefs to place in the generated unity source file after the inclusion of this source file." - FULL_DOCS - "This may be set to a semicolon-separated list of preprocessor symbols." - "cotire will add corresponding #undef directives to the generated unit source file after this file is included." - "Defaults to empty string." - ) - - define_property( - SOURCE PROPERTY "COTIRE_START_NEW_UNITY_SOURCE" - BRIEF_DOCS "Start a new unity source file which includes this source file as the first one." - FULL_DOCS - "If this property is set to TRUE, cotire will complete the current unity file and start a new one." - "The new unity source file will include this source file as the first one." - "This property essentially works as a separator for unity source files." - "Defaults to FALSE." - ) - - define_property( - SOURCE PROPERTY "COTIRE_TARGET" - BRIEF_DOCS "Read-only property. Mark this source file as cotired for the given target." - FULL_DOCS - "cotire sets this property to the name of target, that the source file's build command has been altered for." - "Defaults to empty string." - ) - - message (STATUS "cotire ${COTIRE_CMAKE_MODULE_VERSION} loaded.") - -endif() diff --git a/build/Modules/FindCERES.cmake b/build/Modules/FindCERES.cmake deleted file mode 100644 index 6905393be..000000000 --- a/build/Modules/FindCERES.cmake +++ /dev/null @@ -1,103 +0,0 @@ -# - try to find CERES headers -# -# Users may optionally supply: -# CERES_DIR - a prefix to start searching for the toon headers. -# -# Cache Variables: (probably not for direct use in your scripts) -# CERES_INCLUDE_DIR -# -# Non-cache variables you might use in your CMakeLists.txt: -# CERES_FOUND -# CERES_INCLUDE_DIRS -# CERES_LIBS -# CERES_DEFINITIONS -# -# Requires these CMake modules: -# FindPackageHandleStandardArgs (known included with CMake >=2.6.2) - -#try to use the Config script -if(NOT EXISTS "${CERES_DIR}") - find_path(CERES_DIR "CeresConfig.cmake" - HINTS "${CERES_ROOT}" "$ENV{CERES_ROOT}" "$ENV{CERES_DIR}" "$ENV{CERES_ROOT}/CMake" - PATHS "$ENV{PROGRAMFILES}" "$ENV{PROGRAMW6432}" "/usr" "/usr/local" "/usr/share" "/usr/local/share" "/usr/lib/cmake" "/usr/local/lib/cmake" "/usr/include" "/usr/lib/x86_64-linux-gnu/cmake" - PATH_SUFFIXES "Ceres" - DOC "Root directory of CERES") -endif() - -set(CERES_VERSION "") -if(EXISTS "${CERES_DIR}") - - ## Include the standard CMake script - include("${CERES_DIR}/CeresConfig.cmake") - set(CERES_LIBS ${CERES_LIBS} ${CERES_LIBRARIES}) - -else() - - # Find required packages - FIND_PACKAGE(Eigen ${SYSTEM_PACKAGE_QUIET}) - FIND_PACKAGE(SUITESPARSE ${SYSTEM_PACKAGE_QUIET}) - if(SUITESPARSE_FOUND) - set(CERES_LIBS ${CERES_LIBS} ${SUITESPARSE_LIBS}) - endif() - FIND_PACKAGE(GLOG ${SYSTEM_PACKAGE_QUIET}) - if(GLOG_FOUND) - set(CERES_INCLUDE_DIRS ${CERES_INCLUDE_DIRS} ${GLOG_INCLUDE_DIRS}) - set(CERES_LIBS ${CERES_LIBS} ${GLOG_LIBS}) - endif() - - if(NOT CERES_DIR OR "${CERES_DIR}" STREQUAL "") - set(CERES_DIR "$ENV{CERES_ROOT}") - endif() - set(CERES_DIR "${CERES_DIR}" CACHE PATH "Root directory of CERES library") - - #try to guess path - find_path(CERES_INCLUDE_DIR - NAMES "ceres/ceres.h" - HINTS "${CERES_DIR}" "$ENV{CERES_ROOT}" "/usr" "/usr/local" - PATH_SUFFIXES "include") - - set(CERES_FOUND FALSE) - if(EXISTS "${CERES_INCLUDE_DIR}" AND NOT "${CERES_INCLUDE_DIR}" STREQUAL "") - set(CERES_FOUND TRUE) - - find_library(CERES_LIBRARY_DEBUG "libceres" "ceres" "ceres_shared" PATHS "${CERES_DIR}/lib${PACKAGE_LIB_SUFFIX_DBG}" "$ENV{OpenCV_ROOT}/lib${PACKAGE_LIB_SUFFIX_DBG}" NO_DEFAULT_PATH) - find_library(CERES_LIBRARY_RELEASE "libceres" "ceres" "ceres_shared" PATHS "${CERES_DIR}/lib${PACKAGE_LIB_SUFFIX_REL}" "$ENV{OpenCV_ROOT}/lib${PACKAGE_LIB_SUFFIX_REL}" NO_DEFAULT_PATH) - find_library(CERES_LIBRARY_ALL NAMES "ceres" PATH_SUFFIXES "ceres") - - #Remove the cache value - set(CERES_LIBRARY "" CACHE STRING "" FORCE) - - #both debug/release - if(CERES_LIBRARY_DEBUG AND CERES_LIBRARY_RELEASE) - set(CERES_LIBRARY debug ${CERES_LIBRARY_DEBUG} optimized ${CERES_LIBRARY_RELEASE} CACHE STRING "" FORCE) - #only debug - elseif(CERES_LIBRARY_DEBUG) - set(CERES_LIBRARY ${CERES_LIBRARY_DEBUG} CACHE STRING "" FORCE) - #only release - elseif(CERES_LIBRARY_RELEASE) - set(CERES_LIBRARY ${CERES_LIBRARY_RELEASE} CACHE STRING "" FORCE) - #both debug/release - elseif(CERES_LIBRARY_ALL) - set(CERES_LIBRARY ${CERES_LIBRARY_ALL} CACHE STRING "" FORCE) - #no library found - else() - message("CERES library NOT found") - set(CERES_FOUND FALSE) - endif() - - #Add to the general list - if(CERES_LIBRARY) - set(CERES_LIBS ${CERES_LIBS} ${CERES_LIBRARY}) - endif() - endif() - -endif() - -if(CERES_FOUND) - set(CERES_INCLUDE_DIRS ${CERES_INCLUDE_DIRS} "${CERES_INCLUDE_DIR}") - set(CERES_DIR "${CERES_DIR}" CACHE PATH "" FORCE) - mark_as_advanced(CERES_DIR) - message(STATUS "CERES ${CERES_VERSION} found (include: ${CERES_INCLUDE_DIRS})") -else() - package_report_not_found(CERES "Please specify CERES directory using CERES_ROOT env. variable") -endif() diff --git a/build/Modules/FindCGAL.cmake b/build/Modules/FindCGAL.cmake deleted file mode 100644 index fcdc7b4fd..000000000 --- a/build/Modules/FindCGAL.cmake +++ /dev/null @@ -1,89 +0,0 @@ -########################################################### -# Find CGAL Library -#---------------------------------------------------------- -# CGAL_FOUND - True if headers and requested libraries were found -# CGAL_INCLUDE_DIRS - CGAL include directories -# CGAL_LIBRARY_DIRS - Link directories for CGAL libraries -# CGAL_LIBS - CGAL libraries -# CGAL_VERSION - MAJOR.MINOR -#---------------------------------------------------------- - -set(CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS true) - -if(NOT CGAL_DIR) - # Get the system search path as a list. - if(UNIX) - string(REGEX MATCHALL "[^:]+" CGAL_DIR_SEARCH1 "$ENV{PATH}") - else() - string(REGEX REPLACE "\\\\" "/" CGAL_DIR_SEARCH1 "$ENV{PATH}") - endif() - string(REGEX REPLACE "/;" ";" CGAL_DIR_SEARCH2 "${CGAL_DIR_SEARCH1}") - # Construct a set of paths relative to the system search path. - set(CGAL_DIR_SEARCH "") - foreach(dir ${CGAL_DIR_SEARCH2}) - set(CGAL_DIR_SEARCH ${CGAL_DIR_SEARCH} ${dir}/../lib/CGAL) - endforeach() - set(CGAL_DIR_SEARCH ${CGAL_DIR_SEARCH} "lib" "lib64") - - # - # Look for an installation or build tree. - # - find_path(CGAL_DIR "CGALConfig.cmake" - # Look for an environment variable CGAL_DIR. - HINTS "${CGAL_ROOT}" "$ENV{CGAL_ROOT}" "$ENV{CGAL_DIR}" "$ENV{PROGRAMFILES}" "$ENV{PROGRAMW6432}" - - # Look in places relative to the system executable search path. - ${CGAL_DIR_SEARCH} - - # Look in standard UNIX install locations. - PATHS "/usr" "/usr/local" "/usr/share" "/usr/local/share" "/usr/lib/cmake" "/usr/local/lib/cmake" "/usr/include" "/usr/lib/x86_64-linux-gnu/cmake" - - # Read from the CMakeSetup registry entries. It is likely that - # CGAL will have been recently built. - [HKEY_CURRENT_USER\\Software\\Kitware\\CMakeSetup\\Settings\\StartPath;WhereBuild1] - [HKEY_CURRENT_USER\\Software\\Kitware\\CMakeSetup\\Settings\\StartPath;WhereBuild2] - [HKEY_CURRENT_USER\\Software\\Kitware\\CMakeSetup\\Settings\\StartPath;WhereBuild3] - [HKEY_CURRENT_USER\\Software\\Kitware\\CMakeSetup\\Settings\\StartPath;WhereBuild4] - [HKEY_CURRENT_USER\\Software\\Kitware\\CMakeSetup\\Settings\\StartPath;WhereBuild5] - [HKEY_CURRENT_USER\\Software\\Kitware\\CMakeSetup\\Settings\\StartPath;WhereBuild6] - [HKEY_CURRENT_USER\\Software\\Kitware\\CMakeSetup\\Settings\\StartPath;WhereBuild7] - [HKEY_CURRENT_USER\\Software\\Kitware\\CMakeSetup\\Settings\\StartPath;WhereBuild8] - [HKEY_CURRENT_USER\\Software\\Kitware\\CMakeSetup\\Settings\\StartPath;WhereBuild9] - [HKEY_CURRENT_USER\\Software\\Kitware\\CMakeSetup\\Settings\\StartPath;WhereBuild10] - - PATH_SUFFIXES "CGAL" "share" "share/cgal" "share/cmake" "share/cmake/cgal" - - DOC "Root directory of CGAL library" - ) -endif() - -##==================================================== -## Include CGAL library -##---------------------------------------------------- -set(CGAL_VERSION "") -if(EXISTS "${CGAL_DIR}" AND NOT "${CGAL_DIR}" STREQUAL "") - if(EXISTS "${CGAL_DIR}/CGALConfig.cmake") - include("${CGAL_DIR}/CGALConfig.cmake") - set(CGAL_TARGET_LIBS "") - if(TARGET CGAL::CGAL) - get_target_property(CGAL_TARGET_LIBS CGAL::CGAL INTERFACE_LINK_LIBRARIES) - elseif(TARGET CGAL) - get_target_property(CGAL_TARGET_LIBS CGAL INTERFACE_LINK_LIBRARIES) - endif() - if(NOT CGAL_TARGET_LIBS) - set(CGAL_TARGET_LIBS "") - endif() - set(CGAL_LIBS ${CGAL_LIBS} ${CGAL_LIBRARIES} ${CGAL_LIBRARY} ${CGAL_TARGET_LIBS} ${CGAL_Core_LIBRARY} ${CGAL_ImageIO_LIBRARY} ${CGAL_3RD_PARTY_LIBRARIES} ${CGAL_Core_3RD_PARTY_LIBRARIES} ${CGAL_ImageIO_3RD_PARTY_LIBRARIES} ${MPFR_LIBRARIES} ${GMP_LIBRARIES} ${ZLIB_LIBRARIES}) - set(CGAL_VERSION "${CGAL_MAJOR_VERSION}.${CGAL_MINOR_VERSION}") - else() - set(CGAL_INCLUDE_DIRS "${CGAL_DIR}/include" "${CGAL_DIR}/auxiliary/gmp/include") - set(CGAL_LIBRARY_DIRS "${CGAL_DIR}/lib${PACKAGE_LIB_SUFFIX}") - endif() - set(CGAL_FOUND TRUE) - set(CGAL_DIR "${CGAL_DIR}" CACHE PATH "" FORCE) - mark_as_advanced(CGAL_DIR) - message(STATUS "CGAL ${CGAL_VERSION} found (include: ${CGAL_INCLUDE_DIRS})") -else() - package_report_not_found(CGAL "Please specify CGAL directory using CGAL_ROOT env. variable") -endif() -##==================================================== diff --git a/build/Modules/FindEigen.cmake b/build/Modules/FindEigen.cmake deleted file mode 100644 index 0f1d74c02..000000000 --- a/build/Modules/FindEigen.cmake +++ /dev/null @@ -1,55 +0,0 @@ -########################################################### -# Find EIGEN Library -#---------------------------------------------------------- - -find_path(EIGEN_DIR "Eigen/Core" - HINTS "${EIGEN_ROOT}" "$ENV{EIGEN_ROOT}" - PATHS "$ENV{PROGRAMFILES}" "$ENV{PROGRAMW6432}" "/usr" "/usr/local" "/usr/share" "/usr/local/share" "/usr/lib/x86_64-linux-gnu/cmake" - PATH_SUFFIXES "eigen" "eigen3" "include" - DOC "Root directory of EIGEN library") - -##==================================================== -## Include EIGEN library -##---------------------------------------------------- -if(EXISTS "${EIGEN_DIR}" AND NOT "${EIGEN_DIR}" STREQUAL "") - set(EIGEN_FOUND TRUE) - set(EIGEN_INCLUDE_DIRS ${EIGEN_DIR}) - set(EIGEN_DIR "${EIGEN_DIR}" CACHE PATH "" FORCE) - mark_as_advanced(EIGEN_DIR) - - # Extract Eigen version from Eigen/src/Core/util/Macros.h - SET(EIGEN_VERSION_FILE ${EIGEN_INCLUDE_DIRS}/Eigen/src/Core/util/Macros.h) - IF (NOT EXISTS ${EIGEN_VERSION_FILE}) - EIGEN_REPORT_NOT_FOUND( - "Could not find file: ${EIGEN_VERSION_FILE} " - "containing version information in Eigen install located at: " - "${EIGEN_INCLUDE_DIRS}.") - ELSE (NOT EXISTS ${EIGEN_VERSION_FILE}) - FILE(READ ${EIGEN_VERSION_FILE} EIGEN_VERSION_FILE_CONTENTS) - - STRING(REGEX MATCH "#define EIGEN_WORLD_VERSION [0-9]+" - EIGEN_WORLD_VERSION "${EIGEN_VERSION_FILE_CONTENTS}") - STRING(REGEX REPLACE "#define EIGEN_WORLD_VERSION ([0-9]+)" "\\1" - EIGEN_WORLD_VERSION "${EIGEN_WORLD_VERSION}") - - STRING(REGEX MATCH "#define EIGEN_MAJOR_VERSION [0-9]+" - EIGEN_MAJOR_VERSION "${EIGEN_VERSION_FILE_CONTENTS}") - STRING(REGEX REPLACE "#define EIGEN_MAJOR_VERSION ([0-9]+)" "\\1" - EIGEN_MAJOR_VERSION "${EIGEN_MAJOR_VERSION}") - - STRING(REGEX MATCH "#define EIGEN_MINOR_VERSION [0-9]+" - EIGEN_MINOR_VERSION "${EIGEN_VERSION_FILE_CONTENTS}") - STRING(REGEX REPLACE "#define EIGEN_MINOR_VERSION ([0-9]+)" "\\1" - EIGEN_MINOR_VERSION "${EIGEN_MINOR_VERSION}") - - # This is on a single line s/t CMake does not interpret it as a list of - # elements and insert ';' separators which would result in 3.;2.;0 nonsense. - SET(EIGEN_VERSION "${EIGEN_WORLD_VERSION}.${EIGEN_MAJOR_VERSION}.${EIGEN_MINOR_VERSION}") - ENDIF (NOT EXISTS ${EIGEN_VERSION_FILE}) - set(EIGEN_INCLUDE_DIR ${EIGEN_DIR}) - - message(STATUS "Eigen ${EIGEN_VERSION} found (include: ${EIGEN_INCLUDE_DIRS})") -else() - package_report_not_found(EIGEN "Please specify EIGEN directory using EIGEN_ROOT env. variable") -endif() -##==================================================== diff --git a/build/Modules/FindEigen3.cmake b/build/Modules/FindEigen3.cmake new file mode 100644 index 000000000..0b36805e7 --- /dev/null +++ b/build/Modules/FindEigen3.cmake @@ -0,0 +1,107 @@ +# - Try to find Eigen3 lib +# +# This module supports requiring a minimum version, e.g. you can do +# find_package(Eigen3 3.1.2) +# to require version 3.1.2 or newer of Eigen3. +# +# Once done this will define +# +# EIGEN3_FOUND - system has eigen lib with correct version +# EIGEN3_INCLUDE_DIR - the eigen include directory +# EIGEN3_VERSION - eigen version +# +# and the following imported target: +# +# Eigen3::Eigen - The header-only Eigen library +# +# This module reads hints about search locations from +# the following environment variables: +# +# EIGEN3_ROOT +# EIGEN3_ROOT_DIR + +# Copyright (c) 2006, 2007 Montel Laurent, +# Copyright (c) 2008, 2009 Gael Guennebaud, +# Copyright (c) 2009 Benoit Jacob +# Redistribution and use is allowed according to the terms of the 2-clause BSD license. + +if(NOT Eigen3_FIND_VERSION) + if(NOT Eigen3_FIND_VERSION_MAJOR) + set(Eigen3_FIND_VERSION_MAJOR 2) + endif() + if(NOT Eigen3_FIND_VERSION_MINOR) + set(Eigen3_FIND_VERSION_MINOR 91) + endif() + if(NOT Eigen3_FIND_VERSION_PATCH) + set(Eigen3_FIND_VERSION_PATCH 0) + endif() + + set(Eigen3_FIND_VERSION "${Eigen3_FIND_VERSION_MAJOR}.${Eigen3_FIND_VERSION_MINOR}.${Eigen3_FIND_VERSION_PATCH}") +endif() + +macro(_eigen3_check_version) + file(READ "${EIGEN3_INCLUDE_DIR}/Eigen/src/Core/util/Macros.h" _eigen3_version_header) + + string(REGEX MATCH "define[ \t]+EIGEN_WORLD_VERSION[ \t]+([0-9]+)" _eigen3_world_version_match "${_eigen3_version_header}") + set(EIGEN3_WORLD_VERSION "${CMAKE_MATCH_1}") + string(REGEX MATCH "define[ \t]+EIGEN_MAJOR_VERSION[ \t]+([0-9]+)" _eigen3_major_version_match "${_eigen3_version_header}") + set(EIGEN3_MAJOR_VERSION "${CMAKE_MATCH_1}") + string(REGEX MATCH "define[ \t]+EIGEN_MINOR_VERSION[ \t]+([0-9]+)" _eigen3_minor_version_match "${_eigen3_version_header}") + set(EIGEN3_MINOR_VERSION "${CMAKE_MATCH_1}") + + set(EIGEN3_VERSION ${EIGEN3_WORLD_VERSION}.${EIGEN3_MAJOR_VERSION}.${EIGEN3_MINOR_VERSION}) + if(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION}) + set(EIGEN3_VERSION_OK FALSE) + else() + set(EIGEN3_VERSION_OK TRUE) + endif() + + if(NOT EIGEN3_VERSION_OK) + + message(STATUS "Eigen3 version ${EIGEN3_VERSION} found in ${EIGEN3_INCLUDE_DIR}, " + "but at least version ${Eigen3_FIND_VERSION} is required") + endif() +endmacro() + +if (EIGEN3_INCLUDE_DIR) + + # in cache already + _eigen3_check_version() + set(EIGEN3_FOUND ${EIGEN3_VERSION_OK}) + set(Eigen3_FOUND ${EIGEN3_VERSION_OK}) + +else () + + # search first if an Eigen3Config.cmake is available in the system, + # if successful this would set EIGEN3_INCLUDE_DIR and the rest of + # the script will work as usual + find_package(Eigen3 ${Eigen3_FIND_VERSION} NO_MODULE QUIET) + + if(NOT EIGEN3_INCLUDE_DIR) + find_path(EIGEN3_INCLUDE_DIR NAMES signature_of_eigen3_matrix_library + HINTS + ENV EIGEN3_ROOT + ENV EIGEN3_ROOT_DIR + PATHS + ${CMAKE_INSTALL_PREFIX}/include + ${KDE4_INCLUDE_DIR} + PATH_SUFFIXES eigen3 eigen + ) + endif() + + if(EIGEN3_INCLUDE_DIR) + _eigen3_check_version() + endif() + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(Eigen3 DEFAULT_MSG EIGEN3_INCLUDE_DIR EIGEN3_VERSION_OK) + + mark_as_advanced(EIGEN3_INCLUDE_DIR) + +endif() + +if(EIGEN3_FOUND AND NOT TARGET Eigen3::Eigen) + add_library(Eigen3::Eigen INTERFACE IMPORTED) + set_target_properties(Eigen3::Eigen PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${EIGEN3_INCLUDE_DIR}") +endif() diff --git a/build/OpenMVSConfig.cmake.in b/build/OpenMVSConfig.cmake.in deleted file mode 100644 index 96b8fe2ce..000000000 --- a/build/OpenMVSConfig.cmake.in +++ /dev/null @@ -1,18 +0,0 @@ -# - Configure file for the OpenMVS package -# It defines the following variables -# OpenMVS_INCLUDE_DIRS - include directories for OpenMVS -# OpenMVS_LIBRARIES - libraries to link against -# OpenMVS_BINARIES - the binaries - -# Compute paths -get_filename_component(OpenMVS_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) -set(OpenMVS_INCLUDE_DIRS "@CONF_INCLUDE_DIRS@") - -# Our library dependencies (contains definitions for IMPORTED targets) -if(NOT TARGET MVS AND NOT OpenMVS_BINARY_DIR) - include("${OpenMVS_CMAKE_DIR}/OpenMVSTargets.cmake") -endif() - -# These are IMPORTED targets created by OpenMVSTargets.cmake -set(OpenMVS_LIBRARIES MVS) -set(OpenMVS_BINARIES InterfaceVisualSFM DensifyPointCloud ReconstructMesh RefineMesh TextureMesh) diff --git a/build/OpenMVSConfigVersion.cmake.in b/build/OpenMVSConfigVersion.cmake.in deleted file mode 100644 index 3c9e7eb61..000000000 --- a/build/OpenMVSConfigVersion.cmake.in +++ /dev/null @@ -1,11 +0,0 @@ -set(PACKAGE_VERSION "@OpenMVS_VERSION@") - -# Check whether the requested PACKAGE_FIND_VERSION is compatible -if("${PACKAGE_VERSION}" VERSION_LESS "${PACKAGE_FIND_VERSION}") - set(PACKAGE_VERSION_COMPATIBLE FALSE) -else() - set(PACKAGE_VERSION_COMPATIBLE TRUE) - if ("${PACKAGE_VERSION}" VERSION_EQUAL "${PACKAGE_FIND_VERSION}") - set(PACKAGE_VERSION_EXACT TRUE) - endif() -endif() diff --git a/build/Templates/ConfigLocal.h.in b/build/Templates/ConfigLocal.h.in index 24ba104d3..0ca0e239a 100644 --- a/build/Templates/ConfigLocal.h.in +++ b/build/Templates/ConfigLocal.h.in @@ -6,9 +6,6 @@ // OpenMVS compiled as static or dynamic libs #cmakedefine BUILD_SHARED_LIBS -// Define to 1 if you have the header file -#cmakedefine01 HAVE_INTTYPES_H - // Define to 1 if exceptions are enabled #cmakedefine01 _HAS_EXCEPTIONS @@ -24,6 +21,9 @@ // Boost support #cmakedefine _USE_BOOST +// Boost with Python support +#cmakedefine _USE_BOOST_PYTHON + // Eigen Matrix & Linear Algebra Library #cmakedefine _USE_EIGEN @@ -53,6 +53,9 @@ // Fast float to int support #cmakedefine _USE_FAST_FLOAT2INT +// Fast INVSQRT support +#cmakedefine _USE_FAST_INVSQRT + // Fast CBRT support #cmakedefine _USE_FAST_CBRT diff --git a/build/Templates/OpenMVSConfig.cmake.in b/build/Templates/OpenMVSConfig.cmake.in new file mode 100644 index 000000000..9747b3e16 --- /dev/null +++ b/build/Templates/OpenMVSConfig.cmake.in @@ -0,0 +1,23 @@ +# Configure file for the OpenMVS package, defining the following variables: +# OpenMVS_INCLUDE_DIRS - include directories +# OpenMVS_DEFINITIONS - definitions to be used +# OpenMVS_LIBRARIES - libraries to link against +# OpenMVS_BINARIES - binaries + +@PACKAGE_INIT@ + +set(OpenMVS_VERSION "@OpenMVS_VERSION@") + +# Compute paths +set(OpenMVS_PREFIX "@CMAKE_INSTALL_PREFIX@") +set(OpenMVS_CMAKE_DIR "@INSTALL_CMAKE_DIR_IN@") +set(OpenMVS_INCLUDE_DIRS "@INSTALL_INCLUDE_DIR_IN@") + +set(OpenMVS_DEFINITIONS "@OpenMVS_DEFINITIONS@") + +# These are IMPORTED targets created by OpenMVSTargets.cmake +set(OpenMVS_LIBRARIES MVS) +set(OpenMVS_BINARIES InterfaceCOLMAP DensifyPointCloud ReconstructMesh RefineMesh TextureMesh) + +include("${CMAKE_CURRENT_LIST_DIR}/OpenMVSTargets.cmake") +check_required_components("OpenMVS") diff --git a/build/cmake_uninstall.cmake.in b/build/Templates/cmake_uninstall.cmake.in similarity index 100% rename from build/cmake_uninstall.cmake.in rename to build/Templates/cmake_uninstall.cmake.in diff --git a/build/Utils.cmake b/build/Utils.cmake index f41c9d89f..7cfec3e92 100644 --- a/build/Utils.cmake +++ b/build/Utils.cmake @@ -174,9 +174,11 @@ macro(ComposePackageLibSuffix) set(PACKAGE_LIB_SUFFIX_DBG "") set(PACKAGE_LIB_SUFFIX_REL "") if(MSVC) - if("${MSVC_VERSION}" STREQUAL "1921") + if("${MSVC_VERSION}" STRGREATER "1929") + set(PACKAGE_LIB_SUFFIX "/vc17") + elseif("${MSVC_VERSION}" STRGREATER "1916") set(PACKAGE_LIB_SUFFIX "/vc16") - elseif("${MSVC_VERSION}" STREQUAL "1916") + elseif("${MSVC_VERSION}" STRGREATER "1900") set(PACKAGE_LIB_SUFFIX "/vc15") elseif("${MSVC_VERSION}" STREQUAL "1900") set(PACKAGE_LIB_SUFFIX "/vc14") @@ -273,18 +275,6 @@ macro(add_option variable description value) endmacro() -# Set as Pre-Compiled Header automaticaly or to the given file name -macro(set_target_pch TRGT) - if(ENABLE_PRECOMPILED_HEADERS AND COMMAND cotire) - if(NOT ${ARGN} STREQUAL "") - set_target_properties("${TRGT}" PROPERTIES COTIRE_CXX_PREFIX_HEADER_INIT "${ARGN}") - endif() - set_target_properties("${TRGT}" PROPERTIES COTIRE_ADD_UNITY_BUILD FALSE) - cotire("${TRGT}") - endif() -endmacro() - - # Optimize compiler settings set(STATIC_COMPILER_FAIL_REGEX @@ -370,14 +360,17 @@ macro(add_extra_compiler_option option) if(CMAKE_BUILD_TYPE) set(CMAKE_TRY_COMPILE_CONFIGURATION ${CMAKE_BUILD_TYPE}) endif() - _check_flag_support(CXX "${option}" _varname "${BUILD_EXTRA_CXX_FLAGS} ${ARGN}") - if(${_varname}) - set(BUILD_EXTRA_CXX_FLAGS "${BUILD_EXTRA_CXX_FLAGS} ${option}") + if(CMAKE_CXX_COMPILER_ID) + _check_flag_support(CXX "${option}" _varname "${BUILD_EXTRA_CXX_FLAGS} ${ARGN}") + if(${_varname}) + set(BUILD_EXTRA_CXX_FLAGS "${BUILD_EXTRA_CXX_FLAGS} ${option}") + endif() endif() - - _check_flag_support(C "${option}" _varname "${BUILD_EXTRA_C_FLAGS} ${ARGN}") - if(${_varname}) - set(BUILD_EXTRA_C_FLAGS "${BUILD_EXTRA_C_FLAGS} ${option}") + if(CMAKE_C_COMPILER_ID) + _check_flag_support(C "${option}" _varname "${BUILD_EXTRA_C_FLAGS} ${ARGN}") + if(${_varname}) + set(BUILD_EXTRA_C_FLAGS "${BUILD_EXTRA_C_FLAGS} ${option}") + endif() endif() endmacro() @@ -401,21 +394,6 @@ macro(optimize_default_compiler_settings) add_option(ENABLE_NOISY_WARNINGS "Show all warnings even if they are too noisy" OFF ) add_option(ENABLE_WARNINGS_AS_ERRORS "Treat warnings as errors" OFF ) - if(MINGW) - # mingw compiler is known to produce unstable SSE code with -O3 hence we are trying to use -O2 instead - if(CMAKE_COMPILER_IS_GNUCXX) - foreach(flags CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_RELEASE CMAKE_CXX_FLAGS_DEBUG) - string(REPLACE "-O3" "-O2" ${flags} "${${flags}}") - endforeach() - endif() - - if(CMAKE_COMPILER_IS_GNUCC) - foreach(flags CMAKE_C_FLAGS CMAKE_C_FLAGS_RELEASE CMAKE_C_FLAGS_DEBUG) - string(REPLACE "-O3" "-O2" ${flags} "${${flags}}") - endforeach() - endif() - endif() - set(BUILD_EXTRA_FLAGS "") set(BUILD_EXTRA_C_FLAGS "") set(BUILD_EXTRA_CXX_FLAGS "") @@ -426,25 +404,47 @@ macro(optimize_default_compiler_settings) set(BUILD_EXTRA_EXE_LINKER_FLAGS_DEBUG "") # try to enable C++14/C++11 support - if(${CMAKE_VERSION} VERSION_LESS "3.8.2") - check_cxx_compiler_flag(--std=c++14 SUPPORTS_STD_CXX14) - check_cxx_compiler_flag(--std=c++11 SUPPORTS_STD_CXX11) - if(SUPPORTS_STD_CXX14) + if(CMAKE_VERSION VERSION_LESS "3.8.2") + if (MSVC) + set(CXX_CHECK_PREFIX "/std:") + else() + set(CXX_CHECK_PREFIX "--std=") + endif() + check_cxx_compiler_flag("${CXX_CHECK_PREFIX}c++20" SUPPORTS_STD_CXX20) + check_cxx_compiler_flag("${CXX_CHECK_PREFIX}c++17" SUPPORTS_STD_CXX17) + check_cxx_compiler_flag("${CXX_CHECK_PREFIX}c++14" SUPPORTS_STD_CXX14) + check_cxx_compiler_flag("${CXX_CHECK_PREFIX}c++11" SUPPORTS_STD_CXX11) + if(SUPPORTS_STD_CXX20) + set(CMAKE_CXX_STANDARD 20) + elseif(SUPPORTS_STD_CXX17) + set(CMAKE_CXX_STANDARD 17) + elseif(SUPPORTS_STD_CXX14) set(CMAKE_CXX_STANDARD 14) elseif(SUPPORTS_STD_CXX11) set(CMAKE_CXX_STANDARD 11) endif() else() - foreach(i ${CMAKE_CXX_COMPILE_FEATURES}) - if("${i}" STREQUAL "cxx_std_14") - set(CMAKE_CXX_STANDARD 14) - break() - elseif("${i}" STREQUAL "cxx_std_11") - set(CMAKE_CXX_STANDARD 11) + list(FIND CMAKE_CXX_COMPILE_FEATURES "cxx_std_20" CXX_STD_INDEX) + if(${CXX_STD_INDEX} GREATER -1) + set(CMAKE_CXX_STANDARD 20) + else() + list(FIND CMAKE_CXX_COMPILE_FEATURES "cxx_std_17" CXX_STD_INDEX) + if(${CXX_STD_INDEX} GREATER -1) + set(CMAKE_CXX_STANDARD 17) + else() + list(FIND CMAKE_CXX_COMPILE_FEATURES "cxx_std_14" CXX_STD_INDEX) + if(${CXX_STD_INDEX} GREATER -1) + set(CMAKE_CXX_STANDARD 14) + else() + list(FIND CMAKE_CXX_COMPILE_FEATURES "cxx_std_11" CXX_STD_INDEX) + if(${CXX_STD_INDEX} GREATER -1) + set(CMAKE_CXX_STANDARD 11) + endif() + endif() endif() - endforeach() + endif() endif() - if(CLANG AND (CMAKE_CXX_STANDARD EQUAL 11 OR CMAKE_CXX_STANDARD EQUAL 14)) + if(CLANG AND (CMAKE_CXX_STANDARD EQUAL 11 OR CMAKE_CXX_STANDARD EQUAL 14 OR CMAKE_CXX_STANDARD EQUAL 17 OR CMAKE_CXX_STANDARD EQUAL 20)) set(CMAKE_EXE_LINKER_FLAGS "-stdlib=libc++") add_extra_compiler_option(-stdlib=libc++) endif() @@ -452,15 +452,6 @@ macro(optimize_default_compiler_settings) set(CMAKE_CXX_EXTENSIONS OFF) message("Compiling with C++${CMAKE_CXX_STANDARD}") - if(MINGW) - # http://gcc.gnu.org/bugzilla/show_bug.cgi?id=40838 - # here we are trying to workaround the problem - add_extra_compiler_option(-mstackrealign) - if(NOT HAVE_CXX_MSTACKREALIGN) - add_extra_compiler_option(-mpreferred-stack-boundary=2) - endif() - endif() - if(FLG_COMPILER_IS_GNU) # High level of warnings. add_extra_compiler_option(-W) @@ -471,7 +462,6 @@ macro(optimize_default_compiler_settings) add_extra_compiler_option(-Werror=sequence-point) add_extra_compiler_option(-Wformat) add_extra_compiler_option(-Werror=format-security -Wformat) - add_extra_compiler_option(-Wstrict-prototypes) add_extra_compiler_option(-Winit-self) add_extra_compiler_option(-Wsign-promo) add_extra_compiler_option(-Wreorder) @@ -508,11 +498,15 @@ macro(optimize_default_compiler_settings) add_extra_compiler_option(-Wno-delete-incomplete) add_extra_compiler_option(-Wno-unnamed-type-template-args) add_extra_compiler_option(-Wno-int-in-bool-context) + add_extra_compiler_option(-Wno-deprecated-declarations) + add_extra_compiler_option(-Wno-deprecated-anon-enum-enum-conversion) + add_extra_compiler_option(-Wno-deprecated-enum-compare-conditional) + add_extra_compiler_option(-Wno-deprecated-enum-enum-conversion) endif() add_extra_compiler_option(-fdiagnostics-show-option) add_extra_compiler_option(-ftemplate-backtrace-limit=0) - # The -Wno-long-long is required in 64bit systems when including sytem headers. + # The -Wno-long-long is required in 64bit systems when including system headers. if(X86_64) add_extra_compiler_option(-Wno-long-long) endif() @@ -526,17 +520,13 @@ macro(optimize_default_compiler_settings) add_extra_compiler_option(-Werror) endif() - if(X86 AND NOT MINGW64 AND NOT X86_64 AND NOT APPLE) - add_extra_compiler_option(-march=i686) - endif() - # Other optimizations if(ENABLE_OMIT_FRAME_POINTER) add_extra_compiler_option(-fomit-frame-pointer) else() add_extra_compiler_option(-fno-omit-frame-pointer) - endif() - if(NOT CLANG) + endif() + if(NOT CLANG) if(ENABLE_FAST_MATH) add_extra_compiler_option(-ffast-math) else() @@ -664,16 +654,15 @@ macro(optimize_default_compiler_settings) # fix virtual memory range for PCH exceeded error set(BUILD_EXTRA_FLAGS "${BUILD_EXTRA_FLAGS} /Zm170") + + # enable __cplusplus + set(BUILD_EXTRA_FLAGS "${BUILD_EXTRA_FLAGS} /Zc:__cplusplus") endif() # Extra link libs if the user selects building static libs: # Android does not need these settings because they are already set by toolchain file - if(CMAKE_COMPILER_IS_GNUCXX AND NOT ANDROID) - if(BUILD_SHARED_LIBS) - set(BUILD_EXTRA_FLAGS "${BUILD_EXTRA_FLAGS} -fPIC") - else() - set(BUILD_EXTRA_LINKER_LIBS "${BUILD_EXTRA_LINKER_LIBS} stdc++") - endif() + if(CMAKE_COMPILER_IS_GNUCXX AND NOT ANDROID AND NOT BUILD_SHARED_LIBS) + set(BUILD_EXTRA_LINKER_LIBS "${BUILD_EXTRA_LINKER_LIBS} stdc++") endif() # Add user supplied extra options (optimization, etc...) @@ -721,7 +710,6 @@ macro(optimize_default_compiler_settings) string(REPLACE "/Zm1000" "" ${flags} "${${flags}}") endforeach() endif() - CHECK_INCLUDE_FILE("inttypes.h" HAVE_INTTYPES_H) endmacro() @@ -825,32 +813,35 @@ endmacro() # Initialize variables needed for a library type project. macro(ConfigLibrary) # Offer the user the choice of overriding the installation directories - set(INSTALL_LIB_DIR "lib/${PROJECT_NAME}" CACHE PATH "Installation directory for libraries") - set(INSTALL_BIN_DIR "bin/${PROJECT_NAME}" CACHE PATH "Installation directory for executables") - set(INSTALL_INCLUDE_DIR "include/${PROJECT_NAME}" CACHE PATH "Installation directory for header files") + set(INSTALL_LIB_DIR "lib" CACHE PATH "Installation directory for libraries") + set(INSTALL_BIN_DIR "bin" CACHE PATH "Installation directory for executables") + set(INSTALL_INCLUDE_DIR "include" CACHE PATH "Installation directory for header files") if(WIN32 AND NOT CYGWIN) set(DEF_INSTALL_CMAKE_DIR "CMake") else() - set(DEF_INSTALL_CMAKE_DIR "lib/CMake/${PROJECT_NAME}") + set(DEF_INSTALL_CMAKE_DIR "lib/cmake") endif() set(INSTALL_CMAKE_DIR ${DEF_INSTALL_CMAKE_DIR} CACHE PATH "Installation directory for CMake files") - # Make relative paths absolute (needed later on) foreach(p LIB BIN INCLUDE CMAKE) set(var INSTALL_${p}_DIR) - if(NOT IS_ABSOLUTE "${${var}}") - set(${var} "${CMAKE_INSTALL_PREFIX}/${${var}}") + set(varp INSTALL_${p}_DIR_PREFIX) + if(IS_ABSOLUTE "${${varp}}") + set(${varp} "${${varp}}") + else() + set(${varp} "${CMAKE_INSTALL_PREFIX}/${${var}}") endif() + set(${var} "${${varp}}/${PROJECT_NAME}") endforeach() endmacro() # Defines the main libraries. User tests should link # with one of them. -function(cxx_library_with_type_no_pch name folder type cxx_flags) +function(cxx_library_with_type name folder type cxx_flags) # type can be either STATIC or SHARED to denote a static or shared library. # ARGN refers to additional arguments after 'cxx_flags'. add_library("${name}" ${type} ${ARGN}) - set_target_properties("${name}" PROPERTIES COMPILE_FLAGS "${cxx_flags}") + #set_target_properties("${name}" PROPERTIES COMPILE_FLAGS "${cxx_flags}") if ((BUILD_SHARED_LIBS AND NOT type STREQUAL "STATIC") OR type STREQUAL "SHARED") set_target_properties("${name}" PROPERTIES COMPILE_DEFINITIONS "_USRDLL") else() @@ -858,31 +849,16 @@ function(cxx_library_with_type_no_pch name folder type cxx_flags) endif() # Set project folder set_target_properties("${name}" PROPERTIES FOLDER "${folder}") -endfunction() - -function(cxx_library_with_type name folder type cxx_flags) - cxx_library_with_type_no_pch("${name}" "${folder}" "${type}" "${cxx_flags}" ${ARGN}) - # Generate precompiled headers - set_target_pch("${name}") -endfunction() - -######################################################################## -# -# Helper functions for creating build targets. - -function(cxx_shared_library name folder cxx_flags) - cxx_library_with_type("${name}" "${folder}" SHARED "${cxx_flags}" ${ARGN}) -endfunction() - -function(cxx_library name folder cxx_flags) - cxx_library_with_type("${name}" "${folder}" "" "${cxx_flags}" ${ARGN}) + if(BUILD_SHARED_LIBS OR PARTIAL_BUILD_SHARED_LIBS) + set_target_properties("${name}" PROPERTIES POSITION_INDEPENDENT_CODE ON) + endif() endfunction() # cxx_executable_with_flags(name cxx_flags libs srcs...) # # creates a named C++ executable that depends on the given libraries and # is built from the given source files with the given compiler flags. -function(cxx_executable_with_flags_no_pch name folder cxx_flags libs) +function(cxx_executable_with_flags name folder cxx_flags libs) add_executable("${name}" ${ARGN}) if (cxx_flags) set_target_properties("${name}" PROPERTIES COMPILE_FLAGS "${cxx_flags}") @@ -895,36 +871,3 @@ function(cxx_executable_with_flags_no_pch name folder cxx_flags libs) # Set project folder set_target_properties("${name}" PROPERTIES FOLDER "${folder}") endfunction() - -function(cxx_executable_with_flags name folder cxx_flags libs) - cxx_executable_with_flags_no_pch("${name}" "${folder}" "${cxx_flags}" "${libs}" ${ARGN}) - # Generate precompiled headers - set_target_pch("${name}") -endfunction() - -# cxx_executable(name dir lib srcs...) -# -# creates a named target that depends on the given libs and is built -# from the given source files. dir/name.cc is implicitly included in -# the source file list. -function(cxx_executable name folder dir libs) - cxx_executable_with_flags("${name}" "${folder}" "${cxx_default}" "${libs}" "${dir}/${name}.cpp" ${ARGN}) -endfunction() - -# cxx_test_with_flags(name cxx_flags libs srcs...) -# -# creates a named C++ test that depends on the given libs and is built -# from the given source files with the given compiler flags. -function(cxx_test_with_flags name folder cxx_flags libs) - cxx_executable_with_flags("${name}" "${folder}" "${cxx_flags}" "${libs}" ${ARGN}) - add_test("${name}" "${name}") -endfunction() - -# cxx_test(name libs srcs...) -# -# creates a named test target that depends on the given libs and is -# built from the given source files. Unlike cxx_test_with_flags, -# test/name.cc is already implicitly included in the source file list. -function(cxx_test name folder libs) - cxx_test_with_flags("${name}" "${folder}" "${cxx_default}" "${libs}" "test/${name}.cc" ${ARGN}) -endfunction() diff --git a/docker/Dockerfile b/docker/Dockerfile index 03e40a39d..95a5c5d43 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,46 +1,16 @@ -FROM ubuntu:18.04 +ARG BASE_IMAGE=ubuntu:22.04 -# Initialize the environment -RUN apt update -RUN apt install -y cmake git vim +FROM $BASE_IMAGE -# Prepare and empty machine for building: -RUN apt-get update -yq -RUN apt-get install -yq -RUN apt-get -y install git mercurial cmake libpng-dev libjpeg-dev libtiff-dev libglu1-mesa-dev +ARG MASTER=0 +ARG USER_ID +ARG GROUP_ID +ARG CUDA=0 -# Eigen (Known issues with eigen 3.3.7 as of 12/10/2019, so using this tested branch/commit instead) -RUN git clone https://gitlab.com/libeigen/eigen --branch 3.2 ed5cd0a4d16e12daa1bef608628c103e67969d63 -RUN mkdir eigen_build -RUN cd eigen_build &&\ - cmake . ../eigen &&\ - make && make install &&\ - cd .. +COPY buildInDocker.sh /tmp/buildInDocker.sh +RUN /tmp/buildInDocker.sh --cuda $CUDA --user_id $USER_ID --group_id $GROUP_ID --master $MASTER && rm /tmp/buildInDocker.sh -# Boost -RUN apt-get -y install libboost-iostreams-dev libboost-program-options-dev libboost-system-dev libboost-serialization-dev +USER user -# OpenCV -RUN DEBIAN_FRONTEND=noninteractive apt-get install -yq libopencv-dev - -# CGAL -RUN apt-get -y install libcgal-dev libcgal-qt5-dev - -# VCGLib -RUN git clone https://github.com/cdcseacave/VCG.git vcglib - -# Build from stable release openMVS1.0 -RUN git clone https://github.com/cdcseacave/openMVS.git --branch v1.0 - -# Uncomment below (and comment above) to use the latest commit from the master branch -# RUN git clone https://github.com/cdcseacave/openMVS.git openMVS - -RUN mkdir openMVS_build -RUN cd openMVS_build &&\ - cmake . ../openMVS -DCMAKE_BUILD_TYPE=Release -DVCG_ROOT=/vcglib - -# Install OpenMVS library -RUN cd openMVS_build &&\ - make -j4 &&\ - make install +# Add binaries to path ENV PATH /usr/local/bin/OpenMVS:$PATH diff --git a/docker/README.md b/docker/README.md index d0008569e..f80a7cefc 100644 --- a/docker/README.md +++ b/docker/README.md @@ -6,7 +6,9 @@ 1. Make sure docker is installed on your local machine. 2. Run the 'easy start' script, using the *full local path* to the folder with your SFM input files (perhaps output from openMVG or COLMAP): - ./QUICK_START.sh /path/where/your/SFM/results/are +``` +./QUICK_START.sh /path/where/your/SFM/results/are +``` 3. This will put you in a directory (inside the Docker container) mounted to the local path you specified so that you can run openMVS binaries on your own SFM inputs. Enjoy! @@ -14,12 +16,20 @@ You can also build the docker image from scratch based on the **Dockerfile** (perhaps with your own changes / modifications) using: - ./buildFromScratch.sh /path/where/your/SFM/results/are +```sh +./buildFromScratch.sh --workspace /path/where/your/SFM/results/are +# With CUDA support: +./buildFromScratch.sh --cuda --workspace /path/where/your/SFM/results/are +# From master branch: +./buildFromScratch.sh --master --workspace /path/where/your/SFM/results/are +``` ## NOTES + This workflow is pinned to build from [openMVS 1.0](https://github.com/cdcseacave/openMVS/releases/tag/v1.0). To build from a different release, or build from the latest commit on master, open up the Dockerfile and comment/uncomment as directed. ++ Display is also supported inside docker. If there are any issues with `xauth` make sure to run `xhost +` on your host machine. + + Running openMVS binaries can use a lot of memory (depending on the size of your data set/ imagery). Docker has a relatively small default memory setting (2Gb on Mac). You will probably want to increase this before you run any larger workflows. From Docker desktop on Mac for example, just open the Docker GUI, go to the *Advanced* tab and increase via the slider: ![alt text][dockerParam] diff --git a/docker/buildFromScratch.cmd b/docker/buildFromScratch.cmd new file mode 100644 index 000000000..bbb10a12b --- /dev/null +++ b/docker/buildFromScratch.cmd @@ -0,0 +1,49 @@ +@echo off + +set WORKSPACE=%CD% + +set CUDA_BUILD_ARGS= +set CUDA_RUNTIME_ARGS= +set CUDA_CONTAINER_SUFFIX= +set MASTER_ARGS= +set DISPLAY_ARGS= + +:parse_args +if "%~1" == "" goto build_image +if "%~1" == "--cuda" ( + set "CUDA_BUILD_ARGS=--build-arg CUDA=1 --build-arg BASE_IMAGE=nvidia/cuda:11.8.0-devel-ubuntu22.04" + set "CUDA_RUNTIME_ARGS=--gpus all -e NVIDIA_DRIVER_CAPABILITIES=compute,utility,graphics" + set "CUDA_CONTAINER_SUFFIX=-cuda" + shift + goto parse_args +) +if "%~1" == "--master" ( + set "MASTER_ARGS=--build-arg MASTER=1" + shift + goto parse_args +) +if "%~1" == "--workspace" ( + set "WORKSPACE=%~2" + shift + shift + goto parse_args +) +if "%~1" == "--gui" ( + set "XSOCK=\\.\pipe\X11-unix" + set "XAUTH=%TEMP%\docker.xauth" + set "DISPLAY_ARGS=-e ^"DISPLAY=%COMPUTERNAME%:0.0^" -v ^"%XAUTH%:%XAUTH%:rw^" -e ^"XAUTHORITY=%XAUTH%^"" + shift + goto parse_args +) +echo Unknown argument: %~1 +exit /b 1 + +:build_image +echo Running with workspace: %WORKSPACE% + +docker build -t="openmvs-ubuntu%CUDA_CONTAINER_SUFFIX%" --build-arg "USER_ID=1001" --build-arg "GROUP_ID=1001" %CUDA_BUILD_ARGS% %MASTER_ARGS% . +if errorlevel 1 exit /b 1 + +docker run %CUDA_RUNTIME_ARGS% --entrypoint bash --ipc=host --shm-size=4gb -w /work -v "%WORKSPACE%:/work" %DISPLAY_ARGS% -it openmvs-ubuntu%CUDA_CONTAINER_SUFFIX% + +exit /b 0 diff --git a/docker/buildFromScratch.sh b/docker/buildFromScratch.sh index 2f3e1efa4..3d3e6dd73 100755 --- a/docker/buildFromScratch.sh +++ b/docker/buildFromScratch.sh @@ -1,2 +1,47 @@ -docker build -t="openmvs-ubuntu" .; -docker run -w /working -v $1:/working -it openmvs-ubuntu; \ No newline at end of file +#!/bin/bash + +# Example use: +# ./buildFromScratch.sh --cuda --master --workspace /home/username/datapath/ + +WORKSPACE=$(pwd) + + +while [[ $# -gt 0 ]]; do + key="$1" + case $key in + --cuda) + CUDA_BUILD_ARGS="--build-arg CUDA=1 --build-arg BASE_IMAGE=nvidia/cuda:11.8.0-devel-ubuntu22.04" + + CUDA_RUNTIME_ARGS="--gpus all -e NVIDIA_DRIVER_CAPABILITIES=compute,utility,graphics" + + CUDA_CONTAINER_SUFFIX="-cuda" + shift + ;; + --master) + MASTER_ARGS="--build-arg MASTER=1" + shift + ;; + --workspace) + WORKSPACE=$2 + shift + shift + ;; + *) + echo "Unknown argument: $key" + exit 1 + ;; + esac +done + +# no need to do `xhost +` anymore +XSOCK=/tmp/.X11-unix +XAUTH=/tmp/.docker.xauth +touch $XAUTH +xauth nlist "$DISPLAY" | sed -e 's/^..../ffff/' | xauth -f $XAUTH nmerge - +DISPLAY_ARGS="--volume=$XSOCK:$XSOCK:rw --volume=$XAUTH:$XAUTH:rw --env=XAUTHORITY=$XAUTH --env=DISPLAY=unix$DISPLAY" + +echo Running with workspace: "$WORKSPACE" + +docker build -t="openmvs-ubuntu$CUDA_CONTAINER_SUFFIX" --build-arg "USER_ID=$(id -u)" --build-arg "GROUP_ID=$(id -g)" $CUDA_BUILD_ARGS $MASTER_ARGS . +docker run $CUDA_RUNTIME_ARGS --entrypoint bash --ipc=host --shm-size=4gb -w /work -v "$WORKSPACE:/work" $DISPLAY_ARGS -it openmvs-ubuntu$CUDA_CONTAINER_SUFFIX + diff --git a/docker/buildInDocker.sh b/docker/buildInDocker.sh new file mode 100755 index 000000000..c33917a88 --- /dev/null +++ b/docker/buildInDocker.sh @@ -0,0 +1,95 @@ +#!/bin/bash + +set -e + +# Parse arguments to see if --cuda was passed and equals 1 or --master was passed and equals 1 +while [[ $# -gt 0 ]]; do + key="$1" + case $key in + --cuda) + CUDA="$2" + shift + shift + ;; + --master) + MASTER="$2" + shift + shift + ;; + --user_id) + USER_ID="$2" + shift + shift + ;; + --group_id) + GROUP_ID="$2" + shift + shift + ;; + *) + echo "Unknown argument: $key" + exit 1 + ;; + esac +done + +if [[ "$CUDA" == "1" ]]; then + echo "Building with CUDA support" + EIGEN_BUILD_ARG="-DCUDA_TOOLKIT_ROOT_DIR=/usr/local/cuda/" + OPENMVS_BUILD_ARG="-DOpenMVS_USE_CUDA=ON -DCMAKE_LIBRARY_PATH=/usr/local/cuda/lib64/stubs/ -DCUDA_TOOLKIT_ROOT_DIR=/usr/local/cuda/ -DCUDA_INCLUDE_DIRS=/usr/local/cuda/include/ -DCUDA_CUDART_LIBRARY=/usr/local/cuda/lib64 -DCUDA_NVCC_EXECUTABLE=/usr/local/cuda/bin/" +else + echo "Building without CUDA support" + EIGEN_BUILD_ARG="" + OPENMVS_BUILD_ARG="-DOpenMVS_USE_CUDA=OFF" +fi + +if [[ "$MASTER" == "1" ]]; then + echo "Pulling from master branch" +else + echo "Pulling from develop branch" +fi + +apt-get update -yq + +apt-get -yq install build-essential git cmake libpng-dev libjpeg-dev libtiff-dev libglu1-mesa-dev libglew-dev libglfw3-dev + +# Eigen +git clone https://gitlab.com/libeigen/eigen --branch 3.4 +mkdir eigen_build +cd eigen_build &&\ + cmake . ../eigen $EIGEN_BUILD_ARG &&\ + make && make install &&\ + cd .. && rm -rf eigen_build eigen + +# Boost +apt-get -y install libboost-iostreams-dev libboost-program-options-dev libboost-system-dev libboost-serialization-dev + +# OpenCV +DEBIAN_FRONTEND=noninteractive apt-get install -yq libopencv-dev + +# CGAL +apt-get -yq install libcgal-dev libcgal-qt5-dev + +# VCGLib +git clone https://github.com/cdcseacave/VCG.git vcglib + +# Build from stable openMVS release or the latest commit from the develop branch +if [[ "$MASTER" == "1" ]]; then + git clone https://github.com/cdcseacave/openMVS.git --branch master +else + git clone https://github.com/cdcseacave/openMVS.git --branch develop +fi + +mkdir openMVS_build +cd openMVS_build &&\ + cmake . ../openMVS -DCMAKE_BUILD_TYPE=Release -DVCG_ROOT=/vcglib $OPENMVS_BUILD_ARG + +# Install OpenMVS library +make -j4 &&\ + make install &&\ + cd .. && rm -rf openMVS_build vcglib + +# Set permissions such that the output files can be accessed by the current user (optional) +echo "Setting permissions for user $USER_ID:$GROUP_ID" +addgroup --gid $GROUP_ID user &&\ + adduser --disabled-password --gecos '' --uid $USER_ID --gid $GROUP_ID user diff --git a/libs/Common/AABB.h b/libs/Common/AABB.h index 63c707181..84099691e 100644 --- a/libs/Common/AABB.h +++ b/libs/Common/AABB.h @@ -29,6 +29,7 @@ class TAABB typedef TYPE Type; typedef Eigen::Matrix POINT; typedef Eigen::Matrix MATRIX; + typedef Eigen::AlignedBox ALIGNEDBOX; enum { numChildren = (2<<(DIMS-1)) }; enum { numCorners = (DIMS==1 ? 2 : (DIMS==2 ? 4 : 8)) }; // 2^DIMS enum { numScalar = (2*DIMS) }; @@ -39,33 +40,38 @@ class TAABB inline TAABB() {} inline TAABB(bool); - inline TAABB(const POINT& _pt); + inline TAABB(const POINT& pt); inline TAABB(const POINT& _ptMin, const POINT& _ptMax); inline TAABB(const POINT& center, const TYPE& radius); + inline TAABB(const ALIGNEDBOX& box); template inline TAABB(const TPoint* pts, size_t n); template inline TAABB(const TAABB&); inline void Reset(); - inline void Set(const POINT& _pt); + inline void Set(const POINT& pt); inline void Set(const POINT& _ptMin, const POINT& _ptMax); inline void Set(const POINT& center, const TYPE& radius); + inline void Set(const ALIGNEDBOX& box); template inline void Set(const TPoint* pts, size_t n); inline bool IsEmpty() const; - inline void Enlarge(TYPE); - inline void EnlargePercent(TYPE); + inline TAABB& Enlarge(TYPE); + inline TAABB& EnlargePercent(TYPE); void InsertFull(const POINT&); void Insert(const POINT&); void Insert(const TAABB&); + void BoundBy(const TAABB&); inline void Translate(const POINT&); inline void Transform(const MATRIX&); + inline ALIGNEDBOX GetAlignedBox() const; + inline POINT GetCenter() const; inline void GetCenter(POINT&) const; @@ -88,6 +94,26 @@ class TAABB inline TYPE& operator [] (BYTE i) { ASSERT(i> (std::istream& st, TAABB& obb) { + st >> obb.ptMin; + st >> obb.ptMax; + return st; + } + + #ifdef _USE_BOOST + // implement BOOST serialization + template + void serialize(Archive& ar, const unsigned int /*version*/) { + ar & ptMin; + ar & ptMax; + } + #endif }; // class TAABB /*----------------------------------------------------------------*/ diff --git a/libs/Common/AABB.inl b/libs/Common/AABB.inl index b9040cfea..3ebcef5dd 100644 --- a/libs/Common/AABB.inl +++ b/libs/Common/AABB.inl @@ -19,9 +19,9 @@ inline TAABB::TAABB(bool) { } template -inline TAABB::TAABB(const POINT& _pt) +inline TAABB::TAABB(const POINT& pt) : - ptMin(_pt), ptMax(_pt) + ptMin(pt), ptMax(pt) { } template @@ -37,6 +37,12 @@ inline TAABB::TAABB(const POINT& center, const TYPE& radius) { } template +inline TAABB::TAABB(const ALIGNEDBOX& box) + : + ptMin(box.min()), ptMax(box.max()) +{ +} +template template inline TAABB::TAABB(const TPoint* pts, size_t n) { @@ -59,9 +65,9 @@ inline void TAABB::Reset() ptMax = POINT::Constant(std::numeric_limits::lowest()); } template -inline void TAABB::Set(const POINT& _pt) +inline void TAABB::Set(const POINT& pt) { - ptMin = ptMax = _pt; + ptMin = ptMax = pt; } template inline void TAABB::Set(const POINT& _ptMin, const POINT& _ptMax) @@ -76,6 +82,12 @@ inline void TAABB::Set(const POINT& center, const TYPE& radius) ptMax = center+POINT::Constant(radius); } template +inline void TAABB::Set(const ALIGNEDBOX& box) +{ + ptMin = box.min(); + ptMax = box.max(); +} +template template inline void TAABB::Set(const TPoint* pts, size_t n) { @@ -91,7 +103,7 @@ template inline bool TAABB::IsEmpty() const { for (int i=0; i ptMax[i]) + if (ptMin[i] >= ptMax[i]) return true; return false; } // IsEmpty @@ -99,20 +111,31 @@ inline bool TAABB::IsEmpty() const template -inline void TAABB::Enlarge(TYPE x) +inline TAABB& TAABB::Enlarge(TYPE x) { ptMin.array() -= x; ptMax.array() += x; + return *this; } template -inline void TAABB::EnlargePercent(TYPE x) +inline TAABB& TAABB::EnlargePercent(TYPE x) { - ptMin *= x; - ptMax *= x; + const POINT ptSizeDelta(GetSize() * (x - TYPE(1)) / TYPE(2)); + ptMin -= ptSizeDelta; + ptMax += ptSizeDelta; + return *this; } // Enlarge /*----------------------------------------------------------------*/ +template +inline typename TAABB::ALIGNEDBOX TAABB::GetAlignedBox() const +{ + return ALIGNEDBOX(ptMin, ptMax); +} // GetAlignedBox +/*----------------------------------------------------------------*/ + + template inline typename TAABB::POINT TAABB::GetCenter() const { @@ -240,7 +263,7 @@ void TAABB::Insert(const POINT& pt) } /*----------------------------------------------------------------*/ -// Update the box such that it contains the given point. +// Update the box such that it contains the given bounding box. template void TAABB::Insert(const TAABB& aabb) { @@ -265,6 +288,31 @@ void TAABB::Insert(const TAABB& aabb) } /*----------------------------------------------------------------*/ +// Update the box such that it does not exceed the given bounding box. +template +void TAABB::BoundBy(const TAABB& aabb) +{ + if (ptMin[0] < aabb.ptMin[0]) + ptMin[0] = aabb.ptMin[0]; + if (ptMax[0] > aabb.ptMax[0]) + ptMax[0] = aabb.ptMax[0]; + + if (DIMS > 1) { + if (ptMin[1] < aabb.ptMin[1]) + ptMin[1] = aabb.ptMin[1]; + if (ptMax[1] > aabb.ptMax[1]) + ptMax[1] = aabb.ptMax[1]; + } + + if (DIMS > 2) { + if (ptMin[2] < aabb.ptMin[2]) + ptMin[2] = aabb.ptMin[2]; + if (ptMax[2] > aabb.ptMax[2]) + ptMax[2] = aabb.ptMax[2]; + } +} +/*----------------------------------------------------------------*/ + // intersection between two AABBs template diff --git a/libs/Common/AutoEstimator.h b/libs/Common/AutoEstimator.h index 1cdb6bb1c..25d0cd487 100644 --- a/libs/Common/AutoEstimator.h +++ b/libs/Common/AutoEstimator.h @@ -88,7 +88,7 @@ struct ErrorIndex { #ifdef ACRANSAC_STD_VECTOR typedef std::vector ErrorIndexArr; #else -typedef SEACAVE::cList ErrorIndexArr; +typedef CLISTDEF0(ErrorIndex) ErrorIndexArr; #endif /// Find best NFA and its index wrt square error threshold in e. diff --git a/libs/Common/CMakeLists.txt b/libs/Common/CMakeLists.txt index 2e6c1a408..18899afde 100644 --- a/libs/Common/CMakeLists.txt +++ b/libs/Common/CMakeLists.txt @@ -1,20 +1,15 @@ # List sources files -FILE(GLOB PCH_C "Common.cpp") - FILE(GLOB LIBRARY_FILES_C "*.cpp") FILE(GLOB LIBRARY_FILES_H "*.h" "*.inl") -# Place Common.cpp as the first file in the list -# needed by cotire when setting PCH manually -LIST(REMOVE_ITEM LIBRARY_FILES_C ${PCH_C}) -SET(LIBRARY_FILES_C "${PCH_C};${LIBRARY_FILES_C}") - -cxx_library_with_type_no_pch(Common "Libs" "STATIC" "${cxx_default}" +cxx_library_with_type(Common "Libs" "" "${cxx_default}" ${LIBRARY_FILES_C} ${LIBRARY_FILES_H} ) # Manually set Common.h as the precompiled header -set_target_pch(Common Common.h) +IF(CMAKE_VERSION VERSION_GREATER_EQUAL 3.16.0) + TARGET_PRECOMPILE_HEADERS(Common PRIVATE "Common.h") +endif() # Link its dependencies TARGET_LINK_LIBRARIES(Common ${Boost_LIBRARIES} ${OpenCV_LIBS}) @@ -24,7 +19,7 @@ SET_TARGET_PROPERTIES(Common PROPERTIES PUBLIC_HEADER "${LIBRARY_FILES_H}") INSTALL(TARGETS Common EXPORT OpenMVSTargets - RUNTIME DESTINATION "${INSTALL_BIN_DIR}" COMPONENT bin - LIBRARY DESTINATION "${INSTALL_LIB_DIR}" COMPONENT shlib - ARCHIVE DESTINATION "${INSTALL_LIB_DIR}" COMPONENT lib - PUBLIC_HEADER DESTINATION "${INSTALL_INCLUDE_DIR}/Common" COMPONENT dev) + LIBRARY DESTINATION "${INSTALL_LIB_DIR}" + ARCHIVE DESTINATION "${INSTALL_LIB_DIR}" + RUNTIME DESTINATION "${INSTALL_BIN_DIR}" + PUBLIC_HEADER DESTINATION "${INSTALL_INCLUDE_DIR}/Common") diff --git a/libs/Common/Common.cpp b/libs/Common/Common.cpp index 052951d47..54ccc8aed 100644 --- a/libs/Common/Common.cpp +++ b/libs/Common/Common.cpp @@ -25,12 +25,23 @@ String g_strWorkingFolderFull; #ifdef _USE_BOOST #ifdef BOOST_NO_EXCEPTIONS +#if (BOOST_VERSION / 100000) > 1 || (BOOST_VERSION / 100 % 1000) > 72 +#include +#endif namespace boost { void throw_exception(std::exception const & e) { VERBOSE("exception thrown: %s", e.what()); ASSERT("boost exception thrown" == NULL); exit(EXIT_FAILURE); } + #if (BOOST_VERSION / 100000) > 1 || (BOOST_VERSION / 100 % 1000) > 72 + void throw_exception(std::exception const & e, boost::source_location const & loc) { + std::ostringstream ostr; ostr << loc; + VERBOSE("exception thrown at %s: %s", ostr.str().c_str(), e.what()); + ASSERT("boost exception thrown" == NULL); + exit(EXIT_FAILURE); + } + #endif } // namespace boost #endif #endif diff --git a/libs/Common/Common.h b/libs/Common/Common.h index 8942f1e14..0decd1ace 100644 --- a/libs/Common/Common.h +++ b/libs/Common/Common.h @@ -13,7 +13,7 @@ #include "Config.h" -// macors controling the verbosity +// macros controlling the verbosity #define TD_VERBOSE_OFF 0 #define TD_VERBOSE_ON 1 #define TD_VERBOSE_DEBUG 2 @@ -78,6 +78,12 @@ namespace SEACAVE { extern int g_nVerbosityLevel; } #define LOG_ERR() GET_LOG() //or std::cerr +#ifdef PRINT_ASSERT_MSG +#undef PRINT_ASSERT_MSG +#define PRINT_ASSERT_MSG(exp, ...) {std::cout << SEACAVE::PrintMessageToString("ASSERTION FAILED: (" #exp ") ", __VA_ARGS__) << std::endl;} +#endif + + // macros simplifying the task of composing file paths; // WORKING_FOLDER and WORKING_FOLDER_FULL must be defined as strings // containing the relative/full path to the working folder @@ -86,15 +92,15 @@ namespace SEACAVE { class String; extern String g_strWorkingFolder; // empty by default (current folder) extern String g_strWorkingFolderFull; // full path to current folder -} +} // namespace SEACAVE #define WORKING_FOLDER g_strWorkingFolder // empty by default (current folder) #define WORKING_FOLDER_FULL g_strWorkingFolderFull // full path to current folder #endif #define INIT_WORKING_FOLDER {SEACAVE::Util::ensureValidFolderPath(WORKING_FOLDER); WORKING_FOLDER_FULL = SEACAVE::Util::getFullPath(WORKING_FOLDER);} // initialize working folders -#define MAKE_PATH(str) SEACAVE::Util::getSimplifiedPath(WORKING_FOLDER+(str)) // add working directory to the given file name -#define MAKE_PATH_SAFE(str) (SEACAVE::Util::isFullPath(str) ? SEACAVE::String(str) : MAKE_PATH(str)) // add working directory to the given file name only if not full path already +#define MAKE_PATH(str) SEACAVE::Util::getSimplifiedPath(WORKING_FOLDER+SEACAVE::String(str)) // add working directory to the given file name +#define MAKE_PATH_SAFE(str) (SEACAVE::Util::isFullPath((str).c_str()) ? SEACAVE::String(str) : MAKE_PATH(str)) // add working directory to the given file name only if not full path already #define MAKE_PATH_FULL(p,s) (SEACAVE::Util::isFullPath((s).c_str()) ? SEACAVE::String(s) : SEACAVE::Util::getSimplifiedPath((p)+(s))) // add the given path to the given file name -#define MAKE_PATH_REL(p,s) ((s).compare(0,(p).size(),p) ? SEACAVE::String(s) : SEACAVE::String(SEACAVE::String((s).substr((p).size())))) // remove the given path from the given file name +#define MAKE_PATH_REL(p,s) SEACAVE::Util::getRelativePath(p,s) // remove the given path from the given file name #define GET_PATH_FULL(str) (SEACAVE::Util::isFullPath((str).c_str()) ? SEACAVE::Util::getFilePath(str) : SEACAVE::Util::getSimplifiedPath(WORKING_FOLDER_FULL+SEACAVE::Util::getFilePath(str))) // retrieve the full path to the given file @@ -188,6 +194,8 @@ typedef TOBB OBB2f; typedef TOBB OBB3f; typedef TRay Ray2f; typedef TRay Ray3f; +typedef TLine Line2f; +typedef TLine Line3f; typedef TTriangle Triangle2f; typedef TTriangle Triangle3f; typedef TPlane Planef; @@ -209,6 +217,8 @@ typedef TOBB OBB2d; typedef TOBB OBB3d; typedef TRay Ray2d; typedef TRay Ray3d; +typedef TLine Line2d; +typedef TLine Line3d; typedef TTriangle Triangle2d; typedef TTriangle Triangle3d; typedef TPlane Planed; @@ -230,6 +240,8 @@ typedef TOBB OBB2; typedef TOBB OBB3; typedef TRay Ray2; typedef TRay Ray3; +typedef TLine Line2; +typedef TLine Line3; typedef TTriangle Triangle2; typedef TTriangle Triangle3; typedef TPlane Plane; diff --git a/libs/Common/Config.h b/libs/Common/Config.h index 521355e27..69363eae9 100644 --- a/libs/Common/Config.h +++ b/libs/Common/Config.h @@ -10,6 +10,8 @@ // Configure everything that needs to be globally known +#include "ConfigLocal.h" + // D E F I N E S /////////////////////////////////////////////////// @@ -50,6 +52,27 @@ #define _USE_MATH_DEFINES +#if _MSC_VER >= 1400 +#ifndef _CRT_SECURE_NO_WARNINGS +#define _CRT_SECURE_NO_WARNINGS 1 +#endif +#ifndef _CRT_SECURE_NO_DEPRECATE +#define _CRT_SECURE_NO_DEPRECATE 1 +#endif +#ifndef _ATL_SECURE_NO_DEPRECATE +#define _ATL_SECURE_NO_DEPRECATE 1 +#endif +#ifndef _CRT_NON_CONFORMING_SWPRINTFS +#define _CRT_NON_CONFORMING_SWPRINTFS 1 +#endif +#ifndef _CRT_SECURE_CPP_OVERLOAD_SECURE_NAMES +#define _CRT_SECURE_CPP_OVERLOAD_SECURE_NAMES 0 // disable automatically overloading CPP names to secure versions +#endif +#if 0 && defined(_DEBUG) && !defined(_ITERATOR_DEBUG_LEVEL) // might not build if linking statically to 3rd party libraries +#define _ITERATOR_DEBUG_LEVEL 1 // disable std iterator debugging even in Debug, as it is very slow +#endif +#endif + //---------------------------------------------------------------------- // For Microsoft Visual C++, externally accessible symbols must be @@ -69,11 +92,11 @@ /*----------------------------------------------------------------*/ #ifdef _USRDLL #ifdef Common_EXPORTS - #define GENERAL_API EXPORT_API - #define GENERAL_TPL + #define GENERAL_API EXPORT_API + #define GENERAL_TPL #else - #define GENERAL_API IMPORT_API - #define GENERAL_TPL extern + #define GENERAL_API IMPORT_API + #define GENERAL_TPL extern #endif #else #define GENERAL_API @@ -81,10 +104,6 @@ #endif /*----------------------------------------------------------------*/ -#if _MSC_VER >= 1800 -#define _SUPPORT_CPP11 -#endif - // Define platform type #if _WIN64 #define _ENVIRONMENT64 @@ -106,12 +125,8 @@ #define GENERAL_API #define GENERAL_TPL -#if __cplusplus >= 201103L || (defined(__APPLE__) && __clang_major__ >= 4) -#define _SUPPORT_CPP11 -#endif - // Define platform type -#if __x86_64__ || __ppc64__ +#if defined(__x86_64__) || defined(__ppc64__) || defined(__aarch64__) || defined(__arm64__) || defined(__mips64) #define _ENVIRONMENT64 #else #define _ENVIRONMENT32 @@ -120,34 +135,35 @@ #endif // _MSC_VER -#if !defined(_DEBUG) && !defined(_PROFILE) -#define _RELEASE // exclude code useful only for debug -#endif - - -#if defined(_MSC_VER) && _MSC_VER >= 1400 -#ifndef _CRT_SECURE_NO_WARNINGS -#define _CRT_SECURE_NO_WARNINGS 1 +#if __cplusplus >= 201103L || (__clang_major__ >= 4 || (__clang_major__ >= 3 && __clang_minor__ >= 3)) +#define _SUPPORT_CPP11 #endif -#ifndef _CRT_SECURE_NO_DEPRECATE -#define _CRT_SECURE_NO_DEPRECATE 1 +#if __cplusplus >= 201402L || (__clang_major__ >= 4 || (__clang_major__ >= 3 && __clang_minor__ >= 4)) +#define _SUPPORT_CPP14 #endif -#ifndef _ATL_SECURE_NO_DEPRECATE -#define _ATL_SECURE_NO_DEPRECATE 1 +#if __cplusplus >= 201703L || __clang_major__ >= 5 +#define _SUPPORT_CPP17 #endif -#ifndef _CRT_NON_CONFORMING_SWPRINTFS -#define _CRT_NON_CONFORMING_SWPRINTFS 1 +#if __cplusplus >= 202002L || __clang_major__ >= 10 +#define _SUPPORT_CPP20 #endif -#ifndef _CRT_SECURE_CPP_OVERLOAD_SECURE_NAMES -#define _CRT_SECURE_CPP_OVERLOAD_SECURE_NAMES 0 // disable automatically overloading CPP names to secure versions -#endif -#if 0 && defined(_DEBUG) && !defined(_ITERATOR_DEBUG_LEVEL) // might not build if linking statically to 3rd party libraries -#define _ITERATOR_DEBUG_LEVEL 1 // disable std iterator debugging even in Debug, as it is very slow + + +#if defined(__powerpc__) +#define _PLATFORM_PPC 1 +#elif defined(__arm__) || defined (__arm64__) || defined(__aarch64__) || defined(_M_ARM) || defined(_M_ARMT) +#define _PLATFORM_ARM 1 +#else +#define _PLATFORM_X86 1 #endif + + +#if !defined(_DEBUG) && !defined(_PROFILE) +#define _RELEASE // exclude code useful only for debug #endif -//optimization flags +// optimization flags #if defined(_MSC_VER) # define ALIGN(n) __declspec(align(n)) # define NOINITVTABLE __declspec(novtable) //disable generating code to initialize the vfptr in the constructor(s) and destructor of the class @@ -156,7 +172,7 @@ # define RESTRICT __restrict //applied to a function parameter # define MEMALLOC __declspec(noalias) __declspec(restrict) # define DEPRECATED __declspec(deprecated) -# define NOWARNUNUSED +# define MAYBEUNUSED # define HOT # define COLD # define THREADLOCAL __declspec(thread) @@ -169,7 +185,7 @@ # define RESTRICT __restrict__ # define MEMALLOC __attribute__ ((__malloc__)) # define DEPRECATED __attribute__ ((__deprecated__)) -# define NOWARNUNUSED __attribute__ ((unused)) +# define MAYBEUNUSED __attribute__ ((unused)) # define HOT __attribute__((hot)) __attribute__((optimize("-O3"))) __attribute__((optimize("-ffast-math"))) //optimize for speed, even in debug # define COLD __attribute__((cold)) //optimize for size # define THREADLOCAL __thread @@ -182,15 +198,16 @@ # define RESTRICT # define MEMALLOC # define DEPRECATED -# define NOWARNUNUSED +# define MAYBEUNUSED # define HOT # define COLD # define THREADLOCAL __thread # define FORCEINLINE inline #endif -#ifndef _SUPPORT_CPP11 -# define constexpr inline +#ifdef _SUPPORT_CPP17 +# undef MAYBEUNUSED +# define MAYBEUNUSED [[maybe_unused]] #endif #define SAFE_DELETE(p) { if (p!=NULL) { delete (p); (p)=NULL; } } @@ -199,41 +216,48 @@ #define SAFE_RELEASE(p) { if (p!=NULL) { (p)->Release(); (p)=NULL; } } +#define PRINT_ASSERT_MSG(exp, ...) + #ifdef _DEBUG #ifdef _MSC_VER #define _DEBUGINFO -#define _CRTDBG_MAP_ALLOC //enable this to show also the filename (DEBUG_NEW should also be defined in each file) -#include +#define _CRTDBG_MAP_ALLOC //enable this to show also the filename (DEBUG_NEW should also be defined in each file) +#include #include #ifdef _INC_CRTDBG -#define ASSERT(exp) {if (!(exp) && 1 == _CrtDbgReport(_CRT_ASSERT, __FILE__, __LINE__, NULL, #exp)) _CrtDbgBreak();} +#define SIMPLE_ASSERT(exp) {if (!(exp) && 1 == _CrtDbgReport(_CRT_ASSERT, __FILE__, __LINE__, NULL, #exp)) _CrtDbgBreak();} +#define ASSERT(exp, ...) {static bool bIgnore(false); if (!bIgnore && !(exp)) {PRINT_ASSERT_MSG(exp, __VA_ARGS__); if (!(bIgnore = !(1 == _CrtDbgReport(_CRT_ASSERT, __FILE__, __LINE__, NULL, #exp)))) _CrtDbgBreak();}} #else -#define ASSERT(exp) {if (!(exp)) __debugbreak();} +#define SIMPLE_ASSERT(exp) {if (!(exp)) __debugbreak();} +#define ASSERT(exp, ...) {if (!(exp)) {PRINT_ASSERT_MSG(exp, __VA_ARGS__); __debugbreak();}} #endif // _INC_CRTDBG -#define TRACE(...) {TCHAR buffer[2048]; _sntprintf(buffer, 2048, __VA_ARGS__); OutputDebugString(buffer);} +#define TRACE(...) {TCHAR buffer[2048]; _sntprintf(buffer, 2048, __VA_ARGS__); OutputDebugString(buffer);} #else // _MSC_VER #include -#define ASSERT(exp) assert(exp) +#define SIMPLE_ASSERT(exp) {if (!(exp)) assert(exp);} +#define ASSERT(exp, ...) {if (!(exp)) {PRINT_ASSERT_MSG(exp, __VA_ARGS__); assert(exp);}} #define TRACE(...) #endif // _MSC_VER #else #ifdef _RELEASE -#define ASSERT(exp) +#define SIMPLE_ASSERT(exp) +#define ASSERT(exp, ...) #else #ifdef _MSC_VER -#define ASSERT(exp) {if (!(exp)) __debugbreak();} +#define SIMPLE_ASSERT(exp) {if (!(exp)) __debugbreak();} +#define ASSERT(exp, ...) {if (!(exp)) {PRINT_ASSERT_MSG(exp, __VA_ARGS__); __debugbreak();}} #else // _MSC_VER -#define ASSERT(exp) {if (!(exp)) __builtin_trap();} +#define SIMPLE_ASSERT(exp) {if (!(exp)) __builtin_trap();} +#define ASSERT(exp, ...) {if (!(exp)) {PRINT_ASSERT_MSG(exp, __VA_ARGS__); __builtin_trap();}} #endif // _MSC_VER #endif #define TRACE(...) #endif // _DEBUG -#define ASSERTM(exp, msg) ASSERT(exp) namespace SEACAVE_ASSERT { @@ -248,13 +272,13 @@ namespace SEACAVE_ASSERT } #define STATIC_ASSERT(expression) \ - NOWARNUNUSED typedef char CTA##__LINE__[::SEACAVE_ASSERT::compile_time_assert<(bool)(expression)>::value] + MAYBEUNUSED typedef char CTA##__LINE__[::SEACAVE_ASSERT::compile_time_assert<(bool)(expression)>::value] #define ASSERT_ARE_SAME_TYPE(type1, type2) \ - NOWARNUNUSED typedef char AAST##__LINE__[::SEACAVE_ASSERT::assert_are_same_type::value] + MAYBEUNUSED typedef char AAST##__LINE__[::SEACAVE_ASSERT::assert_are_same_type::value] #define ASSERT_ARE_NOT_SAME_TYPE(type1, type2) \ - NOWARNUNUSED typedef char AANST##__LINE__[::SEACAVE_ASSERT::assert_are_not_same_type::value] + MAYBEUNUSED typedef char AANST##__LINE__[::SEACAVE_ASSERT::assert_are_not_same_type::value] /*----------------------------------------------------------------*/ #endif // __SEACAVE_CONFIG_H__ diff --git a/libs/Common/ConfigTable.h b/libs/Common/ConfigTable.h index d112a5ea1..337512108 100644 --- a/libs/Common/ConfigTable.h +++ b/libs/Common/ConfigTable.h @@ -56,7 +56,7 @@ class GENERAL_API CConfigTable const SML& GetConfig() const { return m_oSML; } SML& GetConfig() { return m_oSML; } inline SMLVALUE& operator[] (const String& name) { return m_oSML[name]; } - inline IDX InsertChild(CConfigTable& oCfg) { oCfg.SetParent(this); return m_oSML.InsertChild((const LPSML)&oCfg.m_oSML); } + inline IDX InsertChild(CConfigTable& oCfg) { oCfg.SetParent(this); return m_oSML.InsertChild(&oCfg.m_oSML); } inline void RemoveChild(CConfigTable& oCfg) { oCfg.SetParent(NULL); m_oSML.RemoveChild(oCfg.m_oSML.GetName()); } // misc methods diff --git a/libs/Common/FastDelegate.h b/libs/Common/FastDelegate.h index 51400ae99..72206cb35 100644 --- a/libs/Common/FastDelegate.h +++ b/libs/Common/FastDelegate.h @@ -1,2105 +1,373 @@ -// FastDelegate.h -// Efficient delegates in C++ that generate only two lines of asm code! -// Documentation is found at http://www.codeproject.com/cpp/FastDelegate.asp -// -// - Don Clugston, Mar 2004. -// Major contributions were made by Jody Hagins. -// History: -// 24-Apr-04 1.0 * Submitted to CodeProject. -// 28-Apr-04 1.1 * Prevent most unsafe uses of evil static function hack. -// * Improved syntax for horrible_cast (thanks Paul Bludov). -// * Tested on Metrowerks MWCC and Intel ICL (IA32) -// * Compiled, but not run, on Comeau C++ and Intel Itanium ICL. -// 27-Jun-04 1.2 * Now works on Borland C++ Builder 5.5 -// * Now works on /clr "managed C++" code on VC7, VC7.1 -// * Comeau C++ now compiles without warnings. -// * Prevent the virtual inheritance case from being used on -// VC6 and earlier, which generate incorrect code. -// * Improved warning and error messages. Non-standard hacks -// now have compile-time checks to make them safer. -// * implicit_cast used instead of static_cast in many cases. -// * If calling a const member function, a const class pointer can be used. -// * MakeDelegate() global helper function added to simplify pass-by-value. -// * Added fastdelegate.clear() -// 16-Jul-04 1.2.1* Workaround for gcc bug (const member function pointers in templates) -// 30-Oct-04 1.3 * Support for (non-void) return values. -// * No more workarounds in client code! -// MSVC and Intel now use a clever hack invented by John Dlugosz: -// - The FASTDELEGATEDECLARE workaround is no longer necessary. -// - No more warning messages for VC6 -// * Less use of macros. Error messages should be more comprehensible. -// * Added include guards -// * Added FastDelegate::empty() to test if invocation is safe (Thanks Neville Franks). -// * Now tested on VS 2005 Express Beta, PGI C++ -// 24-Dec-04 1.4 * Added DelegateMemento, to allow collections of disparate delegates. -// * <,>,<=,>= comparison operators to allow storage in ordered containers. -// * Substantial reduction of code size, especially the 'Closure' class. -// * Standardised all the compiler-specific workarounds. -// * MFP conversion now works for CodePlay (but not yet supported in the full code). -// * Now compiles without warnings on _any_ supported compiler, including BCC 5.5.1 -// * New syntax: FastDelegate< int (char *, double) >. -// 14-Feb-05 1.4.1* Now treats =0 as equivalent to .clear(), ==0 as equivalent to .empty(). (Thanks elfric). -// * Now tested on Intel ICL for AMD64, VS2005 Beta for AMD64 and Itanium. -// 30-Mar-05 1.5 * Safebool idiom: "if (dg)" is now equivalent to "if (!dg.empty())" -// * Fully supported by CodePlay VectorC -// * Bugfix for Metrowerks: empty() was buggy because a valid MFP can be 0 on MWCC! -// * More optimal assignment,== and != operators for static function pointers. - -#ifndef FASTDELEGATE_H -#define FASTDELEGATE_H - -#include // to allow <,> comparisons - -//////////////////////////////////////////////////////////////////////////////// -// Configuration options -// -//////////////////////////////////////////////////////////////////////////////// - -// Uncomment the following #define for optimally-sized delegates. -// In this case, the generated asm code is almost identical to the code you'd get -// if the compiler had native support for delegates. -// It will not work on systems where sizeof(dataptr) < sizeof(codeptr). -// Thus, it will not work for DOS compilers using the medium model. -// It will also probably fail on some DSP systems. -#define FASTDELEGATE_USESTATICFUNCTIONHACK - -// Uncomment the next line to allow function declarator syntax. -// It is automatically enabled for those compilers where it is known to work. -//#define FASTDELEGATE_ALLOW_FUNCTION_TYPE_SYNTAX - -//////////////////////////////////////////////////////////////////////////////// -// Compiler identification for workarounds -// -//////////////////////////////////////////////////////////////////////////////// - -// Compiler identification. It's not easy to identify Visual C++ because -// many vendors fraudulently define Microsoft's identifiers. -#if defined(_MSC_VER) && !defined(__MWERKS__) && !defined(__VECTOR_C) && !defined(__ICL) && !defined(__BORLANDC__) -#define FASTDLGT_ISMSVC - -#if (_MSC_VER <1300) // Many workarounds are required for VC6. -#define FASTDLGT_VC6 -#pragma warning(disable:4786) // disable this ridiculous warning -#endif - -#endif - -// Does the compiler uses Microsoft's member function pointer structure? -// If so, it needs special treatment. -// Metrowerks CodeWarrior, Intel, and CodePlay fraudulently define Microsoft's -// identifier, _MSC_VER. We need to filter Metrowerks out. -#if defined(_MSC_VER) && !defined(__MWERKS__) -#define FASTDLGT_MICROSOFT_MFP - -#if !defined(__VECTOR_C) -// CodePlay doesn't have the __single/multi/virtual_inheritance keywords -#define FASTDLGT_HASINHERITANCE_KEYWORDS -#endif -#endif - -// Does it allow function declarator syntax? The following compilers are known to work: -#if defined(FASTDLGT_ISMSVC) && (_MSC_VER >=1310) // VC 7.1 -#define FASTDELEGATE_ALLOW_FUNCTION_TYPE_SYNTAX -#endif - -// Gcc(2.95+), and versions of Digital Mars, Intel and Comeau in common use. -#if defined (__DMC__) || defined(__GNUC__) || defined(__ICL) || defined(__COMO__) -#define FASTDELEGATE_ALLOW_FUNCTION_TYPE_SYNTAX -#endif - -// It works on Metrowerks MWCC 3.2.2. From boost.Config it should work on earlier ones too. -#if defined (__MWERKS__) -#define FASTDELEGATE_ALLOW_FUNCTION_TYPE_SYNTAX -#endif - -#ifdef __GNUC__ // Workaround GCC bug #8271 - // At present, GCC doesn't recognize constness of MFPs in templates -#define FASTDELEGATE_GCC_BUG_8271 -#endif - - - -//////////////////////////////////////////////////////////////////////////////// -// General tricks used in this code -// -// (a) Error messages are generated by typdefing an array of negative size to -// generate compile-time errors. -// (b) Warning messages on MSVC are generated by declaring unused variables, and -// enabling the "variable XXX is never used" warning. -// (c) Unions are used in a few compiler-specific cases to perform illegal casts. -// (d) For Microsoft and Intel, when adjusting the 'this' pointer, it's cast to -// (char *) first to ensure that the correct number of *bytes* are added. -// -//////////////////////////////////////////////////////////////////////////////// -// Helper templates -// -//////////////////////////////////////////////////////////////////////////////// - - -namespace fastdelegate { -namespace detail { // we'll hide the implementation details in a nested namespace. - -// implicit_cast< > -// I believe this was originally going to be in the C++ standard but -// was left out by accident. It's even milder than static_cast. -// I use it instead of static_cast<> to emphasize that I'm not doing -// anything nasty. -// Usage is identical to static_cast<> -template -inline OutputClass implicit_cast(InputClass input){ - return input; -} +/** \file SRDelegate.hpp + * + * This is a C++11 implementation by janezz55(code.google) for the original "The Impossibly Fast C++ Delegates" authored by Sergey Ryazanov. + * + * This is a copy checkouted from https://code.google.com/p/cpppractice/source/browse/trunk/delegate.hpp on 2014/06/07. + * Last change in the chunk was r370 on Feb 9, 2014. + * + * The following modifications were added by Benjamin YanXiang Huang + * - replace light_ptr with std::shared_ptr + * - renamed src file + * + * Reference: + * - http://codereview.stackexchange.com/questions/14730/impossibly-fast-delegate-in-c11 + * - http://www.codeproject.com/Articles/11015/The-Impossibly-Fast-C-Delegates + * - https://code.google.com/p/cpppractice/source/browse/trunk/delegate.hpp +*/ + +#pragma once +#ifndef SRDELEGATE_HPP +#define SRDELEGATE_HPP + +#include +#include +#include +#include +#include +#include + +namespace fastdelegate +{ -// horrible_cast< > -// This is truly evil. It completely subverts C++'s type system, allowing you -// to cast from any class to any other class. Technically, using a union -// to perform the cast is undefined behaviour (even in C). But we can see if -// it is OK by checking that the union is the same size as each of its members. -// horrible_cast<> should only be used for compiler-specific workarounds. -// Usage is identical to reinterpret_cast<>. - -// This union is declared outside the horrible_cast because BCC 5.5.1 -// can't inline a function with a nested class, and gives a warning. -template -union horrible_union{ - OutputClass out; - InputClass in; -}; +template class delegate; -template -inline OutputClass horrible_cast(const InputClass input){ - horrible_union u; - // Cause a compile-time error if in, out and u are not the same size. - // If the compile fails here, it means the compiler has peculiar - // unions which would prevent the cast from working. - typedef int ERROR_CantUseHorrible_cast[sizeof(InputClass)==sizeof(u) - && sizeof(InputClass)==sizeof(OutputClass) ? 1 : -1]; - u.in = input; - return u.out; -} +template +class delegate +{ + using stub_ptr_type = R(*)(void *, A&&...); -//////////////////////////////////////////////////////////////////////////////// -// Workarounds -// -//////////////////////////////////////////////////////////////////////////////// - -// Backwards compatibility: This macro used to be necessary in the virtual inheritance -// case for Intel and Microsoft. Now it just forward-declares the class. -#define FASTDELEGATEDECLARE(CLASSNAME) class CLASSNAME; - -// Prevent use of the static function hack with the DOS medium model. -#ifdef __MEDIUM__ -#undef FASTDELEGATE_USESTATICFUNCTIONHACK -#endif - -// DefaultVoid - a workaround for 'void' templates in VC6. -// -// (1) VC6 and earlier do not allow 'void' as a default template argument. -// (2) They also doesn't allow you to return 'void' from a function. -// -// Workaround for (1): Declare a dummy type 'DefaultVoid' which we use -// when we'd like to use 'void'. We convert it into 'void' and back -// using the templates DefaultVoidToVoid<> and VoidToDefaultVoid<>. -// Workaround for (2): On VC6, the code for calling a void function is -// identical to the code for calling a non-void function in which the -// return value is never used, provided the return value is returned -// in the EAX register, rather than on the stack. -// This is true for most fundamental types such as int, enum, void *. -// Const void * is the safest option since it doesn't participate -// in any automatic conversions. But on a 16-bit compiler it might -// cause extra code to be generated, so we disable it for all compilers -// except for VC6 (and VC5). -#ifdef FASTDLGT_VC6 -// VC6 workaround -typedef const void * DefaultVoid; -#else -// On any other compiler, just use a normal void. -typedef void DefaultVoid; -#endif - -// Translate from 'DefaultVoid' to 'void'. -// Everything else is unchanged -template -struct DefaultVoidToVoid { typedef T type; }; - -template <> -struct DefaultVoidToVoid { typedef void type; }; - -// Translate from 'void' into 'DefaultVoid' -// Everything else is unchanged -template -struct VoidToDefaultVoid { typedef T type; }; - -template <> -struct VoidToDefaultVoid { typedef DefaultVoid type; }; - - - -//////////////////////////////////////////////////////////////////////////////// -// Fast Delegates, part 1: -// -// Conversion of member function pointer to a standard form -// -//////////////////////////////////////////////////////////////////////////////// - -// GenericClass is a fake class, ONLY used to provide a type. -// It is vitally important that it is never defined, so that the compiler doesn't -// think it can optimize the invocation. For example, Borland generates simpler -// code if it knows the class only uses single inheritance. - -// Compilers using Microsoft's structure need to be treated as a special case. -#ifdef FASTDLGT_MICROSOFT_MFP - -#ifdef FASTDLGT_HASINHERITANCE_KEYWORDS - // For Microsoft and Intel, we want to ensure that it's the most efficient type of MFP - // (4 bytes), even when the /vmg option is used. Declaring an empty class - // would give 16 byte pointers in this case.... - class __single_inheritance GenericClass; -#endif - // ...but for Codeplay, an empty class *always* gives 4 byte pointers. - // If compiled with the /clr option ("managed C++"), the JIT compiler thinks - // it needs to load GenericClass before it can call any of its functions, - // (compiles OK but crashes at runtime!), so we need to declare an - // empty class to make it happy. - // Codeplay and VC4 can't cope with the unknown_inheritance case either. - class GenericClass {}; -#else - class GenericClass; -#endif - -// The size of a single inheritance member function pointer. -const int SINGLE_MEMFUNCPTR_SIZE = sizeof(void (GenericClass::*)()); - -// SimplifyMemFunc< >::Convert() -// -// A template function that converts an arbitrary member function pointer into the -// simplest possible form of member function pointer, using a supplied 'this' pointer. -// According to the standard, this can be done legally with reinterpret_cast<>. -// For (non-standard) compilers which use member function pointers which vary in size -// depending on the class, we need to use knowledge of the internal structure of a -// member function pointer, as used by the compiler. Template specialization is used -// to distinguish between the sizes. Because some compilers don't support partial -// template specialisation, I use full specialisation of a wrapper struct. - -// general case -- don't know how to convert it. Force a compile failure -template -struct SimplifyMemFunc { - template - inline static GenericClass *Convert(X *pthis, XFuncType function_to_bind, - GenericMemFuncType &bound_func) { - // Unsupported member function type -- force a compile failure. - // (it's illegal to have a array with negative size). - typedef char ERROR_Unsupported_member_function_pointer_on_this_compiler[N-100]; - return 0; - } -}; + delegate(void * const o, stub_ptr_type const m) noexcept : object_ptr_(o), stub_ptr_(m) {} -// For compilers where all member func ptrs are the same size, everything goes here. -// For non-standard compilers, only single_inheritance classes go here. -template <> -struct SimplifyMemFunc { - template - inline static GenericClass *Convert(X *pthis, XFuncType function_to_bind, - GenericMemFuncType &bound_func) { -#if defined __DMC__ - // Digital Mars doesn't allow you to cast between abitrary PMF's, - // even though the standard says you can. The 32-bit compiler lets you - // static_cast through an int, but the DOS compiler doesn't. - bound_func = horrible_cast(function_to_bind); -#else - bound_func = reinterpret_cast(function_to_bind); -#endif - return reinterpret_cast(pthis); - } -}; +public: + delegate(void) = default; -//////////////////////////////////////////////////////////////////////////////// -// Fast Delegates, part 1b: -// -// Workarounds for Microsoft and Intel -// -//////////////////////////////////////////////////////////////////////////////// - - -// Compilers with member function pointers which violate the standard (MSVC, Intel, Codeplay), -// need to be treated as a special case. -#ifdef FASTDLGT_MICROSOFT_MFP - -// We use unions to perform horrible_casts. I would like to use #pragma pack(push, 1) -// at the start of each function for extra safety, but VC6 seems to ICE -// intermittently if you do this inside a template. - -// __multiple_inheritance classes go here -// Nasty hack for Microsoft and Intel (IA32 and Itanium) -template<> -struct SimplifyMemFunc< SINGLE_MEMFUNCPTR_SIZE + sizeof(int) > { - template - inline static GenericClass *Convert(X *pthis, XFuncType function_to_bind, - GenericMemFuncType &bound_func) { - // We need to use a horrible_cast to do this conversion. - // In MSVC, a multiple inheritance member pointer is internally defined as: - union { - XFuncType func; - struct { - GenericMemFuncType funcaddress; // points to the actual member function - int delta; // #BYTES to be added to the 'this' pointer - }s; - } u; - // Check that the horrible_cast will work - typedef int ERROR_CantUsehorrible_cast[sizeof(function_to_bind)==sizeof(u.s)? 1 : -1]; - u.func = function_to_bind; - bound_func = u.s.funcaddress; - return reinterpret_cast(reinterpret_cast(pthis) + u.s.delta); - } -}; + delegate(delegate const &) = default; -// virtual inheritance is a real nuisance. It's inefficient and complicated. -// On MSVC and Intel, there isn't enough information in the pointer itself to -// enable conversion to a closure pointer. Earlier versions of this code didn't -// work for all cases, and generated a compile-time error instead. -// But a very clever hack invented by John M. Dlugosz solves this problem. -// My code is somewhat different to his: I have no asm code, and I make no -// assumptions about the calling convention that is used. - -// In VC++ and ICL, a virtual_inheritance member pointer -// is internally defined as: -struct MicrosoftVirtualMFP { - void (GenericClass::*codeptr)(); // points to the actual member function - int delta; // #bytes to be added to the 'this' pointer - int vtable_index; // or 0 if no virtual inheritance -}; -// The CRUCIAL feature of Microsoft/Intel MFPs which we exploit is that the -// m_codeptr member is *always* called, regardless of the values of the other -// members. (This is *not* true for other compilers, eg GCC, which obtain the -// function address from the vtable if a virtual function is being called). -// Dlugosz's trick is to make the codeptr point to a probe function which -// returns the 'this' pointer that was used. - -// Define a generic class that uses virtual inheritance. -// It has a trival member function that returns the value of the 'this' pointer. -struct GenericVirtualClass : virtual public GenericClass -{ - typedef GenericVirtualClass * (GenericVirtualClass::*ProbePtrType)(); - GenericVirtualClass * GetThis() { return this; } -}; + delegate(delegate && d) + : object_ptr_(d.object_ptr_), stub_ptr_(d.stub_ptr_), deleter_(d.deleter_), store_(d.store_), store_size_(d.store_size_) + { + d.object_ptr_ = nullptr; + d.stub_ptr_ = nullptr; + d.deleter_ = nullptr; + d.store_ = nullptr; + d.store_size_ = 0; + } -// __virtual_inheritance classes go here -template <> -struct SimplifyMemFunc -{ + delegate(::std::nullptr_t const) noexcept : delegate() { } - template - inline static GenericClass *Convert(X *pthis, XFuncType function_to_bind, - GenericMemFuncType &bound_func) { - union { - XFuncType func; - GenericClass* (X::*ProbeFunc)(); - MicrosoftVirtualMFP s; - } u; - u.func = function_to_bind; - bound_func = reinterpret_cast(u.s.codeptr); - union { - GenericVirtualClass::ProbePtrType virtfunc; - MicrosoftVirtualMFP s; - } u2; - // Check that the horrible_cast<>s will work - typedef int ERROR_CantUsehorrible_cast[sizeof(function_to_bind)==sizeof(u.s) - && sizeof(function_to_bind)==sizeof(u.ProbeFunc) - && sizeof(u2.virtfunc)==sizeof(u2.s) ? 1 : -1]; - // Unfortunately, taking the address of a MF prevents it from being inlined, so - // this next line can't be completely optimised away by the compiler. - u2.virtfunc = &GenericVirtualClass::GetThis; - u.s.codeptr = u2.s.codeptr; - return (pthis->*u.ProbeFunc)(); - } -}; + template ::value, C>::type> + explicit delegate(C const * const o) noexcept : + object_ptr_(const_cast(o)) + {} -#if (_MSC_VER <1300) + template {}>::type> + explicit delegate(C const & o) noexcept : + object_ptr_(const_cast(&o)) + {} -// Nasty hack for Microsoft Visual C++ 6.0 -// unknown_inheritance classes go here -// There is a compiler bug in MSVC6 which generates incorrect code in this case!! -template <> -struct SimplifyMemFunc -{ - template - inline static GenericClass *Convert(X *pthis, XFuncType function_to_bind, - GenericMemFuncType &bound_func) { - // There is an apalling but obscure compiler bug in MSVC6 and earlier: - // vtable_index and 'vtordisp' are always set to 0 in the - // unknown_inheritance case! - // This means that an incorrect function could be called!!! - // Compiling with the /vmg option leads to potentially incorrect code. - // This is probably the reason that the IDE has a user interface for specifying - // the /vmg option, but it is disabled - you can only specify /vmg on - // the command line. In VC1.5 and earlier, the compiler would ICE if it ever - // encountered this situation. - // It is OK to use the /vmg option if /vmm or /vms is specified. - - // Fortunately, the wrong function is only called in very obscure cases. - // It only occurs when a derived class overrides a virtual function declared - // in a virtual base class, and the member function - // points to the *Derived* version of that function. The problem can be - // completely averted in 100% of cases by using the *Base class* for the - // member fpointer. Ie, if you use the base class as an interface, you'll - // stay out of trouble. - // Occasionally, you might want to point directly to a derived class function - // that isn't an override of a base class. In this case, both vtable_index - // and 'vtordisp' are zero, but a virtual_inheritance pointer will be generated. - // We can generate correct code in this case. To prevent an incorrect call from - // ever being made, on MSVC6 we generate a warning, and call a function to - // make the program crash instantly. - typedef char ERROR_VC6CompilerBug[-100]; - return 0; - } -}; + template + delegate(C * const object_ptr, R(C::* const method_ptr)(A...)) + { + *this = from(object_ptr, method_ptr); + } + template + delegate(C * const object_ptr, R(C::* const method_ptr)(A...) const) + { + *this = from(object_ptr, method_ptr); + } -#else + template + delegate(C & object, R(C::* const method_ptr)(A...)) + { + *this = from(object, method_ptr); + } -// Nasty hack for Microsoft and Intel (IA32 and Itanium) -// unknown_inheritance classes go here -// This is probably the ugliest bit of code I've ever written. Look at the casts! -// There is a compiler bug in MSVC6 which prevents it from using this code. -template <> -struct SimplifyMemFunc -{ - template - inline static GenericClass *Convert(X *pthis, XFuncType function_to_bind, - GenericMemFuncType &bound_func) { - // The member function pointer is 16 bytes long. We can't use a normal cast, but - // we can use a union to do the conversion. - union { - XFuncType func; - // In VC++ and ICL, an unknown_inheritance member pointer - // is internally defined as: - struct { - GenericMemFuncType m_funcaddress; // points to the actual member function - int delta; // #bytes to be added to the 'this' pointer - int vtordisp; // #bytes to add to 'this' to find the vtable - int vtable_index; // or 0 if no virtual inheritance - } s; - } u; - // Check that the horrible_cast will work - typedef int ERROR_CantUsehorrible_cast[sizeof(XFuncType)==sizeof(u.s)? 1 : -1]; - u.func = function_to_bind; - bound_func = u.s.funcaddress; - int virtual_delta = 0; - if (u.s.vtable_index) { // Virtual inheritance is used - // First, get to the vtable. - // It is 'vtordisp' bytes from the start of the class. - const int * vtable = *reinterpret_cast( - reinterpret_cast(pthis) + u.s.vtordisp ); - - // 'vtable_index' tells us where in the table we should be looking. - virtual_delta = u.s.vtordisp + *reinterpret_cast( - reinterpret_cast(vtable) + u.s.vtable_index); - } - // The int at 'virtual_delta' gives us the amount to add to 'this'. - // Finally we can add the three components together. Phew! - return reinterpret_cast( - reinterpret_cast(pthis) + u.s.delta + virtual_delta); - }; -}; -#endif // MSVC 7 and greater - -#endif // MS/Intel hacks - -} // namespace detail - -//////////////////////////////////////////////////////////////////////////////// -// Fast Delegates, part 2: -// -// Define the delegate storage, and cope with static functions -// -//////////////////////////////////////////////////////////////////////////////// - -// DelegateMemento -- an opaque structure which can hold an arbitary delegate. -// It knows nothing about the calling convention or number of arguments used by -// the function pointed to. -// It supplies comparison operators so that it can be stored in STL collections. -// It cannot be set to anything other than null, nor invoked directly: -// it must be converted to a specific delegate. - -// Implementation: -// There are two possible implementations: the Safe method and the Evil method. -// DelegateMemento - Safe version -// -// This implementation is standard-compliant, but a bit tricky. -// A static function pointer is stored inside the class. -// Here are the valid values: -// +-- Static pointer --+--pThis --+-- pMemFunc-+-- Meaning------+ -// | 0 | 0 | 0 | Empty | -// | !=0 |(dontcare)| Invoker | Static function| -// | 0 | !=0 | !=0* | Method call | -// +--------------------+----------+------------+----------------+ -// * For Metrowerks, this can be 0. (first virtual function in a -// single_inheritance class). -// When stored stored inside a specific delegate, the 'dontcare' entries are replaced -// with a reference to the delegate itself. This complicates the = and == operators -// for the delegate class. - -// DelegateMemento - Evil version -// -// For compilers where data pointers are at least as big as code pointers, it is -// possible to store the function pointer in the this pointer, using another -// horrible_cast. In this case the DelegateMemento implementation is simple: -// +--pThis --+-- pMemFunc-+-- Meaning---------------------+ -// | 0 | 0 | Empty | -// | !=0 | !=0* | Static function or method call| -// +----------+------------+-------------------------------+ -// * For Metrowerks, this can be 0. (first virtual function in a -// single_inheritance class). -// Note that the Sun C++ and MSVC documentation explicitly state that they -// support static_cast between void * and function pointers. - -class DelegateMemento { -protected: - // the data is protected, not private, because many - // compilers have problems with template friends. - typedef void (detail::GenericClass::*GenericMemFuncType)(); // arbitrary MFP. - detail::GenericClass *m_pthis; - GenericMemFuncType m_pFunction; - -#if !defined(FASTDELEGATE_USESTATICFUNCTIONHACK) - typedef void (*GenericFuncPtr)(); // arbitrary code pointer - GenericFuncPtr m_pStaticFunction; -#endif + template + delegate(C const & object, R(C::* const method_ptr)(A...) const) + { + *this = from(object, method_ptr); + } -public: -#if !defined(FASTDELEGATE_USESTATICFUNCTIONHACK) - DelegateMemento() : m_pthis(0), m_pFunction(0), m_pStaticFunction(0) {}; - void clear() { - m_pthis=0; m_pFunction=0; m_pStaticFunction=0; - } -#else - DelegateMemento() : m_pthis(0), m_pFunction(0) {}; - void clear() { m_pthis=0; m_pFunction=0; } -#endif -public: -#if !defined(FASTDELEGATE_USESTATICFUNCTIONHACK) - inline bool IsEqual (const DelegateMemento &x) const{ - // We have to cope with the static function pointers as a special case - if (m_pFunction!=x.m_pFunction) return false; - // the static function ptrs must either both be equal, or both be 0. - if (m_pStaticFunction!=x.m_pStaticFunction) return false; - if (m_pStaticFunction!=0) return m_pthis==x.m_pthis; - else return true; - } -#else // Evil Method - inline bool IsEqual (const DelegateMemento &x) const{ - return m_pthis==x.m_pthis && m_pFunction==x.m_pFunction; - } -#endif - // Provide a strict weak ordering for DelegateMementos. - inline bool IsLess(const DelegateMemento &right) const { - // deal with static function pointers first -#if !defined(FASTDELEGATE_USESTATICFUNCTIONHACK) - if (m_pStaticFunction !=0 || right.m_pStaticFunction!=0) - return m_pStaticFunction < right.m_pStaticFunction; -#endif - if (m_pthis !=right.m_pthis) return m_pthis < right.m_pthis; - // There are no ordering operators for member function pointers, - // but we can fake one by comparing each byte. The resulting ordering is - // arbitrary (and compiler-dependent), but it permits storage in ordered STL containers. - return memcmp(&m_pFunction, &right.m_pFunction, sizeof(m_pFunction)) < 0; - - } - // BUGFIX (Mar 2005): - // We can't just compare m_pFunction because on Metrowerks, - // m_pFunction can be zero even if the delegate is not empty! - inline bool operator ! () const // Is it bound to anything? - { return m_pthis==0 && m_pFunction==0; } - inline bool empty() const // Is it bound to anything? - { return m_pthis==0 && m_pFunction==0; } -public: - DelegateMemento & operator = (const DelegateMemento &right) { - SetMementoFrom(right); - return *this; - } - inline bool operator <(const DelegateMemento &right) { - return IsLess(right); - } - inline bool operator >(const DelegateMemento &right) { - return right.IsLess(*this); - } - DelegateMemento (const DelegateMemento &right) : - m_pFunction(right.m_pFunction), m_pthis(right.m_pthis) -#if !defined(FASTDELEGATE_USESTATICFUNCTIONHACK) - , m_pStaticFunction (right.m_pStaticFunction) -#endif - {} -protected: - void SetMementoFrom(const DelegateMemento &right) { - m_pFunction = right.m_pFunction; - m_pthis = right.m_pthis; -#if !defined(FASTDELEGATE_USESTATICFUNCTIONHACK) - m_pStaticFunction = right.m_pStaticFunction; -#endif - } -}; + template < + typename T, + typename = typename ::std::enable_if::type>::value>::type + > + delegate(T&& f) + : store_(operator new(sizeof(typename ::std::decay::type)) + , functor_deleter::type>) + , store_size_(sizeof(typename ::std::decay::type)) + { + using functor_type = typename ::std::decay::type; + + new(store_.get()) functor_type(::std::forward(f)); + object_ptr_ = store_.get(); + + stub_ptr_ = functor_stub; + deleter_ = deleter_stub; + } + delegate & operator=(delegate const &) = default; -// ClosurePtr<> -// -// A private wrapper class that adds function signatures to DelegateMemento. -// It's the class that does most of the actual work. -// The signatures are specified by: -// GenericMemFunc: must be a type of GenericClass member function pointer. -// StaticFuncPtr: must be a type of function pointer with the same signature -// as GenericMemFunc. -// UnvoidStaticFuncPtr: is the same as StaticFuncPtr, except on VC6 -// where it never returns void (returns DefaultVoid instead). + delegate & operator=(delegate&& d) + { + object_ptr_ = d.object_ptr_; + stub_ptr_ = d.stub_ptr_; + deleter_ = d.deleter_; + store_ = d.store_; + store_size_ = d.store_size_; -// An outer class, FastDelegateN<>, handles the invoking and creates the -// necessary typedefs. -// This class does everything else. + d.object_ptr_ = nullptr; + d.stub_ptr_ = nullptr; + d.deleter_ = nullptr; + d.store_ = nullptr; + d.store_size_ = 0; -namespace detail { + return *this; + } -template < class GenericMemFunc, class StaticFuncPtr, class UnvoidStaticFuncPtr> -class ClosurePtr : public DelegateMemento { -public: - // These functions are for setting the delegate to a member function. - - // Here's the clever bit: we convert an arbitrary member function into a - // standard form. XMemFunc should be a member function of class X, but I can't - // enforce that here. It needs to be enforced by the wrapper class. - template < class X, class XMemFunc > - inline void bindmemfunc(X *pthis, XMemFunc function_to_bind ) { - m_pthis = SimplifyMemFunc< sizeof(function_to_bind) > - ::Convert(pthis, function_to_bind, m_pFunction); -#if !defined(FASTDELEGATE_USESTATICFUNCTIONHACK) - m_pStaticFunction = 0; -#endif - } - // For const member functions, we only need a const class pointer. - // Since we know that the member function is const, it's safe to - // remove the const qualifier from the 'this' pointer with a const_cast. - // VC6 has problems if we just overload 'bindmemfunc', so we give it a different name. - template < class X, class XMemFunc> - inline void bindconstmemfunc(const X *pthis, XMemFunc function_to_bind) { - m_pthis= SimplifyMemFunc< sizeof(function_to_bind) > - ::Convert(const_cast(pthis), function_to_bind, m_pFunction); -#if !defined(FASTDELEGATE_USESTATICFUNCTIONHACK) - m_pStaticFunction = 0; -#endif - } -#ifdef FASTDELEGATE_GCC_BUG_8271 // At present, GCC doesn't recognize constness of MFPs in templates - template < class X, class XMemFunc> - inline void bindmemfunc(const X *pthis, XMemFunc function_to_bind) { - bindconstmemfunc(pthis, function_to_bind); -#if !defined(FASTDELEGATE_USESTATICFUNCTIONHACK) - m_pStaticFunction = 0; -#endif - } -#endif - // These functions are required for invoking the stored function - inline GenericClass *GetClosureThis() const { return m_pthis; } - inline GenericMemFunc GetClosureMemPtr() const { return reinterpret_cast(m_pFunction); } - -// There are a few ways of dealing with static function pointers. -// There's a standard-compliant, but tricky method. -// There's also a straightforward hack, that won't work on DOS compilers using the -// medium memory model. It's so evil that I can't recommend it, but I've -// implemented it anyway because it produces very nice asm code. - -#if !defined(FASTDELEGATE_USESTATICFUNCTIONHACK) - -// ClosurePtr<> - Safe version -// -// This implementation is standard-compliant, but a bit tricky. -// I store the function pointer inside the class, and the delegate then -// points to itself. Whenever the delegate is copied, these self-references -// must be transformed, and this complicates the = and == operators. -public: - // The next two functions are for operator ==, =, and the copy constructor. - // We may need to convert the m_pthis pointers, so that - // they remain as self-references. - template< class DerivedClass > - inline void CopyFrom (DerivedClass *pParent, const DelegateMemento &x) { - SetMementoFrom(x); - if (m_pStaticFunction!=0) { - // transform self references... - m_pthis=reinterpret_cast(pParent); - } - } - // For static functions, the 'static_function_invoker' class in the parent - // will be called. The parent then needs to call GetStaticFunction() to find out - // the actual function to invoke. - template < class DerivedClass, class ParentInvokerSig > - inline void bindstaticfunc(DerivedClass *pParent, ParentInvokerSig static_function_invoker, - StaticFuncPtr function_to_bind ) { - if (function_to_bind==0) { // cope with assignment to 0 - m_pFunction=0; - } else { - bindmemfunc(pParent, static_function_invoker); - } - m_pStaticFunction=reinterpret_cast(function_to_bind); - } - inline UnvoidStaticFuncPtr GetStaticFunction() const { - return reinterpret_cast(m_pStaticFunction); - } -#else - -// ClosurePtr<> - Evil version -// -// For compilers where data pointers are at least as big as code pointers, it is -// possible to store the function pointer in the this pointer, using another -// horrible_cast. Invocation isn't any faster, but it saves 4 bytes, and -// speeds up comparison and assignment. If C++ provided direct language support -// for delegates, they would produce asm code that was almost identical to this. -// Note that the Sun C++ and MSVC documentation explicitly state that they -// support static_cast between void * and function pointers. - - template< class DerivedClass > - inline void CopyFrom (DerivedClass *pParent, const DelegateMemento &right) { - SetMementoFrom(right); - } - // For static functions, the 'static_function_invoker' class in the parent - // will be called. The parent then needs to call GetStaticFunction() to find out - // the actual function to invoke. - // ******** EVIL, EVIL CODE! ******* - template < class DerivedClass, class ParentInvokerSig> - inline void bindstaticfunc(DerivedClass *pParent, ParentInvokerSig static_function_invoker, - StaticFuncPtr function_to_bind) { - if (function_to_bind==0) { // cope with assignment to 0 - m_pFunction=0; - } else { - // We'll be ignoring the 'this' pointer, but we need to make sure we pass - // a valid value to bindmemfunc(). - bindmemfunc(pParent, static_function_invoker); + template + delegate & operator=(R(C::* const rhs)(A...)) + { + return *this = from(static_cast(object_ptr_), rhs); + } + + template + delegate & operator=(R(C::* const rhs)(A...) const) + { + return *this = from(static_cast(object_ptr_), rhs); + } + + template < + typename T + , typename = typename ::std::enable_if::type>::value>::type + > + delegate & operator=(T&& f) + { + using functor_type = typename ::std::decay::type; + + if ((sizeof(functor_type) > store_size_) || !store_.unique()) + { + store_.reset(operator new(sizeof(functor_type)), functor_deleter); + store_size_ = sizeof(functor_type); } + else + deleter_(store_.get()); - // WARNING! Evil hack. We store the function in the 'this' pointer! - // Ensure that there's a compilation failure if function pointers - // and data pointers have different sizes. - // If you get this error, you need to #undef FASTDELEGATE_USESTATICFUNCTIONHACK. - typedef int ERROR_CantUseEvilMethod[sizeof(GenericClass *)==sizeof(function_to_bind) ? 1 : -1]; - m_pthis = horrible_cast(function_to_bind); - // MSVC, SunC++ and DMC accept the following (non-standard) code: -// m_pthis = static_cast(static_cast(function_to_bind)); - // BCC32, Comeau and DMC accept this method. MSVC7.1 needs __int64 instead of long -// m_pthis = reinterpret_cast(reinterpret_cast(function_to_bind)); - } - // ******** EVIL, EVIL CODE! ******* - // This function will be called with an invalid 'this' pointer!! - // We're just returning the 'this' pointer, converted into - // a function pointer! - inline UnvoidStaticFuncPtr GetStaticFunction() const { - // Ensure that there's a compilation failure if function pointers - // and data pointers have different sizes. - // If you get this error, you need to #undef FASTDELEGATE_USESTATICFUNCTIONHACK. - typedef int ERROR_CantUseEvilMethod[sizeof(UnvoidStaticFuncPtr)==sizeof(this) ? 1 : -1]; - return horrible_cast(this); - } -#endif // !defined(FASTDELEGATE_USESTATICFUNCTIONHACK) - - // Does the closure contain this static function? - inline bool IsEqualToStaticFuncPtr(StaticFuncPtr funcptr){ - if (funcptr==0) return empty(); - // For the Evil method, if it doesn't actually contain a static function, this will return an arbitrary - // value that is not equal to any valid function pointer. - else return funcptr==reinterpret_cast(GetStaticFunction()); - } -}; + new(store_.get()) functor_type(::std::forward(f)); + object_ptr_ = store_.get(); + stub_ptr_ = functor_stub; + deleter_ = deleter_stub; -} // namespace detail - -//////////////////////////////////////////////////////////////////////////////// -// Fast Delegates, part 3: -// -// Wrapper classes to ensure type safety -// -//////////////////////////////////////////////////////////////////////////////// - - -// Once we have the member function conversion templates, it's easy to make the -// wrapper classes. So that they will work with as many compilers as possible, -// the classes are of the form -// FastDelegate3 -// They can cope with any combination of parameters. The max number of parameters -// allowed is 8, but it is trivial to increase this limit. -// Note that we need to treat const member functions seperately. -// All this class does is to enforce type safety, and invoke the delegate with -// the correct list of parameters. - -// Because of the weird rule about the class of derived member function pointers, -// you sometimes need to apply a downcast to the 'this' pointer. -// This is the reason for the use of "implicit_cast(pthis)" in the code below. -// If CDerivedClass is derived from CBaseClass, but doesn't override SimpleVirtualFunction, -// without this trick you'd need to write: -// MyDelegate(static_cast(&d), &CDerivedClass::SimpleVirtualFunction); -// but with the trick you can write -// MyDelegate(&d, &CDerivedClass::SimpleVirtualFunction); - -// RetType is the type the compiler uses in compiling the template. For VC6, -// it cannot be void. DesiredRetType is the real type which is returned from -// all of the functions. It can be void. - -// Implicit conversion to "bool" is achieved using the safe_bool idiom, -// using member data pointers (MDP). This allows "if (dg)..." syntax -// Because some compilers (eg codeplay) don't have a unique value for a zero -// MDP, an extra padding member is added to the SafeBool struct. -// Some compilers (eg VC6) won't implicitly convert from 0 to an MDP, so -// in that case the static function constructor is not made explicit; this -// allows "if (dg==0) ..." to compile. - -//N=0 -template -class FastDelegate0 { -private: - typedef typename detail::DefaultVoidToVoid::type DesiredRetType; - typedef DesiredRetType (*StaticFunctionPtr)(); - typedef RetType (*UnvoidStaticFunctionPtr)(); - typedef RetType (detail::GenericClass::*GenericMemFn)(); - typedef detail::ClosurePtr ClosureType; - ClosureType m_Closure; -public: - // Typedefs to aid generic programming - typedef FastDelegate0 type; - - // Construction and comparison functions - FastDelegate0() { clear(); } - FastDelegate0(const FastDelegate0 &x) { - m_Closure.CopyFrom(this, x.m_Closure); } - void operator = (const FastDelegate0 &x) { - m_Closure.CopyFrom(this, x.m_Closure); } - bool operator ==(const FastDelegate0 &x) const { - return m_Closure.IsEqual(x.m_Closure); } - bool operator !=(const FastDelegate0 &x) const { - return !m_Closure.IsEqual(x.m_Closure); } - bool operator <(const FastDelegate0 &x) const { - return m_Closure.IsLess(x.m_Closure); } - bool operator >(const FastDelegate0 &x) const { - return x.m_Closure.IsLess(m_Closure); } - // Binding to non-const member functions - template < class X, class Y > - FastDelegate0(Y *pthis, DesiredRetType (X::* function_to_bind)() ) { - m_Closure.bindmemfunc(detail::implicit_cast(pthis), function_to_bind); } - template < class X, class Y > - inline void bind(Y *pthis, DesiredRetType (X::* function_to_bind)()) { - m_Closure.bindmemfunc(detail::implicit_cast(pthis), function_to_bind); } - // Binding to const member functions. - template < class X, class Y > - FastDelegate0(const Y *pthis, DesiredRetType (X::* function_to_bind)() const) { - m_Closure.bindconstmemfunc(detail::implicit_cast(pthis), function_to_bind); } - template < class X, class Y > - inline void bind(const Y *pthis, DesiredRetType (X::* function_to_bind)() const) { - m_Closure.bindconstmemfunc(detail::implicit_cast(pthis), function_to_bind); } - // Static functions. We convert them into a member function call. - // This constructor also provides implicit conversion - FastDelegate0(DesiredRetType (*function_to_bind)() ) { - bind(function_to_bind); } - // for efficiency, prevent creation of a temporary - void operator = (DesiredRetType (*function_to_bind)() ) { - bind(function_to_bind); } - inline void bind(DesiredRetType (*function_to_bind)()) { - m_Closure.bindstaticfunc(this, &FastDelegate0::InvokeStaticFunction, - function_to_bind); } - // Invoke the delegate - RetType operator() () const { - return (m_Closure.GetClosureThis()->*(m_Closure.GetClosureMemPtr()))(); } - // Implicit conversion to "bool" using the safe_bool idiom -private: - typedef struct SafeBoolStruct { - int a_data_pointer_to_this_is_0_on_buggy_compilers; - StaticFunctionPtr m_nonzero; - } UselessTypedef; - typedef StaticFunctionPtr SafeBoolStruct::*unspecified_bool_type; -public: - operator unspecified_bool_type() const { - return empty()? 0: &SafeBoolStruct::m_nonzero; + return *this; } - // necessary to allow ==0 to work despite the safe_bool idiom - inline bool operator==(StaticFunctionPtr funcptr) { - return m_Closure.IsEqualToStaticFuncPtr(funcptr); } - inline bool operator!=(StaticFunctionPtr funcptr) { - return !m_Closure.IsEqualToStaticFuncPtr(funcptr); } - inline bool operator ! () const { // Is it bound to anything? - return !m_Closure; } - inline bool empty() const { - return !m_Closure; } - void clear() { m_Closure.clear();} - // Conversion to and from the DelegateMemento storage class - const DelegateMemento & GetMemento() { return m_Closure; } - void SetMemento(const DelegateMemento &any) { m_Closure.CopyFrom(this, any); } - -private: // Invoker for static functions - RetType InvokeStaticFunction() const { - return (*(m_Closure.GetStaticFunction()))(); } -}; -//N=1 -template -class FastDelegate1 { -private: - typedef typename detail::DefaultVoidToVoid::type DesiredRetType; - typedef DesiredRetType (*StaticFunctionPtr)(Param1 p1); - typedef RetType (*UnvoidStaticFunctionPtr)(Param1 p1); - typedef RetType (detail::GenericClass::*GenericMemFn)(Param1 p1); - typedef detail::ClosurePtr ClosureType; - ClosureType m_Closure; -public: - // Typedefs to aid generic programming - typedef FastDelegate1 type; - - // Construction and comparison functions - FastDelegate1() { clear(); } - FastDelegate1(const FastDelegate1 &x) { - m_Closure.CopyFrom(this, x.m_Closure); } - void operator = (const FastDelegate1 &x) { - m_Closure.CopyFrom(this, x.m_Closure); } - bool operator ==(const FastDelegate1 &x) const { - return m_Closure.IsEqual(x.m_Closure); } - bool operator !=(const FastDelegate1 &x) const { - return !m_Closure.IsEqual(x.m_Closure); } - bool operator <(const FastDelegate1 &x) const { - return m_Closure.IsLess(x.m_Closure); } - bool operator >(const FastDelegate1 &x) const { - return x.m_Closure.IsLess(m_Closure); } - // Binding to non-const member functions - template < class X, class Y > - FastDelegate1(Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1) ) { - m_Closure.bindmemfunc(detail::implicit_cast(pthis), function_to_bind); } - template < class X, class Y > - inline void bind(Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1)) { - m_Closure.bindmemfunc(detail::implicit_cast(pthis), function_to_bind); } - // Binding to const member functions. - template < class X, class Y > - FastDelegate1(const Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1) const) { - m_Closure.bindconstmemfunc(detail::implicit_cast(pthis), function_to_bind); } - template < class X, class Y > - inline void bind(const Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1) const) { - m_Closure.bindconstmemfunc(detail::implicit_cast(pthis), function_to_bind); } - // Static functions. We convert them into a member function call. - // This constructor also provides implicit conversion - FastDelegate1(DesiredRetType (*function_to_bind)(Param1 p1) ) { - bind(function_to_bind); } - // for efficiency, prevent creation of a temporary - void operator = (DesiredRetType (*function_to_bind)(Param1 p1) ) { - bind(function_to_bind); } - inline void bind(DesiredRetType (*function_to_bind)(Param1 p1)) { - m_Closure.bindstaticfunc(this, &FastDelegate1::InvokeStaticFunction, - function_to_bind); } - // Invoke the delegate - RetType operator() (Param1 p1) const { - return (m_Closure.GetClosureThis()->*(m_Closure.GetClosureMemPtr()))(p1); } - // Implicit conversion to "bool" using the safe_bool idiom -private: - typedef struct SafeBoolStruct { - int a_data_pointer_to_this_is_0_on_buggy_compilers; - StaticFunctionPtr m_nonzero; - } UselessTypedef; - typedef StaticFunctionPtr SafeBoolStruct::*unspecified_bool_type; -public: - operator unspecified_bool_type() const { - return empty()? 0: &SafeBoolStruct::m_nonzero; + template + static delegate from(void) noexcept + { + return { nullptr, function_stub }; } - // necessary to allow ==0 to work despite the safe_bool idiom - inline bool operator==(StaticFunctionPtr funcptr) { - return m_Closure.IsEqualToStaticFuncPtr(funcptr); } - inline bool operator!=(StaticFunctionPtr funcptr) { - return !m_Closure.IsEqualToStaticFuncPtr(funcptr); } - inline bool operator ! () const { // Is it bound to anything? - return !m_Closure; } - inline bool empty() const { - return !m_Closure; } - void clear() { m_Closure.clear();} - // Conversion to and from the DelegateMemento storage class - const DelegateMemento & GetMemento() { return m_Closure; } - void SetMemento(const DelegateMemento &any) { m_Closure.CopyFrom(this, any); } - -private: // Invoker for static functions - RetType InvokeStaticFunction(Param1 p1) const { - return (*(m_Closure.GetStaticFunction()))(p1); } -}; -//N=2 -template -class FastDelegate2 { -private: - typedef typename detail::DefaultVoidToVoid::type DesiredRetType; - typedef DesiredRetType (*StaticFunctionPtr)(Param1 p1, Param2 p2); - typedef RetType (*UnvoidStaticFunctionPtr)(Param1 p1, Param2 p2); - typedef RetType (detail::GenericClass::*GenericMemFn)(Param1 p1, Param2 p2); - typedef detail::ClosurePtr ClosureType; - ClosureType m_Closure; -public: - // Typedefs to aid generic programming - typedef FastDelegate2 type; - - // Construction and comparison functions - FastDelegate2() { clear(); } - FastDelegate2(const FastDelegate2 &x) { - m_Closure.CopyFrom(this, x.m_Closure); } - void operator = (const FastDelegate2 &x) { - m_Closure.CopyFrom(this, x.m_Closure); } - bool operator ==(const FastDelegate2 &x) const { - return m_Closure.IsEqual(x.m_Closure); } - bool operator !=(const FastDelegate2 &x) const { - return !m_Closure.IsEqual(x.m_Closure); } - bool operator <(const FastDelegate2 &x) const { - return m_Closure.IsLess(x.m_Closure); } - bool operator >(const FastDelegate2 &x) const { - return x.m_Closure.IsLess(m_Closure); } - // Binding to non-const member functions - template < class X, class Y > - FastDelegate2(Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2) ) { - m_Closure.bindmemfunc(detail::implicit_cast(pthis), function_to_bind); } - template < class X, class Y > - inline void bind(Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2)) { - m_Closure.bindmemfunc(detail::implicit_cast(pthis), function_to_bind); } - // Binding to const member functions. - template < class X, class Y > - FastDelegate2(const Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2) const) { - m_Closure.bindconstmemfunc(detail::implicit_cast(pthis), function_to_bind); } - template < class X, class Y > - inline void bind(const Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2) const) { - m_Closure.bindconstmemfunc(detail::implicit_cast(pthis), function_to_bind); } - // Static functions. We convert them into a member function call. - // This constructor also provides implicit conversion - FastDelegate2(DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2) ) { - bind(function_to_bind); } - // for efficiency, prevent creation of a temporary - void operator = (DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2) ) { - bind(function_to_bind); } - inline void bind(DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2)) { - m_Closure.bindstaticfunc(this, &FastDelegate2::InvokeStaticFunction, - function_to_bind); } - // Invoke the delegate - RetType operator() (Param1 p1, Param2 p2) const { - return (m_Closure.GetClosureThis()->*(m_Closure.GetClosureMemPtr()))(p1, p2); } - // Implicit conversion to "bool" using the safe_bool idiom -private: - typedef struct SafeBoolStruct { - int a_data_pointer_to_this_is_0_on_buggy_compilers; - StaticFunctionPtr m_nonzero; - } UselessTypedef; - typedef StaticFunctionPtr SafeBoolStruct::*unspecified_bool_type; -public: - operator unspecified_bool_type() const { - return empty()? 0: &SafeBoolStruct::m_nonzero; + template + static delegate from(C * const object_ptr) noexcept + { + return { object_ptr, method_stub }; } - // necessary to allow ==0 to work despite the safe_bool idiom - inline bool operator==(StaticFunctionPtr funcptr) { - return m_Closure.IsEqualToStaticFuncPtr(funcptr); } - inline bool operator!=(StaticFunctionPtr funcptr) { - return !m_Closure.IsEqualToStaticFuncPtr(funcptr); } - inline bool operator ! () const { // Is it bound to anything? - return !m_Closure; } - inline bool empty() const { - return !m_Closure; } - void clear() { m_Closure.clear();} - // Conversion to and from the DelegateMemento storage class - const DelegateMemento & GetMemento() { return m_Closure; } - void SetMemento(const DelegateMemento &any) { m_Closure.CopyFrom(this, any); } - -private: // Invoker for static functions - RetType InvokeStaticFunction(Param1 p1, Param2 p2) const { - return (*(m_Closure.GetStaticFunction()))(p1, p2); } -}; -//N=3 -template -class FastDelegate3 { -private: - typedef typename detail::DefaultVoidToVoid::type DesiredRetType; - typedef DesiredRetType (*StaticFunctionPtr)(Param1 p1, Param2 p2, Param3 p3); - typedef RetType (*UnvoidStaticFunctionPtr)(Param1 p1, Param2 p2, Param3 p3); - typedef RetType (detail::GenericClass::*GenericMemFn)(Param1 p1, Param2 p2, Param3 p3); - typedef detail::ClosurePtr ClosureType; - ClosureType m_Closure; -public: - // Typedefs to aid generic programming - typedef FastDelegate3 type; - - // Construction and comparison functions - FastDelegate3() { clear(); } - FastDelegate3(const FastDelegate3 &x) { - m_Closure.CopyFrom(this, x.m_Closure); } - void operator = (const FastDelegate3 &x) { - m_Closure.CopyFrom(this, x.m_Closure); } - bool operator ==(const FastDelegate3 &x) const { - return m_Closure.IsEqual(x.m_Closure); } - bool operator !=(const FastDelegate3 &x) const { - return !m_Closure.IsEqual(x.m_Closure); } - bool operator <(const FastDelegate3 &x) const { - return m_Closure.IsLess(x.m_Closure); } - bool operator >(const FastDelegate3 &x) const { - return x.m_Closure.IsLess(m_Closure); } - // Binding to non-const member functions - template < class X, class Y > - FastDelegate3(Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3) ) { - m_Closure.bindmemfunc(detail::implicit_cast(pthis), function_to_bind); } - template < class X, class Y > - inline void bind(Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3)) { - m_Closure.bindmemfunc(detail::implicit_cast(pthis), function_to_bind); } - // Binding to const member functions. - template < class X, class Y > - FastDelegate3(const Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3) const) { - m_Closure.bindconstmemfunc(detail::implicit_cast(pthis), function_to_bind); } - template < class X, class Y > - inline void bind(const Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3) const) { - m_Closure.bindconstmemfunc(detail::implicit_cast(pthis), function_to_bind); } - // Static functions. We convert them into a member function call. - // This constructor also provides implicit conversion - FastDelegate3(DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2, Param3 p3) ) { - bind(function_to_bind); } - // for efficiency, prevent creation of a temporary - void operator = (DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2, Param3 p3) ) { - bind(function_to_bind); } - inline void bind(DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2, Param3 p3)) { - m_Closure.bindstaticfunc(this, &FastDelegate3::InvokeStaticFunction, - function_to_bind); } - // Invoke the delegate - RetType operator() (Param1 p1, Param2 p2, Param3 p3) const { - return (m_Closure.GetClosureThis()->*(m_Closure.GetClosureMemPtr()))(p1, p2, p3); } - // Implicit conversion to "bool" using the safe_bool idiom -private: - typedef struct SafeBoolStruct { - int a_data_pointer_to_this_is_0_on_buggy_compilers; - StaticFunctionPtr m_nonzero; - } UselessTypedef; - typedef StaticFunctionPtr SafeBoolStruct::*unspecified_bool_type; -public: - operator unspecified_bool_type() const { - return empty()? 0: &SafeBoolStruct::m_nonzero; + template + static delegate from(C const * const object_ptr) noexcept + { + return { const_cast(object_ptr), const_method_stub }; } - // necessary to allow ==0 to work despite the safe_bool idiom - inline bool operator==(StaticFunctionPtr funcptr) { - return m_Closure.IsEqualToStaticFuncPtr(funcptr); } - inline bool operator!=(StaticFunctionPtr funcptr) { - return !m_Closure.IsEqualToStaticFuncPtr(funcptr); } - inline bool operator ! () const { // Is it bound to anything? - return !m_Closure; } - inline bool empty() const { - return !m_Closure; } - void clear() { m_Closure.clear();} - // Conversion to and from the DelegateMemento storage class - const DelegateMemento & GetMemento() { return m_Closure; } - void SetMemento(const DelegateMemento &any) { m_Closure.CopyFrom(this, any); } - -private: // Invoker for static functions - RetType InvokeStaticFunction(Param1 p1, Param2 p2, Param3 p3) const { - return (*(m_Closure.GetStaticFunction()))(p1, p2, p3); } -}; -//N=4 -template -class FastDelegate4 { -private: - typedef typename detail::DefaultVoidToVoid::type DesiredRetType; - typedef DesiredRetType (*StaticFunctionPtr)(Param1 p1, Param2 p2, Param3 p3, Param4 p4); - typedef RetType (*UnvoidStaticFunctionPtr)(Param1 p1, Param2 p2, Param3 p3, Param4 p4); - typedef RetType (detail::GenericClass::*GenericMemFn)(Param1 p1, Param2 p2, Param3 p3, Param4 p4); - typedef detail::ClosurePtr ClosureType; - ClosureType m_Closure; -public: - // Typedefs to aid generic programming - typedef FastDelegate4 type; - - // Construction and comparison functions - FastDelegate4() { clear(); } - FastDelegate4(const FastDelegate4 &x) { - m_Closure.CopyFrom(this, x.m_Closure); } - void operator = (const FastDelegate4 &x) { - m_Closure.CopyFrom(this, x.m_Closure); } - bool operator ==(const FastDelegate4 &x) const { - return m_Closure.IsEqual(x.m_Closure); } - bool operator !=(const FastDelegate4 &x) const { - return !m_Closure.IsEqual(x.m_Closure); } - bool operator <(const FastDelegate4 &x) const { - return m_Closure.IsLess(x.m_Closure); } - bool operator >(const FastDelegate4 &x) const { - return x.m_Closure.IsLess(m_Closure); } - // Binding to non-const member functions - template < class X, class Y > - FastDelegate4(Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4) ) { - m_Closure.bindmemfunc(detail::implicit_cast(pthis), function_to_bind); } - template < class X, class Y > - inline void bind(Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4)) { - m_Closure.bindmemfunc(detail::implicit_cast(pthis), function_to_bind); } - // Binding to const member functions. - template < class X, class Y > - FastDelegate4(const Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4) const) { - m_Closure.bindconstmemfunc(detail::implicit_cast(pthis), function_to_bind); } - template < class X, class Y > - inline void bind(const Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4) const) { - m_Closure.bindconstmemfunc(detail::implicit_cast(pthis), function_to_bind); } - // Static functions. We convert them into a member function call. - // This constructor also provides implicit conversion - FastDelegate4(DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4) ) { - bind(function_to_bind); } - // for efficiency, prevent creation of a temporary - void operator = (DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4) ) { - bind(function_to_bind); } - inline void bind(DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4)) { - m_Closure.bindstaticfunc(this, &FastDelegate4::InvokeStaticFunction, - function_to_bind); } - // Invoke the delegate - RetType operator() (Param1 p1, Param2 p2, Param3 p3, Param4 p4) const { - return (m_Closure.GetClosureThis()->*(m_Closure.GetClosureMemPtr()))(p1, p2, p3, p4); } - // Implicit conversion to "bool" using the safe_bool idiom -private: - typedef struct SafeBoolStruct { - int a_data_pointer_to_this_is_0_on_buggy_compilers; - StaticFunctionPtr m_nonzero; - } UselessTypedef; - typedef StaticFunctionPtr SafeBoolStruct::*unspecified_bool_type; -public: - operator unspecified_bool_type() const { - return empty()? 0: &SafeBoolStruct::m_nonzero; + template + static delegate from(C & object) noexcept + { + return { &object, method_stub }; } - // necessary to allow ==0 to work despite the safe_bool idiom - inline bool operator==(StaticFunctionPtr funcptr) { - return m_Closure.IsEqualToStaticFuncPtr(funcptr); } - inline bool operator!=(StaticFunctionPtr funcptr) { - return !m_Closure.IsEqualToStaticFuncPtr(funcptr); } - inline bool operator ! () const { // Is it bound to anything? - return !m_Closure; } - inline bool empty() const { - return !m_Closure; } - void clear() { m_Closure.clear();} - // Conversion to and from the DelegateMemento storage class - const DelegateMemento & GetMemento() { return m_Closure; } - void SetMemento(const DelegateMemento &any) { m_Closure.CopyFrom(this, any); } - -private: // Invoker for static functions - RetType InvokeStaticFunction(Param1 p1, Param2 p2, Param3 p3, Param4 p4) const { - return (*(m_Closure.GetStaticFunction()))(p1, p2, p3, p4); } -}; -//N=5 -template -class FastDelegate5 { -private: - typedef typename detail::DefaultVoidToVoid::type DesiredRetType; - typedef DesiredRetType (*StaticFunctionPtr)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5); - typedef RetType (*UnvoidStaticFunctionPtr)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5); - typedef RetType (detail::GenericClass::*GenericMemFn)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5); - typedef detail::ClosurePtr ClosureType; - ClosureType m_Closure; -public: - // Typedefs to aid generic programming - typedef FastDelegate5 type; - - // Construction and comparison functions - FastDelegate5() { clear(); } - FastDelegate5(const FastDelegate5 &x) { - m_Closure.CopyFrom(this, x.m_Closure); } - void operator = (const FastDelegate5 &x) { - m_Closure.CopyFrom(this, x.m_Closure); } - bool operator ==(const FastDelegate5 &x) const { - return m_Closure.IsEqual(x.m_Closure); } - bool operator !=(const FastDelegate5 &x) const { - return !m_Closure.IsEqual(x.m_Closure); } - bool operator <(const FastDelegate5 &x) const { - return m_Closure.IsLess(x.m_Closure); } - bool operator >(const FastDelegate5 &x) const { - return x.m_Closure.IsLess(m_Closure); } - // Binding to non-const member functions - template < class X, class Y > - FastDelegate5(Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5) ) { - m_Closure.bindmemfunc(detail::implicit_cast(pthis), function_to_bind); } - template < class X, class Y > - inline void bind(Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5)) { - m_Closure.bindmemfunc(detail::implicit_cast(pthis), function_to_bind); } - // Binding to const member functions. - template < class X, class Y > - FastDelegate5(const Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5) const) { - m_Closure.bindconstmemfunc(detail::implicit_cast(pthis), function_to_bind); } - template < class X, class Y > - inline void bind(const Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5) const) { - m_Closure.bindconstmemfunc(detail::implicit_cast(pthis), function_to_bind); } - // Static functions. We convert them into a member function call. - // This constructor also provides implicit conversion - FastDelegate5(DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5) ) { - bind(function_to_bind); } - // for efficiency, prevent creation of a temporary - void operator = (DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5) ) { - bind(function_to_bind); } - inline void bind(DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5)) { - m_Closure.bindstaticfunc(this, &FastDelegate5::InvokeStaticFunction, - function_to_bind); } - // Invoke the delegate - RetType operator() (Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5) const { - return (m_Closure.GetClosureThis()->*(m_Closure.GetClosureMemPtr()))(p1, p2, p3, p4, p5); } - // Implicit conversion to "bool" using the safe_bool idiom -private: - typedef struct SafeBoolStruct { - int a_data_pointer_to_this_is_0_on_buggy_compilers; - StaticFunctionPtr m_nonzero; - } UselessTypedef; - typedef StaticFunctionPtr SafeBoolStruct::*unspecified_bool_type; -public: - operator unspecified_bool_type() const { - return empty()? 0: &SafeBoolStruct::m_nonzero; + template + static delegate from(C const & object) noexcept + { + return { const_cast(&object), const_method_stub }; } - // necessary to allow ==0 to work despite the safe_bool idiom - inline bool operator==(StaticFunctionPtr funcptr) { - return m_Closure.IsEqualToStaticFuncPtr(funcptr); } - inline bool operator!=(StaticFunctionPtr funcptr) { - return !m_Closure.IsEqualToStaticFuncPtr(funcptr); } - inline bool operator ! () const { // Is it bound to anything? - return !m_Closure; } - inline bool empty() const { - return !m_Closure; } - void clear() { m_Closure.clear();} - // Conversion to and from the DelegateMemento storage class - const DelegateMemento & GetMemento() { return m_Closure; } - void SetMemento(const DelegateMemento &any) { m_Closure.CopyFrom(this, any); } - -private: // Invoker for static functions - RetType InvokeStaticFunction(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5) const { - return (*(m_Closure.GetStaticFunction()))(p1, p2, p3, p4, p5); } -}; -//N=6 -template -class FastDelegate6 { -private: - typedef typename detail::DefaultVoidToVoid::type DesiredRetType; - typedef DesiredRetType (*StaticFunctionPtr)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6); - typedef RetType (*UnvoidStaticFunctionPtr)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6); - typedef RetType (detail::GenericClass::*GenericMemFn)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6); - typedef detail::ClosurePtr ClosureType; - ClosureType m_Closure; -public: - // Typedefs to aid generic programming - typedef FastDelegate6 type; - - // Construction and comparison functions - FastDelegate6() { clear(); } - FastDelegate6(const FastDelegate6 &x) { - m_Closure.CopyFrom(this, x.m_Closure); } - void operator = (const FastDelegate6 &x) { - m_Closure.CopyFrom(this, x.m_Closure); } - bool operator ==(const FastDelegate6 &x) const { - return m_Closure.IsEqual(x.m_Closure); } - bool operator !=(const FastDelegate6 &x) const { - return !m_Closure.IsEqual(x.m_Closure); } - bool operator <(const FastDelegate6 &x) const { - return m_Closure.IsLess(x.m_Closure); } - bool operator >(const FastDelegate6 &x) const { - return x.m_Closure.IsLess(m_Closure); } - // Binding to non-const member functions - template < class X, class Y > - FastDelegate6(Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6) ) { - m_Closure.bindmemfunc(detail::implicit_cast(pthis), function_to_bind); } - template < class X, class Y > - inline void bind(Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6)) { - m_Closure.bindmemfunc(detail::implicit_cast(pthis), function_to_bind); } - // Binding to const member functions. - template < class X, class Y > - FastDelegate6(const Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6) const) { - m_Closure.bindconstmemfunc(detail::implicit_cast(pthis), function_to_bind); } - template < class X, class Y > - inline void bind(const Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6) const) { - m_Closure.bindconstmemfunc(detail::implicit_cast(pthis), function_to_bind); } - // Static functions. We convert them into a member function call. - // This constructor also provides implicit conversion - FastDelegate6(DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6) ) { - bind(function_to_bind); } - // for efficiency, prevent creation of a temporary - void operator = (DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6) ) { - bind(function_to_bind); } - inline void bind(DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6)) { - m_Closure.bindstaticfunc(this, &FastDelegate6::InvokeStaticFunction, - function_to_bind); } - // Invoke the delegate - RetType operator() (Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6) const { - return (m_Closure.GetClosureThis()->*(m_Closure.GetClosureMemPtr()))(p1, p2, p3, p4, p5, p6); } - // Implicit conversion to "bool" using the safe_bool idiom -private: - typedef struct SafeBoolStruct { - int a_data_pointer_to_this_is_0_on_buggy_compilers; - StaticFunctionPtr m_nonzero; - } UselessTypedef; - typedef StaticFunctionPtr SafeBoolStruct::*unspecified_bool_type; -public: - operator unspecified_bool_type() const { - return empty()? 0: &SafeBoolStruct::m_nonzero; + template + static delegate from(T && f) + { + return ::std::forward(f); } - // necessary to allow ==0 to work despite the safe_bool idiom - inline bool operator==(StaticFunctionPtr funcptr) { - return m_Closure.IsEqualToStaticFuncPtr(funcptr); } - inline bool operator!=(StaticFunctionPtr funcptr) { - return !m_Closure.IsEqualToStaticFuncPtr(funcptr); } - inline bool operator ! () const { // Is it bound to anything? - return !m_Closure; } - inline bool empty() const { - return !m_Closure; } - void clear() { m_Closure.clear();} - // Conversion to and from the DelegateMemento storage class - const DelegateMemento & GetMemento() { return m_Closure; } - void SetMemento(const DelegateMemento &any) { m_Closure.CopyFrom(this, any); } - -private: // Invoker for static functions - RetType InvokeStaticFunction(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6) const { - return (*(m_Closure.GetStaticFunction()))(p1, p2, p3, p4, p5, p6); } -}; -//N=7 -template -class FastDelegate7 { -private: - typedef typename detail::DefaultVoidToVoid::type DesiredRetType; - typedef DesiredRetType (*StaticFunctionPtr)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7); - typedef RetType (*UnvoidStaticFunctionPtr)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7); - typedef RetType (detail::GenericClass::*GenericMemFn)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7); - typedef detail::ClosurePtr ClosureType; - ClosureType m_Closure; -public: - // Typedefs to aid generic programming - typedef FastDelegate7 type; - - // Construction and comparison functions - FastDelegate7() { clear(); } - FastDelegate7(const FastDelegate7 &x) { - m_Closure.CopyFrom(this, x.m_Closure); } - void operator = (const FastDelegate7 &x) { - m_Closure.CopyFrom(this, x.m_Closure); } - bool operator ==(const FastDelegate7 &x) const { - return m_Closure.IsEqual(x.m_Closure); } - bool operator !=(const FastDelegate7 &x) const { - return !m_Closure.IsEqual(x.m_Closure); } - bool operator <(const FastDelegate7 &x) const { - return m_Closure.IsLess(x.m_Closure); } - bool operator >(const FastDelegate7 &x) const { - return x.m_Closure.IsLess(m_Closure); } - // Binding to non-const member functions - template < class X, class Y > - FastDelegate7(Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7) ) { - m_Closure.bindmemfunc(detail::implicit_cast(pthis), function_to_bind); } - template < class X, class Y > - inline void bind(Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7)) { - m_Closure.bindmemfunc(detail::implicit_cast(pthis), function_to_bind); } - // Binding to const member functions. - template < class X, class Y > - FastDelegate7(const Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7) const) { - m_Closure.bindconstmemfunc(detail::implicit_cast(pthis), function_to_bind); } - template < class X, class Y > - inline void bind(const Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7) const) { - m_Closure.bindconstmemfunc(detail::implicit_cast(pthis), function_to_bind); } - // Static functions. We convert them into a member function call. - // This constructor also provides implicit conversion - FastDelegate7(DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7) ) { - bind(function_to_bind); } - // for efficiency, prevent creation of a temporary - void operator = (DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7) ) { - bind(function_to_bind); } - inline void bind(DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7)) { - m_Closure.bindstaticfunc(this, &FastDelegate7::InvokeStaticFunction, - function_to_bind); } - // Invoke the delegate - RetType operator() (Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7) const { - return (m_Closure.GetClosureThis()->*(m_Closure.GetClosureMemPtr()))(p1, p2, p3, p4, p5, p6, p7); } - // Implicit conversion to "bool" using the safe_bool idiom -private: - typedef struct SafeBoolStruct { - int a_data_pointer_to_this_is_0_on_buggy_compilers; - StaticFunctionPtr m_nonzero; - } UselessTypedef; - typedef StaticFunctionPtr SafeBoolStruct::*unspecified_bool_type; -public: - operator unspecified_bool_type() const { - return empty()? 0: &SafeBoolStruct::m_nonzero; + static delegate from(R(* const function_ptr)(A...)) + { + return function_ptr; } - // necessary to allow ==0 to work despite the safe_bool idiom - inline bool operator==(StaticFunctionPtr funcptr) { - return m_Closure.IsEqualToStaticFuncPtr(funcptr); } - inline bool operator!=(StaticFunctionPtr funcptr) { - return !m_Closure.IsEqualToStaticFuncPtr(funcptr); } - inline bool operator ! () const { // Is it bound to anything? - return !m_Closure; } - inline bool empty() const { - return !m_Closure; } - void clear() { m_Closure.clear();} - // Conversion to and from the DelegateMemento storage class - const DelegateMemento & GetMemento() { return m_Closure; } - void SetMemento(const DelegateMemento &any) { m_Closure.CopyFrom(this, any); } - -private: // Invoker for static functions - RetType InvokeStaticFunction(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7) const { - return (*(m_Closure.GetStaticFunction()))(p1, p2, p3, p4, p5, p6, p7); } -}; -//N=8 -template -class FastDelegate8 { -private: - typedef typename detail::DefaultVoidToVoid::type DesiredRetType; - typedef DesiredRetType (*StaticFunctionPtr)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8); - typedef RetType (*UnvoidStaticFunctionPtr)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8); - typedef RetType (detail::GenericClass::*GenericMemFn)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8); - typedef detail::ClosurePtr ClosureType; - ClosureType m_Closure; -public: - // Typedefs to aid generic programming - typedef FastDelegate8 type; - - // Construction and comparison functions - FastDelegate8() { clear(); } - FastDelegate8(const FastDelegate8 &x) { - m_Closure.CopyFrom(this, x.m_Closure); } - void operator = (const FastDelegate8 &x) { - m_Closure.CopyFrom(this, x.m_Closure); } - bool operator ==(const FastDelegate8 &x) const { - return m_Closure.IsEqual(x.m_Closure); } - bool operator !=(const FastDelegate8 &x) const { - return !m_Closure.IsEqual(x.m_Closure); } - bool operator <(const FastDelegate8 &x) const { - return m_Closure.IsLess(x.m_Closure); } - bool operator >(const FastDelegate8 &x) const { - return x.m_Closure.IsLess(m_Closure); } - // Binding to non-const member functions - template < class X, class Y > - FastDelegate8(Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8) ) { - m_Closure.bindmemfunc(detail::implicit_cast(pthis), function_to_bind); } - template < class X, class Y > - inline void bind(Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8)) { - m_Closure.bindmemfunc(detail::implicit_cast(pthis), function_to_bind); } - // Binding to const member functions. - template < class X, class Y > - FastDelegate8(const Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8) const) { - m_Closure.bindconstmemfunc(detail::implicit_cast(pthis), function_to_bind); } - template < class X, class Y > - inline void bind(const Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8) const) { - m_Closure.bindconstmemfunc(detail::implicit_cast(pthis), function_to_bind); } - // Static functions. We convert them into a member function call. - // This constructor also provides implicit conversion - FastDelegate8(DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8) ) { - bind(function_to_bind); } - // for efficiency, prevent creation of a temporary - void operator = (DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8) ) { - bind(function_to_bind); } - inline void bind(DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8)) { - m_Closure.bindstaticfunc(this, &FastDelegate8::InvokeStaticFunction, - function_to_bind); } - // Invoke the delegate - RetType operator() (Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8) const { - return (m_Closure.GetClosureThis()->*(m_Closure.GetClosureMemPtr()))(p1, p2, p3, p4, p5, p6, p7, p8); } - // Implicit conversion to "bool" using the safe_bool idiom -private: - typedef struct SafeBoolStruct { - int a_data_pointer_to_this_is_0_on_buggy_compilers; - StaticFunctionPtr m_nonzero; - } UselessTypedef; - typedef StaticFunctionPtr SafeBoolStruct::*unspecified_bool_type; -public: - operator unspecified_bool_type() const { - return empty()? 0: &SafeBoolStruct::m_nonzero; - } - // necessary to allow ==0 to work despite the safe_bool idiom - inline bool operator==(StaticFunctionPtr funcptr) { - return m_Closure.IsEqualToStaticFuncPtr(funcptr); } - inline bool operator!=(StaticFunctionPtr funcptr) { - return !m_Closure.IsEqualToStaticFuncPtr(funcptr); } - inline bool operator ! () const { // Is it bound to anything? - return !m_Closure; } - inline bool empty() const { - return !m_Closure; } - void clear() { m_Closure.clear();} - // Conversion to and from the DelegateMemento storage class - const DelegateMemento & GetMemento() { return m_Closure; } - void SetMemento(const DelegateMemento &any) { m_Closure.CopyFrom(this, any); } - -private: // Invoker for static functions - RetType InvokeStaticFunction(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8) const { - return (*(m_Closure.GetStaticFunction()))(p1, p2, p3, p4, p5, p6, p7, p8); } -}; + template + using member_pair = ::std::pair; + template + using const_member_pair = ::std::pair; -//////////////////////////////////////////////////////////////////////////////// -// Fast Delegates, part 4: -// -// FastDelegate<> class (Original author: Jody Hagins) -// Allows boost::function style syntax like: -// FastDelegate< double (int, long) > -// instead of: -// FastDelegate2< int, long, double > -// -//////////////////////////////////////////////////////////////////////////////// - -#ifdef FASTDELEGATE_ALLOW_FUNCTION_TYPE_SYNTAX - -// Declare FastDelegate as a class template. It will be specialized -// later for all number of arguments. -template -class FastDelegate; - -//N=0 -// Specialization to allow use of -// FastDelegate< R ( ) > -// instead of -// FastDelegate0 < R > -template -class FastDelegate< R ( ) > - // Inherit from FastDelegate0 so that it can be treated just like a FastDelegate0 - : public FastDelegate0 < R > -{ -public: - // Make using the base type a bit easier via typedef. - typedef FastDelegate0 < R > BaseType; - - // Allow users access to the specific type of this delegate. - typedef FastDelegate SelfType; - - // Mimic the base class constructors. - FastDelegate() : BaseType() { } - - template < class X, class Y > - FastDelegate(Y * pthis, - R (X::* function_to_bind)( )) - : BaseType(pthis, function_to_bind) { } - - template < class X, class Y > - FastDelegate(const Y *pthis, - R (X::* function_to_bind)( ) const) - : BaseType(pthis, function_to_bind) - { } - - FastDelegate(R (*function_to_bind)( )) - : BaseType(function_to_bind) { } - void operator = (const BaseType &x) { - *static_cast(this) = x; } -}; + template + static delegate from(C * const object_ptr, R(C::* const method_ptr)(A...)) + { + return member_pair(object_ptr, method_ptr); + } -//N=1 -// Specialization to allow use of -// FastDelegate< R ( Param1 ) > -// instead of -// FastDelegate1 < Param1, R > -template -class FastDelegate< R ( Param1 ) > - // Inherit from FastDelegate1 so that it can be treated just like a FastDelegate1 - : public FastDelegate1 < Param1, R > -{ -public: - // Make using the base type a bit easier via typedef. - typedef FastDelegate1 < Param1, R > BaseType; - - // Allow users access to the specific type of this delegate. - typedef FastDelegate SelfType; - - // Mimic the base class constructors. - FastDelegate() : BaseType() { } - - template < class X, class Y > - FastDelegate(Y * pthis, - R (X::* function_to_bind)( Param1 p1 )) - : BaseType(pthis, function_to_bind) { } - - template < class X, class Y > - FastDelegate(const Y *pthis, - R (X::* function_to_bind)( Param1 p1 ) const) - : BaseType(pthis, function_to_bind) - { } - - FastDelegate(R (*function_to_bind)( Param1 p1 )) - : BaseType(function_to_bind) { } - void operator = (const BaseType &x) { - *static_cast(this) = x; } -}; + template + static delegate from(C const * const object_ptr, R(C::* const method_ptr)(A...) const) + { + return const_member_pair(object_ptr, method_ptr); + } -//N=2 -// Specialization to allow use of -// FastDelegate< R ( Param1, Param2 ) > -// instead of -// FastDelegate2 < Param1, Param2, R > -template -class FastDelegate< R ( Param1, Param2 ) > - // Inherit from FastDelegate2 so that it can be treated just like a FastDelegate2 - : public FastDelegate2 < Param1, Param2, R > -{ -public: - // Make using the base type a bit easier via typedef. - typedef FastDelegate2 < Param1, Param2, R > BaseType; - - // Allow users access to the specific type of this delegate. - typedef FastDelegate SelfType; - - // Mimic the base class constructors. - FastDelegate() : BaseType() { } - - template < class X, class Y > - FastDelegate(Y * pthis, - R (X::* function_to_bind)( Param1 p1, Param2 p2 )) - : BaseType(pthis, function_to_bind) { } - - template < class X, class Y > - FastDelegate(const Y *pthis, - R (X::* function_to_bind)( Param1 p1, Param2 p2 ) const) - : BaseType(pthis, function_to_bind) - { } - - FastDelegate(R (*function_to_bind)( Param1 p1, Param2 p2 )) - : BaseType(function_to_bind) { } - void operator = (const BaseType &x) { - *static_cast(this) = x; } -}; + template + static delegate from(C & object, R(C::* const method_ptr)(A...)) + { + return member_pair(&object, method_ptr); + } -//N=3 -// Specialization to allow use of -// FastDelegate< R ( Param1, Param2, Param3 ) > -// instead of -// FastDelegate3 < Param1, Param2, Param3, R > -template -class FastDelegate< R ( Param1, Param2, Param3 ) > - // Inherit from FastDelegate3 so that it can be treated just like a FastDelegate3 - : public FastDelegate3 < Param1, Param2, Param3, R > -{ -public: - // Make using the base type a bit easier via typedef. - typedef FastDelegate3 < Param1, Param2, Param3, R > BaseType; - - // Allow users access to the specific type of this delegate. - typedef FastDelegate SelfType; - - // Mimic the base class constructors. - FastDelegate() : BaseType() { } - - template < class X, class Y > - FastDelegate(Y * pthis, - R (X::* function_to_bind)( Param1 p1, Param2 p2, Param3 p3 )) - : BaseType(pthis, function_to_bind) { } - - template < class X, class Y > - FastDelegate(const Y *pthis, - R (X::* function_to_bind)( Param1 p1, Param2 p2, Param3 p3 ) const) - : BaseType(pthis, function_to_bind) - { } - - FastDelegate(R (*function_to_bind)( Param1 p1, Param2 p2, Param3 p3 )) - : BaseType(function_to_bind) { } - void operator = (const BaseType &x) { - *static_cast(this) = x; } -}; + template + static delegate from(C const & object, R(C::* const method_ptr)(A...) const) + { + return const_member_pair(&object, method_ptr); + } -//N=4 -// Specialization to allow use of -// FastDelegate< R ( Param1, Param2, Param3, Param4 ) > -// instead of -// FastDelegate4 < Param1, Param2, Param3, Param4, R > -template -class FastDelegate< R ( Param1, Param2, Param3, Param4 ) > - // Inherit from FastDelegate4 so that it can be treated just like a FastDelegate4 - : public FastDelegate4 < Param1, Param2, Param3, Param4, R > -{ -public: - // Make using the base type a bit easier via typedef. - typedef FastDelegate4 < Param1, Param2, Param3, Param4, R > BaseType; - - // Allow users access to the specific type of this delegate. - typedef FastDelegate SelfType; - - // Mimic the base class constructors. - FastDelegate() : BaseType() { } - - template < class X, class Y > - FastDelegate(Y * pthis, - R (X::* function_to_bind)( Param1 p1, Param2 p2, Param3 p3, Param4 p4 )) - : BaseType(pthis, function_to_bind) { } - - template < class X, class Y > - FastDelegate(const Y *pthis, - R (X::* function_to_bind)( Param1 p1, Param2 p2, Param3 p3, Param4 p4 ) const) - : BaseType(pthis, function_to_bind) - { } - - FastDelegate(R (*function_to_bind)( Param1 p1, Param2 p2, Param3 p3, Param4 p4 )) - : BaseType(function_to_bind) { } - void operator = (const BaseType &x) { - *static_cast(this) = x; } -}; + void reset(void) + { + stub_ptr_ = nullptr; + store_.reset(); + } -//N=5 -// Specialization to allow use of -// FastDelegate< R ( Param1, Param2, Param3, Param4, Param5 ) > -// instead of -// FastDelegate5 < Param1, Param2, Param3, Param4, Param5, R > -template -class FastDelegate< R ( Param1, Param2, Param3, Param4, Param5 ) > - // Inherit from FastDelegate5 so that it can be treated just like a FastDelegate5 - : public FastDelegate5 < Param1, Param2, Param3, Param4, Param5, R > -{ -public: - // Make using the base type a bit easier via typedef. - typedef FastDelegate5 < Param1, Param2, Param3, Param4, Param5, R > BaseType; - - // Allow users access to the specific type of this delegate. - typedef FastDelegate SelfType; - - // Mimic the base class constructors. - FastDelegate() : BaseType() { } - - template < class X, class Y > - FastDelegate(Y * pthis, - R (X::* function_to_bind)( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5 )) - : BaseType(pthis, function_to_bind) { } - - template < class X, class Y > - FastDelegate(const Y *pthis, - R (X::* function_to_bind)( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5 ) const) - : BaseType(pthis, function_to_bind) - { } - - FastDelegate(R (*function_to_bind)( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5 )) - : BaseType(function_to_bind) { } - void operator = (const BaseType &x) { - *static_cast(this) = x; } -}; + void reset_stub(void) noexcept { stub_ptr_ = nullptr; } -//N=6 -// Specialization to allow use of -// FastDelegate< R ( Param1, Param2, Param3, Param4, Param5, Param6 ) > -// instead of -// FastDelegate6 < Param1, Param2, Param3, Param4, Param5, Param6, R > -template -class FastDelegate< R ( Param1, Param2, Param3, Param4, Param5, Param6 ) > - // Inherit from FastDelegate6 so that it can be treated just like a FastDelegate6 - : public FastDelegate6 < Param1, Param2, Param3, Param4, Param5, Param6, R > -{ -public: - // Make using the base type a bit easier via typedef. - typedef FastDelegate6 < Param1, Param2, Param3, Param4, Param5, Param6, R > BaseType; - - // Allow users access to the specific type of this delegate. - typedef FastDelegate SelfType; - - // Mimic the base class constructors. - FastDelegate() : BaseType() { } - - template < class X, class Y > - FastDelegate(Y * pthis, - R (X::* function_to_bind)( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6 )) - : BaseType(pthis, function_to_bind) { } - - template < class X, class Y > - FastDelegate(const Y *pthis, - R (X::* function_to_bind)( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6 ) const) - : BaseType(pthis, function_to_bind) - { } - - FastDelegate(R (*function_to_bind)( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6 )) - : BaseType(function_to_bind) { } - void operator = (const BaseType &x) { - *static_cast(this) = x; } -}; + void swap(delegate & other) noexcept { ::std::swap(*this, other); } -//N=7 -// Specialization to allow use of -// FastDelegate< R ( Param1, Param2, Param3, Param4, Param5, Param6, Param7 ) > -// instead of -// FastDelegate7 < Param1, Param2, Param3, Param4, Param5, Param6, Param7, R > -template -class FastDelegate< R ( Param1, Param2, Param3, Param4, Param5, Param6, Param7 ) > - // Inherit from FastDelegate7 so that it can be treated just like a FastDelegate7 - : public FastDelegate7 < Param1, Param2, Param3, Param4, Param5, Param6, Param7, R > -{ -public: - // Make using the base type a bit easier via typedef. - typedef FastDelegate7 < Param1, Param2, Param3, Param4, Param5, Param6, Param7, R > BaseType; - - // Allow users access to the specific type of this delegate. - typedef FastDelegate SelfType; - - // Mimic the base class constructors. - FastDelegate() : BaseType() { } - - template < class X, class Y > - FastDelegate(Y * pthis, - R (X::* function_to_bind)( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7 )) - : BaseType(pthis, function_to_bind) { } - - template < class X, class Y > - FastDelegate(const Y *pthis, - R (X::* function_to_bind)( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7 ) const) - : BaseType(pthis, function_to_bind) - { } - - FastDelegate(R (*function_to_bind)( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7 )) - : BaseType(function_to_bind) { } - void operator = (const BaseType &x) { - *static_cast(this) = x; } -}; + bool operator==(delegate const & rhs) const noexcept + { + // comparison between functor and non-functor is left as undefined at the moment. + if (store_size_ && rhs.store_size_) // both functors + return (std::memcmp(store_.get(), rhs.store_.get(), store_size_) == 0) && (stub_ptr_ == rhs.stub_ptr_); + return (object_ptr_ == rhs.object_ptr_) && (stub_ptr_ == rhs.stub_ptr_); + } -//N=8 -// Specialization to allow use of -// FastDelegate< R ( Param1, Param2, Param3, Param4, Param5, Param6, Param7, Param8 ) > -// instead of -// FastDelegate8 < Param1, Param2, Param3, Param4, Param5, Param6, Param7, Param8, R > -template -class FastDelegate< R ( Param1, Param2, Param3, Param4, Param5, Param6, Param7, Param8 ) > - // Inherit from FastDelegate8 so that it can be treated just like a FastDelegate8 - : public FastDelegate8 < Param1, Param2, Param3, Param4, Param5, Param6, Param7, Param8, R > -{ -public: - // Make using the base type a bit easier via typedef. - typedef FastDelegate8 < Param1, Param2, Param3, Param4, Param5, Param6, Param7, Param8, R > BaseType; - - // Allow users access to the specific type of this delegate. - typedef FastDelegate SelfType; - - // Mimic the base class constructors. - FastDelegate() : BaseType() { } - - template < class X, class Y > - FastDelegate(Y * pthis, - R (X::* function_to_bind)( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8 )) - : BaseType(pthis, function_to_bind) { } - - template < class X, class Y > - FastDelegate(const Y *pthis, - R (X::* function_to_bind)( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8 ) const) - : BaseType(pthis, function_to_bind) - { } - - FastDelegate(R (*function_to_bind)( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8 )) - : BaseType(function_to_bind) { } - void operator = (const BaseType &x) { - *static_cast(this) = x; } -}; + bool operator!=(delegate const & rhs) const noexcept + { + return !operator==(rhs); + } + bool operator<(delegate const & rhs) const noexcept + { + return (object_ptr_ < rhs.object_ptr_) || + ((object_ptr_ == rhs.object_ptr_) && (stub_ptr_ < rhs.stub_ptr_)); + } -#endif //FASTDELEGATE_ALLOW_FUNCTION_TYPE_SYNTAX - -//////////////////////////////////////////////////////////////////////////////// -// Fast Delegates, part 5: -// -// MakeDelegate() helper function -// -// MakeDelegate(&x, &X::func) returns a fastdelegate of the type -// necessary for calling x.func() with the correct number of arguments. -// This makes it possible to eliminate many typedefs from user code. -// -//////////////////////////////////////////////////////////////////////////////// - -// Also declare overloads of a MakeDelegate() global function to -// reduce the need for typedefs. -// We need seperate overloads for const and non-const member functions. -// Also, because of the weird rule about the class of derived member function pointers, -// implicit downcasts may need to be applied later to the 'this' pointer. -// That's why two classes (X and Y) appear in the definitions. Y must be implicitly -// castable to X. - -// Workaround for VC6. VC6 needs void return types converted into DefaultVoid. -// GCC 3.2 and later won't compile this unless it's preceded by 'typename', -// but VC6 doesn't allow 'typename' in this context. -// So, I have to use a macro. - -#ifdef FASTDLGT_VC6 -#define FASTDLGT_RETTYPE detail::VoidToDefaultVoid::type -#else -#define FASTDLGT_RETTYPE RetType -#endif - -//N=0 -template -FastDelegate0 MakeDelegate(Y* x, RetType (X::*func)()) { - return FastDelegate0(x, func); -} + bool operator==(::std::nullptr_t const) const noexcept + { + return !stub_ptr_; + } -template -FastDelegate0 MakeDelegate(Y* x, RetType (X::*func)() const) { - return FastDelegate0(x, func); -} + bool operator!=(::std::nullptr_t const) const noexcept + { + return stub_ptr_; + } -//N=1 -template -FastDelegate1 MakeDelegate(Y* x, RetType (X::*func)(Param1 p1)) { - return FastDelegate1(x, func); -} + explicit operator bool() const noexcept + { + return stub_ptr_; + } -template -FastDelegate1 MakeDelegate(Y* x, RetType (X::*func)(Param1 p1) const) { - return FastDelegate1(x, func); -} + R operator()(A... args) const + { + // assert(stub_ptr); + return stub_ptr_(object_ptr_, ::std::forward(args)...); + } -//N=2 -template -FastDelegate2 MakeDelegate(Y* x, RetType (X::*func)(Param1 p1, Param2 p2)) { - return FastDelegate2(x, func); -} +private: + friend struct ::std::hash; -template -FastDelegate2 MakeDelegate(Y* x, RetType (X::*func)(Param1 p1, Param2 p2) const) { - return FastDelegate2(x, func); -} + using deleter_type = void (*)(void *); -//N=3 -template -FastDelegate3 MakeDelegate(Y* x, RetType (X::*func)(Param1 p1, Param2 p2, Param3 p3)) { - return FastDelegate3(x, func); -} + void * object_ptr_ = nullptr; + stub_ptr_type stub_ptr_ {}; -template -FastDelegate3 MakeDelegate(Y* x, RetType (X::*func)(Param1 p1, Param2 p2, Param3 p3) const) { - return FastDelegate3(x, func); -} + deleter_type deleter_ = nullptr; -//N=4 -template -FastDelegate4 MakeDelegate(Y* x, RetType (X::*func)(Param1 p1, Param2 p2, Param3 p3, Param4 p4)) { - return FastDelegate4(x, func); -} + ::std::shared_ptr store_ = nullptr; + ::std::size_t store_size_ = 0; -template -FastDelegate4 MakeDelegate(Y* x, RetType (X::*func)(Param1 p1, Param2 p2, Param3 p3, Param4 p4) const) { - return FastDelegate4(x, func); -} + template + static void functor_deleter(void * const p) + { + static_cast(p)->~T(); + operator delete(p); + } -//N=5 -template -FastDelegate5 MakeDelegate(Y* x, RetType (X::*func)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5)) { - return FastDelegate5(x, func); -} + template + static void deleter_stub(void * const p) + { + static_cast(p)->~T(); + } -template -FastDelegate5 MakeDelegate(Y* x, RetType (X::*func)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5) const) { - return FastDelegate5(x, func); -} + template + static R function_stub(void * const, A && ... args) + { + return function_ptr(::std::forward(args)...); + } -//N=6 -template -FastDelegate6 MakeDelegate(Y* x, RetType (X::*func)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6)) { - return FastDelegate6(x, func); -} + template + static R method_stub(void * const object_ptr, A && ... args) + { + return (static_cast(object_ptr)->*method_ptr)(::std::forward(args)...); + } -template -FastDelegate6 MakeDelegate(Y* x, RetType (X::*func)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6) const) { - return FastDelegate6(x, func); -} + template + static R const_method_stub(void * const object_ptr, A && ... args) + { + return (static_cast(object_ptr)->*method_ptr)(::std::forward(args)...); + } -//N=7 -template -FastDelegate7 MakeDelegate(Y* x, RetType (X::*func)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7)) { - return FastDelegate7(x, func); -} + template + struct is_member_pair : ::std::false_type { }; -template -FastDelegate7 MakeDelegate(Y* x, RetType (X::*func)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7) const) { - return FastDelegate7(x, func); -} + template + struct is_member_pair< ::std::pair > : ::std::true_type {}; -//N=8 -template -FastDelegate8 MakeDelegate(Y* x, RetType (X::*func)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8)) { - return FastDelegate8(x, func); -} + template + struct is_const_member_pair : ::std::false_type { }; -template -FastDelegate8 MakeDelegate(Y* x, RetType (X::*func)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8) const) { - return FastDelegate8(x, func); -} + template + struct is_const_member_pair< ::std::pair > : ::std::true_type {}; + template + static typename ::std::enable_if::value || is_const_member_pair::value), R>::type + functor_stub(void * const object_ptr, A && ... args) + { + return (*static_cast(object_ptr))(::std::forward(args)...); + } - // clean up after ourselves... -#undef FASTDLGT_RETTYPE + template + static typename ::std::enable_if::value || is_const_member_pair::value, R>::type + functor_stub(void * const object_ptr, A && ... args) + { + return (static_cast(object_ptr)->first->*static_cast(object_ptr)->second)(::std::forward(args)...); + } +}; -} // namespace fastdelegate +} -#endif // !defined(FASTDELEGATE_H) +namespace std +{ +template +struct hash<::fastdelegate::delegate > +{ + size_t operator()(::fastdelegate::delegate const & d) const noexcept + { + auto const seed(hash()(d.object_ptr_)); + return hash()(d.stub_ptr_) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + } +}; +} +#endif // SRDELEGATE_HPP diff --git a/libs/Common/FastDelegateBind.h b/libs/Common/FastDelegateBind.h deleted file mode 100644 index af5bc8d9e..000000000 --- a/libs/Common/FastDelegateBind.h +++ /dev/null @@ -1,240 +0,0 @@ -// FastDelegateBind.h -// Helper file for FastDelegates. Provides bind() function, enabling -// FastDelegates to be rapidly compared to programs using boost::function and boost::bind. -// -// Documentation is found at http://www.codeproject.com/cpp/FastDelegate.asp -// -// Original author: Jody Hagins. -// Minor changes by Don Clugston. -// -// Warning: The arguments to 'bind' are ignored! No actual binding is performed. -// The behaviour is equivalent to boost::bind only when the basic placeholder -// arguments _1, _2, _3, etc are used in order. -// -// HISTORY: -// 1.4 Dec 2004. Initial release as part of FastDelegate 1.4. - - -#ifndef FASTDELEGATEBIND_H -#define FASTDELEGATEBIND_H - -//////////////////////////////////////////////////////////////////////////////// -// FastDelegate bind() -// -// bind() helper function for boost compatibility. -// (Original author: Jody Hagins). -// -// Add another helper, so FastDelegate can be a dropin replacement -// for boost::bind (in a fair number of cases). -// Note the elipses, because boost::bind() takes place holders -// but FastDelegate does not care about them. Getting the place holder -// mechanism to work, and play well with boost is a bit tricky, so -// we do the "easy" thing... -// Assume we have the following code... -// using boost::bind; -// bind(&Foo:func, &foo, _1, _2); -// we should be able to replace the "using" with... -// using fastdelegate::bind; -// and everything should work fine... -//////////////////////////////////////////////////////////////////////////////// - -#ifdef FASTDELEGATE_ALLOW_FUNCTION_TYPE_SYNTAX - -namespace fastdelegate { - -//N=0 -template -FastDelegate< RetType ( ) > -bind( - RetType (X::*func)( ), - Y * y, - ...) -{ - return FastDelegate< RetType ( ) >(y, func); -} - -template -FastDelegate< RetType ( ) > -bind( - RetType (X::*func)( ) const, - Y * y, - ...) -{ - return FastDelegate< RetType ( ) >(y, func); -} - -//N=1 -template -FastDelegate< RetType ( Param1 p1 ) > -bind( - RetType (X::*func)( Param1 p1 ), - Y * y, - ...) -{ - return FastDelegate< RetType ( Param1 p1 ) >(y, func); -} - -template -FastDelegate< RetType ( Param1 p1 ) > -bind( - RetType (X::*func)( Param1 p1 ) const, - Y * y, - ...) -{ - return FastDelegate< RetType ( Param1 p1 ) >(y, func); -} - -//N=2 -template -FastDelegate< RetType ( Param1 p1, Param2 p2 ) > -bind( - RetType (X::*func)( Param1 p1, Param2 p2 ), - Y * y, - ...) -{ - return FastDelegate< RetType ( Param1 p1, Param2 p2 ) >(y, func); -} - -template -FastDelegate< RetType ( Param1 p1, Param2 p2 ) > -bind( - RetType (X::*func)( Param1 p1, Param2 p2 ) const, - Y * y, - ...) -{ - return FastDelegate< RetType ( Param1 p1, Param2 p2 ) >(y, func); -} - -//N=3 -template -FastDelegate< RetType ( Param1 p1, Param2 p2, Param3 p3 ) > -bind( - RetType (X::*func)( Param1 p1, Param2 p2, Param3 p3 ), - Y * y, - ...) -{ - return FastDelegate< RetType ( Param1 p1, Param2 p2, Param3 p3 ) >(y, func); -} - -template -FastDelegate< RetType ( Param1 p1, Param2 p2, Param3 p3 ) > -bind( - RetType (X::*func)( Param1 p1, Param2 p2, Param3 p3 ) const, - Y * y, - ...) -{ - return FastDelegate< RetType ( Param1 p1, Param2 p2, Param3 p3 ) >(y, func); -} - -//N=4 -template -FastDelegate< RetType ( Param1 p1, Param2 p2, Param3 p3, Param4 p4 ) > -bind( - RetType (X::*func)( Param1 p1, Param2 p2, Param3 p3, Param4 p4 ), - Y * y, - ...) -{ - return FastDelegate< RetType ( Param1 p1, Param2 p2, Param3 p3, Param4 p4 ) >(y, func); -} - -template -FastDelegate< RetType ( Param1 p1, Param2 p2, Param3 p3, Param4 p4 ) > -bind( - RetType (X::*func)( Param1 p1, Param2 p2, Param3 p3, Param4 p4 ) const, - Y * y, - ...) -{ - return FastDelegate< RetType ( Param1 p1, Param2 p2, Param3 p3, Param4 p4 ) >(y, func); -} - -//N=5 -template -FastDelegate< RetType ( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5 ) > -bind( - RetType (X::*func)( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5 ), - Y * y, - ...) -{ - return FastDelegate< RetType ( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5 ) >(y, func); -} - -template -FastDelegate< RetType ( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5 ) > -bind( - RetType (X::*func)( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5 ) const, - Y * y, - ...) -{ - return FastDelegate< RetType ( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5 ) >(y, func); -} - -//N=6 -template -FastDelegate< RetType ( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6 ) > -bind( - RetType (X::*func)( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6 ), - Y * y, - ...) -{ - return FastDelegate< RetType ( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6 ) >(y, func); -} - -template -FastDelegate< RetType ( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6 ) > -bind( - RetType (X::*func)( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6 ) const, - Y * y, - ...) -{ - return FastDelegate< RetType ( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6 ) >(y, func); -} - -//N=7 -template -FastDelegate< RetType ( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7 ) > -bind( - RetType (X::*func)( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7 ), - Y * y, - ...) -{ - return FastDelegate< RetType ( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7 ) >(y, func); -} - -template -FastDelegate< RetType ( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7 ) > -bind( - RetType (X::*func)( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7 ) const, - Y * y, - ...) -{ - return FastDelegate< RetType ( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7 ) >(y, func); -} - -//N=8 -template -FastDelegate< RetType ( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8 ) > -bind( - RetType (X::*func)( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8 ), - Y * y, - ...) -{ - return FastDelegate< RetType ( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8 ) >(y, func); -} - -template -FastDelegate< RetType ( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8 ) > -bind( - RetType (X::*func)( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8 ) const, - Y * y, - ...) -{ - return FastDelegate< RetType ( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8 ) >(y, func); -} - - -#endif //FASTDELEGATE_ALLOW_FUNCTION_TYPE_SYNTAX - -} // namespace fastdelegate - -#endif // !defined(FASTDELEGATEBIND_H) - diff --git a/libs/Common/FastDelegateCPP11.h b/libs/Common/FastDelegateCPP11.h deleted file mode 100644 index 26a565bfb..000000000 --- a/libs/Common/FastDelegateCPP11.h +++ /dev/null @@ -1,379 +0,0 @@ -/** \file SRDelegate.hpp - * - * This is a C++11 implementation by janezz55(code.google) for the original "The Impossibly Fast C++ Delegates" authored by Sergey Ryazanov. - * - * This is a copy checkouted from https://code.google.com/p/cpppractice/source/browse/trunk/delegate.hpp on 2014/06/07. - * Last change in the chunk was r370 on Feb 9, 2014. - * - * The following modifications were added by Benjamin YanXiang Huang - * - replace light_ptr with std::shared_ptr - * - renamed src file - * - * Reference: - * - http://codereview.stackexchange.com/questions/14730/impossibly-fast-delegate-in-c11 - * - http://www.codeproject.com/Articles/11015/The-Impossibly-Fast-C-Delegates - * - https://code.google.com/p/cpppractice/source/browse/trunk/delegate.hpp -*/ - -#pragma once -#ifndef SRDELEGATE_HPP -#define SRDELEGATE_HPP - -#include -#include -#include -#include -#include -#include - -// VC work around for constexpr and noexcept: VC2013 and below do not support these 2 keywords -#if defined(_MSC_VER) && (_MSC_VER <= 1800) -#define constexpr const -#define noexcept throw() -#endif - -namespace fastdelegate -{ - -template class delegate; - -template -class delegate -{ - using stub_ptr_type = R(*)(void *, A&&...); - - delegate(void * const o, stub_ptr_type const m) noexcept : object_ptr_(o), stub_ptr_(m) {} - -public: - delegate(void) = default; - - delegate(delegate const &) = default; - - delegate(delegate && d) - : object_ptr_(d.object_ptr_), stub_ptr_(d.stub_ptr_), deleter_(d.deleter_), store_(d.store_), store_size_(d.store_size_) - { - d.object_ptr_ = nullptr; - d.stub_ptr_ = nullptr; - d.deleter_ = nullptr; - d.store_ = nullptr; - d.store_size_ = 0; - } - - delegate(::std::nullptr_t const) noexcept : delegate() { } - - template ::value, C>::type> - explicit delegate(C const * const o) noexcept : - object_ptr_(const_cast(o)) - {} - - template {}>::type> - explicit delegate(C const & o) noexcept : - object_ptr_(const_cast(&o)) - {} - - template - delegate(C * const object_ptr, R(C::* const method_ptr)(A...)) - { - *this = from(object_ptr, method_ptr); - } - - template - delegate(C * const object_ptr, R(C::* const method_ptr)(A...) const) - { - *this = from(object_ptr, method_ptr); - } - - template - delegate(C & object, R(C::* const method_ptr)(A...)) - { - *this = from(object, method_ptr); - } - - template - delegate(C const & object, R(C::* const method_ptr)(A...) const) - { - *this = from(object, method_ptr); - } - - template < - typename T, - typename = typename ::std::enable_if::type>::value>::type - > - delegate(T&& f) - : store_(operator new(sizeof(typename ::std::decay::type)) - , functor_deleter::type>) - , store_size_(sizeof(typename ::std::decay::type)) - { - using functor_type = typename ::std::decay::type; - - new(store_.get()) functor_type(::std::forward(f)); - object_ptr_ = store_.get(); - - stub_ptr_ = functor_stub; - deleter_ = deleter_stub; - } - - delegate & operator=(delegate const &) = default; - - delegate & operator=(delegate&& d) - { - object_ptr_ = d.object_ptr_; - stub_ptr_ = d.stub_ptr_; - deleter_ = d.deleter_; - store_ = d.store_; - store_size_ = d.store_size_; - - d.object_ptr_ = nullptr; - d.stub_ptr_ = nullptr; - d.deleter_ = nullptr; - d.store_ = nullptr; - d.store_size_ = 0; - - return *this; - } - - template - delegate & operator=(R(C::* const rhs)(A...)) - { - return *this = from(static_cast(object_ptr_), rhs); - } - - template - delegate & operator=(R(C::* const rhs)(A...) const) - { - return *this = from(static_cast(object_ptr_), rhs); - } - - template < - typename T - , typename = typename ::std::enable_if::type>::value>::type - > - delegate & operator=(T&& f) - { - using functor_type = typename ::std::decay::type; - - if ((sizeof(functor_type) > store_size_) || !store_.unique()) - { - store_.reset(operator new(sizeof(functor_type)), functor_deleter); - store_size_ = sizeof(functor_type); - } - else - deleter_(store_.get()); - - new(store_.get()) functor_type(::std::forward(f)); - object_ptr_ = store_.get(); - - stub_ptr_ = functor_stub; - deleter_ = deleter_stub; - - return *this; - } - - template - static delegate from(void) noexcept - { - return { nullptr, function_stub }; - } - - template - static delegate from(C * const object_ptr) noexcept - { - return { object_ptr, method_stub }; - } - - template - static delegate from(C const * const object_ptr) noexcept - { - return { const_cast(object_ptr), const_method_stub }; - } - - template - static delegate from(C & object) noexcept - { - return { &object, method_stub }; - } - - template - static delegate from(C const & object) noexcept - { - return { const_cast(&object), const_method_stub }; - } - - template - static delegate from(T && f) - { - return ::std::forward(f); - } - - static delegate from(R(* const function_ptr)(A...)) - { - return function_ptr; - } - - template - using member_pair = ::std::pair; - - template - using const_member_pair = ::std::pair; - - template - static delegate from(C * const object_ptr, R(C::* const method_ptr)(A...)) - { - return member_pair(object_ptr, method_ptr); - } - - template - static delegate from(C const * const object_ptr, R(C::* const method_ptr)(A...) const) - { - return const_member_pair(object_ptr, method_ptr); - } - - template - static delegate from(C & object, R(C::* const method_ptr)(A...)) - { - return member_pair(&object, method_ptr); - } - - template - static delegate from(C const & object, R(C::* const method_ptr)(A...) const) - { - return const_member_pair(&object, method_ptr); - } - - void reset(void) - { - stub_ptr_ = nullptr; - store_.reset(); - } - - void reset_stub(void) noexcept { stub_ptr_ = nullptr; } - - void swap(delegate & other) noexcept { ::std::swap(*this, other); } - - bool operator==(delegate const & rhs) const noexcept - { - // comparison between functor and non-functor is left as undefined at the moment. - if (store_size_ && rhs.store_size_) // both functors - return (std::memcmp(store_.get(), rhs.store_.get(), store_size_) == 0) && (stub_ptr_ == rhs.stub_ptr_); - return (object_ptr_ == rhs.object_ptr_) && (stub_ptr_ == rhs.stub_ptr_); - } - - bool operator!=(delegate const & rhs) const noexcept - { - return !operator==(rhs); - } - - bool operator<(delegate const & rhs) const noexcept - { - return (object_ptr_ < rhs.object_ptr_) || - ((object_ptr_ == rhs.object_ptr_) && (stub_ptr_ < rhs.stub_ptr_)); - } - - bool operator==(::std::nullptr_t const) const noexcept - { - return !stub_ptr_; - } - - bool operator!=(::std::nullptr_t const) const noexcept - { - return stub_ptr_; - } - - explicit operator bool() const noexcept - { - return stub_ptr_; - } - - R operator()(A... args) const - { - // assert(stub_ptr); - return stub_ptr_(object_ptr_, ::std::forward(args)...); - } - -private: - friend struct ::std::hash; - - using deleter_type = void (*)(void *); - - void * object_ptr_ = nullptr; - stub_ptr_type stub_ptr_ {}; - - deleter_type deleter_ = nullptr; - - ::std::shared_ptr store_ = nullptr; - ::std::size_t store_size_ = 0; - - template - static void functor_deleter(void * const p) - { - static_cast(p)->~T(); - operator delete(p); - } - - template - static void deleter_stub(void * const p) - { - static_cast(p)->~T(); - } - - template - static R function_stub(void * const, A && ... args) - { - return function_ptr(::std::forward(args)...); - } - - template - static R method_stub(void * const object_ptr, A && ... args) - { - return (static_cast(object_ptr)->*method_ptr)(::std::forward(args)...); - } - - template - static R const_method_stub(void * const object_ptr, A && ... args) - { - return (static_cast(object_ptr)->*method_ptr)(::std::forward(args)...); - } - - template - struct is_member_pair : ::std::false_type { }; - - template - struct is_member_pair< ::std::pair > : ::std::true_type {}; - - template - struct is_const_member_pair : ::std::false_type { }; - - template - struct is_const_member_pair< ::std::pair > : ::std::true_type {}; - - template - static typename ::std::enable_if::value || is_const_member_pair::value), R>::type - functor_stub(void * const object_ptr, A && ... args) - { - return (*static_cast(object_ptr))(::std::forward(args)...); - } - - template - static typename ::std::enable_if::value || is_const_member_pair::value, R>::type - functor_stub(void * const object_ptr, A && ... args) - { - return (static_cast(object_ptr)->first->*static_cast(object_ptr)->second)(::std::forward(args)...); - } -}; - -} - -namespace std -{ -template -struct hash<::fastdelegate::delegate > -{ - size_t operator()(::fastdelegate::delegate const & d) const noexcept - { - auto const seed(hash()(d.object_ptr_)); - return hash()(d.stub_ptr_) + 0x9e3779b9 + (seed << 6) + (seed >> 2); - } -}; -} - -#endif // SRDELEGATE_HPP diff --git a/libs/Common/File.h b/libs/Common/File.h index f369bb5df..2356ba754 100644 --- a/libs/Common/File.h +++ b/libs/Common/File.h @@ -49,6 +49,20 @@ #define W_OK 2 #define R_OK 4 // Posix-style macros for Windows +#ifndef STATS +#ifdef _WIN64 +#define STATS _stat64 +#else +#define STATS stat +#endif +#endif +#ifndef FSTAT +#ifdef _WIN64 +#define FSTAT _fstat64 +#else +#define FSTAT fstat +#endif +#endif #ifndef S_ISREG #define S_ISREG(mode) ((mode & _S_IFMT) == _S_IFREG) #endif @@ -73,19 +87,43 @@ #else #include #include +#ifndef STATS +#if defined(_ENVIRONMENT64) && !defined(__APPLE__) +#define STATS stat64 +#else +#define STATS stat +#endif +#endif +#ifndef FSTAT +#if defined(_ENVIRONMENT64) && !defined(__APPLE__) +#define FSTAT fstat64 +#else +#define FSTAT fstat +#endif +#endif #define _taccess access #endif +#ifdef __APPLE__ +#define fdatasync fsync +#endif // D E F I N E S /////////////////////////////////////////////////// -/* size of the stored file size variable */ +// size of the stored file size variable #ifdef LARGEFILESIZE #define FILESIZE size_f_t #else #define FILESIZE size_t #endif +// invalid file handle +#ifdef _MSC_VER +#define FILE_INVALID_HANDLE INVALID_HANDLE_VALUE +#else +#define FILE_INVALID_HANDLE int(-1) +#endif + namespace SEACAVE { @@ -106,44 +144,72 @@ class GENERAL_API File : public IOStream { TRUNCATE = 0x04 } FMCREATE; - typedef enum FMFALGS_TYPE { - NOBUFFER = 0x01, - RANDOM = 0x02, - SEQUENTIAL = 0x03 - } FMFALGS; - -#ifdef _MSC_VER - typedef enum FMACCESS_TYPE { - READ = GENERIC_READ, - WRITE = GENERIC_WRITE, - RW = READ | WRITE - } FMACCESS; - - typedef enum FMCHECKACCESS_TYPE { - CA_EXIST = 0, // existence - CA_WRITE = 2, // write - CA_READ = 4, // read - CA_RW = CA_READ | CA_WRITE - } FMCHECKACCESS; + typedef enum FMFLAGS_TYPE { + SYNC = 0x01, + NOBUFFER = 0x02, + RANDOM = 0x04, + SEQUENTIAL = 0x08 + } FMFLAGS; - File() : h(INVALID_HANDLE_VALUE) { + inline File() : h(FILE_INVALID_HANDLE) { #ifndef _RELEASE breakRead = -1; breakWrite = -1; #endif } - - File(LPCTSTR aFileName, int access, int mode, int flags=0) : h(INVALID_HANDLE_VALUE) { + inline File(LPCTSTR aFileName, int access, int mode, int flags=0) : h(FILE_INVALID_HANDLE) { #ifndef _RELEASE breakRead = -1; breakWrite = -1; #endif - open(aFileName, access, mode, flags); + File::open(aFileName, access, mode, flags); + } + + virtual ~File() { + File::close(); + } + + #ifdef _SUPPORT_CPP11 + inline File(File&& rhs) : h(rhs.h) { + #ifndef _RELEASE + breakRead = rhs.breakRead; + breakWrite = rhs.breakWrite; + #endif + rhs.h = FILE_INVALID_HANDLE; } + inline File& operator=(File&& rhs) { + h = rhs.h; + #ifndef _RELEASE + breakRead = rhs.breakRead; + breakWrite = rhs.breakWrite; + #endif + rhs.h = FILE_INVALID_HANDLE; + return *this; + } + #endif + + bool isOpen() const { + return h != FILE_INVALID_HANDLE; + } + +#ifdef _MSC_VER + typedef enum FMACCESS_TYPE { + READ = GENERIC_READ, + WRITE = GENERIC_WRITE, + RW = READ | WRITE + } FMACCESS; + + typedef enum FMCHECKACCESS_TYPE { + CA_EXIST = F_OK, // existence + CA_WRITE = W_OK, // write + CA_READ = R_OK, // read + CA_RW = R_OK | W_OK, + } FMCHECKACCESS; + /** * Open the file specified. - * If there are errors, h is set to INVALID_HANDLE_VALUE. + * If there are errors, h is set to FILE_INVALID_HANDLE. * Use isOpen() to check. */ virtual void open(LPCTSTR aFileName, int access, int mode, int flags=0) { @@ -164,6 +230,8 @@ class GENERAL_API File : public IOStream { } DWORD f = 0; + if (flags & SYNC) + f |= FILE_FLAG_WRITE_THROUGH; if (flags & NOBUFFER) f |= FILE_FLAG_NO_BUFFERING; if (flags & RANDOM) @@ -174,17 +242,16 @@ class GENERAL_API File : public IOStream { h = ::CreateFile(aFileName, access, FILE_SHARE_READ, NULL, m, f, NULL); } - bool isOpen() { return h != INVALID_HANDLE_VALUE; }; - virtual void close() { if (isOpen()) { FlushFileBuffers(h); CloseHandle(h); - h = INVALID_HANDLE_VALUE; + h = FILE_INVALID_HANDLE; } } uint32_t getLastModified() { + ASSERT(isOpen()); FILETIME f = {0}; ::GetFileTime(h, NULL, NULL, &f); return convertTime(&f); @@ -203,7 +270,8 @@ class GENERAL_API File : public IOStream { return 0; } - virtual size_f_t getSize() const { + size_f_t getSize() const override { + ASSERT(isOpen()); DWORD x; DWORD l = ::GetFileSize(h, &x); if ((l == INVALID_FILE_SIZE) && (GetLastError() != NO_ERROR)) @@ -224,7 +292,8 @@ class GENERAL_API File : public IOStream { return true; } - virtual size_f_t getPos() const { + size_f_t getPos() const override { + ASSERT(isOpen()); LONG x = 0; const DWORD l = ::SetFilePointer(h, 0, &x, FILE_CURRENT); if (l == INVALID_SET_FILE_POINTER) @@ -232,22 +301,26 @@ class GENERAL_API File : public IOStream { return (size_f_t)l | ((size_f_t)x)<<32; } - virtual bool setPos(size_f_t pos) { + bool setPos(size_f_t pos) override { + ASSERT(isOpen()); LONG x = (LONG) (pos>>32); return (::SetFilePointer(h, (DWORD)(pos & 0xffffffff), &x, FILE_BEGIN) != INVALID_SET_FILE_POINTER); } virtual bool setEndPos(size_f_t pos) { + ASSERT(isOpen()); LONG x = (LONG) (pos>>32); return (::SetFilePointer(h, (DWORD)(pos & 0xffffffff), &x, FILE_END) != INVALID_SET_FILE_POINTER); } virtual bool movePos(size_f_t pos) { + ASSERT(isOpen()); LONG x = (LONG) (pos>>32); return (::SetFilePointer(h, (DWORD)(pos & 0xffffffff), &x, FILE_CURRENT) != INVALID_SET_FILE_POINTER); } - virtual size_t read(void* buf, size_t len) { + size_t read(void* buf, size_t len) override { + ASSERT(isOpen()); #ifndef _RELEASE if (breakRead != (size_t)(-1)) { if (breakRead <= len) { @@ -264,7 +337,8 @@ class GENERAL_API File : public IOStream { return x; } - virtual size_t write(const void* buf, size_t len) { + size_t write(const void* buf, size_t len) override { + ASSERT(isOpen()); #ifndef _RELEASE if (breakWrite != (size_t)(-1)) { if (breakWrite <= len) { @@ -286,15 +360,13 @@ class GENERAL_API File : public IOStream { return (SetEndOfFile(h) != FALSE); } - virtual size_t flush() { - if (isOpen()) - return 0; - if (!FlushFileBuffers(h)) - return STREAM_ERROR; - return 0; + size_t flush() override { + ASSERT(isOpen()); + return (FlushFileBuffers(h) ? 0 : STREAM_ERROR); } virtual bool getInfo(BY_HANDLE_FILE_INFORMATION* fileInfo) { + ASSERT(isOpen()); return (GetFileInformationByHandle(h, fileInfo) != FALSE); } @@ -320,7 +392,7 @@ class GENERAL_API File : public IOStream { static size_f_t getSize(LPCTSTR aFileName) { const HANDLE fh = ::CreateFile(aFileName, FILE_READ_ATTRIBUTES, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_NO_BUFFERING, NULL); - if (fh == INVALID_HANDLE_VALUE) + if (fh == FILE_INVALID_HANDLE) return SIZE_NA; DWORD x; DWORD l = ::GetFileSize(fh, &x); @@ -331,17 +403,16 @@ class GENERAL_API File : public IOStream { } - static size_f_t findFiles(const String& _strPath, const String& strMask, bool bProcessSubdir, FileInfoArr& arrFiles) - { // List all the files. + static size_f_t findFiles(const String& _strPath, const String& strMask, bool bProcessSubdir, FileInfoArr& arrFiles) { + // List all the files. WIN32_FIND_DATA fd; HANDLE hFind; size_f_t totalSize = 0; String strPath(_strPath); Util::ensureFolderSlash(strPath); - // Find all the files in this folder. + //Find all the files in this folder. hFind = FindFirstFile((strPath + strMask).c_str(), &fd); - if (hFind != INVALID_HANDLE_VALUE) - { + if (hFind != FILE_INVALID_HANDLE) { do { // this is a file that can be used if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) @@ -356,16 +427,14 @@ class GENERAL_API File : public IOStream { #endif fileInfo.attrib = fd.dwFileAttributes; totalSize += fileInfo.size; - } - while (FindNextFile(hFind, &fd)); + } while (FindNextFile(hFind, &fd)); FindClose(hFind); } - // Process the subfolders also... + //Process the subfolders also... if (!bProcessSubdir) return totalSize; hFind = FindFirstFile((strPath + '*').c_str(), &fd); - if (hFind != INVALID_HANDLE_VALUE) - { + if (hFind != FILE_INVALID_HANDLE) { do { // if SUBDIR then process that too if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) @@ -376,8 +445,7 @@ class GENERAL_API File : public IOStream { continue; // Process all subfolders recursively totalSize += findFiles(strPath + fd.cFileName + PATH_SEPARATOR, strMask, true, arrFiles); - } - while (FindNextFile(hFind, &fd)); + } while (FindNextFile(hFind, &fd)); FindClose(hFind); } return totalSize; @@ -399,24 +467,9 @@ class GENERAL_API File : public IOStream { CA_EXEC = X_OK, // execute } FMCHECKACCESS; - File() : h(-1) { - #ifndef _RELEASE - breakRead = -1; - breakWrite = -1; - #endif - } - - File(LPCTSTR aFileName, int access, int mode, int flags=0) : h(-1) { - #ifndef _RELEASE - breakRead = -1; - breakWrite = -1; - #endif - open(aFileName, access, mode, flags); - } - /** * Open the file specified. - * If there are errors, h is set to -1. + * If there are errors, h is set to FILE_INVALID_HANDLE. * Use isOpen() to check. */ virtual void open(LPCTSTR aFileName, int access, int mode, int flags=0) { @@ -437,6 +490,8 @@ class GENERAL_API File : public IOStream { if (mode & TRUNCATE) m |= O_TRUNC; + if (flags & SYNC) + m |= O_DSYNC; #ifndef __APPLE__ if (flags & NOBUFFER) m |= O_DIRECT; @@ -444,40 +499,40 @@ class GENERAL_API File : public IOStream { h = ::open(aFileName, m, S_IRUSR | S_IWUSR); } - bool isOpen() { return h != -1; }; - virtual void close() { - if (h != -1) { + if (h != FILE_INVALID_HANDLE) { ::close(h); - h = -1; + h = FILE_INVALID_HANDLE; } } uint32_t getLastModified() { - struct stat s; - if (::fstat(h, &s) == -1) + ASSERT(isOpen()); + struct STATS s; + if (::FSTAT(h, &s) == -1) return 0; - return (uint32_t)s.st_mtime; } - virtual size_f_t getSize() const { - struct stat s; - if (::fstat(h, &s) == -1) + size_f_t getSize() const override { + ASSERT(isOpen()); + struct STATS s; + if (::FSTAT(h, &s) == -1) return SIZE_NA; - return (size_f_t)s.st_size; } - virtual size_f_t getPos() const { - return (size_f_t) lseek(h, 0, SEEK_CUR); + size_f_t getPos() const override { + ASSERT(isOpen()); + return (size_f_t)lseek(h, 0, SEEK_CUR); } - virtual bool setPos(size_f_t pos) { return lseek(h, (off_t)pos, SEEK_SET) != (off_t)-1; }; - virtual void setEndPos(size_f_t pos) { lseek(h, (off_t)pos, SEEK_END); }; - virtual void movePos(size_f_t pos) { lseek(h, (off_t)pos, SEEK_CUR); }; + bool setPos(size_f_t pos) override { ASSERT(isOpen()); return lseek(h, (off_t)pos, SEEK_SET) != (off_t)-1; } + virtual bool setEndPos(size_f_t pos) { ASSERT(isOpen()); return lseek(h, (off_t)pos, SEEK_END) != (off_t)-1; } + virtual bool movePos(size_f_t pos) { ASSERT(isOpen()); return lseek(h, (off_t)pos, SEEK_CUR) != (off_t)-1; } - virtual size_t read(void* buf, size_t len) { + size_t read(void* buf, size_t len) override { + ASSERT(isOpen()); #ifndef _RELEASE if (breakRead != (size_t)(-1)) { if (breakRead <= len) { @@ -494,7 +549,8 @@ class GENERAL_API File : public IOStream { return (size_t)x; } - virtual size_t write(const void* buf, size_t len) { + size_t write(const void* buf, size_t len) override { + ASSERT(isOpen()); #ifndef _RELEASE if (breakWrite != (size_t)(-1)) { if (breakWrite <= len) { @@ -514,14 +570,17 @@ class GENERAL_API File : public IOStream { } virtual bool setEOF() { + ASSERT(isOpen()); return (ftruncate(h, (off_t)getPos()) != -1); } virtual bool setSize(size_f_t newSize) { + ASSERT(isOpen()); return (ftruncate(h, (off_t)newSize) != -1); } - virtual size_t flush() { - return fsync(h); + size_t flush() override { + ASSERT(isOpen()); + return fdatasync(h); } static void deleteFile(LPCTSTR aFileName) { ::remove(aFileName); } @@ -538,10 +597,10 @@ class GENERAL_API File : public IOStream { } static size_f_t getSize(LPCTSTR aFileName) { - struct stat s; - if (stat(aFileName, &s) == -1) + struct STATS buf; + if (STATS(aFileName, &buf) != 0) return SIZE_NA; - return s.st_size; + return buf.st_size; } #endif // _MSC_VER @@ -549,16 +608,16 @@ class GENERAL_API File : public IOStream { // test for whether there's something (i.e. folder or file) with this name // and what access mode is supported static bool isPresent(LPCTSTR path) { - struct stat buf; - return stat(path, &buf) == 0; + struct STATS buf; + return STATS(path, &buf) == 0; } static bool access(LPCTSTR path, int mode=CA_EXIST) { return ::_taccess(path, mode) == 0; } // test for whether there's something present and its a folder static bool isFolder(LPCTSTR path) { - struct stat buf; - if (!(stat(path, &buf) == 0)) + struct STATS buf; + if (!(STATS(path, &buf) == 0)) return false; // If the object is present, see if it is a directory // this is the Posix-approved way of testing @@ -567,8 +626,8 @@ class GENERAL_API File : public IOStream { // test for whether there's something present and its a file // a file can be a regular file, a symbolic link, a FIFO or a socket, but not a device static bool isFile(LPCTSTR path) { - struct stat buf; - if (!(stat(path, &buf) == 0)) + struct STATS buf; + if (!(STATS(path, &buf) == 0)) return false; // If the object is present, see if it is a file or file-like object // Note that devices are neither folders nor files @@ -576,6 +635,38 @@ class GENERAL_API File : public IOStream { return S_ISREG(buf.st_mode) || S_ISLNK(buf.st_mode) || S_ISSOCK(buf.st_mode) || S_ISFIFO(buf.st_mode); } + // time the file was originally created + static time_t getCreated(LPCTSTR path) { + struct STATS buf; + if (STATS(path, &buf) != 0) return 0; + return buf.st_ctime; + } + // time the file was last modified + static time_t getModified(LPCTSTR path) { + struct STATS buf; + if (STATS(path, &buf) != 0) return 0; + return buf.st_mtime; + } + // time the file was accessed + static time_t getAccessed(LPCTSTR path) { + struct STATS buf; + if (STATS(path, &buf) != 0) return 0; + return buf.st_atime; + } + + // set the current folder + static bool setCurrentFolder(LPCTSTR path) { + if (!isFolder(path)) + return false; + #ifdef _MSC_VER + // Windows implementation - this returns non-zero for success + return (SetCurrentDirectory(path) != 0); + #else + // Unix implementation - this returns zero for success + return (chdir(path) == 0); + #endif + } + template inline size_t write(const VECTOR& arr) { const typename VECTOR::IDX nSize(arr.GetSize()); @@ -593,12 +684,10 @@ class GENERAL_API File : public IOStream { return nBytes; } - virtual ~File() { - File::close(); - } - - static LPCSTR getClassType() { return "File"; } - virtual LPCSTR getClassName() const { return File::getClassType(); } + enum { LAYER_ID_IN=3 }; + InputStream* getInputStream(int typ=InputStream::LAYER_ID_IN) override { return (typ == LAYER_ID_IN ? static_cast(this) : IOStream::getInputStream(typ)); } + enum { LAYER_ID_OUT=3 }; + OutputStream* getOutputStream(int typ=OutputStream::LAYER_ID_OUT) override { return (typ == LAYER_ID_OUT ? static_cast(this) : IOStream::getOutputStream(typ)); } protected: #ifdef _MSC_VER diff --git a/libs/Common/Filters.h b/libs/Common/Filters.h index 55218cc59..ae3d385a6 100644 --- a/libs/Common/Filters.h +++ b/libs/Common/Filters.h @@ -21,76 +21,82 @@ namespace SEACAVE { // S T R U C T S /////////////////////////////////////////////////// -#define LAYER_MASK 1 - -template -class MaskInputStream : public LayerInputStream { +template +class BufferedInputStream : public LayerInputStream { public: - MaskInputStream(InputStream* aStream, size_f_t nPos, size_f_t nSize) : LayerInputStream(aStream), startPos(nPos), size(nSize), pos(0) { } - virtual ~MaskInputStream() { } + typedef LayerInputStream Base; - size_t read(void* wbuf, size_t len) { - if (pos >= size) - return 0; - if (!this->s->setPos(startPos + pos)) - return STREAM_ERROR; - if (pos+(size_f_t)len > size) - len = (size_t)(size - pos); - const size_t r = this->s->read(wbuf, len); - if (r == STREAM_ERROR) - return STREAM_ERROR; - pos += r; - return r; + BufferedInputStream(InputStream* aStream, size_t aBufSize) + : Base(aStream), buf(new uint8_t[aBufSize]), bufSize(aBufSize), cache(0), pos(0) { ASSERT(aBufSize > 0); } + virtual ~BufferedInputStream() { + delete[] buf; } - size_f_t getSize() const { - return size; + size_t read(void* wbuf, size_t len) override { + uint8_t* b = (uint8_t*)wbuf; + const size_t l2 = len; + do { + ASSERT(pos <= cache); + if (pos == cache) { + if (len >= bufSize) { + const size_t r = Base::read(b, len); + if (r == STREAM_ERROR) + return STREAM_ERROR; + return l2 - len + r; + } + pos = 0; + switch (cache = Base::read(buf, bufSize)) { + case 0: + return l2 - len; + case STREAM_ERROR: + return STREAM_ERROR; + } + } + const size_t n = MINF(cache - pos, len); + memcpy(b, buf + pos, n); + b += n; + pos += n; + len -= n; + } while (len > 0); + return l2; } - bool setPos(size_f_t wpos) { - if (wpos > size) - pos = size; - else - pos = wpos; - return true; + bool setPos(size_f_t wpos) override { + pos = cache = 0; + return Base::setPos(wpos); } - size_f_t getPos() const { - return pos; + size_f_t getPos() const override { + const size_f_t r = Base::getPos(); + if (r == SIZE_NA) + return SIZE_NA; + return r-(cache-pos); } - InputStream* getInputStream(int typ) { return (typ == LAYER_MASK ? this : this->s->getInputStream(typ)); } + enum { LAYER_ID_IN=1 }; + InputStream* getInputStream(int typ=InputStream::LAYER_ID_IN) override { return (typ == LAYER_ID_IN ? static_cast(this) : Base::getInputStream(typ)); } private: - const size_f_t size; - const size_f_t startPos; - size_f_t pos; + uint8_t* const buf; + const size_t bufSize; + size_t cache; + size_t pos; }; -#define LAYER_BUFFER 2 - -template +template class BufferedOutputStream : public LayerOutputStream { public: - BufferedOutputStream(OutputStream* aStream, size_t aBufSize) : LayerOutputStream(aStream), buf(new uint8_t[aBufSize]), bufSize(aBufSize), pos(0) { } + typedef LayerOutputStream Base; + + BufferedOutputStream(OutputStream* aStream, size_t aBufSize) + : Base(aStream), buf(new uint8_t[aBufSize]), bufSize(aBufSize), pos(0) { } virtual ~BufferedOutputStream() { flush(); delete[] buf; } - size_t flush() { - size_t ret = 0; - if (pos > 0) { - ret = this->s->write(buf, pos); - pos = 0; - if (ret == STREAM_ERROR) - return STREAM_ERROR; - } - return ret + this->s->flush(); - } - - size_t write(const void* wbuf, size_t len) { + size_t write(const void* wbuf, size_t len) override { if (len < bufSize - pos) { memcpy(buf + pos, (const uint8_t*)wbuf, len); pos += len; @@ -100,11 +106,11 @@ class BufferedOutputStream : public LayerOutputStream { const size_t l2 = len; do { if (pos == bufSize) { - if (this->s->write(buf, bufSize) == STREAM_ERROR) + if (Base::write(buf, bufSize) == STREAM_ERROR) return STREAM_ERROR; pos = 0; - if (len >= bufSize) { - if (this->s->write(b, len) == STREAM_ERROR) + if (len < bufSize) { + if (Base::write(b, len) == STREAM_ERROR) return STREAM_ERROR; break; } @@ -118,128 +124,134 @@ class BufferedOutputStream : public LayerOutputStream { return l2; } - OutputStream* getOutputStream(int typ) { return (typ == LAYER_BUFFER ? this : this->s->getOutputStream(typ)); } + size_f_t getSize() const override { + const size_f_t r = Base::getSize(); + if (r == SIZE_NA) + return SIZE_NA; + return r + pos; + } + + bool setPos(size_f_t wpos) override { + if (pos > 0) { + const size_t ret = Base::write(buf, pos); + pos = 0; + if (ret == STREAM_ERROR) + return false; + } + return Base::setPos(wpos); + } + + size_f_t getPos() const override { + const size_f_t r = Base::getPos(); + if (r == SIZE_NA) + return SIZE_NA; + return r + pos; + } + + size_t flush() override { + size_t ret = 0; + if (pos > 0) { + ret = Base::write(buf, pos); + pos = 0; + if (ret == STREAM_ERROR) + return STREAM_ERROR; + } + return ret + Base::flush(); + } + + enum { LAYER_ID_OUT=1 }; + OutputStream* getOutputStream(int typ=OutputStream::LAYER_ID_OUT) override { return (typ == LAYER_ID_OUT ? static_cast(this) : Base::getOutputStream(typ)); } private: uint8_t* const buf; const size_t bufSize; size_t pos; }; +/*----------------------------------------------------------------*/ -template -class BufferedInputStream : public LayerInputStream { + +template +class FilteredInputStream : public LayerInputStream { public: - BufferedInputStream(InputStream* aStream, size_t aBufSize) : LayerInputStream(aStream), buf(new uint8_t[aBufSize]), bufSize(aBufSize), maxCache(0), caching(0), pos(0) { } - virtual ~BufferedInputStream() { + typedef LayerInputStream Base; + + FilteredInputStream(InputStream* pFile, size_t nFilteredSize, size_t nBufSize, void* pData) + : Base(pFile), buf(new uint8_t[nBufSize]), filteredSize(nFilteredSize), bufSize(nBufSize), valid(0), pos(0), filter(pData), more(true) {} + virtual ~FilteredInputStream() { delete[] buf; } - size_t read(void* wbuf, size_t len) { - uint8_t* b = (uint8_t*)wbuf; + /** + * Read data through filter, keep calling until len returns 0. + * @param rbuf Data buffer + * @param len Buffer size on entry, bytes actually read on exit + * @return Length of data in buffer + */ + size_t read(void* rbuf, size_t len) override { + uint8_t* rb = (uint8_t*)rbuf; + const size_t l2 = len; - do { - if (caching < maxCache) { - size_t sliceLen = bufSize-caching; - if (sliceLen > FILE_READ_MINBUF_SIZE+(FILE_READ_MINBUF_SIZE/2)) - sliceLen = FILE_READ_MINBUF_SIZE; - const size_t r = this->s->read(buf+caching, sliceLen); - if (r == STREAM_ERROR) + while (more && len) { + if (pos == valid) { + valid = Base::read(buf, bufSize); + if (valid == STREAM_ERROR) return STREAM_ERROR; - caching += r; - if (r != sliceLen) { - maxCache = caching; - } - } - else if (pos == maxCache) { - if (len >= bufSize) { - const size_t r = this->s->read(b, len); - if (r == STREAM_ERROR) - return STREAM_ERROR; - return l2 - len + r; - } pos = 0; - switch (caching = this->s->read(buf, FILE_READ_MINBUF_SIZE)) { - case 0: - maxCache = 0; - return l2 - len; - case FILE_READ_MINBUF_SIZE: - maxCache = bufSize; - break; - case STREAM_ERROR: - return STREAM_ERROR; - default: // < FILE_READ_MINBUF_SIZE - maxCache = caching; - } } - const size_t n = MINF(caching - pos, len); - memcpy(b, buf + pos, n); - b += n; - pos += n; + size_t m = valid - pos; + size_t n = len; + more = filter(buf + pos, m, rb, n); + pos += m; + rb += n; len -= n; - } while (len > 0); - return l2; + } + return l2-len; } - bool setPos(size_f_t wpos) { - pos = caching = maxCache = 0; - return this->s->setPos(wpos); + size_f_t getSize() const override { + return filteredSize; + } + + bool setPos(size_f_t wpos) override { + valid = pos = 0; + return Base::setPos(wpos); } - size_f_t getPos() const { - const size_f_t r = this->s->getPos(); + size_f_t getPos() const override { + const size_f_t r = Base::getPos(); if (r == SIZE_NA) return SIZE_NA; - return r-(caching-pos); + return r-(valid-pos); } - InputStream* getInputStream(int typ) { return (typ == LAYER_BUFFER ? this : this->s->getInputStream(typ)); } + enum { LAYER_ID_IN=2 }; + InputStream* getInputStream(int typ=InputStream::LAYER_ID_IN) override { return (typ == LAYER_ID_IN ? static_cast(this) : Base::getInputStream(typ)); } private: uint8_t* const buf; + const size_t filteredSize; const size_t bufSize; - size_t maxCache; - size_t caching; + size_t valid; size_t pos; + Filter filter; + bool more; }; -#define LAYER_ZIP 3 - -template +template class FilteredOutputStream : public LayerOutputStream { public: - using OutputStream::write; + typedef LayerOutputStream Base; - FilteredOutputStream(OutputStream* aFile, size_t aBufSize, void* pData) : LayerOutputStream(aFile), buf(new uint8_t[aBufSize]), bufSize(aBufSize), filter(pData), flushed(false) {} + FilteredOutputStream(OutputStream* aFile, size_t aBufSize, void* pData) + : Base(aFile), buf(new uint8_t[aBufSize]), bufSize(aBufSize), filter(pData), flushed(false) {} virtual ~FilteredOutputStream() { + flush(); delete[] buf; } - size_t flush() { - if (flushed) - return this->s->flush(); - - flushed = true; - size_t written = 0; - - for (;;) { - size_t n = bufSize; - size_t zero = 0; - bool more = filter(NULL, zero, buf, n); - - written += this->s->write(buf, n); - - if (!more) - break; - } - const size_t r = this->s->flush(); - if (r == STREAM_ERROR) - return STREAM_ERROR; - return written + r; - } - - size_t write(const void* wbuf, size_t len) { + size_t write(const void* wbuf, size_t len) override { if (flushed) return STREAM_ERROR; @@ -253,7 +265,7 @@ class FilteredOutputStream : public LayerOutputStream { wb += m; len -= m; - const size_t r = this->s->write(buf, n); + const size_t r = Base::write(buf, n); if (r == STREAM_ERROR) return STREAM_ERROR; written += r; @@ -268,92 +280,41 @@ class FilteredOutputStream : public LayerOutputStream { return written; } - OutputStream* getOutputStream(int typ) { return (typ == LAYER_ZIP ? this : this->s->getOutputStream(typ)); } - -private: - uint8_t* const buf; - const size_t bufSize; - Filter filter; - bool flushed; -}; - -template -class FilteredInputStream : public LayerInputStream { -public: - FilteredInputStream(InputStream* pFile, size_t nFilteredSize, size_t nBufSize, void* pData) : LayerInputStream(pFile), buf(new uint8_t[nBufSize]), filteredSize(nFilteredSize), bufSize(nBufSize), valid(0), pos(0), filter(pData), more(true) {} - virtual ~FilteredInputStream() { - delete[] buf; - } + size_t flush() override { + if (flushed) + return Base::flush(); - /** - * Read data through filter, keep calling until len returns 0. - * @param rbuf Data buffer - * @param len Buffer size on entry, bytes actually read on exit - * @return Length of data in buffer - */ - size_t read(void* rbuf, size_t len) { - uint8_t* rb = (uint8_t*)rbuf; + flushed = true; + size_t written = 0; - const size_t l2 = len; - while (more && len) { - #if ZIP_MODE == BZIP2_MODE - size_t n = len; - filter.flush(rb, n); - rb += n; - len -= n; - #endif + while (true) { + size_t n = bufSize; + size_t zero = 0; + bool more = filter(NULL, zero, buf, n); - if (pos == valid) { - valid = this->s->read(buf, bufSize); - if (valid == STREAM_ERROR) - return STREAM_ERROR; - pos = 0; - } + written += Base::write(buf, n); - size_t m = valid - pos; - #if ZIP_MODE == BZIP2_MODE - n = len; - #else - size_t n = len; - #endif - more = filter(buf + pos, m, rb, n); - pos += m; - rb += n; - len -= n; + if (!more) + break; } - return l2-len; - } - - size_f_t getSize() const { - return filteredSize; - } - - bool setPos(size_f_t wpos) { - valid = pos = 0; - return this->s->setPos(wpos); - } - - size_f_t getPos() const { - const size_f_t r = this->s->getPos(); - if (r == SIZE_NA) - return SIZE_NA; - return r-(valid-pos); + const size_t r = Base::flush(); + if (r == STREAM_ERROR) + return STREAM_ERROR; + return written + r; } - InputStream* getInputStream(int typ) { return (typ == LAYER_ZIP ? this : this->s->getInputStream(typ)); } + enum { LAYER_ID_OUT=2 }; + OutputStream* getOutputStream(int typ=OutputStream::LAYER_ID_OUT) override { return (typ == LAYER_ID_OUT ? static_cast(this) : Base::getOutputStream(typ)); } private: uint8_t* const buf; - const size_t filteredSize; const size_t bufSize; - size_t valid; - size_t pos; Filter filter; - bool more; + bool flushed; }; +/*----------------------------------------------------------------*/ -#define LAYER_TOKEN 4 #ifdef _UNICODE #define TOKEN_SIZE 2 //in bytes @@ -362,12 +323,14 @@ class FilteredInputStream : public LayerInputStream { #endif #define TOKEN_MAXBUF (2048*TOKEN_SIZE) //in bytes #define TOKEN_MAXIGN 32 //in TCHARs -template + +template class TokenInputStream : public LayerInputStream { public: typedef LayerInputStream Base; - TokenInputStream(InputStream* aStream, TCHAR aToken=_T('\n')) : LayerInputStream(aStream), pos(TOKEN_MAXBUF), eos(false), token(aToken) { arrIgnore[0] = _T('\0'); } + TokenInputStream(InputStream* aStream, TCHAR aToken=_T('\n')) + : Base(aStream), pos(TOKEN_MAXBUF), eos(false), token(aToken) { arrIgnore[0] = _T('\0'); } virtual ~TokenInputStream() { } @@ -504,9 +467,9 @@ class TokenInputStream : public LayerInputStream { return l2-len; } // if end of stream return - if (n < len / TOKEN_SIZE) { + if (n < len * TOKEN_SIZE) { eos = true; - return l2-len; + return n / TOKEN_SIZE; } // we reached the end of the buffer, signal it return l2+1; @@ -546,7 +509,7 @@ class TokenInputStream : public LayerInputStream { return ret; } - size_t read(void* wbuf, size_t len) { + size_t read(void* wbuf, size_t len) override { size_t n = 0; if (pos < TOKEN_MAXBUF) { n = TOKEN_MAXBUF-pos; @@ -558,20 +521,20 @@ class TokenInputStream : public LayerInputStream { } if (len == 0) return n; - const size_t r = this->s->read((uint8_t*)wbuf+n, len); + const size_t r = Base::read((uint8_t*)wbuf+n, len); if (r == STREAM_ERROR) return STREAM_ERROR; return n+r; } - bool setPos(size_f_t wpos) { + bool setPos(size_f_t wpos) override { eos = false; pos = TOKEN_MAXBUF; - return this->s->setPos(wpos); + return Base::setPos(wpos); } - size_f_t getPos() const { - const size_f_t r = this->s->getPos(); + size_f_t getPos() const override { + const size_f_t r = Base::getPos(); if (r == SIZE_NA) return SIZE_NA; return r-(TOKEN_MAXBUF-pos); @@ -581,7 +544,8 @@ class TokenInputStream : public LayerInputStream { return eos; } - InputStream* getInputStream(int typ) { return (typ == LAYER_TOKEN ? this : this->s->getInputStream(typ)); } + enum { LAYER_ID_IN=5 }; + InputStream* getInputStream(int typ=InputStream::LAYER_ID_IN) override { return (typ == LAYER_ID_IN ? static_cast(this) : Base::getInputStream(typ)); } private: uint8_t buf[TOKEN_MAXBUF+TOKEN_SIZE]; @@ -592,6 +556,57 @@ class TokenInputStream : public LayerInputStream { }; /*----------------------------------------------------------------*/ + + +template +class MaskInputStream : public LayerInputStream { +public: + typedef LayerInputStream Base; + + MaskInputStream(InputStream* aStream, size_f_t nPos, size_f_t nSize) + : Base(aStream), startPos(nPos), size(nSize), pos(0) { } + virtual ~MaskInputStream() { } + + size_t read(void* wbuf, size_t len) override { + if (pos >= size) + return 0; + if (!Base::setPos(startPos + pos)) + return STREAM_ERROR; + if (pos+(size_f_t)len > size) + len = (size_t)(size - pos); + const size_t r = Base::read(wbuf, len); + if (r == STREAM_ERROR) + return STREAM_ERROR; + pos += r; + return r; + } + + size_f_t getSize() const override { + return size; + } + + bool setPos(size_f_t wpos) override { + if (wpos > size) + pos = size; + else + pos = wpos; + return true; + } + + size_f_t getPos() const override { + return pos; + } + + enum { LAYER_ID_IN=6 }; + InputStream* getInputStream(int typ=InputStream::LAYER_ID_IN) override { return (typ == LAYER_ID_IN ? static_cast(this) : Base::getInputStream(typ)); } + +private: + const size_f_t size; + const size_f_t startPos; + size_f_t pos; +}; +/*----------------------------------------------------------------*/ + } // namespace SEACAVE #endif // __SEACAVE_FILTERS_H__ diff --git a/libs/Common/HTMLDoc.h b/libs/Common/HTMLDoc.h deleted file mode 100644 index 8fd21b5a5..000000000 --- a/libs/Common/HTMLDoc.h +++ /dev/null @@ -1,457 +0,0 @@ -/* - * Modified version of: - * - * @file htmlDoc.h - * @brief Simple HTML document writer and SVG drawer - * @author Pierre MOULON - * - * Copyright (c) 2011, 2012, 2013 Pierre MOULON - * All rights reserved. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -#ifndef _HTMLDOC_H_ -#define _HTMLDOC_H_ - - -// I N C L U D E S ///////////////////////////////////////////////// - - -// D E F I N E S /////////////////////////////////////////////////// - -#define JSXCHART_BORDER 0.2f - - -// S T R U C T S /////////////////////////////////////////////////// - -namespace HTML { - -inline const std::string htmlMarkup(const std::string& markup, const std::string& text) { - std::ostringstream os; - os << '<'<< markup<<'>' << text << "' <<"\n"; - return os.str(); -} -inline const std::string htmlMarkup(const std::string& markup, const std::string& attributes, const std::string& text) { - std::ostringstream os; - os << '<'<' << text << "' <<"\n"; - return os.str(); -} -inline const std::string htmlOpenMarkup(const std::string& markup, const std::string& attributes) { - std::ostringstream os; - os << '<'<" <<"\n"; - return os.str(); -} - -inline const std::string htmlComment(const std::string& text) { - std::ostringstream os; - os << "" << "\n"; - return os.str(); -} - -/// Return a chain in the form attributes="val" -template -inline const std::string quotedAttributes(const std::string& attributes, const T & val) { - std::ostringstream os; - os << attributes << "='" << val << '\''; - return os.str(); -} - -/// Return a chain of the value T -template -inline const std::string toString(const T& val) { - std::ostringstream os; - os << val; - return os.str(); -} - -class htmlDocumentStream -{ -public: - htmlDocumentStream(const std::string& title) { - htmlStream << "\n"; - htmlStream << htmlMarkup("head", - "\n" - "\n" - "\n"); - htmlStream << htmlMarkup("title",title); - } - - htmlDocumentStream(const std::string& title, - const std::vector& vec_css, - const std::vector& vec_js) - { - htmlStream << "\n\n"; - htmlStream << htmlMarkup("title",title); - // CSS and JS resources - for (std::vector::const_iterator iter = vec_css.begin(); iter != vec_css.end(); ++iter) - htmlStream << "\n"; - for (std::vector::const_iterator iter = vec_js.begin(); iter != vec_js.end(); ++iter) - htmlStream << "\n"; - htmlStream << "\n"; - } - - void pushInfo(const std::string& text) { - htmlStream << text; - } - - std::string getDoc() { - return htmlMarkup("html", htmlStream.str()); - } - - std::ostringstream htmlStream; -}; - - -/// Class to draw with the JSXGraph library in HTML page. -class JSXGraphWrapper -{ -public: - typedef float TRANGE; - typedef std::pair< std::pair, std::pair > RANGE; - - JSXGraphWrapper() { - cpt = 0; - } - - void reset() { - stream.str(""); - stream.precision(4); - //stream.setf(std::ios::fixed,std::ios::floatfield); - cpt = 0; - } - - void init(unsigned W, unsigned H, LPCTSTR szGraphName=NULL) { - reset(); - std::string strGraphName; - if (szGraphName == NULL) { - strGraphName = SEACAVE::Util::getUniqueName(); - szGraphName = strGraphName.c_str(); - } - stream << - "\n" - "

\n" - "\n"; - } - - std::string toStr() const { - return stream.str(); - } - - template - static inline RANGE autoViewport(TX maxValX, TY maxValY, TX minValX, TY minValY) { - //Use the value with a little margin - const TX rangeX = maxValX-minValX; - const TY rangeY = maxValY-minValY; - return std::make_pair( - std::make_pair(-JSXCHART_BORDER*rangeX+minValX,JSXCHART_BORDER*rangeX+maxValX), - std::make_pair(-JSXCHART_BORDER*rangeY+minValY,JSXCHART_BORDER*rangeY+maxValY)); - } - template - static RANGE autoViewport(const VECTX& vec_x, const VECTY& vec_y) { - typedef typename VECTX::value_type TX; - typedef typename VECTY::value_type TY; - if (vec_x.empty() || vec_y.empty() || vec_x.size() != vec_y.size()) - return RANGE(); - //For X values - const TX minValX = *std::min_element(vec_x.begin(), vec_x.end()); - const TX maxValX = *std::max_element(vec_x.begin(), vec_x.end()); - //For Y values - const TY minValY = *std::min_element(vec_y.begin(), vec_y.end()); - const TY maxValY = *std::max_element(vec_y.begin(), vec_y.end()); - return autoViewport(maxValX, maxValY, minValX, minValY); - } - template - static RANGE autoViewport(const VECTY& vec_y, bool bForceY0=true) { - typedef T TX; - typedef typename VECTY::value_type TY; - if (vec_y.empty()) - return RANGE(); - //For X values - const TX minValX = TX(0); - const TX maxValX = static_cast(vec_y.size()); - //For Y values - const TY minValY = (bForceY0 ? TY(0) : *std::min_element(vec_y.begin(), vec_y.end())); - const TY maxValY = *std::max_element(vec_y.begin(), vec_y.end()); - return autoViewport(maxValX, maxValY, minValX, minValY); - } - - std::ostringstream stream; - size_t cpt; //increment for variable -}; -/*----------------------------------------------------------------*/ - -} // namespace HTML - - - -namespace SVG { - -/// Basic SVG style -class svgStyle -{ -public: - svgStyle():_sFillCol(""), _sStrokeCol("black"), _sToolTip(""), _fillOpacity(1.f), _strokeW(1.f), _strokeOpacity(1.f) {} - - // Configure fill color - svgStyle& fill(const std::string& col, float opacity = 1.f) - { _sFillCol = col; _fillOpacity = opacity; return *this; } - - // Configure stroke color and width - svgStyle& stroke(const std::string& col, float witdh = 1.f, float opacity = 1.f) - { _sStrokeCol = col; _strokeW = witdh; _strokeOpacity = opacity; return *this; } - - // Configure with no stroke - svgStyle& noStroke() - { _sStrokeCol = ""; _strokeW = 0.f; _strokeOpacity = 0.f; return *this; } - - // Configure tooltip - svgStyle& tooltip(const std::string& sTooltip) - { _sToolTip = sTooltip; return *this; } - - const std::string getSvgStream() const { - std::ostringstream os; - if (!_sStrokeCol.empty()) { - os << " stroke='" << _sStrokeCol << "' stroke-width='" << _strokeW << "'"; - if (_strokeOpacity < 1) - os << " stroke-opacity='" << _strokeOpacity << "'"; - } - if (!_sFillCol.empty()) { - os << " fill='" << _sFillCol << "'"; - if (_fillOpacity < 1) - os << " fill-opacity='" << _fillOpacity << "'"; - } else { - os << " fill='none'"; - } - if (!_sToolTip.empty()) { - os << " tooltip='enable'>" << "" << _sToolTip << ""; - } - return os.str(); - } - - bool bTooltip() const { return !_sToolTip.empty();} - - std::string _sFillCol, _sStrokeCol, _sToolTip; - float _fillOpacity, _strokeW, _strokeOpacity; -}; - - -/// Basic class to handle simple SVG draw. -/// You can draw line, square, rectangle, text and image (xlink) -class svgDrawer -{ -public: - ///Constructor - svgDrawer(size_t W = 0, size_t H = 0) { - svgStream << - "\n" - "\n" - " 0 && H > 0) - svgStream << - " width='" << W << "px' height='"<< H << "px'" - " preserveAspectRatio='xMinYMin meet'" - " viewBox='0 0 " << W << ' ' << H <<"'"; - - svgStream << - " xmlns='http://www.w3.org/2000/svg'" - " xmlns:xlink='http://www.w3.org/1999/xlink'" - " version='1.1'>\n"; - } - ///Circle draw -> x,y position and radius - void drawCircle(float cx, float cy, float r, const svgStyle& style) { - svgStream - << "\n" : "/>\n"); - } - ///Line draw -> start and end point - void drawLine(float ax, float ay, float bx, float by, const svgStyle& style) { - svgStream - << "\n" : "/>\n"); - } - - ///Reference to an image (path must be relative to the SVG file) - void drawImage(const std::string& simagePath, int W, int H, - int posx = 0, int posy = 0, float opacity =1.f) - { - svgStream << - "\n"; - } - - ///Square draw -> x,y position and size - void drawSquare(float cx, float cy, float W, const svgStyle& style) { - drawRectangle(cx, cy, W, W, style); - } - - ///Circle draw -> x,y position and width and height - void drawRectangle(float cx, float cy, float W, float H, const svgStyle& style) { - svgStream - << "\n" : "/>\n"); - } - - ///Text display -> x,y position, font size - void drawText(float cx, float cy, const std::string& stext, const std::string& scol = "", const std::string& sattr = "", float fontSize = 1.f) { - svgStream << "" << stext << "\n"; - } - template< typename DataInputIteratorX, typename DataInputIteratorY> - void drawPolyline(DataInputIteratorX xStart, DataInputIteratorX xEnd, - DataInputIteratorY yStart, DataInputIteratorY /*yEnd*/, - const svgStyle& style) - { - svgStream << "\n" : "/>\n"); - } - - ///Close the svg tag. - std::ostringstream& closeSvgFile() { - svgStream << ""; - return svgStream; - } - - std::ostringstream svgStream; -}; - -/// Helper to draw a SVG histogram -/// ____ -/// | | ___ | -/// | |__| | | -/// | | | | | -/// -----------| -struct svgHisto -{ - template - std::string draw(const VECT& vec_value, - const std::pair& range, - float W, float H) - { - if (vec_value.empty()) - return ""; - - //-- Max value - typedef typename VECT::value_type T; - const T maxi = *max_element(vec_value.begin(), vec_value.end()); - const size_t n = vec_value.size(); - - const float scaleFactorY = H / static_cast(maxi); - const float scaleFactorX = W / static_cast(n); - - svgDrawer svgStream; - - for (typename VECT::const_iterator iter = vec_value.begin(); iter != vec_value.end(); ++iter) - { - const T dist = std::distance(vec_value.begin(), iter); - const T& val = *iter; - std::ostringstream os; - os << '(' << range.first + dist/float(n) * (range.second-range.first) << ',' << val << ')'; - svgStyle style = svgStyle().fill("blue").stroke("black", 1.0).tooltip(os.str()); - svgStream.drawRectangle( - scaleFactorX * dist, H-val * scaleFactorY, - scaleFactorX, val * scaleFactorY, - style); - //_________ - //| |_________ - //| || | - //| || | - //| || | - //0 sFactorX 2*sFactorX - } - svgStyle styleAxis = svgStyle().stroke("black", 1.0f); - // Draw X Axis - svgStream.drawText(.05f*W, 1.2f*H, HTML::toString(range.first), "black", "", .1f*H); - svgStream.drawText(W, 1.2*H, HTML::toString(range.second), "black", "", .1f*H); - svgStream.drawLine(0, 1.1f*H, W, 1.1f*H, styleAxis); - // Draw Y Axis - svgStream.drawText(1.2f*W, .1f*H, HTML::toString(maxi), "black", "", .1f*H); - svgStream.drawText(1.2f*W, H, "0", "black", "", .1f*H); - svgStream.drawLine(1.1f*W, 0, 1.1f*W, H, styleAxis); - - return svgStream.closeSvgFile().str(); - } -}; -/*----------------------------------------------------------------*/ - -} // namespace SVG - -#endif // _HTMLDOC_H_ diff --git a/libs/Common/HalfFloat.h b/libs/Common/HalfFloat.h index 80a0a4c9a..3f38c831a 100644 --- a/libs/Common/HalfFloat.h +++ b/libs/Common/HalfFloat.h @@ -39,10 +39,6 @@ class GENERAL_API hfloat template inline hfloat(T v) : val(fromFloat(static_cast(v))) {} - inline hfloat& operator = (hfloat v) { - val = v.val; - return *this; - } inline hfloat& operator = (float v) { val = fromFloat(v); return *this; diff --git a/libs/Common/Hash.h b/libs/Common/Hash.h index c344b3982..ccb91634f 100644 --- a/libs/Common/Hash.h +++ b/libs/Common/Hash.h @@ -291,7 +291,7 @@ class cMapWrap : public std::unordered_map inline iterator Find(const Key& key) { return this->find(key); } inline iterator Insert(const Key& key, bool& bExisted) { return Insert(key, Type(), bExisted); } inline iterator Insert(const Key& key, const Type& val, bool& bExisted) { - std::pair ret = this->insert(std::pair(key, val)); + const std::pair ret(this->emplace(key, val)); bExisted = !ret.second; return ret.first; } diff --git a/libs/Common/Line.h b/libs/Common/Line.h new file mode 100644 index 000000000..408a1a864 --- /dev/null +++ b/libs/Common/Line.h @@ -0,0 +1,95 @@ +//////////////////////////////////////////////////////////////////// +// Line.h +// +// Copyright 2023 cDc@seacave +// Distributed under the Boost Software License, Version 1.0 +// (See http://www.boost.org/LICENSE_1_0.txt) + +#ifndef __SEACAVE_LINE_H__ +#define __SEACAVE_LINE_H__ + + +// I N C L U D E S ///////////////////////////////////////////////// + + +// D E F I N E S /////////////////////////////////////////////////// + + +namespace SEACAVE { + +// S T R U C T S /////////////////////////////////////////////////// + +// Generic line class represented as two points +template +class TLine +{ + STATIC_ASSERT(DIMS > 1 && DIMS <= 3); + +public: + typedef Eigen::Matrix VECTOR; + typedef Eigen::Matrix POINT; + typedef SEACAVE::TAABB AABB; + typedef SEACAVE::TRay RAY; + enum { numScalar = (2*DIMS) }; + enum { numParams = numScalar-1 }; + + POINT pt1, pt2; // line description + + //--------------------------------------- + + inline TLine() {} + inline TLine(const POINT& pt1, const POINT& pt2); + template + inline TLine(const TLine&); + + inline void Set(const POINT& pt1, const POINT& pt2); + + int Optimize(const POINT*, size_t, int maxIters=100); + template + int Optimize(const POINT*, size_t, const RobustNormFunctor& robust, int maxIters=100); + + inline TYPE GetLength() const; + inline TYPE GetLengthSq() const; + inline POINT GetCenter() const; + inline VECTOR GetDir() const; + inline VECTOR GetNormDir() const; + inline RAY GetRay() const; + + inline bool IsSame(const TLine&, TYPE th) const; + + bool Intersects(const AABB& aabb) const; + bool Intersects(const AABB& aabb, TYPE& t) const; + + inline TYPE DistanceSq(const POINT&) const; + inline TYPE Distance(const POINT&) const; + + inline TYPE Classify(const POINT&) const; + inline POINT ProjectPoint(const POINT&) const; + + inline TYPE& operator [] (BYTE i) { ASSERT(i + void serialize(Archive& ar, const unsigned int /*version*/) { + ar & pt1; + ar & pt2; + } + #endif +}; // class TLine +/*----------------------------------------------------------------*/ + +template +struct FitLineOnline : FitPlaneOnline { + template TPoint3 GetLine(TLine& line) const; +}; +/*----------------------------------------------------------------*/ + + +#include "Line.inl" +/*----------------------------------------------------------------*/ + +} // namespace SEACAVE + +#endif // __SEACAVE_LINE_H__ diff --git a/libs/Common/Line.inl b/libs/Common/Line.inl new file mode 100644 index 000000000..bd7897cd8 --- /dev/null +++ b/libs/Common/Line.inl @@ -0,0 +1,215 @@ +// D E F I N E S /////////////////////////////////////////////////// + + +// S T R U C T S /////////////////////////////////////////////////// + + +// C L A S S ////////////////////////////////////////////////////// + +template +inline TLine::TLine(const POINT& _pt1, const POINT& _pt2) + : + pt1(_pt1), pt2(_pt2) +{ + ASSERT(!ISZERO((_pt1-_pt2).norm())); +} // constructor +template +template +inline TLine::TLine(const TLine& rhs) + : + pt1(rhs.pt1.template cast()), pt2(rhs.pt2.template cast()) +{ +} // copy constructor +/*----------------------------------------------------------------*/ + + +// set attributes +template +inline void TLine::Set(const POINT& _pt1, const POINT& _pt2) +{ + ASSERT(!ISZERO((_pt1-_pt2).norm())); + pt1 = _pt1; + pt2 = _pt2; +} +/*----------------------------------------------------------------*/ + + +// least squares refinement of the line to the given 3D point set +// (return the number of iterations) +template +template +int TLine::Optimize(const POINT* points, size_t size, const RobustNormFunctor& robust, int maxIters) +{ + ASSERT(DIMS == 3); + ASSERT(size >= numParams); + struct OptimizationFunctor { + const POINT* points; + size_t size; + double scale; + const RobustNormFunctor& robust; + // construct with the data points + OptimizationFunctor(const POINT* _points, size_t _size, const RobustNormFunctor& _robust) + : points(_points), size(_size), robust(_robust) { ASSERT(size < std::numeric_limits::max()); } + static void Residuals(const double* x, int nPoints, const void* pData, double* fvec, double* fjac, int* /*info*/) { + const OptimizationFunctor& data = *reinterpret_cast(pData); + ASSERT((size_t)nPoints == data.size && fvec != NULL && fjac == NULL); + TLine line; + for (int j=0; j())); + } + } functor(points, size, robust); + double arrParams[numParams]; + for (int j=0; j +int TLine::Optimize(const POINT* points, size_t size, int maxIters) +{ + const auto identity = [](double x) { return x; }; + return Optimize(points, size, identity, maxIters); +} // Optimize +/*----------------------------------------------------------------*/ + + +// get attributes +template +inline TYPE TLine::GetLength() const +{ + return (pt2 - pt1).norm(); +} +template +inline TYPE TLine::GetLengthSq() const +{ + return (pt2 - pt1).squaredNorm(); +} +template +inline typename TLine::POINT TLine::GetCenter() const +{ + return (pt2 + pt1) / TYPE(2); +} +template +inline typename TLine::VECTOR TLine::GetDir() const +{ + return (pt2 - pt1); +} +template +inline typename TLine::VECTOR TLine::GetNormDir() const +{ + return (pt2 - pt1).normalized(); +} +template +inline typename TLine::RAY TLine::GetRay() const +{ + return RAY(pt1, GetNormDir()); +} +/*----------------------------------------------------------------*/ + + +template +inline bool TLine::IsSame(const TLine& line, TYPE th) const +{ + const TYPE thSq(SQUARE(th)); + const VECTOR l(pt2-pt1); + const TYPE invLenSq(INVERT(l.squaredNorm())); + const VECTOR r1(pt1-line.pt1); + const TYPE dSq1((l.cross(r1)).squaredNorm()*invLenSq); + if (dSq1 > thSq) + return false; + const VECTOR r2(pt1-line.pt2); + const TYPE dSq2((l.cross(r2)).squaredNorm()*invLenSq); + return dSq2 <= thSq; +} +/*----------------------------------------------------------------*/ + + +// test for intersection with aabb +template +bool TLine::Intersects(const AABB &aabb) const +{ + return GetRay().Intersects(aabb); +} // Intersects(AABB) +template +bool TLine::Intersects(const AABB &aabb, TYPE& t) const +{ + return GetRay().Intersects(aabb, t); +} // Intersects(AABB) +/*----------------------------------------------------------------*/ + +// Computes the distance between the line and a point. +template +inline TYPE TLine::DistanceSq(const POINT& pt) const +{ + const VECTOR l(pt2-pt1), r(pt1-pt); + if (DIMS == 2) + return TYPE(SQUARE(l[0]*r[1]-r[0]*l[1])/(l[0]*l[0]+l[1]*l[1])); + ASSERT(DIMS == 3); + return TYPE((l.cross(r)).squaredNorm()/l.squaredNorm()); +} // DistanceSq(POINT) +template +inline TYPE TLine::Distance(const POINT& pt) const +{ + return SQRT(DistanceSq(pt)); +} // Distance(POINT) +/*----------------------------------------------------------------*/ + + +// Computes the position on the line segment of the point projection. +// Returns 0 if it coincides with the first point, and 1 if it coincides with the second point. +template +inline TYPE TLine::Classify(const POINT& p) const +{ + const VECTOR vL(pt2 - pt1); + ASSERT(!ISZERO(vL.squaredNorm())); + const VECTOR vP(p - pt1); + return vL.dot(vP) / vL.squaredNorm(); +} // Classify(POINT) +// Calculate point's projection on this line (closest point to this line). +template +inline typename TLine::POINT TLine::ProjectPoint(const POINT& p) const +{ + const VECTOR vL(pt2 - pt1); + ASSERT(!ISZERO(vL.squaredNorm())); + const VECTOR vP(p - pt1); + return pt1 + vL * (vL.dot(vP) / vL.squaredNorm()); +} // ProjectPoint +/*----------------------------------------------------------------*/ + + +template +template +TPoint3 FitLineOnline::GetLine(TLine& line) const +{ + TPoint3 avg, dir; + const TPoint3 quality(this->GetModel(avg, dir)); + const TPoint3 pt2(avg+dir); + line.Set(TPoint3(avg), TPoint3(pt2)); + return TPoint3(quality); +} +/*----------------------------------------------------------------*/ diff --git a/libs/Common/List.h b/libs/Common/List.h index 7b714308c..bf45e47a2 100644 --- a/libs/Common/List.h +++ b/libs/Common/List.h @@ -11,6 +11,9 @@ // I N C L U D E S ///////////////////////////////////////////////// +#include +#include + // D E F I N E S /////////////////////////////////////////////////// @@ -21,8 +24,10 @@ // cList index type #ifdef _SUPPORT_CPP11 #define ARR2IDX(arr) typename std::remove_reference::type::size_type +#define SIZE2IDX(arr) typename std::remove_const::type>::type #else #define ARR2IDX(arr) IDX +#define SIZE2IDX(arr) IDX #endif // cList iterator by index @@ -42,10 +47,10 @@ // raw data array iterator by index #ifndef FOREACHRAW -#define FOREACHRAW(var, sz) for (IDX var=0, var##Size=(sz); var0; ) +#define RFOREACHRAW(var, sz) for (SIZE2IDX(sz) var=sz; var-->0; ) #endif // raw data array iterator by pointer #ifndef FOREACHRAWPTR @@ -57,11 +62,11 @@ // constructs a cList reference to a given raw data array #ifndef CLISTREFRAW -#define CLISTREFRAW(CLIST, var, arr, sz) uint8_t _ArrData##var[sizeof(CLIST)]; new(_ArrData##var) CLIST(sz, arr); const CLIST& var(*((const CLIST*)_ArrData##var)) +#define CLISTREFRAW(CLIST, var, arr, sz) uint8_t _ArrData##var[sizeof(CLIST)]; new(_ArrData##var) CLIST(sz, const_cast(arr)); const CLIST& var(*reinterpret_cast(_ArrData##var)) #endif // constructs a cList reference to a given std::_vector #ifndef CLISTREFVECTOR -#define CLISTREFVECTOR(CLIST, var, vec) uint8_t _ArrData##var[sizeof(CLIST)]; new(_ArrData##var) CLIST(vec.size(), &vec[0]); const CLIST& var(*((const CLIST*)_ArrData##var)) +#define CLISTREFVECTOR(CLIST, var, vec) uint8_t _ArrData##var[sizeof(CLIST)]; new(_ArrData##var) CLIST(vec.size(), const_cast(&vec[0])); const CLIST& var(*reinterpret_cast(_ArrData##var)) #endif #define CLISTDEF0(TYPE) SEACAVE::cList< TYPE, const TYPE&, 0 > @@ -70,6 +75,10 @@ #define CLISTDEFIDX(TYPE,IDXTYPE) SEACAVE::cList< TYPE, const TYPE&, 1, 16, IDXTYPE > #define CLISTDEF2IDX(TYPE,IDXTYPE) SEACAVE::cList< TYPE, const TYPE&, 2, 16, IDXTYPE > +#ifndef STCALL +#define STCALL +#endif + namespace SEACAVE { @@ -113,14 +122,14 @@ class cList } // construct a list containing size initialized elements - cList(IDX size) : _size(size), _vectorSize(size), _vector((TYPE*)operator new[] (size * sizeof(TYPE))) + cList(IDX size) : _size(size), _vectorSize(size), _vector((TYPE*)operator new[] (static_cast(size) * sizeof(TYPE))) { ASSERT(size > 0 && size < NO_INDEX); _ArrayConstruct(_vector, size); } // construct a list containing size initialized elements and allocated space for _reserved elements - cList(IDX size, IDX _reserved) : _size(size), _vectorSize(_reserved), _vector((TYPE*)operator new[] (_reserved * sizeof(TYPE))) + cList(IDX size, IDX _reserved) : _size(size), _vectorSize(_reserved), _vector((TYPE*)operator new[] (static_cast(_reserved) * sizeof(TYPE))) { ASSERT(_reserved >= size && _reserved < NO_INDEX); _ArrayConstruct(_vector, size); @@ -133,9 +142,23 @@ class cList ASSERT(_size == 0); return; } - _vector = (TYPE*)(operator new[] (_vectorSize * sizeof(TYPE))); + _vector = (TYPE*)(operator new[] (static_cast(_vectorSize) * sizeof(TYPE))); _ArrayCopyConstruct(_vector, rList._vector, _size); } + // move constructor: creates a move-copy of the given list + cList(cList&& rList) : _size(rList._size), _vectorSize(rList._vectorSize), _vector(rList._vector) + { + rList._Init(); + } + + // constructor a list from a raw data array + explicit inline cList(TYPE* pDataBegin, TYPE* pDataEnd) : _size((IDX)(pDataEnd-pDataBegin)), _vectorSize(_size) + { + if (_vectorSize == 0) + return; + _vector = (TYPE*) operator new[] (static_cast(_vectorSize) * sizeof(TYPE)); + _ArrayCopyConstruct(_vector, pDataBegin, _size); + } // constructor a list from a raw data array, taking ownership of the array memory explicit inline cList(IDX nSize, TYPE* pData) : _size(nSize), _vectorSize(nSize), _vector(pData) @@ -147,103 +170,119 @@ class cList _Release(); } + // move the content from the given list + inline cList& operator=(cList&& rList) + { + return CopyOfRemove(rList); + } // copy the content from the given list - inline cList& operator=(const cList& rList) + inline cList& operator=(const cList& rList) { return CopyOf(rList); } - inline cList& CopyOf(const cList& rList, bool bForceResize=false) + inline cList& CopyOf(const cList& rList, bool bForceResize=false) { if (this == &rList) - return (*this); + return *this; if (bForceResize || _vectorSize < rList._vectorSize) { _Release(); _vectorSize = rList._vectorSize; - _vector = (TYPE*) operator new[] (_vectorSize * sizeof(TYPE)); + _vector = (TYPE*) operator new[] (static_cast(_vectorSize) * sizeof(TYPE)); _ArrayCopyConstruct(_vector, rList._vector, rList._size); + } else if (_size >= rList._size) { + _ArrayDestruct(_vector+rList._size, _size-rList._size); + _ArrayCopyRestrict(_vector, rList._vector, rList._size); } else { - if (_size >= rList._size) { - _ArrayDestruct(_vector+rList._size, _size-rList._size); - _ArrayCopyRestrict(_vector, rList._vector, rList._size); - } else { - _ArrayCopyRestrict(_vector, rList._vector, _size); - _ArrayCopyConstruct(_vector+_size, rList._vector+_size, rList._size-_size); - } + _ArrayCopyRestrict(_vector, rList._vector, _size); + _ArrayCopyConstruct(_vector+_size, rList._vector+_size, rList._size-_size); } _size = rList._size; - return (*this); + return *this; } - inline cList& CopyOf(const TYPE* pData, IDX nSize, bool bForceResize=false) + inline cList& CopyOf(const TYPE* pData, IDX nSize, bool bForceResize=false) { if (_vector == pData) - return (*this); + return *this; if (bForceResize || _vectorSize < nSize) { _Release(); _vectorSize = nSize; - _vector = (TYPE*) operator new[] (_vectorSize * sizeof(TYPE)); + _vector = (TYPE*) operator new[] (static_cast(_vectorSize) * sizeof(TYPE)); _ArrayCopyConstruct(_vector, pData, nSize); + } else if (_size >= nSize) { + _ArrayDestruct(_vector+nSize, _size-nSize); + _ArrayCopyRestrict(_vector, pData, nSize); } else { - if (_size >= nSize) { - _ArrayDestruct(_vector+nSize, _size-nSize); - _ArrayCopyRestrict(_vector, pData, nSize); - } else { - _ArrayCopyRestrict(_vector, pData, _size); - _ArrayCopyConstruct(_vector+_size, pData+_size, nSize-_size); - } + _ArrayCopyRestrict(_vector, pData, _size); + _ArrayCopyConstruct(_vector+_size, pData+_size, nSize-_size); } _size = nSize; - return (*this); + return *this; } // release current list and swap the content with the given list - inline cList& CopyOfRemove(cList& rList) + inline cList& CopyOfRemove(cList& rList) { if (this == &rList) - return (*this); + return *this; _Release(); _size = rList._size; _vectorSize = rList._vectorSize; _vector = rList._vector; - rList._vector = NULL; - rList._size = rList._vectorSize = 0; - return (*this); + rList._Init(); + return *this; } - inline void Join(const cList& rList) + inline cList& Join(const cList& rList) { if (this == &rList || rList._size == 0) - return; + return *this; const IDX newSize = _size + rList._size; Reserve(newSize); _ArrayCopyConstruct(_vector+_size, rList._vector, rList._size); _size = newSize; + return *this; } - inline void Join(const TYPE* pData, IDX nSize) + inline cList& Join(const TYPE* pData, IDX nSize) { const IDX newSize = _size + nSize; Reserve(newSize); _ArrayCopyConstruct(_vector+_size, pData, nSize); _size = newSize; + return *this; + } + + template + inline cList& JoinFunctor(IDX nSize, const Functor& functor) { + Reserve(_size + nSize); + if (useConstruct) { + for (IDX n=0; n(_vector+_size, rList._vector, rList._size); _size = newSize; rList._size = 0; + return *this; } // Swap the elements of the two lists. - inline void Swap(cList& rList) + inline cList& Swap(cList& rList) { if (this == &rList) - return; + return *this; const IDX tmpSize = _size; _size = rList._size; rList._size = tmpSize; @@ -253,21 +292,31 @@ class cList TYPE* const tmpVector = _vector; _vector = rList._vector; rList._vector = tmpVector; + return *this; } // Swap the two elements. - inline void Swap(IDX idx1, IDX idx2) + inline void Swap(IDX idx1, IDX idx2) { ASSERT(idx1 < _size && idx2 < _size); TYPE tmp = _vector[idx1]; _vector[idx1] = _vector[idx2]; _vector[idx2] = tmp; } + + inline bool operator==(const cList& rList) const { + if (_size != rList._size) + return false; + for (IDX i = 0; i < _size; ++i) + if (_vector[i] != rList._vector[i]) + return false; + return true; + } // Set the allocated memory (normally used for types without constructor). inline void Memset(uint8_t val) { - memset(_vector, val, _size * sizeof(TYPE)); + memset(_vector, val, static_cast(_size) * sizeof(TYPE)); } inline void MemsetValue(ARG_TYPE val) { @@ -285,7 +334,7 @@ class cList _vector = NULL; return; } - _vector = (TYPE*) operator new[] (newSize * sizeof(TYPE)); + _vector = (TYPE*) operator new[] (static_cast(newSize) * sizeof(TYPE)); _ArrayConstruct(_vector, newSize); } @@ -357,7 +406,11 @@ class cList } inline size_t GetDataSize() const { - return sizeof(TYPE)*_size; + return sizeof(TYPE)*static_cast(_size); + } + inline size_t GetMemorySize() const + { + return sizeof(cList)+sizeof(TYPE)*static_cast(_vectorSize); } inline TYPE* Begin() const @@ -609,13 +662,22 @@ class cList inline TYPE& GetNth(IDX index) { + ASSERT(index < _size); TYPE* const nth(Begin()+index); std::nth_element(Begin(), nth, End()); return *nth; } - inline TYPE& GetMedian() + template ::value,TYPE,REAL>::type> + inline RTYPE GetMedian() { - return GetNth(_size >> 1); + ASSERT(_size > 0); + if (_size%2) + return static_cast(GetNth(_size >> 1)); + TYPE* const nth(Begin() + (_size>>1)); + std::nth_element(Begin(), nth, End()); + TYPE* const nth1(nth-1); + std::nth_element(Begin(), nth1, nth); + return (static_cast(*nth1) + static_cast(*nth)) / RTYPE(2); } inline TYPE GetMean() @@ -1095,6 +1157,17 @@ class cList } } + // remove duplicated values; the values are expected to be sorted, set bSort if not + template + inline void RemoveDuplicates() + { + if (bSort) + Sort(); + const IDX index(std::unique(Begin(), End()) - Begin()); + if (index < _size) + RemoveLast(_size-index); + } + inline void RemoveAtMove(IDX index) { ASSERT(index < _size); @@ -1198,12 +1271,17 @@ class cList { ASSERT(newVectorSize > _vectorSize); // grow by 50% or at least to minNewVectorSize - const IDX expoVectorSize(_vectorSize + (_vectorSize>>1)); + IDX expoVectorSize(_vectorSize + (_vectorSize>>1)); + // cap growth for very large vectors + const size_t maxGrowCapacity(3*1024*1024*1024ull/*3GB*/); + const size_t growCapacity(static_cast(expoVectorSize - _vectorSize) * sizeof(TYPE)); + if (growCapacity > maxGrowCapacity) + expoVectorSize = _vectorSize + static_cast(maxGrowCapacity / sizeof(TYPE)); + // allocate a larger chunk of memory, copy the data and delete the old chunk if (newVectorSize < expoVectorSize) newVectorSize = expoVectorSize; - // allocate a larger chunk of memory, copy the data and delete the old chunk TYPE* const tmp(_vector); - _vector = (TYPE*) operator new[] (newVectorSize * sizeof(TYPE)); + _vector = (TYPE*) operator new[] (static_cast(newVectorSize) * sizeof(TYPE)); _ArrayMoveConstruct(_vector, tmp, _size); _vectorSize = newVectorSize; operator delete[] (tmp); @@ -1225,7 +1303,7 @@ class cList _vector = NULL; } else { TYPE* const tmp(_vector); - _vector = (TYPE*) operator new[] (_vectorSize * sizeof(TYPE)); + _vector = (TYPE*) operator new[] (static_cast(_vectorSize) * sizeof(TYPE)); _ArrayMoveConstruct(_vector, tmp, _vectorSize); operator delete[] (tmp); } @@ -1287,7 +1365,7 @@ class cList (src+n)->~TYPE(); } } else { - const size_t _size(sizeof(TYPE)*n); + const size_t _size(sizeof(TYPE)*static_cast(n)); if (bRestrict) memcpy((void*)dst, (const void*)src, _size); else @@ -1304,7 +1382,7 @@ class cList (src+n)->~TYPE(); } } else { - const size_t _size(sizeof(TYPE)*n); + const size_t _size(sizeof(TYPE)*static_cast(n)); if (useConstruct == 1) while (n--) (dst+n)->~TYPE(); @@ -1334,7 +1412,7 @@ class cList typedef std::vector VectorType; inline cList(const VectorType& rList) { CopyOf(&rList[0], rList.size()); } #ifdef _SUPPORT_CPP11 - inline cList(std::initializer_list l) : _size(0), _vectorSize((size_type)l.size()), _vector(NULL) { ASSERT(l.size() l) : _size(0), _vectorSize((size_type)l.size()), _vector(NULL) { ASSERT(l.size()(_vectorSize)*sizeof(Type)); const Type* first(l.begin()); do new(_vector + _size++) Type(*first++); while (first!=l.end()); } #endif inline bool empty() const { return IsEmpty(); } inline size_type size() const { return GetSize(); } @@ -1393,106 +1471,93 @@ inline bool ValidIDX(const IDX_TYPE& idx) { // some test functions -// run cListTest(99999); -#if 0 -inline bool cListTestIter(unsigned elems) { - std::vector arrR; - cList arr0; - cList arr1; - cList arr2; - cList arrC; - for (unsigned i=0; i +inline bool cListTest(unsigned iters) { + for (unsigned i=0; i arrR; + cList arr0; + cList arr1; + cList arr2; + cList arrC; + for (unsigned i=0; i= arrR.size()) - continue; - arrR.erase(arrR.begin()+nDel, arrR.begin()+nDel+nCount); - arr0.RemoveAtMove(nDel, nCount); - arr1.RemoveAtMove(nDel, nCount); - arr2.RemoveAtMove(nDel, nCount); - } - if (arrR.size() != arr0.GetSize() || - arrR.size() != arr1.GetSize() || - arrR.size() != arr2.GetSize()) - { - ASSERT("there is a problem" == NULL); - return false; - } - for (size_t i=0; i arrS(1+RAND()%(2*elems)); - for (size_t i=0; i<6; ++i) { - arrS.Insert(RAND()); - } - arrS.RemoveLast(RAND()%arrS.GetSize()); - arrS.CopyOf(&arrR[0], arrR.size()); - for (size_t i=0; i<6; ++i) { - arrS.Insert(RAND()); - } - arrS.RemoveLast(6); - for (size_t i=0; i= arrR.size()) + continue; + arrR.erase(arrR.begin()+nDel, arrR.begin()+nDel+nCount); + arr0.RemoveAtMove(nDel, nCount); + arr1.RemoveAtMove(nDel, nCount); + arr2.RemoveAtMove(nDel, nCount); + } + if (arrR.size() != arr0.GetSize() || + arrR.size() != arr1.GetSize() || + arrR.size() != arr2.GetSize()) { ASSERT("there is a problem" == NULL); return false; } + for (size_t i=0; i arrS(1+RAND()%(2*elems)); + for (size_t i=0; i<6; ++i) { + arrS.Insert(RAND()); + } + arrS.RemoveLast(RAND()%arrS.GetSize()); + arrS.CopyOf(&arrR[0], arrR.size()); + for (size_t i=0; i<6; ++i) { + arrS.Insert(RAND()); + } + arrS.RemoveLast(6); + for (size_t i=0; i +* +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see . +* +* +* Additional Terms: +* +* You are required to preserve legal notices and author attributions in +* that material or in the Appropriate Legal Notices displayed by works +* containing it. +*/ + +#pragma once +#ifndef _MVS_LISTFIFO_H_ +#define _MVS_LISTFIFO_H_ + + +// I N C L U D E S ///////////////////////////////////////////////// + +#include +#include + + +// S T R U C T S /////////////////////////////////////////////////// + +namespace SEACAVE { + +// Tracks accesses of some hash-able type T and records the least recently accessed. +template +class ListFIFO { +public: + // add or move an key to the front + void Put(const T& key) { + const auto it = map.find(key); + if (it != map.end()) { + // if key exists, remove it from its current position + order.erase(it->second); + } + // add the key to the front + order.push_front(key); + map[key] = order.begin(); + } + + // remove and return the least used key (from the back) + T Pop() { + ASSERT(!IsEmpty()); + const T leastUsed = order.back(); + order.pop_back(); + map.erase(leastUsed); + return leastUsed; + } + + // return the least used key (from the back) + const T& Back() { + ASSERT(!IsEmpty()); + return order.back(); + } + + // check if the list is empty + bool IsEmpty() const { + return order.empty(); + } + + // get the size of the list + size_t Size() const { + return order.size(); + } + + // return true if the key is in the list + bool Contains(const T& key) const { + return map.find(key) != map.end(); + } + + // return the keys currently in cache + const std::list& GetCachedValues() const { + return order; + } + + // print the current order of elements + void PrintOrder() const { + std::cout << "Current order: "; + for (const auto& element : order) { + std::cout << element << " "; + } + std::cout << std::endl; + } + +private: + std::list order; + std::unordered_map::iterator> map; +}; +/*----------------------------------------------------------------*/ + +} // namespace SEACAVE + +#endif diff --git a/libs/Common/OBB.h b/libs/Common/OBB.h index 30ff5fcb9..adf321e5a 100644 --- a/libs/Common/OBB.h +++ b/libs/Common/OBB.h @@ -42,16 +42,23 @@ class TOBB enum { numCorners = (DIMS==1 ? 2 : (DIMS==2 ? 4 : 8)) }; // 2^DIMS enum { numScalar = (5*DIMS) }; - MATRIX m_rot; // rotation matrix of the transformation (orthonormal axes) - POINT m_pos; // translation of the transformation (center-point) - POINT m_ext; // bounding box extents (half axis length) + MATRIX m_rot; // rotation matrix from world to local (orthonormal axes) + POINT m_pos; // translation from local to world (center-point) + POINT m_ext; // bounding box extents in local (half axis length) //--------------------------------------- inline TOBB() {} + inline TOBB(bool); + inline TOBB(const AABB&); + inline TOBB(const MATRIX& rot, const POINT& ptMin, const POINT& ptMax); inline TOBB(const POINT* pts, size_t n); inline TOBB(const POINT* pts, size_t n, const TRIANGLE* tris, size_t s); + template + inline TOBB(const TOBB&); + inline void Set(const AABB&); // build from AABB + inline void Set(const MATRIX& rot, const POINT& ptMin, const POINT& ptMax); // build from rotation matrix from world to local, and local min/max corners inline void Set(const POINT* pts, size_t n); // build from points inline void Set(const POINT* pts, size_t n, const TRIANGLE* tris, size_t s); // build from triangles inline void Set(const MATRIX& C, const POINT* pts, size_t n); // build from covariance matrix @@ -62,8 +69,10 @@ class TOBB inline void BuildAdd(const POINT&); // add a new point to the online build inline void BuildEnd(); // end online build for computing the rotation - inline void Enlarge(TYPE); - inline void EnlargePercent(TYPE); + inline bool IsValid() const; + + inline TOBB& Enlarge(TYPE); + inline TOBB& EnlargePercent(TYPE); inline void Translate(const POINT&); inline void Transform(const MATRIX&); @@ -79,8 +88,33 @@ class TOBB inline TYPE GetVolume() const; + bool Intersects(const POINT&) const; + inline TYPE& operator [] (BYTE i) { ASSERT(i> (std::istream& st, TOBB& obb) { + st >> obb.m_rot; + st >> obb.m_pos; + st >> obb.m_ext; + return st; + } + + #ifdef _USE_BOOST + // implement BOOST serialization + template + void serialize(Archive& ar, const unsigned int /*version*/) { + ar & m_rot; + ar & m_pos; + ar & m_ext; + } + #endif }; // class TOBB /*----------------------------------------------------------------*/ diff --git a/libs/Common/OBB.inl b/libs/Common/OBB.inl index 94fe16566..6a8dd7622 100644 --- a/libs/Common/OBB.inl +++ b/libs/Common/OBB.inl @@ -11,6 +11,24 @@ // S T R U C T S /////////////////////////////////////////////////// +template +inline TOBB::TOBB(bool) + : + m_rot(MATRIX::Identity()), + m_pos(POINT::Zero()), + m_ext(POINT::Zero()) +{ +} +template +inline TOBB::TOBB(const AABB& aabb) +{ + Set(aabb); +} +template +inline TOBB::TOBB(const MATRIX& rot, const POINT& ptMin, const POINT& ptMax) +{ + Set(rot, ptMin, ptMax); +} template inline TOBB::TOBB(const POINT* pts, size_t n) { @@ -21,9 +39,35 @@ inline TOBB::TOBB(const POINT* pts, size_t n, const TRIANGLE* tris, s { Set(pts, n, tris, s); } // constructor +template +template +inline TOBB::TOBB(const TOBB& rhs) + : + m_rot(rhs.m_rot.template cast()), + m_pos(rhs.m_pos.template cast()), + m_ext(rhs.m_ext.template cast()) +{ +} // copy constructor /*----------------------------------------------------------------*/ +template +inline void TOBB::Set(const AABB& aabb) +{ + m_rot.setIdentity(); + m_pos = aabb.GetCenter(); + m_ext = aabb.GetSize()/TYPE(2); +} + +// build from rotation matrix from world to local, and local min/max corners +template +inline void TOBB::Set(const MATRIX& rot, const POINT& ptMin, const POINT& ptMax) +{ + m_rot = rot; + m_pos = (ptMax + ptMin) * TYPE(0.5); + m_ext = (ptMax - ptMin) * TYPE(0.5); +} + // Inspired from "Fitting Oriented Bounding Boxes" by James Gregson // http://jamesgregson.blogspot.ro/2011/03/latex-test.html @@ -140,24 +184,12 @@ template inline void TOBB::SetRotation(const MATRIX& C) { // extract the eigenvalues and eigenvectors from C - const Eigen::EigenSolver es(C); - if (es.info() != Eigen::Success) - return; - const MATRIX eigvec(es.eigenvectors().real()); - const POINT eigval(es.eigenvalues().real()); - int indices[3] = {0,1,2}; - std::sort(indices, indices+3, [&] (int i, int j) { - return eigval(i) < eigval(j); - }); - + const Eigen::SelfAdjointEigenSolver es(C); + ASSERT(es.info() == Eigen::Success); // find the right, up and forward vectors from the eigenvectors // and set the rotation matrix using the eigenvectors - m_rot.row(0) = eigvec.col(indices[0]); - m_rot.row(1) = eigvec.col(indices[1]); - m_rot.row(2) = eigvec.col(indices[2]); - ASSERT(ISEQUAL(m_rot.row(0).norm(), TYPE(1))); - ASSERT(ISEQUAL(m_rot.row(1).norm(), TYPE(1))); - ASSERT(ISEQUAL(m_rot.row(2).norm(), TYPE(1))); + ASSERT(es.eigenvalues()(0) < es.eigenvalues()(1) && es.eigenvalues()(1) < es.eigenvalues()(2)); + m_rot = es.eigenvectors().transpose(); if (m_rot.determinant() < 0) m_rot = -m_rot; } @@ -235,19 +267,46 @@ inline void TOBB::BuildEnd() /*----------------------------------------------------------------*/ +// check if the oriented bounding box has positive size template -inline void TOBB::Enlarge(TYPE x) +inline bool TOBB::IsValid() const { - m_ext.array() -= x; + return m_ext.minCoeff() > TYPE(0); +} // IsValid +/*----------------------------------------------------------------*/ + + +template +inline TOBB& TOBB::Enlarge(TYPE x) +{ + m_ext.array() += x; + return *this; } template -inline void TOBB::EnlargePercent(TYPE x) +inline TOBB& TOBB::EnlargePercent(TYPE x) { m_ext *= x; + return *this; } // Enlarge /*----------------------------------------------------------------*/ +// Update the box by the given pos delta. +template +inline void TOBB::Translate(const POINT& d) +{ + m_pos += d; +} +// Update the box by the given transform. +template +inline void TOBB::Transform(const MATRIX& m) +{ + m_rot = m * m_rot; + m_pos = m * m_pos; +} +/*----------------------------------------------------------------*/ + + template inline typename TOBB::POINT TOBB::GetCenter() const { @@ -282,10 +341,11 @@ inline void TOBB::GetCorners(POINT pts[numCorners]) const m_rot.row(0)*m_ext[0], m_rot.row(1)*m_ext[1] }; - pts[0] = m_pos - pEAxis[0] - pEAxis[1]; - pts[1] = m_pos + pEAxis[0] - pEAxis[1]; - pts[2] = m_pos + pEAxis[0] + pEAxis[1]; - pts[3] = m_pos - pEAxis[0] + pEAxis[1]; + const POINT pos(m_rot.transpose()*m_pos); + pts[0] = pos - pEAxis[0] - pEAxis[1]; + pts[1] = pos + pEAxis[0] - pEAxis[1]; + pts[2] = pos + pEAxis[0] + pEAxis[1]; + pts[3] = pos - pEAxis[0] + pEAxis[1]; } if (DIMS == 3) { const POINT pEAxis[3] = { @@ -293,41 +353,24 @@ inline void TOBB::GetCorners(POINT pts[numCorners]) const m_rot.row(1)*m_ext[1], m_rot.row(2)*m_ext[2] }; - pts[0] = m_pos - pEAxis[0] - pEAxis[1] - pEAxis[2]; - pts[1] = m_pos + pEAxis[0] - pEAxis[1] - pEAxis[2]; - pts[2] = m_pos + pEAxis[0] + pEAxis[1] - pEAxis[2]; - pts[3] = m_pos - pEAxis[0] + pEAxis[1] - pEAxis[2]; - pts[4] = m_pos - pEAxis[0] - pEAxis[1] + pEAxis[2]; - pts[5] = m_pos + pEAxis[0] - pEAxis[1] + pEAxis[2]; - pts[6] = m_pos + pEAxis[0] + pEAxis[1] + pEAxis[2]; - pts[7] = m_pos - pEAxis[0] + pEAxis[1] + pEAxis[2]; + const POINT pos(m_rot.transpose()*m_pos); + pts[0] = pos - pEAxis[0] - pEAxis[1] - pEAxis[2]; + pts[1] = pos - pEAxis[0] - pEAxis[1] + pEAxis[2]; + pts[2] = pos + pEAxis[0] - pEAxis[1] - pEAxis[2]; + pts[3] = pos + pEAxis[0] - pEAxis[1] + pEAxis[2]; + pts[4] = pos + pEAxis[0] + pEAxis[1] - pEAxis[2]; + pts[5] = pos + pEAxis[0] + pEAxis[1] + pEAxis[2]; + pts[6] = pos - pEAxis[0] + pEAxis[1] - pEAxis[2]; + pts[7] = pos - pEAxis[0] + pEAxis[1] + pEAxis[2]; } } // GetCorners // constructs the corner of the aligned bounding box in world space template inline typename TOBB::AABB TOBB::GetAABB() const { - if (DIMS == 2) { - const POINT pEAxis[2] = { - m_rot.row(0)*m_ext[0], - m_rot.row(1)*m_ext[1] - }; - return AABB( - m_pos - pEAxis[0] - pEAxis[1], - m_pos + pEAxis[0] + pEAxis[1] - ); - } - if (DIMS == 3) { - const POINT pEAxis[3] = { - m_rot.row(0)*m_ext[0], - m_rot.row(1)*m_ext[1], - m_rot.row(2)*m_ext[2] - }; - return AABB( - m_pos - pEAxis[0] - pEAxis[1] - pEAxis[2], - m_pos + pEAxis[0] + pEAxis[1] + pEAxis[2] - ); - } + POINT pts[numCorners]; + GetCorners(pts); + return AABB(pts, numCorners); } // GetAABB /*----------------------------------------------------------------*/ @@ -341,20 +384,18 @@ inline TYPE TOBB::GetVolume() const /*----------------------------------------------------------------*/ -// Update the box by the given pos delta. -template -inline void TOBB::Translate(const POINT& d) -{ - m_pos += d; -} -/*----------------------------------------------------------------*/ - - -// Update the box by the given transform. template -inline void TOBB::Transform(const MATRIX& m) +bool TOBB::Intersects(const POINT& pt) const { - m_rot = m * m_rot; - m_pos = m * m_pos; -} + const POINT dist(m_rot * (pt - m_pos)); + if (DIMS == 2) { + return ABS(dist[0]) <= m_ext[0] + && ABS(dist[1]) <= m_ext[1]; + } + if (DIMS == 3) { + return ABS(dist[0]) <= m_ext[0] + && ABS(dist[1]) <= m_ext[1] + && ABS(dist[2]) <= m_ext[2]; + } +} // Intersects(POINT) /*----------------------------------------------------------------*/ diff --git a/libs/Common/Octree.h b/libs/Common/Octree.h index cc5f22e95..de1bbadbf 100644 --- a/libs/Common/Octree.h +++ b/libs/Common/Octree.h @@ -22,106 +22,84 @@ namespace SEACAVE { // S T R U C T S /////////////////////////////////////////////////// -// raw array wrapper -template -class TItemArr -{ -public: - typedef TYPE Type; - - inline TItemArr() {} - inline TItemArr(const TYPE* data, IDX size) : m_data(data), m_size(size) {} - inline void Set(const TYPE* data, IDX size) { m_data = data; m_size = size; } - - inline const TYPE& operator[](IDX i) const { return m_data[i]; } - inline const TYPE* Begin() const { return m_data; } - inline const TYPE* GetData() const { return m_data; } - inline IDX GetSize() const { return m_size; } - -protected: - const TYPE* m_data; - IDX m_size; -}; // class TItemArr -/*----------------------------------------------------------------*/ - - -// octree cell class -template -class TOctreeCell +// basic octree class +// each item should define the operator const POINT_TYPE& returning its center; +// at build time, a functor must be supplied that returns true as long as +// the current cell should be split farther, given its number of items and radius, ex: +// [](Octree::IDX_TYPE size, Octree::Type radius) { +// return size > SIZE && radius > RADIUS; +// } +// where: +// SIZE is the minimum number of items contained by the cell so that this to be divided further +// RADIUS is the minimum size of the cell allowed to be divided further +// both conditions represent exclusive limits and both should be true for the division to take place +template +class TOctree { STATIC_ASSERT(DIMS > 0 && DIMS <= 3); public: + typedef TYPE Type; + typedef typename ITEMARR_TYPE::Type ITEM_TYPE; + typedef typename ITEMARR_TYPE::IDX IDX_TYPE; + typedef SEACAVE::cList IDXARR_TYPE; typedef Eigen::Matrix POINT_TYPE; typedef SEACAVE::TAABB AABB_TYPE; typedef uint32_t SIZE_TYPE; - typedef struct { - POINT_TYPE center; // center of the current cell - } NODE_TYPE; - typedef struct { - IDX idxBegin; // index in the global array of the first item contained by this cell - SIZE_TYPE size; // number of items contained by this cell in the global array - DATA_TYPE data; // user data associated with this leaf - } LEAF_TYPE; - enum { dataSize = (sizeof(NODE_TYPE)>sizeof(LEAF_TYPE) ? sizeof(NODE_TYPE) : sizeof(LEAF_TYPE)) }; - enum { numChildren = (2<<(DIMS-1)) }; - -public: - inline TOctreeCell(); - inline TOctreeCell(TOctreeCell*); - inline ~TOctreeCell(); - - inline void Release(); - inline void Swap(TOctreeCell&); - - inline unsigned ComputeChild(const POINT_TYPE& item) const; - static void ComputeCenter(POINT_TYPE []); - - inline bool IsLeaf() const { return (m_child==NULL); } - inline const TOctreeCell& GetChild(int i) const { ASSERT(!IsLeaf() && i -class TOctree -{ -public: - typedef TYPE Type; - typedef TOctreeCell CELL_TYPE; - typedef typename ITEMARR_TYPE::Type ITEM_TYPE; - typedef SEACAVE::cList IDXARR_TYPE; - typedef SEACAVE::cList CELLPTRARR_TYPE; - typedef typename IDXARR_TYPE::Type IDX_TYPE; - typedef typename CELL_TYPE::POINT_TYPE POINT_TYPE; - typedef typename CELL_TYPE::AABB_TYPE AABB_TYPE; + class CELL_TYPE { + public: + typedef struct { + POINT_TYPE center; // center of the current cell + } NODE_TYPE; + typedef struct { + IDX_TYPE idxBegin; // index in the global array of the first item contained by this cell + SIZE_TYPE size; // number of items contained by this cell in the global array + DATA_TYPE data; // user data associated with this leaf + } LEAF_TYPE; + enum { numChildren = (2<<(DIMS-1)) }; + + public: + inline CELL_TYPE(); + inline ~CELL_TYPE(); + + inline void Release(); + inline void Swap(CELL_TYPE&); + + inline unsigned ComputeChild(const POINT_TYPE& item) const; + static void ComputeCenter(POINT_TYPE []); + static inline POINT_TYPE ComputeChildCenter(const POINT_TYPE&, TYPE, unsigned); + + inline bool IsLeaf() const { return (m_child==NULL); } + inline const CELL_TYPE& GetChild(int i) const { ASSERT(!IsLeaf() && i CELLPTRARR_TYPE; struct IndexInserter { IDXARR_TYPE& indices; IndexInserter(IDXARR_TYPE& _indices) : indices(_indices) {} void operator()(IDX_TYPE idx) { indices.Insert(idx); } - void operator()(const IDX_TYPE* idices, size_t size) { indices.Join(idices, size); } + void operator()(const IDX_TYPE* idices, SIZE_TYPE size) { indices.Join(idices, size); } }; struct CellInserter { @@ -132,17 +110,23 @@ class TOctree public: inline TOctree() {} - inline TOctree(const ITEMARR_TYPE&); - inline TOctree(const ITEMARR_TYPE&, const AABB_TYPE&); + template + inline TOctree(const ITEMARR_TYPE&, Functor split); + template + inline TOctree(const ITEMARR_TYPE&, const AABB_TYPE&, Functor split); inline void Release(); inline void Swap(TOctree&); - void Insert(const ITEMARR_TYPE&); - void Insert(const ITEMARR_TYPE&, const AABB_TYPE&); + template + void Insert(const ITEMARR_TYPE&, Functor split); + template + void Insert(const ITEMARR_TYPE&, const AABB_TYPE&, Functor split); template inline void CollectCells(INSERTER&) const; + template + void CollectCells(const CELL_TYPE&, INSERTER&) const; template inline void ParseCells(PARSER&); @@ -170,30 +154,28 @@ class TOctree template inline void TraverseCells(const TFrustum&, CELLPTRARR_TYPE&); + template + void SplitVolume(float maxArea, AREAESTIMATOR& areaEstimator, CHUNKINSERTER& chunkInserter); + + inline const CELL_TYPE& GetRoot() const { return m_root; } + inline TYPE GetRadius() const { return m_radius; } inline AABB_TYPE GetAabb() const { return m_root.GetAabb(m_radius); } - inline TYPE GetRadius(const AABB_TYPE& aabb) const { - // radius of the root cell - const POINT_TYPE size(aabb.GetSize() / TYPE(2)); - TYPE radius = size[0]; - if (DIMS > 1 && radius < size[1]) - radius = size[1]; - if (DIMS > 2 && radius < size[2]) - radius = size[2]; - return radius; - } - inline bool IsEmpty() const { return m_indices.IsEmpty(); } - inline size_t GetNumItems() const { return m_indices.GetSize(); } + inline bool IsEmpty() const { return m_indices.empty(); } + inline size_t GetNumItems() const { return m_indices.size(); } inline const IDXARR_TYPE& GetIndexArr() const { return m_indices; } inline const ITEM_TYPE* GetItems() const { return m_items; } + inline void ResetItems() { m_items = NULL; } protected: - static inline POINT_TYPE ComputeChildCenter(const POINT_TYPE&, TYPE, unsigned); - - void _Insert(CELL_TYPE&, TYPE, IDXARR_TYPE []); - void _Insert(CELL_TYPE&, const POINT_TYPE&, TYPE, IDXARR_TYPE&); + template + struct _InsertData { + enum : IDX_TYPE { NO_INDEX = DECLARE_NO_INDEX(IDX_TYPE) }; + IDXARR_TYPE successors; // single connected list of next item indices + Functor split; // used to decide if a cell needs to be split farther + }; + template + void _Insert(CELL_TYPE&, const POINT_TYPE& center, TYPE radius, IDX_TYPE start, IDX_TYPE size, _InsertData&); - template - void _CollectCells(const CELL_TYPE&, INSERTER&) const; template void _ParseCells(CELL_TYPE&, TYPE, PARSER&); @@ -207,13 +189,15 @@ class TOctree template void _TraverseCells(CELL_TYPE&, TYPE, const TFrustum&, PARSER&); + template + void _SplitVolume(const CELL_TYPE& parentCell, TYPE parentRadius, unsigned idxChild, float maxArea, AREAESTIMATOR& areaEstimator, CHUNKINSERTER& chunkInserter, const UnsignedArr& indices=UnsignedArr{0,1,2,3,4,5,6,7}); + protected: const ITEM_TYPE* m_items; // original input items (the only condition is that every item to resolve to a position) IDXARR_TYPE m_indices; // indices to input items re-arranged spatially (as dictated by the octree) CELL_TYPE m_root; // first cell of the tree (always of Node type) TYPE m_radius; // size of the sphere containing all cells -#ifndef _RELEASE public: typedef struct DEBUGINFO_TYPE { size_t memSize; // total memory used @@ -241,7 +225,6 @@ class TOctree protected: void _GetDebugInfo(const CELL_TYPE&, unsigned, DEBUGINFO&) const; -#endif }; // class TOctree /*----------------------------------------------------------------*/ diff --git a/libs/Common/Octree.inl b/libs/Common/Octree.inl index 83075ebb3..0a5592981 100644 --- a/libs/Common/Octree.inl +++ b/libs/Common/Octree.inl @@ -8,66 +8,50 @@ // D E F I N E S /////////////////////////////////////////////////// -// max number of items in one cell -#define OCTREE_CELLITEMS SIZE - #ifdef _USE_OPENMP // minimum number of polygons for which we do multi-threading #define OCTREE_MIN_ITEMS_MINTHREAD 1024*2 #endif -// size of a cell -#define OCTREE_CELLSIZE (TYPE(NOM)/TYPE(DENOM)) -// radius of a cell -#define OCTREE_CELLRADIUS (OCTREE_CELLSIZE/2) - // S T R U C T S /////////////////////////////////////////////////// -template -inline TOctreeCell::TOctreeCell() +template +inline TOctree::CELL_TYPE::CELL_TYPE() : m_child(NULL) { } // constructor -template -inline TOctreeCell::TOctreeCell(TOctreeCell* children) - : - m_child(children) -{ -} // constructor -/*----------------------------------------------------------------*/ - -template -inline TOctreeCell::~TOctreeCell() +template +inline TOctree::CELL_TYPE::~CELL_TYPE() { delete[] m_child; } // destructor /*----------------------------------------------------------------*/ -template -inline void TOctreeCell::Release() +template +inline void TOctree::CELL_TYPE::Release() { delete[] m_child; m_child = NULL; -} // destructor +} // Release // swap the two octrees -template -inline void TOctreeCell::Swap(TOctreeCell& rhs) +template +inline void TOctree::CELL_TYPE::Swap(CELL_TYPE& rhs) { std::swap(m_child, rhs.m_child); - uint8_t tmpData[dataSize]; - memcpy(tmpData, m_data, dataSize); - memcpy(m_data, rhs.m_data, dataSize); - memcpy(rhs.m_data, tmpData, dataSize); + if (IsLeaf()) + std::swap(m_leaf, rhs.m_leaf); + else + std::swap(m_node, rhs.m_node); } // Swap /*----------------------------------------------------------------*/ // compute item's index corresponding to the containing cell -template -inline unsigned TOctreeCell::ComputeChild(const POINT_TYPE& item) const +template +inline unsigned TOctree::CELL_TYPE::ComputeChild(const POINT_TYPE& item) const { ASSERT(!IsLeaf()); unsigned idx = 0; @@ -83,8 +67,8 @@ inline unsigned TOctreeCell::ComputeChild(const POINT_TYPE& } // ComputeChild /*----------------------------------------------------------------*/ -template -void TOctreeCell::ComputeCenter(POINT_TYPE centers[]) +template +void TOctree::CELL_TYPE::ComputeCenter(POINT_TYPE centers[]) { if (DIMS == 1) { centers[0] << -1; @@ -109,10 +93,22 @@ void TOctreeCell::ComputeCenter(POINT_TYPE centers[]) } // ComputeCenter /*----------------------------------------------------------------*/ +template +inline typename TOctree::POINT_TYPE TOctree::CELL_TYPE::ComputeChildCenter(const POINT_TYPE& center, TYPE radius, unsigned idxChild) +{ + struct CENTERARR_TYPE { + POINT_TYPE child[CELL_TYPE::numChildren]; + inline CENTERARR_TYPE() { CELL_TYPE::ComputeCenter(child); } + }; + static const CENTERARR_TYPE centers; + return center + centers.child[idxChild] * radius; +} // ComputeChildCenter +/*----------------------------------------------------------------*/ + // count the number of items contained by the given octree-cell -template -size_t TOctreeCell::GetNumItemsHeld() const +template +size_t TOctree::CELL_TYPE::GetNumItemsHeld() const { if (IsLeaf()) return GetNumItems(); @@ -128,29 +124,31 @@ size_t TOctreeCell::GetNumItemsHeld() const // S T R U C T S /////////////////////////////////////////////////// // build tree with the given items -template -inline TOctree::TOctree(const ITEMARR_TYPE& items) +template +template +inline TOctree::TOctree(const ITEMARR_TYPE& items, Functor split) { - Insert(items); + Insert(items, split); } -template -inline TOctree::TOctree(const ITEMARR_TYPE& items, const AABB_TYPE& aabb) +template +template +inline TOctree::TOctree(const ITEMARR_TYPE& items, const AABB_TYPE& aabb, Functor split) { - Insert(items, aabb); + Insert(items, aabb, split); } // constructor /*----------------------------------------------------------------*/ // destroy tree -template -inline void TOctree::Release() +template +inline void TOctree::Release() { m_indices.Release(); m_root.Release(); } // Release // swap the two octrees -template -inline void TOctree::Swap(TOctree& rhs) +template +inline void TOctree::Swap(TOctree& rhs) { std::swap(m_items, rhs.m_items); m_indices.Swap(rhs.m_indices); @@ -160,122 +158,108 @@ inline void TOctree::Swap(TOctr /*----------------------------------------------------------------*/ -// destroy tree -template -inline typename TOctree::POINT_TYPE TOctree::ComputeChildCenter(const POINT_TYPE& center, TYPE radius, unsigned idxChild) -{ - struct CENTERARR_TYPE { - POINT_TYPE child[CELL_TYPE::numChildren]; - inline CENTERARR_TYPE() { CELL_TYPE::ComputeCenter(child); } - }; - static const CENTERARR_TYPE centers; - return center + centers.child[idxChild] * radius; -} // ComputeChildCenter -/*----------------------------------------------------------------*/ - // add the given item to the tree -template -void TOctree::_Insert(CELL_TYPE& cell, TYPE radius, IDXARR_TYPE childrenIndices[]) -{ - ASSERT(!cell.IsLeaf()); - // setup each cell - const POINT_TYPE& center = cell.Node().center; - const TYPE childRadius = radius / TYPE(2); - for (int i=0; i OCTREE_CELLITEMS && (NOM == 0 || childRadius > OCTREE_CELLRADIUS)) { - // proceed recursively - const POINT_TYPE childCenter(ComputeChildCenter(center, childRadius, i)); - _Insert(child, childCenter, childRadius, childIndices); - } else { - // init leaf - typename CELL_TYPE::LEAF_TYPE& leaf = child.Leaf(); - leaf.idxBegin = m_indices.GetSize(); - leaf.size = (typename CELL_TYPE::SIZE_TYPE)childIndices.GetSize(); - // store items - m_indices.Join(childIndices); - childIndices.Release(); +template +template +void TOctree::_Insert(CELL_TYPE& cell, const POINT_TYPE& center, TYPE radius, IDX_TYPE start, IDX_TYPE size, _InsertData& insertData) +{ + ASSERT(size > 0); + // if this child cell needs to be divided further + if (bForceSplit || insertData.split(size, radius)) { + // init node and proceed recursively + ASSERT(cell.m_child == NULL); + cell.m_child = new CELL_TYPE[CELL_TYPE::numChildren]; + cell.Node().center = center; + struct ChildData { + enum { ESTART=0, EEND=CELL_TYPE::numChildren, ESIZE=CELL_TYPE::numChildren*2, EALL=CELL_TYPE::numChildren*3}; + IDX_TYPE data[EALL]; + ChildData() { memset(data, 0, sizeof(IDX_TYPE)*EALL); } + inline IDX_TYPE Start(unsigned i) const { return data[ESTART+i]; } + inline IDX_TYPE& Start(unsigned i) { return data[ESTART+i]; } + inline IDX_TYPE End(unsigned i) const { return data[EEND+i]; } + inline IDX_TYPE& End(unsigned i) { return data[EEND+i]; } + inline IDX_TYPE Size(unsigned i) const { return data[ESIZE+i]; } + inline IDX_TYPE& Size(unsigned i) { return data[ESIZE+i]; } + } childD; + IDX_TYPE idx(start); + for (IDX_TYPE i=0; i::NO_INDEX); + const TYPE childRadius(radius / TYPE(2)); + for (unsigned i=0; i::NO_INDEX; // mark the end of child successors + const POINT_TYPE childCenter(CELL_TYPE::ComputeChildCenter(center, childRadius, i)); + _Insert(child, childCenter, childRadius, childD.Start(i), childD.Size(i), insertData); + } + } else { + // init leaf + cell.Leaf().idxBegin = m_indices.size(); + cell.Leaf().size = (SIZE_TYPE)size; + for (IDX_TYPE idx=start; idx!=_InsertData::NO_INDEX; idx=insertData.successors[idx]) + m_indices.push_back(idx); } } // _Insert -template -void TOctree::_Insert(CELL_TYPE& cell, const POINT_TYPE& center, TYPE radius, IDXARR_TYPE& indices) -{ - ASSERT(cell.IsLeaf()); - ASSERT(indices.GetSize() > OCTREE_CELLITEMS); - ASSERT(NOM == 0 || radius > OCTREE_CELLRADIUS); - // divide cell - // transform this cell in node - cell.m_child = new CELL_TYPE[CELL_TYPE::numChildren]; - cell.Node().center = center; - // divide the items in separate indices lists corresponding to each child - const size_t reserveSize = indices.GetSize() / 3; - IDXARR_TYPE childrenIndices[CELL_TYPE::numChildren]; - for (int i=0; i -inline void TOctree::Insert(const ITEMARR_TYPE& items, const AABB_TYPE& aabb) +template +template +inline void TOctree::Insert(const ITEMARR_TYPE& items, const AABB_TYPE& aabb, Functor split) { Release(); - m_items = items.Begin(); - m_radius = GetRadius(aabb); - // build tree + m_items = items.data(); // create root as node, even if we do not need to divide - m_indices.Reserve(items.GetSize()); + m_indices.Reserve(items.size()); // divide cell - m_root.m_child = new CELL_TYPE[CELL_TYPE::numChildren]; - m_root.Node().center = aabb.GetCenter(); - // divide the items in separate indices lists corresponding to each child - const size_t reserveSize = items.GetSize() / 3; - IDXARR_TYPE childrenIndices[CELL_TYPE::numChildren]; - for (int i=0; i insertData = {items.size(), split}; + std::iota(insertData.successors.begin(), insertData.successors.end(), IDX_TYPE(1)); + insertData.successors.back() = _InsertData::NO_INDEX; // setup each cell - _Insert(m_root, m_radius, childrenIndices); + _Insert(m_root, center, m_radius, 0, items.size(), insertData); } -template -inline void TOctree::Insert(const ITEMARR_TYPE& items) +template +template +inline void TOctree::Insert(const ITEMARR_TYPE& items, Functor split) { ASSERT(!items.IsEmpty()); - AABB_TYPE aabb; ASSERT(sizeof(POINT_TYPE) == sizeof(typename ITEMARR_TYPE::Type)); - aabb.Set((const POINT_TYPE*)items.Begin(), items.GetSize()); + AABB_TYPE aabb((const POINT_TYPE*)items.data(), items.size()); aabb.Enlarge(ZEROTOLERANCE()*TYPE(10)); - Insert(items, aabb); + Insert(items, aabb, split); } // Insert /*----------------------------------------------------------------*/ -template +template template -void TOctree::_CollectCells(const CELL_TYPE& cell, INSERTER& inserter) const +void TOctree::CollectCells(const CELL_TYPE& cell, INSERTER& inserter) const { if (cell.IsLeaf()) { - inserter(m_indices.Begin()+cell.Leaf().idxBegin, cell.GetNumItems()); + inserter(m_indices.data()+cell.GetFirstItemIdx(), cell.GetNumItems()); return; } for (int i=0; i +template template -void TOctree::_ParseCells(CELL_TYPE& cell, TYPE radius, PARSER& parser) +void TOctree::_ParseCells(CELL_TYPE& cell, TYPE radius, PARSER& parser) { if (cell.IsLeaf()) { parser(cell, radius); @@ -288,16 +272,16 @@ void TOctree::_ParseCells(CELL_ /*----------------------------------------------------------------*/ // calls parser for each leaf of the octree (the IDX_TYPE operator has to be defined) -template +template template -void TOctree::CollectCells(INSERTER& inserter) const +void TOctree::CollectCells(INSERTER& inserter) const { - _CollectCells(m_root, inserter); + CollectCells(m_root, inserter); } // calls parser for each leaf of the octree (the CELL_TYPE operator has to be defined) -template +template template -void TOctree::ParseCells(PARSER& parser) +void TOctree::ParseCells(PARSER& parser) { _ParseCells(m_root, m_radius, parser); } @@ -305,15 +289,15 @@ void TOctree::ParseCells(PARSER // find all items contained by the given bounding box -template +template template -void TOctree::_Collect(const CELL_TYPE& cell, const AABB_TYPE& aabb, INSERTER& inserter) const +void TOctree::_Collect(const CELL_TYPE& cell, const AABB_TYPE& aabb, INSERTER& inserter) const { if (cell.IsLeaf()) { // add all items contained by the bounding-box - for (IDX i=0; i::_Collect(const CE } } // Collect // find all items contained by the cells intersected by the given line -template +template template -void TOctree::_Collect(const CELL_TYPE& cell, TYPE radius, const COLLECTOR& collector, INSERTER& inserter) const +void TOctree::_Collect(const CELL_TYPE& cell, TYPE radius, const COLLECTOR& collector, INSERTER& inserter) const { ASSERT(!cell.IsLeaf()); const TYPE childRadius = radius / TYPE(2); for (int i=0; i::_Collect(const CE } // Collect /*----------------------------------------------------------------*/ -template +template template -inline void TOctree::Collect(INSERTER& inserter, const AABB_TYPE& aabb) const +inline void TOctree::Collect(INSERTER& inserter, const AABB_TYPE& aabb) const { _Collect(m_root, aabb, inserter); } -template -inline void TOctree::Collect(IDXARR_TYPE& indices, const AABB_TYPE& aabb) const +template +inline void TOctree::Collect(IDXARR_TYPE& indices, const AABB_TYPE& aabb) const { - _Collect(m_root, aabb, IndexInserter(indices)); + IndexInserter inserter(indices); + _Collect(m_root, aabb, inserter); } -template +template template -inline void TOctree::Collect(INSERTER& inserter, const POINT_TYPE& center, TYPE radius) const +inline void TOctree::Collect(INSERTER& inserter, const POINT_TYPE& center, TYPE radius) const { _Collect(m_root, AABB_TYPE(center, radius), inserter); } -template -inline void TOctree::Collect(IDXARR_TYPE& indices, const POINT_TYPE& center, TYPE radius) const +template +inline void TOctree::Collect(IDXARR_TYPE& indices, const POINT_TYPE& center, TYPE radius) const { - _Collect(m_root, AABB_TYPE(center, radius), IndexInserter(indices)); + IndexInserter inserter(indices); + _Collect(m_root, AABB_TYPE(center, radius), inserter); } -template +template template -inline void TOctree::Collect(INSERTER& inserter, const COLLECTOR& collector) const +inline void TOctree::Collect(INSERTER& inserter, const COLLECTOR& collector) const { _Collect(m_root, m_radius, collector, inserter); } -template +template template -inline void TOctree::Collect(IDXARR_TYPE& indices, const COLLECTOR& collector) const +inline void TOctree::Collect(IDXARR_TYPE& indices, const COLLECTOR& collector) const { - _Collect(m_root, m_radius, collector, IndexInserter(indices)); + IndexInserter inserter(indices); + _Collect(m_root, m_radius, collector, inserter); } -template -inline void TOctree::Collect(IDX_TYPE maxNeighbors, IDXARR_TYPE& indices, const AABB_TYPE& aabb) const +template +inline void TOctree::Collect(IDX_TYPE maxNeighbors, IDXARR_TYPE& indices, const AABB_TYPE& aabb) const { _Collect(m_root, aabb, IndexInserter(indices)); - if (indices.GetSize() > maxNeighbors) { + if (indices.size() > maxNeighbors) { // keep only the closest neighbors typedef TIndexScore ItemIndexScore; typedef cList ItemIndexScoreArr; - ItemIndexScoreArr indexscores(indices.GetSize()); + ItemIndexScoreArr indexscores(indices.size()); const POINT_TYPE center(aabb.GetCenter()); FOREACH(i, indices) { const IDX_TYPE& idx = indices[i]; - const TYPE score(-(center-(const POINT_TYPE)m_items[idx]).squaredNorm()); + const TYPE score(-(center-m_items[idx]).squaredNorm()); indexscores[i] = ItemIndexScore(idx,score); } indices.Empty(); indexscores.Sort(); - for (IDX i=0; i -inline void TOctree::Collect(IDX_TYPE maxNeighbors, IDXARR_TYPE& indices, const POINT_TYPE& center, TYPE radius) const +template +inline void TOctree::Collect(IDX_TYPE maxNeighbors, IDXARR_TYPE& indices, const POINT_TYPE& center, TYPE radius) const { Collect(maxNeighbors, indices, AABB_TYPE(center, radius)); } // Collect @@ -439,9 +426,9 @@ inline void TOctree::Collect(ID // walk through the tree and collect visible indices -template +template template -void TOctree::_Traverse(const CELL_TYPE& cell, TYPE radius, const TFrustum& frustum, INSERTER& inserter) const +void TOctree::_Traverse(const CELL_TYPE& cell, TYPE radius, const TFrustum& frustum, INSERTER& inserter) const { ASSERT(!cell.IsLeaf()); switch (frustum.Classify(cell.GetAabb(radius))) { @@ -450,9 +437,9 @@ void TOctree::_Traverse(const C for (int i=0; i::_Traverse(const C break; } case VISIBLE: { for (int i=0; i +template template -void TOctree::_TraverseCells(CELL_TYPE& cell, TYPE radius, const TFrustum& frustum, PARSER& parser) +void TOctree::_TraverseCells(CELL_TYPE& cell, TYPE radius, const TFrustum& frustum, PARSER& parser) { ASSERT(!cell.IsLeaf()); switch (frustum.Classify(cell.GetAabb(radius))) { @@ -476,7 +463,7 @@ void TOctree::_TraverseCells(CE for (int i=0; i::_TraverseCells(CE } } -template +template template -inline void TOctree::Traverse(const TFrustum& frustum, INSERTER& inserter) const +inline void TOctree::Traverse(const TFrustum& frustum, INSERTER& inserter) const { _Traverse(m_root, m_radius, frustum, inserter); } -template +template template -inline void TOctree::Traverse(const TFrustum& frustum, IDXARR_TYPE& indices) const +inline void TOctree::Traverse(const TFrustum& frustum, IDXARR_TYPE& indices) const { _Traverse(m_root, m_radius, frustum, IndexInserter(indices)); } -template +template template -inline void TOctree::TraverseCells(const TFrustum& frustum, PARSER& parser) +inline void TOctree::TraverseCells(const TFrustum& frustum, PARSER& parser) { _TraverseCells(m_root, m_radius, frustum, parser); } -template +template template -inline void TOctree::TraverseCells(const TFrustum& frustum, CELLPTRARR_TYPE& leaves) +inline void TOctree::TraverseCells(const TFrustum& frustum, CELLPTRARR_TYPE& leaves) { _TraverseCells(m_root, m_radius, frustum, CellInserter(leaves)); } // Traverse /*----------------------------------------------------------------*/ +template +template +void TOctree::_SplitVolume(const CELL_TYPE& parentCell, TYPE parentRadius, unsigned idxChild, float maxArea, AREAESTIMATOR& areaEstimator, CHUNKINSERTER& chunkInserter, const UnsignedArr& indices) +{ + ASSERT(!indices.empty()); + typedef std::pair PairIndices; + struct GenerateSamples { + const UnsignedArr& indices; + const unsigned numSamples; + const unsigned halfSamples; + const unsigned numCommonAxis; + POINT_TYPE centers[8]; + cList arrHalfIndices; + GenerateSamples(const UnsignedArr& _indices) + : indices(_indices), numSamples((unsigned)indices.size()), halfSamples(numSamples/2), numCommonAxis(halfSamples==4?1:2), arrHalfIndices(0, numSamples) + { + ASSERT(indices.size()%2 == 0 && indices.IsSorted()); + ASSERT(halfSamples == 4 || halfSamples == 2); + CELL_TYPE::ComputeCenter(centers); + UnsignedArr samples(halfSamples); + for (unsigned hs=0; hs= maxArea) + ++numOverAreas; + if (numOverAreas == indices.size()) { + for (unsigned c: indices) + if (childArea[c] > 0) + _SplitVolume(cell, radius, c, maxArea, areaEstimator, chunkInserter); + return; + } + // split mesh children and retain the components with surface smaller than the given area + const cList halfIndices(std::move(GenerateSamples(indices).arrHalfIndices)); + IDX bestSplit(NO_ID); + float bestArea(0); + Point2f bestAs; + FOREACH(idx, halfIndices) { + const PairIndices& pairHalfIndices = halfIndices[idx]; + ASSERT(pairHalfIndices.first.size() == pairHalfIndices.second.size()); + Point2f as(Point2f::ZERO); + for (unsigned i=0; i qIndicesFirst(std::move(GenerateSamples(pairHalfIndices.first).arrHalfIndices)); + const cList qIndicesSecond(std::move(GenerateSamples(pairHalfIndices.second).arrHalfIndices)); + ASSERT(qIndicesFirst.size() == qIndicesSecond.size()); + FOREACH(q, qIndicesFirst) { + const PairIndices& qFirst = qIndicesFirst[q]; + const PairIndices& qSecond = qIndicesSecond[q]; + Eigen::Vector4f as(Eigen::Vector4f::Zero()); + for (unsigned i=0; i 0) { + // store found clusters + for (unsigned i=0; i<4; ++i) { + if (bestAs[i] < maxArea) { + chunkInserter(cell, radius, bestQIndices[i]); + } else { + _SplitVolume(cell, radius, bestQIndices[i][0], maxArea, areaEstimator, chunkInserter); + _SplitVolume(cell, radius, bestQIndices[i][1], maxArea, areaEstimator, chunkInserter); + } + } + return; + } + } + // split each child + for (unsigned c: indices) { + if (childArea[c] == 0) + continue; + if (childArea[c] < maxArea) + chunkInserter(cell, radius, UnsignedArr{c}); + else + _SplitVolume(cell, radius, c, maxArea, areaEstimator, chunkInserter); + } +} +template +template +void TOctree::SplitVolume(float maxArea, AREAESTIMATOR& areaEstimator, CHUNKINSERTER& chunkInserter) +{ + CELL_TYPE parent; + parent.m_child = new CELL_TYPE[1]; + parent.m_child[0].m_child = m_root.m_child; + parent.m_child[0].Node() = m_root.Node(); + parent.Node().center = m_root.Node().center + POINT_TYPE::Constant(m_radius); + _SplitVolume(parent, m_radius*TYPE(2), 0, maxArea, areaEstimator, chunkInserter); + parent.m_child[0].m_child = NULL; +} // SplitVolume +/*----------------------------------------------------------------*/ -#ifndef _RELEASE -template -void TOctree::_GetDebugInfo(const CELL_TYPE& cell, unsigned nDepth, DEBUGINFO& info) const + +template +void TOctree::_GetDebugInfo(const CELL_TYPE& cell, unsigned nDepth, DEBUGINFO& info) const { if (cell.IsLeaf()) { if (info.minDepth > nDepth) @@ -537,8 +741,8 @@ void TOctree::_GetDebugInfo(con _GetDebugInfo(cell.m_child[i], nDepth, info); } -template -void TOctree::GetDebugInfo(DEBUGINFO* pInfo, bool bPrintStats) const +template +void TOctree::GetDebugInfo(DEBUGINFO* pInfo, bool bPrintStats) const { DEBUGINFO localInfo; DEBUGINFO& info = (pInfo ? *pInfo : localInfo); @@ -555,8 +759,8 @@ void TOctree::GetDebugInfo(DEBU } // GetDebugInfo /*----------------------------------------------------------------*/ -template -void TOctree::LogDebugInfo(const DEBUGINFO& info) +template +void TOctree::LogDebugInfo(const DEBUGINFO& info) { //VERBOSE("NoItems: %d; Mem %s; MemItems %s; MemStruct %s; AvgMemStruct %.2f%%%%; NoNodes %d; NoLeaf %d; AvgLeaf %.2f%%%%; AvgDepth %.2f; MinDepth %d; MaxDepth %d", VERBOSE("NumItems %d; Mem %s (%s items, %s struct - %.2f%%%%); NumNodes %d (leaves %d - %.2f%%%%); Depth %.2f (%d min, %d max)", @@ -573,8 +777,8 @@ inline bool OctreeTest(unsigned iters, unsigned maxItems=1000, bool bRandom=true STATIC_ASSERT(DIMS > 0 && DIMS <= 3); srand(bRandom ? (unsigned)time(NULL) : 0); typedef Eigen::Matrix POINT_TYPE; - typedef SEACAVE::cList TestArr; - typedef TOctree TestTree; + typedef CLISTDEF0(POINT_TYPE) TestArr; + typedef TOctree TestTree; const TYPE ptMinData[] = {0,0,0}, ptMaxData[] = {640,480,240}; typename TestTree::AABB_TYPE aabb; aabb.Set(Eigen::Map(ptMinData), Eigen::Map(ptMaxData)); @@ -592,15 +796,16 @@ inline bool OctreeTest(unsigned iters, unsigned maxItems=1000, bool bRandom=true TestArr items(elems); FOREACH(i, items) for (int j=0; j(RAND()%ROUND2INT(ptMaxData[j])); // random query point POINT_TYPE pt; - pt(0) = RAND()%ROUND2INT(ptMaxData[0]); - if (DIMS > 1) pt(1) = RAND()%ROUND2INT(ptMaxData[1]); - if (DIMS > 2) pt(2) = RAND()%ROUND2INT(ptMaxData[2]); + for (int j=0; j(RAND()%ROUND2INT(ptMaxData[j])); const TYPE radius(TYPE(3+RAND()%30)); // build octree and find interest items - TestTree tree(items, aabb); + TestTree tree(items, aabb, [](typename TestTree::IDX_TYPE size, typename TestTree::Type radius) { + return size > 16 && radius > 10; + }); typename TestTree::IDXARR_TYPE indices; tree.Collect(indices, pt, radius); // find interest items by brute force @@ -620,7 +825,7 @@ inline bool OctreeTest(unsigned iters, unsigned maxItems=1000, bool bRandom=true // compare results unsigned nMatches = 0; FOREACH(i, trueIndices) { - const IDX idx = trueIndices[i]; + const typename TestTree::IDX_TYPE idx = trueIndices[i]; FOREACH(j, indices) { if (indices[j] == idx) { ++nMatches; @@ -629,8 +834,8 @@ inline bool OctreeTest(unsigned iters, unsigned maxItems=1000, bool bRandom=true } } nTotalMatches += nMatches; - nTotalMissed += trueIndices.GetSize()-nMatches; - nTotalExtra += indices.GetSize()-nMatches; + nTotalMissed += (unsigned)trueIndices.size()-nMatches; + nTotalExtra += (unsigned)indices.size()-nMatches; #ifndef _RELEASE // print stats typename TestTree::DEBUGINFO_TYPE info; @@ -644,4 +849,3 @@ inline bool OctreeTest(unsigned iters, unsigned maxItems=1000, bool bRandom=true #endif return (nTotalMissed == 0 && nTotalExtra == 0); } -#endif diff --git a/libs/Common/Plane.h b/libs/Common/Plane.h index a293c94d2..ea7abeb28 100644 --- a/libs/Common/Plane.h +++ b/libs/Common/Plane.h @@ -27,10 +27,13 @@ class TPlane STATIC_ASSERT(DIMS > 0 && DIMS <= 3); public: + typedef Eigen::Matrix MATRIX; typedef Eigen::Matrix VECTOR; typedef Eigen::Matrix POINT; typedef SEACAVE::TAABB AABB; typedef SEACAVE::TRay RAY; + enum { numScalar = DIMS+1 }; + enum { numParams = numScalar-1 }; VECTOR m_vN; // plane normal vector TYPE m_fD; // distance to origin @@ -42,17 +45,33 @@ class TPlane inline TPlane(const VECTOR&, const POINT&); inline TPlane(const POINT&, const POINT&, const POINT&); inline TPlane(const TYPE p[DIMS+1]); + inline TPlane(const Eigen::Matrix&); inline void Set(const VECTOR&, TYPE); inline void Set(const VECTOR&, const POINT&); inline void Set(const POINT&, const POINT&, const POINT&); inline void Set(const TYPE p[DIMS+1]); + inline void Set(const Eigen::Matrix&); + + int Optimize(const POINT*, size_t, int maxIters=100); + template + int Optimize(const POINT*, size_t, const RobustNormFunctor& robust, int maxIters=100); + + inline void Invalidate(); + inline bool IsValid() const; + inline void Negate(); + inline TPlane Negated() const; + + inline TPlane Transformed(const MATRIX&) const; + inline TPlane& Transform(const MATRIX&); inline TYPE Distance(const TPlane&) const; inline TYPE Distance(const POINT&) const; inline TYPE DistanceAbs(const POINT&) const; + inline POINT ProjectPoint(const POINT&) const; + inline GCLASS Classify(const POINT&) const; inline GCLASS Classify(const AABB&) const; @@ -62,13 +81,35 @@ class TPlane bool Intersects(const TPlane& plane, RAY& ray) const; bool Intersects(const AABB& aabb) const; - inline TYPE& operator [] (BYTE i) { ASSERT(i<=DIMS); return m_vN.data()[i]; } - inline TYPE operator [] (BYTE i) const { ASSERT(i<=DIMS); return m_vN.data()[i]; } + inline TYPE& operator [] (BYTE i) { ASSERT(i + void serialize(Archive& ar, const unsigned int /*version*/) { + ar & m_vN; + ar & m_fD; + } + #endif }; // class TPlane /*----------------------------------------------------------------*/ +template +struct FitPlaneOnline { + TYPEW sumX, sumSqX, sumXY, sumXZ; + TYPEW sumY, sumSqY, sumYZ; + TYPEW sumZ, sumSqZ; + size_t size; + FitPlaneOnline(); + void Update(const TPoint3& P); + TPoint3 GetModel(TPoint3& avg, TPoint3& dir) const; + template TPoint3 GetPlane(TPlane& plane) const; +}; +/*----------------------------------------------------------------*/ + -// Basic frustum class +// Basic 3D frustum class // (represented as 6 planes oriented toward outside the frustum volume) template class TFrustum @@ -78,27 +119,25 @@ class TFrustum public: typedef Eigen::Matrix MATRIX4x4; typedef Eigen::Matrix MATRIX3x4; - typedef Eigen::Matrix VECTOR4; typedef Eigen::Matrix VECTOR; typedef Eigen::Matrix POINT; typedef SEACAVE::TPlane PLANE; typedef SEACAVE::TSphere SPHERE; typedef SEACAVE::TAABB AABB; + enum { numCorners = (1<<3) }; PLANE m_planes[DIMS]; // left, right, top, bottom, near and far planes //--------------------------------------- inline TFrustum() {} - inline TFrustum(const MATRIX4x4&); - inline TFrustum(const MATRIX3x4&); - inline TFrustum(const MATRIX4x4&, TYPE width, TYPE height, TYPE near=TYPE(0.0001), TYPE far=TYPE(1000)); - inline TFrustum(const MATRIX3x4&, TYPE width, TYPE height, TYPE near=TYPE(0.0001), TYPE far=TYPE(1000)); + inline TFrustum(const MATRIX4x4&, TYPE width, TYPE height, TYPE nearZ=TYPE(0.0001), TYPE farZ=TYPE(1000)); + inline TFrustum(const MATRIX3x4&, TYPE width, TYPE height, TYPE nearZ=TYPE(0.0001), TYPE farZ=TYPE(1000)); - template void Set(const MATRIX4x4&); - template void Set(const MATRIX3x4&); - void Set(const MATRIX4x4&, TYPE width, TYPE height, TYPE near=TYPE(0.0001), TYPE far=TYPE(1000)); - void Set(const MATRIX3x4&, TYPE width, TYPE height, TYPE near=TYPE(0.0001), TYPE far=TYPE(1000)); + void Set(const MATRIX4x4&, TYPE width, TYPE height, TYPE nearZ=TYPE(0.0001), TYPE farZ=TYPE(1000)); + void Set(const MATRIX3x4&, TYPE width, TYPE height, TYPE nearZ=TYPE(0.0001), TYPE farZ=TYPE(1000)); + void Set(const VECTOR corners[numCorners]); + void SetProjectionGL(const MATRIX4x4&); GCLASS Classify(const POINT&) const; GCLASS Classify(const SPHERE&) const; @@ -106,6 +145,14 @@ class TFrustum inline TYPE& operator [] (BYTE i) { ASSERT(i + void serialize(Archive& ar, const unsigned int /*version*/) { + ar & m_planes; + } + #endif }; // class TPlane /*----------------------------------------------------------------*/ diff --git a/libs/Common/Plane.inl b/libs/Common/Plane.inl index 2fcbb76f1..03f456ef8 100644 --- a/libs/Common/Plane.inl +++ b/libs/Common/Plane.inl @@ -1,3 +1,4 @@ +#include "Plane.h" //////////////////////////////////////////////////////////////////// // Plane.inl // @@ -36,6 +37,12 @@ template inline TPlane::TPlane(const TYPE p[DIMS+1]) { Set(p); +} +// Construct plane given its standard equation: Ax + By + Cz + D = 0 +template +inline TPlane::TPlane(const Eigen::Matrix& v) +{ + Set(v); } // constructors /*----------------------------------------------------------------*/ @@ -64,17 +71,124 @@ inline void TPlane::Set(const TYPE p[DIMS+1]) { const Eigen::Map vN(p); const TYPE invD(INVERT(vN.norm())); - Set(vN*invD, p[3]*invD); + Set(vN*invD, p[DIMS]*invD); +} +template +inline void TPlane::Set(const Eigen::Matrix& v) +{ + const VECTOR vN = v.template topLeftCorner<3,1>(); + const TYPE invD(INVERT(vN.norm())); + Set(vN*invD, v(DIMS)*invD); } // Set /*----------------------------------------------------------------*/ +// least squares refinement of the given plane to the 3D point set +// (return the number of iterations) +template +template +int TPlane::Optimize(const POINT* points, size_t size, const RobustNormFunctor& robust, int maxIters) +{ + ASSERT(DIMS == 3); + ASSERT(size >= numParams); + struct OptimizationFunctor { + const POINT* points; + const size_t size; + const RobustNormFunctor& robust; + // construct with the data points + OptimizationFunctor(const POINT* _points, size_t _size, const RobustNormFunctor& _robust) + : points(_points), size(_size), robust(_robust) { ASSERT(size < (size_t)std::numeric_limits::max()); } + static void Residuals(const double* x, int nPoints, const void* pData, double* fvec, double* fjac, int* /*info*/) { + const OptimizationFunctor& data = *reinterpret_cast(pData); + ASSERT((size_t)nPoints == data.size && fvec != NULL && fjac == NULL); + TPlane plane; { + Point3d N; + plane.m_fD = x[0]; + Dir2Normal(reinterpret_cast(x[1]), N); + plane.m_vN = N; + } + for (size_t i=0; i())); + } + } functor(points, size, robust); + double arrParams[numParams]; { + arrParams[0] = (double)m_fD; + const Point3d N(m_vN.x(), m_vN.y(), m_vN.z()); + Normal2Dir(N, reinterpret_cast(arrParams[1])); + } + lm_control_struct control = {1.e-6, 1.e-7, 1.e-8, 1.e-7, 100.0, maxIters}; // lm_control_float; + lm_status_struct status; + lmmin(numParams, arrParams, (int)size, &functor, OptimizationFunctor::Residuals, &control, &status); + switch (status.info) { + //case 4: + case 5: + case 6: + case 7: + case 8: + case 9: + case 10: + case 11: + case 12: + DEBUG_ULTIMATE("error: refine plane: %s", lm_infmsg[status.info]); + return 0; + } + // set plane + { + Point3d N; + Dir2Normal(reinterpret_cast(arrParams[1]), N); + Set(Cast(N), (TYPE)arrParams[0]); + } + return status.nfev; +} +template +int TPlane::Optimize(const POINT* points, size_t size, int maxIters) +{ + const auto identity = [](double x) { return x; }; + return Optimize(points, size, identity, maxIters); +} // Optimize +/*----------------------------------------------------------------*/ + + +template +inline void TPlane::Invalidate() +{ + m_fD = std::numeric_limits::max(); +} // Invalidate +template +inline bool TPlane::IsValid() const +{ + return m_fD != std::numeric_limits::max(); +} // IsValid +/*----------------------------------------------------------------*/ + + template inline void TPlane::Negate() { m_vN = -m_vN; m_fD = -m_fD; -} +} // Negate +template +inline TPlane TPlane::Negated() const +{ + return TPlane(-m_vN, -m_fD); +} // Negated +/*----------------------------------------------------------------*/ + + +// transform plane from one coordinate system to another +template +inline TPlane TPlane::Transformed(const MATRIX& m) const +{ + const POINT p(m_vN * -m_fD); + const POINT pt((m * p.homogeneous()).hnormalized()); + return TPlane(m.template topLeftCorner() * m_vN, pt); +} // Transformed +template +inline TPlane& TPlane::Transform(const MATRIX& m) +{ + return *this = Transformed(m); +} // Transform /*----------------------------------------------------------------*/ @@ -99,13 +213,22 @@ inline TYPE TPlane::DistanceAbs(const POINT& p) const /*----------------------------------------------------------------*/ +// Calculate point's projection on this plane (closest point to this plane). +template +inline typename TPlane::POINT TPlane::ProjectPoint(const POINT& p) const +{ + return p - m_vN*Distance(p); +} +/*----------------------------------------------------------------*/ + + // Classify point to plane. template inline GCLASS TPlane::Classify(const POINT& p) const { const TYPE f(Distance(p)); - if (f > ZEROTOLERANCE()) return FRONT; - if (f < -ZEROTOLERANCE()) return BACK; + if (f > ZEROTOLERANCE()) return FRONT; + if (f < -ZEROTOLERANCE()) return BACK; return PLANAR; } /*----------------------------------------------------------------*/ @@ -160,7 +283,7 @@ bool TPlane::Intersects(const TPlane& plane, RAY& ray) const // if crossproduct of normals 0 than planes parallel const VECTOR vCross(m_vN.cross(plane.m_vN)); const TYPE fSqrLength(vCross.squaredNorm()); - if (fSqrLength < ZEROTOLERANCE()) + if (fSqrLength < ZEROTOLERANCE()) return false; // find line of intersection @@ -253,19 +376,96 @@ bool TPlane::Intersects(const AABB& aabb) const /*----------------------------------------------------------------*/ -// C L A S S ////////////////////////////////////////////////////// - -// Construct frustum given a projection matrix. -template -inline TFrustum::TFrustum(const MATRIX4x4& m) +// same as above, but online version +template +FitPlaneOnline::FitPlaneOnline() + : sumX(0), sumSqX(0), sumXY(0), sumXZ(0), sumY(0), sumSqY(0), sumYZ(0), sumZ(0), sumSqZ(0), size(0) { - Set<0>(m); } -template -inline TFrustum::TFrustum(const MATRIX3x4& m) +template +void FitPlaneOnline::Update(const TPoint3& P) { - Set<0>(m); + const TYPEW X((TYPEW)P.x), Y((TYPEW)P.y), Z((TYPEW)P.z); + sumX += X; sumSqX += X*X; sumXY += X*Y; sumXZ += X*Z; + sumY += Y; sumSqY += Y*Y; sumYZ += Y*Z; + sumZ += Z; sumSqZ += Z*Z; + ++size; +} +template +TPoint3 FitPlaneOnline::GetModel(TPoint3& avg, TPoint3& dir) const +{ + const TYPEW avgX(sumX/(TYPEW)size), avgY(sumY/(TYPEW)size), avgZ(sumZ/(TYPEW)size); + // assemble covariance (lower-triangular) matrix + typedef Eigen::Matrix Mat3x3; + Mat3x3 A; + A(0,0) = sumSqX - TYPEW(2)*sumX*avgX + avgX*avgX*(TYPEW)size; + A(1,0) = sumXY - sumX*avgY - avgX*sumY + avgX*avgY*(TYPEW)size; + A(1,1) = sumSqY - TYPEW(2)*sumY*avgY + avgY*avgY*(TYPEW)size; + A(2,0) = sumXZ - sumX*avgZ - avgX*sumZ + avgX*avgZ*(TYPEW)size; + A(2,1) = sumYZ - sumY*avgZ - avgY*sumZ + avgY*avgZ*(TYPEW)size; + A(2,2) = sumSqZ - TYPEW(2)*sumZ*avgZ + avgZ*avgZ*(TYPEW)size; + // the plane normal is simply the eigenvector corresponding to least eigenvalue + const int nAxis(bFitLineMode ? 2 : 0); + const Eigen::SelfAdjointEigenSolver es(A); + ASSERT(ISEQUAL(es.eigenvectors().col(nAxis).norm(), TYPEW(1))); + avg = TPoint3(avgX,avgY,avgZ); + dir = es.eigenvectors().col(nAxis); + const TYPEW* const vals(es.eigenvalues().data()); + ASSERT(vals[0] <= vals[1] && vals[1] <= vals[2]); + return *reinterpret_cast*>(vals); +} +template +template +TPoint3 FitPlaneOnline::GetPlane(TPlane& plane) const +{ + TPoint3 avg, dir; + const TPoint3 quality(GetModel(avg, dir)); + plane.Set(TPoint3(dir), TPoint3(avg)); + return TPoint3(quality); +} +/*----------------------------------------------------------------*/ + + +// Least squares fits a plane to a 3D point set. +// See http://www.geometrictools.com/Documentation/LeastSquaresFitting.pdf +// Returns a fitting quality (1 - lambda_min/lambda_max): +// 1 is best (zero variance orthogonally to the fitting line) +// 0 is worst (isotropic case, returns a plane with default direction) +template +TYPE FitPlane(const TPoint3* points, size_t size, TPlane& plane) { + // compute a point on the plane, which is shown to be the centroid of the points + const Eigen::Map< const Eigen::Matrix > vPoints((const TYPE*)points, size, 3); + const TPoint3 c(vPoints.colwise().mean()); + + // assemble covariance matrix; matrix numbering: + // 0 + // 1 2 + // 3 4 5 + Eigen::Matrix A(Eigen::Matrix::Zero()); + FOREACHRAWPTR(pPt, points, size) { + const TPoint3 X(*pPt - c); + A(0,0) += X.x*X.x; + A(1,0) += X.x*X.y; + A(1,1) += X.y*X.y; + A(2,0) += X.x*X.z; + A(2,1) += X.y*X.z; + A(2,2) += X.z*X.z; + } + + // the plane normal is simply the eigenvector corresponding to least eigenvalue + const Eigen::SelfAdjointEigenSolver< Eigen::Matrix > es(A); + ASSERT(ISEQUAL(es.eigenvectors().col(0).norm(), TYPE(1))); + plane.Set(es.eigenvectors().col(0), c); + const TYPE* const vals(es.eigenvalues().data()); + ASSERT(vals[0] <= vals[1] && vals[1] <= vals[2]); + return TYPE(1) - vals[0]/vals[1]; } +/*----------------------------------------------------------------*/ + + +// C L A S S ////////////////////////////////////////////////////// + +// Construct frustum given a projection matrix. template inline TFrustum::TFrustum(const MATRIX4x4& m, TYPE w, TYPE h, TYPE n, TYPE f) { @@ -279,124 +479,66 @@ inline TFrustum::TFrustum(const MATRIX3x4& m, TYPE w, TYPE h, TYPE n, /*----------------------------------------------------------------*/ -/** - * Retrieve active frustum planes, normals pointing outwards. - * -> IN/OUT: RDFRUSTUM - address to 6 planes - */ -template -template -void TFrustum::Set(const MATRIX4x4& m) -{ - // left plane - m_planes[0].Set(-(m.col(3)+m.col(0))); - // right plane - if (DIMS > 1) - m_planes[1].Set(-(m.col(3)-m.col(0))); - // top plane - if (DIMS > 2) - m_planes[2].Set(-(m.col(3)+m.col(1))); - // bottom plane - if (DIMS > 3) - m_planes[3].Set(-(m.col(3)-m.col(1))); - // near plane - if (DIMS > 4) - m_planes[4].Set(MODE ? -(m.col(3)+m.col(2)) : -m.col(2)); - // far plane - if (DIMS > 5) - m_planes[5].Set(-(m.col(3)-m.col(2))); -} -// same as above, but the last row of the matrix is (0,0,0,1) -template -template -void TFrustum::Set(const MATRIX3x4& m) -{ - // left plane - m_planes[0].Set(VECTOR4( - -(m(0,3)+m(0,0)), - -(m(1,3)+m(1,0)), - -(m(2,3)+m(2,0)), - -TYPE(1) - )); - // right plane - if (DIMS > 1) - m_planes[1].Set(VECTOR4( - -(m(0,3)-m(0,0)), - -(m(1,3)-m(1,0)), - -(m(2,3)-m(2,0)), - -TYPE(1) - )); - // top plane - if (DIMS > 2) - m_planes[2].Set(VECTOR4( - -(m(0,3)+m(0,1)), - -(m(1,3)+m(1,1)), - -(m(2,3)+m(2,1)), - -TYPE(1) - )); - // bottom plane - if (DIMS > 3) - m_planes[3].Set(VECTOR4( - -(m(0,3)-m(0,1)), - -(m(1,3)-m(1,1)), - -(m(2,3)-m(2,1)), - -TYPE(1) - )); - // near plane - if (DIMS > 4) - m_planes[4].Set(MODE ? - VECTOR4( - -(m(0,3)+m(0,2)), - -(m(1,3)+m(1,2)), - -(m(2,3)+m(2,2)), - -TYPE(1)) - : - VECTOR4( - -m(0,2), - -m(1,2), - -m(2,2), - TYPE(0)) - ); - // far plane - if (DIMS > 5) - m_planes[5].Set(VECTOR4( - -(m(0,3)-m(0,2)), - -(m(1,3)-m(1,2)), - -(m(2,3)-m(2,2)), - -TYPE(1) - )); -} -// same as above, but from SfM projection matrix and image plane details +// Set frustum planes, normals pointing outwards, from SfM projection matrix and image plane details template void TFrustum::Set(const MATRIX4x4& m, TYPE w, TYPE h, TYPE n, TYPE f) { - const VECTOR4 ltn(0,0,n,1), rtn(w*n,0,n,1), lbn(0,h*n,n,1), rbn(w*n,h*n,n,1); - const VECTOR4 ltf(0,0,f,1), rtf(w*f,0,f,1), lbf(0,h*f,f,1), rbf(w*f,h*f,f,1); + const VECTOR ltn(0,0,n), rtn(w*n,0,n), lbn(0,h*n,n), rbn(w*n,h*n,n); + const VECTOR ltf(0,0,f), rtf(w*f,0,f), lbf(0,h*f,f), rbf(w*f,h*f,f); const MATRIX4x4 inv(m.inverse()); - const VECTOR4 ltn3D(inv*ltn), rtn3D(inv*rtn), lbn3D(inv*lbn), rbn3D(inv*rbn); - const VECTOR4 ltf3D(inv*ltf), rtf3D(inv*rtf), lbf3D(inv*lbf), rbf3D(inv*rbf); - m_planes[0].Set(ltn3D.template topRows<3>(), ltf3D.template topRows<3>(), lbf3D.template topRows<3>()); - if (DIMS > 1) - m_planes[1].Set(rtn3D.template topRows<3>(), rbf3D.template topRows<3>(), rtf3D.template topRows<3>()); - if (DIMS > 2) - m_planes[2].Set(ltn3D.template topRows<3>(), rtf3D.template topRows<3>(), ltf3D.template topRows<3>()); - if (DIMS > 3) - m_planes[3].Set(lbn3D.template topRows<3>(), lbf3D.template topRows<3>(), rbf3D.template topRows<3>()); - if (DIMS > 4) - m_planes[4].Set(ltn3D.template topRows<3>(), lbn3D.template topRows<3>(), rbn3D.template topRows<3>()); - if (DIMS > 5) - m_planes[5].Set(ltf3D.template topRows<3>(), rtf3D.template topRows<3>(), rbf3D.template topRows<3>()); + const VECTOR corners[] = { + (inv*ltn.homogeneous()).template topRows<3>(), + (inv*rtn.homogeneous()).template topRows<3>(), + (inv*lbn.homogeneous()).template topRows<3>(), + (inv*rbn.homogeneous()).template topRows<3>(), + (inv*ltf.homogeneous()).template topRows<3>(), + (inv*rtf.homogeneous()).template topRows<3>(), + (inv*lbf.homogeneous()).template topRows<3>(), + (inv*rbf.homogeneous()).template topRows<3>() + }; + Set(corners); } template void TFrustum::Set(const MATRIX3x4& m, TYPE w, TYPE h, TYPE n, TYPE f) { MATRIX4x4 M(MATRIX4x4::Identity()); - #ifdef __GNUC__ - M.topLeftCorner(3,4) = m; - #else M.template topLeftCorner<3,4>() = m; - #endif Set(M, w, h, n, f); +} +// Set frustum planes, normals pointing outwards, from the given corners +template +void TFrustum::Set(const VECTOR corners[numCorners]) +{ + // left clipping plane + m_planes[0].Set(corners[0], corners[4], corners[6]); + if (DIMS > 1) // right clipping plane + m_planes[1].Set(corners[1], corners[7], corners[5]); + if (DIMS > 2) // top clipping plane + m_planes[2].Set(corners[0], corners[5], corners[4]); + if (DIMS > 3) // bottom clipping plane + m_planes[3].Set(corners[2], corners[6], corners[7]); + if (DIMS > 4) // near clipping plane + m_planes[4].Set(corners[0], corners[2], corners[3]); + if (DIMS > 5) // far clipping plane + m_planes[5].Set(corners[4], corners[5], corners[7]); } // Set +// Set frustum planes, normals pointing outwards, from the OpenGL projection-view matrix +template +void TFrustum::SetProjectionGL(const MATRIX4x4& mProjectionView) +{ + // left clipping plane + m_planes[0].Set(-mProjectionView.row(3) - mProjectionView.row(0)); + if (DIMS > 1) // right clipping plane + m_planes[1].Set(-mProjectionView.row(3) + mProjectionView.row(0)); + if (DIMS > 2) // top clipping plane + m_planes[2].Set(-mProjectionView.row(3) + mProjectionView.row(1)); + if (DIMS > 3) // bottom clipping plane + m_planes[3].Set(-mProjectionView.row(3) - mProjectionView.row(1)); + if (DIMS > 4) // near clipping plane + m_planes[4].Set(-mProjectionView.row(3) - mProjectionView.row(2)); + if (DIMS > 5) // far clipping plane + m_planes[5].Set(-mProjectionView.row(3) + mProjectionView.row(2)); +} /*----------------------------------------------------------------*/ diff --git a/libs/Common/Ray.h b/libs/Common/Ray.h index fd1989e6a..8549a2ea7 100644 --- a/libs/Common/Ray.h +++ b/libs/Common/Ray.h @@ -41,6 +41,7 @@ class TTriangle inline void Set(const POINT&, const POINT&, const POINT&); + inline POINT GetCenter() const; inline AABB GetAABB() const; inline PLANE GetPlane() const; @@ -85,10 +86,6 @@ class TRay bool Intersects(const TRIANGLE&, TYPE *t) const; template bool Intersects(const TRIANGLE&, TYPE fL, TYPE *t) const; - bool Intersects(const POINT&, const POINT&, const POINT&, - bool bCull, TYPE *t) const; - bool Intersects(const POINT&, const POINT&, const POINT&, - bool bCull, TYPE fL, TYPE *t) const; bool Intersects(const PLANE& plane, bool bCull, TYPE *t, POINT* pPtHit) const; inline TYPE IntersectsDist(const PLANE& plane) const; @@ -119,6 +116,7 @@ class TRay bool Distance(const POINT&, TYPE&) const; TYPE DistanceSq(const POINT&) const; TYPE Distance(const POINT&) const; + POINT GetPoint(TYPE) const; TRay operator * (const MATRIX&) const; // matrix multiplication inline TRay& operator *= (const MATRIX&); // matrix multiplication diff --git a/libs/Common/Ray.inl b/libs/Common/Ray.inl index 05b1271d6..34d4f455d 100644 --- a/libs/Common/Ray.inl +++ b/libs/Common/Ray.inl @@ -30,6 +30,12 @@ inline void TTriangle::Set(const POINT& p0, const POINT& p1, const PO /*----------------------------------------------------------------*/ +// get center +template +inline typename TTriangle::POINT TTriangle::GetCenter() const +{ + return (a + b + c) / TYPE(3); +} // get AABB template inline typename TTriangle::AABB TTriangle::GetAABB() const @@ -131,144 +137,50 @@ inline TRay& TRay::operator*=(const MATRIX& m) // test for intersection with triangle template template -bool TRay::Intersects(const TRIANGLE& tri, TYPE *t) const -{ - const VECTOR edge1(tri.b - tri.a); - const VECTOR edge2(tri.c - tri.a); - - // if close to 0 ray is parallel - const VECTOR pvec(m_vDir.cross(edge2)); - const TYPE det(edge1.dot(pvec)); - if ((bCull && (det < ZEROTOLERANCE())) || ISZERO(det)) - return false; - - // distance to plane, < 0 means beyond plane - const VECTOR tvec(m_pOrig - tri.a); - const TYPE u(tvec.dot(pvec)); - if (u < TYPE(0) || u > det) - return false; - - const VECTOR qvec(tvec.cross(edge1)); - const TYPE v(m_vDir.dot(qvec)); - if (v < TYPE(0) || u+v > det) - return false; - - if (t) - *t = (edge2.dot(qvec)) / det; - - return true; -} // Intersects(Tri) -/*----------------------------------------------------------------*/ - -// test for intersection with triangle at certain length (line segment), -// same as above but test distance to intersection vs segment length. -template -template -bool TRay::Intersects(const TRIANGLE& tri, TYPE fL, TYPE *t) const +bool TRay::Intersects(const TRIANGLE& tri, TYPE *pt) const { const VECTOR edge1(tri.b - tri.a); const VECTOR edge2(tri.c - tri.a); - - // if close to 0 ray is parallel const VECTOR pvec(m_vDir.cross(edge2)); const TYPE det(edge1.dot(pvec)); - if ((bCull && (det < ZEROTOLERANCE())) || ISZERO(det)) + // check if ray and triangle plane are parallel + if ((bCull ? det : ABS(det)) < ZEROTOLERANCE() * TYPE(0.01)) return false; - - // distance to plane, < 0 means beyond plane + const TYPE invDet(TYPE(1) / det); const VECTOR tvec(m_pOrig - tri.a); - const TYPE u(tvec.dot(pvec)); - if (u < TYPE(0) || u > det) + const TYPE u(tvec.dot(pvec) * invDet); + // check if intersection is outside of the line segment defined by points a and c + if (u < -ZEROTOLERANCE() * TYPE(10)) return false; - const VECTOR qvec(tvec.cross(edge1)); - const TYPE v(m_vDir.dot(qvec)); - if (v < TYPE(0) || u+v > det) - return false; - - if (t) { - *t = (edge2.dot(qvec)) / det; - // collision but not on segment? - if (*t > fL) return false; - } - else { - // collision but not on segment? - if ((edge2.dot(qvec)) / det > fL) return false; - } - - return true; -} // Intersects(Tri at length) -/*----------------------------------------------------------------*/ - - -// test for intersection with triangle -template -bool TRay::Intersects(const POINT& p0, const POINT& p1, const POINT& p2, bool bCull, TYPE *t) const -{ - const VECTOR edge1(p1 - p0); - const VECTOR edge2(p2 - p0); - - // if close to 0 ray is parallel - const VECTOR pvec = m_vDir.cross(edge2); - const TYPE det = edge1.dot(pvec); - if ((bCull && (det < ZEROTOLERANCE())) || ISZERO(det)) + const TYPE v(m_vDir.dot(qvec) * invDet); + // check if intersection is outside of the line segment defined by points a and b + if (v < -ZEROTOLERANCE() * TYPE(10)) return false; - - // distance to plane, < 0 means beyond plane - const VECTOR tvec = m_pOrig - p0; - const TYPE u = tvec * pvec; - if (u < TYPE(0) || u > det) + // check if intersection is outside of the line segment defined by points b and c + if (u + v > TYPE(1) + ZEROTOLERANCE() * TYPE(10)) return false; - - const VECTOR qvec = tvec.cross(edge1); - const TYPE v = m_vDir.dot(qvec); - if (v < TYPE(0) || u+v > det) + const TYPE t(edge2.dot(qvec) * invDet); + // check if intersection is behind the ray origin + if (bCull && t < -ZEROTOLERANCE()) return false; - - if (t) - *t = (edge2.dot(qvec)) / det; - + if (pt) + *pt = t; return true; } // Intersects(Tri) /*----------------------------------------------------------------*/ - -// test for intersection with triangle at certain length (line segment), -// same as above but test distance to intersection vs segment length. +// test for intersection with triangle at certain length (line segment); +// same as above, but test distance to intersection vs segment length template -bool TRay::Intersects(const POINT& p0, const POINT& p1, const POINT& p2, bool bCull, TYPE fL, TYPE *t) const +template +bool TRay::Intersects(const TRIANGLE& tri, TYPE fL, TYPE *t) const { - const VECTOR edge1(p1 - p0); - const VECTOR edge2(p2 - p0); - - // if close to 0 ray is parallel - const VECTOR pvec = m_vDir.cross(edge2); - const TYPE det = edge1.dot(pvec); - if ((bCull && (det < ZEROTOLERANCE())) || ISZERO(det)) - return false; - - // distance to plane, < 0 means beyond plane - const VECTOR tvec = m_pOrig - p0; - const TYPE u = tvec.dot(pvec); - if (u < 0.0f || u > det) - return false; - - const VECTOR qvec = tvec.cross(edge1); - const TYPE v = m_vDir.dot(qvec); - if (v < TYPE(0) || u+v > det) - return false; - - if (t) { - *t = (edge2.dot(qvec)) / det; - // collision but not on segment? - if (*t > fL) return false; - } - else { - // collision but not on segment? - if ((edge2.dot(qvec)) / det > fL) return false; - } - - return true; + TYPE _t; + TYPE* const pt(t ? t : &_t); + Intersects(tri, pt); + // collision but not on segment? + return *pt <= fL; } // Intersects(Tri at length) /*----------------------------------------------------------------*/ @@ -895,6 +807,12 @@ TYPE TRay::Distance(const POINT& pt) const { return SQRT(DistanceSq(pt)); } // Distance(POINT) +// Get the point on the ray at distance t from origin +template +typename TRay::POINT TRay::GetPoint(TYPE t) const +{ + return m_pOrig + m_vDir * t; +} // GetPoint /*----------------------------------------------------------------*/ @@ -1001,3 +919,33 @@ GCLASS TConeIntersect::Classify(const POINT& p, TYPE& t) const return PLANAR; } // Classify /*----------------------------------------------------------------*/ + +template +bool TestRayTriangleIntersection(unsigned iters) { + typedef SEACAVE::TTriangle Triangle; + typedef SEACAVE::TRay Ray; + typedef typename Ray::POINT Point; + typedef typename Point::Scalar Type; + constexpr Type zeroEps(std::is_same::value ? 0.01f : 0.00001f); + for (unsigned iter=0, lives=iters/100; iter(triangle, &t) && !lives--) + return false; + const Point _center(rayCenter.GetPoint(t)); + if ((_center-center).norm() > zeroEps && !lives--) + return false; + const BYTE o((BYTE)(RAND()%3)); + const Point side(((triangle[o].template cast()+triangle[(o+1)%3].template cast()) / 2.0).template cast()); + const Ray raySide(rayCenter.m_pOrig, side, true); + if (!raySide.template Intersects(triangle, &t) && !lives--) + return false; + const Point _side(raySide.GetPoint(t)); + if ((_side-side).norm() > zeroEps && !lives--) + return false; + } + return true; +} diff --git a/libs/Common/Rotation.h b/libs/Common/Rotation.h index 87761e56e..ebc6b85f9 100644 --- a/libs/Common/Rotation.h +++ b/libs/Common/Rotation.h @@ -66,7 +66,7 @@ template class TRMatrixBase; where (x, y, z) is the normalized direction vector of the axis and phi is the angle of rtoation. - Some usefull quaternions: + Some useful quaternions: x y z w Description 0 0 0 1 Identity quaternion, no rotation @@ -123,7 +123,7 @@ class TQuaternion : public TMatrix */ inline void Invert(); - /** makes TQuaternion-representation uniqe, ensuring vector lies + /** makes TQuaternion-representation unique, ensuring vector lies in upper (by real part) hemisphere. (inplace) */ inline void MakeUnique(); @@ -141,7 +141,7 @@ class TQuaternion : public TMatrix */ inline void MultLeft(const TQuaternion &quat); - /** rotates the given Vector qith the quaternion ( q v q* ) + /** rotates the given Vector with the quaternion ( q v q* ) the resulting vector is given in res @returns 0 in case of no error @author Daniel Grest, June 2003 @@ -280,9 +280,6 @@ class TRMatrixBase : public TMatrix inline TRMatrixBase(const BaseBase& rhs) : Base(rhs) {} template inline TRMatrixBase(const cv::Matx& rhs) : Base(rhs) {} - /** @brief Copy constructor from rotation matrix */ - inline TRMatrixBase(const TRMatrixBase& r); - /** @brief Copy constructor from 3x3 matrix @attention Orthonormality of matrix is enforced automatically! */ inline TRMatrixBase(const Mat& mat); @@ -299,7 +296,8 @@ class TRMatrixBase : public TMatrix /** @brief Initialization with the rotation from roll/pitch/yaw (in rad) */ inline TRMatrixBase(TYPE roll, TYPE pitch, TYPE yaw); - inline ~TRMatrixBase(); + /** @brief Initialization with the rotation from direction dir0 to direction dir1 */ + inline TRMatrixBase(const Vec& dir0, const Vec& dir1); template inline TRMatrixBase& operator = (const cv::Matx& rhs) { BaseBase::operator = (rhs); return *this; } inline TRMatrixBase& operator = (const cv::Mat& rhs) { BaseBase::operator = (rhs); return *this; } @@ -464,6 +462,12 @@ class TRMatrixBase : public TMatrix @author jw */ void SetFromHV(const Vec& xh, const Vec& vy); + /** @brief Create rotation matrix that rotates from dir0 to dir1 + @param dir0 represents the first (reference) direction vector + @param dir1 represents the second (target) direction vector + @author cDc */ + TRMatrixBase& SetFromDir2Dir(const Vec& dir0, const Vec& dir1); + /** @brief Calculates quaternion representation for this rotation matrix @attention Scalar part of quaternion will always be non-negative for sake of uniqueness of the resulting quaternion! diff --git a/libs/Common/Rotation.inl b/libs/Common/Rotation.inl index 212d6c075..ca0a8d11c 100644 --- a/libs/Common/Rotation.inl +++ b/libs/Common/Rotation.inl @@ -374,7 +374,7 @@ TQuaternion TQuaternion:: TQuaternion res; TYPE a, b; TYPE cosPhi = (TYPE)p.ScalarProduct(q); - //adjust angle if neccessary + //adjust angle if necessary if (cosPhi < 0) { q.MultiplyIP(-1.0); cosPhi = -cosPhi; @@ -413,7 +413,7 @@ int TQuaternion::SetXYZ(TYPE radX, TYPE radY, TYPE radZ) q_x.Mult(q_y); q_x.Mult(q_z); - (*this) = q_x; + *this = q_x; return 0; } @@ -432,7 +432,7 @@ int TQuaternion::SetZYX(TYPE radX, TYPE radY, TYPE radZ) q_z.Mult(q_y); q_z.Mult(q_x); - (*this) = q_z; + *this = q_z; return 0; } @@ -521,13 +521,6 @@ inline TRMatrixBase::TRMatrixBase() : Base(Base::IDENTITY) {} -template -inline TRMatrixBase::TRMatrixBase(const TRMatrixBase& r) - : Base(r) -{ - ASSERT(Check(R_CONSTRAINT_ACCURACY)); -} - template inline TRMatrixBase::TRMatrixBase(const Mat& mat) : Base(mat) @@ -558,10 +551,11 @@ inline TRMatrixBase::TRMatrixBase(TYPE roll, TYPE pitch, TYPE yaw) { SetXYZ(roll, pitch, yaw); } - template -inline TRMatrixBase::~TRMatrixBase() -{} +inline TRMatrixBase::TRMatrixBase(const Vec& dir0, const Vec& dir1) +{ + SetFromDir2Dir(dir0, dir1); +} template @@ -591,7 +585,7 @@ void TRMatrixBase::SetXYZ(TYPE PhiX, TYPE PhiY, TYPE PhiZ) Rx(2,1) = -Rx(1,2); //sin(PhiX); Rx(2,2) = Rx(1,1); //cos(PhiX); - (*this) = Rx*Ry*Rz; + *this = Rx*Ry*Rz; #else const TYPE sin_x = sin(PhiX); const TYPE sin_y = sin(PhiY); @@ -638,7 +632,7 @@ void TRMatrixBase::SetZYX(TYPE PhiX, TYPE PhiY, TYPE PhiZ) Rx(2,1) = -Rx(1,2); //sin(PhiX); Rx(2,2) = Rx(1,1); //cos(PhiX); - (*this) = Rz*Ry*Rx; + *this = Rz*Ry*Rx; } @@ -668,7 +662,7 @@ void TRMatrixBase::SetYXZ(TYPE PhiY, TYPE PhiX, TYPE PhiZ) Rx(2,1) = -Rx(1,2); //sin(PhiX); Rx(2,2) = Rx(1,1); //cos(PhiX); - (*this) = Ry*Rx*Rz; + *this = Ry*Rx*Rz; } @@ -698,7 +692,7 @@ void TRMatrixBase::SetZXY(TYPE PhiX, TYPE PhiY, TYPE PhiZ) Rx(2,1) = -Rx(1,2); //sin(PhiX); Rx(2,2) = Rx(1,1); //cos(PhiX); - (*this) = Rz*Rx*Ry; + *this = Rz*Rx*Ry; } @@ -708,7 +702,7 @@ void TRMatrixBase::Set(const Vec& wa, TYPE phi) { // zero rotation results in identity matrix if (ISZERO(phi)) { - (*this) = Base::IDENTITY; + *this = Base::IDENTITY; return; } @@ -731,7 +725,7 @@ void TRMatrixBase::Set(const Vec& wa, TYPE phi) const Mat Sin_O(Omega * sin(phi)); const Mat Cos_O_O((Omega * Omega) * (TYPE(1)-cos(phi))); - (*this) = Base::IDENTITY + Sin_O + Cos_O_O; + *this = Base::IDENTITY + Sin_O + Cos_O_O; } @@ -1175,15 +1169,42 @@ void TRMatrixBase::SetFromHV(const Vec& xxx, const Vec& yyy) // compute y base orthogonal in rhs const Vec y0(normalized(cross(z0, x0))); - (*this).SetFromColumnVectors(x0, y0, z0); + SetFromColumnVectors(x0, y0, z0); +} + + +template +TRMatrixBase& TRMatrixBase::SetFromDir2Dir(const Vec& dir0, const Vec& dir1) +{ + ASSERT(ISEQUAL(norm(dir0), TYPE(1)), "Norm = ", norm(dir0)); + ASSERT(ISEQUAL(norm(dir1), TYPE(1)), "Norm = ", norm(dir1)); + const TYPE cos01(CLAMP(dir1.dot(dir0), TYPE(-1), TYPE(1))); + const TYPE sin01Sq(TYPE(1) - SQUARE(cos01)); + if (sin01Sq > EPSILONTOLERANCE()) { + const Vec v(cross(dir0, dir1)); + const Mat V(CreateCrossProductMatrix3T(v)); + *this = Mat::IDENTITY + V + (V*V)*((TYPE(1)-cos01)/sin01Sq); + } else { + *this = Mat::ZERO; + if (cos01 > TYPE(0)) { + Base::operator()(0,0) = TYPE(1); + Base::operator()(1,1) = TYPE(1); + Base::operator()(2,2) = TYPE(1); + } else { + Base::operator()(0,0) = TYPE(-1); + Base::operator()(1,1) = TYPE(-1); + Base::operator()(2,2) = TYPE(-1); + } + } + return *this; } template void TRMatrixBase::SetFromDirUpGL(const Vec& viewDir, const Vec& viewUp) { - ASSERT(ISEQUAL(norm(viewDir), TYPE(1))); - ASSERT(ISEQUAL(norm(viewUp), TYPE(1))); + ASSERT(ISEQUAL(norm(viewDir), TYPE(1)), "Norm = ", norm(viewDir)); + ASSERT(ISEQUAL(norm(viewUp), TYPE(1)), "Norm = ", norm(viewUp)); const Vec right(normalized(cross(viewDir, viewUp))); const Vec up(normalized(cross(right, viewDir))); const Vec forward(viewDir * TYPE(-1)); // convert to right handed system @@ -1193,8 +1214,8 @@ void TRMatrixBase::SetFromDirUpGL(const Vec& viewDir, const Vec& viewUp) template void TRMatrixBase::SetFromDirUp(const Vec& viewDir, const Vec& viewUp) { - ASSERT(ISEQUAL(norm(viewDir), TYPE(1))); - ASSERT(ISEQUAL(norm(viewUp), TYPE(1))); + ASSERT(ISEQUAL(norm(viewDir), TYPE(1)), "Norm = ", norm(viewDir)); + ASSERT(ISEQUAL(norm(viewUp), TYPE(1)), "Norm = ", norm(viewUp)); const Vec right(normalized(cross(viewDir, viewUp))); const Vec up(normalized(cross(viewDir, right))); const Vec& forward(viewDir); @@ -1213,7 +1234,7 @@ inline void TRMatrixBase::SetRotationAxisAngle(const Vec& rot) { #if 0 // set rotation using Rodriguez formula - (*this) = Mat::IDENTITY; + *this = Mat::IDENTITY; const TYPE thetaSq = normSq(rot); if (thetaSq < TYPE(1e-12)) return; @@ -1265,7 +1286,7 @@ inline void TRMatrixBase::SetRotationAxisAngle(const Vec& rot) val[8] = TYPE(1); } #else - (*this) = Eigen::SO3(rot).get_matrix(); + *this = Eigen::SO3(rot).get_matrix(); #endif } @@ -1273,7 +1294,7 @@ template inline void TRMatrixBase::Apply(const Vec& delta) { const TRMatrixBase dR(delta); - (*this) = dR * (*this); + *this = dR * (*this); } template @@ -1503,4 +1524,40 @@ void TRMatrixBase::EnforceOrthogonality() } /*----------------------------------------------------------------*/ + +// if the matrix is a rotation, then the the transpose should be the inverse +template +inline bool IsRotationMatrix(const TMatrix& R) { + ASSERT(sizeof(TMatrix) == sizeof(TRMatrixBase)); + return ((const TRMatrixBase&)R).IsValid(); +} // IsRotationMatrix +template +inline bool IsRotationMatrix(const Eigen::Matrix& R) { + // the trace should be three and the determinant should be one + return (ISEQUAL(R.determinant(), TYPE(1)) && ISEQUAL((R*R.transpose()).trace(), TYPE(3))); +} // IsRotationMatrix +/*----------------------------------------------------------------*/ + +// enforce matrix orthogonality +template +inline void EnsureRotationMatrix(TMatrix& R) { + ASSERT(sizeof(TMatrix) == sizeof(TRMatrixBase)); + ((TRMatrixBase&)R).EnforceOrthogonality(); +} // EnsureRotationMatrix +/*----------------------------------------------------------------*/ + + +// compute the distance on SO(3) between the two given rotations +// using log(R) as in: "Efficient and Robust Large-Scale Rotation Averaging", 2013 +// same result as above, but returns directly the angle +template +inline TYPE ComputeAngleSO3(const TMatrix& I) { + return TYPE(norm(((const TRMatrixBase&)I).GetRotationAxisAngle())); +} // ComputeAngleSO3 +template +FORCEINLINE TYPE ComputeAngleSO3(const TMatrix& R1, const TMatrix& R2) { + return ComputeAngleSO3(TMatrix(R1*R2.t())); +} // ComputeAngleSO3 +/*----------------------------------------------------------------*/ + } // namespace SEACAVE diff --git a/libs/Common/SML.cpp b/libs/Common/SML.cpp index 70f7904e5..56409fdf3 100644 --- a/libs/Common/SML.cpp +++ b/libs/Common/SML.cpp @@ -362,7 +362,7 @@ const SMLVALUE* SML::GetValue(const String& key) const /** - * Insert or retrieve an item; initializa it if necesary + * Insert or retrieve an item; initialize it if necessary */ SMLVALUE& SML::GetValue(const String& key) { @@ -375,6 +375,18 @@ SMLVALUE& SML::GetValue(const String& key) /*----------------------------------------------------------------*/ +/** + * Retrieve an unnamed item by index + */ +const SMLVALUE& SML::GetValue(IDX idx) const +{ + ASSERT(idx < this->size()); + const String szName(_T("Item") + String::ToString(idx)); + return this->at(szName); +} +/*----------------------------------------------------------------*/ + + /** * Reset items' init and release functions */ diff --git a/libs/Common/SML.h b/libs/Common/SML.h index ceb503047..c016d938b 100644 --- a/libs/Common/SML.h +++ b/libs/Common/SML.h @@ -79,6 +79,7 @@ class GENERAL_API SML : public SMLITEMMAP IDX GetChild(const String&) const; const SMLVALUE* GetValue(const String&) const; SMLVALUE& GetValue(const String&); + const SMLVALUE& GetValue(IDX) const; inline SMLVALUE& operator[] (const String& key) { return GetValue(key); } // misc methods diff --git a/libs/Common/Sampler.inl b/libs/Common/Sampler.inl index 9870facda..e22b21f84 100644 --- a/libs/Common/Sampler.inl +++ b/libs/Common/Sampler.inl @@ -22,12 +22,12 @@ namespace Sampler { // Note: The following functors generalize the sampling to more than two neighbors // They all contains the width variable that specify the number of neighbors used for sampling // -// All contain the operator () with the following definition: +// All contain the operator() with the following definition: // // @brief Computes weight associated to neighboring pixels // @author Romuald Perrot // @param x Sampling position -// @param[out] weigth Sampling factors associated to the neighboring +// @param[out] weight Sampling factors associated to the neighboring // @note weight must be at least width length // Linear sampling (ie: linear interpolation between two pixels) template @@ -39,9 +39,9 @@ struct Linear { inline Linear() {} - inline void operator () (const TYPE x, TYPE* const weigth) const { - weigth[0] = TYPE(1) - x; - weigth[1] = x; + inline void operator() (const TYPE x, TYPE* const weight) const { + weight[0] = TYPE(1) - x; + weight[1] = x; } }; @@ -70,19 +70,19 @@ struct Cubic { const TYPE sharpness; inline Cubic(const TYPE& _sharpness=TYPE(0.5)) : sharpness(_sharpness) {} - inline void operator () (const TYPE x, TYPE* const weigth) const { + inline void operator() (const TYPE x, TYPE* const weight) const { // remember : // A B x C D - // weigth[0] -> weight for A + // weight[0] -> weight for A // weight[1] -> weight for B // weight[2] -> weight for C - // weight[3] -> weigth for D + // weight[3] -> weight for D - weigth[0] = CubicInter12(x + TYPE(1)); - weigth[1] = CubicInter01(x); - weigth[2] = CubicInter01(TYPE(1) - x); - weigth[3] = CubicInter12(TYPE(2) - x); + weight[0] = CubicInter12(x + TYPE(1)); + weight[1] = CubicInter01(x); + weight[2] = CubicInter01(TYPE(1) - x); + weight[3] = CubicInter12(TYPE(2) - x); } // Cubic interpolation for x (in [0,1]) @@ -170,11 +170,11 @@ struct Spline16 { inline Spline16() {} - inline void operator () (const TYPE x, TYPE* const weigth) const { - weigth[0] = ((TYPE(-1) / TYPE(3) * x + TYPE(4) / TYPE(5)) * x - TYPE(7) / TYPE(15)) * x; - weigth[1] = ((x - TYPE(9) / TYPE(5)) * x - TYPE(1) / TYPE(5)) * x + TYPE(1); - weigth[2] = ((TYPE(6) / TYPE(5) - x) * x + TYPE(4) / TYPE(5)) * x; - weigth[3] = ((TYPE(1) / TYPE(3) * x - TYPE(1) / TYPE(5)) * x - TYPE(2) / TYPE(15)) * x; + inline void operator() (const TYPE x, TYPE* const weight) const { + weight[0] = ((TYPE(-1) / TYPE(3) * x + TYPE(4) / TYPE(5)) * x - TYPE(7) / TYPE(15)) * x; + weight[1] = ((x - TYPE(9) / TYPE(5)) * x - TYPE(1) / TYPE(5)) * x + TYPE(1); + weight[2] = ((TYPE(6) / TYPE(5) - x) * x + TYPE(4) / TYPE(5)) * x; + weight[3] = ((TYPE(1) / TYPE(3) * x - TYPE(1) / TYPE(5)) * x - TYPE(2) / TYPE(15)) * x; } }; @@ -189,13 +189,13 @@ struct Spline36 { inline Spline36() {} - inline void operator () (const TYPE x, TYPE* const weigth) const { - weigth[0] = ((TYPE(1) / TYPE(11) * x - TYPE(45) / TYPE(209)) * x + TYPE(26) / TYPE(209)) * x; - weigth[1] = ((TYPE(-6) / TYPE(11) * x + TYPE(270) / TYPE(209)) * x - TYPE(156) / TYPE(209)) * x; - weigth[2] = ((TYPE(13) / TYPE(11) * x - TYPE(453) / TYPE(209)) * x - TYPE(3) / TYPE(209)) * x + TYPE(1); - weigth[3] = ((TYPE(-13) / TYPE(11) * x + TYPE(288) / TYPE(209)) * x + TYPE(168) / TYPE(209)) * x; - weigth[4] = ((TYPE(6) / TYPE(11) * x - TYPE(72) / TYPE(209)) * x - TYPE(42) / TYPE(209)) * x; - weigth[5] = ((TYPE(-1) / TYPE(11) * x + TYPE(12) / TYPE(209)) * x + TYPE(7) / TYPE(209)) * x; + inline void operator() (const TYPE x, TYPE* const weight) const { + weight[0] = ((TYPE(1) / TYPE(11) * x - TYPE(45) / TYPE(209)) * x + TYPE(26) / TYPE(209)) * x; + weight[1] = ((TYPE(-6) / TYPE(11) * x + TYPE(270) / TYPE(209)) * x - TYPE(156) / TYPE(209)) * x; + weight[2] = ((TYPE(13) / TYPE(11) * x - TYPE(453) / TYPE(209)) * x - TYPE(3) / TYPE(209)) * x + TYPE(1); + weight[3] = ((TYPE(-13) / TYPE(11) * x + TYPE(288) / TYPE(209)) * x + TYPE(168) / TYPE(209)) * x; + weight[4] = ((TYPE(6) / TYPE(11) * x - TYPE(72) / TYPE(209)) * x - TYPE(42) / TYPE(209)) * x; + weight[5] = ((TYPE(-1) / TYPE(11) * x + TYPE(12) / TYPE(209)) * x + TYPE(7) / TYPE(209)) * x; } }; @@ -210,15 +210,15 @@ struct Spline64 { inline Spline64() {} - inline void operator () (const TYPE x, TYPE* const weigth) const { - weigth[0] = ((TYPE(-1) / TYPE(41) * x + TYPE(168) / TYPE(2911)) * x - TYPE(97) / TYPE(2911)) * x; - weigth[1] = ((TYPE(6) / TYPE(41) * x - TYPE(1008) / TYPE(2911)) * x + TYPE(582) / TYPE(2911)) * x; - weigth[2] = ((TYPE(-24) / TYPE(41) * x + TYPE(4032) / TYPE(2911)) * x - TYPE(2328) / TYPE(2911)) * x; - weigth[3] = ((TYPE(49) / TYPE(41) * x - TYPE(6387) / TYPE(2911)) * x - TYPE(3) / TYPE(2911)) * x + TYPE(1); - weigth[4] = ((TYPE(-49) / TYPE(41) * x + TYPE(4050) / TYPE(2911)) * x + TYPE(2340) / TYPE(2911)) * x; - weigth[5] = ((TYPE(24) / TYPE(41) * x - TYPE(1080) / TYPE(2911)) * x - TYPE(624) / TYPE(2911)) * x; - weigth[6] = ((TYPE(-6) / TYPE(41) * x + TYPE(270) / TYPE(2911)) * x + TYPE(156) / TYPE(2911)) * x; - weigth[7] = ((TYPE(1) / TYPE(41) * x - TYPE(45) / TYPE(2911)) * x - TYPE(26) / TYPE(2911)) * x; + inline void operator() (const TYPE x, TYPE* const weight) const { + weight[0] = ((TYPE(-1) / TYPE(41) * x + TYPE(168) / TYPE(2911)) * x - TYPE(97) / TYPE(2911)) * x; + weight[1] = ((TYPE(6) / TYPE(41) * x - TYPE(1008) / TYPE(2911)) * x + TYPE(582) / TYPE(2911)) * x; + weight[2] = ((TYPE(-24) / TYPE(41) * x + TYPE(4032) / TYPE(2911)) * x - TYPE(2328) / TYPE(2911)) * x; + weight[3] = ((TYPE(49) / TYPE(41) * x - TYPE(6387) / TYPE(2911)) * x - TYPE(3) / TYPE(2911)) * x + TYPE(1); + weight[4] = ((TYPE(-49) / TYPE(41) * x + TYPE(4050) / TYPE(2911)) * x + TYPE(2340) / TYPE(2911)) * x; + weight[5] = ((TYPE(24) / TYPE(41) * x - TYPE(1080) / TYPE(2911)) * x - TYPE(624) / TYPE(2911)) * x; + weight[6] = ((TYPE(-6) / TYPE(41) * x + TYPE(270) / TYPE(2911)) * x + TYPE(156) / TYPE(2911)) * x; + weight[7] = ((TYPE(1) / TYPE(41) * x - TYPE(45) / TYPE(2911)) * x - TYPE(26) / TYPE(2911)) * x; } }; diff --git a/libs/Common/Streams.h b/libs/Common/Streams.h index b732ea528..3c02237b8 100644 --- a/libs/Common/Streams.h +++ b/libs/Common/Streams.h @@ -11,19 +11,14 @@ // I N C L U D E S ///////////////////////////////////////////////// +#include "AutoPtr.h" + // D E F I N E S /////////////////////////////////////////////////// #define SIZE_NA ((size_f_t)-1) #define STREAM_ERROR ((size_t)-1) -#define FILE_WRITE_MINBUF_SIZE (128*1024) // The min size the file write buffer should allocate. -#define FILE_WRITE_MAXBUF_SIZE (100*1024*1024) // The max size the file write buffer should allocate. -#define FILE_READ_MINBUF_SIZE (512*1024) // The min size the file read buffer should allocate. -#define FILE_READ_MAXBUF_SIZE (200*1024*1024) // The max size the file read buffer should allocate. - -#define LAYER_BASE 0 - namespace SEACAVE { @@ -41,6 +36,12 @@ typedef IOStream IOSTREAM; class GENERAL_API NOINITVTABLE Stream { public: virtual ~Stream() { } + + // Identify stream type + virtual InputStream* getInputStream(int) { return NULL; } + virtual OutputStream* getOutputStream(int) { return NULL; } + virtual IOStream* getIOStream(int, int) { return NULL; } + // Get the length of the stream. // SIZE_NA if there were errors. virtual size_f_t getSize() const = 0; @@ -51,6 +52,21 @@ class GENERAL_API NOINITVTABLE Stream { virtual size_f_t getPos() const = 0; }; +class GENERAL_API NOINITVTABLE InputStream : public Stream { +public: + virtual ~InputStream() { } + /** + * Call this function until it returns 0 to get all bytes. + * @return The number of bytes read. len reflects the number of bytes + * actually read from the stream source in this call. + * STREAM_ERROR if there were errors. + */ + virtual size_t read(void* buf, size_t len) = 0; + + enum { LAYER_ID_IN=0 }; + InputStream* getInputStream(int typ=LAYER_ID_IN) override { return (typ == LAYER_ID_IN ? this : (InputStream*)NULL); } +}; + class GENERAL_API NOINITVTABLE OutputStream : public Stream { public: virtual ~OutputStream() { } @@ -70,12 +86,10 @@ class GENERAL_API NOINITVTABLE OutputStream : public Stream { if (len > 2048) { const size_t count((size_t)_vsctprintf(szFormat, args)); ASSERT(count != (size_t)-1); - TCHAR* const buffer(new TCHAR[count]); - _vsntprintf(buffer, count, szFormat, args); + CAutoPtrArr szBufferDyn(new TCHAR[count+1]); + _vsntprintf(szBufferDyn, count+1, szFormat, args); va_end(args); - const size_t size(write(buffer, count)); - delete[] buffer; - return size; + return write(szBufferDyn, count); } va_end(args); return write(szBuffer, len); @@ -100,75 +114,128 @@ class GENERAL_API NOINITVTABLE OutputStream : public Stream { */ virtual size_t flush() = 0; - virtual OutputStream* getOutputStream(int typ) { return (typ == LAYER_BASE ? this : NULL); } + enum { LAYER_ID_OUT=0 }; + OutputStream* getOutputStream(int typ=LAYER_ID_OUT) override { return (typ == LAYER_ID_OUT ? this : (OutputStream*)NULL); } }; -class GENERAL_API NOINITVTABLE InputStream : public Stream { +class GENERAL_API NOINITVTABLE IOStream : public InputStream, public OutputStream { public: - virtual ~InputStream() { } - /** - * Call this function until it returns 0 to get all bytes. - * @return The number of bytes read. len reflects the number of bytes - * actually read from the stream source in this call. - * STREAM_ERROR if there were errors. - */ - virtual size_t read(void* buf, size_t len) = 0; - - virtual InputStream* getInputStream(int typ) { return (typ == LAYER_BASE ? this : NULL); } + InputStream* getInputStream(int typ=LAYER_ID_IN) override { return InputStream::getInputStream(typ); } + OutputStream* getOutputStream(int typ=LAYER_ID_OUT) override { return OutputStream::getOutputStream(typ); } + IOStream* getIOStream(int typIn=LAYER_ID_IN, int typOut=LAYER_ID_OUT) override { + return ((InputStream*)this)->getInputStream(typIn) != NULL && + ((OutputStream*)this)->getOutputStream(typOut) != NULL ? this : (IOStream*)NULL; + } }; +/*----------------------------------------------------------------*/ -class GENERAL_API NOINITVTABLE IOStream : public InputStream, public OutputStream { + + +template +class LayerInputStream : public InputStream { public: - static LPCSTR getClassType() { return "IOStream"; } - virtual LPCSTR getClassName() const { return IOStream::getClassType(); } + LayerInputStream(InputStream* aStream) : s(aStream) { ASSERT(s != NULL); } + virtual ~LayerInputStream() noexcept { if (managed) delete s; } + + InputStream* getInputStream(int typ=InputStream::LAYER_ID_IN) override { return s->getInputStream(typ); } + + size_t read(void* wbuf, size_t len) override { + return s->read(wbuf, len); + } + + size_f_t getSize() const override { + return s->getSize(); + } + + bool setPos(size_f_t wpos) override { + return s->setPos(wpos); + } + + size_f_t getPos() const override { + return s->getPos(); + } + +protected: + InputStream* const s; }; -template +template class LayerOutputStream : public OutputStream { public: LayerOutputStream(OutputStream* aStream) : s(aStream) {} - virtual ~LayerOutputStream() { if (managed) delete s; } + virtual ~LayerOutputStream() noexcept { if (managed) delete s; } - OutputStream* getOutputStream(int typ) { return s->getOutputStream(typ); } + OutputStream* getOutputStream(int typ=OutputStream::LAYER_ID_OUT) override { return s->getOutputStream(typ); } - size_f_t getSize() const { + size_t write(const void* buf, size_t len) override { + return s->write(buf, len); + } + + size_t flush() override { + return s->flush(); + } + + size_f_t getSize() const override { return s->getSize(); } - bool setPos(size_f_t wpos) { + bool setPos(size_f_t wpos) override { return s->setPos(wpos); } - size_f_t getPos() const { + size_f_t getPos() const override { return s->getPos(); } protected: OutputStream* const s; }; +/*----------------------------------------------------------------*/ -template -class LayerInputStream : public InputStream { + + +template +class LayerIOStream : public IOStream { public: - LayerInputStream(InputStream* aStream) : s(aStream) { ASSERT(s != NULL); } - virtual ~LayerInputStream() { if (managed) delete s; } + LayerIOStream(InputStream* aStream) : s(aStream), sIn(aStream), sOut(NULL) { ASSERT(aStream != NULL); } + LayerIOStream(OutputStream* aStream) : s(aStream), sIn(NULL), sOut(aStream) { ASSERT(aStream != NULL); } + LayerIOStream(InputStream* streamIn, OutputStream* streamOut) : s(NULL), sIn(streamIn), sOut(streamOut) { ASSERT(sIn != NULL || sOut != NULL); } + virtual ~LayerIOStream() noexcept { if (managed) { delete sIn; delete sOut; } } - InputStream* getInputStream(int typ) { return s->getInputStream(typ); } + InputStream* getInputStream(int typ=InputStream::LAYER_ID_IN) override { return (sIn ? sIn->getInputStream(typ) : NULL); } + OutputStream* getOutputStream(int typ=OutputStream::LAYER_ID_OUT) override { return (sOut ? sOut->getOutputStream(typ) : NULL); } - size_f_t getSize() const { + size_f_t getSize() const override { return s->getSize(); } - bool setPos(size_f_t wpos) { + bool setPos(size_f_t wpos) override { return s->setPos(wpos); } - size_f_t getPos() const { + size_f_t getPos() const override { return s->getPos(); } + size_t read(void* wbuf, size_t len) override { + return sIn->read(wbuf, len); + } + + size_t write(const void* buf, size_t len) override { + return sOut->write(buf, len); + } + + size_t flush() override { + return sOut->flush(); + } + + operator InputStream* () const { return sIn; } + operator OutputStream* () const { return sOut; } + protected: - InputStream* const s; + Stream* const s; + InputStream* const sIn; + OutputStream* const sOut; }; /*----------------------------------------------------------------*/ diff --git a/libs/Common/Strings.h b/libs/Common/Strings.h index 711d0a5de..aae49c8d3 100644 --- a/libs/Common/Strings.h +++ b/libs/Common/Strings.h @@ -34,6 +34,22 @@ class GENERAL_API String : public std::string inline String(size_t n, value_type v) : Base(n, v) {} inline String(LPCTSTR sz, size_t count) : Base(sz, count) {} inline String(LPCTSTR sz, size_t offset, size_t count) : Base(sz, offset, count) {} + #ifdef _SUPPORT_CPP11 + inline String(Base&& rhs) : Base(std::forward(rhs)) {} + inline String(String&& rhs) : Base(std::forward(rhs)) {} + inline String(const String& rhs) : Base(rhs) {} + + inline String& operator=(Base&& rhs) { Base::operator=(std::forward(rhs)); return *this; } + inline String& operator=(String&& rhs) { Base::operator=(std::forward(rhs)); return *this; } + #endif + inline String& operator=(TCHAR rhs) { Base::operator=(rhs); return *this; } + inline String& operator=(LPCTSTR rhs) { Base::operator=(rhs); return *this; } + inline String& operator=(const String& rhs) { Base::operator=(rhs); return *this; } + + inline String& operator+=(TCHAR rhs) { *this = (*this) + rhs; return *this; } + inline String& operator+=(LPCTSTR rhs) { *this = (*this) + rhs; return *this; } + inline String& operator+=(const Base& rhs) { *this = (*this) + rhs; return *this; } + inline String& operator+=(const String& rhs) { *this = (*this) + rhs; return *this; } inline void Release() { return clear(); } inline bool IsEmpty() const { return empty(); } @@ -189,6 +205,26 @@ class GENERAL_API String : public std::string }; /*----------------------------------------------------------------*/ +inline String operator+(const String& lhs, TCHAR rhs) { return std::operator+(lhs, rhs); } +inline String operator+(const String& lhs, LPCTSTR rhs) { return std::operator+(lhs, rhs); } +inline String operator+(const String& lhs, const std::string& rhs) { return std::operator+(lhs, rhs); } +inline String operator+(TCHAR lhs, const String& rhs) { return std::operator+(lhs, rhs); } +inline String operator+(LPCTSTR lhs, const String& rhs) { return std::operator+(lhs, rhs); } +inline String operator+(const std::string& lhs, const String& rhs) { return std::operator+(lhs, rhs); } +inline String operator+(const String& lhs, const String& rhs) { return std::operator+(lhs, rhs); } +#ifdef _SUPPORT_CPP11 +inline String operator+(String&& lhs, TCHAR rhs) { return std::operator+(std::forward(lhs), rhs); } +inline String operator+(String&& lhs, LPCTSTR rhs) { return std::operator+(std::forward(lhs), rhs); } +inline String operator+(String&& lhs, const std::string& rhs) { return std::operator+(std::forward(lhs), rhs); } +inline String operator+(TCHAR lhs, String&& rhs) { return std::operator+(lhs, std::forward(rhs)); } +inline String operator+(LPCTSTR lhs, String&& rhs) { return std::operator+(lhs, std::forward(rhs)); } +inline String operator+(const std::string& lhs, String&& rhs) { return std::operator+(lhs, std::forward(rhs)); } +inline String operator+(const String& lhs, String&& rhs) { return std::operator+(lhs, std::forward(rhs)); } +inline String operator+(String&& lhs, const String& rhs) { return std::operator+(std::forward(lhs), rhs); } +inline String operator+(String&& lhs, String&& rhs) { return std::operator+(std::forward(lhs), std::forward(rhs)); } +#endif +/*----------------------------------------------------------------*/ + } // namespace SEACAVE diff --git a/libs/Common/Timer.cpp b/libs/Common/Timer.cpp index 332c6e41b..56e6af0b7 100644 --- a/libs/Common/Timer.cpp +++ b/libs/Common/Timer.cpp @@ -120,7 +120,7 @@ LPTSTR Timer::GetClock(uint8_t* nHH, uint8_t* nMM, LPTSTR szChar) const if (szChar == NULL) return NULL; - _stprintf(szChar, "%.2d:%.2d:%.2d", m_nHH, m_nMM, m_nSS); + _sntprintf(szChar, 32, "%.2d:%.2d:%.2d", m_nHH, m_nMM, m_nSS); return szChar; } // GetClock /*----------------------------------------------------------------*/ diff --git a/libs/Common/Types.cpp b/libs/Common/Types.cpp index a07e91652..7d7b8c96e 100644 --- a/libs/Common/Types.cpp +++ b/libs/Common/Types.cpp @@ -55,7 +55,7 @@ String cvMat2String(const TYPE* M, uint32_t rows, uint32_t cols, uint32_t step, for (uint32_t i=0; i #endif +#ifdef _SUPPORT_CPP17 +#if !defined(__GNUC__) || (__GNUC__ > 7) +#include +#endif +#endif #include #include #include @@ -54,31 +59,26 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include #include +#include #ifdef _USE_OPENMP #include #endif // Function delegate functionality -#ifdef _SUPPORT_CPP11 -#include "FastDelegateCPP11.h" +#include "FastDelegate.h" #define DELEGATE fastdelegate::delegate #define DELEGATEBIND(DLGT, FNC) DLGT::from< FNC >() #define DELEGATEBINDCLASS(DLGT, FNC, OBJ) DLGT::from(*OBJ, FNC) -#else -#include "FastDelegate.h" -#include "FastDelegateBind.h" -#define DELEGATE fastdelegate::FastDelegate -#define DELEGATEBIND(DLGT, FNC) fastdelegate::bind(FNC) -#define DELEGATEBINDCLASS(DLGT, FNC, OBJ) fastdelegate::bind(FNC, OBJ) -#endif // include usual boost libraries #ifdef _USE_BOOST @@ -268,7 +268,6 @@ int _vscprintf(LPCSTR format, va_list pargs); #endif //_MSC_VER #define DECLARE_NO_INDEX(...) std::numeric_limits<__VA_ARGS__>::max() -#define NO_ID DECLARE_NO_INDEX(uint32_t) #ifndef MAKEWORD #define MAKEWORD(a, b) ((WORD)(((BYTE)(((DWORD)(a)) & 0xff)) | ((WORD)((BYTE)(((DWORD)(b)) & 0xff))) << 8)) @@ -311,6 +310,10 @@ int _vscprintf(LPCSTR format, va_list pargs); #define MAXF std::max #endif +#ifndef RAND +#define RAND std::rand +#endif + namespace SEACAVE { // signed and unsigned types of the size of the architecture @@ -329,7 +332,10 @@ typedef int64_t size_f_t; // type used as the default floating number precision typedef double REAL; -template +// invalid index +constexpr uint32_t NO_ID = DECLARE_NO_INDEX(uint32_t); + +template struct RealType { typedef typename std::conditional::value, TYPE, REALTYPE>::type type; }; template @@ -341,15 +347,11 @@ inline T MAXF3(const T& x1, const T& x2, const T& x3) { return MAXF(MAXF(x1, x2), x3); } -#ifndef RAND -#define RAND std::rand -#endif template FORCEINLINE T RANDOM() { return T(RAND())/RAND_MAX; } template -union TAliasCast -{ +union TAliasCast { T1 f; T2 i; inline TAliasCast() {} @@ -376,6 +378,25 @@ typedef TAliasCast CastD2I; #endif +// functions simplifying the task of printing messages +namespace SEACAVE { +// print the given message composed of any number of arguments to the given stream +template +std::ostringstream& PrintMessageToStream(std::ostringstream& oss, Args&&... args) { + // fold expression to insert all arguments into the stream + (oss << ... << args); + return oss; +} +// print the given message composed of any number of arguments to a string +template +std::string PrintMessageToString(Args&&... args) { + std::ostringstream oss; + (oss << ... << args); + return oss.str(); +} +} // namespace SEACAVE + + // I N C L U D E S ///////////////////////////////////////////////// #include "Strings.h" @@ -426,7 +447,6 @@ typedef class GENERAL_API cList DoubleArr; #include "EventQueue.h" #include "SML.h" #include "ConfigTable.h" -#include "HTMLDoc.h" // D E F I N E S /////////////////////////////////////////////////// @@ -621,11 +641,11 @@ constexpr T& NEGATE(T& a) { } template constexpr T SQUARE(const T& a) { - return (a * a); + return a * a; } template constexpr T CUBE(const T& a) { - return (a * a * a); + return a * a * a; } template inline T SQRT(const T& a) { @@ -644,7 +664,7 @@ inline T LOG10(const T& a) { return T(log10(a)); } template -constexpr T powi(T base, int exp) { +constexpr T powi(T base, unsigned exp) { T result(1); while (exp) { if (exp & 1) @@ -654,15 +674,15 @@ constexpr T powi(T base, int exp) { } return result; } -constexpr int log2i(int val) { +constexpr int log2i(unsigned val) { int ret = -1; - while (val > 0) { + while (val) { val >>= 1; - ret++; + ++ret; } return ret; } -template constexpr inline int log2i() { return 1+log2i<(N>>1)>(); } +template constexpr inline int log2i() { return 1+log2i<(N>>1)>(); } template <> constexpr inline int log2i<0>() { return -1; } template <> constexpr inline int log2i<1>() { return 0; } template <> constexpr inline int log2i<2>() { return 1; } @@ -680,13 +700,13 @@ constexpr T factorial(T n) { } template constexpr T combinations(const T& n, const T& k) { - ASSERT(n >= k); + SIMPLE_ASSERT(n >= k); #if 1 T num = n; const T den = factorial(k); for (T i=n-k+1; i +FORCEINLINE INTTYPE Floor2Int(float x) { #ifdef _FAST_FLOAT2INT return CRound2Int(double(x)-_float2int_doublemagicroundeps); #else - return int(floor(x)); + return static_cast(floor(x)); #endif } -FORCEINLINE int Floor2Int(double x) { +template +FORCEINLINE INTTYPE Floor2Int(double x) { #ifdef _FAST_FLOAT2INT return CRound2Int(x-_float2int_doublemagicroundeps); #else - return int(floor(x)); + return static_cast(floor(x)); #endif } -FORCEINLINE int Ceil2Int(float x) { +template +FORCEINLINE INTTYPE Ceil2Int(float x) { #ifdef _FAST_FLOAT2INT return CRound2Int(double(x)+_float2int_doublemagicroundeps); #else - return int(ceil(x)); + return static_cast(ceil(x)); #endif } -FORCEINLINE int Ceil2Int(double x) { +template +FORCEINLINE INTTYPE Ceil2Int(double x) { #ifdef _FAST_FLOAT2INT return CRound2Int(x+_float2int_doublemagicroundeps); #else - return int(ceil(x)); + return static_cast(ceil(x)); #endif } -FORCEINLINE int Round2Int(float x) { +template +FORCEINLINE INTTYPE Round2Int(float x) { #ifdef _FAST_FLOAT2INT return CRound2Int(double(x)+_float2int_doublemagicdelta); #else - return int(floor(x+.5f)); + return static_cast(floor(x+.5f)); #endif } -FORCEINLINE int Round2Int(double x) { +template +FORCEINLINE INTTYPE Round2Int(double x) { #ifdef _FAST_FLOAT2INT return CRound2Int(x+_float2int_doublemagicdelta); #else - return int(floor(x+.5)); + return static_cast(floor(x+.5)); #endif } /*----------------------------------------------------------------*/ @@ -1169,20 +1195,22 @@ inline void sse_prefetch(const void* p) {_mm_prefetch((const char*)p, _MM_HINT_N // C L A S S E S /////////////////////////////////////////////////// -inline bool ISINFORNAN(float x) { return (std::isinf(x) || std::isnan(x)); } -inline bool ISINFORNAN(double x) { return (std::isinf(x) || std::isnan(x)); } -inline bool ISFINITE(float x) { return (!std::isinf(x) && !std::isnan(x)); } -inline bool ISFINITE(double x) { return (!std::isinf(x) && !std::isnan(x)); } +inline bool ISINFORNAN(float x) { return std::isinf(x) || std::isnan(x); } +inline bool ISINFORNAN(double x) { return std::isinf(x) || std::isnan(x); } +inline bool ISFINITE(float x) { return std::isfinite(x); } +inline bool ISFINITE(double x) { return std::isfinite(x); } template inline bool ISFINITE(const _Tp* x, size_t n) { for (size_t i=0; i -inline bool ISINSIDE(_Tp v,_Tp l0,_Tp l1) { return (l0 <= v && v < l1); } +inline bool ISINSIDE(_Tp v,_Tp l0,_Tp l1) { ASSERT(l0 +inline bool ISINSIDES(_Tp v,_Tp l0,_Tp l1) { return l0 < l1 ? ISINSIDE(v, l0, l1) : ISINSIDE(v, l1, l0); } template -inline _Tp CLAMP(_Tp v, _Tp c0, _Tp c1) { ASSERT(c0<=c1); return MINF(MAXF(v, c0), c1); } +inline _Tp CLAMP(_Tp v, _Tp l0, _Tp l1) { ASSERT(l0<=l1); return MINF(MAXF(v, l0), l1); } template -inline _Tp CLAMPS(_Tp v, _Tp c0, _Tp c1) { if (c0 <= c1) return CLAMP(v, c0, c1); return CLAMP(v, c1, c0); } +inline _Tp CLAMPS(_Tp v, _Tp l0, _Tp l1) { return l0 <= l1 ? CLAMP(v, l0, l1) : CLAMP(v, l1, l0); } template inline _Tp SIGN(_Tp x) { if (x > _Tp(0)) return _Tp(1); if (x < _Tp(0)) return _Tp(-1); return _Tp(0); } @@ -1191,18 +1219,18 @@ template inline _Tp ABS(_Tp x) { return std::abs(x); } template -inline _Tp ZEROTOLERANCE() { return _Tp(0); } +constexpr _Tp ZEROTOLERANCE() { return _Tp(0); } template<> -inline float ZEROTOLERANCE() { return FZERO_TOLERANCE; } +constexpr float ZEROTOLERANCE() { return FZERO_TOLERANCE; } template<> -inline double ZEROTOLERANCE() { return ZERO_TOLERANCE; } +constexpr double ZEROTOLERANCE() { return ZERO_TOLERANCE; } template -inline _Tp EPSILONTOLERANCE() { return std::numeric_limits<_Tp>::epsilon(); } +constexpr _Tp EPSILONTOLERANCE() { return std::numeric_limits<_Tp>::epsilon(); } template<> -inline float EPSILONTOLERANCE() { return 0.00001f; } +constexpr float EPSILONTOLERANCE() { return 0.00001f; } template<> -inline double EPSILONTOLERANCE() { return 1e-10; } +constexpr double EPSILONTOLERANCE() { return 1e-10; } inline bool ISZERO(float x) { return ABS(x) < FZERO_TOLERANCE; } inline bool ISZERO(double x) { return ABS(x) < ZERO_TOLERANCE; } @@ -1252,6 +1280,7 @@ class TPoint2 : public cv::Point_ #ifdef _USE_EIGEN EIGEN_MAKE_ALIGNED_OPERATOR_NEW_IF_VECTORIZABLE_FIXED_SIZE(TYPE,2) typedef Eigen::Matrix EVec; + typedef Eigen::Map CEVecMap; typedef Eigen::Map EVecMap; #endif @@ -1268,7 +1297,7 @@ class TPoint2 : public cv::Point_ template inline TPoint2(const cv::Matx& rhs) : Base(rhs(0),rhs(1)) {} template inline TPoint2(const cv::Matx& rhs) : Base(rhs(0),rhs(1)) {} #ifdef _USE_EIGEN - inline TPoint2(const EVec& rhs) { operator EVec& () = rhs; } + inline TPoint2(const EVec& rhs) { operator EVecMap () = rhs; } #endif explicit inline TPoint2(const TYPE& _x) : Base(_x,_x) {} inline TPoint2(const TYPE& _x, const TYPE& _y) : Base(_x,_y) {} @@ -1278,19 +1307,25 @@ class TPoint2 : public cv::Point_ template inline TPoint2& operator = (const cv::Matx& rhs) { operator Vec& () = rhs; return *this; } template inline TPoint2& operator = (const cv::Matx& rhs) { operator VecT& () = rhs; return *this; } #ifdef _USE_EIGEN - inline TPoint2& operator = (const EVec& rhs) { operator EVec& () = rhs; return *this; } + inline TPoint2& operator = (const EVec& rhs) { operator EVecMap () = rhs; return *this; } #endif // conversion to another data type - template inline operator TPoint2 () const { return TPoint2(x,y); } + template inline operator TPoint2 () const { return TPoint2((T)x,(T)y); } // pointer to the first element access inline const TYPE* ptr() const { return &x; } inline TYPE* ptr() { return &x; } + // iterator base access to enable range-based for loops + inline const TYPE* begin() const { return &x; } + inline const TYPE* end() const { return &x+3; } + // 1D element access - inline const TYPE& operator [](size_t i) const { ASSERT(i>=0 && i<2); return ptr()[i]; } - inline TYPE& operator [](size_t i) { ASSERT(i>=0 && i<2); return ptr()[i]; } + inline const TYPE& operator ()(int i) const { ASSERT(i>=0 && i<2); return ptr()[i]; } + inline TYPE& operator ()(int i) { ASSERT(i>=0 && i<2); return ptr()[i]; } + inline const TYPE& operator [](int i) const { ASSERT(i>=0 && i<2); return ptr()[i]; } + inline TYPE& operator [](int i) { ASSERT(i>=0 && i<2); return ptr()[i]; } // Access point as Size equivalent inline operator const Size& () const { return *((const Size*)this); } @@ -1306,10 +1341,9 @@ class TPoint2 : public cv::Point_ #ifdef _USE_EIGEN // Access point as Eigen equivalent - inline operator const EVec& () const { return *((const EVec*)this); } - inline operator EVec& () { return *((EVec*)this); } + inline operator EVec () const { return CEVecMap((const TYPE*)this); } // Access point as Eigen::Map equivalent - inline operator const EVecMap () const { return EVecMap((TYPE*)this); } + inline operator const CEVecMap () const { return CEVecMap((const TYPE*)this); } inline operator EVecMap () { return EVecMap((TYPE*)this); } #endif @@ -1344,6 +1378,7 @@ class TPoint3 : public cv::Point3_ #ifdef _USE_EIGEN EIGEN_MAKE_ALIGNED_OPERATOR_NEW_IF_VECTORIZABLE_FIXED_SIZE(TYPE,3) typedef Eigen::Matrix EVec; + typedef Eigen::Map CEVecMap; typedef Eigen::Map EVecMap; #endif @@ -1360,7 +1395,7 @@ class TPoint3 : public cv::Point3_ template inline TPoint3(const cv::Matx& rhs) : Base(rhs(0),rhs(1),rhs(2)) {} template inline TPoint3(const cv::Matx& rhs) : Base(rhs(0),rhs(1),rhs(2)) {} #ifdef _USE_EIGEN - inline TPoint3(const EVec& rhs) { operator EVec& () = rhs; } + inline TPoint3(const EVec& rhs) { operator EVecMap () = rhs; } #endif explicit inline TPoint3(const TYPE& _x) : Base(_x,_x,_x) {} inline TPoint3(const TYPE& _x, const TYPE& _y, const TYPE& _z) : Base(_x,_y,_z) {} @@ -1371,34 +1406,39 @@ class TPoint3 : public cv::Point3_ template inline TPoint3& operator = (const cv::Matx& rhs) { operator Vec& () = rhs; return *this; } template inline TPoint3& operator = (const cv::Matx& rhs) { operator VecT& () = rhs; return *this; } #ifdef _USE_EIGEN - inline TPoint3& operator = (const EVec& rhs) { operator EVec& () = rhs; return *this; } + inline TPoint3& operator = (const EVec& rhs) { operator EVecMap () = rhs; return *this; } #endif // conversion to another data type - template inline operator TPoint3 () const { return TPoint3(x,y,z); } + template inline operator TPoint3 () const { return TPoint3((T)x,(T)y,(T)z); } // pointer to the first element access inline const TYPE* ptr() const { return &x; } inline TYPE* ptr() { return &x; } + // iterator base access to enable range-based for loops + inline const TYPE* begin() const { return &x; } + inline const TYPE* end() const { return &x+3; } + // 1D element access - inline const TYPE& operator [](size_t i) const { ASSERT(i>=0 && i<3); return ptr()[i]; } - inline TYPE& operator [](size_t i) { ASSERT(i>=0 && i<3); return ptr()[i]; } + inline const TYPE& operator ()(int i) const { ASSERT(i>=0 && i<3); return ptr()[i]; } + inline TYPE& operator ()(int i) { ASSERT(i>=0 && i<3); return ptr()[i]; } + inline const TYPE& operator [](int i) const { ASSERT(i>=0 && i<3); return ptr()[i]; } + inline TYPE& operator [](int i) { ASSERT(i>=0 && i<3); return ptr()[i]; } // Access point as vector equivalent - inline operator const Vec& () const { return *((const Vec*)this); } - inline operator Vec& () { return *((Vec*)this); } + inline operator const Vec& () const { return *reinterpret_cast(this); } + inline operator Vec& () { return *reinterpret_cast(this); } // Access point as transposed vector equivalent - inline operator const VecT& () const { return *((const VecT*)this); } - inline operator VecT& () { return *((VecT*)this); } + inline operator const VecT& () const { return *reinterpret_cast(this); } + inline operator VecT& () { return *reinterpret_cast(this); } #ifdef _USE_EIGEN // Access point as Eigen equivalent - inline operator const EVec& () const { return *((const EVec*)this); } - inline operator EVec& () { return *((EVec*)this); } + inline operator EVec () const { return CEVecMap((const TYPE*)this); } // Access point as Eigen::Map equivalent - inline operator const EVecMap () const { return EVecMap((TYPE*)this); } + inline operator const EVecMap () const { return CEVecMap((const TYPE*)this); } inline operator EVecMap () { return EVecMap((TYPE*)this); } #endif @@ -1455,7 +1495,7 @@ class TMatrix : public cv::Matx template inline TMatrix(const cv::Point3_& rhs) : Base(rhs.x, rhs.y, rhs.z) {} inline TMatrix(const cv::Mat& rhs) : Base(rhs) {} #ifdef _USE_EIGEN - inline TMatrix(const EMat& rhs) { operator EMat& () = rhs; } + inline TMatrix(const EMat& rhs) { operator EMatMap () = rhs; } #endif TMatrix(TYPE v0); //!< 1x1 matrix @@ -1492,7 +1532,7 @@ class TMatrix : public cv::Matx template inline TMatrix& operator = (const cv::Matx& rhs) { Base::operator = (rhs); return *this; } inline TMatrix& operator = (const cv::Mat& rhs) { Base::operator = (rhs); return *this; } #ifdef _USE_EIGEN - inline TMatrix& operator = (const EMat& rhs) { operator EMat& () = rhs; return *this; } + inline TMatrix& operator = (const EMat& rhs) { operator EMatMap () = rhs; return *this; } #endif inline bool IsEqual(const Base&) const; @@ -1503,13 +1543,12 @@ class TMatrix : public cv::Matx inline TYPE& operator [](size_t i) { ASSERT(i(this); } + inline operator Vec& () { return *reinterpret_cast(this); } #ifdef _USE_EIGEN // Access point as Eigen equivalent - inline operator const EMat& () const { return *((const EMat*)this); } - inline operator EMat& () { return *((EMat*)this); } + inline operator EMat () const { return CEMatMap((const TYPE*)val); } // Access point as Eigen::Map equivalent inline operator CEMatMap() const { return CEMatMap((const TYPE*)val); } inline operator EMatMap () { return EMatMap((TYPE*)val); } @@ -1545,6 +1584,7 @@ class TDMatrix : public cv::Mat_ typedef cv::Size Size; #ifdef _USE_EIGEN typedef Eigen::Matrix EMat; + typedef Eigen::Map CEMatMap; typedef Eigen::Map EMatMap; #endif @@ -1598,18 +1638,21 @@ class TDMatrix : public cv::Mat_ inline size_t elem_stride() const { ASSERT(dims == 2 && step[1] == sizeof(TYPE)); return step[1]; } /// Compute the area of the 2D matrix inline int area() const { ASSERT(dims == 2); return cols*rows; } + /// Compute the memory size of this matrix (in bytes) + inline size_t memory_size() const { return cv::Mat::total() * cv::Mat::elemSize(); } /// Is this coordinate inside the 2D matrix? - inline bool isInside(const Size& pt) const { - return pt.width>=0 && pt.height>=0 && pt.width + static inline typename std::enable_if::value,bool>::type isInside(const cv::Point_& pt, const cv::Size& size) { + return pt.x>=T(0) && pt.y>=T(0) && pt.x - inline typename std::enable_if::value,bool>::type isInside(const cv::Point_& pt) const { - return pt.x>=0 && pt.y>=0 && pt.x::value,bool>::type isInside(const cv::Point_& pt, const cv::Size& size) { + return pt.x>=T(0) && pt.y>=T(0) && pt.x<=T(size.width) && pt.y<=T(size.height); } template - inline typename std::enable_if::value,bool>::type isInside(const cv::Point_& pt) const { - return pt.x>=T(0) && pt.y>=T(0) && pt.x<=T(Base::size().width) && pt.y<=T(Base::size().height); + inline bool isInside(const cv::Point_& pt) const { + return isInside(pt, Base::size()); } /// Is this coordinate inside the 2D matrix, and not too close to the edges? @@ -1634,6 +1677,22 @@ class TDMatrix : public cv::Mat_ return pt.x>=T(border) && pt.y>=T(border) && pt.x<=T(Base::size().width-(border+1)) && pt.y<=T(Base::size().height-(border+1)); } + template + static inline void clip(TPoint2& ptMin, TPoint2& ptMax, const cv::Size& size) { + if (ptMin.x < T(border)) + ptMin.x = T(border); + if (ptMin.y < T(border)) + ptMin.y = T(border); + if (ptMax.x >= T(size.width-border)) + ptMax.x = T(size.width-(border+1)); + if (ptMax.y >= T(size.height-border)) + ptMax.y = T(size.height-(border+1)); + } + template + inline void clip(TPoint2& ptMin, TPoint2& ptMax) const { + clip(ptMin, ptMax, Base::size()); + } + /// Remove the given element from the vector inline void remove(int idx) { // replace the removed element by the last one and decrease the size @@ -1703,12 +1762,14 @@ class TDMatrix : public cv::Mat_ } /// pointer to the beginning of the matrix data - inline const TYPE* getData() const { ASSERT(cv::Mat::isContinuous()); return (const TYPE*)data; } - inline TYPE* getData() { ASSERT(cv::Mat::isContinuous()); return (TYPE*)data; } + inline const TYPE* getData() const { ASSERT(cv::Mat::empty() || cv::Mat::isContinuous()); return (const TYPE*)data; } + inline TYPE* getData() { ASSERT(cv::Mat::empty() || cv::Mat::isContinuous()); return (TYPE*)data; } #ifdef _USE_EIGEN + // Access point as Eigen equivalent + inline operator EMat () const { return CEMatMap(getData(), rows, cols); } // Access point as Eigen::Map equivalent - inline operator const EMatMap () const { return EMatMap(getData(), rows, cols); } + inline operator const CEMatMap () const { return CEMatMap(getData(), rows, cols); } inline operator EMatMap () { return EMatMap(getData(), rows, cols); } #endif @@ -1728,8 +1789,8 @@ typedef TDMatrix DMatrix32S; typedef TDMatrix DMatrix32U; typedef TDMatrix DMatrix32F; typedef TDMatrix DMatrix64F; -typedef SEACAVE::cList DMatrixArr; -typedef SEACAVE::cList MatArr; +typedef CLISTDEF2(DMatrix) DMatrixArr; +typedef CLISTDEF2(cv::Mat) MatArr; /*----------------------------------------------------------------*/ @@ -1781,7 +1842,7 @@ typedef TDVector DVector32S; typedef TDVector DVector32U; typedef TDVector DVector32F; typedef TDVector DVector64F; -typedef SEACAVE::cList DVectorArr; +typedef CLISTDEF2(DVector) DVectorArr; /*----------------------------------------------------------------*/ @@ -2111,7 +2172,11 @@ class TImage : public TDMatrix inline TImage(const Size& sz) : Base(sz) {} inline TImage(const Size& sz, const TYPE& v) : Base(sz, v) {} inline TImage(const Size& sz, TYPE* _data, size_t _step=Base::AUTO_STEP) : Base(sz.height, sz.width, _data, _step) {} + #ifdef _SUPPORT_CPP11 + inline TImage(cv::Mat&& rhs) : Base(std::forward(rhs)) {} + inline TImage& operator = (cv::Mat&& rhs) { BaseBase::operator=(std::forward(rhs)); return *this; } + #endif inline TImage& operator = (const Base& rhs) { BaseBase::operator=(rhs); return *this; } inline TImage& operator = (const BaseBase& rhs) { BaseBase::operator=(rhs); return *this; } inline TImage& operator = (const cv::MatExpr& rhs) { BaseBase::operator=(rhs); return *this; } @@ -2152,6 +2217,8 @@ class TImage : public TDMatrix template static void RasterizeTriangle(const TPoint2& v1, const TPoint2& v2, const TPoint2& v3, PARSER& parser); + template + static void RasterizeTriangleBary(const TPoint2& v1, const TPoint2& v2, const TPoint2& v3, PARSER& parser); template static void RasterizeTriangleDepth(TPoint3 p1, TPoint3 p2, TPoint3 p3, PARSER& parser); @@ -2167,7 +2234,10 @@ class TImage : public TDMatrix bool Load(const String&); bool Save(const String&) const; + + #ifndef _RELEASE void Show(const String& winname, int delay=0, bool bDestroy=true) const; + #endif #ifdef _USE_BOOST // serialize @@ -2179,6 +2249,7 @@ class TImage : public TDMatrix }; /*----------------------------------------------------------------*/ typedef TImage Image8U; +typedef TImage Image16U; typedef TImage Image16F; typedef TImage Image32F; typedef TImage Image64F; @@ -2361,7 +2432,7 @@ struct TAccumulator { AccumType value; WeightType weight; - inline TAccumulator() : value(0), weight(0) {} + inline TAccumulator() : value(INITTO(static_cast(NULL), 0)), weight(0) {} inline TAccumulator(const Type& v, const WeightType& w) : value(v), weight(w) {} inline bool IsEmpty() const { return weight <= 0; } // adds the given weighted value to the internal value @@ -2441,7 +2512,7 @@ struct TIndexScore { }; /*----------------------------------------------------------------*/ typedef TIndexScore IndexScore; -typedef SEACAVE::cList IndexScoreArr; +typedef CLISTDEF0(IndexScore) IndexScoreArr; /*----------------------------------------------------------------*/ @@ -2493,7 +2564,7 @@ struct PairIdx { inline bool operator==(const PairIdx& r) const { return (idx == r.idx); } }; /*----------------------------------------------------------------*/ -typedef SEACAVE::cList PairIdxArr; +typedef CLISTDEF0(PairIdx) PairIdxArr; inline PairIdx MakePairIdx(uint32_t idxImageA, uint32_t idxImageB) { return (idxImageA struct hash -{ - typedef SEACAVE::ImageRef argument_type; - typedef size_t result_type; - result_type operator()(const argument_type& v) const { - return std::hash()((const uint64_t&)v); +// combine hash values (as in boost) +namespace { +template +inline void hash_combine(std::size_t& seed, T const& v) { + seed ^= std::hash()(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); +} +template ::value - 1> +struct HashValueImpl { + static void apply(size_t& seed, Tuple const& tuple) { + HashValueImpl::apply(seed, tuple); + hash_combine(seed, std::get(tuple)); + } +}; +template +struct HashValueImpl { + static void apply(size_t& seed, Tuple const& tuple) { hash_combine(seed, std::get<0>(tuple)); } +}; +} // namespace + +// hash specialization for pairs/tuples +template +struct hash> { + std::size_t operator()(const std::pair& x) const { + size_t seed = std::hash()(x.first); + hash_combine(seed, x.second); + return seed; + } +}; +template +struct hash> { + size_t operator()(const std::tuple& t) const { + size_t seed = 0; + HashValueImpl>::apply(seed, t); + return seed; } }; -//} // namespace tr1 + +// hash specializations for OpenCV points +template +struct hash> { + size_t operator()(const cv::Point_& v) const { + size_t seed = std::hash()(v.x); + std::hash_combine(seed, v.y); + return seed; + } +}; +template +struct hash> { + size_t operator()(const cv::Point3_& v) const { + size_t seed = std::hash()(v.x); + std::hash_combine(seed, v.y); + std::hash_combine(seed, v.z); + return seed; + } +}; + +// adds the given key-value pair in the map, overwriting the current value if the key exists +template +void MapPut(std::map* map, const Key& key, const T& value) { + auto result = map->emplace(key, value); + if (!result.second) + result.first->second = value; +} +template +void MapPut(std::unordered_map* map, const Key& key, const T& value) { + auto result = map->emplace(key, value); + if (!result.second) + result.first->second = value; +} } // namespace std @@ -41,7 +99,7 @@ INT_TYPE cvRANSACUpdateNumIters(REAL_TYPE p, REAL_TYPE ep, INT_TYPE modelPoints, return 0; num = SEACAVE::LOGN(num); denom = SEACAVE::LOGN(denom); - return (denom >= 0 || -num >= (-denom)*maxIters ? maxIters : (INT_TYPE)ROUND2INT(num/denom)); + return (denom >= 0 || -num >= (-denom)*maxIters ? maxIters : ROUND2INT(num/denom)); } #endif @@ -571,60 +629,60 @@ FORCEINLINE bool ISEQUAL(const cv::Matx& v1, const cv::Matx& } // round -template -FORCEINLINE SEACAVE::TPoint2 Floor2Int(const cv::Point_& v) +template +FORCEINLINE SEACAVE::TPoint2 Floor2Int(const cv::Point_& v) { - return SEACAVE::TPoint2(FLOOR2INT(v.x), FLOOR2INT(v.y)); + return SEACAVE::TPoint2(FLOOR2INT(v.x), FLOOR2INT(v.y)); } -template -FORCEINLINE SEACAVE::TPoint2 Ceil2Int(const cv::Point_& v) +template +FORCEINLINE SEACAVE::TPoint2 Ceil2Int(const cv::Point_& v) { - return SEACAVE::TPoint2(CEIL2INT(v.x), CEIL2INT(v.y)); + return SEACAVE::TPoint2(CEIL2INT(v.x), CEIL2INT(v.y)); } -template -FORCEINLINE SEACAVE::TPoint2 Round2Int(const cv::Point_& v) +template +FORCEINLINE SEACAVE::TPoint2 Round2Int(const cv::Point_& v) { - return SEACAVE::TPoint2(ROUND2INT(v.x), ROUND2INT(v.y)); + return SEACAVE::TPoint2(ROUND2INT(v.x), ROUND2INT(v.y)); } -template -FORCEINLINE SEACAVE::TPoint3 Floor2Int(const cv::Point3_& v) +template +FORCEINLINE SEACAVE::TPoint3 Floor2Int(const cv::Point3_& v) { - return SEACAVE::TPoint3(FLOOR2INT(v.x), FLOOR2INT(v.y), FLOOR2INT(v.z)); + return SEACAVE::TPoint3(FLOOR2INT(v.x), FLOOR2INT(v.y), FLOOR2INT(v.z)); } -template -FORCEINLINE SEACAVE::TPoint3 Ceil2Int(const cv::Point3_& v) +template +FORCEINLINE SEACAVE::TPoint3 Ceil2Int(const cv::Point3_& v) { - return SEACAVE::TPoint3(CEIL2INT(v.x), CEIL2INT(v.y), CEIL2INT(v.z)); + return SEACAVE::TPoint3(CEIL2INT(v.x), CEIL2INT(v.y), CEIL2INT(v.z)); } -template -FORCEINLINE SEACAVE::TPoint3 Round2Int(const cv::Point3_& v) +template +FORCEINLINE SEACAVE::TPoint3 Round2Int(const cv::Point3_& v) { - return SEACAVE::TPoint3(ROUND2INT(v.x), ROUND2INT(v.y), ROUND2INT(v.z)); + return SEACAVE::TPoint3(ROUND2INT(v.x), ROUND2INT(v.y), ROUND2INT(v.z)); } -template -FORCEINLINE SEACAVE::TMatrix Floor2Int(const cv::Matx& v) +template +FORCEINLINE SEACAVE::TMatrix Floor2Int(const cv::Matx& v) { - SEACAVE::TMatrix nv; + SEACAVE::TMatrix nv; for (int i=0; i(v.val[i]); return nv; } -template -FORCEINLINE SEACAVE::TMatrix Ceil2Int(const cv::Matx& v) +template +FORCEINLINE SEACAVE::TMatrix Ceil2Int(const cv::Matx& v) { - SEACAVE::TMatrix nv; + SEACAVE::TMatrix nv; for (int i=0; i(v.val[i]); return nv; } -template -FORCEINLINE SEACAVE::TMatrix Round2Int(const cv::Matx& v) +template +FORCEINLINE SEACAVE::TMatrix Round2Int(const cv::Matx& v) { - SEACAVE::TMatrix nv; + SEACAVE::TMatrix nv; for (int i=0; i(v.val[i]); return nv; } @@ -666,6 +724,29 @@ FORCEINLINE bool ISFINITE(const Eigen::Matrix& m) { return ISFINITE(m.data(), m.size()); } + + +// initializing both scalar and matrix variables +template +FORCEINLINE Scalar INITTO(const Scalar*, Value v) +{ + return static_cast(v); +} +template +FORCEINLINE TPoint2 INITTO(const TPoint2*, Value v) +{ + return TPoint2(static_cast(v)); +} +template +FORCEINLINE TPoint3 INITTO(const TPoint3*, Value v) +{ + return TPoint3(static_cast(v)); +} +template +FORCEINLINE Eigen::Matrix INITTO(const Eigen::Matrix*, Value v) +{ + return Eigen::Matrix::Constant(static_cast(v)); +} /*----------------------------------------------------------------*/ template @@ -2416,15 +2497,9 @@ void TImage::toGray(TImage& out, int code, bool bNormalize, bool bSRGB) template cv::Size TImage::computeResize(const cv::Size& size, REAL scale) { - cv::Size scaledSize; - if (size.width > size.height) { - scaledSize.width = ROUND2INT(REAL(size.width)*scale); - scaledSize.height = ROUND2INT(REAL(size.height)*scaledSize.width/REAL(size.width)); - } else { - scaledSize.height = ROUND2INT(REAL(size.height)*scale); - scaledSize.width = ROUND2INT(REAL(size.width)*scaledSize.height/REAL(size.height)); - } - return scaledSize; + return cv::Size( + cv::saturate_cast((REAL)size.width*scale), + cv::saturate_cast((REAL)size.height*scale)); } // compute the final scaled size by performing successive resizes // with the given scale value @@ -2605,6 +2680,52 @@ void TImage::RasterizeTriangle(const TPoint2& v1, const TPoint2& v2, } } +// same as above, but raster a triangle using barycentric coordinates: +// https://www.scratchapixel.com/lessons/3d-basic-rendering/rasterization-practical-implementation +template +template +void TImage::RasterizeTriangleBary(const TPoint2& v1, const TPoint2& v2, const TPoint2& v3, PARSER& parser) +{ + // compute bounding-box fully containing the triangle + const TPoint2 boxMin(MINF3(v1.x, v2.x, v3.x), MINF3(v1.y, v2.y, v3.y)); + const TPoint2 boxMax(MAXF3(v1.x, v2.x, v3.x), MAXF3(v1.y, v2.y, v3.y)); + // check the bounding-box intersects the image + const cv::Size size(parser.Size()); + if (boxMax.x < T(0) || boxMin.x > T(size.width - 1) || + boxMax.y < T(0) || boxMin.y > T(size.height - 1)) + return; + // clip bounding-box to be fully contained by the image + ImageRef boxMinI(FLOOR2INT(boxMin)); + ImageRef boxMaxI(CEIL2INT(boxMax)); + Base::clip(boxMinI, boxMaxI, size); + // ignore back oriented triangles (negative area) + const T area(EdgeFunction(v1, v2, v3)); + if (CULL && area <= 0) + return; + // parse all pixels inside the bounding-box + const T invArea(T(1) / area); + for (int y = boxMinI.y; y <= boxMaxI.y; ++y) { + for (int x = boxMinI.x; x <= boxMaxI.x; ++x) { + const ImageRef pt(x, y); + const TPoint2 p(Cast(pt)); + // discard point if not in triangle; + // testing only for negative barycentric coordinates + // guarantees all will be in [0,1] at the end of all checks + const T b1(EdgeFunction(v2, v3, p) * invArea); + if (b1 < 0) + continue; + const T b2(EdgeFunction(v3, v1, p) * invArea); + if (b2 < 0) + continue; + const T b3(EdgeFunction(v1, v2, p) * invArea); + if (b3 < 0) + continue; + // output pixel + parser(pt, TPoint3(b1, b2, b3)); + } + } +} + // drawing line between 2 points from left to right // papb -> pcpd // pa, pb, pc, pd must then be sorted before @@ -2965,8 +3086,8 @@ bool TImage::Load(const String& fileName) Base::create(h, w); ASSERT(sizeof(float)*Base::channels() == Base::step.p[1]); const size_t rowbytes((size_t)Base::size.p[1]*Base::step.p[1]); - for (int i=0; i(i), rowbytes) != rowbytes) + for (int i=rows; i>0; ) + if (fImage.read(cv::Mat::template ptr(--i), rowbytes) != rowbytes) return false; return true; } @@ -2984,6 +3105,8 @@ bool TImage::Load(const String& fileName) cv::cvtColor(img, img, cv::COLOR_BGRA2GRAY); else if (img.channels() == 1 && Base::channels() == 4) cv::cvtColor(img, img, cv::COLOR_GRAY2BGRA); + else if (img.channels() == 4 && Base::channels() == 3) + cv::cvtColor(img, img, cv::COLOR_BGRA2BGR); } if (img.type() == Base::type()) cv::swap(img, *this); @@ -3004,7 +3127,7 @@ bool TImage::Save(const String& fileName) const } else if (ext == ".jpg") { compression_params.push_back(cv::IMWRITE_JPEG_QUALITY); - compression_params.push_back(80); + compression_params.push_back(95); } else if (ext == ".pfm") { if (Base::depth() != CV_32F) @@ -3014,14 +3137,16 @@ bool TImage::Save(const String& fileName) const if (!fImage.isOpen()) return false; ASSERT(sizeof(float) == 4); - static const uint8_t b[4] = { 255, 0, 0, 0 }; - static const bool is_little_endian = (*((float*)b) < 1.f); - static const double scale = (is_little_endian ? -1.0 : 1.0); + #if __BYTE_ORDER == __LITTLE_ENDIAN + static const double scale(-1.0); + #else + static const double scale(1.0); + #endif fImage.print("Pf\n%d %d\n%lf\n", width(), height(), scale*Base::channels()); ASSERT(sizeof(float)*Base::channels() == Base::step.p[1]); const size_t rowbytes = (size_t)Base::size.p[1]*Base::step.p[1]; - for (int i=0; i(i), rowbytes); + for (int i=rows; i>0; ) + fImage.write(cv::Mat::template ptr(--i), rowbytes); return true; } @@ -3039,6 +3164,7 @@ bool TImage::Save(const String& fileName) const } /*----------------------------------------------------------------*/ +#ifndef _RELEASE template void TImage::Show(const String& winname, int delay, bool bDestroy) const { @@ -3048,6 +3174,7 @@ void TImage::Show(const String& winname, int delay, bool bDestroy) const cv::destroyWindow(winname); } /*----------------------------------------------------------------*/ +#endif // C L A S S ////////////////////////////////////////////////////// @@ -3563,6 +3690,19 @@ inline Eigen::Matrix operator*(const Eigen::Matrix& lhs, const } /*----------------------------------------------------------------*/ +namespace Eigen { + +template +std::istream& operator >> (std::istream& st, MatrixBase& m) { + for (int i = 0; i < m.rows(); ++i) + for (int j = 0; j < m.cols(); ++j) + st >> m(i, j); + return st; +} + +} // namespace Eigen +/*----------------------------------------------------------------*/ + #endif // _USE_EIGEN @@ -3575,8 +3715,7 @@ namespace boost { // Serialization support for cv::Mat template - void save(Archive& ar, const cv::Mat& m, const unsigned int /*version*/) - { + void save(Archive& ar, const cv::Mat& m, const unsigned int /*version*/) { const int elem_type = m.type(); const size_t elem_size = m.elemSize(); @@ -3595,8 +3734,7 @@ namespace boost { } } template - void load(Archive& ar, cv::Mat& m, const unsigned int /*version*/) - { + void load(Archive& ar, cv::Mat& m, const unsigned int /*version*/) { int cols, rows, elem_type; size_t elem_size; @@ -3617,8 +3755,7 @@ namespace boost { // Serialization support for cv::Mat_ template - void save(Archive& ar, const cv::Mat_<_Tp>& m, const unsigned int /*version*/) - { + void save(Archive& ar, const cv::Mat_<_Tp>& m, const unsigned int /*version*/) { ar & m.cols; ar & m.rows; @@ -3632,8 +3769,7 @@ namespace boost { } } template - void load(Archive& ar, cv::Mat_<_Tp>& m, const unsigned int /*version*/) - { + void load(Archive& ar, cv::Mat_<_Tp>& m, const unsigned int /*version*/) { int cols, rows; ar & cols; ar & rows; @@ -3706,6 +3842,25 @@ namespace boost { ar & m.distance; } + #ifdef _USE_EIGEN + // Serialization support for Eigen::Matrix + // specialization handling fixed sized matrices + template + inline void save(Archive& ar, const Eigen::Matrix& M, const unsigned int /*version*/) { + ar << make_nvp("data", make_array(M.data(), _Rows*_Cols)); + } + template + inline void load(Archive& ar, Eigen::Matrix& M, const unsigned int /*version*/) { + ar >> make_nvp("data", make_array(M.data(), _Rows*_Cols)); + } + // The function that causes boost::serialization to look for separate + // save() and load() functions when serializing and Eigen matrix. + template + inline void serialize(Archive& ar, Eigen::Matrix& M, const unsigned int version) { + split_free(ar, M, version); + } + #endif // _USE_EIGEN + } // namespace serialization } // namespace boost /*----------------------------------------------------------------*/ @@ -3713,6 +3868,7 @@ namespace boost { // include headers that implement a archive in simple text and binary format or XML format #if defined(_MSC_VER) #pragma warning (push) +#pragma warning (disable : 4275) // non dll-interface class #pragma warning (disable : 4715) // not all control paths return a value #endif #include @@ -3724,6 +3880,9 @@ namespace boost { // include headers that implement compressed serialization support #include #include +#if BOOST_VERSION >= 106900 +#include +#endif #if defined(_MSC_VER) #pragma warning (pop) #endif @@ -3733,12 +3892,18 @@ enum ARCHIVE_TYPE { ARCHIVE_TEXT = 0, ARCHIVE_BINARY, ARCHIVE_BINARY_ZIP, - ARCHIVE_LAST + ARCHIVE_BINARY_ZSTD, + ARCHIVE_LAST, + #if BOOST_VERSION >= 106900 + ARCHIVE_DEFAULT = ARCHIVE_BINARY_ZSTD + #else + ARCHIVE_DEFAULT = ARCHIVE_BINARY_ZIP + #endif }; // export the current state of the given reconstruction object template -bool SerializeSave(const TYPE& obj, std::ofstream& fs, ARCHIVE_TYPE type, unsigned flags=0) +bool SerializeSave(const TYPE& obj, std::ofstream& fs, ARCHIVE_TYPE type, unsigned flags=boost::archive::no_header) { // serialize out the current state switch (type) { @@ -3758,6 +3923,16 @@ bool SerializeSave(const TYPE& obj, std::ofstream& fs, ARCHIVE_TYPE type, unsign boost::archive::binary_oarchive ar(ffs, flags); ar << obj; break; } + #if BOOST_VERSION >= 106900 + case ARCHIVE_BINARY_ZSTD: { + namespace io = boost::iostreams; + io::filtering_streambuf ffs; + ffs.push(io::zstd_compressor(io::zstd::best_speed)); + ffs.push(fs); + boost::archive::binary_oarchive ar(ffs, flags); + ar << obj; + break; } + #endif default: VERBOSE("error: Can not save the object, invalid archive type"); return false; @@ -3765,7 +3940,7 @@ bool SerializeSave(const TYPE& obj, std::ofstream& fs, ARCHIVE_TYPE type, unsign return true; } // SerializeSave template -bool SerializeSave(const TYPE& obj, const SEACAVE::String& fileName, ARCHIVE_TYPE type, unsigned flags=0) +bool SerializeSave(const TYPE& obj, const SEACAVE::String& fileName, ARCHIVE_TYPE type, unsigned flags=boost::archive::no_header) { // open the output stream std::ofstream fs(fileName, std::ios::out | std::ios::binary); @@ -3777,7 +3952,7 @@ bool SerializeSave(const TYPE& obj, const SEACAVE::String& fileName, ARCHIVE_TYP // import the state to the given reconstruction object template -bool SerializeLoad(TYPE& obj, std::ifstream& fs, ARCHIVE_TYPE type, unsigned flags=0) +bool SerializeLoad(TYPE& obj, std::ifstream& fs, ARCHIVE_TYPE type, unsigned flags=boost::archive::no_header) { try { // serialize in the saved state @@ -3798,6 +3973,16 @@ bool SerializeLoad(TYPE& obj, std::ifstream& fs, ARCHIVE_TYPE type, unsigned fla boost::archive::binary_iarchive ar(ffs, flags); ar >> obj; break; } + #if BOOST_VERSION >= 106900 + case ARCHIVE_BINARY_ZSTD: { + namespace io = boost::iostreams; + io::filtering_streambuf ffs; + ffs.push(io::zstd_decompressor()); + ffs.push(fs); + boost::archive::binary_iarchive ar(ffs, flags); + ar >> obj; + break; } + #endif default: VERBOSE("error: Can not load the object, invalid archive type"); return false; @@ -3810,7 +3995,7 @@ bool SerializeLoad(TYPE& obj, std::ifstream& fs, ARCHIVE_TYPE type, unsigned fla return true; } // SerializeLoad template -bool SerializeLoad(TYPE& obj, const SEACAVE::String& fileName, ARCHIVE_TYPE type, unsigned flags=0) +bool SerializeLoad(TYPE& obj, const SEACAVE::String& fileName, ARCHIVE_TYPE type, unsigned flags=boost::archive::no_header) { // open the input stream std::ifstream fs(fileName, std::ios::in | std::ios::binary); diff --git a/libs/Common/Util.cpp b/libs/Common/Util.cpp index a32268cf7..176d43508 100644 --- a/libs/Common/Util.cpp +++ b/libs/Common/Util.cpp @@ -264,48 +264,12 @@ String Util::GetCPUInfo() #endif return cpu; } -/*----------------------------------------------------------------*/ String Util::GetRAMInfo() { - #if defined(_MSC_VER) - - #ifdef _WIN64 - MEMORYSTATUSEX memoryStatus; - memset(&memoryStatus, sizeof(MEMORYSTATUSEX), 0); - memoryStatus.dwLength = sizeof(memoryStatus); - ::GlobalMemoryStatusEx(&memoryStatus); - const size_t nTotalPhys((size_t)memoryStatus.ullTotalPhys); - const size_t nTotalVirtual((size_t)memoryStatus.ullTotalVirtual); - #else - MEMORYSTATUS memoryStatus; - memset(&memoryStatus, sizeof(MEMORYSTATUS), 0); - memoryStatus.dwLength = sizeof(MEMORYSTATUS); - ::GlobalMemoryStatus(&memoryStatus); - const size_t nTotalPhys((size_t)memoryStatus.dwTotalPhys); - const size_t nTotalVirtual((size_t)memoryStatus.dwTotalVirtual); - #endif - - #elif defined(__APPLE__) - - int mib[2] = {CTL_HW, HW_MEMSIZE}; - const unsigned namelen = sizeof(mib) / sizeof(mib[0]); - size_t len = sizeof(size_t); - size_t nTotalPhys; - sysctl(mib, namelen, &nTotalPhys, &len, NULL, 0); - const size_t nTotalVirtual(nTotalPhys); - - #else // __GNUC__ - - struct sysinfo info; - sysinfo(&info); - const size_t nTotalPhys((size_t)info.totalram); - const size_t nTotalVirtual((size_t)info.totalswap); - - #endif // _MSC_VER - return formatBytes(nTotalPhys) + _T(" Physical Memory ") + formatBytes(nTotalVirtual) + _T(" Virtual Memory"); + const MemoryInfo memInfo(GetMemoryInfo()); + return formatBytes(memInfo.totalPhysical) + _T(" Physical Memory ") + formatBytes(memInfo.totalVirtual) + _T(" Virtual Memory"); } -/*----------------------------------------------------------------*/ String Util::GetOSInfo() { @@ -430,6 +394,20 @@ String Util::GetOSInfo() #endif // _MSC_VER } + +String Util::GetDiskInfo(const String& path) +{ + #if defined(_SUPPORT_CPP17) && (!defined(__GNUC__) || (__GNUC__ > 7)) + + const std::filesystem::space_info si = std::filesystem::space(path.c_str()); + return String::FormatString("%s (%s) space", formatBytes(si.available).c_str(), formatBytes(si.capacity).c_str()); + + #else + + return String(); + + #endif // _SUPPORT_CPP17 +} /*----------------------------------------------------------------*/ @@ -473,6 +451,7 @@ Flags InitCPU() /*----------------------------------------------------------------*/ +#if _PLATFORM_X86 #ifdef _MSC_VER #include inline void CPUID(int CPUInfo[4], int level) { @@ -485,6 +464,11 @@ inline void CPUID(int CPUInfo[4], int level) { __get_cpuid((unsigned&)level, p+0, p+1, p+2, p+3); } #endif +#else // _PLATFORM_X86 +inline void CPUID(int CPUInfo[4], int level) { + memset(CPUInfo, 0, sizeof(int)*4); +} +#endif // _PLATFORM_X86 /** * Function to detect SSE availability in CPU. @@ -551,6 +535,7 @@ CPUINFO GetCPUInfo() } /*----------------------------------------------------------------*/ +#if _PLATFORM_X86 #ifdef _MSC_VER // Function to detect SSE availability in operating system. bool OSSupportsSSE() @@ -626,10 +611,32 @@ bool OSSupportsAVX() /*----------------------------------------------------------------*/ #endif // _MSC_VER +#else // _PLATFORM_X86 + +// Function to detect SSE availability in operating system. +bool OSSupportsSSE() +{ + return false; +} +// Function to detect AVX availability in operating system. +bool OSSupportsAVX() +{ + return false; +} +/*----------------------------------------------------------------*/ +#endif // _PLATFORM_X86 + // print details about the current build and PC void Util::LogBuild() { + LOG(_T("OpenMVS %s v%u.%u.%u"), + #ifdef _ENVIRONMENT64 + _T("x64"), + #else + _T("x32"), + #endif + OpenMVS_MAJOR_VERSION, OpenMVS_MINOR_VERSION, OpenMVS_PATCH_VERSION); #if TD_VERBOSE == TD_VERBOSE_OFF LOG(_T("Build date: ") __DATE__); #else @@ -638,12 +645,16 @@ void Util::LogBuild() LOG(_T("CPU: %s (%u cores)"), Util::GetCPUInfo().c_str(), Thread::hardwareConcurrency()); LOG((_T("RAM: ") + Util::GetRAMInfo()).c_str()); LOG((_T("OS: ") + Util::GetOSInfo()).c_str()); + #ifdef _SUPPORT_CPP17 + LOG((_T("Disk: ") + Util::GetDiskInfo(WORKING_FOLDER_FULL)).c_str()); + #endif if (!SIMD_ENABLED.isSet(Util::SSE)) LOG(_T("warning: no SSE compatible CPU or OS detected")); else if (!SIMD_ENABLED.isSet(Util::AVX)) LOG(_T("warning: no AVX compatible CPU or OS detected")); else LOG(_T("SSE & AVX compatible CPU & OS detected")); } // print information about the memory usage +#if _PLATFORM_X86 #ifdef _MSC_VER #include #pragma comment(lib, "Psapi.lib") @@ -679,6 +690,67 @@ void Util::LogMemoryInfo() LOG(_T("} ENDINFO")); } #endif // _MSC_VER +#else // _PLATFORM_X86 +void Util::LogMemoryInfo() +{ +} +#endif // _PLATFORM_X86 + + + +// get the total & free physical & virtual memory (in bytes) +Util::MemoryInfo Util::GetMemoryInfo() +{ + #if defined(_MSC_VER) // windows + + #ifdef _WIN64 + MEMORYSTATUSEX status; + status.dwLength = sizeof(MEMORYSTATUSEX); + if (::GlobalMemoryStatusEx(&status) == FALSE) { + ASSERT(false); + return MemoryInfo(); + } + return MemoryInfo(status.ullTotalPhys, status.ullAvailPhys, status.ullTotalVirtual, status.ullAvailVirtual); + #else + MEMORYSTATUS status; + status.dwLength = sizeof(MEMORYSTATUS); + if (::GlobalMemoryStatus(&status) == FALSE) { + ASSERT(false); + return MemoryInfo(); + } + return MemoryInfo(status.dwTotalPhys, status.dwAvailPhys, status.dwTotalVirtual, status.dwAvailVirtual); + #endif + + + #elif defined(__APPLE__) // mac + + int mib[2] ={CTL_HW, HW_MEMSIZE}; + u_int namelen = sizeof(mib) / sizeof(mib[0]); + size_t len = sizeof(size_t); + size_t total_mem; + if (sysctl(mib, namelen, &total_mem, &len, NULL, 0) < 0) { + ASSERT(false); + return MemoryInfo(); + } + return MemoryInfo(total_mem); + + #else // __GNUC__ // linux + + struct sysinfo info; + if (sysinfo(&info) != 0) { + ASSERT(false); + return MemoryInfo(); + } + return MemoryInfo( + (size_t)info.totalram*(size_t)info.mem_unit, + (size_t)info.freeram*(size_t)info.mem_unit, + (size_t)info.totalswap*(size_t)info.mem_unit, + (size_t)info.freeswap*(size_t)info.mem_unit + ); + + #endif +} +/*----------------------------------------------------------------*/ // Parses a ASCII command line string and returns an array of pointers to the command line arguments, diff --git a/libs/Common/Util.h b/libs/Common/Util.h index dee6aab24..0f6af868b 100644 --- a/libs/Common/Util.h +++ b/libs/Common/Util.h @@ -205,9 +205,9 @@ class GENERAL_API Util public: static String getAppName() { #ifdef _MSC_VER - TCHAR buf[MAX_PATH+1]; - GetModuleFileName(NULL, buf, MAX_PATH); - return ensureUnifySlash(String(buf)); + String buf(MAX_PATH+1, '\0'); + GetModuleFileName(NULL, &buf.front(), MAX_PATH); + return ensureUnifySlash(buf); #else // _MSC_VER LPTSTR home = getenv("HOME"); if (home == NULL) @@ -457,7 +457,10 @@ class GENERAL_API Util } static String getFilePath(const String& path) { - const String::size_type i = path.rfind(PATH_SEPARATOR); + const String::size_type size = path.size(); + if (size < 3) + return String(); + const String::size_type i = path.rfind(PATH_SEPARATOR, size-2); return (i != String::npos) ? path.substr(0, i+1) : String(); } static String getFileFullName(const String& path) { @@ -595,34 +598,34 @@ class GENERAL_API Util uint32_t rez = (uint32_t)(sTime / ((int64_t)24*3600*1000)); if (rez) { ++nrNumbers; - len += _stprintf(buf+len, "%ud", rez); + len += _sntprintf(buf+len, 128, "%ud", rez); } if (nAproximate > 3 && nrNumbers > 0) return buf; rez = (uint32_t)((sTime%((int64_t)24*3600*1000)) / (3600*1000)); if (rez) { ++nrNumbers; - len += _stprintf(buf+len, "%uh", rez); + len += _sntprintf(buf+len, 128, "%uh", rez); } if (nAproximate > 2 && nrNumbers > 0) return buf; rez = (uint32_t)((sTime%((int64_t)3600*1000)) / (60*1000)); if (rez) { ++nrNumbers; - len += _stprintf(buf+len, "%um", rez); + len += _sntprintf(buf+len, 128, "%um", rez); } if (nAproximate > 1 && nrNumbers > 0) return buf; rez = (uint32_t)((sTime%((int64_t)60*1000)) / (1*1000)); if (rez) { ++nrNumbers; - len += _stprintf(buf+len, "%us", rez); + len += _sntprintf(buf+len, 128, "%us", rez); } if (nAproximate > 0 && nrNumbers > 0) return buf; rez = (uint32_t)(sTime%((int64_t)1*1000)); if (rez || !nrNumbers) - len += _stprintf(buf+len, "%ums", rez); + len += _sntprintf(buf+len, 128, "%ums", rez); return String(buf, len); } @@ -749,12 +752,23 @@ class GENERAL_API Util static String GetCPUInfo(); static String GetRAMInfo(); static String GetOSInfo(); + static String GetDiskInfo(const String&); enum CPUFNC {NA=0, SSE, AVX}; static const Flags ms_CPUFNC; static void LogBuild(); static void LogMemoryInfo(); + struct MemoryInfo { + size_t totalPhysical; + size_t freePhysical; + size_t totalVirtual; + size_t freeVirtual; + MemoryInfo(size_t tP = 0, size_t fP = 0, size_t tV = 0, size_t fV = 0) + : totalPhysical(tP), freePhysical(fP), totalVirtual(tV), freeVirtual(fV) {} + }; + static MemoryInfo GetMemoryInfo(); + static LPSTR* CommandLineToArgvA(LPCSTR CmdLine, size_t& _argc); static String CommandLineToString(size_t argc, LPCTSTR* argv) { String strCmdLine; diff --git a/libs/Common/Util.inl b/libs/Common/Util.inl index d605b9173..beba6d838 100644 --- a/libs/Common/Util.inl +++ b/libs/Common/Util.inl @@ -13,6 +13,23 @@ namespace SEACAVE { // I N L I N E ///////////////////////////////////////////////////// +// normalize inhomogeneous 2D point by the given camera intrinsics K +// K is assumed to be the [3,3] triangular matrix with: fx, fy, s, cx, cy and scale 1 +template +inline void NormalizeProjection(const TYPE1* K, const TYPE2* x, TYPE3* n) { + ASSERT(ISZERO(K[3]) && ISZERO(K[6]) && ISZERO(K[7]) && ISEQUAL(K[8], TYPE1(1))); + n[0] = TYPE3(K[0]*x[0] + K[1]*x[1] + K[2]); + n[1] = TYPE3( K[4]*x[1] + K[5]); +} // NormalizeProjection +// same as above, but using K inverse +template +inline void NormalizeProjectionInv(const TYPE1* K, const TYPE2* x, TYPE3* n) { + ASSERT(ISZERO(K[3]) && ISZERO(K[6]) && ISZERO(K[7]) && ISEQUAL(K[8], TYPE1(1))); + n[0] = TYPE3((K[4]*x[0] - K[1]*x[1] + (K[5]*K[1]-K[2]*K[4])) / (K[0]*K[4])); + n[1] = TYPE3((x[1] - K[5]) / K[4]); +} // NormalizeProjectionInv +/*----------------------------------------------------------------*/ + // compute relative pose of the given two cameras template inline void ComputeRelativeRotation(const TMatrix& Ri, const TMatrix& Rj, TMatrix& Rij) { @@ -25,6 +42,57 @@ inline void ComputeRelativePose(const TMatrix& Ri, const TPoint3 } // ComputeRelativePose /*----------------------------------------------------------------*/ +// Triangulate the position of a 3D point +// given two corresponding normalized projections and the pose of the second camera relative to the first one; +// returns the triangulated 3D point from the point of view of the first camera +template +bool TriangulatePoint3D( + const TMatrix& R, const TPoint3& C, + const TPoint2& pt1, const TPoint2& pt2, + TPoint3& X +) { + // convert image points to 3-vectors (of unit length) + // used to describe landmark observations/bearings in camera frames + const TPoint3 f1(pt1.x,pt1.y,1); + const TPoint3 f2(pt2.x,pt2.y,1); + const TPoint3 f2_unrotated = R.t() * f2; + const TPoint2 b(C.dot(f1), C.dot(f2_unrotated)); + // optimized inversion of A + const TYPE1 a = normSq(f1); + const TYPE1 c = f1.dot(f2_unrotated); + const TYPE1 d = -normSq(f2_unrotated); + const TYPE1 det = a*d+c*c; + if (ABS(det) < EPSILONTOLERANCE()) + return false; + const TYPE1 invDet = TYPE1(1)/det; + const TPoint2 lambda((d*b.x+c*b.y)*invDet, (a*b.y-c*b.x)*invDet); + const TPoint3 xm = lambda.x * f1; + const TPoint3 xn = C + lambda.y * f2_unrotated; + X = (xm + xn)*TYPE1(0.5); + return true; +} // TriangulatePoint3D +// same as above, but using the two camera poses; +// returns the 3D point in world coordinates +template +bool TriangulatePoint3D( + const TMatrix& K1, const TMatrix& K2, + const TMatrix& R1, const TMatrix& R2, + const TPoint3& C1, const TPoint3& C2, + const TPoint2& x1, const TPoint2& x2, + TPoint3& X +) { + TPoint2 pt1, pt2; + NormalizeProjectionInv(K1.val, x1.ptr(), pt1.ptr()); + NormalizeProjectionInv(K2.val, x2.ptr(), pt2.ptr()); + TMatrix R; TPoint3 C; + ComputeRelativePose(R1, C1, R2, C2, R, C); + if (!TriangulatePoint3D(R, C, pt1, pt2, X)) + return false; + X = R1.t() * X + C1; + return true; +} // TriangulatePoint3D +/*----------------------------------------------------------------*/ + // compute the homography matrix transforming points from image A to image B, // given the relative pose of B with respect to A, and the plane // (see R. Hartley, "Multiple View Geometry," 2004, pp. 234) @@ -109,25 +177,95 @@ inline TMatrix CreateF(const TMatrix& R, const TMatrix -inline bool IsRotationMatrix(const TMatrix& R) { - ASSERT(sizeof(TMatrix) == sizeof(TRMatrixBase)); - return ((const TRMatrixBase&)R).IsValid(); -} // IsRotationMatrix -template -inline bool IsRotationMatrix(const Eigen::Matrix& R) { - // the trace should be three and the determinant should be one - return (ISEQUAL(R.determinant(), TYPE(1)) && ISEQUAL((R*R.transpose()).trace(), TYPE(3))); -} // IsRotationMatrix -/*----------------------------------------------------------------*/ - -// enforce matrix orthogonality -template -inline void EnsureRotationMatrix(TMatrix& R) { - ASSERT(sizeof(TMatrix) == sizeof(TRMatrixBase)); - ((TRMatrixBase&)R).EnforceOrthogonality(); -} // EnsureRotationMatrix +// computes an RQ decomposition of 3x3 matrices as in: +// "Computing euler angles from a rotation matrix", Gregory G Slabaugh, 1999 +template +inline void RQDecomp3x3(Eigen::Matrix M, Eigen::Matrix& R, Eigen::Matrix& Q) { + // find Givens rotation for x axis: + // | 1 0 0 | + // Qx = | 0 c s |, c = m33/sqrt(m32^2 + m33^2), s = m32/sqrt(m32^2 + m33^2) + // | 0 -s c | + Eigen::Matrix cs = Eigen::Matrix(M(2,2), M(2,1)).normalized(); + Eigen::Matrix Qx{ {1, 0, 0}, {0, cs.x(), cs.y()}, {0, -cs.y(), cs.x()} }; + R.noalias() = M * Qx; + ASSERT(std::abs(R(2,1)) < FLT_EPSILON); + R(2,1) = 0; + // find Givens rotation for y axis: + // | c 0 -s | + // Qy = | 0 1 0 |, c = m33/sqrt(m31^2 + m33^2), s = -m31/sqrt(m31^2 + m33^2) + // | s 0 c | + cs = Eigen::Matrix(R(2,2), -R(2,0)).normalized(); + Eigen::Matrix Qy{ {cs.x(), 0, -cs.y()}, {0, 1, 0}, {cs.y(), 0, cs.x()} }; + M.noalias() = R * Qy; + ASSERT(std::abs(M(2,0)) < FLT_EPSILON); + M(2,0) = 0; + // find Givens rotation for z axis: + // | c s 0 | + // Qz = |-s c 0 |, c = m22/sqrt(m21^2 + m22^2), s = m21/sqrt(m21^2 + m22^2) + // | 0 0 1 | + cs = Eigen::Matrix(M(1,1), M(1,0)).normalized(); + Eigen::Matrix Qz{ {cs.x(), cs.y(), 0}, {-cs.y(), cs.x(), 0}, {0, 0, 1} }; + R.noalias() = M * Qz; + ASSERT(std::abs(R(1,0)) < FLT_EPSILON); + R(1,0) = 0; + // solve the decomposition ambiguity: + // - diagonal entries of R, except the last one, shall be positive + // - rotate R by 180 degree if necessary + if (R(0,0) < 0) { + if (R(1,1) < 0) { + // rotate around z for 180 degree: + // |-1, 0, 0| + // | 0, -1, 0| + // | 0, 0, 1| + R(0,0) *= -1; + R(0,1) *= -1; + R(1,1) *= -1; + Qz(0,0) *= -1; + Qz(0,1) *= -1; + Qz(1,0) *= -1; + Qz(1,1) *= -1; + } else { + // rotate around y for 180 degree: + // |-1, 0, 0| + // | 0, 1, 0| + // | 0, 0, -1| + R(0,0) *= -1; + R(0,2) *= -1; + R(1,2) *= -1; + R(2,2) *= -1; + Qz.transposeInPlace(); + Qy(0,0) *= -1; + Qy(0,2) *= -1; + Qy(2,0) *= -1; + Qy(2,2) *= -1; + } + } else if (R(1,1) < 0) { + // rotate around x for 180 degree: + // | 1, 0, 0| + // | 0, -1, 0| + // | 0, 0, -1| + R(0,1) *= -1; + R(0,2) *= -1; + R(1,1) *= -1; + R(1,2) *= -1; + R(2,2) *= -1; + Qz.transposeInPlace(); + Qy.transposeInPlace(); + Qx(1,1) *= -1; + Qx(1,2) *= -1; + Qx(2,1) *= -1; + Qx(2,2) *= -1; + } + // calculate orthogonal matrix + Q.noalias() = Qz.transpose() * Qy.transpose() * Qx.transpose(); +} +template +inline void RQDecomp3x3(const TMatrix& M, TMatrix& R, TMatrix& Q) { + const Eigen::Matrix _M((typename TMatrix::CEMatMap)M); + Eigen::Matrix _R, _Q; + RQDecomp3x3(_M, _R, _Q); + R = _R; Q = _Q; +} // RQDecomp3x3 /*----------------------------------------------------------------*/ @@ -142,17 +280,6 @@ template FORCEINLINE TYPE ComputeAngle(const TMatrix& R1, const TMatrix& R2) { return ComputeAngle(TMatrix(R1*R2.t())); } // ComputeAngle -// compute the distance on SO(3) between the two given rotations -// using log(R) as in: "Efficient and Robust Large-Scale Rotation Averaging", 2013 -// same result as above, but returns directly the angle -template -inline TYPE ComputeAngleSO3(const TMatrix& I) { - return TYPE(norm(((const TRMatrixBase&)I).GetRotationAxisAngle())); -} // ComputeAngleSO3 -template -FORCEINLINE TYPE ComputeAngleSO3(const TMatrix& R1, const TMatrix& R2) { - return ComputeAngleSO3(TMatrix(R1*R2.t())); -} // ComputeAngleSO3 /*----------------------------------------------------------------*/ @@ -413,15 +540,19 @@ FORCEINLINE TYPE AngleFromRotationMatrix(const TMatrix& R) { // given two 3D vectors, // compute the angle between them // returns cos(angle) -template +template inline TYPE2 ComputeAngle(const TYPE1* V1, const TYPE1* V2) { - // compute the angle between the rays return CLAMP(TYPE2((V1[0]*V2[0]+V1[1]*V2[1]+V1[2]*V2[2])/SQRT((V1[0]*V1[0]+V1[1]*V1[1]+V1[2]*V1[2])*(V2[0]*V2[0]+V2[1]*V2[1]+V2[2]*V2[2]))), TYPE2(-1), TYPE2(1)); } // ComputeAngle +// same as above, but with the vectors normalized +template +inline TYPE2 ComputeAngleN(const TYPE1* V1, const TYPE1* V2) { + return CLAMP(TYPE2(V1[0]*V2[0]+V1[1]*V2[1]+V1[2]*V2[2]), TYPE2(-1), TYPE2(1)); +} // ComputeAngleN // given three 3D points, // compute the angle between the vectors formed by the first point with the other two // returns cos(angle) -template +template inline TYPE2 ComputeAngle(const TYPE1* B, const TYPE1* X1, const TYPE1* X2) { // create the two vectors const TYPE1 V1[] = {X1[0]-B[0], X1[1]-B[1], X1[2]-B[2]}; @@ -431,7 +562,7 @@ inline TYPE2 ComputeAngle(const TYPE1* B, const TYPE1* X1, const TYPE1* X2) { // given four 3D points, // compute the angle between the two vectors formed by the first and second pair of points // returns cos(angle) -template +template inline TYPE2 ComputeAngle(const TYPE1* X1, const TYPE1* C1, const TYPE1* X2, const TYPE1* C2) { // subtract out the camera center const TYPE1 V1[] = {X1[0]-C1[0], X1[1]-C1[1], X1[2]-C1[2]}; @@ -468,13 +599,17 @@ TYPE ComputeTriangleAreaLenSq(TYPE lenaSq, TYPE lenbSq, TYPE lencSq) { } // compute area for a triangle defined by three 2D points template +TYPE EdgeFunction(const TPoint2& x0, const TPoint2& x1, const TPoint2& x2) { + return TYPE((x2-x0).cross(x1-x0)); +} +template TYPE ComputeTriangleArea(const TPoint2& x0, const TPoint2& x1, const TPoint2& x2) { - return abs((TYPE)((x0-x2).cross(x0-x1)/TYPE(2))); + return (TYPE)abs(EdgeFunction(x0, x1, x2)/TYPE(2)); } // compute area for a triangle defined by three 3D points template TYPE ComputeTriangleAreaSq(const TPoint3& x0, const TPoint3& x1, const TPoint3& x2) { - return (TYPE)(normSq((x1-x0).cross(x2-x0))/TYPE(4)); + return (TYPE)normSq((x1-x0).cross(x2-x0))/TYPE(4); } template TYPE ComputeTriangleArea(const TPoint3& x0, const TPoint3& x1, const TPoint3& x2) { @@ -607,12 +742,17 @@ inline TPoint3 CorrectBarycentricCoordinates(TPoint2 b) { ASSERT((b.x >= 0) && (b.y >= 0) && (b.x+b.y <= 1) && ISEQUAL(b.x+b.y+z, TYPE(1))); return TPoint3(b.x, b.y, z); } +template +inline TPoint3 PerspectiveCorrectBarycentricCoordinates(const TPoint3& b, TYPE z0, TYPE z1, TYPE z2) { + const TPoint3 pb(b.x * z1 * z2, b.y * z0 * z2, b.z * z0 * z1); + return pb / (pb.x + pb.y + pb.z); +} /*----------------------------------------------------------------*/ // Encodes/decodes a normalized 3D vector in two parameters for the direction template inline void Normal2Dir(const TPoint3& d, TPoint2& p) { - ASSERT(ISEQUAL(norm(d), T(1))); + ASSERT(ISEQUAL(norm(d), T(1)), "Norm = ", norm(d)); p.x = TR(atan2(d.y, d.x)); p.y = TR(acos(d.z)); } @@ -622,7 +762,7 @@ inline void Dir2Normal(const TPoint2& p, TPoint3& d) { d.x = TR(cos(p.x)*siny); d.y = TR(sin(p.x)*siny); d.z = TR(cos(p.y)); - ASSERT(ISEQUAL(norm(d), TR(1))); + ASSERT(ISEQUAL(norm(d), TR(1)), "Norm = ", norm(d)); } // Encodes/decodes a 3D vector in two parameters for the direction and one parameter for the scale template @@ -656,7 +796,7 @@ inline T MaxDepthDifference(T d, T threshold) { } template inline T DepthSimilarity(T d0, T d1) { - ASSERT(d0 > 0 && d1 > 0); + ASSERT(d0 > 0); #if 0 return ABS(d0-d1)*T(2)/(d0+d1); #else @@ -781,83 +921,73 @@ inline void ComputeMeanStdOffline(const TYPE* values, size_t size, TYPEW& mean, stddev = SQRT(variance); } // ComputeMeanStdOffline // same as above, but uses one pass only (online) -template -struct MeanStdOnline { - TYPEW mean; - TYPEW stddev; - size_t size; - MeanStdOnline() : mean(0), stddev(0), size(0) {} - MeanStdOnline(const TYPE* values, size_t _size) : mean(0), stddev(0), size(0) { - Compute(values, _size); - } - void Update(ARGTYPE v) { - const TYPEW val(v); - const TYPEW delta(val-mean); - mean += delta / (float)(++size); - stddev += delta * (val-mean); - } - void Compute(const TYPE* values, size_t _size) { - for (size_t i=0; i<_size; ++i) - Update(values[i]); - } - TYPEW GetMean() const { return mean; } - TYPEW GetVariance() const { return (stddev / (float)(size - 1)); } - TYPEW GetStdDev() const { return SQRT(GetVariance()); } -}; template inline void ComputeMeanStdOnline(const TYPE* values, size_t size, TYPEW& mean, TYPEW& stddev) { ASSERT(size > 0); - mean = TYPEW(0); - stddev = TYPEW(0); - for (size_t i=0; i -struct MeanStdOnlineFast { - TYPEW sum; - TYPEW sumSq; +} // ComputeMeanStdOnlineFast +#define ComputeMeanStd ComputeMeanStdOnline +// same as above, but an interactive version +template +struct MeanStd { + typedef TYPE Type; + typedef TYPEW TypeW; + typedef TYPER TypeR; + typedef ARGTYPE ArgType; + TYPEW sum, sumSq; size_t size; - MeanStdOnlineFast() : sum(0), sumSq(0), size(0) {} - MeanStdOnlineFast(const TYPE* values, size_t _size) : sum(0), sumSq(0), size(0) { - Compute(values, _size); - } - void Update(ARGTYPE v) { - const TYPEW val(v); + MeanStd() : sum(0), sumSq(0), size(0) {} + MeanStd(const Type* values, size_t _size) : MeanStd() { Compute(values, _size); } + void Update(ArgType v) { + const TYPEW val(static_cast(v)); sum += val; sumSq += SQUARE(val); ++size; } - void Compute(const TYPE* values, size_t _size) { + void Compute(const Type* values, size_t _size) { for (size_t i=0; i<_size; ++i) Update(values[i]); } - TYPEW GetMean() const { return sum / (float)size; } - TYPEW GetVariance() const { return (sumSq - SQUARE(sum) / (float)size) / (float)(size - 1); } + TYPEW GetSum() const { return sum; } + TYPEW GetMean() const { return static_cast(sum / static_cast(size)); } + TYPEW GetRMS() const { return static_cast(SQRT(sumSq / static_cast(size))); } + TYPEW GetVarianceN() const { return static_cast(sumSq - SQUARE(sum) / static_cast(size)); } + TYPEW GetVariance() const { return static_cast(GetVarianceN() / static_cast(size)); } TYPEW GetStdDev() const { return SQRT(GetVariance()); } + void Clear() { sum = sumSq = TYPEW(0); size = 0; } }; -template -inline void ComputeMeanStdOnlineFast(const TYPE* values, size_t size, TYPEW& mean, TYPEW& stddev) { - ASSERT(size > 0); - TYPEW sum(0), sumSq(0); - FOREACHRAWPTR(pVal, values, size) { - const TYPEW val(*pVal); - sum += val; - sumSq += SQUARE(val); +// same as above, but records also min/max values +template +struct MeanStdMinMax : MeanStd { + typedef MeanStd Base; + typedef TYPE Type; + typedef TYPEW TypeW; + typedef TYPER TypeR; + typedef ARGTYPE ArgType; + Type minVal, maxVal; + MeanStdMinMax() : minVal(std::numeric_limits::max()), maxVal(std::numeric_limits::lowest()) {} + MeanStdMinMax(const Type* values, size_t _size) : MeanStdMinMax() { Compute(values, _size); } + void Update(ArgType v) { + if (minVal > v) + minVal = v; + if (maxVal < v) + maxVal = v; + Base::Update(v); } - const TYPEW invSize(TYPEW(1)/(float)size); - mean = sum * invSize; - const TYPEW variance((sumSq - SQUARE(sum) * invSize) / (float)(size - 1)); - stddev = SQRT(variance); -} // ComputeMeanStdOnlineFast -#define ComputeMeanStd ComputeMeanStdOnlineFast -#define MeanStd MeanStdOnlineFast + void Compute(const Type* values, size_t _size) { + for (size_t i=0; i<_size; ++i) + Update(values[i]); + } +}; /*----------------------------------------------------------------*/ // given an array of values, compute the X84 threshold as in: @@ -1059,7 +1189,7 @@ inline void ExampleKDE() { File f; SamplesInserter(const String& fileName) : f(fileName, File::WRITE, File::CREATE | File::TRUNCATE) {} - inline void operator () (Real x, Real y) { + inline void operator() (Real x, Real y) { f.print("%g\t%g\n", x, y); } inline bool SamplePeak(bool bMaxRegion) const { @@ -1156,143 +1286,4 @@ void NormalizePoints(const CLISTDEF0(TPoint3)& pointsIn, CLISTDEF0(TPoint3 } /*----------------------------------------------------------------*/ - -// Least squares fits a plane to a 3D point set. -// See http://www.geometrictools.com/Documentation/LeastSquaresFitting.pdf -// Returns a fitting quality (1 - lambda_min/lambda_max): -// 1 is best (zero variance orthogonally to the fitting line) -// 0 is worst (isotropic case, returns a plane with default direction) -template -TYPE FitPlane(const TPoint3* points, size_t size, TPlane& plane) { - // compute a point on the plane, which is shown to be the centroid of the points - const Eigen::Map< const Eigen::Matrix > vPoints((const TYPE*)points, size, 3); - const TPoint3 c(vPoints.colwise().mean()); - - // assemble covariance matrix; matrix numbering: - // 0 - // 1 2 - // 3 4 5 - Eigen::Matrix A(Eigen::Matrix::Zero()); - FOREACHRAWPTR(pPt, points, size) { - const TPoint3 X(*pPt - c); - A(0,0) += X.x*X.x; - A(1,0) += X.x*X.y; - A(1,1) += X.y*X.y; - A(2,0) += X.x*X.z; - A(2,1) += X.y*X.z; - A(2,2) += X.z*X.z; - } - - // the plane normal is simply the eigenvector corresponding to least eigenvalue - const Eigen::SelfAdjointEigenSolver< Eigen::Matrix > es(A); - ASSERT(ISEQUAL(es.eigenvectors().col(0).norm(), TYPE(1))); - plane.Set(es.eigenvectors().col(0), c); - const TYPE* const vals(es.eigenvalues().data()); - ASSERT(vals[0] <= vals[1] && vals[1] <= vals[2]); - return TYPE(1) - vals[0]/vals[1]; -} -/*----------------------------------------------------------------*/ - - -template -struct CLeastSquares { - inline TYPE operator()(const TYPE) const { - return TYPE(1); - } -}; - -/* -template -struct CHuber_TorrMurray { - inline TYPE operator()(const TYPE d) const { - const TYPE b = 0.02; - - const TYPE d_abs = ABS(d); - if (d_abs < b) - return TYPE(1); - if (d_abs < 3 * b) - return b / d; //I think this is possibly wrong--should use 1/root(d) - return TYPE(0); //TODO, probably best to just return SIGMA/d; - } -}; -*/ - -template -struct CHuber { - inline CHuber(TYPE _threshold = 0.005) : threshold(_threshold) {} - inline TYPE operator()(const TYPE d) const { - const TYPE d_abs(ABS(d)); - if (d_abs < threshold) - return TYPE(1); - return SQRT(threshold * (TYPE(2) * d_abs - threshold)) / d_abs; - } - const TYPE threshold; -}; - -template -struct CBlakeZisserman { // Blake-Zisserman Gaussian + uniform - inline CBlakeZisserman(TYPE _threshold = 0.005) : threshold(_threshold) {} - inline TYPE operator()(const TYPE d) const { - const TYPE SD_INV = TYPE(1) / (0.5 * threshold); - const TYPE eps = exp(-SQUARE(threshold * SD_INV)); //Equally likely to be inlier or outlier at thresh - const TYPE d_abs = ABS(d) + 1e-12; - const TYPE zeroPoint = log(TYPE(1) + eps); //Needed for LS... - - const TYPE dCost_sq = zeroPoint - log(exp(-SQUARE(d * SD_INV)) + eps); - ASSERT(dCost_sq >= 0); // Cost computation failed? - - return SQRT(dCost_sq) / d_abs; - } - const TYPE threshold; -}; - -template -struct CPseudoHuber { - inline CPseudoHuber(TYPE _threshold = 0.005) : threshold(_threshold) {} - inline TYPE operator()(const TYPE d) const { - const TYPE b_sq = SQUARE(threshold); - const TYPE d_abs = ABS(d) + 1e-12; - - //C(delta) = 2*b^2*(sqrt(1+(delta/b)^2) - 1); - return SQRT(TYPE(2) * b_sq * (sqrt(1 + SQUARE(d * (1.0 / threshold))) - 1)) / d_abs; - } - const TYPE threshold; -}; - -template -struct CL1 { - inline TYPE operator()(const TYPE d) const { - return TYPE(1) / (SQRT(ABS(d)) + 1e-12); - } -}; - -typedef CBlakeZisserman CRobustNorm; -/*----------------------------------------------------------------*/ - - -// makes sure the inverse NCC score does not exceed 0.3 -#if 0 -template -inline T robustincc(const T x) { return x / (T(1) + T(3) * x); } -template -inline T robustinccg(const T x) { return T(1)/SQUARE(T(1) + T(3) * x); } -template -inline T unrobustincc(const T y) { return y / (T(1) - T(3) * y); } -#elif 0 -template -inline T robustincc(const T x) { return T(1)-EXP(x*x*T(-4)); } -template -inline T robustinccg(const T x) { return T(8)*x*EXP(x*x*T(-4)); } -template -inline T unrobustincc(const T y) { return SQRT(-LOGN(T(1) - y))/T(2); } -#else -template -inline T robustincc(const T x) { return x/SQRT(T(0.3)+x*x); } -template -inline T robustinccg(const T x) { return T(0.3)/((T(0.3)+x*x)*SQRT(T(0.3)+x*x)); } -template -inline T unrobustincc(const T y) { return (SQRT(30)*y)/(T(10)*SQRT(T(1) - y*y)); } -#endif -/*----------------------------------------------------------------*/ - } // namespace SEACAVE diff --git a/libs/Common/CUDA.cpp b/libs/Common/UtilCUDA.cpp similarity index 94% rename from libs/Common/CUDA.cpp rename to libs/Common/UtilCUDA.cpp index c46f3afdd..5b79679dc 100644 --- a/libs/Common/CUDA.cpp +++ b/libs/Common/UtilCUDA.cpp @@ -1,12 +1,12 @@ //////////////////////////////////////////////////////////////////// -// CUDA.cpp +// UtilCUDA.cpp // // Copyright 2007 cDc@seacave // Distributed under the Boost Software License, Version 1.0 // (See http://www.boost.org/LICENSE_1_0.txt) #include "Common.h" -#include "CUDA.h" +#include "UtilCUDA.h" #ifdef _USE_CUDA @@ -20,6 +20,7 @@ namespace CUDA { // S T R U C T S /////////////////////////////////////////////////// +int desiredDeviceID = -1; Devices devices; // GPU Architecture definitions @@ -30,7 +31,7 @@ int _convertSMVer2Cores(int major, int minor) // Defines for GPU Architecture types (using the SM version to determine the # of cores per SM struct sSMtoCores { - int SM; // 0xMm (hexidecimal notation), M = SM Major version, and m = SM minor version + int SM; // 0xMm (hexadecimal notation), M = SM Major version, and m = SM minor version int Cores; }; const sSMtoCores nGpuArchCoresPerSM[] = { @@ -49,6 +50,7 @@ int _convertSMVer2Cores(int major, int minor) {0x70, 64 }, // Volta Generation (SM 7.0) GV100 class {0x72, 64 }, // Volta Generation (SM 7.2) GV10B class {0x75, 64 }, // Turing Generation (SM 7.5) TU1xx class + {0x80, 64 }, // Ampere Generation (SM 8.0) GA100 class {-1, -1} }; @@ -121,10 +123,10 @@ CUresult _gpuGetMaxGflopsDeviceId(Device& bestDevice) if (device.major > 0 && device.major < 9999) { best_SM_arch = MAXF(best_SM_arch, device.major); device.ID = (CUdevice)current_device; - devices.Insert(device); + devices.emplace_back(std::move(device)); } } - if (devices.IsEmpty()) { + if (devices.empty()) { VERBOSE("CUDA error: all devices have compute mode prohibited"); return CUDA_ERROR_PROFILER_DISABLED; } @@ -132,22 +134,22 @@ CUresult _gpuGetMaxGflopsDeviceId(Device& bestDevice) // Find the best CUDA capable GPU device Device* max_perf_device = NULL; size_t max_compute_perf = 0; - FOREACHPTR(pDevice, devices) { - ASSERT(pDevice->computeMode != CU_COMPUTEMODE_PROHIBITED); - int sm_per_multiproc = _convertSMVer2Cores(pDevice->major, pDevice->minor); + for (Device& device: devices) { + ASSERT(device.computeMode != CU_COMPUTEMODE_PROHIBITED); + int sm_per_multiproc = _convertSMVer2Cores(device.major, device.minor); int multiProcessorCount; - if (reportCudaError(cuDeviceGetAttribute(&multiProcessorCount, CU_DEVICE_ATTRIBUTE_MULTIPROCESSOR_COUNT, (CUdevice)pDevice->ID)) != CUDA_SUCCESS) + if (reportCudaError(cuDeviceGetAttribute(&multiProcessorCount, CU_DEVICE_ATTRIBUTE_MULTIPROCESSOR_COUNT, device.ID)) != CUDA_SUCCESS) continue; int clockRate; - if (reportCudaError(cuDeviceGetAttribute(&clockRate, CU_DEVICE_ATTRIBUTE_CLOCK_RATE, pDevice->ID)) != CUDA_SUCCESS) + if (reportCudaError(cuDeviceGetAttribute(&clockRate, CU_DEVICE_ATTRIBUTE_CLOCK_RATE, device.ID)) != CUDA_SUCCESS) continue; size_t compute_perf = (size_t)multiProcessorCount * sm_per_multiproc * clockRate; if (compute_perf > max_compute_perf && (best_SM_arch < 3 || // if we find GPU with SM major > 2, search only these - pDevice->major == best_SM_arch) ) // if our device==dest_SM_arch, choose this, or else pass + device.major == best_SM_arch) ) // if our device==dest_SM_arch, choose this, or else pass { max_compute_perf = compute_perf; - max_perf_device = pDevice; + max_perf_device = &device; } } if (max_perf_device == NULL) @@ -162,6 +164,9 @@ CUresult _gpuGetMaxGflopsDeviceId(Device& bestDevice) // if the given device is -1, the best available device is selected CUresult initDevice(int deviceID) { + if (deviceID < -1) + return CUDA_ERROR_INVALID_DEVICE; + checkCudaError(cuInit(0)); Device device; @@ -175,8 +180,8 @@ CUresult initDevice(int deviceID) VERBOSE("CUDA error: compute capability 3.2 or greater required (available %d.%d for device[%d])", device.ID, device.major, device.minor); return CUDA_ERROR_INVALID_DEVICE; } - devices.Insert(device); - checkCudaError(cuCtxCreate(&devices.Last().ctx, CU_CTX_SCHED_AUTO, device.ID)); + devices.emplace_back(device); + checkCudaError(cuCtxCreate(&devices.back().ctx, CU_CTX_SCHED_AUTO, device.ID)); #if TD_VERBOSE != TD_VERBOSE_OFF char name[2048]; diff --git a/libs/Common/CUDA.h b/libs/Common/UtilCUDA.h similarity index 96% rename from libs/Common/CUDA.h rename to libs/Common/UtilCUDA.h index 1ab9b5303..6cdbb86a3 100644 --- a/libs/Common/CUDA.h +++ b/libs/Common/UtilCUDA.h @@ -1,5 +1,5 @@ //////////////////////////////////////////////////////////////////// -// CUDA.h +// UtilCUDA.h // // Copyright 2007 cDc@seacave // Distributed under the Boost Software License, Version 1.0 @@ -16,15 +16,26 @@ // CUDA driver #include +// CUDA toolkit +#include +#include +#include +#include +#include + +#include "UtilCUDADevice.h" + // D E F I N E S /////////////////////////////////////////////////// +// S T R U C T S /////////////////////////////////////////////////// + namespace SEACAVE { namespace CUDA { -// S T R U C T S /////////////////////////////////////////////////// +extern int desiredDeviceID; // global list of initialized devices struct Device { @@ -56,9 +67,8 @@ inline CUresult __reportCudaError(CUresult result, LPCSTR errorMessage) { ASSERT("CudaError" == NULL); return result; } -#define reportCudaError(val) CUDA::__reportCudaError(val, #val) - -#define checkCudaError(val) { const CUresult ret(CUDA::__reportCudaError(val, #val)); if (ret != CUDA_SUCCESS) return ret; } +#define reportCudaError(val) SEACAVE::CUDA::__reportCudaError(val, #val) +#define checkCudaError(val) { const CUresult ret(SEACAVE::CUDA::__reportCudaError(val, #val)); if (ret != CUDA_SUCCESS) return ret; } // outputs the proper CUDA error code and abort in the event that a CUDA host call returns an error inline void __ensureCudaResult(CUresult result, LPCSTR errorMessage) { @@ -67,7 +77,8 @@ inline void __ensureCudaResult(CUresult result, LPCSTR errorMessage) { ASSERT("CudaAbort" == NULL); exit(EXIT_FAILURE); } -#define ensureCudaResult(val) CUDA::__ensureCudaResult(val, #val) +#define ensureCudaResult(val) SEACAVE::CUDA::__ensureCudaResult(val, #val) +/*----------------------------------------------------------------*/ // rounds up addr to the align boundary template @@ -295,7 +306,6 @@ class KernelRT inline KernelRT() : hKernel(NULL) {} inline KernelRT(const ModuleRTPtr& _ptrModule, LPCSTR functionName) : ptrModule(_ptrModule) { Reset(functionName); } inline KernelRT(LPCSTR program, LPCSTR functionName, int mode=JIT::AUTO) { Reset(program, functionName, mode); } - inline ~KernelRT() { Release(); } inline bool IsValid() const { ASSERT(hKernel == NULL || (ptrModule != NULL && ptrModule->IsValid())); @@ -333,7 +343,7 @@ class KernelRT if ((result=cuParamSetSize(hKernel, paramOffset)) != CUDA_SUCCESS) return result; // launch the kernel (Driver API) - const CUdevprop& deviceProp = CUDA::devices.Last().prop; + const CUdevprop& deviceProp = CUDA::devices.back().prop; const int numBlockThreads(MINF(numThreads, deviceProp.maxThreadsPerBlock)); const int nBlocks(MAXF((numThreads+numBlockThreads-1)/numBlockThreads, 1)); if ((result=cuFuncSetBlockShape(hKernel, numBlockThreads, 1, 1)) != CUDA_SUCCESS) @@ -354,7 +364,7 @@ class KernelRT if ((result=cuParamSetSize(hKernel, paramOffset)) != CUDA_SUCCESS) return result; // launch the kernel (Driver API) - const CUdevprop& deviceProp = CUDA::devices.Last().prop; + const CUdevprop& deviceProp = CUDA::devices.back().prop; const REAL scale(MINF(REAL(1), SQRT((REAL)deviceProp.maxThreadsPerBlock/(REAL)(numThreads.x*numThreads.y)))); const SEACAVE::TPoint2 numBlockThreads(FLOOR2INT(SEACAVE::TPoint2(numThreads)*scale)); const TPoint2 nBlocks( @@ -458,7 +468,7 @@ class TArrayRT public: inline TArrayRT() : hArray(NULL) {} - inline TArrayRT(const Image8U::Size& size, unsigned flags=0) : hArray(NULL) { reportCudaError(Reset(size, flags)); } + inline TArrayRT(const cv::Size& size, unsigned flags=0) : hArray(NULL) { reportCudaError(Reset(size, flags)); } inline TArrayRT(unsigned width, unsigned height, unsigned depth=0, unsigned flags=0) : hArray(NULL) { reportCudaError(Reset(width, height, depth, flags)); } inline ~TArrayRT() { Release(); } @@ -478,7 +488,7 @@ class TArrayRT hArray = NULL; } } - inline CUresult Reset(const Image8U::Size& size, unsigned flags=0) { + inline CUresult Reset(const cv::Size& size, unsigned flags=0) { return Reset((unsigned)size.width, (unsigned)size.height, 0, flags); } CUresult Reset(unsigned width, unsigned height, unsigned depth=0, unsigned flags=0) { diff --git a/libs/Common/UtilCUDADevice.h b/libs/Common/UtilCUDADevice.h new file mode 100644 index 000000000..40791f8a1 --- /dev/null +++ b/libs/Common/UtilCUDADevice.h @@ -0,0 +1,91 @@ +//////////////////////////////////////////////////////////////////// +// UtilCUDADevice.h +// +// Copyright 2024 cDc@seacave +// Distributed under the Boost Software License, Version 1.0 +// (See http://www.boost.org/LICENSE_1_0.txt) + +#ifndef __SEACAVE_CUDA_DEVICE_H__ +#define __SEACAVE_CUDA_DEVICE_H__ + + +// I N C L U D E S ///////////////////////////////////////////////// + +#include "Config.h" + +// CUDA driver +#include + +// CUDA toolkit +#include + +#include + + +// D E F I N E S /////////////////////////////////////////////////// + +#if __CUDA_ARCH__ > 0 +#define __CDC__CUDA__ARCH__ 1 +#else +#undef __CDC__CUDA__ARCH__ +#endif + +#ifndef VERBOSE +#define DEFINE_VERBOSE 1 +#define VERBOSE(...) fprintf(stderr, __VA_ARGS__) +#endif + +// check for CUDA errors following a CUDA call +#define CUDA_CHECK(condition) SEACAVE::CUDA::checkCudaCall(condition) + +// check cudaGetLastError() for success +#define CUDA_CHECK_LAST_ERROR CUDA_CHECK(cudaGetLastError()); + + +// S T R U C T S /////////////////////////////////////////////////// + +namespace SEACAVE { + +namespace CUDA { + +inline void checkCudaCall(const cudaError_t error) { + if (error == cudaSuccess) + return; + VERBOSE("CUDA error at %s:%d: %s (code %d)", __FILE__, __LINE__, cudaGetErrorString(error), error); + ASSERT("CudaError" == NULL); + exit(EXIT_FAILURE); +} + +// define smart pointers for CUDA stream +struct CudaStreamDestructor { + void operator()(cudaStream_t s) { + if (s) + CUDA_CHECK(cudaStreamDestroy(s)); + } +}; + +typedef std::unique_ptr::type, CudaStreamDestructor> CudaStreamPtr; +inline CudaStreamPtr CreateStream() { + cudaStream_t stream; + CUDA_CHECK(cudaStreamCreate(&stream)); + return CudaStreamPtr(stream, CudaStreamDestructor()); +} + +typedef std::shared_ptr::type> CudaStreamSharedPtr; +inline CudaStreamSharedPtr CreateSharedStream() { + cudaStream_t stream; + CUDA_CHECK(cudaStreamCreate(&stream)); + return CudaStreamSharedPtr(stream, CudaStreamDestructor()); +} +/*----------------------------------------------------------------*/ + +} // namespace CUDA + +} // namespace SEACAVE + +#ifdef DEFINE_VERBOSE +#undef DEFINE_VERBOSE +#undef VERBOSE +#endif + +#endif // __SEACAVE_CUDA_DEVICE_H__ diff --git a/libs/IO/CMakeLists.txt b/libs/IO/CMakeLists.txt index a354376be..0f0595194 100644 --- a/libs/IO/CMakeLists.txt +++ b/libs/IO/CMakeLists.txt @@ -2,7 +2,7 @@ FIND_PACKAGE(PNG QUIET) if(PNG_FOUND) INCLUDE_DIRECTORIES(${PNG_INCLUDE_DIRS}) - ADD_DEFINITIONS(${PNG_DEFINITIONS} -D_USE_PNG) + ADD_DEFINITIONS(${PNG_DEFINITIONS}) SET(_USE_PNG TRUE CACHE INTERNAL "") else() SET(PNG_LIBRARIES "") @@ -10,7 +10,7 @@ endif() FIND_PACKAGE(JPEG QUIET) if(JPEG_FOUND) INCLUDE_DIRECTORIES(${JPEG_INCLUDE_DIR}) - ADD_DEFINITIONS(${JPEG_DEFINITIONS} -D_USE_JPG) + ADD_DEFINITIONS(${JPEG_DEFINITIONS}) SET(_USE_JPG TRUE CACHE INTERNAL "") else() SET(JPEG_LIBRARIES "") @@ -18,29 +18,24 @@ endif() FIND_PACKAGE(TIFF QUIET) if(TIFF_FOUND) INCLUDE_DIRECTORIES(${TIFF_INCLUDE_DIR}) - ADD_DEFINITIONS(${TIFF_DEFINITIONS} -D_USE_TIFF) + ADD_DEFINITIONS(${TIFF_DEFINITIONS}) SET(_USE_TIFF TRUE CACHE INTERNAL "") else() SET(TIFF_LIBRARIES "") endif() # List sources files -FILE(GLOB PCH_C "Common.cpp") - FILE(GLOB LIBRARY_FILES_C "*.cpp") FILE(GLOB LIBRARY_FILES_H "*.h" "*.inl") -# Place Common.cpp as the first file in the list -# needed by cotire when setting PCH manually -LIST(REMOVE_ITEM LIBRARY_FILES_C ${PCH_C}) -SET(LIBRARY_FILES_C "${PCH_C};${LIBRARY_FILES_C}") - -cxx_library_with_type_no_pch(IO "Libs" "STATIC" "${cxx_default}" +cxx_library_with_type(IO "Libs" "" "${cxx_default}" ${LIBRARY_FILES_C} ${LIBRARY_FILES_H} ) # Manually set Common.h as the precompiled header -set_target_pch(IO Common.h) +IF(CMAKE_VERSION VERSION_GREATER_EQUAL 3.16.0) + TARGET_PRECOMPILE_HEADERS(IO PRIVATE "Common.h") +endif() # Link its dependencies TARGET_LINK_LIBRARIES(IO Common ${PNG_LIBRARIES} ${JPEG_LIBRARIES} ${TIFF_LIBRARIES} ${EXIV2_LIBS}) @@ -50,7 +45,7 @@ SET_TARGET_PROPERTIES(IO PROPERTIES PUBLIC_HEADER "${LIBRARY_FILES_H}") INSTALL(TARGETS IO EXPORT OpenMVSTargets - RUNTIME DESTINATION "${INSTALL_BIN_DIR}" COMPONENT bin - LIBRARY DESTINATION "${INSTALL_LIB_DIR}" COMPONENT shlib - ARCHIVE DESTINATION "${INSTALL_LIB_DIR}" COMPONENT lib - PUBLIC_HEADER DESTINATION "${INSTALL_INCLUDE_DIR}/IO" COMPONENT dev) + LIBRARY DESTINATION "${INSTALL_LIB_DIR}" + ARCHIVE DESTINATION "${INSTALL_LIB_DIR}" + RUNTIME DESTINATION "${INSTALL_BIN_DIR}" + PUBLIC_HEADER DESTINATION "${INSTALL_INCLUDE_DIR}/IO") diff --git a/libs/IO/Image.cpp b/libs/IO/Image.cpp index 0a9de141f..3d3e1cecb 100644 --- a/libs/IO/Image.cpp +++ b/libs/IO/Image.cpp @@ -893,7 +893,7 @@ CImage* CImage::Create(LPCTSTR szName, IMCREATE mode) pImage = new CImagePNG(); #endif #ifdef _IMAGE_JPG - else if (_tcsncicmp(fext, _T(".jpg"), 4) == 0) + else if (_tcsncicmp(fext, _T(".jpg"), 4) == 0 || _tcsncicmp(fext, _T(".jpeg"), 5) == 0) pImage = new CImageJPG(); #endif #ifdef _IMAGE_TIFF diff --git a/libs/IO/ImageTGA.cpp b/libs/IO/ImageTGA.cpp index 1f50a3408..deefa1227 100644 --- a/libs/IO/ImageTGA.cpp +++ b/libs/IO/ImageTGA.cpp @@ -64,7 +64,7 @@ HRESULT CImageTGA::ReadHeader() ((ISTREAM*)m_pStream)->setPos(0); TGAINFOHEADER tgaInfo; m_pStream->read(&tgaInfo, sizeof(TGAINFOHEADER)); - if (tgaInfo.byCMType != 0) { // palletted images not supported + if (tgaInfo.byCMType != 0) { // paletted images not supported LOG(LT_IMAGE, "error: invalid TGA image"); return _INVALIDFILE; } diff --git a/libs/IO/ImageTIFF.cpp b/libs/IO/ImageTIFF.cpp index 86252c0db..bfdd7e177 100644 --- a/libs/IO/ImageTIFF.cpp +++ b/libs/IO/ImageTIFF.cpp @@ -76,10 +76,10 @@ extern "C" { static tmsize_t _tiffisReadProc(thandle_t fd, void* buf, tmsize_t size); static tmsize_t _tiffosWriteProc(thandle_t fd, void* buf, tmsize_t size); static tmsize_t _tiffisWriteProc(thandle_t, void*, tmsize_t); - static uint64 _tiffosSeekProc(thandle_t fd, uint64 off, int whence); - static uint64 _tiffisSeekProc(thandle_t fd, uint64 off, int whence); - static uint64 _tiffosSizeProc(thandle_t fd); - static uint64 _tiffisSizeProc(thandle_t fd); + static uint64_t _tiffosSeekProc(thandle_t fd, uint64_t off, int whence); + static uint64_t _tiffisSeekProc(thandle_t fd, uint64_t off, int whence); + static uint64_t _tiffosSizeProc(thandle_t fd); + static uint64_t _tiffisSizeProc(thandle_t fd); static int _tiffosCloseProc(thandle_t fd); static int _tiffisCloseProc(thandle_t fd); static int _tiffDummyMapProc(thandle_t, void** base, toff_t* size); @@ -132,26 +132,26 @@ extern "C" { return 0; } - static uint64 _tiffosSeekProc(thandle_t fd, uint64 off, int whence) + static uint64_t _tiffosSeekProc(thandle_t fd, uint64_t off, int whence) { tiffos_data *data = reinterpret_cast(fd); OSTREAM* os = data->stream; // if the stream has already failed, don't do anything if (os == NULL) - return static_cast(-1); + return static_cast(-1); bool bSucceeded(true); switch (whence) { case SEEK_SET: { // Compute 64-bit offset - uint64 new_offset = static_cast(data->start_pos) + off; + uint64_t new_offset = static_cast(data->start_pos) + off; // Verify that value does not overflow size_f_t offset = static_cast(new_offset); - if (static_cast(offset) != new_offset) - return static_cast(-1); + if (static_cast(offset) != new_offset) + return static_cast(-1); bSucceeded = os->setPos(offset); break; @@ -160,8 +160,8 @@ extern "C" { { // Verify that value does not overflow size_f_t offset = static_cast(off); - if (static_cast(offset) != off) - return static_cast(-1); + if (static_cast(offset) != off) + return static_cast(-1); bSucceeded = os->setPos(os->getPos()+offset); break; @@ -170,8 +170,8 @@ extern "C" { { // Verify that value does not overflow size_f_t offset = static_cast(off); - if (static_cast(offset) != off) - return static_cast(-1); + if (static_cast(offset) != off) + return static_cast(-1); bSucceeded = os->setPos(os->getSize()-offset); break; @@ -199,23 +199,23 @@ extern "C" { } // only do something if desired seek position is valid - if ((static_cast(origin) + off) > static_cast(data->start_pos)) { - uint64 num_fill; + if ((static_cast(origin) + off) > static_cast(data->start_pos)) { + uint64_t num_fill; // extend the stream to the expected size os->setPos(os->getSize()); - num_fill = (static_cast(origin)) + off - os->getPos(); + num_fill = (static_cast(origin)) + off - os->getPos(); const char dummy = '\0'; - for (uint64 i = 0; i < num_fill; i++) + for (uint64_t i = 0; i < num_fill; i++) os->write(&dummy, 1); // retry the seek - os->setPos(static_cast(static_cast(origin) + off)); + os->setPos(static_cast(static_cast(origin) + off)); } } - return static_cast(os->getPos()); + return static_cast(os->getPos()); } - static uint64 _tiffisSeekProc(thandle_t fd, uint64 off, int whence) + static uint64_t _tiffisSeekProc(thandle_t fd, uint64_t off, int whence) { tiffis_data *data = reinterpret_cast(fd); ISTREAM* is = data->stream; @@ -224,12 +224,12 @@ extern "C" { case SEEK_SET: { // Compute 64-bit offset - uint64 new_offset = static_cast(data->start_pos) + off; + uint64_t new_offset = static_cast(data->start_pos) + off; // Verify that value does not overflow size_f_t offset = static_cast(new_offset); - if (static_cast(offset) != new_offset) - return static_cast(-1); + if (static_cast(offset) != new_offset) + return static_cast(-1); is->setPos(offset); break; @@ -238,8 +238,8 @@ extern "C" { { // Verify that value does not overflow size_f_t offset = static_cast(off); - if (static_cast(offset) != off) - return static_cast(-1); + if (static_cast(offset) != off) + return static_cast(-1); is->setPos(is->getPos()+offset); break; @@ -248,27 +248,27 @@ extern "C" { { // Verify that value does not overflow size_f_t offset = static_cast(off); - if (static_cast(offset) != off) - return static_cast(-1); + if (static_cast(offset) != off) + return static_cast(-1); is->setPos(is->getSize()-offset); break; } } - return (uint64)(is->getPos() - data->start_pos); + return (uint64_t)(is->getPos() - data->start_pos); } - static uint64 _tiffosSizeProc(thandle_t fd) + static uint64_t _tiffosSizeProc(thandle_t fd) { tiffos_data *data = reinterpret_cast(fd); - return (uint64)data->stream->getSize(); + return (uint64_t)data->stream->getSize(); } - static uint64 _tiffisSizeProc(thandle_t fd) + static uint64_t _tiffisSizeProc(thandle_t fd) { tiffis_data *data = reinterpret_cast(fd); - return (uint64)data->stream->getSize(); + return (uint64_t)data->stream->getSize(); } static int _tiffosCloseProc(thandle_t fd) @@ -448,7 +448,7 @@ HRESULT CImageTIFF::ReadData(void* pData, PIXELFORMAT dataFormat, Size nStride, { if (m_state && m_width && m_height) { TIFF* tif = (TIFF*)m_state; - uint32 tile_width0 = m_width, tile_height0 = 0; + uint32_t tile_width0 = m_width, tile_height0 = 0; int is_tiled = TIFFIsTiled(tif); uint16 photometric; TIFFGetField(tif, TIFFTAG_PHOTOMETRIC, &photometric); @@ -477,14 +477,14 @@ HRESULT CImageTIFF::ReadData(void* pData, PIXELFORMAT dataFormat, Size nStride, tile_width0 = m_width; if (tile_height0 <= 0 || - (!is_tiled && tile_height0 == std::numeric_limits::max())) + (!is_tiled && tile_height0 == std::numeric_limits::max())) tile_height0 = m_height; uint8_t* data = (uint8_t*)pData; if (!is_tiled && tile_height0 == 1 && dataFormat == m_format && nStride == m_stride) { // read image directly to the data buffer for (Size j=0; j m_height) tile_height = m_height - y; - for (uint32 x = 0; x < m_width; x += tile_width0) { - uint32 tile_width = tile_width0; + for (uint32_t x = 0; x < m_width; x += tile_width0) { + uint32_t tile_width = tile_width0; if (x + tile_width > m_width) tile_width = m_width - x; @@ -510,9 +510,9 @@ HRESULT CImageTIFF::ReadData(void* pData, PIXELFORMAT dataFormat, Size nStride, { uint8_t* bstart = buffer; if (!is_tiled) - ok = TIFFReadRGBAStrip(tif, y, (uint32*)buffer); + ok = TIFFReadRGBAStrip(tif, y, (uint32_t*)buffer); else { - ok = TIFFReadRGBATile(tif, x, y, (uint32*)buffer); + ok = TIFFReadRGBATile(tif, x, y, (uint32_t*)buffer); //Tiles fill the buffer from the bottom up bstart += (tile_height0 - tile_height) * tile_width0 * 4; } @@ -521,7 +521,7 @@ HRESULT CImageTIFF::ReadData(void* pData, PIXELFORMAT dataFormat, Size nStride, return _INVALIDFILE; } - for (uint32 i = 0; i < tile_height; ++i) { + for (uint32_t i = 0; i < tile_height; ++i) { uint8_t* dst = data + x*3 + lineWidth*(tile_height - i - 1); uint8_t* src = bstart + i*tile_width0*4; if (!FilterFormat(dst, dataFormat, nStride, src, m_format, m_stride, tile_width)) { diff --git a/libs/IO/OBJ.cpp b/libs/IO/OBJ.cpp index bf82ee43d..0c1f5309b 100644 --- a/libs/IO/OBJ.cpp +++ b/libs/IO/OBJ.cpp @@ -61,7 +61,8 @@ bool ObjModel::MaterialLib::Save(const String& prefix, bool texLossless) const for (int_t i = 0; i < (int_t)materials.size(); ++i) { const Material& mat = materials[i]; // save material description - out << "newmtl " << mat.name << "\n" + std::stringstream ss; + ss << "newmtl " << mat.name << "\n" << "Ka 1.000000 1.000000 1.000000" << "\n" << "Kd " << mat.Kd.r << " " << mat.Kd.g << " " << mat.Kd.b << "\n" << "Ks 0.000000 0.000000 0.000000" << "\n" @@ -69,11 +70,20 @@ bool ObjModel::MaterialLib::Save(const String& prefix, bool texLossless) const << "illum 1" << "\n" << "Ns 1.000000" << "\n"; // save material maps - if (mat.diffuse_map.empty()) + if (mat.diffuse_map.empty()) { + #ifdef OBJ_USE_OPENMP + #pragma omp critical + #endif + out << ss.str(); continue; + } if (mat.diffuse_name.IsEmpty()) const_cast(mat.diffuse_name) = name+"_"+mat.name+"_map_Kd."+(texLossless?"png":"jpg"); - out << "map_Kd " << mat.diffuse_name << "\n"; + ss << "map_Kd " << mat.diffuse_name << "\n"; + #ifdef OBJ_USE_OPENMP + #pragma omp critical + #endif + out << ss.str(); const bool bRet(mat.diffuse_map.Save(pathName+mat.diffuse_name)); #ifdef OBJ_USE_OPENMP #pragma omp critical diff --git a/libs/IO/PLY.cpp b/libs/IO/PLY.cpp index a1b4d94a6..4dbfa5e11 100644 --- a/libs/IO/PLY.cpp +++ b/libs/IO/PLY.cpp @@ -1,7 +1,7 @@ //////////////////////////////////////////////////////////////////// // PLY.cpp // -// Copyright 2007 cDc@seacave +// Copyright 2023 cDc@seacave // Distributed under the Boost Software License, Version 1.0 // (See http://www.boost.org/LICENSE_1_0.txt) @@ -65,9 +65,8 @@ Init PLY data as empty. PLY::PLY() : - mfp(NULL), f(NULL), ostream(NULL), which_elem(NULL), other_elems(NULL), current_rules(NULL), rule_list(NULL), - write_type_names(type_names) + istream(NULL), mfp(NULL), write_type_names(type_names) { } @@ -83,17 +82,28 @@ Free the memory used by a PLY file. void PLY::release() { - flush(); if (mfp) { delete mfp; mfp = NULL; + ostream = NULL; + } else + if (istream) { + if (static_cast(istream)->getInputStream(ISTREAM::LAYER_ID_IN)) { + IOSTREAM* const iostream(static_cast(istream)->getIOStream(ISTREAM::LAYER_ID_IN, OSTREAM::LAYER_ID_OUT)); + if (iostream) + delete iostream; + else + delete istream; + } else { + ASSERT(static_cast(ostream)->getOutputStream(OSTREAM::LAYER_ID_OUT)); + IOSTREAM* const iostream(static_cast(ostream)->getIOStream(ISTREAM::LAYER_ID_IN, OSTREAM::LAYER_ID_OUT)); + if (iostream) + delete iostream; + else + delete ostream; + } + istream = NULL; } - f = NULL; - if (!filename.empty()) { - filename.clear(); - delete ostream; - } - ostream = NULL; if (!elems.empty()) { for (size_t i=0; iother_list.size(); ++i) { OtherElem& elem = other_elems->other_list[i]; delete[] elem.other_data; delete elem.other_props; } - delete other_elems; + delete other_elems; other_elems = NULL; } comments.clear(); obj_info.clear(); @@ -131,46 +141,61 @@ void PLY::release() Given a file pointer, get ready to write PLY data to the file. Entry: -f - the given file pointer +fp - the given file pointer nelems - number of elements in object elem_names - list of element names file_type - file type, either ascii or binary +memBufferSize - memory file initial size (useful if the ply size is unknown) Exit: returns a pointer to a PlyFile, used to refer to this file, or NULL if error ******************************************************************************/ -bool PLY::write(LPCSTR filename, int nelems, LPCSTR* elem_names, int file_type, size_t bufferSize) +bool PLY::write(LPCSTR _filename, int nelems, LPCSTR* elem_names, int _file_type, size_t memBufferSize) { - this->filename = filename; - return write(ostream, nelems, elem_names, file_type, bufferSize); + filename = _filename; + if (memBufferSize == 0) { + // create output file now + File* const pf(new File(filename.c_str(), File::WRITE, File::CREATE | File::TRUNCATE)); + if (!pf->isOpen()) + return false; + return write(new BufferedOutputStream(pf, 64*1024), nelems, elem_names, _file_type, memBufferSize); + } + return write((OSTREAM*)NULL, nelems, elem_names, _file_type, memBufferSize); } -bool PLY::write(OSTREAM* f, int nelems, LPCSTR* elem_names, int file_type, size_t bufferSize) +bool PLY::write(OSTREAM* fp, int nelems, LPCSTR* elem_names, int _file_type, size_t memBufferSize) { - /* create a record for this object */ - this->file_type = file_type; - this->version = 1.0; - this->ostream = f; - this->other_elems = NULL; + // create a record for this object + file_type = _file_type; + version = 1.0; + other_elems = NULL; - /* init buffer if requested */ - if (bufferSize > 0) { - mfp = new MemFile(bufferSize); - this->f = mfp; + if (memBufferSize > 0) { + // write ply into a memory buffer, and save it to disk at the end; + // useful in case the number of ply elements is not know from the start + // in order to avoid an additional file copy operation + ASSERT(fp == NULL && !filename.empty()); + mfp = new MemFile(memBufferSize); + ostream = mfp; } else { - this->f = f; + // directly write ply data to disk; + // in case the number of ply elements is unknown from the start, + // they are written into a temporary file and the header is written + // at the end using a file copy operation + ASSERT(fp != NULL); + ostream = fp; + mfp = NULL; } - /* tuck aside the names of the elements */ - this->elems.resize(nelems); + // tuck aside the names of the elements + elems.resize(nelems); for (int i = 0; i < nelems; ++i) { PlyElement* elem = new PlyElement; elem->name = elem_names[i]; elem->num = 0; - this->elems[i] = elem; + elems[i] = elem; } - return true; } @@ -187,19 +212,19 @@ prop_list - list of properties ******************************************************************************/ void PLY::element_layout( - const char *elem_name, + const char* elem_name, int nelems, int nprops, - PlyProperty *prop_list + PlyProperty* prop_list ) { - /* look for appropriate element */ + // look for appropriate element PlyElement *elem = find_element(elem_name); if (elem == NULL) abort_ply("error: element_layout: can't find element '%s'", elem_name); elem->num = nelems; - /* copy the list of properties */ + // copy the list of properties elem->props.resize(nprops); elem->store_prop.resize(nprops); @@ -220,21 +245,21 @@ elem_name - name of element that information is being specified about prop - the new property ******************************************************************************/ -void PLY::describe_property(const char *elem_name, const PlyProperty& prop) +void PLY::describe_property(const char* elem_name, const PlyProperty& prop) { - /* look for appropriate element */ + // look for appropriate element put_element_setup(elem_name); - /* describe property */ + // describe property describe_property(prop); } -void PLY::describe_property(const char *elem_name, int nprops, const PlyProperty* props) +void PLY::describe_property(const char* elem_name, int nprops, const PlyProperty* props) { - /* look for appropriate element */ + // look for appropriate element put_element_setup(elem_name); - /* describe properties */ + // describe properties for (int i=0; iisOpen()) + std::string filenameTmp; + if (mfp != NULL) { + // ply data was written into a memory buffer, + // now write the header to disk and append the data in memory + ostream = NULL; + } else if (!filename.empty() && ostream->getPos() > 0) { + // close this file, rename it, and open a new file to write the header + delete ostream; ostream = NULL; + filenameTmp = filename+".tmp"; + if (!File::renameFile(filename.c_str(), filenameTmp.c_str())) return false; - ostream = new BufferedOutputStream(ostream, FILE_WRITE_MINBUF_SIZE); + } + if (ostream == NULL) { + File* const pf(new File(filename.c_str(), File::WRITE, File::CREATE | File::TRUNCATE)); + if (!pf->isOpen()) + return false; + ostream = new BufferedOutputStream(pf, 64*1024); } + // write header ostream->print("ply\n"); - switch (this->file_type) { + switch (file_type) { case ASCII: ostream->print("format ascii 1.0\n"); break; @@ -288,39 +327,38 @@ bool PLY::header_complete() ostream->print("format binary_little_endian 1.0\n"); break; default: - abort_ply("error: ply_header_complete: bad file type = %d\n", this->file_type); + abort_ply("error: ply_header_complete: bad file type = %d\n", file_type); } - /* write out the comments */ - for (size_t i = 0; i < this->comments.size(); ++i) - ostream->print("comment %s\n", this->comments[i].c_str()); + // write out the comments + for (size_t i = 0; i < comments.size(); ++i) + ostream->print("comment %s\n", comments[i].c_str()); - /* write out object information */ - for (size_t i = 0; i < this->obj_info.size(); ++i) - ostream->print("obj_info %s\n", this->obj_info[i].c_str()); + // write out object information + for (size_t i = 0; i < obj_info.size(); ++i) + ostream->print("obj_info %s\n", obj_info[i].c_str()); - /* write out information about each element */ - for (size_t i = 0; i < this->elems.size(); ++i) { - PlyElement *elem = this->elems[i]; + // write out information about each element + for (size_t i = 0; i < elems.size(); ++i) { + PlyElement *elem = elems[i]; + ASSERT(elem->num > 0); ostream->print("element %s %d\n", elem->name.c_str(), elem->num); - /* write out each property */ + // write out each property for (size_t j = 0; j < elem->props.size(); ++j) { PlyProperty *prop = elem->props[j]; if (prop->is_list == LIST) { ostream->print("property list "); - write_scalar_type(ostream, prop->count_external); + write_scalar_type(prop->count_external); ostream->print(" "); - write_scalar_type(ostream, prop->external_type); + write_scalar_type(prop->external_type); ostream->print(" %s\n", prop->name.c_str()); - } - else if (prop->is_list == STRING) { + } else if (prop->is_list == STRING) { ostream->print("property string"); ostream->print(" %s\n", prop->name.c_str()); - } - else { + } else { ostream->print("property "); - write_scalar_type(ostream, prop->external_type); + write_scalar_type(prop->external_type); ostream->print(" %s\n", prop->name.c_str()); } } @@ -328,12 +366,26 @@ bool PLY::header_complete() ostream->print("end_header\n"); - /* write the body also if fully buffered */ - if (mfp && mfp->getSize() > 0) { + if (mfp != NULL) { + // now write the ply data from memory to disk ostream->write(mfp->getBuffer(), mfp->getSize()); delete mfp; mfp = NULL; + } else if (!filenameTmp.empty()) { + // append the body of the ply from the temp file, and delete it + File ftmp(filenameTmp.c_str(), File::READ, File::OPEN); + if (!ftmp.isOpen()) + return false; + Unsigned8Arr buffer(256 * 1024); + size_t len; + while ((len = ftmp.read(buffer.data(), buffer.size())) > 0) + ostream->write(buffer.data(), len); + ftmp.close(); + File::deleteFile(filenameTmp.c_str()); + } else { + // element writing is starting next, reset counters + for (size_t i = 0; i < elems.size(); ++i) + elems[i]->num = 0; } - return true; } @@ -346,12 +398,12 @@ before a call to the routine ply_put_element(). elem_name - name of element we're talking about ******************************************************************************/ -void PLY::put_element_setup(const char *elem_name) +void PLY::put_element_setup(const char* elem_name) { PlyElement *elem = find_element(elem_name); if (elem == NULL) abort_ply("error: put_element_setup: can't find element '%s'", elem_name); - this->which_elem = elem; + which_elem = elem; } @@ -372,16 +424,16 @@ void PLY::put_element(const void* elem_ptr) ValueType val; char **other_ptr; - PlyElement *elem = this->which_elem; - elem_data = (char *)elem_ptr; - other_ptr = (char **)(((char *)elem_ptr) + elem->other_offset); + PlyElement *elem = which_elem; + elem_data = (char*)elem_ptr; + other_ptr = (char**)(elem_data + elem->other_offset); - /* write out either to an ascii or binary file */ - if (this->file_type == ASCII) { + // write out either to an ascii or binary file + if (file_type == ASCII) { - /* write an ascii file */ + // write an ascii file - /* write out each property of the element */ + // write out each property of the element for (size_t j = 0; j < elem->props.size(); ++j) { PlyProperty *prop = elem->props[j]; @@ -389,21 +441,21 @@ void PLY::put_element(const void* elem_ptr) if (elem->store_prop[j] == OTHER_PROP) elem_data = *other_ptr; else - elem_data = (char *)elem_ptr; + elem_data = (char*)elem_ptr; switch (prop->is_list) { - case SCALAR: { /* scalar */ + case SCALAR: { // scalar item = elem_data + prop->offset; get_stored_item((void*)item, prop->internal_type, val); write_ascii_item(val, prop->internal_type, prop->external_type); break; } - case LIST: { /* list */ + case LIST: { // list item = elem_data + prop->count_offset; get_stored_item((void*)item, prop->count_internal, val); write_ascii_item(val, prop->count_internal, prop->count_external); const int list_count(ValueType2Type(val, prop->count_external)); - item_ptr = (char **)(elem_data + prop->offset); + item_ptr = (char**)(elem_data + prop->offset); item = item_ptr[0]; const int item_size = ply_type_size[prop->internal_type]; for (int k = 0; k < list_count; k++) { @@ -413,10 +465,10 @@ void PLY::put_element(const void* elem_ptr) } break; } - case STRING: { /* string */ + case STRING: { // string item = elem_data + prop->offset; - char **str = (char **)item; - f->print("\"%s\"", *str); + char** str = (char**)item; + ostream->print("\"%s\"", *str); break; } default: @@ -424,33 +476,32 @@ void PLY::put_element(const void* elem_ptr) } } - f->print("\n"); - } - else { + ostream->print("\n"); + } else { - /* write a binary file */ + // write a binary file - /* write out each property of the element */ + // write out each property of the element for (size_t j = 0; j < elem->props.size(); ++j) { PlyProperty *prop = elem->props[j]; if (elem->store_prop[j] == OTHER_PROP) elem_data = *other_ptr; else - elem_data = (char *) elem_ptr; + elem_data = (char*)elem_ptr; switch (prop->is_list) { - case SCALAR: { /* scalar */ + case SCALAR: { // scalar item = elem_data + prop->offset; get_stored_item((void*)item, prop->internal_type, val); write_binary_item(val, prop->internal_type, prop->external_type); break; } - case LIST: { /* list */ + case LIST: { // list item = elem_data + prop->count_offset; int item_size = ply_type_size[prop->count_internal]; get_stored_item((void*)item, prop->count_internal, val); write_binary_item(val, prop->count_internal, prop->count_external); const int list_count(ValueType2Type(val, prop->count_external)); - item_ptr = (char **)(elem_data + prop->offset); + item_ptr = (char**)(elem_data + prop->offset); item = item_ptr[0]; item_size = ply_type_size[prop->internal_type]; for (int k = 0; k < list_count; k++) { @@ -460,16 +511,16 @@ void PLY::put_element(const void* elem_ptr) } break; } - case STRING: { /* string */ + case STRING: { // string item = elem_data + prop->offset; - char **str = (char **) item; + char** str = (char**)item; - /* write the length */ - const int len = (int)strlen(*str) + 1; - f->write(&len, sizeof(int)); + // write the length + const int len = (int)_tcslen(*str) + 1; + ostream->write(&len, sizeof(int)); - /* write the string, including the null character */ - f->write(*str, len); + // write the string, including the null character + ostream->write(*str, len); break; } default: @@ -478,17 +529,8 @@ void PLY::put_element(const void* elem_ptr) } } - /* if buffered mode, count element items */ - if (mfp) { - if (ostream != NULL) { - if (mfp->getSizeBuffer()-mfp->getSize() < 256) { - ostream->write(mfp->getBuffer(), mfp->getSize()); - mfp->setSize(0); - } - } else { - elem->num++; - } - } + // count element items + elem->num++; } @@ -502,7 +544,7 @@ void PLY::put_element(const void* elem_ptr) Given a file pointer, get ready to read PLY data from the file. Entry: -f - the given file pointer +fp - the given file pointer Exit: nelems - number of elements in object @@ -510,52 +552,51 @@ elem_names - list of element names returns a pointer to a PlyFile, used to refer to this file, or NULL if error ******************************************************************************/ -bool PLY::read(LPCSTR filename) +bool PLY::read(LPCSTR _filename) { - this->filename = filename; - istream = new File(filename, File::READ, File::OPEN); - if (!((File*)istream)->isOpen()) + filename = _filename; + File* const pf(new File(_filename, File::READ, File::OPEN)); + if (!pf->isOpen()) return false; - return read(new BufferedInputStream(istream, FILE_READ_MINBUF_SIZE)); + return read(new BufferedInputStream(pf, 64*1024)); } -bool PLY::read(ISTREAM* f) +bool PLY::read(ISTREAM* fp) { - /* create record for this object */ + // create record for this object ASSERT(elems.empty()); - this->istream = f; - this->other_elems = NULL; - this->rule_list = NULL; + other_elems = NULL; + rule_list = NULL; + istream = fp; - /* read and parse the file's header */ + // read and parse the file's header int nwords; char *orig_line; STRISTREAM sfp(istream); char **words = get_words(sfp, &nwords, &orig_line); if (words == NULL) return false; - if (!equal_strings (words[0], "ply")) { + if (!equal_strings(words[0], "ply")) { free(words); return false; } free(words); - /* parse words */ + // parse words while ((words = get_words(sfp, &nwords, &orig_line)) != NULL) { if (equal_strings(words[0], "format")) { if (nwords != 3) return false; if (equal_strings(words[1], "ascii")) - this->file_type = ASCII; + file_type = ASCII; else if (equal_strings(words[1], "binary_big_endian")) - this->file_type = BINARY_BE; + file_type = BINARY_BE; else if (equal_strings(words[1], "binary_little_endian")) - this->file_type = BINARY_LE; + file_type = BINARY_LE; else return false; - this->version = (float)atof(words[2]); - } - else if (equal_strings(words[0], "element")) + version = (float)atof(words[2]); + } else if (equal_strings(words[0], "element")) add_element((const char**)words, nwords); else if (equal_strings(words[0], "property")) add_property((const char**)words, nwords); @@ -571,16 +612,15 @@ bool PLY::read(ISTREAM* f) } sfp.emptyBuffer(); - /* create tags for each property of each element, to be used */ - /* later to say whether or not to store each property for the user */ - for (size_t i = 0; i < this->elems.size(); ++i) { - PlyElement *elem = this->elems[i]; + // create tags for each property of each element, to be used + // later to say whether or not to store each property for the user + for (size_t i = 0; i < elems.size(); ++i) { + PlyElement *elem = elems[i]; elem->store_prop.resize(elem->props.size()); for (size_t j = 0; j < elem->props.size(); ++j) elem->store_prop[j] = DONT_STORE_PROP; - elem->other_offset = NO_OTHER_PROPS; /* no "other" props by default */ + elem->other_offset = NO_OTHER_PROPS; // no "other" props by default } - return true; } @@ -596,14 +636,14 @@ props - the list of properties returned returns number of elements of this type in the file ******************************************************************************/ -int PLY::get_element_description(const char *elem_name, std::vector& prop_list) const +int PLY::get_element_description(const char* elem_name, std::vector& prop_list) const { - /* find information about the element */ + // find information about the element PlyElement *elem = find_element(elem_name); if (elem == NULL) return 0; - /* make a copy of the element's property list */ + // make a copy of the element's property list prop_list.resize(elem->props.size()); for (size_t i = 0; i < elem->props.size(); ++i) { PlyProperty *prop = new PlyProperty; @@ -626,32 +666,32 @@ prop_list - list of properties ******************************************************************************/ void PLY::get_element_setup( - const char *elem_name, + const char* elem_name, int nprops, - PlyProperty *prop_list + PlyProperty* prop_list ) { - /* find information about the element */ + // find information about the element PlyElement *elem = find_element(elem_name); - this->which_elem = elem; + which_elem = elem; - /* deposit the property information into the element's description */ + // deposit the property information into the element's description for (int i = 0; i < nprops; ++i) { - /* look for actual property */ + // look for actual property int index = find_property(elem, prop_list[i].name.c_str()); if (index == -1) { DEBUG("warning: Can't find property '%s' in element '%s'", prop_list[i].name.c_str(), elem_name); continue; } - /* store its description */ + // store its description PlyProperty *prop = elem->props[index]; prop->internal_type = prop_list[i].internal_type; prop->offset = prop_list[i].offset; prop->count_internal = prop_list[i].count_internal; prop->count_offset = prop_list[i].count_offset; - /* specify that the user wants this property */ + // specify that the user wants this property elem->store_prop[index] = STORE_PROP; } } @@ -668,19 +708,16 @@ elem_name - which element we're talking about prop - property to add to those that will be returned ******************************************************************************/ -void PLY::get_property( - const char *elem_name, - PlyProperty *prop - ) +void PLY::get_property(const char* elem_name, PlyProperty* prop) { - /* find information about the element */ + // find information about the element PlyElement *elem = find_element(elem_name); - this->which_elem = elem; + which_elem = elem; - /* deposit the property information into the element's description */ + // deposit the property information into the element's description int index = find_property(elem, prop->name.c_str()); if (index == -1) { - DEBUG("warning: Can't find property '%s' in element '%s'", prop->name.c_str(), elem_name); + DEBUG("warning: Can't find property '%s' in element '%s'", prop->name.c_str(), elem_name); return; } PlyProperty *prop_ptr = elem->props[index]; @@ -689,7 +726,7 @@ void PLY::get_property( prop_ptr->count_internal = prop->count_internal; prop_ptr->count_offset = prop->count_offset; - /* specify that the user wants this property */ + // specify that the user wants this property elem->store_prop[index] = STORE_PROP; } @@ -705,7 +742,7 @@ elem_ptr - pointer to location where the element information should be put void PLY::get_element(void* elem_ptr) { - if (this->file_type == ASCII) + if (file_type == ASCII) ascii_get_element((uint8_t*)elem_ptr); else binary_get_element((uint8_t*)elem_ptr); @@ -723,7 +760,7 @@ returns the list of comments std::vector& PLY::get_comments() { - return (this->comments); + return comments; } @@ -739,7 +776,7 @@ returns the list of object info lines std::vector& PLY::get_obj_info() { - return (this->obj_info); + return obj_info; } @@ -753,53 +790,53 @@ information. elem - element for which we want to save away other properties ******************************************************************************/ -void PLY::setup_other_props(PlyElement *elem) +void PLY::setup_other_props(PlyElement* elem) { int size = 0; - /* Examine each property in decreasing order of size. */ - /* We do this so that all data types will be aligned by */ - /* word, half-word, or whatever within the structure. */ + // Examine each property in decreasing order of size. + // We do this so that all data types will be aligned by + // word, half-word, or whatever within the structure. for (int type_size = 8; type_size > 0; type_size /= 2) { - /* add up the space taken by each property, and save this information */ - /* away in the property descriptor */ + // add up the space taken by each property, and save this information + // away in the property descriptor for (size_t i = 0; i < elem->props.size(); ++i) { - /* don't bother with properties we've been asked to store explicitly */ + // don't bother with properties we've been asked to store explicitly if (elem->store_prop[i]) continue; PlyProperty *prop = elem->props[i]; - /* internal types will be same as external */ + // internal types will be same as external prop->internal_type = prop->external_type; prop->count_internal = prop->count_external; - /* list case */ + // list case if (prop->is_list == LIST) { - /* pointer to list */ - if (type_size == sizeof (void *)) { + // pointer to list + if (type_size == sizeof(void *)) { prop->offset = size; - size += sizeof (void *); /* always use size of a pointer here */ + size += sizeof(void *); // always use size of a pointer here } - /* count of number of list elements */ + // count of number of list elements if (type_size == ply_type_size[prop->count_external]) { prop->count_offset = size; size += ply_type_size[prop->count_external]; } } - /* string */ + // string else if (prop->is_list == STRING) { - /* pointer to string */ - if (type_size == sizeof (char *)) { + // pointer to string + if (type_size == sizeof(char*)) { prop->offset = size; - size += sizeof (char *); + size += sizeof(char*); } } - /* scalar */ + // scalar else if (type_size == ply_type_size[prop->external_type]) { prop->offset = size; size += ply_type_size[prop->external_type]; @@ -808,7 +845,7 @@ void PLY::setup_other_props(PlyElement *elem) } - /* save the size for the other_props structure */ + // save the size for the other_props structure elem->other_size = size; } @@ -825,18 +862,18 @@ offset - offset to where other_props will be stored inside user's structure returns pointer to structure containing description of other_props ******************************************************************************/ -PLY::PlyOtherProp* PLY::get_other_properties(PlyElement *elem, int offset) +PLY::PlyOtherProp* PLY::get_other_properties(PlyElement* elem, int offset) { - /* remember that this is the "current" element */ - this->which_elem = elem; + // remember that this is the "current" element + which_elem = elem; - /* save the offset to where to store the other_props */ + // save the offset to where to store the other_props elem->other_offset = offset; - /* place the appropriate pointers, etc. in the element's property list */ + // place the appropriate pointers, etc. in the element's property list setup_other_props(elem); - /* create structure for describing other_props */ + // create structure for describing other_props PlyOtherProp *other = new PlyOtherProp; other->name = elem->name; #if 0 @@ -844,13 +881,13 @@ PLY::PlyOtherProp* PLY::get_other_properties(PlyElement *elem, int offset) other->size = 0; other->props = NULL; other->nprops = 0; - return (other); + return other; } #endif other->size = elem->other_size; other->props.reserve(elem->props.size()); - /* save descriptions of each "other" property */ + // save descriptions of each "other" property for (size_t i = 0; i < elem->props.size(); ++i) { if (elem->store_prop[i]) continue; @@ -859,11 +896,11 @@ PLY::PlyOtherProp* PLY::get_other_properties(PlyElement *elem, int offset) other->props.push_back(prop); } - /* set other_offset pointer appropriately if there are NO other properties */ + // set other_offset pointer appropriately if there are NO other properties if (other->props.empty()) elem->other_offset = NO_OTHER_PROPS; - /* return structure */ + // return structure return other; } @@ -881,20 +918,17 @@ offset - offset to where other_props will be stored inside user's structure returns pointer to structure containing description of other_props ******************************************************************************/ -PLY::PlyOtherProp* PLY::get_other_properties( - const char *elem_name, - int offset - ) +PLY::PlyOtherProp* PLY::get_other_properties(const char* elem_name, int offset) { - /* find information about the element */ + // find information about the element PlyElement *elem = find_element(elem_name); if (elem == NULL) { DEBUG("warning: get_other_properties: Can't find element '%s'", elem_name); - return (NULL); + return NULL; } PlyOtherProp *other = get_other_properties(elem, offset); - return (other); + return other; } @@ -920,36 +954,36 @@ returns pointer to ALL the "other" element data for this PLY file PLY::PlyOtherElems* PLY::get_other_element() { - PlyElement *elem = this->which_elem; + PlyElement *elem = which_elem; - /* create room for the new "other" element, initializing the */ - /* other data structure if necessary */ + // create room for the new "other" element, initializing the + // other data structure if necessary OtherElem other; - /* count of element instances in file */ + // count of element instances in file other.elem_count = elem->num; - /* save name of element */ + // save name of element other.elem_name = elem->name; - /* create a list to hold all the current elements */ + // create a list to hold all the current elements other.other_data = new OtherData*[other.elem_count]; - /* set up for getting elements */ + // set up for getting elements other.other_props = get_other_properties(elem->name.c_str(), offsetof(OtherData,other_props)); - /* grab all these elements */ + // grab all these elements for (int i = 0; i < other.elem_count; ++i) { - /* grab and element from the file */ + // grab and element from the file other.other_data[i] = new OtherData; get_element((uint8_t*)other.other_data[i]); } - /* return pointer to the other elements data */ + // return pointer to the other elements data if (other_elems == NULL) other_elems = new PlyOtherElems; other_elems->other_list.push_back(other); - return (other_elems); + return other_elems; } @@ -961,16 +995,16 @@ Write out the "other" elements specified for this PLY file. void PLY::put_other_elements() { - /* make sure we have other elements to write */ - if (this->other_elems == NULL) + // make sure we have other elements to write + if (other_elems == NULL) return; - /* write out the data for each "other" element */ - for (size_t i = 0; i < this->other_elems->other_list.size(); ++i) { - OtherElem *other = &(this->other_elems->other_list[i]); + // write out the data for each "other" element + for (size_t i = 0; i < other_elems->other_list.size(); ++i) { + OtherElem *other = &(other_elems->other_list[i]); put_element_setup(other->elem_name.c_str()); - /* write out each instance of the current element */ + // write out each instance of the current element for (int j = 0; j < other->elem_count; ++j) put_element(other->other_data[j]); } @@ -983,22 +1017,6 @@ void PLY::put_other_elements() /*******************/ -/****************************************************************************** -Flush a PLY file. -******************************************************************************/ - -void PLY::flush() -{ - if (ostream != NULL) { - if (mfp != NULL) { - ostream->write(mfp->getBuffer(), mfp->getSize()); - mfp->setSize(0); - } - ostream->flush(); - } -} - - /****************************************************************************** Use old PLY type names during writing for backward compatibility. ******************************************************************************/ @@ -1019,10 +1037,10 @@ version - version of the file file_type - PLY_ASCII, PLY_BINARY_BE, or PLY_BINARY_LE ******************************************************************************/ -void PLY::get_info(float *version, int *file_type) +void PLY::get_info(float* _version, int* _file_type) { - *version = this->version; - *file_type = this->file_type; + *_version = version; + *_file_type = file_type; } @@ -1036,7 +1054,7 @@ element - name of element we're looking for returns the element, or NULL if not found ******************************************************************************/ -PLY::PlyElement* PLY::find_element(const char *element) const +PLY::PlyElement* PLY::find_element(const char* element) const { for (size_t i=0; iname.c_str())) @@ -1056,7 +1074,7 @@ prop_name - name of property to find returns the index to position in list ******************************************************************************/ -int PLY::find_property(PlyElement *elem, const char *prop_name) const +int PLY::find_property(PlyElement* elem, const char* prop_name) const { for (size_t i=0; iprops.size(); ++i) if (equal_strings(prop_name, elem->props[i]->name.c_str())) @@ -1081,19 +1099,19 @@ void PLY::ascii_get_element(uint8_t* elem_ptr) char *other_data(NULL); int other_flag(0); - /* the kind of element we're reading currently */ - PlyElement *elem = this->which_elem; + // the kind of element we're reading currently + PlyElement *elem = which_elem; - /* do we need to setup for other_props? */ + // do we need to setup for other_props? if (elem->other_offset != NO_OTHER_PROPS) { other_flag = 1; - /* make room for other_props */ + // make room for other_props other_data = new char[elem->other_size]; - /* store pointer in user's structure to the other_props */ + // store pointer in user's structure to the other_props *((char**)(elem_ptr + elem->other_offset)) = other_data; } - /* read in the element */ + // read in the element int nwords; char **words; { @@ -1109,28 +1127,27 @@ void PLY::ascii_get_element(uint8_t* elem_ptr) PlyProperty *prop = elem->props[j]; const int store_it(elem->store_prop[j] | other_flag); - /* store either in the user's structure or in other_props */ + // store either in the user's structure or in other_props if (elem->store_prop[j]) elem_data = (char*)elem_ptr; else elem_data = other_data; - if (prop->is_list == LIST) { /* a list */ - /* get and store the number of items in the list */ + if (prop->is_list == LIST) { // a list + // get and store the number of items in the list get_ascii_item(words[which_word++], prop->count_external, val); if (store_it) { item = elem_data + prop->count_offset; store_item(item, prop->count_internal, val, prop->count_external); } - /* allocate space for an array of items and store a ptr to the array */ + // allocate space for an array of items and store a ptr to the array const int list_count(ValueType2Type(val, prop->count_external)); - char **store_array = (char**)(elem_data + prop->offset); + char** store_array = (char**)(elem_data + prop->offset); if (list_count == 0) { if (store_it) *store_array = NULL; - } - else { + } else { const int item_size(ply_type_size[prop->internal_type]); if (store_it) { @@ -1139,7 +1156,7 @@ void PLY::ascii_get_element(uint8_t* elem_ptr) *store_array = item_ptr; } - /* read items and store them into the array */ + // read items and store them into the array for (int k = 0; k < list_count; k++) { get_ascii_item(words[which_word++], prop->external_type, val); if (store_it) { @@ -1148,18 +1165,14 @@ void PLY::ascii_get_element(uint8_t* elem_ptr) } } } - - } - else if (prop->is_list == STRING) { /* a string */ + } else if (prop->is_list == STRING) { // a string if (store_it) { item = elem_data + prop->offset; - *((char **)item) = strdup(words[which_word++]); - } - else { + *((char**)item) = strdup(words[which_word++]); + } else { which_word++; } - } - else { /* a scalar */ + } else { // a scalar get_ascii_item(words[which_word++], prop->external_type, val); if (store_it) { item = elem_data + prop->offset; @@ -1188,76 +1201,71 @@ void PLY::binary_get_element(uint8_t* elem_ptr) char *other_data(NULL); int other_flag(0); - /* the kind of element we're reading currently */ - PlyElement *elem = this->which_elem; + // the kind of element we're reading currently + PlyElement* elem = which_elem; - /* do we need to setup for other_props? */ + // do we need to setup for other_props? if (elem->other_offset != NO_OTHER_PROPS) { other_flag = 1; - /* make room for other_props */ + // make room for other_props other_data = new char[elem->other_size]; - /* store pointer in user's structure to the other_props */ - *((char **)(elem_ptr + elem->other_offset)) = other_data; + // store pointer in user's structure to the other_props + *((char**)(elem_ptr + elem->other_offset)) = other_data; } - /* read in a number of elements */ + // read in a number of elements for (size_t j = 0; j < elem->props.size(); ++j) { PlyProperty *prop = elem->props[j]; const int store_it(elem->store_prop[j] | other_flag); - /* store either in the user's structure or in other_props */ + // store either in the user's structure or in other_props if (elem->store_prop[j]) elem_data = (char*)elem_ptr; else elem_data = other_data; - if (prop->is_list == LIST) { /* list */ - - /* get and store the number of items in the list */ - get_binary_item(istream, prop->count_external, val); + if (prop->is_list == LIST) { // list + // get and store the number of items in the list + get_binary_item(prop->count_external, val); if (store_it) { item = elem_data + prop->count_offset; store_item(item, prop->count_internal, val, prop->count_external); } - /* allocate space for an array of items and store a ptr to the array */ + // allocate space for an array of items and store a ptr to the array const int list_count(ValueType2Type(val, prop->count_external)); const int item_size(ply_type_size[prop->internal_type]); - char **store_array = (char**)(elem_data + prop->offset); + char** store_array = (char**)(elem_data + prop->offset); if (list_count == 0) { if (store_it) *store_array = NULL; - } - else { + } else { if (store_it) { item_ptr = new char[item_size * list_count]; item = item_ptr; *store_array = item_ptr; } - /* read items and store them into the array */ + // read items and store them into the array for (int k = 0; k < list_count; k++) { - get_binary_item(istream, prop->external_type, val); + get_binary_item(prop->external_type, val); if (store_it) { store_item(item, prop->internal_type, val, prop->external_type); item += item_size; } } } - - } - else if (prop->is_list == STRING) { /* string */ + } else if (prop->is_list == STRING) { // string int len; istream->read(&len, sizeof(int)); char *str = new char[len]; istream->read(str, len); if (store_it) { item = elem_data + prop->offset; - *((char **)item) = str; + *((char**)item) = str; } - } - else { /* scalar */ - get_binary_item(istream, prop->external_type, val); + } else { // scalar + get_binary_item(prop->external_type, val); if (store_it) { item = elem_data + prop->offset; store_item(item, prop->internal_type, val, prop->external_type); @@ -1271,18 +1279,17 @@ void PLY::binary_get_element(uint8_t* elem_ptr) Write to a file the word that represents a PLY data type. Entry: -fp - file pointer code - code for type ******************************************************************************/ -void PLY::write_scalar_type(OSTREAM* fp, int code) +void PLY::write_scalar_type(int code) { - /* make sure this is a valid code */ + // make sure this is a valid code if (code <= StartType || code >= EndType) abort_ply("error: write_scalar_type: bad data code = %d", code); - /* write the code to a file */ - fp->print("%s", write_type_names[code]); + // write the code to a file + ostream->print("%s", write_type_names[code]); } @@ -1293,7 +1300,7 @@ IMPORTANT: The calling routine should call "free" on the returned pointer once finished with it. Entry: -sfp - file to read from +sfp - string file to read from Exit: nwords - number of words returned @@ -1301,7 +1308,7 @@ orig_line - the original line of characters returns a list of words from the line, or NULL if end-of-file ******************************************************************************/ -char** PLY::get_words(STRISTREAM& sfp, int *nwords, char **orig_line) +char** PLY::get_words(STRISTREAM& sfp, int* nwords, char** orig_line) { const int BIG_STRING = 4096; char str[BIG_STRING]; @@ -1311,20 +1318,20 @@ char** PLY::get_words(STRISTREAM& sfp, int *nwords, char **orig_line) int num_words = 0; char *ptr, *ptr2; - char **words = (char **)malloc(sizeof (char *) * max_words); + char** words = (char**)malloc(sizeof(char*) * max_words); - /* read in a line */ + // read in a line size_t len(sfp.readLine(str, BIG_STRING-2)); if (len == 0 || len == STREAM_ERROR) { *nwords = 0; *orig_line = NULL; free(words); - return (NULL); + return NULL; } - /* convert line-feed and tabs into spaces */ - /* (this guarantees that there will be a space before the */ - /* null character at the end of the string) */ + // convert line-feed and tabs into spaces + // (this guarantees that there will be a space before the + // null character at the end of the string) if (str[len-1] == '\r') --len; str[len] = '\n'; @@ -1345,59 +1352,56 @@ char** PLY::get_words(STRISTREAM& sfp, int *nwords, char **orig_line) } EXIT_LOOP: - /* find the words in the line */ + // find the words in the line ptr = str; while (*ptr != '\0') { - /* jump over leading spaces */ + // jump over leading spaces while (*ptr == ' ') ptr++; - /* break if we reach the end */ + // break if we reach the end if (*ptr == '\0') break; - /* allocate more room for words if necessary */ + // allocate more room for words if necessary if (num_words >= max_words) { max_words += 10; - words = (char **) realloc (words, sizeof (char *) * max_words); + words = (char**)realloc(words, sizeof(char*) * max_words); } - if (*ptr == '\"') { /* a quote indicates that we have a string */ - - /* skip over leading quote */ + if (*ptr == '\"') { // a quote indicates that we have a string + // skip over leading quote ptr++; - /* save pointer to beginning of word */ + // save pointer to beginning of word words[num_words++] = ptr; - /* find trailing quote or end of line */ + // find trailing quote or end of line while (*ptr != '\"' && *ptr != '\0') ptr++; - /* replace quote with a null character to mark the end of the word */ - /* if we are not already at the end of the line */ + // replace quote with a null character to mark the end of the word + // if we are not already at the end of the line if (*ptr != '\0') *ptr++ = '\0'; - } - else { /* non-string */ - - /* save pointer to beginning of word */ + } else { // non-string + // save pointer to beginning of word words[num_words++] = ptr; - /* jump over non-spaces */ + // jump over non-spaces while (*ptr != ' ') ptr++; - /* place a null character here to mark the end of the word */ + // place a null character here to mark the end of the word *ptr++ = '\0'; } } - /* return the list of words */ + // return the list of words *nwords = num_words; *orig_line = str_copy; - return (words); + return words; } @@ -1419,35 +1423,35 @@ void PLY::write_binary_item( switch (to_type) { case Int8: { const int8_t v(ValueType2Type(val, from_type)); - f->write(&v, 1); + ostream->write(&v, 1); break; } case Int16: { const int16_t v(ValueType2Type(val, from_type)); - f->write(&v, 2); + ostream->write(&v, 2); break; } case Int32: { const int32_t v(ValueType2Type(val, from_type)); - f->write(&v, 4); + ostream->write(&v, 4); break; } case Uint8: { const uint8_t v(ValueType2Type(val, from_type)); - f->write(&v, 1); + ostream->write(&v, 1); break; } case Uint16: { const uint16_t v(ValueType2Type(val, from_type)); - f->write(&v, 2); + ostream->write(&v, 2); break; } case Uint32: { const uint32_t v(ValueType2Type(val, from_type)); - f->write(&v, 4); + ostream->write(&v, 4); break; } case Float32: { const float v(ValueType2Type(val, from_type)); - f->write(&v, 4); + ostream->write(&v, 4); break; } case Float64: { const double v(ValueType2Type(val, from_type)); - f->write(&v, 8); + ostream->write(&v, 8); break; } default: abort_ply("error: write_binary_item: bad type = %d", to_type); @@ -1474,16 +1478,16 @@ void PLY::write_ascii_item( case Int8: case Int16: case Int32: - f->print("%d ", ValueType2Type(val, from_type)); + ostream->print("%d ", ValueType2Type(val, from_type)); break; case Uint8: case Uint16: case Uint32: - f->print("%u ", ValueType2Type(val, from_type)); + ostream->print("%u ", ValueType2Type(val, from_type)); break; case Float32: case Float64: - f->print("%g ", ValueType2Type(val, from_type)); + ostream->print("%g ", ValueType2Type(val, from_type)); break; default: abort_ply("error: write_ascii_item: bad type = %d", to_type); @@ -1504,35 +1508,35 @@ val - extracted value ******************************************************************************/ void PLY::get_stored_item( - void *ptr, + const void* ptr, int type, ValueType& val ) { switch (type) { case Int8: - val.i8 = *((int8_t*)ptr); + val.i8 = *((const int8_t*)ptr); break; case Uint8: - val.u8 = *((uint8_t*)ptr); + val.u8 = *((const uint8_t*)ptr); break; case Int16: - val.i16 = *((int16_t*)ptr); + val.i16 = *((const int16_t*)ptr); break; case Uint16: - val.u16 = *((uint16_t*)ptr); + val.u16 = *((const uint16_t*)ptr); break; case Int32: - val.i32 = *((int32_t*)ptr); + val.i32 = *((const int32_t*)ptr); break; case Uint32: - val.u32 = *((uint32_t*)ptr); + val.u32 = *((const uint32_t*)ptr); break; case Float32: - val.f = *((float*)ptr); + val.f = *((const float*)ptr); break; case Float64: - val.d = *((double*)ptr); + val.d = *((const double*)ptr); break; default: abort_ply("error: get_stored_item: bad type = %d", type); @@ -1545,43 +1549,38 @@ Get the value of an item from a binary file, and place the result into an integer, an unsigned integer and a double. Entry: -fp - file to get item from type - data type supposedly in the word Exit: val - store value ******************************************************************************/ -void PLY::get_binary_item( - ISTREAM* fp, - int type, - ValueType& val - ) +void PLY::get_binary_item(int type, ValueType& val) { switch (type) { case Int8: - fp->read(&val.i8, 1); + istream->read(&val.i8, 1); break; case Uint8: - fp->read(&val.u8, 1); + istream->read(&val.u8, 1); break; case Int16: - fp->read(&val.i16, 2); + istream->read(&val.i16, 2); break; case Uint16: - fp->read(&val.u16, 2); + istream->read(&val.u16, 2); break; case Int32: - fp->read(&val.i32, 4); + istream->read(&val.i32, 4); break; case Uint32: - fp->read(&val.u32, 4); + istream->read(&val.u32, 4); break; case Float32: - fp->read(&val.f, 4); + istream->read(&val.f, 4); break; case Float64: - fp->read(&val.d, 8); + istream->read(&val.d, 8); break; default: abort_ply("error: get_binary_item: bad type = %d", type); @@ -1601,11 +1600,7 @@ type - data type supposedly in the word val - store value ******************************************************************************/ -void PLY::get_ascii_item( - const char* word, - int type, - ValueType& val - ) +void PLY::get_ascii_item(const char* word, int type, ValueType& val) { switch (type) { case Int8: @@ -1624,7 +1619,7 @@ void PLY::get_ascii_item( val.i32 = atoi(word); break; case Uint32: - val.u32 = strtoul(word, (char **)NULL, 10); + val.u32 = strtoul(word, (char**)NULL, 10); break; case Float32: val.f = (float)atof(word); @@ -1696,15 +1691,15 @@ words - list of words describing the element nwords - number of words in the list ******************************************************************************/ -void PLY::add_element(const char **words, int /*nwords*/) +void PLY::add_element(const char** words, int /*nwords*/) { - /* create the new element */ + // create the new element PlyElement *elem = new PlyElement; elem->name = words[1]; elem->num = atoi(words[2]); - /* add the new element to the object's list */ - this->elems.push_back(elem); + // add the new element to the object's list + elems.push_back(elem); } @@ -1718,20 +1713,20 @@ name - name of property type returns integer code for property, or 0 if not found ******************************************************************************/ -int PLY::get_prop_type(const char *type_name) +int PLY::get_prop_type(const char* type_name) { - /* try to match the type name */ + // try to match the type name for (int i = StartType + 1; i < EndType; ++i) if (equal_strings (type_name, type_names[i])) - return (i); + return i; - /* see if we can match an old type name */ + // see if we can match an old type name for (int i = StartType + 1; i < EndType; ++i) if (equal_strings (type_name, old_type_names[i])) - return (i); + return i; - /* if we get here, we didn't find the type */ - return (0); + // if we get here, we didn't find the type + return 0; } @@ -1743,35 +1738,33 @@ words - list of words describing the property nwords - number of words in the list ******************************************************************************/ -void PLY::add_property(const char **words, int /*nwords*/) +void PLY::add_property(const char** words, int /*nwords*/) { - /* create the new property */ + // create the new property PlyProperty *prop = new PlyProperty; - if (equal_strings(words[1], "list")) { /* list */ + if (equal_strings(words[1], "list")) { // list prop->count_external = get_prop_type (words[2]); prop->external_type = get_prop_type (words[3]); prop->name = words[4]; prop->is_list = LIST; - } - else if (equal_strings(words[1], "string")) { /* string */ + } else if (equal_strings(words[1], "string")) { // string prop->count_external = Int8; prop->external_type = Int8; prop->name = words[2]; prop->is_list = STRING; - } - else { /* scalar */ + } else { // scalar prop->external_type = get_prop_type (words[1]); prop->name = words[2]; prop->is_list = SCALAR; } - /* internal types are the same as external by default */ + // internal types are the same as external by default prop->internal_type = prop->external_type; prop->count_internal = prop->count_external; - /* add this property to the list of properties of the current element */ - PlyElement *elem = this->elems.back(); + // add this property to the list of properties of the current element + PlyElement *elem = elems.back(); elem->props.push_back(prop); } @@ -1783,9 +1776,9 @@ Add a comment to a PLY file descriptor. line - line containing comment ******************************************************************************/ -void PLY::add_comment(const char *line) +void PLY::add_comment(const char* line) { - /* skip over "comment" and leading spaces and tabs */ + // skip over "comment" and leading spaces and tabs int i = 7; while (line[i] == ' ' || line[i] == '\t') i++; @@ -1800,9 +1793,9 @@ Add a some object information to a PLY file descriptor. line - line containing text info ******************************************************************************/ -void PLY::add_obj_info(const char *line) +void PLY::add_obj_info(const char* line) { - /* skip over "obj_info" and leading spaces and tabs */ + // skip over "obj_info" and leading spaces and tabs int i = 8; while (line[i] == ' ' || line[i] == '\t') i++; @@ -1841,11 +1834,11 @@ returns the number of elements int PLY::get_element_list(std::vector& elem_names) const { - /* create the list of element names */ + // create the list of element names elem_names.resize(elems.size()); for (size_t i = 0; i < elems.size(); ++i) elem_names[i] = elems[i]->name; - /* return the number of elements and the list of element names */ + // return the number of elements and the list of element names return (int)elems.size(); } @@ -1857,9 +1850,9 @@ Append a comment to a PLY file. comment - the comment to append ******************************************************************************/ -void PLY::append_comment(const char *comment) +void PLY::append_comment(const char* comment) { - /* add comment to list */ + // add comment to list comments.push_back(comment); } @@ -1886,10 +1879,10 @@ Append object information (arbitrary text) to a PLY file. obj_info - the object info to append ******************************************************************************/ -void PLY::append_obj_info(const char *obj_info) +void PLY::append_obj_info(const char* _obj_info) { - /* add info to list */ - this->obj_info.push_back(obj_info); + // add info to list + obj_info.push_back(_obj_info); } @@ -1922,16 +1915,16 @@ returns pointer to the name of this next element LPCSTR PLY::setup_element_read(int index, int* elem_count) { if ((size_t)index > elems.size()) { - DEBUG("warning: No element with index %d", index); - return (0); + DEBUG("warning: No element with index %d", index); + return 0; } PlyElement* elem = elems[index]; - /* set this to be the current element */ + // set this to be the current element which_elem = elem; - /* return the number of such elements in the file and the element's name */ + // return the number of such elements in the file and the element's name *elem_count = elem->num; return elem->name.c_str(); } @@ -1948,12 +1941,12 @@ prop - property to add to those that will be returned void PLY::setup_property(const PlyProperty& prop) { - PlyElement *elem = this->which_elem; + PlyElement *elem = which_elem; - /* deposit the property information into the element's description */ + // deposit the property information into the element's description int index = find_property(elem, prop.name.c_str()); if (index == -1) { - DEBUG("warning: Can't find property '%s' in element '%s'", prop.name.c_str(), elem->name.c_str()); + DEBUG("warning: Can't find property '%s' in element '%s'", prop.name.c_str(), elem->name.c_str()); return; } PlyProperty *prop_ptr = elem->props[index]; @@ -1962,7 +1955,7 @@ void PLY::setup_property(const PlyProperty& prop) prop_ptr->count_internal = prop.count_internal; prop_ptr->count_offset = prop.count_offset; - /* specify that the user wants this property */ + // specify that the user wants this property elem->store_prop[index] = STORE_PROP; } @@ -1980,7 +1973,7 @@ returns pointer to structure containing description of other_props PLY::PlyOtherProp* PLY::get_other_properties(int offset) { - return get_other_properties(this->which_elem, offset); + return get_other_properties(which_elem, offset); } @@ -1993,20 +1986,17 @@ elem_name - name of element that information is being described nelems - number of elements of this type to be written ******************************************************************************/ -void PLY::describe_element( - char *elem_name, - int nelems - ) +void PLY::describe_element(const char* elem_name, int nelems) { - /* look for appropriate element */ + // look for appropriate element PlyElement *elem = find_element(elem_name); if (elem == NULL) abort_ply("error: describe_element: can't find element '%s'",elem_name); elem->num = nelems; - /* now this element is the current element */ - this->which_elem = elem; + // now this element is the current element + which_elem = elem; } @@ -2019,9 +2009,9 @@ prop - the new property void PLY::describe_property(const PlyProperty& prop) { - PlyElement *elem = this->which_elem; + PlyElement *elem = which_elem; - /* copy the new property */ + // copy the new property PlyProperty *elem_prop = new PlyProperty; copy_property(*elem_prop, prop); elem->props.push_back(elem_prop); @@ -2039,14 +2029,14 @@ void PLY::describe_other_properties( int offset ) { - /* look for appropriate element */ + // look for appropriate element PlyElement *elem = find_element(other->name.c_str()); if (elem == NULL) { DEBUG("warning: describe_other_properties: can't find element '%s'", other->name.c_str()); return; } - /* copy the other properties */ + // copy the other properties for (size_t i = 0; i < other->props.size(); ++i) { PlyProperty *prop = new PlyProperty; copy_property(*prop, *other->props[i]); @@ -2054,7 +2044,7 @@ void PLY::describe_other_properties( elem->store_prop.push_back(OTHER_PROP); } - /* save other info about other properties */ + // save other info about other properties elem->other_size = other->size; elem->other_offset = offset; } @@ -2068,16 +2058,16 @@ PLY file. These other elements were presumably read from another PLY file. other_elems - info about other elements that we want to store ******************************************************************************/ -void PLY::describe_other_elements(PlyOtherElems *other_elems) +void PLY::describe_other_elements(PlyOtherElems* _other_elems) { - /* ignore this call if there is no other element */ - if (other_elems == NULL) + // ignore this call if there is no other element + if (_other_elems == NULL) return; - /* save pointer to this information */ - this->other_elems = other_elems; + // save pointer to this information + other_elems = _other_elems; - /* describe the other properties of this element */ + // describe the other properties of this element for (size_t i = 0; i < other_elems->other_list.size(); ++i) { OtherElem *other = &(other_elems->other_list[i]); element_count(other->elem_name.c_str(), other->elem_count); @@ -2098,7 +2088,7 @@ elem_name - name of the element that we're making the rules for returns pointer to the default rules ******************************************************************************/ -PLY::PlyPropRules* PLY::init_rule(const char *elem_name) +PLY::PlyPropRules* PLY::init_rule(const char* elem_name) { PlyElement *elem = find_element(elem_name); if (elem == NULL) @@ -2109,17 +2099,17 @@ PLY::PlyPropRules* PLY::init_rule(const char *elem_name) rules->max_props = 0; rules->rule_list = NULL; - /* see if there are other rules we should use */ + // see if there are other rules we should use if (elem->props.empty()) - return (rules); + return rules; - /* default is to use averaging rule */ + // default is to use averaging rule rules->rule_list = new int[elem->props.size()]; for (size_t i = 0; i < elem->props.size(); ++i) rules->rule_list[i] = AVERAGE_RULE; - /* try to match the element, property and rule name */ - for (PlyRuleList *list = this->rule_list; list != NULL; list = list->next) { + // try to match the element, property and rule name + for (PlyRuleList *list = rule_list; list != NULL; list = list->next) { if (!equal_strings(list->element, elem->name.c_str())) continue; @@ -2130,7 +2120,7 @@ PLY::PlyPropRules* PLY::init_rule(const char *elem_name) found_prop = 1; - /* look for matching rule name */ + // look for matching rule name for (int j = 0; rule_name_list[j].code != -1; ++j) if (equal_strings(list->name, rule_name_list[j].name.c_str())) { rules->rule_list[i] = rule_name_list[j].code; @@ -2144,7 +2134,7 @@ PLY::PlyPropRules* PLY::init_rule(const char *elem_name) } } - return (rules); + return rules; } @@ -2157,18 +2147,18 @@ prop_name - name of the property whose rule we're modifying rule_type - type of rule (MAXIMUM_RULE, MINIMUM_RULE, MAJORITY_RULE, etc.) ******************************************************************************/ -void PLY::modify_rule(PlyPropRules *rules, const char *prop_name, int rule_type) +void PLY::modify_rule(PlyPropRules* rules, const char* prop_name, int rule_type) { PlyElement *elem = rules->elem; - /* find the property and modify its rule type */ + // find the property and modify its rule type for (size_t i = 0; i < elem->props.size(); ++i) if (equal_strings(elem->props[i]->name.c_str(), prop_name)) { rules->rule_list[i] = rule_type; return; } - /* we didn't find the property if we get here */ + // we didn't find the property if we get here abort_ply("error: modify_rule: Can't find property '%s'", prop_name); } @@ -2180,10 +2170,10 @@ Begin to create a set of properties from a set of propagation rules. rules - rules for the element ******************************************************************************/ -void PLY::start_props(PlyPropRules *rules) +void PLY::start_props(PlyPropRules* rules) { - /* save pointer to the rules in the PLY object */ - this->current_rules = rules; + // save pointer to the rules in the PLY object + current_rules = rules; } @@ -2196,11 +2186,11 @@ weight - weights for this set of properties other_props - the properties to use ******************************************************************************/ -void PLY::weight_props(float weight, void *other_props) +void PLY::weight_props(float weight, void* other_props) { - PlyPropRules *rules = this->current_rules; + PlyPropRules *rules = current_rules; - /* allocate space for properties and weights, if necessary */ + // allocate space for properties and weights, if necessary if (rules->max_props == 0) { rules->max_props = 6; } @@ -2210,7 +2200,7 @@ void PLY::weight_props(float weight, void *other_props) rules->props.reserve(rules->max_props); rules->weights.reserve(rules->max_props); - /* remember these new properties and their weights */ + // remember these new properties and their weights rules->props.push_back(other_props); rules->weights.push_back(weight); } @@ -2227,31 +2217,27 @@ returns a pointer to the new properties void* PLY::get_new_props() { - PlyPropRules *rules = this->current_rules; + PlyPropRules *rules = current_rules; PlyElement *elem = rules->elem; PlyProperty *prop; int offset; int type; ValueType val; - int random_pick; - /* return NULL if we've got no "other" properties */ + // return NULL if we've got no "other" properties if (elem->other_size == 0) - return (NULL); + return NULL; - /* create room for combined other properties */ + // create room for combined other properties char *new_data = new char[elem->other_size]; - /* make sure there is enough room to store values we're to combine */ + // make sure there is enough room to store values we're to combine vals.resize(rules->props.size()); - /* in case we need a random choice */ - random_pick = FLOOR2INT(SEACAVE::random() * rules->props.size()); - - /* calculate the combination for each "other" property of the element */ + // calculate the combination for each "other" property of the element for (size_t i = 0; i < elem->props.size(); ++i) { - /* don't bother with properties we've been asked to store explicitly */ + // don't bother with properties we've been asked to store explicitly if (elem->store_prop[i]) continue; @@ -2259,15 +2245,15 @@ void* PLY::get_new_props() offset = prop->offset; type = prop->external_type; - /* collect together all the values we're to combine */ + // collect together all the values we're to combine for (size_t j = 0; j < rules->props.size(); ++j) { - char* data = (char *)rules->props[j]; + char* data = (char*)rules->props[j]; void* ptr = (void *)(data + offset); get_stored_item((void*)ptr, type, val); vals[j] = ValueType2Type(val, type); } - /* calculate the combined value */ + // calculate the combined value switch (rules->rule_list[i]) { case AVERAGE_RULE: { double sum = 0; @@ -2294,7 +2280,7 @@ void* PLY::get_new_props() break; } case RANDOM_RULE: { - val.d = vals[random_pick]; + val.d = vals[FLOOR2INT(SEACAVE::random() * rules->props.size())]; break; } case SAME_RULE: { @@ -2308,11 +2294,11 @@ void* PLY::get_new_props() abort_ply("error: get_new_props: Bad rule = %d", rules->rule_list[i]); } - /* store the combined value */ - store_item((void*)(new_data + offset), type, val, Float64); + // store the combined value + store_item(new_data + offset, type, val, Float64); } - return ((void*)new_data); + return new_data; } @@ -2320,9 +2306,9 @@ void* PLY::get_new_props() Set the list of user-specified property combination rules. ******************************************************************************/ -void PLY::set_prop_rules(PlyRuleList *prop_rules) +void PLY::set_prop_rules(PlyRuleList* prop_rules) { - this->rule_list = prop_rules; + rule_list = prop_rules; } @@ -2339,26 +2325,25 @@ returns pointer to the new rule list ******************************************************************************/ PLY::PlyRuleList* PLY::append_prop_rule( - PlyRuleList *rule_list, - const char *name, - const char *property + PlyRuleList* rule_list, + const char* name, + const char* property ) { char *str2; char *ptr; - /* find . */ + // find . char *str = strdup(property); for (ptr = str; *ptr != '\0' && *ptr != '.'; ptr++) ; - /* split string at . */ + // split string at . if (*ptr == '.') { *ptr = '\0'; str2 = ptr + 1; - } - else { + } else { DEBUG("warning: Can't find property '%s' for rule '%s'", property, name); - return (rule_list); + return rule_list; } PlyRuleList *rule = new PlyRuleList; @@ -2367,18 +2352,18 @@ PLY::PlyRuleList* PLY::append_prop_rule( rule->property = str2; rule->next = NULL; - /* either start rule list or append to it */ + // either start rule list or append to it if (rule_list == NULL) rule_list = rule; - else { /* append new rule to current list */ + else { // append new rule to current list PlyRuleList *rule_ptr = rule_list; while (rule_ptr->next != NULL) rule_ptr = rule_ptr->next; rule_ptr->next = rule; } - /* return pointer to list */ - return (rule_list); + // return pointer to list + return rule_list; } @@ -2392,7 +2377,7 @@ name - name of rule we're trying to match returns 1 if we find a match, 0 if not ******************************************************************************/ -int PLY::matches_rule_name(const char *name) +int PLY::matches_rule_name(const char* name) { for (int i = 0; rule_name_list[i].code != -1; ++i) if (equal_strings(rule_name_list[i].name.c_str(), name)) diff --git a/libs/IO/PLY.h b/libs/IO/PLY.h index d6354edef..44a0f259e 100644 --- a/libs/IO/PLY.h +++ b/libs/IO/PLY.h @@ -1,22 +1,26 @@ -/* - -PLY polygon files parser. -(originally by Greg Turk, heavily modified by cDc@seacave) - -A PLY file contains a single polygonal _object_. - -An object is composed of lists of _elements_. Typical elements are -vertices, faces, edges and materials. - -Each type of element for a given object has one or more _properties_ -associated with the element type. For instance, a vertex element may -have as properties three floating-point values x,y,z and three unsigned -chars for red, green and blue. - -*/ - -#ifndef __PLY_H__ -#define __PLY_H__ +//////////////////////////////////////////////////////////////////// +// PLY.h +// +// Copyright 2023 cDc@seacave +// Distributed under the Boost Software License, Version 1.0 +// (See http://www.boost.org/LICENSE_1_0.txt) +// +// PLY polygon files parser. +// (originally by Greg Turk, heavily modified by cDc@seacave) +// +// A PLY file contains a single polygonal _object_. +// +// An object is composed of lists of _elements_. Typical elements are +// vertices, faces, edges and materials. +// +// Each type of element for a given object has one or more _properties_ +// associated with the element type. For instance, a vertex element may +// have as properties three floating-point values x,y,z and three unsigned +// chars for red, green and blue. + + +#ifndef __SEACAVE_PLY_H__ +#define __SEACAVE_PLY_H__ // I N C L U D E S ///////////////////////////////////////////////// @@ -24,8 +28,8 @@ chars for red, green and blue. // D E F I N E S /////////////////////////////////////////////////// -#define PLY_OKAY 0 /* ply routine worked okay */ -#define PLY_ERROR -1 /* error in ply routine */ +#define PLY_OKAY 0 // ply routine worked okay +#define PLY_ERROR -1 // error in ply routine // S T R U C T S /////////////////////////////////////////////////// @@ -35,9 +39,9 @@ class IO_API PLY public: // scalar data types supported by PLY format enum FileType { - ASCII = 1, // ascii PLY file - BINARY_BE = 2, // binary PLY file, big endian - BINARY_LE = 3, // binary PLY file, little endian + ASCII = 1, // ascii PLY file + BINARY_BE = 2, // binary PLY file, big endian + BINARY_LE = 3, // binary PLY file, little endian }; enum DataType { StartType = 0, @@ -67,67 +71,67 @@ class IO_API PLY // description of a property struct PlyProperty { - std::string name; /* property name */ - int external_type; /* file's data type */ - int internal_type; /* program's data type */ - int offset; /* offset bytes of prop in a struct */ - - int is_list; /* 0 = scalar, 1 = list, 2 = char string */ - int count_external; /* file's count type */ - int count_internal; /* program's count type */ - int count_offset; /* offset byte for list count */ + std::string name; // property name + int external_type; // file's data type + int internal_type; // program's data type + int offset; // offset bytes of prop in a struct + + int is_list; // 0 = scalar, 1 = list, 2 = char string + int count_external; // file's count type + int count_internal; // program's count type + int count_offset; // offset byte for list count }; // description of an element struct PlyElement { - std::string name; /* element name */ - int num; /* number of elements in this object */ - int size; /* size of element (bytes) or -1 if variable */ - std::vector props;/* list of properties in the file */ - std::vector store_prop; /* flags: property wanted by user? */ - int other_offset; /* offset to un-asked-for props, or -1 if none*/ - int other_size; /* size of other_props structure */ + std::string name; // element name + int num; // number of elements in this object + int size; // size of element (bytes) or -1 if variable + std::vector props;// list of properties in the file + std::vector store_prop; // flags: property wanted by user? + int other_offset; // offset to un-asked-for props, or -1 if none + int other_size; // size of other_props structure }; // describes other properties in an element struct PlyOtherProp { - std::string name; /* element name */ - int size; /* size of other_props */ - std::vector props;/* list of properties in other_props */ + std::string name; // element name + int size; // size of other_props + std::vector props;// list of properties in other_props }; // for storing other_props for an other element struct OtherData { - void *other_props; + void* other_props; }; // data for one "other" element struct OtherElem { - std::string elem_name; /* names of other elements */ - int elem_count; /* count of instances of each element */ - OtherData **other_data; /* actual property data for the elements */ - PlyOtherProp *other_props; /* description of the property data */ + std::string elem_name; // names of other elements + int elem_count; // count of instances of each element + OtherData** other_data; // actual property data for the elements + PlyOtherProp* other_props; // description of the property data }; // "other" elements, not interpreted by user struct PlyOtherElems { - std::vector other_list;/* list of data for other elements */ + std::vector other_list;// list of data for other elements }; // rules for combining "other" properties struct PlyPropRules { - PlyElement *elem; /* element whose rules we are making */ - int *rule_list; /* types of rules (AVERAGE_PLY, MAJORITY_PLY, etc.) */ - uint32_t max_props; /* maximum number of properties we have room for now */ - std::vector props;/* list of properties we're combining */ - std::vector weights;/* list of weights of the properties */ + PlyElement* elem; // element whose rules we are making + int* rule_list; // types of rules (AVERAGE_PLY, MAJORITY_PLY, etc.) + uint32_t max_props; // maximum number of properties we have room for now + std::vector props; // list of properties we're combining + std::vector weights; // list of weights of the properties }; struct PlyRuleList { - LPCSTR name; /* name of the rule */ - char *element; /* name of element that rule applies to */ - char *property; /* name of property that rule applies to */ - struct PlyRuleList *next; /* pointer for linked list of rules */ + LPCSTR name; // name of the rule + char* element; // name of element that rule applies to + char* property; // name of property that rule applies to + struct PlyRuleList* next; // pointer for linked list of rules }; // property propagation rules @@ -156,107 +160,107 @@ class IO_API PLY bool read(LPCSTR); bool read(SEACAVE::ISTREAM*); - bool write(LPCSTR, int, LPCSTR*, int, size_t = 0); - bool write(SEACAVE::OSTREAM*, int, LPCSTR*, int, size_t = 0); - void flush(); + bool write(LPCSTR, int, LPCSTR*, int, size_t memBufferSize=0); + bool write(SEACAVE::OSTREAM*, int, LPCSTR*, int, size_t memBufferSize=0); void release(); void set_legacy_type_names(); - void get_info(float *, int *); + void get_info(float*, int*); - void append_comment(const char *); - void append_obj_info(const char *); + void append_comment(const char*); + void append_obj_info(const char*); void copy_comments(const PLY&); void copy_obj_info(const PLY&); std::vector& get_comments(); std::vector& get_obj_info(); - void describe_property(const char *, const PlyProperty&); - void describe_property(const char *, int nprops, const PlyProperty*); - void get_property(const char *, PlyProperty *); + void describe_property(const char*, const PlyProperty&); + void describe_property(const char*, int nprops, const PlyProperty*); + void get_property(const char*, PlyProperty*); void get_element(void*); - PlyOtherElems *get_other_element(); + PlyOtherElems* get_other_element(); int get_element_list(std::vector&) const; void setup_property(const PlyProperty&); LPCSTR setup_element_read(int, int*); - PlyOtherProp *get_other_properties(int); + PlyOtherProp* get_other_properties(int); - void element_count(const char *, int); + int get_elements_count() const { return (int)elems.size(); } int get_current_element_count() const { return which_elem->num; } - void describe_element(char *, int); + void element_count(const char*, int); + void describe_element(const char*, int); void describe_property(const PlyProperty&); - void describe_other_properties(PlyOtherProp *, int); - void describe_other_elements( PlyOtherElems *); - void get_element_setup(const char *, int, PlyProperty *); - int get_element_description(const char *, std::vector&) const; - void element_layout(const char *, int, int, PlyProperty *); + void describe_other_properties(PlyOtherProp*, int); + void describe_other_elements( PlyOtherElems*); + void get_element_setup(const char*, int, PlyProperty*); + int get_element_description(const char*, std::vector&) const; + void element_layout(const char*, int, int, PlyProperty*); bool header_complete(); - void put_element_setup(const char *); + void put_element_setup(const char*); void put_element(const void*); void put_other_elements(); - PlyPropRules *init_rule(const char *); - void modify_rule(PlyPropRules *, const char *, int); - void start_props(PlyPropRules *); - void weight_props(float, void *); - void *get_new_props(); - void set_prop_rules(PlyRuleList *); - PlyRuleList *append_prop_rule(PlyRuleList *, const char *, const char *); + PlyPropRules* init_rule(const char*); + void modify_rule(PlyPropRules*, const char*, int); + void start_props(PlyPropRules*); + void weight_props(float, void*); + void* get_new_props(); + void set_prop_rules(PlyRuleList*); + PlyRuleList* append_prop_rule(PlyRuleList*, const char*, const char*); - /* find an element in a ply's list */ - PlyElement* find_element(const char *) const; - /* find a property in an element's list */ - int find_property(PlyElement *, const char *) const; + // find an element in a ply's list + PlyElement* find_element(const char*) const; + // find a property in an element's list + int find_property(PlyElement*, const char*) const; static inline bool equal_strings(const char* s1, const char* s2) { return _tcscmp(s1, s2) == 0; } protected: - /* write to a file the word describing a PLY file data type */ - void write_scalar_type(SEACAVE::OSTREAM*, int); + // write to a file the word describing a PLY file data type + void write_scalar_type(int); - /* read a line from a file and break it up into separate words */ + // read a line from a file and break it up into separate words typedef SEACAVE::TokenInputStream STRISTREAM; - char **get_words(STRISTREAM&, int *, char **); + char** get_words(STRISTREAM&, int*, char**); - /* write an item to a file */ + // write an item to a file void write_binary_item(const ValueType&, int, int); void write_ascii_item(const ValueType&, int, int); - /* return the value of a stored item */ - void get_stored_item(void*, int, ValueType&); + // return the value of a stored item + static void get_stored_item(const void*, int, ValueType&); - /* get binary or ascii item and store it according to ptr and type */ - void get_binary_item(SEACAVE::ISTREAM*, int, ValueType&); - void get_ascii_item(const char*, int, ValueType&); + // get binary or ascii item and store it according to ptr and type + void get_binary_item(int, ValueType&); + static void get_ascii_item(const char*, int, ValueType&); - /* store a value into where a pointer and a type specify */ - void store_item(void*, int, const ValueType&, int); + // store a value into where a pointer and a type specify + static void store_item(void*, int, const ValueType&, int); - /* add information to a PLY file descriptor */ - void add_element(const char **, int); - void add_property(const char **, int); - void add_comment(const char *); - void add_obj_info(const char *); + // add information to a PLY file descriptor + void add_element(const char**, int); + void add_property(const char**, int); + void add_comment(const char*); + void add_obj_info(const char*); - /* get a bunch of elements from a file */ + // get a bunch of elements from a file void ascii_get_element(uint8_t*); void binary_get_element(uint8_t*); - void setup_other_props(PlyElement *); - PlyOtherProp *get_other_properties(PlyElement *, int); - PlyOtherProp *get_other_properties(const char *, int); + void setup_other_props(PlyElement*); + PlyOtherProp* get_other_properties(PlyElement*, int); + PlyOtherProp* get_other_properties(const char*, int); - int matches_rule_name(const char *); - int get_prop_type(const char *); + int matches_rule_name(const char*); + int get_prop_type(const char*); - /* copy a property */ + // copy a property static void copy_property(PlyProperty&, const PlyProperty&); - /* return the value as T stored in val as type */ + // return the value as T stored in val as type template static inline T ValueType2Type(const ValueType& val, int type) { switch (type) { @@ -281,32 +285,31 @@ class IO_API PLY return T(0); } -public: +protected: // description of PLY file - std::string filename; /* file name */ - SEACAVE::MemFile* mfp; /* mem file pointer */ - SEACAVE::OSTREAM* f; /* output file pointer */ + std::string filename; // file name + int file_type; // ascii or binary + float version; // version number of file + std::vector elems; // list of elements + std::vector comments;// list of comments + std::vector obj_info;// list of object info items + PlyElement* which_elem; // element we're currently reading or writing + PlyOtherElems* other_elems; // "other" elements from a PLY file + PlyPropRules* current_rules; // current propagation rules + PlyRuleList* rule_list; // rule list from user + std::vector vals; // rule list from user + union { - SEACAVE::ISTREAM* istream; /* input file pointer */ - SEACAVE::OSTREAM* ostream; /* output file pointer */ + SEACAVE::ISTREAM* istream; // input file pointer + SEACAVE::OSTREAM* ostream; // output file pointer }; - int file_type; /* ascii or binary */ - float version; /* version number of file */ - std::vector elems;/* list of elements */ - std::vector comments;/* list of comments */ - std::vector obj_info;/* list of object info items */ - PlyElement *which_elem; /* element we're currently reading or writing */ - PlyOtherElems *other_elems; /* "other" elements from a PLY file */ - PlyPropRules *current_rules; /* current propagation rules */ - PlyRuleList *rule_list; /* rule list from user */ - std::vector vals; /* rule list from user */ - const char* const* write_type_names; /* names of scalar types to be used for writing (new types by default) */ + SEACAVE::MemFile* mfp; // mem-file pointer (optional) + const char* const* write_type_names; // names of scalar types to be used for writing (new types by default) -protected: - static const char* const type_names[9]; // names of scalar types - static const char* const old_type_names[9]; // old names of types for backward compatibility + static const char* const type_names[9]; // names of scalar types + static const char* const old_type_names[9]; // old names of types for backward compatibility static const int ply_type_size[9]; static const RuleName rule_name_list[7]; }; -#endif // __PLY_H__ +#endif // __SEACAVE_PLY_H__ diff --git a/libs/IO/TinyXML2.cpp b/libs/IO/TinyXML2.cpp new file mode 100644 index 000000000..bea34144c --- /dev/null +++ b/libs/IO/TinyXML2.cpp @@ -0,0 +1,2987 @@ +/* +Original code by Lee Thomason (www.grinninglizard.com) + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must +not claim that you wrote the original software. If you use this +software in a product, an acknowledgment in the product documentation +would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and +must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source +distribution. +*/ + +#include "Common.h" +#include "TinyXML2.h" + +#include // yes, this one new style header, is in the Android SDK. +#if defined(ANDROID_NDK) || defined(__BORLANDC__) || defined(__QNXNTO__) +# include +# include +#else +# include +# include +#endif + +#if defined(_MSC_VER) && (_MSC_VER >= 1400 ) && (!defined WINCE) + // Microsoft Visual Studio, version 2005 and higher. Not WinCE. + /*int _snprintf_s( + char *buffer, + size_t sizeOfBuffer, + size_t count, + const char *format [, + argument] ... + );*/ + static inline int TIXML_SNPRINTF( char* buffer, size_t size, const char* format, ... ) + { + va_list va; + va_start( va, format ); + const int result = vsnprintf_s( buffer, size, _TRUNCATE, format, va ); + va_end( va ); + return result; + } + + static inline int TIXML_VSNPRINTF( char* buffer, size_t size, const char* format, va_list va ) + { + const int result = vsnprintf_s( buffer, size, _TRUNCATE, format, va ); + return result; + } + + #define TIXML_VSCPRINTF _vscprintf + #define TIXML_SSCANF sscanf_s +#elif defined _MSC_VER + // Microsoft Visual Studio 2003 and earlier or WinCE + #define TIXML_SNPRINTF _snprintf + #define TIXML_VSNPRINTF _vsnprintf + #define TIXML_SSCANF sscanf + #if (_MSC_VER < 1400 ) && (!defined WINCE) + // Microsoft Visual Studio 2003 and not WinCE. + #define TIXML_VSCPRINTF _vscprintf // VS2003's C runtime has this, but VC6 C runtime or WinCE SDK doesn't have. + #else + // Microsoft Visual Studio 2003 and earlier or WinCE. + static inline int TIXML_VSCPRINTF( const char* format, va_list va ) + { + int len = 512; + for (;;) { + len = len*2; + char* str = new char[len](); + const int required = _vsnprintf(str, len, format, va); + delete[] str; + if ( required != -1 ) { + TIXMLASSERT( required >= 0 ); + len = required; + break; + } + } + TIXMLASSERT( len >= 0 ); + return len; + } + #endif +#else + // GCC version 3 and higher + //#warning( "Using sn* functions." ) + #define TIXML_SNPRINTF snprintf + #define TIXML_VSNPRINTF vsnprintf + static inline int TIXML_VSCPRINTF( const char* format, va_list va ) + { + int len = vsnprintf( 0, 0, format, va ); + TIXMLASSERT( len >= 0 ); + return len; + } + #define TIXML_SSCANF sscanf +#endif + +#if defined(_WIN64) + #define TIXML_FSEEK _fseeki64 + #define TIXML_FTELL _ftelli64 +#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__ANDROID__) + #define TIXML_FSEEK fseeko + #define TIXML_FTELL ftello +#elif defined(__unix__) && defined(__x86_64__) + #define TIXML_FSEEK fseeko64 + #define TIXML_FTELL ftello64 +#else + #define TIXML_FSEEK fseek + #define TIXML_FTELL ftell +#endif + + +static const char LINE_FEED = static_cast(0x0a); // all line endings are normalized to LF +static const char LF = LINE_FEED; +static const char CARRIAGE_RETURN = static_cast(0x0d); // CR gets filtered out +static const char CR = CARRIAGE_RETURN; +static const char SINGLE_QUOTE = '\''; +static const char DOUBLE_QUOTE = '\"'; + +// Bunch of unicode info at: +// http://www.unicode.org/faq/utf_bom.html +// ef bb bf (Microsoft "lead bytes") - designates UTF-8 + +static const unsigned char TIXML_UTF_LEAD_0 = 0xefU; +static const unsigned char TIXML_UTF_LEAD_1 = 0xbbU; +static const unsigned char TIXML_UTF_LEAD_2 = 0xbfU; + +namespace tinyxml2 +{ + +struct Entity { + const char* pattern; + int length; + char value; +}; + +static const int NUM_ENTITIES = 5; +static const Entity entities[NUM_ENTITIES] = { + { "quot", 4, DOUBLE_QUOTE }, + { "amp", 3, '&' }, + { "apos", 4, SINGLE_QUOTE }, + { "lt", 2, '<' }, + { "gt", 2, '>' } +}; + + +StrPair::~StrPair() +{ + Reset(); +} + + +void StrPair::TransferTo( StrPair* other ) +{ + if ( this == other ) { + return; + } + // This in effect implements the assignment operator by "moving" + // ownership (as in auto_ptr). + + TIXMLASSERT( other != 0 ); + TIXMLASSERT( other->_flags == 0 ); + TIXMLASSERT( other->_start == 0 ); + TIXMLASSERT( other->_end == 0 ); + + other->Reset(); + + other->_flags = _flags; + other->_start = _start; + other->_end = _end; + + _flags = 0; + _start = 0; + _end = 0; +} + + +void StrPair::Reset() +{ + if ( _flags & NEEDS_DELETE ) { + delete [] _start; + } + _flags = 0; + _start = 0; + _end = 0; +} + + +void StrPair::SetStr( const char* str, int flags ) +{ + TIXMLASSERT( str ); + Reset(); + size_t len = strlen( str ); + TIXMLASSERT( _start == 0 ); + _start = new char[ len+1 ]; + memcpy( _start, str, len+1 ); + _end = _start + len; + _flags = flags | NEEDS_DELETE; +} + + +char* StrPair::ParseText( char* p, const char* endTag, int strFlags, int* curLineNumPtr ) +{ + TIXMLASSERT( p ); + TIXMLASSERT( endTag && *endTag ); + TIXMLASSERT(curLineNumPtr); + + char* start = p; + const char endChar = *endTag; + size_t length = strlen( endTag ); + + // Inner loop of text parsing. + while ( *p ) { + if ( *p == endChar && strncmp( p, endTag, length ) == 0 ) { + Set( start, p, strFlags ); + return p + length; + } else if (*p == '\n') { + ++(*curLineNumPtr); + } + ++p; + TIXMLASSERT( p ); + } + return 0; +} + + +char* StrPair::ParseName( char* p ) +{ + if ( !p || !(*p) ) { + return 0; + } + if ( !XMLUtil::IsNameStartChar( (unsigned char) *p ) ) { + return 0; + } + + char* const start = p; + ++p; + while ( *p && XMLUtil::IsNameChar( (unsigned char) *p ) ) { + ++p; + } + + Set( start, p, 0 ); + return p; +} + + +void StrPair::CollapseWhitespace() +{ + // Adjusting _start would cause undefined behavior on delete[] + TIXMLASSERT( ( _flags & NEEDS_DELETE ) == 0 ); + // Trim leading space. + _start = XMLUtil::SkipWhiteSpace( _start, 0 ); + + if ( *_start ) { + const char* p = _start; // the read pointer + char* q = _start; // the write pointer + + while( *p ) { + if ( XMLUtil::IsWhiteSpace( *p )) { + p = XMLUtil::SkipWhiteSpace( p, 0 ); + if ( *p == 0 ) { + break; // don't write to q; this trims the trailing space. + } + *q = ' '; + ++q; + } + *q = *p; + ++q; + ++p; + } + *q = 0; + } +} + + +const char* StrPair::GetStr() +{ + TIXMLASSERT( _start ); + TIXMLASSERT( _end ); + if ( _flags & NEEDS_FLUSH ) { + *_end = 0; + _flags ^= NEEDS_FLUSH; + + if ( _flags ) { + const char* p = _start; // the read pointer + char* q = _start; // the write pointer + + while( p < _end ) { + if ( (_flags & NEEDS_NEWLINE_NORMALIZATION) && *p == CR ) { + // CR-LF pair becomes LF + // CR alone becomes LF + // LF-CR becomes LF + if ( *(p+1) == LF ) { + p += 2; + } + else { + ++p; + } + *q = LF; + ++q; + } + else if ( (_flags & NEEDS_NEWLINE_NORMALIZATION) && *p == LF ) { + if ( *(p+1) == CR ) { + p += 2; + } + else { + ++p; + } + *q = LF; + ++q; + } + else if ( (_flags & NEEDS_ENTITY_PROCESSING) && *p == '&' ) { + // Entities handled by tinyXML2: + // - special entities in the entity table [in/out] + // - numeric character reference [in] + // 中 or 中 + + if ( *(p+1) == '#' ) { + const int buflen = 10; + char buf[buflen] = { 0 }; + int len = 0; + const char* adjusted = const_cast( XMLUtil::GetCharacterRef( p, buf, &len ) ); + if ( adjusted == 0 ) { + *q = *p; + ++p; + ++q; + } + else { + TIXMLASSERT( 0 <= len && len <= buflen ); + TIXMLASSERT( q + len <= adjusted ); + p = adjusted; + memcpy( q, buf, len ); + q += len; + } + } + else { + bool entityFound = false; + for( int i = 0; i < NUM_ENTITIES; ++i ) { + const Entity& entity = entities[i]; + if ( strncmp( p + 1, entity.pattern, entity.length ) == 0 + && *( p + entity.length + 1 ) == ';' ) { + // Found an entity - convert. + *q = entity.value; + ++q; + p += entity.length + 2; + entityFound = true; + break; + } + } + if ( !entityFound ) { + // fixme: treat as error? + ++p; + ++q; + } + } + } + else { + *q = *p; + ++p; + ++q; + } + } + *q = 0; + } + // The loop below has plenty going on, and this + // is a less useful mode. Break it out. + if ( _flags & NEEDS_WHITESPACE_COLLAPSING ) { + CollapseWhitespace(); + } + _flags = (_flags & NEEDS_DELETE); + } + TIXMLASSERT( _start ); + return _start; +} + + + + +// --------- XMLUtil ----------- // + +const char* XMLUtil::writeBoolTrue = "true"; +const char* XMLUtil::writeBoolFalse = "false"; + +void XMLUtil::SetBoolSerialization(const char* writeTrue, const char* writeFalse) +{ + static const char* defTrue = "true"; + static const char* defFalse = "false"; + + writeBoolTrue = (writeTrue) ? writeTrue : defTrue; + writeBoolFalse = (writeFalse) ? writeFalse : defFalse; +} + + +const char* XMLUtil::ReadBOM( const char* p, bool* bom ) +{ + TIXMLASSERT( p ); + TIXMLASSERT( bom ); + *bom = false; + const unsigned char* pu = reinterpret_cast(p); + // Check for BOM: + if ( *(pu+0) == TIXML_UTF_LEAD_0 + && *(pu+1) == TIXML_UTF_LEAD_1 + && *(pu+2) == TIXML_UTF_LEAD_2 ) { + *bom = true; + p += 3; + } + TIXMLASSERT( p ); + return p; +} + + +void XMLUtil::ConvertUTF32ToUTF8( unsigned long input, char* output, int* length ) +{ + const unsigned long BYTE_MASK = 0xBF; + const unsigned long BYTE_MARK = 0x80; + const unsigned long FIRST_BYTE_MARK[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; + + if (input < 0x80) { + *length = 1; + } + else if ( input < 0x800 ) { + *length = 2; + } + else if ( input < 0x10000 ) { + *length = 3; + } + else if ( input < 0x200000 ) { + *length = 4; + } + else { + *length = 0; // This code won't convert this correctly anyway. + return; + } + + output += *length; + + // Scary scary fall throughs are annotated with carefully designed comments + // to suppress compiler warnings such as -Wimplicit-fallthrough in gcc + switch (*length) { + case 4: + --output; + *output = static_cast((input | BYTE_MARK) & BYTE_MASK); + input >>= 6; + //fall through + case 3: + --output; + *output = static_cast((input | BYTE_MARK) & BYTE_MASK); + input >>= 6; + //fall through + case 2: + --output; + *output = static_cast((input | BYTE_MARK) & BYTE_MASK); + input >>= 6; + //fall through + case 1: + --output; + *output = static_cast(input | FIRST_BYTE_MARK[*length]); + break; + default: + TIXMLASSERT( false ); + } +} + + +const char* XMLUtil::GetCharacterRef( const char* p, char* value, int* length ) +{ + // Presume an entity, and pull it out. + *length = 0; + + if ( *(p+1) == '#' && *(p+2) ) { + unsigned long ucs = 0; + TIXMLASSERT( sizeof( ucs ) >= 4 ); + ptrdiff_t delta = 0; + unsigned mult = 1; + static const char SEMICOLON = ';'; + + if ( *(p+2) == 'x' ) { + // Hexadecimal. + const char* q = p+3; + if ( !(*q) ) { + return 0; + } + + q = strchr( q, SEMICOLON ); + + if ( !q ) { + return 0; + } + TIXMLASSERT( *q == SEMICOLON ); + + delta = q-p; + --q; + + while ( *q != 'x' ) { + unsigned int digit = 0; + + if ( *q >= '0' && *q <= '9' ) { + digit = *q - '0'; + } + else if ( *q >= 'a' && *q <= 'f' ) { + digit = *q - 'a' + 10; + } + else if ( *q >= 'A' && *q <= 'F' ) { + digit = *q - 'A' + 10; + } + else { + return 0; + } + TIXMLASSERT( digit < 16 ); + TIXMLASSERT( digit == 0 || mult <= UINT_MAX / digit ); + const unsigned int digitScaled = mult * digit; + TIXMLASSERT( ucs <= ULONG_MAX - digitScaled ); + ucs += digitScaled; + TIXMLASSERT( mult <= UINT_MAX / 16 ); + mult *= 16; + --q; + } + } + else { + // Decimal. + const char* q = p+2; + if ( !(*q) ) { + return 0; + } + + q = strchr( q, SEMICOLON ); + + if ( !q ) { + return 0; + } + TIXMLASSERT( *q == SEMICOLON ); + + delta = q-p; + --q; + + while ( *q != '#' ) { + if ( *q >= '0' && *q <= '9' ) { + const unsigned int digit = *q - '0'; + TIXMLASSERT( digit < 10 ); + TIXMLASSERT( digit == 0 || mult <= UINT_MAX / digit ); + const unsigned int digitScaled = mult * digit; + TIXMLASSERT( ucs <= ULONG_MAX - digitScaled ); + ucs += digitScaled; + } + else { + return 0; + } + TIXMLASSERT( mult <= UINT_MAX / 10 ); + mult *= 10; + --q; + } + } + // convert the UCS to UTF-8 + ConvertUTF32ToUTF8( ucs, value, length ); + return p + delta + 1; + } + return p+1; +} + + +void XMLUtil::ToStr( int v, char* buffer, int bufferSize ) +{ + TIXML_SNPRINTF( buffer, bufferSize, "%d", v ); +} + + +void XMLUtil::ToStr( unsigned v, char* buffer, int bufferSize ) +{ + TIXML_SNPRINTF( buffer, bufferSize, "%u", v ); +} + + +void XMLUtil::ToStr( bool v, char* buffer, int bufferSize ) +{ + TIXML_SNPRINTF( buffer, bufferSize, "%s", v ? writeBoolTrue : writeBoolFalse); +} + +/* + ToStr() of a number is a very tricky topic. + https://github.com/leethomason/tinyxml2/issues/106 +*/ +void XMLUtil::ToStr( float v, char* buffer, int bufferSize ) +{ + TIXML_SNPRINTF( buffer, bufferSize, "%.8g", v ); +} + + +void XMLUtil::ToStr( double v, char* buffer, int bufferSize ) +{ + TIXML_SNPRINTF( buffer, bufferSize, "%.17g", v ); +} + + +void XMLUtil::ToStr( int64_t v, char* buffer, int bufferSize ) +{ + // horrible syntax trick to make the compiler happy about %lld + TIXML_SNPRINTF(buffer, bufferSize, "%lld", static_cast(v)); +} + +void XMLUtil::ToStr( uint64_t v, char* buffer, int bufferSize ) +{ + // horrible syntax trick to make the compiler happy about %llu + TIXML_SNPRINTF(buffer, bufferSize, "%llu", (long long)v); +} + +bool XMLUtil::ToInt(const char* str, int* value) +{ + if (IsPrefixHex(str)) { + unsigned v; + if (TIXML_SSCANF(str, "%x", &v) == 1) { + *value = static_cast(v); + return true; + } + } + else { + if (TIXML_SSCANF(str, "%d", value) == 1) { + return true; + } + } + return false; +} + +bool XMLUtil::ToUnsigned(const char* str, unsigned* value) +{ + if (TIXML_SSCANF(str, IsPrefixHex(str) ? "%x" : "%u", value) == 1) { + return true; + } + return false; +} + +bool XMLUtil::ToBool( const char* str, bool* value ) +{ + int ival = 0; + if ( ToInt( str, &ival )) { + *value = (ival==0) ? false : true; + return true; + } + static const char* TRUE_VALS[] = { "true", "True", "TRUE", 0 }; + static const char* FALSE_VALS[] = { "false", "False", "FALSE", 0 }; + + for (int i = 0; TRUE_VALS[i]; ++i) { + if (StringEqual(str, TRUE_VALS[i])) { + *value = true; + return true; + } + } + for (int i = 0; FALSE_VALS[i]; ++i) { + if (StringEqual(str, FALSE_VALS[i])) { + *value = false; + return true; + } + } + return false; +} + + +bool XMLUtil::ToFloat( const char* str, float* value ) +{ + if ( TIXML_SSCANF( str, "%f", value ) == 1 ) { + return true; + } + return false; +} + + +bool XMLUtil::ToDouble( const char* str, double* value ) +{ + if ( TIXML_SSCANF( str, "%lf", value ) == 1 ) { + return true; + } + return false; +} + + +bool XMLUtil::ToInt64(const char* str, int64_t* value) +{ + if (IsPrefixHex(str)) { + unsigned long long v = 0; // horrible syntax trick to make the compiler happy about %llx + if (TIXML_SSCANF(str, "%llx", &v) == 1) { + *value = static_cast(v); + return true; + } + } + else { + long long v = 0; // horrible syntax trick to make the compiler happy about %lld + if (TIXML_SSCANF(str, "%lld", &v) == 1) { + *value = static_cast(v); + return true; + } + } + return false; +} + + +bool XMLUtil::ToUnsigned64(const char* str, uint64_t* value) { + unsigned long long v = 0; // horrible syntax trick to make the compiler happy about %llu + if(TIXML_SSCANF(str, IsPrefixHex(str) ? "%llx" : "%llu", &v) == 1) { + *value = (uint64_t)v; + return true; + } + return false; +} + + +char* XMLDocument::Identify( char* p, XMLNode** node ) +{ + TIXMLASSERT( node ); + TIXMLASSERT( p ); + char* const start = p; + int const startLine = _parseCurLineNum; + p = XMLUtil::SkipWhiteSpace( p, &_parseCurLineNum ); + if( !*p ) { + *node = 0; + TIXMLASSERT( p ); + return p; + } + + // These strings define the matching patterns: + static const char* xmlHeader = { "( _commentPool ); + returnNode->_parseLineNum = _parseCurLineNum; + p += xmlHeaderLen; + } + else if ( XMLUtil::StringEqual( p, commentHeader, commentHeaderLen ) ) { + returnNode = CreateUnlinkedNode( _commentPool ); + returnNode->_parseLineNum = _parseCurLineNum; + p += commentHeaderLen; + } + else if ( XMLUtil::StringEqual( p, cdataHeader, cdataHeaderLen ) ) { + XMLText* text = CreateUnlinkedNode( _textPool ); + returnNode = text; + returnNode->_parseLineNum = _parseCurLineNum; + p += cdataHeaderLen; + text->SetCData( true ); + } + else if ( XMLUtil::StringEqual( p, dtdHeader, dtdHeaderLen ) ) { + returnNode = CreateUnlinkedNode( _commentPool ); + returnNode->_parseLineNum = _parseCurLineNum; + p += dtdHeaderLen; + } + else if ( XMLUtil::StringEqual( p, elementHeader, elementHeaderLen ) ) { + returnNode = CreateUnlinkedNode( _elementPool ); + returnNode->_parseLineNum = _parseCurLineNum; + p += elementHeaderLen; + } + else { + returnNode = CreateUnlinkedNode( _textPool ); + returnNode->_parseLineNum = _parseCurLineNum; // Report line of first non-whitespace character + p = start; // Back it up, all the text counts. + _parseCurLineNum = startLine; + } + + TIXMLASSERT( returnNode ); + TIXMLASSERT( p ); + *node = returnNode; + return p; +} + + +bool XMLDocument::Accept( XMLVisitor* visitor ) const +{ + TIXMLASSERT( visitor ); + if ( visitor->VisitEnter( *this ) ) { + for ( const XMLNode* node=FirstChild(); node; node=node->NextSibling() ) { + if ( !node->Accept( visitor ) ) { + break; + } + } + } + return visitor->VisitExit( *this ); +} + + +// --------- XMLNode ----------- // + +XMLNode::XMLNode( XMLDocument* doc ) : + _document( doc ), + _parent( 0 ), + _value(), + _parseLineNum( 0 ), + _firstChild( 0 ), _lastChild( 0 ), + _prev( 0 ), _next( 0 ), + _userData( 0 ), + _memPool( 0 ) +{ +} + + +XMLNode::~XMLNode() +{ + DeleteChildren(); + if ( _parent ) { + _parent->Unlink( this ); + } +} + +const char* XMLNode::Value() const +{ + // Edge case: XMLDocuments don't have a Value. Return null. + if ( this->ToDocument() ) + return 0; + return _value.GetStr(); +} + +void XMLNode::SetValue( const char* str, bool staticMem ) +{ + if ( staticMem ) { + _value.SetInternedStr( str ); + } + else { + _value.SetStr( str ); + } +} + +XMLNode* XMLNode::DeepClone(XMLDocument* target) const +{ + XMLNode* clone = this->ShallowClone(target); + if (!clone) return 0; + + for (const XMLNode* child = this->FirstChild(); child; child = child->NextSibling()) { + XMLNode* childClone = child->DeepClone(target); + TIXMLASSERT(childClone); + clone->InsertEndChild(childClone); + } + return clone; +} + +void XMLNode::DeleteChildren() +{ + while( _firstChild ) { + TIXMLASSERT( _lastChild ); + DeleteChild( _firstChild ); + } + _firstChild = _lastChild = 0; +} + + +void XMLNode::Unlink( XMLNode* child ) +{ + TIXMLASSERT( child ); + TIXMLASSERT( child->_document == _document ); + TIXMLASSERT( child->_parent == this ); + if ( child == _firstChild ) { + _firstChild = _firstChild->_next; + } + if ( child == _lastChild ) { + _lastChild = _lastChild->_prev; + } + + if ( child->_prev ) { + child->_prev->_next = child->_next; + } + if ( child->_next ) { + child->_next->_prev = child->_prev; + } + child->_next = 0; + child->_prev = 0; + child->_parent = 0; +} + + +void XMLNode::DeleteChild( XMLNode* node ) +{ + TIXMLASSERT( node ); + TIXMLASSERT( node->_document == _document ); + TIXMLASSERT( node->_parent == this ); + Unlink( node ); + TIXMLASSERT(node->_prev == 0); + TIXMLASSERT(node->_next == 0); + TIXMLASSERT(node->_parent == 0); + DeleteNode( node ); +} + + +XMLNode* XMLNode::InsertEndChild( XMLNode* addThis ) +{ + TIXMLASSERT( addThis ); + if ( addThis->_document != _document ) { + TIXMLASSERT( false ); + return 0; + } + InsertChildPreamble( addThis ); + + if ( _lastChild ) { + TIXMLASSERT( _firstChild ); + TIXMLASSERT( _lastChild->_next == 0 ); + _lastChild->_next = addThis; + addThis->_prev = _lastChild; + _lastChild = addThis; + + addThis->_next = 0; + } + else { + TIXMLASSERT( _firstChild == 0 ); + _firstChild = _lastChild = addThis; + + addThis->_prev = 0; + addThis->_next = 0; + } + addThis->_parent = this; + return addThis; +} + + +XMLNode* XMLNode::InsertFirstChild( XMLNode* addThis ) +{ + TIXMLASSERT( addThis ); + if ( addThis->_document != _document ) { + TIXMLASSERT( false ); + return 0; + } + InsertChildPreamble( addThis ); + + if ( _firstChild ) { + TIXMLASSERT( _lastChild ); + TIXMLASSERT( _firstChild->_prev == 0 ); + + _firstChild->_prev = addThis; + addThis->_next = _firstChild; + _firstChild = addThis; + + addThis->_prev = 0; + } + else { + TIXMLASSERT( _lastChild == 0 ); + _firstChild = _lastChild = addThis; + + addThis->_prev = 0; + addThis->_next = 0; + } + addThis->_parent = this; + return addThis; +} + + +XMLNode* XMLNode::InsertAfterChild( XMLNode* afterThis, XMLNode* addThis ) +{ + TIXMLASSERT( addThis ); + if ( addThis->_document != _document ) { + TIXMLASSERT( false ); + return 0; + } + + TIXMLASSERT( afterThis ); + + if ( afterThis->_parent != this ) { + TIXMLASSERT( false ); + return 0; + } + if ( afterThis == addThis ) { + // Current state: BeforeThis -> AddThis -> OneAfterAddThis + // Now AddThis must disappear from it's location and then + // reappear between BeforeThis and OneAfterAddThis. + // So just leave it where it is. + return addThis; + } + + if ( afterThis->_next == 0 ) { + // The last node or the only node. + return InsertEndChild( addThis ); + } + InsertChildPreamble( addThis ); + addThis->_prev = afterThis; + addThis->_next = afterThis->_next; + afterThis->_next->_prev = addThis; + afterThis->_next = addThis; + addThis->_parent = this; + return addThis; +} + + + + +const XMLElement* XMLNode::FirstChildElement( const char* name ) const +{ + for( const XMLNode* node = _firstChild; node; node = node->_next ) { + const XMLElement* element = node->ToElementWithName( name ); + if ( element ) { + return element; + } + } + return 0; +} + + +const XMLElement* XMLNode::LastChildElement( const char* name ) const +{ + for( const XMLNode* node = _lastChild; node; node = node->_prev ) { + const XMLElement* element = node->ToElementWithName( name ); + if ( element ) { + return element; + } + } + return 0; +} + + +const XMLElement* XMLNode::NextSiblingElement( const char* name ) const +{ + for( const XMLNode* node = _next; node; node = node->_next ) { + const XMLElement* element = node->ToElementWithName( name ); + if ( element ) { + return element; + } + } + return 0; +} + + +const XMLElement* XMLNode::PreviousSiblingElement( const char* name ) const +{ + for( const XMLNode* node = _prev; node; node = node->_prev ) { + const XMLElement* element = node->ToElementWithName( name ); + if ( element ) { + return element; + } + } + return 0; +} + + +char* XMLNode::ParseDeep( char* p, StrPair* parentEndTag, int* curLineNumPtr ) +{ + // This is a recursive method, but thinking about it "at the current level" + // it is a pretty simple flat list: + // + // + // + // With a special case: + // + // + // + // + // Where the closing element (/foo) *must* be the next thing after the opening + // element, and the names must match. BUT the tricky bit is that the closing + // element will be read by the child. + // + // 'endTag' is the end tag for this node, it is returned by a call to a child. + // 'parentEnd' is the end tag for the parent, which is filled in and returned. + + XMLDocument::DepthTracker tracker(_document); + if (_document->Error()) + return 0; + + while( p && *p ) { + XMLNode* node = 0; + + p = _document->Identify( p, &node ); + TIXMLASSERT( p ); + if ( node == 0 ) { + break; + } + + const int initialLineNum = node->_parseLineNum; + + StrPair endTag; + p = node->ParseDeep( p, &endTag, curLineNumPtr ); + if ( !p ) { + DeleteNode( node ); + if ( !_document->Error() ) { + _document->SetError( XML_ERROR_PARSING, initialLineNum, 0); + } + break; + } + + const XMLDeclaration* const decl = node->ToDeclaration(); + if ( decl ) { + // Declarations are only allowed at document level + // + // Multiple declarations are allowed but all declarations + // must occur before anything else. + // + // Optimized due to a security test case. If the first node is + // a declaration, and the last node is a declaration, then only + // declarations have so far been added. + bool wellLocated = false; + + if (ToDocument()) { + if (FirstChild()) { + wellLocated = + FirstChild() && + FirstChild()->ToDeclaration() && + LastChild() && + LastChild()->ToDeclaration(); + } + else { + wellLocated = true; + } + } + if ( !wellLocated ) { + _document->SetError( XML_ERROR_PARSING_DECLARATION, initialLineNum, "XMLDeclaration value=%s", decl->Value()); + DeleteNode( node ); + break; + } + } + + XMLElement* ele = node->ToElement(); + if ( ele ) { + // We read the end tag. Return it to the parent. + if ( ele->ClosingType() == XMLElement::CLOSING ) { + if ( parentEndTag ) { + ele->_value.TransferTo( parentEndTag ); + } + node->_memPool->SetTracked(); // created and then immediately deleted. + DeleteNode( node ); + return p; + } + + // Handle an end tag returned to this level. + // And handle a bunch of annoying errors. + bool mismatch = false; + if ( endTag.Empty() ) { + if ( ele->ClosingType() == XMLElement::OPEN ) { + mismatch = true; + } + } + else { + if ( ele->ClosingType() != XMLElement::OPEN ) { + mismatch = true; + } + else if ( !XMLUtil::StringEqual( endTag.GetStr(), ele->Name() ) ) { + mismatch = true; + } + } + if ( mismatch ) { + _document->SetError( XML_ERROR_MISMATCHED_ELEMENT, initialLineNum, "XMLElement name=%s", ele->Name()); + DeleteNode( node ); + break; + } + } + InsertEndChild( node ); + } + return 0; +} + +/*static*/ void XMLNode::DeleteNode( XMLNode* node ) +{ + if ( node == 0 ) { + return; + } + TIXMLASSERT(node->_document); + if (!node->ToDocument()) { + node->_document->MarkInUse(node); + } + + MemPool* pool = node->_memPool; + node->~XMLNode(); + pool->Free( node ); +} + +void XMLNode::InsertChildPreamble( XMLNode* insertThis ) const +{ + TIXMLASSERT( insertThis ); + TIXMLASSERT( insertThis->_document == _document ); + + if (insertThis->_parent) { + insertThis->_parent->Unlink( insertThis ); + } + else { + insertThis->_document->MarkInUse(insertThis); + insertThis->_memPool->SetTracked(); + } +} + +const XMLElement* XMLNode::ToElementWithName( const char* name ) const +{ + const XMLElement* element = this->ToElement(); + if ( element == 0 ) { + return 0; + } + if ( name == 0 ) { + return element; + } + if ( XMLUtil::StringEqual( element->Name(), name ) ) { + return element; + } + return 0; +} + +// --------- XMLText ---------- // +char* XMLText::ParseDeep( char* p, StrPair*, int* curLineNumPtr ) +{ + if ( this->CData() ) { + p = _value.ParseText( p, "]]>", StrPair::NEEDS_NEWLINE_NORMALIZATION, curLineNumPtr ); + if ( !p ) { + _document->SetError( XML_ERROR_PARSING_CDATA, _parseLineNum, 0 ); + } + return p; + } + else { + int flags = _document->ProcessEntities() ? StrPair::TEXT_ELEMENT : StrPair::TEXT_ELEMENT_LEAVE_ENTITIES; + if ( _document->WhitespaceMode() == COLLAPSE_WHITESPACE ) { + flags |= StrPair::NEEDS_WHITESPACE_COLLAPSING; + } + + p = _value.ParseText( p, "<", flags, curLineNumPtr ); + if ( p && *p ) { + return p-1; + } + if ( !p ) { + _document->SetError( XML_ERROR_PARSING_TEXT, _parseLineNum, 0 ); + } + } + return 0; +} + + +XMLNode* XMLText::ShallowClone( XMLDocument* doc ) const +{ + if ( !doc ) { + doc = _document; + } + XMLText* text = doc->NewText( Value() ); // fixme: this will always allocate memory. Intern? + text->SetCData( this->CData() ); + return text; +} + + +bool XMLText::ShallowEqual( const XMLNode* compare ) const +{ + TIXMLASSERT( compare ); + const XMLText* text = compare->ToText(); + return ( text && XMLUtil::StringEqual( text->Value(), Value() ) ); +} + + +bool XMLText::Accept( XMLVisitor* visitor ) const +{ + TIXMLASSERT( visitor ); + return visitor->Visit( *this ); +} + + +// --------- XMLComment ---------- // + +XMLComment::XMLComment( XMLDocument* doc ) : XMLNode( doc ) +{ +} + + +XMLComment::~XMLComment() +{ +} + + +char* XMLComment::ParseDeep( char* p, StrPair*, int* curLineNumPtr ) +{ + // Comment parses as text. + p = _value.ParseText( p, "-->", StrPair::COMMENT, curLineNumPtr ); + if ( p == 0 ) { + _document->SetError( XML_ERROR_PARSING_COMMENT, _parseLineNum, 0 ); + } + return p; +} + + +XMLNode* XMLComment::ShallowClone( XMLDocument* doc ) const +{ + if ( !doc ) { + doc = _document; + } + XMLComment* comment = doc->NewComment( Value() ); // fixme: this will always allocate memory. Intern? + return comment; +} + + +bool XMLComment::ShallowEqual( const XMLNode* compare ) const +{ + TIXMLASSERT( compare ); + const XMLComment* comment = compare->ToComment(); + return ( comment && XMLUtil::StringEqual( comment->Value(), Value() )); +} + + +bool XMLComment::Accept( XMLVisitor* visitor ) const +{ + TIXMLASSERT( visitor ); + return visitor->Visit( *this ); +} + + +// --------- XMLDeclaration ---------- // + +XMLDeclaration::XMLDeclaration( XMLDocument* doc ) : XMLNode( doc ) +{ +} + + +XMLDeclaration::~XMLDeclaration() +{ + //printf( "~XMLDeclaration\n" ); +} + + +char* XMLDeclaration::ParseDeep( char* p, StrPair*, int* curLineNumPtr ) +{ + // Declaration parses as text. + p = _value.ParseText( p, "?>", StrPair::NEEDS_NEWLINE_NORMALIZATION, curLineNumPtr ); + if ( p == 0 ) { + _document->SetError( XML_ERROR_PARSING_DECLARATION, _parseLineNum, 0 ); + } + return p; +} + + +XMLNode* XMLDeclaration::ShallowClone( XMLDocument* doc ) const +{ + if ( !doc ) { + doc = _document; + } + XMLDeclaration* dec = doc->NewDeclaration( Value() ); // fixme: this will always allocate memory. Intern? + return dec; +} + + +bool XMLDeclaration::ShallowEqual( const XMLNode* compare ) const +{ + TIXMLASSERT( compare ); + const XMLDeclaration* declaration = compare->ToDeclaration(); + return ( declaration && XMLUtil::StringEqual( declaration->Value(), Value() )); +} + + + +bool XMLDeclaration::Accept( XMLVisitor* visitor ) const +{ + TIXMLASSERT( visitor ); + return visitor->Visit( *this ); +} + +// --------- XMLUnknown ---------- // + +XMLUnknown::XMLUnknown( XMLDocument* doc ) : XMLNode( doc ) +{ +} + + +XMLUnknown::~XMLUnknown() +{ +} + + +char* XMLUnknown::ParseDeep( char* p, StrPair*, int* curLineNumPtr ) +{ + // Unknown parses as text. + p = _value.ParseText( p, ">", StrPair::NEEDS_NEWLINE_NORMALIZATION, curLineNumPtr ); + if ( !p ) { + _document->SetError( XML_ERROR_PARSING_UNKNOWN, _parseLineNum, 0 ); + } + return p; +} + + +XMLNode* XMLUnknown::ShallowClone( XMLDocument* doc ) const +{ + if ( !doc ) { + doc = _document; + } + XMLUnknown* text = doc->NewUnknown( Value() ); // fixme: this will always allocate memory. Intern? + return text; +} + + +bool XMLUnknown::ShallowEqual( const XMLNode* compare ) const +{ + TIXMLASSERT( compare ); + const XMLUnknown* unknown = compare->ToUnknown(); + return ( unknown && XMLUtil::StringEqual( unknown->Value(), Value() )); +} + + +bool XMLUnknown::Accept( XMLVisitor* visitor ) const +{ + TIXMLASSERT( visitor ); + return visitor->Visit( *this ); +} + +// --------- XMLAttribute ---------- // + +const char* XMLAttribute::Name() const +{ + return _name.GetStr(); +} + +const char* XMLAttribute::Value() const +{ + return _value.GetStr(); +} + +char* XMLAttribute::ParseDeep( char* p, bool processEntities, int* curLineNumPtr ) +{ + // Parse using the name rules: bug fix, was using ParseText before + p = _name.ParseName( p ); + if ( !p || !*p ) { + return 0; + } + + // Skip white space before = + p = XMLUtil::SkipWhiteSpace( p, curLineNumPtr ); + if ( *p != '=' ) { + return 0; + } + + ++p; // move up to opening quote + p = XMLUtil::SkipWhiteSpace( p, curLineNumPtr ); + if ( *p != '\"' && *p != '\'' ) { + return 0; + } + + const char endTag[2] = { *p, 0 }; + ++p; // move past opening quote + + p = _value.ParseText( p, endTag, processEntities ? StrPair::ATTRIBUTE_VALUE : StrPair::ATTRIBUTE_VALUE_LEAVE_ENTITIES, curLineNumPtr ); + return p; +} + + +void XMLAttribute::SetName( const char* n ) +{ + _name.SetStr( n ); +} + + +XMLError XMLAttribute::QueryIntValue( int* value ) const +{ + if ( XMLUtil::ToInt( Value(), value )) { + return XML_SUCCESS; + } + return XML_WRONG_ATTRIBUTE_TYPE; +} + + +XMLError XMLAttribute::QueryUnsignedValue( unsigned int* value ) const +{ + if ( XMLUtil::ToUnsigned( Value(), value )) { + return XML_SUCCESS; + } + return XML_WRONG_ATTRIBUTE_TYPE; +} + + +XMLError XMLAttribute::QueryInt64Value(int64_t* value) const +{ + if (XMLUtil::ToInt64(Value(), value)) { + return XML_SUCCESS; + } + return XML_WRONG_ATTRIBUTE_TYPE; +} + + +XMLError XMLAttribute::QueryUnsigned64Value(uint64_t* value) const +{ + if(XMLUtil::ToUnsigned64(Value(), value)) { + return XML_SUCCESS; + } + return XML_WRONG_ATTRIBUTE_TYPE; +} + + +XMLError XMLAttribute::QueryBoolValue( bool* value ) const +{ + if ( XMLUtil::ToBool( Value(), value )) { + return XML_SUCCESS; + } + return XML_WRONG_ATTRIBUTE_TYPE; +} + + +XMLError XMLAttribute::QueryFloatValue( float* value ) const +{ + if ( XMLUtil::ToFloat( Value(), value )) { + return XML_SUCCESS; + } + return XML_WRONG_ATTRIBUTE_TYPE; +} + + +XMLError XMLAttribute::QueryDoubleValue( double* value ) const +{ + if ( XMLUtil::ToDouble( Value(), value )) { + return XML_SUCCESS; + } + return XML_WRONG_ATTRIBUTE_TYPE; +} + + +void XMLAttribute::SetAttribute( const char* v ) +{ + _value.SetStr( v ); +} + + +void XMLAttribute::SetAttribute( int v ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( v, buf, BUF_SIZE ); + _value.SetStr( buf ); +} + + +void XMLAttribute::SetAttribute( unsigned v ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( v, buf, BUF_SIZE ); + _value.SetStr( buf ); +} + + +void XMLAttribute::SetAttribute(int64_t v) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr(v, buf, BUF_SIZE); + _value.SetStr(buf); +} + +void XMLAttribute::SetAttribute(uint64_t v) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr(v, buf, BUF_SIZE); + _value.SetStr(buf); +} + + +void XMLAttribute::SetAttribute( bool v ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( v, buf, BUF_SIZE ); + _value.SetStr( buf ); +} + +void XMLAttribute::SetAttribute( double v ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( v, buf, BUF_SIZE ); + _value.SetStr( buf ); +} + +void XMLAttribute::SetAttribute( float v ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( v, buf, BUF_SIZE ); + _value.SetStr( buf ); +} + + +// --------- XMLElement ---------- // +XMLElement::XMLElement( XMLDocument* doc ) : XMLNode( doc ), + _closingType( OPEN ), + _rootAttribute( 0 ) +{ +} + + +XMLElement::~XMLElement() +{ + while( _rootAttribute ) { + XMLAttribute* next = _rootAttribute->_next; + DeleteAttribute( _rootAttribute ); + _rootAttribute = next; + } +} + + +const XMLAttribute* XMLElement::FindAttribute( const char* name ) const +{ + for( XMLAttribute* a = _rootAttribute; a; a = a->_next ) { + if ( XMLUtil::StringEqual( a->Name(), name ) ) { + return a; + } + } + return 0; +} + + +const char* XMLElement::Attribute( const char* name, const char* value ) const +{ + const XMLAttribute* a = FindAttribute( name ); + if ( !a ) { + return 0; + } + if ( !value || XMLUtil::StringEqual( a->Value(), value )) { + return a->Value(); + } + return 0; +} + +int XMLElement::IntAttribute(const char* name, int defaultValue) const +{ + int i = defaultValue; + QueryIntAttribute(name, &i); + return i; +} + +unsigned XMLElement::UnsignedAttribute(const char* name, unsigned defaultValue) const +{ + unsigned i = defaultValue; + QueryUnsignedAttribute(name, &i); + return i; +} + +int64_t XMLElement::Int64Attribute(const char* name, int64_t defaultValue) const +{ + int64_t i = defaultValue; + QueryInt64Attribute(name, &i); + return i; +} + +uint64_t XMLElement::Unsigned64Attribute(const char* name, uint64_t defaultValue) const +{ + uint64_t i = defaultValue; + QueryUnsigned64Attribute(name, &i); + return i; +} + +bool XMLElement::BoolAttribute(const char* name, bool defaultValue) const +{ + bool b = defaultValue; + QueryBoolAttribute(name, &b); + return b; +} + +double XMLElement::DoubleAttribute(const char* name, double defaultValue) const +{ + double d = defaultValue; + QueryDoubleAttribute(name, &d); + return d; +} + +float XMLElement::FloatAttribute(const char* name, float defaultValue) const +{ + float f = defaultValue; + QueryFloatAttribute(name, &f); + return f; +} + +const char* XMLElement::GetText() const +{ + /* skip comment node */ + const XMLNode* node = FirstChild(); + while (node) { + if (node->ToComment()) { + node = node->NextSibling(); + continue; + } + break; + } + + if ( node && node->ToText() ) { + return node->Value(); + } + return 0; +} + + +void XMLElement::SetText( const char* inText ) +{ + if ( FirstChild() && FirstChild()->ToText() ) + FirstChild()->SetValue( inText ); + else { + XMLText* theText = GetDocument()->NewText( inText ); + InsertFirstChild( theText ); + } +} + + +void XMLElement::SetText( int v ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( v, buf, BUF_SIZE ); + SetText( buf ); +} + + +void XMLElement::SetText( unsigned v ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( v, buf, BUF_SIZE ); + SetText( buf ); +} + + +void XMLElement::SetText(int64_t v) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr(v, buf, BUF_SIZE); + SetText(buf); +} + +void XMLElement::SetText(uint64_t v) { + char buf[BUF_SIZE]; + XMLUtil::ToStr(v, buf, BUF_SIZE); + SetText(buf); +} + + +void XMLElement::SetText( bool v ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( v, buf, BUF_SIZE ); + SetText( buf ); +} + + +void XMLElement::SetText( float v ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( v, buf, BUF_SIZE ); + SetText( buf ); +} + + +void XMLElement::SetText( double v ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( v, buf, BUF_SIZE ); + SetText( buf ); +} + + +XMLError XMLElement::QueryIntText( int* ival ) const +{ + if ( FirstChild() && FirstChild()->ToText() ) { + const char* t = FirstChild()->Value(); + if ( XMLUtil::ToInt( t, ival ) ) { + return XML_SUCCESS; + } + return XML_CAN_NOT_CONVERT_TEXT; + } + return XML_NO_TEXT_NODE; +} + + +XMLError XMLElement::QueryUnsignedText( unsigned* uval ) const +{ + if ( FirstChild() && FirstChild()->ToText() ) { + const char* t = FirstChild()->Value(); + if ( XMLUtil::ToUnsigned( t, uval ) ) { + return XML_SUCCESS; + } + return XML_CAN_NOT_CONVERT_TEXT; + } + return XML_NO_TEXT_NODE; +} + + +XMLError XMLElement::QueryInt64Text(int64_t* ival) const +{ + if (FirstChild() && FirstChild()->ToText()) { + const char* t = FirstChild()->Value(); + if (XMLUtil::ToInt64(t, ival)) { + return XML_SUCCESS; + } + return XML_CAN_NOT_CONVERT_TEXT; + } + return XML_NO_TEXT_NODE; +} + + +XMLError XMLElement::QueryUnsigned64Text(uint64_t* ival) const +{ + if(FirstChild() && FirstChild()->ToText()) { + const char* t = FirstChild()->Value(); + if(XMLUtil::ToUnsigned64(t, ival)) { + return XML_SUCCESS; + } + return XML_CAN_NOT_CONVERT_TEXT; + } + return XML_NO_TEXT_NODE; +} + + +XMLError XMLElement::QueryBoolText( bool* bval ) const +{ + if ( FirstChild() && FirstChild()->ToText() ) { + const char* t = FirstChild()->Value(); + if ( XMLUtil::ToBool( t, bval ) ) { + return XML_SUCCESS; + } + return XML_CAN_NOT_CONVERT_TEXT; + } + return XML_NO_TEXT_NODE; +} + + +XMLError XMLElement::QueryDoubleText( double* dval ) const +{ + if ( FirstChild() && FirstChild()->ToText() ) { + const char* t = FirstChild()->Value(); + if ( XMLUtil::ToDouble( t, dval ) ) { + return XML_SUCCESS; + } + return XML_CAN_NOT_CONVERT_TEXT; + } + return XML_NO_TEXT_NODE; +} + + +XMLError XMLElement::QueryFloatText( float* fval ) const +{ + if ( FirstChild() && FirstChild()->ToText() ) { + const char* t = FirstChild()->Value(); + if ( XMLUtil::ToFloat( t, fval ) ) { + return XML_SUCCESS; + } + return XML_CAN_NOT_CONVERT_TEXT; + } + return XML_NO_TEXT_NODE; +} + +int XMLElement::IntText(int defaultValue) const +{ + int i = defaultValue; + QueryIntText(&i); + return i; +} + +unsigned XMLElement::UnsignedText(unsigned defaultValue) const +{ + unsigned i = defaultValue; + QueryUnsignedText(&i); + return i; +} + +int64_t XMLElement::Int64Text(int64_t defaultValue) const +{ + int64_t i = defaultValue; + QueryInt64Text(&i); + return i; +} + +uint64_t XMLElement::Unsigned64Text(uint64_t defaultValue) const +{ + uint64_t i = defaultValue; + QueryUnsigned64Text(&i); + return i; +} + +bool XMLElement::BoolText(bool defaultValue) const +{ + bool b = defaultValue; + QueryBoolText(&b); + return b; +} + +double XMLElement::DoubleText(double defaultValue) const +{ + double d = defaultValue; + QueryDoubleText(&d); + return d; +} + +float XMLElement::FloatText(float defaultValue) const +{ + float f = defaultValue; + QueryFloatText(&f); + return f; +} + + +XMLAttribute* XMLElement::FindOrCreateAttribute( const char* name ) +{ + XMLAttribute* last = 0; + XMLAttribute* attrib = 0; + for( attrib = _rootAttribute; + attrib; + last = attrib, attrib = attrib->_next ) { + if ( XMLUtil::StringEqual( attrib->Name(), name ) ) { + break; + } + } + if ( !attrib ) { + attrib = CreateAttribute(); + TIXMLASSERT( attrib ); + if ( last ) { + TIXMLASSERT( last->_next == 0 ); + last->_next = attrib; + } + else { + TIXMLASSERT( _rootAttribute == 0 ); + _rootAttribute = attrib; + } + attrib->SetName( name ); + } + return attrib; +} + + +void XMLElement::DeleteAttribute( const char* name ) +{ + XMLAttribute* prev = 0; + for( XMLAttribute* a=_rootAttribute; a; a=a->_next ) { + if ( XMLUtil::StringEqual( name, a->Name() ) ) { + if ( prev ) { + prev->_next = a->_next; + } + else { + _rootAttribute = a->_next; + } + DeleteAttribute( a ); + break; + } + prev = a; + } +} + + +char* XMLElement::ParseAttributes( char* p, int* curLineNumPtr ) +{ + XMLAttribute* prevAttribute = 0; + + // Read the attributes. + while( p ) { + p = XMLUtil::SkipWhiteSpace( p, curLineNumPtr ); + if ( !(*p) ) { + _document->SetError( XML_ERROR_PARSING_ELEMENT, _parseLineNum, "XMLElement name=%s", Name() ); + return 0; + } + + // attribute. + if (XMLUtil::IsNameStartChar( (unsigned char) *p ) ) { + XMLAttribute* attrib = CreateAttribute(); + TIXMLASSERT( attrib ); + attrib->_parseLineNum = _document->_parseCurLineNum; + + const int attrLineNum = attrib->_parseLineNum; + + p = attrib->ParseDeep( p, _document->ProcessEntities(), curLineNumPtr ); + if ( !p || Attribute( attrib->Name() ) ) { + DeleteAttribute( attrib ); + _document->SetError( XML_ERROR_PARSING_ATTRIBUTE, attrLineNum, "XMLElement name=%s", Name() ); + return 0; + } + // There is a minor bug here: if the attribute in the source xml + // document is duplicated, it will not be detected and the + // attribute will be doubly added. However, tracking the 'prevAttribute' + // avoids re-scanning the attribute list. Preferring performance for + // now, may reconsider in the future. + if ( prevAttribute ) { + TIXMLASSERT( prevAttribute->_next == 0 ); + prevAttribute->_next = attrib; + } + else { + TIXMLASSERT( _rootAttribute == 0 ); + _rootAttribute = attrib; + } + prevAttribute = attrib; + } + // end of the tag + else if ( *p == '>' ) { + ++p; + break; + } + // end of the tag + else if ( *p == '/' && *(p+1) == '>' ) { + _closingType = CLOSED; + return p+2; // done; sealed element. + } + else { + _document->SetError( XML_ERROR_PARSING_ELEMENT, _parseLineNum, 0 ); + return 0; + } + } + return p; +} + +void XMLElement::DeleteAttribute( XMLAttribute* attribute ) +{ + if ( attribute == 0 ) { + return; + } + MemPool* pool = attribute->_memPool; + attribute->~XMLAttribute(); + pool->Free( attribute ); +} + +XMLAttribute* XMLElement::CreateAttribute() +{ + TIXMLASSERT( sizeof( XMLAttribute ) == _document->_attributePool.ItemSize() ); + XMLAttribute* attrib = new (_document->_attributePool.Alloc() ) XMLAttribute(); + TIXMLASSERT( attrib ); + attrib->_memPool = &_document->_attributePool; + attrib->_memPool->SetTracked(); + return attrib; +} + + +XMLElement* XMLElement::InsertNewChildElement(const char* name) +{ + XMLElement* node = _document->NewElement(name); + return InsertEndChild(node) ? node : 0; +} + +XMLComment* XMLElement::InsertNewComment(const char* comment) +{ + XMLComment* node = _document->NewComment(comment); + return InsertEndChild(node) ? node : 0; +} + +XMLText* XMLElement::InsertNewText(const char* text) +{ + XMLText* node = _document->NewText(text); + return InsertEndChild(node) ? node : 0; +} + +XMLDeclaration* XMLElement::InsertNewDeclaration(const char* text) +{ + XMLDeclaration* node = _document->NewDeclaration(text); + return InsertEndChild(node) ? node : 0; +} + +XMLUnknown* XMLElement::InsertNewUnknown(const char* text) +{ + XMLUnknown* node = _document->NewUnknown(text); + return InsertEndChild(node) ? node : 0; +} + + + +// +// +// foobar +// +char* XMLElement::ParseDeep( char* p, StrPair* parentEndTag, int* curLineNumPtr ) +{ + // Read the element name. + p = XMLUtil::SkipWhiteSpace( p, curLineNumPtr ); + + // The closing element is the form. It is + // parsed just like a regular element then deleted from + // the DOM. + if ( *p == '/' ) { + _closingType = CLOSING; + ++p; + } + + p = _value.ParseName( p ); + if ( _value.Empty() ) { + return 0; + } + + p = ParseAttributes( p, curLineNumPtr ); + if ( !p || !*p || _closingType != OPEN ) { + return p; + } + + p = XMLNode::ParseDeep( p, parentEndTag, curLineNumPtr ); + return p; +} + + + +XMLNode* XMLElement::ShallowClone( XMLDocument* doc ) const +{ + if ( !doc ) { + doc = _document; + } + XMLElement* element = doc->NewElement( Value() ); // fixme: this will always allocate memory. Intern? + for( const XMLAttribute* a=FirstAttribute(); a; a=a->Next() ) { + element->SetAttribute( a->Name(), a->Value() ); // fixme: this will always allocate memory. Intern? + } + return element; +} + + +bool XMLElement::ShallowEqual( const XMLNode* compare ) const +{ + TIXMLASSERT( compare ); + const XMLElement* other = compare->ToElement(); + if ( other && XMLUtil::StringEqual( other->Name(), Name() )) { + + const XMLAttribute* a=FirstAttribute(); + const XMLAttribute* b=other->FirstAttribute(); + + while ( a && b ) { + if ( !XMLUtil::StringEqual( a->Value(), b->Value() ) ) { + return false; + } + a = a->Next(); + b = b->Next(); + } + if ( a || b ) { + // different count + return false; + } + return true; + } + return false; +} + + +bool XMLElement::Accept( XMLVisitor* visitor ) const +{ + TIXMLASSERT( visitor ); + if ( visitor->VisitEnter( *this, _rootAttribute ) ) { + for ( const XMLNode* node=FirstChild(); node; node=node->NextSibling() ) { + if ( !node->Accept( visitor ) ) { + break; + } + } + } + return visitor->VisitExit( *this ); +} + + +// --------- XMLDocument ----------- // + +// Warning: List must match 'enum XMLError' +const char* XMLDocument::_errorNames[XML_ERROR_COUNT] = { + "XML_SUCCESS", + "XML_NO_ATTRIBUTE", + "XML_WRONG_ATTRIBUTE_TYPE", + "XML_ERROR_FILE_NOT_FOUND", + "XML_ERROR_FILE_COULD_NOT_BE_OPENED", + "XML_ERROR_FILE_READ_ERROR", + "XML_ERROR_PARSING_ELEMENT", + "XML_ERROR_PARSING_ATTRIBUTE", + "XML_ERROR_PARSING_TEXT", + "XML_ERROR_PARSING_CDATA", + "XML_ERROR_PARSING_COMMENT", + "XML_ERROR_PARSING_DECLARATION", + "XML_ERROR_PARSING_UNKNOWN", + "XML_ERROR_EMPTY_DOCUMENT", + "XML_ERROR_MISMATCHED_ELEMENT", + "XML_ERROR_PARSING", + "XML_CAN_NOT_CONVERT_TEXT", + "XML_NO_TEXT_NODE", + "XML_ELEMENT_DEPTH_EXCEEDED" +}; + + +XMLDocument::XMLDocument( bool processEntities, Whitespace whitespaceMode ) : + XMLNode( 0 ), + _writeBOM( false ), + _processEntities( processEntities ), + _errorID(XML_SUCCESS), + _whitespaceMode( whitespaceMode ), + _errorStr(), + _errorLineNum( 0 ), + _charBuffer( 0 ), + _parseCurLineNum( 0 ), + _parsingDepth(0), + _unlinked(), + _elementPool(), + _attributePool(), + _textPool(), + _commentPool() +{ + // avoid VC++ C4355 warning about 'this' in initializer list (C4355 is off by default in VS2012+) + _document = this; +} + + +XMLDocument::~XMLDocument() +{ + Clear(); +} + + +void XMLDocument::MarkInUse(const XMLNode* const node) +{ + TIXMLASSERT(node); + TIXMLASSERT(node->_parent == 0); + + for (int i = 0; i < _unlinked.Size(); ++i) { + if (node == _unlinked[i]) { + _unlinked.SwapRemove(i); + break; + } + } +} + +void XMLDocument::Clear() +{ + DeleteChildren(); + while( _unlinked.Size()) { + DeleteNode(_unlinked[0]); // Will remove from _unlinked as part of delete. + } + +#ifdef TINYXML2_DEBUG + const bool hadError = Error(); +#endif + ClearError(); + + delete [] _charBuffer; + _charBuffer = 0; + _parsingDepth = 0; + +#if 0 + _textPool.Trace( "text" ); + _elementPool.Trace( "element" ); + _commentPool.Trace( "comment" ); + _attributePool.Trace( "attribute" ); +#endif + +#ifdef TINYXML2_DEBUG + if ( !hadError ) { + TIXMLASSERT( _elementPool.CurrentAllocs() == _elementPool.Untracked() ); + TIXMLASSERT( _attributePool.CurrentAllocs() == _attributePool.Untracked() ); + TIXMLASSERT( _textPool.CurrentAllocs() == _textPool.Untracked() ); + TIXMLASSERT( _commentPool.CurrentAllocs() == _commentPool.Untracked() ); + } +#endif +} + + +void XMLDocument::DeepCopy(XMLDocument* target) const +{ + TIXMLASSERT(target); + if (target == this) { + return; // technically success - a no-op. + } + + target->Clear(); + for (const XMLNode* node = this->FirstChild(); node; node = node->NextSibling()) { + target->InsertEndChild(node->DeepClone(target)); + } +} + +XMLElement* XMLDocument::NewElement( const char* name ) +{ + XMLElement* ele = CreateUnlinkedNode( _elementPool ); + ele->SetName( name ); + return ele; +} + + +XMLComment* XMLDocument::NewComment( const char* str ) +{ + XMLComment* comment = CreateUnlinkedNode( _commentPool ); + comment->SetValue( str ); + return comment; +} + + +XMLText* XMLDocument::NewText( const char* str ) +{ + XMLText* text = CreateUnlinkedNode( _textPool ); + text->SetValue( str ); + return text; +} + + +XMLDeclaration* XMLDocument::NewDeclaration( const char* str ) +{ + XMLDeclaration* dec = CreateUnlinkedNode( _commentPool ); + dec->SetValue( str ? str : "xml version=\"1.0\" encoding=\"UTF-8\"" ); + return dec; +} + + +XMLUnknown* XMLDocument::NewUnknown( const char* str ) +{ + XMLUnknown* unk = CreateUnlinkedNode( _commentPool ); + unk->SetValue( str ); + return unk; +} + +static FILE* callfopen( const char* filepath, const char* mode ) +{ + TIXMLASSERT( filepath ); + TIXMLASSERT( mode ); +#if defined(_MSC_VER) && (_MSC_VER >= 1400 ) && (!defined WINCE) + FILE* fp = 0; + const errno_t err = fopen_s( &fp, filepath, mode ); + if ( err ) { + return 0; + } +#else + FILE* fp = fopen( filepath, mode ); +#endif + return fp; +} + +void XMLDocument::DeleteNode( XMLNode* node ) { + TIXMLASSERT( node ); + TIXMLASSERT(node->_document == this ); + if (node->_parent) { + node->_parent->DeleteChild( node ); + } + else { + // Isn't in the tree. + // Use the parent delete. + // Also, we need to mark it tracked: we 'know' + // it was never used. + node->_memPool->SetTracked(); + // Call the static XMLNode version: + XMLNode::DeleteNode(node); + } +} + + +XMLError XMLDocument::LoadFile( const char* filename ) +{ + if ( !filename ) { + TIXMLASSERT( false ); + SetError( XML_ERROR_FILE_COULD_NOT_BE_OPENED, 0, "filename=" ); + return _errorID; + } + + Clear(); + FILE* fp = callfopen( filename, "rb" ); + if ( !fp ) { + SetError( XML_ERROR_FILE_NOT_FOUND, 0, "filename=%s", filename ); + return _errorID; + } + LoadFile( fp ); + fclose( fp ); + return _errorID; +} + +XMLError XMLDocument::LoadFile( FILE* fp ) +{ + Clear(); + + TIXML_FSEEK( fp, 0, SEEK_SET ); + if ( fgetc( fp ) == EOF && ferror( fp ) != 0 ) { + SetError( XML_ERROR_FILE_READ_ERROR, 0, 0 ); + return _errorID; + } + + TIXML_FSEEK( fp, 0, SEEK_END ); + + unsigned long long filelength; + { + const long long fileLengthSigned = TIXML_FTELL( fp ); + TIXML_FSEEK( fp, 0, SEEK_SET ); + if ( fileLengthSigned == -1L ) { + SetError( XML_ERROR_FILE_READ_ERROR, 0, 0 ); + return _errorID; + } + TIXMLASSERT( fileLengthSigned >= 0 ); + filelength = static_cast(fileLengthSigned); + } + + const size_t maxSizeT = static_cast(-1); + // We'll do the comparison as an unsigned long long, because that's guaranteed to be at + // least 8 bytes, even on a 32-bit platform. + if ( filelength >= static_cast(maxSizeT) ) { + // Cannot handle files which won't fit in buffer together with null terminator + SetError( XML_ERROR_FILE_READ_ERROR, 0, 0 ); + return _errorID; + } + + if ( filelength == 0 ) { + SetError( XML_ERROR_EMPTY_DOCUMENT, 0, 0 ); + return _errorID; + } + + const size_t size = static_cast(filelength); + TIXMLASSERT( _charBuffer == 0 ); + _charBuffer = new char[size+1]; + const size_t read = fread( _charBuffer, 1, size, fp ); + if ( read != size ) { + SetError( XML_ERROR_FILE_READ_ERROR, 0, 0 ); + return _errorID; + } + + _charBuffer[size] = 0; + + Parse(); + return _errorID; +} + + +XMLError XMLDocument::SaveFile( const char* filename, bool compact ) +{ + if ( !filename ) { + TIXMLASSERT( false ); + SetError( XML_ERROR_FILE_COULD_NOT_BE_OPENED, 0, "filename=" ); + return _errorID; + } + + FILE* fp = callfopen( filename, "w" ); + if ( !fp ) { + SetError( XML_ERROR_FILE_COULD_NOT_BE_OPENED, 0, "filename=%s", filename ); + return _errorID; + } + SaveFile(fp, compact); + fclose( fp ); + return _errorID; +} + + +XMLError XMLDocument::SaveFile( FILE* fp, bool compact ) +{ + // Clear any error from the last save, otherwise it will get reported + // for *this* call. + ClearError(); + XMLPrinter stream( fp, compact ); + Print( &stream ); + return _errorID; +} + + +XMLError XMLDocument::Parse( const char* p, size_t len ) +{ + Clear(); + + if ( len == 0 || !p || !*p ) { + SetError( XML_ERROR_EMPTY_DOCUMENT, 0, 0 ); + return _errorID; + } + if ( len == static_cast(-1) ) { + len = strlen( p ); + } + TIXMLASSERT( _charBuffer == 0 ); + _charBuffer = new char[ len+1 ]; + memcpy( _charBuffer, p, len ); + _charBuffer[len] = 0; + + Parse(); + if ( Error() ) { + // clean up now essentially dangling memory. + // and the parse fail can put objects in the + // pools that are dead and inaccessible. + DeleteChildren(); + _elementPool.Clear(); + _attributePool.Clear(); + _textPool.Clear(); + _commentPool.Clear(); + } + return _errorID; +} + + +void XMLDocument::Print( XMLPrinter* streamer ) const +{ + if ( streamer ) { + Accept( streamer ); + } + else { + XMLPrinter stdoutStreamer( stdout ); + Accept( &stdoutStreamer ); + } +} + + +void XMLDocument::ClearError() { + _errorID = XML_SUCCESS; + _errorLineNum = 0; + _errorStr.Reset(); +} + + +void XMLDocument::SetError( XMLError error, int lineNum, const char* format, ... ) +{ + TIXMLASSERT( error >= 0 && error < XML_ERROR_COUNT ); + _errorID = error; + _errorLineNum = lineNum; + _errorStr.Reset(); + + const size_t BUFFER_SIZE = 1000; + char* buffer = new char[BUFFER_SIZE]; + + TIXMLASSERT(sizeof(error) <= sizeof(int)); + TIXML_SNPRINTF(buffer, BUFFER_SIZE, "Error=%s ErrorID=%d (0x%x) Line number=%d", ErrorIDToName(error), int(error), int(error), lineNum); + + if (format) { + size_t len = strlen(buffer); + TIXML_SNPRINTF(buffer + len, BUFFER_SIZE - len, ": "); + len = strlen(buffer); + + va_list va; + va_start(va, format); + TIXML_VSNPRINTF(buffer + len, BUFFER_SIZE - len, format, va); + va_end(va); + } + _errorStr.SetStr(buffer); + delete[] buffer; +} + + +/*static*/ const char* XMLDocument::ErrorIDToName(XMLError errorID) +{ + TIXMLASSERT( errorID >= 0 && errorID < XML_ERROR_COUNT ); + const char* errorName = _errorNames[errorID]; + TIXMLASSERT( errorName && errorName[0] ); + return errorName; +} + +const char* XMLDocument::ErrorStr() const +{ + return _errorStr.Empty() ? "" : _errorStr.GetStr(); +} + + +void XMLDocument::PrintError() const +{ + printf("%s\n", ErrorStr()); +} + +const char* XMLDocument::ErrorName() const +{ + return ErrorIDToName(_errorID); +} + +void XMLDocument::Parse() +{ + TIXMLASSERT( NoChildren() ); // Clear() must have been called previously + TIXMLASSERT( _charBuffer ); + _parseCurLineNum = 1; + _parseLineNum = 1; + char* p = _charBuffer; + p = XMLUtil::SkipWhiteSpace( p, &_parseCurLineNum ); + p = const_cast( XMLUtil::ReadBOM( p, &_writeBOM ) ); + if ( !*p ) { + SetError( XML_ERROR_EMPTY_DOCUMENT, 0, 0 ); + return; + } + ParseDeep(p, 0, &_parseCurLineNum ); +} + +void XMLDocument::PushDepth() +{ + _parsingDepth++; + if (_parsingDepth == TINYXML2_MAX_ELEMENT_DEPTH) { + SetError(XML_ELEMENT_DEPTH_EXCEEDED, _parseCurLineNum, "Element nesting is too deep." ); + } +} + +void XMLDocument::PopDepth() +{ + TIXMLASSERT(_parsingDepth > 0); + --_parsingDepth; +} + +XMLPrinter::XMLPrinter( FILE* file, bool compact, int depth ) : + _elementJustOpened( false ), + _stack(), + _firstElement( true ), + _fp( file ), + _depth( depth ), + _textDepth( -1 ), + _processEntities( true ), + _compactMode( compact ), + _buffer() +{ + for( int i=0; i(entityValue); + TIXMLASSERT( flagIndex < ENTITY_RANGE ); + _entityFlag[flagIndex] = true; + } + _restrictedEntityFlag[static_cast('&')] = true; + _restrictedEntityFlag[static_cast('<')] = true; + _restrictedEntityFlag[static_cast('>')] = true; // not required, but consistency is nice + _buffer.Push( 0 ); +} + + +void XMLPrinter::Print( const char* format, ... ) +{ + va_list va; + va_start( va, format ); + + if ( _fp ) { + vfprintf( _fp, format, va ); + } + else { + const int len = TIXML_VSCPRINTF( format, va ); + // Close out and re-start the va-args + va_end( va ); + TIXMLASSERT( len >= 0 ); + va_start( va, format ); + TIXMLASSERT( _buffer.Size() > 0 && _buffer[_buffer.Size() - 1] == 0 ); + char* p = _buffer.PushArr( len ) - 1; // back up over the null terminator. + TIXML_VSNPRINTF( p, len+1, format, va ); + } + va_end( va ); +} + + +void XMLPrinter::Write( const char* data, size_t size ) +{ + if ( _fp ) { + fwrite ( data , sizeof(char), size, _fp); + } + else { + char* p = _buffer.PushArr( static_cast(size) ) - 1; // back up over the null terminator. + memcpy( p, data, size ); + p[size] = 0; + } +} + + +void XMLPrinter::Putc( char ch ) +{ + if ( _fp ) { + fputc ( ch, _fp); + } + else { + char* p = _buffer.PushArr( sizeof(char) ) - 1; // back up over the null terminator. + p[0] = ch; + p[1] = 0; + } +} + + +void XMLPrinter::PrintSpace( int depth ) +{ + for( int i=0; i 0 && *q < ENTITY_RANGE ) { + // Check for entities. If one is found, flush + // the stream up until the entity, write the + // entity, and keep looking. + if ( flag[static_cast(*q)] ) { + while ( p < q ) { + const size_t delta = q - p; + const int toPrint = ( INT_MAX < delta ) ? INT_MAX : static_cast(delta); + Write( p, toPrint ); + p += toPrint; + } + bool entityPatternPrinted = false; + for( int i=0; i(delta); + Write( p, toPrint ); + } + } + else { + Write( p ); + } +} + + +void XMLPrinter::PushHeader( bool writeBOM, bool writeDec ) +{ + if ( writeBOM ) { + static const unsigned char bom[] = { TIXML_UTF_LEAD_0, TIXML_UTF_LEAD_1, TIXML_UTF_LEAD_2, 0 }; + Write( reinterpret_cast< const char* >( bom ) ); + } + if ( writeDec ) { + PushDeclaration( "xml version=\"1.0\"" ); + } +} + +void XMLPrinter::PrepareForNewNode( bool compactMode ) +{ + SealElementIfJustOpened(); + + if ( compactMode ) { + return; + } + + if ( _firstElement ) { + PrintSpace (_depth); + } else if ( _textDepth < 0) { + Putc( '\n' ); + PrintSpace( _depth ); + } + + _firstElement = false; +} + +void XMLPrinter::OpenElement( const char* name, bool compactMode ) +{ + PrepareForNewNode( compactMode ); + _stack.Push( name ); + + Write ( "<" ); + Write ( name ); + + _elementJustOpened = true; + ++_depth; +} + + +void XMLPrinter::PushAttribute( const char* name, const char* value ) +{ + TIXMLASSERT( _elementJustOpened ); + Putc ( ' ' ); + Write( name ); + Write( "=\"" ); + PrintString( value, false ); + Putc ( '\"' ); +} + + +void XMLPrinter::PushAttribute( const char* name, int v ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( v, buf, BUF_SIZE ); + PushAttribute( name, buf ); +} + + +void XMLPrinter::PushAttribute( const char* name, unsigned v ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( v, buf, BUF_SIZE ); + PushAttribute( name, buf ); +} + + +void XMLPrinter::PushAttribute(const char* name, int64_t v) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr(v, buf, BUF_SIZE); + PushAttribute(name, buf); +} + + +void XMLPrinter::PushAttribute(const char* name, uint64_t v) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr(v, buf, BUF_SIZE); + PushAttribute(name, buf); +} + + +void XMLPrinter::PushAttribute( const char* name, bool v ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( v, buf, BUF_SIZE ); + PushAttribute( name, buf ); +} + + +void XMLPrinter::PushAttribute( const char* name, double v ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( v, buf, BUF_SIZE ); + PushAttribute( name, buf ); +} + + +void XMLPrinter::CloseElement( bool compactMode ) +{ + --_depth; + const char* name = _stack.Pop(); + + if ( _elementJustOpened ) { + Write( "/>" ); + } + else { + if ( _textDepth < 0 && !compactMode) { + Putc( '\n' ); + PrintSpace( _depth ); + } + Write ( "" ); + } + + if ( _textDepth == _depth ) { + _textDepth = -1; + } + if ( _depth == 0 && !compactMode) { + Putc( '\n' ); + } + _elementJustOpened = false; +} + + +void XMLPrinter::SealElementIfJustOpened() +{ + if ( !_elementJustOpened ) { + return; + } + _elementJustOpened = false; + Putc( '>' ); +} + + +void XMLPrinter::PushText( const char* text, bool cdata ) +{ + _textDepth = _depth-1; + + SealElementIfJustOpened(); + if ( cdata ) { + Write( "" ); + } + else { + PrintString( text, true ); + } +} + + +void XMLPrinter::PushText( int64_t value ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( value, buf, BUF_SIZE ); + PushText( buf, false ); +} + + +void XMLPrinter::PushText( uint64_t value ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr(value, buf, BUF_SIZE); + PushText(buf, false); +} + + +void XMLPrinter::PushText( int value ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( value, buf, BUF_SIZE ); + PushText( buf, false ); +} + + +void XMLPrinter::PushText( unsigned value ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( value, buf, BUF_SIZE ); + PushText( buf, false ); +} + + +void XMLPrinter::PushText( bool value ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( value, buf, BUF_SIZE ); + PushText( buf, false ); +} + + +void XMLPrinter::PushText( float value ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( value, buf, BUF_SIZE ); + PushText( buf, false ); +} + + +void XMLPrinter::PushText( double value ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( value, buf, BUF_SIZE ); + PushText( buf, false ); +} + + +void XMLPrinter::PushComment( const char* comment ) +{ + PrepareForNewNode( _compactMode ); + + Write( "" ); +} + + +void XMLPrinter::PushDeclaration( const char* value ) +{ + PrepareForNewNode( _compactMode ); + + Write( "" ); +} + + +void XMLPrinter::PushUnknown( const char* value ) +{ + PrepareForNewNode( _compactMode ); + + Write( "' ); +} + + +bool XMLPrinter::VisitEnter( const XMLDocument& doc ) +{ + _processEntities = doc.ProcessEntities(); + if ( doc.HasBOM() ) { + PushHeader( true, false ); + } + return true; +} + + +bool XMLPrinter::VisitEnter( const XMLElement& element, const XMLAttribute* attribute ) +{ + const XMLElement* parentElem = 0; + if ( element.Parent() ) { + parentElem = element.Parent()->ToElement(); + } + const bool compactMode = parentElem ? CompactMode( *parentElem ) : _compactMode; + OpenElement( element.Name(), compactMode ); + while ( attribute ) { + PushAttribute( attribute->Name(), attribute->Value() ); + attribute = attribute->Next(); + } + return true; +} + + +bool XMLPrinter::VisitExit( const XMLElement& element ) +{ + CloseElement( CompactMode(element) ); + return true; +} + + +bool XMLPrinter::Visit( const XMLText& text ) +{ + PushText( text.Value(), text.CData() ); + return true; +} + + +bool XMLPrinter::Visit( const XMLComment& comment ) +{ + PushComment( comment.Value() ); + return true; +} + +bool XMLPrinter::Visit( const XMLDeclaration& declaration ) +{ + PushDeclaration( declaration.Value() ); + return true; +} + + +bool XMLPrinter::Visit( const XMLUnknown& unknown ) +{ + PushUnknown( unknown.Value() ); + return true; +} + +} // namespace tinyxml2 diff --git a/libs/IO/TinyXML2.h b/libs/IO/TinyXML2.h new file mode 100644 index 000000000..ae237adde --- /dev/null +++ b/libs/IO/TinyXML2.h @@ -0,0 +1,2368 @@ +/* +Original code by Lee Thomason (www.grinninglizard.com) + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must +not claim that you wrote the original software. If you use this +software in a product, an acknowledgment in the product documentation +would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and +must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source +distribution. +*/ + +#ifndef TINYXML2_INCLUDED +#define TINYXML2_INCLUDED + +#if defined(ANDROID_NDK) || defined(__BORLANDC__) || defined(__QNXNTO__) +# include +# include +# include +# include +# include +# if defined(__PS3__) +# include +# endif +#else +# include +# include +# include +# include +# include +#endif +#include + +/* + TODO: intern strings instead of allocation. +*/ +/* + gcc: + g++ -Wall -DTINYXML2_DEBUG tinyxml2.cpp xmltest.cpp -o gccxmltest.exe + + Formatting, Artistic Style: + AStyle.exe --style=1tbs --indent-switches --break-closing-brackets --indent-preprocessor tinyxml2.cpp tinyxml2.h +*/ + +#if defined( _DEBUG ) || defined (__DEBUG__) +# ifndef TINYXML2_DEBUG +# define TINYXML2_DEBUG +# endif +#endif + +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable: 4251) +#endif + +#ifndef TINYXML2_LIB +#define TINYXML2_LIB IO_API +#endif + +#if !defined(TIXMLASSERT) +#if defined(TINYXML2_DEBUG) +# if defined(_MSC_VER) +# define TIXMLASSERT ASSERT +# elif defined (ANDROID_NDK) +# include +# define TIXMLASSERT( x ) if ( !(x)) { __android_log_assert( "assert", "grinliz", "ASSERT in '%s' at %d.", __FILE__, __LINE__ ); } +# else +# include +# define TIXMLASSERT ASSERT +# endif +#else +# define TIXMLASSERT( x ) {} +#endif +#endif + +/* Versioning, past 1.0.14: + http://semver.org/ +*/ +static const int TIXML2_MAJOR_VERSION = 9; +static const int TIXML2_MINOR_VERSION = 0; +static const int TIXML2_PATCH_VERSION = 0; + +#define TINYXML2_MAJOR_VERSION 9 +#define TINYXML2_MINOR_VERSION 0 +#define TINYXML2_PATCH_VERSION 0 + +// A fixed element depth limit is problematic. There needs to be a +// limit to avoid a stack overflow. However, that limit varies per +// system, and the capacity of the stack. On the other hand, it's a trivial +// attack that can result from ill, malicious, or even correctly formed XML, +// so there needs to be a limit in place. +static const int TINYXML2_MAX_ELEMENT_DEPTH = 100; + +namespace tinyxml2 +{ +class XMLDocument; +class XMLElement; +class XMLAttribute; +class XMLComment; +class XMLText; +class XMLDeclaration; +class XMLUnknown; +class XMLPrinter; + +/* + A class that wraps strings. Normally stores the start and end + pointers into the XML file itself, and will apply normalization + and entity translation if actually read. Can also store (and memory + manage) a traditional char[] + + Isn't clear why TINYXML2_LIB is needed; but seems to fix #719 +*/ +class TINYXML2_LIB StrPair +{ +public: + enum Mode { + NEEDS_ENTITY_PROCESSING = 0x01, + NEEDS_NEWLINE_NORMALIZATION = 0x02, + NEEDS_WHITESPACE_COLLAPSING = 0x04, + + TEXT_ELEMENT = NEEDS_ENTITY_PROCESSING | NEEDS_NEWLINE_NORMALIZATION, + TEXT_ELEMENT_LEAVE_ENTITIES = NEEDS_NEWLINE_NORMALIZATION, + ATTRIBUTE_NAME = 0, + ATTRIBUTE_VALUE = NEEDS_ENTITY_PROCESSING | NEEDS_NEWLINE_NORMALIZATION, + ATTRIBUTE_VALUE_LEAVE_ENTITIES = NEEDS_NEWLINE_NORMALIZATION, + COMMENT = NEEDS_NEWLINE_NORMALIZATION + }; + + StrPair() : _flags( 0 ), _start( 0 ), _end( 0 ) {} + ~StrPair(); + + void Set( char* start, char* end, int flags ) { + TIXMLASSERT( start ); + TIXMLASSERT( end ); + Reset(); + _start = start; + _end = end; + _flags = flags | NEEDS_FLUSH; + } + + const char* GetStr(); + + bool Empty() const { + return _start == _end; + } + + void SetInternedStr( const char* str ) { + Reset(); + _start = const_cast(str); + } + + void SetStr( const char* str, int flags=0 ); + + char* ParseText( char* in, const char* endTag, int strFlags, int* curLineNumPtr ); + char* ParseName( char* in ); + + void TransferTo( StrPair* other ); + void Reset(); + +private: + void CollapseWhitespace(); + + enum { + NEEDS_FLUSH = 0x100, + NEEDS_DELETE = 0x200 + }; + + int _flags; + char* _start; + char* _end; + + StrPair( const StrPair& other ); // not supported + void operator=( const StrPair& other ); // not supported, use TransferTo() +}; + + +/* + A dynamic array of Plain Old Data. Doesn't support constructors, etc. + Has a small initial memory pool, so that low or no usage will not + cause a call to new/delete +*/ +template +class DynArray +{ +public: + DynArray() : + _mem( _pool ), + _allocated( INITIAL_SIZE ), + _size( 0 ) + { + } + + ~DynArray() { + if ( _mem != _pool ) { + delete [] _mem; + } + } + + void Clear() { + _size = 0; + } + + void Push( T t ) { + TIXMLASSERT( _size < INT_MAX ); + EnsureCapacity( _size+1 ); + _mem[_size] = t; + ++_size; + } + + T* PushArr( int count ) { + TIXMLASSERT( count >= 0 ); + TIXMLASSERT( _size <= INT_MAX - count ); + EnsureCapacity( _size+count ); + T* ret = &_mem[_size]; + _size += count; + return ret; + } + + T Pop() { + TIXMLASSERT( _size > 0 ); + --_size; + return _mem[_size]; + } + + void PopArr( int count ) { + TIXMLASSERT( _size >= count ); + _size -= count; + } + + bool Empty() const { + return _size == 0; + } + + T& operator[](int i) { + TIXMLASSERT( i>= 0 && i < _size ); + return _mem[i]; + } + + const T& operator[](int i) const { + TIXMLASSERT( i>= 0 && i < _size ); + return _mem[i]; + } + + const T& PeekTop() const { + TIXMLASSERT( _size > 0 ); + return _mem[ _size - 1]; + } + + int Size() const { + TIXMLASSERT( _size >= 0 ); + return _size; + } + + int Capacity() const { + TIXMLASSERT( _allocated >= INITIAL_SIZE ); + return _allocated; + } + + void SwapRemove(int i) { + TIXMLASSERT(i >= 0 && i < _size); + TIXMLASSERT(_size > 0); + _mem[i] = _mem[_size - 1]; + --_size; + } + + const T* Mem() const { + TIXMLASSERT( _mem ); + return _mem; + } + + T* Mem() { + TIXMLASSERT( _mem ); + return _mem; + } + +private: + DynArray( const DynArray& ); // not supported + void operator=( const DynArray& ); // not supported + + void EnsureCapacity( int cap ) { + TIXMLASSERT( cap > 0 ); + if ( cap > _allocated ) { + TIXMLASSERT( cap <= INT_MAX / 2 ); + const int newAllocated = cap * 2; + T* newMem = new T[newAllocated]; + TIXMLASSERT( newAllocated >= _size ); + memcpy( newMem, _mem, sizeof(T)*_size ); // warning: not using constructors, only works for PODs + if ( _mem != _pool ) { + delete [] _mem; + } + _mem = newMem; + _allocated = newAllocated; + } + } + + T* _mem; + T _pool[INITIAL_SIZE]; + int _allocated; // objects allocated + int _size; // number objects in use +}; + + +/* + Parent virtual class of a pool for fast allocation + and deallocation of objects. +*/ +class MemPool +{ +public: + MemPool() {} + virtual ~MemPool() {} + + virtual int ItemSize() const = 0; + virtual void* Alloc() = 0; + virtual void Free( void* ) = 0; + virtual void SetTracked() = 0; +}; + + +/* + Template child class to create pools of the correct type. +*/ +template< int ITEM_SIZE > +class MemPoolT : public MemPool +{ +public: + MemPoolT() : _blockPtrs(), _root(0), _currentAllocs(0), _nAllocs(0), _maxAllocs(0), _nUntracked(0) {} + ~MemPoolT() { + MemPoolT< ITEM_SIZE >::Clear(); + } + + void Clear() { + // Delete the blocks. + while( !_blockPtrs.Empty()) { + Block* lastBlock = _blockPtrs.Pop(); + delete lastBlock; + } + _root = 0; + _currentAllocs = 0; + _nAllocs = 0; + _maxAllocs = 0; + _nUntracked = 0; + } + + virtual int ItemSize() const { + return ITEM_SIZE; + } + int CurrentAllocs() const { + return _currentAllocs; + } + + virtual void* Alloc() { + if ( !_root ) { + // Need a new block. + Block* block = new Block(); + _blockPtrs.Push( block ); + + Item* blockItems = block->items; + for( int i = 0; i < ITEMS_PER_BLOCK - 1; ++i ) { + blockItems[i].next = &(blockItems[i + 1]); + } + blockItems[ITEMS_PER_BLOCK - 1].next = 0; + _root = blockItems; + } + Item* const result = _root; + TIXMLASSERT( result != 0 ); + _root = _root->next; + + ++_currentAllocs; + if ( _currentAllocs > _maxAllocs ) { + _maxAllocs = _currentAllocs; + } + ++_nAllocs; + ++_nUntracked; + return result; + } + + virtual void Free( void* mem ) { + if ( !mem ) { + return; + } + --_currentAllocs; + Item* item = static_cast( mem ); +#ifdef TINYXML2_DEBUG + memset( item, 0xfe, sizeof( *item ) ); +#endif + item->next = _root; + _root = item; + } + void Trace( const char* name ) { + printf( "Mempool %s watermark=%d [%dk] current=%d size=%d nAlloc=%d blocks=%d\n", + name, _maxAllocs, _maxAllocs * ITEM_SIZE / 1024, _currentAllocs, + ITEM_SIZE, _nAllocs, _blockPtrs.Size() ); + } + + void SetTracked() { + --_nUntracked; + } + + int Untracked() const { + return _nUntracked; + } + + // This number is perf sensitive. 4k seems like a good tradeoff on my machine. + // The test file is large, 170k. + // Release: VS2010 gcc(no opt) + // 1k: 4000 + // 2k: 4000 + // 4k: 3900 21000 + // 16k: 5200 + // 32k: 4300 + // 64k: 4000 21000 + // Declared public because some compilers do not accept to use ITEMS_PER_BLOCK + // in private part if ITEMS_PER_BLOCK is private + enum { ITEMS_PER_BLOCK = (4 * 1024) / ITEM_SIZE }; + +private: + MemPoolT( const MemPoolT& ); // not supported + void operator=( const MemPoolT& ); // not supported + + union Item { + Item* next; + char itemData[ITEM_SIZE]; + }; + struct Block { + Item items[ITEMS_PER_BLOCK]; + }; + DynArray< Block*, 10 > _blockPtrs; + Item* _root; + + int _currentAllocs; + int _nAllocs; + int _maxAllocs; + int _nUntracked; +}; + + + +/** + Implements the interface to the "Visitor pattern" (see the Accept() method.) + If you call the Accept() method, it requires being passed a XMLVisitor + class to handle callbacks. For nodes that contain other nodes (Document, Element) + you will get called with a VisitEnter/VisitExit pair. Nodes that are always leafs + are simply called with Visit(). + + If you return 'true' from a Visit method, recursive parsing will continue. If you return + false, no children of this node or its siblings will be visited. + + All flavors of Visit methods have a default implementation that returns 'true' (continue + visiting). You need to only override methods that are interesting to you. + + Generally Accept() is called on the XMLDocument, although all nodes support visiting. + + You should never change the document from a callback. + + @sa XMLNode::Accept() +*/ +class TINYXML2_LIB XMLVisitor +{ +public: + virtual ~XMLVisitor() {} + + /// Visit a document. + virtual bool VisitEnter( const XMLDocument& /*doc*/ ) { + return true; + } + /// Visit a document. + virtual bool VisitExit( const XMLDocument& /*doc*/ ) { + return true; + } + + /// Visit an element. + virtual bool VisitEnter( const XMLElement& /*element*/, const XMLAttribute* /*firstAttribute*/ ) { + return true; + } + /// Visit an element. + virtual bool VisitExit( const XMLElement& /*element*/ ) { + return true; + } + + /// Visit a declaration. + virtual bool Visit( const XMLDeclaration& /*declaration*/ ) { + return true; + } + /// Visit a text node. + virtual bool Visit( const XMLText& /*text*/ ) { + return true; + } + /// Visit a comment node. + virtual bool Visit( const XMLComment& /*comment*/ ) { + return true; + } + /// Visit an unknown node. + virtual bool Visit( const XMLUnknown& /*unknown*/ ) { + return true; + } +}; + +// WARNING: must match XMLDocument::_errorNames[] +enum XMLError { + XML_SUCCESS = 0, + XML_NO_ATTRIBUTE, + XML_WRONG_ATTRIBUTE_TYPE, + XML_ERROR_FILE_NOT_FOUND, + XML_ERROR_FILE_COULD_NOT_BE_OPENED, + XML_ERROR_FILE_READ_ERROR, + XML_ERROR_PARSING_ELEMENT, + XML_ERROR_PARSING_ATTRIBUTE, + XML_ERROR_PARSING_TEXT, + XML_ERROR_PARSING_CDATA, + XML_ERROR_PARSING_COMMENT, + XML_ERROR_PARSING_DECLARATION, + XML_ERROR_PARSING_UNKNOWN, + XML_ERROR_EMPTY_DOCUMENT, + XML_ERROR_MISMATCHED_ELEMENT, + XML_ERROR_PARSING, + XML_CAN_NOT_CONVERT_TEXT, + XML_NO_TEXT_NODE, + XML_ELEMENT_DEPTH_EXCEEDED, + + XML_ERROR_COUNT +}; + + +/* + Utility functionality. +*/ +class TINYXML2_LIB XMLUtil +{ +public: + static const char* SkipWhiteSpace( const char* p, int* curLineNumPtr ) { + TIXMLASSERT( p ); + + while( IsWhiteSpace(*p) ) { + if (curLineNumPtr && *p == '\n') { + ++(*curLineNumPtr); + } + ++p; + } + TIXMLASSERT( p ); + return p; + } + static char* SkipWhiteSpace( char* const p, int* curLineNumPtr ) { + return const_cast( SkipWhiteSpace( const_cast(p), curLineNumPtr ) ); + } + + // Anything in the high order range of UTF-8 is assumed to not be whitespace. This isn't + // correct, but simple, and usually works. + static bool IsWhiteSpace( char p ) { + return !IsUTF8Continuation(p) && isspace( static_cast(p) ); + } + + inline static bool IsNameStartChar( unsigned char ch ) { + if ( ch >= 128 ) { + // This is a heuristic guess in attempt to not implement Unicode-aware isalpha() + return true; + } + if ( isalpha( ch ) ) { + return true; + } + return ch == ':' || ch == '_'; + } + + inline static bool IsNameChar( unsigned char ch ) { + return IsNameStartChar( ch ) + || isdigit( ch ) + || ch == '.' + || ch == '-'; + } + + inline static bool IsPrefixHex( const char* p) { + p = SkipWhiteSpace(p, 0); + return p && *p == '0' && ( *(p + 1) == 'x' || *(p + 1) == 'X'); + } + + inline static bool StringEqual( const char* p, const char* q, int nChar=INT_MAX ) { + if ( p == q ) { + return true; + } + TIXMLASSERT( p ); + TIXMLASSERT( q ); + TIXMLASSERT( nChar >= 0 ); + return strncmp( p, q, nChar ) == 0; + } + + inline static bool IsUTF8Continuation( const char p ) { + return ( p & 0x80 ) != 0; + } + + static const char* ReadBOM( const char* p, bool* hasBOM ); + // p is the starting location, + // the UTF-8 value of the entity will be placed in value, and length filled in. + static const char* GetCharacterRef( const char* p, char* value, int* length ); + static void ConvertUTF32ToUTF8( unsigned long input, char* output, int* length ); + + // converts primitive types to strings + static void ToStr( int v, char* buffer, int bufferSize ); + static void ToStr( unsigned v, char* buffer, int bufferSize ); + static void ToStr( bool v, char* buffer, int bufferSize ); + static void ToStr( float v, char* buffer, int bufferSize ); + static void ToStr( double v, char* buffer, int bufferSize ); + static void ToStr(int64_t v, char* buffer, int bufferSize); + static void ToStr(uint64_t v, char* buffer, int bufferSize); + + // converts strings to primitive types + static bool ToInt( const char* str, int* value ); + static bool ToUnsigned( const char* str, unsigned* value ); + static bool ToBool( const char* str, bool* value ); + static bool ToFloat( const char* str, float* value ); + static bool ToDouble( const char* str, double* value ); + static bool ToInt64(const char* str, int64_t* value); + static bool ToUnsigned64(const char* str, uint64_t* value); + // Changes what is serialized for a boolean value. + // Default to "true" and "false". Shouldn't be changed + // unless you have a special testing or compatibility need. + // Be careful: static, global, & not thread safe. + // Be sure to set static const memory as parameters. + static void SetBoolSerialization(const char* writeTrue, const char* writeFalse); + +private: + static const char* writeBoolTrue; + static const char* writeBoolFalse; +}; + + +/** XMLNode is a base class for every object that is in the + XML Document Object Model (DOM), except XMLAttributes. + Nodes have siblings, a parent, and children which can + be navigated. A node is always in a XMLDocument. + The type of a XMLNode can be queried, and it can + be cast to its more defined type. + + A XMLDocument allocates memory for all its Nodes. + When the XMLDocument gets deleted, all its Nodes + will also be deleted. + + @verbatim + A Document can contain: Element (container or leaf) + Comment (leaf) + Unknown (leaf) + Declaration( leaf ) + + An Element can contain: Element (container or leaf) + Text (leaf) + Attributes (not on tree) + Comment (leaf) + Unknown (leaf) + + @endverbatim +*/ +class TINYXML2_LIB XMLNode +{ + friend class XMLDocument; + friend class XMLElement; +public: + + /// Get the XMLDocument that owns this XMLNode. + const XMLDocument* GetDocument() const { + TIXMLASSERT( _document ); + return _document; + } + /// Get the XMLDocument that owns this XMLNode. + XMLDocument* GetDocument() { + TIXMLASSERT( _document ); + return _document; + } + + /// Safely cast to an Element, or null. + virtual XMLElement* ToElement() { + return 0; + } + /// Safely cast to Text, or null. + virtual XMLText* ToText() { + return 0; + } + /// Safely cast to a Comment, or null. + virtual XMLComment* ToComment() { + return 0; + } + /// Safely cast to a Document, or null. + virtual XMLDocument* ToDocument() { + return 0; + } + /// Safely cast to a Declaration, or null. + virtual XMLDeclaration* ToDeclaration() { + return 0; + } + /// Safely cast to an Unknown, or null. + virtual XMLUnknown* ToUnknown() { + return 0; + } + + virtual const XMLElement* ToElement() const { + return 0; + } + virtual const XMLText* ToText() const { + return 0; + } + virtual const XMLComment* ToComment() const { + return 0; + } + virtual const XMLDocument* ToDocument() const { + return 0; + } + virtual const XMLDeclaration* ToDeclaration() const { + return 0; + } + virtual const XMLUnknown* ToUnknown() const { + return 0; + } + + /** The meaning of 'value' changes for the specific type. + @verbatim + Document: empty (NULL is returned, not an empty string) + Element: name of the element + Comment: the comment text + Unknown: the tag contents + Text: the text string + @endverbatim + */ + const char* Value() const; + + /** Set the Value of an XML node. + @sa Value() + */ + void SetValue( const char* val, bool staticMem=false ); + + /// Gets the line number the node is in, if the document was parsed from a file. + int GetLineNum() const { return _parseLineNum; } + + /// Get the parent of this node on the DOM. + const XMLNode* Parent() const { + return _parent; + } + + XMLNode* Parent() { + return _parent; + } + + /// Returns true if this node has no children. + bool NoChildren() const { + return !_firstChild; + } + + /// Get the first child node, or null if none exists. + const XMLNode* FirstChild() const { + return _firstChild; + } + + XMLNode* FirstChild() { + return _firstChild; + } + + /** Get the first child element, or optionally the first child + element with the specified name. + */ + const XMLElement* FirstChildElement( const char* name = 0 ) const; + + XMLElement* FirstChildElement( const char* name = 0 ) { + return const_cast(const_cast(this)->FirstChildElement( name )); + } + + /// Get the last child node, or null if none exists. + const XMLNode* LastChild() const { + return _lastChild; + } + + XMLNode* LastChild() { + return _lastChild; + } + + /** Get the last child element or optionally the last child + element with the specified name. + */ + const XMLElement* LastChildElement( const char* name = 0 ) const; + + XMLElement* LastChildElement( const char* name = 0 ) { + return const_cast(const_cast(this)->LastChildElement(name) ); + } + + /// Get the previous (left) sibling node of this node. + const XMLNode* PreviousSibling() const { + return _prev; + } + + XMLNode* PreviousSibling() { + return _prev; + } + + /// Get the previous (left) sibling element of this node, with an optionally supplied name. + const XMLElement* PreviousSiblingElement( const char* name = 0 ) const ; + + XMLElement* PreviousSiblingElement( const char* name = 0 ) { + return const_cast(const_cast(this)->PreviousSiblingElement( name ) ); + } + + /// Get the next (right) sibling node of this node. + const XMLNode* NextSibling() const { + return _next; + } + + XMLNode* NextSibling() { + return _next; + } + + /// Get the next (right) sibling element of this node, with an optionally supplied name. + const XMLElement* NextSiblingElement( const char* name = 0 ) const; + + XMLElement* NextSiblingElement( const char* name = 0 ) { + return const_cast(const_cast(this)->NextSiblingElement( name ) ); + } + + /** + Add a child node as the last (right) child. + If the child node is already part of the document, + it is moved from its old location to the new location. + Returns the addThis argument or 0 if the node does not + belong to the same document. + */ + XMLNode* InsertEndChild( XMLNode* addThis ); + + XMLNode* LinkEndChild( XMLNode* addThis ) { + return InsertEndChild( addThis ); + } + /** + Add a child node as the first (left) child. + If the child node is already part of the document, + it is moved from its old location to the new location. + Returns the addThis argument or 0 if the node does not + belong to the same document. + */ + XMLNode* InsertFirstChild( XMLNode* addThis ); + /** + Add a node after the specified child node. + If the child node is already part of the document, + it is moved from its old location to the new location. + Returns the addThis argument or 0 if the afterThis node + is not a child of this node, or if the node does not + belong to the same document. + */ + XMLNode* InsertAfterChild( XMLNode* afterThis, XMLNode* addThis ); + + /** + Delete all the children of this node. + */ + void DeleteChildren(); + + /** + Delete a child of this node. + */ + void DeleteChild( XMLNode* node ); + + /** + Make a copy of this node, but not its children. + You may pass in a Document pointer that will be + the owner of the new Node. If the 'document' is + null, then the node returned will be allocated + from the current Document. (this->GetDocument()) + + Note: if called on a XMLDocument, this will return null. + */ + virtual XMLNode* ShallowClone( XMLDocument* document ) const = 0; + + /** + Make a copy of this node and all its children. + + If the 'target' is null, then the nodes will + be allocated in the current document. If 'target' + is specified, the memory will be allocated is the + specified XMLDocument. + + NOTE: This is probably not the correct tool to + copy a document, since XMLDocuments can have multiple + top level XMLNodes. You probably want to use + XMLDocument::DeepCopy() + */ + XMLNode* DeepClone( XMLDocument* target ) const; + + /** + Test if 2 nodes are the same, but don't test children. + The 2 nodes do not need to be in the same Document. + + Note: if called on a XMLDocument, this will return false. + */ + virtual bool ShallowEqual( const XMLNode* compare ) const = 0; + + /** Accept a hierarchical visit of the nodes in the TinyXML-2 DOM. Every node in the + XML tree will be conditionally visited and the host will be called back + via the XMLVisitor interface. + + This is essentially a SAX interface for TinyXML-2. (Note however it doesn't re-parse + the XML for the callbacks, so the performance of TinyXML-2 is unchanged by using this + interface versus any other.) + + The interface has been based on ideas from: + + - http://www.saxproject.org/ + - http://c2.com/cgi/wiki?HierarchicalVisitorPattern + + Which are both good references for "visiting". + + An example of using Accept(): + @verbatim + XMLPrinter printer; + tinyxmlDoc.Accept( &printer ); + const char* xmlcstr = printer.CStr(); + @endverbatim + */ + virtual bool Accept( XMLVisitor* visitor ) const = 0; + + /** + Set user data into the XMLNode. TinyXML-2 in + no way processes or interprets user data. + It is initially 0. + */ + void SetUserData(void* userData) { _userData = userData; } + + /** + Get user data set into the XMLNode. TinyXML-2 in + no way processes or interprets user data. + It is initially 0. + */ + void* GetUserData() const { return _userData; } + +protected: + explicit XMLNode( XMLDocument* ); + virtual ~XMLNode(); + + virtual char* ParseDeep( char* p, StrPair* parentEndTag, int* curLineNumPtr); + + XMLDocument* _document; + XMLNode* _parent; + mutable StrPair _value; + int _parseLineNum; + + XMLNode* _firstChild; + XMLNode* _lastChild; + + XMLNode* _prev; + XMLNode* _next; + + void* _userData; + +private: + MemPool* _memPool; + void Unlink( XMLNode* child ); + static void DeleteNode( XMLNode* node ); + void InsertChildPreamble( XMLNode* insertThis ) const; + const XMLElement* ToElementWithName( const char* name ) const; + + XMLNode( const XMLNode& ); // not supported + XMLNode& operator=( const XMLNode& ); // not supported +}; + + +/** XML text. + + Note that a text node can have child element nodes, for example: + @verbatim + This is bold + @endverbatim + + A text node can have 2 ways to output the next. "normal" output + and CDATA. It will default to the mode it was parsed from the XML file and + you generally want to leave it alone, but you can change the output mode with + SetCData() and query it with CData(). +*/ +class TINYXML2_LIB XMLText : public XMLNode +{ + friend class XMLDocument; +public: + virtual bool Accept( XMLVisitor* visitor ) const; + + virtual XMLText* ToText() { + return this; + } + virtual const XMLText* ToText() const { + return this; + } + + /// Declare whether this should be CDATA or standard text. + void SetCData( bool isCData ) { + _isCData = isCData; + } + /// Returns true if this is a CDATA text element. + bool CData() const { + return _isCData; + } + + virtual XMLNode* ShallowClone( XMLDocument* document ) const; + virtual bool ShallowEqual( const XMLNode* compare ) const; + +protected: + explicit XMLText( XMLDocument* doc ) : XMLNode( doc ), _isCData( false ) {} + virtual ~XMLText() {} + + char* ParseDeep( char* p, StrPair* parentEndTag, int* curLineNumPtr ); + +private: + bool _isCData; + + XMLText( const XMLText& ); // not supported + XMLText& operator=( const XMLText& ); // not supported +}; + + +/** An XML Comment. */ +class TINYXML2_LIB XMLComment : public XMLNode +{ + friend class XMLDocument; +public: + virtual XMLComment* ToComment() { + return this; + } + virtual const XMLComment* ToComment() const { + return this; + } + + virtual bool Accept( XMLVisitor* visitor ) const; + + virtual XMLNode* ShallowClone( XMLDocument* document ) const; + virtual bool ShallowEqual( const XMLNode* compare ) const; + +protected: + explicit XMLComment( XMLDocument* doc ); + virtual ~XMLComment(); + + char* ParseDeep( char* p, StrPair* parentEndTag, int* curLineNumPtr); + +private: + XMLComment( const XMLComment& ); // not supported + XMLComment& operator=( const XMLComment& ); // not supported +}; + + +/** In correct XML the declaration is the first entry in the file. + @verbatim + + @endverbatim + + TinyXML-2 will happily read or write files without a declaration, + however. + + The text of the declaration isn't interpreted. It is parsed + and written as a string. +*/ +class TINYXML2_LIB XMLDeclaration : public XMLNode +{ + friend class XMLDocument; +public: + virtual XMLDeclaration* ToDeclaration() { + return this; + } + virtual const XMLDeclaration* ToDeclaration() const { + return this; + } + + virtual bool Accept( XMLVisitor* visitor ) const; + + virtual XMLNode* ShallowClone( XMLDocument* document ) const; + virtual bool ShallowEqual( const XMLNode* compare ) const; + +protected: + explicit XMLDeclaration( XMLDocument* doc ); + virtual ~XMLDeclaration(); + + char* ParseDeep( char* p, StrPair* parentEndTag, int* curLineNumPtr ); + +private: + XMLDeclaration( const XMLDeclaration& ); // not supported + XMLDeclaration& operator=( const XMLDeclaration& ); // not supported +}; + + +/** Any tag that TinyXML-2 doesn't recognize is saved as an + unknown. It is a tag of text, but should not be modified. + It will be written back to the XML, unchanged, when the file + is saved. + + DTD tags get thrown into XMLUnknowns. +*/ +class TINYXML2_LIB XMLUnknown : public XMLNode +{ + friend class XMLDocument; +public: + virtual XMLUnknown* ToUnknown() { + return this; + } + virtual const XMLUnknown* ToUnknown() const { + return this; + } + + virtual bool Accept( XMLVisitor* visitor ) const; + + virtual XMLNode* ShallowClone( XMLDocument* document ) const; + virtual bool ShallowEqual( const XMLNode* compare ) const; + +protected: + explicit XMLUnknown( XMLDocument* doc ); + virtual ~XMLUnknown(); + + char* ParseDeep( char* p, StrPair* parentEndTag, int* curLineNumPtr ); + +private: + XMLUnknown( const XMLUnknown& ); // not supported + XMLUnknown& operator=( const XMLUnknown& ); // not supported +}; + + + +/** An attribute is a name-value pair. Elements have an arbitrary + number of attributes, each with a unique name. + + @note The attributes are not XMLNodes. You may only query the + Next() attribute in a list. +*/ +class TINYXML2_LIB XMLAttribute +{ + friend class XMLElement; +public: + /// The name of the attribute. + const char* Name() const; + + /// The value of the attribute. + const char* Value() const; + + /// Gets the line number the attribute is in, if the document was parsed from a file. + int GetLineNum() const { return _parseLineNum; } + + /// The next attribute in the list. + const XMLAttribute* Next() const { + return _next; + } + + /** IntValue interprets the attribute as an integer, and returns the value. + If the value isn't an integer, 0 will be returned. There is no error checking; + use QueryIntValue() if you need error checking. + */ + int IntValue() const { + int i = 0; + QueryIntValue(&i); + return i; + } + + int64_t Int64Value() const { + int64_t i = 0; + QueryInt64Value(&i); + return i; + } + + uint64_t Unsigned64Value() const { + uint64_t i = 0; + QueryUnsigned64Value(&i); + return i; + } + + /// Query as an unsigned integer. See IntValue() + unsigned UnsignedValue() const { + unsigned i=0; + QueryUnsignedValue( &i ); + return i; + } + /// Query as a boolean. See IntValue() + bool BoolValue() const { + bool b=false; + QueryBoolValue( &b ); + return b; + } + /// Query as a double. See IntValue() + double DoubleValue() const { + double d=0; + QueryDoubleValue( &d ); + return d; + } + /// Query as a float. See IntValue() + float FloatValue() const { + float f=0; + QueryFloatValue( &f ); + return f; + } + + /** QueryIntValue interprets the attribute as an integer, and returns the value + in the provided parameter. The function will return XML_SUCCESS on success, + and XML_WRONG_ATTRIBUTE_TYPE if the conversion is not successful. + */ + XMLError QueryIntValue( int* value ) const; + /// See QueryIntValue + XMLError QueryUnsignedValue( unsigned int* value ) const; + /// See QueryIntValue + XMLError QueryInt64Value(int64_t* value) const; + /// See QueryIntValue + XMLError QueryUnsigned64Value(uint64_t* value) const; + /// See QueryIntValue + XMLError QueryBoolValue( bool* value ) const; + /// See QueryIntValue + XMLError QueryDoubleValue( double* value ) const; + /// See QueryIntValue + XMLError QueryFloatValue( float* value ) const; + + /// Set the attribute to a string value. + void SetAttribute( const char* value ); + /// Set the attribute to value. + void SetAttribute( int value ); + /// Set the attribute to value. + void SetAttribute( unsigned value ); + /// Set the attribute to value. + void SetAttribute(int64_t value); + /// Set the attribute to value. + void SetAttribute(uint64_t value); + /// Set the attribute to value. + void SetAttribute( bool value ); + /// Set the attribute to value. + void SetAttribute( double value ); + /// Set the attribute to value. + void SetAttribute( float value ); + +private: + enum { BUF_SIZE = 200 }; + + XMLAttribute() : _name(), _value(),_parseLineNum( 0 ), _next( 0 ), _memPool( 0 ) {} + virtual ~XMLAttribute() {} + + XMLAttribute( const XMLAttribute& ); // not supported + void operator=( const XMLAttribute& ); // not supported + void SetName( const char* name ); + + char* ParseDeep( char* p, bool processEntities, int* curLineNumPtr ); + + mutable StrPair _name; + mutable StrPair _value; + int _parseLineNum; + XMLAttribute* _next; + MemPool* _memPool; +}; + + +/** The element is a container class. It has a value, the element name, + and can contain other elements, text, comments, and unknowns. + Elements also contain an arbitrary number of attributes. +*/ +class TINYXML2_LIB XMLElement : public XMLNode +{ + friend class XMLDocument; +public: + /// Get the name of an element (which is the Value() of the node.) + const char* Name() const { + return Value(); + } + /// Set the name of the element. + void SetName( const char* str, bool staticMem=false ) { + SetValue( str, staticMem ); + } + + virtual XMLElement* ToElement() { + return this; + } + virtual const XMLElement* ToElement() const { + return this; + } + virtual bool Accept( XMLVisitor* visitor ) const; + + /** Given an attribute name, Attribute() returns the value + for the attribute of that name, or null if none + exists. For example: + + @verbatim + const char* value = ele->Attribute( "foo" ); + @endverbatim + + The 'value' parameter is normally null. However, if specified, + the attribute will only be returned if the 'name' and 'value' + match. This allow you to write code: + + @verbatim + if ( ele->Attribute( "foo", "bar" ) ) callFooIsBar(); + @endverbatim + + rather than: + @verbatim + if ( ele->Attribute( "foo" ) ) { + if ( strcmp( ele->Attribute( "foo" ), "bar" ) == 0 ) callFooIsBar(); + } + @endverbatim + */ + const char* Attribute( const char* name, const char* value=0 ) const; + + /** Given an attribute name, IntAttribute() returns the value + of the attribute interpreted as an integer. The default + value will be returned if the attribute isn't present, + or if there is an error. (For a method with error + checking, see QueryIntAttribute()). + */ + int IntAttribute(const char* name, int defaultValue = 0) const; + /// See IntAttribute() + unsigned UnsignedAttribute(const char* name, unsigned defaultValue = 0) const; + /// See IntAttribute() + int64_t Int64Attribute(const char* name, int64_t defaultValue = 0) const; + /// See IntAttribute() + uint64_t Unsigned64Attribute(const char* name, uint64_t defaultValue = 0) const; + /// See IntAttribute() + bool BoolAttribute(const char* name, bool defaultValue = false) const; + /// See IntAttribute() + double DoubleAttribute(const char* name, double defaultValue = 0) const; + /// See IntAttribute() + float FloatAttribute(const char* name, float defaultValue = 0) const; + + /** Given an attribute name, QueryIntAttribute() returns + XML_SUCCESS, XML_WRONG_ATTRIBUTE_TYPE if the conversion + can't be performed, or XML_NO_ATTRIBUTE if the attribute + doesn't exist. If successful, the result of the conversion + will be written to 'value'. If not successful, nothing will + be written to 'value'. This allows you to provide default + value: + + @verbatim + int value = 10; + QueryIntAttribute( "foo", &value ); // if "foo" isn't found, value will still be 10 + @endverbatim + */ + XMLError QueryIntAttribute( const char* name, int* value ) const { + const XMLAttribute* a = FindAttribute( name ); + if ( !a ) { + return XML_NO_ATTRIBUTE; + } + return a->QueryIntValue( value ); + } + + /// See QueryIntAttribute() + XMLError QueryUnsignedAttribute( const char* name, unsigned int* value ) const { + const XMLAttribute* a = FindAttribute( name ); + if ( !a ) { + return XML_NO_ATTRIBUTE; + } + return a->QueryUnsignedValue( value ); + } + + /// See QueryIntAttribute() + XMLError QueryInt64Attribute(const char* name, int64_t* value) const { + const XMLAttribute* a = FindAttribute(name); + if (!a) { + return XML_NO_ATTRIBUTE; + } + return a->QueryInt64Value(value); + } + + /// See QueryIntAttribute() + XMLError QueryUnsigned64Attribute(const char* name, uint64_t* value) const { + const XMLAttribute* a = FindAttribute(name); + if(!a) { + return XML_NO_ATTRIBUTE; + } + return a->QueryUnsigned64Value(value); + } + + /// See QueryIntAttribute() + XMLError QueryBoolAttribute( const char* name, bool* value ) const { + const XMLAttribute* a = FindAttribute( name ); + if ( !a ) { + return XML_NO_ATTRIBUTE; + } + return a->QueryBoolValue( value ); + } + /// See QueryIntAttribute() + XMLError QueryDoubleAttribute( const char* name, double* value ) const { + const XMLAttribute* a = FindAttribute( name ); + if ( !a ) { + return XML_NO_ATTRIBUTE; + } + return a->QueryDoubleValue( value ); + } + /// See QueryIntAttribute() + XMLError QueryFloatAttribute( const char* name, float* value ) const { + const XMLAttribute* a = FindAttribute( name ); + if ( !a ) { + return XML_NO_ATTRIBUTE; + } + return a->QueryFloatValue( value ); + } + + /// See QueryIntAttribute() + XMLError QueryStringAttribute(const char* name, const char** value) const { + const XMLAttribute* a = FindAttribute(name); + if (!a) { + return XML_NO_ATTRIBUTE; + } + *value = a->Value(); + return XML_SUCCESS; + } + + + + /** Given an attribute name, QueryAttribute() returns + XML_SUCCESS, XML_WRONG_ATTRIBUTE_TYPE if the conversion + can't be performed, or XML_NO_ATTRIBUTE if the attribute + doesn't exist. It is overloaded for the primitive types, + and is a generally more convenient replacement of + QueryIntAttribute() and related functions. + + If successful, the result of the conversion + will be written to 'value'. If not successful, nothing will + be written to 'value'. This allows you to provide default + value: + + @verbatim + int value = 10; + QueryAttribute( "foo", &value ); // if "foo" isn't found, value will still be 10 + @endverbatim + */ + XMLError QueryAttribute( const char* name, int* value ) const { + return QueryIntAttribute( name, value ); + } + + XMLError QueryAttribute( const char* name, unsigned int* value ) const { + return QueryUnsignedAttribute( name, value ); + } + + XMLError QueryAttribute(const char* name, int64_t* value) const { + return QueryInt64Attribute(name, value); + } + + XMLError QueryAttribute(const char* name, uint64_t* value) const { + return QueryUnsigned64Attribute(name, value); + } + + XMLError QueryAttribute( const char* name, bool* value ) const { + return QueryBoolAttribute( name, value ); + } + + XMLError QueryAttribute( const char* name, double* value ) const { + return QueryDoubleAttribute( name, value ); + } + + XMLError QueryAttribute( const char* name, float* value ) const { + return QueryFloatAttribute( name, value ); + } + + XMLError QueryAttribute(const char* name, const char** value) const { + return QueryStringAttribute(name, value); + } + + /// Sets the named attribute to value. + void SetAttribute( const char* name, const char* value ) { + XMLAttribute* a = FindOrCreateAttribute( name ); + a->SetAttribute( value ); + } + /// Sets the named attribute to value. + void SetAttribute( const char* name, int value ) { + XMLAttribute* a = FindOrCreateAttribute( name ); + a->SetAttribute( value ); + } + /// Sets the named attribute to value. + void SetAttribute( const char* name, unsigned value ) { + XMLAttribute* a = FindOrCreateAttribute( name ); + a->SetAttribute( value ); + } + + /// Sets the named attribute to value. + void SetAttribute(const char* name, int64_t value) { + XMLAttribute* a = FindOrCreateAttribute(name); + a->SetAttribute(value); + } + + /// Sets the named attribute to value. + void SetAttribute(const char* name, uint64_t value) { + XMLAttribute* a = FindOrCreateAttribute(name); + a->SetAttribute(value); + } + + /// Sets the named attribute to value. + void SetAttribute( const char* name, bool value ) { + XMLAttribute* a = FindOrCreateAttribute( name ); + a->SetAttribute( value ); + } + /// Sets the named attribute to value. + void SetAttribute( const char* name, double value ) { + XMLAttribute* a = FindOrCreateAttribute( name ); + a->SetAttribute( value ); + } + /// Sets the named attribute to value. + void SetAttribute( const char* name, float value ) { + XMLAttribute* a = FindOrCreateAttribute( name ); + a->SetAttribute( value ); + } + + /** + Delete an attribute. + */ + void DeleteAttribute( const char* name ); + + /// Return the first attribute in the list. + const XMLAttribute* FirstAttribute() const { + return _rootAttribute; + } + /// Query a specific attribute in the list. + const XMLAttribute* FindAttribute( const char* name ) const; + + /** Convenience function for easy access to the text inside an element. Although easy + and concise, GetText() is limited compared to getting the XMLText child + and accessing it directly. + + If the first child of 'this' is a XMLText, the GetText() + returns the character string of the Text node, else null is returned. + + This is a convenient method for getting the text of simple contained text: + @verbatim + This is text + const char* str = fooElement->GetText(); + @endverbatim + + 'str' will be a pointer to "This is text". + + Note that this function can be misleading. If the element foo was created from + this XML: + @verbatim + This is text + @endverbatim + + then the value of str would be null. The first child node isn't a text node, it is + another element. From this XML: + @verbatim + This is text + @endverbatim + GetText() will return "This is ". + */ + const char* GetText() const; + + /** Convenience function for easy access to the text inside an element. Although easy + and concise, SetText() is limited compared to creating an XMLText child + and mutating it directly. + + If the first child of 'this' is a XMLText, SetText() sets its value to + the given string, otherwise it will create a first child that is an XMLText. + + This is a convenient method for setting the text of simple contained text: + @verbatim + This is text + fooElement->SetText( "Hullaballoo!" ); + Hullaballoo! + @endverbatim + + Note that this function can be misleading. If the element foo was created from + this XML: + @verbatim + This is text + @endverbatim + + then it will not change "This is text", but rather prefix it with a text element: + @verbatim + Hullaballoo!This is text + @endverbatim + + For this XML: + @verbatim + + @endverbatim + SetText() will generate + @verbatim + Hullaballoo! + @endverbatim + */ + void SetText( const char* inText ); + /// Convenience method for setting text inside an element. See SetText() for important limitations. + void SetText( int value ); + /// Convenience method for setting text inside an element. See SetText() for important limitations. + void SetText( unsigned value ); + /// Convenience method for setting text inside an element. See SetText() for important limitations. + void SetText(int64_t value); + /// Convenience method for setting text inside an element. See SetText() for important limitations. + void SetText(uint64_t value); + /// Convenience method for setting text inside an element. See SetText() for important limitations. + void SetText( bool value ); + /// Convenience method for setting text inside an element. See SetText() for important limitations. + void SetText( double value ); + /// Convenience method for setting text inside an element. See SetText() for important limitations. + void SetText( float value ); + + /** + Convenience method to query the value of a child text node. This is probably best + shown by example. Given you have a document is this form: + @verbatim + + 1 + 1.4 + + @endverbatim + + The QueryIntText() and similar functions provide a safe and easier way to get to the + "value" of x and y. + + @verbatim + int x = 0; + float y = 0; // types of x and y are contrived for example + const XMLElement* xElement = pointElement->FirstChildElement( "x" ); + const XMLElement* yElement = pointElement->FirstChildElement( "y" ); + xElement->QueryIntText( &x ); + yElement->QueryFloatText( &y ); + @endverbatim + + @returns XML_SUCCESS (0) on success, XML_CAN_NOT_CONVERT_TEXT if the text cannot be converted + to the requested type, and XML_NO_TEXT_NODE if there is no child text to query. + + */ + XMLError QueryIntText( int* ival ) const; + /// See QueryIntText() + XMLError QueryUnsignedText( unsigned* uval ) const; + /// See QueryIntText() + XMLError QueryInt64Text(int64_t* uval) const; + /// See QueryIntText() + XMLError QueryUnsigned64Text(uint64_t* uval) const; + /// See QueryIntText() + XMLError QueryBoolText( bool* bval ) const; + /// See QueryIntText() + XMLError QueryDoubleText( double* dval ) const; + /// See QueryIntText() + XMLError QueryFloatText( float* fval ) const; + + int IntText(int defaultValue = 0) const; + + /// See QueryIntText() + unsigned UnsignedText(unsigned defaultValue = 0) const; + /// See QueryIntText() + int64_t Int64Text(int64_t defaultValue = 0) const; + /// See QueryIntText() + uint64_t Unsigned64Text(uint64_t defaultValue = 0) const; + /// See QueryIntText() + bool BoolText(bool defaultValue = false) const; + /// See QueryIntText() + double DoubleText(double defaultValue = 0) const; + /// See QueryIntText() + float FloatText(float defaultValue = 0) const; + + /** + Convenience method to create a new XMLElement and add it as last (right) + child of this node. Returns the created and inserted element. + */ + XMLElement* InsertNewChildElement(const char* name); + /// See InsertNewChildElement() + XMLComment* InsertNewComment(const char* comment); + /// See InsertNewChildElement() + XMLText* InsertNewText(const char* text); + /// See InsertNewChildElement() + XMLDeclaration* InsertNewDeclaration(const char* text); + /// See InsertNewChildElement() + XMLUnknown* InsertNewUnknown(const char* text); + + + // internal: + enum ElementClosingType { + OPEN, // + CLOSED, // + CLOSING // + }; + ElementClosingType ClosingType() const { + return _closingType; + } + virtual XMLNode* ShallowClone( XMLDocument* document ) const; + virtual bool ShallowEqual( const XMLNode* compare ) const; + +protected: + char* ParseDeep( char* p, StrPair* parentEndTag, int* curLineNumPtr ); + +private: + XMLElement( XMLDocument* doc ); + virtual ~XMLElement(); + XMLElement( const XMLElement& ); // not supported + void operator=( const XMLElement& ); // not supported + + XMLAttribute* FindOrCreateAttribute( const char* name ); + char* ParseAttributes( char* p, int* curLineNumPtr ); + static void DeleteAttribute( XMLAttribute* attribute ); + XMLAttribute* CreateAttribute(); + + enum { BUF_SIZE = 200 }; + ElementClosingType _closingType; + // The attribute list is ordered; there is no 'lastAttribute' + // because the list needs to be scanned for dupes before adding + // a new attribute. + XMLAttribute* _rootAttribute; +}; + + +enum Whitespace { + PRESERVE_WHITESPACE, + COLLAPSE_WHITESPACE +}; + + +/** A Document binds together all the functionality. + It can be saved, loaded, and printed to the screen. + All Nodes are connected and allocated to a Document. + If the Document is deleted, all its Nodes are also deleted. +*/ +class TINYXML2_LIB XMLDocument : public XMLNode +{ + friend class XMLElement; + // Gives access to SetError and Push/PopDepth, but over-access for everything else. + // Wishing C++ had "internal" scope. + friend class XMLNode; + friend class XMLText; + friend class XMLComment; + friend class XMLDeclaration; + friend class XMLUnknown; +public: + /// constructor + XMLDocument( bool processEntities = true, Whitespace whitespaceMode = PRESERVE_WHITESPACE ); + ~XMLDocument(); + + virtual XMLDocument* ToDocument() { + TIXMLASSERT( this == _document ); + return this; + } + virtual const XMLDocument* ToDocument() const { + TIXMLASSERT( this == _document ); + return this; + } + + /** + Parse an XML file from a character string. + Returns XML_SUCCESS (0) on success, or + an errorID. + + You may optionally pass in the 'nBytes', which is + the number of bytes which will be parsed. If not + specified, TinyXML-2 will assume 'xml' points to a + null terminated string. + */ + XMLError Parse( const char* xml, size_t nBytes=static_cast(-1) ); + + /** + Load an XML file from disk. + Returns XML_SUCCESS (0) on success, or + an errorID. + */ + XMLError LoadFile( const char* filename ); + + /** + Load an XML file from disk. You are responsible + for providing and closing the FILE*. + + NOTE: The file should be opened as binary ("rb") + not text in order for TinyXML-2 to correctly + do newline normalization. + + Returns XML_SUCCESS (0) on success, or + an errorID. + */ + XMLError LoadFile( FILE* ); + + /** + Save the XML file to disk. + Returns XML_SUCCESS (0) on success, or + an errorID. + */ + XMLError SaveFile( const char* filename, bool compact = false ); + + /** + Save the XML file to disk. You are responsible + for providing and closing the FILE*. + + Returns XML_SUCCESS (0) on success, or + an errorID. + */ + XMLError SaveFile( FILE* fp, bool compact = false ); + + bool ProcessEntities() const { + return _processEntities; + } + Whitespace WhitespaceMode() const { + return _whitespaceMode; + } + + /** + Returns true if this document has a leading Byte Order Mark of UTF8. + */ + bool HasBOM() const { + return _writeBOM; + } + /** Sets whether to write the BOM when writing the file. + */ + void SetBOM( bool useBOM ) { + _writeBOM = useBOM; + } + + /** Return the root element of DOM. Equivalent to FirstChildElement(). + To get the first node, use FirstChild(). + */ + XMLElement* RootElement() { + return FirstChildElement(); + } + const XMLElement* RootElement() const { + return FirstChildElement(); + } + + /** Print the Document. If the Printer is not provided, it will + print to stdout. If you provide Printer, this can print to a file: + @verbatim + XMLPrinter printer( fp ); + doc.Print( &printer ); + @endverbatim + + Or you can use a printer to print to memory: + @verbatim + XMLPrinter printer; + doc.Print( &printer ); + // printer.CStr() has a const char* to the XML + @endverbatim + */ + void Print( XMLPrinter* streamer=0 ) const; + virtual bool Accept( XMLVisitor* visitor ) const; + + /** + Create a new Element associated with + this Document. The memory for the Element + is managed by the Document. + */ + XMLElement* NewElement( const char* name ); + /** + Create a new Comment associated with + this Document. The memory for the Comment + is managed by the Document. + */ + XMLComment* NewComment( const char* comment ); + /** + Create a new Text associated with + this Document. The memory for the Text + is managed by the Document. + */ + XMLText* NewText( const char* text ); + /** + Create a new Declaration associated with + this Document. The memory for the object + is managed by the Document. + + If the 'text' param is null, the standard + declaration is used.: + @verbatim + + @endverbatim + */ + XMLDeclaration* NewDeclaration( const char* text=0 ); + /** + Create a new Unknown associated with + this Document. The memory for the object + is managed by the Document. + */ + XMLUnknown* NewUnknown( const char* text ); + + /** + Delete a node associated with this document. + It will be unlinked from the DOM. + */ + void DeleteNode( XMLNode* node ); + + /// Clears the error flags. + void ClearError(); + + /// Return true if there was an error parsing the document. + bool Error() const { + return _errorID != XML_SUCCESS; + } + /// Return the errorID. + XMLError ErrorID() const { + return _errorID; + } + const char* ErrorName() const; + static const char* ErrorIDToName(XMLError errorID); + + /** Returns a "long form" error description. A hopefully helpful + diagnostic with location, line number, and/or additional info. + */ + const char* ErrorStr() const; + + /// A (trivial) utility function that prints the ErrorStr() to stdout. + void PrintError() const; + + /// Return the line where the error occurred, or zero if unknown. + int ErrorLineNum() const + { + return _errorLineNum; + } + + /// Clear the document, resetting it to the initial state. + void Clear(); + + /** + Copies this document to a target document. + The target will be completely cleared before the copy. + If you want to copy a sub-tree, see XMLNode::DeepClone(). + + NOTE: that the 'target' must be non-null. + */ + void DeepCopy(XMLDocument* target) const; + + // internal + char* Identify( char* p, XMLNode** node ); + + // internal + void MarkInUse(const XMLNode* const); + + virtual XMLNode* ShallowClone( XMLDocument* /*document*/ ) const { + return 0; + } + virtual bool ShallowEqual( const XMLNode* /*compare*/ ) const { + return false; + } + +private: + XMLDocument( const XMLDocument& ); // not supported + void operator=( const XMLDocument& ); // not supported + + bool _writeBOM; + bool _processEntities; + XMLError _errorID; + Whitespace _whitespaceMode; + mutable StrPair _errorStr; + int _errorLineNum; + char* _charBuffer; + int _parseCurLineNum; + int _parsingDepth; + // Memory tracking does add some overhead. + // However, the code assumes that you don't + // have a bunch of unlinked nodes around. + // Therefore it takes less memory to track + // in the document vs. a linked list in the XMLNode, + // and the performance is the same. + DynArray _unlinked; + + MemPoolT< sizeof(XMLElement) > _elementPool; + MemPoolT< sizeof(XMLAttribute) > _attributePool; + MemPoolT< sizeof(XMLText) > _textPool; + MemPoolT< sizeof(XMLComment) > _commentPool; + + static const char* _errorNames[XML_ERROR_COUNT]; + + void Parse(); + + void SetError( XMLError error, int lineNum, const char* format, ... ); + + // Something of an obvious security hole, once it was discovered. + // Either an ill-formed XML or an excessively deep one can overflow + // the stack. Track stack depth, and error out if needed. + class DepthTracker { + public: + explicit DepthTracker(XMLDocument * document) { + this->_document = document; + document->PushDepth(); + } + ~DepthTracker() { + _document->PopDepth(); + } + private: + XMLDocument * _document; + }; + void PushDepth(); + void PopDepth(); + + template + NodeType* CreateUnlinkedNode( MemPoolT& pool ); +}; + +template +inline NodeType* XMLDocument::CreateUnlinkedNode( MemPoolT& pool ) +{ + TIXMLASSERT( sizeof( NodeType ) == PoolElementSize ); + TIXMLASSERT( sizeof( NodeType ) == pool.ItemSize() ); + NodeType* returnNode = new (pool.Alloc()) NodeType( this ); + TIXMLASSERT( returnNode ); + returnNode->_memPool = &pool; + + _unlinked.Push(returnNode); + return returnNode; +} + +/** + A XMLHandle is a class that wraps a node pointer with null checks; this is + an incredibly useful thing. Note that XMLHandle is not part of the TinyXML-2 + DOM structure. It is a separate utility class. + + Take an example: + @verbatim + + + + + + + @endverbatim + + Assuming you want the value of "attributeB" in the 2nd "Child" element, it's very + easy to write a *lot* of code that looks like: + + @verbatim + XMLElement* root = document.FirstChildElement( "Document" ); + if ( root ) + { + XMLElement* element = root->FirstChildElement( "Element" ); + if ( element ) + { + XMLElement* child = element->FirstChildElement( "Child" ); + if ( child ) + { + XMLElement* child2 = child->NextSiblingElement( "Child" ); + if ( child2 ) + { + // Finally do something useful. + @endverbatim + + And that doesn't even cover "else" cases. XMLHandle addresses the verbosity + of such code. A XMLHandle checks for null pointers so it is perfectly safe + and correct to use: + + @verbatim + XMLHandle docHandle( &document ); + XMLElement* child2 = docHandle.FirstChildElement( "Document" ).FirstChildElement( "Element" ).FirstChildElement().NextSiblingElement(); + if ( child2 ) + { + // do something useful + @endverbatim + + Which is MUCH more concise and useful. + + It is also safe to copy handles - internally they are nothing more than node pointers. + @verbatim + XMLHandle handleCopy = handle; + @endverbatim + + See also XMLConstHandle, which is the same as XMLHandle, but operates on const objects. +*/ +class TINYXML2_LIB XMLHandle +{ +public: + /// Create a handle from any node (at any depth of the tree.) This can be a null pointer. + explicit XMLHandle( XMLNode* node ) : _node( node ) { + } + /// Create a handle from a node. + explicit XMLHandle( XMLNode& node ) : _node( &node ) { + } + /// Copy constructor + XMLHandle( const XMLHandle& ref ) : _node( ref._node ) { + } + /// Assignment + XMLHandle& operator=( const XMLHandle& ref ) { + _node = ref._node; + return *this; + } + + /// Get the first child of this handle. + XMLHandle FirstChild() { + return XMLHandle( _node ? _node->FirstChild() : 0 ); + } + /// Get the first child element of this handle. + XMLHandle FirstChildElement( const char* name = 0 ) { + return XMLHandle( _node ? _node->FirstChildElement( name ) : 0 ); + } + /// Get the last child of this handle. + XMLHandle LastChild() { + return XMLHandle( _node ? _node->LastChild() : 0 ); + } + /// Get the last child element of this handle. + XMLHandle LastChildElement( const char* name = 0 ) { + return XMLHandle( _node ? _node->LastChildElement( name ) : 0 ); + } + /// Get the previous sibling of this handle. + XMLHandle PreviousSibling() { + return XMLHandle( _node ? _node->PreviousSibling() : 0 ); + } + /// Get the previous sibling element of this handle. + XMLHandle PreviousSiblingElement( const char* name = 0 ) { + return XMLHandle( _node ? _node->PreviousSiblingElement( name ) : 0 ); + } + /// Get the next sibling of this handle. + XMLHandle NextSibling() { + return XMLHandle( _node ? _node->NextSibling() : 0 ); + } + /// Get the next sibling element of this handle. + XMLHandle NextSiblingElement( const char* name = 0 ) { + return XMLHandle( _node ? _node->NextSiblingElement( name ) : 0 ); + } + + /// Safe cast to XMLNode. This can return null. + XMLNode* ToNode() { + return _node; + } + /// Safe cast to XMLElement. This can return null. + XMLElement* ToElement() { + return ( _node ? _node->ToElement() : 0 ); + } + /// Safe cast to XMLText. This can return null. + XMLText* ToText() { + return ( _node ? _node->ToText() : 0 ); + } + /// Safe cast to XMLUnknown. This can return null. + XMLUnknown* ToUnknown() { + return ( _node ? _node->ToUnknown() : 0 ); + } + /// Safe cast to XMLDeclaration. This can return null. + XMLDeclaration* ToDeclaration() { + return ( _node ? _node->ToDeclaration() : 0 ); + } + +private: + XMLNode* _node; +}; + + +/** + A variant of the XMLHandle class for working with const XMLNodes and Documents. It is the + same in all regards, except for the 'const' qualifiers. See XMLHandle for API. +*/ +class TINYXML2_LIB XMLConstHandle +{ +public: + explicit XMLConstHandle( const XMLNode* node ) : _node( node ) { + } + explicit XMLConstHandle( const XMLNode& node ) : _node( &node ) { + } + XMLConstHandle( const XMLConstHandle& ref ) : _node( ref._node ) { + } + + XMLConstHandle& operator=( const XMLConstHandle& ref ) { + _node = ref._node; + return *this; + } + + const XMLConstHandle FirstChild() const { + return XMLConstHandle( _node ? _node->FirstChild() : 0 ); + } + const XMLConstHandle FirstChildElement( const char* name = 0 ) const { + return XMLConstHandle( _node ? _node->FirstChildElement( name ) : 0 ); + } + const XMLConstHandle LastChild() const { + return XMLConstHandle( _node ? _node->LastChild() : 0 ); + } + const XMLConstHandle LastChildElement( const char* name = 0 ) const { + return XMLConstHandle( _node ? _node->LastChildElement( name ) : 0 ); + } + const XMLConstHandle PreviousSibling() const { + return XMLConstHandle( _node ? _node->PreviousSibling() : 0 ); + } + const XMLConstHandle PreviousSiblingElement( const char* name = 0 ) const { + return XMLConstHandle( _node ? _node->PreviousSiblingElement( name ) : 0 ); + } + const XMLConstHandle NextSibling() const { + return XMLConstHandle( _node ? _node->NextSibling() : 0 ); + } + const XMLConstHandle NextSiblingElement( const char* name = 0 ) const { + return XMLConstHandle( _node ? _node->NextSiblingElement( name ) : 0 ); + } + + + const XMLNode* ToNode() const { + return _node; + } + const XMLElement* ToElement() const { + return ( _node ? _node->ToElement() : 0 ); + } + const XMLText* ToText() const { + return ( _node ? _node->ToText() : 0 ); + } + const XMLUnknown* ToUnknown() const { + return ( _node ? _node->ToUnknown() : 0 ); + } + const XMLDeclaration* ToDeclaration() const { + return ( _node ? _node->ToDeclaration() : 0 ); + } + +private: + const XMLNode* _node; +}; + + +/** + Printing functionality. The XMLPrinter gives you more + options than the XMLDocument::Print() method. + + It can: + -# Print to memory. + -# Print to a file you provide. + -# Print XML without a XMLDocument. + + Print to Memory + + @verbatim + XMLPrinter printer; + doc.Print( &printer ); + SomeFunction( printer.CStr() ); + @endverbatim + + Print to a File + + You provide the file pointer. + @verbatim + XMLPrinter printer( fp ); + doc.Print( &printer ); + @endverbatim + + Print without a XMLDocument + + When loading, an XML parser is very useful. However, sometimes + when saving, it just gets in the way. The code is often set up + for streaming, and constructing the DOM is just overhead. + + The Printer supports the streaming case. The following code + prints out a trivially simple XML file without ever creating + an XML document. + + @verbatim + XMLPrinter printer( fp ); + printer.OpenElement( "foo" ); + printer.PushAttribute( "foo", "bar" ); + printer.CloseElement(); + @endverbatim +*/ +class TINYXML2_LIB XMLPrinter : public XMLVisitor +{ +public: + /** Construct the printer. If the FILE* is specified, + this will print to the FILE. Else it will print + to memory, and the result is available in CStr(). + If 'compact' is set to true, then output is created + with only required whitespace and newlines. + */ + XMLPrinter( FILE* file=0, bool compact = false, int depth = 0 ); + virtual ~XMLPrinter() {} + + /** If streaming, write the BOM and declaration. */ + void PushHeader( bool writeBOM, bool writeDeclaration ); + /** If streaming, start writing an element. + The element must be closed with CloseElement() + */ + void OpenElement( const char* name, bool compactMode=false ); + /// If streaming, add an attribute to an open element. + void PushAttribute( const char* name, const char* value ); + void PushAttribute( const char* name, int value ); + void PushAttribute( const char* name, unsigned value ); + void PushAttribute( const char* name, int64_t value ); + void PushAttribute( const char* name, uint64_t value ); + void PushAttribute( const char* name, bool value ); + void PushAttribute( const char* name, double value ); + /// If streaming, close the Element. + virtual void CloseElement( bool compactMode=false ); + + /// Add a text node. + void PushText( const char* text, bool cdata=false ); + /// Add a text node from an integer. + void PushText( int value ); + /// Add a text node from an unsigned. + void PushText( unsigned value ); + /// Add a text node from a signed 64bit integer. + void PushText( int64_t value ); + /// Add a text node from an unsigned 64bit integer. + void PushText( uint64_t value ); + /// Add a text node from a bool. + void PushText( bool value ); + /// Add a text node from a float. + void PushText( float value ); + /// Add a text node from a double. + void PushText( double value ); + + /// Add a comment + void PushComment( const char* comment ); + + void PushDeclaration( const char* value ); + void PushUnknown( const char* value ); + + virtual bool VisitEnter( const XMLDocument& /*doc*/ ); + virtual bool VisitExit( const XMLDocument& /*doc*/ ) { + return true; + } + + virtual bool VisitEnter( const XMLElement& element, const XMLAttribute* attribute ); + virtual bool VisitExit( const XMLElement& element ); + + virtual bool Visit( const XMLText& text ); + virtual bool Visit( const XMLComment& comment ); + virtual bool Visit( const XMLDeclaration& declaration ); + virtual bool Visit( const XMLUnknown& unknown ); + + /** + If in print to memory mode, return a pointer to + the XML file in memory. + */ + const char* CStr() const { + return _buffer.Mem(); + } + /** + If in print to memory mode, return the size + of the XML file in memory. (Note the size returned + includes the terminating null.) + */ + int CStrSize() const { + return _buffer.Size(); + } + /** + If in print to memory mode, reset the buffer to the + beginning. + */ + void ClearBuffer( bool resetToFirstElement = true ) { + _buffer.Clear(); + _buffer.Push(0); + _firstElement = resetToFirstElement; + } + +protected: + virtual bool CompactMode( const XMLElement& ) { return _compactMode; } + + /** Prints out the space before an element. You may override to change + the space and tabs used. A PrintSpace() override should call Print(). + */ + virtual void PrintSpace( int depth ); + virtual void Print( const char* format, ... ); + virtual void Write( const char* data, size_t size ); + virtual void Putc( char ch ); + + inline void Write(const char* data) { Write(data, strlen(data)); } + + void SealElementIfJustOpened(); + bool _elementJustOpened; + DynArray< const char*, 10 > _stack; + +private: + /** + Prepares to write a new node. This includes sealing an element that was + just opened, and writing any whitespace necessary if not in compact mode. + */ + void PrepareForNewNode( bool compactMode ); + void PrintString( const char*, bool restrictedEntitySet ); // prints out, after detecting entities. + + bool _firstElement; + FILE* _fp; + int _depth; + int _textDepth; + bool _processEntities; + bool _compactMode; + + enum { + ENTITY_RANGE = 64, + BUF_SIZE = 200 + }; + bool _entityFlag[ENTITY_RANGE]; + bool _restrictedEntityFlag[ENTITY_RANGE]; + + DynArray< char, 20 > _buffer; + + // Prohibit cloning, intentionally not implemented + XMLPrinter( const XMLPrinter& ); + XMLPrinter& operator=( const XMLPrinter& ); +}; + + +} // tinyxml2 + +#if defined(_MSC_VER) +# pragma warning(pop) +#endif + +#endif // TINYXML2_INCLUDED diff --git a/libs/IO/json.hpp b/libs/IO/json.hpp new file mode 100644 index 000000000..b6d30f8c9 --- /dev/null +++ b/libs/IO/json.hpp @@ -0,0 +1,20406 @@ +/* + __ _____ _____ _____ + __| | __| | | | JSON for Modern C++ +| | |__ | | | | | | version 3.5.0 +|_____|_____|_____|_|___| https://github.com/nlohmann/json + +Licensed under the MIT License . +SPDX-License-Identifier: MIT +Copyright (c) 2013-2018 Niels Lohmann . + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef NLOHMANN_JSON_HPP +#define NLOHMANN_JSON_HPP + +#define NLOHMANN_JSON_VERSION_MAJOR 3 +#define NLOHMANN_JSON_VERSION_MINOR 5 +#define NLOHMANN_JSON_VERSION_PATCH 0 + +#include // all_of, find, for_each +#include // assert +#include // and, not, or +#include // nullptr_t, ptrdiff_t, size_t +#include // hash, less +#include // initializer_list +#include // istream, ostream +#include // random_access_iterator_tag +#include // accumulate +#include // string, stoi, to_string +#include // declval, forward, move, pair, swap + +// #include +#ifndef NLOHMANN_JSON_FWD_HPP +#define NLOHMANN_JSON_FWD_HPP + +#include // int64_t, uint64_t +#include // map +#include // allocator +#include // string +#include // vector + +/*! +@brief namespace for Niels Lohmann +@see https://github.com/nlohmann +@since version 1.0.0 +*/ +namespace nlohmann +{ +/*! +@brief default JSONSerializer template argument + +This serializer ignores the template arguments and uses ADL +([argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl)) +for serialization. +*/ +template +struct adl_serializer; + +template class ObjectType = + std::map, + template class ArrayType = std::vector, + class StringType = std::string, class BooleanType = bool, + class NumberIntegerType = std::int64_t, + class NumberUnsignedType = std::uint64_t, + class NumberFloatType = double, + template class AllocatorType = std::allocator, + template class JSONSerializer = + adl_serializer> +class basic_json; + +/*! +@brief JSON Pointer + +A JSON pointer defines a string syntax for identifying a specific value +within a JSON document. It can be used with functions `at` and +`operator[]`. Furthermore, JSON pointers are the base for JSON patches. + +@sa [RFC 6901](https://tools.ietf.org/html/rfc6901) + +@since version 2.0.0 +*/ +template +class json_pointer; + +/*! +@brief default JSON class + +This type is the default specialization of the @ref basic_json class which +uses the standard template types. + +@since version 1.0.0 +*/ +using json = basic_json<>; +} // namespace nlohmann + +#endif + +// #include + + +// This file contains all internal macro definitions +// You MUST include macro_unscope.hpp at the end of json.hpp to undef all of them + +// exclude unsupported compilers +#if !defined(JSON_SKIP_UNSUPPORTED_COMPILER_CHECK) + #if defined(__clang__) + #if (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) < 30400 + #error "unsupported Clang version - see https://github.com/nlohmann/json#supported-compilers" + #endif + #elif defined(__GNUC__) && !(defined(__ICC) || defined(__INTEL_COMPILER)) + #if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) < 40800 + #error "unsupported GCC version - see https://github.com/nlohmann/json#supported-compilers" + #endif + #endif +#endif + +// disable float-equal warnings on GCC/clang +#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + +// disable documentation warnings on clang +#if defined(__clang__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wdocumentation" +#endif + +// allow for portable deprecation warnings +#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) + #define JSON_DEPRECATED __attribute__((deprecated)) +#elif defined(_MSC_VER) + #define JSON_DEPRECATED __declspec(deprecated) +#else + #define JSON_DEPRECATED +#endif + +// allow to disable exceptions +#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION) + #define JSON_THROW(exception) throw exception + #define JSON_TRY try + #define JSON_CATCH(exception) catch(exception) + #define JSON_INTERNAL_CATCH(exception) catch(exception) +#else + #define JSON_THROW(exception) std::abort() + #define JSON_TRY if(true) + #define JSON_CATCH(exception) if(false) + #define JSON_INTERNAL_CATCH(exception) if(false) +#endif + +// override exception macros +#if defined(JSON_THROW_USER) + #undef JSON_THROW + #define JSON_THROW JSON_THROW_USER +#endif +#if defined(JSON_TRY_USER) + #undef JSON_TRY + #define JSON_TRY JSON_TRY_USER +#endif +#if defined(JSON_CATCH_USER) + #undef JSON_CATCH + #define JSON_CATCH JSON_CATCH_USER + #undef JSON_INTERNAL_CATCH + #define JSON_INTERNAL_CATCH JSON_CATCH_USER +#endif +#if defined(JSON_INTERNAL_CATCH_USER) + #undef JSON_INTERNAL_CATCH + #define JSON_INTERNAL_CATCH JSON_INTERNAL_CATCH_USER +#endif + +// manual branch prediction +#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) + #define JSON_LIKELY(x) __builtin_expect(!!(x), 1) + #define JSON_UNLIKELY(x) __builtin_expect(!!(x), 0) +#else + #define JSON_LIKELY(x) x + #define JSON_UNLIKELY(x) x +#endif + +// C++ language standard detection +#if (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464 + #define JSON_HAS_CPP_17 + #define JSON_HAS_CPP_14 +#elif (defined(__cplusplus) && __cplusplus >= 201402L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1) + #define JSON_HAS_CPP_14 +#endif + +/*! +@brief macro to briefly define a mapping between an enum and JSON +@def NLOHMANN_JSON_SERIALIZE_ENUM +@since version 3.4.0 +*/ +#define NLOHMANN_JSON_SERIALIZE_ENUM(ENUM_TYPE, ...) \ + template \ + inline void to_json(BasicJsonType& j, const ENUM_TYPE& e) \ + { \ + static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ + static const std::pair m[] = __VA_ARGS__; \ + auto it = std::find_if(std::begin(m), std::end(m), \ + [e](const std::pair& ej_pair) -> bool \ + { \ + return ej_pair.first == e; \ + }); \ + j = ((it != std::end(m)) ? it : std::begin(m))->second; \ + } \ + template \ + inline void from_json(const BasicJsonType& j, ENUM_TYPE& e) \ + { \ + static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ + static const std::pair m[] = __VA_ARGS__; \ + auto it = std::find_if(std::begin(m), std::end(m), \ + [j](const std::pair& ej_pair) -> bool \ + { \ + return ej_pair.second == j; \ + }); \ + e = ((it != std::end(m)) ? it : std::begin(m))->first; \ + } + +// Ugly macros to avoid uglier copy-paste when specializing basic_json. They +// may be removed in the future once the class is split. + +#define NLOHMANN_BASIC_JSON_TPL_DECLARATION \ + template class ObjectType, \ + template class ArrayType, \ + class StringType, class BooleanType, class NumberIntegerType, \ + class NumberUnsignedType, class NumberFloatType, \ + template class AllocatorType, \ + template class JSONSerializer> + +#define NLOHMANN_BASIC_JSON_TPL \ + basic_json + +// #include + + +#include // not +#include // size_t +#include // conditional, enable_if, false_type, integral_constant, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type + +namespace nlohmann +{ +namespace detail +{ +// alias templates to reduce boilerplate +template +using enable_if_t = typename std::enable_if::type; + +template +using uncvref_t = typename std::remove_cv::type>::type; + +// implementation of C++14 index_sequence and affiliates +// source: https://stackoverflow.com/a/32223343 +template +struct index_sequence +{ + using type = index_sequence; + using value_type = std::size_t; + static constexpr std::size_t size() noexcept + { + return sizeof...(Ints); + } +}; + +template +struct merge_and_renumber; + +template +struct merge_and_renumber, index_sequence> + : index_sequence < I1..., (sizeof...(I1) + I2)... > {}; + +template +struct make_index_sequence + : merge_and_renumber < typename make_index_sequence < N / 2 >::type, + typename make_index_sequence < N - N / 2 >::type > {}; + +template<> struct make_index_sequence<0> : index_sequence<> {}; +template<> struct make_index_sequence<1> : index_sequence<0> {}; + +template +using index_sequence_for = make_index_sequence; + +// dispatch utility (taken from ranges-v3) +template struct priority_tag : priority_tag < N - 1 > {}; +template<> struct priority_tag<0> {}; + +// taken from ranges-v3 +template +struct static_const +{ + static constexpr T value{}; +}; + +template +constexpr T static_const::value; +} // namespace detail +} // namespace nlohmann + +// #include + + +#include // not +#include // numeric_limits +#include // false_type, is_constructible, is_integral, is_same, true_type +#include // declval + +// #include + +// #include + + +#include // random_access_iterator_tag + +// #include + + +namespace nlohmann +{ +namespace detail +{ +template struct make_void +{ + using type = void; +}; +template using void_t = typename make_void::type; +} // namespace detail +} // namespace nlohmann + +// #include + + +namespace nlohmann +{ +namespace detail +{ +template +struct iterator_types {}; + +template +struct iterator_types < + It, + void_t> +{ + using difference_type = typename It::difference_type; + using value_type = typename It::value_type; + using pointer = typename It::pointer; + using reference = typename It::reference; + using iterator_category = typename It::iterator_category; +}; + +// This is required as some compilers implement std::iterator_traits in a way that +// doesn't work with SFINAE. See https://github.com/nlohmann/json/issues/1341. +template +struct iterator_traits +{ +}; + +template +struct iterator_traits < T, enable_if_t < !std::is_pointer::value >> + : iterator_types +{ +}; + +template +struct iterator_traits::value>> +{ + using iterator_category = std::random_access_iterator_tag; + using value_type = T; + using difference_type = ptrdiff_t; + using pointer = T*; + using reference = T&; +}; +} +} + +// #include + +// #include + + +#include + +// #include + + +// http://en.cppreference.com/w/cpp/experimental/is_detected +namespace nlohmann +{ +namespace detail +{ +struct nonesuch +{ + nonesuch() = delete; + ~nonesuch() = delete; + nonesuch(nonesuch const&) = delete; + void operator=(nonesuch const&) = delete; +}; + +template class Op, + class... Args> +struct detector +{ + using value_t = std::false_type; + using type = Default; +}; + +template class Op, class... Args> +struct detector>, Op, Args...> +{ + using value_t = std::true_type; + using type = Op; +}; + +template