diff --git a/apps.json b/apps.json index 688518bb0..b1963974b 100644 --- a/apps.json +++ b/apps.json @@ -2238,7 +2238,6 @@ {"name":"worldclock.img","url":"worldclock-icon.js","evaluate":true} ] }, - { "id": "digiclock", "name": "Digital Clock Face", "shortName":"Digi Clock", @@ -2264,5 +2263,18 @@ {"name":"dsdrelay.app.js","url":"dsdrelay.app.js"}, {"name":"dsdrelay.img","url":"dsdrelay-icon.js","evaluate":true} ] + }, + { "id": "mandel", + "name": "Mandelbrot", + "shortName":"Mandel", + "icon": "mandel.png", + "version":"0.01", + "description": "Draw a zoomable Mandelbrot set", + "tags": "game", + "readme": "README.md", + "storage": [ + {"name":"mandel.app.js","url":"mandel.min.js"}, + {"name":"mandel.img","url":"mandel-icon.js","evaluate":true} + ] } ] diff --git a/apps/mandel/README.md b/apps/mandel/README.md new file mode 100644 index 000000000..de8148b53 --- /dev/null +++ b/apps/mandel/README.md @@ -0,0 +1,7 @@ +# Mandel + +Draw a colored rendition of the famous Mandelbrot set. Pushing button 2 activates zoom mode: the top and left edge of the zoom region can be moved up/down +with buttons 1 and 3 and left/right by touching the screen left right. Pushing button 2 again allows movement of the bottom and right edge in the same manner. +Pushing button 2 a third time renders the selected region full screen. + +The code uses inlined C code for perfomance reasons. Full source is provided on github. diff --git a/apps/mandel/mandel-icon.js b/apps/mandel/mandel-icon.js new file mode 100644 index 000000000..2c09ad91b --- /dev/null +++ b/apps/mandel/mandel-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwkA/4A/AEGIACQX/C/4X3x4XX/AXV/4XsBoYYFC6IPFC5gWFGAgXSDAgXIXAYXGF5mPA4ICCF6QUGC5wWKI5YVKR5ovWL7CPZX6zvXC5KPMDBYXVFwgXOB4QWFC9GPC65pKC5aBLC/4X/C54A/ADo")) diff --git a/apps/mandel/mandel.app.js b/apps/mandel/mandel.app.js new file mode 100644 index 000000000..e6d8c766e --- /dev/null +++ b/apps/mandel/mandel.app.js @@ -0,0 +1,192 @@ + +var aux = new Float32Array(8); +var p_aux = E.getAddressOf(aux, true); +aux[0] = -2; +aux[1] = -1.5; +aux[2] = 1; +aux[3] = 1.5; + +var buf_height = 40; + +var frameX1=0, frameX2=239, frameY1=0, frameY2=239; +var frameCorner = 0; + +var imbuf = Graphics.createArrayBuffer(240, buf_height, 16); +var imbufaddr = E.getAddressOf(imbuf.buffer, true); +var mimg = { + width :imbuf.getWidth(), + height:imbuf.getHeight(), + bpp :16, + buffer:imbuf.buffer +}; + +var c = E.compiledC(` +// void mandel(int, int) +union shortbytes { + short s; + char b[2]; +}; +void mandel(float *p, short *ib) { + float mincx = p[0]; + float mincy = p[1]; + float maxcx = p[2]; + float maxcy = p[3]; + int minx = (int)p[4]; + int miny = (int)p[5]; + int maxx = (int)p[6]; + int maxy = (int)p[7]; + int pib = 0; + for (int y=miny; y-2 && zr<1 && zi>-1.5 && zi<1.5) { + float nr = zr*zr-zi*zi + cr; + zi = 2*zr*zi + ci; + zr = nr; + } + union shortbytes c, d; + c.s = niter | (niter << 7)&0x7ff | (niter<<13)&0xffff; + d.b[0] = c.b[1]; + d.b[1] = c.b[0]; + ib[pib++] = d.s; + } +} +`); + +function drawRectangle(x1, y1, x2, y2) { + if (frameCorner==1) g.setColor(1, 0, 0); + else g.setColor(1, 1, 1); + g.drawLine(x1, y1, x2, y1).drawLine(x1, y1, x1, y2); + if (frameCorner==2) g.setColor(1, 0, 0); + else g.setColor(1, 1, 1); + g.drawLine(x1, y2, x2, y2).drawLine(x2, y1, x2, y2); +} + +function restoreRow(y) { + mimg.width = 240; + mimg.height = 1; + aux[4] = 0; + aux[5] = y; + aux[6] = 240; + aux[7] = y+1; + c.mandel(p_aux, imbufaddr); + g.drawImage(mimg, 0, y); +} + +function restoreCol(x) { + mimg.width = 1; + mimg.height = 240; + aux[4] = x; + aux[5] = 0; + aux[6] = x+1; + aux[7] = 240; + c.mandel(p_aux, imbufaddr); + g.drawImage(mimg, x, 0); +} + +function moveUp() { + restoreCol(frameX1); + restoreCol(frameX2); + if (frameCorner==1 && frameY1>3) { + restoreRow(frameY1); + frameY1 -= 4; + } + if (frameCorner==2 && frameY2>3) { + restoreRow(frameY2); + frameY2 -= 4; + } + drawRectangle(frameX1, frameY1, frameX2, frameY2); +} + +function moveDown() { + restoreCol(frameX1); + restoreCol(frameX2); + if (frameCorner==1 && frameY1<235) { + restoreRow(frameY1); + frameY1 += 4; + } + if (frameCorner==2 && frameY2<235) { + restoreRow(frameY2); + frameY2 += 4; + } + drawRectangle(frameX1, frameY1, frameX2, frameY2); +} + +function moveRight() { + restoreRow(frameY1); + restoreRow(frameY2); + if (frameCorner==1 && frameX1<235) { + restoreCol(frameX1); + frameX1 += 4; + } + if (frameCorner==2 && frameX2<235) { + restoreCol(frameX2); + frameX2 += 4; + } + drawRectangle(frameX1, frameY1, frameX2, frameY2); +} + +function moveLeft() { + restoreRow(frameY1); + restoreRow(frameY2); + if (frameCorner==1 && frameX1>3) { + restoreCol(frameX1); + frameX1 -= 4; + } + if (frameCorner==2 && frameX2>3) { + restoreCol(frameX2); + frameX2 -= 4; + } + drawRectangle(frameX1, frameY1, frameX2, frameY2); +} + + +function toggleFrame() { + if (frameCorner<2) { + frameCorner++; + drawRectangle(frameX1, frameY1, frameX2, frameY2); + } + else { + frameCorner = 0; + var mincx = aux[0] + (aux[2]-aux[0])*frameX1/240.0; + var maxcx = aux[0] + (aux[2]-aux[0])*frameX2/240.0; + var mincy = aux[1] + (aux[3]-aux[1])*frameY1/240.0; + var maxcy = aux[1] + (aux[3]-aux[1])*frameY2/240.0; + aux[0] = mincx; + aux[1] = mincy; + aux[2] = maxcx; + aux[3] = maxcy; + drawIt(); + } +} + +setWatch(toggleFrame, BTN2, {repeat: true}); +setWatch(moveUp, BTN1, {repeat: true}); +setWatch(moveDown, BTN3, {repeat: true}); +Bangle.on('touch', function(button) { + switch(button) { + case 1: moveLeft(); break; + case 2: moveRight(); break; + } +}); + + +function drawIt() { + aux[4] = 0; + aux[5] = 0; + aux[6] = 240; + aux[7] = buf_height; + mimg.width = 240; + mimg.height = buf_height; + for (var y=0; y<240/buf_height; ++y) { + c.mandel(p_aux, imbufaddr); + aux[5] += buf_height; + aux[7] += buf_height; + g.drawImage(mimg, 0, y*buf_height); + } +} + +setTimeout(drawIt, 50); diff --git a/apps/mandel/mandel.info b/apps/mandel/mandel.info new file mode 100644 index 000000000..d4cddbc56 --- /dev/null +++ b/apps/mandel/mandel.info @@ -0,0 +1 @@ +{"id":"mandel","name":"Mandel","src":"mandel.app.js","icon":"mandel.img"} \ No newline at end of file diff --git a/apps/mandel/mandel.min.js b/apps/mandel/mandel.min.js new file mode 100644 index 000000000..0a1bd5fcd --- /dev/null +++ b/apps/mandel/mandel.min.js @@ -0,0 +1,163 @@ + +var aux = new Float32Array(8); +var p_aux = E.getAddressOf(aux, true); +aux[0] = -2; +aux[1] = -1.5; +aux[2] = 1; +aux[3] = 1.5; + +var buf_height = 40; + +var frameX1=0, frameX2=239, frameY1=0, frameY2=239; +var frameCorner = 0; + +var imbuf = Graphics.createArrayBuffer(240, buf_height, 16); +var imbufaddr = E.getAddressOf(imbuf.buffer, true); +var mimg = { + width :imbuf.getWidth(), + height:imbuf.getHeight(), + bpp :16, + buffer:imbuf.buffer +}; + +var c = (function(){ + var bin=atob("0O0EepDtAFrQ7QFK0O0COpDtAzqf7URK/e7nei3p8EMX7pBa0O0Fev3u53rF68V8F+6QStDtBnr97ud6ACMX7pCK0O0Hev3u53q47gAqF+6QmvfuABq/7ggaTEVi2gzrAwaeRgHrRgYqRvfuCCqu6wUDQkUTRETaB+4QKnPuxXq47sd6HyNn7od6B+4QSsfuhGq47sd6c+5kenbuhWpn7od6n+0iesfuhFrw7kd6de6kWgE7E/D/AxPQ9O7CevHuEPoO3fTu4Xrx7hD6CdW07sF68e4Q+gTdtO7ievHuEPoR1NgBwPMKAEPqQzMDQ8PzByBg8wcHY/MPJyb4EnABMrXnATSp5yfuR2rw7mUKp+6nanfup3rn7icKdu4merDuYHrG573o8IMAAHBDAAAAAA=="); + return { + mandel:E.nativeCall(1, "void(int, int)", bin), + }; +})(); + +function drawRectangle(x1, y1, x2, y2) { + if (frameCorner==1) g.setColor(1, 0, 0); + else g.setColor(1, 1, 1); + g.drawLine(x1, y1, x2, y1).drawLine(x1, y1, x1, y2); + if (frameCorner==2) g.setColor(1, 0, 0); + else g.setColor(1, 1, 1); + g.drawLine(x1, y2, x2, y2).drawLine(x2, y1, x2, y2); +} + +function restoreRow(y) { + mimg.width = 240; + mimg.height = 1; + aux[4] = 0; + aux[5] = y; + aux[6] = 240; + aux[7] = y+1; + c.mandel(p_aux, imbufaddr); + g.drawImage(mimg, 0, y); +} + +function restoreCol(x) { + mimg.width = 1; + mimg.height = 240; + aux[4] = x; + aux[5] = 0; + aux[6] = x+1; + aux[7] = 240; + c.mandel(p_aux, imbufaddr); + g.drawImage(mimg, x, 0); +} + +function moveUp() { + restoreCol(frameX1); + restoreCol(frameX2); + if (frameCorner==1 && frameY1>3) { + restoreRow(frameY1); + frameY1 -= 4; + } + if (frameCorner==2 && frameY2>3) { + restoreRow(frameY2); + frameY2 -= 4; + } + drawRectangle(frameX1, frameY1, frameX2, frameY2); +} + +function moveDown() { + restoreCol(frameX1); + restoreCol(frameX2); + if (frameCorner==1 && frameY1<235) { + restoreRow(frameY1); + frameY1 += 4; + } + if (frameCorner==2 && frameY2<235) { + restoreRow(frameY2); + frameY2 += 4; + } + drawRectangle(frameX1, frameY1, frameX2, frameY2); +} + +function moveRight() { + restoreRow(frameY1); + restoreRow(frameY2); + if (frameCorner==1 && frameX1<235) { + restoreCol(frameX1); + frameX1 += 4; + } + if (frameCorner==2 && frameX2<235) { + restoreCol(frameX2); + frameX2 += 4; + } + drawRectangle(frameX1, frameY1, frameX2, frameY2); +} + +function moveLeft() { + restoreRow(frameY1); + restoreRow(frameY2); + if (frameCorner==1 && frameX1>3) { + restoreCol(frameX1); + frameX1 -= 4; + } + if (frameCorner==2 && frameX2>3) { + restoreCol(frameX2); + frameX2 -= 4; + } + drawRectangle(frameX1, frameY1, frameX2, frameY2); +} + + +function toggleFrame() { + if (frameCorner<2) { + frameCorner++; + drawRectangle(frameX1, frameY1, frameX2, frameY2); + } + else { + frameCorner = 0; + var mincx = aux[0] + (aux[2]-aux[0])*frameX1/240.0; + var maxcx = aux[0] + (aux[2]-aux[0])*frameX2/240.0; + var mincy = aux[1] + (aux[3]-aux[1])*frameY1/240.0; + var maxcy = aux[1] + (aux[3]-aux[1])*frameY2/240.0; + aux[0] = mincx; + aux[1] = mincy; + aux[2] = maxcx; + aux[3] = maxcy; + drawIt(); + } +} + +setWatch(toggleFrame, BTN2, {repeat: true}); +setWatch(moveUp, BTN1, {repeat: true}); +setWatch(moveDown, BTN3, {repeat: true}); +Bangle.on('touch', function(button) { + switch(button) { + case 1: moveLeft(); break; + case 2: moveRight(); break; + } +}); + + +function drawIt() { + aux[4] = 0; + aux[5] = 0; + aux[6] = 240; + aux[7] = buf_height; + mimg.width = 240; + mimg.height = buf_height; + for (var y=0; y<240/buf_height; ++y) { + c.mandel(p_aux, imbufaddr); + aux[5] += buf_height; + aux[7] += buf_height; + g.drawImage(mimg, 0, y*buf_height); + } +} + +setTimeout(drawIt, 50);