PDA

View Full Version : Moving Qt3D objects



gib
18th August 2019, 06:54
I am learning about Qt3D. Starting from one of the Qt examples, I can display a sphere and move it by QTransform::setTranslation. I want to make multiple moves, with a brief delay each time, so I have:

void MainWindow::runMover()
{
QVector3D delta = QVector3D(0.0f, 0.0f, -0.1f);
for (int i = 0; i<1000; i++) {
mover(delta);
QThread::msleep(10);
}
}

with

void MainWindow::mover(QVector3D delta)
{
modifier->moveSphere(delta);
}

and

void SceneModifier::moveSphere(QVector3D delta)
{
QVector3D trans = sphereTransform->translation();
trans = trans + delta;
sphereTransform->setTranslation(trans);
}

The function runMover() is executed when a signal is received. I expect to see a smooth movement, but instead there is a 10 second delay then suddenly the whole movement occurs. How can I make the translation by delta occur every 10 ms?

d_stranz
18th August 2019, 19:23
I expect to see a smooth movement, but instead there is a 10 second delay then suddenly the whole movement occurs.

Because this for loop does not let control return to the Qt event loop, which needs to process the translation and repaint the window for each move. Qt's event loop removes all but the last paint event when the loop contains multiple pending paint requests.



for (int i = 0; i<1000; i++)
{
mover(delta);
}


To avoid this mistake, get rid of the loop and the sleep command in runMover(). Instead, set up a QTimer with a 10 ms timeout. Connect the timer's timeout() signal to a slot that translates the objects by one step, checks to see how many steps have been made, and starts the QTimer again if there are less than 10 second's worth of steps (1000).



void MainWindow::runMover()
{
// elapsedSteps is a member variable of MainWindow that is inotialized to 0 before
// calling runMover()
if ( elapsedSteps < 1000 )
{
QVector3D delta = QVector3D(0.0f, 0.0f, -0.1f);
mover( delta );
QTimer::singleShot( 10, this, &MainWindow::runMover );
elapsedSteps++;
}
}


Please use CODE tags when posting source code so it is readable. See my signature below.

gib
18th August 2019, 23:27
Because this for loop does not let control return to the Qt event loop, which needs to process the translation and repaint the window for each move. Qt's event loop removes all but the last paint event when the loop contains multiple pending paint requests.



To avoid this mistake, get rid of the loop and the sleep command in runMover(). Instead, set up a QTimer with a 10 ms timeout. Connect the timer's timeout() signal to a slot that translates the objects by one step, checks to see how many steps have been made, and starts the QTimer again if there are less than 10 second's worth of steps (1000).



void MainWindow::runMover()
{
// elapsedSteps is a member variable of MainWindow that is inotialized to 0 before
// calling runMover()
if ( elapsedSteps < 1000 )
{
QVector3D delta = QVector3D(0.0f, 0.0f, -0.1f);
mover( delta );
QTimer::singleShot( 10, this, &MainWindow::runMover );
elapsedSteps++;
}
}


Please use CODE tags when posting source code so it is readable. See my signature below.

That is a great help, thanks a million! Coming from a long career in old-style programming, I haven't fully grasped the event-driven nature of Qt programming.
I now get nice smooth motion. One thing I don't understand (showing my ignorance) is why elapsedSteps needs to be a MainWindow member variable and needs to be initialised outside of runMover. I see that this is the case, since if I set it to 0 in runMover (either before or after the 'if' block) the movement goes on forever. I tried initialising there because I want to be able to generate the motion every time I click the button that triggers the signal that invokes runMover.

Sorry about the code formatting oversight.

Gib

Edit: Oh, now I see that it's obvious why it is useless to set elapsedSteps=0 in runMover, because the function is called repeatedly, therefore the counter would be constantly reset. Doh! The solution is to connect the button-click signal to a function that initialises elapsedSteps then calls runMover. Thanks again.

d_stranz
19th August 2019, 17:52
The solution is to connect the button-click signal to a function that initialises elapsedSteps then calls runMover. Thanks again.

Exactly. And you're welcome.

Event-driven programming takes a while to get your head around. I first started in the late 80s in an XWindows / Motif GUI after also spending years in procedural programming. The key to understanding it is to recognize that your code isn't in charge - there's another part of it that is sitting there idling away until something happens that it needs to tell your code about. It then uses the formal notification system of whatever framework you are using (in Qt's case, Qt events and signals) to tell you what has happened. If your code is interested (i.e., you have implemented a handler for that event or have connected a slot to that signal), then it'll get called.

The trick to keeping your GUI alive is that your code has to let control return to the framework. If you have a heavy computation and your code runs it to completion, your GUI locks up until it finishes and control is returned to the framework. This is essentially what your loop / sleep was doing - a 10 second long computation.

Wysota wrote a good article (https://doc.qt.io/archives/qq/qq27-responsive-guis.html) discussing this a while back that is still available.