-
SurveyJS
Open-Source JSON Form Builder to Create Dynamic Forms Right in Your App. With SurveyJS form UI libraries, you can build and style forms in a fully-integrated drag & drop form builder, render them in your JS app, and store form submission data in any backend, inc. PHP, ASP.NET Core, and Node.js.
If you want more performance, falling sand simulators can further be made parallel by implementing them using Margolus Neighbourhoods, as in Falling Turnip: https://github.com/tranma/falling-turnip
The idea is that a single iteration divides the world into 2x2 squares and then applies effects sequentially within each square, but not between the squares. This means each square can be processed independently. In the next iteration, the division into squares shifts right and down by one cell each direction. This does mean you need more steps than in a sequential implementation, but I found it to be quite a principled approach to parallelizing cellular automata when I first read about it. One interesting side effect of this design is that falling particles end up being separated by blank space, as shown here: https://futhark-lang.org/static/falling-sand-2016.12.04.webm I wonder if that is fixable.
Another way to do this (albeit without simple support for fluids) is to use the Moore neighborhood and use a left and right "bias" to decide which direction a grain should fall towards if it can't fall straight down. This works pretty well as a shader, and has the same side effect with the horizontal lines.
https://github.com/ericleong/sand.js
Typically a cellular automata simulation will have some edge condition like wrapping or mirroring an adjacent cell.
A nice optimization trick is to make the cell buffers 2 cells wider and taller (or two times whatever the neighborhood radius is), and then before each generation you update the "gutter" by copying just the wrapped (or mirrored) pixels. Then your run the rule on the inset rectangle, and the code (in the inner loop) doesn't have to do bounds checking, and can assume there's a valid cell to read in all directions. That saves a hell of a lot of tests and branches in the inner loop.
Also, the Margolus neighborhood can be defined in terms of the Moore neighborhood + vertical phase (even/odd row) + horizontal phase (even/odd column) + time phase (even/odd time). Then you can tell if you're at an even or odd step, and which of the four squares of the grid you're in, to know what to do.
That's how the CAM6 worked in hardware: it used the x/y/time phases as additional bits of the index table lookup.
https://github.com/SimHacker/CAM6/blob/master/javascript/CAM...
Here's how my CAM6 emulator computes the Margolus lookup table index, based on the 9 Moore neighbors + phaseTime, phaseX, and phaseY:
function getTableIndexUnrotated(