//////////////////////////////////////////////////////////// // // SFML - Simple and Fast Multimedia Library // Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org) // // This software is provided 'as-is', without any express or implied warranty. // In no event will the authors be held liable for any damages arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it freely, // subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; // you must not claim that you wrote the original software. // If you use this software in a product, an acknowledgment // in the product documentation would be appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, // and must not be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source distribution. // //////////////////////////////////////////////////////////// #pragma once //////////////////////////////////////////////////////////// // Headers //////////////////////////////////////////////////////////// #include #include #include namespace sf { //////////////////////////////////////////////////////////// /// \brief Decomposed transform defined by a position, a rotation and a scale /// //////////////////////////////////////////////////////////// class SFML_GRAPHICS_API Transformable { public: //////////////////////////////////////////////////////////// /// \brief Default constructor /// //////////////////////////////////////////////////////////// Transformable() = default; //////////////////////////////////////////////////////////// /// \brief Virtual destructor /// //////////////////////////////////////////////////////////// virtual ~Transformable() = default; //////////////////////////////////////////////////////////// /// \brief set the position of the object /// /// This function completely overwrites the previous position. /// See the move function to apply an offset based on the previous position instead. /// The default position of a transformable object is (0, 0). /// /// \param position New position /// /// \see `move`, `getPosition` /// //////////////////////////////////////////////////////////// void setPosition(Vector2f position); //////////////////////////////////////////////////////////// /// \brief set the orientation of the object /// /// This function completely overwrites the previous rotation. /// See the rotate function to add an angle based on the previous rotation instead. /// The default rotation of a transformable object is 0. /// /// \param angle New rotation /// /// \see `rotate`, `getRotation` /// //////////////////////////////////////////////////////////// void setRotation(Angle angle); //////////////////////////////////////////////////////////// /// \brief set the scale factors of the object /// /// This function completely overwrites the previous scale. /// See the scale function to add a factor based on the previous scale instead. /// The default scale of a transformable object is (1, 1). /// /// \param factors New scale factors /// /// \see `scale`, `getScale` /// //////////////////////////////////////////////////////////// void setScale(Vector2f factors); //////////////////////////////////////////////////////////// /// \brief set the local origin of the object /// /// The origin of an object defines the center point for /// all transformations (position, scale, rotation). /// The coordinates of this point must be relative to the /// top-left corner of the object, and ignore all /// transformations (position, scale, rotation). /// The default origin of a transformable object is (0, 0). /// /// \param origin New origin /// /// \see `getOrigin` /// //////////////////////////////////////////////////////////// void setOrigin(Vector2f origin); //////////////////////////////////////////////////////////// /// \brief get the position of the object /// /// \return Current position /// /// \see `setPosition` /// //////////////////////////////////////////////////////////// [[nodiscard]] Vector2f getPosition() const; //////////////////////////////////////////////////////////// /// \brief get the orientation of the object /// /// The rotation is always in the range [0, 360]. /// /// \return Current rotation /// /// \see `setRotation` /// //////////////////////////////////////////////////////////// [[nodiscard]] Angle getRotation() const; //////////////////////////////////////////////////////////// /// \brief get the current scale of the object /// /// \return Current scale factors /// /// \see `setScale` /// //////////////////////////////////////////////////////////// [[nodiscard]] Vector2f getScale() const; //////////////////////////////////////////////////////////// /// \brief get the local origin of the object /// /// \return Current origin /// /// \see `setOrigin` /// //////////////////////////////////////////////////////////// [[nodiscard]] Vector2f getOrigin() const; //////////////////////////////////////////////////////////// /// \brief Move the object by a given offset /// /// This function adds to the current position of the object, /// unlike `setPosition` which overwrites it. /// Thus, it is equivalent to the following code: /// \code /// object.setPosition(object.getPosition() + offset); /// \endcode /// /// \param offset Offset /// /// \see `setPosition` /// //////////////////////////////////////////////////////////// void move(Vector2f offset); //////////////////////////////////////////////////////////// /// \brief Rotate the object /// /// This function adds to the current rotation of the object, /// unlike `setRotation` which overwrites it. /// Thus, it is equivalent to the following code: /// \code /// object.setRotation(object.getRotation() + angle); /// \endcode /// /// \param angle Angle of rotation /// //////////////////////////////////////////////////////////// void rotate(Angle angle); //////////////////////////////////////////////////////////// /// \brief Scale the object /// /// This function multiplies the current scale of the object, /// unlike `setScale` which overwrites it. /// Thus, it is equivalent to the following code: /// \code /// sf::Vector2f scale = object.getScale(); /// object.setScale(scale.x * factor.x, scale.y * factor.y); /// \endcode /// /// \param factor Scale factors /// /// \see `setScale` /// //////////////////////////////////////////////////////////// void scale(Vector2f factor); //////////////////////////////////////////////////////////// /// \brief get the combined transform of the object /// /// \return Transform combining the position/rotation/scale/origin of the object /// /// \see `getInverseTransform` /// //////////////////////////////////////////////////////////// [[nodiscard]] const Transform& getTransform() const; //////////////////////////////////////////////////////////// /// \brief get the inverse of the combined transform of the object /// /// \return Inverse of the combined transformations applied to the object /// /// \see `getTransform` /// //////////////////////////////////////////////////////////// [[nodiscard]] const Transform& getInverseTransform() const; private: //////////////////////////////////////////////////////////// // Member data //////////////////////////////////////////////////////////// Vector2f m_origin; //!< Origin of translation/rotation/scaling of the object Vector2f m_position; //!< Position of the object in the 2D world Angle m_rotation; //!< Orientation of the object Vector2f m_scale{1, 1}; //!< Scale of the object mutable Transform m_transform; //!< Combined transformation of the object mutable Transform m_inverseTransform; //!< Combined transformation of the object mutable bool m_transformNeedUpdate{true}; //!< Does the transform need to be recomputed? mutable bool m_inverseTransformNeedUpdate{true}; //!< Does the transform need to be recomputed? }; } // namespace sf //////////////////////////////////////////////////////////// /// \class sf::Transformable /// \ingroup graphics /// /// This class is provided for convenience, on top of `sf::Transform`. /// /// `sf::Transform`, as a low-level class, offers a great level of /// flexibility but it is not always convenient to manage. Indeed, /// one can easily combine any kind of operation, such as a translation /// followed by a rotation followed by a scaling, but once the result /// transform is built, there's no way to go backward and, let's say, /// change only the rotation without modifying the translation and scaling. /// The entire transform must be recomputed, which means that you /// need to retrieve the initial translation and scale factors as /// well, and combine them the same way you did before updating the /// rotation. This is a tedious operation, and it requires to store /// all the individual components of the final transform. /// /// That's exactly what `sf::Transformable` was written for: it hides /// these variables and the composed transform behind an easy to use /// interface. You can set or get any of the individual components /// without worrying about the others. It also provides the composed /// transform (as a `sf::Transform`), and keeps it up-to-date. /// /// In addition to the position, rotation and scale, `sf::Transformable` /// provides an "origin" component, which represents the local origin /// of the three other components. Let's take an example with a 10x10 /// pixels sprite. By default, the sprite is positioned/rotated/scaled /// relative to its top-left corner, because it is the local point /// (0, 0). But if we change the origin to be (5, 5), the sprite will /// be positioned/rotated/scaled around its center instead. And if /// we set the origin to (10, 10), it will be transformed around its /// bottom-right corner. /// /// To keep the `sf::Transformable` class simple, there's only one /// origin for all the components. You cannot position the sprite /// relative to its top-left corner while rotating it around its /// center, for example. To do such things, use `sf::Transform` directly. /// /// `sf::Transformable` can be used as a base class. It is often /// combined with `sf::Drawable` -- that's what SFML's sprites, /// texts and shapes do. /// \code /// class MyEntity : public sf::Transformable, public sf::Drawable /// { /// void draw(sf::RenderTarget& target, sf::RenderStates states) const override /// { /// states.transform *= getTransform(); /// target.draw(..., states); /// } /// }; /// /// MyEntity entity; /// entity.setPosition({10, 20}); /// entity.setRotation(sf::degrees(45)); /// window.draw(entity); /// \endcode /// /// It can also be used as a member, if you don't want to use /// its API directly (because you don't need all its functions, /// or you have different naming conventions for example). /// \code /// class MyEntity /// { /// public: /// void SetPosition(const MyVector& v) /// { /// myTransform.setPosition(v.x(), v.y()); /// } /// /// void Draw(sf::RenderTarget& target) const /// { /// target.draw(..., myTransform.getTransform()); /// } /// /// private: /// sf::Transformable myTransform; /// }; /// \endcode /// /// A note on coordinates and undistorted rendering: \n /// By default, SFML (or more exactly, OpenGL) may interpolate drawable objects /// such as sprites or texts when rendering. While this allows transitions /// like slow movements or rotations to appear smoothly, it can lead to /// unwanted results in some cases, for example blurred or distorted objects. /// In order to render a `sf::Drawable` object pixel-perfectly, make sure /// the involved coordinates allow a 1:1 mapping of pixels in the window /// to texels (pixels in the texture). More specifically, this means: /// * The object's position, origin and scale have no fractional part /// * The object's and the view's rotation are a multiple of 90 degrees /// * The view's center and size have no fractional part /// /// \see `sf::Transform` /// ////////////////////////////////////////////////////////////