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