Browse Source

Trade: add attenuation, range and cone angle parameters to LightData.

And document how all those go together.
pull/470/head
Vladimír Vondruš 6 years ago
parent
commit
2b987499e2
  1. 6
      doc/changelog.dox
  2. 25
      doc/snippets/MagnumTrade.cpp
  3. 2
      src/Magnum/Trade/CMakeLists.txt
  4. 36
      src/Magnum/Trade/LightData.cpp
  5. 393
      src/Magnum/Trade/LightData.h
  6. 2
      src/Magnum/Trade/Test/CMakeLists.txt
  7. 364
      src/Magnum/Trade/Test/LightDataTest.cpp

6
doc/changelog.dox

@ -99,6 +99,8 @@ See also:
- New @ref Trade::SkinData class and @ref Trade::AbstractImporter::skin2D() /
@ref Trade::AbstractImporter::skin3D() family of APIs for skin import, as
well as support in @ref Trade::AnySceneImporter "AnySceneImporter"
- @ref Trade::LightData got extended to support light attenuation and range
parameters as well and spot light inner and outer angle
@subsection changelog-latest-changes Changes and improvements
@ -283,6 +285,10 @@ See also:
interfaces, which are also @cpp const @ce and can't fail. Documentation of
each function was expanded to suggest a recommended place for potential
error handling.
- @cpp Trade::LightData::Type::Infinite @ce, originally adapted from the
OpenGEX specification, is deprecated in favor of
@ref Trade::LightData::Type::Directional as that's the more commonly used
term
@section changelog-2020-06 2020.06

25
doc/snippets/MagnumTrade.cpp

@ -40,6 +40,7 @@
#include "Magnum/Trade/AbstractImporter.h"
#include "Magnum/Trade/AnimationData.h"
#include "Magnum/Trade/ImageData.h"
#include "Magnum/Trade/LightData.h"
#include "Magnum/Trade/MaterialData.h"
#include "Magnum/Trade/MeshData.h"
#include "Magnum/Trade/ObjectData2D.h"
@ -254,6 +255,30 @@ for(auto&& row: data.mutablePixels<Color3ub>())
/* [ImageData-usage-mutable] */
}
{
/* [LightData-populating-range] */
Trade::LightData data{Trade::LightData::Type::Point,
0xfff3d6_srgbf, 1.0f,
15.0f};
/* [LightData-populating-range] */
}
{
/* [LightData-populating-attenuation] */
Trade::LightData data{Trade::LightData::Type::Spot,
0xf3d6ff_srgbf, 10.0f,
{0.01f, 0.5f, 2.0f},
25.0_degf, 55.0_degf};
/* [LightData-populating-attenuation] */
}
{
/* [LightData-populating-none] */
Trade::LightData data{Trade::LightData::Type::Directional,
0xd6fff3_srgbf, 0.25f};
/* [LightData-populating-none] */
}
{
/* [MaterialAttributeData-name] */
Trade::MaterialAttributeData a{

2
src/Magnum/Trade/CMakeLists.txt

@ -28,7 +28,6 @@ find_package(Corrade REQUIRED PluginManager)
set(MagnumTrade_SRCS
ArrayAllocator.cpp
Data.cpp
LightData.cpp
MeshObjectData2D.cpp
MeshObjectData3D.cpp
SceneData.cpp
@ -42,6 +41,7 @@ set(MagnumTrade_GracefulAssert_SRCS
CameraData.cpp
FlatMaterialData.cpp
ImageData.cpp
LightData.cpp
MaterialData.cpp
MeshData.cpp
ObjectData2D.cpp

36
src/Magnum/Trade/LightData.cpp

@ -27,6 +27,40 @@
namespace Magnum { namespace Trade {
using namespace Math::Literals;
LightData::LightData(const Type type, const Color3& color, const Float intensity, const Vector3& attenuation, const Float range, const Rad innerConeAngle, const Rad outerConeAngle, const void* const importerState) noexcept: _type{type}, _color{color}, _intensity{intensity}, _attenuation{attenuation}, _range{range}, _innerConeAngle{innerConeAngle}, _outerConeAngle{outerConeAngle}, _importerState{importerState} {
CORRADE_ASSERT(_type != Type::Spot || (Deg(_innerConeAngle) >= 0.0_degf && _innerConeAngle <= _outerConeAngle && Deg(_outerConeAngle) <= 360.0_degf),
"Trade::LightData: spot light inner and outer cone angles have to be in range [0°, 360°] and inner not larger than outer but got" << Deg(_innerConeAngle) << "and" << Deg(_outerConeAngle), );
CORRADE_ASSERT(_type == Type::Spot || (Math::equal(Deg(_innerConeAngle),360.0_degf) && Math::equal(Deg(_outerConeAngle), 360.0_degf)),
"Trade::LightData: cone angles have to be 360° for lights that aren't spot but got" << Deg(_innerConeAngle) << "and" << Deg(_outerConeAngle), );
CORRADE_ASSERT(_type != Type::Directional || (_attenuation == Vector3{1.0f, 0.0f, 0.0f}),
"Trade::LightData: attenuation has to be (1, 0, 0) for a directional light but got" << _attenuation, );
CORRADE_ASSERT(_type != Type::Directional || _range == Constants::inf(),
"Trade::LightData: range has to be infinity for a directional light but got" << _range, );
}
LightData::LightData(const Type type, const Color3& color, const Float intensity, const Vector3& attenuation, const Float range, const void* const importerState) noexcept: LightData{type, color, intensity, attenuation, range,
type == Type::Spot ? 0.0_degf : 360.0_degf,
type == Type::Spot ? 45.0_degf : 360.0_degf,
importerState} {}
LightData::LightData(const Type type, const Color3& color, const Float intensity, const Vector3& attenuation, const Rad innerConeAngle, const Rad outerConeAngle, const void* const importerState) noexcept: LightData{type, color, intensity, attenuation, Constants::inf(), innerConeAngle, outerConeAngle, importerState} {}
LightData::LightData(const Type type, const Color3& color, const Float intensity, const Vector3& attenuation, const void* const importerState) noexcept: LightData{type, color, intensity, attenuation, Constants::inf(), importerState} {}
LightData::LightData(const Type type, const Color3& color, const Float intensity, const Float range, const Rad innerConeAngle, const Rad outerConeAngle, const void* const importerState) noexcept: LightData{type, color, intensity,
type == Type::Directional ? Vector3{1.0f, 0.0f, 0.0f} : Vector3{0.0f, 0.0f, 1.0f},
range, innerConeAngle, outerConeAngle, importerState} {}
LightData::LightData(const Type type, const Color3& color, const Float intensity, const Float range, const void* const importerState) noexcept: LightData{type, color, intensity,
type == Type::Directional ? Vector3{1.0f, 0.0f, 0.0f} : Vector3{0.0f, 0.0f, 1.0f},
range, importerState} {}
LightData::LightData(const Type type, const Color3& color, const Float intensity, const Rad innerConeAngle, const Rad outerConeAngle, const void* const importerState) noexcept: LightData{type, color, intensity, Constants::inf(), innerConeAngle, outerConeAngle, importerState} {}
LightData::LightData(const Type type, const Color3& color, const Float intensity, const void* const importerState) noexcept: LightData{type, color, intensity, Constants::inf(), importerState} {}
#ifndef DOXYGEN_GENERATING_OUTPUT
Debug& operator<<(Debug& debug, const LightData::Type value) {
debug << "Trade::LightData::Type" << Debug::nospace;
@ -34,7 +68,7 @@ Debug& operator<<(Debug& debug, const LightData::Type value) {
switch(value) {
/* LCOV_EXCL_START */
#define _c(value) case LightData::Type::value: return debug << "::" #value;
_c(Infinite)
_c(Directional)
_c(Point)
_c(Spot)
#undef _c

393
src/Magnum/Trade/LightData.h

@ -38,9 +38,83 @@ namespace Magnum { namespace Trade {
/**
@brief Light data
@section Trade-LightData-usage Usage
The class exposes light parameters in a way that makes sense as a whole,
allowing to reduce branching in application code --- e.g., a light defined by
just its range has the quadratic attenuation factor set to one, with constant
and linear attenuation being zero, or spot cone angles are the full circle
everything except spotlights.
@section Trade-LightData-populating Populating an instance
You can choose a constructor overload that matches the subset of input
parameters and let the class set the rest implicitly. For example, a
@ref Type::Point light constructed using a range will have @ref attenuation()
implicitly set to @cpp {0.0f, 0.0f, 1.0f} @ce and cone angles to
@cpp 360.0_degf @ce:
@snippet MagnumTrade.cpp LightData-populating-range
Or, a @ref Type::Spot light constructed from a constant / linear / quadratic
attenuation will have @ref range() implicitly set to @ref Constants::inf():
@snippet MagnumTrade.cpp LightData-populating-attenuation
And a @ref Type::Directional light that doesn't attenuate can be constructed
without either, causing @ref attenuation() to be @cpp {1.0f, 0.0f, 0.0f} @ce
and @ref range() @ref Constants::inf(), cancelling out the attenuation equation:
@snippet MagnumTrade.cpp LightData-populating-none
@section Trade-LightData-attenuation Attenuation calculation
To support all common lighting calculations, the class exposes parameters in a
combined equation containing both constant / linear / quadratic attenuation
@f$ \color{m-success} K_c @f$ / @f$ \color{m-success} K_l @f$ /
@f$ \color{m-success} K_q @f$ and a range parameter @f$ \color{m-info} R @f$
over a distance @f$ d @f$: @f[
F_{att} = \frac{\operatorname{clamp}(1 - (\frac{d}{\color{m-info} R})^4, 0, 1)^2}{{\color{m-success} K_c} + {\color{m-success} K_l} d + {\color{m-success} K_q} d^2}
@f]
In most cases you'll have the light data using either one or the other
approach. The classic constant/linear/quadratic equation allows for most
control, but because the attenuated intensity never really reaches zero, it
makes light culling optimizations hard to perform. In this case the
@ref range() is set to @ref Constants::inf(): @f[
F_{att} = \lim_{{\color{m-info} R} \to \infty} \frac{{\color{m-dim} \operatorname{clamp}(}1 {\color{m-dim}- (\frac{d}{R})^4, 0, 1)^2}}{{\color{m-success} K_c} + {\color{m-success} K_l} d + {\color{m-success} K_q} d^2} = \frac{1}{{\color{m-success} K_c} + {\color{m-success} K_l} d + {\color{m-success} K_q} d^2}
@f]
The range-based equation approaches zero when @f$ {\color{m-info} R} = d @f$
and provides a good tradeoff for performance while staying mostly
physically-based. This is modelled after the glTF [KHR_lights_punctual](https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual#range-property)
extension, which in turn is based on the [UE4 implementation](https://github.com/KhronosGroup/glTF/pull/1223#issuecomment-387956919).
In this case, @ref attenuation() is set to
@cpp {0.0f, 0.0f, 1.0f} @ce: @f[
F_{att} = \frac{\operatorname{clamp}(1 - (\frac{d}{\color{m-info} R})^4, 0, 1)^2}{{\color{m-dim} K_c + K_l d + K_q} d^2} = \frac{\operatorname{clamp}(1 - (\frac{d}{\color{m-info} R})^4, 0, 1)^2}{d^2}
@f]
If @f$ {\color{m-info} R} \to \infty @f$ as well, the equation reduces down to
a simple inverse square: @f[
F_{att} = \lim_{{\color{m-info} R} \to \infty} \frac{{\color{m-dim} \operatorname{clamp}(} 1 {\color{m-dim} - (\frac{d}{R})^4, 0, 1)^2}}{{\color{m-dim} K_c + K_l d + K_q} d^2} = \frac{1}{d^2}
@f]
As a special case, a @ref Type::Directional light is defined by
@ref attenuation() set to @cpp {1.0f, 0.0f, 0.0f} @ce and @ref range() to
@ref Constants::inf() --- thus without any attenuation: @f[
F_{att} = \lim_{{\color{m-info} R} \to \infty} \frac{{\color{m-dim} \operatorname{clamp}(} 1 {\color{m-dim} - (\frac{d}{R})^4, 0, 1)^2}}{{\color{m-success} K_c} {\color{m-dim} + K_l d + K_q d^2}} = 1
@f]
@section Trade-LightData-units Units
To follow physically-based principles in lighting calculation, intensity is
assumed to be in in *candela* (lm/sr) for @ref Type::Point and @ref Type::Spot,
and in *lux* (lm/m<sup>2</sup>) for @ref Type::Directional. Distance @f$ d @f$
is in meters.
@see @ref AbstractImporter::light()
*/
class LightData {
class MAGNUM_TRADE_EXPORT LightData {
public:
/**
* @brief Light type
@ -49,18 +123,38 @@ class LightData {
*/
enum class Type: UnsignedByte {
/**
* Light at position that is infinitely far away so its rays are
* parallel. The light rays point in a direction of negative Z
* axis.
* Light at a position that is infinitely far away, emitted in a
* direction of negative Z axis. The rotation is inherited from
* absolute object transformation; scale and position don't affect
* the light in any way. Because the light is at infinite distance,
* it's not attenuated in any way.
* @m_since_latest
*/
Infinite,
Directional,
/** Point light, radiating in all directions */
#ifdef MAGNUM_BUILD_DEPRECATED
/**
* Directional light.
* @m_deprecated_since_latest Use @ref Type::Directional instead.
*/
Infinite CORRADE_DEPRECATED_ENUM("use Type::Directional instead") = Directional,
#endif
/**
* Point light, emitting light in all directions. The position is
* inherited from absolute object transformation; scale and
* rotation don't affect the light in any way. Brightness
* attenuates depending on the @ref range() value.
*/
Point,
/**
* Spot light, radiating in a limited range of direction. The
* primary direction is negative Z axis.
* Spot light, emitting light in a cone in direction of local
* negative Z axis. The position and rotation is inherited from
* absolute object transformation; scale doesn't affect the light
* in any way. The angle and falloff of the cone is defined using
* @ref innerConeAngle() and @ref outerConeAngle() and brightness
* attenuates depending on the @ref range() value.
*/
Spot
};
@ -70,9 +164,220 @@ class LightData {
* @param type Light type
* @param color Light color
* @param intensity Light intensity
* @param attenuation Constant, linear and quadratic light
* attenuation factor. Expected to be @cpp {1.0f, 0.0f, 0.0f} @ce
* for a @ref Type::Directional light.
* @param range Light range, after which the intensity is
* considered to be zero. Expected to be @ref Constants::inf() for
* a @ref Type::Directional light.
* @param innerConeAngle Inner cone angle. Expected to be greater
* than or equal to @cpp 0.0_degf @ce and less than or equal to
* @p outerConeAngle for a @ref Type::Spot light,
* @cpp 360.0_degf @ce otherwise.
* @param outerConeAngle Inner cone angle. Expected to be greater
* than or equal to @p innerConeAngle and less than or equal to
* @cpp 360.0_degf @ce for a @ref Type::Spot light,
* @cpp 360.0_degf @ce otherwise.
* @param importerState Importer-specific state
* @m_since_latest
*
* This is a combined constructor including both attenuation and range
* parameters. Use @ref LightData(Type, const Color3&, Float, const Vector3&, Rad, Rad, const void*)
* for light data defined by just attenuation parameters and
* @ref LightData(Type, const Color3&, Float, Float, Rad, Rad, const void*)
* for light data defined by a range alone, and
* @ref LightData(Type, const Color3&, Float, Rad, Rad, const void*)
* for an implicit inverse square attenuation. See
* @ref Trade-LightData-attenuation for more information.
*
* For lights other than spot it may be more convenient to use
* @ref LightData(Type, const Color3&, Float, const Vector3&, Float, const void*)
* and friends instead.
*/
explicit LightData(Type type, const Color3& color, Float intensity, const Vector3& attenuation, Float range, Rad innerConeAngle, Rad outerConeAngle, const void* importerState = nullptr) noexcept;
/**
* @brief Construct with implicit cone angles
* @param type Light type
* @param color Light color
* @param intensity Light intensity
* @param attenuation Constant, linear and quadratic light
* attenuation factor. Expected to be @cpp {1.0f, 0.0f, 0.0f} @ce
* for a @ref Type::Directional light.
* @param range Light range, after which the intensity is
* considered to be zero. Expected to be @ref Constants::inf() for
* a @ref Type::Directional light.
* @param importerState Importer-specific state
* @m_since_latest
*
* This is a combined constructor including both attenuation and range
* parameters. Use @ref LightData(Type, const Color3&, Float, const Vector3&, const void*)
* for light data defined by just attenuation parameters and
* @ref LightData(Type, const Color3&, Float, Float, const void*) for
* light data defined by a range alone, and
* @ref LightData(Type, const Color3&, Float, const void*) for an
* implicit inverse square attenuation. See
* @ref Trade-LightData-attenuation for more information.
*
* For a @ref Type::Spot light, @ref innerConeAngle() is implicitly set
* to @cpp 0.0_degf @ce and @ref outerConeAngle() to @cpp 45.0_degf @ce,
* and both are @cpp 360.0_degf @ce otherwise. Use
* @ref LightData(Type, const Color3&, Float, const Vector3&, Float, Rad, Rad, const void*)
* in order to specify cone angles as well.
*/
explicit LightData(Type type, const Color3& color, Float intensity, const Vector3& attenuation, Float range, const void* importerState = nullptr) noexcept;
/**
* @brief Construct attenuation-based light data
* @param type Light type
* @param color Light color
* @param intensity Light intensity
* @param attenuation Constant, linear and quadratic light
* attenuation factor. Expected to be @cpp {1.0f, 0.0f, 0.0f} @ce
* for a @ref Type::Directional light.
* @param innerConeAngle Inner cone angle. Expected to be greater
* than or equal to @cpp 0.0_degf @ce and less than or equal to
* @p outerConeAngle for a @ref Type::Spot light,
* @cpp 360.0_degf @ce otherwise.
* @param outerConeAngle Inner cone angle. Expected to be greater
* than or equal to @p innerConeAngle and less than or equal to
* @cpp 360.0_degf @ce for a @ref Type::Spot light,
* @cpp 360.0_degf @ce otherwise.
* @param importerState Importer-specific state
* @m_since_latest
*
* The @ref range() is implicitly set to @ref Constants::inf(). See
* @ref Trade-LightData-attenuation for more information.
*
* For lights other than spot it may be more convenient to use
* @ref LightData(Type, const Color3&, Float, const Vector3&, const void*)
* instead.
*/
explicit LightData(Type type, const Color3& color, Float intensity, const Vector3& attenuation, Rad innerConeAngle, Rad outerConeAngle, const void* importerState = nullptr) noexcept;
/**
* @brief Construct attenuation-based light data with implicit cone angles
* @param type Light type
* @param color Light color
* @param intensity Light intensity
* @param attenuation Constant, linear and quadratic light
* attenuation factor. Expected to be @cpp {1.0f, 0.0f, 0.0f} @ce
* for a @ref Type::Directional light.
* @param importerState Importer-specific state
* @m_since_latest
*
* The @ref range() is implicitly set to @ref Constants::inf(). See
* @ref Trade-LightData-attenuation for more information.
*
* For a @ref Type::Spot light, @ref innerConeAngle() is implicitly set
* to @cpp 0.0_degf @ce and @ref outerConeAngle() to @cpp 45.0_degf @ce,
* and both are @cpp 360.0_degf @ce otherwise. Use
* @ref LightData(Type, const Color3&, Float, const Vector3&, Rad, Rad, const void*)
* in order to specify cone angles as well.
*/
explicit LightData(Type type, const Color3& color, Float intensity, const Vector3& attenuation, const void* importerState = nullptr) noexcept;
/**
* @brief Construct range-based light data
* @param type Light type
* @param color Light color
* @param intensity Light intensity
* @param range Light range, after which the intensity is
* considered to be zero. Expected to be @ref Constants::inf() for
* a @ref Type::Directional light.
* @param innerConeAngle Inner cone angle. Expected to be greater
* than or equal to @cpp 0.0_degf @ce and less than or equal to
* @p outerConeAngle for a @ref Type::Spot light,
* @cpp 360.0_degf @ce otherwise.
* @param outerConeAngle Inner cone angle. Expected to be greater
* than or equal to @p innerConeAngle and less than or equal to
* @cpp 360.0_degf @ce for a @ref Type::Spot light,
* @cpp 360.0_degf @ce otherwise.
* @param importerState Importer-specific state
* @m_since_latest
*
* The @ref attenuation() is implicitly set to @cpp {0.0f, 0.0f, 1.0f} @ce
* for a @ref Type::Point and @ref Type::Spot light and to
* @cpp {1.0f, 0.0f, 0.0f} @ce for a @ref Type::Directional light. See
* @ref Trade-LightData-attenuation for more information.
*
* For lights other than spot it may be more convenient to use
* @ref LightData(Type, const Color3&, Float, Float, const void*)
* instead.
*/
explicit LightData(Type type, const Color3& color, Float intensity, Float range, Rad innerConeAngle, Rad outerConeAngle, const void* importerState = nullptr) noexcept;
/**
* @brief Construct range-based light data with implicit cone angles
* @param type Light type
* @param color Light color
* @param intensity Light intensity
* @param range Light range, after which the intensity is
* considered to be zero. Expected to be @ref Constants::inf() for
* a @ref Type::Directional light.
* @param importerState Importer-specific state
* @m_since_latest
*
* The @ref attenuation() is implicitly set to @cpp {0.0f, 0.0f, 1.0f} @ce
* for a @ref Type::Point and @ref Type::Spot light and to
* @cpp {1.0f, 0.0f, 0.0f} @ce for a @ref Type::Directional light. See
* @ref Trade-LightData-attenuation for more information.
*
* For a @ref Type::Spot light, @ref innerConeAngle() is implicitly set
* to @cpp 0.0_degf @ce and @ref outerConeAngle() to @cpp 45.0_degf @ce,
* and both are @cpp 360.0_degf @ce otherwise. Use
* @ref LightData(Type, const Color3&, Float, Float, Rad, Rad, const void*)
* in order to specify cone angles as well.
*/
explicit LightData(Type type, const Color3& color, Float intensity, Float range, const void* importerState = nullptr) noexcept;
/**
* @brief Construct light data with implicit attenuation
* @param type Light type
* @param color Light color
* @param intensity Light intensity
* @param innerConeAngle Inner cone angle. Expected to be greater
* than or equal to @cpp 0.0_degf @ce and less than or equal to
* @p outerConeAngle for a @ref Type::Spot light,
* @cpp 360.0_degf @ce otherwise.
* @param outerConeAngle Inner cone angle. Expected to be greater
* than or equal to @p innerConeAngle and less than or equal to
* @cpp 360.0_degf @ce for a @ref Type::Spot light,
* @cpp 360.0_degf @ce otherwise.
* @param importerState Importer-specific state
* @m_since_latest
*
* The @ref attenuation() is implicitly set to @cpp {0.0f, 0.0f, 1.0f} @ce
* for a @ref Type::Point and @ref Type::Spot light and to
* @cpp {1.0f, 0.0f, 0.0f} @ce for a @ref Type::Directional light;
* @ref range() is always @ref Constants::inf(). See
* @ref Trade-LightData-attenuation for more information.
*
* For lights other than spot it may be more convenient to use
* @ref LightData(Type, const Color3&, Float, const void*) instead.
*/
explicit LightData(Type type, const Color3& color, Float intensity, Rad innerConeAngle, Rad outerConeAngle, const void* importerState = nullptr) noexcept;
/**
* @brief Construct light data with implicit attenuation and cone angles
* @param type Light type
* @param color Light color
* @param intensity Light intensity
* @param importerState Importer-specific state
*
* The @ref attenuation() is implicitly set to @cpp {0.0f, 0.0f, 1.0f} @ce
* for a @ref Type::Point and @ref Type::Spot light and to
* @cpp {1.0f, 0.0f, 0.0f} @ce for a @ref Type::Directional light;
* @ref range() is always @ref Constants::inf(). See
* @ref Trade-LightData-attenuation for more information.
*
* For a @ref Type::Spot light, @ref innerConeAngle() is implicitly set
* to @cpp 0.0_degf @ce and @ref outerConeAngle() to @cpp 45.0_degf @ce,
* and both are @cpp 360.0_degf @ce otherwise. Use
* @ref LightData(Type, const Color3&, Float, Rad, Rad, const void*) in
* order to specify cone angles as well.
*/
constexpr explicit LightData(Type type, const Color3& color, Float intensity, const void* importerState = nullptr) noexcept: _type{type}, _color{color}, _intensity{intensity}, _importerState{importerState} {}
explicit LightData(Type type, const Color3& color, Float intensity, const void* importerState = nullptr) noexcept;
/** @brief Copying is not allowed */
LightData(const LightData&) = delete;
@ -87,13 +392,72 @@ class LightData {
LightData& operator=(LightData&&) noexcept = default;
/** @brief Light type */
constexpr Type type() const { return _type; }
Type type() const { return _type; }
/** @brief Light color */
constexpr Color3 color() const { return _color; }
Color3 color() const { return _color; }
/**
* @brief Light intensity
*
* Defined in *candela* (lm/sr) for @ref Type::Point and
* @ref Type::Spot, and in *lux* (lm/m<sup>2</sup>) for
* @ref Type::Directional.
*/
Float intensity() const { return _intensity; }
/**
* @brief Constant, linear and quadratic light attenuation
* @m_since_latest
*
* Values of @f$ \color{m-success} K_c @f$,
* @f$ \color{m-success} K_l @f$ and @f$ \color{m-success} K_q @f$ in
* the @ref Trade-LightData-attenuation "attenuation equation". Always
* @cpp {1.0f, 0.0f, 0.0f} @ce for a @ref Type::Directional
* light, set to @cpp {0.0f, 0.0f, 1.0f} @ce for range-based
* attenuation --- and if @ref range() is @ref Constants::inf() as
* well, the attenuation equation is simply
* @f$ F_{att} = \frac{1}{d^2} @f$.
*/
Vector3 attenuation() const { return _attenuation; }
/** @brief Light intensity */
constexpr Float intensity() const { return _intensity; }
/**
* @brief Light range
* @m_since_latest
*
* Value of @f$ \color{m-info} R @f$ in
* the @ref Trade-LightData-attenuation "attenuation equation". If set
* to @ref Constants::inf(), then:
*
* - if @ref attenuation() is @cpp {0.0f, 0.0f, 1.0f} @ce, the
* attenuation equation is @f$ F_{att} = \frac{1}{d^2} @f$;
* - if @ref attenuation() is @cpp {1.0f, 0.0f, 0.0f} @ce, the
* attenuation equation is @f$ F_{att} = 1 @f$.
*
* The latter is always the case for a @ref Type::Directional light.
*/
Float range() const { return _range; }
/**
* @brief Inner cone angle
* @m_since_latest
*
* For a @ref Type::Spot light, it's always less than
* @ref outerConeAngle(). For a @ref Type::Directional or
* @ref Type::Point light it's always @cpp 360.0_degf @ce.
*/
Rad innerConeAngle() const { return _innerConeAngle; }
/**
* @brief Outer cone angle
* @m_since_latest
*
* For a @ref Type::Spot light, it's always greater than
* @ref outerConeAngle() and less than or equal to @cpp 90.0_degf @ce.
* For a @ref Type::Directional or @ref Type::Point light it's always
* @cpp 360.0_degf @ce.
*/
Rad outerConeAngle() const { return _outerConeAngle; }
/**
* @brief Importer-specific state
@ -106,6 +470,9 @@ class LightData {
Type _type;
Vector3 _color;
Float _intensity;
Vector3 _attenuation;
Float _range;
Rad _innerConeAngle, _outerConeAngle;
const void* _importerState;
};

2
src/Magnum/Trade/Test/CMakeLists.txt

@ -50,7 +50,7 @@ corrade_add_test(TradeAnimationDataTest AnimationDataTest.cpp LIBRARIES MagnumTr
corrade_add_test(TradeCameraDataTest CameraDataTest.cpp LIBRARIES MagnumTradeTestLib)
corrade_add_test(TradeDataTest DataTest.cpp LIBRARIES MagnumTrade)
corrade_add_test(TradeImageDataTest ImageDataTest.cpp LIBRARIES MagnumTradeTestLib)
corrade_add_test(TradeLightDataTest LightDataTest.cpp LIBRARIES MagnumTrade)
corrade_add_test(TradeLightDataTest LightDataTest.cpp LIBRARIES MagnumTradeTestLib)
corrade_add_test(TradeMaterialDataTest MaterialDataTest.cpp LIBRARIES MagnumTradeTestLib)
corrade_add_test(TradeMeshDataTest MeshDataTest.cpp LIBRARIES MagnumTradeTestLib)
corrade_add_test(TradeObjectData2DTest ObjectData2DTest.cpp LIBRARIES MagnumTradeTestLib)

364
src/Magnum/Trade/Test/LightDataTest.cpp

@ -26,6 +26,7 @@
#include <sstream>
#include <Corrade/TestSuite/Tester.h>
#include <Corrade/Utility/DebugStl.h>
#include <Corrade/Utility/FormatStl.h>
#include "Magnum/Trade/LightData.h"
@ -35,62 +36,363 @@ struct LightDataTest: TestSuite::Tester {
explicit LightDataTest();
void construct();
void constructAttenuation();
void constructRange();
void constructNone();
void constructInvalid();
void constructCopy();
void constructMove();
void debugType();
};
using namespace Math::Literals;
const struct {
const char* name;
LightData::Type type;
Vector3 attenuation;
Float range;
Rad innerConeAngle;
Rad outerConeAngle;
const char* message;
} ConstructInvalidData[] {
{"invalid directional attenuation", LightData::Type::Directional,
{0.0f, 0.0f, 1.0f}, Constants::inf(), 360.0_degf, 360.0_degf,
"attenuation has to be (1, 0, 0) for a directional light but got Vector(0, 0, 1)"},
{"invalid directional range", LightData::Type::Directional,
{1.0f, 0.0f, 0.0f}, 2.0f, 360.0_degf, 360.0_degf,
"range has to be infinity for a directional light but got 2"},
{"invalid point angles", LightData::Type::Point,
{0.0f, 0.0f, 1.0f}, Constants::inf(), 15.0_degf, 90.0_degf,
"cone angles have to be 360° for lights that aren't spot but got Deg(15) and Deg(90)"},
{"negative inner spot angle", LightData::Type::Spot,
{0.0f, 0.0f, 1.0f}, Constants::inf(), -1.0_degf, 90.0_degf,
"spot light inner and outer cone angles have to be in range [0°, 360°] and inner not larger than outer but got Deg(-1) and Deg(90)"},
{"too big outer spot angle", LightData::Type::Spot,
{0.0f, 0.0f, 1.0f}, Constants::inf(), 0.0_degf, 361.0_degf,
"spot light inner and outer cone angles have to be in range [0°, 360°] and inner not larger than outer but got Deg(0) and Deg(361)"},
{"inner spot angle larger than outer", LightData::Type::Spot,
{0.0f, 0.0f, 1.0f}, Constants::inf(), 35.0_degf, 30.0_degf,
"spot light inner and outer cone angles have to be in range [0°, 360°] and inner not larger than outer but got Deg(35) and Deg(30)"}
};
LightDataTest::LightDataTest() {
addTests({&LightDataTest::construct,
&LightDataTest::constructCopy,
&LightDataTest::constructAttenuation,
&LightDataTest::constructRange,
&LightDataTest::constructNone});
addInstancedTests({&LightDataTest::constructInvalid},
Containers::arraySize(ConstructInvalidData));
addTests({&LightDataTest::constructCopy,
&LightDataTest::constructMove,
&LightDataTest::debugType});
}
void LightDataTest::construct() {
using namespace Math::Literals;
const int a{};
LightData data{LightData::Type::Infinite, 0xccff33_rgbf, 0.8f, &a};
CORRADE_COMPARE(data.type(), LightData::Type::Infinite);
CORRADE_COMPARE(data.color(), 0xccff33_rgbf);
CORRADE_COMPARE(data.intensity(), 0.8f);
CORRADE_COMPARE(data.importerState(), &a);
{
int a;
LightData data{LightData::Type::Spot,
0xccff33_rgbf, 0.8f,
{0.1f, 0.5f, 0.7f}, 15.0f,
15.0_degf, 35.0_degf,
&a};
CORRADE_COMPARE(data.type(), LightData::Type::Spot);
CORRADE_COMPARE(data.color(), 0xccff33_rgbf);
CORRADE_COMPARE(data.intensity(), 0.8f);
CORRADE_COMPARE(data.attenuation(), (Vector3{0.1f, 0.5f, 0.7f}));
CORRADE_COMPARE(data.range(), 15.0f);
CORRADE_COMPARE(data.innerConeAngle(), 15.0_degf);
CORRADE_COMPARE(data.outerConeAngle(), 35.0_degf);
CORRADE_COMPARE(data.importerState(), &a);
/* Implicit spot angles */
} {
int a;
LightData data{LightData::Type::Spot,
0xccff33_rgbf, 0.8f,
{0.1f, 0.5f, 0.7f}, 15.0f,
&a};
CORRADE_COMPARE(data.type(), LightData::Type::Spot);
CORRADE_COMPARE(data.color(), 0xccff33_rgbf);
CORRADE_COMPARE(data.intensity(), 0.8f);
CORRADE_COMPARE(data.attenuation(), (Vector3{0.1f, 0.5f, 0.7f}));
CORRADE_COMPARE(data.range(), 15.0f);
CORRADE_COMPARE(data.innerConeAngle(), 0.0_degf);
CORRADE_COMPARE(data.outerConeAngle(), 45.0_degf);
CORRADE_COMPARE(data.importerState(), &a);
/* Implicit non-spot angles */
} {
int a;
LightData data{LightData::Type::Point,
0xccff33_rgbf, 0.8f,
{0.1f, 0.5f, 0.7f}, 15.0f,
&a};
CORRADE_COMPARE(data.type(), LightData::Type::Point);
CORRADE_COMPARE(data.color(), 0xccff33_rgbf);
CORRADE_COMPARE(data.intensity(), 0.8f);
CORRADE_COMPARE(data.attenuation(), (Vector3{0.1f, 0.5f, 0.7f}));
CORRADE_COMPARE(data.range(), 15.0f);
CORRADE_COMPARE(data.innerConeAngle(), 360.0_degf);
CORRADE_COMPARE(data.outerConeAngle(), 360.0_degf);
CORRADE_COMPARE(data.importerState(), &a);
}
}
void LightDataTest::constructAttenuation() {
/* Implicit range */
{
int a;
LightData data{LightData::Type::Spot,
0xccff33_rgbf, 0.8f,
{0.1f, 0.5f, 0.7f},
15.0_degf, 35.0_degf,
&a};
CORRADE_COMPARE(data.type(), LightData::Type::Spot);
CORRADE_COMPARE(data.color(), 0xccff33_rgbf);
CORRADE_COMPARE(data.intensity(), 0.8f);
CORRADE_COMPARE(data.attenuation(), (Vector3{0.1f, 0.5f, 0.7f}));
CORRADE_COMPARE(data.range(), Constants::inf());
CORRADE_COMPARE(data.innerConeAngle(), 15.0_degf);
CORRADE_COMPARE(data.outerConeAngle(), 35.0_degf);
CORRADE_COMPARE(data.importerState(), &a);
/* Implicit range + spot angles */
} {
int a;
LightData data{LightData::Type::Spot,
0xccff33_rgbf, 0.8f,
{0.1f, 0.5f, 0.7f},
&a};
CORRADE_COMPARE(data.type(), LightData::Type::Spot);
CORRADE_COMPARE(data.color(), 0xccff33_rgbf);
CORRADE_COMPARE(data.intensity(), 0.8f);
CORRADE_COMPARE(data.attenuation(), (Vector3{0.1f, 0.5f, 0.7f}));
CORRADE_COMPARE(data.range(), Constants::inf());
CORRADE_COMPARE(data.innerConeAngle(), 0.0_degf);
CORRADE_COMPARE(data.outerConeAngle(), 45.0_degf);
CORRADE_COMPARE(data.importerState(), &a);
/* Implicit range + non-spot angles */
} {
int a;
LightData data{LightData::Type::Point,
0xccff33_rgbf, 0.8f,
{0.1f, 0.5f, 0.7f},
&a};
CORRADE_COMPARE(data.type(), LightData::Type::Point);
CORRADE_COMPARE(data.color(), 0xccff33_rgbf);
CORRADE_COMPARE(data.intensity(), 0.8f);
CORRADE_COMPARE(data.attenuation(), (Vector3{0.1f, 0.5f, 0.7f}));
CORRADE_COMPARE(data.range(), Constants::inf());
CORRADE_COMPARE(data.innerConeAngle(), 360.0_degf);
CORRADE_COMPARE(data.outerConeAngle(), 360.0_degf);
CORRADE_COMPARE(data.importerState(), &a);
}
}
void LightDataTest::constructRange() {
/* Implicit attenuation for a spot */
{
int a;
LightData data{LightData::Type::Spot,
0xccff33_rgbf, 0.8f,
15.0f,
15.0_degf, 35.0_degf,
&a};
CORRADE_COMPARE(data.type(), LightData::Type::Spot);
CORRADE_COMPARE(data.color(), 0xccff33_rgbf);
CORRADE_COMPARE(data.intensity(), 0.8f);
CORRADE_COMPARE(data.attenuation(), (Vector3{0.0f, 0.0f, 1.0f}));
CORRADE_COMPARE(data.range(), 15.0f);
CORRADE_COMPARE(data.innerConeAngle(), 15.0_degf);
CORRADE_COMPARE(data.outerConeAngle(), 35.0_degf);
CORRADE_COMPARE(data.importerState(), &a);
/* Implicit attenuation for a spot + spot angles */
} {
int a;
LightData data{LightData::Type::Spot,
0xccff33_rgbf, 0.8f,
15.0f,
&a};
CORRADE_COMPARE(data.type(), LightData::Type::Spot);
CORRADE_COMPARE(data.color(), 0xccff33_rgbf);
CORRADE_COMPARE(data.intensity(), 0.8f);
CORRADE_COMPARE(data.attenuation(), (Vector3{0.0f, 0.0f, 1.0f}));
CORRADE_COMPARE(data.range(), 15.0f);
CORRADE_COMPARE(data.innerConeAngle(), 0.0_degf);
CORRADE_COMPARE(data.outerConeAngle(), 45.0_degf);
CORRADE_COMPARE(data.importerState(), &a);
/* Implicit attenuation for a point + non-spot angles */
} {
int a;
LightData data{LightData::Type::Point,
0xccff33_rgbf, 0.8f,
15.0f,
&a};
CORRADE_COMPARE(data.type(), LightData::Type::Point);
CORRADE_COMPARE(data.color(), 0xccff33_rgbf);
CORRADE_COMPARE(data.intensity(), 0.8f);
CORRADE_COMPARE(data.attenuation(), (Vector3{0.0f, 0.0f, 1.0f}));
CORRADE_COMPARE(data.range(), 15.0f);
CORRADE_COMPARE(data.innerConeAngle(), 360.0_degf);
CORRADE_COMPARE(data.outerConeAngle(), 360.0_degf);
CORRADE_COMPARE(data.importerState(), &a);
/* Implicit attenuation for a directional + non-spot angles */
} {
int a;
LightData data{LightData::Type::Directional,
0xccff33_rgbf, 0.8f,
Constants::inf(),
&a};
CORRADE_COMPARE(data.type(), LightData::Type::Directional);
CORRADE_COMPARE(data.color(), 0xccff33_rgbf);
CORRADE_COMPARE(data.intensity(), 0.8f);
CORRADE_COMPARE(data.attenuation(), (Vector3{1.0f, 0.0f, 0.0f}));
CORRADE_COMPARE(data.range(), Constants::inf());
CORRADE_COMPARE(data.innerConeAngle(), 360.0_degf);
CORRADE_COMPARE(data.outerConeAngle(), 360.0_degf);
CORRADE_COMPARE(data.importerState(), &a);
}
}
void LightDataTest::constructNone() {
/* Implicit attenuation + range for a spot */
{
int a;
LightData data{LightData::Type::Spot,
0xccff33_rgbf, 0.8f,
15.0_degf, 35.0_degf,
&a};
CORRADE_COMPARE(data.type(), LightData::Type::Spot);
CORRADE_COMPARE(data.color(), 0xccff33_rgbf);
CORRADE_COMPARE(data.intensity(), 0.8f);
CORRADE_COMPARE(data.attenuation(), (Vector3{0.0f, 0.0f, 1.0f}));
CORRADE_COMPARE(data.range(), Constants::inf());
CORRADE_COMPARE(data.innerConeAngle(), 15.0_degf);
CORRADE_COMPARE(data.outerConeAngle(), 35.0_degf);
CORRADE_COMPARE(data.importerState(), &a);
/* Implicit attenuation + range for a spot + spot angles */
} {
int a;
LightData data{LightData::Type::Spot,
0xccff33_rgbf, 0.8f,
&a};
CORRADE_COMPARE(data.type(), LightData::Type::Spot);
CORRADE_COMPARE(data.color(), 0xccff33_rgbf);
CORRADE_COMPARE(data.intensity(), 0.8f);
CORRADE_COMPARE(data.attenuation(), (Vector3{0.0f, 0.0f, 1.0f}));
CORRADE_COMPARE(data.range(), Constants::inf());
CORRADE_COMPARE(data.innerConeAngle(), 0.0_degf);
CORRADE_COMPARE(data.outerConeAngle(), 45.0_degf);
CORRADE_COMPARE(data.importerState(), &a);
/* Implicit attenuation + range for a point + non-spot angles */
} {
int a;
LightData data{LightData::Type::Point,
0xccff33_rgbf, 0.8f,
&a};
CORRADE_COMPARE(data.type(), LightData::Type::Point);
CORRADE_COMPARE(data.color(), 0xccff33_rgbf);
CORRADE_COMPARE(data.intensity(), 0.8f);
CORRADE_COMPARE(data.attenuation(), (Vector3{0.0f, 0.0f, 1.0f}));
CORRADE_COMPARE(data.range(), Constants::inf());
CORRADE_COMPARE(data.innerConeAngle(), 360.0_degf);
CORRADE_COMPARE(data.outerConeAngle(), 360.0_degf);
CORRADE_COMPARE(data.importerState(), &a);
/* Implicit attenuation for a directional + non-spot angles */
} {
int a;
LightData data{LightData::Type::Directional,
0xccff33_rgbf, 0.8f,
&a};
CORRADE_COMPARE(data.type(), LightData::Type::Directional);
CORRADE_COMPARE(data.color(), 0xccff33_rgbf);
CORRADE_COMPARE(data.intensity(), 0.8f);
CORRADE_COMPARE(data.attenuation(), (Vector3{1.0f, 0.0f, 0.0f}));
CORRADE_COMPARE(data.range(), Constants::inf());
CORRADE_COMPARE(data.innerConeAngle(), 360.0_degf);
CORRADE_COMPARE(data.outerConeAngle(), 360.0_degf);
CORRADE_COMPARE(data.importerState(), &a);
}
}
void LightDataTest::constructInvalid() {
#ifdef CORRADE_NO_ASSERT
CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions");
#endif
auto&& data = ConstructInvalidData[testCaseInstanceId()];
setTestCaseDescription(data.name);
std::ostringstream out;
Error redirectError{&out};
LightData{data.type, {}, {}, data.attenuation, data.range, data.innerConeAngle, data.outerConeAngle};
CORRADE_COMPARE(out.str(), Utility::formatString("Trade::LightData: {}\n", data.message));
}
void LightDataTest::constructCopy() {
CORRADE_VERIFY(!(std::is_constructible<LightData, const LightData&>{}));
CORRADE_VERIFY(!(std::is_assignable<LightData, const LightData&>{}));
CORRADE_VERIFY(std::is_nothrow_move_constructible<LightData>::value);
CORRADE_VERIFY(std::is_nothrow_move_assignable<LightData>::value);
}
void LightDataTest::constructMove() {
using namespace Math::Literals;
const int a{};
LightData data{LightData::Type::Infinite, 0xccff33_rgbf, 0.8f, &a};
CORRADE_COMPARE(data.type(), LightData::Type::Infinite);
CORRADE_COMPARE(data.color(), 0xccff33_rgbf);
CORRADE_COMPARE(data.intensity(), 0.8f);
CORRADE_COMPARE(data.importerState(), &a);
int state;
LightData a{LightData::Type::Spot,
0xccff33_rgbf, 0.8f,
{0.1f, 0.5f, 0.7f}, 15.0f,
15.0_degf, 35.0_degf,
&state};
LightData b{std::move(data)};
CORRADE_COMPARE(b.type(), LightData::Type::Infinite);
LightData b{std::move(a)};
CORRADE_COMPARE(b.type(), LightData::Type::Spot);
CORRADE_COMPARE(b.color(), 0xccff33_rgbf);
CORRADE_COMPARE(b.intensity(), 0.8f);
CORRADE_COMPARE(b.importerState(), &a);
const int c{};
LightData d{LightData::Type::Point, 0xdead00_rgbf, 1.6f, &c};
d = std::move(b);
CORRADE_COMPARE(d.type(), LightData::Type::Infinite);
CORRADE_COMPARE(d.color(), 0xccff33_rgbf);
CORRADE_COMPARE(d.intensity(), 0.8f);
CORRADE_COMPARE(d.importerState(), &a);
CORRADE_COMPARE(b.attenuation(), (Vector3{0.1f, 0.5f, 0.7f}));
CORRADE_COMPARE(b.range(), 15.0f);
CORRADE_COMPARE(b.innerConeAngle(), 15.0_degf);
CORRADE_COMPARE(b.outerConeAngle(), 35.0_degf);
CORRADE_COMPARE(b.importerState(), &state);
LightData c{{}, {}, {}};
c = std::move(a);
CORRADE_COMPARE(c.type(), LightData::Type::Spot);
CORRADE_COMPARE(c.color(), 0xccff33_rgbf);
CORRADE_COMPARE(c.intensity(), 0.8f);
CORRADE_COMPARE(c.attenuation(), (Vector3{0.1f, 0.5f, 0.7f}));
CORRADE_COMPARE(c.range(), 15.0f);
CORRADE_COMPARE(c.innerConeAngle(), 15.0_degf);
CORRADE_COMPARE(c.outerConeAngle(), 35.0_degf);
CORRADE_COMPARE(c.importerState(), &state);
CORRADE_VERIFY(std::is_nothrow_move_constructible<LightData>::value);
CORRADE_VERIFY(std::is_nothrow_move_assignable<LightData>::value);
}
void LightDataTest::debugType() {

Loading…
Cancel
Save