Panning camera so object is at edge of screen - math

I'm trying to write a formula to pan the camera enough so that the center point of an object is just visible at the edge of the screen.
So, in other words if the object is out of view to the right, I would change the x and y position of the camera, so that the object is just at the right edge of the screen (without changing camera angle or z co-ordinate).
Can anyone give me any hints how to do this?

I figured out a solution that serves my purpose, but the only way I could do it was by modifying the camera height (which is not exactly what I wanted):
// note: pos is the center of the object that is to appear at edge of screen
// m_position is the 3d position of the camera
// m_plane[] is array of left, right, etc. planes of camera fustrum
void Camera::PanTo(const Vector3D& pos)
{
int n;
Vector3D vectorNearest;
for (n = 0; n < NUM_PLANES; n++)
{
if (m_plane[n].GetDistance(pos) < 0)
{
m_plane[n].Normalize();
vectorNearest += m_plane[n].GetVectorNearest(pos);
}
}
m_posDesire.m[IX_X] = m_position.m[IX_X] + vectorNearest.m[IX_X];
m_posDesire.m[IX_Y] = m_position.m[IX_Y] + vectorNearest.m[IX_Y];
m_posDesire.m[IX_Z] = m_position.m[IX_Z] + vectorNearest.m[IX_Z];
}
// This is the definition of the Plane class:
class Plane
{
public:
void Normalize()
{
float lenInv = 1.0f/sqrtf(m_a*m_a + m_b*m_b + m_c*m_c);
m_a *= lenInv;
m_b *= lenInv;
m_c *= lenInv;
m_d *= lenInv;
}
float GetDistance(const Vector3D& pos) const
{
return m_a*pos.m[IX_X] + m_b*pos.m[IX_Y] +
m_c*pos.m[IX_Z] + m_d;
}
Vector3D GetVectorNearest(const Vector3D& pos) const
{
Vector3D normal(m_a, m_b, m_c);
float posDotNormal = pos.dotProduct(normal);
Vector3D nearest = normal*(m_d+posDotNormal);
return nearest;
}
float m_a, m_b, m_c, m_d;
};

Related

how to avoid pixmap cutting issues when we using qpainter while rotation

label=new QLabel(this);
label->setGeometry(this->width()/2,this->height()/2,label->width(),label->height());
QPixmap myPixmapForNow;
myPixmapForNow.load("C://Users//abc//Documents//QpixMap//hub_needle.png");
label->setMinimumSize(QSize(myPixmapForNow.width(),myPixmapForNow.width()));
label->setAlignment(Qt::AlignCenter);
QPixmap rotated(label->width(),label->width());
QPainter p(&rotated);
p.setRenderHint(QPainter::Antialiasing);
p.setRenderHint(QPainter::SmoothPixmapTransform);
p.setRenderHint(QPainter::HighQualityAntialiasing);
p.translate(myPixmapForNow.size().width() / 2,
(myPixmapForNow.size().height() / 2));
qDebug()<<"before rotation width:"<<rotated.size().width()<<"height:"<<rotated.size().width();
p.rotate(arg1);
p.translate(-myPixmapForNow.size().width() / 2,
-(myPixmapForNow.size().height() / 2));
qDebug()<<"after rotation height:"<<-rotated.size().width()<<"height:"<<-rotated.size().height();[![enter image description here][1]][1]
p.drawPixmap(QRect(0,0,myPixmapForNow.width(),myPixmapForNow.height()), myPixmapForNow);
p.end();
label->setPixmap(rotated);
After rotation
before rotation
I must admit the OP could have explained the issue a bit more in detail. Unfortunately, OP didn't react on comments.
However, out of curiosity, I tried to puzzle this out in a little demo. (I really like to write little Qt demos, especially with image manipulation and cat pictures.)
My first assumption was that OP has struggled with the order of transformations.
While translations are commutative (changing order doesn't change result), this is not the case for rotations (and other transformations).
However, after having wrapped OPs code into a MCVE, I convinced myself that the order of transformations matched my expectation – a rotation about the center of image.
So, I focused on the title
how to avoid pixmap cutting issues when we using qpainter while rotation
The reason for the “cutting issue” is simple:
To paint a rotated image (rectangle), the output may require a greater range of pixels then the original.
There are two possibilities to fix this:
enlarge the QPixmap for output
scale the result to match the original size of QPixmap.
So, this leaves the task to determine the output size of the rotated image beforehand, to either make the output QPixmap respectively larger or to add the respective scaling.
The bounding rectangle of a rotated rectangle can be calculated with trigonometric functions (sin, cos, etc.) I decided instead (for an IMHO more naïve way) to let Qt do the work for me.
To achieve this, the transformation has to be calculated before creating the QPixmap and QPainter. Hence, the prior
qPainter.translate(cx, cy);
qPainter.rotate(ra);
qPainter.translate(-cx, -cy);
is replaced by:
QTransform xform;
xform.translate(cx, cy);
xform.rotate(ra);
xform.translate(-cx, -cy);
which can be later applied as is:
qPainter.setTransform(xform);
I used the fact that all four corners of the rotated rectangle will touch the bounding rectangle. So, the bounding rectangle can be calculated by applying min() and max() to the x and y components of the rotated image corners:
const QPoint ptTL = xform * QPoint(0, 0);
const QPoint ptTR = xform * QPoint(w - 1, 0);
const QPoint ptBL = xform * QPoint(0, h - 1);
const QPoint ptBR = xform * QPoint(w - 1, h - 1);
QRect qRectBB(
QPoint(
min(ptTL.x(), ptTR.x(), ptBL.x(), ptBR.x()),
min(ptTL.y(), ptTR.y(), ptBL.y(), ptBR.y())),
QPoint(
max(ptTL.x(), ptTR.x(), ptBL.x(), ptBR.x()),
max(ptTL.y(), ptTR.y(), ptBL.y(), ptBR.y())));
Afterwards, the output may be adjusted using the origin and size of qRectBB.
The whole demo application testQPainterRotateCenter.cc:
#include <algorithm>
// Qt header:
#include <QtWidgets>
int min(int x0, int x1, int x2, int x3)
{
return std::min(std::min(x0, x1), std::min(x2, x3));
}
int max(int x0, int x1, int x2, int x3)
{
return std::max(std::max(x0, x1), std::max(x2, x3));
}
QPixmap rotate(
const QPixmap &qPixMapOrig, int cx, int cy, int ra,
bool fitIn, bool keepSize)
{
int w = qPixMapOrig.width(), h = qPixMapOrig.height();
QTransform xform;
xform.translate(cx, cy);
xform.rotate(ra);
xform.translate(-cx, -cy);
if (fitIn) {
// find bounding rect
const QPoint ptTL = xform * QPoint(0, 0);
const QPoint ptTR = xform * QPoint(w - 1, 0);
const QPoint ptBL = xform * QPoint(0, h - 1);
const QPoint ptBR = xform * QPoint(w - 1, h - 1);
QRect qRectBB(
QPoint(
min(ptTL.x(), ptTR.x(), ptBL.x(), ptBR.x()),
min(ptTL.y(), ptTR.y(), ptBL.y(), ptBR.y())),
QPoint(
max(ptTL.x(), ptTR.x(), ptBL.x(), ptBR.x()),
max(ptTL.y(), ptTR.y(), ptBL.y(), ptBR.y())));
qDebug() << "Bounding box:" << qRectBB;
// translate top left corner to (0, 0)
xform *= QTransform().translate(-qRectBB.left(), -qRectBB.top());
if (keepSize) {
// center align scaled image
xform *= w > h
? QTransform().translate((w - h) / 2, 0)
: QTransform().translate(0, (h - w) / 2);
// add scaling to transform
const qreal sx = qreal(w) / qRectBB.width();
const qreal sy = qreal(h) / qRectBB.height();
const qreal s = std::min(sx, sy);
xform *= QTransform().scale(s, s);
} else {
// adjust w and h
w = qRectBB.width(); h = qRectBB.height();
}
}
QPixmap qPixMap(w, h);
qPixMap.fill(Qt::gray);
{ QPainter qPainter(&qPixMap);
qPainter.setRenderHint(QPainter::Antialiasing);
qPainter.setRenderHint(QPainter::SmoothPixmapTransform);
qPainter.setRenderHint(QPainter::HighQualityAntialiasing);
qPainter.setTransform(xform);
qPainter.drawPixmap(0, 0, qPixMapOrig.width(), qPixMapOrig.height(), qPixMapOrig);
} // end of scope -> finalize QPainter
return qPixMap;
}
// main application
int main(int argc, char **argv)
{
qDebug() << "Qt Version:" << QT_VERSION_STR;
QApplication app(argc, argv);
// setup data
const QString file = QString::fromUtf8("cats.jpg");
QPixmap qPixMapOrig;
qPixMapOrig.load(file);
int cx = qPixMapOrig.width() / 2, cy = qPixMapOrig.height() / 2;
int ra = 0;
// setup GUI
QWidget qWin;
qWin.setWindowTitle(
file % QString(" (")
% QString::number(qPixMapOrig.width())
% " x " % QString::number(qPixMapOrig.height())
% ") - testQPainterRotateCenter");
QVBoxLayout qVBox;
QHBoxLayout qHBox1;
QLabel qLblCX(QString::fromUtf8("center x:"));
qHBox1.addWidget(&qLblCX);
QLineEdit qEditCX;
qEditCX.setText(QString::number(cx));
qHBox1.addWidget(&qEditCX, 1);
QLabel qLblCY(QString::fromUtf8("center y:"));
qHBox1.addWidget(&qLblCY);
QLineEdit qEditCY;
qEditCY.setText(QString::number(cy));
qHBox1.addWidget(&qEditCY, 1);
QLabel qLblRA(QString::fromUtf8("rotation angle:"));
qHBox1.addWidget(&qLblRA);
QSpinBox qEditRA;
qEditRA.setValue(ra);
qHBox1.addWidget(&qEditRA, 1);
qVBox.addLayout(&qHBox1);
QHBoxLayout qHBox2;
QCheckBox qTglFitIn(QString::fromUtf8("Zoom to Fit"));
qTglFitIn.setChecked(false);
qHBox2.addWidget(&qTglFitIn);
QCheckBox qTglKeepSize(QString::fromUtf8("Keep Size"));
qTglKeepSize.setChecked(false);
qHBox2.addWidget(&qTglKeepSize);
qVBox.addLayout(&qHBox2);
QLabel qLblImg;
qLblImg.setPixmap(qPixMapOrig);
qLblImg.setAlignment(Qt::AlignCenter);
qVBox.addWidget(&qLblImg, 1);
qWin.setLayout(&qVBox);
qWin.show();
// helper to update pixmap
auto update = [&]() {
cx = qEditCX.text().toInt();
cy = qEditCY.text().toInt();
ra = qEditRA.value();
const bool fitIn = qTglFitIn.isChecked();
const bool keepSize = qTglKeepSize.isChecked();
QPixmap qPixMap = rotate(qPixMapOrig, cx, cy, ra, fitIn, keepSize);
qLblImg.setPixmap(qPixMap);
};
// install signal handlers
QObject::connect(&qEditCX, &QLineEdit::textChanged,
[&](const QString&) { update(); });
QObject::connect(&qEditCY, &QLineEdit::textChanged,
[&](const QString&) { update(); });
QObject::connect(&qEditRA, QOverload<int>::of(&QSpinBox::valueChanged),
[&](int) { update(); });
QObject::connect(&qTglFitIn, &QCheckBox::toggled,
[&](bool) { update(); });
QObject::connect(&qTglKeepSize, &QCheckBox::toggled,
[&](bool) { update(); });
// runtime loop
return app.exec();
}
The Qt project file testQPainterRotateCenter.pro:
SOURCES = testQPainterRotateCenter.cc
QT += widgets
Output:
The rotated image without zoom to fit:
Zoomed to fit:
Zoomed to fit original size:
Notes:
While fiddling originally with a square image of 300×300 pixels, I became aware that rotating a non-square rectangle may result in a bounding box with a different aspect-ratio than the original. Hence, an additional translation might be desirable to align the scaled output in the original bounding box again. I switched to a non-square sample image of 300×200 pixels to illustrate this.
With the fit in calculations, the translations before/after rotation are actually obsolete. The result will be translated in any case to the intended position.
Instead of Qt::gray, the “background color” (i.e. the color the QPixmap is filled with initially) might be set complete transparently. I decided to stick to the Qt::gray for illustration.

Mapping boundingRect()'s to parent item and intersecting yields unexpected results for collision handling

Here is how I'm handling collision of two graphics items. It gets called in mouseMoveEvent as well as itemChange. Like so:
void GraphicsItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
auto delta = event->pos() - event->lastPos();
auto new_pos = handleCollisions(pos() + delta);
setPos(new_pos);
}
The new_pos thing is so I can call it in itemChange and return that as the value. However, this version of collision handling leaves new_pos unchanged. Instead, we move the items that collide with this one (as we move this one by mouse). I'm trying to take the intersection of the two involved bounding rects and shifting the other one by the shorter dimension of the rectangls' intersection. The result, which is unexpected, is that the rectangles aren't moving just that little amount, but instead by the entire width (or height) of either of the boundingRects() (they're the same size). I've tracked it down to that, so the code below that computes the intersection of the bounding rects in the parent's position space (where pos(), setPos() are relative to), is wrong.
So how do I do I accomplish this?
Here is a picture of what I want:
QPointF GraphicsItem::handleCollisions(const QPointF& new_pos) const {
auto collisions = collidingItems();
auto rect = mapToParent(boundingRect().translated(new_pos - pos())).boundingRect();
for(int k=0; k < collisions.count(); k++)
{
auto item = collisions[k];
if (item->parentItem() == parentItem()) {
auto rect1 = mapToParent(item->boundingRect()).boundingRect();
rect1 = rect1.intersected(rect);
qDebug() << (rect1.width());
qDebug() << (rect1.height());
auto v = rect1.center() - rect.center();
if (v.x() >= 0) {
if (v.y() >= 0) {
if (rect1.width() <= rect1.height())
item->setPos(item->pos() + QPointF(-rect1.width(), 0));
else
item->setPos(item->pos() + QPointF(0, -rect1.height()));
}
else {
if (rect1.width() <= rect1.height())
item->setPos(item->pos() + QPointF(rect1.width(), 0));
else
item->setPos(item->pos() + QPointF(0, rect1.height()));
}
}
else {
if (v.y() >= 0) {
if (rect1.width() <= rect1.height())
item->setPos(item->pos() + QPointF(rect1.width(), 0));
else
item->setPos(item->pos() + QPointF(0, -rect1.height()));
}
else {
if (rect1.width() <= rect1.height())
item->setPos(item->pos() + QPointF(rect1.width(), 0));
else
item->setPos(item->pos() + QPointF(0, rect1.height()));
}
}
}
}
return new_pos; // return this position unchanged
}
I have found it.
item->mapToParent(...)
instead of
mapToParent(...)
should be called in the loop.

Bounding rect to the complex form?

The subjects of our project is making a program who simulate a Fusion.
We have some problem with the colliding with our classe Fusion. We want to make a shape complex for our colliding.
printScreenFusionProgramm
Our shape is two circle near each other and we dont want to have a bounding rect but shape "complex"...
this is our Fusion class
Fusion::Fusion(int x, int y)
{
this->setPos(x, y);
}
void Fusion::shape(){
//...
}
void Fusion::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
//set the color
QBrush brushColor(QColor(Qt::blue));
painter->setBrush(brushColor);
painter->setPen(QColor(Qt::blue));
painter->drawEllipse(0,0,40,40);
painter->drawEllipse(-20,-20,40,40);
}
void Fusion::doCollision()
{
// get a new position
// change the angle with randomness
if(qrand() %1)
{
setRotation(rotation() + (180 + (qrand() % 10)));
}
else
{
setRotation(rotation() + (180 + (qrand() % -10)));
}
// check if the new position is in bounds
QPointF newPoint = mapToParent(-(boundingRect().width()), -(boundingRect().width() + 2));
if(!scene()->sceneRect().contains((newPoint)))
{
// move back in bounds
newPoint = mapToParent(0,0);
}
else
{
// set the new position
setPos(newPoint);
}
}
void Fusion::advance(int step)
{
if(!step) return;
if(!scene()->collidingItems(this).isEmpty())
{
doCollision();
}
setPos(mapToParent(0, -1));
}
You need to reimplement the "shape" method for your graphics items to return the actual shape of your object. You can return any shape you want in a QPainterPath, and Qt will use that for collision detection.

How can i change order of widget in layout by drag and drop with the mouse?

I make my own class from QWidget with redefine of paintEvent(), mousePressEvent(), mouseReleaseEvent() and mouseMoveEvent(). All that methods for move widgets over other widget (yellow).
When i create my widgets in a layout, it looks like this:
But when I move black widget to the bottom and red to the top like this:
and resize window, all widgets refresh to their align positions:
But i want, when i move one widget higher then another, the widgets should align in layout in new places, like this:
Which function i should redefine to do it?
P.S.
There is a piece of code, that can move widgets positions inside layout (change their indexes), but i don't know how find out their (x,y) position to calculate new indexes in layout. I think, that i can do it in resizeEvent().
But it when it event was emitted, positions already changed to old. (like before moveing on 1 picture), and i need positions after moveing (like on secon picture). How can i get position of widget before it will be aligned?
or How can i change order of widget in layout by drag and drop with the mouse?
I write my own widget, then redefine following methods: mouseReleaseEvent(), paintEvent(), mousePressEvent(), mouseMoveEvent(). In mousePressEvent() I hold old X and Y positions and mouse position on figure. Then in mouseMoveEvent() i calculate if minimum distance of mouse move is riched and move widget to new position (it not moves widget index in layout). After it, if emitted mouseReleaseEvent() i just calculate new index of moving widget and change and update parent layout. If widget moves less then it height, then layout just updates without changing widget index.
void SimpleWidget::mouseMoveEvent(QMouseEvent *event)
{
if (!(event->buttons() & Qt::LeftButton))
return;
if (!IsMinimumDistanceRiched(event))
{
return;
}
int y = event->globalY() - mouseClickY + oldY;
int BottomBorder = parentWidget->geometry().height() - this->geometry().height();
if(y < 0) y = 0;
else if(y > BottomBorder) y = BottomBorder;
move(oldX, y);
}
void SimpleWidget::mousePressEvent(QMouseEvent *event)
{
if (event->buttons() & Qt::LeftButton)
dragStartPosition = event->pos();
oldX = this->geometry().x();
oldY = this->geometry().y();
mouseClickX = event->globalX();
mouseClickY = event->globalY();
}
bool SimpleWidget::IsMinimumDistanceRiched(QMouseEvent *event)
{
return (event->pos() - dragStartPosition).manhattanLength() >= QApplication::startDragDistance();
}
bool SimpleWidget::moveInLayout(QWidget *widget, MoveDirection direction)
{
QVBoxLayout* myLayout = qobject_cast<QVBoxLayout*>(widget->parentWidget()->layout());
const int index = myLayout->indexOf(widget);
if (direction == MoveUp && index == 0)
{
return false;
}
if (direction == MoveDown && index == myLayout->count()-1 )
{
return false;
}
const int newIndex = direction == MoveUp ? index - 1 : index + 1;
myLayout->removeWidget(widget);
myLayout->insertWidget(newIndex , widget);
return true;
}
void SimpleWidget::paintEvent(QPaintEvent *)
{
QStyleOption o;
o.initFrom(this);
QPainter p(this);
style()->drawPrimitive(QStyle::PE_Widget, &o, &p, this);
}
void SimpleWidget::mouseReleaseEvent(QMouseEvent *)
{
int y = geometry().y();
MoveDirection direct;
int offset;
if(oldY > y)
{
offset = oldY - y;
direct = MoveUp;
}
else if(oldY < y)
{
offset = y - oldY;
direct = MoveDown;
}
int count = offset/height();
for(int i = 0; i < count; i++)
{
moveInLayout(this, direct);
}
update();
QVBoxLayout* myLayout = qobject_cast<QVBoxLayout*>(this->parentWidget->layout());
myLayout->update();
this->saveGeometry();
}

Set zoom out limit for a picture in QGraphicsView

I am zooming an image on a label using QGraphicsView. But when I zoom out I want to set a specific limit for the zoom out. I am using the following code
scene = new QGraphicsScene(this);
view = new QGraphicsView(label);
QPixmap pix("/root/Image);
Scene->addPixmap(pixmap);
view->setScene(scene);
view->setDragMode(QGraphicsView::scrollHandDrag);
In the slot of wheel event
void MainWindow::wheelEvent(QWheelEvent *ZoomEvent)
{
view->setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
double scaleFactor = 1.15;
if(ZoomEvent->delta() >0)
{
view->scale(scaleFactor,scaleFactor);
}
else
{
view->scale(1/scaleFactor,1/scaleFactor);
}
}
I want that the image should not zoom out after a certain extent. What should I do?
I tried setting the minimum size for QGraphicsView but that didn't help.
Thank You :)
Maybe something like this would help:
void MainWindow::wheelEvent(QWheelEvent *ZoomEvent)
{
view->setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
static const double scaleFactor = 1.15;
static double currentScale = 1.0; // stores the current scale value.
static const double scaleMin = .1; // defines the min scale limit.
if(ZoomEvent->delta() > 0) {
view->scale(scaleFactor, scaleFactor);
currentScale *= scaleFactor;
} else if (currentScale > scaleMin) {
view->scale(1 / scaleFactor, 1 / scaleFactor);
currentScale /= scaleFactor;
}
}
The idea, as you can see, is caching the current scale factor and do not zoom out in case of it smaller than some limit.
Here is one more implementation idea in which the current zoom factor is obtained from the transformation matrix for the view:
void View::wheelEvent(QWheelEvent *event) {
const qreal detail = QStyleOptionGraphicsItem::levelOfDetailFromTransform(transform());
const qreal factor = 1.1;
if ((detail < 10) && (event->angleDelta().y() > 0)) scale(factor, factor);
if ((detail > 0.1) && (event->angleDelta().y() < 0)) scale((1 / factor), (1 / factor));
}
Another way to solve the problem:
void GraphWidget::wheelEvent(QWheelEvent *event)
{
int scaleFactor = pow((double)2, - event->delta() / 240.0);
qreal factor = transform().scale(scaleFactor, scaleFactor).mapRect(QRectF(0, 0, 1, 1)).width();
if (factor < 0.5 || factor > 20)
return;
scale(scaleFactor, scaleFactor);
}

Resources