diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..794c8e5c --- /dev/null +++ b/.editorconfig @@ -0,0 +1,18 @@ +# Configuration file for EditorConfig +# More information is available under http://EditorConfig.org + +# Ignore any other files further up in the file system +root = true + +# Configuration for all files +[*] +# Enforce Unix style line endings (\n only) +end_of_line = lf +# Always end files with a blank line +insert_final_newline = true +# Force space characters for indentation +indent_style = space +# Always indent by 2 characters +indent_size = 2 +# Remove whitespace characters at the end of line +trim_trailing_whitespace = true diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..c41f2ef6 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +# Normalize line endings to LF (Unix) on check-in and prevents +# conversion to CRLF (Windows) when the file is checked out. +text eol=lf + diff --git a/.gitignore b/.gitignore index 9345c8f3..8025e113 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,9 @@ version.hpp revision.cpp # project -build/ +build.*/ +build/* +!build.*/.placeholder .loc.yml install_options *.sublime-workspace @@ -12,6 +14,20 @@ install_options # ctags *.tags *.tags_sorted_by_file +local_env.cmake # nomlib's github wiki wiki + +*local_env.cmake* + +#.vscode/CMakeUserPresets.json + +node_modules + +# Visual Code search index extension +index.js + +# auto-generated documentation +docs/html/* +!docs/.placeholder diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..a1b48d7e --- /dev/null +++ b/.gitmodules @@ -0,0 +1,32 @@ +[submodule "vendor/SDL2_ttf.git"] + path = vendor/SDL2_ttf.git + url = https://github.com/libsdl-org/SDL_ttf.git + branch = release-2.0.12 + ignore = dirty +[submodule "vendor/SDL2_image.git"] + path = vendor/SDL2_image.git + url = https://github.com/libsdl-org/SDL_image.git + branch = release-2.0.0 + ignore = dirty +[submodule "vendor/SDL.git"] + path = vendor/SDL.git + url = https://github.com/libsdl-org/SDL + branch = release-2.0.4 + #branch = release-2.24.0 + ignore = dirty +[submodule "vendor/gtest.git"] + path = vendor/gtest.git + url = https://github.com/google/googletest.git + branch = v1.8.x + ignore = dirty +[submodule "vendor/libsndfile.git"] + path = vendor/libsndfile.git + url = https://github.com/libsndfile/libsndfile.git + branch = 1.0.25 + ignore = dirty +[submodule "vendor/openal-soft.git"] + path = vendor/openal-soft.git + url = https://github.com/kcat/openal-soft + branch = 9b6a226 + ignore = dirty + diff --git a/.travis.yml b/.travis.yml index b826c727..b3dc92a0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,7 +27,7 @@ before_install: # - xvfb-run -- :99 -ac -screen 0 1024x768x32 # - /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1024x768x32 - # - brew update + - brew update # - brew install cmake # FIXME: It might be best if we packaged the freetype2 libs for libRocket @@ -36,11 +36,15 @@ before_install: # NOM_DEPS_URL and NOM_OSX_SDK_URL has been set by our repository settings - wget -O /tmp/deps.tar.gz $NOM_DEPS_URL - - wget -O /tmp/osx10.7.sdk.tar.gz $NOM_OSX_SDK_URL + # NOTE: Ensure that nomlib builds against the latest OS X SDK instead of the + # minimum deployment version in the mean time -- until I get around to + # cleaning up this script and building for both the minimum and the latest + # SDKs... + # - wget -O /tmp/osx10.7.sdk.tar.gz $NOM_OSX_SDK_URL before_script: - tar -xzf /tmp/deps.tar.gz -C third-party - - sudo mkdir -p '/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs' - - sudo tar -xzf /tmp/osx10.7.sdk.tar.gz -C /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs + # - sudo mkdir -p '/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs' + # - sudo tar -xzf /tmp/osx10.7.sdk.tar.gz -C /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs - mkdir -p build && cd build - cmake -GXcode -DDEBUG=on -DDEBUG_ASSERT=on -DEXAMPLES=on -DNOM_BUILD_TESTS=on -DCMAKE_OSX_DEPLOYMENT_TARGET=10.7 -DCMAKE_INSTALL_PREFIX=~/Library/Frameworks .. script: diff --git a/.vscode/CMakeUserPresets.json b/.vscode/CMakeUserPresets.json new file mode 100644 index 00000000..e73ebbed --- /dev/null +++ b/.vscode/CMakeUserPresets.json @@ -0,0 +1,3 @@ +{ + "version": 10 +} diff --git a/.vscode/bin/add_rpath.sh b/.vscode/bin/add_rpath.sh new file mode 100755 index 00000000..e305dd6d --- /dev/null +++ b/.vscode/bin/add_rpath.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +# This script is intended to be ran from within the CMake environment. +# +# See cmake/macros.cmake, man 1 install_name_tool + +# install_name_tool +${1} -add_rpath ${2} ${3} + +# We **must** exit with a successful return code for Xcode-generated project +# builds, or else the whole build fails on us +exit 0 diff --git a/.vscode/bin/check_json.sh b/.vscode/bin/check_json.sh new file mode 100755 index 00000000..8dcabe70 --- /dev/null +++ b/.vscode/bin/check_json.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +# NOTE: This script is intended to be non-interactively ran by the project's +# build scripts. + +JSHINT_BIN=$(which jshint) + +if [[ -x $JSHINT_BIN ]]; then + # We only bother validating critical game files that would prevent us from + # initialization. + jshint ../Resources/cards.json + jshint ../Resources/config_game.json + jshint ../Resources/config_assets-low-res.json + jshint ../Resources/config_assets-hi-res.json +else + echo "${0} WARN: Skipping JSON validation; jshint not found!" + echo "See installation instructions at http://jshint.com/install/" + # exit 1 +fi diff --git a/.vscode/bin/clang_build.sh b/.vscode/bin/clang_build.sh new file mode 100755 index 00000000..868a9273 --- /dev/null +++ b/.vscode/bin/clang_build.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +# NOTE: This script is intended to be ran from the project's current build +# directory. + +BUILD_BIN=$(which make) + +# Default build configuration type +BUILD_TYPE="Debug" + +if [[ !( -z "${BUILD_TYPE_ARG}") ]]; then + BUILD_TYPE=$1 +fi + +if [[ !( -z "${NUM_THREADS}") ]]; then + NUM_THREADS_ARG="-j ${NUM_THREADS}" +fi +# echo "NUM_THREADS_ARG: ${NUM_THREADS_ARG}" + +../bin/check_json.sh + +if [[ $? -eq 0 ]]; then + echo "Building ${BUILD_TYPE} project... [target: build]" + + ${BUILD_BIN} ${NUM_THREADS_ARG} +else + echo "${0} ERROR: JSON validation failure has prevented building the project." + exit 1 +fi diff --git a/.vscode/bin/clang_clean.sh b/.vscode/bin/clang_clean.sh new file mode 100755 index 00000000..b516aaa2 --- /dev/null +++ b/.vscode/bin/clang_clean.sh @@ -0,0 +1,22 @@ +#!/bin/sh + +# NOTE: This script is intended to be ran from the project's current build +# directory. + +BUILD_BIN=$(which make) +BUILD_TYPE_ARG=$1 + +# Default +BUILD_TYPE="Debug" + +if [[ !( -z "${BUILD_TYPE_ARG}") ]]; then + BUILD_TYPE=$1 +fi + +if [[ !( -z "${NUM_THREADS}") ]]; then + NUM_THREADS_ARG="-j ${NUM_THREADS}" +fi +# echo "NUM_THREADS_ARG: ${NUM_THREADS_ARG}" + +echo "Building ${BUILD_TYPE} project... [target: build]" +${BUILD_BIN} clean diff --git a/.vscode/bin/clang_install.sh b/.vscode/bin/clang_install.sh new file mode 100755 index 00000000..28778eab --- /dev/null +++ b/.vscode/bin/clang_install.sh @@ -0,0 +1,22 @@ +#!/bin/sh + +# NOTE: This script is intended to be ran from the project's current build +# directory. + +BUILD_BIN=$(which make) +BUILD_TYPE_ARG=$1 + +# Default +BUILD_TYPE="Debug" + +if [[ !( -z "${BUILD_TYPE_ARG}") ]]; then + BUILD_TYPE=$1 +fi + +if [[ !( -z "${NUM_THREADS}") ]]; then + NUM_THREADS_ARG="-j ${NUM_THREADS}" +fi +# echo "NUM_THREADS_ARG: ${NUM_THREADS_ARG}" + +echo "Installing ${BUILD_TYPE} project... [target: build]" +${BUILD_BIN} install diff --git a/.vscode/bin/clang_uninstall.sh b/.vscode/bin/clang_uninstall.sh new file mode 100755 index 00000000..a9f377b1 --- /dev/null +++ b/.vscode/bin/clang_uninstall.sh @@ -0,0 +1,22 @@ +#!/bin/sh + +# NOTE: This script is intended to be ran from the project's current build +# directory. + +BUILD_BIN=$(which make) +BUILD_TYPE_ARG=$1 + +# Default +BUILD_TYPE="Debug" + +if [[ !( -z "${BUILD_TYPE_ARG}") ]]; then + BUILD_TYPE=$1 +fi + +if [[ !( -z "${NUM_THREADS}") ]]; then + NUM_THREADS_ARG="-j ${NUM_THREADS}" +fi +# echo "NUM_THREADS_ARG: ${NUM_THREADS_ARG}" + +echo "Building ${BUILD_TYPE} project... [target: build]" +${BUILD_BIN} uninstall diff --git a/.vscode/bin/configure.sh b/.vscode/bin/configure.sh new file mode 100755 index 00000000..bc902d0d --- /dev/null +++ b/.vscode/bin/configure.sh @@ -0,0 +1,97 @@ +#!/bin/sh + +# NOTE: This script is intended to be ran from the project's current build +# directory. + +# Enable case-insensitive string matches for passing arguments around +shopt -s nocasematch + +function usage_info() +{ + SCRIPT_NAME = $(basename $0) + echo "Usage: ${SCRIPT_NAME} \n" + echo "...where is one of Debug or Release" + echo " (Defaults: ${BUILD_TYPE})\n" + echo "...where is the engine's destination install prefix" + echo " (Defaults: ${BUILD_INSTALL_DIR})" + exit 0 +} + +MKDIR_BIN=$(which mkdir) # GNU coreutils +RM_BIN=$(which rm) # GNU coreutils +CMAKE_BIN=$(which cmake) + +# Default build configuration type +BUILD_TYPE_ARG=$1 +GEN_PROJECT_TYPE_ARG=$2 +BUILD_TYPE="Debug" +GEN_PROJECT_TYPE="" # i.e. CMake project generator: Xcode + +if [[ !( -z "${BUILD_TYPE_ARG}") ]]; then + BUILD_TYPE=${BUILD_TYPE_ARG} +fi + +if [[ !( -z "${GEN_PROJECT_TYPE_ARG}") ]]; then + GEN_PROJECT_TYPE="-G${GEN_PROJECT_TYPE_ARG}" +fi + +# The absolute path to CMake's local cache of project build variables +CMAKE_CACHE_FILE="$(pwd)/CMakeCache.txt" + +# Sane engine defaults for building apps against +# +# IMPORTANT: A space character **must** be inserted at the beginning of the +# BUILD_FLAGS string variable, or else the preprocessor flags will not be +# parsed correctly by cmake +BUILD_FLAGS+=" -DCMAKE_OSX_DEPLOYMENT_TARGET=10.7" +if [ -z "${NOMLIB_DEPS_ROOT}" ]; then + BUILD_FLAGS+=" -DNOMLIB_DEPS_PREFIX=${HOME}/Projects/nomlib.git/third-party/osx" +else + BUILD_FLAGS+=" -DNOMLIB_DEPS_PREFIX=${NOMLIB_DEPS_ROOT}" +fi + +# Default installation path +BUILD_INSTALL_DIR="$(pwd)" +BUILD_INSTALL_DIR_ARG=$3 + +# Check command arguments +if [[ $1 == "-h" || $1 == "--help" ]]; then + usage_info +else + # Set build configuration type + # + # IMPORTANT: A space character **must** be inserted at the beginning of the + # BUILD_FLAGS string variable, or else the preprocessor flags will not be + # parsed correctly by cmake + if [[ ${BUILD_TYPE_ARG} == "Debug" ]]; then + BUILD_FLAGS+=" -DDEBUG=on -DDEBUG_ASSERT=on" + elif [[ ${BUILD_TYPE_ARG} == "Release" ]]; then + BUILD_TYPE="Release" + BUILD_FLAGS+=" -DDEBUG=off -DDEBUG_ASSERT=off" + fi + + echo "BUILD_FLAGS=${BUILD_FLAGS}" + + if [[ !( -z "${BUILD_INSTALL_DIR_ARG}" ) ]]; then + # Override installation prefix path; it is best to let CMake deal with + # file path validation + BUILD_INSTALL_DIR=${3} + fi + + echo "\nClearing CMake cache..." + + if [[ -f "CMakeCache.txt" ]]; then + ${RM_BIN} -rf CMakeCache.txt + fi + + if [[ -d "CMakeFiles" ]]; then + ${RM_BIN} -rf CMakeFiles + fi + + echo "Generating ${BUILD_TYPE} project files..." + echo "BUILD_INSTALL_DIR: ${BUILD_INSTALL_DIR}" + + ${CMAKE_BIN} "$GEN_PROJECT_TYPE" ${BUILD_FLAGS} \ + -DCMAKE_INSTALL_PREFIX=${BUILD_INSTALL_DIR} \ + .. +fi diff --git a/.vscode/bin/ctags.sh b/.vscode/bin/ctags.sh new file mode 100755 index 00000000..cd44323a --- /dev/null +++ b/.vscode/bin/ctags.sh @@ -0,0 +1,14 @@ +#!/bin/sh +# +# Generate project tag file (symbol reference mappings) +# +# NOTE: This script should be ran from the project's root directory, i.e.: +# ~/Projects/nomlib.git/ +# + +SOURCE_DIR=src +HEADER_DIR=include/nomlib/ + +CTAGS_BIN=$(which ctags) + +${CTAGS_BIN} -R ${SOURCE_DIR} ${HEADER_DIR} diff --git a/.vscode/bin/uninstall.sh b/.vscode/bin/uninstall.sh new file mode 100755 index 00000000..2ca2a789 --- /dev/null +++ b/.vscode/bin/uninstall.sh @@ -0,0 +1,52 @@ +#!/bin/sh + +GAME_INSTALL_RECEIPT="./build/install_manifest.txt" +GAME_PREFS=${HOME}/Library/Application\ Support/TTcards +GAME_LOGS_ROOT=${HOME}/Library/Logs/DiagnosticReports/ +# GAME_LOGS_PATH="${GAME_LOGS_ROOT}/ttcards_*" +GAME_SAVED_APP_STATE=${HOME}/Library/Saved\ Application\ State/org.i8degrees.ttcards.savedState + +if [[ -f ${GAME_INSTALL_RECEIPT} ]]; then + echo "Remove game installation?" + read -n1 GAME_INSTALL_RECEIPT_ANSWER + + if [[ ${GAME_INSTALL_RECEIPT_ANSWER} == "Y" || ${GAME_INSTALL_RECEIPT_ANSWER} == "y" ]]; then + cat ${GAME_INSTALL_RECEIPT} + else + echo "Not removing ${GAME_INSTALL_RECEIPT}" + fi +fi + +if [[ -d "${GAME_PREFS}" ]]; then + echo "Remove game preferences?" + read -n1 GAME_PREFS_ANSWER + + if [[ ${GAME_PREFS_ANSWER} == "Y" || ${GAME_PREFS_ANSWER} == "y" ]]; then + /bin/ls -lhas "${GAME_PREFS}" + else + echo "Not removing ${GAME_PREFS}" + fi +fi + +# if [[ -d "${GAME_LOGS_ROOT}" ]]; then +# echo "Remove game logs?" +# read -n1 GAME_LOGS_ANSWER + +# if [[ ${GAME_LOGS_ANSWER} == "Y" || ${GAME_LOGS_ANSWER} == "y" ]]; then +# # /bin/ls -lhas "${GAME_LOGS_PATH}" +# /bin/ls -h ${GAME_LOGS_ROOT} | grep "ttcards_*" +# else +# echo "Not removing ${GAME_LOGS_ROOT}" +# fi +# fi + +if [[ -d "${GAME_SAVED_APP_STATE}" ]]; then + echo "Remove saved app state?" + read -n1 GAME_SAVED_APP_STATE_ANSWER + + if [[ ${GAME_SAVED_APP_STATE_ANSWER} == "Y" || ${GAME_SAVED_APP_STATE_ANSWER} == "y" ]]; then + /bin/ls -lhas "${GAME_SAVED_APP_STATE}" + else + echo "Not removing ${GAME_SAVED_APP_STATE}" + fi +fi diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 00000000..488c06e4 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,19 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "type": "cmake", + "label": "CMake: build", + "command": "make", + "problemMatcher": [], + "detail": "build project using clang" + }, + { + "type": "cmake", + "label": "CMake: configure", + "command": "cmake -DCMAKE_C_COMPILER=/usr/bin/clang -DCMAKE_CXX_COMPILER=/usr/bin/clang++ -DOpenGL_GL_PREFERENCE=LEGACY -DDEBUG=on -DDEBUG_ASSERT=on -DEXAMPLES=off -DNOM_BUILD_TESTS=off build", + "problemMatcher": [], + "detail": "cmake setup build" + } + ] +} diff --git a/CMakeLists.txt b/CMakeLists.txt index 4728edd8..553f60d4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,7 @@ -cmake_minimum_required( VERSION 2.8.12 ) +#cmake_minimum_required( VERSION 2.8.12 ) +cmake_minimum_required( VERSION 3.14 ) + +include ( "${CMAKE_SOURCE_DIR}/version.cmake" ) # Enable the use of MACOSX_RPATH by default; this effectively allows plug 'n' # play functionality, so to speak -- the resulting shared library files can @@ -17,43 +20,69 @@ endif(POLICY CMP0042) # CMake Environment +# `find_package()` uses upper-case `PACKAGENAME_ROOT` variables. +# cmake --help-policy CMP0144 +# Minimum required CMake v3.27 +if(POLICY CMP0144) + cmake_policy(SET CMP0144 NEW) +endif(POLICY CMP0144) + +# PythonInterp modules; acknowledgement +# cmake --help-policy CMP0148 +#cmake_policy(SET CMP0148 NEW) + # Opt out of using CMake v3.0 PROJECT_VERSION variables management for the # project. # http://www.cmake.org/cmake/help/v3.0/command/project.html#command:project -if( POLICY CMP0048 ) - cmake_policy( SET CMP0048 OLD ) -endif( POLICY CMP0048 ) +#if( POLICY CMP0048 ) +#cmake_policy( SET CMP0048 OLD ) +#endif( POLICY CMP0048 ) + +if(POLICY CMP0072) + cmake_policy(SET CMP0072 NEW) +endif(POLICY CMP0072) # set ( CMAKE_VERBOSE_MAKEFILE OFF CACHE PATH "Verbose Makefile" ) set ( CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules" ) set ( CMAKE_TEMPLATE_PATH ${CMAKE_TEMPLATE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/templates" ) -set ( PROJECT_VERSION_MAJOR 0 ) -set ( PROJECT_VERSION_MINOR 11 ) -set ( PROJECT_VERSION_PATCH 0 ) - # Project Options option ( DEBUG "Build with all debugging features" off ) option ( DEBUG_ASSERT "Build with run-time assertions enabled" off ) -option ( EXAMPLES "Build nomlib usage examples" off ) +# ?? Replace DEBUG_ASSERT option with the following; this should result in assertions +# ?? being left on unless explicitly being disabled when DEBUG builds are present. +#option( DISABLE_DEBUG_ASSERTIONS "" off) +option( EXAMPLES "Build nomlib usage examples" off ) option( NOM_BUILD_TESTS "Build unit tests" off ) -set( NOM_INSTALL_GENERATED_DOCS off ) - -option( NOM_BUILD_CORE_UNIT "Engine core" ON ) -option( NOM_BUILD_MATH_UNIT "Math utilities" ON ) -option( NOM_BUILD_FILE_UNIT "Filesystem access" ON ) -option( NOM_BUILD_AUDIO_UNIT "Audio subsystem" ON ) -option( NOM_BUILD_GRAPHICS_UNIT "Graphics core" ON ) -option( NOM_BUILD_EXTRA_RESCALE_ALGO_UNIT "Build with scale2x & hqx algorithms" ON ) -option( NOM_BUILD_SYSTEM_UNIT "System unit" ON ) -option( NOM_BUILD_PTREE_UNIT "Generic container for serialization" ON ) -option( NOM_BUILD_SERIALIZERS_UNIT "Serialization for JSON, XML, HTML" ON ) -option( NOM_BUILD_GUI_UNIT "GUI subsystem" ON ) - -project( nomlib ) # Sets PROJECT_NAME variable for us +option( NOM_INSTALL_GENERATED_DOCS "Build docs" off ) + +option( NOM_BUILD_ACTIONS_UNIT "Animations subsystem" ON) +option( NOM_BUILD_CORE_UNIT "Engine core" ON) +option( NOM_BUILD_MATH_UNIT "Math utilities" ON) +option( NOM_BUILD_FILE_UNIT "Filesystem access" ON) +option( NOM_BUILD_AUDIO_UNIT "Audio subsystem" OFF) +option( NOM_BUILD_GRAPHICS_UNIT "Graphics core" ON) +option( NOM_BUILD_EXTRA_RESCALE_ALGO_UNIT "Build with scale2x & hqx algorithms" ON) +option( NOM_BUILD_SYSTEM_UNIT "System unit" ON) +option( NOM_BUILD_PTREE_UNIT "Generic container for serialization" ON) +option( NOM_BUILD_SERIALIZERS_UNIT "Serialization for JSON, XML, HTML" ON) +option( NOM_BUILD_GUI_UNIT "GUI subsystem" ON) + +# IMPORTANT(JEFF): This initializes our engine versioning variables; +# `PROJECT_VERSION_MAJOR` & friends at `include/nomlib/version.hpp.in` +project( nomlib + VERSION "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}" + DESCRIPTION "C++11 2D game framework using SDL2" + #HOMEPAGE_URL "https://github.com/i8degrees/nomlib" + #SPDX_LICENSE "BSD-2-Clause-FreeBSD" + #LANGUAGES "en" +) # Platform detection include ( "${CMAKE_SOURCE_DIR}/cmake/platform.cmake" ) +# Utility macro helpers +include ( "${CMAKE_CURRENT_SOURCE_DIR}/cmake/functions.cmake" ) +include ( "${CMAKE_CURRENT_SOURCE_DIR}/cmake/macros.cmake" ) if ( PLATFORM_WINDOWS ) # Building nomlib on Windows is only supported as a static library for the @@ -64,13 +93,24 @@ else ( NOT PLATFORM_WINDOWS ) set ( BUILD_SHARED_LIBS on ) endif ( PLATFORM_WINDOWS ) +# TODO: We should be setting CMAKE_BUILD_TYPE directly from the command line +# instead of DEBUG +set ( CMAKE_VERBOSE_MAKEFILE ON ) if ( DEBUG ) - # set ( CMAKE_VERBOSE_MAKEFILE ON ) set ( CMAKE_BUILD_TYPE "Debug" ) + # >> Explicit `DEBUG_ASSERT` option will be deprecated in a future version... + if ( NOT DISABLE_DEBUG_ASSERTIONS ) + set ( DEBUG_ASSERT on ) + endif ( NOT DISABLE_DEBUG_ASSERTIONS ) if ( NOT PLATFORM_WINDOWS ) - # TODO: Enable -Wextra - set ( CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -D NOM_DEBUG -Wall -Wunused -O0" ) #-D_GLIBCXX_DEBUG + # TODO: Enable -Wsign-conversion, -Wextra + # TODO: Consider enabling some of these run-time checks that clang offers, + # i.e.:: -fsanitize=address, -fsanitize=integer, -fsanitize=undefined, + # -fsanitize=unsigned-integer-overflow, -fsanitize=float-divide-by-zero, etc. + # See also: http://clang.llvm.org/docs/UsersManual.html#controlling-code-generation + # See also: Malloc Debugging under OSX: https://developer.apple.com/library/mac/documentation/Performance/Conceptual/ManagingMemory/Articles/MallocDebug.html#//apple_ref/doc/uid/20001884-CJBJFIDD + set ( CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -D NOM_DEBUG -Wall -Wunused -O0" ) # Measure the time it takes per source file to build, so we can try # speeding up slow builds! @@ -82,7 +122,7 @@ if ( DEBUG ) message ( STATUS "Building ${PROJECT_NAME} with debugging." ) message ( STATUS "CMAKE_CXX_FLAGS_DEBUG: ${CMAKE_CXX_FLAGS_DEBUG}" ) -else () # Build with optimizations for maximum speed and minimal size +else ( NOT DEBUG ) # Build with optimizations for maximum speed and minimal size set ( CMAKE_BUILD_TYPE "Release" ) message ( STATUS "Building ${PROJECT_NAME} with high speed, low drag!" ) message ( STATUS "CMAKE_CXX_FLAGS_RELEASE: ${CMAKE_CXX_FLAGS_RELEASE}" ) @@ -102,13 +142,12 @@ else( NOT BUILD_SHARED_LIBS ) set( LIBRARY_OUTPUT_TYPE "STATIC" ) endif( BUILD_SHARED_LIBS ) -# Utility macro helpers -include ( "${CMAKE_CURRENT_SOURCE_DIR}/cmake/macros.cmake" ) - # TODO: Rename to NOMLIB_SRC_DIR..? set( SRC_DIR "${PROJECT_SOURCE_DIR}/src" ) set( NOMLIB_RESOURCES_DIR "${PROJECT_SOURCE_DIR}/Resources" ) +set( NOMLIB_SHARED_SUPPORT_DIR "${NOMLIB_RESOURCES_DIR}/SharedSupport" ) + # TODO: Rename to NOMLIB_INCLUDE_ROOT_DIR..? set( INC_ROOT_DIR "${PROJECT_SOURCE_DIR}/include" ) @@ -195,8 +234,9 @@ if ( PLATFORM_WINDOWS ) set( LIBROCKET_REDIST_DIR "${NOMLIB_DEPS_DIR}/windows/libRocket/lib/${PLATFORM_ARCH}/" ) set( MSVCPP_REDIST_DIR "${NOMLIB_DEPS_DIR}/windows/msvcpp2013/${PLATFORM_ARCH}/" ) +endif ( PLATFORM_WINDOWS ) -elseif ( PLATFORM_OSX ) +if ( PLATFORM_OSX ) if(NOT DEFINED ENV{SDL2DIR}) set(ENV{SDL2DIR} "${NOMLIB_DEPS_DIR}/osx") @@ -229,10 +269,38 @@ elseif ( PLATFORM_OSX ) # version installed under /usr/local set( LIBROCKET_EXT_REDIST_FILES "${NOMLIB_DEPS_DIR}/osx/librocket/bin/libfreetype.6.dylib" ) +endif ( PLATFORM_OSX ) -elseif ( PLATFORM_LINUX ) - # TODO -endif ( PLATFORM_WINDOWS ) +if ( PLATFORM_LINUX OR PLATFORM_POSIX ) + + if(NOT DEFINED ENV{SDL2DIR}) + set(ENV{SDL2DIR} "${NOMLIB_DEPS_DIR}/linux/sdl2") + endif(NOT DEFINED ENV{SDL2DIR}) + + if(NOT DEFINED ENV{SDL2IMAGEDIR}) + set(ENV{SDL2IMAGEDIR} "${NOMLIB_DEPS_DIR}/linux/sdl2_image") + endif(NOT DEFINED ENV{SDL2IMAGEDIR}) + + if(NOT DEFINED ENV{SDL2TTFDIR}) + set(ENV{SDL2TTFDIR} "${NOMLIB_DEPS_DIR}/linux/sdl2_ttf") + endif(NOT DEFINED ENV{SDL2TTFDIR}) + + if(NOT DEFINED ENV{OPENALDIR}) + # Use platform-distributed headers for OpenAL + #set(ENV{OPENALDIR} "${NOMLIB_DEPS_DIR}/linux/openal") + # FIXME(JEFF): This is a temporary workaround until we have a proper distribution + # build of OpenAL-Soft, sourced from `vendor/openal-soft.git` + set(ENV{OPENALDIR} "/usr/include/AL") + endif(NOT DEFINED ENV{OPENALDIR}) + + if(NOT DEFINED ENV{LIBSNDFILEDIR}) + set(ENV{LIBSNDFILEDIR} "${NOMLIB_DEPS_DIR}/linux/libsndfile") + endif(NOT DEFINED ENV{LIBSNDFILEDIR}) + + if(NOT DEFINED ENV{LIBROCKETDIR}) + set(ENV{LIBROCKETDIR} "${NOMLIB_DEPS_DIR}/linux/libRocket") + endif(NOT DEFINED ENV{LIBROCKETDIR}) +endif( PLATFORM_LINUX OR PLATFORM_POSIX ) # List of our source code to be compiled add_subdirectory( ${SRC_DIR} ) @@ -312,15 +380,24 @@ if( PLATFORM_OSX AND FRAMEWORK ) # ) # endif( NOM_INSTALL_GENERATED_DOCS ) -# POSIX install layout scheme is the default installation scheme. This is -# applicable to OS X when FRAMEWORK=off. -elseif( NOM_PLATFORM_POSIX OR PLATFORM_WINDOWS ) - - # Install nomlib's header files - install( DIRECTORY ${INC_DIR} - DESTINATION "include" # i.e.: /usr/local/include/nomlib/ - PATTERN ".*" EXCLUDE - ) + # Install hardware definitions for input devices (game controllers) + install( DIRECTORY "${NOMLIB_SHARED_SUPPORT_DIR}/InputDevices" + DESTINATION "${PROJECT_NAME}.framework/Resources/SharedSupport" + OPTIONAL + PATTERN ".*" EXCLUDE ) +endif ( PLATFORM_OSX AND FRAMEWORK ) + +# POSIX install layout scheme is the default installation scheme. +# Additionally, the following applies to Darwin platform when MacOS library +# framework targets are disabled (FRAMEWORK=0) +if( NOT FRAMEWORK ) + + # Install nomlib's header files to system-wide path for other apps to use + install ( DIRECTORY ${INC_DIR} + DESTINATION "include" + PATTERN "." EXCLUDE # dotfiles, i.e. .DS_Store, .gitignore + PATTERN "*.in" EXCLUDE # cmake generated files, i.e.: config.hpp.in + ) # Install nomlib's icon install( FILES @@ -342,6 +419,14 @@ elseif( NOM_PLATFORM_POSIX OR PLATFORM_WINDOWS ) "CMake" PATTERN ".*" EXCLUDE ) + + # Install hardware definitions for input devices (game controllers) + install( DIRECTORY "${NOMLIB_SHARED_SUPPORT_DIR}/InputDevices" + DESTINATION "SharedSupport" + OPTIONAL + PATTERN ".*" EXCLUDE ) + + else( NOT PLATFORM_WINDOWS ) # i.e.: /usr/local/share/nomlib/CMake install( DIRECTORY @@ -350,6 +435,14 @@ elseif( NOM_PLATFORM_POSIX OR PLATFORM_WINDOWS ) "share/${PROJECT_NAME}/CMake" PATTERN ".*" EXCLUDE ) + # Install cmake find modules at system-wide install prefix for locating + # nomlib + install( DIRECTORY + "${NOMLIB_RESOURCES_DIR}/CMake/" + DESTINATION + "lib/cmake/${PROJECT_NAME}" + PATTERN ".*" EXCLUDE + ) endif( PLATFORM_WINDOWS ) # Install software license & general project information @@ -366,12 +459,7 @@ elseif( NOM_PLATFORM_POSIX OR PLATFORM_WINDOWS ) PATTERN ".*" EXCLUDE ) endif( NOM_INSTALL_GENERATED_DOCS ) - -endif() - -if ( PLATFORM_LINUX ) - install(CODE "MESSAGE(\"Post-install: sudo ldconfig.\")") # FIXME (prettify) -endif ( PLATFORM_LINUX ) +endif ( NOT FRAMEWORK ) # nomlib examples configuration if( EXAMPLES ) @@ -413,6 +501,9 @@ include(GetGitRevisionNumber) git_rev_number(GIT_REVISION "--short") set(GIT_REVISION "${GIT_REVISION}") +# TODO(JEFF): This needs to be tested! +#configure_file( ${INCLUDE_DIR}/version.hpp.in ${INCLUDE_DIR}/version.hpp ) + configure_file( ${SRC_DIR}/revision.cpp.in ${SRC_DIR}/revision.cpp ) # Uninstall target support; 'make uninstall' diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 00000000..06a09d58 --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,147 @@ +{ + "version": 8, + "$schema": "https://cmake.org/cmake/help/latest/_downloads/3e2d73bff478d88a7de0de736ba5e361/schema.json", + "configurePresets": [ + { + "name": "common", + "hidden": true, + "displayName": "Common settings for configure phase", + "description": "cmake configure phase configuration for building nomlib", + "generator": "Unix Makefiles", + "binaryDir": "${sourceDir}/build/${presetName}", + "cacheVariables": { + "CMAKE_C_COMPILER": "/usr/bin/clang", + "CMAKE_CXX_COMPILER": "/usr/bin/clang++" + } + }, + { + "name": "debug-clang", + "displayName": "Debug configuration for clang toolchain", + "inherits": "common", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "DEBUG": "on", + "CMAKE_C_COMPILER": "/usr/bin/clang", + "CMAKE_CXX_COMPILER": "/usr/bin/clang++", + "DEBUG_ASSERT": "on" + } + }, + { + "name": "release-clang", + "displayName": "Release configuration for clang toolchain", + "inherits": "common", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "DEBUG": "off", + "DEBUG_ASSERT": "off" + } + }, + { + "name": "debug-clang-cl", + "displayName": "Debug configuration for clang-cl toolchain", + "inherits": "common", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "DEBUG": "on", + "DEBUG_ASSERT": "on", + "CMAKE_C_COMPILER": "/usr/bin/clang-cl", + "CMAKE_CXX_COMPILER": "/usr/bin/clang-cl" + } + }, + { + "name": "release-clang-cl", + "displayName": "Release configuration for clang-cl toolchain", + "inherits": "common", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "CMAKE_C_COMPILER": "/usr/bin/clang-cl", + "CMAKE_CXX_COMPILER": "/usr/bin/clang-cl" + } + }, + { + "name": "debug-clang-linux", + "displayName": "debug-clang-linux", + "inherits": "debug-clang", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "DEBUG", + "CMAKE_INSTALL_PREFIX": "/usr/local", + "OPENGL_GL_PREFERENCE": "GLVND", + "EXAMPLES": "ON", + "NOM_BUILD_TESTS": "ON", + "NOMLIB_DEPS_DIR": "${sourceDir}/third-party", + "GTEST_INCLUDE_DIR": "${sourceDir}/third-party/linux/gtest/include", + "NOM_USE_OPENAL": "1" + }, + "condition": { + "type": "equals", + "lhs": "$hostSystemName", + "rhs": "Linux" + } + }, + { + "name": "release-clang-linux", + "inherits": "release-clang", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "RELEASE", + "OPENGL_GL_PREFERENCE": "GLVND", + "EXAMPLES": "ON", + "NOM_BUILD_TESTS": "ON", + "CMAKE_INSTALL_PREFIX": "/usr/local", + "NOMLIB_DEPS_DIR": "${sourceDir}/third-party", + "GTEST_INCLUDE_DIR": "${sourceDir}/third-party/linux/gtest/include", + "NOM_USE_OPENAL": "1" + }, + "condition": { + "type": "equals", + "lhs": "$hostSystemName", + "rhs": "Linux" + } + } + ], + "buildPresets": [ + { + "name": "build-debug-clang-linux", + "description": "Build debug target using clang toolchain", + "displayName": "build-debug-clang-linux", + "configurePreset": "debug-clang-linux", + "condition": { + "type": "equals", + "lhs": "$hostSystemName", + "rhs": "Linux" + } + }, + { + "name": "build-release-clang-linux", + "description": "Build release target using clang toolchain", + "displayName": "build-release-clang-linux", + "configurePreset": "release-clang-linux", + "condition": { + "type": "equals", + "lhs": "$hostSystemName", + "rhs": "Linux" + } + }, + { + "name": "build-debug-clang-cl-windows", + "description": "Build debug target using clang-cl toolchain", + "displayName": "build-debug-clang-cl-windows", + "configurePreset": "debug-clang-cl", + "condition": { + "type": "equals", + "lhs": "$hostSystemName", + "rhs": "Windows" + } + }, + { + "name": "build-release-clang-cl-windows", + "description": "Build release target using clang-cl toolchain", + "displayName": "build-release-clang-cl-windows", + "configurePreset": "release-clang-cl", + "condition": { + "type": "equals", + "lhs": "$hostSystemName", + "rhs": "Windows" + } + } + ] +} diff --git a/CMakeUserPresets.json b/CMakeUserPresets.json new file mode 120000 index 00000000..8feda0cb --- /dev/null +++ b/CMakeUserPresets.json @@ -0,0 +1 @@ +.vscode/CMakeUserPresets.json \ No newline at end of file diff --git a/PlayAudioSource_v2.cpp b/PlayAudioSource_v2.cpp new file mode 100644 index 00000000..26cfe1a4 --- /dev/null +++ b/PlayAudioSource_v2.cpp @@ -0,0 +1,324 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014, 2015, 2016 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#include "nomlib/actions/PlayAudioSource.hpp" + +// Private headers +#include "nomlib/core/unique_ptr.hpp" +#include "nomlib/math/math_helpers.hpp" +#include "nomlib/audio/audio_defs.hpp" +#include "nomlib/audio/IOAudioEngine.hpp" + +#include + +// Forward declarations +#include "nomlib/audio/libsndfile/SoundFileReader.hpp" +#include "nomlib/audio/SoundBuffer.hpp" +#include "nomlib/audio/AL/SoundSource.hpp" + +namespace nom { + +// Static initializations +const char* PlayAudioSource::DEBUG_CLASS_NAME = "[PlayAudioSource]:"; + +PlayAudioSource:: +PlayAudioSource(audio::IOAudioEngine* dev, const char* filename) +{ + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE_ACTION, + nom::NOM_LOG_PRIORITY_VERBOSE); + + audio::SoundInfo metadata = {}; + this->impl_ = dev; + this->elapsed_frames_ = 0.0f; + + this->fp_ = new audio::SoundFileReader(); + NOM_ASSERT(this->fp_ != nullptr); + if(this->fp_ != nullptr) { + + if(this->fp_->open(filename, metadata) == false) { + return; + } + + if(this->fp_->valid() == false) { + return; + } + + auto samples_per_second = metadata.sample_rate; + auto num_channels = metadata.channel_count; + auto channel_format = metadata.channel_format; + + this->audible_.resize(nom::audio::NUM_SOURCES); + + // NOTE(jeff): Create a RAM buffer that can hold one second (1s) of audio + // samples + for(auto buffer_idx = 0; buffer_idx != audio::NUM_SOURCES; ++buffer_idx) { + audio::SoundBuffer* buffer = + audio::create_buffer_memory(samples_per_second, num_channels, channel_format); + if(buffer == nullptr) { + return; + } + + if(audio::write_info(buffer, metadata) == false) { + return; + } + + this->set_duration(buffer->duration); + buffer->elapsed_seconds = this->timer_.ticks(); + + this->audible_.push_back(*buffer); + } + + // TODO(jeff): Validity check..? + // if(audio::write_info(this->audible_, metadata) == false) { + // return; + // } + + // if(this->audible_ != nullptr) { + // this->set_duration(&this->audible_[buffer_idx].duration); + // } + + // this->audible_->elapsed_seconds = this->timer_.ticks(); + } +} + +PlayAudioSource:: +PlayAudioSource(audio::IOAudioEngine* dev, audio::SoundBuffer* buffer) +{ + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE_ACTION, + nom::NOM_LOG_PRIORITY_VERBOSE); + + NOM_ASSERT_INVALID_PATH("TODO"); + + this->impl_ = dev; + this->elapsed_frames_ = 0.0f; + // this->audible_ = buffer; + + if(buffer != nullptr) { + this->set_duration(buffer->duration); + } +} + +PlayAudioSource::~PlayAudioSource() +{ + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE_ACTION, + nom::NOM_LOG_PRIORITY_VERBOSE); +} + +std::unique_ptr PlayAudioSource::clone() const +{ + return( nom::make_unique( self_type(*this) ) ); +} + +IActionObject::FrameState +PlayAudioSource::update(real32 t, uint8 b, int16 c, real32 d) +{ + real32 delta_time = t; + auto status = this->status(); + const real32 duration = d; + const auto speed = this->speed(); + auto current_frame = this->curr_frame_; + + uint32 audio_state = + audio::state(&this->audible_[current_frame], this->impl_); + + // Clamp delta values that go beyond the time duration bounds; this adds + // stability to variable time steps + if(delta_time > (duration / speed)) { + delta_time = duration / speed; + } + + // TODO(jeff): Verify alignment of this variable in regards to reading at + // random from the samples data; I'm pretty sure that this **DOES** matter! + auto input_pos = this->input_pos_; + int16* samples = NOM_SCAST(int16*, this->audible_[current_frame].samples); + auto format = this->audible_[current_frame].channel_format; + auto sample_count = (this->audible_[current_frame].sample_count); + auto samples_per_second = (this->audible_[current_frame].sample_rate); + + // Cache up to two seconds (2s) worth of audio data + + if(this->audible_[current_frame].samples_read < sample_count) { + this->audible_[current_frame].samples_read += + this->fp_->read(samples + input_pos, format, samples_per_second); + this->input_pos_ += samples_per_second; + + NOM_LOG_INFO(NOM_LOG_CATEGORY_TEST, "samples read:", + this->audible_[current_frame].samples_read); + + // FIXME(jeff): We shouldn't ever be calling from this pointer directly! + if(this->impl_ != nullptr) { + + // if(this->audible_->samples != nullptr) { + // this->release(); + // } + + // this->impl_->push_buffer(this->audible_); + this->impl_->queue_buffer(&this->audible_[current_frame]); + ++this->curr_frame_; + } + } + // } +NOM_DUMP(curr_frame_); + audio_state = audio::state(&this->audible_[current_frame], this->impl_); + + // Continue playing the animation only when we are inside our frame duration + // bounds; this adds stability to variable time steps + if(delta_time < (duration / speed) && this->audible_[current_frame].samples_read != 0) + // if(delta_time < (duration / speed) && + // if( + // audio_state != audio::AUDIO_STATE_STOPPED) + { + if(audio_state != audio::AUDIO_STATE_PLAYING && + this->audible_[curr_frame_].samples_read >= sample_count) + { + NOM_LOG_INFO(NOM_LOG_CATEGORY_TEST, "play!"); + audio::play(&this->audible_[curr_frame_], this->impl_); + } + + status = FrameState::PLAYING; + this->set_status(status); + } else { + this->last_frame(delta_time); + status = FrameState::COMPLETED; + this->set_status(status); + } + + return status; +} + +IActionObject::FrameState PlayAudioSource::next_frame(real32 delta_time) +{ + delta_time = this->timer_.to_seconds(); + + // if(this->audible_) { + this->audible_[curr_frame_].elapsed_seconds = this->timer_.ticks(); + // } + + this->first_frame(delta_time); + + return this->update(delta_time, 0.0f, 0.0f, this->duration()); +} + +IActionObject::FrameState PlayAudioSource::prev_frame(real32 delta_time) +{ + delta_time = this->timer_.to_seconds(); + // if(this->audible_) { + this->audible_[curr_frame_].elapsed_seconds = this->timer_.ticks(); + // } + + this->first_frame(delta_time); + + return this->update(delta_time, 0.0f, 0.0f, this->duration()); +} + +void PlayAudioSource::pause(real32 delta_time) +{ + this->timer_.pause(); + + // if(this->audible_) { + for(auto buffer_idx = 0; buffer_idx != audio::NUM_SOURCES; ++buffer_idx) { + audio::pause(&this->audible_[buffer_idx], this->impl_); + this->audible_[buffer_idx].elapsed_seconds = this->timer_.ticks(); + } + // } +} + +void PlayAudioSource::resume(real32 delta_time) +{ + this->timer_.unpause(); + + // if(this->audible_) { + // this->audible_->elapsed_seconds = this->timer_.ticks(); + // } + for(auto buffer_idx = 0; buffer_idx != audio::NUM_SOURCES; ++buffer_idx) { + this->audible_[buffer_idx].elapsed_seconds = this->timer_.ticks(); + audio::resume(&this->audible_[buffer_idx], this->impl_); + } +} + +void PlayAudioSource::rewind(real32 delta_time) +{ + // ...Reset the animation... + this->elapsed_frames_ = 0.0f; + this->timer_.stop(); + this->set_status(FrameState::PLAYING); + + // if(this->audible_ != nullptr) { + // audio::stop(this->audible_[curr_frame_], this->impl_); + // this->audible_->samples_read = 0; + // } + + for(auto buffer_idx = 0; buffer_idx != audio::NUM_SOURCES; ++buffer_idx) { + audio::stop(&this->audible_[buffer_idx], this->impl_); + this->audible_[buffer_idx].samples_read = 0; + } +} + +void PlayAudioSource::release() +{ + auto num_buffers = this->audible_.size(); + for(auto buffer_idx = 0; buffer_idx != num_buffers; ++buffer_idx) { + // audio::stop(this->audible_[buffer_idx], this->impl_); + + // audio::free_buffer(this->audible_, this->impl_); + audio::free_samples(this->audible_[buffer_idx].channel_format, + this->audible_[buffer_idx].samples); + // this->audible_[buffer_idx].buffer_id = 0; + } + // audio::free_buffer(this->audible_, this->impl_); + +} + +// Private scope + +void PlayAudioSource::first_frame(real32 delta_time) +{ + if(this->timer_.started() == false) { + this->timer_.start(); + + NOM_LOG_INFO(NOM_LOG_CATEGORY_ACTION, DEBUG_CLASS_NAME, + "BEGIN at", delta_time); + + // audio::play(this->audible_, this->impl_); + } +} + +void PlayAudioSource::last_frame(real32 delta_time) +{ + NOM_LOG_INFO(NOM_LOG_CATEGORY_ACTION, DEBUG_CLASS_NAME, + "END at", delta_time); + + this->timer_.stop(); + + for(auto buffer_idx = 0; buffer_idx != audio::NUM_SOURCES; ++buffer_idx) { + audio::stop(&this->audible_[buffer_idx], this->impl_); + this->audible_[buffer_idx].samples_read = 0; + } +} + +} // namespace nom diff --git a/README.md b/README.md index 5f0aa7a9..72305fff 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # nomlib -C++11 cross-platform 2D game engine (SDL2 powered). +[![Build Status](https://travis-ci.org/i8degrees/nomlib.png?branch=master,dev)](https://travis-ci.org/i8degrees/nomlib) + +C++11 2D game framework using SDL2 ## Projects Using nomlib @@ -16,18 +18,16 @@ First, ensure that you have the following core dependencies installed: Next, you should visit the dependencies section for your platform. After these mundane chores are complete, you should be ready to start the building process for your platform! -### Mac OS X - -master branch: [![Build Status](https://travis-ci.org/i8degrees/nomlib.png?branch=master)](https://travis-ci.org/i8degrees/nomlib) +```shell +git clone "https://github.com/i8degrees/nomlib.git" nomlib.git +mkdir build && cd build +``` -dev branch: [![Build Status](https://travis-ci.org/i8degrees/nomlib.png?branch=dev)](https://travis-ci.org/i8degrees/nomlib) +### Mac OS X After you have the dependencies taken care of, execute the following commands at your terminal prompt: -``` -git clone https://github.com/i8degrees/nomlib -cd nomlib -mkdir build && cd build +```shell # XCode project files can be generated by passing -GXcode cmake -DDEBUG=on -DDEBUG_ASSERT=on -DEXAMPLES=off -DNOM_BUILD_TESTS=on -DCMAKE_OSX_DEPLOYMENT_TARGET=10.7 -DCMAKE_INSTALL_PREFIX=~/Library/Frameworks .. make @@ -39,12 +39,45 @@ Upon a successful build, you should have the library modules built as OS X frame ### Linux -Linux builds are broken at the moment. +A Linux build is presently under way and can be tracked via the `feature/LinuxPort` git branch. Back in development years of nomlib, circa 2014, `clang` was not a viable build platform (yet!) for Linux. But, now in 2025, the switch over to has aided my porting work. I now have approximately `90%` of the porting work completed. + +- 2016 + - ~~My current platform build options for the feature/LinuxPort branch~~ + +```shell +# 2016 +# My current platform build options for the feature/LinuxPort branch +cmake -DCMAKE_C_COMPILER=/usr/bin/clang -DCMAKE_CXX_COMPILER=/usr/bin/clang++ -DDEBUG=on -DDEBUG_ASSERT=on -DEXAMPLES=off -DNOM_BUILD_TESTS=on -DCMAKE_POLICY_VERSION_MINIMUM=3.5 .. +make +#make test +#make install +``` + +- `2026` Latest platform default build set + +```shell +# 2026 +cmake -S /home/jeff/Projects/nomlib.git -B /tmp/nom-debug \ +-DCMAKE_CXX_COMPILER=/usr/bin/clang++ -DDEBUG=on -DDEBUG_ASSERT=on -DEXAMPLES=on -DNOM_BUILD_TESTS=on +#-DGTEST_ROOT= -DGTEST_INCLUDE_DIR= -DGTEST_LIBRARY= +``` + +```shell +# debug +cmake -S /home/jeff/Projects/nomlib.git -B /tmp/nomlib-debug \ +-DCMAKE_CXX_COMPILER=/usr/bin/clang++ -DDEBUG=on -DDEBUG_ASSERT=on -DEXAMPLES=on -DNOM_BUILD_TESTS=on +#-DGTEST_ROOT=/home/jeff/Projects/nomlib.git/third-party/linux/gtest -DGTEST_INCLUDE_DIR=/home/jeff/Projects/nomlib.git/third-party/linux/gtest/include -DGTEST_LIBRARY=/home/jeff/Projects/nomlib.git/third-party/linux/gtest/lib/libgtest.a + +# release +cmake -S /home/jeff/Projects/nomlib.git -B /tmp/nomlib-release \ +-DCMAKE_CXX_COMPILER=/usr/bin/clang++ -DDEBUG=off -DDEBUG_ASSERT=off -DEXAMPLES=on -DNOM_BUILD_TESTS=on +-DGTEST_ROOT=/home/jeff/Projects/nomlib.git/third-party/linux/gtest -DGTEST_INCLUDE_DIR=/home/jeff/Projects/nomlib.git/third-party/linux/gtest/include -DGTEST_LIBRARY=/home/jeff/Projects/nomlib.git/third-party/linux/gtest/lib/libgtest.a +``` ~~Until I get around to writing the proper instructions, you may take a look at my .travis.yml build script in the project root of nomlib for hints!~~ -#### Linux Build Status ##### +#### Linux Build Status [![Build Status](https://travis-ci.org/i8degrees/nomlib.png?branch=master,dev)](https://travis-ci.org/i8degrees/nomlib) @@ -52,11 +85,18 @@ Linux builds are broken at the moment. **NOTE:** Alpha build quality. This has only been tested on MS Windows 7. -After you have the dependencies taken care of, execute the following commands at the DOS prompt: +```console +# Download engine dependencies (binaries only) +set NOM_DEPS_URL="http://downloads.sourceforge.net/project/nomlib/windows/2015-03-15_nomlib-ed23aff_windows-dependencies.zip?r=http%3A%2F%2Fsourceforge.net%2Fprojects%2Fnomlib%2Ffiles%2Fwindows%2F&ts=1448178827&use_mirror=iweb" -``` -git clone https://github.com/i8degrees/nomlib +wget -O c:\temp\deps.zip %NOM_DEPS_URL% + +git clone https://github.com/i8degrees/nomlib.git cd nomlib + +# Install dependency binaries +7z x c:\temp\deps.zip -othird-party + mkdir build && cd build cmake -G"Visual Studio 12" -DARCH_32=on -DDEBUG=on -DDEBUG_ASSERT=on -DEXAMPLES=off -DNOM_BUILD_TESTS=on .. ``` @@ -90,6 +130,8 @@ cmake -D CMAKE_INSTALL_PREFIX=~/Library/Frameworks .. Removal is provided by executing **make uninstall** within your current build directory. +**IMPORTANT:** If you are building multiple target types with the generated MSVCPP or Xcode project files, each of these targets **must** be kept in separate build directories! + ## Mac OS X Dependencies Required OS X version: 10.7 diff --git a/Resources/CMake/nomlib-config.cmake b/Resources/CMake/nomlib-config.cmake index ceb0c51d..d9202046 100644 --- a/Resources/CMake/nomlib-config.cmake +++ b/Resources/CMake/nomlib-config.cmake @@ -67,6 +67,24 @@ # To distribute this file outside of CMake, substitute the full license text # for the above reference. +set( NOMLIB_DEPS_ROOT "NOTFOUND" ) + +if( EXISTS ${NOMLIB_DEPS_PREFIX} ) + # IMPORTANT: NOMLIB_DEPS_PREFIX will be an external CMake variable within + # this scope, of which **must** already be set by the parent script calling + # FIND_PACKAGE, and should **never** be modified for sake of the parent + # script. + list( APPEND NOMLIB_DEPS_ROOT ${NOMLIB_DEPS_PREFIX} ) +endif( EXISTS ${NOMLIB_DEPS_PREFIX} ) + +if( DEFINED ENV{NOMLIB_DEPS_PREFIX} AND EXISTS ENV{NOMLIB_DEPS_PREFIX} ) + list( APPEND NOMLIB_DEPS_ROOT ENV{NOMLIB_DEPS_PREFIX} ) +endif( DEFINED ENV{NOMLIB_DEPS_PREFIX} AND EXISTS ENV{NOMLIB_DEPS_PREFIX} ) + +if( NOMLIB_DEPS_ROOT STREQUAL "NOTFOUND" ) + message( FATAL_ERROR "An absolute directory path to the engine's dependencies must be set. See README.md for details." ) +endif( NOMLIB_DEPS_ROOT STREQUAL "NOTFOUND" ) + if( CMAKE_SYSTEM_NAME STREQUAL "Darwin" ) set( NOM_PLATFORM_OSX TRUE ) elseif( CMAKE_SYSTEM_NAME STREQUAL "Windows" ) @@ -180,7 +198,6 @@ macro( find_nom_dependency output_var output_name ) find_library( ${output_var} NAMES ${ARGN} PATHS ${NOMLIB_DEPS_ROOT} - $ENV{NOMLIB_DEPS_ROOT} ${NOM_SEARCH_PREFIX_PATHS} PATH_SUFFIXES ${NOM_LIBRARY_PATH_SUFFIXES} # osx, *nix @@ -215,7 +232,6 @@ if( NOMLIB_CORE_FOUND ) find_path( SDL2_INCLUDE_DIR SDL.h PATHS ${NOMLIB_DEPS_ROOT} - $ENV{NOMLIB_DEPS_ROOT} ${NOM_SEARCH_PREFIX_PATHS} PATH_SUFFIXES SDL2/include include ) @@ -263,7 +279,6 @@ if( NOMLIB_GUI_FOUND ) find_path( LIBROCKET_INCLUDE_DIR Rocket/Core PATHS ${NOMLIB_DEPS_ROOT} - $ENV{NOMLIB_DEPS_ROOT} ${NOM_SEARCH_PREFIX_PATHS} PATH_SUFFIXES libRocket/include include ) diff --git a/Resources/SharedSupport/ExamplesTemplate.cpp b/Resources/SharedSupport/ExamplesTemplate.cpp index 24f27f50..5924079f 100644 --- a/Resources/SharedSupport/ExamplesTemplate.cpp +++ b/Resources/SharedSupport/ExamplesTemplate.cpp @@ -2,7 +2,7 @@ nomlib - C++11 cross-platform game engine -Copyright (c) 2013, 2014 Jeffrey Carpenter +Copyright (c) 2013, 2014, 2015, 2016 Jeffrey Carpenter All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/Resources/SharedSupport/InputDevices.json b/Resources/SharedSupport/InputDevices.json new file mode 100644 index 00000000..ebb24b65 --- /dev/null +++ b/Resources/SharedSupport/InputDevices.json @@ -0,0 +1,13 @@ +{ + "resources": + { + "search_prefix": + [ + "../../../", + "../../", + "./" + ], + + "path": "Resources/SharedSupport/InputDevices/" + } +} diff --git a/LICENSE_SDL2.txt b/Resources/SharedSupport/InputDevices/LICENSE old mode 100755 new mode 100644 similarity index 100% rename from LICENSE_SDL2.txt rename to Resources/SharedSupport/InputDevices/LICENSE diff --git a/Resources/SharedSupport/InputDevices/gamecontrollerdb.txt b/Resources/SharedSupport/InputDevices/gamecontrollerdb.txt new file mode 100644 index 00000000..ed95de5a --- /dev/null +++ b/Resources/SharedSupport/InputDevices/gamecontrollerdb.txt @@ -0,0 +1,90 @@ +# Windows - DINPUT +8f0e1200000000000000504944564944,Acme,platform:Windows,x:b2,a:b0,b:b1,y:b3,back:b8,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b5,rightshoulder:b6,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a3,righty:a2, +341a3608000000000000504944564944,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +ffff0000000000000000504944564944,GameStop Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, +6d0416c2000000000000504944564944,Generic DirectInput Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +6d0419c2000000000000504944564944,Logitech F710 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +88880803000000000000504944564944,PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.8,dpleft:h0.4,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b9,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:b7,rightx:a3,righty:a4,start:b11,x:b0,y:b3,platform:Windows, +4c056802000000000000504944564944,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Windows, +25090500000000000000504944564944,PS3 DualShock,a:b2,b:b1,back:b9,dpdown:h0.8,dpleft:h0.4,dpright:h0.2,dpup:h0.1,guide:,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b0,y:b3,platform:Windows, +4c05c405000000000000504944564944,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +6d0418c2000000000000504944564944,Logitech RumblePad 2 USB,platform:Windows,x:b0,a:b1,b:b2,y:b3,back:b8,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3, +36280100000000000000504944564944,OUYA Controller,platform:Windows,a:b0,b:b3,y:b2,x:b1,start:b14,guide:b15,leftstick:b6,rightstick:b7,leftshoulder:b4,rightshoulder:b5,dpup:b8,dpleft:b10,dpdown:b9,dpright:b11,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:b12,righttrigger:b13, +4f0400b3000000000000504944564944,Thrustmaster Firestorm Dual Power,a:b0,b:b2,y:b3,x:b1,start:b10,guide:b8,back:b9,leftstick:b11,rightstick:b12,leftshoulder:b4,rightshoulder:b6,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b5,righttrigger:b7,platform:Windows, +00f00300000000000000504944564944,RetroUSB.com RetroPad,a:b1,b:b5,x:b0,y:b4,back:b2,start:b3,leftshoulder:b6,rightshoulder:b7,leftx:a0,lefty:a1,platform:Windows, +00f0f100000000000000504944564944,RetroUSB.com Super RetroPort,a:b1,b:b5,x:b0,y:b4,back:b2,start:b3,leftshoulder:b6,rightshoulder:b7,leftx:a0,lefty:a1,platform:Windows, +28040140000000000000504944564944,GamePad Pro USB,platform:Windows,a:b1,b:b2,x:b0,y:b3,back:b8,start:b9,leftshoulder:b4,rightshoulder:b5,leftx:a0,lefty:a1,lefttrigger:b6,righttrigger:b7, +ff113133000000000000504944564944,SVEN X-PAD,platform:Windows,a:b2,b:b3,y:b1,x:b0,start:b5,back:b4,leftshoulder:b6,rightshoulder:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a4,lefttrigger:b8,righttrigger:b9, +8f0e0300000000000000504944564944,Piranha xtreme,platform:Windows,x:b3,a:b2,b:b1,y:b0,back:b8,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b4,rightshoulder:b7,righttrigger:b5,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a3,righty:a2, +8f0e0d31000000000000504944564944,Multilaser JS071 USB,platform:Windows,a:b1,b:b2,y:b3,x:b0,start:b9,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7, +10080300000000000000504944564944,PS2 USB,platform:Windows,a:b2,b:b1,y:b0,x:b3,start:b9,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b6,rightshoulder:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a4,righty:a2,lefttrigger:b4,righttrigger:b5, +79000600000000000000504944564944,G-Shark GS-GP702,a:b2,b:b1,x:b3,y:b0,back:b8,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a4,lefttrigger:b6,righttrigger:b7,platform:Windows, +4b12014d000000000000504944564944,NYKO AIRFLO,a:b0,b:b1,x:b2,y:b3,back:b8,guide:b10,start:b9,leftstick:a0,rightstick:a2,leftshoulder:a3,rightshoulder:b5,dpup:h0.1,dpdown:h0.0,dpleft:h0.8,dpright:h0.2,leftx:h0.6,lefty:h0.12,rightx:h0.9,righty:h0.4,lefttrigger:b6,righttrigger:b7,platform:Windows, +d6206dca000000000000504944564944,PowerA Pro Ex,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.0,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,platform:Windows, +a3060cff000000000000504944564944,Saitek P2500,a:b2,b:b3,y:b1,x:b0,start:b4,guide:b10,back:b5,leftstick:b8,rightstick:b9,leftshoulder:b6,rightshoulder:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,platform:Windows, +8f0e0300000000000000504944564944,Trust GTX 28,a:b2,b:b1,y:b0,x:b3,start:b9,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,platform:Windows, +4f0415b3000000000000504944564944,Thrustmaster Dual Analog 3.2,platform:Windows,x:b1,a:b0,b:b2,y:b3,back:b8,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b5,rightshoulder:b6,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3, + +# OS X +0500000047532047616d657061640000,GameStop Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Mac OS X, +6d0400000000000016c2000000000000,Logitech F310 Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +6d0400000000000018c2000000000000,Logitech F510 Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +6d040000000000001fc2000000000000,Logitech F710 Gamepad (XInput),a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X, +6d0400000000000019c2000000000000,Logitech Wireless Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +4c050000000000006802000000000000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Mac OS X, +4c05000000000000c405000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,Platform:Mac OS X, +5e040000000000008e02000000000000,X360 Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X, +891600000000000000fd000000000000,Razer Onza Tournament,a:b0,b:b1,y:b3,x:b2,start:b8,guide:b10,back:b9,leftstick:b6,rightstick:b7,leftshoulder:b4,rightshoulder:b5,dpup:b11,dpleft:b13,dpdown:b12,dpright:b14,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,platform:Mac OS X, +4f0400000000000000b3000000000000,Thrustmaster Firestorm Dual Power,a:b0,b:b2,y:b3,x:b1,start:b10,guide:b8,back:b9,leftstick:b11,rightstick:,leftshoulder:b4,rightshoulder:b6,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b5,righttrigger:b7,platform:Mac OS X, +8f0e0000000000000300000000000000,Piranha xtreme,platform:Mac OS X,x:b3,a:b2,b:b1,y:b0,back:b8,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b4,rightshoulder:b7,righttrigger:b5,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a3,righty:a2, +0d0f0000000000004d00000000000000,HORI Gem Pad 3,platform:Mac OS X,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7, +79000000000000000600000000000000,G-Shark GP-702,a:b2,b:b1,x:b3,y:b0,back:b8,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:b6,righttrigger:b7,platform:Mac OS X, +4f0400000000000015b3000000000000,Thrustmaster Dual Analog 3.2,platform:Mac OS X,x:b1,a:b0,b:b2,y:b3,back:b8,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b5,rightshoulder:b6,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3, +AD1B00000000000001F9000000000000,Gamestop BB-070 X360 Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X, + +# Linux +0500000047532047616d657061640000,GameStop Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux, +03000000ba2200002010000001010000,Jess Technology USB Game Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux, +030000006d04000019c2000010010000,Logitech Cordless RumblePad 2,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000006d0400001dc2000014400000,Logitech F310 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000006d0400001ec2000020200000,Logitech F510 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000006d04000019c2000011010000,Logitech F710 Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000006d0400001fc2000005030000,Logitech F710 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000004c0500006802000011010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux, +030000004c050000c405000011010000,Sony DualShock 4,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:b6,righttrigger:b7,platform:Linux, +03000000de280000ff11000001000000,Valve Streaming Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e0400008e02000014010000,X360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e0400008e02000010010000,X360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e0400001907000000010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000100800000100000010010000,Twin USB PS2 Adapter,a:b2,b:b1,y:b0,x:b3,start:b9,guide:,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b6,rightshoulder:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a2,lefttrigger:b4,righttrigger:b5,platform:Linux, +03000000a306000023f6000011010000,Saitek Cyborg V.1 Game Pad,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a4,lefttrigger:b6,righttrigger:b7,platform:Linux, +030000004f04000020b3000010010000,Thrustmaster 2 in 1 DT,a:b0,b:b2,y:b3,x:b1,start:b9,guide:,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b6,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b5,righttrigger:b7,platform:Linux, +030000004f04000023b3000000010000,Thrustmaster Dual Trigger 3-in-1,platform:Linux,x:b0,a:b1,b:b2,y:b3,back:b8,start:b9,dpleft:h0.8,dpdown:h0.0,dpdown:h0.4,dpright:h0.0,dpright:h0.2,dpup:h0.0,dpup:h0.1,leftshoulder:h0.0,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a5, +030000008f0e00000300000010010000,GreenAsia Inc. USB Joystick ,platform:Linux,x:b3,a:b2,b:b1,y:b0,back:b8,start:b9,dpleft:h0.8,dpdown:h0.0,dpdown:h0.4,dpright:h0.0,dpright:h0.2,dpup:h0.0,dpup:h0.1,leftshoulder:h0.0,leftshoulder:b6,lefttrigger:b4,rightshoulder:b7,righttrigger:b5,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a3,righty:a2, +030000008f0e00001200000010010000,GreenAsia Inc. USB Joystick ,platform:Linux,x:b2,a:b0,b:b1,y:b3,back:b8,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b5,rightshoulder:b6,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a3,righty:a2, +030000005e0400009102000007010000,X360 Wireless Controller,a:b0,b:b1,y:b3,x:b2,start:b7,guide:b8,back:b6,leftstick:b9,rightstick:b10,leftshoulder:b4,rightshoulder:b5,dpup:b13,dpleft:b11,dpdown:b14,dpright:b12,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,platform:Linux, +030000006d04000016c2000010010000,Logitech Logitech Dual Action,platform:Linux,x:b0,a:b1,b:b2,y:b3,back:b8,start:b9,dpleft:h0.8,dpdown:h0.0,dpdown:h0.4,dpright:h0.0,dpright:h0.2,dpup:h0.0,dpup:h0.1,leftshoulder:h0.0,dpup:h0.1,leftshoulder:h0.0,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3, +03000000260900008888000000010000,GameCube {WiseGroup USB box},a:b0,b:b2,y:b3,x:b1,start:b7,leftshoulder:,rightshoulder:b6,dpup:h0.1,dpleft:h0.8,rightstick:,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,platform:Linux, +030000006d04000011c2000010010000,Logitech WingMan Cordless RumblePad,a:b0,b:b1,y:b4,x:b3,start:b8,guide:b5,back:b2,leftshoulder:b6,rightshoulder:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:b9,righttrigger:b10,platform:Linux, +030000006d04000018c2000010010000,Logitech Logitech RumblePad 2 USB,platform:Linux,x:b0,a:b1,b:b2,y:b3,back:b8,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3, +05000000d6200000ad0d000001000000,Moga Pro,platform:Linux,a:b0,b:b1,y:b3,x:b2,start:b6,leftstick:b7,rightstick:b8,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a5,righttrigger:a4, +030000004f04000009d0000000010000,Thrustmaster Run N Drive Wireless PS3,platform:Linux,a:b1,b:b2,x:b0,y:b3,start:b9,guide:b12,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7, +030000004f04000008d0000000010000,Thrustmaster Run N Drive Wireless,platform:Linux,a:b1,b:b2,x:b0,y:b3,start:b9,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:b6,righttrigger:b7, +0300000000f000000300000000010000,RetroUSB.com RetroPad,a:b1,b:b5,x:b0,y:b4,back:b2,start:b3,leftshoulder:b6,rightshoulder:b7,leftx:a0,lefty:a1,platform:Linux, +0300000000f00000f100000000010000,RetroUSB.com Super RetroPort,a:b1,b:b5,x:b0,y:b4,back:b2,start:b3,leftshoulder:b6,rightshoulder:b7,leftx:a0,lefty:a1,platform:Linux, +030000006f0e00001f01000000010000,Generic X-Box pad,platform:Linux,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4, +03000000280400000140000000010000,Gravis GamePad Pro USB ,platform:Linux,x:b0,a:b1,b:b2,y:b3,back:b8,start:b9,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftx:a0,lefty:a1, +030000005e0400008902000021010000,Microsoft X-Box pad v2 (US),platform:Linux,x:b3,a:b0,b:b1,y:b4,back:b6,start:b7,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b5,lefttrigger:a2,rightshoulder:b2,righttrigger:a5,leftstick:b8,rightstick:b9,leftx:a0,lefty:a1,rightx:a3,righty:a4, +030000006f0e00001e01000011010000,Rock Candy Gamepad for PS3,platform:Linux,a:b1,b:b2,x:b0,y:b3,back:b8,start:b9,guide:b12,leftshoulder:b4,rightshoulder:b5,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2, +03000000250900000500000000010000,Sony PS2 pad with SmartJoy adapter,platform:Linux,a:b2,b:b1,y:b0,x:b3,start:b8,back:b9,leftstick:b10,rightstick:b11,leftshoulder:b6,rightshoulder:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b4,righttrigger:b5, +030000008916000000fd000024010000,Razer Onza Tournament,a:b0,b:b1,y:b3,x:b2,start:b7,guide:b8,back:b6,leftstick:b9,rightstick:b10,leftshoulder:b4,rightshoulder:b5,dpup:b13,dpleft:b11,dpdown:b14,dpright:b12,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,platform:Linux, +030000004f04000000b3000010010000,Thrustmaster Firestorm Dual Power,a:b0,b:b2,y:b3,x:b1,start:b10,guide:b8,back:b9,leftstick:b11,rightstick:b12,leftshoulder:b4,rightshoulder:b6,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b5,righttrigger:b7,platform:Linux, +03000000ad1b000001f5000033050000,Hori Pad EX Turbo 2,a:b0,b:b1,y:b3,x:b2,start:b7,guide:b8,back:b6,leftstick:b9,rightstick:b10,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,platform:Linux, +050000004c050000c405000000010000,PS4 Controller (Bluetooth),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +060000004c0500006802000000010000,PS3 Controller (Bluetooth),a:b14,b:b13,y:b12,x:b15,start:b3,guide:b16,back:b0,leftstick:b1,rightstick:b2,leftshoulder:b10,rightshoulder:b11,dpup:b4,dpleft:b7,dpdown:b6,dpright:b5,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b8,righttrigger:b9,platform:Linux, +03000000790000000600000010010000,DragonRise Inc. Generic USB Joystick ,platform:Linux,x:b3,a:b2,b:b1,y:b0,back:b8,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a3,righty:a4, +03000000666600000488000000010000,Super Joy Box 5 Pro,platform:Linux,a:b2,b:b1,x:b3,y:b0,back:b9,start:b8,leftshoulder:b6,rightshoulder:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b4,righttrigger:b5,dpup:b12,dpleft:b15,dpdown:b14,dpright:b13, +05000000362800000100000002010000,OUYA Game Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,platform:Linux,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,x:b1,y:b2, +05000000362800000100000003010000,OUYA Game Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,platform:Linux,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,x:b1,y:b2, +030000008916000001fd000024010000,Razer Onza Classic Edition,platform:Linux,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:b11,dpdown:b14,dpright:b12,dpup:b13,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4, +030000005e040000d102000001010000,Microsoft X-Box One pad,platform:Linux,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:h0.8,dpdown:h0.0,dpdown:h0.4,dpright:h0.0,dpright:h0.2,dpup:h0.0,dpup:h0.1,leftshoulder:h0.0,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4, diff --git a/Resources/SharedSupport/InputDevices/source.url b/Resources/SharedSupport/InputDevices/source.url new file mode 100644 index 00000000..97dfbf9b --- /dev/null +++ b/Resources/SharedSupport/InputDevices/source.url @@ -0,0 +1 @@ +https://github.com/gabomdq/SDL_GameControllerDB/ diff --git a/Resources/SharedSupport/Template.cpp b/Resources/SharedSupport/Template.cpp index f99f1289..9a6127e4 100644 --- a/Resources/SharedSupport/Template.cpp +++ b/Resources/SharedSupport/Template.cpp @@ -2,7 +2,7 @@ nomlib - C++11 cross-platform game engine -Copyright (c) 2013, 2014 Jeffrey Carpenter +Copyright (c) 2013, 2014, 2015, 2016 Jeffrey Carpenter All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/Resources/SharedSupport/Template.hpp b/Resources/SharedSupport/Template.hpp index a69de7a5..ef5af8d3 100644 --- a/Resources/SharedSupport/Template.hpp +++ b/Resources/SharedSupport/Template.hpp @@ -2,7 +2,7 @@ nomlib - C++11 cross-platform game engine -Copyright (c) 2013, 2014 Jeffrey Carpenter +Copyright (c) 2013, 2014, 2015, 2016 Jeffrey Carpenter All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/Resources/SharedSupport/UnitTestTemplate.cpp b/Resources/SharedSupport/UnitTestTemplate.cpp index 4beb3e7e..b0324184 100644 --- a/Resources/SharedSupport/UnitTestTemplate.cpp +++ b/Resources/SharedSupport/UnitTestTemplate.cpp @@ -2,7 +2,7 @@ nomlib - C++11 cross-platform game engine -Copyright (c) 2013, 2014 Jeffrey Carpenter +Copyright (c) 2013, 2014, 2015, 2016 Jeffrey Carpenter All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/Resources/SharedSupport/VisualUnitTestTemplate.cpp b/Resources/SharedSupport/VisualUnitTestTemplate.cpp index 7e6cb160..1e10628b 100644 --- a/Resources/SharedSupport/VisualUnitTestTemplate.cpp +++ b/Resources/SharedSupport/VisualUnitTestTemplate.cpp @@ -2,7 +2,7 @@ nomlib - C++11 cross-platform game engine -Copyright (c) 2013, 2014 Jeffrey Carpenter +Copyright (c) 2013, 2014, 2015 Jeffrey Carpenter All rights reserved. Redistribution and use in source and binary forms, with or without @@ -34,6 +34,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include #include namespace nom { @@ -44,7 +45,8 @@ class VisualUnitTestTemplate: public nom::VisualUnitTest /// \remarks This method is called at the start of each unit test. VisualUnitTestTemplate() { - // NOM_LOG_TRACE( NOM ); + NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_UNIT_TEST, + NOM_LOG_PRIORITY_VERBOSE ); // The frame image to compare against the reference image set this->append_screenshot_frame(0); @@ -53,7 +55,8 @@ class VisualUnitTestTemplate: public nom::VisualUnitTest /// \remarks This method is called at the end of each unit test. virtual ~VisualUnitTestTemplate() { - // NOM_LOG_TRACE( NOM ); + NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_UNIT_TEST, + NOM_LOG_PRIORITY_VERBOSE ); } // virtual bool init_rendering() @@ -81,7 +84,8 @@ class VisualUnitTestTemplate: public nom::VisualUnitTest /// unit test. virtual void SetUp() { - // NOM_LOG_TRACE( NOM ); + NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_UNIT_TEST, + NOM_LOG_PRIORITY_VERBOSE ); std::string res_file = nom::UnitTest::test_set() + ".json"; @@ -99,19 +103,22 @@ class VisualUnitTestTemplate: public nom::VisualUnitTest /// unit test. virtual void TearDown() { - // NOM_LOG_TRACE( NOM ); + NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_UNIT_TEST, + NOM_LOG_PRIORITY_VERBOSE ); } /// \remarks This method is called at the start of each test case. static void SetUpTestCase() { - // NOM_LOG_TRACE( NOM ); + NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_UNIT_TEST, + NOM_LOG_PRIORITY_VERBOSE ); } /// \remarks This method is called at the end of each test case. static void TearDownTestCase() { - // NOM_LOG_TRACE( NOM ); + NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_UNIT_TEST, + NOM_LOG_PRIORITY_VERBOSE ); } protected: @@ -126,13 +133,13 @@ TEST_F(VisualUnitTestTemplate, Test) } // namespace nom -int main( int argc, char** argv ) +int main(int argc, char** argv) { - ::testing::InitGoogleTest( &argc, argv ); + ::testing::InitGoogleTest(&argc, argv); // Set the current working directory path to the path leading to this // executable file; used for unit tests that require file-system I/O. - if( nom::init( argc, argv ) == false ) + if( nom::init(argc, argv) == false ) { NOM_LOG_CRIT(NOM_LOG_CATEGORY_APPLICATION, "Could not initialize nomlib."); return NOM_EXIT_FAILURE; @@ -140,14 +147,10 @@ int main( int argc, char** argv ) atexit( nom::quit ); // nom::UnitTest framework integration - nom::init_test( argc, argv ); + nom::init_test(argc, argv); - // nom::SDL2Logger::set_logging_priority( NOM_LOG_CATEGORY_TEST, nom::NOM_LOG_PRIORITY_VERBOSE ); - - // Hides non-critical messages that might be generated by functions when - // running tests. Generally speaking, only tests that explicitly check for - // invalid conditions should be subject to a desire for hiding the logged - // messages (for sake of cleanliness?). + // nom::SDL2Logger::set_logging_priority(NOM_LOG_CATEGORY_TEST, NOM_LOG_PRIORITY_CRITICAL); + // nom::SDL2Logger::set_logging_priority(NOM_LOG_CATEGORY_TEST, NOM_LOG_PRIORITY_DEBUG); // nom::SDL2Logger::set_logging_priority(NOM_LOG_CATEGORY_APPLICATION, nom::NOM_LOG_PRIORITY_CRITICAL); return RUN_ALL_TESTS(); diff --git a/Resources/examples/app/cursors.json b/Resources/examples/app/cursors.json index 7e178204..566941dc 100644 --- a/Resources/examples/app/cursors.json +++ b/Resources/examples/app/cursors.json @@ -37,6 +37,7 @@ "height": 16 } }, + "metadata": { "filename": "cursors.png", diff --git a/Resources/examples/sprites.json b/Resources/examples/audio.json similarity index 68% rename from Resources/examples/sprites.json rename to Resources/examples/audio.json index 13ea7089..c52dd21b 100644 --- a/Resources/examples/sprites.json +++ b/Resources/examples/audio.json @@ -8,6 +8,6 @@ "./" ], - "path": "Resources/examples/app/" + "path": "Resources/tests/audio/ALAudioTest/" } } diff --git a/Resources/examples/audio/sinewave-real32_1s-900.wav b/Resources/examples/audio/sinewave-real32_1s-900.wav new file mode 100644 index 00000000..466b4c41 Binary files /dev/null and b/Resources/examples/audio/sinewave-real32_1s-900.wav differ diff --git a/Resources/examples/audio/sinewave_1s-900.wav b/Resources/examples/audio/sinewave_1s-900.wav new file mode 100644 index 00000000..1e7d8024 Binary files /dev/null and b/Resources/examples/audio/sinewave_1s-900.wav differ diff --git a/Resources/examples/audio/sinewave_1s-chunk.wav b/Resources/examples/audio/sinewave_1s-chunk.wav new file mode 100644 index 00000000..75506546 Binary files /dev/null and b/Resources/examples/audio/sinewave_1s-chunk.wav differ diff --git a/Resources/examples/audio/sinewave_2s-440.wav b/Resources/examples/audio/sinewave_2s-440.wav new file mode 100644 index 00000000..8f425836 Binary files /dev/null and b/Resources/examples/audio/sinewave_2s-440.wav differ diff --git a/Resources/tests/actions/ActionTest.json b/Resources/tests/actions/ActionTest.json new file mode 100644 index 00000000..5995e6b1 --- /dev/null +++ b/Resources/tests/actions/ActionTest.json @@ -0,0 +1,25 @@ +{ + "resources": + { + "search_prefix": + [ + "../../../", + "../../", + "./" + ], + + "path": "Resources/tests/actions/ActionTest/" + }, + + "fonts": + { + "search_prefix": + [ + "../../../", + "../../", + "./" + ], + + "path": "Resources/tests/graphics/BMFontTest/" + } +} diff --git a/Resources/tests/actions/ActionTest/backdrop.png b/Resources/tests/actions/ActionTest/backdrop.png new file mode 100644 index 00000000..91f99dae Binary files /dev/null and b/Resources/tests/actions/ActionTest/backdrop.png differ diff --git a/Resources/tests/actions/ActionTest/card.png b/Resources/tests/actions/ActionTest/card.png new file mode 100644 index 00000000..64dfce35 Binary files /dev/null and b/Resources/tests/actions/ActionTest/card.png differ diff --git a/Resources/tests/actions/ActionTest/cursors.json b/Resources/tests/actions/ActionTest/cursors.json new file mode 100644 index 00000000..75efeb1e --- /dev/null +++ b/Resources/tests/actions/ActionTest/cursors.json @@ -0,0 +1,52 @@ +{ + "frames": + { + "0": + { + "x": 0, + "y": 0, + "width": 52, + "height": 32 + }, + "1": + { + "x": 52, + "y": 0, + "width": 52, + "height": 32 + }, + "2": + { + "x": 104, + "y": 0, + "width": 52, + "height": 32 + }, + "3": + { + "x": 156, + "y": 0, + "width": 52, + "height": 32 + }, + "4": + { + "x": 0, + "y": 32, + "width": 52, + "height": 32 + } + }, + "metadata": + { + "filename": "cursors.png", + "version": "0.4.0", + "width": 256, + "height": 64, + "padding": 0, + "spacing": 0, + "scale": 1, + "pixel_format": "RGBA8888", + "smartupdate": "$TexturePacker:SmartUpdate:e7f11286a2c6285189d173849b7bfc79:507022e10396f82f15531867b27b9c71:5c72a818f45abbf727e428f9aec34004$" + } +} diff --git a/Resources/tests/actions/ActionTest/cursors.png b/Resources/tests/actions/ActionTest/cursors.png new file mode 100644 index 00000000..dcf3ea24 Binary files /dev/null and b/Resources/tests/actions/ActionTest/cursors.png differ diff --git a/Resources/tests/actions/ActionTest/yoshi_000.png b/Resources/tests/actions/ActionTest/yoshi_000.png new file mode 100644 index 00000000..1c8677af Binary files /dev/null and b/Resources/tests/actions/ActionTest/yoshi_000.png differ diff --git a/Resources/tests/actions/ActionTest/yoshi_001.png b/Resources/tests/actions/ActionTest/yoshi_001.png new file mode 100644 index 00000000..6dc9b905 Binary files /dev/null and b/Resources/tests/actions/ActionTest/yoshi_001.png differ diff --git a/Resources/tests/actions/ActionTest/yoshi_002.png b/Resources/tests/actions/ActionTest/yoshi_002.png new file mode 100644 index 00000000..cfb8f27a Binary files /dev/null and b/Resources/tests/actions/ActionTest/yoshi_002.png differ diff --git a/Resources/tests/actions/ActionTest/yoshi_003.png b/Resources/tests/actions/ActionTest/yoshi_003.png new file mode 100644 index 00000000..6dc9b905 Binary files /dev/null and b/Resources/tests/actions/ActionTest/yoshi_003.png differ diff --git a/Resources/tests/actions/ActionTest/yoshi_004.png b/Resources/tests/actions/ActionTest/yoshi_004.png new file mode 100644 index 00000000..2ff40b22 Binary files /dev/null and b/Resources/tests/actions/ActionTest/yoshi_004.png differ diff --git a/Resources/tests/actions/ActionTest/yoshi_005.png b/Resources/tests/actions/ActionTest/yoshi_005.png new file mode 100644 index 00000000..8a2d817f Binary files /dev/null and b/Resources/tests/actions/ActionTest/yoshi_005.png differ diff --git a/Resources/tests/actions/ActionTest/yoshi_006.png b/Resources/tests/actions/ActionTest/yoshi_006.png new file mode 100644 index 00000000..4eab6beb Binary files /dev/null and b/Resources/tests/actions/ActionTest/yoshi_006.png differ diff --git a/Resources/tests/actions/ActionTest/yoshi_007.png b/Resources/tests/actions/ActionTest/yoshi_007.png new file mode 100644 index 00000000..5cd220fa Binary files /dev/null and b/Resources/tests/actions/ActionTest/yoshi_007.png differ diff --git a/Resources/tests/audio/ALAudioTest.json b/Resources/tests/audio/ALAudioTest.json new file mode 100644 index 00000000..c52dd21b --- /dev/null +++ b/Resources/tests/audio/ALAudioTest.json @@ -0,0 +1,13 @@ +{ + "resources": + { + "search_prefix": + [ + "../../../", + "../../", + "./" + ], + + "path": "Resources/tests/audio/ALAudioTest/" + } +} diff --git a/Resources/tests/audio/ALAudioTest/laura_ff1-s16.wav b/Resources/tests/audio/ALAudioTest/laura_ff1-s16.wav new file mode 100644 index 00000000..db45cc27 Binary files /dev/null and b/Resources/tests/audio/ALAudioTest/laura_ff1-s16.wav differ diff --git a/Resources/tests/audio/ALAudioTest/sinewave-4CHN-r32_1s-900.wav b/Resources/tests/audio/ALAudioTest/sinewave-4CHN-r32_1s-900.wav new file mode 100644 index 00000000..7884234d Binary files /dev/null and b/Resources/tests/audio/ALAudioTest/sinewave-4CHN-r32_1s-900.wav differ diff --git a/Resources/tests/audio/ALAudioTest/sinewave-4CHN-r64_1s-900.wav b/Resources/tests/audio/ALAudioTest/sinewave-4CHN-r64_1s-900.wav new file mode 100644 index 00000000..cade0261 Binary files /dev/null and b/Resources/tests/audio/ALAudioTest/sinewave-4CHN-r64_1s-900.wav differ diff --git a/Resources/tests/audio/ALAudioTest/sinewave-4CHN-s16_1s-900.wav b/Resources/tests/audio/ALAudioTest/sinewave-4CHN-s16_1s-900.wav new file mode 100644 index 00000000..029947f5 Binary files /dev/null and b/Resources/tests/audio/ALAudioTest/sinewave-4CHN-s16_1s-900.wav differ diff --git a/Resources/tests/audio/ALAudioTest/sinewave-4CHN-s32_1s-900.wav b/Resources/tests/audio/ALAudioTest/sinewave-4CHN-s32_1s-900.wav new file mode 100644 index 00000000..f2e71954 Binary files /dev/null and b/Resources/tests/audio/ALAudioTest/sinewave-4CHN-s32_1s-900.wav differ diff --git a/Resources/tests/audio/ALAudioTest/sinewave-4CHN-u8_1s-900.wav b/Resources/tests/audio/ALAudioTest/sinewave-4CHN-u8_1s-900.wav new file mode 100644 index 00000000..a538dd33 Binary files /dev/null and b/Resources/tests/audio/ALAudioTest/sinewave-4CHN-u8_1s-900.wav differ diff --git a/Resources/tests/audio/ALAudioTest/sinewave-real32_1s-900.wav b/Resources/tests/audio/ALAudioTest/sinewave-real32_1s-900.wav new file mode 100644 index 00000000..466b4c41 Binary files /dev/null and b/Resources/tests/audio/ALAudioTest/sinewave-real32_1s-900.wav differ diff --git a/Resources/tests/audio/ALAudioTest/sinewave-s16_1s-900.wav b/Resources/tests/audio/ALAudioTest/sinewave-s16_1s-900.wav new file mode 100644 index 00000000..ae5b5624 Binary files /dev/null and b/Resources/tests/audio/ALAudioTest/sinewave-s16_1s-900.wav differ diff --git a/Resources/tests/audio/ALAudioTest/sinewave-s32_1s-900.wav b/Resources/tests/audio/ALAudioTest/sinewave-s32_1s-900.wav new file mode 100644 index 00000000..0f5304dc Binary files /dev/null and b/Resources/tests/audio/ALAudioTest/sinewave-s32_1s-900.wav differ diff --git a/Resources/tests/audio/ALAudioTest/sinewave-s8_1s-900.wav b/Resources/tests/audio/ALAudioTest/sinewave-s8_1s-900.wav new file mode 100644 index 00000000..6a66dfbc Binary files /dev/null and b/Resources/tests/audio/ALAudioTest/sinewave-s8_1s-900.wav differ diff --git a/Resources/tests/audio/ALAudioTest/sinewave_1s-900.wav b/Resources/tests/audio/ALAudioTest/sinewave_1s-900.wav new file mode 100644 index 00000000..1e7d8024 Binary files /dev/null and b/Resources/tests/audio/ALAudioTest/sinewave_1s-900.wav differ diff --git a/Resources/tests/audio/ALAudioTest/sinewave_1s-chunk.wav b/Resources/tests/audio/ALAudioTest/sinewave_1s-chunk.wav new file mode 100644 index 00000000..75506546 Binary files /dev/null and b/Resources/tests/audio/ALAudioTest/sinewave_1s-chunk.wav differ diff --git a/Resources/tests/audio/ALAudioTest/sinewave_2s-440.wav b/Resources/tests/audio/ALAudioTest/sinewave_2s-440.wav new file mode 100644 index 00000000..8f425836 Binary files /dev/null and b/Resources/tests/audio/ALAudioTest/sinewave_2s-440.wav differ diff --git a/Resources/tests/audio/ALAudioTest/sinewave_4s-s16.wav b/Resources/tests/audio/ALAudioTest/sinewave_4s-s16.wav new file mode 100644 index 00000000..5bd7ba08 Binary files /dev/null and b/Resources/tests/audio/ALAudioTest/sinewave_4s-s16.wav differ diff --git a/Resources/tests/audio/ALAudioTest/surround/6ch_aac.mp4 b/Resources/tests/audio/ALAudioTest/surround/6ch_aac.mp4 new file mode 100644 index 00000000..fa1bb5b2 Binary files /dev/null and b/Resources/tests/audio/ALAudioTest/surround/6ch_aac.mp4 differ diff --git a/Resources/tests/audio/ALAudioTest/surround/6ch_ac3.avi b/Resources/tests/audio/ALAudioTest/surround/6ch_ac3.avi new file mode 100644 index 00000000..a1c44e66 Binary files /dev/null and b/Resources/tests/audio/ALAudioTest/surround/6ch_ac3.avi differ diff --git a/Resources/tests/audio/tags.xml b/Resources/tests/audio/tags.xml new file mode 100644 index 00000000..8dfa7e96 --- /dev/null +++ b/Resources/tests/audio/tags.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/Resources/tests/graphics/BMFontTest/gameover.fnt b/Resources/tests/graphics/BMFontTest/gameover.fnt index c77046be..df348a18 100644 --- a/Resources/tests/graphics/BMFontTest/gameover.fnt +++ b/Resources/tests/graphics/BMFontTest/gameover.fnt @@ -16,7 +16,7 @@ char id=42 x=510 y=354 width=38 height=42 xoffset=6 yoffset=14 xadvance=36 page= char id=43 x=240 y=192 width=50 height=50 xoffset=2 yoffset=22 xadvance=41 page=0 chnl=0 char id=44 x=476 y=448 width=24 height=32 xoffset=4 yoffset=56 xadvance=18 page=0 chnl=0 char id=45 x=476 y=482 width=32 height=18 xoffset=3 yoffset=45 xadvance=24 page=0 chnl=0 -char id=46 x=54 y=480 width=20 height=20 xoffset=6 yoffset=57 xadvance=18 page=0 chnl=0 +char id=46 x=54 y=480 width=20 height=20 xoffset=6 yoffset=57 xadvance=24 page=0 chnl=0 char id=47 x=326 y=436 width=34 height=64 xoffset=1 yoffset=13 xadvance=21 page=0 chnl=0 char id=48 x=470 y=128 width=44 height=62 xoffset=3 yoffset=15 xadvance=36 page=0 chnl=0 char id=49 x=366 y=368 width=32 height=62 xoffset=9 yoffset=15 xadvance=36 page=0 chnl=0 @@ -56,7 +56,7 @@ char id=82 x=78 y=130 width=60 height=60 xoffset=2 yoffset=17 xadvance=49 page=0 char id=83 x=240 y=128 width=44 height=62 xoffset=5 yoffset=16 xadvance=41 page=0 chnl=0 char id=84 x=78 y=380 width=54 height=60 xoffset=3 yoffset=17 xadvance=44 page=0 chnl=0 char id=85 x=2 y=352 width=64 height=62 xoffset=1 yoffset=16 xadvance=52 page=0 chnl=0 -char id=86 x=2 y=288 width=64 height=62 xoffset=1 yoffset=16 xadvance=52 page=0 chnl=0 +char id=86 x=2 y=288 width=64 height=62 xoffset=1 yoffset=16 xadvance=62 page=0 chnl=0 char id=87 x=78 y=2 width=80 height=62 xoffset=1 yoffset=16 xadvance=68 page=0 chnl=0 char id=88 x=422 y=2 width=64 height=60 xoffset=1 yoffset=17 xadvance=52 page=0 chnl=0 char id=89 x=356 y=2 width=64 height=60 xoffset=1 yoffset=17 xadvance=52 page=0 chnl=0 diff --git a/Resources/tests/graphics/BMFontTest/scoreboard.fnt b/Resources/tests/graphics/BMFontTest/scoreboard.fnt deleted file mode 100644 index 753336ea..00000000 --- a/Resources/tests/graphics/BMFontTest/scoreboard.fnt +++ /dev/null @@ -1,15 +0,0 @@ -info face="Times New Roman" size=96 bold=0 italic=0 charset="" unicode=0 stretchH=100 smooth=1 aa=1 padding=0,0,0,0 spacing=0,0 -common lineHeight=114 base=114 scaleW=1024 scaleH=1024 pages=1 packed=0 -page id=0 file="scoreboard.png" -chars count=11 -char id=32 x=-1 y=1 width=0 height=0 xoffset=0 yoffset=0 xadvance=12 page=0 chnl=0 -char id=48 x=205 y=1 width=204 height=118 xoffset=0 yoffset=0 xadvance=12 page=0 chnl=0 -char id=49 x=411 y=1 width=135 height=118 xoffset=0 yoffset=0 xadvance=12 page=0 chnl=0 -char id=50 x=617 y=1 width=135 height=118 xoffset=0 yoffset=0 xadvance=12 page=0 chnl=0 -char id=51 x=-1 y=121 width=135 height=118 xoffset=0 yoffset=0 xadvance=12 page=0 chnl=0 -char id=52 x=205 y=121 width=135 height=118 xoffset=0 yoffset=0 xadvance=12 page=0 chnl=0 -char id=53 x=411 y=121 width=135 height=118 xoffset=0 yoffset=0 xadvance=12 page=0 chnl=0 -char id=54 x=617 y=121 width=135 height=118 xoffset=0 yoffset=0 xadvance=12 page=0 chnl=0 -char id=55 x=-1 y=241 width=135 height=118 xoffset=0 yoffset=0 xadvance=12 page=0 chnl=0 -char id=56 x=205 y=241 width=135 height=118 xoffset=0 yoffset=0 xadvance=12 page=0 chnl=0 -char id=57 x=411 y=241 width=135 height=118 xoffset=0 yoffset=0 xadvance=12 page=0 chnl=0 diff --git a/Resources/tests/graphics/BMFontTest/scoreboard.png b/Resources/tests/graphics/BMFontTest/scoreboard.png deleted file mode 100644 index e628201c..00000000 Binary files a/Resources/tests/graphics/BMFontTest/scoreboard.png and /dev/null differ diff --git a/Resources/tests/graphics/SpriteTest.json b/Resources/tests/graphics/SpriteTest.json new file mode 100644 index 00000000..12641812 --- /dev/null +++ b/Resources/tests/graphics/SpriteTest.json @@ -0,0 +1,25 @@ +{ + "resources": + { + "search_prefix": + [ + "../../../", + "../../", + "./" + ], + + "path": "Resources/tests/graphics/SpriteTest/" + }, + + "common": + { + "search_prefix": + [ + "../../../", + "../../", + "./" + ], + + "path": "Resources/examples/app/" + } +} diff --git a/Resources/tests/graphics/SpriteTest/card.png b/Resources/tests/graphics/SpriteTest/card.png new file mode 100644 index 00000000..64dfce35 Binary files /dev/null and b/Resources/tests/graphics/SpriteTest/card.png differ diff --git a/apple_extensions_v2.cpp b/apple_extensions_v2.cpp new file mode 100644 index 00000000..048f0590 --- /dev/null +++ b/apple_extensions_v2.cpp @@ -0,0 +1,125 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014, 2015, 2016 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#include "nomlib/audio/AL/osx/apple_extensions.hpp" + +// Private headers +#include "nomlib/audio/audio_defs.hpp" +#include "nomlib/audio/AL/ALAudioDevice.hpp" + +#include + +// Forward declarations +#include "nomlib/audio/AL/OpenAL.hpp" + +namespace nom { +namespace audio { + +// comment out static sample_rate in ALAudioDevice.cpp + +static +real64 get_sample_rate(ALCdevice_struct* target) +{ + ALCint sample_rate = 0; + real64 result = 0.0f; + + // FIXME(jeff): Implement type polymorphism here + auto dev = NOM_SCAST(ALCdevice_struct*, target); + alcGetIntegerv(dev, ALC_FREQUENCY, sizeof(ALCint), &sample_rate); + + // TODO(jeff): This function signature returns a 64-bit floating-point + // number -- we ought to carefully consider the ramifications of this and + // see about safeguarding against significant bit loss. + result = NOM_SCAST(real64, sample_rate); + + return result; +} + +real64 sample_rate(ALCdevice_struct* target) +{ + real64 sample_rate = 0.0f; + bool osx_extension = false; + + osx_extension = audio::context_extension("ALC_EXT_MAC_OSX", target); + if(osx_extension == true) { + real64(*get_sample_rate_func)() = nullptr; +#if defined(NOM_PLATFORM_OSX) && defined(NOM_USE_APPLE_OPENAL) + get_sample_rate_func = + (osx_get_sample_rate) audio::process_addr("alcMacOSXGetMixerOutputRate"); +#endif + if(get_sample_rate_func != nullptr) { + sample_rate = get_sample_rate_func(); + } + } else if(osx_extension == false) { + auto get_sample_rate_func = + (openal_get_sample_rate_func) get_sample_rate; + if(get_sample_rate_func != nullptr) { + sample_rate = get_sample_rate_func(target); + } + } + + return sample_rate; +} + +void set_sample_rate(real64 sample_rate) +{ +#if defined(NOM_PLATFORM_OSX) && defined(NOM_USE_APPLE_OPENAL) + auto set_sample_rate_func = + (osx_set_sample_rate) audio::process_addr("alcMacOSXMixerOutputRate"); + if(set_sample_rate_func != nullptr) { + set_sample_rate_func(sample_rate); + } +#endif +} + +int max_sources() +{ + int num_sources = 0; +#if defined(NOM_PLATFORM_OSX) && defined(NOM_USE_APPLE_OPENAL) + auto get_max_sources_func = + (osx_get_max_sources) audio::process_addr("alcMacOSXGetMixerMaxiumumBusses"); + if(get_max_sources_func != nullptr) { + num_sources = get_max_sources_func(); + } +#endif + return num_sources; +} + +void set_max_sources(int num_sources) +{ +#if defined(NOM_PLATFORM_OSX) && defined(NOM_USE_APPLE_OPENAL) + auto set_max_sources_func = + (osx_set_max_sources) audio::process_addr("alcMacOSXMixerMaxiumumBusses"); + if(set_max_sources_func != nullptr) { + set_max_sources_func(num_sources); + } +#endif +} + +} // namespace audio +} // namespace nom diff --git a/include/nomlib/system/IJoystick.hpp b/apple_extensions_v2.hpp similarity index 58% rename from include/nomlib/system/IJoystick.hpp rename to apple_extensions_v2.hpp index 4d43a9c8..96651f5c 100644 --- a/include/nomlib/system/IJoystick.hpp +++ b/apple_extensions_v2.hpp @@ -2,7 +2,7 @@ nomlib - C++11 cross-platform game engine -Copyright (c) 2013, 2014 Jeffrey Carpenter +Copyright (c) 2013, 2014, 2015, 2016 Jeffrey Carpenter All rights reserved. Redistribution and use in source and binary forms, with or without @@ -26,56 +26,41 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ -#ifndef NOMLIB_SDL2_SYSTEM_IJOYSTICK_HPP -#define NOMLIB_SDL2_SYSTEM_IJOYSTICK_HPP +#ifndef NOMLIB_AL_OSX_APPLE_EXTENSIONS_HPP +#define NOMLIB_AL_OSX_APPLE_EXTENSIONS_HPP #include "nomlib/config.hpp" -namespace nom { - -/// \brief Abstract interface for joysticks API. -class IJoystick -{ - public: - typedef IJoystick SelfType; - typedef std::unique_ptr UniquePtr; - - /// \brief Default constructor. - IJoystick( void ) - { - // NOM_LOG_TRACE( NOM ); - } - - /// \brief Destructor. - virtual ~IJoystick( void ) - { - // NOM_LOG_TRACE( NOM ); - } +// Forward declarations +struct ALCdevice_struct; - /// \brief Initialize the joystick subsystem. - virtual bool initialize( void ) = 0; - - virtual void shutdown( void ) = 0; - - virtual int num_joysticks( void ) const = 0; - - virtual bool attached( void ) const = 0; +namespace nom { +namespace audio { - virtual int id( void ) const = 0; +/// \brief Get the sampling rate of audio playback. +/// +/// \see http://www.opensource.apple.com/source/OpenAL/OpenAL-54/Source/OpenAL/READ_ME +real64 sample_rate(ALCdevice_struct* target); - virtual const std::string name( void ) const = 0; +/// \brief Set the sampling rate of audio playback. +/// +/// \remarks This allows us to optimize the audio mixing performance based on +/// the sampling rate of its audio assets. +/// +/// \see http://www.opensource.apple.com/source/OpenAL/OpenAL-54/Source/OpenAL/READ_ME +void set_sample_rate(real64 sample_rate); - virtual bool open( int idx ) = 0; +/// \brief Get the maximum number of simultaneous playing audio buffers. +/// +/// \see http://www.opensource.apple.com/source/OpenAL/OpenAL-54/Source/OpenAL/READ_ME +int max_sources(); - virtual void close( void ) = 0; -}; +/// \brief Set the maximum number of simultaneous playing audio buffers. +/// +/// \see http://www.opensource.apple.com/source/OpenAL/OpenAL-54/Source/OpenAL/READ_ME +void set_max_sources(int num_sources); +} // namespace audio } // namespace nom #endif // include guard defined - -/// \class nom::IJoystick -/// \ingroup system -/// -/// [DESCRIPTION STUB] -/// diff --git a/audio_v1.cpp b/audio_v1.cpp new file mode 100644 index 00000000..b2363b07 --- /dev/null +++ b/audio_v1.cpp @@ -0,0 +1,244 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ + +// NOTE: Audio playback usage example + +#include +#include +#include +#include + +#include "tclap/CmdLine.h" + +using namespace nom; + +const std::string APP_NAME = "nomlib: audio"; + +/// File path of the resources directory; this must be a relative to the parent +/// working directory. +const std::string APP_RESOURCES_DIR = "Resources"; + +/// The platform specific file delimiter; '/' on Posix and '\' on Windows. +const nom::Path p; + +/// Sound effect resource file +const std::string RESOURCE_AUDIO_SOUND = APP_RESOURCES_DIR + p.native() + + "cursor_wrong.wav"; + +/// \remarks See program usage by passing --help +struct AppFlags +{ + /// The input file source to play + std::string audio_input = RESOURCE_AUDIO_SOUND; + + /// Test input source with the null audio back-end + bool use_null_interface = false; + + /// Test input source with the music audio interface + /// + /// \fixme This interface is broken; the sound buffer memory is not properly + /// deallocated. + bool use_music_interface = false; + + real32 audio_volume = 100.0f; +}; + +int parse_cmdline(int argument_count, char* arguments[], AppFlags& opts) +{ + using namespace TCLAP; + + if( argument_count < 0 ) { + return NOM_EXIT_FAILURE; + } + + try + { + CmdLine cmd( APP_NAME, ' ', nom::NOM_VERSION.version_string() ); + + std::string null_interface_desc = + "Test the usage of the null interface (defaults to FALSE)."; + std::string music_interface_desc = + "Test the usage of the music interface (defaults to FALSE)."; + + SwitchArg use_null_interface_arg("n", "use-null", null_interface_desc, + cmd, false); + SwitchArg use_music_interface_arg("", "use-music", music_interface_desc, + cmd, false); + + ValueArg audio_volume_arg("v", "volume", + "Gain level of audio playback", + false, opts.audio_volume, + "A number between 0.0f .. 100.0f", + cmd ); + ValueArg audio_file_arg("i", "input", + "File path to audio to play from", + false, opts.audio_input, + "Resources/audio/hello.wav", cmd ); + + cmd.parse(argument_count, arguments); + + opts.use_null_interface = use_null_interface_arg.getValue(); + opts.use_music_interface = use_music_interface_arg.getValue(); + opts.audio_input = audio_file_arg.getValue(); + opts.audio_volume = audio_volume_arg.getValue(); + } + catch(TCLAP::ArgException &e) + { + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, + e.error(), "for arg", e.argId() ); + + return NOM_EXIT_FAILURE; + } + + return NOM_EXIT_SUCCESS; +} + +int main(int argc, char* argv[]) +{ + AppFlags args; + + nom::IAudioDevice* dev = nullptr; // this must be declared first + nom::IListener* listener = nullptr; // Global audio volume control + nom::ISoundBuffer* buffer = nullptr; + nom::ISoundSource* snd = nullptr; + nom::Timer loops; + + nom::SDL2Logger::set_logging_priority(NOM_LOG_CATEGORY_AUDIO, + NOM_LOG_PRIORITY_DEBUG); + + if( parse_cmdline(argc, argv, args) != 0 ) { + exit(NOM_EXIT_FAILURE); + } + + // Fatal error; if we are not able to complete this step, it means that + // we probably cannot rely on our resource paths! + if( nom::init(argc, argv) == false ) { + NOM_LOG_CRIT(NOM_LOG_CATEGORY_APPLICATION, + "Could not initialize nomlib."); + exit(NOM_EXIT_FAILURE); + } + atexit(nom::quit); + + // Quick and dirty method of testing the use of nomlib's audio subsystem + // #undef NOM_USE_OPENAL + + // Initialize audio subsystem... + if( args.use_null_interface == true ) { + dev = new nom::NullAudioDevice(); + listener = new nom::NullListener(); + buffer = new nom::NullSoundBuffer(); + } else { + dev = new nom::AudioDevice(); + listener = new nom::Listener(); + buffer = new nom::SoundBuffer(); + } + + NOM_LOG_INFO( NOM_LOG_CATEGORY_APPLICATION, "Audio device name:", + dev->getDeviceName() ); + + listener->set_volume(args.audio_volume); + + if( buffer->load_file(args.audio_input) == false ) { + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, "Could not load audio file: ", + RESOURCE_AUDIO_SOUND ); + return NOM_EXIT_FAILURE; + } + + if( args.use_music_interface == true ) { + if( args.use_null_interface == true ) { + snd = new nom::NullMusic(); + } else { + // FIXME: This interface is broken; the sound buffer memory is not + // properly deallocated. + snd = new nom::Music(); + } + } else if( args.use_music_interface == false ) { + if( args.use_null_interface == true ) { + snd = new nom::NullSound(); + } else { + snd = new nom::Sound(); + } + } + + NOM_ASSERT(snd != nullptr); + + snd->load_buffer(*buffer); + + snd->setPitch(1.0f); + snd->setPosition( nom::Point3f(0.0f, 0.0f, 100.0f) ); + snd->set_velocity( nom::Point3f(0.0f, 0.0f, 0.0f) ); + snd->setLooping(true); + + snd->Play(); + + nom::uint32 duration = buffer->duration(); + + real32 duration_seconds = duration / 1000.0f; + NOM_DUMP(duration_seconds); + + Timer kill_timer; + kill_timer.start(); + while( kill_timer.ticks() < duration ) {} +#if 0 + real32 initial_volume = snd->volume(); + real32 current_volume = initial_volume; + + real32 fade_seconds = 4.0f; + real32 fade_step = current_volume / fade_seconds; + + while( (snd->getStatus() != nom::SoundStatus::Paused) && + (snd->getStatus() != nom::SoundStatus::Stopped) + ) + { + if( current_volume > nom::Listener::min_volume() ) { + std::cout << "\nFading out\n"; + snd->set_volume(current_volume); + } else { + snd->Stop(); + } + + // current_volume = current_volume - fade_step; + // nom::sleep(1000); + } // end loop +#endif + // NOM_LOG_INFO( NOM_LOG_CATEGORY_APPLICATION, + // "Sample Count: ", buffer->getSampleCount() ); + // NOM_LOG_INFO( NOM_LOG_CATEGORY_APPLICATION, + // "Channel Count: ", buffer->getChannelCount() ); + // NOM_LOG_INFO( NOM_LOG_CATEGORY_APPLICATION, + // "Sample Rate: ", buffer->getSampleRate() ); + + NOM_DELETE_PTR(snd); + NOM_DELETE_PTR(buffer); + NOM_DELETE_PTR(dev); + NOM_DELETE_PTR(listener); + + + return NOM_EXIT_SUCCESS; +} diff --git a/bin/SDL_image-build.sh b/bin/SDL_image-build.sh new file mode 100755 index 00000000..c3238e64 --- /dev/null +++ b/bin/SDL_image-build.sh @@ -0,0 +1,45 @@ +#!/bin/sh + +# Automated build for OS X Framework Bundle + +# webp.framework +SRCROOT=${HOME}/Projects/third-party/SDL2_image.hg/Xcode + +# installation prefix +TARGET_DIR=/private/tmp/SDL2_image + +if [[ -d $SRCROOT ]]; then + + echo "SRCROOT: ${SRCROOT}" + echo "TARGET_DIR: ${TARGET_DIR}" + + cd ${SRCROOT} + + if [[ -d $TARGET_DIR ]]; then + rm -rf "${TARGET_DIR}/*" + else + mkdir -p "${TARGET_DIR}" + fi + + xcodebuild clean + rm -rf build/* + # If the build fails, this file needs to be deleted before we can restart + # the build again + rm -rf Frameworks/Frameworks + + xcodebuild -configuration Debug -scheme Framework \ + SYMROOT="${TARGET_DIR}" \ + BUILD_DIR="${TARGET_DIR}" \ + USER_HEADER_SEARCH_PATHS="${HOME}/Projects/nomlib.git/third-party/osx/SDL2.framework/Headers" FRAMEWORK_SEARCH_PATHS="${HOME}/Projects/nomlib.git/third-party/osx ${SRCROOT}/Frameworks" + + xcodebuild clean + rm -rf build/* + rm -rf Frameworks/Frameworks + + xcodebuild -configuration Release -scheme Framework \ + SYMROOT="${TARGET_DIR}" \ + BUILD_DIR="${TARGET_DIR}" \ + USER_HEADER_SEARCH_PATHS="${HOME}/Projects/nomlib.git/third-party/osx/SDL2.framework/Headers" FRAMEWORK_SEARCH_PATHS="${HOME}/Projects/nomlib.git/third-party/osx ${SRCROOT}/Frameworks" +else + echo "ERROR: Directory path does not exist: ${SRCROOT}" +fi diff --git a/bin/build-deps.sh b/bin/archive-deps.sh similarity index 65% rename from bin/build-deps.sh rename to bin/archive-deps.sh index c935c395..7e10288e 100755 --- a/bin/build-deps.sh +++ b/bin/archive-deps.sh @@ -1,24 +1,16 @@ #!/bin/sh - -# Third-Party dependency packer -# -# This is a replacement for nomdev's archive feature (said feature is incomplete -# and should be considered broken). -# -# NOTE: -# -# This script should always be ran from nomlib's root path, i.e.: # -# $ ~/Projects/nomlib.git/bin/build-deps.sh +# Dependencies archiver # -# Prerequisites: +# IMPORTANT: This script should always be ran from nomlib's root directory, +# i.e.: +# cd ~/Projects/nomlib.git +# bin/archive-deps.sh # -# tar, zip, bash -# -# See also: nomdev.git/src/ExternalDeps.rb, -# https://sf.net/p/nomlib, -# https://sf.net/p/ttcards +# Prerequisites: +# git, date, tar, zip # +# See also: https://sf.net/p/nomlib/files TAR_BIN=$(which tar) TAR_ARGS="-czvf" @@ -27,7 +19,8 @@ ZIP_BIN=$(which zip) ZIP_ARGS="-r" # Exclusion masks for zip & tar -EXCLUSION_MASKS="*.DS_Store*" +#EXCLUSION_MASKS="*.DS_Store*" +EXCLUSION_MASKS="*.DS_Store* *.zip *windows/msvcpp/* *linux/*.sh" PROJECT_NAME="nomlib" DEPS_DIR="third-party" @@ -37,14 +30,13 @@ DATE_BIN=$(which date) TIMESTAMP="$($DATE_BIN +%Y-%m-%d)" # BSD date(1) -# Optional git rev number +# git revision number GIT_BIN=$(which git) -# GIT_VER=$( ${GIT_BIN} rev-parse HEAD) # Full SHA GIT_VER=$( ${GIT_BIN} rev-parse --short HEAD) function usage_info() { - echo "Usage: ./$0 [osx|ios|linux|windows|all]" + echo "Usage: ./$0 [common|osx|ios|linux|windows|all]" } function osx_deps() @@ -92,27 +84,46 @@ function windows_deps() ${ZIP_BIN} ${ZIP_ARGS} ${DEPS_FILENAME} ${INCLUSION_MASKS} -x ${EXCLUSION_MASKS} } -function linux_deps() +#build_deps(os_stamp) +function build_deps() { - echo "STUB: Not implemented." + os_stamp="$1" + if [ -z $os_stamp ]; then + os_stamp="common" + fi + + if [[ ${GIT_BIN} ]]; then + # Put the resulting output file at project's root dir + DEPS_FILENAME="../${TIMESTAMP}_${PROJECT_NAME}-${GIT_VER}_${os_stamp}-dependencies.tar.gz" + else + # Put the resulting output file at project's root dir + DEPS_FILENAME="../${TIMESTAMP}_${PROJECT_NAME}_${os_stamp}-dependencies.tar.gz" + fi + + INCLUSION_MASKS="${os_stamp}/ README.md" + + ${TAR_BIN} --exclude="${EXCLUSION_MASKS}" ${TAR_ARGS} ${DEPS_FILENAME} ${INCLUSION_MASKS} } function all_deps() { + build_deps common osx_deps ios_deps - linux_deps + build_deps linux windows_deps } cd ${DEPS_DIR} -if [[ "$1" == "osx" ]]; then +if [[ "$1" == "common" ]]; then + build_deps "common" +elif [[ "$1" == "osx" ]]; then osx_deps elif [[ $1 == "ios" ]]; then ios_deps elif [[ $1 == "linux" ]]; then - linux_deps + build_deps "linux" elif [[ $1 == "windows" ]]; then windows_deps elif [[ $1 == "ios" ]]; then diff --git a/bin/check_fixme.sh b/bin/check_fixme.sh new file mode 100755 index 00000000..654779a4 --- /dev/null +++ b/bin/check_fixme.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +GREP_BIN=$(which grep) # (BSD grep on OS X) 2.5.1-FreeBSD + +if [[ !(-x $GREP_BIN) ]]; then + echo "$0 ERROR: grep command not found" + exit 1 +fi + +if [[ ! $1 ]]; then + $GREP_BIN "FIXME" --color=auto -s -I -i -R --exclude-dir \*.git src + $GREP_BIN "FIXME" --color=auto -s -I -i -R --exclude-dir \*.git include +else + $GREP_BIN "FIXME" --color=auto -s -I -i -R --exclude-dir \*.git $@ +fi diff --git a/bin/check_todo.sh b/bin/check_todo.sh new file mode 100755 index 00000000..013c54e6 --- /dev/null +++ b/bin/check_todo.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +GREP_BIN=$(which grep) # (BSD grep on OS X) 2.5.1-FreeBSD + +if [[ !(-x $GREP_BIN) ]]; then + echo "$0 ERROR: grep command not found" + exit 1 +fi + +if [[ ! $1 ]]; then + $GREP_BIN "TODO" --color=auto -s -I -i -R --exclude-dir \*.git src + $GREP_BIN "TODO" --color=auto -s -I -i -R --exclude-dir \*.git include +else + $GREP_BIN "TODO" --color=auto -s -I -i -R --exclude-dir \*.git $@ +fi diff --git a/bin/clang_build.sh b/bin/clang_build.sh new file mode 100755 index 00000000..344b2c95 --- /dev/null +++ b/bin/clang_build.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +# NOTE: This script is intended to be ran from the project's current build +# directory. + +# We rely on the outside shell environment to set the system PATH to the +# appropriate locations for the development tooling, i.e.: make +# PATH=/usr/bin:/usr/local/bin + +BUILD_TYPE_ARG=$1 + +# Default +BUILD_TYPE="Debug" + +if [[ !( -z "${BUILD_TYPE_ARG}") ]]; then + BUILD_TYPE=$1 +fi + +if [[ !( -z "${NUM_THREADS}") ]]; then + NUM_THREADS_ARG="-j ${NUM_THREADS}" +fi +# echo "NUM_THREADS_ARG: ${NUM_THREADS_ARG}" + +echo "Building ${BUILD_TYPE} project... [target: build]" + +make ${NUM_THREADS_ARG} diff --git a/bin/clang_clean.sh b/bin/clang_clean.sh new file mode 100755 index 00000000..f491b91b --- /dev/null +++ b/bin/clang_clean.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +# NOTE: This script is intended to be ran from the project's current build +# directory. + +# We rely on the outside shell environment to set the system PATH to the +# appropriate locations for the development tooling, i.e.: make +# PATH=/usr/bin:/usr/local/bin +BUILD_TYPE_ARG=$1 + +# Default +BUILD_TYPE="Debug" + +if [[ !( -z "${BUILD_TYPE_ARG}") ]]; then + BUILD_TYPE=$1 +fi + +if [[ !( -z "${NUM_THREADS}") ]]; then + NUM_THREADS_ARG="-j ${NUM_THREADS}" +fi +# echo "NUM_THREADS_ARG: ${NUM_THREADS_ARG}" + +echo "Building ${BUILD_TYPE} project... [target: build]" +make clean diff --git a/bin/clang_distclean.sh b/bin/clang_distclean.sh new file mode 100755 index 00000000..05123e4b --- /dev/null +++ b/bin/clang_distclean.sh @@ -0,0 +1,71 @@ +#!/bin/bash + +# NOTE: This script is intended to be ran from the project's current build +# directory. + +# Enable case-insensitive string matches for passing arguments around +shopt -s nocasematch + +WORKING_DIR=$(pwd) + +function usage_info() +{ + SCRIPT_NAME = $(basename $0) + echo "Usage: ${SCRIPT_NAME} \n" + echo "...where is one of Debug or Release" + echo " (Defaults: ${BUILD_TYPE})\n" + echo + echo "...where is the engine's source build tree" + echo " (Defaults: ${BUILD_DIR}) -- this flag is not yet implemented." +} + +# Default build configuration type +BUILD_TYPE_ARG=$1 +BUILD_TYPE="Debug" +BUILD_DIR=${WORKING_DIR} + +if [[ -n "${BUILD_TYPE_ARG}" ]]; then + BUILD_TYPE=${BUILD_TYPE_ARG} +fi + +# The absolute path to CMake's local cache of project build variables +CMAKE_CACHE_FILE="$(pwd)/CMakeCache.txt" +CMAKE_INSTALL_RECEIPT="$(pwd)/install_manifest.txt" + +# Default installation path +BUILD_INSTALL_DIR="${HOME}/Library/Frameworks" +BUILD_INSTALL_DIR_ARG=$3 + +# Check command arguments +if [[ $1 == "-h" || $1 == "--help" ]]; then + usage_info + exit 0 +else + # NOTE(jeff): If the end-user has given an alternative installation prefix, + # go ahead and try honoring the request by updating the default install + # prefix used by CMake. + if [[ -n "${BUILD_INSTALL_DIR_ARG}" ]]; then + BUILD_INSTALL_DIR=${3} + fi + + echo "\n...Cleaning up development build environment..." + echo " BUILD_INSTALL_DIR: ${BUILD_INSTALL_DIR}" + + make uninstall + make clean + + echo "\nClearing CMake cache..." + + if [[ -f "${CMAKE_CACHE_FILE}" ]]; then + rm -rf CMakeCache.txt + fi + + if [[ -d "CMakeFiles" ]]; then + rm -rf CMakeFiles + fi + + if [[ -d "${CMAKE_INSTALL_RECEIPT}" ]]; then + rm -rf CMakeFiles + fi + +fi diff --git a/bin/clang_docs.sh b/bin/clang_docs.sh new file mode 100755 index 00000000..d4589de3 --- /dev/null +++ b/bin/clang_docs.sh @@ -0,0 +1,30 @@ +#!/bin/sh + +# NOTE: This script is intended to be ran from the project's current build +# directory. + +# We rely on the outside shell environment to set the system PATH to the +# appropriate locations for the development tooling, i.e.: make +# PATH=/usr/bin:/usr/local/bin + +BUILD_TYPE_ARG=$1 + +# Default +BUILD_TYPE="Debug" + +if [[ !( -z "${BUILD_TYPE_ARG}") ]]; then + BUILD_TYPE=$1 +fi + +echo "Building ${BUILD_TYPE} project... [target: docs]" +make docs + +# Optionally setup local site access for the generated documentation set at +# http://nomlib-docs.dev -- requires an existing Pow installation [1]. +# +# 1. http://pow.cx/ +if [[ $(which pow) ]]; then + ../bin/gen_docs.sh nomlib-docs + + # NOTE: See Auto-Reload plugin for auto-reload of API documentation page +fi diff --git a/bin/clang_install.sh b/bin/clang_install.sh new file mode 100755 index 00000000..92101293 --- /dev/null +++ b/bin/clang_install.sh @@ -0,0 +1,25 @@ +#!/bin/sh + +# NOTE: This script is intended to be ran from the project's current build +# directory. + +# We rely on the outside shell environment to set the system PATH to the +# appropriate locations for the development tooling, i.e.: make +# PATH=/usr/bin:/usr/local/bin + +BUILD_TYPE_ARG=$1 + +# Default +BUILD_TYPE="Debug" + +if [[ !( -z "${BUILD_TYPE_ARG}") ]]; then + BUILD_TYPE=$1 +fi + +if [[ !( -z "${NUM_THREADS}") ]]; then + NUM_THREADS_ARG="-j ${NUM_THREADS}" +fi +# echo "NUM_THREADS_ARG: ${NUM_THREADS_ARG}" + +echo "Installing ${BUILD_TYPE} project... [target: build]" +make install diff --git a/bin/clang_macros.sh b/bin/clang_macros.sh new file mode 100755 index 00000000..d089724a --- /dev/null +++ b/bin/clang_macros.sh @@ -0,0 +1,8 @@ +#!/bin/sh +# +# Snippet for dumping all the compiler macros (clang) available +# to us at build time. +# + +echo | clang -dM -E - + diff --git a/bin/clang_uninstall.sh b/bin/clang_uninstall.sh new file mode 100755 index 00000000..9ea9c68e --- /dev/null +++ b/bin/clang_uninstall.sh @@ -0,0 +1,25 @@ +#!/bin/sh + +# NOTE: This script is intended to be ran from the project's current build +# directory. + +# We rely on the outside shell environment to set the system PATH to the +# appropriate locations for the development tooling, i.e.: make +# PATH=/usr/bin:/usr/local/bin + +BUILD_TYPE_ARG=$1 + +# Default +BUILD_TYPE="Debug" + +if [[ !( -z "${BUILD_TYPE_ARG}") ]]; then + BUILD_TYPE=$1 +fi + +if [[ !( -z "${NUM_THREADS}") ]]; then + NUM_THREADS_ARG="-j ${NUM_THREADS}" +fi +# echo "NUM_THREADS_ARG: ${NUM_THREADS_ARG}" + +echo "Building ${BUILD_TYPE} project... [target: build]" +make uninstall diff --git a/bin/configure.sh b/bin/configure.sh new file mode 100755 index 00000000..45c6635e --- /dev/null +++ b/bin/configure.sh @@ -0,0 +1,97 @@ +#!/bin/bash + +# NOTE: This script is intended to be ran from the project's current build +# directory. + +# Enable case-insensitive string matches for passing arguments around +shopt -s nocasematch + +# We rely on the outside shell environment to set the system PATH to the +# appropriate locations for the development tooling, i.e.: cmake, mkdir, rm +# and so on +# PATH=/usr/bin:/usr/local/bin + +WORKING_DIR=$(pwd) + +function usage_info() +{ + SCRIPT_NAME = $(basename $0) + echo "Usage: ${SCRIPT_NAME} \n" + echo "...where is one of Debug or Release" + echo " (Defaults: ${BUILD_TYPE})\n" + echo + echo "...where is the engine's destination install prefix" + echo " (Defaults: ${BUILD_INSTALL_DIR})" + echo + echo "...where is the engine's source build tree" + echo " (Defaults: ${BUILD_DIR})" + exit 0 +} + +# Default build configuration type +BUILD_TYPE_ARG=$1 +GEN_PROJECT_TYPE_ARG=$2 +BUILD_TYPE="Debug" +BUILD_DIR=${WORKING_DIR} +GEN_PROJECT_TYPE="" # i.e. CMake project generator: Xcode + +if [[ !( -z "${BUILD_TYPE_ARG}") ]]; then + BUILD_TYPE=${BUILD_TYPE_ARG} +fi + +if [[ !( -z "${GEN_PROJECT_TYPE_ARG}") ]]; then + GEN_PROJECT_TYPE="-G${GEN_PROJECT_TYPE_ARG}" +fi + +# The absolute path to CMake's local cache of project build variables +CMAKE_CACHE_FILE="$(pwd)/CMakeCache.txt" + +# Sane engine defaults for building apps against +BUILD_FLAGS="-DEXAMPLES=on -DNOM_BUILD_TESTS=on" + +# Default installation path +BUILD_INSTALL_DIR="${HOME}/Library/Frameworks" +BUILD_INSTALL_DIR_ARG=$3 + +# Check command arguments +if [[ $1 == "-h" || $1 == "--help" ]]; then + usage_info +else + # Set build configuration type + # + # IMPORTANT: A space character **must** be inserted at the beginning of the + # BUILD_FLAGS string variable, or else the preprocessor flags will not be + # parsed correctly by cmake + if [[ ${BUILD_TYPE_ARG} == "Debug" ]]; then + BUILD_FLAGS+=" -DDEBUG=on -DDEBUG_ASSERT=on" + elif [[ ${BUILD_TYPE_ARG} == "Release" ]]; then + BUILD_TYPE="Release" + BUILD_FLAGS+=" -DDEBUG=off -DDEBUG_ASSERT=off" + fi + + echo "BUILD_FLAGS=${BUILD_FLAGS}" + + if [[ !( -z "${BUILD_INSTALL_DIR_ARG}" ) ]]; then + # Override installation prefix path; it is best to let CMake deal with + # file path validation + BUILD_INSTALL_DIR=${3} + fi + + echo "\nClearing CMake cache..." + + if [[ -f "CMakeCache.txt" ]]; then + rm -rf CMakeCache.txt + fi + + if [[ -d "CMakeFiles" ]]; then + rm -rf CMakeFiles + fi + + echo "Generating ${BUILD_TYPE} project files..." + echo "BUILD_INSTALL_DIR: ${BUILD_INSTALL_DIR}" + + cmake "$GEN_PROJECT_TYPE" ${BUILD_FLAGS} \ + -DCMAKE_OSX_DEPLOYMENT_TARGET=10.7 \ + -DCMAKE_INSTALL_PREFIX=${BUILD_INSTALL_DIR} \ + .. +fi diff --git a/bin/ctags.sh b/bin/ctags.sh new file mode 100755 index 00000000..55229bdf --- /dev/null +++ b/bin/ctags.sh @@ -0,0 +1,15 @@ +#!/bin/sh +# +# Generate project tag file (symbol reference mappings) +# +# NOTE: This script should be ran from the project's root directory, i.e.: +# ~/Projects/nomlib.git/ +# + +SOURCE_DIR="src" +HEADER_DIR="include/nomlib/" + +CTAGS_BIN="$(which ctags)" +CTAGS_FILENAME=".tags" + +${CTAGS_BIN} -f ${CTAGS_FILENAME} -R ${SOURCE_DIR} ${HEADER_DIR} diff --git a/bin/gcc_macros.sh b/bin/gcc_macros.sh new file mode 100755 index 00000000..5d701c1e --- /dev/null +++ b/bin/gcc_macros.sh @@ -0,0 +1,8 @@ +#!/bin/sh +# +# Snippet for dumping all the compiler macros for GNU GCC that are available +# to us at build time. +# + +echo | gcc -dM -E - + diff --git a/bin/gen_docs.sh b/bin/gen_docs.sh index 081fd373..feb1e9b2 100755 --- a/bin/gen_docs.sh +++ b/bin/gen_docs.sh @@ -1,64 +1,67 @@ #!/bin/sh -# -# Generate library documentation; prepare the generated documentation for -# locally viewing documentation via pow (http://pow.cx). -# -# NOTE: This script should be ran from the project's current build directory, -# i.e.: ~/Projects/nomlib.git/build -# -# Usage example: -# -# $ cd ~/Projects/nomlib.git/build -# $ ../bin/gen_docs.sh -# -DOCS_INSTALL_PREFIX="docs" +# NOTE: This script is intended to be ran from the project's current build +# directory. + +# See also: xcode_docs.sh -# Helper scripts / binaries to use -LN_BIN=$(which ln) # /usr/bin/ln (BSD LN) +LN_BIN=/bin/ln # BSD ln LN_ARGS="-sF" -# Exit if err -function check_docs_paths() -{ - if [[ -d "${DOCS_INSTALL_PREFIX}" ]]; then - continue # Success! - else - echo "ERROR: ${DOCS_INSTALL_PREFIX} file path does not exist!" - echo "\n" - echo "Have you generated documentation using 'make docs' yet?" - exit - fi +# Relative directory path to the generated documentation files -- from the +# build tree +DOCS_INSTALL_PREFIX="docs" +# Default host name to use +DOCS_HOST_NAME="nomlib-docs" - if [[ -d "${DOCS_INSTALL_PREFIX}/html" ]]; then - continue; # Success! - else - echo "ERROR: ${DOCS_INSTALL_PREFIX}/html file path does not exist!" - echo "\n" - echo "Have you generated documentation using 'make docs' yet?" - exit - fi +function usage_info() +{ + echo "Usage: $(basename $0) \n" + echo "...where is an optional parameter to allow customization " + echo "of the DNS name used to refer to the generated documentation, i.e.:\n" + echo " http://.dev" + echo "(Defaults: http://${DOCS_HOST_NAME}.dev)" + exit 0 } -# Print usage info and quit -function usage_info() +function check_env_sanity() { - echo "Usage: ./$0" - echo "\n" - exit + if [[ !(-d "${DOCS_INSTALL_PREFIX}") ]]; then + echo "ERROR: The file path ${DOCS_INSTALL_PREFIX} does not exist!\n" + echo "...Have you not yet generated the documentation set?" + exit -1 + fi + + if [[ !(-d "${DOCS_INSTALL_PREFIX}/html") ]]; then + echo "ERROR: The file path ${DOCS_INSTALL_PREFIX}/html does not exist!\n" + echo "...Have you not yet generated the documentation set?" + exit -1 + fi } if [[ ${1} == "-h" || ${1} == "--help" ]]; then usage_info else - # Sanity checks; exit if err - check_docs_paths + check_env_sanity + + if [[ !( -z "$1") ]]; then + # End-user override of the default host name + DOCS_HOST_NAME=$1 + fi DOCS_INSTALL_PREFIX=$(pwd)/${DOCS_INSTALL_PREFIX} - # Create a symbolic link from 'docs/html' to 'docs/public' for automatic - # pow configuration. See also: http://pow.cx/ - ${LN_BIN} ${LN_ARGS} ${DOCS_INSTALL_PREFIX}/html ${DOCS_INSTALL_PREFIX}/public + if [[ ! (-L ${DOCS_INSTALL_PREFIX}/public) ]]; then + # Link 'docs/html' -> 'docs/public' + ${LN_BIN} ${LN_ARGS} ${DOCS_INSTALL_PREFIX}/html ${DOCS_INSTALL_PREFIX}/public + echo "A symbolic link from ${DOCS_INSTALL_PREFIX} to ${DOCS_INSTALL_PREFIX}/public has been created." + fi + + if [[ ! (-L ${HOME}/.pow/${DOCS_HOST_NAME}) ]]; then + # Link 'docs/public' -> '~/.pow/' + ${LN_BIN} ${LN_ARGS} ${DOCS_INSTALL_PREFIX} ${HOME}/.pow/${DOCS_HOST_NAME} + echo "A symbolic link from ${DOCS_INSTALL_PREFIX} to ${HOME}/.pow/${DOCS_HOST_NAME} has been created." + fi - echo "Symbolic link created from ${DOCS_INSTALL_PREFIX} to ${DOCS_INSTALL_PREFIX}/public" + echo "The documentation set should be accessible now at http://${DOCS_HOST_NAME}.dev" fi diff --git a/bin/gen_ref_images.sh b/bin/gen_ref_images.sh new file mode 100755 index 00000000..404c7cb2 --- /dev/null +++ b/bin/gen_ref_images.sh @@ -0,0 +1,9 @@ +#!/bin/sh +TESTS_BIN_DIR=build/tests/Debug + +${TESTS_BIN_DIR}/GradientTest -r +${TESTS_BIN_DIR}/BitmapFontTest -r +${TESTS_BIN_DIR}/TrueTypeFontTest -r +${TESTS_BIN_DIR}/BMFontTest -r +${TESTS_BIN_DIR}/libRocketTest -r +${TESTS_BIN_DIR}/libRocketDataGridTest -r diff --git a/bin/linux/build.sh b/bin/linux/build.sh new file mode 100755 index 00000000..fa971a7b --- /dev/null +++ b/bin/linux/build.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +TMPDIR=$1 +[ -z $TMPDIR ] && TMPDIR=/tmp/nom + +mkdir -p $TMPDIR && cd $TMPDIR +LOG_DIR=$TMPDIR/logs +export LOG_DIR +SRC=/home/jeff/Projects/nomlib.git +mkdir -p $LOG_DIR +cmake -S $SRC -B $TMPDIR -DCMAKE_C_COMPILER=/usr/bin/clang -DCMAKE_CXX_COMPILER=/usr/bin/clang++ -DDEBUG=on -DDEBUG_ASSERT=on -DEXAMPLES=on -DNOM_BUILD_TESTS=on -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -DGTEST_ROOT="$SRC/third-party/linux/gtest" -DGTEST_INCLUDE_DIR="$SRC/third-party/linux/gtest/include" -DGTEST_LIBRARY="$SRC/third-party/linux/gtest/lib/libgtest.a" diff --git a/bin/msbuild_build.bat b/bin/msbuild_build.bat new file mode 100644 index 00000000..92fdbc47 --- /dev/null +++ b/bin/msbuild_build.bat @@ -0,0 +1,18 @@ +@echo off + +REM NOTE: This script is intended to be ran from the project's current build +REM directory. + +REM Default flags for msbuild +set MSBUILD_FLAGS=/fl1 /flp1:LogFile=nomlib_err.log;errorsonly /fl2 /flp2:LogFile=nomlib_warn.log;warningsonly /Verbosity:minimal + +if [%1]==[] ( + REM Default build type + set BUILD_TYPE=Debug +) else ( + set BUILD_TYPE=%1 +) + +echo "Build configuration: %BUILD_TYPE%..." + +msbuild /t:build /p:Configuration=%BUILD_TYPE% %MSBUILD_FLAGS% ALL_BUILD.vcxproj diff --git a/bin/msbuild_gen.bat b/bin/msbuild_gen.bat new file mode 100644 index 00000000..2fb9ee6c --- /dev/null +++ b/bin/msbuild_gen.bat @@ -0,0 +1,26 @@ +@echo off + +REM NOTE: This script is intended to be ran from the project's current build +REM directory. + +REM IF NOT EXIST build mkdir build +REM pushd build + +REM Defaults for generating project files for a Debug build +set BUILD_TYPE=Debug +set BUILD_FLAGS=-DDEBUG=on -DDEBUG_ASSERT=on + +if "%1" == "Release" set BUILD_TYPE=Release +if "%BUILD_TYPE%" == "Release" set BUILD_FLAGS= + +echo "Generating %BUILD_TYPE% project files..." +echo "BUILD_FLAGS: %BUILD_FLAGS%" + +REM Only 32-bit builds have been tested; we're probably missing library +REM dependencies for a complete 64-bit build ... +set CMAKE_FLAGS=-DARCH_32=on -DEXAMPLES=on -DNOM_BUILD_TESTS=on + +if EXIST CMakeCache.txt del CMakeCache.txt + +cmake -G "Visual Studio 12" %CMAKE_FLAGS% %BUILD_FLAGS% .. +REM popd diff --git a/bin/msbuild_install.bat b/bin/msbuild_install.bat new file mode 100644 index 00000000..68b6c196 --- /dev/null +++ b/bin/msbuild_install.bat @@ -0,0 +1,18 @@ +@echo off + +REM NOTE: This script is intended to be ran from the project's current build +REM directory. + +REM Default flags for msbuild +set MSBUILD_FLAGS=/Verbosity:minimal + +if [%1]==[] ( + REM Default build type + set BUILD_TYPE=Debug +) else ( + set BUILD_TYPE=%1 +) + +echo "Build configuration: %BUILD_TYPE%..." + +msbuild /t:build /p:Configuration=%BUILD_TYPE% %MSBUILD_FLAGS% INSTALL.vcxproj diff --git a/bin/msbuild_tests.bat b/bin/msbuild_tests.bat new file mode 100644 index 00000000..1109dd61 --- /dev/null +++ b/bin/msbuild_tests.bat @@ -0,0 +1,18 @@ +@echo off + +REM NOTE: This script is intended to be ran from the project's current build +REM directory. + +REM Default flags for msbuild +set MSBUILD_FLAGS=/Verbosity:minimal + +if [%1]==[] ( + REM Default build type + set BUILD_TYPE=Debug +) else ( + set BUILD_TYPE=%1 +) + +echo "Build configuration: %BUILD_TYPE%..." + +msbuild /t:build /p:Configuration=%BUILD_TYPE% %MSBUILD_FLAGS% RUN_TESTS.vcxproj diff --git a/bin/msbuild_uninstall.bat b/bin/msbuild_uninstall.bat new file mode 100644 index 00000000..609a4cd8 --- /dev/null +++ b/bin/msbuild_uninstall.bat @@ -0,0 +1,18 @@ +@echo off + +REM NOTE: This script is intended to be ran from the project's current build +REM directory. + +REM Default flags for msbuild +set MSBUILD_FLAGS=/Verbosity:minimal + +if [%1]==[] ( + REM Default build type + set BUILD_TYPE=Debug +) else ( + set BUILD_TYPE=%1 +) + +echo "Build configuration: %BUILD_TYPE%..." + +msbuild /t:build /p:Configuration=%BUILD_TYPE% %MSBUILD_FLAGS% uninstall.vcxproj diff --git a/bin/osx_build.sh b/bin/osx_build.sh new file mode 100755 index 00000000..6be601a7 --- /dev/null +++ b/bin/osx_build.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +# mkdir -p build +# cd build || exit 255 +cmake -DDEBUG=on -DDEBUG_ASSERT=on -DEXAMPLES=on -DNOM_BUILD_TESTS=on \ + -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 \ + -DCMAKE_INSTALL_PREFIX=~/Library/Frameworks .. +make -j12 diff --git a/bin/windev_nom.bat b/bin/windev_nom.bat new file mode 100644 index 00000000..60224615 --- /dev/null +++ b/bin/windev_nom.bat @@ -0,0 +1,19 @@ +@echo off + +REM Automated Windows build script for nomlib -- windev.local + +REM NOTE: This script is intended to be ran from the project's root +REM directory, i.e.: %HOME%/Projects/nomlib.git + +REM virgo.local source repository +unison nomlib + +IF NOT EXIST build mkdir build +REM pushd build +cd build + +if errorlevel 0 call ..\bin\msbuild_gen.bat Debug +if errorlevel 0 call ..\bin\msbuild_build.bat Debug +if errorlevel 0 call ..\bin\msbuild_install.bat Debug + +REM popd diff --git a/bin/xcode_build.sh b/bin/xcode_build.sh new file mode 100755 index 00000000..fa89d439 --- /dev/null +++ b/bin/xcode_build.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +# NOTE: This script is intended to be ran from the project's current build +# directory. + +XCODEBUILD_BIN=$(which xcodebuild) +BUILD_TYPE_ARG=$1 + +# Default +BUILD_TYPE="Debug" + +if [[ !( -z "${BUILD_TYPE_ARG}") ]]; then + BUILD_TYPE=$1 +fi + +if [[ !( -z "${NUM_THREADS}") ]]; then + NUM_THREADS_ARG="-jobs ${NUM_THREADS}" +fi +# echo "NUM_THREADS_ARG: ${NUM_THREADS_ARG}" + +echo "Building ${BUILD_TYPE} project... [target: build]" +${XCODEBUILD_BIN} ${NUM_THREADS_ARG} -configuration ${BUILD_TYPE} \ +-target ALL_BUILD diff --git a/bin/xcode_clean.sh b/bin/xcode_clean.sh new file mode 100755 index 00000000..df4ecb7f --- /dev/null +++ b/bin/xcode_clean.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +# NOTE: This script is intended to be ran from the project's current build +# directory. + +XCODEBUILD_BIN=$(which xcodebuild) +BUILD_TYPE_ARG=$1 + +# Default +BUILD_TYPE="Debug" + +if [[ !( -z "$BUILD_TYPE_ARG") ]]; then + BUILD_TYPE=$1 +fi + +echo "Cleaning ${BUILD_TYPE} project... [target: clean]" +${XCODEBUILD_BIN} -configuration ${BUILD_TYPE} clean diff --git a/bin/xcode_docs.sh b/bin/xcode_docs.sh new file mode 100755 index 00000000..8bc973ee --- /dev/null +++ b/bin/xcode_docs.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +# NOTE: This script is intended to be ran from the project's current build +# directory. + +XCODEBUILD_BIN=$(which xcodebuild) +BUILD_TYPE_ARG=$1 + +# Default +BUILD_TYPE="Debug" + +if [[ !( -z "${BUILD_TYPE_ARG}") ]]; then + BUILD_TYPE=$1 +fi + +echo "Building ${BUILD_TYPE} project... [target: docs]" +${XCODEBUILD_BIN} -configuration ${BUILD_TYPE} -target docs + +# Optionally setup local site access for the generated documentation set at +# http://nomlib-docs.dev -- requires an existing Pow installation [1]. +# +# 1. http://pow.cx/ +if [[ $(which pow) ]]; then + ../bin/gen_docs.sh nomlib-docs + + # TODO: Finish integration of this script! i.e.: If we could try to remember + # the last jump URL before reloading the page(s), it would be perfect! + /usr/bin/osascript ${HOME}/Projects/dotfiles.git/AppleScript/chrome_reload.applescript +fi diff --git a/bin/xcode_gen.sh b/bin/xcode_gen.sh new file mode 100755 index 00000000..b24fe980 --- /dev/null +++ b/bin/xcode_gen.sh @@ -0,0 +1,66 @@ +#!/bin/sh + +# NOTE: This script is intended to be ran from the project's current build +# directory. + +# Enable case-insensitive string matches for passing arguments around +shopt -s nocasematch + +MKDIR_BIN=$(which mkdir) # GNU coreutils +RM_BIN=$(which rm) # GNU coreutils +CMAKE_BIN=$(which cmake) + +BUILD_TYPE_ARG=$1 +BUILD_INSTALL_DIR_ARG=$2 + +# The absolute path to CMake's local cache of project build variables +CMAKE_CACHE_FILE="$(pwd)/CMakeCache.txt" + +# Defaults for generating project files for a Debug build +BUILD_TYPE="Debug" +BUILD_FLAGS="-DDEBUG=on -DDEBUG_ASSERT=on" + +# Default installation path +BUILD_INSTALL_DIR="${HOME}/Library/Frameworks" + +function usage_info() +{ + echo "Usage: $(basename $0) \n" + echo "...where is either Debug or Release" + echo " (Defaults: ${BUILD_TYPE})\n" + echo "...where is the installation prefix for the engine " + echo "libraries and header files" + echo " (Defaults: ${BUILD_INSTALL_DIR})" + exit 0 +} + +if [[ $1 == "-h" || $1 == "--help" ]]; then + usage_info +else + + if [[ ${BUILD_TYPE_ARG} == "Release" ]]; then + BUILD_TYPE="Release" + BUILD_FLAGS="" + fi + + if [[ !( -z "${BUILD_INSTALL_DIR_ARG}" ) ]]; then + # Custom installation dir; let CMake worry about the file path validation! + BUILD_INSTALL_DIR=${BUILD_INSTALL_DIR_ARG} + fi + + if [[ -f "${CMAKE_CACHE_FILE}" ]]; then + ${RM_BIN} ${CMAKE_CACHE_FILE} + fi + + echo "Generating ${BUILD_TYPE} project files..." + echo "BUILD_FLAGS: ${BUILD_FLAGS}" + echo "BUILD_INSTALL_DIR: ${BUILD_INSTALL_DIR}" + + # I have no idea **why**, but my Sublime Text build script fails unless I + # create this directory beforehand ... + ${MKDIR_BIN} -p CMakeFiles/ALL_BUILD + + ${CMAKE_BIN} -GXcode -DCMAKE_INSTALL_PREFIX=${BUILD_INSTALL_DIR} \ + -DCMAKE_OSX_DEPLOYMENT_TARGET=10.7 ${BUILD_FLAGS} \ + -DEXAMPLES=on -DNOM_BUILD_TESTS=on .. +fi diff --git a/bin/xcode_install.sh b/bin/xcode_install.sh new file mode 100755 index 00000000..b27b4426 --- /dev/null +++ b/bin/xcode_install.sh @@ -0,0 +1,30 @@ +#!/bin/sh + +# NOTE: This script is intended to be ran from the project's current build +# directory. + +XCODEBUILD_BIN=$(which xcodebuild) +BUILD_TYPE_ARG=$1 + +# Default +BUILD_TYPE="Debug" + +if [[ !( -z "${BUILD_TYPE_ARG}") ]]; then + BUILD_TYPE=$1 +fi + +if [[ !( -z "${NUM_THREADS}") ]]; then + NUM_THREADS_ARG="-jobs ${NUM_THREADS}" +fi +echo "NUM_THREADS_ARG: ${NUM_THREADS_ARG}" + +# Try to not waste time by trying to install an incomplete build +if [[ $? -eq 0 ]]; then + echo "Installing ${BUILD_TYPE} project... [target: install]" + ${XCODEBUILD_BIN} ${NUM_THREADS_ARG} -configuration ${BUILD_TYPE} \ + -target install +else + echo "Previous build command failed; **NOT** installing ${BUILD_TYPE} project!" + echo "EXIT_CODE: $?" + exit $? +fi diff --git a/bin/xcode_uninstall.sh b/bin/xcode_uninstall.sh new file mode 100755 index 00000000..67e7cc99 --- /dev/null +++ b/bin/xcode_uninstall.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +# NOTE: This script is intended to be ran from the project's current build +# directory. + +XCODEBUILD_BIN=$(which xcodebuild) +BUILD_TYPE_ARG=$1 + +# Default +BUILD_TYPE="Debug" + +if [[ !( -z "${BUILD_TYPE_ARG}") ]]; then + BUILD_TYPE=$1 +fi + +# Ensure that we do not accidentally lose anything by jumping the gun too +# early! ;-) +if [[ $? -eq 0 ]]; then + echo "Uninstalling ${BUILD_TYPE} project... [target: uninstall]" + ${XCODEBUILD_BIN} -configuration ${BUILD_TYPE} -target uninstall +else + exit -1 +fi diff --git a/cmake/functions.cmake b/cmake/functions.cmake new file mode 100644 index 00000000..1b478a6f --- /dev/null +++ b/cmake/functions.cmake @@ -0,0 +1,111 @@ +# cmake/functions.cmake:jeff +# +# Helper functions for CMake build scripts +# + +# Create and link a library module +# +# source parameter should be enclosed within double quotes. +# headers parameter is not implemented; reserved for future implementation. +# +# external_deps parameters should be separated by semicolons when multiple +# dependencies are specified and enclosed within double quotes. +# +# TODO: Future expansion of this macro should strongly consider refactoring with +# the use of the CMakeParseArguments module. +# http://www.cmake.org/cmake/help/v3.0/module/CMakeParseArguments.html +function(nom_add_library target lib_type source headers external_deps ) + # The library version defines the full build version of said library and + # is used in the actual filename on disk. + set ( LIB_VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}" ) + + # This is embedded as the SONAME in the library's ELF header and determinnes + # which file the dynamic linker searches for at runtime, i.e.: + # `libnomlib-actions.so.1 -> `libnomlib-actions.so.0.11` + # The Application Binary Interface (ABI) version; PATCH level versions are + # intended **not** to break the ABI version. + set ( LIB_SOVERSION ${PROJECT_VERSION_MAJOR} ) + + add_library( ${target} ${lib_type} ${source} ) + + set_target_properties( ${target} PROPERTIES VERSION ${LIB_VERSION} + SOVERSION ${LIB_SOVERSION} DEBUG_POSTFIX "-d" ) + target_link_libraries( ${target} ${external_deps} ) + + if( PLATFORM_OSX AND FRAMEWORK ) + + # Create target.framework + set_target_properties( ${target} PROPERTIES + FRAMEWORK TRUE + MACOSX_FRAMEWORK_INFO_PLIST + "${CMAKE_TEMPLATE_PATH}/Info.plist.in" + MACOSX_FRAMEWORK_NAME + "${target}" + MACOSX_FRAMEWORK_BUNDLE_VERSION + "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}-${CMAKE_BUILD_TYPE}" + MACOSX_FRAMEWORK_SHORT_VERSION_STRING + "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}" + MACOSX_FRAMEWORK_IDENTIFIER + "net.i8degrees.${target}" + # PUBLIC_HEADER + # "${source}" + ) + endif( PLATFORM_OSX AND FRAMEWORK ) + + # Copy target's library file to $CMAKE_INSTALL_PREFIX/lib + install( TARGETS ${target} + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib + LIBRARY FRAMEWORK DESTINATION ${CMAKE_INSTALL_PREFIX} ) + +endfunction(nom_add_library) + +# Copy resource files for engine examples and tests. +# nom_install_resources(spath, dpath, options) +function(install_resource_file spath dpath) + install( + FILES "${spath}" + DESTINATION "${dpath}") +endfunction() + +function(install_resource_dir spath dpath) + install( + DIRECTORY "${spath}" + DESTINATION "${dpath}" + PATTERN ".*" EXCLUDE ) +endfunction() + +# +# target parameter is not implemented; reserved for future implementation. +# +# dest parameter is not implemented; reserved for future implementation. +# macro(nom_install_dep target external_deps dest) + +# # Bundle the appropriate external dependencies +# foreach( dep ${external_deps} ) + +# if( IS_DIRECTORY ${dep} ) + +# # Bundle frameworks we depend on that are not system library bundles +# install( DIRECTORY ${dep} +# DESTINATION "nomlib.framework/Frameworks" +# PATTERN ".*" EXCLUDE ) + +# else( NOT IS_DIRECTORY ${dep} ) + +# # if( IS_SYMLINK ${dep} ) +# # # Resolve real file path when symbolic so CMake's install command +# # # copies the real file +# # get_filename_component( dep ${dep} REALPATH ) +# # endif( IS_SYMLINK ${dep} ) +# # message( STATUS "DEP IS A FILE: ${dep}" ) + +# # Bundle dynamic libraries (*.dylib) that we depend on +# install( FILES ${dep} +# DESTINATION "nomlib.framework/Frameworks" +# PATTERN ".*" EXCLUDE ) + +# endif( IS_DIRECTORY ${dep} ) +# endforeach( dep ${external_deps} ) + +# endmacro(nom_install_dep target external_deps dest) diff --git a/cmake/macros.cmake b/cmake/macros.cmake index c6c20aea..e3b63b0c 100644 --- a/cmake/macros.cmake +++ b/cmake/macros.cmake @@ -1,3 +1,5 @@ +# cmake/macros.cmake:jeff +# # Helper macros for CMake build scripts # @@ -51,122 +53,36 @@ macro ( install_name_rpath rpath binary_path ) endmacro ( install_name_rpath rpath binary_path ) -# Create and link a library module -# -# source parameter should be enclosed within double quotes. -# headers parameter is not implemented; reserved for future implementation. +# Helper function for adding tests through CTest # -# external_deps parameters should be separated by semicolons when multiple -# dependencies are specified and enclosed within double quotes. -# -# TODO: Future expansion of this macro should strongly consider refactoring with -# the use of the CMakeParseArguments module. -# http://www.cmake.org/cmake/help/v3.0/module/CMakeParseArguments.html -macro(nom_add_library target lib_type source headers external_deps) - - add_library( ${target} ${lib_type} ${source} ) - - # The Application Binary Interface (ABI) version; PATCH level versions are - # intended **not** to break the ABI version. - set_target_properties( ${target} PROPERTIES SOVERSION - "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}" - ) - - # The Application Programming Interface (API) version - set_target_properties( ${target} PROPERTIES VERSION - "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}" - ) - - set_target_properties( ${target} PROPERTIES DEBUG_POSTFIX "-d" ) - - target_link_libraries( ${target} ${external_deps} ) - - if( PLATFORM_OSX AND FRAMEWORK ) - - # Create target.framework - set_target_properties( ${target} PROPERTIES - FRAMEWORK TRUE - MACOSX_FRAMEWORK_INFO_PLIST - "${CMAKE_TEMPLATE_PATH}/Info.plist.in" - MACOSX_FRAMEWORK_NAME - "${target}" - MACOSX_FRAMEWORK_BUNDLE_VERSION - "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}-${CMAKE_BUILD_TYPE}" - MACOSX_FRAMEWORK_SHORT_VERSION_STRING - "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}" - MACOSX_FRAMEWORK_IDENTIFIER - "net.i8degrees.${target}" - # TODO? - # PUBLIC_HEADER - # "${source}" - ) - endif( PLATFORM_OSX AND FRAMEWORK ) - - # Copy target's library file to $CMAKE_INSTALL_PREFIX/lib - if( NOT PLATFORM_WINDOWS ) - install( TARGETS ${target} - LIBRARY DESTINATION lib - ARCHIVE DESTINATION lib - LIBRARY FRAMEWORK DESTINATION ${CMAKE_INSTALL_PREFIX} ) - - # FIXME: This is the only way I've been able to get the generated MSVC project - # files to cooperate with CMake -- both the output directory and target name - # (set properly from the MSVC generator) differ from what CMake thinks they - # ought to be. - else( PLATFORM_WINDOWS ) - - if( CMAKE_BUILD_TYPE STREQUAL "Debug" ) - set( LIBRARY_PATH - "${CMAKE_BINARY_DIR}/${CMAKE_BUILD_TYPE}/${target}-d.lib" - ) - else( NOT CMAKE_BUILD_TYPE STREQUAL "Debug" ) - set( LIBRARY_PATH - "${CMAKE_BINARY_DIR}/${CMAKE_BUILD_TYPE}/${target}.lib" - ) - endif( CMAKE_BUILD_TYPE STREQUAL "Debug" ) - - install( FILES - ${LIBRARY_PATH} - DESTINATION lib - ) - endif( NOT PLATFORM_WINDOWS ) - -endmacro(nom_add_library) - +# IMPORTANT: We cannot use the GTEST_ADD_TESTS macro here for adding tests that +# rely on the nom::VisualUnitTest framework because of the way that the macro +# breaks up the test run -- it ends up executing each individual test in a +# separate process, i.e.: 'SpriteTest.SpriteInterfaceWithTextureReference' and +# 'SpriteTest.SpriteInterfaceWithTextureRawPointer' are treated as two +# separated executable binaries. +# This is bad for us because our screen-dumping creates new timestamped +# directories on every new instance of the framework, which normally is OK +# because this yields one directory, but in the case of multiple executable +# runs ... spawns an awful lot more than I'd prefer. +# I hope to one day figure out a proper solution for this work flow issue, +# but in the mean time ... this is the best I can come up with. +macro( nom_add_visual_test test_name executable ) + add_test( ${test_name} ${executable} + --gtest_filter=${test_name}.* ${ARGN} ) +endmacro() + +# Helper function for adding an engine unit test # -# target parameter is not implemented; reserved for future implementation. -# -# dest parameter is not implemented; reserved for future implementation. -# macro(nom_install_dep target external_deps dest) - -# # Bundle the appropriate external dependencies -# foreach( dep ${external_deps} ) - -# if( IS_DIRECTORY ${dep} ) - -# # Bundle frameworks we depend on that are not system library bundles -# install( DIRECTORY ${dep} -# DESTINATION "nomlib.framework/Frameworks" -# PATTERN ".*" EXCLUDE ) - -# else( NOT IS_DIRECTORY ${dep} ) - -# # if( IS_SYMLINK ${dep} ) -# # # Resolve real file path when symbolic so CMake's install command -# # # copies the real file -# # get_filename_component( dep ${dep} REALPATH ) -# # endif( IS_SYMLINK ${dep} ) -# # message( STATUS "DEP IS A FILE: ${dep}" ) - -# # Bundle dynamic libraries (*.dylib) that we depend on -# install( FILES ${dep} -# DESTINATION "nomlib.framework/Frameworks" -# PATTERN ".*" EXCLUDE ) - -# endif( IS_DIRECTORY ${dep} ) -# endforeach( dep ${external_deps} ) - -# endmacro(nom_install_dep target external_deps dest) +# IMPORTANT: Avoid using the newer add_test syntax, i.e.: +# add_test(NAME COMMAND ), because these tests are not +# added to the default test configuration! Using the newer add_test +# syntax leads me to this err message when running ctest from the project's +# build directory (CMake generated XCode project files): +# "Test not available without configuration. (Missing "-C "?)" +macro( nom_add_test test_name test_executable ) + add_test( ${test_name} ${test_executable} ${ARGN} ) +endmacro() macro(NOM_LOG_INFO msg) message( STATUS "INFO: ${msg}" ) diff --git a/cmake/modules/FindLibRocket.cmake b/cmake/modules/FindLibRocket.cmake index 0824c76f..c3c2223b 100644 --- a/cmake/modules/FindLibRocket.cmake +++ b/cmake/modules/FindLibRocket.cmake @@ -44,10 +44,11 @@ set( LIBROCKET_DEBUGGER_NAMES # RocketControlsLua # ) +# Prefix search path set( LIBROCKET_SEARCH_PATHS ~/Library/Frameworks /Library/Frameworks - /usr/local/include # homebrew + /usr/local # homebrew /sw # Fink /opt/local # DarwinPorts /opt/csw # Blastwave @@ -102,7 +103,7 @@ find_library( LIBROCKET_CORE_LIBRARY HINTS $ENV{LIBROCKETDIR} PATH_SUFFIXES - lib64 lib ${VC_LIB_PATH_SUFFIX} + lib64 lib lib/i386-linux-gnu ${VC_LIB_PATH_SUFFIX} PATHS ${LIBROCKET_SEARCH_PATHS} ) @@ -112,7 +113,7 @@ find_library( LIBROCKET_CONTROLS_LIBRARY HINTS $ENV{LIBROCKETDIR} PATH_SUFFIXES - lib64 lib ${VC_LIB_PATH_SUFFIX} + lib64 lib lib/i386-linux-gnu ${VC_LIB_PATH_SUFFIX} PATHS ${LIBROCKET_SEARCH_PATHS} ) @@ -122,7 +123,7 @@ find_library( LIBROCKET_DEBUGGER_LIBRARY HINTS $ENV{LIBROCKETDIR} PATH_SUFFIXES - lib64 lib ${VC_LIB_PATH_SUFFIX} + lib64 lib lib/i386-linux-gnu ${VC_LIB_PATH_SUFFIX} PATHS ${LIBROCKET_SEARCH_PATHS} ) @@ -166,7 +167,7 @@ set( LIBROCKET_LIBRARIES # all listed variables are TRUE include( FindPackageHandleStandardArgs ) -find_package_handle_standard_args( libRocket +find_package_handle_standard_args( LibRocket REQUIRED_VARS LIBROCKET_CORE_LIBRARY LIBROCKET_CONTROLS_LIBRARY diff --git a/cmake/platform.cmake b/cmake/platform.cmake index 513f64f0..7bc67a7a 100644 --- a/cmake/platform.cmake +++ b/cmake/platform.cmake @@ -39,13 +39,42 @@ if ( CMAKE_SYSTEM_NAME STREQUAL "Darwin" ) message ( STATUS "Platform: Darwin (Mac OS X)" ) elseif ( CMAKE_SYSTEM_NAME STREQUAL "Linux" ) # Tested on Ubuntu v12.04-LTS - # TODO: Rename to NOM_PLATFORM_LINUX + set( PLATFORM_LINUX true ) + # DEPRECATED(JEFF): Use `PLATFORM_POSIX` instead as `NOM_PLATFORM_POSIX` + # does not fit with the other platform variables we define. We must update + # our codebase to not use the following before we can remove it! set( NOM_PLATFORM_POSIX true ) + set( PLATFORM_POSIX true ) - # Clang is not supported on Linux due to libc++ not being distributed by - # default yet - set ( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x" ) + # TODO(JEFF): Create build time option to override compilation tooling + # between clang and probably even GNU GCC, once we find the right C++ + # variant for it... + # + # Default to clang based tooling when found... + if ( CMAKE_CXX_COMPILER MATCHES "clang" ) + message ( STATUS "Using clang based platform to build..." ) + #set ( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14" ) + # nomlib began its life under c++14 on Intel Darwin Mac OSX + set ( CMAKE_CXX_STANDARD 14 ) + # >> Modern GTest build requires a c++17 minimum + set ( CMAKE_CXX_STANDARD 17 ) + set ( CMAKE_CXX_STANDARD_REQUIRED ON ) + set ( CMAKE_CXX_EXTENSIONS OFF) + + # libc++ requires OSX v10.7+ + #set ( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -stdlib=libc++" ) + elseif ( CMAKE_C_COMPILER MATCHES "gcc" ) + message ( STATUS "Using gcc based platform to build..." ) + message ( FATAL_ERROR "nomlib only supports building with clang." ) + + # !! GoogleTest unit testing framework v1.10.x requires a minimum C++ level 11 + set( CMAKE_CXX_STANDARD 11 ) + # !! Our engine is based on a c++ level of 14 + set( CMAKE_CXX_STANDARD 14 ) + # NOTE(JEFF): This should only be set when GNU GCC is enabled? + set ( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x" ) + endif( CMAKE_CXX_COMPILER MATCHES "clang" ) # ARCH_32 and ARCH_64 are not presently used here, but are reserved for future # consistency with the other supported platforms. @@ -53,12 +82,13 @@ elseif ( CMAKE_SYSTEM_NAME STREQUAL "Linux" ) # Tested on Ubuntu v12.04-LTS option ( ARCH_64 "Compile ${PROJECT_NAME} as a 64-bit library" on ) message ( STATUS "Platform: Linux" ) + message ( STATUS "Build platform: ${CMAKE_CXX_COMPILER}" ) + message ( STATUS "Compiler C++ level: ${CMAKE_CXX_STANDARD}" ) + message ( STATUS "Compiler flags: ${CMAKE_CXX_FLAGS}" ) elseif ( CMAKE_SYSTEM_NAME STREQUAL "Windows" ) # TODO: Rename to NOM_PLATFORM_WINDOWS set( PLATFORM_WINDOWS true ) - set ( CMAKE_CONFIGURATION_TYPES "${CMAKE_BUILD_TYPE}" ) - option ( ARCH_32 "Compile ${PROJECT_NAME} as a 32-bit library" off ) option ( ARCH_64 "Compile ${PROJECT_NAME} as a 64-bit library" on ) @@ -97,3 +127,4 @@ elseif ( PLATFORM_WINDOWS AND ARCH_64 ) endif ( PLATFORM_WINDOWS AND ARCH_32 ) message ( STATUS "Platform Architecture: ${PLATFORM_ARCH}" ) +#message ( STATUS "CXX=${CMAKE_CXX_FLAGS}" ) diff --git a/cmake/platforms/WinMSVC.cmake b/cmake/platforms/WinMSVC.cmake new file mode 100644 index 00000000..f02831eb --- /dev/null +++ b/cmake/platforms/WinMSVC.cmake @@ -0,0 +1,345 @@ +# https://github.com/llvm/llvm-project/blob/main/llvm/cmake/platforms/WinMsvc.cmake +# +# Cross toolchain configuration for using clang-cl on non-Windows hosts to +# target MSVC. +# +# Usage: +# cmake -G Ninja +# -DCMAKE_TOOLCHAIN_FILE=/path/to/this/file +# -DHOST_ARCH=[aarch64|arm64|armv7|arm|i686|x86|x86_64|x64] +# -DLLVM_NATIVE_TOOLCHAIN=/path/to/llvm/installation +# -DLLVM_WINSYSROOT=/path/to/win/sysroot +# -DMSVC_VER=vc tools version folder name +# -DWINSDK_VER=windows sdk version folder name +# +# HOST_ARCH: +# The architecture to build for. +# +# LLVM_NATIVE_TOOLCHAIN: +# *Absolute path* to a folder containing the toolchain which will be used to +# build. At a minimum, this folder should have a bin directory with a +# copy of clang-cl, clang, clang++, and lld-link, as well as a lib directory +# containing clang's system resource directory. +# +# MSVC_VER/WINSDK_VER: +# (Optional) if not specified, highest version number is used if any. +# +# LLVM_WINSYSROOT and MSVC_VER work together to define a folder layout that +# containing MSVC headers and system libraries. The layout of the folder +# matches that which is intalled by MSVC 2017 on Windows, and should look like +# this: +# +# ${LLVM_WINSYSROOT}/VC/Tools/MSVC/${MSVC_VER}/ +# include +# vector +# stdint.h +# etc... +# lib +# x64 +# libcmt.lib +# msvcrt.lib +# etc... +# x86 +# libcmt.lib +# msvcrt.lib +# etc... +# +# For versions of MSVC < 2017, or where you have a hermetic toolchain in a +# custom format, you must use symlinks or restructure it to look like the above. +# +# LLVM_WINSYSROOT and WINSDK_VER work together to define a folder layout that +# matches that of the Windows SDK installation on a standard Windows machine. +# It should match the layout described below. +# +# Note that if you install Windows SDK to a windows machine and simply copy the +# files, it will already be in the correct layout. +# +# ${LLVM_WINSYSROOT}/Windows Kits/10/ +# Include +# ${WINSDK_VER} +# shared +# ucrt +# um +# windows.h +# etc... +# Lib +# ${WINSDK_VER} +# ucrt +# x64 +# x86 +# ucrt.lib +# etc... +# um +# x64 +# x86 +# kernel32.lib +# etc +# +# IMPORTANT: In order for this to work, you will need a valid copy of the Windows +# SDK and C++ STL headers and libraries on your host. Additionally, since the +# Windows libraries and headers are not case-correct, this toolchain file sets +# up a VFS overlay for the SDK headers and case-correcting symlinks for the +# libraries when running on a case-sensitive filesystem. + +include_guard(GLOBAL) + +# When configuring CMake with a toolchain file against a top-level CMakeLists.txt, +# it will actually run CMake many times, once for each small test program used to +# determine what features a compiler supports. By default, none of these +# invocations share a CMakeCache.txt with the top-level invocation, meaning they +# won't see the value of any arguments the user passed via -D. Since these are +# necessary to properly configure MSVC in both the top-level configuration as well as +# all feature-test invocations, we include them in CMAKE_TRY_COMPILE_PLATFORM_VARIABLES, +# so that they get inherited by child invocations. +list(APPEND CMAKE_TRY_COMPILE_PLATFORM_VARIABLES + HOST_ARCH + LLVM_NATIVE_TOOLCHAIN + LLVM_WINSYSROOT + MSVC_VER + WINSDK_VER + msvc_lib_symlinks_dir + winsdk_lib_symlinks_dir + winsdk_vfs_overlay_path + ) + +function(generate_winsdk_vfs_overlay winsdk_include_dir output_path) + set(include_dirs) + file(GLOB_RECURSE entries LIST_DIRECTORIES true "${winsdk_include_dir}/*") + foreach(entry ${entries}) + if(IS_DIRECTORY "${entry}") + list(APPEND include_dirs "${entry}") + endif() + endforeach() + + file(WRITE "${output_path}" "version: 0\n") + file(APPEND "${output_path}" "case-sensitive: false\n") + file(APPEND "${output_path}" "roots:\n") + + foreach(dir ${include_dirs}) + file(GLOB headers RELATIVE "${dir}" "${dir}/*.h") + if(NOT headers) + continue() + endif() + + file(APPEND "${output_path}" " - name: \"${dir}\"\n") + file(APPEND "${output_path}" " type: directory\n") + file(APPEND "${output_path}" " contents:\n") + + foreach(header ${headers}) + file(APPEND "${output_path}" " - name: \"${header}\"\n") + file(APPEND "${output_path}" " type: file\n") + file(APPEND "${output_path}" " external-contents: \"${dir}/${header}\"\n") + endforeach() + endforeach() +endfunction() + +function(generate_winsdk_lib_symlinks winsdk_um_lib_dir output_dir) + execute_process(COMMAND "${CMAKE_COMMAND}" -E make_directory "${output_dir}") + file(GLOB libraries RELATIVE "${winsdk_um_lib_dir}" "${winsdk_um_lib_dir}/*") + foreach(library ${libraries}) + string(TOLOWER "${library}" all_lowercase_symlink_name) + if(NOT library STREQUAL all_lowercase_symlink_name) + execute_process(COMMAND "${CMAKE_COMMAND}" + -E create_symlink + "${winsdk_um_lib_dir}/${library}" + "${output_dir}/${all_lowercase_symlink_name}") + endif() + + get_filename_component(name_we "${library}" NAME_WE) + get_filename_component(ext "${library}" EXT) + string(TOLOWER "${ext}" lowercase_ext) + set(lowercase_ext_symlink_name "${name_we}${lowercase_ext}") + if(NOT library STREQUAL lowercase_ext_symlink_name AND + NOT all_lowercase_symlink_name STREQUAL lowercase_ext_symlink_name) + execute_process(COMMAND "${CMAKE_COMMAND}" + -E create_symlink + "${winsdk_um_lib_dir}/${library}" + "${output_dir}/${lowercase_ext_symlink_name}") + endif() + endforeach() +endfunction() + +function(generate_msvc_lib_symlinks msvc_lib_dir output_dir) + execute_process(COMMAND "${CMAKE_COMMAND}" -E make_directory "${output_dir}") + file(GLOB libraries RELATIVE "${msvc_lib_dir}" "${msvc_lib_dir}/*.lib") + foreach(library ${libraries}) + get_filename_component(name_wle "${library}" NAME_WLE) + get_filename_component(ext "${library}" LAST_EXT) + string(TOLOWER "${ext}" lowercase_ext) + string(TOUPPER "${name_wle}" all_uppercase_symlink_name_wle) + set(uppercase_symlink_name "${all_uppercase_symlink_name_wle}${lowercase_ext}") + if(NOT library STREQUAL uppercase_symlink_name) + execute_process(COMMAND "${CMAKE_COMMAND}" + -E create_symlink + "${msvc_lib_dir}/${library}" + "${output_dir}/${uppercase_symlink_name}") + endif() + endforeach() +endfunction() + +function(get_highest_version the_dir the_ver) + file(GLOB entries LIST_DIRECTORIES true RELATIVE "${the_dir}" "${the_dir}/[0-9.]*") + foreach(entry ${entries}) + if(IS_DIRECTORY "${the_dir}/${entry}") + set(${the_ver} "${entry}" PARENT_SCOPE) + endif() + endforeach() +endfunction() + +set(CMAKE_SYSTEM_NAME Windows) +set(CMAKE_SYSTEM_VERSION 10.0) +set(CMAKE_SYSTEM_PROCESSOR AMD64) + +if(NOT HOST_ARCH) + set(HOST_ARCH x86_64) +endif() +if(HOST_ARCH STREQUAL "aarch64" OR HOST_ARCH STREQUAL "arm64") + set(TRIPLE_ARCH "aarch64") + set(WINSDK_ARCH "arm64") +elseif(HOST_ARCH STREQUAL "armv7" OR HOST_ARCH STREQUAL "arm") + set(TRIPLE_ARCH "armv7") + set(WINSDK_ARCH "arm") +elseif(HOST_ARCH STREQUAL "i686" OR HOST_ARCH STREQUAL "x86") + set(TRIPLE_ARCH "i686") + set(WINSDK_ARCH "x86") +elseif(HOST_ARCH STREQUAL "x86_64" OR HOST_ARCH STREQUAL "x64") + set(TRIPLE_ARCH "x86_64") + set(WINSDK_ARCH "x64") +else() + message(SEND_ERROR "Unknown host architecture ${HOST_ARCH}. Must be aarch64 (or arm64), armv7 (or arm), i686 (or x86), or x86_64 (or x64).") +endif() + +# Do some sanity checking to make sure we can find a native toolchain and +# that the Windows SDK / MSVC STL directories look kosher. +if(NOT EXISTS "${LLVM_NATIVE_TOOLCHAIN}/bin/clang-cl" OR + NOT EXISTS "${LLVM_NATIVE_TOOLCHAIN}/bin/lld-link") + message(SEND_ERROR + "LLVM_NATIVE_TOOLCHAIN folder '${LLVM_NATIVE_TOOLCHAIN}' does not " + "point to a valid directory containing bin/clang-cl and bin/lld-link " + "binaries") +endif() + +if (NOT MSVC_VER) + get_highest_version("${LLVM_WINSYSROOT}/VC/Tools/MSVC" MSVC_VER) +endif() + +if (NOT WINSDK_VER) + get_highest_version("${LLVM_WINSYSROOT}/Windows Kits/10/Include" WINSDK_VER) +endif() + +if (NOT LLVM_WINSYSROOT OR NOT MSVC_VER OR NOT WINSDK_VER) + message(SEND_ERROR + "Must specify CMake variable LLVM_WINSYSROOT, MSVC_VER and WINSDK_VER") +endif() + +set(ATLMFC_LIB "${LLVM_WINSYSROOT}/VC/Tools/MSVC/${MSVC_VER}/atlmfc/lib") +set(MSVC_INCLUDE "${LLVM_WINSYSROOT}/VC/Tools/MSVC/${MSVC_VER}/include") +set(MSVC_LIB "${LLVM_WINSYSROOT}/VC/Tools/MSVC/${MSVC_VER}/lib") +set(WINSDK_INCLUDE "${LLVM_WINSYSROOT}/Windows Kits/10/Include/${WINSDK_VER}") +set(WINSDK_LIB "${LLVM_WINSYSROOT}/Windows Kits/10/Lib/${WINSDK_VER}") + +if (NOT EXISTS "${MSVC_INCLUDE}" OR NOT EXISTS "${MSVC_LIB}") + message(SEND_ERROR + "CMake variable LLVM_WINSYSROOT and MSVC_VER must point to a folder " + "containing MSVC system headers and libraries") +endif() + +if(NOT EXISTS "${WINSDK_INCLUDE}" OR NOT EXISTS "${WINSDK_LIB}") + message(SEND_ERROR + "CMake variable LLVM_WINSYSROOT and WINSDK_VER must resolve to a " + "valid Windows SDK installation") +endif() + +if(NOT EXISTS "${WINSDK_INCLUDE}/um/Windows.h") + message(SEND_ERROR "Cannot find Windows.h") +endif() +if(NOT EXISTS "${WINSDK_INCLUDE}/um/WINDOWS.H") + set(case_sensitive_filesystem TRUE) +endif() + +set(CMAKE_C_COMPILER "${LLVM_NATIVE_TOOLCHAIN}/bin/clang-cl" CACHE FILEPATH "") +set(CMAKE_CXX_COMPILER "${LLVM_NATIVE_TOOLCHAIN}/bin/clang-cl" CACHE FILEPATH "") +set(CMAKE_LINKER "${LLVM_NATIVE_TOOLCHAIN}/bin/lld-link" CACHE FILEPATH "") +set(CMAKE_AR "${LLVM_NATIVE_TOOLCHAIN}/bin/llvm-lib" CACHE FILEPATH "") +set(CMAKE_ASM_MASM_COMPILER "${LLVM_NATIVE_TOOLCHAIN}/bin/llvm-ml" CACHE FILEPATH "") + +# Even though we're cross-compiling, we need some native tools (e.g. llvm-tblgen), and those +# native tools have to be built before we can start doing the cross-build. LLVM supports +# a CROSS_TOOLCHAIN_FLAGS_NATIVE argument which consists of a list of flags to pass to CMake +# when configuring the NATIVE portion of the cross-build. By default we construct this so +# that it points to the tools in the same location as the native clang-cl that we're using. +list(APPEND _CTF_NATIVE_DEFAULT "-DCMAKE_ASM_COMPILER=${LLVM_NATIVE_TOOLCHAIN}/bin/clang") +list(APPEND _CTF_NATIVE_DEFAULT "-DCMAKE_C_COMPILER=${LLVM_NATIVE_TOOLCHAIN}/bin/clang") +list(APPEND _CTF_NATIVE_DEFAULT "-DCMAKE_CXX_COMPILER=${LLVM_NATIVE_TOOLCHAIN}/bin/clang++") + +# These flags are used during build time. So if CFLAGS/CXXFLAGS/LDFLAGS is set +# for the target, makes sure these are unset during build time. +set(CROSS_TOOLCHAIN_FLAGS_NATIVE "${_CTF_NATIVE_DEFAULT}" CACHE STRING "") + +set(COMPILE_FLAGS + -D_CRT_SECURE_NO_WARNINGS + --target=${TRIPLE_ARCH}-windows-msvc + -fms-compatibility-version=19.28 + -vctoolsversion ${MSVC_VER} + -winsdkversion ${WINSDK_VER} + -winsysroot ${LLVM_WINSYSROOT}) + +if(case_sensitive_filesystem) + # Ensure all sub-configures use the top-level VFS overlay instead of generating their own. + if(NOT winsdk_vfs_overlay_path) + set(winsdk_vfs_overlay_path "${CMAKE_BINARY_DIR}/winsdk_vfs_overlay.yaml") + generate_winsdk_vfs_overlay("${WINSDK_INCLUDE}" "${winsdk_vfs_overlay_path}") + endif() + list(APPEND COMPILE_FLAGS + -Xclang -ivfsoverlay -Xclang "${winsdk_vfs_overlay_path}") +endif() + +string(REPLACE ";" " " COMPILE_FLAGS "${COMPILE_FLAGS}") +string(APPEND CMAKE_C_FLAGS_INIT " ${COMPILE_FLAGS}") +string(APPEND CMAKE_CXX_FLAGS_INIT " ${COMPILE_FLAGS}") +if(TRIPLE_ARCH STREQUAL "x86_64") + string(APPEND CMAKE_ASM_MASM_FLAGS_INIT " -m64") +endif() + +set(LINK_FLAGS + # Prevent CMake from attempting to invoke mt.exe. It only recognizes the slashed form and not the dashed form. + /manifest:no + + -libpath:"${ATLMFC_LIB}/${WINSDK_ARCH}" + -libpath:"${MSVC_LIB}/${WINSDK_ARCH}" + -libpath:"${WINSDK_LIB}/ucrt/${WINSDK_ARCH}" + -libpath:"${WINSDK_LIB}/um/${WINSDK_ARCH}") + +if(case_sensitive_filesystem) + # Ensure all sub-configures use the top-level symlinks dir instead of generating their own. + if(NOT winsdk_lib_symlinks_dir) + set(winsdk_lib_symlinks_dir "${CMAKE_BINARY_DIR}/winsdk_lib_symlinks") + generate_winsdk_lib_symlinks("${WINSDK_LIB}/um/${WINSDK_ARCH}" "${winsdk_lib_symlinks_dir}") + endif() + list(APPEND LINK_FLAGS + -libpath:"${winsdk_lib_symlinks_dir}") + if(NOT msvc_lib_symlinks_dir) + set(msvc_lib_symlinks_dir "${CMAKE_BINARY_DIR}/msvc_lib_symlinks") + generate_msvc_lib_symlinks("${MSVC_LIB}/${WINSDK_ARCH}" "${msvc_lib_symlinks_dir}") + endif() + list(APPEND LINK_FLAGS + -libpath:"${msvc_lib_symlinks_dir}") +endif() + +if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.25") + list(TRANSFORM LINK_FLAGS PREPEND "${CMAKE_CXX_LINKER_WRAPPER_FLAG}") +endif() + +string(REPLACE ";" " " LINK_FLAGS "${LINK_FLAGS}") +string(APPEND CMAKE_EXE_LINKER_FLAGS_INIT " ${LINK_FLAGS}") +string(APPEND CMAKE_MODULE_LINKER_FLAGS_INIT " ${LINK_FLAGS}") +string(APPEND CMAKE_SHARED_LINKER_FLAGS_INIT " ${LINK_FLAGS}") + +# CMake populates these with a bunch of unnecessary libraries, which requires +# extra case-correcting symlinks and what not. Instead, let projects explicitly +# control which libraries they require. +set(CMAKE_C_STANDARD_LIBRARIES "" CACHE STRING "" FORCE) +set(CMAKE_CXX_STANDARD_LIBRARIES "" CACHE STRING "" FORCE) + +# Allow clang-cl to work with macOS paths. +set(CMAKE_USER_MAKE_RULES_OVERRIDE "${CMAKE_CURRENT_LIST_DIR}/ClangClCMakeCompileRules.cmake") diff --git a/cmake/templates/Doxyfile.in b/cmake/templates/Doxyfile.in index 8e493da3..80005ef5 100644 --- a/cmake/templates/Doxyfile.in +++ b/cmake/templates/Doxyfile.in @@ -197,7 +197,7 @@ TAB_SIZE = 2 # will result in a user-defined paragraph with heading "Side Effects:". # You can put \n's in the value part of an alias to insert newlines. -ALIASES = +ALIASES += fixme="\todo" # This tag can be used to specify a number of word-keyword mappings (TCL only). # A mapping has the form "name=value". For example adding @@ -654,7 +654,7 @@ WARN_LOGFILE = # directories like "/usr/src/myproject". Separate the files or directories # with spaces. -INPUT = @PROJECT_SOURCE_DIR@/include/nomlib @PROJECT_SOURCE_DIR@/src @PROJECT_SOURCE_DIR@/README.md @PROJECT_SOURCE_DIR@/LICENSE.md @PROJECT_SOURCE_DIR@/third-party/README.md +INPUT = @PROJECT_SOURCE_DIR@/include/nomlib @PROJECT_SOURCE_DIR@/README.md @PROJECT_SOURCE_DIR@/LICENSE.md @PROJECT_SOURCE_DIR@/third-party/README.md # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is @@ -714,7 +714,7 @@ EXCLUDE_SYMBOLS = # directories that contain example code fragments that are included (see # the \include command). -EXAMPLE_PATH = +EXAMPLE_PATH = @PROJECT_SOURCE_DIR@ # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp @@ -892,13 +892,13 @@ HTML_FILE_EXTENSION = .html # have to redo this when upgrading to a newer version of doxygen or when # changing the value of configuration settings such as GENERATE_TREEVIEW! -HTML_HEADER = @PROJECT_DOXYGEN_DIR@/header.html +# HTML_HEADER = @PROJECT_DOXYGEN_DIR@/header.html # The HTML_FOOTER tag can be used to specify a personal HTML footer for # each generated HTML page. If it is left blank doxygen will generate a # standard footer. -HTML_FOOTER = @PROJECT_DOXYGEN_DIR@/footer.html +# HTML_FOOTER = @PROJECT_DOXYGEN_DIR@/footer.html # The HTML_STYLESHEET tag can be used to specify a user-defined cascading # style sheet that is used by each HTML page. It can be used to @@ -907,7 +907,7 @@ HTML_FOOTER = @PROJECT_DOXYGEN_DIR@/footer.html # HTML_EXTRA_STYLESHEET instead of this one, as it is more robust and this # tag will in the future become obsolete. -HTML_STYLESHEET = @PROJECT_DOXYGEN_DIR@/doxygen.css +# HTML_STYLESHEET = @PROJECT_DOXYGEN_DIR@/doxygen.css # The HTML_EXTRA_STYLESHEET tag can be used to specify an additional # user-defined cascading style sheet that is included after the standard @@ -1227,7 +1227,7 @@ MATHJAX_CODEFILE = # typically be disabled. For large projects the javascript based search engine # can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution. -SEARCHENGINE = NO +SEARCHENGINE = YES # When the SERVER_BASED_SEARCH tag is enabled the search engine will be # implemented using a web server instead of a web client using Javascript. @@ -1471,17 +1471,19 @@ GENERATE_XML = NO XML_OUTPUT = xml +# DEPRECATED # The XML_SCHEMA tag can be used to specify an XML schema, # which can be used by a validating XML parser to check the # syntax of the XML files. -XML_SCHEMA = +# XML_SCHEMA = +# DEPRECATED # The XML_DTD tag can be used to specify an XML DTD, # which can be used by a validating XML parser to check the # syntax of the XML files. -XML_DTD = +# XML_DTD = # If the XML_PROGRAMLISTING tag is set to YES Doxygen will # dump the program listings (including syntax highlighting @@ -1705,7 +1707,7 @@ HAVE_DOT = YES # explicitly to a value larger than 0 to get control over the balance # between CPU load and processing speed. -DOT_NUM_THREADS = 0 +DOT_NUM_THREADS = 1 # By default doxygen will use the Helvetica font for all dot files that # doxygen generates. When you want a differently looking font you can specify @@ -1739,7 +1741,7 @@ CLASS_GRAPH = YES # indirect implementation dependencies (inheritance, containment, and # class references variables) of the class with other documented classes. -COLLABORATION_GRAPH = YES +COLLABORATION_GRAPH = NO # If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen # will generate a graph for groups, showing the direct groups dependencies diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 1a633bf0..1c88fc05 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -24,23 +24,43 @@ set( NOM_BUILD_APP_EXAMPLE ON ) set( NOM_BUILD_AUDIO_EXAMPLE ON ) set( NOM_BUILD_DEVICE_INFO_EXAMPLE ON ) set( NOM_BUILD_EVENTS_EXAMPLE ON ) +set( NOM_BUILD_JOYSTICK_EXAMPLE ON ) +set( NOM_BUILD_GAME_CONTROLLER_EXAMPLE ON ) set( NOM_BUILD_FONTS_EXAMPLE ON ) -set( NOM_BUILD_GAMEOFLIFE_EXAMPLE ON ) set( NOM_BUILD_MOUSE_CURSORS_EXAMPLE ON ) set( NOM_BUILD_MACROS_EXAMPLE ON ) -set( NOM_BUILD_SPRITES_EXAMPLE ON ) -# Used only by the MSVCPP generator; this path will be expanded to include the -# configuration build type, i.e.: Debug or Release, so the full path resolve -# to '/examples/Debug' or '/examples/Release'. -set( CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/examples" ) +if( EXISTS "${CMAKE_CURRENT_LIST_DIR}/local_env.cmake" ) + include( "${CMAKE_CURRENT_LIST_DIR}/local_env.cmake" ) +endif() -# Used by all other generators (Xcode, Unix Makefiles); this path will resolve -# to '/examples/Debug' or '/examples/Release'. -set( CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}/examples/Debug" ) -set( CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/examples/Release" ) - -set( EXAMPLES_INSTALL_DIR "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${CMAKE_BUILD_TYPE}" ) +# Use common build output directories for MSVCPP && Xcode project files. +# +# IMPORTANT: Debug and Release build targets **must** be kept in separate build +# trees! +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}/examples/Debug") +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/examples/Release") + +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}/examples/Debug") +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/examples/Release") + +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}/examples/Debug") +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/examples/Release") + +if(DEBUG) + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_LIBRARY_OUTPUT_DIRECTORY_DEBUG}) + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG}) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG}) +else() # Release builds + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELEASE}) + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE}) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE}) +endif() + +# This path is used for the local installation of required dependencies for +# running examples, such as dependent resource files. Additionally, when +# building on Windows, this will be the path that dependent DLLs are copied to. +set( EXAMPLES_INSTALL_DIR "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}" ) # Project headers files inclusions; our header files namespace, 'nomlib' include_directories( "${INC_ROOT_DIR}" ) @@ -57,15 +77,19 @@ if( SDL2_FOUND ) endif( SDL2_FOUND ) # nomlib-audio external deps -if( PLATFORM_WINDOWS ) +if ( NOM_BUILD_AUDIO_UNIT ) + # nomlib-audio external deps; OpenAL-Soft or Apple's OpenAL find_package( OpenAL REQUIRED ) if( OPENAL_FOUND ) include_directories( ${OPENAL_INCLUDE_DIR} ) - endif( OPENAL_FOUND ) -endif( PLATFORM_WINDOWS ) + message ( STATUS "Found OpenAL headers: ${OPENAL_INCLUDE_DIR}." ) + else ( NOT OPENAL_FOUND ) + message ( FATAL_ERROR "Missing OpenAL headers at ${OPENAL_INCLUDE_DIR}." ) + endif ( OPENAL_FOUND ) +endif() # nomlib-gui external deps -find_package( libRocket REQUIRED ) +find_package( LibRocket REQUIRED ) if( LIBROCKET_FOUND ) # Add development header files include_directories( ${LIBROCKET_INCLUDE_DIRS} ) @@ -82,6 +106,9 @@ if( NOT EXISTS ${TCLAP_INCLUDE_DIR} ) ) else( EXISTS ${TCLAP_INCLUDE_DIR} ) message( STATUS "TCLAP headers found: ${TCLAP_INCLUDE_DIR}" ) + + # Header file inclusion; third-party -- TCLAP + include_directories("${NOM_THIRD_PARTY_COMMON_DIR}") endif( NOT EXISTS ${TCLAP_INCLUDE_DIR} ) if( PLATFORM_WINDOWS ) @@ -98,7 +125,7 @@ endif ( PLATFORM_WINDOWS ) if( NOM_BUILD_APP_EXAMPLE ) add_executable ( app "${EXECUTABLE_FLAGS}" "app/app.cpp" ) - set( APP_DEPS nomlib-gui ) + set( APP_DEPS nomlib-gui nomlib-graphics nomlib-actions ) if( PLATFORM_WINDOWS ) # We need to link to SDL2main library on Windows @@ -116,72 +143,82 @@ if( NOM_BUILD_APP_EXAMPLE ) ) endif( NOM_BUILD_APP_EXAMPLE ) -if( NOM_BUILD_SPRITES_EXAMPLE ) - add_executable ( sprites "${EXECUTABLE_FLAGS}" "sprites.cpp" ) +if(NOM_BUILD_AUDIO_EXAMPLE) + set(EX_SRC "audio/audio.cpp") + add_executable(audio "${EXECUTABLE_FLAGS}" ${EX_SRC}) - set( SPRITES_DEPS nomlib-graphics ) + set(AUDIO_DEPS nomlib-math nomlib-system nomlib-audio nomlib-actions + nomlib-serializers) - if( PLATFORM_WINDOWS ) + if(PLATFORM_WINDOWS) # We need to link to SDL2main library on Windows - list( APPEND SPRITES_DEPS ${SDL2MAIN_LIBRARY} ) - endif( PLATFORM_WINDOWS ) + list(APPEND AUDIO_DEPS ${SDL2MAIN_LIBRARY}) + endif(PLATFORM_WINDOWS) - target_link_libraries( sprites ${SPRITES_DEPS} ) + target_link_libraries(audio ${AUDIO_DEPS}) - # Search paths file to use in finding the resources path - install ( - FILES - "${NOM_EXAMPLES_RESOURCES_DIR}/sprites.json" - DESTINATION - "${EXAMPLES_INSTALL_DIR}" - ) -endif( NOM_BUILD_SPRITES_EXAMPLE ) + # Copy resource files for the example + install_resource_file("${NOM_EXAMPLES_RESOURCES_DIR}/audio.json" + "${EXAMPLES_INSTALL_DIR}") + + endif(NOM_BUILD_AUDIO_EXAMPLE) -if( NOM_BUILD_AUDIO_EXAMPLE ) - add_executable ( audio "${EXECUTABLE_FLAGS}" "audio/audio.cpp" ) +if(NOM_BUILD_EVENTS_EXAMPLE) + set(APP_NAME events) + set(EX_SRC "${APP_NAME}.cpp") + add_executable(${APP_NAME} "${EXECUTABLE_FLAGS}" ${EX_SRC}) + + set(APP_DEPS nomlib-system nomlib-graphics) + + if(PLATFORM_WINDOWS) + # We need to link to SDL2main library on Windows + list(APPEND APP_DEPS ${SDL2MAIN_LIBRARY}) + endif(PLATFORM_WINDOWS) - set( AUDIO_DEPS nomlib-system nomlib-audio ) + target_link_libraries(${APP_NAME} ${APP_DEPS}) +endif(NOM_BUILD_EVENTS_EXAMPLE) + +if( NOM_BUILD_JOYSTICK_EXAMPLE ) + + set( APP_NAME "joystick_events" ) + add_executable( ${APP_NAME} "${EXECUTABLE_FLAGS}" "${APP_NAME}.cpp" ) + + set( APP_DEPS nomlib-system nomlib-graphics ) if( PLATFORM_WINDOWS ) # We need to link to SDL2main library on Windows - list( APPEND AUDIO_DEPS ${SDL2MAIN_LIBRARY} ) + list( APPEND APP_DEPS ${SDL2MAIN_LIBRARY} ) endif( PLATFORM_WINDOWS ) - target_link_libraries( audio ${AUDIO_DEPS} ) + target_link_libraries( ${APP_NAME} ${APP_DEPS} ) +endif( NOM_BUILD_JOYSTICK_EXAMPLE ) - install ( DIRECTORY - "${EXAMPLES_SRC_DIR}/audio/Resources" - DESTINATION - "${EXAMPLES_INSTALL_DIR}" - PATTERN ".*" EXCLUDE - ) - endif( NOM_BUILD_AUDIO_EXAMPLE ) +if( NOM_BUILD_GAME_CONTROLLER_EXAMPLE ) -if( NOM_BUILD_EVENTS_EXAMPLE ) - add_executable ( events "${EXECUTABLE_FLAGS}" "events.cpp" ) + set( APP_NAME "gamecontroller_events" ) + add_executable( ${APP_NAME} "${EXECUTABLE_FLAGS}" "${APP_NAME}.cpp" ) - set( EVENTS_DEPS nomlib-graphics ) + set( APP_DEPS nomlib-system nomlib-graphics ) if( PLATFORM_WINDOWS ) # We need to link to SDL2main library on Windows - list( APPEND EVENTS_DEPS ${SDL2MAIN_LIBRARY} ) + list( APPEND APP_DEPS ${SDL2MAIN_LIBRARY} ) endif( PLATFORM_WINDOWS ) - target_link_libraries( events ${EVENTS_DEPS} ) + target_link_libraries( ${APP_NAME} ${APP_DEPS} ) - install ( DIRECTORY - "${EXAMPLES_SRC_DIR}/events/Resources" - DESTINATION - "${EXAMPLES_INSTALL_DIR}" - PATTERN ".*" EXCLUDE - ) -endif( NOM_BUILD_EVENTS_EXAMPLE ) + install( FILES "${NOMLIB_SHARED_SUPPORT_DIR}/InputDevices.json" + DESTINATION "${EXAMPLES_INSTALL_DIR}" ) +endif( NOM_BUILD_GAME_CONTROLLER_EXAMPLE ) if( NOM_BUILD_DEVICE_INFO_EXAMPLE ) add_executable ( device_info "${EXECUTABLE_FLAGS}" "device_info.cpp" ) - - set( DEVICE_INFO_DEPS nomlib-graphics nomlib-audio ) + if ( NOM_BUILD_AUDIO_UNIT ) + set( DEVICE_INFO_DEPS nomlib-graphics nomlib-audio ) + elseif ( NOT NOM_BUILD_AUDIO_UNIT ) + set( DEVICE_INFO_DEPS nomlib-graphics ) + endif ( NOM_BUILD_AUDIO_UNIT ) # Additional third-party dependency requirements find_package( SDL2_image REQUIRED ) @@ -205,7 +242,7 @@ if( NOM_BUILD_DEVICE_INFO_EXAMPLE ) list( APPEND DEVICE_INFO_DEPS ${OPENGL_LIBRARY} ) endif( OPENGL_FOUND ) - find_package( libRocket REQUIRED ) + find_package( LibRocket REQUIRED ) if( LIBROCKET_FOUND ) # Add development header files include_directories( ${LIBROCKET_INCLUDE_DIRS} ) @@ -231,33 +268,10 @@ if( NOM_BUILD_FONTS_EXAMPLE ) list( APPEND FONTS_DEPS ${SDL2MAIN_LIBRARY} ) endif( PLATFORM_WINDOWS ) - # Header file inclusion; third-party -- TCLAP - include_directories("${NOM_THIRD_PARTY_COMMON_DIR}") - target_link_libraries( renderfont ${FONTS_DEPS} ) endif( NOM_BUILD_FONTS_EXAMPLE ) -if( NOM_BUILD_GAMEOFLIFE_EXAMPLE ) - add_executable ( GameOfLife "${EXECUTABLE_FLAGS}" "GameOfLife/GameOfLife.cpp" ) - - set( GAMEOFLIFE_DEPS nomlib-graphics ) - - if( PLATFORM_WINDOWS ) - # We need to link to SDL2main library on Windows - list( APPEND GAMEOFLIFE_DEPS ${SDL2MAIN_LIBRARY} ) - endif( PLATFORM_WINDOWS ) - - target_link_libraries( GameOfLife ${GAMEOFLIFE_DEPS} ) - - install ( DIRECTORY - "${EXAMPLES_SRC_DIR}/GameOfLife/Resources" - DESTINATION - "${EXAMPLES_INSTALL_DIR}" - PATTERN ".*" EXCLUDE - ) -endif( NOM_BUILD_GAMEOFLIFE_EXAMPLE ) - if( NOM_BUILD_MOUSE_CURSORS_EXAMPLE ) add_executable( cursors "${EXECUTABLE_FLAGS}" "cursors.cpp" ) @@ -269,6 +283,9 @@ if( NOM_BUILD_MOUSE_CURSORS_EXAMPLE ) endif( PLATFORM_WINDOWS ) target_link_libraries( cursors ${CURSORS_DEPS} ) + + install( FILES "${NOMLIB_RESOURCES_DIR}/icon.png" + DESTINATION "${EXAMPLES_INSTALL_DIR}/Resources" ) endif( NOM_BUILD_MOUSE_CURSORS_EXAMPLE ) if( NOM_BUILD_MACROS_EXAMPLE ) @@ -303,3 +320,11 @@ if( PLATFORM_WINDOWS ) ) endif ( PLATFORM_WINDOWS ) + +if ( PLATFORM_LINUX ) + install_resource_dir( "${NOM_EXAMPLES_RESOURCES_DIR}" + "${EXAMPLES_INSTALL_DIR}/Resources") + # FIXME(JEFF): The app example requires the tests resources path + install_resource_dir( "${NOM_TESTS_RESOURCES_DIR}" + "${EXAMPLES_INSTALL_DIR}/Resources") +endif ( PLATFORM_LINUX ) diff --git a/examples/GameOfLife/GameOfLife.cpp b/examples/GameOfLife/GameOfLife.cpp deleted file mode 100644 index d109c4eb..00000000 --- a/examples/GameOfLife/GameOfLife.cpp +++ /dev/null @@ -1,401 +0,0 @@ -/****************************************************************************** - - nomlib - C++11 cross-platform game engine - -Copyright (c) 2013, 2014 Jeffrey Carpenter -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -******************************************************************************/ - -// -// GameOfLife -// -// Created by Fielding Johnston on 3/11/13. -// -// https://github.com/justfielding/GameOfLife -// -// - -#include -#include -#include // FIXME - -// Pubic nomlib interface headers -#include -#include -#include - -#define GAMEOFLIFE_DEBUG 0 - -/// File path name of the resources directory; this must be a relative file path. -const std::string APP_RESOURCES_DIR = "Resources"; - -/// \brief Relative file path name of our resource example -const nom::Path p; -const std::string RESOURCE_ICON = APP_RESOURCES_DIR + p.native() + "icon.png"; - -/// \brief Relative filename path to saved screen shot example -/// -/// Default path should resolve to the same directory as the app example -/// executable -const std::string OUTPUT_SCREENSHOT_FILENAME = "screenshot.png"; - -/// \brief Name of our application. -const std::string APP_NAME = "GameOfLife"; - -/// \brief Length and width for the square viewing area -/// -/// \remarks WINDOW_WIDTH & WINDOW_HEIGHT -const nom::int32 BOARD_SIZE = 800; - -/// \brief length and width (in pixels) for the square cells -const nom::sint CELL_SIZE = 16; - -/// \brief Maximum number of active windows we will attempt to spawn in this -/// example. -const nom::int32 MAXIMUM_WINDOWS = 1; - -const std::string IMG_CREEP = APP_RESOURCES_DIR + p.native() + "creep.png"; -const std::string BG_LIGHT = APP_RESOURCES_DIR + p.native() + "bglight.png"; -const std::string BG_DARK = APP_RESOURCES_DIR + p.native() + "bgdark.png"; - -/// \brief Usage example -class App: public nom::SDLApp -{ - public: - App( nom::int32 argc, char* argv[] ) - { - NOM_LOG_TRACE( NOM ); - - // Fatal error; if we are not able to complete this step, it means that - // we probably cannot rely on our resource paths! - if( nom::init( argc, argv ) == false ) - { - nom::DialogMessageBox( APP_NAME, "ERROR: Could not initialize nomlib." ); - exit( NOM_EXIT_FAILURE ); - } - - atexit( nom::quit ); - } // end App - - ~App( void ) - { - NOM_LOG_TRACE( NOM ); - } // end ~App - - bool on_init( void ) - { - nom::uint32 window_flags = SDL_WINDOW_RESIZABLE; - - for( auto idx = 0; idx < MAXIMUM_WINDOWS; ++idx ) - { - if ( this->window[idx].create( APP_NAME, BOARD_SIZE, BOARD_SIZE, window_flags ) == false ) - { - return false; - } - - if( this->window[idx].set_window_icon( RESOURCE_ICON ) == false ) - { - nom::DialogMessageBox( APP_NAME, "ERROR: Could not load window icon: " + RESOURCE_ICON ); - return false; - } - - // Scale window contents up by the new width & height - this->window[idx].set_logical_size( this->window[idx].size() ); - } - - if( this->creep.load( IMG_CREEP ) == false ) - { - nom::DialogMessageBox( APP_NAME, "ERROR: Could not load resource file: " + IMG_CREEP ); - return false; - } - - if( this->bg_light.load( BG_LIGHT ) == false ) - { - nom::DialogMessageBox( APP_NAME, "ERROR: Could not load resource file: " + BG_LIGHT ); - return false; - } - - if( this->bg_dark.load( BG_DARK ) == false ) - { - nom::DialogMessageBox( APP_NAME, "ERROR: Could not load resource file: " + BG_DARK ); - return false; - } - - this->spawn(); - - return true; - } // end on_init - - void on_update( float ) - { - // Rules: - // 1. Any live cell with fewer than two live neighbours dies, as if caused by under-population. - // 2. Any live cell with two or three live neighbours lives on to the next generation. - // 3. Any live cell with more than three live neighbours dies, as if by overcrowding. - // 4. Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction. - for ( int x = 0; x < BOARD_SIZE / CELL_SIZE; x++ ) - { - for ( int y = 0; y < BOARD_SIZE / CELL_SIZE; y++ ) - { - int neighbors = checkNeighbors( x, y ); - - if ( neighbors < 2 && grid[x][y] == 1 ) - { - if( GAMEOFLIFE_DEBUG ){ std::cout< 3 && grid[x][y] == 1) - { - if( GAMEOFLIFE_DEBUG ){ std::cout<creep.set_position( offset ); - this->creep.draw( target ); - } - else - { - if ( y % 2 != 0 && x % 2 == 0 ) - { - this->bg_dark.set_position( offset ); - this->bg_dark.draw( target ); - } - else - { - this->bg_light.set_position( offset ); - this->bg_light.draw( target ); - } - } - } - } - } - - nom::sint Run( void ) - { - for( auto idx = 0; idx < MAXIMUM_WINDOWS; ++idx ) - { - this->fps_update[idx].start(); - this->fps[idx].start(); - } - - // 1. Events - // 2. Logic - // 3. Render - while( this->running() == true ) - { - nom::Event event; - while( this->poll_event( event ) ) - { - this->on_event( event ); - } - - for( auto idx = 0; idx < MAXIMUM_WINDOWS; ++idx ) - { - this->window[idx].update(); - this->fps[idx].update(); - - // Refresh the frames per second at 1 second intervals - if ( this->fps_update[idx].ticks() > 1000 ) - { - if ( this->show_fps() == true ) - { - this->window[idx].set_window_title( APP_NAME + " - " + this->fps[idx].asString() + ' ' + "fps" ); - } - else - { - this->window[idx].set_window_title( APP_NAME + " [" + std::to_string(this->window[idx].window_id()) + "]" + " - " + "Display" + ' ' + std::to_string ( this->window[idx].window_display_id() ) ); - } - - this->fps_update[idx].restart(); - } // end refresh cycle - } // end for MAXIMUM_WINDOWS update loop - - this->on_update( nom::ticks() ); - - this->window[0].fill( nom::Color4i::Black ); - this->on_draw( this->window[0] ); - - nom::sleep( 500 ); // wait .5 seconds - - } // end while SDLApp::running() is true - - return NOM_EXIT_SUCCESS; - } // end Run() - - private: - /// \brief Event handler for key down actions - /// - /// \remarks Implements nom::Input::on_key_down - void on_key_down( const nom::Event& ev ) - { - switch( ev.key.sym ) - { - default: break; - - // Use inherited SDLApp::on_app_quit method -- you may also provide your - // own event handler for this. - case SDLK_ESCAPE: - case SDLK_q: this->on_app_quit( ev ); break; - - case SDLK_BACKSLASH: - { - if ( this->toggle_fps() ) - { - // Stub for doing something cool here - } - else - { - // Stub for doing something cool here - } - break; - } - - case SDLK_F1: - { - if( this->window[ev.key.window_id - 1].window_id() == ev.key.window_id ) - { - if( this->window[ev.key.window_id - 1].save_screenshot( OUTPUT_SCREENSHOT_FILENAME ) == false ) - { - nom::DialogMessageBox( APP_NAME, "ERROR: Could not save screen-shot"); - break; - } // end save_screenshot err check - } // end window_id check - break; - } - - // Toggle full-screen - case SDLK_f: - { - if ( this->window[ev.key.window_id - 1].window_id() == ev.key.window_id ) - { - this->window[ev.key.window_id - 1].toggle_fullscreen(); - } // end window_id match - break; - } // end SDLK_f - } // end switch key - } // end on_key_down - - void spawn() - { - // fill the array with random cells - int rando = 0; - srand( NOM_SCAST( nom::uint, time( nullptr ) ) ); // FIXME - - for ( int x = 0; x < BOARD_SIZE / CELL_SIZE; x++ ) - { - for ( int y = 0; y < BOARD_SIZE / CELL_SIZE; y++ ) - { - rando = rand() % 2; - grid[x][y] = rando; - if ( GAMEOFLIFE_DEBUG ){ std::cout<<"Setting square at "<= 0 && a <= ( BOARD_SIZE / CELL_SIZE - 1 ) && b >= 0 && b <= ( BOARD_SIZE / CELL_SIZE - 1 ) ) - { - ncount++; - } - } - } - - if ( grid[x][y] ) { ncount--; } // subtract self from neighbor count if currently living - if ( GAMEOFLIFE_DEBUG ){ std::cout< #include +#include #include +#include #include +using namespace nom; + /// \brief Name of our application. const std::string APP_NAME = "nomlib Demo | Multiple Windows"; @@ -46,6 +50,8 @@ const nom::int32 WINDOW_WIDTH = 768; /// \brief Height, in pixels, of our effective rendering surface. const nom::int32 WINDOW_HEIGHT = 448; +const auto WINDOW_RESOLUTION = Size2i(WINDOW_WIDTH/2, WINDOW_HEIGHT); + /// \brief Maximum number of active windows we will attempt to spawn in this example const nom::int32 MAXIMUM_WINDOWS = 3; @@ -68,14 +74,6 @@ const nom::Point2i INFO_BOX_ORIGINS[2] = { const std::string RESOURCE_ICON = "icon.png"; -// const std::string RESOURCE_TRUETYPE_FONT[2] = { -// "arial.ttf", -// "TimesNewRoman.ttf" -// }; - -// const std::string RESOURCE_BITMAP_FONT = "VIII.png"; -// const std::string RESOURCE_BITMAP_SMALL_FONT = "VIII_small.png"; - const std::string RESOURCE_SPRITE_SHEET = "cursors.json"; /// Copyright (c) 2013 Fielding Johnston. All rights reserved. @@ -108,10 +106,9 @@ class App: public nom::SDLApp // functionality in this example). The reasoning for disabling the feature // is solely to cut down on the amount of debug logging. SDLApp( OSX_DISABLE_MINIMIZE_ON_LOSS_FOCUS | OSX_DISABLE_FULLSCREEN_SPACES ), - sprite_angle ( -90.0f ), - // selected_font ( 0 ), // nom::TrueType font - selected_font_size ( 14 ), // Font's size (in pixels) - selected_text_string ( 2 ) // "Yeah Buddy!!!" + sprite_angle(-90.0f), + selected_font_size(nom::DEFAULT_FONT_SIZE), // Font's size (in pixels) + selected_text_string(2) // "Yeah Buddy!!!" { NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE, nom::LogPriority::NOM_LOG_PRIORITY_INFO ); } // App @@ -172,22 +169,28 @@ class App: public nom::SDLApp return false; } - for ( auto idx = 0; idx < MAXIMUM_WINDOWS; idx++ ) - { - if ( this->window[idx].create( APP_NAME, WINDOW_WIDTH/2, WINDOW_HEIGHT, window_flags, render_driver, render_flags ) == false ) + auto num_video_displays = + RenderWindow::num_video_displays(); + nom::size_type display_index = 0; + for( auto idx = 0; idx != MAXIMUM_WINDOWS; ++idx ) { + + if( this->window[idx].create( APP_NAME, + RenderWindow::WINDOW_POS_CENTERED, display_index, WINDOW_RESOLUTION, + window_flags, render_driver, render_flags) == false ) { return false; } - this->window[idx].set_position ( 0+(WINDOW_WIDTH/2) * idx, WINDOW_HEIGHT/2 ); - - if( this->window[idx].set_window_icon( res.path() + RESOURCE_ICON ) == false ) - { + if( this->window[idx].set_window_icon( res.path() + RESOURCE_ICON ) == false ) { nom::DialogMessageBox( APP_NAME, "Could not load window icon: " + res.path() + RESOURCE_ICON ); return false; } + + if( display_index < num_video_displays ) { + ++display_index; + } } if( nom::RocketSDL2RenderInterface::gl_init( this->window[0].size().w, this->window[0].size().h ) == false ) @@ -205,6 +208,8 @@ class App: public nom::SDLApp // Use no pixel unit scaling; this gives us one to one pixel ratio this->window[0].set_scale( nom::Point2f(1,1) ); + SDLApp::set_event_handler(this->evt_handler); + // Initialize the core of libRocket; these are the core dependencies that // libRocket depends on for successful initialization. Rocket::Core::FileInterface* fs = @@ -231,6 +236,8 @@ class App: public nom::SDLApp return false; } + this->desktop.set_event_handler(this->evt_handler); + if( this->desktop.load_font( "Delicious-Bold.otf" ) == false ) { NOM_LOG_CRIT( NOM_LOG_CATEGORY_APPLICATION, "Could not load font file: Delicious-Bold.otf" ); @@ -256,25 +263,6 @@ class App: public nom::SDLApp this->window[0].make_current(); - // if ( this->bitmap_font.load ( RESOURCE_BITMAP_FONT ) == false ) - // { - // nom::DialogMessageBox ( APP_NAME, "Could not load BitmapFont: " + RESOURCE_BITMAP_FONT ); - // return false; - // } - // FIXME: this->bitmap_font.resize ( nom::Texture::ResizeAlgorithm::scale2x ); - - // if ( this->bitmap_small_font.load ( RESOURCE_BITMAP_SMALL_FONT ) == false ) - // { - // nom::DialogMessageBox ( APP_NAME, "Could not load BitmapFont: " + RESOURCE_BITMAP_SMALL_FONT ); - // return false; - // } - - // if ( this->truetype_font.load ( RESOURCE_TRUETYPE_FONT[0] ) == false ) - // { - // nom::DialogMessageBox ( APP_NAME, "Could not load TrueTypeFont: " + RESOURCE_TRUETYPE_FONT[0] ); - // return false; - // } - nom::SpriteSheet sprite_frames; // Load a sprite sheet, using the sheet_filename as the base path to load @@ -299,11 +287,15 @@ class App: public nom::SDLApp this->sprite_tex.resize(nom::Texture::ResizeAlgorithm::scale2x); this->sprite.set_frame(1); // Left-pointing cursor hand + auto ani_sprite_tex = + std::make_shared(); + NOM_ASSERT(ani_sprite_tex != nullptr); + // Sharing the same texture for the animated sprite instead of loading // another texture source would be OK, too, if we didn't care about // preserving the original scale of the sprite here for testing purposes. // this->ani_sprite.set_texture( *this->sprite_tex.clone() ); - if( this->ani_sprite_tex.load( res.path() + sprite_frames.sheet_filename() ) == false ) + if( ani_sprite_tex->load( res.path() + sprite_frames.sheet_filename() ) == false ) { nom::DialogMessageBox( APP_NAME, "Could not load sprite texture: " + @@ -311,15 +303,31 @@ class App: public nom::SDLApp return false; } - this->ani_sprite.set_texture(this->ani_sprite_tex); + this->ani_sprite = + std::make_shared(); + NOM_ASSERT(this->ani_sprite != nullptr); + this->ani_sprite->set_texture(ani_sprite_tex); + // Use the same sprite sheet source for the animated sprite - this->ani_sprite.set_sprite_sheet(sprite_frames); + this->ani_sprite->set_sprite_sheet(sprite_frames); + this->ani_sprite->set_frame(0); + + // 100ms blink (~10 fps) + auto sprite_action = + nom::create_action(this->ani_sprite, 0.100f); + NOM_ASSERT(sprite_action != nullptr); + + auto blinking_sprite_action = + nom::create_action(sprite_action); + NOM_ASSERT(blinking_sprite_action != nullptr); + blinking_sprite_action->set_name("blinking_cursor"); + + this->actions.run_action(blinking_sprite_action); if ( MAXIMUM_WINDOWS > 1 ) { this->window[1].make_current(); - if ( this->background.load ( res.path() + RESOURCE_STATIC_IMAGE, 0 ) == false ) - { + if( this->background.load(res.path() + RESOURCE_STATIC_IMAGE) == false ) { nom::DialogMessageBox( APP_NAME, "Could not load image file: " + res.path() + RESOURCE_STATIC_IMAGE ); @@ -360,7 +368,7 @@ class App: public nom::SDLApp this->info_box[1].show(); this->sprite.set_position( nom::Point2i(this->info_box[0].position().x - this->sprite.size().w, this->info_box[0].position().y) ); - this->ani_sprite.set_position( nom::Point2i(this->info_box[0].position().x + this->info_box[0].size().w + this->sprite.size().w, this->info_box[0].position().y) ); + this->ani_sprite->set_position( nom::Point2i(this->info_box[0].position().x + this->info_box[0].size().w + this->sprite.size().w, this->info_box[0].position().y) ); return true; } // onInit @@ -377,34 +385,35 @@ class App: public nom::SDLApp // 2. Logic // 3. Render - nom::Event ev; - while ( this->running() == true ) - { - while( this->poll_event( ev ) ) - { - this->on_event( ev ); - this->desktop.process_event(ev); + while( this->running() == true ) { + + nom::Event evt; + while( this->evt_handler.poll_event(evt) == true ) { + // NOTE: Pending events will be handled by the event listeners that + // were given an EventHandler object via ::set_event_handler. } - this->ani_sprite.play(); + for( auto idx = 0; idx < MAXIMUM_WINDOWS; idx++ ) { - for ( auto idx = 0; idx < MAXIMUM_WINDOWS; idx++ ) - { this->window[idx].update(); this->desktop.update(); + this->actions.update(0); this->fps[idx].update(); // Refresh the frames per second at 1 second intervals - if ( this->update[idx].ticks() > 1000 ) - { - if ( this->show_fps() == true ) - { - this->window[idx].set_window_title ( APP_NAME + " - " + this->fps[idx].asString() + ' ' + "fps" ); - } - else - { - this->window[idx].set_window_title ( APP_NAME + " [" + std::to_string(this->window[idx].window_id()) + "]" + " - " + "Display" + ' ' + std::to_string ( this->window[idx].window_display_id() ) ); - } + if( this->update[idx].ticks() > 1000 ) { + + auto fps_str = this->fps[idx].asString(); + std::stringstream window_str; + auto window_id_str = + std::to_string( this->window[idx].window_id() ); + auto window_display_id = + std::to_string( this->window[idx].window_display_id() ); + + window_str << "WID: " << window_id_str << " - Display ID: " + << window_display_id << " - " << fps_str << " fps"; + + this->window[idx].set_window_title( window_str.str() ); this->update[idx].restart(); } // end refresh cycle @@ -417,7 +426,9 @@ class App: public nom::SDLApp this->window[0].fill ( nom::Color4i::SkyBlue ); this->desktop.draw(); this->sprite.draw ( this->window[0], this->sprite_angle ); - this->ani_sprite.draw ( this->window[0] ); + if( this->ani_sprite != nullptr && this->ani_sprite->valid() == true ) { + this->ani_sprite->draw(this->window[0]); + } if ( MAXIMUM_WINDOWS > 1 ) { @@ -478,12 +489,28 @@ class App: public nom::SDLApp } } - /// \brief Event handler for key down actions. - /// - /// Implements the nom::Input::on_key_down method. - void on_key_down( const nom::Event& ev ) + /// \brief The default event handler for input events. + void on_input_event(const nom::Event& ev) override + { + switch(ev.type) + { + default: break; + + case nom::Event::KEY_PRESS: + { + this->on_key_down(ev); + } break; + + case nom::Event::MOUSE_WHEEL: + { + this->on_mouse_wheel(ev); + } break; + } // end switch key + } // onKeyDown + + void on_key_down(const nom::Event& ev) { - switch ( ev.key.sym ) + switch(ev.key.sym) { default: break; @@ -636,21 +663,17 @@ class App: public nom::SDLApp } // end window_id match break; } // end SDLK_f - } // end switch key - } // onKeyDown + } + } - void on_mouse_wheel( const nom::Event& ev ) + void on_mouse_wheel(const nom::Event& ev) { - // Filter out non-wheel events (otherwise we can receive false positives) - if( ev.type != SDL_MOUSEWHEEL ) return; - - if( ev.wheel.y > 0 ) // Up - { - this->increase_font_size( 1 ); - } - else if( ev.wheel.y < 0 ) // Down - { - this->decrease_font_size( 1 ); + if( ev.wheel.y > 0 ) { + // Up + this->increase_font_size(1); + } else if( ev.wheel.y < 0 ) { + // Down + this->decrease_font_size(1); } } @@ -662,6 +685,8 @@ class App: public nom::SDLApp nom::UIContext desktop; + nom::EventHandler evt_handler; + /// Interval at which we refresh the frames per second counter nom::Timer update[MAXIMUM_WINDOWS]; @@ -678,36 +703,16 @@ class App: public nom::SDLApp /// Our spiffy sprites nom::Texture sprite_tex; - nom::Texture ani_sprite_tex; nom::SpriteBatch sprite; double sprite_angle; - nom::AnimatedSprite ani_sprite; + std::shared_ptr ani_sprite; - // Our font resources for nom::Text, the text rendering API - // nom::Font bitmap_font; - // nom::Font bitmap_small_font; - // nom::Font truetype_font; + // Animations queue + nom::ActionPlayer actions; - // int selected_font; int selected_font_size; nom::sint selected_text_string; - // nom::Font& select_font( void ) - // { - // if( this->selected_font == 0 ) - // { - // return this->truetype_font; - // } - // else if( this->selected_font == 1 ) - // { - // return this->bitmap_font; - // } - // else - // { - // return this->truetype_font; - // } - // } - nom::sint select_font_size ( void ) { return this->selected_font_size; @@ -748,7 +753,7 @@ nom::int32 main ( nom::int32 argc, char* argv[] ) atexit(nom::quit); // nom::SDL2Logger::set_logging_priority( NOM_LOG_CATEGORY_TRACE, nom::NOM_LOG_PRIORITY_INFO ); - nom::SDL2Logger::set_logging_priority( NOM_LOG_CATEGORY_GUI, nom::NOM_LOG_PRIORITY_INFO ); + // nom::SDL2Logger::set_logging_priority( NOM_LOG_CATEGORY_GUI, nom::NOM_LOG_PRIORITY_INFO ); App game ( argc, argv ); diff --git a/examples/audio/Resources/cursor_wrong.wav b/examples/audio/Resources/cursor_wrong.wav deleted file mode 100644 index 28c77b84..00000000 Binary files a/examples/audio/Resources/cursor_wrong.wav and /dev/null differ diff --git a/examples/audio/audio.cpp b/examples/audio/audio.cpp index fceebcbb..8b55c934 100644 --- a/examples/audio/audio.cpp +++ b/examples/audio/audio.cpp @@ -2,7 +2,7 @@ nomlib - C++11 cross-platform game engine -Copyright (c) 2013, 2014 Jeffrey Carpenter +Copyright (c) 2013, 2014, 2015, 2016 Jeffrey Carpenter All rights reserved. Redistribution and use in source and binary forms, with or without @@ -27,146 +27,364 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ -/// \brief Audio playback usage example - -#include -#include -#include -#include +// NOTE: Audio playback usage example #include +#include #include #include +#include +#include +#include "tclap/CmdLine.h" -/// Name of our application. -const std::string APP_NAME = "Audio Playback"; +#include -/// File path name of the resources directory; this must be a relative file path. -const std::string APP_RESOURCES_DIR = "Resources"; +using namespace nom; -/// Relative file path name of our resource example -const nom::Path p; +const std::string APP_NAME = "nomlib: audio"; -/// Sound effect resource file -const std::string RESOURCE_AUDIO_SOUND = APP_RESOURCES_DIR + p.native() + "cursor_wrong.wav"; +// File resource paths +SearchPath res; -int main ( int argc, char* argv[] ) +/// \remarks See program usage by passing --help +struct AppFlags { - nom::IAudioDevice* dev = nullptr; // this must be declared first - nom::IListener* listener = nullptr; // Global audio volume control - nom::ISoundBuffer* buffer = nullptr; - nom::Timer loops; + /// The input file source to play + std::string audio_input = "\0"; - // Fatal error; if we are not able to complete this step, it means that - // we probably cannot rely on our resource paths! - if ( nom::init ( argc, argv ) == false ) - { - nom::DialogMessageBox ( APP_NAME, "Could not initialize nomlib." ); - exit ( NOM_EXIT_FAILURE ); - } - atexit(nom::quit); + /// Test input source with the null audio back-end + bool use_null_interface = false; - // Quick and dirty method of testing the use of nomlib's audio subsystem - // #undef NOM_USE_OPENAL + /// Test input source with the music audio interface + /// + /// \fixme This interface is broken; the sound buffer memory is not properly + /// deallocated. + bool use_music_interface = false; - // Initialize audio subsystem... - #if defined( NOM_USE_OPENAL ) - dev = new nom::AudioDevice(); - listener = new nom::Listener(); - buffer = new nom::SoundBuffer(); - #else - dev = new nom::NullAudioDevice(); - listener = new nom::NullListener(); - buffer = new nom::NullSoundBuffer(); - #endif // defined NOM_USE_OPENAL + real32 audio_volume = 100.0f; +}; - NOM_DUMP( dev->getDeviceName() ); +int parse_cmdline(int argument_count, char* arguments[], AppFlags& opts) +{ + using namespace TCLAP; - listener->setVolume( 100.0f ); + if( argument_count < 0 ) { + return NOM_EXIT_FAILURE; + } - if ( argv[1] != nullptr ) + try { - if( buffer->load( argv[1] ) == false ) - { - NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, "Could not load audio file:", argv[1] ); - return NOM_EXIT_FAILURE; - } + CmdLine cmd( APP_NAME, ' ', nom::NOM_VERSION.version_string() ); + + std::string null_interface_desc = + "Test the usage of the null interface (defaults to FALSE)."; + std::string music_interface_desc = + "Test the usage of the music interface (defaults to FALSE)."; + + SwitchArg use_null_interface_arg("n", "use-null", null_interface_desc, + cmd, false); + SwitchArg use_music_interface_arg("", "use-music", music_interface_desc, + cmd, false); + + ValueArg audio_volume_arg("v", "volume", + "Gain level of audio playback", + false, opts.audio_volume, + "A number between 0.0f .. 100.0f", + cmd ); + ValueArg audio_file_arg("i", "input", + "File path to audio to play from", + false, opts.audio_input, + "Resources/audio/hello.wav", cmd ); + + cmd.parse(argument_count, arguments); + + opts.use_null_interface = use_null_interface_arg.getValue(); + opts.use_music_interface = use_music_interface_arg.getValue(); + opts.audio_input = audio_file_arg.getValue(); + opts.audio_volume = audio_volume_arg.getValue(); } - else + catch(TCLAP::ArgException &e) { - if( buffer->load( RESOURCE_AUDIO_SOUND ) == false ) - { - NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, "Could not load audio file: ", RESOURCE_AUDIO_SOUND ); - return NOM_EXIT_FAILURE; - } - } + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, + e.error(), "for arg", e.argId() ); - #if defined( NOM_USE_OPENAL ) - // nom::ISoundSource* snd = new nom::Sound(); - #else - // nom::ISoundSource* snd = new nom::NullSound(); - #endif // defined NOM_USE_OPENAL - - #if defined( NOM_USE_OPENAL ) - nom::ISoundSource* snd = new nom::Music(); - #else - nom::ISoundSource* snd = new nom::NullMusic(); - #endif // defined NOM_USE_OPENAL + return NOM_EXIT_FAILURE; + } - snd->setBuffer( *buffer ); + return NOM_EXIT_SUCCESS; +} - snd->setPitch ( 1.0 ); - snd->setVolume ( 100.0f ); - snd->setPosition ( nom::Point3f ( 0.0, 0.0, 0.0 ) ); - snd->setVelocity ( nom::Point3f ( 0.0, 0.0, 0.0 ) ); - snd->setLooping ( true ); +int main(int argc, char* argv[]) +{ + AppFlags args; + auto audio_thread = std::thread(); + Timer elapsed; + EventHandler evt_handler; + + audio::AudioSpec request = {}; + audio::IOAudioEngine* dev = nullptr; + audio::SoundBuffer* buffer = nullptr; + + const char* RES_FILENAME = "audio.json"; + real32 master_gain = 100.0f; + const real32 pitch = 1.0f; + const Point3f audio_pos = {0.0f, 0.0f, 0.0f}; + const Point3f audio_velocity = {0.0f, 0.0f, 0.0f}; + + // TODO(jeff): command-line switches for these variables +NOM_IGNORED_VARS(); + const auto ACTION_FADE_DISPLACEMENT = 100.0f; + const auto ACTION_DURATION = 1.0f; + const auto ACTION_TIMING_CURVE = + nom::make_timing_curve_from_string("linear_ease_in"); + const auto ACTION_SPEED = 1.0f; +NOM_IGNORED_VARS_ENDL(); + // TODO(jeff): command-line switches for these variables..? + nom::SDL2Logger::set_logging_priority(NOM_LOG_CATEGORY_AUDIO, + NOM_LOG_PRIORITY_DEBUG); + + nom::SDL2Logger::set_logging_priority(NOM_LOG_CATEGORY_TRACE_AUDIO, + NOM_LOG_PRIORITY_DEBUG); + + nom::SDL2Logger::set_logging_priority(NOM_LOG_CATEGORY_ACTION, + NOM_LOG_PRIORITY_DEBUG); +#if defined(NOM_DEBUG) + nom::SDL2Logger::set_logging_priority(NOM_LOG_CATEGORY_TEST, + NOM_LOG_PRIORITY_DEBUG); +#else + nom::SDL2Logger::set_logging_priority(NOM_LOG_CATEGORY_TEST, + NOM_LOG_PRIORITY_WARN); +#endif + + ActionPlayer audio_player; + + if(res.load_file(RES_FILENAME, "resources") == false) { + NOM_LOG_CRIT(NOM_LOG_CATEGORY_APPLICATION, + "Could not resolve the resources path from given input:", + RES_FILENAME); + exit(NOM_EXIT_FAILURE); + } - if ( snd->getStatus() != nom::SoundStatus::Playing ) snd->Play(); + if(parse_cmdline(argc, argv, args) != 0) { + exit(NOM_EXIT_FAILURE); + } - nom::uint32 duration = buffer->getDuration(); - float duration_seconds = duration / 1000.0f; - NOM_DUMP( duration_seconds ); + if(args.audio_input.length() < 1) { + args.audio_input = res.path() + "sinewave_1s-900.wav"; + } - loops.start(); + // Fatal error; if we are not able to complete this step, it means that + // we probably cannot rely on our resource paths! + if(nom::init(argc, argv) == false) { + NOM_LOG_CRIT(NOM_LOG_CATEGORY_APPLICATION, + "Could not initialize nomlib."); + exit(NOM_EXIT_FAILURE); + } + atexit(nom::quit); - //float step = 1.0; - // volume / seconds = step + // Quick and dirty method of testing the use of nomlib's audio subsystem + // #undef NOM_USE_OPENAL - //float step = snd->getVolume(); - //float step_by = step / 4; // 4s or 4000ms + // Initialize audio subsystem... + request.engine = "openal"; + // TEST CODE: REMOVE ME +#if 1 + request.sample_rate = 48000; + request.num_mono_sources = 32; + request.num_stereo_sources = 16; +#endif + if(args.use_null_interface == false) { + audio::AudioSpec spec = {}; + dev = audio::init_audio(&request, &spec); + if(dev == nullptr) { + NOM_LOG_CRIT(NOM_LOG_CATEGORY_APPLICATION, + "Failed to create audio device"); + exit(NOM_EXIT_FAILURE); + } + const char* audio_dev_name = spec.name; + if(audio_dev_name == nullptr) { + audio_dev_name = "Unknown device"; + } - float pos = snd->getPlayPosition(); + NOM_LOG_INFO(NOM_LOG_CATEGORY_APPLICATION, "Audio device name:", + audio_dev_name); + } - snd->fadeOut( 4 ); + master_gain = args.audio_volume; + audio::set_volume(master_gain, dev); - while ( ( loops.ticks() <= duration * 2 ) && ( snd->getStatus() != nom::SoundStatus::Paused && snd->getStatus() != nom::SoundStatus::Stopped ) ) - { - // 0.455*2/4 - // ( duration * total_loops ) / milliseconds*2 where seconds is desired fade - // out (over time) - // duration / milliseconds*2 where milliseconds is desired fade out - // ( over time) + buffer = audio::create_buffer(args.audio_input, dev); + if(audio::valid_buffer(buffer, dev) == false) { + NOM_LOG_ERR(NOM_LOG_CATEGORY_APPLICATION, + "Could not load audio samples from:", args.audio_input); + return NOM_EXIT_FAILURE; + } - if ( snd->getPlayPosition() >= 1.0 ) + // TODO(jeff): Implement per-buffer gain level passing via command line + // audio::set_volume(buffer, dev, args.audio_volume); + + audio::set_pitch(buffer, dev, pitch); + audio::set_position(buffer, dev, audio_pos); + audio::set_velocity(buffer, dev, audio_velocity); + // audio::set_state(buffer, dev, audio::AUDIO_STATE_LOOPING); +#if 1 + auto playback_action = + nom::create_action(dev, args.audio_input.c_str()); +#else + auto playback_action = + nom::create_action(dev, buffer, ACTION_FADE_DISPLACEMENT, + ACTION_DURATION); +#endif + // auto playback_action = + // nom::create_action(dev, args.audio_input.c_str(), + // ACTION_FADE_DISPLACEMENT, + // ACTION_DURATION); + playback_action->set_timing_curve(ACTION_TIMING_CURVE); + playback_action->set_speed(ACTION_SPEED); + playback_action->set_name("audio_playback"); + audio_player.run_action(playback_action); + +#if 0 + if(args.use_music_interface == true) { + // ... + } else if(args.use_music_interface == false) { + + audio_thread = + std::thread([=,&audio_player, &evt_handler, &elapsed, &buffer, &dev]() { + elapsed.start(); + audio::play(buffer, dev); +#if 1 + uint32 last_delta = elapsed.ticks(); + uint32 playback_state = audio::AUDIO_STATE_PLAYING; + while(playback_state != audio::AUDIO_STATE_STOPPED) { + playback_state = audio::state(buffer, dev); + + uint32 end_delta = elapsed.ticks(); + uint32 elapsed_delta = end_delta - last_delta; + last_delta = end_delta; + // NOM_DUMP(elapsed_delta); + + audio_player.update(elapsed_delta); + } +#else + // sound state should be set to audio::AUDIO_STATE_PLAYING after issuing + // the play command + uint32 playback_state = audio::state(buffer, dev); + while(elapsed.to_seconds() < 20.0f) { + + nom::Event evt; + while(evt_handler.poll_event(evt) == true) { + + switch(evt.type) { + default: break; + + case Event::QUIT_EVENT: { + NOM_DUMP_VAR(NOM, "goodbye!\n"); + audio::free_buffer(buffer, dev); + audio::shutdown_audio(dev); + exit(NOM_EXIT_SUCCESS); + } break; + } // end switch + } // end event polling loop + + auto dev_connected = dev->connected(); + if(dev_connected == 1) { + // NOM_DUMP_VAR(NOM, "audio connected"); + } else { + NOM_DUMP_VAR(NOM, "audio disconnected"); + break; + } + + playback_state = audio::state(buffer, dev); + if(playback_state == audio::AUDIO_STATE_STOPPED) { + // break; + } + } +#endif + NOM_LOG_INFO(NOM_LOG_CATEGORY_APPLICATION, + "Elapsed duration (seconds):", elapsed.to_seconds()); + }); + } +#endif + + audio::SoundInfo info = audio::info(buffer); + auto playback_pos = audio::playback_position(buffer, dev); + + NOM_LOG_INFO(NOM_LOG_CATEGORY_APPLICATION, + "Playback cursor (seconds):", playback_pos); + NOM_LOG_INFO(NOM_LOG_CATEGORY_APPLICATION, + "Duration (seconds):", info.duration); + NOM_LOG_INFO(NOM_LOG_CATEGORY_APPLICATION, + "Frame count:", info.frame_count); + NOM_LOG_INFO(NOM_LOG_CATEGORY_APPLICATION, + "Sample count:", info.sample_count); + NOM_LOG_INFO(NOM_LOG_CATEGORY_APPLICATION, + "Sample rate:", info.sample_rate); + NOM_LOG_INFO(NOM_LOG_CATEGORY_APPLICATION, + "Channel count:", info.channel_count); + NOM_LOG_INFO(NOM_LOG_CATEGORY_APPLICATION, + "Audio in bytes:", info.total_bytes); + NOM_LOG_INFO(NOM_LOG_CATEGORY_APPLICATION, + "Seekable:", info.seekable); +#if 0 + audio_thread.join(); +#endif + + elapsed.start(); + uint32 last_delta = elapsed.ticks(); + uint32 playback_state = audio::AUDIO_STATE_PLAYING; + + bool playback_eof = false; + while(playback_eof == false) { + playback_state = audio::state(buffer, dev); + + nom::Event evt; + while(evt_handler.poll_event(evt) == true) { + + switch(evt.type) { + default: break; + + case Event::KEY_PRESS: { + switch(evt.key.sym) { + default: { + } break; + + case SDLK_ESCAPE: + case SDLK_q: { + audio::stop(buffer, dev); + playback_eof = true; + } break; + } + } break; + + case Event::QUIT_EVENT: { + audio::stop(buffer, dev); + playback_eof = true; + } break; + } // end switch + } // end event polling loop + + uint32 end_delta = elapsed.ticks(); + uint32 elapsed_delta = end_delta - last_delta; + last_delta = end_delta; + audio_player.update(elapsed_delta); + + auto elapsed_seconds = elapsed.to_seconds(end_delta); + if(elapsed_seconds >= buffer->duration && + playback_state != audio::AUDIO_STATE_PLAYING) { - // ... + audio::stop(buffer, dev); + playback_eof = true; } - if ( snd->getPlayPosition() >= ( pos + 2.5 ) ) - { - // ... - } + nom::sleep(17); // Emulate 60 FPS } - loops.stop(); - NOM_DUMP( loops.ticks() ); - - //std::cout << "Sample Count: " << snd->getSampleCount() << std::endl; - //std::cout << "Channel Count: " << snd->getChannelCount() << std::endl; - //std::cout << "Sample Rate: " << snd->getSampleRate() << std::endl; - - NOM_DELETE_PTR( dev ); - NOM_DELETE_PTR( listener ); + NOM_LOG_INFO(NOM_LOG_CATEGORY_APPLICATION, + "Elapsed duration (seconds):", elapsed.to_seconds()); + // audio::free_buffer(buffer, dev); + playback_action->release(); + audio::shutdown_audio(dev); return NOM_EXIT_SUCCESS; } diff --git a/examples/cursors.cpp b/examples/cursors.cpp index 1a727fdb..6972fa2c 100644 --- a/examples/cursors.cpp +++ b/examples/cursors.cpp @@ -31,7 +31,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -// using namespace nom; +using namespace nom; const nom::Path p; @@ -47,6 +47,8 @@ const nom::int32 WINDOW_WIDTH = 640; /// \brief Height, in pixels, of our effective rendering surface. const nom::int32 WINDOW_HEIGHT = 480; +auto WINDOW_RESOLUTION = Size2i(WINDOW_WIDTH, WINDOW_HEIGHT); + const std::string RESOURCE_ICON = APP_RESOURCES_DIR + "icon.png"; /// \brief Relative filename path to saved screen shots. @@ -92,7 +94,7 @@ class App: public nom::SDLApp NOM_LOG_INFO ( NOM, "Could not disable vertical refresh." ); } - if( this->window.create( APP_NAME, WINDOW_WIDTH, WINDOW_HEIGHT, window_flags ) == false ) + if( this->window.create(APP_NAME, WINDOW_RESOLUTION, window_flags) == false ) { return false; } @@ -106,6 +108,8 @@ class App: public nom::SDLApp // Scale window contents up by the new width & height this->window.set_logical_size( this->window.size() ); + SDLApp::set_event_handler(this->evt_handler); + return true; } @@ -122,11 +126,14 @@ class App: public nom::SDLApp // 1. Events // 2. Logic // 3. Render - while ( this->running() == true ) - { - while( this->poll_event( this->event ) ) - { - this->on_event( this->event ); + while( this->running() == true ) { + + nom::Event evt; + while( this->evt_handler.poll_event(evt) == true ) { + // NOTE: Pending events will be handled by the event listeners that + // were given an EventHandler object via ::set_event_handler. + // + // Additional event processing done in here is still OK, too. } this->window.update(); @@ -172,17 +179,19 @@ class App: public nom::SDLApp } private: - /// \brief Event handler for key down actions. - /// - /// Implements the nom::Input::on_key_down method. - void on_key_down( const nom::Event& ev ) + /// \brief The default event handler for input events. + void on_input_event(const nom::Event& ev) override { - switch ( ev.key.sym ) + if( ev.type != Event::KEY_PRESS ) { + return; + } + + switch(ev.key.sym) { default: break; - // Use inherited SDLApp::on_app_quit() method -- you may also provide - // your own event handler for this. + // Use inherited SDLApp::on_app_quit method -- you may also provide your + // own event handler for this. case SDLK_ESCAPE: case SDLK_q: this->on_app_quit( ev ); break; @@ -230,11 +239,11 @@ class App: public nom::SDLApp } // on_key_down private: - nom::Event event; - /// Window handles nom::RenderWindow window; + nom::EventHandler evt_handler; + /// Interval at which we refresh the frames per second counter nom::Timer update; diff --git a/examples/device_info.cpp b/examples/device_info.cpp index 16270228..f7df0b00 100644 --- a/examples/device_info.cpp +++ b/examples/device_info.cpp @@ -32,6 +32,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // Must be included before NOM_USE_* preprocessor definitions are checked #include "nomlib/config.hpp" +#include "nomlib/platforms.hpp" #if defined( NOM_USE_SDL2_IMAGE ) #include @@ -41,12 +42,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #endif -#if defined( NOM_USE_OPENAL ) +#if defined(NOM_USE_CREATIVE_OPENAL) || defined(NOM_USE_APPLE_OPENAL) || defined(NOM_USE_OPENAL_SOFT) #include "nomlib/audio/AL/OpenAL.hpp" #endif -#if defined(NOM_USE_OPENAL) && defined(NOM_USE_LIBSNDFILE) - #include "nomlib/audio/AL/SoundFile.hpp" +#if defined(NOM_USE_LIBSNDFILE) + #include "nomlib/audio/libsndfile/SoundFileReader.hpp" #endif #if defined( NOM_USE_LIBROCKET ) @@ -57,6 +58,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "nomlib/graphics.hpp" #include "nomlib/system.hpp" +namespace nom { + void nomlib_version_info( void ) { NOM_LOG_INFO ( @@ -168,36 +171,35 @@ void SDL2_ttf_version_info( void ) void OpenAL_version_info( void ) { - #if defined( NOM_USE_OPENAL ) - - struct OpenALVersionInfo - { - std::string version; - std::string renderer; - std::string vendor; - std::string extensions; - }; - - OpenALVersionInfo info; - - AL_CHECK_ERR( info.version = alGetString( AL_VERSION ) ); - AL_CHECK_ERR( info.renderer = alGetString( AL_RENDERER ) ); - AL_CHECK_ERR( info.vendor = alGetString( AL_VENDOR ) ); - AL_CHECK_ERR( info.extensions = alGetString( AL_EXTENSIONS ) ); - - NOM_LOG_INFO( NOM_LOG_CATEGORY_APPLICATION, "OpenAL version: ", info.version ); - NOM_LOG_INFO( NOM_LOG_CATEGORY_APPLICATION, "OpenAL renderer: ", info.renderer ); - NOM_LOG_INFO( NOM_LOG_CATEGORY_APPLICATION, "OpenAL vendor: ", info.vendor ); - NOM_LOG_INFO( NOM_LOG_CATEGORY_APPLICATION, "OpenAL extensions: ", info.extensions ); - #endif + struct OpenALVersionInfo + { + std::string version = "N/A"; + std::string renderer = "N/A"; + std::string vendor = "N/A"; + std::string extensions = "N/A"; + }; + OpenALVersionInfo info; +#if defined(NOM_USE_CREATIVE_OPENAL) || defined(NOM_USE_APPLE_OPENAL) || defined(NOM_USE_OPENAL_SOFT) + AL_CHECK_ERR( info.version = alGetString( AL_VERSION ) ); + AL_CHECK_ERR( info.renderer = alGetString( AL_RENDERER ) ); + AL_CHECK_ERR( info.vendor = alGetString( AL_VENDOR ) ); + AL_CHECK_ERR( info.extensions = alGetString( AL_EXTENSIONS ) ); +#endif // end if NOM_USE_OPENAL + NOM_LOG_INFO( NOM_LOG_CATEGORY_APPLICATION, "OpenAL version: ", info.version ); + NOM_LOG_INFO( NOM_LOG_CATEGORY_APPLICATION, "OpenAL renderer: ", info.renderer ); + NOM_LOG_INFO( NOM_LOG_CATEGORY_APPLICATION, "OpenAL vendor: ", info.vendor ); + NOM_LOG_INFO( NOM_LOG_CATEGORY_APPLICATION, "OpenAL extensions: ", info.extensions ); } void libsndfile_version_info( void ) { - #if defined(NOM_USE_OPENAL) && defined(NOM_USE_LIBSNDFILE) + #if defined(NOM_USE_LIBSNDFILE) NOM_LOG_INFO( NOM_LOG_CATEGORY_APPLICATION, "libsndfile version:", nom::libsndfile_version() ); - #endif + #else + NOM_LOG_INFO( NOM_LOG_CATEGORY_APPLICATION, + "libsndfile version:", "N/A"); + #endif // defined(NOM_USE_LIBSNDFILE) } void libs_version_info( void ) @@ -223,11 +225,16 @@ void libs_version_info( void ) #endif } +} // namespace nom + int main ( int argc, char* argv[] ) { + using namespace nom; + nom::RenderWindow window; nom::Size2i window_size( nom::Size2i::zero ); nom::RendererInfo renderer_info; + uint32 window_flags = SDL_WINDOW_HIDDEN | SDL_WINDOW_OPENGL; // We need SDL2 video initialization so we can obtain the available rendering // caps @@ -237,19 +244,16 @@ int main ( int argc, char* argv[] ) exit( NOM_EXIT_FAILURE ); } - atexit( nom::quit ); + atexit(nom::quit); - #if defined( NOM_PLATFORM_OSX ) - NOM_LOG_INFO( NOM_LOG_CATEGORY_APPLICATION, "Platform: Mac OS X" ); - #elif defined( NOM_PLATFORM_LINUX ) - NOM_LOG_INFO( NOM_LOG_CATEGORY_APPLICATION, "Platform: GNU/Linux" ); - #elif defined( NOM_PLATFORM_POSIX ) - NOM_LOG_INFO( NOM_LOG_CATEGORY_APPLICATION, "Platform: POSIX Unix" ); - #elif defined( NOM_PLATFORM_WINDOWS ) - NOM_LOG_INFO( NOM_LOG_CATEGORY_APPLICATION, "Platform: MS Windows" ); - #else - NOM_LOG_CRIT( NOM_LOG_CATEGORY_APPLICATION, "Platform: Unknown" ); - #endif + auto spec = nom::platform_info(); + NOM_LOG_INFO(NOM_LOG_CATEGORY_APPLICATION, "Platform name:", spec.name); + NOM_LOG_INFO(NOM_LOG_CATEGORY_APPLICATION, "Number of CPUs:", + spec.num_cpus); + NOM_LOG_INFO(NOM_LOG_CATEGORY_APPLICATION, "L1 Cache Size (bytes):", + spec.cpu_cache_size); + NOM_LOG_INFO(NOM_LOG_CATEGORY_APPLICATION, "System RAM (bytes):", + spec.total_ram); // Fix for getting incorrect OpenGL version of 2.1 on my MacBook Air // (Mid 2011) -- when in reality, it is v3.3. We must request a core @@ -261,14 +265,18 @@ int main ( int argc, char* argv[] ) // Output the versions used of nomlib and its dependencies. libs_version_info(); - if( window.create( "device_info", window_size, SDL_WINDOW_HIDDEN | SDL_WINDOW_OPENGL ) == false ) - { - NOM_LOG_CRIT( NOM_LOG_CATEGORY_APPLICATION, "Could not create a window." ); - exit( NOM_EXIT_FAILURE ); + if( window.create("device_info", window_size, window_flags) == false ) { + NOM_LOG_CRIT( NOM_LOG_CATEGORY_APPLICATION, + "Could not create a window." ); + exit(NOM_EXIT_FAILURE); } renderer_info = window.caps(); + NOM_LOG_INFO( NOM_LOG_CATEGORY_APPLICATION, + "Primary display name:", window.display_name() ); + NOM_LOG_INFO( NOM_LOG_CATEGORY_APPLICATION, + "Refresh Rate:", window.refresh_rate() ); NOM_LOG_INFO( NOM_LOG_CATEGORY_APPLICATION, "Renderer: ", renderer_info.name() ); NOM_LOG_INFO( NOM_LOG_CATEGORY_APPLICATION, "SDL_RENDERER_TARGETTEXTURE: ", renderer_info.target_texture() ? "YES" : "NO" ); NOM_LOG_INFO( NOM_LOG_CATEGORY_APPLICATION, "SDL_RENDERER_ACCELERATED: ", renderer_info.accelerated() ? "YES" : "NO" ); diff --git a/examples/events.cpp b/examples/events.cpp index 21c5e699..320d2f20 100644 --- a/examples/events.cpp +++ b/examples/events.cpp @@ -2,7 +2,7 @@ nomlib - C++11 cross-platform game engine -Copyright (c) 2013, 2014 Jeffrey Carpenter +Copyright (c) 2013, 2014, 2015 Jeffrey Carpenter All rights reserved. Redistribution and use in source and binary forms, with or without @@ -26,484 +26,454 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ -#include -#include - -// Pubic nomlib interface headers +#include #include #include #include -/// File path name of the resources directory; this must be a relative file path. -const std::string APP_RESOURCES_DIR = "Resources"; - -/// \brief Relative file path name of our resource example -const nom::Path p; -const std::string RESOURCE_ICON = APP_RESOURCES_DIR + p.native() + "icon.png"; - -/// \brief Name of our application. -const std::string APP_NAME = "Input Mapping"; - -/// \brief Width, in pixels, of our effective rendering surface. -const nom::int32 WINDOW_WIDTH = 768; - -/// \brief Height, in pixels, of our effective rendering surface. -const nom::int32 WINDOW_HEIGHT = 448; +using namespace nom; -/// \brief Maximum number of active windows we will attempt to spawn in this -/// example. -const nom::int32 MAXIMUM_WINDOWS = 2; +// Enable base API sanity checks for nom::EventHandler +#define NOM_TEST_EVENT_HANDLER_API -const nom::int32 USER_EVENT_DEBUG = 0; -/// \brief Usage example -/// \remarks For unit testing: ensure that library is compiled with -/// the appropriate defines enabled within EventHandler.hpp. class App: public nom::SDLApp { public: - App( nom::int32 argc, char* argv[] ) + enum InputDirection: uint8 { - NOM_LOG_TRACE( NOM ); - - // Fatal error; if we are not able to complete this step, it means that - // we probably cannot rely on our resource paths! - if( nom::init( argc, argv ) == false ) - { - nom::DialogMessageBox( APP_NAME, "ERROR: Could not initialize nomlib." ); - exit( NOM_EXIT_FAILURE ); - } - - atexit( nom::quit ); - } // end App + LEFT_DIRECTION, + RIGHT_DIRECTION, + UP_DIRECTION, + DOWN_DIRECTION, + }; - ~App( void ) + App() { - NOM_LOG_TRACE( NOM ); - } // end ~App - - bool on_init( void ) - { - nom::uint32 window_flags = SDL_WINDOW_RESIZABLE; - - for( auto idx = 0; idx < MAXIMUM_WINDOWS; ++idx ) - { - if ( this->window[idx].create( APP_NAME, ( WINDOW_WIDTH / 2 ), WINDOW_HEIGHT, window_flags ) == false ) - { - return false; - } - - this->window[idx].set_position( 0 + ( WINDOW_WIDTH / 2 ) * idx, ( WINDOW_HEIGHT / 2 ) ); + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE, NOM_LOG_PRIORITY_VERBOSE); - if( this->window[idx].set_window_icon( RESOURCE_ICON ) == false ) - { - nom::DialogMessageBox( APP_NAME, "ERROR: Could not load window icon: " + RESOURCE_ICON ); - return false; - } + // ...Initialize the input mapper callbacks... - // Scale window contents up by the new width & height - // this->window[idx].set_logical_size( this->window[idx].size(), this->window[idx].size() ); - } + this->quit_app = nom::event_callback( [=](const nom::Event& evt) { + this->on_app_quit(evt); + }); - // Start out execution with both windows minimized. - nom::EventDispatcher sender; - nom::Event user_event; - - nom::EventCallback delegate1( [&] ( const nom::Event& evt ) { this->minimize( evt ); } ); - nom::EventCallback delegate2( [&] ( const nom::Event& evt ) { this->restore( evt ); } ); - - user_event.type = SDL_USEREVENT; - user_event.timestamp = ticks(); - user_event.user.code = USER_EVENT_DEBUG; - user_event.user.data1 = nullptr; - user_event.user.data2 = NOM_SCAST( nom::EventCallback*, &delegate1 ); - user_event.user.window_id = 0; - // sender.dispatch( user_event ); - - user_event.type = SDL_USEREVENT; - user_event.timestamp = ticks(); - user_event.user.code = USER_EVENT_DEBUG; - user_event.user.data1 = nullptr; - user_event.user.data2 = NOM_SCAST( nom::EventCallback*, &delegate2 ); - user_event.user.window_id = 0; - // sender.dispatch( user_event ); - - // State 0 is mouse button input mapping - // State 1 is keyboard input mapping - // State 2 is joystick button input mapping - // State 3 is mouse wheel input mapping - // State 4 is joystick axis input mapping; note that this implementation - // is broken (not fully implemented). - // State 5 is keyboard input mapping with repeating key active - nom::InputActionMapper state0, state1, state2, state3, state4, state5; - - nom::EventCallback quit_app( [&] ( const nom::Event& evt ) - { - this->on_app_quit( evt ); - } - ); + this->minimize_window = nom::event_callback ( [=](const nom::Event& evt) { + NOM_LOG_INFO( NOM_LOG_CATEGORY_APPLICATION, "Minimizing window 0." ); + this->window[0].minimize_window(); + }); - // Mouse button mappings + this->restore_window = nom::event_callback( [=](const nom::Event& evt) { + NOM_LOG_INFO( NOM_LOG_CATEGORY_APPLICATION, "Restoring window 0." ); + this->window[0].restore_window(); + }); - state0.insert( "minimize_window_0", nom::MouseButtonAction( SDL_MOUSEBUTTONDOWN, SDL_BUTTON_LEFT ), nom::EventCallback( [&] ( const nom::Event& evt ) { this->minimize( evt ); } ) ); - state0.insert( "restore_window_0", nom::MouseButtonAction( SDL_MOUSEBUTTONDOWN, SDL_BUTTON_RIGHT ), nom::EventCallback( [&] ( const nom::Event& evt ) { this->restore( evt ); } ) ); - state0.insert ( - "quit_app", - nom::MouseButtonAction( SDL_MOUSEBUTTONDOWN, SDL_BUTTON_LEFT, 3 ), - quit_app - ); + this->up_action = nom::event_callback( [=](const nom::Event& evt) { + this->color_fill(evt, UP_DIRECTION); + }); - NOM_CONNECT_INPUT_MAPPING( state1, "minimize_window_0", nom::KeyboardAction( SDL_KEYDOWN, SDLK_1 ), minimize, evt ); + this->down_action = nom::event_callback( [=](const nom::Event& evt) { + this->color_fill(evt, DOWN_DIRECTION); + }); - // NOTE: The following keyboard action will have its modifier key reset to - // zero (0) as a side-effect of minimizing the window, ergo you will not be - // able to continue holding down LCTRL after the first time in order to - // re-execute the restoration of said window. This is NOT a bug within the - // the input mapper. - NOM_CONNECT_INPUT_MAPPING( state1, "restore_window_0", nom::KeyboardAction( SDL_KEYDOWN, SDLK_2, KMOD_LCTRL ), restore, evt ); - - state1.insert ( - "quit_app", - nom::KeyboardAction( SDL_KEYDOWN, SDLK_1, KMOD_LCTRL ), - quit_app - ); - - // Joystick button mappings + this->left_action = nom::event_callback( [=](const nom::Event& evt) { + this->color_fill(evt, LEFT_DIRECTION); + }); - state2.insert ( - "minimize_window_0", - nom::JoystickButtonAction( 0, SDL_JOYBUTTONDOWN, nom::PSXBUTTON::L1 ), - nom::EventCallback( [&] ( const nom::Event& evt ) { this->minimize( evt ); } ) - ); + this->right_action = nom::event_callback( [=](const nom::Event& evt) { + this->color_fill(evt, RIGHT_DIRECTION); + }); + } - state2.insert ( - "restore_window_0", - nom::JoystickButtonAction( 0, SDL_JOYBUTTONDOWN, nom::PSXBUTTON::R1 ), - nom::EventCallback( [&] ( const nom::Event& evt ) { this->restore( evt ); } ) - ); + ~App() + { + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE, NOM_LOG_PRIORITY_VERBOSE); + } - // Mouse wheel mappings + bool on_init() + { + uint32 window_flags = 0; - // Wheel is going upward - nom::MouseWheelAction mouse_wheel ( - SDL_MOUSEWHEEL, - nom::MouseWheelAction::AXIS_Y, - nom::MouseWheelAction::UP - ); + for( auto idx = 0; idx < MAXIMUM_WINDOWS; ++idx ) { - state3.insert( "color_fill_1", mouse_wheel, nom::EventCallback( [&] ( const nom::Event& evt ) { this->color_fill( evt, 0 ); } ) ); + if( this->window[idx].create( APP_NAME, + WINDOW_RESOLUTION, window_flags) == false ) + { + return false; + } - // Wheel is going downward - mouse_wheel = nom::MouseWheelAction ( - SDL_MOUSEWHEEL, - nom::MouseWheelAction::AXIS_Y, - nom::MouseWheelAction::DOWN - ); + auto window_pos = Point2i::zero; + window_pos.x = WINDOW_RESOLUTION.w * idx; + window_pos.y = 0; + this->window[idx].set_position(window_pos); + } - state3.insert( "color_fill_1", mouse_wheel, nom::EventCallback( [&] ( const nom::Event& evt ) { this->color_fill( evt, 1 ); } ) ); + nom::InputActionMapper mouse_buttons, keyboard, mouse_wheel, kb_repeat; + nom::InputAction action; - // Wheel is going leftward - mouse_wheel = nom::MouseWheelAction ( - SDL_MOUSEWHEEL, - nom::MouseWheelAction::AXIS_X, - nom::MouseWheelAction::LEFT - ); + // ...Mouse button mappings... - state3.insert( "color_fill_1", mouse_wheel, nom::EventCallback( [&] ( const nom::Event& evt ) { this->color_fill( evt, 2 ); } ) ); + mouse_buttons.insert( "minimize_window_0", + nom::MouseButtonAction(nom::LEFT_MOUSE_BUTTON), + this->minimize_window ); - // Wheel is going rightward - mouse_wheel = nom::MouseWheelAction ( - SDL_MOUSEWHEEL, - nom::MouseWheelAction::AXIS_X, - nom::MouseWheelAction::RIGHT - ); + mouse_buttons.insert( "restore_window_0", + nom::MouseButtonAction(nom::RIGHT_MOUSE_BUTTON), + this->restore_window ); - state3.insert( "color_fill_1", mouse_wheel, nom::EventCallback( [&] ( const nom::Event& evt ) { this->color_fill( evt, 3 ); } ) ); + mouse_buttons.insert( "quit_app", + nom::MouseButtonAction(nom::LEFT_MOUSE_BUTTON, 3), + this->quit_app ); - nom::InputAction jaxis; + // ...Keyboard mappings... - // Joystick axis is going upward - jaxis = nom::JoystickAxisAction ( 0, SDL_JOYAXISMOTION, 0, -1 ); + NOM_CONNECT_INPUT_MAPPING( keyboard, "minimize_window_0", + nom::KeyboardAction(SDLK_1), + this->minimize_window, evt ); - state4.insert( "color_fill_1", jaxis, nom::EventCallback( [&] ( const nom::Event& evt ) { this->color_fill( evt, 0 ); } ) ); + // NOTE: The following keyboard action will have its modifier key reset to + // zero (0) as a side-effect of minimizing the window, ergo you will not be + // able to continue holding down LCTRL after the first time in order to + // re-execute the restoration of said window. This is NOT a bug within the + // the input mapper. + NOM_CONNECT_INPUT_MAPPING( keyboard, "restore_window_0", + nom::KeyboardAction(SDLK_2, KMOD_LCTRL), + this->restore_window, evt ); - // Joystick axis is going downward - jaxis = nom::JoystickAxisAction( 0, SDL_JOYAXISMOTION, 0, 1 ); + keyboard.insert( "quit_app", + nom::KeyboardAction(SDLK_1, KMOD_LCTRL), + this->quit_app ); - state4.insert( "color_fill_1", jaxis, nom::EventCallback( [&] ( const nom::Event& evt ) { this->color_fill( evt, 1 ); } ) ); + // ...Mouse wheel mappings... - // Joystick axis is going leftward - jaxis = nom::JoystickAxisAction( 0, SDL_JOYAXISMOTION, 1, -1 ); + action = nom::MouseWheelAction(nom::MOUSE_WHEEL_UP); + mouse_wheel.insert("color_fill_up", action, this->up_action); - state4.insert( "color_fill_1", jaxis, nom::EventCallback( [&] ( const nom::Event& evt ) { this->color_fill( evt, 2 ); } ) ); + action = nom::MouseWheelAction(nom::MOUSE_WHEEL_DOWN); + mouse_wheel.insert("color_fill_down", action, this->down_action); - // Joystick axis is going rightward - jaxis = nom::JoystickAxisAction( 0, SDL_JOYAXISMOTION, 1, 1 ); + action = nom::MouseWheelAction(nom::MOUSE_WHEEL_LEFT); + mouse_wheel.insert("color_fill_left", action, this->left_action); - state4.insert( "color_fill_1", jaxis, nom::EventCallback( [&] ( const nom::Event& evt ) { this->color_fill( evt, 3 ); } ) ); + action = nom::MouseWheelAction(nom::MOUSE_WHEEL_RIGHT); + mouse_wheel.insert("color_fill_right", action, this->right_action); - nom::InputAction kb_repeat; + // ...Keyboard repeat action... // Keyboard action should only trigger when the key symbols 4 and the // Command (OS X) or Windows modifier key is repeating (pressed down for // at least ~0.5s). - kb_repeat = nom::KeyboardAction( SDL_KEYDOWN, SDLK_3, KMOD_LGUI, 1 ); - state5.insert( "color_fill_1", kb_repeat, nom::EventCallback( [&] ( const nom::Event& evt ) { this->color_fill( evt, 3 ); } ) ); - - // Mouse button input mapping - this->input_mapper.insert( "state0", state0, true ); - - // Keyboard input mapping - this->input_mapper.insert( "state1", state1, true ); - - // Joystick Button input mapping - this->input_mapper.insert( "state2", state2, true ); - - // Mouse wheel input mapping - this->input_mapper.insert( "state3", state3, true ); - - // Joystick Axis input mapping - this->input_mapper.insert( "state4", state4, true ); + action = nom::KeyboardAction(SDLK_3, KMOD_LGUI, 1); + kb_repeat.insert("color_fill_1", action, this->right_action); + + // ...Installation of input mappings... + + this->input_mapper.insert("mouse_buttons", mouse_buttons, true); + this->input_mapper.insert("keyboard", keyboard, true); + this->input_mapper.insert("mouse_wheel", mouse_wheel, true); + this->input_mapper.insert("kb_repeat", kb_repeat, true); + + // ...Isolated tests... +#if 0 + this->input_mapper.disable("mouse_buttons"); + this->input_mapper.disable("keyboard"); + this->input_mapper.disable("mouse_wheel"); + this->input_mapper.disable("kb_repeat"); +#endif + +#if 0 + this->input_mapper.activate_only("keyboard"); +#endif + + if( this->input_mapper.active("keyboard") == false ) { + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, + "Input context 'keyboard' is not active." ); + return false; + } - // Keyboard repeating press mappings - this->input_mapper.insert( "state5", state5, true ); +#if 0 + this->input_mapper.clear(); +#endif - // this->input_mapper.clear(); +#if 0 + // Debugging diagnostics + this->input_mapper.dump(); +#endif - // this->input_mapper.disable( "state0" ); - // this->input_mapper.disable( "state1" ); - // this->input_mapper.disable( "state2" ); - // this->input_mapper.disable( "state3" ); - // this->input_mapper.disable( "state4" ); - // this->input_mapper.disable( "state5" ); +#if defined(NOM_TEST_EVENT_HANDLER_API) + this->test_flush_events(); + this->test_event_watch(); +#endif - // this->input_mapper.activate_only( "state3" ); + SDLApp::set_event_handler(this->evt_handler); - if( this->input_mapper.active( "state1" ) ) - { - // nom::DialogMessageBox( APP_NAME, "ERROR: Input context state1 is active." ); - // return false; - } + this->input_mapper.set_event_handler(this->evt_handler); - // this->input_mapper.activate_only( "state1" ); - // this->input_mapper.dump(); +#if defined(NOM_TEST_EVENT_HANDLER_API) + this->test_simulated_events(); +#endif return true; - } // end on_init - - void minimize( const nom::Event& ev ) - { - ev.dump(); - - if( ev.type == SDL_KEYDOWN ) - { - ev.key.dump(); - } - else if( ev.type == SDL_MOUSEBUTTONDOWN ) - { - ev.mouse.dump(); - } - else if( ev.type == SDL_JOYBUTTONDOWN ) - { - ev.jbutton.dump(); - } - - NOM_DUMP("MINIMIZE WINDOW 0"); - this->window[0].minimize_window(); } - void restore( const nom::Event& ev ) + int Run() { - ev.dump(); - - if( ev.type == SDL_KEYDOWN ) - { - ev.key.dump(); - } - else if( ev.type == SDL_MOUSEBUTTONDOWN ) - { - ev.mouse.dump(); - } - else if( ev.type == SDL_JOYBUTTONDOWN ) - { - ev.jbutton.dump(); + // Clear the render buffer + for( auto idx = 0; idx < MAXIMUM_WINDOWS; ++idx ) { + this->window[idx].fill(nom::Color4i::SkyBlue); + this->window[idx].update(); } - NOM_DUMP("RESTORE WINDOW 0"); - this->window[0].restore_window(); + // 1. Events + // 2. Logic + // 3. Render + while( this->running() == true ) { + + nom::Event evt; + while( evt_handler.poll_event(evt) == true ) { + // NOTE: Pending events will be handled by the event listeners that + // were given an EventHandler object via ::set_event_handler. + // + // Additional event processing done in here is still OK, too. + } // end inner while + } // end outer while + + // ...Clean up ::on_user_event test data... + delete NOM_SCAST(nom::event_callback*, this->user_data1); + delete NOM_SCAST(nom::event_callback*, this->user_data2); + this->user_data1 = nullptr; + this->user_data2 = nullptr; + +#if defined(NOM_TEST_EVENT_HANDLER_API) + this->cleanup_event_watch_test(); +#endif + + return NOM_EXIT_SUCCESS; } - void color_fill( const nom::Event& ev, nom::uint8 dir ) - { - ev.dump(); + private: + const std::string APP_NAME = "InputMapper and events handling"; + const Size2i WINDOW_RESOLUTION = Size2i(640/2, 480); + static const nom::int32 MAXIMUM_WINDOWS = 2; + nom::RenderWindow window[MAXIMUM_WINDOWS]; - if( ev.type == SDL_MOUSEWHEEL ) - { - ev.wheel.dump(); - } - else if( ev.type == SDL_JOYAXISMOTION ) - { - ev.jaxis.dump(); - } + // Event handling + EventHandler evt_handler; + nom::InputStateMapper input_mapper; + + nom::event_callback quit_app; + nom::event_callback minimize_window; + nom::event_callback restore_window; + nom::event_callback up_action; + nom::event_callback down_action; + nom::event_callback left_action; + nom::event_callback right_action; - // NOM_LOG_TRACE( NOM ); + nom::event_filter test_watch_callback; + nom::event_callback* user_data1 = nullptr; + nom::event_callback* user_data2 = nullptr; - switch( dir ) + void color_fill(const nom::Event& ev, InputDirection dir) + { + switch(dir) { default: { - NOM_DUMP( "INVALID" ); + NOM_LOG_INFO(NOM_LOG_CATEGORY_APPLICATION, "INVALID INPUT"); + NOM_ASSERT_INVALID_PATH(); break; } - case 0: + case UP_DIRECTION: { - NOM_DUMP( "WHEEL UP" ); - this->window[1].fill( nom::Color4i::Magenta ); + NOM_LOG_INFO(NOM_LOG_CATEGORY_APPLICATION, "UP INPUT"); + this->window[1].fill(nom::Color4i::Magenta); this->window[1].update(); break; } - case 1: + case DOWN_DIRECTION: { - NOM_DUMP( "WHEEL DOWN" ); - this->window[1].fill( nom::Color4i::Gray ); + NOM_LOG_INFO(NOM_LOG_CATEGORY_APPLICATION, "DOWN INPUT"); + this->window[1].fill(nom::Color4i::Gray); this->window[1].update(); break; } - case 2: + case LEFT_DIRECTION: { - NOM_DUMP( "WHEEL LEFT" ); - this->window[1].fill( nom::Color4i::Orange ); + NOM_LOG_INFO(NOM_LOG_CATEGORY_APPLICATION, "LEFT INPUT"); + this->window[1].fill(nom::Color4i::Orange); this->window[1].update(); break; } - case 3: + case RIGHT_DIRECTION: { - NOM_DUMP( "WHEEL RIGHT" ); - this->window[1].fill( nom::Color4i::Yellow ); + NOM_LOG_INFO(NOM_LOG_CATEGORY_APPLICATION, "RIGHT INPUT"); + this->window[1].fill(nom::Color4i::Yellow); this->window[1].update(); break; } } // end switch dir - } // end color_fill + } - nom::sint Run( void ) + /// \brief The default event handler for user-defined events. + /// + /// \remarks The default implementation in SDLApp::on_user_event does + /// nothing with these events. + void on_user_event(const nom::Event& ev) override { - for( auto idx = 0; idx < MAXIMUM_WINDOWS; ++idx ) - { - this->update[idx].start(); - this->fps[idx].start(); - } + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE_EVENT, NOM_LOG_PRIORITY_INFO); - // Paint the window(s) only once ... let event callbacks do the rest! - for( auto idx = 0; idx < MAXIMUM_WINDOWS; ++idx ) - { - this->window[idx].fill( nom::Color4i::SkyBlue ); - this->window[idx].update(); + nom::event_callback* data1 = + NOM_SCAST(nom::event_callback*, ev.user.data1); + nom::event_callback* data2 = + NOM_SCAST(nom::event_callback*, ev.user.data2); + if( ev.type == Event::USER_EVENT && ev.user.code == 666 ) { + + if( data1 != nullptr ) { + data1->operator()(ev); + } + + if( data2 != nullptr ) { + data2->operator()(ev); + } } + } - // Test the simulation of an input event - nom::Event key_ev; - key_ev.type = SDL_KEYDOWN; - key_ev.timestamp = nom::ticks(); - key_ev.key.sym = SDLK_1; - key_ev.key.mod = KMOD_NONE; - key_ev.key.repeat = 0; - this->push_event( key_ev ); + /// \brief Test events flush API of EventHandler + void test_flush_events() + { + NOM_ASSERT( this->evt_handler.joystick_event_type() == + EventHandler::NO_EVENT_HANDLER ); - // 1. Events - // 2. Logic - // 3. Render - while ( this->running() == true ) - { - while( this->poll_event( this->event ) ) - { - this->on_event( this->event ); + nom::Event test_flush; + test_flush.type = Event::FIRST_EVENT; + test_flush.timestamp = nom::ticks(); + this->evt_handler.push_event(test_flush); + this->evt_handler.push_event(test_flush); + NOM_ASSERT(this->evt_handler.num_events() == 2); - this->input_mapper.on_event( this->event ); - } + this->evt_handler.flush_event(Event::FIRST_EVENT); + NOM_ASSERT(this->evt_handler.num_events() == 1); - for( auto idx = 0; idx < MAXIMUM_WINDOWS; ++idx ) - { - // this->window[idx].update(); - this->fps[idx].update(); + this->evt_handler.flush_events(); + NOM_ASSERT(this->evt_handler.num_events() == 0); + } - // Refresh the frames per second at 1 second intervals - if ( this->update[idx].ticks() > 1000 ) + /// \brief Test the addition and removal API in EventHandler for event + /// watchers. + void test_event_watch() + { + this->test_watch_callback = + nom::event_filter([=](const Event& evt, void* data) { + if( evt.type == Event::KEY_RELEASE && evt.key.sym == SDLK_2 && + evt.key.state == InputState::RELEASED ) { - if ( this->show_fps() == true ) - { - this->window[idx].set_window_title( APP_NAME + " - " + this->fps[idx].asString() + ' ' + "fps" ); - } - else - { - this->window[idx].set_window_title( APP_NAME + " [" + std::to_string(this->window[idx].window_id()) + "]" + " - " + "Display" + ' ' + std::to_string ( this->window[idx].window_display_id() ) ); + if( data != nullptr ) { + NOM_LOG_INFO( NOM_LOG_CATEGORY_APPLICATION, "data:", + NOM_SCAST(char*, data) ); } + } + }); - this->update[idx].restart(); - } // end refresh cycle - } // end for MAXIMUM_WINDOWS update loop - } // end while SDLApp::running() is true + this->evt_handler.append_event_watch( this->test_watch_callback, + (char*)"hello, world!" ); + NOM_ASSERT(this->evt_handler.num_event_watchers() == 1); - return NOM_EXIT_SUCCESS; - } // end Run() + this->evt_handler.append_event_watch(nullptr, nullptr); + NOM_ASSERT(this->evt_handler.num_event_watchers() == 1); - private: - /// \brief Event handler for user-defined events. - /// - /// \remarks Implements nom::EventHandler::on_user_event - void on_user_event( const nom::Event& ev ) - { - // A call is made here to the virtual method being re-implemented here in - // order to catch debugging output with debug builds compiled in; see - // EventHandler.hpp. - // Input::on_user_event( ev ); + this->evt_handler.remove_event_watch(nullptr); + NOM_ASSERT(this->evt_handler.num_event_watchers() == 1); - if( ev.user.code == USER_EVENT_DEBUG ) - { - ev.dump(); - ev.user.dump(); + // NOTE: This should trigger the callback assigned to the event watcher; + // see ::on_watch_callback + nom::Event key_release_ev; + key_release_ev = nom::create_key_release(SDLK_2, KMOD_NONE, 0); + this->evt_handler.push_event(key_release_ev); + } - nom::EventCallback* delegate = ev.user.get_callback(); + void cleanup_event_watch_test() + { + this->evt_handler.remove_event_watch(this->test_watch_callback); - if( delegate != nullptr ) - { - // FIXME: I get a segfault here when we try to execute the callback. - #if ! defined( NOM_PLATFORM_WINDOWS ) - // FIXME: We get a segfault under OS X (and presumed Windows, too) - // when we add more than one kb_repeat input action. No idea why! - // delegate->operator()( ev ); - #endif - } - } + // NOTE: Only the event watchers from SDLApp && input mapper should exist + // at this point! + NOM_ASSERT(this->evt_handler.num_event_watchers() == 2); } - private: - nom::Event event; + /// \brief Test the simulation of input events. + void test_simulated_events() + { + // ...key press... - /// \brief Window handles - /// - /// \todo Use std::vector? - nom::RenderWindow window[MAXIMUM_WINDOWS]; + nom::Event key_ev; + key_ev = nom::create_key_press(SDLK_1, KMOD_NONE, 0); + evt_handler.push_event(key_ev); - /// \brief Interval at which we refresh the frames per second counter - nom::Timer update[MAXIMUM_WINDOWS]; + // ...mouse button click... - /// \brief Timer for tracking frames per second - nom::FPS fps[MAXIMUM_WINDOWS]; + nom::Event mbutton_ev; + mbutton_ev = + nom::create_mouse_button_click(nom::RIGHT_MOUSE_BUTTON, 1, 1); + evt_handler.push_event(mbutton_ev); - nom::InputStateMapper input_mapper; + // ...user event... + + this->user_data1 = new nom::event_callback( [=](const nom::Event& evt) { + if( evt.type == Event::USER_EVENT ) { + NOM_LOG_INFO(NOM_LOG_CATEGORY_APPLICATION, "Event::USER_EVENT"); + NOM_LOG_INFO( NOM_LOG_CATEGORY_APPLICATION, + "event code:", evt.user.code ); + } + }); + + this->user_data2 = new nom::event_callback( [=](const nom::Event& evt) { + if( evt.type == Event::USER_EVENT ) { + NOM_LOG_INFO(NOM_LOG_CATEGORY_APPLICATION, "Event::USER_EVENT"); + } else if( evt.type == Event::QUIT_EVENT ) { + NOM_LOG_INFO(NOM_LOG_CATEGORY_APPLICATION, "Event::QUIT_EVENT"); + } + }); + + nom::Event user_ev = + nom::create_user_event(666, this->user_data1, this->user_data2, 0); + evt_handler.push_event(user_ev); + + // ...quit event... +#if 0 + nom::Event quit_ev = + nom::create_quit_event(this->user_data1, this->user_data2); + evt_handler.push_event(quit_ev); +#endif + } }; // end class App -nom::sint main( nom::int32 argc, char* argv[] ) +int main(nom::int32 argc, char* argv[]) { - App app ( argc, argv ); + // Fatal error; if we are not able to complete this step, it means that + // we probably cannot rely on our resource paths! + if( nom::init(argc, argv) == false ) { + NOM_LOG_CRIT( NOM_LOG_CATEGORY_APPLICATION, + "Could not initialize nomlib." ); + return NOM_EXIT_FAILURE; + } + + atexit(nom::quit); + + // Show add and removal events + nom::SDL2Logger::set_logging_priority( NOM_LOG_CATEGORY_EVENT, + NOM_LOG_PRIORITY_INFO ); + + // Enable event handler queue debugging statistics + nom::set_hint(NOM_EVENT_QUEUE_STATISTICS, "1"); + + App app; - if ( app.on_init() == false ) - { - nom::DialogMessageBox( APP_NAME, "ERROR: Could not initialize application." ); + if( app.on_init() == false ) { + NOM_LOG_CRIT( NOM_LOG_CATEGORY_APPLICATION, + "Could not initialize application." ); return NOM_EXIT_FAILURE; } diff --git a/examples/gamecontroller_events.cpp b/examples/gamecontroller_events.cpp new file mode 100644 index 00000000..fe4a1993 --- /dev/null +++ b/examples/gamecontroller_events.cpp @@ -0,0 +1,644 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014, 2015 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#include +#include +#include +#include +#include + +using namespace nom; +/* +/// \brief The file used for resource path lookups for this example. +const std::string RES_FILE = "InputDevices.json"; + +class App: public nom::SDLApp +{ + public: + App() + { + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE, NOM_LOG_PRIORITY_VERBOSE); + + // ...Initialize the input mapper callbacks... + + this->quit_app = nom::event_callback( [=](const nom::Event& evt) { + int32 numJoysticks = joystick_evt->num_joysticks(); + NOM_LOG_INFO( NOM_LOG_CATEGORY_APPLICATION, numJoysticks, + "remaining in pool"); + this->on_app_quit(evt); + }); + + this->init_db_bindings = nom::event_callback( [=](const nom::Event& evt) { + this->initialize_game_controller_db(this->db_filename); + }); + + this->x_axis_action = nom::event_callback( [=](const nom::Event& evt) { + + int32 axis = evt.caxis.axis; + int32 axis_value = evt.caxis.value; + Point2i rect_pos; + + if( axis_value > nom::JOYSTICK_LEFT_THUMB_DEAD_ZONE || + axis_value < -nom::JOYSTICK_LEFT_THUMB_DEAD_ZONE ) + { + // ...proportionate transform of the joystick axis && screen-space + // coordinate systems + + rect_pos.x = axis_value + 32768; + + // Account for circular dead zone + if( axis_value > nom::JOYSTICK_LEFT_THUMB_DEAD_ZONE ) { + rect_pos.x -= nom::JOYSTICK_LEFT_THUMB_DEAD_ZONE; + } else if( axis_value < -nom::JOYSTICK_LEFT_THUMB_DEAD_ZONE ) { + rect_pos.x += nom::JOYSTICK_LEFT_THUMB_DEAD_ZONE; + } + + rect_pos.x *= WINDOW_RESOLUTION.w; + rect_pos.x /= 65535; + + if( rect_pos.x < 0 ) { + rect_pos.x = 0; + } else if( rect_pos.x > WINDOW_RESOLUTION.w - AXIS_RECT_DIMS.w ) { + rect_pos.x = WINDOW_RESOLUTION.w - AXIS_RECT_DIMS.w; + } + + if( axis < 2 ) { + this->axis_rectangle_pos[0].x = rect_pos.x; + } else { + this->axis_rectangle_pos[1].x = rect_pos.x; + } + } else { + // Resting position (we're in the dead zone) + if( axis < 2 ) { + this->axis_rectangle_pos[0].x = + (WINDOW_RESOLUTION.w - AXIS_RECT_DIMS.w) / 2; + } else { + this->axis_rectangle_pos[1].x = + (WINDOW_RESOLUTION.w - AXIS_RECT_DIMS.w) / 2; + } + } + }); + + this->y_axis_action = nom::event_callback( [=](const nom::Event& evt) { + + int32 axis = evt.caxis.axis; + int32 axis_value = evt.caxis.value; + Point2i rect_pos; + + if( axis_value > nom::JOYSTICK_LEFT_THUMB_DEAD_ZONE || + axis_value < -nom::JOYSTICK_LEFT_THUMB_DEAD_ZONE ) + { + // ...proportionate transform of the joystick axis && screen-space + // coordinate systems + + rect_pos.y = axis_value + 32768; + + // Account for circular dead zone + if( axis_value > nom::JOYSTICK_LEFT_THUMB_DEAD_ZONE ) { + rect_pos.y -= nom::JOYSTICK_LEFT_THUMB_DEAD_ZONE; + } else if( axis_value < -nom::JOYSTICK_LEFT_THUMB_DEAD_ZONE ) { + rect_pos.y += nom::JOYSTICK_LEFT_THUMB_DEAD_ZONE; + } + + rect_pos.y *= WINDOW_RESOLUTION.h; + rect_pos.y /= 65535; + + if( rect_pos.y < 0 ) { + rect_pos.y = 0; + } else if( rect_pos.y > WINDOW_RESOLUTION.h - AXIS_RECT_DIMS.h ) { + rect_pos.y = WINDOW_RESOLUTION.h - AXIS_RECT_DIMS.h; + } + + if( axis < 2 ) { + this->axis_rectangle_pos[0].y = rect_pos.y; + } else { + this->axis_rectangle_pos[1].y = rect_pos.y; + } + } else { + // Resting position (we're in the dead zone) + if( axis < 2 ) { + this->axis_rectangle_pos[0].y = + (WINDOW_RESOLUTION.h - AXIS_RECT_DIMS.h) / 2; + } else { + this->axis_rectangle_pos[1].y = + (WINDOW_RESOLUTION.h - AXIS_RECT_DIMS.h) / 2; + } + } + }); + + this->button_action = nom::event_callback( [=](const nom::Event& evt) { + + int32 button = evt.cbutton.button; + int32 button_state = evt.cbutton.state; + Point2i rect_pos; + + if( button_state == InputState::PRESSED ) { + auto spacing = BUTTON_RECT_DIMS.w * 2; + auto origin = BUTTON_RECT_DIMS.w; + rect_pos.x = + origin + (spacing * button); + rect_pos.y = WINDOW_RESOLUTION.h - BUTTON_RECT_DIMS.h * 2; + } else { + // Render off-screen by default + rect_pos.x = -WINDOW_RESOLUTION.w; + rect_pos.y = -WINDOW_RESOLUTION.h; + } + + if( button < MAX_BUTTONS ) { + this->button_rectangle_pos[button] = rect_pos; + } + }); + } + + ~App() + { + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE, NOM_LOG_PRIORITY_VERBOSE); + } + + bool on_init() + { + InputActionMapper key; + nom::InputAction action; + + nom::uint32 window_flags = SDL_WINDOW_RESIZABLE; + + if( this->res.load_file(RES_FILE, "resources") == false ) { + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, + "Could not resolve the resource path from the file:", + RES_FILE ); + return false; + } + + this->db_filename = res.path() + "gamecontrollerdb.txt"; + + if( this->window.create( APP_NAME, WINDOW_RESOLUTION, + window_flags ) == false ) + { + return false; + } + + NOM_ASSERT( this->evt_handler.joystick_event_type() == + EventHandler::NO_EVENT_HANDLER ); + + if( this->evt_handler.enable_game_controller_polling() == false ) { + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, + "Could not initialize game controller subsystem: ", + nom::error() ); + return false; + } + + NOM_ASSERT( this->evt_handler.joystick_event_type() == + EventHandler::GAME_CONTROLLER_EVENT_HANDLER ); + + this->initialize_game_controller_db(this->db_filename); + + // NOTE: Ensure that closing the joystick device more than once does not + // crash with a double-free memory violation from SDL_GameControllerClose + { + GameController jdev; + for( auto joystick_index = 0; + joystick_index != Joystick::num_joysticks(); + ++joystick_index ) + { + if( jdev.open(joystick_index) == true ) { + jdev.close(); + jdev.close(); + jdev.close(); + } + } + } + + // FIXME(jeff): Why are these actions not being executed? + action = + nom::KeyboardAction(Event::KEY_PRESS, SDLK_ESCAPE); + key.insert("quit_app", action, this->quit_app); + + action = + nom::KeyboardAction(Event::KEY_PRESS, SDLK_r); + key.insert("reload_controller_mappings", action, this->init_db_bindings); + + // ...Test remapping a couple game controller buttons... +#if 0 + { + GameController jdev; + if( jdev.open(0) == true ) { + NOM_LOG_INFO( NOM_LOG_CATEGORY_APPLICATION, + "The game controller's current mapping string is", + jdev.mapping_string() ); + + // Flip the left and right shoulder buttons + const std::string GAME_CONTROLLER_MAPPING_STR = + "6d0400000000000016c2000000000000,Logitech F310 Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b4,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,"; + int result = jdev.load_mapping_string(GAME_CONTROLLER_MAPPING_STR); + // We always expect an existing mapping to be updated + NOM_ASSERT(result == 0); + + jdev.close(); + } + } +#endif + +#if 0 + if( Joystick::num_joysticks() > 0 ) { + std::string mapping_str; + JoystickGUID dev_guid0 = Joystick::device_guid(0); + mapping_str = GameController::mapping_string(dev_guid0); + NOM_LOG_INFO( NOM_LOG_CATEGORY_APPLICATION, + "The game controller's mapping string is", + mapping_str ); + } +#endif + + Joystick jdev; + for( auto joystick_index = 0; + joystick_index != Joystick::num_joysticks(); + ++joystick_index ) + { + if( jdev.open(joystick_index) == true ) { + + std::string guid_str = "0x" + jdev.device_guid_string(); + + NOM_LOG_INFO( NOM_LOG_CATEGORY_APPLICATION, "Joystick", + joystick_index, ":", Joystick::name(joystick_index) ); + + NOM_LOG_INFO( NOM_LOG_CATEGORY_APPLICATION, + "\taxes:", jdev.num_axes() ); + NOM_LOG_INFO( NOM_LOG_CATEGORY_APPLICATION, + "\ttrack balls:", jdev.num_track_balls() ); + NOM_LOG_INFO( NOM_LOG_CATEGORY_APPLICATION, + "\tbuttons:", jdev.num_buttons() ); + NOM_LOG_INFO( NOM_LOG_CATEGORY_APPLICATION, + "\tPOV hats:", jdev.num_hats() ); + + NOM_LOG_INFO(NOM_LOG_CATEGORY_APPLICATION, "\tguid:", guid_str); + } + } + for( auto joystick_index = 0; + joystick_index != Joystick::num_joysticks(); + ++joystick_index ) + { + if( GameController::compatible_joystick(joystick_index) == false ) { + NOM_LOG_WARN( NOM_LOG_CATEGORY_APPLICATION, "Joystick", + joystick_index, ":", Joystick::name(joystick_index), + "is not supported by SDL's game controller interface!" ); + } + } + + // ...Initialize of the visual joystick state positions... + + for( auto axis_idx = 0; axis_idx != MAX_AXES; ++axis_idx ) { + this->axis_rectangle_pos[axis_idx] = + nom::alignment_rect( AXIS_RECT_DIMS, Point2i::zero, + WINDOW_RESOLUTION, Anchor::MiddleCenter ); + } + + for( auto button_idx = 0; button_idx != MAX_BUTTONS; ++button_idx ) { + // Defaults to rendering off-screen + this->button_rectangle_pos[button_idx].x = -WINDOW_RESOLUTION.w; + this->button_rectangle_pos[button_idx].y = -WINDOW_RESOLUTION.h; + } + + SDLApp::set_event_handler(this->evt_handler); + + this->input_mapper.set_event_handler(this->evt_handler); + + return true; + } + + int Run() + { + this->window.fill(nom::Color4i::SkyBlue); + + // 1. Events + // 2. Logic + // 3. Render + while( this->running() == true ) { + + nom::Event evt; + while( evt_handler.poll_event(evt) == true ) { + + switch(evt.type) + { + default: break; + + case Event::JOYSTICK_ADDED: + case Event::JOYSTICK_REMOVED: + { + NOM_ASSERT_INVALID_PATH(); + } break; + + case Event::GAME_CONTROLLER_ADDED: + { + // NOTE: Use the most recently added joystick for mapping input + // bindings to + auto device_index = evt.cdevice.id; + this->on_game_controller_add(device_index); + + // IMPORTANT: We must have the actual hardware initialized before + // simulating this event type! + this->test_simulated_events(); + } break; + + case Event::GAME_CONTROLLER_REMOVED: + { + // NOTE: Use the most recently added joystick for mapping input + // bindings to + auto device_index = evt.cdevice.id; + this->on_game_controller_remove(device_index); + + // NOTE: Upon removal, if we can, remap joystick input bindings + // to whatever device that is still available, so we can still + // control this application example with a joystick + if( Joystick::num_joysticks() > 0 ) { + this->on_game_controller_add(0); + } else { + NOM_LOG_INFO( NOM_LOG_CATEGORY_APPLICATION, + "No joystick devices are available; exiting!" ); + this->on_app_quit(evt); + } + } break; + } + } // end inner while + + this->window.update(); + + this->window.fill(nom::Color4i::SkyBlue); + + for( auto axis_idx = 0; axis_idx != MAX_AXES; ++axis_idx ) { + this->render_rectangle( this->axis_rectangle_pos[axis_idx], + AXIS_RECT_DIMS, Color4i::Green ); + } + + for( auto button_idx = 0; button_idx != MAX_BUTTONS; ++button_idx ) { + this->render_rectangle( this->button_rectangle_pos[button_idx], + BUTTON_RECT_DIMS, Color4i::Green ); + } + + } // end outer while + + return NOM_EXIT_SUCCESS; + } + + private: + nom::SearchPath res; + std::string db_filename; + + const std::string APP_NAME = "Game controller events"; + const Size2i WINDOW_RESOLUTION = Size2i(640, 480); + nom::RenderWindow window; + + // Event handling + + EventHandler evt_handler; + nom::InputStateMapper input_mapper; + + nom::event_callback quit_app; + nom::event_callback init_db_bindings; + nom::event_callback x_axis_action; + nom::event_callback y_axis_action; + nom::event_callback button_action; + + // The maximum number of buttons to render + static const nom::size_type MAX_BUTTONS = + GameController::BUTTON_MAX; + + // The maximum number of axes to render + static const nom::size_type MAX_AXES = 2; + + Point2i axis_rectangle_pos[MAX_AXES]; + Point2i button_rectangle_pos[MAX_BUTTONS]; + const Size2i AXIS_RECT_DIMS = Size2i(16, 16); + const Size2i BUTTON_RECT_DIMS = Size2i(16, 16); + + GameControllerEventHandler joystick_evt; + + /// \brief Test the simulation of input events. + void test_simulated_events() + { + JoystickID dev_id = 0; + auto button = nom::GameController::BUTTON_LEFT_SHOULDER; + nom::Event cbutton_ev; + + cbutton_ev = nom::create_game_controller_button_press(dev_id, button); + this->evt_handler.push_event(cbutton_ev); + + cbutton_ev = nom::create_game_controller_button_release(dev_id, button); + this->evt_handler.push_event(cbutton_ev); + } + + void + render_rectangle(const Point2i& pos, const Size2i& dims, const Color4i& color) + { + IntRect rect_bounds; + rect_bounds.set_position(pos); + rect_bounds.set_size(dims); + + auto rect = Rectangle(rect_bounds, color); + rect.draw(this->window); + } + + /// \brief Load the game controller database for automatic joystick + /// layout mapping. + /// + /// \see https://github.com/gabomdq/SDL_GameControllerDB + void initialize_game_controller_db(const std::string& filename) + { + int mappings_count = GameController::load_mapping_file(filename); + if( mappings_count == -1 ) { + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, + "Could not load the game controller database file:", + filename ); + } else { + NOM_LOG_INFO( NOM_LOG_CATEGORY_APPLICATION, + "Added", mappings_count, + "game controller mappings from file:", filename ); + } + } + + /// \brief Event Handler for device removal + void on_game_controller_remove(JoystickIndex dev_index) { + // NOTE: This device index is platform-dependent and cannot be relied on, + // as the order of joysticks change. + auto dev_name = Joystick::name(dev_index); + + // NOTE: The device's id is unique and is what the input mapper relies on + // for identifying joystick events by! + auto dev_id = GameController::device_id(dev_index); + + NOM_LOG_INFO( NOM_LOG_CATEGORY_APPLICATION, + "Removed game controller", dev_name, + "(ID:", dev_id, ")" ); + } + + /// \brief Event Handler for device additions + void on_game_controller_add(JoystickIndex dev_index) { + // NOTE: This device index is platform-dependent and cannot be relied on, + // as the order of joysticks change. + auto dev_name = Joystick::name(dev_index); + + // NOTE: The device's id is unique and is what the input mapper relies on + // for identifying joystick events by! + auto dev_id = GameController::device_id(dev_index); + + if( this->initialize_game_controller_bindings(dev_id) == false ) { + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, + "Could not re-initialize game controller bindings", + "for instance ID", dev_id ); + } else { + NOM_LOG_INFO( NOM_LOG_CATEGORY_APPLICATION, + "Initialized game controller bindings for", + dev_name, "(ID:", dev_id, ")" ); + } + } + + /// \brief Install the input mappings for the new game controller device. + bool initialize_game_controller_bindings(JoystickID dev_id) + { + bool result0 = false; + bool result1 = false; + bool result2 = false; + InputActionMapper cbutton, caxis, key; + nom::InputAction action; + + NOM_ASSERT(dev_id >= 0); + if( dev_id < 0 ) { + // Err -- invalid instance ID + return false; + } + + // ...buttons... + + for( auto button_idx = 0; button_idx != MAX_BUTTONS; ++button_idx ) { + + std::stringstream desc; + desc << "button_" << button_idx; + + auto button = NOM_SCAST(nom::GameController::Button, button_idx); + + action = + nom::GameControllerButtonAction( dev_id, button, + nom::InputState::PRESSED ); + cbutton.insert(desc.str(), action, this->button_action); + + action = + nom::GameControllerButtonAction( dev_id, button, + nom::InputState::RELEASED ); + cbutton.insert(desc.str(), action, this->button_action); + } + + // ...left thumb axis... + + action = + nom::GameControllerAxisAction(dev_id, nom::GameController::AXIS_LEFT_X); + caxis.insert("left_thumb_left", action, this->x_axis_action); + + action = + nom::GameControllerAxisAction(dev_id, nom::GameController::AXIS_LEFT_X); + caxis.insert("left_thumb_right", action, this->x_axis_action); + + action = + nom::GameControllerAxisAction(dev_id, nom::GameController::AXIS_LEFT_Y); + caxis.insert("left_thumb_up", action, this->y_axis_action); + + action = + nom::GameControllerAxisAction(dev_id, nom::GameController::AXIS_LEFT_Y); + caxis.insert("left_thumb_down", action, this->y_axis_action); + + action = + nom::GameControllerAxisAction(dev_id, GameController::AXIS_TRIGGER_LEFT); + caxis.insert("left_trigger_axis", action, this->x_axis_action); + + // ...right thumb axis... + + action = + nom::GameControllerAxisAction(dev_id, nom::GameController::AXIS_RIGHT_X); + caxis.insert("right_thumb_left", action, this->x_axis_action); + + action = + nom::GameControllerAxisAction(dev_id, nom::GameController::AXIS_RIGHT_X); + caxis.insert("right_thumb_right", action, this->x_axis_action); + + action = + nom::GameControllerAxisAction(dev_id, nom::GameController::AXIS_RIGHT_Y); + caxis.insert("right_thumb_up", action, this->y_axis_action); + + action = + nom::GameControllerAxisAction(dev_id, nom::GameController::AXIS_RIGHT_Y); + caxis.insert("right_thumb_down", action, this->y_axis_action); + + action = + nom::GameControllerAxisAction(dev_id, nom::GameController::AXIS_TRIGGER_RIGHT); + caxis.insert("right_trigger_axis", action, this->x_axis_action); + + this->input_mapper.erase("game_controller_buttons"); + result0 = + this->input_mapper.insert("game_controller_buttons", cbutton, true); + + this->input_mapper.erase("game_controller_axes"); + result1 = + this->input_mapper.insert("game_controller_axes", caxis, true); + + this->input_mapper.erase("keyboard"); + result2 = + this->input_mapper.insert("keyboard", key, true); + + return( result0 == true && result1 == true && result2 == true); + } +}; // end class App +*/ +int main(nom::int32 argc, char* argv[]) +{ + // Fatal error; if we are not able to complete this step, it means that + // we probably cannot rely on our resource paths! + if( nom::init(argc, argv) == false ) { + NOM_LOG_CRIT( NOM_LOG_CATEGORY_APPLICATION, + "Could not initialize nomlib." ); + return NOM_EXIT_FAILURE; + } + + atexit(nom::quit); + + nom::SDL2Logger::set_logging_priority( NOM_LOG_CATEGORY_EVENT, + NOM_LOG_PRIORITY_INFO ); + + // Enable event handler queue debugging statistics + nom::set_hint(NOM_EVENT_QUEUE_STATISTICS, "1"); +/* + App app; + + if( app.on_init() == false ) { + NOM_LOG_CRIT( NOM_LOG_CATEGORY_APPLICATION, + "Could not initialize application." ); + return NOM_EXIT_FAILURE; + } + + return app.Run(); + + // ...Goodbye cruel world! +*/ +} diff --git a/examples/icon.png b/examples/icon.png new file mode 100644 index 00000000..4a28022f Binary files /dev/null and b/examples/icon.png differ diff --git a/examples/joystick_events.cpp b/examples/joystick_events.cpp new file mode 100644 index 00000000..d1d49c68 --- /dev/null +++ b/examples/joystick_events.cpp @@ -0,0 +1,578 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014, 2015 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#include +#include +#include +#include + +using namespace nom; + +class App: public nom::SDLApp +{ + public: + App() + { + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE, NOM_LOG_PRIORITY_VERBOSE); + + // ...Initialize the input mapper callbacks... + + this->quit_app = nom::event_callback( [=](const nom::Event& evt) { + this->on_app_quit(evt); + }); + + this->x_axis_action = nom::event_callback( [=](const nom::Event& evt) { + + int32 axis = evt.jaxis.axis; + int32 axis_value = evt.jaxis.value; + Point2i rect_pos; + + if( axis_value > nom::JOYSTICK_LEFT_THUMB_DEAD_ZONE || + axis_value < -nom::JOYSTICK_LEFT_THUMB_DEAD_ZONE ) + { + // ...proportionate transform of the joystick axis && screen-space + // coordinate systems + + rect_pos.x = axis_value + 32768; + + // Account for circular dead zone + if( axis_value > nom::JOYSTICK_LEFT_THUMB_DEAD_ZONE ) { + rect_pos.x -= nom::JOYSTICK_LEFT_THUMB_DEAD_ZONE; + } else if( axis_value < -nom::JOYSTICK_LEFT_THUMB_DEAD_ZONE ) { + rect_pos.x += nom::JOYSTICK_LEFT_THUMB_DEAD_ZONE; + } + + rect_pos.x *= WINDOW_RESOLUTION.w; + rect_pos.x /= 65535; + + if( rect_pos.x < 0 ) { + rect_pos.x = 0; + } else if( rect_pos.x > WINDOW_RESOLUTION.w - AXIS_RECT_DIMS.w ) { + rect_pos.x = WINDOW_RESOLUTION.w - AXIS_RECT_DIMS.w; + } + + if( axis < 2 ) { + this->axis_rectangle_pos[0].x = rect_pos.x; + } else { + this->axis_rectangle_pos[1].x = rect_pos.x; + } + } else { + // Resting position (we're in the dead zone) + if( axis < 2 ) { + this->axis_rectangle_pos[0].x = + (WINDOW_RESOLUTION.w - AXIS_RECT_DIMS.w) / 2; + } else { + this->axis_rectangle_pos[1].x = + (WINDOW_RESOLUTION.w - AXIS_RECT_DIMS.w) / 2; + } + } + }); + + this->y_axis_action = nom::event_callback( [=](const nom::Event& evt) { + + int32 axis = evt.jaxis.axis; + int32 axis_value = evt.jaxis.value; + Point2i rect_pos; + + if( axis_value > nom::JOYSTICK_LEFT_THUMB_DEAD_ZONE || + axis_value < -nom::JOYSTICK_LEFT_THUMB_DEAD_ZONE ) + { + // ...proportionate transform of the joystick axis && screen-space + // coordinate systems + + rect_pos.y = axis_value + 32768; + + // Account for circular dead zone + if( axis_value > nom::JOYSTICK_LEFT_THUMB_DEAD_ZONE ) { + rect_pos.y -= nom::JOYSTICK_LEFT_THUMB_DEAD_ZONE; + } else if( axis_value < -nom::JOYSTICK_LEFT_THUMB_DEAD_ZONE ) { + rect_pos.y += nom::JOYSTICK_LEFT_THUMB_DEAD_ZONE; + } + + rect_pos.y *= WINDOW_RESOLUTION.h; + rect_pos.y /= 65535; + + if( rect_pos.y < 0 ) { + rect_pos.y = 0; + } else if( rect_pos.y > WINDOW_RESOLUTION.h - AXIS_RECT_DIMS.h ) { + rect_pos.y = WINDOW_RESOLUTION.h - AXIS_RECT_DIMS.h; + } + + if( axis < 2 ) { + this->axis_rectangle_pos[0].y = rect_pos.y; + } else { + this->axis_rectangle_pos[1].y = rect_pos.y; + } + } else { + // Resting position (we're in the dead zone) + if( axis < 2 ) { + this->axis_rectangle_pos[0].y = + (WINDOW_RESOLUTION.h - AXIS_RECT_DIMS.h) / 2; + } else { + this->axis_rectangle_pos[1].y = + (WINDOW_RESOLUTION.h - AXIS_RECT_DIMS.h) / 2; + } + } + }); + + this->hat_action = nom::event_callback( [=](const nom::Event& evt) { + + // int32 hat = evt.jhat.hat; + int32 hat_value = evt.jhat.value; + + Point2i& rect_pos = this->hat_rectangle_pos; + + // Default position + rect_pos = + nom::alignment_rect( HAT_RECT_DIMS, Point2i::zero, + WINDOW_RESOLUTION, Anchor::MiddleCenter ); + + if( hat_value & Joystick::HAT_UP ) { + rect_pos.y = 0; + } else if( hat_value & Joystick::HAT_DOWN ) { + rect_pos.y = WINDOW_RESOLUTION.h - HAT_RECT_DIMS.h; + } + + if( hat_value & Joystick::HAT_LEFT ) { + rect_pos.x = 0; + } else if( evt.jhat.value & Joystick::HAT_RIGHT ) { + rect_pos.x = WINDOW_RESOLUTION.w - HAT_RECT_DIMS.w; + } + }); + + this->button_action = nom::event_callback( [=](const nom::Event& evt) { + + int32 button = evt.jbutton.button; + int32 button_state = evt.jbutton.state; + Point2i rect_pos; + + if( button_state == InputState::PRESSED ) { + auto spacing = BUTTON_RECT_DIMS.w * 2; + auto origin = BUTTON_RECT_DIMS.w; + rect_pos.x = + origin + (spacing * button); + rect_pos.y = WINDOW_RESOLUTION.h - BUTTON_RECT_DIMS.h * 2; + } else { + // Render off-screen by default + rect_pos.x = -WINDOW_RESOLUTION.w; + rect_pos.y = -WINDOW_RESOLUTION.h; + } + + if( button < MAX_BUTTONS ) { + this->button_rectangle_pos[button] = rect_pos; + } + }); + } + + ~App() + { + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE, NOM_LOG_PRIORITY_VERBOSE); + } + + bool on_init() + { + nom::uint32 window_flags = 0; + + if( this->window.create( APP_NAME, WINDOW_RESOLUTION, + window_flags ) == false ) + { + return false; + } + + NOM_ASSERT( this->evt_handler.joystick_event_type() == + EventHandler::NO_EVENT_HANDLER ); + + if( evt_handler.enable_joystick_polling() == false ) { + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, + "Could not initialize joystick subsystem: ", + nom::error() ); + return false; + } + + NOM_ASSERT( this->evt_handler.joystick_event_type() == + EventHandler::SDL_JOYSTICK_EVENT_HANDLER ); + + // NOTE: Ensure that closing the joystick device more than once does + // not crash with a double-free memory violation in SDL_JoystickClose + { + Joystick jdev; + for( auto joystick_index = 0; + joystick_index != Joystick::num_joysticks(); + ++joystick_index ) + { + if( jdev.open(joystick_index) == true ) { + jdev.close(); + jdev.close(); + jdev.close(); + } + } + } + + Joystick jdev; + for( auto joystick_index = 0; + joystick_index != Joystick::num_joysticks(); + ++joystick_index ) + { + if( jdev.open(joystick_index) == true ) { + + std::string guid_str = "0x" + jdev.device_guid_string(); + + NOM_LOG_INFO( NOM_LOG_CATEGORY_APPLICATION, "Joystick", + joystick_index, ":", Joystick::name(joystick_index) ); + + NOM_LOG_INFO( NOM_LOG_CATEGORY_APPLICATION, + "\taxes:", jdev.num_axes() ); + NOM_LOG_INFO( NOM_LOG_CATEGORY_APPLICATION, + "\ttrack balls:", jdev.num_track_balls() ); + NOM_LOG_INFO( NOM_LOG_CATEGORY_APPLICATION, + "\tbuttons:", jdev.num_buttons() ); + NOM_LOG_INFO( NOM_LOG_CATEGORY_APPLICATION, + "\tPOV hats:", jdev.num_hats() ); + + NOM_LOG_INFO(NOM_LOG_CATEGORY_APPLICATION, "\tguid:", guid_str); + } + } + + // ...Initialize of the visual joystick state positions... + + for( auto axis_idx = 0; axis_idx != MAX_AXES; ++axis_idx ) { + this->axis_rectangle_pos[axis_idx] = + nom::alignment_rect( AXIS_RECT_DIMS, Point2i::zero, + WINDOW_RESOLUTION, Anchor::MiddleCenter ); + } + + this->hat_rectangle_pos = + nom::alignment_rect( HAT_RECT_DIMS, Point2i::zero, + WINDOW_RESOLUTION, Anchor::MiddleCenter ); + + for( auto button_idx = 0; button_idx != MAX_BUTTONS; ++button_idx ) { + + // Defaults to rendering off-screen + this->button_rectangle_pos[button_idx].x = -WINDOW_RESOLUTION.w; + this->button_rectangle_pos[button_idx].y = -WINDOW_RESOLUTION.h; + } + + SDLApp::set_event_handler(this->evt_handler); + + this->input_mapper.set_event_handler(this->evt_handler); + + return true; + } + + int Run() + { + // 1. Events + // 2. Logic + // 3. Render + + this->window.fill(nom::Color4i::SkyBlue); + + while( this->running() == true ) { + + nom::Event evt; + while( evt_handler.poll_event(evt) == true ) { + + switch(evt.type) + { + default: break; + + case Event::JOYSTICK_ADDED: + { + // NOTE: Use the most recently added joystick for mapping input + // bindings to + auto device_index = evt.jdevice.id; + this->on_joystick_add(device_index); + + // IMPORTANT: We must have the actual hardware initialized before + // simulating this event type! + this->test_simulated_events(); + } break; + + case Event::JOYSTICK_REMOVED: + { + // NOTE: Upon removal, if we can, remap joystick input bindings + // to whatever device that is still available, so we can still + // control this application example with a joystick + if( Joystick::num_joysticks() > 0 ) { + this->on_joystick_add(0); + } else { + NOM_LOG_INFO( NOM_LOG_CATEGORY_APPLICATION, + "No joystick devices are available; exiting!" ); + this->on_app_quit(evt); + } + + } break; + + case Event::GAME_CONTROLLER_ADDED: + case Event::GAME_CONTROLLER_REMOVED: + case Event::GAME_CONTROLLER_REMAPPED: + { + NOM_ASSERT_INVALID_PATH(); + } break; + } + } // end inner while + + this->window.update(); + + this->window.fill(nom::Color4i::SkyBlue); + + for( auto axis_idx = 0; axis_idx != MAX_AXES; ++axis_idx ) { + this->render_rectangle( this->axis_rectangle_pos[axis_idx], + AXIS_RECT_DIMS, Color4i::Green ); + } + + this->render_rectangle( this->hat_rectangle_pos, HAT_RECT_DIMS, + Color4i::Blue ); + + for( auto button_idx = 0; button_idx != MAX_BUTTONS; ++button_idx ) { + this->render_rectangle( this->button_rectangle_pos[button_idx], + BUTTON_RECT_DIMS, Color4i::Green ); + } + + } // end outer while + + return NOM_EXIT_SUCCESS; + } + + private: + const std::string APP_NAME = "Joystick events"; + const Size2i WINDOW_RESOLUTION = Size2i(640, 480); + nom::RenderWindow window; + + // Event handling + EventHandler evt_handler; + nom::InputStateMapper input_mapper; + + nom::event_callback quit_app; + nom::event_callback x_axis_action; + nom::event_callback y_axis_action; + nom::event_callback hat_action; + nom::event_callback button_action; + + // The maximum number of buttons to render + static const nom::size_type MAX_BUTTONS = Joystick::BUTTON_MAX; + + // The maximum number of axes to render + static const nom::size_type MAX_AXES = 2; + + Point2i axis_rectangle_pos[MAX_AXES]; + Point2i hat_rectangle_pos = Point2i::zero; + Point2i button_rectangle_pos[MAX_BUTTONS]; + const Size2i AXIS_RECT_DIMS = Size2i(16, 16); + const Size2i HAT_RECT_DIMS = Size2i(8, 8); + const Size2i BUTTON_RECT_DIMS = Size2i(16, 16); + + /// \brief Test the simulation of input events. + void test_simulated_events() + { + JoystickID dev_id = 0; + nom::Event jbutton_ev; + + auto button = nom::Joystick::BUTTON_ZERO; + jbutton_ev = nom::create_joystick_button_press(dev_id, button); + this->evt_handler.push_event(jbutton_ev); + + jbutton_ev = nom::create_joystick_button_release(dev_id, button); + this->evt_handler.push_event(jbutton_ev); + } + + void + render_rectangle(const Point2i& pos, const Size2i& dims, const Color4i& color) + { + IntRect rect_bounds; + rect_bounds.set_position(pos); + rect_bounds.set_size(dims); + + auto rect = Rectangle(rect_bounds, color); + rect.draw(this->window); + } + + /// \brief Initialize joystick input bindings + void on_joystick_add(JoystickIndex dev_index) + { + // NOTE: This device index is platform-dependent and cannot be relied on, + // as the order of joysticks change. + auto dev_name = Joystick::name(dev_index); + + // NOTE: The device's id is unique and is what the input mapper relies on + // for identifying joystick events by! + auto dev_id = Joystick::device_id(dev_index); + + if( this->initialize_joystick_bindings(dev_id) == false ) { + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, + "Could not re-initialize joystick bindings", + "for instance ID", dev_id ); + } else { + NOM_LOG_INFO( NOM_LOG_CATEGORY_APPLICATION, + "Initialized joystick bindings for", + dev_name, "(ID:", dev_id, ")" ); + } + } + + /// \brief Install the input mappings for the new joystick device. + bool + initialize_joystick_bindings(JoystickID dev_id) + { + bool result0 = false; + bool result1 = false; + bool result2 = false; + bool result3 = false; + InputActionMapper key, jbutton, jaxis, jhat; + + NOM_ASSERT(dev_id >= 0); + if( dev_id < 0 ) { + // Err -- invalid instance ID + return false; + } + + nom::InputAction action; + + action = + nom::KeyboardAction(Event::KEY_PRESS, SDLK_ESCAPE); + key.insert("quit_app", action, this->quit_app); + + // ...Buttons... + + for( auto button_idx = 0; button_idx != MAX_BUTTONS; ++button_idx ) { + + std::stringstream desc; + desc << "button_" << button_idx; + + action = + nom::JoystickButtonAction( dev_id, button_idx, + nom::InputState::PRESSED ); + jbutton.insert(desc.str(), action, this->button_action); + + action = + nom::JoystickButtonAction( dev_id, button_idx, + nom::InputState::RELEASED ); + jbutton.insert(desc.str(), action, this->button_action); + } + + // ...left thumb axis... + + action = nom::JoystickAxisAction(dev_id, nom::Joystick::AXIS_ZERO); + jaxis.insert("left_thumb_left", action, this->x_axis_action); + + action = nom::JoystickAxisAction(dev_id, nom::Joystick::AXIS_ZERO); + jaxis.insert("left_thumb_right", action, this->x_axis_action); + + action = nom::JoystickAxisAction(dev_id, nom::Joystick::AXIS_ONE); + jaxis.insert("left_thumb_up", action, this->y_axis_action); + + action = nom::JoystickAxisAction(dev_id, nom::Joystick::AXIS_ONE); + jaxis.insert("left_thumb_down", action, this->y_axis_action); + + // ...right thumb axis... + + action = nom::JoystickAxisAction(dev_id, nom::Joystick::AXIS_TWO); + jaxis.insert("right_thumb_left", action, this->x_axis_action); + + action = nom::JoystickAxisAction(dev_id, nom::Joystick::AXIS_TWO); + jaxis.insert("right_thumb_right", action, this->x_axis_action); + + action = nom::JoystickAxisAction(dev_id, nom::Joystick::AXIS_THREE); + jaxis.insert("right_thumb_up", action, this->y_axis_action); + + action = nom::JoystickAxisAction(dev_id, nom::Joystick::AXIS_THREE); + jaxis.insert("right_thumb_down", action, this->y_axis_action); + + // ...POV hats... + + action = nom::JoystickHatAction( dev_id, + nom::Joystick::HAT_ZERO, + nom::Joystick::HAT_LEFT ); + jhat.insert("hat_left", action, this->hat_action); + + action = nom::JoystickHatAction( dev_id, + nom::Joystick::HAT_ZERO, + nom::Joystick::HAT_RIGHT ); + jhat.insert("hat_right", action, this->hat_action); + + action = nom::JoystickHatAction( dev_id, + nom::Joystick::HAT_ZERO, + nom::Joystick::HAT_UP ); + jhat.insert("hat_up", action, this->hat_action); + + action = nom::JoystickHatAction( dev_id, + nom::Joystick::HAT_ZERO, + nom::Joystick::HAT_DOWN ); + jhat.insert("hat_down", action, this->hat_action); + + this->input_mapper.erase("joystick_buttons"); + result0 = + this->input_mapper.insert("joystick_buttons", jbutton, true); + + this->input_mapper.erase("joystick_axes"); + result1 = + this->input_mapper.insert("joystick_axes", jaxis, true); + + this->input_mapper.erase("joystick_hats"); + result2 = + this->input_mapper.insert("joystick_hats", jhat, true); + + this->input_mapper.erase("keyboard"); + result3 = + this->input_mapper.insert("keyboard", key, true); + + return( result0 == true && result1 == true && result2 == true && + result3 == true ); + } +}; // end class App + +int main(nom::int32 argc, char* argv[]) +{ + // Fatal error; if we are not able to complete this step, it means that + // we probably cannot rely on our resource paths! + if( nom::init(argc, argv) == false ) { + NOM_LOG_CRIT( NOM_LOG_CATEGORY_APPLICATION, + "Could not initialize nomlib." ); + return NOM_EXIT_FAILURE; + } + + atexit(nom::quit); + + // Show add and removal events + nom::SDL2Logger::set_logging_priority( NOM_LOG_CATEGORY_EVENT, + NOM_LOG_PRIORITY_INFO ); + + // Enable event handler queue debugging statistics + nom::set_hint(NOM_EVENT_QUEUE_STATISTICS, "1"); + + App app; + + if( app.on_init() == false ) { + NOM_LOG_CRIT( NOM_LOG_CATEGORY_APPLICATION, + "Could not initialize application." ); + return NOM_EXIT_FAILURE; + } + + return app.Run(); + + // ...Goodbye cruel world! +} diff --git a/examples/renderfont.cpp b/examples/renderfont.cpp index 6830e5e7..2ce4f559 100644 --- a/examples/renderfont.cpp +++ b/examples/renderfont.cpp @@ -37,6 +37,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +using namespace nom; + /// Name of our application. const std::string APP_NAME = "renderfont"; @@ -46,6 +48,8 @@ const nom::int32 WINDOW_WIDTH = 640; /// Height, in pixels, of our effective rendering surface. const nom::int32 WINDOW_HEIGHT = 480; +const auto WINDOW_RESOLUTION = Size2i(WINDOW_WIDTH, WINDOW_HEIGHT); + /// Relative filename path to saved screenshot example /// /// Default path should resolve to the same directory as the app example @@ -80,6 +84,8 @@ struct AppFlags int pt_size = nom::DEFAULT_FONT_SIZE; }; +typedef std::vector StringList; + /// \brief Text rendering with nom::Font class FontRenderingApp: public nom::SDLApp { @@ -120,7 +126,7 @@ class FontRenderingApp: public nom::SDLApp NOM_LOG_INFO ( NOM, "Could not set scale quality." ); } */ - if ( this->window.create ( APP_NAME, WINDOW_WIDTH, WINDOW_HEIGHT, window_flags ) == false ) + if ( this->window.create (APP_NAME, WINDOW_RESOLUTION, window_flags) == false ) { return false; } @@ -135,6 +141,8 @@ class FontRenderingApp: public nom::SDLApp // return false; // } + SDLApp::set_event_handler(this->evt_handler); + nom::File fp; nom::Path p; // Used by nom::BMFont interface; this sets the relative file root for @@ -157,11 +165,14 @@ class FontRenderingApp: public nom::SDLApp // 3. Render nom::Event ev; - while ( this->running() == true ) - { - while( this->poll_event( ev ) ) - { - this->on_event( ev ); + while( this->running() == true ) { + + nom::Event evt; + while( this->evt_handler.poll_event(evt) == true ) { + // NOTE: Pending events will be handled by the event listeners that + // were given an EventHandler object via ::set_event_handler. + // + // Additional event processing done in here is still OK, too. } this->window.update(); @@ -197,12 +208,14 @@ class FontRenderingApp: public nom::SDLApp private: AppFlags opts; - /// \brief Event handler for key down actions. - /// - /// Implements the nom::Input::on_key_down method. - void on_key_down( const nom::Event& ev ) override + /// \brief The default event handler for input events. + void on_input_event(const nom::Event& ev) override { - switch ( ev.key.sym ) + if( ev.type != Event::KEY_PRESS ) { + return; + } + + switch(ev.key.sym) { default: break; @@ -245,6 +258,8 @@ class FontRenderingApp: public nom::SDLApp /// Window handle nom::RenderWindow window; + nom::EventHandler evt_handler; + /// Interval at which we refresh the frames per second counter nom::Timer update; @@ -264,8 +279,6 @@ class FontRenderingApp: public nom::SDLApp return false; } - this->font->set_font_kerning(this->opts.use_kerning); - // TODO: support additional hinting types; see TrueTypeFont.hpp // FIXME: Not working; methinks the problem might be in TrueTypeFont // class, although it would not hurt to verify SDL2_ttf functionality @@ -279,13 +292,13 @@ class FontRenderingApp: public nom::SDLApp this->rendered_text.set_text(this->opts.text); this->rendered_text.set_text_size(this->opts.pt_size); this->rendered_text.set_style(this->opts.style); + this->rendered_text.set_text_kerning(this->opts.use_kerning); // TODO: //this->rendered_text.set_color( nom::Color4i(195,209,228) ); - nom::set_alignment( &this->rendered_text, - this->window.size(), - nom::Anchor::MiddleCenter ); + nom::set_alignment( &this->rendered_text, this->rendered_text.position(), + this->window.size(), nom::Anchor::MiddleCenter ); return true; } @@ -294,6 +307,7 @@ class FontRenderingApp: public nom::SDLApp // ./renderfont --text 'Hello, World!' ~/Projects/nomlib.git/Resources/tests/graphics/BitmapFontTest/VIII.png 72 // ./renderfont --text 'Hello, World!' ~/Projects/nomlib.git/Resources/tests/graphics/TrueTypeFontTest/OpenSans-Regular.ttf 72 // ./renderfont --text 'Hello, World!' ~/Projects/nomlib.git/Resources/tests/graphics/BMFontTest/gameover.fnt 72 +// ./renderfont --text 'WAV' --no-kerning ~/Library/Fonts/OpenSans-Regular.ttf 72 nom::int32 main ( nom::int32 argc, char* argv[] ) { using namespace TCLAP; @@ -378,7 +392,7 @@ nom::int32 main ( nom::int32 argc, char* argv[] ) opts.text = text_arg.getValue(); } - nom::StringList args = font_args.getValue(); + StringList args = font_args.getValue(); int pt_size_arg = 0; nom::size_type next_to_last = args.size() - 1; diff --git a/examples/sprites.cpp b/examples/sprites.cpp deleted file mode 100644 index 1ecfb03a..00000000 --- a/examples/sprites.cpp +++ /dev/null @@ -1,312 +0,0 @@ -/****************************************************************************** - - nomlib - C++11 cross-platform game engine - -Copyright (c) 2013, 2014 Jeffrey Carpenter -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -******************************************************************************/ -#include - -// Pubic nomlib interface headers -#include -#include -#include - -const std::string APP_NAME = "Sprites"; -const nom::int32 WINDOW_WIDTH = 640; -const nom::int32 WINDOW_HEIGHT = 480; - -const std::string RESOURCE_ICON = "icon.png"; - -const std::string RESOURCE_SPRITE_SHEET = "cursors.json"; - -/// \brief Relative filename path to saved screen shot example -/// -/// Default path should resolve to the same directory as the app example -/// executable -const std::string OUTPUT_SCREENSHOT_FILENAME = "screenshot.png"; - -class App: public nom::SDLApp -{ - public: - App( nom::int32 argc, char* argv[] ) : - SDLApp(OSX_DISABLE_MINIMIZE_ON_LOSS_FOCUS | OSX_DISABLE_FULLSCREEN_SPACES), - sprite_angle ( -90.0f ) - { - NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE, nom::LogPriority::NOM_LOG_PRIORITY_INFO); - } - - ~App() - { - NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE, nom::LogPriority::NOM_LOG_PRIORITY_INFO); - } - - bool on_init() - { - nom::uint32 window_flags = SDL_WINDOW_RESIZABLE; - nom::uint32 render_flags = SDL_RENDERER_ACCELERATED; - int render_driver = -1; - - nom::SearchPath res; - std::string res_file = "sprites.json"; - - // Determine our resources path based on several possible locations; - // this is dependent upon the build environment - if( res.load_file(res_file,"resources") == false ) - { - NOM_LOG_CRIT( NOM_LOG_CATEGORY_APPLICATION, - "Could not determine the resource path for", res_file ); - return false; - } - - if( nom::set_hint ( SDL_HINT_RENDER_VSYNC, "0" ) == false ) - { - NOM_LOG_INFO ( NOM, "Could not disable vertical refresh." ); - } - - if( nom::set_hint( SDL_HINT_RENDER_SCALE_QUALITY, "nearest" ) == false ) - { - NOM_LOG_INFO( NOM, "Could not set scale quality to " + std::string ( "nearest" ) ); - } - - if( this->window.create( APP_NAME, WINDOW_WIDTH, WINDOW_HEIGHT, window_flags, render_driver, render_flags ) == false ) - { - return false; - } - - if( this->window.set_window_icon( res.path() + RESOURCE_ICON ) == false ) - { - nom::DialogMessageBox( APP_NAME, - "Could not load window icon: " + - res.path() + RESOURCE_ICON ); - return false; - } - - // Allow for automatic rescaling of the output window based on aspect - // ratio (i.e.: handle fullscreen resizing); this will use letterboxing - // when the aspect ratio is greater than what is available, or side-bars - // when the aspect ratio is less than. - this->window.set_logical_size( this->window.size() ); - - // Use no pixel unit scaling; this gives us one to one pixel ratio - this->window.set_scale( nom::Point2f(1,1) ); - - nom::SpriteSheet sprite_frames; - - // Load a sprite sheet, using the sheet_filename as the base path to load - // the image file from disk - if( sprite_frames.load_file(res.path() + RESOURCE_SPRITE_SHEET) == false ) { - nom::DialogMessageBox( APP_NAME, - "Could not load sprite sheet: " + - res.path() + RESOURCE_SPRITE_SHEET ); - return false; - } - - if( this->sprite_tex.load( res.path() + sprite_frames.sheet_filename(), false, nom::Texture::Access::Streaming ) == false ) - { - nom::DialogMessageBox( APP_NAME, - "Could not load sprite texture: " + - res.path() + sprite_frames.sheet_filename() ); - return false; - } - - this->sprite.set_texture(sprite_tex); - this->sprite.set_sprite_sheet(sprite_frames); - this->sprite_tex.resize(nom::Texture::ResizeAlgorithm::scale2x); - this->sprite.set_frame(1); // Left-pointing cursor hand - - // Sharing the same texture for the animated sprite instead of loading - // another texture source would be OK, too, if we didn't care about - // preserving the original scale of the sprite here for testing purposes. - // this->ani_sprite.set_texture( *this->sprite_tex.clone() ); - if( this->ani_sprite_tex.load( res.path() + sprite_frames.sheet_filename() ) == false ) - { - nom::DialogMessageBox( APP_NAME, - "Could not load sprite texture: " + - res.path() + sprite_frames.sheet_filename() ); - return false; - } - - this->ani_sprite.set_texture(this->ani_sprite_tex); - // Use the same sprite sheet source for the animated sprite - this->ani_sprite.set_sprite_sheet(sprite_frames); - - nom::Point2i sprite_offset, ani_sprite_offset; - sprite_offset.x = (WINDOW_WIDTH/2 - this->sprite.size().w)/2; - sprite_offset.y = (WINDOW_HEIGHT - this->sprite.size().h)/2; - NOM_DUMP(this->sprite.size().w); // 52 - NOM_DUMP(this->sprite.size().h); // 32 - ani_sprite_offset.x = WINDOW_WIDTH/2 + this->sprite.size().w; - ani_sprite_offset.y = (WINDOW_HEIGHT - this->sprite.size().h)/2; - - this->sprite.set_position(sprite_offset); - this->ani_sprite.set_position(ani_sprite_offset); - - return true; - } // onInit - - nom::int32 on_run() - { - this->update.start(); - this->fps.start(); - - // 1. Events - // 2. Logic - // 3. Render - - nom::Event ev; - while ( this->running() == true ) - { - while( this->poll_event( ev ) ) - { - this->on_event( ev ); - } - - this->ani_sprite.play(); - - this->window.update(); - this->fps.update(); - - // Refresh the frames per second at 1 second intervals - if( this->update.ticks() > 1000 ) { - if( this->show_fps() == true ) { - this->window.set_window_title( APP_NAME + " - " + this->fps.asString() + ' ' + "fps" ); - } - else { - this->window.set_window_title( APP_NAME + " [" + std::to_string(this->window.window_id()) + "]" + " - " + "Display" + ' ' + std::to_string ( this->window.window_display_id() ) ); - } - - this->update.restart(); - } // end refresh cycle - - // Compute rotation angle to pass to renderer - this->sprite_angle += 4.0f; - if ( this->sprite_angle > 360.0f ) this->sprite_angle -= 360.0f; - - this->window.fill ( nom::Color4i::SkyBlue ); - this->sprite.draw ( this->window, this->sprite_angle ); - this->ani_sprite.draw ( this->window ); - - } // end while SDLApp::running() is true - - return NOM_EXIT_SUCCESS; - } // Run - - private: - /// \brief Event handler for key down actions. - /// - /// Implements the nom::Input::on_key_down method. - void on_key_down( const nom::Event& ev ) - { - switch ( ev.key.sym ) - { - default: break; - - // Use inherited SDLApp::on_app_quit() method -- you may also provide - // your own event handler for this. - case SDLK_ESCAPE: - case SDLK_q: this->on_app_quit(ev); break; - - case SDLK_BACKSLASH: - { - if ( this->toggle_fps() ) - { - // Stub for doing something cool here - } - else - { - // Stub for doing something cool here - } - break; - } - - case SDLK_F1: - { - if( this->window.save_screenshot(OUTPUT_SCREENSHOT_FILENAME) == false ) - { - nom::DialogMessageBox( APP_NAME, "Could not save screenshot"); - break; - } - break; - } - - case SDLK_f: - { - this->window.toggle_fullscreen(); - break; - } // end SDLK_f - } // end switch key - } // onKeyDown - - private: - /// Window handles - nom::RenderWindow window; - - /// Interval at which we refresh the frames per second counter - nom::Timer update; - - /// Timer for tracking frames per second - nom::FPS fps; - - /// Our spiffy sprites - nom::Texture sprite_tex; - nom::Texture ani_sprite_tex; - nom::SpriteBatch sprite; - double sprite_angle; - nom::AnimatedSprite ani_sprite; -}; // class App - -nom::int32 main( nom::int32 argc, char* argv[] ) -{ - // FIXME: This must be done before calling nom::init because of a - // dependency on SDL's video subsystem being initialized first. - // nom::init_third_party should be made explicit (by the end-user). - if( nom::set_hint( SDL_HINT_VIDEO_MAC_FULLSCREEN_SPACES, "0" ) == false ) - { - NOM_LOG_INFO( NOM_LOG_CATEGORY_APPLICATION, - "Could not disable Spaces support." ); - } - - // nom::init sets the working directory to this executable's directory - // path - if( nom::init ( argc, argv ) == false ) - { - nom::DialogMessageBox ( APP_NAME, "Could not initialize nomlib." ); - exit ( NOM_EXIT_FAILURE ); - } - - atexit(nom::quit); - - App game( argc, argv ); - - if ( game.on_init() == false ) - { - nom::DialogMessageBox ( APP_NAME, "Could not initialize application." ); - return NOM_EXIT_FAILURE; - } - - return game.on_run(); - - // ...Goodbye cruel world..! -} diff --git a/include/nomlib/audio/NullMusic.hpp b/include/nomlib/actions.hpp similarity index 61% rename from include/nomlib/audio/NullMusic.hpp rename to include/nomlib/actions.hpp index 1b661dd0..d74b13b6 100644 --- a/include/nomlib/audio/NullMusic.hpp +++ b/include/nomlib/actions.hpp @@ -26,30 +26,31 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ -#ifndef NOMLIB_AUDIO_NULL_MUSIC_HPP -#define NOMLIB_AUDIO_NULL_MUSIC_HPP - -#include "nomlib/config.hpp" -#include "nomlib/audio/NullSoundSource.hpp" - -namespace nom { - -class NullMusic: public NullSoundSource -{ - public: - NullMusic( void ); - virtual ~NullMusic( void ); - - void setBuffer( const ISoundBuffer& copy ); - - void Play( void ); - void Stop( void ); - void Pause( void ); - void togglePause( void ); - - void fadeOut( float seconds ); -}; - -} // namespace nom +#ifndef NOMLIB_ACTIONS_HPP +#define NOMLIB_ACTIONS_HPP + +// Public header file + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include #endif // include guard defined diff --git a/include/nomlib/actions/ActionPlayer.hpp b/include/nomlib/actions/ActionPlayer.hpp new file mode 100644 index 00000000..126c05aa --- /dev/null +++ b/include/nomlib/actions/ActionPlayer.hpp @@ -0,0 +1,258 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#ifndef NOMLIB_ACTIONS_ACTION_PLAYER_HPP +#define NOMLIB_ACTIONS_ACTION_PLAYER_HPP + +#include +#include +#include +#include +#include + +#include "nomlib/config.hpp" + +namespace nom { + +// Forward declarations +class IActionObject; +class DispatchQueue; + +typedef std::function action_callback_func; + +/// \brief Interface for running and controlling action flow +class ActionPlayer +{ + public: + typedef ActionPlayer self_type; + + /// \see ::actions_running, ::cancel_actions + typedef std::vector action_names; + + /// \brief The status of the player. + enum State + { + RUNNING = 0, + PAUSED, + STOPPED, + }; + + ActionPlayer(); + + ~ActionPlayer(); + + /// \brief Disabled copy constructor. + ActionPlayer(const ActionPlayer& rhs) = delete; + + /// \brief Disabled copy assignment operator. + ActionPlayer& operator =(const ActionPlayer& rhs) = delete; + + /// \brief Get the status of the action queue. + /// + /// \returns Boolean FALSE when one or more actions are running, and + /// boolean TRUE when all actions have been completed and have been removed + /// from the queue. + bool idle() const; + + /// \brief Get the number of actions enqueued. + nom::size_type num_actions() const; + + /// \brief Get the control status of the queue. + /// + /// \returns One of the nom::ActionPlayer::State enumeration values. + ActionPlayer::State player_state() const; + + /// \brief Freeze the enqueued actions from advancing forward in time. + /// + /// \remarks Resuming from this control state will continue iterating the + /// enqueued actions forward in time from where it last left off. + /// + /// \note The player state is set to nom::ActionPlayer::State::PAUSED. + void pause(); + + /// \brief Resume the advancement of time for the enqueued actions. + /// + /// \note The player state is set to ActionPlayer::State::RUNNING. + /// + /// \see nom::ActionPlayer::pause, nom::ActionPlayer::stop + void resume(); + + /// \brief Reset the enqueued actions back to its initial starting state. + /// + /// \remarks This resets the state of the enqueued actions back to its + /// initial state, i.e.: before being executed. Resuming from this control + /// state will restart the enqueued actions from their initial state. + /// + /// \note The player state is set to nom::ActionPlayer::State::STOPPED. + void stop(); + + /// \brief Get the completion status of an action. + /// + /// \param action_id The unique identifier of the action. + /// + /// \returns Boolean TRUE if the action has completed, and boolean FALSE + /// if the action has **not** completed. + /// + /// \see nom::IActionObject::set_name. + bool action_running(const std::string& action_id) const; + + /// \brief Stop executing an action. + /// + /// \param action_id The unique identifier of the action to stop. + /// + /// \see nom::IActionObject::set_name. + bool cancel_action(const std::string& action_id); + + void cancel_actions(const action_names& actions); + + /// \brief Stop executing the enqueued actions. + /// + /// \remarks This will instantaneously stop running all enqueued actions. + void cancel_actions(); + + /// \brief Enqueue an action. + /// + /// \param action The action to run; NULL actions are valid. + /// + /// \remarks If an action using the same key is already running, it is + /// removed before the new action is added. + /// + /// \see nom::ActionPlayer::update + bool run_action(const std::shared_ptr& action); + + /// \brief Enqueue an action with a completion callback. + /// + /// \param action The action to run; NULL actions are valid. + /// + /// \param completion_func The function to call when the action is + /// completed -- passing NULL here is valid. + /// + /// \remarks If an action using the same key is already running, it is + /// removed before the new action is added. + /// + /// \see nom::ActionPlayer::update + bool run_action( const std::shared_ptr& action, + const action_callback_func& completion_func ); + + /// \brief Run the enqueued actions' update loop. + /// + /// \param delta_time Reserved for application-defined implementations. + /// + /// \returns Boolean TRUE when one or more actions are running, and boolean + /// FALSE when all actions have been completed. + /// + /// \remarks This method must be called from your application's loop. + /// + /// \see nom::ActionPlayer::idle + bool update(real32 delta_time); + + private: + /// \brief Enqueue an action that runs on a specific dispatch queue. + /// + /// \param action The action to run; NULL actions are valid. + /// + /// \param completion_func The function to call when the action is + /// completed -- passing NULL here is valid. + /// + /// \param dispatch_queue A valid nom::DispatchQueue to run the action on. + /// + /// \remarks It is **not** safe to enqueue more than one action per + /// nom::DispatchQueue instance. When two or more actions share the same + /// instance, it runs the risk of causing memory access violations in the + /// form of double-freeing their dispatch queues upon completion (I believe + /// this actually happens in ActionPlayer::update, but alas!). Using a + /// reference counting mechanism, such as shared_ptr, resolves the issue, + /// but if only that was the end of it ... + /// The update loop in nom::DispatchQueue iterates through actions + /// sequentially, which breaks actions that are intended to run in parallel, + /// i.e.: nom::GroupAction -- but even that problem is trivial to solve. + /// The ~25..50% performance penalty observed made me decide that it was + /// not worth the trouble, at least until I can find a use case for this + /// issue. + bool run_action( const std::shared_ptr& action, + std::unique_ptr dispatch_queue, + const action_callback_func& completion_func ); + + static const char* DEBUG_CLASS_NAME; + + typedef + std::map> container_type; + + typedef container_type::iterator container_iterator; + + ActionPlayer::State player_state_; + + /// \brief Enqueued actions. + container_type actions_; + + /// \brief The actions pending removal. + std::deque free_list_; +}; + +} // namespace nom + +#endif // include guard defined + +/// \class nom::ActionPlayer +/// \ingroup actions +/// +/// **TODO:** This documentation section is a *STUB*! +/// +/// \brief This is the high-level interface for scheduling and controlling the +/// flow of actions. You may find that using multiple instances of the +/// interface is ideal if you have several action groups that can be managed +/// independently from each other. +/// +/// ## Usage Examples +/// +/// \code +/// +/// // Declared somewhere accessible by the game +/// nom::ActionPlayer actions; +/// +/// // Your main game loop +/// while(game_running == true) +/// { +/// // calculate delta_time for this frame +/// +/// // ...Process game, input, etc. events +/// +/// // ...Process game updates... +/// +/// actions.update(delta_time); +/// +/// // ...Process rendering... +/// } +/// +/// \endcode +/// +/// \snippet tests/src/actions/ActionTest.cpp usage_example +/// +/// # References (Conceptual) +/// [SKNode](https://developer.apple.com/library/prerelease/ios/documentation/SpriteKit/Reference/SKNode_Ref/index.html#//apple_ref/occ/cl/SKNode) +/// diff --git a/include/nomlib/actions/ActionTimingCurves.hpp b/include/nomlib/actions/ActionTimingCurves.hpp new file mode 100644 index 00000000..035d170d --- /dev/null +++ b/include/nomlib/actions/ActionTimingCurves.hpp @@ -0,0 +1,407 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Easing algorithms are Copyright (c) 2001 Robert Penner + +******************************************************************************/ +#ifndef NOMLIB_ACTIONS_ACTION_TIMING_CURVES_HPP +#define NOMLIB_ACTIONS_ACTION_TIMING_CURVES_HPP + +#include "nomlib/config.hpp" + +#include + +namespace nom { + +// Standard easing classes + +/// \brief The standard Linear easing class. +/// +/// \note This easing class uses a power of one (1). +struct Linear +{ + /// \param t The elapsed time. + /// \param b The initial value to interpolate from. + /// \param c The total change in value over time. + /// \param d The total duration in time. + /// + /// \remarks The elapsed time and total duration time must be expressed in + /// the same time units. + static real32 ease_in(real32 t, real32 b, real32 c, real32 d); + + /// \param t The elapsed time. + /// \param b The initial value to interpolate from. + /// \param c The total change in value over time. + /// \param d The total duration in time. + /// + /// \remarks The elapsed time and total duration time must be expressed in + /// the same time units. + static real32 ease_out(real32 t, real32 b, real32 c, real32 d); + + /// \param t The elapsed time. + /// \param b The initial value to interpolate from. + /// \param c The total change in value over time. + /// \param d The total duration in time. + /// + /// \remarks The elapsed time and total duration time must be expressed in + /// the same time units. + static real32 ease_in_out(real32 t, real32 b, real32 c, real32 d); +}; + +/// \brief The standard Quad easing class. +/// +/// \note This easing class uses a power of two (2). +struct Quad +{ + /// \param t The elapsed time. + /// \param b The initial value to interpolate from. + /// \param c The total change in value over time. + /// \param d The total duration in time. + /// + /// \remarks The elapsed time and total duration time must be expressed in + /// the same time units. + static real32 ease_in(real32 t, real32 b, real32 c, real32 d); + + /// \param t The elapsed time. + /// \param b The initial value to interpolate from. + /// \param c The total change in value over time. + /// \param d The total duration in time. + /// + /// \remarks The elapsed time and total duration time must be expressed in + /// the same time units. + static real32 ease_out(real32 t, real32 b, real32 c, real32 d); + + /// \param t The elapsed time. + /// \param b The initial value to interpolate from. + /// \param c The total change in value over time. + /// \param d The total duration in time. + /// + /// \remarks The elapsed time and total duration time must be expressed in + /// the same time units. + static real32 ease_in_out(real32 t, real32 b, real32 c, real32 d); +}; + +/// \brief The standard Cubic easing class. +/// +/// \note This easing class uses a power of three (3). +struct Cubic +{ + /// \param t The elapsed time. + /// \param b The initial value to interpolate from. + /// \param c The total change in value over time. + /// \param d The total duration in time. + /// + /// \remarks The elapsed time and total duration time must be expressed in + /// the same time units. + static real32 ease_in(real32 t, real32 b, real32 c, real32 d); + + /// \param t The elapsed time. + /// \param b The initial value to interpolate from. + /// \param c The total change in value over time. + /// \param d The total duration in time. + /// + /// \remarks The elapsed time and total duration time must be expressed in + /// the same time units. + static real32 ease_out(real32 t, real32 b, real32 c, real32 d); + + /// \param t The elapsed time. + /// \param b The initial value to interpolate from. + /// \param c The total change in value over time. + /// \param d The total duration in time. + /// + /// \remarks The elapsed time and total duration time must be expressed in + /// the same time units. + static real32 ease_in_out(real32 t, real32 b, real32 c, real32 d); +}; + +/// \brief The standard Quart easing class. +/// +/// \note This easing class uses a power of four (4). +struct Quart +{ + /// \param t The elapsed time. + /// \param b The initial value to interpolate from. + /// \param c The total change in value over time. + /// \param d The total duration in time. + /// + /// \remarks The elapsed time and total duration time must be expressed in + /// the same time units. + static real32 ease_in(real32 t, real32 b, real32 c, real32 d); + + /// \param t The elapsed time. + /// \param b The initial value to interpolate from. + /// \param c The total change in value over time. + /// \param d The total duration in time. + /// + /// \remarks The elapsed time and total duration time must be expressed in + /// the same time units. + static real32 ease_out(real32 t, real32 b, real32 c, real32 d); + + /// \param t The elapsed time. + /// \param b The initial value to interpolate from. + /// \param c The total change in value over time. + /// \param d The total duration in time. + /// + /// \remarks The elapsed time and total duration time must be expressed in + /// the same time units. + static real32 ease_in_out(real32 t, real32 b, real32 c, real32 d); +}; + +/// \brief The standard Quint easing class -- also known as "Strong". +/// +/// \note This easing class uses a power of five (5). +struct Quint +{ + /// \param t The elapsed time. + /// \param b The initial value to interpolate from. + /// \param c The total change in value over time. + /// \param d The total duration in time. + /// + /// \remarks The elapsed time and total duration time must be expressed in + /// the same time units. + static real32 ease_in(real32 t, real32 b, real32 c, real32 d); + + /// \param t The elapsed time. + /// \param b The initial value to interpolate from. + /// \param c The total change in value over time. + /// \param d The total duration in time. + /// + /// \remarks The elapsed time and total duration time must be expressed in + /// the same time units. + static real32 ease_out(real32 t, real32 b, real32 c, real32 d); + + /// \param t The elapsed time. + /// \param b The initial value to interpolate from. + /// \param c The total change in value over time. + /// \param d The total duration in time. + /// + /// \remarks The elapsed time and total duration time must be expressed in + /// the same time units. + static real32 ease_in_out(real32 t, real32 b, real32 c, real32 d); +}; + +// Additional easing classes + +struct Back +{ + /// \param t The elapsed time. + /// \param b The initial value to interpolate from. + /// \param c The total change in value over time. + /// \param d The total duration in time. + /// + /// \remarks The elapsed time and total duration time must be expressed in + /// the same time units. + static real32 ease_in(real32 t, real32 b, real32 c, real32 d); + + /// \param t The elapsed time. + /// \param b The initial value to interpolate from. + /// \param c The total change in value over time. + /// \param d The total duration in time. + /// + /// \remarks The elapsed time and total duration time must be expressed in + /// the same time units. + static real32 ease_out(real32 t, real32 b, real32 c, real32 d); + + /// \param t The elapsed time. + /// \param b The initial value to interpolate from. + /// \param c The total change in value over time. + /// \param d The total duration in time. + /// + /// \remarks The elapsed time and total duration time must be expressed in + /// the same time units. + static real32 ease_in_out(real32 t, real32 b, real32 c, real32 d); +}; + +struct Bounce +{ + /// \param t The elapsed time. + /// \param b The initial value to interpolate from. + /// \param c The total change in value over time. + /// \param d The total duration in time. + /// + /// \remarks The elapsed time and total duration time must be expressed in + /// the same time units. + static real32 ease_in(real32 t, real32 b, real32 c, real32 d); + + /// \param t The elapsed time. + /// \param b The initial value to interpolate from. + /// \param c The total change in value over time. + /// \param d The total duration in time. + /// + /// \remarks The elapsed time and total duration time must be expressed in + /// the same time units. + static real32 ease_out(real32 t, real32 b, real32 c, real32 d); + + /// \param t The elapsed time. + /// \param b The initial value to interpolate from. + /// \param c The total change in value over time. + /// \param d The total duration in time. + /// + /// \remarks The elapsed time and total duration time must be expressed in + /// the same time units. + static real32 ease_in_out(real32 t, real32 b, real32 c, real32 d); +}; + +struct Circ +{ + /// \param t The elapsed time. + /// \param b The initial value to interpolate from. + /// \param c The total change in value over time. + /// \param d The total duration in time. + /// + /// \remarks The elapsed time and total duration time must be expressed in + /// the same time units. + static real32 ease_in(real32 t, real32 b, real32 c, real32 d); + + /// \param t The elapsed time. + /// \param b The initial value to interpolate from. + /// \param c The total change in value over time. + /// \param d The total duration in time. + /// + /// \remarks The elapsed time and total duration time must be expressed in + /// the same time units. + static real32 ease_out(real32 t, real32 b, real32 c, real32 d); + + /// \param t The elapsed time. + /// \param b The initial value to interpolate from. + /// \param c The total change in value over time. + /// \param d The total duration in time. + /// + /// \remarks The elapsed time and total duration time must be expressed in + /// the same time units. + static real32 ease_in_out(real32 t, real32 b, real32 c, real32 d); +}; + +struct Elastic +{ + /// \param t The elapsed time. + /// \param b The initial value to interpolate from. + /// \param c The total change in value over time. + /// \param d The total duration in time. + /// + /// \remarks The elapsed time and total duration time must be expressed in + /// the same time units. + static real32 ease_in(real32 t, real32 b, real32 c, real32 d); + + /// \param t The elapsed time. + /// \param b The initial value to interpolate from. + /// \param c The total change in value over time. + /// \param d The total duration in time. + /// + /// \remarks The elapsed time and total duration time must be expressed in + /// the same time units. + static real32 ease_out(real32 t, real32 b, real32 c, real32 d); + + /// \param t The elapsed time. + /// \param b The initial value to interpolate from. + /// \param c The total change in value over time. + /// \param d The total duration in time. + /// + /// \remarks The elapsed time and total duration time must be expressed in + /// the same time units. + static real32 ease_in_out(real32 t, real32 b, real32 c, real32 d); +}; + +/// \note This easing class uses a power of ten (10). +struct Expo +{ + /// \param t The elapsed time. + /// \param b The initial value to interpolate from. + /// \param c The total change in value over time. + /// \param d The total duration in time. + /// + /// \remarks The elapsed time and total duration time must be expressed in + /// the same time units. + static real32 ease_in(real32 t, real32 b, real32 c, real32 d); + + /// \param t The elapsed time. + /// \param b The initial value to interpolate from. + /// \param c The total change in value over time. + /// \param d The total duration in time. + /// + /// \remarks The elapsed time and total duration time must be expressed in + /// the same time units. + static real32 ease_out(real32 t, real32 b, real32 c, real32 d); + + /// \param t The elapsed time. + /// \param b The initial value to interpolate from. + /// \param c The total change in value over time. + /// \param d The total duration in time. + /// + /// \remarks The elapsed time and total duration time must be expressed in + /// the same time units. + static real32 ease_in_out(real32 t, real32 b, real32 c, real32 d); +}; + +struct Sine +{ + /// \param t The elapsed time. + /// \param b The initial value to interpolate from. + /// \param c The total change in value over time. + /// \param d The total duration in time. + /// + /// \remarks The elapsed time and total duration time must be expressed in + /// the same time units. + static real32 ease_in(real32 t, real32 b, real32 c, real32 d); + + /// \param t The elapsed time. + /// \param b The initial value to interpolate from. + /// \param c The total change in value over time. + /// \param d The total duration in time. + /// + /// \remarks The elapsed time and total duration time must be expressed in + /// the same time units. + static real32 ease_out(real32 t, real32 b, real32 c, real32 d); + + /// \param t The elapsed time. + /// \param b The initial value to interpolate from. + /// \param c The total change in value over time. + /// \param d The total duration in time. + /// + /// \remarks The elapsed time and total duration time must be expressed in + /// the same time units. + static real32 ease_in_out(real32 t, real32 b, real32 c, real32 d); +}; + +std::function +make_timing_curve_from_string(const std::string& timing_mode); + +} // namespace nom + +#endif // include guard defined + +/// \ingroup actions +/// +/// These easing functions derive from jesusgollonet's [GitHub repository](https://github.com/jesusgollonet/ofpennereasing). +/// The easing algorithms are the work of [Robbert Penner](http://www.robertpenner.com/easing/). +/// +/// ## References +/// +/// \see [Visual graphing of each algorithm](http://easings.net/) +/// \see [Explaining Penner’s equations](http://upshots.org/actionscript/jsas-understanding-easing) +/// \see [Chapter 7 of Penner's book](http://robertpenner.com/easing/penner_chapter7_tweening.pdf) +/// diff --git a/include/nomlib/actions/AnimateTexturesAction.hpp b/include/nomlib/actions/AnimateTexturesAction.hpp new file mode 100644 index 00000000..6409e40e --- /dev/null +++ b/include/nomlib/actions/AnimateTexturesAction.hpp @@ -0,0 +1,130 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#ifndef NOMLIB_ACTIONS_ANIMATE_TEXTURES_ACTION_HPP +#define NOMLIB_ACTIONS_ANIMATE_TEXTURES_ACTION_HPP + +#include +#include + +#include "nomlib/config.hpp" +#include "nomlib/actions/IActionObject.hpp" + +namespace nom { + +// Forward declarations +class Texture; +class Sprite; + +typedef std::vector> texture_frames; + +/// \brief Animate changes to a sprite's texture +class AnimateTexturesAction: public virtual IActionObject +{ + public: + /// \brief Allow access into our private parts for unit testing. + friend class ActionTest; + + typedef AnimateTexturesAction self_type; + typedef IActionObject derived_type; + + /// \brief Animate changes to a sprite’s texture. + /// + /// \param drawable A valid nom::Sprite instance to use in rendering the + /// frames to. + /// + /// \param textures A collection of textures to use to animate the sprite. + /// sprite. + /// + /// \param frame_interval_seconds The duration (in seconds) that each + /// texture is displayed. + AnimateTexturesAction( const std::shared_ptr& drawable, + const texture_frames& textures, + real32 frame_interval_seconds ); + + virtual ~AnimateTexturesAction(); + + virtual std::unique_ptr clone() const override; + + virtual IActionObject::FrameState next_frame(real32 delta_time) override; + + virtual IActionObject::FrameState prev_frame(real32 delta_time) override; + + virtual void pause(real32 delta_time) override; + + virtual void resume(real32 delta_time) override; + + virtual void rewind(real32 delta_time) override; + + virtual void release() override; + + private: + static const char* DEBUG_CLASS_NAME; + + void initialize(const texture_frames& textures, real32 frame_interval); + + IActionObject::FrameState + update(real32 t, real32 b, real32 c, real32 d); + + void first_frame(real32 delta_time); + void last_frame(real32 delta_time); + + typedef texture_frames::iterator frame_iterator; + + /// \brief The total number of textures to animate. + real32 total_displacement_; + + /// \brief The initial starting texture to animate from. + uint32 initial_frame_; + + /// \brief The stored textures. + texture_frames frames_; + frame_iterator next_frame_; + frame_iterator last_frame_; + + /// \brief The provided drawable used to render frames to. + std::shared_ptr drawable_; + + /// \brief The delay (in seconds) before displaying the next texture. + real32 frame_interval_; + real32 last_delta_; +}; + +} // namespace nom + +#endif // include guard defined + +/// \class nom::AnimateTexturesAction +/// \ingroup actions +/// +/// \brief ... +/// +/// \remarks This action is reversible; the sprite sheet frames are displayed +/// in the inverse order. For example, if the sprite sheet frames are +/// {0, 1, 2}, the reversed order would be {2, 1, 0}. +/// diff --git a/include/nomlib/actions/CallbackAction.hpp b/include/nomlib/actions/CallbackAction.hpp new file mode 100644 index 00000000..88871708 --- /dev/null +++ b/include/nomlib/actions/CallbackAction.hpp @@ -0,0 +1,99 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#ifndef NOMLIB_ACTIONS_CALLBACK_ACTION_HPP +#define NOMLIB_ACTIONS_CALLBACK_ACTION_HPP + +#include + +#include "nomlib/config.hpp" +#include "nomlib/actions/IActionObject.hpp" + +namespace nom { + +/// \brief Execute a function over a time period. +class CallbackAction: public virtual IActionObject +{ + public: + /// \brief Allow access into our private parts for unit testing. + friend class ActionTest; + + typedef CallbackAction self_type; + typedef IActionObject derived_type; + + typedef std::function callback_func; + + /// \brief Execute a function instantaneously. + /// + /// \param action A function pointer; NULL actions are valid. + CallbackAction(const callback_func& action); + + /// \brief Execute a function over a time period. + /// + /// \param seconds The duration of the animation. + /// \param action A function pointer; NULL actions are valid. + CallbackAction(real32 seconds, const callback_func& action); + + virtual ~CallbackAction(); + + virtual std::unique_ptr clone() const override; + + virtual IActionObject::FrameState next_frame(real32 delta_time) override; + + virtual IActionObject::FrameState prev_frame(real32 delta_time) override; + + /// \brief This action does not support being paused. + virtual void pause(real32 delta_time) override; + + /// \brief This action does not support being resumed. + virtual void resume(real32 delta_time) override; + + /// \brief This action does not support being reset. + virtual void rewind(real32 delta_time) override; + + virtual void release() override; + + private: + static const char* DEBUG_CLASS_NAME; + + /// \brief The stored function pointer. + callback_func action_; +}; + +} // namespace nom + +#endif // include guard defined + +/// \class nom::CallbackAction +/// \ingroup actions +/// +/// \brief ... +/// +/// \remarks This action is not reversible; the reverse of this action is +/// the same action. +/// diff --git a/include/nomlib/actions/DispatchQueue.hpp b/include/nomlib/actions/DispatchQueue.hpp new file mode 100644 index 00000000..a99c33d1 --- /dev/null +++ b/include/nomlib/actions/DispatchQueue.hpp @@ -0,0 +1,132 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#ifndef NOMLIB_ACTIONS_DISPATCH_QUEUE_HPP +#define NOMLIB_ACTIONS_DISPATCH_QUEUE_HPP + +#include +#include +#include + +#include "nomlib/config.hpp" +#include "nomlib/core/unique_ptr.hpp" + +namespace nom { + +// Forward declarations +class IActionObject; +struct DispatchEnqueue; + +typedef std::function action_callback_func; + +/// \brief The internal processing queue for actions +class DispatchQueue +{ + public: + typedef DispatchQueue self_type; + + /// \brief The status of the queue. + enum State + { + IDLING = 0, + RUNNING, + }; + + DispatchQueue(); + + ~DispatchQueue(); + + /// \brief Get the total number of enqueued actions. + nom::size_type num_actions() const; + + /// \brief Append an action to the list of actions to be executed. + /// + /// \param action The action to run; NULL actions are valid. + /// \param completion_func An optional function to call when the action is + /// completed -- passing NULL here will ignore this parameter. + bool enqueue_action( const std::shared_ptr& action, + const action_callback_func& completion_func ); + + /// \brief Run the enqueued actions' update loop. + /// + /// \param player_state One of the ActionPlayer::State enumeration values. + /// + /// \param delta_time Reserved for application-defined implementations. + /// + /// \returns DispatchQueue::State::RUNNING if one or more actions are + /// executing, or DispatchQueue::State::IDLING when no actions are running, + /// such as when the queue is empty. + /// + /// \fixme Pausing the action does not always take effect for every + /// nom::MoveByAction in the group within + /// ActionTest.RainingRectsStressTest -- some actions are sometimes skipped + /// for some mysterious reason! + /// + /// \see nom::ActionPlayer::update. + DispatchQueue::State + update(uint32 player_state, real32 delta_time); + + private: + static const char* DEBUG_CLASS_NAME; + + typedef std::vector> container_type; + typedef container_type::iterator iterator_type; + + /// \brief Enqueued actions. + container_type actions_; + iterator_type actions_iterator_; + + /// \brief The total number of actions enqueued. + nom::size_type num_actions_ = 0; +}; + +/// \brief Constructor function for creating a nom::DispatchQueue. +/// +/// \param args The arguments to pass to the constructed object. +/// +/// \relates nom::DispatchQueue +template +std::unique_ptr create_dispatch_queue(ObjectArgs&&... args) +{ + auto dispatch_queue = + nom::make_unique( std::forward(args) ... ); + + return std::move(dispatch_queue); +} + +} // namespace nom + +#endif // include guard defined + +/// \class nom::DispatchQueue +/// \ingroup actions +/// +/// \brief This interface was inspired by Apple's [Grand Central Dispatch (GCD)](https://developer.apple.com/library/ios/documentation/Performance/Reference/GCD_libdispatch_Ref/index.html). +/// +/// \see nom::ActionPlayer +/// diff --git a/include/nomlib/actions/FadeAlphaByAction.hpp b/include/nomlib/actions/FadeAlphaByAction.hpp new file mode 100644 index 00000000..9e40cdcc --- /dev/null +++ b/include/nomlib/actions/FadeAlphaByAction.hpp @@ -0,0 +1,111 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#ifndef NOMLIB_ACTIONS_FADE_ALPHA_BY_ACTION_HPP +#define NOMLIB_ACTIONS_FADE_ALPHA_BY_ACTION_HPP + +#include + +#include "nomlib/config.hpp" +#include "nomlib/actions/IActionObject.hpp" + +namespace nom { + +// Forward declarations +class Sprite; + +/// \brief Fade a sprite by a relative alpha value +class FadeAlphaByAction: public virtual IActionObject +{ + public: + /// \brief Allow access into our private parts for unit testing. + friend class ActionTest; + + typedef FadeAlphaByAction self_type; + typedef IActionObject derived_type; + + /// \brief Fade a sprite by a relative alpha value. + /// + /// \param drawable A valid nom::Sprite instance. + /// \param delta The total change in alpha to apply to the drawable over + /// time. + /// \param seconds The duration of the animation. + /// + /// \remarks The valid range of the delta is between -255.0f to 255.0f. + FadeAlphaByAction( const std::shared_ptr& drawable, real32 delta, + real32 seconds ); + + virtual ~FadeAlphaByAction(); + + virtual std::unique_ptr clone() const override; + + virtual IActionObject::FrameState next_frame(real32 delta_time) override; + + virtual IActionObject::FrameState prev_frame(real32 delta_time) override; + + virtual void pause(real32 delta_time) override; + + virtual void resume(real32 delta_time) override; + + virtual void rewind(real32 delta_time) override; + + virtual void release() override; + + private: + static const char* DEBUG_CLASS_NAME; + + IActionObject::FrameState update(real32 t, real32 b, real32 c, real32 d); + + void first_frame(real32 delta_time); + void last_frame(real32 delta_time); + + const real32 total_displacement_ = 0.0f; + real32 initial_alpha_ = 0.0f; + + std::shared_ptr drawable_; + + /// \brief The current alpha value of the drawable. + uint8 alpha_ = 0; +}; + +} // namespace nom + +#endif // include guard defined + +/// \class nom::FadeAlphaByAction +/// \ingroup actions +/// +/// \brief ... +/// +/// \remarks This action is reversible; the reverse is created as if the +/// following code was executed: +/// +/// \code +/// FadeAlphaByAction(drawable, -delta, seconds); +/// \endcode +/// diff --git a/include/nomlib/actions/FadeAudioGainBy.hpp b/include/nomlib/actions/FadeAudioGainBy.hpp new file mode 100644 index 00000000..4096918c --- /dev/null +++ b/include/nomlib/actions/FadeAudioGainBy.hpp @@ -0,0 +1,108 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014, 2015, 2016 Jeffrey Carpenter Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#ifndef NOMLIB_ACTIONS_FADE_AUDIO_GAIN_BY_HPP +#define NOMLIB_ACTIONS_FADE_AUDIO_GAIN_BY_HPP + +#include + +#include "nomlib/config.hpp" +#include "nomlib/actions/IActionObject.hpp" + +namespace nom { +namespace audio { + +// Forward declarations +class IOAudioEngine; +struct SoundBuffer; + +} // namespace audio + +/// \brief [TODO: Description] +// TODO: Update comments! +class FadeAudioGainBy: public virtual IActionObject +{ + public: + /// \brief Allow access into our private parts for unit testing. + friend class ActionTest; + + typedef FadeAudioGainBy self_type; + + /// \brief Default constructor; create the action from an audio file on + /// disk. + FadeAudioGainBy(audio::IOAudioEngine* dev, const char* filename, + real32 delta, real32 duration); + + /// \brief Construct the action from a pre-initialized audio buffer. + FadeAudioGainBy(audio::IOAudioEngine* dev, audio::SoundBuffer* buffer, + real32 delta, real32 duration); + + /// \brief Destructor. + virtual ~FadeAudioGainBy(); + + virtual std::unique_ptr clone() const override; + + virtual IActionObject::FrameState next_frame(real32 delta_time) override; + + virtual IActionObject::FrameState prev_frame(real32 delta_time) override; + + virtual void pause(real32 delta_time) override; + + /// \brief Resume logic for the animation object. + /// + /// \remarks Reserved for future implementation. + virtual void resume(real32 delta_time) override; + + virtual void rewind(real32 delta_time) override; + + virtual void release() override; + + private: + static const char* DEBUG_CLASS_NAME; + + /// \brief Execute the alpha blending logic for the animation. + IActionObject::FrameState update(real32 t, uint8 b, int16 c, real32 d); + + void first_frame(real32 delta_time); + void last_frame(real32 delta_time); + + /// \brief The initial alpha blending value. + real32 initial_volume_; + + /// \brief The total change in the alpha blending value. + const real32 total_displacement_; + + audio::IOAudioEngine* impl_ = nullptr; + + /// \brief The animation proxy object used to perform alpha blending on. + audio::SoundBuffer* audible_ = nullptr; +}; + +} // namespace nom + +#endif // include guard defined diff --git a/include/nomlib/actions/FadeInAction.hpp b/include/nomlib/actions/FadeInAction.hpp new file mode 100644 index 00000000..be187243 --- /dev/null +++ b/include/nomlib/actions/FadeInAction.hpp @@ -0,0 +1,107 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#ifndef NOMLIB_ACTIONS_FADE_IN_ACTION_HPP +#define NOMLIB_ACTIONS_FADE_IN_ACTION_HPP + +#include + +#include "nomlib/config.hpp" +#include "nomlib/actions/IActionObject.hpp" +#include "nomlib/math/Color4.hpp" + +namespace nom { + +// Forward declarations +class Sprite; + +/// \brief Fade a sprite in to full opacity +class FadeInAction: public virtual IActionObject +{ + public: + /// \brief Allow access into our private parts for unit testing. + friend class ActionTest; + + typedef FadeInAction self_type; + typedef IActionObject derived_type; + + /// \brief Fade a sprite in to full opacity. + /// + /// \param drawable A valid nom::Sprite instance. + /// \param seconds The duration of the animation. + FadeInAction(const std::shared_ptr& drawable, real32 seconds); + + virtual ~FadeInAction(); + + virtual std::unique_ptr clone() const override; + + virtual IActionObject::FrameState next_frame(real32 delta_time) override; + + virtual IActionObject::FrameState prev_frame(real32 delta_time) override; + + virtual void pause(real32 delta_time) override; + + virtual void resume(real32 delta_time) override; + + virtual void rewind(real32 delta_time) override; + + virtual void release() override; + + private: + static const char* DEBUG_CLASS_NAME; + + IActionObject::FrameState update(real32 t, real32 b, real32 c, real32 d); + + void first_frame(real32 delta_time); + void last_frame(real32 delta_time); + + const real32 total_displacement_ = Color4i::ALPHA_OPAQUE; + real32 initial_alpha_ = 0.0f; + + std::shared_ptr drawable_; + + /// \brief The current alpha value of the drawable. + uint8 alpha_ = 0; +}; + +} // namespace nom + +#endif // include guard defined + +/// \class nom::FadeInAction +/// \ingroup actions +/// +/// \brief ... +/// +/// \remarks This action is reversible; the reverse is created as if the +/// following code was executed: +/// +/// \code +/// FadeOutAction(drawable, seconds); +/// \endcode +/// diff --git a/include/nomlib/actions/FadeOutAction.hpp b/include/nomlib/actions/FadeOutAction.hpp new file mode 100644 index 00000000..aee93c1b --- /dev/null +++ b/include/nomlib/actions/FadeOutAction.hpp @@ -0,0 +1,107 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#ifndef NOMLIB_ACTIONS_FADE_OUT_ACTION_HPP +#define NOMLIB_ACTIONS_FADE_OUT_ACTION_HPP + +#include + +#include "nomlib/config.hpp" +#include "nomlib/actions/IActionObject.hpp" +#include "nomlib/math/Color4.hpp" + +namespace nom { + +// Forward declarations +class Sprite; + +/// \brief Fade a sprite out to zero opacity +class FadeOutAction: public virtual IActionObject +{ + public: + /// \brief Allow access into our private parts for unit testing. + friend class ActionTest; + + typedef FadeOutAction self_type; + typedef IActionObject derived_type; + + /// \brief Fade a sprite out to zero opacity. + /// + /// \param drawable A valid nom::Sprite instance. + /// \param seconds The duration of the animation. + FadeOutAction(const std::shared_ptr& drawable, real32 seconds); + + virtual ~FadeOutAction(); + + virtual std::unique_ptr clone() const override; + + virtual IActionObject::FrameState next_frame(real32 delta_time) override; + + virtual IActionObject::FrameState prev_frame(real32 delta_time) override; + + virtual void pause(real32 delta_time) override; + + virtual void resume(real32 delta_time) override; + + virtual void rewind(real32 delta_time) override; + + virtual void release() override; + + private: + static const char* DEBUG_CLASS_NAME; + + IActionObject::FrameState update(real32 t, real32 b, real32 c, real32 d); + + void first_frame(real32 delta_time); + void last_frame(real32 delta_time); + + const real32 total_displacement_ = Color4i::ALPHA_OPAQUE; + real32 initial_alpha_ = 0.0f; + + std::shared_ptr drawable_; + + /// \brief The current alpha value of the drawable. + uint8 alpha_ = 0; +}; + +} // namespace nom + +#endif // include guard defined + +/// \class nom::FadeOutAction +/// \ingroup actions +/// +/// \brief ... +/// +/// \remarks This action is reversible; the reverse is created as if the +/// following code was executed: +/// +/// \code +/// FadeInAction(drawable, seconds); +/// \endcode +/// diff --git a/include/nomlib/actions/GroupAction.hpp b/include/nomlib/actions/GroupAction.hpp new file mode 100644 index 00000000..8ed42ffe --- /dev/null +++ b/include/nomlib/actions/GroupAction.hpp @@ -0,0 +1,129 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#ifndef NOMLIB_ACTIONS_GROUP_ACTION_HPP +#define NOMLIB_ACTIONS_GROUP_ACTION_HPP + +#include +#include + +#include "nomlib/config.hpp" +#include "nomlib/actions/IActionObject.hpp" + +namespace nom { + +// Forward declarations +struct group_action; + +/// \brief Run a collection of actions together (in parallel) +class GroupAction: public virtual IActionObject +{ + public: + /// \brief Allow access into our private parts for unit testing. + friend class ActionTest; + + typedef GroupAction self_type; + typedef IActionObject derived_type; + + /// \brief Run a collection of actions together (in parallel). + /// + /// \param actions The group of actions to execute; NULL actions are + /// valid. + GroupAction(const action_list& actions); + + virtual ~GroupAction(); + + virtual std::unique_ptr clone() const override; + + virtual IActionObject::FrameState next_frame(real32 delta_time) override; + + virtual IActionObject::FrameState prev_frame(real32 delta_time) override; + + virtual void pause(real32 delta_time) override; + + virtual void resume(real32 delta_time) override; + + virtual void rewind(real32 delta_time) override; + + virtual void release() override; + + /// \brief Set the speed factor of the child actions. + /// + /// \remarks This has no effect on the parent (this object). + virtual void set_speed(real32 speed) override; + + /// \brief Set the timing mode of the child actions. + /// + /// \remarks This has no effect on the parent (this object). + /// + /// \see nom::IActionObject::timing_curve_func + virtual void + set_timing_curve(const IActionObject::timing_curve_func& mode) override; + + private: + static const char* DEBUG_CLASS_NAME; + + /// \remarks A std::vector container seems most appropriate here because + /// of the contiguous memory access, for lack of any other special needs, + /// i.e.: fast front/back iteration or fast expansion time. + typedef std::vector container_type; + typedef container_type::iterator container_iterator; + + IActionObject::FrameState + update(real32 delta_time, uint32 direction); + + const container_type& actions() const; + + /// \brief The child actions. + container_type actions_; + + /// \brief The total number of completed actions. + nom::size_type num_completed_; + + /// \brief The total number of actions at the time of construction. + nom::size_type num_actions_; +}; + +} // namespace nom + +#endif // include guard defined + +/// \class nom::GroupAction +/// \ingroup actions +/// +/// \brief This action acts on behalf of other actions as a proxy, executing +/// actions together until every action has completed. If an action in the group +/// has a duration less than the group's total duration, the action completes, +/// then idles until the group completes the remaining actions. +/// +/// \remarks This action is not reversible, but the child actions may be +/// reversible. Consult the documentation for the action in question for +/// implementation details. +/// +/// \see nom::SequenceAction +/// diff --git a/include/nomlib/actions/IActionObject.hpp b/include/nomlib/actions/IActionObject.hpp new file mode 100644 index 00000000..a3ebb553 --- /dev/null +++ b/include/nomlib/actions/IActionObject.hpp @@ -0,0 +1,277 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#ifndef NOMLIB_ACTIONS_IACTION_OBJECT_HPP +#define NOMLIB_ACTIONS_IACTION_OBJECT_HPP + +#include +#include + +#include "nomlib/config.hpp" +#include "nomlib/system/Timer.hpp" + +namespace nom { + +/// \brief Pure virtual base class interface for actions. +class IActionObject +{ + public: + typedef IActionObject self_type; + + /// \brief A function pointer to a timing mode. + /// + /// \see nom::Linear, nom::Quad, nom::Cubic, nom::Quart, nom::Quint, + /// nom::Back, nom::Bounce, nom::Circ, nom::Elastic, nom::Expo, nom::Sine + typedef + std::function timing_curve_func; + + /// \brief The update status of the action. + enum FrameState + { + /// The action has finished its update loop. + COMPLETED, + /// The action is still updating; duration remains. + PLAYING, + }; + + IActionObject(); + + virtual ~IActionObject(); + + /// \brief Get the unique identifier of the action. + const std::string& name() const; + + /// \brief Get the duration of the action. + /// + /// \returns The duration in fractional seconds. + real32 duration() const; + + /// \brief Get the speed modifier of the action. + /// + /// \returns The speed factor of the action. + /// + /// \remarks A value of zero (0.0f) stops the action from progressing + /// forward in time. + real32 speed() const; + + /// \brief Get the timing mode function used by the action. + /// + /// \returns The function pointer to the timing curve function. + /// + /// \see nom::IActionObject::timing_curve_func + const IActionObject::timing_curve_func& timing_curve() const; + + /// \brief Set the unique identifier of the action. + void set_name(const std::string& action_id); + + /// \brief Set the speed factor of the action. + virtual void set_speed(real32 speed); + + /// \brief Set the timing mode of the action. + /// + /// \see nom::IActionObject::timing_curve_func + virtual void set_timing_curve(const IActionObject::timing_curve_func& mode); + + /// \brief Create a deep copy instance of the action. + /// + /// \remarks A cloned instance is created using the action's attributes + /// at the time of construction. External resources of the action -- + /// i.e.: nom::Sprite -- are not modified, and you may need to reset the + /// state appropriately if the action has been ran previously. + virtual std::unique_ptr clone() const = 0; + + /// \brief Play the action forward in time by one time step. + /// + /// \param delta_time Reserved for application-defined implementations. + /// + /// \note This method should not normally need to be called externally! + /// Exceptions might include: a) implementing a new action; b) advanced + /// debugging + /// + /// \see nom::DispatchQueue + virtual IActionObject::FrameState next_frame(real32 delta_time) = 0; + + /// \brief Play the action backwards in time by one time step. + /// + /// \param delta_time Reserved for application-defined implementations. + /// + /// \note This method should not normally need to be called externally! + /// Exceptions might include: a) implementing a new action; b) advanced + /// debugging + /// + /// \remarks Not all actions are reversible -- see the action's + /// documentation for its implementation details. + /// + /// \see nom::ReversedAction + virtual IActionObject::FrameState prev_frame(real32 delta_time) = 0; + + /// \brief Freeze the action's internal state. + /// + /// \param delta_time Reserved for application-defined implementations. + /// + /// \note This method should not normally need to be called externally! + /// Exceptions might include: a) implementing a new action; b) advanced + /// debugging + /// + /// \see nom::DispatchQueue + virtual void pause(real32 delta_time) = 0; + + /// \brief Resume the internal state of the action. + /// + /// \param delta_time Reserved for application-defined implementations. + /// + /// \note This method should not normally need to be called externally! + /// Exceptions might include: a) implementing a new action; b) advanced + /// debugging + /// + /// \see nom::DispatchQueue + virtual void resume(real32 delta_time) = 0; + + /// \brief Reset the internal state of the action back to its initial + /// starting values. + /// + /// \param delta_time Reserved for application-defined implementations. + /// + /// \note This method should not normally need to be called externally! + /// Exceptions might include: a) implementing a new action; b) advanced + /// debugging + /// + /// \see nom::RepeatForAction, nom::RepeatForeverAction + virtual void rewind(real32 delta_time) = 0; + + /// \brief Free externally referenced resources held by the action. + /// + /// \note This method should not normally need to be called externally! + /// Exceptions might include: a) implementing a new action; b) advanced + /// debugging + /// + /// \see nom::RemoveAction + virtual void release() = 0; + + protected: + /// \brief Get the current state of the action. + /// + /// \see nom::IActionObject::FrameState + IActionObject::FrameState status() const; + + /// \brief Set the duration of the action. + /// + /// \param seconds The duration in seconds. + void set_duration(real32 seconds); + + /// \brief Set the state of the action. + /// + /// \param state One of the IActionObject::FrameState enumeration values. + void set_status(FrameState state); + + /// \brief Internal frames counter. + /// + /// \remarks This is intended purely for debugging convenience. + real32 elapsed_frames_ = 0.0f; + + /// \brief Internal time clock (milliseconds resolution). + /// + /// \remarks Each action is responsible for keeping track of its clock -- + /// this provides a stable, fixed duration that yields to reliable results + /// across any frame rate -- at the cost of potentially skipping frames + /// when performance suffers. + Timer timer_; + + private: + FrameState status_ = FrameState::PLAYING; + std::string name_; + real32 duration_ = 0.0f; + real32 speed_ = 1.0f; + timing_curve_func timing_curve_ = nullptr; +}; + +/// \brief A collection of actions. +/// +/// \relates nom::IActionObject +typedef std::vector> action_list; + +/// \brief Constructor function for creating an action. +/// +/// \param args The arguments to pass to the constructed object. +/// +/// \relates nom::IActionObject +template +std::shared_ptr create_action(ObjectArgs&&... args) +{ + // IMPORTANT: We risk object slicing if we use std::make_shared here! The + // problem occurs when the end-user tries to return the created action + // pointer by value. + return( std::shared_ptr( + new ObjectType( std::forward(args) ... ) ) ); +} + +/// \brief Constructor function for creating an action that uses a collection +/// of actions. +/// +/// \param actions The collection of actions to pass to the constructed object. +/// +/// \remarks The collection can be constructed in-place with a +/// [std::initializer_list](http://en.cppreference.com/w/cpp/utility/initializer_list) +/// object. +/// +/// \note If you are building with Visual Studio 2013 on the Windows platform, +/// you will want to ensure that you have Update 2 or better applied before +/// using this function call with a std::initializer_list object. See also: +/// [stackoverflow.com: std::shared_ptr in an std::initializer_list appears to be getting destroyed prematurely](http://stackoverflow.com/questions/22924358/stdshared-ptr-in-an-stdinitializer-list-appears-to-be-getting-destroyed-prem/22924473#22924473) +/// +/// \see nom::GroupAction, nom::SequenceAction +/// +/// \relates nom::IActionObject +template +std::shared_ptr +create_action(const action_list& actions) +{ + // IMPORTANT: We risk object slicing if we use std::make_shared here! The + // problem occurs when the end-user tries to return the created action + // pointer by value. + return( std::shared_ptr( new ObjectType(actions) ) ); +} + +} // namespace nom + +#endif // include guard defined + +/// \class nom::IActionObject +/// \ingroup actions +/// +/// **TODO:** This documentation section is a *STUB*! +/// +/// ## Creating Custom Actions +/// +/// A simple, bare-bones example: +/// \snippet src/actions/WaitForDurationAction.cpp creating_custom_actions +/// +/// # References (Conceptual) +/// \see [SpriteKit: SKAction Class Reference](https://developer.apple.com/library/prerelease/ios/documentation/SpriteKit/Reference/SKAction_Ref/index.html) +/// \see [SpriteKit: Creating Actions That Run Other Actions](https://developer.apple.com/library/ios/documentation/GraphicsAnimation/Conceptual/SpriteKit_PG/AddingActionstoSprites/AddingActionstoSprites.html) +/// diff --git a/include/nomlib/actions/MoveByAction.hpp b/include/nomlib/actions/MoveByAction.hpp new file mode 100644 index 00000000..1908db44 --- /dev/null +++ b/include/nomlib/actions/MoveByAction.hpp @@ -0,0 +1,111 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#ifndef NOMLIB_ACTIONS_MOVE_BY_ACTION_HPP +#define NOMLIB_ACTIONS_MOVE_BY_ACTION_HPP + +#include + +#include "nomlib/config.hpp" +#include "nomlib/actions/IActionObject.hpp" +#include "nomlib/math/Point2.hpp" + +namespace nom { + +// Forward declarations +class Sprite; + +/// \brief Move a sprite relative to its current position +class MoveByAction: public virtual IActionObject +{ + public: + /// \brief Allow access into our private parts for unit testing. + friend class ActionTest; + + typedef MoveByAction self_type; + typedef IActionObject derived_type; + + /// \brief Move a sprite relative to its current position. + /// + /// \param drawable A valid nom::Sprite instance. + /// \param delta The total change in position to apply to the drawable over + /// time. + /// \param seconds The duration of the animation. + /// + /// \remarks Negative delta values are valid. + MoveByAction( const std::shared_ptr& drawable, const Point2i& delta, + real32 seconds ); + + virtual ~MoveByAction(); + + virtual std::unique_ptr clone() const override; + + virtual IActionObject::FrameState next_frame(real32 delta_time) override; + + virtual IActionObject::FrameState prev_frame(real32 delta_time) override; + + virtual void pause(real32 delta_time) override; + + virtual void resume(real32 delta_time) override; + + virtual void rewind(real32 delta_time) override; + + virtual void release() override; + + private: + static const char* DEBUG_CLASS_NAME; + + IActionObject::FrameState + update(real32 t, const Point2i& b, const Point2i& c, real32 d); + + void first_frame(real32 delta_time); + + void last_frame(real32 delta_time); + + const Point2i total_displacement_ = Point2i::zero; + Point2i initial_position_ = Point2i::zero; + + std::shared_ptr drawable_; +}; + +} // namespace nom + +#endif // include guard defined + +/// \class nom::MoveByAction +/// \ingroup actions +/// +/// \brief ... +/// +/// \remarks This action is reversible; the reverse is created as if the +/// following code was executed: +/// +/// \code +/// MoveByAction(drawable, -delta, seconds); +/// \endcode +/// diff --git a/include/nomlib/actions/PlayAudioSource.hpp b/include/nomlib/actions/PlayAudioSource.hpp new file mode 100644 index 00000000..e359a1d9 --- /dev/null +++ b/include/nomlib/actions/PlayAudioSource.hpp @@ -0,0 +1,114 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014, 2015, 2016 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#ifndef NOMLIB_ACTIONS_PLAY_AUDIO_SOURCE_HPP +#define NOMLIB_ACTIONS_PLAY_AUDIO_SOURCE_HPP + +#include + +#include "nomlib/config.hpp" +#include "nomlib/actions/IActionObject.hpp" + +namespace nom { +namespace audio { + +// Forward declarations +class IOAudioEngine; +struct SoundBuffer; +class ISoundFileReader; + +} // namespace audio + +/// \brief [TODO: Description] +// TODO: Update comments! +class PlayAudioSource: public virtual IActionObject +{ + public: + /// \brief Allow access into our private parts for unit testing. + friend class ActionTest; + + typedef PlayAudioSource self_type; + + /// \brief Default constructor; create the action from an audio file on + /// disk. + PlayAudioSource(audio::IOAudioEngine* dev, const char* filename); + + /// \brief Construct the action from a pre-initialized audio buffer. + // PlayAudioSource(audio::IOAudioEngine* dev, audio::SoundBuffer* buffer); + + /// \brief Destructor. + virtual ~PlayAudioSource(); + + virtual std::unique_ptr clone() const override; + + virtual IActionObject::FrameState next_frame(real32 delta_time) override; + + virtual IActionObject::FrameState prev_frame(real32 delta_time) override; + + virtual void pause(real32 delta_time) override; + + /// \brief Resume logic for the animation object. + /// + /// \remarks Reserved for future implementation. + virtual void resume(real32 delta_time) override; + + virtual void rewind(real32 delta_time) override; + + virtual void release() override; + + private: + static const char* DEBUG_CLASS_NAME; + + /// \brief Execute the alpha blending logic for the animation. + IActionObject::FrameState update(real32 t, uint8 b, int16 c, real32 d); + + void first_frame(real32 delta_time); + void last_frame(real32 delta_time); + + /// \brief The initial alpha blending value. + // real32 initial_volume_; + + /// \brief The total change in the alpha blending value. + // const real32 total_displacement_; + + nom::size_type curr_frame_ = 0; + + audio::IOAudioEngine* impl_ = nullptr; + + audio::ISoundFileReader* fp_ = nullptr; + + typedef std::vector audio_buffers; + audio_buffers::iterator current_buffer_; + audio_buffers audible_; + + uint32 input_pos_ = 0; +}; + +} // namespace nom + +#endif // include guard defined diff --git a/include/nomlib/actions/RemoveAction.hpp b/include/nomlib/actions/RemoveAction.hpp new file mode 100644 index 00000000..a3dbb333 --- /dev/null +++ b/include/nomlib/actions/RemoveAction.hpp @@ -0,0 +1,101 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#ifndef NOMLIB_ACTIONS_REMOVE_ACTION_HPP +#define NOMLIB_ACTIONS_REMOVE_ACTION_HPP + +#include + +#include "nomlib/config.hpp" +#include "nomlib/actions/IActionObject.hpp" + +namespace nom { + +/// \brief Free the external resources held by an action +class RemoveAction: public virtual IActionObject +{ + public: + /// \brief Allow access into our private parts for unit testing. + friend class ActionTest; + + typedef RemoveAction self_type; + typedef IActionObject derived_type; + + /// \brief Free the external resources held by an action. + /// + /// \param action The action to free its resources from; NULL actions are + /// valid. + /// + /// \remarks The action is executed instantaneously. + RemoveAction(const std::shared_ptr& action); + + virtual ~RemoveAction(); + + virtual std::unique_ptr clone() const override; + + virtual IActionObject::FrameState next_frame(real32 delta_time) override; + + virtual IActionObject::FrameState prev_frame(real32 delta_time) override; + + /// \brief This action does not support being paused. + virtual void pause(real32 delta_time) override; + + /// \brief This action does not support being resumed. + virtual void resume(real32 delta_time) override; + + /// \brief This action does not support being reset. + virtual void rewind(real32 delta_time) override; + + virtual void release() override; + + private: + static const char* DEBUG_CLASS_NAME; + + /// \brief The child action. + std::shared_ptr action_; +}; + +} // namespace nom + +#endif // include guard defined + +/// \class nom::RemoveAction +/// \ingroup actions +/// +/// \brief This interface exists essentially as a placeholder until the +/// engine's infrastructure is further developed. The current interface allows +/// for the convenient removal of sprites from being rendered, instead of +/// always needing to check to see if the action is running before rendering +/// the action's sprite. +/// +/// \remarks This action is not reversible; the reverse of this action is +/// the same action. +// +/// \note It is suggested that you use this method in order to preserve future +/// compatibility. +/// diff --git a/include/nomlib/actions/RepeatForAction.hpp b/include/nomlib/actions/RepeatForAction.hpp new file mode 100644 index 00000000..443cffd8 --- /dev/null +++ b/include/nomlib/actions/RepeatForAction.hpp @@ -0,0 +1,109 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#ifndef NOMLIB_ACTIONS_REPEAT_FOR_ACTION_HPP +#define NOMLIB_ACTIONS_REPEAT_FOR_ACTION_HPP + +#include + +#include "nomlib/config.hpp" +#include "nomlib/actions/IActionObject.hpp" + +namespace nom { + +/// \brief Repeats an action for a specified number of times +class RepeatForAction: public virtual IActionObject +{ + public: + /// \brief Allow access into our private parts for unit testing. + friend class ActionTest; + + typedef RepeatForAction self_type; + typedef IActionObject derived_type; + + /// \brief Repeats an action for a specified number of times. + /// + /// \param action An action object to repeat; NULL actions are valid. + /// \param num_repeats The number of times to repeat the action for. + RepeatForAction( const std::shared_ptr& action, + nom::size_type num_repeats ); + + virtual ~RepeatForAction(); + + virtual std::unique_ptr clone() const override; + + virtual IActionObject::FrameState next_frame(real32 delta_time) override; + + virtual IActionObject::FrameState prev_frame(real32 delta_time) override; + + virtual void pause(real32 delta_time) override; + + virtual void resume(real32 delta_time) override; + + virtual void rewind(real32 delta_time) override; + + virtual void release() override; + + /// \brief Set the speed factor of the child action. + /// + /// \remarks This has no effect on the parent (this object). + virtual void set_speed(real32 speed) override; + + /// \brief Set the timing mode of the child action. + /// + /// \remarks This has no effect on the parent (this object). + /// + /// \see nom::IActionObject::timing_curve_func + virtual void + set_timing_curve(const IActionObject::timing_curve_func& mode) override; + + private: + static const char* DEBUG_CLASS_NAME; + + IActionObject::FrameState + update(real32 delta_time, uint32 direction); + + /// \brief The child action. + std::shared_ptr action_; + nom::size_type elapsed_repeats_; + nom::size_type num_repeats_; +}; + +} // namespace nom + +#endif // include guard defined + +/// \class nom::RepeatForAction +/// \ingroup actions +/// +/// \brief ... +/// +/// \remarks This action is not reversible, but the child action may be. +/// Consult the documentation for the action in question for implementation +/// details. +/// diff --git a/include/nomlib/actions/RepeatForeverAction.hpp b/include/nomlib/actions/RepeatForeverAction.hpp new file mode 100644 index 00000000..cd1f9e90 --- /dev/null +++ b/include/nomlib/actions/RepeatForeverAction.hpp @@ -0,0 +1,106 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#ifndef NOMLIB_ACTIONS_REPEAT_FOREVER_ACTION_HPP +#define NOMLIB_ACTIONS_REPEAT_FOREVER_ACTION_HPP + +#include + +#include "nomlib/config.hpp" +#include "nomlib/actions/IActionObject.hpp" + +namespace nom { + +/// \brief Repeats an action forever +class RepeatForeverAction: public virtual IActionObject +{ + public: + /// \brief Allow access into our private parts for unit testing. + friend class ActionTest; + + typedef RepeatForeverAction self_type; + typedef IActionObject derived_type; + + /// \brief Repeats an action forever. + /// + /// \param action An action object to repeat; NULL actions are valid. + RepeatForeverAction(const std::shared_ptr& action); + + virtual ~RepeatForeverAction(); + + virtual std::unique_ptr clone() const override; + + virtual IActionObject::FrameState next_frame(real32 delta_time) override; + + virtual IActionObject::FrameState prev_frame(real32 delta_time) override; + + virtual void pause(real32 delta_time) override; + + virtual void resume(real32 delta_time) override; + + virtual void rewind(real32 delta_time) override; + + virtual void release() override; + + /// \brief Set the speed factor of the child action. + /// + /// \remarks This has no effect on the parent (this object). + virtual void set_speed(real32 speed) override; + + /// \brief Set the timing mode of the child action. + /// + /// \remarks This has no effect on the parent (this object). + /// + /// \see nom::IActionObject::timing_curve_func + virtual void + set_timing_curve(const IActionObject::timing_curve_func& mode) override; + + private: + static const char* DEBUG_CLASS_NAME; + + IActionObject::FrameState + update(real32 delta_time, uint32 direction); + + /// \brief The child action. + std::shared_ptr action_; + nom::size_type elapsed_repeats_; +}; + +} // namespace nom + +#endif // include guard defined + +/// \class nom::RepeatForeverAction +/// \ingroup actions +/// +/// \brief ... +/// +/// \remarks This action is not reversible, but the child action may be. +/// Consult the documentation for the action in question for implementation +/// details. +/// diff --git a/include/nomlib/actions/ReversedAction.hpp b/include/nomlib/actions/ReversedAction.hpp new file mode 100644 index 00000000..54c0fa7d --- /dev/null +++ b/include/nomlib/actions/ReversedAction.hpp @@ -0,0 +1,95 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#ifndef NOMLIB_ACTIONS_REVERSED_ACTION_HPP +#define NOMLIB_ACTIONS_REVERSED_ACTION_HPP + +#include + +#include "nomlib/config.hpp" +#include "nomlib/actions/IActionObject.hpp" + +namespace nom { + +/// \brief Reverse the behavior of an action +class ReversedAction: public virtual IActionObject +{ + public: + /// \brief Allow access into our private parts for unit testing. + friend class ActionTest; + + typedef ReversedAction self_type; + typedef IActionObject derived_type; + + /// \brief Reverse the behavior of an action. + /// + /// \param action An action object to repeat; NULL actions are valid. + /// + /// \remarks Not all actions are reversible -- see the action's + /// documentation for its implementation details. + ReversedAction(const std::shared_ptr& action); + + virtual ~ReversedAction(); + + virtual std::unique_ptr clone() const override; + + virtual IActionObject::FrameState next_frame(real32 delta_time) override; + + virtual IActionObject::FrameState prev_frame(real32 delta_time) override; + + virtual void pause(real32 delta_time) override; + + virtual void resume(real32 delta_time) override; + + virtual void rewind(real32 delta_time) override; + + virtual void release() override; + + /// \brief Set the speed factor of the child action. + /// + /// \remarks This has no effect on the parent (this object). + virtual void set_speed(real32 speed) override; + + /// \brief Set the timing mode of the child action. + /// + /// \remarks This has no effect on the parent (this object). + /// + /// \see nom::IActionObject::timing_curve_func + virtual void + set_timing_curve(const IActionObject::timing_curve_func& mode) override; + + private: + static const char* DEBUG_CLASS_NAME; + + /// \brief The child action. + std::shared_ptr action_; +}; + +} // namespace nom + +#endif // include guard defined diff --git a/include/nomlib/actions/ScaleByAction.hpp b/include/nomlib/actions/ScaleByAction.hpp new file mode 100644 index 00000000..5a452602 --- /dev/null +++ b/include/nomlib/actions/ScaleByAction.hpp @@ -0,0 +1,113 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#ifndef NOMLIB_ACTIONS_SCALE_BY_ACTION_HPP +#define NOMLIB_ACTIONS_SCALE_BY_ACTION_HPP + +#include + +#include "nomlib/config.hpp" +#include "nomlib/actions/IActionObject.hpp" +#include "nomlib/math/Size2.hpp" +#include "nomlib/math/Rect.hpp" + +namespace nom { + +// Forward declarations +class Sprite; + +/// \brief Apply a relative scale factor to a sprite +class ScaleByAction: public virtual IActionObject +{ + public: + /// \brief Allow access into our private parts for unit testing. + friend class ActionTest; + + typedef ScaleByAction self_type; + typedef IActionObject derived_type; + + /// \brief Apply a relative scale factor to a sprite. + /// + /// \param drawable A valid nom::Sprite instance. + /// \param delta The total scale factor to apply to the drawable over time. + /// \param seconds The duration of the animation. + /// + /// \remarks Negative delta values are valid. + ScaleByAction( const std::shared_ptr& drawable, + const Size2f& delta, real32 seconds ); + + virtual ~ScaleByAction(); + + virtual std::unique_ptr clone() const override; + + virtual IActionObject::FrameState next_frame(real32 delta_time) override; + + virtual IActionObject::FrameState prev_frame(real32 delta_time) override; + + virtual void pause(real32 delta_time) override; + + virtual void resume(real32 delta_time) override; + + virtual void rewind(real32 delta_time) override; + + virtual void release() override; + + private: + static const char* DEBUG_CLASS_NAME; + + IActionObject::FrameState + update(real32 t, const Size2i& b, const Size2f& c, real32 d); + + void first_frame(real32 delta_time); + void last_frame(real32 delta_time); + + const Size2f total_displacement_ = Size2f::zero; + Size2i initial_size_ = Size2i::zero; + + std::shared_ptr drawable_; + + /// \brief The current size of the drawable. + Size2i size_ = Size2i::zero; +}; + +} // namespace nom + +#endif // include guard defined + +/// \class nom::ScaleByAction +/// \ingroup actions +/// +/// \brief ... +/// +/// \remarks This action is reversible; the reverse is created as if the +/// following code was executed: +/// +/// \code +/// ScaleByAction(drawable, -delta, seconds); +/// \endcode +/// diff --git a/include/nomlib/actions/SequenceAction.hpp b/include/nomlib/actions/SequenceAction.hpp new file mode 100644 index 00000000..2812f2cd --- /dev/null +++ b/include/nomlib/actions/SequenceAction.hpp @@ -0,0 +1,127 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#ifndef NOMLIB_ACTIONS_SEQUENCE_ACTION_HPP +#define NOMLIB_ACTIONS_SEQUENCE_ACTION_HPP + +#include +#include + +#include "nomlib/config.hpp" +#include "nomlib/actions/IActionObject.hpp" + +namespace nom { + +/// \brief Run a collection of actions sequentially +class SequenceAction: public virtual IActionObject +{ + public: + /// \brief Allow access into our private parts for unit testing. + friend class ActionTest; + + typedef SequenceAction self_type; + typedef IActionObject derived_type; + + /// \brief Run a collection of actions sequentially. + /// + /// \param actions The group of actions to execute; NULL actions are + /// valid. + SequenceAction(const action_list& actions); + + virtual ~SequenceAction(); + + virtual std::unique_ptr clone() const override; + + virtual IActionObject::FrameState next_frame(real32 delta_time) override; + + virtual IActionObject::FrameState prev_frame(real32 delta_time) override; + + virtual void pause(real32 delta_time) override; + + virtual void resume(real32 delta_time) override; + + virtual void rewind(real32 delta_time) override; + + virtual void release() override; + + /// \brief Set the speed factor of the child actions. + /// + /// \remarks This has no effect on the parent (this object). + virtual void set_speed(real32 speed) override; + + /// \brief Set the timing mode of the child actions. + /// + /// \remarks This has no effect on the parent (this object). + /// + /// \see nom::IActionObject::timing_curve_func + virtual void + set_timing_curve(const IActionObject::timing_curve_func& mode) override; + + private: + static const char* DEBUG_CLASS_NAME; + + IActionObject::FrameState + update(real32 delta_time, uint32 direction); + + typedef std::vector> container_type; + typedef container_type::iterator container_iterator; + + const container_type& actions() const; + + /// \brief The child actions. + container_type actions_; + container_iterator actions_iterator_; + + /// \brief The total number of completed actions. + nom::size_type num_completed_; + + /// \brief The total number of actions at the time of construction. + nom::size_type num_actions_; +}; + +} // namespace nom + +#endif // include guard defined + +/// \class nom::SequenceAction +/// \ingroup actions +/// +/// \brief This action acts on behalf of other actions as a proxy, running +/// actions to completion, one at a time, before iterating to the next +/// action, in FIFO order. The total duration of the sequence action is the sum +/// of every action's durations in the sequence. +/// +/// \remarks This action is not reversible, but the child actions may be +/// reversible. Consult the documentation for the action in question for +/// implementation details. +/// +/// \note Sequence actions are particularly useful for transition-style +/// animations. +/// +/// \see nom::WaitForDurationAction, nom::GroupAction +/// diff --git a/include/nomlib/actions/SpriteBatchAction.hpp b/include/nomlib/actions/SpriteBatchAction.hpp new file mode 100644 index 00000000..87779739 --- /dev/null +++ b/include/nomlib/actions/SpriteBatchAction.hpp @@ -0,0 +1,116 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#ifndef NOMLIB_ACTIONS_SPRITE_BATCH_ACTION_HPP +#define NOMLIB_ACTIONS_SPRITE_BATCH_ACTION_HPP + +#include + +#include "nomlib/config.hpp" +#include "nomlib/actions/IActionObject.hpp" + +namespace nom { + +// Forward declarations +class SpriteBatch; + +/// \brief Animate a sprite using a sprite sheet +class SpriteBatchAction: public virtual IActionObject +{ + public: + /// \brief Allow access into our private parts for unit testing. + friend class ActionTest; + + typedef SpriteBatchAction self_type; + typedef IActionObject derived_type; + + /// \brief Animate a sprite using a sprite sheet. + /// + /// \param drawable A valid nom::SpriteBatch instance with a pre-loaded + /// nom::SpriteSheet filled with the frames to iterate through. + /// + /// \param frame_interval_seconds The duration (in seconds) that each + /// texture is displayed. + /// + /// \see nom::SpriteBatch, nom::SpriteSheet + SpriteBatchAction( const std::shared_ptr& drawable, + real32 frame_interval_seconds ); + + virtual ~SpriteBatchAction(); + + virtual std::unique_ptr clone() const override; + + virtual IActionObject::FrameState next_frame(real32 delta_time) override; + + virtual IActionObject::FrameState prev_frame(real32 delta_time) override; + + virtual void pause(real32 delta_time) override; + + virtual void resume(real32 delta_time) override; + + virtual void rewind(real32 delta_time) override; + + virtual void release() override; + + private: + static const char* DEBUG_CLASS_NAME; + + IActionObject::FrameState + update(real32 t, real32 b, real32 c, real32 d); + + void first_frame(real32 delta_time); + void last_frame(real32 delta_time); + + /// \brief The initial sprite sheet frame to animate from. + uint32 initial_frame_; + + /// \brief The total number of sprite sheet frames to animate. + real32 total_displacement_; + + /// \brief The drawable sprite. + std::shared_ptr drawable_; + + /// \brief The delay (in seconds) before displaying the next texture. + real32 frame_interval_; + real32 last_delta_; +}; + +} // namespace nom + +#endif // include guard defined + +/// \class nom::SpriteBatchAction +/// \ingroup actions +/// +/// \brief ... +/// +/// \remarks This action is reversible; the texture frames are displayed in the +/// inverse order. For example, if the texture frames are +/// {"tex0", "tex1", "tex2"}, the reversed order would be +/// {"tex2", "tex1", "tex0"}. +/// diff --git a/include/nomlib/audio/NullSoundSource.hpp b/include/nomlib/actions/WaitForDurationAction.hpp similarity index 50% rename from include/nomlib/audio/NullSoundSource.hpp rename to include/nomlib/actions/WaitForDurationAction.hpp index abc7af42..c7955fb2 100644 --- a/include/nomlib/audio/NullSoundSource.hpp +++ b/include/nomlib/actions/WaitForDurationAction.hpp @@ -26,66 +26,64 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ -#ifndef NOMLIB_AUDIO_NULL_SOUND_SOURCE_HPP -#define NOMLIB_AUDIO_NULL_SOUND_SOURCE_HPP +#ifndef NOMLIB_ACTIONS_WAIT_FOR_DURATION_ACTION_HPP +#define NOMLIB_ACTIONS_WAIT_FOR_DURATION_ACTION_HPP #include #include "nomlib/config.hpp" -#include "nomlib/math/Point3.hpp" -#include "nomlib/audio/ISoundSource.hpp" +#include "nomlib/actions/IActionObject.hpp" namespace nom { -class NullSoundSource: public ISoundSource +/// \brief Create an action that idles for a specified period of time +class WaitForDurationAction: public virtual IActionObject { public: - virtual ~NullSoundSource( void ); + /// \brief Allow access into our private parts for unit testing. + friend class ActionTest; - float getVolume ( void ) const; - float getMinVolume ( void ) const; - float getMaxVolume ( void ) const; - float getPitch ( void ) const; - bool getLooping ( void ) const; + typedef WaitForDurationAction self_type; + typedef IActionObject derived_type; - Point3f getPosition ( void ) const; - Point3f getVelocity ( void ) const; + /// \brief Create an action that idles for a specified period of time. + /// + /// \param seconds The duration of the idle. + WaitForDurationAction(real32 seconds); - bool getPositionRelativeToListener ( void ) const; - float getMinDistance ( void ) const; - float getAttenuation ( void ) const; + virtual ~WaitForDurationAction(); - int32 getBufferID ( void ) const; + virtual std::unique_ptr clone() const override; - float getPlayPosition ( void ) const; + virtual IActionObject::FrameState next_frame(real32 delta_time) override; - SoundStatus getStatus ( void ) const; + virtual IActionObject::FrameState prev_frame(real32 delta_time) override; - void setVolume ( float gain ); - void setMinVolume ( float gain ); - void setMaxVolume ( float gain ); - void setPitch ( float pitch ); - void setLooping ( bool loops ); + virtual void pause(real32 delta_time) override; - void setPosition ( float x, float y, float z ); - void setPosition ( const Point3f& position ); + virtual void resume(real32 delta_time) override; - void setVelocity ( float x, float y, float z ); - void setVelocity ( const Point3f& velocity ); + virtual void rewind(real32 delta_time) override; - void setPositionRelativeToListener ( bool position ); - void setMinDistance ( float distance ); - void setAttenuation ( float attenuation ); - void setPlayPosition ( float seconds ); + virtual void release() override; - void togglePause( void ); - void fadeOut( float seconds ); - - protected: - /// Constructor can only be called from deriving classes - NullSoundSource( void ); + private: + static const char* DEBUG_CLASS_NAME; }; } // namespace nom #endif // include guard defined + +/// \class nom::WaitForDurationAction +/// \ingroup actions +/// +/// \brief When the action executes, the action waits for the specified amount +/// of time, then ends. This is typically used as part of a sequence of actions +/// to insert a delay between two other actions. You might also use it in +/// conjunction with nom::ActionPlayer::run_action(action, completion_func) +/// to trigger logic that needs to run at a later time. +/// +/// \remarks This action is not reversible; the reverse of this action is +/// the same action. +/// diff --git a/include/nomlib/audio.hpp b/include/nomlib/audio.hpp index 01b2337b..bab7410c 100644 --- a/include/nomlib/audio.hpp +++ b/include/nomlib/audio.hpp @@ -2,7 +2,7 @@ nomlib - C++11 cross-platform game engine -Copyright (c) 2013, 2014 Jeffrey Carpenter +Copyright (c) 2013, 2014, 2015, 2016 Jeffrey Carpenter All rights reserved. Redistribution and use in source and binary forms, with or without @@ -31,28 +31,24 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // Public header file -#include - -#include "nomlib/audio/IAudioDevice.hpp" -#include "nomlib/audio/IListener.hpp" -#include "nomlib/audio/ISoundBuffer.hpp" -#include "nomlib/audio/ISoundSource.hpp" -#include "nomlib/audio/NullAudioDevice.hpp" -#include "nomlib/audio/NullListener.hpp" -#include "nomlib/audio/NullSoundBuffer.hpp" -#include "nomlib/audio/NullSoundSource.hpp" -#include "nomlib/audio/NullSound.hpp" -#include "nomlib/audio/NullMusic.hpp" -#include "nomlib/audio/AudioDeviceLocator.hpp" - -#if defined( NOM_USE_OPENAL ) - #include "nomlib/audio/AL/AudioDevice.hpp" - #include "nomlib/audio/AL/Listener.hpp" - #include "nomlib/audio/AL/Music.hpp" - #include "nomlib/audio/AL/Sound.hpp" - #include "nomlib/audio/AL/SoundBuffer.hpp" - #include "nomlib/audio/AL/SoundFile.hpp" - #include "nomlib/audio/AL/SoundSource.hpp" +#include + +#include +#include +#include + +#if defined(NOM_USE_OPENAL) + // #include + #include + #include + #include +#endif + +// TODO(jeff): Setup conditional compilation unit for this back-end in time to +// come..? +#if defined(NOM_USE_LIBSNDFILE) + #include + #include #endif #endif // include guard defined diff --git a/include/nomlib/audio/AL/ALAudioDevice.hpp b/include/nomlib/audio/AL/ALAudioDevice.hpp new file mode 100644 index 00000000..b4d2e895 --- /dev/null +++ b/include/nomlib/audio/AL/ALAudioDevice.hpp @@ -0,0 +1,168 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014, 2015, 2016 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#ifndef NOMLIB_AL_AUDIO_DEVICE_HEADERS +#define NOMLIB_AL_AUDIO_DEVICE_HEADERS + +#include "nomlib/config.hpp" +#include "nomlib/core/IObject.hpp" +#include "nomlib/math/Point3.hpp" +#include "nomlib/audio/audio_defs.hpp" +#include "nomlib/audio/SoundFile.hpp" + +#include +#include + +// Forward declarations +struct ALCdevice_struct; +struct ALCcontext_struct; + +namespace nom { +namespace audio { + +class IOAudioEngine; +struct AudioSpec; + +ALCdevice_struct* current_device(); +ALCcontext_struct* current_context(); + +void free_audio_device(ALCdevice_struct* dev); +void free_audio_context(ALCcontext_struct* ctx); + +void* +process_addr(const char* token); + +// TODO(jeff): Verify if we should be using alIsExtensionPresent or +// alcIsExtensionPresent for querying extensions, etc. + +/// Obtain support info regarding a particular extension +bool extension_available(const char* key); + +/// Obtain support info regarding a particular extension +// bool extension_available(const char* key, void* target); + +// TODO(jeff): Rename function -- something along the lines of +// config_available..? +// if err, return zero; if successful, return the key's value, a number greater +// than zero. +uint32 enum_available(const char* key); + +bool +context_extension(const char* key, ALCdevice_struct* target); + +typedef std::vector device_name_list; + +const char* default_output_device_name(ALCdevice_struct* target); +const char* default_input_device_name(ALCdevice_struct* target); + +// TODO(jeff): Try to factor out the use of std::vector here +device_name_list output_device_names(void* target); + +// TODO(jeff): Implement function +device_name_list input_device_names(void* target); + +int refresh_rate(void* target); +bool sync_context(void* target); + +/// \brief Get the sampling rate of audio playback. +int sample_rate(void* target); + +/// \brief Set the sampling rate of audio playback. +// void set_sample_rate(int sample_rate, void* target); + +int max_mono_sources(void* target); +int max_stereo_sources(void* target); + +/// \brief Get the maximum number of simultaneous playing audio buffers. +int max_sources(void* target); + +/// \brief Set the maximum number of simultaneous playing audio buffers. +// void set_max_sources(int num_sources, void* target); + +bool cap(uint32 caps, uint32 key); +void set_cap(uint32* caps, uint32 format); + +struct AudioDevice +{ + // ALAudioDevice + void* impl = nullptr; +}; + +/// \see http://www.opensource.apple.com/source/OpenAL/OpenAL-54/Source/OpenAL/ +// TODO(jeff): Private header..? +struct ALAudioDevice +{ + /// Audio device handle + ALCdevice_struct* dev = nullptr; + + /// Audio device context + ALCcontext_struct* ctx = nullptr; + + // IOAudioEngine* impl = nullptr; + + const char* name = "ALAudioDevice"; + + // uint32 max_channels = 0; + + uint32 capabilities = CAPS_UNDEFINED; + + // real64 elapsed_ticks_ = 0.0f; + + // typedef std::function input_devs_func; + typedef std::function output_devs_func; + + output_devs_func enumerate_output_devices; + // input_devs_func enumerate_input_devices; +}; + +IOAudioEngine* +init_audio(const audio::AudioSpec* request, audio::AudioSpec* spec); + +// void shutdown_openal(ALAudioDevice* impl); +void shutdown_audio(IOAudioEngine* impl); + +// const char* audio_device_name(IOAudioEngine* target); + +/// \brief Get the number of audio input/output ports that are available. +/// +/// \returns ... +int num_audio_devices(); + +/// \brief +/// +/// \param dev An audio device that has been created with audio::init_audio. +/// +/// \returns ... +// const char* audio_device(void* target); + +// real64 device_time(void* target); + +} // namespace audio +} // namespace nom + +#endif // NOMLIB_AL_AUDIO_DEVICE_HEADERS defined diff --git a/include/nomlib/audio/AL/ALAudioDeviceCaps.hpp b/include/nomlib/audio/AL/ALAudioDeviceCaps.hpp new file mode 100644 index 00000000..287a2345 --- /dev/null +++ b/include/nomlib/audio/AL/ALAudioDeviceCaps.hpp @@ -0,0 +1,176 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014, 2015, 2016 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#ifndef NOMLIB_AUDIO_AL_DEVICE_CAPS_HPP +#define NOMLIB_AUDIO_AL_DEVICE_CAPS_HPP + +#include "nomlib/config.hpp" +#include "nomlib/math/Point3.hpp" +#include "nomlib/audio/IOAudioEngine.hpp" + +// FIXME(jeff): enums +#include "nomlib/audio/AL/SoundSource.hpp" + +// Forward declarations +struct ALCdevice_struct; +struct ALCcontext_struct; + +namespace nom { +namespace audio { + +// Forward declarations +struct SoundBuffer; +// struct AudioSpec; +struct ALAudioDevice; + +// Function declarations + +void free_buffer(uint32 buffer_id); +void free_source(uint32 buffer_id); + +uint32 buffer_id(const SoundBuffer* target); +uint32 source_id(const SoundBuffer* target); + +/// \brief Register a buffer with the audio subsystem. +/// +/// \returns A unique identifier, representing its allocation made with the +/// underlying audio system. +/// +/// \see nom::audio::create_buffer +uint32 next_buffer_id(); + +/// \brief Register an array of buffers with the audio subsystem. +/// +/// \returns When successful, a pointer to the beginning of an array the size +/// of the given number of sources with 32-bit integer offset alignment. On +/// failure to register the total given number of sources, a NULL pointer is +/// returned. +/// +/// \see nom::audio::create_buffer +uint32* next_buffer_id(uint32 num_buffers); + +/// \brief Register a buffer with the audio subsystem. +/// +/// \returns A unique identifier, representing its allocation made with the +/// underlying audio system. +/// +/// \see nom::audio::fill_audio_buffer +uint32 next_source_id(); + +/// \brief Register an array of sound sources with the audio subsystem. +/// +/// \returns When successful, a pointer to the beginning of an array the size +/// of the given number of sources with 32-bit integer offset alignment. On +/// failure to register the total given number of sources, a NULL pointer is +/// returned. +/// +/// \see nom::audio::create_buffer +uint32* next_source_id(uint32 num_sources); + +// TODO(jeff): Add const to getters! +class ALAudioEngine: public IOAudioEngine +{ + public: + ALAudioEngine(); + virtual ~ALAudioEngine(); + + ALAudioEngine(ALAudioDevice* driver); + virtual void init(void* driver) override; + + virtual bool valid() const override; + + virtual uint32 caps() const override; + virtual void set_cap(uint32 format) override; + + virtual bool connected() const override; + + /// \brief Find a suitable data format for OpenAL. + virtual + uint32 channel_format(uint32 num_channels, uint32 channel_format) override; + + virtual bool valid_buffer(SoundBuffer* target) override; + virtual bool valid_source(SoundBuffer* target) override; + + virtual uint32 state(SoundBuffer* target) override; + virtual real32 pitch(SoundBuffer* target) override; + + virtual real32 volume() const override; + virtual Point3f position() const override; + + virtual real32 volume(SoundBuffer* target) const override; + virtual real32 min_volume(SoundBuffer* target) override; + virtual real32 max_volume(SoundBuffer* target) override; + + virtual Point3f velocity(SoundBuffer* target) override; + virtual Point3f position(SoundBuffer* target) override; + virtual real32 playback_position(SoundBuffer* target) override; + virtual real32 playback_samples(SoundBuffer* target) override; + + // virtual void set_state(SoundBuffer* target, uint32 state) override; + + virtual void set_volume(real32 gain) override; + virtual void set_position(const Point3f& p) override; + + virtual void set_min_volume(SoundBuffer* target, real32 gain) override; + virtual void set_max_volume(SoundBuffer* target, real32 gain) override; + virtual void set_volume(SoundBuffer* target, real32 gain) override; + + virtual void set_velocity(SoundBuffer* target, const Point3f& v) override; + virtual void set_position(SoundBuffer* target, const Point3f& p) override; + virtual void set_pitch(SoundBuffer* target, real32 pitch) override; + virtual void set_playback_position(SoundBuffer* target, real32 offset_seconds) override; + virtual void play(SoundBuffer* target) override; + virtual void stop(SoundBuffer* target) override; + virtual void pause(SoundBuffer* target) override; + virtual void resume(SoundBuffer* target) override; + + virtual bool push_buffer(SoundBuffer* target) override; + virtual bool queue_buffer(SoundBuffer* target) override; + + // IMPORTANT(jeff): The audio hardware capabilities, maximum sources, + // limitations and so on are subject to change anytime that we touch the + // audio context state! + virtual void suspend() override; + virtual void resume() override; + virtual void close() override; + + virtual void free_buffer(SoundBuffer* target) override; + + private: + bool fill_buffer(SoundBuffer* target); + + virtual void close_context(); + virtual void close_device(); + + ALAudioDevice* impl_ = nullptr; +}; + +} // namespace audio +} // namespace nom + +#endif // include guard defined diff --git a/include/nomlib/audio/AL/AudioDevice.hpp b/include/nomlib/audio/AL/AudioDevice.hpp deleted file mode 100644 index c76b343a..00000000 --- a/include/nomlib/audio/AL/AudioDevice.hpp +++ /dev/null @@ -1,98 +0,0 @@ -/****************************************************************************** - - nomlib - C++11 cross-platform game engine - -Copyright (c) 2013, 2014 Jeffrey Carpenter -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -******************************************************************************/ -#ifndef NOMLIB_AL_AUDIO_DEVICE_HEADERS -#define NOMLIB_AL_AUDIO_DEVICE_HEADERS - -#include - -#include "nomlib/config.hpp" -#include "nomlib/audio/IAudioDevice.hpp" - -// Forward declarations -struct ALCdevice_struct {}; -struct ALCcontext_struct {}; -typedef char ALCchar; - -namespace nom { - namespace priv { - -/// Custom deleter for freeing an OpenAL audio device; debugging aid. -void AL_FreeAudioDevice ( ALCdevice_struct* ); - -/// Custom deleter for freeing an OpenAL audio context; debugging aid. -void AL_FreeAudioContext ( ALCcontext_struct* ); - - } // namespace priv -} // namespace nom - - -namespace nom { - -class AudioDevice: public IAudioDevice -{ - public: - /// Default constructor for initializing the default audio device - AudioDevice ( void ); - - /// Constructor variant for initializing a specific audio device - AudioDevice ( const std::string& device_name ); - - virtual ~AudioDevice( void ); - - /// Obtain the initialized OpenAL audio device - // std::shared_ptr getAudioDevice ( void ) const; - - /// Obtain the initialized audio device name - const std::string getDeviceName ( void ) const; - - /// Obtain support info regarding a particular extension - bool isExtensionSupported ( const std::string& extension ) const; - - // frequency - // Suspend context - // Resume context - - private: - /// This keeps OpenAL from all sorts of odd crashes by only allowing - /// initialization to occur once - static bool audio_initialized; - bool initialize ( const ALCchar* device_name ); - /// ... - bool initialized ( void ) const; - /// Audio device handle - std::shared_ptr audio_device; - /// Audio device context - std::shared_ptr audio_context; - /// device name - const ALCchar *device_name; -}; - -} // namespace nom - -#endif // NOMLIB_AL_AUDIO_DEVICE_HEADERS defined diff --git a/include/nomlib/audio/AL/Listener.hpp b/include/nomlib/audio/AL/Listener.hpp deleted file mode 100644 index 449342ab..00000000 --- a/include/nomlib/audio/AL/Listener.hpp +++ /dev/null @@ -1,89 +0,0 @@ -/****************************************************************************** - - nomlib - C++11 cross-platform game engine - -Copyright (c) 2013, 2014 Jeffrey Carpenter -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -******************************************************************************/ -#ifndef NOMLIB_AL_LISTENER_HEADERS -#define NOMLIB_AL_LISTENER_HEADERS - -#include -#include - -#include "nomlib/config.hpp" -#include "nomlib/math/Point3.hpp" -#include "nomlib/audio/IListener.hpp" - -namespace nom { - -/// \brief Global volume control -/// -/// \remarks The use of this class is optional; the OpenAL API provides one by -/// default. -class Listener: public IListener -{ - public: - Listener ( void ); - virtual ~Listener( void ); - - /// Obtain master gain (volume) - /// - /// Volume is between 0 (muted) and 100 (max volume) - /// - /// Default: 100 - float getVolume ( void ) const; - - /// Obtain position - const Point3f getPosition ( void ) const; - - /// Obtain velocity - const Point3f getVelocity ( void ) const; - - /// Obtain direction - const Point3f getDirection ( void ) const; - - /// Set position - void setPosition ( float x, float y, float z ); - void setPosition ( const Point3f& position ); - - /// Set velocity - void setVelocity ( float x, float y, float z ); - void setVelocity ( const Point3f& velocity ); - - /// Set direction - void setDirection ( float x, float y, float z ); - void setDirection ( const Point3f& direction ); - - /// Set master gain (volume) - /// - /// Volume is between 0 (muted) and 100 (max volume) - /// - /// Default: 100 - void setVolume ( float gain ); -}; - -} // namespace nom - -#endif // NOMLIB_AL_LISTENER_HEADERS defined diff --git a/include/nomlib/audio/AL/Music.hpp b/include/nomlib/audio/AL/Music.hpp deleted file mode 100644 index 0879fe84..00000000 --- a/include/nomlib/audio/AL/Music.hpp +++ /dev/null @@ -1,59 +0,0 @@ -/****************************************************************************** - - nomlib - C++11 cross-platform game engine - -Copyright (c) 2013, 2014 Jeffrey Carpenter -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -******************************************************************************/ -#ifndef NOMLIB_AL_MUSIC_HEADERS -#define NOMLIB_AL_MUSIC_HEADERS - -#include -#include - -#include "nomlib/config.hpp" -#include "nomlib/audio/AL/SoundSource.hpp" - -namespace nom { - -class Music: public SoundSource -{ - public: - Music ( void ); - Music ( const ISoundBuffer& copy ); - virtual ~Music( void ); - - void setBuffer ( const ISoundBuffer& copy ); - - void Play ( void ); - void Stop ( void ); - void Pause ( void ); - - private: - //SoundFile *fp; -}; - -} // namespace nom - -#endif // NOMLIB_AL_MUSIC_HEADERS defined diff --git a/include/nomlib/audio/AL/OpenAL.hpp b/include/nomlib/audio/AL/OpenAL.hpp index f5f2e4a6..3307a79b 100644 --- a/include/nomlib/audio/AL/OpenAL.hpp +++ b/include/nomlib/audio/AL/OpenAL.hpp @@ -29,43 +29,79 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef NOMLIB_OPENAL_HEADERS #define NOMLIB_OPENAL_HEADERS -#include -#include -#include - #include "nomlib/config.hpp" -#include "nomlib/core/clock.hpp" -#if defined(NOM_PLATFORM_OSX) // Use platform-distributed OpenAL headers +#if defined(NOM_USE_CREATIVE_OPENAL) || defined(NOM_USE_OPENAL_SOFT) + // Use OpenAL-soft distributed SDK headers + #include + #include + #include +#elif defined(NOM_PLATFORM_OSX) && defined(NOM_USE_APPLE_OPENAL) + // Use Apple's distributed OpenAL SDK headers #include #include -#else // As per the header inclusion path that FindOpenAL.cmake offers us + #include +#else // As per the header inclusion path that FindOpenAL.cmake gives us #include #include + #include #endif -/// Optimal sound frame size (in bytes); used by libsndfile -const nom::uint32 BUFFER_SIZE = 4096; -const nom::uint32 NUM_SOURCES = 16; // not implemented - -const float MIN_VOLUME = 0.0; -const float MAX_VOLUME = 100.0; - -#ifdef NOM_DEBUG +#if defined(NOM_DEBUG) + // IMPORTANT: This macro is DEPRECATED, in favor of AL_CHECK_ERR_VOID. + // The rationale for this decision is readability (clarity); the argument, + // "function", serves no purpose and therefore can be misleading in its use. #define AL_CHECK_ERR(Function) \ - ( (Function ), nom::priv::al_err ( __FILE__, __LINE__ ) ) + ( (Function), nom::priv::al_err(NOM_FUNC, __FILE__, __LINE__) ) + + // TODO: Rename this macro to AL_CHECK_ERR after we have phased AL_CHECK_ERR + // out of the code base + #define AL_CHECK_ERR_VOID() \ + ( nom::priv::al_err(NOM_FUNC, __FILE__, __LINE__) ) #else - #define AL_CHECK_ERR(Function) ( Function ) + #define AL_CHECK_ERR(Function) (Function) + #define AL_CHECK_ERR_VOID() #endif +// Clear the error state of OpenAL +// +// TODO: Clear the error state before calling functions that we check err state +// on -- the err messages we see are potentially out of sync otherwise! +#define AL_CLEAR_ERR() alGetError(); + namespace nom { - namespace priv { +namespace priv { -void al_err ( const std::string& file, uint32 line ); +void al_err(const std::string& func, const std::string& file, uint32 line); - } // namespace priv +} // namespace priv } // namespace nom +namespace nom { +namespace audio { + +// enum AudioError +// { +// ERR_NO_ERROR = 0, +// ERR_SYSTEM_CALL_FAILURE, +// ERR_OUT_OF_MEMORY, +// ERR_INVALID_OPERATION, +// }; + +// struct err_t +// { +// uint32 error_code = 0; +// }; + +// // uint32 err(err_t* err); +// uint32 err(); +// const char* err_str(uint32 errno); + +// void set_err(uint32 errno); +// void set_err(err_t* err); + +} // namespace audio +} // namespace nom #endif // NOMLIB_OPENAL_HEADERS defined diff --git a/include/nomlib/audio/AL/Sound.hpp b/include/nomlib/audio/AL/Sound.hpp deleted file mode 100644 index 586182c7..00000000 --- a/include/nomlib/audio/AL/Sound.hpp +++ /dev/null @@ -1,82 +0,0 @@ -/****************************************************************************** - - nomlib - C++11 cross-platform game engine - -Copyright (c) 2013, 2014 Jeffrey Carpenter -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -******************************************************************************/ -#ifndef NOMLIB_AL_SOUND_HEADERS -#define NOMLIB_AL_SOUND_HEADERS - -#include - -#include "nomlib/config.hpp" -#include "nomlib/audio/AL/SoundSource.hpp" - -namespace nom { - -// forward declarations -class ISoundBuffer; - -/// \brief Audio interface for sound samples -class Sound: public SoundSource -{ - public: - /// \brief SoundBuffer needs access to Sound::reset. - friend class SoundBuffer; - - Sound ( void ); - Sound ( const ISoundBuffer& copy ); - virtual ~Sound( void ); - - void setBuffer ( const ISoundBuffer& copy ); - - void Play ( void ); - void Stop ( void ); - void Pause ( void ); - - /// Obtain the current playback position of source in seconds - //float getPlayPosition ( void ) const; - - /// Set playback position of source in seconds - //void setPlayPosition ( float seconds ); - private: - /// Internally used by SoundBuffer class for properly freeing a sound from - /// its attached buffer - void reset( void ); - - /// Buffer that this sound is attached to - const ISoundBuffer* buffer; -}; - -} // namespace nom - -#endif // NOMLIB_AL_SOUND_HEADERS defined - -/// \class nom::Sound -/// \ingroup Audio -/// -/// A sample is the amplitude of the sound signal at a given point of time, and -/// an array -- 16-bit signed integers -- of samples therefore represents a full -/// sound. diff --git a/include/nomlib/audio/AL/SoundBuffer.hpp b/include/nomlib/audio/AL/SoundBuffer.hpp deleted file mode 100644 index 587e6a35..00000000 --- a/include/nomlib/audio/AL/SoundBuffer.hpp +++ /dev/null @@ -1,86 +0,0 @@ -/****************************************************************************** - - nomlib - C++11 cross-platform game engine - -Copyright (c) 2013, 2014 Jeffrey Carpenter -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -******************************************************************************/ -#ifndef NOMLIB_AL_SOUNDBUFFER_HEADERS -#define NOMLIB_AL_SOUNDBUFFER_HEADERS - -#include -#include - -#include "nomlib/config.hpp" -#include "nomlib/audio/ISoundBuffer.hpp" - -namespace nom { - -class SoundBuffer: public ISoundBuffer -{ - public: - SoundBuffer ( void ); - virtual ~SoundBuffer( void ); - - /// Obtain buffer data - uint32 get ( void ) const; - - /// Obtain buffer duration in milliseconds - /// - /// Default: zero (0) - int64 getDuration( void ) const; - - // getSampleRate - // getChannelCount - // ... - - bool load ( const std::string& filename ); - - private: - friend class Sound; // Sound class needs access to attach & detach methods - - /// Internal list of sounds loaded onto a buffer object - mutable std::set sounds; - - /// Internally used for attaching a sound to our list - void attach ( Sound* sound ) const; - - /// Internally used for detaching a sound from our list - void detach ( Sound* sound ) const; - - /// Used internally by OpenAL for identification of audio buffer - uint32 buffer; - - /// We load our audio data into this buffer - std::vector samples; - - /// Duration of sound buffer - /// - /// Default: zero (0) - int64 buffer_duration; -}; - -} // namespace nom - -#endif // NOMLIB_AL_SOUNDBUFFER_HEADERS defined diff --git a/include/nomlib/audio/AL/SoundFile.hpp b/include/nomlib/audio/AL/SoundFile.hpp deleted file mode 100644 index 3fc4fef0..00000000 --- a/include/nomlib/audio/AL/SoundFile.hpp +++ /dev/null @@ -1,88 +0,0 @@ -/****************************************************************************** - - nomlib - C++11 cross-platform game engine - -Copyright (c) 2013, 2014 Jeffrey Carpenter -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -******************************************************************************/ -#ifndef NOMLIB_AL_SOUNDFILE_HEADERS -#define NOMLIB_AL_SOUNDFILE_HEADERS - -#include -#include -#include -#include - -#include "nomlib/config.hpp" - -// Forward declarations -struct SNDFILE_tag {}; - -namespace nom { - -class SoundFile -{ - public: - SoundFile ( void ); - ~SoundFile ( void ); - - /// Obtain number of samples in audio file - int64 getSampleCount ( void ) const; - - /// Obtain number of channels used by audio file - uint32 getChannelCount ( void ) const; - - /// Obtain audio sampling rate; this is the number of samples per second - uint32 getSampleRate ( void ) const; - - /// Obtain channel format; used internally by OpenAL - uint32 getChannelFormat ( void ) const; - - /// Obtain audio data size in bytes - int64 getDataByteSize ( void ) const; - - bool open ( const std::string& filename ); - bool read ( std::vector& data ); - - private: - /// SNDFILE* file descriptor - /// \todo Change me to a std::unique_ptr - std::shared_ptr fp; - /// Extracted audio stream from file - std::vector samples; - /// Total number of samples in the file - int64 sample_count; - /// Number of audio channels used by sound - uint32 channel_count; - /// Number of samples per second - uint32 sample_rate; - /// OpenAL compatible audio channels used by sound - int32 channel_format; -}; - -std::string libsndfile_version(); - -} // namespace nom - -#endif // NOMLIB_AL_SOUNDFILE_HEADERS defined diff --git a/include/nomlib/audio/AL/SoundSource.hpp b/include/nomlib/audio/AL/SoundSource.hpp index ce560218..3799b984 100644 --- a/include/nomlib/audio/AL/SoundSource.hpp +++ b/include/nomlib/audio/AL/SoundSource.hpp @@ -2,7 +2,7 @@ nomlib - C++11 cross-platform game engine -Copyright (c) 2013, 2014 Jeffrey Carpenter +Copyright (c) 2013, 2014, 2015, 2016 Jeffrey Carpenter All rights reserved. Redistribution and use in source and binary forms, with or without @@ -34,75 +34,228 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "nomlib/config.hpp" #include "nomlib/math/Point3.hpp" -#include "nomlib/audio/ISoundSource.hpp" namespace nom { +namespace audio { -/// \brief Base class for audio inputs -class SoundSource: public ISoundSource +// Forward declarations +class IOAudioEngine; +struct SoundBuffer; +class ISoundFileReader; +struct SoundInfo; + +/// Sound source is one of the three states: stopped, paused or playing +enum AudioState +{ + AUDIO_STATE_INITIAL = 0, + AUDIO_STATE_STOPPED, + AUDIO_STATE_PAUSED, + AUDIO_STATE_PLAYING, + AUDIO_STATE_STREAMING, + + // TODO: Bit mask..? + AUDIO_STATE_LOOPING +}; + +enum AudioSourceType { - public: - virtual ~SoundSource( void ); - - /// Get source volume level - /// - /// Volume is between 0 (muted) and 100 (max volume) - /// - /// Default: 100 - float getVolume ( void ) const; - float getMinVolume ( void ) const; - float getMaxVolume ( void ) const; - float getPitch ( void ) const; - bool getLooping ( void ) const; - - Point3f getPosition ( void ) const; - Point3f getVelocity ( void ) const; - - bool getPositionRelativeToListener ( void ) const; - float getMinDistance ( void ) const; - float getAttenuation ( void ) const; - - /// Obtain buffer identifier of source - int32 getBufferID ( void ) const; - /// Obtain the current playback position of source in seconds - float getPlayPosition ( void ) const; - /// Obtain current state of sound - SoundStatus getStatus ( void ) const; - - /// Set source volume level - /// - /// Volume is between 0 (muted) and 100 (max volume) - /// - /// Default: 100 - void setVolume ( float gain ); - void setMinVolume ( float gain ); - void setMaxVolume ( float gain ); - void setPitch ( float pitch ); - void setLooping ( bool loops ); - - void setPosition ( float x, float y, float z ); - void setPosition ( const Point3f& position ); - - void setVelocity ( float x, float y, float z ); - void setVelocity ( const Point3f& velocity ); - - void setPositionRelativeToListener ( bool position ); - void setMinDistance ( float distance ); - void setAttenuation ( float attenuation ); - /// Set playback position of source in seconds - void setPlayPosition ( float seconds ); - - virtual void togglePause( void ); - virtual void fadeOut( float seconds ); - - protected: - /// Constructor can only be called from deriving classes - SoundSource( void ); - - /// Source identification; used by OpenAL - uint32 source_id; + AUDIO_TYPE_UNKNOWN = 0, + AUDIO_TYPE_STATIC, + AUDIO_TYPE_STREAMING, }; +// buffer creation + +uint32 channel_format(uint32 num_channels, uint32 channel_format, + IOAudioEngine* target); + +bool write_info(SoundBuffer* buffer, const SoundInfo& metadata); + +void* create_samples(nom::size_type alloc_bytes, uint32 num_channels, + uint32 channel_format); + +void free_samples(uint32 channel_format, void* data); + +SoundBuffer* create_buffer_memory(); + +SoundBuffer* +create_buffer_memory(nom::size_type total_sample_bytes, uint32 num_channels, + uint32 channel_format); + +SoundBuffer* +create_buffer(void* samples, const SoundInfo& metadata, IOAudioEngine* target); + +// TODO(jeff): Restructure this function..? Perhaps split it up. +SoundBuffer* +create_buffer(const std::string& filename, ISoundFileReader* fp, + IOAudioEngine* target); + +SoundBuffer* +create_buffer(const std::string& filename, IOAudioEngine* target); + +bool valid_buffer(SoundBuffer* buffer, IOAudioEngine* target); +bool valid_source(SoundBuffer* buffer, IOAudioEngine* target); + +SoundInfo info(SoundBuffer* buffer); + +void free_buffer(SoundBuffer* buffer, IOAudioEngine* target); + +// audio control + +// void queue(SoundBuffer* buffer, IOAudioEngine* target); + +void play(SoundBuffer* buffer, IOAudioEngine* target); + +void stop(SoundBuffer* buffer, IOAudioEngine* target); + +void pause(SoundBuffer* buffer, IOAudioEngine* target); + +void resume(SoundBuffer* buffer, IOAudioEngine* target); + +uint32 state(SoundBuffer* buffer, IOAudioEngine* target); + +real32 pitch(SoundBuffer* buffer, IOAudioEngine* target); + +Point3f +position(SoundBuffer* buffer, IOAudioEngine* target); + +Point3f +velocity(SoundBuffer* buffer, IOAudioEngine* target); + +/// \brief Get the volume level of an audio device. +/// +/// \returns A number between 0..100 (min/max). +real32 +volume(IOAudioEngine* target); + +Point3f +position(IOAudioEngine* target); + +/// \brief Get the volume level of an audio buffer. +/// +/// \returns A number between 0..100 (min/max). +real32 +volume(SoundBuffer* buffer, IOAudioEngine* target); + +/// \brief Get the minimum volume level of an audio buffer. +/// +/// \returns A number between 0..100 (min/max). +real32 +min_volume(SoundBuffer* buffer, IOAudioEngine* target); + +/// \brief Get the maximum volume level of an audio buffer. +/// +/// \returns A number between 0..100 (min/max). +real32 +max_volume(SoundBuffer* buffer, IOAudioEngine* target); + +real32 +playback_position(SoundBuffer* buffer, IOAudioEngine* target); + +real32 +playback_samples(SoundBuffer* buffer, IOAudioEngine* target); + +// TODO(jeff): Update me +SoundBuffer* clone_buffer(const SoundBuffer* rhs); + +// void +// set_state(SoundBuffer* buffer, IOAudioEngine* target, uint32 state); + +/// \brief Set the master volume level of an audio output. +/// +/// \param gain A 32-bit floating-point number between 0..100 (min/max), +/// representing the volume level. +/// +/// \remarks This overrides an audio buffer's local volume level. +void set_volume(real32 gain, IOAudioEngine* target); + +void +set_volume(SoundBuffer* buffer, IOAudioEngine* target, real32 gain); + +/// \brief Set the minimum volume level for an audio buffer. +/// +/// \param gain A 32-bit floating-point number between 0..100 (min/max). +void set_min_volume(SoundBuffer* buffer, IOAudioEngine* target, real32 gain); + +/// \brief Set the maximum volume level for an audio buffer. +/// +/// \param gain A 32-bit floating-point number between 0..100 (min/max). +void set_max_volume(SoundBuffer* buffer, IOAudioEngine* target, real32 gain); + +void set_pitch(SoundBuffer* buffer, IOAudioEngine* target, real32 pitch); + +void +set_velocity(SoundBuffer* buffer, IOAudioEngine* target, const Point3f& v); + +void +set_position(SoundBuffer* buffer, IOAudioEngine* target, const Point3f& pos); + +void +set_playback_position(SoundBuffer* buffer, IOAudioEngine* target, + real32 offset_seconds); + +void suspend(IOAudioEngine* target); +void resume(IOAudioEngine* target); + +} // namespace audio } // namespace nom #endif // NOMLIB_AL_SOUNDSOURCE_HEADERS defined + +/// \class nom::SoundFileReader +/// \ingroup audio +/// +/// **NOTE:** This documentation section is a *work in progress*! +/// +/// ### Using std::vector with nom::SoundFileReader +/// +/// \code +/// +/// ISoundFileReader fp = new SoundFileReader(); +/// NOM_ASSERT(fp != nullptr); +/// if(fp == nullptr) { +/// NOM_LOG_ERR(NOM_LOG_CATEGORY_APPLICATION, +/// "Failed to create audio buffer: out of memory!"); +/// return; +/// } +/// +/// if(fp->open(filename, metadata) == false) { +/// NOM_LOG_ERR(NOM_LOG_CATEGORY_APPLICATION, +/// "Could not load audio from input file:", filename); +/// return; +/// } +/// +/// NOM_ASSERT(fp->valid() == true); +/// +/// // The number of bytes to read into our temporary read buffer in a single +/// // update cycle +/// const nom::size_type CHUNK_SIZE = metadata.total_bytes; +/// +/// // Temporary input buffer +/// std::vector read_buffer; +/// +/// // Output buffer; the audio samples for audio playback with one of nomlib's +/// // audio engines, i.e.: OpenAL +/// std::vector buffer = nullptr; +/// +/// nom::size_type total_samples_read = 0; +/// int64 samples_read = -1; +/// while(samples_read != 0) { +/// samples_read = fp->read(read_buffer.data(), CHUNK_SIZE); +/// +/// auto& samples = buffer; +/// samples.insert(samples.end(), read_buffer.begin(), +/// read_buffer.begin() + samples_read); +/// +/// total_samples_read += samples_read; +/// +/// if(total_samples_read >= CHUNK_SIZE) { +/// break; +/// } +/// } +/// +/// delete NOM_SCAST(int16*, buffer); +/// buffer = nullptr; +/// +/// \endcode +/// diff --git a/include/nomlib/audio/AL/osx/apple_extensions.hpp b/include/nomlib/audio/AL/osx/apple_extensions.hpp new file mode 100644 index 00000000..976b54f4 --- /dev/null +++ b/include/nomlib/audio/AL/osx/apple_extensions.hpp @@ -0,0 +1,68 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014, 2015, 2016 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#ifndef NOMLIB_AL_OSX_APPLE_EXTENSIONS_HPP +#define NOMLIB_AL_OSX_APPLE_EXTENSIONS_HPP + +#include "nomlib/config.hpp" + +// Forward declarations +// struct ALCdevice_struct; + +// TODO(jeff): Private header? + +namespace nom { +namespace audio { + +/// \brief Get the sampling rate of audio playback. +/// +/// \see http://www.opensource.apple.com/source/OpenAL/OpenAL-54/Source/OpenAL/READ_ME +real64 osx_sample_rate(); + +/// \brief Set the sampling rate of audio playback. +/// +/// \remarks This allows us to optimize the audio mixing performance based on +/// the sampling rate of its audio assets. +/// +/// \see http://www.opensource.apple.com/source/OpenAL/OpenAL-54/Source/OpenAL/READ_ME +void osx_set_sample_rate(real64 sample_rate); + +/// \brief Get the maximum number of simultaneous playing audio buffers. +/// +/// \see http://www.opensource.apple.com/source/OpenAL/OpenAL-54/Source/OpenAL/READ_ME +int osx_max_sources(); + +/// \brief Set the maximum number of simultaneous playing audio buffers. +/// +/// \see http://www.opensource.apple.com/source/OpenAL/OpenAL-54/Source/OpenAL/READ_ME +void osx_set_max_sources(int num_sources); + +} // namespace audio +} // namespace nom + +#endif // include guard defined diff --git a/include/nomlib/audio/IAudioDevice.hpp b/include/nomlib/audio/IAudioDevice.hpp index 4a9fd9f6..319fb59c 100644 --- a/include/nomlib/audio/IAudioDevice.hpp +++ b/include/nomlib/audio/IAudioDevice.hpp @@ -30,26 +30,40 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define NOMLIB_AUDIO_IAUDIO_DEVICE_HPP #include -#include #include "nomlib/config.hpp" namespace nom { +namespace audio { + +// Forward declarations +class IOAudioEngine; +struct AudioSpec; /// \brief Abstract audio device interface class IAudioDevice { public: - virtual ~IAudioDevice( void ) + virtual ~IAudioDevice() { - NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_AUDIO, LogPriority::NOM_LOG_PRIORITY_VERBOSE ); + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE_AUDIO, + LogPriority::NOM_LOG_PRIORITY_VERBOSE); } - // virtual std::shared_ptr getAudioDevice( void ) const = 0; - virtual const std::string getDeviceName( void ) const = 0; - virtual bool isExtensionSupported ( const std::string& extension ) const = 0; + // virtual void* device() const = 0; + // virtual void* context() const = 0; + // virtual IOAudioEngine* caps() const = 0; + + virtual bool valid() const = 0; + virtual std::string device_name() const = 0; + + virtual IOAudioEngine* open(const audio::AudioSpec* spec) = 0; + virtual void suspend() = 0; + virtual void resume() = 0; + virtual void close() = 0; }; +} // namespace audio } // namespace nom #endif // include guard defined diff --git a/include/nomlib/audio/IListener.hpp b/include/nomlib/audio/IListener.hpp deleted file mode 100644 index f2c8f279..00000000 --- a/include/nomlib/audio/IListener.hpp +++ /dev/null @@ -1,61 +0,0 @@ -/****************************************************************************** - - nomlib - C++11 cross-platform game engine - -Copyright (c) 2013, 2014 Jeffrey Carpenter -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -******************************************************************************/ -#ifndef NOMLIB_AUDIO_LISTENER_HPP -#define NOMLIB_AUDIO_LISTENER_HPP - -#include "nomlib/config.hpp" -#include "nomlib/math/Point3.hpp" - -namespace nom { - -/// \brief Abstract audio volume interface -class IListener -{ - public: - virtual ~IListener( void ) - { - NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_AUDIO, nom::LogPriority::NOM_LOG_PRIORITY_VERBOSE ); - } - - virtual float getVolume ( void ) const = 0; - virtual const Point3f getPosition ( void ) const = 0; - virtual const Point3f getVelocity ( void ) const = 0; - virtual const Point3f getDirection ( void ) const = 0; - virtual void setPosition ( float x, float y, float z ) = 0; - virtual void setPosition ( const Point3f& position ) = 0; - virtual void setVelocity ( float x, float y, float z ) = 0; - virtual void setVelocity ( const Point3f& velocity ) = 0; - virtual void setDirection ( float x, float y, float z ) = 0; - virtual void setDirection ( const Point3f& direction ) = 0; - virtual void setVolume ( float gain ) = 0; -}; - -} // namespace nom - -#endif // include guard defined diff --git a/include/nomlib/audio/IOAudioEngine.hpp b/include/nomlib/audio/IOAudioEngine.hpp new file mode 100644 index 00000000..68b60660 --- /dev/null +++ b/include/nomlib/audio/IOAudioEngine.hpp @@ -0,0 +1,132 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014, 2015, 2016 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#ifndef NOMLIB_AUDIO_IO_AUDIO_ENGINE_HPP +#define NOMLIB_AUDIO_IO_AUDIO_ENGINE_HPP + +#include "nomlib/config.hpp" +#include "nomlib/math/Point3.hpp" + +namespace nom { +namespace audio { + +// Forward declarations +struct SoundBuffer; + +/// \brief Abstract base class for an instance of the audio hardware interface. +/// +/// \ntoe An audio driver is required to subclass IOAudioDevice in order to +/// provide working audio to the system. A single driver instance will contain +/// a single instance of the driver's IOAudioDevice subclass. The subclass is +/// responsible for mapping all hardware device resources from the service +/// provider nub. It must control access to the hardware so that the hardware +/// doesn't fall into an inconsistent state. + +// TODO(jeff): Add const to getters! +class IOAudioEngine +{ + protected: + IOAudioEngine() + { + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE_AUDIO, + NOM_LOG_PRIORITY_VERBOSE); + } + + public: + virtual ~IOAudioEngine() + { + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE_AUDIO, + NOM_LOG_PRIORITY_VERBOSE); + } + + virtual void init(void* driver) = 0; + + virtual bool valid() const = 0; + + // virtual int sample_rate() const = 0; + // virtual int mono_sources() const = 0; + // virtual int stereo_sources() const = 0; + + virtual uint32 caps() const = 0; + virtual void set_cap(uint32 format) = 0; + + virtual bool connected() const = 0; + + virtual + uint32 channel_format(uint32 num_channels, uint32 channel_format) = 0; + + virtual bool valid_buffer(SoundBuffer* buffer) = 0; + virtual bool valid_source(SoundBuffer* buffer) = 0; + + virtual uint32 state(SoundBuffer* target) = 0; + virtual real32 pitch(SoundBuffer* target) = 0; + + virtual real32 volume() const = 0; + virtual Point3f position() const = 0; + + virtual real32 volume(SoundBuffer* buffer) const = 0; + virtual real32 min_volume(SoundBuffer* buffer) = 0; + virtual real32 max_volume(SoundBuffer* buffer) = 0; + + virtual Point3f velocity(SoundBuffer* buffer) = 0; + virtual Point3f position(SoundBuffer* buffer) = 0; + virtual real32 playback_position(SoundBuffer* buffer) = 0; + virtual real32 playback_samples(SoundBuffer* buffer) = 0; + + // virtual void set_state(SoundBuffer* target, uint32 state) = 0; + + virtual void set_volume(real32 gain) = 0; + virtual void set_position(const Point3f& p) = 0; + + virtual void set_volume(SoundBuffer* target, real32 gain) = 0; + virtual void set_min_volume(SoundBuffer* target, real32 gain) = 0; + virtual void set_max_volume(SoundBuffer* target, real32 gain) = 0; + + virtual void set_velocity(SoundBuffer* target, const Point3f& v) = 0; + virtual void set_position(SoundBuffer* target, const Point3f& p) = 0; + virtual void set_pitch(SoundBuffer* target, real32 pitch) = 0; + virtual void set_playback_position(SoundBuffer* target, real32 offset_seconds) = 0; + virtual void play(SoundBuffer* target) = 0; + virtual void stop(SoundBuffer* target) = 0; + virtual void pause(SoundBuffer* target) = 0; + virtual void resume(SoundBuffer* target) = 0; + + virtual bool push_buffer(SoundBuffer* buffer) = 0; + virtual bool queue_buffer(SoundBuffer* buffer) = 0; + + virtual void suspend() = 0; + virtual void resume() = 0; + virtual void close() = 0; + + virtual void free_buffer(SoundBuffer* buffer) = 0; +}; + +} // namespace audio +} // namespace nom + +#endif // include guard defined diff --git a/include/nomlib/audio/NullSoundBuffer.hpp b/include/nomlib/audio/ISoundFileReader.hpp similarity index 64% rename from include/nomlib/audio/NullSoundBuffer.hpp rename to include/nomlib/audio/ISoundFileReader.hpp index 630b6a27..fbde034f 100644 --- a/include/nomlib/audio/NullSoundBuffer.hpp +++ b/include/nomlib/audio/ISoundFileReader.hpp @@ -2,7 +2,7 @@ nomlib - C++11 cross-platform game engine -Copyright (c) 2013, 2014 Jeffrey Carpenter +Copyright (c) 2013, 2014, 2015, 2016 Jeffrey Carpenter All rights reserved. Redistribution and use in source and binary forms, with or without @@ -26,31 +26,45 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ -#ifndef NOMLIB_AUDIO_NULL_SOUND_BUFFER_HPP -#define NOMLIB_AUDIO_NULL_SOUND_BUFFER_HPP +#ifndef NOMLIB_AUDIO_ISOUND_FILE_READER_HPP +#define NOMLIB_AUDIO_ISOUND_FILE_READER_HPP #include #include "nomlib/config.hpp" -#include "nomlib/audio/ISoundBuffer.hpp" +#include "nomlib/audio/SoundFile.hpp" namespace nom { +namespace audio { -class NullSoundBuffer: public ISoundBuffer +class ISoundFileReader { public: - NullSoundBuffer( void ); - virtual ~NullSoundBuffer( void ); + ISoundFileReader() + { + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE_AUDIO, + NOM_LOG_PRIORITY_VERBOSE); + } - uint32 get( void ) const; - int64 getDuration( void ) const; - bool load( const std::string& filename ); + ~ISoundFileReader() + { + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE_AUDIO, + NOM_LOG_PRIORITY_VERBOSE); + } - private: - void attach( Sound* sound ) const; - void detach( Sound* sound ) const; + virtual bool valid() const = 0; + + virtual bool open(const std::string& filename, SoundInfo& info) = 0; + + virtual int64 + read(void* data, uint32 format, nom::size_type chunk_size) = 0; + + virtual int64 seek(int64 offset, SoundSeek dir) = 0; + + virtual void close() = 0; }; +} // namespace audio } // namespace nom -#endif // include guard defined +#endif // NOMLIB_AL_SOUNDFILE_HEADERS defined diff --git a/include/nomlib/audio/ISoundBuffer.hpp b/include/nomlib/audio/ISoundFileWriter.hpp similarity index 68% rename from include/nomlib/audio/ISoundBuffer.hpp rename to include/nomlib/audio/ISoundFileWriter.hpp index e1091df4..5b583936 100644 --- a/include/nomlib/audio/ISoundBuffer.hpp +++ b/include/nomlib/audio/ISoundFileWriter.hpp @@ -26,36 +26,45 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ -#ifndef NOMLIB_AUDIO_ISOUND_BUFFER_HPP -#define NOMLIB_AUDIO_ISOUND_BUFFER_HPP +#ifndef NOMLIB_AUDIO_ISOUND_FILE_WRITER_HPP +#define NOMLIB_AUDIO_ISOUND_FILE_WRITER_HPP #include #include "nomlib/config.hpp" +#include "nomlib/audio/SoundFile.hpp" namespace nom { +namespace audio { -// Forward declarations -class Sound; - -/// \brief Abstract audio buffer interface -class ISoundBuffer +class ISoundFileWriter { public: - virtual ~ISoundBuffer( void ) + ISoundFileWriter() + { + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE_AUDIO, + NOM_LOG_PRIORITY_VERBOSE); + } + + ~ISoundFileWriter() { - NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_AUDIO, nom::LogPriority::NOM_LOG_PRIORITY_VERBOSE ); + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE_AUDIO, + NOM_LOG_PRIORITY_VERBOSE); } - virtual uint32 get( void ) const = 0; - virtual int64 getDuration( void ) const = 0; - virtual bool load( const std::string& filename ) = 0; + virtual bool valid() const = 0; + + virtual bool open(const std::string& filename, SoundInfo& info) = 0; + + virtual + int64 write(void* data, nom::size_type byte_size) = 0; + + virtual int64 seek(int64 offset, SoundSeek dir) = 0; - // protected: - virtual void attach( Sound* sound ) const = 0; - virtual void detach( Sound* sound ) const = 0; + virtual void close() = 0; }; +} // namespace audio } // namespace nom -#endif // include guard defined +#endif // NOMLIB_AL_SOUNDFILE_HEADERS defined diff --git a/include/nomlib/audio/ISoundSource.hpp b/include/nomlib/audio/ISoundSource.hpp deleted file mode 100644 index 76754d84..00000000 --- a/include/nomlib/audio/ISoundSource.hpp +++ /dev/null @@ -1,92 +0,0 @@ -/****************************************************************************** - - nomlib - C++11 cross-platform game engine - -Copyright (c) 2013, 2014 Jeffrey Carpenter -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -******************************************************************************/ -#ifndef NOMLIB_AUDIO_ISOUND_SOURCE_HPP -#define NOMLIB_AUDIO_ISOUND_SOURCE_HPP - -#include "nomlib/config.hpp" - -namespace nom { - -// Forward declarations -class ISoundBuffer; - -/// Sound source is one of the three states: stopped, paused or playing -enum SoundStatus -{ - Stopped = 0, - Paused = 1, - Playing = 2 -}; - -class ISoundSource -{ - public: - virtual ~ISoundSource( void ) - { - NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_AUDIO, nom::LogPriority::NOM_LOG_PRIORITY_VERBOSE ); - } - - virtual float getVolume ( void ) const = 0; - virtual float getMinVolume ( void ) const = 0; - virtual float getMaxVolume ( void ) const = 0; - virtual float getPitch ( void ) const = 0; - virtual bool getLooping ( void ) const = 0; - virtual Point3f getPosition ( void ) const = 0; - virtual Point3f getVelocity ( void ) const = 0; - virtual bool getPositionRelativeToListener ( void ) const = 0; - virtual float getMinDistance ( void ) const = 0; - virtual float getAttenuation ( void ) const = 0; - virtual int32 getBufferID ( void ) const = 0; - virtual float getPlayPosition ( void ) const = 0; - virtual SoundStatus getStatus ( void ) const = 0; - virtual void setVolume ( float gain ) = 0; - virtual void setMinVolume ( float gain ) = 0; - virtual void setMaxVolume ( float gain ) = 0; - virtual void setPitch ( float pitch ) = 0; - virtual void setLooping ( bool loops ) = 0; - virtual void setPosition ( float x, float y, float z ) = 0; - virtual void setPosition ( const Point3f& position ) = 0; - virtual void setVelocity ( float x, float y, float z ) = 0; - virtual void setVelocity ( const Point3f& velocity ) = 0; - virtual void setPositionRelativeToListener ( bool position ) = 0; - virtual void setMinDistance ( float distance ) = 0; - virtual void setAttenuation ( float attenuation ) = 0; - virtual void setPlayPosition ( float seconds ) = 0; - - virtual void setBuffer( const ISoundBuffer& copy ) = 0; - virtual void Play( void ) = 0; - virtual void Stop( void ) = 0; - virtual void Pause( void ) = 0; - virtual void togglePause( void ) = 0; - virtual void fadeOut( float seconds ) = 0; -}; - -} // namespace nom - -#endif // include guard defined diff --git a/include/nomlib/audio/NullAudioDevice.hpp b/include/nomlib/audio/NullAudioDevice.hpp index f9674c47..c92c7025 100644 --- a/include/nomlib/audio/NullAudioDevice.hpp +++ b/include/nomlib/audio/NullAudioDevice.hpp @@ -36,18 +36,41 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "nomlib/audio/IAudioDevice.hpp" namespace nom { +namespace audio { + +// Forward declarations +// struct SoundBuffer; +// class IOAudioEngine; + +// void null_set_audio_volume(SoundBuffer* target, real32 gain); class NullAudioDevice: public IAudioDevice { public: - NullAudioDevice( void ); - virtual ~NullAudioDevice( void ); + NullAudioDevice(); + virtual ~NullAudioDevice(); + + // virtual void* device() const override; + // virtual void* context() const override; + // virtual IOAudioEngine* caps() const override; - // std::shared_ptr getAudioDevice( void ) const; - const std::string getDeviceName( void ) const; - bool isExtensionSupported( const std::string& extension ) const; + virtual bool valid() const override; + std::string device_name() const override; + + virtual IOAudioEngine* open(const audio::AudioSpec* spec) override; + virtual void suspend() override; + virtual void resume() override; + virtual void close() override; + + private: + bool initialized_ = false; + // IOAudioEngine* impl_ = nullptr; + std::string device_name_; }; +IOAudioEngine* create_null_audio_device(const audio::AudioSpec* spec); + +} // namespace audio } // namespace nom #endif // include guard defined diff --git a/include/nomlib/audio/NullAudioDeviceCaps.hpp b/include/nomlib/audio/NullAudioDeviceCaps.hpp new file mode 100644 index 00000000..761ef40e --- /dev/null +++ b/include/nomlib/audio/NullAudioDeviceCaps.hpp @@ -0,0 +1,98 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#ifndef NOMLIB_AUDIO_NULL_DEVICE_CAPS_HPP +#define NOMLIB_AUDIO_NULL_DEVICE_CAPS_HPP + +#include "nomlib/config.hpp" +#include "nomlib/math/Point3.hpp" +#include "nomlib/audio/IOAudioEngine.hpp" + +namespace nom { +namespace audio { + +// Forward declarations +struct SoundBuffer; + +// TODO(jeff): Add const to getters! +class NullAudioEngineCaps: public IOAudioEngine +{ + public: + NullAudioEngineCaps(); + virtual ~NullAudioEngineCaps(); + + virtual + uint32 channel_format(uint32 num_channels, uint32 channel_format) override; + + virtual bool valid_audio_buffer(SoundBuffer* buffer) override; + virtual bool valid_sound_buffer(SoundBuffer* buffer) override; + + virtual uint32 state(SoundBuffer* target) override; + virtual real32 pitch(SoundBuffer* target) override; + + virtual real32 volume() const override; + virtual Point3f position() const override; + + virtual real32 volume(SoundBuffer* buffer) const override; + virtual real32 min_volume(SoundBuffer* buffer) override; + virtual real32 max_volume(SoundBuffer* buffer) override; + + virtual Point3f velocity(SoundBuffer* buffer) override; + virtual Point3f position(SoundBuffer* buffer) override; + virtual real32 playback_position(SoundBuffer* buffer) override; + virtual real32 playback_samples(SoundBuffer* buffer) override; + + virtual void set_state(SoundBuffer* target, uint32 state) override; + + virtual void set_volume(real32 gain) override; + virtual void set_position(const Point3f& p) override; + + virtual void set_min_volume(SoundBuffer* target, real32 gain) override; + virtual void set_max_volume(SoundBuffer* target, real32 gain) override; + virtual void set_volume(SoundBuffer* target, real32 gain) override; + + virtual void set_velocity(SoundBuffer* target, const Point3f& v) override; + virtual void set_position(SoundBuffer* target, const Point3f& p) override; + virtual void set_pitch(SoundBuffer* target, real32 pitch) override; + virtual void set_playback_position(SoundBuffer* target, real32 offset_seconds) override; + virtual void play(SoundBuffer* target) override; + virtual void stop(SoundBuffer* target) override; + virtual void pause(SoundBuffer* target) override; + virtual void resume(SoundBuffer* target) override; + + virtual bool fill_audio_buffer(SoundBuffer* buffer) override; + virtual void free_buffer(SoundBuffer* buffer) override; + + virtual void suspend() override; + virtual void resume() override; +}; + +} // namespace audio +} // namespace nom + +#endif // include guard defined diff --git a/include/nomlib/audio/NullListener.hpp b/include/nomlib/audio/NullListener.hpp deleted file mode 100644 index 48b409c9..00000000 --- a/include/nomlib/audio/NullListener.hpp +++ /dev/null @@ -1,59 +0,0 @@ -/****************************************************************************** - - nomlib - C++11 cross-platform game engine - -Copyright (c) 2013, 2014 Jeffrey Carpenter -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -******************************************************************************/ -#ifndef NOMLIB_AUDIO_NULL_LISTENER_HPP -#define NOMLIB_AUDIO_NULL_LISTENER_HPP - -#include "nomlib/config.hpp" -#include "nomlib/math/Point3.hpp" -#include "nomlib/audio/IListener.hpp" - -namespace nom { - -class NullListener: public IListener -{ - public: - NullListener( void ); - virtual ~NullListener( void ); - - float getVolume ( void ) const; - const Point3f getPosition ( void ) const; - const Point3f getVelocity ( void ) const; - const Point3f getDirection ( void ) const; - void setPosition ( float x, float y, float z ); - void setPosition ( const Point3f& position ); - void setVelocity ( float x, float y, float z ); - void setVelocity ( const Point3f& velocity ); - void setDirection ( float x, float y, float z ); - void setDirection ( const Point3f& direction ); - void setVolume ( float gain ); -}; - -} // namespace nom - -#endif // include guard defined diff --git a/include/nomlib/audio/NullSound.hpp b/include/nomlib/audio/NullSound.hpp deleted file mode 100644 index 1417539d..00000000 --- a/include/nomlib/audio/NullSound.hpp +++ /dev/null @@ -1,53 +0,0 @@ -/****************************************************************************** - - nomlib - C++11 cross-platform game engine - -Copyright (c) 2013, 2014 Jeffrey Carpenter -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -******************************************************************************/ -#ifndef NOMLIB_AUDIO_NULL_SOUND_HPP -#define NOMLIB_AUDIO_NULL_SOUND_HPP - -#include "nomlib/config.hpp" -#include "nomlib/audio/NullSoundSource.hpp" - -namespace nom { - -/// \brief Null sound interface -class NullSound: public NullSoundSource -{ - public: - virtual ~NullSound( void ); - - void setBuffer( const ISoundBuffer& copy ); - void Play( void ); - void Stop( void ); - void Pause( void ); - void togglePause( void ); - void fadeOut( float seconds ); -}; - -} // namespace nom - -#endif // include guard defined diff --git a/include/nomlib/audio/SoundBuffer.hpp b/include/nomlib/audio/SoundBuffer.hpp new file mode 100644 index 00000000..ddb0dc82 --- /dev/null +++ b/include/nomlib/audio/SoundBuffer.hpp @@ -0,0 +1,85 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014, 2015, 2016 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#ifndef NOMLIB_AUDIO_SOUND_BUFFER_HPP +#define NOMLIB_AUDIO_SOUND_BUFFER_HPP + +#include "nomlib/config.hpp" + +namespace nom { +namespace audio { + +// TODO(jeff): Verify that these fields match nom::SoundInfo data types! +struct SoundBuffer +{ + /// \brief The unique identifier for the buffer. + /// + /// \remarks This is the internal referencing system used by OpenAL. + uint32 buffer_id = 0; + + /// \brief The unique identifier for the sound source. + uint32 source_id = 0; + + /// \brief The audio buffer. + void* samples = nullptr; + + /// \brief The total number of frames in this sample instance + nom::size_type frame_count = 0; + + uint32 channel_count = 0; + + uint32 channel_format = 0; + + uint32 sample_rate = 0; + + int64 sample_count = 0; + + /// \brief The total time, in seconds, of the audio buffer. + real32 duration = 0.0f; + + nom::size_type total_bytes = 0; + + bool seekable = false; + + /// \see nom::audio::ALAudioDeviceCaps + /// \todo Consider using the AudioSourceType enumeration instead + bool stream_source = false; + + nom::size_type samples_read = 0; + // nom::size_type samples_output = 0; + + nom::size_type elapsed_seconds = 0; + + // TODO(jeff): Implement..? See API docs for alcGetContextsDevice(ctx) + void* context = nullptr; +}; + +} // namespace audio +} // namespace nom + +#endif // include guard defined diff --git a/include/nomlib/audio/SoundFile.hpp b/include/nomlib/audio/SoundFile.hpp new file mode 100644 index 00000000..7e209e2f --- /dev/null +++ b/include/nomlib/audio/SoundFile.hpp @@ -0,0 +1,147 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014, 2015, 2016 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#ifndef NOMLIB_AUDIO_SOUND_FILE_HPP +#define NOMLIB_AUDIO_SOUND_FILE_HPP + +#include + +#include "nomlib/config.hpp" + +namespace nom { +namespace audio { + +enum AudioTags +{ + SOUND_TAG_UNDEFINED = 0, + SOUND_TAG_TITLE, + SOUND_TAG_COPYRIGHT, + SOUND_TAG_SOFTWARE, + SOUND_TAG_ARTIST, + SOUND_TAG_COMMENT, + SOUND_TAG_DATE, + SOUND_TAG_ALBUM, + SOUND_TAG_LICENSE, + SOUND_TAG_TRACK_NUMBER, + SOUND_TAG_GENRE, +}; + +enum AudioFormat +{ + AUDIO_FORMAT_UNKNOWN = 0, + + /// \brief Signed 8-bit integer sample data + AUDIO_FORMAT_S8, + + /// \brief Unsigned 8-bit integer sample data + AUDIO_FORMAT_U8, + + /// \brief Signed 16-bit integer sample data + AUDIO_FORMAT_S16, + + /// \brief Signed 24-bit integer sample data + AUDIO_FORMAT_S24, + + /// \brief Signed 32-bit integer sample data + AUDIO_FORMAT_S32, + + /// \brief 32-bit IEEE floating-point sample data + AUDIO_FORMAT_R32, + + /// \brief 64-bit IEEE floating-point sample data + AUDIO_FORMAT_R64, +}; + +#if 0 +enum AudioChannels +{ + // Mono + ONE_CHANNELS = 1, + TWO_CHANNELS, + FOUR_CHANNELS, + FIVE_CHANNELS, + SIX_CHANNELS, + SEVEN_CHANNELS +}; +#endif + +enum SoundSeek +{ + SOUND_SEEK_SET = 0, + SOUND_SEEK_CUR, + SOUND_SEEK_END +}; + +struct SoundInfo +{ + /// \brief The total number of frames. + /// + /// \remarks One audio frame is the ... + nom::size_type frame_count = 0; + + /// \brief The total number of samples. + int64 sample_count = 0; + + /// \brief The number of samples per second. + uint32 sample_rate = 0; + + /// \brief The number of audio channels. + uint32 channel_count = 0; + + /// \brief The total time, in seconds, of the audio samples. + real32 duration = 0.0f; + + /// \brief The total number of bytes represented by the audio samples. + nom::size_type total_bytes = 0; + + /// \see nom::audio::AudioFormat + uint32 channel_format = 0; + + bool seekable = false; + + struct Metadata + { + const char* title = "\0"; + const char* copyright = "\0"; + const char* software = "\0"; + const char* artist = "\0"; + const char* comment = "\0"; + const char* date = "\0"; + const char* album = "\0"; + const char* license = "\0"; + const char* track_number = "\0"; + const char* genre = "\0"; + }; + + Metadata tags; +}; + +} // namespace audio +} // namespace nom + +#endif // NOMLIB_AUDIO_SOUND_FILE_HPP defined diff --git a/include/nomlib/audio/audio_defs.hpp b/include/nomlib/audio/audio_defs.hpp new file mode 100644 index 00000000..7e110f4a --- /dev/null +++ b/include/nomlib/audio/audio_defs.hpp @@ -0,0 +1,163 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014, 2015, 2016 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#ifndef NOMLIB_AUDIO_DEFINITIONS_HPP +#define NOMLIB_AUDIO_DEFINITIONS_HPP + +#include + +namespace nom { +namespace audio { + +// Forward declarations +// struct IAudioDevice; +class IOAudioEngine; + +// exposed to the end-user as 0.0f .. 100.0f +// internally stored as a 32-bit floating-point value that is normalized to 0..1 +const nom::real32 MIN_VOLUME = 0.0f; +const nom::real32 MAX_VOLUME = 100.0f; + +/// Optimal sound frame size (in bytes); used by libsndfile +const nom::uint32 BUFFER_SIZE = kilobyte(32); +const nom::size_type TOTAL_NUM_BUFFERS = 32; +const nom::size_type TOTAL_NUM_MONO_SOURCES = 128; +const nom::size_type TOTAL_NUM_STEREO_SOURCES = 127; +const nom::size_type TOTAL_NUM_SOURCES = + TOTAL_NUM_MONO_SOURCES + TOTAL_NUM_STEREO_SOURCES; + +// TODO(jeff): Collapse with nom::audio::AudioFormat..? +enum AudioCaps +{ + CAPS_UNDEFINED = 0x0000, + + CAPS_FORMAT_MONO_S8 = 0x1000, + CAPS_FORMAT_STEREO_S8 = 0x1002, + CAPS_FORMAT_QUAD_S8 = 0x1004, + CAPS_FORMAT_51CHN_S8 = 0x1006, + CAPS_FORMAT_61CHN_S8 = 0x1008, + CAPS_FORMAT_71CHN_S8 = 0x1010, + + CAPS_FORMAT_MONO_U8 = 0x1100, + CAPS_FORMAT_STEREO_U8 = 0x1102, + CAPS_FORMAT_QUAD_U8 = 0x1104, + CAPS_FORMAT_51CHN_U8 = 0x1106, + CAPS_FORMAT_61CHN_U8 = 0x1108, + CAPS_FORMAT_71CHN_U8 = 0x1110, + + CAPS_FORMAT_MONO_S16 = 0x1200, + CAPS_FORMAT_STEREO_S16 = 0x1202, + CAPS_FORMAT_QUAD_S16 = 0x1204, + CAPS_FORMAT_51CHN_S16 = 0x1206, + CAPS_FORMAT_61CHN_S16 = 0x1208, + CAPS_FORMAT_71CHN_S16 = 0x1210, + + CAPS_FORMAT_MONO_S24 = 0x1300, + CAPS_FORMAT_STEREO_S24 = 0x1302, + CAPS_FORMAT_QUAD_S24 = 0x1304, + CAPS_FORMAT_51CHN_S24 = 0x1306, + CAPS_FORMAT_61CHN_S24 = 0x1308, + CAPS_FORMAT_71CHN_S24 = 0x1310, + + CAPS_FORMAT_MONO_S32 = 0x1400, + CAPS_FORMAT_STEREO_S32 = 0x1402, + CAPS_FORMAT_QUAD_S32 = 0x1404, + CAPS_FORMAT_51CHN_S32 = 0x1406, + CAPS_FORMAT_61CHN_S32 = 0x1408, + CAPS_FORMAT_71CHN_S32 = 0x1410, + + CAPS_FORMAT_MONO_FLOAT32 = 0x1500, + CAPS_FORMAT_STEREO_FLOAT32 = 0x1502, + CAPS_FORMAT_QUAD_R32 = 0x1504, + + CAPS_FORMAT_MONO_FLOAT64 = 0x1600, + CAPS_FORMAT_STEREO_FLOAT64 = 0x1602, + CAPS_FORMAT_QUAD_R64 = 0x1604, + + // CAPS_MAC_STATIC_BUFFER, + // CAPS_MAC_EXT, + // CAPS_EAX, + // CAPS_EFX, + // CAPS_XRAM, +}; + +enum AudioSpecFlags: uint32 +{ + AUDIO_SPEC_UNDEFINED = 0x00000, + + /// \brief A request to the specified audio context that context processing + /// occurs asynchronously where possible. + // AUDIO_SPEC_ASYNC = 0x00100, + AUDIO_SPEC_CAPTURE = 0x00200, + // AUDIO_SPEC_STATIC_BUFFER, + // AUDIO_SPEC_SOURCE_NOTIFICATION, +}; + +struct AudioSpec +{ + // audio front-end + // IAudioDevice* dev = nullptr; + + // audio back-end + // IOAudioEngine* engine = nullptr; + + // audio device name + const char* name = nullptr; + + // audio back-end + const char* engine = nullptr; + + /// \brief The final output audio frequency, specified in Hertz (Hz). + int sample_rate = 44100; + + /// \brief A request for a specific number of mono (one channel) sources to + /// be allocated at the time of an audio context creation. + int num_mono_sources = audio::TOTAL_NUM_MONO_SOURCES; + + /// \brief A request for a specified number of stereo (two channel) sources + /// to be allocated at the time of an audio context creation. + int num_stereo_sources = audio::TOTAL_NUM_STEREO_SOURCES; + + /// \brief The update frequency, in Hertz (Hz), of audio context processing. + int refresh_rate = 0; + + /// \brief A request to the specified audio context that context processing + /// occurs asynchronously where possible. + bool sync_context = false; + + /// \brief One or more bit-masked enumeration values from + /// audio::AudioSpecFlags. + uint32 flags = 0; + + // uint32 channel_format = 0; +}; + +} // namespace audio +} // namespace nom + +#endif // include guard defined diff --git a/include/nomlib/system/AnimationTimer.hpp b/include/nomlib/audio/libsndfile/SoundFileReader.hpp similarity index 56% rename from include/nomlib/system/AnimationTimer.hpp rename to include/nomlib/audio/libsndfile/SoundFileReader.hpp index 3afbacec..e2a732aa 100644 --- a/include/nomlib/system/AnimationTimer.hpp +++ b/include/nomlib/audio/libsndfile/SoundFileReader.hpp @@ -2,7 +2,7 @@ nomlib - C++11 cross-platform game engine -Copyright (c) 2013, 2014 Jeffrey Carpenter +Copyright (c) 2013, 2014, 2015, 2016 Jeffrey Carpenter All rights reserved. Redistribution and use in source and binary forms, with or without @@ -26,53 +26,55 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ -#ifndef NOMLIB_SDL_ANIMATION_TIMER_HEADERS -#define NOMLIB_SDL_ANIMATION_TIMER_HEADERS +#ifndef NOMLIB_AUDIO_LIBSNDFILE_SOUND_FILE_READER_HPP +#define NOMLIB_AUDIO_LIBSNDFILE_SOUND_FILE_READER_HPP -#include #include #include "nomlib/config.hpp" +#include "nomlib/audio/ISoundFileReader.hpp" + +// Forward declarations +struct SNDFILE_tag; +struct SF_INFO; namespace nom { +namespace audio { + +std::string libsndfile_version(); -class AnimationTimer +// TODO(jeff): Error checking +// TODO(jeff): Use nom::err +class SoundFileReader: public ISoundFileReader { public: - AnimationTimer ( void ); - virtual ~AnimationTimer ( void ); - - void start ( void ); - void stop ( void ); + SoundFileReader(); - /// Alias for start - void restart ( void ); + virtual ~SoundFileReader(); - uint32 ticks ( void ) const; + virtual bool valid() const override; - bool started ( void ) const; + virtual bool open(const std::string& filename, SoundInfo& info) override; - const std::string ticksAsString ( void ) const; + virtual int64 + read(void* data, uint32 channel_format, nom::size_type chunk_size) override; - /// Helper method; conversion from milliseconds to seconds - uint32 seconds( float milliseconds ) const; + /// \param offset The cursor offset position, depicted in audio frames. + virtual int64 seek(int64 offset, SoundSeek dir) override; - uint32 framerate ( void ) const; - - void setFrameRate ( uint32 rate ); + virtual void close() override; private: - /// Milliseconds since timer start - uint32 elapsed_ticks; - - /// Tracks whether we are started or not - bool timer_started; + SoundInfo parse_header(SF_INFO& metadata); + static const char* parse_tags(SNDFILE_tag* fp, uint32 sound_tag); - /// Convenience container - uint32 frame_rate; + /// \brief A third-party file descriptor whose owned by the end-user. + /// + /// \see libsndfile + SNDFILE_tag* fp_ = nullptr; }; - +} // namespace audio } // namespace nom -#endif // include guard defined +#endif // NOMLIB_AUDIO_LIBSNDFILE_SOUND_FILE_READER_HPP defined diff --git a/include/nomlib/system/EventCallback.hpp b/include/nomlib/audio/libsndfile/SoundFileWriter.hpp similarity index 62% rename from include/nomlib/system/EventCallback.hpp rename to include/nomlib/audio/libsndfile/SoundFileWriter.hpp index 1a8dafe7..bf9b557e 100644 --- a/include/nomlib/system/EventCallback.hpp +++ b/include/nomlib/audio/libsndfile/SoundFileWriter.hpp @@ -26,43 +26,52 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ -#ifndef NOMLIB_SYSTEM_EVENT_CALLBACK_HPP -#define NOMLIB_SYSTEM_EVENT_CALLBACK_HPP +#ifndef NOMLIB_AUDIO_LIBSNDFILE_SOUND_FILE_WRITER_HPP +#define NOMLIB_AUDIO_LIBSNDFILE_SOUND_FILE_WRITER_HPP -#include +#include #include "nomlib/config.hpp" - -namespace nom { +#include "nomlib/audio/SoundFile.hpp" +#include "nomlib/audio/ISoundFileWriter.hpp" // Forward declarations -struct Event; +struct SNDFILE_tag; +struct SF_INFO; + +namespace nom { +namespace audio { -class EventCallback +// TODO(jeff): Rename to RawSoundFileWriter or so +class SoundFileWriter: public ISoundFileWriter { public: - typedef EventCallback self_type; - typedef std::function callback_type; + SoundFileWriter(); + + virtual ~SoundFileWriter(); - /// \brief Default constructor. - EventCallback( void ); + virtual bool valid() const override; - /// \brief Destructor. - ~EventCallback( void ); + virtual bool open(const std::string& filename, SoundInfo& info) override; - /// \brief Construct an object and initialize its callback function. - EventCallback( const callback_type& callback ); + virtual + int64 write(void* data, nom::size_type byte_size) override; - /// \brief C++ functor; execute the assigned object method. - void operator() ( const Event& evt ) const; + virtual int64 seek(int64 offset, SoundSeek dir) override; - /// \brief C-style method for executing the assigned callback function. - void execute( const Event& evt ) const; + virtual void close() override; private: - callback_type delegate_; + SoundInfo parse_header(SF_INFO& metadata); + static const char* parse_tags(SNDFILE_tag* fp, uint32 sound_tag); + + /// \brief A third-party file descriptor whose owned by the end-user. + /// + /// \see libsndfile + SNDFILE_tag* fp_ = nullptr; }; +} // namespace audio } // namespace nom -#endif // include guard defined +#endif // inclued guard defined diff --git a/include/nomlib/config.hpp.in b/include/nomlib/config.hpp.in index 5ea9e628..15b8f8d7 100644 --- a/include/nomlib/config.hpp.in +++ b/include/nomlib/config.hpp.in @@ -39,6 +39,13 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // Debugging #include +#include // __FILE__ macro post-processing + +// Shortened __FILE__ utility macro +// +// TODO: I'd like to see this integrated into the SDL2Logger interface! +#define NOM_FILE \ + ( strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__ ) /// \brief See also: cmake/platform.cmake #cmakedefine NOM_USE_SDL2_ASSERT @@ -86,7 +93,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /// \param ... Variable list of std::ostringstream compatible arguments. #if defined( NOM_DEBUG ) #define NOM_LOG_VERBOSE( cat, ... ) \ - NOM_LOG_MESSAGE( cat, nom::LogPriority::NOM_LOG_PRIORITY_VERBOSE, "[", __FILE__, ":", __LINE__, "]", __VA_ARGS__ ); + NOM_LOG_MESSAGE( cat, nom::LogPriority::NOM_LOG_PRIORITY_VERBOSE, "[", NOM_FILE, ":", __LINE__, "]", __VA_ARGS__ ); #else #define NOM_LOG_VERBOSE( cat, ... ) \ NOM_LOG_MESSAGE( cat, nom::LogPriority::NOM_LOG_PRIORITY_VERBOSE, __VA_ARGS__ ); @@ -100,7 +107,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /// \param ... Variable list of std::ostringstream compatible arguments. #if defined( NOM_DEBUG ) #define NOM_LOG_DEBUG( cat, ... ) \ - NOM_LOG_MESSAGE( cat, nom::LogPriority::NOM_LOG_PRIORITY_DEBUG, "[", __FILE__, ":", __LINE__, "]", __VA_ARGS__ ); + NOM_LOG_MESSAGE( cat, nom::LogPriority::NOM_LOG_PRIORITY_DEBUG, "[", NOM_FILE, ":", __LINE__, "]", __VA_ARGS__ ); #else #define NOM_LOG_DEBUG( cat, ... ) \ NOM_LOG_MESSAGE( cat, nom::LogPriority::NOM_LOG_PRIORITY_DEBUG, __VA_ARGS__ ); @@ -123,7 +130,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /// \param ... Variable list of std::ostringstream compatible arguments. #if defined( NOM_DEBUG ) #define NOM_LOG_WARN( cat, ... ) \ - NOM_LOG_MESSAGE( cat, nom::LogPriority::NOM_LOG_PRIORITY_WARN, "[", __FILE__, ":", __LINE__, "]", __VA_ARGS__ ); + NOM_LOG_MESSAGE( cat, nom::LogPriority::NOM_LOG_PRIORITY_WARN, "[", NOM_FILE, ":", __LINE__, "]", __VA_ARGS__ ); #else #define NOM_LOG_WARN( cat, ... ) \ NOM_LOG_MESSAGE( cat, nom::LogPriority::NOM_LOG_PRIORITY_WARN, __VA_ARGS__ ); @@ -137,7 +144,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /// \param ... Variable list of std::ostringstream compatible arguments. #if defined( NOM_DEBUG ) #define NOM_LOG_ERR( cat, ... ) \ - NOM_LOG_MESSAGE( cat, nom::LogPriority::NOM_LOG_PRIORITY_ERROR, "[", __FILE__, ":", __LINE__, "]", __VA_ARGS__ ); + NOM_LOG_MESSAGE( cat, nom::LogPriority::NOM_LOG_PRIORITY_ERROR, "[", NOM_FILE, ":", __LINE__, "]", __VA_ARGS__ ); #else #define NOM_LOG_ERR( cat, ... ) \ NOM_LOG_MESSAGE( cat, nom::LogPriority::NOM_LOG_PRIORITY_ERROR, __VA_ARGS__ ); @@ -151,7 +158,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /// \param ... Variable list of std::ostringstream compatible arguments. #if defined( NOM_DEBUG ) #define NOM_LOG_CRIT( cat, ... ) \ - NOM_LOG_MESSAGE( cat, nom::LogPriority::NOM_LOG_PRIORITY_CRITICAL, "[", __FILE__, ":", __LINE__, "]", __VA_ARGS__ ); + NOM_LOG_MESSAGE( cat, nom::LogPriority::NOM_LOG_PRIORITY_CRITICAL, "[", NOM_FILE, ":", __LINE__, "]", __VA_ARGS__ ); #else #define NOM_LOG_CRIT( cat, ... ) \ NOM_LOG_MESSAGE( cat, nom::LogPriority::NOM_LOG_PRIORITY_CRITICAL, __VA_ARGS__ ); @@ -164,7 +171,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /// especially avoid using this, as it forces one to enable NOM logging /// category logging -- cluttering logs with both engine & app-level output. #define NOM_DUMP( var ) \ - NOM_LOG_MESSAGE( NOM, nom::LogPriority::NOM_LOG_PRIORITY_DEBUG, "[", __FILE__, ":", __LINE__, "]", #var, ":", var ); + NOM_LOG_MESSAGE( NOM, nom::LogPriority::NOM_LOG_PRIORITY_DEBUG, "[", NOM_FILE, ":", __LINE__, "]", #var, ":", var ); /// \brief Log one or more variables. /// @@ -175,7 +182,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /// /// \remarks The message is logged under the debug priority. #define NOM_DUMP_VAR( cat, ... ) \ - NOM_LOG_MESSAGE( cat, nom::LogPriority::NOM_LOG_PRIORITY_DEBUG, "[", __FILE__, ":", __LINE__, "]", __VA_ARGS__ ); + NOM_LOG_MESSAGE( cat, nom::LogPriority::NOM_LOG_PRIORITY_DEBUG, "[", NOM_FILE, ":", __LINE__, "]", __VA_ARGS__ ); /// \brief Log class construction and destruction /// @@ -212,8 +219,26 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define NOM_ASSERT(expression) // NO-OP #endif // defined( NOM_DEBUG_ASSERT ) +#define NOM_ASSERT_INVALID_PATH(reserved) \ + NOM_ASSERT(!"Invalid execution path assertion triggered") + +#if defined(NOM_COMPILER_CLANG) + // SOURCE: http://clang.llvm.org/docs/UsersManual.html#controlling-diagnostics-via-pragmas + #define NOM_PRAGMA_STR(msg) #msg + #define NOM_PRAGMA_DEFER(arg, ...) arg(__VA_ARGS__) + #define NOM_PRAGMA_ERROR(msg) \ + _Pragma(NOM_PRAGMA_STR(GCC error(msg " in " \ + NOM_PRAGMA_DEFER(NOM_PRAGMA_STR, __FILE__) \ + ":" NOM_PRAGMA_DEFER(NOM_PRAGMA_STR,__LINE__) ))) +#else + // NO OPCODE defined + #define NOM_PRAGMA_STR(msg) + #define NOM_PRAGMA_DEFER(arg, ...) + #define NOM_PRAGMA_ERROR(msg) +#endif + #ifndef __cplusplus - #pragma message ( "nomlib requires a C++11 capable compiler." ) + NOM_PRAGMA_ERROR("nomlib requires a C++11 capable compiler.") #endif /// \brief See nomlib/platforms.hpp for compiler detection handling. Note that @@ -221,7 +246,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /// during ports, etc. /// /// \note See also: http://clang.llvm.org/docs/UsersManual.html -#if defined( NOM_DEBUG ) && defined( NOM_COMPILER_CLANG ) +#if defined(NOM_DEBUG) && defined(NOM_COMPILER_CLANG) /// \brief Macro for selectively ignoring the compiler warning messages /// generated when unused variables are found. Useful during major code @@ -247,8 +272,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define NOM_IGNORED_VARS() #define NOM_IGNORED_ENDL() + #define NOM_IGNORED_VARS_ENDL() + +#endif // defined(NOM_DEBUG) && defined(NOM_COMPILER_CLANG) -#endif // defined( NOM_DEBUG ) && defined( NOM_COMPILER_CLANG ) +// NOTE(jeff): See src/CMakeLists.txt for declarations // Image loader #cmakedefine NOM_USE_SDL2_IMAGE @NOM_USE_SDL2_IMAGE@ @@ -257,7 +285,22 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #cmakedefine NOM_USE_SDL2_TTF @NOM_USE_SDL2_TTF@ // Audio -#cmakedefine NOM_USE_OPENAL @NOM_USE_OPENAL@ +#cmakedefine NOM_USE_CREATIVE_OPENAL @NOM_USE_CREATIVE_OPENAL@ +#cmakedefine NOM_USE_APPLE_OPENAL @NOM_USE_APPLE_OPENAL@ +#cmakedefine NOM_USE_OPENAL_SOFT @NOM_USE_OPENAL_SOFT@ + +#if defined(NOM_USE_CREATIVE_OPENAL) || defined(NOM_USE_APPLE_OPENAL) || \ + defined(NOM_USE_OPENAL_SOFT) + #define NOM_USE_OPENAL +#endif + +#if defined NOM_BUILD_AUDIO_UNIT + #ifndef NOM_USE_OPENAL + // One of the above variables must be set in src/CMakeLists.txt + NOM_PRAGMA_ERROR("Audio Unit: One of the OpenAL audio engines must be selected") + #endif +#endif + #cmakedefine NOM_USE_LIBSNDFILE @NOM_USE_LIBSNDFILE@ // High-quality rescaling algorithms for low-resolution sprites diff --git a/include/nomlib/core.hpp b/include/nomlib/core.hpp index 3501d280..55408323 100644 --- a/include/nomlib/core.hpp +++ b/include/nomlib/core.hpp @@ -31,12 +31,13 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // Public header file -#include "nomlib/core/helpers.hpp" +#include "nomlib/core/unique_ptr.hpp" +#include "nomlib/core/strings.hpp" #include "nomlib/core/clock.hpp" #include "nomlib/core/ObjectTypeInfo.hpp" #include "nomlib/core/IObject.hpp" #include "nomlib/core/SDL2Logger.hpp" #include "nomlib/core/ConsoleOutput.hpp" - +#include #endif // include guard defined diff --git a/include/nomlib/core/SDL2Logger.hpp b/include/nomlib/core/SDL2Logger.hpp index 800e8441..8a55dddc 100644 --- a/include/nomlib/core/SDL2Logger.hpp +++ b/include/nomlib/core/SDL2Logger.hpp @@ -29,14 +29,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef NOMLIB_CORE_SDL2_LOGGER_HPP #define NOMLIB_CORE_SDL2_LOGGER_HPP -#include #include #include #include -#include "nomlib/core/clock.hpp" - /// \brief The predefined logging categories. /// /// \remarks By default: NOM_LOG_CATEGORY_APPLICATION is enabled at the @@ -83,6 +80,15 @@ enum /// \brief Rendering subsystem logging category NOM_LOG_CATEGORY_RENDER, + /// \brief Action objects + NOM_LOG_CATEGORY_ACTION, + + /// \brief Action engine + NOM_LOG_CATEGORY_ACTION_PLAYER, + + /// \brief Action queue + NOM_LOG_CATEGORY_ACTION_QUEUE, + /// \brief Events logging category NOM_LOG_CATEGORY_EVENT, @@ -117,6 +123,12 @@ enum NOM_LOG_CATEGORY_TRACE_SYSTEM, + /// \brief Call stacks for engine unit tests + NOM_LOG_CATEGORY_TRACE_UNIT_TEST, + + /// \brief Call stacks for the actions engine + NOM_LOG_CATEGORY_TRACE_ACTION, + /// \brief Custom logging category that is reserved for application-level use. /// For example: /// @@ -162,6 +174,11 @@ void log_message( void* ptr, int cat, SDL_LogPriority prio, const char* msg ); } // namespace priv /// \brief Helper method for nom::SDL2Logger. +/// +/// \todo Optionally support modifying floating-point precision so to help +/// catch floating-point math errs such as 254.999984741 being represented +/// in the debug output as 255 without explicitly setting the precision to +/// five (5) or greater. template void write_debug_output( std::ostream& out, const Type& f ) { @@ -234,6 +251,11 @@ class SDL2Logger nom::write_debug_output( this->output_stream(), f ); } + void write(uint8_t f) + { + nom::write_debug_output(this->output_stream(), (int)f); + } + /// \brief Write a log message to the output stream. /// /// \remarks This is an intentional method overload when the desire is to diff --git a/include/nomlib/core/clock.hpp b/include/nomlib/core/clock.hpp index 756c51af..cc675634 100644 --- a/include/nomlib/core/clock.hpp +++ b/include/nomlib/core/clock.hpp @@ -100,11 +100,13 @@ void sleep( uint32 milliseconds ); /// /// \note This is a function wrapper for SDL_GetPerformanceCounter. /// \see https://wiki.libsdl.org/SDL_GetPerformanceCounter?highlight=%28%5CbCategoryTimer%5Cb%29%7C%28CategoryEnum%29%7C%28CategoryStruc%29 -uint64 hires_counter(); +uint64 hires_ticks(); /// \brief Get the count per second of the high resolution counter. /// -/// \returns A platform-specific count per second. +/// \returns A platform-specific count per second, such as one +/// billionth of a second (nanosecond), microsecond (one millionth of a second) +/// or one thousandth of a second (millisecond). /// /// \note This is a function wrapper for SDL_GetPerformanceFrequency. /// \see https://wiki.libsdl.org/SDL_GetPerformanceFrequency diff --git a/include/nomlib/core/err.hpp b/include/nomlib/core/err.hpp new file mode 100644 index 00000000..0f96c718 --- /dev/null +++ b/include/nomlib/core/err.hpp @@ -0,0 +1,103 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#ifndef NOMLIB_CORE_ERR_HPP +#define NOMLIB_CORE_ERR_HPP + +#include + +#include "nomlib/config.hpp" + +namespace nom { + +struct err +{ + err(); + ~err(); + + err(const err& rhs); + + std::stringstream message; + // const char* message = nullptr; +}; + +inline std::stringstream& operator <<(std::stringstream& os, const err& error); + +/// \brief Get the error state (whether or not an error is set) +bool error_state(); + +/// \brief Get the current error. +std::string error(); + +/// \brief Create an error. +err make_error(const char* message); + +/// \brief Set the error message. +/// +/// \remarks The global error buffer is cleared. +void set_error(const err& error); + +/// \brief Set the error message. +/// +/// \remarks The global error buffer is cleared. +void set_error(const char* message); + +/// \brief Set the error message. +/// +/// \remarks The global error buffer is cleared. +void set_error(const std::string& message); + +/// \brief Clears the global error buffer. +void clear_error(); + +// Common error types + +const err OUT_OF_MEMORY_ERR = + nom::make_error("Failed to allocate memory"); + +const err NULL_ARGUMENT_ERR = + nom::make_error("Passed NULL argument"); + +} // namespace nom + +#endif // include guard defined + +/// \class nom::err +/// \ingroup core +/// +/// ## Usage Examples +/// +/// \code +/// +/// #include +/// +/// err error; +/// error.message << "My error message"; +/// nom::set_error(error); +/// NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, nom::error() ); +/// diff --git a/include/nomlib/core/strings.hpp b/include/nomlib/core/strings.hpp new file mode 100644 index 00000000..b39ecdba --- /dev/null +++ b/include/nomlib/core/strings.hpp @@ -0,0 +1,221 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014, 2015, 2016 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#ifndef NOMLIB_CORE_STRINGS_HPP +#define NOMLIB_CORE_STRINGS_HPP + +#include "nomlib/config.hpp" + +#include +#include + +namespace nom { + +/// \brief The maximum size, in bytes, a string may consume in memory. +/// +/// \todo Additional buffer size validation -- underflows and overflows -- need +/// to be happening in these functions! +const nom::size_type MAX_STRING_LENGTH = 255; + +/// \brief Allocate memory for a string. +/// +/// \param str The string to copy in place of the allocated memory. A NULL +/// value is valid and will skip a memory copy of the existing string. +/// +/// \param num_bytes The number of bytes to allocate for the string. +/// +/// \returns On success, a NULL-terminated string will be returned. On failure +/// to allocate the string, such as insufficient memory, or passage of a NULL +/// string, will result in a NULL pointer being returned. +/// +/// \remarks The string length must not exceed nom::MAX_STRING_LENGTH. A string +/// that exceeds this number will be clamped to the length of +/// nom::MAX_STRING_LENGTH. +const char* +create_string(const char* str, nom::size_type num_bytes); + +/// \brief Allocate memory for a string, using the existing string length as +/// the number of bytes to allocate. +/// +/// \param str The string to copy in place of the allocated memory. A NULL +/// value is invalid. +/// +/// \returns On success, a NULL-terminated string will be returned. On failure +/// to allocate the string, such as insufficient memory, or passage of a NULL +/// string, will result in a NULL pointer being returned. +/// +/// \remarks The string length must not exceed nom::MAX_STRING_LENGTH. A string +/// that exceeds this number will be clamped to the length of +/// nom::MAX_STRING_LENGTH. +const char* +create_string(const char* str); + +/// \brief Interpret an integer value from a string. +/// +/// \param str The string to convert to a signed integer. A NULL value is +/// invalid. +/// +/// \param input_base The number base of the given string, i.e.: two (2) for +/// binary, eight (8) for octal, ten (10) for decimal, sixteen (16) for +/// hexadecimal. A value of zero (0) will result in an attempt to auto-detect +/// the number base of the string. +/// +/// \returns On success, a signed integer representing the string's integer +/// value. On failure, such as passing a NULL string value, results in a return +/// of zero (0). +/// +/// \remarks The following rules regarding the number base auto-detection are +/// in place: +/// +/// A string prefix of zero (0) indicates octal base; this applies when the +/// input base is eight (8) or zero (0). +/// +/// A string prefix of '0x' or '0X' indicates hexadecimal base -- this applies +/// when the input base is sixteen (16) or zero (0). +int string_to_int(const char* str, int input_base); + +/// \brief Interpret an integer value from a string. +/// +/// \param str The string to convert to an unsigned integer. A NULL value is +/// invalid. +/// +/// \param input_base The number base of the given string, i.e.: two (2) for +/// binary, eight (8) for octal, ten (10) for decimal, sixteen (16) for +/// hexadecimal. A value of zero (0) will result in an attempt to auto-detect +/// the number base of the string. +/// +/// \returns On success, an unsigned integer representing the string's integer +/// value. On failure, such as passing a NULL string value, results in a return +/// of zero (0). +/// +/// \remarks The following rules regarding the number base auto-detection are +/// in place: +/// +/// A string prefix of zero (0) indicates octal base; this applies when the +/// input base is eight (8) or zero (0). +/// +/// A string prefix of '0x' or '0X' indicates hexadecimal base -- this applies +/// when the input base is sixteen (16) or zero (0). +uint string_to_uint(const char* str, int input_base); + +/// \brief Interpret an integer value from a string. +/// +/// \param str The string to convert to a signed integer. A NULL value is +/// invalid. +/// +/// \returns On success, a signed integer representing the string's integer +/// value. On failure, such as passing a NULL string value, results in a return +/// of zero (0). +/// +/// \remarks The following rules regarding the number base auto-detection are +/// in place: +/// +/// A string prefix of zero (0) indicates octal base; this applies when the +/// input base is eight (8) or zero (0). +/// +/// A string prefix of '0x' or '0X' indicates hexadecimal base -- this applies +/// when the input base is sixteen (16) or zero (0). +int string_to_int(const char* str); + +/// \brief Interpret an integer value from a string. +/// +/// \param str The string to convert to an unsigned integer. A NULL value is +/// invalid. +/// +/// \returns On success, an unsigned integer representing the string's integer +/// value. On failure, such as passing a NULL string value, results in a return +/// of zero (0). +/// +/// \remarks The following rules regarding the number base auto-detection are +/// in place: +/// +/// A string prefix of zero (0) indicates octal base; this applies when the +/// input base is eight (8) or zero (0). +/// +/// A string prefix of '0x' or '0X' indicates hexadecimal base -- this applies +/// when the input base is sixteen (16) or zero (0). +uint string_to_uint(const char* str); + +const char* integer_to_cstr(int number); +std::string integer_to_string(int number); + +nom::size_type string_length(const char* str); +nom::size_type string_length(const std::string& str); + +int +compare_cstr_insensitive(const char* str1, const char* str2, + nom::size_type len); + +int compare_cstr_insensitive(const char* str1, const char* str2); + +int +compare_cstr_sensitive(const char* str1, const char* str2, + nom::size_type len); + +int compare_cstr_sensitive(const char* str1, const char* str2); + +int +compare_string_insensitive(const std::string& str1, const std::string& str2, + nom::size_type len); +int compare_string_insensitive(const std::string& str1, const std::string& str2); + +int +compare_string_sensitive(const std::string& str1, const std::string& str2, + nom::size_type len); +int compare_string_sensitive(const std::string& str1, const std::string& str2); + +void copy_string(const char* source, char* dest); + +/// \brief Create a deep-copy instance a C style string. +/// +/// \param length Size of the string to copy. +/// +/// \returns Null-terminated string up to MAX_STRING_LENGTH. +/// +/// \todo Remove this function; it is a duplicate of nom::create_string +const char* duplicate_string(const char* str, nom::size_type length); + +const char* duplicate_string(const std::string& str, nom::size_type length); + +void free_string(const char* ptr); + +const char* to_lowercase_cstr(const char* str, nom::size_type len); +const char* to_lowercase_cstr(const char* str); + +std::string to_lowercase_string(const std::string& str, nom::size_type len); +std::string to_lowercase_string(const std::string& str); + +const char* to_uppercase_cstr(const char* str, nom::size_type len); +const char* to_uppercase_cstr(const char* str); + +std::string to_uppercase_string(const std::string& str, nom::size_type len); +std::string to_uppercase_string(const std::string& str); + +} // namespace nom + +#endif // include guard defined diff --git a/include/nomlib/core/helpers.hpp b/include/nomlib/core/unique_ptr.hpp similarity index 75% rename from include/nomlib/core/helpers.hpp rename to include/nomlib/core/unique_ptr.hpp index 246d008e..2ec850b0 100644 --- a/include/nomlib/core/helpers.hpp +++ b/include/nomlib/core/unique_ptr.hpp @@ -2,7 +2,7 @@ nomlib - C++11 cross-platform game engine -Copyright (c) 2013, 2014 Jeffrey Carpenter +Copyright (c) 2013, 2014, 2015, 2016 Jeffrey Carpenter All rights reserved. Redistribution and use in source and binary forms, with or without @@ -26,32 +26,14 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ -#ifndef NOMLIB_CORE_HELPERS_HPP -#define NOMLIB_CORE_HELPERS_HPP - -#include +#ifndef NOMLIB_CORE_UNIQUE_PTR_HPP +#define NOMLIB_CORE_UNIQUE_PTR_HPP #include "nomlib/config.hpp" -namespace nom { - -namespace priv { +#include -/// \brief Maximum size a nom::Value string type may be -/// -/// \remarks Buffer overflow protection. -const uint MAX_STRING_LENGTH = 256; - -/// \brief Clone a C style string value. -/// -/// \param length Size of the string to copy. -/// -/// \returns Null-terminated string up to MAX_STRING_LENGTH. -/// -/// \todo Find a better home for this function? -char* duplicate_string( const char* val, uint length ); - -} // namespace priv +namespace nom { /// Convenience helper for providing a version of std::make_unique for /// std::unique_ptr -- C++11 forgot to provide one like they did for @@ -66,7 +48,14 @@ char* duplicate_string( const char* val, uint length ); template std::unique_ptr make_unique(Args&&... args) { - return std::unique_ptr( new T( std::forward( args ) ... ) ); + return std::unique_ptr(new T(std::forward(args) ...)); +} + +template +inline std::string make_str(const T& str) +{ + std::string result = std::to_string(str); + return result; } } // namespace nom diff --git a/include/nomlib/graphics.hpp b/include/nomlib/graphics.hpp index 32ce2412..ad56cfba 100644 --- a/include/nomlib/graphics.hpp +++ b/include/nomlib/graphics.hpp @@ -35,7 +35,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include -#include +#include #include #include #include @@ -56,7 +56,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include -#include #include #include diff --git a/include/nomlib/system/EventDispatcher.hpp b/include/nomlib/graphics/DisplayMode.hpp similarity index 52% rename from include/nomlib/system/EventDispatcher.hpp rename to include/nomlib/graphics/DisplayMode.hpp index 2bbaef0c..1ed93bb8 100644 --- a/include/nomlib/system/EventDispatcher.hpp +++ b/include/nomlib/graphics/DisplayMode.hpp @@ -26,55 +26,56 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ -#ifndef NOMLIB_SDL2_SYSTEM_EVENT_DISPATCHER_HPP -#define NOMLIB_SDL2_SYSTEM_EVENT_DISPATCHER_HPP +#ifndef NOMLIB_GRAPHICS_DISPLAY_MODE_HPP +#define NOMLIB_GRAPHICS_DISPLAY_MODE_HPP -#include -#include - -#include +// #include +#include #include "nomlib/config.hpp" -#include "nomlib/core/clock.hpp" -#include "nomlib/system/Event.hpp" +#include "nomlib/math/Size2.hpp" namespace nom { -/// \brief Events dispatcher; a wrapper for SDL2 user events API. -class EventDispatcher +/// \brief Container for describing a video display mode +struct DisplayMode { - public: - /// \brief Default constructor. - EventDispatcher( void ); - - /// \brief Destructor. - ~EventDispatcher( void ); - - /// \brief Dispatch an event. - /// - /// \returns Boolean TRUE when a message has successfully been dispatched or - /// boolean FALSE on failure, ~~such as when we have hit the allocation cap - /// on user-defined events.~~ - bool dispatch( const Event& ev ); - - private: - /// \brief Internal method wrapper for dispatch. - bool push_event( const Event& ev ); - - /// \brief Allocate a set of user-defined events. - /// - /// \param The number of events to be allocated. - /// - /// \returns ~~The beginning event number for the set of events or (uint32)-1 - /// if there are not enough user-defined events left.~~ - /// - /// \note This method call is currently not used; there may be a potential - /// risk of us running out of room in SDL2's events queue! See - /// our class file (EventDispatcher.cpp) for the incomplete, broken code; - /// I could not ever get more than one user event dispatched. - uint32 register_events( int num_events ); + /// \brief The pixel format of the display video mode. + /// + /// \remarks One of the SDL_PixelFormatEnum enumeration values. + uint32 format = 0; + + /// \brief The horizontal and vertical dimensions, in pixel units, of the + /// display video mode. + Size2i bounds; + + /// \brief The display vertical refresh rate of the display video mode. + /// + /// \remarks The display's vertical refresh rate, in hertz. Zero (0) if + /// unspecified. + int refresh_rate = 0; }; +typedef std::vector DisplayModeList; + +/// \brief Compare two video modes for equality. +bool operator ==(const DisplayMode& lhs, const DisplayMode& rhs); + +/// \brief Compare two video modes for inequality. +bool operator !=(const DisplayMode& lhs, const DisplayMode& rhs); + +/// \brief Compare two video modes for lesser than. +bool operator <(const DisplayMode& lhs, const DisplayMode& rhs); + +/// \brief Compare two video modes for greater than. +bool operator >(const DisplayMode& lhs, const DisplayMode& rhs); + +/// \brief Compare two video modes for lesser than or equal to. +bool operator <=(const DisplayMode& lhs, const DisplayMode& rhs); + +/// \brief Compare two video modes for greater than or equal to. +bool operator >=(const DisplayMode& lhs, const DisplayMode& rhs); + } // namespace nom #endif // include guard defined diff --git a/include/nomlib/graphics/Gradient.hpp b/include/nomlib/graphics/Gradient.hpp index 84a4b3ac..821a8e62 100644 --- a/include/nomlib/graphics/Gradient.hpp +++ b/include/nomlib/graphics/Gradient.hpp @@ -40,12 +40,13 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "nomlib/math/Color4.hpp" #include "nomlib/graphics/IDrawable.hpp" #include "nomlib/graphics/shapes/Rectangle.hpp" -#include "nomlib/core/helpers.hpp" - -#include "nomlib/graphics/Texture.hpp" +#include "nomlib/core/unique_ptr.hpp" namespace nom { +// Forward declarations +class Texture; + /// \brief Rectangle fill class with dithered, linear gradient colors. class Gradient: public Transformable @@ -99,6 +100,15 @@ class Gradient: public Transformable /// \remarks This uniquely identifies the object's type. ObjectTypeInfo type( void ) const; + /// \brief Get the underlying texture of the rendered gradient. + /// + /// \returns A pointer to a new nom::Texture instance of the stored texture + /// used for rendering the gradient. The returned pointer is owned by the + /// caller. + /// + /// \remarks This is **not** a deep-copy of the texture. + Texture* texture() const; + /// \brief Query the validity of the object /// /// \remarks A valid object must have both the positioning & size bounding @@ -174,7 +184,7 @@ class Gradient: public Transformable /// \remarks This greatly optimizes performance of this class when there /// are a large number of rectangles being drawn (i.e.: high resolution /// gradient being used as a backdrop, or several gradient objects). - Texture texture_; + std::shared_ptr texture_; /// \brief The starting & ending colors used in the gradient fill. /// diff --git a/include/nomlib/graphics/IDrawable.hpp b/include/nomlib/graphics/IDrawable.hpp index 8248e467..f3728bc7 100644 --- a/include/nomlib/graphics/IDrawable.hpp +++ b/include/nomlib/graphics/IDrawable.hpp @@ -55,6 +55,7 @@ class IDrawable: public IObject /// object. typedef std::shared_ptr shared_ptr; + /// \todo Remove or relocate this definition to resolve coupling issue? typedef const RenderWindow RenderTarget; /// Do nothing at all default constructor @@ -96,7 +97,6 @@ class IDrawable: public IObject } }; - } // namespace nom #endif // NOMLIB_IDRAWABLE_HEADERS defined diff --git a/include/nomlib/graphics/Image.hpp b/include/nomlib/graphics/Image.hpp index 64af8f53..589baa8c 100644 --- a/include/nomlib/graphics/Image.hpp +++ b/include/nomlib/graphics/Image.hpp @@ -107,7 +107,7 @@ class Image /// \param pixel_format Pixel format to use for the memory buffer. /// /// \deprecated - bool create( const Point2i& size, uint32 pixel_format ); + bool create(const Point2i& size, uint32 pixel_format); bool create( const Size2i& size, uint32 pixel_format ); /// \brief Create a new memory buffer using the most optimal pixel format @@ -143,7 +143,7 @@ class Image const IntRect bounds ( void ) const; /// Check to see if the video surface needs locking - bool must_lock ( void ) const; + bool must_lock() const; /// Obtain the locked status of the video surface memory /// @@ -201,7 +201,7 @@ class Image bool save_png ( const std::string& filename ) const; /// Obtain the width and height (in pixels) of the stored bitmap buffer - const Point2i size ( void ) const; + Size2i size() const; /// Obtain the set color key for this image /// @@ -221,7 +221,7 @@ class Image /// /// \param colorkey Pixel color to mark transparent /// \param flag TRUE to enable color key; FALSE to disable color key - bool set_colorkey ( const Color4i& colorkey, bool flag ); + bool set_colorkey(const Color4i& colorkey, bool flag); /// Set RLE acceleration for this image /// @@ -230,7 +230,7 @@ class Image /// pixels. /// /// \param TRUE to enable RLE acceleration; FALSE to disable - bool RLE ( bool flag ); + bool RLE(bool flag); /// \brief Read a RGBA pixel from the video surface. /// @@ -274,7 +274,7 @@ class Image /// Lock the video surface; this must be done before you attempt to write /// directly to video memory (pixel manipulation). - bool lock ( void ) const; + bool lock() const; /// Unlocks the video surface; this must be done after you are finished /// writing to the video buffer. During the time that the video surface is @@ -311,7 +311,6 @@ class Image IntRect bounds_; // Not implemented }; - } // namespace nom #endif // NOMLIB_SDL_IMAGE_HEADERS defined diff --git a/include/nomlib/graphics/RenderWindow.hpp b/include/nomlib/graphics/RenderWindow.hpp index 91be558f..fd92cf9b 100644 --- a/include/nomlib/graphics/RenderWindow.hpp +++ b/include/nomlib/graphics/RenderWindow.hpp @@ -38,7 +38,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "nomlib/math/Rect.hpp" #include "nomlib/math/Color4.hpp" #include "nomlib/math/Point2.hpp" -#include "nomlib/graphics/VideoMode.hpp" +#include "nomlib/graphics/DisplayMode.hpp" #include "nomlib/graphics/Renderer.hpp" #include "nomlib/graphics/Image.hpp" #include "nomlib/system/SDL_helpers.hpp" @@ -53,14 +53,16 @@ struct PixelsDeleter void operator()(void* ptr); }; -//class Renderer; - class RenderWindow: public Renderer { public: typedef RenderWindow SelfType; typedef SelfType* RawPtr; + static const Point2i DEFAULT_WINDOW_POS; + static const Point2i WINDOW_POS_CENTERED; + static const uint32 DEFAULT_WINDOW_FLAGS = 0; + /// \brief Default constructor; initialize an object to sane, but invalid /// defaults RenderWindow( void ); @@ -83,27 +85,28 @@ class RenderWindow: public Renderer /// \remarks This resource has been marked non-copyable. SelfType& operator =( const SelfType& other ) = delete; - /// Initialize a SDL window and renderer - bool create ( - const std::string& window_title, - int32 width, - int32 height, + /// \brief Initialize a native platform window and renderer. + bool create( const std::string& window_title, const Size2i& res, uint32 window_flags, - int32 rendering_driver = -1, - uint32 context_flags = - SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE - ); + int rendering_driver = DEFAULT_RENDERING_DRIVER, + uint32 renderer_flags = DEFAULT_RENDERER_FLAGS ); - /// Initialize a SDL window and renderer. + /// \brief Initialize a native platform window and renderer. /// - /// \see nom::RenderWindow::create. - bool create ( - const std::string& window_title, - const Size2i& res, - uint32 window_flags, - int32 rendering_driver = -1, - uint32 context_flags = SDL_RENDERER_ACCELERATED - ); + /// \param pos The position of the window, relative to the video display + /// bounds. + /// + /// \param display_index The video display to position the window on. + /// + /// \see nom::RenderWindow::DEFAULT_WINDOW_POS, + /// nom::RenderWindow::WINDOW_POS_CENTERED + /// + /// \see nom::RenderWindow::::display_bounds, + /// nom::RenderWindow::::display_modes + bool create( const std::string& window_title, const Point2i& pos, + int display_index, const Size2i& res, uint32 window_flags, + int rendering_driver = DEFAULT_RENDERING_DRIVER, + uint32 renderer_flags = DEFAULT_RENDERER_FLAGS ); /// Obtain a pointer to this Window object. RenderWindow::RawPtr get ( void ); @@ -120,8 +123,8 @@ class RenderWindow: public Renderer /// Is this object initialized -- not nullptr? bool window_valid( void ) const; - /// Obtain this Window's position. - Point2i position ( void ) const; + /// \brief Get the window's current position. + Point2i position() const; /// \brief Get this window's size dimensions. /// @@ -144,18 +147,30 @@ class RenderWindow: public Renderer /// \todo Test me const IntRect display_bounds ( void ) const; - /// Obtain a list of supported video modes + /// \brief Get the display mode capabilities of the window. + /// + /// \param modes The object reference to fill with the list of display + /// video modes. /// - /// Returns a sorted vector of VideoMode objects, from greatest to least. + /// \returns Boolean TRUE if the enumeration of display video models was + /// successful, and boolean FALSE if the enumeration was non-successful. /// - /// \todo Test out 8-bit, 16-bit, 24-bit video surfaces + /// \remarks The display video modes will be sorted from greater to least. + /// + /// \see nom::DisplayMode, SDL_DisplayMode + bool display_modes(DisplayModeList& modes) const; + + /// \brief Get a video mode's refresh rate for the display of the window. /// - /// \todo SDL2 support - VideoModeList getVideoModes ( void ) const; + /// \returns The video mode vertical refresh rate, in hertz, or zero (0) + /// if unspecified on success, or negative one (-1) on failure, such as if + /// the enumeration of the current display video mode for the window failed. + int refresh_rate() const; void set_size ( int32 width, int32 height ); - void set_position ( int32 x, int32 y ); + /// \brief Set the window's position. + void set_position(const Point2i& window_pos); /// Update the surface of the screen inside the window /// @@ -208,10 +223,29 @@ class RenderWindow: public Renderer /// \todo Rename to window_from_id? static SDL_WINDOW::RawPtr window_id( uint32 id ); - /// Obtain this window's display index + /// \brief Get the display index associated with this window. /// - /// \return This window's display index - int window_display_id ( void ) const; + /// \returns The index of the display containing the center of the window + /// on success, and a negative error code on failure. + /// + /// \remarks The display identifier order is platform-dependent. + int window_display_id() const; + + /// \brief Get the display name associated with a window. + /// + /// \returns The name of the display on success, and a NULL-terminated + /// string on failure. + /// + /// \remarks The display name is platform-dependent. + static std::string display_name(int display_id); + + /// \brief Get the display name associated with this window. + /// + /// \returns The name of the display on success, and a NULL-terminated + /// string on failure. + /// + /// \remarks The display name is platform-dependent. + std::string display_name() const; /// \brief Get the window which currently has mouse focus. /// @@ -272,7 +306,7 @@ class RenderWindow: public Renderer /// \param grab TRUE to grab input; FALSE to release input /// /// \todo Test me - void set_window_grab ( bool grab ); + void set_window_grab(bool grab); /// Set the minimum size of the window's client area. /// @@ -297,7 +331,7 @@ class RenderWindow: public Renderer /// \todo Restructure code shared with ::save_screenshot. /// /// \see RenderWindow::save_screenshot, Image::save_png. - bool save_png_file( const std::string& filename ) const; + bool save_png_file(const std::string& filename) const; /// Save a screen shot of the window as a PNG file /// @@ -312,7 +346,7 @@ class RenderWindow: public Renderer /// \todo Pixels pitch calculation (see screenshot.initialize call) /// /// \see RenderWindow::save_png_file, Image::save_png. - bool save_screenshot( const std::string& filename ) const; + bool save_screenshot(const std::string& filename) const; /// Set the current Window as the active rendering context; this must be /// called before doing any drawing (this includes creation of textures) @@ -328,6 +362,12 @@ class RenderWindow: public Renderer /// active context -- set by nom::RenderWindow::make_current. static SDL_Renderer* context( void ); + /// \brief Get the number of available video displays. + /// + /// \returns The number of video displays -- a number greater than or equal + /// to one (1) on success, or a negative number on failure. + static int num_video_displays(); + private: /// \brief Set a new nom::RenderWindow as the active rendering context; we must /// always have a context active at any given time for generating @@ -338,10 +378,10 @@ class RenderWindow: public Renderer SDL_WINDOW::UniquePtr window_; - /// Cache the unique window identifier we get from SDL upon initialization + /// \brief The unique identifier as recognized internally by SDL. uint32 window_id_; - /// Cache the display identifier we get from SDL upon initialization + /// \brief The unique identifier given to the display. int window_display_id_; /// State of the window (visible or not) diff --git a/include/nomlib/graphics/Renderer.hpp b/include/nomlib/graphics/Renderer.hpp index 7af1250f..0610a249 100644 --- a/include/nomlib/graphics/Renderer.hpp +++ b/include/nomlib/graphics/Renderer.hpp @@ -43,6 +43,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. namespace nom { +// Forward declarations +class Texture; + /// \brief Video subsystem responsible for managing high-level graphics display /// (think: *very* fancy back-buffer). /// @@ -50,6 +53,17 @@ namespace nom { class Renderer { public: + /// \brief The index of the default rendering driver to use. + /// + /// \remarks A value of negative one (-1) will choose the first available + /// driver that supports the requested rendering flags. + /// + /// \see nom::available_render_driver + static const int DEFAULT_RENDERING_DRIVER = -1; + + static const uint32 DEFAULT_RENDERER_FLAGS = + SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE; + /// Default constructor; initializes instance to sane defaults Renderer ( void ); @@ -61,7 +75,9 @@ class Renderer /// Initializes with the first rendering driver supporting our request /// flags /// Enables video acceleration when able - bool create ( SDL_WINDOW::RawPtr window, int32 rendering_driver = -1, uint32 context_flags = SDL_RENDERER_ACCELERATED ); + bool create( SDL_WINDOW::RawPtr window, + int rendering_driver = DEFAULT_RENDERING_DRIVER, + uint32 renderer_flags = DEFAULT_RENDERER_FLAGS ); /// Get a raw pointer to the SDL_Renderer in use SDL_Renderer* renderer ( void ) const; @@ -90,10 +106,8 @@ class Renderer /// \returns Size2i object filled with the width and height fields. Size2i output_size( void ) const; - /// Obtain the renderer's clipping rectangle bounds (X, Y, width & height) - /// in pixels - /// \todo Test me! - const IntRect bounds ( void ) const; + /// \brief Obtain the renderer's clipping rectangle bounds (in pixels). + IntRect clip_bounds() const; /// \brief Obtain information specific to your rendering hardware /// capabilities. @@ -108,11 +122,19 @@ class Renderer /// is probably the API you want to use outside of nomlib. static const RendererInfo caps ( SDL_Renderer* target ); - /// \brief Reset the current rendering target. - /// - /// \see nom::Texture::set_render_target. + /// \brief Set the current rendering target back to the default renderer. bool reset_render_target() const; + /// \brief Set a texture as the current rendering target. + /// + /// \remarks The nom::Texture must be initialized as a + /// nom::Texture::Access::RenderTarget. + /// + /// \note Not all video hardware has support for this feature. + /// + /// \see ::reset_render_target + bool set_render_target(const Texture* texture) const; + /// Update the renderer surface on the attached window void update ( void ) const; @@ -168,13 +190,11 @@ class Renderer /// SDL_BLENDMODE_MOD bool set_blend_mode ( const SDL_BlendMode mode ); - /// Set new clipping rectangle bounds for the rendering target. + /// \brief Set new clipping rectangle bounds for the rendering target. /// /// \param bounds Passing nom::IntRect::null will disable clipping on - /// the target - /// - /// \todo Test me! - bool set_bounds ( const IntRect& bounds ); + /// the target. + bool set_clip_bounds(const IntRect& bounds); /// \brief Obtain pixels buffer of the entire rendering target /// diff --git a/include/nomlib/graphics/Text.hpp b/include/nomlib/graphics/Text.hpp index f2736c31..bb3d6b4e 100644 --- a/include/nomlib/graphics/Text.hpp +++ b/include/nomlib/graphics/Text.hpp @@ -42,12 +42,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. namespace nom { -/// \todo Implement back-buffer for texture rendering class Text: public Transformable { public: typedef Text self_type; - typedef Transformable derived_class; + typedef Transformable derived_type; /// \brief Font face style; multiple styles can be combined from bitwise /// masks. @@ -89,17 +88,43 @@ class Text: public Transformable const Color4i& text_color = Color4i::White ); + /// \brief Copy constructor. + /// + /// \internal + /// \remarks This is necessary for the std::unique_ptr instance we use for + /// the cached texture. + /// \endinternal + Text(const self_type& rhs); + + /// \brief Copy assignment operator. + /// + /// \internal + /// \remarks This is necessary for the std::unique_ptr instance we use for + /// the cached texture. + /// \endinternal + self_type& operator =(const self_type& rhs); + + /// \brief Set the position of the rendered text. + /// + /// \remarks Re-implements Transformable::set_position. + virtual void set_position(const Point2i& pos) override; + /// \brief Implements the required IDrawable::clone method. - IDrawable::raw_ptr clone( void ) const; + Text* clone() const; /// \brief Re-implements the IObject::type method. /// /// \remarks This uniquely identifies the object's type. - ObjectTypeInfo type( void ) const; + ObjectTypeInfo type() const override; const Font& font() const; - const Texture& texture ( void ) const; + /// \brief Get a deep-copy instance of the underlying texture used for the + /// text rendering. + /// + /// \returns A raw pointer to a new nom::Texture instance of the rendered + /// text. The returned pointer is owned by the caller. + Texture* clone_texture() const; /// Obtain validity of the Text object bool valid ( void ) const; @@ -115,9 +140,7 @@ class Text: public Transformable /// in pixels, on success. Zero (0) integer value on failure; /// a possible combination of: no font, bad font, no text string /// etc. - /// - /// \todo Support multi-line texts (newline character handling) - sint text_width ( const std::string& text_string ) const; + int text_width(const std::string& text_buffer) const; /// \brief Obtain the text height in pixels of the set text /// @@ -178,12 +201,18 @@ class Text: public Transformable /// \see The nom::Text::Style enumeration. void set_style( uint32 style ); + /// \brief Set the use of rendering glyphs with respect to kerning offsets, + /// when supported by the underlying font type. + void set_text_kerning(bool state); + /// Render text to a target /// /// \todo Test horizontal tabbing '\t' - void draw ( RenderTarget& target ) const; + void draw(RenderTarget& target) const override; private: + void render_text(RenderTarget& target) const; + /// \brief Apply requested transformations, styles, etc /// /// \remarks This internal method takes care of updating the properties of @@ -191,7 +220,7 @@ class Text: public Transformable /// need to worry about using the API in a particular order. /// /// \note Implements nom::IDrawable::update. - void update(); + void update() override; /// \brief Get the current text width. int width() const; @@ -199,10 +228,23 @@ class Text: public Transformable /// \brief Get the current text height. int height() const; + bool update_cache(); + + int + multiline_width(const std::string& text_buffer, nom::size_type pos) const; + Font font_; - /// \fixme - mutable Texture texture_; + /// \brief A texture atlas created from the nom::Font instance that is + /// referred to in rendering a text. + /// + /// \see ::set_text_size, ::update, ::render_text + mutable Texture glyphs_texture_; + + /// \brief The texture containing the rendered text. + /// + /// \see ::update_cache, ::texture, ::draw + std::unique_ptr rendered_text_; /// Holds contents of text as a string buffer std::string text_; diff --git a/include/nomlib/graphics/Texture.hpp b/include/nomlib/graphics/Texture.hpp index a57d70cb..029535c6 100644 --- a/include/nomlib/graphics/Texture.hpp +++ b/include/nomlib/graphics/Texture.hpp @@ -53,7 +53,7 @@ class RenderWindow; class Texture { public: - typedef std::shared_ptr SharedPtr; + typedef Texture self_type; /// \brief Available pixel rescaling algorithms enum ResizeAlgorithm @@ -90,20 +90,23 @@ class Texture /// time before freeing its memory. ~Texture( void ); - /// \brief Destroy the texture. - /// - /// \remarks The pixel data and pitch value associated with the Texture is - /// freed as well. The other attributes, such as position and color key are - /// *not* reset to their respective defaults. - void free_texture( void ); - /// Copy constructor Texture ( const Texture& copy ); /// Copy assignment operator Texture& operator = ( const Texture& other ); - /// Create a deep copy of this instance. + /// \brief Get a shallow-copy of the underlying stored texture. + /// + /// \returns A pointer to a new nom::Texture instance from the stored + /// data of this object's instance. The returned pointer is owned by the + /// caller. + /// + /// \remarks The cloned instance shares the same internal texture memory. + /// If a deep-copy clone is required, you should either keep the nom::Image + /// source used to create the texture's pixel buffer and clone from it + /// instead. Alternatives may include using a Render To Texture target or + /// nom::Renderer::pixels. Texture* clone() const; /// Initialize an object with specified parameters @@ -116,6 +119,12 @@ class Texture /// SDL_TextureAccess bool initialize ( uint32 format, uint32 flags, int width, int height ); + /// \see ::intialize(uint32 format, uint32 flags, int width, int height) + bool initialize(uint32 format, uint32 flags, const Size2i& dims); + + /// \see Image::texture + bool create(SDL_Texture* source); + /// \remarks Texture::Access::Static type bool create ( const Image& source ); @@ -123,7 +132,7 @@ class Texture /// Access::RenderTarget. bool create( const Image& source, uint32 pixel_format, enum Texture::Access type ); - const Point2i& position( void ) const; + const Point2i& position() const; /// \brief Get the width & height dimensions of the texture. /// @@ -132,28 +141,31 @@ class Texture /// /// \fixme We need to decide if we ought to return the dimensions as per /// what SDL has on record, or if we should use cached values. - const Size2i size( void ) const; + const Size2i& size() const; - const IntRect& bounds( void ) const; + const IntRect& bounds() const; - /// Get the video memory surface of the Texture object + /// \brief Get the underlying texture stored. SDL_Texture* texture() const; /// Is this object initialized -- not nullptr? - bool valid ( void ) const; + bool valid() const; /// \brief Query texture access type /// /// \returns Texture::Access enumeration enum Texture::Access access ( void ) const; - void set_position( const Point2i& pos ); + void set_position(const Point2i& pos); /// \brief Set the width & height dimensions of the texture. - void set_size( const Size2i& size ); + /// + /// \remarks If the width or height dimensions are greater than the + /// original source dimensions, they will automatically be rescaled. + void set_size(const Size2i& size); /// Set bounding coordinates of the Texture - void set_bounds( const IntRect& bounds ); + void set_bounds(const IntRect& bounds); /// Obtain width, in pixels, of texture /// @@ -213,13 +225,13 @@ class Texture static const Point2i maximum_size ( void ); /// \brief Query lock status of texture - bool locked ( void ) const; + bool locked() const; /// \brief Lock the entire bounds of the texture for write access to the /// pixel buffer. /// /// \remarks Texture must have been created as the Access::Streaming type. - bool lock ( void ); + bool lock(); /// \brief Lock a portion of the texture for write access to the pixel /// buffer. @@ -229,7 +241,7 @@ class Texture /// /// \remarks Texture must have been created as the Access::Streaming type. - bool lock ( const IntRect& bounds ); + bool lock(const IntRect& bounds); /// Unlock the texture; signals the OK to upload the pixel buffer to the GPU /// @@ -272,14 +284,14 @@ class Texture /// /// \param SDL_Renderer /// - void draw ( SDL_Renderer* target ) const; + void draw(SDL_Renderer* target) const; /// Draw a nom::Texture to a nom::RenderWindow target /// /// \param nom::RenderWindow /// /// \note This is an alias for nom::Texture::draw ( SDL_Renderer* ) - void draw( const RenderWindow& target ) const; + void draw(const RenderWindow& target) const; /// Draw a rotated nom::Texture to a rendering target /// @@ -287,13 +299,13 @@ class Texture /// \param angle Rotation angle in degrees /// /// \todo Implement pivot point & make use of SDL_RendererFlip enum - void draw ( SDL_Renderer* target, const double angle ) const; + void draw(SDL_Renderer* target, const real64 angle) const; /// Draw a rotated nom::Texture on a nom::RenderWindow /// /// \param target Reference to an active nom::RenderWindow /// \param angle Rotation angle in degrees - void draw( const RenderWindow& target, const double angle ) const; + void draw(const RenderWindow& target, const real64 angle) const; /// \brief Set an additional alpha value multiplied into render copy /// operations. @@ -367,15 +379,6 @@ class Texture bool copy_pixels ( const void* source, int pitch ); - /// \brief Set the nom::Texture as the current renderer target. - /// - /// \remarks nom::Texture access type must be Texture::Access::RenderTarget - /// - /// \note Not all graphics hardware supports this request - /// - /// \see nom::Renderer::reset_render_target. - bool set_render_target(RenderWindow& target); - private: void set_scale_factor(int factor); @@ -393,10 +396,7 @@ class Texture /// width & height members. Point2i position_; // This should probably be an IntRect. (Global bounds). - /// \todo This needs to be used ASAP; we've been using our texture source - /// bounds with our texture size coordinates, and this makes a huge, - /// confusing mess of things. See Texture.cpp for additional comments. - // Size2i size_; + Size2i size_; /// Position & size of texture within memory; X, Y, width & height in pixels. /// diff --git a/include/nomlib/graphics/VideoMode.hpp b/include/nomlib/graphics/VideoMode.hpp deleted file mode 100644 index bd702ebf..00000000 --- a/include/nomlib/graphics/VideoMode.hpp +++ /dev/null @@ -1,119 +0,0 @@ -/****************************************************************************** - - nomlib - C++11 cross-platform game engine - -Copyright (c) 2013, 2014 Jeffrey Carpenter -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -******************************************************************************/ -#ifndef NOMLIB_VIDEO_MODE_HPP -#define NOMLIB_VIDEO_MODE_HPP - -#include -#include -#include -#include -#include - -#include "nomlib/config.hpp" - -namespace nom { - -class VideoMode -{ - public: - /// Default constructor; initialize instance members to zero (0) - VideoMode ( void ); - - /// Initialize this instance with provided provided data - VideoMode ( int32 mode_width, int32 mode_height, uint8 mode_bpp ); - - /// Destructor - ~VideoMode ( void ); - - public: - /// Horizontal number of pixels - int32 width; - - /// Vertical number of pixels - int32 height; - - /// Bits per pixel - uint8 bpp; -}; - -/// Pretty print the mode using xx as the -/// formatting string, and will look like this: -/// -/// 1280x720x32 -std::ostream& operator << ( std::ostream& os, const VideoMode& mode ); - -/// Compare two video modes for equality -bool operator == ( const VideoMode& lhs, const VideoMode& rhs ); - -/// Compare two video modes for inequality -bool operator != ( const VideoMode& lhs, const VideoMode& rhs ); - -/// Compare two video modes for lesser than -bool operator < ( const VideoMode& lhs, const VideoMode& rhs ); - -/// Compare two video modes for greater than -bool operator > ( const VideoMode& lhs, const VideoMode& rhs ); - -/// Compare two video modes for lesser than or equal to -bool operator <= ( const VideoMode& lhs, const VideoMode& rhs ); - -/// Compare two video modes for greater than or equal to -bool operator >= ( const VideoMode& lhs, const VideoMode& rhs ); - -/// Convenience typedef -typedef std::vector VideoModeList; - - -} // namespace nom - -#endif // include guard defined - -/* - VideoMode Class Usage Example - - // Public headers interface; this includes all of nomlib's header files for - // the graphics subsystem supported by your platform. - #include - - // ...or just the header file we need here, if you the minimal route! - #include - - nom::Display context; // Primary video surface - - // This should be safe to call before the initialization of the video display; - // setting of the video resolution beforehand is not necessary. - nom::VideoModeList modes = context.getVideoModes(); - - // Output to the console the available modes we have - for ( nom::uint32 idx = 0; idx != modes.size(); idx++ ) - { - modes[idx].pp(); - } - -*/ diff --git a/include/nomlib/graphics/fonts/BMFont.hpp b/include/nomlib/graphics/fonts/BMFont.hpp index b81011c4..3e31851f 100644 --- a/include/nomlib/graphics/fonts/BMFont.hpp +++ b/include/nomlib/graphics/fonts/BMFont.hpp @@ -108,7 +108,7 @@ class BMFont: public IFont /// /// \param character_size Not implemented. /// - /// \returns A kerning pair offset value on success, or nom::int_min on + /// \returns A kerning pair offset value on success, or nom::NOM_INT_MIN on /// failure, such as if the font in use is invalid. If font kerning is /// disabled, a value of zero (0) is always returned. int kerning(uint32 first_char, uint32 second_char, uint32 character_size) const override; diff --git a/include/nomlib/graphics/fonts/TrueTypeFont.hpp b/include/nomlib/graphics/fonts/TrueTypeFont.hpp index 419555e0..6a3f4f20 100644 --- a/include/nomlib/graphics/fonts/TrueTypeFont.hpp +++ b/include/nomlib/graphics/fonts/TrueTypeFont.hpp @@ -107,7 +107,7 @@ class TrueTypeFont: public IFont /// \brief Obtain the kerning pair offsets between two glyphs. /// - /// \returns A kerning pair offset value on success, or nom::int_min on + /// \returns A kerning pair offset value on success, or nom::NOM_INT_MIN on /// failure, such as if the font in use is invalid. If font kerning is /// disabled, a value of zero (0) is always returned. /// diff --git a/include/nomlib/graphics/graphics_helpers.hpp b/include/nomlib/graphics/graphics_helpers.hpp index 31839951..b19c216b 100644 --- a/include/nomlib/graphics/graphics_helpers.hpp +++ b/include/nomlib/graphics/graphics_helpers.hpp @@ -37,23 +37,38 @@ namespace nom { // Forward declarations class Transformable; +class Texture; /// \brief Calculate an object's alignment rectangle. /// -/// \returns Point2i::null on failure, such as when an invalid Transformable -/// object pointer is passed to this function. +/// \returns The X and Y coordinates computed with respect to alignment to +/// the given boundaries. /// -/// \param obj A nom::Transformable derived object pointer. -/// \param bounds The dimensions to calculate alignment for. +/// \param obj_dims The dimensions of the object to use for alignment. +/// \param pos_offset Additional X, Y coordinates to offset the alignment by. +/// \param align_bounds The total boundary dimensions of the object's alignment. /// \param align One of the nom::Alignment or nom::Anchor enumeration types. -Point2i alignment(Transformable* obj, const Size2i& bounds, uint32 align); +Point2i alignment_rect( const Size2i& obj_dims, const Point2i& pos_offset, + const Size2i& align_bounds, uint32 align ); /// \brief Set an object's alignment. /// -/// \param obj A nom::Transformable derived object pointer. -/// \param bounds The dimensions to be aligned in respect to. +/// \param obj A valid nom::Transformable derived object pointer. +/// \param pos_offset Additional X, Y coordinates to offset the alignment by. +/// \param align_bounds The total boundary dimensions of the object's alignment. /// \param align One of the nom::Alignment or nom::Anchor enumeration types. -void set_alignment(Transformable* obj, const Size2i& bounds, uint32 align); +void set_alignment( Transformable* obj, const Point2i& pos_offset, + const Size2i& align_bounds, uint32 align ); + +/// \brief Set an object's alignment. +/// +/// \param obj A valid nom::Texture derived object pointer. +/// \param pos_offset Additional X, Y coordinates to offset the alignment by. +/// \param align_bounds The total boundary dimensions of the object's alignment. +/// \param align One of the nom::Alignment or nom::Anchor enumeration +/// types. +void set_alignment( Texture* obj, const Point2i& pos_offset, + const Size2i& align_bounds, uint32 align ); } // namespace nom diff --git a/include/nomlib/graphics/shapes/Line.hpp b/include/nomlib/graphics/shapes/Line.hpp index 506264e0..5e056b1a 100644 --- a/include/nomlib/graphics/shapes/Line.hpp +++ b/include/nomlib/graphics/shapes/Line.hpp @@ -49,10 +49,6 @@ class Line: public Shape typedef Line self_type; typedef Shape derived_class; - typedef self_type* raw_ptr; - typedef std::unique_ptr unique_ptr; - typedef std::shared_ptr shared_ptr; - /// \brief Default constructor. Line ( void ); @@ -66,13 +62,13 @@ class Line: public Shape /// \param color nom::Color4i color to render. Line ( const IntRect& bounds, const Color4i& outline ); - /// \brief Implements the required IDrawable::clone method. - IDrawable::raw_ptr clone( void ) const; + /// \brief Implements the required Shape::clone method. + virtual Shape* clone() const override; /// \brief Re-implements the IObject::type method. /// /// \remarks This uniquely identifies the object's type. - ObjectTypeInfo type( void ) const; + ObjectTypeInfo type() const override; /// Do nothing method; we have it only because it is required by interface /// contract with IDrawable (which is fine). @@ -81,12 +77,12 @@ class Line: public Shape /// using a return / abort on rendering when our line object is up-to-date /// and determine if it is worth the implementation VS risk of inconsistency. /// Jeffrey Carpenter @ 2013-10-03 - void update ( void ); + void update() override; /// \brief Render the line segments. /// /// \param target nom::RenderWindow object to render to. - void draw ( RenderTarget& target ) const; + void draw(RenderTarget& target) const override; }; } // namespace nom diff --git a/include/nomlib/graphics/shapes/Rectangle.hpp b/include/nomlib/graphics/shapes/Rectangle.hpp index 36672a29..2fa3010d 100644 --- a/include/nomlib/graphics/shapes/Rectangle.hpp +++ b/include/nomlib/graphics/shapes/Rectangle.hpp @@ -37,6 +37,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. namespace nom { +// Forward declarations +class Texture; + /// \brief 2D Rectangle shape /// /// \todo Use SDL2's new multi-rectangle API; see SDL_RenderFillRects. @@ -46,44 +49,44 @@ class Rectangle: public Shape { public: typedef Rectangle self_type; - typedef self_type* raw_ptr; - - typedef std::unique_ptr unique_ptr; - typedef std::shared_ptr shared_ptr; + typedef Shape derived_type; /// \brief Default constructor. - Rectangle ( void ); + Rectangle(); /// \brief Destructor; should be fine to inherit from. - virtual ~Rectangle ( void ); + virtual ~Rectangle(); /// \brief Construct a Rectangle object from parameters. /// /// \param rect nom::IntRect object containing the coordinates. /// \param color nom::Color4i color to fill with. - Rectangle ( const IntRect& rect, const Color4i& fill ); - - /// \brief Implements the required IDrawable::clone method. - IDrawable::raw_ptr clone( void ) const; + Rectangle(const IntRect& rect, const Color4i& fill); /// \brief Re-implements the IObject::type method. /// /// \remarks This uniquely identifies the object's type. - ObjectTypeInfo type( void ) const; + ObjectTypeInfo type() const override; - /// Do nothing method; we have it only because it is required by interface - /// contract with IDrawable (which is fine). + /// \note Implements the required Shape::clone method. + virtual Shape* clone() const override; + + /// \brief Get a texture representation of the rectangle. + /// + /// \returns A pointer to a new nom::Texture instance containing the + /// rendering of the rectangle. The returned pointer is owned by the + /// caller. /// - /// \todo Measure performance (CPU cycles, ticks/FPS, ...) difference with - /// using a return / abort on rendering when our line object is up-to-date - /// and determine if it is worth the implementation VS risk of inconsistency. - /// Jeffrey Carpenter @ 2013-10-03 - void update ( void ); + /// \remarks This is an expensive function call. + Texture* texture() const; + + /// \note Implements the required IDrawable::update method. + void update() override; /// \brief Render the rectangle shape. /// /// \param target nom::RenderWindow object to render to. - void draw ( RenderTarget& target ) const; + void draw(RenderTarget& target) const override; }; } // namespace nom diff --git a/include/nomlib/graphics/shapes/Shape.hpp b/include/nomlib/graphics/shapes/Shape.hpp index 4ebaf89c..ddd9b64f 100644 --- a/include/nomlib/graphics/shapes/Shape.hpp +++ b/include/nomlib/graphics/shapes/Shape.hpp @@ -49,10 +49,12 @@ class Shape: public Transformable /// \brief Copy assignment operator //Shape& operator = ( const Shape& other ); + virtual Shape* clone() const = 0; + /// \brief Re-implements the IObject::type method. /// /// \remarks This uniquely identifies the object's type. - ObjectTypeInfo type( void ) const; + ObjectTypeInfo type() const override; /// \brief Obtain the outline color used in rendering a shape. const Color4i& outline_color ( void ) const; diff --git a/include/nomlib/graphics/sprite/AnimatedSprite.hpp b/include/nomlib/graphics/sprite/AnimatedSprite.hpp deleted file mode 100644 index 936b495c..00000000 --- a/include/nomlib/graphics/sprite/AnimatedSprite.hpp +++ /dev/null @@ -1,154 +0,0 @@ -/****************************************************************************** - - nomlib - C++11 cross-platform game engine - -Copyright (c) 2013, 2014 Jeffrey Carpenter -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -******************************************************************************/ -#ifndef NOMLIB_ANIMATED_SPRITE_HPP -#define NOMLIB_ANIMATED_SPRITE_HPP - -#include -#include - -#include "nomlib/config.hpp" -#include "nomlib/graphics/sprite/SpriteBatch.hpp" -#include "nomlib/system/AnimationTimer.hpp" - -//#define NOM_DEBUG_ANIMATED_SPRITE - -namespace nom { - -class AnimatedSprite: public SpriteBatch -{ - public: - enum AnimationStyle - { - NoStyle = 0, - Blink, - Oscillate - }; - - enum AnimationStatus - { - Stopped = 0, - Playing, - Paused - }; - - /// Default construct for initializing instance variables to their - /// respective defaults. - AnimatedSprite ( void ); - - /// Destructor. - virtual ~AnimatedSprite ( void ); - - /// \brief Use the sprite frames from an existing SpriteSheet object. - /// - /// \param sheet The pre-loaded sprite sheet instance to use the frames - /// from. - /// - /// \see nom::SpriteSheet::load_file. - virtual void set_sprite_sheet(const SpriteSheet& sheet) override; - - /// Get the maximum number of animation frames for this object - int32 total_frames ( void ) const; - - /// Get the frame increment value of this object - int32 frame_inc ( void ) const; - - /// Get the current frame of this object - int32 frame ( void ) const; - - /// Get the current animation style for this object - AnimatedSprite::AnimationStyle style ( void ) const; - - /// Get the current status of this animation - AnimatedSprite::AnimationStatus status ( void ) const; - - /// Set a new frame rate for the animation - /// - /// The frame rate value needs to be in milliseconds. - void setFrameRate ( int32 rate ); - - /// Set the current frame for animation - /// - /// The frame you are setting must be greater than or equal to - /// total_frames(). - void set_frame ( int32 frame ); - - /// Set a new style of animation for this object - void setAnimationStyle ( AnimatedSprite::AnimationStyle style ); - - /// Play the animation - void play ( void ); - - /// Stop the animation playback. - void stop ( void ); - - /// Pause the animation playback. - void pause ( void ); - - /// Un-pause the animation playback. - void unpause ( void ); - - protected: - /// Updates the playback of the animation - void update_animation ( void ); - - /// Initialize values to their respective defaults - void initialize ( void ); - - /// Set the total number of animated frames for this object - void setMaxFrames ( int32 max ); - - /// Set the increment value of each animated frame of this object - void setFrameIncrement ( int32 increment ); - - /// Set a new status state for this object - void setAnimationStatus ( AnimatedSprite::AnimationStatus status ); - - /// Total number of animation frames - int32 max_frames; - - /// Current animation frame - int32 current_frame; - - /// Value that we increment the current_frame by - int32 frame_increment; - - /// Animation playback logic - enum AnimatedSprite::AnimationStyle animation_style; - - /// Status info - enum AnimatedSprite::AnimationStatus animation_status; - - /// Keeps record of our current tick and frame rate (think: frame delay) - AnimationTimer fps; -}; - - -} // namespace nom - -#endif // include guard defined diff --git a/include/nomlib/graphics/sprite/Sprite.hpp b/include/nomlib/graphics/sprite/Sprite.hpp index fd1c8f79..fbe7c2c9 100644 --- a/include/nomlib/graphics/sprite/Sprite.hpp +++ b/include/nomlib/graphics/sprite/Sprite.hpp @@ -26,22 +26,24 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ -#ifndef NOMLIB_SDL2_SPRITE_HPP -#define NOMLIB_SDL2_SPRITE_HPP +#ifndef NOMLIB_GRAPHICS_SPRITE_HPP +#define NOMLIB_GRAPHICS_SPRITE_HPP -#include -#include #include #include "nomlib/config.hpp" #include "nomlib/math/Transformable.hpp" #include "nomlib/math/Rect.hpp" #include "nomlib/math/Point2.hpp" -#include "nomlib/graphics/Texture.hpp" -#include "nomlib/graphics/Renderer.hpp" +#include "nomlib/system/SDL_helpers.hpp" namespace nom { +// Forward declarations +class Texture; +class RenderWindow; +typedef const RenderWindow RenderTarget; + /// \brief Base class for bitmap objects class Sprite: public Transformable { @@ -49,47 +51,93 @@ class Sprite: public Transformable typedef Sprite self_type; typedef Transformable derived_class; - typedef self_type* raw_ptr; - typedef std::unique_ptr unique_ptr; - typedef std::shared_ptr shared_ptr; - - /// Default construct for initializing instance variables to their - /// respective defaults. Sprite(); - /// Destructor. virtual ~Sprite(); - /// \brief Construct a Sprite object, initializing the width & height - /// coordinates. - Sprite(const Size2i& dims); + /// \brief Construct a sprite from a dimension and color. + /// + /// \params color The color to fill the sprite bounds with. + /// \params dims The width and height, in pixels, of the sprite. + /// + /// \returns Boolean TRUE on successful construction, or boolean FALSE when + /// construction has failed, such as when the texture is invalid, or a + /// failure to allocate the necessary memory. + /// + /// \remarks The position of the sprite will be initialized to + /// Point2i::zero. + bool init_with_color(const Color4i& color, const Size2i& dims); - /// \brief Re-implements Transformable::set_position. + /// \brief Re-implements the IObject::type method. /// - /// \remarks This method updates the internal positioning coordinates of - /// this object; freeing us from needing to call ::update manually. + /// \remarks This uniquely identifies the object's type. + ObjectTypeInfo type() const override; + + /// \brief Re-implements Transformable::set_position. virtual void set_position(const Point2i& pos) override; - // TODO (?): - // virtual void set_size( const Size2i& pos ); + /// \brief Re-implements Transformable::set_size. + virtual void set_size(const Size2i& dims) override; /// \brief Implements the required IDrawable::clone method. - IDrawable* clone() const; + std::unique_ptr clone() const; - /// \brief Re-implements the IObject::type method. + std::shared_ptr texture() const; + + bool valid() const; + + /// \brief Get the texture color of the sprite. + Color4i color() const; + + /// \brief Get the color blend mode of the sprite. + BlendMode color_blend_mode() const; + + /// \brief Get the alpha value of the sprite. + uint8 alpha() const; + + /// \brief Construct a sprite from an existing texture source. /// - /// \remarks This uniquely identifies the object's type. - ObjectTypeInfo type() const override; + /// \params tex An existing, valid nom::Texture reference. + /// + /// \returns Boolean TRUE on successful construction, or boolean FALSE when + /// construction has failed, such as when the texture is invalid, or a + /// failure to allocate the necessary memory. + /// + /// \remarks The passed in nom::Texture instance **must** outlive the + /// destruction of this sprite! + bool set_texture(Texture& tex); - SDL_Texture* texture() const; + /// \brief Construct a sprite from an existing texture source. + /// + /// \params tex An existing, valid nom::Texture pointer. + /// + /// \returns Boolean TRUE on successful construction, or boolean FALSE when + /// construction has failed, such as when the texture is invalid, or a + /// failure to allocate the necessary memory. + /// + /// \remarks The ownership of the pointer is transferred to this sprite. + bool set_texture(Texture* tex); - /// \brief Set the texture source for this sprite instance. + /// \brief Construct a sprite from an existing texture source. /// - /// \remarks Only a reference to the texture is kept for this instance; it - /// must outlive the destruction of this instance. + /// \params tex An existing, valid nom::Texture pointer. /// - /// \see nom::Texture::load - void set_texture(/*const*/ Texture& tex); + /// \returns Boolean TRUE on successful construction, or boolean FALSE when + /// construction has failed, such as when the texture is invalid, or a + /// failure to allocate the necessary memory. + bool set_texture(std::shared_ptr& tex); + + bool set_alpha(uint8 opacity); + + bool set_color(const Color4i& color); + + bool set_color_blend_mode(BlendMode blend); + + /// \brief Free the stored texture. + /// + /// \remarks This decrements the shared reference count, potentially + /// freeing the resource. + void release_texture(); virtual void draw(RenderTarget& target) const override; @@ -97,15 +145,63 @@ class Sprite: public Transformable /// /// \param target Reference to an active nom::RenderWindow /// \param angle Rotation angle in degrees - virtual void draw(RenderTarget& target, const double angle) const; + virtual void draw(RenderTarget& target, real64 angle) const; protected: + /// \brief The underlying texture for the sprite. + std::shared_ptr texture_; + + private: virtual void update() override; - /// \brief Object that holds our sprite image - /*const*/ Texture* texture_; + // TODO: Implement these member variables: + + // nom::SpriteBatch can begin using this immediately. Long term, I would + // like to either re-implement some concept of rescaling, or at the very + // minimum, let nom::Sprite always hold a reference to a value and do + // calculations based off it from other APIs or what not. + // Size2f scale_factor_ = 1.0f; + + // Render queues and z-sorting, oh my! Perhaps we could even just upgrade + // nom::Transformable to Point3..? + // real32 z_depth_; }; +/// \brief Construct a sprite from an existing texture source. +/// +/// \relates ::set_texture +std::unique_ptr +make_unique_sprite(Texture& tex); + +/// \brief Construct a sprite from an existing texture source. +/// +/// \relates ::set_texture +std::unique_ptr +make_unique_sprite(Texture* tex); + +/// \brief Construct a sprite from an existing texture source. +/// +/// \relates ::set_texture +std::unique_ptr +make_unique_sprite(std::shared_ptr& tex); + +/// \brief Construct a sprite from an existing texture source. +/// +/// \relates ::set_texture +std::shared_ptr +make_shared_sprite(Texture& tex); + +/// \brief Construct a sprite from an existing texture source. +/// +/// \relates ::set_texture +std::shared_ptr +make_shared_sprite(Texture* tex); + +/// \brief Construct a sprite from an existing texture source. +/// +/// \relates ::set_texture +std::shared_ptr +make_shared_sprite(std::shared_ptr& tex); } // namespace nom diff --git a/include/nomlib/graphics/sprite/SpriteBatch.hpp b/include/nomlib/graphics/sprite/SpriteBatch.hpp index 014587f3..467b3946 100644 --- a/include/nomlib/graphics/sprite/SpriteBatch.hpp +++ b/include/nomlib/graphics/sprite/SpriteBatch.hpp @@ -26,22 +26,18 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ -#ifndef NOMLIB_SDL2_SPRITE_BATCH_HEADERS -#define NOMLIB_SDL2_SPRITE_BATCH_HEADERS - -#include -#include -#include +#ifndef NOMLIB_GRAPHICS_SPRITE_BATCH_HPP +#define NOMLIB_GRAPHICS_SPRITE_BATCH_HPP #include "nomlib/config.hpp" #include "nomlib/math/Rect.hpp" -#include "nomlib/graphics/IDrawable.hpp" -#include "nomlib/graphics/Texture.hpp" #include "nomlib/graphics/sprite/Sprite.hpp" #include "nomlib/graphics/sprite/SpriteSheet.hpp" namespace nom { +// TODO: Consider compositing from nom::Sprite instead of inheriting! + /// \brief Extended sprite rendering using sprite sheets class SpriteBatch: public Sprite { @@ -49,10 +45,6 @@ class SpriteBatch: public Sprite typedef SpriteBatch self_type; typedef Sprite derived_class; - typedef self_type* raw_ptr; - typedef std::unique_ptr unique_ptr; - typedef std::shared_ptr shared_ptr; - /// Default construct for initializing instance variables to their /// respective defaults. /// @@ -76,10 +68,10 @@ class SpriteBatch: public Sprite /// \brief Re-implements the IObject::type method. /// /// \remarks This uniquely identifies the object's type. - ObjectTypeInfo type() const; + ObjectTypeInfo type() const override; /// \brief Implements the required IDrawable::clone method. - IDrawable::raw_ptr clone() const; + SpriteBatch* clone() const; /// Get the object's current sheet_id. virtual int32 frame() const; @@ -114,16 +106,9 @@ class SpriteBatch: public Sprite /// one (-1). /// /// \note Re-implements Sprite::draw. - virtual void draw(IDrawable::RenderTarget& target, const double angle ) const override; + virtual void draw(RenderTarget& target, real64 angle) const override; protected: - /// \brief Update the sprite for rendering with regard to positioning - /// coordinates and target frame ID. - /// - /// \remarks The sprite is not updated when the frame number is negative - /// one (-1). - virtual void update() override; - /// Source (input) coordinates -- used for sprite sheet positioning IntRect offsets; @@ -132,6 +117,14 @@ class SpriteBatch: public Sprite /// The sheet's frame ID presently in use int32 sheet_id_; + + private: + /// \brief Update the sprite for rendering with regard to positioning + /// coordinates and target frame ID. + /// + /// \remarks The sprite is not updated when the frame number is negative + /// one (-1). + virtual void update() override; }; } // namespace nom diff --git a/include/nomlib/graphics/sprite/SpriteSheet.hpp b/include/nomlib/graphics/sprite/SpriteSheet.hpp index c7056955..2b98f7dd 100644 --- a/include/nomlib/graphics/sprite/SpriteSheet.hpp +++ b/include/nomlib/graphics/sprite/SpriteSheet.hpp @@ -48,7 +48,7 @@ class SpriteSheet public: static const VersionInfo VERSION; - typedef std::shared_ptr SharedPtr; + typedef SpriteSheet self_type; /// Default construct for initializing instance variables to their /// respective defaults. @@ -58,7 +58,7 @@ class SpriteSheet ~SpriteSheet(); /// Make a duplicate of this object's instance - SpriteSheet::SharedPtr clone() const; + SpriteSheet* clone() const; /// Get the calculations made for a particular ID number. const IntRect& dimensions(int index) const; @@ -123,6 +123,15 @@ class SpriteSheet /// \param object An existing, de-serialized object to use. bool load_sheet_object(const Value& object); + bool insert_frame(nom::size_type frame_num, const IntRect& frame_bounds); + bool append_frame(const IntRect& frame_bounds); + + /// \brief Erase an existing frame from the sheet. + bool remove_frame(nom::size_type frame); + + /// \brief Destroy all stored sprite frames. + void remove_frames(); + /// Dump the state of this object instance void dump() const; diff --git a/include/nomlib/gui/DecoratorFinalFantasyFrame.hpp b/include/nomlib/gui/DecoratorFinalFantasyFrame.hpp index e9e2eaa6..76711f21 100644 --- a/include/nomlib/gui/DecoratorFinalFantasyFrame.hpp +++ b/include/nomlib/gui/DecoratorFinalFantasyFrame.hpp @@ -67,10 +67,11 @@ class DecoratorFinalFantasyFrame : public Rocket::Core::Decorator private: std::unique_ptr decorator_; - /// \brief Dirty state; comparison of the element's coordinates and our - /// decorator's is necessary for the resizing and moving of the decorator - /// to work. - IntRect coords_; + /// \brief The bounds of our decorator. + /// + /// \note The state of the element's coordinates are stored every frame so + /// that we can synchronize the coordinates to our custom decorator. + FloatRect bounds_; }; } // namespace nom diff --git a/include/nomlib/gui/RocketSDL2RenderInterface.hpp b/include/nomlib/gui/RocketSDL2RenderInterface.hpp index 2b896f78..86325938 100644 --- a/include/nomlib/gui/RocketSDL2RenderInterface.hpp +++ b/include/nomlib/gui/RocketSDL2RenderInterface.hpp @@ -89,7 +89,11 @@ class RocketSDL2RenderInterface: public Rocket::Core::RenderInterface virtual void Release(); /// Called by Rocket when it wants to render geometry that it does not wish to optimise. - virtual void RenderGeometry(Rocket::Core::Vertex* vertices, int num_vertices, int* indices, int num_indices, Rocket::Core::TextureHandle texture, const Rocket::Core::Vector2f& translation); + virtual void + RenderGeometry( Rocket::Core::Vertex* vertices, int num_vertices, + int* indices, int num_indices, + Rocket::Core::TextureHandle texture_handle, + const Rocket::Core::Vector2f& translation); /// Called by Rocket when it wants to enable or disable scissoring to clip content. virtual void EnableScissorRegion(bool enable); @@ -105,8 +109,10 @@ class RocketSDL2RenderInterface: public Rocket::Core::RenderInterface virtual bool LoadTexture(Rocket::Core::TextureHandle& texture_handle, Rocket::Core::Vector2i& texture_dimensions, const Rocket::Core::String& source); /// Called by Rocket when a texture is required to be built from an internally-generated sequence of pixels. virtual bool GenerateTexture(Rocket::Core::TextureHandle& texture_handle, const Rocket::Core::byte* source, const Rocket::Core::Vector2i& source_dimensions); + /// Called by Rocket when a loaded texture is no longer required. - virtual void ReleaseTexture(Rocket::Core::TextureHandle texture_handle); + virtual void + ReleaseTexture(Rocket::Core::TextureHandle texture_handle); /// \brief nomlib interface bridge between SDL2 and libRocket /// diff --git a/include/nomlib/gui/UIContext.hpp b/include/nomlib/gui/UIContext.hpp index 8b24083a..e8e4bc12 100644 --- a/include/nomlib/gui/UIContext.hpp +++ b/include/nomlib/gui/UIContext.hpp @@ -50,6 +50,7 @@ namespace nom { // Forward declarations struct Event; +class EventHandler; class IUIEventHandler; /// \brief libRocket context abstraction @@ -188,11 +189,14 @@ class UIContext /// as set with SDL2 (logical size). void set_size(const Size2i& dims); - /// \brief Event handler for the context's instance. + /// \brief Install the event handler used by the context. /// - /// \remarks This should be called within the main loop, typically once per - /// frame. - void process_event( const Event& event ); + /// \remarks The application's events will not be processed until a call is + /// made to the event handler's ::poll_event method. + /// + /// \note The installed event handler must outlive the destruction of + /// this interface! + void set_event_handler(nom::EventHandler& evt_handler); /// \remarks The boolean return from the context's ::Update method is /// currently ignored. @@ -212,6 +216,9 @@ class UIContext /// independent scale. void initialize_debugger(); + /// \brief Internal event processing loop for the context. + void process_event(const nom::Event& evt); + /// \brief Active state of libRocket's visual debugger. bool debugger_; @@ -225,9 +232,11 @@ class UIContext /// \remarks This is a pointer managed by libRocket and must not be freed. Rocket::Core::RenderInterface* renderer_; - /// \brief The event handler for this context; automatically initialized to - /// nom::UIContextEventHandler. - std::shared_ptr evt_; + /// \brief The internal event handler for the context. + std::unique_ptr evt_; + + // Non-owned pointer + EventHandler* event_handler_ = nullptr; /// \brief The dimensions of the context. Size2i res_; diff --git a/include/nomlib/gui/UIContextEventHandler.hpp b/include/nomlib/gui/UIContextEventHandler.hpp index 6dfc0b81..4e193f19 100644 --- a/include/nomlib/gui/UIContextEventHandler.hpp +++ b/include/nomlib/gui/UIContextEventHandler.hpp @@ -58,11 +58,11 @@ class UIContextEventHandler: public IUIEventHandler /// \brief Injection of libRocket's event loop. /// /// \remarks Implements IUIEventHandler::process_event. - virtual void process_event( const Event& ev ); + virtual void process_event(const nom::Event& ev); protected: virtual Rocket::Core::Input::KeyIdentifier translate_key( const Event& ev ); - virtual int translate_mouse_button( const Event& ev ); + virtual int translate_mouse_button(const Event& ev); /// \returns Positive X, Y axis wheel values are translated from SDL2 to /// libRocket as the 'up' direction in libRocket and negative X, Y axis @@ -72,7 +72,7 @@ class UIContextEventHandler: public IUIEventHandler /// (away from the end-user), and positive wheel values as the 'down' /// direction (towards the end-user). This is indeed the inverse of how /// SDL2 handles the values. - virtual int translate_mouse_wheel( const Event& ev ); + virtual int translate_mouse_wheel(const Event& ev); virtual int translate_key_modifiers( const Event& ev ); diff --git a/include/nomlib/macros.hpp b/include/nomlib/macros.hpp index 75833a53..711a5628 100644 --- a/include/nomlib/macros.hpp +++ b/include/nomlib/macros.hpp @@ -32,7 +32,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include "nomlib/config.hpp" -#include "nomlib/core/clock.hpp" // nom::timestamp // Note that the following are just the general-purpose (engine-wide) macros. // Other major sources of macro definitions: nomlib/config.hpp, nomlib/types.hpp, @@ -50,9 +49,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define NOM_ENDL(reserved) ( std::cout << std::endl ) #define NOM_DASHED_ENDL(reserved) ( std::cout << "---" << std::endl ) -#define NOM_TIMESTAMP(reserved) \ - ( std::cout << nom::timestamp() << std::endl ) - #define NOM_DELETE_PTR(var) \ if( var != nullptr ) delete var; var = nullptr; @@ -62,4 +58,13 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define NOM_DELETE_VOID_PTR(var) \ if( var != nullptr ) free(var); var = nullptr; +// A macro declaration from gtest_prod.h for allowing internal access into +// private parts of a class. (I prefer not to include the framework's header +// files path into our main project namespace). +#define NOM_GTEST_FRIEND(test_case_name, test_name)\ +friend class test_case_name##_##test_name##_Test + +#define NOM_ARRAY_COUNT(arr) \ + ( sizeof(arr) / sizeof((arr)[0]) ) + #endif // include guard defined diff --git a/include/nomlib/math/Color4.hpp b/include/nomlib/math/Color4.hpp index a57db6e5..453cd12a 100644 --- a/include/nomlib/math/Color4.hpp +++ b/include/nomlib/math/Color4.hpp @@ -53,7 +53,6 @@ const std::string COLOR_DELIMITER = ", "; /// \brief RGBA color container /// -/// \todo Implement lesser than, greater than and so on operators? /// \todo Implement specialized Color4 operators /// \todo Explicitly initialize alpha component of static colors to 255. template @@ -124,31 +123,6 @@ struct Color4 //NOM_LOG_TRACE(NOM); } - // \brief Method overload of binary operator / (Division) - // - // \param rhs Left operand. - // \param rhs Right operand. - // - // \remarks Division of both objects; result is assigned to the left operand. - // - // \returns Reference to the left operand. - // - // \note Borrowed from Ogre::ColourValue. - // inline Color4 operator / ( const float factor ) const - // { - // NOM_ASSERT( factor != 0 ); - - // Color4 div; - // T inv = 1.0f / factor; - - // div.r = ( r / 255 ) * inv; - // div.g = ( g / 255 ) * inv; - // div.b = ( b / 255 ) * inv; - // div.a = ( a / 255 ) * inv; - - // return div; - // } - /// \brief 100% transparent alpha channel value static const T ALPHA_TRANSPARENT; @@ -160,7 +134,11 @@ struct Color4 /// \remarks Null value implementation depends on signed (negative) numbers. static const Color4 null; - /// Primary colors + /// \brief Primary colors + /// + /// \todo Consider using Color4::Transparent instead of Color4::null. + /// \todo Rename constants to ALL UPPERCASE. + static const Color4 Transparent; static const Color4 Black; static const Color4 White; static const Color4 Red; @@ -365,6 +343,96 @@ inline Color4& operator *= ( Color4& left, const Color4& right ) return left = left * right; } +/// \brief Lesser than comparison operator. +/// +/// \param lhs Left operand. +/// \param rhs Right operand. +template +inline bool operator <(const Color4 lhs, const Color4& rhs) +{ + return (lhs.r < rhs.r) && (lhs.r < rhs.r) && + (lhs.g < rhs.g) && (lhs.g < rhs.g) && + (lhs.b < rhs.b) && (lhs.b < rhs.b) && + (lhs.a < rhs.a) && (lhs.a < rhs.a); +} + +/// \brief Greater than or equal to comparison operator. +/// +/// \param lhs Left operand. +/// \param rhs Right operand. +template +inline bool operator >(const Color4& lhs, const Color4& rhs) +{ + return (rhs.r < lhs.r) && (rhs.r < lhs.r) && + (rhs.g < lhs.g) && (rhs.g < lhs.g) && + (rhs.b < lhs.b) && (rhs.b < lhs.b) && + (rhs.a < lhs.a) && (rhs.a < lhs.a); +} + +/// \brief Lesser than or equal to comparison operator. +/// +/// \param lhs Left operand. +/// \param rhs Right operand. +template +inline bool operator <=(const Color4& lhs, const Color4& rhs) +{ + return (lhs.r <= rhs.r) && (lhs.r <= rhs.r) && + (lhs.g <= rhs.g) && (lhs.g <= rhs.g) && + (lhs.b <= rhs.b) && (lhs.b <= rhs.b) && + (lhs.a <= rhs.a) && (lhs.a <= rhs.a); +} + +/// \brief Greater than or equal to comparison operator. +/// +/// \param lhs Left operand. +/// \param rhs Right operand. +template +inline bool operator >=(const Color4& lhs, const Color4& rhs) +{ + return (rhs.r <= lhs.r) && (rhs.r <= lhs.r) && + (rhs.g <= lhs.g) && (rhs.g <= lhs.g) && + (rhs.b <= lhs.b) && (rhs.b <= lhs.b) && + (rhs.a <= lhs.a) && (rhs.a <= lhs.a); +} + +/// \brief Method overload of binary operator / (Division) +/// +/// \param rhs Left operand. +/// \param rhs Right operand. +/// +/// \remarks Division of both objects; result is assigned to the left operand. +/// +/// \returns Reference to the left operand. +template +inline Color4 operator /(const Color4& lhs, const Color4& rhs) +{ + return Color4( lhs.r / rhs.r, + lhs.g / rhs.g, + lhs.b / rhs.b, + lhs.a / rhs.a + ); +} + +/// \brief Method overload of binary operator /= (Division) +/// +/// \param lhs Left operand. +/// \param rhs Right operand. +/// +/// \remarks Division of both objects; result is assigned to the +/// left operand. +/// +/// \returns Reference to left operand. +template +inline Color4& operator /=(Color4& lhs, Color4& rhs) +{ + lhs.r /= rhs.r; + lhs.g /= rhs.g; + lhs.b /= rhs.b; + lhs.a /= rhs.a; + + return lhs; +} + /// Color4 object defined using signed 16-bit integers typedef Color4 Color4i; @@ -387,6 +455,12 @@ typedef std::vector Color4fColors; /// \brief A container of Color4u objects. typedef std::vector Color4uColors; +Color4i +make_color_from_hex_string(const std::string& hex_encoding); + +Color4i +make_color_from_string(const std::string& color); + } // namespace nom #endif // include guard defined diff --git a/include/nomlib/math/Point2.hpp b/include/nomlib/math/Point2.hpp index 5a41fcdc..41f35b2d 100644 --- a/include/nomlib/math/Point2.hpp +++ b/include/nomlib/math/Point2.hpp @@ -198,6 +198,30 @@ inline Point2 operator + ( const Point2& lhs, const Point2& rhs ) ); } +/// \brief Method overload of binary operator + (Addition) +/// +/// \param lhs Left operand. +/// \param rhs Right operand. +/// +/// \returns Addition of both objects. +template +inline Point2 operator +(int lhs, const Point2& rhs) +{ + return Point2(lhs + rhs.x, lhs + rhs.y); +} + +/// \brief Method overload of binary operator + (Addition) +/// +/// \param lhs Left operand. +/// \param rhs Right operand. +/// +/// \returns Addition of both objects. +template +inline Point2 operator +(const Point2& lhs, int rhs) +{ + return Point2(lhs.x + rhs, lhs.y + rhs); +} + /// \brief Method overload of binary operator ++ (Addition by 1) /// /// \param rhs Right operand. @@ -238,6 +262,30 @@ inline Point2 operator - ( const Point2& lhs, const Point2& rhs ) ); } +/// \brief Method overload of binary operator - (subtraction) +/// +/// \param lhs Left operand. +/// \param rhs Right operand. +/// +/// \returns Subtraction of both objects. +template +inline Point2 operator -(int lhs, const Point2& rhs) +{ + return Point2(lhs - rhs.x, lhs - rhs.y); +} + +/// \brief Method overload of binary operator - (subtraction) +/// +/// \param lhs Left operand. +/// \param rhs Right operand. +/// +/// \returns Subtraction of both objects. +template +inline Point2 operator -(const Point2& lhs, int rhs) +{ + return Point2(lhs.x - rhs, lhs.y - rhs); +} + /// \brief Method overload of binary operator -- (subtraction by 1) /// /// \param rhs Right operand. @@ -265,6 +313,30 @@ inline Point2 operator * ( const Point2& lhs, const Point2& rhs ) ); } +/// \brief Method overload of binary operator * (Multiplication) +/// +/// \param rhs Left operand. +/// \param rhs Right operand. +/// +/// \returns Multiplication of the right operand. +template +inline Point2 operator *(int lhs, const Point2& rhs) +{ + return Point2(lhs * rhs.x, lhs * rhs.y); +} + +/// \brief Method overload of binary operator * (Multiplication) +/// +/// \param rhs Left operand. +/// \param rhs Right operand. +/// +/// \returns Multiplication of the right operand. +template +inline Point2 operator *(const Point2& lhs, int rhs) +{ + return Point2(lhs.x * rhs, lhs.y * rhs); +} + /// \brief Method overload of binary operator / (Division) /// /// \param rhs Left operand. @@ -281,6 +353,34 @@ inline Point2 operator / ( const Point2& lhs, const Point2& rhs ) ); } +/// \brief Method overload of binary operator / (Division) +/// +/// \param rhs Left operand. +/// \param rhs Right operand. +/// +/// \remarks Division of both objects; result is assigned to the left operand. +/// +/// \returns Reference to the left operand. +template +inline Point2 operator /(int lhs, const Point2& rhs) +{ + return Point2(lhs / rhs.x, lhs / rhs.y); +} + +/// \brief Method overload of binary operator / (Division) +/// +/// \param rhs Left operand. +/// \param rhs Right operand. +/// +/// \remarks Division of both objects; result is assigned to the left operand. +/// +/// \returns Reference to the left operand. +template +inline Point2 operator /(const Point2& lhs, int rhs) +{ + return Point2(lhs.x / rhs, lhs.y / rhs); +} + /// \brief Method overload of binary operator += (Addition) /// /// \param lhs Left operand. @@ -299,6 +399,42 @@ inline Point2& operator += ( Point2& lhs, const Point2& rhs ) return lhs; } +/// \brief Method overload of binary operator += (Addition) +/// +/// \param lhs Left operand. +/// \param rhs Right operand. +/// +/// \remarks Addition of both objects; result is assigned to the left +/// operand. +/// +/// \returns Reference to left operand, +template +inline Point2& operator +=(int lhs, const Point2& rhs) +{ + lhs += rhs.x; + lhs += rhs.y; + + return lhs; +} + +/// \brief Method overload of binary operator += (Addition) +/// +/// \param lhs Left operand. +/// \param rhs Right operand. +/// +/// \remarks Addition of both objects; result is assigned to the left +/// operand. +/// +/// \returns Reference to left operand, +template +inline Point2& operator +=(Point2& lhs, int rhs) +{ + lhs.x += rhs; + lhs.y += rhs; + + return lhs; +} + /// \brief Method overload of binary operator -= (Subtraction) /// /// \param lhs Left operand. @@ -317,6 +453,42 @@ inline Point2& operator -= ( Point2& lhs, const Point2& rhs ) return lhs; } +/// \brief Method overload of binary operator -= (Subtraction) +/// +/// \param lhs Left operand. +/// \param rhs Right operand. +/// +/// \remarks Subtraction of both objects; result is assigned to the left +/// operand. +/// +/// \returns Reference to left operand. +template +inline Point2& operator -=(int lhs, const Point2& rhs) +{ + lhs -= rhs.x; + lhs -= rhs.y; + + return lhs; +} + +/// \brief Method overload of binary operator -= (Subtraction) +/// +/// \param lhs Left operand. +/// \param rhs Right operand. +/// +/// \remarks Subtraction of both objects; result is assigned to the left +/// operand. +/// +/// \returns Reference to left operand. +template +inline Point2& operator -=(Point2& lhs, int rhs) +{ + lhs.x -= rhs; + lhs.y -= rhs; + + return lhs; +} + /// \brief Method overload of binary operator *= (Multiplication) /// /// \param lhs Left operand. @@ -326,8 +498,6 @@ inline Point2& operator -= ( Point2& lhs, const Point2& rhs ) /// left operand. /// /// \returns Reference to left operand. -/// -/// \todo See tests/math.cpp at Point2 Unit Test 2, Result[1] template inline Point2& operator *= ( Point2& lhs, const Point2& rhs ) { @@ -337,6 +507,42 @@ inline Point2& operator *= ( Point2& lhs, const Point2& rhs ) return lhs; } +/// \brief Method overload of binary operator *= (Multiplication) +/// +/// \param lhs Left operand. +/// \param rhs Right operand. +/// +/// \remarks Multiplication of both objects; result is assigned to the +/// left operand. +/// +/// \returns Reference to left operand. +template +inline Point2& operator *=(int lhs, const Point2& rhs) +{ + lhs *= rhs.x; + lhs *= rhs.y; + + return lhs; +} + +/// \brief Method overload of binary operator *= (Multiplication) +/// +/// \param lhs Left operand. +/// \param rhs Right operand. +/// +/// \remarks Multiplication of both objects; result is assigned to the +/// left operand. +/// +/// \returns Reference to left operand. +template +inline Point2& operator *=(Point2& lhs, int rhs) +{ + lhs.x *= rhs; + lhs.y *= rhs; + + return lhs; +} + /// \brief Method overload of binary operator /= (Division) /// /// \param lhs Left operand. @@ -355,6 +561,42 @@ inline Point2& operator /= ( Point2& lhs, Point2& rhs ) return lhs; } +/// \brief Method overload of binary operator /= (Division) +/// +/// \param lhs Left operand. +/// \param rhs Right operand. +/// +/// \remarks Division of both objects; result is assigned to the +/// left operand. +/// +/// \returns Reference to left operand. +template +inline Point2& operator /=(int lhs, Point2& rhs) +{ + lhs /= rhs.x; + lhs /= rhs.y; + + return lhs; +} + +/// \brief Method overload of binary operator /= (Division) +/// +/// \param lhs Left operand. +/// \param rhs Right operand. +/// +/// \remarks Division of both objects; result is assigned to the +/// left operand. +/// +/// \returns Reference to left operand. +template +inline Point2& operator /=(Point2& lhs, int rhs) +{ + lhs.x /= rhs; + lhs.y /= rhs; + + return lhs; +} + /// Point2 object defined using signed integers typedef Point2 Point2i; diff --git a/include/nomlib/math/Point3.hpp b/include/nomlib/math/Point3.hpp index 4b189160..cdbf1070 100644 --- a/include/nomlib/math/Point3.hpp +++ b/include/nomlib/math/Point3.hpp @@ -41,25 +41,25 @@ template struct Point3 { /// Default constructor; initialize values to Point3::null - Point3 ( void ) : - x ( -1 ), - y ( -1 ), - z ( -1 ) + Point3() : + x(-1), + y(-1), + z(-1) { //NOM_LOG_TRACE(NOM); } /// Destructor - ~Point3 ( void ) + ~Point3() { //NOM_LOG_TRACE(NOM); } /// Constructor variant for initializing x, y, z at construction - Point3 ( T x, T y, T z ) : - x ( x ), - y ( y ), - z ( z ) + Point3(T x, T y, T z) : + x(x), + y(y), + z(z) { //NOM_LOG_TRACE(NOM); } @@ -70,16 +70,16 @@ struct Point3 /// in any instance that it finds incompatible casting occurring, such as if /// you try to down-cast a Point3 to a Point3. template - explicit Point3 ( const Point3& copy ) : - x { static_cast ( copy.x ) }, - y { static_cast ( copy.y ) }, - z { static_cast ( copy.z ) } + explicit Point3(const Point3& copy) : + x( NOM_SCAST(T, copy.x) ), + y( NOM_SCAST(T, copy.y) ), + z( NOM_SCAST(T, copy.z) ) { //NOM_LOG_TRACE(NOM); } /// \brief Obtain a reference of the object. - inline const Point3& get ( void ) const + inline const Point3& get() const { return *this; } @@ -89,6 +89,9 @@ struct Point3 /// \remarks Null value implementation depends on signed (negative) numbers. static const Point3 null; + /// \brief Zero value constant. + static const Point3 zero; + T x; T y; T z; @@ -130,10 +133,10 @@ inline bool operator != ( const Point3& lhs, const Point3& rhs ) typedef Point3 Point3i; /// Point3 object defined using floating-point numbers -typedef Point3 Point3f; +typedef Point3 Point3f; /// Point3 object defined using double precision floating-point numbers -typedef Point3 Point3d; +typedef Point3 Point3d; } // namespace nom diff --git a/include/nomlib/math/Rect.hpp b/include/nomlib/math/Rect.hpp index c7039424..1f63be8c 100644 --- a/include/nomlib/math/Rect.hpp +++ b/include/nomlib/math/Rect.hpp @@ -170,24 +170,38 @@ struct Rect return Size2i( NOM_SCAST( T, this->w ), NOM_SCAST( T, this->h ) ); } - const T& left( void ) const + const T& left() const { return this->x; } - const T& top( void ) const + const T& top() const { return this->y; } - const T& right( void ) const + const T& right() const { - return this->w; + return(this->x + this->w); } - const T& bottom( void ) const + const T& bottom() const { - return this->h; + return(this->y + this->h); + } + + template + void set_position(const Point2& pos) + { + this->x = pos.x; + this->y = pos.y; + } + + template + void set_size(const Size2& dims) + { + this->w = dims.w; + this->h = dims.h; } /// \brief Null value @@ -286,6 +300,30 @@ inline Rect operator + ( const Rect& lhs, const Rect& rhs ) ); } +/// \brief Method overload of binary operator + (Addition) +/// +/// \param lhs Left operand. +/// \param rhs Right operand. +/// +/// \returns Addition of both objects. +template +inline Rect operator +(int lhs, const Rect& rhs) +{ + return Rect(lhs + rhs.x, lhs + rhs.y, lhs + rhs.w, lhs + rhs.h); +} + +/// \brief Method overload of binary operator + (Addition) +/// +/// \param lhs Left operand. +/// \param rhs Right operand. +/// +/// \returns Addition of both objects. +template +inline Rect operator +(const Rect& lhs, int rhs) +{ + return Rect(lhs.x + rhs, lhs.y + rhs, lhs.w + rhs, lhs.h + rhs); +} + /// \brief Method overload of binary operator ++ (Addition by 1) /// /// \param rhs Right operand. @@ -332,6 +370,30 @@ inline Rect operator -( const Rect& lhs, const Rect& rhs ) ); } +/// \brief Method overload of binary operator - (subtraction) +/// +/// \param lhs Left operand. +/// \param rhs Right operand. +/// +/// \returns Subtraction of both objects. +template +inline Rect operator -(int lhs, const Rect& rhs) +{ + return Rect(lhs - rhs.x, lhs - rhs.y, lhs - rhs.w, lhs - rhs.h); +} + +/// \brief Method overload of binary operator - (subtraction) +/// +/// \param lhs Left operand. +/// \param rhs Right operand. +/// +/// \returns Subtraction of both objects. +template +inline Rect operator -(const Rect& lhs, int rhs) +{ + return Rect(lhs.x - rhs, lhs.y - rhs, lhs.w - rhs, lhs.h - rhs); +} + /// \brief Method overload of binary operator -- (subtraction by 1) /// /// \param rhs Right operand. @@ -363,6 +425,34 @@ inline Rect operator * ( const Rect& lhs, const Rect& rhs ) ); } +/// \brief Method overload of binary operator * (Multiplication) +/// +/// \param rhs Left operand. +/// \param rhs Right operand. +/// +/// \returns Multiplication of the right operand. +template +inline Rect operator *(int lhs, const Rect& rhs) +{ + return Rect(lhs * rhs.x, lhs * rhs.y, lhs * rhs.w, lhs * rhs.h); +} + +/// \brief Method overload of binary operator * (Multiplication) +/// +/// \param rhs Left operand. +/// \param rhs Right operand. +/// +/// \returns Multiplication of the right operand. +template +inline Rect operator *(const Rect& lhs, int rhs) +{ + return Rect ( lhs.x * rhs, + lhs.y * rhs, + lhs.w * rhs, + lhs.h * rhs + ); +} + /// \brief Method overload of binary operator / (Division) /// /// \param rhs Left operand. @@ -381,6 +471,42 @@ inline Rect operator /( const Rect& lhs, const Rect& rhs ) ); } +/// \brief Method overload of binary operator / (Division) +/// +/// \param rhs Left operand. +/// \param rhs Right operand. +/// +/// \remarks Division of both objects; result is assigned to the left operand. +/// +/// \returns Reference to the left operand. +template +inline Rect operator /(int lhs, const Rect& rhs) +{ + return Rect ( lhs / rhs.x, + lhs / rhs.y, + lhs / rhs.w, + lhs / rhs.h + ); +} + +/// \brief Method overload of binary operator / (Division) +/// +/// \param rhs Left operand. +/// \param rhs Right operand. +/// +/// \remarks Division of both objects; result is assigned to the left operand. +/// +/// \returns Reference to the left operand. +template +inline Rect operator /(const Rect& lhs, int rhs) +{ + return Rect ( lhs.x / rhs, + lhs.y / rhs, + lhs.w / rhs, + lhs.h / rhs + ); +} + /// \brief Method overload of binary operator += (Addition) /// /// \param lhs Left operand. @@ -401,6 +527,46 @@ inline Rect& operator +=( Rect& lhs, const Rect& rhs ) return lhs; } +/// \brief Method overload of binary operator += (Addition) +/// +/// \param lhs Left operand. +/// \param rhs Right operand. +/// +/// \remarks Addition of both objects; result is assigned to the left +/// operand. +/// +/// \returns Reference to left operand, +template +inline Rect& operator +=(int lhs, const Rect& rhs) +{ + lhs += rhs.x; + lhs += rhs.y; + lhs += rhs.w; + lhs += rhs.h; + + return lhs; +} + +/// \brief Method overload of binary operator += (Addition) +/// +/// \param lhs Left operand. +/// \param rhs Right operand. +/// +/// \remarks Addition of both objects; result is assigned to the left +/// operand. +/// +/// \returns Reference to left operand, +template +inline Rect& operator +=(Rect& lhs, int rhs) +{ + lhs.x += rhs; + lhs.y += rhs; + lhs.w += rhs; + lhs.h += rhs; + + return lhs; +} + /// \brief Method overload of binary operator -= (Subtraction) /// /// \param lhs Left operand. @@ -421,6 +587,46 @@ inline Rect& operator -=( Rect& lhs, const Rect& rhs ) return lhs; } +/// \brief Method overload of binary operator -= (Subtraction) +/// +/// \param lhs Left operand. +/// \param rhs Right operand. +/// +/// \remarks Subtraction of both objects; result is assigned to the left +/// operand. +/// +/// \returns Reference to left operand. +template +inline Rect& operator -=(int lhs, const Rect& rhs) +{ + lhs -= rhs.x; + lhs -= rhs.y; + lhs -= rhs.w; + lhs -= rhs.h; + + return lhs; +} + +/// \brief Method overload of binary operator -= (Subtraction) +/// +/// \param lhs Left operand. +/// \param rhs Right operand. +/// +/// \remarks Subtraction of both objects; result is assigned to the left +/// operand. +/// +/// \returns Reference to left operand. +template +inline Rect& operator -=(Rect& lhs, int rhs) +{ + lhs.x -= rhs; + lhs.y -= rhs; + lhs.w -= rhs; + lhs.h -= rhs; + + return lhs; +} + /// \brief Method overload of binary operator *= (Multiplication) /// /// \param lhs Left operand. @@ -430,8 +636,6 @@ inline Rect& operator -=( Rect& lhs, const Rect& rhs ) /// left operand. /// /// \returns Reference to left operand. -/// -/// \todo See tests/math.cpp at Point2 Unit Test 2, Result[1] template inline Rect& operator *=( Rect& lhs, const Rect& rhs ) { @@ -443,6 +647,46 @@ inline Rect& operator *=( Rect& lhs, const Rect& rhs ) return lhs; } +/// \brief Method overload of binary operator *= (Multiplication) +/// +/// \param lhs Left operand. +/// \param rhs Right operand. +/// +/// \remarks Multiplication of both objects; result is assigned to the +/// left operand. +/// +/// \returns Reference to left operand. +template +inline Rect& operator *=(int lhs, const Rect& rhs) +{ + lhs *= rhs.x; + lhs *= rhs.y; + lhs *= rhs.w; + lhs *= rhs.h; + + return lhs; +} + +/// \brief Method overload of binary operator *= (Multiplication) +/// +/// \param lhs Left operand. +/// \param rhs Right operand. +/// +/// \remarks Multiplication of both objects; result is assigned to the +/// left operand. +/// +/// \returns Reference to left operand. +template +inline Rect& operator *=(Rect& lhs, int rhs) +{ + lhs.x *= rhs; + lhs.y *= rhs; + lhs.w *= rhs; + lhs.h *= rhs; + + return lhs; +} + /// \brief Method overload of binary operator /= (Division) /// /// \param lhs Left operand. @@ -463,10 +707,50 @@ inline Rect& operator /=( Rect& lhs, const Rect& rhs ) return lhs; } +/// \brief Method overload of binary operator /= (Division) +/// +/// \param lhs Left operand. +/// \param rhs Right operand. +/// +/// \remarks Division of both objects; result is assigned to the +/// left operand. +/// +/// \returns Reference to left operand. +template +inline Rect& operator /=(int lhs, const Rect& rhs) +{ + lhs /= rhs.x; + lhs /= rhs.y; + lhs /= rhs.w; + lhs /= rhs.h; + + return lhs; +} + +/// \brief Method overload of binary operator /= (Division) +/// +/// \param lhs Left operand. +/// \param rhs Right operand. +/// +/// \remarks Division of both objects; result is assigned to the +/// left operand. +/// +/// \returns Reference to left operand. +template +inline Rect& operator /=(Rect& lhs, int rhs) +{ + lhs.x /= rhs; + lhs.y /= rhs; + lhs.w /= rhs; + lhs.h /= rhs; + + return lhs; +} + /// \brief Lesser than comparison operator. /// /// \param lhs Left operand. -// / \param rhs Right operand. +/// \param rhs Right operand. template inline bool operator <( const Rect lhs, const Rect& rhs ) { diff --git a/include/nomlib/math/Size2.hpp b/include/nomlib/math/Size2.hpp index af34a283..cc5cf724 100644 --- a/include/nomlib/math/Size2.hpp +++ b/include/nomlib/math/Size2.hpp @@ -173,6 +173,30 @@ inline Size2 operator +( const Size2& lhs, const Size2& rhs ) ); } +/// \brief Method overload of binary operator + (Addition) +/// +/// \param lhs Left operand. +/// \param rhs Right operand. +/// +/// \returns Addition of both objects. +template +inline Size2 operator +(int lhs, const Size2& rhs) +{ + return Size2(lhs + rhs.w, lhs + rhs.h); +} + +/// \brief Method overload of binary operator + (Addition) +/// +/// \param lhs Left operand. +/// \param rhs Right operand. +/// +/// \returns Addition of both objects. +template +inline Size2 operator +(const Size2& lhs, int rhs) +{ + return Size2(lhs.w + rhs, lhs.h + rhs); +} + /// \brief Method overload of binary operator ++ (Addition by 1) /// /// \param rhs Right operand. @@ -216,6 +240,30 @@ inline Size2 operator -( const Size2& lhs, const Size2& rhs ) ); } +/// \brief Method overload of binary operator - (subtraction) +/// +/// \param lhs Left operand. +/// \param rhs Right operand. +/// +/// \returns Subtraction of both objects. +template +inline Size2 operator -(int lhs, const Size2& rhs) +{ + return Size2(lhs - rhs.w, lhs - rhs.h); +} + +/// \brief Method overload of binary operator - (subtraction) +/// +/// \param lhs Left operand. +/// \param rhs Right operand. +/// +/// \returns Subtraction of both objects. +template +inline Size2 operator -(const Size2& lhs, int rhs) +{ + return Size2(lhs.w - rhs, lhs.h - rhs); +} + /// \brief Method overload of binary operator -- (subtraction by 1) /// /// \param rhs Right operand. @@ -244,6 +292,30 @@ inline Size2 operator *( const Size2& lhs, const Size2& rhs ) ); } +/// \brief Method overload of binary operator * (Multiplication) +/// +/// \param rhs Left operand. +/// \param rhs Right operand. +/// +/// \returns Multiplication of the right operand. +template +inline Size2 operator *(int lhs, const Size2& rhs) +{ + return Size2(lhs * rhs.w, lhs * rhs.h); +} + +/// \brief Method overload of binary operator * (Multiplication) +/// +/// \param rhs Left operand. +/// \param rhs Right operand. +/// +/// \returns Multiplication of the right operand. +template +inline Size2 operator *(const Size2& lhs, int rhs) +{ + return Size2(lhs.w * rhs, lhs.h * rhs); +} + /// \brief Method overload of binary operator += (Addition) /// /// \param lhs Left operand. @@ -262,6 +334,42 @@ inline Size2& operator +=( Size2& lhs, const Size2& rhs ) return lhs; } +/// \brief Method overload of binary operator += (Addition) +/// +/// \param lhs Left operand. +/// \param rhs Right operand. +/// +/// \remarks Addition of both objects; result is assigned to the left +/// operand. +/// +/// \returns Reference to left operand, +template +inline Size2& operator +=(int lhs, const Size2& rhs) +{ + lhs += rhs.w; + lhs += rhs.h; + + return lhs; +} + +/// \brief Method overload of binary operator += (Addition) +/// +/// \param lhs Left operand. +/// \param rhs Right operand. +/// +/// \remarks Addition of both objects; result is assigned to the left +/// operand. +/// +/// \returns Reference to left operand, +template +inline Size2& operator +=(Size2& lhs, int rhs) +{ + lhs.w += rhs; + lhs.h += rhs; + + return lhs; +} + /// \brief Method overload of binary operator -= (Subtraction) /// /// \param lhs Left operand. @@ -280,6 +388,42 @@ inline Size2& operator -=( Size2& lhs, const Size2& rhs ) return lhs; } +/// \brief Method overload of binary operator -= (Subtraction) +/// +/// \param lhs Left operand. +/// \param rhs Right operand. +/// +/// \remarks Subtraction of both objects; result is assigned to the left +/// operand. +/// +/// \returns Reference to left operand. +template +inline Size2& operator -=(int lhs, const Size2& rhs) +{ + lhs -= rhs.w; + lhs -= rhs.h; + + return lhs; +} + +/// \brief Method overload of binary operator -= (Subtraction) +/// +/// \param lhs Left operand. +/// \param rhs Right operand. +/// +/// \remarks Subtraction of both objects; result is assigned to the left +/// operand. +/// +/// \returns Reference to left operand. +template +inline Size2& operator -=(Size2& lhs, int rhs) +{ + lhs.w -= rhs; + lhs.h -= rhs; + + return lhs; +} + /// \brief Method overload of binary operator *= (Multiplication) /// /// \param lhs Left operand. @@ -316,6 +460,42 @@ inline Size2& operator /=( Size2& lhs, const Size2& rhs ) return lhs; } +/// \brief Method overload of binary operator /= (Division assignment) +/// +/// \param lhs Left operand. +/// \param rhs Right operand. +/// +/// \remarks Division of both objects; result is assigned to the +/// left operand. +/// +/// \returns Reference to left operand. +template +inline Size2& operator /=(int lhs, const Size2& rhs) +{ + lhs /= rhs.w; + lhs /= rhs.h; + + return lhs; +} + +/// \brief Method overload of binary operator /= (Division assignment) +/// +/// \param lhs Left operand. +/// \param rhs Right operand. +/// +/// \remarks Division of both objects; result is assigned to the +/// left operand. +/// +/// \returns Reference to left operand. +template +inline Size2& operator /=(Size2& lhs, int rhs) +{ + lhs.w /= rhs; + lhs.h /= rhs; + + return lhs; +} + /// \brief Method overload of binary operator / (Division) /// /// \param lhs Left operand. @@ -332,6 +512,38 @@ inline Size2 operator /( const Size2& lhs, const Size2& rhs ) return ret; } +/// \brief Method overload of binary operator / (Division) +/// +/// \param lhs Left operand. +/// \param rhs Right operand. +/// +/// \returns Size2 template type returned by value +template +inline Size2 operator /(int lhs, const Size2& rhs) +{ + Size2 ret; + ret.w = lhs / rhs.w; + ret.h = lhs / rhs.h; + + return ret; +} + +/// \brief Method overload of binary operator / (Division) +/// +/// \param lhs Left operand. +/// \param rhs Right operand. +/// +/// \returns Size2 template type returned by value +template +inline Size2 operator /(const Size2& lhs, int rhs) +{ + Size2 ret; + ret.w = lhs.w / rhs; + ret.h = lhs.h / rhs; + + return ret; +} + /// \brief Lesser than comparison operator. /// /// \param lhs Left operand. diff --git a/include/nomlib/math/Transformable.hpp b/include/nomlib/math/Transformable.hpp index b0b58e1b..bb535e90 100644 --- a/include/nomlib/math/Transformable.hpp +++ b/include/nomlib/math/Transformable.hpp @@ -35,9 +35,13 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. namespace nom { +// TODO: Consider upgrading containers to Point3f ..? + class Transformable: public virtual IDrawable { public: + typedef Transformable self_type; + Transformable( void ); virtual ~Transformable( void ); @@ -50,7 +54,7 @@ class Transformable: public virtual IDrawable /// \brief Re-implements the IObject::type method. /// /// \remarks This uniquely identifies the object's type. - ObjectTypeInfo type( void ) const; + ObjectTypeInfo type() const override; /// \brief Getter for stored positioning coordinates. const Point2i& position( void ) const; diff --git a/include/nomlib/math/math_helpers.hpp b/include/nomlib/math/math_helpers.hpp index 41e00d37..f6acec64 100644 --- a/include/nomlib/math/math_helpers.hpp +++ b/include/nomlib/math/math_helpers.hpp @@ -66,7 +66,7 @@ void init_rand(uint32 seed_seq); /// /// \remarks This function should only be used with signed or unsigned integers. template -T uniform_int_rand(T start_range, T end_range) +inline T uniform_int_rand(T start_range, T end_range) { std::uniform_int_distribution distribution(start_range, end_range); @@ -79,7 +79,7 @@ T uniform_int_rand(T start_range, T end_range) /// /// \remarks This function should only be used with float or double numbers. template -T uniform_real_rand(T start_range, T end_range) +inline T uniform_real_rand(T start_range, T end_range) { std::uniform_real_distribution distribution(start_range, end_range); @@ -90,17 +90,125 @@ T uniform_real_rand(T start_range, T end_range) /// (rotation point) at the given angle (in degrees), clockwise. const Point2d rotate_points ( float angle, float x, float y, float pivot_x, float pivot_y ); -/// Round a fractional value +/// \brief Round a fractional value. /// -/// \param number Number to round up or down +/// \param number The 32-bit floating-point number to round. /// -/// \return Rounded value +/// \remarks The number is round up when it is greater than 0.5 and rounded +/// down when the number is less than 0.5 /// -/// \note Round up when number > 0.5; round down when number < 0.5 +/// \TODO Rename to round_real32 template -T round ( T number ) +inline T round_float(real32 number) { - return number < 0.0 ? ceil ( number - 0.5 ) : floor ( number + 0.5 ); + real32 ret = number < 0.0f ? ceilf(number - 0.5f) : floorf(number + 0.5f); + return NOM_SCAST(T, ret); +} + +/// \brief Round a fractional value down to the nearest integral number. +/// +/// \param number The 32-bit floating-point number to round. +/// +/// \TODO Rename to round_real32_down +template +inline T round_float_down(real32 number) +{ + real32 ret = floorf(number); + return NOM_SCAST(T, ret); +} + +/// \brief Round a fractional value up to the largest integral number. +/// +/// \param number The 32-bit floating-point number to round. +/// +/// \TODO Rename to round_real32_up +template +inline T round_float_up(real32 number) +{ + real32 ret = ceilf(number); + return NOM_SCAST(T, ret); +} + +/// \brief Round a fractional value. +/// +/// \param number The 64-bit floating-point number to round. +/// +/// \remarks The number is round up when it is greater than 0.5 and rounded +/// down when the number is less than 0.5. +/// +/// \TODO Rename to round_double64 +template +inline T round_double(real64 number) +{ + real64 ret = number < 0.0f ? ceil(number - 0.5f) : floor(number + 0.5f); + return NOM_SCAST(T, ret); +} + +// TODO(jeff): Rename to truncate_real32 +template +inline T truncate_float(real32 number) +{ + T ret = NOM_SCAST(T, number); + return(ret); +} + +// FIXME(jeff): We should provide two separate functions here, absolute_real32 +// and absolute_real64. See also: man 3 fabs +inline real32 absolute_real32(real32 number) +{ + real32 result = fabsf(number); + + return result; +} + +inline real64 absolute_real64(real64 number) +{ + real64 result = fabs(number); + + return result; +} + +inline int absolute_int(int number) +{ + int result = std::abs(number); + + return result; +} + +/// \brief Compare two numbers for the smaller of the two values. +/// +/// \returns The smaller of the two given values. +template +inline T minimum(const T& a, const T& b) +{ + T result = (b < a) ? b : a; + return result; +} + +/// \brief Compare two numbers for the larger of the two values. +/// +/// \returns The larger of the two given values. +template +inline T maximum(const T& a, const T& b) +{ + T result = (a < b) ? b : a; + return result; +} + +/// \see nom::minimum +template +inline T clamp_min(const T& a, const T& b) +{ + auto result = nom::minimum(a, b); + return result; +} + +/// \see nom::maximum +template +inline T clamp_max(const T& a, const T& b) +{ + auto result = nom::maximum(a, b); + return result; } } // namespace nom diff --git a/include/nomlib/platforms.hpp b/include/nomlib/platforms.hpp index 93451687..9b19c97e 100644 --- a/include/nomlib/platforms.hpp +++ b/include/nomlib/platforms.hpp @@ -34,17 +34,21 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /// I have platform detection separated from nomlib/config.hpp specifically so /// that one may include this header file for use in an external project without /// worry of polluting the namespace with any unnecessary definitions. -/// -/// \todo Rename NOM_PLATFORM_POSIX to NOM_PLATFORM_UNIX..? -#if defined ( _WIN32) || defined ( __WIN32__ ) +#if defined(_WIN32) || defined(__WIN32__) #define NOM_PLATFORM_WINDOWS -#elif defined ( linux ) || defined ( __linux ) +// NOTE: The following two macros -- linux and __linux -- are obsolete and are +// not POSIX compliant! +#elif defined(__linux__) || defined(linux) || defined(__linux) #define NOM_PLATFORM_LINUX - #define NOM_PLATFORM_POSIX // Assume POSIX-compliant Unix -#elif defined ( __APPLE__ ) || defined ( MACOSX ) || defined ( macintosh ) || defined ( Macintosh ) + #define NOM_PLATFORM_POSIX +#elif defined(__APPLE__) || defined(__MAC__) || defined(MACOSX) || defined(macintosh) || defined(Macintosh) #define NOM_PLATFORM_OSX #define NOM_PLATFORM_POSIX // Assume POSIX-compliant Unix +#elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || \ + defined(__bsdi__) || defined(__DragonFly__) + #define NOM_PLATFORM_BSD + #define NOM_PLATFORM_POSIX #endif /// Platform architecture detection; we only check for a 32-bit or 64-bit @@ -96,4 +100,50 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /// 2. #define PATH_MAX 256 +// Cross-platform macro for obtaining the CPU's Time Stamp Counter (RDTSC) +// +// See also +// 1. http://handmadehero.com +// 2. http://www.strchr.com/performance_measurements_with_rdtsc +#if defined(NOM_PLATFORM_WINDOWS) && defined(NOM_COMPILER_MSVCPP) + // Use built-in compiler intrinsic + #define NOM_RDTSC() __rdtsc(); +#else // Assume clang compiler + #define NOM_RDTSC() nom::rdtsc() +#endif // MS Windows && MSVCPP + +namespace nom { + +enum Platform +{ + PLATFORM_UNKNOWN = 0, /// Undetected + PLATFORM_WINDOWS, /// Microsoft + PLATFORM_LINUX, /// GNU + PLATFORM_BSD, /// FreeBSD, NetBSD, OpenBSD, DragonFly, ... + PLATFORM_OSX, /// Apple + NUM_PLATFORMS, /// Total number of platforms +}; + +struct PlatformSpec +{ + /// \brief The platform name. + const char* name = "Unknown"; + + /// \brief The platform's environment. + Platform env = PLATFORM_UNKNOWN; + + /// \brief The number of available CPUs. + int num_cpus = 1; + + /// \brief The L1 cache size of the CPU (in bytes). + int cpu_cache_size = 0; + + /// \brief The total available system RAM (in bytes). + int total_ram = 0; +}; + +PlatformSpec platform_info(); + +} // namespace nom + #endif // include guard diff --git a/include/nomlib/ptree/VString.hpp b/include/nomlib/ptree/VString.hpp index 1f538a76..54c89b30 100644 --- a/include/nomlib/ptree/VString.hpp +++ b/include/nomlib/ptree/VString.hpp @@ -26,71 +26,65 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ -#ifndef NOMLIB_SYSTEM_PTREE_VSTRING_HPP -#define NOMLIB_SYSTEM_PTREE_VSTRING_HPP +#ifndef NOMLIB_PTREE_VSTRING_HPP +#define NOMLIB_PTREE_VSTRING_HPP #include -#include #include "nomlib/config.hpp" -#include "nomlib/core/helpers.hpp" // priv::duplicate_string -#include "nomlib/ptree/ptree_config.hpp" -#include "nomlib/ptree/ptree_forwards.hpp" namespace nom { -typedef uint ArrayIndex; +typedef nom::size_type ArrayIndex; class VString { public: - typedef VString SelfType; + typedef VString self_type; - /// \brief Default constructor. - VString( void ); + /// \brief Construct an invalid member key. + VString(); - /// \brief Destructor. - ~VString( void ); + ~VString(); - /// \brief Constructor for array element index. - VString( ArrayIndex index ); + /// \brief Construct a member key from a C string. + VString(const char* key); - /// \brief Constructor for key member pair. - VString( const char* key ); + /// \brief Construct a member key from a C++ string. + VString(const std::string& key); - /// \brief Constructor for key member pair. - VString( const std::string& key ); + /// \brief Construct a member key from an array element index. + VString(ArrayIndex index); /// \brief Copy constructor. - VString( const SelfType& copy ); + VString(const self_type& rhs); /// \brief Copy assignment operator. - SelfType& operator =( const SelfType& other ); + self_type& operator =(const self_type& rhs); - void swap( VString& other ); - - /// \brief Short-hand for checking if class variable value_ is nullptr. - bool valid( void ) const; - - /// \note Required implementation for usage inside a std::map template. - bool operator <( const VString& other ) const; + /// \brief Lesser than comparison operator. + /// + /// \note This is required for an implementation inside std::map. + bool operator <(const self_type& rhs) const; - /// \note Required implementation for usage inside a std::map template. - bool operator ==( const VString& other ) const; + /// \brief Equality comparison operator. + /// + /// \note This is required for an implementation inside std::map. + bool operator ==(const self_type& rhs) const; - /// \brief Getter for the C string (char*) copy of the stored member key. - const char* c_str( void ) const; + /// \brief Get the C++ string stored for this member key. + std::string string() const; - // / \brief Getter for std::string copy of the stored member key. - const std::string get_string( void ) const; + /// \brief Get the C string stored for this member key. + const char* c_str() const; - /// \brief Getter for the contained array element index. - /// - /// \returns Index of the element. - ArrayIndex index( void ) const; + /// \brief Get the stored array index key. + ArrayIndex index() const; private: - const char* value_; + void swap(self_type& rhs); + + std::string cstr_; ArrayIndex index_; }; diff --git a/include/nomlib/ptree/Value.hpp b/include/nomlib/ptree/Value.hpp index bbf88f78..243a19aa 100644 --- a/include/nomlib/ptree/Value.hpp +++ b/include/nomlib/ptree/Value.hpp @@ -29,14 +29,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef NOMLIB_SYSTEM_PTREE_VALUE_HPP #define NOMLIB_SYSTEM_PTREE_VALUE_HPP -#include #include -#include -#include #include #include "nomlib/config.hpp" -#include "nomlib/core/helpers.hpp" // priv::duplicate_string #include "nomlib/ptree/ptree_config.hpp" #include "nomlib/ptree/ptree_types.hpp" @@ -50,7 +46,10 @@ class Value typedef Value* RawPtr; typedef Value& Reference; + // TODO: Rename to const_iterator? typedef ValueConstIterator ConstIterator; + + // TODO: Rename to iterator? typedef ValueIterator Iterator; typedef std::vector Members; @@ -79,74 +78,18 @@ class Value /// \brief Declared value of Null for this object. /// /// \remarks Value::null is the default constructor's type. - static const Value null; + static const Value& null; /// \brief Default constructor; constructs an object with NullValue data /// type. - Value( void ); // type 0 - - /// \brief Destructor. - /// - /// \todo Memory management of our pointers. - ~Value( void ); - - /// \brief Copy constructor. - Value( const Value& copy ); - - /// \brief Copy assignment operator. - Value::SelfType& operator =( const SelfType& other ); - - /// \brief Exchange the contents of the container; copy & swap idiom. - /// - /// \remarks In particular, one must be careful to keep track of copying our - /// char* strings as necessary. - /// - /// \note This method is used in the implementation of the copy assignment - /// operator. - void swap( Value& other ); - - /// \brief Lesser than comparison operator. - /// - /// \note Borrowed from JsonCpp library -- thanks! - bool operator <( const Value& other ) const; - - /// \brief Lesser than or equal to comparison operator. - /// - /// \note Borrowed from JsonCpp library -- thanks! - bool operator <=( const Value& other ) const; - - /// \brief Greater than or equal to comparison operator. - /// - /// \note Borrowed from JsonCpp library -- thanks! - bool operator >=( const Value& other ) const; - - /// \brief Greater than or equal to comparison operator. - /// - /// \note Borrowed from JsonCpp library -- thanks! - bool operator >( const Value& other ) const; - - /// \brief Equality comparison operator. - /// - /// \note Borrowed from JsonCpp library -- thanks! - bool operator ==( const Value& other ) const; - - /// \brief Not equal comparison operator. - /// - /// \note Borrowed from JsonCpp library -- thanks! - bool operator !=( const Value& other ) const; + Value(); // type 0 - /// \brief Returns Value::null. - /// - /// \note Borrowed from JsonCpp library -- thanks! - bool operator!( void ) const; - - /// \brief Construct a Value container node of a specified type. - Value( enum ValueType type ); + ~Value(); /// \brief Construct an object using a signed integer value. /// /// \note Type 1 - Value( sint val ); + Value(int val); /// \brief Construct an object using an unsigned (non-negative) integer /// value. @@ -156,13 +99,13 @@ class Value /// correct constructor. /// /// \note Type 2 - Value( uint val ); + Value(uint val); /// \brief Construct an object using a double-precision floating point /// "real" number value. /// /// \note Type 3 - Value( double val ); + Value(real64 val); /// \brief Construct an object from a C-style string value. /// @@ -182,44 +125,102 @@ class Value /// \endinternal /// /// \note Type 4 - Value( const char* val ); + Value(const char* str); /// \brief Construct an object from a C++ string value (std::string). /// /// \note Type 4 - Value( const std::string& val ); + Value(const std::string& str); /// \brief Construct an object from a boolean value. /// /// \note Type 5 - Value( bool val ); + Value(bool val); /// \brief Construct an object with either array or object node values. /// /// \note Type 6 or 7 - Value( const Object& val ); + Value(const Object& obj); + + /// \brief Construct a Value container node of a specified type. + Value(ValueType type); + + /// \brief Copy constructor. + Value(const Value& rhs); + + /// \brief Copy assignment operator. + Value::SelfType& operator =(const SelfType& rhs); + + /// \brief Exchange the contents of the container; copy & swap idiom. + /// + /// \remarks In particular, one must be careful to keep track of copying our + /// char* strings as necessary. + /// + /// \note This method is used in the implementation of the copy assignment + /// operator. + void swap(Value& rhs); + + /// \brief Lesser than comparison operator. + /// + /// \note Borrowed from JsonCpp library -- thanks! + /// + /// \todo Unit testing of the operator overload logic for lesser than + /// equality. + bool operator <(const Value& rhs) const; + + /// \brief Lesser than or equal to comparison operator. + /// + /// \note Borrowed from JsonCpp library -- thanks! + bool operator <=(const Value& rhs) const; + + /// \brief Greater than or equal to comparison operator. + /// + /// \note Borrowed from JsonCpp library -- thanks! + bool operator >=(const Value& rhs) const; + + /// \brief Greater than or equal to comparison operator. + /// + /// \note Borrowed from JsonCpp library -- thanks! + bool operator >(const Value& rhs) const; + + /// \brief Equality comparison operator. + /// + /// \note Borrowed from JsonCpp library -- thanks! + /// + /// \todo Unit testing of the operator overload logic for equality. + bool operator ==(const Value& rhs) const; + + /// \brief Not equal comparison operator. + /// + /// \note Borrowed from JsonCpp library -- thanks! + bool operator !=(const Value& rhs) const; + + /// \brief Returns Value::null. + /// + /// \note Borrowed from JsonCpp library -- thanks! + bool operator!() const; /// \brief Internal helper method for comparing array & object node /// containers. /// /// \note Borrowed from JsonCpp library -- thanks! - int compare( const Value& other ) const; + int compare(const Value& rhs) const; /// \brief Obtain a pointer to the object. /// /// \returns A raw pointer to the object. No transfer of ownership is made. - Value::RawPtr get( void ); + Value::RawPtr get(); /// \brief Obtain a reference to the object. /// /// \returns A reference to the object. - const Value::Reference ref( void ); + Value::Reference ref(); /// \brief Obtain the enumeration type of the object. /// /// \remarks The type is defined by the value type(s) set at construction. /// See also: Value::ValueType enumeration. - enum Value::ValueType type( void ) const; + enum Value::ValueType type() const; /// \brief Obtain the enumeration type of the object as a std::string. /// @@ -227,67 +228,67 @@ class Value /// See also: Value::ValueType enumeration. /// /// \todo Rename to stringify_type? - const std::string type_name( void ) const; + const std::string type_name() const; /// \brief Query if the value type type stored in the object is NULL. - bool null_type( void ) const; + bool null_type() const; /// \brief Query if the value type type stored in the object is a signed /// integer. - bool int_type( void ) const; + bool int_type() const; /// \brief Query if the value type type stored in the object is an unsigned /// integer (non-negative. - bool uint_type( void ) const; + bool uint_type() const; /// \brief Query if the value type type stored in the object is a double- /// precision floating point real number. - bool double_type( void ) const; + bool double_type() const; /// \brief Query if the value type type stored in the object is a double- /// precision floating point real number. - bool float_type( void ) const; + bool float_type() const; /// \brief Query if the value type type stored in the object is a string /// value. - bool string_type( void ) const; + bool string_type() const; /// \brief Query if the value type type stored in the object is a boolean /// value. - bool bool_type( void ) const; + bool bool_type() const; /// \brief Query if the value type type stored in the object are array /// values. /// /// \remarks nom::Value object may be either an array OR object type at any /// given time. - bool array_type( void ) const; + bool array_type() const; /// \brief Query if the value type type stored in the object are object /// values. /// /// \remarks nom::Value object may be either an array OR object type at any /// given time. - bool object_type( void ) const; + bool object_type() const; - const std::string stringify( void ) const; + const std::string stringify() const; /// \brief Obtain the signed integer value stored within the container. /// /// \returns On err, zero (0) is returned. - sint get_int( void ) const; + int get_int() const; /// \brief Obtain the unsigned (non-negative) integer value stored within /// the container. /// /// \returns On err, zero (0) is returned. - uint get_uint( void ) const; + uint get_uint() const; /// \brief Obtain the double-precision floating point "real" number value /// stored within the container. /// /// \returns On err, zero (0) is returned. - double get_double( void ) const; + real64 get_double() const; /// \brief Obtain the double-precision floating point "real" number value /// stored within the container. @@ -296,7 +297,7 @@ class Value /// /// \remarks Conversion from the internally stored double variable to a /// float type is done. - float get_float( void ) const; + real32 get_float() const; /// \brief Obtain the C style string value stored within the container. /// @@ -304,7 +305,7 @@ class Value /// /// ~~\remarks A copy of the stored C string is made, therefore no ownership /// transfers occur; you are responsible for freeing the returned C string.~~ - const char* get_cstring( void ) const; + const char* get_cstring() const; /// \brief Obtain the string value stored within the container. /// @@ -312,78 +313,33 @@ class Value /// std::string is done. /// /// \returns On err, a null-terminated std::string -- "\0" is returned. - const std::string get_string( void ) const; + std::string get_string() const; /// \brief Obtain the boolean value stored within the container. /// /// \returns On err, boolean false is returned. - bool get_bool( void ) const; + bool get_bool() const; - /// \brief Obtain the value stored within the container. - /// - /// \returns If NULL, zero (0) is returned. On error (an unknown value), - /// negative one (-1) is returned. - template - inline T get_value( void ) const - { - if( this->null_type() ) - { - return 0; - } - else if( this->int_type() ) - { - return this->get_int(); - } - else if( this->uint_type() ) - { - return this->get_uint(); - } - else if( this->double_type() ) - { - return this->get_double(); - } - else if( this->string_type() ) - { - return this->get_string(); - } - else if( this->bool_type() ) - { - return this->get_bool(); - } - else if( this->array_type() ) - { - return this->array(); - } - else if( this->object_type() ) - { - return this->object(); - } - else // Handle unknown cases - { - return Value::null; - } - } - - bool array_valid( void ) const; - bool object_valid( void ) const; + bool array_valid() const; + bool object_valid() const; /// \brief Obtain the array values of the object. /// /// \returns ~~Return-by-value cloned copy of the nom::Array pointer held by /// this object.~~ - const Object array( void ) const; + const Object array() const; /// \brief Obtain the object tree of the object. /// /// \returns ~~Return-by-value cloned copy of the nom::Object pointer held by /// this object.~~ - const Object object( void ) const; + const Object object() const; /// \brief Obtain the size of the object's contained values. /// /// \returns Size of the array or object, or one (1) when not said type. /// On err -- when the object is ValueType::NullValue -- zero (0). - uint size( void ) const; + nom::size_type size() const; /// \brief Obtain boolean response in regards to container's empty status. /// @@ -399,7 +355,7 @@ class Value /// /// \note This method has no effect unless the object's container is one of /// two node types: array or object. - void clear( void ); + void clear(); /// \brief Obtain a stored element by index number. /// @@ -411,11 +367,11 @@ class Value /// /// \note In contrast to the standard STL method overloads for /// ::operator[](int), nom::Value should always perform bounds checking. - Value& operator[]( ArrayIndex index ); + Value& operator[](ArrayIndex index); /// \note In contrast to the standard STL method overloads for /// ::operator[](int), nom::Value should always perform bounds checking. - Value& operator[]( int index ); + Value& operator[](int index); /// \brief Obtain a stored element by index number. /// @@ -432,11 +388,11 @@ class Value /// /// \note In contrast to the standard STL method overloads for /// ::operator[](int), nom::Value should always perform bounds checking. - const Value& operator[]( ArrayIndex index ) const; + const Value& operator[](ArrayIndex index) const; /// \note In contrast to the standard STL method overloads for /// ::operator[](int), nom::Value should always perform bounds checking. - const Value& operator[]( int index ) const; + const Value& operator[](int index) const; /// \brief Access an object node's container. /// @@ -446,41 +402,15 @@ class Value /// /// \note In contrast to the standard STL method overloads for /// ::operator[](int), nom::Value should always perform bounds checking. - /*const*/ Value& operator[]( const char* key ) /*const*/; + Value& operator[](const char* key); - Value& operator[]( const std::string& key ); - // const Value& operator[]( const std::string& key ) const; + Value& operator[](const std::string& key); - /// \brief Insert array elements. - Value& push_back( const Value& val ); + const Value& operator[](const char* key) const; + const Value& operator[](const std::string& key) const; - /// \brief Access an element. - /// - /// \returns Returns a reference to the element at position n in the - /// container. - /// - /// \remarks This method is an alias for Value::operator[](int) and is - /// provided to help ease the porting of code from standard container - /// types from STL, i.e.: std::vector, std::map, ... - /// - /// \note This function automatically checks whether n is within the bounds - /// of valid elements in the vector, throwing an assert if it is not (i.e., - /// if n is greater or equal than its size). This error handling behavior is - /// intended to mimic the standard container types -- less and except where - /// otherwise noted here in the documentation. - /// - /// \note Unlike the standard STL err handling behavior for member - /// method overloads ::operator[] and friends, bounds checking is **always** - /// unconditionally performed when using any method a part of the - /// nom::Value interface. - /// - /// Bounds checking err handling that may be disabled by the end-user is via - /// disabling assert macros -- NOM_ASSERT, albeit this alone *should not* - /// modify the error state logic of the method(s) in question, if the - /// programming is without human mistake. - /// - /// \todo Verify working state of method. - // Value& at( int index ); + /// \brief Insert array elements. + Value& push_back(const Value& val); /// \brief Search the object for an existing member. /// @@ -490,7 +420,7 @@ class Value /// /// \note This method will fail with an assert if the container type is /// *not* either a null or object node type. - Value find( const std::string& key ) const; + const Value& find(const std::string& key) const; /// \brief Remove the named member. /// @@ -499,10 +429,7 @@ class Value /// \remarks The object is unchanged if the referenced key does not exist. /// /// \note The object's type is not modified. - /// - /// \note This method will fail with an assert if the container type is - /// *not* either a null or object node type. - Value erase( const std::string& key ); + Value erase(const std::string& key); /// \brief Remove an array object. /// @@ -526,11 +453,11 @@ class Value /// /// \note This method will fail with an assert if the container type is /// *not* either a null or object node type. - Members member_names( void ) const; + Members member_names() const; - Value::ConstIterator begin( void ) const; + Value::ConstIterator begin() const; - Value::ConstIterator end( void ) const; + Value::ConstIterator end() const; /// \brief Iterator access to the beginning of the object's tree. /// @@ -540,7 +467,7 @@ class Value /// /// \remarks The nom::Value object must be initialized as a nom::ArrayValue /// or nom::ObjectValue type. - Value::Iterator begin( void ); + Value::Iterator begin(); /// \brief Iterator access to the end of the object's tree. /// @@ -550,7 +477,7 @@ class Value /// /// \remarks The nom::Value object must be initialized as a nom::ArrayValue /// or nom::ObjectValue type. - Value::Iterator end( void ); + Value::Iterator end(); /// \brief Dump the object's complete value tree. /// @@ -558,20 +485,20 @@ class Value /// /// \note This method is used by the << overload function for nom::Value /// objects. - const std::string dump( const Value& object, int depth = 0 ) const; + const std::string dump(const Value& object, int depth = 0) const; private: /// \brief Internal helper method for nom::Value::dump. - const std::string dump_key( const Value& key ) const; + const std::string dump_key(const Value& key) const; /// \brief Internal helper method for nom::Value::dump. - const std::string dump_value( const Value& val ) const; + const std::string dump_value(const Value& val) const; /// \brief Internal helper method for nom::Value::dump_key. - const std::string print_key( const std::string& type, uint size ) const; + const std::string print_key(const std::string& type, uint size) const; /// \brief Internal helper method for nom::Value::dump_value. - const std::string print_value( const std::string& val ) const; + const std::string print_value(const std::string& val) const; /// \brief Container for the data types able to be held. /// @@ -584,25 +511,28 @@ class Value /// \endinternal union ValueHolder { - sint int_; // Type 1 + int int_; // Type 1 uint uint_; // Type 2 - double real_; // Type 3 + real64 real_; // Type 3 bool bool_; // Type 4 + // NOTE: Instance-owned pointer. const char* string_; // Type 5 + // NOTE: Instance-owned pointer. Object* object_; // Type 6 and 7 - } value_; + }; - /// The type of value held in object container. + /// \brief The type of stored value in this instance. enum ValueType type_; - bool string_allocated_ = false; + /// \brief The stored value in this instance. + ValueHolder value_; }; /// \brief Pretty print the object /// /// \todo Implement upper limit to value length dump; ideally within ~80 /// characters or less? -std::ostream& operator <<( std::ostream& os, const Value& val ); +std::ostream& operator <<(std::ostream& os, const Value& val); } // namespace nom diff --git a/include/nomlib/ptree/ValueConstIterator.hpp b/include/nomlib/ptree/ValueConstIterator.hpp index 925a53ea..f840ef7d 100644 --- a/include/nomlib/ptree/ValueConstIterator.hpp +++ b/include/nomlib/ptree/ValueConstIterator.hpp @@ -48,52 +48,52 @@ class ValueConstIterator: public ValueIteratorBase typedef SelfType* RawPtr; typedef SelfType& Reference; - typedef const ValueIteratorBase::ValueTypeReference ConstReference; + typedef ValueIteratorBase::ValueTypeReference ConstReference; typedef SelfType ConstIterator; /// \brief Default constructor. - ValueConstIterator( void ); + ValueConstIterator(); /// \brief Destructor. - ~ValueConstIterator( void ); + ~ValueConstIterator(); /// \brief Copy constructor - ValueConstIterator( const ValueConstIterator& copy ); + ValueConstIterator(const ValueConstIterator& rhs); /// \brief Copy constructor - ValueConstIterator( const ObjectIterator& itr ); + ValueConstIterator(const ObjectIterator& rhs); - SelfType& operator =( const DerivedType& other ); + SelfType& operator =(const DerivedType& rhs); /// \brief Obtain a pointer to the iterator's object. /// /// \returns A pointer to the nom::Value object. - const Value::RawPtr operator ->( void ) const; + const Value::RawPtr operator ->() const; /// \brief Obtain a reference to the iterator's object. /// /// \returns A reference to the nom::Value object. - ConstReference operator *( void ) const; + ConstReference operator *() const; /// \brief Increment the object by one /// /// \remarks The object is not modified. - SelfType& operator ++( void ); + SelfType& operator ++(); /// \brief Increment by specified parameter /// /// \remarks The object is not modified. - SelfType operator ++( sint ); + SelfType operator ++(int); /// \brief Decrement by one /// /// \remarks The object is not modified. - SelfType& operator --( void ); + SelfType& operator --(); /// \brief Decrement by specified parameter /// /// \remarks The object is not modified. - SelfType operator --( sint ); + SelfType operator --(int); }; } // namespace nom diff --git a/include/nomlib/ptree/ValueIterator.hpp b/include/nomlib/ptree/ValueIterator.hpp index a5eb28e5..795bc067 100644 --- a/include/nomlib/ptree/ValueIterator.hpp +++ b/include/nomlib/ptree/ValueIterator.hpp @@ -46,41 +46,41 @@ class ValueIterator: public ValueIteratorBase typedef SelfType Iterator; /// \brief Default constructor. - ValueIterator( void ); + ValueIterator(); /// \brief Destructor. - ~ValueIterator( void ); + ~ValueIterator(); /// \brief Copy constructor - ValueIterator( const ValueIterator& copy ); + ValueIterator(const ValueIterator& rhs); /// \brief Copy constructor - ValueIterator( const ObjectIterator& itr ); + ValueIterator(const ObjectIterator& rhs); /// \brief Copy assignment - SelfType& operator =( const SelfType& other ); + SelfType& operator =(const SelfType& rhs); /// \brief Obtain a reference to the iterator's object. /// /// \returns A reference to the nom::Value object. - ValueTypeReference operator *( void ) const; + ValueTypeReference operator *() const; /// \brief Obtain a pointer to the iterator's object. /// /// \returns A pointer to the nom::Value object. - ValueTypePointer operator ->( void ) const; + ValueTypePointer operator ->() const; /// \brief Increment the object by one - SelfType& operator ++( void ); + SelfType& operator ++(); /// \brief Increment by specified parameter - SelfType operator ++( sint ); + SelfType operator ++(int); /// \brief Decrement by one - SelfType& operator --( void ); + SelfType& operator --(); /// \brief Decrement by specified parameter - SelfType operator --( sint ); + SelfType operator --(int); }; } // namespace nom diff --git a/include/nomlib/ptree/ValueIteratorBase.hpp b/include/nomlib/ptree/ValueIteratorBase.hpp index 7a88c316..2156dea9 100644 --- a/include/nomlib/ptree/ValueIteratorBase.hpp +++ b/include/nomlib/ptree/ValueIteratorBase.hpp @@ -56,24 +56,24 @@ class ValueIteratorBase }; /// \brief Default constructor. - ValueIteratorBase( void ); + ValueIteratorBase(); /// \brief Destructor. - virtual ~ValueIteratorBase( void ); + virtual ~ValueIteratorBase(); - ValueIteratorBase( const ObjectIterator& itr ); + ValueIteratorBase(const ObjectIterator& rhs); /// \brief query validity of the object. /// /// \returns The object is always assumed to be invalid when /// Value::ValueType::NullValue is the set type (set during construction). - bool valid( void ) const; + bool valid() const; /// \brief Obtain the index of the referenced value. /// /// \returns The array element's index on success, or -1 if the referenced /// value is not an array element. - /*ArrayIndex*/int index( void ) const; + ArrayIndex index() const; /// \brief Obtain the current index or member key value. /// @@ -82,30 +82,28 @@ class ValueIteratorBase /// /// \todo Try handling the return of the key (string) or index (integer) /// more elegantly? (Presently, we are converting index values to a string). - const char* key( void ) const; + const char* key() const; /// \brief Query if the member key exists at the current position of the /// iterator object. - bool key( const std::string& member ) const; + bool key(const std::string& member) const; - Value key_v2( void ) const; + bool operator ==(const SelfType& rhs) const; - bool operator ==( const SelfType& other ) const; + bool operator !=(const SelfType& rhs) const; - bool operator !=( const SelfType& other ) const; - - DifferenceType operator -( const SelfType& other ) const; + DifferenceType operator -(const SelfType& rhs) const; protected: - void copy( const SelfType& other ); - ValueTypeReference dereference( void ) const; - ValueTypePointer pointer( void ) const; - void increment( void ); - void decrement( void ); - DifferenceType distance( const SelfType& other ) const; + void copy(const SelfType& rhs); + ValueTypeReference dereference() const; + ValueTypePointer pointer() const; + void increment(); + void decrement(); + DifferenceType distance(const SelfType& rhs) const; private: - enum IteratorType type( void ) const; + enum IteratorType type() const; /// \brief A pointer to the object type iterator ObjectIterator object_; diff --git a/include/nomlib/serializers.hpp b/include/nomlib/serializers.hpp index 9e7f015a..fdbb6593 100644 --- a/include/nomlib/serializers.hpp +++ b/include/nomlib/serializers.hpp @@ -42,4 +42,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "nomlib/serializers/MiniHTML.hpp" +// Filesystem utilities +#include + #endif // include guard defined diff --git a/include/nomlib/serializers/JsonCppDeserializer.hpp b/include/nomlib/serializers/JsonCppDeserializer.hpp index 8fbad3e0..63b879cd 100644 --- a/include/nomlib/serializers/JsonCppDeserializer.hpp +++ b/include/nomlib/serializers/JsonCppDeserializer.hpp @@ -109,6 +109,8 @@ class JsonCppDeserializer: public IValueDeserializer bool read_object( const Json::Value& object, Value& dest ) const; }; +std::unique_ptr make_unique_json_deserializer(); + } // namespace nom #endif // include guard defined diff --git a/include/nomlib/serializers/JsonCppSerializer.hpp b/include/nomlib/serializers/JsonCppSerializer.hpp index df608f9c..fee0f207 100644 --- a/include/nomlib/serializers/JsonCppSerializer.hpp +++ b/include/nomlib/serializers/JsonCppSerializer.hpp @@ -120,6 +120,8 @@ class JsonCppSerializer: public IValueSerializer bool write_object( const Value& object, Json::Value& dest ) const; }; +std::unique_ptr make_unique_json_serializer(); + } // namespace nom #endif // include guard defined diff --git a/include/nomlib/system/SearchPath.hpp b/include/nomlib/serializers/SearchPath.hpp similarity index 95% rename from include/nomlib/system/SearchPath.hpp rename to include/nomlib/serializers/SearchPath.hpp index 4e5b050a..ee5c5f2f 100644 --- a/include/nomlib/system/SearchPath.hpp +++ b/include/nomlib/serializers/SearchPath.hpp @@ -26,21 +26,20 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ -#ifndef NOMLIB_SYSTEM_SEARCH_PATH_HPP -#define NOMLIB_SYSTEM_SEARCH_PATH_HPP +#ifndef NOMLIB_SERIALIZERS_SEARCH_PATH_HPP +#define NOMLIB_SERIALIZERS_SEARCH_PATH_HPP #include #include #include #include "nomlib/config.hpp" -#include "nomlib/system/File.hpp" - -#include "nomlib/ptree.hpp" -#include "nomlib/serializers.hpp" namespace nom { +// Forward declarations +class IValueDeserializer; + /// \brief Determine the directory location to use based on search prefixes /// /// \todo Add an interface for using this class without a file. @@ -96,7 +95,7 @@ class SearchPath /// Common usage: /// -/// #include "nomlib/system.hpp" +/// #include "nomlib/serializers.hpp" /// /// int main( int argc, char** argv ) /// { diff --git a/include/nomlib/system.hpp b/include/nomlib/system.hpp index 6114c79e..4ce73f1f 100644 --- a/include/nomlib/system.hpp +++ b/include/nomlib/system.hpp @@ -31,7 +31,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // Public header file -#include #include #include #include @@ -41,16 +40,15 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include #include -#include +#include // Engine initialization & shutdown #include #include #include -#include -#include #include #include #include @@ -62,7 +60,4 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include -// Filesystem utilities -#include - #endif // include guard defined diff --git a/include/nomlib/system/Event.hpp b/include/nomlib/system/Event.hpp index e56e0f68..7d95f588 100644 --- a/include/nomlib/system/Event.hpp +++ b/include/nomlib/system/Event.hpp @@ -29,25 +29,76 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef NOMLIB_SYSTEM_EVENT_HPP #define NOMLIB_SYSTEM_EVENT_HPP -#include +#include #include "nomlib/config.hpp" +#include "nomlib/system/Joystick.hpp" namespace nom { -const uint8 MAX_TEXT_INPUT_LENGTH = 255; +const uint8 MAX_TEXT_INPUT_LENGTH = 32; +const uint8 MAX_TEXT_EDITING_LENGTH = 32; -class EventCallback; +/// \brief General hardware device state definitions. +enum InputState: uint8 +{ + RELEASED = 0, + PRESSED, +}; + +/// \brief Mouse button definitions. +enum MouseButton: uint8 +{ + LEFT_MOUSE_BUTTON = 1, + MIDDLE_MOUSE_BUTTON, + RIGHT_MOUSE_BUTTON, + X1_MOUSE_BUTTON, + X2_MOUSE_BUTTON, +}; + +typedef int64 TouchID; +typedef int64 FingerID; + +/// \brief Unique identifier for mouse events simulated with touch input +/// hardware. +const uint32 TOUCH_MOUSE_ID = nom::NOM_UINT32_MAX; + +/// \brief Representation of a program termination request. +struct QuitEvent +{ + /// \brief User-defined data. + void* data1; + + /// \brief User-defined data. + void* data2; +}; /// \brief A structure containing information on a window event. -/// -/// \remarks SDL_WINDOWEVENT. struct WindowEvent { - /// \brief An enumeration of window events. + /// \brief Window event types. + enum EventType: uint8 + { + NONE = 0, + SHOWN, + HIDDEN, + EXPOSED, + MOVED, + RESIZED, + SIZE_CHANGED, + MINIMIZED, + MAXIMIZED, + RESTORED, + MOUSE_FOCUS_GAINED, + MOUSE_FOCUS_LOST, + KEYBOARD_FOCUS_GAINED, + KEYBOARD_FOCUS_LOST, + CLOSE, + }; + + /// \brief The window's event type. /// - /// \remarks SDL_WINDOWEVENT_NONE, SDL_WINDOWEVENT_SHOWN, - /// SDL_WINDOWEVENT_HIDDEN, ..., SDL_WINDOWEVENT_CLOSE. + /// \see nom::WindowEvent::EventType uint8 event; /// \brief Event dependent data; typically the X coordinate position or width @@ -60,69 +111,24 @@ struct WindowEvent /// \brief The identifier of the window at the moment of the event. uint32 window_id; - - void dump( void ) const - { - NOM_DUMP( NOM_SCAST( int, event ) ); - NOM_DUMP( data1 ); - NOM_DUMP( data2 ); - NOM_DUMP( window_id ); - } -}; - -struct KeyboardSymbolCode -{ - // TODO -}; - -struct KeyboardScanCode -{ - // TODO -}; - -/// \brief A structure containing information on a keyboard symbol. -struct KeyboardSymbolEvent -{ - /// \brief The event type. - /// - /// \remarks Not used; reserved for future use. - uint32 type; - - /// \brief The physical key code of the key press event. - /// - /// \todo Implement using the KeyboardScanCode structure. - SDL_Scancode scan_code; - - /// \brief Virtual key code of the key press event. - /// - /// \todo Implement using the KeyboardSymbolCode structure. - SDL_Keycode sym; - - /// \brief An enumeration of key modifier masks; see also: the SDL2 wiki - /// documentation page for [SDL_Keymod](https://wiki.libsdl.org/SDL_Keymod). - uint16 mod; }; /// \brief A structure containing information on a keyboard event. -/// -/// \remarks SDL_KEYDOWN or SDL_KEYUP. struct KeyboardEvent { - SDL_Scancode scan_code; - - /// \brief Symbol of the key press event. - /// - /// \todo Implement using the KeyboardSymbol structure. - // SDL_Keysym sym; + /// \brief The virtual key code. int32 sym; /// \brief An enumeration of key modifier masks; see also: the SDL2 wiki /// documentation page for [SDL_Keymod](https://wiki.libsdl.org/SDL_Keymod). uint16 mod; + /// \brief The physical key code. + uint16 scan_code; + /// \brief The state of the key press event. /// - /// \remarks SDL_PRESSED or SDL_RELEASED. + /// \see nom::InputState uint8 state; /// \brief Non-zero if this is a repeating key press event. @@ -130,21 +136,9 @@ struct KeyboardEvent /// \brief The identifier of the window at the moment of the event. uint32 window_id; - - void dump( void ) const - { - NOM_DUMP( scan_code ); - NOM_DUMP( sym ); - NOM_DUMP( mod ); - NOM_DUMP( NOM_SCAST( int, state ) ); - NOM_DUMP( NOM_SCAST( int, repeat ) ); - NOM_DUMP( window_id ); - } }; /// \brief A structure containing information on a mouse event. -/// -/// \remarks SDL_MOUSEMOTION. struct MouseMotionEvent { /// \brief The mouse instance identifier, or SDL_TOUCH_MOUSEID. @@ -175,22 +169,9 @@ struct MouseMotionEvent /// \brief The identifier of the window at the moment of the event. uint32 window_id; - - void dump( void ) const - { - NOM_DUMP( id ); - NOM_DUMP( x ); - NOM_DUMP( y ); - NOM_DUMP( x_rel ); - NOM_DUMP( y_rel ); - NOM_DUMP( NOM_SCAST( int, state ) ); - NOM_DUMP( window_id ); - } }; /// \brief A structure containing information on a mouse button event. -/// -/// \remarks SDL_MOUSEBUTTONDOWN (1025) or SDL_MOUSEBUTTONUP (1026). struct MouseButtonEvent { /// \brief The mouse instance identifier, or SDL_TOUCH_MOUSEID. @@ -208,15 +189,14 @@ struct MouseButtonEvent /// \brief The Y coordinate, relative to the nom::Window instance. int32 y; - /// \brief The button that has changed. + /// \brief The button's event type. /// - /// \remarks This field may be one of: SDL_BUTTON_LEFT, SDL_BUTTON_MIDDLE, - /// SDL_BUTTON_RIGHT, SDL_BUTTON_X1 or SDL_BUTTON_X2. + /// \see nom::MouseButton. uint8 button; /// \brief The state of the button. /// - /// \remarks SDL_PRESSED or SDL_RELEASED. + /// \see nom::InputState uint8 state; /// \brief Value containing how many mouse buttons were clicked. @@ -225,25 +205,10 @@ struct MouseButtonEvent uint8 clicks; /// \brief The identifier of the window at the moment of the event. - /// - /// \note ev->button.windowID uint32 window_id; - - void dump( void ) const - { - NOM_DUMP( id ); - NOM_DUMP( x ); - NOM_DUMP( y ); - NOM_DUMP( NOM_SCAST( int, button ) ); - NOM_DUMP( NOM_SCAST( int, state ) ); - NOM_DUMP( NOM_SCAST( int, clicks ) ); - NOM_DUMP( window_id ); - } }; /// \brief A structure containing information on a mouse wheel event. -/// -/// \remarks SDL_MOUSEWHEEL. struct MouseWheelEvent { /// \brief The mouse instance identifier, or SDL_TOUCH_MOUSEID. @@ -259,107 +224,120 @@ struct MouseWheelEvent /// \brief Y axis value, relative to the direction of the wheel. int32 y; - // /// \brief The direction of the wheel. - // /// - // /// \remarks The axis of the wheel event; zero (0) for X-axis and one (1) - // /// for Y-axis. - // /// - // /// \note This is only implemented for internal usage within - // /// nom::InputMapper. - // /// - // /// \see nom::MouseWheelAction - // uint8 axis; - /// \brief The identifier of the window at the moment of event. uint32 window_id; - - void dump( void ) const - { - NOM_DUMP( id ); - NOM_DUMP( x ); - NOM_DUMP( y ); - NOM_DUMP( window_id ); - } }; /// \brief A structure containing information on a joystick axis motion event. -/// -/// \remarks SDL_JOYAXISMOTION. struct JoystickAxisEvent { - /// \brief Index of the joystick that reported the event. - SDL_JoystickID id; + /// \brief The joystick instance identifier. + JoystickID id; /// \brief Index of the axis on the device. + /// + /// \see nom::JoystickAxis uint8 axis; /// \brief The current position of the axis. /// /// \remarks The range of the axis value is in between -32,768 to 32,767. int16 value; - - /// \brief Identifier of the window at the moment of the event. - // uint32 window_id; - - void dump( void ) const - { - NOM_DUMP( id ); - NOM_DUMP( NOM_SCAST( int, axis ) ); - NOM_DUMP( value ); - } }; /// \brief A structure containing information on a joystick button event. -/// -/// \remarks SDL_JOYBUTTONDOWN or SDL_JOYBUTTON_UP. struct JoystickButtonEvent { - /// \brief Index of the joystick that reported the event. - SDL_JoystickID id; + /// \brief The joystick instance identifier. + JoystickID id; /// \brief The index of the button. uint8 button; /// \brief The state of the button. /// - /// \remarks SDL_PRESSED or SDL_RELEASED. + /// \see nom::InputState uint8 state; +}; - void dump( void ) const - { - NOM_DUMP( id ); - NOM_DUMP( NOM_SCAST( int, button ) ); - NOM_DUMP( NOM_SCAST( int, state ) ); - } +/// \brief Internal representation of a joystick hat position. +struct JoystickHatEvent +{ + /// \brief The joystick instance identifier. + JoystickID id; + + /// \brief The index of the joystick hat. + /// + /// \see nom::JoystickHat + uint8 hat; + + /// \brief The hat position value. + /// + /// \see nom::JoystickHatPosition + uint8 value; }; -struct JoystickConnectedEvent +/// \brief Internal representation of a joystick device connection event. +struct JoystickDeviceEvent { - /// \brief Index of the joystick that reported the event. - SDL_JoystickID id; + /// \brief The joystick instance identifier. + /// + /// \see Event::JOYSTICK_ADDED, Event::JOYSTICK_REMOVED + JoystickID id; +}; - void dump( void ) const - { - NOM_DUMP( id ); - } +/// \brief A structure containing information on a game controller axis motion +/// event. +struct GameControllerAxisEvent +{ + /// \brief The joystick instance identifier. + JoystickID id; + + /// \brief Index of the axis on the device. + /// + /// \see nom::GameControllerAxis + int8 axis; + + /// \brief The axis value. + /// + /// \remarks The range of this value is between -32,768 to 32,767. + int16 value; }; -struct JoystickDisconnectedEvent +/// \brief A structure containing information on a game controller button event. +struct GameControllerButtonEvent { - /// \brief Index of the joystick that reported the event. - SDL_JoystickID id; + /// \brief The joystick instance identifier. + JoystickID id; - void dump( void ) const - { - NOM_DUMP( id ); - } + /// \brief The index of the button. + /// + /// \see nom::GameControllerButton + int8 button; + + /// \brief The state of the button. + /// + /// \see nom::InputState + uint8 state; +}; + +/// \brief Internal representation of a game controller device connection +/// event. +struct GameControllerDeviceEvent +{ + /// \brief The joystick instance identifier. + /// + /// \see Event::GAME_CONTROLLER_ADDED, + /// Event::GAME_CONTROLLER_REMOVED, + /// Event::GAME_CONTROLLER_REMAPPED + JoystickID id; }; /// \brief A structure containing information on a finger (touch) event. struct FingerEvent { /// \brief The finger index identifier. - SDL_FingerID id; + FingerID id; /// \brief The X coordinate of the event; normalized 0..1. float x; @@ -369,14 +347,6 @@ struct FingerEvent /// \brief The quantity of the pressure applied; normalized 0..1. float pressure; - - void dump( void ) const - { - NOM_DUMP( id ); - NOM_DUMP( x ); - NOM_DUMP( y ); - NOM_DUMP( pressure ); - } }; /// \brief A structure containing information on a finger touch event. @@ -385,7 +355,7 @@ struct FingerEvent struct FingerTouchEvent { /// \brief The touch device index identifier. - SDL_TouchID id; + TouchID id; /// \brief The finger index identifier. FingerEvent finger; @@ -404,18 +374,6 @@ struct FingerTouchEvent /// \brief The quantity of the pressure applied; normalized 0..1. float pressure; - - void dump( void ) const - { - NOM_DUMP( id ); - NOM_DUMP( finger.id ); - finger.dump(); - NOM_DUMP( x ); - NOM_DUMP( y ); - NOM_DUMP( dx ); - NOM_DUMP( dy ); - NOM_DUMP( pressure ); - } }; /// \brief A structure containing information on a multi-finger gesture (touch) @@ -425,7 +383,7 @@ struct FingerTouchEvent struct GestureEvent { /// \brief The touch device index identifier. - SDL_TouchID id; + TouchID id; /// \brief The X coordinate of the event. float x; @@ -441,38 +399,16 @@ struct GestureEvent /// \brief The number of fingers used in the gesture event. uint16 num_fingers; - - void dump( void ) const - { - NOM_DUMP( id ); - NOM_DUMP( dTheta ); - NOM_DUMP( dDist ); - NOM_DUMP( x ); - NOM_DUMP( y ); - NOM_DUMP( num_fingers ); - } }; /// \brief A structure containing information on a Drag 'N' Drop event. -/// -/// \remarks SDL_DROPFILE. struct DragDropEvent { /// \brief The path of the file dropped onto the nom::Window. const char* file_path; - - void dump( void ) const - { - if( file_path != nullptr ) - { - NOM_DUMP( file_path ); - } - } }; /// \brief A structure containing information on a text input event. -/// -/// \remarks SDL_TEXTINPUT. struct TextInputEvent { /// \brief The text input. @@ -480,12 +416,6 @@ struct TextInputEvent /// \brief The identifier of the window at the moment of the event. uint32 window_id; - - void dump( void ) const - { - NOM_DUMP( text ); - NOM_DUMP( window_id ); - } }; /// \brief A structure containing information on a text editing event. @@ -494,7 +424,7 @@ struct TextInputEvent struct TextEditingEvent { /// \brief The text being edited. - char text[SDL_TEXTEDITINGEVENT_TEXT_SIZE]; + char text[MAX_TEXT_EDITING_LENGTH]; /// \brief The location to begin editing from. int32 start; @@ -504,28 +434,11 @@ struct TextEditingEvent /// \brief The identifier of the window at the moment of the event. uint32 window_id; - - void dump( void ) const - { - NOM_DUMP( text ); - NOM_DUMP( start ); - NOM_DUMP( length ); - NOM_DUMP( window_id ); - } }; /// \brief A structure containing information on an user event. -/// -/// \remarks SDL_USEREVENT. struct UserEvent { - /// \brief Convenience getter for nom::EventCallback objects stored in the - /// data2 field. - EventCallback* get_callback( void ) const - { - return NOM_SCAST( EventCallback*, data2 ); - } - /// \brief User defined event code. int32 code; @@ -537,19 +450,79 @@ struct UserEvent /// \brief The identifier of the window at the moment of the event. uint32 window_id; - - void dump( void ) const - { - NOM_DUMP( code ); - NOM_DUMP( window_id ); - } }; /// \brief Event handling types. struct Event { + enum EventType: uint32 + { + FIRST_EVENT = 0, + QUIT_EVENT, + WINDOW_EVENT, + SYSWMEVENT, + KEY_PRESS, + KEY_RELEASE, + MOUSE_MOTION, + MOUSE_BUTTON_CLICK, + MOUSE_BUTTON_RELEASE, + MOUSE_WHEEL, + JOYSTICK_AXIS_MOTION, + JOYSTICK_BALL_MOTION, + JOYSTICK_HAT_MOTION, + JOYSTICK_BUTTON_PRESS, + JOYSTICK_BUTTON_RELEASE, + + /// \brief The device index of the joystick connection event. + JOYSTICK_ADDED, + + /// \brief The instance ID of the joystick removal event. + JOYSTICK_REMOVED, + + GAME_CONTROLLER_AXIS_MOTION, + + GAME_CONTROLLER_BUTTON_PRESS, + GAME_CONTROLLER_BUTTON_RELEASE, + + /// \brief The device index of the game controller connection event. + GAME_CONTROLLER_ADDED, + + /// \brief The instance ID of the game controller removal event. + GAME_CONTROLLER_REMOVED, + + /// \brief The instance ID of the game controller remapping event. + GAME_CONTROLLER_REMAPPED, + + FINGER_MOTION, + + FINGER_PRESS, + + FINGER_RELEASE, + + MULTI_FINGER_GESTURE, + DROP_FILE, + TEXT_INPUT, + TEXT_EDITING, + RENDER_TARGETS_RESET, + +// NOTE: Not available until the release of SDL 2.0.4 +#if 0 + RENDER_DEVICE_RESET, +#endif + USER_EVENT, + }; + + /// \brief The event type. + /// + /// \see nom::Event::EventType + uint32 type; + + /// \brief The recorded time at the moment of the event. + uint32 timestamp; + union { + QuitEvent quit; WindowEvent window; KeyboardEvent key; MouseMotionEvent motion; @@ -557,8 +530,11 @@ struct Event MouseWheelEvent wheel; JoystickButtonEvent jbutton; JoystickAxisEvent jaxis; - JoystickConnectedEvent jconnected; - JoystickDisconnectedEvent jdisconnected; + JoystickHatEvent jhat; + JoystickDeviceEvent jdevice; + GameControllerAxisEvent caxis; + GameControllerButtonEvent cbutton; + GameControllerDeviceEvent cdevice; FingerTouchEvent touch; GestureEvent gesture; DragDropEvent drop; @@ -566,22 +542,11 @@ struct Event TextEditingEvent edit; UserEvent user; }; - - /// \brief The event type. - /// - /// \todo Change to enumeration type? - uint32 type; - - /// \brief The recorded time at the moment of the event. - uint32 timestamp; - - void dump( void ) const - { - NOM_DUMP( type ); - NOM_DUMP( timestamp ); - } }; +typedef std::function event_callback; +typedef std::function event_filter; + } // namespace nom #endif // include guard defined diff --git a/include/nomlib/system/EventHandler.hpp b/include/nomlib/system/EventHandler.hpp index 56a72bc1..d8bab4cf 100644 --- a/include/nomlib/system/EventHandler.hpp +++ b/include/nomlib/system/EventHandler.hpp @@ -26,347 +26,167 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ -#ifndef NOMLIB_SDL2_SYSTEM_INPUT_HEADERS -#define NOMLIB_SDL2_SYSTEM_INPUT_HEADERS +#ifndef NOMLIB_SYSTEM_EVENT_HANDLER_HPP +#define NOMLIB_SYSTEM_EVENT_HANDLER_HPP -#include -#include -#include +#include +#include #include "nomlib/config.hpp" -#include "nomlib/system/SDL_helpers.hpp" -#include "nomlib/system/Joystick.hpp" #include "nomlib/system/Event.hpp" -// Enable debugging output of quit (end program execution) events. -// #define NOM_DEBUG_SDL2_QUIT_EVENT - -// Enable debugging output of user defined events. -// #define NOM_DEBUG_SDL2_USER_EVENT - -// Enable debugging output of window events. -// #define NOM_DEBUG_SDL2_WINDOW_INPUT - -// Enable debugging output of window focus events. -// #define NOM_DEBUG_SDL2_WINDOW_FOCUS_INPUT - -// Enable debugging output of key press & release events. -// #define NOM_DEBUG_SDL2_KEYBOARD_INPUT - -// Enable debugging output of mouse button events. -// #define NOM_DEBUG_SDL2_MOUSE_INPUT - -// Enable debugging output of mouse motion events. -// #define NOM_DEBUG_SDL2_MOUSE_MOTION_INPUT - -// Enable debugging output of joystick button events. -// #define NOM_DEBUG_SDL2_JOYSTICK_BUTTON_INPUT - -// Enable debugging output of joystick axis events. -// #define NOM_DEBUG_SDL2_JOYSTICK_AXIS_INPUT - -// Enable debugging output of joystick addition & removal events. -// #define NOM_DEBUG_SDL2_JOYSTICK_EVENT - -// Enable debugging output of touch motion events. -// #define NOM_DEBUG_SDL2_TOUCH_MOTION_EVENT - -// Enable debugging output of finger touch events. -// #define NOM_DEBUG_SDL2_TOUCH_EVENT - -// Enable debugging output of multiple finger gesture events. -// #define NOM_DEBUG_SDL2_GESTURE_EVENT - -// Enable debugging output of Drag 'N' Drop events. -#define NOM_DEBUG_SDL2_DRAG_DROP_INPUT - -// Enable debugging output of text input events. -// #define NOM_DEBUG_SDL2_TEXT_INPUT_EVENT - -// Enable debugging output of text editing events. -// #define NOM_DEBUG_SDL2_TEXT_EDIT_EVENT +// Forward declarations (third-party) +union SDL_Event; namespace nom { -/// \brief High-level events handling. +// Forward declarations +struct event_watcher; +class JoystickEventHandler; +class GameControllerEventHandler; + +/// \brief Event handling abstraction class EventHandler { public: - /// \brief Default constructor. - /// - /// \remarks The joystick subsystem is initialized here. - EventHandler( void ); + typedef EventHandler self_type; - /// \brief Destructor. - /// - /// \remarks The joystick subsystem is shutdown within nom::SDLApp. - virtual ~EventHandler( void ); + enum JoystickHandlerType + { + NO_EVENT_HANDLER = 0, + SDL_JOYSTICK_EVENT_HANDLER, + GAME_CONTROLLER_EVENT_HANDLER, + }; - /// \brief The high-level events handler. - /// - /// \remarks This method is required when overloading the event handlers - /// provided (via inheritance) from this class. - void process_event( const Event& ev ); + EventHandler(); - /// \brief Enumerate the available events from the high-level events - /// subsystem (queue). - /// - /// \param ev The nom::Event structure used to place the top (most current) - /// event into (if any). - /// - /// \note This method must still be called even when using the optional - /// event handlers provided by this class. - bool poll_event( Event& ev ); + ~EventHandler(); - // virtual bool poll_event( SDL_Event* ev ); + /// \brief Disabled copy constructor. + EventHandler(const EventHandler& rhs) = delete; - protected: - /// \brief The application level event generated by either the user or API - /// requesting that the program execution be shutdown. - /// - /// \note This event is necessary for Command + Q functionality under - /// Mac OS X. - /// - /// \remarks type = 256 - virtual void on_app_quit( const Event& ev ); + /// \brief Disabled copy assignment operator. + EventHandler& operator =(const EventHandler& rhs) = delete; - /// \brief The window event generated once the nom::RenderWindow instance - /// has been shown. - /// - /// \remarks ev.window.event = 1 + /// \brief Get the total number of enqueued events. + nom::size_type num_events() const; - virtual void on_window_shown( const Event& ev ); + /// \brief Get the total number of event watchers. + nom::size_type num_event_watchers() const; - /// \brief The window event generated once the nom::RenderWindow instance - /// has been hidden. + /// \brief Get a non-owned pointer to the joystick device event handler. /// - /// \remarks ev.window.event = 2 - virtual void on_window_hidden( const Event& ev ); - - /// \brief The window event generated once the nom::RenderWindow instance - /// has been exposed and should be redrawn. + /// \remarks This pointer is invalid until an explicit call has been made + /// to nom::EventHandler::enable_joystick_polling. /// - /// \remarks ev.window.event = 3 - virtual void on_window_exposed( const Event& ev ); + /// \see nom::EventHandler::joystick_event_type + JoystickEventHandler* joystick_event_handler() const; - /// \brief The window event generated once the nom::RenderWindow instance - /// has been moved. + /// \brief Get a non-owned pointer to the game controller device event + /// handler. /// - /// \param ev The resulting X & Y coordinates of the moved window is - /// assigned in ev.window.data1 and ev.window.data2, respectively. + /// \remarks This pointer is invalid until an explicit call has been made + /// to nom::EventHandler::enable_game_controller_polling. /// - /// \remarks ev.window.event = 4 - virtual void on_window_moved( const Event& ev ); + /// \see nom::EventHandler::joystick_event_type + GameControllerEventHandler* game_controller_event_handler() const; - /// \brief The window event generated once the nom::RenderWindow instance - /// has been resized. - /// - /// \param ev The resulting width & height coordinates of the resized - /// nom::RenderWindow instance is assigned in ev.window.data1 and - /// ev.window.data2, respectively. - /// - /// \remarks This event is always preceded by SDL_WINDOWEVENT_SIZE_CHANGED. - /// - /// \remarks ev.window.event = 5 - virtual void on_window_resized( const Event& ev ); + JoystickHandlerType joystick_event_type() const; - /// \brief The window event generated after the nom::RenderWindow instance - /// size -- width & height (in pixels) -- has been changed, either as a - /// result of an API call, through the system (window manager?) or by the - /// end-user. - /// - /// \param ev The resulting width & height coordinates of the resized - /// nom::RenderWindow instance is assigned in ev.window.data1 and - /// ev.window.data2, respectively. - /// - /// \remarks This event is followed by SDL_WINDOWEVENT_RESIZED if the size - /// was changed by an external event, such as the user or the window - /// manager. + /// \brief Initialize joystick events polling. /// - /// \remarks ev.window.event = 6 - virtual void on_window_size_changed( const Event& ev ); + /// \see nom::JoystickEventHandler + bool enable_joystick_polling(); - /// \brief The window event generated after the nom::RenderWindow instance - /// has been minimized. + /// \brief Initialize game controller events polling. /// - /// \remarks ev.window.event = 7 - virtual void on_window_minimized( const Event& ev ); + /// \see nom::GameControllerEventHandler + bool enable_game_controller_polling(); - /// \brief The window event generated after the nom::RenderWindow instance - /// has been maximized. - /// - /// \remarks ev.window.event = 8 - virtual void on_window_maximized( const Event& ev ); - - /// \brief The window event generated after the nom::RenderWindow instance - /// has been restored to normal size and position. - /// - /// \remarks ev.window.event = 9 - virtual void on_window_restored( const Event& ev ); + /// \brief Shutdown the joystick events polling subsystem. + void disable_joystick_polling(); - /// \brief The window event generated once the nom::RenderWindow instance - /// has gained mouse focus. - /// - /// \remarks ev.window.event = 10 - virtual void on_window_mouse_focus( const Event& ev ); + /// \brief Shutdown the game controller events polling subsystem. + void disable_game_controller_polling(); - /// \brief The window event generated once the nom::RenderWindow instance - /// has lost mouse focus. + /// \brief Enumerate the available events. /// - /// \remarks ev.window.event = 11 - virtual void on_window_mouse_focus_lost( const Event& ev ); + /// \param ev The nom::Event to use for the retrieved event. + bool poll_event(Event& ev); - /// \brief The window event generated once the nom::RenderWindow instance - /// has gained keyboard focus. + /// \brief Enqueue an event. /// - /// \remarks ev.window.event = 12 - virtual void on_window_keyboard_focus( const Event& ev ); + /// \remarks This can be used to simulate synthetic input events. + void push_event(const Event& ev); - /// \brief The window event generated once the nom::RenderWindow instance - /// has lost keyboard focus. + /// \brief Clear an event from the queue. /// - /// \remarks ev.window.event = 13 - virtual void on_window_keyboard_focus_lost( const Event& ev ); + /// \param The type of event to remove. + void flush_event(Event::EventType type); - /// \brief The window event generated (by from the window manager) - /// requesting that the nom::RenderWindow instance be closed. + /// \brief Clear events from the queue. /// - /// \note ev.window.event = 14 - virtual void on_window_close( const Event& ev ); + /// \param The type of event to remove. + void flush_events(Event::EventType type); - /// \brief The keyboard event generated by a key press. - /// - /// \remarks type = 768 - virtual void on_key_down( const Event& ev ); + /// \brief Clear the events queue. + void flush_events(); - /// \brief The keyboard event generated by a key release. + /// \brief Add an event listener. /// - /// \remarks type = 769 - virtual void on_key_up( const Event& ev ); - - virtual void on_mouse_motion( const Event& ev ); + /// \param data User-defined data; it is your responsibility to free this + /// data pointer! + void append_event_watch(const event_filter& filter, void* data); - virtual void on_mouse_left_button_down( const Event& ev ); - virtual void on_mouse_middle_button_down( const Event& ev ); - virtual void on_mouse_right_button_down( const Event& ev ); - virtual void on_mouse_button_four_down( const Event& ev ); - virtual void on_mouse_button_five_down( const Event& ev ); + /// \brief Erase an event listener. + void remove_event_watch(const event_filter& filter); - virtual void on_mouse_left_button_up( const Event& ev ); - virtual void on_mouse_middle_button_up( const Event& ev ); - virtual void on_mouse_right_button_up( const Event& ev ); - virtual void on_mouse_button_four_up( const Event& ev ); - virtual void on_mouse_button_five_up( const Event& ev ); + /// \brief Erase all event watchers. + void remove_event_watchers(); - /// \brief Handler for mouse wheel events. - /// - /// \remarks Upward wheel motion (scroll forward) generates a positive y - /// value and downward wheel motion (scroll backward) generates a negative - /// Y value. - /// Wheel motion to the left generates a negative X value and motion to - /// the right generates a positive X value. - /// - /// \note The end-user's platform may invert the wheel values documented - /// here (i.e.: Mac OS X's "Scroll direction: natural" Mouse preference). - /// - /// \code - /// y > 0 = Up - /// y < 0 = Down - /// x > 0 = Left - /// x < 0 = Right - /// \endcode - /// - /// \todo Verify documentation notes regarding left and right wheel axis - /// values mapping (it's presently only been checked with virtual / fake - /// hardware). - virtual void on_mouse_wheel( const Event& ev ); - - virtual void on_joy_axis( const Event& ev ); - - virtual void on_joy_button_down( const Event& ev ); - virtual void on_joy_button_up( const Event& ev ); + private: + bool pop_event(Event& ev); - virtual void on_joystick_connected( const Event& ev ); - virtual void on_joystick_disconnected( const Event& ev ); + /// \brief Enumerate the available events from the underlying platform. + void process_events(); - virtual void on_touch_motion( const Event& ev ); - virtual void on_touch_down( const Event& ev ); - virtual void on_touch_up( const Event& ev ); + /// \brief Enumerate the available events from the underlying platform. + void process_event(const SDL_Event* ev); - /// \brief The finger touch event generated when a multiple finger touch - /// gesture event is received. - virtual void on_gesture( const Event& ev ); + void process_joystick_event(const SDL_Event* ev); + void process_game_controller_event(const SDL_Event* ev); - /// Drag 'N' Drop events - /// - /// \remarks To enable drag and drop events on Mac OS X, you must add the - /// appropriate keys in your application bundle's Info.plist, like so: - /// - /// CFBundleDocumentTypes - /// - /// - /// CFBundleTypeRole - /// Editor - /// CFBundleTypeName - /// TTcards - /// CFBundleTypeExtensions - /// - /// json - /// - /// CFBundleTypeIconFile - /// TTcards - /// - /// + /// \brief Enqueued events. /// - /// \note The SDL2 documentation stated for CFBundleMIMETypes did not work - /// for me. - virtual void on_drag_drop( const Event& ev ); + /// \see nom::EventHandler::process_event + std::deque events_; - /// \brief The text input event. - virtual void on_text_input( const Event& ev ); + std::vector> event_watchers_; - /// \brief Text editing event. - /// - /// \todo Finish implementation. - virtual void on_text_edit( const Event& ev ); + /// \brief The maximum number of events processed per queue cycle -- i.e.: + /// one frame of the game's update loop. + nom::size_type max_events_count_ = 0; - /// \brief The event handler for user-defined events. - virtual void on_user_event( const Event& ev ); + void* joystick_event_handler_ = nullptr; + JoystickHandlerType joystick_event_type_ = NO_EVENT_HANDLER; +}; - /// \brief Remove the top event from the high-level events queue. - bool pop_event( Event& ev ); +Event create_key_press(int32 sym, uint16 mod, uint8 repeat); +Event create_key_release(int32 sym, uint16 mod, uint8 repeat); +Event create_mouse_button_click(uint8 button, uint8 clicks, uint32 window_id); +Event create_mouse_button_release(uint8 button, uint8 clicks, uint32 window_id); - /// \brief Insert an event into the high-level events queue. - /// - /// \remarks This can be used to simulate input events; i.e.: key press, - /// mouse click, ... - void push_event( const Event& ev ); +Event create_joystick_button_press(JoystickID id, uint8 button); +Event create_joystick_button_release(JoystickID id, uint8 button); - private: - /// \brief Enumerate the available events from the underlying events - /// subsystem (SDL_Event). - /// - /// \remarks This is where the wrapping of SDL_Event(s) onto our high-level - /// events queue happens. - void process_event( const SDL_Event* ev ); +Event create_joystick_hat_motion(JoystickID id, uint8 hat, uint8 value); - /// \brief Enumerate the available events from the underlying events - /// subsystem (SDL_Event). - void process_events( void ); +Event create_game_controller_button_press(JoystickID id, uint8 button); +Event create_game_controller_button_release(JoystickID id, uint8 button); - /// \brief The high-level queue of available events. - /// - /// - /// \remarks Decouple our high-level events handling system from this class? - /// - /// \note This queue wraps the underlying events subsystem (SDL_Event) and - /// should always contain the same top event as a polled SDL_Event union - /// does, less and except any events omitted from processing within the - /// nom::EventHandler::process_event method. - std::queue events_; +Event create_user_event(int32 code, void* data1, void* data2, uint32 window_id); - Joystick joystick; -}; +Event create_quit_event(void* data1, void* data2); } // namespace nom @@ -377,15 +197,3 @@ class EventHandler /// /// [DESCRIPTION STUB] /// -/// See also -/// -/// nom::SDLApp, -/// examples/app/app.cpp, -/// Resources/SharedSupport/ExamplesTemplate.cpp -/// -/// References -/// -/// 1. http://www.sdltutorials.com/sdl-app-states -/// -/// \todo Relocate the debugging preprocessors & code to the events example. -/// diff --git a/include/nomlib/system/File.hpp b/include/nomlib/system/File.hpp index 680c7373..1d88ad27 100644 --- a/include/nomlib/system/File.hpp +++ b/include/nomlib/system/File.hpp @@ -29,15 +29,13 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef NOMLIB_FILE_HEADERS #define NOMLIB_FILE_HEADERS -#include #include -#include -#include +#include #include "nomlib/config.hpp" #include "nomlib/system/IFile.hpp" -#if defined ( NOM_PLATFORM_OSX ) || defined ( NOM_PLATFORM_LINUX ) +#if defined ( NOM_PLATFORM_OSX ) || defined ( NOM_PLATFORM_POSIX ) #include "nomlib/system/unix/UnixFile.hpp" @@ -49,67 +47,73 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. namespace nom { +// Forward declarations +class IFile; + /// \brief Platform-agnostic file system interface class File { public: - File ( void ); - ~File ( void ); + File(); + ~File(); /// Re-implements nom::IFile::extension - const std::string extension ( const std::string& file ); + std::string extension(const std::string& file) const; /// Re-implements nom::IFile::size - int32 size ( const std::string& file_path ); + int32 size(const std::string& file_path) const; /// \brief Test for the existence of a directory. - bool is_dir( const std::string& file_path ); + bool is_dir(const std::string& file_path) const; - bool is_file( const std::string& file_path ); + bool is_file(const std::string& file_path) const; /// Re-implements nom::IFile::exists - bool exists( const std::string& file_path ); + bool exists(const std::string& file_path) const; /// \brief Platform-specific implementation of IFile::path. /// /// \see UnixFile::path, WinFile::path. - const std::string path( const std::string& dir_path ); + std::string path(const std::string& dir_path) const; /// Re-implements nom::IFile::currentPath - std::string currentPath( void ) const; + std::string currentPath() const; /// Re-implements nom::IFile::set_path - bool set_path ( const std::string& path ); + bool set_path(const std::string& path) const; /// Re-implements nom::IFile::basename - const std::string basename ( const std::string& filename ); + std::string basename(const std::string& filename) const; - std::vector read_dir( const std::string& dir_path ); + std::vector read_dir(const std::string& dir_path) const; - const std::string resource_path( const std::string& identifier = "\0" ); + std::string resource_path(const std::string& identifier = "\0") const; - const std::string user_documents_path( void ); + std::string user_documents_path() const; - const std::string user_app_support_path( void ); + std::string user_app_support_path() const; - const std::string user_home_path( void ); + std::string user_home_path() const; - const std::string system_path( void ); + std::string system_path() const; /// \see UnixFile::mkdir, WinFile::mkdir. - bool mkdir( const std::string& path ); + bool mkdir(const std::string& path) const; /// \see UnixFile::recursive_mkdir, WinFile::recursive_mkdir. - bool recursive_mkdir( const std::string& path ); + bool recursive_mkdir(const std::string& path) const; /// \see UnixFile::rmdir, WinFile::rmdir. - bool rmdir( const std::string& path ); + bool rmdir(const std::string& path) const; /// \see UnixFile::mkfile, WinFile::mkfile. - bool mkfile( const std::string& path ); + bool mkfile(const std::string& path) const; /// \see UnixFile::env, WinFile::env. - std::string env( const std::string& path ); + std::string env(const std::string& path) const; + + /// \see UnixFile::num_files, WinFile::num_files + nom::size_type num_files(const std::string& path) const; private: std::unique_ptr file; diff --git a/include/nomlib/system/GameController.hpp b/include/nomlib/system/GameController.hpp new file mode 100644 index 00000000..e43735e4 --- /dev/null +++ b/include/nomlib/system/GameController.hpp @@ -0,0 +1,229 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#ifndef NOMLIB_SYSTEM_GAME_CONTROLLER_HPP +#define NOMLIB_SYSTEM_GAME_CONTROLLER_HPP + +#include "nomlib/config.hpp" +#include "nomlib/system/Joystick.hpp" + +// Forward declarations (third-party) +typedef struct _SDL_Joystick SDL_Joystick; +typedef struct _SDL_GameController SDL_GameController; + +namespace nom { + +/// \brief Initialize the joystick and game controller subsystems. +/// +/// \returns Boolean TRUE if the joystick and game controller subsystems was +/// successfully initialized, and boolean FALSE if the joystick and game +/// controller subsystems was **not** successfully initialized. +bool init_game_controller_subsystem(); + +/// \brief Shutdown the joystick and game controller subsystems. +void shutdown_game_controller_subsystem(); + +/// \brief Custom deleter for SDL_GameController pointers. +void GameControllerDeleter(SDL_GameController* dev); + +/// \brief SDL2 game controller interface +class GameController +{ + public: + /// \brief Game controller axis definitions. + enum Axis: int8 + { + AXIS_INVALID = -1, + AXIS_LEFT_X, + AXIS_LEFT_Y, + AXIS_RIGHT_X, + AXIS_RIGHT_Y, + AXIS_TRIGGER_LEFT, + AXIS_TRIGGER_RIGHT, + AXIS_MAX, + }; + + /// \brief Game controller button definitions. + enum Button: int8 + { + BUTTON_INVALID = -1, + BUTTON_A, + BUTTON_B, + BUTTON_X, + BUTTON_Y, + BUTTON_BACK, + BUTTON_GUIDE, + BUTTON_START, + BUTTON_LEFT_STICK, + BUTTON_RIGHT_STICK, + BUTTON_LEFT_SHOULDER, + BUTTON_RIGHT_SHOULDER, + BUTTON_DPAD_UP, + BUTTON_DPAD_DOWN, + BUTTON_DPAD_LEFT, + BUTTON_DPAD_RIGHT, + BUTTON_MAX = SDL_CONTROLLER_BUTTON_MAX, + }; + + // NOTE: This is a workaround to prevent a double-free bug that + // occasionally happens when the joystick device is closed more than once. + // This issue can be reliably reproduced by adding a second + // SDL_GameControllerClose call to test/testgamecontroller.c from the SDL + // source repository. + static bool device_closed_; + + typedef GameController self_type; + + GameController(); + + ~GameController(); + + /// \brief Get the underlying implementation pointer. + /// + /// \returns A non-owned pointer to the underlying implementation. + SDL_Joystick* device() const; + + /// \brief Get the status of the joystick. + /// + /// \returns Boolean TRUE if the joystick has been opened, and boolean + /// FALSE if it has not been opened. + bool attached() const; + + /// \brief Get the instance ID of an opened game controller. + /// + /// \returns The device instance ID for the game controller device on + /// success, and a negative error code on failure. + JoystickID device_id() const; + + /// \brief Get the implementation-dependent name of the joystick. + /// + /// \returns The name of the joystick on success, and a null-terminated + /// string on failure, such as an invalid device state. + std::string name() const; + + /// \brief Open a joystick for use. + /// + /// \param device_index The device index of the joystick as recognized by + /// SDL. + /// + /// \remarks The device index is **not** the same as the instance ID of the + /// joystick, which is used to identify the joystick in future events. + /// + /// \see Joystick::num_joysticks + bool open(JoystickIndex device_index); + + /// \brief Close an opened joystick. + /// + /// \remarks This effectively frees the resources of the joystick. + void close(); + + /// \brief Get the implementation-dependent name of the joystick. + /// + /// \param device_index The index of the joystick device, as recognized by + /// SDL. + /// + /// \returns The name of the indexed joystick on success, and a zero-length + /// string on failure, such as when no name for the device index can be found. + /// + /// \remarks This function can be called before any joysticks are accessed. + /// + /// \see Joystick::num_joysticks + static std::string name(JoystickIndex device_index); + + /// \brief Toggle the game controller's event polling state. + /// + /// \param state One of the following: SDL_QUERY, SDL_IGNORE or SDL_ENABLE. + /// + /// \returns One (1) if enabled, zero (0) if disabled or a negative error + /// code on failure. If the state was SDL_QUERY, then the current state is + /// returned, otherwise the new processing state is returned. + /// + /// \remarks If game controller events are disabled, you must call + /// GameController::update and manually check the state of the joystick + /// when event data is needed. + /// + /// \note This does not disable the underlying joystick events. + /// + /// \see Joystick::set_event_state + static int set_event_state(int state); + + /// \brief Update the current state of the opened game controllers. + /// + /// \remarks This method is automatically called by the internal event loop + /// of SDL if any joystick events are enabled. + static void update(); + + std::string mapping_string(); + + /// \remarks This function can be called before any joysticks are accessed. + static std::string mapping_string(JoystickGUID guid); + + /// \todo Implement this function + static int load_mapping_memory(const char* buffer, int buffer_size); + + /// \fixme This function call leaks memory from within SDL! + static int load_mapping_file(const std::string& filename); + + static int load_mapping_string(const std::string& mapping); + + /// \brief Get the instance ID of a joystick device. + /// + /// \param device_index The index of the joystick device, as recognized by + /// SDL. + /// + /// \returns The device instance ID for the joystick device on success, and + /// a negative error code on failure. + /// + /// \remarks This function can be called before any joysticks are accessed. + /// + /// \see Joystick::num_joysticks + static JoystickID device_id(JoystickIndex device_index); + + /// \brief Check to see if the joystick is supported by SDL's game + /// controller interface. + static bool compatible_joystick(JoystickIndex device_index); + + private: + typedef std::unique_ptr + joystick_dev; + + joystick_dev device_; +}; + +std::unique_ptr make_unique_game_controller(); +std::shared_ptr make_shared_game_controller(); + +} // namespace nom + +#endif // include guard defined + +/// \class nom::GameController +/// \ingroup system +/// +/// [DESCRIPTION STUB] +/// diff --git a/include/nomlib/system/GameControllerEventHandler.hpp b/include/nomlib/system/GameControllerEventHandler.hpp new file mode 100644 index 00000000..c7483e07 --- /dev/null +++ b/include/nomlib/system/GameControllerEventHandler.hpp @@ -0,0 +1,95 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#ifndef NOMLIB_SYSTEM_GAME_CONTROLLER_EVENT_HANDLER_HPP +#define NOMLIB_SYSTEM_GAME_CONTROLLER_EVENT_HANDLER_HPP + +#include +#include + +#include "nomlib/config.hpp" +#include "nomlib/system/GameController.hpp" + +namespace nom { + +// Forward declarations +class GameController; + +/// \brief Internal management of hot-pluggable joystick devices handling +class GameControllerEventHandler +{ + public: + GameControllerEventHandler(); + ~GameControllerEventHandler(); + + /// \brief Get the number of accessible joysticks. + nom::size_type num_joysticks() const; + + // Non-owned pointer + GameController* joystick(JoystickID dev_id) const; + + /// \brief Query for the existence of an attached joystick. + /// + /// \returns Boolean TRUE when the joystick exists, and boolean FALSE when + /// the joystick does **not** exist. + bool joystick_exists(JoystickID dev_id) const; + + /// \brief Append a game controller to the active devices pool. + /// + /// \returns A non-owned pointer to the game controller device on success, + /// or NULL on failure, such as when the device can not be opened. + GameController* add_joystick(JoystickIndex device_index); + + /// \brief Remove a connected joystick from the joystick event pool. + /// + /// \returns Boolean TRUE when the joystick exists, and boolean FALSE when + /// the joystick does **not** exist. + bool remove_joystick(JoystickID dev_id); + + /// \brief Remove all joystick connection IDs from the joystick event pool. + /// + /// \returns void + void remove_joysticks(); + + private: + typedef std::map> joysticks; + + /// \brief Active game controllers event pool. + /// + /// \remarks This is a mapping of the current joysticks that are available + /// to the end-user for use. Without this mapping, the end-user would be + /// responsible for the management of each joystick's life-time; we must + /// hold onto the device reference at the time of the SDL event, else we + /// aren't able to use it beyond that frame -- the same is true of removal + /// events. + joysticks joysticks_; +}; + +} // namespace nom + +#endif // include guard defined diff --git a/include/nomlib/system/HighResolutionTimer.hpp b/include/nomlib/system/HighResolutionTimer.hpp new file mode 100644 index 00000000..930cdb4f --- /dev/null +++ b/include/nomlib/system/HighResolutionTimer.hpp @@ -0,0 +1,104 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#ifndef NOMLIB_SYSTEM_HIGH_RESOLUTION_TIMER_HPP +#define NOMLIB_SYSTEM_HIGH_RESOLUTION_TIMER_HPP + +#include "nomlib/config.hpp" + +namespace nom { + +/// \brief High-granularity time keeper +class HighResolutionTimer +{ + public: + /// \brief Default constructor. + HighResolutionTimer(); + + /// \brief Destructor. + ~HighResolutionTimer(); + + bool started() const; + bool paused() const; + + uint64 ticks() const; + + std::string ticks_str() const; + + void start(); + void stop(); + + /// \brief Alias for start. + void restart(); + + void pause(); + void unpause(); + + /// \brief Get the elapsed frequency count per second of the high + /// resolution counter. + static + real64 elapsed_ticks(uint64 start_hires_ticks, uint64 end_hires_ticks); + + /// \brief Convert a high resolution timing value to milliseconds. + /// + /// \param hires_ticks A high resolution timing value to convert. + /// + /// \see ::ticks, nom::hires_ticks + static real64 to_milliseconds(uint64 hires_ticks); + + /// \brief Convert a high resolution timing value to seconds. + /// + /// \param hires_ticks A high resolution timing value to convert. + /// + /// \see ::ticks, nom::hires_ticks + real64 to_seconds() const; + + /// \brief Convert a high resolution timing value to seconds. + /// + /// \param hires_ticks A high resolution timing value to convert. + /// + /// \see ::ticks, nom::hires_ticks + static real64 to_seconds(uint64 hires_ticks); + + private: + /// \brief Activity status of the timer. + bool paused_; + + /// \brief Running status of the timer. + bool started_; + + /// \brief The recorded timestamp at the time of starting the timer. + uint64 elapsed_ticks_; + + /// \brief The recorded timestamp at the time of pausing the timer. + uint64 paused_ticks_; +}; + +} // namespace nom + +#endif // include guard defined diff --git a/include/nomlib/system/IFile.hpp b/include/nomlib/system/IFile.hpp index 824e726a..c044f8a6 100644 --- a/include/nomlib/system/IFile.hpp +++ b/include/nomlib/system/IFile.hpp @@ -104,6 +104,11 @@ class IFile virtual bool mkfile( const std::string& path ) = 0; virtual std::string env( const std::string& path ) = 0; + + /// \brief Get the total number of files in a directory. + /// + /// \params An absolute directory path. + virtual nom::size_type num_files(const std::string& path) = 0; }; diff --git a/include/nomlib/system/IState.hpp b/include/nomlib/system/IState.hpp index c1f54700..95fe71de 100644 --- a/include/nomlib/system/IState.hpp +++ b/include/nomlib/system/IState.hpp @@ -30,17 +30,17 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define NOMLIB_SYSTEM_ISTATE_HPP #include "nomlib/config.hpp" -#include "nomlib/system/EventHandler.hpp" namespace nom { // Forward declarations class RenderWindow; +struct Event; /// \brief Abstract interface for game states /// /// \remarks See also nom::StateMachine, nom::SDLApp -class IState: public EventHandler +class IState { public: typedef std::unique_ptr unique_ptr; @@ -120,19 +120,13 @@ class IState: public EventHandler /// /// \param ev The passed nom::Event object. /// - /// \remarks This method provides a means of control for event propagation - /// flow. - /// - /// \fixme This is currently required for GUI events processing in TTcards, - /// and probably can be handled better... - /// /// \returns This method should return Boolean TRUE when the nom::Event /// object has been processed (think: consumed) by the user-implemented /// method, and boolean FALSE when the nom::Event object has not been /// processed (think: consumed). The default implementation returns false. /// /// \see StateMachine::on_event - virtual bool on_event( const nom::Event& ev ); + virtual bool on_event(const nom::Event& ev); /// \brief User-defined implementation of the state's update logic. /// diff --git a/include/nomlib/system/InputMapper/InputAction.hpp b/include/nomlib/system/InputMapper/InputAction.hpp index 19368990..2a3754ca 100644 --- a/include/nomlib/system/InputMapper/InputAction.hpp +++ b/include/nomlib/system/InputMapper/InputAction.hpp @@ -30,8 +30,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define NOMLIB_SYSTEM_INPUT_MAPPER_INPUT_ACTION_HPP #include "nomlib/config.hpp" -#include "nomlib/system/EventCallback.hpp" #include "nomlib/system/Event.hpp" +#include "nomlib/system/GameController.hpp" namespace nom { @@ -41,8 +41,10 @@ namespace nom { class InputAction { public: - /// \brief Destructor. - virtual ~InputAction( void ); + /// \brief Default constructor; constructs an invalid action state. + InputAction(); + + virtual ~InputAction(); /// \brief Get the underlying event associated with the input action. /// @@ -51,51 +53,50 @@ class InputAction virtual const Event& event( void ) const; /// \brief Get the callback assigned to the input action. - const EventCallback& callback( void ) const; + const nom::event_callback& callback( void ) const; - void set_callback( const EventCallback& delegate ); + void set_callback(const nom::event_callback& delegate); /// \brief C++ functor; execute the callback assigned for the input action. - void operator() ( const Event& evt ) const; - - /// \brief Diagnostic output of the object state. - virtual void dump( void ) const; + void operator()(const Event& evt) const; - protected: /// \brief The event type and relevant input data; the criteria is used to /// match against user input. Event event_; private: /// \brief The delegate to call upon a successful action to event match. - EventCallback callback_; + nom::event_callback callback_; }; /// \brief A structure containing information on a keyboard action. struct KeyboardAction: public InputAction { - /// \brief Destructor. - virtual ~KeyboardAction( void ); + virtual ~KeyboardAction(); /// \brief Constructor for initializing an object to a valid action state. /// + /// \param state One of the nom::InputState enumeration values. + /// /// \remarks The key modifier is initialized to KMOD_NONE. - KeyboardAction( uint32 type, int32 sym ); + KeyboardAction(int32 sym, InputState state = InputState::PRESSED); /// \brief Constructor for initializing an object to a valid action state. /// /// \param mod An enumeration of key modifier masks; see also: the SDL2 wiki /// documentation page for [SDL_Keymod](https://wiki.libsdl.org/SDL_Keymod). - KeyboardAction( uint32 type, int32 sym, uint16 mod ); + /// + /// \param state One of the nom::InputState enumeration values. + KeyboardAction(int32 sym, uint16 mod, InputState state = InputState::PRESSED); /// \brief Constructor for initializing an object to a valid action state. /// /// \param mod An enumeration of key modifier masks; see also: the SDL2 wiki /// documentation page for [SDL_Keymod](https://wiki.libsdl.org/SDL_Keymod). - KeyboardAction( uint32 type, int32 sym, uint16 mod, uint8 repeat ); - - /// \brief Diagnostic output of the object state. - void dump( void ) const; + /// + /// \param state One of the nom::InputState enumeration values. + KeyboardAction( int32 sym, uint16 mod, uint8 repeat, + InputState state = InputState::PRESSED ); }; /// \brief A structure containing information on a mouse button action. @@ -105,104 +106,126 @@ struct KeyboardAction: public InputAction /// device). struct MouseButtonAction: public InputAction { - /// \brief Destructor. virtual ~MouseButtonAction(); /// \brief Constructor for initializing an object to a valid action state. /// - /// \param type One of the following event enumeration values: - /// SDL_MOUSEBUTTONDOWN or SDL_MOUSEBUTTONUP. - /// - /// \param button One of the following enumeration values: SDL_BUTTON_LEFT, - /// SDL_BUTTON_MIDDLE, SDL_BUTTON_RIGHT, SDL_BUTTON_X1 or SDL_BUTTON_X2. + /// \param button One of the nom::MouseButton enumeration values. + /// \param state One of the nom::InputState enumeration values. /// /// \remarks The number of mouse button clicks for this action will be /// initialized to one (a single mouse button click). - MouseButtonAction( uint32 type, uint8 button ); + MouseButtonAction(uint8 button, InputState state = InputState::PRESSED); /// \brief Constructor for initializing an object to a valid action state. /// - /// \param type One of the following event enumeration values: - /// SDL_MOUSEBUTTONDOWN or SDL_MOUSEBUTTONUP. - /// - /// \param button One of the following enumeration values: SDL_BUTTON_LEFT, - /// SDL_BUTTON_MIDDLE, SDL_BUTTON_RIGHT, SDL_BUTTON_X1 or SDL_BUTTON_X2. + /// \param button One of the nom::MouseButton enumeration values. + /// \param state One of the nom::InputState enumeration values. /// /// \param clicks The number of button clicks to react on; one (1) for /// a single-click, two (2) for a double-click, three (3) for a triple-click, /// and so on (hardware-dependent). - MouseButtonAction( uint32 type, uint8 button, uint8 clicks ); + MouseButtonAction( uint8 button, uint8 clicks, + InputState state = InputState::PRESSED ); +}; - /// \brief Diagnostic output of the object state. - void dump( void ) const; +enum MouseWheelDirection: uint8 +{ + MOUSE_WHEEL_INVALID = 0, + MOUSE_WHEEL_LEFT = 1, + MOUSE_WHEEL_RIGHT = 2, + MOUSE_WHEEL_UP = 4, + MOUSE_WHEEL_DOWN = 8, }; /// \brief A structure containing information on a mouse wheel action. struct MouseWheelAction: public InputAction { - /// \brief Left-right and up-down axis. - /// - /// \remarks Conceptually, a wheel action event has been modeled similarly to a - /// joystick axis -- note the axis field -- with the left-right axis being zero - /// (0) and the up-down axis being one (1). - enum: int - { - INVALID = -1, - AXIS_X = 0, - AXIS_Y = 1 - }; - - /// \brief Sensitivity ranges to action mapping. - enum: int - { - UP = 1, - DOWN = -1, - RIGHT = -1, - LEFT = 1 - }; - - /// \brief An enumeration of the invalid state(s). - enum: int - { - null = 0 - }; - - /// \brief Destructor. - virtual ~MouseWheelAction( void ); + virtual ~MouseWheelAction(); - /// \brief Constructor for initializing an object to a valid action state. - MouseWheelAction( uint32 type, uint8 axis, int32 value ); - - /// \brief Diagnostic output of the object state. - void dump( void ) const; + /// \brief Construct a mouse wheel action. + /// + /// \param dir One of the MouseWheelDirection enumeration values. + MouseWheelAction(MouseWheelDirection dir); }; /// \brief A structure containing information on a joystick button action. -/// -/// \todo Implement button state (SDL_PRESSED or SDL_RELEASED). struct JoystickButtonAction: public InputAction { - /// \brief Destructor. - virtual ~JoystickButtonAction( void ); + virtual ~JoystickButtonAction(); /// \brief Constructor for initializing an object to a valid action state. - JoystickButtonAction( SDL_JoystickID id, uint32 type, uint8 button ); - - /// \brief Diagnostic output of the object state. - void dump( void ) const; + /// + /// \param id The unique instance ID given to the joystick by the underlying + /// platform. + /// \param button One of the nom::Joystick::Button enumeration values. + /// \param state One of the nom::InputState enumeration values. + /// + /// \see Joystick::device_id + JoystickButtonAction(JoystickID id, uint8 button, InputState state); }; -/// \brief A structure containing information on a joystick axis motion action. +/// \brief A structure containing information on a joystick axis action. struct JoystickAxisAction: public InputAction { - /// \brief Destructor. - virtual ~JoystickAxisAction( void ); + virtual ~JoystickAxisAction(); /// \brief Constructor for initializing an object to a valid action state. - JoystickAxisAction( SDL_JoystickID id, uint32 type, uint8 axis, int16 value ); + /// + /// \param id The unique instance ID given to the joystick by the underlying + /// platform. + /// \param axis One of the nom::Joystick::Axis enumeration values. + /// + /// \see Joystick::device_id + JoystickAxisAction(JoystickID id, uint8 axis); +}; - /// \brief Diagnostic output of the object state. - void dump( void ) const; +/// \brief A structure containing information on a joystick POV hat action. +struct JoystickHatAction: public InputAction +{ + virtual ~JoystickHatAction(); + + /// \brief Constructor for initializing an object to a valid action state. + /// + /// \param id The unique instance ID given to the joystick by the underlying + /// platform. + /// \param hat One of the nom::Joystick::Hat enumeration values. + /// \param value One of the nom::Joystick::HatPosition enumeration values. + /// + /// \see Joystick::device_id + JoystickHatAction(JoystickID id, uint8 hat, uint8 value); +}; + +/// \brief Create a game controller button action. +struct GameControllerButtonAction: public InputAction +{ + virtual ~GameControllerButtonAction(); + + /// \brief Constructor for initializing an object to a valid action state. + /// + /// \param id The unique instance ID given to the joystick by the underlying + /// platform. + /// \param button One of the nom::GameController::Button enumeration values. + /// \param state One of the nom::InputState enumeration values. + /// + /// \see GameController::device_id + GameControllerButtonAction( JoystickID id, GameController::Button button, + InputState state = InputState::PRESSED ); +}; + +/// \brief Create a game controller axis action. +struct GameControllerAxisAction: public InputAction +{ + virtual ~GameControllerAxisAction(); + + /// \brief Constructor for initializing an object to a valid action state. + /// + /// \param id The unique instance ID given to the joystick by the underlying + /// platform. + /// \param axis One of the nom::GameController::Axis enumeration values. + /// + /// \see GameController::device_id + GameControllerAxisAction(JoystickID id, GameController::Axis axis); }; } // namespace nom diff --git a/include/nomlib/system/InputMapper/InputActionMapper.hpp b/include/nomlib/system/InputMapper/InputActionMapper.hpp index ef76b53a..989c489b 100644 --- a/include/nomlib/system/InputMapper/InputActionMapper.hpp +++ b/include/nomlib/system/InputMapper/InputActionMapper.hpp @@ -33,10 +33,13 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include "nomlib/config.hpp" -#include "nomlib/system/InputMapper/InputAction.hpp" +#include "nomlib/system/Event.hpp" namespace nom { +// Forward declarations +class InputAction; + /// \brief High-level API for associating names with input actions. /// /// \remarks See also, nom::InputAction. @@ -56,16 +59,15 @@ class InputActionMapper ~InputActionMapper( void ); /// \brief Insert an action mapping. - bool insert( const std::string& key, const InputAction& action, const EventCallback& callback ); + /// + /// \see nom::InputAction + bool + insert( const std::string& key, const InputAction& action, + const event_callback& callback ); /// \brief Remove an action mapping. bool erase( const std::string& key ); - /// \brief Diagnostic output. - /// - /// remarks Dumps only the active states. - void dump( void ) const; - private: /// \brief Obtain the input map's contents. /// @@ -79,8 +81,8 @@ class InputActionMapper ActionMap input_map_; }; -#define NOM_CONNECT_INPUT_MAPPING( obj, key, action, func, ... ) \ - ( obj.insert( key, action, nom::EventCallback( [&] ( const nom::Event& evt ) { func( __VA_ARGS__ ); } ) ) ) +#define NOM_CONNECT_INPUT_MAPPING(obj, key, action, func, ...) \ + ( obj.insert( key, action, nom::event_callback( [&](const nom::Event& evt) { func( __VA_ARGS__ ); } ) ) ) } // namespace nom diff --git a/include/nomlib/system/InputMapper/InputStateMapper.hpp b/include/nomlib/system/InputMapper/InputStateMapper.hpp index 46e45a56..88a6e85b 100644 --- a/include/nomlib/system/InputMapper/InputStateMapper.hpp +++ b/include/nomlib/system/InputMapper/InputStateMapper.hpp @@ -32,11 +32,15 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include "nomlib/config.hpp" -#include "nomlib/system/Event.hpp" #include "nomlib/system/InputMapper/InputActionMapper.hpp" namespace nom { +// Forward declarations +struct Event; +class EventHandler; +class InputAction; + struct InputActionState { bool active; @@ -78,7 +82,7 @@ class InputStateMapper /// \brief Remove a state. /// /// \param key The state string index to remove. - bool erase( const std::string& key ); + bool erase(const std::string& key); /// \brief Activate a state. /// @@ -105,17 +109,24 @@ class InputStateMapper /// \brief Diagnostic output. /// /// remarks Dumps only the active states. - void dump( void ); + void dump(); - /// \brief Event handler for triggering mapped action states. + /// \brief Install the event handler used by the interface. + /// + /// \remarks The application's events will not be processed until a call is + /// made to the event handler's ::poll_event method. /// - /// \remarks This method must be located within the main input loop. - void on_event( const Event& ev ); + /// \note The installed event handler must outlive the destruction of + /// this interface! + void set_event_handler(EventHandler& evt_handler); private: + /// \brief Internal event handler for triggering mapped action states. + void on_event(const Event& ev); + /// \brief Internal event handler for matching a keyboard action to a /// keyboard event. - bool on_key_press( const InputAction& mapping, const Event& ev ); + bool on_key_press(const InputAction& mapping, const Event& ev); /// \brief Internal event handler for matching a mouse button action to a /// mouse button event. @@ -127,13 +138,26 @@ class InputStateMapper /// \brief Internal event handler for matching a joystick button action to /// a joystick button event. - bool on_joystick_button( const InputAction& mapping, const Event& ev ); + bool on_joystick_button(const InputAction& mapping, const Event& ev); /// \brief Internal event handler for matching a joystick axis action to /// a joystick axis event. - /// - /// \todo Do not use; the implementation is incomplete! - bool on_joystick_axis( const InputAction& mapping, const Event& ev ); + bool on_joystick_axis(const InputAction& mapping, const Event& ev); + + bool on_joystick_hat(const InputAction& mapping, const Event& ev); + + /// \brief Internal event handler for matching a game controller button + /// action to an equivalent event. + bool + on_game_controller_button(const InputAction& mapping, const Event& ev); + + /// \brief Internal event handler for matching a game controller axis + /// action to an equivalent event. + bool + on_game_controller_axis(const InputAction& mapping, const Event& ev); + + // Non-owned pointer + EventHandler* event_handler_ = nullptr; InputStateMap states_; }; diff --git a/include/nomlib/system/Joystick.hpp b/include/nomlib/system/Joystick.hpp index bf73965c..a3dc17ae 100644 --- a/include/nomlib/system/Joystick.hpp +++ b/include/nomlib/system/Joystick.hpp @@ -26,104 +26,312 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ -#ifndef NOMLIB_SDL2_SYSTEM_JOYSTICK_HPP -#define NOMLIB_SDL2_SYSTEM_JOYSTICK_HPP +#ifndef NOMLIB_SYSTEM_JOYSTICK_HPP +#define NOMLIB_SYSTEM_JOYSTICK_HPP #include -#include #include -#include -#include #include "nomlib/config.hpp" -#include "nomlib/system/IJoystick.hpp" -#include "nomlib/system/SDLJoystick.hpp" + +// Forward declarations (third-party) +typedef struct _SDL_Joystick SDL_Joystick; namespace nom { -/// \brief SDL mapping to individual PS3 axes. -enum PSXAXES -{ - LEFTX = 0, - LEFTY = 1, - RIGHTX = 2, - RIGHTY = 3 -}; +typedef int32 JoystickIndex; -/// \brief SDL mapping to individual PS3 controller buttons. -enum PSXBUTTON +/// \note This is a forward declaration of SDL_JoystickID. +typedef int32 JoystickID; + +/// \brief the maximal data size of the JoystickGUID structure. +/// +/// \note This should always match the size of the data array member of +/// SDL_JoystickGUID. +const nom::size_type GUID_MAX_LENGTH = 16; + +/// \brief Internal representation of a stable, fixed identifier -- 128 bits +/// wide -- for a joystick device. This identifier is platform-dependent. +/// +/// \note This is a forward declaration of SDL_JoystickGUID. +struct JoystickGUID { - SELECT = 0, - JOYCLICKLEFT = 1, - JOYCLICKRIGHT = 2, - START = 3, - UP = 4, - RIGHT = 5, - DOWN = 6, - LEFT = 7, - L2 = 8, - R2 = 9, - L1 = 10, - R1 = 11, - TRIANGLE = 12, - CIRCLE = 13, - CROSS = 14, - SQUARE = 15, - PSBUTTON = 16 + uint8 data[GUID_MAX_LENGTH]; }; -/// \brief High-level joystick handling API. +/// \brief The default dead zone values for joysticks. +/// +/// \note NOTE: These values are sourced from XInput.h, documented at [MSDN: Getting Started With XInput](https://msdn.microsoft.com/en-us/library/windows/desktop/ee417001%28v=vs.85%29.aspx#dead_zone). +const int16 JOYSTICK_LEFT_THUMB_DEAD_ZONE = 7849; +const int16 JOYSTICK_RIGHT_THUMB_DEAD_ZONE = 8689; +const int16 JOYSTICK_TRIGGER_THRESHOLD = 30; + +/// \brief Initialize the joystick subsystem. +/// +/// \returns Boolean TRUE if the joystick subsystem was successfully +/// initialized, and boolean FALSE if the joystick subsystem was **not** +/// successfully initialized. +bool init_joystick_subsystem(); + +/// \brief Shutdown the joystick subsystem. +void shutdown_joystick_subsystem(); + +/// \brief Custom deleter for SDL_Joystick pointers. +void JoystickDeleter(SDL_Joystick* dev); + +/// \brief SDL2 joystick interface class Joystick { public: - typedef Joystick SelfType; - typedef int JoystickID; + /// \brief Joystick axis definitions. + enum Axis: uint8 + { + AXIS_ZERO = 0, + AXIS_ONE, + AXIS_TWO, + AXIS_THREE, + AXIS_FOUR, + AXIS_FIVE, + AXIS_SIX, + AXIS_SEVEN, + AXIS_EIGHT, + AXIS_MAX, + }; + + /// \brief Joystick button definitions. + enum Button: uint8 + { + BUTTON_ZERO = 0, + BUTTON_ONE, + BUTTON_TWO, + BUTTON_THREE, + BUTTON_FOUR, + BUTTON_FIVE, + BUTTON_SIX, + BUTTON_SEVEN, + BUTTON_EIGHT, + BUTTON_NINE, + BUTTON_TEN, + BUTTON_ELEVEN, + BUTTON_TWELVE, + BUTTON_FOURTEEN, + BUTTON_FIFTEEN, + BUTTON_SIXTEEN, + BUTTON_MAX, + }; + + /// \brief Joystick hat definitions. + enum Hat: uint8 + { + HAT_ZERO = 0, + HAT_ONE, + HAT_TWO, + HAT_THREE, + HAT_FOUR, + HAT_MAX, + }; + + /// \brief Joystick hat position definitions. + enum HatPosition: uint8 + { + HAT_CENTERED = 0x00, + HAT_UP = 0x01, + HAT_RIGHT = 0x02, + HAT_DOWN = 0x04, + HAT_LEFT = 0x08, + HAT_RIGHTUP = (HAT_RIGHT | HAT_UP), + HAT_RIGHTDOWN = (HAT_RIGHT | HAT_DOWN), + HAT_LEFTUP = (HAT_LEFT | HAT_UP), + HAT_LEFTDOWN = (HAT_LEFT | HAT_DOWN), + }; + + typedef Joystick self_type; + + Joystick(); + + ~Joystick(); + + /// \brief Get the underlying implementation pointer. + /// + /// \returns A non-owned pointer to the underlying implementation. + SDL_Joystick* device() const; + + /// \brief Get the status of the joystick. + /// + /// \returns Boolean TRUE if the joystick has been opened, and boolean + /// FALSE if it has not been opened. + bool attached() const; + + /// \brief Get the instance ID of an opened joystick. + /// + /// \returns The device instance ID for the joystick device on success, and + /// a negative error code on failure. + JoystickID device_id() const; + + /// \brief Get the implementation-dependent name of the joystick. + /// + /// \returns The name of the joystick on success, and a null-terminated + /// string on failure, such as an invalid device state. + std::string name() const; + + /// \brief Open a joystick for use. + /// + /// \param device_index The device index of the joystick as recognized by + /// SDL. + /// + /// \remarks The device index is **not** the same as the instance ID of the + /// joystick, which is used to identify the joystick in future events. + /// + /// \see Joystick::num_joysticks + bool open(JoystickIndex device_index); + + /// \brief Close an opened joystick. + /// + /// \remarks This effectively frees the resources of the joystick. + void close(); + + /// \brief Get the number of axes for this joystick. + /// + /// \returns The number of axes on success, and a negative error code on + /// failure. + /// + /// \remarks This function requires an opened joystick. + int num_axes(); - typedef std::pair Pair; - typedef std::vector JoystickNames; - typedef std::map Joysticks; + /// \brief Get the number of track balls for this joystick. + /// + /// \returns The number of track balls on success, and a negative error + /// code on failure. + /// + /// \remarks This function requires an opened joystick. + int num_track_balls(); - /// \brief Default constructor. - Joystick( void ); + /// \brief Get the number of buttons for this joystick. + /// + /// \returns The number of buttons on success, and a negative error code on + /// failure. + /// + /// \remarks This function requires an opened joystick. + int num_buttons(); - /// \brief Destructor. + /// \brief Get the number of POV hats for this joystick. /// - /// \remarks Close opened joystick devices and shutdown the joystick - /// subsystem. + /// \returns The number of POV hats on success, and a negative error code + /// on failure. /// - /// \note ~~The joystick subsystem shutdown is presently handled in the - /// destruction of SDLApp~~. - ~Joystick( void ); + /// \remarks This function requires an opened joystick. + int num_hats(); - /// \brief Initialize the underlying joystick subsystem. - bool initialize( void ); + /// \brief Toggle the joystick event polling state. + /// + /// \param state One of the following: SDL_QUERY, SDL_IGNORE or SDL_ENABLE. + /// + /// \returns One (1) if enabled, zero (0) if disabled and a negative error + /// code on failure. If the state was SDL_QUERY, then the current state is + /// returned, otherwise the new processing state is returned. + /// + /// \remarks If joystick events are disabled, you must call + /// Joystick::update and manually check the state of the joystick when + /// event data is needed. + /// + /// \note Calling this method may delete all events currently in SDL's + /// event queue. + static int set_event_state(int state); - /// \brief Shutdown the joystick subsystem. - void shutdown( void ); + /// \brief Update the current state of the opened joysticks. + /// + /// \remarks This method is automatically called by the internal event loop + /// of SDL if any joystick events are enabled. + static void update(); - /// \brief Obtain the first joystick device identifier as recognized by the - /// underlying implementation. - JoystickID first_joystick( void ) const; + /// \brief Get the number of joysticks attached to the system. + /// + /// \returns The number of attached joysticks on success, and a negative + /// error code on failure. + static int num_joysticks(); - /// \brief Obtain the last joystick device identifier as recognized by the - /// underlying implementation. - JoystickID last_joystick( void ) const; + /// \brief Get the implementation-dependent name of the joystick. + /// + /// \param device_index The index of the joystick device, as recognized by + /// SDL. + /// + /// \returns The name of the indexed joystick on success, and a zero-length + /// string on failure, such as when no name for the device index can be + /// found. + /// + /// \remarks This function can be called before any joysticks are accessed. + /// + /// \see Joystick::num_joysticks + static std::string name(JoystickIndex device_index); - /// \brief Obtain the number of joystick devices as recognized by the - /// underlying implementation. - int num_joysticks( void ) const; + /// \brief Get the implementation-dependent GUID for this joystick + /// instance. + /// + /// \returns The GUID of the joystick on success, and a zero-length GUID on + /// failure, such as when the device state is invalid. + /// + /// \remarks This function requires an opened joystick. + /// + /// \see Joystick::attached + JoystickGUID device_guid() const; - const std::string& name( JoystickID idx ); + /// \brief Get the implementation-dependent GUID for a joystick. + /// + /// \param device_index The index of the joystick device, as recognized by + /// SDL. + /// + /// \returns The GUID of the joystick on success, and a zero-length GUID on + /// failure, such as when an invalid index is passed. + /// + /// \remarks This function can be called before any joysticks are accessed. + /// + /// \see Joystick::num_joysticks + static JoystickGUID device_guid(JoystickIndex device_index); - const JoystickNames names( void ) const; + /// \brief Get the ASCII string representation of a joystick GUID. + /// + /// \returns The GUID string of the joystick on success, and a zero-length + /// GUID on failure, such as when an invalid GUID has been passed. + /// + /// \remarks This function can be called before any joysticks are accessed. + static std::string device_guid_string(JoystickGUID guid); - void enumerate_devices( void ); + /// \brief Get the ASCII string representation of this joystick instance + /// GUID. + /// + /// \returns The GUID string of the joystick on success, and a zero-length + /// GUID on failure, such as when the device state is invalid. + /// + /// \remarks This function requires an opened joystick. + /// + /// \see Joystick::attached + std::string device_guid_string() const; + + /// \brief Get the instance ID of a joystick device. + /// + /// \param device_index The index of the joystick device, as recognized by + /// SDL. + /// + /// \returns The device instance ID for the joystick device on success, and + /// a negative error code on failure. + /// + /// \remarks This function can be called before any joysticks are accessed. + /// + /// \see Joystick::num_joysticks + static JoystickID device_id(JoystickIndex device_index); private: - std::unique_ptr impl_; - Joysticks joysticks_; + typedef std::unique_ptr joystick_dev; + + joystick_dev device_; }; +std::unique_ptr make_unique_joystick(); +std::shared_ptr make_shared_joystick(); + +JoystickGUID convert_SDL_JoystickGUID(SDL_JoystickGUID dev_guid); +SDL_JoystickGUID convert_SDL_JoystickGUID(JoystickGUID dev_guid); + } // namespace nom #endif // include guard defined diff --git a/include/nomlib/system/JoystickEventHandler.hpp b/include/nomlib/system/JoystickEventHandler.hpp new file mode 100644 index 00000000..d270c0e1 --- /dev/null +++ b/include/nomlib/system/JoystickEventHandler.hpp @@ -0,0 +1,90 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#ifndef NOMLIB_SYSTEM_JOYSTICK_EVENT_HANDLER_HPP +#define NOMLIB_SYSTEM_JOYSTICK_EVENT_HANDLER_HPP + +#include +#include + +#include "nomlib/config.hpp" +#include "nomlib/system/Joystick.hpp" + +namespace nom { + +// Forward declarations +class Joystick; + +/// \brief Internal management of hot-pluggable joystick devices handling +class JoystickEventHandler +{ + public: + JoystickEventHandler(); + ~JoystickEventHandler(); + + /// \brief Get the number of accessible joysticks. + nom::size_type num_joysticks() const; + + // Non-owned pointer + Joystick* joystick(JoystickID dev_id) const; + + /// \brief Query for the existence of an attached joystick. + /// + /// \returns Boolean TRUE when the joystick exists, and boolean FALSE when + /// the joystick does **not** exist. + bool joystick_exists(JoystickID dev_id); + + /// \brief Append a game controller to the active devices pool. + /// + /// \returns A non-owned pointer to the game controller device on success, + /// or NULL on failure, such as when the device can not be opened. + Joystick* add_joystick(JoystickIndex device_index); + + /// \brief Remove a connected joystick from the joystick event pool. + /// + /// \returns Boolean TRUE when the joystick exists, and boolean FALSE when + /// the joystick does **not** exist. + bool remove_joystick(JoystickID dev_id); + + private: + typedef std::map> joysticks; + + /// \brief Active joysticks event pool. + /// + /// \remarks This is a mapping of the current joysticks that are available + /// to the end-user for use. Without this mapping, the end-user would be + /// responsible for the management of each joystick's life-time; we must + /// hold onto the device reference at the time of the SDL event, else we + /// aren't able to use it beyond that frame -- the same is true of removal + /// events. + joysticks joysticks_; +}; + +} // namespace nom + +#endif // include guard defined diff --git a/include/nomlib/system/SDLApp.hpp b/include/nomlib/system/SDLApp.hpp index 2ea4103e..ca69dd7f 100644 --- a/include/nomlib/system/SDLApp.hpp +++ b/include/nomlib/system/SDLApp.hpp @@ -33,13 +33,13 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include "nomlib/config.hpp" -#include "nomlib/system/EventHandler.hpp" #include "nomlib/system/Timer.hpp" namespace nom { // Forward declarations struct Event; +class EventHandler; class RenderWindow; class IState; class StateMachine; @@ -48,7 +48,7 @@ class StateMachine; /// /// \note http://docs.wxwidgets.org/trunk/classwx_app.html /// \note http://doc.qt.digia.com/4.6/qapplication.html -class SDLApp: public EventHandler +class SDLApp { public: typedef SDLApp self_type; @@ -85,12 +85,6 @@ class SDLApp: public EventHandler /// \todo Rename to exec..? virtual sint Run( void ); - /// \brief The application-level handler for events. - /// - /// \remarks This method is called once every frame from within the main - /// loop. - virtual void on_event( const Event& ev ); - /// \brief The application-level handler for logic. /// /// \remarks This method is called once every frame from within the main @@ -106,18 +100,6 @@ class SDLApp: public EventHandler /// away with this! virtual void on_draw( RenderWindow& ); - /// \brief Implements the nom::EventHandler::on_window_close method. - /// - /// \remarks The default implementation is to let the SDLApp::on_app_quit - /// method handle things for us. - virtual void on_window_close( const Event& ev ); - - /// \brief Implements the nom::EventHandler::on_app_quit method. - /// - /// \remarks The default implementation is to let the SDLApp::quit method - /// handle things for us. - virtual void on_app_quit( const Event& ev ); - /// \brief Query status of the application state. /// /// \returns Boolean true or false. @@ -150,11 +132,113 @@ class SDLApp: public EventHandler void set_state_machine( StateMachine* mech ); + /// \brief Install the event handler used by the interface. + /// + /// \remarks The application's events will not be processed until a call is + /// made to the event handler's ::poll_event method. + /// + /// \note The installed event handler must outlive the destruction of + /// this interface! + void set_event_handler(EventHandler& evt_handler); + protected: + /// \brief Default event handler for input events. + virtual void on_input_event(const Event& ev); + + /// \brief The event handler for when a request for halting program + /// execution occurs. + /// + /// \remarks The default implementation calls nom::SDLApp::quit. + /// + /// \note This event handles the system's default quit handler, i.e.: + /// COMMAND + Q on Mac OS X and ALT + F4 on Windows. + virtual void on_app_quit(const Event& ev); + + virtual void on_window_shown(const Event& ev); + + virtual void on_window_hidden(const Event& ev); + + virtual void on_window_exposed(const Event& ev); + + virtual void on_window_moved(const Event& ev); + + virtual void on_window_resized(const Event& ev); - //GameStates* state_factory; + virtual void on_window_size_changed(const Event& ev); + + virtual void on_window_minimized(const Event& ev); + + virtual void on_window_maximized(const Event& ev); + + virtual void on_window_restored(const Event& ev); + + virtual void on_window_mouse_focus(const Event& ev); + + virtual void on_window_mouse_focus_lost(const Event& ev); + + virtual void on_window_keyboard_focus(const Event& ev); + + virtual void on_window_keyboard_focus_lost(const Event& ev); + + /// \brief The event handler for when a request for closing a window + /// occurs. + /// + /// \remarks The default implementation calls nom::SDLApp::on_app_quit. + virtual void on_window_close(const Event& ev); + + /// Drag 'N' Drop events + /// + /// \remarks To enable drag and drop events on Mac OS X, you must add the + /// appropriate keys in your application bundle's Info.plist, like so: + /// + /// CFBundleDocumentTypes + /// + /// + /// CFBundleTypeRole + /// Editor + /// CFBundleTypeName + /// TTcards + /// CFBundleTypeExtensions + /// + /// json + /// + /// CFBundleTypeIconFile + /// TTcards + /// + /// + virtual void on_drag_drop(const Event& ev); + + /// \remarks This is applicable only to Direct3D renderers. + virtual void on_render_targets_reset(const Event& ev); + + /// \remarks This is applicable only to Direct3D renderers. + +// NOTE: Not available until the release of SDL 2.0.4 +#if 0 + virtual void on_render_device_reset(const Event& ev); +#endif + + // NOTE: Proposed naming scheme for iOS / Android event handlers + // virtual void on_app_destroy(const Event& ev); + // virtual void on_app_low_memory(const Event& ev); + // virtual void on_app_enter_background(const Event& ev); + // virtual void on_app_background(const Event& ev); + // virtual void on_app_enter_foreground(const Event& ev); + // virtual void on_app_foreground(const Event& ev); + + /// \brief The default event handler for user-defined events. + virtual void on_user_event(const Event& ev); private: + bool initialize( uint32 flags ); + + void on_app_event(const Event& ev); + + void process_event(const Event& ev); + + // Non-owned pointer + EventHandler* event_handler_ = nullptr; + /// \brief State machine manager. /// /// \remarks This object pointer must be initialized prior to use. @@ -162,8 +246,6 @@ class SDLApp: public EventHandler /// \see SDLApp::set_state_machine std::unique_ptr state_; - bool initialize( uint32 flags ); - /// \brief Global application state. bool app_state_; diff --git a/include/nomlib/system/SDLJoystick.hpp b/include/nomlib/system/SDLJoystick.hpp deleted file mode 100644 index c91da21f..00000000 --- a/include/nomlib/system/SDLJoystick.hpp +++ /dev/null @@ -1,157 +0,0 @@ -/****************************************************************************** - - nomlib - C++11 cross-platform game engine - -Copyright (c) 2013, 2014 Jeffrey Carpenter -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -******************************************************************************/ -#ifndef NOMLIB_SDL2_SYSTEM_SDL_JOYSTICK_HPP -#define NOMLIB_SDL2_SYSTEM_SDL_JOYSTICK_HPP - -#include -#include -#include - -#include - -#include "nomlib/config.hpp" -#include "nomlib/system/IJoystick.hpp" - -namespace nom { - -/// \brief Low-level joystick handling API. -class SDLJoystick: public IJoystick -{ - public: - typedef SDLJoystick SelfType; - typedef std::unique_ptr UniquePtr; - - /// \brief Default constructor. - SDLJoystick( void ); - - /// \brief Destructor. - ~SDLJoystick( void ); - - /// \brief Initialize the joystick subsystem. - /// - /// \returns Boolean TRUE when the SDL joystick subsystem is initialized - /// and the joystick event state is enabled successfully. - bool initialize( void ); - - /// \brief Shutdown the joystick subsystem. - void shutdown( void ); - - /// \brief Obtain the number of joysticks attached to the system. - /// - /// \returns The number of joysticks attached on success, or a negative - /// error code on failure. - int num_joysticks( void ) const; - - /// \brief Obtain the status of the joystick device. - /// - /// \returns Boolean TRUE if the joystick has been opened, boolean FALSE - /// otherwise. - bool attached( void ) const; - - /// \brief Obtain the device index of an opened joystick. - /// - /// \returns An instance identifier of the joystick device on success, or a - /// negative error code on failure. - int id( void ) const; - - /// \brief Obtain the implementation dependent name of the joystick device. - /// - /// \returns Name of the joystick device on success, or a null-terminated - /// string on failure (such as an invalid device state). - const std::string name( void ) const; - - /// \brief Open a joystick device - /// - /// \param idx Device index of the joystick as recognized by SDL2. - bool open( int idx ); - - /// \brief Close a previously opened joystick device. - void close( void ); - - /// \brief Obtain the implementation-dependent GUID for the joystick. - /// - /// \param idx The joystick device index identifier as recognized by SDL2. - /// - /// \returns A valid GUID of the given joystick on success, if an invalid - /// index, zero GUID. - SDL_JoystickGUID device_guid( int idx ) const; - - /// \brief Obtain the implementation-dependent GUID for the opened joystick. - /// - /// \returns A valid GUID of the given joystick on success, if an invalid - /// index, zero GUID. - SDL_JoystickGUID device_guid( void ) const; - - const std::string device_guid_string( void ) const; - - bool game_controller( void ) const; - - private: - /// \brief Toggle the joystick event polling state. - /// - /// \param state One of the following: SDL_QUERY, SDL_IGNORE or SDL_ENABLE. - /// - /// \returns One (1) if enabled, zero (0) if disabled or a negative error - /// code on failure. If the state was SDL_QUERY, then the current state is - /// returned, otherwise the new processing state is returned. - /// - /// \remarks If joystick events are disabled, you must call - /// SDLJoystick::update and manually check the state of the joystick when - /// event data is needed. - /// - /// \note Calling this method may delete all events currently in SDL's event - /// queue. - int set_event_state( int state ) const; - - /// \brief Update the current state of the opened joystick(s). - /// - /// \remarks This method is automatically called by the event loop if any - /// joystick events are enabled. - void update( void ) const; - - SDLJoystick::UniquePtr device_; -}; - -namespace priv { - -/// Custom deleter for SDL_Joystick* structures. -void Free_Joystick( SDL_Joystick* ); - -} // namespace priv -} // namespace nom - -#endif // include guard defined - -/// \class nom::SDLJoystick -/// \ingroup system -/// -/// [DESCRIPTION STUB] -/// -/// \todo Implement SDL2's GameController API for Joysticks. -/// diff --git a/include/nomlib/system/SDL_helpers.hpp b/include/nomlib/system/SDL_helpers.hpp index 01765f38..7cf109eb 100644 --- a/include/nomlib/system/SDL_helpers.hpp +++ b/include/nomlib/system/SDL_helpers.hpp @@ -58,9 +58,55 @@ struct _TTF_Font {}; namespace nom { +enum BlendMode +{ + /// no blending + /// dstRGBA = srcRGBA + BLEND_MODE_NONE = SDL_BLENDMODE_NONE, + + /// alpha blending + /// dstRGB = (srcRGB * srcA) + (dstRGB * (1-srcA)) + /// dstA = srcA + (dstA * (1-srcA)) + BLEND_MODE_BLEND = SDL_BLENDMODE_BLEND, + + /// additive blending + /// dstRGB = (srcRGB * srcA) + dstRGB + /// dstA = dstA + BLEND_MODE_ADD = SDL_BLENDMODE_ADD, + + /// color modulate + /// dstRGB = srcRGB * dstRGB + /// dstA = dstA + BLEND_MODE_MOD = SDL_BLENDMODE_MOD +}; + +/// \brief The encoding scheme of a set of pixels. +enum PixelFormat +{ + /// \brief An invalid pixel format. + /// + /// \remarks Conditions that could create this state include, but are not + /// limited to: invalid or missing data, unsupported memory layout by the + /// platform or GPU and so on. + PIXEL_FORMAT_INVALID = SDL_PIXELFORMAT_UNKNOWN, + + /// \brief Each pixel is a four (4) byte (32-bits) unsigned integer value. + PIXEL_FORMAT_ARGB8888 = SDL_PIXELFORMAT_ARGB8888, + + /// planar mode: Y + V + U (3 planes) + PIXEL_FORMAT_YV12 = SDL_PIXELFORMAT_YV12, + + /// planar mode: Y + U + V (3 planes) + PIXEL_FORMAT_IYUV = SDL_PIXELFORMAT_IYUV, + + /// packed mode: U0+Y0+V0+Y1 (1 plane) + PIXEL_FORMAT_UYVY = SDL_PIXELFORMAT_UYVY, +}; + /// \brief Convenience definitions for pointer types /// -/// \todo Remove. +/// \todo Remove or perhaps better yet, refer to as a light-weight handle, +/// i.e.: nom::WindowHandle? namespace SDL_WINDOW { typedef std::unique_ptr UniquePtr; @@ -69,7 +115,7 @@ namespace SDL_WINDOW /// \brief Convenience definitions for pointer types /// -/// \todo Remove. +/// \todo Remove or perhaps better yet, refer to as a light-weight handle..? namespace SDL_PIXELFORMAT { typedef SDL_PixelFormat* RawPtr; @@ -77,7 +123,8 @@ namespace SDL_PIXELFORMAT /// \brief Convenience definitions for pointer types /// -/// \todo Remove. +/// \todo Remove or perhaps better yet, refer to as a light-weight handle, +/// i.e.: nom::SurfaceHandle? namespace SDL_SURFACE { typedef std::unique_ptr UniquePtr; @@ -87,17 +134,21 @@ namespace SDL_SURFACE /// \brief Convenience definitions for pointer types /// -/// \todo Remove. +/// \todo Remove or perhaps better yet, refer to as a light-weight handle, +/// i.e.: nom::TextureHandle? namespace SDL_TEXTURE { typedef std::shared_ptr SharedPtr; typedef SDL_Texture* RawPtr; } -/// SDL2 data structure wrappers for nomlib -/// -/// \return A SDL_bool from a boolean value -SDL_bool SDL_BOOL ( bool value ); +/// \brief Convert a SDL_BlendMode enumeration value to a nom::BlendMode +/// enumeration value. +BlendMode blend_mode(SDL_BlendMode mode); + +/// \brief Convert a nom::BlendMode enumeration value to a SDL_BlendMode +/// enumeration value. +SDL_BlendMode SDL_blend_mode(BlendMode mode); /// SDL data structure wrappers for nomlib /// @@ -173,7 +224,7 @@ uint32 RGBA ( const Color4i& color, uint32 fmt ); /// \param name Hint to query /// /// \return Value of the queried name, or a null-terminated string if not set -std::string hint( const std::string& name ); +std::string hint(const std::string& name); /// SDL2 helper function /// @@ -186,7 +237,7 @@ std::string hint( const std::string& name ); /// been made, as certain hints, i.e.: SDL_HINT_VIDEO_MAC_FULLSCREEN_SPACES /// must be called before video initialization, whereas others depend on a valid /// renderer, etc. -bool set_hint( const std::string& name, const std::string& value ); +bool set_hint(const std::string& name, const std::string& value); const std::string PIXEL_FORMAT_NAME ( uint32 format ); diff --git a/include/nomlib/system/StateMachine.hpp b/include/nomlib/system/StateMachine.hpp index a678b239..7bed2968 100644 --- a/include/nomlib/system/StateMachine.hpp +++ b/include/nomlib/system/StateMachine.hpp @@ -107,11 +107,8 @@ class StateMachine /// \brief Event handling logic for the active state. /// - /// \remarks EventHandler::process_event for the state is *not* executed if - /// nom::IState::on_event returns boolean FALSE (the default implementation). - /// /// \see nom::IState::on_event. - void on_event( const Event& ev ); + void on_event(const Event& ev); /// \brief State logic handling /// diff --git a/include/nomlib/system/Timer.hpp b/include/nomlib/system/Timer.hpp index 2153e9f9..c5b6405b 100644 --- a/include/nomlib/system/Timer.hpp +++ b/include/nomlib/system/Timer.hpp @@ -58,8 +58,21 @@ class Timer const std::string ticksAsString ( void ) const; - /// Helper method; conversion from milliseconds to seconds - uint32 seconds( float milliseconds ) const; + real32 to_seconds() const; + + /// \brief Convert milliseconds to seconds. + /// + /// \param elapsed_ticks A timing value in milliseconds to convert. + /// + /// \see ::ticks, nom::ticks + static uint32 to_milliseconds(real32 seconds); + + /// \brief Convert a timer value to seconds. + /// + /// \param elapsed_ticks A timing value in milliseconds to convert. + /// + /// \see ::ticks, nom::ticks + static real32 to_seconds(uint32 ticks); private: /// Milliseconds since timer start diff --git a/include/nomlib/system/unix/UnixFile.hpp b/include/nomlib/system/unix/UnixFile.hpp index e42ff13f..7ed85304 100644 --- a/include/nomlib/system/unix/UnixFile.hpp +++ b/include/nomlib/system/unix/UnixFile.hpp @@ -36,12 +36,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -#include -#include -#include -#include -#include - #include "nomlib/config.hpp" #include "nomlib/system/IFile.hpp" @@ -247,6 +241,8 @@ class UnixFile: public IFile /// \returns A null-terminated string (zero length) upon err, such as an /// non-existent environment variable. std::string env( const std::string& path ); + + nom::size_type num_files(const std::string& path); }; diff --git a/include/nomlib/system/windows/WinFile.hpp b/include/nomlib/system/windows/WinFile.hpp index 433c29d4..62c0454a 100644 --- a/include/nomlib/system/windows/WinFile.hpp +++ b/include/nomlib/system/windows/WinFile.hpp @@ -230,6 +230,8 @@ class WinFile: public IFile /// /// \see http://msdn.microsoft.com/en-us/library/tehxacec.aspx std::string env( const std::string& path ); + + nom::size_type num_files(const std::string& path); }; diff --git a/include/nomlib/types.hpp b/include/nomlib/types.hpp index c718b889..3d70509d 100644 --- a/include/nomlib/types.hpp +++ b/include/nomlib/types.hpp @@ -29,15 +29,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef NOMLIB_STDINT_TYPES_HPP #define NOMLIB_STDINT_TYPES_HPP -#include // std::size_t -#include -#include // min && max types +#include // std::size_t +#include // min && max types #include "nomlib/platforms.hpp" -// RTTI for library objects. -#include "nomlib/core/ObjectTypeInfo.hpp" - /* TODO: This should be replaced by an actual CMake script -- think: compile-time check for the necessary feature support for C++11 style @@ -47,10 +43,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. known as stdint.h. MSVCPP2013 & llvm-clang are fine with it). */ -#ifndef NOM_PLATFORM_LINUX // To be replace with NOM_COMPILER_FEATURE_NULLPTR - // (see above TODO note). +// IMPORTANT(JEFF): These declarations, too, will integrate into our (CMake) +// platform detection routines some day. +#if defined(__GNUC__) #include -#else +#elif defined(__llvm__) #include #endif @@ -96,6 +93,12 @@ typedef int32_t int32; /// \brief Unsigned 16-bit integer. typedef uint32_t uint32; +/// \brief 32-bit IEEE floating-point value. +typedef float real32; + +/// \brief 64-bit IEEE floating-point value. +typedef double real64; + /// \brief 64-bit integer types /// \note As per **/usr/include/MacTypes.h**: /// @@ -136,22 +139,48 @@ typedef void* void_ptr; typedef unsigned long ulong; -// Definitions for minimum && maximum integral types +// Numerical min, max values for commonly used data types // // http://en.cppreference.com/w/cpp/types/numeric_limits -const int int_min = std::numeric_limits::lowest(); -const int int_max = std::numeric_limits::max(); -const uint uint_min = std::numeric_limits::lowest(); -const uint uint_max = std::numeric_limits::max(); -const int char_min = std::numeric_limits::lowest(); -const int char_max = std::numeric_limits::max(); -const uint uchar_min = std::numeric_limits::lowest(); -const uint uchar_max = std::numeric_limits::max(); +const char NOM_CHAR_MIN = std::numeric_limits::lowest(); +const char NOM_CHAR_MAX = std::numeric_limits::max(); +const uchar NOM_UCHAR_MIN = std::numeric_limits::lowest(); +const uchar NOM_UCHAR_MAX = std::numeric_limits::max(); + +const int8 NOM_INT8_MIN = std::numeric_limits::lowest(); +const int8 NOM_INT8_MAX = std::numeric_limits::max(); +const uint8 NOM_UINT8_MIN = std::numeric_limits::lowest(); +const uint8 NOM_UINT8_MAX = std::numeric_limits::max(); + +const int16 NOM_INT16_MIN = std::numeric_limits::lowest(); +const int16 NOM_INT16_MAX = std::numeric_limits::max(); +const uint16 NOM_UINT16_MIN = std::numeric_limits::lowest(); +const uint16 NOM_UINT16_MAX = std::numeric_limits::max(); + +const int NOM_INT_MIN = std::numeric_limits::lowest(); +const int NOM_INT_MAX = std::numeric_limits::max(); +const uint NOM_UINT_MIN = std::numeric_limits::lowest(); +const uint NOM_UINT_MAX = std::numeric_limits::max(); + +const int32 NOM_INT32_MIN = std::numeric_limits::lowest(); +const int32 NOM_INT32_MAX = std::numeric_limits::max(); +const uint32 NOM_UINT32_MIN = std::numeric_limits::lowest(); +const uint32 NOM_UINT32_MAX = std::numeric_limits::max(); -// Always an unsigned type -const size_type size_type_min = std::numeric_limits::lowest(); -const size_type size_type_max = std::numeric_limits::max(); +const int64 NOM_INT64_MIN = std::numeric_limits::lowest(); +const int64 NOM_INT64_MAX = std::numeric_limits::max(); +const uint64 NOM_UINT64_MIN = std::numeric_limits::lowest(); +const uint64 NOM_UINT64_MAX = std::numeric_limits::max(); + +const size_type NOM_SIZE_TYPE_MIN = std::numeric_limits::lowest(); +const size_type NOM_SIZE_TYPE_MAX = std::numeric_limits::max(); + +const real32 NOM_REAL32_MIN = std::numeric_limits::lowest(); +const real32 NOM_REAL32_MAX = std::numeric_limits::max(); + +const real64 NOM_REAL64_MIN = std::numeric_limits::lowest(); +const real64 NOM_REAL64_MAX = std::numeric_limits::max(); /// \brief An integer indicating that there is no match, an error or NULL. static const int npos = -1; @@ -205,14 +234,46 @@ enum Anchor: uint32 BottomRight = Y_BOTTOM | X_RIGHT // Hex: 0x44, Dec: 68 }; -typedef std::vector StringList; +static_assert(sizeof(nom::uint8) == 1, "nom::uint8"); +static_assert(sizeof(nom::int8) == 1, "nom::int8"); + +const nom::size_type NOM_BYTE = sizeof(uint8); // 1024; + +inline +nom::size_type kilobyte(nom::size_type bytes) +{ + nom::size_type result = (bytes * NOM_BYTE); + + return result; +} + +inline +nom::size_type megabyte(nom::size_type bytes) +{ + nom::size_type result = ( kilobyte(bytes) * NOM_BYTE); + + return result; +} + +inline +nom::size_type gigabyte(nom::size_type bytes) +{ + nom::size_type result = ( megabyte(bytes) * NOM_BYTE ); + + return result; +} + +inline +nom::size_type terabyte(nom::size_type bytes) +{ + nom::size_type result = ( gigabyte(bytes) * NOM_BYTE ); + + return result; +} } // namespace nom /// Ensure our data types have the right sizes using C++11 compile-time asserts. -static_assert ( sizeof ( nom::uint8 ) == 1, "nom::uint8" ); -static_assert ( sizeof ( nom::int8 ) == 1, "nom::int8" ); - static_assert ( sizeof ( nom::uint16 ) == 2, "nom::uint16" ); static_assert ( sizeof ( nom::int16 ) == 2, "nom::int16" ); @@ -222,10 +283,14 @@ static_assert ( sizeof ( nom::int32 ) == 4, "nom::int32" ); static_assert ( sizeof ( nom::uint64 ) == 8, "nom::uint64" ); static_assert ( sizeof ( nom::int64 ) == 8, "nom::int64" ); +static_assert ( sizeof ( nom::real32 ) == 4, "nom::real32" ); +static_assert ( sizeof ( nom::real64 ) == 8, "nom::real64" ); + static_assert ( sizeof ( nom::uchar ) == 1, "nom::uchar" ); // Blindly assumes we are on either a 64-bit or 32-bit platform. -#if defined( NOM_PLATFORM_ARCH_X86_64 ) +// TODO: Relocate this def to run-time (cmake gen) +#if defined(NOM_PLATFORM_ARCH_X86_64) static_assert( sizeof ( nom::ulong ) == 8, "nom::ulong" ); static_assert( sizeof ( nom::size_type ) == 8, "nom::size_type" ); #else // #elif defined( NOM_PLATFORM_ARCH_X86_86 ) @@ -234,7 +299,7 @@ static_assert ( sizeof ( nom::uchar ) == 1, "nom::uchar" ); #endif // Blindly assumes we are on either a 64-bit or 32-bit platform. -#if defined( NOM_PLATFORM_ARCH_X86_64 ) +#if defined(NOM_PLATFORM_ARCH_X86_64) static_assert( sizeof(nom::int_ptr) == 8, "nom::int_ptr" ); static_assert( sizeof(nom::uint_ptr) == 8, "nom::uint_ptr" ); static_assert( sizeof ( nom::int32_ptr ) == ( sizeof(long) ), "nom::int32_ptr" ); @@ -255,4 +320,13 @@ const nom::sint NOM_EXIT_SUCCESS = 0; // EXIT_SUCCESS from cstdlib headers const nom::sint SDL_SUCCESS = 0; // Non-error return value for SDL2 API //#endif +#define NOM_KILOBYTES(bytes) kilobyte(bytes) +#define NOM_MEGABYTES(bytes) megabyte(bytes) +#define NOM_GIGABYTES(bytes) gigabyte(bytes) +#define NOM_TERABYTES(bytes) terabytes(bytes) + +// Configuration variables for the engine + +#define NOM_EVENT_QUEUE_STATISTICS "NOM_EVENT_QUEUE_STATISTICS" + #endif // include guard defined diff --git a/include/nomlib/version.hpp.in b/include/nomlib/version.hpp.in index 1ce37f86..8d175792 100644 --- a/include/nomlib/version.hpp.in +++ b/include/nomlib/version.hpp.in @@ -75,7 +75,7 @@ struct VersionInfo /// \brief Convert a version string to major, minor and patch integers. /// /// \param ver_string A version string in the format of x.x.x, where x is a - /// number between INT_MIN and INT_MAX. + /// number between nom::NOM_INT_MIN and nom::NOM_INT_MAX. /// /// \returns Boolean TRUE upon a successful conversion, or boolean FALSE upon a /// unsuccessful conversion, such as if the version string format is invalid. @@ -125,9 +125,7 @@ bool operator >=(const VersionInfo& lhs, const VersionInfo& rhs); /// \remarks This data is taken from from the project's root CMake build script. /// /// \see CMakeLists.txt -const VersionInfo NOM_VERSION( @PROJECT_VERSION_MAJOR@, - @PROJECT_VERSION_MINOR@, - @PROJECT_VERSION_PATCH@ ); +const VersionInfo NOM_VERSION(@PROJECT_VERSION_MAJOR@, @PROJECT_VERSION_MINOR@, @PROJECT_VERSION_PATCH@); /// \brief Get the current revision number build of nomlib. /// diff --git a/LICENSE_OpenALSoft.txt b/licenses/LICENSE_OpenALSoft.txt similarity index 100% rename from LICENSE_OpenALSoft.txt rename to licenses/LICENSE_OpenALSoft.txt diff --git a/licenses/LICENSE_SDL2.txt b/licenses/LICENSE_SDL2.txt new file mode 100755 index 00000000..397e7b45 --- /dev/null +++ b/licenses/LICENSE_SDL2.txt @@ -0,0 +1,20 @@ + +Simple DirectMedia Layer +Copyright (C) 1997-2013 Sam Lantinga + +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. + diff --git a/LICENSE_SDL2_image.txt b/licenses/LICENSE_SDL2_image.txt similarity index 100% rename from LICENSE_SDL2_image.txt rename to licenses/LICENSE_SDL2_image.txt diff --git a/LICENSE_SDL2_ttf.txt b/licenses/LICENSE_SDL2_ttf.txt similarity index 100% rename from LICENSE_SDL2_ttf.txt rename to licenses/LICENSE_SDL2_ttf.txt diff --git a/LICENSE_VisualUnitTest.txt b/licenses/LICENSE_VisualUnitTest.txt similarity index 100% rename from LICENSE_VisualUnitTest.txt rename to licenses/LICENSE_VisualUnitTest.txt diff --git a/LICENSE_freetype.txt b/licenses/LICENSE_freetype.txt similarity index 100% rename from LICENSE_freetype.txt rename to licenses/LICENSE_freetype.txt diff --git a/licenses/LICENSE_gtest.txt b/licenses/LICENSE_gtest.txt new file mode 100644 index 00000000..1941a11f --- /dev/null +++ b/licenses/LICENSE_gtest.txt @@ -0,0 +1,28 @@ +Copyright 2008, Google Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/LICENSE_hqx.md b/licenses/LICENSE_hqx.md similarity index 100% rename from LICENSE_hqx.md rename to licenses/LICENSE_hqx.md diff --git a/LICENSE_jsoncpp.txt b/licenses/LICENSE_jsoncpp.txt similarity index 100% rename from LICENSE_jsoncpp.txt rename to licenses/LICENSE_jsoncpp.txt diff --git a/LICENSE_libRocket.md b/licenses/LICENSE_libRocket.md similarity index 100% rename from LICENSE_libRocket.md rename to licenses/LICENSE_libRocket.md diff --git a/LICENSE_libjpeg.txt b/licenses/LICENSE_libjpeg.txt similarity index 100% rename from LICENSE_libjpeg.txt rename to licenses/LICENSE_libjpeg.txt diff --git a/LICENSE_libpng.txt b/licenses/LICENSE_libpng.txt similarity index 100% rename from LICENSE_libpng.txt rename to licenses/LICENSE_libpng.txt diff --git a/LICENSE_libsndfile.txt b/licenses/LICENSE_libsndfile.txt similarity index 100% rename from LICENSE_libsndfile.txt rename to licenses/LICENSE_libsndfile.txt diff --git a/LICENSE_libtiff.txt b/licenses/LICENSE_libtiff.txt similarity index 100% rename from LICENSE_libtiff.txt rename to licenses/LICENSE_libtiff.txt diff --git a/licenses/LICENSE_rapidxml.txt b/licenses/LICENSE_rapidxml.txt new file mode 100755 index 00000000..14098318 --- /dev/null +++ b/licenses/LICENSE_rapidxml.txt @@ -0,0 +1,52 @@ +Use of this software is granted under one of the following two licenses, +to be chosen freely by the user. + +1. Boost Software License - Version 1.0 - August 17th, 2003 +=============================================================================== + +Copyright (c) 2006, 2007 Marcin Kalicinski + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +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, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + +2. The MIT License +=============================================================================== + +Copyright (c) 2006, 2007 Marcin Kalicinski + +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. diff --git a/LICENSE_scale2x.md b/licenses/LICENSE_scale2x.md similarity index 100% rename from LICENSE_scale2x.md rename to licenses/LICENSE_scale2x.md diff --git a/licenses/LICENSE_tclap.txt b/licenses/LICENSE_tclap.txt new file mode 100644 index 00000000..987be0ce --- /dev/null +++ b/licenses/LICENSE_tclap.txt @@ -0,0 +1,25 @@ + + +Copyright (c) 2003 Michael E. Smoot + +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. + + diff --git a/LICENSE_webp.txt b/licenses/LICENSE_webp.txt similarity index 100% rename from LICENSE_webp.txt rename to licenses/LICENSE_webp.txt diff --git a/LICENSE_zlib.txt b/licenses/LICENSE_zlib.txt similarity index 100% rename from LICENSE_zlib.txt rename to licenses/LICENSE_zlib.txt diff --git a/nomlib.code-workspace b/nomlib.code-workspace new file mode 100644 index 00000000..9636e755 --- /dev/null +++ b/nomlib.code-workspace @@ -0,0 +1,23 @@ +{ + "folders": [ + { + "path": "." + } + ], + "settings": { + "files.association": { + "limits": "cpp" + }, + "files.exclude": { + "**/.git": true, + "**/.svn": true, + "**/.hg": true, + "**/.DS_Store": true, + "**/Thumbs.db": true, + "**/CVS": true, + "**/.ruby-lsp": true, + "node_modules": true, + "**/node_modules": true + } + } +} diff --git a/nomlib.sublime-project b/nomlib.sublime-project index 13b52829..cbd0f53d 100644 --- a/nomlib.sublime-project +++ b/nomlib.sublime-project @@ -1,65 +1,34 @@ { - // File inclusion and exclusion patterns effect search results when using - // 'goto' (super+p), in addition to side bar file list. - // - // It is necessary to explicitly include the project's include and source - // folders in order for the Switch Script package to function. - "folders": - [ - { - "path": ".", - // The directories 'src', 'include' and 'tests' are ignored here in order - // to prevent duplicate search results when using 'goto'. The duplicates - // are created when we define the "inc" and "src" folder names (see below), - // for the SwitchScript package. - // - // 'xcode' is my CMake build directory for Xcode generated project files - // and must be excluded due to a bug that often crashes Sublime Text 2 - // anytime I build from this directory. - // - // See also: http://www.sublimetext.com/forum/viewtopic.php?f=3&t=8417 - "folder_exclude_patterns": [ "build", "xcode", "tmp", "third-party", "src", "include", "tests" ] - }, - - { - "name": "inc", - "path": "include/nomlib", - "file_include_patterns": [ "*.hpp", "*.h", "*.hxx", "*.hpp.in" ] - // "file_exclude_patterns": [ "config.hpp" ] - }, - - { - "name": "src", - "path": "src", - "file_include_patterns": [ "*.txt", "*.cpp", "*.c", "*.cxx", "*.cpp.in" ] - }, - - { - "name": "tests", - "path": "tests", - "file_include_patterns": [ "*.txt", "*.cpp", "*.c", "*.cxx", "*.cpp.in", "*.hpp", "*.h", "*.hxx", "*.hpp.in" ] - }, - - {} // placeholder - ], - - "settings": - { - "tab_size": 2, - "translate_tabs_to_spaces": true, - //"sublimegdb_workingdir": "${folder:${project_path:build}}", - //"sublimegdb_workingdir": "${folder:${project_path:build/TTcards.app/Contents/MacOS/ttcards}}", - //"sublimegdb_commandline": "gdb --interpreter=mi ./your_executable_name" - "ctags_command": "/usr/local/bin/ctags -R -f .tags src/ include/" - - }, - - "build_systems": - [ - { - // placeholder - }, - - {} // placeholder - ] + "folders": + [ + { + "file_exclude_patterns": + [ + ".sublime-*", + "version.hpp", + "config.hpp", + "revision.cpp" + ], + "folder_exclude_patterns": + [ + "build", + "build-release", + "tmp", + "third-party" + ], + "name": "nomlib.git", + "path": "." + }, + { + "path": "/Volumes/Media/Dropbox/Notes/Quiver.qvlibrary/00774B96-279B-4760-840D-8D19C696D344.qvnotebook/714CAACA-5B1F-4DFE-8084-425D8C82F74F.qvnote" + }, + { + "path": "/Users/jeff/Desktop/policyserver.txt" + } + ], + "settings": + { + "tab_size": 2, + "translate_tabs_to_spaces": true + } } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a55e2fdc..a9a537b4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,10 +1,27 @@ # Project modules source tree root -# IDE project generators such as Xcode and MSVC will append the build -# configuration type, i.e.: '/Debug' or -# '/Release' to this output path. -set( CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR} ) -set( CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR} ) +# Use common build output directories for MSVCPP && Xcode project files. +# +# IMPORTANT: Debug and Release build targets **must** be kept in separate build +# trees! +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}/Debug") +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/Release") + +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}/Debug") +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/Release") + +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}/Debug") +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/Release") + +if(DEBUG) + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_LIBRARY_OUTPUT_DIRECTORY_DEBUG}) + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG}) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG}) +else() # Release builds + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELEASE}) + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE}) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE}) +endif() # Third-party dependencies @@ -25,11 +42,6 @@ if( NOM_BUILD_GUI_UNIT ) # set( NOM_USE_LIBROCKET_LUA FALSE ) endif( NOM_BUILD_GUI_UNIT ) -if( NOM_BUILD_AUDIO_UNIT ) - set( NOM_USE_OPENAL TRUE ) - set( NOM_USE_LIBSNDFILE TRUE ) -endif( NOM_BUILD_AUDIO_UNIT ) - if( NOM_BUILD_EXTRA_RESCALE_ALGO_UNIT ) set( NOM_USE_SCALEX TRUE ) set( NOM_USE_HQX TRUE ) @@ -72,7 +84,9 @@ if( NOM_BUILD_SYSTEM_UNIT ) endif( NOM_BUILD_SYSTEM_UNIT ) # nomlib-graphics -add_subdirectory("graphics") +if(NOM_BUILD_GRAPHICS_UNIT) + add_subdirectory("graphics") +endif(NOM_BUILD_GRAPHICS_UNIT) # nomlib-gui if( NOM_BUILD_GUI_UNIT ) @@ -80,4 +94,32 @@ if( NOM_BUILD_GUI_UNIT ) endif( NOM_BUILD_GUI_UNIT ) # nomlib-audio -add_subdirectory("audio") +if(NOM_BUILD_AUDIO_UNIT) + # TODO(jeff): Consider setting NOM_USE_OPENAL when one of these three are + # set; it might also be good to add a check inside the OpenAL header file + # ensuring that only one of these three options are toggled at any given + # time. + + # NOTE(jeff): OpenAL-Soft offers quad, 5.1, 6.1 and 7.1 channel + # configurations. + # + # http://openal.996291.n3.nabble.com/Openal-S-PDIF-and-surround-td2410.html + # http://steamcommunity.com/sharedfiles/filedetails/?id=230642515 + set(NOM_USE_CREATIVE_OPENAL FALSE) + set(NOM_USE_OPENAL_SOFT FALSE) + set(NOM_USE_LIBSNDFILE TRUE) + + if ( PLATFORM_POSIX ) + set(NOM_USE_OPENAL_SOFT TRUE) + endif ( PLATFORM_POSIX ) + + if ( PLATFORM_OSX ) + set(NOM_USE_APPLE_OPENAL TRUE) + endif ( PLATFORM_OSX ) + + add_subdirectory("audio") +endif(NOM_BUILD_AUDIO_UNIT) + +if(NOM_BUILD_ACTIONS_UNIT) + add_subdirectory("actions") +endif(NOM_BUILD_ACTIONS_UNIT) diff --git a/src/actions/ActionPlayer.cpp b/src/actions/ActionPlayer.cpp new file mode 100644 index 00000000..17f6468c --- /dev/null +++ b/src/actions/ActionPlayer.cpp @@ -0,0 +1,265 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#include "nomlib/actions/ActionPlayer.hpp" + +// Forward declarations +#include "nomlib/actions/IActionObject.hpp" +#include "nomlib/actions/DispatchQueue.hpp" + +namespace nom { + +// A unique identifier that is auto-generated for actions without an assigned +// name. +static uint64 next_action_id_ = 0; + +static uint64 generate_action_id() +{ + return( ++(next_action_id_) ); +} + +// Static initializations +const char* ActionPlayer::DEBUG_CLASS_NAME = "[ActionPlayer]:"; + +ActionPlayer::ActionPlayer() : + player_state_(ActionPlayer::State::RUNNING) +{ + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE_ACTION, NOM_LOG_PRIORITY_VERBOSE); +} + +ActionPlayer::~ActionPlayer() +{ + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE_ACTION, NOM_LOG_PRIORITY_VERBOSE); +} + +bool ActionPlayer::idle() const +{ + return(this->actions_.size() == 0 && this->free_list_.size() == 0); +} + +nom::size_type ActionPlayer::num_actions() const +{ + return this->actions_.size(); +} + +ActionPlayer::State ActionPlayer::player_state() const +{ + return this->player_state_; +} + +void ActionPlayer::pause() +{ + this->player_state_ = ActionPlayer::State::PAUSED; +} + +void ActionPlayer::resume() +{ + this->player_state_ = ActionPlayer::State::RUNNING; +} + +void ActionPlayer::stop() +{ + this->player_state_ = ActionPlayer::State::STOPPED; +} + +bool ActionPlayer::action_running(const std::string& action_id) const +{ + auto res = this->actions_.find(action_id); + + if( res == this->actions_.end() ) { + // The action is **not** running + return false; + } else { + // The action is still running + return true; + } +} + +bool ActionPlayer::cancel_action(const std::string& action_id) +{ + auto res = this->actions_.find(action_id); + + if( res == this->actions_.end() ) { + // Err -- no action by that name found + return false; + } else { + + // Success -- action was found + this->actions_.erase(res); + + return true; + } + + // Err -- no action by that name found + return false; +} + +void +ActionPlayer::cancel_actions(const ActionPlayer::action_names& actions) +{ + for( auto itr = actions.begin(); itr != actions.end(); ++itr ) { + this->cancel_action(*itr); + } +} + +void ActionPlayer::cancel_actions() +{ + this->free_list_.clear(); + this->actions_.clear(); +} + +bool ActionPlayer::run_action(const std::shared_ptr& action) +{ + return this->run_action(action, nullptr); +} + +bool ActionPlayer:: +run_action( const std::shared_ptr& action, + const action_callback_func& completion_func ) +{ + std::string action_id; + + auto dispatch_queue = + nom::create_dispatch_queue(); + if( dispatch_queue != nullptr ) { + return this->run_action(action, std::move(dispatch_queue), completion_func); + } + + if( action != nullptr ) { + action_id = action->name(); + } + + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, "Failed to enqueue action: ", + "could not allocate memory for the dispatch queue!\n", + "[action_id]:", action_id ); + return false; +} + +bool ActionPlayer::update(real32 delta_time) +{ + ActionPlayer::State player_state = this->player_state(); + DispatchQueue::State dispatch_running = DispatchQueue::State::IDLING; + + // Process the queue in FIFO order + for( auto itr = this->actions_.begin(); itr != this->actions_.end(); ++itr ) { + + auto action_id = itr->first; + auto action_queue = itr->second.get(); + + // This is a valid condition; enqueued actions are subject to being removed + // before their completion, i.e.: ::run_action will happily overwrite + // existing action keys + if( action_queue == nullptr ) { + NOM_LOG_DEBUG( NOM_LOG_CATEGORY_ACTION_PLAYER, DEBUG_CLASS_NAME, + "enqueue erasable (NULL)", "[action_id]:", action_id ); + + this->free_list_.emplace_back(itr); + } else { + + dispatch_running = (action_queue)->update(player_state, delta_time); + + if( dispatch_running == DispatchQueue::State::IDLING ) { + NOM_LOG_DEBUG( NOM_LOG_CATEGORY_ACTION_PLAYER, DEBUG_CLASS_NAME, + "enqueue erasable", "[action_id]:", action_id ); + + this->free_list_.emplace_back(itr); + } // end if IDLE + } // end if action queue is valid + } // end for loop + + + // Erase the actions from the queue in LIFO order + while( this->free_list_.empty() == false ) { + auto res = this->free_list_.front(); + + NOM_LOG_DEBUG( NOM_LOG_CATEGORY_ACTION_PLAYER, DEBUG_CLASS_NAME, + "erasing action", "[action_id]:", res->first ); + + this->actions_.erase(res); + this->free_list_.pop_front(); + } + + if( this->actions_.empty() == true ) { + // Finished update iterations; all actions are completed + return false; + } + + // Not finished with update iterations; one or more actions are still running + return true; +} + +// Private scope + +bool ActionPlayer:: +run_action( const std::shared_ptr& action, + std::unique_ptr dispatch_queue, + const action_callback_func& completion_func ) +{ + std::string action_id; + + if( action == nullptr ) { + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, + "Could not enqueue the action -- action was NULL." ); + return false; + } + + if( dispatch_queue == nullptr ) { + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, + "Could not enqueue the action -- dispatch queue was NULL." ); + return false; + } + + action_id = action->name(); + + if( action_id.length() > 0 ) { + // Use the existing action name + } else { + uint64 id = nom::generate_action_id(); + NOM_ASSERT(id <= std::numeric_limits::max() ); + action_id = std::to_string(id); + action->set_name(action_id); + } + + if( dispatch_queue->enqueue_action(action, completion_func) == false ) { + return false; + } + + // NOTE: This logging category is disabled by default + if( this->action_running(action_id) == true) { + + NOM_LOG_WARN( NOM_LOG_CATEGORY_ACTION_PLAYER, + "Another action with the same name exists -- overwriting", + "with", action_id ); + } // end if action was running + + this->actions_[action_id] = std::move(dispatch_queue); + + return true; +} + +} // namespace nom diff --git a/src/actions/ActionTimingCurves.cpp b/src/actions/ActionTimingCurves.cpp new file mode 100644 index 00000000..9e3e74c0 --- /dev/null +++ b/src/actions/ActionTimingCurves.cpp @@ -0,0 +1,435 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Easing algorithms are Copyright (c) 2001 Robert Penner + +******************************************************************************/ +#include "nomlib/actions/ActionTimingCurves.hpp" + +// Private headers +#include "nomlib/core/strings.hpp" // string helpers +#include "nomlib/math/math_helpers.hpp" // definition of PI + +namespace nom { + +// Ignore warnings generated by the use of the expression ```t /= d``` -- these +// warnings cause no known problems. +#pragma clang diagnostic push +#pragma GCC diagnostic ignored "-Wunsequenced" + +// ...Linear... + +real32 Linear::ease_in(real32 t, real32 b, real32 c, real32 d) +{ + return c * (t /= d) + b; +} + +real32 Linear::ease_out(real32 t, real32 b, real32 c, real32 d) +{ + return c * (t /= d) + b; +} + +real32 Linear::ease_in_out(real32 t, real32 b, real32 c, real32 d) +{ + return c * (t /= d) + b; +} + +// ...Quad... + +real32 Quad::ease_in(real32 t, real32 b, real32 c, real32 d) +{ + return c * (t /= d) * t + b; +} + +real32 Quad::ease_out(real32 t, real32 b, real32 c, real32 d) +{ + return -c * (t /= d) * (t - 2) + b; +} + +real32 Quad::ease_in_out(real32 t, real32 b, real32 c, real32 d) +{ + // if( (t /= d / 2) < 1) { + // return( (c / 2) * (t * t) ) + b; + // } + + // return -c / 2 * ( ((t - 2) * (--t)) - 1) + b; + + /* + originally return -c/2 * (((t-2)*(--t)) - 1) + b; + + I've had to swap (--t)*(t-2) due to diffence in behaviour in + pre-increment operators between java and c++, after hours + of joy + */ + + // The above easing algorithm [1] does not return the proper frame step value + // as per our unit test for this function [2]. I expected to see a return + // value of 300 at elapsed frame marker 1000, but instead got a return value + // of 164. + // + // The easing algorithm below [3] works as expected. + // + // 1.https://github.com/jesusgollonet/ofpennereasing). The easing algorithms + // are the work of [Robbert Penner](http://www.robertpenner.com/easing/). + // 2. ActionTimingCurvesTest::QuadEaseOut + // 3. http://gsgd.co.uk/sandbox/jquery/easing/jquery.easing.1.3.js + if( (t /= d / 2) < 1 ) { + return c / 2 * t * t + b; + } + + return -c / 2 * ( (--t) * (t - 2) - 1) + b; +} + +// ...Cubic... + +real32 Cubic::ease_in(real32 t, real32 b, real32 c, real32 d) +{ + return c * (t /= d) * t * t + b; +} + +real32 Cubic::ease_out(real32 t, real32 b, real32 c, real32 d) +{ + return c * ( (t = t / d - 1) * t * t + 1) + b; +} + +real32 Cubic::ease_in_out(real32 t, real32 b, real32 c, real32 d) +{ + if( (t /= d / 2) < 1) { + return c / 2 * t * t * t + b; + } + + return c / 2 * ( (t -= 2) * t * t + 2) + b; +} + +// ...Quart... + +real32 Quart::ease_in(real32 t, real32 b, real32 c, real32 d) +{ + return c * (t /= d) * t * t * t + b; +} + +real32 Quart::ease_out(real32 t, real32 b, real32 c, real32 d) +{ + return -c * ((t = t / d - 1) * t* t * t - 1) + b; +} + +real32 Quart::ease_in_out(real32 t, real32 b, real32 c, real32 d) +{ + if( (t /= d / 2) < 1) { + return c / 2 * t * t * t * t + b; + } + + return -c / 2 * ((t -= 2) * t* t * t - 2) + b; +} + +// ...Quint... + +real32 Quint::ease_in(real32 t, real32 b, real32 c, real32 d) +{ + return c * (t /= d) * t * t * t * t + b; +} + +real32 Quint::ease_out(real32 t, real32 b, real32 c, real32 d) +{ + return c * ( (t = t / d - 1) * t * t * t * t + 1) + b; +} + +real32 Quint::ease_in_out(real32 t, real32 b, real32 c, real32 d) +{ + if( (t /= d / 2) < 1) { + return c / 2 * t * t * t * t * t + b; + } + + return c / 2 * ((t -= 2) * t * t * t * t + 2) + b; +} + +// ...Back... + +real32 Back::ease_in(real32 t, real32 b, real32 c, real32 d) +{ + float s = 1.70158f; + float postFix = t /= d; + + return c * (postFix) * t * ( (s + 1) * t - s) + b; +} + +real32 Back::ease_out(real32 t, real32 b, real32 c, real32 d) +{ + float s = 1.70158f; + + return c * ( (t = t / d - 1) * t * ( (s + 1) * t + s) + 1) + b; +} + +real32 Back::ease_in_out(real32 t, real32 b, real32 c, real32 d) +{ + float s = 1.70158f; + + if( (t /= d / 2) < 1) { + return c / 2 * (t * t * (((s *= (1.525f)) + 1) * t - s)) + b; + } + + float postFix = t -= 2; + + return c / 2 * ( (postFix) * t * (((s *= (1.525f)) + 1) * t + s) + 2) + b; +} + +// ...Bounce... + +real32 Bounce::ease_in(real32 t, real32 b, real32 c, real32 d) +{ + return c - Bounce::ease_out(d - t, 0, c, d) + b; +} + +real32 Bounce::ease_out(real32 t, real32 b, real32 c, real32 d) +{ + if( (t /= d) < (1 / 2.75f) ) { + return c * (7.5625f * t * t) + b; + } else if( t < (2 / 2.75f) ) { + float postFix = t -= (1.5f / 2.75f); + return c * (7.5625f * (postFix) * t + .75f) + b; + } else if( t < (2.5 / 2.75) ) { + float postFix = t -= (2.25f / 2.75f); + return c * (7.5625f * (postFix) * t + .9375f) + b; + } else { + float postFix = t -= (2.625f / 2.75f); + return c * (7.5625f * (postFix) * t + .984375f) + b; + } +} + +real32 Bounce::ease_in_out(real32 t, real32 b, real32 c, real32 d) +{ + if( t < d/2 ) { + return Bounce::ease_in( t * 2, 0, c, d) * .5f + b; + } else { + return Bounce::ease_out( t * 2 - d, 0, c, d) * .5f + c * .5f + b; + } +} + +// ...Circ... + +real32 Circ::ease_in(real32 t, real32 b, real32 c, real32 d) +{ + return -c * ( sqrt(1 - (t /= d) * t) - 1) + b; +} + +real32 Circ::ease_out(real32 t, real32 b, real32 c, real32 d) +{ + return c * sqrt(1 - (t = t / d - 1) * t) + b; +} + +real32 Circ::ease_in_out(real32 t, real32 b, real32 c, real32 d) +{ + if( (t /= d / 2) < 1) { + return -c / 2 * (sqrt(1 - t * t) - 1) + b; + } + + return c / 2 * (sqrt(1 - t * (t -= 2)) + 1) + b; +} + +// ...Elastic... + +real32 Elastic::ease_in(real32 t, real32 b, real32 c, real32 d) +{ + if(t == 0) { + return b; + } + if( (t /= d) == 1) { + return b + c; + } + + float p = d * .3f; + float a = c; + float s = p / 4; + // this is a fix, again, with post-increment operators + float postFix = a * pow(2, 10 * (t -= 1)); + + return -(postFix * sin( (t * d - s) * (2 * PI) / p ) ) + b; +} + +real32 Elastic::ease_out(real32 t, real32 b, real32 c, real32 d) +{ + if(t == 0) { + return b; + } + if( (t /= d) == 1) { + return b + c; + } + + float p = d * .3f; + float a = c; + float s = p / 4; + + return( a * pow(2, -10 * t) * sin( (t * d - s) * (2 * PI) / p ) + c + b ); +} + +real32 Elastic::ease_in_out(real32 t, real32 b, real32 c, real32 d) +{ + if(t == 0) { + return b; + } + if( (t /= d / 2) == 2) { + return b + c; + } + + float p = d * (.3f * 1.5f); + float a = c; + float s = p / 4; + + if(t < 1) { + float postFix = a * pow(2, 10 * (t -= 1)); // postIncrement is evil + return -.5f * (postFix * sin( (t * d - s) * (2 * PI) / p )) + b; + } + + float postFix = a * pow(2, -10 * (t -= 1)); // postIncrement is evil + + return postFix * sin( (t * d - s) * (2 * PI) / p ) * .5f + c + b; +} + +// ...Expo... + +real32 Expo::ease_in(real32 t, real32 b, real32 c, real32 d) +{ + return(t == 0) ? b : c * pow(2, 10 * (t/d - 1) ) + b; +} + +real32 Expo::ease_out(real32 t, real32 b, real32 c, real32 d) +{ + return(t == d) ? b+c : c * (-pow(2, -10 * t/d) + 1) + b; +} + +real32 Expo::ease_in_out(real32 t, real32 b, real32 c, real32 d) +{ + if(t == 0) { + return b; + } + + if(t == d) { + return b+c; + } + + if( (t/=d/2) < 1) { + return c/2 * pow(2, 10 * (t - 1) ) + b; + } + + return c/2 * ( -pow(2, -10 * --t) + 2) + b; +} + +// ...Sine... + +real32 Sine::ease_in(real32 t, real32 b, real32 c, real32 d) +{ + return -c * cos(t / d * (PI / 2) ) + c + b; +} + +real32 Sine::ease_out(real32 t, real32 b, real32 c, real32 d) +{ + return c * sin(t / d * (PI / 2) ) + b; +} + +real32 Sine::ease_in_out(real32 t, real32 b, real32 c, real32 d) +{ + return -c / 2 * ( cos(PI* t / d) - 1) + b; +} + +#pragma clang diagnostic pop + +std::function +make_timing_curve_from_string(const std::string& timing_mode) +{ + // Default timing mode + auto mode = nom::Linear::ease_in_out; + + if( nom::compare_string_insensitive(timing_mode, "linear_ease_in") == 0 ) { + mode = nom::Linear::ease_in; + } else if( nom::compare_string_insensitive(timing_mode, "linear_ease_out") == 0 ) { + mode = nom::Linear::ease_out; + } else if( nom::compare_string_insensitive(timing_mode, "quad_ease_in") == 0 ) { + mode = nom::Quad::ease_in; + } else if( nom::compare_string_insensitive(timing_mode, "quad_ease_out") == 0 ) { + mode = nom::Quad::ease_out; + } else if( nom::compare_string_insensitive(timing_mode, "quad_ease_in_out") == 0 ) { + mode = nom::Quad::ease_in_out; + } else if( nom::compare_string_insensitive(timing_mode, "cubic_ease_in") == 0 ) { + mode = nom::Cubic::ease_in; + } else if( nom::compare_string_insensitive(timing_mode, "cubic_ease_out") == 0 ) { + mode = nom::Cubic::ease_out; + } else if( nom::compare_string_insensitive(timing_mode, "cubic_ease_in_out") == 0 ) { + mode = nom::Cubic::ease_in_out; + } else if( nom::compare_string_insensitive(timing_mode, "quart_ease_in") == 0 ) { + mode = nom::Quart::ease_in; + } else if( nom::compare_string_insensitive(timing_mode, "quart_ease_out") == 0 ) { + mode = nom::Quart::ease_out; + } else if( nom::compare_string_insensitive(timing_mode, "quart_ease_in_out") == 0 ) { + mode = nom::Quart::ease_in_out; + } else if( nom::compare_string_insensitive(timing_mode, "quint_ease_in") == 0 ) { + mode = nom::Quint::ease_in; + } else if( nom::compare_string_insensitive(timing_mode, "quint_ease_out") == 0 ) { + mode = nom::Quint::ease_out; + } else if( nom::compare_string_insensitive(timing_mode, "quint_ease_in_out") == 0 ) { + mode = nom::Quint::ease_in_out; + } else if( nom::compare_string_insensitive(timing_mode, "back_ease_in") == 0 ) { + mode = nom::Back::ease_in; + } else if( nom::compare_string_insensitive(timing_mode, "back_ease_out") == 0 ) { + mode = nom::Back::ease_out; + } else if( nom::compare_string_insensitive(timing_mode, "back_ease_in_out") == 0 ) { + mode = nom::Back::ease_in_out; + } else if( nom::compare_string_insensitive(timing_mode, "bounce_ease_in") == 0 ) { + mode = nom::Bounce::ease_in; + } else if( nom::compare_string_insensitive(timing_mode, "bounce_ease_out") == 0 ) { + mode = nom::Bounce::ease_out; + } else if( nom::compare_string_insensitive(timing_mode, "bounce_ease_in_out") == 0 ) { + mode = nom::Bounce::ease_in_out; + } else if( nom::compare_string_insensitive(timing_mode, "circ_ease_in") == 0 ) { + mode = nom::Circ::ease_in; + } else if( nom::compare_string_insensitive(timing_mode, "circ_ease_out") == 0 ) { + mode = nom::Circ::ease_out; + } else if( nom::compare_string_insensitive(timing_mode, "circ_ease_in_out") == 0 ) { + mode = nom::Circ::ease_in_out; + } else if( nom::compare_string_insensitive(timing_mode, "elastic_ease_in") == 0 ) { + mode = nom::Elastic::ease_in; + } else if( nom::compare_string_insensitive(timing_mode, "elastic_ease_out") == 0 ) { + mode = nom::Elastic::ease_out; + } else if( nom::compare_string_insensitive(timing_mode, "elastic_ease_in_out") == 0 ) { + mode = nom::Elastic::ease_in_out; + } else if( nom::compare_string_insensitive(timing_mode, "expo_ease_in") == 0 ) { + mode = nom::Expo::ease_in; + } else if( nom::compare_string_insensitive(timing_mode, "expo_ease_out") == 0 ) { + mode = nom::Expo::ease_out; + } else if( nom::compare_string_insensitive(timing_mode, "expo_ease_in_out") == 0 ) { + mode = nom::Expo::ease_in_out; + } else if( nom::compare_string_insensitive(timing_mode, "sine_ease_in") == 0 ) { + mode = nom::Sine::ease_in; + } else if( nom::compare_string_insensitive(timing_mode, "sine_ease_out") == 0 ) { + mode = nom::Sine::ease_out; + } else if( nom::compare_string_insensitive(timing_mode, "sine_ease_in_out") == 0 ) { + mode = nom::Sine::ease_in_out; + } + + return mode; +} + +} // namespace nom diff --git a/src/actions/AnimateTexturesAction.cpp b/src/actions/AnimateTexturesAction.cpp new file mode 100644 index 00000000..68f4684b --- /dev/null +++ b/src/actions/AnimateTexturesAction.cpp @@ -0,0 +1,284 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#include "nomlib/actions/AnimateTexturesAction.hpp" + +// Private headers +#include "nomlib/core/unique_ptr.hpp" +#include "nomlib/math/math_helpers.hpp" + +// Forward declarations +#include "nomlib/graphics/Texture.hpp" +#include "nomlib/graphics/sprite/Sprite.hpp" + +namespace nom { + +enum FrameStateDirection +{ + NEXT_FRAME, + PREV_FRAME +}; + +// Static initializations +const char* AnimateTexturesAction::DEBUG_CLASS_NAME = + "[AnimateTexturesAction]:"; + +void AnimateTexturesAction:: +initialize(const texture_frames& textures, real32 frame_interval_seconds) +{ + + this->elapsed_frames_ = 0.0f; + this->initial_frame_ = 0; + this->frame_interval_ = frame_interval_seconds; + this->last_delta_ = 0.0f; + + this->frames_ = textures; + NOM_ASSERT(this->frames_.size() > 0); + this->total_displacement_ = this->frames_.size(); + + real32 action_duration_seconds = + (this->frame_interval_ * this->total_displacement_); + this->set_duration(action_duration_seconds); +} + +AnimateTexturesAction:: +AnimateTexturesAction( const std::shared_ptr& drawable, + const texture_frames& textures, + real32 frame_interval_seconds ) +{ + NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_ACTION, + nom::NOM_LOG_PRIORITY_VERBOSE ); + + this->drawable_ = drawable; + this->initialize(textures, frame_interval_seconds); +} + +AnimateTexturesAction::~AnimateTexturesAction() +{ + NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_ACTION, + nom::NOM_LOG_PRIORITY_VERBOSE ); +} + +std::unique_ptr AnimateTexturesAction::clone() const +{ + return( nom::make_unique( self_type(*this) ) ); +} + +IActionObject::FrameState +AnimateTexturesAction::update(real32 t, real32 b, real32 c, real32 d) +{ + real32 frame_interval = + this->frame_interval_ / this->speed(); + + // The current frame to scale + real32 delta_time = t; + + // Total duration of the action + const real32 duration = d; + + // The computed texture frame to show next + real32 displacement(0.0f); + uint32 displacement_as_integer = 0; + + // Clamp delta values that go beyond maximal duration + if( delta_time > (duration / this->speed() ) ) { + delta_time = duration / this->speed(); + } + + FrameStateDirection direction; + if( c >= 0 ) { + direction = NEXT_FRAME; + } else { + direction = PREV_FRAME; + } + + // Apply speed scalar onto current frame time + real32 frame_time = delta_time * this->speed(); + + NOM_ASSERT(this->timing_curve() != nullptr); + + displacement = + this->timing_curve().operator()(frame_time, b, c, duration); + NOM_ASSERT(displacement <= this->total_displacement_); + NOM_ASSERT(displacement >= this->initial_frame_); + + if( delta_time >= (this->last_delta_ + frame_interval) && + delta_time < ( duration / this->speed() ) ) + { + this->elapsed_frames_ = displacement; + this->last_delta_ = delta_time; + + frame_iterator curr_frame; + if( direction == NEXT_FRAME ) { + ++this->next_frame_; + if( this->next_frame_ == (this->frames_.end() - 1) ) { + this->next_frame_ = + (this->frames_.begin() + this->initial_frame_); + } + + curr_frame = this->next_frame_; + } else if( direction == PREV_FRAME ) { + + --this->last_frame_; + if( this->last_frame_ == this->frames_.begin() ) { + this->last_frame_ = (this->frames_.end() - 1); + } + + curr_frame = this->last_frame_; + } + + NOM_ASSERT( curr_frame != this->frames_.end() ); + if( curr_frame != this->frames_.end() ) { + auto tex = curr_frame; + + if( this->drawable_ != nullptr && (*tex)->valid() == true ) { + this->drawable_->set_texture( (*tex) ); + } + } + + displacement_as_integer = + nom::round_float_down(displacement); + NOM_LOG_DEBUG( NOM_LOG_CATEGORY_ACTION, DEBUG_CLASS_NAME, + "delta_time:", delta_time, "frame_time:", frame_time, + "[elapsed frames]:", this->elapsed_frames_, + "displacement (output):", displacement_as_integer ); + + } + + if( delta_time < (duration / this->speed() ) ) { + this->set_status(FrameState::PLAYING); + } else { + this->last_frame(delta_time); + this->set_status(FrameState::COMPLETED); + } + + return this->status(); +} + +IActionObject::FrameState AnimateTexturesAction::next_frame(real32 delta_time) +{ + delta_time = ( Timer::to_seconds( this->timer_.ticks() ) ); + + this->first_frame(delta_time); + + return( this->update( delta_time, this->initial_frame_, + this->total_displacement_, this->duration() ) ); +} + +IActionObject::FrameState AnimateTexturesAction::prev_frame(real32 delta_time) +{ + delta_time = ( Timer::to_seconds( this->timer_.ticks() ) ); + + this->first_frame(delta_time); + + return( this->update( delta_time, this->total_displacement_, + -(this->total_displacement_), this->duration() ) ); +} + +void AnimateTexturesAction::pause(real32 delta_time) +{ + this->timer_.pause(); +} + +void AnimateTexturesAction::resume(real32 delta_time) +{ + this->timer_.unpause(); +} + +void AnimateTexturesAction::rewind(real32 delta_time) +{ + this->elapsed_frames_ = 0.0f; + this->initial_frame_ = 0; + this->next_frame_ = (this->frames_.begin() + this->initial_frame_); + this->last_frame_ = (this->frames_.end() - 1); + this->last_delta_ = 0.0f; + this->timer_.stop(); + + this->set_status(FrameState::PLAYING); + + auto curr_frame = this->next_frame_; + NOM_ASSERT( curr_frame != this->frames_.end() ); + if( this->drawable_ != nullptr && + curr_frame != this->frames_.end() ) + { + auto tex = curr_frame; + if( this->drawable_ != nullptr && (*tex)->valid() == true ) { + this->drawable_->set_texture( (*tex) ); + } + } +} + +void AnimateTexturesAction::release() +{ + if( this->drawable_ != nullptr ) { + this->drawable_->release_texture(); + } + + this->drawable_.reset(); +} + +// Private scope + +void AnimateTexturesAction::first_frame(real32 delta_time) +{ + if( this->timer_.started() == false ) { + this->elapsed_frames_ = 0.0f; + this->initial_frame_ = 0; + this->last_delta_ = 0.0f; + this->next_frame_ = (this->frames_.begin() + this->initial_frame_); + this->last_frame_ = (this->frames_.end() - 1); + this->timer_.start(); + + NOM_LOG_DEBUG( NOM_LOG_CATEGORY_ACTION, DEBUG_CLASS_NAME, + "BEGIN at", delta_time ); + + auto curr_frame = this->next_frame_; + NOM_ASSERT( curr_frame != this->frames_.end() ); + if( curr_frame != this->frames_.end() ) { + + auto tex = curr_frame; + // NOTE: Set the texture of the sprite immediately, so we do not have a + // momentary gap in rendering + if( this->drawable_ != nullptr && (*tex)->valid() == true ) { + this->drawable_->set_texture( (*tex) ); + } + } + + NOM_LOG_DEBUG( NOM_LOG_CATEGORY_ACTION, + "[initial_frame]:", this->initial_frame_, + "[num_frames]:", this->total_displacement_, + "[frame_interval]:", this->frame_interval_ ); + } +} + +void AnimateTexturesAction::last_frame(real32 delta_time) +{ + this->timer_.stop(); +} + +} // namespace nom diff --git a/src/actions/CMakeLists.txt b/src/actions/CMakeLists.txt new file mode 100644 index 00000000..205606d3 --- /dev/null +++ b/src/actions/CMakeLists.txt @@ -0,0 +1,80 @@ +# nomlib-actions library + +if(NOM_BUILD_ACTIONS_UNIT) + # Library name + set(NOM_ACTIONS_LIBRARY "nomlib-actions") + + # Library headers inclusion path + include_directories("${INC_ROOT_DIR}") + + set(NOM_ACTIONS_SOURCE + ${INC_DIR}/actions.hpp + ${SRC_DIR}/actions/ActionTimingCurves.cpp + ${INC_DIR}/actions/ActionTimingCurves.hpp + ${SRC_DIR}/actions/IActionObject.cpp + ${INC_DIR}/actions/IActionObject.hpp + ${SRC_DIR}/actions/ActionPlayer.cpp + ${INC_DIR}/actions/ActionPlayer.hpp + ${SRC_DIR}/actions/CallbackAction.cpp + ${INC_DIR}/actions/CallbackAction.hpp + ${SRC_DIR}/actions/DispatchQueue.cpp + ${INC_DIR}/actions/DispatchQueue.hpp + ${SRC_DIR}/actions/GroupAction.cpp + ${INC_DIR}/actions/GroupAction.hpp + ${SRC_DIR}/actions/RemoveAction.cpp + ${INC_DIR}/actions/RemoveAction.hpp + ${SRC_DIR}/actions/RepeatForAction.cpp + ${INC_DIR}/actions/RepeatForAction.hpp + ${SRC_DIR}/actions/RepeatForeverAction.cpp + ${INC_DIR}/actions/RepeatForeverAction.hpp + ${SRC_DIR}/actions/ReversedAction.cpp + ${INC_DIR}/actions/ReversedAction.hpp + ${SRC_DIR}/actions/SequenceAction.cpp + ${INC_DIR}/actions/SequenceAction.hpp + ${SRC_DIR}/actions/WaitForDurationAction.cpp + ${INC_DIR}/actions/WaitForDurationAction.hpp) + + # Common internal dependencies for actions + set(NOM_ACTIONS_DEPS nomlib-core nomlib-math nomlib-system) + + if(NOM_BUILD_GRAPHICS_UNIT) + list(APPEND NOM_ACTIONS_SOURCE + ${SRC_DIR}/actions/AnimateTexturesAction.cpp + ${INC_DIR}/actions/AnimateTexturesAction.hpp + ${SRC_DIR}/actions/FadeInAction.cpp + ${INC_DIR}/actions/FadeInAction.hpp + ${SRC_DIR}/actions/FadeOutAction.cpp + ${INC_DIR}/actions/FadeOutAction.hpp + ${SRC_DIR}/actions/FadeAlphaByAction.cpp + ${INC_DIR}/actions/FadeAlphaByAction.hpp + ${SRC_DIR}/actions/MoveByAction.cpp + ${INC_DIR}/actions/MoveByAction.hpp + ${SRC_DIR}/actions/ScaleByAction.cpp + ${INC_DIR}/actions/ScaleByAction.hpp + ${SRC_DIR}/actions/SpriteBatchAction.cpp + ${INC_DIR}/actions/SpriteBatchAction.hpp) + + # Internal dependencies for this action subgroup + list(APPEND NOM_ACTIONS_DEPS nomlib-graphics) + endif(NOM_BUILD_GRAPHICS_UNIT) + + if(NOM_BUILD_AUDIO_UNIT) + list(APPEND NOM_ACTIONS_SOURCE + ${SRC_DIR}/actions/FadeAudioGainBy.cpp + ${INC_DIR}/actions/FadeAudioGainBy.hpp + ${SRC_DIR}/actions/PlayAudioSource.cpp + ${INC_DIR}/actions/PlayAudioSource.hpp) + + # Internal dependencies for this action subgroup + list(APPEND NOM_ACTIONS_DEPS nomlib-audio) + endif(NOM_BUILD_AUDIO_UNIT) + + # Add and link the library + nom_add_library( ${NOM_ACTIONS_LIBRARY} ${LIBRARY_OUTPUT_TYPE} + "${NOM_ACTIONS_SOURCE}" "" + "${NOM_ACTIONS_DEPS}" ) + + # Re-declare globally, so other build units that depend on us can refer to + # this variable + # set(NOM_ACTIONS_LIBRARY ${NOM_ACTIONS_LIBRARY} PARENT_SCOPE) +endif(NOM_BUILD_ACTIONS_UNIT) diff --git a/src/actions/CallbackAction.cpp b/src/actions/CallbackAction.cpp new file mode 100644 index 00000000..cee7af19 --- /dev/null +++ b/src/actions/CallbackAction.cpp @@ -0,0 +1,145 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#include "nomlib/actions/CallbackAction.hpp" + +#include "nomlib/core/unique_ptr.hpp" + +namespace nom { + +// Static initializations +const char* CallbackAction::DEBUG_CLASS_NAME = "[CallbackAction]:"; + +CallbackAction::CallbackAction(const callback_func& action) : + action_(action) +{ + NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_ACTION, + nom::NOM_LOG_PRIORITY_VERBOSE ); + + this->set_duration(0.0f); + this->elapsed_frames_ = 0.0f; +} + +CallbackAction::CallbackAction(real32 seconds, const callback_func& action) : + action_(action) +{ + NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_ACTION, + nom::NOM_LOG_PRIORITY_VERBOSE ); + + this->set_duration(seconds); + this->elapsed_frames_ = 0.0f; +} + +CallbackAction::~CallbackAction() +{ + NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_ACTION, + nom::NOM_LOG_PRIORITY_VERBOSE ); +} + +std::unique_ptr CallbackAction::clone() const +{ + return( nom::make_unique( self_type(*this) ) ); +} + +IActionObject::FrameState CallbackAction::next_frame(real32 delta_time) +{ + delta_time = ( Timer::to_seconds( this->timer_.ticks() ) ); + + if( this->timer_.started() == false ) { + this->timer_.start(); + + NOM_LOG_DEBUG( NOM_LOG_CATEGORY_ACTION, DEBUG_CLASS_NAME, + "BEGIN at", delta_time ); + } + + // Clamp delta values that go beyond maximal duration + if( delta_time > (this->duration() / this->speed() ) ) { + delta_time = this->duration() / this->speed(); + } + + // Apply speed scalar onto current frame time + real32 frame_time = delta_time * this->speed(); + + if( this->duration() == 0.0f ) { + + ++this->elapsed_frames_; + + if( this->action_ != nullptr ) { + this->action_.operator()(); + } + + this->set_status(FrameState::COMPLETED); + + } else if( delta_time < (this->duration() / this->speed() ) ) { + + ++this->elapsed_frames_; + + if( this->action_ != nullptr ) { + this->action_.operator()(); + } + + this->set_status(FrameState::PLAYING); + + } else { + this->set_status(FrameState::COMPLETED); + } + + NOM_LOG_DEBUG( NOM_LOG_CATEGORY_ACTION, DEBUG_CLASS_NAME, + "delta_time:", delta_time, "frame_time:", frame_time, + "[elapsed frames]:", this->elapsed_frames_ ); + + return this->status(); +} + +IActionObject::FrameState CallbackAction::prev_frame(real32 delta_time) +{ + // NOTE: This action is not reversible + return this->next_frame(delta_time); +} + +void CallbackAction::pause(real32 delta_time) +{ + // Not supported +} + +void CallbackAction::resume(real32 delta_time) +{ + // Not supported +} + +void CallbackAction::rewind(real32 delta_time) +{ + // Not supported +} + +void CallbackAction::release() +{ + this->action_.~function(); +} + +} // namespace nom diff --git a/src/actions/DispatchQueue.cpp b/src/actions/DispatchQueue.cpp new file mode 100644 index 00000000..2e688692 --- /dev/null +++ b/src/actions/DispatchQueue.cpp @@ -0,0 +1,172 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#include "nomlib/actions/DispatchQueue.hpp" + +#include "nomlib/core/unique_ptr.hpp" + +// Forward declarations +#include "nomlib/actions/IActionObject.hpp" +#include "nomlib/actions/ActionPlayer.hpp" + +namespace nom { + +// Internal representation of an enqueued action. +struct DispatchEnqueue +{ + // The enqueued action. + std::shared_ptr action; + + /// \brief The action's last known state in reference to the player's state. + uint32 last_action_state = ActionPlayer::State::RUNNING; + + // An optional function pointer that is called when the action is completed. + action_callback_func on_completion_callback; +}; + +// Static initializations +const char* DispatchQueue::DEBUG_CLASS_NAME = "[DispatchQueue]:"; + +DispatchQueue::DispatchQueue() +{ + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE_ACTION, NOM_LOG_PRIORITY_VERBOSE); +} + +DispatchQueue::~DispatchQueue() +{ + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE_ACTION, NOM_LOG_PRIORITY_VERBOSE); +} + +nom::size_type DispatchQueue::num_actions() const +{ + return this->num_actions_; +} + +bool DispatchQueue:: +enqueue_action( const std::shared_ptr& action, + const action_callback_func& completion_func ) +{ + auto enqueued_action = + nom::make_unique(); + if( enqueued_action == nullptr ) { + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, + "Failed to enqueue the action:", + "could not allocate memory for the dispatch entry!" ); + return false; + } + + enqueued_action->action = action; + enqueued_action->on_completion_callback = completion_func; + + this->actions_.emplace_back( std::move(enqueued_action) ); + this->actions_iterator_ = this->actions_.begin(); + ++this->num_actions_; + + return true; +} + +DispatchQueue::State +DispatchQueue::update(uint32 player_state, real32 delta_time) +{ + auto itr = this->actions_iterator_; + auto actions_end = this->actions_.end(); + + if( itr == actions_end ) { + // Finished updating; nothing left to do + return State::IDLING; + } + + auto action = (*itr)->action; + if( action == nullptr ) { + // Finished updating; nothing left to do + return State::IDLING; + } + + uint32 &last_action_state = (*itr)->last_action_state; + + IActionObject::FrameState action_status = + // IActionObject::FrameState::COMPLETED; + + action_status = action->next_frame(delta_time); + + // Handle the current action state with respect to the global player state + if( action_status != IActionObject::FrameState::COMPLETED ) { + + if( player_state == ActionPlayer::State::PAUSED && + last_action_state != ActionPlayer::State::PAUSED ) + { + action->pause(delta_time); + last_action_state = + ActionPlayer::State::PAUSED; + } else if( player_state == ActionPlayer::State::STOPPED && + last_action_state != ActionPlayer::State::STOPPED ) + { + action->rewind(delta_time); + last_action_state = + ActionPlayer::State::STOPPED; + } else if( player_state == ActionPlayer::State::RUNNING && + last_action_state != ActionPlayer::State::RUNNING ) + { + action->resume(delta_time); + last_action_state = + ActionPlayer::State::RUNNING; + } // end if status != COMPLETED + } + + // EOF -- handle internal clean up + if( action_status == IActionObject::FrameState::COMPLETED ) { + + std::string action_id = action->name(); + action_callback_func completion_func = + (*itr)->on_completion_callback; + + --this->num_actions_; + ++this->actions_iterator_; + + NOM_LOG_DEBUG( NOM_LOG_CATEGORY_ACTION_QUEUE, DEBUG_CLASS_NAME, + "erasing:", action_id, "[remaining_actions]:", + this->num_actions_ ); + + NOM_ASSERT(this->num_actions_ >= 0); + + // Holla back + if( completion_func != nullptr ) { + completion_func.operator()(); + } + } // end if FrameState::COMPLETED + + if( this->actions_iterator_ == actions_end ) { + // Finished update cycle + return State::IDLING; + } else { + // Update cycle is **not** finished + return State::RUNNING; + } +} + +} // namespace nom diff --git a/src/actions/FadeAlphaByAction.cpp b/src/actions/FadeAlphaByAction.cpp new file mode 100644 index 00000000..89021dec --- /dev/null +++ b/src/actions/FadeAlphaByAction.cpp @@ -0,0 +1,204 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#include "nomlib/actions/FadeAlphaByAction.hpp" + +// Private headers +#include "nomlib/core/unique_ptr.hpp" +#include "nomlib/math/Color4.hpp" +#include "nomlib/math/math_helpers.hpp" + +// Forward declarations +#include "nomlib/graphics/sprite/Sprite.hpp" + +namespace nom { + +// Static initializations +const char* FadeAlphaByAction::DEBUG_CLASS_NAME = "[FadeAlphaByAction]:"; + +FadeAlphaByAction::FadeAlphaByAction( const std::shared_ptr& drawable, + real32 delta, real32 seconds ) : + total_displacement_(delta) +{ + NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_ACTION, + nom::NOM_LOG_PRIORITY_VERBOSE ); + + this->set_duration(seconds); + this->elapsed_frames_ = 0.0f; + this->drawable_ = drawable; +} + +FadeAlphaByAction::~FadeAlphaByAction() +{ + NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_ACTION, + nom::NOM_LOG_PRIORITY_VERBOSE ); +} + +std::unique_ptr FadeAlphaByAction::clone() const +{ + return( nom::make_unique( self_type(*this) ) ); +} + +IActionObject::FrameState +FadeAlphaByAction::update(real32 t, real32 b, real32 c, real32 d) +{ + real32 delta_time = t; + const real32 duration = d; + + // initial starting value + const real32 b1 = b; + + // Total change over time + real32 c1 = c; + + real32 displacement = 0.0f; + real32 displacement_as_rgba = 0.0f; + + // Clamp delta values that go beyond maximal duration + if( delta_time > (duration / this->speed() ) ) { + delta_time = duration / this->speed(); + } + + // Apply speed scalar onto current frame time + real32 frame_time = delta_time * this->speed(); + + NOM_ASSERT(this->timing_curve() != nullptr); + + displacement = + this->timing_curve().operator()(frame_time, b1, c1, duration); + + if( this->drawable_ != nullptr ) { + + ++this->elapsed_frames_; + + // Convert the floating-point value to an unsigned 8-bit RGBA value + displacement_as_rgba = + nom::absolute_real32( (displacement / 255) * 255); + + this->drawable_->set_alpha(displacement_as_rgba); + this->alpha_ = displacement_as_rgba; + + NOM_LOG_DEBUG( NOM_LOG_CATEGORY_ACTION, DEBUG_CLASS_NAME, + "delta_time:", delta_time, "frame_time:", frame_time, + "[elapsed frames]:", this->elapsed_frames_ ); + + NOM_LOG_DEBUG( NOM_LOG_CATEGORY_ACTION, + "alpha (input):", + NOM_SCAST(int, this->drawable_->alpha() ), + "displacement (output):", displacement_as_rgba ); + + NOM_ASSERT(displacement_as_rgba <= Color4i::ALPHA_OPAQUE); + NOM_ASSERT(displacement_as_rgba >= Color4i::ALPHA_TRANSPARENT); + } + + if( delta_time < (duration / this->speed() ) ) { + this->set_status(FrameState::PLAYING); + } else { + this->last_frame(delta_time); + this->set_status(FrameState::COMPLETED); + } + + return this->status(); +} + +IActionObject::FrameState FadeAlphaByAction::next_frame(real32 delta_time) +{ + delta_time = ( Timer::to_seconds( this->timer_.ticks() ) ); + + this->first_frame(delta_time); + + return this->update( delta_time, this->initial_alpha_, + this->total_displacement_, this->duration() ); +} + +IActionObject::FrameState FadeAlphaByAction::prev_frame(real32 delta_time) +{ + delta_time = ( Timer::to_seconds( this->timer_.ticks() ) ); + + this->first_frame(delta_time); + + return this->update( delta_time, this->initial_alpha_, + -(this->total_displacement_), this->duration() ); +} + +void FadeAlphaByAction::pause(real32 delta_time) +{ + this->timer_.pause(); +} + +void FadeAlphaByAction::resume(real32 delta_time) +{ + this->timer_.unpause(); +} + +void FadeAlphaByAction::rewind(real32 delta_time) +{ + this->elapsed_frames_ = 0.0f; + this->alpha_ = 0; + this->timer_.stop(); + this->set_status(FrameState::PLAYING); + + if( this->drawable_ != nullptr ) { + this->drawable_->set_alpha(this->initial_alpha_); + } +} + +void FadeAlphaByAction::release() +{ + if( this->drawable_ != nullptr ) { + this->drawable_->release_texture(); + } + + this->drawable_.reset(); +} + +// Private scope + +void FadeAlphaByAction::first_frame(real32 delta_time) +{ + if( this->timer_.started() == false ) { + this->timer_.start(); + + NOM_LOG_DEBUG( NOM_LOG_CATEGORY_ACTION, DEBUG_CLASS_NAME, + "BEGIN at", delta_time ); + + if( this->drawable_ != nullptr ) { + this->initial_alpha_ = this->drawable_->alpha(); + } + + NOM_LOG_DEBUG( NOM_LOG_CATEGORY_ACTION, "initial_alpha:", + NOM_SCAST(int, this->initial_alpha_) ); + } +} + +void FadeAlphaByAction::last_frame(real32 delta_time) +{ + this->timer_.stop(); +} + +} // namespace nom diff --git a/src/actions/FadeAudioGainBy.cpp b/src/actions/FadeAudioGainBy.cpp new file mode 100644 index 00000000..e2776aa8 --- /dev/null +++ b/src/actions/FadeAudioGainBy.cpp @@ -0,0 +1,249 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014, 2015, 2016 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#include "nomlib/actions/FadeAudioGainBy.hpp" + +// Private headers +#include "nomlib/core/unique_ptr.hpp" +#include "nomlib/math/math_helpers.hpp" +#include "nomlib/audio/audio_defs.hpp" + +// Forward declarations +#include "nomlib/audio/SoundBuffer.hpp" +#include "nomlib/audio/SoundFile.hpp" +#include "nomlib/audio/AL/SoundSource.hpp" + +namespace nom { + +// Static initializations +const char* FadeAudioGainBy::DEBUG_CLASS_NAME = "[FadeAudioGainBy]:"; + +FadeAudioGainBy:: +FadeAudioGainBy(audio::IOAudioEngine* dev, const char* filename, real32 delta, + real32 duration) + : total_displacement_(delta) +{ + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE_ACTION, + nom::NOM_LOG_PRIORITY_VERBOSE); + + this->impl_ = dev; + this->elapsed_frames_ = 0.0f; + this->audible_ = audio::create_buffer(filename, this->impl_); + // TODO(jeff): Validity check..? + + this->set_duration(duration); + this->initial_volume_ = audio::volume(this->audible_, dev); +} + +FadeAudioGainBy:: +FadeAudioGainBy(audio::IOAudioEngine* dev, audio::SoundBuffer* buffer, + real32 delta, real32 duration) + : total_displacement_(delta) +{ + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE_ACTION, + nom::NOM_LOG_PRIORITY_VERBOSE); + + this->impl_ = dev; + this->elapsed_frames_ = 0.0f; + this->audible_ = buffer; + + this->set_duration(duration); + this->initial_volume_ = audio::volume(buffer, dev); +} + +FadeAudioGainBy::~FadeAudioGainBy() +{ + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE_ACTION, + nom::NOM_LOG_PRIORITY_VERBOSE); +} + +std::unique_ptr FadeAudioGainBy::clone() const +{ + return( nom::make_unique( self_type(*this) ) ); +} + +IActionObject::FrameState +FadeAudioGainBy::update(real32 t, uint8 b, int16 c, real32 d) +{ + real32 delta_time = t; + const real32 duration = d; + auto status = this->status(); + const auto speed = this->speed(); + const auto timing_mode = this->timing_curve(); + + real32 b1 = b; + real32 c1 = c; + + // The current displacement value of this frame + real32 gain = 0.0f; + real32 displacement = 0.0f; + + // Clamp values to stay within bounds of the initial value + if(c1 > b1) { + c1 = c1 - b1; + } else { + c1 = -b1; + } + + // Clamp delta values that go beyond the time duration bounds; this adds + // stability to variable time steps + if(delta_time > (duration / speed)) { + delta_time = duration / speed; + } + + // Apply speed scalar onto current frame time + real32 frame_time = delta_time * speed; + + NOM_ASSERT(timing_mode != nullptr); + if(timing_mode != nullptr) { + gain = + timing_mode.operator()(frame_time, b1, c1, duration); + } + + // Update our internal elapsed frames counter (diagnostics + ++this->elapsed_frames_; + + if(this->audible_ != nullptr) { + // FIXME(jeff): The external audio API (us) for gain accepts values between + // 0..100 whereas the internal audio API (OpenAL) expects a value between + // 0..1 + displacement = + nom::absolute_real32((gain / 1.0f) * 0.01f); + displacement *= 100.0f; + audio::set_volume(this->audible_, this->impl_, displacement); + + // Diagnostics + NOM_LOG_DEBUG( NOM_LOG_CATEGORY_ACTION, DEBUG_CLASS_NAME, + "delta_time:", delta_time, "frame_time:", frame_time, + "[elapsed frames]:", this->elapsed_frames_ ); + + NOM_LOG_DEBUG( NOM_LOG_CATEGORY_ACTION, + "volume this frame:", gain, + "output gain:", displacement); + + NOM_ASSERT(displacement <= audio::MAX_VOLUME); + NOM_ASSERT(displacement >= audio::MIN_VOLUME); + } + + // Continue playing the animation only when we are inside our frame duration + // bounds; this adds stability to variable time steps + if(delta_time < (duration / speed)) { + this->set_status(FrameState::PLAYING); + status = this->status(); + } else { + this->last_frame(delta_time); + + this->set_status(FrameState::COMPLETED); + status = this->status(); + } + + return status; +} + +IActionObject::FrameState FadeAudioGainBy::next_frame(real32 delta_time) +{ + delta_time = this->timer_.to_seconds(); + + this->first_frame(delta_time); + + return this->update(delta_time, this->initial_volume_, + this->total_displacement_, this->duration()); +} + +IActionObject::FrameState FadeAudioGainBy::prev_frame(real32 delta_time) +{ + delta_time = this->timer_.to_seconds(); + + this->first_frame(delta_time); + + return this->update(delta_time, this->initial_volume_, + -(this->total_displacement_), this->duration()); +} + +void FadeAudioGainBy::pause(real32 delta_time) +{ + // audio::pause(this->audible_, this->impl_); + this->timer_.pause(); +} + +void FadeAudioGainBy::resume(real32 delta_time) +{ + // audio::resume(this->audible_, this->impl_); + this->timer_.unpause(); +} + +void FadeAudioGainBy::rewind(real32 delta_time) +{ + // ...Reset the animation... + this->elapsed_frames_ = 0.0f; + this->timer_.stop(); + this->set_status(FrameState::PLAYING); + + if(this->audible_ != nullptr) { + audio::set_volume(this->audible_, this->impl_, this->initial_volume_); + } + + // audio::stop(this->audible_, this->impl_); +} + +void FadeAudioGainBy::release() +{ + audio::free_buffer(this->audible_, this->impl_); + this->audible_ = nullptr; +} + +// Private scope + +void FadeAudioGainBy::first_frame(real32 delta_time) +{ + if(this->timer_.started() == false) { + this->timer_.start(); + + NOM_LOG_INFO(NOM_LOG_CATEGORY_ACTION, DEBUG_CLASS_NAME, + "BEGIN at", delta_time); + + // ...Set the animation up... + if(this->audible_ != nullptr) { + this->initial_volume_ = audio::volume(this->audible_, this->impl_); + + // Diagnostics + NOM_LOG_INFO(NOM_LOG_CATEGORY_ACTION, + "initial_volume:", this->initial_volume_); + } + + // audio::play(this->audible_, this->impl_); + } +} + +void FadeAudioGainBy::last_frame(real32 delta_time) +{ + // audio::stop(this->audible_, this->impl_); + this->timer_.stop(); +} + +} // namespace nom diff --git a/src/actions/FadeInAction.cpp b/src/actions/FadeInAction.cpp new file mode 100644 index 00000000..05a897a8 --- /dev/null +++ b/src/actions/FadeInAction.cpp @@ -0,0 +1,212 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#include "nomlib/actions/FadeInAction.hpp" + +// Private headers +#include "nomlib/core/unique_ptr.hpp" +#include "nomlib/math/math_helpers.hpp" + +// Forward declarations +#include "nomlib/graphics/sprite/Sprite.hpp" + +namespace nom { + +// Static initializations +const char* FadeInAction::DEBUG_CLASS_NAME = "[FadeInAction]:"; + +FadeInAction::FadeInAction( const std::shared_ptr& drawable, + real32 seconds ) +{ + NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_ACTION, + nom::NOM_LOG_PRIORITY_VERBOSE ); + + this->set_duration(seconds); + + this->elapsed_frames_ = 0.0f; + this->drawable_ = drawable; +} + +FadeInAction::~FadeInAction() +{ + NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_ACTION, + nom::NOM_LOG_PRIORITY_VERBOSE ); +} + +std::unique_ptr FadeInAction::clone() const +{ + return( nom::make_unique( self_type(*this) ) ); +} + +IActionObject::FrameState +FadeInAction::update(real32 t, real32 b, real32 c, real32 d) +{ + real32 delta_time = t; + const real32 duration = d; + + // initial starting value + const real32 b1 = b; + + // Total change over time + real32 c1 = c; + + real32 displacement = 0.0f; + real32 displacement_as_rgba = 0.0f; + + // Clamp delta values that go beyond maximal duration + if( delta_time > (duration / this->speed() ) ) { + delta_time = duration / this->speed(); + } + + // Compute the total difference to blend between + if( c1 > b1 ) { + c1 = c1 - b1; + } else { + c1 = -b1; + } + + // Apply speed scalar onto current frame time + real32 frame_time = delta_time * this->speed(); + + NOM_ASSERT(this->timing_curve() != nullptr); + + displacement = + this->timing_curve().operator()(frame_time, b1, c1, duration); + + if( this->drawable_ != nullptr ) { + + ++this->elapsed_frames_; + + // Convert the floating-point value to an unsigned 8-bit RGBA value + displacement_as_rgba = + nom::absolute_real32( (displacement / 255) * 255 ); + + this->drawable_->set_alpha(displacement_as_rgba); + this->alpha_ = displacement_as_rgba; + + NOM_LOG_DEBUG( NOM_LOG_CATEGORY_ACTION, DEBUG_CLASS_NAME, + "delta_time:", delta_time, "frame_time:", frame_time, + "[elapsed frames]:", this->elapsed_frames_ ); + + NOM_LOG_DEBUG( NOM_LOG_CATEGORY_ACTION, + "alpha (input):", + NOM_SCAST(int, this->drawable_->alpha() ), + "displacement (output):", displacement_as_rgba ); + + NOM_ASSERT(displacement_as_rgba <= Color4i::ALPHA_OPAQUE); + NOM_ASSERT(displacement_as_rgba >= Color4i::ALPHA_TRANSPARENT); + } + + if( delta_time < (duration / this->speed() ) ) { + this->set_status(FrameState::PLAYING); + } else { + NOM_ASSERT( this->alpha_ == Color4i::ALPHA_TRANSPARENT || + this->alpha_ == Color4i::ALPHA_OPAQUE ); + this->last_frame(delta_time); + this->set_status(FrameState::COMPLETED); + } + + return this->status(); +} + +IActionObject::FrameState FadeInAction::next_frame(real32 delta_time) +{ + delta_time = ( Timer::to_seconds( this->timer_.ticks() ) ); + + this->first_frame(delta_time); + + return this->update( delta_time, this->initial_alpha_, + this->total_displacement_, this->duration() ); +} + +IActionObject::FrameState FadeInAction::prev_frame(real32 delta_time) +{ + delta_time = ( Timer::to_seconds( this->timer_.ticks() ) ); + + this->first_frame(delta_time); + + return this->update( delta_time, this->initial_alpha_, + -(this->total_displacement_), this->duration() ); +} + +void FadeInAction::pause(real32 delta_time) +{ + this->timer_.pause(); +} + +void FadeInAction::resume(real32 delta_time) +{ + this->timer_.unpause(); +} + +void FadeInAction::rewind(real32 delta_time) +{ + this->elapsed_frames_ = 0.0f; + this->alpha_ = 0; + this->timer_.stop(); + this->set_status(FrameState::PLAYING); + + if( this->drawable_ != nullptr ) { + this->drawable_->set_alpha(this->initial_alpha_); + } +} + +void FadeInAction::release() +{ + if( this->drawable_ != nullptr ) { + this->drawable_->release_texture(); + } + + this->drawable_.reset(); +} + +// Private scope + +void FadeInAction::first_frame(real32 delta_time) +{ + if( this->timer_.started() == false ) { + this->timer_.start(); + + NOM_LOG_DEBUG( NOM_LOG_CATEGORY_ACTION, DEBUG_CLASS_NAME, + "BEGIN at", delta_time ); + + if( this->drawable_ != nullptr ) { + this->initial_alpha_ = this->drawable_->alpha(); + } + + NOM_LOG_DEBUG( NOM_LOG_CATEGORY_ACTION, "initial_alpha:", + NOM_SCAST(int, this->initial_alpha_) ); + } +} + +void FadeInAction::last_frame(real32 delta_time) +{ + this->timer_.stop(); +} + +} // namespace nom diff --git a/src/actions/FadeOutAction.cpp b/src/actions/FadeOutAction.cpp new file mode 100644 index 00000000..3f02622d --- /dev/null +++ b/src/actions/FadeOutAction.cpp @@ -0,0 +1,213 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#include "nomlib/actions/FadeOutAction.hpp" + +// Private headers +#include "nomlib/core/unique_ptr.hpp" +#include "nomlib/math/math_helpers.hpp" + +// Forward declarations +#include "nomlib/graphics/sprite/Sprite.hpp" + +namespace nom { + +// Static initializations +const char* FadeOutAction::DEBUG_CLASS_NAME = "[FadeOutAction]:"; + +FadeOutAction::FadeOutAction( const std::shared_ptr& drawable, + real32 seconds ) +{ + NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_ACTION, + nom::NOM_LOG_PRIORITY_VERBOSE ); + + this->set_duration(seconds); + + this->elapsed_frames_ = 0.0f; + this->drawable_ = drawable; +} + +FadeOutAction::~FadeOutAction() +{ + NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_ACTION, + nom::NOM_LOG_PRIORITY_VERBOSE ); +} + +std::unique_ptr FadeOutAction::clone() const +{ + return( nom::make_unique( self_type(*this) ) ); +} + +IActionObject::FrameState +FadeOutAction::update(real32 t, real32 b, real32 c, real32 d) +{ + real32 delta_time = t; + const real32 duration = d; + + // initial starting value + const real32 b1 = b; + + // Total change over time + real32 c1 = c; + + real32 displacement = 0.0f; + real32 displacement_as_rgba = 0.0f; + + // Clamp delta values that go beyond maximal duration + if( delta_time > (duration / this->speed() ) ) { + delta_time = duration / this->speed(); + } + + // Compute the total difference to blend between + if( c1 > b1 ) { + c1 = c1 - b1; + } else { + c1 = -b1; + } + + // Apply speed scalar onto current frame time + real32 frame_time = delta_time * this->speed(); + + NOM_ASSERT(this->timing_curve() != nullptr); + + displacement = + this->timing_curve().operator()(frame_time, b1, c1, duration); + + if( this->drawable_ != nullptr ) { + + ++this->elapsed_frames_; + + // Convert the floating-point value to an unsigned 8-bit RGBA value + displacement_as_rgba = + nom::absolute_real32( (displacement / 255) * 255 ); + + this->drawable_->set_alpha(displacement_as_rgba); + this->alpha_ = displacement_as_rgba; + + NOM_LOG_DEBUG( NOM_LOG_CATEGORY_ACTION, DEBUG_CLASS_NAME, + "delta_time:", delta_time, + "frame_time:", frame_time, + "[elapsed frames]:", this->elapsed_frames_ ); + + NOM_LOG_DEBUG( NOM_LOG_CATEGORY_ACTION, + "alpha (input):", + NOM_SCAST(int, this->drawable_->alpha() ), + "displacement (output):", displacement_as_rgba ); + + NOM_ASSERT(displacement_as_rgba <= Color4i::ALPHA_OPAQUE); + NOM_ASSERT(displacement_as_rgba >= Color4i::ALPHA_TRANSPARENT); + } + + if( delta_time < (duration / this->speed() ) ) { + this->set_status(FrameState::PLAYING); + } else { + NOM_ASSERT( this->alpha_ == Color4i::ALPHA_TRANSPARENT || + this->alpha_ == Color4i::ALPHA_OPAQUE ); + this->last_frame(delta_time); + this->set_status(FrameState::COMPLETED); + } + + return this->status(); +} + +IActionObject::FrameState FadeOutAction::next_frame(real32 delta_time) +{ + delta_time = ( Timer::to_seconds( this->timer_.ticks() ) ); + + this->first_frame(delta_time); + + return this->update( delta_time, this->initial_alpha_, + -(this->total_displacement_), this->duration() ); +} + +IActionObject::FrameState FadeOutAction::prev_frame(real32 delta_time) +{ + delta_time = ( Timer::to_seconds( this->timer_.ticks() ) ); + + this->first_frame(delta_time); + + return this->update( delta_time, this->initial_alpha_, + this->total_displacement_, this->duration() ); +} + +void FadeOutAction::pause(real32 delta_time) +{ + this->timer_.pause(); +} + +void FadeOutAction::resume(real32 delta_time) +{ + this->timer_.unpause(); +} + +void FadeOutAction::rewind(real32 delta_time) +{ + this->elapsed_frames_ = 0.0f; + this->alpha_ = 0; + this->timer_.stop(); + this->set_status(FrameState::PLAYING); + + if( this->drawable_ != nullptr ) { + this->drawable_->set_alpha(this->initial_alpha_); + } +} + +void FadeOutAction::release() +{ + if( this->drawable_ != nullptr ) { + this->drawable_->release_texture(); + } + + this->drawable_.reset(); +} + +// Private scope + +void FadeOutAction::first_frame(real32 delta_time) +{ + if( this->timer_.started() == false ) { + this->timer_.start(); + + NOM_LOG_DEBUG( NOM_LOG_CATEGORY_ACTION, DEBUG_CLASS_NAME, + "BEGIN at", delta_time ); + + if( this->drawable_ != nullptr ) { + this->initial_alpha_ = this->drawable_->alpha(); + } + + NOM_LOG_DEBUG( NOM_LOG_CATEGORY_ACTION, "initial_alpha:", + NOM_SCAST(int, this->initial_alpha_) ); + } +} + +void FadeOutAction::last_frame(real32 delta_time) +{ + this->timer_.stop(); +} + +} // namespace nom diff --git a/src/actions/GroupAction.cpp b/src/actions/GroupAction.cpp new file mode 100644 index 00000000..7bdd9fd5 --- /dev/null +++ b/src/actions/GroupAction.cpp @@ -0,0 +1,262 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#include "nomlib/actions/GroupAction.hpp" + +#include "nomlib/core/clock.hpp" +#include "nomlib/core/unique_ptr.hpp" + +namespace nom { + +enum FrameStateDirection +{ + NEXT_FRAME, + PREV_FRAME +}; + +// Internal representation of a stored action. +struct group_action +{ + IActionObject::FrameState status; + std::shared_ptr action; +}; + +// Static initializations +const char* GroupAction::DEBUG_CLASS_NAME = "[GroupAction]:"; + +GroupAction::GroupAction(const action_list& actions) +{ + NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_ACTION, + nom::NOM_LOG_PRIORITY_VERBOSE ); + + group_action entry; + for( auto itr = actions.begin(); itr != actions.end(); ++itr ) { + + entry.action = *itr; + entry.status = FrameState::PLAYING; + this->actions_.emplace_back(entry); + } + + this->num_completed_ = 0; + this->num_actions_ = this->actions_.size(); +} + +GroupAction::~GroupAction() +{ + NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_ACTION, + nom::NOM_LOG_PRIORITY_VERBOSE ); +} + +std::unique_ptr GroupAction::clone() const +{ + auto cloned_obj = nom::make_unique( self_type(*this) ); + if( cloned_obj != nullptr ) { + + cloned_obj->set_status(FrameState::PLAYING); + + cloned_obj->actions_.clear(); + group_action cloned_entry = {}; + auto actions_end = this->actions_.end(); + for( auto itr = this->actions_.begin(); itr != actions_end; ++itr ) { + if( (*itr).action != nullptr ) { + cloned_entry.action = (*itr).action->clone(); + } + + cloned_entry.status = FrameState::PLAYING; + cloned_obj->actions_.emplace_back(cloned_entry); + } + + cloned_obj->num_completed_ = 0; + cloned_obj->num_actions_ = cloned_obj->actions_.size(); + + // IMPORTANT: This is done to prevent the cloned action from being erased + // from a running queue at the same time as the original instance! + cloned_obj->set_name( "__" + this->name() + "_cloned" ); + + return std::move(cloned_obj); + } else { + return nullptr; + } +} + +IActionObject::FrameState +GroupAction::update(real32 delta_time, uint32 direction) +{ + std::string action_id = "action"; + + // Program flow is structured to never call back here after the actions are + // finished -- this serves only as a reminder to the intended flow. + if( this->status() == FrameState::COMPLETED ) { + NOM_ASSERT_INVALID_PATH(); + return this->status(); + } + + for( auto itr = this->actions_.begin(); itr != this->actions_.end(); ++itr ) { + + IActionObject* action = (*itr).action.get(); + FrameState action_status = FrameState::COMPLETED; + + if( action != nullptr ) { + + if( action->name() != "" ) { + action_id = action->name(); + } + + if( direction == FrameStateDirection::NEXT_FRAME ) { + action_status = action->next_frame(delta_time); + } else { + action_status = action->prev_frame(delta_time); + } + } + + if( action_status == FrameState::COMPLETED ) { + + if( (*itr).status != FrameState::COMPLETED ) { + + (*itr).status = FrameState::COMPLETED; + ++this->num_completed_; + + NOM_LOG_DEBUG( NOM_LOG_CATEGORY_ACTION, DEBUG_CLASS_NAME, + action_id, "has finished at", + Timer::to_seconds( nom::ticks() ), + "[", this->num_completed_, "/", this->num_actions_, "]", + "[action_id]:", this->name() ); + } + + } + + if( this->num_completed_ == this->num_actions_ ) { + NOM_LOG_DEBUG( NOM_LOG_CATEGORY_ACTION, DEBUG_CLASS_NAME, + "Finished at:", Timer::to_seconds( nom::ticks() ), + "[num_completed]:", this->num_completed_ ); + this->set_status(FrameState::COMPLETED); + return this->status(); + } else { + this->set_status(FrameState::PLAYING); + } + + } + + return this->status(); +} + +IActionObject::FrameState GroupAction::next_frame(real32 delta_time) +{ + return this->update(delta_time, FrameStateDirection::NEXT_FRAME); +} + +IActionObject::FrameState GroupAction::prev_frame(real32 delta_time) +{ + return this->update(delta_time, FrameStateDirection::PREV_FRAME); +} + +void GroupAction::pause(real32 delta_time) +{ + for( auto itr = this->actions_.begin(); itr != this->actions_.end(); ++itr ) { + + IActionObject* action = (*itr).action.get(); + if( action != nullptr ) { + action->pause(delta_time); + } + } // end for loop +} + +void GroupAction::resume(real32 delta_time) +{ + for( auto itr = this->actions_.begin(); itr != this->actions_.end(); ++itr ) { + + IActionObject* action = (*itr).action.get(); + if( action != nullptr ) { + action->resume(delta_time); + } + } // end for loop +} + +void GroupAction::rewind(real32 delta_time) +{ + this->num_completed_ = 0; + this->set_status(FrameState::PLAYING); + + for( auto itr = this->actions_.begin(); itr != this->actions_.end(); ++itr ) { + + (*itr).status = FrameState::PLAYING; + IActionObject* action = (*itr).action.get(); + if( action != nullptr ) { + action->rewind(delta_time); + } + } // end for loop +} + +void GroupAction::release() +{ + for( auto itr = this->actions_.begin(); itr != this->actions_.end(); ++itr ) { + + IActionObject* action = (*itr).action.get(); + if( action != nullptr ) { + action->release(); + } + } // end for loop +} + +void GroupAction::set_speed(real32 speed) +{ + IActionObject::set_speed(speed); + + // Propagate the speed modifier for our children + for( auto itr = this->actions_.begin(); itr != this->actions_.end(); ++itr ) { + + IActionObject* action = (*itr).action.get(); + if( action != nullptr ) { + action->set_speed(speed); + } + } // end for loop +} + +void +GroupAction::set_timing_curve(const IActionObject::timing_curve_func& mode) +{ + IActionObject::set_timing_curve(mode); + + // Propagate the timing mode for our children + for( auto itr = this->actions_.begin(); itr != this->actions_.end(); ++itr ) { + + IActionObject* action = (*itr).action.get(); + if( action != nullptr ) { + action->set_timing_curve(mode); + } + } // end for loop +} + +// Private scope + +const GroupAction::container_type& GroupAction::actions() const +{ + return this->actions_; +} + +} // namespace nom diff --git a/src/audio/NullListener.cpp b/src/actions/IActionObject.cpp similarity index 55% rename from src/audio/NullListener.cpp rename to src/actions/IActionObject.cpp index 58c25093..601bc363 100644 --- a/src/audio/NullListener.cpp +++ b/src/actions/IActionObject.cpp @@ -26,73 +26,80 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ -#include "nomlib/audio/NullListener.hpp" +#include "nomlib/actions/IActionObject.hpp" + +// Private headers +#include "nomlib/actions/ActionTimingCurves.hpp" namespace nom { -NullListener::NullListener( void ) +IActionObject::IActionObject() : + timing_curve_(nom::Linear::ease_in_out) { - NOM_LOG_TRACE( NOM_LOG_CATEGORY_TRACE_AUDIO ); + NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_ACTION, + NOM_LOG_PRIORITY_VERBOSE ); } -NullListener::~NullListener( void ) +IActionObject::~IActionObject() { - NOM_LOG_TRACE( NOM_LOG_CATEGORY_TRACE_AUDIO ); + NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_ACTION, + NOM_LOG_PRIORITY_VERBOSE ); } -float NullListener::getVolume ( void ) const +const std::string& IActionObject::name() const { - return 0.0f; + return this->name_; } -const Point3f NullListener::getPosition ( void ) const +real32 IActionObject::duration() const { - return Point3f( 0.0f, 0.0f, 0.0f ); + return this->duration_; } -const Point3f NullListener::getVelocity ( void ) const +real32 IActionObject::speed() const { - return Point3f( 0.0f, 0.0f, 0.0f ); + return this->speed_; } -const Point3f NullListener::getDirection ( void ) const +const +IActionObject::timing_curve_func& IActionObject::timing_curve() const { - return Point3f( 0.0f, 0.0f, -1.0f ); + return this->timing_curve_; } -void NullListener::setPosition ( float x, float y, float z ) +void IActionObject::set_name(const std::string& action_id) { - // Do nothing + this->name_ = action_id; } -void NullListener::setPosition ( const Point3f& position ) +void IActionObject::set_speed(real32 speed) { - // Do nothing + // Default implementation + this->speed_ = speed; } -void NullListener::setVelocity ( float x, float y, float z ) +void +IActionObject::set_timing_curve(const IActionObject::timing_curve_func& mode) { - // Do nothing + // Default implementation + this->timing_curve_ = mode; } -void NullListener::setVelocity ( const Point3f& velocity ) -{ - // Do nothing -} +// Protected scope -void NullListener::setDirection ( float x, float y, float z ) +IActionObject::FrameState IActionObject::status() const { - // Do nothing + return this->status_; } -void NullListener::setDirection ( const Point3f& direction ) +void IActionObject::set_duration(real32 seconds) { - // Do nothing + this->duration_ = seconds; } -void NullListener::setVolume ( float gain ) +void IActionObject::set_status(FrameState state) { - // Do nothing + this->status_ = state; } } // namespace nom diff --git a/src/actions/MoveByAction.cpp b/src/actions/MoveByAction.cpp new file mode 100644 index 00000000..14d991ef --- /dev/null +++ b/src/actions/MoveByAction.cpp @@ -0,0 +1,220 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#include "nomlib/actions/MoveByAction.hpp" + +// Private headers +#include "nomlib/core/unique_ptr.hpp" +#include "nomlib/math/math_helpers.hpp" + +// Forward declarations +#include "nomlib/graphics/sprite/Sprite.hpp" + +namespace nom { + +// Static initializations +const char* MoveByAction::DEBUG_CLASS_NAME = "[MoveByAction]:"; + +MoveByAction::MoveByAction( const std::shared_ptr& drawable, + const Point2i& delta, real32 seconds ) : + total_displacement_(delta) +{ + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE_ACTION, NOM_LOG_PRIORITY_VERBOSE); + + this->set_duration(seconds); + this->elapsed_frames_ = 0.0f; + this->drawable_ = drawable; +} + +MoveByAction::~MoveByAction() +{ + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE_ACTION, NOM_LOG_PRIORITY_VERBOSE); +} + +std::unique_ptr MoveByAction::clone() const +{ + return( nom::make_unique( self_type(*this) ) ); +} + +IActionObject::FrameState +MoveByAction::update(real32 t, const Point2i& b, const Point2i& c, real32 d) +{ + real32 delta_time = t; + const real32 duration = d; + + // Initial starting values + const real32 b1 = b.x; + const real32 b2 = b.y; + + // Total change over time + real32 c1 = c.x; + real32 c2 = c.y; + + Point2f displacement(Point2f::zero); + Point2i displacement_as_integer(Point2i::zero); + + // Clamp delta values that go beyond maximal duration + if( delta_time > (duration / this->speed() ) ) { + delta_time = duration / this->speed(); + } + + // Account for the translating of negative X and Y displacements + if( c1 >= b1 ) { + c1 = c1 - b1; + } else { + // Use existing value + } + + if( c2 >= b2 ) { + c2 = c2 - b2; + } else { + // Use existing value + } + + // Apply speed scalar onto current frame time + real32 frame_time = delta_time * this->speed(); + + NOM_ASSERT(this->timing_curve() != nullptr); + + displacement.x = + this->timing_curve().operator()(frame_time, b1, c1, duration); + displacement.y = + this->timing_curve().operator()(frame_time, b2, c2, duration); + + if( this->drawable_ != nullptr ) { + + ++this->elapsed_frames_; + + // Convert floating-point value to integer; it is critical that we round + // the values so that values like 254.999984741 are represented as 255.00 + displacement_as_integer.x = + nom::round_float(displacement.x); + displacement_as_integer.y = + nom::round_float(displacement.y); + + this->drawable_->set_position(displacement_as_integer); + + NOM_LOG_DEBUG( NOM_LOG_CATEGORY_ACTION, DEBUG_CLASS_NAME, + "delta_time:", delta_time, + "frame_time:", frame_time, + "[elapsed frames]:", this->elapsed_frames_ ); + + NOM_LOG_DEBUG( NOM_LOG_CATEGORY_ACTION, + "position (input):", this->drawable_->position(), + "displacement (output):", displacement_as_integer ); + + NOM_LOG_VERBOSE( NOM_LOG_CATEGORY_ACTION, DEBUG_CLASS_NAME, + "[b1]:", b1, "[c1]:", c1, + "[b2]:", b2, "[c2]:", c2 ); + } + + if( delta_time < (duration / this->speed() ) ) { + this->set_status(FrameState::PLAYING); + } else { + this->last_frame(delta_time); + this->set_status(FrameState::COMPLETED); + } + + return this->status(); +} + +IActionObject::FrameState MoveByAction::next_frame(real32 delta_time) +{ + delta_time = ( Timer::to_seconds( this->timer_.ticks() ) ); + + this->first_frame(delta_time); + + return this->update( delta_time, this->initial_position_, + this->total_displacement_, this->duration() ); +} + +IActionObject::FrameState MoveByAction::prev_frame(real32 delta_time) +{ + delta_time = ( Timer::to_seconds( this->timer_.ticks() ) ); + + this->first_frame(delta_time); + + return this->update( delta_time, this->initial_position_, + -(this->total_displacement_), this->duration() ); +} + +void MoveByAction::pause(real32 delta_time) +{ + this->timer_.pause(); +} + +void MoveByAction::resume(real32 delta_time) +{ + this->timer_.unpause(); +} + +void MoveByAction::rewind(real32 delta_time) +{ + this->elapsed_frames_ = 0.0f; + this->timer_.stop(); + this->set_status(FrameState::PLAYING); + + if( this->drawable_ != nullptr ) { + this->drawable_->set_position(this->initial_position_); + } +} + +void MoveByAction::release() +{ + if( this->drawable_ != nullptr ) { + this->drawable_->release_texture(); + } + + this->drawable_.reset(); +} + +// Private scope + +void MoveByAction::first_frame(real32 delta_time) +{ + if( this->timer_.started() == false ) { + this->timer_.start(); + + NOM_LOG_DEBUG( NOM_LOG_CATEGORY_ACTION, DEBUG_CLASS_NAME, + "BEGIN at", delta_time ); + + if( this->drawable_ != nullptr ) { + this->initial_position_ = this->drawable_->position(); + } + + NOM_LOG_DEBUG( NOM_LOG_CATEGORY_ACTION, "initial_position:", + this->initial_position_ ); + } +} + +void MoveByAction::last_frame(real32 delta_time) +{ + this->timer_.stop(); +} + +} // namespace nom diff --git a/src/actions/PlayAudioSource.cpp b/src/actions/PlayAudioSource.cpp new file mode 100644 index 00000000..b7a5a2d1 --- /dev/null +++ b/src/actions/PlayAudioSource.cpp @@ -0,0 +1,327 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014, 2015, 2016 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#include "nomlib/actions/PlayAudioSource.hpp" + +// Private headers +#include "nomlib/core/unique_ptr.hpp" +#include "nomlib/math/math_helpers.hpp" +#include "nomlib/audio/audio_defs.hpp" +#include "nomlib/audio/IOAudioEngine.hpp" + +// Forward declarations +#include "nomlib/audio/libsndfile/SoundFileReader.hpp" +#include "nomlib/audio/SoundBuffer.hpp" +#include "nomlib/audio/AL/SoundSource.hpp" +// #include "nomlib/audio/AL/ALAudioDeviceCaps.hpp" + +namespace nom { + +// Static initializations +const char* PlayAudioSource::DEBUG_CLASS_NAME = "[PlayAudioSource]:"; + +PlayAudioSource:: +PlayAudioSource(audio::IOAudioEngine* dev, const char* filename) +{ + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE_ACTION, + nom::NOM_LOG_PRIORITY_VERBOSE); + + audio::SoundBuffer* buffer = nullptr; + audio::SoundInfo metadata = {}; + this->impl_ = dev; + this->elapsed_frames_ = 0.0f; + + this->fp_ = new audio::SoundFileReader(); + NOM_ASSERT(this->fp_ != nullptr); + if(this->fp_ != nullptr) { + + if(this->fp_->open(filename, metadata) == false) { + return; + } + + if(this->fp_->valid() == false) { + return; + } + + auto samples_per_second = metadata.sample_rate; + auto num_channels = metadata.channel_count; + auto channel_format = metadata.channel_format; + + buffer = + audio::create_buffer_memory(samples_per_second, num_channels, + channel_format); + + // NOTE(jeff): Create a queue of buffers to stream out in chunks + for(auto buffer_idx = 0; + buffer_idx != audio::TOTAL_NUM_BUFFERS; + ++buffer_idx) + { + if(buffer == nullptr) { + break; + } + + // TODO(jeff): Validity check..? + if(audio::write_info(buffer, metadata) == false) { + return; + } + + this->set_duration(buffer->duration); + this->audible_.push_back(buffer); + + } // end for TOTAL_NUM_BUFFERS loop + + this->current_buffer_ = this->audible_.begin(); + } + NOM_DUMP(this->audible_.size()); + + this->input_pos_ = 0; +} +#if 0 +PlayAudioSource:: +PlayAudioSource(audio::IOAudioEngine* dev, audio::SoundBuffer* buffer) +{ + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE_ACTION, + nom::NOM_LOG_PRIORITY_VERBOSE); + + NOM_ASSERT_INVALID_PATH("TODO"); + + this->impl_ = dev; + this->elapsed_frames_ = 0.0f; + this->audible_ = buffer; + + if(buffer != nullptr) { + this->set_duration(buffer->duration); + } +} +#endif +PlayAudioSource::~PlayAudioSource() +{ + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE_ACTION, + nom::NOM_LOG_PRIORITY_VERBOSE); +} + +std::unique_ptr PlayAudioSource::clone() const +{ + return( nom::make_unique( self_type(*this) ) ); +} + +IActionObject::FrameState +PlayAudioSource::update(real32 t, uint8 b, int16 c, real32 d) +{ + real32 delta_time = t; + auto status = this->status(); + const real32 duration = d; + const auto speed = this->speed(); + + auto& itr = this->current_buffer_; + if(*itr == nullptr || (*itr)->samples == nullptr) { + status = FrameState::COMPLETED; + this->set_status(status); + return status; + } + + uint32 audio_state = audio::state(*itr, this->impl_); + + // Clamp delta values that go beyond the time duration bounds; this adds + // stability to variable time steps + if(delta_time > (duration / speed)) { + delta_time = duration / speed; + } + + int16* samples = NOM_SCAST(int16*, (*itr)->samples); + auto format = (*itr)->channel_format; + auto sample_count = (*itr)->sample_count; + auto samples_per_second = (*itr)->sample_rate; + + while(this->input_pos_ != (samples_per_second * 4.0f) && + this->input_pos_ != sample_count) { + + if((*itr)->samples != nullptr) { + // audio::free_samples( (*itr)->channel_format, (*itr)->samples); + } + + (*itr)->samples_read = + this->fp_->read(samples + this->input_pos_, format, samples_per_second); + this->input_pos_ += samples_per_second; + + NOM_LOG_DEBUG(NOM_LOG_CATEGORY_TEST, "samples read:", + (*itr)->samples_read, "input_pos:", this->input_pos_); + } + + // this->impl_->push_buffer(this->audible_); + this->impl_->queue_buffer(*itr); + + ++this->current_buffer_; + if(this->current_buffer_ == (this->audible_.end() - 1)) { + this->current_buffer_ = this->audible_.begin(); + } + + audio_state = audio::state((*itr), this->impl_); + + // Continue playing the animation only when we are inside our frame duration + // bounds; this adds stability to variable time steps + if(delta_time < (duration / speed)) { + if(audio_state != audio::AUDIO_STATE_PLAYING) { + + NOM_LOG_INFO(NOM_LOG_CATEGORY_TEST, "play!"); + audio::play((*this->audible_.begin()), this->impl_); + } + + status = FrameState::PLAYING; + this->set_status(status); + } else { + this->last_frame(delta_time); + status = FrameState::COMPLETED; + this->set_status(status); + } + + return status; +} + +IActionObject::FrameState PlayAudioSource::next_frame(real32 delta_time) +{ + delta_time = this->timer_.to_seconds(); +#if 0 + if(this->audible_) { + this->audible_->elapsed_seconds = this->timer_.ticks(); + } +#endif + this->first_frame(delta_time); + + return this->update(delta_time, 0.0f, 0.0f, this->duration()); +} + +IActionObject::FrameState PlayAudioSource::prev_frame(real32 delta_time) +{ + delta_time = this->timer_.to_seconds(); +#if 0 + if(this->audible_) { + this->audible_->elapsed_seconds = this->timer_.ticks(); + } +#endif + this->first_frame(delta_time); + + return this->update(delta_time, 0.0f, 0.0f, this->duration()); +} + +void PlayAudioSource::pause(real32 delta_time) +{ + auto itr = this->current_buffer_; + + this->timer_.pause(); +#if 0 + audio::pause((*itr), this->impl_); + + if(this->audible_) { + this->audible_->elapsed_seconds = this->timer_.ticks(); + } +#endif +} + +void PlayAudioSource::resume(real32 delta_time) +{ + auto itr = this->current_buffer_; + + this->timer_.unpause(); +#if 0 + if(this->audible_) { + this->audible_->elapsed_seconds = this->timer_.ticks(); + } +#endif + audio::resume((*itr), this->impl_); +} + +void PlayAudioSource::rewind(real32 delta_time) +{ + auto itr = this->current_buffer_; + + // ...Reset the animation... + this->elapsed_frames_ = 0.0f; + this->timer_.stop(); + this->set_status(FrameState::PLAYING); + + if((*itr) != nullptr) { + audio::stop((*itr), this->impl_); + (*itr)->samples_read = 0; + } +} + +void PlayAudioSource::release() +{ + auto itr = this->current_buffer_; + + if(*itr != nullptr) { + // audio::stop((*itr), this->impl_); + + auto num_buffers = this->audible_.size(); + + NOM_LOG_DEBUG(NOM_LOG_CATEGORY_TEST, "processed_buffers:", num_buffers); + + auto audible_end = this->audible_.end(); + for(auto itr = this->audible_.begin(); + itr != audible_end; ++itr) + { + audio::free_buffer((*itr), this->impl_); + } + #if 0 + audio::free_buffer(this->audible_, this->impl_); + this->audible_ = nullptr; + #endif + } +} + +// Private scope + +void PlayAudioSource::first_frame(real32 delta_time) +{ + if(this->timer_.started() == false) { + this->timer_.start(); + + NOM_LOG_INFO(NOM_LOG_CATEGORY_ACTION, DEBUG_CLASS_NAME, + "BEGIN at", delta_time); + + this->input_pos_ = 0; + // audio::play((*itr), this->impl_); + } +} + +void PlayAudioSource::last_frame(real32 delta_time) +{ + NOM_LOG_INFO(NOM_LOG_CATEGORY_ACTION, DEBUG_CLASS_NAME, + "END at", delta_time); + auto itr = this->current_buffer_; + + this->timer_.stop(); + + // TODO(jeff): ? + // audio::stop((*itr), this->impl_); + // (*itr)->samples_read = 0; + this->input_pos_ = 0; +} + +} // namespace nom diff --git a/src/actions/RemoveAction.cpp b/src/actions/RemoveAction.cpp new file mode 100644 index 00000000..1ec75965 --- /dev/null +++ b/src/actions/RemoveAction.cpp @@ -0,0 +1,121 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#include "nomlib/actions/RemoveAction.hpp" + +#include "nomlib/core/unique_ptr.hpp" + +namespace nom { + +// Static initializations +const char* RemoveAction::DEBUG_CLASS_NAME = "[RemoveAction]:"; + +RemoveAction::RemoveAction(const std::shared_ptr& action) +{ + NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_ACTION, + nom::NOM_LOG_PRIORITY_VERBOSE ); + + this->action_ = action; +} + +RemoveAction::~RemoveAction() +{ + NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_ACTION, + nom::NOM_LOG_PRIORITY_VERBOSE ); +} + +std::unique_ptr RemoveAction::clone() const +{ + auto cloned_obj = nom::make_unique( self_type(*this) ); + if( cloned_obj != nullptr ) { + + if( this->action_ != nullptr ) { + cloned_obj->action_ = this->action_->clone(); + } + + // IMPORTANT: This is done to prevent the cloned action from being erased + // from a running queue at the same time as the original instance! + cloned_obj->set_name( "__" + this->name() + "_cloned" ); + + return std::move(cloned_obj); + } else { + return nullptr; + } +} + +IActionObject::FrameState RemoveAction::next_frame(real32 delta_time) +{ + if( this->action_ != nullptr ) { + + std::string action_id = this->action_->name(); + if( action_id == "" ) { + action_id = "action"; + } + + NOM_LOG_DEBUG( NOM_LOG_CATEGORY_ACTION, DEBUG_CLASS_NAME, + "removing", action_id, "from container", + "[action_id]:", this->name() ); + + this->release(); + } + + this->set_status(FrameState::COMPLETED); + return this->status(); +} + +IActionObject::FrameState RemoveAction::prev_frame(real32 delta_time) +{ + // NOTE: This action is not reversible + return this->next_frame(delta_time); +} + +void RemoveAction::pause(real32 delta_time) +{ + // Not supported +} + +void RemoveAction::resume(real32 delta_time) +{ + // Not supported +} + +void RemoveAction::rewind(real32 delta_time) +{ + // Not supported +} + +void RemoveAction::release() +{ + if( this->action_ != nullptr ) { + this->action_->release(); + } + + this->action_.reset(); +} + +} // namespace nom diff --git a/src/actions/RepeatForAction.cpp b/src/actions/RepeatForAction.cpp new file mode 100644 index 00000000..5fe08ca9 --- /dev/null +++ b/src/actions/RepeatForAction.cpp @@ -0,0 +1,188 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#include "nomlib/actions/RepeatForAction.hpp" + +#include "nomlib/core/unique_ptr.hpp" + +namespace nom { + +enum FrameStateDirection +{ + NEXT_FRAME, + PREV_FRAME +}; + +// Static initializations +const char* RepeatForAction::DEBUG_CLASS_NAME = "[RepeatForAction]:"; + +RepeatForAction::RepeatForAction( const std::shared_ptr& action, + nom::size_type num_repeats ) : + num_repeats_(num_repeats) +{ + NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_ACTION, + nom::NOM_LOG_PRIORITY_VERBOSE ); + + this->action_ = action; + this->elapsed_repeats_ = 0; +} + +RepeatForAction::~RepeatForAction() +{ + NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_ACTION, + nom::NOM_LOG_PRIORITY_VERBOSE ); +} + +std::unique_ptr RepeatForAction::clone() const +{ + auto cloned_obj = nom::make_unique( self_type(*this) ); + if( cloned_obj != nullptr ) { + + cloned_obj->set_status(FrameState::PLAYING); + cloned_obj->elapsed_repeats_ = 0; + cloned_obj->num_repeats_ = this->num_repeats_; + + if( this->action_ != nullptr ) { + cloned_obj->action_ = this->action_->clone(); + } + + // IMPORTANT: This is done to prevent the cloned action from being erased + // from a running queue at the same time as the original instance! + cloned_obj->set_name( "__" + this->name() + "_cloned" ); + + return std::move(cloned_obj); + } else { + return nullptr; + } +} + +IActionObject::FrameState +RepeatForAction::update(real32 delta_time, uint32 direction) +{ + IActionObject::FrameState obj_status; + IActionObject* action = this->action_.get(); + + if( action == nullptr ) { + // No action to repeat! + this->set_status(FrameState::COMPLETED); + return this->status(); + } + + if( this->elapsed_repeats_ == this->num_repeats_ ) { + this->set_status(FrameState::COMPLETED); + return this->status(); + } + + if( direction == FrameStateDirection::NEXT_FRAME ) { + obj_status = action->next_frame(delta_time); + } else { + obj_status = action->prev_frame(delta_time); + } + + if( obj_status == FrameState::COMPLETED ) { + + ++this->elapsed_repeats_; + if( this->elapsed_repeats_ < this->num_repeats_ ) { + this->set_status(FrameState::PLAYING); + action->rewind(delta_time); + } else { + this->set_status(FrameState::COMPLETED); + NOM_ASSERT(this->num_repeats_ == this->elapsed_repeats_); + } + + NOM_LOG_DEBUG( NOM_LOG_CATEGORY_ACTION, DEBUG_CLASS_NAME, + "[elapsed_repeats]:", this->elapsed_repeats_, + "[num_repeats]:", this->num_repeats_ ); + } + + return this->status(); +} + +IActionObject::FrameState RepeatForAction::next_frame(real32 delta_time) +{ + return this->update(delta_time, FrameStateDirection::NEXT_FRAME); +} + +IActionObject::FrameState RepeatForAction::prev_frame(real32 delta_time) +{ + return this->update(delta_time, FrameStateDirection::PREV_FRAME); +} + +void RepeatForAction::pause(real32 delta_time) +{ + if( this->action_ != nullptr ) { + this->action_->pause(delta_time); + } +} + +void RepeatForAction::resume(real32 delta_time) +{ + if( this->action_ != nullptr ) { + this->action_->resume(delta_time); + } +} + +void RepeatForAction::rewind(real32 delta_time) +{ + this->elapsed_repeats_ = 0; + this->set_status(FrameState::PLAYING); + + if( this->action_ != nullptr ) { + this->action_->rewind(delta_time); + } +} + +void RepeatForAction::release() +{ + if( this->action_ != nullptr ) { + this->action_->release(); + } + + this->action_.reset(); +} + +void RepeatForAction::set_speed(real32 speed) +{ + IActionObject::set_speed(speed); + + if( this->action_ != nullptr ) { + this->action_->set_speed(speed); + } +} + +void +RepeatForAction::set_timing_curve(const IActionObject::timing_curve_func& mode) +{ + IActionObject::set_timing_curve(mode); + + if( this->action_ != nullptr ) { + this->action_->set_timing_curve(mode); + } +} + +} // namespace nom diff --git a/src/actions/RepeatForeverAction.cpp b/src/actions/RepeatForeverAction.cpp new file mode 100644 index 00000000..d2f2ea88 --- /dev/null +++ b/src/actions/RepeatForeverAction.cpp @@ -0,0 +1,175 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#include "nomlib/actions/RepeatForeverAction.hpp" + +#include "nomlib/core/unique_ptr.hpp" + +namespace nom { + +enum FrameStateDirection +{ + NEXT_FRAME, + PREV_FRAME +}; + +// Static initializations +const char* RepeatForeverAction::DEBUG_CLASS_NAME = "[RepeatForeverAction]:"; + +RepeatForeverAction:: +RepeatForeverAction(const std::shared_ptr& action) +{ + NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_ACTION, + nom::NOM_LOG_PRIORITY_VERBOSE ); + + this->action_ = action; + this->elapsed_repeats_ = 0; +} + +RepeatForeverAction::~RepeatForeverAction() +{ + NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_ACTION, + nom::NOM_LOG_PRIORITY_VERBOSE ); +} + +std::unique_ptr RepeatForeverAction::clone() const +{ + auto cloned_obj = nom::make_unique( self_type(*this) ); + if( cloned_obj != nullptr ) { + + cloned_obj->set_status(FrameState::PLAYING); + cloned_obj->elapsed_repeats_ = 0; + + if( this->action_ != nullptr ) { + cloned_obj->action_ = this->action_->clone(); + } + + // IMPORTANT: This is done to prevent the cloned action from being erased + // from a running queue at the same time as the original instance! + cloned_obj->set_name( "__" + this->name() + "_cloned" ); + + return std::move(cloned_obj); + } else { + return nullptr; + } +} + +IActionObject::FrameState +RepeatForeverAction::update(real32 delta_time, uint32 direction) +{ + IActionObject::FrameState obj_status; + IActionObject* action = this->action_.get(); + + if( action == nullptr ) { + // No action to repeat! + this->set_status(FrameState::COMPLETED); + return this->status(); + } + + if( direction == FrameStateDirection::NEXT_FRAME ) { + obj_status = action->next_frame(delta_time); + } else { + obj_status = action->prev_frame(delta_time); + } + + if( obj_status == FrameState::COMPLETED ) { + + ++this->elapsed_repeats_; + action->rewind(delta_time); + + NOM_LOG_DEBUG( NOM_LOG_CATEGORY_ACTION, DEBUG_CLASS_NAME, + "[elapsed_repeats]:", this->elapsed_repeats_ ); + } + + this->set_status(FrameState::PLAYING); + return this->status(); +} + +IActionObject::FrameState RepeatForeverAction::next_frame(real32 delta_time) +{ + return this->update(delta_time, FrameStateDirection::NEXT_FRAME); +} + +IActionObject::FrameState RepeatForeverAction::prev_frame(real32 delta_time) +{ + return this->update(delta_time, FrameStateDirection::PREV_FRAME); +} + +void RepeatForeverAction::pause(real32 delta_time) +{ + if( this->action_ != nullptr ) { + this->action_->pause(delta_time); + } +} + +void RepeatForeverAction::resume(real32 delta_time) +{ + if( this->action_ != nullptr ) { + this->action_->resume(delta_time); + } +} + +void RepeatForeverAction::rewind(real32 delta_time) +{ + this->elapsed_repeats_ = 0; + this->set_status(FrameState::PLAYING); + + if( this->action_ != nullptr ) { + this->action_->rewind(delta_time); + } +} + +void RepeatForeverAction::release() +{ + if( this->action_ != nullptr ) { + this->action_->release(); + } + + this->action_.reset(); +} + +void RepeatForeverAction::set_speed(real32 speed) +{ + IActionObject::set_speed(speed); + + if( this->action_ != nullptr ) { + this->action_->set_speed(speed); + } +} + +void RepeatForeverAction:: +set_timing_curve(const IActionObject::timing_curve_func& mode) +{ + IActionObject::set_timing_curve(mode); + + if( this->action_ != nullptr ) { + this->action_->set_timing_curve(mode); + } +} + +} // namespace nom diff --git a/src/actions/ReversedAction.cpp b/src/actions/ReversedAction.cpp new file mode 100644 index 00000000..3ed23799 --- /dev/null +++ b/src/actions/ReversedAction.cpp @@ -0,0 +1,149 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#include "nomlib/actions/ReversedAction.hpp" + +#include "nomlib/core/unique_ptr.hpp" + +namespace nom { + +// Static initializations +const char* ReversedAction::DEBUG_CLASS_NAME = "[ReversedAction]:"; + +ReversedAction::ReversedAction(const std::shared_ptr& action) +{ + NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_ACTION, + nom::NOM_LOG_PRIORITY_VERBOSE ); + + this->action_ = action; +} + +ReversedAction::~ReversedAction() +{ + NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_ACTION, + nom::NOM_LOG_PRIORITY_VERBOSE ); +} + +std::unique_ptr ReversedAction::clone() const +{ + auto cloned_obj = nom::make_unique( self_type(*this) ); + if( cloned_obj != nullptr ) { + + cloned_obj->set_status(FrameState::PLAYING); + + if( this->action_ != nullptr ) { + cloned_obj->action_ = this->action_->clone(); + } + + // IMPORTANT: This is done to prevent the cloned action from being erased + // from a running queue at the same time as the original instance! + cloned_obj->set_name( "__" + this->name() + "_cloned" ); + + return std::move(cloned_obj); + } else { + return nullptr; + } +} + +IActionObject::FrameState ReversedAction::next_frame(real32 delta_time) +{ + if( this->action_ != nullptr ) { + this->set_status( this->action_->prev_frame(delta_time) ); + } else { + // No action to reverse! + this->set_status(FrameState::COMPLETED); + } + + return this->status(); +} + +IActionObject::FrameState ReversedAction::prev_frame(real32 delta_time) +{ + if( this->action_ != nullptr ) { + this->set_status( this->action_->next_frame(delta_time) ); + return this->status(); + } else { + // No action to reverse! + this->set_status(FrameState::COMPLETED); + } + + return this->status(); +} + +void ReversedAction::pause(real32 delta_time) +{ + if( this->action_ != nullptr ) { + this->action_->pause(delta_time); + } +} + +void ReversedAction::resume(real32 delta_time) +{ + if( this->action_ != nullptr ) { + this->action_->resume(delta_time); + } +} + +void ReversedAction::rewind(real32 delta_time) +{ + this->set_status(FrameState::PLAYING); + + if( this->action_ != nullptr ) { + this->action_->rewind(delta_time); + } +} + +void ReversedAction::release() +{ + if( this->action_ != nullptr ) { + this->action_->release(); + } + + this->action_.reset(); +} + +void ReversedAction::set_speed(real32 speed) +{ + IActionObject::set_speed(speed); + + if( this->action_ != nullptr ) { + this->action_->set_speed(speed); + } +} + +void +ReversedAction::set_timing_curve(const IActionObject::timing_curve_func& mode) +{ + IActionObject::set_timing_curve(mode); + + if( this->action_ != nullptr ) { + this->action_->set_timing_curve(mode); + } +} + +} // namespace nom diff --git a/src/actions/ScaleByAction.cpp b/src/actions/ScaleByAction.cpp new file mode 100644 index 00000000..f7c7fa0d --- /dev/null +++ b/src/actions/ScaleByAction.cpp @@ -0,0 +1,228 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#include "nomlib/actions/ScaleByAction.hpp" + +// Private headers +#include "nomlib/core/unique_ptr.hpp" +#include "nomlib/math/math_helpers.hpp" + +// Forward declarations +#include "nomlib/graphics/sprite/Sprite.hpp" + +namespace nom { + +// Static initializations +const char* ScaleByAction::DEBUG_CLASS_NAME = "[ScaleByAction]:"; + +ScaleByAction::ScaleByAction( const std::shared_ptr& drawable, + const Size2f& delta, real32 seconds ) : + total_displacement_(delta) +{ + NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_ACTION, + nom::NOM_LOG_PRIORITY_VERBOSE ); + + this->set_duration(seconds); + this->elapsed_frames_ = 0.0f; + this->drawable_ = drawable; +} + +ScaleByAction::~ScaleByAction() +{ + NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_ACTION, + nom::NOM_LOG_PRIORITY_VERBOSE ); +} + +std::unique_ptr ScaleByAction::clone() const +{ + return( nom::make_unique( self_type(*this) ) ); +} + +IActionObject::FrameState +ScaleByAction::update(real32 t, const Size2i& b, const Size2f& c, real32 d) +{ + real32 delta_time = t; + const real32 duration = d; + + // initial starting values + const real32 b1 = b.w; + const real32 b2 = b.h; + + // total change over time + real32 c1 = c.w; + real32 c2 = c.h; + + Size2f displacement(Size2f::zero); + Size2i displacement_as_integer(Size2i::zero); + + // Clamp delta values that go beyond maximal duration + if( delta_time > (duration / this->speed() ) ) { + delta_time = duration / this->speed(); + } + + // Account for the translating of negative X and Y scale factors + if( (b1 * c1 - b1) > 0 ) { + c1 = (b1 * c1) - b1; + } else { + // resulting value is a positive number + c1 = -( (b1 / c1) ) - b1; + } + + if( (b2 * c2 - b2) > 0 ) { + c2 = (b2 * c2) - b2; + } else { + // resulting value is a positive number + c2 = -( (b2 / c2) ) - b2; + } + + // Apply speed scalar onto current frame time + real32 frame_time = delta_time * this->speed(); + + NOM_ASSERT(this->timing_curve() != nullptr); + + displacement.w = + this->timing_curve().operator()(frame_time, b1, c1, duration); + + displacement.h = + this->timing_curve().operator()(frame_time, b2, c2, duration); + + if( this->drawable_ != nullptr ) { + + ++this->elapsed_frames_; + + // Convert floating-point value to integer; it is critical that we round + // the values so that values like 254.999984741 are represented as 255.00 + displacement_as_integer.w = + nom::round_float(displacement.w); + displacement_as_integer.h = + nom::round_float(displacement.h); + + displacement_as_integer.w = abs(displacement_as_integer.w); + displacement_as_integer.h = abs(displacement_as_integer.h); + + this->drawable_->set_size(displacement_as_integer); + this->size_ = displacement_as_integer; + + NOM_LOG_DEBUG( NOM_LOG_CATEGORY_ACTION, DEBUG_CLASS_NAME, + "delta_time:", delta_time, + "frame_time:", frame_time, + "[elapsed frames]:", this->elapsed_frames_ ); + + NOM_LOG_DEBUG( NOM_LOG_CATEGORY_ACTION, + DEBUG_CLASS_NAME, "size (input):", this->drawable_->size(), + "displacement (output):", displacement_as_integer ); + } + + if( delta_time < (duration / this->speed() ) ) { + this->set_status(FrameState::PLAYING); + } else { + this->last_frame(delta_time); + this->set_status(FrameState::COMPLETED); + } + + return this->status(); +} + +IActionObject::FrameState ScaleByAction::next_frame(real32 delta_time) +{ + delta_time = ( Timer::to_seconds( this->timer_.ticks() ) ); + + this->first_frame(delta_time); + + return this->update( delta_time, this->initial_size_, + this->total_displacement_, this->duration() ); +} + +IActionObject::FrameState ScaleByAction::prev_frame(real32 delta_time) +{ + delta_time = ( Timer::to_seconds( this->timer_.ticks() ) ); + + this->first_frame(delta_time); + + Size2f delta(Size2f::zero); + delta.w = -(this->total_displacement_.w); + delta.h = -(this->total_displacement_.h); + + return this->update(delta_time, this->initial_size_, delta, this->duration() ); +} + +void ScaleByAction::pause(real32 delta_time) +{ + this->timer_.pause(); +} + +void ScaleByAction::resume(real32 delta_time) +{ + this->timer_.unpause(); +} + +void ScaleByAction::rewind(real32 delta_time) +{ + this->elapsed_frames_ = 0.0f; + this->timer_.stop(); + this->set_status(FrameState::PLAYING); + + if( this->drawable_ != nullptr ) { + this->drawable_->set_size(this->initial_size_); + } +} + +void ScaleByAction::release() +{ + if( this->drawable_ != nullptr ) { + this->drawable_->release_texture(); + } + + this->drawable_.reset(); +} + +// Private scope + +void ScaleByAction::first_frame(real32 delta_time) +{ + if( this->timer_.started() == false ) { + this->timer_.start(); + + NOM_LOG_DEBUG( NOM_LOG_CATEGORY_ACTION, DEBUG_CLASS_NAME, + "BEGIN at", delta_time ); + + if( this->drawable_ != nullptr ) { + this->initial_size_ = this->drawable_->size(); + } + + NOM_LOG_DEBUG( NOM_LOG_CATEGORY_ACTION, "initial_size:", + this->initial_size_ ); + } +} + +void ScaleByAction::last_frame(real32 delta_time) +{ + this->timer_.stop(); +} + +} // namespace nom diff --git a/src/actions/SequenceAction.cpp b/src/actions/SequenceAction.cpp new file mode 100644 index 00000000..568895cf --- /dev/null +++ b/src/actions/SequenceAction.cpp @@ -0,0 +1,234 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#include "nomlib/actions/SequenceAction.hpp" + +#include "nomlib/core/clock.hpp" +#include "nomlib/core/unique_ptr.hpp" + +namespace nom { + +enum FrameStateDirection +{ + NEXT_FRAME, + PREV_FRAME +}; + +// Static initializations +const char* SequenceAction::DEBUG_CLASS_NAME = "[SequenceAction]:"; + +SequenceAction::SequenceAction(const action_list& actions) +{ + NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_ACTION, + nom::NOM_LOG_PRIORITY_VERBOSE ); + + for( auto itr = actions.begin(); itr != actions.end(); ++itr ) { + this->actions_.emplace_back(*itr); + } + + this->num_completed_ = 0; + this->num_actions_ = this->actions_.size(); + this->actions_iterator_ = this->actions_.begin(); +} + +SequenceAction::~SequenceAction() +{ + NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_ACTION, + nom::NOM_LOG_PRIORITY_VERBOSE ); +} + +std::unique_ptr SequenceAction::clone() const +{ + auto cloned_obj = nom::make_unique( self_type(*this) ); + if( cloned_obj != nullptr ) { + + cloned_obj->set_status(FrameState::PLAYING); + + cloned_obj->actions_.clear(); + auto actions_end = this->actions_.end(); + for( auto itr = this->actions_.begin(); itr != actions_end; ++itr ) { + cloned_obj->actions_.emplace_back( (*itr)->clone() ); + } + + cloned_obj->actions_iterator_ = cloned_obj->actions_.begin(); + cloned_obj->num_completed_ = 0; + cloned_obj->num_actions_ = cloned_obj->actions_.size(); + + // IMPORTANT: This is done to prevent the cloned action from being erased + // from a running queue at the same time as the original instance! + cloned_obj->set_name( "__" + this->name() + "_cloned" ); + + return std::move(cloned_obj); + } else { + return nullptr; + } +} + +IActionObject::FrameState +SequenceAction::update(real32 delta_time, uint32 direction) +{ + std::string action_id = "action"; + FrameState action_status = FrameState::COMPLETED; + + // Program flow is structured to never call back here after the actions are + // finished -- this serves only as a reminder to the intended flow. + if( this->status() == FrameState::COMPLETED ) { + NOM_ASSERT_INVALID_PATH(); + return this->status(); + } + + auto &itr = this->actions_iterator_; + auto actions_end = this->actions_.end(); + auto action = *itr; + NOM_ASSERT(itr != actions_end); + + if( action != nullptr ) { + + if( action->name() != "" ) { + action_id = action->name(); + } + + if( direction == FrameStateDirection::NEXT_FRAME ) { + action_status = action->next_frame(delta_time); + } else { + action_status = action->prev_frame(delta_time); + } + } + + if( action_status == FrameState::COMPLETED ) { + + ++itr; + ++this->num_completed_; + + NOM_LOG_DEBUG( NOM_LOG_CATEGORY_ACTION, DEBUG_CLASS_NAME, + action_id, "has finished at", + Timer::to_seconds( nom::ticks() ), + "[", this->num_completed_, "/", this->num_actions_, "]", + "[action_id]:", this->name() ); + + this->set_status(FrameState::PLAYING); + } + + if( this->num_completed_ == this->num_actions_ ) { + this->set_status(FrameState::COMPLETED); + return this->status(); + } + + return this->status(); +} + +IActionObject::FrameState SequenceAction::next_frame(real32 delta_time) +{ + return this->update(delta_time, FrameStateDirection::NEXT_FRAME); +} + +IActionObject::FrameState SequenceAction::prev_frame(real32 delta_time) +{ + return this->update(delta_time, FrameStateDirection::PREV_FRAME); +} + +void SequenceAction::pause(real32 delta_time) +{ + for( auto itr = this->actions_.begin(); itr != this->actions_.end(); ++itr ) { + + if( *itr != nullptr ) { + (*itr)->pause(delta_time); + } + } +} + +void SequenceAction::resume(real32 delta_time) +{ + for( auto itr = this->actions_.begin(); itr != this->actions_.end(); ++itr ) { + + if( *itr != nullptr ) { + (*itr)->resume(delta_time); + } + } +} + +void SequenceAction::rewind(real32 delta_time) +{ + this->num_completed_ = 0; + this->actions_iterator_ = this->actions_.begin(); + this->set_status(FrameState::PLAYING); + + for( auto itr = this->actions_.begin(); itr != this->actions_.end(); ++itr ) { + + if( *itr != nullptr ) { + (*itr)->rewind(delta_time); + } + } +} + +void SequenceAction::release() +{ + for( auto itr = this->actions_.begin(); itr != this->actions_.end(); ++itr ) { + + if( *itr != nullptr ) { + (*itr)->release(); + (*itr).reset(); + } + } +} + +void SequenceAction::set_speed(real32 speed) +{ + IActionObject::set_speed(speed); + + // Propagate the speed modifier for our children + for( auto itr = this->actions_.begin(); itr != this->actions_.end(); ++itr ) { + + if( *itr != nullptr ) { + (*itr)->set_speed(speed); + } + } +} + +void +SequenceAction::set_timing_curve(const IActionObject::timing_curve_func& mode) +{ + IActionObject::set_timing_curve(mode); + + // Propagate the timing mode for our children + for( auto itr = this->actions_.begin(); itr != this->actions_.end(); ++itr ) { + + if( *itr != nullptr ) { + (*itr)->set_timing_curve(mode); + } + } +} + +// Private scope + +const SequenceAction::container_type& SequenceAction::actions() const +{ + return this->actions_; +} + +} // namespace nom diff --git a/src/actions/SpriteBatchAction.cpp b/src/actions/SpriteBatchAction.cpp new file mode 100644 index 00000000..6df3f03a --- /dev/null +++ b/src/actions/SpriteBatchAction.cpp @@ -0,0 +1,220 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#include "nomlib/actions/SpriteBatchAction.hpp" + +// Private headers +#include "nomlib/core/unique_ptr.hpp" +#include "nomlib/math/math_helpers.hpp" + +// Forward declarations +#include "nomlib/graphics/sprite/SpriteBatch.hpp" + +namespace nom { + +// Static initializations +const char* SpriteBatchAction::DEBUG_CLASS_NAME = "[SpriteBatchAction]:"; + +SpriteBatchAction:: +SpriteBatchAction( const std::shared_ptr& drawable, + real32 frame_interval_seconds ) +{ + NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_ACTION, + nom::NOM_LOG_PRIORITY_VERBOSE ); + + this->elapsed_frames_ = 0.0f; + this->frame_interval_ = frame_interval_seconds; + this->total_displacement_ = 0.0f; + this->initial_frame_ = 0; + this->last_delta_ = 0.0f; + + this->drawable_ = drawable; + if( this->drawable_ != nullptr ) { + NOM_ASSERT(this->drawable_->frames() > 0); + this->total_displacement_ = this->drawable_->frames(); + } + + real32 action_duration_seconds = + (this->frame_interval_ * this->total_displacement_); + this->set_duration(action_duration_seconds); +} + +SpriteBatchAction::~SpriteBatchAction() +{ + NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_ACTION, + nom::NOM_LOG_PRIORITY_VERBOSE ); +} + +std::unique_ptr SpriteBatchAction::clone() const +{ + return( nom::make_unique( self_type(*this) ) ); +} + +IActionObject::FrameState +SpriteBatchAction::update(real32 t, real32 b, real32 c, real32 d) +{ + real32 frame_interval = + this->frame_interval_ / this->speed(); + + // The current frame to scale + real32 delta_time = t; + + // Total duration of the action + const real32 duration = d; + + // initial starting value + const int b1 = b; + + // Total number of frames over time + real32 c1 = c; + + // The computed texture frame to show next + real32 displacement(0.0f); + int displacement_as_integer = 0; + + // Clamp delta values that go beyond maximal duration + if( delta_time > (duration / this->speed() ) ) { + delta_time = duration / this->speed(); + } + + // Apply speed scalar onto current frame time + real32 frame_time = delta_time * this->speed(); + + NOM_ASSERT(this->timing_curve() != nullptr); + + displacement = + this->timing_curve().operator()(frame_time, b1, c1, duration); + NOM_ASSERT(displacement <= this->total_displacement_); + NOM_ASSERT(displacement >= this->initial_frame_); + + if( delta_time >= (this->last_delta_ + frame_interval) && + delta_time < (duration / this->speed() ) ) + { + this->elapsed_frames_ = displacement; + this->last_delta_ = delta_time; + + displacement_as_integer = + nom::round_float_down(displacement); + + NOM_LOG_DEBUG( NOM_LOG_CATEGORY_ACTION, DEBUG_CLASS_NAME, + "delta_time:", delta_time, "frame_time:", frame_time, + "[elapsed frames]:", this->elapsed_frames_, + "displacement (output):", displacement_as_integer ); + + if( this->drawable_ != nullptr ) { + this->drawable_->set_frame(displacement_as_integer); + } + } + + if( delta_time < (duration / this->speed() ) ) { + this->set_status(FrameState::PLAYING); + } else { + this->last_frame(delta_time); + this->set_status(FrameState::COMPLETED); + } + + return this->status(); +} + +IActionObject::FrameState SpriteBatchAction::next_frame(real32 delta_time) +{ + delta_time = ( Timer::to_seconds( this->timer_.ticks() ) ); + + this->first_frame(delta_time); + + return( this->update( delta_time, this->initial_frame_, + this->total_displacement_, this->duration() ) ); +} + +IActionObject::FrameState SpriteBatchAction::prev_frame(real32 delta_time) +{ + delta_time = ( Timer::to_seconds( this->timer_.ticks() ) ); + + this->first_frame(delta_time); + + return( this->update( delta_time, this->total_displacement_, + -(this->total_displacement_), this->duration() ) ); +} + +void SpriteBatchAction::pause(real32 delta_time) +{ + this->timer_.pause(); +} + +void SpriteBatchAction::resume(real32 delta_time) +{ + this->timer_.unpause(); +} + +void SpriteBatchAction::rewind(real32 delta_time) +{ + this->elapsed_frames_ = 0.0f; + this->last_delta_ = 0.0f; + this->timer_.stop(); + this->set_status(FrameState::PLAYING); + + if( this->drawable_ != nullptr ) { + this->drawable_->set_frame(this->initial_frame_); + } +} + +void SpriteBatchAction::release() +{ + if( this->drawable_ != nullptr ) { + this->drawable_->release_texture(); + } + + this->drawable_.reset(); +} + +void SpriteBatchAction::first_frame(real32 delta_time) +{ + if( this->timer_.started() == false ) { + this->last_delta_ = 0.0f; + this->timer_.start(); + + NOM_LOG_DEBUG( NOM_LOG_CATEGORY_ACTION, DEBUG_CLASS_NAME, + "BEGIN at", delta_time ); + + if( this->drawable_ != nullptr ) { + this->initial_frame_ = this->drawable_->frame(); + } + + NOM_LOG_DEBUG( NOM_LOG_CATEGORY_ACTION, + "[initial_frame]:", this->initial_frame_, + "[num_frames]:", this->total_displacement_, + "[frame_interval]:", this->frame_interval_ ); + } +} + +void SpriteBatchAction::last_frame(real32 delta_time) +{ + this->timer_.stop(); +} + +} // namespace nom diff --git a/src/actions/WaitForDurationAction.cpp b/src/actions/WaitForDurationAction.cpp new file mode 100644 index 00000000..9ebba2b4 --- /dev/null +++ b/src/actions/WaitForDurationAction.cpp @@ -0,0 +1,127 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#include "nomlib/actions/WaitForDurationAction.hpp" + +#include "nomlib/core/unique_ptr.hpp" + +namespace nom { + +// Static initializations +const char* WaitForDurationAction::DEBUG_CLASS_NAME = + "[WaitForDurationAction]:"; + +//! [creating_custom_actions] + +WaitForDurationAction::WaitForDurationAction(real32 seconds) +{ + NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_ACTION, + nom::NOM_LOG_PRIORITY_VERBOSE ); + + this->set_duration(seconds); + this->elapsed_frames_ = 0.0f; +} + +WaitForDurationAction::~WaitForDurationAction() +{ + NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_ACTION, + nom::NOM_LOG_PRIORITY_VERBOSE ); +} + +std::unique_ptr WaitForDurationAction::clone() const +{ + return( nom::make_unique( self_type(*this) ) ); +} + +IActionObject::FrameState WaitForDurationAction::next_frame(real32 delta_time) +{ + delta_time = ( Timer::to_seconds( this->timer_.ticks() ) ); + + if( this->timer_.started() == false ) { + this->timer_.start(); + + NOM_LOG_DEBUG( NOM_LOG_CATEGORY_ACTION, DEBUG_CLASS_NAME, + "BEGIN at", delta_time ); + } + + // Clamp delta values that go beyond maximal duration + if( delta_time > (this->duration() / this->speed() ) ) { + delta_time = this->duration() / this->speed(); + } + + // Apply speed scalar onto current frame time + real32 frame_time = delta_time * this->speed(); + + if( delta_time < (this->duration() / this->speed() ) ) { + + ++this->elapsed_frames_; + + NOM_LOG_DEBUG( NOM_LOG_CATEGORY_ACTION, DEBUG_CLASS_NAME, + "delta_time:", delta_time, + "frame_time:", frame_time, + "[elapsed frames]:", this->elapsed_frames_ ); + + this->set_status(FrameState::PLAYING); + } else { + this->set_status(FrameState::COMPLETED); + } + + return this->status(); +} + +IActionObject::FrameState WaitForDurationAction::prev_frame(real32 delta_time) +{ + // NOTE: This action is not reversible + return this->next_frame(delta_time); +} + +void WaitForDurationAction::pause(real32 delta_time) +{ + this->timer_.pause(); +} + +void WaitForDurationAction::resume(real32 delta_time) +{ + this->timer_.unpause(); +} + +void WaitForDurationAction::rewind(real32 delta_time) +{ + this->elapsed_frames_ = 0.0f; + this->timer_.stop(); + this->set_status(FrameState::PLAYING); +} + +void WaitForDurationAction::release() +{ + // Nothing to free! +} + +//! [creating_custom_actions] + +} // namespace nom diff --git a/src/audio/AL/ALAudioDevice.cpp b/src/audio/AL/ALAudioDevice.cpp new file mode 100644 index 00000000..47cfdad9 --- /dev/null +++ b/src/audio/AL/ALAudioDevice.cpp @@ -0,0 +1,1189 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014, 2015, 2016 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#include "nomlib/audio/AL/ALAudioDevice.hpp" + +// Private headers +#include "nomlib/audio/AL/ALAudioDeviceCaps.hpp" +#include "nomlib/audio/audio_defs.hpp" +#include "nomlib/core/strings.hpp" +#include "nomlib/core/err.hpp" +// #include "nomlib/system/HighResolutionTimer.hpp" +#include "nomlib/system/Timer.hpp" +#include "nomlib/audio/AL/osx/apple_extensions.hpp" +#include "nomlib/math/math_helpers.hpp" + +// Forward declarations +#include "nomlib/audio/AL/OpenAL.hpp" + +namespace nom { +namespace audio { + +#if 0 +enum ExtensionType: uint32 +{ + EXTENSION_TYPE_UNDEFINED = 0x00000, + + EXTENSION_TYPE_CONTEXT = 0x10000, + EXTENSION_TYPE_DEVICE = 0x20000, +}; + +// Get the type of extension query you are looking for by inspecting the first +// three characters of the parameter key. +static +uint32 extension_type(const char* key) +{ + uint32 ext_type = EXTENSION_TYPE_UNDEFINED; + int ctx_ext = 0; + int dev_ext = 0; + + if(key == nullptr) { + return ext_type; + } + + ctx_ext = nom::compare_cstr_insensitive("ALC_", key, 2); + dev_ext = nom::compare_cstr_insensitive("AL_", key, 2); + + if(ctx_ext == 0) { + ext_type = EXTENSION_TYPE_CONTEXT; + } else if(dev_ext == 0) { + ext_type = EXTENSION_TYPE_DEVICE; + } + + return ext_type; +} +#endif + +// static +ALCdevice_struct* current_device() +{ + ALCcontext_struct* ctx = audio::current_context(); + ALCdevice_struct* dev = nullptr; + + if(ctx != nullptr) { + dev = alcGetContextsDevice(ctx); + } + + return dev; +} + +ALCcontext_struct* current_context() +{ + ALCcontext_struct* ctx = nullptr; + + ctx = alcGetCurrentContext(); + + return ctx; +} + +void free_audio_device(ALCdevice* dev) +{ + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE_AUDIO, NOM_LOG_PRIORITY_DEBUG); + + if(dev != nullptr) { + alcCloseDevice(dev); +#if 0 + // TODO(jeff): Verify that this enumeration is available on Windows + // Availability verified: Apple's OpenAL SDK and OpenAL-Soft. + { + auto enum_avail = audio::enum_available("ALC_CONNECTED"); + if(enum_avail != 0) { + int dev_connected = 0; + // NOTE(jeff): Defined in file at openal-soft.git/include/AL/alext.h + #if defined(ALC_EXT_disconnect) + alcGetIntegerv(dev, ALC_CONNECTED, sizeof(int), &dev_connected); + #endif + NOM_DUMP(dev_connected); // should be 1 when the extension is available + } + } +#endif + dev = nullptr; + } +} + +void free_audio_context(ALCcontext* ctx) +{ + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE_AUDIO, NOM_LOG_PRIORITY_DEBUG); + + if(ctx != nullptr) { + // Disable context; we must not free a current context as per OpenAL v1.1 + // API docs + if(alcMakeContextCurrent(nullptr) == false) { + // TODO(jeff): Use nom::set_error + NOM_LOG_ERR(NOM_LOG_CATEGORY_APPLICATION, + "Failed to stop the current context."); + } + + // Release context + AL_CLEAR_ERR(); + alcDestroyContext(ctx); + ALenum error = alGetError(); + if(error != AL_NO_ERROR) { + // TODO(jeff): Use nom::set_error + NOM_LOG_ERR(NOM_LOG_CATEGORY_APPLICATION, "Failed to destroy context."); + } + + ctx = nullptr; + } +} + +void* process_addr(const char* token) +{ + auto token_addr = NOM_SCAST(const ALCchar*, token); + void* result = alGetProcAddress(token_addr); + + return result; +} + +bool cap(uint32 caps, uint32 key) +{ + uint32 result = CAPS_UNDEFINED; + + if(caps != CAPS_UNDEFINED) { + result = ((caps & key) == key); + } + + return result; +} + +void set_cap(uint32* caps, uint32 format) +{ + *caps |= format; +} + +static +const char* al_string(int param, ALCdevice_struct* target) +{ + const char* result = "\0"; + const char* dev_name = nullptr; + + dev_name = alcGetString(target, param); + + if(dev_name != nullptr) { + result = dev_name; + } + + return result; +} + +bool extension(const char* key) +{ + bool result = false; + ALboolean ext_avail = AL_FALSE; + + if(key == nullptr) { + // TODO(jeff): Err handling + return false; + } + + ext_avail = alIsExtensionPresent(key); + if(ext_avail == AL_TRUE) { + result = true; + } + + return result; +} + +bool context_extension(const char* key, ALCdevice_struct* target) +{ + bool result = false; + ALCboolean ext_avail = ALC_FALSE; + + if(key == nullptr) { + // TODO(jeff): Err handling + return false; + } + + ext_avail = alcIsExtensionPresent(target, key); + + if(ext_avail == ALC_TRUE) { + result = true; + } + + return result; +} + +uint32 enum_available(const char* key) +{ + uint32 result = 0; + ALenum enum_avail = AL_NONE; + + if(key != nullptr) { + + enum_avail = alGetEnumValue(key); + + // IMPORTANT(jeff): Fixes a bug regarding Apple's OpenAL SDK API return + // value of negative one (-1). + if(enum_avail == AL_INVALID) { + enum_avail = 0; + } + + result = enum_avail; + + } + + return result; +} + +const char* +default_output_device_name(ALCdevice_struct* target) +{ + const char* result = "\0"; + const char* dev_name = nullptr; + ALCdevice_struct* dev = target; + + if(audio::context_extension("ALC_ENUMERATE_ALL_EXT", dev) == true) { + dev_name = audio::al_string(ALC_DEFAULT_ALL_DEVICES_SPECIFIER, dev); + } else if(audio::context_extension("ALC_ENUMERATION_EXT", dev) == true) { + dev_name = audio::al_string(ALC_DEFAULT_DEVICE_SPECIFIER, dev); + } + + if(dev_name != nullptr) { + result = dev_name; + } + + return result; +} + +const char* +default_input_device_name(ALCdevice_struct* target) +{ + const char* result = "\0"; + const char* dev_name = nullptr; + ALCdevice_struct* dev = target; + + // TODO(jeff): Verify if we need to check for extensions here + dev_name = audio::al_string(ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER, dev); + + if(dev_name != nullptr) { + result = dev_name; + } + + return result; +} + +// TODO(jeff): Verify if we have any way of querying the size of the device +// names from OpenAL +static +void parse_openal_device_strings(const char* dev_name, std::stringstream& os) +{ + uint32 found_null_term = 0; + uint32 current_char = 0; + for(uint32 char_pos = 0; char_pos < nom::MAX_STRING_LENGTH; ++char_pos) { + + current_char = dev_name[char_pos]; + os << NOM_SCAST(uint8, current_char); + + // A space, or... end of string + if(current_char == 0x0) { + ++found_null_term; + } + + // End of string + if(found_null_term == 2) { + break; + } + } +} + +device_name_list output_device_names(void* target) +{ + ALCdevice_struct* dev = nullptr; + const char* output_devs = nullptr; + device_name_list output_dev_list; + + if(target != nullptr) { + // dev = target->dev; + // FIXME(jeff): Implement polymorphism! + dev = NOM_SCAST(ALCdevice_struct*, target); + } + + if(audio::context_extension("ALC_ENUMERATE_ALL_EXT", dev) == AL_TRUE) { + // IMPORTANT(jeff): Prefer the non-standard enumeration extension when it + // is available to us -- a complete list of available audio devices will be + // presented. + output_devs = audio::al_string(ALC_ALL_DEVICES_SPECIFIER, dev); + } else if(audio::context_extension("ALC_ENUMERATION_EXT", dev) == AL_TRUE) { + // IMPORTANT(jeff): A complete list of audio devices will **not** be shown + // to us -- this implementation is unlikely to show us more than the current + // default audio interface. + output_devs = audio::al_string(ALC_DEVICE_SPECIFIER, dev); + } + + if(output_devs != nullptr) { + std::stringstream os; + audio::parse_openal_device_strings(output_devs, os); + output_dev_list.push_back(os.str()); + } + + return output_dev_list; +} + +// TODO(jeff): Verify that this is right +device_name_list input_device_names(void* target) +{ + ALCdevice_struct* dev = nullptr; + const char* input_devs = nullptr; + device_name_list input_dev_list; + + if(target != nullptr) { + // dev = target->dev; + // FIXME(jeff): Implement polymorphism! + dev = NOM_SCAST(ALCdevice_struct*, target); + } + + if(audio::context_extension("ALC_ENUMERATE_ALL_EXT", dev) == AL_TRUE) { + // IMPORTANT(jeff): Prefer the non-standard enumeration extension when it + // is available to us -- a complete list of available audio devices will be + // presented. + input_devs = audio::al_string(ALC_ALL_DEVICES_SPECIFIER, dev); + } else if(audio::context_extension("ALC_ENUMERATION_EXT", dev) == AL_TRUE) { + // IMPORTANT(jeff): A complete list of audio devices will **not** be shown + // to us -- this implementation is unlikely to show us more than the current + // default audio interface. + input_devs = audio::al_string(ALC_CAPTURE_DEVICE_SPECIFIER, dev); + } + + if(input_devs != nullptr) { + std::stringstream os; + audio::parse_openal_device_strings(input_devs, os); + input_dev_list.push_back(os.str()); + } + + return input_dev_list; +} + +static int* +create_openal_attributes(const AudioSpec* spec) +{ + const uint8 NUM_ATTRIBUTES = 11; + + ALCint* attrs = new ALCint[NUM_ATTRIBUTES]; + if(attrs == nullptr) { + nom::set_error("Failed to allocate memory for attributes."); + return attrs; + } + + + if(spec != nullptr) { + int sync_ctx = 0; + + if(spec->sync_context == true) { + sync_ctx = 1; + } else { + sync_ctx = 0; + } + + attrs[0] = ALC_FREQUENCY; + attrs[1] = spec->sample_rate; + + attrs[2] = ALC_REFRESH; + attrs[3] = spec->refresh_rate; + + attrs[4] = ALC_SYNC; + attrs[5] = sync_ctx; + + attrs[6] = ALC_MONO_SOURCES; + attrs[7] = spec->num_mono_sources; + + attrs[8] = ALC_STEREO_SOURCES; + attrs[9] = spec->num_stereo_sources; + } + + attrs[10] = 0; + + return attrs; +} + +static audio::AudioSpec +create_openal_attributes(ALCdevice_struct* dev) +{ + audio::AudioSpec spec = {}; + + if(dev == nullptr) { + nom::set_error("Target audio device was invalid (NULL)."); + return spec; + } + + if(dev != nullptr) { + spec.engine = "openal"; + spec.name = audio::default_output_device_name(dev); + spec.sample_rate = audio::sample_rate(dev); + spec.refresh_rate = audio::refresh_rate(dev); + spec.sync_context = audio::sync_context(dev); + spec.num_mono_sources = audio::max_mono_sources(dev); + spec.num_stereo_sources = audio::max_stereo_sources(dev); + } + + return spec; +} + +// TODO(jeff): Finish implementation; split off from ::init_openal +// +// See also: http://stackoverflow.com/questions/5056986/openal-how-can-i-create-more-than-2-or-3-stereo-sources +static ALCcontext_struct* +init_openal_context(const audio::AudioSpec* request, void* driver) +{ + ALenum error_code = AL_NO_ERROR; + int* attributes = nullptr; + + // FIXME(jeff): Implement polymorphism here + auto dev = NOM_SCAST(ALCdevice_struct*, driver); + // auto ctx = NOM_SCAST(ALCcontext_struct*, context); + ALCcontext_struct* ctx = nullptr; + + nom::clear_error(); + attributes = audio::create_openal_attributes(request); + if(nom::error_state() == true) { + // TODO(jeff): Use nom::set_error + NOM_LOG_ERR(NOM_LOG_CATEGORY_APPLICATION, nom::error()); + } + + // NOTE: attach a context state to the audio device + AL_CLEAR_ERR(); + ctx = alcCreateContext(dev, attributes); + if(ctx == nullptr) { + error_code = alGetError(); + NOM_LOG_ERR(NOM_LOG_CATEGORY_APPLICATION, + "Failed to create the audio context.", + "See OpenAL error code", error_code); + return ctx; + } + + if(alcMakeContextCurrent(ctx) == false) { + // TODO(jeff): Use nom::set_error + NOM_LOG_ERR(NOM_LOG_CATEGORY_APPLICATION, + "Failed to make the context current."); + return ctx; + } + + AL_CLEAR_ERR(); + alcProcessContext(ctx); + error_code = alGetError(); + if(error_code != AL_NO_ERROR) { + NOM_LOG_ERR(NOM_LOG_CATEGORY_APPLICATION, + "Failed to make the context current.", + "See OpenAL error code", error_code); + return ctx; + } + +#if defined(NOM_PLATFORM_OSX) && defined(NOM_USE_APPLE_OPENAL) + if( audio::context_extension("ALC_EXT_MAC_OSX", dev) == true ) { + int total_num_sources = audio::osx_max_sources(); + int half_total_num_sources = (total_num_sources / 2); + int max_mono_sources = attributes[7]; + int max_stereo_sources = attributes[9]; + int max_sources = 0; // computed result + + // IMPORTANT(jeff): When using Apple's OpenAL distribution, we must use + // the ALC_EXT_MAC_OSX extension for querying and setting the output audio + // frequency rate; the ALC_FREQUENCY attribute is ignored during context + // creation. + audio::osx_set_sample_rate(attributes[1]); + + NOM_LOG_VERBOSE(NOM_LOG_CATEGORY_TEST, + "max_mono_sources (clamped):", + nom::clamp_min(max_mono_sources, half_total_num_sources)); + NOM_LOG_VERBOSE(NOM_LOG_CATEGORY_TEST, + "max_stereo_sources (clamped):", + nom::clamp_min(max_stereo_sources, half_total_num_sources)); + + max_sources = (max_mono_sources + max_stereo_sources); + NOM_ASSERT(max_sources <= total_num_sources); + + audio::osx_set_max_sources(max_sources); + } +#endif // end if defined NOM_USE_APPLE_OPENAL + + return ctx; +} + +int refresh_rate(void* target) +{ + ALCint refresh_rate = 0; + // FIXME(jeff): Implement type polymorphism here + auto dev = NOM_SCAST(ALCdevice_struct*, target); + + if(dev != nullptr) { + alcGetIntegerv(dev, ALC_REFRESH, sizeof(ALCint), &refresh_rate); + } + + return refresh_rate; +} + +bool sync_context(void* target) +{ + ALCint sync_context = 0; + bool result = false; + // FIXME(jeff): Implement type polymorphism here + auto dev = NOM_SCAST(ALCdevice_struct*, target); + + alcGetIntegerv(dev, ALC_SYNC, sizeof(ALCint), &sync_context); + + if(sync_context == 1) { + result = true; + } + + return result; +} + +int sample_rate(void* target) +{ + ALCint sample_rate = 0; + // FIXME(jeff): Implement type polymorphism here + auto dev = NOM_SCAST(ALCdevice_struct*, target); + + bool osx_extension = audio::context_extension("ALC_EXT_MAC_OSX", dev); + if(osx_extension == true) { +#if defined(NOM_PLATFORM_OSX) && defined(NOM_USE_APPLE_OPENAL) + // TODO(jeff): This function signature returns a 64-bit floating-point + // number -- we ought to carefully consider the ramifications of this and + // see about safeguarding against significant bit loss. + sample_rate = NOM_SCAST(int, audio::osx_sample_rate()); +#endif + } else if(osx_extension == false) { + alcGetIntegerv(dev, ALC_FREQUENCY, sizeof(ALCint), &sample_rate); + } + + return sample_rate; +} + +#if 0 +static +void set_sample_rate(int sample_rate, void* target) +{ + // FIXME(jeff): Implement type polymorphism here + auto dev = NOM_SCAST(ALCdevice_struct*, target); + + bool osx_extension = audio::context_extension("ALC_EXT_MAC_OSX", dev); + if(osx_extension == true) { + audio::osx_set_sample_rate(sample_rate); + } +} +#endif + +int max_mono_sources(void* target) +{ + int num_mono_sources = 0; + + // FIXME: Implement polymorphism for target pointer + auto dev = NOM_SCAST(ALCdevice_struct*, target); + + if(dev == nullptr) { + return num_mono_sources; + } + + alcGetIntegerv(dev, ALC_MONO_SOURCES, sizeof(num_mono_sources), + &num_mono_sources); + + return num_mono_sources; +} + +int max_stereo_sources(void* target) +{ + int num_stereo_sources = 0; + + // FIXME: Implement polymorphism for target pointer + auto dev = NOM_SCAST(ALCdevice_struct*, target); + + if(dev == nullptr) { + return num_stereo_sources; + } + + alcGetIntegerv(dev, ALC_STEREO_SOURCES, sizeof(num_stereo_sources), + &num_stereo_sources); + + return num_stereo_sources; +} +#if 1 +int max_sources(void* target) +{ + int num_mono_sources = 0; + int num_stereo_sources = 0; + int total_num_sources = 0; + + // FIXME(jeff): Implement type polymorphism here + auto dev = NOM_SCAST(ALCdevice_struct*, target); + + if(audio::context_extension("ALC_EXT_MAC_OSX", dev) == true) { +#if defined(NOM_PLATFORM_OSX) && defined(NOM_USE_APPLE_OPENAL) + total_num_sources = audio::osx_max_sources(); +#endif + } else { + // IMPORTANT(jeff): The Mac OS X extension [1] for querying the number of + // sources only offers query of the total number of sources, and not the + // individual number of mono and stereo sources. In interests of a + // consistent API, we do the following math below to create a comparable + // sum of the total number of sound sources. + // + // 1. We must go through the extension's API in order to determine the + // correct number of sources. + alcGetIntegerv(dev, ALC_MONO_SOURCES, sizeof(num_mono_sources), + &num_mono_sources); + alcGetIntegerv(dev, ALC_STEREO_SOURCES, sizeof(num_stereo_sources), + &num_stereo_sources); + + total_num_sources = + nom::clamp_min( (num_mono_sources + num_stereo_sources), + TOTAL_NUM_SOURCES); + NOM_ASSERT(total_num_sources <= TOTAL_NUM_SOURCES); + } + + return total_num_sources; +} +#endif +#if 0 +void set_max_sources(int num_sources, void* target) +{ + // FIXME(jeff): Implement type polymorphism here + auto dev = NOM_SCAST(ALCdevice_struct*, target); + + if(audio::context_extension("ALC_EXT_MAC_OSX", dev) == true) { +#if defined(NOM_PLATFORM_OSX) && defined(NOM_USE_APPLE_OPENAL) + audio::osx_set_max_sources(num_sources); +#endif + } + // TODO(jeff): Finish implementation; see also, ::create_spec, + // ::init_openal_context + + // int* attributes = nullptr; + audio::AudioSpec spec = {}; + + spec.sample_rate = audio::sample_rate(dev); + spec.refresh_rate = audio::refresh_rate(dev); + spec.sync_context = audio::sync_context(dev); + spec.num_mono_sources = num_sources; + spec.num_stereo_sources = num_sources; + + // attributes = audio::create_openal_attributes(&spec); + // ALCcontext* ctx = current_context(); + // if(ctx != nullptr && dev->valid() == true) { + // } + + // ALCcontext_struct* ctx = nullptr; + // ctx = audio::init_openal_context(&spec, dev); +} +#endif + +static IOAudioEngine* +init_openal_output(const audio::AudioSpec* request, audio::AudioSpec* spec) +{ + ALAudioDevice* driver = new ALAudioDevice; + IOAudioEngine* impl = nullptr; + const char* req_audio_dev_name = nullptr; + + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE_AUDIO, NOM_LOG_PRIORITY_VERBOSE); + + if(driver == nullptr) { + return impl; + } + + auto dev = driver->dev; + auto ctx = driver->ctx; + + if(request != nullptr) { + if(request->name != nullptr) { + req_audio_dev_name = request->name; + } + } + + // If this object instance has been used before; we must clean up its memory + // first... + // if(dev != nullptr) { + // audio::shutdown_openal(driver); + // } + + // TODO(jeff): Verify that this enumeration is available on Windows + // Availability verified: Apple's OpenAL SDK and OpenAL-Soft. + { + if(audio::context_extension("ALC_EXT_disconnect", dev) == true) { + auto enum_avail = audio::enum_available("ALC_CONNECTED"); + if(enum_avail != 0) { + int dev_connected = 0; + // NOTE(jeff): Defined in file at openal-soft.git/include/AL/alext.h + #if defined(ALC_EXT_disconnect) + alcGetIntegerv(nullptr, ALC_CONNECTED, sizeof(int), &dev_connected); + #endif + NOM_DUMP(dev_connected); // should be 1 when the extension is available + } + } + } + + // create the audio device handle + AL_CLEAR_ERR(); + dev = alcOpenDevice(req_audio_dev_name); + if(dev == nullptr) { + std::string al_err_str; + + ALenum error = alGetError(); + if(error != AL_NO_ERROR) { + al_err_str = nom::integer_to_string(error); + } + + std::string err_str = "Failed to open the audio device; " + + std::string("OpenAL error code" + al_err_str); + // Err; memory allocation failure? + nom::set_error(err_str); + return impl; + } + + ctx = audio::init_openal_context(request, dev); + + // IAudioDevice* testme = new AudioDevice(); + // if(NOM_ISA(AudioDevice*, testme) == true) { + // NOM_DUMP("!"); + // } + + // NOTE(jeff): Enumerate the audio hardware capabilities available to us via + // the OpenAL API -- these are subject to change upon state jumps between + // audio contexts. + ALCint res_num_mono = 0; + ALCint res_num_stereo = 0; + // alcGetIntegerv(dev, ALC_FREQUENCY, sizeof(ALCint), &res_frequency); + alcGetIntegerv(dev, ALC_MONO_SOURCES, sizeof(ALCint), &res_num_mono); + alcGetIntegerv(dev, ALC_STEREO_SOURCES, sizeof(ALCint), &res_num_stereo); + // NOM_DUMP(res_frequency); + NOM_DUMP(res_num_mono); + NOM_DUMP(res_num_stereo); +#if 0 + ALCint size; + alcGetIntegerv(dev, ALC_ATTRIBUTES_SIZE, sizeof(ALCint), &size); + NOM_DUMP(size); + + int attrs[size]; + alcGetIntegerv(dev, ALC_ALL_ATTRIBUTES, size, attrs); + for(auto attr_index = 0; attr_index != size; ++attr_index) { + + if(attrs[attr_index] == ALC_FREQUENCY) { + if(attr_index + 1 < size) { + NOM_DUMP_VAR(NOM, "frequency:", attrs[attr_index+1]); + } + } else if(attrs[attr_index] == ALC_REFRESH) { + if(attr_index + 1 < size) { + NOM_DUMP_VAR(NOM, "refresh:", attrs[attr_index+1]); + } + } else if(attrs[attr_index] == ALC_SYNC) { + if(attr_index + 1 < size) { + NOM_DUMP_VAR(NOM, "sync:", attrs[attr_index+1]); + } + } else if(attrs[attr_index] == ALC_MONO_SOURCES) { + if(attr_index + 1 < size) { + NOM_DUMP_VAR(NOM, "max mono sources:", attrs[attr_index+1]); + } + } else if(attrs[attr_index] == ALC_STEREO_SOURCES) { + if(attr_index + 1 < size) { + NOM_DUMP_VAR(NOM, "max stereo sources:", attrs[attr_index+1]); + } + } else { + // NOM_DUMP(attrs[attr_index]); + } + } +#endif + + std::vector enum_extensions = { + // 8-bit integers + "AL_FORMAT_MONO8", + "AL_FORMAT_STEREO8", + "AL_FORMAT_QUAD8", + "AL_FORMAT_51CHN8", + "AL_FORMAT_61CHN8", + "AL_FORMAT_71CHN8", + // 16-bit integers + "AL_FORMAT_MONO16", + "AL_FORMAT_STEREO16", + "AL_FORMAT_QUAD16", + "AL_FORMAT_51CHN16", + "AL_FORMAT_61CHN16", + "AL_FORMAT_71CHN16", + // 32-bit floating-point + "AL_FORMAT_MONO_FLOAT32", + "AL_FORMAT_STEREO_FLOAT32", + "AL_FORMAT_MONO_DOUBLE_EXT", + "AL_FORMAT_STEREO_DOUBLE_EXT", + // 32-bit integer + "AL_FORMAT_QUAD32", + "AL_FORMAT_51CHN32", + "AL_FORMAT_61CHN32", + "AL_FORMAT_71CHN32" + }; + + { + // bool enum_available = false; + for(auto itr = enum_extensions.begin(); + itr != enum_extensions.end(); + ++itr) + { + if(audio::enum_available(*itr) != 0) { + NOM_DUMP_VAR(NOM, "available extension:", *itr); + } else { + NOM_DUMP_VAR(NOM, "unavailable extension:", *itr); + } + } // end for loop + } + + { + if(audio::enum_available("AL_FORMAT_MONO8") != 0) { + driver->capabilities |= CAPS_FORMAT_MONO_S8; + } + + if(audio::enum_available("AL_FORMAT_STEREO8") != 0) { + driver->capabilities |= CAPS_FORMAT_STEREO_S8; + } + + if(audio::enum_available("AL_FORMAT_QUAD8") != 0) { + driver->capabilities |= CAPS_FORMAT_QUAD_S8; + } + + if(audio::enum_available("AL_FORMAT_51CHN_S8") != 0) { + driver->capabilities |= CAPS_FORMAT_51CHN_S8; + } + + if(audio::enum_available("AL_FORMAT_61CHN_S8") != 0) { + driver->capabilities |= CAPS_FORMAT_61CHN_S8; + } + + if(audio::enum_available("AL_FORMAT_71CHN_S8") != 0) { + driver->capabilities |= CAPS_FORMAT_71CHN_S8; + } + + if(audio::enum_available("AL_FORMAT_MONO16") != 0) { + // driver->capabilities |= CAPS_FORMAT_MONO_S16; + audio::set_cap(&driver->capabilities, CAPS_FORMAT_MONO_S16); + } + + if(audio::enum_available("AL_FORMAT_STEREO16") != 0) { + // driver->capabilities |= CAPS_FORMAT_STEREO_S16; + audio::set_cap(&driver->capabilities, CAPS_FORMAT_STEREO_S16); + } + + if(audio::enum_available("AL_FORMAT_QUAD16") != 0) { + driver->capabilities |= CAPS_FORMAT_QUAD_S16; + } + + if(audio::enum_available("AL_FORMAT_51CHN16") != 0) { + driver->capabilities |= CAPS_FORMAT_51CHN_S16; + } + + if(audio::enum_available("AL_FORMAT_61CHN16") != 0) { + driver->capabilities |= CAPS_FORMAT_61CHN_S16; + } + + if(audio::enum_available("AL_FORMAT_71CHN16") != 0) { + driver->capabilities |= CAPS_FORMAT_71CHN_S16; + } + +// #if defined(NOM_USE_LIBSNDFILE) +// driver->capabilities &= ~CAPS_FORMAT_MONO_S8; +// driver->capabilities &= ~CAPS_FORMAT_STEREO_S8; +// driver->capabilities &= ~CAPS_FORMAT_QUAD_S8; +// driver->capabilities &= ~CAPS_FORMAT_51CHN_S8; +// driver->capabilities &= ~CAPS_FORMAT_61CHN_S8; +// driver->capabilities &= ~CAPS_FORMAT_71CHN_S8; + +// driver->capabilities &= ~CAPS_FORMAT_MONO_U8; +// driver->capabilities &= ~CAPS_FORMAT_STEREO_U8; +// driver->capabilities &= ~CAPS_FORMAT_QUAD_U8; +// driver->capabilities &= ~CAPS_FORMAT_51CHN_U8; +// driver->capabilities &= ~CAPS_FORMAT_61CHN_U8; +// driver->capabilities &= ~CAPS_FORMAT_71CHN_U8; +// #endif + } + + { + bool ext = audio::extension("AL_EXT_float32"); + NOM_DUMP_VAR(NOM, "AL_EXT_float32 extension:", ext); + { + auto format = audio::enum_available("AL_FORMAT_MONO_FLOAT32"); + NOM_DUMP_VAR(NOM, "AL_FORMAT_MONO_FLOAT32:", format); + if(format != 0) { + driver->capabilities |= CAPS_FORMAT_MONO_FLOAT32; + } + } + + { + auto format = audio::enum_available("AL_FORMAT_STEREO_FLOAT32"); + NOM_DUMP_VAR(NOM, "AL_FORMAT_STEREO_FLOAT32:", format); + if(format != 0) { + driver->capabilities |= CAPS_FORMAT_STEREO_FLOAT32; + } + } + } + + { + bool ext = audio::extension("AL_EXT_DOUBLE"); + NOM_DUMP_VAR(NOM, "AL_EXT_DOUBLE extension:", ext); + + { + auto format = audio::enum_available("AL_FORMAT_MONO_DOUBLE_EXT"); + NOM_DUMP_VAR(NOM, "AL_FORMAT_MONO_DOUBLE_EXT:", format); + if(format != 0) { + driver->capabilities |= CAPS_FORMAT_MONO_FLOAT64; + } + } + + { + auto format = audio::enum_available("AL_FORMAT_STEREO_DOUBLE_EXT"); + NOM_DUMP_VAR(NOM, "AL_FORMAT_STEREO_DOUBLE_EXT:", format); + if(format != 0) { + driver->capabilities |= CAPS_FORMAT_STEREO_FLOAT64; + } + } + } + + NOM_DUMP_VAR(NOM, "caps:", driver->capabilities); + + { + bool ext = audio::extension("AL_EXT_MCFORMATS"); + NOM_DUMP_VAR(NOM, "AL_EXT_MCFORMATS extension:", ext); + } + + { + bool ext = audio::extension("AL_LOKI_quadriphonic"); + NOM_DUMP_VAR(NOM, "AL_LOKI_quadriphonic extension:", ext); + } + + // TODO(jeff): Verify that this enumeration is available on Windows + // Availability verified: Apple's OpenAL SDK and OpenAL-Soft. + { + auto enum_avail = audio::enum_available("ALC_CONNECTED"); + if(enum_avail != 0) { + int dev_connected = 0; +// NOTE(jeff): Defined in file at openal-soft.git/include/AL/alext.h +#if defined(ALC_EXT_disconnect) + alcGetIntegerv(dev, ALC_CONNECTED, sizeof(int), &dev_connected); +#endif + NOM_DUMP(dev_connected); // should be 1 when the extension is available + } + } + + // See http://openal.996291.n3.nabble.com/ALC-ENUMERATE-ALL-EXT-and-pluggable-sound-devices-tp5333p5337.html + { + auto previous_devices = driver->name; + // ignore that C string funcs fail on the null-separated list for now. + const char* current_devices = audio::default_output_device_name(dev); + if(strcmp(current_devices, previous_devices) != 0) { + // handle device list change here, if for some reason you need to. + // free(previous_devices); + previous_devices = strdup(current_devices); + } + + NOM_DUMP(previous_devices); + NOM_DUMP(current_devices); + } + + + // Check for EAX 2.0 support + { + bool ext = alIsExtensionPresent("EAX2.0"); + NOM_DUMP_VAR(NOM, "EAX2.0 extension:", ext); + } + // if(ext == AL_TRUE) { + // auto eax_fn = (eax_extension)audio::process_addr("EAXSet"); + // if(eax_fn == NULL) { + // ext = false; + // } + // } + // } + + { + bool ext = audio::context_extension("ALC_EXT_EFX", dev); + NOM_DUMP_VAR(NOM, "ALC_EXT_EFX extension:", ext); + } + + { + bool ext = audio::context_extension("ALC_EXT_MAC_OSX", dev); + NOM_DUMP_VAR(NOM, "ALC_EXT_MAC_OSX extension:", ext); + } + + { + int sample_rate = 0; + int max_mono_sources = 0; + int max_stereo_sources = 0; + int max_sources = 0; + + sample_rate = audio::sample_rate(dev); + NOM_LOG_DEBUG(NOM_LOG_CATEGORY_TEST, + "frequency:", sample_rate); + + max_mono_sources = audio::max_mono_sources(dev); + NOM_LOG_DEBUG(NOM_LOG_CATEGORY_TEST, + "max_mono_sources:", max_mono_sources); + + max_stereo_sources = audio::max_stereo_sources(dev); + NOM_LOG_DEBUG(NOM_LOG_CATEGORY_TEST, + "max_stereo_sources:", max_stereo_sources); + + max_sources = audio::max_sources(dev); + NOM_LOG_DEBUG(NOM_LOG_CATEGORY_TEST, + "max_sources:", max_sources); + } + + // device_name_list output_devs = + // audio::output_device_names(dev); + // driver->enumerate_output_devices = audio::output_device_names; + + // device_name_list input_devs = + // audio::input_device_names(dev); + // driver->enumerate_input_devices = audio::input_device_names; + + nom::clear_error(); + *spec = audio::create_openal_attributes(dev); + if(spec == nullptr) { + bool error_state = nom::error_state(); + if(error_state == true) { + auto err = nom::error(); + // TODO(jeff): Use nom::set_error here + NOM_LOG_ERR(NOM_LOG_CATEGORY_APPLICATION, err); + return impl; + } + } + + driver->name = spec->name; + driver->dev = dev; + driver->ctx = ctx; + impl = new ALAudioEngine(driver); + + if(impl == nullptr) { + nom::set_error("Failed to allocate memory for IOAudioEngine."); + return impl; + } + + return impl; +} + +// TODO +static IOAudioEngine* +init_openal_input(const audio::AudioSpec* request, audio::AudioSpec* spec) +{ + return nullptr; +} + +IOAudioEngine* +init_audio(const audio::AudioSpec* request, audio::AudioSpec* spec) +{ + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE_AUDIO, NOM_LOG_PRIORITY_VERBOSE); + + IOAudioEngine* driver = nullptr; + if(request != nullptr && request->engine != nullptr) { + if(nom::compare_cstr_insensitive(request->engine, "openal") == 0) { + driver = audio::init_openal_output(request, spec); + } + + // TODO(jeff): Implement general purpose code for NULL device + // initialization + } + + return driver; +} + +void shutdown_audio(IOAudioEngine* impl) +{ + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE_AUDIO, NOM_LOG_PRIORITY_DEBUG); + + if(impl != nullptr && impl->valid() == true) { + impl->close(); + } + + impl = nullptr; +} + +// const char* audio_device_name(IOAudioEngine* target) +// { +// const char* result = "\0"; + +// // if(target != nullptr && target->valid() == true) { +// { +// // result = target->device_name(); +// } + +// return result; +// } + +int num_audio_devices() +{ + int num_devices = -1; + + // + + return num_devices; +} +#if 0 +NOM_IGNORED_VARS(); +const char* audio_device(void* target) +{ + const char* result = "\0"; + const char* device_name = nullptr; + ALAudioDevice* audio_dev = nullptr; + + if(target == nullptr) { + return result; + } + + // FIXME(jeff): Implement type polymorphism here + ALAudioDevice* dev = NOM_SCAST(ALAudioDevice*, target); +#if 0 + if (alcIsExtensionPresent(NULL, "ALC_enumeration_EXT") == AL_TRUE) { + + if (alcIsExtensionPresent(NULL, "ALC_enumerate_all_EXT") == AL_FALSE) { + s = (char *)alcGetString(NULL, ALC_DEVICE_SPECIFIER); + } else { + s = (char *)alcGetString(NULL, ALC_ALL_DEVICES_SPECIFIER); + } + displayDevices("output", s); + + s = (char *)alcGetString(NULL, ALC_CAPTURE_DEVICE_SPECIFIER); + displayDevices("input", s); + } + + return(s != nullptr); + } +#endif + + return result; +} +NOM_IGNORED_VARS_ENDL(); +#endif +#if 0 +real64 device_time(void* target) +{ + real64 result = 0.0f; + // FIXME(jeff): Implement type polymorphism here + auto dev = NOM_SCAST(ALAudioDevice*, target); + + if(dev == nullptr) { + return result; + } + + // result = HighResolutionTimer::to_seconds(dev->elapsed_ticks_); + result = Timer::to_seconds(dev->elapsed_ticks_); + + return result; +} +#endif +} // namespace audio +} // namespace nom diff --git a/src/audio/AL/ALAudioDeviceCaps.cpp b/src/audio/AL/ALAudioDeviceCaps.cpp new file mode 100644 index 00000000..9ffc7d31 --- /dev/null +++ b/src/audio/AL/ALAudioDeviceCaps.cpp @@ -0,0 +1,1333 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014, 2015, 2016 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#include "nomlib/audio/AL/ALAudioDeviceCaps.hpp" + +// Private headers +#include "nomlib/core/SDL2Logger.hpp" // TODO: REMOVE + +#include "nomlib/core/err.hpp" +#include "nomlib/core/strings.hpp" +#include "nomlib/audio/audio_defs.hpp" +#include "nomlib/audio/AL/OpenAL.hpp" +#include "nomlib/audio/SoundFile.hpp" + +// Forward declarations +#include "nomlib/audio/AL/ALAudioDevice.hpp" +#include "nomlib/audio/SoundBuffer.hpp" + +namespace nom { +namespace audio { + +static +bool valid_buffer(uint32 buffer_id) +{ + bool result = alIsBuffer(buffer_id); + + return result; +} + +static +bool valid_source(uint32 source_id) +{ + bool result = alIsSource(source_id); + + return result; +} + +static +void attach_buffer(uint32 source_id, uint32 buffer_id) +{ + // NOTE(jeff): Attach a single buffer to the sound source + AL_CLEAR_ERR(); + alSourcei(source_id, AL_BUFFER, buffer_id); + + ALenum error = alGetError(); + if(error != AL_NO_ERROR) { + auto al_err_str = nom::integer_to_string(error); + // TODO(jeff): Improve err handling + std::string err_str = "OpenAL error code: " + al_err_str; + nom::set_error(err_str); + } +} + +static +void clear_buffer(uint32 source_id) +{ + uint32 buffer_id = 0; + + audio::attach_buffer(source_id, buffer_id); +} + +// TODO(jeff): Should this be accessible to the end-user..? +static +nom::size_type processed_buffers(SoundBuffer* target) +{ + ALint result = 0; + + if(target != nullptr) { + AL_CLEAR_ERR(); + alGetSourcei(target->source_id, AL_BUFFERS_PROCESSED, &result); + AL_CHECK_ERR_VOID(); + } + + return result; +} + +// TODO(jeff): Should this be accessible to the end-user..? +static +nom::size_type queued_buffers(SoundBuffer* target) +{ + ALint result = 0; + + if(target != nullptr) { + AL_CLEAR_ERR(); + alGetSourcei(target->source_id, AL_BUFFERS_QUEUED, &result); + AL_CHECK_ERR_VOID(); + } + + return result; +} + +// TODO(jeff): Should this be accessible to the end-user..? +static +void free_stream(SoundBuffer* target) +{ + uint32 num_sources = 1; + + if(target != nullptr) { + AL_CLEAR_ERR(); + alSourceUnqueueBuffers(target->source_id, num_sources, &target->buffer_id); + AL_CHECK_ERR_VOID(); + } +} + +// TODO(jeff): Should this be accessible to the end-user..? +static +void free_source(SoundBuffer* target) +{ + uint32 num_sources = 1; + + if(target != nullptr) { + AL_CLEAR_ERR(); + alDeleteSources(num_sources, &target->source_id); + AL_CHECK_ERR_VOID(); + } +} + +void free_buffer(uint32 buffer_id) +{ + uint32 num_sources = 1; + + // NOTE(jeff): Free the audio buffer from our scope + AL_CLEAR_ERR(); + alDeleteBuffers(num_sources, &buffer_id); + AL_CHECK_ERR_VOID(); +} + +void free_source(uint32 source_id) +{ + uint32 num_sources = 1; + + // NOTE(jeff): Free the audio buffer from our scope + AL_CLEAR_ERR(); + alDeleteBuffers(num_sources, &source_id); + AL_CHECK_ERR_VOID(); +} + +uint32 buffer_id(const SoundBuffer* target) +{ + uint32 id = 0; + + if(target != nullptr) { + id = target->buffer_id; + } + + return id; +} + +uint32 source_id(const SoundBuffer* target) +{ + uint32 id = 0; + + if(target != nullptr) { + id = target->source_id; + } + + return id; +} + +uint32 next_buffer_id() +{ + uint32 id = 0; + uint32 num_buffers = 1; + + AL_CLEAR_ERR(); + alGenBuffers(num_buffers, &id); + ALenum error_code = alGetError(); + if(error_code != AL_NO_ERROR) { + // TODO(jeff): Handle errors here; not sure how we ought to do this + // just yet -- OpenAL error handling value range is between + // 40961..40965 + // id = 0; + } + + return id; +} + +uint32* next_buffer_id(uint32 num_buffers) +{ + uint32* buffers = nullptr; + + buffers = new uint32[num_buffers]; + + AL_CLEAR_ERR(); + alGenBuffers(num_buffers, buffers); + ALenum error_code = alGetError(); + if(error_code != AL_NO_ERROR) { + // TODO(jeff): Handle errors here; not sure how we ought to do this + // just yet -- OpenAL error handling value range is between + // 40961..40965 + // id = 0; + } + + return buffers; +} + +uint32 next_source_id() +{ + uint32 id = 0; + uint32 num_sources = 1; + + AL_CLEAR_ERR(); + alGenSources(num_sources, &id); + ALenum error_code = alGetError(); + if(error_code != AL_NO_ERROR) { + // TODO(jeff): Handle errors here; not sure how we ought to do this + // just yet -- OpenAL error handling value range is between + // 40961..40965 + // id = 0; + } + + return id; +} + +uint32* next_source_id(uint32 num_sources) +{ + uint32* sources = nullptr; + + sources = new uint32[num_sources]; + + AL_CLEAR_ERR(); + alGenBuffers(num_sources, sources); + ALenum error_code = alGetError(); + if(error_code != AL_NO_ERROR) { + // TODO(jeff): Handle errors here; not sure how we ought to do this + // just yet -- OpenAL error handling value range is between + // 40961..40965 + // id = 0; + } + + return sources; +} + +#if 0 +uint32 buffer_id(uint32 num_buffers, SoundBuffer* target) + // uint32 generated_buffer_ids[]) +{ + // uint32 id[num_buffers]; + uint32 id = 0; + + if(buffer != nullptr) { + // NOTE(jeff): Generate a new unique identifier for the audio buffer when + // the ID is unused, otherwise, use the existing identifier found. + if(target->buffer_id == 0) { + // NOTE(jeff): The local variable id stores the buffer's ID, given to us from + // OpenAL. + AL_CLEAR_ERR(); + alGenBuffers(num_buffers, &id); + + ALenum error_code = alGetError(); + if(error_code != AL_NO_ERROR) { + // TODO(jeff): Handle errors here; not sure how we ought to do this + // just yet -- OpenAL error handling value range is between + // 40961..40965 + // id = 0; + } + } else { + id = target->buffer_id; + } + } + + return id; +} + +uint32 buffer_id(SoundBuffer* target) +{ + uint32 id = 0; + uint32 num_buffers = 1; + + id = audio::buffer_id(num_buffers, buffer); + + return id; +} +#endif + +#if 0 +uint32 sound_id(uint32 num_sources, SoundBuffer* target) +{ + uint32 id = 0; + + if(buffer != nullptr) { + // NOTE(jeff): Generate a new unique identifier for the sound source when + // the source ID is not set. Otherwise, use the existing identifier that + // has presumably been assigned to it by a previous call to OpenAL. + if(target->source_id == 0) { + AL_CLEAR_ERR(); + alGenSources(num_sources, &id); + + ALenum error_code = alGetError(); + if(error_code != AL_NO_ERROR) { + // TODO(jeff): Handle errors here; not sure how we ought to do this + // just yet -- OpenAL error handling value range is between + // 40961..40965 + // id = 0; + } + } else { + id = target->source_id; + } + } + + return id; +} + +uint32 sound_id(SoundBuffer* target) +{ + uint32 id = 0; + uint32 num_sources = 1; + + id = audio::sound_id(num_sources, buffer); + + return id; +} +#endif + +ALAudioEngine::ALAudioEngine() +{ + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE_AUDIO, NOM_LOG_PRIORITY_DEBUG); + + // TODO(jeff): Remove + NOM_LOG_INFO(NOM, "Audio device initialized"); +} + +ALAudioEngine::~ALAudioEngine() +{ + if(this->valid() == true) { + this->close(); + } +} + +ALAudioEngine::ALAudioEngine(ALAudioDevice* driver) +{ + this->impl_ = driver; +} + +void ALAudioEngine::init(void* driver) +{ + this->impl_ = (ALAudioDevice*)driver; +} + +bool ALAudioEngine::valid() const +{ + bool result = false; + + if(this->impl_ != nullptr) { + auto dev = this->impl_->dev; + auto ctx = this->impl_->ctx; + result = (dev != nullptr && ctx != nullptr); + } + + return result; +} + +uint32 ALAudioEngine::caps() const +{ + uint32 result = 0; + if(this->impl_ != nullptr) { + result = this->impl_->capabilities; + } + + return result; +} + +void ALAudioEngine::set_cap(uint32 format) +{ + this->impl_->capabilities |= format; +} + +bool ALAudioEngine::connected() const +{ + auto device = this->impl_->dev; + int result = 0; + + // NOTE(jeff): Defined in file at openal-soft.git/include/AL/alext.h + #if defined(ALC_EXT_disconnect) + alcGetIntegerv(device, ALC_CONNECTED, sizeof(int), &result); + #endif + + if(result == 1) { + return true; + } + + return false; +} + +// auto dev = NOM_SCAST(ALCdevice_struct*, target->device()); +// uint32 value = alcGetEnumValue(dev, "AL_FORMAT_STEREO_FLOAT32"); +// NOM_DUMP(value); +// uint32 error_code = alcGetError(dev); +// NOM_DUMP(error_code); +uint32 ALAudioEngine::channel_format(uint32 num_channels, uint32 channel_format) +{ + ALenum format = 0; + auto channel_count = num_channels; + + // FIXME(jeff): I'm unable to test 8-bit audio formats yet, due to a lacking + // implementation of data handling within nom::SoundFileReader. We are unable + // to interpret the 8-bit data natively using libsndfile. + if(channel_format == AUDIO_FORMAT_U8 || channel_format == AUDIO_FORMAT_S8) { + // channel_format = AUDIO_FORMAT_S16; + } + + if(this->impl_ != nullptr) { + auto caps = this->caps(); + if(caps & CAPS_UNDEFINED) { + NOM_DUMP("???undefined???"); + } + + if(audio::cap(caps, CAPS_FORMAT_MONO_S8) == false) { + NOM_DUMP("missing caps for CAPS_FORMAT_MONO_S8"); + } + + if(audio::cap(caps, CAPS_FORMAT_STEREO_S8) == false) { + NOM_DUMP("missing caps for CAPS_FORMAT_STEREO_S8"); + } + + if(audio::cap(caps, CAPS_FORMAT_MONO_U8) == false) { + NOM_DUMP("missing caps for CAPS_FORMAT_MONO_U8"); + } + + if(audio::cap(caps, CAPS_FORMAT_STEREO_U8) == false) { + NOM_DUMP("missing caps for CAPS_FORMAT_STEREO_U8"); + } + + if(audio::cap(caps, CAPS_FORMAT_MONO_S16) == false) { + NOM_DUMP("missing caps for CAPS_FORMAT_MONO_S16"); + } + + if(audio::cap(caps, CAPS_FORMAT_STEREO_S16) == false) { + NOM_DUMP("missing caps for CAPS_FORMAT_STEREO_S16"); + } + + if(audio::cap(caps, CAPS_FORMAT_QUAD_U8) == false) { + NOM_DUMP("missing caps for CAPS_FORMAT_QUAD_U8"); + } + + if(audio::cap(caps, CAPS_FORMAT_QUAD_S16) == false) { + NOM_DUMP("missing caps for CAPS_FORMAT_QUAD_S16"); + } + + if(audio::cap(caps, CAPS_FORMAT_MONO_FLOAT32) == false) { + NOM_DUMP("missing caps for CAPS_FORMAT_MONO_FLOAT32"); + } + + if(audio::cap(caps, CAPS_FORMAT_STEREO_FLOAT32) == false) { + NOM_DUMP("missing caps for CAPS_FORMAT_STEREO_FLOAT32"); + } + + if(audio::cap(caps, CAPS_FORMAT_MONO_FLOAT64) == false) { + NOM_DUMP("missing caps for CAPS_FORMAT_MONO_FLOAT64"); + } + + if(audio::cap(caps, CAPS_FORMAT_STEREO_FLOAT64) == false) { + NOM_DUMP("missing caps for CAPS_FORMAT_STEREO_FLOAT64"); + } + + switch(channel_count) { + default: + case 1: { + if(channel_format == AUDIO_FORMAT_S8) { + if(audio::cap(caps, CAPS_FORMAT_MONO_S8) == true) { + format = audio::enum_available("AL_FORMAT_MONO8"); + } + } else if(channel_format == AUDIO_FORMAT_U8) { + if(audio::cap(caps, CAPS_FORMAT_MONO_U8) == true) { + format = audio::enum_available("AL_FORMAT_MONO8"); + } + } else if(channel_format == AUDIO_FORMAT_S16) { + if(audio::cap(caps, CAPS_FORMAT_MONO_S16) == true) { + format = audio::enum_available("AL_FORMAT_MONO16"); + } + } else if(channel_format == AUDIO_FORMAT_S24) { + if(audio::cap(caps, CAPS_FORMAT_MONO_S24) == true) { + format = audio::enum_available("AL_FORMAT_MONO24"); + } + } else if(channel_format == AUDIO_FORMAT_S32) { + if(audio::cap(caps, CAPS_FORMAT_MONO_S32) == true) { + format = audio::enum_available("AL_FORMAT_MONO32"); + } + } else if(channel_format == AUDIO_FORMAT_R32) { + if(audio::cap(caps, CAPS_FORMAT_MONO_FLOAT32) == true) { + format = audio::enum_available("AL_FORMAT_MONO_FLOAT32"); + } + } else if(channel_format == AUDIO_FORMAT_R64) { + if(audio::cap(caps, CAPS_FORMAT_MONO_FLOAT64) == true) { + format = audio::enum_available("AL_FORMAT_MONO_DOUBLE_EXT"); + } + } + } break; + + case 2: { + // ... + } break; + + case 3: { + // ... + } break; + + case 4: { + if(channel_format == AUDIO_FORMAT_S8) { + if(audio::cap(caps, CAPS_FORMAT_QUAD_S8) == true) { + format = audio::enum_available("AL_FORMAT_QUAD8"); + } + } else if(channel_format == AUDIO_FORMAT_U8) { + if(audio::cap(caps, CAPS_FORMAT_QUAD_U8) == true) { + format = audio::enum_available("AL_FORMAT_QUAD8"); + } + } else if(channel_format == AUDIO_FORMAT_S16) { + if(audio::cap(caps, CAPS_FORMAT_QUAD_S16) == true) { + format = audio::enum_available("AL_FORMAT_QUAD16"); + } + } else if(channel_format == AUDIO_FORMAT_S24) { + if(audio::cap(caps, CAPS_FORMAT_QUAD_S24) == true) { + format = audio::enum_available("AL_FORMAT_QUAD24"); + } + } else if(channel_format == AUDIO_FORMAT_S32) { + if(audio::cap(caps, CAPS_FORMAT_QUAD_S32) == true) { + format = audio::enum_available("AL_FORMAT_QUAD32"); + } + } else if(channel_format == AUDIO_FORMAT_R32) { + if(audio::cap(caps, CAPS_FORMAT_QUAD_R32) == true) { + format = audio::enum_available("AL_FORMAT_QUAD32"); // Invalid! + } + } else if(channel_format == AUDIO_FORMAT_R64) { + format = audio::enum_available("AL_FORMAT_QUAD32"); // Invalid! + } + } break; + + case 5: { + // ... + } break; + + case 6: { + // ... + } break; + + case 7: { + // ... + } break; + } + } + + switch(channel_format) { + + default: + case AUDIO_FORMAT_UNKNOWN: { + return 0; + } break; + +// IMPORTANT(jeff): Fixes testing of resource file [1]. +// 1. Resources/tests/audio/ALAudioTest/sinewave-4CHN-u8_1s-900.wav +#if 0 + case AUDIO_FORMAT_U8: + case AUDIO_FORMAT_S8: { + // NOM_ASSERT_INVALID_PATH("Not implemented within SoundFileReader"); + + switch(channel_count) { + case 1: { + format = audio::enum_available("AL_FORMAT_MONO8"); + } break; + + case 4: { + format = audio::enum_available("AL_FORMAT_QUAD8"); + } break; + + case 5: { + format = audio::enum_available("AL_FORMAT_51CHN8"); + } break; + + case 6: { + format = audio::enum_available("AL_FORMAT_61CHN8"); + } break; + + case 7: { + format = audio::enum_available("AL_FORMAT_71CHN8"); + } break; + + default: + case 2: { + format = audio::enum_available("AL_FORMAT_STEREO8"); + } break; + } + } break; +#else + + case AUDIO_FORMAT_S8: + case AUDIO_FORMAT_U8: +#endif + case AUDIO_FORMAT_S16: { + switch(channel_count) { + + case 1: + default: { + format = audio::enum_available("AL_FORMAT_MONO16"); + } break; + + // stereo (2) channel + case 2: { + format = audio::enum_available("AL_FORMAT_STEREO16"); + } break; + + // quad (4) channel + case 4: { + // format = alGetEnumValue("AL_FORMAT_QUAD16"); + format = audio::enum_available("AL_FORMAT_QUAD16"); + } break; + + // 5.1 Dolby Surround Sound + case 5: { + format = audio::enum_available("AL_FORMAT_51CHN16"); + } break; + + // 6.1 Dolby Surround Sound + case 6: { + format = audio::enum_available("AL_FORMAT_61CHN16"); + } break; + + // 7.1 Dolby Surround Sound + case 7: { + format = audio::enum_available("AL_FORMAT_71CHN16"); + } break; + } break; + } break; + + case AUDIO_FORMAT_S32: + case AUDIO_FORMAT_R32: + case AUDIO_FORMAT_R64: { + switch(channel_count) { + case 1: { + format = audio::enum_available("AL_FORMAT_MONO_FLOAT32"); + } break; + + // TODO(jeff): These need to be explicitly tested! + case 4: { + format = audio::enum_available("AL_FORMAT_QUAD32"); + } break; + + case 5: { + format = audio::enum_available("AL_FORMAT_51CHN32"); + } break; + + case 6: { + format = audio::enum_available("AL_FORMAT_61CHN32"); + } break; + + case 7: { + format = audio::enum_available("AL_FORMAT_71CHN32"); + } break; + + // case 4: + // case 5: + // case 6: + // case 7: { + // NOM_ASSERT_INVALID_PATH("Not implemented"); + // } break; + + default: + case 2: { + format = audio::enum_available("AL_FORMAT_STEREO_FLOAT32"); + } break; + } + } break; + } + + // NOM_ASSERT(channel_format != NOM_UINT32_MAX); + + // NOTE(jeff): Apple's OpenAL implementation returns negative one (-1), as + // opposed to zero (0), when an unknown enumeration has been given. + // + // This is a last ditch efforts to offer a valid channel format for OpenAL + // processing + if(format == AL_INVALID || format == AL_NONE) { + format = audio::enum_available("AL_FORMAT_STEREO16"); + } + + return format; +} + +bool ALAudioEngine::valid_buffer(SoundBuffer* target) +{ + bool result = false; + + if(target != nullptr) { + result = audio::valid_buffer(target->buffer_id); + } + + return result; +} + +bool ALAudioEngine::valid_source(SoundBuffer* target) +{ + bool result = false; + + if(target != nullptr) { + result = audio::valid_source(target->source_id); + } + + return result; +} + +uint32 ALAudioEngine::state(SoundBuffer* target) +{ + auto result = AUDIO_STATE_STOPPED; + ALint audio_state = AL_STOPPED; + ALint loop_state = AL_FALSE; + + if(target != nullptr && this->valid_source(target) == true) { + AL_CLEAR_ERR(); + alGetSourcei(target->source_id, AL_SOURCE_STATE, &audio_state); + AL_CHECK_ERR_VOID(); + + AL_CLEAR_ERR(); + alGetSourcei(target->source_id, AL_LOOPING, &loop_state); + AL_CHECK_ERR_VOID(); + } + + switch(audio_state) { + default: + case AL_INITIAL: { + result = AUDIO_STATE_INITIAL; + } break; + + case AL_STOPPED: { + result = AUDIO_STATE_STOPPED; + } break; + + case AL_PAUSED: { + result = AUDIO_STATE_PAUSED; + } break; + + case AL_PLAYING: { + result = AUDIO_STATE_PLAYING; + } break; + + case AL_STREAMING: { + result = AUDIO_STATE_STREAMING; + } break; + } + + if(loop_state == AL_TRUE) { + result = AUDIO_STATE_LOOPING; + } + + return result; +} + +real32 ALAudioEngine::pitch(SoundBuffer* target) +{ + auto pitch = 1.0f; + + if(target != nullptr) { + AL_CLEAR_ERR(); + alGetSourcef(target->source_id, AL_PITCH, &pitch); + AL_CHECK_ERR_VOID(); + } + + return pitch; +} + +real32 ALAudioEngine::volume() const +{ + auto gain_level = nom::audio::MIN_VOLUME; + + AL_CLEAR_ERR(); + alGetListenerf(AL_GAIN, &gain_level); + AL_CHECK_ERR_VOID(); + + // De-normalized gain level; 0..1 -> 0..100 + return(gain_level * 100.0f); +} + +Point3f ALAudioEngine::position() const +{ + // Defaults as per /System/Library/Frameworks/OpenAL/Headers/al.h + Point3f p(0.0f, 0.0f, 0.0f); + + AL_CLEAR_ERR(); + alGetListener3f(AL_POSITION, &p.x, &p.y, &p.z); + AL_CHECK_ERR_VOID(); + + return p; +} + +real32 ALAudioEngine::volume(SoundBuffer* target) const +{ + real32 gain_level = nom::audio::MIN_VOLUME; + + if(target != nullptr) { + auto sound_id = target->source_id; + + AL_CLEAR_ERR(); + alGetSourcef(sound_id, AL_GAIN, &gain_level); + AL_CHECK_ERR_VOID(); + } + + // De-normalize; 0..1 -> 0..100 + gain_level = gain_level * 100.0f; + + return gain_level; +} + +real32 ALAudioEngine::min_volume(SoundBuffer* target) +{ + real32 min_gain = nom::audio::MIN_VOLUME; + + if(target != nullptr) { + AL_CLEAR_ERR(); + alGetSourcef(target->source_id, AL_MIN_GAIN, &min_gain); + AL_CHECK_ERR_VOID(); + } + + // De-normalize; 0..1 -> 0..100 + min_gain = min_gain * 100.0f; + + return min_gain; +} + +real32 ALAudioEngine::max_volume(SoundBuffer* target) +{ + real32 max_gain = nom::audio::MIN_VOLUME; + + if(target != nullptr) { + AL_CLEAR_ERR(); + alGetSourcef(target->source_id, AL_MAX_GAIN, &max_gain); + AL_CHECK_ERR_VOID(); + } + + // De-normalize; 0..1 -> 0..100 + max_gain = max_gain * 100.0f; + + return max_gain; +} + +Point3f ALAudioEngine::velocity(SoundBuffer* target) +{ + Point3f v(Point3f::zero); + + if(target != nullptr) { + AL_CLEAR_ERR(); + alGetSource3f(target->source_id, AL_VELOCITY, &v.x, &v.y, &v.z); + AL_CHECK_ERR_VOID(); + } + + return v; +} + +Point3f ALAudioEngine::position(SoundBuffer* target) +{ + Point3f p(Point3f::zero); + + if(target != nullptr) { + AL_CLEAR_ERR(); + alGetSource3f(target->source_id, AL_POSITION, &p.x, &p.y, &p.z); + AL_CHECK_ERR_VOID(); + } + + return p; +} + +real32 ALAudioEngine::playback_position(SoundBuffer* target) +{ + real32 pos = 0.0f; + + if(target != nullptr) { + AL_CLEAR_ERR(); + alGetSourcef(target->source_id, AL_SEC_OFFSET, &pos); + AL_CHECK_ERR_VOID(); + } + + return pos; +} + +real32 ALAudioEngine::playback_samples(SoundBuffer* target) +{ + real32 samples = 0.0f; + + if(target != nullptr) { + AL_CLEAR_ERR(); + alGetSourcef(target->source_id, AL_SAMPLE_OFFSET, &samples); + AL_CHECK_ERR_VOID(); + } + + return samples; +} +#if 0 +void ALAudioEngine::set_state(SoundBuffer* target, uint32 state) +{ + ALint al_state = AL_STOPPED; + + if(target != nullptr) { + switch(state) { + default: + case AUDIO_STATE_INITIAL: { + al_state = AL_INITIAL; + } break; + + case AUDIO_STATE_STOPPED: { + al_state = AL_STOPPED; + } break; + + case AUDIO_STATE_PAUSED: { + al_state = AL_PAUSED; + } break; + + case AUDIO_STATE_PLAYING: { + al_state = AL_PLAYING; + } break; + + case AUDIO_STATE_STREAMING: { + al_state = AL_STREAMING; + } break; + } + + // TODO(jeff): Handle OpenAL source loop state + if(state == AUDIO_STATE_LOOPING) { + al_state = AL_LOOPING; + } + + if(state == AUDIO_STATE_LOOPING) { + AL_CLEAR_ERR(); + alSourcei(target->source_id, AL_LOOPING, true); + AL_CHECK_ERR_VOID(); + } else { + AL_CLEAR_ERR(); + alSourcei(target->source_id, AL_SOURCE_STATE, state); + AL_CHECK_ERR_VOID(); + } + } +} +#endif +void ALAudioEngine::set_volume(real32 gain) +{ + if(gain >= nom::audio::MIN_VOLUME && gain <= nom::audio::MAX_VOLUME) { + // normalize the current gain level + auto normalized_gain = gain * 0.01f; + + AL_CLEAR_ERR(); + alListenerf(AL_GAIN, normalized_gain); + AL_CHECK_ERR_VOID(); + } +} + +void ALAudioEngine::set_position(const Point3f& p) +{ + AL_CLEAR_ERR(); + alListener3f(AL_POSITION, p.x, p.y, p.z); + AL_CHECK_ERR_VOID(); +} + +void ALAudioEngine::set_volume(SoundBuffer* target, real32 gain) +{ + if(gain >= nom::audio::MIN_VOLUME && gain <= nom::audio::MAX_VOLUME) { + // normalize the current gain level to a number between 0..1 + gain = gain * 0.01f; + + if(target != nullptr) { + AL_CLEAR_ERR(); + alSourcef(target->source_id, AL_GAIN, gain); + AL_CHECK_ERR_VOID(); + } + } +} + +void ALAudioEngine::set_min_volume(SoundBuffer* target, real32 gain) +{ + if(gain >= nom::audio::MIN_VOLUME && gain <= nom::audio::MAX_VOLUME) { + // normalize the current gain level to a number between 0..1 + gain = gain * 0.01f; + } + + if(target != nullptr) { + AL_CLEAR_ERR(); + alSourcef(target->source_id, AL_MIN_GAIN, gain); + AL_CHECK_ERR_VOID(); + } +} + +void ALAudioEngine::set_max_volume(SoundBuffer* target, real32 gain) +{ + if(gain >= nom::audio::MIN_VOLUME && gain <= nom::audio::MAX_VOLUME) { + // normalize the current gain level to a number between 0..1 + gain = gain * 0.01f; + } + + if(target != nullptr && this->valid() == true) { + AL_CLEAR_ERR(); + alSourcef(target->source_id, AL_MAX_GAIN, gain); + AL_CHECK_ERR_VOID(); + } +} + +void ALAudioEngine::set_velocity(SoundBuffer* target, const Point3f& v) +{ + if(target != nullptr && this->valid() == true) { + AL_CLEAR_ERR(); + alSource3f(target->source_id, AL_VELOCITY, v.x, v.y, v.z); + AL_CHECK_ERR_VOID(); + } +} + +void ALAudioEngine::set_position(SoundBuffer* target, const Point3f& p) +{ + if(target != nullptr && this->valid() == true) { + AL_CLEAR_ERR(); + alSource3f(target->source_id, AL_POSITION, p.x, p.y, p.z); + AL_CHECK_ERR_VOID(); + } +} + +void ALAudioEngine::set_pitch(SoundBuffer* target, real32 pitch) +{ + if(target != nullptr && this->valid() == true) { + AL_CLEAR_ERR(); + alSourcef(target->source_id, AL_PITCH, pitch); + AL_CHECK_ERR_VOID(); + } +} + +void ALAudioEngine::set_playback_position(SoundBuffer* target, + real32 offset_seconds) +{ + if(target != nullptr && this->valid() == true) { + AL_CLEAR_ERR(); + alSourcef(target->source_id, AL_SEC_OFFSET, offset_seconds); + AL_CHECK_ERR_VOID(); + } +} + +void ALAudioEngine::play(SoundBuffer* target) +{ + if(target != nullptr && this->valid() == true) { + AL_CLEAR_ERR(); + alSourcePlay(target->source_id); + AL_CHECK_ERR_VOID(); + } +} + +void ALAudioEngine::stop(SoundBuffer* target) +{ + if(target != nullptr && this->valid() == true) { + if(this->state(target) != AUDIO_STATE_STOPPED) { + AL_CLEAR_ERR(); + alSourceStop(target->source_id); + AL_CHECK_ERR_VOID(); + } + } +} + +void ALAudioEngine::pause(SoundBuffer* target) +{ + if(target != nullptr && this->valid() == true) { + AL_CLEAR_ERR(); + alSourcePause(target->source_id); + AL_CHECK_ERR_VOID(); + } +} + +void ALAudioEngine::resume(SoundBuffer* target) +{ + this->play(target); +} + +bool ALAudioEngine::push_buffer(SoundBuffer* target) +{ + if(this->fill_buffer(target) == false) { + std::string err_str = "Failed to fill audio buffer: " + nom::error(); + nom::set_error(err_str); + return false; + } + + audio::attach_buffer(target->source_id, target->buffer_id); + + // TODO(jeff): Improve err handling here! + return true; +} + +// TODO(jeff): Create test for streaming sources in ALAudioTest +bool ALAudioEngine::queue_buffer(SoundBuffer* target) +{ + if(this->fill_buffer(target) == false) { + std::string err_str = "Failed to fill audio buffer: " + nom::error(); + nom::set_error(err_str); + return false; + } + + // NOTE(jeff): Push the buffer to the sound source queue + AL_CLEAR_ERR(); + alSourceQueueBuffers(target->source_id, 1, &target->buffer_id); + ALenum error = alGetError(); + if(error != AL_NO_ERROR) { + auto al_err_str = nom::integer_to_string(error); + std::string err_str = "OpenAL error code: " + al_err_str; + nom::set_error(err_str); + } + + ALenum streaming_source = AL_UNDETERMINED; + alGetSourcei(target->source_id, AL_SOURCE_TYPE, &streaming_source); + if(streaming_source == AL_STREAMING) { + target->stream_source = true; + } + +#if 1 + auto processed_buffers = audio::processed_buffers(target); + auto queued_buffers = audio::queued_buffers(target); + if(target != nullptr) { + NOM_LOG_DEBUG(NOM_LOG_CATEGORY_TEST, + "streaming_source:", target->stream_source, + "processed_buffers:", processed_buffers, + "queued_buffers:", queued_buffers); + } +#endif + + // TODO(jeff): Improve err handling here! + return true; +} + +void ALAudioEngine::suspend() +{ + // auto ctx = NOM_SCAST(ALCcontext*, this->context()); + auto impl = NOM_SCAST(ALAudioDevice*, this->impl_); + auto ctx = NOM_SCAST(ALCcontext*, impl->ctx); + NOM_ASSERT(ctx != nullptr); + + AL_CLEAR_ERR(); + alcSuspendContext(ctx); + AL_CHECK_ERR_VOID(); + + AL_CLEAR_ERR(); + alcMakeContextCurrent(ctx); + AL_CHECK_ERR_VOID(); +} + +void ALAudioEngine::resume() +{ + // auto ctx = NOM_SCAST(ALCcontext*, this->context()); + auto impl = NOM_SCAST(ALAudioDevice*, this->impl_); + auto ctx = NOM_SCAST(ALCcontext*, impl->ctx); + NOM_ASSERT(ctx != nullptr); + + AL_CLEAR_ERR(); + alcMakeContextCurrent(ctx); + AL_CHECK_ERR_VOID(); + + AL_CLEAR_ERR(); + alcProcessContext(ctx); + AL_CHECK_ERR_VOID(); +} + +void ALAudioEngine::close() +{ + this->close_context(); + this->close_device(); + + delete NOM_SCAST(ALAudioDevice*, this->impl_); + this->impl_ = nullptr; +} + +void ALAudioEngine::free_buffer(SoundBuffer* target) +{ + uint32 num_sources = 1; + + if(target != nullptr) { + + if(this->valid_source(target) == true) { + + // NOTE(jeff): Streaming audio buffers are a special use case + if(target->stream_source == true) { + auto processed_buffers = audio::processed_buffers(target); + if(target != nullptr) { + NOM_LOG_DEBUG(NOM_LOG_CATEGORY_TEST, + "processed_buffers:", processed_buffers); + } + + audio::free_stream(target); + if(target != nullptr) { + auto queued_buffers = audio::queued_buffers(target); + NOM_LOG_DEBUG(NOM_LOG_CATEGORY_TEST, + "queued_buffers:", queued_buffers); + } + // NOM_ASSERT(queued_buffers == 0); + } + + // NOTE(jeff): Free the sound source from our scope + audio::free_source(target); + } // end if target buffer is a valid source + + // NOTE(jeff): Free the audio buffer from our scope + if(this->valid_buffer(target) == true) { + AL_CLEAR_ERR(); + alDeleteBuffers(num_sources, &target->buffer_id); + AL_CHECK_ERR_VOID(); + } + + if(target->samples) { + audio::free_samples(target->channel_format, target->samples); + target->samples = nullptr; + } + } +} + +// private members + +bool ALAudioEngine::fill_buffer(SoundBuffer* target) +{ + if(target == nullptr) { + std::string buffer_id_str = nom::integer_to_string(target->buffer_id); + std::string err_str = "Invalid buffer (NULL)"; + nom::set_error(err_str); + return false; + } + + if(target->samples == nullptr) { + return true; + } + + // if(target->buffer_id == 0) { + // NOTE(jeff): Generate an audio buffer id for identification and + // communication between OpenAL and the calling library -- us. + target->buffer_id = audio::next_buffer_id(); + if(this->valid_buffer(target) == false) { + std::string buffer_id_str = nom::integer_to_string(target->buffer_id); + std::string err_str = "Invalid buffer ID " + buffer_id_str; + nom::set_error(err_str); + return false; + } + // } else { + // TODO(jeff): Restructure this branch! + // return true; + // } + + auto buffer_id = target->buffer_id; + auto samples = target->samples; + auto channel_count = target->channel_count; + auto channel_format = target->channel_format; + auto frequency = target->sample_rate; + auto bytes = target->total_bytes; + NOM_LOG_INFO(NOM_LOG_CATEGORY_TEST, "buffer_id:", buffer_id); + NOM_LOG_INFO(NOM_LOG_CATEGORY_TEST, "channel_count:", channel_count); + NOM_LOG_INFO(NOM_LOG_CATEGORY_TEST, "channel_format:", channel_format); + NOM_LOG_INFO(NOM_LOG_CATEGORY_TEST, "frequency:", frequency); + NOM_LOG_INFO(NOM_LOG_CATEGORY_TEST, "bytes:", bytes); + + // static_buffer = alGetProcAddress((const ALCchar*) token); + + // NOTE(jeff): Fill the audio buffer with file's sample data + // FIXME(jeff): Implement support for buffers attached to more than one + // sound source. + // + // I think we need to add an API for handling the attachment of one or more + // sound sources per buffer by letting the end-user explicitly set it up. + auto format = this->channel_format(channel_count, channel_format); + AL_CLEAR_ERR(); + alBufferData(buffer_id, format, samples, bytes, frequency); + AL_CHECK_ERR_VOID(); + NOM_LOG_INFO(NOM_LOG_CATEGORY_TEST, "format:", format); + + // TODO(jeff): Check for success before freeing the buffer! + // audio::free_samples(format, samples); + // target->samples = nullptr; + + // NOTE(jeff): Generate a sound source identifier for storing it in memory, + // i.e.: repeated playback of the audio buffer samples. + target->source_id = audio::next_source_id(); + if(this->valid_source(target) == false) { + std::string source_id_str = nom::integer_to_string(target->source_id); + std::string err_str = "Invalid source ID " + source_id_str; + nom::set_error(err_str); + return false; + } + + // IMPORTANT(jeff): Ensure that the reserved memory for this attached audio + // source buffer is cleared before potentially being re-used! + audio::clear_buffer(target->source_id); + + return true; +} + +void ALAudioEngine::close_context() +{ + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE_AUDIO, NOM_LOG_PRIORITY_DEBUG); + + if(this->impl_ != nullptr) { + auto ctx = this->impl_->ctx; + audio::free_audio_context(ctx); + ctx = nullptr; + + // TODO(jeff): Swap back + // NOM_LOG_DEBUG(NOM_LOG_PRIORITY_DEBUG, "Audio context released"); + NOM_LOG_INFO(NOM, "Audio context released"); + } +} + +void ALAudioEngine::close_device() +{ + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE_AUDIO, NOM_LOG_PRIORITY_DEBUG); + + if(this->impl_ != nullptr) { + auto dev = this->impl_->dev; + audio::free_audio_device(dev); + dev = nullptr; + + // TODO(jeff): Swap back + // NOM_LOG_DEBUG(NOM_LOG_PRIORITY_DEBUG, "Audio device released"); + NOM_LOG_INFO(NOM, "Audio device released"); + } +} + +} // namespace audio +} // namespace nom diff --git a/src/audio/AL/AudioDevice.cpp b/src/audio/AL/AudioDevice.cpp deleted file mode 100644 index ed7175b8..00000000 --- a/src/audio/AL/AudioDevice.cpp +++ /dev/null @@ -1,160 +0,0 @@ -/****************************************************************************** - - nomlib - C++11 cross-platform game engine - -Copyright (c) 2013, 2014 Jeffrey Carpenter -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -******************************************************************************/ -#include "nomlib/audio/AL/AudioDevice.hpp" - -// Forward declarations -#include "nomlib/audio/AL/OpenAL.hpp" - -namespace nom { - namespace priv { - -void AL_FreeAudioDevice ( ALCdevice* dev ) -{ - NOM_LOG_TRACE( NOM_LOG_CATEGORY_TRACE_AUDIO ); - - if ( dev != nullptr ) - { - alcCloseDevice ( dev ); - } -} - -void AL_FreeAudioContext ( ALCcontext* ctx ) -{ - NOM_LOG_TRACE( NOM_LOG_CATEGORY_TRACE_AUDIO ); - - if ( ctx != nullptr ) - { - // Disable context - alcMakeContextCurrent ( nullptr ); - - // Release context - alcDestroyContext ( ctx ); - } -} - - - } // namespace priv -} // namespace nom - -namespace nom { - -// Static initializations -bool AudioDevice::audio_initialized = false; - -AudioDevice::AudioDevice ( void ) -{ - if ( ! this->initialized() ) - initialize ( nullptr ); -} - -AudioDevice::AudioDevice ( const std::string& device_name ) -{ - if ( ! this->initialized() ) - initialize ( device_name.c_str() ); -} - -AudioDevice::~AudioDevice( void ) -{ - NOM_LOG_TRACE( NOM_LOG_CATEGORY_TRACE_AUDIO ); -} - -bool AudioDevice::initialize ( const ALCchar* device_name ) -{ - NOM_LOG_TRACE( NOM_LOG_CATEGORY_TRACE_AUDIO ); - - audio_device.reset(); - audio_context.reset(); - - // audio device handle - this->audio_device = std::shared_ptr ( alcOpenDevice ( device_name ), nom::priv::AL_FreeAudioDevice ); - - // attach a context (think: listener) to device - if ( this->audio_device ) - { - // Store the audio device name now that it is confirmed valid - this->device_name = alcGetString ( this->audio_device.get(), ALC_DEFAULT_DEVICE_SPECIFIER ); - - this->audio_context = std::shared_ptr ( alcCreateContext ( this->audio_device.get(), nullptr ), nom::priv::AL_FreeAudioContext ); - - if ( this->audio_context ) - { - alcMakeContextCurrent ( this->audio_context.get() ); - } - else - { -NOM_LOG_ERR ( NOM, "Failed to create the audio context." ); - return false; - } - } - else - { -NOM_LOG_ERR ( NOM, "Failed to open the audio device." ); - return false; - } - - this->audio_initialized = true; - - return true; -} - -bool AudioDevice::initialized ( void ) const -{ - // if ( this->audio_initialized ) - // return true; - // else - return false; -} - -// std::shared_ptr AudioDevice::getAudioDevice ( void ) const -// { -// return this->audio_device; -// } - -const std::string AudioDevice::getDeviceName ( void ) const -{ - return this->device_name; -} - -bool AudioDevice::isExtensionSupported ( const std::string& extension ) const -{ - if ( ( extension.length() > 2 ) && ( extension.substr ( 0, 3 ) == "ALC" ) ) - { - if ( alcIsExtensionPresent ( this->audio_device.get(), extension.c_str() ) != AL_FALSE ) - return true; - } - else - { - if ( alIsExtensionPresent ( extension.c_str() ) != AL_FALSE ) - return true; - } - - return false; -} - -} // namespace nom diff --git a/src/audio/AL/Listener.cpp b/src/audio/AL/Listener.cpp deleted file mode 100644 index 904d84e1..00000000 --- a/src/audio/AL/Listener.cpp +++ /dev/null @@ -1,134 +0,0 @@ -/****************************************************************************** - - nomlib - C++11 cross-platform game engine - -Copyright (c) 2013, 2014 Jeffrey Carpenter -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -******************************************************************************/ -#include "nomlib/audio/AL/Listener.hpp" - -// Private headers -#include "nomlib/audio/AL/OpenAL.hpp" - -namespace nom { - -Listener::Listener ( void ) -{ - NOM_LOG_TRACE( NOM_LOG_CATEGORY_TRACE_AUDIO ); - - // Defaults as per OpenAL/al.h - ALfloat position[] = { 0.0, 0.0, 0.0 }; - ALfloat velocity[] = { 0.0, 0.0, 0.0 }; - // Listener is facing into the screen - ALfloat direction[] = { 0.0, 0.0, -1.0, 0.0, 1.0, 0.0 }; - ALfloat gain = 1.0; - - // Initialize with sane defaults to be on the safe side; note that you must - // have the audio card initialized before-hand or these will be invalid - // presets -AL_CHECK_ERR ( alListenerf ( AL_GAIN, gain ) ); -AL_CHECK_ERR ( alListenerfv ( AL_POSITION, position ) ); -AL_CHECK_ERR ( alListenerfv ( AL_VELOCITY, velocity ) ); -AL_CHECK_ERR ( alListenerfv ( AL_ORIENTATION, direction ) ); -} - -Listener::~Listener( void ) -{ - NOM_LOG_TRACE( NOM_LOG_CATEGORY_TRACE_AUDIO ); - - // Clean up instance variables -} - -float Listener::getVolume ( void ) const -{ - ALfloat master_volume; - -AL_CHECK_ERR ( alGetListenerf ( AL_GAIN, &master_volume ) ); - - return master_volume * 100.f; -} - -const Point3f Listener::getPosition ( void ) const -{ - Point3f position; - -AL_CHECK_ERR ( alGetListener3f ( AL_POSITION, &position.x, &position.y, &position.z ) ); - - return position; -} - -const Point3f Listener::getVelocity ( void ) const -{ - Point3f velocity; - -AL_CHECK_ERR ( alGetListener3f ( AL_VELOCITY, &velocity.x, &velocity.y, &velocity.z ) ); - - return velocity; -} - -const Point3f Listener::getDirection ( void ) const -{ - Point3f direction; - -AL_CHECK_ERR ( alGetListener3f ( AL_ORIENTATION, &direction.x, &direction.y, &direction.z ) ); - - return direction; -} - -void Listener::setPosition ( float x, float y, float z ) -{ -AL_CHECK_ERR ( alListener3f ( AL_POSITION, x, y, z ) ); -} - -void Listener::setPosition ( const Point3f& position ) -{ - this->setPosition ( position.x, position.y, position.z ); -} - -void Listener::setVelocity ( float x, float y, float z ) -{ -AL_CHECK_ERR ( alListener3f ( AL_VELOCITY, x, y, z ) ); -} - -void Listener::setVelocity ( const Point3f& velocity ) -{ - this->setVelocity ( velocity.x, velocity.y, velocity.z ); -} - -void Listener::setDirection ( float x, float y, float z ) -{ -AL_CHECK_ERR ( alListener3f ( AL_ORIENTATION, x, y, z ) ); -} - -void Listener::setDirection ( const Point3f& direction ) -{ - this->setDirection ( direction.x, direction.y, direction.z ); -} - -void Listener::setVolume ( float gain ) -{ -AL_CHECK_ERR ( alListenerf ( AL_GAIN, gain * 0.01f ) ); -} - -} // namespace nom diff --git a/src/audio/AL/OpenAL.cpp b/src/audio/AL/OpenAL.cpp index 206888b8..b502a2f3 100644 --- a/src/audio/AL/OpenAL.cpp +++ b/src/audio/AL/OpenAL.cpp @@ -2,7 +2,7 @@ nomlib - C++11 cross-platform game engine -Copyright (c) 2013, 2014 Jeffrey Carpenter +Copyright (c) 2013, 2014, 2015, 2016 Jeffrey Carpenter All rights reserved. Redistribution and use in source and binary forms, with or without @@ -27,70 +27,109 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ #include "nomlib/audio/AL/OpenAL.hpp" +#include "nomlib/core/err.hpp" + +// Private headers +// #include // OpenAL Error Handling +// namespace nom { +// namespace audio { + +// uint32 err() +// { +// return 0; +// } + +// const char* err_str(uint32 errno) +// { +// const char* err_string = "ERR_NO_ERROR"; + +// switch(errno) { + +// default: +// case ERR_NO_ERROR: { +// // ... +// } break; + +// case ERR_SYSTEM_CALL_FAILURE: { +// err_string = "ERR_SYSTEM_CALL_FAILURE"; +// } break; + +// case ERR_OUT_OF_MEMORY: { +// err_string = "ERR_OUT_OF_MEMORY"; +// } break; + +// case ERR_INVALID_OPERATION: { +// err_string = "ERR_INVALID_OPERATION"; +// } break; +// } + +// return err_string; +// } + +// void set_err(uint32 errno) +// { +// return; +// } + +// void set_err(err_t* err) +// { +// return; +// } + +// } // namespace audio +// } // namespace nom + namespace nom { - namespace priv { +namespace priv { -void al_err ( const std::string& file, uint32 line ) +void +al_err(const std::string& func, const std::string& file, uint32 line) { ALenum error_code = alGetError(); - if ( error_code != AL_NO_ERROR ) - { - std::string error, description; - - switch ( error_code ) - { - default: - { - error = "Unknown"; - description = "An unknown error has occurred."; - } - break; - - case AL_INVALID_NAME: - { - error = "AL_INVALID_NAME"; - description = "Invalid name (identifier)."; - } - break; - - case AL_INVALID_ENUM: - { - error = "AL_INVALID_ENUM"; - description = "Invalid enumeration (attribute)."; - } - break; - - case AL_INVALID_VALUE: - { - error = "AL_INVALID_VALUE"; - description = "Invalid value."; - } - break; - - case AL_INVALID_OPERATION: - { - error = "AL_INVALID_OPERATION"; - description = "Requested operation is not valid."; - } - break; - - case AL_OUT_OF_MEMORY: - { - error = "AL_OUT_OF_MEMORY"; - description = "Requested operation resulted in OpenAL running out of memory"; - } - break; - } // end switch + if(error_code != AL_NO_ERROR) { + const char* err_cstr = nullptr; + + switch(error_code) { + default: { + err_cstr = "Unknown err"; + } break; + + case AL_NO_ERROR: { + err_cstr = "No error"; + } break; + + case AL_INVALID_NAME: { + err_cstr = "AL_INVALID_NAME; Invalid name (identifier)."; + } break; - std::cout << "NOM_LOG_ERR at " << nom::timestamp() << "In file " << file << ":" << line << std::endl << "Error: " << error << ", " << description << std::endl << std::endl; + case AL_INVALID_ENUM: { + err_cstr = "AL_INVALID_ENUM; Invalid enumeration (attribute)"; + } break; + case AL_INVALID_VALUE: { + err_cstr = "AL_INVALID_VALUE; Invalid value."; + } break; + + case AL_INVALID_OPERATION: { + err_cstr = + "AL_INVALID_OPERATION; Requested operation is not valid."; + } break; + + case AL_OUT_OF_MEMORY: { + err_cstr = + "AL_OUT_OF_MEMORY; Out of memory."; + } break; + } // end switch + + NOM_LOG_ERR(NOM_LOG_CATEGORY_AUDIO, err_cstr, "at", file, ":", line, + "in", func); + nom::set_error(err_cstr); } // end if AL_NO_ERROR } - - } // namespace priv +} // namespace priv } // namespace nom diff --git a/src/audio/AL/Sound.cpp b/src/audio/AL/Sound.cpp deleted file mode 100644 index 020b2a8c..00000000 --- a/src/audio/AL/Sound.cpp +++ /dev/null @@ -1,118 +0,0 @@ -/****************************************************************************** - - nomlib - C++11 cross-platform game engine - -Copyright (c) 2013, 2014 Jeffrey Carpenter -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -******************************************************************************/ -#include "nomlib/audio/AL/Sound.hpp" - -// Private headers -#include "nomlib/audio/AL/OpenAL.hpp" - -// Forward declarations -#include "nomlib/audio/ISoundBuffer.hpp" - -namespace nom { - -Sound::Sound ( void ) : buffer ( nullptr ) -{ - NOM_LOG_TRACE( NOM_LOG_CATEGORY_TRACE_AUDIO ); -} - -Sound::Sound ( const ISoundBuffer& copy ) : buffer ( nullptr ) -{ - this->setBuffer ( copy ); -} - -Sound::~Sound ( void ) -{ - NOM_LOG_TRACE( NOM_LOG_CATEGORY_TRACE_AUDIO ); - - this->Stop(); - - if ( this->buffer ) - this->buffer->detach ( this ); -} - -void Sound::setBuffer ( const ISoundBuffer& copy ) -{ - NOM_LOG_TRACE( NOM_LOG_CATEGORY_TRACE_AUDIO ); - - // First, detach previous buffer - if ( this->buffer ) - { - this->Stop(); - this->buffer->detach ( this ); - } - - // Assign new buffer & use it - this->buffer = © - this->buffer->attach ( this ); - -AL_CHECK_ERR ( alSourcei ( source_id, AL_BUFFER, this->buffer->get() ) ); -} - -void Sound::Play ( void ) -{ -AL_CHECK_ERR ( alSourcePlay ( source_id ) ); -} - -void Sound::Stop ( void ) -{ -AL_CHECK_ERR ( alSourceStop ( source_id ) ); -} - -void Sound::Pause ( void ) -{ -AL_CHECK_ERR ( alSourcePause ( source_id ) ); -} - -// TODO -/* -float Sound::getPlayPosition ( void ) const -{ - ALfloat playback_position; - - alGetSourcef ( source_id, AL_SEC_OFFSET, &playback_position ); - - return playback_position; -} - -void Sound::setPlayPosition ( float seconds ) -{ - alSourcef ( source_id, AL_SEC_OFFSET, seconds ); -} -*/ - -void Sound::reset( void ) -{ - this->Stop(); - - AL_CHECK_ERR( alSourcei( source_id, AL_BUFFER, 0 ) ); - - buffer = nullptr; -} - -} // namespace nom diff --git a/src/audio/AL/SoundBuffer.cpp b/src/audio/AL/SoundBuffer.cpp index 66958b1e..e9bfcbce 100644 --- a/src/audio/AL/SoundBuffer.cpp +++ b/src/audio/AL/SoundBuffer.cpp @@ -26,84 +26,20 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ -#include "nomlib/audio/AL/SoundBuffer.hpp" +// #include "nomlib/audio/AL/SoundBuffer.hpp" +#include "SoundBuffer_priv.hpp" + +// Forward declrations +#include "nomlib/audio/AL/AudioDevice.hpp" +// #include "nomlib/audio/AL/SoundSource.hpp" +#include "ALAudioDeviceCaps.hpp" // Private headers #include "nomlib/audio/AL/OpenAL.hpp" -#include "nomlib/audio/AL/Sound.hpp" -#include "nomlib/audio/AL/SoundFile.hpp" - -namespace nom { - -SoundBuffer::SoundBuffer ( void ) : buffer ( 0 ) -{ - NOM_LOG_TRACE( NOM_LOG_CATEGORY_TRACE_AUDIO ); - -AL_CHECK_ERR ( alGenBuffers ( 1, &this->buffer ) ); -} - -SoundBuffer::~SoundBuffer( void ) -{ - NOM_LOG_TRACE( NOM_LOG_CATEGORY_TRACE_AUDIO ); - - // First, release attached sound resources from this buffer - for ( auto it = this->sounds.begin(); it != this->sounds.end(); ++it ) - { - (*it)->reset(); - } - - // Goodbye buffer! - if ( this->buffer ) - { -AL_CHECK_ERR ( alDeleteBuffers ( 1, &this->buffer ) ); - } -} - -uint32 SoundBuffer::get ( void ) const -{ - return this->buffer; -} -int64 SoundBuffer::getDuration( void ) const -{ - return this->buffer_duration; -} - -bool SoundBuffer::load ( const std::string& filename ) -{ - SoundFile fp; - - if ( ! fp.open ( filename ) ) - { -NOM_LOG_ERR ( NOM, "Could not load audio: " + filename ); - return false; - } - - if ( ! fp.read ( this->samples ) ) - { -NOM_LOG_ERR ( NOM, "Could not read audio samples: " + filename ); - return false; - } - - this->buffer_duration = ( 1000 * fp.getSampleCount() / fp.getSampleRate() / fp.getChannelCount() ); - -// Fill the audio buffer with loaded sample data -AL_CHECK_ERR ( alBufferData ( this->buffer, fp.getChannelFormat(), - &this->samples.front(), fp.getDataByteSize(), - fp.getSampleRate() ) - ); - - return true; -} - -void SoundBuffer::attach ( Sound* sound ) const -{ - sounds.insert ( sound ); -} - -void SoundBuffer::detach ( Sound* sound ) const -{ - sounds.erase ( sound ); -} +#include "nomlib/audio/libsndfile/SoundFileReader.hpp" +namespace nom { +namespace audio { +} // namespace audio } // namespace nom diff --git a/src/audio/AL/SoundFile.cpp b/src/audio/AL/SoundFile.cpp deleted file mode 100644 index 553fca28..00000000 --- a/src/audio/AL/SoundFile.cpp +++ /dev/null @@ -1,129 +0,0 @@ -/****************************************************************************** - - nomlib - C++11 cross-platform game engine - -Copyright (c) 2013, 2014 Jeffrey Carpenter -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -******************************************************************************/ -#include "nomlib/audio/AL/SoundFile.hpp" - -// Private headers -#include "nomlib/audio/AL/OpenAL.hpp" - -// Forward declarations (third-party) -#include - -namespace nom { - -SoundFile::SoundFile ( void ) -{ - NOM_LOG_TRACE( NOM_LOG_CATEGORY_TRACE_AUDIO ); - - this->fp.reset(); - - // Initialize here -} - -SoundFile::~SoundFile ( void ) -{ - NOM_LOG_TRACE( NOM_LOG_CATEGORY_TRACE_AUDIO ); - // Clean up instance variables -} - -int64 SoundFile::getSampleCount ( void ) const -{ - return this->sample_count; -} - -uint32 SoundFile::getChannelCount ( void ) const -{ - return this->channel_count; -} - -uint32 SoundFile::getSampleRate ( void ) const -{ - return this->sample_rate; -} - -uint32 SoundFile::getChannelFormat ( void ) const -{ - return this->channel_format; -} - -int64 SoundFile::getDataByteSize ( void ) const -{ - return this->getSampleCount() * sizeof ( int16 ); -} - -bool SoundFile::open ( const std::string& filename ) -{ - SF_INFO info; - - this->fp = std::shared_ptr ( sf_open ( filename.c_str(), SFM_READ, &info ), sf_close ); - - if ( this->fp.get() == nullptr ) - { -NOM_LOG_ERR ( NOM, "Could not not audio file: " + filename ); - return false; - } - - this->channel_count = info.channels; - // sample_count should be the same size as samples - this->sample_count = info.frames * info.channels; - this->sample_rate = info.samplerate; - - switch ( info.channels ) - { - default: this->channel_format = 0; break; - case 1: this->channel_format = alGetEnumValue ( "AL_FORMAT_MONO16" ); break; - case 2: this->channel_format = alGetEnumValue ( "AL_FORMAT_STEREO16" ); break; - case 4: this->channel_format = alGetEnumValue ( "AL_FORMAT_QUAD16" ); break; - case 6: this->channel_format = alGetEnumValue ( "AL_FORMAT_51CHN16" ); break; - case 7: this->channel_format = alGetEnumValue ( "AL_FORMAT_61CHN16" ); break; - case 8: this->channel_format = alGetEnumValue ( "AL_FORMAT_71CHN16" ); break; - } - - return true; -} - -bool SoundFile::read ( std::vector& data ) -{ - sf_count_t read_size = 0; - std::vector read_buffer; - read_buffer.resize ( BUFFER_SIZE ); - - while ( ( read_size = sf_read_short ( this->fp.get(), read_buffer.data(), read_buffer.size() ) ) != 0 ) - { - data.insert ( data.end(), read_buffer.begin(), read_buffer.begin() + read_size); - } - - return true; -} - -std::string libsndfile_version() -{ - return sf_version_string(); -} - -} // namespace nom diff --git a/src/audio/AL/SoundSource.cpp b/src/audio/AL/SoundSource.cpp index eae289db..33c717b5 100644 --- a/src/audio/AL/SoundSource.cpp +++ b/src/audio/AL/SoundSource.cpp @@ -2,7 +2,7 @@ nomlib - C++11 cross-platform game engine -Copyright (c) 2013, 2014 Jeffrey Carpenter +Copyright (c) 2013, 2014, 2015, 2016 Jeffrey Carpenter All rights reserved. Redistribution and use in source and binary forms, with or without @@ -29,262 +29,580 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "nomlib/audio/AL/SoundSource.hpp" // Private headers -#include "nomlib/audio/AL/OpenAL.hpp" -#include "nomlib/core/clock.hpp" +#include "nomlib/audio/audio_defs.hpp" +// #include "nomlib/audio/SoundFile.hpp" +#include "nomlib/audio/libsndfile/SoundFileReader.hpp" + +// Forward declarations +#include "nomlib/math/Point3.hpp" +#include "nomlib/audio/SoundBuffer.hpp" +#include "nomlib/audio/IOAudioEngine.hpp" namespace nom { +namespace audio { -SoundSource::SoundSource ( void ) +uint32 channel_format(uint32 num_channels, uint32 channel_format, + IOAudioEngine* target) { - NOM_LOG_TRACE( NOM_LOG_CATEGORY_TRACE_AUDIO ); + if(num_channels > 0 && target != nullptr && target->valid() == true) { + target->channel_format(num_channels, channel_format); + } -AL_CHECK_ERR ( alGenSources ( 1, &source_id ) ); -AL_CHECK_ERR ( alSourcei ( source_id, AL_BUFFER, 0 ) ); + return channel_format; } -SoundSource::~SoundSource ( void ) +bool write_info(SoundBuffer* buffer, const SoundInfo& metadata) { - NOM_LOG_TRACE( NOM_LOG_CATEGORY_TRACE_AUDIO ); + if(buffer == nullptr) { + return false; + } + + buffer->frame_count = metadata.frame_count; + buffer->sample_count = metadata.sample_count; + buffer->sample_rate = metadata.sample_rate; + buffer->channel_count = metadata.channel_count; + buffer->channel_format = metadata.channel_format; + buffer->duration = metadata.duration; + buffer->total_bytes = metadata.total_bytes; + buffer->seekable = metadata.seekable; -AL_CHECK_ERR ( alSourcei ( source_id, AL_BUFFER, 0 ) ); -AL_CHECK_ERR ( alDeleteSources ( 1, &source_id ) ); + return true; } -float SoundSource::getVolume ( void ) const +void* +create_samples(nom::size_type alloc_bytes, uint32 num_channels, + uint32 channel_format) { - ALfloat volume; - -AL_CHECK_ERR ( alGetSourcef ( source_id, AL_GAIN, &volume ) ); + void* samples_buffer = nullptr; + + switch(channel_format) { + default: + case AUDIO_FORMAT_UNKNOWN: { + // ...Err state... + samples_buffer = nullptr; + } break; + + case AUDIO_FORMAT_S8: + case AUDIO_FORMAT_U8: + case AUDIO_FORMAT_S16: { + // FIXME(jeff): I'm not sure we need this large of a buffer here; verify! + samples_buffer = new int16[alloc_bytes * num_channels]; + } break; + + case AUDIO_FORMAT_S24: { + NOM_ASSERT_INVALID_PATH("Not implemented"); + } break; + + case AUDIO_FORMAT_S32: + case AUDIO_FORMAT_R32: + case AUDIO_FORMAT_R64: { + // FIXME(jeff): I'm not sure we need this large of a buffer here; verify! + samples_buffer = new real32[alloc_bytes * num_channels]; + } break; + } - return volume * 100.f; + return samples_buffer; } -float SoundSource::getMinVolume ( void ) const +void free_samples(uint32 channel_format, void* data) { - ALfloat min_volume; - -AL_CHECK_ERR ( alGetSourcef ( source_id, AL_MIN_GAIN, &min_volume ) ); + NOM_LOG_TRACE(NOM_LOG_CATEGORY_TRACE_AUDIO); + + // Goodbye buffer! + if(data != nullptr) { + switch(channel_format) { + + default: + case AUDIO_FORMAT_UNKNOWN: { + // Err state; do nothing + } break; + + case AUDIO_FORMAT_S8: + case AUDIO_FORMAT_U8: + case AUDIO_FORMAT_S16: { + delete NOM_SCAST(int16*, data); + } break; + + case AUDIO_FORMAT_S24: { + NOM_ASSERT_INVALID_PATH("Not implemented"); + } break; + + case AUDIO_FORMAT_S32: + case AUDIO_FORMAT_R32: + case AUDIO_FORMAT_R64: { + delete NOM_SCAST(real32*, data); + } break; + } - return min_volume; + data = nullptr; + } } -float SoundSource::getMaxVolume ( void ) const +SoundBuffer* create_buffer_memory() { - ALfloat max_volume; + auto buffer = new SoundBuffer(); + return buffer; +} -AL_CHECK_ERR ( alGetSourcef ( source_id, AL_MAX_GAIN, &max_volume ) ); +SoundBuffer* +create_buffer_memory(nom::size_type total_sample_bytes, uint32 num_channels, + uint32 channel_format) +{ + auto buffer = audio::create_buffer_memory(); + if(buffer != nullptr) { + // Generate PCM data that can be used by the audio engine + buffer->samples = + audio::create_samples(total_sample_bytes, num_channels, channel_format); + } - return max_volume; + return buffer; } -float SoundSource::getPitch ( void ) const +SoundBuffer* +create_buffer(void* samples, const SoundInfo& metadata, IOAudioEngine* target) { - ALfloat pitch; + SoundBuffer* buffer = audio::create_buffer_memory(); + const nom::size_type CHUNK_SIZE = metadata.total_bytes; + NOM_ASSERT(CHUNK_SIZE > 0); + + // NOM_ASSERT(buffer != nullptr); + if(buffer == nullptr) { + NOM_LOG_ERR(NOM_LOG_CATEGORY_APPLICATION, + "Failed to create audio buffer: out of memory!"); + return buffer; + } + + if(target == nullptr) { + NOM_LOG_ERR(NOM_LOG_CATEGORY_APPLICATION, + "Failed to create audio buffer: audio device is invalid."); + return buffer; + } + + // Fill our audio buffer with audio samples given to us by the end-user + buffer->samples = samples; -AL_CHECK_ERR ( alGetSourcef ( source_id, AL_PITCH, &pitch ) ); + if(audio::write_info(buffer, metadata) == false) { + NOM_LOG_ERR(NOM_LOG_CATEGORY_APPLICATION, + "Failed to create audio buffer: NULL buffer"); + return buffer; + } - return pitch; + // NOTE: Fill the audio buffer with the sampled data + if(target->push_buffer(buffer) == false) { + return nullptr; + } + + return buffer; } -bool SoundSource::getLooping ( void ) const +SoundBuffer* +create_buffer(const std::string& filename, ISoundFileReader* fp, + IOAudioEngine* target) { - ALint looping; + SoundInfo metadata; + SoundBuffer* buffer = audio::create_buffer_memory(); + + // NOM_ASSERT(buffer != nullptr); + if(buffer == nullptr) { + NOM_LOG_ERR(NOM_LOG_CATEGORY_APPLICATION, + "Failed to create audio buffer: out of memory!"); + return buffer; + } + + if(target == nullptr) { + NOM_LOG_ERR(NOM_LOG_CATEGORY_APPLICATION, + "Failed to create audio buffer: audio device is invalid."); + return buffer; + } + + NOM_ASSERT(fp != nullptr); + if(fp == nullptr) { + NOM_LOG_ERR(NOM_LOG_CATEGORY_APPLICATION, + "Failed to create audio buffer: out of memory!"); + return buffer; + } + + if(fp->open(filename, metadata) == false) { + NOM_LOG_ERR(NOM_LOG_CATEGORY_APPLICATION, + "Could not load audio from input file:", filename); + return buffer; + } + + if(audio::write_info(buffer, metadata) == false) { + NOM_LOG_ERR(NOM_LOG_CATEGORY_APPLICATION, + "Failed to write audio metadata."); + return buffer; + } -AL_CHECK_ERR ( alGetSourcei ( source_id, AL_LOOPING, &looping ) ); + const nom::size_type CHUNK_SIZE = metadata.total_bytes; + NOM_ASSERT(CHUNK_SIZE > 0); - switch ( looping ) - { - case AL_TRUE: return true; break; - case AL_FALSE: return false; break; + auto channel_format = buffer->channel_format; + auto num_channels = buffer->channel_count; + + // Generate PCM data that can be used by the audio engine + buffer->samples = + audio::create_samples(CHUNK_SIZE, num_channels, channel_format); + + NOM_ASSERT(buffer->samples != nullptr); + if(buffer->samples == nullptr) { + NOM_LOG_ERR(NOM_LOG_CATEGORY_APPLICATION, + "Failed to create audio buffer: out of memory for samples."); + return buffer; + } + + if(fp->read(buffer->samples, channel_format, CHUNK_SIZE) == false) { + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, + "Could not read audio samples from file:", filename ); + return buffer; + } + + + // NOTE: Fill the audio buffer with the sampled data + if(target->push_buffer(buffer) == false) { + return buffer; } - return false; + return buffer; } -Point3f SoundSource::getPosition ( void ) const +SoundBuffer* +create_buffer(const std::string& filename, IOAudioEngine* target) { - Point3f position; + SoundBuffer* result = nullptr; + ISoundFileReader* fp = new SoundFileReader(); -AL_CHECK_ERR ( alGetSource3f ( source_id, AL_POSITION, &position.x, &position.y, - &position.z ) - ); + result = audio::create_buffer(filename, fp, target); - return position; + return result; } -Point3f SoundSource::getVelocity ( void ) const +bool valid_buffer(SoundBuffer* buffer, IOAudioEngine* target) { - Point3f velocity; + bool result = false; -AL_CHECK_ERR ( alGetSource3f ( source_id, AL_VELOCITY, &velocity.x, &velocity.y, - &velocity.z ) - ); + if(buffer != nullptr && target->valid() == true) { + result = target->valid_buffer(buffer); + } - return velocity; + return result; } -bool SoundSource::getPositionRelativeToListener ( void ) const +bool valid_source(SoundBuffer* buffer, IOAudioEngine* target) { - ALint relative; + bool result = false; -AL_CHECK_ERR ( alGetSourcei ( source_id, AL_SOURCE_RELATIVE, &relative ) ); + if(buffer != nullptr && target->valid() == true) { + result = target->valid_source(buffer); + } - return (relative != 0); + return result; } -float SoundSource::getMinDistance ( void ) const +SoundInfo info(SoundBuffer* buffer) { - ALfloat distance; + SoundInfo metadata = {}; + + if(buffer == nullptr) { + return metadata; + } -AL_CHECK_ERR ( alGetSourcef ( source_id, AL_REFERENCE_DISTANCE, &distance ) ); + metadata.frame_count = buffer->frame_count; + metadata.sample_count = buffer->sample_count; + metadata.sample_rate = buffer->sample_rate; + metadata.channel_count = buffer->channel_count; + metadata.channel_format = buffer->channel_format; + metadata.duration = buffer->duration; + metadata.total_bytes = buffer->total_bytes; + metadata.seekable = buffer->seekable; - return distance; + return metadata; } -float SoundSource::getAttenuation ( void ) const +void free_buffer(SoundBuffer* buffer, IOAudioEngine* target) { - ALfloat attenuation; + NOM_LOG_TRACE(NOM_LOG_CATEGORY_TRACE_AUDIO); -AL_CHECK_ERR ( alGetSourcef ( source_id, AL_ROLLOFF_FACTOR, &attenuation ) ); + // Goodbye buffer! + if(buffer != nullptr && target != nullptr && target->valid() == true) { + target->free_buffer(buffer); + } - return attenuation; + NOM_DELETE_PTR(buffer); } -int32 SoundSource::getBufferID ( void ) const -{ - ALint buffer_id; +// audio control -AL_CHECK_ERR ( alGetSourcei ( source_id, AL_BUFFER, &buffer_id ) ); +void play(SoundBuffer* buffer, IOAudioEngine* target) +{ + if(buffer != nullptr && target != nullptr && target->valid() == true) { + target->play(buffer); + } +} - return buffer_id; +void +stop(SoundBuffer* buffer, IOAudioEngine* target) +{ + if( buffer != nullptr && target != nullptr && target->valid() == true) { + target->stop(buffer); + } } -float SoundSource::getPlayPosition ( void ) const +void +pause(SoundBuffer* buffer, IOAudioEngine* target) { - ALfloat playback_position; + if( buffer != nullptr && target != nullptr && target->valid() == true) { -AL_CHECK_ERR ( alGetSourcef ( source_id, AL_SEC_OFFSET, &playback_position ) ); + auto state = audio::state(buffer, target); + + if( state != AudioState::AUDIO_STATE_STOPPED ) { + target->pause(buffer); + } + } +} - return playback_position; +void +resume(SoundBuffer* buffer, IOAudioEngine* target) +{ + audio::play(buffer, target); } -SoundStatus SoundSource::getStatus ( void ) const +uint32 +state(SoundBuffer* buffer, IOAudioEngine* target) { - ALint state; + uint32 result = AudioState::AUDIO_STATE_STOPPED; + + if( buffer != nullptr && target != nullptr && target->valid() == true) { + result = target->state(buffer); + } -AL_CHECK_ERR ( alGetSourcei ( source_id, AL_SOURCE_STATE, &state ) ); + return NOM_SCAST(AudioState, result); +} + +real32 +pitch(SoundBuffer* buffer, IOAudioEngine* target) +{ + auto result = 0.0f; - switch ( state ) - { - case AL_INITIAL: - case AL_STOPPED: return Stopped; - case AL_PAUSED: return Paused; - case AL_PLAYING: return Playing; + if( buffer != nullptr && target != nullptr && target->valid() == true) { + result = target->pitch(buffer); } - return Stopped; + return result; } -void SoundSource::setVolume ( float gain ) +Point3f position(SoundBuffer* buffer, IOAudioEngine* target) { -AL_CHECK_ERR ( alSourcef ( source_id, AL_GAIN, gain * 0.01f ) ); + Point3f p(Point3f::zero); + + if( buffer != nullptr && target != nullptr && target->valid() == true) { + target->position(buffer); + } + + return p; } -void SoundSource::setMinVolume ( float gain ) +Point3f +velocity(SoundBuffer* buffer, IOAudioEngine* target) { -AL_CHECK_ERR ( alSourcef ( source_id, AL_MIN_GAIN, gain ) ); + Point3f v(Point3f::zero); + + if( buffer != nullptr && target != nullptr && target->valid() == true) { + v = target->velocity(buffer); + } + + return v; } -void SoundSource::setMaxVolume ( float gain ) +real32 volume(IOAudioEngine* target) { -AL_CHECK_ERR ( alSourcef ( source_id, AL_MAX_GAIN, gain ) ); + real32 gain_level = nom::audio::MIN_VOLUME; + + if(target != nullptr && target->valid() == true) { + gain_level = target->volume(); + } + + return gain_level; } -void SoundSource::setPitch ( float pitch ) +Point3f position(IOAudioEngine* target) { -AL_CHECK_ERR ( alSourcef ( source_id, AL_PITCH, pitch ) ); + Point3f pos(0.0f, 0.0f, 0.0f); + + if(target != nullptr && target->valid() == true) { + pos = target->position(); + } + + return pos; } -void SoundSource::setLooping ( bool loops ) +real32 volume(SoundBuffer* buffer, IOAudioEngine* target) { -AL_CHECK_ERR ( alSourcei ( source_id, AL_LOOPING, loops ) ); + real32 gain_level = nom::audio::MIN_VOLUME; + + if(buffer != nullptr && target != nullptr && target->valid() == true) { + gain_level = target->volume(buffer); + } + + return gain_level; } -void SoundSource::setPosition ( float x, float y, float z ) +real32 +min_volume(SoundBuffer* buffer, IOAudioEngine* target) { -AL_CHECK_ERR ( alSource3f ( source_id, AL_POSITION, x, y, z ) ); + real32 min_gain = nom::audio::MIN_VOLUME; + + if(buffer != nullptr && target != nullptr && target->valid() == true) { + min_gain = target->min_volume(buffer); + } + + return min_gain; } -void SoundSource::setPosition ( const Point3f& position ) +real32 +max_volume(SoundBuffer* buffer, IOAudioEngine* target) { - this->setPosition ( position.x, position.y, position.z ); + real32 max_gain = nom::audio::MIN_VOLUME; + + if(buffer != nullptr && target != nullptr && target->valid() == true) { + max_gain = target->max_volume(buffer); + } + + return max_gain; } -void SoundSource::setVelocity ( float x, float y, float z ) +real32 +playback_position(SoundBuffer* buffer, IOAudioEngine* target) { -AL_CHECK_ERR ( alSource3f ( source_id, AL_VELOCITY, x, y, z ) ); + real32 pos = 0.0f; + + if(buffer != nullptr && target != nullptr && target->valid() == true) { + pos = target->playback_position(buffer); + } + + return pos; } -void SoundSource::setVelocity ( const Point3f& velocity ) +real32 +playback_samples(SoundBuffer* buffer, IOAudioEngine* target) { - this->setVelocity ( velocity.x, velocity.y, velocity.z ); + real32 pos = 0.0f; + + if(buffer != nullptr && target != nullptr && target->valid() == true) { + pos = target->playback_samples(buffer); + } + + return pos; } -void SoundSource::setPositionRelativeToListener ( bool relative ) +SoundBuffer* clone_buffer(const SoundBuffer* rhs) +{ + SoundBuffer* buffer = new audio::SoundBuffer(); + if(buffer != nullptr) { + buffer = {}; + buffer->buffer_id = rhs->buffer_id; + buffer->source_id = rhs->source_id; + buffer->samples = rhs->samples; + buffer->frame_count = rhs->frame_count; + buffer->channel_count = rhs->channel_count; + buffer->channel_format = rhs->channel_format; + buffer->sample_rate = rhs->sample_rate; + buffer->sample_count = rhs->sample_count; + buffer->duration = rhs->duration; + buffer->total_bytes = rhs->total_bytes; + buffer->seekable = rhs->seekable; + } + + return buffer; +} +#if 0 +void +set_state(SoundBuffer* buffer, IOAudioEngine* target, uint32 state) { -AL_CHECK_ERR ( alSourcei ( source_id, AL_SOURCE_RELATIVE, relative ) ); + if(buffer != nullptr && target != nullptr && target->valid() == true) { + target->set_state(buffer, state); + } +} +#endif +void set_volume(real32 gain, IOAudioEngine* target) +{ + if(gain >= nom::audio::MIN_VOLUME && gain <= nom::audio::MAX_VOLUME) { + if(target != nullptr && target->valid() == true) { + target->set_volume(gain); + } + } } -void SoundSource::setMinDistance ( float distance ) +void set_volume(SoundBuffer* buffer, IOAudioEngine* target, real32 gain) { -AL_CHECK_ERR ( alSourcef ( source_id, AL_REFERENCE_DISTANCE, distance ) ); + if(gain >= nom::audio::MIN_VOLUME && gain <= nom::audio::MAX_VOLUME) { + + // normalize the current gain level to a number between 0..1 + if( buffer != nullptr && target != nullptr && target->valid() == true) { + target->set_volume(buffer, gain); + } + } } -void SoundSource::setAttenuation ( float attenuation ) +void set_min_volume(SoundBuffer* buffer, IOAudioEngine* target, real32 gain) { -AL_CHECK_ERR ( alSourcef ( source_id, AL_ROLLOFF_FACTOR, attenuation ) ); + if(buffer != nullptr && target != nullptr && target->valid() == true) { + target->set_min_volume(buffer, gain); + } } -void SoundSource::setPlayPosition ( float seconds ) +void set_max_volume(SoundBuffer* buffer, IOAudioEngine* target, real32 gain) { -AL_CHECK_ERR ( alSourcef ( source_id, AL_SEC_OFFSET, seconds ) ); + if(buffer != nullptr && target != nullptr && target->valid() == true) { + target->set_min_volume(buffer, gain); + } } -void SoundSource::togglePause( void ) +void set_pitch(SoundBuffer* buffer, IOAudioEngine* target, real32 pitch) { - if ( this->getStatus() == SoundStatus::Paused ) - { - this->Play(); + if( buffer != nullptr && target != nullptr && target->valid() == true) { + target->set_pitch(buffer, pitch); } - else if ( this->getStatus() == SoundStatus::Playing ) - { - this->Pause(); +} + +void +set_velocity(SoundBuffer* buffer, IOAudioEngine* target, const Point3f& v) +{ + if( buffer != nullptr && target != nullptr && target->valid() == true) { + target->set_velocity(buffer, v); } } -void SoundSource::fadeOut( float seconds ) +void +set_position(SoundBuffer* buffer, IOAudioEngine* target, const Point3f& pos) { - float current_volume = this->getVolume(); - float fade_step = current_volume / seconds; + if(buffer != nullptr && target != nullptr && target->valid() == true) { + target->set_position(buffer, pos); + } +} - while ( this->getStatus() != SoundStatus::Paused && this->getStatus() != SoundStatus::Stopped ) - { - if ( current_volume > 0.0 ) - { - std::cout << "\nFading out\n"; - this->setVolume ( current_volume ); - NOM_DUMP( current_volume ); - } - else - { - std::cout << "\nStopped\n"; - this->Pause(); - } +void +set_playback_position(SoundBuffer* buffer, IOAudioEngine* target, + real32 offset_seconds) +{ + if( buffer != nullptr && target != nullptr && target->valid() == true) { + target->set_playback_position(buffer, offset_seconds); + } +} - current_volume = current_volume - fade_step; - sleep ( 1000 ); // FIXME +void suspend(IOAudioEngine* target) +{ + if(target != nullptr && target->valid() == true) { + target->suspend(); + } +} - } // while getStatus loop +void resume(IOAudioEngine* target) +{ + if(target != nullptr) { + target->resume(); + } } +} // namespace audio } // namespace nom diff --git a/src/audio/AL/osx/apple_extensions.cpp b/src/audio/AL/osx/apple_extensions.cpp new file mode 100644 index 00000000..950c0ce1 --- /dev/null +++ b/src/audio/AL/osx/apple_extensions.cpp @@ -0,0 +1,102 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014, 2015, 2016 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#include "nomlib/audio/AL/osx/apple_extensions.hpp" + +// Private headers +#include "nomlib/audio/audio_defs.hpp" +#include "nomlib/audio/AL/ALAudioDevice.hpp" + +#include + +// Forward declarations +#include "nomlib/audio/AL/OpenAL.hpp" + +namespace nom { +namespace audio { + +// Apple OpenAL v1.1-v54 extensions +// See http://www.opensource.apple.com/source/OpenAL/OpenAL-54/Source/OpenAL/ +// typedef alcMacOSXGetMixerOutputRateProcPtr osx_get_sample_rate_func; +// typedef alcMacOSXMixerOutputRateProcPtr osx_set_sample_rate_func; +// typedef alcMacOSXGetMixerMaxiumumBussesProcPtr osx_get_max_sources_func; +// typedef alcMacOSXMixerMaxiumumBussesProcPtr osx_set_max_sources_func; + +// typedef real64(*openal_get_sample_rate_func)(); +// typedef real64(*openal_get_sample_rate_func)(ALCdevice_struct*); + +// typedef void(*openal_set_sample_rate_func)(real64 sample_rate); +// typedef int(*openal_get_max_sources_func)(); +// typedef void(*openal_set_max_sources_func)(int num_sources); + +real64 osx_sample_rate() +{ + real64 sample_rate = 0.0f; + + ALdouble(*get_sample_rate_func)() = + (alcMacOSXGetMixerOutputRateProcPtr) audio::process_addr("alcMacOSXGetMixerOutputRate"); + if(get_sample_rate_func != nullptr) { + sample_rate = get_sample_rate_func(); + } + + return sample_rate; +} + +void osx_set_sample_rate(real64 sample_rate) +{ + ALvoid(*set_sample_rate_func)(ALdouble) = + (alcMacOSXMixerOutputRateProcPtr) audio::process_addr("alcMacOSXMixerOutputRate"); + if(set_sample_rate_func != nullptr) { + set_sample_rate_func(sample_rate); + } +} + +int osx_max_sources() +{ + int num_sources = 0; + + ALint(*get_max_sources_func)() = + (alcMacOSXGetMixerMaxiumumBussesProcPtr) audio::process_addr("alcMacOSXGetMixerMaxiumumBusses"); + if(get_max_sources_func != nullptr) { + num_sources = get_max_sources_func(); + } + + return num_sources; +} + +void osx_set_max_sources(int num_sources) +{ + ALvoid(*set_max_sources_func)(ALint) = + (alcMacOSXMixerMaxiumumBussesProcPtr) audio::process_addr("alcMacOSXMixerMaxiumumBusses"); + if(set_max_sources_func != nullptr) { + set_max_sources_func(num_sources); + } +} + +} // namespace audio +} // namespace nom diff --git a/src/audio/CMakeLists.txt b/src/audio/CMakeLists.txt index 834bfb4e..5b2bd355 100644 --- a/src/audio/CMakeLists.txt +++ b/src/audio/CMakeLists.txt @@ -9,84 +9,89 @@ set( NOM_AUDIO_LIBRARY "nomlib-audio" ) # Default implementation sources (always built) set( NOM_AUDIO_SOURCE ${INC_DIR}/audio.hpp - ${SRC_DIR}/audio/AudioDeviceLocator.cpp - ${INC_DIR}/audio/AudioDeviceLocator.hpp + ${INC_DIR}/audio/audio_defs.hpp - ${INC_DIR}/audio/IAudioDevice.hpp - ${INC_DIR}/audio/IListener.hpp - ${INC_DIR}/audio/ISoundBuffer.hpp - ${INC_DIR}/audio/ISoundSource.hpp - - ${SRC_DIR}/audio/NullAudioDevice.cpp - ${INC_DIR}/audio/NullAudioDevice.hpp - - ${SRC_DIR}/audio/NullListener.cpp - ${INC_DIR}/audio/NullListener.hpp + ${INC_DIR}/audio/SoundBuffer.hpp + ${INC_DIR}/audio/ISoundFileReader.hpp + ${INC_DIR}/audio/ISoundFileWriter.hpp + ${INC_DIR}/audio/SoundFile.hpp - ${SRC_DIR}/audio/NullMusic.cpp - ${INC_DIR}/audio/NullMusic.hpp - - ${SRC_DIR}/audio/NullSound.cpp - ${INC_DIR}/audio/NullSound.hpp + ${INC_DIR}/audio/IAudioDevice.hpp + ${INC_DIR}/audio/IOAudioEngine.hpp - ${SRC_DIR}/audio/NullSoundBuffer.cpp - ${INC_DIR}/audio/NullSoundBuffer.hpp + # ${SRC_DIR}/audio/AudioDeviceLocator.cpp + # ${INC_DIR}/audio/AudioDeviceLocator.hpp - ${SRC_DIR}/audio/NullSoundSource.cpp - ${INC_DIR}/audio/NullSoundSource.hpp + # ${SRC_DIR}/audio/NullAudioDevice.cpp + # ${INC_DIR}/audio/NullAudioDevice.hpp + # ${SRC_DIR}/audio/NullAudioDeviceCaps.cpp + # ${INC_DIR}/audio/NullAudioDeviceCaps.hpp ) # Common dependencies on all platforms -set( NOM_AUDIO_DEPS nomlib-core ) - -# OpenAL implementation -if( NOM_BUILD_AUDIO_UNIT ) - - set( NOM_AUDIO_AL_SOURCE - ${SRC_DIR}/audio/AL/AudioDevice.cpp - ${INC_DIR}/audio/AL/AudioDevice.hpp - - ${SRC_DIR}/audio/AL/Listener.cpp - ${INC_DIR}/audio/AL/Listener.hpp - - ${SRC_DIR}/audio/AL/Music.cpp - ${INC_DIR}/audio/AL/Music.hpp - - ${SRC_DIR}/audio/AL/OpenAL.cpp - ${INC_DIR}/audio/AL/OpenAL.hpp - - ${SRC_DIR}/audio/AL/Sound.cpp - ${INC_DIR}/audio/AL/Sound.hpp - - ${SRC_DIR}/audio/AL/SoundBuffer.cpp - ${INC_DIR}/audio/AL/SoundBuffer.hpp - - ${SRC_DIR}/audio/AL/SoundFile.cpp - ${INC_DIR}/audio/AL/SoundFile.hpp - - ${SRC_DIR}/audio/AL/SoundSource.cpp - ${INC_DIR}/audio/AL/SoundSource.hpp - ) - - list( APPEND NOM_AUDIO_SOURCE ${NOM_AUDIO_AL_SOURCE} ) - - # Platform-specific dependencies - - find_package( OpenAL REQUIRED ) - if( OPENAL_FOUND ) +# TODO(jeff): Try and rid ourselves of dependence on nomlib-system here for the +# audio subsystem! +set(NOM_AUDIO_DEPS nomlib-core nomlib-math nomlib-system) + +if(NOM_BUILD_AUDIO_UNIT) + + # Common implementation sources (OpenAL) + set(NOM_AUDIO_AL_SOURCE + ${SRC_DIR}/audio/AL/ALAudioDevice.cpp + ${INC_DIR}/audio/AL/ALAudioDevice.hpp + ${SRC_DIR}/audio/AL/ALAudioDeviceCaps.cpp + ${INC_DIR}/audio/AL/ALAudioDeviceCaps.hpp + + ${SRC_DIR}/audio/AL/OpenAL.cpp + ${INC_DIR}/audio/AL/OpenAL.hpp + + # TODO(jeff): Divide libsndfile directory into its own source segment + ${SRC_DIR}/audio/libsndfile/SoundFileReader.cpp + ${INC_DIR}/audio/libsndfile/SoundFileReader.hpp + # ${SRC_DIR}/audio/libsndfile/SoundFileWriter.cpp + # ${INC_DIR}/audio/libsndfile/SoundFileWriter.hpp + + ${SRC_DIR}/audio/AL/SoundSource.cpp + ${INC_DIR}/audio/AL/SoundSource.hpp) + + list(APPEND NOM_AUDIO_SOURCE ${NOM_AUDIO_AL_SOURCE}) + + # ...Mac OS X specific OpenAL implementation details... + # FIXME(jeff): ... + if(PLATFORM_OSX AND NOM_USE_APPLE_OPENAL) + set(NOM_AUDIO_AL_OSX_SOURCE + ${SRC_DIR}/audio/AL/osx/apple_extensions.cpp + ${INC_DIR}/audio/AL/osx/apple_extensions.hpp) + list(APPEND NOM_AUDIO_SOURCE ${NOM_AUDIO_AL_OSX_SOURCE}) + endif(PLATFORM_OSX AND NOM_USE_APPLE_OPENAL) + + # OpenAL Library dependencies + if(NOM_USE_OPENAL OR NOM_USE_APPLE_OPENAL) + find_package(OpenAL REQUIRED) + elseif(NOM_USE_OPENAL_SOFT) + # TODO(jeff): Implement custom packaging of the OpenAL library and header + # file paths; nomlib-config.cmake & friends. + set(OPENAL_INCLUDE_DIR "/usr/local/opt/openal-soft/include/") + set(OPENAL_LIBRARY "/usr/local/opt/openal-soft/lib/libopenal.dylib") + set(OPENAL_FOUND true) + endif(NOM_USE_OPENAL OR NOM_USE_APPLE_OPENAL) + + if(OPENAL_FOUND) # Add development header files; al.h, alc.h - include_directories( ${OPENAL_INCLUDE_DIR} ) - list( APPEND NOM_AUDIO_DEPS ${OPENAL_LIBRARY} ) - endif( OPENAL_FOUND ) + NOM_LOG_INFO("OPENAL_INCLUDE_DIR: ${OPENAL_INCLUDE_DIR}") + include_directories(${OPENAL_INCLUDE_DIR}) + list(APPEND NOM_AUDIO_DEPS ${OPENAL_LIBRARY}) + endif(OPENAL_FOUND) - find_package( libsndfile REQUIRED ) - if( LIBSNDFILE_FOUND ) + find_package(libsndfile REQUIRED) + if(LIBSNDFILE_FOUND) # Add development header files; sndfile.h - include_directories( ${LIBSNDFILE_INCLUDE_DIR} ) - list( APPEND NOM_AUDIO_DEPS ${LIBSNDFILE_LIBRARY} ) - endif( LIBSNDFILE_FOUND ) + include_directories(${LIBSNDFILE_INCLUDE_DIR}) + endif(LIBSNDFILE_FOUND) + + list(APPEND NOM_AUDIO_DEPS ${LIBSNDFILE_LIBRARY}) -endif( NOM_BUILD_AUDIO_UNIT ) +endif(NOM_BUILD_AUDIO_UNIT) # Add and link the library nom_add_library( ${NOM_AUDIO_LIBRARY} ${LIBRARY_OUTPUT_TYPE} diff --git a/src/audio/NullAudioDevice.cpp b/src/audio/NullAudioDevice.cpp index 21df2219..e6026fee 100644 --- a/src/audio/NullAudioDevice.cpp +++ b/src/audio/NullAudioDevice.cpp @@ -28,31 +28,100 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ #include "nomlib/audio/NullAudioDevice.hpp" +#include "nomlib/audio/NullAudioDeviceCaps.hpp" + namespace nom { +namespace audio { -NullAudioDevice::NullAudioDevice( void ) +NullAudioDevice::NullAudioDevice() { - NOM_LOG_TRACE( NOM_LOG_CATEGORY_TRACE_AUDIO ); + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE_AUDIO, NOM_LOG_PRIORITY_DEBUG); +} + +NullAudioDevice::~NullAudioDevice() +{ + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE_AUDIO, NOM_LOG_PRIORITY_DEBUG); + + this->close(); } -NullAudioDevice::~NullAudioDevice( void ) +bool NullAudioDevice::valid() const { - NOM_LOG_TRACE( NOM_LOG_CATEGORY_TRACE_AUDIO ); + return this->initialized_; } -// std::shared_ptr NullAudioDevice::getAudioDevice( void ) const +// void* NullAudioDevice::device() const +// { +// return nullptr; +// } + +// void* NullAudioDevice::context() const // { // return nullptr; // } -const std::string NullAudioDevice::getDeviceName( void ) const +std::string NullAudioDevice::device_name() const { - return "NullAudioDevice"; + return this->device_name_; } -bool NullAudioDevice::isExtensionSupported( const std::string& extension ) const +// IOAudioEngine* NullAudioDevice::caps() const +// { +// return this->impl_; +// } + +IOAudioEngine* NullAudioDevice::open(const audio::AudioSpec* spec) { - return false; + IOAudioEngine* engine = nullptr; + + if(spec != nullptr) { + } + + this->device_name_ = "NullAudioDevice"; + + // this->impl_ = new NullAudioEngineCaps(); + // if(this->impl_ != nullptr) { + // this->initialized_ = true; + // } + + // return(this->valid() == true); + + engine = new NullAudioEngineCaps(); + if(engine != nullptr) { + this->initialized_ = true; + } + + return engine; +} + +void NullAudioDevice::suspend() +{ + +} + +void NullAudioDevice::resume() +{ + +} + +void NullAudioDevice::close() +{ + // NOM_DELETE_PTR(this->impl_); +} + +IOAudioEngine* create_null_audio_device(const audio::AudioSpec* spec) +{ + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE_AUDIO, NOM_LOG_PRIORITY_VERBOSE); + + IOAudioEngine* engine = nullptr; + NullAudioDevice* dev = new NullAudioDevice(); + + if(dev != nullptr) { + engine = dev->open(spec); + } + + return engine; } +} // namespace audio } // namespace nom diff --git a/src/audio/NullAudioDeviceCaps.cpp b/src/audio/NullAudioDeviceCaps.cpp new file mode 100644 index 00000000..0f4d6e5c --- /dev/null +++ b/src/audio/NullAudioDeviceCaps.cpp @@ -0,0 +1,217 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#include "nomlib/audio/NullAudioDeviceCaps.hpp" + +// Private headers +#include "nomlib/audio/audio_defs.hpp" + +// FIXME(jeff): enums +#include "nomlib/audio/AL/SoundSource.hpp" + +namespace nom { +namespace audio { + +NullAudioEngineCaps::NullAudioEngineCaps() +{ + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE_AUDIO, NOM_LOG_PRIORITY_DEBUG); +} + +NullAudioEngineCaps::~NullAudioEngineCaps() +{ + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE_AUDIO, NOM_LOG_PRIORITY_DEBUG); +} + +uint32 +NullAudioEngineCaps::channel_format(uint32 num_channels, uint32 channel_format) +{ + channel_format = 0; + return channel_format; +} + +bool NullAudioEngineCaps::valid_audio_buffer(SoundBuffer* buffer) +{ + bool valid = false; + return valid; +} + +bool NullAudioEngineCaps::valid_sound_buffer(SoundBuffer* buffer) +{ + bool valid = false; + return valid; +} + +uint32 NullAudioEngineCaps::state(SoundBuffer* buffer) +{ + uint32 result = AUDIO_STATE_STOPPED; + return result; +} +real32 NullAudioEngineCaps::pitch(SoundBuffer* buffer) +{ + auto pitch = 0.0f; + return pitch; +} + +real32 NullAudioEngineCaps::volume() const +{ + auto gain_level = nom::audio::MIN_VOLUME; + return gain_level; +} + +Point3f NullAudioEngineCaps::position() const +{ + // Defaults as per /System/Library/Frameworks/OpenAL/Headers/al.h + Point3f p(0.0f, 0.0f, 0.0f); + return p; +} + +real32 NullAudioEngineCaps::volume(SoundBuffer* buffer) const +{ + real32 gain_level = nom::audio::MIN_VOLUME; + return gain_level; +} + +real32 NullAudioEngineCaps::min_volume(SoundBuffer* buffer) +{ + real32 min_gain = nom::audio::MIN_VOLUME; + return min_gain; +} + +real32 NullAudioEngineCaps::max_volume(SoundBuffer* buffer) +{ + real32 max_gain = nom::audio::MIN_VOLUME; + return max_gain; +} + +Point3f NullAudioEngineCaps::velocity(SoundBuffer* buffer) +{ + Point3f v(Point3f::zero); + return v; +} + +Point3f NullAudioEngineCaps::position(SoundBuffer* buffer) +{ + Point3f p(Point3f::zero); + return p; +} + +real32 NullAudioEngineCaps::playback_position(SoundBuffer* buffer) +{ + real32 pos = 0.0f; + return pos; +} + +real32 NullAudioEngineCaps::playback_samples(SoundBuffer* buffer) +{ + real32 samples = 0.0f; + return samples; +} + +void NullAudioEngineCaps::set_state(SoundBuffer* target, uint32 state) +{ + +} + +void NullAudioEngineCaps::set_volume(real32 gain) +{ + +} + +void NullAudioEngineCaps::set_position(const Point3f& p) +{ +} + +void NullAudioEngineCaps::set_volume(SoundBuffer* target, real32 gain) +{ + +} + +void NullAudioEngineCaps::set_min_volume(SoundBuffer* target, real32 gain) +{ + +} + +void NullAudioEngineCaps::set_max_volume(SoundBuffer* target, real32 gain) +{ + +} + +void NullAudioEngineCaps::set_velocity(SoundBuffer* target, const Point3f& v) +{ + +} + +void NullAudioEngineCaps::set_position(SoundBuffer* target, const Point3f& p) +{ + +} + +void NullAudioEngineCaps::set_pitch(SoundBuffer* buffer, real32 pitch) +{ + +} + +void NullAudioEngineCaps::set_playback_position(SoundBuffer* target, + real32 offset_seconds) +{ + +} + +void NullAudioEngineCaps::play(SoundBuffer* buffer) +{ + +} + +void NullAudioEngineCaps::stop(SoundBuffer* buffer) +{ + +} + +void NullAudioEngineCaps::pause(SoundBuffer* buffer) +{ + +} + +void NullAudioEngineCaps::resume(SoundBuffer* buffer) +{ + +} + +bool NullAudioEngineCaps::fill_audio_buffer(SoundBuffer* buffer) +{ + bool result = false; + return result; +} + +void NullAudioEngineCaps::free_buffer(SoundBuffer* buffer) +{ + +} + +} // namespace audio +} // namespace nom diff --git a/src/audio/NullSound.cpp b/src/audio/NullSound.cpp deleted file mode 100644 index 38ba4c25..00000000 --- a/src/audio/NullSound.cpp +++ /dev/null @@ -1,68 +0,0 @@ -/****************************************************************************** - - nomlib - C++11 cross-platform game engine - -Copyright (c) 2013, 2014 Jeffrey Carpenter -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -******************************************************************************/ -#include "nomlib/audio/NullSound.hpp" - -namespace nom { - -NullSound::~NullSound( void ) -{ - NOM_LOG_TRACE( NOM_LOG_CATEGORY_TRACE_AUDIO ); -} - -void NullSound::setBuffer( const ISoundBuffer& copy ) -{ - // Do nothing -} - -void NullSound::Play( void ) -{ - // Do nothing -} - -void NullSound::Stop( void ) -{ - // Do nothing -} - -void NullSound::Pause( void ) -{ - // Do nothing -} - -void NullSound::togglePause( void ) -{ - // Do nothing -} - -void NullSound::fadeOut( float seconds ) -{ - // Do nothing -} - -} // namespace nom diff --git a/src/audio/NullSoundBuffer.cpp b/src/audio/NullSoundBuffer.cpp deleted file mode 100644 index bec1f905..00000000 --- a/src/audio/NullSoundBuffer.cpp +++ /dev/null @@ -1,68 +0,0 @@ -/****************************************************************************** - - nomlib - C++11 cross-platform game engine - -Copyright (c) 2013, 2014 Jeffrey Carpenter -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -******************************************************************************/ -#include "nomlib/audio/NullSoundBuffer.hpp" - -namespace nom { - -NullSoundBuffer::NullSoundBuffer( void ) -{ - NOM_LOG_TRACE( NOM_LOG_CATEGORY_TRACE_AUDIO ); -} - -NullSoundBuffer::~NullSoundBuffer( void ) -{ - NOM_LOG_TRACE( NOM_LOG_CATEGORY_TRACE_AUDIO ); -} - -uint32 NullSoundBuffer::get( void ) const -{ - return 0; -} - -int64 NullSoundBuffer::getDuration( void ) const -{ - return 0; -} - -bool NullSoundBuffer::load( const std::string& filename ) -{ - return false; -} - -void NullSoundBuffer::attach( Sound* sound ) const -{ - // Do nothing -} - -void NullSoundBuffer::detach( Sound* sound ) const -{ - // Do nothing -} - -} // namespace nom diff --git a/src/audio/NullSoundSource.cpp b/src/audio/NullSoundSource.cpp deleted file mode 100644 index a37df98c..00000000 --- a/src/audio/NullSoundSource.cpp +++ /dev/null @@ -1,183 +0,0 @@ -/****************************************************************************** - - nomlib - C++11 cross-platform game engine - -Copyright (c) 2013, 2014 Jeffrey Carpenter -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -******************************************************************************/ -#include "nomlib/audio/NullSoundSource.hpp" - -namespace nom { - -NullSoundSource::NullSoundSource( void ) -{ - NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_AUDIO, nom::LogPriority::NOM_LOG_PRIORITY_VERBOSE ); -} - -NullSoundSource::~NullSoundSource( void ) -{ - NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_AUDIO, nom::LogPriority::NOM_LOG_PRIORITY_VERBOSE ); -} - -float NullSoundSource::getVolume( void ) const -{ - return 0.0f; -} - -float NullSoundSource::getMinVolume( void ) const -{ - return 0.0f; -} - -float NullSoundSource::getMaxVolume( void ) const -{ - return 0.0f; -} - -float NullSoundSource::getPitch( void ) const -{ - return 0.0f; -} - -bool NullSoundSource::getLooping( void ) const -{ - return false; -} - -Point3f NullSoundSource::getPosition( void ) const -{ - return Point3f( 0.0f, 0.0f, 0.0f ); -} - -Point3f NullSoundSource::getVelocity( void ) const -{ - return Point3f( 0.0f, 0.0f, 0.0f ); -} - -bool NullSoundSource::getPositionRelativeToListener( void ) const -{ - return false; -} - -float NullSoundSource::getMinDistance( void ) const -{ - return 0.0f; -} - -float NullSoundSource::getAttenuation( void ) const -{ - return 0.0f; -} - -int32 NullSoundSource::getBufferID( void ) const -{ - return -1; -} - -float NullSoundSource::getPlayPosition( void ) const -{ - return 0.0f; -} - -SoundStatus NullSoundSource::getStatus( void ) const -{ - return SoundStatus::Stopped; -} - -void NullSoundSource::setVolume( float gain ) -{ - // Do nothing -} - -void NullSoundSource::setMinVolume( float gain ) -{ - // Do nothing -} - -void NullSoundSource::setMaxVolume( float gain ) -{ - // Do nothing -} - -void NullSoundSource::setPitch( float pitch ) -{ - // Do nothing -} - -void NullSoundSource::setLooping( bool loops ) -{ - // Do nothing -} - -void NullSoundSource::setPosition( float x, float y, float z ) -{ - // Do nothing -} - -void NullSoundSource::setPosition( const Point3f& position ) -{ - // Do nothing -} - -void NullSoundSource::setVelocity( float x, float y, float z ) -{ - // Do nothing -} - -void NullSoundSource::setVelocity( const Point3f& velocity ) -{ - // Do nothing -} - -void NullSoundSource::setPositionRelativeToListener( bool relative ) -{ - // Do nothing -} - -void NullSoundSource::setMinDistance( float distance ) -{ - // Do nothing -} - -void NullSoundSource::setAttenuation( float attenuation ) -{ - // Do nothing -} - -void NullSoundSource::setPlayPosition( float seconds ) -{ - // Do nothing -} - -void NullSoundSource::togglePause( void ) -{ - // Do nothing -} - -void NullSoundSource::fadeOut( float seconds ) -{ - // Do nothing -} - -} // namespace nom diff --git a/src/audio/libsndfile/SoundFileReader.cpp b/src/audio/libsndfile/SoundFileReader.cpp new file mode 100644 index 00000000..f7da7757 --- /dev/null +++ b/src/audio/libsndfile/SoundFileReader.cpp @@ -0,0 +1,401 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014, 2015, 2016 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#include "nomlib/audio/libsndfile/SoundFileReader.hpp" + +// Private headers +#include "nomlib/math/math_helpers.hpp" + +// Forward declarations (third-party) +#include + +namespace nom { +namespace audio { + +// TODO(jeff): Integrate a list of valid channel data formats that is +// acceptable for the audio hardware API to buffer in +// +// See also, +// 1. ALAudioDeviceCaps::channel_format +// 2. /Volumes/Media/Projects/third-party/SDL2.hg/src/audio/SDL_audiocvt.c +#if 0 +static uint32 native_format[] = { + AUDIO_FORMAT_S16, + AUDIO_FORMAT_R32, +}; +#endif +std::string libsndfile_version() +{ + return sf_version_string(); +} + +static uint64 sample_bytes(uint32 channel_format, int64 sample_count) +{ + uint64 total_bytes = 0; + + // IMPORTANT(jeff): This is dependent upon the internal data type of the + // samples when they were read from the input file. + uint8 bit_size = 0; + + switch(channel_format) + { + default: + case AUDIO_FORMAT_UNKNOWN: { + // Err; do nothing and the total bytes calculation zero out + } break; + + case AUDIO_FORMAT_S8: + case AUDIO_FORMAT_U8: + case AUDIO_FORMAT_S16: { + bit_size = sizeof(int16); + } break; + + case AUDIO_FORMAT_S24: { + NOM_ASSERT_INVALID_PATH("Not implemented"); + } break; + + case AUDIO_FORMAT_S32: + case AUDIO_FORMAT_R32: + case AUDIO_FORMAT_R64: { + bit_size = sizeof(real32); + } break; + } + + // Clamp negative values to zero + sample_count = nom::maximum(0, sample_count); + + total_bytes = (sample_count * bit_size); + + return total_bytes; +} + +static real32 duration_seconds(SF_INFO& metadata) +{ + auto sample_count = metadata.frames * metadata.channels; + auto sample_rate = metadata.samplerate; + auto channel_count = metadata.channels; + real32 duration = 0.0f; + + // IMPORTANT(jeff): When sample_count, an integer, is smaller than + // sample_rate, it must be seen as a fractional value, or else when we + // divide by it, we get a value of zero! + duration = + ( (real32)sample_count / sample_rate) / channel_count; + + return duration; +} + +static bool libsndfile_check_error(SNDFILE_tag* fp) +{ + int err = sf_error(fp); + if(err != SF_ERR_NO_ERROR) { + const char* err_string = sf_strerror(fp); + + NOM_LOG_ERR(NOM_LOG_CATEGORY_APPLICATION, err_string); + return false; + } + + return true; +} + +SoundFileReader::SoundFileReader() +{ + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE_AUDIO, NOM_LOG_PRIORITY_DEBUG); +} + +SoundFileReader::~SoundFileReader() +{ + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE_AUDIO, NOM_LOG_PRIORITY_DEBUG); + + this->close(); +} + +bool SoundFileReader::valid() const +{ + return(this->fp_ != nullptr); +} + +bool SoundFileReader::open(const std::string& filename, SoundInfo& info) +{ + SF_INFO metadata = {}; + + // If this object instance has been used before; we must clean up its memory + // first... + if(this->fp_ != nullptr) { + this->close(); + } + + // NOTE(jeff): As per the libsndfile API documentation [1], we *should* set + // this field to zero before calling sf_open. Less and except when we are + // opening a RAW file (i.e.: PCM?), where the caller must set the sample rate, + // channels and format fields to their appropriate values. + // + // 1. http://www.mega-nerd.com/libsndfile/api.html#open + metadata.format = 0; + + this->fp_ = sf_open(filename.c_str(), SFM_READ, &metadata); + + if(libsndfile_check_error(this->fp_) == false) { + return false; + } + + info = this->parse_header(metadata); + + return true; +} + +int64 +SoundFileReader::read(void* data, uint32 channel_format, + nom::size_type chunk_size) +{ + sf_count_t sample_frames_read = 0; + + NOM_ASSERT(this->fp_ != nullptr); + if(this->fp_ == nullptr) { + return sample_frames_read; + } + + switch(channel_format) + { + default: + case AUDIO_FORMAT_UNKNOWN: { + return sample_frames_read; + } break; + + // TODO(jeff): sample conversion to uint8 with this equation: + // + // (int16_val)/(uint8_val) + // (32767+1)/(255+1) + case AUDIO_FORMAT_U8: + case AUDIO_FORMAT_S8: + case AUDIO_FORMAT_S16: { + auto samples = NOM_SCAST(int16*, data); + sample_frames_read = sf_readf_short(this->fp_, samples, chunk_size); + } break; + + case AUDIO_FORMAT_S24: { + NOM_ASSERT_INVALID_PATH("Not implemented"); + } break; + + // IMPORTANT(jeff): We must convert 32-bit integer PCM data to normalized + // floating-point values due to lack of support in OpenAL -- see also: + // + // 1. ::channel_format + // 2. http://openal.org/pipermail/openal/2014-December/000287.html + // 3. http://openal.org/pipermail/openal/2014-December/000289.html + + // TODO(jeff): Implement a method of toggling a quirks mode for OpenAL + // instead of doing it here, for sake of a generic codebase. + case AUDIO_FORMAT_S32: + case AUDIO_FORMAT_R32: + case AUDIO_FORMAT_R64: { + auto samples = NOM_SCAST(real32*, data); + sample_frames_read = sf_readf_float(this->fp_, samples, chunk_size); + } break; + } + + if(libsndfile_check_error(this->fp_) == false) { + return sample_frames_read; + } + + return(sample_frames_read); +} + +int64 SoundFileReader::seek(int64 offset, SoundSeek dir) +{ + sf_count_t cursor_pos = 0; + int whence = 0; + + switch(dir) + { + default: + case SOUND_SEEK_SET: { + whence = SEEK_SET; + } break; + + case SOUND_SEEK_CUR: { + whence = SEEK_CUR; + } break; + + case SOUND_SEEK_END: { + whence = SEEK_END; + } break; + } + + cursor_pos = sf_seek(this->fp_, offset, whence); + + return cursor_pos; +} + +void SoundFileReader::close() +{ + if(this->fp_ != nullptr) { + sf_close(this->fp_); + } + + this->fp_ = nullptr; +} + +SoundInfo SoundFileReader::parse_header(SF_INFO& metadata) +{ + SoundInfo info; + + // IMPORTANT(jeff): Take heed that these may be order-dependent! + info.frame_count = metadata.frames; + info.sample_count = metadata.frames * metadata.channels; + info.sample_rate = metadata.samplerate; + info.channel_count = metadata.channels; + info.duration = audio::duration_seconds(metadata); + info.seekable = metadata.seekable; + + auto format = (metadata.format & SF_FORMAT_SUBMASK); + switch(format) + { + default: + case AUDIO_FORMAT_UNKNOWN: { + info.channel_format = AUDIO_FORMAT_UNKNOWN; + NOM_LOG_INFO(NOM_LOG_CATEGORY_TEST, "Unknown audio format", + info.channel_format); + } break; + + case SF_FORMAT_PCM_16: { + info.channel_format = AUDIO_FORMAT_S16; + NOM_LOG_INFO(NOM_LOG_CATEGORY_TEST, "PCM 16-bit", info.channel_format); + } break; + + case SF_FORMAT_PCM_S8: { + info.channel_format = AUDIO_FORMAT_S8; + NOM_LOG_INFO(NOM_LOG_CATEGORY_TEST, "PCM 8-bit", info.channel_format); + } break; + + case SF_FORMAT_PCM_U8: { + info.channel_format = AUDIO_FORMAT_U8; + NOM_LOG_INFO(NOM_LOG_CATEGORY_TEST, "PCM (unsigned) 8-bit", + info.channel_format); + } break; + + case SF_FORMAT_PCM_24: { + info.channel_format = AUDIO_FORMAT_S24; + NOM_LOG_INFO(NOM_LOG_CATEGORY_TEST, "PCM 24-bit", info.channel_format); + } break; + + case SF_FORMAT_PCM_32: { + info.channel_format = AUDIO_FORMAT_S32; + NOM_LOG_INFO(NOM_LOG_CATEGORY_TEST, "PCM 32-bit", info.channel_format); + } break; + + case SF_FORMAT_FLOAT: { + info.channel_format = AUDIO_FORMAT_R32; + NOM_LOG_INFO(NOM_LOG_CATEGORY_TEST, "PCM 32-bit (floating-point)", + info.channel_format); + } break; + + case SF_FORMAT_DOUBLE: { + info.channel_format = AUDIO_FORMAT_R64; + NOM_LOG_INFO(NOM_LOG_CATEGORY_TEST, "PCM 64-bit (floating-point)", + info.channel_format); + } break; + } + + info.total_bytes = + audio::sample_bytes(info.channel_format, info.sample_count); + + // Audio metadata + info.tags.title = this->parse_tags(this->fp_, SOUND_TAG_TITLE); + info.tags.copyright = this->parse_tags(this->fp_, SOUND_TAG_COPYRIGHT); + info.tags.software = this->parse_tags(this->fp_, SOUND_TAG_SOFTWARE); + info.tags.artist = this->parse_tags(this->fp_, SOUND_TAG_ARTIST); + info.tags.comment = this->parse_tags(this->fp_, SOUND_TAG_COMMENT); + info.tags.date = this->parse_tags(this->fp_, SOUND_TAG_DATE); + info.tags.album = this->parse_tags(this->fp_, SOUND_TAG_ALBUM); + info.tags.license = this->parse_tags(this->fp_, SOUND_TAG_LICENSE); + info.tags.track_number = this->parse_tags(this->fp_, SOUND_TAG_TRACK_NUMBER); + info.tags.genre = this->parse_tags(this->fp_, SOUND_TAG_GENRE); + + return info; +} + +const char* SoundFileReader::parse_tags(SNDFILE_tag* fp, uint32 sound_tag) +{ + const char* tag = nullptr; + + switch(sound_tag) { + default: { + // ... + } break; + + case SOUND_TAG_TITLE: { + tag = sf_get_string(fp, SF_STR_TITLE); + } break; + + case SOUND_TAG_COPYRIGHT: { + tag = sf_get_string(fp, SF_STR_COPYRIGHT); + } break; + + case SOUND_TAG_SOFTWARE: { + tag = sf_get_string(fp, SF_STR_SOFTWARE); + } break; + + case SOUND_TAG_ARTIST: { + tag = sf_get_string(fp, SF_STR_ARTIST); + } break; + + case SOUND_TAG_COMMENT: { + tag = sf_get_string(fp, SF_STR_COMMENT); + } break; + + case SOUND_TAG_DATE: { + tag = sf_get_string(fp, SF_STR_DATE); + } break; + + case SOUND_TAG_ALBUM: { + tag = sf_get_string(fp, SF_STR_ALBUM); + } break; + + case SOUND_TAG_LICENSE: { + tag = sf_get_string(fp, SF_STR_LICENSE); + } break; + + case SOUND_TAG_TRACK_NUMBER: { + tag = sf_get_string(fp, SF_STR_TRACKNUMBER); + } break; + + case SOUND_TAG_GENRE: { + tag = sf_get_string(fp, SF_STR_GENRE); + } break; + } + + if(tag == nullptr) { + tag = "\0"; + } + + return tag; +} + +} // namespace audio +} // namespace nom diff --git a/src/audio/libsndfile/SoundFileWriter.cpp b/src/audio/libsndfile/SoundFileWriter.cpp new file mode 100644 index 00000000..6f112b2f --- /dev/null +++ b/src/audio/libsndfile/SoundFileWriter.cpp @@ -0,0 +1,287 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#include "nomlib/audio/libsndfile/SoundFileWriter.hpp" + +// Private headers +#include "nomlib/math/math_helpers.hpp" + +// Forward declarations (third-party) +#include + +namespace nom { + +// static std::string libsndfile_version() +// { +// return sf_version_string(); +// } + +static uint64 sample_bytes(SoundFormat channel_format, int64 sample_count) +{ + uint64 total_bytes = 0; + + // IMPORTANT(jeff): This is dependent upon the internal data type of the + // samples when they were read from the input file. + uint8 bit_size = 0; + + if(channel_format == FORMAT_PCM_S8) { + bit_size = sizeof(int8); + } else if(channel_format == FORMAT_PCM_S16) { + bit_size = sizeof(int16); + } else if(channel_format == FORMAT_PCM_S32) { + bit_size = sizeof(int32); + } else if(channel_format == FORMAT_PCM_R32) { + bit_size = sizeof(real32); + } else if(channel_format == FORMAT_PCM_R32) { + bit_size = sizeof(real64); + } + + // Clamp negative values to zero + sample_count = nom::maximum(0, sample_count); + + total_bytes = (sample_count * bit_size); + + return total_bytes; +} + +static real32 duration_seconds(SF_INFO& metadata) +{ + auto sample_count = metadata.frames * metadata.channels; + auto sample_rate = metadata.samplerate; + auto channel_count = metadata.channels; + real32 duration = 0.0f; + + // IMPORTANT(jeff): When sample_count, an integer, is smaller than + // sample_rate, it must be seen as a fractional value, or else when we + // divide by it, we get a value of zero! + duration = + ( (real32)sample_count / sample_rate) / channel_count; + + return duration; +} + +static bool libsndfile_check_error(SNDFILE_tag* fp) +{ + int err = sf_error(fp); + if(err != SF_ERR_NO_ERROR) { + const char* err_string = sf_strerror(fp); + + NOM_LOG_ERR(NOM_LOG_CATEGORY_APPLICATION, err_string); + return false; + } + + return true; +} + +SoundFileWriter::SoundFileWriter() +{ + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE_AUDIO, NOM_LOG_PRIORITY_DEBUG); +} + +SoundFileWriter::~SoundFileWriter() +{ + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE_AUDIO, NOM_LOG_PRIORITY_DEBUG); + + this->close(); +} + +bool SoundFileWriter::valid() const +{ + return(this->fp_ != nullptr); +} + +// TODO: +// bool SoundFileWriter::open(void* data, SoundInfo& info) +bool SoundFileWriter::open(const std::string& filename, SoundInfo& info) +{ + SF_INFO metadata = {}; + + // If this object instance has been used before; we must free its memory + // first! + if(this->fp_ != nullptr) { + this->close(); + } + + // NOTE(jeff): As per the libsndfile API documentation [1], we *should* set + // this field to zero before calling sf_open. Less and except when we are + // opening a RAW file (i.e.: PCM?), where the caller must set the sample rate, + // channels and format fields to their appropriate values. + // + // 1. http://www.mega-nerd.com/libsndfile/api.html#open + metadata.format = 0; + + this->fp_ = sf_open(filename.c_str(), SFM_READ, &metadata); + + if(libsndfile_check_error(this->fp_) == false) { + return false; + } + + info = this->parse_header(metadata); + + return true; +} + +int64 SoundFileWriter::write(void* data, nom::size_type byte_size) +{ + sf_count_t samples_written = 0; + + NOM_ASSERT(this->fp_ != nullptr); + if(this->fp_ == nullptr) { + return samples_written; + } + + samples_written = sf_write_short(this->fp_, (int16*)data, byte_size); + + if(libsndfile_check_error(this->fp_) == false) { + return samples_written; + } + + return(samples_written); +} + +int64 SoundFileWriter::seek(int64 offset, SoundSeek dir) +{ + sf_count_t cursor_pos = 0; + int whence = 0; + + switch(dir) + { + default: + case SOUND_SEEK_SET: { + whence = SEEK_SET; + } break; + + case SOUND_SEEK_CUR: { + whence = SEEK_CUR; + } break; + + case SOUND_SEEK_END: { + whence = SEEK_END; + } break; + } + + cursor_pos = sf_seek(this->fp_, offset, whence); + + return cursor_pos; +} + +void SoundFileWriter::close() +{ + if(this->fp_ != nullptr) { + sf_close(this->fp_); + } + + this->fp_ = nullptr; +} + +SoundInfo SoundFileWriter::parse_header(SF_INFO& metadata) +{ + SoundInfo info; + + // IMPORTANT(jeff): Take heed that these may be order-dependent! + info.frame_count = metadata.frames; + info.sample_count = metadata.frames * metadata.channels; + info.sample_rate = metadata.samplerate; + info.channel_count = metadata.channels; + info.total_bytes = nom::sample_bytes(AUDIO_FORMAT_S16, info.sample_count); + info.duration = nom::duration_seconds(metadata); + info.seekable = metadata.seekable; + + // Audio metadata + info.tags.title = this->parse_tags(this->fp_, SOUND_TAG_TITLE); + info.tags.copyright = this->parse_tags(this->fp_, SOUND_TAG_COPYRIGHT); + info.tags.software = this->parse_tags(this->fp_, SOUND_TAG_SOFTWARE); + info.tags.artist = this->parse_tags(this->fp_, SOUND_TAG_ARTIST); + info.tags.comment = this->parse_tags(this->fp_, SOUND_TAG_COMMENT); + info.tags.date = this->parse_tags(this->fp_, SOUND_TAG_DATE); + info.tags.album = this->parse_tags(this->fp_, SOUND_TAG_ALBUM); + info.tags.license = this->parse_tags(this->fp_, SOUND_TAG_LICENSE); + info.tags.track_number = this->parse_tags(this->fp_, SOUND_TAG_TRACK_NUMBER); + info.tags.genre = this->parse_tags(this->fp_, SOUND_TAG_GENRE); + + return info; +} + +const char* SoundFileWriter::parse_tags(SNDFILE_tag* fp, uint32 sound_tag) +{ + const char* tag = nullptr; + + switch(sound_tag) { + default: { + tag = ""; + } break; + + case SOUND_TAG_TITLE: { + tag = sf_get_string(fp, SF_STR_TITLE); + } break; + + case SOUND_TAG_COPYRIGHT: { + tag = sf_get_string(fp, SF_STR_COPYRIGHT); + } break; + + case SOUND_TAG_SOFTWARE: { + tag = sf_get_string(fp, SF_STR_SOFTWARE); + } break; + + case SOUND_TAG_ARTIST: { + tag = sf_get_string(fp, SF_STR_ARTIST); + } break; + + case SOUND_TAG_COMMENT: { + tag = sf_get_string(fp, SF_STR_COMMENT); + } break; + + case SOUND_TAG_DATE: { + tag = sf_get_string(fp, SF_STR_DATE); + } break; + + case SOUND_TAG_ALBUM: { + tag = sf_get_string(fp, SF_STR_ALBUM); + } break; + + case SOUND_TAG_LICENSE: { + tag = sf_get_string(fp, SF_STR_LICENSE); + } break; + + case SOUND_TAG_TRACK_NUMBER: { + tag = sf_get_string(fp, SF_STR_TRACKNUMBER); + } break; + + case SOUND_TAG_GENRE: { + tag = sf_get_string(fp, SF_STR_GENRE); + } break; + } + + if(tag == nullptr) { + tag = ""; + } + + return tag; +} + +} // namespace nom diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index b5d996b9..6cedd92e 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -9,7 +9,10 @@ set( NOM_CORE_LIBRARY "nomlib-core" ) set( NOM_CORE_SOURCE ${INC_DIR}/core.hpp ${INC_DIR}/macros.hpp + + ${SRC_DIR}/platforms.cpp ${INC_DIR}/platforms.hpp + ${INC_DIR}/types.hpp ${INC_DIR}/config.hpp # auto-generated @@ -23,8 +26,10 @@ set( NOM_CORE_SOURCE ${SRC_DIR}/revision.cpp.in ${INC_DIR}/revision.hpp - ${SRC_DIR}/core/helpers.cpp - ${INC_DIR}/core/helpers.hpp + ${INC_DIR}/core/unique_ptr.hpp + + ${SRC_DIR}/core/strings.cpp + ${INC_DIR}/core/strings.hpp ${SRC_DIR}/core/clock.cpp ${INC_DIR}/core/clock.hpp @@ -43,6 +48,9 @@ set( NOM_CORE_SOURCE ${SRC_DIR}/core/SDL_assertion_helpers.cpp ${INC_DIR}/core/SDL_assertion_helpers.hpp + + ${SRC_DIR}/core/err.cpp + ${INC_DIR}/core/err.hpp ) # Platform-specific implementations & dependencies diff --git a/src/core/SDL2Logger.cpp b/src/core/SDL2Logger.cpp index c1fe3629..fa90b49a 100644 --- a/src/core/SDL2Logger.cpp +++ b/src/core/SDL2Logger.cpp @@ -29,6 +29,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "nomlib/core/SDL2Logger.hpp" // Private headers +#include "nomlib/core/clock.hpp" #include "nomlib/core/ConsoleOutput.hpp" namespace nom { diff --git a/src/core/clock.cpp b/src/core/clock.cpp index 460b4e07..d73d601b 100644 --- a/src/core/clock.cpp +++ b/src/core/clock.cpp @@ -77,7 +77,7 @@ void sleep( uint32 milliseconds ) SDL_Delay ( std::max ( milliseconds, static_cast ( 10 ) ) ); } -uint64 hires_counter() +uint64 hires_ticks() { return SDL_GetPerformanceCounter(); } diff --git a/src/system/AnimationTimer.cpp b/src/core/err.cpp similarity index 50% rename from src/system/AnimationTimer.cpp rename to src/core/err.cpp index 18f939a3..a1b7581a 100644 --- a/src/system/AnimationTimer.cpp +++ b/src/core/err.cpp @@ -2,7 +2,7 @@ nomlib - C++11 cross-platform game engine -Copyright (c) 2013, 2014 Jeffrey Carpenter +Copyright (c) 2013, 2014, 2015 Jeffrey Carpenter All rights reserved. Redistribution and use in source and binary forms, with or without @@ -26,89 +26,103 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ -#include "nomlib/system/AnimationTimer.hpp" - -// Private headers (third-party libs) -#include +#include "nomlib/core/err.hpp" namespace nom { -AnimationTimer::AnimationTimer ( void ) -{ -//NOM_LOG_TRACE ( NOM ); - - if ( SDL_WasInit( SDL_INIT_TIMER ) == false ) - { - if ( SDL_InitSubSystem ( SDL_INIT_TIMER ) == -1 ) - { -NOM_LOG_ERR ( NOM, SDL_GetError() ); - return; - } - } +// Global err buffer +static err err_buffer; - this->timer_started = false; - this->elapsed_ticks = 0; - this->frame_rate = 0; +err::err() + : message("\0") +{ + // :-( } -AnimationTimer::~AnimationTimer ( void ) +err::~err() { -//NOM_LOG_TRACE ( NOM ); + // NOTE: An explicitly-declared destructor is necessary for building with + // MSVCPP 2013 +} - SDL_QuitSubSystem ( SDL_INIT_TIMER ); +err::err(const err& rhs) + : message(rhs.message.str()) + // message(rhs.message) +{ + // NOTE: An explicitly-declared copy constructor is necessary for building + // with MSVCPP 2013 } -void AnimationTimer::start ( void ) +#if 0 +std::stringstream& operator <<(std::stringstream& os, const err& error) { - this->elapsed_ticks = SDL_GetTicks(); - this->timer_started = true; + os << error.message.rdbuf(); + + return os; } +#endif -void AnimationTimer::stop ( void ) +bool error_state() { - this->elapsed_ticks = 0; - this->timer_started = false; + auto string_length = err_buffer.message.str().length(); + bool error_state = false; + + if(string_length > 0) { + error_state = true; + } + + return error_state; } -void AnimationTimer::restart ( void ) +std::string error() { - this->start(); + return err_buffer.message.str(); + // return err_buffer.message; } -uint32 AnimationTimer::ticks ( void ) const +err make_error(const char* message) { - if ( this->timer_started == true ) - { - return this->elapsed_ticks; + err error; + + if( message != nullptr ) { + error.message << message; + // error.message = message; } - // Timer is not running - return 0; + return error; } -bool AnimationTimer::started ( void ) const +void set_error(const err& error) { - return this->timer_started; -} + nom::clear_error(); -const std::string AnimationTimer::ticksAsString ( void ) const -{ - return std::to_string ( static_cast ( this->ticks() ) ); + err_buffer.message << error.message.str(); + // err_buffer.message = error.message; } -uint32 AnimationTimer::seconds ( float milliseconds ) const +void set_error(const char* message) { - return static_cast ( milliseconds * 1000.f ); + nom::clear_error(); + + if(message != nullptr) { + err_buffer.message << message; + // err_buffer.message = message; + } } -uint32 AnimationTimer::framerate ( void ) const +void set_error(const std::string& message) { - return this->frame_rate; + nom::clear_error(); + + if(message.length() > 0) { + err_buffer.message << message; + } } -void AnimationTimer::setFrameRate ( uint32 rate ) +void clear_error() { - this->frame_rate = rate; + err_buffer.message.str(""); + // err_buffer.message = "\0"; } } // namespace nom diff --git a/src/core/helpers.cpp b/src/core/helpers.cpp deleted file mode 100644 index 328bfc42..00000000 --- a/src/core/helpers.cpp +++ /dev/null @@ -1,56 +0,0 @@ - -/****************************************************************************** - - nomlib - C++11 cross-platform game engine - -Copyright (c) 2013, 2014 Jeffrey Carpenter -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -******************************************************************************/ -#include "nomlib/core/helpers.hpp" - -namespace nom { - -namespace priv { - -char* duplicate_string( const char* val, uint length ) -{ - // Buffer overflow protection - if( length >= priv::MAX_STRING_LENGTH ) - { - length = priv::MAX_STRING_LENGTH - 1; - } - - // Allocate memory for duplicating the C string - char* duplicate_string = static_cast ( malloc( length + 1 ) ); - std::memcpy( duplicate_string, val, length ); - - // Null-terminate - duplicate_string[length] = 0; - - return duplicate_string; -} - -} // namespace priv - -} // namespace nom diff --git a/src/core/strings.cpp b/src/core/strings.cpp new file mode 100644 index 00000000..0ef04e1f --- /dev/null +++ b/src/core/strings.cpp @@ -0,0 +1,376 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014, 2015, 2016 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#include "nomlib/core/strings.hpp" + +// Private headers (third-party) +#include +#include +#include + +// Private headers (internal) +#include "nomlib/core/err.hpp" +#include "nomlib/math/math_helpers.hpp" + +namespace nom { + +const char* +create_string(const char* str, nom::size_type num_bytes) +{ + auto str_length = num_bytes; + + // NOTE(jeff): Make sure we have room for null-termination + if(str_length >= MAX_STRING_LENGTH) { + str_length = (MAX_STRING_LENGTH - 1); + } + + char* result = NOM_SCAST(char*, malloc(str_length + 1)); + + if(result == nullptr) { + nom::set_error("Failed to allocate memory for string; NULL result"); + return result; + } + + if(str != nullptr) { + std::memcpy(result, str, str_length); + } + + // NOTE(jeff): Null terminate the resulting string + result[str_length] = '\0'; + + return result; +} + +const char* +create_string(const char* str) +{ + nom::size_type str_length = 0; + const char* result = nullptr; + + if(str == nullptr) { + nom::set_error("Failed to allocate memory for string; passed NULL string"); + return result; + } + + str_length = nom::string_length(str); + + result = nom::create_string(str, str_length); + + return result; +} + +int string_to_int(const char* str, int input_base) +{ + int result = 0; + + if(str != nullptr) { + result = strtoimax(str, nullptr, input_base); + } else { + result = 0; + } + + return result; +} + +int string_to_int(const char* str) +{ + int result = 0; + int input_base = 0; + + if(str != nullptr) { + result = strtoimax(str, nullptr, input_base); + } else { + result = 0; + } + + return result; +} + +uint string_to_uint(const char* str, int input_base) +{ + int result = 0; + + if(str != nullptr) { + result = strtoumax(str, nullptr, input_base); + } else { + result = 0; + } + + return result; +} + +uint string_to_uint(const char* str) +{ + int result = 0; + int input_base = 0; + + if(str != nullptr) { + result = strtoumax(str, nullptr, input_base); + } else { + result = 0; + } + + return result; +} + +const char* integer_to_cstr(int number) +{ + char* result = nullptr; + + sprintf(result, "%d", number); + + return result; +} + +std::string integer_to_string(int number) +{ + auto result = std::to_string(number); + + return result; +} + +nom::size_type string_length(const char* str) +{ + return strlen(str); +} + +nom::size_type string_length(const std::string& str) +{ + return str.length(); +} + +int +compare_cstr_insensitive(const char* str1, const char* str2, + nom::size_type len) +{ + auto str1_tolower = nom::to_lowercase_cstr(str1); + auto str2_tolower = nom::to_lowercase_cstr(str2); + + auto result = strncmp(str1_tolower, str2_tolower, len); + + nom::free_string(str1_tolower); + nom::free_string(str2_tolower); + + return result; +} + +int compare_cstr_insensitive(const char* str1, const char* str2) +{ + auto str1_len = nom::string_length(str1); + auto str2_len = nom::string_length(str2); + const auto STR_LENGTH = nom::clamp_min(str1_len, str2_len); + + auto result = nom::compare_cstr_insensitive(str1, str2, STR_LENGTH); + + return result; +} + +int +compare_cstr_sensitive(const char* str1, const char* str2, + nom::size_type len) +{ + auto result = strncmp(str1, str2, len); + + return result; +} + +int compare_cstr_sensitive(const char* str1, const char* str2) +{ + auto str1_len = nom::string_length(str1); + auto str2_len = nom::string_length(str2); + const auto STR_LENGTH = nom::clamp_min(str1_len, str2_len); + + auto result = nom::compare_cstr_sensitive(str1, str2, STR_LENGTH); + + return result; +} + +int +compare_string_insensitive(const std::string& str1, const std::string& str2, + nom::size_type len) +{ + auto str1_cstr = str1.c_str(); + auto str2_cstr = str2.c_str(); + + auto result = + nom::compare_cstr_insensitive(str1_cstr, str2_cstr, len); + + return result; +} + +int compare_string_insensitive(const std::string& str1, const std::string& str2) +{ + auto str1_len = str1.length(); + auto str2_len = str2.length(); + const auto STR_LENGTH = nom::clamp_min(str1_len, str2_len); + + auto result = + nom::compare_string_insensitive(str1, str2, STR_LENGTH); + + return result; +} + +int +compare_string_sensitive(const std::string& str1, const std::string& str2, + nom::size_type len) +{ + auto result = str1.compare(0, len, str2); + + return result; +} + +int compare_string_sensitive(const std::string& str1, const std::string& str2) +{ + auto str1_len = str1.length(); + auto str2_len = str2.length(); + const auto STR_LENGTH = nom::clamp_min(str1_len, str2_len); + + auto result = nom::compare_string_sensitive(str1, str2, STR_LENGTH); + + return result; +} + +void copy_string(const char* source, char* dest) +{ + std::strcpy(dest, source); +} + +const char* duplicate_string(const char* str, nom::size_type length) +{ + // Buffer overflow protection + if(length >= MAX_STRING_LENGTH) { + // NOTE(jeff): One extra character is needed for a NULL-terminated string + length = MAX_STRING_LENGTH - 1; + } + + char* duplicate_string = NOM_SCAST(char*, malloc(length + 1)); + std::memcpy(duplicate_string, str, length); + + // NOTE(jeff): Add null-termination of the new string + duplicate_string[length] = '\0'; + + return duplicate_string; +} + +const char* duplicate_string(const std::string& str, nom::size_type length) +{ + return nom::duplicate_string(str.c_str(), length); +} + +void free_string(const char* ptr) +{ + auto str = NOM_CCAST(char*, ptr); + std::free(str); +} + +const char* to_lowercase_cstr(const char* str, nom::size_type len) +{ + auto result = NOM_CCAST(char*, nom::create_string(str)); + + uint32 current_char = 0; + for(uint32 char_pos = 0; char_pos != len; ++char_pos) { + current_char = result[char_pos]; + result[char_pos] = tolower(current_char); + } + + return result; +} + +const char* to_lowercase_cstr(const char* str) +{ + auto str_len = nom::string_length(str); + auto result = + NOM_CCAST(char*, nom::to_lowercase_cstr(str, str_len)); + + return result; +} + +std::string to_lowercase_string(const std::string& str, nom::size_type len) +{ + auto result = str; + + uint32 current_char = 0; + for(uint32 char_pos = 0; char_pos != len; ++char_pos) { + current_char = result[char_pos]; + result[char_pos] = tolower(current_char); + } + + return result; +} + +std::string to_lowercase_string(const std::string& str) +{ + auto str_len = str.length(); + auto result = nom::to_lowercase_string(str, str_len); + + return result; +} + +const char* to_uppercase_cstr(const char* str, nom::size_type len) +{ + auto result = NOM_CCAST(char*, nom::create_string(str)); + + uint32 current_char = 0; + for(uint32 char_pos = 0; char_pos != len; ++char_pos) { + current_char = result[char_pos]; + result[char_pos] = toupper(current_char); + } + + return result; +} + +const char* to_uppercase_cstr(const char* str) +{ + auto str_len = nom::string_length(str); + auto result = nom::to_uppercase_cstr(str, str_len); + + return result; +} + +std::string to_uppercase_string(const std::string& str, nom::size_type len) +{ + auto result = str; + + uint32 current_char = 0; + for(uint32 char_pos = 0; char_pos != len; ++char_pos) { + current_char = result[char_pos]; + result[char_pos] = tolower(current_char); + } + + return result; +} + +std::string to_uppercase_string(const std::string& str) +{ + auto str_len = str.length(); + auto result = nom::to_uppercase_string(str, str_len); + + return result; +} + +} // namespace nom diff --git a/src/graphics/CMakeLists.txt b/src/graphics/CMakeLists.txt index 0d2673e0..16aa866a 100644 --- a/src/graphics/CMakeLists.txt +++ b/src/graphics/CMakeLists.txt @@ -37,8 +37,8 @@ if( NOM_BUILD_GRAPHICS_UNIT ) ${SRC_DIR}/graphics/Texture.cpp ${INC_DIR}/graphics/Texture.hpp - ${SRC_DIR}/graphics/VideoMode.cpp - ${INC_DIR}/graphics/VideoMode.hpp + ${SRC_DIR}/graphics/DisplayMode.cpp + ${INC_DIR}/graphics/DisplayMode.hpp ${SRC_DIR}/graphics/fonts/BMFont.cpp ${INC_DIR}/graphics/fonts/BMFont.hpp @@ -79,9 +79,6 @@ if( NOM_BUILD_GRAPHICS_UNIT ) ${SRC_DIR}/graphics/shapes/Rectangle.cpp ${INC_DIR}/graphics/shapes/Rectangle.hpp - ${SRC_DIR}/graphics/sprite/AnimatedSprite.cpp - ${INC_DIR}/graphics/sprite/AnimatedSprite.hpp - ${SRC_DIR}/graphics/sprite/Sprite.cpp ${INC_DIR}/graphics/sprite/Sprite.hpp diff --git a/src/audio/AL/Music.cpp b/src/graphics/DisplayMode.cpp similarity index 65% rename from src/audio/AL/Music.cpp rename to src/graphics/DisplayMode.cpp index aa7bd4a2..d37c7578 100644 --- a/src/audio/AL/Music.cpp +++ b/src/graphics/DisplayMode.cpp @@ -26,55 +26,51 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ -#include "nomlib/audio/AL/Music.hpp" +#include "nomlib/graphics/DisplayMode.hpp" // Private headers -#include "nomlib/audio/AL/OpenAL.hpp" - -// Forward declarations -#include "nomlib/audio/ISoundBuffer.hpp" +#include namespace nom { -Music::Music ( void ) +bool operator ==(const DisplayMode& lhs, const DisplayMode& rhs) { - NOM_LOG_TRACE( NOM_LOG_CATEGORY_TRACE_AUDIO ); - - // Initialize here + return ( lhs.bounds == rhs.bounds ) && + ( lhs.format == rhs.format ); } -Music::Music ( const ISoundBuffer& copy ) +bool operator !=(const DisplayMode& lhs, const DisplayMode& rhs) { - this->setBuffer ( copy ); + return ! ( lhs == rhs ); } -Music::~Music ( void ) +bool operator <(const DisplayMode& lhs, const DisplayMode& rhs) { - NOM_LOG_TRACE( NOM_LOG_CATEGORY_TRACE_AUDIO ); - - this->Stop(); -} - -void Music::setBuffer ( const ISoundBuffer& copy ) -{ - NOM_LOG_TRACE( NOM_LOG_CATEGORY_TRACE_AUDIO ); - -AL_CHECK_ERR ( alSourcei ( source_id, AL_BUFFER, copy.get() ) ); + if( lhs.format == rhs.format ) { + if( lhs.bounds.w == rhs.bounds.w ) { + return lhs.bounds.h < rhs.bounds.h; + } + else { + return lhs.bounds.w < rhs.bounds.w; + } + } else { + return lhs.format < rhs.format; + } } -void Music::Play ( void ) +bool operator >(const DisplayMode& lhs, const DisplayMode& rhs) { -AL_CHECK_ERR ( alSourcePlay ( source_id ) ); + return rhs < lhs; } -void Music::Stop ( void ) +bool operator <=(const DisplayMode& lhs, const DisplayMode& rhs) { -AL_CHECK_ERR ( alSourceStop ( source_id ) ); + return ! (rhs < lhs ); } -void Music::Pause ( void ) +bool operator >=(const DisplayMode& lhs, const DisplayMode& rhs) { -AL_CHECK_ERR ( alSourcePause ( source_id ) ); + return ! ( lhs < rhs ); } } // namespace nom diff --git a/src/graphics/Gradient.cpp b/src/graphics/Gradient.cpp index 6e85c53d..cb9fb723 100644 --- a/src/graphics/Gradient.cpp +++ b/src/graphics/Gradient.cpp @@ -28,6 +28,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ #include "nomlib/graphics/Gradient.hpp" +// Forward declarations +#include "nomlib/graphics/Texture.hpp" + namespace nom { Gradient::Gradient( void ) : @@ -35,6 +38,8 @@ Gradient::Gradient( void ) : gradient_( { Color4i::Blue, Color4i::Blue } ) // Opaque color to serve as // warning! { + this->texture_.reset( new Texture() ); + // No margins set. this->set_margins( Point2i( 0, 0 ) ); @@ -57,11 +62,11 @@ Gradient::Gradient ( Transformable( pos, size ), // Base class gradient_(colors) { + this->texture_.reset( new Texture() ); + this->set_margins( margin ); this->set_fill_direction( direction ); this->set_dithering( true ); - - this->update(); } Gradient::~Gradient( void ) @@ -79,6 +84,15 @@ ObjectTypeInfo Gradient::type( void ) const return NOM_OBJECT_TYPE_INFO( self_type ); } +Texture* Gradient::texture() const +{ + if( this->texture_ != nullptr ) { + return( new Texture(*this->texture_) ); + } else { + return nullptr; + } +} + bool Gradient::valid( void ) const { if ( this->position() != Point2i::null && this->size() != Size2i::null ) @@ -169,13 +183,9 @@ void Gradient::set_dithering( bool state ) void Gradient::draw( RenderTarget& target ) const { - if( this->texture_.valid() ) - { - // this->texture_.draw( target ); - this->texture_.draw( RenderWindow::context() ); + if( this->texture_ != nullptr && this->texture_->valid() == true ) { + this->texture_->draw(target); } - - NOM_ASSERT( RenderWindow::context() == target.renderer() ); } // Private scope @@ -215,7 +225,6 @@ void Gradient::update( void ) if( this->update_cache() == false ) { - NOM_LOG_ERR( NOM_LOG_CATEGORY_RENDER, "Could not update cache for gradient."); return; } } @@ -300,7 +309,7 @@ bool Gradient::update_cache() // Sanity check if( context == nullptr ) { - NOM_LOG_ERR( NOM_LOG_CATEGORY_RENDER, + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, "Could not update texture cache for the gradient:", "invalid renderer."); return false; @@ -309,72 +318,138 @@ bool Gradient::update_cache() // Obtain the optimal pixel format for the platform RendererInfo caps = context->caps(); - // Create the texture cache that will hold our gradient; since the dimensions - // are local to this object, we deal with translating relative coordinates to - // what will be the absolute coordinate space (rendering window) - if( this->texture_.initialize( caps.optimal_texture_format(), SDL_TEXTUREACCESS_TARGET, this->size().w, this->size().h ) == false ) - { - NOM_LOG_ERR( NOM_LOG_CATEGORY_RENDER, - "Could not initialize the texture cache for the gradient." ); - // NOM_LOG_ERR( NOM_LOG_CATEGORY_RENDER, SDL_GetError() ); + if( this->texture_ == nullptr ) { + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, + "Could not update texture cache for the gradient:", + "NULL texture."); return false; } + if( this->texture_->size() != this->size() ) { + + // Poor man's counter of how often we are re-allocating this texture + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_RENDER, NOM_LOG_PRIORITY_DEBUG); + NOM_LOG_DEBUG( NOM_LOG_CATEGORY_RENDER, + "old_size:", this->texture_->size(), + "new_size:", this->size() ); + + // Create the texture cache that will hold our gradient; since the + // dimensions are local to this object, we deal with translating relative + // coordinates to what will be the absolute coordinate space -- our + // output rendering window + if( this->texture_->initialize( caps.optimal_texture_format(), SDL_TEXTUREACCESS_TARGET, this->size() ) == false ) + { + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, + "Could not initialize the texture cache for the gradient." ); + return false; + } + } + // Local coordinates (relative) - this->texture_.set_bounds( IntRect( this->margins(), this->size() ) ); - this->texture_.set_position( this->position() + this->margins() ); + this->texture_->set_bounds( IntRect( this->margins(), this->size() ) ); + this->texture_->set_position( this->position() + this->margins() ); // Set the rendering target to the texture (uses FBO) - if( this->texture_.set_render_target(*context) == false ) { - NOM_LOG_ERR( NOM_LOG_CATEGORY_RENDER, - "Could not set the rendering target to the texture cache." ); - // NOM_LOG_ERR( NOM_LOG_CATEGORY_RENDER, SDL_GetError() ); + if( context->set_render_target( this->texture_.get() ) == false ) { + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, + "Could not update texture cache: failed to set rendering" + "target" ); return false; } // Debugging aid; red background indicates something went wrong - if( context->set_color(Color4i::Red) == false ) { - // NOM_LOG_ERR( NOM_LOG_CATEGORY_RENDER, SDL_GetError() ); - return false; - } - - // Ensure that the texture surface is cleared, otherwise we can get artifacts - // leftover from whatever the previous state was set to. - if( context->clear() == false ) { - // NOM_LOG_ERR( NOM_LOG_CATEGORY_RENDER, SDL_GetError() ); - return false; + if( context->fill(Color4i::Red) == false ) { } // Relative to absolute transformations Point2i relative_pos; for( auto itr = this->rectangles_.begin(); itr != this->rectangles_.end(); ++itr ) { - if( context->set_color( Color4i( itr->fill_color().r, itr->fill_color().g, itr->fill_color().b, itr->fill_color().a ) ) == false ) { - // NOM_LOG_ERR( NOM_LOG_CATEGORY_RENDER, SDL_GetError() ); - return false; - } - // NOM_DUMP( (*itr).fill_color() ); - relative_pos = ( (*itr).position() - this->position() ) + this->margins(); - // NOM_DUMP(relative_pos); itr->set_position(relative_pos); itr->draw(*context); - // itr->set_size( itr->size() ); } // Reset the rendering target now that we are done using it. - if( context->reset_render_target() == false ) - { - NOM_LOG_ERR( NOM_LOG_CATEGORY_RENDER, "Could not reset the rendering target." ); - // NOM_LOG_ERR( NOM_LOG_CATEGORY_RENDER, SDL_GetError() ); + if( context->reset_render_target() == false ) { + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, + "Could not update texture cache:", + "failed to reset the rendering target." ); return false; } - NOM_ASSERT( this->texture_.position() == this->position() + this->margins() ); - NOM_ASSERT( this->texture_.bounds() == IntRect( this->margins(), this->size() ) ); + NOM_ASSERT( this->texture_->position() == this->position() + this->margins() ); + NOM_ASSERT( this->texture_->bounds() == IntRect( this->margins(), this->size() ) ); return true; } +// Broken +// Texture* Gradient::clone_texture() const +// { +// Texture* texture = new Texture(); +// NOM_ASSERT(texture != nullptr); + +// RenderWindow* context = nom::render_interface(); +// NOM_ASSERT(context != nullptr); + +// // Sanity check +// if( context == nullptr ) { +// NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, +// "Could not update texture cache for the gradient:", +// "invalid renderer."); +// NOM_DELETE_PTR(texture); +// return nullptr; +// } + +// // Obtain the optimal pixel format for the platform +// RendererInfo caps = context->caps(); + +// // Create the texture cache that will hold our gradient; since the +// // dimensions are local to this object, we deal with translating relative +// // coordinates to what will be the absolute coordinate space -- our +// // output rendering window +// if( texture->initialize( caps.optimal_texture_format(), SDL_TEXTUREACCESS_TARGET, this->size() ) == false ) +// { +// NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, +// "Could not initialize the texture cache for the gradient." ); +// NOM_DELETE_PTR(texture); +// return nullptr; +// } + +// // Local coordinates (relative) +// texture->set_bounds( IntRect( this->margins(), this->size() ) ); +// texture->set_position( this->position() + this->margins() ); + +// // Set the rendering target to the texture (uses FBO) +// if( context->set_render_target(texture) == false ) { +// NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, +// "Could not update texture cache: failed to set rendering" +// "target" ); +// NOM_DELETE_PTR(texture); +// return nullptr; +// } + +// // Debugging aid; red background indicates something went wrong +// if( context->fill(Color4i::Red) == false ) { +// } + +// this->draw(*context); + +// // Reset the rendering target now that we are done using it. +// if( context->reset_render_target() == false ) { +// NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, +// "Could not update texture cache:", +// "failed to reset the rendering target." ); +// NOM_DELETE_PTR(texture); +// return nullptr; +// } + +// NOM_ASSERT( texture->position() == this->position() + this->margins() ); +// NOM_ASSERT( texture->bounds() == IntRect( this->margins(), this->size() ) ); + +// return texture; +// } + } // namespace nom diff --git a/src/graphics/Image.cpp b/src/graphics/Image.cpp index 3b88c60f..93a18420 100644 --- a/src/graphics/Image.cpp +++ b/src/graphics/Image.cpp @@ -184,7 +184,7 @@ bool Image::initialize ( const Point2i& size ) return true; } -bool Image::create( const Point2i& size, uint32 pixel_format ) +bool Image::create(const Point2i& size, uint32 pixel_format) { int bpp = 0; // bits per pixel uint32 red_mask = 0; @@ -193,8 +193,10 @@ bool Image::create( const Point2i& size, uint32 pixel_format ) uint32 alpha_mask = 0; // Find the best surface format based on the requested pixel_format - if ( SDL_BOOL( SDL_PixelFormatEnumToMasks ( pixel_format, &bpp, &red_mask, &green_mask, &blue_mask, &alpha_mask ) ) != true ) - { + SDL_bool result = + SDL_PixelFormatEnumToMasks( pixel_format, &bpp, &red_mask, &green_mask, + &blue_mask, &alpha_mask ); + if( result != SDL_TRUE ) { NOM_LOG_ERR( NOM, SDL_GetError() ); return false; } @@ -281,11 +283,10 @@ const IntRect Image::bounds ( void ) const return IntRect(bounds.x, bounds.y, bounds.w, bounds.h); } -bool Image::must_lock ( void ) const +bool Image::must_lock() const { - if ( SDL_MUSTLOCK ( this->image() ) ) return true; - - return false; + bool result = SDL_MUSTLOCK( this->image() ); + return result; } bool Image::locked( void ) const @@ -427,12 +428,17 @@ bool Image::save_png ( const std::string& filename ) const return true; } -const Point2i Image::size ( void ) const +Size2i Image::size() const { + Size2i dims(Size2i::null); + SDL_Surface* buffer = this->image(); - Point2i image_pos ( buffer->w, buffer->h ); + if( buffer != nullptr ) { + dims.w = buffer->w; + dims.h = buffer->h; + } - return image_pos; + return dims; } const Color4i Image::colorkey ( void ) const @@ -481,35 +487,35 @@ const Point2i Image::position ( void ) const return this->position_; } -bool Image::set_colorkey ( const Color4i& colorkey, bool flag ) +bool Image::set_colorkey(const Color4i& colorkey, bool flag) { SDL_Surface* buffer = this->image(); - uint32 transparent_color = RGB ( colorkey, buffer->format ); + uint32 transparent_color = RGB(colorkey, buffer->format); - if ( this->valid() == false ) - { -NOM_LOG_ERR ( NOM, "Could not set color key: invalid image buffer." ); - priv::FreeSurface ( buffer ); + if( this->valid() == false ) { + NOM_LOG_ERR ( NOM, "Could not set color key: invalid image buffer." ); + priv::FreeSurface(buffer); return false; } - if ( SDL_SetColorKey ( buffer, SDL_BOOL(flag), transparent_color ) != 0 ) - { -NOM_LOG_ERR ( NOM, SDL_GetError() ); - priv::FreeSurface ( buffer ); + int result = + SDL_SetColorKey(buffer, (int32)flag, transparent_color); + if( result != 0 ) { + NOM_LOG_ERR( NOM, SDL_GetError() ); + priv::FreeSurface(buffer); return false; } return true; } -bool Image::RLE ( bool flag ) +bool Image::RLE(bool flag) { - if ( SDL_SetSurfaceRLE ( this->image(), SDL_BOOL(flag) ) != 0 ) - { -NOM_LOG_ERR ( NOM, SDL_GetError() ); + if( SDL_SetSurfaceRLE( this->image(), (int32)flag ) != 0 ) { + NOM_LOG_ERR ( NOM, SDL_GetError() ); return false; } + return true; } @@ -637,13 +643,12 @@ NOM_LOG_ERR ( NOM, SDL_GetError() ); return true; } -bool Image::lock ( void ) const +bool Image::lock() const { - if ( SDL_BOOL ( this->must_lock() ) ) - { - if ( SDL_LockSurface ( this->image() ) != 0 ) - { -NOM_LOG_ERR ( NOM, "Could not lock video surface memory." ); + if( this->must_lock() == true ) { + + if( SDL_LockSurface( this->image() ) != 0 ) { + NOM_LOG_ERR( NOM, "Could not lock video surface memory." ); return false; } } diff --git a/src/graphics/RenderWindow.cpp b/src/graphics/RenderWindow.cpp index 9d597332..40f17f63 100644 --- a/src/graphics/RenderWindow.cpp +++ b/src/graphics/RenderWindow.cpp @@ -27,7 +27,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ #include "nomlib/graphics/RenderWindow.hpp" -//#include "nomlib/graphics/Renderer.hpp" // Private headers #include @@ -39,9 +38,15 @@ void PixelsDeleter::operator()(void* ptr) std::free(ptr); } -// static initialization +// Static Initializations SDL_Renderer* RenderWindow::context_ = nullptr; +const Point2i RenderWindow::DEFAULT_WINDOW_POS = + Point2i(SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED); + +const Point2i RenderWindow::WINDOW_POS_CENTERED = + Point2i(SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED); + RenderWindow::RenderWindow( void ) : window_ { SDL_WINDOW::UniquePtr ( nullptr, priv::FreeWindow ) }, window_id_ ( 0 ), window_display_id_ ( -1 ), @@ -57,75 +62,75 @@ RenderWindow::~RenderWindow( void ) priv::FreeRenderTarget( context_ ); } -bool RenderWindow::create ( - const std::string& window_title, - int32 width, - int32 height, - uint32 window_flags, - int32 rendering_driver, - uint32 context_flags - ) -{ - this->window_.reset ( SDL_CreateWindow ( - window_title.c_str(), - SDL_WINDOWPOS_UNDEFINED, - SDL_WINDOWPOS_UNDEFINED, - width, - height, - window_flags - ) - ); - - if ( this->window_valid() == false ) - { - NOM_LOG_ERR( NOM, SDL_GetError() ); +bool +RenderWindow::create( const std::string& window_title, const Size2i& res, + uint32 window_flags, int rendering_driver, + uint32 renderer_flags ) +{ + return this->create( window_title, DEFAULT_WINDOW_POS, 0, res, window_flags, + rendering_driver, renderer_flags ); +} + +bool +RenderWindow::create( const std::string& window_title, const Point2i& pos, + int display_index, const Size2i& res, uint32 window_flags, + int rendering_driver, uint32 renderer_flags ) +{ + int window_pos_x = 0; + int window_pos_y = 0; + + if( SDL_WINDOWPOS_ISCENTERED(pos.x) || SDL_WINDOWPOS_ISCENTERED(pos.y) ) { + window_pos_x = SDL_WINDOWPOS_CENTERED_DISPLAY(display_index); + window_pos_y = SDL_WINDOWPOS_CENTERED_DISPLAY(display_index); + } + + if( SDL_WINDOWPOS_ISUNDEFINED(pos.x) || SDL_WINDOWPOS_ISUNDEFINED(pos.y) ) { + window_pos_x = SDL_WINDOWPOS_UNDEFINED_DISPLAY(display_index); + window_pos_y = SDL_WINDOWPOS_UNDEFINED_DISPLAY(display_index); + } + + auto render_window = SDL_CreateWindow( window_title.c_str(), window_pos_x, + window_pos_y, res.w, res.h, + window_flags ); + this->window_.reset(render_window); + + if( this->window_valid() == false ) { + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, + "Failed to create rendering window:", SDL_GetError() ); return false; } - Renderer::create ( this->window(), rendering_driver, context_flags ); + Renderer::create(this->window(), rendering_driver, renderer_flags); - if( this->renderer_valid() == false ) - { - NOM_LOG_ERR( NOM, SDL_GetError() ); + if( this->renderer_valid() == false ) { + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, + "Failed to initialize rendering driver:", SDL_GetError() ); return false; } // Track our unique identifiers for our brand spanking new window! - this->window_id_ = SDL_GetWindowID ( this->window() ); - this->window_display_id_ = SDL_GetWindowDisplayIndex ( this->window() ); + this->window_id_ = SDL_GetWindowID( this->window() ); + this->window_display_id_ = SDL_GetWindowDisplayIndex( this->window() ); this->enabled_ = true; - // You must *always* have an active, valid rendering context. An invalid - // rendering context will break the vast majority of graphics operations! + // We must **always** have a valid rendering context. Without this, you can + // kiss pretty much everything but SDL1-era functionality goodbye! // - // By default, we use the rendering context that is created at the - // initialization of this window object (see above). - // - // Note that the same rendering context that was used during the creation of - // a resource *must* stay around for as long as the resource(s) are using - // said rendering context. + // NOTE:Resource data, i.e.: nom::Texture && friends, are dependent upon the + // rendering context that is active during the creation of said resource. + // Therefore, the context **must** remain valid for the lifetime of the + // resource. this->make_current(); - // Try to ensure that we have no leftover artifacts by clearing and filling - // window with a solid black paint bucket fill. - this->fill ( Color4i::Black ); + // Clearing the window to a known value Helps keep rendering artifacts + // (garbage) away. + this->fill(Color4i::Transparent); nom::set_render_interface(*this); return true; } -bool RenderWindow::create ( - const std::string& window_title, - const Size2i& res, - uint32 window_flags, - int32 rendering_driver, - uint32 context_flags - ) -{ - return this->create( window_title, res.w, res.h, window_flags, rendering_driver, context_flags ); -} - RenderWindow::RawPtr RenderWindow::get ( void ) { return this; @@ -161,11 +166,10 @@ bool RenderWindow::window_valid( void ) const return false; } -Point2i RenderWindow::position ( void ) const +Point2i RenderWindow::position() const { Point2i pos; - - SDL_GetWindowPosition ( this->window(), &pos.x, &pos.y ); + SDL_GetWindowPosition(this->window(), &pos.x, &pos.y); return pos; } @@ -213,36 +217,58 @@ const IntRect RenderWindow::display_bounds ( void ) const return bounds; } -VideoModeList RenderWindow::getVideoModes ( void ) const +bool RenderWindow::display_modes(DisplayModeList& modes) const { -/* - VideoModeList modes; - SDL_Rect** mode; + int display_mode_count = 0; + int display_id = this->window_display_id(); + SDL_DisplayMode mode = {}; - mode = SDL_ListModes ( nullptr, SDL_FULLSCREEN ); - - if ( mode == nullptr ) - { -NOM_LOG_INFO ( NOM, "Any video mode is supported." ); // FIXME? - return modes; - } - else if ( mode == ( SDL_Rect**) - 1 ) - { -NOM_LOG_INFO ( NOM, "No video modes are supported." ); - return modes; + // Get the number of display modes available for this window + display_mode_count = SDL_GetNumDisplayModes(display_id); + if( display_mode_count < 1 ) { + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, + "Could not enumerate window's display modes:", + SDL_GetError() ); + return false; } - else - { - for ( int32 idx = 0; mode[idx]; idx++ ) - { - modes.push_back ( VideoMode ( mode[idx]->w, mode[idx]->h, this->getDisplayColorBits() ) ); + + // Enumerate through the list of video modes for this window + for( auto idx = 0; idx != display_mode_count; ++idx ) { + + if( SDL_GetDisplayMode(this->window_display_id(), idx, &mode) != 0 ) { + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, + "Could not enumerate window's display modes:", + SDL_GetError() ); + return false; } - std::sort ( modes.begin(), modes.end(), std::greater() ); + // Construct a nom::DisplayMode object from the data in the + // SDL_DisplayMode struct + DisplayMode video_mode; + video_mode.format = mode.format; + video_mode.bounds.w = mode.w; + video_mode.bounds.h = mode.h; + video_mode.refresh_rate = mode.refresh_rate; + + modes.push_back(video_mode); + } + + return true; +} + +int RenderWindow::refresh_rate() const +{ + int window_display_id = this->window_display_id(); + SDL_DisplayMode current_mode = {}; + + if( SDL_GetCurrentDisplayMode(window_display_id, ¤t_mode) != 0 ) { + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, + "Could not get display video mode for the window:", + SDL_GetError() ); + return -1; } - return modes; -*/ - return VideoModeList(); + + return current_mode.refresh_rate; } bool RenderWindow::flip ( void ) const @@ -338,9 +364,9 @@ void RenderWindow::set_size ( int32 width, int32 height ) SDL_SetWindowSize ( this->window(), width, height ); } -void RenderWindow::set_position ( int32 x, int32 y ) +void RenderWindow::set_position(const Point2i& window_pos) { - SDL_SetWindowPosition ( this->window(), x, y ); + SDL_SetWindowPosition(this->window(), window_pos.x, window_pos.y); } uint32 RenderWindow::window_id( void ) const @@ -358,11 +384,30 @@ SDL_Window* RenderWindow::mouse_focus( void ) const return SDL_GetMouseFocus(); } -int RenderWindow::window_display_id ( void ) const +int RenderWindow::window_display_id() const { return this->window_display_id_; } +// static +std::string RenderWindow::display_name(int display_id) +{ + const char* result_str = nullptr; + std::string result = "\0"; + + result_str = SDL_GetDisplayName(display_id); + if( result_str != nullptr ) { + result = result_str; + } + + return result; +} + +std::string RenderWindow::display_name() const +{ + return this->display_name(this->window_display_id_); +} + void RenderWindow::disable_screensaver ( void ) { SDL_DisableScreenSaver(); @@ -403,9 +448,9 @@ void RenderWindow::hide_window ( void ) SDL_HideWindow ( this->window() ); } -void RenderWindow::set_window_grab ( bool grab ) +void RenderWindow::set_window_grab(bool grab) { - SDL_SetWindowGrab ( this->window(), SDL_BOOL(grab) ); + SDL_SetWindowGrab( this->window(), (SDL_bool)grab ); } void RenderWindow::set_minimum_window_size ( int min_width, int min_height ) @@ -418,7 +463,7 @@ void RenderWindow::set_maximum_window_size ( int max_width, int max_height ) SDL_SetWindowMaximumSize ( this->window(), max_width, max_height ); } -bool RenderWindow::save_png_file( const std::string& filename ) const +bool RenderWindow::save_png_file(const std::string& filename) const { int bpp = 0; // bits per pixel uint32 red_mask = 0; @@ -433,8 +478,10 @@ bool RenderWindow::save_png_file( const std::string& filename ) const // Width & height of target in pixels Size2i renderer_size = Renderer::output_size(); - if ( SDL_BOOL( SDL_PixelFormatEnumToMasks ( caps.optimal_texture_format(), &bpp, &red_mask, &green_mask, &blue_mask, &alpha_mask ) ) != true ) - { + SDL_bool result = + SDL_PixelFormatEnumToMasks( caps.optimal_texture_format(), &bpp, &red_mask, + &green_mask, &blue_mask, &alpha_mask ); + if( result != SDL_TRUE ) { NOM_LOG_ERR( NOM, SDL_GetError() ); return false; } @@ -458,7 +505,7 @@ bool RenderWindow::save_png_file( const std::string& filename ) const return true; } -bool RenderWindow::save_screenshot( const std::string& filename ) const +bool RenderWindow::save_screenshot(const std::string& filename) const { RendererInfo caps = this->caps(); Image screenshot; @@ -478,8 +525,10 @@ bool RenderWindow::save_screenshot( const std::string& filename ) const uint32 blue_mask = 0; uint32 alpha_mask = 0; - if ( SDL_BOOL( SDL_PixelFormatEnumToMasks ( caps.optimal_texture_format(), &bpp, &red_mask, &green_mask, &blue_mask, &alpha_mask ) ) != true ) - { + SDL_bool result = + SDL_PixelFormatEnumToMasks( caps.optimal_texture_format(), &bpp, &red_mask, + &green_mask, &blue_mask, &alpha_mask ); + if( result != SDL_TRUE ) { NOM_LOG_ERR( NOM, SDL_GetError() ); return false; } @@ -527,6 +576,11 @@ void RenderWindow::set_context ( RenderWindow::RawPtr window ) context_ = window->renderer(); } +int RenderWindow::num_video_displays() +{ + return SDL_GetNumVideoDisplays(); +} + namespace priv { // Static initializations diff --git a/src/graphics/Renderer.cpp b/src/graphics/Renderer.cpp index f7400dcc..fcabe868 100644 --- a/src/graphics/Renderer.cpp +++ b/src/graphics/Renderer.cpp @@ -28,6 +28,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ #include "nomlib/graphics/Renderer.hpp" +// Forward declarations +#include "nomlib/graphics/Texture.hpp" + // Private headers #include @@ -46,18 +49,17 @@ Renderer::~Renderer ( void ) // Thanks for all the fish! } -bool Renderer::create ( SDL_WINDOW::RawPtr window, int32 rendering_driver, uint32 context_flags ) +bool +Renderer::create( SDL_WINDOW::RawPtr window, int rendering_driver, + uint32 renderer_flags ) { // NOM_LOG_TRACE( NOM ); - this->renderer_.reset( SDL_CreateRenderer( window, rendering_driver, context_flags ) ); - - if( this->renderer_valid() == false ) - { - return false; - } + auto render_driver = + SDL_CreateRenderer(window, rendering_driver, renderer_flags); + this->renderer_.reset(render_driver); - return true; + return this->renderer_valid(); } SDL_Renderer* Renderer::renderer ( void ) const @@ -131,13 +133,13 @@ Size2i Renderer::output_size( void ) const return size; } -const IntRect Renderer::bounds ( void ) const +IntRect Renderer::clip_bounds() const { SDL_Rect clip; - SDL_RenderGetClipRect( this->renderer(), &clip ); + SDL_RenderGetClipRect(this->renderer(), &clip); - return IntRect ( clip.x, clip.y, clip.w, clip.h ); + return IntRect(clip.x, clip.y, clip.w, clip.h); } const RendererInfo Renderer::caps ( void ) const @@ -159,7 +161,7 @@ const RendererInfo Renderer::caps ( SDL_Renderer* target ) renderer_info.name_ = info.name; renderer_info.flags_ = info.flags; - NOM_ASSERT ( info.num_texture_formats > 1 ); + NOM_ASSERT (info.num_texture_formats >= 1); for ( uint32 idx = 0; idx < info.num_texture_formats; ++idx ) { @@ -172,26 +174,41 @@ const RendererInfo Renderer::caps ( SDL_Renderer* target ) return renderer_info; } -bool Renderer::reset_render_target() const +bool Renderer::set_render_target(const Texture* texture) const { + SDL_Renderer* renderer = this->renderer(); + NOM_ASSERT(renderer != nullptr); + + SDL_Texture* target = nullptr; + if( texture != nullptr ) { + target = texture->texture(); + } + RendererInfo caps = this->caps(); - // Ensure that the rendering device supports FBO + // Check to see if the rendering hardware supports FBO if( caps.target_texture() == false ) { NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, - "Video hardware does not support render to texture" ); + "Failed to set rendering target:", + "the hardware does not support the operation." ); return false; } // Try to honor the request; render to the source texture - if( SDL_SetRenderTarget(this->renderer(), nullptr) != 0 ) { - NOM_LOG_ERR ( NOM_LOG_CATEGORY_APPLICATION, SDL_GetError() ); + if( SDL_SetRenderTarget(renderer, target) != 0 ) { + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, + "Failed to set rendering target:", SDL_GetError() ); return false; } return true; } +bool Renderer::reset_render_target() const +{ + return this->set_render_target(nullptr); +} + void Renderer::update ( void ) const { SDL_RenderPresent ( this->renderer() ); @@ -295,24 +312,19 @@ NOM_LOG_ERR ( NOM, SDL_GetError() ); return true; } -bool Renderer::set_bounds ( const IntRect& bounds ) +bool Renderer::set_clip_bounds(const IntRect& bounds) { - if ( bounds == IntRect::null ) // Disable clipping bounds rectangle - { - if ( SDL_RenderSetClipRect( this->renderer(), nullptr ) != 0 ) - { -NOM_LOG_ERR ( NOM, SDL_GetError() ); - return false; - } + SDL_Rect* clip = nullptr; - return true; + if( bounds != IntRect::null ) { + clip = new SDL_Rect( SDL_RECT(bounds) ); + } else { + // Disable clipping bounds } - // IntRect argument is not null -- try setting the requested bounds - SDL_Rect clip = SDL_RECT ( bounds ); - if ( SDL_RenderSetClipRect( this->renderer(), &clip ) != 0 ) - { -NOM_LOG_ERR ( NOM, SDL_GetError() ); + if( SDL_RenderSetClipRect(this->renderer(), clip) != 0 ) { + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, + "Failed to set clipping bounds:", SDL_GetError() ); return false; } diff --git a/src/graphics/Text.cpp b/src/graphics/Text.cpp index bad1527c..3efcadd1 100644 --- a/src/graphics/Text.cpp +++ b/src/graphics/Text.cpp @@ -34,11 +34,14 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // Private headers #include "nomlib/graphics/fonts/Glyph.hpp" +#include "nomlib/graphics/shapes/Rectangle.hpp" + +// #define NOM_OLD_TEXT_RENDER namespace nom { Text::Text( void ) : - Transformable { Point2i::null, Size2i::null }, // Base class + Transformable(Point2i::zero, Size2i::null), // Base class text_size_ ( nom::DEFAULT_FONT_SIZE ), color_ ( Color4i::White ), style_ ( Text::Style::Normal ), @@ -58,7 +61,7 @@ Text::Text ( uint character_size, // Default parameter const Color4i& text_color // Default parameter ) : - Transformable { Point2i::null, Size2i::null }, // Base class + Transformable(Point2i::zero, Size2i::null), // Base class text_ ( text ), text_size_( character_size ), style_( Text::Style::Normal ), @@ -76,7 +79,7 @@ Text::Text ( uint character_size, // Default parameter const Color4i& text_color // Default parameter ) : - Transformable { Point2i::null, Size2i::null }, // Base class + Transformable(Point2i::zero, Size2i::null), // Base class text_ ( text ), text_size_( character_size ), style_( Text::Style::Normal ), @@ -88,9 +91,57 @@ Text::Text ( this->set_color( text_color ); } -IDrawable::raw_ptr Text::clone( void ) const +Text::Text(const self_type& rhs) : + Transformable( rhs.position(), rhs.size() ), + font_(rhs.font_), + glyphs_texture_(rhs.glyphs_texture_), + text_(rhs.text_), + text_size_(rhs.text_size_), + color_(rhs.color_), + style_(rhs.style_), + dirty_(rhs.dirty_) +{ + // NOM_LOG_TRACE( NOM ); + + if( this->rendered_text_ != nullptr ) { + this->rendered_text_.reset( new Texture(*rhs.rendered_text_) ); + } +} + +Text::self_type& Text::operator =(const self_type& rhs) +{ + Transformable::set_position( rhs.position() ); + Transformable::set_size( rhs.size() ); + this->font_ = rhs.font_; + this->glyphs_texture_ = rhs.glyphs_texture_; + + if( this->rendered_text_ != nullptr ) { + this->rendered_text_.reset( new Texture(*rhs.rendered_text_) ); + } + + this->text_ = rhs.text_; + this->text_size_ = rhs.text_size_; + this->color_ = rhs.color_; + this->style_ = rhs.style_; + this->dirty_ = rhs.dirty_; + + return *this; +} + +void Text::set_position(const Point2i& pos) +{ + Transformable::set_position(pos); + + if( this->rendered_text_ != nullptr && + this->rendered_text_->valid() == true ) + { + this->rendered_text_->set_position( this->position() ); + } +} + +Text* Text::clone() const { - return Text::raw_ptr( new Text( *this ) ); + return( new self_type(*this) ); } ObjectTypeInfo Text::type( void ) const @@ -103,9 +154,78 @@ const Font& Text::font() const return this->font_; } -const Texture& Text::texture ( void ) const +// NOTE: It is necessary to always return a new Texture instance because the +// stored texture may be reallocated at any time, i.e.: glyph rebuild from +// point size modification -- leaving the end-user with an invalid texture! +Texture* Text::clone_texture() const { - return this->texture_; + Texture* texture = new Texture(); + NOM_ASSERT(texture != nullptr); + + // Our cached texture dimensions should always be the same as the rendered + // text's dimensions; if the rendered text appears cut off, the calculated + // text width or height is likely to blame! + Size2i texture_dims(Size2i::zero); + texture_dims = this->size(); + // Padding + // texture_dims.w += this->text_width(" "); + + RenderWindow* context = nom::render_interface(); + NOM_ASSERT(context != nullptr); + if( context == nullptr ) { + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, "Could not update cache", + "invalid renderer." ); + NOM_DELETE_PTR(texture); + return texture; + } + + // Obtain the optimal pixel format for the platform + RendererInfo caps = context->caps(); + + if( texture->initialize( caps.optimal_texture_format(), + SDL_TEXTUREACCESS_TARGET, texture_dims ) == false ) + { + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, + "Could not update cache: failed texture creation." ); + NOM_DELETE_PTR(texture); + return texture; + } + + // Use an alpha channel; otherwise the text is rendered on a black + // background! + texture->set_blend_mode(SDL_BLENDMODE_BLEND); + + // Set the destination (screen) positioning of the rendered text + texture->set_position( this->position() ); + + if( context->set_render_target(texture) == false ) { + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, + "Could not update cache: failed to set the rendering target." ); + NOM_DELETE_PTR(texture); + return texture; + } + + // Clear the rendering backdrop color to be fully transparent; this preserves + // any existing alpha channel data from the rendered text + if( context->fill(Color4i::Transparent) == false ) { + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, + "Could not update cache:", + "failed to set the render target's color." ); + NOM_DELETE_PTR(texture); + return texture; + } + + this->render_text(*context); + + if( context->reset_render_target() == false ) { + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, + "Could not update cache:", + "failed to reset the rendering target." ); + NOM_DELETE_PTR(texture); + return texture; + } + + return texture; } bool Text::valid ( void ) const @@ -123,58 +243,98 @@ bool Text::valid ( void ) const // return IFont::FontType::NotDefined; // } -sint Text::text_width ( const std::string& text_string ) const +int Text::text_width(const std::string& text_buffer) const { - sint text_width = 0; - uint32 previous_char = 0; // Kerning calculation - int kerning_offset = 0; - std::string text_buffer = text_string; + int text_width = 0; + int max_text_width = 0; // Ensure that our font pointer is still valid - if ( this->valid() == false ) - { + if( this->valid() == false ) { NOM_LOG_ERR( NOM, "Invalid font for width calculation" ); return text_width; } - // Calculate text width up to either: a) end of string; b) newline character. - // - // We add kerning offsets, the glyph's advance offsets, and spacing onto the - // total text_width count. - for( auto pos = 0; pos < text_buffer.length(); ++pos ) - { - // Apply kerning offset + for( auto pos = 0; pos < text_buffer.length(); ++pos ) { + uint32 current_char = text_buffer[pos]; - kerning_offset = this->font()->kerning( previous_char, current_char, this->text_size() ); + if( current_char == '\n' ) { - if( kerning_offset != nom::int_min ) { - text_width += kerning_offset; + max_text_width = + std::max( max_text_width, this->multiline_width(text_buffer, pos) ); + } else { + + // Printable ASCII glyph; 33..127 + text_width = + std::max(text_width, this->multiline_width(text_buffer, pos) ); } + } // end for loop - previous_char = current_char; + return( std::max(text_width, max_text_width) ); +} - if ( current_char == ' ' ) // ASCII space glyph (32) - { - text_width += this->font()->spacing ( this->text_size() ); - } - else if ( current_char == '\t' ) // Tab character (we indent two spaces) - { - text_width += this->font()->spacing ( this->text_size() ) * 2; - } - else if( current_char == '\n' ) - { - break; +// Private call +int +Text::multiline_width(const std::string& text_buffer, nom::size_type pos) const +{ + int text_width = 0; + int max_text_width = 0; + uint32 previous_kerning_char = 0; + int kerning_offset = 0; + nom::size_type text_buffer_end = text_buffer.length(); + + nom::size_type next_newline = + text_buffer.find('\n', pos + 1); + + if( next_newline != std::string::npos ) { + text_buffer_end = next_newline; + } else { + // Use default initialized result + } + + for( auto character_pos = pos; + character_pos != text_buffer_end; + ++character_pos ) + { + uint32 current_char = text_buffer[character_pos]; + + kerning_offset = + this->font()->kerning( previous_kerning_char, current_char, + this->text_size() ); + + if( kerning_offset != nom::NOM_INT_MIN ) { + text_width += kerning_offset; } - else // Printable ASCII glyph (33..127) - { - // Match the offset calculations done in the text rendering -- hence the - // plus one (+1) spacing. - text_width += this->font()->glyph(current_char, this->text_size() ).advance + 1; + + previous_kerning_char = current_char; + + if( current_char == ' ' ) { + + // ASCII 32; space glyph + text_width += + this->font()->spacing( this->text_size() ); + + } else if( current_char == '\t' ) { + + // Tab character; indent two space glyphs + text_width += + this->font()->spacing( this->text_size() ) * 2; + + } else if( current_char == '\n' ) { + // Do nothing + } else { + + text_width += + this->font()->glyph(current_char, this->text_size() ).advance + 1; } - } // end for loop - return text_width; + max_text_width = std::max(text_width, max_text_width); + } + + // Padding for rendered_text_ + // max_text_width += this->font()->spacing( this->text_size() ); + + return max_text_width; } sint Text::text_height ( const std::string& text_string ) const @@ -257,6 +417,11 @@ void Text::set_text( const std::string& text ) void Text::set_text_size ( uint character_size ) { + RenderWindow* context = nom::render_interface(); + + // Default pixel format used when context is NULL + uint32 pixel_format = SDL_PIXELFORMAT_ARGB8888; + if ( this->valid() == false ) { NOM_LOG_ERR( NOM, "Could not set text size: the font is invalid." ); @@ -268,14 +433,31 @@ void Text::set_text_size ( uint character_size ) // Force a rebuild of the texture atlas at the specified point size this->font()->set_point_size( this->text_size() ); + if( context != nullptr ) { + RendererInfo caps = context->caps(); + pixel_format = caps.optimal_texture_format(); + } + // We can only (sanely) initialize our texture once we know the text size; we // will update this texture only as it becomes necessary (see ::update). - if( this->texture_.create( *this->font()->image( this->text_size() ), SDL_PIXELFORMAT_ARGB8888, Texture::Access::Streaming ) == false ) { - NOM_LOG_ERR( NOM, "Could not create texture at point size:", + if( this->glyphs_texture_.create( *this->font()->image( this->text_size() ), pixel_format, Texture::Access::Streaming ) == false ) { + NOM_LOG_ERR( NOM, "Could not create texture atlas at point size:", std::to_string( this->text_size() ) ); return; } + if( this->rendered_text_ == nullptr ) { + this->rendered_text_.reset( new Texture() ); + } + + NOM_ASSERT(this->rendered_text_ != nullptr); + if( this->rendered_text_ == nullptr ) { + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, + "Could not create texture cache:", + "failed to allocate texture memory." ); + return; + } + this->dirty_ = true; this->update(); } @@ -307,16 +489,46 @@ void Text::set_style( uint32 style ) this->update(); } -void Text::draw ( RenderTarget& target ) const +void Text::set_text_kerning(bool state) +{ + this->font()->set_font_kerning(state); + + this->dirty_ = true; + this->update(); +} + +void Text::draw(RenderTarget& target) const +{ + +#if defined(NOM_OLD_TEXT_RENDER) + if( this->valid() == true ) { + this->render_text(target); + } +#else + if( this->rendered_text_ != nullptr && + this->rendered_text_->valid() == true ) + { + this->rendered_text_->draw(target); + } +#endif +} + +// Private scope + +void Text::render_text(RenderTarget& target) const { int kerning_offset = 0; uint32 previous_char = 0; uint32 current_char = 0; - // Use coordinates provided by interface user as our starting origin - // coordinates to compute from + // Our position should always start from zero, when rendering text to a + // rendering target (glyphs_texture_ -> rendered_text_), so that updating + // text positions only effect the rendered text. +#if defined(NOM_OLD_TEXT_RENDER) Point2i pos( this->position() ); - +#else + Point2i pos(Point2i::zero); +#endif // Additional rendering offsets; applicable to nom::BMFont Point2i glyph_offset(Point2i::zero); @@ -345,7 +557,7 @@ void Text::draw ( RenderTarget& target ) const current_char = text_buffer[i]; kerning_offset = this->font()->kerning( previous_char, current_char, this->text_size() ); - if( kerning_offset != nom::int_min ) { + if( kerning_offset != nom::NOM_INT_MIN ) { pos.x += kerning_offset; } @@ -371,19 +583,40 @@ void Text::draw ( RenderTarget& target ) const } else // The time to render is now! { + IntRect glyph_bounds = + this->font()->glyph( current_char, this->text_size() ).bounds; + + Size2i tex_dims; + tex_dims.w = glyph_bounds.w; + tex_dims.h = glyph_bounds.h; + // Apply rendering offsets; applicable to nom::BMFont glyphs - tex_pos = Point2i(pos.x + glyph_offset.x, pos.y + glyph_offset.y); + tex_pos = + Point2i(pos.x + glyph_offset.x, pos.y + glyph_offset.y); + this->glyphs_texture_.set_position(tex_pos); - this->texture_.set_position(tex_pos); - this->texture_.set_bounds ( this->font()->glyph(current_char, this->text_size() ).bounds ); + this->glyphs_texture_.set_bounds(glyph_bounds); + this->glyphs_texture_.set_size(tex_dims); - this->texture_.draw ( target.renderer(), angle ); + this->glyphs_texture_.draw(target.renderer(), angle); // Move over the width of the character with one pixel of padding pos.x += ( this->font()->glyph(current_char, this->text_size() ).advance ) + 1; } // end else } // end for loop + +// Debugging overlay to show the bounds of the overall text rendering +#if 0 + IntRect text_overlay; + text_overlay.x = this->position().x; + text_overlay.y = this->position().y; + text_overlay.w = this->size().w; + text_overlay.h = this->size().h; + Color4i text_overlay_color = Color4i(151, 161, 225, 128); + Rectangle text_bounds(text_overlay, text_overlay_color); + text_bounds.draw(target); +#endif } void Text::update() @@ -428,31 +661,48 @@ void Text::update() // Update the texture atlas; this is necessary anytime we rebuild the font's // glyphs cache, such as when we change the text's font point size or rendering // style. + // This is an expensive call. const Image* source = this->font()->image( this->text_size() ); + NOM_ASSERT(source != nullptr); if( source->valid() == false ) { - NOM_LOG_ERR( NOM, - "Could not update texture: invalid glyph image source." ); + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, + "Could not update glyphs texture: invalid image source." ); return; } - this->texture_.lock(); - this->texture_.copy_pixels( source->pixels(), - source->pitch() * source->height() ); - this->texture_.unlock(); + // Expensive call + if( glyphs_texture_.lock() == true ) { + glyphs_texture_.copy_pixels( source->pixels(), source->pitch() * source->height() ); + glyphs_texture_.unlock(); + } else { + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, + "Could not update glyphs texture: could not lock texture." ); + return; + } - // Preserve alpha channel - this->texture_.set_blend_mode(SDL_BLENDMODE_BLEND); + // NOTE: By **not** preserving the alpha channel here, we introduce a bug that + // can clip off the edges of certain glyphs when using kerning -- as can be + // seen in the letter 'A' of the 'Kerning' tests of BMFont and TrueTypeFont. + // + // The downside to blending is that we lose the original pixel data around the + // edges of text; the input color gets blended in with the destination color. + this->glyphs_texture_.set_blend_mode(SDL_BLENDMODE_BLEND); // Update the font's text color. - this->texture_.set_color_modulation( this->color() ); + this->glyphs_texture_.set_color_modulation( this->color() ); // Set the overall size of this text label to the width & height of the text, - // with consideration to the specific font in use. + // with consideration to the font. this->set_size( Size2i( this->width(), this->height() ) ); -} -// Private scope +#if ! defined(NOM_OLD_TEXT_RENDER) + if( this->size().w > 0 && this->size().h > 0 ) { + // Expensive call + this->update_cache(); + } +#endif +} int Text::width() const { @@ -464,4 +714,76 @@ int Text::height() const return this->text_height( this->text() ); } +bool Text::update_cache() +{ + // Our cached texture dimensions should always be the same as the rendered + // text's dimensions; if the rendered text appears cut off, the calculated + // text width or height is likely to blame! + Size2i texture_dims(Size2i::zero); + texture_dims = this->size(); + // Padding + // texture_dims.w += this->text_width(" "); + + RenderWindow* context = nom::render_interface(); + NOM_ASSERT(context != nullptr); + if( context == nullptr ) { + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, "Could not update cache", + "invalid renderer." ); + return false; + } + + // Obtain the optimal pixel format for the platform + RendererInfo caps = context->caps(); + + if( this->rendered_text_->size() != texture_dims ) { + + // Poor man's counter of how often we are re-allocating this texture + // NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_RENDER, NOM_LOG_PRIORITY_DEBUG); + // NOM_LOG_DEBUG( NOM_LOG_CATEGORY_RENDER, + // "old_size:", this->rendered_text_->size(), + // "new_size:", texture_dims ); + + if( this->rendered_text_->initialize( caps.optimal_texture_format(), + SDL_TEXTUREACCESS_TARGET, texture_dims ) == false ) + { + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, + "Could not update cache: failed texture creation." ); + return false; + } + } + + // Use an alpha channel; otherwise the text is rendered on a black + // background! + this->rendered_text_->set_blend_mode(SDL_BLENDMODE_BLEND); + + // Set the destination (screen) positioning of the rendered text + this->rendered_text_->set_position( this->position() ); + + if( context->set_render_target( this->rendered_text_.get() ) == false ) { + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, + "Could not update cache: render targets not supported." ); + return false; + } + + // Clear the rendering backdrop color to be fully transparent; this preserves + // any existing alpha channel data from the rendered text + if( context->fill(Color4i::Transparent) == false ) { + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, + "Could not update cache:", + "failed to set the render target's color." ); + return false; + } + + this->render_text(*context); + + if( context->reset_render_target() == false ) { + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, + "Could not update cache:", + "failed to reset the rendering target." ); + return false; + } + + return true; +} + } // namespace nom diff --git a/src/graphics/Texture.cpp b/src/graphics/Texture.cpp index c1636f43..244c200b 100644 --- a/src/graphics/Texture.cpp +++ b/src/graphics/Texture.cpp @@ -50,10 +50,9 @@ Texture::Texture( void ) : texture_ { nullptr, priv::FreeTexture }, pixels_ ( nullptr ), pitch_ ( 0 ), - position_ ( 0, 0 ), - bounds_ ( 0, 0, -1, -1 ), - // TODO: use this for the texture size - // size_( Size2i::zero ), + position_(Point2i::zero), + size_(Size2i::zero), + bounds_(0, 0, -1, -1), colorkey_ { Color4i::Black }, scale_factor_(1) { @@ -63,42 +62,6 @@ Texture::Texture( void ) : Texture::~Texture( void ) { NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_RENDER, nom::LogPriority::NOM_LOG_PRIORITY_VERBOSE ); - - this->free_texture(); -} - -void Texture::free_texture( void ) -{ - // Ensure that the lock on the video memory is freed before destruction; - // this is done so I can have a bit more peace of mind that I don't forget to - // clean something up properly when bailing out of a method on err - // (i.e.: Texture::resize). - if( this->locked() ) - { - this->unlock(); - } - - // Free any saved pixels we may have stored in memory - NOM_DELETE_VOID_PTR( this->pixels_ ); - - // ...Goodbye cruel world! - NOM_DELETE_SMART_PTR( this->texture_ ); - - // Reset default initializations, in case we re-initialize the same texture: - this->pitch_ = 0; - - // FIXME: This should be Point2i::null - // this->position_ = Point2i( 0, 0 ); - - // FIXME: This should be IntRect::null - // this->bounds_ = IntRect( 0, 0, -1, -1 ); - - // this->size_ = Size2i::zero; - - // FIXME: This should be Color4i::null - // this->colorkey_ = Color4i::Black; - - this->set_scale_factor(1); } Texture::Texture ( const Texture& copy ) : @@ -106,9 +69,8 @@ Texture::Texture ( const Texture& copy ) : pixels_ { copy.pixels() }, pitch_ { copy.pitch() }, position_ { copy.position() }, - // TODO: use this for the texture size - // size_{ copy.size() }, - bounds_ { copy.bounds() }, + size_( copy.size() ), + bounds_( copy.bounds() ), colorkey_ { copy.colorkey() }, scale_factor_( copy.scale_factor() ) { @@ -121,8 +83,7 @@ Texture& Texture::operator = ( const Texture& other ) this->pixels_ = other.pixels(); this->pitch_ = other.pitch(); this->position_ = other.position(); - // TODO: use this for the texture size - // this->size_ = other.size(); + this->size_ = other.size(); this->bounds_ = other.bounds(); this->set_scale_factor( other.scale_factor() ); @@ -131,7 +92,7 @@ Texture& Texture::operator = ( const Texture& other ) Texture* Texture::clone() const { - return( new Texture ( *this ) ); + return( new self_type(*this) ); } bool Texture::initialize ( uint32 format, uint32 flags, int32 width, int32 height ) @@ -160,19 +121,40 @@ bool Texture::initialize ( uint32 format, uint32 flags, int32 width, int32 heigh this->texture_.reset ( SDL_CreateTexture ( context, format, flags, width, height ), priv::FreeTexture ); - if ( this->valid() == false ) - { - NOM_LOG_ERR ( NOM, SDL_GetError() ); + if( this->valid() == false ) { + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, + "Failed to initialize valid texture: ", SDL_GetError() ); return false; } - // TODO: cache width & height? + // TODO: set_position? + this->set_size( Size2i(width, height) ); - // Cache the size of our new Texture object with the existing surface info - this->set_bounds( IntRect(0, 0, width, height) ); + return true; +} - // TODO: use this for the texture size - // this->set_size( Size2i( width, height ) ); +bool Texture::initialize(uint32 format, uint32 flags, const Size2i& dims) +{ + return this->initialize(format, flags, dims.w, dims.h); +} + +bool Texture::create(SDL_Texture* source) +{ + // Static access type + this->texture_.reset(source, priv::FreeTexture); + + if( this->valid() == false ) { + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, + "Failed to initialize valid texture: ", SDL_GetError() ); + return false; + } + + Size2i tex_dims; + tex_dims.w = this->width(); + tex_dims.h = this->height(); + + // TODO: set_position? + this->set_size(tex_dims); return true; } @@ -182,15 +164,14 @@ bool Texture::create( const Image& source ) // Static access type this->texture_.reset ( source.texture(), priv::FreeTexture ); - if ( this->valid() == false ) - { - NOM_LOG_ERR ( NOM, SDL_GetError() ); + if( this->valid() == false ) { + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, + "Failed to initialize valid texture: ", SDL_GetError() ); return false; } - // TODO: cache width & height? - // Cache the size of our new Texture object with the existing surface info - this->set_bounds ( IntRect(0, 0, source.width(), source.height()) ); + // TODO: set_position? + this->set_size( source.size() ); return true; } @@ -249,9 +230,11 @@ SDL_Texture* Texture::texture() const return this->texture_.get(); } -bool Texture::valid ( void ) const +bool Texture::valid() const { - if ( this->texture() != nullptr ) return true; + if( this->texture() != nullptr ) { + return true; + } return false; } @@ -272,40 +255,33 @@ enum Texture::Access Texture::access ( void ) const return Texture::Access::Invalid; } -const Point2i& Texture::position( void ) const +const Point2i& Texture::position() const { return this->position_; } -const Size2i Texture::size( void ) const +const Size2i& Texture::size() const { - return Size2i( this->bounds_.w, this->bounds_.h ); - - // TODO: use this for the texture size - // return Size2i( this->size_.w, this->size_.h ); + return this->size_; } -const IntRect& Texture::bounds( void ) const +const IntRect& Texture::bounds() const { return this->bounds_; } -void Texture::set_position( const Point2i& pos ) +void Texture::set_position(const Point2i& pos) { this->position_.x = pos.x; this->position_.y = pos.y; } -void Texture::set_size( const Size2i& size ) +void Texture::set_size(const Size2i& size) { - this->bounds_.w = size.w; - this->bounds_.h = size.h; - - // TODO: use this for the texture size - // this->size_ = size; + this->size_ = size; } -void Texture::set_bounds( const IntRect& bounds ) +void Texture::set_bounds(const IntRect& bounds) { this->bounds_ = bounds; } @@ -430,21 +406,17 @@ const Point2i Texture::maximum_size ( void ) return Point2i ( info.texture_width(), info.texture_height() ); } -bool Texture::locked ( void ) const +bool Texture::locked() const { - if ( this->pixels() == nullptr ) return false; - - return true; + return this->pixels(); } -bool Texture::lock ( void ) +bool Texture::lock() { - if ( this->lock( IntRect::null ) == false ) return false; - - return true; + return this->lock(IntRect::null); } -bool Texture::lock ( const IntRect& bounds ) +bool Texture::lock(const IntRect& bounds) { if ( this->locked() ) { @@ -518,8 +490,6 @@ bool Texture::load ( const std::string& filename, // Set our default blending mode for texture copies this->set_blend_mode( SDL_BLENDMODE_BLEND ); - // Update our Texture clipping bounds with the new source - this->set_bounds ( source.bounds() ); return true; } @@ -548,104 +518,64 @@ bool Texture::update_pixels(const void* pixels, uint16 pitch, const IntRect& bou return true; } -void Texture::draw ( SDL_Renderer* target ) const +void Texture::draw(SDL_Renderer* target) const { - NOM_ASSERT( this->valid() ); - - Point2i pos = this->position(); - SDL_Rect render_coords; - - render_coords.x = pos.x; - render_coords.y = pos.y; - - // Use preset clipping bounds for the width and height of this texture - // - // These two coordinates can be used for rendering a rescaled texture - render_coords.w = this->bounds().w; - render_coords.h = this->bounds().h; - - // TODO: use texture size bounds here, texture source bounds are *not* always - // the same thing... - // render_coords.w = this->size().w; - // render_coords.h = this->size().h; - - // Render with set clipping bounds; we are rendering only a portion of a - // larger Texture; think: sprite sheets. - // - // NOTE: We have yet to encounter the case where these are not -1; testing of - // examples/app & TTcards confirms this. - - // TODO: use texture source bounds here, texture size is *not* always the - // same thing - if( render_coords.w != -1 && render_coords.h != -1 ) - // if ( this->bounds().w != -1 && this->bounds().h != -1 ) - { - SDL_Rect render_bounds; - render_bounds.x = this->bounds().x; - render_bounds.y = this->bounds().y; - render_bounds.w = this->bounds().w; - render_bounds.h = this->bounds().h; - if ( SDL_RenderCopy ( target, this->texture(), &render_bounds, &render_coords ) != 0 ) - { - NOM_LOG_ERR ( NOM, SDL_GetError() ); - return; - } - } - else // Render the entire Texture we have in memory - { - if ( SDL_RenderCopy ( target, this->texture(), nullptr, &render_coords ) != 0 ) - { - NOM_LOG_ERR ( NOM, SDL_GetError() ); - return; - } - } + this->draw(target, 0.0f); } -void Texture::draw ( const RenderWindow& target ) const +void Texture::draw(const RenderWindow& target) const { - this->draw ( target.renderer() ); + this->draw(target.renderer(), 0.0f ); } -void Texture::draw ( SDL_Renderer* target, const double angle ) const +void Texture::draw(SDL_Renderer* target, const real64 angle) const { + SDL_Rect render_coords = {}; + + if( target == nullptr ) { + return; + } + + if( this->texture() == nullptr ) { + return; + } + Point2i pos = this->position(); - SDL_Rect render_coords; + Size2i dims = this->size(); render_coords.x = pos.x; render_coords.y = pos.y; + render_coords.w = dims.w; + render_coords.h = dims.h; - // Use preset clipping bounds for the width and height of this texture - render_coords.w = this->bounds().w; - render_coords.h = this->bounds().h; + if( this->bounds().w != -1 && this->bounds().h != -1 ) { - // Render with set clipping bounds; we are rendering only a portion of a - // larger Texture; think: sprite sheets. - if ( this->bounds().w != -1 && this->bounds().h != -1 ) - { - SDL_Rect render_bounds; - render_bounds.x = this->bounds().x; - render_bounds.y = this->bounds().y; - render_bounds.w = this->bounds().w; - render_bounds.h = this->bounds().h; - if ( SDL_RenderCopyEx ( target, this->texture(), &render_bounds, &render_coords, angle, nullptr, SDL_FLIP_NONE ) != 0 ) + // Render a portion of the texture + SDL_Rect render_bounds = {}; + IntRect tex_bounds = this->bounds(); + render_bounds = nom::SDL_RECT(tex_bounds); + + if( SDL_RenderCopyEx( target, this->texture(), &render_bounds, + &render_coords, angle, nullptr, SDL_FLIP_NONE ) != 0 ) { -NOM_LOG_ERR ( NOM, SDL_GetError() ); + // NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, SDL_GetError() ); return; } - } - else // Render the entire Texture we have in memory - { - if ( SDL_RenderCopyEx ( target, this->texture(), nullptr, &render_coords, angle, nullptr, SDL_FLIP_NONE ) != 0 ) + } else { + + // Render the whole texture + if( SDL_RenderCopyEx( target, this->texture(), nullptr, + &render_coords, angle, nullptr, SDL_FLIP_NONE ) != 0 ) { -NOM_LOG_ERR ( NOM, SDL_GetError() ); + // NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, SDL_GetError() ); return; } } } -void Texture::draw ( const RenderWindow& target, const double degrees ) const +void Texture::draw(const RenderWindow& target, const real64 angle) const { - this->draw ( target.renderer(), degrees ); + this->draw(target.renderer(), 0.0f ); } bool Texture::set_alpha ( uint8 opacity ) @@ -1072,26 +1002,6 @@ bool Texture::copy_pixels ( const void* source, int pitch ) return true; } -bool Texture::set_render_target(RenderWindow& target) -{ - RendererInfo caps = target.caps(); - - // Ensure that the rendering device supports FBO - if( caps.target_texture() == false ) { - NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, - "Video hardware does not support render to texture" ); - return false; - } - - // Try to honor the request; render to the source texture - if( SDL_SetRenderTarget( target.context(), this->texture() ) != 0 ) { - NOM_LOG_ERR ( NOM_LOG_CATEGORY_APPLICATION, SDL_GetError() ); - return false; - } - - return true; -} - // Private scope void Texture::set_scale_factor(int factor) diff --git a/src/graphics/VideoMode.cpp b/src/graphics/VideoMode.cpp deleted file mode 100644 index d58a3c81..00000000 --- a/src/graphics/VideoMode.cpp +++ /dev/null @@ -1,93 +0,0 @@ -/****************************************************************************** - - nomlib - C++11 cross-platform game engine - -Copyright (c) 2013, 2014 Jeffrey Carpenter -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -******************************************************************************/ -#include "nomlib/graphics/VideoMode.hpp" - -namespace nom { - -VideoMode::VideoMode ( void ) : width ( 0 ), height ( 0 ), bpp ( 0 ) {} - -VideoMode::VideoMode ( int32 mode_width, int32 mode_height, uint8 mode_bpp ) : width ( mode_width ), height ( mode_height ), bpp ( mode_bpp ) -{} - -VideoMode::~VideoMode ( void ) {} - -std::ostream& operator << ( std::ostream& os, const VideoMode& mode ) -{ - os << mode.width << "x" << mode.height << "x" << static_cast ( mode.bpp ); - return os; -} - -bool operator == ( const VideoMode& lhs, const VideoMode& rhs ) -{ - return ( lhs.width == rhs.width ) && - ( lhs.height == rhs.height ) && - ( lhs.bpp == rhs.bpp ); -} - -bool operator != ( const VideoMode& lhs, const VideoMode& rhs ) -{ - return ! ( lhs == rhs ); -} - -bool operator < ( const VideoMode& lhs, const VideoMode& rhs ) -{ - if ( lhs.bpp == rhs.bpp ) - { - if ( lhs.width == rhs.width ) - { - return lhs.height < rhs.height; - } - else - { - return lhs.width < rhs.width; - } - } - else - { - return lhs.bpp < rhs.bpp; - } -} - -bool operator > ( const VideoMode& lhs, const VideoMode& rhs ) -{ - return rhs < lhs; -} - -bool operator <= ( const VideoMode& lhs, const VideoMode& rhs ) -{ - return ! (rhs < lhs ); -} - -bool operator >= ( const VideoMode& lhs, const VideoMode& rhs ) -{ - return ! ( lhs < rhs ); -} - - -} // namespace nom diff --git a/src/graphics/fonts/BMFont.cpp b/src/graphics/fonts/BMFont.cpp index bcb8cbd1..bbc86385 100644 --- a/src/graphics/fonts/BMFont.cpp +++ b/src/graphics/fonts/BMFont.cpp @@ -116,7 +116,7 @@ int BMFont::kerning(uint32 first_char, uint32 second_char, uint32 character_size // Possible FIXME: BMFontTest::KerningParserSanity fails here if we do // validity check // if( this->valid() == false ) { - // return nom::int_min; + // return nom::NOM_INT_MIN; // } if( this->use_kerning() == false ) { @@ -245,11 +245,11 @@ bool BMFont::build(uint32 character_size) return false; } - Point2i size( this->pages_[0].texture->size() ); + Size2i size( this->pages_[0].texture->size() ); // Sanity checks - NOM_ASSERT( size.x == this->page_size_.w ); - NOM_ASSERT( size.y == this->page_size_.h ); + NOM_ASSERT( size.w == this->page_size_.w ); + NOM_ASSERT( size.h == this->page_size_.h ); return true; } @@ -262,6 +262,12 @@ bool BMFont::parse_ascii_file(std::istream& fp) std::string key; // left-hand constituent of the buffer std::string value; // right-hand constituent of the buffer + if( fp.good() == false ) { + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, + "Could not parse BMFont file stream." ); + return false; + } + while( !fp.eof() ) { std::stringstream stream; diff --git a/src/graphics/fonts/BitmapFont.cpp b/src/graphics/fonts/BitmapFont.cpp index a0068dcc..db3632dc 100644 --- a/src/graphics/fonts/BitmapFont.cpp +++ b/src/graphics/fonts/BitmapFont.cpp @@ -375,12 +375,13 @@ bool BitmapFont::build ( uint32 character_size ) page.texture->unlock(); // Finished messing with pixels - // Calculate new line by subtracting the baseline of the "chosen glyph" - // from the bitmap's ascent. - // - // (See also: baseline_glyph variable) this->metrics_.newline = base - top; + // NOTE: The original new line feed computation -- 'base - top', breaks text + // rendering bounds within nom::Text::update_cache because the height + // calculation it relies on was inaccurate. + this->metrics_.newline = base + top + 1; + // Loop off excess top pixels for ( uint32 top_pixels = 0; top_pixels < current_char; ++top_pixels ) { diff --git a/src/graphics/fonts/TrueTypeFont.cpp b/src/graphics/fonts/TrueTypeFont.cpp index 5faad494..c72bdb4e 100644 --- a/src/graphics/fonts/TrueTypeFont.cpp +++ b/src/graphics/fonts/TrueTypeFont.cpp @@ -141,7 +141,7 @@ int TrueTypeFont::kerning( uint32 first_char, uint32 second_char, uint32 charact if( this->valid() == false ) { // Invalid font - return nom::int_min; + return nom::NOM_INT_MIN; } // NOM_LOG_INFO( NOM, "sdl2_ttf kerning: ", TTF_GetFontKerning( this->font() ) ); @@ -151,7 +151,7 @@ int TrueTypeFont::kerning( uint32 first_char, uint32 second_char, uint32 charact kerning_offset = TTF_GetFontKerningSize(this->font(), first_char, second_char); - if( kerning_offset == nom::int_min ) { + if( kerning_offset == nom::NOM_INT_MIN ) { NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, "Could not obtain kerning offset: ", TTF_GetError() ); diff --git a/src/graphics/graphics_helpers.cpp b/src/graphics/graphics_helpers.cpp index 029b9fa5..04e2f642 100644 --- a/src/graphics/graphics_helpers.cpp +++ b/src/graphics/graphics_helpers.cpp @@ -30,82 +30,83 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // Forward declarations #include "nomlib/math/Transformable.hpp" +#include "nomlib/graphics/Texture.hpp" namespace nom { -Point2i alignment(Transformable* obj, const Size2i& bounds, uint32 align) +Point2i +alignment_rect( const Size2i& obj_dims, const Point2i& pos_offset, + const Size2i& align_bounds, uint32 align ) { // Resulting alignment calculation - Point2i offset(Point2i::zero); - - // Object's rendered position - Point2i pos(Point2i::zero); + Point2i align_offset(Point2i::zero); // Object's rendered width and height - Size2i dims(Size2i::zero); - - if( obj != nullptr ) { - pos = obj->position(); - dims = obj->size(); - } else { - // Err: invalid pointer given - return Point2i::null; - } - - // Reset alignment - // if( align & Alignment::NONE ) { - // offset.x = pos.x; - // offset.y = pos.y; - // } + Size2i dims = obj_dims; // Anchor::TopLeft, Anchor::Left, Anchor::BottomLeft if( align & Alignment::X_LEFT ) { - offset.x = pos.x; + align_offset.x = pos_offset.x; } // Anchor::TopCenter, Anchor::MiddleCenter, Anchor::BottomCenter if( align & Alignment::X_CENTER ) { - offset.x = pos.x + (bounds.w - dims.w) / 2; + align_offset.x = pos_offset.x + (align_bounds.w - dims.w) / 2; } // Anchor::TopRight, Anchor::MiddleRight, Anchor::BottomRight if( align & Alignment::X_RIGHT ) { - offset.x = pos.x + (bounds.w - dims.w); + align_offset.x = pos_offset.x + (align_bounds.w - dims.w); } // Anchor::TopLeft, Anchor::TopCenter, Anchor::TopRight if( align & Alignment::Y_TOP ) { - offset.y = pos.y; + align_offset.y = pos_offset.y; } // Anchor::MiddleLeft, Anchor::MiddleCenter, Anchor::MiddleRight if( align & Alignment::Y_CENTER ) { - offset.y = pos.y + (bounds.h - dims.h) / 2; + align_offset.y = pos_offset.y + (align_bounds.h - dims.h) / 2; } // Anchor::BottomLeft, Anchor::BottomCenter, Anchor::BottomRight if( align & Alignment::Y_BOTTOM ) { - offset.y = pos.y + (bounds.h - dims.h); + align_offset.y = pos_offset.y + (align_bounds.h - dims.h); } - return offset; + return align_offset; } -void set_alignment(Transformable* obj, const Size2i& bounds, uint32 align) +void +set_alignment( Transformable* obj, const Point2i& pos_offset, + const Size2i& align_bounds, uint32 align ) { // Resulting alignment calculation Point2i offset(Point2i::zero); - NOM_ASSERT(obj != nullptr); if( obj == nullptr ) { return; // Err } - offset = alignment(obj, bounds, align); + offset = nom::alignment_rect(obj->size(), pos_offset, align_bounds, align); - if(offset != Point2i::null) { - obj->set_position(offset); + obj->set_position(offset); +} + +void set_alignment( Texture* obj, const Point2i& pos_offset, + const Size2i& align_bounds, uint32 align ) +{ + // Resulting alignment calculation + Point2i align_offset(Point2i::zero); + + if( obj == nullptr ) { + return; // Err } + + align_offset = + nom::alignment_rect(obj->size(), pos_offset, align_bounds, align); + + obj->set_position(align_offset); } } // namespace nom diff --git a/src/graphics/shapes/Line.cpp b/src/graphics/shapes/Line.cpp index 0ff6b7f8..16dfbc27 100644 --- a/src/graphics/shapes/Line.cpp +++ b/src/graphics/shapes/Line.cpp @@ -48,9 +48,9 @@ Line::Line ( const IntRect& bounds, const Color4i& outline ) this->set_outline_color ( outline ); } -IDrawable::raw_ptr Line::clone( void ) const +Shape* Line::clone() const { - return Line::raw_ptr( new Line( *this ) ); + return( new self_type(*this) ); } ObjectTypeInfo Line::type( void ) const diff --git a/src/graphics/shapes/Rectangle.cpp b/src/graphics/shapes/Rectangle.cpp index 7801fcb7..091e0b57 100644 --- a/src/graphics/shapes/Rectangle.cpp +++ b/src/graphics/shapes/Rectangle.cpp @@ -28,42 +28,107 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ #include "nomlib/graphics/shapes/Rectangle.hpp" +// Forward declarations +#include "nomlib/graphics/Texture.hpp" + namespace nom { -Rectangle::Rectangle ( void ) +Rectangle::Rectangle() { - //NOM_LOG_TRACE(NOM); + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE_RENDER, NOM_LOG_PRIORITY_VERBOSE); } -Rectangle::~Rectangle ( void ) +Rectangle::~Rectangle() { - //NOM_LOG_TRACE(NOM); + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE_RENDER, NOM_LOG_PRIORITY_VERBOSE); } -Rectangle::Rectangle ( const IntRect& rect, const Color4i& fill ) +Rectangle::Rectangle(const IntRect& rect, const Color4i& fill) { - //NOM_LOG_TRACE(NOM); - this->set_position ( Point2i( rect.x, rect.y ) ); - this->set_size ( Size2i( rect.w, rect.h ) ); - this->set_fill_color ( fill ); + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE_RENDER, NOM_LOG_PRIORITY_VERBOSE); + + this->set_bounds(rect); + this->set_fill_color(fill); } -IDrawable::raw_ptr Rectangle::clone( void ) const +ObjectTypeInfo Rectangle::type() const { - return Rectangle::raw_ptr( new Rectangle( *this ) ); + return NOM_OBJECT_TYPE_INFO( self_type ); } -ObjectTypeInfo Rectangle::type( void ) const +Shape* Rectangle::clone() const { - return NOM_OBJECT_TYPE_INFO( self_type ); + return( new self_type(*this) ); +} + +Texture* Rectangle::texture() const +{ + Texture* texture = new Texture(); + + NOM_ASSERT(texture != nullptr); + if( texture == nullptr ) { + NOM_LOG_ERR( NOM_LOG_CATEGORY_RENDER, "Could not update cache:", + "failed to allocate texture memory." ); + NOM_DELETE_PTR(texture); + return nullptr; + } + + RenderWindow* context = nom::render_interface(); + NOM_ASSERT(context != nullptr); + + if( context == nullptr ) { + NOM_LOG_ERR( NOM_LOG_CATEGORY_RENDER, "Could not update cache", + "invalid renderer." ); + NOM_DELETE_PTR(texture); + return nullptr; + } + + // Obtain the optimal pixel format for the platform + RendererInfo caps = context->caps(); + + if( texture->initialize( caps.optimal_texture_format(), + SDL_TEXTUREACCESS_TARGET, this->size() ) == false ) + { + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, "Could not update cache:", + "failed texture creation." ); + NOM_DELETE_PTR(texture); + return nullptr; + } + + texture->set_position( this->position() ); + + if( context->set_render_target(texture) == false ) { + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, "Could not update cache:", + "could not set rendering target." ); + NOM_DELETE_PTR(texture); + return nullptr; + } + + if( context->fill( this->fill_color() ) == false ) { + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, "Could not update cache:", + "failed to set texture color." ); + NOM_DELETE_PTR(texture); + return nullptr; + } + + this->draw(*context); + + if( context->reset_render_target() == false ) { + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, "Could not update cache:", + "failed to reset the rendering target." ); + NOM_DELETE_PTR(texture); + return nullptr; + } + + return texture; } -void Rectangle::update ( void ) +void Rectangle::update() { - // Stub (NO-OP) + // Stub } -void Rectangle::draw ( RenderTarget& target ) const +void Rectangle::draw(RenderTarget& target) const { SDL_SetRenderDrawBlendMode( target.renderer(), SDL_BLENDMODE_BLEND ); diff --git a/src/graphics/shapes/Shape.cpp b/src/graphics/shapes/Shape.cpp index ef33ddf9..888db224 100644 --- a/src/graphics/shapes/Shape.cpp +++ b/src/graphics/shapes/Shape.cpp @@ -34,12 +34,12 @@ Shape::Shape ( void ) : outline_color_ { Color4i::White }, fill_color_ { Color4i::Black } { - //NOM_LOG_TRACE ( NOM ); + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE_RENDER, NOM_LOG_PRIORITY_VERBOSE); } Shape::~Shape ( void ) { - //NOM_LOG_TRACE ( NOM ); + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE_RENDER, NOM_LOG_PRIORITY_VERBOSE); } Shape::Shape ( const Shape& copy ) : @@ -47,7 +47,7 @@ Shape::Shape ( const Shape& copy ) : outline_color_ { copy.outline_color() }, fill_color_ { copy.fill_color() } { - //NOM_LOG_TRACE(NOM); + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE_RENDER, NOM_LOG_PRIORITY_VERBOSE); } /* diff --git a/src/graphics/sprite/AnimatedSprite.cpp b/src/graphics/sprite/AnimatedSprite.cpp deleted file mode 100644 index 09179ff2..00000000 --- a/src/graphics/sprite/AnimatedSprite.cpp +++ /dev/null @@ -1,218 +0,0 @@ -/****************************************************************************** - - nomlib - C++11 cross-platform game engine - -Copyright (c) 2013, 2014 Jeffrey Carpenter -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -******************************************************************************/ -#include "nomlib/graphics/sprite/AnimatedSprite.hpp" - -// Private headers (third-party libs) -#include - -namespace nom { - -void AnimatedSprite::initialize ( void ) -{ - this->setMaxFrames ( 1 ); - // this->set_frame ( 0 ); - this->current_frame = 0; - this->setFrameIncrement ( 1 ); - this->setAnimationStyle ( AnimatedSprite::AnimationStyle::NoStyle ); - this->setAnimationStatus ( AnimatedSprite::AnimationStatus::Stopped ); - this->fps.setFrameRate ( 100 ); -} - -AnimatedSprite::AnimatedSprite ( void ) -{ - NOM_LOG_TRACE( NOM_LOG_CATEGORY_TRACE_RENDER ); - - this->initialize(); -} - -AnimatedSprite::~AnimatedSprite ( void ) -{ - NOM_LOG_TRACE( NOM_LOG_CATEGORY_TRACE_RENDER ); -} - -void AnimatedSprite::set_sprite_sheet(const SpriteSheet& sheet) -{ - // NOM_LOG_TRACE ( NOM ); - - this->initialize(); - - SpriteBatch::set_sprite_sheet(sheet); - this->setMaxFrames( this->sprite_sheet.frames() ); -} - -int32 AnimatedSprite::total_frames ( void ) const -{ - return this->max_frames; -} - -int32 AnimatedSprite::frame_inc ( void ) const -{ - return this->frame_increment; -} - -int32 AnimatedSprite::frame ( void ) const -{ - return this->current_frame; -} - -AnimatedSprite::AnimationStyle AnimatedSprite::style ( void ) const -{ - return this->animation_style; -} - -AnimatedSprite::AnimationStatus AnimatedSprite::status ( void ) const -{ - return this->animation_status; -} - -void AnimatedSprite::setMaxFrames ( int32 max ) -{ - this->max_frames = max; -} - -void AnimatedSprite::setFrameIncrement ( int32 increment ) -{ - this->frame_increment = increment; -} - -void AnimatedSprite::setFrameRate ( int32 rate ) -{ - this->fps.setFrameRate ( rate ); -} - -void AnimatedSprite::set_frame ( int32 frame ) -{ - if ( frame < 0 || frame > this->total_frames() ) - { -NOM_LOG_ERR ( NOM, "Could not update animation frame: requested frame value is too low or too high." ); - return; - } - - this->current_frame = frame; - SpriteBatch::set_frame(frame); -} - -void AnimatedSprite::setAnimationStyle ( AnimatedSprite::AnimationStyle style ) -{ - this->animation_style = style; -} - -void AnimatedSprite::setAnimationStatus ( AnimatedSprite::AnimationStatus status ) -{ - this->animation_status = status; -} - -void AnimatedSprite::update_animation ( void ) -{ - if ( this->status() != AnimatedSprite::AnimationStatus::Playing ) return; - - if ( this->fps.ticks() + this->fps.framerate() > SDL_GetTicks() ) - { - return; - } - - this->fps.start(); - - // this->set_frame(this->current_frame + this->frame_increment); - - if ( this->style() == AnimatedSprite::AnimationStyle::Oscillate ) - { - if ( this->frame_inc() > 0 ) - { - if ( this->frame() >= this->total_frames() ) - { - this->setFrameIncrement ( -( this->frame_inc() ) ); - } - } - else - { - if ( this->frame() <= 0 ) - { - this->setFrameIncrement ( -( this->frame_inc() ) ); - } - } - } - else if ( this->style() == AnimatedSprite::AnimationStyle::Blink ) - { - if ( this->frame() >= this->total_frames() ) - { - -#if defined ( NOM_DEBUG_ANIMATED_SPRITE ) - NOM_DUMP( this->frame() ); -#endif - - this->set_frame ( this->frame() - 2 ); - -#if defined ( NOM_DEBUG_ANIMATED_SPRITE ) - NOM_DUMP( this->frame() ); -#endif - - } - } - else - { - if ( this->frame() >= this->total_frames() ) - { - // this->set_frame ( 0 ); - } - } - - if( this->current_frame + this->frame_increment < this->total_frames() ) { - this->set_frame(this->current_frame + this->frame_increment); - } - else { - this->set_frame(0); - } - - // this->set_frame ( this->frame() ); - // SpriteBatch::update(); -} - -void AnimatedSprite::play ( void ) -{ - this->setAnimationStatus ( AnimatedSprite::AnimationStatus::Playing ); - this->update_animation(); -} - -void AnimatedSprite::stop ( void ) -{ - this->setAnimationStatus ( AnimatedSprite::AnimationStatus::Stopped ); -} - -void AnimatedSprite::pause ( void ) -{ - this->setAnimationStatus ( AnimatedSprite::AnimationStatus::Paused ); -} - -void AnimatedSprite::unpause ( void ) -{ - this->setAnimationStatus ( AnimatedSprite::AnimationStatus::Playing ); -} - -} // namespace nom diff --git a/src/graphics/sprite/Sprite.cpp b/src/graphics/sprite/Sprite.cpp index 02c4fa82..6b4761d1 100644 --- a/src/graphics/sprite/Sprite.cpp +++ b/src/graphics/sprite/Sprite.cpp @@ -28,89 +28,306 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ #include "nomlib/graphics/sprite/Sprite.hpp" +// Private headers +#include "nomlib/graphics/shapes/Rectangle.hpp" +#include "nomlib/core/unique_ptr.hpp" + +// Forward declarations +#include "nomlib/graphics/Texture.hpp" +#include "nomlib/graphics/RenderWindow.hpp" + namespace nom { +inline static +void TextureReferenceDeleter(Texture* tex) +{ + // Memory management is free for us here; the sprite does not own the + // texture! +} + Sprite::Sprite() : - Transformable( Point2i(0, 0), Size2i(0, 0) ), - texture_(nullptr) + Transformable(Point2i::zero, Size2i::zero) +{ + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_RENDER, NOM_LOG_PRIORITY_VERBOSE); +} + +Sprite::~Sprite() { -// NOM_LOG_TRACE ( NOM ); + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_RENDER, NOM_LOG_PRIORITY_VERBOSE); } -Sprite::~Sprite ( void ) +bool Sprite::init_with_color(const Color4i& color, const Size2i& dims) { -// NOM_LOG_TRACE ( NOM ); + Point2i rect_pos(Point2i::zero); + Size2i rect_dims(dims); + IntRect rect_bounds(rect_pos, rect_dims); + + Rectangle rect(rect_bounds, color); + auto tex = rect.texture(); + return this->set_texture(tex); } -Sprite::Sprite(const Size2i& dims) : - Transformable( Point2i(0, 0), dims ), - texture_(nullptr) +ObjectTypeInfo Sprite::type() const { - // NOM_LOG_TRACE ( NOM ); + return NOM_OBJECT_TYPE_INFO(self_type); } void Sprite::set_position(const Point2i& pos) { Transformable::set_position(pos); - Sprite::update(); + if( this->texture_ != nullptr ) { + this->texture_->set_position( this->position() ); + } } -IDrawable* Sprite::clone() const +void Sprite::set_size(const Size2i& dims) { - return( new Sprite( *this ) ); + Transformable::set_size(dims); + + if( this->texture_ != nullptr ) { + this->texture_->set_size( this->size() ); + } } -ObjectTypeInfo Sprite::type() const +std::unique_ptr Sprite::clone() const { - return NOM_OBJECT_TYPE_INFO(self_type); + return( nom::make_unique( self_type(*this) ) ); +} + +std::shared_ptr Sprite::texture() const +{ + return this->texture_; +} + +bool Sprite::valid() const +{ + if( this->texture_ != nullptr ) { + return this->texture_->valid(); + } else { + return false; + } } -SDL_Texture* Sprite::texture() const +uint8 Sprite::alpha() const { - NOM_ASSERT(this->texture_ != nullptr); + uint8 alpha = 0; // default return - if(this->texture_ != nullptr) { - return this->texture_->texture(); + if( this->valid() == true ) { + alpha = this->texture_->alpha(); + } else { + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, + "Could not get the alpha value of the sprite." ); } - // Invalid - return nullptr; + return alpha; } -void Sprite::set_texture(/*const*/ Texture& tex) +Color4i Sprite::color() const { - this->texture_ = &tex; + Color4i color = Color4i::null; // default return + + if( this->valid() == true ) { + color = this->texture_->color_modulation(); + } else { + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, + "Could not get the color value of the sprite." ); + } + + return color; } -void Sprite::draw( RenderTarget& target ) const +BlendMode Sprite::color_blend_mode() const { - NOM_ASSERT(this->texture_ != nullptr); + // default return + BlendMode mode = BlendMode::BLEND_MODE_NONE; - if(this->texture_ != nullptr) { - this->texture_->draw( target.renderer() ); + if( this->valid() == true ) { + mode = nom::blend_mode( this->texture_->blend_mode() ); + } else { + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, + "Could not get the color blending mode of the sprite." ); + } + + return mode; +} + +bool Sprite::set_texture(Texture& tex) +{ + this->texture_.reset(&tex, TextureReferenceDeleter); + + if( this->texture_ != nullptr ) { + this->set_position( this->texture_->position() ); + this->set_size( this->texture_->size() ); + return true; + } else { + // Err; out of memory??? + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, + "Could not allocate a texture for the sprite." ); + return false; + } +} + +bool Sprite::set_texture(Texture* tex) +{ + this->texture_.reset(tex); + + if( this->texture_ != nullptr ) { + this->set_position( this->texture_->position() ); + this->set_size( this->texture_->size() ); + return true; + } else { + // Err; out of memory??? + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, + "Could not allocate a texture for the sprite." ); + return false; + } +} + +bool Sprite::set_texture(std::shared_ptr& tex) +{ + this->texture_ = tex; + + if( this->texture_ != nullptr ) { + this->set_position( this->texture_->position() ); + this->set_size( this->texture_->size() ); + return true; + } else { + // Err; out of memory??? + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, + "Could not allocate texture for sprite." ); + return false; + } +} + +bool Sprite::set_alpha(uint8 opacity) +{ + if( this->texture_ != nullptr ) { + return this->texture_->set_alpha(opacity); + } else { + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, + "Could not set the alpha value of the sprite." ); + return false; + } +} + +bool Sprite::set_color(const Color4i& color) +{ + if( this->valid() == true ) { + return this->texture_->set_color_modulation(color); + } else { + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, + "Could not set the color of the sprite." ); + return false; + } +} + +bool Sprite::set_color_blend_mode(BlendMode blend) +{ + SDL_BlendMode mode = nom::SDL_blend_mode(blend); + + if( this->texture_ != nullptr ) { + return this->texture_->set_blend_mode(mode); + } else { + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, + "Could not set the color blending mode of the sprite." ); + return false; } } -void Sprite::draw(RenderTarget& target, const double degrees) const +void Sprite::release_texture() { - NOM_ASSERT(this->texture_ != nullptr); + this->texture_.reset(); +} - if(this->texture_ != nullptr) { +void Sprite::draw(RenderTarget& target) const +{ + if( this->valid() == true ) { + this->texture_->draw( target.renderer() ); + } +} + +void Sprite::draw(RenderTarget& target, real64 degrees) const +{ + if( this->valid() == true ) { this->texture_->draw(target.renderer(), degrees); } } -// Protected scope +// Private scope void Sprite::update() { - NOM_ASSERT(this->texture_ != nullptr); + // Stub +} + +// Non-member factory functions + +std::unique_ptr +make_unique_sprite(Texture& tex) +{ + auto sprite = nom::make_unique(); + if( sprite != nullptr ) { + sprite->set_texture(tex); + } + + return std::move(sprite); +} + +std::unique_ptr +make_unique_sprite(Texture* tex) +{ + auto sprite = nom::make_unique(); + if( sprite != nullptr ) { + sprite->set_texture(tex); + } + + return std::move(sprite); +} + +std::unique_ptr +make_unique_sprite(std::shared_ptr& tex) +{ + auto sprite = nom::make_unique(); + if( sprite != nullptr ) { + sprite->set_texture(tex); + } - if(this->texture_ != nullptr) { - this->texture_->set_position( Point2i( this->position().x, this->position().y ) ); + return std::move(sprite); +} + +std::shared_ptr +make_shared_sprite(Texture& tex) +{ + auto sprite = std::make_shared(); + if( sprite != nullptr ) { + sprite->set_texture(tex); } + + return sprite; +} + +std::shared_ptr +make_shared_sprite(Texture* tex) +{ + auto sprite = std::make_shared(); + if( sprite != nullptr ) { + sprite->set_texture(tex); + } + + return sprite; +} + +std::shared_ptr +make_shared_sprite(std::shared_ptr& tex) +{ + auto sprite = std::make_shared(); + if( sprite != nullptr ) { + sprite->set_texture(tex); + } + + return sprite; } } // namespace nom diff --git a/src/graphics/sprite/SpriteBatch.cpp b/src/graphics/sprite/SpriteBatch.cpp index 8acb9f71..38b56ec0 100644 --- a/src/graphics/sprite/SpriteBatch.cpp +++ b/src/graphics/sprite/SpriteBatch.cpp @@ -28,6 +28,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ #include "nomlib/graphics/sprite/SpriteBatch.hpp" +// Forward declarations +#include "nomlib/graphics/Texture.hpp" + namespace nom { SpriteBatch::SpriteBatch() : @@ -49,9 +52,7 @@ void SpriteBatch::set_sprite_sheet(const SpriteSheet& sheet) dims = this->sprite_sheet.dimensions(0); - // this->set_size( Size2i( this->sprite_sheet.sheet_width(), this->sprite_sheet.sheet_height() ) ); - // this->set_bounds(dims); - Sprite( dims.size() ); + Sprite::set_size( dims.size() ); this->set_frame(0); @@ -63,9 +64,9 @@ ObjectTypeInfo SpriteBatch::type() const return NOM_OBJECT_TYPE_INFO(self_type); } -IDrawable* SpriteBatch::clone() const +SpriteBatch* SpriteBatch::clone() const { - return( new SpriteBatch( *this ) ); + return( new SpriteBatch(*this) ); } int32 SpriteBatch::frame() const @@ -93,7 +94,7 @@ void SpriteBatch::draw(IDrawable::RenderTarget& target) const } } -void SpriteBatch::draw(IDrawable::RenderTarget& target, const double angle) const +void SpriteBatch::draw(IDrawable::RenderTarget& target, real64 angle) const { if( this->frame() >= 0 ) { @@ -101,12 +102,10 @@ void SpriteBatch::draw(IDrawable::RenderTarget& target, const double angle) cons } } -// Protected scope +// Private scope void SpriteBatch::update() { - NOM_ASSERT(this->texture_ != nullptr); - int scale_factor = 1; if( this->texture_ != nullptr ) { @@ -115,7 +114,7 @@ void SpriteBatch::update() if( this->frame() >= 0 ) { - Sprite::update(); + Sprite::set_position( this->position() ); IntRect dims = this->sprite_sheet.dimensions( this->frame() ); @@ -124,7 +123,7 @@ void SpriteBatch::update() this->offsets.w = dims.w * scale_factor; this->offsets.h = dims.h * scale_factor; - this->set_size( Size2i(offsets.w, offsets.h) ); + Sprite::set_size( Size2i(offsets.w, offsets.h) ); if( this->texture_ != nullptr ) { this->texture_->set_bounds(this->offsets); } diff --git a/src/graphics/sprite/SpriteSheet.cpp b/src/graphics/sprite/SpriteSheet.cpp index fbf1e223..813247aa 100644 --- a/src/graphics/sprite/SpriteSheet.cpp +++ b/src/graphics/sprite/SpriteSheet.cpp @@ -29,6 +29,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "nomlib/graphics/sprite/SpriteSheet.hpp" // Private headers +#include "nomlib/core/unique_ptr.hpp" #include "nomlib/serializers/JsonCppSerializer.hpp" #include "nomlib/serializers/JsonCppDeserializer.hpp" @@ -53,6 +54,11 @@ SpriteSheet::~SpriteSheet() NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_RENDER, nom::NOM_LOG_PRIORITY_VERBOSE ); } +SpriteSheet* SpriteSheet::clone() const +{ + return( new SpriteSheet(*this) ); +} + const IntRect& SpriteSheet::dimensions(int index) const { NOM_ASSERT( index < this->sheet_.size() ); @@ -107,19 +113,20 @@ int SpriteSheet::total_frames() const return this->total_frames_; } -SpriteSheet::SharedPtr SpriteSheet::clone() const -{ - return SpriteSheet::SharedPtr ( new SpriteSheet ( *this ) ); -} - bool SpriteSheet::load_file(const std::string& filename) { - IValueDeserializer* serializer = new JsonCppDeserializer(); - Value output; // Value buffer of resulting de-serialized input. + Value output; - if( serializer->load( filename, output ) == false ) - { - NOM_LOG_ERR( NOM, "Unable to parse JSON file:", filename ); + auto deserializer = nom::make_unique_json_deserializer(); + if( deserializer == nullptr ) { + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, + "Could not load input file: failure to allocate memory!" ); + return false; + } + + if( deserializer->load(filename, output) == false ) { + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, + "Unable to parse JSON file:", filename ); return false; } @@ -240,6 +247,52 @@ bool SpriteSheet::load_sheet_object(const Value& object) return true; } +bool SpriteSheet:: +insert_frame(nom::size_type frame_num, const IntRect& frame_bounds) +{ + this->sheet_[frame_num] = frame_bounds; + + return true; +} + +bool SpriteSheet::append_frame(const IntRect& frame_bounds) +{ + nom::size_type num_frames = this->frames(); + + auto pair = + std::make_pair(num_frames, frame_bounds); + + auto res = this->sheet_.insert(pair); + if( res.second == true ) { + // Success! + return true; + } else { + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, + "Could not append frame: key already exists at", num_frames ); + return false; + } +} + +bool SpriteSheet::remove_frame(nom::size_type frame) +{ + auto res = this->sheet_.find(frame); + + if( res == this->sheet_.end() ) { + // Not found + return false; + } else { + + // Found + this->sheet_.erase(res); + return true; + } +} + +void SpriteSheet::remove_frames() +{ + this->sheet_.clear(); +} + void SpriteSheet::dump ( void ) const { // Sheet vector state diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 80bf66b2..aadc892f 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -105,7 +105,7 @@ endif( OPENGL_FOUND ) # endif( LUA_FOUND ) # endif( NOM_USE_LIBROCKET_LUA ) -find_package( libRocket REQUIRED ) +find_package( LibRocket REQUIRED ) if( LIBROCKET_FOUND ) # Add development header files include_directories( ${LIBROCKET_INCLUDE_DIRS} ) diff --git a/src/gui/DecoratorFinalFantasyFrame.cpp b/src/gui/DecoratorFinalFantasyFrame.cpp index cff6b42b..33aa53b8 100644 --- a/src/gui/DecoratorFinalFantasyFrame.cpp +++ b/src/gui/DecoratorFinalFantasyFrame.cpp @@ -98,33 +98,32 @@ void DecoratorFinalFantasyFrame::RenderElement(Rocket::Core::Element* element, R if( size.x <= 0 || size.y <= 0 ) return; // Check for whether or not we need to update our decorator - if( this->coords_.x != position.x || this->coords_.y != position.y || this->coords_.w != size.x || this->coords_.h != size.y ) + if( this->bounds_.x != position.x || this->bounds_.y != position.y || this->bounds_.w != size.x || this->bounds_.h != size.y ) { // Update our coordinates to match new element coordinates - this->coords_.x = position.x; - this->coords_.y = position.y; - this->coords_.w = size.x; - this->coords_.h = size.y; + this->bounds_.x = position.x; + this->bounds_.y = position.y; + this->bounds_.w = size.x; + this->bounds_.h = size.y; // Keeps our decorator within positive bounds on the left side of the // window, else it will vanish on us - if( this->coords_.x <= 0 ) + if( this->bounds_.x <= 0 ) { - int x_offset = abs( this->coords_.x ); + int x_offset = fabs(this->bounds_.x); // We need to recalculate widths when the object is partially off-screen, // otherwise stretching of the layout occurs -- a possible bug in // libRocket? - if( this->coords_.w > x_offset ) // Bad things happen if w < 0 + if( this->bounds_.w > x_offset ) // Bad things happen if w < 0 { - this->coords_.w = this->coords_.w - x_offset; - this->coords_.x = 0; + this->bounds_.w = this->bounds_.w - x_offset; + this->bounds_.x = 0; } // else The object is entirely offscreen (this is valid) } - decorator_->set_bounds( this->coords_ ); - decorator_->update(); + decorator_->set_bounds( IntRect(this->bounds_) ); } decorator_->draw( *context ); diff --git a/src/gui/RocketSDL2RenderInterface.cpp b/src/gui/RocketSDL2RenderInterface.cpp index d1b28a10..61061148 100644 --- a/src/gui/RocketSDL2RenderInterface.cpp +++ b/src/gui/RocketSDL2RenderInterface.cpp @@ -81,15 +81,6 @@ bool RocketSDL2RenderInterface::gl_init( int width, int height ) return false; } - // Try to obtain the function address of the exported symbol with SDL - RocketSDL2RenderInterface::ctx_ = - (priv::glUseProgramObjectARB_func) new priv::glUseProgramObjectARB_func; - - NOM_ASSERT(RocketSDL2RenderInterface::ctx_ != nullptr); - if( RocketSDL2RenderInterface::ctx_ == nullptr ) { - return false; - } - // It may be unsafe (CRASH) to use this function pointer when // SDL_GL_ExtensionSupported returns FALSE as per SDL2 wiki documentation [1] // @@ -138,8 +129,13 @@ void RocketSDL2RenderInterface::Release() delete this; } -void RocketSDL2RenderInterface::RenderGeometry(Rocket::Core::Vertex* vertices, int num_vertices, int* indices, int num_indices, const Rocket::Core::TextureHandle texture, const Rocket::Core::Vector2f& translation) +void RocketSDL2RenderInterface:: +RenderGeometry( Rocket::Core::Vertex* vertices, int num_vertices, int* indices, + int num_indices, Rocket::Core::TextureHandle texture_handle, + const Rocket::Core::Vector2f& translation ) { + SDL_Texture* sdl_texture = NULL; + // Support for independent resolution scale -- SDL2 logical viewport -- we // translate positioning coordinates in respect to the current scale Point2f scale; @@ -159,11 +155,10 @@ void RocketSDL2RenderInterface::RenderGeometry(Rocket::Core::Vertex* vertices, i std::vector TexCoords(num_vertices); float texw, texh; - SDL_Texture* sdl_texture = NULL; - if(texture) - { + auto nom_texture = (nom::Texture*)texture_handle; + if( nom_texture != nullptr ) { glEnableClientState(GL_TEXTURE_COORD_ARRAY); - sdl_texture = (SDL_Texture *) texture; + sdl_texture = (SDL_Texture*) nom_texture->texture(); SDL_GL_BindTexture(sdl_texture, &texw, &texh); } @@ -172,13 +167,11 @@ void RocketSDL2RenderInterface::RenderGeometry(Rocket::Core::Vertex* vertices, i Positions[i].x = vertices[i].position.x * scale.x; Positions[i].y = vertices[i].position.y * scale.y; Colors[i] = vertices[i].colour; - if( sdl_texture ) - { + + if( sdl_texture != nullptr ) { TexCoords[i].x = vertices[i].tex_coord.x * texw; TexCoords[i].y = vertices[i].tex_coord.y * texh; - } - else - { + } else { TexCoords[i] = vertices[i].tex_coord; } } @@ -196,8 +189,7 @@ void RocketSDL2RenderInterface::RenderGeometry(Rocket::Core::Vertex* vertices, i glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_COLOR_ARRAY); - if (sdl_texture) - { + if( sdl_texture != nullptr ) { SDL_GL_UnbindTexture(sdl_texture); glDisableClientState(GL_TEXTURE_COORD_ARRAY); } @@ -218,7 +210,7 @@ void RocketSDL2RenderInterface::RenderGeometry(Rocket::Core::Vertex* vertices, i // work in the instance I'm working in (custom libRocket decorator)... if( this->window_->set_color( Color4i::Blue ) == false ) { - NOM_LOG_ERR ( NOM, SDL_GetError() ); + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, SDL_GetError() ); } } @@ -271,7 +263,9 @@ bool RocketSDL2RenderInterface::LoadTexture(Rocket::Core::TextureHandle& texture if( !file_handle ) { - NOM_LOG_ERR( NOM_LOG_CATEGORY_GUI, "Could not obtain file handle for source:", source.CString() ); + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, + "Could not obtain file handle for source:", + source.CString() ); return false; } @@ -295,27 +289,32 @@ bool RocketSDL2RenderInterface::LoadTexture(Rocket::Core::TextureHandle& texture Rocket::Core::String extension = source.Substring(i+1, source.Length()-i); Image surface; + Texture* texture = new Texture(); + NOM_ASSERT(texture != nullptr); if( surface.load_memory( buffer, buffer_size, extension.CString() ) == true) { - // ::ReleaseTexture is responsible for freeing this pointer - Texture* texture = new Texture(); if( texture->create( surface ) == true ) { - texture_handle = (Rocket::Core::TextureHandle) texture->texture(); + // ::ReleaseTexture is responsible for freeing this pointer now + texture_handle = (Rocket::Core::TextureHandle) texture; texture_dimensions = Rocket::Core::Vector2i(surface.width(), surface.height() ); } else { - NOM_LOG_ERR( NOM_LOG_CATEGORY_GUI, "Could not create texture handle from image source." ); + NOM_DELETE_PTR(texture); + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, + "Could not create texture handle from image source." ); return false; } return true; } - NOM_LOG_ERR( NOM_LOG_CATEGORY_GUI, "Could not create texture handle." ); + NOM_DELETE_PTR(texture); + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, + "Could not create texture handle." ); return false; } @@ -336,9 +335,6 @@ bool RocketSDL2RenderInterface::GenerateTexture(Rocket::Core::TextureHandle& tex Image surface; bool ret; - // ::ReleaseTexture is responsible for freeing this pointer - Texture* texture = new Texture(); - ret = surface.initialize( // pixels (void*) source, @@ -352,27 +348,40 @@ bool RocketSDL2RenderInterface::GenerateTexture(Rocket::Core::TextureHandle& tex source_dimensions.x * 4, rmask, gmask, bmask, amask ); + Texture* texture = new Texture(); + NOM_ASSERT(texture != nullptr); + if( ret ) { if( texture->create(surface) == false ) { - NOM_LOG_ERR( NOM_LOG_CATEGORY_GUI, "Could not generate texture from pixel data." ); + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, + "Could not generate texture from pixel data." ); + NOM_DELETE_PTR(texture); return false; } SDL_SetTextureBlendMode( texture->texture(), SDL_BLENDMODE_BLEND ); - texture_handle = (Rocket::Core::TextureHandle) texture->texture(); + + // ::ReleaseTexture is responsible for freeing this pointer now + texture_handle = (Rocket::Core::TextureHandle) texture; return true; } - NOM_LOG_ERR( NOM_LOG_CATEGORY_GUI, "Could not generate texture." ); + NOM_DELETE_PTR(texture); + + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, "Could not generate texture." ); return false; } -void RocketSDL2RenderInterface::ReleaseTexture(Rocket::Core::TextureHandle texture_handle) +void RocketSDL2RenderInterface:: +ReleaseTexture(Rocket::Core::TextureHandle texture_handle) { - priv::FreeTexture( (SDL_Texture*)texture_handle ); + auto texture = (nom::Texture*)texture_handle; + if( texture != nullptr ) { + NOM_DELETE_PTR(texture); + } } } // namespace nom diff --git a/src/gui/UIContext.cpp b/src/gui/UIContext.cpp index 66f21af7..10d62ebc 100644 --- a/src/gui/UIContext.cpp +++ b/src/gui/UIContext.cpp @@ -35,6 +35,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // Forward declarations #include "nomlib/system/Event.hpp" +#include "nomlib/system/EventHandler.hpp" #include "nomlib/gui/UIContextEventHandler.hpp" // Private headers @@ -339,7 +340,7 @@ bool UIContext::create_context( const std::string& name, const Size2i& res, } // Install the default event handler for this context - this->evt_.reset( new UIContextEventHandler( this ) ); + this->evt_.reset( new UIContextEventHandler(this) ); return true; } @@ -443,13 +444,15 @@ void UIContext::set_size(const Size2i& dims) this->context_->SetDimensions( Rocket::Core::Vector2i(res.w, res.h) ); } -void UIContext::process_event( const Event& ev ) +void UIContext::set_event_handler(nom::EventHandler& evt_handler) { - NOM_ASSERT( this->evt_ != nullptr ); - if( this->evt_ != nullptr ) - { - this->evt_->process_event( ev ); - } + this->event_handler_ = &evt_handler; + + auto event_watch = nom::event_filter( [=](const nom::Event& evt, void* data) { + this->process_event(evt); + }); + + this->event_handler_->append_event_watch(event_watch, nullptr); } void UIContext::update() @@ -538,4 +541,11 @@ void UIContext::initialize_debugger() // } } +void UIContext::process_event(const nom::Event& evt) +{ + if( this->evt_ != nullptr ) { + this->evt_->process_event(evt); + } +} + } // namespace nom diff --git a/src/gui/UIContextEventHandler.cpp b/src/gui/UIContextEventHandler.cpp index 963ca919..33aeb548 100644 --- a/src/gui/UIContextEventHandler.cpp +++ b/src/gui/UIContextEventHandler.cpp @@ -51,17 +51,17 @@ UIContextEventHandler::~UIContextEventHandler() NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE, nom::NOM_LOG_PRIORITY_VERBOSE ); } -void UIContextEventHandler::process_event( const Event& ev ) +void UIContextEventHandler::process_event(const nom::Event& ev) { switch( ev.type ) { default: break; - case SDL_WINDOWEVENT: + case nom::Event::WINDOW_EVENT: { - switch( ev.window.event ) + switch(ev.window.event) { - case SDL_WINDOWEVENT_SIZE_CHANGED: + case nom::WindowEvent::SIZE_CHANGED: { // Update desktop dimensions; this should not be used with SDL2's // independent resolution scale feature (logical view-port), @@ -69,42 +69,36 @@ void UIContextEventHandler::process_event( const Event& ev ) // the internally calculated aspect ratio that SDL2 does when using // the feature upon a size change. // this->ctx_->set_size( Size2i( ev.window.data1, ev.window.data2 ) ); - break; - } + } break; } - break; - } + } break; - case SDL_MOUSEMOTION: + case nom::Event::MOUSE_MOTION: { this->ctx_->context()->ProcessMouseMove( ev.motion.x, ev.motion.y, this->translate_key_modifiers(ev) ); - break; - } + } break; - case SDL_MOUSEBUTTONDOWN: + case nom::Event::MOUSE_BUTTON_CLICK: { this->ctx_->context()->ProcessMouseButtonDown( this->translate_mouse_button(ev), this->translate_key_modifiers(ev) ); - break; - } + } break; - case SDL_MOUSEBUTTONUP: + case nom::Event::MOUSE_BUTTON_RELEASE: { this->ctx_->context()->ProcessMouseButtonUp( this->translate_mouse_button(ev), this->translate_key_modifiers(ev) ); - break; - } + } break; - case SDL_MOUSEWHEEL: + case nom::Event::MOUSE_WHEEL: { this->ctx_->context()->ProcessMouseWheel( this->translate_mouse_wheel(ev), this->translate_key_modifiers(ev) ); - break; - } + } break; - case SDL_KEYDOWN: + case Event::KEY_PRESS: { // Intercept key input shift-~ for toggling the visual debugger if( ev.key.sym == SDLK_BACKQUOTE && ev.key.mod == KMOD_LSHIFT ) @@ -119,24 +113,19 @@ void UIContextEventHandler::process_event( const Event& ev ) this->ctx_->context()->ProcessKeyDown( this->translate_key(ev), this->translate_key_modifiers(ev) ); - break; - } // end SDL_KEYDOWN + } break; - case SDL_KEYUP: + case Event::KEY_RELEASE: { this->ctx_->context()->ProcessKeyUp( this->translate_key(ev), this->translate_key_modifiers(ev) ); - break; - } // end SDL_KEYUP + } break; - // TODO: Support Unicode text input with SDL_StartTextInput and - // SDL_StopTextInput; on mobile platforms, this will bring up the - // virtual keyboard for the end-user. - case SDL_TEXTINPUT: + // TODO: Implement + case Event::TEXT_INPUT: { - this->ctx_->context()->ProcessTextInput( ev.text.text ); - break; - } + this->ctx_->context()->ProcessTextInput(ev.text.text); + } break; } } @@ -483,55 +472,61 @@ UIContextEventHandler::translate_key( const Event& ev ) } } -int UIContextEventHandler::translate_mouse_button( const Event& ev ) +int UIContextEventHandler::translate_mouse_button(const Event& ev) { - switch( ev.mouse.button ) + int result = ev.mouse.button; + + switch(ev.mouse.button) { default: { - // Try to match what SDL2 appears to be returning - return ev.mouse.button + 1; - } + NOM_ASSERT_INVALID_PATH(); + } break; + + case nom::MouseButton::LEFT_MOUSE_BUTTON: + { + result = 0; + } break; - case SDL_BUTTON_LEFT: + case nom::MouseButton::RIGHT_MOUSE_BUTTON: { - return 0; - } + result = 1; + } break; - case SDL_BUTTON_RIGHT: + case nom::MouseButton::MIDDLE_MOUSE_BUTTON: { - return 1; - } + result = 2; + } break; - case SDL_BUTTON_MIDDLE: + case nom::MouseButton::X1_MOUSE_BUTTON: { - return 2; - } + result = 3; + } break; + + case nom::MouseButton::X2_MOUSE_BUTTON: + { + result = 4; + } break; } + + return result; } -int UIContextEventHandler::translate_mouse_wheel( const Event& ev ) +int UIContextEventHandler::translate_mouse_wheel(const Event& ev) { - if( ev.wheel.y > 0 ) // Up - { - return -1; - } - else if( ev.wheel.y < 0 ) // Down - { - return 1; - } - else if( ev.wheel.x > 0 ) // Left - { - return -1; - } - else if( ev.wheel.x < 0 ) // Right - { - return 1; - } - else // *shrug* Assume downward motion - { - return 1; + int result = 0; + + if( ev.wheel.y > 0 ) { + // Up + result = -1; + } else if( ev.wheel.y < 0 ) { + // Down + result = 1; + } else { + // Invalid } + + return result; } int UIContextEventHandler::translate_key_modifiers( const Event& ev ) diff --git a/src/gui/UIMessageBox.cpp b/src/gui/UIMessageBox.cpp index 488f3f56..91996618 100644 --- a/src/gui/UIMessageBox.cpp +++ b/src/gui/UIMessageBox.cpp @@ -195,8 +195,12 @@ void UIMessageBox::set_message_text( const std::string& text ) NOM_ASSERT( this->valid() != false ); rocket::Element* content = this->document()->GetElementById( this->message_id().c_str() ); - NOM_ASSERT( content != nullptr ); + if( content != nullptr ) { + if( content->GetInnerRML() != text.c_str() ) { + content->SetInnerRML( text.c_str() ); + } + } // std::shared_ptr container; // container.reset( this->document()->CreateElement( "p" ), free_element ); @@ -208,8 +212,6 @@ void UIMessageBox::set_message_text( const std::string& text ) // NOM_ASSERT( message != nullptr ); - content->SetInnerRML( text.c_str() ); - // FIXME: I believe that the second method call is the correct way -- we get // '

Hello, World

' if we disable the first and enable the second... // Otherwise, what's the point of creating the container??? diff --git a/src/math/Color4.cpp b/src/math/Color4.cpp index 4ccb10d8..17404389 100644 --- a/src/math/Color4.cpp +++ b/src/math/Color4.cpp @@ -28,6 +28,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ #include "nomlib/math/Color4.hpp" +// Private headers +#include "nomlib/core/strings.hpp" + namespace nom { template <> const uint8 Color4u::ALPHA_TRANSPARENT = 0; @@ -45,6 +48,7 @@ template <> const Color4i Color4i::null ( -1, -1, -1, Color4i::ALPHA_OPAQUE ); /// Null value for a nom::Color4 using floating point numbers template <> const Color4f Color4f::null ( -1, -1, -1, Color4f::ALPHA_OPAQUE ); +template <> const Color4i Color4i::Transparent(0, 0, 0, 0); template <> const Color4i Color4i::Black (0, 0, 0); template <> const Color4i Color4i::White (255, 255, 255); template <> const Color4i Color4i::Red (255, 0, 0); @@ -60,8 +64,7 @@ template <> const Color4i Color4i::LightGray (99, 99, 99); template <> const Color4i Color4i::Gray (67, 67, 67); template <> const Color4i Color4i::SkyBlue (110,144,190); -// template <> const Color4iColors Color4iColors::ButtonGradient{ Color4i( 201, 222, 241, 255 ), Color4i( 136, 183, 237, 255 ) }; - +template <> const Color4f Color4f::Transparent(0.0f, 0.0f, 0.0f, 0.0f); template <> const Color4f Color4f::Black (0.0f, 0.0f, 0.0f); template <> const Color4f Color4f::White (1.0f, 1.0f, 1.0f); template <> const Color4f Color4f::Red (1.0f, 0.0f, 0.0f); @@ -71,4 +74,102 @@ template <> const Color4f Color4f::Yellow (1.0f, 1.0f, 0.0f); template <> const Color4f Color4f::Magenta (1.0f, 0.0f, 1.0f); template <> const Color4f Color4f::Cyan (0.0f, 1.0f, 1.0f); +Color4i +make_color_from_hex_string(const std::string& hex_encoding) +{ + std::string hex_str = hex_encoding; + auto hex_str_len = hex_encoding.length(); + nom::size_type pos = 1; // string position + const nom::size_type NUM_CHARS = 2; // increment by + Color4i result = Color4i::Black; // catch-all case + + if(hex_str.empty() == true) { + return result; + } + + if(hex_str[0] != '#') { + pos = 0; + } + + std::string red_str; + std::string green_str; + std::string blue_str; + + if(hex_str_len < 2) { + red_str = "0"; + } else { + red_str = hex_encoding.substr(pos, NUM_CHARS); + pos += NUM_CHARS; + } + + red_str = "0x" + red_str; + + if(hex_str_len < 3) { + green_str = "0"; + } else { + green_str = hex_encoding.substr(pos, NUM_CHARS); + pos += NUM_CHARS; + } + + green_str = "0x" + green_str; + + if(hex_str_len < 6) { + blue_str = "0"; + } else { + blue_str = hex_encoding.substr(pos, NUM_CHARS); + } + + blue_str = "0x" + blue_str; + + int red_channel = nom::string_to_int(red_str.c_str()); + int green_channel = nom::string_to_int(green_str.c_str()); + int blue_channel = nom::string_to_int(blue_str.c_str()); + + result.r = red_channel; + result.g = green_channel; + result.b = blue_channel; + + return result; +} + +Color4i +make_color_from_string(const std::string& color) +{ + Color4i result(Color4i::Transparent); + + if( nom::compare_string_insensitive(color, "transparent") == 0 ) { + result = Color4i::Transparent; + } else if( nom::compare_string_insensitive(color, "white") == 0 ) { + result = Color4i::White; + } else if( nom::compare_string_insensitive(color, "red") == 0 ) { + result = Color4i::Red; + } else if( nom::compare_string_insensitive(color, "green") == 0 ) { + result = Color4i::Green; + } else if( nom::compare_string_insensitive(color, "blue") == 0 ) { + result = Color4i::Blue; + } else if( nom::compare_string_insensitive(color, "yellow") == 0 ) { + result = Color4i::Yellow; + } else if( nom::compare_string_insensitive(color, "magenta") == 0 ) { + result = Color4i::Magenta; + } else if( nom::compare_string_insensitive(color, "cyan") == 0 ) { + result = Color4i::Cyan; + } else if( nom::compare_string_insensitive(color, "silver") == 0 ) { + result = Color4i::Silver; + } else if( nom::compare_string_insensitive(color, "purple") == 0 ) { + result = Color4i::Purple; + } else if( nom::compare_string_insensitive(color, "orange") == 0 ) { + result = Color4i::Orange; + } else if( nom::compare_string_insensitive(color, "lightgray") == 0 || + nom::compare_string_insensitive(color, "light gray") == 0 ) + { + result = Color4i::LightGray; + } else if( nom::compare_string_insensitive(color, "gray") == 0 ) { + result = Color4i::Gray; + } else if( nom::compare_string_insensitive(color, "skyblue") == 0 ) { + result = Color4i::SkyBlue; + } + + return result; +} + } // namespace nom diff --git a/src/math/Point3.cpp b/src/math/Point3.cpp index 08b3ad17..4018bc59 100644 --- a/src/math/Point3.cpp +++ b/src/math/Point3.cpp @@ -31,12 +31,22 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. namespace nom { /// Null value for a nom::Point3 using signed integers -template <> const Point3i Point3i::null ( -1, -1, -1 ); +template <> const Point3i Point3i::null(-1, -1, -1); /// Null value for a nom::Point3 using floating point numbers -template <> const Point3f Point3f::null ( -1.0f, -1.0f, -1.0f ); +template <> const Point3f Point3f::null(-1.0f, -1.0f, -1.0f); /// Null value for a nom::Point3 using double precision floating point numbers -template <> const Point3d Point3d::null ( -1.0f, -1.0f, -1.0f ); +template <> const Point3d Point3d::null(-1.0f, -1.0f, -1.0f); + +/// \brief Zero value for a nom::Point3 using signed integers +template <> const Point3i Point3i::zero(0, 0, 0); + +/// \brief Zero value for a nom::Point3 using 32-bit floating-point numbers. +template <> const Point3f Point3f::zero(0.0f, 0.0f, 0.0f); + +/// \brief Zero value for a nom::Point3 using double precision (64-bit) +/// floating-point numbers. +template <> const Point3d Point3d::zero(0.0f, 0.0f, 0.0f); } // namespace nom diff --git a/src/audio/NullMusic.cpp b/src/platforms.cpp similarity index 58% rename from src/audio/NullMusic.cpp rename to src/platforms.cpp index e77df74f..b334d128 100644 --- a/src/audio/NullMusic.cpp +++ b/src/platforms.cpp @@ -2,7 +2,7 @@ nomlib - C++11 cross-platform game engine -Copyright (c) 2013, 2014 Jeffrey Carpenter +Copyright (c) 2013, 2014, 2015 Jeffrey Carpenter All rights reserved. Redistribution and use in source and binary forms, with or without @@ -26,48 +26,50 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ -#include "nomlib/audio/NullMusic.hpp" +#include "nomlib/platforms.hpp" +#include "nomlib/types.hpp" -namespace nom { - -NullMusic::NullMusic( void ) -{ - NOM_LOG_TRACE( NOM_LOG_CATEGORY_TRACE_AUDIO ); -} - -NullMusic::~NullMusic( void ) -{ - NOM_LOG_TRACE( NOM_LOG_CATEGORY_TRACE_AUDIO ); -} - -void NullMusic::setBuffer( const ISoundBuffer& copy ) -{ - // Do nothing -} +#include -void NullMusic::Play( void ) -{ - // Do nothing -} - -void NullMusic::Stop( void ) -{ - // Do nothing -} - -void NullMusic::Pause( void ) -{ - // Do nothing -} +namespace nom { -void NullMusic::togglePause( void ) +// Source: http://www.brue.org/2013/01/using-rdtsc-in-gcc-and-clang/ +volatile uint64 rdtsc() { - // Do nothing + uint32 a = 0.0f; + uint32 d = 0.0f; + asm volatile + (".byte 0x0f, 0x31 #rdtsc\n" // edx:eax + :"=a"(a), "=d"(d)::); + return( ( (uint64) d) << 32) | (uint64) a; } -void NullMusic::fadeOut( float seconds ) +PlatformSpec platform_info() { - // Do nothing + PlatformSpec spec = {}; + spec.num_cpus = SDL_GetCPUCount(); + auto cpu_cache_size_kb = SDL_GetCPUCacheLineSize(); + auto total_ram_kb = SDL_GetSystemRAM(); + + // Conversion to bytes + spec.cpu_cache_size = cpu_cache_size_kb * nom::NOM_BYTE; + spec.total_ram = total_ram_kb * nom::NOM_BYTE; + +#if defined(NOM_PLATFORM_OSX) + spec.name = "Mac OS X"; + spec.env = PLATFORM_OSX; +#elif defined(NOM_PLATFORM_LINUX) + spec.name = "Linux"; + spec.env = PLATFORM_LINUX; +#elif defined(NOM_PLATFORM_BSD) + spec.name = "BSD"; + spec.env = PLATFORM_BSD; +#elif defined(NOM_PLATFORM_WINDOWS) + spec.name = "Windows"; + spec.env = PLATFORM_WINDOWS; +#endif + + return spec; } } // namespace nom diff --git a/src/ptree/VString.cpp b/src/ptree/VString.cpp index 839274e7..efb0639f 100644 --- a/src/ptree/VString.cpp +++ b/src/ptree/VString.cpp @@ -28,118 +28,98 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ #include "nomlib/ptree/VString.hpp" -namespace nom { +#include "nomlib/core/strings.hpp" -VString::VString( void ) : - value_( nullptr ), - index_( 0 ) -{ - //NOM_LOG_TRACE(NOM); -} +namespace nom { -VString::~VString( void ) +VString::VString() { - //NOM_LOG_TRACE(NOM); + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE, NOM_LOG_PRIORITY_VERBOSE); - // free( const_cast ( this->value_ ) ); - // this->value_ = nullptr; + this->index_ = 0; } -VString::VString( ArrayIndex index ) : - value_( nullptr ), - index_( index ) +VString::~VString() { - //NOM_LOG_TRACE(NOM); + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE, NOM_LOG_PRIORITY_VERBOSE); } -VString::VString( const char* key ) : - index_( 0 ) +VString::VString(const char* key) { - //NOM_LOG_TRACE(NOM); + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE, NOM_LOG_PRIORITY_VERBOSE); - uint size = strlen( key ); - this->value_ = priv::duplicate_string( key, size ); + this->cstr_ = std::string(key); + this->index_ = 0; } -VString::VString( const std::string& key ) : - index_( 0 ) +VString::VString(const std::string& key) { - //NOM_LOG_TRACE(NOM); + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE, NOM_LOG_PRIORITY_VERBOSE); - uint size = strlen( key.c_str() ); - this->value_ = priv::duplicate_string( key.c_str(), size ); + this->cstr_ = key; + this->index_ = 0; } -VString::VString( const SelfType& copy ) : - index_{ copy.index() } +VString::VString(ArrayIndex index) { - // NOM_LOG_TRACE(NOM); + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE, NOM_LOG_PRIORITY_VERBOSE); - this->value_ = copy.value_; + this->index_ = index; } -VString::SelfType& VString::operator =( const SelfType& other ) +VString::VString(const self_type& rhs) { - VString temp( other ); - this->swap( temp ); + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE, NOM_LOG_PRIORITY_VERBOSE); - return *this; + this->cstr_ = rhs.cstr_; + this->index_ = rhs.index_; } -void VString::swap( VString& other ) +VString::self_type& VString::operator =(const self_type& rhs) { - std::swap( this->value_, other.value_ ); - std::swap( this->index_, other.index_ ); + VString copy(rhs); + this->swap(copy); + + return *this; } -bool VString::valid( void ) const +void VString::swap(VString& rhs) { - if( this->c_str() != nullptr ) return true; - - return false; + std::swap(this->cstr_, rhs.cstr_); + std::swap(this->index_, rhs.index_); } -bool VString::operator <( const VString& other ) const +// TODO: Try to use a more efficient sorting algorithm for string comparisons? +bool VString::operator <(const self_type& rhs) const { - // Key member - if( this->valid() && other.valid() ) - { - return strcmp( this->c_str(), other.c_str() ) < 0; - } - - // Array element index - else - { - return( this->index() < other.index() ); + if( this->cstr_ != "" && rhs.cstr_ != "" ) { + return( nom::compare_string_sensitive(this->cstr_, rhs.cstr_) < 0); + } else { + return(this->index_ < rhs.index_); } } -bool VString::operator ==( const VString& other ) const +// TODO: Try to use a more efficient sorting algorithm for string comparisons? +bool VString::operator ==(const self_type& rhs) const { - // Key member - if( this->valid() && other.valid() ) - { - return strcmp( this->c_str(), other.c_str() ) == 0; - } - - // Array element index - else - { - return( this->index() == other.index() ); + if( this->cstr_ != "" && rhs.cstr_ != "" ) { + return( nom::compare_string_sensitive(this->cstr_, rhs.cstr_) == 0); + } else { + return(this->index_ == rhs.index_); } } -const char* VString::c_str( void ) const +std::string VString::string() const { - return this->value_; + return this->cstr_; } -const std::string VString::get_string( void ) const +const char* VString::c_str() const { - return std::string( this->c_str() ); + return this->cstr_.c_str(); } -ArrayIndex VString::index( void ) const +ArrayIndex VString::index() const { return this->index_; } diff --git a/src/ptree/Value.cpp b/src/ptree/Value.cpp index 32d5c4fe..40e8d6f2 100644 --- a/src/ptree/Value.cpp +++ b/src/ptree/Value.cpp @@ -29,6 +29,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "nomlib/ptree/Value.hpp" // Private headers +#include "nomlib/core/strings.hpp" #include // Forward declarations @@ -44,461 +45,398 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. namespace nom { // Static initializations -const Value Value::null = Value(); +const Value& Value::null = Value(); -Value::Value( void ) : - type_ ( ValueType::Null ) +Value::Value() : + type_(ValueType::Null) { // NOM_LOG_TRACE(NOM); } -// FIXME -Value::~Value( void ) +Value::~Value() { - // NOM_LOG_TRACE(NOM); -/* - if( this->array_valid() ) - { - // this->value_.array_->clear(); - for( auto itr = this->value_.object_->begin(); itr != this->value_.object_->end(); ++itr ) - // for( auto itr = this->value_.array_->begin(); itr != this->value_.array_->end(); --itr ) - { - if( itr->array_valid() ) - { - // NOM_DUMP("a"); - // delete itr->value_.array_; - } - else if( itr->object_valid() ) - { - // NOM_DUMP("o"); - // delete this->value_.object_; - } - } - // delete this->value_.array_; - // this->value_.array_ = nullptr; - } -*/ - // else - if( this->object_valid() ) - { - // this->value_.object_->clear(); - // for( auto itr = this->value_.object_->end(); itr != this->value_.object_->begin(); --itr ) - for( auto itr = this->value_.object_->begin(); itr != this->value_.object_->end(); ++itr ) + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE, NOM_LOG_PRIORITY_VERBOSE); + + switch(this->type_) { + + default: /* Nothing to free for most primitive types */ break; + + case ValueType::String: { - if( itr->second.array_valid() ) - { - // NOM_DUMP("aa"); - // delete itr->second.value_.array_; - } - else if( itr->second.object_valid() ) - { - // NOM_DUMP("oo"); - // delete itr->second.value_.object_; + if( this->value_.string_ != nullptr ) { + nom::free_string(this->value_.string_); + this->value_.string_ = nullptr; } - } - // this->value_.object_->clear(); - // delete this->value_.object_; - // this->value_.object_ = nullptr; - } - else if( this->string_type() ) - { - if( this->string_allocated_ ) + } break; + + case ValueType::ArrayValues: + case ValueType::ObjectValues: { - // See priv::duplicate_string - free( const_cast ( this->get_cstring() ) ); - this->value_.string_ = nullptr; - } - } - else - { - // Do nothing + NOM_DELETE_PTR(this->value_.object_); + } break; } } -Value::Value( const Value& copy ) : - type_ ( copy.type() ), - string_allocated_ ( false ) +Value::Value(int val) : + type_(ValueType::SignedInteger) +{ + //NOM_LOG_TRACE(NOM); + + this->value_.int_ = val; +} + +Value::Value(uint val) : + type_(ValueType::UnsignedInteger) +{ + //NOM_LOG_TRACE(NOM); + + this->value_.uint_ = val; +} + +Value::Value(real64 val) : + type_(ValueType::RealNumber) +{ + //NOM_LOG_TRACE(NOM); + + this->value_.real_ = val; +} + +Value::Value(const char* str) : + type_(ValueType::String) +{ + //NOM_LOG_TRACE(NOM); + + nom::size_type str_len = nom::string_length(str); + this->value_.string_ = nom::duplicate_string(str, str_len); +} + +Value::Value(const std::string& str) : + type_(ValueType::String) +{ + //NOM_LOG_TRACE(NOM); + + nom::size_type str_len = nom::string_length(str); + this->value_.string_ = nom::duplicate_string(str, str_len); +} + +Value::Value(bool val) : + type_(ValueType::Boolean) +{ + //NOM_LOG_TRACE(NOM); + + this->value_.bool_ = val; +} + +Value::Value(const Object& obj) : + type_(ValueType::ObjectValues) { // NOM_LOG_TRACE(NOM); - switch( this->type() ) + this->value_.object_ = new Object(obj); +} + +Value::Value(ValueType type) : + type_(type) +{ + // NOM_LOG_TRACE( NOM ); + + switch(this->type_) { default: case ValueType::Null: - { - // Do nothing - break; - } + break; + case ValueType::SignedInteger: { - this->value_.int_ = copy.value_.int_; - break; - } + this->value_.int_ = 0; + } break; + case ValueType::UnsignedInteger: { - this->value_.uint_ = copy.value_.uint_; - break; - } + this->value_.uint_ = 0; + } break; + case ValueType::RealNumber: { - this->value_.real_ = copy.value_.real_; - break; - } + this->value_.real_ = 0.0f; + } break; case ValueType::Boolean: { - this->value_.bool_ = copy.value_.bool_; - break; - } + this->value_.bool_ = false; + } break; case ValueType::String: { - if( copy.get_cstring() ) - { - uint size = strlen( copy.get_cstring() ); - this->value_.string_ = priv::duplicate_string( copy.get_cstring(), size ); - this->string_allocated_ = true; - break; - } - else - { - this->value_.string_ = nullptr; - this->string_allocated_ = false; - break; - } - } + this->value_.string_ = nullptr; + } break; case ValueType::ArrayValues: case ValueType::ObjectValues: { - this->value_.object_ = copy.value_.object_; - break; - } - } // end switch type -} - -Value::SelfType& Value::operator =( const SelfType& other ) -{ - Value temp( other ); - - this->swap( temp ); - - return *this; -} - -void Value::swap( Value& other ) -{ - Value::ValueType temp = this->type(); - - this->type_ = other.type(); - other.type_ = temp; - std::swap( this->value_, other.value_ ); - - int temp2 = this->string_allocated_; - - this->string_allocated_ = other.string_allocated_; - other.string_allocated_ = temp2; + this->value_.object_ = new Object(); + NOM_ASSERT(this->value_.object_ != nullptr); + } break; + } } -bool Value::operator <( const Value& other ) const +Value::Value(const Value& rhs) { - int type_delta = this->type() - other.type(); + // NOM_LOG_TRACE(NOM); - if( type_delta ) - { - return type_delta < 0 ? true : false; - } + this->type_ = rhs.type_; - switch( this->type() ) + switch(this->type_) { default: case ValueType::Null: { - return false; + // Do nothing break; } - case ValueType::SignedInteger: { - return this->value_.int_ < other.value_.int_; + this->value_.int_ = rhs.value_.int_; break; } - case ValueType::UnsignedInteger: { - return this->value_.uint_ < other.value_.uint_; + this->value_.uint_ = rhs.value_.uint_; break; } - case ValueType::RealNumber: { - return this->value_.real_ < other.value_.real_; + this->value_.real_ = rhs.value_.real_; break; } case ValueType::Boolean: { - return this->value_.bool_ < other.value_.bool_; + this->value_.bool_ = rhs.value_.bool_; break; } case ValueType::String: { - return( this->value_.string_ == 0 && other.value_.string_ ) - || ( other.value_.string_ && this->value_.string_ - && strcmp - ( this->value_.string_, other.value_.string_ ) < 0 ); - break; - } - - case ValueType::ArrayValues: - { - int delta = int ( this->value_.object_->size() - other.value_.object_->size() ); + const char* rhs_string_value = rhs.value_.string_; + if( rhs_string_value != nullptr ) { + nom::size_type str_len = nom::string_length(rhs_string_value); - if( delta ) return delta < 0; - - break; - - return( *this->value_.object_ ) < ( *other.value_.object_ ); - - break; - } + this->value_.string_ = + nom::duplicate_string(rhs_string_value, str_len); + } + } break; + case ValueType::ArrayValues: case ValueType::ObjectValues: { - int delta = int ( this->value_.object_->size() - other.value_.object_->size() ); - - if( delta ) return delta < 0; - - break; - - return( *this->value_.object_ ) < ( *other.value_.object_ ); - - break; - } + this->value_.object_ = new Object(*rhs.value_.object_); + } break; } // end switch type - - return false; // Err } -bool Value::operator <= ( const Value& other ) const +Value::SelfType& Value::operator =(const SelfType& rhs) { - return ! ( other < *this ); -} + if( this != &rhs ) { + Value copy(rhs); + this->swap(copy); + } -bool Value::operator >= ( const Value& other ) const -{ - return ! ( *this < other ); + return *this; } -bool Value::operator > ( const Value& other ) const +void Value::swap(Value& rhs) { - return ( other < *this ); + Value::ValueType temp = this->type_; + + this->type_ = rhs.type_; + rhs.type_ = temp; + std::swap(this->value_, rhs.value_); } -bool Value::operator == ( const Value& other ) const +bool Value::operator <(const Value& rhs) const { - //if ( type_ != other.type_ ) - // GCC 2.95.3 says: - // attempt to take address of bit-field structure member `Json::Value::type_' - // Beats me, but a temp solves the problem. - int temp = other.type_; + bool comp_result = false; - if ( this->type() != temp ) - { - return false; + int type_delta = this->type_ - rhs.type_; + if( type_delta > 0 ) { + comp_result = (type_delta < 0) ? true : false; } - switch( type() ) + switch(this->type_) { default: case ValueType::Null: { - return true; - break; - } + comp_result = false; + } break; case ValueType::SignedInteger: { - return this->value_.int_ == other.value_.int_; - break; - } + comp_result = this->value_.int_ < rhs.value_.int_; + } break; case ValueType::UnsignedInteger: { - return this->value_.uint_ == other.value_.uint_; - break; - } + comp_result = this->value_.uint_ < rhs.value_.uint_; + } break; case ValueType::RealNumber: { - return this->value_.real_ == other.value_.real_; - break; - } + comp_result = this->value_.real_ < rhs.value_.real_; + } break; case ValueType::Boolean: { - return this->value_.bool_ == other.value_.bool_; - break; - } + comp_result = this->value_.bool_ < rhs.value_.bool_; + } break; case ValueType::String: { - return( this->value_.string_ == other.value_.string_ ) - || ( other.value_.string_ && this->value_.string_ - && strcmp ( this->value_.string_, other.value_.string_ ) == 0 ); + if( this->value_.string_ == nullptr || rhs.value_.string_ == nullptr ) { - break; - } + if( rhs.value_.string_ != nullptr ) { + comp_result = true; + } else { + comp_result = false; + } + } else { + comp_result = + nom::compare_cstr_sensitive(this->value_.string_, rhs.value_.string_) < 0; + } + } break; case ValueType::ArrayValues: - { - return this->value_.object_->size() == other.value_.object_->size() - && ( *this->value_.object_ ) == ( *other.value_.object_ ); - - break; - } - case ValueType::ObjectValues: { - return this->value_.object_->size() == other.value_.object_->size() - && ( *this->value_.object_ ) == ( *other.value_.object_ ); - - break; - } + int delta = + this->value_.object_->size() - rhs.value_.object_->size(); + if( delta > 0 ) { + comp_result = delta < 0; + } else { + comp_result = *this->value_.object_ < *rhs.value_.object_; + } + } break; } // end switch type - return false; // Err + return comp_result; } -bool Value::operator != ( const Value& other ) const +bool Value::operator <=(const Value& rhs) const { - return ! ( *this == other ); + return !( rhs < *this ); } -bool Value::operator !( void ) const +bool Value::operator >=(const Value& rhs) const { - return this->null_type(); + return !( *this < rhs ); } -Value::Value( enum ValueType type ) : - type_ ( type ) +bool Value::operator >(const Value& rhs) const { - // NOM_LOG_TRACE( NOM ); + return( rhs < *this ); +} + +bool Value::operator ==(const Value& rhs) const +{ + bool comp_result = false; + + if( this->type_ != rhs.type_ ) { + comp_result = false; + return comp_result; + } - switch( this->type() ) + switch(this->type_) { default: case ValueType::Null: + { + comp_result = true; + } break; + case ValueType::SignedInteger: + { + comp_result = this->value_.int_ == rhs.value_.int_; + } break; + case ValueType::UnsignedInteger: + { + comp_result = this->value_.uint_ == rhs.value_.uint_; + } break; + case ValueType::RealNumber: + { + comp_result = this->value_.real_ == rhs.value_.real_; + } break; + case ValueType::Boolean: { - // Do nothing extra - break; - } + comp_result = this->value_.bool_ == rhs.value_.bool_; + } break; case ValueType::String: { - // TODO: Allocate string (duplicate?) - break; - } + if( this->value_.string_ == nullptr || rhs.value_.string_ == nullptr ) { + comp_result = this->value_.string_ == rhs.value_.string_; + } else { + comp_result = + nom::compare_cstr_sensitive(this->value_.string_, rhs.value_.string_) == 0; + } + } break; case ValueType::ArrayValues: - { - this->value_.object_ = new Object(); - break; - } - case ValueType::ObjectValues: { - this->value_.object_ = new Object(); - break; - } + comp_result = + ( this->value_.object_->size() == rhs.value_.object_->size() ) && + ( *this->value_.object_ ) == ( *rhs.value_.object_ ); + } break; } -} -Value::Value( sint val ) : - type_ ( ValueType::SignedInteger ) -{ - //NOM_LOG_TRACE(NOM); - this->value_.int_ = val; + return comp_result; } -Value::Value( uint val ) : - type_ ( ValueType::UnsignedInteger ) +bool Value::operator != (const Value& rhs) const { - //NOM_LOG_TRACE(NOM); - this->value_.uint_ = val; + return !(*this == rhs); } -Value::Value( double val ) : - type_ ( ValueType::RealNumber ) +bool Value::operator !() const { - //NOM_LOG_TRACE(NOM); - this->value_.real_ = val; -} - -Value::Value( const char* val ) : - type_ ( ValueType::String ) -{ - //NOM_LOG_TRACE(NOM); - uint size = strlen( val ); - this->value_.string_ = priv::duplicate_string( val, size ); - this->string_allocated_ = true; -} - -Value::Value( const std::string& val ) : - type_ ( ValueType::String ) -{ - //NOM_LOG_TRACE(NOM); - uint size = val.length(); - this->value_.string_ = priv::duplicate_string( val.c_str(), size ); - this->string_allocated_ = true; -} - -Value::Value( bool val ) : - type_ ( ValueType::Boolean ) -{ - //NOM_LOG_TRACE(NOM); - this->value_.bool_ = val; -} - -Value::Value( const Object& val ) : - type_ ( ValueType::ObjectValues ) -{ - //NOM_LOG_TRACE(NOM); - this->value_.object_ = new Object( val ); - // this->object_ = val; + return this->null_type(); } // Derives from the JsonCpp library -sint Value::compare( const Value& other ) const +int Value::compare(const Value& rhs) const { - if ( *this < other ) return -1; - - if ( *this > other ) return 1; + int comp_result = 0; + if( *this < rhs ) { + comp_result = -1; + } else if( *this > rhs ) { + comp_result = 1; + } else { + comp_result = 0; + } - return 0; + return comp_result; } -Value::RawPtr Value::get( void ) +Value::RawPtr Value::get() { return this; } -const Value::Reference Value::ref( void ) +Value::Reference Value::ref() { return *this; } -enum Value::ValueType Value::type( void ) const +enum Value::ValueType Value::type() const { return this->type_; } -const std::string Value::type_name( void ) const +const std::string Value::type_name() const { switch ( this->type() ) { @@ -515,47 +453,47 @@ const std::string Value::type_name( void ) const } } -bool Value::null_type( void ) const +bool Value::null_type() const { return ( this->type() == ValueType::Null ); } -bool Value::int_type( void ) const +bool Value::int_type() const { return ( this->type() == ValueType::SignedInteger ); } -bool Value::uint_type( void ) const +bool Value::uint_type() const { return ( this->type() == ValueType::UnsignedInteger ); } -bool Value::double_type( void ) const +bool Value::double_type() const { return ( this->type() == ValueType::RealNumber ); } -bool Value::string_type( void ) const +bool Value::string_type() const { return ( this->type() == ValueType::String ); } -bool Value::bool_type( void ) const +bool Value::bool_type() const { return ( this->type() == ValueType::Boolean ); } -bool Value::array_type( void ) const +bool Value::array_type() const { return ( this->type() == ValueType::ArrayValues ); } -bool Value::object_type( void ) const +bool Value::object_type() const { return ( this->type() == ValueType::ObjectValues ); } -const std::string Value::stringify( void ) const +const std::string Value::stringify() const { if( this->null_type() ) { @@ -598,37 +536,29 @@ const std::string Value::stringify( void ) const } } -sint Value::get_int ( void ) const +int Value::get_int() const { - // NOM_ASSERT( this->int_type() ); - if( this->int_type() ) return this->value_.int_; return 0; // Not found } -uint Value::get_uint ( void ) const +uint Value::get_uint() const { - // NOM_ASSERT( this->uint_type() ); - if( this->uint_type() ) return this->value_.uint_; return 0; // Not found } -double Value::get_double ( void ) const +real64 Value::get_double() const { - // NOM_ASSERT( this->double_type() ); - if( this->double_type() ) return this->value_.real_; return 0; // Not found } -float Value::get_float ( void ) const +real32 Value::get_float() const { - // NOM_ASSERT( this->double_type() ); - if( this->double_type() ) { return static_cast ( this->get_double() ); @@ -637,46 +567,34 @@ float Value::get_float ( void ) const return 0; // Not found } -const char* Value::get_cstring ( void ) const +const char* Value::get_cstring() const { - // NOM_ASSERT( this->string_type() ); - - if( this->string_type() ) - { - uint size = strlen( this->value_.string_ ); - return priv::duplicate_string( this->value_.string_, size ); - // return this->value_.string_; + if( this->string_type() ) { + return this->value_.string_; } - return nullptr; // If no string value is found + return nullptr; } -const std::string Value::get_string ( void ) const +std::string Value::get_string() const { - // NOM_ASSERT( this->string_type() ); - - if( this->string_type() ) - { - // char* to std::string - return std::string( this->get_cstring() ); + if( this->string_type() ) { + return std::string(this->value_.string_); } - return "\0"; // Null-terminated string if no string value is found + return "\0"; } -bool Value::get_bool ( void ) const +bool Value::get_bool() const { - // NOM_ASSERT( this->bool_type() ); - - if( this->bool_type() ) - { + if( this->bool_type() ) { return this->value_.bool_; } return false; } -bool Value::array_valid( void ) const +bool Value::array_valid() const { if( this->array_type() ) { @@ -689,7 +607,7 @@ bool Value::array_valid( void ) const return false; } -bool Value::object_valid( void ) const +bool Value::object_valid() const { if( this->object_type() ) { @@ -702,10 +620,8 @@ bool Value::object_valid( void ) const return false; } -const Object Value::array( void ) const +const Object Value::array() const { - // NOM_ASSERT( this->array_type() ); - if( this->array_valid() ) { return Object( *this->value_.object_ ); @@ -715,10 +631,8 @@ const Object Value::array( void ) const return Object(); } -const Object Value::object( void ) const +const Object Value::object() const { - NOM_ASSERT( this->object_type() ); - if( this->object_valid() ) { return Object( *this->value_.object_ ); @@ -728,251 +642,190 @@ const Object Value::object( void ) const return Object(); } -uint Value::size( void ) const +nom::size_type Value::size() const { - switch( this->type() ) - { - default: return 1; break; // Non-NULL, non-array, non-object value type + nom::size_type result = 0; - case ValueType::Null: return 0; break; // Empty + switch(this->type_) + { + default: + { + result = 1; + } break; - case ValueType::ArrayValues: + case ValueType::Null: { - if( this->array_valid() ) - { - return this->value_.object_->size(); - } - return 0; // Not initialized + result = 0; + } break; - break; - } + case ValueType::ArrayValues: case ValueType::ObjectValues: { - if( this->object_valid() ) - { - return this->value_.object_->size(); + if( this->array_valid() == true || this->object_valid() == true ) { + result = this->value_.object_->size(); } - return 0; // Not initialized - - break; - } + } break; } + + return result; } bool Value::empty() const { - NOM_ASSERT( this->null_type() || this->array_type() || this->object_type() ); + // NOM_ASSERT( this->null_type() || this->array_type() || this->object_type() ); - switch( this->type() ) + bool result = true; + + switch(this->type_) { - // Not an array or object type - default: /* Do nothing */ break; + default: break; case ValueType::ArrayValues: - { - if( this->array_valid() ) - { - return this->value_.object_->empty(); - } - - break; - } - case ValueType::ObjectValues: { - if( this->object_valid() ) - { - return this->value_.object_->empty(); + if( this->array_valid() == true || this->object_valid() == true ) { + result = this->value_.object_->empty(); } + } break; + } - break; - } - } // end switch type - - return true; + return result; } -void Value::clear( void ) +void Value::clear() { - NOM_ASSERT( this->null_type() || this->array_type() || this->object_type() ); + // NOM_ASSERT( this->null_type() || this->array_type() || this->object_type() ); - switch( this->type() ) + switch(this->type_) { - // Not an array or object type - default: /* Do nothing */ break; + default: break; case ValueType::ArrayValues: - { - if( this->array_valid() ) - { - this->value_.object_->clear(); - } - - break; - } - case ValueType::ObjectValues: { - if( this->object_valid() ) - { + if( this->array_valid() == true || this->object_valid() == true ) { this->value_.object_->clear(); } - - break; - } + } break; } } // Derives from JsonCpp implementation -Value& Value::operator[]( ArrayIndex index ) +Value& Value::operator[](ArrayIndex index) { NOM_ASSERT( this->null_type() || this->array_type() ); - if( this->null_type() ) - { - *this = Value( ValueType::ArrayValues ); + if( this->null_type() ) { + *this = Value(ValueType::ArrayValues); } - VString key( index ); + VString key(index); // Returns an iterator pointing to the first element in the container whose // key is not considered to go before k -- key_type -- (i.e., either it is // equivalent or goes after). - ObjectIterator it = this->value_.object_->lower_bound( key ); + ObjectIterator it = this->value_.object_->lower_bound(key); - if( it != this->value_.object_->end() && (*it).first == key ) - { + if( it != this->value_.object_->end() && (*it).first == key ) { return (*it).second; } - ObjectPair default_value( key, Value::null ); + ObjectPair default_value(key, Value::null); - it = this->value_.object_->insert( it, default_value ); + it = this->value_.object_->insert(it, default_value); return (*it).second; } -// Derives from JsonCpp implementation -Value& Value::operator[]( int index ) +Value& Value::operator[](int index) { - NOM_ASSERT( index >= 0 ); + NOM_ASSERT(index >= 0); - return (*this)[ ArrayIndex( index ) ]; + return (*this)[ ArrayIndex(index) ]; } -// Derives from JsonCpp implementation -const Value& Value::operator[]( ArrayIndex index ) const +const Value& Value::operator[](ArrayIndex index) const { NOM_ASSERT( this->null_type() || this->array_type() ); - if( this->null_type() ) - { - return null; + if( this->null_type() ) { + return Value::null; } - VString key( index ); + VString key(index); - ObjectConstIterator it = this->value_.object_->find( key ); + ObjectConstIterator it = this->value_.object_->find(key); - if( it == this->value_.object_->end() ) - { - return null; + if( it == this->value_.object_->end() ) { + return Value::null; } return (*it).second; } -// Derives from JsonCpp implementation -const Value& Value::operator[]( int index ) const +const Value& Value::operator[](int index) const { - NOM_ASSERT( index >= 0 ); + NOM_ASSERT(index >= 0); - return (*this)[ ArrayIndex( index ) ]; + return (*this)[ ArrayIndex(index) ]; } -/*const*/ Value& Value::operator[]( const char* key ) /*const*/ +// Derives from JsonCpp implementation +Value& Value::operator[](const char* key) { - // An object node container is required for this method call. - if( ! this->object_valid() ) - { - this->type_ = ValueType::ObjectValues; - this->value_.object_ = new Object(); + NOM_ASSERT( this->null_type() || this->object_type() ); + if( this->null_type() ) { + *this = Value(ValueType::ObjectValues); } -// TODO: TRY ME OUT -/* - NOM_ASSERT( this->null_type() || this->object_type() ); + auto it = this->value_.object_->lower_bound(key); - if( this->null_type() ) - { - return Value::null; + if( it != this->value_.object_->end() && (*it).first == key ) { + return (*it).second; } -*/ - auto res = this->value_.object_->find( key ); - if( res == this->value_.object_->end() ) // No match found! - { - this->value_.object_->insert( std::pair( key, Value() ) ); + auto default_value = + std::pair(key, Value::null); + auto res = this->value_.object_->insert(it, default_value); - auto res2 = this->value_.object_->find( key ); + Value& value = (*res).second; + return value; +} - if( res2 != this->value_.object_->end() ) // Match found! - { - return res2->second; - } - else // No match found - { - return *this; - } - } - else // Match found! - { - this->value_.object_->insert( std::pair( key, res->second ) ); +Value& Value::operator[](const std::string& key) +{ + return (*this)[ key.c_str() ]; +} - return res->second; +const Value& Value::operator[](const char* key) const +{ + const Value& found = this->find(key); + + if( found == Value::null ) { + return Value::null; } - return res->second; + return found; } -Value& Value::operator[]( const std::string& key ) +const Value& Value::operator[](const std::string& key) const { - return (*this)[ key.c_str() ]; -} + const Value& found = this->find( key.c_str() ); -// const Value& Value::operator[]( const std::string& key ) const -// { -// return (*this)[ key ]; -// } + if( found == Value::null ) { + return Value::null; + } + + return found; +} // Implementation derives from JsonCpp -Value& Value::push_back( const Value& val ) +Value& Value::push_back(const Value& val) { return (*this)[ ArrayIndex( this->size() )] = val; } -// Value& Value::at( int index ) -// { -// NOM_ASSERT( index >= 0 && index > this->size() ); -// if( index < 0 && index < this->size() ) -// { -// // Valid position -// return( *this )[ (index) ]; -// } - -// // An object node container is required for this method call. -// if( ! this->object_valid() ) -// { -// this->type_ = ValueType::ObjectValues; -// this->value_.object_ = new Object(); -// } - -// // Err; invalid position -// // return Value::null; -// return *this; -// } - -Value Value::find( const std::string& key ) const +const Value& Value::find(const std::string& key) const { // Sanity check NOM_ASSERT( this->null_type() || this->object_type() ); @@ -991,26 +844,27 @@ Value Value::find( const std::string& key ) const return Value::null; } -Value Value::erase( const std::string& key ) +Value Value::erase(const std::string& key) { - // Sanity check + VString k(key); + Value ret = Value::null; + + // Invalid object state! NOM_ASSERT( this->null_type() || this->object_type() ); - VString k( key ); - auto res = this->value_.object_->find( k ); + auto res = this->value_.object_->find(k); - if( res == this->value_.object_->end() ) // No match found - { - return Value::null; - } - else // Success -- match found; erasing found key pair! - { - this->value_.object_->erase( res ); + if( res == this->value_.object_->end() ) { + // Failure; no match found + return ret; + } else { + // Success; matching key value found + ret = res->second; - return res->second; + this->value_.object_->erase(res); } - return Value::null; + return ret; } // Value Value::erase( int index ) @@ -1037,7 +891,7 @@ Value Value::erase( const std::string& key ) // return Value::null; // } -Value::Members Value::member_names( void ) const +Value::Members Value::member_names() const { Members keys; @@ -1056,7 +910,7 @@ Value::Members Value::member_names( void ) const return keys; } -Value::ConstIterator Value::begin( void ) const +Value::ConstIterator Value::begin() const { if( this->array_valid() ) // ArrayIterator { @@ -1073,7 +927,7 @@ Value::ConstIterator Value::begin( void ) const return ValueConstIterator(); // Not an array or object type } -Value::ConstIterator Value::end( void ) const +Value::ConstIterator Value::end() const { if( this->array_valid() ) // ArrayIterator { @@ -1090,7 +944,7 @@ Value::ConstIterator Value::end( void ) const return ValueConstIterator(); // Not an array or object type } -Value::Iterator Value::begin( void ) +Value::Iterator Value::begin() { if( this->array_valid() ) // ArrayIterator { @@ -1106,7 +960,7 @@ Value::Iterator Value::begin( void ) return ValueIterator(); // Error: not an array or object type } -Value::Iterator Value::end( void ) +Value::Iterator Value::end() { if( this->array_valid() ) // ArrayIterator { @@ -1122,7 +976,7 @@ Value::Iterator Value::end( void ) return ValueIterator(); // Error: not an array or object type } -const std::string Value::dump( const Value& object, int depth ) const +const std::string Value::dump(const Value& object, int depth) const { std::string key; std::stringstream os; // Output buffer @@ -1189,7 +1043,7 @@ const std::string Value::dump( const Value& object, int depth ) const return os.str(); } -const std::string Value::dump_key( const Value& key ) const +const std::string Value::dump_key(const Value& key) const { std::stringstream os; // Output buffer @@ -1233,7 +1087,7 @@ const std::string Value::dump_key( const Value& key ) const return os.str(); } -const std::string Value::dump_value( const Value& val ) const +const std::string Value::dump_value(const Value& val) const { std::stringstream os; // Output buffer @@ -1277,7 +1131,7 @@ const std::string Value::dump_value( const Value& val ) const return os.str(); } -const std::string Value::print_key( const std::string& type, uint size ) const +const std::string Value::print_key(const std::string& type, uint size) const { std::stringstream os; @@ -1286,7 +1140,7 @@ const std::string Value::print_key( const std::string& type, uint size ) const return os.str(); } -const std::string Value::print_value( const std::string& val ) const +const std::string Value::print_value(const std::string& val) const { std::stringstream os; @@ -1295,7 +1149,7 @@ const std::string Value::print_value( const std::string& val ) const return os.str(); } -std::ostream& operator <<( std::ostream& os, const Value& val ) +std::ostream& operator <<(std::ostream& os, const Value& val) { os << val.dump( val ); diff --git a/src/ptree/ValueConstIterator.cpp b/src/ptree/ValueConstIterator.cpp index 8ed12e22..c020d64e 100644 --- a/src/ptree/ValueConstIterator.cpp +++ b/src/ptree/ValueConstIterator.cpp @@ -30,70 +30,73 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. namespace nom { -ValueConstIterator::ValueConstIterator( void ) +ValueConstIterator::ValueConstIterator() { //NOM_LOG_TRACE(NOM); } -ValueConstIterator::~ValueConstIterator( void ) +ValueConstIterator::~ValueConstIterator() { //NOM_LOG_TRACE(NOM); } -ValueConstIterator::ValueConstIterator( const ValueConstIterator& copy ) +ValueConstIterator::ValueConstIterator(const ValueConstIterator& rhs) { //NOM_LOG_TRACE(NOM); - this->copy( copy ); + + this->copy(rhs); } -ValueConstIterator::ValueConstIterator( const ObjectIterator& itr ) : - ValueIteratorBase ( itr ) +ValueConstIterator::ValueConstIterator(const ObjectIterator& rhs) : + ValueIteratorBase(rhs) { //NOM_LOG_TRACE(NOM); } -ValueConstIterator::SelfType& ValueConstIterator::operator =( const DerivedType& other ) +ValueConstIterator::SelfType& +ValueConstIterator::operator =(const DerivedType& rhs) { - this->copy( other ); + this->copy(rhs); return *this; } -ValueConstIterator::ConstReference ValueConstIterator::operator *( void ) const +ValueConstIterator::ConstReference ValueConstIterator::operator *() const { return this->dereference(); // const nom::Value& } -const ValueConstIterator::ValueTypePointer ValueConstIterator::operator ->( void ) const +const ValueConstIterator::ValueTypePointer +ValueConstIterator::operator ->() const { return this->pointer(); } -ValueConstIterator::SelfType& ValueConstIterator::operator ++( void ) +ValueConstIterator::SelfType& ValueConstIterator::operator ++() { this->increment(); return *this; } -ValueConstIterator::SelfType ValueConstIterator::operator ++( sint ) +ValueConstIterator::SelfType ValueConstIterator::operator ++(int) { - SelfType itr( *this ); + SelfType itr(*this); ++*this; return itr; } -ValueConstIterator::SelfType& ValueConstIterator::operator --( void ) +ValueConstIterator::SelfType& ValueConstIterator::operator --() { this->decrement(); return *this; } -ValueConstIterator::SelfType ValueConstIterator::operator --( sint ) +ValueConstIterator::SelfType ValueConstIterator::operator --(int) { - SelfType itr( *this ); + SelfType itr(*this); return itr; } diff --git a/src/ptree/ValueIterator.cpp b/src/ptree/ValueIterator.cpp index 26311f1f..5e245a03 100644 --- a/src/ptree/ValueIterator.cpp +++ b/src/ptree/ValueIterator.cpp @@ -30,70 +30,71 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. namespace nom { -ValueIterator::ValueIterator( void ) +ValueIterator::ValueIterator() { //NOM_LOG_TRACE(NOM); } -ValueIterator::~ValueIterator( void ) +ValueIterator::~ValueIterator() { //NOM_LOG_TRACE(NOM); } -ValueIterator::ValueIterator( const ValueIterator& copy ) +ValueIterator::ValueIterator(const ValueIterator& rhs) { //NOM_LOG_TRACE(NOM); - this->copy( copy ); + + this->copy(rhs); } -ValueIterator::ValueIterator( const ObjectIterator& itr ) : - ValueIteratorBase ( itr ) +ValueIterator::ValueIterator(const ObjectIterator& rhs) : + ValueIteratorBase(rhs) { //NOM_LOG_TRACE(NOM); } -ValueIterator::SelfType& ValueIterator::operator =( const SelfType& other ) +ValueIterator::SelfType& ValueIterator::operator =(const SelfType& rhs) { - this->copy( other ); + this->copy(rhs); return *this; } -ValueIterator::ValueTypeReference ValueIterator::operator *( void ) const +ValueIterator::ValueTypeReference ValueIterator::operator *() const { return this->dereference(); } -ValueIterator::ValueTypePointer ValueIterator::operator ->( void ) const +ValueIterator::ValueTypePointer ValueIterator::operator ->() const { return this->pointer(); } -ValueIterator::SelfType& ValueIterator::operator ++( void ) +ValueIterator::SelfType& ValueIterator::operator ++() { this->increment(); return *this; } -ValueIterator::SelfType ValueIterator::operator ++( sint ) +ValueIterator::SelfType ValueIterator::operator ++(int) { - SelfType itr( *this ); + SelfType itr(*this); ++*this; return itr; } -ValueIterator::SelfType& ValueIterator::operator --( void ) +ValueIterator::SelfType& ValueIterator::operator --() { this->decrement(); return *this; } -ValueIterator::SelfType ValueIterator::operator --( sint ) +ValueIterator::SelfType ValueIterator::operator --(int) { - SelfType itr( *this ); + SelfType itr(*this); return itr; } diff --git a/src/ptree/ValueIteratorBase.cpp b/src/ptree/ValueIteratorBase.cpp index 56960ea3..87e7a9bb 100644 --- a/src/ptree/ValueIteratorBase.cpp +++ b/src/ptree/ValueIteratorBase.cpp @@ -30,32 +30,32 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. namespace nom { -ValueIteratorBase::ValueIteratorBase ( void ) : - type_ ( IteratorType::Null ) +ValueIteratorBase::ValueIteratorBase() : + type_(IteratorType::Null) { //NOM_LOG_TRACE(NOM); } -ValueIteratorBase::~ValueIteratorBase ( void ) +ValueIteratorBase::~ValueIteratorBase() { //NOM_LOG_TRACE(NOM); } -ValueIteratorBase::ValueIteratorBase( const ObjectIterator& itr ) : - object_( itr ), - type_( IteratorType::ObjectValues ) +ValueIteratorBase::ValueIteratorBase(const ObjectIterator& rhs) : + object_(rhs), + type_(IteratorType::ObjectValues) { //NOM_LOG_TRACE(NOM); } -bool ValueIteratorBase::valid( void ) const +bool ValueIteratorBase::valid() const { if( this->type() != IteratorType::Null ) return true; return false; } -const char* ValueIteratorBase::key( void ) const +const char* ValueIteratorBase::key() const { if( this->type() == IteratorType::ObjectValues ) { @@ -84,7 +84,7 @@ const char* ValueIteratorBase::key( void ) const } } -bool ValueIteratorBase::key( const std::string& member ) const +bool ValueIteratorBase::key(const std::string& member) const { if( this->type() == IteratorType::ObjectValues ) { @@ -101,42 +101,25 @@ bool ValueIteratorBase::key( const std::string& member ) const } } -/*ArrayIndex*/int ValueIteratorBase::index( void ) const +ArrayIndex ValueIteratorBase::index() const { const VString& key = this->object_->first; - if( key.c_str() == nullptr ) - { - return key.index(); - } - - return -1; -} - -Value ValueIteratorBase::key_v2( void ) const -{ - VString key = this->object_->first; - - if( key.c_str() != nullptr ) - { - return Value( key.c_str() ); - } - - return Value( key.index() ); + return key.index(); } -void ValueIteratorBase::copy( const SelfType& other ) +void ValueIteratorBase::copy(const SelfType& rhs) { - this->object_ = other.object_; - this->type_ = other.type(); + this->object_ = rhs.object_; + this->type_ = rhs.type(); } -ValueIteratorBase::ValueTypeReference ValueIteratorBase::dereference( void ) const +ValueIteratorBase::ValueTypeReference ValueIteratorBase::dereference() const { return this->object_->second; } -ValueIteratorBase::ValueTypePointer ValueIteratorBase::pointer( void ) const +ValueIteratorBase::ValueTypePointer ValueIteratorBase::pointer() const { if( this->type() == IteratorType::ObjectValues ) { @@ -148,36 +131,37 @@ ValueIteratorBase::ValueTypePointer ValueIteratorBase::pointer( void ) const } } -bool ValueIteratorBase::operator ==( const SelfType& other ) const +bool ValueIteratorBase::operator ==(const SelfType& rhs) const { if( this->type() == IteratorType::ObjectValues ) { - return this->object_ == other.object_; + return this->object_ == rhs.object_; } else // IteratorType::NullValue { - return ( other.type() == IteratorType::Null ); + return ( rhs.type() == IteratorType::Null ); } } -bool ValueIteratorBase::operator !=( const SelfType& other ) const +bool ValueIteratorBase::operator !=(const SelfType& rhs) const { if( this->type() == IteratorType::ObjectValues ) { - return ! ( this->object_ == other.object_ ); + return ! ( this->object_ == rhs.object_ ); } else // IteratorType::NullValue { - return ! ( other.type() == IteratorType::Null ); + return ! ( rhs.type() == IteratorType::Null ); } } -ValueIteratorBase::DifferenceType ValueIteratorBase::operator -( const SelfType& other ) const +ValueIteratorBase::DifferenceType +ValueIteratorBase::operator -(const SelfType& rhs) const { - return distance( other ); + return distance(rhs); } -void ValueIteratorBase::increment( void ) +void ValueIteratorBase::increment() { if( this->type() == IteratorType::ObjectValues ) { @@ -189,7 +173,7 @@ void ValueIteratorBase::increment( void ) } } -void ValueIteratorBase::decrement( void ) +void ValueIteratorBase::decrement() { if( this->type() == IteratorType::ObjectValues ) { @@ -201,11 +185,12 @@ void ValueIteratorBase::decrement( void ) } } -ValueIteratorBase::DifferenceType ValueIteratorBase::distance( const SelfType& other ) const +ValueIteratorBase::DifferenceType +ValueIteratorBase::distance(const SelfType& rhs) const { if( this->type() == IteratorType::ObjectValues ) { - return std::distance( this->object_, other.object_ ); + return std::distance( this->object_, rhs.object_ ); } else // IteratorType::NullValue { @@ -213,7 +198,7 @@ ValueIteratorBase::DifferenceType ValueIteratorBase::distance( const SelfType& o } } -enum ValueIteratorBase::IteratorType ValueIteratorBase::type( void ) const +enum ValueIteratorBase::IteratorType ValueIteratorBase::type() const { return this->type_; } diff --git a/src/serializers/CMakeLists.txt b/src/serializers/CMakeLists.txt index 973a7447..b5f56cab 100644 --- a/src/serializers/CMakeLists.txt +++ b/src/serializers/CMakeLists.txt @@ -33,9 +33,8 @@ set( NOM_SERIALIZERS_SOURCE ${SRC_DIR}/serializers/RapidXmlSerializer.cpp ${INC_DIR}/serializers/RapidXmlSerializer.hpp - # TODO: Relocate SearchPath from system to serializers? - ${SRC_DIR}/system/SearchPath.cpp - ${INC_DIR}/system/SearchPath.hpp + ${SRC_DIR}/serializers/SearchPath.cpp + ${INC_DIR}/serializers/SearchPath.hpp ${INC_DIR}/serializers/serializers_config.hpp ) diff --git a/src/serializers/JsonCppDeserializer.cpp b/src/serializers/JsonCppDeserializer.cpp index 8eb9f51e..7f594a28 100644 --- a/src/serializers/JsonCppDeserializer.cpp +++ b/src/serializers/JsonCppDeserializer.cpp @@ -28,6 +28,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ #include "nomlib/serializers/JsonCppDeserializer.hpp" +#include "nomlib/core/unique_ptr.hpp" +#include "nomlib/core/strings.hpp" + // Private headers (third-party) #include "jsoncpp/json.h" @@ -516,4 +519,12 @@ bool JsonCppDeserializer::read_object( const Json::Value& object, Value& dest ) return true; } +std::unique_ptr make_unique_json_deserializer() +{ + std::unique_ptr deserializer = + nom::make_unique(); + + return std::move(deserializer); +} + } // namespace nom diff --git a/src/serializers/JsonCppSerializer.cpp b/src/serializers/JsonCppSerializer.cpp index 84401dc7..e8e503b1 100644 --- a/src/serializers/JsonCppSerializer.cpp +++ b/src/serializers/JsonCppSerializer.cpp @@ -28,6 +28,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ #include "nomlib/serializers/JsonCppSerializer.hpp" +#include "nomlib/core/unique_ptr.hpp" +#include "nomlib/core/strings.hpp" + // Private headers (third-party) #include "jsoncpp/json.h" @@ -564,4 +567,12 @@ bool JsonCppSerializer::write_object( const Value& object, Json::Value& dest ) c return true; } +std::unique_ptr make_unique_json_serializer() +{ + std::unique_ptr serializer = + nom::make_unique(); + + return std::move(serializer); +} + } // namespace nom diff --git a/src/system/SearchPath.cpp b/src/serializers/SearchPath.cpp similarity index 95% rename from src/system/SearchPath.cpp rename to src/serializers/SearchPath.cpp index 4729b5ac..fdc0fccb 100644 --- a/src/system/SearchPath.cpp +++ b/src/serializers/SearchPath.cpp @@ -26,7 +26,14 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ -#include "nomlib/system/SearchPath.hpp" +#include "nomlib/serializers/SearchPath.hpp" + +#include "nomlib/system/File.hpp" +#include "nomlib/ptree/Value.hpp" +#include "nomlib/serializers/JsonCppDeserializer.hpp" + +// Forward declarations +#include "nomlib/serializers/IValueDeserializer.hpp" namespace nom { diff --git a/src/system/CMakeLists.txt b/src/system/CMakeLists.txt index 7978a2ba..b133a574 100644 --- a/src/system/CMakeLists.txt +++ b/src/system/CMakeLists.txt @@ -8,8 +8,6 @@ set( NOM_SYSTEM_LIBRARY "nomlib-system" ) set( NOM_SYSTEM_SOURCE ${INC_DIR}/system.hpp - ${SRC_DIR}/system/AnimationTimer.cpp - ${INC_DIR}/system/AnimationTimer.hpp # TODO: Consider relocating this to graphics ${SRC_DIR}/system/ColorDatabase.cpp @@ -17,25 +15,23 @@ set( NOM_SYSTEM_SOURCE ${INC_DIR}/system/Event.hpp - ${SRC_DIR}/system/EventCallback.cpp - ${INC_DIR}/system/EventCallback.hpp - - ${SRC_DIR}/system/EventDispatcher.cpp - ${INC_DIR}/system/EventDispatcher.hpp - ${SRC_DIR}/system/EventHandler.cpp ${INC_DIR}/system/EventHandler.hpp + ${SRC_DIR}/system/JoystickEventHandler.cpp + ${INC_DIR}/system/JoystickEventHandler.hpp + ${SRC_DIR}/system/GameControllerEventHandler.cpp + ${INC_DIR}/system/GameControllerEventHandler.hpp + ${SRC_DIR}/system/Joystick.cpp + ${INC_DIR}/system/Joystick.hpp + ${SRC_DIR}/system/GameController.cpp + ${INC_DIR}/system/GameController.hpp ${SRC_DIR}/system/FPS.cpp ${INC_DIR}/system/FPS.hpp - ${INC_DIR}/system/IJoystick.hpp ${SRC_DIR}/system/IState.cpp ${INC_DIR}/system/IState.hpp - ${SRC_DIR}/system/Joystick.cpp - ${INC_DIR}/system/Joystick.hpp - ${INC_DIR}/system/ResourceCache.hpp ${SRC_DIR}/system/ResourceFile.cpp ${INC_DIR}/system/ResourceFile.hpp @@ -43,9 +39,6 @@ set( NOM_SYSTEM_SOURCE ${SRC_DIR}/system/SDLApp.cpp ${INC_DIR}/system/SDLApp.hpp - ${SRC_DIR}/system/SDLJoystick.cpp - ${INC_DIR}/system/SDLJoystick.hpp - ${SRC_DIR}/system/SDL_helpers.cpp ${INC_DIR}/system/SDL_helpers.hpp @@ -55,6 +48,9 @@ set( NOM_SYSTEM_SOURCE ${SRC_DIR}/system/Timer.cpp ${INC_DIR}/system/Timer.hpp + ${SRC_DIR}/system/HighResolutionTimer.cpp + ${INC_DIR}/system/HighResolutionTimer.hpp + ${SRC_DIR}/system/dialog_messagebox.cpp ${INC_DIR}/system/dialog_messagebox.hpp diff --git a/src/system/EventCallback.cpp b/src/system/EventCallback.cpp deleted file mode 100644 index 65b81a89..00000000 --- a/src/system/EventCallback.cpp +++ /dev/null @@ -1,62 +0,0 @@ -/****************************************************************************** - - nomlib - C++11 cross-platform game engine - -Copyright (c) 2013, 2014 Jeffrey Carpenter -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -******************************************************************************/ -#include "nomlib/system/EventCallback.hpp" - -// Forward declarations -#include "nomlib/system/Event.hpp" - -namespace nom { - -EventCallback::EventCallback( void ) -{ - // NOM_LOG_TRACE( NOM ); -} - -EventCallback::~EventCallback( void ) -{ - // NOM_LOG_TRACE( NOM ); -} - -EventCallback::EventCallback( const callback_type& callback ) : - delegate_{ callback } -{ - // NOM_LOG_TRACE( NOM ); -} - -void EventCallback::operator() ( const Event& evt ) const -{ - this->delegate_( evt ); -} - -void EventCallback::execute( const Event& evt ) const -{ - this->delegate_( evt ); -} - -} // namespace nom diff --git a/src/system/EventDispatcher.cpp b/src/system/EventDispatcher.cpp deleted file mode 100644 index 7f8a1078..00000000 --- a/src/system/EventDispatcher.cpp +++ /dev/null @@ -1,91 +0,0 @@ -/****************************************************************************** - - nomlib - C++11 cross-platform game engine - -Copyright (c) 2013, 2014 Jeffrey Carpenter -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -******************************************************************************/ -#include "nomlib/system/EventDispatcher.hpp" - -namespace nom { - -EventDispatcher::EventDispatcher( void ) -{ - // NOM_LOG_TRACE( NOM ); -} - -EventDispatcher::~EventDispatcher( void ) -{ - // NOM_LOG_TRACE( NOM ); -} - -bool EventDispatcher::dispatch( const Event& ev ) -{ - if( this->push_event( ev ) == false ) - { - NOM_LOG_ERR( NOM, "ERROR: Could not dispatch event." ); - return false; - } - - return true; -} - -bool EventDispatcher::push_event( const Event& ev ) -{ - // uint32 event_type = this->register_events( 1 ); - - // if( event_type == ( (uint32) - 1 ) ) // Error - // { - // NOM_LOG_ERR( NOM, "ERROR: Out of room for user-defined events." ); - // return false; - // } - - SDL_Event event; - SDL_zero( event ); // Initialize our event structure - - // event.type = event_type; - // event.user.type = event_type; - event.type = SDL_USEREVENT; - event.user.type = SDL_USEREVENT; - event.user.timestamp = ev.timestamp; - event.user.code = ev.user.code; - event.user.data1 = ev.user.data1; - event.user.data2 = ev.user.data2; - event.user.windowID = ev.user.window_id; - - if( SDL_PushEvent( &event ) != 1 ) - { - NOM_LOG_ERR ( NOM, SDL_GetError() ); - return false; - } - - return true; -} - -uint32 EventDispatcher::register_events( int num_events ) -{ - return SDL_RegisterEvents( num_events ); -} - -} // namespace nom diff --git a/src/system/EventHandler.cpp b/src/system/EventHandler.cpp index f773f333..e1bde588 100644 --- a/src/system/EventHandler.cpp +++ b/src/system/EventHandler.cpp @@ -28,813 +28,247 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ #include "nomlib/system/EventHandler.hpp" -// Private headers (third-party) -#include - -namespace nom { - -EventHandler::EventHandler( void ) -{ - NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_EVENT, nom::LogPriority::NOM_LOG_PRIORITY_VERBOSE ); - - // TODO: Err handling? - this->joystick.initialize(); -} - -EventHandler::~EventHandler( void ) -{ - NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_EVENT, nom::LogPriority::NOM_LOG_PRIORITY_VERBOSE ); -} - -void EventHandler::process_event( const Event& ev ) -{ - // Process events gathered from the high-level events queue subsystem; wraps - // the SDL events system. - switch( ev.type ) - { - default: break; - - case SDL_QUIT: - { - this->on_app_quit( ev ); - break; - } - - case SDL_WINDOWEVENT: - { - switch( ev.window.event ) - { - default: break; - - case SDL_WINDOWEVENT_NONE: break; - - case SDL_WINDOWEVENT_SHOWN: - { - this->on_window_shown( ev ); - break; - } - - case SDL_WINDOWEVENT_HIDDEN: - { - this->on_window_hidden( ev ); - break; - } - - case SDL_WINDOWEVENT_EXPOSED: - { - this->on_window_exposed( ev ); - break; - } - - case SDL_WINDOWEVENT_MOVED: - { - this->on_window_moved( ev ); - break; - } - - case SDL_WINDOWEVENT_RESIZED: - { - // FIXME: TTcards ends up in an infinite loop with the virtual method - // call below. - // this->on_window_resized( ev ); - break; - } - - case SDL_WINDOWEVENT_SIZE_CHANGED: - { - this->on_window_size_changed( ev ); - break; - } - - case SDL_WINDOWEVENT_MINIMIZED: - { - this->on_window_minimized( ev ); - break; - } - - case SDL_WINDOWEVENT_MAXIMIZED: - { - this->on_window_maximized( ev ); - break; - } - - case SDL_WINDOWEVENT_RESTORED: - { - this->on_window_restored( ev ); - break; - } - - case SDL_WINDOWEVENT_ENTER: - { - this->on_window_mouse_focus( ev ); - break; - } - - case SDL_WINDOWEVENT_LEAVE: - { - this->on_window_mouse_focus_lost( ev ); - break; - } - - case SDL_WINDOWEVENT_FOCUS_GAINED: - { - this->on_window_keyboard_focus( ev ); - break; - } - - case SDL_WINDOWEVENT_FOCUS_LOST: - { - this->on_window_keyboard_focus_lost( ev ); - break; - } - - case SDL_WINDOWEVENT_CLOSE: - { - this->on_window_close( ev ); - break; - } - } // end switch ev.window.event - - break; - } // end case SDL_WINDOWEVENT - - case SDL_USEREVENT: - { - this->on_user_event( ev ); - break; - } - - case SDL_SYSWMEVENT: break; - - case SDL_KEYDOWN: - { - this->on_key_down( ev ); - break; - } - - case SDL_KEYUP: - { - this->on_key_up( ev ); - break; - } - - case SDL_MOUSEMOTION: - { - this->on_mouse_motion( ev ); - break; - } - - case SDL_MOUSEBUTTONDOWN: - { - switch ( ev.mouse.button ) - { - default: break; - - case SDL_BUTTON_LEFT: - { - this->on_mouse_left_button_down( ev ); - break; - } - - case SDL_BUTTON_MIDDLE: - { - this->on_mouse_middle_button_down( ev ); - break; - } - - case SDL_BUTTON_RIGHT: - { - this->on_mouse_right_button_down( ev ); - break; - } - - case SDL_BUTTON_X1: - { - this->on_mouse_button_four_down( ev ); - break; - } - - case SDL_BUTTON_X2: - { - this->on_mouse_button_five_down( ev ); - break; - } - } // end switch ev.button.button - - break; - } // end switch SDL_MOUSEBUTTONDOWN - - case SDL_MOUSEBUTTONUP: - { - switch ( ev.mouse.button ) - { - default: break; - - case SDL_BUTTON_LEFT: - { - this->on_mouse_left_button_up( ev ); - break; - } - - case SDL_BUTTON_MIDDLE: - { - this->on_mouse_middle_button_up( ev ); - break; - } - - case SDL_BUTTON_RIGHT: - { - this->on_mouse_right_button_up( ev ); - break; - } - - case SDL_BUTTON_X1: - { - this->on_mouse_button_four_up( ev ); - break; - } - - case SDL_BUTTON_X2: - { - this->on_mouse_button_five_up( ev ); - break; - } - } // end switch ev.button.button - - break; - } // end switch SDL_MOUSEBUTTONUP - - case SDL_MOUSEWHEEL: - { - this->on_mouse_wheel( ev ); - break; - } - - case SDL_JOYBUTTONDOWN: - { - this->on_joy_button_down( ev ); - break; - } - - case SDL_JOYBUTTONUP: - { - this->on_joy_button_up( ev ); - break; - } - - case SDL_JOYAXISMOTION: - { - this->on_joy_axis( ev ); - break; - } - - case SDL_JOYDEVICEADDED: - { - this->on_joystick_connected( ev ); - break; - } - - case SDL_JOYDEVICEREMOVED: - { - this->on_joystick_disconnected( ev ); - break; - } - - case SDL_FINGERMOTION: - { - this->on_touch_motion( ev ); - break; - } - - case SDL_FINGERDOWN: - { - this->on_touch_down( ev ); - break; - } - - case SDL_FINGERUP: - { - this->on_touch_up( ev ); - break; - } - - case SDL_MULTIGESTURE: - { - this->on_gesture( ev ); - break; - } - - case SDL_DROPFILE: - { - this->on_drag_drop( ev ); - break; - } - - case SDL_TEXTINPUT: - { - this->on_text_input( ev ); - break; - } - - case SDL_TEXTEDITING: - { - this->on_text_edit( ev ); - break; - } - } // end switch ev.type -} - -bool EventHandler::poll_event( Event& ev ) -{ - // We have pending events in the queue! - if( this->pop_event( ev ) ) - { - return true; - } - - // No pending events in queue - return false; -} - -// bool EventHandler::poll_event( SDL_Event* ev ) -// { -// if ( SDL_PollEvent( ev ) == 1 ) -// { -// return true; -// } - -// return false; -// } - -void EventHandler::on_app_quit( const Event& ev ) -{ - // User implemented - - #if defined( NOM_DEBUG_SDL2_QUIT_EVENT ) - NOM_LOG_TRACE( NOM ); - ev.dump(); - #endif -} - -void EventHandler::on_window_shown( const Event& ev ) -{ - // User implemented - - #if defined( NOM_DEBUG_SDL2_WINDOW_INPUT ) - NOM_LOG_TRACE( NOM ); - ev.dump(); - ev.window.dump(); - #endif -} - -void EventHandler::on_window_hidden( const Event& ev ) -{ - // User implemented - - #if defined( NOM_DEBUG_SDL2_WINDOW_INPUT ) - NOM_LOG_TRACE( NOM ); - ev.dump(); - ev.window.dump(); - #endif -} - -void EventHandler::on_window_exposed( const Event& ev ) -{ - // User implemented - - #if defined( NOM_DEBUG_SDL2_WINDOW_INPUT ) - NOM_LOG_TRACE( NOM ); - ev.dump(); - ev.window.dump(); - #endif -} - -void EventHandler::on_window_moved( const Event& ev ) -{ - // User implemented - - #if defined( NOM_DEBUG_SDL2_WINDOW_INPUT ) - NOM_LOG_TRACE( NOM ); - ev.dump(); - ev.window.dump(); - #endif -} - -void EventHandler::on_window_resized( const Event& ev ) -{ - // User implemented - - #if defined( NOM_DEBUG_SDL2_WINDOW_INPUT ) - NOM_LOG_TRACE( NOM ); - ev.dump(); - ev.window.dump(); - #endif -} - -void EventHandler::on_window_size_changed( const Event& ev ) -{ - // User implemented - - #if defined( NOM_DEBUG_SDL2_WINDOW_INPUT ) - NOM_LOG_TRACE( NOM ); - ev.dump(); - ev.window.dump(); - #endif -} - -void EventHandler::on_window_minimized( const Event& ev ) -{ - // User implemented - - #if defined( NOM_DEBUG_SDL2_WINDOW_INPUT ) - NOM_LOG_TRACE( NOM ); - ev.dump(); - ev.window.dump(); - #endif -} - -void EventHandler::on_window_maximized( const Event& ev ) -{ - // User implemented - - #if defined( NOM_DEBUG_SDL2_WINDOW_INPUT ) - NOM_LOG_TRACE( NOM ); - ev.dump(); - ev.window.dump(); - #endif -} - -void EventHandler::on_window_restored( const Event& ev ) -{ - // User implemented - - #if defined( NOM_DEBUG_SDL2_WINDOW_INPUT ) - NOM_LOG_TRACE( NOM ); - ev.dump(); - ev.window.dump(); - #endif -} - -void EventHandler::on_window_mouse_focus( const Event& ev ) -{ - // User implemented - - #if defined( NOM_DEBUG_SDL2_WINDOW_FOCUS_INPUT ) - NOM_LOG_TRACE( NOM ); - ev.dump(); - ev.window.dump(); - #endif -} - -void EventHandler::on_window_mouse_focus_lost( const Event& ev ) -{ - // User implemented - - #if defined( NOM_DEBUG_SDL2_WINDOW_FOCUS_INPUT ) - NOM_LOG_TRACE( NOM ); - ev.dump(); - ev.window.dump(); - #endif -} - -void EventHandler::on_window_keyboard_focus( const Event& ev ) -{ - // User implemented - - #if defined( NOM_DEBUG_SDL2_WINDOW_FOCUS_INPUT ) - NOM_LOG_TRACE( NOM ); - ev.dump(); - ev.window.dump(); - #endif -} - -void EventHandler::on_window_keyboard_focus_lost( const Event& ev ) -{ - // User implemented - - #if defined( NOM_DEBUG_SDL2_WINDOW_FOCUS_INPUT ) - NOM_LOG_TRACE( NOM ); - ev.dump(); - ev.window.dump(); - #endif -} - -void EventHandler::on_window_close( const Event& ev ) -{ - // User implemented - - #if defined( NOM_DEBUG_SDL2_WINDOW_INPUT ) - NOM_LOG_TRACE( NOM ); - ev.dump(); - ev.window.dump(); - #endif -} - -void EventHandler::on_key_down( const Event& ev ) -{ - #if defined( NOM_DEBUG_SDL2_KEYBOARD_INPUT ) - NOM_LOG_TRACE( NOM ); - ev.dump(); - ev.key.dump(); - #endif -} - -void EventHandler::on_key_up( const Event& ev ) -{ - // User implemented +// Private headers +#include "nomlib/core/err.hpp" +#include "nomlib/core/clock.hpp" +#include "nomlib/core/strings.hpp" +#include "nomlib/core/unique_ptr.hpp" +#include "nomlib/system/SDL_helpers.hpp" +#include "nomlib/system/JoystickEventHandler.hpp" +#include "nomlib/system/GameControllerEventHandler.hpp" - #if defined( NOM_DEBUG_SDL2_KEYBOARD_INPUT ) - NOM_LOG_TRACE( NOM ); - ev.dump(); - ev.key.dump(); - #endif -} +#include -void EventHandler::on_mouse_motion( const Event& ev ) -{ - // User implemented +static_assert( nom::InputState::RELEASED == + SDL_RELEASED, "Event mismatch" ); +static_assert( nom::InputState::PRESSED == + SDL_PRESSED, "Event mismatch" ); + +static_assert( nom::MouseButton::LEFT_MOUSE_BUTTON == + SDL_BUTTON_LEFT, "Event mismatch" ); +static_assert( nom::MouseButton::MIDDLE_MOUSE_BUTTON == + SDL_BUTTON_MIDDLE, "Event mismatch" ); +static_assert( nom::MouseButton::RIGHT_MOUSE_BUTTON == + SDL_BUTTON_RIGHT, "Event mismatch" ); +static_assert( nom::MouseButton::X1_MOUSE_BUTTON == + SDL_BUTTON_X1, "Event mismatch" ); +static_assert( nom::MouseButton::X2_MOUSE_BUTTON == + SDL_BUTTON_X2, "Event mismatch" ); - #if defined( NOM_DEBUG_SDL2_MOUSE_MOTION_INPUT ) - NOM_LOG_TRACE( NOM ); - ev.dump(); - ev.motion.dump(); - #endif -} +namespace nom { -void EventHandler::on_mouse_wheel( const Event& ev ) +// Forward declarations +struct event_watcher { - // User implemented - - if( ev.type != SDL_MOUSEWHEEL ) return; - - #if defined( NOM_DEBUG_SDL2_MOUSE_INPUT ) - NOM_LOG_TRACE( NOM ); - ev.dump(); - ev.wheel.dump(); - #endif -} + event_filter callback = nullptr; + void* data1 = nullptr; +}; -void EventHandler::on_mouse_left_button_down( const Event& ev ) +EventHandler::EventHandler() { - // User implemented - - #if defined( NOM_DEBUG_SDL2_MOUSE_INPUT ) - NOM_LOG_TRACE( NOM ); - ev.dump(); - ev.mouse.dump(); - #endif + NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_EVENT, + NOM_LOG_PRIORITY_VERBOSE ); } -void EventHandler::on_mouse_middle_button_down( const Event& ev ) +EventHandler::~EventHandler() { - // User implemented + NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_EVENT, + NOM_LOG_PRIORITY_VERBOSE ); + + auto enable_report = nom::hint("NOM_EVENT_QUEUE_STATISTICS"); + if( nom::string_to_int(enable_report.c_str()) != 0 ) { + NOM_LOG_DEBUG( NOM_LOG_CATEGORY_APPLICATION, + "num_events:", this->num_events() ); + NOM_LOG_DEBUG( NOM_LOG_CATEGORY_APPLICATION, + "max_events_count:", max_events_count_ ); + } - #if defined( NOM_DEBUG_SDL2_MOUSE_INPUT ) - NOM_LOG_TRACE( NOM ); - ev.dump(); - ev.mouse.dump(); - #endif + if( this->joystick_event_type() == SDL_JOYSTICK_EVENT_HANDLER ) { + this->disable_joystick_polling(); + } else if( this->joystick_event_type() == GAME_CONTROLLER_EVENT_HANDLER ) { + this->disable_game_controller_polling(); + } } -void EventHandler::on_mouse_right_button_down( const Event& ev ) +nom::size_type EventHandler::num_events() const { - // User implemented - - #if defined( NOM_DEBUG_SDL2_MOUSE_INPUT ) - NOM_LOG_TRACE( NOM ); - ev.dump(); - ev.mouse.dump(); - #endif + return this->events_.size(); } -void EventHandler::on_mouse_button_four_down( const Event& ev ) +nom::size_type EventHandler::num_event_watchers() const { - // User implemented - - #if defined( NOM_DEBUG_SDL2_MOUSE_INPUT ) - NOM_LOG_TRACE( NOM ); - ev.dump(); - ev.mouse.dump(); - #endif + return this->event_watchers_.size(); } -void EventHandler::on_mouse_button_five_down( const Event& ev ) +JoystickEventHandler* EventHandler::joystick_event_handler() const { - // User implemented + auto result = (JoystickEventHandler*)this->joystick_event_handler_; - #if defined( NOM_DEBUG_SDL2_MOUSE_INPUT ) - NOM_LOG_TRACE( NOM ); - ev.dump(); - ev.mouse.dump(); - #endif + return result; } -void EventHandler::on_mouse_left_button_up( const Event& ev ) +GameControllerEventHandler* EventHandler::game_controller_event_handler() const { - // User implemented + auto result = (GameControllerEventHandler*)this->joystick_event_handler_; - #if defined( NOM_DEBUG_SDL2_MOUSE_INPUT ) - NOM_LOG_TRACE( NOM ); - ev.dump(); - ev.mouse.dump(); - #endif + return result; } -void EventHandler::on_mouse_middle_button_up( const Event& ev ) +EventHandler::JoystickHandlerType +EventHandler::joystick_event_type() const { - // User implemented - - #if defined( NOM_DEBUG_SDL2_MOUSE_INPUT ) - NOM_LOG_TRACE( NOM ); - ev.dump(); - ev.mouse.dump(); - #endif + return this->joystick_event_type_; } -void EventHandler::on_mouse_right_button_up( const Event& ev ) +bool EventHandler::enable_joystick_polling() { - // User implemented + if( nom::init_joystick_subsystem() == false ) { + return false; + } - #if defined( NOM_DEBUG_SDL2_MOUSE_INPUT ) - NOM_LOG_TRACE( NOM ); - ev.dump(); - ev.mouse.dump(); - #endif -} + this->joystick_event_handler_ = new JoystickEventHandler(); + if( this->joystick_event_handler_ == nullptr ) { + nom::set_error(nom::OUT_OF_MEMORY_ERR); + return false; + } -void EventHandler::on_mouse_button_four_up( const Event& ev ) -{ - // User implemented + this->joystick_event_type_ = SDL_JOYSTICK_EVENT_HANDLER; - #if defined( NOM_DEBUG_SDL2_MOUSE_INPUT ) - NOM_LOG_TRACE( NOM ); - ev.dump(); - ev.mouse.dump(); - #endif + return true; } -void EventHandler::on_mouse_button_five_up( const Event& ev ) +bool EventHandler::enable_game_controller_polling() { - // User implemented + if( nom::init_game_controller_subsystem() == false ) { + return false; + } - #if defined( NOM_DEBUG_SDL2_MOUSE_INPUT ) - NOM_LOG_TRACE( NOM ); - ev.dump(); - ev.mouse.dump(); - #endif -} + this->joystick_event_handler_ = new GameControllerEventHandler(); + if( this->joystick_event_handler_ == nullptr ) { + nom::set_error(nom::OUT_OF_MEMORY_ERR); + return false; + } -void EventHandler::on_joy_axis( const Event& ev ) -{ - // User implemented + this->joystick_event_type_ = GAME_CONTROLLER_EVENT_HANDLER; - #if defined( NOM_DEBUG_SDL2_JOYSTICK_AXIS_INPUT ) - NOM_LOG_TRACE( NOM ); - ev.dump(); - ev.jaxis.dump(); - #endif + return true; } -void EventHandler::on_joy_button_down( const Event& ev ) +void EventHandler::disable_joystick_polling() { - // User implemented + if( this->joystick_event_type() == SDL_JOYSTICK_EVENT_HANDLER ) { + + auto evt_handler = this->joystick_event_handler(); + NOM_DELETE_PTR(evt_handler); + + this->joystick_event_type_ = NO_EVENT_HANDLER; + nom::shutdown_joystick_subsystem(); + } else if( this->joystick_event_type() == NO_EVENT_HANDLER ) { + // Nothing to do + } else { + // Possible memory leak + NOM_ASSERT_INVALID_PATH(); + } - #if defined( NOM_DEBUG_SDL2_JOYSTICK_BUTTON_INPUT ) - NOM_LOG_TRACE( NOM ); - ev.dump(); - ev.jbutton.dump(); - #endif + this->joystick_event_handler_ = nullptr; } -void EventHandler::on_joy_button_up( const Event& ev ) +void EventHandler::disable_game_controller_polling() { - // User implemented + if( this->joystick_event_type() == GAME_CONTROLLER_EVENT_HANDLER ) { + + auto evt_handler = this->game_controller_event_handler(); + NOM_DELETE_PTR(evt_handler); + + this->joystick_event_type_ = NO_EVENT_HANDLER; + nom::shutdown_game_controller_subsystem(); + } else if( this->joystick_event_type() == NO_EVENT_HANDLER ) { + // Nothing to do + } else { + // Possible memory leak + NOM_ASSERT_INVALID_PATH(); + } - #if defined( NOM_DEBUG_SDL2_JOYSTICK_BUTTON_INPUT ) - NOM_LOG_TRACE( NOM ); - ev.dump(); - ev.jbutton.dump(); - #endif + this->joystick_event_handler_ = nullptr; } -void EventHandler::on_joystick_connected( const Event& ev ) +bool EventHandler::poll_event(Event& ev) { - // User implemented - - #if defined( NOM_DEBUG_SDL2_JOYSTICK_EVENT ) - NOM_LOG_TRACE( NOM ); - ev.dump(); - ev.jconnected.dump(); - #endif -} -void EventHandler::on_joystick_disconnected( const Event& ev ) -{ - // User implemented - - #if defined( NOM_DEBUG_SDL2_JOYSTICK_EVENT ) - NOM_LOG_TRACE( NOM ); - ev.dump(); - ev.jdisconnected.dump(); - #endif + if( this->pop_event(ev) == true ) { + // Pending events + return true; + } else { + // No pending events in queue + return false; + } } -void EventHandler::on_touch_motion( const Event& ev ) +void EventHandler::append_event_watch(const event_filter& filter, void* data) { - // User implemented - - #if defined( NOM_DEBUG_SDL2_TOUCH_MOTION_EVENT ) - NOM_LOG_TRACE( NOM ); - ev.dump(); - ev.touch.dump(); - #endif -} + if( filter == nullptr ) { + return; + } -void EventHandler::on_touch_down( const Event& ev ) -{ - // User implemented + auto event_watch = nom::make_unique(); + if( event_watch == nullptr ) { + // Err -- out of memory..?? + NOM_ASSERT_INVALID_PATH(); + return; + } - #if defined( NOM_DEBUG_SDL2_TOUCH_EVENT ) - NOM_LOG_TRACE( NOM ); - ev.dump(); - ev.touch.dump(); - #endif + event_watch->callback = filter; + event_watch->data1 = data; + this->event_watchers_.push_back( std::move(event_watch) ); } -void EventHandler::on_touch_up( const Event& ev ) +void EventHandler::remove_event_watch(const event_filter& filter) { - // User implemented + if( filter == nullptr ) { + return; + } - #if defined( NOM_DEBUG_SDL2_TOUCH_EVENT ) - NOM_LOG_TRACE( NOM ); - ev.dump(); - ev.touch.dump(); - #endif -} + auto& evt_watch = this->event_watchers_; + for( auto itr = evt_watch.begin(); itr != evt_watch.end(); ++itr ) { -void EventHandler::on_gesture( const Event& ev ) -{ - // User implemented + event_filter* callback = (*itr)->callback.target(); + const event_filter* arg = filter.target(); - #if defined( NOM_DEBUG_SDL2_GESTURE_EVENT ) - NOM_LOG_TRACE( NOM ); - ev.dump(); - ev.gesture.dump(); - #endif + if( callback == arg ) { + evt_watch.erase(itr); + return; + } + } } -void EventHandler::on_drag_drop( const Event& ev ) +void EventHandler::remove_event_watchers() { - // User implemented - - #if defined( NOM_DEBUG_SDL2_DRAG_DROP_INPUT ) - NOM_LOG_TRACE( NOM ); - ev.dump(); - ev.drop.dump(); - #endif + this->event_watchers_.clear(); } -void EventHandler::on_text_input( const Event& ev ) +void EventHandler::push_event(const Event& ev) { - // User implemented - - #if defined( NOM_DEBUG_SDL2_TEXT_INPUT_EVENT ) - NOM_LOG_TRACE( NOM ); - ev.dump(); - ev.text.dump(); - #endif -} + nom::size_type num_events = 0; + this->events_.emplace_back(ev); -void EventHandler::on_text_edit( const Event& ev ) -{ - // User implemented + num_events = this->num_events(); + if( num_events > this->max_events_count_ ) { + this->max_events_count_ = num_events; + } - #if defined( NOM_DEBUG_SDL2_TEXT_EDIT_EVENT ) - NOM_LOG_TRACE( NOM ); - ev.dump(); - ev.edit.dump(); - #endif + auto& evt_watch = this->event_watchers_; + for( auto itr = evt_watch.begin(); itr != evt_watch.end(); ++itr ) { + if( (*itr)->callback != nullptr ) { + (*itr)->callback.operator()(ev, (*itr)->data1); + } + } } -void EventHandler::on_user_event( const Event& ev ) +bool EventHandler::pop_event(Event& ev) { - // User implemented + bool result = false; - #if defined( NOM_DEBUG_SDL2_USER_EVENT ) - NOM_LOG_TRACE( NOM ); - ev.dump(); - ev.user.dump(); - #endif -} + if( this->events_.empty() == true ) { -bool EventHandler::pop_event( Event& ev ) -{ - // Empty event queue -- it's time to start gathering events - if( this->events_.empty() ) - { - // Use the underlying events subsystem (SDL2 events) to poll for available - // events. Once processed, our wrapped nom::Event queue should contain the - // same SDL_Event struct(s) preicsely, less & except minus any event type we - // may omit from pushing into our events queue. this->process_events(); // TODO: @@ -847,230 +281,258 @@ bool EventHandler::pop_event( Event& ev ) // this->process_events(); // } // } + + result = false; } - // Events queue is NOT empty; return the top of the queue stack to the - // end-user as the current event and mark it processed (remove it from queue). - if( ! this->events_.empty() ) - { + if( this->events_.empty() == false ) { + + // Leave a copy of the reference for end-user retrieval ev = this->events_.front(); - this->events_.pop(); - return true; + this->events_.pop_front(); + result = true; } - return false; + return result; } -void EventHandler::push_event( const Event& ev ) +void EventHandler::process_events() { - this->events_.push( ev ); + int result = 1; + SDL_Event ev; + + // Enumerate events from all available input devices + SDL_PumpEvents(); + + while( result > 0 ) { + result = + SDL_PeepEvents(&ev, 1, SDL_GETEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT); + + if( result < 0 ) { + NOM_ASSERT_INVALID_PATH(); + } else if( result > 0 ) { + + // Enqueue retrieved events from underlying platform (SDL) + + if( this->joystick_event_handler_ != nullptr ) { + + auto type = this->joystick_event_type(); + if( type == SDL_JOYSTICK_EVENT_HANDLER ) { + this->process_joystick_event(&ev); + } else if( type == GAME_CONTROLLER_EVENT_HANDLER ) { + this->process_game_controller_event(&ev); + } + } + + this->process_event(&ev); + } + } // end while } -void EventHandler::process_event( const SDL_Event* ev ) +void EventHandler::process_event(const SDL_Event* ev) { - // Create our event structure from the existing SDL_Event information. - switch( ev->type ) + switch(ev->type) { default: break; case SDL_QUIT: { - Event event; - event.type = ev->quit.type; + nom::Event event; + event.type = Event::QUIT_EVENT; event.timestamp = ev->quit.timestamp; - this->push_event( event ); - break; - } + // NOTE: user-defined fields + event.quit.data1 = nullptr; + event.quit.data2 = nullptr; + this->push_event(event); + } break; case SDL_WINDOWEVENT: { - switch( ev->window.event ) + switch(ev->window.event) { + case SDL_WINDOWEVENT_NONE: default: break; - // Not implemented as per SDL2 wiki documentation - case SDL_WINDOWEVENT_NONE: break; - case SDL_WINDOWEVENT_SHOWN: { Event event; - event.type = ev->window.type; + event.type = Event::WINDOW_EVENT; event.timestamp = ev->window.timestamp; - event.window.event = ev->window.event; + event.window.event = WindowEvent::SHOWN; event.window.data1 = ev->window.data1; event.window.data2 = ev->window.data2; event.window.window_id = ev->window.windowID; - this->push_event( event ); + this->push_event(event); break; } case SDL_WINDOWEVENT_HIDDEN: { Event event; - event.type = ev->window.type; + event.type = Event::WINDOW_EVENT; event.timestamp = ev->window.timestamp; - event.window.event = ev->window.event; + event.window.event = WindowEvent::HIDDEN; event.window.data1 = ev->window.data1; event.window.data2 = ev->window.data2; event.window.window_id = ev->window.windowID; - this->push_event( event ); + this->push_event(event); break; } case SDL_WINDOWEVENT_EXPOSED: { Event event; - event.type = ev->window.type; + event.type = Event::WINDOW_EVENT; event.timestamp = ev->window.timestamp; - event.window.event = ev->window.event; + event.window.event = WindowEvent::EXPOSED; event.window.data1 = ev->window.data1; event.window.data2 = ev->window.data2; event.window.window_id = ev->window.windowID; - this->push_event( event ); + this->push_event(event); break; } case SDL_WINDOWEVENT_MOVED: { Event event; - event.type = ev->window.type; + event.type = Event::WINDOW_EVENT; event.timestamp = ev->window.timestamp; - event.window.event = ev->window.event; + event.window.event = WindowEvent::MOVED; event.window.data1 = ev->window.data1; event.window.data2 = ev->window.data2; event.window.window_id = ev->window.windowID; - this->push_event( event ); + this->push_event(event); break; } case SDL_WINDOWEVENT_RESIZED: { Event event; - event.type = ev->window.type; + event.type = Event::WINDOW_EVENT; event.timestamp = ev->window.timestamp; - event.window.event = ev->window.event; + event.window.event = WindowEvent::RESIZED; event.window.data1 = ev->window.data1; event.window.data2 = ev->window.data2; event.window.window_id = ev->window.windowID; - this->push_event( event ); + this->push_event(event); break; } case SDL_WINDOWEVENT_SIZE_CHANGED: { Event event; - event.type = ev->window.type; + event.type = Event::WINDOW_EVENT; event.timestamp = ev->window.timestamp; - event.window.event = ev->window.event; + event.window.event = WindowEvent::SIZE_CHANGED; event.window.data1 = ev->window.data1; event.window.data2 = ev->window.data2; event.window.window_id = ev->window.windowID; - this->push_event( event ); + this->push_event(event); break; } case SDL_WINDOWEVENT_MINIMIZED: { Event event; - event.type = ev->window.type; + event.type = Event::WINDOW_EVENT; event.timestamp = ev->window.timestamp; - event.window.event = ev->window.event; + event.window.event = WindowEvent::MINIMIZED; event.window.data1 = ev->window.data1; event.window.data2 = ev->window.data2; event.window.window_id = ev->window.windowID; - this->push_event( event ); + this->push_event(event); break; } case SDL_WINDOWEVENT_MAXIMIZED: { Event event; - event.type = ev->window.type; + event.type = Event::WINDOW_EVENT; event.timestamp = ev->window.timestamp; - event.window.event = ev->window.event; + event.window.event = WindowEvent::MAXIMIZED; event.window.data1 = ev->window.data1; event.window.data2 = ev->window.data2; event.window.window_id = ev->window.windowID; - this->push_event( event ); + this->push_event(event); break; } case SDL_WINDOWEVENT_RESTORED: { Event event; - event.type = ev->window.type; + event.type = Event::WINDOW_EVENT; event.timestamp = ev->window.timestamp; - event.window.event = ev->window.event; + event.window.event = WindowEvent::RESTORED; event.window.data1 = ev->window.data1; event.window.data2 = ev->window.data2; event.window.window_id = ev->window.windowID; - this->push_event( event ); + this->push_event(event); break; } case SDL_WINDOWEVENT_ENTER: { Event event; - event.type = ev->window.type; + event.type = Event::WINDOW_EVENT; event.timestamp = ev->window.timestamp; - event.window.event = ev->window.event; + event.window.event = WindowEvent::MOUSE_FOCUS_GAINED; event.window.data1 = ev->window.data1; event.window.data2 = ev->window.data2; event.window.window_id = ev->window.windowID; - this->push_event( event ); + this->push_event(event); break; } case SDL_WINDOWEVENT_LEAVE: { Event event; - event.type = ev->window.type; + event.type = Event::WINDOW_EVENT; event.timestamp = ev->window.timestamp; - event.window.event = ev->window.event; + event.window.event = WindowEvent::MOUSE_FOCUS_LOST; event.window.data1 = ev->window.data1; event.window.data2 = ev->window.data2; event.window.window_id = ev->window.windowID; - this->push_event( event ); + this->push_event(event); break; } case SDL_WINDOWEVENT_FOCUS_GAINED: { Event event; - event.type = ev->window.type; + event.type = Event::WINDOW_EVENT; event.timestamp = ev->window.timestamp; - event.window.event = ev->window.event; + event.window.event = WindowEvent::KEYBOARD_FOCUS_GAINED; event.window.data1 = ev->window.data1; event.window.data2 = ev->window.data2; event.window.window_id = ev->window.windowID; - this->push_event( event ); + this->push_event(event); break; } case SDL_WINDOWEVENT_FOCUS_LOST: { Event event; - event.type = ev->window.type; + event.type = Event::WINDOW_EVENT; event.timestamp = ev->window.timestamp; - event.window.event = ev->window.event; + event.window.event = WindowEvent::KEYBOARD_FOCUS_LOST; event.window.data1 = ev->window.data1; event.window.data2 = ev->window.data2; event.window.window_id = ev->window.windowID; - this->push_event( event ); + this->push_event(event); break; } case SDL_WINDOWEVENT_CLOSE: { Event event; - event.type = ev->window.type; + event.type = Event::WINDOW_EVENT; event.timestamp = ev->window.timestamp; - event.window.event = ev->window.event; + event.window.event = WindowEvent::CLOSE; event.window.data1 = ev->window.data1; event.window.data2 = ev->window.data2; event.window.window_id = ev->window.windowID; - this->push_event( event ); + this->push_event(event); break; } } // end switch ev->window.event @@ -1083,7 +545,7 @@ void EventHandler::process_event( const SDL_Event* ev ) case SDL_KEYDOWN: { Event event; - event.type = ev->key.type; + event.type = Event::KEY_PRESS; event.timestamp = ev->key.timestamp; event.key.scan_code = ev->key.keysym.scancode; event.key.sym = ev->key.keysym.sym; @@ -1091,14 +553,14 @@ void EventHandler::process_event( const SDL_Event* ev ) event.key.state = ev->key.state; event.key.repeat = ev->key.repeat; event.key.window_id = ev->key.windowID; - this->push_event( event ); + this->push_event(event); break; } case SDL_KEYUP: { Event event; - event.type = ev->key.type; + event.type = Event::KEY_RELEASE; event.timestamp = ev->key.timestamp; event.key.scan_code = ev->key.keysym.scancode; event.key.sym = ev->key.keysym.sym; @@ -1106,14 +568,14 @@ void EventHandler::process_event( const SDL_Event* ev ) event.key.state = ev->key.state; event.key.repeat = ev->key.repeat; event.key.window_id = ev->key.windowID; - this->push_event( event ); + this->push_event(event); break; } case SDL_MOUSEMOTION: { Event event; - event.type = ev->motion.type; + event.type = Event::MOUSE_MOTION; event.timestamp = ev->motion.timestamp; event.motion.id = ev->motion.which; event.motion.x = ev->motion.x; @@ -1122,93 +584,93 @@ void EventHandler::process_event( const SDL_Event* ev ) event.motion.y_rel = ev->motion.yrel; event.motion.state = ev->motion.state; event.motion.window_id = ev->motion.windowID; - this->push_event( event ); + this->push_event(event); break; } case SDL_MOUSEBUTTONDOWN: { - switch ( ev->button.button ) + switch (ev->button.button) { default: break; case SDL_BUTTON_LEFT: { Event event; - event.type = ev->button.type; + event.type = Event::MOUSE_BUTTON_CLICK; event.timestamp = ev->button.timestamp; event.mouse.id = ev->button.which; event.mouse.x = ev->button.x; event.mouse.y = ev->button.y; - event.mouse.button = ev->button.button; + event.mouse.button = MouseButton::LEFT_MOUSE_BUTTON; event.mouse.state = ev->button.state; event.mouse.clicks = ev->button.clicks; event.mouse.window_id = ev->button.windowID; - this->push_event( event ); + this->push_event(event); break; } case SDL_BUTTON_MIDDLE: { Event event; - event.type = ev->button.type; + event.type = Event::MOUSE_BUTTON_CLICK; event.timestamp = ev->button.timestamp; event.mouse.id = ev->button.which; event.mouse.x = ev->button.x; event.mouse.y = ev->button.y; - event.mouse.button = ev->button.button; + event.mouse.button = MouseButton::MIDDLE_MOUSE_BUTTON; event.mouse.state = ev->button.state; event.mouse.clicks = ev->button.clicks; event.mouse.window_id = ev->button.windowID; - this->push_event( event ); + this->push_event(event); break; } case SDL_BUTTON_RIGHT: { Event event; - event.type = ev->button.type; + event.type = Event::MOUSE_BUTTON_CLICK; event.timestamp = ev->button.timestamp; event.mouse.id = ev->button.which; event.mouse.x = ev->button.x; event.mouse.y = ev->button.y; - event.mouse.button = ev->button.button; + event.mouse.button = MouseButton::RIGHT_MOUSE_BUTTON; event.mouse.state = ev->button.state; event.mouse.clicks = ev->button.clicks; event.mouse.window_id = ev->button.windowID; - this->push_event( event ); + this->push_event(event); break; } case SDL_BUTTON_X1: { Event event; - event.type = ev->button.type; + event.type = Event::MOUSE_BUTTON_CLICK; event.timestamp = ev->button.timestamp; event.mouse.id = ev->button.which; event.mouse.x = ev->button.x; event.mouse.y = ev->button.y; - event.mouse.button = ev->button.button; + event.mouse.button = MouseButton::X1_MOUSE_BUTTON; event.mouse.state = ev->button.state; event.mouse.clicks = ev->button.clicks; event.mouse.window_id = ev->button.windowID; - this->push_event( event ); + this->push_event(event); break; } case SDL_BUTTON_X2: { Event event; - event.type = ev->button.type; + event.type = Event::MOUSE_BUTTON_CLICK; event.timestamp = ev->button.timestamp; event.mouse.id = ev->button.which; event.mouse.x = ev->button.x; event.mouse.y = ev->button.y; - event.mouse.button = ev->button.button; + event.mouse.button = MouseButton::X2_MOUSE_BUTTON; event.mouse.state = ev->button.state; event.mouse.clicks = ev->button.clicks; event.mouse.window_id = ev->button.windowID; - this->push_event( event ); + this->push_event(event); break; } } // end switch ev->button.button @@ -1225,80 +687,80 @@ void EventHandler::process_event( const SDL_Event* ev ) case SDL_BUTTON_LEFT: { Event event; - event.type = ev->button.type; + event.type = Event::MOUSE_BUTTON_RELEASE; event.timestamp = ev->button.timestamp; event.mouse.id = ev->button.which; event.mouse.x = ev->button.x; event.mouse.y = ev->button.y; - event.mouse.button = ev->button.button; + event.mouse.button = MouseButton::LEFT_MOUSE_BUTTON; event.mouse.state = ev->button.state; event.mouse.clicks = ev->button.clicks; event.mouse.window_id = ev->button.windowID; - this->push_event( event ); + this->push_event(event); break; } case SDL_BUTTON_MIDDLE: { Event event; - event.type = ev->button.type; + event.type = Event::MOUSE_BUTTON_RELEASE; event.timestamp = ev->button.timestamp; event.mouse.id = ev->button.which; event.mouse.x = ev->button.x; event.mouse.y = ev->button.y; - event.mouse.button = ev->button.button; + event.mouse.button = MouseButton::MIDDLE_MOUSE_BUTTON; event.mouse.state = ev->button.state; event.mouse.clicks = ev->button.clicks; event.mouse.window_id = ev->button.windowID; - this->push_event( event ); + this->push_event(event); break; } case SDL_BUTTON_RIGHT: { Event event; - event.type = ev->button.type; + event.type = Event::MOUSE_BUTTON_RELEASE; event.timestamp = ev->button.timestamp; event.mouse.id = ev->button.which; event.mouse.x = ev->button.x; event.mouse.y = ev->button.y; - event.mouse.button = ev->button.button; + event.mouse.button = MouseButton::RIGHT_MOUSE_BUTTON; event.mouse.state = ev->button.state; event.mouse.clicks = ev->button.clicks; event.mouse.window_id = ev->button.windowID; - this->push_event( event ); + this->push_event(event); break; } case SDL_BUTTON_X1: { Event event; - event.type = ev->button.type; + event.type = Event::MOUSE_BUTTON_RELEASE; event.timestamp = ev->button.timestamp; event.mouse.id = ev->button.which; event.mouse.x = ev->button.x; event.mouse.y = ev->button.y; - event.mouse.button = ev->button.button; + event.mouse.button = MouseButton::X1_MOUSE_BUTTON; event.mouse.state = ev->button.state; event.mouse.clicks = ev->button.clicks; event.mouse.window_id = ev->button.windowID; - this->push_event( event ); + this->push_event(event); break; } case SDL_BUTTON_X2: { Event event; - event.type = ev->button.type; + event.type = Event::MOUSE_BUTTON_RELEASE; event.timestamp = ev->button.timestamp; event.mouse.id = ev->button.which; event.mouse.x = ev->button.x; event.mouse.y = ev->button.y; - event.mouse.button = ev->button.button; + event.mouse.button = MouseButton::X2_MOUSE_BUTTON; event.mouse.state = ev->button.state; event.mouse.clicks = ev->button.clicks; event.mouse.window_id = ev->button.windowID; - this->push_event( event ); + this->push_event(event); break; } } // end switch ev->button.button @@ -1309,87 +771,20 @@ void EventHandler::process_event( const SDL_Event* ev ) case SDL_MOUSEWHEEL: { Event event; - event.type = ev->wheel.type; + event.type = Event::MOUSE_WHEEL; event.timestamp = ev->wheel.timestamp; event.wheel.id = ev->wheel.which; event.wheel.x = ev->wheel.x; event.wheel.y = ev->wheel.y; event.wheel.window_id = ev->wheel.windowID; - this->push_event( event ); - break; - } - - case SDL_JOYBUTTONDOWN: - { - Event event; - event.type = ev->jbutton.type; - event.timestamp = ev->jbutton.timestamp; - event.jbutton.id = this->joystick.first_joystick(); - event.jbutton.button = ev->jbutton.button; - event.jbutton.state = ev->jbutton.state; - // event.jbutton.window_id = ev->jbutton.windowID; - this->push_event( event ); - break; - } - - case SDL_JOYBUTTONUP: - { - Event event; - event.type = ev->jbutton.type; - event.timestamp = ev->jbutton.timestamp; - event.jbutton.id = this->joystick.first_joystick(); - event.jbutton.button = ev->jbutton.button; - event.jbutton.state = ev->jbutton.state; - // event.jbutton.window_id = ev->jbutton.windowID; - this->push_event( event ); - break; - } - - case SDL_JOYAXISMOTION: - { - Event event; - event.type = ev->jaxis.type; - event.timestamp = ev->jaxis.timestamp; - event.jaxis.id = this->joystick.first_joystick(); - event.jaxis.axis = ev->jaxis.axis; - event.jaxis.value = ev->jaxis.value; - // event.jbutton.window_id = ev->jbutton.windowID; - this->push_event( event ); - break; - } - - case SDL_JOYDEVICEADDED: - { - // Poll available joystick devices in order to react to joysticks that are - // connected after initialization of the engine. - this->joystick.enumerate_devices(); - - Event event; - event.type = ev->jdevice.type; - event.timestamp = ev->jdevice.timestamp; - event.jconnected.id = this->joystick.first_joystick(); - this->push_event( event ); - break; - } - - case SDL_JOYDEVICEREMOVED: - { - // Poll available joystick devices in order to react to joysticks that are - // connected after initialization of the engine. - this->joystick.enumerate_devices(); - - Event event; - event.type = ev->jdevice.type; - event.timestamp = ev->jdevice.timestamp; - event.jdisconnected.id = this->joystick.first_joystick(); - this->push_event( event ); + this->push_event(event); break; } case SDL_FINGERMOTION: { Event event; - event.type = ev->tfinger.type; + event.type = Event::FINGER_MOTION; event.timestamp = ev->tfinger.timestamp; event.touch.id = ev->tfinger.touchId; event.touch.finger.id = ev->tfinger.fingerId; @@ -1398,14 +793,14 @@ void EventHandler::process_event( const SDL_Event* ev ) event.touch.dx = ev->tfinger.dx; event.touch.dy = ev->tfinger.dy; event.touch.pressure = ev->tfinger.pressure; - this->push_event( event ); + this->push_event(event); break; } case SDL_FINGERDOWN: { Event event; - event.type = ev->tfinger.type; + event.type = Event::FINGER_PRESS; event.timestamp = ev->tfinger.timestamp; event.touch.id = ev->tfinger.touchId; event.touch.finger.id = ev->tfinger.fingerId; @@ -1414,14 +809,14 @@ void EventHandler::process_event( const SDL_Event* ev ) event.touch.dx = ev->tfinger.dx; event.touch.dy = ev->tfinger.dy; event.touch.pressure = ev->tfinger.pressure; - this->push_event( event ); + this->push_event(event); break; } case SDL_FINGERUP: { Event event; - event.type = ev->tfinger.type; + event.type = Event::FINGER_RELEASE; event.timestamp = ev->tfinger.timestamp; event.touch.id = ev->tfinger.touchId; event.touch.finger.id = ev->tfinger.fingerId; @@ -1430,14 +825,14 @@ void EventHandler::process_event( const SDL_Event* ev ) event.touch.dx = ev->tfinger.dx; event.touch.dy = ev->tfinger.dy; event.touch.pressure = ev->tfinger.pressure; - this->push_event( event ); + this->push_event(event); break; } case SDL_MULTIGESTURE: { Event event; - event.type = ev->mgesture.type; + event.type = Event::MULTI_FINGER_GESTURE; event.timestamp = ev->mgesture.timestamp; event.gesture.id = ev->mgesture.touchId; event.gesture.dTheta = ev->mgesture.dTheta; @@ -1445,68 +840,433 @@ void EventHandler::process_event( const SDL_Event* ev ) event.gesture.x = ev->mgesture.x; event.gesture.y = ev->mgesture.y; event.gesture.num_fingers = ev->mgesture.numFingers; - this->push_event( event ); + this->push_event(event); break; } case SDL_DROPFILE: { Event event; - event.type = ev->drop.type; - event.timestamp = ticks(); + event.type = Event::DROP_FILE; + event.timestamp = nom::ticks(); event.drop.file_path = ev->drop.file; - this->push_event( event ); + this->push_event(event); break; } case SDL_TEXTINPUT: { Event event; - event.type = ev->text.type; + event.type = Event::TEXT_INPUT; event.timestamp = ev->text.timestamp; - strcpy( event.text.text, ev->text.text ); + nom::copy_string(ev->text.text, event.text.text); event.text.window_id = ev->text.windowID; - this->push_event( event ); + this->push_event(event); break; } - case SDL_TEXTEDITING: // FIXME + case SDL_TEXTEDITING: { Event event; - event.type = ev->edit.type; + event.type = Event::TEXT_EDITING; event.timestamp = ev->edit.timestamp; - strcpy( event.edit.text, ev->edit.text ); + event.edit.start = ev->edit.start; + event.edit.length = ev->edit.length; + nom::copy_string(ev->edit.text, event.edit.text); event.edit.window_id = ev->edit.windowID; - this->push_event( event ); + this->push_event(event); break; } - case SDL_USEREVENT: + case SDL_RENDER_TARGETS_RESET: { Event event; - event.type = ev->user.type; + event.type = Event::RENDER_TARGETS_RESET; + event.timestamp = nom::ticks(); + } break; + +// NOTE: Not available until the release of SDL 2.0.4 +#if 0 + case SDL_RENDER_DEVICE_RESET: + { + Event event; + event.type = Event::RENDER_DEVICE_RESET; + event.timestamp = nom::ticks(); + } break; +#endif + case SDL_USEREVENT: + { + nom::Event event; + event.type = Event::USER_EVENT; event.timestamp = ev->user.timestamp; event.user.code = ev->user.code; event.user.data1 = ev->user.data1; event.user.data2 = ev->user.data2; event.user.window_id = ev->user.windowID; - this->push_event( event ); - break; - } + this->push_event(event); + } break; } // end switch event->type } -void EventHandler::process_events( void ) +void EventHandler::process_joystick_event(const SDL_Event* ev) { - SDL_Event ev; + NOM_ASSERT(this->joystick_event_type() == SDL_JOYSTICK_EVENT_HANDLER); - // Enumerate events from all available input devices - SDL_PumpEvents(); + auto evt_handler = this->joystick_event_handler(); + NOM_ASSERT(evt_handler != nullptr); + + switch(ev->type) + { + default: break; + + case SDL_JOYDEVICEADDED: + { + Event event; + event.type = Event::JOYSTICK_ADDED; + event.timestamp = ev->jdevice.timestamp; + event.jdevice.id = ev->jdevice.which; + this->push_event(event); + + auto dev_index = event.jdevice.id; + auto joy_dev = evt_handler->add_joystick(dev_index); + if( joy_dev != nullptr ) { + auto dev_id = joy_dev->device_id(); + NOM_LOG_INFO( NOM_LOG_CATEGORY_EVENT, + "Registered joystick instance ID", + dev_id, "for", joy_dev->name() ); + } else { + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, + "Failed to register joystick:", nom::error() ); + } + } break; + + // IMPORTANT: The joystick device state is invalid by the time we get this + // message! + case SDL_JOYDEVICEREMOVED: + { + Event event; + event.type = Event::JOYSTICK_REMOVED; + event.timestamp = ev->jdevice.timestamp; + event.jdevice.id = ev->jdevice.which; + this->push_event(event); + + auto dev_id = event.jdevice.id; + if( evt_handler->remove_joystick(dev_id) == true ) { + NOM_LOG_INFO( NOM_LOG_CATEGORY_EVENT, + "Removing registered instance ID", dev_id ); + } else { + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, + "Failed to remove registered instance ID:", + nom::error() ); + } + } break; + + case SDL_JOYBUTTONDOWN: + { + Event event; + event.type = Event::JOYSTICK_BUTTON_PRESS; + event.timestamp = ev->jbutton.timestamp; + event.jbutton.id = ev->jbutton.which; + event.jbutton.button = ev->jbutton.button; + event.jbutton.state = ev->jbutton.state; + this->push_event(event); + } break; + + case SDL_JOYBUTTONUP: + { + Event event; + event.type = Event::JOYSTICK_BUTTON_RELEASE; + event.timestamp = ev->jbutton.timestamp; + event.jbutton.id = ev->jbutton.which; + event.jbutton.button = ev->jbutton.button; + event.jbutton.state = ev->jbutton.state; + this->push_event(event); + } break; + + case SDL_JOYAXISMOTION: + { + Event event; + event.type = Event::JOYSTICK_AXIS_MOTION; + event.timestamp = ev->jaxis.timestamp; + event.jaxis.id = ev->jaxis.which; + event.jaxis.axis = ev->jaxis.axis; + event.jaxis.value = ev->jaxis.value; + this->push_event(event); + } break; + + case SDL_JOYHATMOTION: + { + Event event; + event.type = Event::JOYSTICK_HAT_MOTION; + event.timestamp = ev->jhat.timestamp; + event.jhat.id = ev->jhat.which; + event.jhat.hat = ev->jhat.hat; + event.jhat.value = ev->jhat.value; + this->push_event(event); + } break; + } +} - while( SDL_PeepEvents( &ev, 1, SDL_GETEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT ) ) +void EventHandler::process_game_controller_event(const SDL_Event* ev) +{ + NOM_ASSERT(this->joystick_event_type() == GAME_CONTROLLER_EVENT_HANDLER); + + auto evt_handler = this->game_controller_event_handler(); + NOM_ASSERT(evt_handler != nullptr); + + switch(ev->type) { - this->process_event( &ev ); + default: break; + + case SDL_CONTROLLERAXISMOTION: + { + Event event; + event.type = Event::GAME_CONTROLLER_AXIS_MOTION; + event.timestamp = ev->caxis.timestamp; + event.caxis.id = ev->caxis.which; + event.caxis.axis = ev->caxis.axis; + event.caxis.value = ev->caxis.value; + this->push_event(event); + } break; + + case SDL_CONTROLLERBUTTONDOWN: + { + Event event; + event.type = Event::GAME_CONTROLLER_BUTTON_PRESS; + event.timestamp = ev->cbutton.timestamp; + event.cbutton.id = ev->cbutton.which; + event.cbutton.button = ev->cbutton.button; + event.cbutton.state = ev->cbutton.state; + this->push_event(event); + } break; + + case SDL_CONTROLLERBUTTONUP: + { + Event event; + event.type = Event::GAME_CONTROLLER_BUTTON_RELEASE; + event.timestamp = ev->cbutton.timestamp; + event.cbutton.id = ev->cbutton.which; + event.cbutton.button = ev->cbutton.button; + event.cbutton.state = ev->cbutton.state; + this->push_event(event); + } break; + + case SDL_CONTROLLERDEVICEADDED: + { + Event event; + event.type = Event::GAME_CONTROLLER_ADDED; + event.timestamp = ev->cdevice.timestamp; + event.cdevice.id = ev->cdevice.which; + this->push_event(event); + + auto dev_index = event.cdevice.id; + auto joy_dev = evt_handler->add_joystick(dev_index); + if( joy_dev != nullptr ) { + auto dev_id = joy_dev->device_id(); + NOM_LOG_INFO( NOM_LOG_CATEGORY_EVENT, + "Registered game controller instance ID", + dev_id, "for", joy_dev->name() ); + } else { + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, + "Failed to register game controller:", nom::error() ); + } + } break; + + // IMPORTANT: The joystick device state is invalid by the time we get this + // message! + case SDL_CONTROLLERDEVICEREMOVED: + { + Event event; + event.type = Event::GAME_CONTROLLER_REMOVED; + event.timestamp = ev->cdevice.timestamp; + event.cdevice.id = ev->cdevice.which; + this->push_event(event); + + auto dev_id = event.cdevice.id; + if( evt_handler->remove_joystick(dev_id) == true ) { + NOM_LOG_INFO( NOM_LOG_CATEGORY_EVENT, + "Removing registered instance ID", dev_id ); + } else { + NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, + "Failed to remove registered instance ID:", + nom::error() ); + } + } break; + + // TODO: I have no idea how this event is suppose to work ... we receive + // more than one of these events at a time -- which instance ID do we use?? + case SDL_CONTROLLERDEVICEREMAPPED: + { + Event event; + event.type = Event::GAME_CONTROLLER_REMAPPED; + event.timestamp = ev->cdevice.timestamp; + event.cdevice.id = ev->cdevice.which; + this->push_event(event); + } break; + } +} + +void EventHandler::flush_event(Event::EventType type) +{ + for( auto itr = this->events_.begin(); itr != this->events_.end(); ++itr ) { + if( (*itr).type == type ) { + this->events_.erase(itr); + return; + } + } +} + +void EventHandler::flush_events(Event::EventType type) +{ + for( auto itr = this->events_.begin(); itr != this->events_.end(); ++itr ) { + if( (*itr).type == type ) { + this->events_.erase(itr); + } } } +void EventHandler::flush_events() +{ + this->events_.clear(); +} + +Event create_key_press(int32 sym, uint16 mod, uint8 repeat) +{ + nom::Event result; + result.type = Event::KEY_PRESS; + result.timestamp = nom::ticks(); + result.key.scan_code = SDL_GetScancodeFromKey(sym); + result.key.sym = sym; + result.key.mod = mod; + result.key.state = InputState::PRESSED; + result.key.repeat = repeat; + + return result; +} + +Event create_key_release(int32 sym, uint16 mod, uint8 repeat) +{ + nom::Event result; + result.type = Event::KEY_RELEASE; + result.timestamp = nom::ticks(); + result.key.scan_code = SDL_GetScancodeFromKey(sym); + result.key.sym = sym; + result.key.mod = mod; + result.key.state = InputState::RELEASED; + result.key.repeat = repeat; + + return result; +} + +Event create_mouse_button_click(uint8 button, uint8 clicks, uint32 window_id) +{ + nom::Event result; + result.type = Event::MOUSE_BUTTON_CLICK; + result.timestamp = nom::ticks(); + result.mouse.button = button; + result.mouse.state = InputState::PRESSED; + result.mouse.clicks = clicks; + result.mouse.window_id = window_id; + + return result; +} + +Event create_mouse_button_release(uint8 button, uint8 clicks, uint32 window_id) +{ + nom::Event result; + result.type = Event::MOUSE_BUTTON_RELEASE; + result.timestamp = nom::ticks(); + result.mouse.button = button; + result.mouse.state = InputState::RELEASED; + result.mouse.clicks = clicks; + result.mouse.window_id = window_id; + + return result; +} + +Event create_joystick_button_press(JoystickID id, uint8 button) +{ + nom::Event result; + result.type = Event::JOYSTICK_BUTTON_PRESS; + result.timestamp = nom::ticks(); + result.jbutton.id = id; + result.jbutton.button = button; + result.jbutton.state = InputState::PRESSED; + + return result; +} + +Event create_joystick_button_release(JoystickID id, uint8 button) +{ + nom::Event result; + result.type = Event::JOYSTICK_BUTTON_RELEASE; + result.timestamp = nom::ticks(); + result.jbutton.id = id; + result.jbutton.button = button; + result.jbutton.state = InputState::RELEASED; + + return result; +} + +Event create_joystick_hat_motion(JoystickID id, uint8 hat, uint8 value) +{ + nom::Event result; + result.type = Event::JOYSTICK_HAT_MOTION; + result.timestamp = nom::ticks(); + result.jhat.id = id; + result.jhat.hat = hat; + result.jhat.value = value; + + return result; +} + +Event create_game_controller_button_press(JoystickID id, uint8 button) +{ + nom::Event result; + result.type = Event::GAME_CONTROLLER_BUTTON_PRESS; + result.timestamp = nom::ticks(); + result.cbutton.id = id; + result.cbutton.button = button; + result.cbutton.state = InputState::PRESSED; + + return result; +} + +Event create_game_controller_button_release(JoystickID id, uint8 button) +{ + nom::Event result; + result.type = Event::GAME_CONTROLLER_BUTTON_RELEASE; + result.timestamp = nom::ticks(); + result.cbutton.id = id; + result.cbutton.button = button; + result.cbutton.state = InputState::RELEASED; + + return result; +} + +Event +create_user_event(int32 code, void* data1, void* data2, uint32 window_id) +{ + nom::Event result; + result.type = Event::USER_EVENT; + result.timestamp = nom::ticks(); + result.user.code = code; + result.user.data1 = data1; + result.user.data2 = data2; + result.user.window_id = window_id; + + return result; +} + +Event create_quit_event(void* data1, void* data2) +{ + nom::Event result; + result.type = Event::QUIT_EVENT; + result.timestamp = nom::ticks(); + result.quit.data1 = data1; + result.quit.data2 = data2; + + return result; +} + } // namespace nom diff --git a/src/system/File.cpp b/src/system/File.cpp index aa971e7b..342c8de6 100644 --- a/src/system/File.cpp +++ b/src/system/File.cpp @@ -28,117 +28,136 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ #include "nomlib/system/File.hpp" +// Forward declarations +#include "nomlib/system/IFile.hpp" + +#if defined(NOM_PLATFORM_OSX) || defined(NOM_PLATFORM_LINUX) + #include "nomlib/system/unix/UnixFile.hpp" +#elif defined(NOM_PLATFORM_WINDOWS) + #include "nomlib/system/windows/WinFile.hpp" +#else + #pragma message("The file interface must be implemented for this platform") +#endif + namespace nom { -File::File ( void ) +File::File() { - #if defined (NOM_PLATFORM_WINDOWS) // Use Windows APIs - this->file = std::unique_ptr ( new WinFile() ); - #else // Assume POSIX compatibility; use POSIX / BSD & GNU APIs - this->file = std::unique_ptr ( new UnixFile() ); - #endif +#if defined (NOM_PLATFORM_WINDOWS) // Use Windows APIs + this->file = std::unique_ptr ( new WinFile() ); +#else // Assume POSIX compatibility; use POSIX / BSD & GNU APIs + this->file = std::unique_ptr ( new UnixFile() ); +#endif } -File::~File ( void ) {} +File::~File() +{ + // +} -const std::string File::extension ( const std::string& file ) +std::string File::extension(const std::string& file) const { - return this->file->extension ( file ); + return this->file->extension(file); } -int32 File::size ( const std::string& file_path ) +int32 File::size(const std::string& file_path) const { - return this->file->size ( file_path ); + return this->file->size(file_path); } -bool File::is_dir( const std::string& file_path ) +bool File::is_dir(const std::string& file_path) const { - return this->file->is_dir( file_path ); + return this->file->is_dir(file_path); } -bool File::is_file( const std::string& file_path ) +bool File::is_file(const std::string& file_path) const { - return this->file->is_file( file_path ); + return this->file->is_file(file_path); } -bool File::exists( const std::string& file_path ) +bool File::exists(const std::string& file_path) const { - return this->file->exists( file_path ); + return this->file->exists(file_path); } -const std::string File::path( const std::string& dir_path ) +std::string File::path(const std::string& dir_path) const { - return this->file->path ( dir_path ); + return this->file->path(dir_path); } -std::string File::currentPath( void ) const +std::string File::currentPath() const { return this->file->currentPath(); } -bool File::set_path ( const std::string& path ) +bool File::set_path (const std::string& path) const { return this->file->set_path(path); } -const std::string File::basename ( const std::string& filename ) +std::string File::basename(const std::string& filename) const { return this->file->basename(filename); } -std::vector File::read_dir( const std::string& dir_path ) +std::vector File::read_dir(const std::string& dir_path) const { - return this->file->read_dir( dir_path ); + return this->file->read_dir(dir_path); } -const std::string File::resource_path( const std::string& identifier ) +std::string File::resource_path(const std::string& identifier) const { - return this->file->resource_path( identifier ); + return this->file->resource_path(identifier); } -const std::string File::user_documents_path( void ) +std::string File::user_documents_path() const { return this->file->user_documents_path(); } -const std::string File::user_app_support_path( void ) +std::string File::user_app_support_path() const { return this->file->user_app_support_path(); } -const std::string File::user_home_path( void ) +std::string File::user_home_path() const { return this->file->user_home_path(); } -const std::string File::system_path( void ) +std::string File::system_path() const { return this->file->system_path(); } -bool File::mkdir( const std::string& path ) +bool File::mkdir(const std::string& path) const +{ + return this->file->mkdir(path); +} + +bool File::recursive_mkdir(const std::string& path) const { - return this->file->mkdir( path ); + return this->file->recursive_mkdir(path); } -bool File::recursive_mkdir( const std::string& path ) +bool File::rmdir(const std::string& path) const { - return this->file->recursive_mkdir( path ); + return this->file->rmdir(path); } -bool File::rmdir( const std::string& path ) +bool File::mkfile(const std::string& path) const { - return this->file->rmdir( path ); + return this->file->mkfile(path); } -bool File::mkfile( const std::string& path ) +std::string File::env(const std::string& path) const { - return this->file->mkfile( path ); + return this->file->env(path); } -std::string File::env( const std::string& path ) +nom::size_type File::num_files(const std::string& path) const { - return this->file->env( path ); + return this->file->num_files(path); } namespace priv { diff --git a/src/system/GameController.cpp b/src/system/GameController.cpp new file mode 100644 index 00000000..0af2187d --- /dev/null +++ b/src/system/GameController.cpp @@ -0,0 +1,321 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#include "nomlib/system/GameController.hpp" + +// Private helpers +#include "nomlib/core/err.hpp" +#include "nomlib/core/unique_ptr.hpp" +#include "nomlib/system/SDL_helpers.hpp" + +// Forward declarations +#include + +static_assert( nom::GameController::AXIS_INVALID == + SDL_CONTROLLER_AXIS_INVALID, "Event mismatch" ); +static_assert( nom::GameController::AXIS_MAX == + SDL_CONTROLLER_AXIS_MAX, "Event mismatch" ); + +static_assert( nom::GameController::BUTTON_INVALID == + SDL_CONTROLLER_BUTTON_INVALID, "Event mismatch" ); +// !! This needs to be addressed ASAP -- out of bounds +//static_assert( nom::GameController::BUTTON_MAX == +// SDL_CONTROLLER_BUTTON_MAX, "Event mismatch" ); + +namespace nom { + +bool init_game_controller_subsystem() +{ + uint32 init_flags = SDL_INIT_GAMECONTROLLER; + + if( nom::init_joystick_subsystem() == false ) { + return false; + } + + if( SDL_WasInit(init_flags) == 0 ) { + if( SDL_InitSubSystem(init_flags) < 0 ) { + nom::set_error( SDL_GetError() ); + return false; + } + } + + return true; +} + +void shutdown_game_controller_subsystem() +{ + if( SDL_WasInit(SDL_INIT_GAMECONTROLLER) != 0 ) { + SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER); + } + + nom::shutdown_joystick_subsystem(); +} + +bool GameController::device_closed_ = false; + +void GameControllerDeleter(SDL_GameController* dev) +{ + if( GameController::device_closed_ == true ) { + return; + } + + if( dev != nullptr && SDL_GameControllerGetAttached(dev) == true ) { + SDL_GameControllerClose(dev); + } +} + +GameController::GameController() : + device_( joystick_dev(nullptr, GameControllerDeleter) ) +{ + NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_EVENT, + NOM_LOG_PRIORITY_VERBOSE ); +} + +GameController::~GameController() +{ + NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_EVENT, + NOM_LOG_PRIORITY_VERBOSE ); +} + +SDL_Joystick* GameController::device() const +{ + SDL_Joystick* joy_dev = + SDL_GameControllerGetJoystick( this->device_.get() ); + if( joy_dev == nullptr ) { + nom::set_error( SDL_GetError() ); + } + + return joy_dev; +} + +bool GameController::attached() const +{ + SDL_bool result = SDL_GameControllerGetAttached( this->device_.get() ); + + if( result == SDL_FALSE ) { + nom::set_error( SDL_GetError() ); + return false; + } + + return true; +} + +JoystickID GameController::device_id() const +{ + SDL_JoystickID dev_id = -1; + + SDL_Joystick* joy_dev = this->device(); + if( joy_dev != nullptr ) { + dev_id = SDL_JoystickInstanceID(joy_dev); + } + + if( dev_id < 0 ) { + nom::set_error( SDL_GetError() ); + } + + return dev_id; +} + +std::string GameController::name() const +{ + const char* dev_name = nullptr; + std::string result; + + dev_name = SDL_GameControllerName( this->device_.get() ); + if( dev_name != nullptr ) { + result = dev_name; + } else { + nom::set_error( SDL_GetError() ); + } + + return result; +} + +bool GameController::open(JoystickIndex device_index) +{ + SDL_GameController* dev = SDL_GameControllerOpen(device_index); + if( dev != nullptr ) { + this->device_.reset(dev); + + // Success! + return( this->attached() == true ); + } else { + nom::set_error( SDL_GetError() ); + return false; + } +} + +void GameController::close() +{ + if( GameController::device_closed_ == true ) { + return; + } + + if( this->device_ != nullptr ) { + SDL_GameControllerClose( this->device_.get() ); + GameController::device_closed_ = true; + } +} + +// static +std::string GameController::name(JoystickIndex device_index) +{ + const char* dev_name = nullptr; + std::string result; + + dev_name = SDL_GameControllerNameForIndex(device_index); + if( dev_name != nullptr ) { + result = dev_name; + } else { + nom::set_error( SDL_GetError() ); + } + + return result; +} + +// static +int GameController::set_event_state(int state) +{ + int result = SDL_GameControllerEventState(state); + + return result; +} + +// static +void GameController::update() +{ + SDL_GameControllerUpdate(); +} + +std::string GameController::mapping_string() +{ + char* result_cstr = nullptr; + std::string result; + + result_cstr = SDL_GameControllerMapping( this->device_.get() ); + if( result_cstr != nullptr ) { + result = result_cstr; + } else { + nom::set_error( SDL_GetError() ); + } + + return result; +} + +// static +std::string GameController::mapping_string(JoystickGUID guid) +{ + char* result_cstr = nullptr; + std::string result; + + auto dev_guid = nom::convert_SDL_JoystickGUID(guid); + + result_cstr = SDL_GameControllerMappingForGUID(dev_guid); + if( result_cstr != nullptr ) { + result = result_cstr; + } else { + nom::set_error( SDL_GetError() ); + } + + return result; +} + +// static +int GameController::load_mapping_memory(const char* buffer, int buffer_size) +{ + // int result = SDL_GameControllerAddMappingsFromRW(); + return -1; + NOM_ASSERT_INVALID_PATH(); +} + +// static +int GameController::load_mapping_file(const std::string& filename) +{ + int result = -1; + + result = SDL_GameControllerAddMappingsFromFile( filename.c_str() ); + if( result < 0 ) { + nom::set_error( SDL_GetError() ); + } + + return result; +} + +// static +int GameController::load_mapping_string(const std::string& mapping) +{ + int result = -1; + + result = SDL_GameControllerAddMapping( mapping.c_str() ); + if( result < 0 ) { + nom::set_error( SDL_GetError() ); + } + + return result; +} + +// static +JoystickID GameController::device_id(JoystickIndex device_index) +{ + JoystickID dev_id = -1; + + Joystick jdev; + if( jdev.open(device_index) == true ) { + dev_id = jdev.device_id(); + } + + return dev_id; +} + +// static +bool GameController::compatible_joystick(JoystickIndex device_index) +{ + SDL_bool result = SDL_IsGameController(device_index); + if( result == SDL_FALSE ) { + nom::set_error( SDL_GetError() ); + return false; + } + + return true; +} + +std::unique_ptr make_unique_game_controller() +{ + auto dev = nom::make_unique(); + + return std::move(dev); +} + +std::shared_ptr make_shared_game_controller() +{ + auto dev = std::make_shared(); + + return dev; +} + +} // namespace nom diff --git a/src/system/GameControllerEventHandler.cpp b/src/system/GameControllerEventHandler.cpp new file mode 100644 index 00000000..b07574f3 --- /dev/null +++ b/src/system/GameControllerEventHandler.cpp @@ -0,0 +1,146 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#include "nomlib/system/GameControllerEventHandler.hpp" + +// Private headers +#include "nomlib/core/err.hpp" + +namespace nom { + +GameControllerEventHandler::GameControllerEventHandler() +{ + NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_EVENT, + NOM_LOG_PRIORITY_VERBOSE ); +} + +GameControllerEventHandler::~GameControllerEventHandler() +{ + NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_EVENT, + NOM_LOG_PRIORITY_VERBOSE ); +} + +nom::size_type GameControllerEventHandler::num_joysticks() const +{ + auto result = this->joysticks_.size(); + return result; +} + +GameController* GameControllerEventHandler::joystick(JoystickID dev_id) const +{ + GameController* result = nullptr; + + auto res = this->joysticks_.find(dev_id); + if(res == this->joysticks_.end()) { + return result; + } + + if( res != this->joysticks_.end() ) { + // Success -- device found + result = res->second.get(); + } + + return result; +} + +// TODO(jeff): Use GameControllerEventHandler::joystick method within this +// function! +bool GameControllerEventHandler::joystick_exists(JoystickID dev_id) const { + bool result = false; + auto res = this->joysticks_.find(dev_id); + if(res == this->joysticks_.end()) { + return result; + } + + if( res != this->joysticks_.end() ) { + // Success -- device exists + result = true; + } + + return result; +} + +GameController* +GameControllerEventHandler::add_joystick(JoystickIndex device_index) +{ + GameController* result = nullptr; + + auto joy_dev = nom::make_unique_game_controller(); + if( joy_dev != nullptr && joy_dev->open(device_index) == true ) { + + JoystickID dev_id = joy_dev->device_id(); + std::string dev_name = joy_dev->name(); + + if( dev_id < 0 ) { + // Err + return result; + } + + // Success! + this->joysticks_[dev_id] = std::move(joy_dev); + result = this->joysticks_[dev_id].get(); + } + + return result; +} + +bool GameControllerEventHandler::remove_joystick(JoystickID dev_id) +{ + bool result = false; + + auto res = this->joysticks_.find(dev_id); + if(res == this->joysticks_.end()) { + return result; + } + + if( res != this->joysticks_.end() ) { + + // Success -- found device; say buh-bye! + res->second->close(); + this->joysticks_.erase(res); + result = true; + } + + return result; +} + +void GameControllerEventHandler::remove_joysticks() { + auto res = this->joysticks_.begin(); + if(res == this->joysticks_.end()) { + return; + } + + if(res != this->joysticks_.end()) { + // Success -- found device; say buh-bye! + res->second->close(); + // FIXME(jeff): How do we properly erase + this->joysticks_.clear(); + } +} + +} // namespace nom diff --git a/src/system/HighResolutionTimer.cpp b/src/system/HighResolutionTimer.cpp new file mode 100644 index 00000000..4b375d1b --- /dev/null +++ b/src/system/HighResolutionTimer.cpp @@ -0,0 +1,161 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#include "nomlib/system/HighResolutionTimer.hpp" + +// Private headers +#include + +#include + +#include "nomlib/core/clock.hpp" + +namespace nom { + +HighResolutionTimer::HighResolutionTimer() : + paused_(false), + started_(false), + elapsed_ticks_(0), + paused_ticks_(0) +{ + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE, NOM_LOG_PRIORITY_VERBOSE); + + // TODO: This should probably be taken care of in nom::init or so?? + // if( SDL_WasInit( SDL_INIT_TIMER ) == false ) { + // if( SDL_InitSubSystem ( SDL_INIT_TIMER ) == -1 ) { + // NOM_LOG_ERR( NOM_LOG_CATEGORY_APPLICATION, + // "Could not initialize timing subsystem:", SDL_GetError() ); + // } + // } +} + +HighResolutionTimer::~HighResolutionTimer() +{ + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE, NOM_LOG_PRIORITY_VERBOSE); +} + +bool HighResolutionTimer::started() const +{ + return this->started_; +} + +bool HighResolutionTimer::paused() const +{ + return this->paused_; +} + +uint64 HighResolutionTimer::ticks() const +{ + if( this->started() == true ) { + if( this->paused() == true ) { + return this->paused_ticks_; + } else { + return nom::hires_ticks() - this->elapsed_ticks_; + } + } + + return 0; // Timer is not running +} + +std::string HighResolutionTimer::ticks_str() const +{ + return std::to_string( this->ticks() ); +} + +void HighResolutionTimer::start() +{ + this->elapsed_ticks_ = nom::hires_ticks(); + this->started_ = true; + this->paused_ = false; +} + +void HighResolutionTimer::stop() +{ + this->elapsed_ticks_ = 0; + this->started_ = false; + this->paused_ = false; +} + +void HighResolutionTimer::restart() +{ + this->start(); +} + +void HighResolutionTimer::pause() +{ + if( this->started() == true && this->paused() == false ) { + this->paused_ = true; + this->paused_ticks_ = nom::hires_ticks() - this->elapsed_ticks_; + } +} + +void HighResolutionTimer::unpause() +{ + if( this->paused() == true ) { + this->paused_ = false; + this->elapsed_ticks_ = nom::hires_ticks() - this->paused_ticks_; + this->paused_ticks_ = 0; + } +} + +real64 +HighResolutionTimer::elapsed_ticks( uint64 start_hires_ticks, + uint64 end_hires_ticks ) +{ + real64 result = ( (real32)(end_hires_ticks - start_hires_ticks) / + (real64)nom::hires_frequency() ); + + return result; +} + +real64 HighResolutionTimer::to_milliseconds(uint64 hires_ticks) +{ + real64 result = ( (1000.0f * (real64)(hires_ticks) / + (real64)nom::hires_frequency() ) ); + + return result; +} + +real64 HighResolutionTimer::to_seconds() const +{ + auto elapsed_ticks = this->ticks(); + + real64 result = this->to_seconds(elapsed_ticks); + + return result; +} + +real64 HighResolutionTimer::to_seconds(uint64 hires_ticks) +{ + real64 result = + ( (HighResolutionTimer::to_milliseconds(hires_ticks) / 1000.0f) ); + + return result; +} + +} // namespace nom diff --git a/src/system/IState.cpp b/src/system/IState.cpp index 889125da..1f1e8a71 100644 --- a/src/system/IState.cpp +++ b/src/system/IState.cpp @@ -30,6 +30,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // Forward declarations #include "nomlib/graphics/RenderWindow.hpp" +#include "nomlib/system/Event.hpp" namespace nom { @@ -85,7 +86,7 @@ IState::Type IState::type( void ) const return this->type_; } -bool IState::on_event( const Event& ev ) +bool IState::on_event(const Event& ev) { // User-defined virtual diff --git a/src/system/InputMapper/InputAction.cpp b/src/system/InputMapper/InputAction.cpp index 567e8a8e..b5233be3 100644 --- a/src/system/InputMapper/InputAction.cpp +++ b/src/system/InputMapper/InputAction.cpp @@ -32,7 +32,14 @@ namespace nom { // InputAction (base class) -InputAction::~InputAction( void ) +InputAction::InputAction() +{ + // NOM_LOG_TRACE( NOM ); + + this->event_ = {}; +} + +InputAction::~InputAction() { // NOM_LOG_TRACE( NOM ); } @@ -42,75 +49,83 @@ const Event& InputAction::event( void ) const return this->event_; } -const EventCallback& InputAction::callback( void ) const +const nom::event_callback& InputAction::callback() const { return this->callback_; } -void InputAction::set_callback( const EventCallback& delegate ) +void InputAction::set_callback(const nom::event_callback& delegate) { this->callback_ = delegate; } -void InputAction::operator() ( const Event& evt ) const -{ - this->callback_( evt ); -} - -void InputAction::dump( void ) const +void InputAction::operator()(const Event& evt) const { - // Event type and timestamp info - this->event_.dump(); + this->callback_(evt); } // KeyboardAction -KeyboardAction::~KeyboardAction( void ) +KeyboardAction::~KeyboardAction() { // NOM_LOG_TRACE( NOM ); } -KeyboardAction::KeyboardAction( uint32 type, int32 sym ) +KeyboardAction::KeyboardAction(int32 sym, InputState state) { // NOM_LOG_TRACE( NOM ); - this->event_.type = type; + if( state == InputState::PRESSED ) { + this->event_.type = Event::KEY_PRESS; + } else { + this->event_.type = Event::KEY_RELEASE; + } + + this->event_.timestamp = 0; this->event_.key.sym = sym; this->event_.key.mod = KMOD_NONE; this->event_.key.repeat = 0; + this->event_.key.state = state; this->event_.key.window_id = 0; } -KeyboardAction::KeyboardAction( uint32 type, int32 sym, uint16 mod ) +KeyboardAction::KeyboardAction(int32 sym, uint16 mod, InputState state) { // NOM_LOG_TRACE( NOM ); - this->event_.type = type; + if( state == InputState::PRESSED ) { + this->event_.type = Event::KEY_PRESS; + } else { + this->event_.type = Event::KEY_RELEASE; + } + + this->event_.timestamp = 0; this->event_.key.sym = sym; this->event_.key.mod = mod; this->event_.key.repeat = 0; + this->event_.key.state = state; this->event_.key.window_id = 0; } -KeyboardAction::KeyboardAction( uint32 type, int32 sym, uint16 mod, uint8 repeat ) +KeyboardAction:: +KeyboardAction(int32 sym, uint16 mod, uint8 repeat, InputState state) { // NOM_LOG_TRACE( NOM ); - this->event_.type = type; + if( state == InputState::PRESSED ) { + this->event_.type = Event::KEY_PRESS; + } else { + this->event_.type = Event::KEY_RELEASE; + } + + this->event_.timestamp = 0; this->event_.key.sym = sym; this->event_.key.mod = mod; this->event_.key.repeat = repeat; + this->event_.key.state = state; this->event_.key.window_id = 0; } -void KeyboardAction::dump( void ) const -{ - // Event type & timestamp info - InputAction::dump(); - - this->event_.key.dump(); -} - // MouseButtonAction MouseButtonAction::~MouseButtonAction() @@ -118,129 +133,182 @@ MouseButtonAction::~MouseButtonAction() // NOM_LOG_TRACE( NOM ); } -MouseButtonAction::MouseButtonAction( uint32 type, uint8 button ) +MouseButtonAction::MouseButtonAction(uint8 button, InputState state) { // NOM_LOG_TRACE( NOM ); - this->event_.type = type; + if( state == InputState::PRESSED ) { + this->event_.type = Event::MOUSE_BUTTON_CLICK; + } else { + this->event_.type = Event::MOUSE_BUTTON_RELEASE; + } + + this->event_.timestamp = 0; this->event_.mouse.x = 0; this->event_.mouse.y = 0; this->event_.mouse.button = button; this->event_.mouse.clicks = 1; + this->event_.mouse.state = state; this->event_.mouse.window_id = 0; } -MouseButtonAction::MouseButtonAction( uint32 type, uint8 button, uint8 clicks ) +MouseButtonAction:: +MouseButtonAction(uint8 button, uint8 clicks, InputState state) { // NOM_LOG_TRACE( NOM ); - this->event_.type = type; + if( state == nom::InputState::PRESSED ) { + this->event_.type = Event::MOUSE_BUTTON_CLICK; + } else { + this->event_.type = Event::MOUSE_BUTTON_RELEASE; + } + + this->event_.timestamp = 0; this->event_.mouse.x = 0; this->event_.mouse.y = 0; this->event_.mouse.button = button; this->event_.mouse.clicks = clicks; + this->event_.mouse.state = state; this->event_.mouse.window_id = 0; } -void MouseButtonAction::dump( void ) const -{ - // Event type & timestamp info - InputAction::dump(); - - this->event_.mouse.dump(); -} - // MouseWheelAction -MouseWheelAction::~MouseWheelAction( void ) +MouseWheelAction::~MouseWheelAction() { // NOM_LOG_TRACE( NOM ); } -MouseWheelAction::MouseWheelAction( uint32 type, uint8 axis, int32 value ) +MouseWheelAction::MouseWheelAction(MouseWheelDirection dir) { // NOM_LOG_TRACE( NOM ); - this->event_.type = type; - // this->event_.wheel.axis = axis; + this->event_.type = Event::MOUSE_WHEEL; + this->event_.timestamp = 0; + this->event_.wheel.id = 0; this->event_.wheel.window_id = 0; - // Wheel direction is left or right - // if( this->event_.wheel.axis == AXIS_X ) - if( axis == AXIS_X ) - { - this->event_.wheel.x = value; - this->event_.wheel.y = MouseWheelAction::null; - } - // Wheel direction is up or down - // else if( this->event_.wheel.axis == AXIS_Y ) - else if( axis == AXIS_Y ) - { - this->event_.wheel.y = value; - this->event_.wheel.x = MouseWheelAction::null; - } - else // Invalid state - { - this->event_.wheel.x = MouseWheelAction::null; - this->event_.wheel.y = MouseWheelAction::null; + if( dir == MOUSE_WHEEL_UP ) { + this->event_.wheel.y = MOUSE_WHEEL_UP; + this->event_.wheel.x = MOUSE_WHEEL_INVALID; + } else if( dir == MOUSE_WHEEL_DOWN ) { + this->event_.wheel.y = MOUSE_WHEEL_DOWN; + this->event_.wheel.x = MOUSE_WHEEL_INVALID; + } else if( dir == MOUSE_WHEEL_LEFT ) { + this->event_.wheel.x = MOUSE_WHEEL_LEFT; + this->event_.wheel.y = MOUSE_WHEEL_INVALID; + } else if( dir == MOUSE_WHEEL_RIGHT ) { + this->event_.wheel.x = MOUSE_WHEEL_RIGHT; + this->event_.wheel.y = MOUSE_WHEEL_INVALID; + } else { + this->event_.wheel.x = MOUSE_WHEEL_INVALID; + this->event_.wheel.y = MOUSE_WHEEL_INVALID; } } -void MouseWheelAction::dump( void ) const +// JoystickButtonAction + +JoystickButtonAction::~JoystickButtonAction() { - // Event type & timestamp info - InputAction::dump(); + // NOM_LOG_TRACE( NOM ); +} - this->event_.wheel.dump(); +JoystickButtonAction:: +JoystickButtonAction(JoystickID id, uint8 button, InputState state) + +{ + // NOM_LOG_TRACE( NOM ); + + if( state == InputState::PRESSED ) { + this->event_.type = Event::JOYSTICK_BUTTON_PRESS; + } else { + this->event_.type = Event::JOYSTICK_BUTTON_RELEASE; + } + + this->event_.timestamp = 0; + this->event_.jbutton.id = id; + this->event_.jbutton.button = button; + this->event_.jbutton.state = state; } -// JoystickButtonAction +// JoystickAxisAction -JoystickButtonAction::~JoystickButtonAction( void ) +JoystickAxisAction::~JoystickAxisAction() { // NOM_LOG_TRACE( NOM ); } -JoystickButtonAction::JoystickButtonAction( SDL_JoystickID id, uint32 type, uint8 button ) +JoystickAxisAction::JoystickAxisAction(JoystickID id, uint8 axis) { // NOM_LOG_TRACE( NOM ); - this->event_.type = type; - this->event_.jbutton.id = id; - this->event_.jbutton.button = button; + this->event_.type = Event::JOYSTICK_AXIS_MOTION; + this->event_.timestamp = 0; + this->event_.jaxis.id = id; + this->event_.jaxis.axis = axis; + this->event_.jaxis.value = 0; } -void JoystickButtonAction::dump( void ) const +// JoystickHatAction + +JoystickHatAction::~JoystickHatAction() { - // Event type & timestamp info - InputAction::dump(); + // NOM_LOG_TRACE( NOM ); +} + +JoystickHatAction::JoystickHatAction(JoystickID id, uint8 hat, uint8 value) +{ + // NOM_LOG_TRACE( NOM ); - this->event_.jbutton.dump(); + this->event_.type = Event::JOYSTICK_HAT_MOTION; + this->event_.timestamp = 0; + this->event_.jhat.id = id; + this->event_.jhat.hat = hat; + this->event_.jhat.value = value; } -// JoystickAxisAction +// GameControllerButtonAction -JoystickAxisAction::~JoystickAxisAction( void ) +GameControllerButtonAction::~GameControllerButtonAction() { // NOM_LOG_TRACE( NOM ); } -JoystickAxisAction::JoystickAxisAction( SDL_JoystickID id, uint32 type, uint8 axis, int16 value ) +GameControllerButtonAction:: +GameControllerButtonAction( JoystickID id, GameController::Button button, + InputState state ) { // NOM_LOG_TRACE( NOM ); - this->event_.type = type; - this->event_.jaxis.id = id; - this->event_.jaxis.axis = axis; - this->event_.jaxis.value = value; + if( state == InputState::PRESSED ) { + this->event_.type = Event::GAME_CONTROLLER_BUTTON_PRESS; + } else { + this->event_.type = Event::GAME_CONTROLLER_BUTTON_RELEASE; + } + + this->event_.timestamp = 0; + this->event_.cbutton.id = id; + this->event_.cbutton.button = button; + this->event_.cbutton.state = state; } -void JoystickAxisAction::dump( void ) const +// GameControllerAxisAction + +GameControllerAxisAction::~GameControllerAxisAction() { - // Event type & timestamp info - InputAction::dump(); + // NOM_LOG_TRACE( NOM ); +} + +GameControllerAxisAction:: +GameControllerAxisAction(JoystickID id, GameController::Axis axis) +{ + // NOM_LOG_TRACE( NOM ); - this->event_.jaxis.dump(); + this->event_.type = Event::GAME_CONTROLLER_AXIS_MOTION; + this->event_.timestamp = 0; + this->event_.caxis.id = id; + this->event_.caxis.axis = axis; + this->event_.caxis.value = 0; } } // namespace nom diff --git a/src/system/InputMapper/InputActionMapper.cpp b/src/system/InputMapper/InputActionMapper.cpp index 0e833b53..1a038d62 100644 --- a/src/system/InputMapper/InputActionMapper.cpp +++ b/src/system/InputMapper/InputActionMapper.cpp @@ -28,6 +28,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ #include "nomlib/system/InputMapper/InputActionMapper.hpp" +// Forward declarations +#include "nomlib/system/InputMapper/InputAction.hpp" + namespace nom { InputActionMapper::InputActionMapper( void ) @@ -45,7 +48,9 @@ const InputActionMapper::ActionMap& InputActionMapper::get( void ) const return this->input_map_; } -bool InputActionMapper::insert( const std::string& key, const InputAction& action, const EventCallback& callback ) +bool InputActionMapper:: +insert( const std::string& key, const InputAction& action, + const event_callback& callback ) { ActionPair p( key, std::make_shared( action ) ); p.second->set_callback( callback ); @@ -81,19 +86,4 @@ bool InputActionMapper::erase( const std::string& key ) return false; } -void InputActionMapper::dump( void ) const -{ - for( ActionMap::const_iterator itr = this->input_map_.begin(); itr != this->input_map_.end(); ++itr ) - { - if( itr->second != nullptr ) - { - itr->second->dump(); - } - else - { - NOM_LOG_ERR( NOM, "Invalid input action." ); - } - } -} - } // namespace nom diff --git a/src/system/InputMapper/InputStateMapper.cpp b/src/system/InputMapper/InputStateMapper.cpp index e494adfd..52c45985 100644 --- a/src/system/InputMapper/InputStateMapper.cpp +++ b/src/system/InputMapper/InputStateMapper.cpp @@ -28,6 +28,14 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ #include "nomlib/system/InputMapper/InputStateMapper.hpp" +// Private headers +#include "nomlib/core/err.hpp" + +// Forward declarations +#include "nomlib/system/Event.hpp" +#include "nomlib/system/EventHandler.hpp" +#include "nomlib/system/InputMapper/InputAction.hpp" + namespace nom { InputStateMapper::InputStateMapper( void ) @@ -76,23 +84,22 @@ bool InputStateMapper::insert( const std::string& key, const InputActionMapper& return true; } -bool InputStateMapper::erase( const std::string& key ) +bool InputStateMapper::erase(const std::string& key) { - InputStateMap::const_iterator itr = this->states_.find( key ); + bool result = false; - // No match found; do nothing. - if( itr == this->states_.end() ) - { - NOM_LOG_ERR( NOM, "Could not remove specified state key: " + key ); - return false; - } - else // Match found; remove the context mapping - { - this->states_.erase( itr ); - return true; + InputStateMap::const_iterator itr = this->states_.find(key); + + if( itr == this->states_.end() ) { + // Err -- match **not** found + result = false; + } else { + this->states_.erase(itr); + // Success -- match found + result = true; } - return false; + return result; } bool InputStateMapper::activate( const std::string& key ) @@ -194,12 +201,10 @@ void InputStateMapper::dump( void ) { NOM_DUMP( itr->first ); - if ( itr->second != nullptr ) - { - itr->second->dump(); - } - else - { + if( itr->second != nullptr ) { + NOM_DUMP(itr->second->event_.type); + NOM_DUMP(itr->second->event_.timestamp); + } else { NOM_LOG_ERR( NOM, "Invalid input mapping state." ); } } @@ -207,75 +212,121 @@ void InputStateMapper::dump( void ) } } -void InputStateMapper::on_event( const Event& ev ) +void InputStateMapper::set_event_handler(EventHandler& evt_handler) { - for( auto itr = this->states_.begin(); itr != this->states_.end(); ++itr ) - { - if( itr->second.active == true ) - { + this->event_handler_ = &evt_handler; + + auto event_watch = nom::event_filter( [=](const Event& evt, void* data) { + this->on_event(evt); + }); + + this->event_handler_->append_event_watch(event_watch, nullptr); +} + +// Private scope + +void InputStateMapper::on_event(const Event& ev) +{ + for( auto itr = this->states_.begin(); itr != this->states_.end(); ++itr ) { + + if( itr->second.active == true ) { + InputActionMapper::ActionMap input_map = itr->second.actions; - for( InputActionMapper::ActionMap::const_iterator itr = input_map.begin(); itr != input_map.end(); ++itr ) - { - if( ev.type == SDL_KEYDOWN || ev.type == SDL_KEYUP ) + + for( auto itr = input_map.begin(); itr != input_map.end(); ++itr ) { + + switch(ev.type) { - if( this->on_key_press( *itr->second, ev ) ) + default: break; + + case Event::KEY_PRESS: + case Event::KEY_RELEASE: { - itr->second->operator()( ev ); - } - } - else if( ev.type == SDL_MOUSEBUTTONDOWN || ev.type == SDL_MOUSEBUTTONUP ) - { - if( this->on_mouse_button( *itr->second, ev ) ) + if( this->on_key_press(*itr->second, ev) == true ) { + itr->second->operator()(ev); + } + } break; + + case Event::MOUSE_BUTTON_CLICK: + case Event::MOUSE_BUTTON_RELEASE: { - itr->second->operator()( ev ); - } - } - else if( ev.type == SDL_MOUSEWHEEL || ev.type == SDL_MOUSEWHEEL ) - { - if( this->on_mouse_wheel( *itr->second, ev ) ) + if( this->on_mouse_button(*itr->second, ev) == true ) { + itr->second->operator()(ev); + } + } break; + + case Event::MOUSE_WHEEL: { - itr->second->operator()( ev ); - } - } - else if( ev.type == SDL_JOYBUTTONDOWN || ev.type == SDL_JOYBUTTONUP ) - { - if( this->on_joystick_button( *itr->second, ev ) ) + if( this->on_mouse_wheel(*itr->second, ev) == true ) { + itr->second->operator()(ev); + } + } break; + + case Event::JOYSTICK_AXIS_MOTION: { - itr->second->operator()( ev ); - } - } - else if( ev.type == SDL_JOYAXISMOTION ) - { - if( this->on_joystick_axis( *itr->second, ev ) ) + if( this->on_joystick_axis(*itr->second, ev) == true ) { + itr->second->operator()(ev); + } + } break; + + case Event::JOYSTICK_BUTTON_PRESS: + case Event::JOYSTICK_BUTTON_RELEASE: + { + if( this->on_joystick_button(*itr->second, ev) == true ) { + itr->second->operator()(ev); + } + } break; + + case Event::JOYSTICK_HAT_MOTION: + { + if( this->on_joystick_hat(*itr->second, ev) == true ) { + itr->second->operator()(ev); + } + } break; + + case Event::GAME_CONTROLLER_AXIS_MOTION: + { + if( this->on_game_controller_axis(*itr->second, ev) == true ) { + itr->second->operator()(ev); + } + } break; + + case Event::GAME_CONTROLLER_BUTTON_PRESS: + case Event::GAME_CONTROLLER_BUTTON_RELEASE: { - itr->second->operator()( ev ); - } + if( this->on_game_controller_button(*itr->second, ev) == true ) { + itr->second->operator()(ev); + } + } break; } } // end input_map iteration } // end conditional active input state } } -bool InputStateMapper::on_key_press( const InputAction& mapping, const Event& ev ) +bool +InputStateMapper::on_key_press(const InputAction& mapping, const Event& ev) { Event evt = mapping.event(); - if( evt.type != ev.type ) return false; + if( evt.type != ev.type ) { + return false; + } // Handle a keyboard action with repeat; only trigger if the event is a // repeating one - if( evt.key.repeat != 0 ) - { + if( evt.key.repeat != 0 ) { if( evt.key.sym == ev.key.sym && evt.key.mod == ev.key.mod && - evt.key.repeat == ev.key.repeat ) + evt.key.repeat == ev.key.repeat && evt.key.state == ev.key.state ) { // Matched return true; } - } - else // Handle normal keyboard action; repeating makes no difference to us - { - if( evt.key.sym == ev.key.sym && evt.key.mod == ev.key.mod ) { + } else { + // Handle normal keyboard action; repeating makes no difference to us + if( evt.key.sym == ev.key.sym && evt.key.mod == ev.key.mod && + evt.key.state == ev.key.state ) + { // Matched return true; } @@ -289,11 +340,14 @@ bool InputStateMapper::on_mouse_button( const InputAction& mapping, const Event& { Event evt = mapping.event(); - if( evt.type != ev.type ) return false; + if( evt.type != ev.type ) { + return false; + } // Successful match is a mouse click that matches both the button used // (left, middle, right, ...) and its number of clicks (single, double, ...) - if( evt.mouse.clicks == ev.mouse.clicks && evt.mouse.button == ev.mouse.button ) + if( evt.mouse.clicks == ev.mouse.clicks && + evt.mouse.button == ev.mouse.button && evt.mouse.state == ev.mouse.state ) { // Match return true; @@ -307,99 +361,138 @@ bool InputStateMapper::on_mouse_wheel( const InputAction& mapping, const Event& { Event evt = mapping.event(); - // if( mapping.wheel == nullptr ) return false; - - if( evt.type != ev.type ) return false; + if( evt.type != ev.type ) { + return false; + } - // NOTE: X & Y coordinate values depend on the construction of a WheelAction - // object. - // if( evt.wheel.axis == MouseWheelAction::AXIS_X ) - if( evt.wheel.x != MouseWheelAction::null ) + if( evt.wheel.x == MOUSE_WHEEL_INVALID && + evt.wheel.y == MOUSE_WHEEL_INVALID ) { - // Left - if( evt.wheel.x >= MouseWheelAction::LEFT && ev.wheel.x >= MouseWheelAction::LEFT ) - { + return false; + } + + if( evt.wheel.x == MOUSE_WHEEL_INVALID ) { + + if( evt.wheel.y == MOUSE_WHEEL_UP && ev.wheel.y > 0 ) { + return true; + } else if( evt.wheel.y == MOUSE_WHEEL_DOWN && ev.wheel.y < 0 ) { return true; } + } else if( evt.wheel.y == MOUSE_WHEEL_INVALID ) { - // Right - if( evt.wheel.x <= MouseWheelAction::RIGHT && ev.wheel.x <= MouseWheelAction::RIGHT ) - { + if( evt.wheel.x == MOUSE_WHEEL_LEFT && ev.wheel.x < 0 ) { + return true; + } else if( evt.wheel.x == MOUSE_WHEEL_RIGHT && ev.wheel.x > 0 ) { return true; } } - // else if( evt.wheel.axis == MouseWheelAction::AXIS_Y ) - else if( evt.wheel.y != MouseWheelAction::null ) + + return false; +} + +bool InputStateMapper:: +on_joystick_button(const InputAction& mapping, const Event& ev) +{ + Event evt = mapping.event(); + + if( evt.type != ev.type ) { + return false; + } + + if( evt.jbutton.id != ev.jbutton.id ) { + return false; + } + + if( evt.jbutton.button == ev.jbutton.button && + evt.jbutton.state == ev.jbutton.state ) { - // Up - if( evt.wheel.y >= MouseWheelAction::UP && ev.wheel.y >= MouseWheelAction::UP ) - { - return true; - } + return true; + } - // Down - if( evt.wheel.y <= MouseWheelAction::DOWN && ev.wheel.y <= MouseWheelAction::DOWN ) - { - return true; - } + return false; +} + +bool InputStateMapper:: +on_joystick_axis(const InputAction& mapping, const Event& ev) +{ + Event evt = mapping.event(); + + if( evt.type != ev.type ) { + return false; + } + + if( evt.jaxis.id != ev.jaxis.id ) { + return false; } - // Initialized to an invalid state! - NOM_ASSERT( evt.wheel.x == 0 || evt.wheel.y == 0 ); + if( evt.jaxis.axis == ev.jaxis.axis ) { + return true; + } return false; } -bool InputStateMapper::on_joystick_button( const InputAction& mapping, const Event& ev ) +bool InputStateMapper:: +on_joystick_hat(const InputAction& mapping, const Event& ev) { Event evt = mapping.event(); - // if( mapping.jbutton == nullptr ) return false; + if( evt.type != ev.type ) { + return false; + } - if( evt.type != ev.type ) return false; + if( evt.jhat.id != ev.jhat.id ) { + return false; + } - if( evt.jbutton.id != ev.jbutton.id ) return false; + if( evt.jhat.hat != ev.jhat.hat ) { + return false; + } - if( evt.jbutton.button == ev.jbutton.button ) return true; + if( evt.jhat.value ^ ev.jhat.value ) { + return true; + } return false; } -// FIXME: Implementation is incomplete! -bool InputStateMapper::on_joystick_axis( const InputAction& mapping, const Event& ev ) +bool InputStateMapper:: +on_game_controller_button(const InputAction& mapping, const Event& ev) { Event evt = mapping.event(); - // if( mapping.jaxis == nullptr ) return false; + if( evt.type != ev.type ) { + return false; + } - if( evt.type != ev.type ) return false; + if( evt.cbutton.id != ev.cbutton.id ) { + return false; + } - if( evt.jaxis.id != ev.jaxis.id ) return false; + if( evt.cbutton.button == ev.cbutton.button && + evt.cbutton.state == ev.cbutton.state ) + { + return true; + } - if( evt.jaxis.axis != ev.jaxis.axis ) return false; + return false; +} - // Within dead-zone tolerance - if( ( ev.jaxis.value < -3200 ) || ( ev.jaxis.value > 3200 ) ) - { - // Up-down axis (Sony PS3 game controller) - if( evt.jaxis.axis == 0 ) - { - // Up - if( evt.jaxis.value < 0 ) return true; +bool InputStateMapper:: +on_game_controller_axis(const InputAction& mapping, const Event& ev) +{ + Event evt = mapping.event(); - // Down - if( evt.jaxis.value > 0 ) return true; - } + if( evt.type != ev.type ) { + return false; + } - // Left-right axis (Sony PS3 game controller) - if( evt.jaxis.axis == 1 ) - { - // Left - if( evt.jaxis.value < 0 ) return true; + if( evt.caxis.id != ev.caxis.id ) { + return false; + } - // Right - if( evt.jaxis.value > 0 ) return true; - } + if( evt.caxis.axis == ev.caxis.axis ) { + return true; } return false; diff --git a/src/system/Joystick.cpp b/src/system/Joystick.cpp index 434c4df8..0766f90c 100644 --- a/src/system/Joystick.cpp +++ b/src/system/Joystick.cpp @@ -28,104 +28,301 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ #include "nomlib/system/Joystick.hpp" +// Private helpers +#include "nomlib/core/err.hpp" +#include "nomlib/core/unique_ptr.hpp" +#include "nomlib/system/SDL_helpers.hpp" + +// Forward declarations +#include + +static_assert( sizeof(SDL_JoystickGUID) == nom::GUID_MAX_LENGTH, + "SDL_JoystickGUID struct mismatch" ); + namespace nom { -Joystick::Joystick( void ) +bool init_joystick_subsystem() { - NOM_LOG_TRACE( NOM_LOG_CATEGORY_TRACE_EVENT ); + if( SDL_WasInit(SDL_INIT_JOYSTICK) == 0 ) { + if( SDL_InitSubSystem(SDL_INIT_JOYSTICK) < 0 ) { + nom::set_error( SDL_GetError() ); + return false; + } + } - this->impl_ = IJoystick::UniquePtr ( new SDLJoystick() ); + return true; } -Joystick::~Joystick( void ) +void shutdown_joystick_subsystem() { - NOM_LOG_TRACE( NOM_LOG_CATEGORY_TRACE_EVENT ); + if( SDL_WasInit(SDL_INIT_JOYSTICK) != 0 ) { + SDL_QuitSubSystem(SDL_INIT_JOYSTICK); + } +} - this->shutdown(); +void JoystickDeleter(SDL_Joystick* dev) +{ + if( dev != nullptr && SDL_JoystickGetAttached(dev) == true ) { + SDL_JoystickClose(dev); + } } -bool Joystick::initialize( void ) +Joystick::Joystick() : + device_( joystick_dev(nullptr, JoystickDeleter) ) { - if( this->impl_ && this->impl_->initialize() ) return true; + NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_EVENT, + NOM_LOG_PRIORITY_VERBOSE ); +} - return false; +Joystick::~Joystick() +{ + NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_EVENT, + NOM_LOG_PRIORITY_VERBOSE ); } -void Joystick::shutdown( void ) +SDL_Joystick* Joystick::device() const { - if( this->impl_ ) this->impl_->shutdown(); + return this->device_.get(); } -Joystick::JoystickID Joystick::first_joystick( void ) const +bool Joystick::attached() const { - auto itr = this->joysticks_.begin(); + SDL_bool result = SDL_JoystickGetAttached( this->device_.get() ); + if( result == SDL_FALSE ) { + nom::set_error( SDL_GetError() ); + return false; + } - return itr->first; + return true; } -Joystick::JoystickID Joystick::last_joystick( void ) const +JoystickID Joystick::device_id() const { - auto itr = this->joysticks_.end(); + SDL_JoystickID dev_id = SDL_JoystickInstanceID( this->device_.get() ); + if( dev_id < 0 ) { + nom::set_error( SDL_GetError() ); + } + + return dev_id; +} + +std::string Joystick::name() const +{ + const char* dev_name = nullptr; + std::string result; + + dev_name = SDL_JoystickName( this->device_.get() ); + if( dev_name != nullptr ) { + result = dev_name; + } else { + nom::set_error( SDL_GetError() ); + } - return itr->first; + return result; } -int Joystick::num_joysticks( void ) const +bool Joystick::open(JoystickIndex device_index) { - if( this->impl_ ) - { - return this->impl_->num_joysticks(); + SDL_Joystick* dev = SDL_JoystickOpen(device_index); + if( dev != nullptr ) { + this->device_.reset(dev); + + // Success! + return( this->attached() == true ); + } else { + nom::set_error( SDL_GetError() ); + return false; } +} + +void Joystick::close() +{ + if( this->device_ != nullptr && this->attached() == true ) { + SDL_JoystickClose( this->device_.get() ); + } +} + +int Joystick::num_axes() +{ + int num_axes = SDL_JoystickNumAxes( this->device_.get() ); + if( num_axes < 0 ) { + nom::set_error( SDL_GetError() ); + } + + return num_axes; +} + +int Joystick::num_track_balls() +{ + int num_balls = SDL_JoystickNumBalls( this->device_.get() ); + if( num_balls < 0 ) { + nom::set_error( SDL_GetError() ); + } + + return num_balls; +} + +int Joystick::num_buttons() +{ + int num_buttons = SDL_JoystickNumButtons( this->device_.get() ); + if( num_buttons < 0 ) { + nom::set_error( SDL_GetError() ); + } + + return num_buttons; +} + +int Joystick::num_hats() +{ + int num_hats = SDL_JoystickNumHats( this->device_.get() ); + if( num_hats < 0 ) { + nom::set_error( SDL_GetError() ); + } + + return num_hats; +} + +// static +int Joystick::set_event_state(int state) +{ + int result = SDL_JoystickEventState(state); - return 0; + return result; } -const std::string& Joystick::name( JoystickID idx ) +// static +void Joystick::update() { - return this->joysticks_[idx]; + SDL_JoystickUpdate(); } -const Joystick::JoystickNames Joystick::names( void ) const +// static +int Joystick::num_joysticks() { - JoystickNames joysticks; + int num_joysticks = SDL_NumJoysticks(); - for( auto itr = this->joysticks_.begin(); itr != this->joysticks_.end(); ++itr ) - { - joysticks.push_back( itr->second ); + return num_joysticks; +} + +// static +std::string Joystick::name(JoystickIndex device_index) +{ + const char* dev_name = nullptr; + std::string result; + + dev_name = SDL_JoystickNameForIndex(device_index); + if( dev_name != nullptr ) { + result = dev_name; + } else { + nom::set_error( SDL_GetError() ); } - return joysticks; + return result; } -void Joystick::enumerate_devices( void ) +JoystickGUID Joystick::device_guid() const { - int num_joysticks = 0; + JoystickGUID cloned_guid = {}; - if( ! impl_ ) - { - NOM_LOG_ERR( NOM, "No joysticks are available: impl_ is NULL." ); - return; + if( this->attached() == true ) { + + SDL_JoystickGUID dev_guid = SDL_JoystickGetGUID( this->device_.get() ); + + cloned_guid = nom::convert_SDL_JoystickGUID(dev_guid); } - num_joysticks = this->impl_->num_joysticks(); + return cloned_guid; +} + +// static +JoystickGUID Joystick::device_guid(JoystickIndex device_index) +{ + JoystickGUID cloned_guid = {}; + SDL_JoystickGUID dev_guid = SDL_JoystickGetDeviceGUID(device_index); + + cloned_guid = nom::convert_SDL_JoystickGUID(dev_guid); - NOM_LOG_INFO( NOM_LOG_CATEGORY_EVENT, num_joysticks, "joysticks were found" ); + return cloned_guid; +} + +// static +std::string Joystick::device_guid_string(JoystickGUID guid) +{ + std::string result; - for( uint idx = 0; idx < num_joysticks; ++idx ) - { - if( this->impl_->open( idx ) ) - { - NOM_LOG_INFO( NOM_LOG_CATEGORY_EVENT, this->impl_->name() ); + // NOTE: Match the GUID output string length in SDL2.hg/test/testjoystick.c + char output_guid[64]; - Joystick::Pair p( this->impl_->id(), this->impl_->name() ); - this->joysticks_.insert( p ); + SDL_JoystickGUID dev_guid = nom::convert_SDL_JoystickGUID(guid); - // if( res == this->joysticks_.end() ) - // { - // NOM_LOG_ERR( NOM, "Could not insert joystick into map." ); - // } + SDL_JoystickGetGUIDString(dev_guid, output_guid, sizeof(output_guid) ); + + result = output_guid; + + return result; +} + +std::string Joystick::device_guid_string() const +{ + JoystickGUID dev_guid = {}; + std::string result; + + if( this->attached() == true ) { + dev_guid = this->device_guid(); + + result = Joystick::device_guid_string(dev_guid); + + if( result.length() < 1 ) { + nom::set_error( SDL_GetError() ); } } + + return result; +} + +// static +JoystickID Joystick::device_id(JoystickIndex device_index) +{ + JoystickID dev_id = -1; + + Joystick jdev; + if( jdev.open(device_index) == true ) { + dev_id = jdev.device_id(); + } + + return dev_id; +} + +std::unique_ptr make_unique_joystick() +{ + auto dev = nom::make_unique(); + + return std::move(dev); +} + +std::shared_ptr make_shared_joystick() +{ + auto dev = std::make_shared(); + + return dev; +} + +JoystickGUID convert_SDL_JoystickGUID(SDL_JoystickGUID dev_guid) +{ + JoystickGUID cloned_guid = {}; + + std::memcpy(cloned_guid.data, dev_guid.data, sizeof(dev_guid.data) ); + + return cloned_guid; +} + +SDL_JoystickGUID convert_SDL_JoystickGUID(JoystickGUID dev_guid) +{ + SDL_JoystickGUID cloned_guid = {}; + + std::memcpy(cloned_guid.data, dev_guid.data, sizeof(dev_guid.data) ); + + return cloned_guid; } } // namespace nom diff --git a/src/system/JoystickEventHandler.cpp b/src/system/JoystickEventHandler.cpp new file mode 100644 index 00000000..3fa97975 --- /dev/null +++ b/src/system/JoystickEventHandler.cpp @@ -0,0 +1,118 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#include "nomlib/system/JoystickEventHandler.hpp" + +// Private headers +#include "nomlib/core/err.hpp" + +namespace nom { + +JoystickEventHandler::JoystickEventHandler() +{ + NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_EVENT, + NOM_LOG_PRIORITY_VERBOSE ); +} + +JoystickEventHandler::~JoystickEventHandler() +{ + NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_EVENT, + NOM_LOG_PRIORITY_VERBOSE ); +} + +nom::size_type JoystickEventHandler::num_joysticks() const +{ + auto result = this->joysticks_.size(); + + return result; +} + +Joystick* JoystickEventHandler::joystick(JoystickID dev_id) const +{ + auto res = this->joysticks_.find(dev_id); + if( res != this->joysticks_.end() ) { + // Success -- device found + return res->second.get(); + } else { + // Err -- device **not** found + return nullptr; + } +} + +bool JoystickEventHandler::joystick_exists(JoystickID dev_id) +{ + auto res = this->joysticks_.find(dev_id); + if( res != this->joysticks_.end() ) { + // Success -- device exists! + return true; + } else { + // Failure -- device does **not** exist! + return false; + } +} + +Joystick* JoystickEventHandler::add_joystick(JoystickIndex device_index) +{ + Joystick* result = nullptr; + + auto joy_dev = nom::make_unique_joystick(); + if( joy_dev != nullptr && joy_dev->open(device_index) == true ) { + + JoystickID dev_id = joy_dev->device_id(); + std::string dev_name = joy_dev->name(); + + if( dev_id < 0 ) { + // Err + return result; + } + + // Success! + this->joysticks_[dev_id] = std::move(joy_dev); + result = this->joysticks_[dev_id].get(); + } + + return result; +} + +bool JoystickEventHandler::remove_joystick(JoystickID dev_id) +{ + auto res = this->joysticks_.find(dev_id); + if( res != this->joysticks_.end() ) { + + // Success -- found device; freeing and removing + res->second->close(); + this->joysticks_.erase(res); + + return true; + } else { + // Err -- device not found + return false; + } +} + +} // namespace nom diff --git a/src/system/SDLApp.cpp b/src/system/SDLApp.cpp index 75f39dce..b2c908aa 100644 --- a/src/system/SDLApp.cpp +++ b/src/system/SDLApp.cpp @@ -28,16 +28,15 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ #include "nomlib/system/SDLApp.hpp" -// Private headers (third-party libs) -// #include - // Private headers +#include "nomlib/core/err.hpp" #include "nomlib/system/SDL_helpers.hpp" #include "nomlib/system/init.hpp" #include "nomlib/system/ColorDatabase.hpp" // Forward declarations #include "nomlib/system/Event.hpp" +#include "nomlib/system/EventHandler.hpp" #include "nomlib/graphics/IDrawable.hpp" #include "nomlib/system/StateMachine.hpp" @@ -86,18 +85,6 @@ sint SDLApp::Run( void ) return NOM_EXIT_SUCCESS; } -void SDLApp::on_event( const Event& ev ) -{ - // First, handle our own events - EventHandler::process_event( ev ); - - // Next, handle the state machine's event loop: - if( this->state_ != nullptr ) - { - this->state()->on_event( ev ); - } -} - void SDLApp::on_update( float delta ) { if( this->state_ != nullptr ) @@ -114,26 +101,6 @@ void SDLApp::on_draw( RenderWindow& target ) } } -void SDLApp::on_window_close( const Event& ev ) -{ - // A call is made here to the virtual method being re-implemented here in - // order to catch debugging output with debug builds compiled in; see - // EventHandler.hpp. - EventHandler::on_window_close( ev ); - - this->on_app_quit( ev ); -} - -void SDLApp::on_app_quit( const Event& ev ) -{ - // A call is made here to the virtual method being re-implemented here in - // order to catch debugging output with debug builds compiled in; see - // EventHandler.hpp. - EventHandler::on_app_quit( ev ); - - this->quit(); -} - bool SDLApp::running( void ) { if ( this->app_state() == true ) return true; @@ -194,8 +161,261 @@ void SDLApp::set_state_machine( StateMachine* mech ) this->state_.reset( mech ); } +void SDLApp::set_event_handler(EventHandler& evt_handler) +{ + this->event_handler_ = &evt_handler; + + auto event_watch = nom::event_filter( [=](const Event& evt, void* data) { + this->on_app_event(evt); + }); + + this->event_handler_->append_event_watch(event_watch, nullptr); +} + +// Protected scope + +void SDLApp::on_app_quit(const Event& ev) +{ + // Default implementation + + this->quit(); +} + +void SDLApp::on_window_shown(const Event& ev) +{ + // Default implementation +} + +void SDLApp::on_window_hidden(const Event& ev) +{ + // Default implementation +} + +void SDLApp::on_window_exposed(const Event& ev) +{ + // Default implementation +} + +void SDLApp::on_window_moved(const Event& ev) +{ + // Default implementation +} + +void SDLApp::on_window_resized(const Event& ev) +{ + // Default implementation +} + +void SDLApp::on_window_size_changed(const Event& ev) +{ + // Default implementation +} + +void SDLApp::on_window_minimized(const Event& ev) +{ + // Default implementation +} + +void SDLApp::on_window_maximized(const Event& ev) +{ + // Default implementation +} + +void SDLApp::on_window_restored(const Event& ev) +{ + // Default implementation +} + +void SDLApp::on_window_mouse_focus(const Event& ev) +{ + // Default implementation +} + +void SDLApp::on_window_mouse_focus_lost(const Event& ev) +{ + // Default implementation +} + +void SDLApp::on_window_keyboard_focus(const Event& ev) +{ + // Default implementation +} + +void SDLApp::on_window_keyboard_focus_lost(const Event& ev) +{ + // Default implementation +} + +void SDLApp::on_window_close(const Event& ev) +{ + // Default implementation + + this->on_app_quit(ev); +} + +void SDLApp::on_input_event(const Event& ev) +{ + // Default implementation +} + +void SDLApp::on_drag_drop(const Event& ev) +{ + // Default implementation +} + +void SDLApp::on_user_event(const Event& ev) +{ + // Default implementation +} + +void SDLApp::on_render_targets_reset(const Event& ev) +{ + // Default implementation +} + +// NOTE: Not available until the release of SDL 2.0.4 +#if 0 +void SDLApp::on_render_device_reset(const Event& ev) +{ + // Default implementation +} +#endif + // Private scope +void SDLApp::on_app_event(const Event& ev) +{ + // Handle our events + this->process_event(ev); + + // Handle the state's events + if( this->state_ != nullptr ) { + this->state()->on_event(ev); + } +} + +void SDLApp::process_event(const Event& ev) +{ + switch(ev.type) + { + case Event::QUIT_EVENT: + { + this->on_app_quit(ev); + } break; + + case Event::WINDOW_EVENT: + { + switch(ev.window.event) + { + default: break; + + case WindowEvent::NONE: break; + + case WindowEvent::SHOWN: + { + this->on_window_shown(ev); + } break; + + case WindowEvent::HIDDEN: + { + this->on_window_hidden(ev); + } break; + + case WindowEvent::EXPOSED: + { + this->on_window_exposed(ev); + } break; + + case WindowEvent::MOVED: + { + this->on_window_moved(ev); + } break; + + case WindowEvent::RESIZED: + { + this->on_window_resized(ev); + } break; + + case WindowEvent::SIZE_CHANGED: + { + this->on_window_size_changed(ev); + } break; + + case WindowEvent::MINIMIZED: + { + this->on_window_minimized(ev); + } break; + + case WindowEvent::MAXIMIZED: + { + this->on_window_maximized(ev); + } break; + + case WindowEvent::RESTORED: + { + this->on_window_restored(ev); + } break; + + case WindowEvent::MOUSE_FOCUS_GAINED: + { + this->on_window_mouse_focus(ev); + } break; + + case WindowEvent::MOUSE_FOCUS_LOST: + { + this->on_window_mouse_focus_lost(ev); + } break; + + case WindowEvent::KEYBOARD_FOCUS_GAINED: + { + this->on_window_keyboard_focus(ev); + } break; + + case WindowEvent::KEYBOARD_FOCUS_LOST: + { + this->on_window_keyboard_focus_lost(ev); + } break; + + case WindowEvent::CLOSE: + { + this->on_window_close(ev); + } break; + } // end switch ev.window.event + + } break; // end case WINDOW_EVENT + + case Event::SYSWMEVENT: break; + + case Event::DROP_FILE: + { + this->on_drag_drop(ev); + } break; + + case Event::RENDER_TARGETS_RESET: + { + this->on_render_targets_reset(ev); + } break; + +// NOTE: Not available until the release of SDL 2.0.4 +#if 0 + case Event::RENDER_DEVICE_RESET: + { + this->on_render_device_reset(ev); + } break; +#endif + + case Event::USER_EVENT: + { + this->on_user_event(ev); + } break; + + default: + { + this->on_input_event(ev); + } break; + } // end switch ev.type +} + bool SDLApp::initialize( uint32 flags ) { this->app_timer_.start(); diff --git a/src/system/SDLJoystick.cpp b/src/system/SDLJoystick.cpp deleted file mode 100644 index 1600132c..00000000 --- a/src/system/SDLJoystick.cpp +++ /dev/null @@ -1,258 +0,0 @@ -/****************************************************************************** - - nomlib - C++11 cross-platform game engine - -Copyright (c) 2013, 2014 Jeffrey Carpenter -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -******************************************************************************/ -#include "nomlib/system/SDLJoystick.hpp" - -namespace nom { - -SDLJoystick::SDLJoystick( void ) : - device_{ SDLJoystick::UniquePtr( nullptr, priv::Free_Joystick ) } -{ - // NOM_LOG_TRACE( NOM ); -} - -SDLJoystick::~SDLJoystick( void ) -{ - // NOM_LOG_TRACE( NOM ); - - priv::Free_Joystick( this->device_.get() ); - - SDLJoystick::shutdown(); -} - -bool SDLJoystick::initialize( void ) -{ - if ( SDL_WasInit( SDL_INIT_JOYSTICK ) == false ) - { - if ( SDL_InitSubSystem( SDL_INIT_JOYSTICK ) < 0 ) - { - NOM_LOG_ERR( NOM, SDL_GetError() ); - return false; - } - } - - if( this->set_event_state( SDL_ENABLE ) != 1 ) - { - NOM_LOG_ERR( NOM, SDL_GetError() ); - - return false; - } - - return true; -} - -void SDLJoystick::shutdown( void ) -{ - // Implemented in nom::quit; see src/system/init.cpp - // - // FIXME?: - // - // We cannot shutdown the system here due to issues with the joystick object - // we have in EventHandler being destructed in between game states. -} - -int SDLJoystick::num_joysticks( void ) const -{ - int num_joysticks = 0; - - num_joysticks = SDL_NumJoysticks(); - - if( num_joysticks < 0 ) - { - NOM_LOG_ERR( NOM, SDL_GetError() ); - } - - return num_joysticks; -} - -bool SDLJoystick::attached( void ) const -{ - SDL_bool ret; - - ret = SDL_JoystickGetAttached( this->device_.get() ); - - if( ret == SDL_TRUE ) return true; - - // SDL_FALSE - return false; -} - -int SDLJoystick::id( void ) const -{ - SDL_JoystickID device_id; - - device_id = SDL_JoystickInstanceID( this->device_.get() ); - - if( device_id < 0 ) - { - NOM_LOG_ERR( NOM, SDL_GetError() ); - } - - return device_id; -} - -const std::string SDLJoystick::name( void ) const -{ - std::string device_name = SDL_JoystickName( this->device_.get() ); - - if( device_name.length() < 1 ) - { - NOM_LOG_ERR( NOM, SDL_GetError() ); - } - - return device_name; -} - -bool SDLJoystick::open( int idx ) -{ - SDL_Joystick* device = nullptr; - - device = SDL_JoystickOpen( idx ); - - if( device != nullptr ) - { - this->device_.reset( device ); - - return true; - } - - // Not open? - return false; -} - -void SDLJoystick::close( void ) -{ - SDL_JoystickClose( this->device_.get() ); -} - -SDL_JoystickGUID SDLJoystick::device_guid( int idx ) const -{ - SDL_JoystickGUID id; - - id = SDL_JoystickGetDeviceGUID( idx ); - - // if( id.data[0] == 0 ) - // { - // NOM_LOG_ERR( NOM, SDL_GetError() ); - // } - - return id; -} - -SDL_JoystickGUID SDLJoystick::device_guid( void ) const -{ - SDL_JoystickGUID id; - - id = SDL_JoystickGetGUID( this->device_.get() ); - - // if( id.data[0] == 0 ) - // { - // NOM_LOG_ERR( NOM, SDL_GetError() ); - // } - - return id; -} - -const std::string SDLJoystick::device_guid_string( void ) const -{ - const uint GUID_STRING_LENGTH = 64; - char guid_string[GUID_STRING_LENGTH]; - - SDL_JoystickGUID id; - - if( this->attached() ) - { - id = this->device_guid(); - - SDL_JoystickGetGUIDString( id, guid_string, GUID_STRING_LENGTH ); - - return std::string( guid_string ); - } - - NOM_LOG_ERR( NOM, "Could not obtain joystick device GUID: device is not open." ); - - return "\0"; -} - -// FIXME -bool SDLJoystick::game_controller( void ) const -{ - SDL_bool ret; - int id = 0; - - id = this->id(); - - if( id >= 0 ) - { - ret = SDL_IsGameController( id ); - - if( ret == SDL_TRUE ) - { - return true; - } - } - - // SDL_FALSE - return false; -} - -int SDLJoystick::set_event_state( int state ) const -{ - int ret = 0; - - ret = SDL_JoystickEventState( state ); - - if( ret < 0 ) - { - NOM_LOG_ERR( NOM, SDL_GetError() ); - } - - return ret; -} - -void SDLJoystick::update( void ) const -{ - SDL_JoystickUpdate(); -} - -namespace priv { - -void Free_Joystick( SDL_Joystick* joy ) -{ - if ( joy != nullptr ) - { - if ( SDL_JoystickGetAttached( joy ) ) - { - SDL_JoystickClose( joy ); - joy = nullptr; - } - } -} - -} // namespace priv -} // namespace nom diff --git a/src/system/SDL_helpers.cpp b/src/system/SDL_helpers.cpp index d609a473..0dfe8291 100644 --- a/src/system/SDL_helpers.cpp +++ b/src/system/SDL_helpers.cpp @@ -36,16 +36,30 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. namespace nom { -SDL_bool SDL_BOOL ( bool value ) +BlendMode blend_mode(SDL_BlendMode mode) { - if ( value ) - { - return SDL_TRUE; + if( mode == SDL_BLENDMODE_BLEND ) { + return BlendMode::BLEND_MODE_BLEND; + } else if( mode == SDL_BLENDMODE_ADD ) { + return BlendMode::BLEND_MODE_ADD; + } else if( mode == SDL_BLENDMODE_MOD ) { + return BlendMode::BLEND_MODE_MOD; } - else - { - return SDL_FALSE; + + return BlendMode::BLEND_MODE_NONE; +} + +SDL_BlendMode SDL_blend_mode(BlendMode mode) +{ + if( mode == BlendMode::BLEND_MODE_BLEND ) { + return SDL_BLENDMODE_BLEND; + } else if( mode == BlendMode::BLEND_MODE_ADD ) { + return SDL_BLENDMODE_ADD; + } else if( mode == BlendMode::BLEND_MODE_MOD ) { + return SDL_BLENDMODE_MOD; } + + return SDL_BLENDMODE_NONE; } SDL_Rect SDL_RECT ( const IntRect& rectangle ) @@ -142,24 +156,29 @@ uint32 RGBA ( const Color4i& color, uint32 fmt ) return SDL_MapRGBA ( SDL_AllocFormat(fmt), color.r, color.g, color.b, color.a ); } -std::string hint( const std::string& name ) +std::string hint(const std::string& name) { - const char* result; - - result = SDL_GetHint( name.c_str() ); + const char* hint_str = name.c_str(); + const char* hint_result = nullptr; + std::string result = "\0"; - // Success - if( result != nullptr ) return result; + hint_result = SDL_GetHint(hint_str); + if( hint_result != nullptr ) { + result = hint_result; + } - // Err - return "\0"; + return result; } -bool set_hint( const std::string& name, const std::string& value ) +bool set_hint(const std::string& name, const std::string& value) { - if( SDL_SetHint( name.c_str(), value.c_str() ) == false ) return false; + bool result = true; - return true; + if( SDL_SetHint( name.c_str(), value.c_str() ) == false ) { + result = false; + } + + return result; } const std::string PIXEL_FORMAT_NAME ( uint32 format ) diff --git a/src/system/StateMachine.cpp b/src/system/StateMachine.cpp index 8d0d87e4..ced07c79 100644 --- a/src/system/StateMachine.cpp +++ b/src/system/StateMachine.cpp @@ -135,21 +135,11 @@ void StateMachine::pop_state( void_ptr data ) // this->states_.back()->on_resume( data ); } -void StateMachine::on_event( const Event& ev ) +void StateMachine::on_event(const Event& ev) { // Ensure that we have a state in which we can handle events on - if ( ! this->states_.empty() ) - { - // Call state's EventHandler::process_event only when IState::on_event - // implementation returns FALSE (the default implementation). - // - // This is currently required if we wish to process GUI events; see also: - // TTcards::Game::on_event, TTcards::ContinueMenuState::on_event. - if( this->states_.back()->on_event( ev ) == false ) - { - // Game state inputs & events loop processing - this->states_.back()->process_event( ev ); - } + if( this->states_.empty() == false ) { + this->states_.back()->on_event(ev); } } diff --git a/src/system/Timer.cpp b/src/system/Timer.cpp index 977384d0..45cb0cc0 100644 --- a/src/system/Timer.cpp +++ b/src/system/Timer.cpp @@ -130,9 +130,27 @@ const std::string Timer::ticksAsString ( void ) const return std::to_string ( static_cast ( this->ticks() ) ); } -uint32 Timer::seconds ( float milliseconds ) const +real32 Timer::to_seconds() const { - return static_cast ( milliseconds * 1000.f ); + real32 elapsed_ticks = this->ticks(); + + real32 result = this->to_seconds(elapsed_ticks); + + return result; +} + +uint32 Timer::to_milliseconds(real32 seconds) +{ + uint32 result = (seconds * 1000.0f); + + return result; +} + +real32 Timer::to_seconds(uint32 ticks) +{ + real32 result = (ticks / 1000.0f) * 1.0f; + + return result; } } // namespace nom diff --git a/src/system/init.cpp b/src/system/init.cpp index a11f8664..063f7608 100644 --- a/src/system/init.cpp +++ b/src/system/init.cpp @@ -38,7 +38,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "nomlib/system/File.hpp" // Private headers (SystemColors) -#include "nomlib/core/helpers.hpp" +#include "nomlib/core/unique_ptr.hpp" // Forward declarations (SystemColors) #include "nomlib/system/ColorDatabase.hpp" @@ -200,11 +200,6 @@ void quit( void ) TTF_Quit(); IMG_Quit(); - if( SDL_WasInit( SDL_INIT_JOYSTICK ) ) - { - SDL_QuitSubSystem( SDL_INIT_JOYSTICK ); - } - SDL_Quit(); NOM_LOG_DEBUG( NOM_LOG_CATEGORY_MEMORY_TOTALS, "Total memory allocation (in bytes): ", IObject::total_alloc_bytes ); diff --git a/src/system/unix/UnixFile.cpp b/src/system/unix/UnixFile.cpp index 4f695d25..d9c01783 100644 --- a/src/system/unix/UnixFile.cpp +++ b/src/system/unix/UnixFile.cpp @@ -29,11 +29,21 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "nomlib/system/unix/UnixFile.hpp" // Private headers (third-party libs) +#include +//#include +#include +#include +#include + #if defined( NOM_PLATFORM_OSX ) #include #include #endif +#if defined (NOM_PLATFORM_LINUX ) + //#include +#endif + // Private headers #include "nomlib/system/Path.hpp" @@ -225,6 +235,8 @@ std::vector UnixFile::read_dir( const std::string& dir_path ) const std::string UnixFile::resource_path( const std::string& identifier ) { + return "\0"; +/* char resources_path [ PATH_MAX ]; // file-system path CFBundleRef bundle; // bundle type reference @@ -259,13 +271,16 @@ const std::string UnixFile::resource_path( const std::string& identifier ) CFRelease ( resourcesURL ); return resources_path; + */ } -#pragma clang diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +//#pragma clang diagnostic push +//#pragma GCC diagnostic ignored "-Wdeprecated-declarations" const std::string UnixFile::user_documents_path( void ) { + return "/home/jeff/Documents"; +/* FSRef ref; OSType folderType = kDocumentsFolderType; char path[PATH_MAX]; @@ -275,10 +290,13 @@ const std::string UnixFile::user_documents_path( void ) FSRefMakePath ( &ref, (uint8*) &path, PATH_MAX ); return std::string ( path ); + */ } const std::string UnixFile::user_app_support_path( void ) { + return "/home/jeff/.config"; + /* FSRef ref; OSType folderType = kApplicationSupportFolderType; char path[PATH_MAX]; @@ -288,10 +306,13 @@ const std::string UnixFile::user_app_support_path( void ) FSRefMakePath ( &ref, (uint8*) &path, PATH_MAX ); return std::string ( path ); + */ } const std::string UnixFile::user_home_path( void ) { + return "/home/jeff"; + /* char path[PATH_MAX]; FSRef ref; OSType folderType = kCurrentUserFolderType; @@ -300,11 +321,13 @@ const std::string UnixFile::user_home_path( void ) FSRefMakePath( &ref, ( uint8* ) &path, PATH_MAX ); - return path; + return path;*/ } const std::string UnixFile::system_path( void ) { + return "\0"; + /* char path[PATH_MAX]; FSRef ref; OSType folderType = kSystemFolderType; @@ -314,6 +337,7 @@ const std::string UnixFile::system_path( void ) FSRefMakePath( &ref, ( uint8* ) &path, PATH_MAX ); return path; +*/ } // const std::string UnixFile::system_library_path( void ) @@ -330,7 +354,7 @@ const std::string UnixFile::system_path( void ) // return p.prepend( "Fonts" ); // } -#pragma clang diagnostic pop +//#pragma clang diagnostic pop bool UnixFile::mkdir( const std::string& path ) { @@ -436,4 +460,12 @@ std::string UnixFile::env( const std::string& path ) return "\0"; } +nom::size_type UnixFile::num_files(const std::string& path) +{ + std::vector num_files_per_dir = + this->read_dir(path); + + return num_files_per_dir.size(); +} + } // namespace nom diff --git a/src/system/windows/WinFile.cpp b/src/system/windows/WinFile.cpp index 7ded97cd..76b13c80 100644 --- a/src/system/windows/WinFile.cpp +++ b/src/system/windows/WinFile.cpp @@ -465,4 +465,12 @@ std::string WinFile::env( const std::string& path ) return "\0"; } +nom::size_type WinFile::num_files(const std::string& path) +{ + std::vector num_files_per_dir = + this->read_dir(path); + + return num_files_per_dir.size(); +} + } // namespace nom diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt old mode 100644 new mode 100755 index e9aab9fe..705cecd6 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -7,20 +7,6 @@ # NOTE: You may need to issue a 'make rebuild_cache' after changing test # fixture names, in order to get 'make test' to recognize the modified test # code. -# -# NOTE: To display a unit test's full debugging output (NOM_LOG_ERR, NOM_DUMP -# and friends), run the test's executable directly. This debugging output will -# not be shown when using the 'test' target -- i.e.: make target. -# -# NOTE: Compiling nomlib under Windows without iterator asserts -- -# /D_ITERATOR_DBUG_LEVEL=0 -- will cause GTest to throw SEH exception errors -# before it is able to display actual & expected results when a unit test fails. -# -# NOTE: The custom command line processing used by nom::UnitTest for interactive -# interactive test runs breaks under Windows when building with the target -# library ${GTEST_BOTH_LIBRARIES}, due to GTest's main library overwriting our -# unit test's main functions. In summary, only the library target -# ${GTEST_LIBRARY} should be used, to ensure cross-platform support. # Source file inclusion root (directory); must be an absolute path. set( TESTS_SRC_DIR "${PROJECT_SOURCE_DIR}/tests/src" ) @@ -28,6 +14,39 @@ set( TESTS_SRC_DIR "${PROJECT_SOURCE_DIR}/tests/src" ) # Header file inclusion root (directory); must be an absolute path. set( TESTS_INC_DIR "${PROJECT_SOURCE_DIR}/tests/include" ) +# Use common build output directories for MSVCPP && Xcode project files. +# +# IMPORTANT: Debug and Release build targets **must** be kept in separate build +# trees! +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}/tests/Debug") +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/tests/Release") + +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}/tests/Debug") +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/tests/Release") + +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}/tests/Debug") +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/tests/Release") + +if(DEBUG) + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_LIBRARY_OUTPUT_DIRECTORY_DEBUG}) + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG}) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG}) +else() # Release builds + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELEASE}) + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE}) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE}) +endif() + +# This path is used for the local installation of required dependencies for +# running unit tests, such as dependent resource files. Additionally, when +# building on Windows, this will be the path that dependent DLLs are copied to. +# +# IMPORTANT: This path serves a vital role in the *consistent* usage of CTest +# and GTest across the supported CMake project generators -- Unix Makefiles, +# Xcode and MSVCPP. The path **must** be prepended on every unit test +# executable created using the add_test and GTEST_ADD_TESTS commands. +set( TESTS_INSTALL_DIR "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}" ) + # TODO: Relocate me? set( NOM_BUILD_UNIT_TEST_UNIT ON ) set( NOM_BUILD_VISUAL_UNIT_TEST_UNIT ON ) @@ -40,31 +59,16 @@ set( NOM_BUILD_SERIALIZERS_TESTS ON ) set( NOM_BUILD_SYSTEM_TESTS ON ) set( NOM_BUILD_AUDIO_TESTS ON ) set( NOM_BUILD_GRAPHICS_TESTS ON ) +set( NOM_BUILD_ACTIONS_TESTS ON ) set( NOM_BUILD_GUI_TESTS ON ) # Tests for the testing framework set( NOM_BUILD_UNIT_TEST_TESTS ON ) set( NOM_BUILD_VTEST_TESTS ON ) -# Used only by the MSVCPP generator; this path will be expanded to include the -# configuration build type, i.e.: Debug or Release, so the full path resolve -# to '/tests/Debug' or '/tests/Release'. -set( CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/tests" ) - -# Used by all other generators (Xcode, Unix Makefiles); this path will resolve -# to '/tests/Debug' or '/tests/Release'. -set( CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}/tests/Debug" ) -set( CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/tests/Release" ) - -# This path is used for the local installation of required dependencies for -# running a unit test executable, such as DLLs when running on Windows. -# -# This path also serves a vital role in the *consistent* usage of CTest and -# GTest integration across CMake project generators -- Unix Makefiles, Xcode -# and MSVCPP all function under the hood very differently! In effect, this path -# **must** be prepended on every unit test executable name created using the -# add_test or GTEST_ADD_TESTS commands (used for integration with CTest runner). -set( TESTS_INSTALL_DIR "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${CMAKE_BUILD_TYPE}" ) +if( EXISTS "${CMAKE_CURRENT_LIST_DIR}/local_env.cmake" ) + include( "${CMAKE_CURRENT_LIST_DIR}/local_env.cmake" ) +endif() # Project headers files inclusions; our header files namespace, 'nomlib' include_directories( "${INC_ROOT_DIR}" ) @@ -79,23 +83,31 @@ find_package( SDL2 REQUIRED ) if( SDL2_FOUND ) # Add development header files; SDL.h & friends include_directories( ${SDL2_INCLUDE_DIR} ) -endif( SDL2_FOUND ) + message ( STATUS "Found SDL2 headers: ${SDL2_INCLUDE_DIR}." ) +elseif ( NOT SDL2_FOUND ) + message ( FATAL_ERROR "Missing SDL2 headers at ${SDL2_INCLUDE_DIR}." ) +endif ( SDL2_FOUND ) -# nomlib-audio external deps -if( PLATFORM_WINDOWS ) +if(NOM_BUILD_AUDIO_UNIT) + # nomlib-audio external deps find_package( OpenAL REQUIRED ) if( OPENAL_FOUND ) - # Add development header files; al.h, alc.h include_directories( ${OPENAL_INCLUDE_DIR} ) - endif( OPENAL_FOUND ) -endif( PLATFORM_WINDOWS ) + message ( STATUS "Found OpenAL headers: ${OPENAL_INCLUDE_DIR}." ) + else ( NOT OPENAL_FOUND ) + message ( FATAL_ERROR "Missing OpenAL headers at ${OPENAL_INCLUDE_DIR}." ) + endif ( OPENAL_FOUND ) +endif(NOM_BUILD_AUDIO_UNIT) # nomlib-gui external deps -find_package( libRocket REQUIRED ) +find_package( LibRocket REQUIRED ) if( LIBROCKET_FOUND ) # Add development header files include_directories( ${LIBROCKET_INCLUDE_DIRS} ) -endif( LIBROCKET_FOUND ) + message ( STATUS "Found libRocket headers: ${LIBROCKET_INCLUDE_DIRS}." ) +else ( NOT LIBROCKET_FOUND ) + message ( FATAL_ERROR "Missing libRocket headers at ${LIBROCKET_INCLUDE_DIRS}." ) +endif ( LIBROCKET_FOUND ) # third-party, headers-only dependency TCLAP set( TCLAP_INCLUDE_DIR "${NOM_THIRD_PARTY_COMMON_DIR}/tclap" ) @@ -107,26 +119,50 @@ if( NOT EXISTS ${TCLAP_INCLUDE_DIR} ) "required dependency." ) else( EXISTS ${TCLAP_INCLUDE_DIR} ) + # Header file inclusion; third-party -- TCLAP + include_directories("${NOM_THIRD_PARTY_COMMON_DIR}") message( STATUS "TCLAP headers found: ${TCLAP_INCLUDE_DIR}" ) endif( NOT EXISTS ${TCLAP_INCLUDE_DIR} ) -# Header file inclusion; third-party -- TCLAP -include_directories("${NOM_THIRD_PARTY_COMMON_DIR}") +if ( NOM_BUILD_AUDIO_TESTS ) + find_package( libsndfile REQUIRED ) +elseif ( NOT NOM_BUILD_AUDIO_TESTS ) + find_package( libsndfile OPTIONAL ) +endif ( NOM_BUILD_AUDIO_TESTS ) + +if ( NOT LIBSNDFILE_FOUND AND NOM_BUILD_AUDIO_TESTS ) + message ( FATAL_ERROR "libsndfile library not found." ) + # TODO(JEFF): Improve the fatal error message by offering suggestions on how to + # resolve or bypass the issue. +else ( LIBSNDFILE_FOUND AND NOT NOM_BUILD_AUDIO_TESTS ) + # FIXME(JEFF): Verify where and when we include the header files for libsndfile + message ( STATUS "libsndfile library found: ${LIBSNDFILE_LIBRARY}" ) +endif () # Unit test dependencies (common / global) -# -# NOTE: We prefer linking to GTest statically. if( PLATFORM_OSX ) set( GTEST_ROOT "${NOMLIB_DEPS_DIR}/osx/gtest" ) -else( PLATFORM_WINDOWS ) +elseif( PLATFORM_WINDOWS ) set( GTEST_ROOT "${NOMLIB_DEPS_DIR}/windows/gtest" ) -else( PLATFORM_LINUX ) - # TODO -endif( PLATFORM_OSX ) - -find_package( GTest REQUIRED ) - -include_directories( ${GTEST_INCLUDE_DIRS} ) +elseif( PLATFORM_LINUX ) # PLATFORM_POSIX (?) + set( GTEST_ROOT "${NOMLIB_DEPS_DIR}/linux/gtest") + set( GTEST_INCLUDE_DIR "${NOMLIB_DEPS_DIR}/linux/gtest/include" ) +endif ( PLATFORM_OSX ) + +find_package ( GTest REQUIRED ) +if ( GTEST_FOUND ) + message( STATUS "Found GoogleTest ${GTEST_ROOT}." ) + set ( GTEST_INCLUDE_DIRS ${GTEST_ROOT}/include ) + set ( GTEST_LIBRARY ${GTEST_ROOT}/lib/libgtest.a ) + message( STATUS "Using GoogleTest headers: ${GTEST_INCLUDE_DIRS}." ) + message( STATUS "Using GoogleTest library: ${GTEST_LIBRARY}." ) + include_directories("${GTEST_INCLUDE_DIRS}") +else ( NOT GTEST_FOUND ) + message( STATUS "Missing GoogleTest: ${GTEST_ROOT}." ) + message( STATUS "Missing GoogleTest headers: ${GTEST_INCLUDE_DIRS}." ) + message( STATUS "Missing GoogleTest library: ${GTEST_LIBRARY}." ) + message ( FATAL_ERROR "Failed to setup engine unit tests." ) +endif ( GTEST_FOUND ) set( NOM_TESTS_UNIT_TEST_SOURCE ${TESTS_SRC_DIR}/UnitTest/UnitTest.cpp @@ -157,33 +193,29 @@ set( NOM_TESTS_VTEST_SOURCE ) if( NOM_BUILD_UNIT_TEST_UNIT ) - - set( NOM_UNIT_TEST_LIBRARY_DEPS ${GTEST_LIBRARY} nomlib-core nomlib-file ) + set( NOM_UNIT_TEST_LIBRARY_DEPS nomlib-core nomlib-file ) if( PLATFORM_WINDOWS ) list( APPEND NOM_UNIT_TEST_LIBRARY_DEPS ${SDL2MAIN_LIBRARY} ) endif( PLATFORM_WINDOWS ) + # >> this will include GoogleTest script and calls test discovery function + # >> defined in it, so you donot have to create main function for tests, + # >> they will be auto discovered add_library( nomlib-unit-test ${LIBRARY_OUTPUT_TYPE} ${NOM_TESTS_UNIT_TEST_SOURCE} ) - - # Link nomlib-unit-test - target_link_libraries( nomlib-unit-test ${NOM_UNIT_TEST_LIBRARY_DEPS} ) - + # >> Link nomlib-unit-test to GTest + target_link_libraries(nomlib-unit-test ${GTEST_LIBRARY} ${NOM_UNIT_TEST_LIBRARY_DEPS} ) endif( NOM_BUILD_UNIT_TEST_UNIT ) if( NOM_BUILD_VISUAL_UNIT_TEST_UNIT ) - set( NOM_VISUAL_UNIT_TEST_LIBRARY_DEPS nomlib-unit-test nomlib-graphics ) - add_library( nomlib-visual-unit-test ${LIBRARY_OUTPUT_TYPE} ${NOM_TESTS_VTEST_SOURCE} ) - - # Link nomlib-visual-unit-test - target_link_libraries( nomlib-visual-unit-test ${NOM_VISUAL_UNIT_TEST_LIBRARY_DEPS} ) - + target_link_libraries(nomlib-visual-unit-test ${GTEST_LIBRARY} ${NOM_VISUAL_UNIT_TEST_LIBRARY_DEPS} ) endif( NOM_BUILD_VISUAL_UNIT_TEST_UNIT ) +# >> tests dev headers include_directories("${TESTS_INC_DIR}") if( NOM_BUILD_UNIT_TEST_TESTS ) @@ -206,6 +238,10 @@ if( NOM_BUILD_GRAPHICS_TESTS ) add_subdirectory("src/graphics") endif( NOM_BUILD_GRAPHICS_TESTS ) +if( NOM_BUILD_ACTIONS_TESTS ) + add_subdirectory("src/actions") +endif( NOM_BUILD_ACTIONS_TESTS ) + if( NOM_BUILD_GUI_TESTS ) add_subdirectory("src/gui") endif( NOM_BUILD_GUI_TESTS ) @@ -229,6 +265,17 @@ endif( NOM_BUILD_SERIALIZERS_TESTS ) # Install library dependencies into tests output directory so we can always # execute the binaries with the proper dependency versions. if ( PLATFORM_WINDOWS ) + + if( CMAKE_BUILD_TYPE STREQUAL "Debug" ) + set( GTEST_LIBRARY_REDIST_DLL "${GTEST_ROOT}/lib/gtestd.dll" ) + else() + set( GTEST_LIBRARY_REDIST_DLL "${GTEST_ROOT}/lib/gtest.dll" ) + endif() + + install( FILES ${GTEST_LIBRARY_REDIST_DLL} + DESTINATION ${TESTS_INSTALL_DIR} + RENAME "gtest.dll" ) + install ( DIRECTORY ${SDL2_REDIST_DIR} ${SDL2_IMAGE_REDIST_DIR} @@ -236,9 +283,13 @@ if ( PLATFORM_WINDOWS ) ${OPENAL_REDIST_DIR} ${LIBSNDFILE_REDIST_DIR} ${MSVCPP_REDIST_DIR} - "${GTEST_ROOT}/lib/" # GTest re-dist libs DESTINATION ${TESTS_INSTALL_DIR} FILES_MATCHING PATTERN "*.dll" ) -endif ( PLATFORM_WINDOWS ) +endif( PLATFORM_WINDOWS ) + +if( PLATFORM_LINUX ) + install_resource_dir( "${NOM_TESTS_RESOURCES_DIR}" + "${TESTS_INSTALL_DIR}/Resources") +endif ( PLATFORM_LINUX ) diff --git a/tests/include/nomlib/tests/UnitTest/UnitTest.hpp b/tests/include/nomlib/tests/UnitTest/UnitTest.hpp index e5ad5a3d..10f18a3b 100644 --- a/tests/include/nomlib/tests/UnitTest/UnitTest.hpp +++ b/tests/include/nomlib/tests/UnitTest/UnitTest.hpp @@ -31,6 +31,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +#include #include "gtest/gtest.h" // Google Test framework @@ -39,6 +40,13 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /// \brief Set or get a global state control flag. #define NOM_TEST_FLAG(var) nom::priv::test_flags.var +// Forward declarations +namespace TCLAP { + class Arg; +} // namespace TCLAP + +typedef std::vector TCLAPArgs; + namespace nom { namespace priv { @@ -110,6 +118,10 @@ extern std::string test_output_directory; /// \brief Initialize nomlib's unit testing framework. /// +/// \param add_args A std::vector containing pointers to the additional +/// arguments for TCLAP to parse. The ownership of the pointers is transferred +/// to TCLAP. +/// /// \remarks This method parses the command line for state control options /// supported by the framework. /// @@ -117,8 +129,15 @@ extern std::string test_output_directory; /// and before the use of any other feature of the nom::UnitTest framework, /// generally within the main execution function. /// +/// \note An err is thrown by TCLAP if an attempt to overwrite any existing +/// arguments, such as those defined by nom::init_test. The responsibility to +/// ensure that there are no conflicts is your burden. It is strongly +/// recommended to **not** use short forms of the arguments in order to +/// decrease the potential of collision. +/// /// \see nom::UnitTest, nom::UnitTestFlags, nom::VisualUnitTest -void init_test( int argc, char** argv ); +/// \see http://tclap.sourceforge.net/html/classTCLAP_1_1Arg.html +void init_test(int argc, char** argv, const TCLAPArgs& add_args = TCLAPArgs() ); /// \brief Base class interface for unit testing within Google Test class UnitTest: public ::testing::Test diff --git a/tests/include/nomlib/tests/VisualUnitTest.hpp b/tests/include/nomlib/tests/VisualUnitTest.hpp index ff5fc934..30324aac 100644 --- a/tests/include/nomlib/tests/VisualUnitTest.hpp +++ b/tests/include/nomlib/tests/VisualUnitTest.hpp @@ -29,12 +29,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef NOMLIB_TESTS_VISUAL_UNIT_TEST_PUBLIC_HPP #define NOMLIB_TESTS_VISUAL_UNIT_TEST_PUBLIC_HPP -#include "gtest/gtest.h" // Google Test framework - -#include "nomlib/config.hpp" -#include "nomlib/tests/UnitTest/UnitTest.hpp" #include "nomlib/tests/VisualUnitTest/VisualUnitTest.hpp" -#include "nomlib/tests/VisualUnitTest/ImageTestSet.hpp" -#include "nomlib/tests/VisualUnitTest/ImageDiff.hpp" #endif // include guard defined diff --git a/tests/include/nomlib/tests/VisualUnitTest/VisualUnitTest.hpp b/tests/include/nomlib/tests/VisualUnitTest/VisualUnitTest.hpp index 9b8c36c3..40ba3e1e 100644 --- a/tests/include/nomlib/tests/VisualUnitTest/VisualUnitTest.hpp +++ b/tests/include/nomlib/tests/VisualUnitTest/VisualUnitTest.hpp @@ -49,6 +49,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. namespace nom { +// IMPORTANT: When adding new tests that use this framework, you should always +// use the nom_add_visual_test macro. See cmake/macros.cmake for usage and +// rationale behind why. + /// \brief Base class interface for visual unit testing within Google Test class VisualUnitTest: public UnitTest { @@ -59,6 +63,10 @@ class VisualUnitTest: public UnitTest typedef std::function event_callback_type; typedef std::function update_callback_type; + + /// \todo Consider removing the callback argument; it is confusing as it is + /// invalid to use a RenderWindow object that differs from the one defined + /// in this class. typedef std::function render_callback_type; /// \brief Initialize the environment for the unit test. @@ -92,8 +100,10 @@ class VisualUnitTest: public UnitTest /// \brief Destructor. /// /// \remarks Destruction of the object flushes the configuration file for - /// the visual test set when it has images in its list. - virtual ~VisualUnitTest( void ); + /// the visual test set when it has images in its list. Also, output + /// directories that are empty -- no screen-shots taken during the test -- + /// will automatically be deleted. + virtual ~VisualUnitTest(); /// \brief Initialize the rendering subsystem. /// @@ -198,9 +208,6 @@ class VisualUnitTest: public UnitTest /// loop -- meaning the test has captured its target frames -- boolean FALSE /// when the unit test is not ready to terminate its main loop (has not /// captured all of its target frames). - /// - /// \fixme Screen-shot dumps of more than the first frame in the list are - /// broken. virtual bool on_frame_end( uint elapsed_frames ); // void save_screenshot( const std::string& file_path ); @@ -209,14 +216,65 @@ class VisualUnitTest: public UnitTest VisualUnitTest::update_callback_type default_update_callback(); VisualUnitTest::render_callback_type default_render_callback(); + /// \brief Insert an event callback at an ordered position for execution + /// in the main loop. + /// + /// \param pos The insertion position from the beginning of the container. + /// \param func The event callback function. + /// /// \returns The number of registered event callbacks (after insertion). - int append_event_callback( const std::function& func ); + /// + /// \see nom::VisualUnitTest::append_event_callback + int insert_event_callback( nom::size_type pos, + const event_callback_type& func ); + /// \brief Insert an event callback to the end of the list of callbacks + /// to be executed in the main loop. + /// + /// \param func The event callback function. + /// /// \returns The number of registered event callbacks (after insertion). - int append_update_callback( const std::function& func ); + int append_event_callback(const event_callback_type& func); - /// \returns The number of registered event callbacks (after insertion). - int append_render_callback( const std::function& func ); + /// \brief Insert an update callback at an ordered position for execution + /// in the main loop. + /// + /// \param pos The insertion position from the beginning of the container. + /// \param func The update callback function. + /// + /// \returns The number of registered update callbacks (after insertion). + /// + /// \see nom::VisualUnitTest::append_update_callback + int insert_update_callback( nom::size_type pos, + const update_callback_type& func ); + + /// \brief Insert an update callback to the end of the list of callbacks + /// to be executed in the main loop. + /// + /// \param func The update callback function. + /// + /// \returns The number of registered update callbacks (after insertion). + int append_update_callback(const update_callback_type& func); + + /// \brief Insert a rendering callback at an ordered position for execution + /// in the main loop. + /// + /// \param pos The insertion position from the beginning of the container. + /// \param func The render callback function. + /// + /// \returns The number of registered render callbacks (after insertion). + /// + /// \see nom::VisualUnitTest::append_render_callback + int insert_render_callback( nom::size_type pos, + const render_callback_type& func ); + + /// \brief Insert a rendering callback to the end of the list of callbacks + /// to be executed in the main loop. + /// + /// \param func The render callback function. + /// + /// \returns The number of registered render callbacks (after insertion). + int append_render_callback(const render_callback_type& func); /// \brief Destroy all of the registered event callbacks in storage. /// @@ -279,9 +337,8 @@ class VisualUnitTest: public UnitTest /// /// \see ::set_output_directory. /// - /// \todo Clean up the logic in this method. - void initialize_directories( void ); - // void initialize_directories( const std::string& dir_name ); + /// \todo Finish err handling for file I/O. + void initialize_directories(); /// \brief Get the timestamp string recorded for the visual unit test. static const std::string& timestamp( void ); diff --git a/tests/include/nomlib/tests/actions/ActionTest.hpp b/tests/include/nomlib/tests/actions/ActionTest.hpp new file mode 100644 index 00000000..9af7a17f --- /dev/null +++ b/tests/include/nomlib/tests/actions/ActionTest.hpp @@ -0,0 +1,254 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#ifndef NOMLIB_ACTIONS_ACTION_TEST_HPP +#define NOMLIB_ACTIONS_ACTION_TEST_HPP + +#include +#include + +// Private headers (third-party) +#include "tclap/CmdLine.h" + +// nom::VisualUnitTest framework +#include "nomlib/tests/VisualUnitTest.hpp" + +#include +#include +#include +#include +#include +#include + +/// \brief Set or get a global state control flag. +#define NOM_ACTION_TEST_FLAG(var) nom::anim_test_flags.var + +namespace nom { + +/// \brief Testing state flags container. +/// +/// \remarks See all of the available testing flags by passing --help to this +/// executable. +struct ActionTestFlags +{ + /// \brief The frame interval to update by in action tests. + /// + /// \remarks This should **not** affect test results (in particular, test + /// duration). + uint32 fps = 30; + + /// \brief Control flag for whether or not to try enable Vertical Blank + /// Synchronization (VSYNC). + /// + /// \remarks This should **not** affect test results (in particular, test + /// duration, animation smoothness, etc.). + bool enable_vsync = false; + + /// \brief The speed modifier used in action tests. + /// + /// \remarks This affects the test duration. + real32 speed = 1.0f; + + /// \brief The timing mode used in action tests. + IActionObject::timing_curve_func timing_curve = nom::Linear::ease_in_out; + std::string timing_mode_str = "linear_ease_in_out"; + + /// \brief The number of objects to spawn in a test. + /// + /// \remarks This value is currently only used in + /// ActionTest.RainingRectsStressTest. + nom::size_type num_objects = 500; + + /// \brief Enable debug logging of actions. + bool enable_action_logging = false; + + /// \brief Enable debug logging of the action queues. + bool enable_queue_logging = false; +}; + +/// \brief Testing state flags. +/// +/// \remarks These are accessible at build-time via NOM_ACTION_TEST_FLAG and +/// run-time via command line switch. +extern ActionTestFlags anim_test_flags; + +/// \brief Animation engine unit tests +class ActionTest: public nom::VisualUnitTest +{ + public: + /// \remarks This method is called at the start of each unit test. + ActionTest(); + + /// \remarks This method is called at the end of each unit test. + virtual ~ActionTest(); + + virtual bool init_rendering(); + + /// \remarks This method is called after construction, at the start of each + /// unit test. + virtual void SetUp(); + + /// \remarks This method is called before destruction, at the end of each + /// unit test. + virtual void TearDown(); + + /// \remarks This method is called at the start of each test case. + static void SetUpTestCase(); + + /// \remarks This method is called at the end of each test case. + static void TearDownTestCase(); + + /// \brief Helper method for testing properties of nom::IActionObject. + void + expected_common_params( const IActionObject* obj, float duration, + float speed, const std::string& scope_name = "" ); + + /// \brief Helper method for testing properties of nom::GroupAction. + void expected_action_params( const GroupAction* action, + nom::size_type size, + const std::string& scope_name = "" ); + + /// \brief Helper method for testing properties of nom::SequenceAction. + void expected_action_params( const SequenceAction* action, + nom::size_type size, + const std::string& scope_name = "" ); + + /// \brief Helper method for testing properties of nom::FadeInAction. + void expected_alpha_in_params( const FadeInAction* obj, uint8 alpha, + const Sprite* tex = nullptr, + const std::string& scope_name = "" ); + + /// \brief Helper method for testing properties of nom::FadeOutAction. + void expected_alpha_out_params( const FadeOutAction* obj, uint8 alpha, + const Sprite* tex = nullptr, + const std::string& scope_name = "" ); + + /// \brief Helper method for testing properties of nom::FadeAlphaByAction. + void expected_alpha_by_params( const FadeAlphaByAction* obj, uint8 alpha, + const Sprite* tex = nullptr, + const std::string& scope_name = "" ); + + /// \brief Helper method for testing properties of nom::RepeatForAction. + void expected_repeat_params( const RepeatForAction* obj, + uint32 num_repeats, + const std::string& scope_name = "" ); + + /// \brief Helper method for testing properties of + /// nom::RepeatForeverAction. + void expected_repeat_params( const RepeatForeverAction* obj, + uint32 num_repeats, + const std::string& scope_name = "" ); + + /// \brief Helper method for testing properties of + /// nom::AnimateTexturesAction. + void + expected_animate_textures_params( const AnimateTexturesAction* obj, + nom::size_type num_frames, + real32 duration, real32 speed, + const std::string& scope_name = "" ); + + /// \brief Helper method for testing properties of nom::SpriteBatchAction. + void expected_sprite_batch_action_params( const SpriteBatchAction* obj, + nom::size_type num_frames, + real32 duration, real32 speed, + const + std::string& scope_name = "" ); + + bool + expected_min_duration( real32 duration, real32 speed, + const std::string& scope_name = "" ); + + /// \brief Set the frame interval at which actions are updated. + /// + /// \param interval A value of zero disables this function. + /// + /// \remarks This should always be at the very end of the render + /// callback(s) when used. + void set_frame_interval(uint32 interval); + + void + init_animate_textures_test( const std::vector& texture_filenames, + texture_frames& anim_frames ); + + enum ActionType: uint32 + { + GROUP = 0x2, + SEQUENCE = 0x4, + ACTION = 0x8, + REPEAT_FOR = 0x100, + REPEAT_FOREVER = 0x200, + REVERSED = 0x400, + }; + + /// \params duration The maximum testing duration before termination of + /// the test occurs. The number of times the action has repeated will be + /// validated at the time of termination. + /// + /// \params num_repeats The number of times to repeat the action. + void + setup_repeating_cursor_test( real32 duration, real32 speed, real32 fps, + uint32 type, nom::size_type num_repeats, + const std::string& scope_name = "" ); + + void + append_render_queue(const Texture* drawable); + + void + append_render_queue(const Sprite* drawable); + + void + append_render_queue(const std::vector>& drawables); + + /// \remarks This should only be done once per test. + void append_frame_interval(uint32 fps); + + protected: + const Size2i WINDOW_DIMS = Size2i( this->resolution() ); + + /// \brief Test-specific and shared font resource paths. + SearchPath resources[2]; + + /// \brief Our instance of the animation player. + ActionPlayer player; + + /// \brief The stored return value of ActionPlayer::run_action. + bool run_action_ret = false; + + Timer test_timer; +}; + +bool init_cmd_line_args(int argc, char** argv); + +/// \brief Helper method for converting the timing mode string passed in from +/// command-line or NOM_ACTION_TEST_FLAG to a timing mode function. +IActionObject::timing_curve_func +timing_curve_from_str(const std::string& timing_mode); + +} // namespace nom + +#endif // include guard defined diff --git a/tests/include/nomlib/tests/gui/datagrid/Card.hpp b/tests/include/nomlib/tests/gui/datagrid/Card.hpp index ddee816a..e576f518 100644 --- a/tests/include/nomlib/tests/gui/datagrid/Card.hpp +++ b/tests/include/nomlib/tests/gui/datagrid/Card.hpp @@ -30,6 +30,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define NOMLIB_GUI_TESTS_DATAGRID_CARD_HPP #include +#include #include "nomlib/config.hpp" diff --git a/tests/include/nomlib/tests/gui/datagrid/CardCollection.hpp b/tests/include/nomlib/tests/gui/datagrid/CardCollection.hpp index 3aca7d65..b9c54797 100644 --- a/tests/include/nomlib/tests/gui/datagrid/CardCollection.hpp +++ b/tests/include/nomlib/tests/gui/datagrid/CardCollection.hpp @@ -30,6 +30,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define NOMLIB_GUI_TESTS_DATAGRID_CARD_COLLECTION_HPP #include +#include #include "nomlib/config.hpp" #include "nomlib/tests/gui/datagrid/Card.hpp" diff --git a/tests/include/nomlib/tests/gui/datagrid/CardsPageDataSource.hpp b/tests/include/nomlib/tests/gui/datagrid/CardsPageDataSource.hpp index 2826307a..9860b780 100644 --- a/tests/include/nomlib/tests/gui/datagrid/CardsPageDataSource.hpp +++ b/tests/include/nomlib/tests/gui/datagrid/CardsPageDataSource.hpp @@ -2,7 +2,7 @@ nomlib - C++11 cross-platform game engine -Copyright (c) 2013, 2014 Jeffrey Carpenter +Copyright (c) 2013, 2014, 2015, 2016 Jeffrey Carpenter All rights reserved. Redistribution and use in source and binary forms, with or without @@ -41,6 +41,8 @@ using namespace Rocket::Core; namespace nom { +typedef std::vector StringList; + /// \brief Mock model of the CardsMenu player's hand selection widget in TTcards /// /// \see http://librocket.com/wiki/documentation/C%2B%2BManual/Controls/DataGrid diff --git a/tests/src/UnitTest/UnitTest.cpp b/tests/src/UnitTest/UnitTest.cpp index e1f4f89d..17bb2c01 100644 --- a/tests/src/UnitTest/UnitTest.cpp +++ b/tests/src/UnitTest/UnitTest.cpp @@ -48,7 +48,7 @@ UnitTestFlags test_flags = UnitTestFlags(); // Static initializations std::string test_output_directory = "\0"; -void init_test( int argc, char** argv ) +void init_test(int argc, char** argv, const std::vector& add_args) { Path p; File fp; @@ -170,6 +170,15 @@ void init_test( int argc, char** argv ) NOM_TEST_FLAG(force_overwrite) ); + // Append additional arguments; conflicts will result in an err being + // thrown + for( auto itr = add_args.begin(); itr != add_args.end(); ++itr ) { + NOM_ASSERT( *itr != nullptr ); + if( *itr != nullptr ) { + cmd.add(*itr); + } + } + cmd.parse( argc, argv ); if( interactive.getValue() == true ) diff --git a/tests/src/VisualUnitTest/CMakeLists.txt b/tests/src/VisualUnitTest/CMakeLists.txt index 7bd4ee73..8b492bbc 100644 --- a/tests/src/VisualUnitTest/CMakeLists.txt +++ b/tests/src/VisualUnitTest/CMakeLists.txt @@ -2,22 +2,16 @@ add_executable( VisualUnitTestFrameworkTest "VisualUnitTestFrameworkTest.cpp" ) -target_link_libraries( VisualUnitTestFrameworkTest - nomlib-visual-unit-test ) +target_link_libraries(VisualUnitTestFrameworkTest nomlib-visual-unit-test + nomlib-graphics) -add_test( # test name - VisualUnitTestFrameworkTestGenRefImages, - # executable name - ${TESTS_INSTALL_DIR}/VisualUnitTestFrameworkTest - # args - "-r" ) +nom_add_visual_test( VisualUnitTestFrameworkTestGenRefImages + ${TESTS_INSTALL_DIR}/VisualUnitTestFrameworkTest + "-r" ) -add_test( # test name - VisualUnitTestFrameworkTestCompareImages, - # executable name - ${TESTS_INSTALL_DIR}/VisualUnitTestFrameworkTest - # args - "" ) +nom_add_visual_test( VisualUnitTestFrameworkTestCompareImages + ${TESTS_INSTALL_DIR}/VisualUnitTestFrameworkTest + "" ) add_executable( VisualUnitTestFrameworkNonDefaultCtorTest "VisualUnitTestFrameworkNonDefaultCtorTest.cpp" ) @@ -25,19 +19,13 @@ add_executable( VisualUnitTestFrameworkNonDefaultCtorTest target_link_libraries( VisualUnitTestFrameworkNonDefaultCtorTest nomlib-visual-unit-test ) -add_test( # test name - VisualUnitTestFrameworkNonDefaultCtorTestGenRefImages, - # executable name - ${TESTS_INSTALL_DIR}/VisualUnitTestFrameworkNonDefaultCtorTest - # args - "-r" ) +nom_add_visual_test( VisualUnitTestFrameworkNonDefaultCtorTestGenRefImages + ${TESTS_INSTALL_DIR}/VisualUnitTestFrameworkNonDefaultCtorTest + "-r" ) -add_test( # test name - VisualUnitTestFrameworkNonDefaultCtorTestCompareImages, - # executable name - ${TESTS_INSTALL_DIR}/VisualUnitTestFrameworkNonDefaultCtorTest - # args - "" ) +nom_add_visual_test( VisualUnitTestFrameworkNonDefaultCtorTestCompareImages + ${TESTS_INSTALL_DIR}/VisualUnitTestFrameworkNonDefaultCtorTest + "" ) # FIXME # add_executable( ImageTestSetTest "ImageTestSetTest.cpp" ) diff --git a/tests/src/VisualUnitTest/ImageTestSet.cpp b/tests/src/VisualUnitTest/ImageTestSet.cpp index affee4f7..40f7826c 100644 --- a/tests/src/VisualUnitTest/ImageTestSet.cpp +++ b/tests/src/VisualUnitTest/ImageTestSet.cpp @@ -44,7 +44,7 @@ ImageTestSet::ImageTestSet( void ) { // NOM_LOG_TRACE( NOM ); - this->set_version( nom::revision() ); + this->set_version( NOM_VERSION.version_string() + "-" + nom::revision() ); // Should not be used; see documentation notes for why // this->set_timestamp( nom::timestamp() ); diff --git a/tests/src/VisualUnitTest/VisualUnitTest.cpp b/tests/src/VisualUnitTest/VisualUnitTest.cpp index 4f216a67..667ad235 100644 --- a/tests/src/VisualUnitTest/VisualUnitTest.cpp +++ b/tests/src/VisualUnitTest/VisualUnitTest.cpp @@ -32,6 +32,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "nomlib/system/init.hpp" #include "nomlib/system/Path.hpp" #include "nomlib/system/File.hpp" +#include "nomlib/system/InputMapper/InputAction.hpp" #include "nomlib/serializers/IValueSerializer.hpp" #include "nomlib/serializers/IValueDeserializer.hpp" #include "nomlib/serializers/JsonCppSerializer.hpp" @@ -59,8 +60,7 @@ void VisualUnitTest::initialize( const Size2i& res ) // Use the same timestamp string across multiple object instances to prevent // multiple output directories for screen-shots intended for the same test // set. - if( VisualUnitTest::timestamp_initialized_ == false ) - { + if( VisualUnitTest::timestamp_initialized_ == false ) { // Use underscore char instead of a space char (filename friendly) VisualUnitTest::timestamp_ = nom::file_timestamp(); @@ -164,19 +164,17 @@ void VisualUnitTest::SetUp( void ) // Register default input bindings InputActionMapper state; - EventCallback ev_quit( [&] ( const Event& evt ) { this->quit(); } ); + auto ev_quit = ( [&](const Event& evt) { + this->quit(); + }); - EventCallback ev_screenshot( [&] ( const Event& evt ) - { - this->render_window().save_screenshot( this->test_name() + ".png" ); - } - ); + auto ev_screenshot = ( [&](const Event& evt) { + this->render_window().save_screenshot( this->test_name() + ".png" ); + }); - EventCallback ev_fullscreen( [&] ( const Event& evt ) - { - this->render_window().toggle_fullscreen(); - } - ); + auto ev_fullscreen = ( [&](const Event& evt) { + this->render_window().toggle_fullscreen(); + }); // A convenient key binding for *quickly* terminating all unit tests and test // cases within the executable immediately. @@ -188,59 +186,33 @@ void VisualUnitTest::SetUp( void ) // // NOTE: This key binding (along with any other registered binding) is not // available during non-interactive test executions. - EventCallback term_tests( [&] ( const Event& evt ) - { - std::exit( this->failed_test_count() ); - } - ); - - state.insert ( - "ev_quit", - nom::KeyboardAction( SDL_KEYDOWN, SDLK_ESCAPE ), - ev_quit - ); - - state.insert ( - "ev_quit", - nom::KeyboardAction( SDL_KEYDOWN, SDLK_q ), - ev_quit - ); - - state.insert ( - "ev_screenshot", - nom::KeyboardAction( SDL_KEYDOWN, SDLK_F1 ), - ev_screenshot - ); - - state.insert ( - "ev_fullscreen", - nom::KeyboardAction( SDL_KEYDOWN, SDLK_f ), - ev_fullscreen - ); - - state.insert ( - "term_tests", - - // Try to use native platform key modifiers - #if defined( NOM_PLATFORM_OSX ) // Use CMD key modifier - nom::KeyboardAction( SDL_KEYDOWN, SDLK_ESCAPE, KMOD_LGUI ), - #else - // Use SHIFT key modifier; KMOD_GUI is probably going - // to be the Windows logo key... - // - // FIXME: Find a better key modifier to use here...? - // On Win7, ALT+ESCAPE switches windows and CTRL+ESCAPE - // brings up the Start menu. - nom::KeyboardAction( SDL_KEYDOWN, SDLK_BACKQUOTE, KMOD_LCTRL ), - #endif // defined NOM_PLATFORM_OSX - - term_tests - ); + auto term_tests = ( [&](const Event& evt) { + std::exit( this->failed_test_count() ); + }); + + state.insert("ev_quit", nom::KeyboardAction(SDLK_ESCAPE), ev_quit); + + state.insert("ev_quit", nom::KeyboardAction(SDLK_q), ev_quit); + + state.insert("ev_screenshot", nom::KeyboardAction(SDLK_F1), ev_screenshot); + + state.insert("ev_fullscreen", nom::KeyboardAction(SDLK_f), ev_fullscreen); + +// TODO: Find a better key modifier to use here that fits both operating +// systems?? On Windows 7, ALT+ESCAPE switches windows and CTRL+ESCAPE +// both brings up the Start menu... +#if defined(NOM_PLATFORM_OSX) + state.insert( "term_tests", nom::KeyboardAction(SDLK_ESCAPE, KMOD_LGUI), + term_tests ); +#else + state.insert( "term_tests", nom::KeyboardAction(SDLK_BACKQUOTE, KMOD_LCTRL), + term_tests ); +#endif this->input_mapper_.insert( this->test_set(), state, true ); - // Register the default callbacks to be used within the game loop - this->append_event_callback( [&] ( Event ev ) { this->input_mapper_.on_event( ev ); } ); + this->input_mapper_.set_event_handler(this->evt_); + this->append_update_callback( this->default_update_callback() ); // Default background color to use for the render window @@ -317,17 +289,48 @@ ::testing::AssertionResult VisualUnitTest::compare( void ) << result_msg.str(); } -VisualUnitTest::~VisualUnitTest( void ) +VisualUnitTest::~VisualUnitTest() { // NOM_LOG_TRACE( NOM ); - IValueSerializer* fp = new JsonCppSerializer(); + Path p; + File file; + std::unique_ptr fp; + + if( NOM_TEST_FLAG(disable_comparison) == true ) { + // Nothing to clean up; no files or directories should have ever been + // created! + return; + } + + std::string output_dir = + this->output_directory(); + + fp = nom::make_unique_json_serializer(); // Save info file only if we have one or more images successfully captured - if( fp != nullptr && VisualUnitTest::visual_test_.images().size() > 0 ) - { - VisualUnitTest::visual_test_.save_file( fp ); + if( fp != nullptr && VisualUnitTest::visual_test_.images().size() > 0 ) { + VisualUnitTest::visual_test_.save_file( fp.get() ); + } + + // Handle cleaning up of empty directories at the end of each test + if( VisualUnitTest::visual_test_.images().empty() == true ) { + + NOM_LOG_DEBUG( NOM_LOG_CATEGORY_APPLICATION, "output_dir:", output_dir ); + NOM_LOG_DEBUG( NOM_LOG_CATEGORY_APPLICATION, "num_files:", + file.num_files( this->output_directory() ) ); + + if( file.exists(output_dir) == true ) { + + // NOTE: I don't **think** that there should ever be a case where there + // is an empty directory minus an info.json file, but bugs have had their + // way of sneaking in here before, so -- this hopefully this will catch + // any nasty surprises! + NOM_ASSERT(file.exists(output_dir + p.native() + "info.json") == false); + file.rmdir(output_dir); + } } + } int VisualUnitTest::on_run( void ) @@ -348,46 +351,27 @@ int VisualUnitTest::on_run( void ) NOM_LOG_ERR( NOM, "Did you forget to append screen-shot frames to the unit test before calling ::on_run?" ); } - Event ev; - while( this->app_state() == true ) - { - while( this->evt_.poll_event( ev ) ) - { - // This plumbs in proper quit functionality for when the end-user requests - // program termination through the window manager's close button; this - // includes Windows ALT+F4 signal. - // - // Perhaps the event handling system needs a bit of rethinking... - if( ev.type == SDL_WINDOWEVENT && ev.window.event == SDL_WINDOWEVENT_CLOSE ) + while( this->app_state() == true ) { + + Event ev; + while( this->evt_.poll_event(ev) == true ) { + + if( ev.type == Event::WINDOW_EVENT && + ev.window.event == WindowEvent::CLOSE ) { this->set_app_state(false); + } else if( ev.type == Event::QUIT_EVENT ) { + this->set_app_state(false); } - for( auto itr = this->event_callbacks_.begin(); itr != this->event_callbacks_.end(); ++itr ) + for( auto itr = this->event_callbacks_.begin(); + itr != this->event_callbacks_.end(); + ++itr ) { - itr->operator()( ev ); + itr->operator()(ev); } } - this->fps_counter_.update(); - - // Refresh the FPS display at one (1) second intervals - if( this->fps_counter_update_.ticks() > 1000 ) - { - if( this->fps() == true ) - { - // Show test title plus FPS counter - this->render_window().set_window_title( title + " - " + this->fps_counter_.asString() + ' ' + "fps" ); - } - else - { - // Restore window title to defaults - this->render_window().set_window_title( title ); - } - - this->fps_counter_update_.restart(); - } // end FPS refresh cycle - for( auto itr = this->update_callbacks_.begin(); itr != this->update_callbacks_.end(); ++itr ) { itr->operator()( 0 ); @@ -407,6 +391,24 @@ int VisualUnitTest::on_run( void ) } } + this->fps_counter_.update(); + // Refresh the FPS display at one (1) second intervals + if( this->fps_counter_update_.ticks() > 1000 ) { + if( this->fps() == true ) { + std::stringstream fps_str; + fps_str << title << " - " << this->fps_counter_.asString() + << ' ' << "fps"; + + // Show test title plus FPS counter + this->render_window().set_window_title(fps_str.str()); + } else { + // Restore window title to defaults + this->render_window().set_window_title(title); + } + + this->fps_counter_update_.restart(); + } // end FPS refresh cycle + ++elapsed_frames; } // end while loop @@ -560,23 +562,59 @@ VisualUnitTest::default_render_callback() }); } -int VisualUnitTest::append_event_callback( const std::function& func ) +int +VisualUnitTest::insert_event_callback( nom::size_type pos, + const event_callback_type& func ) +{ + NOM_ASSERT( pos <= this->event_callbacks_.size() ); + + this->event_callbacks_.insert( (this->event_callbacks_.begin()+pos), func); + + return this->event_callbacks_.size(); +} + +int +VisualUnitTest::append_event_callback(const event_callback_type& func) { - this->event_callbacks_.push_back( func ); + this->insert_event_callback(this->event_callbacks_.size(), func); return this->event_callbacks_.size(); } -int VisualUnitTest::append_update_callback( const std::function& func ) +int +VisualUnitTest::insert_update_callback( nom::size_type pos, + const update_callback_type& func ) { - this->update_callbacks_.push_back( func ); + NOM_ASSERT( pos <= this->update_callbacks_.size() ); + + this->update_callbacks_.insert( (this->update_callbacks_.begin()+pos), func); + + return this->update_callbacks_.size(); +} + +int +VisualUnitTest::append_update_callback(const update_callback_type& func) +{ + this->insert_update_callback(this->update_callbacks_.size(), func); return this->update_callbacks_.size(); } -int VisualUnitTest::append_render_callback( const std::function& func ) +int +VisualUnitTest::insert_render_callback( nom::size_type pos, + const render_callback_type& func ) { - this->render_callbacks_.push_back( func ); + NOM_ASSERT( pos <= this->render_callbacks_.size() ); + + this->render_callbacks_.insert( (this->render_callbacks_.begin()+pos), func); + + return this->render_callbacks_.size(); +} + +int +VisualUnitTest::append_render_callback(const render_callback_type& func) +{ + this->insert_render_callback(this->render_callbacks_.size(), func); return this->render_callbacks_.size(); } @@ -603,41 +641,37 @@ const std::string& VisualUnitTest::timestamp( void ) return VisualUnitTest::timestamp_; } -void VisualUnitTest::initialize_directories( void ) +void VisualUnitTest::initialize_directories() { Path p; File fp; - if( NOM_TEST_FLAG( disable_comparison ) == true ) - { - // NOM_LOG_INFO( NOM, "Image set comparison has been explicitly disabled." ); - return; - } + if( NOM_TEST_FLAG(reference_screenshot) == true ) { + std::string test_dir = + nom::test_output_directory + p.native() + this->test_set(); - if( NOM_TEST_FLAG(reference_screenshot) == true ) - { - std::string test_dir = nom::test_output_directory + p.native() + this->test_set(); std::string ref_dir = test_dir + p.native() + "Reference"; - if( fp.recursive_mkdir( ref_dir ) == false ) - { + if( fp.recursive_mkdir(ref_dir) == false ) { // NOM_LOG_ERR( NOM, "Could not create directory for reference ", this->test_set(), " at: ", ref_dir ); // exit( NOM_EXIT_FAILURE ); } - this->set_output_directory( ref_dir ); - } - else - { - std::string test_name_dir = nom::test_output_directory + p.native() + this->test_set() + p.native() + this->test_set() + "_" + VisualUnitTest::timestamp(); + this->set_output_directory(ref_dir); + } else if( NOM_TEST_FLAG(disable_comparison) == false ) { + std::string test_name_dir = + nom::test_output_directory + p.native() + this->test_set() + + p.native() + this->test_set() + "_" + VisualUnitTest::timestamp(); - if( fp.recursive_mkdir( test_name_dir ) == false ) - { + if( fp.recursive_mkdir(test_name_dir) == false ) { // NOM_LOG_ERR( NOM, "Could not create directory for test name ", this->test_name(), " at: ", test_name_dir ); // exit( NOM_EXIT_FAILURE ); } - this->set_output_directory( test_name_dir ); + // NOTE: This directory is "garbage collected" on class destruction when + // there no screen-shots have been dumped. The clean up handling depends + // on this output directory being available. + this->set_output_directory(test_name_dir); } } diff --git a/tests/src/VisualUnitTest/VisualUnitTestFrameworkNonDefaultCtorTest.cpp b/tests/src/VisualUnitTest/VisualUnitTestFrameworkNonDefaultCtorTest.cpp index 4083f5ec..61b1f1f2 100644 --- a/tests/src/VisualUnitTest/VisualUnitTestFrameworkNonDefaultCtorTest.cpp +++ b/tests/src/VisualUnitTest/VisualUnitTestFrameworkNonDefaultCtorTest.cpp @@ -70,7 +70,6 @@ class VisualUnitTestFrameworkNonDefaultCtorTest: public VisualUnitTest // Always use the same backdrop color on each unit test NOM_TEST_ADD_RENDER( const RenderWindow& win, this->render_window().fill( Color4i::SkyBlue ) ); - // FIXME: // Take a snapshot on the first and 50th rendered frames this->append_screenshot_frame( 0 ); this->append_screenshot_frame( 50 ); diff --git a/tests/src/actions/ActionTest.cpp b/tests/src/actions/ActionTest.cpp new file mode 100644 index 00000000..e3b130ae --- /dev/null +++ b/tests/src/actions/ActionTest.cpp @@ -0,0 +1,1335 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#include "nomlib/tests/actions/ActionTest.hpp" + +namespace nom { + +// extern initialization +ActionTestFlags anim_test_flags = {}; + +ActionTest::ActionTest() +{ + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE_UNIT_TEST, NOM_LOG_PRIORITY_VERBOSE); + + // ActionTest reports various test statistics at this log level + nom::SDL2Logger::set_logging_priority( NOM_LOG_CATEGORY_ACTION, + nom::NOM_LOG_PRIORITY_INFO ); + + // Enable debug diagnostics for action objects + if( NOM_ACTION_TEST_FLAG(enable_action_logging) == true ) { + nom::SDL2Logger::set_logging_priority(NOM_LOG_CATEGORY_ACTION, nom::NOM_LOG_PRIORITY_DEBUG); + } + // nom::SDL2Logger::set_logging_priority(NOM_LOG_CATEGORY_ACTION, nom::NOM_LOG_PRIORITY_VERBOSE); + + // Enable debug diagnostics for action engine && internal queue + if( NOM_ACTION_TEST_FLAG(enable_queue_logging) == true ) { + nom::SDL2Logger::set_logging_priority(NOM_LOG_CATEGORY_ACTION_QUEUE, nom::NOM_LOG_PRIORITY_DEBUG); + nom::SDL2Logger::set_logging_priority(NOM_LOG_CATEGORY_ACTION_PLAYER, nom::NOM_LOG_PRIORITY_DEBUG); + } + + // Enable debug call stack diagnostics + // nom::SDL2Logger::set_logging_priority(NOM_LOG_CATEGORY_TRACE_ACTION, NOM_LOG_PRIORITY_VERBOSE); + // nom::SDL2Logger::set_logging_priority(NOM_LOG_CATEGORY_TRACE_RENDER, NOM_LOG_PRIORITY_VERBOSE); + // nom::SDL2Logger::set_logging_priority(NOM_LOG_CATEGORY_TRACE_UNIT_TEST, NOM_LOG_PRIORITY_VERBOSE); + + if( NOM_ACTION_TEST_FLAG(enable_vsync) == true ) { + nom::set_hint(SDL_HINT_RENDER_VSYNC, "1"); + } else { + nom::set_hint(SDL_HINT_RENDER_VSYNC, "0"); + } + + // I get a subtle animation flicker when toggled full screen when using + // nearest scale quality + if( nom::set_hint(SDL_HINT_RENDER_SCALE_QUALITY, "linear") == false ) { + NOM_LOG_INFO( NOM_LOG_CATEGORY_APPLICATION, + "Could not set scale quality to linear;", + "animation flicker may result!" ); + } +} + +ActionTest::~ActionTest() +{ + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE_UNIT_TEST, NOM_LOG_PRIORITY_VERBOSE); + + NOM_LOG_INFO( NOM_LOG_CATEGORY_ACTION, + "Number of actions remaining in queue at the time of exit:", + this->player.num_actions() ); + + NOM_LOG_INFO( NOM_LOG_CATEGORY_ACTION, "test completion time (seconds):", + Timer::to_seconds( this->test_timer.ticks() ) ); + + this->test_timer.stop(); +} + +bool ActionTest::init_rendering() +{ + uint32 window_flags = SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE; + + // Initialize rendering window (and its GL context) + if( this->window_.create( this->test_set(), Point2i(0,0), 0, Size2i(1024,768), + window_flags ) == false ) + { + return false; + } + + + + // Allow for automatic rescaling of the output window based on aspect + // ratio (i.e.: handle full-screen resizing); this will use letter-boxing + // when the aspect ratio is greater than what is available, or side-bars + // when the aspect ratio is less than. + this->render_window().set_logical_size( this->resolution() ); + + // Use no pixel unit scaling; this gives us one to one pixel ratio + this->render_window().set_scale( nom::Point2f(1,1) ); + + // Try to set a sensible (optimal) refresh rate based on the display + // capabilities when VSYNC is enabled. + if( NOM_ACTION_TEST_FLAG(enable_vsync) == true ) { + auto display_refresh_rate = + this->render_window().refresh_rate(); + if( display_refresh_rate > 0 ) { + // Disable frame rate governor; the effective frame rate will be + // determined by updating the display at the display's refresh rate + NOM_ACTION_TEST_FLAG(fps) = 0; + } else { + // ...fall back to using the initialized value of the FPS test flag + NOM_ASSERT( NOM_ACTION_TEST_FLAG(fps) >= 0); + } + } + + return true; +} + +void ActionTest::SetUp() +{ + // NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE_UNIT_TEST, NOM_LOG_PRIORITY_VERBOSE); + + std::string res_file = nom::UnitTest::test_set() + ".json"; + + if( resources[0].load_file( res_file, "resources" ) == false ) { + FAIL() + << "Could not resolve the 'resources' path from input file: " << res_file; + } + + if( resources[1].load_file( res_file, "fonts" ) == false ) { + FAIL() + << "Could not resolve the 'fonts' path from input file: " << res_file; + } + + // VisualUnitTest environment init + VisualUnitTest::SetUp(); + + // Interactive toggling of the animation player states; pausing, stopping + // and clearing of the animation loop + this->append_event_callback( [=](const Event ev) { + uint32 pstate = this->player.player_state(); + switch(ev.type) { + default: break; + + case Event::KEY_PRESS: + { + switch(ev.key.sym) { + default: break; + + case SDLK_p: + { + if( pstate != ActionPlayer::State::PAUSED ) { + this->player.pause(); + } else if( pstate == ActionPlayer::State::PAUSED ) { + this->player.resume(); + } + } break; + + case SDLK_SPACE: + { + if( pstate != ActionPlayer::State::STOPPED ) { + this->player.stop(); + } else if( pstate == ActionPlayer::State::STOPPED ) { + this->player.resume(); + } + } break; + + case SDLK_c: + { + this->player.cancel_actions(); + this->clear_render_callbacks(); + } break; + + } break; // end switch ev.key.sym + } break; // end Event::KEY_PRESS case + + case Event::WINDOW_EVENT: + { + switch(ev.window.event) { + default: break; + + // This event will always be posted first + case WindowEvent::RESIZED: + { + this->player.pause(); + } break; + + case WindowEvent::SIZE_CHANGED: + { + this->player.resume(); + } break; + } break; // end switch ev.window.event + } break; // end Event::WINDOW_EVENT case + + } // end switch ev.type + }); + + Timer anim_timer; + anim_timer.start(); + uint32 last_delta = anim_timer.ticks(); + this->append_update_callback( [&, anim_timer, last_delta](float) mutable { + + uint32 end_delta = anim_timer.ticks(); + uint32 elapsed_delta = end_delta - last_delta; + last_delta = end_delta; + + // NOM_DUMP(elapsed_delta); + this->player.update(elapsed_delta); + }); + + this->test_timer.start(); +} + +void ActionTest::TearDown() +{ + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE_UNIT_TEST, nom::NOM_LOG_PRIORITY_VERBOSE); +} + +void ActionTest::SetUpTestCase() +{ + // NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE_UNIT_TEST, NOM_LOG_PRIORITY_VERBOSE); +} + +void ActionTest::TearDownTestCase() +{ + NOM_LOG_TRACE_PRIO(NOM_LOG_CATEGORY_TRACE_UNIT_TEST, NOM_LOG_PRIORITY_VERBOSE); +} + +void +ActionTest::expected_common_params( const IActionObject* obj, + float duration, float speed, + const std::string& scope_name ) +{ + ASSERT_TRUE(obj != nullptr) + << "expected_common_params: " << scope_name << "\n"; + + EXPECT_EQ(speed, obj->speed() ) + << "expected_common_params: " << scope_name << "\n"; + + EXPECT_EQ(duration, obj->duration() ) + << "expected_common_params scoped_name: " << scope_name << "\n"; +} + +void ActionTest::expected_action_params( const GroupAction* action, + nom::size_type size, + const std::string& scope_name ) +{ + ASSERT_TRUE(action != nullptr) + << "expected_action_params scoped_name: " << scope_name << "\n"; + + EXPECT_EQ(size, action->num_actions_) + << "expected_action_params scoped_name: " << scope_name << "\n"; +} + +void ActionTest::expected_action_params( const SequenceAction* action, + nom::size_type size, + const std::string& scope_name ) +{ + ASSERT_TRUE(action != nullptr) + << "expected_action_params scoped_name: " << scope_name << "\n"; + + EXPECT_EQ(size, action->num_actions_) + << "expected_action_params scoped_name: " << scope_name << "\n"; +} + +void +ActionTest::expected_alpha_in_params( const FadeInAction* obj, + uint8 alpha, const Sprite* tex, + const std::string& scope_name ) +{ + ASSERT_TRUE(obj != nullptr) + << "expected_alpha_in_params scoped_name: " << scope_name << "\n"; + + if( obj != nullptr ) { + EXPECT_EQ(alpha, obj->alpha_) + << "expected_alpha_in_params scoped_name: " << scope_name << "\n"; + } + + if( tex != nullptr ) { + EXPECT_EQ(alpha, tex->alpha() ) + << "scoped_name: " << scope_name << "\n"; + } +} + +void +ActionTest::expected_alpha_out_params( const FadeOutAction* obj, + uint8 alpha, const Sprite* tex, + const std::string& scope_name ) +{ + ASSERT_TRUE(obj != nullptr) + << "expected_alpha_out_params scoped_name: " << scope_name << "\n"; + + if( obj != nullptr ) { + EXPECT_EQ(alpha, obj->alpha_) + << "expected_alpha_out_params scoped_name: " << scope_name << "\n"; + } + + if( tex != nullptr ) { + EXPECT_EQ(alpha, tex->alpha() ) + << "expected_alpha_out_params scoped_name: " << scope_name << "\n"; + } +} + +void +ActionTest::expected_alpha_by_params( const FadeAlphaByAction* obj, + uint8 alpha, const Sprite* tex, + const std::string& scope_name ) +{ + ASSERT_TRUE(obj != nullptr) + << "expected_alpha_by_params scoped_name: " << scope_name << "\n"; + + if( obj != nullptr ) { + EXPECT_EQ(alpha, obj->alpha_) + << "expected_alpha_by_params scoped_name: " << scope_name << "\n"; + } + + if( tex != nullptr ) { + EXPECT_EQ(alpha, tex->alpha() ) + << "expected_alpha_by_params scoped_name: " << scope_name << "\n"; + } +} + +void ActionTest::expected_repeat_params( const RepeatForAction* obj, + uint32 num_repeats, + const std::string& scope_name ) +{ + ASSERT_TRUE(obj != nullptr) + << "expected_repeat_params scoped_name: " << scope_name << "\n"; + + EXPECT_EQ(num_repeats, obj->num_repeats_) + << "expected_repeat_params scoped_name: " << scope_name << "\n"; + + EXPECT_EQ(num_repeats, obj->elapsed_repeats_) + << "expected_repeat_params scoped_name: " << scope_name << "\n"; +} + +void ActionTest::expected_repeat_params( const RepeatForeverAction* obj, + uint32 num_repeats, + const std::string& scope_name ) +{ + ASSERT_TRUE(obj != nullptr) + << "expected_repeat_params scoped_name: " << scope_name << "\n"; + + EXPECT_EQ(num_repeats, obj->elapsed_repeats_) + << "expected_repeat_params scoped_name: " << scope_name << "\n"; +} + +void ActionTest:: +expected_animate_textures_params( const AnimateTexturesAction* obj, + nom::size_type num_frames, + real32 duration, real32 speed, + const std::string& scope_name ) +{ + ASSERT_TRUE(obj != nullptr) + << "expected_animate_textures_params scoped_name: " + << scope_name << "\n"; + + EXPECT_EQ(num_frames, obj->frames_.size() ) + << "expected_animate_textures_params scoped_name: " + << scope_name << "\n"; + + EXPECT_EQ(duration, obj->duration() ) + << "expected_animate_textures_params scoped_name: " + << scope_name << "\n"; + + EXPECT_EQ(speed, obj->speed() ) + << "expected_animate_textures_params scoped_name: " + << scope_name << "\n"; +} + +void +ActionTest::expected_sprite_batch_action_params( const SpriteBatchAction* obj, + nom::size_type num_frames, + real32 duration, + real32 speed, + const + std::string& scope_name ) +{ + ASSERT_TRUE(obj != nullptr) + << "expected_sprite_batch_action_params scoped_name: " << scope_name << "\n"; + + EXPECT_EQ(num_frames, obj->drawable_->frames() ) + << "expected_sprite_batch_action_params scoped_name: " << scope_name << "\n"; + + EXPECT_EQ(duration, obj->duration() ) + << "expected_sprite_batch_action_params scoped_name: " << scope_name << "\n"; + + EXPECT_EQ(speed, obj->speed() ) + << "expected_sprite_batch_action_params scoped_name: " << scope_name << "\n"; +} + +bool +ActionTest::expected_min_duration( real32 duration, real32 speed, + const std::string& scope_name ) +{ + // NOTE: We must additionally check for the state of the action queue + // because our action will not yet be running on the first time around on + // the main loop's update iteration. + // EXPECT_EQ( ActionPlayer::IDLE, this->player.player_state() ); + if( this->player.idle() == true ) { + uint32 action_done_time = this->test_timer.ticks(); + EXPECT_GE(action_done_time, ( Timer::to_milliseconds(duration) ) / speed) + << "expected_min_duration scoped_name: " << scope_name << "\n"; + EXPECT_EQ(0, this->player.num_actions() ) + << "expected_min_duration scoped_name: " << scope_name << "\n"; + + return true; + } else { + return false; + } +} + +// NOTE: This implementation derives from [Handmade Hero](https://www.handmadehero.org/)'s +// "Enforcing a Video Frame Rate" (Week 4). It is currently assumed that +// "granular sleep" AKA high-resolution timing is properly supported by the +// platform -- this might come back to bite us in the ass someday! +void ActionTest::set_frame_interval(uint32 interval) +{ + real32 target_seconds_per_frame = + 1.0f / (real32)interval; + uint64 last_delta = 0; + HighResolutionTimer anim_timer; + + // Abort our frame capping logic when explicitly requested + if( interval == 0 ) { + return; + } + + anim_timer.start(); + + last_delta = anim_timer.ticks(); + + real32 elapsed_delta = + HighResolutionTimer::elapsed_ticks(last_delta, anim_timer.ticks() ); + + if(elapsed_delta < target_seconds_per_frame) { + + uint32 sleep_ms = + (uint32)(1000.0f * (target_seconds_per_frame - elapsed_delta) ); + if(sleep_ms > 0) { + nom::sleep(sleep_ms); + } + } +} + +void +ActionTest::init_animate_textures_test( const std::vector& + texture_filenames, texture_frames& + anim_frames ) +{ + for( auto itr = texture_filenames.begin(); + itr != texture_filenames.end(); + ++itr ) + { + ASSERT_TRUE(*itr != nullptr); + + auto tex = std::make_shared(); + ASSERT_TRUE(tex != nullptr); + + bool ret = tex->load(this->resources[0].path() + *itr ); + if( ret == false ) { + FAIL() << "Could not load texture" << *itr << "from file path: " + << this->resources[0].path() << "\n"; + } + + nom::set_alignment( tex.get(), Point2i::zero, this->WINDOW_DIMS, + Anchor::MiddleCenter ); + + anim_frames.emplace_back(tex); + } // end for tex filenames loop +} + +void ActionTest::append_render_queue(const Texture* drawable) +{ + this->append_render_callback( [=](const RenderWindow&) { + + if( drawable != nullptr && drawable->valid() == true ) { + drawable->draw( this->render_window() ); + } + }); +} + +void ActionTest::append_render_queue(const Sprite* drawable) +{ + this->append_render_callback( [=](const RenderWindow&) { + + if( drawable != nullptr && drawable->valid() == true ) { + drawable->draw( this->render_window() ); + } + }); +} + +void ActionTest:: +append_render_queue(const std::vector>& drawables) +{ + this->append_render_callback( [=](const RenderWindow&) { + for( auto itr = drawables.begin(); itr != drawables.end(); ++itr ) { + auto sp = (*itr).get(); + if( sp != nullptr && sp->valid() == true ) { + sp->draw( this->render_window() ); + } + } + }); +} + +void ActionTest::append_frame_interval(uint32 fps) +{ + this->append_render_callback( [=](const RenderWindow&) { + this->set_frame_interval(fps); + }); +} + +bool init_cmd_line_args(int argc, char** argv) +{ + // Append additional command line arguments for this test case + TCLAPArgs args; + + TCLAP::ValueArg speed_modifier_arg( + // Option short form (not supported) + "", + // Option long form + "speed", + // Option description + "The speed modifier used by each test", + // Not required + false, + // Option default + NOM_ACTION_TEST_FLAG(speed), + // Option example (part of description) + "32-bit floating-point number" + ); + + std::stringstream timing_mode_arg_desc; + timing_mode_arg_desc << "One of the easing algorithms: linear (default), " + << "quad_ease_in, quad_ease_out, quad_ease_in_out, " + << "cubic_ease_in, cubic_ease_out, cubic_ease_in_out, " + << "quart_ease_in, quart_ease_out, quart_ease_in_out, " + << "quint_ease_in, quint_ease_out, quint_ease_in_out, " + << "back_ease_in, back_ease_out, back_ease_in_out, " + << "bounce_ease_in, bounce_ease_out, bounce_ease_in_out, " + << "circ_ease_in, circ_ease_out, circ_ease_in_out, " + << "elastic_ease_in, elastic_ease_out, elastic_ease_in_out, " + << "expo_ease_in, expo_ease_out, expo_ease_in_out, " + << "sine_ease_in, sine_ease_out, sine_ease_in_out"; + TCLAP::ValueArg timing_mode_arg( + // Option short form (not supported) + "", + // Option long form + "timing-mode", + // Option description + "The timing mode used by every test", + // Not required + false, + // Option default + "linear_ease_in_out", + // Option example (part of description) + timing_mode_arg_desc.str().c_str() + ); + + TCLAP::ValueArg fps_arg( + // Option short form (not supported) + "", + // Option long form + "fps", + // Option description + "The frame interval to update by for every test", + // Not required + false, + // Option default + NOM_ACTION_TEST_FLAG(fps), + // Option example (part of description) + "30, 60, 90, 120, ..." + ); + + std::stringstream vsync_arg_desc; + vsync_arg_desc << "Try to always render the action during the monitor's " + << "VSYNC period -- enabling this option will override any " + << "end-user specified FPS value when a sensible value can " + << "be determined from the display hardware."; + TCLAP::SwitchArg vsync_arg( + // Option short form (not supported) + "", + // Option long form + "enable-vsync", + // Option description + vsync_arg_desc.str().c_str(), + // Option default + NOM_ACTION_TEST_FLAG(enable_vsync) + ); + + std::stringstream num_objects_arg_desc; + num_objects_arg_desc << "The number of objects to spawn; this value is " + << "currently only used in " + << "ActionTest.RainingRectsStressTest"; + TCLAP::ValueArg num_objects_arg( + // Option short form (not supported) + "", + // Option long form + "num-objects", + // Option description + num_objects_arg_desc.str().c_str(), + // Not required + false, + // Option default + NOM_ACTION_TEST_FLAG(num_objects), + // Option example (part of description) + "100" + ); + + TCLAP::SwitchArg action_logging_arg( + // Option short form (not supported) + "", + // Option long form + "enable-action-logging", + // Option description + "Enable debug logging of actions", + // Option default + NOM_ACTION_TEST_FLAG(enable_action_logging) + ); + + TCLAP::SwitchArg queue_logging_arg( + // Option short form (not supported) + "", + // Option long form + "enable-queue-logging", + // Option description + "Enable debug logging of the engine queues", + // Option default + NOM_ACTION_TEST_FLAG(enable_queue_logging) + ); + + args.push_back(&speed_modifier_arg); + args.push_back(&timing_mode_arg); + args.push_back(&fps_arg); + args.push_back(&vsync_arg); + args.push_back(&num_objects_arg); + args.push_back(&action_logging_arg); + args.push_back(&queue_logging_arg); + + // nom::UnitTest framework integration + nom::init_test(argc, argv, args); + + if( speed_modifier_arg.getValue() != NOM_ACTION_TEST_FLAG(speed) ) { + NOM_ACTION_TEST_FLAG(speed) = speed_modifier_arg.getValue(); + } + + // nom::Linear is the default timing mode when none is given + nom::IActionObject::timing_curve_func mode = + nom::make_timing_curve_from_string( timing_mode_arg.getValue() ); + + NOM_ACTION_TEST_FLAG(timing_curve) = mode; + NOM_ACTION_TEST_FLAG(timing_mode_str) = timing_mode_arg.getValue(); + + uint32 fps = fps_arg.getValue(); + if( fps != NOM_ACTION_TEST_FLAG(fps) ) { + NOM_ACTION_TEST_FLAG(fps) = fps; + } + + NOM_ACTION_TEST_FLAG(enable_vsync) = vsync_arg.getValue(); + NOM_ACTION_TEST_FLAG(num_objects) = num_objects_arg.getValue(); + NOM_ACTION_TEST_FLAG(enable_action_logging) = action_logging_arg.getValue(); + NOM_ACTION_TEST_FLAG(enable_queue_logging) = queue_logging_arg.getValue(); + + return true; +} + +/// \brief Test animation timing sanity. +TEST_F(ActionTest, WaitForDurationAction) +{ + // Testing parameters + const real32 DURATION = 2.0f; + const real32 SPEED_MOD = NOM_ACTION_TEST_FLAG(speed); + const IActionObject::timing_curve_func TIMING_MODE = + NOM_ACTION_TEST_FLAG(timing_curve); + const uint32 FPS = NOM_ACTION_TEST_FLAG(fps); + + auto idle2s = + nom::create_action( WaitForDurationAction(DURATION) ); + ASSERT_TRUE(idle2s != nullptr); + + auto action0 = + nom::create_action( {idle2s} ); + ASSERT_TRUE(action0 != nullptr); + action0->set_timing_curve(TIMING_MODE); + action0->set_speed(SPEED_MOD); + action0->set_name("action0"); + + EXPECT_EQ(0, this->player.num_actions() ); + this->run_action_ret = + this->player.run_action(action0, [=]() { + + EXPECT_EQ(1, this->player.num_actions() ); + this->expected_action_params(action0.get(), 1); + this->expected_common_params(idle2s.get(), DURATION, SPEED_MOD); + }); + EXPECT_EQ(true, this->run_action_ret) + << "Failed to queue action0"; + EXPECT_EQ(1, this->player.num_actions() ); + + this->append_update_callback( [=](float) { + if( this->expected_min_duration(DURATION, SPEED_MOD) == true ) { + this->quit(); + } + }); + + this->append_frame_interval(FPS); + + EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); +} + +/// \remarks Thanks goes to Tim Jones of [sdltutorials.com](http://www.sdltutorials.com/sdl-animation) +/// for the sprite frames of Yoshi chosen for this test! +TEST_F(ActionTest, AnimateTexturesAction) +{ + // Testing parameters + texture_frames anim_frames; + const std::vector texture_filenames = {{ + "yoshi_000.png", "yoshi_001.png", "yoshi_002.png", "yoshi_003.png", + "yoshi_004.png", "yoshi_005.png", "yoshi_006.png", "yoshi_007.png" + }}; + + // fps per texture + const real32 FRAME_DURATION = 0.5f; + + // total test duration + const real32 DURATION = FRAME_DURATION * texture_filenames.size(); + + const real32 SPEED_MOD = NOM_ACTION_TEST_FLAG(speed); + const IActionObject::timing_curve_func TIMING_MODE = + NOM_ACTION_TEST_FLAG(timing_curve); + const uint32 FPS = NOM_ACTION_TEST_FLAG(fps); + + this->init_animate_textures_test(texture_filenames, anim_frames); + + EXPECT_EQ( anim_frames.size(), texture_filenames.size() ); + + auto sprite0 = + std::make_shared(); + ASSERT_TRUE(sprite0 != nullptr); + + auto tex_bg = + nom::create_action( sprite0, anim_frames, + FRAME_DURATION ); + ASSERT_TRUE(tex_bg != nullptr); + + auto action0 = + nom::create_action( {tex_bg} ); + ASSERT_TRUE(action0 != nullptr); + action0->set_timing_curve(TIMING_MODE); + action0->set_speed(SPEED_MOD); + action0->set_name("action0"); + + auto remove_action0 = + nom::create_action(action0); + ASSERT_TRUE(remove_action0 != nullptr); + remove_action0->set_name("remove_action0"); + + EXPECT_EQ(0, this->player.num_actions() ); + this->run_action_ret = + this->player.run_action(action0, [=]() { + + EXPECT_EQ(1, this->player.num_actions() ); + this->expected_animate_textures_params( tex_bg.get(), anim_frames.size(), + DURATION, SPEED_MOD, + "animate_textures_params" ); + this->expected_action_params(action0.get(), 1); + + this->player.run_action(remove_action0, [=]() { + ASSERT_TRUE(sprite0 != nullptr); + EXPECT_FALSE( sprite0->valid() ); + }); + }); + EXPECT_EQ(true, this->run_action_ret) + << "Failed to queue action0"; + EXPECT_EQ(1, this->player.num_actions() ); + + this->append_update_callback( [=](real32) { + if( this->expected_min_duration(DURATION, SPEED_MOD) == true ) { + this->quit(); + } + }); + + this->append_render_queue( sprite0.get() ); + this->append_frame_interval(FPS); + + EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); +} + +//! [usage_example] +TEST_F(ActionTest, MoveByAction) +{ + // Testing parameters + const float DURATION = 2.5f; + const float SPEED_MOD = NOM_ACTION_TEST_FLAG(speed); + const IActionObject::timing_curve_func TIMING_MODE = + NOM_ACTION_TEST_FLAG(timing_curve); + const Point2i TRANSLATE_POS( Point2i(200,0) ); + const uint32 FPS = NOM_ACTION_TEST_FLAG(fps); + + // Initial texture position and size + const Point2i RECT_POS(Point2i::zero); + const Size2i RECT_SIZE(WINDOW_DIMS.w/4, WINDOW_DIMS.h); + const Point2i EXPECTED_TEX_POS(TRANSLATE_POS); + + auto rect = std::make_shared( + Rectangle( IntRect(RECT_POS, RECT_SIZE), Color4i::Green) ); + ASSERT_TRUE(rect != nullptr); + + auto sprite = nom::make_shared_sprite( rect->texture() ); + ASSERT_TRUE(sprite != nullptr); + + auto translate = + nom::create_action(sprite, TRANSLATE_POS, DURATION); + ASSERT_TRUE(translate != nullptr); + + auto action0 = + nom::create_action( {translate} ); + ASSERT_TRUE(action0 != nullptr); + action0->set_timing_curve(TIMING_MODE); + action0->set_speed(SPEED_MOD); + action0->set_name("action0"); + + EXPECT_EQ(0, this->player.num_actions() ); + this->run_action_ret = + this->player.run_action(action0, [=]() { + + EXPECT_EQ( EXPECTED_TEX_POS, sprite->position() ); + EXPECT_EQ(1, this->player.num_actions() ); + + this->expected_action_params(action0.get(), 1); + this->expected_common_params(translate.get(), DURATION, SPEED_MOD); + }); + EXPECT_EQ(true, this->run_action_ret) + << "Failed to queue the action!"; + EXPECT_EQ(1, this->player.num_actions() ); + + this->append_update_callback( [=](float) { + if( this->expected_min_duration(DURATION, SPEED_MOD) == true ) { + this->quit(); + } + }); + + this->append_render_queue( sprite.get() ); + this->append_frame_interval(FPS); + + EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); +} +//! [usage_example] + +TEST_F(ActionTest, MoveByActionNegativeXDelta) +{ + // Testing parameters + const float DURATION = 2.0f; + const float SPEED_MOD = NOM_ACTION_TEST_FLAG(speed); + const IActionObject::timing_curve_func TIMING_MODE = + NOM_ACTION_TEST_FLAG(timing_curve); + const uint32 FPS = NOM_ACTION_TEST_FLAG(fps); + + // Initial texture position and size + const Size2i RECT_SIZE(WINDOW_DIMS.w/4, WINDOW_DIMS.h); + const Point2i RECT_POS(WINDOW_DIMS.w, 0); + const Point2i TRANSLATE_POS( Point2i(-WINDOW_DIMS.w,0) ); + const Point2i EXPECTED_TEX_POS(0,0); + + auto rect = std::make_shared( + Rectangle( IntRect(RECT_POS, RECT_SIZE), Color4i::Green) ); + ASSERT_TRUE(rect != nullptr); + + auto sprite = nom::make_shared_sprite( rect->texture() ); + ASSERT_TRUE(sprite != nullptr); + + auto translate = + nom::create_action(sprite, TRANSLATE_POS, DURATION); + ASSERT_TRUE(translate != nullptr); + + auto action0 = + nom::create_action( {translate} ); + ASSERT_TRUE(action0 != nullptr); + action0->set_timing_curve(TIMING_MODE); + action0->set_speed(SPEED_MOD); + action0->set_name("action0"); + + EXPECT_EQ(0, this->player.num_actions() ); + this->run_action_ret = + this->player.run_action(action0, [=]() { + + EXPECT_EQ(EXPECTED_TEX_POS, sprite->position() ); + EXPECT_EQ(1, this->player.num_actions() ); + + this->expected_action_params(action0.get(), 1); + this->expected_common_params(translate.get(), DURATION, SPEED_MOD); + }); + EXPECT_EQ(true, this->run_action_ret) + << "Failed to queue the action!"; + EXPECT_EQ(1, this->player.num_actions() ); + + this->append_update_callback( [=](float) { + if( this->expected_min_duration(DURATION, SPEED_MOD) == true ) { + this->quit(); + } + }); + + this->append_render_queue( sprite.get() ); + this->append_frame_interval(FPS); + + EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); +} + +TEST_F(ActionTest, MoveByActionWithNegativeYDelta) +{ + // Testing parameters + const float DURATION = 2.0f; + const float SPEED_MOD = NOM_ACTION_TEST_FLAG(speed); + const IActionObject::timing_curve_func TIMING_MODE = + NOM_ACTION_TEST_FLAG(timing_curve); + const uint32 FPS = NOM_ACTION_TEST_FLAG(fps); + + // Initial texture position and size + const Size2i RECT_SIZE(WINDOW_DIMS.w/4, WINDOW_DIMS.h/4); + const Point2i RECT_POS(0, WINDOW_DIMS.h); + const Point2i TRANSLATE_POS( Point2i(0,-WINDOW_DIMS.h) ); + const Point2i EXPECTED_TEX_POS(0,0); + + auto rect = std::make_shared( + Rectangle( IntRect(RECT_POS, RECT_SIZE), Color4i::Green) ); + ASSERT_TRUE(rect != nullptr); + + auto sprite = nom::make_shared_sprite( rect->texture() ); + ASSERT_TRUE(sprite != nullptr); + + auto translate = + nom::create_action(sprite, TRANSLATE_POS, DURATION); + ASSERT_TRUE(translate != nullptr); + + auto action0 = + nom::create_action( {translate} ); + ASSERT_TRUE(action0 != nullptr); + action0->set_timing_curve(TIMING_MODE); + action0->set_speed(SPEED_MOD); + action0->set_name("action0"); + + EXPECT_EQ(0, this->player.num_actions() ); + this->run_action_ret = + this->player.run_action(action0, [=]() { + + EXPECT_EQ(EXPECTED_TEX_POS, sprite->position() ); + EXPECT_EQ(1, this->player.num_actions() ); + + this->expected_action_params(action0.get(), 1); + this->expected_common_params(translate.get(), DURATION, SPEED_MOD); + }); + EXPECT_EQ(true, this->run_action_ret) + << "Failed to queue action0"; + EXPECT_EQ(1, this->player.num_actions() ); + + this->append_update_callback( [=](float) { + if( this->expected_min_duration(DURATION, SPEED_MOD) == true ) { + this->quit(); + } + }); + + this->append_render_queue( sprite.get() ); + this->append_frame_interval(FPS); + + EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); +} + +TEST_F(ActionTest, ScaleByAction) +{ + // Testing parameters + const float DURATION = 1.5f; + const float SPEED_MOD = NOM_ACTION_TEST_FLAG(speed); + const IActionObject::timing_curve_func TIMING_MODE = + NOM_ACTION_TEST_FLAG(timing_curve); + const uint32 FPS = NOM_ACTION_TEST_FLAG(fps); + const Size2f SCALE_FACTOR(2.0f, 2.0f); + + // Initial texture position and size + const Point2i TEX_POS(Point2i::zero); + const Size2i TEX_SIZE(128, 128); + const Size2i EXPECTED_TEX_SIZE( Size2i( TEX_SIZE.w * SCALE_FACTOR.w, + TEX_SIZE.h * SCALE_FACTOR.h ) + ); + + auto rect = std::make_shared( + IntRect(TEX_POS, TEX_SIZE), Color4i::Blue); + ASSERT_TRUE(rect != nullptr); + + auto tex = rect->texture(); + + auto sprite = std::make_shared(); + ASSERT_TRUE(sprite != nullptr); + EXPECT_EQ(true, sprite->set_texture(tex) ); + + auto scale_by = + nom::create_action(sprite, SCALE_FACTOR, DURATION); + ASSERT_TRUE(scale_by != nullptr); + + auto action0 = + nom::create_action( {scale_by} ); + ASSERT_TRUE(action0 != nullptr); + action0->set_timing_curve(TIMING_MODE); + action0->set_speed(SPEED_MOD); + action0->set_name("action0"); + + EXPECT_EQ(0, this->player.num_actions() ); + this->run_action_ret = + this->player.run_action(action0, [=]() { + + EXPECT_EQ( EXPECTED_TEX_SIZE, sprite->size() ); + EXPECT_EQ(1, this->player.num_actions() ); + + this->expected_action_params(action0.get(), 1); + this->expected_common_params(scale_by.get(), DURATION, SPEED_MOD); + }); + EXPECT_EQ(true, this->run_action_ret) + << "Failed to queue the action!"; + EXPECT_EQ(1, this->player.num_actions() ); + + this->append_update_callback( [=](float) mutable { + nom::set_alignment( sprite.get(), Point2i::zero, WINDOW_DIMS, + Anchor::MiddleCenter ); + }); + + this->append_update_callback( [=](float) { + if( this->expected_min_duration(DURATION, SPEED_MOD) == true ) { + this->quit(); + } + }); + + this->append_render_queue( sprite.get() ); + this->append_frame_interval(FPS); + + EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); +} + +TEST_F(ActionTest, ScaleByActionWithNegativeFactor) +{ + // Testing parameters + const float DURATION = 1.5f; + const float SPEED_MOD = NOM_ACTION_TEST_FLAG(speed); + const IActionObject::timing_curve_func TIMING_MODE = + NOM_ACTION_TEST_FLAG(timing_curve); + const uint32 FPS = NOM_ACTION_TEST_FLAG(fps); + const Size2f SCALE_FACTOR(-3.0f, -3.0f); + + // Initial texture position and size + const Point2i TEX_POS(Point2i::zero); + const Size2i TEX_SIZE(128, 128); + const Size2i EXPECTED_TEX_SIZE( + nom::round_float( (real32)(TEX_SIZE.w) / fabs(SCALE_FACTOR.w) ), + nom::round_float( (real32)(TEX_SIZE.h) / fabs(SCALE_FACTOR.h) ) + ); + + std::string TEX_FILE_PATH = resources[0].path() + "card.png"; + + auto tex = std::make_shared( Texture() ); + ASSERT_TRUE(tex != nullptr); + if( tex->load(TEX_FILE_PATH) == false ) { + FAIL() << "Could not load texture from file: " << TEX_FILE_PATH; + } + + auto sprite = + std::make_shared(); + ASSERT_TRUE(sprite != nullptr); + EXPECT_EQ(true, sprite->set_texture(tex) ); + + auto scale_by = + nom::create_action(sprite, SCALE_FACTOR, DURATION); + ASSERT_TRUE(scale_by != nullptr); + scale_by->set_name("scale_by"); + + auto action0 = + nom::create_action( {scale_by} ); + ASSERT_TRUE(action0 != nullptr); + action0->set_timing_curve(TIMING_MODE); + action0->set_speed(SPEED_MOD); + action0->set_name("action0"); + + EXPECT_EQ(0, this->player.num_actions() ); + this->run_action_ret = + this->player.run_action(action0, [=]() { + + EXPECT_EQ(EXPECTED_TEX_SIZE, sprite->size() ); + EXPECT_EQ(1, this->player.num_actions() ); + + this->expected_action_params(action0.get(), 1); + this->expected_common_params(scale_by.get(), DURATION, SPEED_MOD); + }); + EXPECT_EQ(true, this->run_action_ret) + << "Failed to queue the action!"; + EXPECT_EQ(1, this->player.num_actions() ); + + this->append_update_callback( [=](float) mutable { + nom::set_alignment( sprite.get(), Point2i::zero, WINDOW_DIMS, + Anchor::MiddleCenter ); + }); + + this->append_update_callback( [=](float) { + if( this->expected_min_duration(DURATION, SPEED_MOD) == true ) { + this->quit(); + } + }); + + this->append_render_queue( sprite.get() ); + this->append_frame_interval(FPS); + + EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); +} + +TEST_F(ActionTest, ScaleByActionWithNonPowerOfTwo) +{ + // Testing parameters + const float DURATION = 1.5f; + const float SPEED_MOD = NOM_ACTION_TEST_FLAG(speed); + const Size2f SCALE_FACTOR(2.25f, 1.75f); + const IActionObject::timing_curve_func TIMING_MODE = + NOM_ACTION_TEST_FLAG(timing_curve); + const uint32 FPS = NOM_ACTION_TEST_FLAG(fps); + + // Initial texture position and size + const Point2i TEX_POS(Point2i::zero); + const Size2i TEX_SIZE(129, 129); + const Size2i EXPECTED_TEX_SIZE = + Size2i( TEX_SIZE.w * SCALE_FACTOR.w, + nom::round_float(TEX_SIZE.h * SCALE_FACTOR.h) ); + + auto rect = std::make_shared( + IntRect(TEX_POS, TEX_SIZE), Color4i::Blue); + ASSERT_TRUE(rect != nullptr); + + auto tex = rect->texture(); + + auto sprite = std::make_shared(); + ASSERT_TRUE(sprite != nullptr); + EXPECT_EQ(true, sprite->set_texture(tex) ); + + auto scale_by = + nom::create_action(sprite, SCALE_FACTOR, DURATION); + ASSERT_TRUE(scale_by != nullptr); + scale_by->set_name("scale_by"); + + auto action0 = + nom::create_action( {scale_by} ); + ASSERT_TRUE(action0 != nullptr); + action0->set_timing_curve(TIMING_MODE); + action0->set_speed(SPEED_MOD); + action0->set_name("action0"); + + EXPECT_EQ(0, this->player.num_actions() ); + this->run_action_ret = + this->player.run_action(action0, [=]() { + + EXPECT_EQ( EXPECTED_TEX_SIZE, sprite->size() ); + EXPECT_EQ(1, this->player.num_actions() ); + + this->expected_action_params(action0.get(), 1); + this->expected_common_params(scale_by.get(), DURATION, SPEED_MOD); + }); + EXPECT_EQ(true, this->run_action_ret) + << "Failed to queue the action!"; + EXPECT_EQ(1, this->player.num_actions() ); + + this->append_update_callback( [=](float) mutable { + nom::set_alignment( sprite.get(), Point2i::zero, WINDOW_DIMS, + Anchor::MiddleCenter ); + }); + + this->append_update_callback( [=](float) { + if( this->expected_min_duration(DURATION, SPEED_MOD) == true ) { + this->quit(); + } + }); + + this->append_render_queue( sprite.get() ); + this->append_frame_interval(FPS); + + EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); +} + +TEST_F(ActionTest, CallbackActionDefaultDuration) +{ + // Testing parameters + const float DURATION = 0.0f; + const float SPEED_MOD = NOM_ACTION_TEST_FLAG(speed); + const IActionObject::timing_curve_func TIMING_MODE = + NOM_ACTION_TEST_FLAG(timing_curve); + const uint32 FPS = NOM_ACTION_TEST_FLAG(fps); + + CallbackAction::callback_func func; + func = [=]() { + std::cout << "Hello, there!\n"; + }; + + auto anim0 = nom::create_action(func); + + auto action0 = + nom::create_action( {anim0} ); + ASSERT_TRUE(action0 != nullptr); + action0->set_timing_curve(TIMING_MODE); + action0->set_speed(SPEED_MOD); + action0->set_name("action0"); + + auto remove_action0 = + nom::create_action(action0); + ASSERT_TRUE(remove_action0 != nullptr); + remove_action0->set_name("remove_action0"); + + EXPECT_EQ(0, this->player.num_actions() ); + this->run_action_ret = + this->player.run_action(action0, [=]() { + + EXPECT_EQ(1, this->player.num_actions() ); + + this->expected_action_params(action0.get(), 1); + this->expected_common_params(anim0.get(), DURATION, SPEED_MOD); + + this->player.run_action(remove_action0); + }); + EXPECT_EQ(true, this->run_action_ret) + << "Failed to queue the action!"; + EXPECT_EQ(1, this->player.num_actions() ); + + this->append_update_callback( [=](float) { + if( this->expected_min_duration(DURATION, SPEED_MOD) == true ) { + this->quit(); + } + }); + + this->append_frame_interval(FPS); + + EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); +} + +TEST_F(ActionTest, CallbackActionWithNonZeroDuration) +{ + // Testing parameters + const real32 DURATION = 0.05f; + const float SPEED_MOD = NOM_ACTION_TEST_FLAG(speed); + const IActionObject::timing_curve_func TIMING_MODE = + NOM_ACTION_TEST_FLAG(timing_curve); + const uint32 FPS = NOM_ACTION_TEST_FLAG(fps); + + CallbackAction::callback_func func; + func = [=]() { + std::cout << "Hello, there!\n"; + }; + + auto anim0 = nom::create_action(DURATION, func); + + auto action0 = + nom::create_action( {anim0} ); + ASSERT_TRUE(action0 != nullptr); + action0->set_timing_curve(TIMING_MODE); + action0->set_speed(SPEED_MOD); + action0->set_name("action0"); + + auto remove_action0 = + nom::create_action(action0); + ASSERT_TRUE(remove_action0 != nullptr); + remove_action0->set_name("remove_action0"); + + EXPECT_EQ(0, this->player.num_actions() ); + this->run_action_ret = + this->player.run_action(action0, [=]() { + + EXPECT_EQ(1, this->player.num_actions() ); + + this->expected_action_params(action0.get(), 1); + this->expected_common_params(anim0.get(), DURATION, SPEED_MOD); + + this->player.run_action(remove_action0); + }); + EXPECT_EQ(true, this->run_action_ret) + << "Failed to queue the action!"; + EXPECT_EQ(1, this->player.num_actions() ); + + this->append_update_callback( [=](float) { + if( this->expected_min_duration(DURATION, SPEED_MOD) == true ) { + this->quit(); + } + }); + + this->append_frame_interval(FPS); + + EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); +} + +} // namespace nom + +int main(int argc, char** argv) +{ + ::testing::InitGoogleTest(&argc, argv); + + // NOTE: The animated full-screen delay on OS X causes us to miss several key + // frames anytime it is toggled + nom::set_hint(SDL_HINT_VIDEO_MAC_FULLSCREEN_SPACES, "0"); + + // Set the current working directory path to the path leading to this + // executable file; used for unit tests that require file-system I/O. + if( nom::init(argc, argv) == false ) { + NOM_LOG_CRIT(NOM_LOG_CATEGORY_APPLICATION, "Could not initialize nomlib."); + return NOM_EXIT_FAILURE; + } + atexit(nom::quit); + + if( nom::init_cmd_line_args(argc, argv) == false ) { + NOM_LOG_CRIT( NOM_LOG_CATEGORY_APPLICATION, + "Could not initialize unit testing framework." ); + return NOM_EXIT_FAILURE; + } + + // We do not utilize screen dump comparison features for any of these tests, + // so we might as well leave the logic disabled... + NOM_TEST_FLAG(disable_comparison) = true; + + // These tests **must** always run with the interactive flag set, so that the + // main loop does not prematurely terminate on us before we are done + // executing the animation's loop; we explicitly terminate each test upon + // the appropriate completion conditions. + NOM_TEST_FLAG(interactive) = true; + + return RUN_ALL_TESTS(); +} diff --git a/tests/src/actions/ActionTest_ActionPlayer.cpp b/tests/src/actions/ActionTest_ActionPlayer.cpp new file mode 100644 index 00000000..91e4e395 --- /dev/null +++ b/tests/src/actions/ActionTest_ActionPlayer.cpp @@ -0,0 +1,1103 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#include "nomlib/tests/actions/ActionTest.hpp" + +namespace nom { + +/// \brief The two group actions should finish within one frame of each other. +TEST_F(ActionTest, GroupActionFinishEquality) +{ + // Testing parameters + const real32 DURATION = 1.0f; + const real32 SPEED_MOD0 = NOM_ACTION_TEST_FLAG(speed); + const real32 SPEED_MOD1 = NOM_ACTION_TEST_FLAG(speed); + const IActionObject::timing_curve_func TIMING_MODE = + NOM_ACTION_TEST_FLAG(timing_curve); + const uint32 FPS = NOM_ACTION_TEST_FLAG(fps); + + // Initial texture position and size + const Size2i RECT_SIZE(WINDOW_DIMS.w/4, WINDOW_DIMS.h/4); + const Point2i TRANSLATE_POS( Point2i(WINDOW_DIMS.w-RECT_SIZE.w,0) ); + const Point2i RECT0_POS(0, RECT_SIZE.h*0); + const Point2i RECT1_POS(0, RECT_SIZE.h*1); + const Point2i RECT2_POS(0, RECT_SIZE.h*2); + const Point2i RECT3_POS(0, RECT_SIZE.h*3); + const Point2i EXPECTED_TEX0_POS(TRANSLATE_POS); + const Point2i EXPECTED_TEX1_POS(TRANSLATE_POS.x, RECT_SIZE.h*1); + const Point2i EXPECTED_TEX2_POS(TRANSLATE_POS.x, RECT_SIZE.h*2); + const Point2i EXPECTED_TEX3_POS(TRANSLATE_POS.x, RECT_SIZE.h*3); + + auto rect0 = + std::make_shared(IntRect(RECT0_POS, RECT_SIZE), Color4i::Green); + ASSERT_TRUE(rect0 != nullptr); + + auto rect1 = + std::make_shared(IntRect(RECT1_POS, RECT_SIZE), Color4i::Red); + ASSERT_TRUE(rect1 != nullptr); + + auto rect2 = + std::make_shared(IntRect(RECT2_POS, RECT_SIZE), Color4i::Blue); + ASSERT_TRUE(rect2 != nullptr); + + auto rect3 = + std::make_shared(IntRect(RECT3_POS, RECT_SIZE), Color4i::Yellow); + ASSERT_TRUE(rect3 != nullptr); + + auto sprite0 = nom::make_shared_sprite( rect0->texture() ); + ASSERT_TRUE(sprite0 != nullptr); + + auto sprite1 = nom::make_shared_sprite( rect1->texture() ); + ASSERT_TRUE(sprite1 != nullptr); + + auto sprite2 = nom::make_shared_sprite( rect2->texture() ); + ASSERT_TRUE(sprite2 != nullptr); + + auto sprite3 = nom::make_shared_sprite( rect3->texture() ); + ASSERT_TRUE(sprite3 != nullptr); + + auto translate0 = + nom::create_action(sprite0, TRANSLATE_POS, DURATION); + ASSERT_TRUE(translate0 != nullptr); + + auto translate1 = + nom::create_action(sprite1, TRANSLATE_POS, DURATION); + ASSERT_TRUE(translate1 != nullptr); + + auto translate2 = + nom::create_action(sprite2, TRANSLATE_POS, DURATION); + ASSERT_TRUE(translate2 != nullptr); + + auto translate3 = + nom::create_action(sprite3, TRANSLATE_POS, DURATION); + ASSERT_TRUE(translate3 != nullptr); + + auto action0 = + nom::create_action( {translate0, translate1} ); + ASSERT_TRUE(action0 != nullptr); + action0->set_timing_curve(TIMING_MODE); + action0->set_speed(SPEED_MOD0); + action0->set_name("action0"); + + auto action1 = + nom::create_action( {translate2, translate3} ); + ASSERT_TRUE(action1 != nullptr); + action1->set_speed(SPEED_MOD1); + action1->set_timing_curve(TIMING_MODE); + action1->set_name("action1"); + + auto remove_action0 = + nom::create_action(action0); + ASSERT_TRUE(remove_action0 != nullptr); + remove_action0->set_name("remove_action0"); + + auto remove_action1 = + nom::create_action(action1); + ASSERT_TRUE(remove_action1 != nullptr); + remove_action1->set_name("remove_action1"); + + EXPECT_EQ(0, this->player.num_actions() ); + this->run_action_ret = + this->player.run_action(action0, [=]() { + + EXPECT_EQ( EXPECTED_TEX0_POS, sprite0->position() ); + EXPECT_EQ( EXPECTED_TEX1_POS, sprite1->position() ); + this->expected_action_params(action0.get(), 2); + this->expected_common_params(translate0.get(), DURATION, SPEED_MOD0); + this->expected_common_params(translate1.get(), DURATION, SPEED_MOD0); + + this->player.run_action(remove_action0, [=]() { + // NOTE: The removal of the action should free the stored texture of each + // sprite, preventing it from being rendered by invalidating the texture. + EXPECT_TRUE(sprite0 != nullptr); + EXPECT_FALSE( sprite0->valid() ); + EXPECT_TRUE(sprite1 != nullptr); + EXPECT_FALSE( sprite1->valid() ); + }); + }); + EXPECT_EQ(true, this->run_action_ret) + << "Failed to queue action0"; + EXPECT_EQ(1, this->player.num_actions() ); + + EXPECT_EQ(1, this->player.num_actions() ); + this->run_action_ret = + this->player.run_action(action1, [=]() { + + EXPECT_EQ( EXPECTED_TEX2_POS, sprite2->position() ); + EXPECT_EQ( EXPECTED_TEX3_POS, sprite3->position() ); + this->expected_action_params(action1.get(), 2); + this->expected_common_params(translate2.get(), DURATION, SPEED_MOD1); + this->expected_common_params(translate3.get(), DURATION, SPEED_MOD1); + + this->player.run_action(remove_action1, [=]() { + // NOTE: The removal of the action should free the stored texture of each + // sprite, preventing it from being rendered by invalidating the texture. + EXPECT_TRUE(sprite2 != nullptr); + EXPECT_FALSE( sprite2->valid() ); + EXPECT_TRUE(sprite2 != nullptr); + EXPECT_FALSE( sprite2->valid() ); + }); + }); + EXPECT_EQ(true, this->run_action_ret) + << "Failed to queue action1"; + EXPECT_EQ(2, this->player.num_actions() ); + + this->append_update_callback( [=](float) { + + if( this->player.action_running("action0") == false && + this->player.action_running("action1") == false && + // The smallest speed modifier should always be used here + this->expected_min_duration(DURATION, SPEED_MOD0) == true ) + { + this->quit(); + } + }); + + this->append_render_queue( sprite0.get() ); // action0 + this->append_render_queue( sprite1.get() ); // action1 + this->append_render_queue( sprite2.get() ); // action2 + this->append_render_queue( sprite3.get() ); // action3 + this->append_frame_interval(FPS); + + EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); +} + +TEST_F(ActionTest, CancelAction) +{ + // Testing parameters + const float DURATION = 1.0f; + const Point2i TRANSLATE_POS(Point2i::zero); + + auto translate0 = + nom::create_action(nullptr, TRANSLATE_POS, DURATION); + ASSERT_TRUE(translate0 != nullptr); + + auto action0 = + nom::create_action( {translate0} ); + ASSERT_TRUE(action0 != nullptr); + action0->set_name("action0"); + + auto action1 = + nom::create_action( { translate0->clone() } ); + ASSERT_TRUE(action1 != nullptr); + action1->set_name("action1"); + + EXPECT_EQ(0, this->player.num_actions() ); + this->run_action_ret = + this->player.run_action(action0); + EXPECT_EQ(true, this->run_action_ret) + << "Failed to queue action0"; + EXPECT_EQ(1, this->player.num_actions() ); + + EXPECT_EQ(true, this->player.cancel_action("action0") ); + EXPECT_EQ(false, this->player.cancel_action("action1") ); + + this->run_action_ret = + this->player.run_action(action1); + EXPECT_EQ(true, this->run_action_ret) + << "Failed to queue action1"; + EXPECT_EQ(1, this->player.num_actions() ); + + EXPECT_EQ(true, this->player.cancel_action("action1") ); +} + +TEST_F(ActionTest, ConcurrentGroupActions) +{ + // Testing parameters + const real32 DURATION = 1.0f; + const real32 SPEED_MOD0 = NOM_ACTION_TEST_FLAG(speed) * 1.0f; // action0 + const real32 SPEED_MOD1 = NOM_ACTION_TEST_FLAG(speed) * 2.0f; // action1 + const real32 SPEED_MOD2 = NOM_ACTION_TEST_FLAG(speed) * 2.25f; // action2 + const real32 SPEED_MOD3 = NOM_ACTION_TEST_FLAG(speed) * 1.25f; // action3 + const IActionObject::timing_curve_func TIMING_MODE = + NOM_ACTION_TEST_FLAG(timing_curve); + const uint32 FPS = NOM_ACTION_TEST_FLAG(fps); + + // Initial texture position and size + const Size2i RECT_SIZE(WINDOW_DIMS.w/4, WINDOW_DIMS.h/4); + const Point2i TRANSLATE_POS( Point2i(WINDOW_DIMS.w-RECT_SIZE.w,0) ); + const Point2i RECT0_POS(0, RECT_SIZE.h*0); + const Point2i RECT1_POS(0, RECT_SIZE.h*1); + const Point2i RECT2_POS(0, RECT_SIZE.h*2); + const Point2i RECT3_POS(0, RECT_SIZE.h*3); + const Point2i EXPECTED_TEX0_POS(TRANSLATE_POS); + const Point2i EXPECTED_TEX1_POS(TRANSLATE_POS.x, RECT_SIZE.h*1); + const Point2i EXPECTED_TEX2_POS(TRANSLATE_POS.x, RECT_SIZE.h*2); + const Point2i EXPECTED_TEX3_POS(TRANSLATE_POS.x, RECT_SIZE.h*3); + + auto rect0 = + std::make_shared(IntRect(RECT0_POS, RECT_SIZE), Color4i::Green); + ASSERT_TRUE(rect0 != nullptr); + + auto rect1 = + std::make_shared(IntRect(RECT1_POS, RECT_SIZE), Color4i::Red); + ASSERT_TRUE(rect1 != nullptr); + + auto rect2 = + std::make_shared(IntRect(RECT2_POS, RECT_SIZE), Color4i::Blue); + ASSERT_TRUE(rect2 != nullptr); + + auto rect3 = + std::make_shared(IntRect(RECT3_POS, RECT_SIZE), Color4i::Yellow); + ASSERT_TRUE(rect3 != nullptr); + + auto sprite0 = nom::make_shared_sprite( rect0->texture() ); + ASSERT_TRUE(sprite0 != nullptr); + + auto sprite1 = nom::make_shared_sprite( rect1->texture() ); + ASSERT_TRUE(sprite1 != nullptr); + + auto sprite2 = nom::make_shared_sprite( rect2->texture() ); + ASSERT_TRUE(sprite2 != nullptr); + + auto sprite3 = nom::make_shared_sprite( rect3->texture() ); + ASSERT_TRUE(sprite3 != nullptr); + + auto translate0 = + nom::create_action(sprite0, TRANSLATE_POS, DURATION); + ASSERT_TRUE(translate0 != nullptr); + + auto translate1 = + nom::create_action(sprite1, TRANSLATE_POS, DURATION); + ASSERT_TRUE(translate1 != nullptr); + + auto translate2 = + nom::create_action(sprite2, TRANSLATE_POS, DURATION); + ASSERT_TRUE(translate2 != nullptr); + + auto translate3 = + nom::create_action(sprite3, TRANSLATE_POS, DURATION); + ASSERT_TRUE(translate3 != nullptr); + + translate0->set_name("translate0"); + translate1->set_name("translate1"); + translate2->set_name("translate2"); + translate3->set_name("translate3"); + + auto action0 = nom::create_action( {translate0} ); + ASSERT_TRUE(action0 != nullptr); + action0->set_timing_curve(TIMING_MODE); + action0->set_speed(SPEED_MOD0); + action0->set_name("action0"); + + auto action1 = nom::create_action( {translate1} ); + ASSERT_TRUE(action1 != nullptr); + action1->set_speed(SPEED_MOD1); + action1->set_timing_curve(TIMING_MODE); + action1->set_name("action1"); + + auto action2 = nom::create_action( {translate2} ); + ASSERT_TRUE(action2 != nullptr); + action2->set_speed(SPEED_MOD2); + action2->set_timing_curve(TIMING_MODE); + action2->set_name("action2"); + + auto action3 = nom::create_action( {translate3} ); + ASSERT_TRUE(action3 != nullptr); + action3->set_speed(SPEED_MOD3); + action3->set_timing_curve(TIMING_MODE); + action3->set_name("action3"); + + auto remove_action0 = + nom::create_action(action0); + ASSERT_TRUE(remove_action0 != nullptr); + remove_action0->set_name("remove_action0"); + + auto remove_action1 = + nom::create_action(action1); + ASSERT_TRUE(remove_action1 != nullptr); + remove_action1->set_name("remove_action1"); + + auto remove_action2 = + nom::create_action(action2); + ASSERT_TRUE(remove_action2 != nullptr); + remove_action2->set_name("remove_action2"); + + auto remove_action3 = + nom::create_action(action3); + ASSERT_TRUE(remove_action3 != nullptr); + remove_action3->set_name("remove_action3"); + + EXPECT_EQ(0, this->player.num_actions() ); + this->run_action_ret = + this->player.run_action(action0, [=]() { + + EXPECT_EQ( EXPECTED_TEX0_POS, sprite0->position() ); + this->expected_action_params(action0.get(), 1); + this->expected_common_params(translate0.get(), DURATION, SPEED_MOD0); + + this->player.run_action(remove_action0, [=]() { + // NOTE: The removal of the action should free the stored texture of each + // sprite, preventing it from being rendered by invalidating the texture. + EXPECT_TRUE(sprite0 != nullptr); + EXPECT_FALSE( sprite0->valid() ); + }); + }); + EXPECT_EQ(true, this->run_action_ret) + << "Failed to queue action0"; + EXPECT_EQ(1, this->player.num_actions() ); + + this->run_action_ret = + this->player.run_action(action1, [=]() { + + EXPECT_EQ( EXPECTED_TEX1_POS, sprite1->position() ); + this->expected_action_params(action1.get(), 1); + this->expected_common_params(translate1.get(), DURATION, SPEED_MOD1); + + this->player.run_action(remove_action1, [=]() { + // NOTE: The removal of the action should free the stored texture of each + // sprite, preventing it from being rendered by invalidating the texture. + EXPECT_TRUE(sprite1 != nullptr); + EXPECT_FALSE( sprite1->valid() ); + }); + }); + EXPECT_EQ(true, this->run_action_ret) + << "Failed to queue action1"; + EXPECT_EQ(2, this->player.num_actions() ); + + this->run_action_ret = + this->player.run_action(action2, [=]() { + + EXPECT_EQ( EXPECTED_TEX2_POS, sprite2->position() ); + this->expected_action_params(action2.get(), 1); + this->expected_common_params(translate2.get(), DURATION, SPEED_MOD2); + + this->player.run_action(remove_action2, [=]() { + // NOTE: The removal of the action should free the stored texture of each + // sprite, preventing it from being rendered by invalidating the texture. + EXPECT_TRUE(sprite2 != nullptr); + EXPECT_FALSE( sprite2->valid() ); + }); + }); + EXPECT_EQ(true, this->run_action_ret) + << "Failed to queue action2"; + EXPECT_EQ(3, this->player.num_actions() ); + + this->run_action_ret = + this->player.run_action(action3, [=]() { + + EXPECT_EQ( EXPECTED_TEX3_POS, sprite3->position() ); + this->expected_action_params(action3.get(), 1); + this->expected_common_params(translate3.get(), DURATION, SPEED_MOD3); + + this->player.run_action(remove_action3, [=]() { + // NOTE: The removal of the action should free the stored texture of each + // sprite, preventing it from being rendered by invalidating the texture. + EXPECT_TRUE(sprite3 != nullptr); + EXPECT_FALSE( sprite3->valid() ); + }); + }); + EXPECT_EQ(true, this->run_action_ret) + << "Failed to queue action3"; + EXPECT_EQ(4, this->player.num_actions() ); + + this->append_update_callback( [=](float) { + + if( this->player.action_running("action0") == false && + this->player.action_running("action1") == false && + this->player.action_running("action2") == false && + this->player.action_running("action3") == false && + this->expected_min_duration(DURATION, SPEED_MOD2) == true ) + { + this->quit(); + } + }); + + this->append_render_queue( sprite0.get() ); // action0 + this->append_render_queue( sprite1.get() ); // action1 + this->append_render_queue( sprite2.get() ); // action2 + this->append_render_queue( sprite3.get() ); // action3 + this->append_frame_interval(FPS); + + EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); +} + +TEST_F(ActionTest, ConcurrentSequenceActions) +{ + // Testing parameters + const float DURATION = 1.0f; + const real32 SPEED_MOD0 = NOM_ACTION_TEST_FLAG(speed) * 1.0f; // action0(2/2) + const real32 SPEED_MOD1 = NOM_ACTION_TEST_FLAG(speed) * 2.0f; // action0(1/2) + const real32 SPEED_MOD2 = NOM_ACTION_TEST_FLAG(speed) * 2.25f; // action1(1/2) + const real32 SPEED_MOD3 = NOM_ACTION_TEST_FLAG(speed) * 1.25f; // action1(2/2) + const IActionObject::timing_curve_func TIMING_MODE = + NOM_ACTION_TEST_FLAG(timing_curve); + const uint32 FPS = NOM_ACTION_TEST_FLAG(fps); + + // Initial texture position and size + const Size2i RECT_SIZE(WINDOW_DIMS.w/4, WINDOW_DIMS.h/4); + const Point2i TRANSLATE_POS( Point2i(WINDOW_DIMS.w-RECT_SIZE.w,0) ); + const Point2i RECT0_POS(0, RECT_SIZE.h*0); + const Point2i RECT1_POS(0, RECT_SIZE.h*1); + const Point2i RECT2_POS(0, RECT_SIZE.h*2); + const Point2i RECT3_POS(0, RECT_SIZE.h*3); + const Point2i EXPECTED_TEX0_POS(TRANSLATE_POS); + const Point2i EXPECTED_TEX1_POS(TRANSLATE_POS.x, RECT_SIZE.h*1); + const Point2i EXPECTED_TEX2_POS(TRANSLATE_POS.x, RECT_SIZE.h*2); + const Point2i EXPECTED_TEX3_POS(TRANSLATE_POS.x, RECT_SIZE.h*3); + + auto rect0 = + std::make_shared(IntRect(RECT0_POS, RECT_SIZE), Color4i::Green); + ASSERT_TRUE(rect0 != nullptr); + + auto rect1 = + std::make_shared(IntRect(RECT1_POS, RECT_SIZE), Color4i::Red); + ASSERT_TRUE(rect1 != nullptr); + + auto rect2 = + std::make_shared(IntRect(RECT2_POS, RECT_SIZE), Color4i::Blue); + ASSERT_TRUE(rect2 != nullptr); + + auto rect3 = + std::make_shared(IntRect(RECT3_POS, RECT_SIZE), Color4i::Yellow); + ASSERT_TRUE(rect3 != nullptr); + + auto sprite0 = nom::make_shared_sprite( rect0->texture() ); + ASSERT_TRUE(sprite0 != nullptr); + + auto sprite1 = nom::make_shared_sprite( rect1->texture() ); + ASSERT_TRUE(sprite1 != nullptr); + + auto sprite2 = nom::make_shared_sprite( rect2->texture() ); + ASSERT_TRUE(sprite2 != nullptr); + + auto sprite3 = nom::make_shared_sprite( rect3->texture() ); + ASSERT_TRUE(sprite3 != nullptr); + + auto translate0 = + nom::create_action(sprite0, TRANSLATE_POS, DURATION); + ASSERT_TRUE(translate0 != nullptr); + + auto translate1 = + nom::create_action(sprite1, TRANSLATE_POS, DURATION); + ASSERT_TRUE(translate1 != nullptr); + + auto translate2 = + nom::create_action(sprite2, TRANSLATE_POS, DURATION); + ASSERT_TRUE(translate2 != nullptr); + + auto translate3 = + nom::create_action(sprite3, TRANSLATE_POS, DURATION); + ASSERT_TRUE(translate3 != nullptr); + + translate0->set_name("translate0"); + translate1->set_name("translate1"); + translate2->set_name("translate2"); + translate3->set_name("translate3"); + translate0->set_speed(SPEED_MOD0); + translate1->set_speed(SPEED_MOD1); + translate2->set_speed(SPEED_MOD2); + translate3->set_speed(SPEED_MOD3); + + auto action0 = + nom::create_action( {translate0, translate1} ); + ASSERT_TRUE(action0 != nullptr); + action0->set_timing_curve(TIMING_MODE); + action0->set_name("action0"); + + auto action1 = + nom::create_action( {translate2, translate3} ); + ASSERT_TRUE(action1 != nullptr); + action1->set_timing_curve(TIMING_MODE); + action1->set_name("action1"); + + auto remove_action0 = + nom::create_action(action0); + ASSERT_TRUE(remove_action0 != nullptr); + remove_action0->set_name("remove_action0"); + + auto remove_action1 = + nom::create_action(action1); + ASSERT_TRUE(remove_action1 != nullptr); + remove_action1->set_name("remove_action1"); + + EXPECT_EQ(0, this->player.num_actions() ); + this->run_action_ret = + this->player.run_action(action0, [=]() { + + EXPECT_EQ( EXPECTED_TEX0_POS, sprite0->position() ); + EXPECT_EQ( EXPECTED_TEX1_POS, sprite1->position() ); + this->expected_action_params(action0.get(), 2); + this->expected_common_params(translate0.get(), DURATION, SPEED_MOD0); + this->expected_common_params(translate1.get(), DURATION, SPEED_MOD1); + + this->player.run_action(remove_action0, [=]() { + // NOTE: The removal of the action should free the stored texture of each + // sprite, preventing it from being rendered by invalidating the texture. + EXPECT_TRUE(sprite0 != nullptr); + EXPECT_FALSE( sprite0->valid() ); + EXPECT_TRUE(sprite1 != nullptr); + EXPECT_FALSE( sprite1->valid() ); + }); + }); + EXPECT_EQ(true, this->run_action_ret) + << "Failed to queue action0"; + EXPECT_EQ(1, this->player.num_actions() ); + + this->run_action_ret = + this->player.run_action(action1, [=]() { + + EXPECT_EQ( EXPECTED_TEX2_POS, sprite2->position() ); + EXPECT_EQ( EXPECTED_TEX3_POS, sprite3->position() ); + this->expected_action_params(action1.get(), 2); + this->expected_common_params(translate2.get(), DURATION, SPEED_MOD2); + this->expected_common_params(translate3.get(), DURATION, SPEED_MOD3); + + this->player.run_action(remove_action1, [=]() { + // NOTE: The removal of the action should free the stored texture of each + // sprite, preventing it from being rendered by invalidating the texture. + EXPECT_TRUE(sprite2 != nullptr); + EXPECT_FALSE( sprite2->valid() ); + EXPECT_TRUE(sprite3 != nullptr); + EXPECT_FALSE( sprite3->valid() ); + }); + }); + EXPECT_EQ(true, this->run_action_ret) + << "Failed to queue action1"; + EXPECT_EQ(2, this->player.num_actions() ); + + this->append_update_callback( [=](float) { + + if( this->player.action_running("action0") == false && + this->player.action_running("action1") == false && + this->expected_min_duration(DURATION, SPEED_MOD2) == true ) + { + this->quit(); + } + }); + + this->append_render_queue( sprite0.get() ); // action0 + this->append_render_queue( sprite1.get() ); // action0 + this->append_render_queue( sprite2.get() ); // action1 + this->append_render_queue( sprite3.get() ); // action1 + this->append_frame_interval(FPS); + + EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); +} + +TEST_F(ActionTest, ConcurrentGroupAndSequenceActions) +{ + // Testing parameters + const float DURATION = 2.0f; + const real32 SPEED_MOD0 = NOM_ACTION_TEST_FLAG(speed) * 1.0f; // action0 + const real32 SPEED_MOD1 = NOM_ACTION_TEST_FLAG(speed) * 2.0f; // action0 + const real32 SPEED_MOD2 = NOM_ACTION_TEST_FLAG(speed) * 2.25f; // action1 + const real32 SPEED_MOD3 = NOM_ACTION_TEST_FLAG(speed) * 1.25f; // action1 + const IActionObject::timing_curve_func TIMING_MODE = + NOM_ACTION_TEST_FLAG(timing_curve); + const uint32 FPS = NOM_ACTION_TEST_FLAG(fps); + + // Initial texture position and size + const Size2i RECT_SIZE(WINDOW_DIMS.w/4, WINDOW_DIMS.h/4); + const Point2i TRANSLATE_POS( Point2i(WINDOW_DIMS.w-RECT_SIZE.w,0) ); + const Point2i RECT0_POS(0, RECT_SIZE.h*0); + const Point2i RECT1_POS(0, RECT_SIZE.h*1); + const Point2i RECT2_POS(0, RECT_SIZE.h*2); + const Point2i RECT3_POS(0, RECT_SIZE.h*3); + const Point2i EXPECTED_TEX0_POS(TRANSLATE_POS); + const Point2i EXPECTED_TEX1_POS(TRANSLATE_POS.x, RECT_SIZE.h*1); + const Point2i EXPECTED_TEX2_POS(TRANSLATE_POS.x, RECT_SIZE.h*2); + const Point2i EXPECTED_TEX3_POS(TRANSLATE_POS.x, RECT_SIZE.h*3); + + auto rect0 = + std::make_shared(IntRect(RECT0_POS, RECT_SIZE), Color4i::Green); + ASSERT_TRUE(rect0 != nullptr); + + auto rect1 = + std::make_shared(IntRect(RECT1_POS, RECT_SIZE), Color4i::Red); + ASSERT_TRUE(rect1 != nullptr); + + auto rect2 = + std::make_shared(IntRect(RECT2_POS, RECT_SIZE), Color4i::Blue); + ASSERT_TRUE(rect2 != nullptr); + + auto rect3 = + std::make_shared(IntRect(RECT3_POS, RECT_SIZE), Color4i::Yellow); + ASSERT_TRUE(rect3 != nullptr); + + auto sprite0 = nom::make_shared_sprite( rect0->texture() ); + ASSERT_TRUE(sprite0 != nullptr); + + auto sprite1 = nom::make_shared_sprite( rect1->texture() ); + ASSERT_TRUE(sprite1 != nullptr); + + auto sprite2 = nom::make_shared_sprite( rect2->texture() ); + ASSERT_TRUE(sprite2 != nullptr); + + auto sprite3 = nom::make_shared_sprite( rect3->texture() ); + ASSERT_TRUE(sprite3 != nullptr); + + auto translate0 = + nom::create_action(sprite0, TRANSLATE_POS, DURATION); + ASSERT_TRUE(translate0 != nullptr); + + auto translate1 = + nom::create_action(sprite1, TRANSLATE_POS, DURATION); + ASSERT_TRUE(translate1 != nullptr); + + auto translate2 = + nom::create_action(sprite2, TRANSLATE_POS, DURATION); + ASSERT_TRUE(translate2 != nullptr); + + auto translate3 = + nom::create_action(sprite3, TRANSLATE_POS, DURATION); + ASSERT_TRUE(translate3 != nullptr); + + translate0->set_name("translate0"); + translate1->set_name("translate1"); + translate2->set_name("translate2"); + translate3->set_name("translate3"); + translate0->set_speed(SPEED_MOD0); + translate1->set_speed(SPEED_MOD1); + translate2->set_speed(SPEED_MOD2); + translate3->set_speed(SPEED_MOD3); + + auto action0 = + nom::create_action( {translate0, translate1} ); + ASSERT_TRUE(action0 != nullptr); + action0->set_timing_curve(TIMING_MODE); + action0->set_name("action0"); + + auto action1 = + nom::create_action( {translate2, translate3} ); + ASSERT_TRUE(action1 != nullptr); + action1->set_timing_curve(TIMING_MODE); + action1->set_name("action1"); + + auto remove_action0 = + nom::create_action(action0); + ASSERT_TRUE(remove_action0 != nullptr); + remove_action0->set_name("remove_action0"); + + auto remove_action1 = + nom::create_action(action1); + ASSERT_TRUE(remove_action1 != nullptr); + remove_action1->set_name("remove_action1"); + + // EXPECT_EQ( ActionPlayer::IDLE, this->player.player_state() ); + EXPECT_EQ(0, this->player.num_actions() ); + this->run_action_ret = + this->player.run_action(action0, [=]() { + + EXPECT_EQ( EXPECTED_TEX0_POS, sprite0->position() ); + EXPECT_EQ( EXPECTED_TEX1_POS, sprite1->position() ); + this->expected_action_params(action0.get(), 2); + this->expected_common_params(translate0.get(), DURATION, SPEED_MOD0); + this->expected_common_params(translate1.get(), DURATION, SPEED_MOD1); + + this->player.run_action(remove_action0, [=]() { + // NOTE: The removal of the action should free the stored texture of each + // sprite, preventing it from being rendered by invalidating the texture. + EXPECT_TRUE(sprite0 != nullptr); + EXPECT_FALSE( sprite0->valid() ); + EXPECT_TRUE(sprite1 != nullptr); + EXPECT_FALSE( sprite1->valid() ); + }); + }); + EXPECT_EQ(true, this->run_action_ret) + << "Failed to queue action0"; + EXPECT_EQ(1, this->player.num_actions() ); + + // EXPECT_EQ( ActionPlayer::IDLE, this->player.player_state() ); + this->run_action_ret = + this->player.run_action(action1, [=]() { + + EXPECT_EQ( EXPECTED_TEX2_POS, sprite2->position() ); + EXPECT_EQ( EXPECTED_TEX3_POS, sprite3->position() ); + this->expected_action_params(action1.get(), 2); + this->expected_common_params(translate2.get(), DURATION, SPEED_MOD2); + this->expected_common_params(translate3.get(), DURATION, SPEED_MOD3); + + this->player.run_action(remove_action1, [=]() { + // NOTE: The removal of the action should free the stored texture of each + // sprite, preventing it from being rendered by invalidating the texture. + EXPECT_TRUE(sprite2 != nullptr); + EXPECT_FALSE( sprite2->valid() ); + EXPECT_TRUE(sprite3 != nullptr); + EXPECT_FALSE( sprite3->valid() ); + }); + }); + EXPECT_EQ(true, this->run_action_ret) + << "Failed to queue action1"; + EXPECT_EQ(2, this->player.num_actions() ); + + this->append_update_callback( [=](float) { + + if( this->player.action_running("action0") == false && + this->player.action_running("action1") == false && + this->expected_min_duration(DURATION, SPEED_MOD2) == true ) + { + this->quit(); + } + }); + + this->append_render_queue( sprite0.get() ); // action0 + this->append_render_queue( sprite1.get() ); // action0 + this->append_render_queue( sprite2.get() ); // action1 + this->append_render_queue( sprite3.get() ); // action1 + this->append_frame_interval(FPS); + + EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); +} + +TEST_F(ActionTest, RunActionWithName) +{ + // Testing parameters + const float DURATION = 0.0f; + const Point2i TRANSLATE_POS(Point2i::zero); + + auto translate0 = + nom::create_action(nullptr, TRANSLATE_POS, DURATION); + ASSERT_TRUE(translate0 != nullptr); + + auto action0 = + nom::create_action( {translate0} ); + ASSERT_TRUE(action0 != nullptr); + action0->set_name("action0"); + + EXPECT_EQ(0, this->player.num_actions() ); + this->run_action_ret = + this->player.run_action(action0); + EXPECT_EQ(true, this->run_action_ret) + << "Failed to queue action0"; + EXPECT_EQ(1, this->player.num_actions() ); + + EXPECT_EQ(true, this->player.action_running("action0") ); + EXPECT_EQ(1, this->player.num_actions() ); +} + +TEST_F(ActionTest, ClonedGroupAction) +{ + // Testing parameters + const real32 DURATION = 2.0f; + const real32 SPEED_MOD = NOM_ACTION_TEST_FLAG(speed); + const IActionObject::timing_curve_func TIMING_MODE = + NOM_ACTION_TEST_FLAG(timing_curve); + const Point2i TRANSLATE_POS( Point2i(200,0) ); + const uint32 FPS = NOM_ACTION_TEST_FLAG(fps); + + // Initial texture position and size + const Point2i RECT_POS(Point2i::zero); + const Size2i RECT_SIZE(WINDOW_DIMS.w/4, WINDOW_DIMS.h); + const Point2i EXPECTED_TEX_POS(TRANSLATE_POS); + + auto sprite = std::make_shared(); + ASSERT_TRUE(sprite != nullptr); + sprite->init_with_color(Color4i::Green, RECT_SIZE); + sprite->set_position(RECT_POS); + EXPECT_EQ( true, sprite->valid() ); + + auto translate = + nom::create_action(sprite, TRANSLATE_POS, DURATION); + ASSERT_TRUE(translate != nullptr); + + auto action0 = + nom::create_action( {translate} ); + ASSERT_TRUE(action0 != nullptr); + action0->set_timing_curve(TIMING_MODE); + action0->set_speed(SPEED_MOD); + action0->set_name("action0"); + + auto remove_action0 = + nom::create_action(action0); + ASSERT_TRUE(remove_action0 != nullptr); + remove_action0->set_name("remove_action0"); + + EXPECT_EQ(0, this->player.num_actions() ); + this->run_action_ret = + this->player.run_action(action0, [=]() { + + EXPECT_EQ( EXPECTED_TEX_POS, sprite->position() ); + EXPECT_EQ(1, this->player.num_actions() ); + + this->expected_action_params(action0.get(), 1); + this->expected_common_params(translate.get(), DURATION, SPEED_MOD); + + sprite->set_position(RECT_POS); + this->player.run_action(action0->clone(), [=]() { + + EXPECT_EQ( EXPECTED_TEX_POS, sprite->position() ); + EXPECT_EQ(1, this->player.num_actions() ); + + this->expected_action_params(action0.get(), 1); + this->expected_common_params(translate.get(), DURATION, SPEED_MOD); + + this->player.run_action(remove_action0, [=]() { + ASSERT_TRUE(sprite != nullptr); + EXPECT_FALSE( sprite->valid() ); + }); + }); + }); + EXPECT_EQ(true, this->run_action_ret) + << "Failed to queue the action!"; + EXPECT_EQ(1, this->player.num_actions() ); + + this->append_update_callback( [=](real32) { + if( this->expected_min_duration(DURATION*2, SPEED_MOD) == true ) { + this->quit(); + } + }); + + this->append_render_queue( sprite.get() ); + this->append_frame_interval(FPS); + + EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); +} + +TEST_F(ActionTest, ClonedSequenceAction) +{ + // Testing parameters + const real32 DURATION = 2.0f; + const real32 SPEED_MOD = NOM_ACTION_TEST_FLAG(speed); + const IActionObject::timing_curve_func TIMING_MODE = + NOM_ACTION_TEST_FLAG(timing_curve); + const Point2i TRANSLATE_POS( Point2i(200,0) ); + const uint32 FPS = NOM_ACTION_TEST_FLAG(fps); + + // Initial texture position and size + const Point2i RECT_POS(Point2i::zero); + const Size2i RECT_SIZE(WINDOW_DIMS.w/4, WINDOW_DIMS.h); + const Point2i EXPECTED_TEX_POS(TRANSLATE_POS); + + auto sprite = std::make_shared(); + ASSERT_TRUE(sprite != nullptr); + sprite->init_with_color(Color4i::Green, RECT_SIZE); + sprite->set_position(RECT_POS); + EXPECT_EQ( true, sprite->valid() ); + + auto translate = + nom::create_action(sprite, TRANSLATE_POS, DURATION); + ASSERT_TRUE(translate != nullptr); + + auto action0 = + nom::create_action( {translate} ); + ASSERT_TRUE(action0 != nullptr); + action0->set_timing_curve(TIMING_MODE); + action0->set_speed(SPEED_MOD); + action0->set_name("action0"); + + auto remove_action0 = + nom::create_action(action0); + ASSERT_TRUE(remove_action0 != nullptr); + remove_action0->set_name("remove_action0"); + + EXPECT_EQ(0, this->player.num_actions() ); + this->run_action_ret = + this->player.run_action(action0, [=]() { + + EXPECT_EQ( EXPECTED_TEX_POS, sprite->position() ); + EXPECT_EQ(1, this->player.num_actions() ); + + this->expected_action_params(action0.get(), 1); + this->expected_common_params(translate.get(), DURATION, SPEED_MOD); + + sprite->set_position(RECT_POS); + this->player.run_action(action0->clone(), [=]() { + + EXPECT_EQ( EXPECTED_TEX_POS, sprite->position() ); + EXPECT_EQ(1, this->player.num_actions() ); + + this->expected_action_params(action0.get(), 1); + this->expected_common_params(translate.get(), DURATION, SPEED_MOD); + + this->player.run_action(remove_action0, [=]() { + ASSERT_TRUE(sprite != nullptr); + EXPECT_FALSE( sprite->valid() ); + }); + }); + }); + EXPECT_EQ(true, this->run_action_ret) + << "Failed to queue the action!"; + EXPECT_EQ(1, this->player.num_actions() ); + + this->append_update_callback( [=](real32) { + if( this->expected_min_duration(DURATION*2, SPEED_MOD) == true ) { + this->quit(); + } + }); + + this->append_render_queue( sprite.get() ); + this->append_frame_interval(FPS); + + EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); +} + +TEST_F(ActionTest, ClonedReversedAction) +{ + // Testing parameters + const real32 DURATION = 2.0f; + const real32 SPEED_MOD = NOM_ACTION_TEST_FLAG(speed); + const IActionObject::timing_curve_func TIMING_MODE = + NOM_ACTION_TEST_FLAG(timing_curve); + const Point2i TRANSLATE_POS( Point2i(200,0) ); + const uint32 FPS = NOM_ACTION_TEST_FLAG(fps); + + // Initial texture position and size + const Size2i RECT_SIZE(WINDOW_DIMS.w/4, WINDOW_DIMS.h); + const Point2i RECT_POS(WINDOW_DIMS.w-RECT_SIZE.w, 0); + const Point2i EXPECTED_TEX_POS(RECT_POS-TRANSLATE_POS); + + auto sprite = std::make_shared(); + ASSERT_TRUE(sprite != nullptr); + sprite->init_with_color(Color4i::Green, RECT_SIZE); + sprite->set_position(RECT_POS); + EXPECT_EQ( true, sprite->valid() ); + + auto translate = + nom::create_action(sprite, TRANSLATE_POS, DURATION); + ASSERT_TRUE(translate != nullptr); + + auto action0 = + nom::create_action(translate); + ASSERT_TRUE(action0 != nullptr); + action0->set_timing_curve(TIMING_MODE); + action0->set_speed(SPEED_MOD); + action0->set_name("action0"); + + auto remove_action0 = + nom::create_action(action0); + ASSERT_TRUE(remove_action0 != nullptr); + remove_action0->set_name("remove_action0"); + + EXPECT_EQ(0, this->player.num_actions() ); + this->run_action_ret = + this->player.run_action(action0, [=]() { + + EXPECT_EQ( EXPECTED_TEX_POS, sprite->position() ); + EXPECT_EQ(1, this->player.num_actions() ); + this->expected_common_params(translate.get(), DURATION, SPEED_MOD); + + sprite->set_position(RECT_POS); + this->player.run_action(action0->clone(), [=]() { + + EXPECT_EQ( EXPECTED_TEX_POS, sprite->position() ); + EXPECT_EQ(1, this->player.num_actions() ); + this->expected_common_params(translate.get(), DURATION, SPEED_MOD); + + this->player.run_action(remove_action0, [=]() { + ASSERT_TRUE(sprite != nullptr); + EXPECT_FALSE( sprite->valid() ); + }); + }); + }); + EXPECT_EQ(true, this->run_action_ret) + << "Failed to queue the action!"; + EXPECT_EQ(1, this->player.num_actions() ); + + this->append_update_callback( [=](real32) { + if( this->expected_min_duration(DURATION*2, SPEED_MOD) == true ) { + this->quit(); + } + }); + + this->append_render_queue( sprite.get() ); + this->append_frame_interval(FPS); + + EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); +} + +TEST_F(ActionTest, ClonedRepeatForAction) +{ + // Testing parameters + const real32 DURATION = 2.0f; + const real32 SPEED_MOD = NOM_ACTION_TEST_FLAG(speed); + const IActionObject::timing_curve_func TIMING_MODE = + NOM_ACTION_TEST_FLAG(timing_curve); + const Point2i TRANSLATE_POS( Point2i(200,0) ); + const uint32 FPS = NOM_ACTION_TEST_FLAG(fps); + const nom::size_type NUM_REPEATS = 1; + + // Initial texture position and size + const Point2i RECT_POS(Point2i::zero); + const Size2i RECT_SIZE(WINDOW_DIMS.w/4, WINDOW_DIMS.h); + const Point2i EXPECTED_TEX_POS(TRANSLATE_POS); + + auto sprite = std::make_shared(); + ASSERT_TRUE(sprite != nullptr); + sprite->init_with_color(Color4i::Green, RECT_SIZE); + sprite->set_position(RECT_POS); + EXPECT_EQ( true, sprite->valid() ); + + auto translate = + nom::create_action(sprite, TRANSLATE_POS, DURATION); + ASSERT_TRUE(translate != nullptr); + + auto action0 = + nom::create_action(translate, NUM_REPEATS); + ASSERT_TRUE(action0 != nullptr); + action0->set_timing_curve(TIMING_MODE); + action0->set_speed(SPEED_MOD); + action0->set_name("action0"); + + auto remove_action0 = + nom::create_action(action0); + ASSERT_TRUE(remove_action0 != nullptr); + remove_action0->set_name("remove_action0"); + + EXPECT_EQ(0, this->player.num_actions() ); + this->run_action_ret = + this->player.run_action(action0, [=]() { + + EXPECT_EQ( EXPECTED_TEX_POS, sprite->position() ); + EXPECT_EQ(1, this->player.num_actions() ); + + this->expected_repeat_params(action0.get(), NUM_REPEATS, "num_repeats" ); + this->expected_common_params(translate.get(), DURATION, SPEED_MOD); + + sprite->set_position(RECT_POS); + this->player.run_action(action0->clone(), [=]() { + + EXPECT_EQ( EXPECTED_TEX_POS, sprite->position() ); + EXPECT_EQ(1, this->player.num_actions() ); + + this->expected_repeat_params(action0.get(), NUM_REPEATS, "num_repeats" ); + this->expected_common_params(translate.get(), DURATION, SPEED_MOD); + + this->player.run_action(remove_action0, [=]() { + ASSERT_TRUE(sprite != nullptr); + EXPECT_FALSE( sprite->valid() ); + }); + }); + }); + EXPECT_EQ(true, this->run_action_ret) + << "Failed to queue the action!"; + EXPECT_EQ(1, this->player.num_actions() ); + + this->append_update_callback( [=](real32) { + if( this->expected_min_duration(DURATION*2, SPEED_MOD) == true ) { + this->quit(); + } + }); + + this->append_render_queue( sprite.get() ); + this->append_frame_interval(FPS); + + EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); +} + +} // namespace nom diff --git a/tests/src/actions/ActionTest_AlphaBlending.cpp b/tests/src/actions/ActionTest_AlphaBlending.cpp new file mode 100644 index 00000000..eb2bb709 --- /dev/null +++ b/tests/src/actions/ActionTest_AlphaBlending.cpp @@ -0,0 +1,645 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#include "nomlib/tests/actions/ActionTest.hpp" + +namespace nom { + +TEST_F(ActionTest, FadeInAction) +{ + // Testing parameters + const real32 DURATION = 2.0f; + const real32 SPEED_MOD = NOM_ACTION_TEST_FLAG(speed); + const IActionObject::timing_curve_func TIMING_MODE = + NOM_ACTION_TEST_FLAG(timing_curve); + const uint32 FPS = NOM_ACTION_TEST_FLAG(fps); + const uint8 INITIAL_ALPHA = Color4i::ALPHA_TRANSPARENT; + const uint8 EXPECTED_ALPHA = Color4i::ALPHA_OPAQUE; + + // Initial texture position and size + const Point2i TEX_POS(Point2i::zero); + const Size2i TEX_SIZE(256, 256); + + auto sprite = + std::make_shared(); + ASSERT_TRUE(sprite != nullptr); + EXPECT_EQ(true, sprite->init_with_color(Color4i::Blue, TEX_SIZE) ); + sprite->set_position(TEX_POS); + sprite->set_alpha(INITIAL_ALPHA); + sprite->set_color_blend_mode(BLEND_MODE_BLEND); + + auto fade_in = + nom::create_action(sprite, DURATION); + ASSERT_TRUE(fade_in != nullptr); + fade_in->set_name("fade_in"); + + auto action0 = + nom::create_action( {fade_in} ); + ASSERT_TRUE(action0 != nullptr); + action0->set_timing_curve(TIMING_MODE); + action0->set_speed(SPEED_MOD); + action0->set_name("action0"); + + EXPECT_EQ(0, this->player.num_actions() ); + this->run_action_ret = + this->player.run_action(action0, [=]() { + + this->expected_alpha_in_params( fade_in.get(), EXPECTED_ALPHA, + sprite.get() ); + + EXPECT_EQ(1, this->player.num_actions() ); + + this->expected_action_params(action0.get(), 1, nom::UnitTest::test_name() ); + this->expected_common_params(fade_in.get(), DURATION, SPEED_MOD); + }); + EXPECT_EQ(true, this->run_action_ret) + << "Failed to queue action0"; + EXPECT_EQ(1, this->player.num_actions() ); + + this->append_update_callback( [=](real32) mutable { + nom::set_alignment( sprite.get(), Point2i::zero, WINDOW_DIMS, + Anchor::MiddleCenter ); + }); + + this->append_update_callback( [=](real32) { + if( this->expected_min_duration(DURATION, SPEED_MOD) == true ) { + this->quit(); + } + }); + + this->append_render_queue( sprite.get() ); + this->append_frame_interval(FPS); + + EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); +} + +TEST_F(ActionTest, FadeInActionFromNonTransparentAlpha) +{ + // Testing parameters + const real32 DURATION = 1.0f; + const real32 SPEED_MOD = NOM_ACTION_TEST_FLAG(speed); + const IActionObject::timing_curve_func TIMING_MODE = + NOM_ACTION_TEST_FLAG(timing_curve); + const uint32 FPS = NOM_ACTION_TEST_FLAG(fps); + const uint8 INITIAL_ALPHA = (Color4i::ALPHA_OPAQUE / 2); + const uint8 EXPECTED_ALPHA = Color4i::ALPHA_OPAQUE; + + // Initial texture position and size + const Point2i TEX_POS(Point2i::zero); + const Size2i TEX_SIZE(256, 256); + + auto sprite = + std::make_shared(); + ASSERT_TRUE(sprite != nullptr); + EXPECT_EQ(true, sprite->init_with_color(Color4i::Blue, TEX_SIZE) ); + sprite->set_position(TEX_POS); + sprite->set_alpha(INITIAL_ALPHA); + sprite->set_color_blend_mode(BLEND_MODE_BLEND); + + auto fade_in = + nom::create_action(sprite, DURATION); + ASSERT_TRUE(fade_in != nullptr); + + auto action0 = + nom::create_action( {fade_in} ); + ASSERT_TRUE(action0 != nullptr); + action0->set_timing_curve(TIMING_MODE); + action0->set_speed(SPEED_MOD); + action0->set_name("action0"); + + EXPECT_EQ(0, this->player.num_actions() ); + this->run_action_ret = + this->player.run_action(action0, [=]() { + + this->expected_alpha_in_params( fade_in.get(), EXPECTED_ALPHA, + sprite.get() ); + + EXPECT_EQ(1, this->player.num_actions() ); + this->expected_action_params(action0.get(), 1, nom::UnitTest::test_name() ); + this->expected_common_params(fade_in.get(), DURATION, SPEED_MOD); + }); + EXPECT_EQ(true, this->run_action_ret) + << "Failed to queue action0"; + EXPECT_EQ(1, this->player.num_actions() ); + + this->append_update_callback( [=](real32) mutable { + nom::set_alignment( sprite.get(), Point2i::zero, WINDOW_DIMS, + Anchor::MiddleCenter ); + }); + + this->append_update_callback( [=](real32) { + if( this->expected_min_duration(DURATION, SPEED_MOD) == true ) { + this->quit(); + } + }); + + this->append_render_queue( sprite.get() ); + this->append_frame_interval(FPS); + + EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); +} + +TEST_F(ActionTest, FadeOutAction) +{ + // Testing parameters + const real32 DURATION = 2.0f; + const real32 SPEED_MOD = NOM_ACTION_TEST_FLAG(speed); + const IActionObject::timing_curve_func TIMING_MODE = + NOM_ACTION_TEST_FLAG(timing_curve); + const uint32 FPS = NOM_ACTION_TEST_FLAG(fps); + const uint8 INITIAL_ALPHA = Color4i::ALPHA_OPAQUE; + const uint8 EXPECTED_ALPHA = Color4i::ALPHA_TRANSPARENT; + + // Initial texture position and size + const Point2i TEX_POS(Point2i::zero); + const Size2i TEX_SIZE(256, 256); + + auto sprite = + std::make_shared(); + ASSERT_TRUE(sprite != nullptr); + EXPECT_EQ(true, sprite->init_with_color(Color4i::Blue, TEX_SIZE) ); + sprite->set_position(TEX_POS); + sprite->set_alpha(INITIAL_ALPHA); + sprite->set_color_blend_mode(BLEND_MODE_BLEND); + + auto fade_out = + nom::create_action(sprite, DURATION); + ASSERT_TRUE(fade_out != nullptr); + + auto action0 = + nom::create_action( {fade_out} ); + ASSERT_TRUE(action0 != nullptr); + action0->set_timing_curve(TIMING_MODE); + action0->set_speed(SPEED_MOD); + action0->set_name("action0"); + + EXPECT_EQ(0, this->player.num_actions() ); + + this->run_action_ret = + this->player.run_action(action0, [=]() { + + EXPECT_EQ(1, this->player.num_actions() ); + this->expected_alpha_out_params( fade_out.get(), EXPECTED_ALPHA, + sprite.get() ); + + this->expected_action_params(action0.get(), 1, nom::UnitTest::test_name() ); + this->expected_common_params(fade_out.get(), DURATION, SPEED_MOD); + }); + EXPECT_EQ(true, this->run_action_ret) + << "Failed to queue action0"; + + EXPECT_EQ(1, this->player.num_actions() ); + + this->append_update_callback( [=](real32) mutable { + nom::set_alignment( sprite.get(), Point2i::zero, WINDOW_DIMS, + Anchor::MiddleCenter ); + }); + + this->append_update_callback( [=](real32) { + if( this->expected_min_duration(DURATION, SPEED_MOD) == true ) { + this->quit(); + } + }); + + this->append_render_queue( sprite.get() ); + this->append_frame_interval(FPS); + + EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); +} + +TEST_F(ActionTest, FadeOutActionFromNonOpaqueAlpha) +{ + // Testing parameters + const real32 DURATION = 1.0f; + const real32 SPEED_MOD = NOM_ACTION_TEST_FLAG(speed); + const IActionObject::timing_curve_func TIMING_MODE = + NOM_ACTION_TEST_FLAG(timing_curve); + const uint32 FPS = NOM_ACTION_TEST_FLAG(fps); + const uint8 INITIAL_ALPHA = (Color4i::ALPHA_OPAQUE / 2); + const uint8 EXPECTED_ALPHA = Color4i::ALPHA_TRANSPARENT; + + // Initial texture position and size + const Point2i TEX_POS(Point2i::zero); + const Size2i TEX_SIZE(256, 256); + + auto sprite = + std::make_shared(); + ASSERT_TRUE(sprite != nullptr); + EXPECT_EQ(true, sprite->init_with_color(Color4i::Blue, TEX_SIZE) ); + sprite->set_position(TEX_POS); + sprite->set_alpha(INITIAL_ALPHA); + sprite->set_color_blend_mode(BLEND_MODE_BLEND); + + auto fade_out = + nom::create_action(sprite, DURATION); + ASSERT_TRUE(fade_out != nullptr); + + auto action0 = + nom::create_action( {fade_out} ); + ASSERT_TRUE(action0 != nullptr); + action0->set_timing_curve(TIMING_MODE); + action0->set_speed(SPEED_MOD); + action0->set_name("action0"); + + EXPECT_EQ(0, this->player.num_actions() ); + + this->run_action_ret = + this->player.run_action(action0, [=]() { + + this->expected_alpha_out_params( fade_out.get(), EXPECTED_ALPHA, + sprite.get() ); + + EXPECT_EQ(1, this->player.num_actions() ); + this->expected_action_params(action0.get(), 1, nom::UnitTest::test_name() ); + this->expected_common_params(fade_out.get(), DURATION, SPEED_MOD); + }); + EXPECT_EQ(true, this->run_action_ret) + << "Failed to queue action0"; + EXPECT_EQ(1, this->player.num_actions() ); + + this->append_update_callback( [=](real32) mutable { + nom::set_alignment( sprite.get(), Point2i::zero, WINDOW_DIMS, + Anchor::MiddleCenter ); + }); + + this->append_update_callback( [=](real32) { + if( this->expected_min_duration(DURATION, SPEED_MOD) == true ) { + this->quit(); + } + }); + + this->append_render_queue( sprite.get() ); + this->append_frame_interval(FPS); + + EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); +} + +TEST_F(ActionTest, FadeAlphaByAction) +{ + // Testing parameters + const real32 DURATION = 2.0f; + const real32 SPEED_MOD = NOM_ACTION_TEST_FLAG(speed); + const IActionObject::timing_curve_func TIMING_MODE = + NOM_ACTION_TEST_FLAG(timing_curve); + const uint32 FPS = NOM_ACTION_TEST_FLAG(fps); + const uint8 INITIAL_ALPHA = Color4i::ALPHA_TRANSPARENT; + const real32 FADE_BY = 129; + const uint8 EXPECTED_ALPHA = (INITIAL_ALPHA + fabs(FADE_BY) ); + + // Initial texture position and size + const Point2i TEX_POS(Point2i::zero); + const Size2i TEX_SIZE(256, 256); + + auto sprite = + std::make_shared(); + ASSERT_TRUE(sprite != nullptr); + EXPECT_EQ(true, sprite->init_with_color(Color4i::Blue, TEX_SIZE) ); + sprite->set_position(TEX_POS); + sprite->set_alpha(INITIAL_ALPHA); + sprite->set_color_blend_mode(BLEND_MODE_BLEND); + + auto fade_by = + nom::create_action(sprite, FADE_BY, DURATION); + ASSERT_TRUE(fade_by != nullptr); + + auto action0 = + nom::create_action( {fade_by} ); + ASSERT_TRUE(action0 != nullptr); + action0->set_timing_curve(TIMING_MODE); + action0->set_speed(SPEED_MOD); + action0->set_name("action0"); + + EXPECT_EQ(0, this->player.num_actions() ); + this->run_action_ret = + this->player.run_action(action0, [=]() { + + this->expected_alpha_by_params( fade_by.get(), EXPECTED_ALPHA, + sprite.get() ); + EXPECT_EQ(1, this->player.num_actions() ); + + this->expected_action_params(action0.get(), 1, nom::UnitTest::test_name() ); + this->expected_common_params(fade_by.get(), DURATION, SPEED_MOD); + }); + EXPECT_EQ(true, this->run_action_ret) + << "Failed to queue action0"; + EXPECT_EQ(1, this->player.num_actions() ); + + this->append_update_callback( [=](real32) mutable { + nom::set_alignment( sprite.get(), Point2i::zero, WINDOW_DIMS, + Anchor::MiddleCenter ); + }); + + this->append_update_callback( [=](real32) { + if( this->expected_min_duration(DURATION, SPEED_MOD) == true ) { + this->quit(); + } + }); + + this->append_render_queue( sprite.get() ); + this->append_frame_interval(FPS); + + EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); +} + +TEST_F(ActionTest, FadeAlphaByActionFromNonOpaqueAlpha) +{ + // Testing parameters + const real32 DURATION = 2.0f; + const real32 SPEED_MOD = NOM_ACTION_TEST_FLAG(speed); + const IActionObject::timing_curve_func TIMING_MODE = + NOM_ACTION_TEST_FLAG(timing_curve); + const uint32 FPS = NOM_ACTION_TEST_FLAG(fps); + const uint8 INITIAL_ALPHA = (Color4i::ALPHA_OPAQUE / 4); + const real32 FADE_BY = 129; + const uint8 EXPECTED_ALPHA = (INITIAL_ALPHA + FADE_BY); + + // Initial texture position and size + const Point2i TEX_POS(Point2i::zero); + const Size2i TEX_SIZE(256, 256); + + auto sprite = + std::make_shared(); + ASSERT_TRUE(sprite != nullptr); + EXPECT_EQ(true, sprite->init_with_color(Color4i::Blue, TEX_SIZE) ); + sprite->set_position(TEX_POS); + sprite->set_alpha(INITIAL_ALPHA); + sprite->set_color_blend_mode(BLEND_MODE_BLEND); + + auto fade_by = + nom::create_action(sprite, FADE_BY, DURATION); + ASSERT_TRUE(fade_by != nullptr); + + auto action0 = + nom::create_action( {fade_by} ); + ASSERT_TRUE(action0 != nullptr); + action0->set_timing_curve(TIMING_MODE); + action0->set_speed(SPEED_MOD); + action0->set_name("action0"); + + EXPECT_EQ(0, this->player.num_actions() ); + this->run_action_ret = + this->player.run_action(action0, [=]() { + + EXPECT_EQ(1, this->player.num_actions() ); + this->expected_alpha_by_params( fade_by.get(), EXPECTED_ALPHA, + sprite.get() ); + this->expected_action_params(action0.get(), 1, nom::UnitTest::test_name() ); + this->expected_common_params(fade_by.get(), DURATION, SPEED_MOD); + }); + EXPECT_EQ(true, this->run_action_ret) + << "Failed to queue action0"; + EXPECT_EQ(1, this->player.num_actions() ); + + this->append_update_callback( [=](real32) mutable { + nom::set_alignment( sprite.get(), Point2i::zero, WINDOW_DIMS, + Anchor::MiddleCenter ); + }); + + this->append_update_callback( [=](real32) { + if( this->expected_min_duration(DURATION, SPEED_MOD) == true ) { + this->quit(); + } + }); + + this->append_render_queue( sprite.get() ); + this->append_frame_interval(FPS); + + EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); +} + +TEST_F(ActionTest, FadeAlphaByActionWithNegativeValue) +{ + // Testing parameters + const real32 DURATION = 2.0f; + const real32 SPEED_MOD = NOM_ACTION_TEST_FLAG(speed); + const IActionObject::timing_curve_func TIMING_MODE = + NOM_ACTION_TEST_FLAG(timing_curve); + const uint32 FPS = NOM_ACTION_TEST_FLAG(fps); + const uint8 INITIAL_ALPHA = Color4i::ALPHA_OPAQUE; + const real32 FADE_BY = -129; + const uint8 EXPECTED_ALPHA = (INITIAL_ALPHA - fabs(FADE_BY) ); + + // Initial texture position and size + const Point2i TEX_POS(Point2i::zero); + const Size2i TEX_SIZE(256, 256); + + auto sprite = + std::make_shared(); + ASSERT_TRUE(sprite != nullptr); + EXPECT_EQ(true, sprite->init_with_color(Color4i::Blue, TEX_SIZE) ); + sprite->set_position(TEX_POS); + sprite->set_alpha(INITIAL_ALPHA); + sprite->set_color_blend_mode(BLEND_MODE_BLEND); + + auto fade_by = + nom::create_action(sprite, FADE_BY, DURATION); + ASSERT_TRUE(fade_by != nullptr); + + auto action0 = + nom::create_action( {fade_by} ); + ASSERT_TRUE(action0 != nullptr); + action0->set_timing_curve(TIMING_MODE); + action0->set_speed(SPEED_MOD); + action0->set_name("action0"); + + EXPECT_EQ(0, this->player.num_actions() ); + this->run_action_ret = + this->player.run_action(action0, [=]() { + + EXPECT_EQ(1, this->player.num_actions() ); + this->expected_alpha_by_params( fade_by.get(), EXPECTED_ALPHA, + sprite.get() ); + + this->expected_action_params(action0.get(), 1, nom::UnitTest::test_name() ); + this->expected_common_params(fade_by.get(), DURATION, SPEED_MOD); + }); + EXPECT_EQ(true, this->run_action_ret) + << "Failed to queue action0"; + EXPECT_EQ(1, this->player.num_actions() ); + + this->append_update_callback( [=](real32) mutable { + nom::set_alignment( sprite.get(), Point2i::zero, WINDOW_DIMS, + Anchor::MiddleCenter ); + }); + + this->append_update_callback( [=](real32) { + if( this->expected_min_duration(DURATION, SPEED_MOD) == true ) { + this->quit(); + } + }); + + this->append_render_queue( sprite.get() ); + this->append_frame_interval(FPS); + + EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); +} + +TEST_F(ActionTest, AlphaBlendingDemo) +{ + // Testing parameters + const real32 DURATION = 2.5f; // 5s total duration due to x2 sequences + const real32 SPEED_MOD = NOM_ACTION_TEST_FLAG(speed); + const IActionObject::timing_curve_func TIMING_MODE = + NOM_ACTION_TEST_FLAG(timing_curve); + const uint32 FPS = NOM_ACTION_TEST_FLAG(fps); + + // Initial texture position and size + const Point2i RECT_POS(Point2i::zero); + const Size2i RECT_SIZE(256, 256); + + // NOTE: action0 init + + auto bg_sprite_tex = std::make_shared(); + ASSERT_TRUE(bg_sprite_tex != nullptr); + if( bg_sprite_tex->load( resources[0].path() + "backdrop.png" ) == false ) { + FAIL() << "Could not load 'backdrop.png' input file from " + << resources[0].path(); + } + + auto bg_sprite = std::make_shared(); + ASSERT_TRUE(bg_sprite != nullptr); + EXPECT_EQ(true, bg_sprite->set_texture(bg_sprite_tex) ); + bg_sprite->set_alpha(Color4i::ALPHA_OPAQUE); + // Stretched dimensions to cover entire window + bg_sprite->set_size(WINDOW_DIMS); + + auto fade_bg_sprite_out_action = + nom::create_action(bg_sprite, DURATION); + ASSERT_TRUE(fade_bg_sprite_out_action != nullptr); + + auto action0 = + nom::create_action( {fade_bg_sprite_out_action} ); + ASSERT_TRUE(action0 != nullptr); + action0->set_timing_curve(TIMING_MODE); + action0->set_speed(SPEED_MOD); + action0->set_name("fade_bg_sprite_out"); + + // NOTE: action1 init + + auto magenta_rect = + std::make_shared( IntRect(RECT_POS, RECT_SIZE), + Color4i::Magenta ); + ASSERT_TRUE(magenta_rect != nullptr); + + auto blue_rect = + std::make_shared( IntRect(RECT_POS, RECT_SIZE), + Color4i::Blue ); + ASSERT_TRUE(blue_rect != nullptr); + + auto magenta_sprite = + std::make_shared(); + ASSERT_TRUE(magenta_sprite != nullptr); + EXPECT_EQ(true, magenta_sprite->set_texture( magenta_rect->texture() ) ); + magenta_sprite->set_alpha(Color4i::ALPHA_TRANSPARENT); + magenta_sprite->set_color_blend_mode(BLEND_MODE_BLEND); + + auto blue_sprite = + std::make_shared(); + ASSERT_TRUE(blue_sprite != nullptr); + EXPECT_EQ(true, blue_sprite->set_texture( blue_rect->texture() ) ); + blue_sprite->set_alpha(Color4i::ALPHA_OPAQUE); + blue_sprite->set_color_blend_mode(BLEND_MODE_BLEND); + + auto fade_magenta_sprite_in_action = + nom::create_action(magenta_sprite, DURATION); + ASSERT_TRUE(fade_magenta_sprite_in_action != nullptr); + + auto fade_blue_sprite_out = + nom::create_action(blue_sprite, DURATION); + ASSERT_TRUE(fade_blue_sprite_out != nullptr); + + auto action1 = + nom::create_action( { + fade_magenta_sprite_in_action, fade_blue_sprite_out} ); + ASSERT_TRUE(action1 != nullptr); + action1->set_timing_curve(TIMING_MODE); + action1->set_speed(SPEED_MOD); + action1->set_name("action1"); + + EXPECT_EQ(0, this->player.num_actions() ); + this->run_action_ret = + this->player.run_action(action0, [=]() { + + EXPECT_EQ(2, this->player.num_actions() ); + this->expected_action_params(action0.get(), 1); + this->expected_common_params( fade_bg_sprite_out_action.get(), DURATION, + SPEED_MOD ); + this->expected_alpha_out_params( fade_bg_sprite_out_action.get(), + Color4i::ALPHA_TRANSPARENT, + bg_sprite.get(), "fade_bg_sprite_out" ); + + // action1 + this->expected_alpha_in_params( fade_magenta_sprite_in_action.get(), + Color4i::ALPHA_OPAQUE, + magenta_sprite.get(), + "fade_magenta_sprite_in" ); + }); + EXPECT_EQ(true, this->run_action_ret) + << "Failed to enqueue action0!"; + EXPECT_EQ(1, this->player.num_actions() ); + + this->run_action_ret = + this->player.run_action(action1, [=]() { + + EXPECT_EQ(1, this->player.num_actions() ); + this->expected_action_params(action1.get(), 2); + this->expected_common_params( fade_magenta_sprite_in_action.get(), + DURATION, SPEED_MOD ); + this->expected_common_params( fade_blue_sprite_out.get(), DURATION, + SPEED_MOD ); + this->expected_alpha_out_params( fade_blue_sprite_out.get(), + Color4i::ALPHA_TRANSPARENT, + blue_sprite.get(), + "fade_blue_sprite_out" ); + }); + EXPECT_EQ(true, this->run_action_ret) + << "Failed to enqueue fade_blue_rect!"; + EXPECT_EQ(2, this->player.num_actions() ); + + this->append_update_callback( [=](real32) mutable { + nom::set_alignment( magenta_sprite.get(), Point2i::zero, WINDOW_DIMS, + Anchor::MiddleCenter ); + nom::set_alignment( blue_sprite.get(), Point2i::zero, WINDOW_DIMS, + Anchor::MiddleCenter ); + }); + + this->append_update_callback( [=](real32) { + if( this->expected_min_duration(DURATION, SPEED_MOD) == true ) { + this->quit(); + } + }); + + this->append_render_queue( bg_sprite.get() ); + this->append_render_queue( magenta_sprite.get() ); + this->append_render_queue( blue_sprite.get() ); + this->append_frame_interval(FPS); + + EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); +} + +} // namespace nom diff --git a/tests/src/actions/ActionTest_Demos.cpp b/tests/src/actions/ActionTest_Demos.cpp new file mode 100644 index 00000000..d854f3ab --- /dev/null +++ b/tests/src/actions/ActionTest_Demos.cpp @@ -0,0 +1,747 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#include "nomlib/tests/actions/ActionTest.hpp" + +namespace nom { + +struct test_action +{ + std::shared_ptr sprite; + std::shared_ptr action; + std::shared_ptr container; + Point2i sprite_pos = Point2i::null; + real32 speed = 1.0f; +}; + +typedef std::map test_action_list; + +/// \brief This test is intended to simulate a worst-case scenario, i.e.: a +/// large number of objects enqueued and deallocated at the same time. +/// +/// \note When building this test under Windows, iterator debugging can slow +/// this test down massively! See also: _ITERATOR_DEBUG_LEVEL=0 +TEST_F(ActionTest, RainingRectsStressTest) +{ + // Testing parameters + const real32 DURATION = 2.0f; + IActionObject::timing_curve_func timing_mode = nullptr; + const uint32 FPS = NOM_ACTION_TEST_FLAG(fps); + const real32 MIN_SPEED_MOD = DURATION; + const real32 MAX_SPEED_MOD = MIN_SPEED_MOD + NOM_ACTION_TEST_FLAG(speed); + const nom::size_type NUM_REPEATS = 4; + const nom::size_type NUM_OBJECTS = NOM_ACTION_TEST_FLAG(num_objects); + + const Size2i MIN_RECT_SIZE(8, 8); + const Size2i MAX_RECT_SIZE(16, 16); + + // Position delta applied over duration + const Point2i TRANSLATE_POS( Point2i(0, WINDOW_DIMS.h-MAX_RECT_SIZE.h) ); + + if( NOM_ACTION_TEST_FLAG(timing_mode_str) != "linear_ease_in_out" ) { + timing_mode = NOM_ACTION_TEST_FLAG(timing_curve); + } else { + // Default used timing curve when the end-user does not override us + timing_mode = nom::Bounce::ease_in_out; + } + + if( NOM_ACTION_TEST_FLAG(enable_vsync) == false ) { + NOM_LOG_WARN( NOM_LOG_CATEGORY_APPLICATION, + "Passing --enable-vsync may help boost test performance", + "considerably!" ); + } + + auto rand_seed = nom::ticks(); + nom::init_rand(rand_seed); + NOM_LOG_DEBUG(NOM_LOG_CATEGORY_ACTION, "Random seed value:", rand_seed); + + test_action_list actions; + + // drawables generation + int x_offset = 0; + for( auto idx = 0; idx != NUM_OBJECTS; ++idx ) { + + Point2i pos(x_offset, 0); + Size2i dims(Size2i::zero); + Color4i color; + + int16 red = nom::uniform_real_rand(0.0f,255.0f); + int16 green = nom::uniform_real_rand(0.0f,255.0f); + int16 blue = nom::uniform_real_rand(0.0f,255.0f); + color = Color4i(red, green, blue); + + dims.w = + nom::uniform_real_rand(MIN_RECT_SIZE.w, MAX_RECT_SIZE.w); + dims.h = + nom::uniform_real_rand(MIN_RECT_SIZE.h, MAX_RECT_SIZE.h); + + if( x_offset >= 640 ) { + x_offset = 0; + } else { + x_offset += dims.w + (dims.w / 2); + } + + IntRect rect_bounds(pos, dims); + + auto rect = + new Rectangle(rect_bounds, color); + ASSERT_TRUE(rect != nullptr); + + auto sprite = nom::make_shared_sprite( rect->texture() ); + ASSERT_TRUE(sprite != nullptr); + NOM_DELETE_PTR(rect); + + actions[idx].sprite_pos = sprite->position(); + actions[idx].sprite = sprite; + } + + for( auto idx = 0; idx != NUM_OBJECTS; ++idx ) { + auto translate = + nom::create_action(actions[idx].sprite, TRANSLATE_POS, DURATION); + ASSERT_TRUE(translate != nullptr); + translate->set_name("MoveByAction" + std::to_string(idx) ); + actions[idx].action = translate; + + real32 random_speed_mod = + nom::uniform_real_rand(MIN_SPEED_MOD, MAX_SPEED_MOD); + translate->set_speed(random_speed_mod); + actions[idx].speed = random_speed_mod; + + auto repeat = + nom::create_action(translate, NUM_REPEATS); + ASSERT_TRUE(repeat != nullptr); + repeat->set_name("RepeatForAction_MoveByAction" + std::to_string(idx) ); + actions[idx].container = repeat; + } + + action_list group_actions; + for( auto idx = 0; idx != NUM_OBJECTS; ++idx ) { + group_actions.emplace_back(actions[idx].container); + } + + auto action0 = + nom::create_action(group_actions); + ASSERT_TRUE(action0 != nullptr); + action0->set_timing_curve(timing_mode); + action0->set_name("group_action0"); + + auto remove_action0 = + nom::create_action(action0); + ASSERT_TRUE(remove_action0 != nullptr); + remove_action0->set_name("remove_action0"); + + this->run_action_ret = + this->player.run_action(action0, [=]() mutable { + + for( auto idx = 0; idx != NUM_OBJECTS; ++idx ) { + + auto sprite = actions[idx].sprite.get(); + auto sprite_pos = actions[idx].sprite_pos; + ASSERT_TRUE(sprite != nullptr); + EXPECT_EQ(sprite_pos.x, sprite->position().x); + EXPECT_EQ(TRANSLATE_POS.y, sprite->position().y); + + auto action = actions[idx].action.get(); + ASSERT_TRUE(action != nullptr); + this->expected_common_params( action, DURATION, actions[idx].speed, + "action_speed" ); + + auto action_container = actions[idx].container.get(); + ASSERT_TRUE(action_container != nullptr); + this->expected_repeat_params( action_container, NUM_REPEATS, + "action_num_repeats" ); + } + + this->player.run_action(remove_action0, [=]() mutable { + + for( auto idx = 0; idx != NUM_OBJECTS; ++idx ) { + + auto sprite = actions[idx].sprite.get(); + ASSERT_TRUE(sprite != nullptr); + EXPECT_FALSE( sprite->valid() ); + } + }); + + }); + EXPECT_EQ(true, this->run_action_ret) + << "Failed to queue action0"; + + this->append_update_callback( [=](real32) { + if( this->expected_min_duration(DURATION, MAX_SPEED_MOD) == true ) { + this->quit(); + } + }); + + for( auto idx = 0; idx != NUM_OBJECTS; ++idx ) { + auto drawable = actions[idx].sprite.get(); + this->append_render_queue(drawable); + } + + this->append_frame_interval(FPS); + + EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); +} + +TEST_F(ActionTest, CardPlacementEffectsDemo) +{ + // Testing parameters + const float DURATION = 0.5f; + const float SPEED_MOD = NOM_ACTION_TEST_FLAG(speed); + const IActionObject::timing_curve_func TIMING_MODE = + NOM_ACTION_TEST_FLAG(timing_curve); + const uint32 FPS = NOM_ACTION_TEST_FLAG(fps); + const Size2f SCALE_FACTOR(3.0f, 3.0f); + + // Initial texture position and size + const Point2i TEX_POS(Point2i::zero); + const Size2i TEX_SIZE(128, 128); + + const Point2i MOVE_UP0_TRANSLATE_POS(192, -320); + const Point2i MOVE_UP1_TRANSLATE_POS(-320+128, -192-128); + + // Expected total change over time + const Point2i A1_EXPECTED_TEX_POS(192,32); + const Size2i A1_EXPECTED_TEX_SIZE( TEX_SIZE.w * SCALE_FACTOR.w, + TEX_SIZE.h * SCALE_FACTOR.h); + const Point2i A2_EXPECTED_TEX_POS(192,32); + const Point2i A3_EXPECTED_TEX_POS(320,32); + + // The texture will be twice its size when the second action to reverse the + // rescaling starts running + const Size2i A2_EXPECTED_TEX_SIZE(TEX_SIZE.w, TEX_SIZE.h); + const Size2i A3_EXPECTED_TEX_SIZE(TEX_SIZE.w, TEX_SIZE.h); + + auto bg_tex = std::make_shared(); + ASSERT_TRUE(bg_tex != nullptr); + if( bg_tex->load( resources[0].path() + "backdrop.png" ) == false ) { + FAIL() << "Could not load 'backdrop.png' input file from " + << resources[0].path(); + } + if( bg_tex->valid() == false ) { + FAIL() << "Texture is not valid."; + } + + // Stretched dimensions to cover entire window + bg_tex->set_size(WINDOW_DIMS); + + // action0, action1 + auto tex0 = std::make_shared(); + ASSERT_TRUE(tex0 != nullptr); + tex0->load(resources[0].path() + "card.png"); + if( tex0->valid() == false ) { + FAIL() << "Could not load texture from file source."; + } + + // action2, action3 + auto tex1 = std::make_shared(); + ASSERT_TRUE(tex1 != nullptr); + tex1->load(resources[0].path() + "card.png"); + if( tex1->valid() == false ) { + FAIL() << "Could not load texture from file source."; + } + + auto sprite0 = std::make_shared(); + ASSERT_TRUE(sprite0 != nullptr); + sprite0->set_texture(tex0); + sprite0->set_size(TEX_SIZE); + + auto sprite1 = std::make_shared(); + ASSERT_TRUE(sprite1 != nullptr); + sprite1->set_texture(tex1); + sprite1->set_size(TEX_SIZE); + + nom::set_alignment(sprite0.get(), Point2i::zero, WINDOW_DIMS, Anchor::BottomLeft); + ASSERT_TRUE( sprite0->position() == Point2i(0, 352) ) + << sprite0->position(); + + nom::set_alignment(sprite1.get(), Point2i::zero, WINDOW_DIMS, Anchor::BottomRight); + ASSERT_TRUE( sprite1->position() == Point2i(512, 352) ) + << sprite1->position(); + + auto move_up0 = + nom::create_action(sprite0, MOVE_UP0_TRANSLATE_POS, DURATION); + ASSERT_TRUE(move_up0 != nullptr); + + auto move_up1 = + nom::create_action(sprite1, MOVE_UP1_TRANSLATE_POS, DURATION); + ASSERT_TRUE(move_up1 != nullptr); + + auto scale_by0 = + nom::create_action(sprite0, SCALE_FACTOR, DURATION); + ASSERT_TRUE(scale_by0 != nullptr); + + auto scale_by1 = + nom::create_action(sprite1, SCALE_FACTOR, DURATION); + ASSERT_TRUE(scale_by1 != nullptr); + + auto scale_by_reversed0 = + nom::create_action( scale_by0->clone() ); + ASSERT_TRUE(scale_by_reversed0 != nullptr); + + auto scale_by_reversed1 = + nom::create_action( scale_by1->clone() ); + ASSERT_TRUE(scale_by_reversed1 != nullptr); + + move_up0->set_name("move_up0"); + move_up1->set_name("move_up1"); + + scale_by0->set_name("scale_by0"); + scale_by1->set_name("scale_by1"); + + scale_by_reversed0->set_name("scale_by_reversed0"); + scale_by_reversed1->set_name("scale_by_reversed1"); + + auto action0 = + nom::create_action( {move_up0, scale_by0} ); + ASSERT_TRUE(action0 != nullptr); + action0->set_timing_curve(nom::Expo::ease_out); + action0->set_speed(SPEED_MOD); + action0->set_name("action0"); + + auto action1 = + nom::create_action( {scale_by_reversed0} ); + ASSERT_TRUE(action1 != nullptr); + action1->set_timing_curve(nom::Expo::ease_out); + action1->set_speed(SPEED_MOD); + action1->set_name("action1"); + + auto action2 = + nom::create_action( {move_up1, scale_by1} ); + ASSERT_TRUE(action2 != nullptr); + action2->set_timing_curve(nom::Expo::ease_out); + action2->set_speed(SPEED_MOD); + action2->set_name("action2"); + + auto action3 = + nom::create_action( {scale_by_reversed1} ); + ASSERT_TRUE(action3 != nullptr); + action3->set_timing_curve(nom::Expo::ease_out); + action3->set_speed(SPEED_MOD); + action3->set_name("action3"); + + EXPECT_EQ(0, this->player.num_actions() ); + this->run_action_ret = + this->player.run_action(action0, [=]() { + + // move_up0 + EXPECT_EQ( A1_EXPECTED_TEX_POS, sprite0->position() ); + // scale_by0 + EXPECT_EQ( A1_EXPECTED_TEX_SIZE, sprite0->size() ); + EXPECT_EQ(2, this->player.num_actions() ); + + this->expected_action_params(action0.get(), 2); + this->expected_common_params(move_up0.get(), DURATION, SPEED_MOD); + this->expected_common_params(scale_by0.get(), DURATION, SPEED_MOD); + + this->player.run_action(action1, [=]() { + + // move_up0 + EXPECT_EQ( A2_EXPECTED_TEX_POS, sprite0->position() ); + // scale_by_reversed0 + EXPECT_EQ( A2_EXPECTED_TEX_SIZE, sprite0->size() ); + EXPECT_EQ(2, this->player.num_actions() ); + + this->expected_action_params(action1.get(), 1); + }); + }); + EXPECT_EQ(true, this->run_action_ret) + << "Failed to queue the action!"; + EXPECT_EQ(1, this->player.num_actions() ); + + EXPECT_EQ(1, this->player.num_actions() ); + this->run_action_ret = + this->player.run_action(action2, [=]() { + + // move_up1 + EXPECT_EQ( A3_EXPECTED_TEX_POS, sprite1->position() ); + // scale_by1 + EXPECT_EQ( A1_EXPECTED_TEX_SIZE, sprite1->size() ); + EXPECT_EQ(3, this->player.num_actions() ); + + this->expected_action_params(action2.get(), 2); + this->expected_common_params(move_up1.get(), DURATION, SPEED_MOD); + this->expected_common_params(scale_by1.get(), DURATION, SPEED_MOD); + + this->player.run_action(action3, [=]() { + + // move_up1 + EXPECT_EQ( A3_EXPECTED_TEX_POS, sprite1->position() ); + // scale_by_reversed1 + EXPECT_EQ( A3_EXPECTED_TEX_SIZE, sprite1->size() ); + EXPECT_EQ(2, this->player.num_actions() ); + + this->expected_action_params(action3.get(), 1); + }); + }); + + this->append_update_callback( [=](float) { + if( this->expected_min_duration(DURATION, SPEED_MOD) == true ) { + this->quit(); + } + }); + + this->append_render_queue( bg_tex.get() ); + this->append_render_queue( sprite0.get() ); + this->append_render_queue( sprite1.get() ); + this->append_frame_interval(FPS); + + EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); +} + +TEST_F(ActionTest, ScrollingTextDemo) +{ + nom::Font font; + nom::Text rendered_text; + + // #define DEV_SCROLLING_TEXT_NO_WAIT_TIMER + + // Testing parameters + const real32 DURATION = 15.5f; + const float SPEED_MOD = NOM_ACTION_TEST_FLAG(speed); + const IActionObject::timing_curve_func TIMING_MODE = + NOM_ACTION_TEST_FLAG(timing_curve); + const uint32 FPS = NOM_ACTION_TEST_FLAG(fps); + + const real32 MOVE_BY_DURATION = 2.5f; + const real32 FADE_OUT_DURATION = 2.5f; + const real32 FADE_IN_DURATION = 2.5f; + const real32 NUM_REPEATS = 2; + +#if defined(DEV_SCROLLING_TEXT_NO_WAIT_TIMER) + const real32 WAIT1S_DURATION = 0.0f; + const real32 WAIT2S_DURATION = 0.0f; +#else + const real32 WAIT1S_DURATION = 1.0f; + const real32 WAIT2S_DURATION = 2.0f; +#endif + + auto bg_tex = std::make_shared(); + if( bg_tex->load( resources[0].path() + "backdrop.png" ) == false ) { + NOM_LOG_WARN( NOM_LOG_CATEGORY_APPLICATION, + "Could not load 'backdrop.png' input file from ", + resources[0].path() ); + } + if( (bg_tex != nullptr) && bg_tex->valid() == false ) { + NOM_LOG_WARN( NOM_LOG_CATEGORY_APPLICATION, + "Texture for 'backdrop.png' is not valid!" ); + } + + // Stretched dimensions to cover entire window + bg_tex->set_size(WINDOW_DIMS); + + auto bg_sprite = + std::make_shared(); + ASSERT_TRUE(bg_sprite != nullptr); + EXPECT_EQ(true, bg_sprite->set_texture(bg_tex) ); + + nom::set_file_root( resources[1].path() ); + if( font.load( resources[1].path() + "gameover.fnt" ) == false ) { + FAIL() << "Could not load 'gameover.fnt' input file from " + << resources[1].path(); + } + + rendered_text.set_font(&font); + rendered_text.set_text("Hello, world!"); + rendered_text.set_position( Point2i(0,0) ); + + // This is a deep-copy clone of rendered_text object's text texture; used for + // animation of the displacement + auto anim_text_tex = + std::shared_ptr( rendered_text.clone_texture() ); + ASSERT_TRUE(anim_text_tex != nullptr); + + auto anim_text_sprite = std::make_shared(); + ASSERT_TRUE(anim_text_sprite != nullptr); + EXPECT_EQ(true, anim_text_sprite->set_texture(anim_text_tex) ); + + // This is sharing the same texture used for the animated text, in order to + // modify the same texture data for alpha blending animations + auto anim_fade_tex = + std::shared_ptr( anim_text_sprite->texture() ); + ASSERT_TRUE(anim_fade_tex != nullptr); + + auto anim_fade_sprite = std::make_shared(); + ASSERT_TRUE(anim_fade_sprite != nullptr); + EXPECT_EQ(true, anim_fade_sprite->set_texture(anim_fade_tex) ); + + nom::set_alignment( anim_text_sprite.get(), rendered_text.position(), + WINDOW_DIMS, Anchor::MiddleLeft ); + + // Initialize texture with its starting alpha value for testing alpha + // blending to full opacity + anim_fade_sprite->set_alpha(Color4i::ALPHA_TRANSPARENT); + + auto anim_translate0 = + nom::create_action(anim_text_sprite, Point2i(200, 0), MOVE_BY_DURATION); + + auto anim_translate2 = + nom::create_action(anim_text_sprite, Point2i(0,300), MOVE_BY_DURATION); + + auto anim_fade_out0 = + nom::create_action(anim_fade_sprite, FADE_OUT_DURATION); + + auto anim_fade_in = + nom::create_action(anim_fade_sprite, FADE_IN_DURATION); + ASSERT_TRUE(anim_fade_in != nullptr); + + auto wait1s = + nom::create_action(WAIT1S_DURATION); + ASSERT_TRUE(wait1s != nullptr); + + auto wait2s = + nom::create_action(WAIT2S_DURATION); + ASSERT_TRUE(wait2s != nullptr); + + auto wait3s = + nom::create_action( {wait1s, wait2s} ); + ASSERT_TRUE(wait3s != nullptr); + wait3s->set_speed(SPEED_MOD); + wait3s->set_timing_curve(TIMING_MODE); + wait3s->set_name("wait3s"); + + auto scroll_right = + nom::create_action( {anim_translate0, anim_fade_in} ); + ASSERT_TRUE(scroll_right != nullptr); + scroll_right->set_speed(SPEED_MOD); + scroll_right->set_timing_curve(TIMING_MODE); + scroll_right->set_name("scroll_right"); + if( NOM_ACTION_TEST_FLAG(timing_mode_str) != "linear_ease_in_out" ) { + scroll_right->set_timing_curve(TIMING_MODE); + } else { + scroll_right->set_timing_curve(nom::Quad::ease_in_out); + } + + std::shared_ptr scroll_left_action; + scroll_left_action = anim_translate0->clone(); + + auto scroll_left = + nom::create_action(scroll_left_action); + ASSERT_TRUE(scroll_left != nullptr); + scroll_left->set_speed(SPEED_MOD); + scroll_left->set_name("scroll_left"); + if( NOM_ACTION_TEST_FLAG(timing_mode_str) != "linear_ease_in_out" ) { + scroll_left->set_timing_curve(TIMING_MODE); + } else { + scroll_left->set_timing_curve(nom::Expo::ease_out); + } + + auto scroll_right_again_action = + nom::create_action(anim_text_sprite, Point2i(200,0), MOVE_BY_DURATION); + ASSERT_TRUE(scroll_right_again_action != nullptr); + + auto scroll_right_again = + nom::create_action( {scroll_right_again_action} ); + ASSERT_TRUE(scroll_right_again != nullptr); + scroll_right_again->set_speed(SPEED_MOD); + scroll_right_again->set_timing_curve(TIMING_MODE); + scroll_right_again->set_name("scroll_right_again"); + + auto scroll_up_move_action = + nom::create_action(anim_text_sprite, Point2i(0,-300), MOVE_BY_DURATION); + ASSERT_TRUE(scroll_up_move_action != nullptr); + + auto scroll_up = + nom::create_action( { + scroll_up_move_action, anim_fade_out0->clone() } ); + ASSERT_TRUE(scroll_up != nullptr); + scroll_up->set_speed(SPEED_MOD); + scroll_up->set_name("scroll_up"); + if( NOM_ACTION_TEST_FLAG(timing_mode_str) != "linear_ease_in_out" ) { + scroll_up->set_timing_curve(TIMING_MODE); + } else { + scroll_up->set_timing_curve(nom::Quad::ease_in); + scroll_up->set_speed(SPEED_MOD); + } + + auto scroll_down_repeat2_translate = + nom::create_action(anim_translate2, NUM_REPEATS); + ASSERT_TRUE(scroll_down_repeat2_translate != nullptr); + + auto scroll_down_repeat2_fade_out = + nom::create_action(anim_fade_out0, NUM_REPEATS); + ASSERT_TRUE(scroll_down_repeat2_fade_out != nullptr); + + auto scroll_down_repeat2 = + nom::create_action( { + scroll_down_repeat2_translate, scroll_down_repeat2_fade_out} ); + ASSERT_TRUE(scroll_down_repeat2 != nullptr); + scroll_down_repeat2->set_speed(SPEED_MOD); + scroll_down_repeat2->set_name("scroll_down_repeat2"); + if( NOM_ACTION_TEST_FLAG(timing_mode_str) != "linear_ease_in_out" ) { + scroll_down_repeat2->set_timing_curve(TIMING_MODE); + } else { + scroll_down_repeat2->set_timing_curve(nom::Bounce::ease_out); + } + + auto fade_screen_out = + nom::create_action(bg_sprite, FADE_OUT_DURATION); + + auto fade_screen_out_action = + nom::create_action( {fade_screen_out} ); + ASSERT_TRUE(fade_screen_out_action != nullptr); + fade_screen_out_action->set_speed(SPEED_MOD); + fade_screen_out_action->set_name("fade_screen_out"); + if( NOM_ACTION_TEST_FLAG(timing_mode_str) != "linear_ease_in_out" ) { + fade_screen_out_action->set_timing_curve(TIMING_MODE); + } else { + fade_screen_out_action->set_timing_curve(nom::Bounce::ease_in_out); + } + + EXPECT_EQ(0, this->player.num_actions() ); + this->run_action_ret = + this->player.run_action(wait3s, [=]() { + EXPECT_EQ(1, this->player.num_actions() ); + this->expected_action_params(wait3s.get(), 2); + + this->expected_common_params( wait1s.get(), WAIT1S_DURATION, SPEED_MOD, + "wait1s" ); + this->expected_common_params( wait2s.get(), WAIT2S_DURATION, SPEED_MOD, + "wait2s" ); + this->player.run_action(scroll_right, [=]() { + + EXPECT_EQ(1, this->player.num_actions() ); + EXPECT_EQ( Point2i(200,195), anim_text_sprite->position() ); + + this->expected_alpha_in_params( anim_fade_in.get(), + Color4i::ALPHA_OPAQUE, + anim_fade_sprite.get() ); + + this->expected_action_params(scroll_right.get(), 2, "scroll_right" ); + + this->expected_common_params( anim_translate0.get(), MOVE_BY_DURATION, + SPEED_MOD, "scroll_right" ); + this->expected_common_params( anim_fade_in.get(), FADE_IN_DURATION, + SPEED_MOD, "scroll_right" ); + + this->player.run_action(scroll_left, [=]() { + + EXPECT_EQ(1, this->player.num_actions() ); + EXPECT_EQ( Point2i(0,195), anim_text_sprite->position() ); + EXPECT_EQ( Color4i::ALPHA_OPAQUE, anim_fade_sprite->alpha() ); + + this->expected_common_params( scroll_left_action.get(), + MOVE_BY_DURATION, SPEED_MOD, + "scroll_left" ); + + this->player.run_action(scroll_right_again, [=]() { + + EXPECT_EQ(1, this->player.num_actions() ); + EXPECT_EQ( Point2i(200,195), anim_text_sprite->position() ); + EXPECT_EQ( Color4i::ALPHA_OPAQUE, anim_fade_sprite->alpha() ); + + this->expected_action_params( scroll_right_again.get(), 1, + "scroll_right_again" ); + this->expected_common_params( scroll_right_again_action.get(), + MOVE_BY_DURATION, SPEED_MOD, + "scroll_right_again" ); + + this->player.run_action(scroll_up, [=]() { + + EXPECT_EQ(1, this->player.num_actions() ); + EXPECT_EQ( Point2i(200,-105), anim_text_sprite->position() ); + this->expected_alpha_out_params( anim_fade_out0.get(), + Color4i::ALPHA_TRANSPARENT, + nullptr, + "scroll_up" ); + this->expected_action_params(scroll_up.get(), 2, "scroll_up" ); + this->expected_common_params( scroll_up_move_action.get(), + MOVE_BY_DURATION, SPEED_MOD, + "scroll_up" ); + this->expected_common_params( anim_fade_out0.get(), + FADE_OUT_DURATION, SPEED_MOD, + "scroll_up" ); + + // Reset the initial alpha value for the next action to re-use; + // we are using a shallow-copy of the fade out action -- the + // underlying texture data is shared across clones + anim_fade_sprite->set_alpha(Color4i::ALPHA_OPAQUE); + + this->player.run_action(scroll_down_repeat2, [=]() { + + EXPECT_EQ(1, this->player.num_actions() ); + EXPECT_EQ( Point2i(200,300), anim_text_sprite->position() ); + this->expected_alpha_out_params( anim_fade_out0.get(), + Color4i::ALPHA_TRANSPARENT, + anim_fade_sprite.get(), + "scroll_down_repeat2" ); + + this->expected_repeat_params( scroll_down_repeat2_translate.get(), + NUM_REPEATS, + "scroll_down_repeat2" ); + this->expected_repeat_params( scroll_down_repeat2_fade_out.get(), + NUM_REPEATS, + "scroll_down_repeat2" ); + this->expected_action_params( scroll_down_repeat2.get(), 2, + "scroll_down_repeat2" ); + + this->expected_common_params( anim_translate2.get(), + MOVE_BY_DURATION, SPEED_MOD, + "scroll_down_repeat2" ); + this->expected_common_params( anim_fade_out0.get(), + FADE_OUT_DURATION, SPEED_MOD, + "scroll_down_repeat2" ); + + this->player.run_action(fade_screen_out_action, [=]() { + + EXPECT_EQ(1, this->player.num_actions() ); + this->expected_alpha_out_params( fade_screen_out.get(), + Color4i::ALPHA_TRANSPARENT, + bg_sprite.get(), "fade_screen_out" ); + + this->expected_action_params( fade_screen_out_action.get(), 1, + "fade_screen_out" ); + }); + }); + }); + }); + }); + }); + }); + EXPECT_EQ(true, this->run_action_ret) + << "Failed to queue one or more actions!"; + EXPECT_EQ(1, this->player.num_actions() ); + + this->append_update_callback( [=](float) { + if( this->expected_min_duration(DURATION, SPEED_MOD) == true ) { + this->quit(); + } + }); + + this->append_render_callback( [=](const RenderWindow& win) { + // For the aesthetic appeal of fading the screen out + this->render_window().fill(Color4i::Black); + }); + + this->append_render_queue( bg_sprite.get() ); + this->append_render_queue( anim_text_sprite.get() ); + this->append_frame_interval(FPS); + + EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); +} + +} // namespace nom diff --git a/tests/src/actions/ActionTest_Repeating.cpp b/tests/src/actions/ActionTest_Repeating.cpp new file mode 100644 index 00000000..e297f72a --- /dev/null +++ b/tests/src/actions/ActionTest_Repeating.cpp @@ -0,0 +1,576 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#include "nomlib/tests/actions/ActionTest.hpp" + +namespace nom { + +void +ActionTest::setup_repeating_cursor_test( real32 duration, real32 speed, + real32 fps, uint32 type, + nom::size_type num_repeats, + const std::string& scope_name ) +{ + // Testing parameters + const nom::size_type NUM_REPEATS = num_repeats; + const std::string TEXTURE_FILENAME = "cursors.png"; + const std::string SPRITE_SHEET_FILENAME = "cursors.json"; + + // fps per texture + const real32 FRAME_DURATION = 0.100f; + + const nom::size_type NUM_FRAMES = 3; + + // total duration of a single repeat + const real32 ACTION_DURATION = (FRAME_DURATION * NUM_FRAMES); + + // maximum duration before test termination occurs + const real32 DURATION = duration; + + const IActionObject::timing_curve_func TIMING_MODE = + NOM_ACTION_TEST_FLAG(timing_curve); + const real32 SPEED_MOD = speed; + const uint32 FPS = fps; + nom::SpriteSheet anim_frames; + + auto tex = std::make_shared(); + ASSERT_TRUE(tex != nullptr); + + bool ret = tex->load(this->resources[0].path() + TEXTURE_FILENAME); + if( ret == false ) { + FAIL() << "Could not load texture " << TEXTURE_FILENAME + << " from file path: " << this->resources[0].path() << "\n"; + } + + if( anim_frames.load_file( this->resources[0].path() + + SPRITE_SHEET_FILENAME ) == false ) + { + FAIL() << "Could not load sprite sheet " << TEXTURE_FILENAME + << " from file path: " << this->resources[0].path() << "\n"; + } + + EXPECT_EQ(true, anim_frames.remove_frame(3) ); + EXPECT_EQ(true, anim_frames.remove_frame(4) ); + + auto sprite = std::make_shared(); + ASSERT_TRUE(sprite != nullptr); + EXPECT_EQ(true, sprite->set_texture(tex) ); + sprite->set_sprite_sheet(anim_frames); + sprite->set_frame(0); + + nom::set_alignment( sprite.get(), Point2i::zero, this->WINDOW_DIMS, + Anchor::MiddleCenter ); + + EXPECT_EQ( NUM_FRAMES, sprite->frames() ); + + auto sprite_action = + nom::create_action(sprite, FRAME_DURATION); + ASSERT_TRUE(sprite_action != nullptr); + sprite_action->set_speed(SPEED_MOD); + + std::shared_ptr action0; + if( type & ActionType::GROUP ) { + action0 = nom::create_action( {sprite_action} ); + } else if( type & ActionType::SEQUENCE ) { + action0 = nom::create_action( {sprite_action} ); + } else if( type & ActionType::REVERSED ) { + action0 = nom::create_action(sprite_action); + } else if( type & ActionType::ACTION ) { + action0 = sprite_action; + } else { + ASSERT_TRUE("ActionType must be either one of the enumeration values") + << scope_name; + } + action0->set_name("action0"); + + ASSERT_TRUE(action0 != nullptr); + action0->set_timing_curve(TIMING_MODE); + + std::shared_ptr repeat; + if( type & ActionType::REPEAT_FOR ) { + repeat = + nom::create_action(action0, NUM_REPEATS); + } else if( type & ActionType::REPEAT_FOREVER ) { + repeat = + nom::create_action(action0); + } + ASSERT_TRUE(repeat != nullptr); + repeat->set_name("action0_repeat"); + + EXPECT_EQ(0, this->player.num_actions() ) + << scope_name; + this->run_action_ret = + this->player.run_action(repeat, [=]() { + + this->expected_sprite_batch_action_params( sprite_action.get(), NUM_FRAMES, + ACTION_DURATION, SPEED_MOD, + "sprite_batch_params" ); + }); + EXPECT_EQ(true, this->run_action_ret) + << "Failed to enqueue repeating action in " << scope_name; + EXPECT_EQ(1, this->player.num_actions() ) + << scope_name; + + auto kill_timer = + nom::create_action(DURATION); + ASSERT_TRUE(kill_timer != nullptr); + kill_timer->set_name("kill_timer"); + + EXPECT_EQ(1, this->player.num_actions() ) + << scope_name; + this->run_action_ret = + this->player.run_action(kill_timer, [=]() { + + this->expected_common_params( kill_timer.get(), DURATION, SPEED_MOD, + "common_params" ); + if( type & ActionType::REPEAT_FOR ) { + RepeatForAction* repeat_obj = + NOM_DYN_PTR_CAST(RepeatForAction*, repeat.get() ); + + this->expected_repeat_params( repeat_obj, NUM_REPEATS, "repeat_params" ); + } else if( type & ActionType::REPEAT_FOREVER ) { + RepeatForeverAction* repeat_obj = + NOM_DYN_PTR_CAST(RepeatForeverAction*, repeat.get() ); + + this->expected_repeat_params( repeat_obj, NUM_REPEATS, "repeat_params" ); + } + + this->quit(); + }); + EXPECT_EQ(true, this->run_action_ret) + << "Failed to queue the kill_timer in " << scope_name; + EXPECT_EQ(2, this->player.num_actions() ) + << scope_name; + + this->append_render_queue( sprite.get() ); + this->append_frame_interval(FPS); + + EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); +} + +TEST_F(ActionTest, RepeatForAction) +{ + // Testing parameters + const float DURATION = 0.5f; + const float SPEED_MOD = NOM_ACTION_TEST_FLAG(speed); + const uint NUM_REPEATS = 4; + const IActionObject::timing_curve_func TIMING_MODE = + NOM_ACTION_TEST_FLAG(timing_curve); + const uint32 FPS = NOM_ACTION_TEST_FLAG(fps); + const Point2i TRANSLATE_POS( Point2i(WINDOW_DIMS.w,0) ); + + // Initial texture position and size + const Point2i RECT_POS(Point2i::zero); + const Size2i RECT_SIZE(WINDOW_DIMS.w/4, WINDOW_DIMS.h); + const Point2i EXPECTED_TEX_POS(TRANSLATE_POS); + + auto rect = std::make_shared( + Rectangle( IntRect(RECT_POS, RECT_SIZE), Color4i::Red) ); + ASSERT_TRUE(rect != nullptr); + + auto sprite = nom::make_shared_sprite( rect->texture() ); + ASSERT_TRUE(sprite != nullptr); + + auto translate = + nom::create_action(sprite, TRANSLATE_POS, DURATION); + ASSERT_TRUE(translate != nullptr); + + auto repeat = + nom::create_action(translate, NUM_REPEATS); + ASSERT_TRUE(repeat != nullptr); + + auto action0 = + nom::create_action( {repeat} ); + ASSERT_TRUE(action0 != nullptr); + action0->set_timing_curve(TIMING_MODE); + action0->set_speed(SPEED_MOD); + action0->set_name("action0"); + + EXPECT_EQ(0, this->player.num_actions() ); + this->run_action_ret = + this->player.run_action(action0, [=]() { + + EXPECT_EQ(1, this->player.num_actions() ); + + this->expected_action_params(action0.get(), 1); + this->expected_common_params(translate.get(), DURATION, SPEED_MOD); + this->expected_repeat_params(repeat.get(), NUM_REPEATS); + }); + EXPECT_EQ(true, this->run_action_ret) + << "Failed to queue the action!"; + EXPECT_EQ(1, this->player.num_actions() ); + + this->append_update_callback( [=](float) { + if( this->expected_min_duration(DURATION, SPEED_MOD) == true ) { + this->quit(); + } + }); + + this->append_render_queue( sprite.get() ); + this->append_frame_interval(FPS); + + EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); +} + +/// \remarks This test does not honor custom speed modifiers passed via command +/// line. +TEST_F(ActionTest, RepeatForeverAction) +{ + // Testing parameters + const real32 TEST_DURATION = 2.5f; // when to stop testing "forever" + const nom::size_type NUM_REPEATS = 4; + const real32 DURATION = 0.5f; + + // IMPORTANT: This value must remain constant for reproducing consistent test + // results! + const real32 SPEED_MOD = 1.0f; + + const IActionObject::timing_curve_func TIMING_MODE = + NOM_ACTION_TEST_FLAG(timing_curve); + const uint32 FPS = NOM_ACTION_TEST_FLAG(fps); + + const Point2i TRANSLATE_POS( Point2i(WINDOW_DIMS.w,0) ); + // Initial texture position and size + const Point2i RECT_POS(Point2i::zero); + const Size2i RECT_SIZE(WINDOW_DIMS.w/4, WINDOW_DIMS.h); + + auto rect = + std::make_shared(IntRect(RECT_POS, RECT_SIZE), Color4i::Red); + ASSERT_TRUE(rect != nullptr); + + auto sprite = nom::make_shared_sprite( rect->texture() ); + ASSERT_TRUE(sprite != nullptr); + + auto translate = + nom::create_action(sprite, TRANSLATE_POS, DURATION); + ASSERT_TRUE(translate != nullptr); + + auto repeat_forever = + nom::create_action(translate); + ASSERT_TRUE(repeat_forever != nullptr); + + auto action0 = + nom::create_action( {repeat_forever} ); + ASSERT_TRUE(action0 != nullptr); + action0->set_timing_curve(TIMING_MODE); + action0->set_speed(SPEED_MOD); + action0->set_name("action0"); + + auto kill_timer = + nom::create_action(TEST_DURATION); + ASSERT_TRUE(kill_timer != nullptr); + kill_timer->set_speed(SPEED_MOD); + kill_timer->set_name("kill_timer"); + + EXPECT_EQ(0, this->player.num_actions() ); + this->run_action_ret = + this->player.run_action(action0, [=]() { + FAIL() << "action0 should never complete!"; + }); + EXPECT_EQ(true, this->run_action_ret) + << "Failed to queue the action!"; + EXPECT_EQ(1, this->player.num_actions() ); + + this->run_action_ret = + this->player.run_action(kill_timer, [=]() { + + EXPECT_EQ(2, this->player.num_actions() ); + this->expected_repeat_params(repeat_forever.get(), NUM_REPEATS); + this->expected_action_params(action0.get(), 1); + this->expected_common_params(translate.get(), DURATION, SPEED_MOD); + this->expected_common_params(kill_timer.get(), TEST_DURATION, SPEED_MOD); + + this->quit(); + }); + EXPECT_EQ(true, this->run_action_ret) + << "Failed to queue kill_timer!"; + EXPECT_EQ(2, this->player.num_actions() ); + + this->append_render_queue( sprite.get() ); + this->append_frame_interval(FPS); + + EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); +} + +TEST_F(ActionTest, SpriteBatchActionRepeatForever) +{ + // Testing parameters + + // maximal test duration before termination + const real32 TEST_DURATION = 1.5f; + // this value correlates with TEST_DURATION + const nom::size_type NUM_REPEATS = 4; + const uint32 action_type = + ActionType::ACTION | ActionType::REPEAT_FOREVER; + + // IMPORTANT: This value must remain constant for reproducing consistent test + // results! + const real32 SPEED_MOD = 1.0f; + const uint32 FPS = NOM_ACTION_TEST_FLAG(fps); + + this->setup_repeating_cursor_test( TEST_DURATION, SPEED_MOD, FPS, + action_type, NUM_REPEATS, + nom::UnitTest::test_name() ); +} + +TEST_F(ActionTest, SpriteBatchActionRepeatForeverReversed) +{ + // Testing parameters + + // maximal test duration before termination + const real32 TEST_DURATION = 1.5f; + // this value correlates with TEST_DURATION + const nom::size_type NUM_REPEATS = 4; + const uint32 action_type = + ActionType::REVERSED | ActionType::REPEAT_FOREVER; + + // IMPORTANT: This value must remain constant for reproducing consistent test + // results! + const real32 SPEED_MOD = 1.0f; + const uint32 FPS = NOM_ACTION_TEST_FLAG(fps); + + this->setup_repeating_cursor_test( TEST_DURATION, SPEED_MOD, FPS, + action_type, NUM_REPEATS, + nom::UnitTest::test_name() ); +} + +TEST_F(ActionTest, SpriteBatchActionGroup) +{ + // Testing parameters + + // maximal test duration before termination + const real32 TEST_DURATION = 1.5f; + // this value correlates with TEST_DURATION + const nom::size_type NUM_REPEATS = 4; + const uint32 action_type = + ActionType::GROUP | ActionType::REPEAT_FOR; + + // IMPORTANT: This value must remain constant for reproducing consistent test + // results! + const real32 SPEED_MOD = 1.0f; + const uint32 FPS = NOM_ACTION_TEST_FLAG(fps); + + this->setup_repeating_cursor_test( TEST_DURATION, SPEED_MOD, FPS, + action_type, NUM_REPEATS, + nom::UnitTest::test_name() ); +} + +TEST_F(ActionTest, SpriteBatchActionSequence) +{ + // Testing parameters + + // maximal test duration before termination + const real32 TEST_DURATION = 1.5f; + // this value correlates with TEST_DURATION + const nom::size_type NUM_REPEATS = 4; + const uint32 action_type = + ActionType::SEQUENCE | ActionType::REPEAT_FOR; + + // IMPORTANT: This value must remain constant for reproducing consistent test + // results! + const real32 SPEED_MOD = 1.0f; + const uint32 FPS = NOM_ACTION_TEST_FLAG(fps); + + this->setup_repeating_cursor_test( TEST_DURATION, SPEED_MOD, FPS, + action_type, NUM_REPEATS, + nom::UnitTest::test_name() ); +} + +TEST_F(ActionTest, SpriteBatchActionReversed) +{ + // Testing parameters + + // maximal test duration before termination + const real32 TEST_DURATION = 1.5f; + // this value correlates with TEST_DURATION + const nom::size_type NUM_REPEATS = 4; + const uint32 action_type = + ActionType::REVERSED | ActionType::REPEAT_FOR; + + // IMPORTANT: This value must remain constant for reproducing consistent test + // results! + const real32 SPEED_MOD = 1.0f; + const uint32 FPS = NOM_ACTION_TEST_FLAG(fps); + + this->setup_repeating_cursor_test( TEST_DURATION, SPEED_MOD, FPS, + action_type, NUM_REPEATS, + nom::UnitTest::test_name() ); +} + +/// \remarks This test does not honor custom speed modifiers passed via command +/// line. +TEST_F(ActionTest, SpriteBatchActionGroupRepeatingForever) +{ + // Testing parameters + + // maximal test duration before termination + const real32 TEST_DURATION = 1.5f; + // this value correlates with TEST_DURATION + const nom::size_type NUM_REPEATS = 4; + const uint32 action_type = + ActionType::GROUP | ActionType::REPEAT_FOREVER; + const uint32 FPS = NOM_ACTION_TEST_FLAG(fps); + + // IMPORTANT: This value must remain constant for reproducing consistent test + // results! + const real32 SPEED_MOD = 1.0f; + + this->setup_repeating_cursor_test( TEST_DURATION, SPEED_MOD, FPS, + action_type, NUM_REPEATS, + nom::UnitTest::test_name() ); +} + +/// \remarks This test does not honor custom speed modifiers and frame +/// intervals passed via command line. +TEST_F(ActionTest, SpriteBatchActionSequenceRepeatingForever) +{ + // Testing parameters + + // maximal test duration before termination + const real32 TEST_DURATION = 1.5f; + // this value correlates with TEST_DURATION + const nom::size_type NUM_REPEATS = 4; + const uint32 action_type = + ActionType::SEQUENCE | ActionType::REPEAT_FOREVER; + const uint32 FPS = NOM_ACTION_TEST_FLAG(fps); + + // IMPORTANT: This value must remain constant for reproducing consistent test + // results! + const real32 SPEED_MOD = 1.0f; + + this->setup_repeating_cursor_test( TEST_DURATION, SPEED_MOD, FPS, + action_type, NUM_REPEATS, + nom::UnitTest::test_name() ); +} + +/// \remarks This test does not honor custom speed modifiers and frame +/// intervals passed via command line. +TEST_F(ActionTest, SpriteBatchActionRepeatingForeverReversed) +{ + // Testing parameters + + // maximal test duration before termination + const real32 TEST_DURATION = 1.5f; + // this value correlates with TEST_DURATION + const nom::size_type NUM_REPEATS = 4; + const uint32 action_type = + ActionType::REVERSED | ActionType::REPEAT_FOREVER; + const uint32 FPS = NOM_ACTION_TEST_FLAG(fps); + + // IMPORTANT: This value must remain constant for reproducing consistent test + // results! + const real32 SPEED_MOD = 1.0f; + + this->setup_repeating_cursor_test( TEST_DURATION, SPEED_MOD, FPS, + action_type, NUM_REPEATS, + nom::UnitTest::test_name() ); +} + +/// \remarks Thanks goes to Tim Jones of [sdltutorials.com](http://www.sdltutorials.com/sdl-animation) +/// for the sprite frames of Yoshi chosen for this test! +TEST_F(ActionTest, AnimateTexturesActionActionWithRepeatForAction) +{ + // Testing parameters + texture_frames anim_frames; + const std::vector texture_filenames = {{ + "yoshi_000.png", "yoshi_001.png", "yoshi_002.png", "yoshi_003.png", + "yoshi_004.png", "yoshi_005.png", "yoshi_006.png", "yoshi_007.png" + }}; + + // fps per texture + const real32 FRAME_DURATION = 0.100f; + + // total duration of a single repeat + const real32 ACTION_DURATION = + (FRAME_DURATION * texture_filenames.size() ); + + const nom::size_type NUM_REPEATS = 4; + const real32 SPEED_MOD = NOM_ACTION_TEST_FLAG(speed); + const IActionObject::timing_curve_func TIMING_MODE = + NOM_ACTION_TEST_FLAG(timing_curve); + const uint32 FPS = NOM_ACTION_TEST_FLAG(fps); + + this->init_animate_textures_test(texture_filenames, anim_frames); + + EXPECT_EQ( anim_frames.size(), texture_filenames.size() ); + + auto sprite0 = + std::make_shared(); + ASSERT_TRUE(sprite0 != nullptr); + + auto tex_bg = + nom::create_action( sprite0, anim_frames, + FRAME_DURATION ); + ASSERT_TRUE(tex_bg != nullptr); + + auto action0 = + nom::create_action(tex_bg, NUM_REPEATS); + ASSERT_TRUE(action0 != nullptr); + action0->set_timing_curve(TIMING_MODE); + action0->set_speed(SPEED_MOD); + action0->set_name("action0_repeat"); + + auto remove_action0 = + nom::create_action(action0); + ASSERT_TRUE(remove_action0 != nullptr); + remove_action0->set_name("remove_action0"); + + EXPECT_EQ(0, this->player.num_actions() ); + this->run_action_ret = + this->player.run_action(action0, [=]() { + + EXPECT_EQ(1, this->player.num_actions() ); + this->expected_animate_textures_params( tex_bg.get(), anim_frames.size(), + ACTION_DURATION, SPEED_MOD, + "animate_textures_params" ); + this->expected_repeat_params(action0.get(), NUM_REPEATS); + + this->player.run_action(remove_action0, [=]() { + ASSERT_TRUE(sprite0 != nullptr); + EXPECT_FALSE( sprite0->valid() ); + }); + }); + EXPECT_EQ(true, this->run_action_ret) + << "Failed to queue action0"; + EXPECT_EQ(1, this->player.num_actions() ); + + this->append_update_callback( [=](real32) { + if( this->expected_min_duration( (ACTION_DURATION * NUM_REPEATS), + SPEED_MOD ) == true ) + { + this->quit(); + } + }); + + this->append_render_queue( sprite0.get() ); + this->append_frame_interval(FPS); + + EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); +} + +} // namespace nom diff --git a/tests/src/actions/ActionTest_Reversed.cpp b/tests/src/actions/ActionTest_Reversed.cpp new file mode 100644 index 00000000..63822600 --- /dev/null +++ b/tests/src/actions/ActionTest_Reversed.cpp @@ -0,0 +1,615 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#include "nomlib/tests/actions/ActionTest.hpp" + +namespace nom { + +TEST_F(ActionTest, FadeInActionReversed) +{ + // Testing parameters + const real32 DURATION = 2.0f; + const real32 SPEED_MOD = NOM_ACTION_TEST_FLAG(speed); + const IActionObject::timing_curve_func TIMING_MODE = + NOM_ACTION_TEST_FLAG(timing_curve); + const uint32 FPS = NOM_ACTION_TEST_FLAG(fps); + const uint8 INITIAL_ALPHA = Color4i::ALPHA_OPAQUE; + const uint8 EXPECTED_ALPHA = Color4i::ALPHA_TRANSPARENT; + + // Initial texture position and size + const Point2i TEX_POS(Point2i::zero); + const Size2i TEX_SIZE(256, 256); + + auto sprite = + std::make_shared(); + ASSERT_TRUE(sprite != nullptr); + EXPECT_EQ(true, sprite->init_with_color(Color4i::Blue, TEX_SIZE) ); + sprite->set_position(TEX_POS); + sprite->set_alpha(INITIAL_ALPHA); + sprite->set_color_blend_mode(BLEND_MODE_BLEND); + + auto fade_in = + nom::create_action(sprite, DURATION); + ASSERT_TRUE(fade_in != nullptr); + + auto fade_in_reversed = + nom::create_action(fade_in); + ASSERT_TRUE(fade_in_reversed != nullptr); + + auto action0 = + nom::create_action( {fade_in_reversed} ); + ASSERT_TRUE(action0 != nullptr); + action0->set_timing_curve(TIMING_MODE); + action0->set_speed(SPEED_MOD); + action0->set_name("action0"); + + EXPECT_EQ(0, this->player.num_actions() ); + this->run_action_ret = + this->player.run_action(action0, [=]() { + + this->expected_alpha_in_params( fade_in.get(), EXPECTED_ALPHA, + sprite.get() ); + EXPECT_EQ(1, this->player.num_actions() ); + + this->expected_action_params(action0.get(), 1, nom::UnitTest::test_name() ); + }); + EXPECT_EQ(true, this->run_action_ret) + << "Failed to queue the action!"; + EXPECT_EQ(1, this->player.num_actions() ); + + this->append_update_callback( [=](real32) mutable { + nom::set_alignment( sprite.get(), Point2i::zero, WINDOW_DIMS, + Anchor::MiddleCenter ); + }); + + this->append_update_callback( [=](real32) { + if( this->expected_min_duration(DURATION, SPEED_MOD) == true ) { + this->quit(); + } + }); + + this->append_render_queue( sprite.get() ); + this->append_frame_interval(FPS); + + EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); +} + +TEST_F(ActionTest, FadeOutActionReversed) +{ + // Testing parameters + const real32 DURATION = 2.0f; + const real32 SPEED_MOD = NOM_ACTION_TEST_FLAG(speed); + const IActionObject::timing_curve_func TIMING_MODE = + NOM_ACTION_TEST_FLAG(timing_curve); + const uint32 FPS = NOM_ACTION_TEST_FLAG(fps); + const uint8 INITIAL_ALPHA = Color4i::ALPHA_TRANSPARENT; + const uint8 EXPECTED_ALPHA = Color4i::ALPHA_OPAQUE; + + // Initial texture position and size + const Point2i TEX_POS(Point2i::zero); + const Size2i TEX_SIZE(256, 256); + + auto sprite = + std::make_shared(); + ASSERT_TRUE(sprite != nullptr); + EXPECT_EQ(true, sprite->init_with_color(Color4i::Blue, TEX_SIZE) ); + sprite->set_position(TEX_POS); + sprite->set_alpha(INITIAL_ALPHA); + sprite->set_color_blend_mode(BLEND_MODE_BLEND); + + auto fade_out = + nom::create_action(sprite, DURATION); + ASSERT_TRUE(fade_out != nullptr); + + auto fade_out_reversed = + nom::create_action(fade_out); + ASSERT_TRUE(fade_out_reversed != nullptr); + + auto action0 = + nom::create_action( {fade_out_reversed} ); + ASSERT_TRUE(action0 != nullptr); + action0->set_timing_curve(TIMING_MODE); + action0->set_speed(SPEED_MOD); + action0->set_name("action0"); + + EXPECT_EQ(0, this->player.num_actions() ); + this->run_action_ret = + this->player.run_action(action0, [=]() { + + EXPECT_EQ(1, this->player.num_actions() ); + this->expected_alpha_out_params( fade_out.get(), EXPECTED_ALPHA, + sprite.get() ); + this->expected_action_params(action0.get(), 1, nom::UnitTest::test_name() ); + }); + EXPECT_EQ(true, this->run_action_ret) + << "Failed to queue action0"; + EXPECT_EQ(1, this->player.num_actions() ); + + this->append_update_callback( [=](real32) mutable { + nom::set_alignment( sprite.get(), Point2i::zero, WINDOW_DIMS, + Anchor::MiddleCenter ); + }); + + this->append_update_callback( [=](real32) { + if( this->expected_min_duration(DURATION, SPEED_MOD) == true ) { + this->quit(); + } + }); + + this->append_render_queue( sprite.get() ); + this->append_frame_interval(FPS); + + EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); +} + +TEST_F(ActionTest, FadeAlphaByActionReversed) +{ + // Testing parameters + const real32 DURATION = 2.0f; + const real32 SPEED_MOD = NOM_ACTION_TEST_FLAG(speed); + const IActionObject::timing_curve_func TIMING_MODE = + NOM_ACTION_TEST_FLAG(timing_curve); + const uint32 FPS = NOM_ACTION_TEST_FLAG(fps); + const uint8 FADE_BY = 129; + const uint8 INITIAL_ALPHA = Color4i::ALPHA_OPAQUE; + const uint8 EXPECTED_ALPHA = (INITIAL_ALPHA - abs(FADE_BY) ); + + // Initial texture position and size + const Point2i TEX_POS(Point2i::zero); + const Size2i TEX_SIZE(256, 256); + + auto sprite = + std::make_shared(); + ASSERT_TRUE(sprite != nullptr); + EXPECT_EQ(true, sprite->init_with_color(Color4i::Blue, TEX_SIZE) ); + sprite->set_position(TEX_POS); + sprite->set_alpha(INITIAL_ALPHA); + sprite->set_color_blend_mode(BLEND_MODE_BLEND); + + auto fade_by = + nom::create_action(sprite, FADE_BY, DURATION); + ASSERT_TRUE(fade_by != nullptr); + + auto fade_by_reversed = + nom::create_action(fade_by); + ASSERT_TRUE(fade_by_reversed != nullptr); + + auto action0 = + nom::create_action( {fade_by_reversed} ); + ASSERT_TRUE(action0 != nullptr); + action0->set_timing_curve(TIMING_MODE); + action0->set_speed(SPEED_MOD); + action0->set_name("action0"); + + EXPECT_EQ(0, this->player.num_actions() ); + this->run_action_ret = + this->player.run_action(action0, [=]() { + + EXPECT_EQ(1, this->player.num_actions() ); + this->expected_alpha_by_params(fade_by.get(), EXPECTED_ALPHA); + EXPECT_EQ( EXPECTED_ALPHA, sprite->alpha() ); + this->expected_action_params(action0.get(), 1, nom::UnitTest::test_name() ); + }); + EXPECT_EQ(true, this->run_action_ret) + << "Failed to queue action0"; + EXPECT_EQ(1, this->player.num_actions() ); + + this->append_update_callback( [=](real32) mutable { + nom::set_alignment( sprite.get(), Point2i::zero, WINDOW_DIMS, + Anchor::MiddleCenter ); + }); + + this->append_update_callback( [=](real32) { + if( this->expected_min_duration(DURATION, SPEED_MOD) == true ) { + this->quit(); + } + }); + + this->append_render_queue( sprite.get() ); + this->append_frame_interval(FPS); + + EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); +} + +TEST_F(ActionTest, MoveByActionReversed) +{ + // Testing parameters + const float DURATION = 2.5f; + const float SPEED_MOD = NOM_ACTION_TEST_FLAG(speed); + const Point2i TRANSLATE_POS( Point2i(200,0) ); + const IActionObject::timing_curve_func TIMING_MODE = + NOM_ACTION_TEST_FLAG(timing_curve); + const uint32 FPS = NOM_ACTION_TEST_FLAG(fps); + + // Initial texture position and size + const Point2i RECT_POS(TRANSLATE_POS); + const Size2i RECT_SIZE(WINDOW_DIMS.w/4, WINDOW_DIMS.h); + const Point2i EXPECTED_TEX_POS(0, 0); + + auto rect = + std::make_shared(IntRect(RECT_POS, RECT_SIZE), Color4i::Green); + ASSERT_TRUE(rect != nullptr); + + auto sprite = nom::make_shared_sprite( rect->texture() ); + ASSERT_TRUE(sprite != nullptr); + + auto translate = + nom::create_action(sprite, TRANSLATE_POS, DURATION); + ASSERT_TRUE(translate != nullptr); + + auto translate_rev = + nom::create_action(translate); + ASSERT_TRUE(translate_rev != nullptr); + + auto action0 = + nom::create_action( {translate_rev} ); + ASSERT_TRUE(action0 != nullptr); + action0->set_timing_curve(TIMING_MODE); + action0->set_speed(SPEED_MOD); + action0->set_name("action0"); + + EXPECT_EQ(0, this->player.num_actions() ); + this->run_action_ret = + this->player.run_action(action0, [=]() { + + EXPECT_EQ( EXPECTED_TEX_POS, sprite->position() ); + EXPECT_EQ(1, this->player.num_actions() ); + + this->expected_action_params(action0.get(), 1); + this->expected_common_params(translate.get(), DURATION, SPEED_MOD); + }); + EXPECT_EQ(true, this->run_action_ret) + << "Failed to queue action0"; + EXPECT_EQ(1, this->player.num_actions() ); + + this->append_update_callback( [=](float) { + if( this->expected_min_duration(DURATION, SPEED_MOD) == true ) { + this->quit(); + } + }); + + this->append_render_queue( sprite.get() ); + this->append_frame_interval(FPS); + + EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); +} + +TEST_F(ActionTest, ScaleByActionReversed) +{ + // Testing parameters + const float DURATION = 1.5f; // 90 frames @ 60 FPS + const float SPEED_MOD = NOM_ACTION_TEST_FLAG(speed); + const Size2f SCALE_FACTOR(2.0f, 2.0f); + const IActionObject::timing_curve_func TIMING_MODE = + NOM_ACTION_TEST_FLAG(timing_curve); + const uint32 FPS = NOM_ACTION_TEST_FLAG(fps); + + // Initial texture position and size + const Point2i TEX_POS(Point2i::zero); + const Size2i TEX_SIZE(128, 128); + + // Resulting animation frame effect to test for + const Size2i EXPECTED_TEX_SIZE( Size2i( TEX_SIZE.w / SCALE_FACTOR.w, + TEX_SIZE.h / SCALE_FACTOR.h ) + ); + + auto rect = std::make_shared( + IntRect(TEX_POS, TEX_SIZE), Color4i::Blue); + ASSERT_TRUE(rect != nullptr); + + auto tex = + std::shared_ptr( rect->texture() ); + ASSERT_TRUE(tex != nullptr); + + auto sprite = + std::make_shared(); + ASSERT_TRUE(sprite != nullptr); + EXPECT_EQ(true, sprite->set_texture(tex) ); + + auto scale_by = + nom::create_action(sprite, SCALE_FACTOR, DURATION); + ASSERT_TRUE(scale_by != nullptr); + + auto scale_by_reversed = + nom::create_action(scale_by); + ASSERT_TRUE(scale_by_reversed != nullptr); + + auto action0 = + nom::create_action( {scale_by_reversed} ); + ASSERT_TRUE(action0 != nullptr); + action0->set_timing_curve(TIMING_MODE); + action0->set_speed(SPEED_MOD); + action0->set_name("action0"); + + EXPECT_EQ(0, this->player.num_actions() ); + this->run_action_ret = + this->player.run_action(action0, [=]() { + + EXPECT_EQ( EXPECTED_TEX_SIZE, sprite->size() ); + EXPECT_EQ(1, this->player.num_actions() ); + this->expected_action_params(action0.get(), 1); + this->expected_common_params(scale_by.get(), DURATION, SPEED_MOD); + }); + EXPECT_EQ(true, this->run_action_ret) + << "Failed to queue action0"; + EXPECT_EQ(1, this->player.num_actions() ); + + this->append_update_callback( [=](float) mutable { + nom::set_alignment( sprite.get(), Point2i::zero, WINDOW_DIMS, + Anchor::MiddleCenter ); + }); + + this->append_update_callback( [=](float) { + if( this->expected_min_duration(DURATION, SPEED_MOD) == true ) { + this->quit(); + } + }); + + this->append_render_queue( sprite.get() ); + this->append_frame_interval(FPS); + + EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); +} + +TEST_F(ActionTest, MoveByActionRepeatForReversed) +{ + // Testing parameters + const float DURATION = 0.5f; + const float SPEED_MOD = NOM_ACTION_TEST_FLAG(speed); + const uint NUM_REPEATS = 4; + const Point2i TRANSLATE_POS( Point2i(WINDOW_DIMS.w,0) ); + const IActionObject::timing_curve_func TIMING_MODE = + NOM_ACTION_TEST_FLAG(timing_curve); + const uint32 FPS = NOM_ACTION_TEST_FLAG(fps); + + // Initial texture position and size + const Point2i RECT_POS(TRANSLATE_POS); + const Size2i RECT_SIZE(WINDOW_DIMS.w/4, WINDOW_DIMS.h); + const Point2i EXPECTED_TEX_POS(TRANSLATE_POS-TRANSLATE_POS); + + auto rect = + std::make_shared(IntRect(RECT_POS, RECT_SIZE), Color4i::Red); + ASSERT_TRUE(rect != nullptr); + + auto sprite = nom::make_shared_sprite( rect->texture() ); + ASSERT_TRUE(sprite != nullptr); + + auto translate = + nom::create_action(sprite, TRANSLATE_POS, DURATION); + ASSERT_TRUE(translate != nullptr); + + auto repeat = + nom::create_action(translate, NUM_REPEATS); + ASSERT_TRUE(repeat != nullptr); + + auto repeat_reversed = + nom::create_action(repeat); + ASSERT_TRUE(repeat_reversed != nullptr); + + auto action0 = + nom::create_action( {repeat_reversed} ); + ASSERT_TRUE(action0 != nullptr); + action0->set_timing_curve(TIMING_MODE); + action0->set_speed(SPEED_MOD); + action0->set_name("action0"); + + EXPECT_EQ(0, this->player.num_actions() ); + this->run_action_ret = + this->player.run_action(action0, [=]() { + + EXPECT_EQ(EXPECTED_TEX_POS, sprite->position() ); + EXPECT_EQ(1, this->player.num_actions() ); + + this->expected_action_params(action0.get(), 1); + this->expected_common_params(translate.get(), DURATION, SPEED_MOD); + this->expected_repeat_params(repeat.get(), NUM_REPEATS); + }); + EXPECT_EQ(true, this->run_action_ret) + << "Failed to queue action0"; + + EXPECT_EQ(1, this->player.num_actions() ); + + this->append_update_callback( [=](float) { + if( this->expected_min_duration(DURATION, SPEED_MOD) == true ) { + this->quit(); + } + }); + + this->append_render_queue( sprite.get() ); + this->append_frame_interval(FPS); + + EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); +} + +/// \remarks This test does not honor custom speed modifiers passed via command +/// line. +TEST_F(ActionTest, RepeatForeverActionReversed) +{ + // Testing parameters + const real32 TEST_DURATION = 2.5f; // when to stop testing "forever" + const nom::size_type NUM_REPEATS = 4; + const real32 DURATION = 0.5f; + + // IMPORTANT: This value must remain constant for reproducing consistent test + // results! + const real32 SPEED_MOD = 1.0f; + const IActionObject::timing_curve_func TIMING_MODE = + NOM_ACTION_TEST_FLAG(timing_curve); + const uint32 FPS = NOM_ACTION_TEST_FLAG(fps); + + const Point2i TRANSLATE_POS( Point2i(WINDOW_DIMS.w,0) ); + // Initial texture position and size + const Point2i RECT_POS(TRANSLATE_POS); + const Size2i RECT_SIZE(WINDOW_DIMS.w/4, WINDOW_DIMS.h); + + auto rect = + std::make_shared(IntRect(RECT_POS, RECT_SIZE), Color4i::Red); + ASSERT_TRUE(rect != nullptr); + + auto sprite = nom::make_shared_sprite( rect->texture() ); + ASSERT_TRUE(sprite != nullptr); + + auto translate = + nom::create_action(sprite, TRANSLATE_POS, DURATION); + ASSERT_TRUE(translate != nullptr); + + auto repeat_forever = + nom::create_action(translate); + ASSERT_TRUE(repeat_forever != nullptr); + + auto repeat_forever_rev = + nom::create_action(repeat_forever); + ASSERT_TRUE(repeat_forever_rev != nullptr); + + auto action0 = + nom::create_action( {repeat_forever_rev} ); + ASSERT_TRUE(action0 != nullptr); + action0->set_timing_curve(TIMING_MODE); + action0->set_speed(SPEED_MOD); + action0->set_name("action0"); + + auto kill_timer = + nom::create_action(TEST_DURATION); + ASSERT_TRUE(kill_timer != nullptr); + kill_timer->set_speed(SPEED_MOD); + kill_timer->set_name("kill_timer"); + + EXPECT_EQ(0, this->player.num_actions() ); + this->run_action_ret = + this->player.run_action(action0, [=]() { + FAIL() << "action0 should never complete!"; + }); + EXPECT_EQ(true, this->run_action_ret) + << "Failed to queue action0"; + EXPECT_EQ(1, this->player.num_actions() ); + + this->run_action_ret = + this->player.run_action(kill_timer, [=]() { + + EXPECT_EQ(2, this->player.num_actions() ); + this->expected_repeat_params( repeat_forever.get(), NUM_REPEATS, + "repeat_params" ); + + this->expected_common_params( kill_timer.get(), TEST_DURATION, SPEED_MOD, + "common_params" ); + this->expected_common_params( translate.get(), DURATION, SPEED_MOD, + "common_params" ); + + this->expected_action_params(action0.get(), 1, "action_params" ); + + this->quit(); + }); + EXPECT_EQ(true, this->run_action_ret) + << "Failed to queue kill_timer!"; + EXPECT_EQ(2, this->player.num_actions() ); + + this->append_render_queue( sprite.get() ); + this->append_frame_interval(FPS); + + EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); +} + +/// \remarks Thanks goes to Tim Jones of [sdltutorials.com](http://www.sdltutorials.com/sdl-animation) +/// for the sprite frames of Yoshi chosen for this test! +TEST_F(ActionTest, AnimateTexturesActionReversed) +{ + // Testing parameters + texture_frames anim_frames; + const std::vector texture_filenames = {{ + "yoshi_000.png", "yoshi_001.png", "yoshi_002.png", "yoshi_003.png", + "yoshi_004.png", "yoshi_005.png", "yoshi_006.png", "yoshi_007.png" + }}; + + // fps per shown texture + const real32 FRAME_DURATION = 0.5f; + + // total test duration + const real32 DURATION = FRAME_DURATION * texture_filenames.size(); + + const real32 SPEED_MOD = NOM_ACTION_TEST_FLAG(speed); + const IActionObject::timing_curve_func TIMING_MODE = + NOM_ACTION_TEST_FLAG(timing_curve); + const uint32 FPS = NOM_ACTION_TEST_FLAG(fps); + + this->init_animate_textures_test(texture_filenames, anim_frames); + + EXPECT_EQ( anim_frames.size(), texture_filenames.size() ); + + auto sprite0 = + std::make_shared(); + ASSERT_TRUE(sprite0 != nullptr); + + auto tex_bg = + nom::create_action( sprite0, anim_frames, + FRAME_DURATION ); + ASSERT_TRUE(tex_bg != nullptr); + + auto action0 = + nom::create_action(tex_bg); + ASSERT_TRUE(action0 != nullptr); + action0->set_timing_curve(TIMING_MODE); + action0->set_speed(SPEED_MOD); + action0->set_name("action0"); + + auto remove_action0 = + nom::create_action(action0); + ASSERT_TRUE(remove_action0 != nullptr); + remove_action0->set_name("remove_action0"); + + EXPECT_EQ(0, this->player.num_actions() ); + this->run_action_ret = + this->player.run_action(action0, [=]() { + + EXPECT_EQ(1, this->player.num_actions() ); + this->expected_animate_textures_params( tex_bg.get(), anim_frames.size(), + DURATION, SPEED_MOD, + "animate_textures_params" ); + this->expected_common_params(tex_bg.get(), DURATION, SPEED_MOD); + + this->player.run_action(remove_action0, [=]() { + ASSERT_TRUE(sprite0 != nullptr); + EXPECT_FALSE( sprite0->valid() ); + }); + }); + EXPECT_EQ(true, this->run_action_ret) + << "Failed to queue action0"; + EXPECT_EQ(1, this->player.num_actions() ); + + this->append_update_callback( [=](real32) { + if( this->expected_min_duration(DURATION, SPEED_MOD) == true ) { + this->quit(); + } + }); + + this->append_render_queue( sprite0.get() ); + this->append_frame_interval(FPS); + + EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); +} + +} // namespace nom diff --git a/tests/src/actions/ActionTimingCurvesTest.cpp b/tests/src/actions/ActionTimingCurvesTest.cpp new file mode 100644 index 00000000..b2219d07 --- /dev/null +++ b/tests/src/actions/ActionTimingCurvesTest.cpp @@ -0,0 +1,1118 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#include + +#include +#include +#include + +using namespace nom; + +/// \brief Animation engine unit tests +class ActionTimingCurvesTest: public ::testing::Test +{ + public: + /// \remarks This method is called at the start of each unit test. + ActionTimingCurvesTest() + { + // + } + + /// \remarks This method is called at the end of each unit test. + virtual ~ActionTimingCurvesTest() + { + // + } + + /// \remarks This method is called after construction, at the start of each + /// unit test. + virtual void SetUp() + { + // + } + + /// \remarks This method is called before destruction, at the end of each + /// unit test. + virtual void TearDown() + { + // + } + + /// \remarks This method is called at the start of each test case. + static void SetUpTestCase() + { + // + } + + /// \remarks This method is called at the end of each test case. + static void TearDownTestCase() + { + // + } + + protected: + /// \brief The total number of frames, in fractional seconds. + const real32 DURATION = 2.5f * 1000; + + /// \brief The initial value to begin from. + const real32 b = 100.0f; + + /// \brief The total change over time. + const real32 c = 300.0f - b; + + /// \brief The return value of the timing mode function. + real32 ret = 0.0f; +}; + +// ...Linear tests... + +TEST_F(ActionTimingCurvesTest, LinearEaseIn) +{ + ret = nom::Linear::ease_in(0, b, c, DURATION); + EXPECT_FLOAT_EQ(100, ret); + + ret = nom::Linear::ease_in(10, b, c, DURATION); + EXPECT_FLOAT_EQ(100.8, ret); + + ret = nom::Linear::ease_in(50, b, c, DURATION); + EXPECT_FLOAT_EQ(104, ret); + + ret = nom::Linear::ease_in(100, b, c, DURATION); + EXPECT_FLOAT_EQ(108, ret); + + ret = nom::Linear::ease_in(250, b, c, DURATION); + EXPECT_FLOAT_EQ(120, ret); + + ret = nom::Linear::ease_in(500, b, c, DURATION); + EXPECT_FLOAT_EQ(140, ret); + + ret = nom::Linear::ease_in(750, b, c, DURATION); + EXPECT_FLOAT_EQ(160, ret); + + ret = nom::Linear::ease_in(1000, b, c, DURATION); + EXPECT_FLOAT_EQ(180, ret); + + ret = nom::Linear::ease_in(DURATION, b, c, DURATION); + EXPECT_FLOAT_EQ(300, ret); +} + +TEST_F(ActionTimingCurvesTest, LinearEaseOut) +{ + ret = nom::Linear::ease_out(0, b, c, DURATION); + EXPECT_FLOAT_EQ(100, ret); + + ret = nom::Linear::ease_out(10, b, c, DURATION); + EXPECT_FLOAT_EQ(100.8, ret); + + ret = nom::Linear::ease_out(50, b, c, DURATION); + EXPECT_FLOAT_EQ(104, ret); + + ret = nom::Linear::ease_out(100, b, c, DURATION); + EXPECT_FLOAT_EQ(108, ret); + + ret = nom::Linear::ease_out(250, b, c, DURATION); + EXPECT_FLOAT_EQ(120, ret); + + ret = nom::Linear::ease_out(500, b, c, DURATION); + EXPECT_FLOAT_EQ(140, ret); + + ret = nom::Linear::ease_out(750, b, c, DURATION); + EXPECT_FLOAT_EQ(160, ret); + + ret = nom::Linear::ease_out(1000, b, c, DURATION); + EXPECT_FLOAT_EQ(180, ret); + + ret = nom::Linear::ease_out(DURATION, b, c, DURATION); + EXPECT_FLOAT_EQ(300, ret); +} + +TEST_F(ActionTimingCurvesTest, LinearEaseInOut) +{ + ret = nom::Linear::ease_in_out(0, b, c, DURATION); + EXPECT_FLOAT_EQ(100, ret); + + ret = nom::Linear::ease_in_out(10, b, c, DURATION); + EXPECT_FLOAT_EQ(100.8, ret); + + ret = nom::Linear::ease_in_out(50, b, c, DURATION); + EXPECT_FLOAT_EQ(104, ret); + + ret = nom::Linear::ease_in_out(100, b, c, DURATION); + EXPECT_FLOAT_EQ(108, ret); + + ret = nom::Linear::ease_in_out(250, b, c, DURATION); + EXPECT_FLOAT_EQ(120, ret); + + ret = nom::Linear::ease_in_out(500, b, c, DURATION); + EXPECT_FLOAT_EQ(140, ret); + + ret = nom::Linear::ease_in_out(750, b, c, DURATION); + EXPECT_FLOAT_EQ(160, ret); + + ret = nom::Linear::ease_in_out(1000, b, c, DURATION); + EXPECT_FLOAT_EQ(180, ret); + + ret = nom::Linear::ease_in_out(DURATION, b, c, DURATION); + EXPECT_FLOAT_EQ(300, ret); +} + +// ...Quad tests... + +TEST_F(ActionTimingCurvesTest, QuadEaseIn) +{ + ret = nom::Quad::ease_in(0, b, c, DURATION); + EXPECT_FLOAT_EQ(100, ret); + + ret = nom::Quad::ease_in(10, b, c, DURATION); + EXPECT_FLOAT_EQ(100.0032, ret); + + ret = nom::Quad::ease_in(50, b, c, DURATION); + EXPECT_FLOAT_EQ(100.08, ret); + + ret = nom::Quad::ease_in(100, b, c, DURATION); + EXPECT_FLOAT_EQ(100.32, ret); + + ret = nom::Quad::ease_in(250, b, c, DURATION); + EXPECT_FLOAT_EQ(102, ret); + + ret = nom::Quad::ease_in(500, b, c, DURATION); + EXPECT_FLOAT_EQ(108, ret); + + ret = nom::Quad::ease_in(750, b, c, DURATION); + EXPECT_FLOAT_EQ(118, ret); + + ret = nom::Quad::ease_in(1000, b, c, DURATION); + EXPECT_FLOAT_EQ(132, ret); + + ret = nom::Quad::ease_in(DURATION, b, c, DURATION); + EXPECT_FLOAT_EQ(300, ret); +} + +TEST_F(ActionTimingCurvesTest, QuadEaseOut) +{ + ret = nom::Quad::ease_out(0, b, c, DURATION); + EXPECT_FLOAT_EQ(100, ret); + + ret = nom::Quad::ease_out(10, b, c, DURATION); + EXPECT_FLOAT_EQ(101.5968, ret); + + ret = nom::Quad::ease_out(50, b, c, DURATION); + EXPECT_FLOAT_EQ(107.92, ret); + + ret = nom::Quad::ease_out(100, b, c, DURATION); + EXPECT_FLOAT_EQ(115.68, ret); + + ret = nom::Quad::ease_out(250, b, c, DURATION); + EXPECT_FLOAT_EQ(138, ret); + + ret = nom::Quad::ease_out(500, b, c, DURATION); + EXPECT_FLOAT_EQ(172, ret); + + ret = nom::Quad::ease_out(750, b, c, DURATION); + EXPECT_FLOAT_EQ(202, ret); + + ret = nom::Quad::ease_out(1000, b, c, DURATION); + EXPECT_FLOAT_EQ(228, ret); + + ret = nom::Quad::ease_out(DURATION, b, c, DURATION); + EXPECT_FLOAT_EQ(300, ret); +} + +TEST_F(ActionTimingCurvesTest, QuadEaseInOut) +{ + ret = nom::Quad::ease_in_out(0, b, c, DURATION); + EXPECT_FLOAT_EQ(100, ret); + + ret = nom::Quad::ease_in_out(10, b, c, DURATION); + EXPECT_FLOAT_EQ(100.0064, ret); + + ret = nom::Quad::ease_in_out(50, b, c, DURATION); + EXPECT_FLOAT_EQ(100.16, ret); + + ret = nom::Quad::ease_in_out(100, b, c, DURATION); + EXPECT_FLOAT_EQ(100.64, ret); + + ret = nom::Quad::ease_in_out(250, b, c, DURATION); + EXPECT_FLOAT_EQ(104, ret); + + ret = nom::Quad::ease_in_out(500, b, c, DURATION); + EXPECT_FLOAT_EQ(116, ret); + + ret = nom::Quad::ease_in_out(750, b, c, DURATION); + EXPECT_FLOAT_EQ(136, ret); + + ret = nom::Quad::ease_in_out(1000, b, c, DURATION); + EXPECT_FLOAT_EQ(164, ret); + + ret = nom::Quad::ease_in_out(DURATION, b, c, DURATION); + EXPECT_FLOAT_EQ(300, ret); +} + +// ...Cubic tests... + +TEST_F(ActionTimingCurvesTest, CubicEaseIn) +{ + ret = nom::Cubic::ease_in(0, b, c, DURATION); + EXPECT_FLOAT_EQ(100, ret); + + ret = nom::Cubic::ease_in(10, b, c, DURATION); + EXPECT_FLOAT_EQ(100.00002, ret); + + ret = nom::Cubic::ease_in(50, b, c, DURATION); + EXPECT_FLOAT_EQ(100.0016, ret); + + ret = nom::Cubic::ease_in(100, b, c, DURATION); + EXPECT_FLOAT_EQ(100.0128, ret); + + ret = nom::Cubic::ease_in(250, b, c, DURATION); + EXPECT_FLOAT_EQ(100.2, ret); + + ret = nom::Cubic::ease_in(500, b, c, DURATION); + EXPECT_FLOAT_EQ(101.6, ret); + + ret = nom::Cubic::ease_in(750, b, c, DURATION); + EXPECT_FLOAT_EQ(105.4, ret); + + ret = nom::Cubic::ease_in(1000, b, c, DURATION); + EXPECT_FLOAT_EQ(112.8, ret); + + ret = nom::Cubic::ease_in(DURATION, b, c, DURATION); + EXPECT_FLOAT_EQ(300, ret); +} + +TEST_F(ActionTimingCurvesTest, CubicEaseOut) +{ + ret = nom::Cubic::ease_out(0, b, c, DURATION); + EXPECT_FLOAT_EQ(100, ret); + + ret = nom::Cubic::ease_out(10, b, c, DURATION); + EXPECT_FLOAT_EQ(102.39042, ret); + + ret = nom::Cubic::ease_out(50, b, c, DURATION); + EXPECT_FLOAT_EQ(111.76158, ret); + + ret = nom::Cubic::ease_out(100, b, c, DURATION); + EXPECT_FLOAT_EQ(123.05281, ret); + + ret = nom::Cubic::ease_out(250, b, c, DURATION); + EXPECT_FLOAT_EQ(154.20001, ret); + + ret = nom::Cubic::ease_out(500, b, c, DURATION); + EXPECT_FLOAT_EQ(197.60001, ret); + + ret = nom::Cubic::ease_out(750, b, c, DURATION); + EXPECT_FLOAT_EQ(231.39999, ret); + + ret = nom::Cubic::ease_out(1000, b, c, DURATION); + EXPECT_FLOAT_EQ(256.79999, ret); + + ret = nom::Cubic::ease_out(DURATION, b, c, DURATION); + EXPECT_FLOAT_EQ(300, ret); +} + +TEST_F(ActionTimingCurvesTest, CubicEaseInOut) +{ + ret = nom::Cubic::ease_in_out(0, b, c, DURATION); + EXPECT_FLOAT_EQ(100, ret); + + ret = nom::Cubic::ease_in_out(10, b, c, DURATION); + EXPECT_FLOAT_EQ(100.00005, ret); + + ret = nom::Cubic::ease_in_out(50, b, c, DURATION); + EXPECT_FLOAT_EQ(100.0064, ret); + + ret = nom::Cubic::ease_in_out(100, b, c, DURATION); + EXPECT_FLOAT_EQ(100.0512, ret); + + ret = nom::Cubic::ease_in_out(250, b, c, DURATION); + EXPECT_FLOAT_EQ(100.8, ret); + + ret = nom::Cubic::ease_in_out(500, b, c, DURATION); + EXPECT_FLOAT_EQ(106.4, ret); + + ret = nom::Cubic::ease_in_out(750, b, c, DURATION); + EXPECT_FLOAT_EQ(121.60001, ret); + + ret = nom::Cubic::ease_in_out(1000, b, c, DURATION); + EXPECT_FLOAT_EQ(151.2, ret); + + ret = nom::Cubic::ease_in_out(DURATION, b, c, DURATION); + EXPECT_FLOAT_EQ(300, ret); +} + +// ...Quart tests... + +TEST_F(ActionTimingCurvesTest, QuartEaseIn) +{ + ret = nom::Quart::ease_in(0, b, c, DURATION); + EXPECT_FLOAT_EQ(100, ret); + + ret = nom::Quart::ease_in(10, b, c, DURATION); + EXPECT_FLOAT_EQ(100.00002, ret); + + ret = nom::Quart::ease_in(50, b, c, DURATION); + EXPECT_FLOAT_EQ(100.00003, ret); + + ret = nom::Quart::ease_in(100, b, c, DURATION); + EXPECT_FLOAT_EQ(100.00051, ret); + + ret = nom::Quart::ease_in(250, b, c, DURATION); + EXPECT_FLOAT_EQ(100.02, ret); + + ret = nom::Quart::ease_in(500, b, c, DURATION); + EXPECT_FLOAT_EQ(100.32, ret); + + ret = nom::Quart::ease_in(750, b, c, DURATION); + EXPECT_FLOAT_EQ(101.62, ret); + + ret = nom::Quart::ease_in(1000, b, c, DURATION); + EXPECT_FLOAT_EQ(105.12, ret); + + ret = nom::Quart::ease_in(DURATION, b, c, DURATION); + EXPECT_FLOAT_EQ(300, ret); +} + +TEST_F(ActionTimingCurvesTest, QuartEaseOut) +{ + ret = nom::Quart::ease_out(0, b, c, DURATION); + EXPECT_FLOAT_EQ(100, ret); + + ret = nom::Quart::ease_out(10, b, c, DURATION); + EXPECT_FLOAT_EQ(103.18086, ret); + + ret = nom::Quart::ease_out(50, b, c, DURATION); + EXPECT_FLOAT_EQ(115.52634, ret); + + ret = nom::Quart::ease_out(100, b, c, DURATION); + EXPECT_FLOAT_EQ(130.13071, ret); + + ret = nom::Quart::ease_out(250, b, c, DURATION); + EXPECT_FLOAT_EQ(168.78001, ret); + + ret = nom::Quart::ease_out(500, b, c, DURATION); + EXPECT_FLOAT_EQ(218.07999, ret); + + ret = nom::Quart::ease_out(750, b, c, DURATION); + EXPECT_FLOAT_EQ(251.98, ret); + + ret = nom::Quart::ease_out(1000, b, c, DURATION); + EXPECT_FLOAT_EQ(274.07999, ret); + + ret = nom::Quart::ease_out(DURATION, b, c, DURATION); + EXPECT_FLOAT_EQ(300, ret); +} + +TEST_F(ActionTimingCurvesTest, QuartEaseInOut) +{ + ret = nom::Quart::ease_in_out(0, b, c, DURATION); + EXPECT_FLOAT_EQ(100, ret); + + ret = nom::Quart::ease_in_out(10, b, c, DURATION); + EXPECT_FLOAT_EQ(100, ret); + + ret = nom::Quart::ease_in_out(50, b, c, DURATION); + EXPECT_FLOAT_EQ(100.00026, ret); + + ret = nom::Quart::ease_in_out(100, b, c, DURATION); + EXPECT_FLOAT_EQ(100.0041, ret); + + ret = nom::Quart::ease_in_out(250, b, c, DURATION); + EXPECT_FLOAT_EQ(100.16, ret); + + ret = nom::Quart::ease_in_out(500, b, c, DURATION); + EXPECT_FLOAT_EQ(102.56, ret); + + ret = nom::Quart::ease_in_out(750, b, c, DURATION); + EXPECT_FLOAT_EQ(112.96, ret); + + ret = nom::Quart::ease_in_out(1000, b, c, DURATION); + EXPECT_FLOAT_EQ(140.96001, ret); + + ret = nom::Quart::ease_in_out(DURATION, b, c, DURATION); + EXPECT_FLOAT_EQ(300, ret); +} + +// ...Quint tests... + +TEST_F(ActionTimingCurvesTest, QuintEaseIn) +{ + ret = nom::Quint::ease_in(0, b, c, DURATION); + EXPECT_FLOAT_EQ(100, ret); + + ret = nom::Quint::ease_in(10, b, c, DURATION); + EXPECT_FLOAT_EQ(100.00002, ret); + + ret = nom::Quint::ease_in(50, b, c, DURATION); + EXPECT_FLOAT_EQ(100, ret); + + ret = nom::Quint::ease_in(100, b, c, DURATION); + EXPECT_FLOAT_EQ(100.00002, ret); + + ret = nom::Quint::ease_in(250, b, c, DURATION); + EXPECT_FLOAT_EQ(100.002, ret); + + ret = nom::Quint::ease_in(500, b, c, DURATION); + EXPECT_FLOAT_EQ(100.064, ret); + + ret = nom::Quint::ease_in(750, b, c, DURATION); + EXPECT_FLOAT_EQ(100.486, ret); + + ret = nom::Quint::ease_in(1000, b, c, DURATION); + EXPECT_FLOAT_EQ(102.048, ret); + + ret = nom::Quint::ease_in(DURATION, b, c, DURATION); + EXPECT_FLOAT_EQ(300, ret); +} + +TEST_F(ActionTimingCurvesTest, QuintEaseOut) +{ + ret = nom::Quint::ease_out(0, b, c, DURATION); + EXPECT_FLOAT_EQ(100, ret); + + ret = nom::Quint::ease_out(10, b, c, DURATION); + EXPECT_FLOAT_EQ(103.96814, ret); + + ret = nom::Quint::ease_out(50, b, c, DURATION); + EXPECT_FLOAT_EQ(119.21581, ret); + + ret = nom::Quint::ease_out(100, b, c, DURATION); + EXPECT_FLOAT_EQ(136.92548, ret); + + ret = nom::Quint::ease_out(250, b, c, DURATION); + EXPECT_FLOAT_EQ(181.90201, ret); + + ret = nom::Quint::ease_out(500, b, c, DURATION); + EXPECT_FLOAT_EQ(234.464, ret); + + ret = nom::Quint::ease_out(750, b, c, DURATION); + EXPECT_FLOAT_EQ(266.38599, ret); + + ret = nom::Quint::ease_out(1000, b, c, DURATION); + EXPECT_FLOAT_EQ(284.448, ret); + + ret = nom::Quint::ease_out(DURATION, b, c, DURATION); + EXPECT_FLOAT_EQ(300, ret); +} + +TEST_F(ActionTimingCurvesTest, QuintEaseInOut) +{ + ret = nom::Quint::ease_in_out(0, b, c, DURATION); + EXPECT_FLOAT_EQ(100, ret); + + ret = nom::Quint::ease_in_out(10, b, c, DURATION); + EXPECT_FLOAT_EQ(100, ret); + + ret = nom::Quint::ease_in_out(50, b, c, DURATION); + EXPECT_FLOAT_EQ(100.00001, ret); + + ret = nom::Quint::ease_in_out(100, b, c, DURATION); + EXPECT_FLOAT_EQ(100.00033, ret); + + ret = nom::Quint::ease_in_out(250, b, c, DURATION); + EXPECT_FLOAT_EQ(100.032, ret); + + ret = nom::Quint::ease_in_out(500, b, c, DURATION); + EXPECT_FLOAT_EQ(101.024, ret); + + ret = nom::Quint::ease_in_out(750, b, c, DURATION); + EXPECT_FLOAT_EQ(107.776, ret); + + ret = nom::Quint::ease_in_out(1000, b, c, DURATION); + EXPECT_FLOAT_EQ(132.76801, ret); + + ret = nom::Quint::ease_in_out(DURATION, b, c, DURATION); + EXPECT_FLOAT_EQ(300, ret); +} + +// ...Back tests... + +TEST_F(ActionTimingCurvesTest, BackEaseIn) +{ + ret = nom::Back::ease_in(0, b, c, DURATION); + EXPECT_FLOAT_EQ(100, ret); + + ret = nom::Back::ease_in(10, b, c, DURATION); + EXPECT_FLOAT_EQ(99.994591, ret); + + ret = nom::Back::ease_in(50, b, c, DURATION); + EXPECT_FLOAT_EQ(99.868195, ret); + + ret = nom::Back::ease_in(100, b, c, DURATION); + EXPECT_FLOAT_EQ(99.490074, ret); + + ret = nom::Back::ease_in(250, b, c, DURATION); + EXPECT_FLOAT_EQ(97.137154, ret); + + ret = nom::Back::ease_in(500, b, c, DURATION); + EXPECT_FLOAT_EQ(90.709885, ret); + + ret = nom::Back::ease_in(750, b, c, DURATION); + EXPECT_FLOAT_EQ(83.960091, ret); + + ret = nom::Back::ease_in(1000, b, c, DURATION); + EXPECT_FLOAT_EQ(80.129669, ret); + + ret = nom::Back::ease_in(DURATION, b, c, DURATION); + EXPECT_FLOAT_EQ(300, ret); +} + +TEST_F(ActionTimingCurvesTest, BackEaseOut) +{ + ret = nom::Back::ease_out(0, b, c, DURATION); + EXPECT_FLOAT_EQ(100, ret); + + ret = nom::Back::ease_out(10, b, c, DURATION); + EXPECT_FLOAT_EQ(103.74081, ret); + + ret = nom::Back::ease_out(50, b, c, DURATION); + EXPECT_FLOAT_EQ(118.29839, ret); + + ret = nom::Back::ease_out(100, b, c, DURATION); + EXPECT_FLOAT_EQ(135.59822, ret); + + ret = nom::Back::ease_out(250, b, c, DURATION); + EXPECT_FLOAT_EQ(181.76562, ret); + + ret = nom::Back::ease_out(500, b, c, DURATION); + EXPECT_FLOAT_EQ(241.16043, ret); + + ret = nom::Back::ease_out(750, b, c, DURATION); + EXPECT_FLOAT_EQ(281.42645, ret); + + ret = nom::Back::ease_out(1000, b, c, DURATION); + EXPECT_FLOAT_EQ(305.80548, ret); + + ret = nom::Back::ease_out(DURATION, b, c, DURATION); + EXPECT_FLOAT_EQ(300, ret); +} + +TEST_F(ActionTimingCurvesTest, BackEaseInOut) +{ + ret = nom::Back::ease_in_out(0, b, c, DURATION); + EXPECT_FLOAT_EQ(100, ret); + + ret = nom::Back::ease_in_out(10, b, c, DURATION); + EXPECT_FLOAT_EQ(99.983574, ret); + + ret = nom::Back::ease_in_out(50, b, c, DURATION); + EXPECT_FLOAT_EQ(99.607819, ret); + + ret = nom::Back::ease_in_out(100, b, c, DURATION); + EXPECT_FLOAT_EQ(98.523315, ret); + + ret = nom::Back::ease_in_out(250, b, c, DURATION); + EXPECT_FLOAT_EQ(92.496292, ret); + + ret = nom::Back::ease_in_out(500, b, c, DURATION); + EXPECT_FLOAT_EQ(81.488869, ret); + + ret = nom::Back::ease_in_out(750, b, c, DURATION); + EXPECT_FLOAT_EQ(84.233307, ret); + + ret = nom::Back::ease_in_out(1000, b, c, DURATION); + EXPECT_FLOAT_EQ(117.98517, ret); + + ret = nom::Back::ease_in_out(DURATION, b, c, DURATION); + EXPECT_FLOAT_EQ(300, ret); +} + +// ...Bounce tests... + +TEST_F(ActionTimingCurvesTest, BounceEaseIn) +{ + ret = nom::Bounce::ease_in(0, b, c, DURATION); + EXPECT_FLOAT_EQ(100, ret); + + ret = nom::Bounce::ease_in(10, b, c, DURATION); + EXPECT_FLOAT_EQ(100.5258, ret); + + ret = nom::Bounce::ease_in(50, b, c, DURATION); + EXPECT_FLOAT_EQ(102.14499, ret); + + ret = nom::Bounce::ease_in(100, b, c, DURATION); + EXPECT_FLOAT_EQ(103.08, ret); + + ret = nom::Bounce::ease_in(250, b, c, DURATION); + EXPECT_FLOAT_EQ(102.375, ret); + + ret = nom::Bounce::ease_in(500, b, c, DURATION); + EXPECT_FLOAT_EQ(112, ret); + + ret = nom::Bounce::ease_in(750, b, c, DURATION); + EXPECT_FLOAT_EQ(113.875, ret); + + ret = nom::Bounce::ease_in(1000, b, c, DURATION); + EXPECT_FLOAT_EQ(145.5, ret); + + ret = nom::Bounce::ease_in(DURATION, b, c, DURATION); + EXPECT_FLOAT_EQ(300, ret); +} + +TEST_F(ActionTimingCurvesTest, BounceEaseOut) +{ + ret = nom::Bounce::ease_out(0, b, c, DURATION); + EXPECT_FLOAT_EQ(100, ret); + + ret = nom::Bounce::ease_out(10, b, c, DURATION); + EXPECT_FLOAT_EQ(100.0242, ret); + + ret = nom::Bounce::ease_out(50, b, c, DURATION); + EXPECT_FLOAT_EQ(100.605, ret); + + ret = nom::Bounce::ease_out(100, b, c, DURATION); + EXPECT_FLOAT_EQ(102.42, ret); + + ret = nom::Bounce::ease_out(250, b, c, DURATION); + EXPECT_FLOAT_EQ(115.125, ret); + + ret = nom::Bounce::ease_out(500, b, c, DURATION); + EXPECT_FLOAT_EQ(160.5, ret); + + ret = nom::Bounce::ease_out(750, b, c, DURATION); + EXPECT_FLOAT_EQ(236.12502, ret); + + ret = nom::Bounce::ease_out(1000, b, c, DURATION); + EXPECT_FLOAT_EQ(282, ret); + + ret = nom::Bounce::ease_out(DURATION, b, c, DURATION); + EXPECT_FLOAT_EQ(300, ret); +} + +TEST_F(ActionTimingCurvesTest, BounceEaseInOut) +{ + ret = nom::Bounce::ease_in_out(0, b, c, DURATION); + EXPECT_FLOAT_EQ(100, ret); + + ret = nom::Bounce::ease_in_out(10, b, c, DURATION); + EXPECT_FLOAT_EQ(100.5016, ret); + + ret = nom::Bounce::ease_in_out(50, b, c, DURATION); + EXPECT_FLOAT_EQ(101.54, ret); + + ret = nom::Bounce::ease_in_out(100, b, c, DURATION); + EXPECT_FLOAT_EQ(100.66, ret); + + ret = nom::Bounce::ease_in_out(250, b, c, DURATION); + EXPECT_FLOAT_EQ(106, ret); + + ret = nom::Bounce::ease_in_out(500, b, c, DURATION); + EXPECT_FLOAT_EQ(122.75, ret); + + ret = nom::Bounce::ease_in_out(750, b, c, DURATION); + EXPECT_FLOAT_EQ(109, ret); + + ret = nom::Bounce::ease_in_out(1000, b, c, DURATION); + EXPECT_FLOAT_EQ(169.75, ret); + + ret = nom::Bounce::ease_in_out(DURATION, b, c, DURATION); + EXPECT_FLOAT_EQ(300, ret); +} + +// ...Circ tests... + +TEST_F(ActionTimingCurvesTest, CircEaseIn) +{ + ret = nom::Circ::ease_in(0, b, c, DURATION); + EXPECT_FLOAT_EQ(100, ret); + + ret = nom::Circ::ease_in(10, b, c, DURATION); + EXPECT_FLOAT_EQ(100.00159, ret); + + ret = nom::Circ::ease_in(50, b, c, DURATION); + EXPECT_FLOAT_EQ(100.04, ret); + + ret = nom::Circ::ease_in(100, b, c, DURATION); + EXPECT_FLOAT_EQ(100.16006, ret); + + ret = nom::Circ::ease_in(250, b, c, DURATION); + EXPECT_FLOAT_EQ(101.00251, ret); + + ret = nom::Circ::ease_in(500, b, c, DURATION); + EXPECT_FLOAT_EQ(104.04082, ret); + + ret = nom::Circ::ease_in(750, b, c, DURATION); + EXPECT_FLOAT_EQ(109.21217, ret); + + ret = nom::Circ::ease_in(1000, b, c, DURATION); + EXPECT_FLOAT_EQ(116.69698, ret); + + ret = nom::Circ::ease_in(DURATION, b, c, DURATION); + EXPECT_FLOAT_EQ(300, ret); +} + +TEST_F(ActionTimingCurvesTest, CircEaseOut) +{ + ret = nom::Circ::ease_out(0, b, c, DURATION); + EXPECT_FLOAT_EQ(100, ret); + + ret = nom::Circ::ease_out(10, b, c, DURATION); + EXPECT_FLOAT_EQ(117.8707, ret); + + ret = nom::Circ::ease_out(50, b, c, DURATION); + EXPECT_FLOAT_EQ(139.79947, ret); + + ret = nom::Circ::ease_out(100, b, c, DURATION); + EXPECT_FLOAT_EQ(156, ret); + + ret = nom::Circ::ease_out(250, b, c, DURATION); + EXPECT_FLOAT_EQ(187.17799, ret); + + ret = nom::Circ::ease_out(500, b, c, DURATION); + EXPECT_FLOAT_EQ(220, ret); + + ret = nom::Circ::ease_out(750, b, c, DURATION); + EXPECT_FLOAT_EQ(242.82857, ret); + + ret = nom::Circ::ease_out(1000, b, c, DURATION); + EXPECT_FLOAT_EQ(260, ret); + + ret = nom::Circ::ease_out(DURATION, b, c, DURATION); + EXPECT_FLOAT_EQ(300, ret); +} + +TEST_F(ActionTimingCurvesTest, CircEaseInOut) +{ + ret = nom::Circ::ease_in_out(0, b, c, DURATION); + EXPECT_FLOAT_EQ(100, ret); + + ret = nom::Circ::ease_in_out(10, b, c, DURATION); + EXPECT_FLOAT_EQ(100.0032, ret); + + ret = nom::Circ::ease_in_out(50, b, c, DURATION); + EXPECT_FLOAT_EQ(100.08003, ret); + + ret = nom::Circ::ease_in_out(100, b, c, DURATION); + EXPECT_FLOAT_EQ(100.32051, ret); + + ret = nom::Circ::ease_in_out(250, b, c, DURATION); + EXPECT_FLOAT_EQ(102.02041, ret); + + ret = nom::Circ::ease_in_out(500, b, c, DURATION); + EXPECT_FLOAT_EQ(108.34849, ret); + + ret = nom::Circ::ease_in_out(750, b, c, DURATION); + EXPECT_FLOAT_EQ(120, ret); + + ret = nom::Circ::ease_in_out(1000, b, c, DURATION); + EXPECT_FLOAT_EQ(140, ret); + + ret = nom::Circ::ease_in_out(DURATION, b, c, DURATION); + EXPECT_FLOAT_EQ(300, ret); +} + +// ...Elastic tests... + +TEST_F(ActionTimingCurvesTest, ElasticEaseIn) +{ + ret = nom::Elastic::ease_in(0, b, c, DURATION); + EXPECT_FLOAT_EQ(100, ret); + + ret = nom::Elastic::ease_in(10, b, c, DURATION); + EXPECT_FLOAT_EQ(99.914505, ret); + + ret = nom::Elastic::ease_in(50, b, c, DURATION); + EXPECT_FLOAT_EQ(99.976547, ret); + + ret = nom::Elastic::ease_in(100, b, c, DURATION); + EXPECT_FLOAT_EQ(100.07964, ret); + + ret = nom::Elastic::ease_in(250, b, c, DURATION); + EXPECT_FLOAT_EQ(100.39062, ret); + + ret = nom::Elastic::ease_in(500, b, c, DURATION); + EXPECT_FLOAT_EQ(99.609375, ret); + + ret = nom::Elastic::ease_in(750, b, c, DURATION); + EXPECT_FLOAT_EQ(99.21875, ret); + + ret = nom::Elastic::ease_in(1000, b, c, DURATION); + EXPECT_FLOAT_EQ(103.125, ret); + + ret = nom::Elastic::ease_in(DURATION, b, c, DURATION); + EXPECT_FLOAT_EQ(300, ret); +} + +TEST_F(ActionTimingCurvesTest, ElasticEaseOut) +{ + ret = nom::Elastic::ease_out(0, b, c, DURATION); + EXPECT_FLOAT_EQ(100, ret); + + ret = nom::Elastic::ease_out(10, b, c, DURATION); + EXPECT_FLOAT_EQ(106.15126, ret); + + ret = nom::Elastic::ease_out(50, b, c, DURATION); + EXPECT_FLOAT_EQ(140.94249, ret); + + ret = nom::Elastic::ease_out(100, b, c, DURATION); + EXPECT_FLOAT_EQ(198.57877, ret); + + ret = nom::Elastic::ease_out(250, b, c, DURATION); + EXPECT_FLOAT_EQ(350, ret); + + ret = nom::Elastic::ease_out(500, b, c, DURATION); + EXPECT_FLOAT_EQ(325, ret); + + ret = nom::Elastic::ease_out(750, b, c, DURATION); + EXPECT_FLOAT_EQ(275, ret); + + ret = nom::Elastic::ease_out(1000, b, c, DURATION); + EXPECT_FLOAT_EQ(306.25, ret); + + ret = nom::Elastic::ease_out(DURATION, b, c, DURATION); + EXPECT_FLOAT_EQ(300, ret); +} + +TEST_F(ActionTimingCurvesTest, ElasticEaseInOut) +{ + ret = nom::Elastic::ease_in_out(0, b, c, DURATION); + EXPECT_FLOAT_EQ(100, ret); + + ret = nom::Elastic::ease_in_out(10, b, c, DURATION); + EXPECT_FLOAT_EQ(100.02914, ret); + + ret = nom::Elastic::ease_in_out(50, b, c, DURATION); + EXPECT_FLOAT_EQ(100.08622, ret); + + ret = nom::Elastic::ease_in_out(100, b, c, DURATION); + EXPECT_FLOAT_EQ(100.16344, ret); + + ret = nom::Elastic::ease_in_out(250, b, c, DURATION); + EXPECT_FLOAT_EQ(100.06783, ret); + + ret = nom::Elastic::ease_in_out(500, b, c, DURATION); + EXPECT_FLOAT_EQ(99.21875, ret); + + ret = nom::Elastic::ease_in_out(750, b, c, DURATION); + EXPECT_FLOAT_EQ(104.78778, ret); + + ret = nom::Elastic::ease_in_out(1000, b, c, DURATION); + EXPECT_FLOAT_EQ(76.507683, ret); + + ret = nom::Elastic::ease_in_out(DURATION, b, c, DURATION); + EXPECT_FLOAT_EQ(300, ret); +} + +// ...Expo tests... + +TEST_F(ActionTimingCurvesTest, ExpoEaseIn) +{ + ret = nom::Expo::ease_in(0, b, c, DURATION); + EXPECT_FLOAT_EQ(100, ret); + + ret = nom::Expo::ease_in(10, b, c, DURATION); + EXPECT_FLOAT_EQ(100.20081, ret); + + ret = nom::Expo::ease_in(50, b, c, DURATION); + EXPECT_FLOAT_EQ(100.22436, ret); + + ret = nom::Expo::ease_in(100, b, c, DURATION); + EXPECT_FLOAT_EQ(100.25771, ret); + + ret = nom::Expo::ease_in(250, b, c, DURATION); + EXPECT_FLOAT_EQ(100.39062, ret); + + ret = nom::Expo::ease_in(500, b, c, DURATION); + EXPECT_FLOAT_EQ(100.78125, ret); + + ret = nom::Expo::ease_in(750, b, c, DURATION); + EXPECT_FLOAT_EQ(101.5625, ret); + + ret = nom::Expo::ease_in(1000, b, c, DURATION); + EXPECT_FLOAT_EQ(103.125, ret); + + ret = nom::Expo::ease_in(DURATION, b, c, DURATION); + EXPECT_FLOAT_EQ(300, ret); +} + +TEST_F(ActionTimingCurvesTest, ExpoEaseOut) +{ + ret = nom::Expo::ease_out(0, b, c, DURATION); + EXPECT_FLOAT_EQ(100, ret); + + ret = nom::Expo::ease_out(10, b, c, DURATION); + EXPECT_FLOAT_EQ(105.46901, ret); + + ret = nom::Expo::ease_out(50, b, c, DURATION); + EXPECT_FLOAT_EQ(125.88988, ret); + + ret = nom::Expo::ease_out(100, b, c, DURATION); + EXPECT_FLOAT_EQ(148.42834, ret); + + ret = nom::Expo::ease_out(250, b, c, DURATION); + EXPECT_FLOAT_EQ(200, ret); + + ret = nom::Expo::ease_out(500, b, c, DURATION); + EXPECT_FLOAT_EQ(250, ret); + + ret = nom::Expo::ease_out(750, b, c, DURATION); + EXPECT_FLOAT_EQ(275, ret); + + ret = nom::Expo::ease_out(1000, b, c, DURATION); + EXPECT_FLOAT_EQ(287.5, ret); + + ret = nom::Expo::ease_out(DURATION, b, c, DURATION); + EXPECT_FLOAT_EQ(300, ret); +} + +TEST_F(ActionTimingCurvesTest, ExpoEaseInOut) +{ + ret = nom::Expo::ease_in_out(0, b, c, DURATION); + EXPECT_FLOAT_EQ(100, ret); + + ret = nom::Expo::ease_in_out(10, b, c, DURATION); + EXPECT_FLOAT_EQ(100.10322, ret); + + ret = nom::Expo::ease_in_out(50, b, c, DURATION); + EXPECT_FLOAT_EQ(100.12886, ret); + + ret = nom::Expo::ease_in_out(100, b, c, DURATION); + EXPECT_FLOAT_EQ(100.17003, ret); + + ret = nom::Expo::ease_in_out(250, b, c, DURATION); + EXPECT_FLOAT_EQ(100.39062, ret); + + ret = nom::Expo::ease_in_out(500, b, c, DURATION); + EXPECT_FLOAT_EQ(101.5625, ret); + + ret = nom::Expo::ease_in_out(750, b, c, DURATION); + EXPECT_FLOAT_EQ(106.25, ret); + + ret = nom::Expo::ease_in_out(1000, b, c, DURATION); + EXPECT_FLOAT_EQ(125, ret); + + ret = nom::Expo::ease_in_out(DURATION, b, c, DURATION); + EXPECT_FLOAT_EQ(300, ret); +} + +// ...Sine tests... + +TEST_F(ActionTimingCurvesTest, SineEaseIn) +{ + ret = nom::Sine::ease_in(0, b, c, DURATION); + EXPECT_FLOAT_EQ(100, ret); + + ret = nom::Sine::ease_in(10, b, c, DURATION); + EXPECT_FLOAT_EQ(100.00394, ret); + + ret = nom::Sine::ease_in(50, b, c, DURATION); + EXPECT_FLOAT_EQ(100.09869, ret); + + ret = nom::Sine::ease_in(100, b, c, DURATION); + EXPECT_FLOAT_EQ(100.39465, ret); + + ret = nom::Sine::ease_in(250, b, c, DURATION); + EXPECT_FLOAT_EQ(102.46233, ret); + + ret = nom::Sine::ease_in(500, b, c, DURATION); + EXPECT_FLOAT_EQ(109.7887, ret); + + ret = nom::Sine::ease_in(750, b, c, DURATION); + EXPECT_FLOAT_EQ(121.7987, ret); + + ret = nom::Sine::ease_in(1000, b, c, DURATION); + EXPECT_FLOAT_EQ(138.19661, ret); + + ret = nom::Sine::ease_in(DURATION, b, c, DURATION); + EXPECT_FLOAT_EQ(300, ret); +} + +TEST_F(ActionTimingCurvesTest, SineEaseOut) +{ + ret = nom::Sine::ease_out(0, b, c, DURATION); + EXPECT_FLOAT_EQ(100, ret); + + ret = nom::Sine::ease_out(10, b, c, DURATION); + EXPECT_FLOAT_EQ(101.25663, ret); + + ret = nom::Sine::ease_out(50, b, c, DURATION); + EXPECT_FLOAT_EQ(106.28215, ret); + + ret = nom::Sine::ease_out(100, b, c, DURATION); + EXPECT_FLOAT_EQ(112.55811, ret); + + ret = nom::Sine::ease_out(250, b, c, DURATION); + EXPECT_FLOAT_EQ(131.2869, ret); + + ret = nom::Sine::ease_out(500, b, c, DURATION); + EXPECT_FLOAT_EQ(161.80341, ret); + + ret = nom::Sine::ease_out(750, b, c, DURATION); + EXPECT_FLOAT_EQ(190.7981, ret); + + ret = nom::Sine::ease_out(1000, b, c, DURATION); + EXPECT_FLOAT_EQ(217.55705, ret); + + ret = nom::Sine::ease_out(DURATION, b, c, DURATION); + EXPECT_FLOAT_EQ(300, ret); +} + +TEST_F(ActionTimingCurvesTest, SineEaseInOut) +{ + ret = nom::Sine::ease_in_out(0, b, c, DURATION); + EXPECT_FLOAT_EQ(100, ret); + + ret = nom::Sine::ease_in_out(10, b, c, DURATION); + EXPECT_FLOAT_EQ(100.0079, ret); + + ret = nom::Sine::ease_in_out(50, b, c, DURATION); + EXPECT_FLOAT_EQ(100.19733, ret); + + ret = nom::Sine::ease_in_out(100, b, c, DURATION); + EXPECT_FLOAT_EQ(100.78853, ret); + + ret = nom::Sine::ease_in_out(250, b, c, DURATION); + EXPECT_FLOAT_EQ(104.89435, ret); + + ret = nom::Sine::ease_in_out(500, b, c, DURATION); + EXPECT_FLOAT_EQ(119.0983, ret); + + ret = nom::Sine::ease_in_out(750, b, c, DURATION); + EXPECT_FLOAT_EQ(141.22148, ret); + + ret = nom::Sine::ease_in_out(1000, b, c, DURATION); + EXPECT_FLOAT_EQ(169.0983, ret); + + ret = nom::Sine::ease_in_out(DURATION, b, c, DURATION); + EXPECT_FLOAT_EQ(300, ret); +} + +int main(int argc, char** argv) +{ + ::testing::InitGoogleTest(&argc, argv); + + // Set the current working directory path to the path leading to this + // executable file; used for unit tests that require file-system I/O. + if( nom::init(argc, argv) == false ) { + NOM_LOG_CRIT(NOM_LOG_CATEGORY_APPLICATION, "Could not initialize nomlib."); + return NOM_EXIT_FAILURE; + } + atexit(nom::quit); + + return RUN_ALL_TESTS(); +} diff --git a/tests/src/actions/CMakeLists.txt b/tests/src/actions/CMakeLists.txt new file mode 100644 index 00000000..89adb150 --- /dev/null +++ b/tests/src/actions/CMakeLists.txt @@ -0,0 +1,113 @@ +# nomlib-actions module tests + +set( NOM_BUILD_ACTION_TESTS ON ) +set( NOM_BUILD_ACTION_TIMING_CURVES_TESTS ON ) + +if( EXISTS "${CMAKE_CURRENT_LIST_DIR}/local_env.cmake" ) + include( "${CMAKE_CURRENT_LIST_DIR}/local_env.cmake" ) +endif() + +if( NOM_BUILD_ACTION_TESTS ) + + set( ACTION_SRC + ${ACTION_SRC} + "ActionTest.cpp" + "ActionTest_ActionPlayer.cpp" + "ActionTest_AlphaBlending.cpp" + "ActionTest_Reversed.cpp" + "ActionTest_Demos.cpp" + "ActionTest_Repeating.cpp" + ) + + add_executable( ActionTest ${ACTION_SRC} ) + + target_link_libraries(ActionTest nomlib-visual-unit-test nomlib-actions + nomlib-graphics) + + set( ACTION_TEST_BIN "${TESTS_INSTALL_DIR}/ActionTest" ) + + # NOTE: Any frame rate that your hardware can handle should be fine here; a + # conservative value is chosen here chiefly to lower system utilization + # during test runs. + set( ACTION_TEST_ARGS --fps 30 ) + + # Fractional speed modifier values, such as 22.5 and the oddball number of 45 + # seemed to carry the most bugs during development of this feature. + nom_add_test( ActionTest_SpeedModifierAt22.5 ${ACTION_TEST_BIN} + ${ACTION_TEST_ARGS} --speed 22.5 --timing-mode linear ) + + # NOTE: The total test duration of these tests when ran sequentially is + # around ~900 seconds. Therefore, it is strongly recommended that you run + # these tests in parallel when possible -- whether you have more than one + # physical processing core or not does not matter so much, as all but one + # test is designed to be light on system load. + # + # See also: man 1 ctest, --parallel + nom_add_test( ActionTest_SpeedModifierAt0.5 ${ACTION_TEST_BIN} + ${ACTION_TEST_ARGS} --speed 0.5 ) + + # NOTE: Only the standard five timing curve functions are tested here; there + # are many more modes available in the testing suite! + # + # See also: ActionTest --help, --timing-mode + set( ACTION_TEST_ARGS ${ACTION_TEST_ARGS} --speed 100.0 ) + + nom_add_test( ActionTest_QuadEaseIn ${ACTION_TEST_BIN} + ${ACTION_TEST_ARGS} --timing-mode quad_ease_in ) + + nom_add_test( ActionTest_QuadEaseOut ${ACTION_TEST_BIN} + ${ACTION_TEST_ARGS} --timing-mode quad_ease_out ) + + nom_add_test( ActionTest_QuadEaseInOut ${ACTION_TEST_BIN} + ${ACTION_TEST_ARGS} --timing-mode quad_ease_in_out ) + + nom_add_test( ActionTest_CubicEaseIn ${ACTION_TEST_BIN} + ${ACTION_TEST_ARGS} --timing-mode cubic_ease_in ) + + nom_add_test( ActionTest_CubicEaseOut ${ACTION_TEST_BIN} + ${ACTION_TEST_ARGS} --timing-mode cubic_ease_out ) + + nom_add_test( ActionTest_CubicEaseInOut ${ACTION_TEST_BIN} + ${ACTION_TEST_ARGS} --timing-mode cubic_ease_in_out ) + + nom_add_test( ActionTest_QuartEaseIn ${ACTION_TEST_BIN} + ${ACTION_TEST_ARGS} --timing-mode quart_ease_in ) + + nom_add_test( ActionTest_QuartEaseOut ${ACTION_TEST_BIN} + ${ACTION_TEST_ARGS} --timing-mode quart_ease_out ) + + nom_add_test( ActionTest_QuartEaseInOut ${ACTION_TEST_BIN} + ${ACTION_TEST_ARGS} --timing-mode quart_ease_in_out ) + + nom_add_test( ActionTest_QuintEaseIn ${ACTION_TEST_BIN} + ${ACTION_TEST_ARGS} --timing-mode quint_ease_in ) + + nom_add_test( ActionTest_QuintEaseOut ${ACTION_TEST_BIN} + ${ACTION_TEST_ARGS} --timing-mode quint_ease_out ) + + nom_add_test( ActionTest_QuintEaseInOut ${ACTION_TEST_BIN} + ${ACTION_TEST_ARGS} --timing-mode quint_ease_in_out ) + + # Copy resource files for the test + install( FILES + "${NOM_TESTS_RESOURCES_DIR}/actions/ActionTest.json" + DESTINATION "${TESTS_INSTALL_DIR}" ) + +endif( NOM_BUILD_ACTION_TESTS ) + +if( NOM_BUILD_ACTION_TIMING_CURVES_TESTS ) + + set( ACTION_TIMING_CURVES_SRC + ${ACTION_TIMING_CURVES_SRC} + "ActionTimingCurvesTest.cpp" ) + + add_executable( ActionTimingCurvesTest ${ACTION_TIMING_CURVES_SRC} ) + + target_link_libraries(ActionTimingCurvesTest nomlib-unit-test + nomlib-actions) + + GTEST_ADD_TESTS( ${TESTS_INSTALL_DIR}/ActionTimingCurvesTest + "" # args + ${ACTION_TIMING_CURVES_SRC} ) + +endif( NOM_BUILD_ACTION_TIMING_CURVES_TESTS ) diff --git a/tests/src/audio/ALAudioTest.cpp b/tests/src/audio/ALAudioTest.cpp index 724c1342..0844a9c5 100644 --- a/tests/src/audio/ALAudioTest.cpp +++ b/tests/src/audio/ALAudioTest.cpp @@ -2,7 +2,7 @@ nomlib - C++11 cross-platform game engine -Copyright (c) 2013, 2014 Jeffrey Carpenter +Copyright (c) 2013, 2014, 2015, 2016 Jeffrey Carpenter All rights reserved. Redistribution and use in source and binary forms, with or without @@ -28,273 +28,908 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ #include #include +#include -#include "gtest/gtest.h" +#include -#include "nomlib/config.hpp" -#include "nomlib/system/init.hpp" -#include "nomlib/system/Path.hpp" -#include "nomlib/audio.hpp" +#include +#include +#include +#include +#include +#include + +// SCRATCH CODE +// #include +#include using namespace nom; +#define EXPECT_FLOAT_NEAR(val1, val2, abs_error) \ + EXPECT_NEAR(val1, val2, abs_error) + class ALAudioTest: public ::testing::Test { public: /// \remarks This method is called at the start of each unit test. - ALAudioTest( void ) : - dev{ nullptr }, - listener{ nullptr }, - buffer{ nullptr }, - sound{ nullptr } + ALAudioTest() { // NOM_LOG_TRACE( NOM ); + nom::SDL2Logger::set_logging_priority(NOM_LOG_CATEGORY_AUDIO, + NOM_LOG_PRIORITY_VERBOSE); } /// \remarks This method is called at the end of each unit test. - virtual ~ALAudioTest( void ) + virtual ~ALAudioTest() { // NOM_LOG_TRACE( NOM ); } /// \remarks This method is called after construction, at the start of each /// unit test. - virtual void SetUp( void ) + virtual void SetUp() { // NOM_LOG_TRACE( NOM ); + + // NOTE(jeff): Find resource files for the test + const std::string RES_FILENAME = "ALAudioTest.json"; + + if(res.load_file(RES_FILENAME, "resources") == false) { + FAIL() + << "Could not resolve the 'resources' path from input file: " + << RES_FILENAME; + } + + auto p = file_ref("chunk", res.path() + "sinewave_1s-chunk.wav"); + AUDIO_RESOURCES.insert(p); + + p = file_ref("sine1s-900", res.path() + "sinewave_1s-900.wav"); + AUDIO_RESOURCES.insert(p); + + p = file_ref("sine2s-440", res.path() + "sinewave_2s-440.wav"); + AUDIO_RESOURCES.insert(p); + + p = file_ref("sine1s-real32", res.path() + "sinewave-real32_1s-900.wav"); + AUDIO_RESOURCES.insert(p); + + this->null_request.engine = "null"; // default + this->openal_request.engine = "openal"; } /// \remarks This method is called before destruction, at the end of each /// unit test. - virtual void TearDown( void ) + virtual void TearDown() { // NOM_LOG_TRACE( NOM ); - - NOM_DELETE_PTR( dev ); - NOM_DELETE_PTR( listener ); - NOM_DELETE_PTR( buffer ); - NOM_DELETE_PTR( sound ); } /// \remarks This method is called at the start of each test case. - static void SetUpTestCase( void ) + static void SetUpTestCase() { // NOM_LOG_TRACE( NOM ); } /// \remarks This method is called at the end of each test case. - static void TearDownTestCase( void ) + static void TearDownTestCase() { // NOM_LOG_TRACE( NOM ); } protected: - const std::string APP_RESOURCES_DIR = "Resources"; - const nom::Path p; - const std::string RESOURCE_AUDIO_SOUND = APP_RESOURCES_DIR + p.native() + "cursor_wrong.wav"; - - nom::IAudioDevice* dev; - nom::IListener* listener; - nom::ISoundBuffer* buffer; - nom::ISoundSource* sound; + const bool TEST_CHUNK_PLAYBACK = true; + + // Test resources (file paths) + SearchPath res; + + typedef std::pair file_ref; + std::map AUDIO_RESOURCES; + + audio::AudioSpec null_request = {}; + audio::AudioSpec openal_request = {}; }; -TEST_F( ALAudioTest, NullAudioDevice ) +namespace test { + +static void play_audio(void* samples, const audio::SoundInfo& metadata, + audio::IOAudioEngine* target) { - this->dev = new nom::NullAudioDevice(); - EXPECT_EQ( "NullAudioDevice", this->dev->getDeviceName() ); + nom::Timer elapsed; + + audio::SoundBuffer* buffer = + audio::create_buffer(samples, metadata, target); + ASSERT_TRUE(buffer != nullptr); + + EXPECT_NE(0, audio::buffer_id(buffer)); + EXPECT_NE(0, audio::source_id(buffer)); + + // TODO(jeff): Write test for testing the playback of audio::play multiple + // times; ensure that the buffering only occurs when necessary! + // audio::queue(buffer, target); + audio::play(buffer, target); - NOM_LOG_INFO( NOM_LOG_CATEGORY_TEST, this->dev->getDeviceName() ); + elapsed.start(); + NOM_LOG_INFO(NOM_LOG_CATEGORY_TEST, "start ticks:", elapsed.ticks() ); + NOM_LOG_INFO(NOM_LOG_CATEGORY_TEST, "Total samples played:", + audio::playback_samples(buffer, target)); + + bool playback_samples_shown = false; + uint32 playback_state = audio::AUDIO_STATE_PLAYING; + while(playback_state != audio::AUDIO_STATE_STOPPED && + elapsed.to_seconds() < metadata.duration) + { + + playback_state = audio::state(buffer, target); + + if(elapsed.ticks() > 500 && playback_samples_shown == false) { + NOM_LOG_INFO(NOM_LOG_CATEGORY_TEST, "Total samples played:", + audio::playback_samples(buffer, target)); + playback_samples_shown = true; + } + } + +// IMPORTANT(jeff): OpenAL-Soft resets the samples playback cursor upon +// completion of the audio buffer, whereas Apple's distributed OpenAL SDK does +// not. +#if NOM_USE_APPLE_OPENAL + EXPECT_EQ(metadata.sample_rate * metadata.duration, + audio::playback_samples(buffer, target)); +#endif + + NOM_LOG_INFO(NOM_LOG_CATEGORY_TEST, "total samples played:", + audio::playback_samples(buffer, target)); + + EXPECT_GE(elapsed.ticks(), buffer->duration) + << "The number of elapsed ticks should always be greater than the audio " + << "playback duration!"; + + NOM_LOG_INFO(NOM_LOG_CATEGORY_TEST, "elapsed ticks:", elapsed.ticks() ); + + audio::free_buffer(buffer, target); } -TEST_F( ALAudioTest, NullAudioListener ) +} // namespace test + +// TODO(jeff): Dedicate to SoundFileReaderTest::AudioRead test executable +TEST_F(ALAudioTest, SoundFileReader_AudioRead) { - // Dependency - this->dev = new nom::NullAudioDevice(); + const auto AUDIO_FILENAME = AUDIO_RESOURCES["sine1s-900"]; + void* buffer = nullptr; + audio::SoundInfo metadata = {}; + + audio::ISoundFileReader* fp = new audio::SoundFileReader(); + ASSERT_TRUE(fp != nullptr); + + EXPECT_EQ(true, fp->open(AUDIO_FILENAME, metadata)) + << "Could not load audio from input file " << AUDIO_FILENAME; + EXPECT_EQ(true, fp->valid()); + + const nom::size_type CHUNK_SIZE = metadata.total_bytes; + buffer = new int16[CHUNK_SIZE * metadata.channel_count]; - this->listener = new nom::NullListener(); + EXPECT_NE(0, fp->read(buffer, metadata.channel_format, CHUNK_SIZE)) + << "Could not read audio samples from input file " << AUDIO_FILENAME; - EXPECT_EQ( 0.0f, this->listener->getVolume() ); - EXPECT_EQ( Point3f(0,0,0), this->listener->getPosition() ); - EXPECT_EQ( Point3f(0.0f,0.0f,0.0f), this->listener->getVelocity() ); - EXPECT_EQ( Point3f(0,0,-1), this->listener->getDirection() ); + fp->close(); +#if 0 + NOM_DELETE_PTR(fp); +#endif + + EXPECT_EQ(-15, NOM_SCAST(int16*, buffer)[0]); + EXPECT_EQ(-5, NOM_SCAST(int16*, buffer)[1]); + + delete NOM_SCAST(int16*, buffer); + buffer = nullptr; } -TEST_F( ALAudioTest, NullSoundBuffer ) +// TODO(jeff): Dedicate to SoundFileReaderTest::AudioRead test executable +TEST_F(ALAudioTest, SoundFileReader_AudioReadChunk) { - this->buffer = new nom::NullSoundBuffer(); - EXPECT_EQ( 0, this->buffer->get() ); - EXPECT_EQ( 0, this->buffer->getDuration() ); - EXPECT_FALSE( this->buffer->load( RESOURCE_AUDIO_SOUND ) ); + const auto AUDIO_FILENAME = AUDIO_RESOURCES["chunk"]; + + audio::SoundInfo metadata = {}; + nom::Timer elapsed; + + audio::IOAudioEngine* dev = nullptr; + audio::AudioSpec spec = {}; + if(TEST_CHUNK_PLAYBACK == true) { + dev = audio::init_audio(&this->openal_request, &spec); + ASSERT_TRUE(dev != nullptr); + } + + audio::ISoundFileReader* fp = new audio::SoundFileReader(); + ASSERT_TRUE(fp != nullptr); + + EXPECT_EQ(true, fp->open(AUDIO_FILENAME, metadata)) + << "Could not load audio from input file " << AUDIO_FILENAME; + EXPECT_EQ(true, fp->valid()); + + const nom::size_type CHUNK_SIZE = metadata.total_bytes / 2; // 44100 + EXPECT_TRUE(CHUNK_SIZE == metadata.sample_rate) + << "The data read chunk size should match the input audio sample rate"; + + EXPECT_TRUE(CHUNK_SIZE*2 == metadata.total_bytes) + << "The data read chunk size should match the total size of the input audio"; + + // Create a buffer that is large enough to read in both data chunks + const nom::size_type READ_SIZE = CHUNK_SIZE/2; + nom::size_type offset = 0; + + void* samples = new int16[metadata.total_bytes * metadata.channel_count]; + ASSERT_TRUE(samples != nullptr); + + auto first_half = + fp->read((int16*)samples, metadata.channel_format, READ_SIZE); + auto first_half_sample = NOM_SCAST(int16*, samples)[offset]; + EXPECT_EQ(-1, first_half_sample) + << "read chunk offset: " << offset; + + offset += READ_SIZE; + + auto second_half = + fp->read((int16*)samples + offset, metadata.channel_format, READ_SIZE); + auto second_half_sample = NOM_SCAST(int16*, samples)[offset]; // offset = 22050 + EXPECT_EQ(3, second_half_sample) + << "read chunk offset: " << offset; + + EXPECT_EQ(READ_SIZE, first_half); + EXPECT_EQ(READ_SIZE, second_half); + + if(TEST_CHUNK_PLAYBACK == true) { + // audio::set_volume(4.0f, dev); + // EXPECT_EQ(4.0f, audio::volume(dev)); + test::play_audio(samples, metadata, dev); + } else { + audio::free_samples(metadata.channel_format, samples); + } + + fp->close(); + // TODO(jeff): cppcheck reports `fp` as a memory leak; my own visual + // examination agrees with the report. Verify this to be true! +#if 0 + NOM_DELETE_PTR(fp); +#endif + + if(TEST_CHUNK_PLAYBACK == true) { + audio::shutdown_audio(dev); + } } -TEST_F( ALAudioTest, NullSound ) +// TODO(jeff): Dedicate to SoundFileReaderTest::Seek test executable +TEST_F(ALAudioTest, SoundFileReader_Seek) { - // Dependencies - this->dev = new nom::NullAudioDevice(); - this->listener = new nom::NullListener(); - this->buffer = new nom::NullSoundBuffer(); - this->sound = new nom::NullSound(); - - EXPECT_EQ( 0, this->buffer->get() ); - EXPECT_EQ( 0, this->buffer->getDuration() ); - EXPECT_FALSE( this->buffer->load( RESOURCE_AUDIO_SOUND ) ); - - this->sound->setBuffer( *buffer ); - EXPECT_EQ( nom::SoundStatus::Stopped, this->sound->getStatus() ); - EXPECT_EQ( 0.0f, this->sound->getVolume() ); - EXPECT_EQ( 0.0f, this->sound->getMinVolume() ); - EXPECT_EQ( 0.0f, this->sound->getMaxVolume() ); - EXPECT_EQ( 0.0f, this->sound->getPitch() ); - EXPECT_EQ( false, this->sound->getLooping() ); - EXPECT_EQ( Point3f(0.0f,0.0f,0.0f), this->sound->getPosition() ); - EXPECT_EQ( Point3f(0.0f,0.0f,0.0f), this->sound->getVelocity() ); - EXPECT_EQ( false, this->sound->getPositionRelativeToListener() ); - EXPECT_EQ( 0.0f, this->sound->getMinDistance() ); - EXPECT_EQ( 0.0f, this->sound->getAttenuation() ); - EXPECT_EQ( -1, this->sound->getBufferID() ); - EXPECT_EQ( 0.0f, this->sound->getPlayPosition() ); + const auto AUDIO_FILENAME = AUDIO_RESOURCES["chunk"]; + nom::size_type offset = 0; + + audio::SoundInfo metadata = {}; + nom::Timer elapsed; + + audio::IOAudioEngine* dev = nullptr; + audio::AudioSpec spec = {}; + if(TEST_CHUNK_PLAYBACK == true) { + dev = audio::init_audio(&this->openal_request, &spec); + ASSERT_TRUE(dev != nullptr); + } + + audio::ISoundFileReader* fp = new audio::SoundFileReader(); + ASSERT_TRUE(fp != nullptr); + + EXPECT_EQ(true, fp->open(AUDIO_FILENAME, metadata)) + << "Could not load audio from input file " << AUDIO_FILENAME; + EXPECT_EQ(true, fp->valid()); + + offset = fp->seek(0, audio::SOUND_SEEK_CUR); + EXPECT_EQ(0, offset); + EXPECT_EQ(metadata.frame_count, fp->seek(0, audio::SOUND_SEEK_END)); + + const nom::size_type CHUNK_SIZE = metadata.total_bytes / 2; // 44100 + EXPECT_TRUE(CHUNK_SIZE == metadata.sample_rate) + << "The data read chunk size should match the input audio sample rate"; + + EXPECT_TRUE(CHUNK_SIZE*2 == metadata.total_bytes) + << "The data read chunk size should match the total size of the input audio"; + + // Create a buffer that is large enough to read in both data chunks + const nom::size_type READ_SIZE = (CHUNK_SIZE / 2); // 22050 + // void* samples = new int16[metadata.total_bytes/2]; + void* samples = new int16[ (READ_SIZE * 2) * metadata.channel_count]; + ASSERT_TRUE(samples != nullptr); + + // Seek to halfway through the audio sample + offset = fp->seek(READ_SIZE, audio::SOUND_SEEK_SET); + EXPECT_EQ(READ_SIZE, offset); + + auto first_half = + fp->read( NOM_SCAST(int16*, samples), metadata.channel_format, READ_SIZE); + EXPECT_EQ(READ_SIZE, first_half); + + if(TEST_CHUNK_PLAYBACK == true) { + // audio::set_volume(4.0f, dev); + // EXPECT_EQ(4.0f, audio::volume(dev)); + test::play_audio(samples, metadata, dev); + } else { + audio::free_samples(metadata.channel_format, samples); + } + + fp->close(); + // TODO(jeff): cppcheck reports `fp` as a memory leak; my own visual + // examination agrees with the report. Verify this to be true! +#if 0 + NOM_DELETE_PTR(fp); +#endif + if(TEST_CHUNK_PLAYBACK == true) { + audio::shutdown_audio(dev); + } } -TEST_F( ALAudioTest, NullMusic ) +TEST_F(ALAudioTest, SoundFileReader_Metadata) { - // Dependencies - this->dev = new nom::NullAudioDevice(); - this->listener = new nom::NullListener(); - this->buffer = new nom::NullSoundBuffer(); - this->sound = new nom::NullMusic(); - - EXPECT_EQ( 0, this->buffer->get() ); - EXPECT_EQ( 0, this->buffer->getDuration() ); - EXPECT_FALSE( this->buffer->load( RESOURCE_AUDIO_SOUND ) ); - this->sound->setBuffer( *buffer ); - EXPECT_EQ( nom::SoundStatus::Stopped, this->sound->getStatus() ); + nom::Timer elapsed; + + for(auto itr = AUDIO_RESOURCES.begin(); itr != AUDIO_RESOURCES.end(); + ++itr) + { + audio::SoundInfo metadata = {}; + audio::SoundFileReader* fp = new audio::SoundFileReader(); + ASSERT_TRUE(fp != nullptr); + + EXPECT_EQ(true, fp->open(itr->second, metadata)) + << "Could not load audio from input file " << itr->second; + + EXPECT_EQ(true, fp->valid()); + NOM_LOG_DEBUG(NOM_LOG_CATEGORY_TEST, + " Audio resource:", itr->second); + NOM_LOG_DEBUG(NOM_LOG_CATEGORY_TEST, "title:", + metadata.tags.title); + NOM_LOG_DEBUG(NOM_LOG_CATEGORY_TEST, "copyright:", + metadata.tags.copyright); + NOM_LOG_DEBUG(NOM_LOG_CATEGORY_TEST, "artist:", + metadata.tags.artist); + NOM_LOG_DEBUG(NOM_LOG_CATEGORY_TEST, "comment:", + metadata.tags.comment); + NOM_LOG_DEBUG(NOM_LOG_CATEGORY_TEST, "date:", + metadata.tags.date); + NOM_LOG_DEBUG(NOM_LOG_CATEGORY_TEST, "album:", + metadata.tags.album); + NOM_LOG_DEBUG(NOM_LOG_CATEGORY_TEST, "license:", + metadata.tags.license); + NOM_LOG_DEBUG(NOM_LOG_CATEGORY_TEST, "track_number:", + metadata.tags.track_number); + NOM_LOG_DEBUG(NOM_LOG_CATEGORY_TEST, "genre:", + metadata.tags.genre); + + fp->close(); + // TODO(jeff): cppcheck reports `fp` as a memory leak; my own visual + // examination agrees with the report. Verify this to be true! + #if 0 + NOM_DELETE_PTR(fp); + #endif + } } -TEST_F( ALAudioTest, NullAudioDeviceLocatorAPI ) +// TODO(jeff): Dedicate to SoundFileWriterTest::RawWrite test executable +#if 0 +TEST_F(ALAudioTest, DISABLED_SoundFileWriterTest_RawWrite) { - nom::AudioDeviceLocator::set_provider( nullptr ); + const auto AUDIO_FILENAME = AUDIO_RESOURCES["sine1s-900"]; + int16* samples = nullptr; + SoundInfo metadata = {}; + + // TODO: + // ISoundFileReader* fpr = new SoundFileReader(); + // fpr->open(AUDIO_FILENAME, metadata); + // ... + // fpr->read(samples, ...); + // fpw->open(AUDIO_FILENAME + ".raw"); + // fpw->write(samples, ...); + // ... + // fpw->close(); + // fpr->close(); + ISoundFileWriter* fpw = new SoundFileWriter(); + ASSERT_TRUE(fpw != nullptr); + + EXPECT_EQ(true, fpw->open(AUDIO_FILENAME, metadata)) + << "Could not load audio from input file " << AUDIO_FILENAME; + + EXPECT_EQ(true, fpw->valid()); + + // const nom::size_type CHUNK_SIZE = metadata.total_bytes; + // buffer = new int16[CHUNK_SIZE * metadata.channel_count]; + + // EXPECT_NE(0, fp->read(buffer, CHUNK_SIZE)) + // << "Could not read audio samples from input file " << AUDIO_FILENAME; + + fpw->close(); + + // EXPECT_EQ(-15, NOM_SCAST(int16*, buffer)[0]); + // EXPECT_EQ(-5, NOM_SCAST(int16*, buffer)[1]); + + nom::free_samples(metadata.channel_format, samples); +} +#endif + +TEST_F(ALAudioTest, NullAudioDevice) +{ + audio::AudioSpec spec = {}; + audio::IOAudioEngine* dev = + audio::init_audio(nullptr, &spec); + ASSERT_TRUE(dev == nullptr); + // EXPECT_EQ(false, dev->valid()); + + // FIXME + // EXPECT_EQ("NullAudioDevice", dev->device_name() ); + EXPECT_EQ(0.0f, audio::volume(dev)); + EXPECT_EQ(Point3f(0,0,0), audio::position(dev)); + + audio::shutdown_audio(dev); +} + +TEST_F(ALAudioTest, NullAudioVolume) +{ + audio::AudioSpec spec = {}; + audio::IOAudioEngine* dev = + audio::init_audio(nullptr, &spec); + ASSERT_TRUE(dev == nullptr); + // EXPECT_EQ(false, dev->valid()); + + // FIXME + // EXPECT_EQ("NullAudioDevice", dev->device_name() ); + EXPECT_EQ(0.0f, audio::volume(dev)); + EXPECT_EQ(Point3f(0,0,0), audio::position(dev)); + EXPECT_EQ(0.0f, audio::volume(dev)); + EXPECT_EQ(Point3f(0,0,0), audio::position(dev)); + + audio::shutdown_audio(dev); +} + +TEST_F(ALAudioTest, NullSoundBuffer) +{ + audio::AudioSpec spec = {}; + audio::IOAudioEngine* dev = + audio::init_audio(nullptr, &spec); + ASSERT_TRUE(dev == nullptr); + // EXPECT_EQ(false, dev->valid()); + + audio::SoundBuffer* buffer = nullptr; + + buffer = audio::create_buffer_memory(); + EXPECT_TRUE(buffer != nullptr); + + EXPECT_EQ(0, audio::buffer_id(buffer)); + EXPECT_EQ(0, audio::source_id(buffer)); + EXPECT_EQ(0, buffer->duration); + EXPECT_NE(audio::AUDIO_STATE_LOOPING, audio::state(buffer, dev) ); + + audio::free_buffer(buffer, dev); + EXPECT_TRUE(buffer != nullptr); + + audio::shutdown_audio(dev); +} + +TEST_F(ALAudioTest, NullSound) +{ + audio::AudioSpec spec = {}; + audio::IOAudioEngine* dev = + audio::init_audio(nullptr, &spec); + ASSERT_TRUE(dev == nullptr); + // EXPECT_EQ(false, dev->valid()); + + auto buffer = audio::create_buffer_memory(); + EXPECT_TRUE(buffer != nullptr); + + EXPECT_EQ(audio::AUDIO_STATE_STOPPED, audio::pitch(buffer, dev)); + EXPECT_FLOAT_EQ(0.0f, audio::volume(buffer, dev)); + EXPECT_FLOAT_EQ(0.0f, audio::min_volume(buffer, dev)); + EXPECT_FLOAT_EQ(0.0f, audio::max_volume(buffer, dev)); + EXPECT_FLOAT_EQ(0.0f, audio::pitch(buffer, dev)); + EXPECT_EQ(Point3f::zero, audio::position(buffer, dev)); + EXPECT_EQ(Point3f::zero, audio::velocity(buffer, dev)); + + EXPECT_EQ(0.0f, audio::playback_position(buffer, dev)); + EXPECT_EQ(0.0f, audio::playback_samples(buffer, dev)); + + EXPECT_EQ(0, audio::buffer_id(buffer)); + EXPECT_EQ(0, audio::source_id(buffer)); + + audio::free_buffer(buffer, dev); + EXPECT_TRUE(buffer != nullptr); + + audio::shutdown_audio(dev); +} + +// TODO(jeff): Finish implementation +#if 1 +TEST_F(ALAudioTest, NullMusic) +{ + audio::AudioSpec spec = {}; + audio::IOAudioEngine* dev = nullptr; + audio::SoundBuffer* buffer = audio::create_buffer_memory(); + // audio::SoundBuffer* buffer = nullptr; + ASSERT_TRUE(buffer != nullptr); + + dev = audio::init_audio(&this->null_request, &spec); + ASSERT_TRUE(dev == nullptr); + + EXPECT_EQ(0, audio::buffer_id(buffer)); + EXPECT_EQ(0, audio::source_id(buffer)); + EXPECT_TRUE(buffer->samples == nullptr); + + EXPECT_EQ(audio::AUDIO_STATE_STOPPED, audio::state(buffer, dev)); + EXPECT_EQ(0.0f, audio::pitch(buffer, dev)); + EXPECT_EQ(0.0f, audio::volume(buffer, dev)); + EXPECT_EQ(0.0f, audio::min_volume(buffer, dev)); + EXPECT_EQ(0.0f, audio::max_volume(buffer, dev)); + EXPECT_EQ( Point3f::zero, audio::position(buffer, dev)); + EXPECT_EQ( Point3f::zero, audio::velocity(buffer, dev)); + // EXPECT_EQ( false, sound.getPositionRelativeToListener() ); + // EXPECT_EQ( 0.0f, sound.getMinDistance() ); + // EXPECT_EQ( 0.0f, sound.getAttenuation() ); + EXPECT_EQ(0.0f, audio::playback_position(buffer, dev)); + + audio::free_buffer(buffer, dev); + // EXPECT_TRUE(buffer != nullptr); + + audio::shutdown_audio(dev); +} +#endif + +#if 0 +TEST_F( ALAudioTest, NullAudioDeviceLocatorAPI) +{ + nom::AudioDeviceLocator::set_provider(nullptr); EXPECT_EQ( "NullAudioDevice", nom::AudioDeviceLocator::audio_device().getDeviceName() ); NOM_LOG_INFO( NOM_LOG_CATEGORY_TEST, nom::AudioDeviceLocator::audio_device().getDeviceName() ); } +#endif // EOF NullAudio tests -#if defined( NOM_USE_OPENAL ) - -TEST_F( ALAudioTest, AudioDevice ) +TEST_F(ALAudioTest, AudioOutputDeviceOpen) { - this->dev = new nom::AudioDevice(); - EXPECT_NE( "", this->dev->getDeviceName() ); + audio::AudioSpec spec = {}; + audio::IOAudioEngine* dev = + audio::init_audio(&this->openal_request, &spec); + ASSERT_TRUE(dev != nullptr); - NOM_LOG_INFO( NOM_LOG_CATEGORY_TEST, this->dev->getDeviceName() ); + EXPECT_NE("", spec.name); + NOM_LOG_INFO(NOM_LOG_CATEGORY_TEST, spec.name); + + audio::shutdown_audio(dev); } -TEST_F( ALAudioTest, AudioListener ) +TEST_F(ALAudioTest, GlobalAudioOutputVolume) { - // Dependency - this->dev = new nom::AudioDevice(); + audio::AudioSpec spec = {}; + audio::IOAudioEngine* dev = + audio::init_audio(&this->openal_request, &spec); + ASSERT_TRUE(dev != nullptr); - this->listener = new nom::Listener(); + EXPECT_EQ(100.0f, audio::volume(dev)); + EXPECT_EQ(Point3f(0.0f, 0.0f, 0.0f), audio::position(dev)); - EXPECT_EQ( 100, this->listener->getVolume() ); - EXPECT_EQ( Point3f(0,0,0), this->listener->getPosition() ); - EXPECT_EQ( Point3f(0.0f,0.0f,0.0f), this->listener->getVelocity() ); - EXPECT_EQ( Point3f(-1,-1,-1), this->listener->getDirection() ); + audio::shutdown_audio(dev); } -TEST_F( ALAudioTest, SoundBuffer ) +TEST_F(ALAudioTest, AudioOutputBufferVolume) { - this->buffer = new nom::SoundBuffer(); - EXPECT_TRUE( this->buffer->load( RESOURCE_AUDIO_SOUND ) ); - - // EXPECT_EQ( 2400, this->buffer->get() ); - EXPECT_EQ( 455, this->buffer->getDuration() ); + const auto AUDIO_FILENAME = AUDIO_RESOURCES["sine1s-900"]; + + audio::AudioSpec spec = {}; + audio::IOAudioEngine* dev = + audio::init_audio(&this->openal_request, &spec); + ASSERT_TRUE(dev != nullptr); + + EXPECT_EQ(100.0f, audio::volume(dev)); + + audio::SoundBuffer* buffer = + audio::create_buffer(AUDIO_FILENAME, dev); + EXPECT_EQ(100.0f, audio::volume(dev)); + EXPECT_EQ(100.0f, audio::volume(buffer, dev)); + EXPECT_EQ(0.0f, audio::min_volume(buffer, dev)); + EXPECT_EQ(100.0f, audio::max_volume(buffer, dev)); + EXPECT_EQ(Point3f(0.0f, 0.0f, 0.0f), audio::position(buffer, dev)); + EXPECT_EQ(Point3f(0.0f, 0.0f, 0.0f), audio::velocity(buffer, dev)); + + audio::free_buffer(buffer, dev); + audio::shutdown_audio(dev); } -TEST_F( ALAudioTest, Sound ) +TEST_F(ALAudioTest, AudioSpec) { - // Dependencies - this->dev = new nom::AudioDevice(); - this->listener = new nom::Listener(); - this->buffer = new nom::SoundBuffer(); - this->sound = new nom::Sound(); - - EXPECT_TRUE( this->buffer->load( RESOURCE_AUDIO_SOUND ) ); - // EXPECT_EQ( 2401, this->buffer->get() ); - EXPECT_EQ( 455, this->buffer->getDuration() ); - - this->sound->setBuffer( *buffer ); - EXPECT_EQ( nom::SoundStatus::Stopped, this->sound->getStatus() ); - - EXPECT_EQ( 100, this->sound->getVolume() ); - EXPECT_EQ( 0, this->sound->getMinVolume() ); - EXPECT_EQ( 1.0f, this->sound->getMaxVolume() ); - EXPECT_EQ( 1, this->sound->getPitch() ); - EXPECT_EQ( false, this->sound->getLooping() ); - EXPECT_EQ( Point3f(0.0f,0.0f,0.0f), this->sound->getPosition() ); - EXPECT_EQ( Point3f(0.0f,0.0f,0.0f), this->sound->getVelocity() ); - EXPECT_EQ( false, this->sound->getPositionRelativeToListener() ); - EXPECT_EQ( 1, this->sound->getMinDistance() ); - EXPECT_EQ( 1, this->sound->getAttenuation() ); - // EXPECT_EQ( 2401, this->sound->getBufferID() ); - EXPECT_EQ( 0.0f, this->sound->getPlayPosition() ); + audio::IOAudioEngine* dev = nullptr; + audio::AudioSpec request = {}; // input spec + audio::AudioSpec spec = {}; // output spec + + request.name = ""; + request.sample_rate = 44100; + request.engine = "openal"; + + dev = audio::init_audio(&request, &spec); + ASSERT_TRUE(dev != nullptr); + EXPECT_EQ(true, dev->valid()); + EXPECT_NE("", spec.name); + EXPECT_EQ(request.sample_rate, spec.sample_rate); + + audio::shutdown_audio(dev); + + request.sample_rate = 48000; + dev = audio::init_audio(&request, &spec); + ASSERT_TRUE(dev != nullptr); + EXPECT_EQ(true, dev->valid()); + EXPECT_STREQ("openal", spec.engine); + EXPECT_NE("", spec.name); + EXPECT_EQ(48000, spec.sample_rate); + + auto driver = audio::current_device(); + // spec = audio::create_openal_attributes(driver); + NOM_LOG_INFO(NOM_LOG_CATEGORY_TEST, "refresh rate (Hz):", + spec.refresh_rate); + + NOM_LOG_INFO(NOM_LOG_CATEGORY_TEST, "synchronous context (boolean):", + spec.sync_context); + + NOM_LOG_INFO(NOM_LOG_CATEGORY_TEST, "max sources:", + audio::max_sources(driver)); + + NOM_LOG_INFO(NOM_LOG_CATEGORY_TEST, "max mono sources:", + spec.num_mono_sources); + + NOM_LOG_INFO(NOM_LOG_CATEGORY_TEST, "max stereo sources:", + spec.num_stereo_sources); + + audio::shutdown_audio(dev); + + // TODO(jeff): Test these attributes out +#if 0 + request.refresh_rate = 30; + request.sync_context = true; + request.mono_sources = 100; + request.stereo_sources = 100; +#endif + // FIXME(jeff): Implement NULL audio hardware abstraction + // request.name = "null device"; + // dev = audio::init_audio(&request, &spec); + // ASSERT_TRUE(dev == nullptr); + // EXPECT_STREQ("openal", spec.engine); + // EXPECT_NE("", spec.name); + // EXPECT_EQ(request.frequency, spec.frequency); + + // audio::shutdown_audio(dev); } -TEST_F( ALAudioTest, Music ) +TEST_F(ALAudioTest, SoundBuffer) { - // Dependencies - this->dev = new nom::AudioDevice(); - this->listener = new nom::Listener(); - this->buffer = new nom::SoundBuffer(); - this->sound = new nom::Music(); - - EXPECT_TRUE( this->buffer->load( RESOURCE_AUDIO_SOUND ) ); - // EXPECT_EQ( 2404, this->buffer->get() ); - EXPECT_EQ( 455, this->buffer->getDuration() ); - - this->sound->setBuffer( *buffer ); - EXPECT_EQ( nom::SoundStatus::Stopped, this->sound->getStatus() ); + const auto AUDIO_FILENAME = AUDIO_RESOURCES["sine1s-900"]; + + audio::AudioSpec spec = {}; + audio::IOAudioEngine* dev = + audio::init_audio(&this->openal_request, &spec); + ASSERT_TRUE(dev != nullptr); + + audio::SoundBuffer* buffer = nullptr; + buffer = audio::create_buffer(AUDIO_FILENAME, dev); + ASSERT_TRUE(buffer != nullptr); + EXPECT_FLOAT_NEAR(1.0f, buffer->duration, 0.01f); + + EXPECT_EQ(1, audio::buffer_id(buffer)); + EXPECT_EQ(2, audio::source_id(buffer)); + + audio::free_buffer(buffer, dev); + EXPECT_TRUE(buffer != nullptr); + + audio::shutdown_audio(dev); } -TEST_F( ALAudioTest, ThreeAudioDeviceInitalizations ) +TEST_F(ALAudioTest, Sound) { - this->dev = new nom::AudioDevice(); - EXPECT_NE( "", this->dev->getDeviceName() ); + const auto AUDIO_FILENAME = AUDIO_RESOURCES["sine1s-900"]; + + audio::AudioSpec spec = {}; + audio::IOAudioEngine* dev = + audio::init_audio(&this->openal_request, &spec); + ASSERT_TRUE(dev != nullptr); + + auto buffer = audio::create_buffer(AUDIO_FILENAME, dev); + ASSERT_TRUE(buffer != nullptr); + EXPECT_FLOAT_NEAR(1.0f, buffer->duration, 0.01f); + + EXPECT_EQ(audio::AUDIO_STATE_STOPPED, audio::state(buffer, dev)); + EXPECT_FLOAT_EQ(100.0f, audio::volume(buffer, dev)); + EXPECT_FLOAT_EQ(0.0f, audio::min_volume(buffer, dev)); + EXPECT_FLOAT_EQ(100.0f, audio::max_volume(buffer, dev)); + EXPECT_FLOAT_EQ(1.0f, audio::pitch(buffer, dev)); + EXPECT_EQ(Point3f::zero, audio::position(buffer, dev)); + EXPECT_EQ(Point3f::zero, audio::velocity(buffer, dev)); - NOM_LOG_INFO( NOM_LOG_CATEGORY_TEST, this->dev->getDeviceName() ); + EXPECT_NE(0, audio::buffer_id(buffer)); + EXPECT_NE(0, audio::source_id(buffer)); - this->dev = new nom::AudioDevice(); - EXPECT_NE( "", this->dev->getDeviceName() ); + EXPECT_EQ(0.0f, audio::playback_position(buffer, dev)); + EXPECT_EQ(0.0f, audio::playback_samples(buffer, dev)); - NOM_LOG_INFO( NOM_LOG_CATEGORY_TEST, this->dev->getDeviceName() ); + audio::free_buffer(buffer, dev); + audio::shutdown_audio(dev); +} +#if 0 +TEST_F(ALAudioTest, Music) +{ + Music sound; + dev = test::create_audio_handle(); - this->dev = new nom::AudioDevice(); - EXPECT_NE( "", this->dev->getDeviceName() ); + // auto buffer = audio::create_buffer(RESOURCE_AUDIO_SOUND); + // ASSERT_TRUE(buffer != nullptr); + EXPECT_FLOAT_NEAR(1.0f, buffer->duration, 0.01f); - NOM_LOG_INFO( NOM_LOG_CATEGORY_TEST, this->dev->getDeviceName() ); + sound.load_file(RESOURCE_AUDIO_SOUND); + EXPECT_EQ(SOUND_STOPPED, sound.status() ); + + // nom::free_buffer(buffer, dev); + // EXPECT_TRUE(buffer != nullptr); } +#endif -TEST_F( ALAudioTest, AudioDeviceLocatorAPI ) +#if 0 +TEST_F(ALAudioTest, AudioDeviceLocatorAPI) { - this->dev = new nom::AudioDevice(); - nom::AudioDeviceLocator::set_provider( this->dev ); + dev = test::create_audio_handle(); + nom::AudioDeviceLocator::set_provider(dev); EXPECT_NE( "", nom::AudioDeviceLocator::audio_device().getDeviceName() ); NOM_LOG_INFO( NOM_LOG_CATEGORY_TEST, nom::AudioDeviceLocator::audio_device().getDeviceName() ); - EXPECT_TRUE( this->dev == &nom::AudioDeviceLocator::audio_device() ); + EXPECT_TRUE( dev == &nom::AudioDeviceLocator::audio_device() ); +} +#endif + +// TODO(jeff): Test non-NULL dev pointer passing +TEST_F(ALAudioTest, DefaultOutputDeviceName) +{ + audio::AudioSpec spec = {}; // output + const char* dev_name = nullptr; + audio::IOAudioEngine* dev = nullptr; + + // dev = audio::init_audio(&this->openal_request, &spec); + // ASSERT_TRUE(dev != nullptr); + + dev_name = audio::default_output_device_name(nullptr); + ASSERT_TRUE(dev_name != nullptr); + + NOM_LOG_INFO(NOM_LOG_CATEGORY_TEST, + "Default output audio device:", dev_name); + + audio::shutdown_audio(dev); +} + +// TODO(jeff): Test non-NULL dev pointer passing to output_device_names +TEST_F(ALAudioTest, ListOutputDeviceNames) +{ + audio::AudioSpec spec = {}; // output + audio::device_name_list dev_names; + audio::IOAudioEngine* dev = nullptr; + + // dev = audio::init_audio(&this->openal_request, &spec); + // ASSERT_TRUE(dev != nullptr); + + dev_names = audio::output_device_names(nullptr); + + NOM_LOG_INFO(NOM_LOG_CATEGORY_TEST, "Available output audio devices:"); + for(auto itr = dev_names.begin(); itr != dev_names.end(); ++itr) { + NOM_LOG_INFO(NOM_LOG_CATEGORY_TEST, *itr); + } + + audio::shutdown_audio(dev); +} + +// TODO(jeff): Test non-NULL dev pointer passing to input_device_names +TEST_F(ALAudioTest, ListInputDeviceNames) +{ + audio::AudioSpec spec = {}; // output + audio::device_name_list dev_names; + audio::IOAudioEngine* dev = nullptr; + + // dev = audio::init_audio(&this->openal_request, &spec); + // ASSERT_TRUE(dev != nullptr); + + dev_names = audio::input_device_names(nullptr); + + NOM_LOG_INFO(NOM_LOG_CATEGORY_TEST, "Available input audio devices:"); + for(auto itr = dev_names.begin(); itr != dev_names.end(); ++itr) { + NOM_LOG_INFO(NOM_LOG_CATEGORY_TEST, *itr); + } + + audio::shutdown_audio(dev); } -#endif // defined NOM_USE_OPENAL +// TODO(jeff): Test non-NULL dev pointer passing +TEST_F(ALAudioTest, DefaultInputDeviceName) +{ + audio::AudioSpec spec = {}; // output + const char* dev_name = nullptr; + audio::IOAudioEngine* dev = nullptr; -int main( int argc, char** argv ) + // dev = audio::init_audio(&this->openal_request, &spec); + // ASSERT_TRUE(dev != nullptr); + + dev_name = audio::default_input_device_name(nullptr); + ASSERT_TRUE(dev_name != nullptr); + + NOM_LOG_INFO(NOM_LOG_CATEGORY_TEST, + "Default input audio device:", dev_name); + + audio::shutdown_audio(dev); +} + +// TODO(jeff): Implement the following tests: +// ALAudioTest, InputDeviceNames + +#if defined(NOM_USE_APPLE_OPENAL) +#include "nomlib/audio/AL/osx/apple_extensions.hpp" +TEST_F(ALAudioTest, MacOSX_OpenALExtensions) { - ::testing::InitGoogleTest( &argc, argv ); + audio::AudioSpec spec = {}; // output + const char* dev_name = nullptr; + audio::IOAudioEngine* dev = nullptr; + + this->openal_request.num_mono_sources = 32; + this->openal_request.num_stereo_sources = 16; + dev = audio::init_audio(&this->openal_request, &spec); + ASSERT_TRUE(dev != nullptr); + + int max_sources = audio::osx_max_sources(); + EXPECT_EQ(64, max_sources); + NOM_DUMP(max_sources); + + NOM_LOG_INFO(NOM_LOG_CATEGORY_TEST, + "max sources:", max_sources); + + audio::shutdown_audio(dev); +} +#endif + +// +// Scratch tests +// - NOM_ASSERT( nom::init ( argc, argv ) == true ); - atexit( nom::quit ); +typedef void signature_t(void* data, nom::size_type byte_size); +signature_t* func = nullptr; + +void null_func_signature(void* data, nom::size_type bytes) +{ + NOM_LOG_INFO(NOM_LOG_CATEGORY_TEST, "NULL"); +} + +void func_signature(void* data, nom::size_type bytes) +{ + if(data) { + NOM_LOG_INFO(NOM_LOG_CATEGORY_TEST, "data:", (const char*)data); + } + NOM_LOG_INFO(NOM_LOG_CATEGORY_TEST, "number of bytes:", bytes); +} + +TEST_F(ALAudioTest, Scratch) +{ + void* data = NOM_CCAST(char*, nom::create_string("boobies")); + +#if 1 + func = func_signature; +#else + func = nullptr; +#endif + + if(func == nullptr) { + func = null_func_signature; + } + + func(data, 4); +} + +int main(int argc, char** argv) +{ + ::testing::InitGoogleTest(&argc, argv); + + NOM_ASSERT(nom::init(argc, argv) == true); + atexit(nom::quit); return RUN_ALL_TESTS(); } + diff --git a/tests/src/audio/CMakeLists.txt b/tests/src/audio/CMakeLists.txt index 7d49d67b..12effe50 100644 --- a/tests/src/audio/CMakeLists.txt +++ b/tests/src/audio/CMakeLists.txt @@ -1,31 +1,25 @@ # nomlib-audio module tests -set( NOM_BUILD_OPENAL_AUDIO_TESTS ON ) +set(NOM_BUILD_OPENAL_AUDIO_TESTS ON) -if( NOM_BUILD_OPENAL_AUDIO_TESTS ) - - add_executable( ALAudioTest "ALAudioTest.cpp" ) +if(NOM_BUILD_OPENAL_AUDIO_TESTS) + set(TEST_SRC "ALAudioTest.cpp") + add_executable(ALAudioTest ${TEST_SRC}) # FIXME: We only need init from nomlib-system - set( AUDIO_DEPS ${GTEST_LIBRARY} nomlib-core nomlib-audio nomlib-system ) + set(AUDIO_DEPS ${GTEST_LIBRARY} nomlib-core nomlib-math nomlib-audio + nomlib-system nomlib-serializers) - if( PLATFORM_WINDOWS ) - list( APPEND AUDIO_DEPS ${SDL2MAIN_LIBRARY} ) - endif( PLATFORM_WINDOWS ) + if(PLATFORM_WINDOWS) + list(APPEND AUDIO_DEPS ${SDL2MAIN_LIBRARY}) + endif(PLATFORM_WINDOWS) - target_link_libraries( ALAudioTest ${AUDIO_DEPS} ) + target_link_libraries(ALAudioTest ${AUDIO_DEPS}) - GTEST_ADD_TESTS( ${TESTS_INSTALL_DIR}/ALAudioTest - "" # args - "ALAudioTest.cpp" ) + GTEST_ADD_TESTS(${TESTS_INSTALL_DIR}/ALAudioTest "" ${TEST_SRC}) - # Resources for audio tests - install ( - DIRECTORY - "${EXAMPLES_SRC_DIR}/audio/Resources" - DESTINATION - "${TESTS_INSTALL_DIR}" - FILES_MATCHING PATTERN "*.wav" - ) + # Copy resource files for the test + install_resource_file("${NOM_TESTS_RESOURCES_DIR}/audio/ALAudioTest.json" + "${TESTS_INSTALL_DIR}") -endif( NOM_BUILD_OPENAL_AUDIO_TESTS ) +endif(NOM_BUILD_OPENAL_AUDIO_TESTS) diff --git a/tests/src/core/CMakeLists.txt b/tests/src/core/CMakeLists.txt index 33963eff..08ee0853 100644 --- a/tests/src/core/CMakeLists.txt +++ b/tests/src/core/CMakeLists.txt @@ -1,7 +1,12 @@ # nomlib-core module tests -set( NOM_BUILD_VERSION_INFO_TEST ON ) -set( NOM_BUILD_SDL2_LOGGER_TESTS ON ) +set(NOM_BUILD_VERSION_INFO_TEST ON) +set(NOM_BUILD_SDL2_LOGGER_TESTS ON) +set(NOM_BUILD_STRINGS_TEST ON) + +if( EXISTS "${CMAKE_CURRENT_LIST_DIR}/local_env.cmake" ) + include( "${CMAKE_CURRENT_LIST_DIR}/local_env.cmake" ) +endif() if(NOM_BUILD_VERSION_INFO_TEST) @@ -49,3 +54,21 @@ if( NOM_BUILD_SDL2_LOGGER_TESTS ) "ConsoleOutputTest.cpp" ) endif( NOM_BUILD_SDL2_LOGGER_TESTS ) + +if(NOM_BUILD_STRINGS_TEST) + + set(NOM_CORE_TESTS_DEPS ${GTEST_LIBRARY} nomlib-core) + + if(PLATFORM_WINDOWS) + list(APPEND NOM_CORE_TESTS_DEPS ${SDL2MAIN_LIBRARY}) + endif(PLATFORM_WINDOWS) + + add_executable(StringTest "StringTest.cpp") + + target_link_libraries(StringTest ${NOM_CORE_TESTS_DEPS}) + + GTEST_ADD_TESTS(${TESTS_INSTALL_DIR}/StringTest + "" # args + "StringTest.cpp") + +endif(NOM_BUILD_STRINGS_TEST) diff --git a/tests/src/core/StringTest.cpp b/tests/src/core/StringTest.cpp new file mode 100644 index 00000000..2f3a7e69 --- /dev/null +++ b/tests/src/core/StringTest.cpp @@ -0,0 +1,143 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014, 2015, 2016 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#include "gtest/gtest.h" + +#include "nomlib/config.hpp" +#include "nomlib/core.hpp" + +using namespace nom; + +TEST(StringTest, AllocateNullCString) +{ + const char* ret = nullptr; + + ret = nom::create_string(nullptr, 1); + if(ret) { + ret = "f"; + NOM_DUMP(ret); + } + + ret = nom::create_string(nullptr); + if(ret) { + ret = "f"; + NOM_DUMP(ret); + } +} + +TEST(StringTest, CompareSensitiveCString) +{ + int ret = 0; + + ret = nom::compare_cstr_sensitive("opengl", "opengl"); + EXPECT_EQ(0, ret); + + ret = nom::compare_cstr_sensitive("opengl", "Opengl"); + EXPECT_NE(0, ret); + + ret = nom::compare_cstr_sensitive("opengl", "Opengl", 2); + EXPECT_NE(0, ret); + + ret = nom::compare_cstr_sensitive("opengl", "opengl", 2); + EXPECT_EQ(0, ret); +} + +TEST(StringTest, CompareSensitiveString) +{ + int ret = 0; + + ret = nom::compare_string_sensitive("opengl", "Opengl"); + EXPECT_NE(0, ret); + + ret = nom::compare_string_sensitive("opengl", "Opengl", 1); + EXPECT_NE(0, ret); +} + +TEST(StringTest, CompareInsensitiveCString) +{ + int ret = 0; + + ret = nom::compare_cstr_insensitive("opengl", "openGL"); + EXPECT_EQ(0, ret); + + ret = nom::compare_cstr_insensitive("opengl", "openGL", 5); + EXPECT_EQ(0, ret); + + ret = nom::compare_cstr_insensitive("ALC", "ALC_testme", 2); + EXPECT_EQ(0, ret); +} + +TEST(StringTest, CompareInsensitiveString) +{ + int ret = 0; + + ret = nom::compare_string_insensitive("opengl", "openGL", 5); + EXPECT_EQ(0, ret); + + ret = nom::compare_string_insensitive("opengl", "openGL"); + EXPECT_EQ(0, ret); + + ret = nom::compare_string_insensitive("ALC", "ALC_testme", 3); + EXPECT_EQ(0, ret); +} + +TEST(StringTest, NullCStringToSignedInteger) +{ + int ret = 0; + + ret = nom::string_to_int(nullptr, 10); + NOM_DUMP(ret); + EXPECT_EQ(0, ret); + + ret = nom::string_to_int(nullptr); + NOM_DUMP(ret); + EXPECT_EQ(0, ret); +} + +TEST(StringTest, NullCStringToUnsignedInteger) +{ + uint ret = 0; + + ret = nom::string_to_uint(nullptr, 10); + NOM_DUMP(ret); + EXPECT_EQ(0, ret); + + ret = nom::string_to_uint(nullptr); + NOM_DUMP(ret); + EXPECT_EQ(0, ret); +} + +int main(int argc, char** argv) +{ + ::testing::InitGoogleTest(&argc, argv); + + // NOTE(jeff): There is no need to initialize nomlib -- nom::init -- before + // using the string functions. + + return RUN_ALL_TESTS(); +} diff --git a/tests/src/graphics/BMFontTest.cpp b/tests/src/graphics/BMFontTest.cpp index dd715288..088d08ce 100644 --- a/tests/src/graphics/BMFontTest.cpp +++ b/tests/src/graphics/BMFontTest.cpp @@ -37,6 +37,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include #include namespace nom { @@ -108,7 +109,7 @@ TEST_F(BMFontTest, BasicParserSanity) { BMFont font; Glyph g; - std::string filename = resources.path() + "scoreboard.fnt"; + std::string filename = resources.path() + "gameover.fnt"; std::ifstream fp(filename); @@ -120,49 +121,49 @@ TEST_F(BMFontTest, BasicParserSanity) EXPECT_EQ(IFont::FontType::BMFont, font.type() ); // Info tag - EXPECT_EQ(96, font.point_size() ); + EXPECT_EQ(72, font.point_size() ); // Common tag - EXPECT_EQ(114, font.newline(0) ); - EXPECT_EQ(114, font.metrics().newline); - EXPECT_EQ(114, font.metrics().ascent); - EXPECT_EQ(Size2i(1024,1024), font.page_size(0) ); + EXPECT_EQ(90, font.newline(0) ); + EXPECT_EQ(90, font.metrics().newline); + EXPECT_EQ(76, font.metrics().ascent); + EXPECT_EQ(Size2i(576, 512), font.page_size(0) ); // EXPECT_EQ(1, font.pages_.size() ); // First glyph (chars tag) g = font.glyph(32,0); - EXPECT_EQ(IntRect(-1,1,0,0), g.bounds) + EXPECT_EQ(IntRect(64, 82, 0, 0), g.bounds) << "Incorrect x, y, width or height for char"; - EXPECT_EQ(12, font.spacing(0) ) - << "Incorrect xadvance for char"; + EXPECT_EQ(18, font.spacing(0) ) + << "Incorrect xadvance for char id 32"; // Rendering offset - EXPECT_EQ( Point2i(0,0), g.offset ) + EXPECT_EQ( Point2i(0, 65), g.offset ) << "Incorrect xoffset or yoffset for char"; // Second glyph (chars tag) - g = font.glyph(48,0); - EXPECT_EQ(IntRect(205,1,204,118), g.bounds) + g = font.glyph(33,0); + EXPECT_EQ(IntRect(444, 418, 20, 62), g.bounds) << "Incorrect x, y, width or height for char"; - EXPECT_EQ(12, font.spacing(0) ) - << "Incorrect xadvance for char"; + EXPECT_EQ(18, font.spacing(0) ) + << "Incorrect xadvance for char id 32"; // Rendering offset - EXPECT_EQ( Point2i(0,0), g.offset ) + EXPECT_EQ( Point2i(9, 15), g.offset ) << "Incorrect xoffset or yoffset for char"; // Last glyph (chars tag) - g = font.glyph(57,0); - EXPECT_EQ(IntRect(411,241,135,118), g.bounds) + g = font.glyph(126,0); + EXPECT_EQ(IntRect(240, 484, 50, 22), g.bounds) << "Incorrect x, y, width or height for char"; - EXPECT_EQ(12, font.spacing(0) ) - << "Incorrect xadvance for char"; + EXPECT_EQ(18, font.spacing(0) ) + << "Incorrect xadvance for char id 32"; // Rendering offset - EXPECT_EQ( Point2i(0,0), g.offset ) + EXPECT_EQ( Point2i(1, 41), g.offset ) << "Incorrect xoffset or yoffset for char"; } @@ -190,37 +191,6 @@ TEST_F(BMFontTest, KerningParserSanity) EXPECT_EQ(-9, font.kerning(86,46,0) ); } -TEST_F(BMFontTest, RenderScoreboardFont) -{ - Glyph g; - nom::Font font; - - Text rendered_text; - std::string filename = resources.path() + "scoreboard.fnt"; - - ASSERT_TRUE( font.load(filename) ) - << "Could not load input texture source: " << filename; - - rendered_text.set_font(font); - rendered_text.set_text("7"); - - // TTcards player 2 scoreboard origins (approximation) - rendered_text.set_position( Point2i(64,0) ); - nom::set_alignment(&rendered_text, this->resolution(), Anchor::BottomLeft); - // player1 - // rendered_text.set_position( Point2i(-64,0) ); - // nom::set_alignment(&rendered_text, this->resolution(), Anchor::BottomRight); - - EXPECT_EQ( Size2i(13,114), rendered_text.size() ); - - this->append_render_callback( [&] ( const RenderWindow& win ) { - rendered_text.draw( this->render_window() ); - }); - - EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); - EXPECT_TRUE( this->compare() ); -} - TEST_F(BMFontTest, RenderGameOverFont) { nom::Font font; @@ -234,9 +204,10 @@ TEST_F(BMFontTest, RenderGameOverFont) rendered_text.set_font(font); rendered_text.set_text("You Lose..."); rendered_text.set_position( Point2i(0,0) ); - nom::set_alignment(&rendered_text, this->resolution(), Anchor::MiddleCenter); + nom::set_alignment( &rendered_text, rendered_text.position(), + this->resolution(), Anchor::MiddleCenter ); - EXPECT_EQ( Size2i(340,90), rendered_text.size() ); + EXPECT_EQ( Size2i(358,90), rendered_text.size() ); EXPECT_EQ( 90, font->newline(0) ) << "Text line spacing (newline) should be the same as the 'lineHeight' field."; @@ -261,9 +232,11 @@ TEST_F(BMFontTest, RenderMultipleLines) rendered_text.set_font(font); rendered_text.set_text("You Lose...\nYou Win!\nDraw"); rendered_text.set_position( Point2i(0,0) ); - nom::set_alignment(&rendered_text, this->resolution(), Anchor::MiddleCenter); + nom::set_alignment( &rendered_text, rendered_text.position(), + this->resolution(), Anchor::MiddleCenter ); - EXPECT_EQ( Size2i(340,270), rendered_text.size() ); + EXPECT_EQ(Size2i(358, 270), rendered_text.size() ) + << "The rendered text length should be the longest line: 'You Lose...'"; EXPECT_EQ( 90, font->newline(0) ) << "Text line spacing (newline) should be the same as the 'lineHeight' field."; @@ -289,12 +262,13 @@ TEST_F(BMFontTest, Kerning) rendered_text.set_font(font); rendered_text.set_text("WAV"); rendered_text.set_position( Point2i(0,0) ); - nom::set_alignment(&rendered_text, this->resolution(), Anchor::MiddleCenter); + nom::set_alignment( &rendered_text, rendered_text.position(), + this->resolution(), Anchor::MiddleCenter ); kerning_offset = font->kerning(87, 65, 0); EXPECT_EQ(-7, kerning_offset); - EXPECT_EQ( Size2i(159,90), rendered_text.size() ); + EXPECT_EQ( Size2i(169, 90), rendered_text.size() ); EXPECT_EQ( 90, font->newline(0) ) << "Text line spacing (newline) should be the same as the 'lineHeight' field."; @@ -320,14 +294,15 @@ TEST_F(BMFontTest, NoKerning) rendered_text.set_font(font); rendered_text.set_text("WAV"); rendered_text.set_position( Point2i(0,0) ); - nom::set_alignment(&rendered_text, this->resolution(), Anchor::MiddleCenter); - - font->set_font_kerning(false); + rendered_text.set_text_kerning(false); + nom::set_alignment( &rendered_text, rendered_text.position(), + this->resolution(), Anchor::MiddleCenter ); kerning_offset = font->kerning(87, 65, 0); EXPECT_EQ(0, kerning_offset); - EXPECT_EQ( Size2i(159,90), rendered_text.size() ); + EXPECT_EQ( Size2i(185,90), rendered_text.size() ) + << "Text length without kerning should be larger than the previous test!"; EXPECT_EQ( 90, font->newline(0) ) << "Text line spacing (newline) should be the same as the 'lineHeight' field."; @@ -354,7 +329,8 @@ TEST_F(BMFontTest, RenderAngelCodeBMFontExport) rendered_text.set_font(font); rendered_text.set_text("Hello, World!"); rendered_text.set_position( Point2i(0,0) ); - nom::set_alignment(&rendered_text, this->resolution(), Anchor::MiddleCenter); + nom::set_alignment( &rendered_text, rendered_text.position(), + this->resolution(), Anchor::MiddleCenter ); EXPECT_EQ( Size2i(169,32), rendered_text.size() ); EXPECT_EQ( 32, font->newline(0) ) @@ -368,6 +344,43 @@ TEST_F(BMFontTest, RenderAngelCodeBMFontExport) EXPECT_TRUE( this->compare() ); } +TEST_F(BMFontTest, ClonedTexture) +{ + nom::Font font; + + Text rendered_text; + std::string filename = resources.path() + "gameover.fnt"; + + ASSERT_TRUE( font.load(filename) ) + << "Could not load input texture source: " << filename; + + rendered_text.set_font(font); + rendered_text.set_text("You Lose..."); + rendered_text.set_position( Point2i(0,0) ); + + auto tex = std::make_shared(); + ASSERT_TRUE(tex != nullptr); + + EXPECT_EQ(true, tex->set_texture( rendered_text.clone_texture() ) ); + nom::set_alignment( tex.get(), rendered_text.position(), + this->resolution(), Anchor::MiddleCenter ); + + // Should **not** change the texture -- the clone should be a deep-copy + rendered_text.set_text("You Win!"); + + EXPECT_EQ( Size2i(289,90), rendered_text.size() ); + EXPECT_EQ( Size2i(358,90), tex->size() ); + + this->append_render_callback( [&] ( const RenderWindow& win ) { + if( tex != nullptr && tex->valid() == true ) { + tex->draw( this->render_window() ); + } + }); + + EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); + EXPECT_TRUE( this->compare() ); +} + } // namespace nom int main( int argc, char** argv ) diff --git a/tests/src/graphics/BitmapFontTest.cpp b/tests/src/graphics/BitmapFontTest.cpp index 7a668993..154c53ca 100644 --- a/tests/src/graphics/BitmapFontTest.cpp +++ b/tests/src/graphics/BitmapFontTest.cpp @@ -37,6 +37,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include #include namespace nom { @@ -78,7 +79,7 @@ class BitmapFontTest: public nom::VisualUnitTest // Text rendering defaults this->text = - "!\"#$%&'()*+,-.\n//0123456789\n:;<=>?@\nABCDEFGHIJKLMNOPQRSTUVWXYZ\n[\\]^_`\nabcdefghijklmnopqrstuvwxyz\n{|}~"; + "!\"#$%&'()*+,-.\n/0123456789\n:;<=>?@\nABCDEFGHIJKLMNOPQRSTUVWXYZ\n[\\]^_`\nabcdefghijklmnopqrstuvwxyz\n{|}~"; this->pt_size = nom::DEFAULT_FONT_SIZE; this->pos = Point2i(0,0); @@ -119,11 +120,12 @@ class BitmapFontTest: public nom::VisualUnitTest // text size is **not** implemented for bitmap fonts. this->rendered_text.set_style(this->style); this->rendered_text.set_text_size(this->pt_size); - - // FIXME: Proper multi-line alignment logic is not implemented this->rendered_text.set_position(this->pos); - nom::set_alignment( &this->rendered_text, - this->resolution(), this->align); + + this->append_update_callback( [=] (float) { + nom::set_alignment( &this->rendered_text, Point2i(0, 0), + this->resolution(), this->align ); + }); this->append_render_callback( [&] ( const RenderWindow& win ) { this->rendered_text.draw( this->render_window() ); @@ -240,11 +242,11 @@ TEST_F(BitmapFontTest, NoKerning) EXPECT_EQ(true, this->load_font(font) ) << "Could not load font file: " << font; - this->font->set_font_kerning(false); + this->rendered_text.set_text_kerning(false); kerning_offset = this->font->kerning(87, 65, this->pt_size); EXPECT_EQ(0, kerning_offset) - << "nom::BitmapFont does not implemented kerning pair offsets???"; + << "nom::BitmapFont does not implement kerning pair offsets!"; EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); EXPECT_TRUE( this->compare() ); diff --git a/tests/src/graphics/CMakeLists.txt b/tests/src/graphics/CMakeLists.txt index a510c68f..915cbd52 100644 --- a/tests/src/graphics/CMakeLists.txt +++ b/tests/src/graphics/CMakeLists.txt @@ -5,6 +5,11 @@ set( NOM_BUILD_GRADIENT_TESTS ON ) set( NOM_BUILD_BITMAP_FONT_TEST ON ) set( NOM_BUILD_TRUETYPE_FONT_TEST ON ) set( NOM_BUILD_BMFONT_TEST ON ) +set( NOM_BUILD_SPRITE_TESTS ON ) + +if( EXISTS "${CMAKE_CURRENT_LIST_DIR}/local_env.cmake" ) + include( "${CMAKE_CURRENT_LIST_DIR}/local_env.cmake" ) +endif() if( NOM_BUILD_RENDERWINDOW_TESTS ) @@ -38,10 +43,8 @@ if( NOM_BUILD_GRADIENT_TESTS ) target_link_libraries( GradientTest nomlib-graphics nomlib-visual-unit-test ) - GTEST_ADD_TESTS ( ${TESTS_INSTALL_DIR}/GradientTest - "" # args - "GradientTest.cpp" - ) + nom_add_visual_test( GradientTest + ${TESTS_INSTALL_DIR}/GradientTest ) # Copy resource files for the test install ( @@ -59,10 +62,8 @@ if(NOM_BUILD_BITMAP_FONT_TEST) target_link_libraries( BitmapFontTest nomlib-graphics nomlib-visual-unit-test ) - GTEST_ADD_TESTS ( ${TESTS_INSTALL_DIR}/BitmapFontTest - "" # args - "BitmapFontTest.cpp" - ) + nom_add_visual_test( BitmapFontTest + "${TESTS_INSTALL_DIR}/BitmapFontTest" ) # Copy resource files for the test install ( @@ -80,9 +81,8 @@ if(NOM_BUILD_TRUETYPE_FONT_TEST) target_link_libraries( TrueTypeFontTest nomlib-graphics nomlib-visual-unit-test ) - GTEST_ADD_TESTS ( ${TESTS_INSTALL_DIR}/TrueTypeFontTest - "" # args - "TrueTypeFontTest.cpp" ) + nom_add_visual_test( TrueTypeFontTest + "${TESTS_INSTALL_DIR}/TrueTypeFontTest" ) # Copy resource files for the test install ( @@ -100,9 +100,9 @@ if(NOM_BUILD_BMFONT_TEST) target_link_libraries( BMFontTest nomlib-graphics nomlib-visual-unit-test ) - GTEST_ADD_TESTS ( ${TESTS_INSTALL_DIR}/BMFontTest - "" # args - "BMFontTest.cpp" ) + nom_add_visual_test( BMFontTest + "${TESTS_INSTALL_DIR}/BMFontTest" ) + # Copy resource files for the test install ( @@ -113,3 +113,19 @@ if(NOM_BUILD_BMFONT_TEST) ) endif(NOM_BUILD_BMFONT_TEST) + +if(NOM_BUILD_SPRITE_TESTS) + + add_executable(SpriteTest "SpriteTest.cpp") + + target_link_libraries(SpriteTest nomlib-graphics nomlib-visual-unit-test) + + nom_add_visual_test( SpriteTest + ${TESTS_INSTALL_DIR}/SpriteTest ) + + # Copy resource files for the test + install( FILES + "${NOM_TESTS_RESOURCES_DIR}/graphics/SpriteTest.json" + DESTINATION "${TESTS_INSTALL_DIR}" ) + +endif(NOM_BUILD_SPRITE_TESTS) diff --git a/tests/src/graphics/GradientTest.cpp b/tests/src/graphics/GradientTest.cpp index de3356b8..6e8b374e 100644 --- a/tests/src/graphics/GradientTest.cpp +++ b/tests/src/graphics/GradientTest.cpp @@ -1,3 +1,31 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ #include #include @@ -8,6 +36,7 @@ #include #include +#include #include // #if ! defined( NOM_USE_SCALEX ) @@ -53,19 +82,17 @@ class GradientTest: public nom::VisualUnitTest // VisualUnitTest environment init... VisualUnitTest::SetUp(); - EventCallback debug_info( [&] ( const Event& evt ) - { - NOM_LOG_INFO( NOM_LOG_CATEGORY_TEST, "x:", evt.mouse.x, "y:", evt.mouse.y ); - } - ); + auto debug_info = ( [&](const Event& evt) { + NOM_LOG_INFO( NOM_LOG_CATEGORY_TEST, "x:", evt.mouse.x, "y:", evt.mouse.y ); + }); InputActionMapper state; - state.insert( "debug_info", nom::MouseButtonAction( - SDL_MOUSEBUTTONDOWN, SDL_BUTTON_LEFT ), + state.insert( "debug_info", + nom::MouseButtonAction(MouseButton::LEFT_MOUSE_BUTTON), debug_info ); - this->input_mapper_.insert( "debug_info", state, true ); + this->input_mapper_.insert("debug_info", state, true); this->colors[0] = { Color4iColors{ @@ -88,10 +115,6 @@ class GradientTest: public nom::VisualUnitTest this->pos2 = Point2i( 0, h / 2 ); this->dims2 = Size2i( w, h / 2 ); - // Not used - this->pos3 = Point2i( w/3, h/2 ); - this->dims3 = Size2i( w/3, h/3 ); - this->grad1.set_colors( this->colors[0] ); this->grad1.set_position( this->pos1 ); this->grad1.set_size( this->dims1 ); @@ -100,15 +123,9 @@ class GradientTest: public nom::VisualUnitTest this->grad2.set_position( this->pos2 ); this->grad2.set_size( this->dims2 ); - // Not used - this->grad3.set_colors( this->colors[0] ); - this->grad3.set_position( this->pos3 ); - this->grad3.set_size( this->dims3 ); - // Register our main loop this->append_render_callback( [&] ( const RenderWindow& win ) { this->grad1.draw( this->render_window() ); } ); this->append_render_callback( [&] ( const RenderWindow& win ) { this->grad2.draw( this->render_window() ); } ); - // this->append_render_callback( [&] ( const RenderWindow& win ) { this->grad3.draw( this->render_window() ); } ); } /// \remarks This method is called before destruction, at the end of each @@ -135,8 +152,6 @@ class GradientTest: public nom::VisualUnitTest Gradient grad1; Gradient grad2; - // Not used - Gradient grad3; Point2i pos1; Size2i dims1; @@ -144,10 +159,6 @@ class GradientTest: public nom::VisualUnitTest Point2i pos2; Size2i dims2; - // Not used - Point2i pos3; - Size2i dims3; - /// \brief Colors used to create the gradient /// \remarks One of two used @@ -158,7 +169,6 @@ TEST_F( GradientTest, TopToBottomLinearFill ) { this->grad1.set_fill_direction( Gradient::FillDirection::Top ); this->grad2.set_fill_direction( Gradient::FillDirection::Top ); - // this->grad3.set_fill_direction( Gradient::FillDirection::Top ); EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); EXPECT_TRUE( this->compare() ); @@ -168,7 +178,6 @@ TEST_F( GradientTest, BottomToTopLinearFill ) { this->grad1.set_fill_direction( Gradient::FillDirection::Bottom ); this->grad2.set_fill_direction( Gradient::FillDirection::Bottom ); - // this->grad3.set_fill_direction( Gradient::FillDirection::Bottom ); EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); EXPECT_TRUE( this->compare() ); @@ -178,7 +187,6 @@ TEST_F( GradientTest, LeftToRightLinearFill ) { this->grad1.set_fill_direction( Gradient::FillDirection::Left ); this->grad2.set_fill_direction( Gradient::FillDirection::Left ); - // this->grad3.set_fill_direction( Gradient::FillDirection::Left ); EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); EXPECT_TRUE( this->compare() ); @@ -188,7 +196,6 @@ TEST_F( GradientTest, RightToLeftLinearFill ) { this->grad1.set_fill_direction( Gradient::FillDirection::Right ); this->grad2.set_fill_direction( Gradient::FillDirection::Right ); - // this->grad3.set_fill_direction( Gradient::FillDirection::Right ); EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); EXPECT_TRUE( this->compare() ); @@ -274,6 +281,51 @@ TEST_F( GradientTest, Margins ) EXPECT_TRUE( this->compare() ); } +TEST_F(GradientTest, SharedTextureForSprite) +{ + this->grad1.set_fill_direction(Gradient::FillDirection::Right); + this->grad2.set_fill_direction(Gradient::FillDirection::Right); + +#if 1 + auto grad1_tex = + std::shared_ptr( this->grad1.texture() ); + auto grad2_tex = + std::shared_ptr( this->grad2.texture() ); +#else + // ...Broken... + auto grad1_tex = std::shared_ptr( this->grad1.clone_texture() ); + auto grad2_tex = std::shared_ptr( this->grad2.clone_texture() ); +#endif + + auto sprite_grad1 = + nom::make_shared_sprite(grad1_tex); + ASSERT_TRUE(sprite_grad1 != nullptr); + ASSERT_TRUE(sprite_grad1->valid() != false); + + auto sprite_grad2 = + nom::make_shared_sprite(grad2_tex); + ASSERT_TRUE(sprite_grad2 != nullptr); + ASSERT_TRUE(sprite_grad2->valid() != false); + + // Need to first clear out the default grad objects in here + this->clear_render_callbacks(); + this->append_render_callback( this->default_render_callback() ); + + this->append_render_callback( [=](const RenderWindow& win) { + + if( sprite_grad1 != nullptr && sprite_grad1->valid() == true ) { + sprite_grad1->draw( this->render_window() ); + } + + if( sprite_grad2 != nullptr && sprite_grad2->valid() == true ) { + sprite_grad2->draw( this->render_window() ); + } + }); + + EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); + EXPECT_TRUE( this->compare() ); +} + } // namespace nom int main( int argc, char** argv ) @@ -292,7 +344,7 @@ int main( int argc, char** argv ) // nom::UnitTest framework integration nom::init_test( argc, argv ); - nom::SDL2Logger::set_logging_priority( NOM_LOG_CATEGORY_RENDER, nom::NOM_LOG_PRIORITY_VERBOSE ); + // nom::SDL2Logger::set_logging_priority( NOM_LOG_CATEGORY_RENDER, nom::NOM_LOG_PRIORITY_VERBOSE ); // nom::SDL2Logger::set_logging_priority( NOM_LOG_CATEGORY_TRACE, nom::NOM_LOG_PRIORITY_VERBOSE ); return RUN_ALL_TESTS(); diff --git a/tests/src/graphics/RenderWindowTest.cpp b/tests/src/graphics/RenderWindowTest.cpp index 3bc7508d..5d47ded5 100644 --- a/tests/src/graphics/RenderWindowTest.cpp +++ b/tests/src/graphics/RenderWindowTest.cpp @@ -52,12 +52,11 @@ class RenderWindowTest: public ::testing::Test virtual void SetUp( void ) { - const int WINDOW_WIDTH = 320; - const int WINDOW_HEIGHT = 240; + const auto WINDOW_RESOLUTION = Size2i(320, 240); // uint32 wflags = SDL_WINDOW_SHOWN; uint32 wflags = SDL_WINDOW_HIDDEN; - ASSERT_TRUE( this->window.create( "RenderWindowTest", WINDOW_WIDTH, WINDOW_HEIGHT, wflags ) == true ); + ASSERT_TRUE( this->window.create( "RenderWindowTest", WINDOW_RESOLUTION, wflags) == true ); } virtual void TearDown( void ) @@ -94,6 +93,36 @@ TEST_F( RenderWindowTest, SetWindowTitle ) EXPECT_EQ( "SetWindowTitle", this->window.window_title() ); } +TEST_F(RenderWindowTest, DisplayModes) +{ + bool ret; + DisplayModeList modes; + + ret = this->window.display_modes(modes); + + EXPECT_EQ(true, ret); + EXPECT_NE(0, modes.size() ); + + nom::size_type mode_idx = 0; + for( auto itr = modes.begin(); itr != modes.end(); ++itr ) { + NOM_LOG_INFO( NOM_LOG_CATEGORY_TEST, + "Mode", mode_idx, + (itr)->bounds.w, "x", (itr)->bounds.h, + "x", SDL_BITSPERPIXEL( (itr)->format ), + "@", (itr)->refresh_rate, "Hz" ); + ++mode_idx; + } +} + +TEST_F(RenderWindowTest, VerticalRefreshRate) +{ + int refresh_rate = -1; + + refresh_rate = this->window.refresh_rate(); + + EXPECT_NE(-1, refresh_rate); +} + } // namespace nom int main( int argc, char** argv ) @@ -102,9 +131,16 @@ int main( int argc, char** argv ) // Set the current working directory path to the path leading to this // executable file; used for unit tests that require file-system I/O. - NOM_ASSERT( nom::init( argc, argv ) == true ); - - atexit( nom::quit ); + if( nom::init( argc, argv ) == false ) + { + NOM_LOG_CRIT(NOM_LOG_CATEGORY_APPLICATION, "Could not initialize nomlib."); + return NOM_EXIT_FAILURE; + } + atexit(nom::quit); + + // Disable verbose, debug output + nom::SDL2Logger::set_logging_priority( NOM_LOG_CATEGORY_TEST, + nom::NOM_LOG_PRIORITY_CRITICAL ); return RUN_ALL_TESTS(); } diff --git a/tests/src/graphics/SpriteTest.cpp b/tests/src/graphics/SpriteTest.cpp new file mode 100644 index 00000000..5382234c --- /dev/null +++ b/tests/src/graphics/SpriteTest.cpp @@ -0,0 +1,432 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#include + +// nom::VisualUnitTest framework +#include "nomlib/tests/VisualUnitTest.hpp" + +#include +#include +#include +#include +#include + +namespace nom { + +class SpriteTest: public nom::VisualUnitTest +{ + public: + /// \remarks This method is called at the start of each unit test. + SpriteTest() + { + NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_UNIT_TEST, + NOM_LOG_PRIORITY_VERBOSE ); + + this->set_window_flags(SDL_WINDOW_RESIZABLE); + + if( nom::set_hint(SDL_HINT_RENDER_SCALE_QUALITY, "nearest") == false ) { + NOM_LOG_INFO( NOM_LOG_CATEGORY_APPLICATION, + "Failed to set render scale quality to", "nearest" ); + } + + // The frame image to compare against the reference image set + this->append_screenshot_frame(0); + } + + /// \remarks This method is called at the end of each unit test. + virtual ~SpriteTest() + { + NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_UNIT_TEST, + NOM_LOG_PRIORITY_VERBOSE ); + } + + /// \remarks This method is called after construction, at the start of each + /// unit test. + virtual void SetUp() + { + NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_UNIT_TEST, + NOM_LOG_PRIORITY_VERBOSE ); + + std::string res_file = nom::UnitTest::test_set() + ".json"; + + // Determine our resources path based on several possible locations; + // this is dependent upon the build environment + if( resources[0].load_file( res_file, "resources" ) == false ) { + FAIL() + << "Could not resolve the resource path from file: " << res_file; + } + + if( resources[1].load_file( res_file, "common" ) == false ) { + FAIL() + << "Could not resolve the resource path from file: " << res_file; + } + + // VisualUnitTest environment init + VisualUnitTest::SetUp(); + } + + /// \remarks This method is called before destruction, at the end of each + /// unit test. + virtual void TearDown() + { + NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_UNIT_TEST, + NOM_LOG_PRIORITY_VERBOSE ); + } + + /// \remarks This method is called at the start of each test case. + static void SetUpTestCase() + { + NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_UNIT_TEST, + NOM_LOG_PRIORITY_VERBOSE ); + } + + /// \remarks This method is called at the end of each test case. + static void TearDownTestCase() + { + NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE_UNIT_TEST, + NOM_LOG_PRIORITY_VERBOSE ); + } + + struct sprite_test + { + // input + std::string sprite_tex_path; + Point2i pos = Point2i::zero; + Texture::Access tex_type; + + // output + Texture tex; + Sprite sprite; + }; + + void init_sprite_test(sprite_test& params) + { + if( params.sprite_tex_path != "" ) { + + if( params.tex.load(params.sprite_tex_path, false, + params.tex_type) == false ) + { + FAIL() << "Could not load the sprite texture from: " + << params.sprite_tex_path; + } + + EXPECT_EQ(true, params.sprite.set_texture(params.tex) ); + ASSERT_TRUE(params.sprite.valid() == true); + } + + if( params.sprite.valid() == true ) { + nom::set_alignment( ¶ms.sprite, params.pos, WINDOW_DIMS, + Anchor::MiddleCenter ); + } + + this->append_render_callback( [=, ¶ms](const RenderWindow& win) { + + ASSERT_TRUE(params.sprite.valid() == true); + if( params.sprite.valid() ) { + params.sprite.draw( this->render_window() ); + } + }); + } + + protected: + const Size2i WINDOW_DIMS = Size2i( this->resolution() ); + SearchPath resources[2]; +}; + +TEST_F(SpriteTest, SpriteInterfaceWithTextureReference) +{ + sprite_test params; + params.sprite_tex_path = + this->resources[0].path() + "card.png"; + params.tex_type = Texture::Access::Streaming; + + this->init_sprite_test(params); + + EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); + EXPECT_TRUE( this->compare() ); +} + +TEST_F(SpriteTest, SpriteInterfaceWithTextureRawPointer) +{ + const std::string TEX_FILE_PATH = + this->resources[0].path() + "card.png"; + const Point2i SPRITE_POS(Point2i::zero); + + Texture* tex = new Texture(); + nom::Sprite* sprite = new Sprite(); + ASSERT_TRUE(tex != nullptr); + ASSERT_TRUE(sprite != nullptr); + + if( tex->load(TEX_FILE_PATH, false, + nom::Texture::Access::Streaming) == false ) + { + FAIL() << "Could not load the sprite texture from: " << TEX_FILE_PATH; + } + ASSERT_TRUE(tex->valid() == true); + + EXPECT_EQ(true, sprite->set_texture(tex) ); + ASSERT_TRUE(sprite->valid() == true); + + nom::set_alignment(sprite, SPRITE_POS, WINDOW_DIMS, Anchor::MiddleCenter); + + this->append_render_callback( [=, &sprite](const RenderWindow& win) { + if( sprite->valid() ) { + sprite->draw( this->render_window() ); + } + }); + + EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); + EXPECT_TRUE( this->compare() ); + + NOM_DELETE_PTR(sprite); +} + +TEST_F(SpriteTest, SpriteInterfaceWithTextureRawPointerAsReference) +{ + const std::string TEX_FILE_PATH = + this->resources[0].path() + "card.png"; + const Point2i SPRITE_POS(Point2i::zero); + + Texture* tex = new Texture(); + nom::Sprite* sprite = new Sprite(); + ASSERT_TRUE(tex != nullptr); + ASSERT_TRUE(sprite != nullptr); + + if( tex->load(TEX_FILE_PATH, false, + nom::Texture::Access::Streaming) == false ) + { + FAIL() << "Could not load the sprite texture from: " << TEX_FILE_PATH; + } + ASSERT_TRUE(tex->valid() == true); + + EXPECT_EQ(true, sprite->set_texture(*tex) ); + ASSERT_TRUE(sprite->valid() == true); + + nom::set_alignment(sprite, SPRITE_POS, WINDOW_DIMS, Anchor::MiddleCenter); + + this->append_render_callback( [=, &sprite](const RenderWindow& win) { + if( sprite->valid() ) { + sprite->draw( this->render_window() ); + } + }); + + EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); + EXPECT_TRUE( this->compare() ); + + NOM_DELETE_PTR(sprite); + NOM_DELETE_PTR(tex); +} + +TEST_F(SpriteTest, SpriteInterfaceWithTextureUniquePointer) +{ + const std::string TEX_FILE_PATH = + this->resources[0].path() + "card.png"; + const Point2i SPRITE_POS(Point2i::zero); + + std::unique_ptr tex; + tex.reset( new Texture() ); + ASSERT_TRUE(tex != nullptr); + + std::unique_ptr sprite; + sprite.reset( new Sprite() ); + ASSERT_TRUE(sprite != nullptr); + + if( tex->load(TEX_FILE_PATH, false, + nom::Texture::Access::Streaming) == false ) + { + FAIL() << "Could not load the sprite texture from: " << TEX_FILE_PATH; + } + ASSERT_TRUE(tex->valid() == true); + + EXPECT_EQ(true, sprite->set_texture( *tex.get()) ); + ASSERT_TRUE(sprite->valid() == true); + + nom::set_alignment( sprite.get(), SPRITE_POS, WINDOW_DIMS, + Anchor::MiddleCenter ); + + this->append_render_callback( [=, &sprite](const RenderWindow& win) { + if( sprite->valid() ) { + sprite->draw( this->render_window() ); + } + }); + + EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); + EXPECT_TRUE( this->compare() ); +} + +TEST_F(SpriteTest, SpriteInterfaceWithTextureSharedPointer) +{ + const std::string TEX_FILE_PATH = + this->resources[0].path() + "card.png"; + const Point2i SPRITE_POS(Point2i::zero); + + std::shared_ptr tex; + tex.reset( new Texture() ); + + std::shared_ptr sprite; + sprite.reset( new Sprite() ); + + ASSERT_TRUE(tex != nullptr); + ASSERT_TRUE(sprite != nullptr); + + if( tex->load(TEX_FILE_PATH, false, + nom::Texture::Access::Streaming) == false ) + { + FAIL() << "Could not load the sprite texture from: " << TEX_FILE_PATH; + } + ASSERT_TRUE(tex->valid() == true); + + EXPECT_EQ(true, sprite->set_texture(tex) ); + ASSERT_TRUE(sprite->valid() == true); + + nom::set_alignment(sprite.get(), SPRITE_POS, WINDOW_DIMS, Anchor::MiddleCenter); + + this->append_render_callback( [=, &sprite](const RenderWindow& win) { + if( sprite->valid() ) { + sprite->draw( this->render_window() ); + } + }); + + EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); + EXPECT_TRUE( this->compare() ); +} + +TEST_F(SpriteTest, SpriteInterfaceSetAlpha) +{ + sprite_test params; + params.sprite_tex_path = this->resources[0].path() + "card.png"; + params.tex_type = Texture::Access::Streaming; + + this->init_sprite_test(params); + + EXPECT_EQ(true, params.sprite.set_alpha(128) ); + + EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); + EXPECT_TRUE( this->compare() ); +} + +TEST_F(SpriteTest, SpriteInterfaceSetColor) +{ + sprite_test params; + params.sprite_tex_path = this->resources[0].path() + "card.png"; + params.tex_type = Texture::Access::Streaming; + + this->init_sprite_test(params); + + params.sprite.set_color(Color4i::Magenta); + + EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); + EXPECT_TRUE( this->compare() ); +} + +TEST_F(SpriteTest, SpriteInterfaceInitWithColor) +{ + sprite_test params; + params.sprite_tex_path = ""; + + const auto SPRITE_COLOR = Color4i::Magenta; + const auto SPRITE_DIMS = WINDOW_DIMS/2; + + this->init_sprite_test(params); + params.sprite.init_with_color(SPRITE_COLOR, SPRITE_DIMS); + + if( params.sprite.valid() == true ) { + nom::set_alignment( ¶ms.sprite, params.pos, WINDOW_DIMS, + Anchor::MiddleCenter ); + } + + EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); + EXPECT_TRUE( this->compare() ); +} + +TEST_F(SpriteTest, SpriteBatchInterface) +{ + const std::string TEX_FILE_PATH = + this->resources[1].path() + "cursors.png"; + const std::string SHEET_FILE_PATH = + this->resources[1].path() + "cursors.json"; + const Point2i SPRITE_POS(Point2i::zero); + + SpriteSheet sprite_frames; + Texture tex; + SpriteBatch sprite; + + if( sprite_frames.load_file(SHEET_FILE_PATH) == false ) { + FAIL() << "Could not load the sprite sheet from: " << SHEET_FILE_PATH; + } + + if( tex.load(TEX_FILE_PATH, false, + nom::Texture::Access::Streaming) == false ) + { + FAIL() << "Could not load the sprite texture from: " << TEX_FILE_PATH; + } + ASSERT_TRUE(tex.valid() == true); + + EXPECT_EQ(true, sprite.set_texture(tex) ); + ASSERT_TRUE(sprite.valid() == true); + + sprite.set_sprite_sheet(sprite_frames); + sprite.set_frame(3); // Right-pointing cursor hand + + nom::set_alignment(&sprite, SPRITE_POS, WINDOW_DIMS, Anchor::MiddleCenter); + + this->append_render_callback( [=, &sprite](const RenderWindow& win) { + if( sprite.valid() ) { + sprite.draw( this->render_window() ); + } + }); + + EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); + EXPECT_TRUE( this->compare() ); +} + +} // namespace nom + +int main(int argc, char** argv) +{ + ::testing::InitGoogleTest(&argc, argv); + + // Set the current working directory path to the path leading to this + // executable file; used for unit tests that require file-system I/O. + if( nom::init(argc, argv) == false ) + { + NOM_LOG_CRIT(NOM_LOG_CATEGORY_APPLICATION, "Could not initialize nomlib."); + return NOM_EXIT_FAILURE; + } + atexit( nom::quit ); + + // nom::UnitTest framework integration + nom::init_test(argc, argv); + + // nom::SDL2Logger::set_logging_priority(NOM_LOG_CATEGORY_TEST, NOM_LOG_PRIORITY_CRITICAL); + // nom::SDL2Logger::set_logging_priority(NOM_LOG_CATEGORY_TEST, NOM_LOG_PRIORITY_DEBUG); + // nom::SDL2Logger::set_logging_priority(NOM_LOG_CATEGORY_APPLICATION, nom::NOM_LOG_PRIORITY_CRITICAL); + + return RUN_ALL_TESTS(); +} diff --git a/tests/src/graphics/TrueTypeFontTest.cpp b/tests/src/graphics/TrueTypeFontTest.cpp index 74ff96eb..ef677463 100644 --- a/tests/src/graphics/TrueTypeFontTest.cpp +++ b/tests/src/graphics/TrueTypeFontTest.cpp @@ -37,6 +37,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include #include namespace nom { @@ -78,7 +79,7 @@ class TrueTypeFontTest: public nom::VisualUnitTest // Text rendering defaults this->text = - "!\"#$%&'()*+,-.\n//0123456789\n:;<=>?@\nABCDEFGHIJKLMNOPQRSTUVWXYZ\n[\\]^_`\nabcdefghijklmnopqrstuvwxyz\n{|}~"; + "!\"#$%&'()*+,-.\n/0123456789\n:;<=>?@\nABCDEFGHIJKLMNOPQRSTUVWXYZ\n[\\]^_`\nabcdefghijklmnopqrstuvwxyz\n{|}~"; this->pt_size = nom::DEFAULT_FONT_SIZE; this->pos = Point2i(0,0); @@ -116,11 +117,13 @@ class TrueTypeFontTest: public nom::VisualUnitTest this->rendered_text.set_style(this->style); this->rendered_text.set_text_size(this->pt_size); this->rendered_text.set_color(this->color); - - // FIXME: Proper multi-line alignment logic is not implemented this->rendered_text.set_position(this->pos); - nom::set_alignment( &this->rendered_text, - this->resolution(), this->align); + + this->append_update_callback( [=](float) { + nom::set_alignment( &this->rendered_text, Point2i(0,0), + Size2i( this->resolution() ), + this->align ); + }); this->append_render_callback( [&] ( const RenderWindow& win ) { this->rendered_text.draw( this->render_window() ); @@ -143,7 +146,7 @@ class TrueTypeFontTest: public nom::VisualUnitTest int pt_size; /// \brief The text rendering position. - Point2i pos; + Point2i pos = Point2i::zero; /// \brief The text alignment. uint32 align = Anchor::None; @@ -162,6 +165,10 @@ TEST_F(TrueTypeFontTest, OpenSansRegular) EXPECT_EQ(true, this->load_font(font) ) << "Could not load font file: " << font; + EXPECT_EQ(Size2i(223, 119), rendered_text.size() ) + << "The rendered text length should be the longest line: " + << "'ABCDEFGHIJKLMNOPQRSTUVWXYZ'"; + EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); EXPECT_TRUE( this->compare() ); } @@ -174,6 +181,10 @@ TEST_F(TrueTypeFontTest, OpenSansBold) EXPECT_EQ(true, this->load_font(font) ) << "Could not load font file: " << font; + EXPECT_EQ(Size2i(236, 119), rendered_text.size() ) + << "The rendered text length should be the longest line: " + << "'ABCDEFGHIJKLMNOPQRSTUVWXYZ'"; + EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); EXPECT_TRUE( this->compare() ); } @@ -186,6 +197,10 @@ TEST_F(TrueTypeFontTest, LiberationSerif) EXPECT_EQ(true, this->load_font(font) ) << "Could not load font file: " << font; + EXPECT_EQ(Size2i(238, 98), rendered_text.size() ) + << "The rendered text length should be the longest line: " + << "'ABCDEFGHIJKLMNOPQRSTUVWXYZ'"; + EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); EXPECT_TRUE( this->compare() ); } @@ -198,6 +213,10 @@ TEST_F(TrueTypeFontTest, LiberationSerifBold) EXPECT_EQ(true, this->load_font(font) ) << "Could not load font file: " << font; + EXPECT_EQ(Size2i(239, 98), rendered_text.size() ) + << "The rendered text length should be the longest line: " + << "'ABCDEFGHIJKLMNOPQRSTUVWXYZ'"; + EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); EXPECT_TRUE( this->compare() ); } @@ -212,6 +231,10 @@ TEST_F(TrueTypeFontTest, MagentaText) EXPECT_EQ(true, this->load_font(font) ) << "Could not load font file: " << font; + EXPECT_EQ(Size2i(223, 119), rendered_text.size() ) + << "The rendered text length should be the longest line: " + << "'ABCDEFGHIJKLMNOPQRSTUVWXYZ'"; + EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); EXPECT_TRUE( this->compare() ); } @@ -233,6 +256,8 @@ TEST_F(TrueTypeFontTest, Kerning) << "Are you not using a build of SDL2_ttf with the kerning pairs patch " << "applied? See third-party/README.md for details."; + EXPECT_EQ( Size2i(202, 131), rendered_text.size() ); + EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); EXPECT_TRUE( this->compare() ); } @@ -249,11 +274,14 @@ TEST_F(TrueTypeFontTest, NoKerning) EXPECT_EQ(true, this->load_font(font) ) << "Could not load font file: " << font; - this->font->set_font_kerning(false); + this->rendered_text.set_text_kerning(false); kerning_offset = this->font->kerning(87, 65, this->pt_size); EXPECT_EQ(0, kerning_offset); + EXPECT_EQ( Size2i(210, 131), rendered_text.size() ) + << "Text length without kerning should be larger than the previous test!"; + EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); EXPECT_TRUE( this->compare() ); } @@ -474,8 +502,16 @@ TEST_F(TrueTypeFontTest, UseAllTextStyles) EXPECT_TRUE( this->compare() ); } +/// \remarks This test is only ran when the interactive flag (-i) is passed. TEST_F(TrueTypeFontTest, InteractiveGlyphCache) { + // No point in running this test when the end-user is not present; this + // speeds up automated test runs, particularly in the case of older systems. + if( NOM_TEST_FLAG(interactive) == false ) { + SUCCEED(); + return; + } + const int MIN_POINT_SIZE = 9; const int MAX_POINT_SIZE = 72; std::stringstream help_info; @@ -497,6 +533,10 @@ TEST_F(TrueTypeFontTest, InteractiveGlyphCache) this->pt_size = MIN_POINT_SIZE; this->align = Anchor::MiddleCenter; + // Caching font point sizes notification for slower systems + this->render_window().set_window_title( this->test_set() + "::" + + this->test_name() + "Loading..." ); + ASSERT_TRUE(this->load_font(font) == true) << "Could not load font file: " << font; @@ -510,57 +550,37 @@ TEST_F(TrueTypeFontTest, InteractiveGlyphCache) // Register additional input bindings InputActionMapper wheel; - EventCallback zoom_in( [&] (const Event& evt) { + auto zoom_in = ( [&](const Event& evt) { int current_size = this->rendered_text.text_size(); if( current_size < MAX_POINT_SIZE ) { this->rendered_text.set_text_size(current_size += 1); - - // Reset alignment calcs - nom::set_alignment( &this->rendered_text, - Size2i( this->resolution() ), - Anchor::None ); - - nom::set_alignment( &this->rendered_text, - Size2i( this->resolution() ), - this->align ); } }); - EventCallback zoom_out( [&] (const Event& evt) { + auto zoom_out = ( [&](const Event& evt) { int current_size = this->rendered_text.text_size(); if( current_size >= MIN_POINT_SIZE ) { this->rendered_text.set_text_size(current_size -= 1); - - // Reset alignment calcs - nom::set_alignment( &this->rendered_text, - Size2i( this->resolution() ), - Anchor::None ); - - nom::set_alignment( &this->rendered_text, - Size2i( this->resolution() ), - this->align ); } }); - wheel.insert( "zoom_in", - MouseWheelAction( SDL_MOUSEWHEEL, - MouseWheelAction::AXIS_Y, - MouseWheelAction::UP ), zoom_in ); - - wheel.insert( "zoom_out", - MouseWheelAction( SDL_MOUSEWHEEL, - MouseWheelAction::AXIS_Y, - MouseWheelAction::DOWN ), zoom_out ); + wheel.insert("zoom_in", MouseWheelAction(nom::MOUSE_WHEEL_UP), zoom_in); + wheel.insert("zoom_out", MouseWheelAction(nom::MOUSE_WHEEL_DOWN), zoom_out); this->input_mapper_.insert("zoom_in", wheel, true); this->input_mapper_.insert("zoom_out", wheel, true); - if( NOM_TEST_FLAG(interactive) == true ) { - nom::DialogMessageBox( this->test_case() + ":" + this->test_name(), - help_info.str() ); - } +<<<<<<< HEAD + // Done loading ... reset title to default + this->render_window().set_window_title( this->test_set() + "::" + + this->test_name() ); + +======= +>>>>>>> a35377e7 (Bail out of InteractiveGlyphCache test when not running w/ -i) + nom::DialogMessageBox( this->test_case() + ":" + this->test_name(), + help_info.str() ); EXPECT_EQ( NOM_EXIT_SUCCESS, this->on_run() ); // EXPECT_TRUE( this->compare() ); diff --git a/tests/src/gui/CMakeLists.txt b/tests/src/gui/CMakeLists.txt index 3def620c..f66b8477 100644 --- a/tests/src/gui/CMakeLists.txt +++ b/tests/src/gui/CMakeLists.txt @@ -3,15 +3,18 @@ set( NOM_BUILD_GUI_BASE_TEST ON ) set( NOM_BUILD_GUI_DATAGRID_TESTS ON ) +if( EXISTS "${CMAKE_CURRENT_LIST_DIR}/local_env.cmake" ) + include( "${CMAKE_CURRENT_LIST_DIR}/local_env.cmake" ) +endif() + if( NOM_BUILD_GUI_BASE_TEST ) add_executable( libRocketTest "libRocketTest.cpp" ) target_link_libraries( libRocketTest nomlib-visual-unit-test nomlib-gui ) - GTEST_ADD_TESTS ( ${TESTS_INSTALL_DIR}/libRocketTest - "" # args - "libRocketTest.cpp" ) + nom_add_visual_test( libRocketTest + ${TESTS_INSTALL_DIR}/libRocketTest ) # Search paths to use in finding resources path install ( @@ -33,9 +36,8 @@ if( NOM_BUILD_GUI_DATAGRID_TESTS ) target_link_libraries( libRocketDataGridTest nomlib-visual-unit-test nomlib-gui ) - GTEST_ADD_TESTS( ${TESTS_INSTALL_DIR}/libRocketDataGridTest - "" # args - "libRocketDataGridTest.cpp" ) + nom_add_visual_test( libRocketDataGridTest + ${TESTS_INSTALL_DIR}/libRocketDataGridTest ) # Search paths file to use in finding resources path install ( diff --git a/tests/src/gui/libRocketDataGridTest.cpp b/tests/src/gui/libRocketDataGridTest.cpp index b7423f11..b4707d9d 100644 --- a/tests/src/gui/libRocketDataGridTest.cpp +++ b/tests/src/gui/libRocketDataGridTest.cpp @@ -43,6 +43,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include #include #include @@ -383,14 +384,17 @@ class libRocketDataGridTest: public nom::VisualUnitTest Rocket::Core::Factory::RegisterDecoratorInstancer("sprite-sheet", decorator1 ); decorator1->RemoveReference(); - /// Put our event polling within the main event's loop - this->append_event_callback( [&] ( const Event ev ) { this->desktop.process_event( ev ); } ); + this->desktop.set_event_handler(this->evt_); // Register GUI updates onto our main loop (::on_run). - this->append_update_callback( [&] ( float delta ) { this->desktop.update(); } ); + this->append_update_callback( [&](real32 delta) { + this->desktop.update(); + }); // Register GUI rendering onto our main loop (::on_run). - this->append_render_callback( [&] ( const RenderWindow& win ) { this->desktop.draw(); } ); + this->append_render_callback( [&](const RenderWindow& win) { + this->desktop.draw(); + }); } /// \remarks This method is called before destruction, at the end of each diff --git a/tests/src/gui/libRocketTest.cpp b/tests/src/gui/libRocketTest.cpp index 75e1c0aa..488d286f 100644 --- a/tests/src/gui/libRocketTest.cpp +++ b/tests/src/gui/libRocketTest.cpp @@ -11,6 +11,7 @@ #include #include +#include #include #include @@ -302,27 +303,26 @@ class libRocketTest: public nom::VisualUnitTest // EXPERIMENTAL: Reload document, and its dependencies (i.e.: templates // and style sheets) during run-time. - EventCallback reload_docs( [&] ( const Event& evt ) - { - this->reload_docs( evt ); - } ); + auto reload_docs = ( [&](const Event& evt) { + this->reload_docs(evt); + }); - state.insert ( "reload_docs", - nom::KeyboardAction( SDL_KEYDOWN, - SDLK_r ), - reload_docs ); + state.insert("reload_docs", nom::KeyboardAction(SDLK_r), reload_docs); // Additional input bindings for VisualUnitTest's event loop. - this->input_mapper_.insert( "reload_docs", state, true ); + this->input_mapper_.insert("reload_docs", state, true); - /// Put our event polling within the main event's loop - this->append_event_callback( [&] ( const Event ev ) { this->desktop.process_event( ev ); } ); + this->desktop.set_event_handler(this->evt_); // Register GUI updates onto our main loop (::on_run). - this->append_update_callback( [&] ( float delta ) { this->desktop.update(); } ); + this->append_update_callback( [&] (float delta) { + this->desktop.update(); + }); // Register GUI rendering onto our main loop (::on_run). - this->append_render_callback( [&] ( const RenderWindow& win ) { this->desktop.draw(); } ); + this->append_render_callback( [&] (const RenderWindow& win) { + this->desktop.draw(); + }); } /// \remarks This method is called before destruction, at the end of each diff --git a/tests/src/math/MathTest.cpp b/tests/src/math/MathTest.cpp index 53e73ff1..a541cc4b 100644 --- a/tests/src/math/MathTest.cpp +++ b/tests/src/math/MathTest.cpp @@ -29,6 +29,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "gtest/gtest.h" #include +#include // scratch // #define NOM_DEBUG_MATH_TEST_OUTPUT_COLORS // #define NOM_DEBUG_MATH_TEST_OUTPUT_POINTS @@ -36,6 +37,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. namespace nom { +#define EXPECT_COLOR_EQ(expected, actual) \ + { EXPECT_EQ(expected.r, actual.r); \ + EXPECT_EQ(expected.g, actual.g); \ + EXPECT_EQ(expected.b, actual.b); \ + EXPECT_EQ(expected.a, actual.a); } + class MathTest: public ::testing::Test { public: @@ -426,6 +433,86 @@ TEST_F( MathTest, RectOperators ) EXPECT_EQ( false, lhs == rhs ); } +TEST_F(MathTest, IntRectAdditionOperators) +{ + nom::IntRect dims(2, 4, 2, 4); + + EXPECT_EQ( IntRect(4, 8, 4, 8), dims + dims); + EXPECT_EQ( IntRect(4, 6, 4, 6), 2 + dims); + EXPECT_EQ( IntRect(4, 6, 4, 6), dims + 2); + EXPECT_EQ( IntRect(4, 6, 4, 6), dims += 2); +} + +TEST_F(MathTest, IntRectSubtractionOperators) +{ + nom::IntRect dims(2, 4, 2, 4); + + EXPECT_EQ( IntRect(0, 0, 0, 0), dims - dims); + EXPECT_EQ( IntRect(0, -2, 0, -2), 2 - dims); + EXPECT_EQ( IntRect(0, 2, 0, 2), dims - 2); + EXPECT_EQ( IntRect(0, 2, 0, 2), dims -= 2); +} + +TEST_F(MathTest, IntRectMultiplyOperators) +{ + nom::IntRect dims(2, 4, 2, 4); + + EXPECT_EQ( IntRect(4, 16, 4, 16), dims * dims); + EXPECT_EQ( IntRect(4, 8, 4, 8), 2 * dims); + EXPECT_EQ( IntRect(4, 8, 4, 8), dims * 2); + EXPECT_EQ( IntRect(4, 16, 4, 16), dims *= dims); +} + +TEST_F(MathTest, IntRectDivisionOperators) +{ + nom::IntRect dims(2, 4, 2, 4); + + EXPECT_EQ( IntRect(1, 1, 1, 1), dims / dims); + EXPECT_EQ( IntRect(1, 0, 1, 0), 2 / dims); + EXPECT_EQ( IntRect(1, 2, 1, 2), dims / 2); + EXPECT_EQ( IntRect(1, 1, 1, 1), dims /= dims); +} + +TEST_F(MathTest, Point2iAdditionOperators) +{ + nom::Point2i dims(2, 4); + + EXPECT_EQ( Point2i(4, 8), dims + dims); + EXPECT_EQ( Point2i(4, 6), 2 + dims); + EXPECT_EQ( Point2i(4, 6), dims + 2); + EXPECT_EQ( Point2i(4, 6), dims += 2); +} + +TEST_F(MathTest, Point2iSubtractionOperators) +{ + nom::Point2i dims(2, 4); + + EXPECT_EQ( Point2i(0, 0), dims - dims); + EXPECT_EQ( Point2i(0, -2), 2 - dims); + EXPECT_EQ( Point2i(0, 2), dims - 2); + EXPECT_EQ( Point2i(0, 2), dims -= 2); +} + +TEST_F(MathTest, Point2iMultiplyOperators) +{ + nom::Point2i dims(2, 4); + + EXPECT_EQ( Point2i(4, 16), dims * dims); + EXPECT_EQ( Point2i(4, 8), 2 * dims); + EXPECT_EQ( Point2i(4, 8), dims * 2); + EXPECT_EQ( Point2i(4, 16), dims *= dims); +} + +TEST_F(MathTest, Point2iDivisionOperators) +{ + nom::Point2i dims(2, 4); + + EXPECT_EQ( Point2i(1, 1), dims / dims); + EXPECT_EQ( Point2i(1, 0), 2 / dims); + EXPECT_EQ( Point2i(1, 2), dims / 2); + EXPECT_EQ( Point2i(1, 1), dims /= dims); +} + TEST_F( MathTest, Size2Operators ) { nom::Size2i lhs( 25, 25 ); @@ -444,6 +531,117 @@ TEST_F( MathTest, Size2Operators ) EXPECT_EQ( false, lhs == rhs ); } +TEST_F(MathTest, Size2iAdditionOperators) +{ + nom::Size2i dims(2, 4); + + EXPECT_EQ( Size2i(4, 8), dims + dims); + EXPECT_EQ( Size2i(4, 6), 2 + dims); + EXPECT_EQ( Size2i(4, 6), dims + 2); + EXPECT_EQ( Size2i(4, 6), dims += 2); +} + +TEST_F(MathTest, Size2iSubtractionOperators) +{ + nom::Size2i dims(2, 4); + + EXPECT_EQ( Size2i(0, 0), dims - dims); + EXPECT_EQ( Size2i(0, -2), 2 - dims); + EXPECT_EQ( Size2i(0, 2), dims - 2); + EXPECT_EQ( Size2i(0, 2), dims -= 2); +} + +TEST_F(MathTest, Size2iMultiplyOperators) +{ + nom::Size2i dims(2, 4); + + EXPECT_EQ( Size2i(4, 16), dims * dims); + EXPECT_EQ( Size2i(4, 8), 2 * dims); + EXPECT_EQ( Size2i(4, 8), dims * 2); + EXPECT_EQ( Size2i(4, 16), dims *= dims); +} + +TEST_F(MathTest, Size2iDivisionOperators) +{ + nom::Size2i dims(2, 4); + + EXPECT_EQ( Size2i(1, 1), dims / dims); + EXPECT_EQ( Size2i(1, 0), 2 / dims); + EXPECT_EQ( Size2i(1, 2), dims / 2); + EXPECT_EQ( Size2i(1, 1), dims /= dims); +} + +// TODO(jeff): Finish test (make every color that we can from string input!) +TEST_F(MathTest, GenerateColorsFromStrings) +{ + nom::Color4i gen_color; + + gen_color = nom::make_color_from_string("ORANGE"); + EXPECT_COLOR_EQ(Color4i::Orange, gen_color); + + gen_color = nom::make_color_from_string("orange"); + EXPECT_COLOR_EQ(Color4i::Orange, gen_color); + + gen_color = nom::make_color_from_string("transparent"); + EXPECT_COLOR_EQ(Color4i::Transparent, gen_color); + + gen_color = nom::make_color_from_string("TRANSparent"); + EXPECT_COLOR_EQ(Color4i::Transparent, gen_color); +} + +TEST_F(MathTest, CreateColorFromInvalidHexString) +{ + nom::Color4i ret_color; + + // no # prefix, empty string + ret_color = nom::make_color_from_hex_string(""); + EXPECT_COLOR_EQ(nom::Color4i::Black, ret_color); + + // no # prefix, string length of two (2) + ret_color = nom::make_color_from_hex_string("AA"); + EXPECT_COLOR_EQ(nom::Color4i(170, 0, 0), ret_color); + + // no # prefix, string length of four (4) + ret_color = nom::make_color_from_hex_string("FFFF"); + EXPECT_COLOR_EQ(nom::Color4i(255, 255, 0), ret_color); + + // no # prefix + ret_color = nom::make_color_from_hex_string("0FFFFF"); + EXPECT_COLOR_EQ(nom::Color4i(15, 255, 255), ret_color); + + // no # prefix, one character over (7) + ret_color = nom::make_color_from_hex_string("0FFFFF0"); + EXPECT_COLOR_EQ(nom::Color4i(15, 255, 255), ret_color); +} + +TEST_F(MathTest, CreateColorFromValidHexString) +{ + nom::Color4i ret_color; + + ret_color = nom::make_color_from_hex_string("#ff00ff"); + EXPECT_COLOR_EQ(nom::Color4i(255,0,255), ret_color); + + ret_color = nom::make_color_from_hex_string("#FF00FF"); + EXPECT_COLOR_EQ(nom::Color4i(255,0,255), ret_color); + + ret_color = nom::make_color_from_hex_string("#ffffff"); + EXPECT_COLOR_EQ(nom::Color4i::White, ret_color); + + ret_color = nom::make_color_from_hex_string("#FFFFFF"); + EXPECT_COLOR_EQ(nom::Color4i::White, ret_color); + + // string length of seven (7) -- one over + ret_color = nom::make_color_from_hex_string("#FFFFFF0"); + EXPECT_COLOR_EQ(nom::Color4i::White, ret_color); + + // string length of seven (8) -- two over + ret_color = nom::make_color_from_hex_string("#0FFFAAF0"); + EXPECT_COLOR_EQ(nom::Color4i(15, 255, 170), ret_color); + + ret_color = nom::make_color_from_hex_string("#0FFFAA"); + EXPECT_COLOR_EQ(nom::Color4i(15, 255, 170), ret_color); +} + /// \remarks This test should always be ran before nom::init_rand is used. TEST_F(MathTest, UniformIntRand) { diff --git a/tests/src/ptree/ptree.cpp b/tests/src/ptree/ptree.cpp index 7610349a..c1f1fbda 100644 --- a/tests/src/ptree/ptree.cpp +++ b/tests/src/ptree/ptree.cpp @@ -331,9 +331,10 @@ TEST_F( PropertyTreeTest, ObjectValuesKeyNames ) TEST_F( PropertyTreeTest, ValueFindEraseInterface ) { Value obj; + const int KEY1_VALUE = -6; obj["key0"] = Value(); - obj["key1"] = -6; + obj["key1"] = KEY1_VALUE; obj["key2"] = 66u; obj["key3"] = 6.806; obj["key4"] = "Hello, world!"; @@ -343,9 +344,9 @@ TEST_F( PropertyTreeTest, ValueFindEraseInterface ) obj["key6"][0] = 11u; obj["key6"][1] = "Hello, world!"; - EXPECT_NE( Value::null, obj.find( "key2" ) ) << obj; - EXPECT_NE( Value::null, obj.erase( "key1" ) ); - EXPECT_EQ( Value::null, obj.erase( "key1" ) ); + EXPECT_NE( Value::null, obj.find("key2") ) << obj; + EXPECT_EQ( Value(KEY1_VALUE), obj.erase("key1") ); + EXPECT_EQ( Value::null, obj.erase("key1") ); } TEST_F( PropertyTreeTest, OperatorConstructDuplicateMemberKeys ) diff --git a/tests/src/serializers/CMakeLists.txt b/tests/src/serializers/CMakeLists.txt index 2ae8d00e..8be8485b 100644 --- a/tests/src/serializers/CMakeLists.txt +++ b/tests/src/serializers/CMakeLists.txt @@ -5,6 +5,10 @@ set( NOM_BUILD_SERIALIZERS_XML_TESTS ON ) set( NOM_BUILD_JSON_CONFIG_FILE_TESTS ON ) set( NOM_BUILD_SERIALIZERS_HTML_TESTS ON ) +if( EXISTS "${CMAKE_CURRENT_LIST_DIR}/local_env.cmake" ) + include( "${CMAKE_CURRENT_LIST_DIR}/local_env.cmake" ) +endif() + set( NOM_SERIALIZER_TESTS_DEPS ${GTEST_LIBRARY} nomlib-serializers # FIXME: Just needs init... nomlib-system diff --git a/tests/src/serializers/JsonCppSerializerTest.cpp b/tests/src/serializers/JsonCppSerializerTest.cpp index 1df76c67..ea84cecc 100644 --- a/tests/src/serializers/JsonCppSerializerTest.cpp +++ b/tests/src/serializers/JsonCppSerializerTest.cpp @@ -6,7 +6,6 @@ // nom::init functions #include "nomlib/system/init.hpp" #include "nomlib/system/dialog_messagebox.hpp" -#include "nomlib/system/SearchPath.hpp" #include #include // Property Tree (nom::Value) @@ -62,13 +61,24 @@ class JsonCppSerializerTest: public ::testing::Test nom::IValueDeserializer* fp_in; nom::IValueSerializer* fp_outs; - void expected_out( Value& in, const std::string& out ) + void expected_out( Value& in, const std::string& out, + const std::string& scope_name = "" ) { std::string ret; - ret = fp->serialize( in ); + ret = fp->serialize(in); + + EXPECT_EQ(out, ret) << in; + + // Debugging aid; these two files are serialized with "human friendly" + // JSON for maximum readability + if( scope_name != "" ) { + fp_outs->save(in, "actual_" + scope_name + ".json" ); - EXPECT_EQ( out, ret ) << in; + Value output; + output = fp_in->deserialize(out); + fp_outs->save(output, "expected_" + scope_name + ".json" ); + } } void not_expected_out( Value& in, const std::string& out ) @@ -79,16 +89,6 @@ class JsonCppSerializerTest: public ::testing::Test EXPECT_NE( out, ret ) << in; } - - std::string pp_out( const Value& in ) - { - return fp->serialize( in ); - } - - std::string pp_outs( const Value& in ) - { - return fp_outs->serialize( in ); - } }; // FIXME: Output should be just a newline, I think. @@ -250,33 +250,40 @@ TEST_F( JsonCppSerializerTest, SerializeArrayValues_MultipleArrayValuesInObjectV expected_out( o, "[{\"id\":5,\"level\":4,\"name\":\"Diablos\",\"owner\":false,\"ranks\":[4,8,5,10]},{\"id\":0,\"level\":1,\"name\":\"Geezard\",\"owner\":true,\"ranks\":[5,3,1,3]}]\n" ); } -TEST_F( JsonCppSerializerTest, SerializeArrayValues_ObjectInsideArrayValues ) +TEST_F(JsonCppSerializerTest, SerializeArrayValues_ObjectInsideArrayValues) { - Value o, obj; + const std::string TEST_NAME = "SerializeArrayValues_ObjectInsideArrayValues"; + const std::string EXPECTED_JSON_OUTPUT = + "[{\"cards\":[{\"id\":5,\"level\":4,\"name\":\"Diablos\",\"owner\":false,\"ranks\":[4,8,5,10]},{\"id\":0,\"level\":1,\"name\":\"Geezard\",\"owner\":true,\"ranks\":[5,3,1,3]}]},{\"cards\":[{\"id\":5,\"level\":4,\"name\":\"Diablos\",\"owner\":false,\"ranks\":[4,8,5,10]},{\"id\":0,\"level\":1,\"name\":\"Geezard\",\"owner\":true,\"ranks\":[5,3,1,3]}]}]\n"; + + Value cards_array; + Value diablos_obj, geezard_obj; - obj[0]["id"] = 5u; - obj[0]["name"] = "Diablos"; - obj[0]["level"] = 4u; - obj[0]["ranks"][0] = 4u; - obj[0]["ranks"][1] = 8u; - obj[0]["ranks"][2] = 5u; - obj[0]["ranks"][3] = 10u; - obj[0]["owner"] = false; + diablos_obj["id"] = 5u; + diablos_obj["name"] = "Diablos"; + diablos_obj["level"] = 4u; + diablos_obj["ranks"][0] = 4u; + diablos_obj["ranks"][1] = 8u; + diablos_obj["ranks"][2] = 5u; + diablos_obj["ranks"][3] = 10u; + diablos_obj["owner"] = false; - o[0]["cards"] = obj; + geezard_obj["id"] = 0u; + geezard_obj["name"] = "Geezard"; + geezard_obj["level"] = 1u; + geezard_obj["ranks"][0] = 5u; + geezard_obj["ranks"][1] = 3u; + geezard_obj["ranks"][2] = 1u; + geezard_obj["ranks"][3] = 3u; + geezard_obj["owner"] = true; - obj[1]["id"] = 0u; - obj[1]["name"] = "Geezard"; - obj[1]["level"] = 1u; - obj[1]["ranks"][0] = 5u; - obj[1]["ranks"][1] = 3u; - obj[1]["ranks"][2] = 1u; - obj[1]["ranks"][3] = 3u; - obj[1]["owner"] = true; + cards_array[0]["cards"][0] = diablos_obj; + cards_array[0]["cards"][1] = geezard_obj; - o[1]["cards"] = obj; + cards_array[1]["cards"][0] = diablos_obj; + cards_array[1]["cards"][1] = geezard_obj; - expected_out( o, "[{\"cards\":[{\"id\":5,\"level\":4,\"name\":\"Diablos\",\"owner\":false,\"ranks\":[4,8,5,10]},{\"id\":0,\"level\":1,\"name\":\"Geezard\",\"owner\":true,\"ranks\":[5,3,1,3]}]},{\"cards\":[{\"id\":5,\"level\":4,\"name\":\"Diablos\",\"owner\":false,\"ranks\":[4,8,5,10]},{\"id\":0,\"level\":1,\"name\":\"Geezard\",\"owner\":true,\"ranks\":[5,3,1,3]}]}]\n" ); + expected_out(cards_array, EXPECTED_JSON_OUTPUT, TEST_NAME); } TEST_F( JsonCppSerializerTest, SerializeArrayValues_ComplexArrayValues ) @@ -477,33 +484,40 @@ TEST_F( JsonCppSerializerTest, SerializeObjectValues_MultipleArrayValuesInObject expected_out( o, "{\"obj1\":{\"id\":5,\"level\":4,\"name\":\"Diablos\",\"owner\":false,\"ranks\":[4,8,5,10]},\"obj2\":{\"id\":0,\"level\":1,\"name\":\"Geezard\",\"owner\":true,\"ranks\":[5,3,1,3]}}\n" ); } -TEST_F( JsonCppSerializerTest, SerializeObjectValues_ObjectInsideArrayValues ) +TEST_F(JsonCppSerializerTest, SerializeObjectValues_ObjectInsideArrayValues) { - Value o, obj; + const std::string TEST_NAME = "SerializeObjectValues_ObjectInsideArrayValues"; + const std::string EXPECTED_JSON_OUTPUT = + "{\"root\":{\"cards\":[{\"obj1\":{\"id\":5,\"level\":4,\"name\":\"Diablos\",\"owner\":false,\"ranks\":[4,8,5,10]},\"obj2\":{\"id\":0,\"level\":1,\"name\":\"Geezard\",\"owner\":true,\"ranks\":[5,3,1,3]}},{\"obj1\":{\"id\":5,\"level\":4,\"name\":\"Diablos\",\"owner\":false,\"ranks\":[4,8,5,10]},\"obj2\":{\"id\":0,\"level\":1,\"name\":\"Geezard\",\"owner\":true,\"ranks\":[5,3,1,3]}}]}}\n"; + + Value cards_object; + Value diablos_obj, geezard_obj; - obj["obj1"]["id"] = 5u; - obj["obj1"]["name"] = "Diablos"; - obj["obj1"]["level"] = 4u; - obj["obj1"]["ranks"][0] = 4u; - obj["obj1"]["ranks"][1] = 8u; - obj["obj1"]["ranks"][2] = 5u; - obj["obj1"]["ranks"][3] = 10u; - obj["obj1"]["owner"] = false; + diablos_obj["id"] = 5u; + diablos_obj["name"] = "Diablos"; + diablos_obj["level"] = 4u; + diablos_obj["ranks"][0] = 4u; + diablos_obj["ranks"][1] = 8u; + diablos_obj["ranks"][2] = 5u; + diablos_obj["ranks"][3] = 10u; + diablos_obj["owner"] = false; - o["root"]["cards"].push_back( obj ); + geezard_obj["id"] = 0u; + geezard_obj["name"] = "Geezard"; + geezard_obj["level"] = 1u; + geezard_obj["ranks"][0] = 5u; + geezard_obj["ranks"][1] = 3u; + geezard_obj["ranks"][2] = 1u; + geezard_obj["ranks"][3] = 3u; + geezard_obj["owner"] = true; - obj["obj2"]["id"] = 0u; - obj["obj2"]["name"] = "Geezard"; - obj["obj2"]["level"] = 1u; - obj["obj2"]["ranks"][0] = 5u; - obj["obj2"]["ranks"][1] = 3u; - obj["obj2"]["ranks"][2] = 1u; - obj["obj2"]["ranks"][3] = 3u; - obj["obj2"]["owner"] = true; + cards_object["root"]["cards"][0]["obj1"] = diablos_obj; + cards_object["root"]["cards"][0]["obj2"] = geezard_obj; - o["root"]["cards"].push_back( obj ); + cards_object["root"]["cards"][1]["obj1"] = diablos_obj; + cards_object["root"]["cards"][1]["obj2"] = geezard_obj; - expected_out( o, "{\"root\":{\"cards\":[{\"obj1\":{\"id\":5,\"level\":4,\"name\":\"Diablos\",\"owner\":false,\"ranks\":[4,8,5,10]},\"obj2\":{\"id\":0,\"level\":1,\"name\":\"Geezard\",\"owner\":true,\"ranks\":[5,3,1,3]}},{\"obj1\":{\"id\":5,\"level\":4,\"name\":\"Diablos\",\"owner\":false,\"ranks\":[4,8,5,10]},\"obj2\":{\"id\":0,\"level\":1,\"name\":\"Geezard\",\"owner\":true,\"ranks\":[5,3,1,3]}}]}}\n" ); + expected_out(cards_object, EXPECTED_JSON_OUTPUT, TEST_NAME); } TEST_F( JsonCppSerializerTest, SerializeObjectValues_ComplexArrayValues ) @@ -576,7 +590,7 @@ TEST_F( JsonCppSerializerTest, FileIO ) o = sanity2_out(); - ASSERT_TRUE( fp->save( o, "output_human-readable.json" ) ) << o; + ASSERT_TRUE( fp_outs->save( o, "output_human-readable.json" ) ) << o; // Just to be safe! o.clear(); @@ -602,20 +616,20 @@ TEST_F( JsonCppSerializerTest, FileIO ) /// array objects, and must be implemented in order for the /// RESOURCE_JSON_INVENTORY JSON tests to pass. /// to. -TEST_F( JsonCppSerializerTest, ExtraIO ) +TEST_F(JsonCppSerializerTest, DISABLED_ExtraIO) { Value in; Value os; - // ASSERT_TRUE( fp_in->load( resources.path() + RESOURCE_JSON_INVENTORY, os ) ); - // EXPECT_EQ( 2, os.size() ); + ASSERT_TRUE( fp_in->load( resources.path() + RESOURCE_JSON_INVENTORY, os ) ); + EXPECT_EQ( 2, os.size() ); - // ASSERT_TRUE( fp_in->load( resources.path() + RESOURCE_JSON_INVENTORY, os ) ); - // EXPECT_EQ( 2, os.size() ); + ASSERT_TRUE( fp_in->load( resources.path() + RESOURCE_JSON_INVENTORY, os ) ); + EXPECT_EQ( 2, os.size() ); // FIXME: This will load for us only if we use Json::Reader directly to read. - // ASSERT_TRUE( fp_in->load( resources.path() + RESOURCE_JSON_GAMEDATA, os ) ); - // EXPECT_EQ( 4, os.size() ); + ASSERT_TRUE( fp_in->load( resources.path() + RESOURCE_JSON_GAMEDATA, os ) ); + EXPECT_EQ( 4, os.size() ); } } // namespace nom diff --git a/tests/src/serializers/RapidXmlSerializerTest.cpp b/tests/src/serializers/RapidXmlSerializerTest.cpp index de72777b..8289f5fd 100644 --- a/tests/src/serializers/RapidXmlSerializerTest.cpp +++ b/tests/src/serializers/RapidXmlSerializerTest.cpp @@ -1,3 +1,31 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014, 2015, 2016 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ #include #include @@ -6,7 +34,6 @@ // nom::init functions #include "nomlib/system/init.hpp" #include "nomlib/system/dialog_messagebox.hpp" -#include "nomlib/system/SearchPath.hpp" #include #include // Property Tree (nom::Value) diff --git a/tests/src/system/CMakeLists.txt b/tests/src/system/CMakeLists.txt index 6bde852f..446c345e 100644 --- a/tests/src/system/CMakeLists.txt +++ b/tests/src/system/CMakeLists.txt @@ -3,6 +3,11 @@ set( NOM_BUILD_FILE_TESTS ON ) set( NOM_BUILD_FONT_CACHE_TESTS ON ) set( NOM_BUILD_COLOR_DB_TESTS ON ) +set( NOM_BUILD_TIMER_TESTS ON ) + +if( EXISTS "${CMAKE_CURRENT_LIST_DIR}/local_env.cmake" ) + include( "${CMAKE_CURRENT_LIST_DIR}/local_env.cmake" ) +endif() if( NOM_BUILD_FONT_CACHE_TESTS ) @@ -61,3 +66,21 @@ if( NOM_BUILD_FILE_TESTS ) "FileInterfaceTest.cpp" ) endif( NOM_BUILD_FILE_TESTS ) + +if( NOM_BUILD_TIMER_TESTS ) + + add_executable( TimerTest "TimerTest.cpp" ) + + set( TIMER_DEPS ${GTEST_LIBRARY} nomlib-system ) + + if( PLATFORM_WINDOWS ) + list( APPEND TIMER_DEPS ${SDL2MAIN_LIBRARY} ) + endif( PLATFORM_WINDOWS ) + + target_link_libraries( TimerTest ${TIMER_DEPS} ) + + GTEST_ADD_TESTS( ${TESTS_INSTALL_DIR}/TimerTest + "" # args + "TimerTest.cpp" ) + +endif( NOM_BUILD_TIMER_TESTS ) diff --git a/tests/src/system/FontCacheTest.cpp b/tests/src/system/FontCacheTest.cpp index bae7cffa..91ebb95f 100644 --- a/tests/src/system/FontCacheTest.cpp +++ b/tests/src/system/FontCacheTest.cpp @@ -5,8 +5,9 @@ #include #include +#include -namespace nom { +using namespace nom; class FontCacheTest: public nom::UnitTest { @@ -50,6 +51,8 @@ class FontCacheTest: public nom::UnitTest } protected: + const Size2i WINDOW_RESOLUTION = Size2i(0, 0); + nom::SearchPath res_bitmap; nom::SearchPath res_truetype; nom::SearchPath res_bm; @@ -128,7 +131,7 @@ TEST_F( FontCacheTest, FontCacheAPI ) nom::init_third_party(0); // Necessary for loading font resources - ASSERT_TRUE( window.create( this->test_case(), 0, 0, SDL_WINDOW_HIDDEN ) == true ) + ASSERT_TRUE( window.create(this->test_case(), WINDOW_RESOLUTION, SDL_WINDOW_HIDDEN) == true ) << "Could not create nom::RenderWindow object for loading font resources from"; // cache.set_resource_handler( [&] (const ResourceFile& res, IFont* font) { @@ -194,8 +197,6 @@ TEST_F( FontCacheTest, FontCacheAPI ) << "bfont4 should **not** be the same as bfont5"; } -} // namespace nom - int main( int argc, char** argv ) { ::testing::InitGoogleTest( &argc, argv ); diff --git a/tests/src/system/TimerTest.cpp b/tests/src/system/TimerTest.cpp new file mode 100644 index 00000000..a7b2d32e --- /dev/null +++ b/tests/src/system/TimerTest.cpp @@ -0,0 +1,157 @@ +/****************************************************************************** + + nomlib - C++11 cross-platform game engine + +Copyright (c) 2013, 2014 Jeffrey Carpenter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ +#include + +#include + +#include +#include +#include + +using namespace nom; + +class TimerTest: public ::testing::Test +{ + public: + /// \remarks This method is called at the start of each unit test. + TimerTest() + { + // NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE, nom::NOM_LOG_PRIORITY_VERBOSE ); + + // Disable verbose, debug output + nom::SDL2Logger::set_logging_priority( NOM_LOG_CATEGORY_TEST, + NOM_LOG_PRIORITY_WARN ); + } + + /// \remarks This method is called at the end of each unit test. + virtual ~TimerTest() + { + // NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE, nom::NOM_LOG_PRIORITY_VERBOSE ); + } + + /// \remarks This method is called after construction, at the start of each + /// unit test. + virtual void SetUp() + { + // NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE, nom::NOM_LOG_PRIORITY_VERBOSE ); + } + + /// \remarks This method is called before destruction, at the end of each + /// unit test. + virtual void TearDown() + { + // NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE, nom::NOM_LOG_PRIORITY_VERBOSE ); + } + + /// \remarks This method is called at the start of each test case. + static void SetUpTestCase() + { + // NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE, nom::NOM_LOG_PRIORITY_VERBOSE ); + } + + /// \remarks This method is called at the end of each test case. + static void TearDownTestCase() + { + // NOM_LOG_TRACE_PRIO( NOM_LOG_CATEGORY_TRACE, nom::NOM_LOG_PRIORITY_VERBOSE ); + } + + protected: + HighResolutionTimer duration_timer; + HighResolutionTimer timer; + uint64 t1 = 0; + uint64 t2 = 0; +}; + +TEST_F(TimerTest, ConversionToMilliseconds) +{ + const uint32 DURATION = 2000; + + timer.start(); + duration_timer.start(); + while( HighResolutionTimer::to_milliseconds(t1) < DURATION ) { + t1 = duration_timer.ticks(); + } + + uint64 ret = HighResolutionTimer::to_milliseconds(t1); + EXPECT_GE(ret, DURATION); +} + +TEST_F(TimerTest, ConversionToSeconds) +{ + const uint32 DURATION = 2; + + timer.start(); + duration_timer.start(); + while( HighResolutionTimer::to_seconds(t1) < DURATION ) { + t1 = duration_timer.ticks(); + } + + uint64 ret = HighResolutionTimer::to_seconds(t1); + EXPECT_GE(ret, DURATION); +} + +TEST_F(TimerTest, ElapsedSeconds) +{ + const uint32 DURATION_T1 = 2000; + const uint32 DURATION_T2 = DURATION_T1 * 2; + HighResolutionTimer t2_timer; + + timer.start(); + duration_timer.start(); + t2_timer.start(); + while( HighResolutionTimer::to_milliseconds(t1) < DURATION_T1 ) { + t1 = duration_timer.ticks(); + } + + while( HighResolutionTimer::to_milliseconds(t2) < (DURATION_T2) ) { + t2 = t2_timer.ticks(); + } + + uint64 ret = HighResolutionTimer::elapsed_ticks(t1, t2); + EXPECT_LE(ret, t1); + EXPECT_GE(t1, ret); + EXPECT_LE(t1, t2); + EXPECT_GE(t2, t1); +} + +int main(int argc, char** argv) +{ + ::testing::InitGoogleTest(&argc, argv); + + // Set the current working directory path to the path leading to this + // executable file; used for unit tests that require file-system I/O. + if( nom::init(argc, argv) == false ) + { + NOM_LOG_CRIT(NOM_LOG_CATEGORY_APPLICATION, "Could not initialize nomlib."); + return NOM_EXIT_FAILURE; + } + atexit(nom::quit); + + return RUN_ALL_TESTS(); +} diff --git a/third-party/.gitignore b/third-party/.gitignore index 63784e39..618223f9 100644 --- a/third-party/.gitignore +++ b/third-party/.gitignore @@ -3,4 +3,7 @@ windows/* osx/* ios/* linux/* +!linux/*.sh +!linux/freetype common/* +linux/* diff --git a/third-party/README.md b/third-party/README.md index ca8f5617..f1d25375 100644 --- a/third-party/README.md +++ b/third-party/README.md @@ -20,8 +20,12 @@ Both the 64-bit and 32-bit official development binaries versions of: * [JsonCpp v0.6.0-rc2](https://sf.net/p/jsoncpp) built with its distributed amalgamate.py script. * [TCLAP headers on branch master, commit 12cee3](https://sourceforge.net/p/tclap/code/ci/master/tree/) +* [OpenAL-Soft v1.17.0]() + * ALSOFT_LOGLEVEL=0 + * ALSOFT_LOGLEVEL=3 + * Packaged only under Mac OS X - * [SDL2.framework v2.0.3] with a self-signed certificate for running app bundles built with this framework within Instruments. Using a self-signed certificate also resolves a XCode v6 crash for me that results when trying to run a process from the IDE, complaining about an invalid code signature for SDL2.framework. + * [SDL2.framework v2.0.3-d] with a self-signed certificate for running app bundles built with this framework within Instruments. Using a self-signed certificate also resolves a XCode v6 crash for me that results when trying to run a process from the IDE, complaining about an invalid code signature for SDL2.framework. * [libsndfile v1.0.24](http://www.mega-nerd.com/libsndfile/) * Distributed framework is copied from the [SFML2 master branch](https://github.com/LaurentGomila/SFML/tree/master/) with me moving the distribution's **sndfile.h** file to it under a new Headers directory. @@ -31,7 +35,7 @@ Both the 64-bit and 32-bit official development binaries versions of: * [libjpeg 8d](https://github.com/Homebrew/homebrew/commits/master/Library/Formula/jpeg.rb) from Homebrew's repository with the --universal build flag. * [SDL_ttf.framework v2.0.12](http://libsdl.org/projects/SDL_ttf) built with two patches: [TTF_GetFontKerningSize fix](https://bugzilla.libsdl.org/show_bug.cgi?id=2572) and related [TTF_GetFontKerningSize err code fix](https://bugzilla.libsdl.org/show_bug.cgi?id=2779). - + * [libRocket v1.3.0](http://librocket.com) compiled with the FreeType v2.3.5 libraries from [homebrew](http://brew.sh). Refer to my [libRocket fork: dev branch](https://github.com/i8degrees/libRocket/tree/dev) to see any changes from the upstream [libRocket: master branch](https://github.com/libRocket/libRocket/tree/master) library. ``` # dynamic libs @@ -46,6 +50,15 @@ Both the 64-bit and 32-bit official development binaries versions of: ``` cmake -DBUILD_SHARED_LIBS=on .. msbuild /t:build gtest.vcxproj + msbuild /t:build gtest_main.vcxproj + ``` + + ```console + # Release library + mkdir build-release && cd build-release + cmake -DBUILD_SHARED_LIBS=on -DCMAKE_BUILD_TYPE=Release .. + msbuild /t:build gtest.vcxproj /p:Configuration=Release + msbuild /t:build gtest_main.vcxproj /p:Configuration=Release ``` * [libsndfile v1.0.25](http://www.mega-nerd.com/libsndfile/) diff --git a/third-party/linux/gtest.sh b/third-party/linux/gtest.sh new file mode 100644 index 00000000..aa418a1b --- /dev/null +++ b/third-party/linux/gtest.sh @@ -0,0 +1,41 @@ +#!/bin/bash +# +# + +[ -n "$DEBUG_TRACE" ] && set -o xtrace + +# old code +if [ -n "$INIT_BUILD" ]; then + cd /home/jeff/tmp/nomlib/ + git clone "https://github.com/google/googletest.git" + cd googletest + git checkout v1.8.x + mkdir build && cd build + cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -DCMAKE_C_COMPILER=clang \ + -DCMAKE_CXX_COMPILER=clang++ .. + # copy header files and library files to dist +fi + +# new code +SRC_DIR=/mnt/fs1/Projects/nomlib.git.bck/vendor/gtest.git +VENDOR_DEST_DIR=/mnt/fs1/Projects/nomlib.git.bck/third-party/linux/gtest +test -r "$VENDOR_DEST_DIR" || echo -e "CRITICAL: The path at $VENDOR_DEST_DIR does not exist!\n"; exit 1 + +#git clone "https://github.com/google/googletest.git" +#cd googletest +#git checkout v1.8.x +pushd "$SRC_DIR" || exit 2 +mkdir build +if [ -e "./build" ]; then + # TODO(JEFF): Fresh build upon X flag +else + cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \ + -DCMAKE_INSTALL_PREFIX=$VENDOR_DEST_DIR .. + # clang is NOT supported + #-DCMAKE_C_COMPILER=clang \ + #-DCMAKE_CXX_COMPILER=clang++ +fi + +# TODO(JEFF): Append cxx_base_flags from here via cmake build cmd above +# /mnt/fs1/Projects/nomlib.git.bck/third-party/linux/gtest/googletest/cmake/internal_utils.cmake:105 +set(cxx_base_flags "-Wall -Wshadow") diff --git a/third-party/linux/libRocket.sh b/third-party/linux/libRocket.sh new file mode 100644 index 00000000..88785a4c --- /dev/null +++ b/third-party/linux/libRocket.sh @@ -0,0 +1,31 @@ +#!/bin/bash +# +# + +if [ -n "$INIT_BUILD" ]; then + cd /home/jeff/tmp/nomlib/ + git clone "https://github.com/libRocket/libRocket.git" librocket.git + cd librocket.git + git checkout release-1.2.0 +fi + +SDL_LIBS="/mnt/fs1/Projects/nomlib.git.bck/third-party/linux/sdl2/lib/libSDL2.a" +CPPFLAGS="/mnt/fs1/Projects/nomlib.git.bck/third-party/linux/sdl2/include/SDL2" + +SDL2_IMAGE_DEST=/mnt/fs1/Projects/nomlib.git.bck/third-party/linux/sdl2_image +export SDL2_IMAGE_DEST + +if [ -n "$DEBUG" ]; then + export SDL_LIBS + export CPPFLAGS + export SDL2_IMAGE_DEST +fi + +# assume that we are in the right directory, i.e.: /home/jeff/tmp/nomlib/sdl2_image.git/ +# build dir... +cd /home/jeff/tmp/nomlib/sdl2_image.git +make clean +./autogen.sh +./configure --prefix=$SDL2_IMAGE_DEST +make install + diff --git a/third-party/linux/sdl2.sh b/third-party/linux/sdl2.sh new file mode 100644 index 00000000..24a8205c --- /dev/null +++ b/third-party/linux/sdl2.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +SDL2_DEST=/mnt/fs1/Projects/nomlib.git/third-party/linux/sdl2 +export SDL2_DEST + +if [ -n "$INIT_BUILD" ]; then + git clone "https://github.com/libsdl-org/SDL.git" sdl2.git + cd sdl2.git + #git checkout release-2.0.6 + git checkout release-2.24.0 + mkdir build && cd build && rm -rf * +fi + +#-DCMAKE_POLICY_VERSION_MINIMUM=3.5 +cmake \ + -DCMAKE_INSTALL_PREFIX=$SDL2_DEST + -DVIDEO_KMSDRM=off -DCMAKE_WAYLAND=on \ + -DWAYLAND_SHARED=off \ +.. +make -j8 +make -j1 install + diff --git a/third-party/linux/sdl2_image.sh b/third-party/linux/sdl2_image.sh new file mode 100644 index 00000000..812c85bc --- /dev/null +++ b/third-party/linux/sdl2_image.sh @@ -0,0 +1,33 @@ +#!/bin/bash +# +# + +if [ -n "$INIT_BUILD" ]; then + cd /home/jeff/tmp/nomlib/ + git clone "https://github.com/libsdl-org/SDL_image.git" sdl2_image.git + cd sdl2_image.git + git checkout release-2.0.0 +fi + + +SDL_LIBS="/mnt/fs1/Projects/nomlib.git.fixed/third-party/linux/sdl2/lib/libSDL2.a" +CPPFLAGS="/mnt/fs1/Projects/nomlib.git.fixed/third-party/linux/sdl2/include/SDL2" + +SDL2_IMAGE_DEST=/mnt/fs1/Projects/nomlib.git.fixed/third-party/linux/sdl2_image +export SDL2_IMAGE_DEST +SDL_PREFIX=/mnt/fs1/Projects/nomlib.git.fixed/third-party/linux/sdl2 + +if [ -n "$DEBUG" ]; then + export SDL_LIBS + export CPPFLAGS + export SDL2_IMAGE_DEST +fi + +# assume that we are in the right directory, i.e.: /home/jeff/tmp/nomlib/sdl2_image.git/ +# build dir... +cd /home/jeff/tmp/nomlib/sdl2_image.git +make clean +./autogen.sh +./configure --prefix=$SDL2_IMAGE_DEST --with-sdl-prefix="$SDL_PREFIX" +make install + diff --git a/third-party/linux/sdl2_ttf.sh b/third-party/linux/sdl2_ttf.sh new file mode 100644 index 00000000..4c05f7c8 --- /dev/null +++ b/third-party/linux/sdl2_ttf.sh @@ -0,0 +1,35 @@ +#!/bin/bash +# +# + +[ -n "$DEBUG_TRACE" ] && set -o xtrace + +SDL2_TTF_DEST=/mnt/fs1/Projects/nomlib.git/third-party/linux/sdl2_ttf +export SDL2_TTF_DEST +# freetype2/bin/freetype-config +FREETYPE_PREFIX=/mnt/fs1/Projects/nomlib.git/third-party/linux/freetype2 +export FREETYPE_PREFIX +SDL_PREFIX=/mnt/fs1/Projects/nomlib.git/third-party/linux/sdl2 +export SDL_PREFIX + +SRC_PREFIX_DIR=/mnt/fs1/Projects/nomlib.git/vendor +if [ -e "$SRC_PREFIX_DIR" ]; then + git clone "https://github.com/libsdl-org/SDL_ttf.git" sdl2_ttf.git || exit 2 + pushd "sdl2_ttf.git" + git checkout release-2.0.12 + make clean + ./autogen.sh + autoupdate + # !! We must use our freetype-config workaround script here as the original + # script no longer exists; it has been replaced with a pkg-config equivalent. + ./configure --prefix=$SDL2_TTF_DEST --with-freetype-prefix=$FREETYPE_PREFIX \ + --with-sdl-prefix=$SDL_PREFIX + make + make -j1 install + popd +else + echo "CRITICAL: The path at $SRC_PREFIX_DIR does not exist!" + echo + exit 255 +fi + diff --git a/vendor/SDL.git b/vendor/SDL.git new file mode 160000 index 00000000..fe6b8f1c --- /dev/null +++ b/vendor/SDL.git @@ -0,0 +1 @@ +Subproject commit fe6b8f1c314e94f58db8119e40ad4e1f849fb667 diff --git a/vendor/SDL2_image.git b/vendor/SDL2_image.git new file mode 160000 index 00000000..6290d131 --- /dev/null +++ b/vendor/SDL2_image.git @@ -0,0 +1 @@ +Subproject commit 6290d1310bb11efec072dcbde0b2f2c2ca2969a6 diff --git a/vendor/SDL2_ttf.git b/vendor/SDL2_ttf.git new file mode 160000 index 00000000..2b891b4a --- /dev/null +++ b/vendor/SDL2_ttf.git @@ -0,0 +1 @@ +Subproject commit 2b891b4ae91f3e24494ff8c06bc6f05acda3fd0a diff --git a/vendor/gtest.git b/vendor/gtest.git new file mode 160000 index 00000000..dea0216d --- /dev/null +++ b/vendor/gtest.git @@ -0,0 +1 @@ +Subproject commit dea0216d0c6bc5e63cf5f6c8651cd268668032ec diff --git a/vendor/libsndfile.git b/vendor/libsndfile.git new file mode 160000 index 00000000..68f6c16f --- /dev/null +++ b/vendor/libsndfile.git @@ -0,0 +1 @@ +Subproject commit 68f6c16fe1407eff4cdde158566694c3ed666c2f diff --git a/vendor/openal-soft.git b/vendor/openal-soft.git new file mode 160000 index 00000000..9b6a226d --- /dev/null +++ b/vendor/openal-soft.git @@ -0,0 +1 @@ +Subproject commit 9b6a226da55a987cb883f425eeb568776ea12c8d diff --git a/version.cmake b/version.cmake new file mode 100644 index 00000000..b19becbb --- /dev/null +++ b/version.cmake @@ -0,0 +1,14 @@ +# version.cmake:jeff +# +# Engine version used to publish in build +# +# See also +# 1. Top-level CMakeLists.txt +# + +# IMPORTANT(JEFF): This is the version info given to nomlib's project call +# and should always be updated every time we publish a new engine version by +# creating a release branch or tag the branch with the version. +set( VERSION_MAJOR 0 ) +set( VERSION_MINOR 13 ) +set( VERSION_PATCH 1 )