1 
2 
3 #include "toonz/plasticdeformerfx.h"
4 
5 // TnzLib includes
6 #include "toonz/txsheet.h"
7 #include "toonz/txshleveltypes.h"
8 #include "toonz/txshcell.h"
9 #include "toonz/tcolumnfx.h"
10 #include "toonz/txshlevelcolumn.h"
11 #include "toonz/dpiscale.h"
12 #include "toonz/stage.h"
13 
14 // TnzExt includes
15 #include "ext/plasticskeleton.h"
16 #include "ext/plasticdeformerstorage.h"
17 #include "ext/ttexturesstorage.h"
18 #include "ext/plasticvisualsettings.h"
19 #include "ext/meshutils.h"
20 
21 // TnzBase includes
22 #include "trenderer.h"
23 
24 // TnzCore includes
25 #include "tgl.h"
26 #include "tofflinegl.h"
27 #include "tgldisplaylistsmanager.h"
28 #include "tconvert.h"
29 #include "trop.h"
30 
31 #include <QOpenGLFramebufferObject>
32 #include <QOffscreenSurface>
33 #include <QSurfaceFormat>
34 #include <QOpenGLContext>
35 #include <QImage>
36 
37 FX_IDENTIFIER_IS_HIDDEN(PlasticDeformerFx, "plasticDeformerFx")
38 
39 //***************************************************************************************************
40 //    Local namespace
41 //***************************************************************************************************
42 
43 namespace {
44 
toString(const TAffine & aff)45 std::string toString(const TAffine &aff) {
46   return
47       // Observe that toString distinguishes + and - 0. That is a problem
48       // when comparing aliases - so near 0 values are explicitly rounded to 0.
49       (areAlmostEqual(aff.a11, 0.0) ? "0" : ::to_string(aff.a11, 5)) + "," +
50       (areAlmostEqual(aff.a12, 0.0) ? "0" : ::to_string(aff.a12, 5)) + "," +
51       (areAlmostEqual(aff.a13, 0.0) ? "0" : ::to_string(aff.a13, 5)) + "," +
52       (areAlmostEqual(aff.a21, 0.0) ? "0" : ::to_string(aff.a21, 5)) + "," +
53       (areAlmostEqual(aff.a22, 0.0) ? "0" : ::to_string(aff.a22, 5)) + "," +
54       (areAlmostEqual(aff.a23, 0.0) ? "0" : ::to_string(aff.a23, 5));
55 }
56 
57 //-----------------------------------------------------------------------------------
58 
toString(SkVD * vd,double sdFrame)59 std::string toString(SkVD *vd, double sdFrame) {
60   std::string result;
61 
62   for (int p = 0; p < SkVD::PARAMS_COUNT; ++p)
63     result += ::to_string(vd->m_params[p]->getValue(sdFrame), 5) + " ";
64 
65   return result;
66 }
67 
68 //-----------------------------------------------------------------------------------
69 
toString(const PlasticSkeleton::vertex_type & vx)70 std::string toString(const PlasticSkeleton::vertex_type &vx) {
71   // TODO: Add z and rigidity
72   return ::to_string(vx.P().x, 5) + " " + ::to_string(vx.P().y, 5);
73 }
74 
75 //-----------------------------------------------------------------------------------
76 
toString(const PlasticSkeletonDeformationP & sd,double sdFrame)77 std::string toString(const PlasticSkeletonDeformationP &sd, double sdFrame) {
78   std::string result;
79 
80   const PlasticSkeletonP &skeleton = sd->skeleton(sdFrame);
81   if (!skeleton || skeleton->empty()) return result;
82 
83   const tcg::list<PlasticSkeleton::vertex_type> &vertices =
84       skeleton->vertices();
85 
86   tcg::list<PlasticSkeleton::vertex_type>::const_iterator vt,
87       vEnd(vertices.end());
88 
89   result = toString(*vertices.begin());
90   for (vt = vertices.begin(); vt != vEnd; ++vt) {
91     result += "; " + toString(*vt);
92     result += " " + toString(sd->vertexDeformation(vt->name()), sdFrame);
93   }
94 
95   return result;
96 }
97 
98 }  // namespace
99 
100 //***************************************************************************************************
101 //    PlasticDeformerFx  implementation
102 //***************************************************************************************************
103 
PlasticDeformerFx()104 PlasticDeformerFx::PlasticDeformerFx() : TRasterFx() {
105   addInputPort("source", m_port);
106 }
107 
108 //-----------------------------------------------------------------------------------
109 
clone(bool recursive) const110 TFx *PlasticDeformerFx::clone(bool recursive) const {
111   PlasticDeformerFx *fx =
112       dynamic_cast<PlasticDeformerFx *>(TFx::clone(recursive));
113   assert(fx);
114 
115   fx->m_xsh = m_xsh;
116   fx->m_col = m_col;
117 
118   return fx;
119 }
120 
121 //-----------------------------------------------------------------------------------
122 
canHandle(const TRenderSettings & info,double frame)123 bool PlasticDeformerFx::canHandle(const TRenderSettings &info, double frame) {
124   // Yep. Affines are handled. Well - it's easy, since OpenGL lets you do that
125   // directly
126   // with a glPushMatrix...
127 
128   return true;
129 }
130 
131 //-----------------------------------------------------------------------------------
132 
getAlias(double frame,const TRenderSettings & info) const133 std::string PlasticDeformerFx::getAlias(double frame,
134                                         const TRenderSettings &info) const {
135   std::string alias(getFxType());
136   alias += "[";
137 
138   if (m_port.isConnected()) {
139     TRasterFxP ifx = m_port.getFx();
140     assert(ifx);
141 
142     alias += ifx->getAlias(frame, info);
143   }
144 
145   TStageObject *meshColumnObj =
146       m_xsh->getStageObject(TStageObjectId::ColumnId(m_col));
147   const PlasticSkeletonDeformationP &sd =
148       meshColumnObj->getPlasticSkeletonDeformation();
149   if (sd) alias += ", " + toString(sd, meshColumnObj->paramsTime(frame));
150 
151   alias + "]";
152 
153   return alias;
154 }
155 
156 //-----------------------------------------------------------------------------------
157 
doGetBBox(double frame,TRectD & bbox,const TRenderSettings & info)158 bool PlasticDeformerFx::doGetBBox(double frame, TRectD &bbox,
159                                   const TRenderSettings &info) {
160   if (!m_port.isConnected()) return false;
161 
162   // It's hard work to calculate the bounding box of a plastic deformation.
163   // Decline.
164   bbox = TConsts::infiniteRectD;
165   return true;
166 }
167 
168 //-----------------------------------------------------------------------------------
169 
buildRenderSettings(double frame,TRenderSettings & info)170 void PlasticDeformerFx::buildRenderSettings(double frame,
171                                             TRenderSettings &info) {
172   // As previously pointed out, this fx is able to handle affines. We can,
173   // actually, *decide*
174   // the input reference to work with.
175 
176   // So, the best choice is to let the *input fx* decide the appropriate
177   // reference, by invoking
178   // its handledAffine() method.
179   m_was64bit = false;
180   if (info.m_bpp == 64) {
181     m_was64bit = true;
182     info.m_bpp = 32;  // We need to fix the input to 32-bpp
183   }
184   info.m_affine = m_port->handledAffine(info, frame);
185 }
186 
187 //-----------------------------------------------------------------------------------
188 
buildTextureDataSl(double frame,TRenderSettings & info,TAffine & worldLevelToLevelAff)189 bool PlasticDeformerFx::buildTextureDataSl(double frame, TRenderSettings &info,
190                                            TAffine &worldLevelToLevelAff) {
191   int row = (int)frame;
192 
193   // Initialize level vars
194   TLevelColumnFx *lcfx       = (TLevelColumnFx *)m_port.getFx();
195   TXshLevelColumn *texColumn = lcfx->getColumn();
196 
197   const TXshCell &texCell = texColumn->getCell(row);
198 
199   TXshSimpleLevel *texSl = texCell.getSimpleLevel();
200   const TFrameId &texFid = texCell.getFrameId();
201 
202   if (!texSl || texSl->getType() == MESH_XSHLEVEL) return false;
203 
204   // Build dpi data
205   TPointD texDpi(texSl->getDpi(texFid, 0));
206   if (texDpi.x == 0.0 || texDpi.y == 0.0 || texSl->getType() == PLI_XSHLEVEL)
207     texDpi.x = texDpi.y = Stage::inch;
208 
209   // Build reference transforms data
210 
211   // NOTE: TAffine() corresponds to IMAGE coordinates here, not WORLD
212   // coordinates. This is achieved
213   // by removing the level's dpi affine during render-tree build-up (see
214   // scenefx.cpp).
215 
216   worldLevelToLevelAff = TScale(texDpi.x / Stage::inch, texDpi.y / Stage::inch);
217 
218   // Initialize input render settings
219 
220   // In the case of vector images, in order to retain the image quality required
221   // by info.m_affine,
222   // the scale component is allowed too.
223 
224   // In the raster image case, we'll use the original image reference IF the
225   // affine is a magnification
226   // (ie the scale is > 1.0) - OTHERWISE, the OpenGL minification filter is too
227   // crude since it renders
228   // a fragment using its 4 adjacent pixels ONLY; in this case, we'll pass the
229   // affine below.
230 
231   const TAffine &handledAff = TRasterFx::handledAffine(info, frame);
232 
233   if (texSl->getType() == PLI_XSHLEVEL) {
234     info.m_affine = handledAff;
235     buildRenderSettings(frame, info);
236   } else {
237     info.m_affine = TAffine();
238     buildRenderSettings(frame, info);
239 
240     // NOTE: scale = handledAff.a11 / worldLevelToLevelAff.a11
241     if (handledAff.a11 < worldLevelToLevelAff.a11)
242       info.m_affine =
243           TScale(handledAff.a11 / worldLevelToLevelAff.a11) * info.m_affine;
244   }
245 
246   return true;
247 }
248 
249 //-----------------------------------------------------------------------------------
250 
buildTextureData(double frame,TRenderSettings & info,TAffine & worldLevelToLevelAff)251 bool PlasticDeformerFx::buildTextureData(double frame, TRenderSettings &info,
252                                          TAffine &worldLevelToLevelAff) {
253   // Common case (typically happen with sub-xsheets)
254 
255   buildRenderSettings(frame, info);  // Adjust the info
256   worldLevelToLevelAff = TAffine();  // Reference match
257 
258   return true;
259 }
260 
261 //-----------------------------------------------------------------------------------
262 
doCompute(TTile & tile,double frame,const TRenderSettings & info)263 void PlasticDeformerFx::doCompute(TTile &tile, double frame,
264                                   const TRenderSettings &info) {
265   if (!m_port.isConnected()) {
266     tile.getRaster()->clear();
267     return;
268   }
269 
270   int row = (int)frame;
271 
272   // Build texture data
273   TRenderSettings texInfo(info);
274   TAffine worldTexLevelToTexLevelAff;
275 
276   if (dynamic_cast<TLevelColumnFx *>(m_port.getFx())) {
277     if (!buildTextureDataSl(frame, texInfo, worldTexLevelToTexLevelAff)) return;
278   } else
279     buildTextureData(frame, texInfo, worldTexLevelToTexLevelAff);
280 
281   // Initialize mesh level vars
282 
283   const TXshCell &meshCell = m_xsh->getCell(row, m_col);
284 
285   TXshSimpleLevel *meshSl = meshCell.getSimpleLevel();
286   const TFrameId &meshFid = meshCell.getFrameId();
287 
288   if (!meshSl || meshSl->getType() != MESH_XSHLEVEL) return;
289 
290   // Retrieve mesh image and deformation
291 
292   TStageObject *meshColumnObj =
293       m_xsh->getStageObject(TStageObjectId::ColumnId(m_col));
294 
295   TMeshImageP mi(meshSl->getFrame(meshFid, false));
296   if (!mi) return;
297 
298   // Retrieve deformation data
299 
300   const PlasticSkeletonDeformationP &sd =
301       meshColumnObj->getPlasticSkeletonDeformation();
302   assert(sd);
303 
304   double sdFrame = meshColumnObj->paramsTime(frame);
305 
306   // Build dpi data
307 
308   TPointD meshDpi(meshSl->getDpi(meshFid, 0));
309   assert(meshDpi.x != 0.0 && meshDpi.y != 0.0);
310 
311   // Build reference transforms data
312 
313   // Build affines
314 
315   const TAffine &imageToTextureAff           = texInfo.m_affine;
316   const TAffine &worldTexLevelToWorldMeshAff = m_texPlacement;
317   const TAffine &meshToWorldMeshAff =
318       TScale(Stage::inch / meshDpi.x, Stage::inch / meshDpi.y);
319 
320   const TAffine &meshToTexLevelAff = worldTexLevelToTexLevelAff *
321                                      worldTexLevelToWorldMeshAff.inv() *
322                                      meshToWorldMeshAff;
323   const TAffine &meshToTextureAff = imageToTextureAff * meshToTexLevelAff;
324 
325   // Retrieve deformer data
326 
327   TScale worldMeshToMeshAff(meshDpi.x / Stage::inch, meshDpi.y / Stage::inch);
328 
329   std::unique_ptr<const PlasticDeformerDataGroup> dataGroup(
330       PlasticDeformerStorage::instance()->processOnce(
331           sdFrame, mi.getPointer(), sd.getPointer(), sd->skeletonId(sdFrame),
332           worldMeshToMeshAff));
333 
334   // Build texture
335 
336   // Build the mesh's bounding box and map it to input reference
337   TRectD meshBBox(meshToTextureAff * mi->getBBox());
338 
339   // Now, build the tile's geometry
340   TRectD texBBox;
341   m_port->getBBox(frame, texBBox, texInfo);
342 
343   TRectD bbox = texBBox * meshBBox;
344   if (bbox.getLx() <= 0.0 || bbox.getLy() <= 0.0) return;
345 
346   bbox.x0 = tfloor(bbox.x0);
347   bbox.y0 = tfloor(bbox.y0);
348   bbox.x1 = tceil(bbox.x1);
349   bbox.y1 = tceil(bbox.y1);
350 
351   TDimension tileSize(tround(bbox.getLx()), tround(bbox.getLy()));
352 
353   // Then, compute the input image
354   TTile inTile;
355   m_port->allocateAndCompute(inTile, bbox.getP00(), tileSize, TRasterP(), frame,
356                              texInfo);
357   QOpenGLContext *context;
358   // Draw the textured mesh
359   {
360     // Prepare texture
361     TRaster32P tex(inTile.getRaster());
362     TRop::depremultiply(tex);  // Textures must be stored depremultiplied.
363                                // See docs about the tglDraw() below.
364     static TAtomicVar var;
365     const std::string &texId = "render_tex " + std::to_string(++var);
366 
367     // Prepare an OpenGL context
368     context = new QOpenGLContext();
369     if (QOpenGLContext::currentContext())
370       context->setShareContext(QOpenGLContext::currentContext());
371     context->setFormat(QSurfaceFormat::defaultFormat());
372     context->create();
373     context->makeCurrent(info.m_offScreenSurface.get());
374 
375     TDimension d = tile.getRaster()->getSize();
376     QOpenGLFramebufferObject fb(d.lx, d.ly);
377 
378     fb.bind();
379 
380     // Load texture into the context
381     TTexturesStorage *ts                = TTexturesStorage::instance();
382     const DrawableTextureDataP &texData = ts->loadTexture(texId, tex, bbox);
383 
384     // Draw
385     glViewport(0, 0, d.lx, d.ly);
386     glClearColor(0, 0, 0, 0);
387     glClear(GL_COLOR_BUFFER_BIT);
388 
389     glMatrixMode(GL_PROJECTION);
390     glLoadIdentity();
391     gluOrtho2D(0, d.lx, 0, d.ly);
392 
393     glMatrixMode(GL_MODELVIEW);
394     glLoadIdentity();
395     tglMultMatrix(TTranslation(-tile.m_pos) * info.m_affine *
396                   meshToWorldMeshAff);
397 
398     glEnable(GL_BLEND);
399     glEnable(GL_TEXTURE_2D);
400 
401     tglDraw(*mi, *texData, meshToTextureAff, *dataGroup);
402 
403     // Retrieve drawing and copy to output tile
404 
405     QImage img = fb.toImage().scaled(QSize(d.lx, d.ly), Qt::IgnoreAspectRatio,
406                                      Qt::SmoothTransformation);
407     int wrap   = tile.getRaster()->getLx() * sizeof(TPixel32);
408     if (!m_was64bit) {
409       uchar *srcPix = img.bits();
410       uchar *dstPix = tile.getRaster()->getRawData() + wrap * (d.ly - 1);
411       for (int y = 0; y < d.ly; y++) {
412         memcpy(dstPix, srcPix, wrap);
413         dstPix -= wrap;
414         srcPix += wrap;
415       }
416     } else if (m_was64bit) {
417       TRaster64P newRaster(tile.getRaster()->getSize());
418       TRaster32P tempRaster(tile.getRaster()->getSize());
419       uchar *srcPix = img.bits();
420       uchar *dstPix = tempRaster.getPointer()->getRawData() + wrap * (d.ly - 1);
421       for (int y = 0; y < d.ly; y++) {
422         memcpy(dstPix, srcPix, wrap);
423         dstPix -= wrap;
424         srcPix += wrap;
425       }
426       TRop::convert(newRaster, tempRaster);
427       int size = tile.getRaster()->getLx() * tile.getRaster()->getLy() *
428                  sizeof(TPixel64);
429       srcPix = newRaster.getPointer()->getRawData();
430       dstPix = tile.getRaster()->getRawData();
431       memcpy(dstPix, srcPix, size);
432       texInfo.m_bpp = 64;
433     }
434 
435     fb.release();
436 
437     // context->getRaster(tile.getRaster());
438     glFlush();
439     glFinish();
440     // Cleanup
441 
442     // No need to disable stuff - the context dies here
443 
444     // ts->unloadTexture(texId);                                // Auto-released
445     // due to display list destruction
446     context->deleteLater();
447     // context->doneCurrent();
448   }
449   assert(glGetError() == GL_NO_ERROR);
450 }
451 
452 //-----------------------------------------------------------------------------------
453 
doDryCompute(TRectD & rect,double frame,const TRenderSettings & info)454 void PlasticDeformerFx::doDryCompute(TRectD &rect, double frame,
455                                      const TRenderSettings &info) {
456   if (!m_port.isConnected()) return;
457 
458   int row = (int)frame;
459 
460   TRenderSettings texInfo(info);
461   TAffine worldTexLevelToTexLevelAff;
462 
463   if (dynamic_cast<TLevelColumnFx *>(m_port.getFx())) {
464     if (!buildTextureDataSl(frame, texInfo, worldTexLevelToTexLevelAff)) return;
465   } else
466     buildTextureData(frame, texInfo, worldTexLevelToTexLevelAff);
467 
468   const TXshCell &meshCell = m_xsh->getCell(row, m_col);
469 
470   TXshSimpleLevel *meshSl = meshCell.getSimpleLevel();
471   const TFrameId &meshFid = meshCell.getFrameId();
472 
473   if (!meshSl || meshSl->getType() == MESH_XSHLEVEL) return;
474 
475   TStageObject *meshColumnObj =
476       m_xsh->getStageObject(TStageObjectId::ColumnId(m_col));
477 
478   TMeshImageP mi(meshSl->getFrame(meshFid, false));
479   if (!mi) return;
480 
481   const PlasticSkeletonDeformationP &sd =
482       meshColumnObj->getPlasticSkeletonDeformation();
483   assert(sd);
484 
485   TPointD meshDpi(meshSl->getDpi(meshFid, 0));
486   assert(meshDpi.x != 0.0 && meshDpi.y != 0.0);
487 
488   const TAffine &textureToImageAff        = texInfo.m_affine;
489   const TAffine &worldImageToWorldMeshAff = m_texPlacement;
490   const TAffine &meshToWorldMeshAff =
491       TScale(Stage::inch / meshDpi.x, Stage::inch / meshDpi.y);
492 
493   const TAffine &meshToTextureAff =
494       textureToImageAff.inv() * worldTexLevelToTexLevelAff *
495       worldImageToWorldMeshAff.inv() * meshToWorldMeshAff;
496 
497   // Build the mesh's bounding box and map it to input reference
498   TRectD meshBBox(meshToTextureAff * mi->getBBox());
499 
500   // Now, build the tile's geometry
501   TRectD texBBox;
502   m_port->getBBox(frame, texBBox, texInfo);
503 
504   TRectD bbox = texBBox * meshBBox;
505   if (bbox.getLx() <= 0.0 || bbox.getLy() <= 0.0) return;
506 
507   bbox.x0 = tfloor(bbox.x0);
508   bbox.y0 = tfloor(bbox.y0);
509   bbox.x1 = tceil(bbox.x1);
510   bbox.y1 = tceil(bbox.y1);
511 
512   m_port->dryCompute(bbox, frame, texInfo);
513 }
514