From f062f2dd8671b409392cd971a07953448cabedec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 30 Oct 2012 11:32:22 +0100 Subject: [PATCH 1/9] SceneGraph rework, part 1: removed Light object. It's nearly useless and doesn't contain any useful code. Will be done differently. --- src/SceneGraph/CMakeLists.txt | 4 +-- src/SceneGraph/Light.cpp | 26 --------------- src/SceneGraph/Light.h | 60 ----------------------------------- 3 files changed, 1 insertion(+), 89 deletions(-) delete mode 100644 src/SceneGraph/Light.cpp delete mode 100644 src/SceneGraph/Light.h diff --git a/src/SceneGraph/CMakeLists.txt b/src/SceneGraph/CMakeLists.txt index dd8f5bee3..7eb5ef7b6 100644 --- a/src/SceneGraph/CMakeLists.txt +++ b/src/SceneGraph/CMakeLists.txt @@ -1,10 +1,8 @@ # Files shared between main library and unit test library set(MagnumSceneGraph_SRCS - Camera.cpp - Light.cpp) + Camera.cpp) set(MagnumSceneGraph_HEADERS Camera.h - Light.h Object.h Scene.h diff --git a/src/SceneGraph/Light.cpp b/src/SceneGraph/Light.cpp deleted file mode 100644 index 385c6efc4..000000000 --- a/src/SceneGraph/Light.cpp +++ /dev/null @@ -1,26 +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 "Light.h" - -namespace Magnum { namespace SceneGraph { - -void Light::clean(const Matrix4& absoluteTransformation) { - Object3D::clean(absoluteTransformation); - - _position = absoluteTransformation[3]; -} - -}} 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 From f66ded2d6063171ecd0149d42dc510b4719b7d3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 30 Oct 2012 23:54:08 +0100 Subject: [PATCH 2/9] SceneGraph rework, part 2: introductional documentation. To make sure we know where it all goes. --- doc/compilation-speedup.dox | 79 +++++++++++ doc/features.dox | 2 + doc/namespaces.dox | 3 +- doc/scenegraph.dox | 267 ++++++++++++++++++++++++++++++++++++ 4 files changed, 350 insertions(+), 1 deletion(-) create mode 100644 doc/compilation-speedup.dox create mode 100644 doc/scenegraph.dox 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). +*/ +}} From 06398dcf7c1cb21169e04c166ca932ad3273f466 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 31 Oct 2012 00:16:46 +0100 Subject: [PATCH 3/9] SceneGraph rework, part 3: splitting base object. Functionality present in Object is now split into three main components: * Object itself, handling parent/children relationships * Transformation implementation and interfaces for common functionality * Object features, providing transformation caching and base for cameras, collision shapes, rigid bodies etc. Some functionality depending on former implementation is temporarily disabled and will be reworked later. --- src/Physics/CMakeLists.txt | 11 +- src/Physics/Test/CMakeLists.txt | 2 - src/SceneGraph/AbstractFeature.h | 319 +++++++++++++ src/SceneGraph/AbstractObject.h | 130 ++++++ src/SceneGraph/AbstractTransformation.h | 169 +++++++ .../AbstractTranslationRotation2D.h | 56 +++ .../AbstractTranslationRotation3D.h | 57 +++ .../AbstractTranslationRotationScaling2D.h | 46 ++ .../AbstractTranslationRotationScaling3D.h | 46 ++ src/SceneGraph/CMakeLists.txt | 26 +- src/SceneGraph/MatrixTransformation2D.cpp | 26 ++ src/SceneGraph/MatrixTransformation2D.h | 136 ++++++ src/SceneGraph/MatrixTransformation3D.cpp | 26 ++ src/SceneGraph/MatrixTransformation3D.h | 125 ++++++ src/SceneGraph/Object.cpp | 147 ------ src/SceneGraph/Object.h | 419 +++++------------- src/SceneGraph/Object.hpp | 159 +++++++ src/SceneGraph/Scene.h | 27 +- src/SceneGraph/Test/CMakeLists.txt | 1 - src/SceneGraph/Test/ObjectTest.cpp | 152 ++++--- src/SceneGraph/Test/ObjectTest.h | 21 +- src/SceneGraph/Test/SceneTest.cpp | 6 +- 22 files changed, 1526 insertions(+), 581 deletions(-) create mode 100644 src/SceneGraph/AbstractFeature.h create mode 100644 src/SceneGraph/AbstractObject.h create mode 100644 src/SceneGraph/AbstractTransformation.h create mode 100644 src/SceneGraph/AbstractTranslationRotation2D.h create mode 100644 src/SceneGraph/AbstractTranslationRotation3D.h create mode 100644 src/SceneGraph/AbstractTranslationRotationScaling2D.h create mode 100644 src/SceneGraph/AbstractTranslationRotationScaling3D.h create mode 100644 src/SceneGraph/MatrixTransformation2D.cpp create mode 100644 src/SceneGraph/MatrixTransformation2D.h create mode 100644 src/SceneGraph/MatrixTransformation3D.cpp create mode 100644 src/SceneGraph/MatrixTransformation3D.h delete mode 100644 src/SceneGraph/Object.cpp create mode 100644 src/SceneGraph/Object.hpp 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/AbstractObject.h b/src/SceneGraph/AbstractObject.h new file mode 100644 index 000000000..7c58ae068 --- /dev/null +++ b/src/SceneGraph/AbstractObject.h @@ -0,0 +1,130 @@ +#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(); + } + + /** @{ @name Object transformation */ + + /** + * @brief Transformation matrix relative to root object + * + * @see Object::absoluteTransformation() + */ + virtual typename DimensionTraits::MatrixType absoluteTransformationMatrix() const = 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..67178b269 --- /dev/null +++ b/src/SceneGraph/AbstractTranslationRotation3D.h @@ -0,0 +1,57 @@ +#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" + +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; +}; + +}} + +#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 7eb5ef7b6..12c1a2ede 100644 --- a/src/SceneGraph/CMakeLists.txt +++ b/src/SceneGraph/CMakeLists.txt @@ -1,25 +1,33 @@ -# Files shared between main library and unit test library -set(MagnumSceneGraph_SRCS - Camera.cpp) +set(MagnumSceneGraph_SRCS ) set(MagnumSceneGraph_HEADERS - Camera.h + AbstractFeature.h + AbstractObject.h + AbstractTransformation.h + AbstractTranslationRotation2D.h + AbstractTranslationRotation3D.h + AbstractTranslationRotationScaling2D.h + AbstractTranslationRotationScaling3D.h + MatrixTransformation2D.h + MatrixTransformation3D.h Object.h + Object.hpp Scene.h magnumSceneGraphVisibility.h) -add_library(MagnumSceneGraphObjects OBJECT ${MagnumSceneGraph_SRCS}) +# 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 -set_target_properties(MagnumSceneGraphObjects PROPERTIES COMPILE_FLAGS "-DMagnumSceneGraphObjects_EXPORTS ${CMAKE_SHARED_LIBRARY_CXX_FLAGS}") +# set_target_properties(MagnumSceneGraphObjects PROPERTIES COMPILE_FLAGS "-DMagnumSceneGraphObjects_EXPORTS ${CMAKE_SHARED_LIBRARY_CXX_FLAGS}") # SceneGraph library add_library(MagnumSceneGraph SHARED - $ +# $ ${MagnumSceneGraph_GracefulAssert_SRCS}) target_link_libraries(MagnumSceneGraph Magnum) @@ -31,7 +39,7 @@ if(BUILD_TESTS) # Library with graceful assert for testing add_library(MagnumSceneGraphTestLib SHARED - $ +# $ ${MagnumSceneGraph_GracefulAssert_SRCS}) set_target_properties(MagnumSceneGraphTestLib PROPERTIES COMPILE_FLAGS -DCORRADE_GRACEFUL_ASSERT) target_link_libraries(MagnumSceneGraphTestLib Magnum) diff --git a/src/SceneGraph/MatrixTransformation2D.cpp b/src/SceneGraph/MatrixTransformation2D.cpp new file mode 100644 index 000000000..340371b97 --- /dev/null +++ b/src/SceneGraph/MatrixTransformation2D.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 "MatrixTransformation2D.h" + +#include "Object.hpp" + +namespace Magnum { namespace SceneGraph { + +#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..493b275ef --- /dev/null +++ b/src/SceneGraph/MatrixTransformation3D.h @@ -0,0 +1,125 @@ +#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; + } + + /** + * @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..73676b6f0 100644 --- a/src/SceneGraph/Object.h +++ b/src/SceneGraph/Object.h @@ -16,89 +16,71 @@ */ /** @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 }; - 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): flags(Flag::Dirty) { setParent(parent); } @@ -108,9 +90,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 +105,135 @@ 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(); } + inline Object* firstChild() { + return Corrade::Containers::LinkedList>::first(); + } + + /** @overload */ + inline const Object* firstChild() const { + 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(); } + inline Object* lastChild() { + return Corrade::Containers::LinkedList>::last(); + } + + /** @overload */ + inline const Object* lastChild() const { + return Corrade::Containers::LinkedList>::last(); + } /** * @brief Set parent object * @return Pointer to self (for method chaining) */ - ObjectType* setParent(ObjectType* parent); + Object* setParent(Object* parent); /*@}*/ - /** @{ @name Object transformation - * - * All transformations (except absoluteTransformation()) are relative - * to parent. - */ + /** @{ @name Object transformation */ - /** @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 typename DimensionTraits::MatrixType absoluteTransformationMatrix() const override { + return Transformation::toMatrix(absoluteTransformation()); } /** - * @brief Absolute transformation + * @brief Transformation relative to root object * - * 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) + * @see absoluteTransformationMatrix() */ - 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); - } + typename Transformation::DataType absoluteTransformation() const; /*@}*/ /** - * @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. + * @{ @name Transformation caching * - * 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. + * See @ref scenegraph-caching for more information. */ - inline bool isDirty() const { return dirty; } /** - * @brief Set object and all its children as dirty + * @brief Whether absolute transformation is 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(). + * Returns `true` if transformation of the object or any parent has + * changed since last call to setClean(), `false` otherwise. * - * Reimplementations should call this function at the end, i.e.: - * @code - * void setDirty() { - * // ... + * All objects are dirty by default. * - * Object::setDirty(); - * } - * @endcode + * @see @ref scenegraph-caching */ - virtual void setDirty(); + inline bool isDirty() const { return !!(flags & Flag::Dirty); } /** - * @brief Set object and all its parents as clean + * @brief Set object absolute transformation as dirty * - * Recursively calls clean() on every parent which is not already - * clean. + * 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() */ - void setClean(); + void setDirty(); - 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); + * @brief Clean object absolute transformation * - * // ... - * } - * @endcode + * 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 clean(const typename DimensionTraits::MatrixType& absoluteTransformation); + void setClean(); /*@}*/ 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) {} - - /** - * @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; - } - - /** - * @brief Scale object - * @return Pointer to self (for method chaining) - * - * Same as calling multiplyTransformation() with Matrix3::scaling(). - */ - inline Object2D* scale(const Vector2& vec, Transformation type = Transformation::Global) { - multiplyTransformation(Matrix3::scaling(vec), type); - return this; - } - - /** - * @brief Rotate object - * @return Pointer to self (for method chaining) - * - * Same as calling multiplyTransformation() with Matrix3::rotation(). - */ - inline Object2D* rotate(GLfloat angle, Transformation type = Transformation::Global) { - multiplyTransformation(Matrix3::rotation(angle), 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 Object2D* move(Object2D* under) { - parent()->Corrade::Containers::LinkedList::move(this, under); - return this; - } -}; - -/** -@brief Three-dimensional object - -@see Object2D -*/ -class SCENEGRAPH_EXPORT Object3D: public AbstractObject<3> { - public: - /** @copydoc AbstractObject::AbstractObject() */ - inline Object3D(Object3D* parent = nullptr): AbstractObject<3>(parent) {} - - /** - * @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; - } - - /** - * @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; - } - - /** - * @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; + Flags flags; }; }} diff --git a/src/SceneGraph/Object.hpp b/src/SceneGraph/Object.hpp new file mode 100644 index 000000000..dc54f42ca --- /dev/null +++ b/src/SceneGraph/Object.hpp @@ -0,0 +1,159 @@ +#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() { + Object* p(this); + while(p && !p->isScene()) p = p->parent(); + return static_cast*>(p); +} + +template const Scene* Object::scene() const { + const Object* p(this); + while(p && !p->isScene()) p = p->parent(); + return static_cast*>(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; + } +} + +}} + +#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/CMakeLists.txt b/src/SceneGraph/Test/CMakeLists.txt index e0db0910d..189382bdf 100644 --- a/src/SceneGraph/Test/CMakeLists.txt +++ b/src/SceneGraph/Test/CMakeLists.txt @@ -1,3 +1,2 @@ corrade_add_test2(SceneGraphObjectTest ObjectTest.cpp LIBRARIES MagnumSceneGraphTestLib) -corrade_add_test2(SceneGraphCameraTest CameraTest.cpp LIBRARIES MagnumSceneGraph) corrade_add_test2(SceneGraphSceneTest SceneTest.cpp LIBRARIES MagnumSceneGraph) diff --git a/src/SceneGraph/Test/ObjectTest.cpp b/src/SceneGraph/Test/ObjectTest.cpp index c838704f8..995d2aeff 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,14 @@ 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::caching); } void ObjectTest::parenting() { @@ -66,61 +67,19 @@ 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::absoluteTransformationWrongCamera() { - stringstream ss; - Error::setOutput(&ss); - - 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"); -} - 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.rotate(deg(90.0f), Vector3::yAxis()); 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::rotation(deg(90.0f), Vector3::yAxis())); + 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})); @@ -128,6 +87,7 @@ void ObjectTest::absoluteTransformation() { void ObjectTest::scene() { Scene3D scene; + CORRADE_VERIFY(scene.scene() == &scene); Object3D* childOne = new Object3D(&scene); Object3D* childTwo = new Object3D(childOne); @@ -139,23 +99,81 @@ void ObjectTest::scene() { CORRADE_VERIFY(childOfOrphan->scene() == nullptr); } -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 +187,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..b0294ca89 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,9 @@ 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 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; From abac1d26dc6af739ab5cd21d00fa54ad81baa2fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 31 Oct 2012 00:51:43 +0100 Subject: [PATCH 4/9] SceneGraph rework, part 4: faster rotation for X, Y and Z axes. By default implemented using rotate() so transformation implementers don't need to bother, but MatrixTransformation3D reimplements it using optimized rotation functions. --- .../AbstractTranslationRotation3D.h | 43 +++++++++++++++++++ src/SceneGraph/MatrixTransformation3D.h | 42 ++++++++++++++++++ 2 files changed, 85 insertions(+) diff --git a/src/SceneGraph/AbstractTranslationRotation3D.h b/src/SceneGraph/AbstractTranslationRotation3D.h index 67178b269..4bc174a59 100644 --- a/src/SceneGraph/AbstractTranslationRotation3D.h +++ b/src/SceneGraph/AbstractTranslationRotation3D.h @@ -20,6 +20,7 @@ */ #include "AbstractTransformation.h" +#include "Math/Vector3.h" namespace Magnum { namespace SceneGraph { @@ -50,6 +51,48 @@ template class AbstractTranslationRotation3D: public Abstract * @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); + } }; }} diff --git a/src/SceneGraph/MatrixTransformation3D.h b/src/SceneGraph/MatrixTransformation3D.h index 493b275ef..973e0bd36 100644 --- a/src/SceneGraph/MatrixTransformation3D.h +++ b/src/SceneGraph/MatrixTransformation3D.h @@ -103,6 +103,48 @@ template class MatrixTransformation3D: public AbstractTransla 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(). From 342d2f46ac344ac2d38f81384a18f2aa4193b8c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Mon, 5 Nov 2012 19:19:51 +0100 Subject: [PATCH 5/9] SceneGraph: allow features to access scene and object's dirty status. The downside of this is that the functions are now virtual, which makes them slower than before. Maybe final qualifier would help? AbstractObject::sceneObject() is marked as private in Object to avoid confusion. --- src/SceneGraph/AbstractObject.h | 55 +++++++++++++++++++++++++++++++++ src/SceneGraph/Object.h | 47 ++++------------------------ src/SceneGraph/Object.hpp | 14 +++++++-- 3 files changed, 72 insertions(+), 44 deletions(-) diff --git a/src/SceneGraph/AbstractObject.h b/src/SceneGraph/AbstractObject.h index 7c58ae068..662a56c07 100644 --- a/src/SceneGraph/AbstractObject.h +++ b/src/SceneGraph/AbstractObject.h @@ -79,6 +79,19 @@ template class AbstractObject 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 */ /** @@ -89,6 +102,48 @@ template class AbstractObject virtual typename DimensionTraits::MatrixType absoluteTransformationMatrix() 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; + + /*@}*/ }; /** diff --git a/src/SceneGraph/Object.h b/src/SceneGraph/Object.h index 73676b6f0..587f2f07d 100644 --- a/src/SceneGraph/Object.h +++ b/src/SceneGraph/Object.h @@ -188,49 +188,14 @@ template class Object: public AbstractObject* sceneObject() override; + const Object* sceneObject() const override; + typedef Implementation::ObjectFlag Flag; typedef Implementation::ObjectFlags Flags; Flags flags; diff --git a/src/SceneGraph/Object.hpp b/src/SceneGraph/Object.hpp index dc54f42ca..092cc9c70 100644 --- a/src/SceneGraph/Object.hpp +++ b/src/SceneGraph/Object.hpp @@ -28,15 +28,23 @@ 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 static_cast*>(p); + return p; } -template const Scene* Object::scene() const { +template const Object* Object::sceneObject() const { const Object* p(this); while(p && !p->isScene()) p = p->parent(); - return static_cast*>(p); + return p; } template Object* Object::setParent(Object* parent) { From ac38af4ef1ffa0a6bb3b728dfbc0ea660ec84225 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 31 Oct 2012 00:25:09 +0100 Subject: [PATCH 6/9] SceneGraph: cleanup and reorganization of Object test. No functional change. --- src/SceneGraph/Test/ObjectTest.cpp | 32 +++++++++++++++--------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/SceneGraph/Test/ObjectTest.cpp b/src/SceneGraph/Test/ObjectTest.cpp index 995d2aeff..80f8a2236 100644 --- a/src/SceneGraph/Test/ObjectTest.cpp +++ b/src/SceneGraph/Test/ObjectTest.cpp @@ -67,6 +67,20 @@ void ObjectTest::parenting() { CORRADE_VERIFY(!childOne->hasChildren()); } +void ObjectTest::scene() { + Scene3D scene; + CORRADE_VERIFY(scene.scene() == &scene); + + Object3D* childOne = new Object3D(&scene); + Object3D* childTwo = new Object3D(childOne); + + Object3D orphan; + Object3D* childOfOrphan = new Object3D(&orphan); + + CORRADE_VERIFY(childTwo->scene() == &scene); + CORRADE_VERIFY(childOfOrphan->scene() == nullptr); +} + void ObjectTest::absoluteTransformation() { Scene3D s; @@ -74,9 +88,9 @@ void ObjectTest::absoluteTransformation() { Object3D o(&s); o.translate(Vector3::xAxis(2.0f)); Object3D o2(&o); - o2.rotate(deg(90.0f), Vector3::yAxis()); + o2.rotateY(deg(90.0f)); CORRADE_COMPARE(o2.absoluteTransformation(), - Matrix4::translation(Vector3::xAxis(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 */ @@ -85,20 +99,6 @@ void ObjectTest::absoluteTransformation() { CORRADE_COMPARE(o3.absoluteTransformation(), Matrix4::translation({1.0f, 2.0f, 3.0f})); } -void ObjectTest::scene() { - Scene3D scene; - CORRADE_VERIFY(scene.scene() == &scene); - - Object3D* childOne = new Object3D(&scene); - Object3D* childTwo = new Object3D(childOne); - - Object3D orphan; - Object3D* childOfOrphan = new Object3D(&orphan); - - CORRADE_VERIFY(childTwo->scene() == &scene); - CORRADE_VERIFY(childOfOrphan->scene() == nullptr); -} - void ObjectTest::caching() { Scene3D scene; From 4e53b17f8dc2c70eb8f8935d3140f3df155a6538 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 7 Nov 2012 01:45:14 +0100 Subject: [PATCH 7/9] SceneGraph rework, part 5: bulk transformation computing. --- src/SceneGraph/AbstractObject.h | 11 +++ src/SceneGraph/Object.h | 22 ++++- src/SceneGraph/Object.hpp | 124 +++++++++++++++++++++++++++++ src/SceneGraph/Test/ObjectTest.cpp | 69 ++++++++++++++++ src/SceneGraph/Test/ObjectTest.h | 1 + 5 files changed, 225 insertions(+), 2 deletions(-) diff --git a/src/SceneGraph/AbstractObject.h b/src/SceneGraph/AbstractObject.h index 662a56c07..286e56f2b 100644 --- a/src/SceneGraph/AbstractObject.h +++ b/src/SceneGraph/AbstractObject.h @@ -101,6 +101,17 @@ template class AbstractObject */ 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; + /*@}*/ /** diff --git a/src/SceneGraph/Object.h b/src/SceneGraph/Object.h index 587f2f07d..94ac66e63 100644 --- a/src/SceneGraph/Object.h +++ b/src/SceneGraph/Object.h @@ -33,7 +33,9 @@ template class Scene; #ifndef DOXYGEN_GENERATING_OUTPUT namespace Implementation { enum class ObjectFlag: std::uint8_t { - Dirty = 1 << 0 + Dirty = 1 << 0, + Visited = 1 << 1, + Joint = 1 << 2 }; typedef Corrade::Containers::EnumSet ObjectFlags; @@ -80,7 +82,7 @@ template class Object: public AbstractObject* parent = nullptr): flags(Flag::Dirty) { + inline Object(Object* parent = nullptr): counter(0xFFFFu), flags(Flag::Dirty) { setParent(parent); } @@ -186,6 +188,17 @@ template class Object: public AbstractObject transformations(std::vector*> objects, const typename Transformation::DataType& initialTransformation = typename Transformation::DataType()) const; + /*@}*/ inline bool isDirty() const override { return !!(flags & Flag::Dirty); } @@ -196,8 +209,13 @@ template class Object: public AbstractObject* sceneObject() override; const Object* sceneObject() const override; + std::vector::MatrixType> transformationMatrices(const std::vector*>& objects, const typename DimensionTraits::MatrixType& initialTransformationMatrix = typename DimensionTraits::MatrixType()) const override; + + typename Transformation::DataType computeJointTransformation(const std::vector*>& jointObjects, std::vector& jointTransformations, const std::size_t joint, const typename Transformation::DataType& initialTransformation) const; + 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 index 092cc9c70..dec18a843 100644 --- a/src/SceneGraph/Object.hpp +++ b/src/SceneGraph/Object.hpp @@ -162,6 +162,130 @@ template void Object::setClean() { } } +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/Test/ObjectTest.cpp b/src/SceneGraph/Test/ObjectTest.cpp index 80f8a2236..bba87ffe8 100644 --- a/src/SceneGraph/Test/ObjectTest.cpp +++ b/src/SceneGraph/Test/ObjectTest.cpp @@ -34,6 +34,7 @@ ObjectTest::ObjectTest() { addTests(&ObjectTest::parenting, &ObjectTest::scene, &ObjectTest::absoluteTransformation, + &ObjectTest::transformations, &ObjectTest::caching); } @@ -99,6 +100,74 @@ void ObjectTest::absoluteTransformation() { CORRADE_COMPARE(o3.absoluteTransformation(), Matrix4::translation({1.0f, 2.0f, 3.0f})); } +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; + 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::caching() { Scene3D scene; diff --git a/src/SceneGraph/Test/ObjectTest.h b/src/SceneGraph/Test/ObjectTest.h index b0294ca89..f0e44b5c7 100644 --- a/src/SceneGraph/Test/ObjectTest.h +++ b/src/SceneGraph/Test/ObjectTest.h @@ -26,6 +26,7 @@ class ObjectTest: public Corrade::TestSuite::Tester { void parenting(); void scene(); void absoluteTransformation(); + void transformations(); void caching(); }; From a65a0430d0c020adb19ef25dbb5d43bbdc552e54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 7 Nov 2012 01:46:48 +0100 Subject: [PATCH 8/9] SceneGraph rework, part 6: base for grouped features. --- src/SceneGraph/AbstractGroupedFeature.h | 239 ++++++++++++++++++++++++ src/SceneGraph/CMakeLists.txt | 1 + 2 files changed, 240 insertions(+) create mode 100644 src/SceneGraph/AbstractGroupedFeature.h 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/CMakeLists.txt b/src/SceneGraph/CMakeLists.txt index 12c1a2ede..270a4f91a 100644 --- a/src/SceneGraph/CMakeLists.txt +++ b/src/SceneGraph/CMakeLists.txt @@ -1,6 +1,7 @@ set(MagnumSceneGraph_SRCS ) set(MagnumSceneGraph_HEADERS AbstractFeature.h + AbstractGroupedFeature.h AbstractObject.h AbstractTransformation.h AbstractTranslationRotation2D.h From bef497c46acb845bf2695e09957f54d12f28d03f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 7 Nov 2012 01:51:13 +0100 Subject: [PATCH 9/9] SceneGraph rework, part 7: Drawable and Camera features. * Camera is now templated also on underlying floating-point type. * Drawable objects can be split into groups (e.g. for separated rendering of transparent objects) * Added (long time missing) test for draw() function. --- src/SceneGraph/CMakeLists.txt | 14 +- src/SceneGraph/Camera.cpp | 115 +-------------- src/SceneGraph/Camera.h | 215 +++++++++++++++++++---------- src/SceneGraph/Camera.hpp | 145 +++++++++++++++++++ src/SceneGraph/Drawable.h | 192 ++++++++++++++++++++++++++ src/SceneGraph/Test/CMakeLists.txt | 1 + src/SceneGraph/Test/CameraTest.cpp | 89 +++++++++--- src/SceneGraph/Test/CameraTest.h | 1 + 8 files changed, 571 insertions(+), 201 deletions(-) create mode 100644 src/SceneGraph/Camera.hpp create mode 100644 src/SceneGraph/Drawable.h diff --git a/src/SceneGraph/CMakeLists.txt b/src/SceneGraph/CMakeLists.txt index 270a4f91a..2268b0dd5 100644 --- a/src/SceneGraph/CMakeLists.txt +++ b/src/SceneGraph/CMakeLists.txt @@ -1,4 +1,5 @@ -set(MagnumSceneGraph_SRCS ) +set(MagnumSceneGraph_SRCS + Camera.cpp) set(MagnumSceneGraph_HEADERS AbstractFeature.h AbstractGroupedFeature.h @@ -8,6 +9,9 @@ set(MagnumSceneGraph_HEADERS AbstractTranslationRotation3D.h AbstractTranslationRotationScaling2D.h AbstractTranslationRotationScaling3D.h + Camera.h + Camera.hpp + Drawable.h MatrixTransformation2D.h MatrixTransformation3D.h Object.h @@ -15,7 +19,7 @@ set(MagnumSceneGraph_HEADERS Scene.h magnumSceneGraphVisibility.h) -# add_library(MagnumSceneGraphObjects OBJECT ${MagnumSceneGraph_SRCS}) +add_library(MagnumSceneGraphObjects OBJECT ${MagnumSceneGraph_SRCS}) # Files compiled with different flags for main library and unit test library set(MagnumSceneGraph_GracefulAssert_SRCS @@ -24,11 +28,11 @@ set(MagnumSceneGraph_GracefulAssert_SRCS # 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 -# set_target_properties(MagnumSceneGraphObjects PROPERTIES COMPILE_FLAGS "-DMagnumSceneGraphObjects_EXPORTS ${CMAKE_SHARED_LIBRARY_CXX_FLAGS}") +set_target_properties(MagnumSceneGraphObjects PROPERTIES COMPILE_FLAGS "-DMagnumSceneGraphObjects_EXPORTS ${CMAKE_SHARED_LIBRARY_CXX_FLAGS}") # SceneGraph library add_library(MagnumSceneGraph SHARED -# $ + $ ${MagnumSceneGraph_GracefulAssert_SRCS}) target_link_libraries(MagnumSceneGraph Magnum) @@ -40,7 +44,7 @@ if(BUILD_TESTS) # Library with graceful assert for testing add_library(MagnumSceneGraphTestLib SHARED -# $ + $ ${MagnumSceneGraph_GracefulAssert_SRCS}) set_target_properties(MagnumSceneGraphTestLib PROPERTIES COMPILE_FLAGS -DCORRADE_GRACEFUL_ASSERT) target_link_libraries(MagnumSceneGraphTestLib Magnum) 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/Test/CMakeLists.txt b/src/SceneGraph/Test/CMakeLists.txt index 189382bdf..e0db0910d 100644 --- a/src/SceneGraph/Test/CMakeLists.txt +++ b/src/SceneGraph/Test/CMakeLists.txt @@ -1,2 +1,3 @@ corrade_add_test2(SceneGraphObjectTest ObjectTest.cpp LIBRARIES MagnumSceneGraphTestLib) +corrade_add_test2(SceneGraphCameraTest CameraTest.cpp LIBRARIES MagnumSceneGraph) corrade_add_test2(SceneGraphSceneTest SceneTest.cpp LIBRARIES MagnumSceneGraph) 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(); }; }}}