From 496943a5bd5827aa88ca1d5468f57cacfb65c697 Mon Sep 17 00:00:00 2001 From: indiana-dev Date: Wed, 26 Oct 2022 18:07:04 +0200 Subject: [PATCH] initial commit --- .gitignore | 13 ++ CMakeLists.txt | 50 ++++++ assets-cg/example.png | Bin 0 -> 3816 bytes assets-cg/fxconv-metadata.txt | 3 + assets-cg/icon-sel.png | Bin 0 -> 8388 bytes assets-cg/icon-uns.png | Bin 0 -> 4629 bytes assets-fx/example.png | Bin 0 -> 3569 bytes assets-fx/fxconv-metadata.txt | 11 ++ assets-fx/icon.png | Bin 0 -> 7494 bytes assets-fx/myfont.png | Bin 0 -> 16578 bytes src/defines.h | 85 +++++++++ src/fluid128x64.c | 196 +++++++++++++++++++++ src/fluid64x64.c | 176 +++++++++++++++++++ src/main.c | 134 ++++++++++++++ src/menus.c | 321 ++++++++++++++++++++++++++++++++++ 15 files changed, 989 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 assets-cg/example.png create mode 100644 assets-cg/fxconv-metadata.txt create mode 100644 assets-cg/icon-sel.png create mode 100644 assets-cg/icon-uns.png create mode 100644 assets-fx/example.png create mode 100644 assets-fx/fxconv-metadata.txt create mode 100644 assets-fx/icon.png create mode 100644 assets-fx/myfont.png create mode 100644 src/defines.h create mode 100644 src/fluid128x64.c create mode 100644 src/fluid64x64.c create mode 100644 src/main.c create mode 100644 src/menus.c 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 0000000000000000000000000000000000000000..8826800383eeae92dade9545feaeb68faf0720f2 GIT binary patch literal 3816 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 eRZ5JQiUR=bBzf^!G-(0=0000 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 ag#Q8hHNm}t`+-sb0000 zaB^>EX>4U6ba`-PAZ2)IW&i+q+U=Q1b|gEFMdw^aErBH1E{9-qPVFF%Ir$HAB8KVSRx`CJP=-Jc(? z@cJ3t*BcsNw2+OR*FP32?@t!`&5e-1O_2Bg@$1HQyf==&Sk%QI|Mk^A z|DUtkIr}-?NCh{P=TOfU?qeASDqfO1t?;h=&wQ`<-FY`Z{0zx2KfUsvp@$gQJfyH( z7~zB+7N0XrY)o;-%4du-%DvX4#vZ4536ks`POQ>pNs}luZV^dN;@g&R_w9GT85%pU zz|a_2EHLFy-|nvl{GInX)8nHM%$XXmudpjx5@3c!&VDkCfQ0jkTfPqdc)wrnOJIcv z=IiFh0f*OT3C-a%x8x>(FfQ}?uAza%dNE9TI+4}*oBhJz4Y2!?|lsR9LbG5m^~OV zhDH@lnzd-vrd`J*eP)_@mRV<;eU25EthDkftFE^C8ar%K$?dfBF1zlw`yPi}J95&= zr<{7)>1Vv7_KNBs-v1zK;T5%bBW2_2J8E35)fT!-1jCtZF(YCz1tMN80wi>_nE4h` ziWWJGneUOR$Ppv6#byORTquMv?GW<`@7R4t?r+D<;p@MOTl_nba~8V)K;)v^@-1$^ ziP|zGoHt@m6`D`IP<@OUvGEUQTRyRO8&cMotFeqwm@DlSQun3oRKuz1oR)pm5!a$X zho zweN3-nIp>7wvTz?+d%XR%ncoyLauQBfXAV?16eH?E;Pnh2$L@g1AZ{ytOK>2!} z-=4Tr%S_8G18_Yw@`p^OPc+b&j6Zo-iP#1$$fSyoVbQn_wYG4 zrk@KqxM)JCDJLtOfA+`2Dq_7u`W?}vYDF7aNuV5 zf+$FtAz`+qMb6yh%C3VQ)=8xu8TqNjc0F1V9VMeo2v1*Ne|P;li~~sOyQcNHFC5uj z+Y;XhBj7kY*kZz})_$0HR?$sKY)O@sZqd$@Z4F^One{sngDT+gt^@(xOLO}}5Rp%)|+m0PCOt?Tt>4=}@ z3m$28-D8D4j9(U#mv6lh{%2c@h{pO6q{otv3mq1TRBM~RnEG{&`@;Bu{l%`!kE&EFr=K?R%xr@y^5l0s&14r;d>~lw@r?WRniI_rYDyTB$lvz zI!gwOwIK44c50%Es$H!^+HfJ^x6D3%lQov~fLDGpcK~OYr5*OEkTkprP2y*ivtFWT z{>H7L;nG(3^X-e?qt&~n!uc4vP!Mlo9Q9ZmLE+9Gq7Y2 zs3tJd^Bql52%hG!_)#AP~`<>lYos# z1S$mF~QCPuqCQ=5D5y}u*9YTC55gaB==Jh{?i(CKNaCWtwHxw z5&pgg(L_L6szoRQ+L298p(H?Y=O=oTiaY~yHl2cli^ki7P>!0OjGH##RR7#C5ZLeN>)xCG+&Nm%y zY_kS3(-`A#E$;5>V`kJ1$DaU$^?+&P#SV5^}eLni^i-J8$m=U?^P0X1>vsX3WH#1QR+}$&F5v z)Ta_HhV2!~&O#X`o7$XdB?oRM)}Ek~uTrb|J@Ali!x!7ElM z&CV?J5RLk1V+Z&IpMfQXumcLl+od^@lu_Uo2@W9C$wmOpp*J`FrrRc$vSh*!n9?v& zQ7M5er+^mawk$jN>W~PPy{ST|f)_Q*|VNL+rT8#y6rs;4(V`M>XH)3ngMTg_yFkJ_Auf+li-k2F2K$JB8|tE z4R`^4Q3@Trb_4pYy`bGUT`QDJcs0-#(HwFa#6`JlYu1rlLh4Xj8rTx|YvKH0cmP@8 z=~*~M_L^yZM}TOjq=YjNgR|0z%;Gy009ZL>ss_&j9OWhF(uX!PS%zB5#>0Nu)8cU2 z)tDa$O9DU0(cBAV!_U}!O#6r9fz8|M-MVZZk;_2&==2=0@+Y?g_!Y6D&-4h@OM> zL5n0o7l%m^t$1D4yPwjK7B-GMRFJnW?LKEh*^`9mBiusr;};UMn_i!lE@sVYQU?xv zlxghtgpZI@w!f35?%$`zhZ)^JrpB6gBDW(dtI>Xl(&-}3TqE78@2oL5GS5O{e;Xj# z>`DC96JH|~peo7)T7 zm(>n6((%`2{eKvMu02poa5{@jy8=BRyX}EV&(r!{l%+k-z@`l19(QD-_mDaLwS*aT zr0Hq{xJjqkd{Vn|@H+3K1beb^LIy=)HfA$@JKJl4q7Z}jCU<&ncRFHQXsAD@d)g9Y z_9Dgm^E4%65y@^WGUPoSTSL6`-uE?tfXutmi}A6+9=PD!@5>Bv?`7(18Okzz^}>r1 z(t0maaKmef-98L)_Ncm2;`m|y>W8by?tFW8bIo`gfin6$N_w6{hS(pm@;c8i4uuKe zzz>JM{AuE(3iQMW^5l-H$YV$ZYHeHfZr8_xYV#Q@1f@}-4X940FDHv)6CS{&4 zHqd>Jyix~gM;g3SBH*RWEXr_K4@)bx5zK9*_I+ggI&yOL6_oEc9wYiSqE!K3JgRmv!fYs6+3b-{` zm+sY-QV>(Yn#CwaWjnP(X_!V?fEAQNzR@Ghk)Z(mS{nSmbj)OboWN)B(YC&ylt9q< z*PkW+x1a9cUEu#0`oFLch=>LS7XAnQTdXQe8tq&F000SaNLh0L01FcU01FcV0GgZ_ z00007bV*G`2jd106DSx@HxUjR55_;C2hJY6wFkX(BjJGm1n~lakQhQ*A;E~@;;$e?ASo?Y-Dan=TMuI< z<8HgVn%P>)d#Bmiwsb%FzVCZ|Or+6hAc#Lh0R#XApa2w!f!$j*LQX1YC=xk5EqlC^ z+7v68FQ`qu|B`LnilXp^ZQI_jH|tc7V!klzRPK}C7#xhC#0Sy)x6Wd(e_VUoD6877e~hlH*V^z|lUv5DcEmwS4= z(&xj`NvT>rsebn$!V!o>G^}}ma1+6*>H)${B(7_6q5L$>!_)F{rSk5}fsd!`mrAud zwLG=WpPvz7mmXr_&(DakOAoQ|=VwINWf)?iw*A8-?~jgrpp?H=2@RCJTp*lF5WR?8 zg6Ku$5{6#Pdc9ORlU9AWKtxaR)r-SZeD&h+lrZ!nxg-bll%_80p^>I8D_tXDK?&$7 zqF91>iYS&)Jw-m0rk>(06St>$%f#&|oerg`r+CZculkgxp3=Ecx}#5#45e%Ols0+F zx$9Hf;VICkbZwu4rSvI;5{4!_br&JDxVpv`jy2z*mgU$KV_|tk^pqzLA0UM0mR`0E zigR@EVr4C=X~AXU(97FG0U zJsO?Q=h$88bSNQDA3u@8*w3mD!nT$Bw8GMg$?m7uj4sBsw7~g(r>}W0?%Q#gd56Yev)Q-D1(5I|!lOVsSp7 zn_GJ6=xCY6h;&BACpsm4y!Fy5aojbiRVo=K`S@msbK!!L#X@rWwCYpbRYt5qj7ZZA z;tnEi#&vBpozCX+EKQ`d2rGRY8rj}>~PubkC>-!X}D8^Z3_-e`r%5W-`?Cm}N{OOY4&%*QGrzCW}e0Y%R@4t0@ z$Y*qb5ULTfyuMy26u-Jov$(^usj6Vh4th#2;3+@>y`^E5P)oW@h6a*%(wY1Bru+{A zLLfLt3s3+GKmjNK1)u;FfC5ke3P1rU00jb|02F`%Pyh-*`JeF%0p|(b4g|HY00000 LNkvXXu0mjfMjhGA literal 0 HcmV?d00001 diff --git a/assets-fx/example.png b/assets-fx/example.png new file mode 100644 index 0000000000000000000000000000000000000000..b26ba9ac37f37f0bbc06db263613c33184d47a12 GIT binary patch literal 3569 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 QtpET307*qoM6N<$g8Ni@KmY&$ literal 0 HcmV?d00001 diff --git a/assets-fx/myfont.png b/assets-fx/myfont.png new file mode 100644 index 0000000000000000000000000000000000000000..bb877fea6a7c4d28531ec07ef26b58de20e259b8 GIT binary patch literal 16578 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 FV1gkEw^0B9 literal 0 HcmV?d00001 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