diff --git a/.gitignore b/.gitignore index d1ef973af..d513341ca 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -build* +build*/ pkg *.kdev4 *~ diff --git a/CMakeLists.txt b/CMakeLists.txt index b400342aa..1018727c0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,6 +37,7 @@ cmake_dependent_option(TARGET_GLES2 "Build for OpenGL ES 2" ON "TARGET_GLES" OFF cmake_dependent_option(TARGET_DESKTOP_GLES "Build for OpenGL ES on desktop" OFF "TARGET_GLES" OFF) # Parts of the library +option(WITH_AUDIO "Build Audio library" ON) option(WITH_DEBUGTOOLS "Build DebugTools library" ON) cmake_dependent_option(WITH_MESHTOOLS "Build MeshTools library" ON "NOT WITH_DEBUGTOOLS" ON) cmake_dependent_option(WITH_PRIMITIVES "Builf Primitives library" ON "NOT WITH_DEBUGTOOLS" ON) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ec263025d..673ba7918 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -53,7 +53,8 @@ Code contribution Contact ------- -- Website - http://mosra.cz/blog/ -- GitHub - https://github.com/mosra/magnum -- E-mail - mosra@centrum.cz -- Jabber - mosra@jabbim.cz +* Website - http://mosra.cz/blog/magnum.php +* GitHub - https://github.com/mosra/magnum +* Twitter - https://twitter.com/czmosra +* E-mail - mosra@centrum.cz +* Jabber - mosra@jabbim.cz diff --git a/Doxyfile b/Doxyfile index ec9dbea6a..9bb7567bd 100644 --- a/Doxyfile +++ b/Doxyfile @@ -142,7 +142,8 @@ STRIP_FROM_PATH = ../ # are normally passed to the compiler using the -I flag. STRIP_FROM_INC_PATH = ../magnum/src \ - ../magnum-plugins/src + ../magnum-plugins/src \ + ../magnum-integration/src # If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter # (but less readable) file names. This can be useful if your file system @@ -224,7 +225,9 @@ ALIASES = \ "requires_gles20=@xrefitem requires-gles20 \"Requires OpenGL ES 2.0\" \"Functionality requiring OpenGL ES 2.0 (not available in ES 3.0 and desktop OpenGL)\"" \ "requires_es_extension=@xrefitem requires-es-extension \"Requires OpenGL ES extension\" \"Functionality requiring specific OpenGL ES extension\"" \ "es_extension{2}=\1_\2" \ - "es_extension2{3}=\1_\2" + "es_extension2{3}=\1_\2" \ + "fn_al{1}=`al\1()`" \ + "def_al{1}=`AL_\1`" # 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 @@ -686,6 +689,8 @@ INPUT = src/ \ doc/ \ ../magnum-plugins/src/ \ ../magnum-plugins/doc/ \ + ../magnum-integration/src/ \ + ../magnum-integration/doc/ \ ../magnum-examples/doc/ # This tag can be used to specify the character encoding of the source files @@ -770,7 +775,8 @@ EXAMPLE_RECURSIVE = NO # directories that contain image that are included in the documentation (see # the \image command). -IMAGE_PATH = ../magnum-examples/src/ +IMAGE_PATH = doc/ \ + ../magnum-examples/src/ # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program diff --git a/README.md b/README.md index 292534235..bd95badec 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ DESIGN GOALS SUPPORTED PLATFORMS =================== -* **OpenGL** 2.1 through 4.3, core profile functionality and modern +* **OpenGL** 2.1 through 4.4, core profile functionality and modern extensions * **OpenGL ES** 2.0, 3.0 and extensions to match desktop OpenGL functionality * **Linux** and embedded Linux (natively using GLX/EGL and Xlib or through @@ -72,13 +72,14 @@ more comprehensive guide for building, packaging and crosscompiling. Minimal dependencies -------------------- - * C++ compiler with good C++11 support. Currently there are two compilers - which are tested to support everything needed: **GCC** >= 4.6 and **Clang** - >= 3.1. - * **CMake** >= 2.8.8 (needed for `OBJECT` library target) - * **GLEW** - OpenGL extension wrangler (only if targeting desktop OpenGL) - * **Corrade** - Plugin management and utility library. You can get it at - https://github.com/mosra/corrade. +* C++ compiler with good C++11 support. Currently there are two compilers + which are tested to support everything needed: **GCC** >= 4.6 and **Clang** + >= 3.1. On Windows you can use **MinGW**, Visual Studio compiler still + lacks some needed features. +* **CMake** >= 2.8.8 +* **GLEW** - OpenGL extension wrangler (only if targeting desktop OpenGL) +* **Corrade** - Plugin management and utility library. You can get it at + https://github.com/mosra/corrade. Compilation, installation ------------------------- @@ -121,26 +122,40 @@ in root directory (i.e. where `Doxyfile` is). Resulting HTML documentation will be in `build/doc/` directory. You might need to create `build/` directory if it doesn't exist yet. -PLUGINS AND EXAMPLES -==================== +GETTING STARTED +=============== -Various importer plugins for image and 3D model formats are maintained in +The Doxygen documentation has a thorough [guide how to start using Magnum](http://mosra.cz/blog/magnum-doc/getting-started.html) +in your project. + +RELATED PROJECTS +================ + +The engine itself is kept as small as possible with only little dependencies. +Additional functionality, often depending on external libraries, is provided in +separate repositories. Integration with various external math and physics +libraries can be found at https://github.com/mosra/magnum-integration. Various +importer plugins for image, audio and 3D model formats are maintained in separate repository, which can be found at https://github.com/mosra/magnum-plugins. There are also examples of engine usage, varying from simple *Hello World*-like example to more advanced applications, such as viewer for complex 3D models. Example repository is at https://github.com/mosra/magnum-examples. +Repository with bootstrap projects for many use cases, helping you get up and +running in no time is located at https://github.com/mosra/magnum-bootstrap. + CONTACT ======= -Want to learn more about the library? Found a bug or want to tell me an -awesome idea? Feel free to visit my website or contact me at: +Want to learn more about the library? Found a bug or want to tell me an awesome +idea? Feel free to visit my website or contact me at: - * Website - http://mosra.cz/blog/ - * GitHub - https://github.com/mosra/magnum - * E-mail - mosra@centrum.cz - * Jabber - mosra@jabbim.cz +* Website - http://mosra.cz/blog/magnum.php +* GitHub - https://github.com/mosra/magnum +* Twitter - https://twitter.com/czmosra +* E-mail - mosra@centrum.cz +* Jabber - mosra@jabbim.cz LICENSE ======= diff --git a/doc/best-practices.dox b/doc/best-practices.dox index 5e5c31ca1..e0f0c9331 100644 --- a/doc/best-practices.dox +++ b/doc/best-practices.dox @@ -65,6 +65,12 @@ to desired target, either in constructor or using Buffer::setTargetHint(). To ease up the development, @ref Mesh checks proper target hint when adding vertex and index buffers. +@section best-practices-hw Hardware-specific + +@subsection best-practices-intel Intel hardware + +- [Performance tuning applications for Intel Graphics for Linux and Chrome OS](http://software.intel.com/sites/default/files/Performance-tuning-applications-for-Intel-GEN-Graphics-for-Linux-and-Google-Chrome-OS.pdf) [PDF] + @subsection best-practices-powervr PowerVR hardware - [PowerVR Performance Recommendations](http://www.imgtec.com/powervr/insider/docs/PowerVR.Performance%20Recommendations.1.0.28.External.pdf) [PDF] diff --git a/doc/building.dox b/doc/building.dox index def7912d6..97044ac8b 100644 --- a/doc/building.dox +++ b/doc/building.dox @@ -38,8 +38,9 @@ Minimal set of tools and libraries required for building is: - C++ compiler with good C++11 support. Currently there are two compilers which are tested to support everything needed: **GCC** >= 4.6 and **Clang** - >= 3.1. -- **CMake** >= 2.8.8 (needed for `OBJECT` library target) + >= 3.1. On Windows you can use **MinGW**, Visual Studio compiler still lacks + some needed features. +- **CMake** >= 2.8.8 - **GLEW** - OpenGL extension wrangler (only if targeting desktop OpenGL) - **Corrade** - Plugin management and utility library. See @ref building-corrade "Corrade download and installation guide" for more @@ -63,9 +64,10 @@ subdirectory. @section building-compilation Compilation, installation -The library (for example with support for GLUT applications) can be built and -installed using these four commands. See below for more information about -optional features. +@subsection building-linux Via command-line (on Linux/Unix) + +On Unix-based OSs, the library (for example with support for GLUT applications) +can be built and installed using these four commands: mkdir -p build && cd build cmake .. \ @@ -74,17 +76,68 @@ optional features. make make install -The libraries are build as shared by default, pass `-DBUILD_STATIC=ON` to build -them as static. If you plan them to use with shared libraries later, enable -also position-independent code with `-DBUILD_STATIC_PIC=ON`. If you want to -build with another compiler (e.g. Clang), pass `-DCMAKE_CXX_COMPILER=clang++` -to CMake. +The library provides a lot of CMake options (described in sections later). They +can be passed to CMake either as `-Dname=value` parameters on command-line +(like above) or set conveniently using `cmake-gui`: + + cd build + cmake-gui . + +@subsection building-windows Using QtCreator and CMake GUI (on Windows) + +On Windows, if you don't want to touch the command-line, the easiest way is to +install QtCreator (just QtCreator, you don't need the full Qt SDK) and +configure it to use MinGW and CMake. + +For most convenient usage it's best to install (or copy/paste) all library +dependencies into directory where MinGW is installed (e.g. `C:/MinGW/`), +following proper filesystem hierarchy, i.e. headers into `include/` and +binaries into `bin/` or `lib/`. CMake will then have no problem finding them +and you won't need to explicitly specify path to each one. + +Then just open project's root `CMakeLists.txt` file within QtCreator. It then +asks you where to create build directory, allows you to specify initial CMake +parameters and then you can just press Configure and everything should be ready +to be built. You might need to set some CMake parameters before configuring, +they can be set with `-Dname=value`. See below for more information. + +After the initial import you might want to reconfigure some CMake variables +(more information below). Start CMake GUI, point it to the recently created +build dir, modify the variables and press Generate. QtCreator will detect the +changes and reparse the project accordingly. + +For most convenient usage it's best to set `CMAKE_INSTALL_PREFIX` to directory +where MinGW is installed (e.g. `C:/MinGW/`) and add `C:/MinGW/bin` and +`C:/MinGW/lib` to `PATH`. Installation to given prefix can be then done from +within QtCreator by adding new `make install` build rule. -@subsection building-optional Enabling or disabling features +@subsubsection building-windows-troubleshooting Windows troubleshooting + +If CMake isn't able to find dependencies (e.g. Corrade is not found) and you +have installed them to MinGW directory, point to `CMAKE_FIND_ROOT_PATH` to +MinGW installation prefix, e.g. specify `-DCMAKE_FIND_ROOT_PATH=C:/MinGW/` +CMake parameter. + +If building fails with GLEW linking errors (`undefined reference to glew...`), +you have to set `GLEW_LIBRARY_DLL` and `GLEW_LIBRARY_LIB` CMake variables +manually. One of them should point to `glew32.dll` and the other to +`glew32.lib`. CMake currently isn't able to distinguish between them and in +most cases points both to the same location. + +See also Corrade's @ref building-corrade-windows-troubleshooting "troubleshooting section". + +@subsection building-features Enabling or disabling features + +The libraries are build as shared by default. If you are developing for +platform which doesn't support shared libraries or if you just want to link +them statically, enable `BUILD_STATIC` to build the libraries as static. If you +plan to use them with shared libraries later, enable also position-independent +code with `BUILD_STATIC_PIC`. If you want to build with another compiler (e.g. +Clang), pass `-DCMAKE_CXX_COMPILER=clang++` to CMake. By default the engine is built for desktop OpenGL. Using `TARGET_*` CMake -parameters you can target other platforms. Note that some features are available -for desktop OpenGL only, see @ref requires-gl. +parameters you can target other platforms. Note that some features are +available for desktop OpenGL only, see @ref requires-gl. - `TARGET_GLES` - Target OpenGL ES. - `TARGET_GLES2` - Target OpenGL ES 2.0. Currently enabled by default when @@ -92,10 +145,16 @@ for desktop OpenGL only, see @ref requires-gl. - `TARGET_DESKTOP_GLES` - Target OpenGL ES on desktop, i.e. use OpenGL ES emulation in desktop OpenGL library. Might not be supported in all drivers. -By default the engine is built with everything except application libraries (see -below). Using `WITH_*` CMake parameters you can specify which parts will be built -and which not: +The features used can be conveniently detected in depending projects both in +CMake and C++ sources, see @ref cmake and @ref src/Magnum.h for more +information. See also @ref corrade-cmake and @ref src/Corrade.h for additional +information. + +By default the engine is built with everything except application libraries +(see below). Using `WITH_*` CMake parameters you can specify which parts will +be built and which not: + - `WITH_AUDIO` - Audio library. Requires **OpenAL** library. - `WITH_DEBUGTOOLS` - DebugTools library. Enables also building of MeshTools, Primitives, SceneGraph, Shaders and Shapes libraries. - `WITH_MESHTOOLS` - MeshTools library. Enabled automatically if `WITH_DEBUGTOOLS` @@ -134,13 +193,15 @@ platform best: @subsection building-tests Building and running unit tests -If you want to build also unit tests (which are not built by default), pass -`-DBUILD_TESTS=ON` to CMake. Unit tests use Corrade's @ref Corrade::TestSuite -"TestSuite" framework and can be run using +If you want to build also unit tests (which are not built by default), enable +`BUILD_TEST` in CMake. Unit tests use Corrade's @ref Corrade::TestSuite +"TestSuite" framework and can be run either manually (the binaries are located +in `Test/` subdirectories of build directory) or using ctest --output-on-failure -in build directory. Everything should pass ;-) +in build directory. On Windows the tests require the library to be installed +with DLLs accessible through `PATH`. See above for more information. @subsection building-doc Building documentation @@ -155,23 +216,31 @@ in root directory (i.e. where `Doxyfile` is). Resulting HTML documentation will be in `build/doc/` directory. You might need to create `build/` directory if it doesn't exist yet. +@section building-related Related projects + +The engine itself is kept as small as possible with only little dependencies. +Additional functionality, often depending on external libraries, is provided in +separate repositories. Various importer plugins for image, audio and 3D model +formats are maintained in @ref building-plugins "Plugins repository", +Integration with various external math and physics libraries is provided by +@ref building-integration "Integration library". + @section building-arch Building ArchLinux packages In `package/archlinux` directory is currently one PKGBUILD for Git development build. The package is also in AUR under the same name. -There is also development PKGBUILD and MinGW development PKGBUILD in root, -which allows you to build and install the package directly from source tree -without downloading anything. The PKGBUILD also contains `check()` function -which will run all unit tests before packaging. Note that the unit tests -require Qt, as said above. +There are also a few development PKGBUILDs in project root, which allow you to +build and install the package directly from source tree without downloading +anything. The native PKGBUILD also contains `check()` function which will run +all unit tests before packaging. If you want to build with another compiler (e.g. Clang), run makepkg this way: CXX=clang++ makepkg -Both development PKGBUILDs can detect when Clang is used and remove -unsupported CXX flags. +Development PKGBUILDs can detect when Clang is used and remove unsupported CXX +flags. @section building-win Crosscompiling for Windows using MinGW diff --git a/doc/cmake.dox b/doc/cmake.dox index b23a19c51..6ebd791d6 100644 --- a/doc/cmake.dox +++ b/doc/cmake.dox @@ -51,6 +51,7 @@ components. The base library depends on %Corrade, OpenGL and GLEW libraries (or OpenGL ES libraries). Additional dependencies are specified by the components. The optional components are: +- `%Audio` -- Audio library (depends on OpenAL library) - `%DebugTools` -- DebugTools library (depends on `%MeshTools`, `%Primitives`, `%SceneGraph`, `%Shaders` and `%Shapes` components) - `%MeshTools` -- MeshTools library @@ -105,6 +106,8 @@ are also available as preprocessor variables if including Magnum.h: %Corrade library provides also its own set of CMake macros and variables, see @ref corrade-cmake "its documentation" for more information. +@ref cmake-plugins "Plugins repository" and @ref cmake-integration "Integration library" +have also their own CMake modules. */ } diff --git a/doc/getting-started-blue.png b/doc/getting-started-blue.png new file mode 100644 index 000000000..554613df2 Binary files /dev/null and b/doc/getting-started-blue.png differ diff --git a/doc/getting-started.dox b/doc/getting-started.dox new file mode 100644 index 000000000..d98af28d5 --- /dev/null +++ b/doc/getting-started.dox @@ -0,0 +1,202 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013 Vladimír VondruÅ¡ + + 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. +*/ + +namespace Magnum { +/** @page getting-started Getting started +@brief Get started with %Magnum in matter of minutes. + +@tableofcontents + +@section getting-started-download Download, build and install Magnum + +Get latest version from GitHub and install it. Read full guide on +@ref building "how to download, build and install Magnum" on platform of your +choice. For our first project we will use GLUT toolkit, don't forget to enable +it for building using `WITH_GLUTAPPLICATION` CMake parameter. + +@section getting-started-bootstrap Download bootstrap project + +Setting up a new project can be pretty gruesome and nobody likes repeating the +same process every time. %Magnum provides "bootstrap" project structures for +many use cases, helping you get up and running in no time. + +The [bootstrap repository](https://github.com/mosra/magnum-bootstrap) is +located on GitHub. The `master` branch contains just an README file and the +actual bootstrap projects are in various other branches, each covering some +particular use case. For your first project you would need the `base` branch, +which contains only the essential files you need. Download the branch [as an +archive](https://github.com/mosra/magnum-bootstrap/archive/base.zip) and +extract it somewhere. Do it rather than cloning the full repository, as it's +better to init your own repository from scratch to avoid having the history +polluted. + +@section getting-started-review Review project structure + +The base project consists of just seven files in two subfolders. %Magnum uses +CMake build system, see @ref cmake for more information. + + modules/FindCorrade.cmake + modules/FindMagnum.cmake + modules/FindGLEW.cmake + src/MyApplication.cpp + src/CMakeLists.txt + CMakeLists.txt + .gitignore + +In root there is pre-filled `.gitignore` for your Git project and also +project-wide `CMakeLists.txt`. It just sets up project name, specifies module +directory and delegates everything important to `CMakeLists.txt` in `src/` +subdirectory. +@code +cmake_minimum_required(VERSION 2.8.8) +project(MyApplication) + +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/modules/") + +add_subdirectory(src) +@endcode + +Directory `modules/` contains CMake modules for finding the needed +dependencies. Unlike modules for finding e.g. GLUT and OpenGL, which are part +of standard CMake installation, these aren't part of it and thus must be +distributed with the project. These files are just verbatim copied from %Magnum +repository. + +Directory `src/` contains the actual project. To keep things simple, the +project consists of just one source file with the most minimal code possible: +@code +#include +#include + +using namespace Magnum; + +class MyApplication: public Platform::Application { + public: + explicit MyApplication(const Arguments& arguments); + + protected: + void viewportEvent(const Vector2i& size) override; + void drawEvent() override; +}; + +MyApplication::MyApplication(const Arguments& arguments): Platform::Application(arguments) {} + +void MyApplication::viewportEvent(const Vector2i& size) { + defaultFramebuffer.setViewport({{}, size}); +} + +void MyApplication::drawEvent() { + defaultFramebuffer.clear(FramebufferClear::Color); + swapBuffers(); +} + +MAGNUM_APPLICATION_MAIN(MyApplication) +@endcode + +The application essentially does nothing, just clears properly sized screen +framebuffer to default (black) color and then does buffer swap to actually +display it on the screen. `CMakeLists.txt` finds %Magnum, sets up compiler +flags, creates the executable and links it to all needed libraries: +@code +find_package(Magnum REQUIRED GlutApplication) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CORRADE_CXX_FLAGS}") +include_directories(${MAGNUM_INCLUDE_DIRS} ${MAGNUM_APPLICATION_INCLUDE_DIRS}) + +add_executable(MyApplication MyApplication.cpp) +target_link_libraries(MyApplication + ${MAGNUM_LIBRARIES} + ${MAGNUM_APPLICATION_LIBRARIES}) +@endcode + +In the following tutorials the code will be explained more thoroughly. + +@section getting-started-build Build it and run + +In Linux (and other Unix-based OSs) you can build the example using the +following three commands: create out-of-source build directory, run cmake in it +and then run make. The application binary will then appear in src/ subdirectory +of build dir: + + mkdir -p build && cd build + cmake .. + make + ./src/MyApplication + +On Windows, if you don't want to touch the command-line, the easiest way is to +open root `CMakeLists.txt` in QtCreator, let it import the project and then +just build and run the application. If CMake isn't able to find the +dependencies or the building fails for some reason, you might want to look at +@ref building-windows-troubleshooting. + +@image html getting-started.png +@image latex getting-started.png + +Now you can try to change something in the code. Without going too deep into +the concepts of graphics programming, we can change clear color to something +else and also print basic information about the GPU the engine is running on. +First include the needed headers: +@code +#include +#include +#include +@endcode + +And in the constructor (which is currently empty) change the clear color and +print something to debug output: +@code +Renderer::setClearColor({0.07f, 0.44f, 0.73f}); + +Debug() << "Hello! This application is running on" << Context::current()->version() + << "using" << Context::current()->rendererString(); +@endcode + +After rebuilding and starting the application, the clear color changes to +blueish one and something like this would be printed to the console: +@code +Hello! This application is running on OpenGL 3.3 using Geforce GT 330M +@endcode + +@image html getting-started-blue.png +@image latex getting-started-blue.png + +@section getting-started-tutorials Follow tutorials and learn the principles + +Now that you have your first application up and running, the best way to +continue is to render your first triangle in @ref example-index "step-by-step tutorial". +Then you can dig deeper and try other examples, read about +@ref features "fundamental principles" in the documentation and start +experimenting on your own! + +@section getting-started-more Additional information + +- @subpage building +- @subpage building-plugins +- @subpage building-integration +- @subpage cmake +- @subpage cmake-plugins +- @subpage cmake-integration + +*/ +} diff --git a/doc/getting-started.png b/doc/getting-started.png new file mode 100644 index 000000000..cc0fc6112 Binary files /dev/null and b/doc/getting-started.png differ diff --git a/doc/mainpage.dox b/doc/mainpage.dox index 7ed82d7d5..ff86d27a1 100644 --- a/doc/mainpage.dox +++ b/doc/mainpage.dox @@ -62,7 +62,7 @@ namespace Magnum { @section mainpage-platforms Supported platforms -- **OpenGL** 2.1 through 4.3, core profile functionality and modern +- **OpenGL** 2.1 through 4.4, core profile functionality and modern extensions - **OpenGL ES** 2.0, 3.0 and extensions to match desktop OpenGL functionality - **Linux** and embedded Linux (natively using GLX/EGL and Xlib or through @@ -83,16 +83,10 @@ namespace Magnum { - Pre-made shaders, primitives and other tools for easy prototyping and debugging. -@section mainpage-download-build Downloading and building Magnum - -Guide @ref building "how to download and build Magnum" on different platforms. - @section mainpage-getting-started Getting started -The best way to get started is to render your first triangle in -@ref example-index "step-by-step tutorial". Then you can dig deeper and try -other examples, read about @ref features "fundamental principles" in the -documentation or start experimenting on your own! +Read thorough @ref getting-started "guide to download, build, install and start using Magnum" +in your project. @section mainpage-hacking Hacking Magnum @@ -105,8 +99,9 @@ make the library as consistent and maintainable as possible. Feel free to get more information or contact the author at: +- Website - http://mosra.cz/blog/magnum.php - GitHub - https://github.com/mosra/magnum -- Website - http://mosra.cz/blog/ +- Twitter - https://twitter.com/czmosra - E-mail - mosra@centrum.cz - Jabber - mosra@jabbim.cz diff --git a/doc/namespaces.dox b/doc/namespaces.dox index 8a4e1b3b7..229abad25 100644 --- a/doc/namespaces.dox +++ b/doc/namespaces.dox @@ -88,6 +88,20 @@ This library is built by default and found by default in CMake. See @ref building and @ref cmake for more information. */ +/** @dir Audio + * @brief Namespace Magnum::Audio + */ +/** @namespace Magnum::Audio +@brief %Audio playback + +Audio import, playback and integration with @ref SceneGraph. + +This library is built when `WITH_AUDIO` is enabled and found as `%Audio` +component in CMake. See @ref building and @ref cmake for more information. +Additional plugins are part of plugin repository, see @ref building-plugins and +@ref cmake-plugins for more information. +*/ + /** @dir DebugTools * @brief Namespace Magnum::DebugTools */ @@ -177,6 +191,8 @@ Font texture creation and text layouting. This library is built when `WITH_TEXT` is enabled and found as `%Text` component in CMake. See @ref building and @ref cmake for more information. +Additional plugins are part of plugin repository, see @ref building-plugins and +@ref cmake-plugins for more information. */ /** @dir TextureTools @@ -202,5 +218,7 @@ Contains plugin interfaces for importing data of various formats and classes for direct access to the data. This library is built by default and found by default in CMake. See -@ref building and @ref cmake for more information. +@ref building and @ref cmake for more information. Additional plugins are part +of plugin repository, see @ref building-plugins and @ref cmake-plugins for more +information. */ diff --git a/doc/shapes.dox b/doc/shapes.dox index cb99d5454..1e5772d56 100644 --- a/doc/shapes.dox +++ b/doc/shapes.dox @@ -51,6 +51,7 @@ line and point. Collision of two lines can be detected only in 2D. @subsection shapes-3D Three-dimensional shapes - @ref Shapes::Sphere "Shapes::Sphere*D" -- @copybrief Shapes::Sphere +- @ref Shapes::Cylinder "Shapes::Cylinder*D" -- @copybrief Shapes::Cylinder - @ref Shapes::Capsule "Shapes::Capsule*D" -- @copybrief Shapes::Capsule - @ref Shapes::AxisAlignedBox "Shapes::AxisAlignedBox*D" -- @copybrief Shapes::AxisAlignedBox - @ref Shapes::Box "Shapes::Box*D" -- @copybrief Shapes::Box diff --git a/doc/types.dox b/doc/types.dox index 99d6f8755..3ddb332a6 100644 --- a/doc/types.dox +++ b/doc/types.dox @@ -69,7 +69,7 @@ type, `i` is @ref Int underlying type and `d` is for @ref Double underlying type | --------------------------------- | ------------------------------------ | | @ref Matrix2 or @ref Matrix2d | `mat2`/`mat2x2` or `dmat2`/`dmat2x2` | | @ref Matrix3 or @ref Matrix3d | `mat3`/`mat3x3` or `dmat3`/`dmat3x3` | -| @ref Matrix4 or @ref Matrix4d | `mat4`/`mat4x4` or `dmat3`/`dmat4x4` | +| @ref Matrix4 or @ref Matrix4d | `mat4`/`mat4x4` or `dmat4`/`dmat4x4` | | @ref Matrix2x3 or @ref Matrix2x3d | `mat2x3` or `dmat2x3` | | @ref Matrix3x2 or @ref Matrix3x2d | `mat3x2` or `dmat3x2` | | @ref Matrix2x4 or @ref Matrix2x4d | `mat2x4` or `dmat2x4` | diff --git a/modules/FindMagnum.cmake b/modules/FindMagnum.cmake index 4cfaa9393..060f80595 100644 --- a/modules/FindMagnum.cmake +++ b/modules/FindMagnum.cmake @@ -7,14 +7,20 @@ # MAGNUM_LIBRARIES - Magnum library and dependent libraries # MAGNUM_INCLUDE_DIRS - Root include dir and include dirs of # dependencies +# MAGNUM_PLUGINS_DIR - Base directory with plugins. You can modify +# it (e.g. set it to `.` when deploying on Windows with plugins stored +# relatively to the executable), the following MAGNUM_PLUGINS_*_DIR +# variables depend on it. # MAGNUM_PLUGINS_FONT_DIR - Directory with font plugins # MAGNUM_PLUGINS_FONTCONVERTER_DIR - Directory with font converter plugins # MAGNUM_PLUGINS_IMAGECONVERTER_DIR - Directory with image converter plugins # MAGNUM_PLUGINS_IMPORTER_DIR - Directory with importer plugins +# MAGNUM_PLUGINS_AUDIOIMPORTER_DIR - Directory with audio importer plugins # This command will try to find only the base library, not the optional # components. The base library depends on Corrade, OpenGL and GLEW # libraries. Additional dependencies are specified by the components. The # optional components are: +# Audio - Audio library (depends on OpenAL library) # DebugTools - DebugTools library (depends on MeshTools, Primitives, # SceneGraph, Shaders and Shapes components) # MeshTools - MeshTools library @@ -70,6 +76,8 @@ # installation directory # MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR - Importer plugin installation # directory +# MAGNUM_PLUGINS_AUDIOIMPORTER_INSTALL_DIR - Audio omporter plugin +# installation directory # MAGNUM_CMAKE_MODULE_INSTALL_DIR - Installation dir for CMake # modules # MAGNUM_INCLUDE_INSTALL_DIR - Header installation directory @@ -215,6 +223,19 @@ foreach(component ${Magnum_FIND_COMPONENTS}) endif() endif() + # Audio library + if(${component} STREQUAL Audio) + set(_MAGNUM_${_COMPONENT}_INCLUDE_PATH_NAMES Audio.h) + + find_package(OpenAL) + if(OPENAL_FOUND) + set(_MAGNUM_${_COMPONENT}_LIBRARIES ${OPENAL_LIBRARY}) + set(_MAGNUM_${_COMPONENT}_INCLUDE_DIRS ${OPENAL_INCLUDE_DIR}) + else() + unset(MAGNUM_${_COMPONENT}_LIBRARY) + endif() + endif() + # DebugTools library if(${component} STREQUAL DebugTools) set(_MAGNUM_${_COMPONENT}_INCLUDE_PATH_NAMES DebugTools.h) @@ -325,6 +346,7 @@ set(MAGNUM_PLUGINS_FONT_INSTALL_DIR ${MAGNUM_PLUGINS_INSTALL_DIR}/fonts) set(MAGNUM_PLUGINS_FONTCONVERTER_INSTALL_DIR ${MAGNUM_PLUGINS_INSTALL_DIR}/fontconverters) set(MAGNUM_PLUGINS_IMAGECONVERTER_INSTALL_DIR ${MAGNUM_PLUGINS_INSTALL_DIR}/imageconverters) set(MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR ${MAGNUM_PLUGINS_INSTALL_DIR}/importers) +set(MAGNUM_PLUGINS_AUDIOIMPORTER_INSTALL_DIR ${MAGNUM_PLUGINS_INSTALL_DIR}/audioimporters) set(MAGNUM_CMAKE_MODULE_INSTALL_DIR ${CMAKE_ROOT}/Modules) set(MAGNUM_INCLUDE_INSTALL_DIR ${CMAKE_INSTALL_PREFIX}/include/Magnum) set(MAGNUM_PLUGINS_INCLUDE_INSTALL_DIR ${CMAKE_INSTALL_PREFIX}/include/Magnum/Plugins) @@ -337,19 +359,17 @@ mark_as_advanced(FORCE MAGNUM_PLUGINS_FONTCONVERTER_INSTALL_DIR MAGNUM_PLUGINS_IMAGECONVERTER_INSTALL_DIR MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR + MAGNUM_PLUGINS_AUDIOIMPORTER_INSTALL_DIR MAGNUM_CMAKE_MODULE_INSTALL_DIR MAGNUM_INCLUDE_INSTALL_DIR MAGNUM_PLUGINS_INCLUDE_INSTALL_DIR) +set(MAGNUM_PLUGINS_DIR ${MAGNUM_PLUGINS_INSTALL_DIR} + CACHE PATH "Base directory where to look for Magnum plugins") + # Plugin directories -if(NOT WIN32) - set(MAGNUM_PLUGINS_FONT_DIR ${MAGNUM_PLUGINS_INSTALL_DIR}/fonts) - set(MAGNUM_PLUGINS_FONTCONVERTER_DIR ${MAGNUM_PLUGINS_INSTALL_DIR}/fontconverters) - set(MAGNUM_PLUGINS_IMAGECONVERTER_DIR ${MAGNUM_PLUGINS_INSTALL_DIR}/imageconverters) - set(MAGNUM_PLUGINS_IMPORTER_DIR ${MAGNUM_PLUGINS_INSTALL_DIR}/importers) -else() - set(MAGNUM_PLUGINS_FONT_DIR fonts) - set(MAGNUM_PLUGINS_FONTCONVERTER_DIR fontconverters) - set(MAGNUM_PLUGINS_IMAGECONVERTER_DIR imageconverters) - set(MAGNUM_PLUGINS_IMPORTER_DIR importers) -endif() +set(MAGNUM_PLUGINS_FONT_DIR ${MAGNUM_PLUGINS_DIR}/fonts) +set(MAGNUM_PLUGINS_FONTCONVERTER_DIR ${MAGNUM_PLUGINS_DIR}/fontconverters) +set(MAGNUM_PLUGINS_IMAGECONVERTER_DIR ${MAGNUM_PLUGINS_DIR}/imageconverters) +set(MAGNUM_PLUGINS_IMPORTER_DIR ${MAGNUM_PLUGINS_DIR}/importers) +set(MAGNUM_PLUGINS_AUDIOIMPORTER_DIR ${MAGNUM_PLUGINS_DIR}/audioimporters) diff --git a/package/archlinux/magnum-git/PKGBUILD b/package/archlinux/magnum-git/PKGBUILD index 5e3128719..12dc8f573 100644 --- a/package/archlinux/magnum-git/PKGBUILD +++ b/package/archlinux/magnum-git/PKGBUILD @@ -1,17 +1,23 @@ # Author: mosra pkgname=magnum-git -pkgver=20120331 +pkgver=20130819 pkgrel=1 -pkgdesc="OpenGL 3 graphics engine (Git version)" +pkgdesc="C++11 and OpenGL 2D/3D graphics engine (Git version)" arch=('i686' 'x86_64') -url="https://github.com/mosra/magnum" +url="http://mosra.cz/blog/magnum.php" license=('MIT') -depends=('corrade-git' 'glew') +depends=('corrade-git' 'glew' 'glut' 'openal') makedepends=('cmake' 'git') +provides=('magnum') +conflicts=('magnum') _gitroot="git://github.com/mosra/magnum.git" _gitname="magnum" +pkgver() { + date +%Y%m%d +} + build() { cd "$srcdir" msg "Connecting to Git server..." @@ -31,15 +37,14 @@ build() { cmake ../$_gitname \ -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_INSTALL_PREFIX=/usr + -DCMAKE_INSTALL_PREFIX=/usr \ + -DWITH_GLUTAPPLICATION=ON \ + -DWITH_GLXAPPLICATION=ON \ + -DWITH_WINDOWLESSGLXAPPLICATION=ON \ + -DWITH_MAGNUMINFO=ON make } -check() { - cd "$startdir/build" - ctest --output-on-failure -E Benchmark -} - package() { cd "$srcdir/build" make DESTDIR="$pkgdir/" install diff --git a/src/AbstractResourceLoader.h b/src/AbstractResourceLoader.h index 3c7e4e697..830297489 100644 --- a/src/AbstractResourceLoader.h +++ b/src/AbstractResourceLoader.h @@ -161,6 +161,11 @@ template class AbstractResourceLoader { */ void set(ResourceKey key, T* data, ResourceDataState state, ResourcePolicy policy); + /** @overload */ + template void set(ResourceKey key, U&& data, ResourceDataState state, ResourcePolicy policy) { + set(key, new typename std::remove_cv::type>::type(std::forward(data)), state, policy); + } + /** * @brief Set loaded resource to resource manager * @@ -171,6 +176,11 @@ template class AbstractResourceLoader { set(key, data, ResourceDataState::Final, ResourcePolicy::Resident); } + /** @overload */ + template void set(ResourceKey key, U&& data) { + set(key, new typename std::remove_cv::type>::type(std::forward(data))); + } + /** * @brief Mark resource as not found * diff --git a/src/Audio/AbstractImporter.cpp b/src/Audio/AbstractImporter.cpp new file mode 100644 index 000000000..2e0ced0e3 --- /dev/null +++ b/src/Audio/AbstractImporter.cpp @@ -0,0 +1,97 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013 Vladimír VondruÅ¡ + + 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. +*/ + +#include "AbstractImporter.h" + +#include +#include +#include + +namespace Magnum { namespace Audio { + +AbstractImporter::AbstractImporter() = default; + +AbstractImporter::AbstractImporter(PluginManager::AbstractManager* manager, std::string plugin): PluginManager::AbstractPlugin(manager, std::move(plugin)) {} + +bool AbstractImporter::openData(Containers::ArrayReference data) { + CORRADE_ASSERT(features() & Feature::OpenData, + "Audio::AbstractImporter::openData(): feature not supported", nullptr); + + close(); + doOpenData(data); + return isOpened(); +} + +void AbstractImporter::doOpenData(Containers::ArrayReference) { + CORRADE_ASSERT(false, "Audio::AbstractImporter::openData(): feature advertised but not implemented", ); +} + +bool AbstractImporter::openFile(const std::string& filename) { + close(); + doOpenFile(filename); + return isOpened(); +} + +void AbstractImporter::doOpenFile(const std::string& filename) { + CORRADE_ASSERT(features() & Feature::OpenData, "Audio::AbstractImporter::openFile(): not implemented", ); + + /* Open file */ + std::ifstream in(filename.data(), std::ios::binary); + if(!in.good()) { + Error() << "Trade::AbstractImporter::openFile(): cannot open file" << filename; + return; + } + + /* Create array to hold file contents */ + in.seekg(0, std::ios::end); + Containers::Array data(in.tellg()); + + /* Read data, close */ + in.seekg(0, std::ios::beg); + in.read(reinterpret_cast(data.begin()), data.size()); + in.close(); + + doOpenData(data); +} + +void AbstractImporter::close() { + if(isOpened()) doClose(); +} + +Buffer::Format AbstractImporter::format() const { + CORRADE_ASSERT(isOpened(), "Audio::AbstractImporter::format(): no file opened", {}); + return doFormat(); +} + +UnsignedInt AbstractImporter::frequency() const { + CORRADE_ASSERT(isOpened(), "Audio::AbstractImporter::frequency(): no file opened", {}); + return doFrequency(); +} + +Containers::Array AbstractImporter::data() { + CORRADE_ASSERT(isOpened(), "Audio::AbstractImporter::data(): no file opened", {}); + return doData(); +} + +}} diff --git a/src/Audio/AbstractImporter.h b/src/Audio/AbstractImporter.h new file mode 100644 index 000000000..9e49fc8bf --- /dev/null +++ b/src/Audio/AbstractImporter.h @@ -0,0 +1,163 @@ +#ifndef Magnum_Audio_AbstractImporter_h +#define Magnum_Audio_AbstractImporter_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013 Vladimír VondruÅ¡ + + 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. +*/ + +/** @file Audio/AbstractImporter.h + * @brief Class Magnum::Audio::AbstractImporter + */ + +#include + +#include "Magnum.h" +#include "Audio/Buffer.h" + +namespace Magnum { namespace Audio { + +/** +@brief Base for audio importer plugins + +@section Audio-AbstractImporter-subclassing Subclassing + +Plugin implements function doFeatures(), doIsOpened(), one of or both +doOpenData() and doOpenFile() functions, function doClose() and data access +functions doFormat(), doFrequency() and doData(). + +You don't need to do most of the redundant sanity checks, these things are +checked by the implementation: + +- Functions doOpenData() and doOpenFile() are called after the previous file + was closed, function doClose() is called only if there is any file opened. +- Function doOpenData() is called only if @ref Feature "Feature::OpenData" + is supported. +- All `do*()` implementations working on opened file are called only if + there is any file opened. +*/ +class MAGNUM_AUDIO_EXPORT AbstractImporter: public PluginManager::AbstractPlugin { + CORRADE_PLUGIN_INTERFACE("cz.mosra.magnum.Audio.AbstractImporter/0.1") + + public: + /** + * @brief Features supported by this importer + * + * @see Features, features() + */ + enum class Feature: UnsignedByte { + /** Opening files from raw data using openData() */ + OpenData = 1 << 0 + }; + + /** + * @brief Features supported by this importer + * + * @see features() + */ + typedef Containers::EnumSet Features; + + /** @brief Default constructor */ + explicit AbstractImporter(); + + /** @brief Plugin manager constructor */ + explicit AbstractImporter(PluginManager::AbstractManager* manager, std::string plugin); + + /** @brief Features supported by this importer */ + Features features() const { return doFeatures(); } + + /** @brief Whether any file is opened */ + bool isOpened() const { return doIsOpened(); } + + /** + * @brief Open raw data + * + * Closes previous file, if it was opened, and tries to open given + * file. Available only if @ref Feature "Feature::OpenData" is + * supported. Returns `true` on success, `false` otherwise. + * @see features(), openFile() + */ + bool openData(Containers::ArrayReference data); + + /** + * @brief Open file + * + * Closes previous file, if it was opened, and tries to open given + * file. Returns `true` on success, `false` otherwise. + * @see features(), openData() + */ + bool openFile(const std::string& filename); + + /** @brief Close file */ + void close(); + + /** @{ @name Data access */ + + /** @brief Sample format */ + Buffer::Format format() const; + + /** @brief Sample frequency */ + UnsignedInt frequency() const; + + /** @brief Sample data */ + Containers::Array data(); + + /*@}*/ + + #ifndef DOXYGEN_GENERATING_OUTPUT + private: + #else + protected: + #endif + /** @brief Implementation for features() */ + virtual Features doFeatures() const = 0; + + /** @brief Implementation for isOpened() */ + virtual bool doIsOpened() const = 0; + + /** @brief Implementation for openData() */ + virtual void doOpenData(Containers::ArrayReference data); + + /** + * @brief Implementation for openFile() + * + * If @ref Feature "Feature::OpenData" is supported, default + * implementation opens the file and calls doOpenData() with its + * contents. + */ + virtual void doOpenFile(const std::string& filename); + + /** @brief Implementation for close() */ + virtual void doClose() = 0; + + /** @brief Implementation for format() */ + virtual Buffer::Format doFormat() const = 0; + + /** @brief Implementation for frequency() */ + virtual UnsignedInt doFrequency() const = 0; + + /** @brief Implementation for data() */ + virtual Containers::Array doData() = 0; +}; + +}} + +#endif diff --git a/src/Audio/Audio.cpp b/src/Audio/Audio.cpp new file mode 100644 index 000000000..25c70e5e4 --- /dev/null +++ b/src/Audio/Audio.cpp @@ -0,0 +1,49 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013 Vladimír VondruÅ¡ + + 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. +*/ + +#include +#include + +#include "Types.h" + +namespace Magnum { namespace Audio { + +/* Verify types */ +static_assert(std::is_same::value, "ALubyte is not the same as UnsignedByte"); +static_assert(std::is_same::value, "ALbyte is not the same as Byte"); +static_assert(std::is_same::value, "ALushort is not the same as UnsignedShort"); +static_assert(std::is_same::value, "ALshort is not the same as Short"); +static_assert(std::is_same::value, "ALuint is not the same as UnsignedInt"); +static_assert(std::is_same::value, "ALint is not the same as Int"); +static_assert(std::is_same::value, "ALsizei is not the same as Int"); +static_assert(std::is_same::value, "ALfloat is not the same as Float"); +#ifndef MAGNUM_TARGET_GLES +static_assert(std::is_same::value, "ALdouble is not the same as Double"); +#endif + +/* Verify boolean values */ +static_assert(AL_FALSE == false, "AL_FALSE is not the same as false"); +static_assert(AL_TRUE == true, "AL_TRUE is not the same as true"); + +}} diff --git a/src/Audio/Audio.h b/src/Audio/Audio.h new file mode 100644 index 000000000..7fd001759 --- /dev/null +++ b/src/Audio/Audio.h @@ -0,0 +1,41 @@ +#ifndef Magnum_Audio_Audio_h +#define Magnum_Audio_Audio_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013 Vladimír VondruÅ¡ + + 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. +*/ + +/** @file + * @brief Forward declarations for Magnum::Audio namespace + */ + +namespace Magnum { namespace Audio { + +class AbstractImporter; +class Buffer; +class Context; +class Source; +/* Renderer used only statically */ + +}} + +#endif diff --git a/src/Audio/Buffer.cpp b/src/Audio/Buffer.cpp new file mode 100644 index 000000000..1a28d95c1 --- /dev/null +++ b/src/Audio/Buffer.cpp @@ -0,0 +1,44 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013 Vladimír VondruÅ¡ + + 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. +*/ + +#include "Buffer.h" + +#include + +namespace Magnum { namespace Audio { + +Debug operator<<(Debug debug, const Buffer::Format value) { + switch(value) { + #define _c(value) case Buffer::Format::value: return debug << "Audio::Buffer::Format::" #value; + _c(Mono8) + _c(Mono16) + _c(Stereo8) + _c(Stereo16) + #undef _c + } + + return debug << "Audio::Buffer::Format::(invalid)"; +} + +}} diff --git a/src/Audio/Buffer.h b/src/Audio/Buffer.h new file mode 100644 index 000000000..31774c9e5 --- /dev/null +++ b/src/Audio/Buffer.h @@ -0,0 +1,120 @@ +#ifndef Magnum_Audio_Buffer_h +#define Magnum_Audio_Buffer_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013 Vladimír VondruÅ¡ + + 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. +*/ + +/** @file + * @brief Class Magnum::Audio::Buffer + */ + +#include +#include +#include + +#include "Magnum.h" +#include "Audio/magnumAudioVisibility.h" + +namespace Magnum { namespace Audio { + +/** @brief Sample buffer */ +class Buffer { + public: + /** + * @brief Sample format + * + * @note Multi-channel format is played without 3D spatialization + * (useful for background music) + * @see @ref setData() + */ + enum class Format: ALenum { + Mono8 = AL_FORMAT_MONO8, /**< 8-bit unsigned mono */ + Mono16 = AL_FORMAT_MONO16, /**< 16-bit signed mono */ + Stereo8 = AL_FORMAT_STEREO8, /**< 8-bit interleaved unsigned stereo */ + Stereo16 = AL_FORMAT_STEREO16 /**< 16-bit interleaved signed stereo */ + }; + + /** + * @brief Constructor + * + * Creates OpenAL buffer object. + * @see @fn_al{GenBuffers} + */ + explicit Buffer() { alGenBuffers(1, &_id); } + + /** + * @brief Destructor + * + * Deletes OpenAL buffer object. + * @see @fn_al{DeleteBuffers} + */ + ~Buffer() { if(_id) alDeleteBuffers(1, &_id); } + + /** @brief Copying is not allowed */ + Buffer(const Buffer&) = delete; + + /** @brief Move constructor */ + Buffer(Buffer&& other); + + /** @brief Copying is not allowed */ + Buffer& operator=(const Buffer&) = delete; + + /** @brief Move assignment */ + Buffer& operator=(Buffer&& other); + + /** @brief OpenAL buffer ID */ + ALuint id() const { return _id; } + + /** + * @brief Set buffer data + * @param format Sample format + * @param data Data + * @param frequency Frequency + * @return Reference to self (for method chaining) + * + * @see @fn_al{BufferData} + */ + Buffer& setData(Format format, Containers::ArrayReference data, ALsizei frequency) { + alBufferData(_id, ALenum(format), data, data.size(), frequency); + return *this; + } + + private: + ALuint _id; +}; + +/** @debugoperator{Magnum::Audio::Buffer} */ +Debug MAGNUM_AUDIO_EXPORT operator<<(Debug debug, Buffer::Format value); + +inline Buffer::Buffer(Buffer&& other): _id(other._id) { + other._id = 0; +} + +inline Buffer& Buffer::operator=(Buffer&& other) { + std::swap(_id, other._id); + return *this; +} + +}} + +#endif diff --git a/src/Audio/CMakeLists.txt b/src/Audio/CMakeLists.txt new file mode 100644 index 000000000..ba125c954 --- /dev/null +++ b/src/Audio/CMakeLists.txt @@ -0,0 +1,55 @@ +# +# This file is part of Magnum. +# +# Copyright © 2010, 2011, 2012, 2013 Vladimír VondruÅ¡ +# +# 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. +# + +find_package(OpenAL REQUIRED) + +include_directories(${OPENAL_INCLUDE_DIR}) + +set(MagnumAudio_SOURCES + AbstractImporter.cpp + Audio.cpp + Buffer.cpp + Context.cpp + Renderer.cpp + Source.cpp) + +set(MagnumAudio_HEADERS + AbstractImporter.h + Audio.h + Buffer.h + Context.h + Renderer.h + Source.h + + magnumAudioVisibility.h) + +add_library(MagnumAudio ${SHARED_OR_STATIC} ${MagnumAudio_SOURCES}) +target_link_libraries(MagnumAudio ${CORRADE_PLUGINMANAGER_LIBRARIES} ${OPENAL_LIBRARY}) + +install(TARGETS MagnumAudio DESTINATION ${MAGNUM_LIBRARY_INSTALL_DIR}) +install(FILES ${MagnumAudio_HEADERS} DESTINATION ${MAGNUM_INCLUDE_INSTALL_DIR}/Audio) + +if(BUILD_TESTS) + add_subdirectory(Test) +endif() diff --git a/src/Audio/Context.cpp b/src/Audio/Context.cpp new file mode 100644 index 000000000..86af094de --- /dev/null +++ b/src/Audio/Context.cpp @@ -0,0 +1,65 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013 Vladimír VondruÅ¡ + + 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. +*/ + +#include "Context.h" + +#include +#include +#include + +#include "Magnum.h" + +namespace Magnum { namespace Audio { + +Context* Context::_current = nullptr; + +Context::Context() { + CORRADE_ASSERT(!_current, "Audio::Context: context already created", ); + + /* Open default device */ + const ALCchar* const defaultDevice = alcGetString(nullptr, ALC_DEFAULT_DEVICE_SPECIFIER); + _device = alcOpenDevice(defaultDevice); + if(!_device) { + Error() << "Audio::Context: cannot open sound device" << defaultDevice; + std::exit(1); + } + + _context = alcCreateContext(_device, nullptr); + if(!_context) { + Error() << "Audio::Context: cannot create context:" << alcGetError(_device); + std::exit(1); + } + + alcMakeContextCurrent(_context); + _current = this; +} + +Context::~Context() { + CORRADE_INTERNAL_ASSERT(_current == this); + + alcDestroyContext(_context); + alcCloseDevice(_device); +} + +}} diff --git a/src/Audio/Context.h b/src/Audio/Context.h new file mode 100644 index 000000000..b28bba470 --- /dev/null +++ b/src/Audio/Context.h @@ -0,0 +1,95 @@ +#ifndef Magnum_Audio_Context_h +#define Magnum_Audio_Context_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013 Vladimír VondruÅ¡ + + 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. +*/ + +/** @file + * @brief Class Magnum::Audio::Context + */ + +#include +#include + +#include "Audio/magnumAudioVisibility.h" + +#ifndef DOXYGEN_GENERATING_OUTPUT +typedef struct ALCdevice_struct ALCdevice; +typedef struct ALCcontext_struct ALCcontext; +#endif + +namespace Magnum { namespace Audio { + +/** +@brief OpenAL context + */ +class MAGNUM_AUDIO_EXPORT Context { + public: + /** @brief Current context */ + static Context* current() { return _current; } + + /** + * @brief Constructor + * + * Creates OpenAL context. + */ + explicit Context(); + + /** + * @brief Destructor + * + * Destroys OpenAL context. + */ + ~Context(); + + /** + * @brief Vendor string + * + * @see rendererString(), @fn_al{GetString} with @def_al{VENDOR} + */ + std::string vendorString() const { return alGetString(AL_VENDOR); } + + /** + * @brief %Renderer string + * + * @see vendorString(), @fn_al{GetString} with @def_al{RENDERER} + */ + std::string rendererString() const { return alGetString(AL_RENDERER); } + + /** + * @brief Version string + * + * @see @fn_al{GetString} with @def_al{VERSION} + */ + std::string versionString() const { return alGetString(AL_VERSION); } + + private: + static Context* _current; + + ALCdevice* _device; + ALCcontext* _context; +}; + +}} + +#endif diff --git a/src/Audio/Renderer.cpp b/src/Audio/Renderer.cpp new file mode 100644 index 000000000..4dbeae53f --- /dev/null +++ b/src/Audio/Renderer.cpp @@ -0,0 +1,46 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013 Vladimír VondruÅ¡ + + 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. +*/ + +#include "Renderer.h" + +#include + +namespace Magnum { namespace Audio { + +Debug operator<<(Debug debug, const Renderer::Error value) { + switch(value) { + #define _c(value) case Renderer::Error::value: return debug << "Audio::Renderer::Error::" #value; + _c(NoError) + _c(InvalidName) + _c(InvalidEnum) + _c(InvalidValue) + _c(InvalidOperation) + _c(OutOfMemory) + #undef _c + } + + return debug << "Audio::Renderer::Error::(invalid)"; +} + +}} diff --git a/src/Audio/Renderer.h b/src/Audio/Renderer.h new file mode 100644 index 000000000..f8041e28a --- /dev/null +++ b/src/Audio/Renderer.h @@ -0,0 +1,202 @@ +#ifndef Magnum_Audio_Renderer_h +#define Magnum_Audio_Renderer_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013 Vladimír VondruÅ¡ + + 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. +*/ + +/** @file + * @brief Class Magnum::Audio::Renderer + */ + +#include + +#include "Math/Vector3.h" +#include "Magnum.h" +#include "Audio/magnumAudioVisibility.h" + +namespace Magnum { namespace Audio { + +/** @brief Global renderer configuration */ +class Renderer { + public: + Renderer() = delete; + + /** + * @brief Error status + * + * @see error() + */ + enum class Error: ALenum { + NoError = AL_NO_ERROR, /**< No error occured */ + InvalidName = AL_INVALID_NAME, /**< Invalid name parameter */ + InvalidEnum = AL_INVALID_ENUM, /**< Invalid enum parameter */ + InvalidValue = AL_INVALID_VALUE, /**< Invalid enum value parameter */ + InvalidOperation = AL_INVALID_OPERATION, /**< Illegal call */ + OutOfMemory = AL_OUT_OF_MEMORY /**< Unable to allocate memory */ + }; + + /** @brief Error status */ + static Error error() { return Error(alGetError()); } + + /** @{ @name Listener positioning */ + + /** + * @brief Set listener position + * + * Default is `{0.0f, 0.0f, 0.0f}`. + * @see @fn_al{Listenerfv} with @def_al{POSITION} + */ + static void setListenerPosition(const Vector3& position) { + alListenerfv(AL_POSITION, position.data()); + } + + /** @overload + * @see @fn_al{Listeneriv} with @def_al{POSITION} + */ + static void setListenerPosition(const Vector3i& position) { + alListeneriv(AL_POSITION, position.data()); + } + + /** + * @brief Set listener orientation + * + * The values must be linearly independent and don't need to be + * normalized. Default is -Z and +Y. + * @see @fn_al{Listenerfv} with @def_al{ORIENTATION} + */ + static void setListenerOrientation(const Vector3& forward, const Vector3& up); + + /** @overload + * @see @fn_al{Listeneriv} with @def_al{ORIENTATION} + */ + static void setListenerOrientation(const Vector3i& forward, const Vector3i& up); + + /** + * @brief Set listener velocity + * + * Default is `{0.0f, 0.0f, 0.0f}`. + * @see @fn_al{Listenerfv} with @def_al{VELOCITY} + */ + static void setListenerVelocity(const Vector3& velocity) { + alListenerfv(AL_VELOCITY, velocity.data()); + } + + /** @overload + * @see @fn_al{Listeneriv} with @def_al{VELOCITY} + */ + static void setListenerVelocity(const Vector3i& velocity) { + alListeneriv(AL_VELOCITY, velocity.data()); + } + + /*@}*/ + + /** @{ @name Global behavior */ + + /** + * @brief Distance model + * + * @see setDistanceModel() + */ + enum class DistanceModel: ALenum { + /** No distance attenuation calculation */ + None = AL_NONE, + + /** Inverse distance */ + Inverse = AL_INVERSE_DISTANCE, + + /** Inverse distance, clamped */ + InverseClamped = AL_INVERSE_DISTANCE_CLAMPED, + + /** Linear distance */ + Linear = AL_LINEAR_DISTANCE, + + /** Linear distance, clamped */ + LinearClamped = AL_LINEAR_DISTANCE_CLAMPED, + + /** Exponential distance */ + Exponent = AL_EXPONENT_DISTANCE, + + /** Exponential distance, clamped */ + ExponentClamped = AL_EXPONENT_DISTANCE_CLAMPED + }; + + /** + * @brief Set listener gain + * + * Default is `1.0f`, which means that the sound is unattenuated. + * If set to `0.0f`, all sound is muted. + * @see @fn_al{Listenerf} with @def_al{GAIN} + */ + static void setListenerGain(Float gain) { + alListenerf(AL_GAIN, gain); + } + + /** + * @brief Set Doppler factor + * + * Default is `1.0f`. If set to `0.0f`, the effect is disabled. + * @see @ref setSpeedOfSound(), @fn_al{DopplerFactor} + */ + static void setDopplerFactor(Float factor) { + alDopplerFactor(factor); + } + + /** + * @brief Set speed of sound + * + * Default is `343.3f` (meters per second). + * @see @ref setDopplerFactor(), @fn_al{SpeedOfSound} + */ + static void setSpeedOfSound(Float speed) { + alSpeedOfSound(speed); + } + + /** + * @brief Set distance model + * + * Default is @ref DistanceModel "DistanceModel::InverseClamped". + * @see @fn_al{DistanceModel} + */ + static void setDistanceModel(DistanceModel model) { + alDistanceModel(ALenum(model)); + } + + /*@}*/ +}; + +/** @debugoperator{Magnum::Audio::Renderer} */ +Debug MAGNUM_AUDIO_EXPORT operator<<(Debug debug, Renderer::Error value); + +inline void Renderer::setListenerOrientation(const Vector3& forward, const Vector3& up) { + const Vector3 data[] = {forward, up}; + alListenerfv(AL_ORIENTATION, data[0].data()); +} + +inline void Renderer::setListenerOrientation(const Vector3i& forward, const Vector3i& up) { + const Vector3i data[] = {forward, up}; + alListeneriv(AL_ORIENTATION, data[0].data()); +} + +}} + +#endif diff --git a/src/Audio/Source.cpp b/src/Audio/Source.cpp new file mode 100644 index 000000000..8eba148bc --- /dev/null +++ b/src/Audio/Source.cpp @@ -0,0 +1,123 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013 Vladimír VondruÅ¡ + + 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. +*/ + +#include "Source.h" + +#include "Audio/Buffer.h" + +namespace Magnum { namespace Audio { + +/** @todo C++14: use VLA to avoid unnecessary allocations */ + +Source& Source::setBuffer(Buffer* buffer) { + alSourcei(_id, AL_BUFFER, buffer ? buffer->id() : 0); + return *this; +} + +namespace { + +const ALuint* sourceIds(const std::initializer_list& sources) { + ALuint* const ids = new ALuint[sources.size()]; + for(auto it = sources.begin(); it != sources.end(); ++it) { + CORRADE_INTERNAL_ASSERT(*it); + ids[it-sources.begin()] = (*it)->id(); + } + return ids; +} + +const ALuint* sourceIds(const std::vector& sources) { + ALuint* const ids = new ALuint[sources.size()]; + for(auto it = sources.begin(); it != sources.end(); ++it) { + CORRADE_INTERNAL_ASSERT(*it); + ids[it-sources.begin()] = (*it)->id(); + } + return ids; +} + +} + +/** @todo Okay, this is too much code copying even for me */ + +void Source::play(std::initializer_list sources) { + const ALuint* const ids = sourceIds(sources); + alSourcePlayv(sources.size(), ids); + delete[] ids; +} + +void Source::play(const std::vector& sources) { + const ALuint* const ids = sourceIds(sources); + alSourcePlayv(sources.size(), ids); + delete[] ids; +} + +void Source::pause(std::initializer_list sources) { + const ALuint* const ids = sourceIds(sources); + alSourcePausev(sources.size(), ids); + delete[] ids; +} + +void Source::pause(const std::vector& sources) { + const ALuint* const ids = sourceIds(sources); + alSourcePausev(sources.size(), ids); + delete[] ids; +} + +void Source::stop(std::initializer_list sources) { + const ALuint* const ids = sourceIds(sources); + alSourceStopv(sources.size(), ids); + delete[] ids; +} + +void Source::stop(const std::vector& sources) { + const ALuint* const ids = sourceIds(sources); + alSourceStopv(sources.size(), ids); + delete[] ids; +} + +void Source::rewind(std::initializer_list sources) { + const ALuint* const ids = sourceIds(sources); + alSourceRewindv(sources.size(), ids); + delete[] ids; +} + +void Source::rewind(const std::vector& sources) { + const ALuint* const ids = sourceIds(sources); + alSourceRewindv(sources.size(), ids); + delete[] ids; +} + +Debug operator<<(Debug debug, const Source::State value) { + switch(value) { + #define _c(value) case Source::State::value: return debug << "Audio::Source::State::" #value; + _c(Initial) + _c(Playing) + _c(Paused) + _c(Stopped) + #undef _c + } + + return debug << "Audio::Source::State::(invalid)"; +} + +}} diff --git a/src/Audio/Source.h b/src/Audio/Source.h new file mode 100644 index 000000000..f96de05c1 --- /dev/null +++ b/src/Audio/Source.h @@ -0,0 +1,596 @@ +#ifndef Magnum_Audio_Source_h +#define Magnum_Audio_Source_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013 Vladimír VondruÅ¡ + + 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. +*/ + +/** @file + * @brief Class Magnum::Audio::Source + */ + +#include +#include +#include + +#include "Math/Vector3.h" +#include "Magnum.h" +#include "Audio/Audio.h" +#include "Audio/magnumAudioVisibility.h" + +namespace Magnum { namespace Audio { + +/** +@brief %Source + +Manages positional audio source. +@todo Expose convenient API for buffer queuing +*/ +class MAGNUM_AUDIO_EXPORT Source { + public: + /** + * @brief Constructor + * + * Creates OpenAL source object. + * @see @fn_al{GenSources} + */ + explicit Source() { alGenSources(1, &_id); } + + /** + * @brief Destructor + * + * Deletes OpenAL source object. + * @see @fn_al{DeleteSources} + */ + ~Source() { if(_id) alDeleteSources(1, &_id); } + + /** @brief Copying is not allowed */ + Source(const Source&) = delete; + + /** @brief Move constructor */ + Source(Source&& other); + + /** @brief Copying is not allowed */ + Source& operator=(const Source&) = delete; + + /** @brief Move assignment */ + Source& operator=(Source&& other); + + /** @brief OpenAL source ID */ + ALuint id() const { return _id; } + + /** @{ @name Source positioning */ + + /** + * @brief Set position + * @return Reference to self (for method chaining) + * + * Default is `{0.0f, 0.0f, 0.0f}`. + * @see @ref setRelative(), @fn_al{Sourcefv} with @def_al{POSITION} + */ + Source& setPosition(const Vector3& position) { + alSourcefv(_id, AL_POSITION, position.data()); + return *this; + } + + /** @overload + * @see @fn_al{Sourceiv} with @def_al{POSITION} + */ + Source& setPosition(const Vector3i& position) { + alSourceiv(_id, AL_POSITION, position.data()); + return *this; + } + + /** + * @brief Set velocity + * @return Reference to self (for method chaining) + * + * Default is `{0.0f, 0.0f, 0.0f}`. + * @see @ref setRelative(), @fn_al{Sourcefv} with @def_al{VELOCITY} + */ + Source& setVelocity(const Vector3& velocity) { + alSourcefv(_id, AL_VELOCITY, velocity.data()); + return *this; + } + + /** @overload + * @see @fn_al{Sourceiv} with @def_al{VELOCITY} + */ + Source& setVelocity(const Vector3i& velocity) { + alSourceiv(_id, AL_VELOCITY, velocity.data()); + return *this; + } + + /** + * @brief Interpret source relatively to listener + * + * When enabled, source position, direction and velocity will be + * interpreted relatively to listener. Default is `false`. + * @see @ref setPosition(), @ref setDirection(), @ref setVelocity(), + * @fn_al{Sourcei} with @def_al{SOURCE_RELATIVE} + */ + Source& setRelative(bool relative) { + alSourcei(_id, AL_SOURCE_RELATIVE, relative); + return *this; + } + + /*@}*/ + + /** @{ @name Source behavior */ + + /** + * @brief Set gain + * @return Reference to self (for method chaining) + * + * Default is `1.0f`, which means that the sound is unattenuated. + * If set to `0.0f`, the source is muted. + * @see @ref setMinGain(), @ref setMaxGain(), @fn_al{Sourcef} with + * @def_al{GAIN} + */ + Source& setGain(Float gain) { + alSourcef(_id, AL_GAIN, gain); + return *this; + } + + /** + * @brief Set min gain + * @return Reference to self (for method chaining) + * + * If effective gain is lower than min gain, min gain is used. Note + * that this is done before listener gain is applied. Default is + * `0.0f`. + * @see @ref setMinGain(), @ref setGain(), @fn_al{Sourcef} with + * @def_al{MIN_GAIN} + */ + Source& setMinGain(Float gain) { + alSourcef(_id, AL_MIN_GAIN, gain); + return *this; + } + + /** + * @brief Set max gain + * @return Reference to self (for method chaining) + * + * If effective gain is higher than max gain, max gain is used. Note + * that this is done before listener gain is applied. Default is + * `1.0f`. If set to `0.0f`, the source is muted. + * @see @ref setMinGain(), @ref setGain(), @fn_al{Sourcef} with + * @def_al{MIN_GAIN} + */ + Source& setMaxGain(Float gain) { + alSourcef(_id, AL_MAX_GAIN, gain); + return *this; + } + + /** + * @brief Set reference distance + * @return Reference to self (for method chaining) + * + * Default is `1.0f`. + * @see @ref setRolloffFactor(), @fn_al{Sourcef} with + * @def_al{REFERENCE_DISTANCE} + */ + Source& setReferenceDistance(Float distance) { + alSourcef(_id, AL_REFERENCE_DISTANCE, distance); + return *this; + } + + /** @overload + * @see @fn_al{Sourcei} with @def_al{REFERENCE_DISTANCE} + */ + Source& setReferenceDistance(Int distance) { + alSourcei(_id, AL_REFERENCE_DISTANCE, distance); + return *this; + } + + /** + * @brief Set rolloff factor + * @return Reference to self (for method chaining) + * + * Default is `1.0f`. + * @see @ref setReferenceDistance(), @fn_al{Sourcef} with + * @def_al{ROLLOFF_FACTOR} + */ + Source& setRolloffFactor(Float factor) { + alSourcef(_id, AL_ROLLOFF_FACTOR, factor); + return *this; + } + + /** @overload + * @see @fn_al{Sourcei} with @def_al{ROLLOFF_FACTOR} + */ + Source& setRolloffFactor(Int factor) { + alSourcei(_id, AL_ROLLOFF_FACTOR, factor); + return *this; + } + + /** + * @brief Set max distance + * @return Reference to self (for method chaining) + * + * Default is max representable value. + * @see @fn_al{Sourcef} with @def_al{MAX_DISTANCE} + */ + Source& setMaxDistance(Float distance) { + alSourcef(_id, AL_MAX_DISTANCE, distance); + return *this; + } + + /** @overload + * @see @fn_al{Sourcei} with @def_al{MAX_DISTANCE} + */ + Source& setMaxDistance(Int distance) { + alSourcef(_id, AL_MAX_DISTANCE, distance); + return *this; + } + + /** + * @brief Set direction + * @return Reference to self (for method chaining) + * + * Default is `{0.0f, 0.0f, 0.0f}`, which means that the source is not + * directional. + * @see @ref setInnerConeAngle(), @ref setOuterConeAngle(), + * @ref setRelative(), @fn_al{Sourcefv} with @def_al{DIRECTION} + */ + Source& setDirection(const Vector3& direction) { + alSourcefv(_id, AL_DIRECTION, direction.data()); + return *this; + } + + /** @overload + * @see @fn_al{Sourceiv} with @def_al{DIRECTION} + */ + Source& setDirection(const Vector3i& direction) { + alSourceiv(_id, AL_DIRECTION, direction.data()); + return *this; + } + + /** + * @brief Set inner cone angle + * @return Reference to self (for method chaining) + * + * Has effect only if the source is directional. Default is + * `360.0_degf`. + * @see @ref setOuterConeAngle(), @ref setDirection(), @fn_al{Sourcef} + * with @def_al{CONE_INNER_ANGLE} + */ + Source& setInnerConeAngle(Deg angle) { + alSourcef(_id, AL_CONE_INNER_ANGLE, Float(angle)); + return *this; + } + + /** + * @brief Set outer cone angle + * @return Reference to self (for method chaining) + * + * Has effect only if the source is directional. Default is + * `360.0_degf`. + * @see @ref setInnerConeAngle(), @ref setDirection(), + * @ref setOuterConeGain() @fn_al{Sourcef} with + * @def_al{CONE_OUTER_ANGLE} + */ + Source& setOuterConeAngle(Deg angle) { + alSourcef(_id, AL_CONE_OUTER_ANGLE, Float(angle)); + return *this; + } + + /** + * @brief Set outer cone gain multiplier + * @return Reference to self (for method chaining) + * + * The factor with which the gain is multiplied outside the outer cone. + * Default is `0.0f`. + * @see @ref setGain(), @ref setOuterConeAngle(), @fn_al{Sourcef} with + * @def_al{CONE_OUTER_GAIN} + */ + Source& setOuterConeGain(Float multiplier) { + alSourcef(_id, AL_CONE_OUTER_ANGLE, multiplier); + return *this; + } + + /** + * @brief Set pitch + * @return Reference to self (for method chaining) + * + * Default is `1.0f`. + * @see @fn_al{Sourcef} with @def_al{PITCH} + */ + Source& setPitch(Float pitch) { + alSourcef(_id, AL_PITCH, pitch); + return *this; + } + + /*@}*/ + + /** @{ @name Buffer management */ + + /** + * @brief %Source type + * + * @see @ref type() + */ + enum class Type: ALint { + Undetermined = AL_UNDETERMINED, /**< Undetermined (default) */ + Static = AL_STATIC, /**< Static source */ + Streaming = AL_STREAMING /**< Streaming source */ + }; + + /** + * @brief Source type + * + * @see @ref setBuffer(), @fn_al{GetSourcei} with @def_al{SOURCE_TYPE} + */ + Type type() const; + + /** + * @brief Attach buffer + * @param buffer Buffer to attach or `nullptr` + * @return Reference to self (for method chaining) + * + * If an buffer is attached, changes source type to + * @ref Type "Type::Static", if detached, changes source type to + * @ref Type "Type::Undetermined". The buffer must be already filled + * with data. + * @see @ref type(), @fn_al{Sourcei} with @def_al{BUFFER} + */ + Source& setBuffer(Buffer* buffer); + + /*@}*/ + + /** @{ @name State management */ + + /** + * @brief %Source state + * + * @see @ref state(), @ref play(), @ref pause(), @ref stop(), + * @ref rewind() + */ + enum class State: ALint { + Initial = AL_INITIAL, /**< Initial state (default) */ + Playing = AL_PLAYING, /**< The source is playing */ + Paused = AL_PAUSED, /**< The source is paused */ + Stopped = AL_STOPPED /**< The source is stopped */ + }; + + /** + * @brief Play more sources at once + * + * The operation is guaranteed to be done for all sources at the same + * time. `nullptr` is not allowed. + * @see @ref play(), @ref pause(std::initializer_list), + * @ref stop(std::initializer_list), + * @ref rewind(std::initializer_list), + * @fn_al{SourcePlayv} + */ + static void play(std::initializer_list sources); + static void play(const std::vector& sources); /**< @overload */ + + /** + * @brief Pause more sources at once + * + * The operation is guaranteed to be done for all sources at the same + * time. `nullptr` is not allowed. + * @see @ref pause(), @ref play(std::initializer_list), + * @ref stop(std::initializer_list), + * @ref rewind(std::initializer_list), + * @fn_al{SourcePausev} + */ + static void pause(std::initializer_list sources); + static void pause(const std::vector& sources); /**< @overload */ + + /** + * @brief Stop more sources at once + * + * The operation is guaranteed to be done for all sources at the same + * time. `nullptr` is not allowed. + * @see @ref stop(), @ref play(std::initializer_list), + * @ref pause(std::initializer_list), + * @ref rewind(std::initializer_list), + * @fn_al{SourceStopv} + */ + static void stop(std::initializer_list sources); + static void stop(const std::vector& sources); /**< @overload */ + + /** + * @brief Rewind more sources at once + * + * The operation is guaranteed to be done for all sources at the same + * time. `nullptr` is not allowed. + * @see @ref rewind(), @ref play(std::initializer_list), + * @ref pause(std::initializer_list), + * @ref stop(std::initializer_list), + * @fn_al{SourceRewindv} + */ + static void rewind(std::initializer_list sources); + static void rewind(const std::vector& sources); /**< @overload */ + + /** + * @brief State + * + * @see @ref play(), @ref pause(), @ref stop(), @ref rewind(), + * @fn_al{GetSourcei} with @def_al{SOURCE_STATE} + */ + State state() const; + + /** + * @brief Play + * + * @see @ref play(std::initializer_list), @ref state(), + * @ref pause(), @ref stop(), @ref rewind(), @fn_al{SourcePlay} + */ + void play() { alSourcePlay(_id); } + + /** + * @brief Pause + * + * @see @ref pause(std::initializer_list), @ref state(), + * @ref play(), @ref stop(), @ref rewind(), @fn_al{SourcePause} + */ + void pause() { alSourcePause(_id); } + + /** + * @brief Stop + * + * @see @ref stop(std::initializer_list), @ref state(), + * @ref play(), @ref pause(), @ref rewind(), @fn_al{SourceStop} + */ + void stop() { alSourceStop(_id); } + + /** + * @brief Rewind + * + * @see @ref rewind(std::initializer_list), @ref state(), + * @ref play(), @ref pause(), @ref stop(), @fn_al{SourceRewind} + */ + void rewind() { alSourceRewind(_id); } + + /** + * @brief Whether the source is looping + * + * @see @fn_al{GetSourcei} with @def_al{LOOPING} + */ + bool isLooping() const; + + /** + * @brief Set source looping + * @return Reference to self (for method chaining) + * + * Default is `false`. + * @see @fn_al{Sourcei} with @def_al{LOOPING} + */ + Source& setLooping(bool loop) { + alSourcei(_id, AL_LOOPING, loop); + return *this; + } + + /** + * @brief Offset in seconds + * + * @see @ref offsetInBytes(), @ref offsetInSamples(), + * @fn_al{GetSourcef} with @def_al{SEC_OFFSET} + */ + Float offsetInSeconds() const; + + /** + * @brief Set offset in seconds + * @return Reference to self (for method chaining) + * + * @see @ref setOffsetInBytes(), @ref setOffsetInSamples(), + * @fn_al{Sourcef} with @def_al{SEC_OFFSET} + */ + Source& setOffsetInSeconds(Float offset) { + alSourcef(_id, AL_SEC_OFFSET, offset); + return *this; + } + + /** + * @brief Offset in bytes + * + * @see @ref offsetInSeconds(), @ref offsetInSamples(), + * @fn_al{GetSourcei} with @def_al{BYTE_OFFSET} + */ + Int offsetInBytes() const; + + /** + * @brief Set offset in bytes + * @return Reference to self (for method chaining) + * + * @see @ref setOffsetInSeconds(), @ref setOffsetInSamples(), + * @fn_al{Sourcei} with @def_al{SEC_OFFSET} + */ + Source& setOffsetInBytes(Int offset) { + alSourcei(_id, AL_BYTE_OFFSET, offset); + return *this; + } + + /** + * @brief Offset in samples + * + * @see @ref offsetInSeconds(), @ref offsetInBytes(), + * @fn_al{GetSourcei} with @def_al{SAMPLE_OFFSET} + */ + Int offsetInSamples() const; + + /** + * @brief Set offset in samples + * @return Reference to self (for method chaining) + * + * @see @ref setOffsetInSeconds(), @ref setOffsetInBytes(), + * @fn_al{Sourcei} with @def_al{SEC_OFFSET} + */ + Source& setOffsetInSamples(Int offset) { + alSourcei(_id, AL_SAMPLE_OFFSET, offset); + return *this; + } + + /*@}*/ + + private: + ALuint _id; +}; + +/** @debugoperator{Magnum::Audio::Source} */ +Debug MAGNUM_AUDIO_EXPORT operator<<(Debug debug, Source::State value); + +inline Source::Source(Source&& other): _id(other._id) { + other._id = 0; +} + +inline Source& Source::operator=(Source&& other) { + std::swap(_id, other._id); + return *this; +} + +auto Source::state() const -> State { + ALint state; + alGetSourcei(_id, AL_SOURCE_STATE, &state); + return State(state); +} + +inline bool Source::isLooping() const { + ALint looping; + alGetSourcei(_id, AL_LOOPING, &looping); + return looping; +} + +inline Float Source::offsetInSeconds() const { + Float offset; + alGetSourcef(_id, AL_SEC_OFFSET, &offset); + return offset; +} + +inline Int Source::offsetInBytes() const { + Int offset; + alGetSourcei(_id, AL_BYTE_OFFSET, &offset); + return offset; +} + +inline Int Source::offsetInSamples() const { + Int offset; + alGetSourcei(_id, AL_SAMPLE_OFFSET, &offset); + return offset; +} + +}} + +#endif diff --git a/src/Audio/Test/AbstractImporterTest.cpp b/src/Audio/Test/AbstractImporterTest.cpp new file mode 100644 index 000000000..ff479d654 --- /dev/null +++ b/src/Audio/Test/AbstractImporterTest.cpp @@ -0,0 +1,76 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013 Vladimír VondruÅ¡ + + 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. +*/ + +#include +#include +#include + +#include "Audio/AbstractImporter.h" + +#include "testConfigure.h" + +namespace Magnum { namespace Audio { namespace Test { + +class AbstractImporterTest: public TestSuite::Tester { + public: + explicit AbstractImporterTest(); + + void openFile(); +}; + +AbstractImporterTest::AbstractImporterTest() { + addTests({&AbstractImporterTest::openFile}); +} + +void AbstractImporterTest::openFile() { + class DataImporter: public Audio::AbstractImporter { + public: + explicit DataImporter(): opened(false) {} + + private: + Features doFeatures() const override { return Feature::OpenData; } + bool doIsOpened() const override { return opened; } + void doClose() override {} + + void doOpenData(Containers::ArrayReference data) override { + opened = (data.size() == 1 && data[0] == 0xa5); + } + + Buffer::Format doFormat() const override { return {}; } + UnsignedInt doFrequency() const override { return {}; } + Corrade::Containers::Array doData() override { return nullptr; } + + bool opened; + }; + + /* doOpenFile() should call doOpenData() */ + DataImporter importer; + CORRADE_VERIFY(!importer.isOpened()); + importer.openFile(Utility::Directory::join(AUDIO_TEST_DIR, "file.bin")); + CORRADE_VERIFY(importer.isOpened()); +} + +}}} + +CORRADE_TEST_MAIN(Magnum::Audio::Test::AbstractImporterTest) diff --git a/src/Audio/Test/BufferTest.cpp b/src/Audio/Test/BufferTest.cpp new file mode 100644 index 000000000..56c6f2ba9 --- /dev/null +++ b/src/Audio/Test/BufferTest.cpp @@ -0,0 +1,51 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013 Vladimír VondruÅ¡ + + 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. +*/ + +#include +#include + +#include "Audio/Buffer.h" + +namespace Magnum { namespace Audio { namespace Test { + +class BufferTest: public TestSuite::Tester { + public: + explicit BufferTest(); + + void debugFormat(); +}; + +BufferTest::BufferTest() { + addTests({&BufferTest::debugFormat}); +} + +void BufferTest::debugFormat() { + std::ostringstream out; + Debug(&out) << Buffer::Format::Stereo16; + CORRADE_COMPARE(out.str(), "Audio::Buffer::Format::Stereo16\n"); +} + +}}} + +CORRADE_TEST_MAIN(Magnum::Audio::Test::BufferTest) diff --git a/src/Audio/Test/CMakeLists.txt b/src/Audio/Test/CMakeLists.txt new file mode 100644 index 000000000..3a42d1f27 --- /dev/null +++ b/src/Audio/Test/CMakeLists.txt @@ -0,0 +1,33 @@ +# +# This file is part of Magnum. +# +# Copyright © 2010, 2011, 2012, 2013 Vladimír VondruÅ¡ +# +# 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. +# + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/testConfigure.h.cmake + ${CMAKE_CURRENT_BINARY_DIR}/testConfigure.h) + +include_directories(${CMAKE_CURRENT_BINARY_DIR}) + +corrade_add_test(AudioAbstractImporterTest AbstractImporterTest.cpp LIBRARIES MagnumAudio) +corrade_add_test(AudioBufferTest BufferTest.cpp LIBRARIES MagnumAudio) +corrade_add_test(AudioRendererTest RendererTest.cpp LIBRARIES MagnumAudio) +corrade_add_test(AudioSourceTest SourceTest.cpp LIBRARIES MagnumAudio) diff --git a/src/Audio/Test/RendererTest.cpp b/src/Audio/Test/RendererTest.cpp new file mode 100644 index 000000000..9be69593d --- /dev/null +++ b/src/Audio/Test/RendererTest.cpp @@ -0,0 +1,51 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013 Vladimír VondruÅ¡ + + 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. +*/ + +#include +#include + +#include "Audio/Renderer.h" + +namespace Magnum { namespace Audio { namespace Test { + +class RendererTest: public TestSuite::Tester { + public: + explicit RendererTest(); + + void debugError(); +}; + +RendererTest::RendererTest() { + addTests({&RendererTest::debugError}); +} + +void RendererTest::debugError() { + std::ostringstream out; + Debug(&out) << Renderer::Error::InvalidOperation; + CORRADE_COMPARE(out.str(), "Audio::Renderer::Error::InvalidOperation\n"); +} + +}}} + +CORRADE_TEST_MAIN(Magnum::Audio::Test::RendererTest) diff --git a/src/Audio/Test/SourceTest.cpp b/src/Audio/Test/SourceTest.cpp new file mode 100644 index 000000000..90424ce29 --- /dev/null +++ b/src/Audio/Test/SourceTest.cpp @@ -0,0 +1,51 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013 Vladimír VondruÅ¡ + + 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. +*/ + +#include +#include + +#include "Audio/Source.h" + +namespace Magnum { namespace Audio { namespace Test { + +class SourceTest: public TestSuite::Tester { + public: + explicit SourceTest(); + + void debugState(); +}; + +SourceTest::SourceTest() { + addTests({&SourceTest::debugState}); +} + +void SourceTest::debugState() { + std::ostringstream out; + Debug(&out) << Source::State::Playing; + CORRADE_COMPARE(out.str(), "Audio::Source::State::Playing\n"); +} + +}}} + +CORRADE_TEST_MAIN(Magnum::Audio::Test::SourceTest) diff --git a/src/Audio/Test/file.bin b/src/Audio/Test/file.bin new file mode 100644 index 000000000..dd6737691 --- /dev/null +++ b/src/Audio/Test/file.bin @@ -0,0 +1 @@ +¥ \ No newline at end of file diff --git a/src/Audio/Test/testConfigure.h.cmake b/src/Audio/Test/testConfigure.h.cmake new file mode 100644 index 000000000..29272db06 --- /dev/null +++ b/src/Audio/Test/testConfigure.h.cmake @@ -0,0 +1,25 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013 Vladimír VondruÅ¡ + + 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. +*/ + +#define AUDIO_TEST_DIR "${CMAKE_CURRENT_SOURCE_DIR}" diff --git a/src/Audio/magnumAudioVisibility.h b/src/Audio/magnumAudioVisibility.h new file mode 100644 index 000000000..e4a7c0f67 --- /dev/null +++ b/src/Audio/magnumAudioVisibility.h @@ -0,0 +1,35 @@ +#ifndef Magnum_Audio_magnumAudioVisibility_h +#define Magnum_Audio_magnumAudioVisibility_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013 Vladimír VondruÅ¡ + + 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. +*/ + +#include + +#ifdef MagnumAudio_EXPORTS + #define MAGNUM_AUDIO_EXPORT CORRADE_VISIBILITY_EXPORT +#else + #define MAGNUM_AUDIO_EXPORT CORRADE_VISIBILITY_IMPORT +#endif + +#endif diff --git a/src/Buffer.h b/src/Buffer.h index 73163292e..65dbdf508 100644 --- a/src/Buffer.h +++ b/src/Buffer.h @@ -24,7 +24,7 @@ DEALINGS IN THE SOFTWARE. */ -/** @file +/** @file /Buffer.h * @brief Class Magnum::Buffer */ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b3cbcee62..566f39791 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -58,6 +58,7 @@ set(Magnum_SRCS Image.cpp ImageFormat.cpp Mesh.cpp + MeshView.cpp OpenGL.cpp Query.cpp Renderbuffer.cpp @@ -117,6 +118,7 @@ set(Magnum_HEADERS ImageReference.h Magnum.h Mesh.h + MeshView.h OpenGL.h Query.h Renderbuffer.h @@ -191,6 +193,10 @@ add_subdirectory(Math) add_subdirectory(Platform) add_subdirectory(Trade) +if(WITH_AUDIO) + add_subdirectory(Audio) +endif() + if(WITH_DEBUGTOOLS) add_subdirectory(DebugTools) endif() @@ -236,5 +242,11 @@ if(BUILD_TESTS) set_target_properties(MagnumTestLib PROPERTIES COMPILE_FLAGS -DCORRADE_GRACEFUL_ASSERT) target_link_libraries(MagnumTestLib ${Magnum_LIBS}) + # On Windows we need to install first and then run the tests to avoid "DLL + # not found" hell, thus we need to install this too + if(WIN32 AND NOT CMAKE_CROSSCOMPILING) + install(TARGETS MagnumMathTestLib MagnumTestLib DESTINATION ${MAGNUM_LIBRARY_INSTALL_DIR}) + endif() + add_subdirectory(Test) endif() diff --git a/src/Context.h b/src/Context.h index 50b7941f5..cf527da56 100644 --- a/src/Context.h +++ b/src/Context.h @@ -24,7 +24,7 @@ DEALINGS IN THE SOFTWARE. */ -/** @file +/** @file /Context.h * @brief Enum Magnum::Version, class Magnum::Context, Magnum::Extension, macro MAGNUM_ASSERT_VERSION_SUPPORTED(), MAGNUM_ASSERT_EXTENSION_SUPPORTED() */ diff --git a/src/DebugTools/CMakeLists.txt b/src/DebugTools/CMakeLists.txt index 66546c172..d348e3b8b 100644 --- a/src/DebugTools/CMakeLists.txt +++ b/src/DebugTools/CMakeLists.txt @@ -33,6 +33,8 @@ set(MagnumDebugTools_SRCS Implementation/AbstractShapeRenderer.cpp Implementation/AxisAlignedBoxRenderer.cpp Implementation/BoxRenderer.cpp + Implementation/CapsuleRenderer.cpp + Implementation/CylinderRenderer.cpp Implementation/LineSegmentRenderer.cpp Implementation/PointRenderer.cpp Implementation/SphereRenderer.cpp) diff --git a/src/DebugTools/Implementation/CapsuleRenderer.cpp b/src/DebugTools/Implementation/CapsuleRenderer.cpp new file mode 100644 index 000000000..1cd1922bd --- /dev/null +++ b/src/DebugTools/Implementation/CapsuleRenderer.cpp @@ -0,0 +1,116 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013 Vladimír VondruÅ¡ + + 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. +*/ + +#include "CapsuleRenderer.h" + +#include "MeshView.h" +#include "DebugTools/ResourceManager.h" +#include "DebugTools/ShapeRenderer.h" +#include "Primitives/Capsule.h" +#include "Shapes/Capsule.h" +#include "Shaders/Flat.h" +#include "Trade/MeshData2D.h" +#include "Trade/MeshData3D.h" + +#include "DebugTools/Implementation/CapsuleRendererTransformation.h" + +namespace Magnum { namespace DebugTools { namespace Implementation { + +AbstractCapsuleRenderer<2>::AbstractCapsuleRenderer(): AbstractShapeRenderer<2>("capsule2d", "capsule2d-vertices", "capsule2d-indices") { + constexpr UnsignedInt rings = 10; + if(!wireframeMesh) createResources(Primitives::Capsule2D::wireframe(rings, 1, 1.0f)); + + /* Bottom hemisphere */ + if(!(bottom = ResourceManager::instance().get("capsule2d-bottom"))) { + auto view = new MeshView(wireframeMesh); + view->setIndexRange(0, rings*4, 0, rings*2+1); + ResourceManager::instance().set(bottom.key(), view, ResourceDataState::Final, ResourcePolicy::Manual); + } + + /* Cylinder */ + if(!(cylinder = ResourceManager::instance().get("capsule2d-cylinder"))) { + auto view = new MeshView(wireframeMesh); + view->setIndexRange(rings*4, 4, rings*2+1, rings*2+3); + ResourceManager::instance().set(cylinder.key(), view, ResourceDataState::Final, ResourcePolicy::Manual); + } + + /* Top hemisphere */ + if(!(top = ResourceManager::instance().get("capsule2d-top"))) { + auto view = new MeshView(wireframeMesh); + view->setIndexRange(rings*4+4, rings*4, rings*2+3, rings*4+4); + ResourceManager::instance().set(top.key(), view, ResourceDataState::Final, ResourcePolicy::Manual); + } +} + +AbstractCapsuleRenderer<3>::AbstractCapsuleRenderer(): AbstractShapeRenderer<3>("capsule3d", "capsule3d-vertices", "capsule3d-indices") { + constexpr UnsignedInt rings = 10; + constexpr UnsignedInt segments = 40; + if(!wireframeMesh) createResources(Primitives::Capsule3D::wireframe(rings, 1, segments, 1.0f)); + + /* Bottom hemisphere */ + if(!(bottom = ResourceManager::instance().get("capsule3d-bottom"))) { + auto view = new MeshView(wireframeMesh); + view->setIndexRange(0, rings*8, 0, rings*4+1); + ResourceManager::instance().set(bottom.key(), view, ResourceDataState::Final, ResourcePolicy::Manual); + } + + /* Cylinder */ + if(!(cylinder = ResourceManager::instance().get("capsule3d-cylinder"))) { + auto view = new MeshView(wireframeMesh); + view->setIndexRange(rings*8, segments*4+8, rings*4+1, rings*4+segments*2+5); + ResourceManager::instance().set(cylinder.key(), view, ResourceDataState::Final, ResourcePolicy::Manual); + } + + /* Top */ + if(!(top = ResourceManager::instance().get("capsule3d-top"))) { + auto view = new MeshView(wireframeMesh); + view->setIndexRange(rings*8+segments*4+8, rings*8, rings*4+segments*2+5, rings*8+segments*2+6); + ResourceManager::instance().set(top.key(), view, ResourceDataState::Final, ResourcePolicy::Manual); + } +} + +template CapsuleRenderer::CapsuleRenderer(const Shapes::Implementation::AbstractShape& capsule): capsule(static_cast>&>(capsule).shape) {} + +template void CapsuleRenderer::draw(Resource& options, const typename DimensionTraits::MatrixType& projectionMatrix) { + std::array::MatrixType, 3> transformations = Implementation::capsuleRendererTransformation(capsule.a(), capsule.b(), capsule.radius()); + AbstractShapeRenderer::wireframeShader->setColor(options->color()) + .use(); + + /* Bottom */ + AbstractShapeRenderer::wireframeShader->setTransformationProjectionMatrix(projectionMatrix*transformations[0]); + AbstractCapsuleRenderer::bottom->draw(); + + /* Cylinder */ + AbstractShapeRenderer::wireframeShader->setTransformationProjectionMatrix(projectionMatrix*transformations[1]); + AbstractCapsuleRenderer::cylinder->draw(); + + /* Top */ + AbstractShapeRenderer::wireframeShader->setTransformationProjectionMatrix(projectionMatrix*transformations[2]); + AbstractCapsuleRenderer::top->draw(); +} + +template class CapsuleRenderer<2>; +template class CapsuleRenderer<3>; + +}}} diff --git a/src/DebugTools/Implementation/CapsuleRenderer.h b/src/DebugTools/Implementation/CapsuleRenderer.h new file mode 100644 index 000000000..d00c5e47f --- /dev/null +++ b/src/DebugTools/Implementation/CapsuleRenderer.h @@ -0,0 +1,66 @@ +#ifndef Magnum_DebugTools_Implementation_CapsuleRenderer_h +#define Magnum_DebugTools_Implementation_CapsuleRenderer_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013 Vladimír VondruÅ¡ + + 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. +*/ + +#include "AbstractShapeRenderer.h" + +#include "Shapes/Shapes.h" + +#include "corradeCompatibility.h" + +namespace Magnum { namespace DebugTools { namespace Implementation { + +template class AbstractCapsuleRenderer; + +template<> class AbstractCapsuleRenderer<2>: public AbstractShapeRenderer<2> { + public: + explicit AbstractCapsuleRenderer(); + + protected: + Resource bottom, cylinder, top; +}; + +template<> class AbstractCapsuleRenderer<3>: public AbstractShapeRenderer<3> { + public: + explicit AbstractCapsuleRenderer(); + + protected: + Resource bottom, cylinder, top; +}; + +template class CapsuleRenderer: public AbstractCapsuleRenderer { + public: + explicit CapsuleRenderer(const Shapes::Implementation::AbstractShape& capsule); + CapsuleRenderer(const Shapes::Implementation::AbstractShape&&) = delete; + + void draw(Resource& options, const typename DimensionTraits::MatrixType& projectionMatrix) override; + + private: + const Shapes::Capsule& capsule; +}; + +}}} + +#endif diff --git a/src/DebugTools/Implementation/CapsuleRendererTransformation.h b/src/DebugTools/Implementation/CapsuleRendererTransformation.h new file mode 100644 index 000000000..5f9271a67 --- /dev/null +++ b/src/DebugTools/Implementation/CapsuleRendererTransformation.h @@ -0,0 +1,106 @@ +#ifndef Magnum_DebugTools_Implementation_ForceRendererTransformation_h +#define Magnum_DebugTools_Implementation_ForceRendererTransformation_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013 Vladimír VondruÅ¡ + + 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. +*/ + +#include + +#include "Math/Functions.h" +#include "Math/Matrix3.h" +#include "Math/Matrix4.h" +#include "Magnum.h" +#include "DimensionTraits.h" + +namespace Magnum { namespace DebugTools { namespace Implementation { + +template std::array::MatrixType, 3> capsuleRendererTransformation(const typename DimensionTraits::VectorType& a, const typename DimensionTraits::VectorType& b, Float radius); + +template<> std::array capsuleRendererTransformation<2>(const Vector2& a, const Vector2& b, const Float radius) { + /* Vector from capsule center to top hemisphere center */ + const Vector2 direction = 0.5f*(b - a); + const Float length = direction.length(); + + /* Capsule rotation and distance to caps after they are scaled to proper + radius (if nonzero cylinder length) */ + Matrix3 rotation; + Vector2 capDistance; + if(length >= Math::TypeTraits::epsilon()) { + rotation.up() = direction/length; + rotation.right() = rotation.up().perpendicular(); + CORRADE_INTERNAL_ASSERT(rotation.right().isNormalized()); + + capDistance = direction*(radius/length); + } + + /* Scaling and translation of all parts */ + const auto rotationScaling = rotation*Matrix3::scaling(Vector2(radius)); + return {{ + Matrix3::translation(a+capDistance)*rotationScaling, + Matrix3::translation(0.5f*(a + b))*rotation*Matrix3::scaling({radius, length}), + Matrix3::translation(b-capDistance)*rotationScaling + }}; +} + +template<> std::array capsuleRendererTransformation<3>(const Vector3& a, const Vector3& b, const Float radius) { + /* Vector from capsule center to top hemisphere center */ + const Vector3 direction = 0.5f*(b - a); + const Float length = direction.length(); + + /* Capsule rotation and distance to caps after they are scaled to proper + radius (if nonzero cylinder length) */ + Matrix4 rotation; + Vector3 capDistance; + if(length >= Math::TypeTraits::epsilon()) { + const Vector3 directionNormalized = direction/length; + const Float dot = Vector3::dot(directionNormalized, Vector3::zAxis()); + + /* Direction is parallel to Z axis, special rotation case */ + if(Math::abs(dot) > 1.0f - Math::TypeTraits::epsilon()) { + rotation.up() = dot*Vector3::zAxis(); + rotation.right() = Vector3::xAxis(); + rotation.backward() = -dot*Vector3::yAxis(); + + /* Common case */ + } else { + rotation.up() = directionNormalized; + rotation.right() = Vector3::cross(rotation.up(), Vector3::zAxis()).normalized(); + rotation.backward() = Vector3::cross(rotation.right(), rotation.up()); + CORRADE_INTERNAL_ASSERT(rotation.up().isNormalized() && rotation.backward().isNormalized()); + } + + capDistance = directionNormalized*radius; + } + + /* Scaling and translation of all parts */ + const auto rotationScaling = rotation*Matrix4::scaling(Vector3(radius)); + return {{ + Matrix4::translation(a+capDistance)*rotationScaling, + Matrix4::translation(0.5f*(a + b))*rotation*Matrix4::scaling({radius, length, radius}), + Matrix4::translation(b-capDistance)*rotationScaling + }}; +} + +}}} + +#endif diff --git a/src/DebugTools/Implementation/CylinderRenderer.cpp b/src/DebugTools/Implementation/CylinderRenderer.cpp new file mode 100644 index 000000000..f7cfbd1e2 --- /dev/null +++ b/src/DebugTools/Implementation/CylinderRenderer.cpp @@ -0,0 +1,61 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013 Vladimír VondruÅ¡ + + 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. +*/ + +#include "CylinderRenderer.h" + +#include "Mesh.h" +#include "DebugTools/ShapeRenderer.h" +#include "Shapes/Cylinder.h" +#include "Primitives/Cylinder.h" +#include "Primitives/Square.h" +#include "Shaders/Flat.h" +#include "Trade/MeshData2D.h" +#include "Trade/MeshData3D.h" + +#include "DebugTools/Implementation/CylinderRendererTransformation.h" + +namespace Magnum { namespace DebugTools { namespace Implementation { + +AbstractCylinderRenderer<2>::AbstractCylinderRenderer(): AbstractShapeRenderer<2>("cylinder2d", "cylinder2d-vertices", {}) { + if(!wireframeMesh) createResources(Primitives::Square::wireframe()); +} + +AbstractCylinderRenderer<3>::AbstractCylinderRenderer(): AbstractShapeRenderer<3>("cylinder3d", "cylinder3d-vertices", "cylinder3d-indices") { + if(!wireframeMesh) createResources(Primitives::Cylinder::wireframe(1, 40, 1.0f)); +} + +template CylinderRenderer::CylinderRenderer(const Shapes::Implementation::AbstractShape& cylinder): cylinder(static_cast>&>(cylinder).shape) {} + +template void CylinderRenderer::draw(Resource& options, const typename DimensionTraits::MatrixType& projectionMatrix) { + AbstractShapeRenderer::wireframeShader->setTransformationProjectionMatrix(projectionMatrix* + Implementation::cylinderRendererTransformation(cylinder.a(), cylinder.b(), cylinder.radius())) + .setColor(options->color()) + .use(); + AbstractShapeRenderer::wireframeMesh->draw(); +} + +template class CylinderRenderer<2>; +template class CylinderRenderer<3>; + +}}} diff --git a/src/DebugTools/Implementation/CylinderRenderer.h b/src/DebugTools/Implementation/CylinderRenderer.h new file mode 100644 index 000000000..d5a511254 --- /dev/null +++ b/src/DebugTools/Implementation/CylinderRenderer.h @@ -0,0 +1,60 @@ +#ifndef Magnum_DebugTools_Implementation_CylinderRenderer_h +#define Magnum_DebugTools_Implementation_CylinderRenderer_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013 Vladimír VondruÅ¡ + + 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. +*/ + +#include "AbstractShapeRenderer.h" + +#include "Shapes/Shapes.h" + +#include "corradeCompatibility.h" + +namespace Magnum { namespace DebugTools { namespace Implementation { + +template class AbstractCylinderRenderer; + +template<> class AbstractCylinderRenderer<2>: public AbstractShapeRenderer<2> { + public: + explicit AbstractCylinderRenderer(); +}; + +template<> class AbstractCylinderRenderer<3>: public AbstractShapeRenderer<3> { + public: + explicit AbstractCylinderRenderer(); +}; + +template class CylinderRenderer: public AbstractCylinderRenderer { + public: + explicit CylinderRenderer(const Shapes::Implementation::AbstractShape& cylinder); + CylinderRenderer(const Shapes::Implementation::AbstractShape&&) = delete; + + void draw(Resource& options, const typename DimensionTraits::MatrixType& projectionMatrix) override; + + private: + const Shapes::Cylinder& cylinder; +}; + +}}} + +#endif diff --git a/src/DebugTools/Implementation/CylinderRendererTransformation.h b/src/DebugTools/Implementation/CylinderRendererTransformation.h new file mode 100644 index 000000000..4caf8c5d6 --- /dev/null +++ b/src/DebugTools/Implementation/CylinderRendererTransformation.h @@ -0,0 +1,88 @@ +#ifndef Magnum_DebugTools_Implementation_ForceRendererTransformation_h +#define Magnum_DebugTools_Implementation_ForceRendererTransformation_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013 Vladimír VondruÅ¡ + + 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. +*/ + +#include "Math/Functions.h" +#include "Math/Matrix3.h" +#include "Math/Matrix4.h" +#include "Magnum.h" +#include "DimensionTraits.h" + +namespace Magnum { namespace DebugTools { namespace Implementation { + +template typename DimensionTraits::MatrixType cylinderRendererTransformation(const typename DimensionTraits::VectorType& a, const typename DimensionTraits::VectorType& b, Float radius); + +template<> Matrix3 cylinderRendererTransformation<2>(const Vector2& a, const Vector2& b, const Float radius) { + /* Vector from cylinder center to top hemisphere center */ + const Vector2 direction = 0.5f*(b - a); + const Float length = direction.length(); + + /* Capsule rotation and distance to caps after they are scaled to proper + radius (if nonzero cylinder length) */ + Matrix3 rotation; + if(length >= Math::TypeTraits::epsilon()) { + rotation.up() = direction/length; + rotation.right() = rotation.up().perpendicular(); + CORRADE_INTERNAL_ASSERT(rotation.right().isNormalized()); + } + + /* Scaling and translation */ + return Matrix3::translation(0.5f*(a + b))*rotation*Matrix3::scaling({radius, length}); +} + +template<> Matrix4 cylinderRendererTransformation<3>(const Vector3& a, const Vector3& b, const Float radius) { + /* Vector from cylinder center to top hemisphere center */ + const Vector3 direction = 0.5f*(b - a); + const Float length = direction.length(); + + /* Capsule rotation and distance to caps after they are scaled to proper + radius (if nonzero cylinder length) */ + Matrix4 rotation; + if(length >= Math::TypeTraits::epsilon()) { + const Vector3 directionNormalized = direction/length; + const Float dot = Vector3::dot(directionNormalized, Vector3::zAxis()); + + /* Direction is parallel to Z axis, special rotation case */ + if(Math::abs(dot) > 1.0f - Math::TypeTraits::epsilon()) { + rotation.up() = dot*Vector3::zAxis(); + rotation.right() = Vector3::xAxis(); + rotation.backward() = -dot*Vector3::yAxis(); + + /* Common case */ + } else { + rotation.up() = directionNormalized; + rotation.right() = Vector3::cross(rotation.up(), Vector3::zAxis()).normalized(); + rotation.backward() = Vector3::cross(rotation.right(), rotation.up()); + CORRADE_INTERNAL_ASSERT(rotation.up().isNormalized() && rotation.backward().isNormalized()); + } + } + + /* Scaling and translation */ + return Matrix4::translation(0.5f*(a + b))*rotation*Matrix4::scaling({radius, length, radius}); +} + +}}} + +#endif diff --git a/src/DebugTools/Implementation/ForceRendererTransformation.h b/src/DebugTools/Implementation/ForceRendererTransformation.h index 83879104c..8674cbafa 100644 --- a/src/DebugTools/Implementation/ForceRendererTransformation.h +++ b/src/DebugTools/Implementation/ForceRendererTransformation.h @@ -24,6 +24,7 @@ DEALINGS IN THE SOFTWARE. */ +#include "Math/Functions.h" #include "Math/Matrix3.h" #include "Math/Matrix4.h" #include "Magnum.h" @@ -34,7 +35,7 @@ namespace Magnum { namespace DebugTools { namespace Implementation { template typename DimensionTraits::MatrixType forceRendererTransformation(const typename DimensionTraits::VectorType& forcePosition, const typename DimensionTraits::VectorType& force); template<> inline Matrix3 forceRendererTransformation<2>(const Vector2& forcePosition, const Vector2& force) { - return Matrix3::from({force, Vector2(-force.y(), force.x())}, forcePosition); + return Matrix3::from({force, force.perpendicular()}, forcePosition); } template<> Matrix4 forceRendererTransformation<3>(const Vector3& forcePosition, const Vector3& force) { @@ -48,18 +49,15 @@ template<> Matrix4 forceRendererTransformation<3>(const Vector3& forcePosition, const Float dot = Vector3::dot(force/forceLength, Vector3::xAxis()); /* Force is parallel to X axis, just scaling */ - if(dot > 1.0f - Math::TypeTraits::epsilon()) - return translation*Matrix4::scaling(Vector3(forceLength)); - - /* Force is antiparallel to X axis, scaling inverted on X */ - if(-dot > 1.0f - Math::TypeTraits::epsilon()) - return translation*Matrix4::scaling({-forceLength, forceLength, forceLength}); + if(Math::abs(dot) > 1.0f - Math::TypeTraits::epsilon()) + return translation*Matrix4::scaling({Math::sign(dot)*forceLength, forceLength, forceLength}); /* Normal of plane going through force vector and X axis vector */ const Vector3 normal = Vector3::cross(Vector3::xAxis(), force).normalized(); /* Third base vector, orthogonal to force and normal */ - const Vector3 binormal = Vector3::cross(normal, force).normalized(); + const Vector3 binormal = Vector3::cross(normal, force/forceLength); + CORRADE_INTERNAL_ASSERT(binormal.isNormalized()); /* Transformation matrix from scaled base vectors and translation vector */ return Matrix4::from({force, normal*forceLength, binormal*forceLength}, forcePosition); diff --git a/src/DebugTools/Implementation/SphereRenderer.cpp b/src/DebugTools/Implementation/SphereRenderer.cpp index 9a6815738..59362b226 100644 --- a/src/DebugTools/Implementation/SphereRenderer.cpp +++ b/src/DebugTools/Implementation/SphereRenderer.cpp @@ -40,7 +40,7 @@ AbstractSphereRenderer<2>::AbstractSphereRenderer(): AbstractShapeRenderer<2>("s } AbstractSphereRenderer<3>::AbstractSphereRenderer(): AbstractShapeRenderer<3>("sphere3d", "sphere3d-vertices", "sphere3d-indices") { - if(!wireframeMesh) createResources(Primitives::UVSphere::wireframe(40, 20)); + if(!wireframeMesh) createResources(Primitives::UVSphere::wireframe(20, 40)); } template SphereRenderer::SphereRenderer(const Shapes::Implementation::AbstractShape& sphere): sphere(static_cast>&>(sphere).shape) {} diff --git a/src/DebugTools/ResourceManager.cpp b/src/DebugTools/ResourceManager.cpp index 204c28357..6b385af7d 100644 --- a/src/DebugTools/ResourceManager.cpp +++ b/src/DebugTools/ResourceManager.cpp @@ -28,13 +28,14 @@ #include "Buffer.h" #include "Mesh.h" +#include "MeshView.h" #include "DebugTools/ForceRenderer.h" #include "DebugTools/ObjectRenderer.h" #include "DebugTools/ShapeRenderer.h" namespace Magnum { -template class ResourceManager; +template class ResourceManager; namespace DebugTools { diff --git a/src/DebugTools/ResourceManager.h b/src/DebugTools/ResourceManager.h index ccf907718..f5205e044 100644 --- a/src/DebugTools/ResourceManager.h +++ b/src/DebugTools/ResourceManager.h @@ -43,7 +43,9 @@ namespace Magnum { -extern template ResourceManager MAGNUM_DEBUGTOOLS_EXPORT *& ResourceManager::internalInstance(); +/** @todo Do the listing in one place, not five thousand! */ + +extern template ResourceManager MAGNUM_DEBUGTOOLS_EXPORT *& ResourceManager::internalInstance(); namespace DebugTools { @@ -53,7 +55,7 @@ namespace DebugTools { Stores various data used by debug renderers. See @ref debug-tools for more information. */ -class MAGNUM_DEBUGTOOLS_EXPORT ResourceManager: public Magnum::ResourceManager { +class MAGNUM_DEBUGTOOLS_EXPORT ResourceManager: public Magnum::ResourceManager { public: explicit ResourceManager(); ~ResourceManager(); diff --git a/src/DebugTools/ShapeRenderer.cpp b/src/DebugTools/ShapeRenderer.cpp index 91223c153..ee31c2626 100644 --- a/src/DebugTools/ShapeRenderer.cpp +++ b/src/DebugTools/ShapeRenderer.cpp @@ -31,6 +31,8 @@ #include "Implementation/AxisAlignedBoxRenderer.h" #include "Implementation/BoxRenderer.h" +#include "Implementation/CapsuleRenderer.h" +#include "Implementation/CylinderRenderer.h" #include "Implementation/LineSegmentRenderer.h" #include "Implementation/PointRenderer.h" #include "Implementation/SphereRenderer.h" @@ -56,6 +58,12 @@ template<> void createDebugMesh(ShapeRenderer<2>& renderer, const Shapes::Implem case Shapes::AbstractShape2D::Type::Sphere: renderer.renderers.push_back(new Implementation::SphereRenderer<2>(shape)); break; + case Shapes::AbstractShape2D::Type::Capsule: + renderer.renderers.push_back(new Implementation::CapsuleRenderer<2>(shape)); + break; + case Shapes::AbstractShape2D::Type::Cylinder: + renderer.renderers.push_back(new Implementation::CylinderRenderer<2>(shape)); + break; case Shapes::AbstractShape2D::Type::Composition: { const Shapes::Composition2D& composition = static_cast&>(shape).shape; @@ -84,6 +92,12 @@ template<> void createDebugMesh(ShapeRenderer<3>& renderer, const Shapes::Implem case Shapes::AbstractShape3D::Type::Sphere: renderer.renderers.push_back(new Implementation::SphereRenderer<3>(shape)); break; + case Shapes::AbstractShape3D::Type::Capsule: + renderer.renderers.push_back(new Implementation::CapsuleRenderer<3>(shape)); + break; + case Shapes::AbstractShape3D::Type::Cylinder: + renderer.renderers.push_back(new Implementation::CylinderRenderer<3>(shape)); + break; case Shapes::AbstractShape3D::Type::Composition: { const Shapes::Composition3D& composition = static_cast&>(shape).shape; diff --git a/src/DebugTools/Test/CMakeLists.txt b/src/DebugTools/Test/CMakeLists.txt index 1e945d0c0..35fca1640 100644 --- a/src/DebugTools/Test/CMakeLists.txt +++ b/src/DebugTools/Test/CMakeLists.txt @@ -22,5 +22,7 @@ # DEALINGS IN THE SOFTWARE. # +corrade_add_test(DebugToolsCapsuleRendererTest CapsuleRendererTest.cpp LIBRARIES MagnumMathTestLib) +corrade_add_test(DebugToolsCylinderRendererTest CylinderRendererTest.cpp LIBRARIES MagnumMathTestLib) corrade_add_test(DebugToolsForceRendererTest ForceRendererTest.cpp LIBRARIES MagnumMathTestLib) corrade_add_test(DebugToolsLineSegmentRendererTest LineSegmentRendererTest.cpp LIBRARIES MagnumMathTestLib) diff --git a/src/DebugTools/Test/CapsuleRendererTest.cpp b/src/DebugTools/Test/CapsuleRendererTest.cpp new file mode 100644 index 000000000..4c0f7d762 --- /dev/null +++ b/src/DebugTools/Test/CapsuleRendererTest.cpp @@ -0,0 +1,177 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013 Vladimír VondruÅ¡ + + 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. +*/ + +#include + +#include "DebugTools/Implementation/CapsuleRendererTransformation.h" + +namespace Magnum { namespace DebugTools { namespace Test { + +class CapsuleRendererTest: public TestSuite::Tester { + public: + explicit CapsuleRendererTest(); + + void zeroLength2D(); + void common2D(); + + void zeroLength3D(); + void parallel3D(); + void antiParallel3D(); + void common3D(); +}; + +CapsuleRendererTest::CapsuleRendererTest() { + addTests({&CapsuleRendererTest::zeroLength2D, + &CapsuleRendererTest::common2D, + + &CapsuleRendererTest::zeroLength3D, + &CapsuleRendererTest::parallel3D, + &CapsuleRendererTest::antiParallel3D, + &CapsuleRendererTest::common3D}); +} + +void CapsuleRendererTest::zeroLength2D() { + const Vector2 a(0.5f, 3.0f); + std::array transformation = Implementation::capsuleRendererTransformation<2>(a, a, 3.5f); + + const auto scaling = Math::Matrix<2, Float>::fromDiagonal(Vector2(3.5f)); + CORRADE_COMPARE(transformation[0].rotationScaling(), scaling); + CORRADE_COMPARE(transformation[1].rotationScaling(), (Math::Matrix<2, Float>::fromDiagonal({3.5f, 0.0f}))); + CORRADE_COMPARE(transformation[2].rotationScaling(), scaling); + + CORRADE_COMPARE(transformation[0].translation(), a); + CORRADE_COMPARE(transformation[1].translation(), a); + CORRADE_COMPARE(transformation[2].translation(), a); +} + +void CapsuleRendererTest::common2D() { + const Vector2 a(0.5f, 3.0f); + const Vector2 b(7.5f, -1.0f); + std::array transformation = Implementation::capsuleRendererTransformation<2>(a, b, 3.5f); + + /* Vector from capsule center to top hemisphere center */ + const Vector2 up(3.5f, -2.0f); + CORRADE_COMPARE(transformation[0].up(), up.resized(3.5f)); + CORRADE_COMPARE(transformation[1].up(), up); + CORRADE_COMPARE(transformation[2].up(), up.resized(3.5f)); + + const auto right = Vector2(4.0f, 7.0f).resized(3.5f); + CORRADE_COMPARE(transformation[0].right(), right); + CORRADE_COMPARE(transformation[1].right(), right); + CORRADE_COMPARE(transformation[2].right(), right); + + /* Orthogonality */ + CORRADE_COMPARE(Vector2::dot(transformation[0].up(), transformation[0].right()), 0.0f); + + const Vector2 capDistance = up.resized(3.5f); + CORRADE_COMPARE(transformation[0].translation(), a+capDistance); + CORRADE_COMPARE(transformation[1].translation(), 0.5f*(a + b)); + CORRADE_COMPARE(transformation[2].translation(), b-capDistance); +} + +void CapsuleRendererTest::zeroLength3D() { + const Vector3 a(0.5f, 3.0f, 7.0f); + std::array transformation = Implementation::capsuleRendererTransformation<3>(a, a, 3.5f); + + const auto scaling = Math::Matrix<3, Float>::fromDiagonal(Vector3(3.5f)); + CORRADE_COMPARE(transformation[0].rotationScaling(), scaling); + CORRADE_COMPARE(transformation[1].rotationScaling(), (Math::Matrix<3, Float>::fromDiagonal({3.5f, 0.0f, 3.5f}))); + CORRADE_COMPARE(transformation[2].rotationScaling(), scaling); + + CORRADE_COMPARE(transformation[0].translation(), a); + CORRADE_COMPARE(transformation[1].translation(), a); + CORRADE_COMPARE(transformation[2].translation(), a); +} + +void CapsuleRendererTest::parallel3D() { + const Vector3 a(0.5f, 3.0f, 7.0f); + const Vector3 b(0.5f, 3.0f, 11.0f); + std::array transformation = Implementation::capsuleRendererTransformation<3>(a, b, 3.5f); + + const auto rotation = Matrix4::rotationX(Deg(90.0f)); + const auto scaling = (rotation*Matrix4::scaling(Vector3(3.5f))).rotationScaling(); + CORRADE_COMPARE(transformation[0].rotationScaling(), scaling); + CORRADE_COMPARE(transformation[1].rotationScaling(), + (rotation*Matrix4::scaling({3.5f, 2.0f, 3.5f})).rotationScaling()); + CORRADE_COMPARE(transformation[2].rotationScaling(), scaling); + + const auto capDistance = Vector3::zAxis(3.5f); + CORRADE_COMPARE(transformation[0].translation(), a+capDistance); + CORRADE_COMPARE(transformation[1].translation(), a+Vector3::zAxis(2.0f)); + CORRADE_COMPARE(transformation[2].translation(), b-capDistance); +} + +void CapsuleRendererTest::antiParallel3D() { + const Vector3 a(0.5f, 3.0f, 7.0f); + const Vector3 b(0.5f, 3.0f, 3.0f); + std::array transformation = Implementation::capsuleRendererTransformation<3>(a, b, 3.5f); + + const auto rotation = Matrix4::rotationX(-Deg(90.0f)); + const auto rotationScaling = (rotation*Matrix4::scaling(Vector3(3.5f))).rotationScaling(); + CORRADE_COMPARE(transformation[0].rotationScaling(), rotationScaling); + CORRADE_COMPARE(transformation[1].rotationScaling(), + (rotation*Matrix4::scaling({3.5f, 2.0f, 3.5f})).rotationScaling()); + CORRADE_COMPARE(transformation[2].rotationScaling(), rotationScaling); + + const auto capDistance = Vector3::zAxis(-3.5f); + CORRADE_COMPARE(transformation[0].translation(), a+capDistance); + CORRADE_COMPARE(transformation[1].translation(), a+Vector3::zAxis(-2.0f)); + CORRADE_COMPARE(transformation[2].translation(), b-capDistance); +} + +void CapsuleRendererTest::common3D() { + const Vector3 a(0.5f, 3.0f, 7.0f); + const Vector3 b(7.5f, -1.0f, 1.5f); + std::array transformation = Implementation::capsuleRendererTransformation<3>(a, b, 3.5f); + + /* Vector from capsule center to top hemisphere center */ + const Vector3 up(3.5f, -2.0f, -2.75f); + CORRADE_COMPARE(transformation[0].up(), up.resized(3.5f)); + CORRADE_COMPARE(transformation[1].up(), up); + CORRADE_COMPARE(transformation[2].up(), up.resized(3.5f)); + + const auto right = Vector3(-2.0f, -3.5f, 0.0f).resized(3.5f); + CORRADE_COMPARE(transformation[0].right(), right); + CORRADE_COMPARE(transformation[1].right(), right); + CORRADE_COMPARE(transformation[2].right(), right); + + const auto backward = Vector3(9.625f, -5.5f, 16.25f).resized(3.5f); + CORRADE_COMPARE(transformation[0].backward(), backward); + CORRADE_COMPARE(transformation[1].backward(), backward); + CORRADE_COMPARE(transformation[2].backward(), backward); + + /* Orthogonality */ + CORRADE_COMPARE(Vector3::dot(transformation[0].up(), transformation[0].right()), 0.0f); + CORRADE_COMPARE(Vector3::dot(transformation[0].up(), transformation[0].backward()), 0.0f); + CORRADE_COMPARE(Vector3::dot(transformation[0].right(), transformation[0].backward()), 0.0f); + + const Vector3 capDistance = up.resized(3.5f); + CORRADE_COMPARE(transformation[0].translation(), a+capDistance); + CORRADE_COMPARE(transformation[1].translation(), 0.5f*(a + b)); + CORRADE_COMPARE(transformation[2].translation(), b-capDistance); +} + +}}} + +CORRADE_TEST_MAIN(Magnum::DebugTools::Test::CapsuleRendererTest) diff --git a/src/DebugTools/Test/CylinderRendererTest.cpp b/src/DebugTools/Test/CylinderRendererTest.cpp new file mode 100644 index 000000000..958003f96 --- /dev/null +++ b/src/DebugTools/Test/CylinderRendererTest.cpp @@ -0,0 +1,125 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013 Vladimír VondruÅ¡ + + 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. +*/ + +#include + +#include "DebugTools/Implementation/CylinderRendererTransformation.h" + +namespace Magnum { namespace DebugTools { namespace Test { + +class CylinderRendererTest: public TestSuite::Tester { + public: + explicit CylinderRendererTest(); + + void zeroLength2D(); + void common2D(); + + void zeroLength3D(); + void parallel3D(); + void antiParallel3D(); + void common3D(); +}; + +CylinderRendererTest::CylinderRendererTest() { + addTests({&CylinderRendererTest::zeroLength2D, + &CylinderRendererTest::common2D, + + &CylinderRendererTest::zeroLength3D, + &CylinderRendererTest::parallel3D, + &CylinderRendererTest::antiParallel3D, + &CylinderRendererTest::common3D}); +} + +void CylinderRendererTest::zeroLength2D() { + const Vector2 a(0.5f, 3.0f); + const Matrix3 transformation = Implementation::cylinderRendererTransformation<2>(a, a, 3.5f); + + CORRADE_COMPARE(transformation.rotationScaling(), (Math::Matrix<2, Float>::fromDiagonal({3.5f, 0.0f}))); + CORRADE_COMPARE(transformation.translation(), a); +} + +void CylinderRendererTest::common2D() { + const Vector2 a(0.5f, 3.0f); + const Vector2 b(7.5f, -1.0f); + const Matrix3 transformation = Implementation::cylinderRendererTransformation<2>(a, b, 3.5f); + + /* Rotation + scaling, test orthogonality */ + CORRADE_COMPARE(transformation.up(), Vector2(3.5f, -2.0f)); + CORRADE_COMPARE(transformation.right(), Vector2(4.0f, 7.0f).resized(3.5f)); + CORRADE_COMPARE(Vector2::dot(transformation.up(), transformation.right()), 0.0f); + + CORRADE_COMPARE(transformation.translation(), 0.5f*(a + b)); +} + +void CylinderRendererTest::zeroLength3D() { + const Vector3 a(0.5f, 3.0f, 7.0f); + const Matrix4 transformation = Implementation::cylinderRendererTransformation<3>(a, a, 3.5f); + + CORRADE_COMPARE(transformation.rotationScaling(), (Math::Matrix<3, Float>::fromDiagonal({3.5f, 0.0f, 3.5f}))); + CORRADE_COMPARE(transformation.translation(), a); +} + +void CylinderRendererTest::parallel3D() { + const Vector3 a(0.5f, 3.0f, 7.0f); + const Vector3 b(0.5f, 3.0f, 11.0f); + const Matrix4 transformation = Implementation::cylinderRendererTransformation<3>(a, b, 3.5f); + + CORRADE_COMPARE(transformation.rotationScaling(), + (Matrix4::rotationX(Deg(90.0f))*Matrix4::scaling({3.5f, 2.0f, 3.5f})).rotationScaling()); + + CORRADE_COMPARE(transformation.translation(), a+Vector3::zAxis(2.0f)); +} + +void CylinderRendererTest::antiParallel3D() { + const Vector3 a(0.5f, 3.0f, 7.0f); + const Vector3 b(0.5f, 3.0f, 3.0f); + const Matrix4 transformation = Implementation::cylinderRendererTransformation<3>(a, b, 3.5f); + + CORRADE_COMPARE(transformation.rotationScaling(), + (Matrix4::rotationX(-Deg(90.0f))*Matrix4::scaling({3.5f, 2.0f, 3.5f})).rotationScaling()); + + CORRADE_COMPARE(transformation.translation(), a+Vector3::zAxis(-2.0f)); +} + +void CylinderRendererTest::common3D() { + const Vector3 a(0.5f, 3.0f, 7.0f); + const Vector3 b(7.5f, -1.0f, 1.5f); + const Matrix4 transformation = Implementation::cylinderRendererTransformation<3>(a, b, 3.5f); + + /* Rotation + scaling */ + CORRADE_COMPARE(transformation.up(), Vector3(3.5f, -2.0f, -2.75f)); + CORRADE_COMPARE(transformation.right(), Vector3(-2.0f, -3.5f, 0.0f).resized(3.5f)); + CORRADE_COMPARE(transformation.backward(), Vector3(9.625f, -5.5f, 16.25f).resized(3.5f)); + + /* Orthogonality */ + CORRADE_COMPARE(Vector3::dot(transformation.up(), transformation.right()), 0.0f); + CORRADE_COMPARE(Vector3::dot(transformation.up(), transformation.backward()), 0.0f); + CORRADE_COMPARE(Vector3::dot(transformation.right(), transformation.backward()), 0.0f); + + CORRADE_COMPARE(transformation.translation(), 0.5f*(a + b)); +} + +}}} + +CORRADE_TEST_MAIN(Magnum::DebugTools::Test::CylinderRendererTest) diff --git a/src/DebugTools/Test/ForceRendererTest.cpp b/src/DebugTools/Test/ForceRendererTest.cpp index e02d3ce7d..d560e8e38 100644 --- a/src/DebugTools/Test/ForceRendererTest.cpp +++ b/src/DebugTools/Test/ForceRendererTest.cpp @@ -33,9 +33,7 @@ class ForceRendererTest: public TestSuite::Tester { explicit ForceRendererTest(); void zero2D(); - void parallel2D(); - void antiParallel2D(); - void arbitrary2D(); + void common2D(); void zero3D(); void parallel3D(); @@ -45,9 +43,7 @@ class ForceRendererTest: public TestSuite::Tester { ForceRendererTest::ForceRendererTest() { addTests({&ForceRendererTest::zero2D, - &ForceRendererTest::parallel2D, - &ForceRendererTest::antiParallel2D, - &ForceRendererTest::arbitrary2D, + &ForceRendererTest::common2D, &ForceRendererTest::zero3D, &ForceRendererTest::parallel3D, @@ -60,17 +56,7 @@ void ForceRendererTest::zero2D() { Matrix3::translation({0.5f, -3.0f})*Matrix3::scaling(Vector2(0.0f))); } -void ForceRendererTest::parallel2D() { - CORRADE_COMPARE(Implementation::forceRendererTransformation<2>({0.5f, -3.0f}, Vector2::xAxis(2.5f)), - Matrix3::translation({0.5f, -3.0f})*Matrix3::scaling(Vector2(2.5f))); -} - -void ForceRendererTest::antiParallel2D() { - CORRADE_COMPARE(Implementation::forceRendererTransformation<2>({0.5f, -3.0f}, Vector2::xAxis(-2.5f)), - Matrix3::translation({0.5f, -3.0f})*Matrix3::scaling(Vector2(-2.5f))); -} - -void ForceRendererTest::arbitrary2D() { +void ForceRendererTest::common2D() { Vector2 force(2.7f, -11.5f); Matrix3 m = Implementation::forceRendererTransformation<2>({0.5f, -3.0f}, force); @@ -81,7 +67,7 @@ void ForceRendererTest::arbitrary2D() { /* All vectors have the same length */ CORRADE_COMPARE(m.up().length(), force.length()); - /* All vectors are parallel */ + /* All vectors are orthogonal */ CORRADE_COMPARE(Vector2::dot(m.right(), m.up()), 0.0f); } @@ -112,10 +98,11 @@ void ForceRendererTest::arbitrary3D() { CORRADE_COMPARE(m.up().length(), force.length()); CORRADE_COMPARE(m.backward().length(), force.length()); - /* All vectors are parallel */ + /* All vectors are orthogonal */ CORRADE_COMPARE(Vector3::dot(m.right(), m.up()), 0.0f); CORRADE_COMPARE(Vector3::dot(m.right(), m.backward()), 0.0f); - CORRADE_COMPARE(Vector3::dot(m.up(), m.backward()), 0.0f); + /** @todo This shouldn't be too different */ + CORRADE_COMPARE(Vector3::dot(m.up(), m.backward()), -Math::TypeTraits::epsilon()); } }}}} diff --git a/src/Framebuffer.h b/src/Framebuffer.h index 8f79517b7..3da23479c 100644 --- a/src/Framebuffer.h +++ b/src/Framebuffer.h @@ -161,7 +161,7 @@ class MAGNUM_EXPORT Framebuffer: public AbstractFramebuffer { * @see attachRenderbuffer(), attachTexture1D(), attachTexture2D(), * attachCubeMapTexture(), attachTexture3D() */ - class BufferAttachment { + class MAGNUM_EXPORT BufferAttachment { public: /** @brief Depth buffer */ static const BufferAttachment Depth; diff --git a/src/Magnum.h b/src/Magnum.h index c79918592..8aea39d19 100644 --- a/src/Magnum.h +++ b/src/Magnum.h @@ -392,6 +392,7 @@ typedef ImageReference<2> ImageReference2D; typedef ImageReference<3> ImageReference3D; class Mesh; +class MeshView; /* AbstractQuery is not used directly */ class PrimitiveQuery; diff --git a/src/Math/Matrix3.h b/src/Math/Matrix3.h index c0c3510cf..07f2c204a 100644 --- a/src/Math/Matrix3.h +++ b/src/Math/Matrix3.h @@ -168,7 +168,7 @@ template class Matrix3: public Matrix<3, T> { * * Upper-left 2x2 part of the matrix. * @see from(const Matrix<2, T>&, const Vector2&), rotation() const - * rotationNormalized(), rotation(T), + * rotationNormalized(), @ref uniformScaling(), rotation(T), * Matrix4::rotationScaling() const */ constexpr Matrix<2, T> rotationScaling() const { @@ -181,7 +181,8 @@ template class Matrix3: public Matrix<3, T> { * * Similar to @ref rotationScaling(), but additionally checks that the * base vectors are normalized. - * @see rotation() const, @ref Matrix4::rotationNormalized() + * @see rotation() const, @ref uniformScaling(), + * @ref Matrix4::rotationNormalized() * @todo assert also orthogonality or this is good enough? */ Matrix<2, T> rotationNormalized() const { @@ -194,17 +195,47 @@ template class Matrix3: public Matrix<3, T> { /** * @brief 2D rotation part of the matrix * - * Normalized upper-left 2x2 part of the matrix. - * @see rotationNormalized(), rotationScaling() const, rotation(T), - * Matrix4::rotation() const - * @todo assert uniform scaling (otherwise this would be garbage) + * Normalized upper-left 2x2 part of the matrix. Expects uniform + * scaling. + * @see rotationNormalized(), rotationScaling(), @ref uniformScaling(), + * rotation(T), Matrix4::rotation() const */ Matrix<2, T> rotation() const { + CORRADE_ASSERT(TypeTraits::equals((*this)[0].xy().dot(), (*this)[1].xy().dot()), + "Math::Matrix3::rotation(): the matrix doesn't have uniform scaling", {}); return {(*this)[0].xy().normalized(), (*this)[1].xy().normalized()}; } - /** @todo uniform scaling extraction */ + /** + * @brief Uniform scaling part of the matrix, squared + * + * Squared length of vectors in upper-left 2x2 part of the matrix. + * Expects that the scaling is the same in all axes. Faster alternative + * to @ref uniformScaling(), because it doesn't compute the square + * root. + * @see @ref rotationScaling(), @ref rotation(), + * @ref rotationNormalized(), @ref scaling(const Vector2&), + * @ref Matrix4::uniformScaling() + */ + T uniformScalingSquared() const { + const T scalingSquared = (*this)[0].xy().dot(); + CORRADE_ASSERT(TypeTraits::equals((*this)[1].xy().dot(), scalingSquared), + "Math::Matrix3::uniformScaling(): the matrix doesn't have uniform scaling", {}); + return scalingSquared; + } + + /** + * @brief Uniform scaling part of the matrix + * + * Length of vectors in upper-left 2x2 part of the matrix. Expects that + * the scaling is the same in all axes. Use faster alternative + * @ref uniformScalingSquared() where possible. + * @see @ref rotationScaling(), @ref rotation(), + * @ref rotationNormalized(), @ref scaling(const Vector2&), + * @ref Matrix4::uniformScaling() + */ + T uniformScaling() const { return std::sqrt(uniformScalingSquared()); } /** * @brief Right-pointing 2D vector diff --git a/src/Math/Matrix4.h b/src/Math/Matrix4.h index 6e7fbe7f7..872cfffee 100644 --- a/src/Math/Matrix4.h +++ b/src/Math/Matrix4.h @@ -231,8 +231,8 @@ template class Matrix4: public Matrix<4, T> { * * Upper-left 3x3 part of the matrix. * @see from(const Matrix<3, T>&, const Vector3&), rotation() const, - * rotationNormalized(), rotation(T, const Vector3&), - * Matrix3::rotationScaling() const + * rotationNormalized(), @ref uniformScaling(), + * rotation(T, const Vector3&), Matrix3::rotationScaling() const */ /* Not Matrix3, because it is for affine 2D transformations */ constexpr Matrix<3, T> rotationScaling() const { @@ -246,7 +246,8 @@ template class Matrix4: public Matrix<4, T> { * * Similar to @ref rotationScaling(), but additionally checks that the * base vectors are normalized. - * @see rotation() const, @ref Matrix3::rotationNormalized() + * @see rotation() const, @ref uniformScaling(), + * @ref Matrix3::rotationNormalized() * @todo assert also orthogonality or this is good enough? */ /* Not Matrix3, because it is for affine 2D transformations */ @@ -261,15 +262,39 @@ template class Matrix4: public Matrix<4, T> { /** * @brief 3D rotation part of the matrix * - * Normalized upper-left 3x3 part of the matrix. + * Normalized upper-left 3x3 part of the matrix. Expects uniform + * scaling. * @see rotationNormalized(), rotationScaling() const, - * rotation(T, const Vector3&), Matrix3::rotation() const - * @todo assert uniform scaling (otherwise this would be garbage) + * @ref uniformScaling(), rotation(T, const Vector3&), + * Matrix3::rotation() const */ /* Not Matrix3, because it is for affine 2D transformations */ Matrix<3, T> rotation() const; - /** @todo uniform scaling extraction */ + /** + * @brief Uniform scaling part of the matrix, squared + * + * Squared length of vectors in upper-left 3x3 part of the matrix. + * Expects that the scaling is the same in all axes. Faster alternative + * to @ref uniformScaling(), because it doesn't compute the square + * root. + * @see @ref rotationScaling(), @ref rotation(), + * @ref rotationNormalized(), @ref scaling(const Vector3&), + * @ref Matrix3::uniformScaling() + */ + T uniformScalingSquared() const; + + /** + * @brief Uniform scaling part of the matrix + * + * Length of vectors in upper-left 3x3 part of the matrix. Expects that + * the scaling is the same in all axes. Use faster alternative + * @ref uniformScalingSquared() where possible. + * @see @ref rotationScaling(), @ref rotation(), + * @ref rotationNormalized(), @ref scaling(const Vector3&), + * @ref Matrix3::uniformScaling() + */ + T uniformScaling() const { return std::sqrt(uniformScalingSquared()); } /** * @brief Right-pointing 3D vector @@ -457,11 +482,22 @@ template Matrix4 Matrix4::perspectiveProjection(const Vector2& } template inline Matrix<3, T> Matrix4::rotation() const { + CORRADE_ASSERT(TypeTraits::equals((*this)[0].xyz().dot(), (*this)[1].xyz().dot()) && + TypeTraits::equals((*this)[1].xyz().dot(), (*this)[2].xyz().dot()), + "Math::Matrix4::rotation(): the matrix doesn't have uniform scaling", {}); return {(*this)[0].xyz().normalized(), (*this)[1].xyz().normalized(), (*this)[2].xyz().normalized()}; } +template T Matrix4::uniformScalingSquared() const { + const T scalingSquared = (*this)[0].xyz().dot(); + CORRADE_ASSERT(TypeTraits::equals((*this)[1].xyz().dot(), scalingSquared) && + TypeTraits::equals((*this)[2].xyz().dot(), scalingSquared), + "Math::Matrix4::uniformScaling(): the matrix doesn't have uniform scaling", {}); + return scalingSquared; +} + template Matrix4 Matrix4::invertedRigid() const { CORRADE_ASSERT(isRigidTransformation(), "Math::Matrix4::invertedRigid(): the matrix doesn't represent rigid transformation", {}); diff --git a/src/Math/Test/Matrix3Test.cpp b/src/Math/Test/Matrix3Test.cpp index 6daafea13..778f78e3a 100644 --- a/src/Math/Test/Matrix3Test.cpp +++ b/src/Math/Test/Matrix3Test.cpp @@ -78,6 +78,7 @@ class Matrix3Test: public Corrade::TestSuite::Tester { void rotationScalingPart(); void rotationNormalizedPart(); void rotationPart(); + void uniformScalingPart(); void vectorParts(); void invertedRigid(); void transform(); @@ -113,6 +114,7 @@ Matrix3Test::Matrix3Test() { &Matrix3Test::rotationScalingPart, &Matrix3Test::rotationNormalizedPart, &Matrix3Test::rotationPart, + &Matrix3Test::uniformScalingPart, &Matrix3Test::vectorParts, &Matrix3Test::invertedRigid, &Matrix3Test::transform, @@ -322,21 +324,32 @@ void Matrix3Test::rotationPart() { CORRADE_COMPARE(rotationTranslationPart, expectedRotationPart); /* Test uniform scaling */ - Matrix3 rotationScaling = rotation*Matrix3::scaling(Vector2(9.0f)); + Matrix3 rotationScaling = rotation*Matrix3::scaling(Vector2(3.0f)); Matrix2 rotationScalingPart = rotationScaling.rotation(); CORRADE_COMPARE(rotationScalingPart.determinant(), 1.0f); CORRADE_COMPARE(rotationScalingPart*rotationScalingPart.transposed(), Matrix2()); CORRADE_COMPARE(rotationScalingPart, expectedRotationPart); /* Fails on non-uniform scaling */ - { - CORRADE_EXPECT_FAIL("Assertion on uniform scaling is not implemented yet."); - std::ostringstream o; - Error::setOutput(&o); - Matrix3 rotationScaling2 = rotation*Matrix3::scaling(Vector2::yScale(3.5f)); - CORRADE_COMPARE(o.str(), "Math::Matrix3::rotation(): the matrix doesn't have uniform scaling\n"); - CORRADE_COMPARE(rotationScaling2, Matrix3(Matrix3::Zero)); - } + std::ostringstream o; + Error::setOutput(&o); + Matrix2 rotationScaling2 = (rotation*Matrix3::scaling(Vector2::yScale(3.5f))).rotation(); + CORRADE_COMPARE(o.str(), "Math::Matrix3::rotation(): the matrix doesn't have uniform scaling\n"); + CORRADE_COMPARE(rotationScaling2, Matrix2()); +} + +void Matrix3Test::uniformScalingPart() { + const Matrix3 rotation = Matrix3::rotation(Deg(-74.0f)); + + /* Test uniform scaling */ + CORRADE_COMPARE((rotation*Matrix3::scaling(Vector2(3.0f))).uniformScaling(), 3.0f); + + /* Fails on non-uniform scaling */ + std::ostringstream o; + Error::setOutput(&o); + const Float nonUniformScaling = (rotation*Matrix3::scaling(Vector2::yScale(3.0f))).uniformScaling(); + CORRADE_COMPARE(o.str(), "Math::Matrix3::uniformScaling(): the matrix doesn't have uniform scaling\n"); + CORRADE_COMPARE(nonUniformScaling, 0.0f); } void Matrix3Test::vectorParts() { diff --git a/src/Math/Test/Matrix4Test.cpp b/src/Math/Test/Matrix4Test.cpp index c311b6c24..0ccfbc13f 100644 --- a/src/Math/Test/Matrix4Test.cpp +++ b/src/Math/Test/Matrix4Test.cpp @@ -85,6 +85,7 @@ class Matrix4Test: public Corrade::TestSuite::Tester { void rotationScalingPart(); void rotationNormalizedPart(); void rotationPart(); + void uniformScalingPart(); void vectorParts(); void invertedRigid(); void transform(); @@ -125,6 +126,7 @@ Matrix4Test::Matrix4Test() { &Matrix4Test::rotationScalingPart, &Matrix4Test::rotationNormalizedPart, &Matrix4Test::rotationPart, + &Matrix4Test::uniformScalingPart, &Matrix4Test::vectorParts, &Matrix4Test::invertedRigid, &Matrix4Test::transform, @@ -408,21 +410,32 @@ void Matrix4Test::rotationPart() { CORRADE_COMPARE(rotationTranslationPart, expectedRotationPart); /* Test uniform scaling */ - Matrix4 rotationScaling = rotation*Matrix4::scaling(Vector3(9.0f)); + Matrix4 rotationScaling = rotation*Matrix4::scaling(Vector3(3.0f)); Matrix3 rotationScalingPart = rotationScaling.rotation(); CORRADE_COMPARE(rotationScalingPart.determinant(), 1.0f); CORRADE_COMPARE(rotationScalingPart*rotationScalingPart.transposed(), Matrix3()); CORRADE_COMPARE(rotationScalingPart, expectedRotationPart); /* Fails on non-uniform scaling */ - { - CORRADE_EXPECT_FAIL("Assertion on uniform scaling is not implemented yet."); - std::ostringstream o; - Error::setOutput(&o); - Matrix4 rotationScaling2 = rotation*Matrix4::scaling(Vector3::yScale(3.5f)); - CORRADE_COMPARE(o.str(), "Math::Matrix4::rotation(): the matrix doesn't have uniform scaling\n"); - CORRADE_COMPARE(rotationScaling2, Matrix4(Matrix4::Zero)); - } + std::ostringstream o; + Error::setOutput(&o); + Matrix3 rotationScaling2 = (rotation*Matrix4::scaling(Vector3::yScale(3.5f))).rotation(); + CORRADE_COMPARE(o.str(), "Math::Matrix4::rotation(): the matrix doesn't have uniform scaling\n"); + CORRADE_COMPARE(rotationScaling2, Matrix3()); +} + +void Matrix4Test::uniformScalingPart() { + const Matrix4 rotation = Matrix4::rotation(Deg(-74.0f), Vector3(-1.0f, 2.0f, 2.0f).normalized()); + + /* Test uniform scaling */ + CORRADE_COMPARE((rotation*Matrix4::scaling(Vector3(3.0f))).uniformScaling(), 3.0f); + + /* Fails on non-uniform scaling */ + std::ostringstream o; + Error::setOutput(&o); + const Float nonUniformScaling = (rotation*Matrix4::scaling(Vector3::yScale(3.0f))).uniformScaling(); + CORRADE_COMPARE(o.str(), "Math::Matrix4::uniformScaling(): the matrix doesn't have uniform scaling\n"); + CORRADE_COMPARE(nonUniformScaling, 0.0f); } void Matrix4Test::vectorParts() { diff --git a/src/Math/instantiation.cpp b/src/Math/instantiation.cpp index dcaaaa601..1bf7f918b 100644 --- a/src/Math/instantiation.cpp +++ b/src/Math/instantiation.cpp @@ -105,6 +105,46 @@ template Corrade::Utility::Debug operator<<(Corrade::Utility::Debug, const Quate template Corrade::Utility::Debug operator<<(Corrade::Utility::Debug, const Quaternion&); #endif +/* Check proper size of GL types */ +static_assert(sizeof(Vector<2, Float>) == 8, "Improper size of 2-element Float vector"); +static_assert(sizeof(Vector<3, Float>) == 12, "Improper size of 3-element Float vector"); +static_assert(sizeof(Vector<4, Float>) == 16, "Improper size of 4-element Float vector"); +static_assert(sizeof(Vector<2, Int>) == 8, "Improper size of 2-element Int vector"); +static_assert(sizeof(Vector<3, Int>) == 12, "Improper size of 3-element Int vector"); +static_assert(sizeof(Vector<4, Int>) == 16, "Improper size of 4-element Int vector"); +static_assert(sizeof(Vector<2, UnsignedInt>) == 8, "Improper size of 2-element UnsignedInt vector"); +static_assert(sizeof(Vector<3, UnsignedInt>) == 12, "Improper size of 3-element UnsignedInt vector"); +static_assert(sizeof(Vector<4, UnsignedInt>) == 16, "Improper size of 4-element UnsignedInt vector"); +#ifndef MAGNUM_TARGET_GLES +static_assert(sizeof(Vector<2, Double>) == 16, "Improper size of 2-element Double vector"); +static_assert(sizeof(Vector<3, Double>) == 24, "Improper size of 3-element Double vector"); +static_assert(sizeof(Vector<4, Double>) == 32, "Improper size of 4-element Double vector"); +#endif + +static_assert(sizeof(RectangularMatrix<2, 2, Float>) == 16, "Improper size of 2x2 Float matrix"); +static_assert(sizeof(RectangularMatrix<3, 3, Float>) == 36, "Improper size of 3x3 Float matrix"); +static_assert(sizeof(RectangularMatrix<4, 4, Float>) == 64, "Improper size of 4x4 Float matrix"); +#ifndef MAGNUM_TARGET_GLES +static_assert(sizeof(RectangularMatrix<2, 2, Double>) == 32, "Improper size of 2x2 Double matrix"); +static_assert(sizeof(RectangularMatrix<3, 3, Double>) == 72, "Improper size of 3x3 Double matrix"); +static_assert(sizeof(RectangularMatrix<4, 4, Double>) == 128, "Improper size of 4x4 Double matrix"); +#endif + +static_assert(sizeof(RectangularMatrix<2, 3, Float>) == 24, "Improper size of 2x3 Float matrix"); +static_assert(sizeof(RectangularMatrix<3, 2, Float>) == 24, "Improper size of 3x2 Float matrix"); +static_assert(sizeof(RectangularMatrix<2, 4, Float>) == 32, "Improper size of 2x4 Float matrix"); +static_assert(sizeof(RectangularMatrix<4, 2, Float>) == 32, "Improper size of 4x2 Float matrix"); +static_assert(sizeof(RectangularMatrix<3, 4, Float>) == 48, "Improper size of 3x4 Float matrix"); +static_assert(sizeof(RectangularMatrix<4, 3, Float>) == 48, "Improper size of 4x3 Float matrix"); +#ifndef MAGNUM_TARGET_GLES +static_assert(sizeof(RectangularMatrix<2, 3, Double>) == 48, "Improper size of 2x3 Double matrix"); +static_assert(sizeof(RectangularMatrix<3, 2, Double>) == 48, "Improper size of 3x2 Double matrix"); +static_assert(sizeof(RectangularMatrix<2, 4, Double>) == 64, "Improper size of 2x4 Double matrix"); +static_assert(sizeof(RectangularMatrix<4, 2, Double>) == 64, "Improper size of 4x2 Double matrix"); +static_assert(sizeof(RectangularMatrix<3, 4, Double>) == 96, "Improper size of 3x4 Double matrix"); +static_assert(sizeof(RectangularMatrix<4, 3, Double>) == 96, "Improper size of 4x3 Double matrix"); +#endif + template Corrade::Utility::Debug operator<<(Corrade::Utility::Debug, const RectangularMatrix<2, 2, Float>&); template Corrade::Utility::Debug operator<<(Corrade::Utility::Debug, const RectangularMatrix<3, 3, Float>&); template Corrade::Utility::Debug operator<<(Corrade::Utility::Debug, const RectangularMatrix<4, 4, Float>&); diff --git a/src/Mesh.cpp b/src/Mesh.cpp index ceb74ebae..176e30f97 100644 --- a/src/Mesh.cpp +++ b/src/Mesh.cpp @@ -60,9 +60,9 @@ std::size_t Mesh::indexSize(IndexType type) { Mesh::Mesh(Primitive primitive): _primitive(primitive), _vertexCount(0), _indexCount(0) #ifndef MAGNUM_TARGET_GLES2 - , indexStart(0), indexEnd(0) + , _indexStart(0), _indexEnd(0) #endif - , indexOffset(0), indexType(IndexType::UnsignedInt), indexBuffer(nullptr) + , _indexOffset(0), _indexType(IndexType::UnsignedInt), _indexBuffer(nullptr) { (this->*createImplementation)(); } @@ -70,49 +70,49 @@ Mesh::Mesh(Primitive primitive): _primitive(primitive), _vertexCount(0), _indexC Mesh::~Mesh() { /* Remove current vao from the state */ GLuint& current = Context::current()->state().mesh->currentVAO; - if(current == vao) current = 0; + if(current == _id) current = 0; (this->*destroyImplementation)(); } -Mesh::Mesh(Mesh&& other): vao(other.vao), _primitive(other._primitive), _vertexCount(other._vertexCount), _indexCount(other._indexCount) +Mesh::Mesh(Mesh&& other): _id(other._id), _primitive(other._primitive), _vertexCount(other._vertexCount), _indexCount(other._indexCount) #ifndef MAGNUM_TARGET_GLES2 - , indexStart(other.indexStart), indexEnd(other.indexEnd) + , _indexStart(other._indexStart), _indexEnd(other._indexEnd) #endif - , indexOffset(other.indexOffset), indexType(other.indexType), indexBuffer(other.indexBuffer), attributes(std::move(other.attributes)) + , _indexOffset(other._indexOffset), _indexType(other._indexType), _indexBuffer(other._indexBuffer), _attributes(std::move(other._attributes)) #ifndef MAGNUM_TARGET_GLES2 - , integerAttributes(std::move(other.integerAttributes)) + , _integerAttributes(std::move(other._integerAttributes)) #ifndef MAGNUM_TARGET_GLES - , longAttributes(std::move(other.longAttributes)) + , _longAttributes(std::move(other._longAttributes)) #endif #endif { - other.vao = 0; + other._id = 0; } Mesh& Mesh::operator=(Mesh&& other) { (this->*destroyImplementation)(); - vao = other.vao; + _id = other._id; _primitive = other._primitive; _vertexCount = other._vertexCount; _indexCount = other._indexCount; #ifndef MAGNUM_TARGET_GLES2 - indexStart = other.indexStart; - indexEnd = other.indexEnd; + _indexStart = other._indexStart; + _indexEnd = other._indexEnd; #endif - indexOffset = other.indexOffset; - indexType = other.indexType; - indexBuffer = other.indexBuffer; - attributes = std::move(other.attributes); + _indexOffset = other._indexOffset; + _indexType = other._indexType; + _indexBuffer = other._indexBuffer; + _attributes = std::move(other._attributes); #ifndef MAGNUM_TARGET_GLES2 - integerAttributes = std::move(other.integerAttributes); + _integerAttributes = std::move(other._integerAttributes); #ifndef MAGNUM_TARGET_GLES - longAttributes = std::move(other.longAttributes); + _longAttributes = std::move(other._longAttributes); #endif #endif - other.vao = 0; + other._id = 0; return *this; } @@ -120,14 +120,14 @@ Mesh& Mesh::operator=(Mesh&& other) { Mesh& Mesh::setIndexBuffer(Buffer& buffer, GLintptr offset, IndexType type, UnsignedInt start, UnsignedInt end) { #ifdef CORRADE_TARGET_NACL CORRADE_ASSERT(buffer.targetHint() == Buffer::Target::ElementArray, - "Mesh::setIndexBuffer(): the buffer has unexpected target hint, expected" << Buffer::Target::ElementArray << "but got" << buffer.targetHint(), this); + "Mesh::setIndexBuffer(): the buffer has unexpected target hint, expected" << Buffer::Target::ElementArray << "but got" << buffer.targetHint(), *this); #endif - indexOffset = offset; - indexType = type; + _indexOffset = offset; + _indexType = type; #ifndef MAGNUM_TARGET_GLES2 - indexStart = start; - indexEnd = end; + _indexStart = start; + _indexEnd = end; #else static_cast(start); static_cast(end); @@ -136,25 +136,25 @@ Mesh& Mesh::setIndexBuffer(Buffer& buffer, GLintptr offset, IndexType type, Unsi return *this; } -void Mesh::draw() { +void Mesh::drawInternal(Int firstVertex, Int vertexCount, GLintptr indexOffset, Int indexCount, Int indexStart, Int indexEnd) { /* Nothing to draw */ - if(!_vertexCount && !_indexCount) return; + if(!vertexCount && !indexCount) return; (this->*bindImplementation)(); /* Non-indexed mesh */ - if(!_indexCount) - glDrawArrays(static_cast(_primitive), 0, _vertexCount); + if(!indexCount) + glDrawArrays(static_cast(_primitive), firstVertex, vertexCount); #ifndef MAGNUM_TARGET_GLES2 /* Indexed mesh with specified range */ else if(indexEnd) - glDrawRangeElements(static_cast(_primitive), indexStart, indexEnd, _indexCount, static_cast(indexType), reinterpret_cast(indexOffset)); + glDrawRangeElements(static_cast(_primitive), indexStart, indexEnd, indexCount, static_cast(_indexType), reinterpret_cast(indexOffset)); #endif /* Indexed mesh without specified range */ else - glDrawElements(static_cast(_primitive), _indexCount, static_cast(indexType), reinterpret_cast(indexOffset)); + glDrawElements(static_cast(_primitive), indexCount, static_cast(_indexType), reinterpret_cast(indexOffset)); (this->*unbindImplementation)(); } @@ -226,7 +226,7 @@ void Mesh::createImplementationDefault() {} void Mesh::createImplementationVAO() { /** @todo Get some extension wrangler instead to avoid linker errors to glGenVertexArrays() on ES2 */ #ifndef MAGNUM_TARGET_GLES2 - glGenVertexArrays(1, &vao); + glGenVertexArrays(1, &_id); #endif } @@ -235,7 +235,7 @@ void Mesh::destroyImplementationDefault() {} void Mesh::destroyImplementationVAO() { /** @todo Get some extension wrangler instead to avoid linker errors to glDeleteVertexArrays() on ES2 */ #ifndef MAGNUM_TARGET_GLES2 - if(vao) glDeleteVertexArrays(1, &vao); + if(_id) glDeleteVertexArrays(1, &_id); #endif } @@ -245,7 +245,7 @@ void Mesh::attributePointerImplementationDefault(const Attribute& attribute) { "Mesh::addVertexBuffer(): the buffer has unexpected target hint, expected" << Buffer::Target::Array << "but got" << attribute.buffer->targetHint(), ); #endif - attributes.push_back(attribute); + _attributes.push_back(attribute); } void Mesh::attributePointerImplementationVAO(const Attribute& attribute) { @@ -254,57 +254,57 @@ void Mesh::attributePointerImplementationVAO(const Attribute& attribute) { "Mesh::addVertexBuffer(): the buffer has unexpected target hint, expected" << Buffer::Target::Array << "but got" << attribute.buffer->targetHint(), ); #endif - bindVAO(vao); + bindVAO(_id); vertexAttribPointer(attribute); } #ifndef MAGNUM_TARGET_GLES void Mesh::attributePointerImplementationDSA(const Attribute& attribute) { - glEnableVertexArrayAttribEXT(vao, attribute.location); - glVertexArrayVertexAttribOffsetEXT(vao, attribute.buffer->id(), attribute.location, attribute.size, attribute.type, attribute.normalized, attribute.stride, attribute.offset); + glEnableVertexArrayAttribEXT(_id, attribute.location); + glVertexArrayVertexAttribOffsetEXT(_id, attribute.buffer->id(), attribute.location, attribute.size, attribute.type, attribute.normalized, attribute.stride, attribute.offset); } #endif #ifndef MAGNUM_TARGET_GLES2 void Mesh::attributePointerImplementationDefault(const IntegerAttribute& attribute) { - integerAttributes.push_back(attribute); + _integerAttributes.push_back(attribute); } void Mesh::attributePointerImplementationVAO(const IntegerAttribute& attribute) { - bindVAO(vao); + bindVAO(_id); vertexAttribPointer(attribute); } #ifndef MAGNUM_TARGET_GLES void Mesh::attributePointerImplementationDSA(const IntegerAttribute& attribute) { - glEnableVertexArrayAttribEXT(vao, attribute.location); - glVertexArrayVertexAttribIOffsetEXT(vao, attribute.buffer->id(), attribute.location, attribute.size, attribute.type, attribute.stride, attribute.offset); + glEnableVertexArrayAttribEXT(_id, attribute.location); + glVertexArrayVertexAttribIOffsetEXT(_id, attribute.buffer->id(), attribute.location, attribute.size, attribute.type, attribute.stride, attribute.offset); } #endif #ifndef MAGNUM_TARGET_GLES void Mesh::attributePointerImplementationDefault(const LongAttribute& attribute) { - longAttributes.push_back(attribute); + _longAttributes.push_back(attribute); } void Mesh::attributePointerImplementationVAO(const LongAttribute& attribute) { - bindVAO(vao); + bindVAO(_id); vertexAttribPointer(attribute); } void Mesh::attributePointerImplementationDSA(const LongAttribute& attribute) { - glEnableVertexArrayAttribEXT(vao, attribute.location); - glVertexArrayVertexAttribLOffsetEXT(vao, attribute.buffer->id(), attribute.location, attribute.size, attribute.type, attribute.stride, attribute.offset); + glEnableVertexArrayAttribEXT(_id, attribute.location); + glVertexArrayVertexAttribLOffsetEXT(_id, attribute.buffer->id(), attribute.location, attribute.size, attribute.type, attribute.stride, attribute.offset); } #endif #endif void Mesh::bindIndexBufferImplementationDefault(Buffer& buffer) { - indexBuffer = &buffer; + _indexBuffer = &buffer; } void Mesh::bindIndexBufferImplementationVAO(Buffer& buffer) { - bindVAO(vao); + bindVAO(_id); /* Reset ElementArray binding to force explicit glBindBuffer call later */ /** @todo Do this cleaner way */ @@ -315,37 +315,37 @@ void Mesh::bindIndexBufferImplementationVAO(Buffer& buffer) { void Mesh::bindImplementationDefault() { /* Specify vertex attributes */ - for(auto it = attributes.begin(); it != attributes.end(); ++it) + for(auto it = _attributes.begin(); it != _attributes.end(); ++it) vertexAttribPointer(*it); #ifndef MAGNUM_TARGET_GLES2 - for(auto it = integerAttributes.begin(); it != integerAttributes.end(); ++it) + for(auto it = _integerAttributes.begin(); it != _integerAttributes.end(); ++it) vertexAttribPointer(*it); #ifndef MAGNUM_TARGET_GLES - for(auto it = longAttributes.begin(); it != longAttributes.end(); ++it) + for(auto it = _longAttributes.begin(); it != _longAttributes.end(); ++it) vertexAttribPointer(*it); #endif #endif /* Bind index buffer, if the mesh is indexed */ - if(_indexCount) indexBuffer->bind(Buffer::Target::ElementArray); + if(_indexCount) _indexBuffer->bind(Buffer::Target::ElementArray); } void Mesh::bindImplementationVAO() { - bindVAO(vao); + bindVAO(_id); } void Mesh::unbindImplementationDefault() { - for(auto it = attributes.begin(); it != attributes.end(); ++it) + for(auto it = _attributes.begin(); it != _attributes.end(); ++it) glDisableVertexAttribArray(it->location); #ifndef MAGNUM_TARGET_GLES2 - for(auto it = integerAttributes.begin(); it != integerAttributes.end(); ++it) + for(auto it = _integerAttributes.begin(); it != _integerAttributes.end(); ++it) glDisableVertexAttribArray(it->location); #ifndef MAGNUM_TARGET_GLES - for(auto it = longAttributes.begin(); it != longAttributes.end(); ++it) + for(auto it = _longAttributes.begin(); it != _longAttributes.end(); ++it) glDisableVertexAttribArray(it->location); #endif #endif diff --git a/src/Mesh.h b/src/Mesh.h index 5afadf688..16859d610 100644 --- a/src/Mesh.h +++ b/src/Mesh.h @@ -240,6 +240,7 @@ for more information. */ class MAGNUM_EXPORT Mesh { friend class Context; + friend class MeshView; Mesh(const Mesh&) = delete; Mesh& operator=(const Mesh&) = delete; @@ -335,7 +336,11 @@ class MAGNUM_EXPORT Mesh { UnsignedInt = GL_UNSIGNED_INT }; - /** @brief Size of given index type */ + /** + * @brief Size of given index type + * + * @see indexSize() const + */ static std::size_t indexSize(IndexType type); /** @@ -362,6 +367,13 @@ class MAGNUM_EXPORT Mesh { /** @brief Move assignment */ Mesh& operator=(Mesh&& other); + /** + * @brief Index size + * + * @see indexSize(IndexType) + */ + std::size_t indexSize() const { return indexSize(_indexType); } + /** @brief Primitive type */ Primitive primitive() const { return _primitive; } @@ -584,7 +596,9 @@ class MAGNUM_EXPORT Mesh { * or @fn_gl{BindVertexArray} (if @extension{APPLE,vertex_array_object} * is available), @fn_gl{DrawArrays} or @fn_gl{DrawElements}/@fn_gl{DrawRangeElements}. */ - void draw(); + void draw() { + drawInternal(0, _vertexCount, _indexOffset, _indexCount, _indexStart, _indexEnd); + } private: #ifndef DOXYGEN_GENERATING_OUTPUT @@ -708,6 +722,8 @@ class MAGNUM_EXPORT Mesh { #endif #endif + void drawInternal(Int firstVertex, Int vertexCount, GLintptr indexOffset, Int indexCount, Int indexStart, Int indexEnd); + typedef void(Mesh::*CreateImplementation)(); void MAGNUM_LOCAL createImplementationDefault(); void MAGNUM_LOCAL createImplementationVAO(); @@ -759,21 +775,21 @@ class MAGNUM_EXPORT Mesh { void MAGNUM_LOCAL unbindImplementationVAO(); static MAGNUM_LOCAL UnbindImplementation unbindImplementation; - GLuint vao; + GLuint _id; Primitive _primitive; Int _vertexCount, _indexCount; #ifndef MAGNUM_TARGET_GLES2 - UnsignedInt indexStart, indexEnd; + UnsignedInt _indexStart, _indexEnd; #endif - GLintptr indexOffset; - IndexType indexType; - Buffer* indexBuffer; + GLintptr _indexOffset; + IndexType _indexType; + Buffer* _indexBuffer; - std::vector attributes; + std::vector _attributes; #ifndef MAGNUM_TARGET_GLES2 - std::vector integerAttributes; + std::vector _integerAttributes; #ifndef MAGNUM_TARGET_GLES - std::vector longAttributes; + std::vector _longAttributes; #endif #endif }; @@ -792,7 +808,6 @@ template inline Mesh& Mesh::addVertexBuffer(Buffer& buffer, GLintptr return *this; } - } namespace Corrade { namespace Utility { diff --git a/src/MeshTools/CMakeLists.txt b/src/MeshTools/CMakeLists.txt index ef0c26d2e..6e9762e58 100644 --- a/src/MeshTools/CMakeLists.txt +++ b/src/MeshTools/CMakeLists.txt @@ -77,5 +77,11 @@ if(BUILD_TESTS) set_target_properties(MagnumMeshToolsTestLib PROPERTIES COMPILE_FLAGS "-DCORRADE_GRACEFUL_ASSERT -DMagnumMeshTools_EXPORTS") target_link_libraries(MagnumMeshToolsTestLib Magnum) + # On Windows we need to install first and then run the tests to avoid "DLL + # not found" hell, thus we need to install this too + if(WIN32 AND NOT CMAKE_CROSSCOMPILING) + install(TARGETS MagnumMeshToolsTestLib DESTINATION ${MAGNUM_LIBRARY_INSTALL_DIR}) + endif() + add_subdirectory(Test) endif() diff --git a/src/MeshView.cpp b/src/MeshView.cpp new file mode 100644 index 000000000..302f268e7 --- /dev/null +++ b/src/MeshView.cpp @@ -0,0 +1,43 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013 Vladimír VondruÅ¡ + + 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. +*/ + +#include "MeshView.h" + +#include "Mesh.h" + +namespace Magnum { + +MeshView& MeshView::setIndexRange(Int first, Int count, UnsignedInt start, UnsignedInt end) { + _indexOffset = first*_original->indexSize(); + _indexCount = count; + _indexStart = start; + _indexEnd = end; + return *this; +} + +void MeshView::draw() { + _original->drawInternal(_firstVertex, _vertexCount, _indexOffset, _indexCount, _indexStart, _indexEnd); +} + +} diff --git a/src/MeshView.h b/src/MeshView.h new file mode 100644 index 000000000..3fdb0aa0f --- /dev/null +++ b/src/MeshView.h @@ -0,0 +1,139 @@ +#ifndef Magnum_MeshView_h +#define Magnum_MeshView_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013 Vladimír VondruÅ¡ + + 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. +*/ + +/** @file + * @brief Class Magnum::MeshView + */ + +#include "Magnum.h" +#include "magnumVisibility.h" + +#ifndef DOXYGEN_GENERATING_OUTPUT +typedef std::ptrdiff_t GLintptr; +#endif + +namespace Magnum { + +/** +@brief %Mesh view + +Allows different interpretation of given @ref Mesh data via different vertex or +index count and offset. It is then possible to reuse one mesh buffer +configuration for different views. %Mesh primitive, index type, attribute +bindings and attached buffers are reused from original mesh. + +The same rules as in Mesh apply, i.e. if the view has non-zero index count, it +is treated as indexed mesh, otherwise it is treated as non-indexed mesh. If +both index and vertex count is zero, the view is treated as empty and no draw +commands are issued when calling draw(). + +You must ensure that the original mesh remains available for whole view +lifetime. +@todo Might cause issues when there are more data than just indices in index + buffer (wrongly computed offset) +*/ +class MAGNUM_EXPORT MeshView { + public: + /** + * @brief Constructor + * @param original Original, already configured mesh + */ + explicit MeshView(Mesh& original); + + /** @brief Copy constructor */ + MeshView(const MeshView& other) = default; + + /** @brief Movement is not allowed */ + MeshView(MeshView&& other) = delete; + + /** @brief Copy assignment */ + MeshView& operator=(const MeshView&) = default; + + /** @brief Movement is not allowed */ + MeshView& operator=(MeshView&& other) = delete; + + /** + * @brief Set vertex range + * @param first First vertex + * @param count Vertex count + * @return Reference to self (for method chaining) + * + * Default is zero @p offset and zero @p count. If index range is + * non-zero, vertex range is ignored, see main class documentation for + * more information. + */ + MeshView& setVertexRange(Int first, Int count) { + _firstVertex = first; + _vertexCount = count; + return *this; + } + + /** + * @brief Set index range + * @param first First index + * @param count Index count + * @param start Minimum array index contained in the buffer + * @param end Maximum array index contained in the buffer + * @return Reference to self (for method chaining) + * + * Specifying `0` for both @p start and @p end behaves the same as + * @ref setIndexRange(Int, Int). + */ + MeshView& setIndexRange(Int first, Int count, UnsignedInt start, UnsignedInt end); + + /** + * @brief Set index range + * @param first First index + * @param count Index count + * @return Reference to self (for method chaining) + * + * Prefer to use @ref setIndexRange(Int, Int, UnsignedInt, UnsignedInt) + * for better performance. + */ + MeshView& setIndexRange(Int first, Int count) { + return setIndexRange(first, count, 0, 0); + } + + /** + * @brief Draw the mesh + * + * See @ref Mesh::draw() for more information. + */ + void draw(); + + private: + Mesh* _original; + + Int _firstVertex, _vertexCount, _indexCount; + GLintptr _indexOffset; + UnsignedInt _indexStart, _indexEnd; +}; + +inline MeshView::MeshView(Mesh& original): _original(&original), _firstVertex(0), _vertexCount(0), _indexCount(0), _indexOffset(0), _indexStart(0), _indexEnd(0) {} + +} + +#endif diff --git a/src/Primitives/Capsule.cpp b/src/Primitives/Capsule.cpp index 50885b3bd..852d23b8c 100644 --- a/src/Primitives/Capsule.cpp +++ b/src/Primitives/Capsule.cpp @@ -25,20 +25,77 @@ #include "Capsule.h" #include "Math/Vector3.h" +#include "Math/Functions.h" #include "Primitives/Implementation/Spheroid.h" #include "Primitives/Implementation/WireframeSpheroid.h" +#include "Trade/MeshData2D.h" #include "Trade/MeshData3D.h" namespace Magnum { namespace Primitives { -Trade::MeshData3D Capsule::solid(UnsignedInt hemisphereRings, UnsignedInt cylinderRings, UnsignedInt segments, Float length, TextureCoords textureCoords) { +Trade::MeshData2D Capsule2D::wireframe(UnsignedInt hemisphereRings, UnsignedInt cylinderRings, Float halfLength) { + CORRADE_ASSERT(hemisphereRings >= 1 && cylinderRings >= 1, "Capsule must have at least one hemisphere ring, one cylinder ring and three segments", Trade::MeshData2D(Mesh::Primitive::Lines, {}, {}, {})); + + std::vector positions; + positions.reserve(hemisphereRings*4+2+(cylinderRings-1)*2); + const Rad angleIncrement(Constants::pi()/(2.0f*hemisphereRings)); + const Float cylinderIncrement = 2.0f*halfLength/cylinderRings; + + /* Bottom cap vertex */ + positions.emplace_back(0.0f, -halfLength-1.0f); + + /* Bottom hemisphere */ + for(UnsignedInt i = 0; i != hemisphereRings; ++i) { + const Rad angle((i+1)*angleIncrement); + const Float x = Math::sin(angle); + const Float y = -Math::cos(angle)-halfLength; + positions.insert(positions.end(), {{-x, y}, {x, y}}); + } + + /* Cylinder (bottom and top vertices are done within caps */ + for(UnsignedInt i = 0; i != cylinderRings-1; ++i) { + const Float y = (i+1)*cylinderIncrement-halfLength; + positions.insert(positions.end(), {{-1.0f, y}, {1.0f, y}}); + } + + /* Top hemisphere */ + for(UnsignedInt i = 0; i != hemisphereRings; ++i) { + const Rad angle(i*angleIncrement); + const Float x = Math::cos(angle); + const Float y = Math::sin(angle)+halfLength; + positions.insert(positions.end(), {{-x, y}, {x, y}}); + } + + /* Top cap vertex */ + positions.emplace_back(0.0f, halfLength+1.0f); + + std::vector indices; + indices.reserve(hemisphereRings*8+cylinderRings*4); + + /* Bottom cap indices */ + indices.insert(indices.end(), {0, 1, 0, 2}); + + /* Side indices */ + for(UnsignedInt i = 0; i != cylinderRings+hemisphereRings*2-2; ++i) + indices.insert(indices.end(), {i*2+1, i*2+3, + i*2+2, i*2+4}); + + /* Top cap indices */ + indices.insert(indices.end(), + {UnsignedInt(positions.size())-3, UnsignedInt(positions.size())-1, + UnsignedInt(positions.size())-2, UnsignedInt(positions.size())-1}); + + return Trade::MeshData2D(Mesh::Primitive::Lines, std::move(indices), {std::move(positions)}, {}); +} + +Trade::MeshData3D Capsule3D::solid(UnsignedInt hemisphereRings, UnsignedInt cylinderRings, UnsignedInt segments, Float halfLength, TextureCoords textureCoords) { CORRADE_ASSERT(hemisphereRings >= 1 && cylinderRings >= 1 && segments >= 3, "Capsule must have at least one hemisphere ring, one cylinder ring and three segments", Trade::MeshData3D(Mesh::Primitive::Triangles, {}, {}, {}, {})); Implementation::Spheroid capsule(segments, textureCoords == TextureCoords::Generate ? Implementation::Spheroid::TextureCoords::Generate : Implementation::Spheroid::TextureCoords::DontGenerate); - Float height = 2.0f+length; + Float height = 2.0f+2.0f*halfLength; Float hemisphereTextureCoordsVIncrement = 1.0f/(hemisphereRings*height); Rad hemisphereRingAngleIncrement(Constants::pi()/(2*hemisphereRings)); @@ -46,13 +103,13 @@ Trade::MeshData3D Capsule::solid(UnsignedInt hemisphereRings, UnsignedInt cylind capsule.capVertex(-height/2, -1.0f, 0.0f); /* Rings of bottom hemisphere */ - capsule.hemisphereVertexRings(hemisphereRings-1, -length/2, -Rad(Constants::pi())/2+hemisphereRingAngleIncrement, hemisphereRingAngleIncrement, hemisphereTextureCoordsVIncrement, hemisphereTextureCoordsVIncrement); + capsule.hemisphereVertexRings(hemisphereRings-1, -halfLength, -Rad(Constants::pi())/2+hemisphereRingAngleIncrement, hemisphereRingAngleIncrement, hemisphereTextureCoordsVIncrement, hemisphereTextureCoordsVIncrement); /* Rings of cylinder */ - capsule.cylinderVertexRings(cylinderRings+1, -length/2, length/cylinderRings, 1.0f/height, length/(cylinderRings*height)); + capsule.cylinderVertexRings(cylinderRings+1, -halfLength, 2.0f*halfLength/cylinderRings, 1.0f/height, 2.0f*halfLength/(cylinderRings*height)); /* Rings of top hemisphere */ - capsule.hemisphereVertexRings(hemisphereRings-1, length/2, hemisphereRingAngleIncrement, hemisphereRingAngleIncrement, (1.0f + length)/height+hemisphereTextureCoordsVIncrement, hemisphereTextureCoordsVIncrement); + capsule.hemisphereVertexRings(hemisphereRings-1, halfLength, hemisphereRingAngleIncrement, hemisphereRingAngleIncrement, (1.0f + 2.0f*halfLength)/height+hemisphereTextureCoordsVIncrement, hemisphereTextureCoordsVIncrement); /* Top cap vertex */ capsule.capVertex(height/2, 1.0f, 1.0f); @@ -65,23 +122,23 @@ Trade::MeshData3D Capsule::solid(UnsignedInt hemisphereRings, UnsignedInt cylind return capsule.finalize(); } -Trade::MeshData3D Capsule::wireframe(const UnsignedInt hemisphereRings, const UnsignedInt cylinderRings, const UnsignedInt segments, const Float length) { +Trade::MeshData3D Capsule3D::wireframe(const UnsignedInt hemisphereRings, const UnsignedInt cylinderRings, const UnsignedInt segments, const Float halfLength) { CORRADE_ASSERT(hemisphereRings >= 1 && cylinderRings >= 1 && segments >= 4 && segments%4 == 0, "Primitives::Capsule::wireframe(): improper parameters", Trade::MeshData3D(Mesh::Primitive::Lines, {}, {}, {}, {})); Implementation::WireframeSpheroid capsule(segments/4); /* Bottom hemisphere */ - capsule.bottomHemisphere(-length/2, hemisphereRings); + capsule.bottomHemisphere(-halfLength, hemisphereRings); /* Cylinder */ - capsule.ring(-length/2); + capsule.ring(-halfLength); for(UnsignedInt i = 0; i != cylinderRings; ++i) { capsule.cylinder(); - capsule.ring(-length/2 + (i+1)*(length/cylinderRings)); + capsule.ring(-halfLength + (i+1)*(2.0f*halfLength/cylinderRings)); } /* Top hemisphere */ - capsule.topHemisphere(length/2, hemisphereRings); + capsule.topHemisphere(halfLength, hemisphereRings); return capsule.finalize(); } diff --git a/src/Primitives/Capsule.h b/src/Primitives/Capsule.h index bf9195aed..64adb1dbd 100644 --- a/src/Primitives/Capsule.h +++ b/src/Primitives/Capsule.h @@ -33,12 +33,32 @@ namespace Magnum { namespace Primitives { +/** +@brief 2D capsule primitive + +%Cylinder of radius `1` along Y axis with hemispheres instead of caps. +*/ +class MAGNUM_PRIMITIVES_EXPORT Capsule2D { + public: + /** + * @brief Wireframe capsule + * @param hemisphereRings Number of (line) rings for each hemisphere. + * Must be larger or equal to 1. + * @param cylinderRings Number of (line) rings for cylinder. Must be + * larger or equal to 1. + * @param halfLength Half the length of cylinder part + * + * Indexed @ref Mesh::Primitive "Lines". + */ + static Trade::MeshData2D wireframe(UnsignedInt hemisphereRings, UnsignedInt cylinderRings, Float halfLength); +}; + /** @brief 3D capsule primitive %Cylinder of radius `1` along Y axis with hemispheres instead of caps. */ -class MAGNUM_PRIMITIVES_EXPORT Capsule { +class MAGNUM_PRIMITIVES_EXPORT Capsule3D { public: /** @brief Whether to generate texture coordinates */ enum class TextureCoords: UnsignedByte { @@ -54,14 +74,14 @@ class MAGNUM_PRIMITIVES_EXPORT Capsule { * larger or equal to 1. * @param segments Number of (face) segments. Must be larger or * equal to 3. - * @param length Length of the capsule, excluding hemispheres. + * @param halfLength Half the length of cylinder part * @param textureCoords Whether to generate texture coordinates. * * Indexed @ref Mesh::Primitive "Triangles" with normals and optional * 2D texture coordinates. If texture coordinates are generated, * vertices of one segment are duplicated for texture wrapping. */ - static Trade::MeshData3D solid(UnsignedInt hemisphereRings, UnsignedInt cylinderRings, UnsignedInt segments, Float length, TextureCoords textureCoords = TextureCoords::DontGenerate); + static Trade::MeshData3D solid(UnsignedInt hemisphereRings, UnsignedInt cylinderRings, UnsignedInt segments, Float halfLength, TextureCoords textureCoords = TextureCoords::DontGenerate); /** * @brief Wireframe capsule @@ -71,11 +91,11 @@ class MAGNUM_PRIMITIVES_EXPORT Capsule { * larger or equal to 1. * @param segments Number of line segments. Must be larger or * equal to 4 and multiple of 4. - * @param length Length of the capsule, excluding hemispheres. + * @param halfLength Half the length of cylinder part * * Indexed @ref Mesh::Primitive "Lines". */ - static Trade::MeshData3D wireframe(UnsignedInt hemisphereRings, UnsignedInt cylinderRings, UnsignedInt segments, Float length); + static Trade::MeshData3D wireframe(UnsignedInt hemisphereRings, UnsignedInt cylinderRings, UnsignedInt segments, Float halfLength); }; }} diff --git a/src/Primitives/Cylinder.cpp b/src/Primitives/Cylinder.cpp index b1af5e6a8..da6b28b81 100644 --- a/src/Primitives/Cylinder.cpp +++ b/src/Primitives/Cylinder.cpp @@ -31,27 +31,27 @@ namespace Magnum { namespace Primitives { -Trade::MeshData3D Cylinder::solid(UnsignedInt rings, UnsignedInt segments, Float length, Cylinder::Flags flags) { +Trade::MeshData3D Cylinder::solid(const UnsignedInt rings, const UnsignedInt segments, const Float halfLength, const Flags flags) { CORRADE_ASSERT(rings >= 1 && segments >= 3, "Primitives::Cylinder::solid(): cylinder must have at least one ring and three segments", Trade::MeshData3D(Mesh::Primitive::Triangles, {}, {}, {}, {})); Implementation::Spheroid cylinder(segments, flags & Flag::GenerateTextureCoords ? Implementation::Spheroid::TextureCoords::Generate : Implementation::Spheroid::TextureCoords::DontGenerate); - Float y = length*0.5f; - Float textureCoordsV = flags & Flag::CapEnds ? 1.0f/(length+2.0f) : 0.0f; + const Float length = 2.0f*halfLength; + const Float textureCoordsV = flags & Flag::CapEnds ? 1.0f/(length+2.0f) : 0.0f; /* Bottom cap */ if(flags & Flag::CapEnds) { - cylinder.capVertex(-y, -1.0f, 0.0f); - cylinder.capVertexRing(-y, textureCoordsV, Vector3::yAxis(-1.0f)); + cylinder.capVertex(-halfLength, -1.0f, 0.0f); + cylinder.capVertexRing(-halfLength, textureCoordsV, Vector3::yAxis(-1.0f)); } /* Vertex rings */ - cylinder.cylinderVertexRings(rings+1, -y, length/rings, textureCoordsV, length/(rings*(flags & Flag::CapEnds ? length + 2.0f : length))); + cylinder.cylinderVertexRings(rings+1, -halfLength, length/rings, textureCoordsV, length/(rings*(flags & Flag::CapEnds ? length + 2.0f : length))); /* Top cap */ if(flags & Flag::CapEnds) { - cylinder.capVertexRing(y, 1.0f - textureCoordsV, Vector3::yAxis(1.0f)); - cylinder.capVertex(y, 1.0f, 1.0f); + cylinder.capVertexRing(halfLength, 1.0f - textureCoordsV, Vector3::yAxis(1.0f)); + cylinder.capVertex(halfLength, 1.0f, 1.0f); } /* Faces */ @@ -62,16 +62,18 @@ Trade::MeshData3D Cylinder::solid(UnsignedInt rings, UnsignedInt segments, Float return cylinder.finalize(); } -Trade::MeshData3D Cylinder::wireframe(const UnsignedInt rings, const UnsignedInt segments, const Float length) { +Trade::MeshData3D Cylinder::wireframe(const UnsignedInt rings, const UnsignedInt segments, const Float halfLength) { CORRADE_ASSERT(rings >= 1 && segments >= 4 && segments%4 == 0, "Primitives::Cylinder::wireframe(): improper parameters", Trade::MeshData3D(Mesh::Primitive::Lines, {}, {}, {}, {})); Implementation::WireframeSpheroid cylinder(segments/4); + const Float increment = 2*halfLength/rings; + /* Rings */ - cylinder.ring(-length/2); + cylinder.ring(-halfLength); for(UnsignedInt i = 0; i != rings; ++i) { cylinder.cylinder(); - cylinder.ring(-length/2 + (i+1)*(length/rings)); + cylinder.ring(-halfLength + (i+1)*increment); } return cylinder.finalize(); diff --git a/src/Primitives/Cylinder.h b/src/Primitives/Cylinder.h index 8b8d92bfb..e00946d63 100644 --- a/src/Primitives/Cylinder.h +++ b/src/Primitives/Cylinder.h @@ -62,7 +62,7 @@ class MAGNUM_PRIMITIVES_EXPORT Cylinder { * equal to 1. * @param segments Number of (face) segments. Must be larger or * equal to 3. - * @param length Cylinder length + * @param halfLength Half the cylinder length * @param flags Flags * * Indexed @ref Mesh::Primitive "Triangles" with normals, optional 2D @@ -70,7 +70,7 @@ class MAGNUM_PRIMITIVES_EXPORT Cylinder { * are generated, vertices of one segment are duplicated for texture * wrapping. */ - static Trade::MeshData3D solid(UnsignedInt rings, UnsignedInt segments, Float length, Flags flags = Flags()); + static Trade::MeshData3D solid(UnsignedInt rings, UnsignedInt segments, Float halfLength, Flags flags = Flags()); /** * @brief Wireframe cylinder @@ -78,11 +78,11 @@ class MAGNUM_PRIMITIVES_EXPORT Cylinder { * to 1. * @param segments Number of (line) segments. Must be larger or * equal to 4 and multiple of 4. - * @param length Cylinder length + * @param halfLength Half the cylinder length * * Indexed @ref Mesh::Primitive "Lines". */ - static Trade::MeshData3D wireframe(UnsignedInt rings, UnsignedInt segments, Float length); + static Trade::MeshData3D wireframe(UnsignedInt rings, UnsignedInt segments, Float halfLength); }; CORRADE_ENUMSET_OPERATORS(Cylinder::Flags) diff --git a/src/Primitives/Test/CapsuleTest.cpp b/src/Primitives/Test/CapsuleTest.cpp index 77e7be8e3..23efaba1e 100644 --- a/src/Primitives/Test/CapsuleTest.cpp +++ b/src/Primitives/Test/CapsuleTest.cpp @@ -29,6 +29,7 @@ #include #include "Math/Vector3.h" +#include "Trade/MeshData2D.h" #include "Trade/MeshData3D.h" #include "Primitives/Capsule.h" @@ -38,19 +39,66 @@ class CapsuleTest: public TestSuite::Tester { public: CapsuleTest(); - void solidWithoutTextureCoords(); - void solidWithTextureCoords(); - void wireframe(); + void wireframe2D(); + + void solid3DWithoutTextureCoords(); + void solid3DWithTextureCoords(); + void wireframe3D(); }; CapsuleTest::CapsuleTest() { - addTests({&CapsuleTest::solidWithoutTextureCoords, - &CapsuleTest::solidWithTextureCoords, - &CapsuleTest::wireframe}); + addTests({&CapsuleTest::wireframe2D, + &CapsuleTest::solid3DWithoutTextureCoords, + &CapsuleTest::solid3DWithTextureCoords, + &CapsuleTest::wireframe3D}); } -void CapsuleTest::solidWithoutTextureCoords() { - Trade::MeshData3D capsule = Capsule::solid(2, 2, 3, 1.0f); +void CapsuleTest::wireframe2D() { + Trade::MeshData2D capsule = Capsule2D::wireframe(2, 4, 0.5f); + + CORRADE_COMPARE_AS(capsule.positions(0), (std::vector{ + {0.0f, -1.5f}, + + {-0.707107f, -1.20711f}, + {0.707107f, -1.20711f}, + + {-1.0f, -0.5f}, + {1.0f, -0.5f}, + + {-1.0f, -0.25f}, + {1.0f, -0.25f}, + + {-1.0f, 0.0f}, + {1.0f, 0.0f}, + + {-1.0f, 0.25f}, + {1.0f, 0.25f}, + + {-1.0f, 0.5f}, + {1.0f, 0.5f}, + + {-0.707107f, 1.20711f}, + {0.707107f, 1.20711f}, + + {0.0f, 1.5f} + }), TestSuite::Compare::Container); + + CORRADE_COMPARE_AS(capsule.indices(), (std::vector{ + 0, 1, 0, 2, + + 1, 3, 2, 4, + 3, 5, 4, 6, + 5, 7, 6, 8, + 7, 9, 8, 10, + 9, 11, 10, 12, + 11, 13, 12, 14, + + 13, 15, 14, 15 + }), TestSuite::Compare::Container); +} + +void CapsuleTest::solid3DWithoutTextureCoords() { + Trade::MeshData3D capsule = Capsule3D::solid(2, 4, 3, 0.5f); CORRADE_COMPARE_AS(capsule.positions(0), (std::vector{ {0.0f, -1.5f, 0.0f}, @@ -63,10 +111,18 @@ void CapsuleTest::solidWithoutTextureCoords() { {0.866025f, -0.5f, -0.5f}, {-0.866025f, -0.5f, -0.5f}, + {0.0f, -0.25f, 1.0f}, + {0.866025f, -0.25f, -0.5f}, + {-0.866025f, -0.25f, -0.5f}, + {0.0f, 0.0f, 1.0f}, {0.866025f, 0.0f, -0.5f}, {-0.866025f, 0.0f, -0.5f}, + {0.0f, 0.25f, 1.0f}, + {0.866025f, 0.25f, -0.5f}, + {-0.866025f, 0.25f, -0.5f}, + {0.0f, 0.5f, 1.0f}, {0.866025f, 0.5f, -0.5f}, {-0.866025f, 0.5f, -0.5f}, @@ -97,6 +153,14 @@ void CapsuleTest::solidWithoutTextureCoords() { {0.866025f, 0.0f, -0.5f}, {-0.866025f, 0.0f, -0.5f}, + {0.0f, 0.0f, 1.0f}, + {0.866025f, 0.0f, -0.5f}, + {-0.866025f, 0.0f, -0.5f}, + + {0.0f, 0.0f, 1.0f}, + {0.866025f, 0.0f, -0.5f}, + {-0.866025f, 0.0f, -0.5f}, + {0.0f, 0.707107f, 0.707107f}, {0.612372f, 0.707107f, -0.353553f}, {-0.612372f, 0.707107f, -0.353553f}, @@ -110,12 +174,14 @@ void CapsuleTest::solidWithoutTextureCoords() { 4, 5, 8, 4, 8, 7, 5, 6, 9, 5, 9, 8, 6, 4, 7, 6, 7, 9, 7, 8, 11, 7, 11, 10, 8, 9, 12, 8, 12, 11, 9, 7, 10, 9, 10, 12, 10, 11, 14, 10, 14, 13, 11, 12, 15, 11, 15, 14, 12, 10, 13, 12, 13, 15, - 13, 14, 16, 14, 15, 16, 15, 13, 16 + 13, 14, 17, 13, 17, 16, 14, 15, 18, 14, 18, 17, 15, 13, 16, 15, 16, 18, + 16, 17, 20, 16, 20, 19, 17, 18, 21, 17, 21, 20, 18, 16, 19, 18, 19, 21, + 19, 20, 22, 20, 21, 22, 21, 19, 22 }), TestSuite::Compare::Container); } -void CapsuleTest::solidWithTextureCoords() { - Trade::MeshData3D capsule = Capsule::solid(2, 2, 3, 1.0f, Capsule::TextureCoords::Generate); +void CapsuleTest::solid3DWithTextureCoords() { + Trade::MeshData3D capsule = Capsule3D::solid(2, 2, 3, 0.5f, Capsule3D::TextureCoords::Generate); CORRADE_COMPARE_AS(capsule.positions(0), (std::vector{ {0.0f, -1.5f, 0.0f}, @@ -189,8 +255,8 @@ void CapsuleTest::solidWithTextureCoords() { }), TestSuite::Compare::Container); } -void CapsuleTest::wireframe() { - Trade::MeshData3D capsule = Capsule::wireframe(2, 2, 8, 1.0f); +void CapsuleTest::wireframe3D() { + Trade::MeshData3D capsule = Capsule3D::wireframe(2, 2, 8, 0.5f); CORRADE_COMPARE_AS(capsule.positions(0), (std::vector{ {0.0f, -1.5f, 0.0f}, diff --git a/src/Primitives/Test/CylinderTest.cpp b/src/Primitives/Test/CylinderTest.cpp index 7aa532bf6..10bc34add 100644 --- a/src/Primitives/Test/CylinderTest.cpp +++ b/src/Primitives/Test/CylinderTest.cpp @@ -47,7 +47,7 @@ CylinderTest::CylinderTest() { } void CylinderTest::solidWithoutAnything() { - Trade::MeshData3D cylinder = Cylinder::solid(2, 3, 3.0f); + Trade::MeshData3D cylinder = Cylinder::solid(2, 3, 1.5f); CORRADE_COMPARE_AS(cylinder.positions(0), (std::vector{ {0.0f, -1.5f, 1.0f}, @@ -84,7 +84,7 @@ void CylinderTest::solidWithoutAnything() { } void CylinderTest::solidWithTextureCoordsAndCaps() { - Trade::MeshData3D cylinder = Cylinder::solid(2, 3, 3.0f, Cylinder::Flag::GenerateTextureCoords|Cylinder::Flag::CapEnds); + Trade::MeshData3D cylinder = Cylinder::solid(2, 3, 1.5f, Cylinder::Flag::GenerateTextureCoords|Cylinder::Flag::CapEnds); CORRADE_COMPARE_AS(cylinder.positions(0), (std::vector{ {0.0f, -1.5f, 0.0f}, @@ -188,7 +188,7 @@ void CylinderTest::solidWithTextureCoordsAndCaps() { } void CylinderTest::wireframe() { - Trade::MeshData3D cylinder = Cylinder::wireframe(2, 8, 1.0f); + Trade::MeshData3D cylinder = Cylinder::wireframe(2, 8, 0.5f); CORRADE_COMPARE_AS(cylinder.positions(0), (std::vector{ {0.0f, -0.5f, 1.0f}, diff --git a/src/Renderer.h b/src/Renderer.h index cbef32112..1e345f3ea 100644 --- a/src/Renderer.h +++ b/src/Renderer.h @@ -24,7 +24,7 @@ DEALINGS IN THE SOFTWARE. */ -/** @file +/** @file /Renderer.h * @brief Class Magnum::Renderer */ @@ -37,9 +37,8 @@ namespace Magnum { /** @nosubgrouping -@brief %Renderer +@brief Global renderer configuration. -Access to global renderer configuration. @todo @extension{ARB,viewport_array} */ class MAGNUM_EXPORT Renderer { diff --git a/src/ResourceManager.h b/src/ResourceManager.h index 7dc825890..d8c964379 100644 --- a/src/ResourceManager.h +++ b/src/ResourceManager.h @@ -313,17 +313,27 @@ template class ResourceManager: private Implementation::Resource return *this; } + /** @overload */ + template ResourceManager& set(ResourceKey key, U&& data, ResourceDataState state, ResourcePolicy policy) { + return set(key, new typename std::remove_cv::type>::type(std::forward(data)), state, policy); + } + /** * @brief Set resource data * @return Reference to self (for method chaining) * - * Same as above function with state set to @ref ResourceDataState "ResourceDataState::Final" + * Same as above with state set to @ref ResourceDataState "ResourceDataState::Final" * and policy to @ref ResourcePolicy "ResourcePolicy::Resident". */ template ResourceManager& set(ResourceKey key, T* data) { return set(key, data, ResourceDataState::Final, ResourcePolicy::Resident); } + /** @overload */ + template ResourceManager& set(ResourceKey key, U&& data) { + return set(key, new typename std::remove_cv::type>::type(std::forward(data))); + } + /** @brief Fallback for not found resources */ template T* fallback() { return this->Implementation::ResourceManagerData::fallback(); @@ -343,6 +353,11 @@ template class ResourceManager: private Implementation::Resource return *this; } + /** @overload */ + template ResourceManager& setFallback(U&& data) { + return setFallback(new typename std::remove_cv::type>::type(std::forward(data))); + } + /** * @brief Free all resources of given type which are not referenced * @return Reference to self (for method chaining) diff --git a/src/SceneGraph/CMakeLists.txt b/src/SceneGraph/CMakeLists.txt index e4f7d6145..8238973a5 100644 --- a/src/SceneGraph/CMakeLists.txt +++ b/src/SceneGraph/CMakeLists.txt @@ -92,5 +92,11 @@ if(BUILD_TESTS) set_target_properties(MagnumSceneGraphTestLib PROPERTIES COMPILE_FLAGS "-DCORRADE_GRACEFUL_ASSERT -DMagnumSceneGraph_EXPORTS") target_link_libraries(MagnumSceneGraphTestLib MagnumMathTestLib) + # On Windows we need to install first and then run the tests to avoid "DLL + # not found" hell, thus we need to install this too + if(WIN32 AND NOT CMAKE_CROSSCOMPILING) + install(TARGETS MagnumSceneGraphTestLib DESTINATION ${MAGNUM_LIBRARY_INSTALL_DIR}) + endif() + add_subdirectory(Test) endif() diff --git a/src/Shapes/CMakeLists.txt b/src/Shapes/CMakeLists.txt index 2f23c8abb..de98a9474 100644 --- a/src/Shapes/CMakeLists.txt +++ b/src/Shapes/CMakeLists.txt @@ -27,6 +27,7 @@ set(MagnumShapes_SRCS AxisAlignedBox.cpp Box.cpp Capsule.cpp + Cylinder.cpp Composition.cpp Line.cpp Plane.cpp @@ -44,6 +45,7 @@ set(MagnumShapes_HEADERS AxisAlignedBox.h Box.h Capsule.h + Cylinder.h Composition.h Line.h LineSegment.h diff --git a/src/Shapes/Capsule.cpp b/src/Shapes/Capsule.cpp index 497696d09..d0df89afa 100644 --- a/src/Shapes/Capsule.cpp +++ b/src/Shapes/Capsule.cpp @@ -37,8 +37,7 @@ using namespace Magnum::Math::Geometry; namespace Magnum { namespace Shapes { template Capsule Capsule::transformed(const typename DimensionTraits::MatrixType& matrix) const { - return Capsule(matrix.transformPoint(_a), matrix.transformPoint(_b), - (matrix.rotationScaling()*typename DimensionTraits::VectorType(1/Constants::sqrt3())).length()*_radius); + return Capsule(matrix.transformPoint(_a), matrix.transformPoint(_b), matrix.uniformScaling()*_radius); } template bool Capsule::operator%(const Point& other) const { diff --git a/src/Shapes/Capsule.h b/src/Shapes/Capsule.h index 2d85ddd97..c0f0d4bbc 100644 --- a/src/Shapes/Capsule.h +++ b/src/Shapes/Capsule.h @@ -38,11 +38,11 @@ namespace Magnum { namespace Shapes { /** @brief %Capsule defined by cylinder start and end point and radius -Unlike other elements the capsule doesn't support asymmetric scaling. When -applying transformation, the scale factor is averaged from all axes. See -@ref shapes for brief introduction. -@see Capsule2D, Capsule3D -@todo Assert for asymmetric scaling +Unlike other elements the capsule expects uniform scaling. See @ref shapes for +brief introduction. +@see Capsule2D, Capsule3D, Cylinder +@todo Store the radius as squared value to avoid sqrt/pow? Will complicate + collision detection with sphere. */ template class MAGNUM_SHAPES_EXPORT Capsule { public: diff --git a/src/Shapes/Composition.h b/src/Shapes/Composition.h index 5552352cc..1783ee593 100644 --- a/src/Shapes/Composition.h +++ b/src/Shapes/Composition.h @@ -79,6 +79,7 @@ template class MAGNUM_SHAPES_EXPORT Composition { Line, /**< Line */ LineSegment, /**< @ref LineSegment "Line segment" */ Sphere, /**< Sphere */ + Cylinder, /**< @ref Cylinder */ Capsule, /**< Capsule */ AxisAlignedBox, /**< @ref AxisAlignedBox "Axis aligned box" */ Box, /**< Box */ diff --git a/src/Shapes/Cylinder.cpp b/src/Shapes/Cylinder.cpp new file mode 100644 index 000000000..b24cc43f0 --- /dev/null +++ b/src/Shapes/Cylinder.cpp @@ -0,0 +1,58 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013 Vladimír VondruÅ¡ + + 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. +*/ + +#include "Cylinder.h" + +#include "Math/Functions.h" +#include "Math/Matrix3.h" +#include "Math/Matrix4.h" +#include "Math/Geometry/Distance.h" +#include "Magnum.h" +#include "Shapes/Point.h" +#include "Shapes/Sphere.h" + +using namespace Magnum::Math::Geometry; + +namespace Magnum { namespace Shapes { + +template Cylinder Cylinder::transformed(const typename DimensionTraits::MatrixType& matrix) const { + return Cylinder(matrix.transformPoint(_a), matrix.transformPoint(_b), matrix.uniformScaling()*_radius); +} + +template bool Cylinder::operator%(const Point& other) const { + return Distance::linePointSquared(_a, _b, other.position()) < + Math::pow<2>(_radius); +} + +template bool Cylinder::operator%(const Sphere& other) const { + return Distance::linePointSquared(_a, _b, other.position()) < + Math::pow<2>(_radius+other.radius()); +} + +#ifndef DOXYGEN_GENERATING_OUTPUT +template class MAGNUM_SHAPES_EXPORT Cylinder<2>; +template class MAGNUM_SHAPES_EXPORT Cylinder<3>; +#endif + +}} diff --git a/src/Shapes/Cylinder.h b/src/Shapes/Cylinder.h new file mode 100644 index 000000000..e9a04d33b --- /dev/null +++ b/src/Shapes/Cylinder.h @@ -0,0 +1,117 @@ +#ifndef Magnum_Shapes_Cylinder_h +#define Magnum_Shapes_Cylinder_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013 Vladimír VondruÅ¡ + + 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. +*/ + +/** @file + * @brief Class Magnum::Shapes::Cylinder, typedef Magnum::Shapes::Cylinder2D, Magnum::Shapes::Cylinder3D + */ + +#include "Math/Vector3.h" +#include "DimensionTraits.h" +#include "Shapes/Shapes.h" +#include "Shapes/magnumShapesVisibility.h" + +namespace Magnum { namespace Shapes { + +/** +@brief Infinite cylinder defined by line and radius + +Unlike other elements the cylinder expects uniform scaling. See @ref shapes for +brief introduction. +@see @ref Cylinder2D, @ref Cylinder3D, @ref Capsule +@todo Store the radius as squared value to avoid sqrt/pow? Will complicate + collision detection with sphere. +*/ +template class MAGNUM_SHAPES_EXPORT Cylinder { + public: + enum: UnsignedInt { + Dimensions = dimensions /**< Dimension count */ + }; + + /** + * @brief Constructor + * + * Creates zero-sized cylinder at origin. + */ + constexpr /*implicit*/ Cylinder(): _radius(0.0f) {} + + /** @brief Constructor */ + constexpr /*implicit*/ Cylinder(const typename DimensionTraits::VectorType& a, const typename DimensionTraits::VectorType& b, Float radius): _a(a), _b(b), _radius(radius) {} + + /** @brief Transformed shape */ + Cylinder transformed(const typename DimensionTraits::MatrixType& matrix) const; + + /** @brief First point */ + constexpr typename DimensionTraits::VectorType a() const { + return _a; + } + + /** @brief Set first point */ + void setA(const typename DimensionTraits::VectorType& a) { + _a = a; + } + + /** @brief Second point */ + constexpr typename DimensionTraits::VectorType b() const { + return _b; + } + + /** @brief Set second point */ + void setB(const typename DimensionTraits::VectorType& b) { + _b = b; + } + + /** @brief Radius */ + constexpr Float radius() const { return _radius; } + + /** @brief Set radius */ + void setRadius(Float radius) { _radius = radius; } + + /** @brief Collision with point */ + bool operator%(const Point& other) const; + + /** @brief Collision with sphere */ + bool operator%(const Sphere& other) const; + + private: + typename DimensionTraits::VectorType _a, _b; + Float _radius; +}; + +/** @brief Infinite two-dimensional cylinder */ +typedef Cylinder<2> Cylinder2D; + +/** @brief Infinite three-dimensional cylinder */ +typedef Cylinder<3> Cylinder3D; + +/** @collisionoperator{Point,Cylinder} */ +template inline bool operator%(const Point& a, const Cylinder& b) { return b % a; } + +/** @collisionoperator{Sphere,Cylinder} */ +template inline bool operator%(const Sphere& a, const Cylinder& b) { return b % a; } + +}} + +#endif diff --git a/src/Shapes/Implementation/CollisionDispatch.cpp b/src/Shapes/Implementation/CollisionDispatch.cpp index 0b144b5c9..3818b6e6f 100644 --- a/src/Shapes/Implementation/CollisionDispatch.cpp +++ b/src/Shapes/Implementation/CollisionDispatch.cpp @@ -27,6 +27,7 @@ #include "Shapes/AxisAlignedBox.h" #include "Shapes/Box.h" #include "Shapes/Capsule.h" +#include "Shapes/Cylinder.h" #include "Shapes/LineSegment.h" #include "Shapes/Plane.h" #include "Shapes/Point.h" @@ -48,6 +49,9 @@ template<> bool collides(const AbstractShape<2>& a, const AbstractShape<2>& b) { _c(Sphere, Sphere2D, LineSegment, LineSegment2D) _c(Sphere, Sphere2D, Sphere, Sphere2D) + _c(Cylinder, Cylinder2D, Point, Point2D) + _c(Cylinder, Cylinder2D, Sphere, Sphere2D) + _c(Capsule, Capsule2D, Point, Point2D) _c(Capsule, Capsule2D, Sphere, Sphere2D) @@ -71,6 +75,9 @@ template<> bool collides(const AbstractShape<3>& a, const AbstractShape<3>& b) { _c(Sphere, Sphere3D, LineSegment, LineSegment3D) _c(Sphere, Sphere3D, Sphere, Sphere3D) + _c(Cylinder, Cylinder3D, Point, Point3D) + _c(Cylinder, Cylinder3D, Sphere, Sphere3D) + _c(Capsule, Capsule3D, Point, Point3D) _c(Capsule, Capsule3D, Sphere, Sphere3D) diff --git a/src/Shapes/Plane.cpp b/src/Shapes/Plane.cpp index e62d5a15f..7c2ef4fc0 100644 --- a/src/Shapes/Plane.cpp +++ b/src/Shapes/Plane.cpp @@ -35,8 +35,11 @@ using namespace Magnum::Math::Geometry; namespace Magnum { namespace Shapes { Plane Plane::transformed(const Matrix4& matrix) const { + /* Using matrix.rotation() would result in two more normalizations (slow), + using .normalized() instead of matrix.uniformScaling() would not check + uniform scaling */ return Plane(matrix.transformPoint(_position), - matrix.rotation()*_normal); + matrix.rotationScaling()*_normal/matrix.uniformScaling()); } bool Plane::operator%(const Line3D& other) const { diff --git a/src/Shapes/Plane.h b/src/Shapes/Plane.h index c3f3471d8..81f1608e5 100644 --- a/src/Shapes/Plane.h +++ b/src/Shapes/Plane.h @@ -38,7 +38,8 @@ namespace Magnum { namespace Shapes { /** @brief Infinite plane, defined by position and normal (3D only) -See @ref shapes for brief introduction. +Unlike other elements the plane expects uniform scaling. See @ref shapes for +brief introduction. */ class MAGNUM_SHAPES_EXPORT Plane { public: diff --git a/src/Shapes/Shapes.h b/src/Shapes/Shapes.h index dfc0d0fd7..057cb7e90 100644 --- a/src/Shapes/Shapes.h +++ b/src/Shapes/Shapes.h @@ -54,6 +54,10 @@ template class Composition; typedef Composition<2> Composition2D; typedef Composition<3> Composition3D; +template class Cylinder; +typedef Cylinder<2> Cylinder2D; +typedef Cylinder<3> Cylinder3D; + template class Line; typedef Line<2> Line2D; typedef Line<3> Line3D; diff --git a/src/Shapes/Sphere.cpp b/src/Shapes/Sphere.cpp index f1af70317..295a9ab0a 100644 --- a/src/Shapes/Sphere.cpp +++ b/src/Shapes/Sphere.cpp @@ -36,21 +36,8 @@ using namespace Magnum::Math::Geometry; namespace Magnum { namespace Shapes { -namespace { - template static typename DimensionTraits::VectorType unitVector(); - - template<> inline Vector2 unitVector<2>() { - return Vector2(1/Constants::sqrt2()); - } - - template<> inline Vector3 unitVector<3>() { - return Vector3(1/Constants::sqrt3()); - } -} - template Sphere Sphere::transformed(const typename DimensionTraits::MatrixType& matrix) const { - return Sphere(matrix.transformPoint(_position), - (matrix.rotationScaling()*unitVector()).length()*_radius); + return Sphere(matrix.transformPoint(_position), matrix.uniformScaling()*_radius); } template bool Sphere::operator%(const Point& other) const { diff --git a/src/Shapes/Sphere.h b/src/Shapes/Sphere.h index c100f90b3..17d27bdd0 100644 --- a/src/Shapes/Sphere.h +++ b/src/Shapes/Sphere.h @@ -38,11 +38,11 @@ namespace Magnum { namespace Shapes { /** @brief %Sphere defined by position and radius -Unlike other elements the sphere doesn't support asymmetric scaling. When -applying transformation, the scale factor is averaged from all axes. See -@ref shapes for brief introduction. +Unlike other elements the sphere expects uniform scaling. See @ref shapes for +brief introduction. @see Sphere2D, Sphere3D -@todo Assert for asymmetric scaling +@todo Store the radius as squared value to avoid sqrt/pow? Will complicate + collision detection with another sphere. */ template class MAGNUM_SHAPES_EXPORT Sphere { public: diff --git a/src/Shapes/Test/CMakeLists.txt b/src/Shapes/Test/CMakeLists.txt index 2615b6d99..b08d2ec0a 100644 --- a/src/Shapes/Test/CMakeLists.txt +++ b/src/Shapes/Test/CMakeLists.txt @@ -26,6 +26,7 @@ corrade_add_test(ShapesShapeImplementationTest ShapeImplementationTest.cpp LIBRA corrade_add_test(ShapesAxisAlignedBoxTest AxisAlignedBoxTest.cpp LIBRARIES MagnumShapes) corrade_add_test(ShapesBoxTest BoxTest.cpp LIBRARIES MagnumShapes) corrade_add_test(ShapesCapsuleTest CapsuleTest.cpp LIBRARIES MagnumShapes) +corrade_add_test(ShapesCylinderTest CylinderTest.cpp LIBRARIES MagnumShapes) corrade_add_test(ShapesLineTest LineTest.cpp LIBRARIES MagnumShapes) corrade_add_test(ShapesPlaneTest PlaneTest.cpp LIBRARIES MagnumShapes) corrade_add_test(ShapesPointTest PointTest.cpp LIBRARIES MagnumShapes) diff --git a/src/Shapes/Test/CapsuleTest.cpp b/src/Shapes/Test/CapsuleTest.cpp index b4fd8a048..ebbb78241 100644 --- a/src/Shapes/Test/CapsuleTest.cpp +++ b/src/Shapes/Test/CapsuleTest.cpp @@ -22,6 +22,7 @@ DEALINGS IN THE SOFTWARE. */ +#include "Math/Matrix3.h" #include "Math/Matrix4.h" #include "Magnum.h" #include "Shapes/Capsule.h" @@ -51,14 +52,10 @@ CapsuleTest::CapsuleTest() { void CapsuleTest::transformed() { const Shapes::Capsule3D capsule({1.0f, 2.0f, 3.0f}, {-1.0f, -2.0f, -3.0f}, 7.0f); - const auto transformed = capsule.transformed(Matrix4::rotation(Deg(90.0f), Vector3::zAxis())); - CORRADE_COMPARE(transformed.a(), Vector3(-2.0f, 1.0f, 3.0f)); - CORRADE_COMPARE(transformed.b(), Vector3(2.0f, -1.0f, -3.0f)); - CORRADE_COMPARE(transformed.radius(), 7.0f); - - /* Apply average scaling to radius */ - const auto scaled = capsule.transformed(Matrix4::scaling({Constants::sqrt3(), -Constants::sqrt2(), 2.0f})); - CORRADE_COMPARE(scaled.radius(), Constants::sqrt3()*7.0f); + const auto transformed = capsule.transformed(Matrix4::scaling(Vector3(2.0f))*Matrix4::rotation(Deg(90.0f), Vector3::zAxis())); + CORRADE_COMPARE(transformed.a(), Vector3(-4.0f, 2.0f, 6.0f)); + CORRADE_COMPARE(transformed.b(), Vector3(4.0f, -2.0f, -6.0f)); + CORRADE_COMPARE(transformed.radius(), 14.0f); } void CapsuleTest::collisionPoint() { diff --git a/src/Shapes/Test/CylinderTest.cpp b/src/Shapes/Test/CylinderTest.cpp new file mode 100644 index 000000000..013f16da9 --- /dev/null +++ b/src/Shapes/Test/CylinderTest.cpp @@ -0,0 +1,85 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013 Vladimír VondruÅ¡ + + 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. +*/ + +#include "Math/Matrix3.h" +#include "Math/Matrix4.h" +#include "Magnum.h" +#include "Shapes/Cylinder.h" +#include "Shapes/Point.h" +#include "Shapes/Sphere.h" + +#include "ShapeTestBase.h" + +namespace Magnum { namespace Shapes { namespace Test { + +class CylinderTest: public TestSuite::Tester { + public: + CylinderTest(); + + void transformed(); + void transformedAverageScaling(); + void collisionPoint(); + void collisionSphere(); +}; + +CylinderTest::CylinderTest() { + addTests({&CylinderTest::transformed, + &CylinderTest::collisionPoint, + &CylinderTest::collisionSphere}); +} + +void CylinderTest::transformed() { + const Shapes::Cylinder3D cylinder({1.0f, 2.0f, 3.0f}, {-1.0f, -2.0f, -3.0f}, 7.0f); + + const auto transformed = cylinder.transformed(Matrix4::scaling(Vector3(2.0f))*Matrix4::rotation(Deg(90.0f), Vector3::zAxis())); + CORRADE_COMPARE(transformed.a(), Vector3(-4.0f, 2.0f, 6.0f)); + CORRADE_COMPARE(transformed.b(), Vector3(4.0f, -2.0f, -6.0f)); + CORRADE_COMPARE(transformed.radius(), 14.0f); +} + +void CylinderTest::collisionPoint() { + Shapes::Cylinder3D cylinder({-1.0f, -1.0f, 0.0f}, {1.0f, 1.0f, 0.0f}, 2.0f); + Shapes::Point3D point({2.0f, 0.0f, 0.0f}); + Shapes::Point3D point1({1.0f, 3.1f, 0.0f}); + Shapes::Point3D point2({2.9f, -1.0f, 0.0f}); + + VERIFY_COLLIDES(cylinder, point); + VERIFY_COLLIDES(cylinder, point1); + VERIFY_NOT_COLLIDES(cylinder, point2); +} + +void CylinderTest::collisionSphere() { + Shapes::Cylinder3D cylinder({-1.0f, -1.0f, 0.0f}, {1.0f, 1.0f, 0.0f}, 2.0f); + Shapes::Sphere3D sphere({3.0f, 0.0f, 0.0f}, 0.9f); + Shapes::Sphere3D sphere1({1.0f, 4.1f, 0.0f}, 1.0f); + Shapes::Sphere3D sphere2({3.5f, -1.0f, 0.0f}, 0.6f); + + VERIFY_COLLIDES(cylinder, sphere); + VERIFY_COLLIDES(cylinder, sphere1); + VERIFY_NOT_COLLIDES(cylinder, sphere2); +} + +}}} + +CORRADE_TEST_MAIN(Magnum::Shapes::Test::CylinderTest) diff --git a/src/Shapes/Test/PlaneTest.cpp b/src/Shapes/Test/PlaneTest.cpp index c86a6386b..5041aeb85 100644 --- a/src/Shapes/Test/PlaneTest.cpp +++ b/src/Shapes/Test/PlaneTest.cpp @@ -49,14 +49,10 @@ PlaneTest::PlaneTest() { void PlaneTest::transformed() { const Shapes::Plane plane({1.0f, 2.0f, 3.0f}, {Constants::sqrt2(), -Constants::sqrt2(), 0}); - const auto transformed = plane.transformed(Matrix4::rotation(Deg(90.0f), Vector3::xAxis())); - CORRADE_COMPARE(transformed.position(), Vector3(1.0f, -3.0f, 2.0f)); + /* The normal should stay normalized after scaling */ + const auto transformed = plane.transformed(Matrix4::scaling(Vector3(2.0f))*Matrix4::rotation(Deg(90.0f), Vector3::xAxis())); + CORRADE_COMPARE(transformed.position(), Vector3(2.0f, -6.0f, 4.0f)); CORRADE_COMPARE(transformed.normal(), Vector3(Constants::sqrt2(), 0, -Constants::sqrt2())); - - /* The normal should stay normalized */ - const auto scaled = plane.transformed(Matrix4::scaling({1.5f, 2.0f, 3.0f})); - CORRADE_COMPARE(scaled.position(), Vector3(1.5f, 4.0f, 9.0f)); - CORRADE_COMPARE(scaled.normal(), Vector3(Constants::sqrt2(), -Constants::sqrt2(), 0)); } void PlaneTest::collisionLine() { diff --git a/src/Shapes/Test/SphereTest.cpp b/src/Shapes/Test/SphereTest.cpp index 70f413be5..b66e86e50 100644 --- a/src/Shapes/Test/SphereTest.cpp +++ b/src/Shapes/Test/SphereTest.cpp @@ -22,6 +22,7 @@ DEALINGS IN THE SOFTWARE. */ +#include "Math/Matrix3.h" #include "Math/Matrix4.h" #include "Magnum.h" #include "Shapes/LineSegment.h" @@ -54,18 +55,9 @@ SphereTest::SphereTest() { void SphereTest::transformed() { const Shapes::Sphere3D sphere({1.0f, 2.0f, 3.0f}, 7.0f); - const auto transformed = sphere.transformed(Matrix4::rotation(Deg(90.0f), Vector3::yAxis())); - CORRADE_COMPARE(transformed.position(), Vector3(3.0f, 2.0f, -1.0f)); - CORRADE_COMPARE(transformed.radius(), 7.0f); - - /* Symmetric scaling */ - const auto scaled = sphere.transformed(Matrix4::scaling(Vector3(2.0f))); - CORRADE_COMPARE(scaled.position(), Vector3(2.0f, 4.0f, 6.0f)); - CORRADE_COMPARE(scaled.radius(), 14.0f); - - /* Apply average scaling to radius */ - const auto nonEven = sphere.transformed(Matrix4::scaling({Constants::sqrt3(), -Constants::sqrt2(), 2.0f})); - CORRADE_COMPARE(nonEven.radius(), Constants::sqrt3()*7.0f); + const auto transformed = sphere.transformed(Matrix4::scaling(Vector3(2.0f))*Matrix4::rotation(Deg(90.0f), Vector3::yAxis())); + CORRADE_COMPARE(transformed.position(), Vector3(6.0f, 4.0f, -2.0f)); + CORRADE_COMPARE(transformed.radius(), 14.0f); } void SphereTest::collisionPoint() { diff --git a/src/Shapes/shapeImplementation.cpp b/src/Shapes/shapeImplementation.cpp index 314dde4f5..0c25f2fa1 100644 --- a/src/Shapes/shapeImplementation.cpp +++ b/src/Shapes/shapeImplementation.cpp @@ -36,6 +36,7 @@ Debug operator<<(Debug debug, ShapeDimensionTraits<2>::Type value) { _val(LineSegment) _val(Sphere) _val(Capsule) + _val(Cylinder) _val(AxisAlignedBox) _val(Box) _val(Composition) @@ -53,6 +54,7 @@ Debug operator<<(Debug debug, ShapeDimensionTraits<3>::Type value) { _val(LineSegment) _val(Sphere) _val(Capsule) + _val(Cylinder) _val(AxisAlignedBox) _val(Box) _val(Plane) diff --git a/src/Shapes/shapeImplementation.h b/src/Shapes/shapeImplementation.h index ae2b02f5d..4209c5a9a 100644 --- a/src/Shapes/shapeImplementation.h +++ b/src/Shapes/shapeImplementation.h @@ -26,6 +26,7 @@ #include #include +#include #include "DimensionTraits.h" #include "Magnum.h" @@ -34,6 +35,23 @@ namespace Magnum { namespace Shapes { namespace Implementation { +/* + Adding new collision type: + + 1. Add the type into the 2D/3D enums below, pick new prime number and + preserve complexity ordering + 2. Update debug output operators for changed enums + 3. Add TypeOf struct specialization (either for both 2D/3D or for only one + of them) + 4. Add the enum value to (documentation-only) enum in Composition + 5. Update doc/shapes.dox with new type + + Adding new collision detection implementation: + + 1. Update Implementation/CollisionDispatch.cpp with newly implemented + 2D/3D pair +*/ + /* Shape type for given dimension count */ template struct ShapeDimensionTraits; @@ -44,10 +62,11 @@ template<> struct ShapeDimensionTraits<2> { Line = 2, LineSegment = 3, Sphere = 5, - Capsule = 7, - AxisAlignedBox = 11, - Box = 13, - Composition = 17 + Cylinder = 7, + Capsule = 11, + AxisAlignedBox = 13, + Box = 17, + Composition = 19 }; }; @@ -57,11 +76,12 @@ template<> struct ShapeDimensionTraits<3> { Line = 2, LineSegment = 3, Sphere = 5, - Capsule = 7, - AxisAlignedBox = 11, - Box = 13, - Plane = 17, - Composition = 19 + Cylinder = 7, + Capsule = 11, + AxisAlignedBox = 13, + Box = 17, + Plane = 19, + Composition = 23 }; }; @@ -92,6 +112,11 @@ template struct TypeOf> { return ShapeDimensionTraits::Type::Sphere; } }; +template struct TypeOf> { + constexpr static typename ShapeDimensionTraits::Type type() { + return ShapeDimensionTraits::Type::Cylinder; + } +}; template struct TypeOf> { constexpr static typename ShapeDimensionTraits::Type type() { return ShapeDimensionTraits::Type::Capsule; diff --git a/src/Test/AbstractOpenGLTester.h b/src/Test/AbstractOpenGLTester.h index fe23735a2..f23a83e67 100644 --- a/src/Test/AbstractOpenGLTester.h +++ b/src/Test/AbstractOpenGLTester.h @@ -25,6 +25,7 @@ */ #include +#include #include "Renderer.h" diff --git a/src/Test/ResourceManagerTest.cpp b/src/Test/ResourceManagerTest.cpp index c6d38cf02..7718c1906 100644 --- a/src/Test/ResourceManagerTest.cpp +++ b/src/Test/ResourceManagerTest.cpp @@ -98,7 +98,7 @@ void ResourceManagerTest::state() { void ResourceManagerTest::stateFallback() { { ResourceManager rm; - rm.setFallback(new Data); + rm.setFallback(new Data); Resource data = rm.get("data"); CORRADE_VERIFY(data); @@ -129,8 +129,7 @@ void ResourceManagerTest::stateDisallowed() { std::ostringstream out; Error::setOutput(&out); - Data d; - rm.set("data", &d, ResourceDataState::Loading, ResourcePolicy::Resident); + rm.set("data", Data(), ResourceDataState::Loading, ResourcePolicy::Resident); CORRADE_COMPARE(out.str(), "ResourceManager::set(): data should be null if and only if state is NotFound or Loading\n"); out.str({}); @@ -144,8 +143,8 @@ void ResourceManagerTest::basic() { /* One mutable, one final */ ResourceKey questionKey("the-question"); ResourceKey answerKey("the-answer"); - rm.set(questionKey, new Int(10), ResourceDataState::Mutable, ResourcePolicy::Resident); - rm.set(answerKey, new Int(42), ResourceDataState::Final, ResourcePolicy::Resident); + rm.set(questionKey, 10, ResourceDataState::Mutable, ResourcePolicy::Resident); + rm.set(answerKey, 42, ResourceDataState::Final, ResourcePolicy::Resident); Resource theQuestion = rm.get(questionKey); Resource theAnswer = rm.get(answerKey); @@ -159,12 +158,12 @@ void ResourceManagerTest::basic() { /* Cannot change already final resource */ std::ostringstream out; Error::setOutput(&out); - rm.set(answerKey, new Int(43), ResourceDataState::Mutable, ResourcePolicy::Resident); + rm.set(answerKey, 43, ResourceDataState::Mutable, ResourcePolicy::Resident); CORRADE_COMPARE(*theAnswer, 42); CORRADE_COMPARE(out.str(), "ResourceManager::set(): cannot change already final resource " + answerKey.hexString() + '\n'); /* But non-final can be changed */ - rm.set(questionKey, new Int(20), ResourceDataState::Final, ResourcePolicy::Resident); + rm.set(questionKey, 20, ResourceDataState::Final, ResourcePolicy::Resident); CORRADE_COMPARE(theQuestion.state(), ResourceState::Final); CORRADE_COMPARE(*theQuestion, 20); } @@ -189,7 +188,7 @@ void ResourceManagerTest::referenceCountedPolicy() { /* Reference counted resources must be requested first */ { - rm.set(dataRefCountKey, new Data(), ResourceDataState::Final, ResourcePolicy::ReferenceCounted); + rm.set(dataRefCountKey, new Data, ResourceDataState::Final, ResourcePolicy::ReferenceCounted); CORRADE_COMPARE(rm.count(), 0); Resource data = rm.get(dataRefCountKey); CORRADE_COMPARE(data.state(), ResourceState::NotLoaded); @@ -198,7 +197,7 @@ void ResourceManagerTest::referenceCountedPolicy() { Resource data = rm.get(dataRefCountKey); CORRADE_COMPARE(rm.count(), 1); CORRADE_COMPARE(data.state(), ResourceState::NotLoaded); - rm.set(dataRefCountKey, new Data(), ResourceDataState::Final, ResourcePolicy::ReferenceCounted); + rm.set(dataRefCountKey, new Data, ResourceDataState::Final, ResourcePolicy::ReferenceCounted); CORRADE_COMPARE(data.state(), ResourceState::Final); CORRADE_COMPARE(Data::count, 1); } @@ -250,7 +249,7 @@ void ResourceManagerTest::clearWhileReferenced() { Error::setOutput(&out); ResourceManager rm; - rm.set("blah", new Int); + rm.set("blah", Int()); /** @todo this will leak, is there any better solution without hitting assertion in decrementReferenceCount()? */ new Resource(rm.get("blah")); @@ -265,7 +264,7 @@ void ResourceManagerTest::loader() { IntResourceLoader(): resource(ResourceManager::instance().get("data")) {} void load() { - set("hello", new Int(773), ResourceDataState::Final, ResourcePolicy::Resident); + set("hello", 773, ResourceDataState::Final, ResourcePolicy::Resident); setNotFound("world"); } diff --git a/src/Text/AbstractFont.cpp b/src/Text/AbstractFont.cpp index 52c9feaff..aa8e054e1 100644 --- a/src/Text/AbstractFont.cpp +++ b/src/Text/AbstractFont.cpp @@ -124,7 +124,12 @@ void AbstractFont::fillGlyphCache(GlyphCache& cache, const std::string& characte doFillGlyphCache(cache, Utility::Unicode::utf32(characters)); } -void AbstractFont::doFillGlyphCache(GlyphCache&, const std::u32string&) { +#ifndef _WIN32 +void AbstractFont::doFillGlyphCache(GlyphCache&, const std::u32string&) +#else +void AbstractFont::doFillGlyphCache(GlyphCache&, const std::vector&) +#endif +{ CORRADE_ASSERT(false, "Text::AbstractFont::fillGlyphCache(): feature advertised but not implemented", ); } diff --git a/src/Text/AbstractFont.h b/src/Text/AbstractFont.h index bab9763cd..6f20f9197 100644 --- a/src/Text/AbstractFont.h +++ b/src/Text/AbstractFont.h @@ -250,8 +250,15 @@ class MAGNUM_TEXT_EXPORT AbstractFont: public PluginManager::AbstractPlugin { * * The string is converted from UTF-8 to UTF-32, unique characters are * *not* removed. + * @note On Windows uses `std::vector` instead of + * `std::u32string`. See @ref Corrade::Utility::Unicode::utf32() + * for more information. */ + #ifndef _WIN32 virtual void doFillGlyphCache(GlyphCache& cache, const std::u32string& characters); + #else + virtual void doFillGlyphCache(GlyphCache& cache, const std::vector& characters); + #endif /** * @brief Implementation for createGlyphCache() diff --git a/src/Text/AbstractFontConverter.cpp b/src/Text/AbstractFontConverter.cpp index a415bc66f..17c6445d1 100644 --- a/src/Text/AbstractFontConverter.cpp +++ b/src/Text/AbstractFontConverter.cpp @@ -43,7 +43,12 @@ std::vector>> AbstractFo return doExportFontToData(font, cache, filename, uniqueUnicode(characters)); } -std::vector>> AbstractFontConverter::doExportFontToData(AbstractFont& font, GlyphCache& cache, const std::string& filename, const std::u32string& characters) const { +#ifndef _WIN32 +std::vector>> AbstractFontConverter::doExportFontToData(AbstractFont& font, GlyphCache& cache, const std::string& filename, const std::u32string& characters) const +#else +std::vector>> AbstractFontConverter::doExportFontToData(AbstractFont& font, GlyphCache& cache, const std::string& filename, const std::vector& characters) const +#endif +{ CORRADE_ASSERT(!(features() & Feature::MultiFile), "Text::AbstractFontConverter::exportFontToData(): feature advertised but not implemented", {}); @@ -68,7 +73,12 @@ Containers::Array AbstractFontConverter::exportFontToSingleData(A return doExportFontToSingleData(font, cache, uniqueUnicode(characters)); } -Containers::Array AbstractFontConverter::doExportFontToSingleData(AbstractFont&, GlyphCache&, const std::u32string&) const { +#ifndef _WIN32 +Containers::Array AbstractFontConverter::doExportFontToSingleData(AbstractFont&, GlyphCache&, const std::u32string&) const +#else +Containers::Array AbstractFontConverter::doExportFontToSingleData(AbstractFont&, GlyphCache&, const std::vector&) const +#endif +{ #ifndef CORRADE_GCC45_COMPATIBILITY CORRADE_ASSERT(false, "Text::AbstractFontConverter::exportFontToSingleData(): feature advertised but not implemented", nullptr); @@ -85,7 +95,12 @@ bool AbstractFontConverter::exportFontToFile(AbstractFont& font, GlyphCache& cac return doExportFontToFile(font, cache, filename, uniqueUnicode(characters)); } -bool AbstractFontConverter::doExportFontToFile(AbstractFont& font, GlyphCache& cache, const std::string& filename, const std::u32string& characters) const { +#ifndef _WIN32 +bool AbstractFontConverter::doExportFontToFile(AbstractFont& font, GlyphCache& cache, const std::string& filename, const std::u32string& characters) const +#else +bool AbstractFontConverter::doExportFontToFile(AbstractFont& font, GlyphCache& cache, const std::string& filename, const std::vector& characters) const +#endif +{ CORRADE_ASSERT(features() & Feature::ConvertData, "Text::AbstractFontConverter::exportFontToFile(): not implemented", false); @@ -242,9 +257,18 @@ GlyphCache* AbstractFontConverter::doImportGlyphCacheFromFile(const std::string& return doImportGlyphCacheFromSingleData(data); } -std::u32string AbstractFontConverter::uniqueUnicode(const std::string& characters) { +#ifndef _WIN32 +std::u32string AbstractFontConverter::uniqueUnicode(const std::string& characters) +#else +std::vector AbstractFontConverter::uniqueUnicode(const std::string& characters) +#endif +{ /* Convert UTF-8 to UTF-32 */ + #ifndef _WIN32 std::u32string result = Utility::Unicode::utf32(characters); + #else + std::vector result = Utility::Unicode::utf32(characters); + #endif /* Remove duplicate glyphs */ std::sort(result.begin(), result.end()); diff --git a/src/Text/AbstractFontConverter.h b/src/Text/AbstractFontConverter.h index 2ea3c0aad..f674fb17f 100644 --- a/src/Text/AbstractFontConverter.h +++ b/src/Text/AbstractFontConverter.h @@ -250,11 +250,28 @@ class MAGNUM_TEXT_EXPORT AbstractFontConverter: public PluginManager::AbstractPl * * If the plugin doesn't have @ref Feature "Feature::MultiFile", * default implementation calls doExportFontToSingleData(). + * @note On Windows uses `std::vector` instead of + * `std::u32string`. See @ref Corrade::Utility::Unicode::utf32() + * for more information. */ + #ifndef _WIN32 virtual std::vector>> doExportFontToData(AbstractFont& font, GlyphCache& cache, const std::string& filename, const std::u32string& characters) const; + #else + virtual std::vector>> doExportFontToData(AbstractFont& font, GlyphCache& cache, const std::string& filename, const std::vector& characters) const; + #endif - /** @brief Implementation for exportFontToSingleData() */ + /** + * @brief Implementation for exportFontToSingleData() + * + * @note On Windows uses `std::vector` instead of + * `std::u32string`. See @ref Corrade::Utility::Unicode::utf32() + * for more information. + */ + #ifndef _WIN32 virtual Containers::Array doExportFontToSingleData(AbstractFont& font, GlyphCache& cache, const std::u32string& characters) const; + #else + virtual Containers::Array doExportFontToSingleData(AbstractFont& font, GlyphCache& cache, const std::vector& characters) const; + #endif /** * @brief Implementation for exportFontToFile() @@ -262,8 +279,15 @@ class MAGNUM_TEXT_EXPORT AbstractFontConverter: public PluginManager::AbstractPl * If @ref Feature "Feature::ConvertData" is supported, default * implementation calls doExportFontToData() and saves the result to * given file(s). + * @note On Windows uses `std::vector` instead of + * `std::u32string`. See @ref Corrade::Utility::Unicode::utf32() + * for more information. */ + #ifndef _WIN32 virtual bool doExportFontToFile(AbstractFont& font, GlyphCache& cache, const std::string& filename, const std::u32string& characters) const; + #else + virtual bool doExportFontToFile(AbstractFont& font, GlyphCache& cache, const std::string& filename, const std::vector& characters) const; + #endif /** * @brief Implementation for exportGlyphCacheToData() @@ -307,7 +331,11 @@ class MAGNUM_TEXT_EXPORT AbstractFontConverter: public PluginManager::AbstractPl virtual GlyphCache* doImportGlyphCacheFromFile(const std::string& filename) const; private: + #ifndef _WIN32 MAGNUM_TEXT_LOCAL static std::u32string uniqueUnicode(const std::string& characters); + #else + MAGNUM_TEXT_LOCAL static std::vector uniqueUnicode(const std::string& characters); + #endif }; CORRADE_ENUMSET_OPERATORS(AbstractFontConverter::Features) diff --git a/src/Text/Test/AbstractFontConverterTest.cpp b/src/Text/Test/AbstractFontConverterTest.cpp index 5013db7a0..408f2a075 100644 --- a/src/Text/Test/AbstractFontConverterTest.cpp +++ b/src/Text/Test/AbstractFontConverterTest.cpp @@ -65,12 +65,21 @@ AbstractFontConverterTest::AbstractFontConverterTest() { void AbstractFontConverterTest::convertGlyphs() { class GlyphExporter: public AbstractFontConverter { public: + #ifndef _WIN32 GlyphExporter(std::u32string& characters): characters(characters) {} + #else + GlyphExporter(std::vector& characters): characters(characters) {} + #endif private: Features doFeatures() const override { return Feature::ConvertData|Feature::ExportFont; } - Containers::Array doExportFontToSingleData(AbstractFont&, GlyphCache&, const std::u32string& characters) const override { + #ifndef _WIN32 + Containers::Array doExportFontToSingleData(AbstractFont&, GlyphCache&, const std::u32string& characters) const override + #else + Containers::Array doExportFontToSingleData(AbstractFont&, GlyphCache&, const std::vector& characters) const override + #endif + { this->characters = characters; #ifndef CORRADE_GCC45_COMPATIBILITY return nullptr; @@ -79,14 +88,26 @@ void AbstractFontConverterTest::convertGlyphs() { #endif } + #ifndef _WIN32 std::u32string& characters; + #else + std::vector& characters; + #endif }; + #ifndef _WIN32 std::u32string characters; + #else + std::vector characters; + #endif GlyphExporter exporter(characters); exporter.exportFontToSingleData(*static_cast(nullptr), *static_cast(nullptr), "abC01a0 "); - CORRADE_COMPARE(characters, (std::u32string{ + #ifndef _WIN32 + CORRADE_COMPARE(characters, U" 01Cab"); + #else + CORRADE_COMPARE(characters, (std::vector{ U' ', U'0', U'1', U'C', U'a', U'b'})); + #endif } void AbstractFontConverterTest::exportFontToSingleData() { @@ -94,7 +115,12 @@ void AbstractFontConverterTest::exportFontToSingleData() { private: Features doFeatures() const override { return Feature::ConvertData|Feature::ExportFont; } - Containers::Array doExportFontToSingleData(AbstractFont&, GlyphCache&, const std::u32string&) const override { + #ifndef _WIN32 + Containers::Array doExportFontToSingleData(AbstractFont&, GlyphCache&, const std::u32string&) const override + #else + Containers::Array doExportFontToSingleData(AbstractFont&, GlyphCache&, const std::vector&) const override + #endif + { Containers::Array data(1); data[0] = 0xee; return std::move(data); @@ -115,7 +141,12 @@ void AbstractFontConverterTest::exportFontToFile() { private: Features doFeatures() const override { return Feature::ConvertData|Feature::ExportFont|Feature::MultiFile; } - std::vector>> doExportFontToData(AbstractFont&, GlyphCache&, const std::string& filename, const std::u32string&) const override { + #ifndef _WIN32 + std::vector>> doExportFontToData(AbstractFont&, GlyphCache&, const std::string& filename, const std::u32string&) const override + #else + std::vector>> doExportFontToData(AbstractFont&, GlyphCache&, const std::string& filename, const std::vector&) const override + #endif + { Containers::Array file(1); file[0] = 0xf0; diff --git a/src/TextureTools/DistanceField.h b/src/TextureTools/DistanceField.h index 811ff6ba3..b788179f9 100644 --- a/src/TextureTools/DistanceField.h +++ b/src/TextureTools/DistanceField.h @@ -51,7 +51,8 @@ field (stored in red channel in @p rectangle of @p output). The purpose of this function is to convert high-resolution binary image (such as vector artwork or font glyphs) to low-resolution grayscale image. The image will then occupy much less memory and can be scaled without aliasing issues. Additionally it provides -foundation for features like outlining, glow or drop shadow essentialy for free. +foundation for features like outlining, glow or drop shadow essentially for +free. For each pixel inside @p rectangle the algorithm looks at corresponding pixel in @p input and tries to find nearest pixel of opposite color in area given by diff --git a/src/Trade/AbstractImporter.cpp b/src/Trade/AbstractImporter.cpp index f6c53eb4a..e5e2eca1a 100644 --- a/src/Trade/AbstractImporter.cpp +++ b/src/Trade/AbstractImporter.cpp @@ -87,8 +87,8 @@ Int AbstractImporter::defaultScene() { Int AbstractImporter::doDefaultScene() { return -1; } UnsignedInt AbstractImporter::sceneCount() const { - CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::sceneCount(): no file opened", 0); - return doSceneCount(); + CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::sceneCount(): no file opened", 0); + return doSceneCount(); } UnsignedInt AbstractImporter::doSceneCount() const { return 0; } diff --git a/src/Trade/Test/AbstractImporterTest.cpp b/src/Trade/Test/AbstractImporterTest.cpp index 0e1143d9e..3dacf06dd 100644 --- a/src/Trade/Test/AbstractImporterTest.cpp +++ b/src/Trade/Test/AbstractImporterTest.cpp @@ -45,6 +45,9 @@ AbstractImporterTest::AbstractImporterTest() { void AbstractImporterTest::openFile() { class DataImporter: public Trade::AbstractImporter { + public: + explicit DataImporter(): opened(false) {} + private: Features doFeatures() const override { return Feature::OpenData; } bool doIsOpened() const override { return opened; }