1 
2 
3 // TnzCore includes
4 #include "tofflinegl.h"
5 #include "tstroke.h"
6 #include "tpalette.h"
7 #include "tvectorimage.h"
8 #include "tvectorrenderdata.h"
9 #include "texception.h"
10 #include "trasterimage.h"
11 #include "drawutil.h"
12 
13 // TnzBase includes
14 #include "trasterfx.h"
15 #include "tparamuiconcept.h"
16 
17 // TnzLib includes
18 #include "toonz/toonzimageutils.h"
19 
20 // TnzStdfx includes
21 #include "particlesengine.h"
22 #include "particlesmanager.h"
23 
24 #include "particlesfx.h"
25 
26 //**************************************************************************
27 //    Particles Fx  implementation
28 //**************************************************************************
29 
ParticlesFx()30 ParticlesFx::ParticlesFx()
31     : m_source("Texture")
32     , m_control("Control")
33     , source_ctrl_val(0)
34     , bright_thres_val(25)
35     , multi_source_val(false)
36     , center_val(TPointD(0.0, 0.0))
37     , length_val(5.0)
38     , height_val(4.0)
39     , maxnum_val(10.0)
40     , lifetime_val(DoublePair(100., 100.))
41     , lifetime_ctrl_val(0)
42     , column_lifetime_val(false)
43     , startpos_val(1)
44     , randseed_val(1)
45     , gravity_val(0.0)
46     , g_angle_val(0.0)
47     , gravity_ctrl_val(0)
48     //, gravity_radius_val (4)
49     , friction_val(0.0)
50     , friction_ctrl_val(0)
51     , windint_val(0.0)
52     , windangle_val(0.0)
53     , swingmode_val(new TIntEnumParam(SWING_RANDOM, "Random"))
54     , randomx_val(DoublePair(0., 0.))
55     , randomy_val(DoublePair(0., 0.))
56     , randomx_ctrl_val(0)
57     , randomy_ctrl_val(0)
58     , swing_val(DoublePair(0., 0.))
59     , speed_val(DoublePair(0., 10.))
60     , speed_ctrl_val(0)
61     , speeda_val(DoublePair(0., 0.))
62     , speeda_ctrl_val(0)
63     , speeda_use_gradient_val(false)
64     , speedscale_val(false)
65     , toplayer_val(new TIntEnumParam(TOP_YOUNGER, "Younger"))
66     , mass_val(DoublePair(1., 1.))
67     , scale_val(DoublePair(100., 100.))
68     , scale_ctrl_val(0)
69     , scale_ctrl_all_val(false)
70     , rot_val(DoublePair(0., 0.))
71     , rot_ctrl_val(0)
72     , trail_val(DoublePair(0., 0.))
73     , trailstep_val(0.0)
74     , rotswingmode_val(new TIntEnumParam(SWING_RANDOM, "Random"))
75     , rotspeed_val(0.0)
76     , rotsca_val(DoublePair(0., 0.))
77     , rotswing_val(DoublePair(0., 0.))
78     , pathaim_val(false)
79     , opacity_val(DoublePair(0., 100.))
80     , opacity_ctrl_val(0)
81     , trailopacity_val(DoublePair(0., 100.))
82     , scalestep_val(DoublePair(0., 0.))
83     , scalestep_ctrl_val(0)
84     , fadein_val(0.0)
85     , fadeout_val(0.0)
86     , animation_val(new TIntEnumParam(ANIM_HOLD, "Hold Frame"))
87     , step_val(1)
88     , gencol_ctrl_val(0)
89     , gencol_spread_val(0.0)
90     , genfadecol_val(0.0)
91     , fincol_ctrl_val(0)
92     , fincol_spread_val(0.0)
93     , finrangecol_val(0.0)
94     , finfadecol_val(0.0)
95     , foutcol_ctrl_val(0)
96     , foutcol_spread_val(0.0)
97     , foutrangecol_val(0.0)
98     , foutfadecol_val(0.0)
99     , source_gradation_val(false)
100     , pick_color_for_every_frame_val(false)
101     , perspective_distribution_val(false) {
102   addInputPort("Texture1", new TRasterFxPort, 0);
103   addInputPort("Control1", new TRasterFxPort, 1);
104 
105   length_val->setMeasureName("fxLength");
106   height_val->setMeasureName("fxLength");
107   center_val->getX()->setMeasureName("fxLength");
108   center_val->getY()->setMeasureName("fxLength");
109 
110   bindParam(this, "source_ctrl", source_ctrl_val);
111   bindParam(this, "bright_thres", bright_thres_val);
112   bright_thres_val->setValueRange(0, 255);
113   bindParam(this, "multi_source", multi_source_val);
114   bindParam(this, "center", center_val);
115   bindParam(this, "length", length_val);
116   length_val->setValueRange(1.0, (std::numeric_limits<double>::max)());
117   bindParam(this, "height", height_val);
118   height_val->setValueRange(1.0, (std::numeric_limits<double>::max)());
119   bindParam(this, "birth_rate", maxnum_val);
120   maxnum_val->setValueRange(0.0, (std::numeric_limits<double>::max)());
121   bindParam(this, "lifetime", lifetime_val);
122   lifetime_val->getMin()->setValueRange(0., +3000.);
123   lifetime_val->getMax()->setValueRange(0., +3000.);
124   bindParam(this, "lifetime_ctrl", lifetime_ctrl_val);
125   bindParam(this, "column_lifetime", column_lifetime_val);
126   bindParam(this, "starting_frame", startpos_val);
127   bindParam(this, "random_seed", randseed_val);
128   bindParam(this, "gravity", gravity_val);
129   gravity_val->setValueRange(0.0, (std::numeric_limits<double>::max)());
130   bindParam(this, "gravity_angle", g_angle_val);
131   g_angle_val->setMeasureName("angle");
132   bindParam(this, "gravity_ctrl", gravity_ctrl_val);
133   //  bindParam(this,"gravity_radius", gravity_radius_val);
134   //  gravity_radius_val->setValueRange(0,40);
135   bindParam(this, "friction", friction_val);
136   bindParam(this, "friction_ctrl", friction_ctrl_val);
137   bindParam(this, "wind", windint_val);
138   bindParam(this, "wind_angle", windangle_val);
139   windangle_val->setMeasureName("angle");
140   bindParam(this, "swing_mode", swingmode_val);
141   swingmode_val->addItem(SWING_SMOOTH, "Smooth");
142   bindParam(this, "scattering_x", randomx_val);
143   randomx_val->getMin()->setMeasureName("fxLength");
144   randomx_val->getMax()->setMeasureName("fxLength");
145   randomx_val->getMin()->setValueRange(-1000., +1000.);
146   randomx_val->getMax()->setValueRange(-1000., +1000.);
147   bindParam(this, "scattering_y", randomy_val);
148   randomy_val->getMin()->setMeasureName("fxLength");
149   randomy_val->getMax()->setMeasureName("fxLength");
150   randomy_val->getMin()->setValueRange(-1000., +1000.);
151   randomy_val->getMax()->setValueRange(-1000., +1000.);
152   bindParam(this, "scattering_x_ctrl", randomx_ctrl_val);
153   bindParam(this, "scattering_y_ctrl", randomy_ctrl_val);
154   bindParam(this, "swing", swing_val);
155   swing_val->getMin()->setValueRange(-1000., +1000.);
156   swing_val->getMax()->setValueRange(-1000., +1000.);
157   speed_val->getMin()->setMeasureName("fxLength");
158   speed_val->getMax()->setMeasureName("fxLength");
159   bindParam(this, "speed", speed_val);
160   speed_val->getMin()->setValueRange(-1000., +1000.);
161   speed_val->getMax()->setValueRange(-1000., +1000.);
162   bindParam(this, "speed_ctrl", speed_ctrl_val);
163   bindParam(this, "speed_angle", speeda_val);
164   speeda_val->getMin()->setValueRange(-1000., +1000.);
165   speeda_val->getMax()->setValueRange(-1000., +1000.);
166   speeda_val->getMin()->setMeasureName("angle");
167   speeda_val->getMax()->setMeasureName("angle");
168   bindParam(this, "speeda_ctrl", speeda_ctrl_val);
169   bindParam(this, "speeda_use_gradient", speeda_use_gradient_val);
170   bindParam(this, "speed_size", speedscale_val);
171   bindParam(this, "top_layer", toplayer_val);
172   toplayer_val->addItem(TOP_OLDER, "Older");
173   toplayer_val->addItem(TOP_SMALLER, "Smaller");
174   toplayer_val->addItem(TOP_BIGGER, "Bigger");
175   toplayer_val->addItem(TOP_RANDOM, "Random");
176   bindParam(this, "mass", mass_val);
177   mass_val->getMin()->setValueRange(0., +1000.);
178   mass_val->getMax()->setValueRange(0., +1000.);
179   bindParam(this, "scale", scale_val);
180   scale_val->getMin()->setValueRange(0., +1000.);
181   scale_val->getMax()->setValueRange(0., +1000.);
182   bindParam(this, "scale_ctrl", scale_ctrl_val);
183   bindParam(this, "scale_ctrl_all", scale_ctrl_all_val);
184   bindParam(this, "rot", rot_val);
185   rot_val->getMin()->setValueRange(-1000., +1000.);
186   rot_val->getMax()->setValueRange(-1000., +1000.);
187   rot_val->getMin()->setMeasureName("angle");
188   rot_val->getMax()->setMeasureName("angle");
189   bindParam(this, "rot_ctrl", rot_ctrl_val);
190   bindParam(this, "trail", trail_val);
191   trail_val->getMin()->setValueRange(0., +1000.);
192   trail_val->getMax()->setValueRange(0., +1000.);
193   bindParam(this, "trail_step", trailstep_val);
194   trailstep_val->setValueRange(1.0, (std::numeric_limits<double>::max)());
195   bindParam(this, "spin_swing_mode", rotswingmode_val);
196   rotswingmode_val->addItem(SWING_SMOOTH, "Smooth");
197   bindParam(this, "spin_speed", rotspeed_val);
198   rotspeed_val->setMeasureName("angle");
199   bindParam(this, "spin_random", rotsca_val);
200   rotsca_val->getMin()->setValueRange(-1000., +1000.);
201   rotsca_val->getMax()->setValueRange(-1000., +1000.);
202   rotsca_val->getMin()->setMeasureName("angle");
203   rotsca_val->getMax()->setMeasureName("angle");
204   bindParam(this, "spin_swing", rotswing_val);
205   rotswing_val->getMin()->setValueRange(-1000., +1000.);
206   rotswing_val->getMax()->setValueRange(-1000., +1000.);
207   rotswing_val->getMin()->setMeasureName("angle");
208   rotswing_val->getMax()->setMeasureName("angle");
209   bindParam(this, "path_aim", pathaim_val);
210   bindParam(this, "opacity", opacity_val);
211   opacity_val->getMin()->setValueRange(0., +100.);
212   opacity_val->getMax()->setValueRange(0., +100.);
213   bindParam(this, "opacity_ctrl", opacity_ctrl_val);
214   bindParam(this, "trail_opacity", trailopacity_val);
215   trailopacity_val->getMin()->setValueRange(0., +100.);
216   trailopacity_val->getMax()->setValueRange(0., +100.);
217   bindParam(this, "scale_step", scalestep_val);
218   bindParam(this, "scale_step_ctrl", scalestep_ctrl_val);
219   scalestep_val->getMin()->setValueRange(-100., +100.);
220   scalestep_val->getMax()->setValueRange(-100., +100.);
221   bindParam(this, "fade_in", fadein_val);
222   bindParam(this, "fade_out", fadeout_val);
223   bindParam(this, "animation", animation_val);
224   animation_val->addItem(ANIM_RANDOM, "Random Frame");
225   animation_val->addItem(ANIM_CYCLE, "Column");
226   animation_val->addItem(ANIM_R_CYCLE, "Column - Random Start");
227   animation_val->addItem(ANIM_SR_CYCLE, "Column Swing - Random Start");
228   bindParam(this, "step", step_val);
229   step_val->setValueRange(1, (std::numeric_limits<int>::max)());
230   std::vector<TSpectrum::ColorKey> colors = {
231       TSpectrum::ColorKey(0, TPixel32::Red),
232       TSpectrum::ColorKey(1, TPixel32::Red)};
233   gencol_val = TSpectrumParamP(colors);
234   bindParam(this, "birth_color", gencol_val);
235   bindParam(this, "birth_color_ctrl", gencol_ctrl_val);
236   bindParam(this, "birth_color_spread", gencol_spread_val);
237   gencol_spread_val->setValueRange(0.0, (std::numeric_limits<int>::max)());
238   bindParam(this, "birth_color_fade", genfadecol_val);
239   genfadecol_val->setValueRange(0.0, 100.0);
240   std::vector<TSpectrum::ColorKey> colors1 = {
241       TSpectrum::ColorKey(0, TPixel32::Green),
242       TSpectrum::ColorKey(1, TPixel32::Green)};
243   fincol_val = TSpectrumParamP(colors1);
244   bindParam(this, "fadein_color", fincol_val);
245   bindParam(this, "fadein_color_ctrl", fincol_ctrl_val);
246   bindParam(this, "fadein_color_spread", fincol_spread_val);
247   fincol_spread_val->setValueRange(0.0, (std::numeric_limits<int>::max)());
248   bindParam(this, "fadein_color_range", finrangecol_val);
249   finrangecol_val->setValueRange(0.0, (std::numeric_limits<double>::max)());
250   bindParam(this, "fadein_color_fade", finfadecol_val);
251   finfadecol_val->setValueRange(0.0, 100.0);
252   std::vector<TSpectrum::ColorKey> colors2 = {
253       TSpectrum::ColorKey(0, TPixel32::Blue),
254       TSpectrum::ColorKey(1, TPixel32::Blue)};
255   foutcol_val = TSpectrumParamP(colors2);
256   bindParam(this, "fadeout_color", foutcol_val);
257   bindParam(this, "fadeout_color_ctrl", foutcol_ctrl_val);
258   bindParam(this, "fadeout_color_spread", foutcol_spread_val);
259   foutcol_spread_val->setValueRange(0.0, (std::numeric_limits<int>::max)());
260   bindParam(this, "fadeout_color_range", foutrangecol_val);
261   foutrangecol_val->setValueRange(0.0, (std::numeric_limits<double>::max)());
262   bindParam(this, "fadeout_color_fade", foutfadecol_val);
263   foutfadecol_val->setValueRange(0.0, 100.0);
264   bindParam(this, "source_gradation", source_gradation_val);
265   bindParam(this, "pick_color_for_every_frame", pick_color_for_every_frame_val);
266   bindParam(this, "perspective_distribution", perspective_distribution_val);
267 }
268 
269 //------------------------------------------------------------------
270 
~ParticlesFx()271 ParticlesFx::~ParticlesFx() {}
272 
273 //------------------------------------------------------------------
274 
getParamUIs(TParamUIConcept * & concepts,int & length)275 void ParticlesFx::getParamUIs(TParamUIConcept *&concepts, int &length) {
276   concepts = new TParamUIConcept[length = 2];
277 
278   concepts[0].m_type  = TParamUIConcept::POINT;
279   concepts[0].m_label = "Center";
280   concepts[0].m_params.push_back(center_val);
281 
282   concepts[1].m_type = TParamUIConcept::RECT;
283   concepts[1].m_params.push_back(length_val);
284   concepts[1].m_params.push_back(height_val);
285   concepts[1].m_params.push_back(center_val);
286 }
287 
288 //------------------------------------------------------------------
289 
doGetBBox(double frame,TRectD & bBox,const TRenderSettings & info)290 bool ParticlesFx::doGetBBox(double frame, TRectD &bBox,
291                             const TRenderSettings &info) {
292   // Returning an infinite rect. This is necessary since building the actual
293   // bbox
294   // is a very complicate task.
295 
296   bBox = TConsts::infiniteRectD;
297   return true;
298 }
299 
300 //------------------------------------------------------------------
301 
getAlias(double frame,const TRenderSettings & info) const302 std::string ParticlesFx::getAlias(double frame,
303                                   const TRenderSettings &info) const {
304   std::string alias = getFxType();
305   alias += "[";
306 
307   // alias degli effetti connessi alle porte di input separati da virgole
308   // una porta non connessa da luogo a un alias vuoto (stringa vuota)
309   for (int i = 0; i < getInputPortCount(); ++i) {
310     TFxPort *port = getInputPort(i);
311     if (port->isConnected()) {
312       TRasterFxP ifx = port->getFx();
313       assert(ifx);
314       alias += ifx->getAlias(frame, info);
315     }
316     alias += ",";
317   }
318 
319   std::string paramalias("");
320   for (int i = 0; i < getParams()->getParamCount(); ++i) {
321     TParam *param = getParams()->getParam(i);
322     paramalias += param->getName() + "=" + param->getValueAlias(frame, 3);
323   }
324 
325   return alias + std::to_string(frame) + "," + std::to_string(getIdentifier()) +
326          paramalias + "]";
327 }
328 
329 //------------------------------------------------------------------
330 
allowUserCacheOnPort(int portNum)331 bool ParticlesFx::allowUserCacheOnPort(int portNum) {
332   // Only control port are currently allowed to cache upon explicit user's
333   // request
334   std::string tmpName = getInputPortName(portNum);
335   return tmpName.find("Control") != std::string::npos;
336 }
337 
338 //------------------------------------------------------------------
339 
doDryCompute(TRectD & rect,double frame,const TRenderSettings & info)340 void ParticlesFx::doDryCompute(TRectD &rect, double frame,
341                                const TRenderSettings &info) {
342   ParticlesManager *pc = ParticlesManager::instance();
343   unsigned long fxId   = getIdentifier();
344   int inputPortCount   = getInputPortCount();
345 
346   int i, j, curr_frame = frame, startframe = startpos_val->getValue();
347 
348   TRenderSettings infoOnInput(info);
349   infoOnInput.m_affine =
350       TAffine();  // Using the standard reference - indep. from cameras.
351   infoOnInput.m_bpp =
352       32;  // Control ports rendered at 32 bit - since not visible.
353 
354   for (i = startframe - 1; i <= curr_frame; ++i) {
355     double frame = std::max(0, i);
356 
357     for (j = 0; j < inputPortCount; ++j) {
358       TFxPort *port       = getInputPort(j);
359       std::string tmpName = getInputPortName(j);
360       if (port->isConnected()) {
361         TRasterFxP fx = port->getFx();
362 
363         // Now, consider that source ports work different than control ones
364         QString portName = QString::fromStdString(tmpName);
365         if (portName.startsWith("C")) {
366           // Control ports are calculated from start to current frame, since
367           // particle mechanics at current frame is influenced by previous ones
368           // (and therefore by all previous control images).
369 
370           TRectD bbox;
371           fx->getBBox(frame, bbox, infoOnInput);
372           if (bbox == TConsts::infiniteRectD) bbox = info.m_affine.inv() * rect;
373           fx->dryCompute(bbox, frame, infoOnInput);
374         } else if (portName.startsWith("T")) {
375           // Particles handle source ports caching procedures on its own.
376         }
377       }
378     }
379   }
380 }
381 
382 //------------------------------------------------------------------
383 
doCompute(TTile & tile,double frame,const TRenderSettings & ri)384 void ParticlesFx::doCompute(TTile &tile, double frame,
385                             const TRenderSettings &ri) {
386   std::vector<int> lastframe;
387   std::vector<TLevelP> partLevel;
388 
389   TPointD p_offset;
390   TDimension p_size(0, 0);
391 
392   /*-- 参照画像ポートの取得 --*/
393   std::vector<TRasterFxPort *> part_ports; /*- テクスチャ素材画像のポート -*/
394   std::map<int, TRasterFxPort *>
395       ctrl_ports; /*- コントロール画像のポート番号/ポート -*/
396   int portsCount = this->getInputPortCount();
397 
398   for (int i = 0; i < portsCount; ++i) {
399     std::string tmpName = this->getInputPortName(i);
400     QString portName    = QString::fromStdString(tmpName);
401 
402     if (portName.startsWith("T")) {
403       TRasterFxPort *tmpPart = (TRasterFxPort *)this->getInputPort(tmpName);
404       if (tmpPart->isConnected())
405         part_ports.push_back((TRasterFxPort *)this->getInputPort(tmpName));
406     } else {
407       portName.replace(QString("Control"), QString(""));
408       TRasterFxPort *tmpCtrl = (TRasterFxPort *)this->getInputPort(tmpName);
409       if (tmpCtrl->isConnected())
410         ctrl_ports[portName.toInt()] =
411             (TRasterFxPort *)this->getInputPort(tmpName);
412     }
413   }
414   /*-- テクスチャ素材のバウンディングボックスを足し合わせる --*/
415   if (!part_ports.empty()) {
416     TRectD outTileBBox(tile.m_pos, TDimensionD(tile.getRaster()->getLx(),
417                                                tile.getRaster()->getLy()));
418     TRectD bbox;
419 
420     for (unsigned int i = 0; i < (int)part_ports.size(); ++i) {
421       const TFxTimeRegion &tr = (*part_ports[i])->getTimeRegion();
422 
423       lastframe.push_back(tr.getLastFrame() + 1);
424       partLevel.push_back(new TLevel());
425       partLevel[i]->setName((*part_ports[i])->getAlias(0, ri));
426 
427       // The particles offset must be calculated without considering the
428       // affine's translational
429       // component
430       TRenderSettings riZero(ri);
431       riZero.m_affine.a13 = riZero.m_affine.a23 = 0;
432 
433       // Calculate the bboxes union
434       for (int t = 0; t <= tr.getLastFrame(); ++t) {
435         TRectD inputBox;
436         (*part_ports[i])->getBBox(t, inputBox, riZero);
437         bbox += inputBox;
438       }
439     }
440 
441     if (bbox.isEmpty()) return;
442     if (bbox == TConsts::infiniteRectD) bbox *= outTileBBox;
443 
444     p_size.lx = (int)bbox.getLx() + 1;
445     p_size.ly = (int)bbox.getLy() + 1;
446     p_offset  = TPointD(0.5 * (bbox.x0 + bbox.x1), 0.5 * (bbox.y0 + bbox.y1));
447   }
448   /*- テクスチャ素材が無い場合、丸を描く -*/
449   else {
450     partLevel.push_back(new TLevel());
451     partLevel[0]->setName("particles");
452     TDimension vecsize(10, 10);
453     TOfflineGL *offlineGlContext = new TOfflineGL(vecsize);
454     offlineGlContext->makeCurrent();
455     offlineGlContext->clear(TPixel32(0, 0, 0, 0));
456 
457     TStroke *stroke;
458     stroke = makeEllipticStroke(
459         0.07, TPointD((vecsize.lx - 1) * .5, (vecsize.ly - 1) * .5), 2.0, 2.0);
460     TVectorImageP vectmp = new TVectorImage();
461 
462     TPalette *plt = new TPalette();
463     vectmp->setPalette(plt);
464     vectmp->addStroke(stroke);
465     TVectorRenderData rd(AffI, TRect(vecsize), plt, 0, true, true);
466 
467     offlineGlContext->draw(vectmp, rd);
468 
469     partLevel[0]->setFrame(
470         0, TRasterImageP(offlineGlContext->getRaster()->clone()));
471     p_size.lx = vecsize.lx + 1;
472     p_size.ly = vecsize.ly + 1;
473     lastframe.push_back(1);
474 
475     delete offlineGlContext;
476   }
477 
478   Particles_Engine myEngine(this, frame);
479 
480   // Retrieving the dpi multiplier from the accumulated affine (which is
481   // isotropic). That is,
482   // the affine will be applied *before* this effect - and we'll multiply
483   // geometrical parameters
484   // by this dpi mult. in order to compensate.
485   float dpi = sqrt(fabs(ri.m_affine.det())) * 100;
486 
487   TTile tileIn;
488   if (TRaster32P raster32 = tile.getRaster()) {
489     myEngine.render_particles(&tile, part_ports, ri, p_size, p_offset,
490                               ctrl_ports, partLevel, 1, (int)frame, 1, 0, 0, 0,
491                               0, lastframe, getIdentifier());
492   } else if (TRaster64P raster64 = tile.getRaster()) {
493     myEngine.render_particles(&tile, part_ports, ri, p_size, p_offset,
494                               ctrl_ports, partLevel, 1, (int)frame, 1, 0, 0, 0,
495                               0, lastframe, getIdentifier());
496   } else
497     throw TException("ParticlesFx: unsupported Pixel Type");
498 }
499 
500 //------------------------------------------------------------------
501 
compatibilityTranslatePort(int major,int minor,std::string & portName)502 void ParticlesFx::compatibilityTranslatePort(int major, int minor,
503                                              std::string &portName) {
504   VersionNumber version(major, minor);
505 
506   if (version < VersionNumber(1, 16)) {
507     if (portName == "Texture") portName = "Texture1";
508   } else if (version < VersionNumber(1, 20)) {
509     int idx;
510 
511     bool chop =
512         ((idx = portName.find("Texture")) != std::string::npos && idx > 0) ||
513         ((idx = portName.find("Control")) != std::string::npos && idx > 0);
514 
515     if (chop) portName.erase(portName.begin(), portName.begin() + idx);
516   }
517 }
518 
519 //==============================================================================
520 
521 FX_PLUGIN_IDENTIFIER(ParticlesFx, "particlesFx");
522