I've implemented a QwtPlot which scrolls across the screen as data is added in real-time. Based on user input, an image of the plot is occasionally rendered to a file using QwtPlotRenderer. However, because the axis scrolls during normal operation, the QwtScaleDiv tick marks can look a little wonky at render time (they are right-aligned):
Is there some easy way in which I can recalculate the division prior to rendering so that the first label is on the far left and the last one is on the far right?
This isn't as difficult as it looked at first. Bascially, all you need to do is temporarily replace the axisScaleDiv.
auto divX = this->axisScaleDiv(xBottom);
double ub = divX.upperBound();
double lb = divX.lowerBound();
double numTicks = 11.0; // 10 even divisions
// you can create minor/medium ticks if you want to, I didn't.
QList<double> majorTicks;
for (int i = 0; i < numTicks; ++i)
{
majorTicks.push_back(lb + i * ((ub - lb) / (numTicks - 1)));
}
// set the scale to the newly created division
QwtScaleDiv renderDivX(divX.lowerBound(), divX.upperBound(),
QList<double>(), QList<double>(), majorTicks);
this->setAxisScaleDiv(xBottom, renderDivX);
// DO PLOT RENDERING
QwtPlotRender renderer;
renderer.renderDocument(...);
// RESOTRE PREVIOUS STATE
this->setAxisScaleDiv(xBottom, divX);
this->setAxisScaleDiv(yLeft, divY);
// update the axes
this->updateAxes();
Related
UPDATE:
I couldn't figure out the exact problem, however I made a fix that's good enough for me: Whenever the player's X value is less then half the screen's width, I just snap the view back to the center (up left corner) using sf::View::setCenter().
So I'm working on a recreating of Zelda II to help learn SFML good enough so I can make my own game based off of Zelda II. The issue is the screen scrolling, for some reason, if link walks away from the wall and initiated the camera to follow him, and then move back toward the wall, the camera won't go all the way back to the end of the wall, which occurs on the other wall at the end of the scene/room. This can be done multiple times to keep making the said camera block get further away from the wall. This happens on both sides of the scene, and I have reason to believe it has something to do with me trying to make the game frame independent, here's an included GIF of my issue to help understand:
My camera function:
void Game::camera() {
if (this->player.getVar('x') >= this->WIDTH / 2 and this->player.getVar('x') < this->sceneWidth - this->WIDTH / 2) {
this->view.move(int(this->player.getVar('v') * this->player.dt * this->player.dtM), 0);
}
}
player.getVar() is a temporary function I'm using to get the players x position and x velocity, using the argument 'x' returns the players x position, and 'v' returns the x velocity. WIDTH is equal to 256, and sceneWidth equals 767, which is the image I'm using for the background's width. dt and dtM are variables for the frame independence I mentioned earlier, this is the deceleration:
sf::Clock sclock;
float dt = 0;
float dtM = 60;
int frame = 0;
void updateTime() {
dt = sclock.restart().asSeconds();
frame += 1 * dt * dtM;
}
updateTime() is called every frame, so dt is updated every frame as well. frame is just a frame counter for Link's animations, and isn't relevant to the question. Everything that moves and is rendered on the screen is multiplied by dt and dtM respectively.
There's a clear mismatch between the movement of the player and the one of the camera... You don't show the code to move the player, but if I guess you don't cast to int the movement there, as you are doing on the view.move call. That wouldn't be a problem if you were setting the absolute position of the camera, but as you are constantly moving it, the little offset accumulates each frame, causing your problem.
One possible solution on is to skip the cast, which is unnecessary because sf::View::move accepts float as arguments.
void Game::camera() {
if (this->player.getVar('x') >= this->WIDTH / 2 and this->player.getVar('x') < this->sceneWidth - this->WIDTH / 2) {
this->view.move(this->player.getVar('v') * this->player.dt * this->player.dtM, 0);
}
}
Or even better, not to use view.move but to directly set the position of the camera each frame. Something like:
void Game::camera() {
if (this->player.getVar('x') >= this->WIDTH / 2 and this->player.getVar('x') < this->sceneWidth - this->WIDTH / 2) {
this->view.setCenter(this->player.getVar('x'), this->view.getCenter().y);
}
}
I am working on a MFC app which is a MDI. One of the child frame uses OpenGL(mixed with fixed function and modern version) called 3d view and another child frame uses GDI called plan view. Both of the views use the same doc.
The 3d view has a function to detect if the mouse cursor is over rendered 3d model by reading pixels and check its depth value.
The function is used for WM_MOUSEMOVE and WM_LBUTTONDOWN events. Most time it works pretty well. But it failed when I move my cursor from the plan view(currently active) to the 3d view and left mouse click. The depth values read from the pixels(called from onLButtonDown) are always all zeros though it is over a model. There is no OpenGL error reported. It only fails on the first mouse click when the 3d view is not activated. Afterwards, everything works well again.
The issue doesn't happen on all machines. And it happens to me but not to another guy with the same hardware machine with me. Is that possible hardware related or a code bug?
Things tried:
I tried to increase the pixel block size much bigger but depths are still all zero.
If I click on the title bar of the 3d view to activate it first, then works.
I tried to set the 3d view active and foreground in the onLButtonDown method before reading pixels. But still failed.(btw, the 3d view should be active already before the OnLButtonDown handler via other message handler fired by the left button down).
I tried to invalidate rect before reading pixels, failed too.
The code is as below:
BOOL CMy3DView::IsOverModel(int x0, int y0, int &xM, int &yM, GLfloat &zWin, int width0 , int height0 )
{
int width = max(1,width0);
int height= max(1,height0);
CRect RectView;
GetClientRect(&RectView);
GLint realy = RectView.Height() - 1 - (GLint)y0 ; /* OpenGL y coordinate position */
std::vector<GLfloat> z(width*height);
//Read the window z co-ordinates the z value of the points in a rectangle of width*height )
xM = max(0, x0-(width-1)/2);
yM = max(0, realy-(height-1)/2);
glReadPixels(xM, yM, (GLsizei)width, (GLsizei)height, GL_DEPTH_COMPONENT, GL_FLOAT, &z[0]); OutputGlError(_T("glReadPixels")) ;
/* check pixels along concentric, nested boxes around the central point */
for (int k=0; k<=(min(height,width)-1)/2; ++k){
for (int i=-k;i<=k;++i){
xM = x0+i;
for (int j=-k;j<=k;++j){
if (abs(i)==k || abs(j)==k) {
yM = realy+j;
zWin=z[(i+(width-1)/2)+width*(j+(height-1)/2)];
if (zWin<1.0-FLT_EPSILON) break;
}
}
if (zWin<1.0-FLT_EPSILON) break;
}
if (zWin<1.0-FLT_EPSILON) break;
}
yM = RectView.Height() - 1 - yM;
if (zWin>1.0-FLT_EPSILON || zWin<FLT_EPSILON) {// z is the depth, between 0 and 1, i.e. between Near and Far plans.
xM=x0; yM=y0;
return FALSE;
}
return TRUE;
}
Just found a solution for that: I called render(GetDC) before any processing in OnLButtonDown. somehow it fixed the issue though I don't think it's necessary.
InvalideRect wont fix the issue since it will update the view for the next WM_PAINT.
Weird, since it works for some machines without the fix. Still curious about the reason.
Alright, so I am trying to use the analog stick on a gamepad to move the desktop mouse cursor around. The problem is that I need to be able to get the same smoothness as Attempt 2, but without using an absolute mouse position. The cursor needs to be moved relative to its current position. The reason for this is that many applications (mainly video games) also set the mouse to an absolute position. This causes the application and attempt 2 to fight one another for control of the mouse.
Attempt 1 (relative)
// keep updating the controller state and mouse position
while (true)
{
// getState returns a 2d float vector with normalized values from [-1, 1]
// The cursor is being set relative to its current position here.
SetCursorPosition(GetCursorPosition() + analogStick->getState());
}
This solution works, but suffers from a rounding issue because GetCursorPosition and SetCursorPosition are based on integers. As a result, small movements are not registered because smaller analog movements will always get truncated. Visually speaking, small movements on the analog stick will only move the mouse along the X or Y axis even if you are try to make a diagonal movement.
Attempt 2 (absolute)
vec2 mouseTargetPosition = GetCursorPosition(); // global cursor position
while (true)
{
mouseTargetPosition += leftStick->getState();
vec2 newPosition = lerp(GetCursorPos(), mouseTargetPosition, 0.8f);
SetCursorPos(round(newPosition.x), round(newPosition.y));
}
This solution works great, the mouse responds to the smallest of movements and moves very naturally as a result of interpolating the accumulated analog movements. But, it sets the mouse to an absolute position (mouseTargetPosition), making this solution a deal breaker.
This is an awfully specific question in the first place I suppose. After fooling around with several configurations this is the one that feels smoothest and works well. It's basically magic considering because it can add native feeling analog support for games and model viewers that don't have it :)
vec2 mouseTargetPos, mouseCurrentPos, change;
while (true)
{
// Their actual position doesn't matter so much as how the 'current' vector approaches
// the 'target vector'
mouseTargetPos += primary->state;
mouseCurrentPos = util::lerp(mouseCurrentPos, mouseTargetPos, 0.75f);
change = mouseTargetPos - mouseCurrentPos;
// movement was too small to be recognized, so we accumulate it
if (fabs(change.x) < 0.5f) accumulator.x += change.x;
if (fabs(change.y) < 0.5f) accumulator.y += change.y;
// If the value is too small to be recognized ( < 0.5 ) then the position will remain the same
SetCursorPos(GetCursorPos() + change);
SetCursorPos(GetCursorPos() + accumulator);
// once the accumulator has been used, reset it for the next accumulation.
if (fabs(accumulator.x) >= 0.5f) accumulator.x = 0;
if (fabs(accumulator.y) >= 0.5f) accumulator.y = 0;
}
In order to create an animation in cocos2d-x 3.2 I do this:
SpriteFrameCache* cache = SpriteFrameCache::getInstance();
Vector<SpriteFrame*> animFrames(15);
for(int i = 1; i <= 7; ++i)
{
SpriteFrame* frame = cache->getSpriteFrameByName(String::createWithFormat("%d.png", i)->getCString());
animFrames.pushBack(frame);
}
auto animation = Animation::createWithSpriteFrames(animFrames, 1 / animFrames.size());
auto animate = Animate::create(animation);
pSprite->runAction(animate);
But now I need some frames to scaleByX with -1 in order to create a mirrored image. SpriteFrame has not scale method. Also I can't scale the pSprite as only some of the frames should be scaled. How can I solve this problem?
You have a pretty weird situation :)
You can schedule an update selector on sprite and set flipX to true/false based on your desired conditions. That's my personal preference.
You can't hack SpriteFrame that way, but you can use RenderTexture: http://www.cocos2d-x.org/reference/native-cpp/V3.0alpha0/d9/ddc/classcocos2d_1_1_render_texture.html - flip your desired sprites in a new texture, basically generate a new sprite-sheet on the fly. Now that's a bad idea.
I have QGraphicsTextItem objects on a QGraphicsScene. The user can scale the QGraphicsTextItem objects by dragging the corners. (I am using a custom "transformation editor" to do this.) The user can also change the size of the QGraphicsTextItem by changing the font size from a property panel. What I would like to do is unify these so that when the user scales the object by dragging the corner with the mouse, behind the scenes it actually is calculating "What size font is necessary to make the resulting object fit the target size and keep the scale factor at 1.0?"
What I am doing now is letting the object scale as normal using QGraphicsItem::mouseMoveEvent and then triggering a FinalizeMapScale method in QGraphicsItem::mouseReleaseEvent once the mouse scale is complete. This method should then change the font to the appropriate size and set the scale back to 1.0.
I have a solution that appears to be working, but I'm not crazy about it. I'm relatively new to both Qt and C++, so would appreciate any comments or corrections.
Is there a better way to architect this whole thing?
Are there Qt methods that already do this?
Is my method on the right track but has some Qt or C++ errors?
Feel free to comment on my answer below on submit your own preferred solution. Thanks!
[EDIT] As requested in comment, here is the basics of the scaling code. We actually went a different direction with this, so this code (and the code below) is no longer being used. This code is in the mouseMoveEvent method, having previously set a "scaling_" flag to true in mousePressEvent if the mouse was clicked in the bottom-right "hot spot". Note that this code is in a decorator QGraphicsItem that holds a pointer to the target it is scaling. This abstraction was necessary for our project, but is probably overkill for most uses.
void TransformDecorator::mouseMoveEvent(QGraphicsSceneMouseEvent *event) {
...
if (scaling_) {
QGraphicsItem *target_item = target_->AsQGraphicsItem();
target_item->setTransformOriginPoint(0.0, 0.0);
QPointF origin_scene = mapToScene(target_item->transformOriginPoint());
QPointF scale_position_scene = mapToScene(event->pos());
qreal unscaled_width = target_item->boundingRect().width();
qreal scale_x = (scale_position_scene.x() - origin_scene.x()) / unscaled_width;
if (scale_x * unscaled_width < kMinimumSize) {
scale_x = kMinimumSize / unscaled_width;
}
target_item->setScale(scale_x);
} else {
QGraphicsObject::mouseMoveEvent(event);
}
}
Please no holy wars about the loop-with-exit construct. We're comfortable with it.
void MapTextElement::FinalizeMapScale() {
// scene_document_width is the width of the text document as it appears in
// the scene after scaling. After we are finished with this method, we want
// the document to be as close as possible to this width with a scale of 1.0.
qreal scene_document_width = document()->size().width() * scale();
QString text = toPlainText();
// Once the difference between scene_document_width and the calculated width
// is below this value, we accept the new font size.
const qreal acceptable_delta = 1.0;
// If the difference between scene_document_width and the calculated width is
// more than this value, we guess at the new font size by calculating a new
// scale factor. Once it is beneath this value, we creep up (or down) by tiny
// increments. Without this, we would sometimes incur long "back and forth"
// loops when using the scale factor.
const qreal creep_delta = 8.0;
const qreal creep_increment = 0.1;
QScopedPointer<QTextDocument> test_document(document()->clone());
QFont new_font = this->font();
qreal delta = 0.0;
// To prevent infinite loops, we store the font size values that we try.
// Because of the unpredictable (at least to me) relationship between font
// point size and rendering size, this was the only way I could get it to
// work reliably.
QList<qreal> attempted_font_sizes;
while (true) {
test_document->setDefaultFont(new_font);
delta = scene_document_width - test_document->size().width();
if (std::abs(delta) <= acceptable_delta ||
attempted_font_sizes.contains(new_font.pointSizeF())) {
break;
}
attempted_font_sizes.append(new_font.pointSizeF());
qreal new_font_size = 0.0;
if (std::abs(delta) <= creep_delta) {
new_font_size = delta > 0.0 ? new_font.pointSizeF() + creep_increment
: new_font.pointSizeF() - creep_increment;
} else {
new_font_size = new_font.pointSizeF()
* scene_document_width
/ test_document->size().width();
}
new_font.setPointSizeF(new_font_size);
}
this->setFont(new_font);
this->setScale(1.0);
}
Another way to look at the problem is: Qt has scaled the font, what is the effective font size (as it appears to the user, not the font size set in the text item) that I need to display to the user as their choice of new font size? This is just an alternative, you still need a calculation similar to yours.
I have a similar problem. I have a text item that I want to be unit size (one pixel size) like my other unit graphic items (and then the user can scale them.) What font (setPointSize) needs to be set? (Also what setTextWidth and what setDocumentMargin?) The advantage of this design is that you don't need to treat the scaling of text items different than the scaling of any other shape of graphics item. (But I don't have it working yet.)
Also, a user interface issue: if the user changes the font size, does the item change size? Or does it stay the same size and the text wrap differently, leaving more or less blank space at the end of the text? When the user appends new text, does the font size change so all the text fits in the size of the shape, or does the shape size grow to accommodate more text? In other words, is it more like a flowchart app (where the shape size is fixed and the font shrinks), or like a word processor app (where the font size is constant and the shape (number of pages) grows?