11
11
12
12
using namespace scratchcpprender ;
13
13
14
+ static const double pi = std::acos(-1 ); // TODO: Use std::numbers::pi in C++20
15
+
14
16
std::unordered_map<libscratchcpp::IEngine *, IPenLayer *> PenLayer::m_projectPenLayers;
15
17
18
+ // TODO: Move this to a separate class
19
+ template <typename T>
20
+ short sgn (T x)
21
+ {
22
+ return (T (0 ) < x) - (x < T (0 ));
23
+ }
24
+
16
25
PenLayer::PenLayer (QNanoQuickItem *parent) :
17
26
IPenLayer(parent)
18
27
{
@@ -24,7 +33,7 @@ PenLayer::~PenLayer()
24
33
if (m_engine)
25
34
m_projectPenLayers.erase (m_engine);
26
35
27
- if (m_blitter. isCreated () ) {
36
+ if (m_vao != 0 ) {
28
37
// Delete vertex array and buffer
29
38
m_glF->glDeleteVertexArrays (1 , &m_vao);
30
39
m_glF->glDeleteBuffers (1 , &m_vbo);
@@ -68,13 +77,9 @@ void PenLayer::setEngine(libscratchcpp::IEngine *newEngine)
68
77
m_glF->initializeOpenGLFunctions ();
69
78
}
70
79
71
- if (!m_blitter.isCreated ()) {
72
- m_blitter.create ();
73
-
80
+ if (m_vao == 0 ) {
74
81
// Set up VBO and VAO
75
- float vertices[] = {
76
- -1 .0f , -1 .0f , 0 .0f , 0 .0f , 1 .0f , -1 .0f , 1 .0f , 0 .0f , -1 .0f , 1 .0f , 0 .0f , 1 .0f , 1 .0f , 1 .0f , 1 .0f , 1 .0f ,
77
- };
82
+ float vertices[] = { -1 .0f , -1 .0f , 0 .0f , 0 .0f , 1 .0f , -1 .0f , 1 .0f , 0 .0f , -1 .0f , 1 .0f , 0 .0f , 1 .0f , 1 .0f , -1 .0f , 1 .0f , 0 .0f , 1 .0f , 1 .0f , 1 .0f , 1 .0f , -1 .0f , 1 .0f , 0 .0f , 1 .0f };
78
83
79
84
m_glF->glGenVertexArrays (1 , &m_vao);
80
85
m_glF->glGenBuffers (1 , &m_vbo);
@@ -195,74 +200,65 @@ void scratchcpprender::PenLayer::drawLine(const PenAttributes &penAttributes, do
195
200
update ();
196
201
}
197
202
198
- /*
199
- * A brief description of how stamping is implemented:
200
- * 1. Get rotation, size and coordinates and translate them.
201
- * 2. Draw the texture onto a temporary texture using shaders.
202
- * 3. Blit the resulting texture to a FBO with a square texture (required for rotation).
203
- * 4. Blit the resulting texture to the pen layer using QOpenGLTextureBlitter with transform.
204
- *
205
- * If you think this is too complicated, contributions are welcome!
206
- */
207
203
void PenLayer::stamp (IRenderedTarget *target)
208
204
{
209
- if (!target || !m_fbo || !m_texture.isValid () || !m_blitter. isCreated () )
205
+ if (!target || !m_fbo || !m_texture.isValid () || m_vao == 0 || m_vbo == 0 )
210
206
return ;
211
207
212
- double x = 0 ;
213
- double y = 0 ;
214
- double angle = 0 ;
215
- double scale = 1 ;
216
- bool mirror = false ;
217
- std::shared_ptr<libscratchcpp::Costume> costume;
208
+ const float stageWidth = m_engine->stageWidth () * m_scale;
209
+ const float stageHeight = m_engine->stageHeight () * m_scale;
210
+ float x = 0 ;
211
+ float y = 0 ;
212
+ float angle = 180 ;
213
+ float scaleX = 1 ;
214
+ float scaleY = 1 ;
218
215
219
216
SpriteModel *spriteModel = target->spriteModel ();
220
217
221
218
if (spriteModel) {
222
219
libscratchcpp::Sprite *sprite = spriteModel->sprite ();
223
- x = sprite->x ();
224
- y = sprite->y ();
225
220
226
221
switch (sprite->rotationStyle ()) {
227
222
case libscratchcpp::Sprite::RotationStyle::AllAround:
228
- angle = 90 - sprite->direction ();
223
+ angle = 270 - sprite->direction ();
229
224
break ;
230
225
231
226
case libscratchcpp::Sprite::RotationStyle::LeftRight:
232
- mirror = (sprite->direction () < 0 );
227
+ scaleX = sgn (sprite->direction ());
233
228
break ;
234
229
235
230
default :
236
231
break ;
237
232
}
238
233
239
- scale = sprite->size () / 100 ;
240
- costume = sprite->currentCostume ();
241
- } else
242
- costume = target->stageModel ()->stage ()->currentCostume ();
243
-
244
- // Apply scale (HQ pen)
245
- scale *= m_scale;
234
+ scaleY = sprite->size () / 100 ;
235
+ scaleX *= scaleY;
236
+ }
246
237
247
- const double bitmapRes = costume->bitmapResolution ();
248
- const double centerX = costume->rotationCenterX () / bitmapRes;
249
- const double centerY = costume->rotationCenterY () / bitmapRes;
238
+ scaleX *= m_scale;
239
+ scaleY *= m_scale;
250
240
241
+ libscratchcpp::Rect bounds = target->getFastBounds ();
251
242
const Texture &texture = target->cpuTexture ();
252
243
253
244
if (!texture.isValid ())
254
245
return ;
255
246
256
- const double textureScale = texture.width () / static_cast <double >(target->costumeWidth ());
257
-
258
- // Apply scale (HQ pen)
259
- x *= m_scale;
260
- y *= m_scale;
261
-
262
- // Translate the coordinates
263
- x = std::floor (x + m_texture.width () / 2.0 );
264
- y = std::floor (-y + m_texture.height () / 2.0 );
265
-
247
+ const float textureScale = texture.width () / static_cast <float >(target->costumeWidth ());
248
+ const float skinWidth = texture.width ();
249
+ const float skinHeight = texture.height ();
250
+
251
+ // Projection matrix
252
+ QMatrix4x4 projectionMatrix;
253
+ const float aspectRatio = skinHeight / skinWidth;
254
+ projectionMatrix.ortho (1 .0f , -1 .0f , aspectRatio, -aspectRatio, 0 .1f , 0 .0f );
255
+ projectionMatrix.scale (skinWidth / bounds.width () / m_scale, skinHeight / bounds.height () / m_scale);
256
+
257
+ // Model matrix
258
+ // TODO: This should be calculated and cached by targets
259
+ QMatrix4x4 modelMatrix;
260
+ modelMatrix.rotate (angle, 0 , 0 , 1 );
261
+ modelMatrix.scale (scaleX / textureScale, aspectRatio * scaleY / textureScale);
266
262
m_glF->glDisable (GL_SCISSOR_TEST);
267
263
268
264
// For some reason nothing is rendered without this
@@ -271,10 +267,6 @@ void PenLayer::stamp(IRenderedTarget *target)
271
267
m_painter->stroke ();
272
268
m_painter->endFrame ();
273
269
274
- // Create a temporary FBO for graphic effects
275
- QOpenGLFramebufferObject tmpFbo (texture.size ());
276
- m_painter->beginFrame (tmpFbo.width (), tmpFbo.height ());
277
-
278
270
// Create a FBO for the current texture
279
271
unsigned int fbo;
280
272
m_glF->glGenFramebuffers (1 , &fbo);
@@ -287,6 +279,9 @@ void PenLayer::stamp(IRenderedTarget *target)
287
279
return ;
288
280
}
289
281
282
+ // Set viewport
283
+ m_glF->glViewport ((stageWidth / 2 ) + bounds.left () * m_scale, (stageHeight / 2 ) + bounds.bottom () * m_scale, bounds.width () * m_scale, bounds.height () * m_scale);
284
+
290
285
// Get the shader program for the current set of effects
291
286
ShaderManager *shaderManager = ShaderManager::instance ();
292
287
@@ -298,32 +293,15 @@ void PenLayer::stamp(IRenderedTarget *target)
298
293
m_glF->glBindBuffer (GL_ARRAY_BUFFER, m_vbo);
299
294
300
295
// Render to the target framebuffer
301
- m_glF->glBindFramebuffer (GL_FRAMEBUFFER, tmpFbo. handle ());
296
+ m_glF->glBindFramebuffer (GL_FRAMEBUFFER, m_fbo-> handle ());
302
297
shaderProgram->bind ();
303
298
m_glF->glBindVertexArray (m_vao);
304
299
m_glF->glActiveTexture (GL_TEXTURE0);
305
300
m_glF->glBindTexture (GL_TEXTURE_2D, texture.handle ());
306
301
shaderManager->setUniforms (shaderProgram, 0 , texture.size (), effects); // set texture and effect uniforms
307
- m_glF->glDrawArrays (GL_TRIANGLE_STRIP, 0 , 4 );
308
-
309
- m_painter->endFrame ();
310
-
311
- // Resize to square (for rotation)
312
- const double dim = std::max (tmpFbo.width (), tmpFbo.height ());
313
- QOpenGLFramebufferObject resizeFbo (dim, dim);
314
- resizeFbo.bind ();
315
- m_painter->beginFrame (dim, dim);
316
-
317
- const QRect resizeRect (QPoint (0 , 0 ), tmpFbo.size ());
318
- const QMatrix4x4 matrix = QOpenGLTextureBlitter::targetTransform (resizeRect, QRect (QPoint (0 , 0 ), resizeFbo.size ()));
319
- m_glF->glClearColor (0 .0f , 0 .0f , 0 .0f , 0 .0f );
320
- m_glF->glClear (GL_COLOR_BUFFER_BIT);
321
- m_blitter.bind ();
322
- m_blitter.blit (tmpFbo.texture (), matrix, QOpenGLTextureBlitter::OriginBottomLeft);
323
- m_blitter.release ();
324
-
325
- m_painter->endFrame ();
326
- resizeFbo.release ();
302
+ shaderProgram->setUniformValue (" u_projectionMatrix" , projectionMatrix);
303
+ shaderProgram->setUniformValue (" u_modelMatrix" , modelMatrix);
304
+ m_glF->glDrawArrays (GL_TRIANGLES, 0 , 6 );
327
305
328
306
// Cleanup
329
307
shaderProgram->release ();
@@ -332,27 +310,6 @@ void PenLayer::stamp(IRenderedTarget *target)
332
310
m_glF->glBindFramebuffer (GL_FRAMEBUFFER, 0 );
333
311
m_glF->glDeleteFramebuffers (1 , &fbo);
334
312
335
- // Transform
336
- const double width = resizeFbo.width () / textureScale;
337
- const double height = resizeFbo.height () / textureScale;
338
- QRectF targetRect (QPoint (x, y), QSizeF (width, height));
339
- QTransform transform = QOpenGLTextureBlitter::targetTransform (targetRect, QRect (QPoint (centerX, centerY), m_fbo->size ())).toTransform ();
340
- const double dx = 2 * (centerX - width / 2.0 ) / width;
341
- const double dy = -2 * (centerY - height / 2.0 ) / height;
342
- transform.translate (dx, dy);
343
- transform.rotate (angle);
344
- transform.scale (scale * (mirror ? -1 : 1 ), scale);
345
- transform.translate (-dx, -dy);
346
-
347
- // Blit
348
- m_fbo->bind ();
349
- m_painter->beginFrame (m_fbo->width (), m_fbo->height ());
350
- m_blitter.bind ();
351
- m_blitter.blit (resizeFbo.texture (), transform, QOpenGLTextureBlitter::OriginBottomLeft);
352
- m_blitter.release ();
353
- m_painter->endFrame ();
354
- m_fbo->release ();
355
-
356
313
m_glF->glEnable (GL_SCISSOR_TEST);
357
314
358
315
m_textureDirty = true ;
0 commit comments