diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..be51d83 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +# Build files +/build-fx +/build-cg +/*.g1a +/*.g3a + +# Python bytecode +__pycache__/ + +# Common IDE files +*.sublime-project +*.sublime-workspace +.vscode diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..5497b9e --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,50 @@ +# Configure with [fxsdk build-fx] or [fxsdk build-cg], which provide the +# toolchain file and module path of the fxSDK + +cmake_minimum_required(VERSION 3.15) +project(MyAddin) + +include(GenerateG1A) +include(GenerateG3A) +include(Fxconv) +find_package(Gint 2.9 REQUIRED) +find_package(LibProf 2.4 REQUIRED) + + +set(SOURCES + src/main.c + # src/menus.c + # src/fluid64x64.c + # src/fluid128x64.c +) +# Shared assets, fx-9860G-only assets and fx-CG-50-only assets +set(ASSETS + # ... +) +set(ASSETS_fx + assets-fx/example.png + assets-fx/myfont.png + # ... +) +set(ASSETS_cg + assets-cg/example.png + # ... +) + +fxconv_declare_assets(${ASSETS} ${ASSETS_fx} ${ASSETS_cg} WITH_METADATA) + +add_executable(myaddin ${SOURCES} ${ASSETS} ${ASSETS_${FXSDK_PLATFORM}}) +target_compile_options(myaddin PRIVATE -Wall -Wextra -O3) +target_link_options(myaddin PRIVATE + -Wl,-Map=map -Wl,--print-memory-usage) +target_link_libraries(myaddin LibProf::LibProf) +target_link_libraries(myaddin Gint::Gint) + + +if("${FXSDK_PLATFORM_LONG}" STREQUAL fx9860G) + generate_g1a(TARGET myaddin OUTPUT "MyAddin.g1a" + NAME "MyAddin" ICON assets-fx/icon.png) +elseif("${FXSDK_PLATFORM_LONG}" STREQUAL fxCG50) + generate_g3a(TARGET myaddin OUTPUT "MyAddin.g3a" + NAME "MyAddin" ICONS assets-cg/icon-uns.png assets-cg/icon-sel.png) +endif() diff --git a/assets-cg/example.png b/assets-cg/example.png new file mode 100644 index 0000000..8826800 Binary files /dev/null and b/assets-cg/example.png differ diff --git a/assets-cg/fxconv-metadata.txt b/assets-cg/fxconv-metadata.txt new file mode 100644 index 0000000..d435d5f --- /dev/null +++ b/assets-cg/fxconv-metadata.txt @@ -0,0 +1,3 @@ +example.png: + type: bopti-image + name: img_example diff --git a/assets-cg/icon-sel.png b/assets-cg/icon-sel.png new file mode 100644 index 0000000..7137b50 Binary files /dev/null and b/assets-cg/icon-sel.png differ diff --git a/assets-cg/icon-uns.png b/assets-cg/icon-uns.png new file mode 100644 index 0000000..3c99f62 Binary files /dev/null and b/assets-cg/icon-uns.png differ diff --git a/assets-fx/example.png b/assets-fx/example.png new file mode 100644 index 0000000..b26ba9a Binary files /dev/null and b/assets-fx/example.png differ diff --git a/assets-fx/fxconv-metadata.txt b/assets-fx/fxconv-metadata.txt new file mode 100644 index 0000000..b3c1020 --- /dev/null +++ b/assets-fx/fxconv-metadata.txt @@ -0,0 +1,11 @@ +example.png: + type: bopti-image + name: img_example + +myfont.png: + type: font + name: myfont + charset: print + grid.size: 5x7 + grid.padding: 1 + proportional: true \ No newline at end of file diff --git a/assets-fx/icon.png b/assets-fx/icon.png new file mode 100644 index 0000000..ee1edc4 Binary files /dev/null and b/assets-fx/icon.png differ diff --git a/assets-fx/myfont.png b/assets-fx/myfont.png new file mode 100644 index 0000000..bb877fe Binary files /dev/null and b/assets-fx/myfont.png differ diff --git a/src/defines.h b/src/defines.h new file mode 100644 index 0000000..b51f34c --- /dev/null +++ b/src/defines.h @@ -0,0 +1,85 @@ +#ifndef DEFINES_H +#define DEFINES_H + +// Fixed Point + +typedef int fix; + +#define DB 16 +#define FIX(x) ((x)<>DB) // fixed to int + +fix fmul(fix x, fix y) { + int64_t p = (int64_t)x * (int64_t)y; + return (int32_t)(p >> DB); +} + +fix ffrac(fix f) { + return f & ((1 << DB) - 1); +} + +float fixtof(fix f) { + return ((float)f)/(1< MAX_X2) x = MAX_X2; + if (y < 0) y = 0; + else if (y > MAX_Y) y = MAX_Y; + + int x1 = UNFIX(x); + int y1 = UNFIX(y); + int x2 = x1 + 1; + int y2 = y1 + 1; + + int tl = ID2(x1, y1); + int tr = ID2(x2, y1); + int bl = ID2(x1, y2); + int br = ID2(x2, y2); + + fix s1 = ffrac(x); + fix s0 = ONE - s1; + fix t1 = ffrac(y); + fix t0 = ONE - t1; + + outx[id] = fmul(s0, fmul(t0, vx[tl]) + fmul(t1, vx[bl])) + + fmul(s1, fmul(t0, vx[tr]) + fmul(t1, vx[br])); + + outy[id] = fmul(s0, fmul(t0, vy[tl]) + fmul(t1, vy[bl])) + + fmul(s1, fmul(t0, vy[tr]) + fmul(t1, vy[br])); + + outz[id] = fmul(s0, fmul(t0, dye[tl]) + fmul(t1, dye[bl])) + + fmul(s1, fmul(t0, dye[tr]) + fmul(t1, dye[br])); + } + } +} + +void fluid_step128x64() { + // Advect the velocity field and the dye (27.7ms) + advect128x64(dye, vx, vy, tx, ty, tz); + // Swap the pointers + swap(vx, tx); + swap(vy, ty); + swap(dye, tz); + + // Velocity boundary conditions (0.1ms) + for (int i = 0; i < W2; i++) { + vy[ID2(i, 0)] = -vy[ID2(i, 1)]; + vy[ID2(i, H-1)] = -vy[ID2(i, H-2)]; + vx[ID2(i, 0)] = vx[ID2(i, 1)]; + vx[ID2(i, H-1)] = vx[ID2(i, H-2)]; + } + for (int j = 0; j < H; j++) { + vx[ID2(0, j)] = -vx[ID2(1, j)]; + vx[ID2(W2-1, j)] = -vx[ID2(W2-2, j)]; + vy[ID2(0, j)] = vy[ID2(1, j)]; + vy[ID2(W2-1, j)] = vy[ID2(W2-2, j)]; + } + + // Calculate divergence & first pressure iteration (3.6ms) + for (int j = 1; j < H-1; j++) { + for (int i = 1; i < W2-1; i++) { + div[ID2(i, j)] = fmul(alphaHalfRdx, vx[ID2(i+1,j)] - vx[ID2(i-1, j)] + vy[ID2(i, j+1)] - vy[ID2(i, j-1)]); + p[ID2(i, j)] = div[ID2(i, j)] >> 2; + } + } + + // Initial Pressure boundary conditions (0ms) + for (int i = 0; i < W2; i++) { + p[ID2(i, 0)] = p[ID2(i, 1)]; + p[ID2(i, H-1)] = p[ID2(i, H-2)]; + } + for (int j = 0; j < H; j++) { + p[ID2(0, j)] = p[ID2(1, j)]; + p[ID2(W2-1, j)] = p[ID2(W2-2, j)]; + } + + // Poisson pressure solver (20.8ms) + for (int k = 0; k < pressureIterations-1; k++) { + // Jacobi iteration (20.4ms) + for (int j = 1; j < H-1; j++) { + for (int i = 1; i < W2-1; i++) { + int id = ID2(i, j); + p[id] = (div[id] + p[ID2(i-1, j)] + p[ID2(i+1, j)] + p[ID2(i, j-1)] + p[ID2(i, j+1)]) >> 2; + } + } + + // Pressure boundary conditions (0.4ms) + for (int i = 0; i < W2; i++) { + p[ID2(i, 0)] = p[ID2(i, 1)]; + p[ID2(i, H-1)] = p[ID2(i, H-2)]; + } + for (int j = 0; j < H; j++) { + p[ID2(0, j)] = p[ID2(1, j)]; + p[ID2(W2-1, j)] = p[ID2(W2-2, j)]; + } + } + + // Apply Forces + if (currentView == 0) { + int lastX = addX; + int lastY = addY; + + if (keydown(KEY_LEFT)) { addX -= 1; if (addX < 1) addX = W2-2; } + else if (keydown(KEY_RIGHT)) { addX += 1; if (addX > W2-2) addX = 1; } + if (keydown(KEY_UP)) { addY -= 1; if (addY < 1) addY = H-2; } + else if (keydown(KEY_DOWN)) { addY += 1; if (addY > H-2) addY = 1; } + + fix adx = FIX(addX - lastX); + fix ady = FIX(addY - lastY); + + if (adx || ady) { + fix ddx = abs(adx); + fix ddy = abs(ady); + fix vdx = adx / 10; + fix vdy = ady / 10; + fix ddxy = ddx + ddy; + + for (int j = addY-3; j < addY+3; j++) { + for (int i = addX-3; i < addX+3; i++) { + if (i<=0||j<=0||i>=W2-1||j>=H-1) continue; + int id = ID2(i, j); + + fix dist = radius - FIX((addX-i)*(addX-i)+(addY-j)*(addY-j)); + if (dist < 0) continue; + fix dyeD = fmul(dist, dyeIntensity); + fix velD = fmul(dist, velIntensity); + + dye[id] = dye[id] + fmul(ddxy, dyeD); + if (dye[id] > maxDyeIntensity) dye[id] = maxDyeIntensity; + vx[id] = vx[id] + fmul(vdx, velD); + vy[id] = vy[id] + fmul(vdy, velD); + } + } + } + } + + // Subtract pressure from velocity & Diffuse dye and velocity (3.4ms) + for (int j = 1; j < H-1; j++) { + for (int i = 1; i < W2-1; i++) { + int id = ID2(i, j); + vx[id] = fmul(vx[id] - fmul(halfRdx, p[ID2(i+1, j)] - p[ID2(i-1, j)]), velDiffusion); + vy[id] = fmul(vy[id] - fmul(halfRdx, p[ID2(i, j+1)] - p[ID2(i, j-1)]), velDiffusion); + } + } + + // Draw dye to vram (3.1ms) + uint32_t *vram = gint_vram+4; + for (int j = 1; j < H-1; j++) { + uint32_t data = 0; + + data = (data << 1); + for(int k = 1; k < 32; k++) { + int x = 32*0+k; + int id = ID2(x, j); + dye[id] = fmul(dye[id], dyeDiffusion); + data = (data << 1) | (dye[id] > saturateTreshold || (dye[id] > emptyTreshold && (x+j)%2)); + } + vram[0] = data; + data = 0; + + data = (data << 1); + for(int k = 0; k < 32; k++) { + int x = 32*1+k; + int id = ID2(x, j); + dye[id] = fmul(dye[id], dyeDiffusion); + data = (data << 1) | (dye[id] > saturateTreshold || (dye[id] > emptyTreshold && (x+j)%2)); + } + vram[1] = data; + data = 0; + + data = (data << 1); + for(int k = 0; k < 32; k++) { + int x = 32*2+k; + int id = ID2(x, j); + dye[id] = fmul(dye[id], dyeDiffusion); + data = (data << 1) | (dye[id] > saturateTreshold || (dye[id] > emptyTreshold && (x+j)%2)); + } + vram[2] = data; + data = 0; + + for(int k = 0; k < 31; k++) { + int x = 32*3+k; + int id = ID2(x, j); + dye[id] = fmul(dye[id], dyeDiffusion); + data = (data << 1) | (dye[id] > saturateTreshold || (dye[id] > emptyTreshold && (x+j)%2)); + } + data = (data << 1); + vram[3] = data; + + vram+=4; + } +} \ No newline at end of file diff --git a/src/fluid64x64.c b/src/fluid64x64.c new file mode 100644 index 0000000..ac872b5 --- /dev/null +++ b/src/fluid64x64.c @@ -0,0 +1,176 @@ +void advect64x64(fix* dye, fix* vx, fix* vy, fix* outx, fix* outy, fix* outz) { + for (int j = 1; j < H-1; j++) { + for (int i = 1; i < W-1; i++) { + int id = ID(i, j); + + fix x = FIX(i) - fmul(dtRdx, vx[id]); + fix y = FIX(j) - fmul(dtRdx, vy[id]); + + if (x < 0) x = 0; + else if (x > MAX_X) x = MAX_X; + if (y < 0) y = 0; + else if (y > MAX_Y) y = MAX_Y; + + int x1 = UNFIX(x); + int y1 = UNFIX(y); + int x2 = x1 + 1; + int y2 = y1 + 1; + + int tl = ID(x1, y1); + int tr = ID(x2, y1); + int bl = ID(x1, y2); + int br = ID(x2, y2); + + fix s1 = ffrac(x); + fix s0 = ONE - s1; + fix t1 = ffrac(y); + fix t0 = ONE - t1; + + outx[id] = fmul(s0, fmul(t0, vx[tl]) + fmul(t1, vx[bl])) + + fmul(s1, fmul(t0, vx[tr]) + fmul(t1, vx[br])); + + outy[id] = fmul(s0, fmul(t0, vy[tl]) + fmul(t1, vy[bl])) + + fmul(s1, fmul(t0, vy[tr]) + fmul(t1, vy[br])); + + outz[id] = fmul(s0, fmul(t0, dye[tl]) + fmul(t1, dye[bl])) + + fmul(s1, fmul(t0, dye[tr]) + fmul(t1, dye[br])); + } + } +} + +void fluid_step64x64() { + // Advect the velocity field and the dye (27.7ms) + advect64x64(dye, vx, vy, tx, ty, tz); + // Swap the pointers + swap(vx, tx); + swap(vy, ty); + swap(dye, tz); + + // Velocity boundary conditions (0.1ms) + for (int i = 0; i < W; i++) { + vy[ID(i, 0)] = -vy[ID(i, 1)]; + vy[ID(i, H-1)] = -vy[ID(i, H-2)]; + vx[ID(i, 0)] = vx[ID(i, 1)]; + vx[ID(i, H-1)] = vx[ID(i, H-2)]; + } + for (int j = 0; j < H; j++) { + vx[ID(0, j)] = -vx[ID(1, j)]; + vx[ID(W-1, j)] = -vx[ID(W-2, j)]; + vy[ID(0, j)] = vy[ID(1, j)]; + vy[ID(W-1, j)] = vy[ID(W-2, j)]; + } + + // Calculate divergence & first pressure iteration (3.6ms) + for (int j = 1; j < H-1; j++) { + for (int i = 1; i < W-1; i++) { + div[ID(i, j)] = fmul(alphaHalfRdx, vx[ID(i+1,j)] - vx[ID(i-1, j)] + vy[ID(i, j+1)] - vy[ID(i, j-1)]); + p[ID(i, j)] = div[ID(i, j)] >> 2; + } + } + + // Initial Pressure boundary conditions (0ms) + for (int i = 0; i < W; i++) { + p[ID(i, 0)] = p[ID(i, 1)]; + p[ID(i, H-1)] = p[ID(i, H-2)]; + } + for (int j = 0; j < H; j++) { + p[ID(0, j)] = p[ID(1, j)]; + p[ID(W-1, j)] = p[ID(W-2, j)]; + } + + // Poisson pressure solver (20.8ms) + for (int k = 0; k < pressureIterations-1; k++) { + // Jacobi iteration (20.4ms) + for (int j = 1; j < H-1; j++) { + for (int i = 1; i < W-1; i++) { + int id = ID(i, j); + p[id] = (div[id] + p[ID(i-1, j)] + p[ID(i+1, j)] + p[ID(i, j-1)] + p[ID(i, j+1)]) >> 2; + } + } + + // Pressure boundary conditions (0.4ms) + for (int i = 0; i < W; i++) { + p[ID(i, 0)] = p[ID(i, 1)]; + p[ID(i, H-1)] = p[ID(i, H-2)]; + } + for (int j = 0; j < H; j++) { + p[ID(0, j)] = p[ID(1, j)]; + p[ID(W-1, j)] = p[ID(W-2, j)]; + } + } + + // Apply Forces + if (currentView == 0) { + int lastX = addX; + int lastY = addY; + + if (keydown(KEY_LEFT)) { addX -= 1; if (addX < 1) addX = W-2; } + else if (keydown(KEY_RIGHT)) { addX += 1; if (addX > W-2) addX = 1; } + if (keydown(KEY_UP)) { addY -= 1; if (addY < 1) addY = H-2; } + else if (keydown(KEY_DOWN)) { addY += 1; if (addY > H-2) addY = 1; } + + fix adx = FIX(addX - lastX); + fix ady = FIX(addY - lastY); + + if (adx || ady) { + fix ddx = abs(adx); + fix ddy = abs(ady); + fix vdx = adx / 10; + fix vdy = ady / 10; + fix ddxy = ddx + ddy; + + for (int j = addY-3; j < addY+3; j++) { + for (int i = addX-3; i < addX+3; i++) { + if (i<=0||j<=0||i>=W-1||j>=H-1) continue; + int id = ID(i, j); + + fix dist = radius - FIX((addX-i)*(addX-i)+(addY-j)*(addY-j)); + if (dist < 0) continue; + fix dyeD = fmul(dist, dyeIntensity); + fix velD = fmul(dist, velIntensity); + + dye[id] = dye[id] + fmul(ddxy, dyeD); + if (dye[id] > maxDyeIntensity) dye[id] = maxDyeIntensity; + vx[id] = vx[id] + fmul(vdx, velD); + vy[id] = vy[id] + fmul(vdy, velD); + } + } + } + } + + // Subtract pressure from velocity & Diffuse dye and velocity (3.4ms) + for (int j = 1; j < H-1; j++) { + for (int i = 1; i < W-1; i++) { + int id = ID(i, j); + vx[id] = fmul(vx[id] - fmul(halfRdx, p[ID(i+1, j)] - p[ID(i-1, j)]), velDiffusion); + vy[id] = fmul(vy[id] - fmul(halfRdx, p[ID(i, j+1)] - p[ID(i, j-1)]), velDiffusion); + } + } + + // Draw dye to vram (3.1ms) + uint32_t *vram = gint_vram+4; + for (int j = 1; j < H-1; j++) { + uint32_t data = 0; + + data = (data << 1); + for(int k = 1; k < 32; k++) { + int x = 32*0+k; + int id = ID(x, j); + dye[id] = fmul(dye[id], dyeDiffusion); + data = (data << 1) | (dye[id] > saturateTreshold || (dye[id] > emptyTreshold && (x+j)%2)); + } + vram[0] = data; + data = 0; + + for(int k = 0; k < 31; k++) { + int x = 32*1+k; + int id = ID(x, j); + dye[id] = fmul(dye[id], dyeDiffusion); + data = (data << 1) | (dye[id] > saturateTreshold || (dye[id] > emptyTreshold && (x+j)%2)); + } + data = (data << 1); + vram[1] = data; + + vram+=4; + } +} \ No newline at end of file diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..99ae9af --- /dev/null +++ b/src/main.c @@ -0,0 +1,134 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "defines.h" +#include "menus.c" +#include "fluid64x64.c" +#include "fluid128x64.c" + +int main(void) { + if(gint[HWCALC] == HWCALC_G35PE2) { + dtext_opt(64, 10, C_BLACK, C_WHITE, DTEXT_CENTER, DTEXT_TOP, "Cette calculatrice", -1); + dtext_opt(64, 25, C_BLACK, C_WHITE, DTEXT_CENTER, DTEXT_TOP, "est incompatible.", -1); + dtext_opt(64, 45, C_BLACK, C_WHITE, DTEXT_CENTER, DTEXT_TOP, "[EXE] Quitter", -1); + dupdate(); + getkey(); + return 1; + } + + fix (*buffers)[W*H] = (void *)0x88040000; // magic pointer to extra memory + fix (*buffersFS)[W2*H] = (void *)0x88040000; + dye = buffers[0]; // dye concentration + vx = buffers[1]; // fluid x velocity + vy = buffers[2]; // fluid y velocity + tx = buffers[3]; // temp buffer (used to advect vx & to compute div) + ty = buffers[4]; // temp buffer (used to advect vy & to compute p) + tz = buffers[5]; // temp buffer (used to advect dye) + + // Empty all buffers + memset(buffers, 0, W*H*sizeof(fix)*6); + + // Init timer + prof_init(); + prof_t prof = prof_make(); + uint32_t lastTime = 0, time = 0; + + // Init font + extern font_t myfont; + dfont(&myfont); + __printf_enable_fp(); + + main_menu(); + // settings_menu(true); + + /// Main Loop /// + bool fullscreen = false; + bool showFPS = true; + addX = W/2; + addY = H/2; + while (1) { + prof_enter(prof); // resume timer + + clearevents(); // Read all events + + if (fullscreen) { // fullscreen 128x64 simulation + fluid_step128x64(); + drect_border(0, 0, 127, H-1, C_NONE, 1, C_BLACK); + + // Handle fullscreen exit + if (keydown(KEY_MINUS) || keydown(K_PARAMS) || keydown(K_COLORS)) { + fullscreen = false; + memset(buffers, 0, W*H*sizeof(fix)*6); + + dye = buffers[0]; + vx = buffers[1]; + vy = buffers[2]; + tx = buffers[3]; + ty = buffers[4]; + tz = buffers[5]; + + dclear(C_WHITE); + if (keydown(K_PARAMS)) settings_menu(true); + } + } else { // halfscreen 64x64 simulation + fluid_step64x64(); + settings_menu(false); + + // Handle fullscreen enter + if (keydown(KEY_PLUS)) { + fullscreen = true; + currentView = 0; + memset(buffers, 0, W2*H*sizeof(fix)*6); + + dye = buffersFS[0]; + vx = buffersFS[1]; + vy = buffersFS[2]; + tx = buffersFS[3]; + ty = buffersFS[4]; + tz = buffersFS[5]; + + dclear(C_WHITE); + } + } + + // Simulation reset + if (keydown(K_RESET)) { + memset(buffers, 0, (fullscreen ? W2 : W)*H*sizeof(fix)*6); + } // Toggle FPS + else if (keydown(K_FPS)) { + showFPS = !showFPS; + } // Toggle help screen + else if (keydown(K_HELP) || keydown(KEY_EXIT)) { + int res = help_menu(); + currentView = 0; + if (res == -1) return 1; + } + + color_menu(); + + // Show FPS + if (showFPS) { + int ms = (time - lastTime)/1000; + sprintf(strFPS, "%dms / %dfps", (time - lastTime)/100, 1000/ms); + dtext_opt(66, 1, C_BLACK, C_WHITE, DTEXT_LEFT, DTEXT_TOP, strFPS, -1); + dline(64, 8, 127, 8, C_BLACK); + } + + // Update vram + dupdate(); + + // pause timer + prof_leave(prof); + lastTime = time; + time = prof_time(prof); + } + + return 1; +} \ No newline at end of file diff --git a/src/menus.c b/src/menus.c new file mode 100644 index 0000000..811b592 --- /dev/null +++ b/src/menus.c @@ -0,0 +1,321 @@ +char strFPS[20]; +char strDyeDiff[20]; +char strVelDiff[20]; +char strDyeForce[20]; +char strVelForce[20]; +char strRadius[20]; +char strPressureIter[20]; +char strEmptyTreshold[20]; +char strSaturateTreshold[20]; + +// Main Screen +void main_menu() { + const int gap = 8; + const int startY = 11; + dhline(5, C_BLACK); + dtext_opt(64, 2, C_BLACK, C_WHITE, DTEXT_CENTER, DTEXT_TOP, " CASIO FLUID SIMULATION ", -1); + dtext_opt(3, startY, C_BLACK, C_WHITE, DTEXT_LEFT, DTEXT_TOP, "Implementation of Jos Stam's", -1); + dtext_opt(3, startY + gap, C_BLACK, C_WHITE, DTEXT_LEFT, DTEXT_TOP, "\"Real Time Fluid Dynamics for", -1); + dtext_opt(3, startY + gap*2, C_BLACK, C_WHITE, DTEXT_LEFT, DTEXT_TOP, "games\" paper on monochrome", -1); + dtext_opt(3, startY + gap*3, C_BLACK, C_WHITE, DTEXT_LEFT, DTEXT_TOP, "Casio calculators", -1); + dtext_opt(3, startY + gap*4+2, C_BLACK, C_WHITE, DTEXT_LEFT, DTEXT_TOP, "[Optn] key displays the help", -1); + dtext_opt(124, 62, C_BLACK, C_WHITE, DTEXT_RIGHT, DTEXT_BOTTOM, "[EXE] Start", -1); + dupdate(); + dclear(C_WHITE); + + sleep_ms(500); + + while(1) { + clearevents(); + uint key = getkey().key; + if (key == KEY_EXE || key == K_HELP) break; + } +} + +// Controls/Help Menu +int help_menu() { + const int margin = 2; + + drect_border(margin, margin, 128 - margin-1, 64 - margin-1, C_WHITE, 1, C_BLACK); + dtext_opt(margin+2, margin+3+8*0, C_BLACK, C_WHITE, DTEXT_LEFT, DTEXT_TOP, "[Arrows]: Interact", -1); + dtext_opt(margin+2, margin+3+8*1, C_BLACK, C_WHITE, DTEXT_LEFT, DTEXT_TOP, "[Shift]: Toggle edit mode", -1); + dtext_opt(margin+2, margin+3+8*2, C_BLACK, C_WHITE, DTEXT_LEFT, DTEXT_TOP, "[Alpha]: Toggle color mode", -1); + dtext_opt(margin+2, margin+3+8*3, C_BLACK, C_WHITE, DTEXT_LEFT, DTEXT_TOP, "[Del]: Reset the simulation", -1); + dtext_opt(margin+2, margin+3+8*4, C_BLACK, C_WHITE, DTEXT_LEFT, DTEXT_TOP, "[+]/[-]: Toggle fullscreen", -1); + dtext_opt(margin+2, margin+3+8*5, C_BLACK, C_WHITE, DTEXT_LEFT, DTEXT_TOP, "[x]: Hide/Show FPS", -1); + dtext_opt(margin+2, margin+3+8*6, C_BLACK, C_WHITE, DTEXT_LEFT, DTEXT_TOP, "[Exit]: Quit [EXE]: Continue", -1); + dupdate(); + dclear(C_WHITE); + + while(1) { + uint key = getkey().key; + if (key == KEY_EXIT) return -1; + if (key == KEY_EXE || key == K_HELP) return 0; + } + // if (getkey().key == KEY_EXIT) return -1; + // return 0; +} + +// Fluid Simulation Settings Menu +void settings_menu(bool redraw) { + static int selected = 0; + const int menuX = 66; + const int menuY = 2; + const int gap = 8; + const int rx = 64; + const bool activated = redraw; + bool updateRect = false, updateDyeD = activated, updateVelD = activated, updateDyeF = activated, updateVelF = activated, updateRadius = activated, updatePressureIter = activated; + static bool releaseParams = true; + + if (currentView == 1 && releaseParams && keydown(K_PARAMS)) { + releaseParams = false; + currentView = 0; + int ry = menuY+gap*(selected+1)+2; + drect(rx, ry, rx, ry+2, C_WHITE); + drect_border(63, 0, 127, H-1, C_NONE, 1, C_WHITE); + } + else if (currentView != 1 && releaseParams && keydown(K_PARAMS)) { + releaseParams = false; + currentView = 1; + updateRect = true; + updateDyeD = true, updateVelD = true, updateDyeF = true, updateVelF = true, updateRadius = true, updatePressureIter = true; + drect(64, 0, 128, 64, C_WHITE); + drect_border(0, 0, W-1, H-1, C_NONE, 1, C_WHITE); + } else if (!keydown(K_PARAMS)) { + releaseParams = true; + } + + if (currentView == 0) { + drect_border(0, 0, W-1, H-1, C_NONE, 1, C_BLACK); + if (!redraw) return; + } else if (currentView == 2) return; + { + drect_border(0, 0, W-1, H-1, C_NONE, 1, C_WHITE); + drect_border(63, 0, 127, H-1, C_NONE, 1, C_BLACK); + } + + if (keydown(KEY_LEFT)) { + if (selected == 0 && dyeDiffusion > 0) { + dyeDiffusion -= PREC_STEP; + updateDyeD = true; + } + else if (selected == 1 && velDiffusion > 0) { + velDiffusion -= PREC_STEP; + updateVelD = true; + } + else if (selected == 2 && dyeIntensity > 0) { + dyeIntensity -= PREC_STEP; + updateDyeF = true; + } + else if (selected == 3 && velIntensity > 0) { + velIntensity -= PREC_STEP; + updateVelF = true; + } + else if (selected == 4 && radius > ONE) { + radius -= FIX(1); + updateRadius = true; + } + else if (selected == 5 && pressureIterations > 0) { + pressureIterations--; + updatePressureIter = true; + } + } + else if (keydown(KEY_RIGHT)) { + if (selected == 0 && dyeDiffusion < ONE) { + dyeDiffusion += PREC_STEP; + updateDyeD = true; + } + else if (selected == 1 && velDiffusion < ONE) { + velDiffusion += PREC_STEP; + updateVelD = true; + } + else if (selected == 2 && dyeIntensity < ONE*2) { + dyeIntensity += PREC_STEP; + updateDyeF = true; + } + else if (selected == 3 && velIntensity < ONE*2) { + velIntensity += PREC_STEP; + updateVelF = true; + } + else if (selected == 4 && radius < ONE*10) { + radius += FIX(1); + updateRadius = true; + } + else if (selected == 5) { + pressureIterations++; + updatePressureIter = true; + } + } + else if (keydown(KEY_UP)) { + int ry = menuY+gap*(selected+1)+2; + drect(rx, ry, rx, ry+2, C_WHITE); + if (--selected < 0) selected = 5; + updateRect = true; + } + else if (keydown(KEY_DOWN)) { + int ry = menuY+gap*(selected+1)+2; + drect(rx, ry, rx, ry+2, C_WHITE); + if (++selected > 5) selected = 0; + updateRect = true; + } + + if (updateDyeD) { + sprintf(strDyeDiff, "dye diff:%.3f ", fixtof(dyeDiffusion)); + dtext_opt(menuX, menuY + gap, C_BLACK, C_WHITE, DTEXT_LEFT, DTEXT_TOP, strDyeDiff, -1); + } + if (updateVelD) { + sprintf(strVelDiff, "vel diff:%.3f ", fixtof(velDiffusion)); + dtext_opt(menuX, menuY + gap*2, C_BLACK, C_WHITE, DTEXT_LEFT, DTEXT_TOP, strVelDiff, -1); + } + if (updateDyeF) { + sprintf(strDyeForce, "dye force:%.1f ", fixtof(dyeIntensity)); + dtext_opt(menuX, menuY + gap*3, C_BLACK, C_WHITE, DTEXT_LEFT, DTEXT_TOP, strDyeForce, -1); + } + if (updateVelF) { + sprintf(strVelForce, "vel force:%.1f ", fixtof(velIntensity)); + dtext_opt(menuX, menuY + gap*4, C_BLACK, C_WHITE, DTEXT_LEFT, DTEXT_TOP, strVelForce, -1); + } + if (updateRadius) { + sprintf(strRadius, "radius: %d ", UNFIX(radius)); + dtext_opt(menuX, menuY + gap*5, C_BLACK, C_WHITE, DTEXT_LEFT, DTEXT_TOP, strRadius, -1); + } + if (updatePressureIter) { + sprintf(strPressureIter, "iterations: %d ", pressureIterations); + dtext_opt(menuX, menuY + gap*6, C_BLACK, C_WHITE, DTEXT_LEFT, DTEXT_TOP, strPressureIter, -1); + } + if (updateRect) { + int ry = menuY+gap*(selected+1)+2; + drect(rx, ry, rx, ry+2, C_BLACK); + } +} + +// Fluid color settings Menu +void color_menu() { + const int xmargin = 3; + const float fw = 64. - (float)xmargin * 2.; + const int y = 22; + const int h = 5; + const int bar_overflow = 3; + const int STEP_MULT = 20; + + const float tresh1 = fixtof(emptyTreshold); + const float tresh2 = fixtof(saturateTreshold); + const int x1 = 64 + xmargin + (int)(tresh1 * fw); + const int x2 = 64 + xmargin + (int)(tresh2 * fw); + + static bool x1Selected = true; + static bool releaseKey = true; + bool updated = false; + + // Manage keys + if (keydown(K_COLORS) && releaseKey) { + releaseKey = false; + if (currentView == 2) { + currentView = 0; + drect_border(63, 0, 127, H-1, C_NONE, 1, C_WHITE); + } + else { + currentView = 2; + updated = true; + } + } else if (!keydown(K_COLORS) && !releaseKey) { + releaseKey = true; + } + + if (currentView != 2) return; + + if (keydown(KEY_F1)) { + emptyTreshold -= PREC_STEP * STEP_MULT; + if (emptyTreshold < 0) emptyTreshold = 0; + + x1Selected = true; + updated = true; + } + else if (keydown(KEY_F2)) { + emptyTreshold += PREC_STEP * STEP_MULT; + if (emptyTreshold > ONE) emptyTreshold = ONE; + if (emptyTreshold > saturateTreshold) saturateTreshold = emptyTreshold; + + x1Selected = true; + updated = true; + } + else if (keydown(KEY_F3)) { + saturateTreshold -= PREC_STEP * STEP_MULT; + if (saturateTreshold < 0) saturateTreshold = 0; + if (saturateTreshold < emptyTreshold) emptyTreshold = saturateTreshold; + + x1Selected = false; + updated = true; + } + else if (keydown(KEY_F4)) { + saturateTreshold += PREC_STEP * STEP_MULT; + if (saturateTreshold > ONE) saturateTreshold = ONE; + + x1Selected = false; + updated = true; + } + else if (keydown(KEY_F5)) { + fix diff = saturateTreshold - emptyTreshold; + + emptyTreshold -= PREC_STEP * STEP_MULT; + if (emptyTreshold < 0) emptyTreshold = 0; + saturateTreshold = emptyTreshold + diff; + + x1Selected = true; + updated = true; + } + else if (keydown(KEY_F6)) { + fix diff = saturateTreshold - emptyTreshold; + + saturateTreshold += PREC_STEP * STEP_MULT; + if (saturateTreshold > ONE) saturateTreshold = ONE; + emptyTreshold = saturateTreshold - diff; + + x1Selected = false; + updated = true; + } + + if (updated) { + // Clear + drect(64, 0, 128, 64, C_WHITE); + drect_border(0, 0, W-1, H-1, C_NONE, 1, C_WHITE); + + // Draw selectors + if (x1Selected) { + drect(x1-1, y - bar_overflow-1, x1+1, y+h+bar_overflow+1, C_BLACK); + dline(x2, y - bar_overflow, x2, y+h+bar_overflow, C_BLACK); + } else { + dline(x1, y - bar_overflow, x1, y+h+bar_overflow, C_BLACK); + drect(x2-1, y - bar_overflow-1, x2+1, y+h+bar_overflow+1, C_BLACK); + } + + // Draw white band + drect(64 + xmargin, y, x1, y + h, C_WHITE); + + // Draw checker band + for (int j = y; j <= y+h; j++) { + dline(x1, j, x2, j, j%2 ? C_BLACK : C_WHITE); + } + for (int i = x1; i < x2; i+=2) { + dline(i, y, i, y+h, C_BLACK); + } + + // Draw black band + drect(x2, y, 128 - xmargin, y + h, C_BLACK); + + // Draw border + drect_border(64 + xmargin-1, y-1, 128 - xmargin+1, y+h+1, C_NONE, 1, C_BLACK); + + // Draw float values + sprintf(strEmptyTreshold, "%.2f", tresh1); + sprintf(strSaturateTreshold, "%.2f", tresh2); + dtext_opt(x1, y - bar_overflow - 1, C_BLACK, C_WHITE, DTEXT_CENTER, DTEXT_BOTTOM, strEmptyTreshold, -1); + dtext_opt(x2, y + h + bar_overflow + 1, C_BLACK, C_WHITE, DTEXT_CENTER, DTEXT_TOP, strSaturateTreshold, -1); + + dtext_opt(65, 40, C_BLACK, C_WHITE, DTEXT_LEFT, DTEXT_TOP, "[F1/F2] Lower", -1); + dtext_opt(65, 48, C_BLACK, C_WHITE, DTEXT_LEFT, DTEXT_TOP, "[F3/F4] Upper", -1); + dtext_opt(65, 56, C_BLACK, C_WHITE, DTEXT_LEFT, DTEXT_TOP, "[F5/F6] Both", -1); + } + + drect_border(63, 0, 127, H-1, C_NONE, 1, C_BLACK); +} \ No newline at end of file