From ed5aba215148616d14081be34521fd60c1f9e821 Mon Sep 17 00:00:00 2001 From: Felipe Manga Date: Sun, 14 Jan 2024 17:04:17 +0000 Subject: [PATCH 01/27] Add Warpdrive watchface --- apps/warpdrive/README.md | 5 + apps/warpdrive/app-icon.js | 1 + apps/warpdrive/app.js | 614 +++++++++++++++++++++++++++++++++++ apps/warpdrive/app.png | Bin 0 -> 5936 bytes apps/warpdrive/metadata.json | 16 + 5 files changed, 636 insertions(+) create mode 100644 apps/warpdrive/README.md create mode 100644 apps/warpdrive/app-icon.js create mode 100644 apps/warpdrive/app.js create mode 100644 apps/warpdrive/app.png create mode 100644 apps/warpdrive/metadata.json diff --git a/apps/warpdrive/README.md b/apps/warpdrive/README.md new file mode 100644 index 000000000..0f300c518 --- /dev/null +++ b/apps/warpdrive/README.md @@ -0,0 +1,5 @@ +# WarpDrive + +An animated watchface featuring 3D spaceships traveling just shy of ludicrous speed. + +WE BREAK FOR NOBODY. \ No newline at end of file diff --git a/apps/warpdrive/app-icon.js b/apps/warpdrive/app-icon.js new file mode 100644 index 000000000..1ac6aa67d --- /dev/null +++ b/apps/warpdrive/app-icon.js @@ -0,0 +1 @@ +atob("MDDD/wAA//+nMRF8r3PznG1znfdmmccUUUUUMMQQQQQQQQUdFFc8/66rKKLBBBBBBBBBBB6zyy+mnFcUUUUUMMQQQQMQMddFFdk3266rLKLLBBBBBBBBK6y6x/7FddcUUUUUMMMQQQQMcdUdd9e66q6rLJqLBBBBBBBSpyqze65dcUccUUUUUMMMMMNdccdd9ce66pqqppqLBBBBBBOp6qu/6qpcccccccUUUMMOsOccEdFlcUW5q66ppprLJBBDDWyr64866rIcUcccUUUUUMMUXfEGkGlddUWpqJ6pppqLLhCjO26u4867LLIcdFlFl0UUUXEMVdEEGndcUUVpjKs30330303ra1Ng8327LLIUW0VlnF9FEXcEEQUOnHdcUUVppppA0033u77sxBBA036pqrIUUUUUWHHHFHFHUYQUdENcEcVpppppBL373swzBBLB3rK4irAUUUUUYYGHGHEMMYUUcMMEdcVp5pprBDBWc2hBBBBLbC7Ba6ocUUUUMUYGmEUYQMUUMUUMFddpppprK5A82rDDBDBKLLDDK64UUcefEQGlcMMMQMQUcUMMVFdppp/2pA36BBBBDJDKpppjDKsUcedHccYQQQYUUUUFccUUVcdpr+38rBBBBBBDJBDBJ3KJqK4UEVelcYUYUQUQUMYYcdcdcUVoprc6rK6hJLLBDBBBBCqqqKIEUWkUUenUUVdUMYUUQQQcUUWpqfrDK22rK4+rBDDJBC5BJpIUW8UUUddcfGnUFFEUMdccUUVqeBLKKbrLY82q6yuBCa66ppAW0UUUXcUUGnddddUUVcdFUUWeLDDLdrDI+7a66ypLJpyyrLC0UMUV8UUe8VccddUUUUdcUUOLBBCdrBDfC66J6rJJqKKyBBAUMMV0UMW0cccUcccUUUVUUMNBBCdqBBKKKprLJ1pqLDLDBBAMMeUUMMUUccUUW0UUUUMUMMRBD1qJBDDKJrLLfLLLBBDLDLAUHUUMMUUUUUUW0UUUUMUUUUNAqKJBBBDKLLLXLLDJBDBBDLAEUUUMQMUUUcVUUUUUMUMMMUQqLKBBBDLLK6rLDBBBBDDBBBAUUUMQMUUUfcUMMMQMMUUQQQSKLBBBBDLL2rBBBBBBBBBBBBAUUMMMMUUfcUMMMMQMMMMQQQTLBBBBBDL2rBBBBBBBhBBBBBA=") diff --git a/apps/warpdrive/app.js b/apps/warpdrive/app.js new file mode 100644 index 000000000..990b6a961 --- /dev/null +++ b/apps/warpdrive/app.js @@ -0,0 +1,614 @@ +const gfx = E.compiledC(` +// void init(int, int, int) +// void clear() +// void render(int, int) +// void setCamera(int, int, int) +// void stars() + +unsigned char* fb; +int stride; +unsigned char* sint; + +const int near = 5 << 8; +int f = 0; + +typedef struct { + int x, y, z; +} Point; + +Point camera; +Point rotation; +Point scale; +Point position; + +const unsigned char ship[] = { +0,38,25,10,3,8,6,10,7,3,6,13,3,11,5,13,1,12,3,15,3,5,8,15,1,3,7,13,12,11,3,15,5,6,8,15,6,1,7,10,5,0,6,10,0,1,6,12,5,11,4,12,12,1,2,12,2,11,12,12,10,5,4,13,5,10,0,12,2,1,9,13,9,1,0,12,4,11,2,10,19,22,21,12,4,2,10,12,10,2,9,10,13,16,15,13,10,9,0,15,21,20,19,15,15,14,13,15,19,20,22,15,13,14,16,15,21,23,20,15,15,17,14,15,22,20,23,10,22,24,21,15,16,14,17,10,16,18,15,15,24,23,21,15,18,17,15,15,22,23,24,15,16,17,18,0,0,62,236,243,244,247,0,234,0,229,194,11,0,234,21,243,246,0,234,33,193,250,20,63,249,19,249,4,3,9,4,3,7,247,222,250,247,222,240,0,22,238,13,22,226,1,20,229,7,62,225,11,20,208,27,62,19,0,20,22,12,20,33,0,18,30,5,60,34,10,18,52,26,60 +}; + +unsigned int _rngState; +unsigned int rng() { + _rngState ^= _rngState << 17; + _rngState ^= _rngState >> 13; + _rngState ^= _rngState << 5; + return _rngState; +} + +void init(unsigned char* _fb, int _stride, unsigned char* _sint) { + fb = _fb; + stride = _stride; + sint = _sint; +} + +int sin(int angle) { + int a = (angle >> 7) & 0xFF; + if (angle & (1 << 15)) + a = 0xFF - a; + int v = sint[a]; + if (angle & (1 << 16)) + v = -v; + return v; +} + +int cos(int angle) { + return sin(angle + 0x8000); +} + +void clear() { + unsigned short* cursor = (unsigned short*) fb; + for (int y = 0; y < 176; ++y) { + for (int x = 0; x < 66/2; ++x) + *cursor++ = 0; + cursor++; + } +} + +void setCamera(int x, int y, int z) { + camera.x = x; + camera.y = y; + camera.z = z; +} + +unsigned int solid(unsigned int c) { + c &= 7; + c |= c << 3; + c |= c << 6; + c |= c << 12; + c |= c << 24; + return c; +} + +unsigned int alternate(unsigned int a, unsigned int b) { + unsigned int c = (a & 7) | ((b & 7) << 3); + c |= c << 6; + c |= c << 12; + c |= c << 24; + return c; +} + +void drawHLine(int x, unsigned int y, int l, unsigned int c) { + if (x < 0) { + l += x; + x = 0; + } + if (x + l >= 176) { + l = 176 - x; + } + if (l <= 0 || y >= 176) + return; + + if (y & 1) + c = alternate(c >> 3, c); + + int bitstart = x * 3; + int bitend = (x + l) * 3; + int wstart = bitstart >> 5; + int wend = bitend >> 5; + int padstart = bitstart & 31; + int padend = bitend & 31; + int maskstart = -1 << padstart; + int maskend = unsigned(-1) >> (32 - padend); + if (wstart == wend) { + maskstart &= maskend; + maskend = 0; + } + + int* row = (int*) &fb[y * stride]; + if (maskstart) { + row[wstart] = (row[wstart] & ~maskstart) | ((c << padstart) & maskstart); + while (bitstart >> 5 == wstart) + bitstart += 3; + } + if (maskend) + row[wend] = (row[wend] & ~maskend) | + (((c >> (30 - padend)) | (c >> (36 - padend))) & maskend); + bitend -= padend; + for (int x = bitstart; x < bitend; x += 10 * 3) { + unsigned int R = x & 31; + row[x >> 5] = (c << R) | (c >> (36 - R)) | (c >> (30 - R)) | (c << (R - 6)); + } +} + +void fillTriangle( int x0, int y0, + int x1, int y1, + int x2, int y2, + unsigned int col) { + int a, b, y, last, tmp; + + a = 176; + b = 176; + if( x0 < 0 && x1 < 0 && x2 < 0 ) return; + if( x0 >= a && x1 > a && x2 > a ) return; + if( y0 < 0 && y1 < 0 && y2 < 0 ) return; + if( y0 >= b && y1 > b && y2 > b ) return; + + // Sort coordinates by Y order (y2 >= y1 >= y0) + if (y0 > y1) { + tmp = y0; y0 = y1; y1 = tmp; + tmp = x0; x0 = x1; x1 = tmp; + } + if (y1 > y2) { + tmp = y2; y2 = y1; y1 = tmp; + tmp = x2; x2 = x1; x1 = tmp; + } + if (y0 > y1) { + tmp = y0; y0 = y1; y1 = tmp; + tmp = x0; x0 = x1; x1 = tmp; + } + + if (y0 == y2) { // Handle awkward all-on-same-line case as its own thing + a = b = x0; + if (x1 < a) a = x1; + else if (x1 > b) b = x1; + if (x2 < a) a = x2; + else if (x2 > b) b = x2; + drawHLine(a, y0, b - a + 1, col); + return; + } + + int dx01 = x1 - x0, + dx02 = x2 - x0, + dy02 = (1<<16) / (y2 - y0), + dx12 = x2 - x1, + sa = 0, + sb = 0; + + // For upper part of triangle, find scanline crossings for segments + // 0-1 and 0-2. If y1=y2 (flat-bottomed triangle), the scanline y1 + // is included here (and second loop will be skipped, avoiding a /0 + // error there), otherwise scanline y1 is skipped here and handled + // in the second loop...which also avoids a /0 error here if y0=y1 + // (flat-topped triangle). + if (y1 == y2) last = y1; // Include y1 scanline + else last = y1 - 1; // Skip it + + y = y0; + + if( y0 != y1 ){ + int dy01 = (1<<16) / (y1 - y0); + for (y = y0; y <= last; y++) { + a = x0 + ((sa * dy01) >> 16); + b = x0 + ((sb * dy02) >> 16); + sa += dx01; + sb += dx02; + /* longhand: + a = x0 + (x1 - x0) * (y - y0) / (y1 - y0); + b = x0 + (x2 - x0) * (y - y0) / (y2 - y0); + */ + if (a > b){ + tmp = a; + a = b; + b = tmp; + } + drawHLine(a, y, b - a + 1, col); + } + } + + // For lower part of triangle, find scanline crossings for segments + // 0-2 and 1-2. This loop is skipped if y1=y2. + if( y1 != y2 ){ + int dy12 = (1<<16) / (y2 - y1); + sa = dx12 * (y - y1); + sb = dx02 * (y - y0); + for (; y <= y2; y++) { + a = x1 + ((sa * dy12) >> 16); + b = x0 + ((sb * dy02) >> 16); + sa += dx12; + sb += dx02; + if (a > b){ + tmp = a; + a = b; + b = tmp; + } + drawHLine(a, y, b - a + 1, col); + } + } +} + +void v_project(Point* p){ + int fovz = ((90 << 16) / ((90 << 8) + p->z)); // 16:8 / 16:8 -> 16:8 + p->x = (p->x * fovz >> 8) + (176/2 << 8); // 16:8 * 16:8 = 16:16 -> 16:8 + p->y = (176/2 << 8) - (p->y * fovz >> 8); + p->z = fovz; +} + +void stars() { + f += 5; + _rngState = 1013904223; + + // rng(); rng(); rng(); + + for (int i = 0; i < 100; ++i) { + int s = rng(); + position.x = ((signed char)(s & 0xFF)) << 9; + s = rng(); + position.y = ((signed char)(s & 0xFF)) << 9; + s = rng(); + position.z = 0xFF - ((s + f) & 0xFF); + position.z <<= 12; + position.z -= 100 << 8; + + int light = position.z < (800 << 8); + int dark = position.z > ((800 + 500) << 8); + + scale = position; + scale.z += 30 << 10; + + v_project(&position); + v_project(&scale); + + s = (((s & 3) + 3) * position.z + 256) >> 7; + if (s < 1) s = 1; + if (s > 10) s = 10; + + position.x >>= 8; + position.y >>= 8; + scale.x >>= 8; + scale.y >>= 8; + + if (position.x < - 100 || position.x > 276) continue; + if (position.y < - 100 || position.y > 276) continue; + int color = 4 | (i & 1); + fillTriangle( + scale.x, scale.y, + position.x - s, position.y, + position.x + s, position.y, + light ? alternate(color, 7) : + dark ? alternate(color, 0) : + solid(color) + ); + } +} + +void transform(Point* p) { + int x = p->x; + int y = p->y; + int z = p->z; + int s, c; + if (rotation.z) { + s = sin(rotation.z); + c = cos(rotation.z); + p->x = (x*c>>8) - (y*s>>8); + p->y = (x*s>>8) + (y*c>>8); + x = p->x; + y = p->y; + } + + if (rotation.y) { + s = sin(rotation.y); + c = cos(rotation.y); + p->x = (x*c>>8) - (z*s>>8); + p->z = (x*s>>8) + (z*c>>8); + } + +// Scale + p->x = p->x * scale.x >> 8; + p->y = p->y * scale.y >> 8; + p->z = p->z * scale.z >> 8; + +// Translate + p->x += position.x; + p->y += position.y; + p->z += position.z; +} + +void render(int* n, const unsigned char* m){ + rotation.x = n[0]; + rotation.y = n[1]; + rotation.z = n[2]; + scale.x = n[3]; + scale.y = n[4]; + scale.z = n[5]; + position.x = n[6] - camera.x; + position.y = n[7] - camera.y; + position.z = n[8] - camera.z; + unsigned char tint = n[9]; + + if (position.z < near) + return; + + if (!m) + m = ship; + + int light = position.z < (800 << 8); + int dark = position.z > ((800 + 500) << 8); + + int faceCount = (((int)m[0]) << 8) + (int)m[1]; + // int vtxCount = m[2]; + const unsigned char* faceOffset = m + 3; + const unsigned char* vtxOffset = faceOffset + faceCount*4; + + Point pointA, pointB, pointC; + Point* A = &pointA; + unsigned char* Ai = 0; + Point* B = &pointB; + unsigned char* Bi = 0; + Point* C = &pointC; + unsigned char* Ci = 0; + bool Ab, Bb, Cb; + + for (int face = 0; facex = ((signed char)*indexA++) << 8; + A->y = ((signed char)*indexA++) << 8; + A->z = ((signed char)*indexA) << 8; + transform(A); + if(A->z <= near) continue; + v_project(A); + } + + if (!B) { + if (!Ab) { B = &pointA; Ab = true; } + else if (!Bb) { B = &pointB; Bb = true; } + else if (!Cb) { B = &pointC; Cb = true; } + B->x = ((signed char)*indexB++) << 8; + B->y = ((signed char)*indexB++) << 8; + B->z = ((signed char)*indexB) << 8; + transform(B); + if(B->z <= near) continue; + v_project(B); + } + + if (!C) { + if (!Ab) { C = &pointA; Ab = true; } + else if (!Bb) { C = &pointB; Bb = true; } + else if (!Cb) { C = &pointC; Cb = true; } + C->x = ((signed char)*indexC++) << 8; + C->y = ((signed char)*indexC++) << 8; + C->z = ((signed char)*indexC) << 8; + transform(C); + if(C->z <= near) continue; + v_project(C); + } + + if (((A->x - B->x) >> 8)*((A->y - C->y) >> 8) - + ((A->y - B->y) >> 8)*((A->x - C->x) >> 8) < 0) + continue; + + fillTriangle( + A->x >> 8, A->y >> 8, + B->x >> 8, B->y >> 8, + C->x >> 8, C->y >> 8, + light ? alternate(color, 7) : + dark ? alternate(color, 0) : + solid(color) + ); + } +} + +`); + +const nodeCount = 4; +const nodes = new Array(nodeCount); +const sintable = new Uint8Array(256); +const translation = new Uint32Array(10); +const BLACK = g.setColor.bind(g, 0); +const WHITE = g.setColor.bind(g, 0xFFFF); +let lcdBuffer = 0, + start = 0; + +let locked = false; +var interval; + +function setupInterval() { + if (interval) + clearInterval(interval); + if (!locked) + tick(); + interval = setInterval(tick.bind(null, !locked), locked ? 60 * 1000 : 70); +} + +function test(addr, y) { + BLACK().fillRect(0, y, 176, y); + if (peek8(addr)) return false; + WHITE().fillRect(0, y, 176, y); + let b = peek8(addr); + BLACK().fillRect(0, y, 176, y); + if (!b) return false; + return !peek8(addr); +} + +function probe() { + if (!start) + start = 0x20000000; + const end = Math.min(start + 0x800, 0x20038000); + + if (start >= end) { + print("Could not find framebuffer"); + return; + } + + BLACK().fillRect(0, 0, 176, 0); + // sampling every 64 bytes since a 176-pixel row is 66 bytes at 3bpp + for (; start < end; start += 64) { + if (peek8(start)) continue; + WHITE().fillRect(0, 0, 176, 0); + let b = peek8(start); + BLACK().fillRect(0, 0, 176, 0); + if (!b) continue; + if (!peek8(start)) break; + } + + if (start >= end) { + setTimeout(probe, 1); + return; + } + + // find the beginning of the row + while (test(start - 1, 0)) + start--; + + /* + let stride = (176 * 3 + 7) >> 3, + padding = 0; + for (let i = 0; i < 20; ++i, ++padding) { + if (test(start + stride + padding, 1)) { + break; + } + } + + stride += padding; + if (padding == 20) { + print("Warning: Could not calculate padding"); + stride = 68; + } + */ + stride = 68; + + lcdBuffer = start; + print('Found lcdBuffer at ' + lcdBuffer.toString(16) + ' stride=' + stride); + gfx.init(start, stride, E.getAddressOf(sintable, true)); + gfx.setCamera(0, 0, -300 << 8); + setupInterval(); +} + +function init() { + g.clear(); + g.setFont('6x8', 2); + g.setFontAlign(0, 0.5); + g.drawString("[LOADING]", 90, 66); + + // setup sin/cos table + for (let i = 0; i < sintable.length; ++i) + sintable[i] = Math.sin((i * Math.PI * 0.5) / sintable.length) * ((1 << 8) - 1); + + // setup nodes + let o = 0; + for (let i = 0; i < nodeCount; ++i) { + nodes[i] = { + rx: 0, + ry: 256, + rz: 0, + sx: 4, + sy: 4, + sz: 4, + vx: Math.random() * 20 - 10, + vy: Math.random() * 20 - 10, + vz: Math.random() * 5 - 2.5, + x: Math.random() * 2000 - 1000, + y: Math.random() * 2000 - 1000, + z: i * 500 + 500, + c: i + }; + } + setTimeout(probe, 1); +} + +function updateNode(index) { + let o = nodes[index]; + let x = o.x; + let y = o.y; + let z = o.z; + let tz = index * 500 + 500; + o.vx += (x < 0) * 10 - 5; + o.vy += (y < 0) * 10 - 5; + o.vz += (z < tz) * 1 - 0.5; + // lean into the curve + o.rz = o.vx * 0.5; + + x += o.vx; + y += o.vy; + z += o.vz; + + o.x = x; + o.y = y; + o.z = z; + + // iterative bubble sort + let p = nodes[index - 1]; + if (p && z > p.z) { + nodes[index - 1] = o; + nodes[index] = p; + } +} + +function drawNode(index) { + let o = nodes[index]; + let i = 0; + // float to 23.8 fixed + translation[i++] = o.rx * 256; + translation[i++] = o.ry * 256; + translation[i++] = o.rz * 256; + translation[i++] = o.sx * 256; + translation[i++] = o.sy * 256; + translation[i++] = o.sz * 256; + translation[i++] = o.x * 256; + translation[i++] = o.y * 256; + translation[i++] = o.z * 256; + translation[i++] = o.c; + gfx.render(E.getAddressOf(translation, true)); +} + +function tick(full) { + if (!lcdBuffer) + full = false; + + if (full) { + BLACK().drawRect(-1, -1, 0, 177); // dirty all the rows + gfx.clear(); + gfx.stars(); + // gfx.setCamera(0, 0, 0); + for (let i = 0; i < nodeCount; ++i) + updateNode(i); + for (let i = 0; i < nodeCount; ++i) + drawNode(i); + } + + var d = new Date(); + var h = d.getHours(), + m = d.getMinutes(); + var time = (" " + h).substr(-2) + ":" + m.toString().padStart(2, 0); + WHITE().drawString(time, 176 / 2, 176 - 16, true); +} + +init(); + +Bangle.on("lock", l => { + locked = l; + setupInterval(); +}); diff --git a/apps/warpdrive/app.png b/apps/warpdrive/app.png new file mode 100644 index 0000000000000000000000000000000000000000..ceef9691283ce5f2886a93a68bf7a95564163628 GIT binary patch literal 5936 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 SQ@D5l0000 Date: Sun, 14 Jan 2024 17:19:35 +0000 Subject: [PATCH 02/27] Fix incorrect file name in manifest --- apps/warpdrive/metadata.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/warpdrive/metadata.json b/apps/warpdrive/metadata.json index e4ca6a2af..6c01f18a2 100644 --- a/apps/warpdrive/metadata.json +++ b/apps/warpdrive/metadata.json @@ -10,7 +10,7 @@ "supports": ["BANGLEJS2"], "allow_emulator": false, "storage": [ - {"name":"pokewalk.app.js","url":"app.js"}, - {"name":"pokewalk.img","url":"app-icon.js","evaluate":true} + {"name":"warpdrive.app.js","url":"app.js"}, + {"name":"warpdrive.img","url":"app-icon.js","evaluate":true} ] } From f82a15abd6264336830c9596f20869f56f3607b2 Mon Sep 17 00:00:00 2001 From: Felipe Manga Date: Sun, 14 Jan 2024 17:33:53 +0000 Subject: [PATCH 03/27] Added screenshot --- apps/warpdrive/metadata.json | 1 + apps/warpdrive/screenshot.png | Bin 0 -> 3500 bytes 2 files changed, 1 insertion(+) create mode 100644 apps/warpdrive/screenshot.png diff --git a/apps/warpdrive/metadata.json b/apps/warpdrive/metadata.json index 6c01f18a2..c4fa1277e 100644 --- a/apps/warpdrive/metadata.json +++ b/apps/warpdrive/metadata.json @@ -5,6 +5,7 @@ "description": "A watchface with an animated 3D scene.", "readme": "README.md", "icon": "app.png", + "screenshots": [{"url":"screenshot.png"}], "type": "clock", "tags": "clock", "supports": ["BANGLEJS2"], diff --git a/apps/warpdrive/screenshot.png b/apps/warpdrive/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..110642499e82e1c0ec795126101c79023ee0e631 GIT binary patch literal 3500 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 Date: Mon, 15 Jan 2024 10:14:21 +0000 Subject: [PATCH 04/27] Make starfield into a tube --- apps/warpdrive/app.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/apps/warpdrive/app.js b/apps/warpdrive/app.js index 990b6a961..760a5e02b 100644 --- a/apps/warpdrive/app.js +++ b/apps/warpdrive/app.js @@ -239,6 +239,12 @@ void stars() { for (int i = 0; i < 100; ++i) { int s = rng(); + int a = rng(); + int ca = cos(a); + int sa = sin(a); + int r = ((rng() & 0xFF) + 0xFF) << 9; + position.x = ca << 9; + position.y = sa << 9; position.x = ((signed char)(s & 0xFF)) << 9; s = rng(); position.y = ((signed char)(s & 0xFF)) << 9; @@ -260,6 +266,9 @@ void stars() { if (s < 1) s = 1; if (s > 10) s = 10; + int rx = s*sa >> 8; + int ry = s*ca >> 8; + position.x >>= 8; position.y >>= 8; scale.x >>= 8; @@ -270,8 +279,8 @@ void stars() { int color = 4 | (i & 1); fillTriangle( scale.x, scale.y, - position.x - s, position.y, - position.x + s, position.y, + position.x - rx, position.y - ry, + position.x + rx, position.y + ry, light ? alternate(color, 7) : dark ? alternate(color, 0) : solid(color) From d202e4c76e9ac041b26ca4fe722a11ff69717462 Mon Sep 17 00:00:00 2001 From: Felipe Manga Date: Mon, 15 Jan 2024 10:19:50 +0000 Subject: [PATCH 05/27] Track charging status --- apps/warpdrive/app.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/warpdrive/app.js b/apps/warpdrive/app.js index 760a5e02b..27c90e5fb 100644 --- a/apps/warpdrive/app.js +++ b/apps/warpdrive/app.js @@ -621,3 +621,8 @@ Bangle.on("lock", l => { locked = l; setupInterval(); }); + +Bangle.on('charging', c => { + charging = c; + setupInterval(); +}); From ca9ae0f7fe6ff633ed3ea33b227ef31679bd4799 Mon Sep 17 00:00:00 2001 From: Felipe Manga Date: Mon, 15 Jan 2024 11:17:56 +0000 Subject: [PATCH 06/27] Give the starfield a slight twist --- apps/warpdrive/app.js | 32 +++++++++++--------------------- 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/apps/warpdrive/app.js b/apps/warpdrive/app.js index 27c90e5fb..e7291eadf 100644 --- a/apps/warpdrive/app.js +++ b/apps/warpdrive/app.js @@ -232,40 +232,30 @@ void v_project(Point* p){ } void stars() { - f += 5; + f += 7; _rngState = 1013904223; - // rng(); rng(); rng(); - for (int i = 0; i < 100; ++i) { - int s = rng(); - int a = rng(); + int a = rng() + ((i & 1 ? f : -f) << 7); int ca = cos(a); int sa = sin(a); - int r = ((rng() & 0xFF) + 0xFF) << 9; - position.x = ca << 9; - position.y = sa << 9; - position.x = ((signed char)(s & 0xFF)) << 9; - s = rng(); - position.y = ((signed char)(s & 0xFF)) << 9; - s = rng(); - position.z = 0xFF - ((s + f) & 0xFF); + int r = ((rng() & 0xFF) + 0xFF); + position.x = r*ca; + position.y = r*sa; + position.z = 0xFF - ((rng() + f) & 0xFF); position.z <<= 12; position.z -= 100 << 8; - int light = position.z < (800 << 8); int dark = position.z > ((800 + 500) << 8); - scale = position; - scale.z += 30 << 10; v_project(&position); + int s = 32 * position.z >> 8; + if (!s) + continue; + + scale.z += 30 << 10; v_project(&scale); - - s = (((s & 3) + 3) * position.z + 256) >> 7; - if (s < 1) s = 1; - if (s > 10) s = 10; - int rx = s*sa >> 8; int ry = s*ca >> 8; From d4f2ecd8feaaed5248bc4ea5d7c2a9e60d89832d Mon Sep 17 00:00:00 2001 From: Felipe Manga Date: Mon, 15 Jan 2024 11:33:55 +0000 Subject: [PATCH 07/27] Try to skip loading screen --- apps/warpdrive/app.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/warpdrive/app.js b/apps/warpdrive/app.js index e7291eadf..81cdda1d9 100644 --- a/apps/warpdrive/app.js +++ b/apps/warpdrive/app.js @@ -453,8 +453,11 @@ function test(addr, y) { } function probe() { - if (!start) + if (!start) { start = 0x20000000; + if (test(0x2002d3fe, 0)) // try to skip loading if possible + start = 0x2002d3fe; // FW=2v20 + } const end = Math.min(start + 0x800, 0x20038000); if (start >= end) { From 6477d4b60cf7d282aa77deddd90bde62533d6c2a Mon Sep 17 00:00:00 2001 From: Felipe Manga Date: Mon, 15 Jan 2024 11:34:25 +0000 Subject: [PATCH 08/27] Smooth speed transition between animating and frozen --- apps/warpdrive/app.js | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/apps/warpdrive/app.js b/apps/warpdrive/app.js index 81cdda1d9..92d62e486 100644 --- a/apps/warpdrive/app.js +++ b/apps/warpdrive/app.js @@ -431,15 +431,27 @@ const WHITE = g.setColor.bind(g, 0xFFFF); let lcdBuffer = 0, start = 0; -let locked = false; -var interval; +let locked = false, + charging = false; +var interval = 30, timeout; function setupInterval() { - if (interval) - clearInterval(interval); - if (!locked) - tick(); - interval = setInterval(tick.bind(null, !locked), locked ? 60 * 1000 : 70); + if (timeout) + clearTimeout(timeout); + if (!locked || charging) + tick(1); + let trigger; + let schedule = interval => { + timeout = setTimeout(trigger, interval | 0); + // print(interval); + }; + trigger = _ => { + if (!locked || charging) interval = 30; + else interval -= (interval - 130) * 0.15; + tick(1); + schedule(interval > 120 ? 60000 : interval); + }; + schedule(interval); } function test(addr, y) { From a11989315c1775e3e4906dc84e943a194727484c Mon Sep 17 00:00:00 2001 From: Felipe Manga Date: Mon, 15 Jan 2024 12:01:37 +0000 Subject: [PATCH 09/27] Add animated gif --- apps/warpdrive/README.md | 2 ++ apps/warpdrive/warpdrive.gif | Bin 0 -> 95494 bytes 2 files changed, 2 insertions(+) create mode 100644 apps/warpdrive/warpdrive.gif diff --git a/apps/warpdrive/README.md b/apps/warpdrive/README.md index 0f300c518..14fff1c97 100644 --- a/apps/warpdrive/README.md +++ b/apps/warpdrive/README.md @@ -2,4 +2,6 @@ An animated watchface featuring 3D spaceships traveling just shy of ludicrous speed. +![](warpdrive.gif) + WE BREAK FOR NOBODY. \ No newline at end of file diff --git a/apps/warpdrive/warpdrive.gif b/apps/warpdrive/warpdrive.gif new file mode 100644 index 0000000000000000000000000000000000000000..514edb24911ff39572e0c61e9805a57748e7822f GIT binary patch literal 95494 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 Date: Mon, 15 Jan 2024 15:10:15 +0000 Subject: [PATCH 10/27] Open launcher when button is pressed, show widgets when locked --- apps/warpdrive/app.js | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/apps/warpdrive/app.js b/apps/warpdrive/app.js index 92d62e486..b66295d3a 100644 --- a/apps/warpdrive/app.js +++ b/apps/warpdrive/app.js @@ -439,7 +439,7 @@ function setupInterval() { if (timeout) clearTimeout(timeout); if (!locked || charging) - tick(1); + tick(locked); let trigger; let schedule = interval => { timeout = setTimeout(trigger, interval | 0); @@ -448,7 +448,7 @@ function setupInterval() { trigger = _ => { if (!locked || charging) interval = 30; else interval -= (interval - 130) * 0.15; - tick(1); + tick(locked); schedule(interval > 120 ? 60000 : interval); }; schedule(interval); @@ -598,11 +598,8 @@ function drawNode(index) { gfx.render(E.getAddressOf(translation, true)); } -function tick(full) { - if (!lcdBuffer) - full = false; - - if (full) { +function tick(widgets) { + if (lcdBuffer) { BLACK().drawRect(-1, -1, 0, 177); // dirty all the rows gfx.clear(); gfx.stars(); @@ -617,11 +614,19 @@ function tick(full) { var h = d.getHours(), m = d.getMinutes(); var time = (" " + h).substr(-2) + ":" + m.toString().padStart(2, 0); - WHITE().drawString(time, 176 / 2, 176 - 16, true); + WHITE() + .setFontAlign(0, 0.5) + .setFont('6x8', 2) + .drawString(time, 176 / 2, 176 - 16, true); + + if (widgets) + Bangle.drawWidgets(); } init(); +Bangle.setUI("clock"); +Bangle.loadWidgets(); Bangle.on("lock", l => { locked = l; setupInterval(); From 04c08de89aa6a7f6c4c36dcac717f1ae96870d3a Mon Sep 17 00:00:00 2001 From: Felipe Manga Date: Mon, 15 Jan 2024 15:23:39 +0000 Subject: [PATCH 11/27] Don't update animation when screen is locked --- apps/warpdrive/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/warpdrive/app.js b/apps/warpdrive/app.js index b66295d3a..9559de693 100644 --- a/apps/warpdrive/app.js +++ b/apps/warpdrive/app.js @@ -599,7 +599,7 @@ function drawNode(index) { } function tick(widgets) { - if (lcdBuffer) { + if (lcdBuffer && !widgets) { BLACK().drawRect(-1, -1, 0, 177); // dirty all the rows gfx.clear(); gfx.stars(); From 9a82a2e1eef6e55d03fe8b13626f954d20851a20 Mon Sep 17 00:00:00 2001 From: Felipe Manga Date: Mon, 15 Jan 2024 17:43:05 +0000 Subject: [PATCH 12/27] Ensure text is drawn with a black background --- apps/warpdrive/app.js | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/warpdrive/app.js b/apps/warpdrive/app.js index 9559de693..d3404276d 100644 --- a/apps/warpdrive/app.js +++ b/apps/warpdrive/app.js @@ -615,6 +615,7 @@ function tick(widgets) { m = d.getMinutes(); var time = (" " + h).substr(-2) + ":" + m.toString().padStart(2, 0); WHITE() + .setBgColor(0) .setFontAlign(0, 0.5) .setFont('6x8', 2) .drawString(time, 176 / 2, 176 - 16, true); From 84fd254d547443f03f02b876b2c6e3c05e0ac929 Mon Sep 17 00:00:00 2001 From: Felipe Manga Date: Mon, 15 Jan 2024 18:36:24 +0000 Subject: [PATCH 13/27] Implement full theme support --- apps/warpdrive/app.js | 95 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 79 insertions(+), 16 deletions(-) diff --git a/apps/warpdrive/app.js b/apps/warpdrive/app.js index d3404276d..3ceac4e89 100644 --- a/apps/warpdrive/app.js +++ b/apps/warpdrive/app.js @@ -1,6 +1,6 @@ const gfx = E.compiledC(` // void init(int, int, int) -// void clear() +// void clear(int) // void render(int, int) // void setCamera(int, int, int) // void stars() @@ -53,15 +53,6 @@ int cos(int angle) { return sin(angle + 0x8000); } -void clear() { - unsigned short* cursor = (unsigned short*) fb; - for (int y = 0; y < 176; ++y) { - for (int x = 0; x < 66/2; ++x) - *cursor++ = 0; - cursor++; - } -} - void setCamera(int x, int y, int z) { camera.x = x; camera.y = y; @@ -128,6 +119,76 @@ void drawHLine(int x, unsigned int y, int l, unsigned int c) { } } +void fillRect(int x, unsigned int y, int w, int h, unsigned int c) { + if (x < 0) { + w += x; + x = 0; + } + if (x + w >= 176) { + w = 176 - x; + } + if (w <= 0 || y >= 176) + return; + + if (y < 0) { + h += y; + y = 0; + } + if (y + h >= 176) { + h = 176 - y; + } + if (h <= 0 || y >= 176) + return; + + int bitstart = x * 3; + int bitend = (x + w) * 3; + int wstart = bitstart >> 5; + int wend = bitend >> 5; + int padstart = bitstart & 31; + int padend = bitend & 31; + int maskstart = -1 << padstart; + int maskend = unsigned(-1) >> (32 - padend); + if (wstart == wend) { + maskstart &= maskend; + maskend = 0; + } + + int* row = (int*) &fb[y * stride]; + if (maskstart) { + for (int i = 0; i < h; ++i) + row[wstart + (i*stride>>2)] = (row[wstart + (i*stride>>2)] & ~maskstart) | ((c << padstart) & maskstart); + while (bitstart >> 5 == wstart) + bitstart += 3; + } + if (maskend) { + for (int i = 0; i < h; ++i) + row[wend + (i*stride>>2)] = (row[wend + (i*stride>>2)] & ~maskend) | + (((c >> (30 - padend)) | (c >> (36 - padend))) & maskend); + } + bitend -= padend; + for (int x = bitstart; x < bitend; x += 10 * 3) { + unsigned int R = x & 31; + R = (c << R) | (c >> (36 - R)) | (c >> (30 - R)) | (c << (R - 6)); + for (int i = 0; i < h; ++i) + row[(x >> 5) + (i*stride>>2)] = R; + } +} + +void clear(int c) { + c &= 7; + if (!c || c==7) { + c = solid(c); + unsigned short* cursor = (unsigned short*) fb; + for (int y = 0; y < 176; ++y) { + for (int x = 0; x < 66/2; ++x) + *cursor++ = c; + cursor++; + } + } else { + fillRect(0, 0, 176, 176, solid(c)); + } +} + void fillTriangle( int x0, int y0, int x1, int y1, int x2, int y2, @@ -426,6 +487,7 @@ const nodeCount = 4; const nodes = new Array(nodeCount); const sintable = new Uint8Array(256); const translation = new Uint32Array(10); +let bgColor = 0; const BLACK = g.setColor.bind(g, 0); const WHITE = g.setColor.bind(g, 0xFFFF); let lcdBuffer = 0, @@ -433,7 +495,8 @@ let lcdBuffer = 0, let locked = false, charging = false; -var interval = 30, timeout; +var interval = 30, + timeout; function setupInterval() { if (timeout) @@ -522,6 +585,9 @@ function probe() { } function init() { + bgColor = g.theme.bg & 0x8410; + bgColor = ((bgColor >> 15) | (bgColor >> 9) | (bgColor >> 2)); + g.clear(); g.setFont('6x8', 2); g.setFontAlign(0, 0.5); @@ -601,9 +667,8 @@ function drawNode(index) { function tick(widgets) { if (lcdBuffer && !widgets) { BLACK().drawRect(-1, -1, 0, 177); // dirty all the rows - gfx.clear(); + gfx.clear(bgColor); gfx.stars(); - // gfx.setCamera(0, 0, 0); for (let i = 0; i < nodeCount; ++i) updateNode(i); for (let i = 0; i < nodeCount; ++i) @@ -614,9 +679,7 @@ function tick(widgets) { var h = d.getHours(), m = d.getMinutes(); var time = (" " + h).substr(-2) + ":" + m.toString().padStart(2, 0); - WHITE() - .setBgColor(0) - .setFontAlign(0, 0.5) + g.setFontAlign(0, 0.5) .setFont('6x8', 2) .drawString(time, 176 / 2, 176 - 16, true); From d41ffac16b648dc24dac8716f84df16e7546729a Mon Sep 17 00:00:00 2001 From: Felipe Manga Date: Mon, 15 Jan 2024 18:38:37 +0000 Subject: [PATCH 14/27] Update README.md --- apps/warpdrive/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/warpdrive/README.md b/apps/warpdrive/README.md index 14fff1c97..0f596a500 100644 --- a/apps/warpdrive/README.md +++ b/apps/warpdrive/README.md @@ -4,4 +4,6 @@ An animated watchface featuring 3D spaceships traveling just shy of ludicrous sp ![](warpdrive.gif) -WE BREAK FOR NOBODY. \ No newline at end of file +WE BREAK FOR NOBODY. + +Theme colors and widgets supported. Widgets only appear when the screen is locked. \ No newline at end of file From 7633f9e2752903177dee98a7dd84bf0a8c106f39 Mon Sep 17 00:00:00 2001 From: Felipe Manga Date: Mon, 15 Jan 2024 18:59:29 +0000 Subject: [PATCH 15/27] Make sure text is drawn with the foreground color --- apps/warpdrive/app.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/warpdrive/app.js b/apps/warpdrive/app.js index 3ceac4e89..d80356ecc 100644 --- a/apps/warpdrive/app.js +++ b/apps/warpdrive/app.js @@ -679,7 +679,8 @@ function tick(widgets) { var h = d.getHours(), m = d.getMinutes(); var time = (" " + h).substr(-2) + ":" + m.toString().padStart(2, 0); - g.setFontAlign(0, 0.5) + g.setColor(g.theme.fg) + .setFontAlign(0, 0.5) .setFont('6x8', 2) .drawString(time, 176 / 2, 176 - 16, true); From f5ae409a9212fea0f41879291c6d8b5dc849e18b Mon Sep 17 00:00:00 2001 From: Felipe Manga Date: Mon, 15 Jan 2024 19:35:23 +0000 Subject: [PATCH 16/27] Simplify scheduling code since it wasn't playing well with widgets --- apps/warpdrive/app.js | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/apps/warpdrive/app.js b/apps/warpdrive/app.js index d80356ecc..cf7d40b6e 100644 --- a/apps/warpdrive/app.js +++ b/apps/warpdrive/app.js @@ -498,23 +498,12 @@ let locked = false, var interval = 30, timeout; -function setupInterval() { +function setupInterval(force) { if (timeout) clearTimeout(timeout); - if (!locked || charging) - tick(locked); - let trigger; - let schedule = interval => { - timeout = setTimeout(trigger, interval | 0); - // print(interval); - }; - trigger = _ => { - if (!locked || charging) interval = 30; - else interval -= (interval - 130) * 0.15; - tick(locked); - schedule(interval > 120 ? 60000 : interval); - }; - schedule(interval); + let stopped = locked && !charging; + timeout = setTimeout(setupInterval, stopped ? 60000 : 30); + tick(stopped && !force); } function test(addr, y) { @@ -581,7 +570,7 @@ function probe() { print('Found lcdBuffer at ' + lcdBuffer.toString(16) + ' stride=' + stride); gfx.init(start, stride, E.getAddressOf(sintable, true)); gfx.setCamera(0, 0, -300 << 8); - setupInterval(); + setupInterval(true); } function init() { From a2c6f53ecebccf0b2b301a64326f096e0e3ae82b Mon Sep 17 00:00:00 2001 From: Felipe Manga Date: Tue, 16 Jan 2024 23:21:37 +0000 Subject: [PATCH 17/27] Add Synthwave variant of Warpdrive watchface --- apps/synthwave/README.md | 9 + apps/synthwave/app-icon.js | 1 + apps/synthwave/app.js | 724 ++++++++++++++++++++++++++++++++++ apps/synthwave/app.png | Bin 0 -> 4862 bytes apps/synthwave/metadata.json | 17 + apps/synthwave/screenshot.png | Bin 0 -> 3006 bytes apps/synthwave/theme.png | Bin 0 -> 3082 bytes apps/synthwave/widgets.png | Bin 0 -> 3171 bytes 8 files changed, 751 insertions(+) create mode 100644 apps/synthwave/README.md create mode 100644 apps/synthwave/app-icon.js create mode 100644 apps/synthwave/app.js create mode 100644 apps/synthwave/app.png create mode 100644 apps/synthwave/metadata.json create mode 100644 apps/synthwave/screenshot.png create mode 100644 apps/synthwave/theme.png create mode 100644 apps/synthwave/widgets.png diff --git a/apps/synthwave/README.md b/apps/synthwave/README.md new file mode 100644 index 000000000..9f92de33f --- /dev/null +++ b/apps/synthwave/README.md @@ -0,0 +1,9 @@ +# Synthwave Watch + +Fly towards the sunset in a 3D jet, cruising to the sound of futuristic synthesizers*. + +![](screenshot.png) ![](widgets.png) ![](theme.png) + +Theme colors and widgets supported. Widgets only appear when the screen is locked. + +* synthesizers not included diff --git a/apps/synthwave/app-icon.js b/apps/synthwave/app-icon.js new file mode 100644 index 000000000..5b46e62cf --- /dev/null +++ b/apps/synthwave/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4f/AoP///NjHvlGf/e4yMVzFf/cUqNHAQNFAQnVBAvPtu27fv2Vp1Wr12r3Vp00YsMEiEd/v3jv3798uIcBuMFEYtvEYP79++926AIPq9eq02atOGiEUqvf799+8RoscuPFikQEYlp0+uEYW6AQIFB12u3WqJQOaCgQeBAgPNmnTpoHBosEEYZlC3YmCSYP/3369YLBEYVBEoYgBEYQCBTQVBoo7BMoJEBEAPnz1//5QD3XrMoMUuYdCpl548dEYUc6P3+4TBMoX//1DhmSvIoBEYP69/+uPHIIhnB48cuhKE+47CMwXyEYMyvPnEYQOCWAIjD48avOGjFxBALhBu/XC4f//kwyVJkmTk4gCAQVfEYV9mOmjARB7lxcAQCCEAJoBvOMkgjBk88pMw/JKDCgP06dBoOECIMluFJkxuCEYQ7C/EaEYlK7OT8wOCNAcfeYIjCua9BqPx44OBaIdDzfACIOf82YtOmiFDEYcx48eot0ic161044jBocHEYI4B/3/+cY7MP1M/wEYEYMMz1//zLDpl9ilcq/sqHDg0evPHjpHC//tgkQpVI9EDmEwknDk4NB3/TrojC4sQEAJEBpOHzzaCI4IkC/XQgEGmFByZECBgX/CgXcqKJC4sWjBEBKYRrD73//P4tf2zEwIgOfEQYjDa4PFqtOuPHjhEDEYgVB9XyIAPnEAYLBa4vV6r+BDogCC6IqBDAP+12vDwX+XgQgCAQOu79Nj8UEA4CBJYIjBC4m/IIO/BAgCB9euuPNm5ELEYW6DQoCKqPHj/fo4aB5v0KAMcqP3EYf6LgP/HYJoC3Xr3369er3YjBEAQmB7l94/3793ml9osXuPFigaBEAW+34gBRIRoC1wFBC4PXudHfQNHj9d+v3JoPfI4MX3fu9w7BDQO/1eu3eu/XqEgPq91zoogB/vXrt3i9duvXqYvBqN17/7JAO7JQROC3XuEAIFB33u7oXBJQVR+omBEYIgBFgXX/Q+B9wdBxwjDAQPo9f6AoIA=")) diff --git a/apps/synthwave/app.js b/apps/synthwave/app.js new file mode 100644 index 000000000..ac277b739 --- /dev/null +++ b/apps/synthwave/app.js @@ -0,0 +1,724 @@ +const gfx = E.compiledC(` +// void init(int, int, int) +// void tick(int) +// void render(int) +// void setCamera(int, int, int) +// void bubble(int, int, int, int) + +unsigned char* fb; +int stride; +unsigned char* sint; + +const int near = 5 << 8; +int f = 0; + +typedef struct { + int x, y, z; +} Point; + +Point camera; +Point rotation; +Point scale; +Point position; +Point speed; + +const unsigned char ship[] = { +0,38,25,10,3,8,6,10,7,3,6,13,3,11,5,13,1,12,3,15,3,5,8,15,1,3,7,13,12,11,3,15,5,6,8,15,6,1,7,10,5,0,6,10,0,1,6,12,5,11,4,12,12,1,2,12,2,11,12,12,10,5,4,13,5,10,0,12,2,1,9,13,9,1,0,12,4,11,2,10,19,22,21,12,4,2,10,12,10,2,9,10,13,16,15,13,10,9,0,15,21,20,19,15,15,14,13,15,19,20,22,15,13,14,16,15,21,23,20,15,15,17,14,15,22,20,23,10,22,24,21,15,16,14,17,10,16,18,15,15,24,23,21,15,18,17,15,15,22,23,24,15,16,17,18,0,0,62,236,243,244,247,0,234,0,229,194,11,0,234,21,243,246,0,234,33,193,250,20,63,249,19,249,4,3,9,4,3,7,247,222,250,247,222,240,0,22,238,13,22,226,1,20,229,7,62,225,11,20,208,27,62,19,0,20,22,12,20,33,0,18,30,5,60,34,10,18,52,26,60 +}; + +const unsigned int terrainLength = 12; +const unsigned int terrainWidth = 12; +unsigned char terrain[terrainLength][terrainWidth]; +unsigned int travel = 0; + +unsigned int _rngState; +unsigned int rng() { + _rngState ^= _rngState << 17; + _rngState ^= _rngState >> 13; + _rngState ^= _rngState << 5; + return _rngState; +} + +void shiftTerrain() { + travel++; + for (int i = terrainLength - 1; i > 0; --i) { + for (int x = 0; x < terrainWidth; ++x) + terrain[i][x] = terrain[i-1][x]; + } + + for (int x = 0; x < terrainWidth; ++x) + terrain[0][x] = (rng() & 0x3F) + 0xF; + for (int x = 0; x < (terrainWidth >> 3); ++x) + terrain[0][((terrainWidth>>1)-(terrainWidth>>4)) + x] >>= 1; + for (int x = 0; x < (terrainWidth >> 2); ++x) + terrain[0][((terrainWidth>>1)-(terrainWidth>>5)) + x] = 0; +} + +void init(unsigned char* _fb, int _stride, unsigned char* _sint) { + fb = _fb; + stride = _stride; + sint = _sint; + _rngState = 1013904223; + for (int i = 0; i < terrainLength; ++i) + shiftTerrain(); + speed.x = 0; + speed.y = 0; + speed.z = 0; + position.x = 100 << 8; + position.y = -150 << 8; + position.z = 100 << 8; + rotation.x = 0; + rotation.y = 256 << 8; + rotation.z = 0; + scale.x = 1 << 8; + scale.y = 1 << 8; + scale.z = 1 << 8; +} + +int sin(int angle) { + int a = (angle >> 7) & 0xFF; + if (angle & (1 << 15)) + a = 0xFF - a; + int v = sint[a]; + if (angle & (1 << 16)) + v = -v; + return v; +} + +int cos(int angle) { + return sin(angle + 0x8000); +} + +void setCamera(int x, int y, int z) { + camera.x = x; + camera.y = y; + camera.z = z; +} + +unsigned int solid(unsigned int c) { + c &= 7; + c |= c << 3; + c |= c << 6; + c |= c << 12; + c |= c << 24; + return c; +} + +unsigned int alternate(unsigned int a, unsigned int b) { + unsigned int c = (a & 7) | ((b & 7) << 3); + c |= c << 6; + c |= c << 12; + c |= c << 24; + return c; +} + +void drawHLine(int x, unsigned int y, int l, unsigned int c) { + if (x < 0) { + l += x; + x = 0; + } + if (x + l >= 176) { + l = 176 - x; + } + if (l <= 0 || y >= 176) + return; + + if (y & 1) + c = alternate(c >> 3, c); + + int bitstart = x * 3; + int bitend = (x + l) * 3; + int wstart = bitstart >> 5; + int wend = bitend >> 5; + int padstart = bitstart & 31; + int padend = bitend & 31; + int maskstart = -1 << padstart; + int maskend = unsigned(-1) >> (32 - padend); + if (wstart == wend) { + maskstart &= maskend; + maskend = 0; + } + + int* row = (int*) &fb[y * stride]; + if (maskstart) { + row[wstart] = (row[wstart] & ~maskstart) | ((c << padstart) & maskstart); + while (bitstart >> 5 == wstart) + bitstart += 3; + } + if (maskend) + row[wend] = (row[wend] & ~maskend) | + (((c >> (30 - padend)) | (c >> (36 - padend))) & maskend); + bitend -= padend; + for (int x = bitstart; x < bitend; x += 10 * 3) { + unsigned int R = x & 31; + row[x >> 5] = (c << R) | (c >> (36 - R)) | (c >> (30 - R)) | (c << (R - 6)); + } +} + +void fillRect(int x, unsigned int y, int w, int h, unsigned int c) { + if (x < 0) { + w += x; + x = 0; + } + if (x + w >= 176) { + w = 176 - x; + } + if (w <= 0 || y >= 176) + return; + + if (y < 0) { + h += y; + y = 0; + } + if (y + h >= 176) { + h = 176 - y; + } + if (h <= 0 || y >= 176) + return; + + int bitstart = x * 3; + int bitend = (x + w) * 3; + int wstart = bitstart >> 5; + int wend = bitend >> 5; + int padstart = bitstart & 31; + int padend = bitend & 31; + int maskstart = -1 << padstart; + int maskend = unsigned(-1) >> (32 - padend); + if (wstart == wend) { + maskstart &= maskend; + maskend = 0; + } + + int* row = (int*) &fb[y * stride]; + if (maskstart) { + for (int i = 0; i < h; ++i) + row[wstart + (i*stride>>2)] = (row[wstart + (i*stride>>2)] & ~maskstart) | ((c << padstart) & maskstart); + while (bitstart >> 5 == wstart) + bitstart += 3; + } + if (maskend) { + for (int i = 0; i < h; ++i) + row[wend + (i*stride>>2)] = (row[wend + (i*stride>>2)] & ~maskend) | + (((c >> (30 - padend)) | (c >> (36 - padend))) & maskend); + } + bitend -= padend; + for (int x = bitstart; x < bitend; x += 10 * 3) { + unsigned int R = x & 31; + R = (c << R) | (c >> (36 - R)) | (c >> (30 - R)) | (c << (R - 6)); + for (int i = 0; i < h; ++i) + row[(x >> 5) + (i*stride>>2)] = R; + } +} + +void fillTriangle( int x0, int y0, + int x1, int y1, + int x2, int y2, + unsigned int col) { + int a, b, y, last, tmp; + + a = 176; + b = 176; + if( x0 < 0 && x1 < 0 && x2 < 0 ) return; + if( x0 >= a && x1 > a && x2 > a ) return; + if( y0 < 0 && y1 < 0 && y2 < 0 ) return; + if( y0 >= b && y1 > b && y2 > b ) return; + + // Sort coordinates by Y order (y2 >= y1 >= y0) + if (y0 > y1) { + tmp = y0; y0 = y1; y1 = tmp; + tmp = x0; x0 = x1; x1 = tmp; + } + if (y1 > y2) { + tmp = y2; y2 = y1; y1 = tmp; + tmp = x2; x2 = x1; x1 = tmp; + } + if (y0 > y1) { + tmp = y0; y0 = y1; y1 = tmp; + tmp = x0; x0 = x1; x1 = tmp; + } + + if (y0 == y2) { // Handle awkward all-on-same-line case as its own thing + a = b = x0; + if (x1 < a) a = x1; + else if (x1 > b) b = x1; + if (x2 < a) a = x2; + else if (x2 > b) b = x2; + drawHLine(a, y0, b - a + 1, col); + return; + } + + int dx01 = x1 - x0, + dx02 = x2 - x0, + dy02 = (1<<16) / (y2 - y0), + dx12 = x2 - x1, + sa = 0, + sb = 0; + + // For upper part of triangle, find scanline crossings for segments + // 0-1 and 0-2. If y1=y2 (flat-bottomed triangle), the scanline y1 + // is included here (and second loop will be skipped, avoiding a /0 + // error there), otherwise scanline y1 is skipped here and handled + // in the second loop...which also avoids a /0 error here if y0=y1 + // (flat-topped triangle). + if (y1 == y2) last = y1; // Include y1 scanline + else last = y1 - 1; // Skip it + + y = y0; + + if( y0 != y1 ){ + int dy01 = (1<<16) / (y1 - y0); + for (y = y0; y <= last; y++) { + a = x0 + ((sa * dy01) >> 16); + b = x0 + ((sb * dy02) >> 16); + sa += dx01; + sb += dx02; + /* longhand: + a = x0 + (x1 - x0) * (y - y0) / (y1 - y0); + b = x0 + (x2 - x0) * (y - y0) / (y2 - y0); + */ + if (a > b){ + tmp = a; + a = b; + b = tmp; + } + drawHLine(a, y, b - a + 1, col); + } + } + + // For lower part of triangle, find scanline crossings for segments + // 0-2 and 1-2. This loop is skipped if y1=y2. + if( y1 != y2 ){ + int dy12 = (1<<16) / (y2 - y1); + sa = dx12 * (y - y1); + sb = dx02 * (y - y0); + for (; y <= y2; y++) { + a = x1 + ((sa * dy12) >> 16); + b = x0 + ((sb * dy02) >> 16); + sa += dx12; + sb += dx02; + if (a > b){ + tmp = a; + a = b; + b = tmp; + } + drawHLine(a, y, b - a + 1, col); + } + } +} + +void v_project(Point* p){ + int fovz = ((90 << 16) / ((90 << 8) + p->z)); // 16:16 / 16:8 -> 16:8 + p->x = (p->x * fovz >> 8) + (176/2 << 8); // 16:8 * 16:8 = 16:16 -> 16:8 + p->y = (176/2 << 8) - (p->y * fovz >> 8); + p->z = fovz; +} + +void drawTerrain() { + const int tileSize = 40 << 8; + camera.x = (terrainWidth + 2) * tileSize / 2; + camera.y = 60 << 8; + camera.z += 6 << 8; + if (camera.z > tileSize * 3) { + camera.z -= tileSize; + shiftTerrain(); + } + + int dist[] = { + solid(7), + alternate(5, 7), + solid(5), + solid(5), + alternate(5, 0), + solid(0) + }; + int line = solid(5); + + int fovz; + int prvz = ((90 << 16) / ((90 << 8) + ((terrainLength) * tileSize - camera.z))); // 16:16 / 16:8 = 16:8 + for (int i = 0; i < terrainLength - 1; ++i, prvz = fovz) { + fovz = ((90 << 16) / ((90 << 8) + ((terrainLength - (i + 1)) * tileSize - camera.z))); // 16:16 / 16:8 = 16:8 + int lum = i < 3 ? i - 3 : 3; + for (int x = 0; x < terrainWidth - 1; ++x) { + int ax = (((((x ) * tileSize - camera.x) >> 8) * prvz) >> 8) + (176/2); // int * 16:8 = 16:8 -> int + int bx = (((((x + 1) * tileSize - camera.x) >> 8) * prvz) >> 8) + (176/2); // int * 16:8 = 16:8 -> int + int cx = (((((x ) * tileSize - camera.x) >> 8) * fovz) >> 8) + (176/2); // int * 16:8 = 16:8 -> int + int dx = (((((x + 1) * tileSize - camera.x) >> 8) * fovz) >> 8) + (176/2); // int * 16:8 = 16:8 -> int + + int ay = (176/2) - ((((terrain[i ][x ] << 8) - camera.y) >> 8) * prvz >> 8); + int by = (176/2) - ((((terrain[i ][x + 1] << 8) - camera.y) >> 8) * prvz >> 8); + int cy = (176/2) - ((((terrain[i + 1][x ] << 8) - camera.y) >> 8) * fovz >> 8); + int dy = (176/2) - ((((terrain[i + 1][x + 1] << 8) - camera.y) >> 8) * fovz >> 8); + + int na = (ax - bx)*(ay - cy) - (ay - by)*(ax - cx); + if (na > 0) { + int c = (lum + (na >> 8)); + if (c < 0) c = 0; + else if (c > 5) c = 5; + c = dist[c]; + fillTriangle(ax, ay, bx, by, cx, cy, c); + } + + na = (bx - dx)*(by - cy) - (by - dy)*(bx - cx); + if (na > 0) { + int c = (lum + (na >> 8)); + if (c < 0) c = 0; + else if (c > 5) c = 5; + c = dist[c]; + fillTriangle(bx, by, cx, cy, dx, dy, c); + if (!c) { + fillTriangle(ax, ay, bx, by, bx, by - 1, line); + fillTriangle(ax, ay, cx, cy, cx, cy - 1, line); + } + } + } + } +} + +void transform(Point* p) { + int x = p->x; + int y = p->y; + int z = p->z; + int s, c; + + if (rotation.x) { + s = sin(rotation.x); + c = cos(rotation.x); + p->y = (y*c>>8) - (z*s>>8); + p->z = (y*s>>8) + (z*c>>8); + y = p->y; + z = p->z; + } + + if (rotation.z) { + s = sin(rotation.z); + c = cos(rotation.z); + p->x = (x*c>>8) - (y*s>>8); + p->y = (x*s>>8) + (y*c>>8); + x = p->x; + y = p->y; + } + + if (rotation.y) { + s = sin(rotation.y); + c = cos(rotation.y); + p->x = (x*c>>8) - (z*s>>8); + p->z = (x*s>>8) + (z*c>>8); + } + +// Scale + p->x = p->x * scale.x >> 8; + p->y = p->y * scale.y >> 8; + p->z = p->z * scale.z >> 8; + +// Translate + p->x += position.x; + p->y += position.y; + p->z += position.z; +} + +void fillCircleInternal(int xc, int yc, int x, int y, int c) { + drawHLine(xc - x, yc - y, x * 2, c); + drawHLine(xc - x, yc + y, x * 2, c); + drawHLine(xc - y, yc - x, y * 2, c); + drawHLine(xc - y, yc + x, y * 2, c); +} + +void fillCircle(int xc, int yc, int r, int color) { + if (r < 1 || xc + r < 0 || xc - r >= 176 || yc + r < 0 || yc - r >= 176) + return; + int x = 0, y = r; + int d = 3 - 2 * r; + fillCircleInternal(xc, yc, x, y, color); + while (y >= x) { + x++; + if (d > 0) { + y--; + d = d + 4 * (x - y) + 10; + } else { + d = d + 4 * x + 6; + } + fillCircleInternal(xc, yc, x, y, color); + } +} + +void bubble(int x, int y, int r, int c) { + fillCircle(x, y, r + 3, alternate(7, 4)); + fillCircle(x, y, r, alternate(c, 0)); + int rs = r * 0xE666 >> 16; + int off = (r - rs) * 0x9696 >> 16; + fillCircle(x + off, y - off, rs, solid(c)); + rs = r * 0x4CCC >> 16; + off = (r - rs) * 0x9696 >> 16; + fillCircle(x + off, y - off, rs, alternate(c, 7)); + rs = r * 0x1999 >> 16; + off = (r - rs) * 0x8E38 >> 16; + fillCircle(x + off, y - off, rs, solid(7)); +} + +void render(const unsigned char* m){ + if (position.z < near) + return; + + if (!m) + m = ship; + + int faceCount = (((int)m[0]) << 8) + (int)m[1]; + const unsigned char* faceOffset = m + 3; + const unsigned char* vtxOffset = faceOffset + faceCount*4; + + Point pointA, pointB, pointC; + Point* A = &pointA; + unsigned char* Ai = 0; + Point* B = &pointB; + unsigned char* Bi = 0; + Point* C = &pointC; + unsigned char* Ci = 0; + bool Ab, Bb, Cb; + + for (int face = 0; face> 2) & 1; + + const unsigned char* indexA = vtxOffset + ((int)*faceOffset++) * 3; + const unsigned char* indexB = vtxOffset + ((int)*faceOffset++) * 3; + const unsigned char* indexC = vtxOffset + ((int)*faceOffset++) * 3; + + if( indexA == Ai ){ Ab = true; } + else if( indexA == Bi ){ A = &pointB; Bb = true; } + else if( indexA == Ci ){ A = &pointC; Cb = true; } + else A = 0; + + if (indexB == Bi) { Bb = true; } + else if (indexB == Ai) { B = &pointA; Ab = true; } + else if (indexB == Ci) { B = &pointC; Cb = true; } + else B = 0; + + if (indexC == Ci) { Cb = true; } + else if (indexC == Bi) { C = &pointB; Bb = true; } + else if (indexC == Ai) { C = &pointA; Ab = true; } + else C = 0; + + if (!A) { + if (!Ab) { A = &pointA; Ab = true; } + else if (!Bb) { A = &pointB; Bb = true; } + else if (!Cb) { A = &pointC; Cb = true; } + A->x = ((signed char)*indexA++) << 8; + A->y = ((signed char)*indexA++) << 8; + A->z = ((signed char)*indexA) << 8; + transform(A); + if(A->z <= near) continue; + v_project(A); + } + + if (!B) { + if (!Ab) { B = &pointA; Ab = true; } + else if (!Bb) { B = &pointB; Bb = true; } + else if (!Cb) { B = &pointC; Cb = true; } + B->x = ((signed char)*indexB++) << 8; + B->y = ((signed char)*indexB++) << 8; + B->z = ((signed char)*indexB) << 8; + transform(B); + if(B->z <= near) continue; + v_project(B); + } + + if (!C) { + if (!Ab) { C = &pointA; Ab = true; } + else if (!Bb) { C = &pointB; Bb = true; } + else if (!Cb) { C = &pointC; Cb = true; } + C->x = ((signed char)*indexC++) << 8; + C->y = ((signed char)*indexC++) << 8; + C->z = ((signed char)*indexC) << 8; + transform(C); + if(C->z <= near) continue; + v_project(C); + } + + int cross = (A->x - B->x)*(A->y - C->y) - (A->y - B->y)*(A->x - C->x); + if (cross < 0) + continue; + + cross >>= 8; + int light = cross > (20000 << 3); + int dark = cross < (5000 << 2); + + fillTriangle( + A->x >> 8, A->y >> 8, + B->x >> 8, B->y >> 8, + C->x >> 8, C->y >> 8, + light ? alternate(color, 7) : + dark ? alternate(color, 0) : + solid(color) + ); + } +} + +void tick(int c) { + c &= 7; + if (!c || c==7) { + c = solid(c); + unsigned short* cursor = (unsigned short*) fb; + for (int y = 0; y < 176; ++y) { + for (int x = 0; x < 66/2; ++x) + *cursor++ = c; + cursor++; + } + } else { + fillRect(0, 0, 176, 176, solid(c)); + } + + + fillCircle(88, 110, 35, alternate(5,0)); + fillCircle(88, 110, 27, alternate(5,7)); + fillCircle(88, 110, 20, solid(7)); + drawTerrain(); + + speed.x += ((position.x < 0) ? 1 : -1) << 8; + speed.y += ((position.y < (-80 << 8)) ? 1 : -1) << 8; + rotation.x = speed.y; + rotation.z = speed.x; + position.y += speed.y >> 1; + position.x += speed.x >> 1; + + render(ship); +} + + +`); + +const sintable = new Uint8Array(256); +let bgColor = 0; +const BLACK = g.setColor.bind(g, 0); +const WHITE = g.setColor.bind(g, 0xFFFF); +let lcdBuffer = 0, + start = 0; + +let locked = false, + charging = false; +var interval = 30; +var timeout; + +function setupInterval(force) { + if (timeout) + clearTimeout(timeout); + let stopped = locked && !charging; + timeout = setTimeout(setupInterval, stopped ? 60000 : 60); + tick(stopped && !force); +} + +function test(addr, y) { + BLACK().fillRect(0, y, 176, y); + if (peek8(addr)) return false; + WHITE().fillRect(0, y, 176, y); + let b = peek8(addr); + BLACK().fillRect(0, y, 176, y); + if (!b) return false; + return !peek8(addr); +} + +function probe() { + if (!start) { + start = 0x20000000; + if (test(0x2002d3fe, 0)) // try to skip loading if possible + start = 0x2002d3fe; // FW=2v20 + } + const end = Math.min(start + 0x800, 0x20038000); + + if (start >= end) { + print("Could not find framebuffer"); + return; + } + + BLACK().fillRect(0, 0, 176, 0); + // sampling every 64 bytes since a 176-pixel row is 66 bytes at 3bpp + for (; start < end; start += 64) { + if (peek8(start)) continue; + WHITE().fillRect(0, 0, 176, 0); + let b = peek8(start); + BLACK().fillRect(0, 0, 176, 0); + if (!b) continue; + if (!peek8(start)) break; + } + + if (start >= end) { + setTimeout(probe, 1); + return; + } + + // find the beginning of the row + while (test(start - 1, 0)) + start--; + + /* + let stride = (176 * 3 + 7) >> 3, + padding = 0; + for (let i = 0; i < 20; ++i, ++padding) { + if (test(start + stride + padding, 1)) { + break; + } + } + + stride += padding; + if (padding == 20) { + print("Warning: Could not calculate padding"); + stride = 68; + } + */ + stride = 68; + + lcdBuffer = start; + print('Found lcdBuffer at ' + lcdBuffer.toString(16) + ' stride=' + stride); + gfx.init(start, stride, E.getAddressOf(sintable, true)); + gfx.setCamera(0, 0, 0); + setupInterval(true); +} + +function init() { + require("Font5x9Numeric7Seg").add(Graphics); + g.setFont("5x9Numeric7Seg"); + bgColor = g.theme.bg & 0x8410; + bgColor = ((bgColor >> 15) | (bgColor >> 9) | (bgColor >> 2)); + + g.clear(); + g.setFontAlign(0, 0.5); + g.drawString("[LOADING]", 90, 66); + + // setup sin/cos table + for (let i = 0; i < sintable.length; ++i) + sintable[i] = Math.sin((i * Math.PI * 0.5) / sintable.length) * ((1 << 8) - 1); + setTimeout(probe, 1); +} + +function tick(widgets) { + if (lcdBuffer && !widgets) { + BLACK().drawRect(-1, -1, 0, 177); // dirty all the rows + gfx.tick(bgColor); + } + + var d = new Date(); + var h = d.getHours(), + m = d.getMinutes(); + var time = (" " + h).substr(-2) + ":" + m.toString().padStart(2, 0); + g.setColor(g.theme.fg) + .setFontAlign(0, 0.5) + .setFont('5x9Numeric7Seg', 5) + .drawString(time, 176 / 2, 50, true); + + if (widgets) + Bangle.drawWidgets(); +} + +init(); + +Bangle.setUI("clock"); +Bangle.loadWidgets(); +Bangle.on("lock", l => { + locked = l; + setupInterval(); +}); + +Bangle.on('charging', c => { + charging = c; + setupInterval(); +}); diff --git a/apps/synthwave/app.png b/apps/synthwave/app.png new file mode 100644 index 0000000000000000000000000000000000000000..b85c1ac62d3d576ba8c42e0061143982aaf82254 GIT binary patch literal 4862 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 T4`BR$9(XR;I6$kdkoW!vCi?}6 literal 0 HcmV?d00001 From 72edbf964e9587933e72572e9a5da64118fc290e Mon Sep 17 00:00:00 2001 From: Felipe Manga Date: Wed, 17 Jan 2024 11:29:38 +0000 Subject: [PATCH 18/27] Improve terrain lighting calculation --- apps/synthwave/app.js | 82 ++++++++++++++++++++++++++++--------------- 1 file changed, 54 insertions(+), 28 deletions(-) diff --git a/apps/synthwave/app.js b/apps/synthwave/app.js index ac277b739..8ac8d259b 100644 --- a/apps/synthwave/app.js +++ b/apps/synthwave/app.js @@ -325,6 +325,8 @@ void drawTerrain() { int dist[] = { solid(7), + solid(7), + alternate(5, 7), alternate(5, 7), solid(5), solid(5), @@ -333,39 +335,63 @@ void drawTerrain() { }; int line = solid(5); - int fovz; - int prvz = ((90 << 16) / ((90 << 8) + ((terrainLength) * tileSize - camera.z))); // 16:16 / 16:8 = 16:8 - for (int i = 0; i < terrainLength - 1; ++i, prvz = fovz) { - fovz = ((90 << 16) / ((90 << 8) + ((terrainLength - (i + 1)) * tileSize - camera.z))); // 16:16 / 16:8 = 16:8 - int lum = i < 3 ? i - 3 : 3; + int fovz, fz; + int pz = (terrainLength) * tileSize - camera.z; + int prvz = ((90 << 16) / ((90 << 8) + pz)); // 16:16 / 16:8 = 16:8 + for (int i = 0; i < terrainLength - 1; ++i, prvz = fovz, pz = fz) { + fz = (terrainLength - (i + 1)) * tileSize - camera.z; + fovz = ((90 << 16) / ((90 << 8) + fz)); // 16:16 / 16:8 = 16:8 + int lum = i < 7 ? i : 7; for (int x = 0; x < terrainWidth - 1; ++x) { - int ax = (((((x ) * tileSize - camera.x) >> 8) * prvz) >> 8) + (176/2); // int * 16:8 = 16:8 -> int - int bx = (((((x + 1) * tileSize - camera.x) >> 8) * prvz) >> 8) + (176/2); // int * 16:8 = 16:8 -> int - int cx = (((((x ) * tileSize - camera.x) >> 8) * fovz) >> 8) + (176/2); // int * 16:8 = 16:8 -> int - int dx = (((((x + 1) * tileSize - camera.x) >> 8) * fovz) >> 8) + (176/2); // int * 16:8 = 16:8 -> int + int ax = ((x ) * tileSize - camera.x) >> 8; + int bx = ((x + 1) * tileSize - camera.x) >> 8; + int cx = ((x ) * tileSize - camera.x) >> 8; + int dx = ((x + 1) * tileSize - camera.x) >> 8; - int ay = (176/2) - ((((terrain[i ][x ] << 8) - camera.y) >> 8) * prvz >> 8); - int by = (176/2) - ((((terrain[i ][x + 1] << 8) - camera.y) >> 8) * prvz >> 8); - int cy = (176/2) - ((((terrain[i + 1][x ] << 8) - camera.y) >> 8) * fovz >> 8); - int dy = (176/2) - ((((terrain[i + 1][x + 1] << 8) - camera.y) >> 8) * fovz >> 8); + int ay = ((terrain[i ][x ] << 8) - camera.y) >> 8; + int by = ((terrain[i ][x + 1] << 8) - camera.y) >> 8; + int cy = ((terrain[i + 1][x ] << 8) - camera.y) >> 8; + int dy = ((terrain[i + 1][x + 1] << 8) - camera.y) >> 8; - int na = (ax - bx)*(ay - cy) - (ay - by)*(ax - cx); - if (na > 0) { - int c = (lum + (na >> 8)); - if (c < 0) c = 0; - else if (c > 5) c = 5; - c = dist[c]; - fillTriangle(ax, ay, bx, by, cx, cy, c); + int na = ((ax - bx)*(ay - cy) - (ay - by)*(ax - cx)) >> 8; + int nb = ((bx - dx)*(by - cy) - (by - dy)*(bx - cx)) >> 8; + int ca = lum - na; + int cb = lum - nb; + + ax = 88 + (ax * prvz >> 8); + bx = 88 + (bx * prvz >> 8); + cx = 88 + (cx * fovz >> 8); + dx = 88 + (dx * fovz >> 8); + ay = 88 - (ay * prvz >> 8); + by = 88 - (by * prvz >> 8); + cy = 88 - (cy * fovz >> 8); + dy = 88 - (dy * fovz >> 8); + + int av = (ax - bx)*(ay - cy) - (ay - by)*(ax - cx); + int bv = (bx - dx)*(by - cy) - (by - dy)*(bx - cx); + + if (av > 0) { + if (ca < 0) ca = 0; + else if (ca >= 7) ca = 7; + if (ca >= 6 && x >= terrainWidth/2 && x < terrainWidth/2+2) ca = 6; + ca = dist[ca]; + fillTriangle(ax, ay, bx, by, cx, cy, ca); } - na = (bx - dx)*(by - cy) - (by - dy)*(bx - cx); - if (na > 0) { - int c = (lum + (na >> 8)); - if (c < 0) c = 0; - else if (c > 5) c = 5; - c = dist[c]; - fillTriangle(bx, by, cx, cy, dx, dy, c); - if (!c) { + if (bv > 0) { + int hasLine = false; + if (cb < 0) cb = 0; + else if (cb >= 7) { + cb = 7; + hasLine = true; + } + if (cb >= 6 && x >= terrainWidth/2 && x < terrainWidth/2+2) { + cb = 6; + hasLine = true; + } + cb = dist[cb]; + fillTriangle(bx, by, cx, cy, dx, dy, cb); + if (hasLine) { fillTriangle(ax, ay, bx, by, bx, by - 1, line); fillTriangle(ax, ay, cx, cy, cx, cy - 1, line); } From f450062cfb322f9287c87e7862ec866a9d38ba79 Mon Sep 17 00:00:00 2001 From: Felipe Manga Date: Wed, 17 Jan 2024 11:32:14 +0000 Subject: [PATCH 19/27] Use 93dub font with no separator --- apps/synthwave/app.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/apps/synthwave/app.js b/apps/synthwave/app.js index 8ac8d259b..5365ff52e 100644 --- a/apps/synthwave/app.js +++ b/apps/synthwave/app.js @@ -613,6 +613,9 @@ void tick(int c) { `); +// font from 93dub +var fontNum = atob("AAAAAAAAAAAAAA//8D//g//8P/+I//8//44//w//j4//A/+P4/8A/4/4AAAAD/4AAAAP/wAAAAf/gAAAA//AAAAB/+AAAAD/8AAAAH/4AAAAP/wAAAAf/gAAAA//AAAAB/+AAAAD/8AAAAH/wAAAAH/H/gH/H8f/gf/Hx//h//HH//n//Ef/+H//B//4H//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/wB/4AP/4H/4A//4f/4D//5//4P//h//4//+B//4AAAAAAAAAAAAAAAAAf/+AAAB//4gAAD//jgAAD/+PgABj/4/gAHj/j/gAfgAP/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/AA/AAf8f88AAfx/8wAAfH/8AAAcf/8AAAR//4AAAH//gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAA4AAAAAD4AAYAAP4AD8AA/4AH4AD/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/wAHgAH/H/GH/H8f/gf/Hx//h//HH//n//Ef/+H//B//4H//AAAAAAAAAAAAAAP//AAAAP//AAAAP//AAAAP/8AAAAP/2AAAAP/eAAAAAB+AAAAAD8AAAAAH4AAAAAPwAAAAAfgAAAAA/AAAAAB+AAAAAD8AAAAAH4AAAAAPwAAAAAfgAAAAA/AAAAAB+AAAAAD8AAAB/7x/4AH/7H/4Af/4f/4B//5//4H//h//4f/+B//4AAAAAAAAAAAAAD//wAAAD//wAAAj//gAADj/+AAAPj/5gAA/j/ngAD/gAfgAP/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/AA/AAf8AA8f8fwAAx/8fAAAH/8cAAAf/8QAAA//8AAAA//8AAAAAAAAAAAAAA//8D//g//8P/+I//8//44//0//j4//Y/+P4/94/4/4AH4AD/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/wAPwAH/AAPH/H8AAMf/HwAAB//HAAAH//EAAAH//AAAAH//AAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAGAAAAAAOAAAAAAeAAAAAA+AAAAAB+AAAAAD8AAAAAH4AAAAAPwAAAAAfgAAAAA/AAAAAB+AAAAAD8AAAAAH4AAAAAPwAAAAAfgAAAAA/AAAAAB8AAAAADx/4B/4HH/4H/4Mf/4f/4R//5//4H//h//4f/+B//4AAAAAAAAAAAAAD//wP/+D//w//4j//z//jj//T/+Pj/9j/4/j/3j/j/gAfgAP/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/AA/AAf8f+8f8fx/+x/8fH/+H/8cf/+f/8R//4f/8H//gf/8AAAAAAAAAAAAAA//8AAAA//8AAAI//8AAA4//0AAD4//YAAP4/94AA/4AH4AD/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/wAPwAH/H/vH/H8f/sf/Hx//h//HH//n//Ef/+H//B//4H//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); + const sintable = new Uint8Array(256); let bgColor = 0; const BLACK = g.setColor.bind(g, 0); @@ -723,13 +726,11 @@ function tick(widgets) { } var d = new Date(); - var h = d.getHours(), - m = d.getMinutes(); - var time = (" " + h).substr(-2) + ":" + m.toString().padStart(2, 0); - g.setColor(g.theme.fg) - .setFontAlign(0, 0.5) - .setFont('5x9Numeric7Seg', 5) - .drawString(time, 176 / 2, 50, true); + var h = d.getHours(), m = d.getMinutes(); + g.setColor(widgets ? g.theme.fg : g.toColor(1,0,1)) + .setFontCustom(fontNum, 48, 28, 41) + .setFontAlign(-1, -1) + .drawString(("0" + h).substr(-2) + ("0"+m).substr(-2), 30, 30, true); if (widgets) Bangle.drawWidgets(); From 189e1888bf86656945dc8922a6752ea46d772069 Mon Sep 17 00:00:00 2001 From: Felipe Manga Date: Wed, 17 Jan 2024 11:41:16 +0000 Subject: [PATCH 20/27] Update Synthwave screenshots --- apps/synthwave/screenshot.png | Bin 3006 -> 3921 bytes apps/synthwave/theme.png | Bin 3082 -> 3304 bytes apps/synthwave/widgets.png | Bin 3171 -> 4170 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git a/apps/synthwave/screenshot.png b/apps/synthwave/screenshot.png index 60e3c18aade61b86ffd3d28cd569c20da0f301dd..e8697bd0a1710afaf25e9a582f75550f5b308682 100644 GIT binary patch literal 3921 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 VIrAUU`}-;b40KHHR%kgq{SQCYh>idN literal 3006 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 IiTjiP0Lb4~`v3p{ literal 3082 zcmb_eXEYmZ8>UvPuU(27MXjo+Qd?DPCPu3ysS&Fsir7I_RLzi5O0>4By+=f3>! zbCLesmeqWRQBhqU(AUy5^-JAMGeqzyvxjBV%3w(M?U^6HHqIh7=Tq@8WgRvgT&hoR z(7k;tQy#*d(R`oZZtgPojG2tQX_dqoZO3(he}O(X_;s+;bsHPr|9|K&r`keBjJ-niDIkh=m);of9`9lJC$Ycp{TI)_!w}yE`jsy2 zfZx>@{t5ea_^_3l_1{UliB06*(OHEbt>^tF{?G-r8G+TG?wQ78V-vqlyk33{qGn6e zysPDXZ${vK`{^0!h{t!2!n77lBlr8zt+Byonm<1;oz1%$$0D+pf&_9b-DBzaad|-ud6SPAN1v$$EGSdU1tyWhC-tgLZ5ir zB8@rv6?drt|N*%hM1rJ31-uDFy5ucQa zs79pVo$r4VA5bR+eNK^4w9`#O09yqcPoIrwhrA02U0)njIv<`j<&R^v>kNS<9D8{Q1hr$9%^e0xQ4vZ7TEr}D-&tLu(4KGUMuo5{V zi~Xt0lbc90kF}npl$E_^)!1j><|3GLc3Ig1PY$Yx);DC1O(#X#)+3kL$bw2MU#sAk zWOSa0bdJYKE&TLVv_?dRu5mjTfgi^rt(Yg;)Z)WHm+mZ)8jhUy7*nO1<5}%i;hFfr zX_|udjkTbDOuW&&All-K{eC)tUIJeugD_fnumyGA)7C z2u*(_#QNYiXRpLccaEyC)=eDI8vD@$Aw9Aldds00#(fmNPn01Z?8`FC@1Yfg`>7`3_6EA#;@!P zHJUy09ZR$%Mq$$1k$PrI*qE+}I$l8PM!&@Ofoedh=SnH6DH@Q+_a&bbs0?v(f-*h8 zc53kLc9X+y2#iB6`X7^6*&!~gCxWvYkesXQ)zdE>qup~i0t_4yuNiZ?_iYw3BtiNL zYj_@DyYmh-L2Jy)Q*ZNvECTzQ)3CDLlUW2l@U+sa({KsF2N=d}omf*ox7c-s!(Uc$ zAScr={FI~P#Kf|2+v`p@THeffcDpZ%USM9CQRY1H4(wsyo}XXZdY99|Zn&7tek2!{ zQ|{!Qc6twcr4r=)k!CC^Tu!~)!zRPj#U&0(6cvk-_wrLJCJPdlHE-^l-)#>*J;4Ku zrGcCBMV4BNqtQjtxpUzh zMQ+(rdbl*7ov&*-I!(3`Uw9rGpx@UCK$9mWHv~MdIuxIcwyj?r0*lyuI9FI6itBpdJGsZ!iLd!>ZJqfiW-lU~-M=EqbZXHP?}hKJ(Wa)P zEvi&*HxXaiUF{s@w#wsK=&{1A(HgXReN27##KOZ<+_2h2m6=4hqhjK3>gsam8sY@& z{4F^S4MV-6sHriJ-s!vps?Vv-_qGv?Dff&~8$#pMT3xm4`Ak)BKemWyDk%usqh!t8xB|bMcS(%=o&gN=8Ydt-#<1>53F|&E;dTFO~uzJGaMB7 zq@OoGt8X@8`Ru)vN0+LFQKq$~1n>uC-ZmM`Byi`nb2W1$_PU1A`0|I0_=f}DP zzjh690E@ZYoX;HS4EAaw)9xGR)m^Um8VVX-&dugK@~#bF(@{E^_263Nz@hzcegU_o zY{ZRL42;g9$Zd^JRz?{;LuuMO`xie+m&rS^x*79yUs75sB+(?zp#aEqS`^`(7kOMl zt2d0SVzZ^4safA(sn7BSBP?pEYh*b;YtBb7+q2~@aL>9TFicv3tU;s~Hl^DWL0VFx z^T!z?#}>tBvTBWBjBT=XC<`3dp4j% z?9-70U{E&+A1k&4OjwH2Ln4b9Vy0>%ss(mNm7FACTSP#w^jiRi-L2Rdhlad~{zx!0 zn&zFt=XSL9)h(CK*=@LEZ>_|~FYH5lf5X{i;-=g7>~8w!MvC%Pidks{Zo%oq)!Uw z_L=-(RSg<8NTba*ETCWYywRAG?;xumGukFs@_P@3Uz&eZ<;QPwDrv?gSt;kC4k?wf zbmi}sW)asJ11t7m2$!$q#an2rRoUl&u^taDcfA>51?tUGL!E`>P1Pc*T8V*56Dz zZ}`sA#yf%t^cws72LaG;IWN`A71(IV7-rQJu4-r29f`w0LzlL&I=d83S(saTyzJJG zN^`&>rq;K?q`DbhGsn$aF`6cn~uoxfXD zqfk_hKa7gluw}j_WQREyyw3dA{)T~9 z>T}S;`}Hbwdo7)h+$IkS@79T32!wAY75t$|(!%s>iW5;5rRcmO}OR9VLTQybI&g`QVe6pPZ;Avgm1V`c1^0>L<$Ry}#EkwF(Cg7_pvgc z%d)x!7XaWQH`q|m_DLqy*&=94jpw>h73&fGXW=;xBzv}d03{(y?D+mNj(U+W-7n zZ)sF6-SK~~m)n^tevGu!o)`LXz|e^RPs6JiA9k3Ae|V-5mKg>+5!-IYn`wpjhPkk1 z$tZOs)$}AYoJnsAU@Zn%^7|aFmANaWI9#9dS`pvS*_LBnc%+5qeZrSA`9j}tt}vLs zj4`gw$IK;4bgCC9b^!O8{*k2u^-R5zsFa`RsnW%gPa=H&(36Q4K8XnupF`jAR0jFX z%~iFst(UVNpzkp*GGv1?DW5>i?wa%93sLbL65z93JWZo5k7 zqo9-M4-&@m&9ZMe2wJ*z3XPIhqwGc(reP5~^w%vP3visr*(1!yU{yI z!gV>lue>`t{l;2A5r;802_x#1nczr*w z!?$d2alBef=wDMNms$0ik@Q~)0lD78cO=nt0eFY?K%C_q1Md}zG^R#m2RAnK+=xf? zEd@|(I=P;(x@hxD4OlguC2Wpfc|b6y-`HXggo>6 zcprl;6@l8NE=;1daA2RP#IYaaUV$L;wSQJQ_i-8#w|z^Jo}!h912^b7A8FeHMLfTM4YQS;x)&$jf>5~ki&_5vY2<5Rh`h+3DlPe6PB*mo2o!+fp zTS1>FsZ{qziD;4-o{ous_n)ebet?sQhu$Y(OXCR@T&SRsICQksE`8w}ZDWG8G2jrh z1C&kSE)blMdW0(E<)X08V6mFdi&*L+r_YWK9hRE6wiicl1fQ-ts&I+gfN_VXqg(9S zw#E-V*E6h9B?+50T@tCJ{JoH(*uA=ggY&?*cKI)=ZG}iV`(>+p$=$VBPcvdjONjed zu0Yv$A%nP227P}5b`VZT%cjCmvy(GY{QY?cd=~klqn|)FxMY*D&LzbeW?2NRn4>?T z$}v7{xFBVexGoVWZhEy}c6fljES zrlM6-!HLzAK-{CWq5K!y;*_#JcySl?)Umb)&x%0X0X*`Er!LFtURx%y%@RXY#T0?JHg_yUsq6uR!*(^Lg#Q? zMq`CX+EKQj0nGB#5t8aBA$3a_6giwkPZ7 z17|aactKCJRFw5I&nxQMxc1e%(KF$ln4TEIBAVPWW0{)d@Ug8)_q{OLcUz5XLLb{< z>x8Q5&Pn3o+wgOyUm;$9Nt$=;8yBRt&m6YHvK@qyh3a5J&8M`TAMhr4eb`|u;wkan z&z`Kd_J>jk>SCgXqK~Q!!hC`IEI6(aaSB20?4Rk}f51gc_@L(@>`i%LVdST15^LaR z3Y(B6677S?wm&_dIdEhWy{AWj(f!ISufumw3z?xU{!{A9lNczg+y<{A9Q}xxd8C={ zNh2JAf>`=O9xtDjvaw-8teN9i+2cQhD$@K?9uUm-J=!fE|Fdbkwlx`7t?O01FH7sK zc{vXM;BxJRby~ARWK8e6pi zX?Oo@a$+gHW_JL~^JXT`D+6bhqYBQRUB?tOyQB_&7+-LFS*-kp{wl{NnlXbk){;Ic zkrAigHWTwbEGfv)&6StDVz`<=xBNqB)KFidmid=ivY-o9mqS0W2p%O}NrCH?-kETqYprZ8}SII%=;9dp|WMYk(A z@sR@f+KdB%@|iJ^xFGj!1`&~|atY}X%b@E_Us>^xd{$vGDMU|Hs{WHFqLz_+znA(i zV^2qIvou8hTs0MXruKl3wy0o{Dx*l*3$;L1-b$_ge77^GZqC!3$Q zmmB^%`_@vecj&m_)JwfZT*B{!YQs4mqM28l7d}ZX9dgvl^OSDf7{fTJm;C#(JHgQ&`=`qL?ag?FQ)24v_%q3M@{413XWT!k?8E*{AJB9{kXj>9Bd}V_BV!G6Vfm{vH*yAR(`MLN64&+p`P&X= zu<2SO)U*8G34tIth`=RGo@5|MS+zi?%Dex7={x4nKY`!i#VJvO-g&PwC9JuaMtVyK z_PfXlZw;V1dOjA}t;Sy5hEfh)D(D0Ztb<5fQx0>|icFmJX8fzB$hECBtvcOKLDB+& zzaHW$eDf4(gLhPXUoq`4AH8()fLe2LERqQ19}t^9FsU4Q;%hQ2BJnM})Y!j5bfO|j z(yL@AJlAXMLRylwOujjw+ zZSm4+GS=)JQrrQO8D^9B-JW94l)sN3&rZ$D3$&UlV8m)ZBG?wy+4eg+$B$P!QW4qX zE86@jI_2zWgimoAk|AA!g?3=db5N-&G5nXOtakK0l$Z8FBx7I0s9E6I{%FVC$4pz1 zHRHhb{?VSH||}wxdmcBp$naO?$S40-o+M>d<4taT4he{UC{rL_q^0z5kek;?j?JZJEx` zwK-hVyt!Y48c=7x#gpORZdt*4bARhM;Z|y_VjH%lMREjaS7bFBX4o!J!!U|Hlczp} zH$i&eY`U5vwo^V0G)%J`H-oz5eBZZ81oc9n#&uSinxBr`BuHNi``u)n%LGo7?cPgAGO9pcaV`|A zR$Lq5bMezibNoYXQ&>0?>jDA0ynk0tz{(Xxe;{3v@8~D*6&G;xF2HI z1fkMz_s`juYjHtx$X#g1tCrOGy}?!B)U(qph4n}E(Xp|r+uPeqJ6lPyR28-Nn=`|o zrnAQuwcH~#erkgSR}}Y3%wQqvsT=-aH1Sl1X-UQ#oS?qg<~x1xvC01OOMFbDD9H$; zF2|2N0i}p^k2|45PVrDT>~q2;wJDd}7iBJ|z-MQ_;ee=XPd^oz@r8|XlsaWcYrNl# zGdx^egb$j&-urX1$3-i+3y|o30W=XAb9Xn_p9=`dK^O$f+?%%;d z&BS%Vab4LPb(4Z0!9z9qCsXXOFWYc0(-U{CPGyk300BbsX6mcai*5u&Ku6L<@u4r+ zJy3j4%BDagV|Y&k%VrvL7(HDD>G#Fmhe-LE4Ts_;gz`ZqDKU& z-h;O=G^fT2DXNrrt>f>S+?mf8VgcHZhU++U?O_TcA3)|Td+mqEwW@FEfDocXDbBf_ zQarJ*48O5G2REJ`d9NfmA7J94P_xZ zPfvS`_|Yhr#tc@s%>hfWmil2cLLs9bJ>M2@bGFsm`fg+HUtfjs>fO?$(V(gfmiEQN zZZ*UuQkuN9^Q&RzbR-DcQZmDkzLpR6;B2$K5mAhj2UpBLIMnEDcNb-Jbss DcU>iA literal 3171 zcmZ8kc{CJ?_s3*s7?oXWgdW~R_O&oFH7Fw6(3nD!!HhjIld+7oXBqM)qK)i@89QSn zW0x&WVHC+S4aqXexA*(wcg}l%+sMBv- z`;Q0-{MI#;Pu@H{Vj35qmd;V+wTG87-&3Vt_h(RqF!(N00-jtttgSPWV5!Ze3<zs;uBj?=pyqa)KU5D(X{${l~P?^WNym@ zKhLA&gGva`m${$pK`aIS(|u|%JpYuxM9AEqkL=2Hcl+@0stBkipVDf?X+DeM9EaAu z!i?6#2xIM~b2Df-E>37$pvGQLz@fn{#Z^a4{a9hJ$Bm5?eAOIXxEhD6x-4>7M2=X+ z$Lk4JWhopzJqrJXQG1aU?7DqBxNy256(JF6e|qYcqZoONL`Yi42)@AsKUmR3%)?#WJr%Vl>2@VF{a#56ieGU3!=EAsFXTo(V$Qn>s!z+cjuw_pIUvn9Ag1`oXzc#@}{h(H_`g%~dEW+Wpzn(OB0(V;BQ( zlmJk9_(vGh5Y*~#MLpQh1|*jouX{0Ot0|T855RK>U(->gA|Q+J6XHg-Wc%Iyuy>P* zfZ&4|&Bh3>J#nWypFOd(&bSk6gudANOJ4zkjMTnX_H+#;^DS#>>66aIOz&IgjOTZH zBds(YRX#eKhY*95xdTd?syB#ny*E))n49VlHHNH@x`Rl3hmR08*2r6U%*$>2-TRJ@ zV`xfXgcqco&9&Mz4OPW#6=>u~a29F9K75O*hCee!RgH8)0^iIt7H8Y96VR%1XL2*w zl-~wB&(9EFo+s1=1y;WmbMh6Fld>YPK1)hNe#TD|S>R~PThIqm3cck`C8_Tlr!1|z z-SDCkC?DGVa>#@r2NXG1&`AZS3hS-XVWx+sgozu#)nDFwanxe(TeLhypSGcpg~Hb? zF)ipgzB`9hMsSudPjZr@f^UigP&Yxrt1%g23%UKz7%m!F1pqkFFwCcXx-!ht2vr%5 zfc%+Z$%wv-kt>zB*8X<+duH3|01kUQYjlJ7vrJdbyR_Y5GUdGt#px-*IMvYy;23xO zk8p*2sX$2H1CzLXz!G6brPI+IwI1ZCVWHF|w6rzvm*%H(4Dg(;`A2KsZr`X?fX|c7 zJv!psaC}@wQlu`!8rr7?&B6JnL^?}Q{W=PvTJEH=@YMaau$cVTw(ZV`#3lL5um&8K zmFMT{Jyv2#&-R@Y$<)0+{_alvC!@czD<@C-hCCWSv>iEp1I^QOj^H;MF076kF_S zvJ?~x{rP)ZHE7bM&7QG=u>(Pp@n&Z!J)Mf2OrVlp*cZjebbSwM+e1h`|6IysPd|B~ zf!cJ*Kw$9*(vN9+J+>%8I+<)4B&PVfqU87$9c)?@@eB|l)qh(j0OJFPJybRnurO;I z@FXV2>U8}`$xrzLi8!7a<@)GEm7h3~90XtO&q%`-W2vGxT}Rm@av*$(r4sGkpc9fY zN8CBPC*EYYI4^R=TG8Z%m?vhNlzuLA|JPqi=cx|@AD@x2aCtSVmok-?0Q+c6If(>E zgRmd!Uq|n+edq`*lYcY9XA>&blOgksBHx*->;V&ypsamJ?l1Z_{xS=E#Y5J{24nfgn0~!Y1)m%%W}*8D7er<>WIPuYMDhiaj~3-f;Q=JES7{ zMBe3$Xo>!M2B;$4dACZB;XyOIy@J&ZA(g^hJ6gM=l5cV zi{{PL6w7N)bdX}dr?;Hjd^K91R#2wpK-}nsWS_l1(!!Q@ReG4UnBF)j;?sOAL`h+J zj8;})o_FwlITRUxpop(Law+{?FFVevWzjMVVKYghESA=rgg8`^Wz2_gy1hPXs2bT3 z4OD4yk4KJX#0qmKZa64+g<8J-DWGEe==$ls^_j=C$lWAg69KmW51IHo#I5w}iCjgm ztnxFJ)J9g#yD@p0mJPyZ7QG&d0U{1B?5!{lDTy1;y7pdV&fOXQ>vRLYC@-7n|9f`OiyV z9;HijObiqhFxYbRJGS!ax}v9>WXE{!{$mFA!PicUiUm}1v2p~ZZ~qqvt@!QNpl0~c zirYtqZT+hJ_SV4~!i|xk-+VP_KcLTuPVxn=%7SF3=xy@{-9sYG9sa8I7DOj>!6Y({ zW**5L^M_JYKh=J$Pt7me4BE;jfYDX;OhrP$qwCfb=(25YvwGgFNMR5B2_1hohG}%f zFZK5wbv1o8O#0yVUOSXHk3HcL5Q;JkTnURYv`4BMHi8c&C)Re^J_MNFdKlFa-^%>q z-@HI#MulH|#oxfE)+`&?}5DIMpYPDo}Lc*|x0RBcc~ zc3?jU_rt$G3w;kxr>g@iRxtCnu}z}qL_hnN}z z1uB$P?4cQ&41{$41IkYP1QooHa)LRfA^4F2Bj;g~>_U;tK)2iR4Xm}JtuDPE8dh zGIyhvMXT&HYY%DgVir0p(LIwJr)21FM4~>oPHqQ#d&%3aZ~{97Ij={xSd}RgedNa?8P#v zTBv3+toPCAUE&d8x6<&(Zw;g~+=cb|)V>COmRwM*C%Pr#Jgt%^Iy!kw+#qvDO8{T2 zb3{+eXoIW-({6$|PQ2TGO2XU6hVZr^Q6+(blY9tb|Sn2{VY T4`BR$9(XR;I6$kdkoW!vCi?}6 From ea112f26a8c204ff4edb2da0df8240d232070917 Mon Sep 17 00:00:00 2001 From: Felipe Manga Date: Wed, 17 Jan 2024 14:24:19 +0000 Subject: [PATCH 21/27] Simplify terrain generation --- apps/synthwave/app.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/apps/synthwave/app.js b/apps/synthwave/app.js index 5365ff52e..9edd2abff 100644 --- a/apps/synthwave/app.js +++ b/apps/synthwave/app.js @@ -48,10 +48,12 @@ void shiftTerrain() { for (int x = 0; x < terrainWidth; ++x) terrain[0][x] = (rng() & 0x3F) + 0xF; - for (int x = 0; x < (terrainWidth >> 3); ++x) - terrain[0][((terrainWidth>>1)-(terrainWidth>>4)) + x] >>= 1; - for (int x = 0; x < (terrainWidth >> 2); ++x) - terrain[0][((terrainWidth>>1)-(terrainWidth>>5)) + x] = 0; + int mid = terrainWidth >> 1; + terrain[0][mid-1] >>= 1; + terrain[0][mid ] = 0; + terrain[0][mid+1] = 0; + terrain[0][mid+2] = 0; + terrain[0][mid+3] >>= 1; } void init(unsigned char* _fb, int _stride, unsigned char* _sint) { From 16c08d0cf38bc12658dfe87b5c53abf9e2b68472 Mon Sep 17 00:00:00 2001 From: Felipe Manga Date: Wed, 17 Jan 2024 14:30:43 +0000 Subject: [PATCH 22/27] Generate larger/smoother hills --- apps/synthwave/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/synthwave/app.js b/apps/synthwave/app.js index 9edd2abff..a04300f44 100644 --- a/apps/synthwave/app.js +++ b/apps/synthwave/app.js @@ -47,7 +47,7 @@ void shiftTerrain() { } for (int x = 0; x < terrainWidth; ++x) - terrain[0][x] = (rng() & 0x3F) + 0xF; + terrain[0][x] = (int(terrain[0][x]) + ((rng() & 0x7F) + 0x7)) >> 1; int mid = terrainWidth >> 1; terrain[0][mid-1] >>= 1; terrain[0][mid ] = 0; From 2304572e7f1f8fe725f5f2b37b28e403839b6786 Mon Sep 17 00:00:00 2001 From: Felipe Manga Date: Wed, 17 Jan 2024 20:31:53 +0000 Subject: [PATCH 23/27] Add support for FW2v21 lcdBufferPtr --- apps/synthwave/app.js | 7 +++---- apps/warpdrive/app.js | 4 +++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/apps/synthwave/app.js b/apps/synthwave/app.js index a04300f44..891008df0 100644 --- a/apps/synthwave/app.js +++ b/apps/synthwave/app.js @@ -29,8 +29,6 @@ const unsigned char ship[] = { const unsigned int terrainLength = 12; const unsigned int terrainWidth = 12; unsigned char terrain[terrainLength][terrainWidth]; -unsigned int travel = 0; - unsigned int _rngState; unsigned int rng() { _rngState ^= _rngState << 17; @@ -40,7 +38,6 @@ unsigned int rng() { } void shiftTerrain() { - travel++; for (int i = terrainLength - 1; i > 0; --i) { for (int x = 0; x < terrainWidth; ++x) terrain[i][x] = terrain[i-1][x]; @@ -651,7 +648,9 @@ function test(addr, y) { function probe() { if (!start) { start = 0x20000000; - if (test(0x2002d3fe, 0)) // try to skip loading if possible + if (test(Bangle.getOptions().lcdBufferPtr, 0)) + start = Bangle.getOptions().lcdBufferPtr; // FW=2v21 + else if (test(0x2002d3fe, 0)) // try to skip loading if possible start = 0x2002d3fe; // FW=2v20 } const end = Math.min(start + 0x800, 0x20038000); diff --git a/apps/warpdrive/app.js b/apps/warpdrive/app.js index cf7d40b6e..46b998379 100644 --- a/apps/warpdrive/app.js +++ b/apps/warpdrive/app.js @@ -519,7 +519,9 @@ function test(addr, y) { function probe() { if (!start) { start = 0x20000000; - if (test(0x2002d3fe, 0)) // try to skip loading if possible + if (test(Bangle.getOptions().lcdBufferPtr, 0)) + start = Bangle.getOptions().lcdBufferPtr; // FW=2v21 + else if (test(0x2002d3fe, 0)) // try to skip loading if possible start = 0x2002d3fe; // FW=2v20 } const end = Math.min(start + 0x800, 0x20038000); From 3bfe4448fb16a59d4fe617eb6c7cfd38527b735f Mon Sep 17 00:00:00 2001 From: Felipe Manga Date: Thu, 18 Jan 2024 15:23:57 +0000 Subject: [PATCH 24/27] Ensure text uses theme background color --- apps/synthwave/app.js | 7 ++++--- apps/warpdrive/app.js | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/synthwave/app.js b/apps/synthwave/app.js index 891008df0..6e99865a7 100644 --- a/apps/synthwave/app.js +++ b/apps/synthwave/app.js @@ -729,9 +729,10 @@ function tick(widgets) { var d = new Date(); var h = d.getHours(), m = d.getMinutes(); g.setColor(widgets ? g.theme.fg : g.toColor(1,0,1)) - .setFontCustom(fontNum, 48, 28, 41) - .setFontAlign(-1, -1) - .drawString(("0" + h).substr(-2) + ("0"+m).substr(-2), 30, 30, true); + .setBgColor(g.theme.bg) + .setFontCustom(fontNum, 48, 28, 41) + .setFontAlign(-1, -1) + .drawString(("0" + h).substr(-2) + ("0"+m).substr(-2), 30, 30, true); if (widgets) Bangle.drawWidgets(); diff --git a/apps/warpdrive/app.js b/apps/warpdrive/app.js index 46b998379..4f55db43d 100644 --- a/apps/warpdrive/app.js +++ b/apps/warpdrive/app.js @@ -671,6 +671,7 @@ function tick(widgets) { m = d.getMinutes(); var time = (" " + h).substr(-2) + ":" + m.toString().padStart(2, 0); g.setColor(g.theme.fg) + .setBgColor(g.theme.bg) .setFontAlign(0, 0.5) .setFont('6x8', 2) .drawString(time, 176 / 2, 176 - 16, true); From 417b434cf182e65d7a6aa2b0ffd05f9eb381c09c Mon Sep 17 00:00:00 2001 From: Felipe Manga Date: Thu, 18 Jan 2024 19:09:53 +0000 Subject: [PATCH 25/27] Ensure graphics is in a known state when drawing --- apps/synthwave/app.js | 2 ++ apps/warpdrive/app.js | 2 ++ 2 files changed, 4 insertions(+) diff --git a/apps/synthwave/app.js b/apps/synthwave/app.js index 6e99865a7..cbc6b8794 100644 --- a/apps/synthwave/app.js +++ b/apps/synthwave/app.js @@ -721,6 +721,8 @@ function init() { } function tick(widgets) { + g.reset(); + if (lcdBuffer && !widgets) { BLACK().drawRect(-1, -1, 0, 177); // dirty all the rows gfx.tick(bgColor); diff --git a/apps/warpdrive/app.js b/apps/warpdrive/app.js index 4f55db43d..ad9b88223 100644 --- a/apps/warpdrive/app.js +++ b/apps/warpdrive/app.js @@ -656,6 +656,8 @@ function drawNode(index) { } function tick(widgets) { + g.reset(); + if (lcdBuffer && !widgets) { BLACK().drawRect(-1, -1, 0, 177); // dirty all the rows gfx.clear(bgColor); From 58e8c032c7260b59f66e46452e775518ca0749a9 Mon Sep 17 00:00:00 2001 From: Felipe Manga Date: Fri, 19 Jan 2024 11:48:21 +0000 Subject: [PATCH 26/27] Call widget_utils' show/hide instead of Bangle.drawWidgets() --- apps/synthwave/app.js | 33 +++++++++++++++++++-------------- apps/warpdrive/app.js | 33 +++++++++++++++++++-------------- 2 files changed, 38 insertions(+), 28 deletions(-) diff --git a/apps/synthwave/app.js b/apps/synthwave/app.js index cbc6b8794..85fbfe7ad 100644 --- a/apps/synthwave/app.js +++ b/apps/synthwave/app.js @@ -623,16 +623,23 @@ let lcdBuffer = 0, start = 0; let locked = false, - charging = false; -var interval = 30; -var timeout; +let charging = false; +let stopped = true; +let interval = 30; +let timeout; function setupInterval(force) { if (timeout) clearTimeout(timeout); - let stopped = locked && !charging; - timeout = setTimeout(setupInterval, stopped ? 60000 : 60); - tick(stopped && !force); + let stop = locked && !charging; + timeout = setTimeout(setupInterval, stop ? 60000 : 60); + tick(stop && !force); + if (stop != stopped) { + stopped = stop; + let widget_utils = require("widget_utils"); + if (stop) widget_utils.show(); + else if (widget_utils.hide) widget_utils.hide(); + } } function test(addr, y) { @@ -720,30 +727,26 @@ function init() { setTimeout(probe, 1); } -function tick(widgets) { +function tick(locked) { g.reset(); - if (lcdBuffer && !widgets) { + if (lcdBuffer && !locked) { BLACK().drawRect(-1, -1, 0, 177); // dirty all the rows gfx.tick(bgColor); } var d = new Date(); var h = d.getHours(), m = d.getMinutes(); - g.setColor(widgets ? g.theme.fg : g.toColor(1,0,1)) + g.setColor(locked ? g.theme.fg : g.toColor(1,0,1)) .setBgColor(g.theme.bg) .setFontCustom(fontNum, 48, 28, 41) .setFontAlign(-1, -1) .drawString(("0" + h).substr(-2) + ("0"+m).substr(-2), 30, 30, true); - - if (widgets) - Bangle.drawWidgets(); } -init(); - Bangle.setUI("clock"); Bangle.loadWidgets(); + Bangle.on("lock", l => { locked = l; setupInterval(); @@ -753,3 +756,5 @@ Bangle.on('charging', c => { charging = c; setupInterval(); }); + +init(); diff --git a/apps/warpdrive/app.js b/apps/warpdrive/app.js index ad9b88223..855b86c78 100644 --- a/apps/warpdrive/app.js +++ b/apps/warpdrive/app.js @@ -493,17 +493,24 @@ const WHITE = g.setColor.bind(g, 0xFFFF); let lcdBuffer = 0, start = 0; -let locked = false, - charging = false; -var interval = 30, - timeout; +let locked = false; +let charging = false; +let stopped = true; +let interval = 30; +let timeout; function setupInterval(force) { if (timeout) clearTimeout(timeout); - let stopped = locked && !charging; - timeout = setTimeout(setupInterval, stopped ? 60000 : 30); - tick(stopped && !force); + let stop = locked && !charging; + timeout = setTimeout(setupInterval, stop ? 60000 : 60); + tick(stop && !force); + if (stop != stopped) { + stopped = stop; + let widget_utils = require("widget_utils"); + if (stop) widget_utils.show(); + else if (widget_utils.hide) widget_utils.hide(); + } } function test(addr, y) { @@ -655,10 +662,10 @@ function drawNode(index) { gfx.render(E.getAddressOf(translation, true)); } -function tick(widgets) { +function tick(locked) { g.reset(); - if (lcdBuffer && !widgets) { + if (lcdBuffer && !locked) { BLACK().drawRect(-1, -1, 0, 177); // dirty all the rows gfx.clear(bgColor); gfx.stars(); @@ -677,15 +684,11 @@ function tick(widgets) { .setFontAlign(0, 0.5) .setFont('6x8', 2) .drawString(time, 176 / 2, 176 - 16, true); - - if (widgets) - Bangle.drawWidgets(); } -init(); - Bangle.setUI("clock"); Bangle.loadWidgets(); + Bangle.on("lock", l => { locked = l; setupInterval(); @@ -695,3 +698,5 @@ Bangle.on('charging', c => { charging = c; setupInterval(); }); + +init(); From c4aebc6a8c8a017cb868ba6023027ac36f9723b7 Mon Sep 17 00:00:00 2001 From: Felipe Manga Date: Fri, 19 Jan 2024 11:54:13 +0000 Subject: [PATCH 27/27] Fix typo --- apps/synthwave/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/synthwave/app.js b/apps/synthwave/app.js index 85fbfe7ad..61a0a1e79 100644 --- a/apps/synthwave/app.js +++ b/apps/synthwave/app.js @@ -622,7 +622,7 @@ const WHITE = g.setColor.bind(g, 0xFFFF); let lcdBuffer = 0, start = 0; -let locked = false, +let locked = false; let charging = false; let stopped = true; let interval = 30;