1111
1212using namespace scratchcpprender ;
1313
14+ static const double pi = std::acos(-1 ); // TODO: Use std::numbers::pi in C++20
15+
1416std::unordered_map<libscratchcpp::IEngine *, IPenLayer *> PenLayer::m_projectPenLayers;
1517
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+
1625PenLayer::PenLayer (QNanoQuickItem *parent) :
1726 IPenLayer(parent)
1827{
@@ -24,10 +33,13 @@ PenLayer::~PenLayer()
2433 if (m_engine)
2534 m_projectPenLayers.erase (m_engine);
2635
27- if (m_blitter. isCreated () ) {
36+ if (m_vao != 0 ) {
2837 // Delete vertex array and buffer
2938 m_glF->glDeleteVertexArrays (1 , &m_vao);
3039 m_glF->glDeleteBuffers (1 , &m_vbo);
40+
41+ // Delete stamp FBO
42+ m_glF->glDeleteFramebuffers (1 , &m_stampFbo);
3143 }
3244}
3345
@@ -68,13 +80,9 @@ void PenLayer::setEngine(libscratchcpp::IEngine *newEngine)
6880 m_glF->initializeOpenGLFunctions ();
6981 }
7082
71- if (!m_blitter.isCreated ()) {
72- m_blitter.create ();
73-
83+ if (m_vao == 0 ) {
7484 // 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- };
85+ 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 };
7886
7987 m_glF->glGenVertexArrays (1 , &m_vao);
8088 m_glF->glGenBuffers (1 , &m_vbo);
@@ -94,6 +102,9 @@ void PenLayer::setEngine(libscratchcpp::IEngine *newEngine)
94102
95103 m_glF->glBindVertexArray (0 );
96104 m_glF->glBindBuffer (GL_ARRAY_BUFFER, 0 );
105+
106+ // Create stamp FBO
107+ m_glF->glGenFramebuffers (1 , &m_stampFbo);
97108 }
98109
99110 clear ();
@@ -195,98 +206,81 @@ void scratchcpprender::PenLayer::drawLine(const PenAttributes &penAttributes, do
195206 update ();
196207}
197208
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- */
207209void PenLayer::stamp (IRenderedTarget *target)
208210{
209- if (!target || !m_fbo || !m_texture.isValid () || !m_blitter. isCreated () )
211+ if (!target || !m_fbo || !m_texture.isValid () || m_vao == 0 || m_vbo == 0 )
210212 return ;
211213
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;
214+ const float stageWidth = m_engine->stageWidth () * m_scale;
215+ const float stageHeight = m_engine->stageHeight () * m_scale;
216+ float x = 0 ;
217+ float y = 0 ;
218+ float angle = 180 ;
219+ float scaleX = 1 ;
220+ float scaleY = 1 ;
218221
219222 SpriteModel *spriteModel = target->spriteModel ();
220223
221224 if (spriteModel) {
222225 libscratchcpp::Sprite *sprite = spriteModel->sprite ();
223- x = sprite->x ();
224- y = sprite->y ();
225226
226227 switch (sprite->rotationStyle ()) {
227228 case libscratchcpp::Sprite::RotationStyle::AllAround:
228- angle = 90 - sprite->direction ();
229+ angle = 270 - sprite->direction ();
229230 break ;
230231
231232 case libscratchcpp::Sprite::RotationStyle::LeftRight:
232- mirror = (sprite->direction () < 0 );
233+ scaleX = sgn (sprite->direction ());
233234 break ;
234235
235236 default :
236237 break ;
237238 }
238239
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;
240+ scaleY = sprite->size () / 100 ;
241+ scaleX *= scaleY;
242+ }
246243
247- const double bitmapRes = costume->bitmapResolution ();
248- const double centerX = costume->rotationCenterX () / bitmapRes;
249- const double centerY = costume->rotationCenterY () / bitmapRes;
244+ scaleX *= m_scale;
245+ scaleY *= m_scale;
250246
247+ libscratchcpp::Rect bounds = target->getFastBounds ();
251248 const Texture &texture = target->cpuTexture ();
252249
253250 if (!texture.isValid ())
254251 return ;
255252
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-
253+ const float textureScale = texture.width () / static_cast <float >(target->costumeWidth ());
254+ const float skinWidth = texture.width ();
255+ const float skinHeight = texture.height ();
256+
257+ // Projection matrix
258+ QMatrix4x4 projectionMatrix;
259+ const float aspectRatio = skinHeight / skinWidth;
260+ projectionMatrix.ortho (1 .0f , -1 .0f , aspectRatio, -aspectRatio, 0 .1f , 0 .0f );
261+ projectionMatrix.scale (skinWidth / bounds.width () / m_scale, skinHeight / bounds.height () / m_scale);
262+
263+ // Model matrix
264+ // TODO: This should be calculated and cached by targets
265+ QMatrix4x4 modelMatrix;
266+ modelMatrix.rotate (angle, 0 , 0 , 1 );
267+ modelMatrix.scale (scaleX / textureScale, aspectRatio * scaleY / textureScale);
266268 m_glF->glDisable (GL_SCISSOR_TEST);
267-
268- // For some reason nothing is rendered without this
269- // TODO: Find out why this is happening
270- m_painter->beginFrame (m_fbo->width (), m_fbo->height ());
271- m_painter->stroke ();
272- m_painter->endFrame ();
273-
274- // Create a temporary FBO for graphic effects
275- QOpenGLFramebufferObject tmpFbo (texture.size ());
276- m_painter->beginFrame (tmpFbo.width (), tmpFbo.height ());
269+ m_glF->glDisable (GL_DEPTH_TEST);
277270
278271 // Create a FBO for the current texture
279- unsigned int fbo;
280- m_glF->glGenFramebuffers (1 , &fbo);
281- m_glF->glBindFramebuffer (GL_FRAMEBUFFER, fbo);
272+ m_glF->glBindFramebuffer (GL_FRAMEBUFFER, m_stampFbo);
282273 m_glF->glFramebufferTexture2D (GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture.handle (), 0 );
283274
284275 if (m_glF->glCheckFramebufferStatus (GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
285276 qWarning () << " error: framebuffer incomplete (stamp " + target->scratchTarget ()->name () + " )" ;
286- m_glF->glDeleteFramebuffers ( 1 , &fbo );
277+ m_glF->glBindFramebuffer (GL_FRAMEBUFFER, 0 );
287278 return ;
288279 }
289280
281+ // Set viewport
282+ m_glF->glViewport ((stageWidth / 2 ) + bounds.left () * m_scale, (stageHeight / 2 ) + bounds.bottom () * m_scale, bounds.width () * m_scale, bounds.height () * m_scale);
283+
290284 // Get the shader program for the current set of effects
291285 ShaderManager *shaderManager = ShaderManager::instance ();
292286
@@ -298,62 +292,24 @@ void PenLayer::stamp(IRenderedTarget *target)
298292 m_glF->glBindBuffer (GL_ARRAY_BUFFER, m_vbo);
299293
300294 // Render to the target framebuffer
301- m_glF->glBindFramebuffer (GL_FRAMEBUFFER, tmpFbo. handle ());
295+ m_glF->glBindFramebuffer (GL_FRAMEBUFFER, m_fbo-> handle ());
302296 shaderProgram->bind ();
303297 m_glF->glBindVertexArray (m_vao);
304298 m_glF->glActiveTexture (GL_TEXTURE0);
305299 m_glF->glBindTexture (GL_TEXTURE_2D, texture.handle ());
306300 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 ();
301+ shaderProgram->setUniformValue (" u_projectionMatrix" , projectionMatrix);
302+ shaderProgram->setUniformValue (" u_modelMatrix" , modelMatrix);
303+ m_glF->glDrawArrays (GL_TRIANGLES, 0 , 6 );
327304
328305 // Cleanup
329306 shaderProgram->release ();
330307 m_glF->glBindVertexArray (0 );
331308 m_glF->glBindBuffer (GL_ARRAY_BUFFER, 0 );
332309 m_glF->glBindFramebuffer (GL_FRAMEBUFFER, 0 );
333- m_glF->glDeleteFramebuffers (1 , &fbo);
334-
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 ();
355310
356311 m_glF->glEnable (GL_SCISSOR_TEST);
312+ m_glF->glEnable (GL_DEPTH_TEST);
357313
358314 m_textureDirty = true ;
359315 m_boundsDirty = true ;
0 commit comments