// ===================================================================== // BLENDER SMOOTHIE for R1BBIT - Waveshare ESP32-P4 Smart 86 Box 720x720 // // A glass jug blender on a kitchen counter, full of forest berries // (blueberry, raspberry, blackberry, wild strawberry, red currant). // Slide the speed slider: the blades spin, a vortex forms, the berries // are pulled down and shredded, and the juice turns into a smooth, // frothy purple smoothie. Tap the jug to refill it with fresh berries. // ===================================================================== CONFIG // -------------------------------------------------- // Display: ST7703 720x720, MIPI-DSI // -------------------------------------------------- DISPLAY_DRIVER ST7703 DISPLAY_BUS DSI DISPLAY_W 720 DISPLAY_H 720 DISPLAY_RST 27 // display hardware reset pin DISPLAY_BL 26 // backlight control pin // -------------------------------------------------- // I2C: shared by touch controller and audio codec // -------------------------------------------------- I2C0_SDA 7 I2C0_SCL 8 I2C0_FREQ 400000 // -------------------------------------------------- // Touch: GT911 // -------------------------------------------------- TOUCH_DRIVER GT911 TOUCH_I2C 0 TOUCH_RST 46 // touch controller reset pin TOUCH_ADDR 0x5D // I2C device address // -------------------------------------------------- // Audio codec: ES8311 // -------------------------------------------------- CODEC ES8311 CODEC_ADDR 0x18 CODEC_I2C 0 CODEC_I2S 0 CODEC_PA_PIN 53 // speaker amplifier CODEC_PA_ACTIVE_LOW 0 // -------------------------------------------------- // I2S between ESP32 and ES8311 // -------------------------------------------------- I2S_MCLK 13 I2S_BCLK 12 I2S_WS 10 I2S_DOUT 9 I2S_DIN 11 AUDIO_OUT CODEC AUDIO_IN CODEC // -------------------------------------------------- // MicroSD, SDMMC 4-bit // -------------------------------------------------- SDMMC_WIDTH 4 SDMMC_CLK 43 SDMMC_CMD 44 SDMMC_D0 39 SDMMC_D1 40 SDMMC_D2 41 SDMMC_D3 42 END_CONFIG DISPLAY_INIT // Canvas over the whole panel; frames are pushed with DISPLAY_UPDATE. DISPLAY_INIT_CANVAS(0, 0, 720, 720) TIMER_RESET DISPLAY_INIT // Canvas over the whole panel; frames are pushed with DISPLAY_UPDATE. DISPLAY_INIT_CANVAS(0, 0, 720, 720) TIMER_RESET // ----------------------------- settings ------------------------------ CONST PI = 3.14159265 VAR CX = 360 // jug axis VAR YT = 112 // jug rim VAR YB = 495 // jug bottom VAR HWT = 132 // half width at the rim VAR HWB = 84 // half width at the bottom VAR WALL = 9 // glass thickness VAR FRAMEMS = 33 // ~30 fps VAR SLX0 = 130 // speed slider on the counter VAR SLX1 = 590 VAR SLY = 688 VAR SLTOP = 656 CONST NB = 18 // whole berries CONST NF = 40 // shredded pieces CONST NBU = 16 // foam bubbles // ------------------------------- state ------------------------------- VAR RPM, TGT, SDRAWN, P, HF, T, T0, FT, TCNT, TX, TY, TAP, ANG VAR I, J, Q, K, X, Y, U, DX, XC, YA, YB2, SZ VAR LH, SYW, DP, SURF, SLOPE, YL, SHD VAR PR, PG, PB, PC, MR, MG, MB, MC VAR BX, BY, BSZ, BTY, BSH, DA, AN VAR LR, LG, LB, CC, SK, WW, CV, FEED, NXT, G VAR BTA[18], BAN[18], BRD[18], BSS[18], BHP[18], BPH[18], ST[18], AGE[18], BYY[18], BV[18] VAR FAN[40], FRD[40], FYY[40], FSZ[40], FCL[40] VAR UX[16], UR[16], UPH[16] SLOPE = (HWT - HWB) / (YB - YT) RPM = 0 LH = 0 SYW = YB DP = 0 TGT = 0 SDRAWN = -1 TCNT = 0 T = 0 ANG = 0 GOSUB Refill // ---------------------------- static scene --------------------------- GOSUB DrawKitchen GOSUB DrawBase GOSUB DrawHandle GOSUB DrawSlider // ------------------------------ main loop ---------------------------- WHILE 1 T0 = TIMER_GET T = T + 1 TAP = 0 IF TOUCH_PRESSED = 1 THEN TCNT = TCNT + 1 IF TCNT >= 2 THEN TX = TOUCH_X TY = TOUCH_Y IF TY >= SLTOP THEN TGT = (TX - SLX0) / (SLX1 - SLX0) IF TGT < 0 THEN TGT = 0 IF TGT > 1 THEN TGT = 1 IF TGT < 0.04 THEN TGT = 0 ELSEIF TCNT = 2 AND TY > YT - 30 AND TY < YB AND TX > 200 AND TX < 520 THEN TAP = 1 ENDIF ENDIF ELSE TCNT = 0 ENDIF IF TAP = 1 THEN GOSUB Refill IF ABS(TGT - SDRAWN) > 0.004 THEN GOSUB DrawSlider // motor: speed eases towards the slider RPM = RPM + (TGT - RPM) * 0.06 IF TGT = 0 AND RPM < 0.02 THEN RPM = 0 ANG = ANG + RPM * 0.35 GOSUB Physics GOSUB DrawJug DISPLAY_UPDATE FT = TIMER_GET - T0 IF FT < FRAMEMS THEN PAUSE FRAMEMS - FT ELSE PAUSE 1 ENDIF WEND END // new load: a few berries already in the jug, the rest fall in later Refill: P = 0 G = 0 FEED = 0 NXT = 5 FOR I = 0 TO NB - 1 BTA[I] = I - FLOOR(I / 5) * 5 BAN[I] = RND(6.28) BRD[I] = 6 + RND(36) BSS[I] = 20 + RND(8) BHP[I] = RND(1) BPH[I] = RND(6.28) AGE[I] = 0 BV[I] = 0 BYY[I] = 0 IF I < 5 THEN ST[I] = 2 ELSE ST[I] = 0 ENDIF NEXT FOR I = 0 TO NF - 1 FAN[I] = RND(6.28) FRD[I] = 6 + RND(70) FYY[I] = RND(1) FSZ[I] = 2 + RND(3) FCL[I] = I - FLOOR(I / 4) * 4 NEXT FOR I = 0 TO NBU - 1 UX[I] = RND(1) UR[I] = 4 + RND(6) UPH[I] = RND(6.28) NEXT RETURN // ===================================================================== // KITCHEN, BASE, HANDLE, SLIDER // ===================================================================== // colour from PR, PG, PB (0..255, any float) into PC Pack: PR = MAX(0, MIN(255, PR)) PG = MAX(0, MIN(255, PG)) PB = MAX(0, MIN(255, PB)) PC = FLOOR(PR) * 65536 + FLOOR(PG) * 256 + FLOOR(PB) RETURN DrawKitchen: // tiled wall, soft vertical gradient FOR Y = 0 TO 636 STEP 8 PR = 238 - Y * 0.045 PG = 232 - Y * 0.05 PB = 220 - Y * 0.055 GOSUB Pack DISPLAY_FILL_RECT(0, Y, 720, 8, PC) NEXT // tile joints FOR Y = 20 TO 636 STEP 72 DISPLAY_FILL_RECT(0, Y, 720, 2, 0xC9C1B0) NEXT FOR J = 0 TO 8 FOR Y = 20 TO 636 STEP 144 DISPLAY_FILL_RECT(J * 90 + 10, Y, 2, 72, 0xC9C1B0) DISPLAY_FILL_RECT(J * 90 + 55, Y + 72, 2, 72, 0xC9C1B0) NEXT NEXT // window light glow behind the jug DISPLAY_FILL_CIRCLE(CX, 300, 250, 0xEFE9DB) DISPLAY_FILL_CIRCLE(CX, 300, 190, 0xF3EEE2) DISPLAY_FILL_CIRCLE(CX, 300, 120, 0xF7F3EA) // wooden counter top FOR Y = 640 TO 719 STEP 4 PR = 168 - (Y - 640) * 0.9 PG = 118 - (Y - 640) * 0.7 PB = 72 - (Y - 640) * 0.45 GOSUB Pack DISPLAY_FILL_RECT(0, Y, 720, 4, PC) NEXT DISPLAY_FILL_RECT(0, 638, 720, 4, 0xE3C9A0) DISPLAY_FILL_RECT(0, 642, 720, 2, 0x8A5C36) // wood grain FOR J = 0 TO 5 DISPLAY_LINE(0, 660 + J * 11, 720, 662 + J * 11, 0x8A5A34, 1) NEXT RETURN DrawBase: // contact shadow DISPLAY_FILL_RECT(170, 628, 380, 22, 0x6E4A2C, 11) DISPLAY_FILL_RECT(184, 634, 352, 14, 0x4F3520, 7) // motor housing: brushed graphite cylinder, shaded in columns FOR X = 182 TO 536 STEP 4 U = (X + 2 - 360) / 180 SHD = 1 - U * U * 0.62 + 0.28 * MAX(0, 1 - ABS(U + 0.55) * 5) PR = 62 * SHD PG = 66 * SHD PB = 76 * SHD GOSUB Pack YA = 536 + 54 * POW(ABS(U), 6) DISPLAY_FILL_RECT(X, YA, 4, 640 - YA, PC) NEXT // chrome trim ring on top of the housing FOR X = 214 TO 506 STEP 4 U = (X + 2 - 360) / 146 SHD = 1 - U * U * 0.5 + 0.35 * MAX(0, 1 - ABS(U + 0.5) * 4) PR = 190 * SHD PG = 196 * SHD PB = 205 * SHD GOSUB Pack DISPLAY_FILL_RECT(X, 528, 4, 12, PC) NEXT // blade housing (collar) under the jug FOR X = 292 TO 428 STEP 4 U = (X + 2 - 360) / 68 SHD = 1 - U * U * 0.55 + 0.4 * MAX(0, 1 - ABS(U + 0.45) * 4) PR = 150 * SHD PG = 156 * SHD PB = 165 * SHD GOSUB Pack DISPLAY_FILL_RECT(X, 498, 4, 32, PC) NEXT DISPLAY_FILL_RECT(292, 498, 136, 3, 0x40454C) DISPLAY_FILL_RECT(296, 512, 128, 2, 0x3A3F46) // front panel with the logo and status lights DISPLAY_FILL_RECT(240, 566, 240, 52, 0x16181C, 12) DISPLAY_RECT(240, 566, 240, 52, 0x3C4249, 2, 12) DISPLAY_TEXT(270, 580, "R1BBIT BLEND", 0xC9D0D8, 20) DISPLAY_FILL_CIRCLE(262, 592, 4, 0x2B3036) DISPLAY_FILL_CIRCLE(458, 592, 4, 0x2B3036) // soft reflection on the housing DISPLAY_LINE(210, 570, 214, 624, 0x7C8591, 3) // lid: black rubber, top cap, rim DISPLAY_FILL_POLYGON(CX - 126, YT + 6, CX + 126, YT + 6, CX + 112, 88, CX - 112, 88, 0x14161A) DISPLAY_FILL_POLYGON(CX - 60, YT + 6, CX + 126, YT + 6, CX + 112, 88, CX - 20, 88, 0x1E2126) DISPLAY_LINE(CX - 108, 92, CX + 100, 92, 0x4A5058, 3) DISPLAY_FILL_RECT(CX - 32, 66, 64, 26, 0x1A1C20, 8) DISPLAY_FILL_RECT(CX - 26, 68, 18, 20, 0x3B4047, 6) DISPLAY_FILL_RECT(CX - 140, YT + 2, 280, 8, 0x2C3036, 4) DISPLAY_LINE(CX - 136, YT + 4, CX - 40, YT + 4, 0x6D757F, 2) RETURN // black plastic handle on the right: shadow, body, highlight DrawHandle: FOR K = 0 TO 2 IF K = 0 THEN CC = 0x14161A SZ = 22 DX = 3 ELSEIF K = 1 THEN CC = 0x2A2F36 SZ = 17 DX = 0 ELSE CC = 0x6A727C SZ = 3 DX = -4 ENDIF DISPLAY_LINE(484 + DX, 150, 534 + DX, 170, CC, SZ) DISPLAY_LINE(534 + DX, 170, 552 + DX, 232, CC, SZ) DISPLAY_LINE(552 + DX, 232, 552 + DX, 322, CC, SZ) DISPLAY_LINE(552 + DX, 322, 526 + DX, 388, CC, SZ) DISPLAY_LINE(526 + DX, 388, 462 + DX, 410, CC, SZ) IF K < 2 THEN DISPLAY_FILL_CIRCLE(534 + DX, 170, SZ / 2, CC) DISPLAY_FILL_CIRCLE(552 + DX, 232, SZ / 2, CC) DISPLAY_FILL_CIRCLE(552 + DX, 322, SZ / 2, CC) DISPLAY_FILL_CIRCLE(526 + DX, 388, SZ / 2, CC) ENDIF NEXT RETURN DrawSlider: SDRAWN = TGT DISPLAY_FILL_RECT(SLX0 - 30, 652, SLX1 - SLX0 + 60, 64, 0x231A14, 16) DISPLAY_RECT(SLX0 - 30, 652, SLX1 - SLX0 + 60, 64, 0x5A4230, 2, 16) DISPLAY_TEXT(SLX0 - 14, 660, "SPEED", 0xC9B08E, 16) IF TGT = 0 THEN DISPLAY_TEXT(SLX1 - 36, 658, "OFF", 0xC9B08E, 20) ELSE K = ROUND(TGT * 100) X = SLX1 - 24 IF K >= 100 THEN X = SLX1 - 36 DISPLAY_TEXT(X, 658, "" + K + "%", 0xFF6FC8, 20) ENDIF DISPLAY_FILL_RECT(SLX0, SLY - 5, SLX1 - SLX0, 10, 0x3A2D22, 5) X = SLX0 + (SLX1 - SLX0) * TGT IF X > SLX0 + 4 THEN DISPLAY_FILL_RECT(SLX0, SLY - 5, X - SLX0, 10, 0x6A2A8A, 5) DISPLAY_FILL_RECT(SLX0 + (X - SLX0) * 0.5, SLY - 5, (X - SLX0) * 0.5, 10, 0xB03AA8, 5) DISPLAY_FILL_RECT(SLX0 + (X - SLX0) * 0.8, SLY - 5, (X - SLX0) * 0.2, 10, 0xFF5FB8, 5) ENDIF DISPLAY_FILL_CIRCLE(X + 2, SLY + 3, 17, 0x0B0806) DISPLAY_FILL_CIRCLE(X, SLY, 17, 0xB9C0C8) DISPLAY_FILL_CIRCLE(X - 1, SLY - 1, 13, 0xE9EDF1) IF TGT > 0 THEN DISPLAY_FILL_CIRCLE(X, SLY, 6, 0xC83CA8) ELSE DISPLAY_FILL_CIRCLE(X, SLY, 6, 0x7D8794) ENDIF RETURN // ===================================================================== // JUG (redrawn every frame, inside the glass) // ===================================================================== DrawJug: // interior seen through clean glass DISPLAY_FILL_POLYGON(CX - HWT + WALL, YT + 10, CX + HWT - WALL, YT + 10, CX + HWB - WALL, YB, CX - HWB + WALL, YB, 0xE9EBE6) DISPLAY_FILL_POLYGON(CX + 40, YT + 10, CX + HWT - WALL, YT + 10, CX + HWB - WALL, YB, CX + 30, YB, 0xDDE2DE) // liquid colour: dark juice -> light creamy smoothie LR = 118 + 58 * P LG = 26 + 38 * P LB = 70 + 72 * P LH = 215 * P DP = MIN(50 * RPM, LH * 0.5) SYW = YB - LH - 0.35 * DP GOSUB Liquid GOSUB Blades GOSUB Berries GOSUB Pieces GOSUB Foam GOSUB Glass RETURN // liquid as 4 px columns; the surface is a funnel (vortex) when spinning Liquid: FOR X = CX - HWT + WALL TO CX + HWT - WALL - 4 STEP 4 XC = X + 2 DX = ABS(XC - CX) U = (XC - CX) / (HWT - WALL) YL = YB IF DX + WALL + 2 > HWB THEN YL = YB - (DX + WALL + 2 - HWB) / SLOPE IF YL > YB THEN YL = YB SURF = SYW + DP * (1 - U * U) + 2.4 * RPM * SIN(XC * 0.13 + T * 0.45) SHD = 1.04 - 0.5 * U * U + 0.28 * MAX(0, 1 - ABS(U + 0.5) * 5) + 0.07 * SIN(XC * 0.16 - ANG * 3) * RPM IF SURF < YL - 2 THEN PR = LR * SHD PG = LG * SHD PB = LB * SHD GOSUB Pack DISPLAY_FILL_RECT(X, SURF, 4, YL - SURF, PC) // deeper, darker part near the glass bottom IF YL > YB - 50 AND SURF < YB - 50 THEN PR = LR * SHD * 0.72 PG = LG * SHD * 0.72 PB = LB * SHD * 0.72 GOSUB Pack DISPLAY_FILL_RECT(X, YB - 50, 4, YL - (YB - 50), PC) ENDIF // meniscus: bright rim along the surface PR = LR * SHD + 70 PG = LG * SHD + 55 PB = LB * SHD + 70 GOSUB Pack DISPLAY_FILL_RECT(X, SURF, 4, 2, PC) ENDIF NEXT RETURN // blades at the bottom: bare steel until juice covers them, then seen // through the liquid; motion blur when fast Blades: YA = YB - 16 // CV: how much liquid is over the blades (0 none .. 1 covered) CV = MIN(1, LH / 30) IF RPM < 0.3 THEN PR = 190 * (1 - 0.5 * CV) + LR * 0.5 * CV PG = 196 * (1 - 0.5 * CV) + LG * 0.5 * CV PB = 205 * (1 - 0.5 * CV) + LB * 0.5 * CV GOSUB Pack FOR I = 0 TO 3 AN = ANG * 3 + I * PI / 2 DX = 60 * COS(AN) K = 11 * SIN(I * PI / 2 + 0.7) DISPLAY_FILL_POLYGON(CX, YA - 3, CX + DX, YA - 4 - K, CX + DX, YA + 2 - K, CX, YA + 4, PC) NEXT ELSE IF LH >= 24 THEN PR = LR * (1 - 0.3 * RPM) + 60 * RPM PG = LG * (1 - 0.3 * RPM) + 60 * RPM PB = LB * (1 - 0.3 * RPM) + 62 * RPM GOSUB Pack DISPLAY_FILL_POLYGON(CX - 62, YA, CX - 30, YA - 13, CX + 30, YA - 13, CX + 62, YA, CX + 30, YA + 9, CX - 30, YA + 9, PC) ENDIF PR = 190 * (1 - 0.6 * CV) + LR * 0.4 * CV + 40 * CV PG = 196 * (1 - 0.6 * CV) + LG * 0.4 * CV + 40 * CV PB = 205 * (1 - 0.6 * CV) + LB * 0.4 * CV + 40 * CV GOSUB Pack FOR I = 0 TO 1 AN = ANG * 3 + I * 1.57 DX = 58 * COS(AN) DISPLAY_LINE(CX - DX, YA + 6 * SIN(AN + 1), CX + DX, YA - 8 * SIN(AN + 1), PC, 3) NEXT ENDIF DISPLAY_FILL_CIRCLE(CX, YA, 7, 0x9AA2AC) DISPLAY_FILL_CIRCLE(CX - 2, YA - 2, 4, 0xDCE2E8) RETURN // whole berries. A berry falls in from the top, lands on the pile, is // pulled under by the vortex, dragged to the blades and shredded. // ST: 0 waiting, 1 falling, 2 in the jug, 4 gone. AGE grows with the // speed. Back ones are drawn first; submerged ones are a bit darker. Berries: FOR Q = 0 TO 1 FOR I = 0 TO NB - 1 IF ST[I] = 1 OR ST[I] = 2 THEN SK = MIN(1, AGE[I] / 25) WW = MIN(1, MAX(0, (AGE[I] - 25) / 25)) SZ = 1 - MIN(1, MAX(0, (AGE[I] - 50) / 12)) AN = BAN[I] + ANG * (0.8 + BPH[I] * 0.1) * (1 + SK * 0.8) U = SIN(AN) IF (Q = 0 AND U < 0) OR (Q = 1 AND U >= 0) THEN BX = CX + BRD[I] * (1 - 0.5 * SK) * (1 - 0.4 * WW) * COS(AN) DX = (BX - CX) / (HWT - WALL) SURF = SYW + DP * (1 - DX * DX) IF SURF > YB - 8 THEN SURF = YB - 8 BSZ = BSS[I] * SZ * (1 + 0.1 * U) IF ST[I] = 1 THEN BY = BYY[I] ELSE YA = SURF - BSZ * 0.4 - ((1 - BRD[I] / 48) * 24 + BHP[I] * 10) * (1 - SK) + 6 * SIN(AN * 2 + BPH[I]) * RPM * (1 - SK) YL = SURF + 10 + BHP[I] * 40 IF YL > YB - 30 THEN YL = YB - 30 YL = YL + WW * (YB - 34 - YL) BY = YA + (YL - YA) * SK + 4 * SIN(AN * 2 + BPH[I]) * SK ENDIF BTY = BTA[I] BSH = (0.8 + 0.2 * U) * (1 - 0.3 * SK) IF BSZ >= 1.5 THEN GOSUB Berry ENDIF ENDIF NEXT NEXT RETURN // berry feed and juice volume: one berry drops in every few frames while // the motor runs; the juice level follows how much is already ground Physics: FEED = FEED + RPM IF RPM > 0.12 AND FEED >= 10 AND NXT < NB THEN FEED = 0 ST[NXT] = 1 // start just below the lid so a berry never touches it BYY[NXT] = YT + 12 + BSS[NXT] * 1.4 BV[NXT] = 0 AGE[NXT] = 0 NXT = NXT + 1 ENDIF G = 0 FOR I = 0 TO NB - 1 IF ST[I] = 1 THEN BV[I] = BV[I] + 0.9 BYY[I] = BYY[I] + BV[I] AN = BAN[I] + ANG * (0.8 + BPH[I] * 0.1) DX = BRD[I] * COS(AN) / (HWT - WALL) U = SYW + DP * (1 - DX * DX) IF U > YB - 8 THEN U = YB - 8 IF BYY[I] >= U - BSS[I] * 0.4 - ((1 - BRD[I] / 48) * 24 + BHP[I] * 10) THEN ST[I] = 2 AGE[I] = 0 ENDIF ELSEIF ST[I] = 2 THEN AGE[I] = AGE[I] + RPM IF AGE[I] >= 62 THEN ST[I] = 4 G = G + MIN(1, MAX(0, (AGE[I] - 50) / 12)) ELSEIF ST[I] = 4 THEN G = G + 1 ENDIF NEXT P = G / NB RETURN // shredded pieces swirling in the juice: big chunks first, then smaller // and smaller, until the smoothie is perfectly smooth Pieces: IF RPM > 0.04 AND P > 0.01 AND P < 0.97 THEN // count grows as berries break up, fades out near the end K = NF * MIN(1, P * 4) * MIN(1, (0.97 - P) * 12) // chunk size shrinks with progress DA = 2.5 - 2 * P FOR I = 0 TO K - 1 AN = FAN[I] + ANG * (1.3 + FRD[I] / 60) BX = CX + FRD[I] * 0.9 * COS(AN) DX = (BX - CX) / (HWT - WALL) SURF = SYW + DP * (1 - DX * DX) IF SURF > YB - 14 THEN SURF = YB - 14 BY = SURF + 4 + FYY[I] * (YB - SURF - 22) + 4 * SIN(AN * 3) BSZ = FSZ[I] * DA * (0.75 + 0.25 * SIN(AN)) IF FCL[I] = 0 THEN CC = 0x2B1450 ELSEIF FCL[I] = 1 THEN CC = 0xC8203C ELSEIF FCL[I] = 2 THEN CC = 0x5A2D86 ELSE CC = 0xF2A6C4 ENDIF IF BSZ > 3.2 THEN // irregular chunk: a tumbling triangle with a lit spot DISPLAY_FILL_TRIANGLE(BX + BSZ * COS(AN * 2), BY + BSZ * SIN(AN * 2), BX + BSZ * COS(AN * 2 + 2.4), BY + BSZ * SIN(AN * 2 + 2.4), BX + BSZ * COS(AN * 2 + 4.3), BY + BSZ * SIN(AN * 2 + 4.3), CC) DISPLAY_FILL_CIRCLE(BX - BSZ * 0.2, BY - BSZ * 0.25, BSZ * 0.22, 0xFFD0DC) ELSE DISPLAY_FILL_CIRCLE(BX, BY, BSZ, CC) ENDIF NEXT ENDIF RETURN // foam: bubbles on the surface once the smoothie gets creamy Foam: IF P > 0.3 THEN K = NBU * MIN(1, (P - 0.3) * 1.8) FOR I = 0 TO K - 1 BX = CX - 78 + UX[I] * 156 DX = (BX - CX) / (HWT - WALL) SURF = SYW + DP * (1 - DX * DX) + 2.4 * RPM * SIN(BX * 0.13 + T * 0.45) BY = SURF - UR[I] * 0.4 + 1.5 * SIN(T * 0.2 * (0.2 + RPM) + UPH[I]) BSZ = UR[I] DISPLAY_FILL_CIRCLE(BX, BY + 1, BSZ, 0xB86AA0) DISPLAY_FILL_CIRCLE(BX, BY, BSZ - 1, 0xEBB4D6) DISPLAY_FILL_CIRCLE(BX - BSZ * 0.3, BY - BSZ * 0.35, BSZ * 0.3, 0xFFFFFF) NEXT ENDIF RETURN // glass walls, thick bottom, graduation marks, reflections, splashes Glass: // splashes on the glass above the liquid IF P > 0.1 THEN K = NBU * MIN(1, P * 1.5) FOR I = 0 TO K - 1 BY = SYW - 20 - UPH[I] * 14 - I * 7 IF BY > YT + 14 THEN DX = HWB + (YB - BY) * SLOPE - WALL - 6 IF I - FLOOR(I / 2) * 2 = 0 THEN BX = CX - DX + UPH[I] ELSE BX = CX + DX - UPH[I] ENDIF PR = LR * 0.9 + 25 PG = LG * 0.9 + 12 PB = LB * 0.9 + 25 GOSUB Pack DISPLAY_FILL_CIRCLE(BX, BY, 2 + UR[I] * 0.3, PC) ENDIF NEXT ENDIF // graduation marks on the inner left wall FOR I = 1 TO 4 Y = YB - 70 * I X = CX - (HWB + (YB - Y) * SLOPE) + WALL DISPLAY_LINE(X, Y, X + 16, Y, 0x4F5C5E, 2) DISPLAY_LINE(X, Y + 35, X + 8, Y + 35, 0x6C797B, 1) DISPLAY_TEXT(X + 20, Y - 8, "" + (I * 250), 0x4F5C5E, 12) NEXT // glass walls (left lit, right in shade) and thick bottom DISPLAY_FILL_POLYGON(CX - HWT, YT + 10, CX - HWT + WALL, YT + 10, CX - HWB + WALL, YB, CX - HWB, YB, 0xB4D0D2) DISPLAY_FILL_POLYGON(CX + HWT - WALL, YT + 10, CX + HWT, YT + 10, CX + HWB, YB, CX + HWB - WALL, YB, 0x7C9A9E) DISPLAY_FILL_RECT(CX - HWB, YB - 6, 2 * HWB, 14, 0x8EAEB2, 5) DISPLAY_FILL_RECT(CX - HWB + 6, YB - 6, 2 * HWB - 12, 4, 0xC4DCDC, 2) DISPLAY_LINE(CX - HWT + WALL, YT + 10, CX - HWB + WALL, YB, 0xE6F2F2, 2) DISPLAY_LINE(CX + HWT - WALL, YT + 10, CX + HWB - WALL, YB, 0x5F7A7E, 2) // long soft reflections DISPLAY_LINE(CX - HWT + 30, YT + 40, CX - HWB + 20, YB - 50, 0xF7FBFB, 6) DISPLAY_LINE(CX - HWT + 44, YT + 60, CX - HWB + 33, YB - 90, 0xDCEAEA, 2) DISPLAY_LINE(CX + HWT - 24, YT + 50, CX + HWB - 16, YB - 120, 0xD2E2E2, 3) DISPLAY_FILL_CIRCLE(CX - HWT + 26, YT + 36, 3, 0xFFFFFF) RETURN // ===================================================================== // ONE BERRY at BX, BY, radius BSZ, type BTY, shade BSH (0.6..1) // 0 blueberry 1 raspberry 2 blackberry 3 wild strawberry // 4 red currant // ===================================================================== // PR, PG, PB scaled by the depth shade, then packed into PC PackS: PR = PR * BSH PG = PG * BSH PB = PB * BSH GOSUB Pack RETURN Berry: // contact shadow on the liquid DISPLAY_FILL_CIRCLE(BX + 2, BY + 3, BSZ, 0x4A1636) IF BTY = 0 THEN PR = 30 PG = 40 PB = 98 GOSUB PackS DISPLAY_FILL_CIRCLE(BX, BY, BSZ, PC) PR = 56 PG = 74 PB = 156 GOSUB PackS DISPLAY_FILL_CIRCLE(BX, BY, BSZ * 0.93, PC) // waxy bloom PR = 104 PG = 126 PB = 196 GOSUB PackS DISPLAY_FILL_CIRCLE(BX - BSZ * 0.22, BY - BSZ * 0.2, BSZ * 0.62, PC) PR = 72 PG = 94 PB = 170 GOSUB PackS DISPLAY_FILL_CIRCLE(BX - BSZ * 0.1, BY - BSZ * 0.08, BSZ * 0.5, PC) // star crown DISPLAY_FILL_CIRCLE(BX + BSZ * 0.28, BY + BSZ * 0.05, BSZ * 0.27, 0x161A44) DISPLAY_FILL_CIRCLE(BX + BSZ * 0.28, BY + BSZ * 0.05, BSZ * 0.14, 0x3A4690) DISPLAY_FILL_CIRCLE(BX - BSZ * 0.4, BY - BSZ * 0.45, BSZ * 0.15, 0xFFFFFF) ELSEIF BTY = 1 OR BTY = 2 THEN // drupelet berries: dark body, ring of glossy beads, centre bead IF BTY = 1 THEN PR = 120 PG = 10 PB = 36 ELSE PR = 24 PG = 10 PB = 36 ENDIF GOSUB PackS DISPLAY_FILL_CIRCLE(BX, BY, BSZ, PC) FOR J = 0 TO 11 IF J < 7 THEN AN = J * 0.8976 + 0.3 DX = BSZ * 0.58 SZ = BSZ * 0.4 ELSE AN = (J - 7) * 1.2566 DX = BSZ * 0.2 SZ = BSZ * 0.34 ENDIF XC = BX + COS(AN) * DX YA = BY + SIN(AN) * DX IF BTY = 1 THEN PR = 214 - SIN(AN) * 30 PG = 30 PB = 66 ELSE PR = 70 - SIN(AN) * 14 PG = 30 PB = 96 - SIN(AN) * 12 ENDIF GOSUB PackS DISPLAY_FILL_CIRCLE(XC, YA, SZ, PC) DISPLAY_FILL_CIRCLE(XC - SZ * 0.3, YA - SZ * 0.35, SZ * 0.3, 0xFFD6E0) NEXT ELSEIF BTY = 3 THEN PR = 150 PG = 18 PB = 28 GOSUB PackS DISPLAY_FILL_CIRCLE(BX, BY, BSZ, PC) PR = 226 PG = 44 PB = 52 GOSUB PackS DISPLAY_FILL_CIRCLE(BX - BSZ * 0.06, BY - BSZ * 0.06, BSZ * 0.9, PC) // seeds FOR J = 0 TO 6 AN = J * 0.8976 + 0.5 DISPLAY_FILL_CIRCLE(BX + COS(AN) * BSZ * 0.55, BY + SIN(AN) * BSZ * 0.55, 1.4, 0xF2D35A) NEXT DISPLAY_FILL_CIRCLE(BX, BY + BSZ * 0.1, 1.4, 0xF2D35A) // green leaves on top DISPLAY_FILL_TRIANGLE(BX, BY - BSZ * 0.7, BX - BSZ * 0.7, BY - BSZ * 0.95, BX - BSZ * 0.2, BY - BSZ * 0.35, 0x3E8A34) DISPLAY_FILL_TRIANGLE(BX, BY - BSZ * 0.7, BX + BSZ * 0.7, BY - BSZ * 0.95, BX + BSZ * 0.2, BY - BSZ * 0.35, 0x2F6E28) DISPLAY_FILL_TRIANGLE(BX - BSZ * 0.3, BY - BSZ * 0.6, BX + BSZ * 0.3, BY - BSZ * 0.6, BX, BY - BSZ * 1.15, 0x4BA03E) DISPLAY_FILL_CIRCLE(BX - BSZ * 0.4, BY - BSZ * 0.2, BSZ * 0.14, 0xFFC8CC) ELSE PR = 140 PG = 12 PB = 30 GOSUB PackS DISPLAY_FILL_CIRCLE(BX, BY, BSZ, PC) PR = 232 PG = 40 PB = 58 GOSUB PackS DISPLAY_FILL_CIRCLE(BX - BSZ * 0.08, BY - BSZ * 0.08, BSZ * 0.86, PC) PR = 250 PG = 96 PB = 112 GOSUB PackS DISPLAY_FILL_CIRCLE(BX - BSZ * 0.22, BY - BSZ * 0.24, BSZ * 0.5, PC) DISPLAY_FILL_CIRCLE(BX + BSZ * 0.32, BY + BSZ * 0.38, BSZ * 0.14, 0x5A0A18) DISPLAY_FILL_CIRCLE(BX - BSZ * 0.42, BY - BSZ * 0.46, BSZ * 0.2, 0xFFFFFF) ENDIF RETURN