diff --git a/doc/compilation-speedup.dox b/doc/compilation-speedup.dox new file mode 100644 index 000000000..b14647277 --- /dev/null +++ b/doc/compilation-speedup.dox @@ -0,0 +1,79 @@ +namespace Magnum { +/** @page compilation-speedup Speeding up compilation +@brief Techniques for reducing compilation times. + +@section compilation-forward-declarations Forward declarations instead of includes + +Essential thing when speeding up compilation is reducing number of `#``include` +directives in both headers and source files. %Magnum is strictly applying this +policy in all header files, so all types which are not directly used in the +header have only forward declarations. + +For example, when including Magnum.h, you get shortcut typedefs for +floating-point vectors and matrices like @ref Vector3 and @ref Matrix4, but to +actually use any of them, you have to include the respective header, e.g. +Math/Vector3.h. + +@section compilation-speedup-templates Templates + +Many things in %Magnum are templated to allow handling of various types and +sizes of data, for example whole Scene graph can operate either with `float`s +or with `double`s. However, having templated classes and function usually +means that the compiler compiles the whole templated code again in each +compilation unit (i.e. source file). In linking stage of the application or +library the duplicates are just thrown out, which is a waste of compilation +time. A few techniques are employed in %Magnum to avoid this. + +@subsection compilation-speedup-hpp Template headers and implementation files + +When templated code is too large, it is not stored in header file, but in +so-called *template implementation file*. Generally, all header files in +%Magnum have `*.h` extension and all source files have `*.cpp` extension. +Template implementation files have `*.hpp` extension (hinting that they are +something between `*.h` and `*.cpp` files). + +Template implementation file can be included along the header itself and it +will just work, but it doesn't positively affect compilation time. If you are +using one template specialization on many places, template implementation +files give you the ability to explicitly instantiate the template in some +source file. Then you can include only the header everywhere else and leave +the rest on the linker. + +Templated classes which have implementation files state in their documentation +all common specializations that are already compiled in the libraries. So, +unless the templated class is too generic (ResourceManager for example) or you +need something special, you don't have to mess with Object implementation +files at all. See Color3 or SceneGraph::Object for an example. + +Sometimes you however need to use your own specialization and that's why +template implementation files are included in the library. For example we want +to use @ref SceneGraph::Object "Object" from SceneGraph with +@ref SceneGraph::MatrixTransformation3D "MatrixTransformation3D" with +`GLdouble` as underlying type, because our scene will span the whole universe. +We include the implementation file in dedicated source file and explicitly +instantiate the template: +@code +// Object.cpp +#include "SceneGraph/Object.hpp" + +using namespace Magnum::SceneGraph; + +template class Object>; +@endcode +All other files using the same object specialization now need to include only +SceneGraph/Object.h header and if we compile our `Object.cpp` together with +the rest, the Object specialization will be compiled only once. + +@subsection compilation-speedup-extern-templates Extern templates + +Keyword `extern template` is new thing in C++11, attempting to solve +compilation time problems. However, when used on whole classes, on some +compilers it causes conflicting symbol errors, so in %Magnum its used only for +specific functions. + +This is completely transparent to end user, so no special care is needed. +Extern template is used for example for @ref debugoperators "debug operators" +for common types of matrices and vectors. + +*/ +} diff --git a/doc/features.dox b/doc/features.dox index 27da4a060..d033816fe 100644 --- a/doc/features.dox +++ b/doc/features.dox @@ -3,6 +3,8 @@ namespace Magnum { @brief Fundamental principles and design goals - @subpage matrix-vector - @copybrief matrix-vector +- @subpage scenegraph - @copybrief scenegraph - @subpage collision-detection - @copybrief collision-detection +- @subpage compilation-speedup - @copybrief compilation-speedup */ } diff --git a/doc/namespaces.dox b/doc/namespaces.dox index 6c1ff042a..4794c670e 100644 --- a/doc/namespaces.dox +++ b/doc/namespaces.dox @@ -72,7 +72,8 @@ Basic primitives for testing purposes. @namespace Magnum::SceneGraph @brief %Scene graph library -Setting up and rendering the scene. +Managing object hierarchy, transformations and interactions. See +@ref scenegraph for introduction. */ /** @dir Shaders diff --git a/doc/scenegraph.dox b/doc/scenegraph.dox new file mode 100644 index 000000000..4adbf419f --- /dev/null +++ b/doc/scenegraph.dox @@ -0,0 +1,267 @@ +namespace Magnum { namespace SceneGraph { +/** @page scenegraph Using scene graph +@brief Overview of scene management capabilities. + +@tableofcontents + +%Scene graph provides way to hiearchically manage your objects, their +transformation, physics interaction, animation and rendering. There are +naturally many possible combinations (2D vs. 3D, different transformation +representations, animated vs. static, object can have collision shape, +participate in physics events, have forward vs. deferred rendering...). To +make everything possible without combinatiorial explosion and allow the users +to provide their own features, scene graph in %Magnum is composed of three +main components: + + - objects, providing parent/children hierarchy + - transformations, implementing particular transformation type + - features, providing rendering capabilities, collision detection, physics + etc. + +@section scenegraph-transformation Transformations + +Transformation handles object position, rotation etc. and its basic property +is dimension count (2D or 3D) and underlying floating-point type (by default +`float`s are used everywhere, but you can use `double`s too). + +%Scene graph has implementation of transformations in both 2D and 3D, using +either matrices or combination of position and rotation. Each implementation +has its own advantages and disadvantages -- for example when using matrices +you can have nearly arbitrary transformations, but composing transformations +and computing their inverse is costly operation. On the other hand quaternions +won't allow you to scale or shear objects, but are more memory efficient than +matrices. + +It's also possible to implement your own transformation class for specific +needs, see @ref AbstractTransformation-subclassing +"AbstractTransformation documentation" for more information. + +@section scenegraph-hierarchy Scene hierarchy + +%Scene hierarchy is skeleton part of scene graph. In the root there is Scene +and its children are Object instances. The hierarchy has some transformation +type, identical for all objects (because for example having part of the tree +in 2D and part in 3D just wouldn't make sense). Common usage is to typedef +%Scene and %Object with desired transformation type: +@code +typedef SceneGraph::Scene> Scene3D; +typedef SceneGraph::Object> Object3D; +@endcode + +Then you can start building the hierarchy by *parenting* one object to another. +Parent object can be either passed in constructor or using Object::setParent(). +%Scene is always root object, so it naturally cannot have parent object. +@code +Scene3D scene; + +Object3D* first = new Object3D(&scene); +Object3D* second = new Object3D(&first); +@endcode + +Object3D children can be accessed using Object::firstChild() and +Object::lastChild(), then you can traverse siblings (objects with the same +parent) with Object::previousSibling() and Object::nextSibling(). For example +all children of an object can be traversed the following way: +@code +Object3D* o; +for(Object3D* child = o->firstChild(); child; child = child->nextSibling()) { + // ... +} +@endcode + +The hierarchy takes care of memory management - when an object is destroyed, +all its children are destroyed too. See detailed explanation of +@ref scenegraph-object-construction-order "construction and destruction order" +for information about possible issues. + +The object is derived from the transformation you specified earlier in the +`typedef`, so you can directly transform the objects using methods of given +transformation implementation. %Scene, as a root object, cannot have any +transformation. For convenience you can use method chaining: +@code +Object3D* next = new Object3D; +next->setParent(another) + ->translate(Vector3::yAxis(3.0f)) + ->rotateY(deg(35.0f)); +@endcode + +@section scenegraph-features Object features + +The object itself handles only parent/child relationship and transformation. +To make the object renderable, animatable, add collision shape to it etc., you +have to add a *feature* to it. + +Each feature takes pointer to holder object in constructor, so adding a +feature to an object might look like this: +@code +Object3D* o; +MyFeature* feature = new MyFeature(o); +@endcode + +Features of an object can be accessed using Object::firstFeature() and +Object::lastFeature(), then you can traverse the features using +AbstractFeature::previousFeature() and AbstractFeature::nextFeature(), +similarly to traversing object children: +@code +Object3D* o; +for(Object3D::FeatureType feature = o->firstFeature(); feature; feature = feature->nextFeature()) { + // ... +} +@endcode + +Some features are passive, some active. Passive features can be just added to +an object like above, without any additional work (for example collision shape). +Active features require the user to implement some virtual function (for +example to draw the object on screen or perform animation step). To make things +convenient, features can be added directly to object itself using multiple +inheritance, so you can conveniently add all the active features you want and +implement needed functions in your own Object subclass without having to +subclass each feature individually (and making the code overly verbose). +Simplified example: +@code +class Bomb: public Object3D, Drawable, Animatable { + public: + inline Bomb(Object3D* parent): Object3D(parent), Drawable(this), Animatable(this) {} + + protected: + void draw() { + // drawing implementation for Drawable feature + } + + void animationStep() { + // animation step for Animatable feature + } +}; +@endcode + +From the outside there is no difference between features added as member and +features added using multiple inheritance, they can be both accessed from +feature list. + +Similarly to object hierarchy, when destroying object, all its features (both +member and inherited) are destroyed. See detailed explanation of +@ref scenegraph-feature-construction-order "construction and destruction order" +for information about possible issues. + +@section scenegraph-caching Transformation caching + +Some features need to operate with absolute transformations and their +inversions - for example camera needs its inverse transformation to render the +scene, collision detection needs to know about positions of surrounding +objects etc. To avoid computing the transformations from scratch every time, +the feature can cache them. + +The cached data stay until the object is marked as dirty - that is by changing +transformation, changing parent or explicitly calling Object::setDirty(). If +the object is marked as dirty, all its children are marked as dirty too and +AbstractFeature::markDirty() is called on every feature. Calling +Object::setClean() cleans the dirty object and all its dirty parents. +The function goes through all object features and calls AbstractFeature::clean() +or AbstractFeature::cleanInverted() depending on which caching is enabled on +given feature. If the object is already clean, Object::setClean() does nothing. + +Most probably you will need caching in Object itself -- which doesn't support +it on its own -- however you can take advantage of multiple inheritance and +implement it using AbstractFeature. In order to have caching, you must enable +it first, because by default the caching is disabled. You can enable it using +AbstractFeature::setCachedTransformations() and then implement corresponding +cleaning function(s): +@code +class CachingObject: public Object3D, Object3D::FeatureType { + public: + CachingObject(Object3D* parent): Object3D::FeatureType(this) { + setCachedTransformations(CachedTransformation::Absolute); + } + + protected: + void clean(const Matrix4& absoluteTransformation) override { + absolutePosition = absoluteTransformation.translation(); + } + + private: + Vector3 absolutePosition; +}; +@endcode + +When you need to use the cached value, you can explicitly request the cleanup +by calling Object::setClean(). Camera, for example, calls it automatically +before it starts rendering, as it needs its own inverse transformation to +properly draw the objects. + +@section scenegraph-construction-order Construction and destruction order + +There aren't any limitations and usage trade-offs of what you can and can't do +when working with objects and features, but there are two issues which you +should be aware of: + +@subsection scenegraph-object-construction-order Object hierarchy + +When objects are created on the heap (the preferred way, using `new`), they +can be constructed in any order and they will be destroyed when their parent +is destroyed. When creating them on the stack, however, they will be destroyed +when they go out of scope. Normally, the natural order of creation is not a +problem: +@code +{ + Scene3D scene; + Object3D object(&scene); +} +@endcode +The object is created last, so it will be destroyed first, removing itself +from `scene`'s children list, causing no problems when destroying `scene` +object later. However, if their order is swapped, it will cause problems: +@code +{ + Object3D object; + Scene3D scene; + + object.setParent(&scene); +} // crash! +@endcode +The scene will be destroyed first, deleting all its children, which is wrong, +because `object` is created on stack. If this doesn't already crash, the +`object` destructor is called (again), making things even worse. + +@subsection scenegraph-feature-construction-order Member and inherited features + +When destroying the object, all its features are destroyed. For features added +as member it's no issue, features added using multiple inheritance must be +inherited after the %Object class: +@code +class MyObject: public Object3D, MyFeature { + public: + inline MyObject(Object3D* parent): Object3D(parent), MyFeature(this) {} +}; +@endcode +When constructing MyObject, Object3D constructor is called first and then +MyFeature constructor adds itself to Object3D's list of features. When +destroying MyObject, its destructor is called and then the destructors of +ancestor classes -- first MyFeature destructor, which will remove itself from +Object3D's list, then Object3D destructor. + +However, if we would inherit MyFeature first, it will cause problems: +@code +class MyObject: MyFeature, public Object3D { + public: + inline MyObject(Object3D* parent): MyFeature(this), Object3D(parent) {} // crash! +}; +@endcode +MyFeature tries to add itself to feature list in not-yet-constructed Object3D, +causing undefined behavior. Then, if this doesn't already crash, Object3D is +created, creating empty feature list, making the feature invisible. + +If we would construct them in swapped order (if it is even possible), it +wouldn't help either: +@code +class MyObject: MyFeature, public Object3D { + public: + inline MyObject(Object3D* parent): Object3D(parent), MyFeature(this) {} + + // crash on destruction! +}; +@endcode +On destruction, Object3D destructor is called first, deleting MyFeature, +which is wrong, because MyFeature is in the same object. After that (if the +program didn't already crash) destructor of MyFeature is called (again). +*/ +}} diff --git a/src/Physics/CMakeLists.txt b/src/Physics/CMakeLists.txt index 1dc1bf4cf..fdfde2722 100644 --- a/src/Physics/CMakeLists.txt +++ b/src/Physics/CMakeLists.txt @@ -3,29 +3,20 @@ set(MagnumPhysics_SRCS AxisAlignedBox.cpp Box.cpp Capsule.cpp - DebugDrawResourceManager.cpp Line.cpp Plane.cpp Point.cpp - ShapedObject.cpp - ShapedObjectGroup.cpp ShapeGroup.cpp - Sphere.cpp - - Implementation/AbstractDebugRenderer.cpp - Implementation/BoxRenderer.cpp) + Sphere.cpp) set(MagnumPhysics_HEADERS AbstractShape.h AxisAlignedBox.h Box.h Capsule.h - DebugDrawResourceManager.h Line.h LineSegment.h Plane.h Point.h - ShapedObject.h - ShapedObjectGroup.h ShapeGroup.h Sphere.h diff --git a/src/Physics/Test/CMakeLists.txt b/src/Physics/Test/CMakeLists.txt index dd56a0631..4f8130a83 100644 --- a/src/Physics/Test/CMakeLists.txt +++ b/src/Physics/Test/CMakeLists.txt @@ -6,5 +6,3 @@ corrade_add_test2(PhysicsPlaneTest PlaneTest.cpp LIBRARIES MagnumPhysics) corrade_add_test2(PhysicsPointTest PointTest.cpp LIBRARIES MagnumPhysics) corrade_add_test2(PhysicsShapeGroupTest ShapeGroupTest.cpp LIBRARIES MagnumPhysics) corrade_add_test2(PhysicsSphereTest SphereTest.cpp LIBRARIES MagnumPhysics) - -corrade_add_test2(PhysicsShapedObjectTest ShapedObjectTest.cpp LIBRARIES MagnumPhysics) diff --git a/src/SceneGraph/AbstractFeature.h b/src/SceneGraph/AbstractFeature.h new file mode 100644 index 000000000..34e3feffd --- /dev/null +++ b/src/SceneGraph/AbstractFeature.h @@ -0,0 +1,319 @@ +#ifndef Magnum_SceneGraph_AbstractFeature_h +#define Magnum_SceneGraph_AbstractFeature_h +/* + Copyright © 2010, 2011, 2012 Vladimír Vondruš + + This file is part of Magnum. + + Magnum is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 3 + only, as published by the Free Software Foundation. + + Magnum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License version 3 for more details. +*/ + +/** @file + * @brief Class Magnum::SceneGraph::AbstractFeature, alias Magnum::SceneGraph::AbstractFeature2D, Magnum::SceneGraph::AbstractFeature3D + */ + +#include +#include + +#include "AbstractObject.h" + +namespace Magnum { namespace SceneGraph { + +#ifndef DOXYGEN_GENERATING_OUTPUT +namespace Implementation { + enum class FeatureCachedTransformation: std::uint8_t { + Absolute = 1 << 0, + InvertedAbsolute = 1 << 1 + }; + + typedef Corrade::Containers::EnumSet FeatureCachedTransformations; + + CORRADE_ENUMSET_OPERATORS(FeatureCachedTransformations) +} +#endif + +/** +@brief Base for object features + +Contained in Object, takes care of transformation caching. See @ref scenegraph +for introduction. + +@section AbstractFeature-subclassing Subclassing + +Feature is templated on dimension count and underlying transformation type, so +it can be used only on object having transformation with the same dimension +count and type. + +@subsection AbstractFeature-subclassing-caching Caching transformations in features + +Features can cache absolute transformation of the object instead of computing +it from scratch every time to achieve better performance. See +@ref scenegraph-caching for introduction. + +In order to have caching, you must enable it first, because by default the +caching is disabled. You can enable it using setCachedTransformations() and +then implement corresponding cleaning function(s) -- either clean(), +cleanInverted() or both. Example: +@code +class CachingFeature: public SceneGraph::AbstractFeature3D<> { + public: + CachingFeature(SceneGraph::AbstractObject3D<>* object): SceneGraph::AbstractFeature3D<>(object) { + setCachedTransformations(CachedTransformation::Absolute); + } + + protected: + void clean(const Matrix4& absoluteTransformation) override { + absolutePosition = absoluteTransformation.translation(); + } + + private: + Vector3 absolutePosition; +}; +@endcode + +Before using the cached value explicitly request object cleaning by calling +`object()->setClean()`. + +@subsection AbstractFeature-subclassing-transformation Accessing object transformation + +Features has by default access only to AbstractObject, which is base of Object +not depending on any particular transformation implementation. This has the +advantage that features doesn't have to be implemented for all possible +transformation implementations, thus preventing code duplication. However it +is impossible to transform the object using only pointer to AbstractObject. + +The transformations have interfaces for common functionality, so the feature +can use that interface instead of being specialized for all relevant +transformation implementations. Using small trick we are able to get pointer +to both AbstractObject and needed transformation from one constructor +parameter: +@code +class TransformingFeature: public SceneGraph::AbstractFeature3D<> { + public: + template inline TransformingFeature(SceneGraph::Object* object): + SceneGraph::AbstractFeature3D<>(object), transformation(object) {} + + private: + SceneGraph::AbstractTranslationRotation3D<>* transformation; +}; +@endcode +If we take for example @ref Object "Object>", it is +derived from @ref AbstractObject "AbstractObject3D<>" and +@ref MatrixTransformation3D "MatrixTransformation3D<>", which is derived from +@ref AbstractTranslationRotationScaling3D "AbstractTranslationRotationScaling3D<>", +which is derived from +@ref AbstractTranslationRotation3D "AbstractTranslationRotation3D<>", +which is automatically extracted from the pointer in our constructor. + +@see AbstractFeature2D, AbstractFeature3D +*/ +template class AbstractFeature + #ifndef DOXYGEN_GENERATING_OUTPUT + : private Corrade::Containers::LinkedListItem, AbstractObject> + #endif +{ + friend class Corrade::Containers::LinkedList>; + friend class Corrade::Containers::LinkedListItem, AbstractObject>; + template friend class Object; + + public: + /** + * @brief Constructor + * @param object %Object holding this feature + */ + inline AbstractFeature(AbstractObject* object) { + object->Corrade::Containers::LinkedList>::insert(this); + } + + virtual ~AbstractFeature() = 0; + + /** @brief %Object holding this feature */ + inline AbstractObject* object() { + return Corrade::Containers::LinkedListItem, AbstractObject>::list(); + } + + /** @overload */ + inline const AbstractObject* object() const { + return Corrade::Containers::LinkedListItem, AbstractObject>::list(); + } + + /** @brief Previous feature or `nullptr`, if this is first feature */ + inline AbstractFeature* previousFeature() { + return Corrade::Containers::LinkedListItem, AbstractObject>::previous(); + } + + /** @overload */ + inline const AbstractFeature* previousFeature() const { + return Corrade::Containers::LinkedListItem, AbstractObject>::previous(); + } + + /** @brief Next feature or `nullptr`, if this is last feature */ + inline AbstractFeature* nextFeature() { + return Corrade::Containers::LinkedListItem, AbstractObject>::next(); + } + + /** @overload */ + inline const AbstractFeature* nextFeature() const { + return Corrade::Containers::LinkedListItem, AbstractObject>::next(); + } + + /** + * @{ @name Transformation caching + * + * See @ref scenegraph-caching for more information. + */ + + /** + * @brief Which transformation to cache in this feature + * + * @see @ref scenegraph-caching, CachedTransformations, + * setCachedTransformations(), clean(), cleanInverted() + * @todo Provide also simpler representations from which could benefit + * other transformation implementations, as they won't need to + * e.g. create transformation matrix from quaternion? + */ + #ifndef DOXYGEN_GENERATING_OUTPUT + typedef Implementation::FeatureCachedTransformation CachedTransformation; + #else + enum class CachedTransformation: std::uint8_t { + /** + * Absolute transformation is cached. + * + * If enabled, clean() is called when cleaning object. + */ + Absolute = 1 << 0, + + /** + * Inverted absolute transformation is cached. + * + * If enabled, cleanInverted() is called when cleaning object. + */ + InvertedAbsolute = 1 << 1 + }; + #endif + + /** + * @brief Which transformations to cache in this feature + * + * @see @ref scenegraph-caching, setCachedTransformations(), clean(), + * cleanInverted() + */ + #ifndef DOXYGEN_GENERATING_OUTPUT + typedef Implementation::FeatureCachedTransformations CachedTransformations; + #else + typedef Corrade::Containers::EnumSet CachedTransformations; + #endif + + /** + * @brief Which transformations are cached + * + * @see @ref scenegraph-caching, clean(), cleanInverted() + */ + inline CachedTransformations cachedTransformations() const { return _cachedTransformations; } + + protected: + /** + * @brief Set transformations to be cached + * + * Based on which transformation types are enabled, clean() or + * cleanInverted() is called when cleaning absolute object + * transformation. + * + * Nothing is enabled by default. + * @see @ref scenegraph-caching + */ + inline void setCachedTransformations(CachedTransformations transformations) { _cachedTransformations = transformations; } + + /** + * @brief Mark feature as dirty + * + * Reimplement only if you want to invalidate some external data when + * object is marked as dirty. All expensive computations should be + * done in clean() and cleanInverted(). + * + * Default implementation does nothing. + * @see @ref scenegraph-caching + */ + inline virtual void markDirty() {} + + /** + * @brief Clean data based on absolute transformation + * + * When object is cleaned and + * @ref CachedTransformation "CachedTransformation::Absolute" is + * enabled in setCachedTransformations(), this function is called to + * recalculate data based on absolute object transformation. + * + * Default implementation does nothing. + * @see @ref scenegraph-caching, cleanInverted() + */ + virtual void clean(const typename DimensionTraits::MatrixType& absoluteTransformation); + + /** + * @brief Clean data based on inverted absolute transformation + * + * When object is cleaned and + * @ref CachedTransformation "CachedTransformation::InvertedAbsolute" + * is enabled in setCachedTransformations(), this function is called + * to recalculate data based on inverted absolute object + * transformation. + * + * Default implementation does nothing. + * @see @ref scenegraph-caching, clean() + */ + virtual void cleanInverted(const typename DimensionTraits::MatrixType& invertedAbsoluteTransformation); + + /*@}*/ + + private: + CachedTransformations _cachedTransformations; +}; + +template inline AbstractFeature::~AbstractFeature() {} +template inline void AbstractFeature::clean(const typename DimensionTraits::MatrixType&) {} +template inline void AbstractFeature::cleanInverted(const typename DimensionTraits::MatrixType&) {} + +/** +@brief Base for two-dimensional features + +Convenience alternative to %AbstractFeature<2, T>. See AbstractFeature +for more information. +@note Not available on GCC < 4.7. Use %AbstractFeature<2, T> instead. +@see AbstractFeature3D +@todoc Remove workaround when Doxygen supports alias template +*/ +#ifndef DOXYGEN_GENERATING_OUTPUT +#ifndef MAGNUM_GCC46_COMPATIBILITY +template using AbstractFeature2D = AbstractFeature<2, T>; +#endif +#else +typedef AbstractFeature<2, T = GLfloat> AbstractFeature2D; +#endif + +/** +@brief Base for three-dimensional features + +Convenience alternative to %AbstractFeature<3, T>. See AbstractFeature +for more information. +@note Not available on GCC < 4.7. Use %AbstractFeature<3, T> instead. +@see AbstractFeature2D +@todoc Remove workaround when Doxygen supports alias template +*/ +#ifndef DOXYGEN_GENERATING_OUTPUT +#ifndef MAGNUM_GCC46_COMPATIBILITY +template using AbstractFeature3D = AbstractFeature<3, T>; +#endif +#else +typedef AbstractFeature<2, T = GLfloat> AbstractFeature3D; +#endif + +}} + +#endif diff --git a/src/SceneGraph/AbstractGroupedFeature.h b/src/SceneGraph/AbstractGroupedFeature.h new file mode 100644 index 000000000..a60ba6c55 --- /dev/null +++ b/src/SceneGraph/AbstractGroupedFeature.h @@ -0,0 +1,239 @@ +#ifndef Magnum_SceneGraph_AbstractGroupedFeature_h +#define Magnum_SceneGraph_AbstractGroupedFeature_h +/* + Copyright © 2010, 2011, 2012 Vladimír Vondruš + + This file is part of Magnum. + + Magnum is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 3 + only, as published by the Free Software Foundation. + + Magnum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License version 3 for more details. +*/ + +/** @file + * @brief Class Magnum::SceneGraph::AbstractGroupedFeature, Magnum::SceneGraph::FeatureGroup, alias Magnum::SceneGraph::AbstractGroupedFeature2D, Magnum::SceneGraph::AbstractGroupedFeature3D, Magnum::SceneGraph::FeatureGroup2D, Magnum::SceneGraph::FeatureGroup3D + */ + +#include +#include + +#include "AbstractFeature.h" + +namespace Magnum { namespace SceneGraph { + +template class FeatureGroup; + +/** +@brief Base for grouped features + +Used together with FeatureGroup. + +@section AbstractGroupedFeature-usage Usage + +Usage is via subclassing the feature using [CRTP](http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern) +and typedef'ing FeatureGroup to accept only given type, e.g.: +@code +class Drawable: public SceneGraph::AbstractGroupedFeature3D { + // ... +}; + +typedef SceneGraph::FeatureGroup3D DrawableGroup; +@endcode + +@see AbstractGroupedFeature2D, AbstractGroupedFeature3D, FeatureGroup, + FeatureGroup2D, FeatureGroup3D +*/ +template class AbstractGroupedFeature: public AbstractFeature { + friend class FeatureGroup; + + public: + /** + * @brief Constructor + * @param object %Object this feature belongs to + * @param group Group this feature belongs to + * + * Adds the feature to the object and to group, if specified. + * @see FeatureGroup::add(), FeatureGroup::remove() + */ + inline AbstractGroupedFeature(AbstractObject* object, FeatureGroup* group = nullptr): AbstractFeature(object), _group(nullptr) { + if(group) group->add(static_cast(this)); + } + + /** + * @brief Destructor + * + * Removes the feature from object and from group, if it belongs to + * any. + */ + inline ~AbstractGroupedFeature() { + if(_group) _group->remove(static_cast(this)); + } + + /** @brief Group this feature belongs to */ + inline FeatureGroup* group() { + return _group; + } + + /** @overload */ + inline const FeatureGroup* group() const { + return _group; + } + + private: + FeatureGroup* _group; +}; + +/** +@brief Base for two-dimensional grouped features + +Convenience alternative to %AbstractGroupedFeature<2, Derived, T>. See +AbstractGroupedFeature for more information. +@see AbstractGroupedFeature3D +@note Not available on GCC < 4.7. Use %AbstractGroupedFeature<2, Derived, T> + instead. +@todoc Remove workaround when Doxygen supports alias template +*/ +#ifndef DOXYGEN_GENERATING_OUTPUT +#ifndef MAGNUM_GCC46_COMPATIBILITY +template using AbstractGroupedFeature2D = AbstractGroupedFeature<2, Derived, T>; +#endif +#else +typedef AbstractGroupedFeature<2, Derived, T = GLfloat> AbstractGroupedFeature2D; +#endif + +/** +@brief Base for three-dimensional grouped features + +Convenience alternative to %AbstractGroupedFeature<3, Derived, T>. See +AbstractGroupedFeature for more information. +@see AbstractGroupedFeature2D +@note Not available on GCC < 4.7. Use %AbstractGroupedFeature<3, Derived, T> + instead. +@todoc Remove workaround when Doxygen supports alias template +*/ +#ifndef DOXYGEN_GENERATING_OUTPUT +#ifndef MAGNUM_GCC46_COMPATIBILITY +template using AbstractGroupedFeature3D = AbstractGroupedFeature<3, Derived, T>; +#endif +#else +typedef AbstractGroupedFeature<3, Derived, T = GLfloat> AbstractGroupedFeature3D; +#endif + +/** +@brief Group of features + +See AbstractGroupedFeature for more information. +@see FeatureGroup2D, FeatureGroup3D +*/ +template class FeatureGroup { + friend class AbstractGroupedFeature; + + public: + /** + * @brief Destructor + * + * Deletes all features belogning to this group. + */ + inline virtual ~FeatureGroup() { + for(auto i: features) { + i->_group = nullptr; + delete i; + } + } + + /** @brief Whether the group is empty */ + inline bool isEmpty() const { return features.empty(); } + + /** @brief Count of features in the group */ + inline std::size_t size() const { return features.size(); } + + /** @brief Feature at given index */ + inline Feature* operator[](std::size_t index) { + return features[index]; + } + + /** @overload */ + inline const Feature* operator[](std::size_t index) const { + return features[index]; + } + + /** + * @brief Add feature to the group + * + * If the features is part of another group, it is removed from it. + */ + void add(Feature* feature) { + /** @todo Assert the same scene for all items? -- can't easily + watch when feature object is removed from hierarchy */ + + /* Remove from previous group */ + if(feature->_group) + feature->_group->remove(feature); + + /* Crossreference the feature and group together */ + features.push_back(feature); + feature->_group = this; + } + + /** + * @brief Remove feature from the group + * + * The feature must be part of the group. + */ + void remove(Feature* feature) { + CORRADE_ASSERT(feature->_group == this, + "SceneGraph::AbstractFeatureGroup::remove(): feature is not part of this group", ); + + /* Remove the feature and reset group pointer */ + features.erase(std::find(features.begin(), features.end(), feature)); + feature->_group = nullptr; + } + + private: + std::vector features; +}; + +/** +@brief Base for two-dimensional object features + +Convenience alternative to %FeatureGroup<2, Feature, T>. See +AbstractGroupedFeature for more information. +@note Not available on GCC < 4.7. Use %FeatureGroup<2, Feature, T> + instead. +@see FeatureGroup3D +@todoc Remove workaround when Doxygen supports alias template +*/ +#ifndef DOXYGEN_GENERATING_OUTPUT +#ifndef MAGNUM_GCC46_COMPATIBILITY +template using FeatureGroup2D = FeatureGroup<2, Feature, T>; +#endif +#else +typedef FeatureGroup<2, Feature, T = GLfloat> FeatureGroup2D; +#endif + +/** +@brief Base for three-dimensional object features + +Convenience alternative to %FeatureGroup<3, Feature, T>. See +AbstractGroupedFeature for more information. +@note Not available on GCC < 4.7. Use %FeatureGroup<3, Feature, T> + instead. +@see FeatureGroup2D +@todoc Remove workaround when Doxygen supports alias template +*/ +#ifndef DOXYGEN_GENERATING_OUTPUT +#ifndef MAGNUM_GCC46_COMPATIBILITY +template using FeatureGroup3D = FeatureGroup<3, Feature, T>; +#endif +#else +typedef FeatureGroup<3, Feature, T = GLfloat> FeatureGroup3D; +#endif + +}} + +#endif diff --git a/src/SceneGraph/AbstractObject.h b/src/SceneGraph/AbstractObject.h new file mode 100644 index 000000000..286e56f2b --- /dev/null +++ b/src/SceneGraph/AbstractObject.h @@ -0,0 +1,196 @@ +#ifndef Magnum_SceneGraph_AbstractObject_h +#define Magnum_SceneGraph_AbstractObject_h +/* + Copyright © 2010, 2011, 2012 Vladimír Vondruš + + This file is part of Magnum. + + Magnum is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 3 + only, as published by the Free Software Foundation. + + Magnum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License version 3 for more details. +*/ + +/** @file + * @brief Class Magnum::SceneGraph::AbstractObject, alias Magnum::SceneGraph::AbstractObject2D, Magnum::SceneGraph::AbstractObject3D + */ + +#include + +#include "DimensionTraits.h" +#include "Magnum.h" + +#include "magnumCompatibility.h" + +namespace Magnum { namespace SceneGraph { + +template class AbstractFeature; + +/** +@brief Base for objects + +Provides minimal interface for features, not depending on object +transformation implementation. See Object or @ref scenegraph for more +information. + +@see AbstractObject2D, AbstractObject3D +*/ +template class AbstractObject + #ifndef DOXYGEN_GENERATING_OUTPUT + : private Corrade::Containers::LinkedList> + #endif +{ + friend class Corrade::Containers::LinkedList>; + friend class Corrade::Containers::LinkedListItem, AbstractObject>; + friend AbstractFeature::AbstractFeature(AbstractObject*); + + public: + /** @brief Feature object type */ + typedef AbstractFeature FeatureType; + + inline virtual ~AbstractObject() {} + + /** @brief Whether this object has features */ + inline bool hasFeatures() const { + return !Corrade::Containers::LinkedList>::isEmpty(); + } + + /** @brief First object feature or `nullptr`, if this object has no features */ + inline FeatureType* firstFeature() { + return Corrade::Containers::LinkedList>::first(); + } + + /** @overload */ + inline const FeatureType* firstFeature() const { + return Corrade::Containers::LinkedList>::first(); + } + + /** @brief Last object feature or `nullptr`, if this object has no features */ + inline FeatureType* lastFeature() { + return Corrade::Containers::LinkedList>::last(); + } + + /** @overload */ + inline const FeatureType* lastFeature() const { + return Corrade::Containers::LinkedList>::last(); + } + + /** + * @brief %Scene object + * @return Root object which is also scene or `nullptr`, if the object + * is not part of any scene. + * + * @todo Rename to scene() when I fully understand and fix covariant + * return issues. + */ + virtual AbstractObject* sceneObject() = 0; + + /** @overload */ + virtual const AbstractObject* sceneObject() const = 0; + + /** @{ @name Object transformation */ + + /** + * @brief Transformation matrix relative to root object + * + * @see Object::absoluteTransformation() + */ + virtual typename DimensionTraits::MatrixType absoluteTransformationMatrix() const = 0; + + /** + * @brief Transformation matrices of given set of objects relative to this object + * + * All transformations are premultiplied with @p initialTransformationMatrix, + * if specified. + * @warning This function cannot check if all objects are of the same + * Object type, use typesafe Object::transformations() when + * possible. + */ + virtual std::vector::MatrixType> transformationMatrices(const std::vector*>& objects, const typename DimensionTraits::MatrixType& initialTransformationMatrix = typename DimensionTraits::MatrixType()) const = 0; + + /*@}*/ + + /** + * @{ @name Transformation caching + * + * See @ref scenegraph-caching for more information. + */ + + /** + * @brief Whether absolute transformation is dirty + * + * Returns `true` if transformation of the object or any parent has + * changed since last call to setClean(), `false` otherwise. + * + * All objects are dirty by default. + * + * @see @ref scenegraph-caching + */ + virtual bool isDirty() const = 0; + + /** + * @brief Set object absolute transformation as dirty + * + * Calls AbstractFeature::markDirty() on all object features and + * recursively calls setDirty() on every child object which is not + * already dirty. If the object is already marked as dirty, the + * function does nothing. + * @see @ref scenegraph-caching, setClean(), isDirty() + */ + virtual void setDirty() = 0; + + /** + * @brief Clean object absolute transformation + * + * Calls AbstractFeature::clean() and/or AbstractFeature::cleanInverted() + * on all object features which have caching enabled and recursively + * calls setClean() on every parent which is not already clean. If the + * object is already clean, the function does nothing. + * @see @ref scenegraph-caching, setDirty(), isDirty() + */ + virtual void setClean() = 0; + + /*@}*/ +}; + +/** +@brief Base for two-dimensional objects + +Convenience alternative to %AbstractObject<2, T>. See AbstractObject +for more information. +@note Not available on GCC < 4.7. Use %AbstractObject<2, T> instead. +@see AbstractObject3D +@todoc Remove workaround when Doxygen supports alias template +*/ +#ifndef DOXYGEN_GENERATING_OUTPUT +#ifndef MAGNUM_GCC46_COMPATIBILITY +template using AbstractObject2D = AbstractObject<2, T>; +#endif +#else +typedef AbstractObject<2, T = GLfloat> AbstractObject2D; +#endif + +/** +@brief Base for three-dimensional objects + +Convenience alternative to %AbstractObject<3, T>. See AbstractObject +for more information. +@note Not available on GCC < 4.7. Use %AbstractObject<3, T> instead. +@see AbstractObject2D +@todoc Remove workaround when Doxygen supports alias template +*/ +#ifndef DOXYGEN_GENERATING_OUTPUT +#ifndef MAGNUM_GCC46_COMPATIBILITY +template using AbstractObject3D = AbstractObject<3, T>; +#endif +#else +typedef AbstractObject<3, T = GLfloat> AbstractObject3D; +#endif + +}} + +#endif diff --git a/src/SceneGraph/AbstractTransformation.h b/src/SceneGraph/AbstractTransformation.h new file mode 100644 index 000000000..a2c73d818 --- /dev/null +++ b/src/SceneGraph/AbstractTransformation.h @@ -0,0 +1,169 @@ +#ifndef Magnum_SceneGraph_AbstractTransformation_h +#define Magnum_SceneGraph_AbstractTransformation_h +/* + Copyright © 2010, 2011, 2012 Vladimír Vondruš + + This file is part of Magnum. + + Magnum is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 3 + only, as published by the Free Software Foundation. + + Magnum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License version 3 for more details. +*/ + +/** @file + * @brief Class Magnum::SceneGraph::AbstractTransformation, enum Magnum::SceneGraph::TransformationType, alias Magnum::SceneGraph::AbstractTransformation2D, Magnum::SceneGraph::AbstractTransformation3D + */ + +#include + +#include "Magnum.h" +#include "DimensionTraits.h" + +namespace Magnum { namespace SceneGraph { + +template class Object; + +/** +@brief Base for transformations + +Provides transformation implementation for Object instances. See @ref scenegraph +for introduction. + +@section AbstractTransformation-subclassing Subclassing + +When sublassing, you have to: + +- Implement all members described in **Subclass implementation** group above +- Provide implicit (parameterless) constructor + +@see AbstractTransformation2D, AbstractTransformation3D +*/ +template class AbstractTransformation { + public: + /** @brief Underlying floating-point type */ + typedef T Type; + + /** @brief Dimension count */ + static const std::uint8_t Dimensions = dimensions; + + virtual ~AbstractTransformation() = 0; + + #ifdef DOXYGEN_GENERATING_OUTPUT + /** + * @{ @name Subclass implementation + * + * These members must be defined by the implementation. + */ + + /** + * @brief Transformation data type + * + * The type must satisfy the following requirements: + * + * - Default constructor must create identity transformation + * + * Defined in subclasses. + */ + typedef U DataType; + + /** + * @brief Convert transformation to matrix + * + * Defined in subclasses. + */ + static typename DimensionTraits::MatrixType toMatrix(const DataType& transformation); + + /** + * @brief Convert transformation from matrix + * + * Defined in subclasses. + */ + static DataType fromMatrix(const typename DimensionTraits::MatrixType& matrix); + + /** + * @brief Compose transformations + * + * Defined in subclasses. + */ + static DataType compose(const DataType& parent, const DataType& child); + + /** + * @brief Inverted transformation + * + * Defined in subclasses. + */ + static DataType inverted(const DataType& transformation); + + /** + * @brief %Object transformation + * + * Relative to parent. Defined in subclasses. + */ + DataType transformation() const; + + /** + * @brief Absolute transformation + * + * Relative to root object. Defined in subclasses. + */ + DataType absoluteTransformation() const; + + /*@}*/ + #endif +}; + +/** @brief Transformation type */ +enum class TransformationType: std::uint8_t { + /** Global transformation, applied after all other transformations. */ + Global = 0x00, + + /** Local transformation, applied before all other transformations. */ + Local = 0x01 +}; + +template inline AbstractTransformation::~AbstractTransformation() {} + +/** +@brief Base for two-dimensional transformations + +Convenience alternative to %AbstractTransformation<2, T>. See +AbstractTransformation for more information. +@note Not available on GCC < 4.7. Use %AbstractTransformation<2, T> + instead. +@see AbstractTransformation3D +@todoc Remove workaround when Doxygen supports alias template +*/ +#ifndef DOXYGEN_GENERATING_OUTPUT +#ifndef MAGNUM_GCC46_COMPATIBILITY +template using AbstractTransformation2D = AbstractTransformation<2, T>; +#endif +#else +typedef AbstractTransformation<2, T = GLfloat> AbstractTransformation2D; +#endif + +/** +@brief Base for three-dimensional transformations + +Convenience alternative to %AbstractTransformation<3, T>. See +AbstractTransformation for more information. +@note Not available on GCC < 4.7. Use %AbstractTransformation<3, T> + instead. +@see AbstractTransformation2D +@todoc Remove workaround when Doxygen supports alias template +*/ +#ifndef DOXYGEN_GENERATING_OUTPUT +#ifndef MAGNUM_GCC46_COMPATIBILITY +template using AbstractTransformation3D = AbstractTransformation<3, T>; +#endif +#else +typedef AbstractTransformation<3, T = GLfloat> AbstractTransformation3D; +#endif + +}} + +#endif diff --git a/src/SceneGraph/AbstractTranslationRotation2D.h b/src/SceneGraph/AbstractTranslationRotation2D.h new file mode 100644 index 000000000..22d407b14 --- /dev/null +++ b/src/SceneGraph/AbstractTranslationRotation2D.h @@ -0,0 +1,56 @@ +#ifndef Magnum_SceneGraph_AbstractTranslationRotation2D_h +#define Magnum_SceneGraph_AbstractTranslationRotation2D_h +/* + Copyright © 2010, 2011, 2012 Vladimír Vondruš + + This file is part of Magnum. + + Magnum is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 3 + only, as published by the Free Software Foundation. + + Magnum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License version 3 for more details. +*/ + +/** @file + * @brief Class Magnum::SceneGraph::AbstractTranslationRotation2D + */ + +#include "AbstractTransformation.h" + +namespace Magnum { namespace SceneGraph { + +/** +@brief Base for two-dimensional transformations supporting translation and rotation + +@see AbstractTranslationRotation3D +*/ +template class AbstractTranslationRotation2D: public AbstractTransformation<2, T> { + public: + /** + * @brief Translate object + * @param vector Translation vector + * @param type Transformation type + * @return Pointer to self (for method chaining) + * + * @see Vector2::xAxis(), Vector2::yAxis() + */ + virtual AbstractTranslationRotation2D* translate(const Math::Vector2& vector, TransformationType type = TransformationType::Global) = 0; + + /** + * @brief Rotate object + * @param angle Angle in radians, counterclockwise + * @param type Transformation type + * @return Pointer to self (for method chaining) + * + * @see deg(), rad() + */ + virtual AbstractTranslationRotation2D* rotate(T angle, TransformationType type = TransformationType::Global) = 0; +}; + +}} + +#endif diff --git a/src/SceneGraph/AbstractTranslationRotation3D.h b/src/SceneGraph/AbstractTranslationRotation3D.h new file mode 100644 index 000000000..4bc174a59 --- /dev/null +++ b/src/SceneGraph/AbstractTranslationRotation3D.h @@ -0,0 +1,100 @@ +#ifndef Magnum_SceneGraph_AbstractTranslationRotation3D_h +#define Magnum_SceneGraph_AbstractTranslationRotation3D_h +/* + Copyright © 2010, 2011, 2012 Vladimír Vondruš + + This file is part of Magnum. + + Magnum is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 3 + only, as published by the Free Software Foundation. + + Magnum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License version 3 for more details. +*/ + +/** @file + * @brief Class Magnum::SceneGraph::AbstractTranslationRotation3D + */ + +#include "AbstractTransformation.h" +#include "Math/Vector3.h" + +namespace Magnum { namespace SceneGraph { + +/** +@brief Base for three-dimensional transformations supporting translation and rotation + +@see AbstractTranslationRotation2D +*/ +template class AbstractTranslationRotation3D: public AbstractTransformation<3, T> { + public: + /** + * @brief Translate object + * @param vector Translation vector + * @param type Transformation type + * @return Pointer to self (for method chaining) + * + * @see Vector3::xAxis(), Vector3::yAxis(), Vector3::zAxis() + */ + virtual AbstractTranslationRotation3D* translate(const Math::Vector3& vector, TransformationType type = TransformationType::Global) = 0; + + /** + * @brief Rotate object + * @param angle Angle in radians, counterclockwise + * @param normalizedAxis Normalized rotation axis + * @param type Transformation type + * @return Pointer to self (for method chaining) + * + * @see deg(), rad(), Vector3::xAxis(), Vector3::yAxis(), Vector3::zAxis() + */ + virtual AbstractTranslationRotation3D* rotate(T angle, const Math::Vector3& normalizedAxis, TransformationType type = TransformationType::Global) = 0; + + /** + * @brief Rotate object around X axis + * @param angle Angle in radians, counterclockwise + * @param type Transformation type + * @return Pointer to self (for method chaining) + * + * In some implementations faster than calling + * `rotate(angle, Vector3::xAxis())`. + * @see deg(), rad() + */ + virtual AbstractTranslationRotation3D* rotateX(T angle, TransformationType type = TransformationType::Global) { + return rotate(angle, Math::Vector3::xAxis(), type); + } + + /** + * @brief Rotate object around Y axis + * @param angle Angle in radians, counterclockwise + * @param type Transformation type + * @return Pointer to self (for method chaining) + * + * In some implementations faster than calling + * `rotate(angle, Vector3::yAxis())`. + * @see deg(), rad() + */ + virtual AbstractTranslationRotation3D* rotateY(T angle, TransformationType type = TransformationType::Global) { + return rotate(angle, Math::Vector3::yAxis(), type); + } + + /** + * @brief Rotate object around Z axis + * @param angle Angle in radians, counterclockwise + * @param type Transformation type + * @return Pointer to self (for method chaining) + * + * In some implementations faster than calling + * `rotate(angle, Vector3::zAxis())`. + * @see deg(), rad() + */ + virtual AbstractTranslationRotation3D* rotateZ(T angle, TransformationType type = TransformationType::Global) { + return rotate(angle, Math::Vector3::zAxis(), type); + } +}; + +}} + +#endif diff --git a/src/SceneGraph/AbstractTranslationRotationScaling2D.h b/src/SceneGraph/AbstractTranslationRotationScaling2D.h new file mode 100644 index 000000000..6d5d3f66d --- /dev/null +++ b/src/SceneGraph/AbstractTranslationRotationScaling2D.h @@ -0,0 +1,46 @@ +#ifndef Magnum_SceneGraph_AbstractTranslationRotationScaling2D_h +#define Magnum_SceneGraph_AbstractTranslationRotationScaling2D_h +/* + Copyright © 2010, 2011, 2012 Vladimír Vondruš + + This file is part of Magnum. + + Magnum is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 3 + only, as published by the Free Software Foundation. + + Magnum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License version 3 for more details. +*/ + +/** @file + * @brief Class Magnum::SceneGraph::AbstractTranslationRotationScaling2D + */ + +#include "AbstractTranslationRotation2D.h" + +namespace Magnum { namespace SceneGraph { + +/** +@brief Base for two-dimensional transformations supporting translation, rotation and scaling + +@see AbstractTranslationRotationScaling2D +*/ +template class AbstractTranslationRotationScaling2D: public AbstractTranslationRotation2D { + public: + /** + * @brief Scale object + * @param vector Scaling vector + * @param type Transformation type + * @return Pointer to self (for method chaining) + * + * @see Vector2::xScale(), Vector2::yScale() + */ + virtual AbstractTranslationRotationScaling2D* scale(const Math::Vector2& vector, TransformationType type = TransformationType::Global) = 0; +}; + +}} + +#endif diff --git a/src/SceneGraph/AbstractTranslationRotationScaling3D.h b/src/SceneGraph/AbstractTranslationRotationScaling3D.h new file mode 100644 index 000000000..c4c880ae5 --- /dev/null +++ b/src/SceneGraph/AbstractTranslationRotationScaling3D.h @@ -0,0 +1,46 @@ +#ifndef Magnum_SceneGraph_AbstractTranslationRotationScaling3D_h +#define Magnum_SceneGraph_AbstractTranslationRotationScaling3D_h +/* + Copyright © 2010, 2011, 2012 Vladimír Vondruš + + This file is part of Magnum. + + Magnum is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 3 + only, as published by the Free Software Foundation. + + Magnum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License version 3 for more details. +*/ + +/** @file + * @brief Class Magnum::SceneGraph::AbstractTranslationRotationScaling3D + */ + +#include "AbstractTranslationRotation3D.h" + +namespace Magnum { namespace SceneGraph { + +/** +@brief Base for three-dimensional transformations supporting translation, rotation and scaling + +@see AbstractTranslationRotationScaling2D +*/ +template class AbstractTranslationRotationScaling3D: public AbstractTranslationRotation3D { + public: + /** + * @brief Scale object + * @param vector Scaling vector + * @param type Transformation type + * @return Pointer to self (for method chaining) + * + * @see Vector3::xScale(), Vector3::yScale(), Vector3::zScale() + */ + virtual AbstractTranslationRotationScaling3D* scale(const Math::Vector3& vector, TransformationType type = TransformationType::Global) = 0; +}; + +}} + +#endif diff --git a/src/SceneGraph/CMakeLists.txt b/src/SceneGraph/CMakeLists.txt index dd8f5bee3..2268b0dd5 100644 --- a/src/SceneGraph/CMakeLists.txt +++ b/src/SceneGraph/CMakeLists.txt @@ -1,11 +1,21 @@ -# Files shared between main library and unit test library set(MagnumSceneGraph_SRCS - Camera.cpp - Light.cpp) + Camera.cpp) set(MagnumSceneGraph_HEADERS + AbstractFeature.h + AbstractGroupedFeature.h + AbstractObject.h + AbstractTransformation.h + AbstractTranslationRotation2D.h + AbstractTranslationRotation3D.h + AbstractTranslationRotationScaling2D.h + AbstractTranslationRotationScaling3D.h Camera.h - Light.h + Camera.hpp + Drawable.h + MatrixTransformation2D.h + MatrixTransformation3D.h Object.h + Object.hpp Scene.h magnumSceneGraphVisibility.h) @@ -13,7 +23,8 @@ add_library(MagnumSceneGraphObjects OBJECT ${MagnumSceneGraph_SRCS}) # Files compiled with different flags for main library and unit test library set(MagnumSceneGraph_GracefulAssert_SRCS - Object.cpp) + MatrixTransformation2D.cpp + MatrixTransformation3D.cpp) # Set shared library flags for the objects, as they will be part of shared lib # TODO: fix when CMake sets target_EXPORTS for OBJECT targets as well diff --git a/src/SceneGraph/Camera.cpp b/src/SceneGraph/Camera.cpp index d51a9ff7b..f76b3a6d1 100644 --- a/src/SceneGraph/Camera.cpp +++ b/src/SceneGraph/Camera.cpp @@ -14,121 +14,16 @@ */ #include "Camera.h" -#include "Scene.h" -using namespace std; +#include "Camera.hpp" namespace Magnum { namespace SceneGraph { #ifndef DOXYGEN_GENERATING_OUTPUT -namespace Implementation { - -template MatrixType aspectRatioFix(AspectRatioPolicy aspectRatioPolicy, const Vector2& projectionScale, const Math::Vector2& viewport) { - /* Don't divide by zero / don't preserve anything */ - if(projectionScale.x() == 0 || projectionScale.y() == 0 || viewport.x() == 0 || viewport.y() == 0 || aspectRatioPolicy == AspectRatioPolicy::NotPreserved) - return MatrixType(); - - Vector2 relativeAspectRatio = Vector2::from(viewport)*projectionScale; - - /* Extend on larger side = scale larger side down - Clip on smaller side = scale smaller side up */ - return Camera::aspectRatioScale( - (relativeAspectRatio.x() > relativeAspectRatio.y()) == (aspectRatioPolicy == AspectRatioPolicy::Extend) ? - Vector2(relativeAspectRatio.y()/relativeAspectRatio.x(), 1.0f) : - Vector2(1.0f, relativeAspectRatio.x()/relativeAspectRatio.y())); -} - -/* Explicitly instantiate the templates */ -template Matrix3 aspectRatioFix(AspectRatioPolicy, const Vector2&, const Math::Vector2&); -template Matrix4 aspectRatioFix(AspectRatioPolicy, const Vector2&, const Math::Vector2&); - -} +template class SCENEGRAPH_EXPORT AbstractCamera<2, GLfloat>; +template class SCENEGRAPH_EXPORT AbstractCamera<3, GLfloat>; +template class SCENEGRAPH_EXPORT Camera2D; +template class SCENEGRAPH_EXPORT Camera3D; #endif -template AbstractCamera::AbstractCamera(typename AbstractObject::ObjectType* parent): AbstractObject::ObjectType(parent), _aspectRatioPolicy(AspectRatioPolicy::NotPreserved) {} - -template typename AbstractObject::CameraType* AbstractCamera::setAspectRatioPolicy(AspectRatioPolicy policy) { - _aspectRatioPolicy = policy; - fixAspectRatio(); - return static_cast::CameraType*>(this); -} - -template void AbstractCamera::setViewport(const Math::Vector2& size) { - _viewport = size; - fixAspectRatio(); -} - -template void AbstractCamera::clean(const typename DimensionTraits::MatrixType& absoluteTransformation) { - AbstractObject::ObjectType::clean(absoluteTransformation); - - _cameraMatrix = absoluteTransformation.inverted(); -} - -template void AbstractCamera::draw() { - typename AbstractObject::SceneType* s = this->scene(); - CORRADE_ASSERT(s, "Camera: cannot draw without camera attached to scene", ); - - /* Recursively draw child objects */ - drawChildren(s, cameraMatrix()); -} - -template void AbstractCamera::drawChildren(typename AbstractObject::ObjectType* object, const typename DimensionTraits::MatrixType& transformationMatrix) { - for(typename AbstractObject::ObjectType* i = object->firstChild(); i; i = i->nextSibling()) { - /* Transformation matrix for the object */ - typename DimensionTraits::MatrixType matrix = transformationMatrix*i->transformation(); - - /* Draw the object and its children */ - i->draw(matrix, static_cast::CameraType*>(this)); - drawChildren(i, matrix); - } -} - -Camera2D* Camera2D::setProjection(const Vector2& size) { - /* Scale the volume down so it fits in (-1, 1) in all directions */ - rawProjectionMatrix = Matrix3::scaling(2.0f/size); - - fixAspectRatio(); - return this; -} - -Camera3D* Camera3D::setOrthographic(const Vector2& size, GLfloat near, GLfloat far) { - _near = near; - _far = far; - - Vector2 xyScale = 2.0f/size; - GLfloat zScale = 2.0f/(near-far); - - rawProjectionMatrix = Matrix4( - xyScale.x(), 0.0f, 0.0f, 0.0f, - 0.0f, xyScale.y(), 0.0f, 0.0f, - 0.0f, 0.0f, zScale, 0.0f, - 0.0f, 0.0f, near*zScale-1, 1.0f - ); - - fixAspectRatio(); - return this; -} - -Camera3D* Camera3D::setPerspective(GLfloat fov, GLfloat near, GLfloat far) { - _near = near; - _far = far; - - GLfloat xyScale = 1.0f/tan(fov/2); /* == near/size */ - GLfloat zScale = 1.0f/(near-far); - - rawProjectionMatrix = Matrix4( - xyScale, 0.0f, 0.0f, 0.0f, - 0.0f, xyScale, 0.0f, 0.0f, - 0.0f, 0.0f, (far+near)*zScale, -1.0f, - 0.0f, 0.0f, (2*far*near)*zScale, 0.0f - ); - - fixAspectRatio(); - return this; -} - -/* Explicitly instantiate the templates */ -template class AbstractCamera<2>; -template class AbstractCamera<3>; - }} diff --git a/src/SceneGraph/Camera.h b/src/SceneGraph/Camera.h index 21325afce..37d0bed94 100644 --- a/src/SceneGraph/Camera.h +++ b/src/SceneGraph/Camera.h @@ -16,10 +16,14 @@ */ /** @file - * @brief Class Magnum::SceneGraph::AbstractCamera, Magnum::SceneGraph::Camera2D, Magnum::SceneGraph::Camera3D + * @brief Class Magnum::SceneGraph::AbstractCamera, Magnum::SceneGraph::Camera2D, Magnum::SceneGraph::Camera3D, alias Magnum::SceneGraph::AbstractCamera2D, Magnum::SceneGraph::AbstractCamera3D */ -#include "Object.h" +#include "Math/Matrix3.h" +#include "Math/Matrix4.h" +#include "AbstractFeature.h" + +#include "magnumSceneGraphVisibility.h" #ifdef WIN32 /* I so HATE windows.h */ #undef near @@ -28,6 +32,14 @@ namespace Magnum { namespace SceneGraph { +template class Drawable; +template class FeatureGroup; +#ifndef MAGNUM_GCC46_COMPATIBILITY +template using DrawableGroup = FeatureGroup, T>; +#else +template class DrawableGroup; +#endif + /** @todo Export implementation symbols only for tests */ #ifndef DOXYGEN_GENERATING_OUTPUT @@ -36,18 +48,28 @@ namespace Implementation { NotPreserved, Extend, Clip }; - template MatrixType aspectRatioFix(AspectRatioPolicy aspectRatioPolicy, const Vector2& projectionScale, const Math::Vector2& viewport); - - /* These templates are instantiated in source file */ - extern template SCENEGRAPH_EXPORT Matrix3 aspectRatioFix(AspectRatioPolicy, const Vector2&, const Math::Vector2&); - extern template SCENEGRAPH_EXPORT Matrix4 aspectRatioFix(AspectRatioPolicy, const Vector2&, const Math::Vector2&); + template typename DimensionTraits::MatrixType aspectRatioFix(AspectRatioPolicy aspectRatioPolicy, const Math::Vector2& projectionScale, const Math::Vector2& viewport); } #endif /** -@brief %Camera object - */ -template class SCENEGRAPH_EXPORT AbstractCamera: public AbstractObject::ObjectType { +@brief Base for cameras + +See Drawable documentation for more information. + +@section AbstractCamera-explicit-specializations Explicit template specializations + +The following specialization are explicitly compiled into SceneGraph library. +For other specializations you have to use Camera.hpp implementation file to +avoid linker errors. See @ref compilation-speedup-hpp for more information. + + - @ref AbstractCamera "AbstractCamera<2>" + - @ref AbstractCamera "AbstractCamera<3>" + +@see Camera2D, Camera3D, Drawable, DrawableGroup, AbstractCamera2D, + AbstractCamera3D +*/ +template class SCENEGRAPH_EXPORT AbstractCamera: public AbstractFeature { public: /** * @brief Aspect ratio policy @@ -64,8 +86,13 @@ template class SCENEGRAPH_EXPORT AbstractCamera: public }; #endif - /** @copydoc AbstractObject::AbstractObject() */ - AbstractCamera(typename AbstractObject::ObjectType* parent = nullptr); + /** + * @brief Constructor + * @param object Object holding the camera + */ + inline AbstractCamera(AbstractObject* object): AbstractFeature(object), _aspectRatioPolicy(AspectRatioPolicy::NotPreserved) { + AbstractFeature::setCachedTransformations(AbstractFeature::CachedTransformation::InvertedAbsolute); + } virtual ~AbstractCamera() = 0; @@ -76,7 +103,7 @@ template class SCENEGRAPH_EXPORT AbstractCamera: public * @brief Set aspect ratio policy * @return Pointer to self (for method chaining) */ - typename AbstractObject::CameraType* setAspectRatioPolicy(AspectRatioPolicy policy); + AbstractCamera* setAspectRatioPolicy(AspectRatioPolicy policy); /** * @brief Camera matrix @@ -84,8 +111,8 @@ template class SCENEGRAPH_EXPORT AbstractCamera: public * Camera matrix describes world position relative to the camera and is * applied as first. */ - inline typename DimensionTraits::MatrixType cameraMatrix() { - this->setClean(); + inline typename DimensionTraits::MatrixType cameraMatrix() { + AbstractFeature::object()->setClean(); return _cameraMatrix; } @@ -96,7 +123,7 @@ template class SCENEGRAPH_EXPORT AbstractCamera: public * as last. * @see projectionSize() */ - inline typename DimensionTraits::MatrixType projectionMatrix() const { return _projectionMatrix; } + inline typename DimensionTraits::MatrixType projectionMatrix() const { return _projectionMatrix; } /** * @brief Size of (near) XY plane in current projection @@ -104,8 +131,8 @@ template class SCENEGRAPH_EXPORT AbstractCamera: public * Returns size of near XY plane computed from projection matrix. * @see projectionMatrix() */ - inline Vector2 projectionSize() const { - return {2.0f/_projectionMatrix[0].x(), 2.0f/_projectionMatrix[1].y()}; + inline Math::Vector2 projectionSize() const { + return {T(2.0)/_projectionMatrix[0].x(), T(2.0)/_projectionMatrix[1].y()}; } /** @brief Viewport size */ @@ -121,79 +148,95 @@ template class SCENEGRAPH_EXPORT AbstractCamera: public virtual void setViewport(const Math::Vector2& size); /** - * @brief Draw the scene + * @brief Draw * - * Draws the scene using drawChildren(). + * Draws given group of drawables. */ - virtual void draw(); - - using AbstractObject::ObjectType::draw; /* Don't hide Object's draw() */ + virtual void draw(DrawableGroup& group); protected: - /** - * Recalculates camera matrix. - */ - void clean(const typename DimensionTraits::MatrixType& absoluteTransformation); - - /** - * @brief Draw object children - * - * Recursively draws all children of the object. - */ - void drawChildren(typename AbstractObject::ObjectType* object, const typename DimensionTraits::MatrixType& transformationMatrix); + /** Recalculates camera matrix */ + inline void cleanInverted(const typename DimensionTraits::MatrixType& invertedAbsoluteTransformation) override { + _cameraMatrix = invertedAbsoluteTransformation; + } #ifndef DOXYGEN_GENERATING_OUTPUT inline void fixAspectRatio() { - _projectionMatrix = Implementation::aspectRatioFix::MatrixType>(_aspectRatioPolicy, {rawProjectionMatrix[0].x(), rawProjectionMatrix[1].y()}, _viewport)*rawProjectionMatrix; + _projectionMatrix = Implementation::aspectRatioFix(_aspectRatioPolicy, {rawProjectionMatrix[0].x(), rawProjectionMatrix[1].y()}, _viewport)*rawProjectionMatrix; } - typename DimensionTraits::MatrixType rawProjectionMatrix; + typename DimensionTraits::MatrixType rawProjectionMatrix; AspectRatioPolicy _aspectRatioPolicy; #endif private: - typename DimensionTraits::MatrixType _projectionMatrix; - typename DimensionTraits::MatrixType _cameraMatrix; + typename DimensionTraits::MatrixType _projectionMatrix; + typename DimensionTraits::MatrixType _cameraMatrix; Math::Vector2 _viewport; }; -template inline AbstractCamera::~AbstractCamera() {} +template inline AbstractCamera::~AbstractCamera() {} + +/** +@brief Base for two-dimensional cameras +Convenience alternative to %AbstractCamera<2, T>. See AbstractCamera +for more information. +@note Not available on GCC < 4.7. Use %AbstractCamera<2, T> instead. +@see AbstractCamera3D +@todoc Remove workaround when Doxygen supports alias template +*/ #ifndef DOXYGEN_GENERATING_OUTPUT -namespace Implementation { - template class Camera {}; +#ifndef MAGNUM_GCC46_COMPATIBILITY +template using AbstractCamera2D = AbstractCamera<2, T>; +#endif +#else +typedef AbstractCamera<2, T = GLfloat> AbstractCamera2D; +#endif - template<> class Camera<2> { - public: - inline constexpr static Matrix3 aspectRatioScale(const Vector2& scale) { - return Matrix3::scaling({scale.x(), scale.y()}); - } - }; - template<> class Camera<3> { - public: - inline constexpr static Matrix4 aspectRatioScale(const Vector2& scale) { - return Matrix4::scaling({scale.x(), scale.y(), 1.0f}); - } - }; -} +/** +@brief Base for three-dimensional cameras + +Convenience alternative to %AbstractCamera<3, T>. See AbstractCamera +for more information. +@note Not available on GCC < 4.7. Use %AbstractCamera<3, T> instead. +@see AbstractCamera2D +@todoc Remove workaround when Doxygen supports alias template +*/ +#ifndef DOXYGEN_GENERATING_OUTPUT +#ifndef MAGNUM_GCC46_COMPATIBILITY +template using AbstractCamera3D = AbstractCamera<3, T>; +#endif +#else +typedef AbstractCamera<3, T = GLfloat> AbstractCamera3D; #endif /** -@brief %Camera for two-dimensional scenes +@brief Camera for two-dimensional scenes + +See Drawable documentation for more information. + +@section Object-explicit-specializations Explicit template specializations -@see Camera3D +The following specialization are explicitly compiled into SceneGraph library. +For other specializations you have to use Camera.hpp implementation file to +avoid linker errors. See @ref compilation-speedup-hpp for more information. + + - @ref Camera2D "Camera2D" + +@see Camera3D, Drawable, DrawableGroup */ -class SCENEGRAPH_EXPORT Camera2D: public AbstractCamera<2> { +template class SCENEGRAPH_EXPORT Camera2D: public AbstractCamera<2, T> { public: /** * @brief Constructor - * @param parent Parent object + * @param object %Object holding this feature * * Sets orthographic projection to the default OpenGL cube (range @f$ [-1; 1] @f$ in all directions). * @see setOrthographic() */ - inline Camera2D(Object2D* parent = nullptr): AbstractCamera<2>(parent) {} + inline Camera2D(AbstractObject<2, T>* object): AbstractCamera<2, T>(object) {} /** * @brief Set projection @@ -203,24 +246,42 @@ class SCENEGRAPH_EXPORT Camera2D: public AbstractCamera<2> { * The area of given size will be scaled down to range @f$ [-1; 1] @f$ * on all directions. */ - Camera2D* setProjection(const Vector2& size); + Camera2D* setProjection(const Math::Vector2& size); + + /* Overloads to remove WTF-factor from method chaining order */ + #ifndef DOXYGEN_GENERATING_OUTPUT + inline Camera2D* setAspectRatioPolicy(typename AbstractCamera<2, T>::AspectRatioPolicy policy) { + AbstractCamera<2, T>::setAspectRatioPolicy(policy); + return this; + } + #endif }; /** -@brief %Camera for three-dimensional scenes +@brief Camera for three-dimensional scenes + +See Drawable documentation for more information. + +@section Object-explicit-specializations Explicit template specializations + +The following specialization are explicitly compiled into SceneGraph library. +For other specializations you have to use Camera.hpp implementation file to +avoid linker errors. See @ref compilation-speedup-hpp for more information. + + - @ref Camera3D "Camera3D" -@see Camera2D +@see Camera2D, Drawable, DrawableGroup */ -class SCENEGRAPH_EXPORT Camera3D: public AbstractCamera<3> { +template class SCENEGRAPH_EXPORT Camera3D: public AbstractCamera<3, T> { public: /** * @brief Constructor - * @param parent Parent object + * @param object %Object holding this feature * * Sets orthographic projection to the default OpenGL cube (range @f$ [-1; 1] @f$ in all directions). * @see setOrthographic(), setPerspective() */ - inline Camera3D(Object3D* parent = nullptr): AbstractCamera<3>(parent), _near(0.0f), _far(0.0f) {} + inline Camera3D(AbstractObject<3, T>* object): AbstractCamera<3, T>(object), _near(0.0f), _far(0.0f) {} /** * @brief Set orthographic projection @@ -232,7 +293,7 @@ class SCENEGRAPH_EXPORT Camera3D: public AbstractCamera<3> { * The volume of given size will be scaled down to range @f$ [-1; 1] @f$ * on all directions. */ - Camera3D* setOrthographic(const Vector2& size, GLfloat near, GLfloat far); + Camera3D* setOrthographic(const Math::Vector2& size, T near, T far); /** * @brief Set perspective projection @@ -243,18 +304,32 @@ class SCENEGRAPH_EXPORT Camera3D: public AbstractCamera<3> { * * @todo Aspect ratio */ - Camera3D* setPerspective(GLfloat fov, GLfloat near, GLfloat far); + Camera3D* setPerspective(T fov, T near, T far); /** @brief Near clipping plane */ - inline GLfloat near() const { return _near; } + inline T near() const { return _near; } /** @brief Far clipping plane */ - inline GLfloat far() const { return _far; } + inline T far() const { return _far; } + + /* Overloads to remove WTF-factor from method chaining order */ + #ifndef DOXYGEN_GENERATING_OUTPUT + inline Camera3D* setAspectRatioPolicy(typename AbstractCamera<3, T>::AspectRatioPolicy policy) { + AbstractCamera<3, T>::setAspectRatioPolicy(policy); + return this; + } + #endif private: - GLfloat _near, _far; + T _near, _far; }; +/* Make implementers' life easier */ +#ifndef MAGNUM_GCC46_COMPATIBILITY +template using DrawableGroup2D = DrawableGroup<2, T>; +template using DrawableGroup3D = DrawableGroup<3, T>; +#endif + }} #endif diff --git a/src/SceneGraph/Camera.hpp b/src/SceneGraph/Camera.hpp new file mode 100644 index 000000000..2d1e7a42a --- /dev/null +++ b/src/SceneGraph/Camera.hpp @@ -0,0 +1,145 @@ +#ifndef Magnum_SceneGraph_Camera_hpp +#define Magnum_SceneGraph_Camera_hpp +/* + Copyright © 2010, 2011, 2012 Vladimír Vondruš + + This file is part of Magnum. + + Magnum is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 3 + only, as published by the Free Software Foundation. + + Magnum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License version 3 for more details. +*/ + +/** @file + * @brief @ref compilation-speedup-hpp "Template implementation" for Camera.h + */ + +#include "Camera.h" + +#include + +#include "Drawable.h" +#include "Scene.h" + +using namespace std; + +namespace Magnum { namespace SceneGraph { + +#ifndef DOXYGEN_GENERATING_OUTPUT +namespace Implementation { + +template class Camera {}; + +template class Camera<2, T> { + public: + inline constexpr static Math::Matrix3 aspectRatioScale(const Math::Vector2& scale) { + return Math::Matrix3::scaling({scale.x(), scale.y()}); + } +}; +template class Camera<3, T> { + public: + inline constexpr static Math::Matrix4 aspectRatioScale(const Math::Vector2& scale) { + return Math::Matrix4::scaling({scale.x(), scale.y(), 1.0f}); + } +}; + +template typename DimensionTraits::MatrixType aspectRatioFix(AspectRatioPolicy aspectRatioPolicy, const Math::Vector2& projectionScale, const Math::Vector2& viewport) { + /* Don't divide by zero / don't preserve anything */ + if(projectionScale.x() == 0 || projectionScale.y() == 0 || viewport.x() == 0 || viewport.y() == 0 || aspectRatioPolicy == AspectRatioPolicy::NotPreserved) + return {}; + + Math::Vector2 relativeAspectRatio = Math::Vector2::from(viewport)*projectionScale; + + /* Extend on larger side = scale larger side down + Clip on smaller side = scale smaller side up */ + return Camera::aspectRatioScale( + (relativeAspectRatio.x() > relativeAspectRatio.y()) == (aspectRatioPolicy == AspectRatioPolicy::Extend) ? + Vector2(relativeAspectRatio.y()/relativeAspectRatio.x(), T(1.0)) : + Vector2(T(1.0), relativeAspectRatio.x()/relativeAspectRatio.y())); +} + +} +#endif + +template AbstractCamera* AbstractCamera::setAspectRatioPolicy(AspectRatioPolicy policy) { + _aspectRatioPolicy = policy; + fixAspectRatio(); + return this; +} + +template void AbstractCamera::setViewport(const Math::Vector2& size) { + _viewport = size; + fixAspectRatio(); +} + +template void AbstractCamera::draw(DrawableGroup& group) { + AbstractObject* scene = AbstractFeature::object()->sceneObject(); + CORRADE_ASSERT(scene, "Camera::draw(): cannot draw when camera is not part of any scene", ); + + /* Compute camera matrix */ + AbstractFeature::object()->setClean(); + + /* Compute transformations of all objects in the group relative to the camera */ + std::vector*> objects(group.size()); + for(std::size_t i = 0; i != group.size(); ++i) + objects[i] = group[i]->object(); + std::vector::MatrixType> transformations = + scene->transformationMatrices(objects, _cameraMatrix); + + /* Perform the drawing */ + for(std::size_t i = 0; i != transformations.size(); ++i) + group[i]->draw(transformations[i], this); +} + +template Camera2D* Camera2D::setProjection(const Math::Vector2& size) { + /* Scale the volume down so it fits in (-1, 1) in all directions */ + AbstractCamera<2, T>::rawProjectionMatrix = Math::Matrix3::scaling(2.0f/size); + + AbstractCamera<2, T>::fixAspectRatio(); + return this; +} + +template Camera3D* Camera3D::setOrthographic(const Math::Vector2& size, T near, T far) { + _near = near; + _far = far; + + Math::Vector2 xyScale = T(2.0)/size; + T zScale = T(2.0)/(near-far); + + AbstractCamera<3, T>::rawProjectionMatrix = Math::Matrix4( + xyScale.x(), T(0.0), T(0.0), T(0.0), + T(0.0), xyScale.y(), T(0.0), T(0.0), + T(0.0), T(0.0), zScale, T(0.0), + T(0.0), T(0.0), near*zScale-1, T(1.0) + ); + + AbstractCamera<3, T>::fixAspectRatio(); + return this; +} + +template Camera3D* Camera3D::setPerspective(T fov, T near, T far) { + _near = near; + _far = far; + + T xyScale = T(1.0)/tan(fov/2); /* == near/size */ + T zScale = T(1.0)/(near-far); + + AbstractCamera<3, T>::rawProjectionMatrix = Matrix4( + xyScale, T(0.0), T(0.0), T(0.0), + T(0.0), xyScale, T(0.0), T(0.0), + T(0.0), T(0.0), (far+near)*zScale, T(-1.0), + T(0.0), T(0.0), (2*far*near)*zScale, T(0.0) + ); + + AbstractCamera<3, T>::fixAspectRatio(); + return this; +} + +}} + +#endif diff --git a/src/SceneGraph/Drawable.h b/src/SceneGraph/Drawable.h new file mode 100644 index 000000000..7b93cc061 --- /dev/null +++ b/src/SceneGraph/Drawable.h @@ -0,0 +1,192 @@ +#ifndef Magnum_SceneGraph_Drawable_h +#define Magnum_SceneGraph_Drawable_h +/* + Copyright © 2010, 2011, 2012 Vladimír Vondruš + + This file is part of Magnum. + + Magnum is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 3 + only, as published by the Free Software Foundation. + + Magnum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License version 3 for more details. +*/ + +/** @file + * @brief Class Magnum::SceneGraph::Drawable, Magnum::SceneGraph::DrawableGroup, alias Magnum::SceneGraph::Drawable2D, Magnum::SceneGraph::Drawable3D, Magnum::SceneGraph::DrawableGroup2D, Magnum::SceneGraph::DrawableGroup3D + */ + +#include "AbstractGroupedFeature.h" + +namespace Magnum { namespace SceneGraph { + +template class AbstractCamera; +template class Drawable; +#ifndef MAGNUM_GCC46_COMPATIBILITY +template using DrawableGroup = FeatureGroup, T>; +#else +template class DrawableGroup; +#endif + +/** +@brief %Drawable + +Adds drawing function to object. Each %Drawable is part of some DrawableGroup +and the whole group is drawn with particular camera using AbstractCamera::draw(). + +@section Drawable-usage Usage + +First thing is add Drawable feature to some object and implement draw(). You +can do it conveniently using multiple inheritance (see @ref scenegraph-features +for introduction). Example: +@code +typedef SceneGraph::Object> Object3D; +typedef SceneGraph::Scene> Scene3D; + +class DrawableObject: public Object3D, SceneGraph::Drawable3D<> { + public: + DrawableObject(Object* parent, SceneGraph::DrawableGroup3D<>* group): Object3D(parent), SceneGraph::Drawable3D<>(this, group) { + // ... + } + + void draw(const Matrix4& transformationMatrix, AbstractCamera3D<>* camera) override { + // ... + } +} +@endcode + +Then you add these objects to your scene and some drawable group and transform +them as you like: +@code +Scene3D scene; +SceneGraph::DrawableGroup3D<> group; + +(new DrawableObject(&scene, &group)) + ->translate(Vector3::yAxis(-0.3f)) + ->rotateX(deg(30.0f)); +(new AnotherDrawableObject(&scene, &group)) + ->translate(Vector3::zAxis(0.5f)); +// ... +@endcode + +The last thing you need is Camera attached to some object (thus using its +transformation) and with it you can perform drawing in your draw event: +@code +Camera3D<> camera(&cameraObject); + +void MyApplication::drawEvent() { + camera.draw(&group); +} +@endcode + +@see Drawable2D, Drawable3D, DrawableGroup2D, DrawableGroup3D +*/ +template class Drawable: public AbstractGroupedFeature, T> { + public: + /** @copydoc AbstractGroupedFeature::AbstractGroupedFeature() */ + inline Drawable(AbstractObject* object, DrawableGroup* group = nullptr): AbstractGroupedFeature, T>(object, group) {} + + /** + * @brief Draw the object using given camera + * @param transformationMatrix %Object transformation relative + * to camera + * @param camera Camera + * + * Projection matrix can be retrieved from AbstractCamera::projectionMatrix(). + */ + virtual void draw(const typename DimensionTraits::MatrixType& transformationMatrix, AbstractCamera* camera) = 0; +}; + +/** +@brief Two-dimensional drawable + +Convenience alternative to %Drawable<2, T>. See Drawable for more +information. +@note Not available on GCC < 4.7. Use %Drawable<2, T> instead. +@see Drawable3D +@todoc Remove workaround when Doxygen supports alias template +*/ +#ifndef DOXYGEN_GENERATING_OUTPUT +#ifndef MAGNUM_GCC46_COMPATIBILITY +template using Drawable2D = Drawable<2, T>; +#endif +#else +typedef Drawable<2, T = GLfloat> Drawable2D; +#endif + +/** +@brief Three-dimensional drawable + +Convenience alternative to %Drawable<3, T>. See Drawable for more +information. +@note Not available on GCC < 4.7. Use %Drawable<3, T> instead. +@see Drawable2D +@todoc Remove workaround when Doxygen supports alias template +*/ +#ifndef DOXYGEN_GENERATING_OUTPUT +#ifndef MAGNUM_GCC46_COMPATIBILITY +template using Drawable3D = Drawable<3, T>; +#endif +#else +typedef Drawable<3, T = GLfloat> Drawable3D; +#endif + +/** +@brief Group of drawables + +See Drawable for more information. +@see DrawableGroup2D, DrawableGroup3D +@todoc Remove workaround when Doxygen supports alias template +*/ +#if !defined(MAGNUM_GCC46_COMPATIBILITY) && !defined(DOXYGEN_GENERATING_OUTPUT) +template using DrawableGroup = FeatureGroup, T>; +#else +template class DrawableGroup: public FeatureGroup, T> {}; +#endif + +/** +@brief Group of two-dimensional drawables + +Convenience alternative to %DrawableGroup<2, T>. See Drawable for +more information. +@note Not available on GCC < 4.7. Use %Drawable<2, T> instead. +@see DrawableGroup3D +@todoc Remove workaround when Doxygen supports alias template +*/ +#ifndef DOXYGEN_GENERATING_OUTPUT +#ifndef MAGNUM_GCC46_COMPATIBILITY +template using DrawableGroup2D = DrawableGroup<2, T>; +#endif +#else +typedef DrawableGroup<2, T = GLfloat> DrawableGroup2D; +#endif + +/** +@brief Group of three-dimensional drawables + +Convenience alternative to %DrawableGroup<3, T>. See Drawable for +more information. +@note Not available on GCC < 4.7. Use %Drawable<3, T> instead. +@see DrawableGroup2D +@todoc Remove workaround when Doxygen supports alias template +*/ +#ifndef DOXYGEN_GENERATING_OUTPUT +#ifndef MAGNUM_GCC46_COMPATIBILITY +template using DrawableGroup3D = DrawableGroup<3, T>; +#endif +#else +typedef DrawableGroup<3, T = GLfloat> DrawableGroup3D; +#endif + +/* Make implementers' life easier */ +#ifndef MAGNUM_GCC46_COMPATIBILITY +template using AbstractCamera2D = AbstractCamera<2, T>; +template using AbstractCamera3D = AbstractCamera<3, T>; +#endif + +}} + +#endif diff --git a/src/SceneGraph/Light.h b/src/SceneGraph/Light.h deleted file mode 100644 index 74070122c..000000000 --- a/src/SceneGraph/Light.h +++ /dev/null @@ -1,60 +0,0 @@ -#ifndef Magnum_SceneGraph_Light_h -#define Magnum_SceneGraph_Light_h -/* - Copyright © 2010, 2011, 2012 Vladimír Vondruš - - This file is part of Magnum. - - Magnum is free software: you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License version 3 - only, as published by the Free Software Foundation. - - Magnum is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Lesser General Public License version 3 for more details. -*/ - -/** @file - * @brief Class Magnum::SceneGraph::Light - */ - -#include "Math/Point3D.h" -#include "Object.h" - -namespace Magnum { namespace SceneGraph { - -/** - * @brief Basic light object - * - * Provides cached light position. - */ -class SCENEGRAPH_EXPORT Light: public Object3D { - public: - /** - * @brief Constructor - * @param parent Parent object - */ - inline Light(Object3D* parent = nullptr): Object3D(parent) {} - - /** - * @brief Light position relative to root object (scene) - */ - inline Point3D position() { - setClean(); - return _position; - } - - protected: - /** - * Recomputes light position. - */ - void clean(const Matrix4& absoluteTransformation); - - private: - Point3D _position; -}; - -}} - -#endif diff --git a/src/SceneGraph/Light.cpp b/src/SceneGraph/MatrixTransformation2D.cpp similarity index 78% rename from src/SceneGraph/Light.cpp rename to src/SceneGraph/MatrixTransformation2D.cpp index 385c6efc4..340371b97 100644 --- a/src/SceneGraph/Light.cpp +++ b/src/SceneGraph/MatrixTransformation2D.cpp @@ -13,14 +13,14 @@ GNU Lesser General Public License version 3 for more details. */ -#include "Light.h" +#include "MatrixTransformation2D.h" -namespace Magnum { namespace SceneGraph { +#include "Object.hpp" -void Light::clean(const Matrix4& absoluteTransformation) { - Object3D::clean(absoluteTransformation); +namespace Magnum { namespace SceneGraph { - _position = absoluteTransformation[3]; -} +#ifndef DOXYGEN_GENERATING_OUTPUT +template class SCENEGRAPH_EXPORT Object>; +#endif }} diff --git a/src/SceneGraph/MatrixTransformation2D.h b/src/SceneGraph/MatrixTransformation2D.h new file mode 100644 index 000000000..e2e9b783f --- /dev/null +++ b/src/SceneGraph/MatrixTransformation2D.h @@ -0,0 +1,136 @@ +#ifndef Magnum_SceneGraph_MatrixTransformation2D_h +#define Magnum_SceneGraph_MatrixTransformation2D_h +/* + Copyright © 2010, 2011, 2012 Vladimír Vondruš + + This file is part of Magnum. + + Magnum is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 3 + only, as published by the Free Software Foundation. + + Magnum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License version 3 for more details. +*/ + +/** @file + * @brief Class Magnum::SceneGraph::MatrixTransformation2D + */ + +#include "Math/Matrix3.h" +#include "AbstractTranslationRotationScaling2D.h" +#include "Object.h" + +namespace Magnum { namespace SceneGraph { + +/** +@brief Two-dimensional transformation implemented using matrices + +@see MatrixTransformation3D +*/ +template class MatrixTransformation2D: public AbstractTranslationRotationScaling2D { + public: + /** @brief Transformation matrix type */ + typedef typename DimensionTraits<2, T>::MatrixType DataType; + + #ifndef DOXYGEN_GENERATING_OUTPUT + inline constexpr static Math::Matrix3 fromMatrix(const Math::Matrix3& matrix) { + return matrix; + } + + inline constexpr static Math::Matrix3 toMatrix(const Math::Matrix3& transformation) { + return transformation; + } + + inline static Math::Matrix3 compose(const Math::Matrix3& parent, const Math::Matrix3& child) { + return parent*child; + } + + inline static Math::Matrix3 inverted(const Math::Matrix3& transformation) { + return transformation.inverted(); + } + + inline Math::Matrix3 transformation() const { + return _transformation; + } + #endif + + /** + * @brief Set transformation + * @return Pointer to self (for method chaining) + */ + MatrixTransformation2D* setTransformation(const Math::Matrix3& transformation) { + /* Setting transformation is forbidden for the scene */ + /** @todo Assert for this? */ + /** @todo Do this in some common code? */ + if(!static_cast>*>(this)->isScene()) { + _transformation = transformation; + static_cast>*>(this)->setDirty(); + } + + return this; + } + + /** + * @brief Multiply transformation + * @param transformation Transformation + * @param type Transformation type + * @return Pointer to self (for method chaining) + */ + inline MatrixTransformation2D* multiplyTransformation(const Math::Matrix3& transformation, TransformationType type = TransformationType::Global) { + setTransformation(type == TransformationType::Global ? + transformation*_transformation : _transformation*transformation); + return this; + } + + /** + * @copydoc AbstractTranslationRotationScaling2D::translate() + * Same as calling multiplyTransformation() with Matrix3::translation(). + */ + inline MatrixTransformation2D* translate(const Math::Vector2& vector, TransformationType type = TransformationType::Global) override { + multiplyTransformation(Math::Matrix3::translation(vector), type); + return this; + } + + /** + * @copydoc AbstractTranslationRotationScaling2D::rotate() + * Same as calling multiplyTransformation() with Matrix3::rotation(). + */ + inline MatrixTransformation2D* rotate(T angle, TransformationType type = TransformationType::Global) override { + multiplyTransformation(Math::Matrix3::rotation(angle), type); + return this; + } + + /** + * @copydoc AbstractTranslationRotationScaling2D::scale() + * Same as calling multiplyTransformation() with Matrix3::scaling(). + */ + inline MatrixTransformation2D* scale(const Math::Vector2& vector, TransformationType type = TransformationType::Global) override { + multiplyTransformation(Math::Matrix3::scaling(vector), type); + return this; + } + + /** + * @brief Move object in stacking order + * @param under Sibling object under which to move or `nullptr`, + * if you want to move it above all. + * @return Pointer to self (for method chaining) + */ + inline MatrixTransformation2D* move(Object>* under) { + static_cast*>(this)->Corrade::Containers::LinkedList>>::move(this, under); + return this; + } + + protected: + /* Allow construction only from Object */ + inline MatrixTransformation2D() {} + + private: + Math::Matrix3 _transformation; +}; + +}} + +#endif diff --git a/src/SceneGraph/MatrixTransformation3D.cpp b/src/SceneGraph/MatrixTransformation3D.cpp new file mode 100644 index 000000000..8c2d78a48 --- /dev/null +++ b/src/SceneGraph/MatrixTransformation3D.cpp @@ -0,0 +1,26 @@ +/* + Copyright © 2010, 2011, 2012 Vladimír Vondruš + + This file is part of Magnum. + + Magnum is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 3 + only, as published by the Free Software Foundation. + + Magnum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License version 3 for more details. +*/ + +#include "MatrixTransformation3D.h" + +#include "Object.hpp" + +namespace Magnum { namespace SceneGraph { + +#ifndef DOXYGEN_GENERATING_OUTPUT +template class SCENEGRAPH_EXPORT Object>; +#endif + +}} diff --git a/src/SceneGraph/MatrixTransformation3D.h b/src/SceneGraph/MatrixTransformation3D.h new file mode 100644 index 000000000..973e0bd36 --- /dev/null +++ b/src/SceneGraph/MatrixTransformation3D.h @@ -0,0 +1,167 @@ +#ifndef Magnum_SceneGraph_MatrixTransformation3D_h +#define Magnum_SceneGraph_MatrixTransformation3D_h +/* + Copyright © 2010, 2011, 2012 Vladimír Vondruš + + This file is part of Magnum. + + Magnum is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 3 + only, as published by the Free Software Foundation. + + Magnum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License version 3 for more details. +*/ + +/** @file + * @brief Class Magnum::SceneGraph::MatrixTransformation3D + */ + +#include "Math/Matrix4.h" +#include "AbstractTranslationRotationScaling3D.h" +#include "Object.h" + +namespace Magnum { namespace SceneGraph { + +/** +@brief Three-dimensional transformation implemented using matrices + +@see MatrixTransformation2D +*/ +template class MatrixTransformation3D: public AbstractTranslationRotationScaling3D { + public: + /** @brief Transformation matrix type */ + typedef typename DimensionTraits<3, T>::MatrixType DataType; + + #ifndef DOXYGEN_GENERATING_OUTPUT + inline constexpr static Math::Matrix4 fromMatrix(const Math::Matrix4& matrix) { + return matrix; + } + + inline constexpr static Math::Matrix4 toMatrix(const Math::Matrix4& transformation) { + return transformation; + } + + inline static Math::Matrix4 compose(const Math::Matrix4& parent, const Math::Matrix4& child) { + return parent*child; + } + + inline static Math::Matrix4 inverted(const Math::Matrix4& transformation) { + return transformation.inverted(); + } + + inline Math::Matrix4 transformation() const { + return _transformation; + } + #endif + + /** + * @brief Set transformation + * @return Pointer to self (for method chaining) + */ + MatrixTransformation3D* setTransformation(const Math::Matrix4& transformation) { + /* Setting transformation is forbidden for the scene */ + /** @todo Assert for this? */ + /** @todo Do this in some common code? */ + if(!static_cast>*>(this)->isScene()) { + _transformation = transformation; + static_cast>*>(this)->setDirty(); + } + + return this; + } + + /** + * @brief Multiply transformation + * @param transformation Transformation + * @param type Transformation type + * @return Pointer to self (for method chaining) + */ + inline MatrixTransformation3D* multiplyTransformation(const Math::Matrix4& transformation, TransformationType type = TransformationType::Global) { + setTransformation(type == TransformationType::Global ? + transformation*_transformation : _transformation*transformation); + return this; + } + + /** + * @copydoc AbstractTranslationRotationScaling3D::translate() + * Same as calling multiplyTransformation() with Matrix4::translation(). + */ + inline MatrixTransformation3D* translate(const Math::Vector3& vector, TransformationType type = TransformationType::Global) override { + multiplyTransformation(Math::Matrix4::translation(vector), type); + return this; + } + + /** + * @copydoc AbstractTranslationRotationScaling3D::rotate() + * Same as calling multiplyTransformation() with Matrix4::rotation(). + */ + inline MatrixTransformation3D* rotate(T angle, const Math::Vector3& normalizedAxis, TransformationType type = TransformationType::Global) override { + multiplyTransformation(Math::Matrix4::rotation(angle, normalizedAxis), type); + return this; + } + + /** + * @brief Rotate object around X axis + * @param angle Angle in radians, counterclockwise + * @param type Transformation type + * @return Pointer to self (for method chaining) + * + * Same as calling multiplyTransformation() with Matrix4::rotationX(). + * @see deg(), rad() + */ + inline MatrixTransformation3D* rotateX(T angle, TransformationType type = TransformationType::Global) override { + multiplyTransformation(Math::Matrix4::rotationX(angle), type); + return this; + } + + /** + * @brief Rotate object around Y axis + * @param angle Angle in radians, counterclockwise + * @param type Transformation type + * @return Pointer to self (for method chaining) + * + * Same as calling multiplyTransformation() with Matrix4::rotationY(). + * @see deg(), rad() + */ + inline MatrixTransformation3D* rotateY(T angle, TransformationType type = TransformationType::Global) override { + multiplyTransformation(Math::Matrix4::rotationY(angle), type); + return this; + } + + /** + * @brief Rotate object around Z axis + * @param angle Angle in radians, counterclockwise + * @param type Transformation type + * @return Pointer to self (for method chaining) + * + * Same as calling multiplyTransformation() with Matrix4::rotationZ(). + * @see deg(), rad() + */ + inline MatrixTransformation3D* rotateZ(T angle, TransformationType type = TransformationType::Global) override { + multiplyTransformation(Math::Matrix4::rotationZ(angle), type); + return this; + } + + /** + * @copydoc AbstractTranslationRotationScaling3D::scale() + * Same as calling multiplyTransformation() with Matrix4::scaling(). + */ + inline MatrixTransformation3D* scale(const Math::Vector3& vector, TransformationType type = TransformationType::Global) override { + multiplyTransformation(Math::Matrix4::scaling(vector), type); + return this; + } + + protected: + /* Allow construction only from Object */ + inline MatrixTransformation3D() {} + + private: + Math::Matrix4 _transformation; +}; + +}} + +#endif diff --git a/src/SceneGraph/Object.cpp b/src/SceneGraph/Object.cpp deleted file mode 100644 index 3cf9b2316..000000000 --- a/src/SceneGraph/Object.cpp +++ /dev/null @@ -1,147 +0,0 @@ -/* - Copyright © 2010, 2011, 2012 Vladimír Vondruš - - This file is part of Magnum. - - Magnum is free software: you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License version 3 - only, as published by the Free Software Foundation. - - Magnum is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Lesser General Public License version 3 for more details. -*/ - -#include "Object.h" - -#include - -#include "Scene.h" -#include "Camera.h" - -using namespace std; -using namespace Magnum::Math; - -namespace Magnum { namespace SceneGraph { - -template typename AbstractObject::ObjectType* AbstractObject::setParent(ObjectType* parent) { - /* Skip if nothing to do or this is scene */ - if(this->parent() == parent || isScene()) return static_cast(this); - - /* Only Fry can be his own grandfather */ - ObjectType* p = parent; - while(p) { - /** @todo Assert for this */ - if(p == this) return static_cast(this); - p = p->parent(); - } - - /* Remove the object from old parent children list */ - if(this->parent()) - this->parent()->cut(static_cast(this)); - - /* Add the object to list of new parent */ - if(parent) - parent->insert(static_cast(this)); - - setDirty(); - return static_cast(this); -} - -template typename DimensionTraits::MatrixType AbstractObject::absoluteTransformation(CameraType* camera) { - /* Shortcut for absolute transformation of camera relative to itself */ - if(camera == this) return typename DimensionTraits::MatrixType(); - - typename DimensionTraits::MatrixType t = _transformation; - - ObjectType* p = parent(); - while(p != nullptr) { - t = p->transformation()*t; - - /* We got to the scene, multiply with camera matrix */ - if(p->isScene()) { - if(camera) { - CORRADE_ASSERT(camera->scene() == scene(), "Object::absoluteTransformation(): the camera is not part of the same scene as object!", t); - t = camera->cameraMatrix()*t; - } - - break; - } - - p = p->parent(); - } - - CORRADE_ASSERT(p != nullptr || camera == nullptr, "Object::absoluteTransformation(): the object is not part of camera scene!", t); - - return t; -} - -template typename AbstractObject::SceneType* AbstractObject::scene() { - /* Goes up the family tree until it finds object which is parent of itself - (that's the scene) */ - ObjectType* p = parent(); - while(p != nullptr) { - if(p->isScene()) return static_cast(p); - p = p->parent(); - } - - return nullptr; -} - -template typename AbstractObject::ObjectType* AbstractObject::setTransformation(const typename DimensionTraits::MatrixType& transformation) { - /* Setting transformation is forbidden for the scene */ - /** @todo Assert for this? */ - if(isScene()) return static_cast(this); - - _transformation = transformation; - setDirty(); - return static_cast(this); -} - -template void AbstractObject::setDirty() { - /* The object (and all its children) are already dirty, nothing to do */ - if(dirty) return; - - dirty = true; - - /* Make all children dirty */ - for(ObjectType* i = firstChild(); i; i = i->nextSibling()) - i->setDirty(); -} - -template void AbstractObject::setClean() { - /* The object (and all its parents) are already clean, nothing to do */ - if(!dirty) return; - - /* Collect all parents */ - stack objects; - ObjectType* p = static_cast(this); - for(;;) { - objects.push(p); - - /* Stop on root object / clean object */ - if(p->parent() == nullptr || !p->parent()->isDirty()) - break; - - p = p->parent(); - } - - /* Call setClean(const Matrix4&) for every parent and also this object */ - ObjectType* o = objects.top(); - objects.pop(); - typename DimensionTraits::MatrixType absoluteTransformation = o->absoluteTransformation(); - o->clean(absoluteTransformation); - while(!objects.empty()) { - o = objects.top(); - objects.pop(); - absoluteTransformation = absoluteTransformation*o->transformation(); - o->clean(absoluteTransformation); - } -} - -/* Explicitly instantiate the templates */ -template class AbstractObject<2>; -template class AbstractObject<3>; - -}} diff --git a/src/SceneGraph/Object.h b/src/SceneGraph/Object.h index 61038bc5c..94ac66e63 100644 --- a/src/SceneGraph/Object.h +++ b/src/SceneGraph/Object.h @@ -16,89 +16,73 @@ */ /** @file - * @brief Class Magnum::SceneGraph::AbstractObject, Magnum::SceneGraph::Object2D, Magnum::SceneGraph::Object3D + * @brief Class Magnum::SceneGraph::Object */ -#include +#include -#include "Math/Matrix3.h" -#include "Math/Matrix4.h" -#include "Magnum.h" -#include "DimensionTraits.h" +#include "AbstractFeature.h" +#include "AbstractObject.h" #include "magnumSceneGraphVisibility.h" namespace Magnum { namespace SceneGraph { -class Camera2D; -class Camera3D; -class Object2D; -class Object3D; -template class Scene; -typedef Scene<2> Scene2D; -typedef Scene<3> Scene3D; +template class Scene; #ifndef DOXYGEN_GENERATING_OUTPUT namespace Implementation { - template struct ObjectDimensionTraits {}; - - template<> struct ObjectDimensionTraits<2> { - typedef Object2D ObjectType; - typedef Camera2D CameraType; - typedef Scene2D SceneType; + enum class ObjectFlag: std::uint8_t { + Dirty = 1 << 0, + Visited = 1 << 1, + Joint = 1 << 2 }; - template<> struct ObjectDimensionTraits<3> { - typedef Object3D ObjectType; - typedef Camera3D CameraType; - typedef Scene3D SceneType; - }; + typedef Corrade::Containers::EnumSet ObjectFlags; + + CORRADE_ENUMSET_OPERATORS(ObjectFlags) } #endif /** -@todo User-specified Object implementation: -- for front-to-back sorting, LoD changes etc. -- for different parent/children implementation (e.g. no std::set, direct - access to scene etc.) -- for using doubles/halves instead of floats -- for using quat + position instead of matrices (where (asymmetric) scaling is - not needed) -*/ +@brief %Object -/** -@brief Base for all positioned objects +Base of scene graph. Contains specific transformation implementation, takes +care of parent/children relationships and contains features. See +@ref scenegraph for introduction. -@todo Transform transformation when changing parent, so the object stays in -place. - */ -template class SCENEGRAPH_EXPORT AbstractObject: public Corrade::Containers::LinkedList::ObjectType>, public Corrade::Containers::LinkedListItem::ObjectType, typename Implementation::ObjectDimensionTraits::ObjectType> { - #ifndef DOXYGEN_GENERATING_OUTPUT - AbstractObject(const AbstractObject& other) = delete; - AbstractObject(AbstractObject&& other) = delete; - AbstractObject& operator=(const AbstractObject& other) = delete; - AbstractObject& operator=(AbstractObject&& other) = delete; - #endif +@section Object-explicit-specializations Explicit template specializations - public: - static const std::uint8_t Dimensions = dimensions; /**< @brief %Object dimension count */ +The following specialization are explicitly compiled into SceneGraph library. +For other specializations you have to use Object.hpp implementation file to +avoid linker errors. See @ref compilation-speedup-hpp for more information. - /** @brief %Object type for given dimension count */ - typedef typename Implementation::ObjectDimensionTraits::ObjectType ObjectType; + - @ref MatrixTransformation2D "Object>" + - @ref MatrixTransformation3D "Object>" - /** @brief %Camera type for given dimension count */ - typedef typename Implementation::ObjectDimensionTraits::CameraType CameraType; +@see Scene, AbstractFeature, AbstractTransformation +*/ +template class Object: public AbstractObject, public Transformation + #ifndef DOXYGEN_GENERATING_OUTPUT + , private Corrade::Containers::LinkedList>, private Corrade::Containers::LinkedListItem, Object> + #endif +{ + friend class Corrade::Containers::LinkedList>; + friend class Corrade::Containers::LinkedListItem, Object>; - /** @brief %Scene type for given dimension count */ - typedef typename Implementation::ObjectDimensionTraits::SceneType SceneType; + #ifndef DOXYGEN_GENERATING_OUTPUT + Object(const Object& other) = delete; + Object(Object&& other) = delete; + Object& operator=(const Object& other) = delete; + Object& operator=(Object&& other) = delete; + #endif + public: /** * @brief Constructor * @param parent Parent object - * - * Sets all transformations to their default values. */ - inline AbstractObject(ObjectType* parent = nullptr): dirty(true) { + inline Object(Object* parent = nullptr): counter(0xFFFFu), flags(Flag::Dirty) { setParent(parent); } @@ -108,9 +92,13 @@ template class SCENEGRAPH_EXPORT AbstractObject: public * Removes itself from parent's children list and destroys all own * children. */ - virtual ~AbstractObject() = 0; + inline virtual ~Object() {} - /** @{ @name Scene hierarchy */ + /** + * @{ @name Scene hierarchy + * + * See @ref scenegraph-hierarchy for more information. + */ /** @brief Whether this object is scene */ virtual inline bool isScene() const { return false; } @@ -119,296 +107,116 @@ template class SCENEGRAPH_EXPORT AbstractObject: public * @brief %Scene * @return %Scene or `nullptr`, if the object is not part of any scene. */ - SceneType* scene(); + Scene* scene(); + + /** @overload */ + const Scene* scene() const; /** @brief Parent object or `nullptr`, if this is root object */ - inline ObjectType* parent() { return Corrade::Containers::LinkedListItem::list(); } + inline Object* parent() { + return Corrade::Containers::LinkedListItem, Object>::list(); + } + + /** @overload */ + inline const Object* parent() const { + return Corrade::Containers::LinkedListItem, Object>::list(); + } /** @brief Previous sibling object or `nullptr`, if this is first object */ - inline ObjectType* previousSibling() { return Corrade::Containers::LinkedListItem::previous(); } + inline Object* previousSibling() { + return Corrade::Containers::LinkedListItem, Object>::previous(); + } + + /** @overload */ + inline const Object* previousSibling() const { + return Corrade::Containers::LinkedListItem, Object>::previous(); + } /** @brief Next sibling object or `nullptr`, if this is last object */ - inline ObjectType* nextSibling() { return Corrade::Containers::LinkedListItem::next(); } + inline Object* nextSibling() { + return Corrade::Containers::LinkedListItem, Object>::next(); + } + + /** @overload */ + inline const Object* nextSibling() const { + return Corrade::Containers::LinkedListItem, Object>::next(); + } /** @brief Whether this object has children */ - inline bool hasChildren() const { return !Corrade::Containers::LinkedList::isEmpty(); } + inline bool hasChildren() const { + return !Corrade::Containers::LinkedList>::isEmpty(); + } /** @brief First child object or `nullptr`, if this object has no children */ - inline ObjectType* firstChild() { return Corrade::Containers::LinkedList::first(); } - - /** @brief Last child object or `nullptr`, if this object has no children */ - inline ObjectType* lastChild() { return Corrade::Containers::LinkedList::last(); } - - /** - * @brief Set parent object - * @return Pointer to self (for method chaining) - */ - ObjectType* setParent(ObjectType* parent); - - /*@}*/ - - /** @{ @name Object transformation - * - * All transformations (except absoluteTransformation()) are relative - * to parent. - */ - - /** @brief Transformation type */ - enum class Transformation: char { - /** Global transformation, applied after all other transformations. */ - Global = 0x00, - - /** Local transformation, applied before all other transformations. */ - Local = 0x01 - }; - - /** @brief Transformation */ - inline typename DimensionTraits::MatrixType transformation() const { - return _transformation; + inline Object* firstChild() { + return Corrade::Containers::LinkedList>::first(); } - /** - * @brief Absolute transformation - * - * Returns absolute transformation matrix relative to the camera or - * root object, if no camera is specified. If the camera is specified, - * it should be part of the same scene as object. - * - * Note that the absolute transformation is computed from all parent - * objects every time it is asked, unless this function is - * reimplemented in a different way. - */ - virtual typename DimensionTraits::MatrixType absoluteTransformation(CameraType* camera = nullptr); - - /** - * @brief Set transformation - * @return Pointer to self (for method chaining) - */ - ObjectType* setTransformation(const typename DimensionTraits::MatrixType& transformation); - - /** - * @brief Multiply transformation - * @param transformation Transformation - * @param type Transformation type - * @return Pointer to self (for method chaining) - */ - inline ObjectType* multiplyTransformation(const typename DimensionTraits::MatrixType& transformation, Transformation type = Transformation::Global) { - setTransformation(type == Transformation::Global ? - transformation*_transformation : _transformation*transformation); - return static_cast(this); + /** @overload */ + inline const Object* firstChild() const { + return Corrade::Containers::LinkedList>::first(); } - /*@}*/ - - /** - * @brief Draw object - * @param transformationMatrix %Matrix specifying object - * transformation relative to the scene. - * @param camera Active camera (containing - * projection matrix) - * - * Default implementation does nothing. - */ - virtual void draw(const typename DimensionTraits::MatrixType& transformationMatrix, CameraType* camera); - - /** @{ @name Caching helpers - * - * If the object (absolute) transformation or anything depending on it - * is used many times when drawing (such as e.g. position of light - * object), it's good to cache these values, so they don't have to be - * recalculated again on every request. - * - * If setDirty() is called on an object (or the object is transformed), - * it and all its children are marked as dirty. If any object is - * already dirty, it and all its children are skipped, because they - * are already dirty too. - * - * If setClean() is called on an object, it and all its parents are - * cleaned. If any object is already clean, it and all its parents are - * skipped, because they are already clean too. - * - * These functions are used to manage dirty status of the object. If - * the object doesn't cache anything, it's no need to bother about - * them, but if does, clean() should be reimplemented and used to - * regenerate the cache. - */ - - /** - * @brief Whether the object is dirty - * @return True, if transformation of the object, any parent or camera - * has changed since last asking, false otherwise. - */ - inline bool isDirty() const { return dirty; } - - /** - * @brief Set object and all its children as dirty - * - * Recursively calls setDirty() on every child. If the object is - * already marked as dirty, the function does nothing. It is usually - * not needed to reimplement this function, only if you for example - * need to reset some state on object which is not child of this. All - * computations should be done in setClean(). - * - * Reimplementations should call this function at the end, i.e.: - * @code - * void setDirty() { - * // ... - * - * Object::setDirty(); - * } - * @endcode - */ - virtual void setDirty(); + /** @brief Last child object or `nullptr`, if this object has no children */ + inline Object* lastChild() { + return Corrade::Containers::LinkedList>::last(); + } - /** - * @brief Set object and all its parents as clean - * - * Recursively calls clean() on every parent which is not already - * clean. - */ - void setClean(); + /** @overload */ + inline const Object* lastChild() const { + return Corrade::Containers::LinkedList>::last(); + } - protected: /** - * @brief Clean the object - * - * When reimplementing, use absolute transformation passed as - * parameter instead of absoluteTransformation(), which is not - * efficient. The reimplementation should call this function at the - * beginning, i.e.: - * @code - * void clean(const Matrix4& absoluteTransformation) { - * Object::clean(absoluteTransformation); - * - * // ... - * } - * @endcode + * @brief Set parent object + * @return Pointer to self (for method chaining) */ - virtual void clean(const typename DimensionTraits::MatrixType& absoluteTransformation); + Object* setParent(Object* parent); /*@}*/ - private: - /* Hide base class members, as they are aliased to more meaningful names */ - using Corrade::Containers::LinkedList::first; - using Corrade::Containers::LinkedList::last; - using Corrade::Containers::LinkedList::isEmpty; - using Corrade::Containers::LinkedList::insert; - using Corrade::Containers::LinkedList::cut; - using Corrade::Containers::LinkedList::move; - using Corrade::Containers::LinkedList::erase; - using Corrade::Containers::LinkedList::clear; - using Corrade::Containers::LinkedListItem::list; - using Corrade::Containers::LinkedListItem::previous; - using Corrade::Containers::LinkedListItem::next; - - typename DimensionTraits::MatrixType _transformation; - bool dirty; -}; - -template inline AbstractObject::~AbstractObject() {} - -/* Implementations for inline functions with unused parameters */ -template inline void AbstractObject::draw(const typename DimensionTraits::MatrixType&, CameraType*) {} -template inline void AbstractObject::clean(const typename DimensionTraits::MatrixType&) { dirty = false; } - -/** -@brief Two-dimensional object - -@see Object3D -*/ -class SCENEGRAPH_EXPORT Object2D: public AbstractObject<2> { - public: - /** @copydoc AbstractObject::AbstractObject() */ - inline Object2D(Object2D* parent = nullptr): AbstractObject<2>(parent) {} + /** @{ @name Object transformation */ - /** - * @brief Translate object - * @return Pointer to self (for method chaining) - * - * Same as calling multiplyTransformation() with Matrix3::translation(). - */ - inline Object2D* translate(const Vector2& vec, Transformation type = Transformation::Global) { - multiplyTransformation(Matrix3::translation(vec), type); - return this; + inline typename DimensionTraits::MatrixType absoluteTransformationMatrix() const override { + return Transformation::toMatrix(absoluteTransformation()); } /** - * @brief Scale object - * @return Pointer to self (for method chaining) + * @brief Transformation relative to root object * - * Same as calling multiplyTransformation() with Matrix3::scaling(). + * @see absoluteTransformationMatrix() */ - inline Object2D* scale(const Vector2& vec, Transformation type = Transformation::Global) { - multiplyTransformation(Matrix3::scaling(vec), type); - return this; - } + typename Transformation::DataType absoluteTransformation() const; /** - * @brief Rotate object - * @return Pointer to self (for method chaining) + * @brief Transformations of given group of objects relative to this object * - * Same as calling multiplyTransformation() with Matrix3::rotation(). + * All transformations can be premultiplied with @p initialTransformation, + * if specified. + * @see AbstractObject::transformationMatrices() */ - inline Object2D* rotate(GLfloat angle, Transformation type = Transformation::Global) { - multiplyTransformation(Matrix3::rotation(angle), type); - return this; - } + /* `objects` passed by copy intentionally (to allow move from + transformationMatrices() and avoid copy in the function itself) */ + std::vector transformations(std::vector*> objects, const typename Transformation::DataType& initialTransformation = typename Transformation::DataType()) const; - /** - * @brief Move object in stacking order - * @param under Sibling object under which to move or `nullptr`, - * if you want to move it above all. - * @return Pointer to self (for method chaining) - */ - inline Object2D* move(Object2D* under) { - parent()->Corrade::Containers::LinkedList::move(this, under); - return this; - } -}; + /*@}*/ -/** -@brief Three-dimensional object + inline bool isDirty() const override { return !!(flags & Flag::Dirty); } + void setDirty() override; + void setClean() override; -@see Object2D -*/ -class SCENEGRAPH_EXPORT Object3D: public AbstractObject<3> { - public: - /** @copydoc AbstractObject::AbstractObject() */ - inline Object3D(Object3D* parent = nullptr): AbstractObject<3>(parent) {} + private: + Object* sceneObject() override; + const Object* sceneObject() const override; - /** - * @brief Translate object - * @return Pointer to self (for method chaining) - * - * Same as calling multiplyTransformation() with Matrix4::translation(). - */ - inline Object3D* translate(const Vector3& vec, Transformation type = Transformation::Global) { - multiplyTransformation(Matrix4::translation(vec), type); - return this; - } + std::vector::MatrixType> transformationMatrices(const std::vector*>& objects, const typename DimensionTraits::MatrixType& initialTransformationMatrix = typename DimensionTraits::MatrixType()) const override; - /** - * @brief Scale object - * @return Pointer to self (for method chaining) - * - * Same as calling multiplyTransformation() with Matrix4::scaling(). - */ - inline Object3D* scale(const Vector3& vec, Transformation type = Transformation::Global) { - multiplyTransformation(Matrix4::scaling(vec), type); - return this; - } + typename Transformation::DataType computeJointTransformation(const std::vector*>& jointObjects, std::vector& jointTransformations, const std::size_t joint, const typename Transformation::DataType& initialTransformation) const; - /** - * @brief Rotate object - * @return Pointer to self (for method chaining) - * - * Same as calling multiplyTransformation() with Matrix4::rotation(). - */ - inline Object3D* rotate(GLfloat angle, const Vector3& vec, Transformation type = Transformation::Global) { - multiplyTransformation(Matrix4::rotation(angle, vec), type); - return this; - } + typedef Implementation::ObjectFlag Flag; + typedef Implementation::ObjectFlags Flags; + std::uint16_t counter; + Flags flags; }; }} diff --git a/src/SceneGraph/Object.hpp b/src/SceneGraph/Object.hpp new file mode 100644 index 000000000..dec18a843 --- /dev/null +++ b/src/SceneGraph/Object.hpp @@ -0,0 +1,291 @@ +#ifndef Magnum_SceneGraph_Object_hpp +#define Magnum_SceneGraph_Object_hpp +/* + Copyright © 2010, 2011, 2012 Vladimír Vondruš + + This file is part of Magnum. + + Magnum is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 3 + only, as published by the Free Software Foundation. + + Magnum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License version 3 for more details. +*/ + +/** @file + * @brief @ref compilation-speedup-hpp "Template implementation" for Object.h + */ + +#include "Object.h" + +#include + +#include "Scene.h" + +namespace Magnum { namespace SceneGraph { + +template Scene* Object::scene() { + return static_cast*>(sceneObject()); +} + +template const Scene* Object::scene() const { + return static_cast*>(sceneObject()); +} + +template Object* Object::sceneObject() { + Object* p(this); + while(p && !p->isScene()) p = p->parent(); + return p; +} + +template const Object* Object::sceneObject() const { + const Object* p(this); + while(p && !p->isScene()) p = p->parent(); + return p; +} + +template Object* Object::setParent(Object* parent) { + /* Skip if parent is already parent or this is scene (which cannot have parent) */ + /** @todo Assert for setting parent to scene */ + if(this->parent() == parent || isScene()) return this; + + /* Object cannot be parented to its child */ + Object* p = parent; + while(p) { + /** @todo Assert for this */ + if(p == this) return this; + p = p->parent(); + } + + /* Remove the object from old parent children list */ + if(this->parent()) this->parent()->Corrade::Containers::LinkedList>::cut(this); + + /* Add the object to list of new parent */ + if(parent) parent->Corrade::Containers::LinkedList>::insert(this); + + setDirty(); + return this; +} + +template typename Transformation::DataType Object::absoluteTransformation() const { + if(!parent()) return Transformation::transformation(); + return Transformation::compose(parent()->absoluteTransformation(), Transformation::transformation()); +} + +template void Object::setDirty() { + /* The transformation of this object (and all children) is already dirty, + nothing to do */ + if(flags & Flag::Dirty) return; + + Object* self = static_cast*>(this); + + /* Make all features dirty */ + for(AbstractFeature* i = self->firstFeature(); i; i = i->nextFeature()) + i->markDirty(); + + /* Make all children dirty */ + for(Object* i = self->firstChild(); i; i = i->nextSibling()) + i->setDirty(); + + /* Mark object as dirty */ + flags |= Flag::Dirty; +} + +template void Object::setClean() { + /* The object (and all its parents) are already clean, nothing to do */ + if(!(flags & Flag::Dirty)) return; + + /* Collect all parents, compute base transformation */ + std::stack*> objects; + typename Transformation::DataType absoluteTransformation; + Object* p = static_cast*>(this); + for(;;) { + objects.push(p); + + p = p->parent(); + + /* On root object, base transformation is identity */ + if(!p) break; + + /* Parent object is clean, base transformation is its absolute + transformation */ + if(!p->isDirty()) { + absoluteTransformation = p->absoluteTransformation(); + break; + } + } + + /* Clean features on every collected object, going down from root object */ + while(!objects.empty()) { + Object* o = objects.top(); + objects.pop(); + + /* Compose transformations */ + absoluteTransformation = Transformation::compose(absoluteTransformation, o->transformation()); + + /* "Lazy storage" for transformation matrix and inverted transformation matrix */ + typedef typename AbstractFeature::CachedTransformation CachedTransformation; + typename AbstractFeature::CachedTransformations cached; + typename DimensionTraits::MatrixType + matrix, invertedMatrix; + + /* Clean all features */ + for(AbstractFeature* i = o->firstFeature(); i; i = i->nextFeature()) { + /* Cached absolute transformation, compute it if it wasn't + computed already */ + if(i->cachedTransformations() & CachedTransformation::Absolute) { + if(!(cached & CachedTransformation::Absolute)) { + cached |= CachedTransformation::Absolute; + matrix = Transformation::toMatrix(absoluteTransformation); + } + + i->clean(matrix); + } + + /* Cached inverse absolute transformation, compute it if it wasn't + computed already */ + if(i->cachedTransformations() & CachedTransformation::InvertedAbsolute) { + if(!(cached & CachedTransformation::InvertedAbsolute)) { + cached |= CachedTransformation::InvertedAbsolute; + invertedMatrix = Transformation::toMatrix(Transformation::inverted(absoluteTransformation)); + } + + i->cleanInverted(invertedMatrix); + } + } + + /* Mark object as clean */ + o->flags &= ~Flag::Dirty; + } +} + +template std::vector::MatrixType> Object::transformationMatrices(const std::vector*>& objects, const typename DimensionTraits::MatrixType& initialTransformationMatrix) const { + std::vector*> castObjects(objects.size()); + for(std::size_t i = 0; i != objects.size(); ++i) + /** @todo Ensure this doesn't crash, somehow */ + castObjects[i] = static_cast*>(objects[i]); + + std::vector transformations = this->transformations(std::move(castObjects), Transformation::fromMatrix(initialTransformationMatrix)); + std::vector::MatrixType> transformationMatrices(transformations.size()); + for(std::size_t i = 0; i != objects.size(); ++i) + transformationMatrices[i] = Transformation::toMatrix(transformations[i]); + + return transformationMatrices; +} + +template std::vector Object::transformations(std::vector*> objects, const typename Transformation::DataType& initialTransformation) const { + /* Remember object count for later */ + std::size_t objectCount = objects.size(); + + /* Create initial list of joints from original objects */ + std::vector*> jointObjects(objects.size()); + for(std::size_t i = 0; i != jointObjects.size(); ++i) { + jointObjects[i] = static_cast*>(objects[i]); + CORRADE_INTERNAL_ASSERT(jointObjects[i]->counter == 0xFFFFu); + jointObjects[i]->counter = i; + jointObjects[i]->flags |= Flag::Joint; + } + + /* Scene object */ + const Scene* scene = this->scene(); + + /* Nearest common ancestor not yet implemented - assert this is done on scene */ + CORRADE_ASSERT(scene == this, "SceneGraph::Object::transformationMatrices(): currently implemented only for Scene", {}); + + /* Mark all objects up the hierarchy as visited */ + auto it = objects.begin(); + while(!objects.empty()) { + /* Mark the object as visited */ + CORRADE_INTERNAL_ASSERT(!((*it)->flags & Flag::Visited)); + (*it)->flags |= Flag::Visited; + + Object* parent = (*it)->parent(); + + /* If this is root object, remove from list */ + if(!parent) { + CORRADE_ASSERT(*it == scene, "SceneGraph::Object::transformations(): the objects are not part of the same tree", {}); + it = objects.erase(it); + + /* Parent is an joint or already visited - remove current from list */ + } else if(parent->flags & (Flag::Visited|Flag::Joint)) { + it = objects.erase(it); + + /* If not already marked as joint, mark it as such and add it to + list of joint objects */ + if(!(parent->flags & Flag::Joint)) { + CORRADE_INTERNAL_ASSERT(parent->counter == 0xFFFFu); + parent->counter = jointObjects.size(); + parent->flags |= Flag::Joint; + jointObjects.push_back(parent); + } + + /* Else go up the hierarchy */ + } else *it = parent; + + /* Cycle if reached end */ + if(it == objects.end()) it = objects.begin(); + } + + CORRADE_ASSERT(objects.size() < 0xFFFFu, "SceneGraph::Object::transformations(): too large scene", {}); + + /* Array of absolute transformations in joints */ + std::vector jointTransformations(jointObjects.size()); + + /* Compute transformations for all joints */ + for(std::size_t i = 0; i != jointTransformations.size(); ++i) + computeJointTransformation(jointObjects, jointTransformations, i, initialTransformation); + + /* All visited marks are now cleaned, clean joint marks and counters */ + for(auto i: jointObjects) { + CORRADE_INTERNAL_ASSERT(i->flags & Flag::Joint); + i->flags &= ~Flag::Joint; + i->counter = 0xFFFFu; + } + + /* Shrink the array to contain only transformations of requested objects and return */ + jointTransformations.resize(objectCount); + return jointTransformations; +} + +template typename Transformation::DataType Object::computeJointTransformation(const std::vector*>& jointObjects, std::vector& jointTransformations, const std::size_t joint, const typename Transformation::DataType& initialTransformation) const { + Object* o = jointObjects[joint]; + + /* Transformation already computed ("unvisited" by this function before), done */ + if(!(o->flags & Flag::Visited)) return jointTransformations[joint]; + + /* Initialize transformation */ + jointTransformations[joint] = o->transformation(); + + /* Go up until next joint or root */ + for(;;) { + /* Clean visited mark */ + CORRADE_INTERNAL_ASSERT(o->flags & Flag::Visited); + o->flags &= ~Flag::Visited; + + Object* parent = o->parent(); + + /* Root object, compose transformation with initial, done */ + if(!parent) { + CORRADE_INTERNAL_ASSERT(o->isScene()); + return (jointTransformations[joint] = + Transformation::compose(initialTransformation, jointTransformations[joint])); + + /* Joint object, compose transformation with the joint, done */ + } else if(parent->flags & Flag::Joint) { + return (jointTransformations[joint] = + Transformation::compose(computeJointTransformation(jointObjects, jointTransformations, parent->counter, initialTransformation), jointTransformations[joint])); + + /* Else compose transformation with parent, go up the hierarchy */ + } else { + jointTransformations[joint] = Transformation::compose(parent->transformation(), jointTransformations[joint]); + o = parent; + } + } +} + +}} + +#endif diff --git a/src/SceneGraph/Scene.h b/src/SceneGraph/Scene.h index 9966bca21..eecc231f0 100644 --- a/src/SceneGraph/Scene.h +++ b/src/SceneGraph/Scene.h @@ -16,7 +16,7 @@ */ /** @file - * @brief Class Magnum::SceneGraph::Scene, typedef Magnum::SceneGraph::Scene2D, Magnum::SceneGraph::Scene3D + * @brief Class Magnum::SceneGraph::Scene */ #include "Object.h" @@ -26,33 +26,14 @@ namespace Magnum { namespace SceneGraph { /** @brief %Scene -@see Scene2D, Scene3D +Basically Object which cannot have parent or non-default transformation. +See @ref scenegraph for introduction. */ -template class SCENEGRAPH_EXPORT Scene: public AbstractObject::ObjectType { +template class Scene: public Object { public: - /** @copydoc AbstractObject::isScene() */ inline bool isScene() const { return true; } - - /** @todo Some deleted functions belong only to Scene2D, some only to Scene3D - what to do? */ - #ifndef DOXYGEN_GENERATING_OUTPUT - void setParent(typename AbstractObject::ObjectType* parent) = delete; - void setTransformation(const typename DimensionTraits::MatrixType& transformation) = delete; - void multiplyTransformation(const typename DimensionTraits::MatrixType& transformation, typename AbstractObject::Transformation type = (DimensionTraits::Transformation::Global)) = delete; - void translate(const typename DimensionTraits::VectorType& vec, typename AbstractObject::Transformation type = AbstractObject::Transformation::Global) = delete; - void scale(const typename DimensionTraits::VectorType& vec, typename AbstractObject::Transformation type = AbstractObject::Transformation::Global) = delete; - void rotate(GLfloat angle, const typename DimensionTraits::VectorType& vec, typename AbstractObject::Transformation type = AbstractObject::Transformation::Global) = delete; - #endif - - private: - inline void draw(const typename DimensionTraits::MatrixType&, typename AbstractObject::CameraType*) {} }; -/** @brief Two-dimensional scene */ -typedef Scene<2> Scene2D; - -/** @brief Three-dimensional scene */ -typedef Scene<3> Scene3D; - }} #endif diff --git a/src/SceneGraph/Test/CameraTest.cpp b/src/SceneGraph/Test/CameraTest.cpp index f7a111cf1..36f901ed0 100644 --- a/src/SceneGraph/Test/CameraTest.cpp +++ b/src/SceneGraph/Test/CameraTest.cpp @@ -17,11 +17,21 @@ #include "Math/Constants.h" #include "SceneGraph/Camera.h" +#include "SceneGraph/Camera.hpp" /* only for aspectRatioFix(), so it doesn't have to be exported */ +#include "SceneGraph/Drawable.h" +#include "SceneGraph/MatrixTransformation2D.h" +#include "SceneGraph/MatrixTransformation3D.h" CORRADE_TEST_MAIN(Magnum::SceneGraph::Test::CameraTest) namespace Magnum { namespace SceneGraph { namespace Test { +typedef SceneGraph::Object> Object2D; +typedef SceneGraph::Object> Object3D; +typedef SceneGraph::Scene> Scene3D; +typedef SceneGraph::Camera2D<> Camera2D; +typedef SceneGraph::Camera3D<> Camera3D; + CameraTest::CameraTest() { addTests(&CameraTest::fixAspectRatio, &CameraTest::defaultProjection2D, @@ -29,7 +39,8 @@ CameraTest::CameraTest() { &CameraTest::projection2D, &CameraTest::orthographic, &CameraTest::perspective, - &CameraTest::projectionSizeViewport); + &CameraTest::projectionSizeViewport, + &CameraTest::draw); } void CameraTest::fixAspectRatio() { @@ -41,54 +52,57 @@ void CameraTest::fixAspectRatio() { Vector2 projectionScaleZeroX(0.0f, 0.5f); Math::Vector2 sizeZeroY(400, 0); Math::Vector2 sizeZeroX(0, 300); - CORRADE_COMPARE(Implementation::aspectRatioFix(Implementation::AspectRatioPolicy::Clip, projectionScaleZeroX, size), Matrix4()); - CORRADE_COMPARE(Implementation::aspectRatioFix(Implementation::AspectRatioPolicy::Clip, projectionScaleZeroY, size), Matrix4()); - CORRADE_COMPARE(Implementation::aspectRatioFix(Implementation::AspectRatioPolicy::Clip, projectionScale, sizeZeroY), Matrix4()); - CORRADE_COMPARE(Implementation::aspectRatioFix(Implementation::AspectRatioPolicy::Extend, projectionScale, sizeZeroX), Matrix4()); + CORRADE_COMPARE((Implementation::aspectRatioFix<3, GLfloat>(Implementation::AspectRatioPolicy::Clip, projectionScaleZeroX, size)), Matrix4()); + CORRADE_COMPARE((Implementation::aspectRatioFix<3, GLfloat>(Implementation::AspectRatioPolicy::Clip, projectionScaleZeroY, size)), Matrix4()); + CORRADE_COMPARE((Implementation::aspectRatioFix<3, GLfloat>(Implementation::AspectRatioPolicy::Clip, projectionScale, sizeZeroY)), Matrix4()); + CORRADE_COMPARE((Implementation::aspectRatioFix<3, GLfloat>(Implementation::AspectRatioPolicy::Extend, projectionScale, sizeZeroX)), Matrix4()); /* Not preserved */ - CORRADE_COMPARE(Implementation::aspectRatioFix(Implementation::AspectRatioPolicy::NotPreserved, projectionScale, size), Matrix4()); + CORRADE_COMPARE((Implementation::aspectRatioFix<3, GLfloat>(Implementation::AspectRatioPolicy::NotPreserved, projectionScale, size)), Matrix4()); /* Clip */ Matrix4 expectedClip(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 4.0f/3.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f); - CORRADE_COMPARE(Implementation::aspectRatioFix(Implementation::AspectRatioPolicy::Clip, Vector2(0.5f), size), expectedClip); + CORRADE_COMPARE((Implementation::aspectRatioFix<3, GLfloat>(Implementation::AspectRatioPolicy::Clip, Vector2(0.5f), size)), expectedClip); Matrix4 expectedClipRectangle(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 2.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f); - CORRADE_COMPARE(Implementation::aspectRatioFix(Implementation::AspectRatioPolicy::Clip, projectionScale, size), expectedClipRectangle); + CORRADE_COMPARE((Implementation::aspectRatioFix<3, GLfloat>(Implementation::AspectRatioPolicy::Clip, projectionScale, size)), expectedClipRectangle); /* Extend */ Matrix4 expectedExtend(3.0f/4.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f); - CORRADE_COMPARE(Implementation::aspectRatioFix(Implementation::AspectRatioPolicy::Extend, Vector2(0.5f), size), expectedExtend); + CORRADE_COMPARE((Implementation::aspectRatioFix<3, GLfloat>(Implementation::AspectRatioPolicy::Extend, Vector2(0.5f), size)), expectedExtend); Matrix4 expectedExtendRectangle(0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f); - CORRADE_COMPARE(Implementation::aspectRatioFix(Implementation::AspectRatioPolicy::Extend, projectionScale, size), expectedExtendRectangle); + CORRADE_COMPARE((Implementation::aspectRatioFix<3, GLfloat>(Implementation::AspectRatioPolicy::Extend, projectionScale, size)), expectedExtendRectangle); } void CameraTest::defaultProjection2D() { - Camera2D camera; + Object2D o; + Camera2D camera(&o); CORRADE_COMPARE(camera.projectionMatrix(), Matrix3()); CORRADE_COMPARE(camera.projectionSize(), Vector2(2.0f)); } void CameraTest::defaultProjection3D() { - Camera3D camera; + Object3D o; + Camera3D camera(&o); CORRADE_COMPARE(camera.projectionMatrix(), Matrix4()); CORRADE_COMPARE(camera.projectionSize(), Vector2(2.0f)); } void CameraTest::projection2D() { Vector2 projectionSize(4.0f, 3.0f); - Camera2D camera; + Object2D o; + Camera2D camera(&o); camera.setProjection(projectionSize); Matrix3 a(2.0f/4.0f, 0.0f, 0.0f, @@ -101,7 +115,8 @@ void CameraTest::projection2D() { void CameraTest::orthographic() { Vector2 projectionSize(5); - Camera3D camera; + Object3D o; + Camera3D camera(&o); camera.setOrthographic(projectionSize, 1, 9); Matrix4 a(0.4f, 0.0f, 0.0f, 0.0f, @@ -125,7 +140,8 @@ void CameraTest::orthographic() { } void CameraTest::perspective() { - Camera3D camera; + Object3D o; + Camera3D camera(&o); camera.setPerspective(deg(27.0f), 32.0f, 100); Matrix4 a(4.1652994f, 0.0f, 0.0f, 0.0f, @@ -138,7 +154,8 @@ void CameraTest::perspective() { } void CameraTest::projectionSizeViewport() { - Camera3D camera; + Object3D o; + Camera3D camera(&o); camera.setViewport({200, 300}); CORRADE_COMPARE(camera.projectionSize(), Vector2(2.0f, 2.0f)); @@ -149,4 +166,44 @@ void CameraTest::projectionSizeViewport() { CORRADE_COMPARE(camera.projectionSize(), Vector2(4.0f/3.0f, 2.0f)); } +void CameraTest::draw() { + class Drawable: public SceneGraph::Drawable<3> { + public: + inline Drawable(AbstractObject<3>* object, DrawableGroup<3>* group, Matrix4& result): SceneGraph::Drawable<3>(object, group), result(result) {} + + protected: + void draw(const Matrix4& transformationMatrix, AbstractCamera<3>*) { + result = transformationMatrix; + } + + private: + Matrix4& result; + }; + + DrawableGroup<3> group; + Scene3D scene; + + Object3D first(&scene); + Matrix4 firstTransformation; + first.scale(Vector3(5.0f)); + new Drawable(&first, &group, firstTransformation); + + Object3D second(&scene); + Matrix4 secondTransformation; + second.translate(Vector3::yAxis(3.0f)); + new Drawable(&second, &group, secondTransformation); + + Object3D third(&second); + Matrix4 thirdTransformation; + third.translate(Vector3::zAxis(-1.5f)); + new Drawable(&third, &group, thirdTransformation); + + Camera3D camera(&third); + camera.draw(group); + + CORRADE_COMPARE(firstTransformation, Matrix4::translation({0.0f, -3.0f, 1.5f})*Matrix4::scaling(Vector3(5.0f))); + CORRADE_COMPARE(secondTransformation, Matrix4::translation(Vector3::zAxis(1.5f))); + CORRADE_COMPARE(thirdTransformation, Matrix4()); +} + }}} diff --git a/src/SceneGraph/Test/CameraTest.h b/src/SceneGraph/Test/CameraTest.h index 2ada59d3b..1a9ab9198 100644 --- a/src/SceneGraph/Test/CameraTest.h +++ b/src/SceneGraph/Test/CameraTest.h @@ -30,6 +30,7 @@ class CameraTest: public Corrade::TestSuite::Tester { void orthographic(); void perspective(); void projectionSizeViewport(); + void draw(); }; }}} diff --git a/src/SceneGraph/Test/ObjectTest.cpp b/src/SceneGraph/Test/ObjectTest.cpp index c838704f8..bba87ffe8 100644 --- a/src/SceneGraph/Test/ObjectTest.cpp +++ b/src/SceneGraph/Test/ObjectTest.cpp @@ -18,7 +18,7 @@ #include #include "Math/Constants.h" -#include "SceneGraph/Camera.h" +#include "SceneGraph/MatrixTransformation3D.h" #include "SceneGraph/Scene.h" using namespace std; @@ -27,13 +27,15 @@ CORRADE_TEST_MAIN(Magnum::SceneGraph::Test::ObjectTest) namespace Magnum { namespace SceneGraph { namespace Test { +typedef SceneGraph::Object> Object3D; +typedef SceneGraph::Scene> Scene3D; + ObjectTest::ObjectTest() { addTests(&ObjectTest::parenting, - &ObjectTest::transformation, - &ObjectTest::absoluteTransformationWrongCamera, - &ObjectTest::absoluteTransformation, &ObjectTest::scene, - &ObjectTest::dirty); + &ObjectTest::absoluteTransformation, + &ObjectTest::transformations, + &ObjectTest::caching); } void ObjectTest::parenting() { @@ -66,96 +68,181 @@ void ObjectTest::parenting() { CORRADE_VERIFY(!childOne->hasChildren()); } -void ObjectTest::transformation() { - Object3D o; - Object3D o2; - - o.setTransformation(Matrix4::translation(Vector3::xAxis(1.0f))); - o2.translate(Vector3::xAxis(1.0f)); - o.multiplyTransformation(Matrix4::rotation(deg(35.0f), Vector3::zAxis())); - o2.rotate(deg(35.0f), Vector3::zAxis()); - - CORRADE_COMPARE(o.transformation(), Matrix4::rotation(deg(35.0f), Vector3::zAxis())* - Matrix4::translation(Vector3::xAxis(1.0f))); - CORRADE_COMPARE(o2.transformation(), o.transformation()); - - o.multiplyTransformation(Matrix4::scaling(Vector3(2.0f)), Object3D::Transformation::Local); - o2.scale(Vector3(2.0f), Object3D::Transformation::Local); - CORRADE_COMPARE(o.transformation(), Matrix4::rotation(deg(35.0f), Vector3::zAxis())* - Matrix4::translation(Vector3::xAxis(1.0f))* - Matrix4::scaling(Vector3(2.0f))); - CORRADE_COMPARE(o2.transformation(), o.transformation()); -} +void ObjectTest::scene() { + Scene3D scene; + CORRADE_VERIFY(scene.scene() == &scene); -void ObjectTest::absoluteTransformationWrongCamera() { - stringstream ss; - Error::setOutput(&ss); + Object3D* childOne = new Object3D(&scene); + Object3D* childTwo = new Object3D(childOne); - Scene3D s; - Object3D o(&s); - o.translate(Vector3::yAxis()); - Camera3D c; - CORRADE_COMPARE(o.absoluteTransformation(&c), Matrix4::translation(Vector3::yAxis())); - CORRADE_COMPARE(ss.str(), "Object::absoluteTransformation(): the camera is not part of the same scene as object!\n"); - - ss.str(""); - Object3D o2; - o2.translate(Vector3::xAxis()); - CORRADE_COMPARE(o2.absoluteTransformation(&c), Matrix4::translation(Vector3::xAxis())); - CORRADE_COMPARE(ss.str(), "Object::absoluteTransformation(): the object is not part of camera scene!\n"); + Object3D orphan; + Object3D* childOfOrphan = new Object3D(&orphan); + + CORRADE_VERIFY(childTwo->scene() == &scene); + CORRADE_VERIFY(childOfOrphan->scene() == nullptr); } void ObjectTest::absoluteTransformation() { Scene3D s; - Camera3D c(&s); - c.translate(Vector3::zAxis(2.0f)); - CORRADE_COMPARE(s.absoluteTransformation(), Matrix4()); - CORRADE_COMPARE(c.absoluteTransformation(&c), Matrix4()); + /* Proper transformation composition */ Object3D o(&s); - o.scale(Vector3(2.0f)); + o.translate(Vector3::xAxis(2.0f)); Object3D o2(&o); - o.rotate(deg(90.0f), Vector3::yAxis()); + o2.rotateY(deg(90.0f)); CORRADE_COMPARE(o2.absoluteTransformation(), - Matrix4::scaling(Vector3(2.0f))*Matrix4::rotation(deg(90.0f), Vector3::yAxis())); - CORRADE_COMPARE(o2.absoluteTransformation(&c), - (Matrix4::translation(Vector3::zAxis(2.0f)).inverted())*Matrix4::scaling(Vector3(2.0f))*Matrix4::rotation(deg(90.0f), Vector3::yAxis())); + Matrix4::translation(Vector3::xAxis(2.0f))*Matrix4::rotationY(deg(90.0f))); + CORRADE_COMPARE(o2.absoluteTransformation(), o2.absoluteTransformationMatrix()); + /* Transformation of root object */ Object3D o3; o3.translate({1.0f, 2.0f, 3.0f}); CORRADE_COMPARE(o3.absoluteTransformation(), Matrix4::translation({1.0f, 2.0f, 3.0f})); } -void ObjectTest::scene() { - Scene3D scene; - - Object3D* childOne = new Object3D(&scene); - Object3D* childTwo = new Object3D(childOne); +void ObjectTest::transformations() { + Scene3D s; + Matrix4 initial = Matrix4::rotationX(deg(90.0f)).inverted(); + + /* Scene alone */ + CORRADE_COMPARE(s.transformations({&s}, initial), vector{initial}); + + /* One object */ + Object3D first(&s); + first.rotateZ(deg(30.0f)); + Object3D second(&first); + second.scale(Vector3(0.5f)); + CORRADE_COMPARE(s.transformations({&second}, initial), vector{ + initial*Matrix4::rotationZ(deg(30.0f))*Matrix4::scaling(Vector3(0.5f)) + }); + + /* One object and scene */ + CORRADE_COMPARE(s.transformations({&second, &s}, initial), (vector{ + initial*Matrix4::rotationZ(deg(30.0f))*Matrix4::scaling(Vector3(0.5f)), + initial + })); + + /* Two objects with foreign joint */ + Object3D third(&first); + third.translate(Vector3::xAxis(5.0f)); + CORRADE_COMPARE(s.transformations({&second, &third}, initial), (vector{ + initial*Matrix4::rotationZ(deg(30.0f))*Matrix4::scaling(Vector3(0.5f)), + initial*Matrix4::rotationZ(deg(30.0f))*Matrix4::translation(Vector3::xAxis(5.0f)), + })); + + /* Three objects with joint as one of them */ + CORRADE_COMPARE(s.transformations({&second, &third, &first}, initial), (vector{ + initial*Matrix4::rotationZ(deg(30.0f))*Matrix4::scaling(Vector3(0.5f)), + initial*Matrix4::rotationZ(deg(30.0f))*Matrix4::translation(Vector3::xAxis(5.0f)), + initial*Matrix4::rotationZ(deg(30.0f)), + })); + + { + CORRADE_EXPECT_FAIL("Transformations not relative to scene are not yet implemented."); + + /* Transformation relative to another object */ + CORRADE_COMPARE(second.transformations({&third}), vector{ + Matrix4::scaling(Vector3(0.5f)).inverted()*Matrix4::translation(Vector3::xAxis(5.0f)) + }); + + /* Transformation relative to another object, not part of any scene (but should work) */ + Object3D orphanParent1; + orphanParent1.rotate(deg(31.0f), Vector3(1.0f).normalized()); + Object3D orphanParent(&orphanParent1); + Object3D orphan1(&orphanParent); + orphan1.scale(Vector3::xScale(3.0f)); + Object3D orphan2(&orphanParent); + orphan2.translate(Vector3::zAxis(5.0f)); + CORRADE_COMPARE(orphan1.transformations({&orphan2}), vector{ + Matrix4::scaling(Vector3::xScale(3.0f)).inverted()*Matrix4::translation(Vector3::zAxis(5.0f)) + }); + } + + ostringstream o; + Error::setOutput(&o); + + /* Transformation of objects not part of the same scene */ Object3D orphan; - Object3D* childOfOrphan = new Object3D(&orphan); - - CORRADE_VERIFY(childTwo->scene() == &scene); - CORRADE_VERIFY(childOfOrphan->scene() == nullptr); + CORRADE_COMPARE(s.transformations({&orphan}), vector()); + CORRADE_COMPARE(o.str(), "SceneGraph::Object::transformations(): the objects are not part of the same tree\n"); } -void ObjectTest::dirty() { +void ObjectTest::caching() { Scene3D scene; - CleaningObject* childOne = new CleaningObject(&scene); + class CachingFeature: public AbstractFeature<3, GLfloat> { + public: + CachingFeature(AbstractObject<3, GLfloat>* object): AbstractFeature<3, GLfloat>(object) { + setCachedTransformations(CachedTransformation::Absolute); + } + + Matrix4 cleanedAbsoluteTransformation; + + void clean(const Matrix4& absoluteTransformation) override { + cleanedAbsoluteTransformation = absoluteTransformation; + } + }; + + class CachingInvertedFeature: public AbstractFeature<3, GLfloat> { + public: + CachingInvertedFeature(AbstractObject<3, GLfloat>* object): AbstractFeature<3, GLfloat>(object) { + setCachedTransformations(CachedTransformation::InvertedAbsolute); + } + + Matrix4 cleanedInvertedAbsoluteTransformation; + + void cleanInverted(const Matrix4& invertedAbsoluteTransformation) override { + cleanedInvertedAbsoluteTransformation = invertedAbsoluteTransformation; + } + }; + + class CachingObject: public Object3D, AbstractFeature<3, GLfloat> { + public: + inline CachingObject(Object3D* parent = nullptr): Object3D(parent), AbstractFeature<3, GLfloat>(this) { + setCachedTransformations(CachedTransformation::Absolute); + } + + Matrix4 cleanedAbsoluteTransformation; + + protected: + void clean(const Matrix4& absoluteTransformation) override { + cleanedAbsoluteTransformation = absoluteTransformation; + } + }; + + CachingObject* childOne = new CachingObject(&scene); childOne->scale(Vector3(2.0f)); - CleaningObject* childTwo = new CleaningObject(childOne); + + CachingObject* childTwo = new CachingObject(childOne); childTwo->translate(Vector3::xAxis(1.0f)); - CleaningObject* childThree = new CleaningObject(childTwo); + CachingFeature* childTwoFeature = new CachingFeature(childTwo); + CachingInvertedFeature* childTwoFeature2 = new CachingInvertedFeature(childTwo); + + CachingObject* childThree = new CachingObject(childTwo); childThree->rotate(deg(90.0f), Vector3::yAxis()); /* Object is dirty at the beginning */ CORRADE_VERIFY(scene.isDirty()); CORRADE_VERIFY(childOne->isDirty()); + CORRADE_VERIFY(childTwo->isDirty()); + CORRADE_VERIFY(childThree->isDirty()); /* Clean the object and all its dirty parents (but not children) */ - childOne->setClean(); - CORRADE_COMPARE(childOne->cleanedAbsoluteTransformation, childOne->absoluteTransformation()); + childTwo->setClean(); + CORRADE_VERIFY(!scene.isDirty()); + CORRADE_VERIFY(!childOne->isDirty()); + CORRADE_VERIFY(!childTwo->isDirty()); + CORRADE_VERIFY(childThree->isDirty()); + + /* Verify the right matrices were passed */ + CORRADE_COMPARE(childOne->cleanedAbsoluteTransformation, childOne->absoluteTransformationMatrix()); + CORRADE_COMPARE(childTwo->cleanedAbsoluteTransformation, childTwo->absoluteTransformationMatrix()); + CORRADE_COMPARE(childTwoFeature->cleanedAbsoluteTransformation, childTwo->absoluteTransformationMatrix()); + CORRADE_COMPARE(childTwoFeature2->cleanedInvertedAbsoluteTransformation, childTwo->absoluteTransformationMatrix().inverted()); + + /* Mark object and all its children as dirty (but not parents) */ + childTwo->setDirty(); CORRADE_VERIFY(!scene.isDirty()); CORRADE_VERIFY(!childOne->isDirty()); CORRADE_VERIFY(childTwo->isDirty()); @@ -169,24 +256,14 @@ void ObjectTest::dirty() { /* If any object in the hierarchy is already clean, it shouldn't clean it again */ childTwo->setClean(); CORRADE_COMPARE(childOne->cleanedAbsoluteTransformation, Matrix4(Matrix4::Zero)); - CORRADE_COMPARE(childTwo->cleanedAbsoluteTransformation, childTwo->absoluteTransformation()); - CORRADE_VERIFY(!childOne->isDirty()); - CORRADE_VERIFY(!childTwo->isDirty()); - CORRADE_VERIFY(childThree->isDirty()); - - /* Mark object and all its children as dirty (but not parents) */ - childTwo->setDirty(); - CORRADE_VERIFY(!scene.isDirty()); - CORRADE_VERIFY(!childOne->isDirty()); - CORRADE_VERIFY(childTwo->isDirty()); - CORRADE_VERIFY(childThree->isDirty()); - /* Reparent object => make it and its children dirty (but not parents) */ + /* Remove object from tree => make it and its children dirty */ childThree->setClean(); - CORRADE_COMPARE(childThree->cleanedAbsoluteTransformation, childThree->absoluteTransformation()); childTwo->setParent(nullptr); CORRADE_VERIFY(childTwo->isDirty()); CORRADE_VERIFY(!childOne->isDirty()); + + /* Add object to tree => make it and its children dirty, don't touch parents */ childTwo->setParent(&scene); CORRADE_VERIFY(!scene.isDirty()); CORRADE_VERIFY(childTwo->isDirty()); diff --git a/src/SceneGraph/Test/ObjectTest.h b/src/SceneGraph/Test/ObjectTest.h index 01149310f..f0e44b5c7 100644 --- a/src/SceneGraph/Test/ObjectTest.h +++ b/src/SceneGraph/Test/ObjectTest.h @@ -17,8 +17,6 @@ #include -#include "SceneGraph/Object.h" - namespace Magnum { namespace SceneGraph { namespace Test { class ObjectTest: public Corrade::TestSuite::Tester { @@ -26,24 +24,10 @@ class ObjectTest: public Corrade::TestSuite::Tester { ObjectTest(); void parenting(); - void transformation(); - void absoluteTransformationWrongCamera(); - void absoluteTransformation(); void scene(); - void dirty(); - - private: - class CleaningObject: public Object3D { - public: - CleaningObject(Object3D* parent = nullptr): Object3D(parent) {} - - inline void clean(const Matrix4& absoluteTransformation) { - Object3D::clean(absoluteTransformation); - - cleanedAbsoluteTransformation = absoluteTransformation; - } - Matrix4 cleanedAbsoluteTransformation; - }; + void absoluteTransformation(); + void transformations(); + void caching(); }; }}} diff --git a/src/SceneGraph/Test/SceneTest.cpp b/src/SceneGraph/Test/SceneTest.cpp index 65bff5daa..301a82846 100644 --- a/src/SceneGraph/Test/SceneTest.cpp +++ b/src/SceneGraph/Test/SceneTest.cpp @@ -15,12 +15,17 @@ #include "SceneTest.h" +#include "Magnum.h" +#include "SceneGraph/MatrixTransformation3D.h" #include "SceneGraph/Scene.h" CORRADE_TEST_MAIN(Magnum::SceneGraph::Test::SceneTest) namespace Magnum { namespace SceneGraph { namespace Test { +typedef SceneGraph::Scene> Scene3D; +typedef SceneGraph::Object> Object3D; + SceneTest::SceneTest() { addTests(&SceneTest::transformation, &SceneTest::parent); @@ -36,7 +41,6 @@ void SceneTest::transformation() { void SceneTest::parent() { Scene3D scene; - CORRADE_VERIFY(scene.isScene()); /* Scene parent cannot be changed */ Object3D* scenePointer = &scene;