1 
2 
3 // TnzBase includes
4 #include "tfxattributes.h"
5 #include "tfxutil.h"
6 #include "tmacrofx.h"
7 #include "toutputproperties.h"
8 #include "tparamcontainer.h"
9 
10 // TnzLib includes
11 #include "toonz/txsheet.h"
12 #include "toonz/tstageobjecttree.h"
13 #include "toonz/tcolumnfx.h"
14 #include "toonz/tcolumnfxset.h"
15 #include "toonz/fxdag.h"
16 #include "toonz/txshchildlevel.h"
17 #include "toonz/txshcell.h"
18 #include "toonz/txshleveltypes.h"
19 #include "toonz/txshlevelcolumn.h"
20 #include "toonz/txshpalettecolumn.h"
21 #include "toonz/txshzeraryfxcolumn.h"
22 #include "toonz/txshsimplelevel.h"
23 #include "toonz/dpiscale.h"
24 #include "toonz/tcamera.h"
25 #include "toonz/toonzscene.h"
26 #include "toonz/sceneproperties.h"
27 #include "toonz/plasticdeformerfx.h"
28 #include "toonz/stage.h"
29 #include "toonz/preferences.h"
30 #include "ttzpimagefx.h"
31 #include "toonz/txshsoundtextcolumn.h"
32 #include "toonz/txshsoundtextlevel.h"
33 
34 #include "../stdfx/motionawarebasefx.h"
35 #include "../stdfx/textawarebasefx.h"
36 
37 #include "toonz/scenefx.h"
38 
39 /*
40   TODO: Some parts of the following render-tree building procedure should be
41   revised. In particular,
42         there is scarce support for frame-shifting fxs, whenever the frame-shift
43   can be resolved
44         only during rendering (as is the case for ParticlesFx).
45 */
46 
47 //***************************************************************************************************
48 //    TimeShuffleFx  definition
49 //***************************************************************************************************
50 
51 //! TimeShuffleFx is the rendering-tree equivalent of a sub-xsheet column.
52 /*!
53   TimeShuffleFx is a special-purpose fx which is used in render-tree building
54 procedures
55   to simulate the effect of a sub-xsheet.
56 \n\n
57   A rendering tree is a fully expanded tree that mixes implicit xsheet nesting
58 with
59   the explicit fxs dag <I> for a specific frame <\I>. Since the frame the tree
60 is developed from
61   is fixed, a sub-xsheet can be seen as a <I> frame setter <\I> fx.
62 */
63 
64 class TimeShuffleFx final : public TRasterFx {
65   FX_DECLARATION(TimeShuffleFx)
66 
67 private:
68   int m_frame;                 //!< Frame this fx redirects to
69   TFxTimeRegion m_timeRegion;  //!< Input (outer) valid column frame range
70   TRasterFxPort m_port;        //!< Input port
71   TXshCellColumn *m_cellColumn;
72 
73 public:
TimeShuffleFx()74   TimeShuffleFx()
75       : TRasterFx(), m_frame(0), m_timeRegion(), m_cellColumn(nullptr) {
76     addInputPort("source", m_port);
77   }
~TimeShuffleFx()78   ~TimeShuffleFx() {}
79 
clone(bool recursive=true) const80   TFx *clone(bool recursive = true) const override {
81     TimeShuffleFx *fx = dynamic_cast<TimeShuffleFx *>(TFx::clone(recursive));
82     assert(fx);
83 
84     fx->setFrame(m_frame);
85     fx->setTimeRegion(getTimeRegion());
86     fx->setCellColumn(m_cellColumn);
87 
88     return fx;
89   }
90 
getFrame() const91   int getFrame() const { return m_frame; }
setFrame(int frame)92   void setFrame(int frame) { m_frame = frame; }
93 
setTimeRegion(const TFxTimeRegion & timeRegion)94   void setTimeRegion(const TFxTimeRegion &timeRegion) {
95     m_timeRegion = timeRegion;
96   }
getTimeRegion() const97   TFxTimeRegion getTimeRegion() const override { return m_timeRegion; }
98 
setCellColumn(TXshCellColumn * cellColumn)99   void setCellColumn(TXshCellColumn *cellColumn) { m_cellColumn = cellColumn; }
100 
canHandle(const TRenderSettings & info,double frame)101   bool canHandle(const TRenderSettings &info, double frame) override {
102     return true;
103   }
104 
getPluginId() const105   std::string getPluginId() const override { return std::string(); }
106 
getLevelFrame(int frame) const107   int getLevelFrame(int frame) const {
108     if (!m_cellColumn) return m_frame;
109     TXshCell cell = m_cellColumn->getCell(tfloor(frame));
110     assert(!cell.isEmpty());
111     return cell.m_frameId.getNumber() - 1;
112   }
113 
doCompute(TTile & tile,double frame,const TRenderSettings & ri)114   void doCompute(TTile &tile, double frame,
115                  const TRenderSettings &ri) override {
116     if (!m_port.isConnected()) {
117       tile.getRaster()->clear();
118       return;
119     }
120 
121     // Exchange frame with the stored one
122     TRasterFxP(m_port.getFx())->compute(tile, getLevelFrame(frame), ri);
123   }
124 
doGetBBox(double frame,TRectD & bbox,const TRenderSettings & info)125   bool doGetBBox(double frame, TRectD &bbox,
126                  const TRenderSettings &info) override {
127     if (!m_port.isConnected()) return false;
128     return TRasterFxP(m_port.getFx())
129         ->doGetBBox(getLevelFrame(frame), bbox, info);
130   }
131 
getAlias(double frame,const TRenderSettings & info) const132   std::string getAlias(double frame,
133                        const TRenderSettings &info) const override {
134     return TRasterFx::getAlias(getLevelFrame(frame), info);
135   }
136 
doDryCompute(TRectD & rect,double frame,const TRenderSettings & info)137   void doDryCompute(TRectD &rect, double frame,
138                     const TRenderSettings &info) override {
139     if (m_port.isConnected())
140       TRasterFxP(m_port.getFx())->dryCompute(rect, getLevelFrame(frame), info);
141   }
142 
143 private:
144   // not implemented
145   TimeShuffleFx(const TimeShuffleFx &);
146   TimeShuffleFx &operator=(const TimeShuffleFx &);
147 };
148 
149 FX_IDENTIFIER_IS_HIDDEN(TimeShuffleFx, "timeShuffleFx")
150 
151 //***************************************************************************************************
152 //    AffineFx  definition
153 //***************************************************************************************************
154 
155 //! AffineFx is a specialization of TGeometryFx which implements animated or
156 //! stage-controlled affines
157 /*!
158   This specific implementation of TGeometryFx is needed to deal with those
159   affines which are best
160   \b not resolved during the rendering-tree expansion procedure.
161 */
162 
163 class AffineFx final : public TGeometryFx {
164   FX_DECLARATION(AffineFx)
165 
166 private:
167   TXsheet *m_xsheet;            //!< Xsheet owning m_stageObject
168   TStageObject *m_stageObject;  //!< The stage object this AffineFx refers to
169   TRasterFxPort m_input;        //!< The input port
170 
171 public:
AffineFx()172   AffineFx() : m_xsheet(0), m_stageObject(0) {
173     addInputPort("source", m_input);
174     setName(L"AffineFx");
175   }
AffineFx(TXsheet * xsh,TStageObject * pegbar)176   AffineFx(TXsheet *xsh, TStageObject *pegbar)
177       : m_xsheet(xsh), m_stageObject(pegbar) {
178     addInputPort("source", m_input);
179     setName(L"AffineFx");
180   }
~AffineFx()181   ~AffineFx() {}
182 
clone(bool recursive=true) const183   TFx *clone(bool recursive = true) const override {
184     AffineFx *fx = dynamic_cast<AffineFx *>(TFx::clone(recursive));
185     assert(fx);
186     fx->m_stageObject = m_stageObject;
187     fx->m_xsheet      = m_xsheet;
188     return fx;
189   }
190 
canHandle(const TRenderSettings & info,double frame)191   bool canHandle(const TRenderSettings &info, double frame) override {
192     return true;
193   }
194 
getPlacement(double frame)195   TAffine getPlacement(double frame) override {
196     TAffine objAff = m_stageObject->getPlacement(frame);
197 
198     double objZ        = m_stageObject->getZ(frame);
199     double objNoScaleZ = m_stageObject->getGlobalNoScaleZ();
200 
201     TStageObjectId cameraId =
202         m_xsheet->getStageObjectTree()->getCurrentCameraId();
203     TStageObject *camera = m_xsheet->getStageObject(cameraId);
204     TAffine cameraAff    = camera->getPlacement(frame);
205     double cameraZ       = camera->getZ(frame);
206 
207     TAffine aff;
208     bool isVisible = TStageObject::perspective(aff, cameraAff, cameraZ, objAff,
209                                                objZ, objNoScaleZ);
210 
211     if (!isVisible)
212       return TAffine();  // uh oh
213     else
214       return aff;
215   }
216 
getParentPlacement(double frame)217   TAffine getParentPlacement(double frame) override {
218     return m_stageObject->getPlacement(frame);
219   }
220 
getPluginId() const221   std::string getPluginId() const override { return std::string(); }
222 
223 private:
224   // not implemented
225   AffineFx(const AffineFx &);
226   AffineFx &operator=(const AffineFx &);
227 };
228 
229 FX_IDENTIFIER_IS_HIDDEN(AffineFx, "affineFx")
230 
231 //***************************************************************************************************
232 //    PlacedFx  definition
233 //***************************************************************************************************
234 
235 //! PlacedFx is the enriched form of a TRasterFx during render-tree building.
236 class PlacedFx {
237 public:
238   double m_z;         //!< Z value for this fx's column
239   double m_so;        //!< Same as above, for stacking order
240   int m_columnIndex;  //!< This fx's column index
241 
242   TFxP m_fx;      //!< The referenced fx
243   TAffine m_aff;  //!<
244 
245   TFxPort *m_leftXsheetPort;
246 
247 public:
PlacedFx()248   PlacedFx()
249       : m_z(0)
250       , m_so(0)
251       , m_columnIndex(-1)
252       , m_fx(0)
253       , m_aff()
254       , m_leftXsheetPort(0) {}
PlacedFx(const TFxP & fx)255   explicit PlacedFx(const TFxP &fx)
256       : m_z(0)
257       , m_so(0)
258       , m_columnIndex(-1)
259       , m_fx(fx)
260       , m_aff()
261       , m_leftXsheetPort(0) {}
262 
operator <(const PlacedFx & pf) const263   bool operator<(const PlacedFx &pf) const {
264     return (m_z < pf.m_z)
265                ? true
266                : (m_z > pf.m_z)
267                      ? false
268                      : (m_so < pf.m_so)
269                            ? true
270                            : (m_so > pf.m_so)
271                                  ? false
272                                  : (m_columnIndex < pf.m_columnIndex);
273   }
274 
makeFx()275   TFxP makeFx() {
276     return (!m_fx)
277                ? TFxP()
278                : (m_aff == TAffine()) ? m_fx : TFxUtil::makeAffine(m_fx, m_aff);
279   }
280 };
281 
282 //***************************************************************************************************
283 //    Local namespace
284 //***************************************************************************************************
285 
286 namespace {
287 
timeShuffle(TFxP fx,int frame,TFxTimeRegion timeRegion,TXshCellColumn * cellColumn)288 TFxP timeShuffle(TFxP fx, int frame, TFxTimeRegion timeRegion,
289                  TXshCellColumn *cellColumn) {
290   TimeShuffleFx *timeShuffle = new TimeShuffleFx();
291 
292   timeShuffle->setFrame(frame);
293   timeShuffle->setTimeRegion(timeRegion);
294   timeShuffle->setCellColumn(cellColumn);
295   if (!timeShuffle->connect("source", fx.getPointer()))
296     assert(!"Could not connect ports!");
297 
298   return timeShuffle;
299 };
300 
301 }  // namespace
302 
303 //***************************************************************************************************
304 //    Column-related functions
305 //***************************************************************************************************
306 
getColumnPlacement(TAffine & aff,TXsheet * xsh,double row,int col,bool isPreview)307 bool getColumnPlacement(TAffine &aff, TXsheet *xsh, double row, int col,
308                         bool isPreview) {
309   if (col < 0) return false;
310   TStageObject *pegbar = xsh->getStageObject(TStageObjectId::ColumnId(col));
311   TAffine objAff       = pegbar->getPlacement(row);
312   double objZ          = pegbar->getZ(row);
313   double noScaleZ      = pegbar->getGlobalNoScaleZ();
314 
315   TStageObjectId cameraId;
316   if (isPreview)
317     cameraId = xsh->getStageObjectTree()->getCurrentPreviewCameraId();
318   else
319     cameraId = xsh->getStageObjectTree()->getCurrentCameraId();
320   TStageObject *camera = xsh->getStageObject(cameraId);
321   TAffine cameraAff    = camera->getPlacement(row);
322   double cameraZ       = camera->getZ(row);
323 
324   bool isVisible = TStageObject::perspective(aff, cameraAff, cameraZ, objAff,
325                                              objZ, noScaleZ);
326 
327   return isVisible;
328 }
329 
330 //-------------------------------------------------------------------
331 
getColumnPlacement(PlacedFx & pf,TXsheet * xsh,double row,int col,bool isPreview)332 static bool getColumnPlacement(PlacedFx &pf, TXsheet *xsh, double row, int col,
333                                bool isPreview) {
334   if (col < 0) return false;
335   TStageObject *pegbar = xsh->getStageObject(TStageObjectId::ColumnId(col));
336   TAffine objAff       = pegbar->getPlacement(row);
337   pf.m_z               = pegbar->getZ(row);
338   pf.m_so              = pegbar->getSO(row);
339 
340   TStageObjectId cameraId;
341   if (isPreview)
342     cameraId = xsh->getStageObjectTree()->getCurrentPreviewCameraId();
343   else
344     cameraId = xsh->getStageObjectTree()->getCurrentCameraId();
345   TStageObject *camera = xsh->getStageObject(cameraId);
346   TAffine cameraAff    = camera->getPlacement(row);
347   double cameraZ       = camera->getZ(row);
348 
349   bool isVisible =
350       TStageObject::perspective(pf.m_aff, cameraAff, cameraZ, objAff, pf.m_z,
351                                 pegbar->getGlobalNoScaleZ());
352 
353   return isVisible;
354 }
355 
356 //-------------------------------------------------------------------
357 /*-- Objectの位置を得る --*/
getStageObjectPlacement(TAffine & aff,TXsheet * xsh,double row,TStageObjectId & id,bool isPreview)358 static bool getStageObjectPlacement(TAffine &aff, TXsheet *xsh, double row,
359                                     TStageObjectId &id, bool isPreview) {
360   TStageObject *pegbar = xsh->getStageObjectTree()->getStageObject(id, false);
361   if (!pegbar) return false;
362 
363   TAffine objAff  = pegbar->getPlacement(row);
364   double objZ     = pegbar->getZ(row);
365   double noScaleZ = pegbar->getGlobalNoScaleZ();
366 
367   TStageObjectId cameraId;
368   if (isPreview)
369     cameraId = xsh->getStageObjectTree()->getCurrentPreviewCameraId();
370   else
371     cameraId = xsh->getStageObjectTree()->getCurrentCameraId();
372   TStageObject *camera = xsh->getStageObject(cameraId);
373   TAffine cameraAff    = camera->getPlacement(row);
374   double cameraZ       = camera->getZ(row);
375 
376   bool isVisible = TStageObject::perspective(aff, cameraAff, cameraZ, objAff,
377                                              objZ, noScaleZ);
378 
379   return isVisible;
380 }
381 
382 /*-- typeとindexからStageObjectIdを得る --*/
383 namespace {
getMotionObjectId(MotionObjectType type,int index)384 TStageObjectId getMotionObjectId(MotionObjectType type, int index) {
385   switch (type) {
386   case OBJTYPE_OWN:
387     return TStageObjectId::NoneId;
388     break;
389   case OBJTYPE_COLUMN:
390     if (index == 0) return TStageObjectId::NoneId;
391     return TStageObjectId::ColumnId(index - 1);
392     break;
393   case OBJTYPE_PEGBAR:
394     if (index == 0) return TStageObjectId::NoneId;
395     return TStageObjectId::PegbarId(index - 1);
396     break;
397   case OBJTYPE_TABLE:
398     return TStageObjectId::TableId;
399     break;
400   case OBJTYPE_CAMERA:
401     if (index == 0) return TStageObjectId::NoneId;
402     return TStageObjectId::CameraId(index - 1);
403     break;
404   }
405 
406   return TStageObjectId::NoneId;
407 }
408 };  // namespace
409 
410 //-------------------------------------------------------------------
411 
getColumnSpeed(TXsheet * xsh,double row,int col,bool isPreview)412 static TPointD getColumnSpeed(TXsheet *xsh, double row, int col,
413                               bool isPreview) {
414   TAffine aff;
415   TPointD a, b;
416   const double h = 0.001;
417   getColumnPlacement(aff, xsh, row + h, col, isPreview);
418 
419   /*-- カラムと、カメラの動きに反応 --*/
420   TStageObjectId cameraId;
421   if (isPreview)
422     cameraId = xsh->getStageObjectTree()->getCurrentPreviewCameraId();
423   else
424     cameraId = xsh->getStageObjectTree()->getCurrentCameraId();
425   TStageObject *camera = xsh->getStageObject(cameraId);
426   TAffine cameraAff    = camera->getPlacement(row + h);
427   a                    = aff * TPointD(-cameraAff.a13, -cameraAff.a23);
428 
429   aff = TAffine();
430   getColumnPlacement(aff, xsh, row - h, col, isPreview);
431 
432   cameraAff = camera->getPlacement(row - h);
433   b         = aff * TPointD(-cameraAff.a13, -cameraAff.a23);
434 
435   return (b - a) * (0.5 / h);
436 }
437 
438 //-------------------------------------------------------------------
439 /*-- オブジェクトの軌跡を、基準点との差分で得る
440         objectId: 移動の参考にするオブジェクト。自分自身の場合はNoneId
441 --*/
getColumnMotionPoints(TXsheet * xsh,double row,int col,TStageObjectId & objectId,bool isPreview,double shutterStart,double shutterEnd,int traceResolution)442 static QList<TPointD> getColumnMotionPoints(TXsheet *xsh, double row, int col,
443                                             TStageObjectId &objectId,
444                                             bool isPreview, double shutterStart,
445                                             double shutterEnd,
446                                             int traceResolution) {
447   /*-- 前後フレームが共に0なら空のリストを返す --*/
448   if (shutterStart == 0.0 && shutterEnd == 0.0) return QList<TPointD>();
449 
450   /*-- 現在のカメラを得る --*/
451   TStageObjectId cameraId;
452   if (isPreview)
453     cameraId = xsh->getStageObjectTree()->getCurrentPreviewCameraId();
454   else
455     cameraId = xsh->getStageObjectTree()->getCurrentCameraId();
456   TStageObject *camera = xsh->getStageObject(cameraId);
457   TAffine dpiAff       = getDpiAffine(camera->getCamera());
458 
459   /*-- 基準点の位置を得る --*/
460   TAffine aff;
461 
462   /*-- objectIdが有効なものかどうかチェック --*/
463   bool useOwnMotion = false;
464   if (objectId == TStageObjectId::NoneId ||
465       !xsh->getStageObjectTree()->getStageObject(objectId, false)) {
466     getColumnPlacement(aff, xsh, row, col, isPreview);
467     useOwnMotion = true;
468   } else {
469     getStageObjectPlacement(aff, xsh, row, objectId, isPreview);
470   }
471 
472   TAffine cameraAff = camera->getPlacement(row);
473   TPointD basePos =
474       dpiAff.inv() * aff * TPointD(-cameraAff.a13, -cameraAff.a23);
475 
476   /*-- 結果を収めるリスト --*/
477   QList<TPointD> points;
478   /*-- 軌跡点間のフレーム間隔 --*/
479   double dFrame = (shutterStart + shutterEnd) / (double)traceResolution;
480   /*-- 各点の位置を、基準点との差分で格納していく --*/
481   for (int i = 0; i <= traceResolution; i++) {
482     /*-- 基準位置とのフレーム差 --*/
483     double frameOffset = -shutterStart + dFrame * (double)i;
484     /*-- 基準位置とのフレーム差が無ければ、基準点に一致するので差分は0を入れる
485      * --*/
486     if (frameOffset == 0.0) {
487       points.append(TPointD(0.0, 0.0));
488       continue;
489     }
490 
491     double targetFrame = row + frameOffset;
492     // Proper position cannot be obtained for frame = -1.0
493     if (targetFrame == -1.0) targetFrame = -0.9999;
494 
495     /*-- 自分自身の動きを使うか、別オブジェクトの動きを使うか --*/
496     if (useOwnMotion)
497       getColumnPlacement(aff, xsh, targetFrame, col, isPreview);
498     else
499       getStageObjectPlacement(aff, xsh, targetFrame, objectId, isPreview);
500 
501     TAffine cameraAff = camera->getPlacement(targetFrame);
502     TPointD tmpPos =
503         dpiAff.inv() * aff * TPointD(-cameraAff.a13, -cameraAff.a23);
504 
505     /*-- 基準位置との差を記録 --*/
506     points.append(tmpPos - basePos);
507   }
508   return points;
509 }
510 
511 namespace {
512 
getNoteText(TXsheet * xsh,double row,int col,int noteColumnIndex,bool neighbor)513 QString getNoteText(TXsheet *xsh, double row, int col, int noteColumnIndex,
514                     bool neighbor) {
515   int colIndex;
516   if (neighbor)
517     colIndex = col - 1;
518   else
519     colIndex = noteColumnIndex;
520 
521   TXshColumn *column = xsh->getColumn(colIndex);
522   if (!column || !column->getSoundTextColumn()) return QString();
523 
524   TXshCell cell = xsh->getCell(row, colIndex);
525   if (cell.isEmpty() || !cell.getSoundTextLevel()) return QString();
526 
527   return cell.getSoundTextLevel()->getFrameText(cell.m_frameId.getNumber() - 1);
528 }
529 };  // namespace
530 
531 //***************************************************************************************************
532 //    FxBuilder  definition
533 //***************************************************************************************************
534 
535 class FxBuilder {
536 public:
537   ToonzScene *m_scene;
538   TXsheet *m_xsh;
539   TAffine m_cameraAff;
540   double m_cameraZ;
541   double m_frame;
542   int m_whichLevels;
543   bool m_isPreview;
544   bool m_expandXSheet;
545 
546   // in the makePF() methods m_particleDescendentCount>0 iff the TFx* is an
547   // ancestor
548   // (at least) of a particle Fx
549   int m_particleDescendentCount;
550 
551 public:
552   FxBuilder(ToonzScene *scene, TXsheet *xsh, double frame, int whichLevels,
553             bool isPreview = false, bool expandXSheet = true);
554 
555   TFxP buildFx();
556   TFxP buildFx(const TFxP &root, BSFX_Transforms_Enum transforms);
557 
558   PlacedFx makePF(TLevelColumnFx *fx);
559   PlacedFx makePF(TPaletteColumnFx *fx);
560   PlacedFx makePF(TZeraryColumnFx *fx);
561   PlacedFx makePF(TXsheetFx *fx);
562   PlacedFx makePFfromUnaryFx(TFx *fx);
563   PlacedFx makePFfromGenericFx(TFx *fx);
564   PlacedFx makePF(TFx *fx);
565 
566   TFxP getFxWithColumnMovements(const PlacedFx &pf);
567 
568   bool addPlasticDeformerFx(PlacedFx &pf);
569 };
570 
571 //===================================================================
572 
FxBuilder(ToonzScene * scene,TXsheet * xsh,double frame,int whichLevels,bool isPreview,bool expandXSheet)573 FxBuilder::FxBuilder(ToonzScene *scene, TXsheet *xsh, double frame,
574                      int whichLevels, bool isPreview, bool expandXSheet)
575     : m_scene(scene)
576     , m_xsh(xsh)
577     , m_frame(frame)
578     , m_whichLevels(whichLevels)
579     , m_isPreview(isPreview)
580     , m_expandXSheet(expandXSheet)
581     , m_particleDescendentCount(0) {
582   TStageObjectId cameraId;
583   if (m_isPreview)
584     cameraId = m_xsh->getStageObjectTree()->getCurrentPreviewCameraId();
585   else
586     cameraId = m_xsh->getStageObjectTree()->getCurrentCameraId();
587 
588   TStageObject *camera = m_xsh->getStageObject(cameraId);
589   m_cameraAff          = camera->getPlacement(m_frame);
590   m_cameraZ            = camera->getZ(m_frame);
591 }
592 
593 //-------------------------------------------------------------------
594 
buildFx()595 TFxP FxBuilder::buildFx() {
596   TFx *outputFx = m_xsh->getFxDag()->getOutputFx(0);
597   if (!outputFx || outputFx->getInputPortCount() != 1 ||
598       outputFx->getInputPort(0)->getFx() == 0)
599     return TFxP();
600 
601   outputFx->setName(L"OutputFx");
602 
603   assert(m_particleDescendentCount == 0);
604   PlacedFx pf = makePF(outputFx->getInputPort(0)->getFx());
605   assert(m_particleDescendentCount == 0);
606 
607   TAffine cameraFullAff = m_cameraAff * TScale((1000 + m_cameraZ) / 1000);
608   return TFxUtil::makeAffine(pf.makeFx(), cameraFullAff.inv());
609 }
610 
611 //-------------------------------------------------------------------
612 
buildFx(const TFxP & root,BSFX_Transforms_Enum transforms)613 TFxP FxBuilder::buildFx(const TFxP &root, BSFX_Transforms_Enum transforms) {
614   assert(m_particleDescendentCount == 0);
615   PlacedFx pf = makePF(root.getPointer());
616   assert(m_particleDescendentCount == 0);
617 
618   TFxP fx = (transforms & BSFX_COLUMN_TR) ? pf.makeFx() : pf.m_fx;
619   if (transforms & BSFX_CAMERA_TR) {
620     TAffine cameraFullAff = m_cameraAff * TScale((1000 + m_cameraZ) / 1000);
621     fx                    = TFxUtil::makeAffine(fx, cameraFullAff.inv());
622   }
623 
624   return fx;
625 }
626 
627 //-------------------------------------------------------------------
628 
getFxWithColumnMovements(const PlacedFx & pf)629 TFxP FxBuilder::getFxWithColumnMovements(const PlacedFx &pf) {
630   TFxP fx = pf.m_fx;
631   if (!fx) return fx;
632 
633   if (pf.m_columnIndex == -1) return pf.m_fx;
634 
635   TStageObjectId id    = TStageObjectId::ColumnId(pf.m_columnIndex);
636   TStageObject *pegbar = m_xsh->getStageObject(id);
637 
638   AffineFx *affFx = new AffineFx(m_xsh, pegbar);
639   affFx->getInputPort(0)->setFx(fx.getPointer());
640 
641   return affFx;
642 }
643 
644 //-------------------------------------------------------------------
645 
addPlasticDeformerFx(PlacedFx & pf)646 bool FxBuilder::addPlasticDeformerFx(PlacedFx &pf) {
647   TStageObject *obj =
648       m_xsh->getStageObject(TStageObjectId::ColumnId(pf.m_columnIndex));
649   TStageObjectId parentId(obj->getParent());
650 
651   if (parentId.isColumn() && obj->getParentHandle()[0] != 'H') {
652     const SkDP &sd =
653         m_xsh->getStageObject(parentId)->getPlasticSkeletonDeformation();
654 
655     const TXshCell &parentCell = m_xsh->getCell(m_frame, parentId.getIndex());
656     TXshSimpleLevel *parentSl  = parentCell.getSimpleLevel();
657 
658     if (sd && parentSl && (parentSl->getType() == MESH_XSHLEVEL)) {
659       // Plastic Deformer case - add the corresponding fx,
660       // absorb the dpi and local column placement affines
661 
662       PlasticDeformerFx *plasticFx = new PlasticDeformerFx;
663       plasticFx->m_xsh             = m_xsh;
664       plasticFx->m_col             = parentId.getIndex();
665       plasticFx->m_texPlacement    = obj->getLocalPlacement(m_frame);
666 
667       if (!plasticFx->connect("source", pf.m_fx.getPointer()))
668         assert(!"Could not connect ports!");
669 
670       pf.m_fx  = plasticFx;
671       pf.m_aff = pf.m_aff * plasticFx->m_texPlacement.inv();
672 
673       return true;
674     }
675   }
676 
677   return false;
678 }
679 
680 //-------------------------------------------------------------------
681 
makePF(TFx * fx)682 PlacedFx FxBuilder::makePF(TFx *fx) {
683   if (!fx) return PlacedFx();
684 
685   if (TLevelColumnFx *lcfx = dynamic_cast<TLevelColumnFx *>(fx))
686     return makePF(lcfx);
687   else if (TPaletteColumnFx *pcfx = dynamic_cast<TPaletteColumnFx *>(fx))
688     return makePF(pcfx);
689   else if (TZeraryColumnFx *zcfx = dynamic_cast<TZeraryColumnFx *>(fx))
690     return makePF(zcfx);
691   else if (TXsheetFx *xsfx = dynamic_cast<TXsheetFx *>(fx))
692     return makePF(xsfx);
693   else if (fx->getInputPortCount() == 1)
694     return makePFfromUnaryFx(fx);
695   else
696     return makePFfromGenericFx(fx);
697 }
698 
699 //-------------------------------------------------------------------
700 
makePF(TXsheetFx * fx)701 PlacedFx FxBuilder::makePF(TXsheetFx *fx) {
702   if (!m_expandXSheet)  // Xsheet expansion is typically blocked for render-tree
703                         // building of
704     return PlacedFx(fx);  // post-xsheet fxs only.
705 
706   // Expand the render-tree from terminal fxs
707   TFxSet *fxs = m_xsh->getFxDag()->getTerminalFxs();
708   int m       = fxs->getFxCount();
709   if (m == 0) return PlacedFx();
710 
711   std::vector<PlacedFx> pfs(m);
712   int i;
713   for (i = 0; i < m; i++) {
714     // Expand each terminal fx
715     TFx *fx = fxs->getFx(i);
716     assert(fx);
717     pfs[i] = makePF(fx);  // Builds the sub-render-trees here
718   }
719 
720   /*--
721    * Xsheetに複数ノードが繋がっていた場合、PlacedFxの条件に従ってOverノードの付く順番を決める
722    * --*/
723   std::sort(pfs.begin(),
724             pfs.end());  // Sort each terminal depending on Z/SO/Column index
725 
726   // Compose them in a cascade of overs (or affines 'leftXsheetPort' cases)
727   TFxP currentFx =
728       pfs[0].makeFx();  // Adds an NaAffineFx if pf.m_aff is not the identity
729   for (i = 1; i < m; i++) {
730     TFxP fx = pfs[i].makeFx();  // See above
731     if (pfs[i].m_leftXsheetPort) {
732       // LeftXsheetPort cases happen for those fxs like Add, Multiply, etc that
733       // declare an xsheet-like input port.
734       // That is, all terminal fxs below ours are attached COMPOSED to enter the
735       // fx's leftXsheet input port.
736 
737       TFxP inputFx = currentFx;
738       inputFx      = TFxUtil::makeAffine(inputFx, pfs[i].m_aff.inv());
739       pfs[i].m_leftXsheetPort->setFx(inputFx.getPointer());
740       currentFx = fx;
741     } else {
742       if (Preferences::instance()
743               ->isShowRasterImagesDarkenBlendedInViewerEnabled())
744         currentFx = TFxUtil::makeDarken(currentFx, fx);
745       else
746         currentFx = TFxUtil::makeOver(currentFx, fx);  // Common over case
747     }
748   }
749 
750   return PlacedFx(currentFx);
751 }
752 
753 //-------------------------------------------------------------------
754 
755 //! Creates and returns a PlacedFx for a TLevelColumnFx.
756 /*
757   Fxs under a ParticlesFx node seem to have special treatment - that is,
758   empty column cells are still attached to a not-empty PlacedFx.
759 
760   This must be a remnant of old Toonz code, that should no longer remain here -
761   in fact, well, you can only extract an empty render from an empty column!
762   So why bother?
763 */
makePF(TLevelColumnFx * lcfx)764 PlacedFx FxBuilder::makePF(TLevelColumnFx *lcfx) {
765   assert(m_scene);
766   assert(lcfx);
767   assert(lcfx->getColumn());
768   if (!lcfx || !lcfx->getColumn()) return PlacedFx();
769 
770   if (!lcfx->getColumn()->isPreviewVisible())  // This is the 'eye' icon
771                                                // property in the column header
772                                                // interface
773     return PlacedFx();  // that disables rendering of this particular column
774 
775   // Retrieve the corresponding xsheet cell to build up
776   /*-- 現在のフレームのセルを取得 --*/
777   TXshCell cell  = lcfx->getColumn()->getCell(tfloor(m_frame));
778   int levelFrame = cell.m_frameId.getNumber() - 1;
779 
780   /*--  ParticlesFxに繋がっておらず、空セルの場合は 中身無しを返す --*/
781   if (m_particleDescendentCount == 0 && cell.isEmpty()) return PlacedFx();
782 
783   if (m_whichLevels == TOutputProperties::AnimatedOnly) {
784     // In case only 'animated levels' are selected to be rendered, exclude all
785     // 'backgrounds' - that is,
786     // fullcolor levels...
787 
788     // Still, I wonder if this is still used in Toonz. I don't remember seeing
789     // it anywhere :\ ?
790 
791     TXshLevel *xl = cell.m_level.getPointer();
792 
793     /*-- ParticleFxのTextureポートに繋がっていない場合 --*/
794     if (m_particleDescendentCount == 0) {
795       if (!xl ||
796           (xl->getType() != PLI_XSHLEVEL && xl->getType() != TZP_XSHLEVEL &&
797            xl->getType() != CHILD_XSHLEVEL))
798         return PlacedFx();
799     }
800     /*-- ParticleFxのTextureポートに繋がっている場合 --*/
801     else {
802       if (xl && xl->getType() != PLI_XSHLEVEL &&
803           xl->getType() != TZP_XSHLEVEL && xl->getType() != CHILD_XSHLEVEL)
804         return PlacedFx();
805     }
806   }
807 
808   // Build a PlacedFx for the column - start with the standard version for
809   // common (image) levels
810   PlacedFx pf;
811   pf.m_columnIndex = lcfx->getColumn()->getIndex();
812   pf.m_fx          = lcfx;
813 
814   // Build column placement
815   bool columnVisible =
816       getColumnPlacement(pf, m_xsh, m_frame, pf.m_columnIndex, m_isPreview);
817 
818   /*-- subXsheetのとき、その中身もBuildFxを実行 --*/
819   if (!cell.isEmpty() && cell.m_level->getChildLevel()) {
820     // Treat the sub-xsheet case - build the sub-render-tree and reassign stuff
821     // to pf
822     TXsheet *xsh = cell.m_level->getChildLevel()->getXsheet();
823 
824     // Build the sub-render-tree
825     FxBuilder builder(m_scene, xsh, levelFrame, m_whichLevels, m_isPreview);
826 
827     // Then, add the TimeShuffleFx
828     pf.m_fx = timeShuffle(builder.buildFx(), levelFrame, lcfx->getTimeRegion(),
829                           lcfx->getColumn());
830     pf.m_fx->setIdentifier(lcfx->getIdentifier());
831     pf.m_fx->getAttributes()->passiveCacheDataIdx() =
832         lcfx->getAttributes()->passiveCacheDataIdx();
833 
834     // If the level should sustain a Plastic deformation, add the corresponding
835     // fx
836     addPlasticDeformerFx(pf);
837   }
838 
839   if (columnVisible) {
840     // Column is visible, alright
841     TXshSimpleLevel *sl = cell.isEmpty() ? 0 : cell.m_level->getSimpleLevel();
842     if (sl) {
843       // If the level should sustain a Plastic deformation, add the
844       // corresponding fx
845       if (!addPlasticDeformerFx(pf)) {
846         // Common (image) level case - add an NaAffineFx to compensate for the
847         // image's dpi
848         TAffine dpiAff = ::getDpiAffine(
849             sl, cell.m_frameId, true);  // true stands for 'force full-sampling'
850         pf.m_fx = TFxUtil::makeAffine(pf.m_fx, dpiAff);
851         if (pf.m_fx) pf.m_fx->setName(L"LevelColumn AffineFx");
852       }
853     } else {
854       // Okay, weird code ensues. This is what happens on non-common image
855       // cases, which should be:
856 
857       //  1. Sub-Xsheet cases - and it really shouldn't
858       //  2. Empty cell cases - with m_particles_blabla > 0; and again I don't
859       //  get why on earth this should happen...
860 
861       // Please, note that (1) is a bug, although it happens when inserting a
862       // common level and a sub-xsh
863       // level in the same column...
864 
865       // when a cell not exists, there is no way to keep the dpi of the image!
866       // in this case it is kept the dpi of the first cell not empty in the
867       // column!
868       /*--
869        * 空セルのとき、Dpiアフィン変換には、その素材が入っている一番上のセルのものを使う
870        * --*/
871       TXshLevelColumn *column = lcfx->getColumn();
872       int i;
873       for (i = 0; i < column->getRowCount(); i++) {
874         TXshCell dpiCell = lcfx->getColumn()->getCell(i);
875         if (dpiCell.isEmpty()) continue;
876 
877         sl = dpiCell.m_level->getSimpleLevel();
878         if (!sl) break;
879 
880         TAffine dpiAff = ::getDpiAffine(sl, dpiCell.m_frameId, true);
881         pf.m_fx        = TFxUtil::makeAffine(pf.m_fx, dpiAff);
882         break;
883       }
884     }
885 
886     // Apply column's color filter and semi-transparency for rendering
887     TXshLevelColumn *column = lcfx->getColumn();
888     if (m_scene->getProperties()->isColumnColorFilterOnRenderEnabled() &&
889         (column->getFilterColorId() != TXshColumn::FilterNone ||
890          (column->isCamstandVisible() && column->getOpacity() != 255))) {
891       TPixel32 colorScale = column->getFilterColor();
892       colorScale.m        = column->getOpacity();
893       pf.m_fx             = TFxUtil::makeColumnColorFilter(pf.m_fx, colorScale);
894     }
895 
896     return pf;
897   } else
898     return PlacedFx();
899 }
900 
901 //-------------------------------------------------------------------
902 
makePF(TPaletteColumnFx * pcfx)903 PlacedFx FxBuilder::makePF(TPaletteColumnFx *pcfx) {
904   assert(pcfx);
905   assert(pcfx->getColumn());
906   if (!pcfx->getColumn()->isPreviewVisible()) return PlacedFx();
907 
908   TXshCell cell = pcfx->getColumn()->getCell(tfloor(m_frame));
909   if (cell.isEmpty()) return PlacedFx();
910 
911   PlacedFx pf;
912   pf.m_columnIndex = pcfx->getColumn()->getIndex();
913   pf.m_fx          = pcfx;
914 
915   return pf;
916 }
917 
918 //-------------------------------------------------------------------
919 
makePF(TZeraryColumnFx * zcfx)920 PlacedFx FxBuilder::makePF(TZeraryColumnFx *zcfx) {
921   assert(zcfx);
922   assert(zcfx->getColumn());
923 
924   if (!zcfx->getColumn()->isPreviewVisible())  // ...
925     return PlacedFx();
926 
927   if (!zcfx->getAttributes()->isEnabled())  // ...
928     return PlacedFx();
929 
930   TFx *fx = zcfx->getZeraryFx();
931   if (!fx || !fx->getAttributes()->isEnabled())  // ... Perhaps these shouldn't
932                                                  // be tested altogether? Only 1
933                                                  // truly works !
934     return PlacedFx();
935 
936   TXshCell cell = zcfx->getColumn()->getCell(tfloor(m_frame));
937   if (cell.isEmpty()) return PlacedFx();
938 
939   // Build
940   PlacedFx pf;
941   pf.m_columnIndex = zcfx->getColumn()->getIndex();
942   pf.m_fx =
943       fx->clone(false);  // Detach the fx with a clone. Why? It's typically done
944                          // to build fx connections in the render-tree freely.
945                          // Here, it's used just for particles, I guess...
946   // Deal with input sub-trees
947   for (int i = 0; i < fx->getInputPortCount(); ++i) {
948     // Note that only particles should end up here, currently
949     if (TFxP inputFx = fx->getInputPort(i)->getFx()) {
950       PlacedFx inputPF;
951 
952       // if the effect is a particle fx, it is necessary to consider also empty
953       // cells
954       // this causes a connection with the effect and a level also with empty
955       // cells.
956       if (fx->getFxType() == "STD_particlesFx" ||
957           fx->getFxType() == "STD_iwa_TiledParticlesFx" ||
958           fx->getFxType() == "STD_tiledParticlesFx") {
959         m_particleDescendentCount++;
960         inputPF = makePF(inputFx.getPointer());
961         m_particleDescendentCount--;
962       } else
963         inputPF = makePF(inputFx.getPointer());
964 
965       inputFx = getFxWithColumnMovements(inputPF);
966       if (!inputFx) continue;
967 
968       inputFx = TFxUtil::makeAffine(inputFx, pf.m_aff.inv());
969       if (!pf.m_fx->connect(pf.m_fx->getInputPortName(i), inputFx.getPointer()))
970         assert(!"Could not connect ports!");
971     }
972   }
973 
974   if (pf.m_fx->getFxType() == "STD_iwa_TextFx") {
975     TextAwareBaseFx *textFx =
976         dynamic_cast<TextAwareBaseFx *>(pf.m_fx.getPointer());
977     if (textFx && textFx->getSourceType() != TextAwareBaseFx::INPUT_TEXT) {
978       int noteColumnIndex = textFx->getNoteColumnIndex();
979       bool getNeighbor =
980           (textFx->getSourceType() == TextAwareBaseFx::NEARBY_COLUMN);
981       textFx->setNoteLevelStr(getNoteText(m_xsh, m_frame, pf.m_columnIndex,
982                                           noteColumnIndex, getNeighbor));
983     }
984   }
985 
986   // Add the column placement NaAffineFx
987   if (getColumnPlacement(pf, m_xsh, m_frame, pf.m_columnIndex, m_isPreview))
988     return pf;
989   else
990     return PlacedFx();
991 }
992 
993 //-------------------------------------------------------------------
994 
makePFfromUnaryFx(TFx * fx)995 PlacedFx FxBuilder::makePFfromUnaryFx(TFx *fx) {
996   assert(!dynamic_cast<TLevelColumnFx *>(fx));
997   assert(!dynamic_cast<TZeraryColumnFx *>(fx));
998   assert(fx->getInputPortCount() == 1);
999 
1000   TFx *inputFx = fx->getInputPort(0)->getFx();
1001   if (!inputFx) return PlacedFx();
1002 
1003   PlacedFx pf = makePF(inputFx);  // Build sub-render-tree
1004   if (!pf.m_fx) return PlacedFx();
1005 
1006   if (fx->getAttributes()->isEnabled()) {
1007     // Fx is enabled, so insert it in the render-tree
1008 
1009     // Clone this fx necessary
1010     if (pf.m_fx.getPointer() != inputFx ||  // As in an earlier makePF, clone
1011                                             // whenever input connections have
1012                                             // changed
1013         fx->getAttributes()->isSpeedAware() ||  // In the 'speedAware' case,
1014                                                 // we'll alter the fx's
1015                                                 // attributes (see below)
1016         dynamic_cast<TMacroFx *>(fx))  // As for macros... I'm not sure. Not
1017                                        // even who wrote this *understood*
1018     // why - it just solved a bug  X( . Investigate!
1019     {
1020       fx = fx->clone(false);
1021       if (!fx->connect(fx->getInputPortName(0), pf.m_fx.getPointer()))
1022         assert(!"Could not connect ports!");
1023     }
1024 
1025     pf.m_fx = fx;
1026 
1027     if (fx->getAttributes()->isSpeedAware()) {
1028       /*-- スピードでなく、軌跡を取得する場合 --*/
1029       MotionAwareBaseFx *mabfx = dynamic_cast<MotionAwareBaseFx *>(fx);
1030       if (mabfx) {
1031         double shutterStart = mabfx->getShutterStart()->getValue(m_frame);
1032         double shutterEnd   = mabfx->getShutterEnd()->getValue(m_frame);
1033         int traceResolution = mabfx->getTraceResolution()->getValue();
1034         /*-- 移動の参考にするオブジェクトの取得。自分自身の場合はNoneId --*/
1035         MotionObjectType type   = mabfx->getMotionObjectType();
1036         int index               = mabfx->getMotionObjectIndex()->getValue();
1037         TStageObjectId objectId = getMotionObjectId(type, index);
1038         fx->getAttributes()->setMotionPoints(getColumnMotionPoints(
1039             m_xsh, m_frame, pf.m_columnIndex, objectId, m_isPreview,
1040             shutterStart, shutterEnd, traceResolution));
1041       } else {
1042         TPointD speed =
1043             getColumnSpeed(m_xsh, m_frame, pf.m_columnIndex, m_isPreview);
1044         fx->getAttributes()->setSpeed(speed);
1045       }
1046     }
1047   }
1048 
1049   return pf;
1050 }
1051 
1052 //-------------------------------------------------------------------
1053 
makePFfromGenericFx(TFx * fx)1054 PlacedFx FxBuilder::makePFfromGenericFx(TFx *fx) {
1055   assert(!dynamic_cast<TLevelColumnFx *>(fx));
1056   assert(!dynamic_cast<TZeraryColumnFx *>(fx));
1057 
1058   PlacedFx pf;
1059 
1060   if (!fx->getAttributes()->isEnabled()) {
1061     if (fx->getInputPortCount() == 0) return PlacedFx();
1062 
1063     TFxP inputFx = fx->getInputPort(fx->getPreferredInputPort())->getFx();
1064     if (inputFx) return makePF(inputFx.getPointer());
1065 
1066     return pf;
1067   }
1068 
1069   // Multi-input fxs are always cloned - since at least one of its input ports
1070   // will have an NaAffineFx
1071   // injected just before its actual input fx.
1072   pf.m_fx = fx->clone(false);
1073 
1074   bool firstInput = true;
1075 
1076   int m = fx->getInputPortCount();
1077   for (int i = 0; i < m; ++i) {
1078     if (TFxP inputFx = fx->getInputPort(i)->getFx()) {
1079       PlacedFx inputPF = makePF(inputFx.getPointer());
1080       inputFx          = inputPF.m_fx;
1081       if (!inputFx) continue;
1082 
1083       if (firstInput) {
1084         firstInput = false;
1085 
1086         // The first found input PlacedFx carries its placement infos up
1087         pf.m_aff         = inputPF.m_aff;
1088         pf.m_columnIndex = inputPF.m_columnIndex;
1089         pf.m_z           = inputPF.m_z;
1090         pf.m_so          = inputPF.m_so;
1091 
1092         /*-- 軌跡を取得するBinaryFxの場合 --*/
1093         if (pf.m_fx->getAttributes()->isSpeedAware()) {
1094           MotionAwareBaseFx *mabfx =
1095               dynamic_cast<MotionAwareBaseFx *>(pf.m_fx.getPointer());
1096           if (mabfx) {
1097             double shutterStart = mabfx->getShutterStart()->getValue(m_frame);
1098             double shutterEnd   = mabfx->getShutterEnd()->getValue(m_frame);
1099             int traceResolution = mabfx->getTraceResolution()->getValue();
1100             /*-- 移動の参考にするオブジェクトの取得。自分自身の場合はNoneId --*/
1101             MotionObjectType type   = mabfx->getMotionObjectType();
1102             int index               = mabfx->getMotionObjectIndex()->getValue();
1103             TStageObjectId objectId = getMotionObjectId(type, index);
1104             pf.m_fx->getAttributes()->setMotionPoints(getColumnMotionPoints(
1105                 m_xsh, m_frame, pf.m_columnIndex, objectId, m_isPreview,
1106                 shutterStart, shutterEnd, traceResolution));
1107           }
1108         }
1109 
1110       } else {
1111         // The follow-ups traduce their PlacedFx::m_aff into an NaAffineFx,
1112         // instead
1113         inputFx = getFxWithColumnMovements(inputPF);
1114         inputFx = TFxUtil::makeAffine(inputFx, pf.m_aff.inv());
1115       }
1116 
1117       if (!pf.m_fx->connect(pf.m_fx->getInputPortName(i), inputFx.getPointer()))
1118         assert(!"Could not connect ports!");
1119     }
1120   }
1121 
1122   // The xsheet-like input port is activated and brought upwards whenever it is
1123   // both
1124   // specified by the fx, and there is no input fx attached to it.
1125   if (pf.m_fx->getXsheetPort() && pf.m_fx->getXsheetPort()->getFx() == 0)
1126     pf.m_leftXsheetPort = pf.m_fx->getXsheetPort();
1127 
1128   return pf;
1129 }
1130 
1131 //***************************************************************************************************
1132 //    Exported  Render-Tree building  functions
1133 //***************************************************************************************************
1134 
buildSceneFx(ToonzScene * scene,TXsheet * xsh,double row,int whichLevels,int shrink,bool isPreview)1135 TFxP buildSceneFx(ToonzScene *scene, TXsheet *xsh, double row, int whichLevels,
1136                   int shrink, bool isPreview) {
1137   FxBuilder builder(scene, xsh, row, whichLevels, isPreview);
1138   TFxP fx = builder.buildFx();
1139   TStageObjectId cameraId;
1140   if (isPreview)
1141     cameraId = xsh->getStageObjectTree()->getCurrentPreviewCameraId();
1142   else
1143     cameraId = xsh->getStageObjectTree()->getCurrentCameraId();
1144   TStageObject *cameraPegbar = xsh->getStageObject(cameraId);
1145   assert(cameraPegbar);
1146   TCamera *camera = cameraPegbar->getCamera();
1147   assert(camera);
1148 
1149   TAffine aff = getDpiAffine(camera).inv();
1150   if (shrink > 1) {
1151     double fac = 0.5 * (1.0 / shrink - 1.0);
1152     aff = TTranslation(fac * camera->getRes().lx, fac * camera->getRes().ly) *
1153           TScale(1.0 / shrink) * aff;
1154   }
1155 
1156   fx = TFxUtil::makeAffine(fx, aff);
1157   if (fx) fx->setName(L"CameraDPI and Shrink NAffineFx");
1158 
1159   fx = TFxUtil::makeOver(
1160       TFxUtil::makeColorCard(scene->getProperties()->getBgColor()), fx);
1161   return fx;
1162 }
1163 
1164 //===================================================================
1165 
buildSceneFx(ToonzScene * scene,TXsheet * xsh,double row,int shrink,bool isPreview)1166 TFxP buildSceneFx(ToonzScene *scene, TXsheet *xsh, double row, int shrink,
1167                   bool isPreview) {
1168   int whichLevels =
1169       scene->getProperties()->getOutputProperties()->getWhichLevels();
1170   return buildSceneFx(scene, xsh, row, whichLevels, shrink, isPreview);
1171 }
1172 
1173 //===================================================================
1174 
buildSceneFx(ToonzScene * scene,double row,int shrink,bool isPreview)1175 TFxP buildSceneFx(ToonzScene *scene, double row, int shrink, bool isPreview) {
1176   return buildSceneFx(scene, scene->getXsheet(), row, shrink, isPreview);
1177 }
1178 
1179 //===================================================================
1180 
buildSceneFx(ToonzScene * scene,TXsheet * xsh,double row,const TFxP & root,bool isPreview)1181 TFxP buildSceneFx(ToonzScene *scene, TXsheet *xsh, double row, const TFxP &root,
1182                   bool isPreview) {
1183   int whichLevels =
1184       scene->getProperties()->getOutputProperties()->getWhichLevels();
1185   FxBuilder builder(scene, xsh, row, whichLevels, isPreview);
1186   return builder.buildFx(root, BSFX_NO_TR);
1187 }
1188 
1189 //===================================================================
1190 
buildSceneFx(ToonzScene * scene,double row,const TFxP & root,bool isPreview)1191 TFxP buildSceneFx(ToonzScene *scene, double row, const TFxP &root,
1192                   bool isPreview) {
1193   return buildSceneFx(scene, scene->getXsheet(), row, root, isPreview);
1194 }
1195 
1196 //===================================================================
1197 
1198 //! Similar to buildSceneFx(ToonzScene *scene, double row, const TFxP &root,
1199 //! bool isPreview) method, build the sceneFx
1200 //! adding also camera transformations. Used for Preview Fx function.
buildPartialSceneFx(ToonzScene * scene,double row,const TFxP & root,int shrink,bool isPreview)1201 DVAPI TFxP buildPartialSceneFx(ToonzScene *scene, double row, const TFxP &root,
1202                                int shrink, bool isPreview) {
1203   int whichLevels =
1204       scene->getProperties()->getOutputProperties()->getWhichLevels();
1205   FxBuilder builder(scene, scene->getXsheet(), row, whichLevels, isPreview);
1206   TFxP fx = builder.buildFx(
1207       root, BSFX_Transforms_Enum(BSFX_CAMERA_TR | BSFX_COLUMN_TR));
1208 
1209   TXsheet *xsh = scene->getXsheet();
1210   TStageObjectId cameraId;
1211   if (isPreview)
1212     cameraId = xsh->getStageObjectTree()->getCurrentPreviewCameraId();
1213   else
1214     cameraId = xsh->getStageObjectTree()->getCurrentCameraId();
1215   TStageObject *cameraPegbar = xsh->getStageObject(cameraId);
1216   assert(cameraPegbar);
1217   TCamera *camera = cameraPegbar->getCamera();
1218   assert(camera);
1219 
1220   TAffine aff = getDpiAffine(camera).inv();
1221   if (shrink > 1) {
1222     double fac = 0.5 * (1.0 / shrink - 1.0);
1223 
1224     aff = TTranslation(fac * camera->getRes().lx, fac * camera->getRes().ly) *
1225           TScale(1.0 / shrink) * aff;
1226   }
1227 
1228   fx = TFxUtil::makeAffine(fx, aff);
1229   return fx;
1230 }
1231 
1232 //===================================================================
1233 
buildPartialSceneFx(ToonzScene * scene,TXsheet * xsheet,double row,const TFxP & root,int shrink,bool isPreview)1234 DVAPI TFxP buildPartialSceneFx(ToonzScene *scene, TXsheet *xsheet, double row,
1235                                const TFxP &root, int shrink, bool isPreview) {
1236   int whichLevels =
1237       scene->getProperties()->getOutputProperties()->getWhichLevels();
1238   FxBuilder builder(scene, xsheet, row, whichLevels, isPreview);
1239   TFxP fx = builder.buildFx(
1240       root, BSFX_Transforms_Enum(BSFX_CAMERA_TR | BSFX_COLUMN_TR));
1241 
1242   TStageObjectId cameraId;
1243   if (isPreview)
1244     cameraId = xsheet->getStageObjectTree()->getCurrentPreviewCameraId();
1245   else
1246     cameraId = xsheet->getStageObjectTree()->getCurrentCameraId();
1247   TStageObject *cameraPegbar = xsheet->getStageObject(cameraId);
1248   assert(cameraPegbar);
1249   TCamera *camera = cameraPegbar->getCamera();
1250   assert(camera);
1251 
1252   TAffine aff = getDpiAffine(camera).inv();
1253   if (shrink > 1) {
1254     double fac = 0.5 * (1.0 / shrink - 1.0);
1255 
1256     aff = TTranslation(fac * camera->getRes().lx, fac * camera->getRes().ly) *
1257           TScale(1.0 / shrink) * aff;
1258   }
1259 
1260   fx = TFxUtil::makeAffine(fx, aff);
1261   return fx;
1262 }
1263 
1264 //===================================================================
1265 
1266 /*!
1267   Builds the post-rendering fxs tree - that is, all fxs between the xsheet node
1268   and
1269   current output node.
1270 
1271   This function can be used to isolate global post-processing fxs that typically
1272   do not
1273   contribute to scene compositing. When encountered, the xsheet node is \a not
1274   xpanded - it must be replaced manually.
1275 */
buildPostSceneFx(ToonzScene * scene,double frame,int shrink,bool isPreview)1276 DVAPI TFxP buildPostSceneFx(ToonzScene *scene, double frame, int shrink,
1277                             bool isPreview) {
1278   // NOTE: Should whichLevels access output AND PREVIEW settings?
1279   int whichLevels =
1280       scene->getProperties()->getOutputProperties()->getWhichLevels();
1281 
1282   TXsheet *xsh = scene->getXsheet();
1283   if (!xsh) xsh = scene->getXsheet();
1284 
1285   // Do not expand the xsheet node
1286   FxBuilder builder(scene, xsh, frame, whichLevels, isPreview, false);
1287 
1288   TFxP fx = builder.buildFx();
1289 
1290   TStageObjectId cameraId;
1291   if (isPreview)
1292     cameraId = xsh->getStageObjectTree()->getCurrentPreviewCameraId();
1293   else
1294     cameraId = xsh->getStageObjectTree()->getCurrentCameraId();
1295   TStageObject *cameraPegbar = xsh->getStageObject(cameraId);
1296   assert(cameraPegbar);
1297   TCamera *camera = cameraPegbar->getCamera();
1298   assert(camera);
1299 
1300   TAffine aff = getDpiAffine(camera).inv();
1301 
1302   if (shrink > 1) {
1303     double fac = 0.5 * (1.0 / shrink - 1.0);
1304     aff = TTranslation(fac * camera->getRes().lx, fac * camera->getRes().ly) *
1305           TScale(1.0 / shrink) * aff;
1306   }
1307 
1308   if (!aff.isIdentity()) fx = TFxUtil::makeAffine(fx, aff);
1309 
1310   return fx;
1311 }
1312 
1313 //===================================================================
1314 
buildSceneFx(ToonzScene * scene,double frame,TXsheet * xsh,const TFxP & root,BSFX_Transforms_Enum transforms,bool isPreview,int whichLevels,int shrink)1315 DVAPI TFxP buildSceneFx(ToonzScene *scene, double frame, TXsheet *xsh,
1316                         const TFxP &root, BSFX_Transforms_Enum transforms,
1317                         bool isPreview, int whichLevels, int shrink) {
1318   // NOTE: Should whichLevels access output AND PREVIEW settings?
1319   if (whichLevels == -1)
1320     whichLevels =
1321         scene->getProperties()->getOutputProperties()->getWhichLevels();
1322 
1323   if (!xsh) xsh = scene->getXsheet();
1324 
1325   FxBuilder builder(scene, xsh, frame, whichLevels, isPreview);
1326 
1327   TFxP fx = root ? builder.buildFx(root, transforms) : builder.buildFx();
1328 
1329   TStageObjectId cameraId =
1330       isPreview ? xsh->getStageObjectTree()->getCurrentPreviewCameraId()
1331                 : xsh->getStageObjectTree()->getCurrentCameraId();
1332 
1333   TStageObject *cameraPegbar = xsh->getStageObject(cameraId);
1334   assert(cameraPegbar);
1335 
1336   TCamera *camera = cameraPegbar->getCamera();
1337   assert(camera);
1338 
1339   TAffine aff;
1340   if (transforms & BSFX_CAMERA_DPI_TR) aff = getDpiAffine(camera).inv();
1341 
1342   if (shrink > 1) {
1343     double fac = 0.5 * (1.0 / shrink - 1.0);
1344     aff = TTranslation(fac * camera->getRes().lx, fac * camera->getRes().ly) *
1345           TScale(1.0 / shrink) * aff;
1346   }
1347 
1348   if (!aff.isIdentity()) fx = TFxUtil::makeAffine(fx, aff);
1349 
1350   return fx;
1351 }
1352