reFlow
- 2 Devlogs
- 3 Total hours
a in-browser 3d CFD simulation site that I made on a whim
a in-browser 3d CFD simulation site that I made on a whim
reFlow Devlog #2 - moar suffering!!!!
round two. we are back. the wind tunnel is no longer just a 3d playground; it’s a quantum control deck. here is how i spent the last few hours losing sleep over perspective cameras, slice projection math, and custom state toggle controls.
hour 7: the flatland incursion (toggling 2d slice mode)
why turn a 3d tunnel back to 2d? because aerodynamics is all about cross-sections.
how to constrain a 3d particle swarm to 2d without a total refactor: when “is2D” is true, we hijack the particle spawner. instead of spreading particles across x-axis depth, we force positions[idx] = (Math.random() - 0.5) * 0.1, wrapping the flow into a super thin sheet at x = 0.
we also restricted the smoke line emitters to a vertical slice. the result looks like a clean laser-cut sheet of wind passing through the wing. (not really, but it’s the thought that counts)
hour 8: camera capture and control hijacking
particles are in 2d, but the camera still rotates in 3d. rotating it breaks the illusion (just a flat sheet of dots).
solution: camera hijacking.
when 2d mode triggers, we fly the camera to (38, 0, 0) looking down the x-axis at the wing. we disable OrbitControls rotation (controls.enableRotate = false) and force camera.up.set(0, 1, 0).
debugging this was a nightmare—OrbitControls flips the camera if target/up vectors aren’t updated first. now, swapping dimensions is smooth.
hour 9: freezing time (the pause engine)
sometimes wind moves too fast to see gradients. we need a freeze-frame.
added “isPaused” state.
first try: skip the animate loop. but that froze the rendering, stopping camera rotation.
second try: keep the rendering loop active, but bypass particle position updates.
now you can pause, orbit in 3d, and inspect smoke streams and vectors suspended in amber. sci-fi vibes.
hour 10: tuning the storm (particle customizability)
default 6,000 particles is cool, but my GPU was barely breaking a sweat.
expanded particle count up to 20,000 (step 1000) and added a size slider (0.1 to 1.5).
adjusting particle size from tiny dust specks to fat plasma spheres is satisfying. setting count to 20,000 makes the wind tunnel look like a dense gas nebula. the cpu is sweating, but performance is smooth thanks to typed buffer array updates.
hour 11: cyberpunk telemetry deck (hud overhaul)
with new toggleable states (2d/3d and pause/play), the dashboard needed a facelift.
built a cyber-cyan “Switch 2D” button with custom ambient glow shadows (shadow-[0_0_15px_rgba(125,249,255,0.15)]) and a yellow pause button changing to play icon.
added a footer telemetry status pill showing “2D Slice | [Active Object]” vs “3D Space | [Active Object]”. makes the app feel like a high-budget engineering interface.
hour 12: collision math strikes back (local normals)
particles clipped through spherical and box obstacles due to lazy velocity mirroring.
introduced a robust getNormalLocal(type, x, y, z) helper.
when a particle collides with a sphere, we calculate the exact radial normal: (x/r, y/r, z/r). for box geometry, the helper evaluates which face was breached (min === dx logic) and returns an axis normal.
now particles bounce and slide off physical bodies with realistic deflection vectors. no more ghosting.
next up: actually making the visuals work (someone send help)
reFlow Devlog 1 - building a 3d wind tunnel without losing my mind (?)
enjoy my suffering.
hour 1: the “it’s not that hard” phase
decided on: react, three.js, and tailwind css.
getting three.js to render took half an hour because i forgot lights. stared at a pitch black void wondering why my life is like this. finally got a grey cube spinning. les go spinny coob
hour 2: wikipedia math is scary
goal: make an airfoil (wing shape).
searched “naca airfoil math formula” and got hit with an abomination of an equation: yt = 5 * t * (0.2969 * sqrt(x) - 0.1260 * x - 0.3516 * x^2 + 0.2843 * x^3 - 0.1015 * x^4). wouldn’t have signed up if i knew math was involved.
translated it into a javascript loop generating a THREE.Shape. my eyes watered staring at parentheses. messed up the loop direction 3 times and made twisted metal instead of a wing. had to scale it up cuz default was microscopic. after 4 tries, it looks like a wing. LETS GOOO
hour 3: the wind has to blow
made a particle system with 6,000 points. spawn at front, move back, respawn.
easy, but particles must bend around the wing. wrote a collision loop.
first draft: particles got stuck, formed a massive blue blob.
second draft: calculated slope. particles flew off at 90-degree angles.
coded a simplified double-flow vector calc. not real CFD (my laptop would explode running Navier-Stokes), but it looks real.
hour 4: adding cool dashboards
app needed to look like a telemetry deck. added:
sidebar with sliders for viscosity, inlet speed, angle of attack.
custom html5 canvas real-time graph. draws lift (cyan) and drag (orange) lines. legit math at home guys.
pressure heatmap. flat vertical plane in center. slow air hitting nose = high pressure (RED). fast air over top = low pressure (NEON GREEN). tilting the wing shifts the red spot. ASMR for the eyes wooooo
hour 5: stall warning and ai pilot
added stall physics! over 15deg tilt, lift cuts by 70% and drag multiplies. added a flashing CRITICAL STALL WARNING banner.
integrated Gemini API for aero reports then realized this comp requires no auth, so api keys are prolly an issue.
wrote a local backup expert system. no API key = runs generateLocalReport() with rule-based templates. 100% plug-and-play.
hour 6: single-file challenge
wanted app super portable. one .html file.
vite/tailwind output to .js and .css. wrote bundle.js to inline them which ended up giving
“ReferenceError: require is not defined in ES module scope.” bruh. renamed it to bundle.cjs and that worked yay
btw throwing html into google AI studio gets a free hosted site?? the more you know i guess
lots of math still broken lol.
coolest project i’ve built though. going to sleep for a week.