Problems keeping orthographic projection proportional using Qt/OpenGL - qt

I am having a difficult time understanding what values I should use in the call I make to QMatrix4x4::ortho(left, right, bottom, top, near, far).
Specifically, I don't understand the documentation for the values for left,right,bottom, and top. I have a working demo that can draw the OpenGL monkey with a projection matrix. I set up the demo so that when I hit 'O' on the keyboard, it switches the projection matrix from perspective to orthographic. The perspective projection works well in that it keeps the model's aspect ratio constant (i.e. it doesn't stretch in width or height). Here's the function that gets called when either the 'O' or 'P' key gets called and updates m_proj. It's a bit of a hot mess as you can see I've tried a bunch of ideas and none of them really works the way I would like.
Thanks for any insight that would help me understand this. Other helpful details: my view eye is at z=2 facing center (0,0,0) with up being (0,1,0).
void AppGLScene::setProjectionMatrix(void)
{
m_projectionMatrix.setToIdentity();
float windowWidth = rect().width();
float windowHeight = rect().height();
float left, right, bottom, top;
float aratio = (float) windowWidth / (float) windowHeight;
qDebug() << "win wid, win hei" << windowWidth << windowHeight;
// I modify the vertical FOV in an attempt to keep the size of the
// model the same as the vertical size of the window changes.
//
float vFov = 90 * ((float)windowHeight / m_initialWinHeight);
qDebug() << "vFov" << vFov;
switch (m_proj)
{
case PROJ_PERSP:
m_projectionMatrix.perspective(vFov, qreal(windowWidth)/qreal(windowHeight), 0.5, 40);
break;
case PROJ_ORTHO:
default:
// left = rect().x();
// right = rect().x() + rect().width();
// bottom = rect().y();
// top = rect().y() + rect().height();
if (windowWidth > windowHeight)
{
left = -(3.0 - ((float)windowHeight/(float)windowWidth));
right = -left;
bottom = -3.0;
top = 3.0;
}
else
{
left = -3.0;
right = 3.0;
bottom = -(3.0 - ((float)windowWidth/(float)windowHeight));
top = -bottom;
}
qDebug() << "l r b t = " << left << right << bottom << top;
m_projectionMatrix.ortho(left, right, bottom, top, 0.5, 40);
// m_projectionMatrix.ortho(-3.0, 3.0, -3.0, 3.0, 0.5, 40);
// m_projectionMatrix.ortho(-aratio, aratio, -aratio, aratio, 0.5, 40);
break;
}
}

To avoid stretching your objects in either direction, you need to have (right - left) / (top - bottom) match the aspect ratio of the window. Which in your case you can ensure by having right be the value of top multiplied by the aspect ratio.
It looks like you want to use the range [-3.0, 3.0] for the shorter window dimension, and adjust the longer one accordingly. The above then translates into:
if (windowWidth > windowHeight)
{
top = 3.0f;
bottom = -top;
right = top * aratio;
left = -right;
}
else
{
right = 3.0f;
left = -right;
top = right / aratio;
bottom = -top;
}
Note that right / top = aratio for both cases.

Related

How to align to the a rectangle to the left of a QGraphicsView without scaling the scene?

Here is what I want to do.
I need to draw in a QGraphicsView a series of rectangles that are aligned left and right. By this I mean that if rectangle i has posion (0,y) rectangle i+1 needs to have position (0,max) where max is such that the right side of the rectangle "touches" the right side of the QGraphicsView.
When the window is resized I need to recalculate the value of max such that the rectangle is always touching the right side of the screen.
Here is how I add my scene (this references a class that inherits QGraphicsView)
scene = new QGraphicsScene(this);
this->setScene(scene);
this->setAlignment(Qt::AlignTop|Qt::AlignLeft);
To add a rectangle that touches the left border I add it a (0,yvalue,width,height).
How do I calculate the value of X so that that the rectangle will touch the right border?
Ok. So this should go in the resize event of the QGraphicsView. But this does what I wanted:
void AView::resized(){
scene->clear();
QSize newsize = this->size();
qreal w = newsize.width()*.99;
qreal h = newsize.height()*.99;
this->setSceneRect(0,0,w,h);
scene->setSceneRect(0,0,w,h);
qreal rectwidth = 100;
qreal newx = w - rectwidth;
left = new QGraphicsRectItem(0,0,rectwidth,100);
left->setBrush(QBrush(Qt::red));
right = new QGraphicsRectItem(newx,100,rectwidth,100);
right->setBrush(QColor(Qt::blue));
scene->addItem(left);
scene->addItem(right);
}
In this way the blue rectangle is pretty much always at the border. Aslo there is no stretching of the image. There is a gap between the right borders which increases due to the window resizing, but it seems that the size returned by this->size() is slightly larger than the "white area" that you see on screen. Adding the .99 gives a much better results from my experiments.
This little example should calculate the shift and move all items from a selection or list of items, based on alignment you desire (I showed right, but let, center, top, bottom would be the same).
QRectF refRect = scene()->sceneRect();
QList<QGraphicsItem*> sel = allItemsYouWantAligned; // scene()->selectedItems(); for example
foreach(QGraphicsItem* selItem, sel)
{
qreal dx = 0, dy = 0;
QRectF itemRect = selItem->mapToScene(selItem->boundingRect()).boundingRect();
if(align_right)
dx = refRect.right() - itemRect.right();
else
.. calculate either dx or dy on how you want to align
selItem->moveBy(dx, dy);
}
Re-reading the question - I see you don't really need to move rectangle, but create new larger and larger rectangles.
Your solution is simple enough. If you want to resize instead of move -
you would have to setRect() on your items by dx increment:
QRectF r = selItem->rect();
r.setWidth(r.width + dx);
selItem->setRect(r);

Qt: resizable and movable main window without title bar

I need draw a Qt-window without the title bar. Generally this can be easy by just setting the CustomizeWindowHint window attribute. But then window has fixed size and position.
I need a movable and resizable window. This post shows how to move the window manually so I wonder whether there is a way to at least keep the borders resizable.
Of course I can also resize the window manually but I expect jumping windows and many sorts of weird behavior so I hope there is a way to use the existing functionality
From the docs on Qt::CustomizeWindowHint:
This flag must be set to allow the WindowTitleHint, WindowSystemMenuHint, WindowMinimizeButtonHint, WindowMaximizeButtonHint and WindowCloseButtonHint flags to be changed.
Basically, Qt::CustomizeWindowHint is not meant to be used without other title bar hints.
Use Qt::FramelessWindowHint in conjunction with some event reimplentation for dragging (see the solution to your other question). You can get a size grip to show like so:
auto statusBarWidget = statusBar();
statusBarWidget->setSizeGripEnabled(true);
You may need to do some work with the size grip and status bar to work with the frameless window flag set.
Edit: since you aren't using a statusbar, add a "hotspot" in your central widget to start resizing instead of moving, i.e. check if the mouse is in the bottom right corner of the window and set the cursor appropriately. If they click, begin resizing instead of moving. This can all be done by reimplementing the mouse events.
Edit: I got it
There is some jumping behavior but this is a working manual resize/move of window without a title bar I implemented for my personal project:
//Below two methods partially from Qt Shaped Clock example
void MainWindow::mousePressEvent(QMouseEvent *event){
//From Qt Documentation:
//Reason why pos() wasn't working is because the global
//position at time of event may be very different
//This is why the mpos = event->pos(); line before was
//possibly causing jumping behavior
if (event->button() == Qt::LeftButton){
//Coordinates have been mapped such that the mouse position is relative to the
//upper left of the main window
mpos = event->globalPos() - frameGeometry().topLeft();
//At the moment of mouse click, capture global position and
//lock the size of window for resizing
global_mpos = event->globalPos();
storeWidth = this->width();
storeHeight= this->height();
event->accept();
}
}
void MainWindow::mouseMoveEvent(QMouseEvent *event){
//mapped mouse relative to upper left of window
rs_mpos=event->globalPos()-frameGeometry().topLeft();//general position tracker for resizing
QTextStream out(stdout);
//How much of the corner is considered a "resizing zone"
//I was experiencing jumping behavior with rs_size is 10 so
//I recommend rs_size=50
int rs_size=50;
//Big if statement checks if your mouse is in the upper left,
//upper right, lower left, and lower right
if ( (abs(rs_mpos.x()) < rs_size && abs(rs_mpos.y()) < rs_size) ||
(abs(rs_mpos.x()) > this->width()-rs_size && abs(rs_mpos.y()) <rs_size) ||
(abs(rs_mpos.x()) < rs_size && abs(rs_mpos.y())> this->height()-rs_size) ||
(abs(rs_mpos.x()) > this->width()-rs_size && abs(rs_mpos.y())> this->height()-rs_size)
){
//Below for debugging
/*
out << rs_mpos.x() << " , " << rs_mpos.y() << "\n";
out << "window: " << this->width() << " , " << this->height() << "\n";
out << "globalpos: " << event->globalPos().x() << " , "
<< event->globalPos().y() << "\n";
*/
//Use 2x2 matrix to adjust how much you are resizing and how much you
//are moving. Since the default coordinates are relative to upper left
//You cannot just have one way of resizing and moving the window.
//It will depend on which corner you are referring to
//adjXfac and adjYfac are for calculating the difference between your
//current mouse position and where your mouse was when you clicked.
//With respect to the upper left corner, moving your mouse to the right
//is an increase in coordinates, moving mouse to the bottom is increase
//etc.
//However, with other corners this is not so and since I chose to subtract
//This difference at the end for resizing, adjXfac and adjYfac should be
//1 or -1 depending on whether moving the mouse in the x or y directions
//increases or decreases the coordinates respectively.
//transXfac transYfac is to move the window over. Resizing the window does not
//automatically pull the window back toward your mouse. This is what
//transfac is for (translate window in some direction). It will be either
//0 or 1 depending on whether you need to translate in that direction.
//Initiate matrix
int adjXfac=0;
int adjYfac=0;
int transXfac=0;
int transYfac=0;
//Upper left corner section
if ( (abs(rs_mpos.x()) < rs_size && abs(rs_mpos.y()) < rs_size)){
this->setCursor(Qt::SizeFDiagCursor);
//Upper left. No flipping of axis, no translating window
adjXfac=1;
adjYfac=1;
transXfac=0;
transYfac=0;
}
//Upper right corner section
else if(abs(rs_mpos.x()) > this->width()-rs_size &&
abs(rs_mpos.y()) <rs_size){
this->setCursor(Qt::SizeBDiagCursor);
//upper right. Flip displacements in mouse movement across x axis
//and translate window left toward the mouse
adjXfac=-1;
adjYfac=1;
transXfac = 1;
transYfac =0;
}
//Lower left corner section
else if(abs(rs_mpos.x()) < rs_size &&
abs(rs_mpos.y())> this->height()-rs_size){
this->setCursor(Qt::SizeBDiagCursor);
//lower left. Flip displacements in mouse movement across y axis
//and translate window up toward mouse
adjXfac=1;
adjYfac=-1;
transXfac=0;
transYfac=1;
}
//Lower right corner section
else if(abs(rs_mpos.x()) > this->width()-rs_size &&
abs(rs_mpos.y())> this->height()-rs_size){
this->setCursor(Qt::SizeFDiagCursor);
//lower right. Flip mouse displacements on both axis and
//translate in both x and y direction left and up toward mouse.
adjXfac=-1;
adjYfac=-1;
transXfac=1;
transYfac=1;
}
if (event->buttons()==Qt::LeftButton ){
//Calculation of displacement. adjXfac=1 means normal displacement
//adjXfac=-1 means flip over axis
int adjXdiff = adjXfac*(event->globalPos().x() - global_mpos.x());
int adjYdiff = adjYfac*(event->globalPos().y() - global_mpos.y());
//if transfac is 1 then movepoint of mouse is translated
QPoint movePoint(mpos.x() - transXfac*adjXdiff, mpos.y()-transYfac*adjYdiff);
move(event->globalPos()-movePoint);
resize(storeWidth-adjXdiff, storeHeight-adjYdiff);
event->accept();
}
}
//in any move event if it is not in a resize region use the default cursor
else{
this->setCursor(Qt::ArrowCursor);
//simple move section
if (event->buttons()==Qt::LeftButton &&
resizeZone==false){
move(event->globalPos() - mpos);
event->accept();
}
}
}
Set these in your header file
private:
QPoint mpos; //For dragging, relative mouse position to upper left
QPoint global_mpos; //For resizing, global mouse position at mouse click
QPoint rs_mpos; //for resizing
int storeWidth; //fix window size at mouseclick for resizing
int storeHeight;

Zooming to point of interest

I have the following variables:
Point of interest which is the position(x,y) in pixels of the place
to focus.
Screen width,height which are the dimensions of the window.
Zoom level which sets the zoom level of the camera.
And this is the code I have so far.
void Zoom(int pointOfInterestX,int pointOfInterstY,int screenWidth,
int screenHeight,int zoomLevel)
{
glScalef(1,1,1);
glTranslatef( (pointOfInterestX/2) - (screenWidth/2), (pointOfInterestY/2) - (screenHeight/2),0);
glScalef(zoomLevel,zoomLevel,1);
}
And I want to do zoom in/out but keep the point of interest in the middle of the screen. but so far all of my attempts have failed.
You can start the rendering of your frame like this:
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
GLdouble left = (0 - pointOfInterestX) / zoomLevel + pointOfInterestX;
GLdouble right = (WindowW - pointOfInterestX) / zoomLevel + pointOfInterestX;
GLdouble bottom = (WindowH - pointOfInterestY) / zoomLevel + pointOfInterestY;
GLdouble top = (0 - pointOfInterestY) / zoomLevel + pointOfInterestY;
glOrtho(left, right, bottom, top, -1, 1);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();

How do I allow a user to smoothly resize elements in Flex 3

I have a Flex 3 app that has elements that a user can add to the main canvas then resize and reposition.
There are 3 key functions I am using for the resize which are as follows:
When the resize begins:
private function startResize(event:MouseEvent):void
{
RESIZE_START_MOUSE_X = event.localX;
RESIZE_START_MOUSE_Y = event.localY;
RESIZE_START_WIDTH = this.width;
RESIZE_START_HEIGHT = this.height;
RESIZE_START_X = this.x;
RESIZE_START_Y = this.y;
RESIZE_BOUND = calculateResizeBound(event);
addEventListener(MouseEvent.MOUSE_MOVE, resizeMouseHandler);
isResizing = true;
}
When the resize is complete:
private function endResize():void
{
RESIZE_START_MOUSE_X = -1;
RESIZE_START_MOUSE_Y = -1;
RESIZE_START_WIDTH = this.width;
RESIZE_START_HEIGHT = this.height;
RESIZE_START_X = -1;
RESIZE_START_Y = -1;
RESIZE_BOUND = '';
removeEventListener(MouseEvent.MOUSE_MOVE, resizeMouseHandler);
isResizing = false;
}
Whilst the user is resizing:
private function resizeMouseHandler(event:MouseEvent):void
{
var deltaX:Number = event.localX - RESIZE_START_MOUSE_X;
var deltaY:Number = event.localY - RESIZE_START_MOUSE_Y;
if (RESIZE_BOUND.indexOf('T') > -1)
//We are fixing the top so move the bottom edge
{
this.height = RESIZE_START_HEIGHT + deltaY;
}
if (RESIZE_BOUND.indexOf('B') > -1)
//We are fixing the bottom so move the top edge
{
this.y = RESIZE_START_Y + deltaY;
this.height = RESIZE_START_HEIGHT - deltaY;
}
if (RESIZE_BOUND.indexOf('L') > -1)
//We are fixing the left so move the right edge
{
this.width = RESIZE_START_WIDTH + deltaX;
}
if (RESIZE_BOUND.indexOf('R') > -1)
//We are fixing the right so move the left edge
{
this.x = RESIZE_START_X + deltaX;
this.width = RESIZE_START_WIDTH - deltaX;
}
}
There is another function referenced in these called calculateResizeBound(). What this does is return a string indicating which edge / corner should remain fixed during the resize. Eg 'TL' means that the top left corner should stay fixed, 'BR' means bottom right, 'L' means just the left edge etc etc
When the resize starts from the 'normal' position, ie the top left corner stays fixed, everything works great. Similarly with the left or top edges fixed. However for the bottom and right cases, I need to reposition the element at the same time as resizing it since all the co-ordinates are calculated from the top left.
The problem that I have is that when it does this, the resize is not smooth, it keeps jumping up and down slightly as you resize it. Not only that but when you resize from the 'normal' edges the cursor position remains fixed relative to the fixed edge / corner however from one of the other edges, you can see it start to drift away from the edge / corner as you resize.
With this kind of thing, it is easy to get the + / - of the different bits of the calculation muddled but since the resize is working in the correct direction each time, I assume I have these correct.
So presumably the problem is coming from the simultaneous moving and resizing but I can't find a work-around for it. Any thoughts / suggestions would be much appreciated
Doug McCune has an awesome Resize wrapper that you can use to resize elements. Then you just need to add a mover on it. See the blog post for code/sample: http://dougmccune.com/blog/2007/08/17/my-360flex-slides-and-code/

HowTo stick QDialog to Screen Borders like Skype do?

A long time ago I tried to find method how to stick QDialog window to screen borders for my small projects like Skype windows do it, but I failed. May be I was looking this code not in the right place, so now I'm looking the solution here, on stack! :)
So, does any one have a deal with some kind of such code, links, samples?
In my opinion, we have to reimplement QDialog moveEvent function, like below, but that code does not working:
void CDialog::moveEvent(QMoveEvent * event) {
QRect wndRect;
int leftTaskbar = 0, rightTaskbar = 0, topTaskbar = 0, bottomTaskbar = 0;
// int top = 0, left = 0, right = 0, bottom = 0;
wndRect = this->frameGeometry();
// Screen resolution
int screenWidth = QApplication::desktop()->width();
int screenHeight = QApplication::desktop()->height();
int wndWidth = wndRect.right() - wndRect.left();
int wndHeight = wndRect.bottom() - wndRect.top();
int posX = event->pos().x();
int posY = event->pos().y();
// Snap to screen border
// Left border
if (posX >= -m_nXOffset + leftTaskbar &&
posX <= leftTaskbar + m_nXOffset) {
//left = leftTaskbar;
this->move(leftTaskbar, posY);
return;
}
// Top border
if (posY >= -m_nYOffset &&
posY <= topTaskbar + m_nYOffset) {
//top = topTaskbar;
this->move(posX, topTaskbar);
return;
}
// Right border
if (posX + wndWidth <= screenWidth - rightTaskbar + m_nXOffset &&
posX + wndWidth >= screenWidth - rightTaskbar - m_nXOffset) {
//right = screenWidth - rightTaskbar - wndWidth;
this->move(screenWidth - rightTaskbar - wndWidth, posY);
return;
}
// Bottom border
if (posY + wndHeight <= screenHeight - bottomTaskbar + m_nYOffset &&
posY + wndHeight >= screenHeight - bottomTaskbar - m_nYOffset) {
//bottom = screenHeight - bottomTaskbar - wndHeight;
this->move(posX, screenHeight - bottomTaskbar - wndHeight);
return;
}
QDialog::moveEvent(event);
}
Thanks.
As you thought you can achieve this in the moveEvent function.
I guess the following code do the trick but since I have nothing to test here I will write some pseudo code:
First get the available screen area:
const QRect screen = QApplication::availableGeometry(this);
// This get the screen rect where you can drag a dialog
Then get the position of your dialog relative to the desktop (if your dialog is a child of an other widget, you need to transform coordinates from widget relative to desktop relative):
const QRect dialog = geometry();
// Do here transformation
Now test if dialog is near screen border
if( abs(dialog.left()-screen.left() < OFFSET )
move(screen.left(), dialog.top();
else if( abs(dialog.top()-screen.top() < OFFSET )
move(dialog.left(), screen.top() )
// etc. for the 2 other cases
Let me know if it works
In the pos property description from the QWidget documentation, there is the following warning about moving a window inside the move event handling method.
Warning: Calling move() or setGeometry() inside moveEvent() can
lead to infinite recursion.
That said, there is no proper way to stick the dialog window inside the screen border.
Note :
The behavior you observed in KDE comes from the Window Manager. Actually, the Window Manager is the one that arranges the application windows (like dialog boxes) to show them on the screen. The KDE Window Manager has an option to make all application windows (called client) stick to the border.

Resources