// point 为屏幕坐标,拉伸后保证,这个点在屏幕的位置不变化。
virtual bool zoomScale(QPointF scale, QPointF point) = 0;
// offset 为屏幕坐标系。
virtual bool moveTranslate(QPointF offset) = 0;
virtual bool resetTranslate() = 0;
关于移动:
bool FlipWidgetAngle::moveTranslate(QPointF offset) {
auto mViewRect = mCurlView->getRenderer()->getViewRect();
// OpenGL 坐标系
float fdx = 1.0 * offset.x() / width() * mViewRect.width() / m_scale.x();
float fdy = 1.0 * offset.y() / height() * mViewRect.height() / m_scale.y();
m_offset.setX(m_offset.x() + fdx);
m_offset.setY(m_offset.y() + fdy);
checkResetScaleOffset();
this->update(); // moveTranslate
return true;
}
bool FlipWidgetAngle::resetTranslate() {
m_offset = QPointF(0.0, 0.0);
this->update(); // resetTranslate
return true;
}
关于缩放:
bool FlipWidgetAngle::zoomScale(QPointF scale, QPointF point) {
QPointF glpoint; // 在 OpenGL 中的坐标。
glpoint.setX(1.0 * (point.x() - width() / 2) / (1.0 * width() / 2));
glpoint.setY(1.0 * (height() / 2 - point.y()) / (1.0 * height() / 2));
float mAnimationCenterTarget = mCurlView->getAnimationCenterTarget();
auto mViewRect = mCurlView->getRenderer()->getViewRect();
mAnimationCenterTarget = mAnimationCenterTarget / mViewRect.width() * 2;
glpoint.setX(glpoint.x() - mAnimationCenterTarget);
auto projectionk = projection;
projectionk.scale(m_scale.x(), m_scale.y());
projectionk.translate(m_offset.x(), m_offset.y());
// orig --> glpoint
auto orig = projectionk.inverted() * glpoint;
auto target1 = projectionk * orig;
m_scale = scale;
projectionk = projection;
projectionk.scale(m_scale.x(), m_scale.y());
auto target2 = projectionk * orig;
m_offset = projectionk.inverted() * (target1 - target2);
projectionk.translate(m_offset.x(), m_offset.y());
target2 = projectionk * orig;
assert(abs(target1.x() - target2.x()) <= 0.0001);
assert(abs(target1.y() - target2.y()) <= 0.0001);
checkResetScaleOffset();
this->update(); // zoomScale
return true;
}
矩阵作用,是左运算,这个很关键。
本来有一个坐标系 projection,后来在上面加入了位移动画。 最后又加上了 scale & offset,代码就变的很复杂。
float mAnimationCenterSource = 0;
ULONGLONG mAnimationCenterStartTime = 0;
float mAnimationCenterTarget = 0; // 这里是自己坐标系的偏移。
bool CurlViewAngle::clickPaperTest(QPointF point, QMatrix4x4 projection, QPointF scale, QPointF offset) {
QRectF rightRect = mRenderer.getPageRect(PAGE_RIGHT);
QRectF leftRect = mRenderer.getPageRect(PAGE_LEFT);
// 相当于拼接在 projection 的左边。先起作用。
float offsetz = this->mAnimationCenterTarget; // / mRenderer.getViewRect().width() * 2;
// 自动居中 的逻辑。
QMatrix4x4 matrix = projection;
matrix.translate(offsetz, 0);
matrix.scale(scale.x(), scale.y());
matrix.translate(offset.x(), offset.y());
mRenderer.translate(point); // 自定义空间坐标。
point = projection.map(point); // OpenGL 坐标。
leftRect = matrix.mapRect(leftRect);
rightRect = matrix.mapRect(rightRect);
if (mViewMode == SHOW_TWO_PAGES) {
if (mCurrentIndex > 0) {
if (leftRect.contains(point)) {
return true;
}
}
if (mCurrentIndex < mPageProvider->getPaperCount()) {
if (rightRect.contains(point)) {
return true;
}
}
return false;
}
// 单页模式
if (mCurrentIndex < mPageProvider->getPaperCount() && rightRect.contains(point)) {
return true;
}
return false;
}
void FlipWidgetAngle::paintGL() {
ULONGLONG currentTime = GetTickCount64();
FastGLWidget::paintGL();
QMatrix4x4 projectionk = projection;
long mAnimationDurationTime = 300 * PAGE_FLIPPING_SPEED;
ULONGLONG mAnimationCenterStartTime = mCurlView->getAnimationCenterStartTime();
float mAnimationCenterSource = mCurlView->getAnimationCenterSource();
float mAnimationCenterTarget = mCurlView->getAnimationCenterTarget();
bool mAnimate = false;
if (currentTime >= mAnimationCenterStartTime + mAnimationDurationTime) {
projectionk.translate(mAnimationCenterTarget, 0);
} else {
float t = 1.f - ((float)(currentTime - mAnimationCenterStartTime) / mAnimationDurationTime);
t = 1.f - (t * t * t * (3 - 2 * t));
float offsetx = mAnimationCenterSource + (mAnimationCenterTarget - mAnimationCenterSource) * t;
projectionk.translate(offsetx, 0);
mAnimate = true;
}
projectionk.scale(m_scale.x(), m_scale.y());
projectionk.translate(m_offset.x(), m_offset.y());
bool brotate = false;
m_framebuffer->bind();
brotate = mCurlView->getRenderer()->onDrawFrame(*this, projectionk, program, false) || brotate;
m_framebuffer->release();
m_framebufferMask->bind();
brotate = mCurlView->getRenderer()->onDrawFrame(*this, projectionk, program, true) || brotate;
m_framebufferMask->release();
brotate = mCurlView->getRenderer()->onDrawFrame(*this, projectionk, program, false) || brotate;
//m_framebuffer->toImage().save("m_framebuffer.png");
//m_framebufferMask->toImage().save("m_framebufferMask.png");
// 使用 32 位 ARGB 格式(0xAARRGGBB)存储帧。这相当于 QImage :: Format_ARGB32_Premultiplied。
//QImage frame = this->grabFramebuffer();
mCurlView->getRenderer()->onPostProcessing(*this, program, //
*m_framebuffer, *m_framebufferMask);
static int paintConter = 0;
printf("FlipWidgetAngle::paintGL[%d] %d \n", ++paintConter, GetTickCount64() - currentTime);
if (m_callback) {
m_callback->onFlipWidgetPaintGL(*this);
}
if (mAnimate) { // 下一轮动画。
QTimer::singleShot(MY_ANIMATE_TIMER, this, [&]() {
this->update(); // 居中动画。
});
} else if (brotate) {
QTimer::singleShot(MY_ANIMATE_TIMER, this, [&]() {
this->update(); // 旋转动画。
});
}
}
bool FlipWidgetAngle::checkScaleOffset(bool xaxis, float& recommend) {
recommend = 0;
QRectF rightRect = mCurlView->getRenderer()->getPageRect(PAGE_RIGHT);
QRectF leftRect = mCurlView->getRenderer()->getPageRect(PAGE_LEFT);
QMatrix4x4 projectionk = projection;
float mAnimationCenterTarget = mCurlView->getAnimationCenterTarget();
auto mViewRect = mCurlView->getRenderer()->getViewRect();
// 相当于拼接在 projection 的左边。先起作用。
projectionk.translate(mAnimationCenterTarget, 0);
projectionk.scale(m_scale.x(), m_scale.y());
projectionk.translate(m_offset.x(), m_offset.y());
QPointF lt, rb;
if (mCurlView->getViewMode() == SHOW_ONE_PAGE) {
lt = projectionk * rightRect.topLeft();
rb = projectionk * rightRect.bottomRight();
} else {
// 双页模式。
lt = projectionk * leftRect.topLeft();
rb = projectionk * rightRect.bottomRight();
int curidx = this->getCurrentIndex();
auto total = mCurlView->getPaperCount();
if (curidx == 0) {
lt = projectionk * rightRect.topLeft();
rb = projectionk * rightRect.bottomRight();
} else if (curidx == total) {
lt = projectionk * leftRect.topLeft();
rb = projectionk * leftRect.bottomRight();
}
}
auto x1 = lt + (rb - lt) * 0.75; // inside
auto x2 = rb - (rb - lt) * 0.75; // inside
auto x1outside = lt + (rb - lt) * 0.25;
auto x2outside = rb - (rb - lt) * 0.25;
if (m_scale.x() >= 1.0) {
x1 = lt + (rb - lt) * 0.85;
x2 = rb - (rb - lt) * 0.85;
x1outside = lt + (rb - lt) * 0.15;
x2outside = rb - (rb - lt) * 0.15;
}
if (xaxis) {
bool retv = (x1.x() >= -1.0 && x1.x() <= 1.0) || (x2.x() >= -1.0 && x2.x() <= 1.0);
// 两头都看不到了,其实中间的还能看到。
for (int i = 0; i <= 100 && !retv; i++) {
float tmpx = x1.x() + (x2.x() - x1.x()) * i / 100;
retv = retv || (tmpx >= -1.0 && tmpx <= 1.0);
}
if (!retv) {
if (x1outside.x() >= 1.0) {
recommend = -abs(m_offset.x()) / 100;
} else if (x2outside.x() <= -1.0) {
recommend = abs(m_offset.x()) / 100;
} else {
assert(false);
}
}
return retv;
}
bool retv = (x1.y() >= -1.0 && x1.y() <= 1.0) || (x2.y() >= -1.0 && x2.y() <= 1.0);
// 两头都看不到了,其实中间的还能看到。
for (int i = 0; i <= 100 && !retv; i++) {
float tmpx = x1.y() + (x2.y() - x1.y()) * i / 100;
retv = retv || (tmpx >= -1.0 && tmpx <= 1.0);
}
if (!retv) {
if (x1outside.y() <= -1.0) {
recommend = abs(m_offset.y()) / 100;
} else if (x2outside.y() >= 1.0) {
recommend = -abs(m_offset.y()) / 100;
} else {
assert(false);
}
}
return retv;
}
bool FlipWidgetAngle::checkResetScaleOffset() {
for (int i = 0; i <= 100; i++) {
assert(i != 100); // 如果到这一步了,存在 bug 了。
bool changed = false;
float recommend = 0;
if (!checkScaleOffset(true, recommend)) {
m_offset.setX(m_offset.x() + recommend);
changed = true;
}
recommend = 0;
if (!checkScaleOffset(false, recommend)) {
m_offset.setY(m_offset.y() + recommend);
changed = true;
}
if (!changed) {
break;
}
}
return true;
}
m_offset = projectionk.inverted() * (target1 - target2);
最终实现的效果:
纹理旋转:
auto rect = getPageRect(PAGE_RIGHT);
QMatrix4x4 matrix;
matrix.setToIdentity();
matrix.translate(0.5, 0.5);
matrix.scale(rect.height(), rect.width());
matrix.rotate(GetTickCount(), 0, 0, 1);
matrix.scale(1.0 / rect.height(), 1.0 / rect.width());
matrix.translate(-0.5, -0.5);
program.setUniformValue("texture_matrix", matrix);