dumb by default


dir: Home / Applets / Fractal
published-date: 20 Jun 2025 14:14 +0700

Fractal


warning: max iter value above 200 will likely hang your browser tab.

  1window.onload = function()
  2{
  3
  4const canvas = document.getElementById('viewport');
  5const ctx = canvas.getContext('2d', {'alpha':false});
  6const ftoi = (v)=>~~v
  7var frame = null;
  8var fractals = {};
  9var inputProxy = null; 
 10
 11
 12class Complex {
 13  constructor(re, im) {this.re=re||0;this.im=im||0};
 14  addc(z){this.re+=z.re;this.im+=z.im;return this};
 15  mulc(z){
 16    let re = (this.re * z.re) - (this.im * z.im);
 17    let im = (this.re * z.im) + (this.im * z.re);
 18    this.re = re; this.im = im;
 19    return this
 20  };
 21  divc(z){
 22    let t_re = z.re;
 23    let t_im = z.im;
 24    z.inverse_i();
 25    this.mulc(z);
 26    z.re = t_re;
 27    z.im = t_im;
 28    return this;
 29  };
 30  square() {return this.re**2 + this.im**2};
 31  conjugate_i(){this.im *=-1};
 32  inverse_i(){
 33    let l = this.square();
 34    this.re = this.re/l;
 35    this.im = -this.im/l;
 36    return this
 37  };
 38  inverse(){return new Complex(this.re, this.im).inverse_i()};
 39  magnitude(){return Math.sqrt(this.square())};
 40};
 41
 42
 43function cInputProxy () {
 44  this.values = {};
 45  this.target = {};
 46  this.display = {};
 47  this.defaults = {};
 48  this.register = (name, _default=null, cast=(v)=>v) => {
 49    this.target[name] = document.getElementById(name);
 50    this.display[name] = this.target[name].parentElement.querySelector("#" + name + "_value");
 51    this.values[name] = cast(this.target[name].value);
 52    this.defaults[name] = _default;
 53    this.target[name].addEventListener("input", (event) => {
 54      this.values[name] = cast(event.target.value);
 55      if (this.display[name] !== null) {
 56        this.display[name].innerHTML = "" + event.target.value;
 57      };
 58    });
 59    if (_default !== null) {
 60      this.target[name].value = "" + _default;
 61      this.values[name] = cast(_default);
 62      if (this.display[name] !== null) {
 63        this.display[name].innerHTML = "" + _default;
 64      };
 65    };
 66    return this.target[name];
 67  };
 68};
 69
 70
 71class Interp {
 72
 73  static _bi_loop(aw, ah, bw, bh, cb) {
 74    let cx, cy, rx, ry, ax, ay, bx, by, axl, axh, ayl, ayh;
 75    rx = (aw-2) / (bw-1);
 76    ry = (ah-2) / (bh-1);
 77    for (bx=0; bx<bw; bx++) {
 78      for (by=0; by<bh; by++) {
 79        axl = Math.floor(rx * bx);
 80        axh = Math.ceil (rx * bx);
 81        ayl = Math.floor(ry * by);
 82        ayh = Math.ceil (ry * by);
 83        cb(
 84          bx, by,
 85          axl, axh, ayl, ayh,
 86          rx*bx-axl, ry*by-ayl
 87        )
 88      };
 89    };
 90  };
 91
 92  static _bi_inv(ms, md, bi_method) {
 93    Interp._bi_loop(ms.w, ms.h, md.w, md.h,
 94      (x, y, xl, xh, yl, yh, xt, yt)=>{
 95        if (x<0 || x>=md.w || y<0 || y>=md.h) return;
 96        if (xl<0 || xl>=ms.w || yl<0 || yl>=ms.h) return;
 97        if (xh<0 || xh>=ms.w || yh<0 || yh>=ms.h) return;
 98        md.set(x, y, bi_method(
 99          ms.get(xl, yl), ms.get(xh, yl),
100          ms.get(xl, yh), ms.get(xh, yh),
101          xt, yt
102        ));
103      }
104    );
105  };
106
107  static nearest(a, b, t) {
108    return t >= .5 ? b : a;
109  };
110
111  static binearest(a1, b1, a2, b2, t1, t2) {
112    return Interp.nearest(
113      Interp.nearest(a1, b1, t1),
114      Interp.nearest(a2, b2, t1),
115      t2
116    )
117  };
118
119  static binearest_m(ms, md) {Interp._bi_inv(ms, md, Interp.binearest);};
120
121  static linear(a, b, t) {
122    return a + (b - a) * t
123  };
124
125  static bilinear(a1, b1, a2, b2, t1, t2) {
126    return Interp.linear(
127      Interp.linear(a1, b1, t1),
128      Interp.linear(a2, b2, t1),
129      t2
130    )
131  };
132
133  static bilinear_m(ms, md) {Interp._bi_inv(ms, md, Interp.bilinear)};
134};
135
136
137class ac2DMatrix {
138  itox(i) {return i % this.w};
139  itoy(i) {return ftoi(i / this.w)};
140  ctoi(x, y) {return x + (y * this.w)};
141  get(x, y) {return this.buf[this.ctoi(x, y)]};
142  set(x, y, v) {this.buf[this.ctoi(x, y)] = v;};
143  map(cb) {for (let i=0; i<this.length; i++){this.buf[i] = cb(this.buf[i])}};
144  zeros() {for (let i=0; i<this.length; i++) {this.buf[i] = 0}};
145  sum() {let s=0; for (let i=0; i<this.length; i++) {s+=this.buf[i]}; return s};
146  count(o) {let s=0; for (let i=0; i<this.length; i++) {if(this.buf[i]===o){s+=1}}; return s};
147  load2DArray(arr) {
148    for (let x=0; x<this.w; x++) {
149      for (let y=0; y<this.h; y++) {
150        this.set(x, y, arr[y][x]);
151      };
152    };
153  };
154  to2DArray() {
155    let r = []
156    for (let y=0; y<this.h; y++) {
157      r.push([]);
158      for (let x=0; x<this.w; x++) {
159        r[y].push(this.get(x, y));
160      };
161    };
162    return r;
163  };
164  queryKernel(mk, x, y, wrap) {
165    const lx = ftoi(mk.w/2);
166    const ly = ftoi(mk.h/2);
167    for (let kx=0; kx<mk.w; kx++) {
168      for (let ky=0; ky<mk.h; ky++) {
169        let dx = x+kx-lx;
170        let dy = y+ky-ly;
171        if (wrap||false) {
172          dx = iwrap(dx, this.w)
173          dy = iwrap(dy, this.h)
174        };
175        mk.set(kx, ky, this.get(dx, dy) || null)
176      };
177    };
178  };
179};
180
181
182class cFloat32Matrix extends ac2DMatrix {
183  constructor (w, h) {
184    super();
185    this.w = w; this.h = h; this.length = w*h;
186    this.buf = new Float32Array(this.length);
187  };
188  unloadImageData(data) {
189    for (let i=0; i<data.length; i+=4) {
190      let p = this.buf[ftoi(i/4)];
191      p = p > 1 ? 1 : p;
192      p = p < 0 ? 0 : p;
193      p = ftoi(p * 255);
194      data[i+0] = p;
195      data[i+1] = p;
196      data[i+2] = p;
197    };
198  };
199};
200
201class cComplexMatrix extends ac2DMatrix {
202  constructor (w, h) {
203    super();
204    this.w = w; this.h = h; this.length = w*h;
205    this.buf = [];
206    for (let y=0; y<h; y++) {
207      for (let x=0; x<w; x++) {
208        // this.buf.push(new Complex(x/(w-1)-.5, y/(h-1)-.5))
209        this.buf.push(new Complex(0,0))
210      };
211    };
212  };
213  set(x, y, im, re) {
214    let c = this.buf[this.ctoi(x, y)];
215    c.im = im||0;
216    c.re = re||0;
217  };
218  zeros() {for (let i=0; i<this.length; i++) {this.buf[i].im=0;this.buf[i].re=0;}};
219  sum() {
220    let s = new Complex(0, 0);
221    for (let i=0; i<this.length; i++) {s.addc(this.buf[i])};
222    return s
223  };
224  count(o) {throw "matrix count method is not defined"};
225  load2DArray(arr) {throw "matrix load2DArray method is not defined"};
226};
227
228
229class MandelbrotSet {
230  constructor(w, h) {
231    this.buffer = new cFloat32Matrix(w, h);
232    this.z = new Complex(0, 0);
233    this.c = new Complex(0, 0);
234    this.thresh_re = 1e9;
235    this.thresh_im = 1e9;
236  };
237  is_oob() {
238    return Math.abs(this.z.re) > this.thresh_re || Math.abs(this.z.im) > this.thresh_im
239  };
240  compute(iter, x, y, scale) {
241    scale = scale||1;
242    x = x||0;
243    y = y||0;
244    iter = Math.min(iter||10, 300);
245    let cv=0, pv=0, dv=0;
246    for (let i=0; i<this.buffer.length; i++){
247      let rx=this.buffer.itox(i)/this.buffer.w-.5;
248      let ry=this.buffer.itoy(i)/this.buffer.h-.5;
249      this.c.re = rx*scale+x;
250      this.c.im = ry*scale+y;
251      this.z.re = 0;
252      this.z.im = 0;
253      let j=0;
254      cv=0; pv=0; dv=0;
255      for (j=0; j<iter; j++){
256        this.z.mulc(this.z);
257        this.z.addc(this.c);
258        pv = cv;
259        cv = this.z.square();
260        dv = cv - pv;
261        if (this.is_oob()) break;
262      };
263      this.buffer.buf[i] = dv < 4 ? 0 : j/iter;
264    };
265  };
266};
267
268// ===========================================
269
270function renderCanvas() {
271  let imd = ctx.getImageData(0, 0, canvas.width, canvas.height);
272  Interp.bilinear_m(fractals['mandelbrot'].buffer, frame);
273  frame.unloadImageData(imd.data);
274  ctx.putImageData(imd, 0, 0);
275};
276
277
278function appInit() {
279  frame = new cFloat32Matrix(canvas.width, canvas.height);
280  fractals['mandelbrot'] = new MandelbrotSet(1000, 1000);
281  inputProxy = new cInputProxy();
282  inputProxy.register('OffsetX', -.743643887037151, parseFloat);
283  inputProxy.register('OffsetY', .131825904205330, parseFloat);
284  inputProxy.register('Zoom', 50, parseFloat);
285  inputProxy.register('Iter', 80, parseInt);
286  inputProxy.register('Compute').addEventListener('click', (e)=>{
287    appUpdate();
288    renderCanvas();
289  });
290  inputProxy.register('Reset').addEventListener('click', (e)=>{
291    inputProxy.values.Iter = 50;
292    inputProxy.values.OffsetX = -1;
293    inputProxy.values.OffsetY = 0;
294    inputProxy.values.Zoom = 0.33;
295    inputProxy.target.Iter.value = 50;
296    inputProxy.target.OffsetX.value = -1;
297    inputProxy.target.OffsetY.value = 0;
298    inputProxy.target.Zoom.value = 0.33;
299    appUpdate();
300    renderCanvas();
301  });
302  appUpdate();
303  renderCanvas();
304};
305
306function appUpdate() {
307  fractals['mandelbrot'].compute(
308    inputProxy.values.Iter,
309    inputProxy.values.OffsetX,
310    inputProxy.values.OffsetY,
311    1/inputProxy.values.Zoom
312  );
313};
314
315
316appInit()
317// fractals['mandelbrot'].compute(ii, -.743643887037151, .131825904205330, 1/50);
318
319};




Built with Hugo | previoip (c) 2025