5

I'm trying to track the movement of a bunch of particles by adding fading trail effect and dynamically update particle's position. However, when I need to track several hundreds of them, drawing may take too much time, making dynamic updating impossible. So I would like to ask are there any way to create an effective fading trail effect in Mathematica, maybe proper tuning of Dynamic or drawing method may help.

The effect I would like to achieve is as follow:

(*Test data*)
priml = Table[# + {.07 k, Sin[.15 k]} & /@ Tuples[Range@15, 2], {k, 
    130}];
cf = Blend[{Yellow, Blend[{Orange, Red}]}, #/1.5] &;
coll = cf /@ Range[0., 1.5, 0.05];
sizel = .15 Range[0., 1.5, .05]^1.5;

(*Update position*)
Do[
  FinishDynamic[];
  track = priml[[i ;; i + 30]];
  , {i, 100}] // AbsoluteTiming

(*Dynamically create the fading trail effect.*)
Dynamic@Graphics[
  Thread[{coll, 
    MapThread[
     Function[{dat, size}, Disk[#, size] & /@ dat], {track, sizel}]}],
   PlotRange -> {{-2, 25}, {-2, 17}}]

Sample effect

Well, but for simplicity, the first step could be setting sizel to a constant array ConstantArray[.15,31].

Actually the only change between two consecutive frames is the addition of new head, deletion of the tail, and the alternation in color, so I strongly suspect lot of time could be saved by proper updation instead of complete redraw the graphics.

In my real application, the calculation in a single Do loop takes about 0.5s, so a drawing time less than 0.05s would be satisfying.

Any idea?

Note: Slight decrease in quality is accepted, e.g. Raster method is allowed

Wjx
  • 9,558
  • 1
  • 34
  • 70
  • sounds like a job for GPU/CUDA or at least for a Compile[expr,CompilationTarget->"C",Parallelization->True,RuntimeOptions->Listable] which will essentially add all of your trails in parallel over the entire list of your particles using machine-compiled C code. – Gregory Klopper Apr 07 '17 at 06:01
  • 1
    Is the florid effect necessary? If you just need a fading trail, the implementation can be much easier and faster. – xzczd Apr 07 '17 at 06:50
  • @xzczd yep,the color is necessary. But I suppose this effect could be easily achieved by ColorCombine. As the color function is just a linear blend so it won't be that hard. – Wjx Apr 07 '17 at 08:21
  • @Kuba yeah, related, but the goal is different, this is more about performance that effect, right? – Wjx Apr 07 '17 at 08:43
  • 1
    For multiple copies of the same object it is often faster to use Translate. For example use MapThread[{#1, Translate[Disk[{0, 0}, #2], #3]} &, {coll, sizel, track}] as your Graphics expression. – Simon Woods Apr 07 '17 at 19:02
  • @SimonWoods, Wow, it speed things up dramatically! But any more suggestions to make them even faster? – Wjx Apr 09 '17 at 01:21

1 Answers1

2

Errr, I found one partial solution with the help by @njpipeorgan, use raster image instead of vector image and use listable operations to update frames to ensure high efficiency.

(*Set plot range and length resolution*)
{{xmin, xmax}, {ymin, ymax}} = {{-2, 25}, {-2, 17}};
resolution = .05;

(*simple convertion*)
convert[{x_, y_}] := Round[{x - xmin, y - ymin}/resolution];
convert[s_] := Round[s/resolution]

(*Create a frame, a "frame" is given by a raster array here*)
createmat[cen_, r_] := 
 Block[{arr = ConstantArray[0, convert[{xmax, ymax}]], pr = convert@r},
  ((arr[[#1 - pr ;; #1 + pr, #2 - pr ;; #2 + pr]] = 
         1.06 DiskMatrix@pr) & @@ convert@#) & /@ cen;
  arr]

(*Update the current image, add new frame and make modification to older frames so that it looks like a fading trail: 1. color alternation 2. Shrinking in size*)
update[mat_, newprim_, r_] := 
 Clip[mat + 
   createmat[newprim, 
    r] - .05 UnitStep[GradientFilter[mat, 1] - .2] - .06, {0, 1}]

(*Define frames and color used in rendering*)
priml = Table[# + {.07 k, Sin[.15 k]} & /@ Tuples[Range@15, 2], {k, 
    130}];
col1 = List @@ Yellow;
col2 = List @@ Blend[{Orange, Red}];

(*This function will convert a numeric 2-D array to a colored image using linear blend*)
restorecol[mat_, col1_, col2_] := 
 ColorCombine[
  MapThread[
   Image[1 + Unitize[mat]*(mat #2 + (1 - mat) #1 - 1)] &, {col1, 
    col2}], "RGB"]

(*Dynamic!*)
Dynamic@restorecol[Reverse@Transpose@mat, col1, col2]

(*Initialization*)
mat = createmat[{}, .15];

(*Update*)
Do[
 mat = update[mat, priml[[i]], .15],
 {i, 100}]

A so-so result

The performance is satisfying on my computer, update a frame cost 0.06s in total.

But the usage of Raster is, in some ways, accelerating the process at a cost of decreased quality, so I still hope there could be a better answer!

Also, the performance still can be further improved, so any suggestions on this direction is also welcomed!

Wjx
  • 9,558
  • 1
  • 34
  • 70