root/trunk/qd/test2.d

Revision 485, 18.4 kB (checked in by FeepingCreature, 4 years ago)
  • Buddhabrot added
Line 
1 module test2;
2 import qd, SDL_ttf;
3 import tools.base, tools.functional, tools.stackthreads, tools.threadpool, std.string, std.file, tools.log: logln;
4 import std.c.stdlib: exit;
5
6 import std.random;
7 import gcc.builtins, std.math:trunc, log, pow, PI, atan;
8 real sqrtl(real r) { return __builtin_sqrtl(r); }
9 float sqrtf(float f) { return __builtin_sqrtf(f); }
10 double sqrt(double d) { return __builtin_sqrt(d); }
11
12 float randf() { return (rand()*1f)/(typeof(rand()).max*1f); }
13 real fract(real r) { if (r<0) return -r-trunc(-r); else return r-trunc(r); }
14
15 template Pow(string what, int count) {
16   static if (!count) const string Pow="typeof("~what~").init";
17   else static if (count==1) const string Pow=what;
18   else const string Pow=what~"*"~Pow!(what, count-1);
19 }
20
21 real Ï†;
22 static this() { φ=(1.0+sqrt(5.0))/2.0; }
23
24 void delegate() idle;
25
26 class Primitives {
27   void drawFrame(ref Area orig) {
28     with (orig) screen.With(orig) = line(0, 0, (size-pt(1, 1)).tupleof, Box=White);
29     orig=orig.shrink(1);
30   }
31   TTF_FontClass font;
32   this(string fn) {
33     New(font, read(fn), 16);
34   }
35 }
36
37 Primitives primitives;
38
39 interface Widget {
40   void draw();
41   void area(Area); Area area();
42 }
43
44 class StandardWidget : Widget {
45   protected Area myArea;
46   abstract void draw();
47   void area(Area target) { myArea=target; }
48   Area area() { return myArea; }
49   this(Area _area) { area=_area; }
50   this() { }
51 }
52
53 class StackThreadSched {
54   Coroutine[] threads;
55   void opCatAssign(Coroutine c) { threads ~= c; }
56   void opCall() { foreach (thread; threads) thread(); }
57   bool runsMe() { foreach (thread; threads) if (thread.runsMe) return true; return false; }
58 }
59
60 StackThreadSched mouse_sched;
61 static this() { New(mouse_sched); }
62
63 import std.thread, tools.threads;
64 class FractalWidget(T=cfloat) : StandardWidget {
65   static assert(is(T: creal));
66   Coroutine drawthr;
67   Threadpool pool;
68   T from, to;
69   int iterations;
70   enum DrawState { Reset, Stop }
71   bool cancel;
72   Area temp;
73   this() {
74     New(drawthr, &render);
75     iterations = 16384;
76     New(pool, 4);
77     idle=&drawthr.opCall;
78     New(temp);
79     temp.tl=pt(0, 0);
80     from=-2-2i; to=2+2i;
81     super();
82   }
83   void zoom(T nfrom, T nto) {
84     from = nfrom; to = nto;
85     cancel=true; while (cancel) drawthr(); New(drawthr, drawthr.dg);
86     // auto backup = myArea; area = null; area = backup;
87   }
88   /*void handleMouse(proc yield) {
89     enum State { start, inside, drag_zoomin, doZoomin, drag_zoomout, doZoomout };
90     pt start, end;
91     void delegate(ref State)[State] tf=[
92       State.inside: (ref State s) { if (mouse.pressed) start=mouse.pos; },
93       State.drag_zoomin: (ref State s) { if (!mouse.pressed) end=mouse.pos; },
94       State.drag_zoomout: (ref State s) { if (!mouse.pressed) end=mouse.pos; },
95       State.doZoomin: (ref State s) {
96         T nfrom=myArea.locate(start, from, to), nto=myArea.locate(end, from, to);
97         zoom(nfrom, nto);
98         logln("New range: ", from, " -> ", to);
99         s=State.start;
100       },
101       State.doZoomout: (ref State s) {
102         T ifrom=myArea.locate(start, from, to), ito=myArea.locate(end, from, to);
103         // determine transformation from new range to current range
104         T scale=(to.re-from.re)/(ito.re-ifrom.re) + (to.im-from.im)/(ito.im-ifrom.im)*1i;
105         T offs=(to.re+from.re-ito.re-ifrom.re)/2.0 + ((to.im+from.im-ito.im-ifrom.im)/2.0)*1i;
106         offs=offs.re*scale.re + offs.im*scale.im*1i;
107         // now apply transformation to current range
108         T nfrom=(from.re+to.re - (to.re-from.re)*scale.re)/2.0 + ((from.im+to.im - (to.im-from.im)*scale.im)/2.0)*1i;
109         T nto=(from.re+to.re + (to.re-from.re)*scale.re)/2.0 + ((from.im+to.im + (to.im-from.im)*scale.im)/2.0)*1i;
110         zoom(nfrom+offs, nto+offs);
111         s=State.start;
112       }
113     ];
114     tf.addBidirectionalEdge({ return (mouse in area) && !mouse.pressed; }, { return !(mouse in area); }, State.start, State.inside);
115     tf.addEdges(
116       { return mouse.pressed(Button.Left); }, State.inside, State.drag_zoomin,
117       { return mouse.pressed(Button.Right); }, State.inside, State.drag_zoomout,
118       { return !mouse.pressed; }, State.drag_zoomin, State.doZoomin,
119       { return !mouse.pressed; }, State.drag_zoomout, State.doZoomout
120     );
121     tf.runState(yield);
122   }*/
123   alias void delegate(T p, void delegate(float)) func;
124   void lyapunov(T p, void delegate(float) dg) {
125     p*=2; // prescale
126     string str="AAAAAAAAAABBAABB";
127     int limit=200;
128     size_t which=0;
129     float next_r() {
130       auto ch=str[which++];
131       if (which>=str.length) which=0;
132       if (ch=='A') return p.im;
133       if (ch=='B') return p.re;
134       asm { int 3; }
135     }
136     float sum=0;
137     int i=0;
138     float x=0.5;
139     for (i=0; i<limit; ++i) {
140       float r=next_r;
141       x=x*r*(1f-x);
142       if (i>limit/2) sum+=log(abs(r*(1f-2f*x)));
143       if (sum>=float.max) break; // definitely outside
144     }
145     float f=sum/(limit/2f);
146     if (f>0) dg(1);
147     else dg(pow(atan(-f)/PI, cast(real)0.3f));
148   }
149   void bailout_type(alias FN) (T p, void delegate(float) dg) {
150     T c = 0+0i;
151     static assert(is(ParameterTypeTuple!(FN) == Tuple!(T, T)));
152     int i = iterations;
153     for (; i>0; i--) {
154       FN(c, p);
155       if (c.re*c.re + c.im*c.im!<4) break;
156     }
157     if (i !> 0) dg(1); else {
158       //auto smooth = (iterations - i + 1) - log(log(abs(pre_c)))/log(2);
159       dg(i / 128f);
160     }
161   }
162   void mandelbrot_sse(T[4] ps, void delegate(int, float) dg) {
163     T[4] cs;
164     foreach (ref c; cs) c = 0+0i;
165     ubyte left = 15; // bit array
166     int i = iterations;
167     for (; i > 0; i--) {
168       for (int k = 0; k < 4; ++k) {
169         cs[k] *= cs[k]; cs[k] += ps[k];
170       }
171       for (int k = 0; k < 4; ++k) {
172         if (left & (1 << k)) {
173           auto c = cs[k];
174           if (c.re*c.re + c.im * c.im !< 4) {
175             left &= ~(1 << k);
176             dg(k, i/128f);
177           }
178         }
179       }
180       if (!left) break;
181     }
182     if (!i) for (int k = 0; k < 4; ++k) if (left & (1 << k)) dg(k, 1);
183   }
184   final void mandelbrot_fn(ref T c, T p) { c *= c; c += p; }
185   final void burning_ship_fn(ref T c, T p) {
186     c = abs(c.re) + abs(c.im)*1.0i;
187     c *= c; c += p;
188   }
189   alias bailout_type!(mandelbrot_fn) mandelbrot;
190   alias bailout_type!(burning_ship_fn) burning_ship;
191   void julia(T p, T c, void delegate(float) dg) {
192     int limit=iterations;
193     T x=p;
194     int i=0;
195     float sq=void;
196     for (;i<limit; ++i) {
197       x=x*x + c;
198       sq=x.re*x.re+x.im*x.im;
199       if (sq>=4) break;
200     }
201     dg((i-atan(sqrtf(sq)-2)/1.18)*0.1);
202   }
203   void render(proc yield) {
204     logln("render proc - temp=", temp);
205     assert(drawthr.runsMe());
206     size_t aa=1;
207     float scale = 255f / (aa*aa);
208     size_t cc=0;
209     int count=0;
210     void renderRange(int y1, int y2) {
211       scope(exit) synchronized(this) --count;
212       auto size = (to.re-from.re) + (to.im-from.im)*1.0i;
213       for (int yc=y1; yc<y2; ++yc) {
214         for (int xc=0; xc<temp.width; xc += 4) {
215           if (cc) synchronized { cc--; return; }
216           temp.pset(pt(xc, yc), 255, 255, 255);
217           //float red=0f, green=0f, blue=0f;
218           float[4] red=[0f, 0f, 0f, 0f], green=[0f, 0f, 0f, 0f], blue=[0f, 0f, 0f, 0f];
219           for (int xa=0; xa<aa; ++xa) for (int ya=0; ya<aa; ++ya) {
220             T[4] start;
221             for (int k = 0; k < 4; ++k) {
222               auto mxc = xc + k;
223               if (mxc !< temp.width) break;
224               start[k]=(mxc*aa*1f + xa)/(temp.width*aa*1f) + (yc*aa*1f + ya)/(temp.height*aa*1f)*1i;
225               start[k]=from.re+size.re*start[k].re - (from.im+size.im*start[k].im)*1.0i;
226             }
227             //lyapunov(start, (float t) {
228             //mandelbrot(start, (float t) {
229             mandelbrot_sse(start, (int k, float t) {
230             //burning_ship(start, (float t) {
231             //julia(start, -0.835-0.2321i, (float t) {
232               green[k]+=fract(t*2); red[k]+=fract(t); blue[k]+=fract(t/2);
233             });
234           }
235           for (int k = 0; k < 4; ++k) {
236             if (k !< temp.width) break;
237             red[k]*=scale; green[k]*=scale; blue[k]*=scale;
238             temp.pset(pt(xc+k, yc), cast(ubyte) red[k], cast(ubyte) green[k], cast(ubyte) blue[k]);
239           }
240         }
241       }
242     }
243     for (int y=0; y<temp.height; y+=16) {
244       pool.addTask("Worker "~.toString(y), &renderRange /fix/ stuple(y, min(y+16, temp.height)));
245       synchronized(this) ++count;
246     }
247     while (true) {
248       yield();
249       if (!count) if (temp.surface && filename.length) {
250         SDL_SaveBMP(temp.surface, filename);
251         //exit(0);
252         filename = "";
253       }
254       if (cancel) {
255         cc=count;
256         while (cc) yield();
257         cancel=false;
258         yield();
259       }
260     }
261   }
262   Area area() { return super.area; }
263   void area(Area target) {
264     if (target && (!myArea || target.dimensions!=myArea.dimensions)) {
265       temp = screen.With(target.width, target.height) = cls;
266     }
267     super.area=target;
268   }
269   void draw() {
270     myArea.blit(temp);
271   }
272 }
273
274 template UseWidget(alias W) {
275   private bool valid() { static if(isPointer!(typeof(W))) return W&&*W; else return W !is null; }
276   void draw() { if (valid) W.draw(); }
277   void area(Area target) { if (valid) W.area=target; }
278   Area area() { if (valid) return W.area; return null; }
279 }
280
281 class GroupWidget : StandardWidget {
282   mixin This!("super(myArea)");
283   Widget[] children;
284   void opCatAssign(Widget[] w) { children~=w; }
285   void opCatAssign(Widget w) { children~=w; }
286 }
287
288 class Draggable : Area {
289   void delegate(ref pt)[] constraints;
290   this(int x, int y, int w, int h) { tl=pt(x, y); dimensions=pt(w, h); }
291   void set(Area area) {
292     tl=area.tl;
293     dimensions=area.dimensions;
294     surface=area.surface;
295   }
296   void mousethr(proc yield) {
297     enum State { start, inside, dragging }
298     pt startpos, lastpos;
299     void delegate(ref State)[State] transitions=[
300       State.inside: (ref State s) { lastpos=startpos=mouse.pos; },
301       State.dragging: (ref State s) {
302         pt offset=mouse.pos-lastpos;
303         lastpos=mouse.pos;
304         tl+=offset;
305         foreach (constraint; constraints) constraint(tl);
306       }
307     ];
308     transitions.addBidirectionalEdge({ return mouse.pressed; }, State.inside, State.dragging);
309     transitions.addEdges(
310       { return (mouse.pos in this)&&!mouse.pressed; }, State.start, State.inside,
311       { return !(mouse.pos in this); }, State.inside, State.start
312     );
313     transitions.runState(yield);
314   }
315 }
316
317 class BaseSliderWidget : StandardWidget {
318   float pos=0;
319   invariant { assert(pos>=0f && pos<=1f); }
320   void delegate(ref float) limit;
321   Draggable box;
322   void mousethr(proc yield) { box.mousethr({ update; yield(); }); }
323   void update() { }
324   void constrain(ref pt p) {
325     p.y=myArea.tl.y+myArea.height/2;
326   }
327   void area(Area area) {
328     super.area(area);
329     auto new_box=area.width(cast(int)(area.width*(0.1+pos*0.9))).width(-area.width/10);
330     if (!box) box=new Draggable(0, 0, 0, 0);
331     box.set(new_box);
332   }
333   Area area() { return super.area(); }
334   void draw() {
335     with (area.select(pt(cast(int)(area.width*0.9), 3), Center)) line(tl.tupleof, br.tupleof, Box=White, Fill=Black);
336     if (box) with (box) line(tl.tupleof, br.tupleof, Box=White, Fill=White~Black~Black);
337   }
338 }
339
340 class SplitWidget(bool vert=true) : GroupWidget {
341   int[] percentages;
342   mixin This!("super(myArea)");
343   invariant {
344     assert(percentages.length==children.length);
345     assert(!percentages.length || (percentages /Reduce!("$+=$2") == 100));
346   }
347   void opCatAssign(Widget[] w) { super~=w; percentages~=0; }
348   void add(Widget w, int p) { super~=w; percentages~=p; }
349   void area(Area target) {
350     primitives.drawFrame(target);
351     auto length=vert?target.height:target.length;
352     super.area=target;
353     if (!percentages.length) return;
354     auto offsets=new int[percentages.length];
355     offsets[0]=0;
356     foreach (id, ref entry; offsets[1..$]) entry=(percentages[id]*length)/100+offsets[id];
357     foreach (id, child; children) {
358       static if (vert) child.area=target.select(0, offsets[id]).height((percentages[id]*length)/100);
359       else child.area=target.select(offsets[id], 0).width((percentages[id]*.length)/100);
360     }
361   }
362   void draw() {
363     Area meep=target; primitives.drawFrame(meep);
364     foreach (child; children) child.draw();
365   }
366 }
367
368 class FixedSplitWidget(bool vert=true) : Widget {
369   Widget head, child;
370   int offset;
371   mixin This!("head, child, offset");
372   void draw() { head.draw; child.draw; }
373   void area(Area target) {
374     static if (vert) {
375       head.area=target.height(offset);
376       child.area=target.height(-target.height+offset+1);
377     } else {
378       head.area=target.width(offset);
379       child.area=target.width(-target.width+offset+1);
380     }
381   }
382   Area area() { return null; }
383 }
384
385 class Proxy : Widget {
386   Widget *target;
387   this(Widget *w) { target=w; }
388   mixin UseWidget!(target);
389 }
390
391 class Frame : StandardWidget {
392   Widget child; mixin This!("child=null");
393   void draw() {
394     Area feep=myArea;
395     primitives.drawFrame(feep);
396     if (child) {
397       child.area=feep.shrink(1);
398       child.draw;
399     }
400   }
401 }
402
403 class AlignWidget : StandardWidget {
404   Widget target;
405   Align ment;
406   this(Widget w, Align a) { target=w; ment=a; }
407   void draw() { target.draw; }
408   void area(Area a) {
409     super.area=a;
410     if (target) target.area=a.select(target.area.size, ment); // retarget
411   }
412 }
413
414 class StackWidget : Widget {
415   Widget[] targets;
416   mixin This!("targets");
417   void draw() { foreach (target; targets) if (target) target.draw; }
418   void area(Area a) { foreach (target; targets) if (target) target.area=a; }
419   Area area() { return targets[0].area(); }
420 }
421
422 T[] array(T)(T[] elems...) { return elems.dup; }
423
424 import tools.list;
425 class WindowManager {
426
427   class Window : Widget {
428     private Widget frame, headline, box;
429     Widget child;
430     StackThreadSched motion;
431     this(Area area=null, Widget ch=null) {
432       headline=new TextBox("Window", Center|Left);
433       box=new Frame(); box.area=Area(pt(0, 0), pt(10, 10));
434       this.child=ch;
435       frame=new Frame(new StackWidget(array!(Widget)(
436         new FixedSplitWidget!()(new Frame(headline), new Proxy(&child), 32),
437         new AlignWidget(box, Bottom|Right)
438       )));
439       New(motion);
440       if (area) frame.area=area;
441     }
442     mixin UseWidget!(frame);
443   }
444  
445   list!(Window) windows;
446   this() { mouse_sched ~= new Coroutine(&mousethr); }
447   void draw() { windows.each((Window w) { w.draw(); }); }
448   void mousethr(proc yield) {
449     assert(mouse_sched.runsMe);
450     enum State { start, title_inside, title_dragging, resize_inside, resize_dragging }
451     pt lastpos;
452     bool mouseInTitle() { with (windows) return length && top.headline.area && (mouse.pos in top.headline.area); }
453     bool mouseInBox() { with (windows) return length && top.box.area && (mouse.pos in top.box.area); }
454     void delegate(ref State)[State] transitions=[
455       State.start: (ref State s) {
456         size_t newActive=size_t.max;
457         windows.each_reverse((size_t id, ref Window w, proc brk) {
458           if (mouse.pos in w.area) {
459             if (mouse.pressed) {
460               newActive=id; // make current
461               if (s==State.start && (w is windows.top)) w.motion();
462             } else w.motion();
463             brk();
464           }
465         });
466         if (newActive!=size_t.max && newActive!=windows.length-1) {
467           windows.push_back(windows.take(newActive));
468         }
469       },
470       State.title_inside: (ref State s) { lastpos=mouse.pos; },
471       State.title_dragging: (ref State s) {
472         with (windows.top) area=area.move(mouse.pos-lastpos);
473         lastpos=mouse.pos;
474       },
475       State.resize_inside: (ref State s) { lastpos=mouse.pos; },
476       State.resize_dragging: (ref State s) {
477         auto ns=mouse.pos-lastpos;
478         with (windows.top) area=area.select(0, 0, area.width+ns.x, area.height+ns.y);
479         lastpos=mouse.pos;
480       }
481     ];
482     transitions.addBidirectionalEdges(
483       { return mouseInTitle && !mouse.pressed; }, { return !mouseInTitle; }, State.start, State.title_inside,
484       { return mouseInBox && !mouse.pressed; }, { return !mouseInBox; }, State.start, State.resize_inside,
485       { return mouse.pressed; }, State.title_inside, State.title_dragging,
486       { return mouse.pressed; }, State.resize_inside, State.resize_dragging
487     );
488     transitions.runState(yield);
489   }
490 }
491
492 class Text : StandardWidget {
493   Align alignment;
494   this(string s, Align alignment=Left|Top) { text=primitives.font.render(s); this.alignment=alignment; }
495   Area text;
496   void draw() { myArea.select(text.size, alignment).blit(text); }
497 }
498
499 class TextBox : Text {
500   this(string s, Align alignment=Left|Top) { super(s, alignment); }
501   void draw() { screen.With(myArea) = cls; super.draw(); }
502 }
503
504 import std.c.time: time;
505 void countFPS() {
506   static int last_time;
507   static int fps=0;
508   auto now=time(null);
509   if (last_time!=now) {
510     logln(fps, " fps.");
511     fps=0;
512     last_time=now;
513   } else ++fps;
514 }
515
516 real transfer(real a, real b, real a2, real b2, real t) {
517   real fp=(a2-a)*(b-a)/(a2-a+b-b2);
518   fp+=a;
519   float fac1=(fp-a)/(fp-a2), fac2=(b-fp)/(b2-fp);
520   assert(abs(fac1-fac2)<0.001, format("Inequality: ", fac1, " != ", fac2));
521   auto fac=fac1;
522   auto steps=log(fac)/log(2.0);
523   auto scale=pow(cast(real)2.0, steps*t);
524   return fp-(fp-a)/scale;
525 }
526 creal transfer(creal a, creal b, creal a2, creal b2, real t) {
527   return transfer(a.re, b.re, a2.re, b2.re, t)+transfer(a.im, b.im, a2.im, b2.im, t)*1.0i;
528 }
529
530 string filename;
531
532 import tools.time: getTime=time;
533 void main(string[] args) {
534   New(primitives, "Vera.ttf");
535   auto wm=new WindowManager;
536   auto f1=new FractalWidget!(creal);
537   screen(700, 700, SDL_RESIZABLE);
538   flip=off;
539   auto w1=wm.new Window(display.select(10, 10, 260, 229), f1);
540   wm.windows~=w1;
541   //wm.windows~=wm.new Window(display.select(10, 10, 160, 60), new BaseSliderWidget);
542   auto start_from = f1.from, start_to = f1.to;
543  
544   if (args.length>6) {
545     creal nfrom=args[1].atof() + args[2].atof()*1.0i, nto=args[3].atof() + args[4].atof()*1.0i;
546     //float step=args[5].atof()/args[6].atof();
547     int length = args[5].atoi();
548     void renderStep(int which) {
549       real step = (1.0*which)/(1.0*length);
550       auto nffrom=transfer(start_from, start_to, nfrom, nto, step);
551       auto nfto=transfer(start_to, start_from, nto, nfrom, step);
552       auto num = toString(which);
553       auto limit = toString(length);
554       while (num.length < limit.length) num = "0"~num;
555       filename = args[6] ~ num ~ ".bmp";
556       if (filename.exists()) { logln("Skipping ", filename); return; }
557       f1.zoom(nffrom, nfto);
558       logln("new from ", nffrom, " to ", nfto, " step being ", step);
559       while (filename.length) { cls(Black); wm.draw(); flip; idle(); events; countFPS(); }
560     }
561     for (int i = 0; i < length; ++i) renderStep(i);
562     return;
563   }
564  
565   long idletime=20;
566   while (true) {
567     cls(Black);
568     wm.draw();
569     flip;
570     idle();
571     events;
572     countFPS();
573     mouse_sched ();
574   }
575 }
Note: See TracBrowser for help on using the browser.