-
Notifications
You must be signed in to change notification settings - Fork 11
/
Copy pathasciiFluidSimulation.c
235 lines (196 loc) · 9.31 KB
/
asciiFluidSimulation.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
// this de-obfuscated version by Davide Della Casa
// original obfuscated version by Yusuke Endoh
// compile like so:
// gcc asciiFluidSimulation.c -o asciiFluidSimulation
#include <stdio.h>
#include <unistd.h>
#include <complex.h>
#include <math.h>
#define _POSITION +0
#define _WALLFLAG +1
#define _DENSITY +2
#define _FORCE +3
#define _VELOCITY +4
#define _NEXTPARTICLE +5
#define PARTICLE 5*
#define NEXTSCREENROW 80+
#define CONSOLE_WIDTH 80
#define CONSOLE_HEIGHT 24
double complex particles[CONSOLE_WIDTH * CONSOLE_HEIGHT * 2 * 5], sandboxAreaScan = 0 /* 0 is top-left */, particlesDistance, particlesInteraction;
int x, y, screenBufferIndex, totalOfParticles;
int gravity = 1, pressure = 4, viscosity = 7;
char screenBuffer[CONSOLE_WIDTH * CONSOLE_HEIGHT + 1];
int main(){
// terminal escape code to clear the screen
puts("\x1b[2J");
// read the input file to initialise the particles.
// # stands for "wall", i.e. unmovable particles (very high density)
// any other non-space character represents normal particles.
int particlesCounter = 0;
while ((x = getc(stdin)) != EOF) {
switch ( x ) {
case '\n':
// next row, going down the real part increases
// rewind the complex part too so particle is at the left
sandboxAreaScan = creal(sandboxAreaScan) + 2 + _Complex_I;
break;
case ' ':
break;
case '#':
// The character # represents “wall particle” (a particle with fixed position),
// and any other non-space characters represent free particles.
// A wall sets the flag on 2 particles side by side.
particles[PARTICLE particlesCounter _WALLFLAG] = particles[PARTICLE particlesCounter _NEXTPARTICLE _WALLFLAG] = 1;
default:
// Each non-empty character sets the position of two
// particles one below the other (real part is rows)
// i.e. each cell in the input file corresponds to 1x2 particle spaces,
// and each character sets two particles
// one on top of each other.
// It's as if the input map maps to a space that has twice the height, as if the vertical
// resolution was higher than the horizontal one.
// This is corrected later, see "y scale correction" comment.
// I think this is because of gravity simulation, the vertical resolution has to be
// higher, or conversely you can get away with simulating a lot less of what goes on in the
// horizontal axis.
particles[PARTICLE particlesCounter _POSITION] = sandboxAreaScan;
particles[PARTICLE particlesCounter _NEXTPARTICLE _POSITION] = sandboxAreaScan + 1;
// we just added two particles
totalOfParticles = particlesCounter += 2;
}
// next column, going to the right the complex part decreases
sandboxAreaScan = sandboxAreaScan - _Complex_I;
}
while (1) {
int particlesCursor, particlesCursor2;
// Iterate over every pair of particles to calculate the densities
for (particlesCursor = 0; particlesCursor < totalOfParticles; particlesCursor++){
// density of "wall" particles is high, other particles will bounce off them.
particles[PARTICLE particlesCursor _DENSITY] = particles[PARTICLE particlesCursor _WALLFLAG] * 9;
for (particlesCursor2 = 0; particlesCursor2 < totalOfParticles; particlesCursor2++){
particlesDistance = particles[PARTICLE particlesCursor _POSITION] - particles[PARTICLE particlesCursor2 _POSITION];
particlesInteraction = cabs(particlesDistance) / 2 - 1;
// this line here with the alternative test
// works much better visually but breaks simmetry with the
// next block
//if (round(creal(particlesInteraction)) < 1){
// density is updated only if particles are close enough
if (floor(1.0 - creal(particlesInteraction)) > 0){
particles[PARTICLE particlesCursor _DENSITY] += particlesInteraction * particlesInteraction;
}
}
}
// Iterate over every pair of particles to calculate the forces
for (particlesCursor = 0; particlesCursor < totalOfParticles; particlesCursor++){
particles[PARTICLE particlesCursor _FORCE] = gravity;
for (particlesCursor2 = 0; particlesCursor2 < totalOfParticles; particlesCursor2++){
particlesDistance = particles[PARTICLE particlesCursor _POSITION] - particles[PARTICLE particlesCursor2 _POSITION];
particlesInteraction = cabs(particlesDistance) / 2 - 1;
// force is updated only if particles are close enough
if (floor(1.0 - creal(particlesInteraction)) > 0){
particles[PARTICLE particlesCursor _FORCE] += particlesInteraction * (particlesDistance * (3 - particles[PARTICLE particlesCursor _DENSITY] - particles[PARTICLE particlesCursor2 _DENSITY]) * pressure + particles[PARTICLE particlesCursor _VELOCITY] *
viscosity - particles[PARTICLE particlesCursor2 _VELOCITY] * viscosity) / particles[PARTICLE particlesCursor _DENSITY];
}
}
}
// empty the buffer
for (screenBufferIndex = 0; screenBufferIndex < CONSOLE_WIDTH * CONSOLE_HEIGHT; screenBufferIndex++){
screenBuffer[screenBufferIndex] = 0;
}
for (particlesCursor = 0; particlesCursor < totalOfParticles; particlesCursor++) {
if (!particles[PARTICLE particlesCursor _WALLFLAG]) {
// This is the newtonian mechanics part: knowing the force vector acting on each
// particle, we accelerate the particle (see the change in velocity).
// In turn, velocity changes the position at each tick.
// Position is the integral of velocity, velocity is the integral of acceleration and
// acceleration is proportional to the force.
// force affects velocity
if (cabs(particles[PARTICLE particlesCursor _FORCE]) < 4.2)
particles[PARTICLE particlesCursor _VELOCITY] += particles[PARTICLE particlesCursor _FORCE] / 10;
else
particles[PARTICLE particlesCursor _VELOCITY] += particles[PARTICLE particlesCursor _FORCE] / 11;
// velocity affects position
particles[PARTICLE particlesCursor _POSITION] += particles[PARTICLE particlesCursor _VELOCITY];
}
// given the position of the particle, determine the screen buffer
// position that it's going to be in.
x = particles[PARTICLE particlesCursor _POSITION] * _Complex_I;
// y scale correction, since each cell of the input map has
// "2" rows in the particle space.
y = particles[PARTICLE particlesCursor _POSITION] / 2;
screenBufferIndex = x + CONSOLE_WIDTH * y;
// if the particle is on screen, update
// four buffer cells around it
// in a manner of a "gradient",
// the representation of 1 particle will be like this:
//
// 8 4
// 2 1
//
// which after the lookup that puts chars on the
// screen will look like:
//
// ,.
// `'
//
// With this mechanism, each particle creates
// a gradient over a small area (four screen locations).
// As the gradients of several particles "mix",
// (because the bits are flipped
// independently),
// a character will be chosen such that
// it gives an idea of what's going on under it.
// You can see how corners can only have values of 8,4,2,1
// which will have suitably "pointy" characters.
// A "long vertical edge" (imagine two particles above another)
// would be like this:
//
// 8 4
// 10 5
// 2 1
//
// and hence 5 and 10 are both vertical bars.
// Same for horizontal edges (two particles aside each other)
//
// 8 12 4
// 2 3 1
//
// and hence 3 and 12 are both horizontal dashes.
// ... and so on for the other combinations such as
// particles placed diagonally, where the diagonal bars
// are used, and places where four particles are present,
// in which case the highest number is reached, 15, which
// maps into the blackest character of the sequence, '#'
if (y >= 0 && y < CONSOLE_HEIGHT - 1 && x >= 0 && x < CONSOLE_WIDTH - 1) {
screenBuffer[screenBufferIndex] |= 8; // set 4th bit to 1
screenBuffer[screenBufferIndex+1] |= 4; // set 3rd bit to 1
// now the cell in row below
screenBuffer[NEXTSCREENROW screenBufferIndex] |= 2; // set 2nd bit to 1
screenBuffer[NEXTSCREENROW screenBufferIndex+1] |= 1; // set 1st bit to 1
}
}
// Update the screen buffer
for (screenBufferIndex = 0; screenBufferIndex < CONSOLE_WIDTH * CONSOLE_HEIGHT; screenBufferIndex++){
if ( screenBufferIndex % CONSOLE_WIDTH == CONSOLE_WIDTH - 1)
screenBuffer[screenBufferIndex] = '\n';
else
// the string below contains 16 characters, which is for all
// the possible combinations of values in the screenbuffer since
// it can be subject to flipping of the first 4 bits
screenBuffer[screenBufferIndex] = " '`-.|//,\\|\\_\\/#"[screenBuffer[screenBufferIndex]];
// ---------------------- the mappings --------------
// 0 maps into space
// 1 maps into ' 2 maps into ` 3 maps into -
// 4 maps into . 5 maps into | 6 maps into /
// 7 maps into / 8 maps into , 9 maps into \
// 10 maps into | 11 maps into \ 12 maps into _
// 13 maps into \ 14 maps into / 15 maps into #
}
// terminal escape code to put cursor back to the top left of the screen
puts("\x1b[1;1H");
// finally blit the screen buffer to screen
puts(screenBuffer);
// don't peg the cpu, be merciful, pause a little.
usleep(3000);
}
}