diff --git a/doc/changelog.dox b/doc/changelog.dox index 4d89b2ada..feea9600f 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -148,6 +148,9 @@ See also: - New @ref Trade::AbstractMaterialData::flags(), @ref Trade::AbstractMaterialData::alphaMode() and @ref Trade::AbstractMaterialData::alphaMask() material properties +- New @ref Trade::CameraData::type(), @ref Trade::CameraData::aspectRatio() + and @ref Trade::CameraData::size() properties, ability to describe 2D + cameras - @ref Trade::ObjectData2D and @ref Trade::ObjectData3D now support also separate translation / rotation / scaling specification instead of a combined transformation matrix. See @ref Trade::ObjectData2D::transformation() @@ -382,6 +385,9 @@ See also: deprecated, use @ref Trade::AbstractMaterialData::AbstractMaterialData(MaterialType, Flags, MaterialAlphaMode, Float, const void*) and @ref Trade::PhongMaterialData::PhongMaterialData(Flags, MaterialAlphaMode, Float, Float, const void*) instead +- @ref Trade::CameraData constructor not taking an explicit type enum is + deprecated, use @ref Trade::CameraData::CameraData(CameraType, Rad, Float, Float, Float, const void*) + instead @subsection changelog-latest-compatibility Potential compatibility breakages, removed APIs diff --git a/src/Magnum/Trade/CMakeLists.txt b/src/Magnum/Trade/CMakeLists.txt index bfcfc0189..34cf4829a 100644 --- a/src/Magnum/Trade/CMakeLists.txt +++ b/src/Magnum/Trade/CMakeLists.txt @@ -39,6 +39,7 @@ set(MagnumTrade_GracefulAssert_SRCS AbstractImageConverter.cpp AbstractImporter.cpp AnimationData.cpp + CameraData.cpp ImageData.cpp ObjectData2D.cpp ObjectData3D.cpp diff --git a/src/Magnum/Trade/CameraData.cpp b/src/Magnum/Trade/CameraData.cpp new file mode 100644 index 000000000..f215a3ac1 --- /dev/null +++ b/src/Magnum/Trade/CameraData.cpp @@ -0,0 +1,63 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 + 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 "CameraData.h" + +#include +#include + +namespace Magnum { namespace Trade { + +CameraData::CameraData(const CameraType type, const Rad fov, const Float aspectRatio, const Float near, const Float far, const void* const importerState) noexcept: _type{type}, _size{2*near*Math::tan(fov*0.5f)*Vector2::yScale(1.0f/aspectRatio)}, _near{near}, _far{far}, _importerState{importerState} { + CORRADE_ASSERT(type == CameraType::Perspective3D, + "Trade::CameraData: only perspective cameras can have FoV specified", ); +} + +CameraData::CameraData(const CameraType type, const Vector2& size, const Float near, const Float far, const void* const importerState) noexcept: _type{type}, _size{size}, _near{near}, _far{far}, _importerState{importerState} { + CORRADE_ASSERT(type != CameraType::Orthographic2D || (!near && !far), + "Trade::CameraData: 2D cameras can't be specified with near and far clipping planes", ); +} + +Rad CameraData::fov() const { + CORRADE_ASSERT(_type == CameraType::Perspective3D, + "Trade::CameraData::fov(): the camera is not perspective", {}); + return 2*Math::atan(_size.x()/(2*_near)); +} + +Debug& operator<<(Debug& debug, const CameraType value) { + switch(value) { + /* LCOV_EXCL_START */ + #define _c(value) case CameraType::value: return debug << "Trade::CameraType::" #value; + _c(Orthographic2D) + _c(Orthographic3D) + _c(Perspective3D) + #undef _c + /* LCOV_EXCL_STOP */ + } + + return debug << "Trade::CameraType(" << Debug::nospace << reinterpret_cast(UnsignedByte(value)) << Debug::nospace << ")"; +} + +}} diff --git a/src/Magnum/Trade/CameraData.h b/src/Magnum/Trade/CameraData.h index 1f4fabba9..7b29ba34d 100644 --- a/src/Magnum/Trade/CameraData.h +++ b/src/Magnum/Trade/CameraData.h @@ -30,27 +30,79 @@ */ #include "Magnum/Magnum.h" -#include "Magnum/Math/Angle.h" +#include "Magnum/Math/Vector2.h" +#include "Magnum/Trade/visibility.h" namespace Magnum { namespace Trade { /** -@brief 3D camera data +@brief Camera type + +@see @ref CameraData +*/ +enum class CameraType: UnsignedByte { + Orthographic2D, /**< 2D orthographic camera */ + Orthographic3D, /**< 3D orthographic camera */ + Perspective3D /**< 3D perspective camera */ +}; + +/** +@brief Camera data + +@see @ref AbstractImporter::camera(), + @ref Math::Matrix4::perspectiveProjection(Rad, T, T, T), + @ref Math::Matrix4::perspectiveProjection(const Vector2&, T, T), + @ref Math::Matrix4::orthographicProjection(const Vector2&, T, T), + @ref Math::Matrix3::projection(const Vector2&) */ -class CameraData { +class MAGNUM_TRADE_EXPORT CameraData { public: /** - * @brief Constructor - * @param fov Field-of-view angle + * @brief Construct a camera using a field of view + * @param type Camera type + * @param fov Horizontal field-of-view angle @f$ \theta @f$ + * @param aspectRatio Horizontal:vertical aspect ratio @f$ a @f$ + * @param near Near clipping plane @f$ n @f$ + * @param far Far clipping plane @f$ f @f$. Set to + * @ref Constants::inf() for an infinite far plane. + * @param importerState Importer-specific state + * + * The constructor converts the @p fov and @p aspectRatio to near plane + * size using the following formula and stores that: @f[ + * \boldsymbol{s} = 2n \tan \left(\tfrac{\theta}{2} \right) + * \begin{pmatrix} + * 1 \\ + * \frac{1}{a} + * \end{pmatrix} + * @f] + * + * The @p type parameter has to be @ref CameraType::Perspective3D, use + * @ref CameraData(CameraType, const Vector2&, Float, Float, const void*) + * for orthographic and 2D cameras instead. + */ + explicit CameraData(CameraType type, Rad fov, Float aspectRatio, Float near, Float far, const void* importerState = nullptr) noexcept; + + /** + * @brief Construct a camera using a projection size + * @param type Camera type + * @param size Size of the near clipping plane * @param near Near clipping plane - * @param far Far clipping plane + * @param far Far clipping plane. Set to @ref Constants::inf() + * for an infinite far plane. * @param importerState Importer-specific state * - * If `NaN` is specified for any parameter, default value is used - * instead, which is `35.0_degf` for @p fov, `0.01f` for @p near and - * `100.0f` for @p far. + * For @ref CameraType::Orthographic2D, @p near and @p far is expected + * to be @cpp 0.0f @ce. */ - explicit CameraData(Rad fov, Float near, Float far, const void* importerState = nullptr) noexcept; + explicit CameraData(CameraType type, const Vector2& size, Float near, Float far, const void* importerState = nullptr) noexcept; + + #ifdef MAGNUM_BUILD_DEPRECATED + /** @brief @copybrief CameraData(CameraType, Rad, Float, Float, Float, const void*) + * @deprecated Use @ref CameraData(CameraType, Rad, Float, Float, Float, const void*) + * instead. + */ + explicit CORRADE_DEPRECATED("use CameraData(CameraType, Rad, Float, Float, const void*) instead") CameraData(Rad fov, Float near, Float far, const void* importerState = nullptr) noexcept: CameraData{CameraType::Perspective3D, fov, 1.0f, near, far, importerState} {} + #endif /** @brief Copying is not allowed */ CameraData(const CameraData&) = delete; @@ -64,13 +116,44 @@ class CameraData { /** @brief Move assignment */ CameraData& operator=(CameraData&&) noexcept = default; - /** @brief Field-of-view angle */ - Rad fov() const { return _fov; } + /** @brief Camera type */ + CameraType type() const { return _type; } + + /** + * @brief Size of the near clipping plane + * + * For @ref CameraType::Perspective3D, this property is also available + * through @ref fov() and @ref aspectRatio(). + */ + Vector2 size() const { return _size; } + + /** + * @brief Field-of-view angle + * + * Expects that @ref type() is @ref CameraType::Perspective3D. The + * value is calculated from @ref size() using the following formula: @f[ + * \theta = 2 \arctan \left( \frac{s_x}{2n} \right) + * @f] + */ + Rad fov() const; + + /** + * @brief Aspect ratio + * + * Similarly to @ref fov(), the value is calculated from @ref size(). + */ + Float aspectRatio() const { return _size.aspectRatio(); } /** @brief Near clipping plane */ Float near() const { return _near; } - /** @brief Far clipping plane */ + /** + * @brief Far clipping plane + * + * Can be set to infinity, in which case it denotes a lack of far + * clipping plane. + * @see @ref Constants::inf(), @ref Math::isInf() + */ Float far() const { return _far; } /** @@ -81,16 +164,14 @@ class CameraData { const void* importerState() const { return _importerState; } private: - Rad _fov; + CameraType _type; + Vector2 _size; Float _near, _far; const void* _importerState; }; -inline CameraData::CameraData(const Rad fov, const Float near, const Float far, const void* const importerState) noexcept: - _fov{fov != fov ? Rad{Deg{35.0f}} : fov}, - _near{near != near ? 0.01f : near}, - _far{far != far ? 100.0f : far}, - _importerState{importerState} {} +/** @debugoperatorenum{CameraType} */ +MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, CameraType value); }} diff --git a/src/Magnum/Trade/ObjectData2D.h b/src/Magnum/Trade/ObjectData2D.h index 128909f81..670e30146 100644 --- a/src/Magnum/Trade/ObjectData2D.h +++ b/src/Magnum/Trade/ObjectData2D.h @@ -44,7 +44,7 @@ namespace Magnum { namespace Trade { @see @ref ObjectData2D::instanceType() */ enum class ObjectInstanceType2D: UnsignedByte { - Camera, /**< Camera instance (see CameraData) */ + Camera, /**< Camera instance (see @ref CameraData) */ /** * Mesh instance. The data can be cast to @ref MeshObjectData2D to provide diff --git a/src/Magnum/Trade/Test/AbstractImporterTest.cpp b/src/Magnum/Trade/Test/AbstractImporterTest.cpp index 9906eb9db..10faaad9b 100644 --- a/src/Magnum/Trade/Test/AbstractImporterTest.cpp +++ b/src/Magnum/Trade/Test/AbstractImporterTest.cpp @@ -1475,7 +1475,7 @@ void AbstractImporterTest::camera() { else return {}; } Containers::Optional doCamera(UnsignedInt id) override { - if(id == 7) return CameraData{{}, {}, {}, &state}; + if(id == 7) return CameraData{{}, Vector2{}, {}, {}, &state}; else return {}; } }; diff --git a/src/Magnum/Trade/Test/CMakeLists.txt b/src/Magnum/Trade/Test/CMakeLists.txt index 9e49cc7cb..d5535cb6e 100644 --- a/src/Magnum/Trade/Test/CMakeLists.txt +++ b/src/Magnum/Trade/Test/CMakeLists.txt @@ -43,7 +43,7 @@ corrade_add_test(TradeAbstractImporterTest AbstractImporterTest.cpp target_include_directories(TradeAbstractImporterTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) corrade_add_test(TradeAnimationDataTest AnimationDataTest.cpp LIBRARIES MagnumTradeTestLib) -corrade_add_test(TradeCameraDataTest CameraDataTest.cpp LIBRARIES MagnumTrade) +corrade_add_test(TradeCameraDataTest CameraDataTest.cpp LIBRARIES MagnumTradeTestLib) corrade_add_test(TradeImageDataTest ImageDataTest.cpp LIBRARIES MagnumTradeTestLib) corrade_add_test(TradeLightDataTest LightDataTest.cpp LIBRARIES MagnumTrade) corrade_add_test(TradeMaterialDataTest MaterialDataTest.cpp LIBRARIES MagnumTradeTestLib) diff --git a/src/Magnum/Trade/Test/CameraDataTest.cpp b/src/Magnum/Trade/Test/CameraDataTest.cpp index 2b081915e..16b3ce2d5 100644 --- a/src/Magnum/Trade/Test/CameraDataTest.cpp +++ b/src/Magnum/Trade/Test/CameraDataTest.cpp @@ -23,6 +23,7 @@ DEALINGS IN THE SOFTWARE. */ +#include #include #include "Magnum/Trade/CameraData.h" @@ -32,66 +33,94 @@ namespace Magnum { namespace Trade { namespace Test { struct CameraDataTest: TestSuite::Tester { explicit CameraDataTest(); - void construct(); - void constructDefaults(); + void construct3DFoV(); + void construct3DSize(); + + void construct2D(); + void construct2DFoV(); + void construct2DNearFar(); + void constructCopy(); void constructMove(); -}; -namespace { - -using namespace Math::Literals; + void fovNonPerspective(); -enum: std::size_t { ConstructDefaultsDataCount = 3 }; - -struct { - const char* name; - Rad fov, expectedFov; - Float near, expectedNear; - Float far, expectedFar; -} ConstructDefaultsData[ConstructDefaultsDataCount]{ - {"fov", Rad{Constants::nan()}, 35.0_degf, 0.5f, 0.5f, 120.0f, 120.0f}, - {"near", 25.0_degf, 25.0_degf, Constants::nan(), 0.01f, 120.0f, 120.0f}, - {"far", 25.0_degf, 25.0_degf, 0.5f, 0.5f, Constants::nan(), 100.0f} + void debugType(); }; -} - CameraDataTest::CameraDataTest() { - addTests({&CameraDataTest::construct}); + addTests({&CameraDataTest::construct3DFoV, + &CameraDataTest::construct3DSize, + + &CameraDataTest::construct2D, + &CameraDataTest::construct2DFoV, + &CameraDataTest::construct2DNearFar, + + &CameraDataTest::constructCopy, + &CameraDataTest::constructMove, - addInstancedTests({&CameraDataTest::constructDefaults}, ConstructDefaultsDataCount); + &CameraDataTest::fovNonPerspective, - addTests({&CameraDataTest::constructCopy, - &CameraDataTest::constructMove}); + &CameraDataTest::debugType}); } -void CameraDataTest::construct() { +using namespace Math::Literals; + +void CameraDataTest::construct3DFoV() { const int a{}; - CameraData data{25.0_degf, 0.001f, 1000.0f, &a}; + CameraData data{CameraType::Perspective3D, 25.0_degf, 4.0f/3.0f, 0.1f, 1000.0f, &a}; + CORRADE_COMPARE(data.type(), CameraType::Perspective3D); + CORRADE_COMPARE(data.size(), (Vector2{0.0443389f, 0.0332542f})); CORRADE_COMPARE(data.fov(), 25.0_degf); - CORRADE_COMPARE(data.near(), 0.001f); + CORRADE_COMPARE(data.aspectRatio(), 1.333333f); + CORRADE_COMPARE(data.near(), 0.1f); CORRADE_COMPARE(data.far(), 1000.0f); CORRADE_COMPARE(data.importerState(), &a); } -void CameraDataTest::constructDefaults() { - setTestCaseDescription(ConstructDefaultsData[testCaseInstanceId()].name); +void CameraDataTest::construct3DSize() { + const int a{}; + CameraData data{CameraType::Orthographic3D, {0.03f, 0.04f}, 0.01f, 1000.0f, &a}; + + CORRADE_COMPARE(data.type(), CameraType::Orthographic3D); + CORRADE_COMPARE(data.size(), (Vector2{0.03f, 0.04f})); + CORRADE_COMPARE(data.aspectRatio(), 0.75f); + CORRADE_COMPARE(data.near(), 0.01f); + CORRADE_COMPARE(data.far(), 1000.0f); + CORRADE_COMPARE(data.importerState(), &a); +} +void CameraDataTest::construct2D() { const int a{}; - CameraData data{ - ConstructDefaultsData[testCaseInstanceId()].fov, - ConstructDefaultsData[testCaseInstanceId()].near, - ConstructDefaultsData[testCaseInstanceId()].far, - &a}; - - CORRADE_COMPARE(data.fov(), ConstructDefaultsData[testCaseInstanceId()].expectedFov); - CORRADE_COMPARE(data.near(), ConstructDefaultsData[testCaseInstanceId()].expectedNear); - CORRADE_COMPARE(data.far(), ConstructDefaultsData[testCaseInstanceId()].expectedFar); + CameraData data{CameraType::Orthographic2D, {4.0f, 2.0f}, {}, {}, &a}; + + CORRADE_COMPARE(data.type(), CameraType::Orthographic2D); + CORRADE_COMPARE(data.size(), (Vector2{4.0f, 2.0f})); + CORRADE_COMPARE(data.aspectRatio(), 2.0f); + CORRADE_COMPARE(data.near(), 0.0f); + CORRADE_COMPARE(data.far(), 0.0f); CORRADE_COMPARE(data.importerState(), &a); } +void CameraDataTest::construct2DFoV() { + std::ostringstream out; + Error redirectError{&out}; + + CameraData{CameraType::Orthographic2D, 25.0_degf, 1.0f, 0.001f, 1000.0f}; + + CORRADE_COMPARE(out.str(), "Trade::CameraData: only perspective cameras can have FoV specified\n"); +} + +void CameraDataTest::construct2DNearFar() { + std::ostringstream out; + Error redirectError{&out}; + + CameraData{CameraType::Orthographic2D, {3.0f, 4.0f}, 0.001f, 1000.0f}; + + CORRADE_COMPARE(out.str(), "Trade::CameraData: 2D cameras can't be specified with near and far clipping planes\n"); +} + void CameraDataTest::constructCopy() { CORRADE_VERIFY(!(std::is_constructible{})); CORRADE_VERIFY(!(std::is_assignable{})); @@ -99,23 +128,46 @@ void CameraDataTest::constructCopy() { void CameraDataTest::constructMove() { const int a{}; - CameraData data{25.0_degf, 0.001f, 1000.0f, &a}; + CameraData data{CameraType::Perspective3D, 25.0_degf, 2.35f, 1.0f, 1000.0f, &a}; CameraData b{std::move(data)}; + CORRADE_COMPARE(b.type(), CameraType::Perspective3D); + CORRADE_COMPARE(b.size(), (Vector2{0.443389f, 0.188676f})); CORRADE_COMPARE(b.fov(), 25.0_degf); - CORRADE_COMPARE(b.near(), 0.001f); + CORRADE_COMPARE(b.aspectRatio(), 2.35f); + CORRADE_COMPARE(b.near(), 1.0f); CORRADE_COMPARE(b.far(), 1000.0f); CORRADE_COMPARE(b.importerState(), &a); const int c{}; - CameraData d{75.0_degf, 0.5f, 10.0f, &c}; + CameraData d{CameraType::Orthographic3D, {2.0f, 1.0f}, 0.5f, 10.0f, &c}; d = std::move(b); - CORRADE_COMPARE(d.fov(), 25.0_degf); - CORRADE_COMPARE(d.near(), 0.001f); + CORRADE_COMPARE(b.type(), CameraType::Perspective3D); + CORRADE_COMPARE(b.size(), (Vector2{0.443389f, 0.188676f})); + CORRADE_COMPARE(b.fov(), 25.0_degf); + CORRADE_COMPARE(b.aspectRatio(), 2.35f); + CORRADE_COMPARE(d.near(), 1.0f); CORRADE_COMPARE(d.far(), 1000.0f); CORRADE_COMPARE(d.importerState(), &a); } +void CameraDataTest::fovNonPerspective() { + std::ostringstream out; + Error redirectError{&out}; + + CameraData a{CameraType::Orthographic2D, {3.0f, 4.0f}, {}, {}}; + a.fov(); + + CORRADE_COMPARE(out.str(), "Trade::CameraData::fov(): the camera is not perspective\n"); +} + +void CameraDataTest::debugType() { + std::ostringstream out; + + Debug{&out} << CameraType::Orthographic3D << CameraType(0xde); + CORRADE_COMPARE(out.str(), "Trade::CameraType::Orthographic3D Trade::CameraType(0xde)\n"); +} + }}} CORRADE_TEST_MAIN(Magnum::Trade::Test::CameraDataTest) diff --git a/src/Magnum/Trade/Trade.h b/src/Magnum/Trade/Trade.h index b696ace1f..793717ab1 100644 --- a/src/Magnum/Trade/Trade.h +++ b/src/Magnum/Trade/Trade.h @@ -48,6 +48,7 @@ enum class AnimationTrackType: UnsignedByte; class AnimationTrackData; class AnimationData; +enum class CameraType: UnsignedByte; class CameraData; template class ImageData;