My work Environment : Qt 5.8 MSVC2015 64bit, QT GraphicsView, Windows 7 64 bit
When GraphicsView vertical scroll bar is goes away, zoom out should stop.
So I tried with below code, but it failed to work :
void GraphicsView::scale(qreal scaleFactor)
{
QRectF r(0, 0, 1, 1); // A reference
int pos_x = this->horizontalScrollBar()->value();
int pos_y = this->verticalScrollBar()->value();
qreal factor = transform().scale(scaleFactor, scaleFactor).mapRect(r).width(); // absolute zoom factor
if ( factor > 7) { // Check zoom out limit
return;
}
//Failed, this code failed If zoom out again.**
if(pos_x <= 0 && pos_y <= 0 )
{
return;
}
Any suggestion How I can do to fix the above code ?
No reply for my question. Here is work around solution from me,
Check from wheelEvent are we doing zoom in or zoom out. I scale check vertical & horizontal scroll bar.
here _steps is private data member of my class GraphicsView. GraphicsView derived from QGraphicsView.
void GraphicsView::wheelEvent(QWheelEvent * event)
{
// Typical Calculations (Ref Qt Doc)
const int degrees = event->delta() / 8;
_steps = degrees / 15; // _steps = 1 for Zoom in, _steps = -1 for Zoom out.
}
void GraphicsView::scale(qreal scaleFactor)
{
QRectF r(0, 0, 1, 1); // A reference
int pos_x = this->horizontalScrollBar()->value();
int pos_y = this->verticalScrollBar()->value();
qreal factor = transform().scale(scaleFactor, scaleFactor).mapRect(r).width(); // absolute zoom factor
if ( factor > 7) { // Calculate your zoom in limit from factor
return;
}
//When there is no scroll bar, am I still I am zooming, stop it using _steps
if(pos_x <= 0 && pos_y <= 0 && _steps == -1)
{
return;
}
QGraphicsView::scale(scaleFactor, scaleFactor);
}
I know there is better solution then this, But I found this only :(
Related
I'm attempting to implement mouse cursor centered zoom in/out on an image. For simplicity, assume I only need to zoom in/out in the horizontal axis. I am using Qt with QPainter to draw my scene and QWidget:: mouseWheelEvent override to calculate a zoom factor and a zoom position.
To put it into words before showing the code, the zoom factor, and zoom position define the transformation needed to transform from the original image into the zoomed image (to put it another way, I don't combine matrices or something similar, but calculate the absolute transformation in the paint event), as can be finally seen here:
void MyWidget::paintEvent(QPaintEvent* e)
{
QPainter painter{this};
painter.translate(m_zoomCenterX, 0.);
painter.scale(m_zoomFactor, 1.);
painter.translate(-m_zoomCenterX, 0.);
// content margins are all 0 btw
painter.drawImage(contentsRect(), m_image);
}
In the mouse wheel event, I try to calculate the scaled difference between the zoom center and the new position which I add to the zoom center. Zoom factor is computed simply by multiplying with a constant according to the angle delta. The final if-else branching does some boundaries checking (which is something I'd like to do so the image stays stretched in the view and doesn't move somewhere I don't want to):
void MyWidget::wheelEvent(QWheelEvent* e)
{
const QRect rect = contentsRect();
// m_zoomCenterX is first initialized to 0 in the MyWidget constructor, m_zoomFactor to 1.
const qreal diff = (e->pos().x() - m_zoomCenterX) / m_zoomFactor;
qDebug() << diff;
m_zoomCenterX += diff;
static constexpr const qreal ZOOM_IN_FACTOR = 1.1,
ZOOM_OUT_FACTOR = .9;
m_zoomFactor *= e->angleDelta().y() > 0. ? ZOOM_IN_FACTOR : ZOOM_OUT_FACTOR;
if (m_zoomFactor < 1.)
{
m_zoomFactor = 1.;
m_zoomCenterX = 0.;
}
else if (m_zoomFactor > 5.)
{
m_zoomFactor = 5.;
}
if (m_zoomCenterX < 0)
{
m_zoomCenter = 0;
}
else if (m_zoomCenterX >= rect.width())
{
m_zoomCenterX = rect.width() - 1;
}
update();
}
This doesn't seem to work once I try to zoom in/out after let's say zooming in (see picture:
, sorry if it's not much), the zoom center position is likely not correctly computed but I'm not sure why. The qDebug line gives me non-zero differences after consecutive scrollings when the cursor is on a new position, but after the first scrolling it should be zero...
I guess it must be something trivial but I'm quite stuck at the moment.
Well I got it working. I use inverse matrix to compute the new zoom center, but once zoomed in, it will not match the mouse position, so the entire image must be translated additionally by the inverted_position - mouse_position. Here's the final code for any future wanderers. m_extraTranslation is obviously initialised 0 in the constructor.
QMatrix MyWidget::computeScaleMatrix() const noexcept
{
QMatrix scaleMatrix{1., 0., 0., 1., 0., 0.};
scaleMatrix.translate(-m_extraTranslation, 0.);
scaleMatrix.translate(m_zoomCenter.x(), 0.);
scaleMatrix.scale(m_zoomFactor, 1.);
scaleMatrix.translate(-m_zoomCenter.x(), 0.);
return scaleMatrix;
}
void MyWidget::wheelEvent(QWheelEvent* e)
{
const QMatrix invScaleMatrix = computeScaleMatrix().inverted();
const QPointF invPos = invScaleMatrix.map(e->pos());
m_zoomCenter.setX(invPos.x());
static constexpr const qreal ZOOM_IN_FACTOR = 1.1,
ZOOM_OUT_FACTOR = .9;
m_zoomFactor *= e->angleDelta().y() > 0. ? ZOOM_IN_FACTOR : ZOOM_OUT_FACTOR;
m_extraTranslation = invPos.x() - e->pos().x();
// here I'm making sure the image corners do not wander off inside the widget, this is necessary to do for zooming out
const QMatrix scaleMatrix = computeScaleMatrix();
const qreal startX = scaleMatrix.map(QPointF{0., 0.}).x(),
endX = scaleMatrix.map(QPointF(contentsRect().width() - 1, 0.)).x();
if (startX > 0.)
{
m_extraTranslation += startX;
}
else if (endX < contentsRect().width() - 1)
{
m_extraTranslation -= contentsRect().width() - endX - 1;
}
if (m_zoomFactor < 1.)
{
m_zoomFactor = 1.;
m_zoomCenter = {0, 0};
m_extraTranslation = 0;
}
else if (m_zoomFactor > 5.)
{
m_zoomFactor = 5.;
}
if (m_zoomCenter.x() < 0)
{
m_zoomCenter.setX(0);
}
else if (m_zoomCenter.x() >= contentsRect().width())
{
m_zoomCenter.setX(contentsRect().width() - 1);
}
update();
e->accept();
}
void MyWidget::paintEvent(QPaintEvent* e)
{
QPainter painter{this};
painter.setRenderHint(QPainter::Antialiasing);
painter.setRenderHint(QPainter::SmoothPixmapTransform);
painter.setMatrix(computeScaleMatrix()));
painter.drawImage(contentsRect(), m_image);
}
Extending this for the Y axis should be trivial since it would follow the same logic.
I'm creating a screen where users can add certain tiles to use in an editor, but when adding a tile the window does not correctly resize to fit the content. Except that when I drag the window or resize it even just a little then it snaps to the correct size immediately.
And when just dragging the window it snaps to the correct size.
I tried using resize(sizeHint()); which gave me an incorrect size and the following error, but the snapping to correct size still happens when resizing/dragging.
QWindowsWindow::setGeometry: Unable to set geometry 299x329+991+536 on QWidgetWindow/'TileSetterWindow'. Resulting geometry: 299x399+991+536 (frame: 8, 31, 8, 8, custom margin: 0, 0, 0, 0, minimum size: 259x329, maximum size: 16777215x16777215).
I also tried using updateGeometry() and update(), but it didn't seem to do much if anything.
When setting the window to fixedSize it will immediately resize, but then the user cannot resize the window anymore. What am I doing wrong here and where do I start to solve it?
Edit
Minimal verifiable example and the .ui file.
selected_layout is of type Flowlayout
The flowlayout_placeholder_1 is only there because I can't place a flowlayout directly into the designer.
Edit2
Here is a minimal Visual Studio example. I use Visual Studio for Qt development. I tried creating a project in Qt Creator, but I didn't get that to work.
Edit3
Added a little video (80 KB).
Edit4
Here is the updated Visual Studio example. It has the new changes proposed by jpo38. It fixes the issue of the bad resizing. Though now trying to downsize the windows causes issues. They don't correctly fill up vertical space anymore if you try to reduce the horizontal space even though there is room for more rows.
Great MCVE, exactly what's needed to easily investigate the issue.
Looks like this FlowLayout class was not designed to have it's minimum size change on user action. Layout gets updated 'by chance' by QWidget kernel when the window is moved.
I could make it work smartly by modifying FlowLayout::minimumSize() behaviour, here are the changes I did:
Added QSize minSize; attribute to FlowLayout class
Modifed FlowLayout::minimumSize() to simply return this attribute
Added a third parameter QSize* pMinSize to doLayout function. This will be used to update this minSize attribute
Modified doLayout to save computed size to pMinSize parameter if specified
Had FlowLayout::setGeometry pass minSize attribute to doLayout and invalidate the layout if min size changed
The layout then behaves as expected.
int FlowLayout::heightForWidth(int width) const {
const int height = doLayout(QRect(0, 0, width, 0), true,NULL); // jpo38: set added parameter to NULL here
return height;
}
void FlowLayout::setGeometry(const QRect &rect) {
QLayout::setGeometry(rect);
// jpo38: update minSize from here, force layout to consider it if it changed
QSize oldSize = minSize;
doLayout(rect, false,&minSize);
if ( oldSize != minSize )
{
// force layout to consider new minimum size!
invalidate();
}
}
QSize FlowLayout::minimumSize() const {
// jpo38: Simply return computed min size
return minSize;
}
int FlowLayout::doLayout(const QRect &rect, bool testOnly,QSize* pMinSize) const {
int left, top, right, bottom;
getContentsMargins(&left, &top, &right, &bottom);
QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom);
int x = effectiveRect.x();
int y = effectiveRect.y();
int lineHeight = 0;
// jpo38: store max X
int maxX = 0;
for (auto&& item : itemList) {
QWidget *wid = item->widget();
int spaceX = horizontalSpacing();
if (spaceX == -1)
spaceX = wid->style()->layoutSpacing(QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Horizontal);
int spaceY = verticalSpacing();
if (spaceY == -1)
spaceY = wid->style()->layoutSpacing(QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Vertical);
int nextX = x + item->sizeHint().width() + spaceX;
if (nextX - spaceX > effectiveRect.right() && lineHeight > 0) {
x = effectiveRect.x();
y = y + lineHeight + spaceY;
nextX = x + item->sizeHint().width() + spaceX;
lineHeight = 0;
}
if (!testOnly)
item->setGeometry(QRect(QPoint(x, y), item->sizeHint()));
// jpo38: update max X based on current position
maxX = qMax( maxX, x + item->sizeHint().width() - rect.x() + left );
x = nextX;
lineHeight = qMax(lineHeight, item->sizeHint().height());
}
// jpo38: save height/width as max height/xidth in pMinSize is specified
int height = y + lineHeight - rect.y() + bottom;
if ( pMinSize )
{
pMinSize->setHeight( height );
pMinSize->setWidth( maxX );
}
return height;
}
I was having the same exact issue (albeit on PySide2 rather than C++).
#jpo38's answer above did not work directly, but it un-stuck me by giving me a new approach.
What worked was storing the last geometry, and using that geometry's width to calculate the minimum height.
Here is an untested C++ implementation based on the code in jpo38's answer (I don't code much in C++ so apologies in advance if some syntax is wrong):
int FlowLayout::heightForWidth(int width) const {
const int height = doLayout(QRect(0, 0, width, 0), true);
return height;
}
void FlowLayout::setGeometry(const QRect &rect) {
QLayout::setGeometry(rect);
// e-l: update lastSize from here
lastSize = rect.size();
doLayout(rect, false);
}
QSize FlowLayout::minimumSize() const {
// e-l: Call heightForWidth from here, my doLayout is doing things a bit differently with regards to margins, so might have to add or not add the margins here to the height
QSize size;
for (const QLayoutItem *item : qAsConst(itemList))
size = size.expandedTo(item->minimumSize());
const QMargins margins = contentsMargins();
size += QSize(margins.left() + margins.right(), margins.top() + margins.bottom());
size.setHeight(heightForWidth(qMax(lastSize.width(), size.width())));
return size;
}
int FlowLayout::doLayout(const QRect &rect, bool testOnly) const {
int left, top, right, bottom;
getContentsMargins(&left, &top, &right, &bottom);
QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom);
int x = effectiveRect.x();
int y = effectiveRect.y();
int lineHeight = 0;
for (auto&& item : itemList) {
QWidget *wid = item->widget();
int spaceX = horizontalSpacing();
if (spaceX == -1)
spaceX = wid->style()->layoutSpacing(QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Horizontal);
int spaceY = verticalSpacing();
if (spaceY == -1)
spaceY = wid->style()->layoutSpacing(QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Vertical);
int nextX = x + item->sizeHint().width() + spaceX;
if (nextX - spaceX > effectiveRect.right() && lineHeight > 0) {
x = effectiveRect.x();
y = y + lineHeight + spaceY;
nextX = x + item->sizeHint().width() + spaceX;
lineHeight = 0;
}
if (!testOnly)
item->setGeometry(QRect(QPoint(x, y), item->sizeHint()));
x = nextX;
lineHeight = qMax(lineHeight, item->sizeHint().height());
}
int height = y + lineHeight - rect.y() + bottom;
return height;
}
I am using Qt 5.6, I want to draw a number of text labels around a circle and rotate the text labels to orientate the text according to its position around the circle, so 12 o'clock would have a 0 degree rotation, 3 o'clock would be rotated 90 degrees, 6 o'clock would be rotated 180 degrees etc.
I am aligning the text around its centre position:
void drawText(QPainter* pobjPainter, qreal fltX, qreal fltY
,int intFlags, const QString* pobjText) {
const qreal fltSize = 32767.0;
QPointF ptfCorner(fltX, fltY - fltSize);
if ( (intFlags & Qt::AlignHCenter) == Qt::AlignHCenter ) {
ptfCorner.rx() -= fltSize / 2.0;
} else if ( (intFlags & Qt::AlignRight) == Qt::AlignRight ) {
ptfCorner.rx() -= fltSize;
}
if ( (intFlags & Qt::AlignVCenter) == Qt::AlignVCenter ) {
ptfCorner.ry() += fltSize / 2.0;
} else if ( (intFlags & Qt::AlignTop) == Qt::AlignTop ) {
ptfCorner.ry() += fltSize;
}
QRectF rctPos(ptfCorner, QSizeF(fltSize, fltSize));
pobjPainter->drawText(rctPos, intFlags, *pobjText);
}
I would like to apply a rotation to the text.
I would like to reproduce something similar to what is shown:
http://www.informit.com/articles/article.aspx?p=1405545&seqNum=2
It seems that the rotate function rotates the entire painter canvas, so the coordinates must take into account the rotation which is really giving me a hard time. I want to position the text around the ellipse and then rotate it, how do I know what the coordinates should be?
Sticking with the clock example you could try something like...
virtual void paintEvent (QPaintEvent *event) override
{
QPainter painter(this);
double radius = std::min(width(), height()) / 3;
for (int i = 0; i < 12; ++i) {
int numeral = i + 1;
double radians = numeral * 2.0 * 3.141592654 / 12;
/*
* Calculate the position of the text centre as it would be required
* in the absence of a transform.
*/
QPoint pos = rect().center() + QPoint(radius * std::sin(radians), -radius * std::cos(radians));
/*
* Set up the transform.
*/
QTransform t;
t.translate(pos.x(), pos.y());
t.rotateRadians(radians);
painter.setTransform(t);
/*
* Specify a huge bounding rectangle centred at the origin. The
* transform should take care of position and orientation.
*/
painter.drawText(QRect(-(INT_MAX / 2), -(INT_MAX / 2), INT_MAX, INT_MAX), Qt::AlignCenter, QString("%1").arg(numeral));
}
}
I have a CCLayer which holds all my game objects and i have implemented scaling and scrolling. To make sure that you can't scroll out of bounds i am calculating a rect that represents the screen and use it to check if the rect is within bounds. The problem is that my calculations are wrong. The layer is scaled by a scaleFactor like so:
world->setScale(scaleFactor);
and then i calculate the scroll rect:
float scrollWidth = winSize.width * ( 1 / scaleFactor); // winSize is the size of the screen (1280x
float scrollHeight = winSize.height * ( 1 / scaleFactor);
if(scrollRect.l < 0) scrollRect.l = 0;
if(scrollRect.l + scrollWidth > levelWidth) scrollRect.l -= (scrollRect.l + scrollWidth - levelWidth);
scrollRect.r = scrollRect.l + scrollWidth;
world->setPosition(-scrollRect.l, -scrollRect.b);
(the scale factor value is between 1.0 and 0.5)
This sort of works but only when the layer is zoomed out to the max or zoomed in to the minimum but when scaleFactor isn't MAX/MIN it is wrong (there is some left space).
What am i doing wrong? I've also tried changing the anchor points (they are currently set to 0,0) but without any success.
You can do this whatever your scale factor...
here _tileMap is your world
//Code for getting difference b/w two position
CCTouch *fingerOne = (CCTouch*)touchArray->objectAtIndex(0);
CCPoint newTouchLocation = fingerOne->getLocationInView();
newTouchLocation = CCDirector::sharedDirector()->convertToGL(newTouchLocation);
newTouchLocation=_tileMap->convertToNodeSpace(newTouchLocation);
CCPoint oldTouchLocation = fingerOne->getPreviousLocationInView();
oldTouchLocation = CCDirector::sharedDirector()->convertToGL(oldTouchLocation);
oldTouchLocation = _tileMap->convertToNodeSpace(oldTouchLocation);
//get the difference in the finger touches when the player was dragging
CCPoint difference = ccpSub(newTouchLocation, oldTouchLocation);
CCPoint ASD=ccpAdd(_tileMap->getPosition(), ccpMult(difference, _tileMap->getScale()));
CCPoint bottomLeft =ASD;
// Bounding Box....
if (bottomLeft.x >0) {
bottomLeft.x = 0;
}
if (bottomLeft.y>0) {
bottomLeft.y = 0;
}
if (bottomLeft.x < -(mapWidth*_tileMap->getScale() - _screenSize.width)) {
bottomLeft.x = -(mapWidth*_tileMap->getScale()- _screenSize.width);
}
if (bottomLeft.y <-(mapHieght*_tileMap->getScale() - _screenSize.height)) {
bottomLeft.y = - (mapHieght*_tileMap->getScale() - _screenSize.height);
}
_tileMap->setPosition(bottomLeft);
I hope this may help you..
I want to implement the algorithm for a 2D water surface described here and here.
But instead of using two int arrays and calculating on the CPU I would like to use SFML's sf::RenderTexture's (FBO's basically) and a GLSL shader to run everything on the GPU. I want to use SFML, because it's so simple and I have worked with it before, so I know my way around it a little.
I've made some good progress so far. I was able to set up 3 sf::RenderTextures and ping-pong between them correctly (because other than int array you can't read and write to the same sf::RenderTexture at the same time). I was also able to adapt the algorithm for the height field creation form being in the range -32.767 to 32.767 to the range 0 to 1 (or to be more precise -0.5 to 0.5 for the calculation). Also adding new ripples works to some extend. So up to this point you can actually see a little of waves going on.
Here comes my problem now: The waves disappear really, really fast and I don't even apply any damping yet. According to the algorithm the ripples are not stopping if there is no damping applied. It's even the other way around. If I apply "amplification" the waves look close to what you would expect them to look like (but they still disappear without any damping applied to them). My first thought was that this is, because I use float's in range 0 - 1 instead of integers, but I only see this being a problem if multiplication is used, but I only use addition and subtraction.
Here is my SFML C++ code :
#include <SFML/Graphics.hpp>
#include <iostream>
int main()
{
sf::RenderWindow window(sf::VideoMode(1000, 1000), "SFML works!");
window.setFramerateLimit(12);
sf::RenderTexture buffers[3];
buffers[0].create(500, 500);
buffers[1].create(500, 500);
buffers[2].create(500, 500);
sf::RenderTexture* firstBuffer = buffers;
sf::RenderTexture* secondBuffer = &buffers[1];
sf::RenderTexture* finalBuffer = &buffers[2];
firstBuffer->clear(sf::Color(128, 128, 128));
secondBuffer->clear(sf::Color(128, 128, 128));
finalBuffer->clear(sf::Color(128, 128, 128));
sf::Shader waterHeightmapShader;
waterHeightmapShader.loadFromFile("waterHeightmapShader.glsl", sf::Shader::Fragment);
sf::Sprite spritefirst;
spritefirst.setPosition(0, 0);
spritefirst.setTexture(firstBuffer->getTexture());
sf::Sprite spritesecond;
spritesecond.setPosition(500, 0);
spritesecond.setTexture(secondBuffer->getTexture());
sf::Sprite spritefinal;
spritefinal.setPosition(0, 500);
spritefinal.setTexture(finalBuffer->getTexture());
while (window.isOpen())
{
sf::Event event;
while (window.pollEvent(event))
{
if(event.type == sf::Event::Closed)
window.close();
if(event.type == sf::Event::KeyReleased && event.key.code == sf::Keyboard::Escape)
window.close();
}
waterHeightmapShader.setParameter("mousePosition", sf::Vector2f(-1.f, -1.f));
// if mouse button is pressed add new ripples
if(sf::Mouse::isButtonPressed(sf::Mouse::Left))
{
sf::Vector2i mousePosition = sf::Mouse::getPosition(window);
if(mousePosition.x < 500 && mousePosition.y < 500)
{
sf::Vector2f mouse(mousePosition);
mouse.x /= 500.f;
mouse.y /= 500.f;
mouse.y = 1 - mouse.y;
std::cout << mouse.x << " " << mouse.y << std::endl;
waterHeightmapShader.setParameter("mousePosition", mouse);
}
}
waterHeightmapShader.setParameter("textureTwoFramesAgo", firstBuffer->getTexture());
waterHeightmapShader.setParameter("textureOneFrameAgo", secondBuffer->getTexture());
// create the heightmap
secondBuffer->display();
finalBuffer->clear(sf::Color(128, 128, 128));
finalBuffer->draw(sf::Sprite(secondBuffer->getTexture()), &waterHeightmapShader);
finalBuffer->display();
spritefirst.setTexture(firstBuffer->getTexture());
spritesecond.setTexture(secondBuffer->getTexture());
spritefinal.setTexture(finalBuffer->getTexture());
window.clear();
window.draw(spritefirst);
window.draw(spritesecond);
window.draw(spritefinal);
window.display();
// swap the buffers around, first becomes second, second becomes third and third becomes first
sf::RenderTexture* swapper = firstBuffer;
firstBuffer = secondBuffer;
secondBuffer = finalBuffer;
finalBuffer = swapper;
}
return 0;
}
And here is my GLSL shader code :
uniform sampler2D textureTwoFramesAgo;
uniform sampler2D textureOneFrameAgo;
uniform vec2 mousePosition;
const float textureSize = 500.0;
const float pixelSize = 1.0 / textureSize;
void main()
{
// pixels position
vec2 position = gl_TexCoord[0].st;
vec4 finalColor = ((texture2D(textureOneFrameAgo, vec2(position.x - pixelSize, position.y)) +
texture2D(textureOneFrameAgo, vec2(position.x + pixelSize, position.y)) +
texture2D(textureOneFrameAgo, vec2(position.x, position.y + pixelSize)) +
texture2D(textureOneFrameAgo, vec2(position.x, position.y - pixelSize)) - 2.0) / 2) -
(texture2D(textureTwoFramesAgo, position) - 0.5);
// damping
// finalColor.rgb *= 1.9; // <---- uncomment this for the "amplifiction" ie. to see the waves better
finalColor.rgb += 0.5;
// add new ripples
if(mousePosition.x > 0.0)
{
if(distance(position, mousePosition) < pixelSize * 5)
{
finalColor = vec4(0.9, 0.9, 0.9, 1.0);
}
}
gl_FragColor = finalColor;
}
Please remember that this is all just about the height field creation. There is no shading of the water yet.
Do you know why the waves disappear by them self without damping?
If I am reading the code correctly you sample the previous frame for the texture's colors/height and use four neighboring pixels/texels to determine the color/height of the current pixel.
As you are calculating (scaling) these neighbors you might run into missing the texel that contains the color/height you are looking for. It might not be the heighest texel, just one next to it a little bit lower causing the unexpected damping.
This is where you do not just use addition and subtraction:
const float pixelSize = 1.0 / textureSize;
By using this value you could just miss the texel you are looking for.
EDIT
Also: you are averaging the samples so the result will always be less than the maximum value of the samples. So instead of averaging you could select the maximum value. That might give weird results but also extra insight.
Here are some "Processing" codes which implements the same algorithm you've posted above, and its damping is correct, I hope you can get some points from it :
// codes begin
int Width = 800;
int Height = 600;
int FullSize = 0;
//int Spacing = 10;
int[] source, dest;
PImage bg;
void setup()
{
// if you want to run these codes by "Processing"
// please make a picture named "HelloWorld.png"
bg = loadImage("HelloWorld.png");
Width = bg.width;
Height = bg.height;
FullSize = Width * Height;
size(Width, Height);
source = new int[FullSize];
dest = new int[FullSize];
for (int i=0; i< FullSize; i++)
source[i] = dest[i] = 0;
}
void draw()
{
for (int i=Width; i< FullSize-Width; i++)
{
// check for bounds
int xi = i % Width;
if ((xi==0) || (xi==Width-1)) continue;
dest[i] = (
((source[i-1]+
source[i+1]+
source[i-Width]+
source[i+Width]) >>1) ) -dest[i];
int dampFactor = 1000;
dest[i] -= (dest[i] >> dampFactor); // Damping - Quick divde by 32 (5 bits)
}
//image(bg, 0, 0);
loadPixels();
for (int i=Width; i< FullSize-Width; i++)
{
// check for bounds
int xi = i % Width;
if ((xi==0) || (xi==Width-1)) continue;
int xoffset = dest[i-1] - dest[i+1];
int yoffset = dest[i-Width] - dest[i+Width];
int offset = i+xoffset+yoffset*Width;
if (offset>0 && offset<FullSize)
{
// TODO: make better map
pixels[i] = bg.pixels[offset];
}
}
//bg.updatePixels();
updatePixels();
//swap
int[] temp = source;
source = dest;
dest = temp;
}
void mouseDragged()
{
if (mouseX > 0 && mouseX < Width && mouseY > 0 && mouseY < Height)
source[mouseY*Width+mouseX] = (int)random(50, 100);
}
void mousePressed()
{
// TODO: make a area pulse value, like a radius circle
if (mouseX > 0 && mouseX < Width && mouseY > 0 && mouseY < Height)
source[mouseY*Width+mouseX] = (int)random(50, 100);
}
// codes end