Qt's QTransform offers some more optimized ways to construct a translated/scaled QTransform matrix using these static methods:
QTransform::fromScale
QTransform::fromTranslate
Now I need a rotatated transform and I thought it would be nice to also have a QTransform::fromRotate. But this one does not exist.
In my case I am modifying a existing transform accordingly to mouse interaction like paning, zooming and also rotating.
void MapDrawingItem::wheelEvent(QWheelEvent* event)
{
//Moving the hovered point to the top left point on screen
m_view_transform *= QTransform::fromTranslate(-event->posF().x(), -event->posF().y());
//Apply transformations accordingly
if((event->modifiers() & Qt::ControlModifier) == Qt::ControlModifier)
m_view_transform *= QTransform().rotate(event->delta() / 30.);
else
{
auto factor = qPow(1.001, event->delta());
m_view_transform *= QTransform::fromScale(factor, factor);
}
//Move the hovered point back to the mouse cursor
m_view_transform *= QTransform::fromTranslate(event->posF().x(), event->posF().y());
emit signalViewTransformChanged(m_view_transform);
update();
}
The code works correctly, but I would like to replace the QTransform().rotate(...) with a QTransform::fromRotate(...)
Why does this method not exist already? I just can't imagine the Qt developers forgot for this one. Is there anything that makes this impossible?
The most probable reason for that is that creating and using a static function to create a rotation transformation forces to always link the mathematical library (for the sin(3) and cos(3) mathematical functions) so instead you can use the specific constructor for that and use them yourself.
By the way, you can use one of the constructors to specify the constants to use for the elements of the matrix. I'ts quite common to conserve the matrices for reuse when using the transformations library.
Related
Context
I have a class representing a text box. the text box contains a header, some text and a rectangle to enclose the box. It only displays itself (for now):
struct Textbox : public sf::Drawable, public sf::Transformable{
sf::Text header;
sf::Text text;
sf::RectangleShape border;
Textbox(){
// set relative locations of the members
header.setPosition(0,0);
auto header_bounds = header.getGlobalBounds();
// the text should be just below the header
text.setPosition(0, header_bounds.top + header_bounds.height);
auto source_bounds = text.getGlobalBounds();
// this function just returns a rectangle enclosing two rectangles
sf::FloatRect rect = enclosing_rect(header_bounds, source_bounds);
// this function sets the position, width and length of border to be equal to rect's.
setRectParams(border, rect);
}
void draw(sf::RenderTarget& target, sf::RenderStates states){
states.transform = getTransform();
target.draw(header,states);
target.draw(text,states);
target.draw(border,states);
};
The Problem
What I want
I want to add a contains method. It should return true if coor is inside the border of the box. Here is my naive implementation:
bool Textbox::contains(sf::Vector2i coor) const {
return border.getGlobalBounds().contains(coor.x, coor.y);
}
Why this implementation doesn't work
This implementation breaks when I move the Textbox via the Transformable non-virtual functions. The Textbox moves and it also draws the shapes as transformed. But! It does not actually transform them! it only displays them as transformed. So the border doesn't even know it has been moved.
Possible solutions
I can add all the functions of the Transformable API to this class, thus shadowing them and calling transform by myself on each of the members. I don;t like this because it make me write sooo much more code than I wanted. It also raises the question of how to tackle the double transforms (the one for the Textbox and the others for it's members).
I can write a completely different class Group that holds a vector of drawables and transformables and it has all that shadowing API mechanism. All that is left is to inherit from it. This doesn't actually sound that bad.
I heard about Entity System Component - it's just sound pretty overkill.
I can apply the transform when contains is called. The function is const - it's a query. Also, it's bad design to update your data on seemingly random calls.
just as before just that the transform applies to a function-local rectangle. This smells too - why do I call the transform functions on the whole Textbox just so it would apply them on every method call (so far just it's draw and contains but down the line who knows)
Make the members mutable and somehow transform them inside the draw method. This smell hackish.
The question
How do I group transformations onto multiple entities via an ergonomic API?
The only method that you really need to 'change', but to be fair add on your own is getGlobalBounds().
When you are inheriting from sf::Transformable, sf::Drawable you should treat the base class (your Textbox struct) as a shape itself therfore you just need to call myTextbox.getGlobalBounds().contains(x,y), where myTextbox is a Textbox.
Using your own code:
struct Textbox : public sf::Drawable, public sf::Transformable{
sf::Text header;
sf::Text text;
sf::RectangleShape border;
sf::FloatRect getGlobalBounds() const {
auto header_bounds = header.getGlobalBounds();
auto source_bounds = text.getGlobalBounds();
sf::FloatRect rect = enclosing_rect(header_bounds, source_bounds);
//Don't really know what it does but let say that it returns Top and Left as 0, and calculates Height, Width.
return sf::FloatRect(getPosition(), sf::Vector2f(rect.width,rect.height));
}
};
But you still have to manage the rotation, resizing,etc. when calculating globalBounds.
EDIT:
One way to implement rotation and scaling.
sf::FloatRect getGlobalBounds() const {
auto header_bounds = header.getGlobalBounds();
auto source_bounds = text.getGlobalBounds();
sf::FloatRect rect = enclosing_rect(header_bounds, source_bounds);
//Don't really know what it does but let say that it returns Top and Left as 0, and calculates Height, Width.
sf::RectangleShape textbox(sf::Vector2f(rect.width, rect.height));
//at this point textbox = globalBounds of Textbox without transformations
textbox.setOrigin(getOrigin());//setOrigin (point of transformation) before transforming
textbox.setScale(getScale());
textbox.setRotation(getRotation());
textbox.setPosition(getPosition());
//after transformation get the bounds
return textbox.getGlobalBounds();
}
The solution might be much more simple than you expect. Instead of applying all the transforms to the transformable children/members, just de-transform the point you want to check (take it to local space).
Try this:
bool Textbox::contains(sf::Vector2i coor) const {
// Get point in the local space of the rectangle
sf::Transform inverseTr = this->getInverseTransform();
sf::Vector2f pointAsLocal = inverseTr.transformPoint(coor.x, coor.y);
// Check if the point, now in local space, is containted in the rectangle
return border.getLocalBounds().contains(pointAsLocal);
// ^
// Important! Use local bounds here, not global
}
Why does this work?
Math!
When you work with transformation matrices, you can think of them as portals between spaces. You have a local space where no transformation have been applied, and you have a final space, where all transformations are applied.
The problem with global bounds of a transformable member is that they belong neither to the local space nor the final space. They are just a rectangle bounding the shape in a possibly intermediate space where this bounds doesn't even take rotation into account.
What we are doing here is taking the coordinates, that exist in the final space, and take them to the local space of the rectangle, thanks to the inverse transformation matrix. So no matter how many translations, rotations or scales (or even skews, if you have customized the matrix) you apply to the rectangle. The inverse matrix takes the point to a new space where you can just check if it belongs, as if no transformation have ever been applied.
Note: I'm certain I cannot be the first to answer this question. Unfortunately, the answers I need are buried under an avalanche of posts asking how to move coordinates between frames of reference, and other similar but unhelpful problems.
As part of an ongoing project, I've built a Transform object. As one might expect, this is essentially a glorified wrapper around a 4x4 transformation matrix (and various base values), and provides abstracted ways of interacting with position, orientation, and scale, as well as handling parenting to other instances to build complex hierarchies.
I am currently trying to allow the user to set and alter values of child transforms in global terms. Position is straightforward (if I'm doing it right), as this simply involves converting a coordinate from a global frame or reference to a local one.
void Transform::setGlobalPosition(const glm::vec3& newPos)
{
glm::vec4 nlp = glm::vec4(newPos, 1.0f);
if (m_hasParent) {
// This gets the parent transform from the Entity-Component System.
auto& p = p_registry->get<Transform>(m_parent);
nlp = glm::inverse(p.m_globalTransformM) * nlp;
}
setPosition(nlp);
}
void Transform::alterGlobalPosition(const glm::vec3& deltaPos)
{
glm::vec4 nlp = glm::vec4(m_globalPosition + deltaPos, 1.0f);
if (m_hasParent) {
// This gets the parent transform from the Entity-Component System.
auto& p = p_registry->get<Transform>(m_parent);
nlp = glm::inverse(p.m_globalTransformM) * nlp;
}
setPosition(nlp);
}
However, I have absolutely no clue how to accomplish the same for the following.
// Transform member functions.
void setGlobalScale(const glm::vec3& newScale);
void alterGlobalScale(const glm::vec3& deltaScale);
void setGlobalOrientation(const glm::quat& newOri);
void alterGlobalOrientation(const glm::quat& deltaOri);
Position coordinates are easy, but how does one approach the problem for rotations and scaling? Any help would be greatly appreciated.
basically I am trying to implement the Hover event on a QPointF, I need it to be clickable or at least being highlighted in some way.
What I've tried is to create a custom point starting by inheriting from QPointF, and then added hover,enter and leave methods as described in this tutorial, but it is not working. Also, the tut is based on generic QWidget and not specifically on points.
have you guys some hints/resources? google points me on a few tuts that are not useful, and the Qt page referred to HoverEvent is not tailored with examples.
thanks!
EDIT
More info needed. I am trying to draw a rectangle in a scene. The rectangle is of type QPolygonF and the scene is a QGraphicsScene type.
below the code block used to create a polygon from a list of points and its inclusion in the scene:
void
MyDialog::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event);
QPolygonF poly;
QPointF first;
for(int i = 0;i < points->size();i++)
{
double length = points->at(i).split(",").at(0).toDouble();
double rad = qDegreesToRadians( points->at(i).split(",").at(1).toDouble());
QPointF pt(length * qCos(rad),length * qSin(rad));
if(i == 0) first = pt;
poly << pt;
}
poly << first;
scene->addPolygon(poly,QPen(),QBrush(Qt::green,Qt::SolidPattern));
}
QPointF is a data object; it does not provide any graphical representation whatsoever (and I would strongly advise you against inheriting from it to add one).
One way to possibly do it is to override the QGraphicsScene::mouseMoveEvent() and QGraphicsScene::mousePressEvent() functions, allowing you to listen-in on where the user moves and clicks, and reacting on that. There are probably better ways to do this, though - I've only needed to react on clicks (and not on hovers) so far, so my experiences are limited.
I'm trying do some 3D animation in GraphicsScene, for example, to rotate pictures in GraphicsScene (using class, subclassed from qPixmapItem and QObject, if it matters) with Animation framework.
Everything works fine, until i want to rotate pictures around vertical axis.
There is no way doing so via item.rotate(), so i'm using QTranform.
The problem is that doing so does not animate anything at all. What am i doing wrong?
P.S. I do not want use OpenGl for this.
Here is the way i'm doing it. This way works for animating simpler properties like pos, rotation(via rotation, setRotation)
My code :
// hybrid graphicsSceneItem, supporting animation
class ScenePixmap : public QObject, public QGraphicsPixmapItem
{
Q_OBJECT
Q_PROPERTY(QTransform transform READ transform WRITE setTransform)
public:
ScenePixmap(const QPixmap &pixmap, QObject* parent = NULL, QGraphicsItem* parentItem = NULL):
QObject(parent),
QGraphicsPixmapItem(pixmap, parentItem)
{}
};
Here is how I setup scene and animation:
//setup scene
//Unrelated stuff, loading pictures, etc.
scene = new QGraphicsScene(this);
foreach(const QPixmap& image, images)
{
ScenePixmap* item = new ScenePixmap(image);
item->moveBy(70*i, 0);
i++;
this->images.append(item);
scene->addItem(item);
}
}
ui->graphicsView->setBackgroundBrush(QBrush(Qt::black, Qt::SolidPattern));
ui->graphicsView->setScene(scene);
//setup animation
QTransform getTransform()
{
QTransform transform;
transform.rotate(-30, Qt::ZAxis);//also tried transform = transform.rotate(...)
return transform;
}
QAbstractAnimation* SetupRotationAnimation(ScenePixmap* pixmapItem)
{
QPropertyAnimation* animation = new QPropertyAnimation(pixmapItem, "transform");
animation->setDuration(1400);
animation->setStartValue( pixmapItem->transform());
animation->setEndValue(getTransform());//here i tried to multiply with default transform , this does not work either
return animation;
}
here is the way i start animation:
void MainWindow::keyPressEvent ( QKeyEvent * event )
{
if((event->modifiers() & Qt::ControlModifier))
{
QAnimationGroup* groupAnimation = new QParallelAnimationGroup();
foreach(ScenePixmap* image, images)
{
groupAnimation->addAnimation( SetupRotationAnimation(image));
}
groupAnimation->start(QAbstractAnimation::DeleteWhenStopped);
}
}
EDIT[Solved] thx to Darko Maksimovic:
Here is the code that worked out for me:
QGraphicsRotation* getGraphicRotation()
{
QGraphicsRotation* transform = new QGraphicsRotation(this);
transform->setAxis(Qt::YAxis);
return transform;
}
QAbstractAnimation* SetupRotationAnimation(ScenePixmap* pixmapItem)
{
QGraphicsRotation* rotation = getGraphicRotation();
QPropertyAnimation* animation = new QPropertyAnimation(rotation, "angle");
animation->setDuration(1400);
animation->setStartValue( 0);
animation->setEndValue(45);
pixmapItem->setTransformOriginPoint(pixmapItem->boundingRect().center());
QList<QGraphicsTransform*> transfromations = pixmapItem->transformations();
transfromations.append(rotation);
pixmapItem->setTransformations(transfromations);
return animation;
}
I see you use QTransform. If you want only one rotation, and simple rotation that is, it is better that you use setRotation [don't forget about setTransformOriginPoint].
If you, however, want to remember many rotations, around different transform points for example, then you should use QGraphicsTransform, i.e. its specialised derived class, QGraphicsRotation, which you apply by calling setTransformations on a graphics object (you should first fetch the existing transformations by calling o.transformations(), append to this, then call setTransformations; if you keep the pointer to the added transformation you can also change it later directly).
From the code you posted I can't see where your error is coming from, but by using specialised functions you can avoid some of frequent problems.
P.S. I also see you didn't use prepareGeometryChange in the code you posted, so please be advised that this is necessary when transforming objects.
The problem is very simple. QVariantAnimation and QPropertyAnimation don't support QTransform. Heck, they don't even support unsigned integers at the moment. That's all there's to it. Per Qt documentation:
If you need to interpolate other variant types, including custom types, you have to implement interpolation for these yourself. To do this, you can register an interpolator function for a given type. This function takes 3 parameters: the start value, the end value and the current progress.
It'd might not be all that trivial to generate such an interpolator. Remember that you have to "blend" between two matrices, while maintaining the orthonormality of the matrix, and the visual effect. You'd need to decompose the matrix into separate rotation-along-axis, scaling, skew, etc. sub-matrices, and then interpolate each of them separately. No wonder the Qt folks didn't do it. It's probably much easier to do the inverse problem: generate the needed transformation matrix as a function of some parameter t, by composing the necessary rotations, translations, etc., all given as (perhaps constant) functions of t.
Just because a QVariant can carry a type doesn't mean it's supported by the default interpolator. There probably should be a runtime warning issued to this effect.
I have been following lazyfoos tutorials on SDL, and I have heavily modified his code to make sort of a ship game, that moves around. I'm trying to make the ship shoot, but i have absolutely no idea how to go about doing this. I have the ship and it's movements and the actual application of the image in a class, and I as wondering if anyone had any techniques or certain ways that are efficient in making the ship shoot, making the shot move independently and then disappearing when it goes off screen. I know that I am giving a vague explanation sort of, but I don't want to be given all of the answers, just a little sample code and a point in the right direction.
Create a class to hold a projectile, with all the information you need in it, such as this:
struct Projectile
{
Vector2 position;
Vector2 velocity;
shared_ptr<Image> graphic;
Time time_until_my_destruction;
bool dead;
void update(Time time_delta) {
if(!dead) {
position += velocity * time_delta;
time_until_my_destruction -= time_delta;
if(time_until_my_destruction < 0.0) dead = true;
}
}
void draw(DrawDest & dest) const {
graphic->draw(dest, position);
}
bool checkCollision(const GameObject & object) const {
return object.area().contains(position);
}
};
This class is not complete obviously, you'll probably want to make adjustments to access levels, and write some constructors and other things, but it should give you the basic idea.
Make a container of those. When the ship fires, put one into the container. Each frame, call update, draw, check if the projectile is dead and check for collisions against the game objects. If a collision occurs, apply damage or whatever. If the object is dead, remove it from the container.
I can only absolutely recommend Aaron's Game Programming Tutorials, it uses C++ and SDL.